🐛Fix: prompt-module/@commitlint (#336)
* fix(commitlint/utils.ts): correct variable used in search for JSON block end tag * ♻️ (commitlint/config.ts & pwd-commitlint.ts): Refactor commitlint config loading to support both CJS and ESM modules 💡 (pwd-commitlint.ts): Add detailed comments and error handling for better clarity and robustness in commitlint module loading process * ✨ (package.json): Add setup script for e2e tests to install dependencies for commitlint configurations 🔧 (setup.sh): Add shell script to set up commitlint configurations for e2e tests * ✨ (config.ts): Add support for OCO_TEST_MOCK_TYPE configuration key to define test mock type for testing purposes 📝 (config.ts): Update documentation for OCO_TEST_MOCK_TYPE configuration key in configValidators and getConfig functions 📝 (testAi.ts): Add TEST_MOCK_TYPES constant array to define supported test mock types 📝 (testAi.ts): Update generateCommitMessage function to use OCO_TEST_MOCK_TYPE from config for different test mock types 📝 (commitlint.test.ts): Add e2e test for running "oco commitlint force" with different @commitlint versions using CJS and ESM 📝 (utils.ts): Add wait function to introduce delay in milliseconds for testing purposes * ✨ (commitlint.test.ts): refactor setupCommitlint function to accept a version parameter for better code organization and readability 📝 (commitlint.test.ts): add test case for commitlint@9 using CJS to ensure proper functionality and compatibility 📝 (commitlint.test.ts): add test case for commitlint@18 using CJS to ensure proper functionality and compatibility 📝 (commitlint.test.ts): add test case for commitlint@19 using ESM to ensure proper functionality and compatibility * 🔧 (commitlint.test.ts): remove unnecessary commands to create and add index.ts file before running tests * refactor(test/e2e/prompt-module/commitlint.test.ts): remove unused import configure style(test/e2e/prompt-module/commitlint.test.ts): add missing semicolon for consistency test(test/e2e/prompt-module/commitlint.test.ts): add e2e tests for @commitlint prompt-module integration * ✨ (e2e tests): add package.json copying to setupCommitlint for version accuracy ♻️ (commitlint config): refactor commitlint.config.js to use ES module syntax ✨ (package.json): specify "type": "module" to support ES module syntax
This commit is contained in:
committed by
GitHub
parent
c58e0c62a4
commit
73ccae9de3
+2
-1
@@ -54,7 +54,8 @@
|
||||
"test:docker-build": "docker build -t oco-test -f test/Dockerfile .",
|
||||
"test:unit": "NODE_OPTIONS=--experimental-vm-modules jest test/unit",
|
||||
"test:unit:docker": "npm run test:docker-build && DOCKER_CONTENT_TRUST=0 docker run --rm oco-test npm run test:unit",
|
||||
"test:e2e": "jest test/e2e",
|
||||
"test:e2e": "npm run test:e2e:setup && jest test/e2e",
|
||||
"test:e2e:setup": "sh test/e2e/setup.sh",
|
||||
"test:e2e:docker": "npm run test:docker-build && DOCKER_CONTENT_TRUST=0 docker run --rm oco-test npm run test:e2e"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+12
-1
@@ -10,6 +10,7 @@ import { intro, outro } from '@clack/prompts';
|
||||
|
||||
import { COMMANDS } from '../CommandsEnum';
|
||||
import { getI18nLocal } from '../i18n';
|
||||
import { TEST_MOCK_TYPES } from '../engine/testAi';
|
||||
|
||||
export enum CONFIG_KEYS {
|
||||
OCO_OPENAI_API_KEY = 'OCO_OPENAI_API_KEY',
|
||||
@@ -29,7 +30,8 @@ export enum CONFIG_KEYS {
|
||||
OCO_AI_PROVIDER = 'OCO_AI_PROVIDER',
|
||||
OCO_GITPUSH = 'OCO_GITPUSH',
|
||||
OCO_ONE_LINE_COMMIT = 'OCO_ONE_LINE_COMMIT',
|
||||
OCO_AZURE_ENDPOINT = 'OCO_AZURE_ENDPOINT'
|
||||
OCO_AZURE_ENDPOINT = 'OCO_AZURE_ENDPOINT',
|
||||
OCO_TEST_MOCK_TYPE = 'OCO_TEST_MOCK_TYPE',
|
||||
}
|
||||
|
||||
export enum CONFIG_MODES {
|
||||
@@ -309,6 +311,14 @@ export const configValidators = {
|
||||
|
||||
return value;
|
||||
},
|
||||
[CONFIG_KEYS.OCO_TEST_MOCK_TYPE](value: any) {
|
||||
validateConfig(
|
||||
CONFIG_KEYS.OCO_TEST_MOCK_TYPE,
|
||||
TEST_MOCK_TYPES.includes(value),
|
||||
`${value} is not supported yet, use ${TEST_MOCK_TYPES.map(t => `'${t}'`).join(', ')}`
|
||||
);
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
export type ConfigType = {
|
||||
@@ -351,6 +361,7 @@ export const getConfig = ({
|
||||
OCO_ONE_LINE_COMMIT:
|
||||
process.env.OCO_ONE_LINE_COMMIT === 'true' ? true : false,
|
||||
OCO_AZURE_ENDPOINT: process.env.OCO_AZURE_ENDPOINT || '',
|
||||
OCO_TEST_MOCK_TYPE: process.env.OCO_TEST_MOCK_TYPE || 'commit-message'
|
||||
};
|
||||
|
||||
const configExists = existsSync(configPath);
|
||||
|
||||
+22
-2
@@ -1,11 +1,31 @@
|
||||
import { ChatCompletionRequestMessage } from 'openai';
|
||||
import { AiEngine } from './Engine';
|
||||
import { getConfig } from '../commands/config';
|
||||
|
||||
export const TEST_MOCK_TYPES = [
|
||||
'commit-message',
|
||||
'prompt-module-commitlint-config',
|
||||
] as const
|
||||
type TestMockType = typeof TEST_MOCK_TYPES[number];
|
||||
|
||||
export class TestAi implements AiEngine {
|
||||
async generateCommitMessage(
|
||||
messages: Array<ChatCompletionRequestMessage>
|
||||
_messages: Array<ChatCompletionRequestMessage>
|
||||
): Promise<string | undefined> {
|
||||
return 'test commit message';
|
||||
const config = getConfig();
|
||||
switch (config?.OCO_TEST_MOCK_TYPE as TestMockType | undefined) {
|
||||
case 'commit-message':
|
||||
return 'fix(testAi.ts): test commit message';
|
||||
case 'prompt-module-commitlint-config':
|
||||
return `{\n` +
|
||||
` "localLanguage": "english",\n` +
|
||||
` "commitFix": "fix(server): Change 'port' variable to uppercase 'PORT'",\n` +
|
||||
` "commitFeat": "feat(server): Allow server to listen on a port specified through environment variable",\n` +
|
||||
` "commitDescription": "Change 'port' variable to uppercase 'PORT'. Allow server to listen on a port specified through environment variable."\n` +
|
||||
`}`
|
||||
default:
|
||||
throw Error('unsupported test mock type')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,16 @@ export const configureCommitlintIntegration = async (force = false) => {
|
||||
|
||||
const fileExists = await utils.commitlintLLMConfigExists();
|
||||
|
||||
let commitLintConfig = await getCommitLintPWDConfig();
|
||||
const commitLintConfig = await getCommitLintPWDConfig();
|
||||
if (commitLintConfig === null) {
|
||||
throw new Error(
|
||||
`Failed to load @commitlint config. Please check the following:
|
||||
* @commitlint >= 9.0.0 is installed in the local directory.
|
||||
* 'node_modules/@commitlint/load' package exists.
|
||||
* A valid @commitlint configuration exists.
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
// debug complete @commitlint configuration
|
||||
// await fs.writeFile(
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
const nodeModulesPath = path.join(
|
||||
process.env.PWD || process.cwd(),
|
||||
'node_modules',
|
||||
'@commitlint',
|
||||
'load'
|
||||
);
|
||||
const getCommitLintModuleType = async (): Promise<'cjs' | 'esm'> => {
|
||||
const packageFile = 'node_modules/@commitlint/load/package.json';
|
||||
const packageJsonPath = path.join(
|
||||
process.env.PWD || process.cwd(),
|
||||
packageFile,
|
||||
);
|
||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||
if (!packageJson) {
|
||||
throw new Error(`Failed to parse ${packageFile}`);
|
||||
}
|
||||
|
||||
return packageJson.type === 'module' ? 'esm' : 'cjs';
|
||||
};
|
||||
|
||||
/**
|
||||
* QualifiedConfig from any version of @commitlint/types
|
||||
* @see https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/types/src/load.ts
|
||||
*/
|
||||
type QualifiedConfigOnAnyVersion = { [key:string]: unknown };
|
||||
|
||||
/**
|
||||
* This code is loading the configuration for the `@commitlint` package from the current working
|
||||
@@ -13,8 +27,31 @@ const nodeModulesPath = path.join(
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export const getCommitLintPWDConfig = async () => {
|
||||
const load = require(nodeModulesPath).default;
|
||||
export const getCommitLintPWDConfig = async (): Promise<QualifiedConfigOnAnyVersion | null> => {
|
||||
let load, nodeModulesPath;
|
||||
switch (await getCommitLintModuleType()) {
|
||||
case 'cjs':
|
||||
/**
|
||||
* CommonJS (<= commitlint@v18.x.x.)
|
||||
*/
|
||||
nodeModulesPath = path.join(
|
||||
process.env.PWD || process.cwd(),
|
||||
'node_modules/@commitlint/load',
|
||||
);
|
||||
load = require(nodeModulesPath).default;
|
||||
break;
|
||||
case 'esm':
|
||||
/**
|
||||
* ES Module (commitlint@v19.x.x. <= )
|
||||
* Directory import is not supported in ES Module resolution, so import the file directly
|
||||
*/
|
||||
nodeModulesPath = path.join(
|
||||
process.env.PWD || process.cwd(),
|
||||
'node_modules/@commitlint/load/lib/load.js',
|
||||
);
|
||||
load = (await import(nodeModulesPath)).default;
|
||||
break;
|
||||
}
|
||||
|
||||
if (load && typeof load === 'function') {
|
||||
return await load();
|
||||
|
||||
@@ -20,8 +20,8 @@ export const getJSONBlock = (input: string): string => {
|
||||
const jsonIndex = input.search('```json');
|
||||
if (jsonIndex > -1) {
|
||||
input = input.slice(jsonIndex + 8);
|
||||
const endJsonIndex = consistency.search('```');
|
||||
input = input.slice(0, endJsonIndex);
|
||||
const endJsonIndex = input.search('```');
|
||||
input = input.slice(0, endJsonIndex);
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
import { resolve } from 'path';
|
||||
import { render } from 'cli-testing-library';
|
||||
import 'cli-testing-library/extend-expect';
|
||||
import { prepareEnvironment, wait } from '../utils';
|
||||
import path from 'path';
|
||||
|
||||
function getAbsolutePath(relativePath: string) {
|
||||
const scriptDir = path.dirname(__filename);
|
||||
return path.resolve(scriptDir, relativePath);
|
||||
}
|
||||
async function setupCommitlint(dir: string, ver: 9 | 18 | 19) {
|
||||
let packagePath, packageJsonPath, configPath;
|
||||
switch (ver) {
|
||||
case 9:
|
||||
packagePath = getAbsolutePath('./data/commitlint_9/node_modules');
|
||||
packageJsonPath = getAbsolutePath('./data/commitlint_9/package.json');
|
||||
configPath = getAbsolutePath('./data/commitlint_9/commitlint.config.js');
|
||||
break;
|
||||
case 18:
|
||||
packagePath = getAbsolutePath('./data/commitlint_18/node_modules');
|
||||
packageJsonPath = getAbsolutePath('./data/commitlint_18/package.json');
|
||||
configPath = getAbsolutePath('./data/commitlint_18/commitlint.config.js');
|
||||
break;
|
||||
case 19:
|
||||
packagePath = getAbsolutePath('./data/commitlint_19/node_modules');
|
||||
packageJsonPath = getAbsolutePath('./data/commitlint_19/package.json');
|
||||
configPath = getAbsolutePath('./data/commitlint_19/commitlint.config.js');
|
||||
break;
|
||||
}
|
||||
await render('cp', ['-r', packagePath, '.'], { cwd: dir });
|
||||
await render('cp', [packageJsonPath, '.'], { cwd: dir });
|
||||
await render('cp', [configPath, '.'], { cwd: dir });
|
||||
await wait(3000); // Avoid flakiness by waiting
|
||||
}
|
||||
|
||||
describe('cli flow to run "oco commitlint force"', () => {
|
||||
it('on commitlint@9 using CJS', async () => {
|
||||
const { gitDir, cleanup } = await prepareEnvironment();
|
||||
|
||||
await setupCommitlint(gitDir, 9);
|
||||
const npmList = await render('npm', ['list', '@commitlint/load'], {
|
||||
cwd: gitDir
|
||||
});
|
||||
expect(await npmList.findByText('@commitlint/load@9')).toBeInTheConsole();
|
||||
|
||||
const { findByText } = await render(
|
||||
`
|
||||
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
|
||||
OCO_PROMPT_MODULE='@commitlint' \
|
||||
OCO_AI_PROVIDER='test' \
|
||||
node ${resolve('./out/cli.cjs')} commitlint force \
|
||||
`,
|
||||
[],
|
||||
{ cwd: gitDir }
|
||||
);
|
||||
|
||||
expect(
|
||||
await findByText('opencommit — configure @commitlint')
|
||||
).toBeInTheConsole();
|
||||
expect(
|
||||
await findByText('Read @commitlint configuration')
|
||||
).toBeInTheConsole();
|
||||
|
||||
expect(
|
||||
await findByText('Generating consistency with given @commitlint rules')
|
||||
).toBeInTheConsole();
|
||||
expect(
|
||||
await findByText('Done - please review contents of')
|
||||
).toBeInTheConsole();
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
it('on commitlint@18 using CJS', async () => {
|
||||
const { gitDir, cleanup } = await prepareEnvironment();
|
||||
|
||||
await setupCommitlint(gitDir, 18);
|
||||
const npmList = await render('npm', ['list', '@commitlint/load'], {
|
||||
cwd: gitDir
|
||||
});
|
||||
expect(await npmList.findByText('@commitlint/load@18')).toBeInTheConsole();
|
||||
|
||||
const { findByText } = await render(
|
||||
`
|
||||
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
|
||||
OCO_PROMPT_MODULE='@commitlint' \
|
||||
OCO_AI_PROVIDER='test' \
|
||||
node ${resolve('./out/cli.cjs')} commitlint force \
|
||||
`,
|
||||
[],
|
||||
{ cwd: gitDir }
|
||||
);
|
||||
|
||||
expect(
|
||||
await findByText('opencommit — configure @commitlint')
|
||||
).toBeInTheConsole();
|
||||
expect(
|
||||
await findByText('Read @commitlint configuration')
|
||||
).toBeInTheConsole();
|
||||
|
||||
expect(
|
||||
await findByText('Generating consistency with given @commitlint rules')
|
||||
).toBeInTheConsole();
|
||||
expect(
|
||||
await findByText('Done - please review contents of')
|
||||
).toBeInTheConsole();
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
it('on commitlint@19 using ESM', async () => {
|
||||
const { gitDir, cleanup } = await prepareEnvironment();
|
||||
|
||||
await setupCommitlint(gitDir, 19);
|
||||
const npmList = await render('npm', ['list', '@commitlint/load'], {
|
||||
cwd: gitDir
|
||||
});
|
||||
expect(await npmList.findByText('@commitlint/load@19')).toBeInTheConsole();
|
||||
|
||||
const { findByText } = await render(
|
||||
`
|
||||
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
|
||||
OCO_PROMPT_MODULE='@commitlint' \
|
||||
OCO_AI_PROVIDER='test' \
|
||||
node ${resolve('./out/cli.cjs')} commitlint force \
|
||||
`,
|
||||
[],
|
||||
{ cwd: gitDir }
|
||||
);
|
||||
|
||||
expect(
|
||||
await findByText('opencommit — configure @commitlint')
|
||||
).toBeInTheConsole();
|
||||
expect(
|
||||
await findByText('Read @commitlint configuration')
|
||||
).toBeInTheConsole();
|
||||
|
||||
expect(
|
||||
await findByText('Generating consistency with given @commitlint rules')
|
||||
).toBeInTheConsole();
|
||||
expect(
|
||||
await findByText('Done - please review contents of')
|
||||
).toBeInTheConsole();
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli flow to generate commit message using @commitlint prompt-module', () => {
|
||||
it('on commitlint@19 using ESM', async () => {
|
||||
const { gitDir, cleanup } = await prepareEnvironment();
|
||||
|
||||
// Setup commitlint@19
|
||||
await setupCommitlint(gitDir, 19);
|
||||
const npmList = await render('npm', ['list', '@commitlint/load'], {
|
||||
cwd: gitDir
|
||||
});
|
||||
expect(await npmList.findByText('@commitlint/load@19')).toBeInTheConsole();
|
||||
|
||||
// Run `oco commitlint force`
|
||||
const commitlintForce = await render(
|
||||
`
|
||||
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
|
||||
OCO_PROMPT_MODULE='@commitlint' \
|
||||
OCO_AI_PROVIDER='test' \
|
||||
node ${resolve('./out/cli.cjs')} commitlint force \
|
||||
`,
|
||||
[],
|
||||
{ cwd: gitDir }
|
||||
);
|
||||
expect(
|
||||
await commitlintForce.findByText('Done - please review contents of')
|
||||
).toBeInTheConsole();
|
||||
|
||||
// Run `oco commitlint get`
|
||||
const commitlintGet = await render(
|
||||
`
|
||||
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
|
||||
OCO_PROMPT_MODULE='@commitlint' \
|
||||
OCO_AI_PROVIDER='test' \
|
||||
node ${resolve('./out/cli.cjs')} commitlint get \
|
||||
`,
|
||||
[],
|
||||
{ cwd: gitDir }
|
||||
);
|
||||
expect(
|
||||
await commitlintGet.findByText('[object Object]')
|
||||
).toBeInTheConsole();
|
||||
|
||||
// Run 'oco' using .opencommit-commitlint
|
||||
await render('echo', [`'console.log("Hello World");' > index.ts`], {
|
||||
cwd: gitDir
|
||||
});
|
||||
await render('git', ['add index.ts'], { cwd: gitDir });
|
||||
|
||||
const oco = await render(
|
||||
`
|
||||
OCO_TEST_MOCK_TYPE='commit-message' \
|
||||
OCO_PROMPT_MODULE='@commitlint' \
|
||||
OCO_AI_PROVIDER='test' \
|
||||
node ${resolve('./out/cli.cjs')} \
|
||||
`,
|
||||
[],
|
||||
{ cwd: gitDir }
|
||||
);
|
||||
|
||||
expect(
|
||||
await oco.findByText('Generating the commit message')
|
||||
).toBeInTheConsole();
|
||||
expect(
|
||||
await oco.findByText('Confirm the commit message?')
|
||||
).toBeInTheConsole();
|
||||
oco.userEvent.keyboard('[Enter]');
|
||||
|
||||
expect(
|
||||
await oco.findByText('Choose a remote to push to')
|
||||
).toBeInTheConsole();
|
||||
oco.userEvent.keyboard('[Enter]');
|
||||
|
||||
expect(
|
||||
await oco.findByText('Successfully pushed all commits to origin')
|
||||
).toBeInTheConsole();
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional']
|
||||
};
|
||||
+2029
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "commitlint-test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^18.0.0",
|
||||
"@commitlint/config-conventional": "^18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
extends: ['@commitlint/config-conventional']
|
||||
};
|
||||
+1453
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "commitlint-test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.0.0",
|
||||
"@commitlint/config-conventional": "^19.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional']
|
||||
};
|
||||
+1671
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "commitlint-test",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^9.0.0",
|
||||
"@commitlint/config-conventional": "^9.0.0"
|
||||
}
|
||||
}
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
current_dir=$(pwd)
|
||||
setup_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# Set up for prompt-module/commitlint
|
||||
cd $setup_dir && cd prompt-module/data/commitlint_9 && npm ci
|
||||
cd $setup_dir && cd prompt-module/data/commitlint_18 && npm ci
|
||||
cd $setup_dir && cd prompt-module/data/commitlint_19 && npm ci
|
||||
|
||||
cd $current_dir
|
||||
@@ -29,3 +29,5 @@ export const prepareEnvironment = async (): Promise<{
|
||||
cleanup,
|
||||
}
|
||||
}
|
||||
|
||||
export const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
Reference in New Issue
Block a user