Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/create-cli-e2e/tests/init.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { executeProcess, readJsonFile, readTextFile } from '@code-pushup/utils';
const fakeCacheFolderName = () =>
`fake-cache-${new Date().toISOString().replace(/[:.]/g, '-')}`;

// TODO: #1240 — rewrite e2e tests for the new setup wizard (old tests reference removed nx-plugin integration)
// TODO: rewrite e2e tests for the new setup wizard (old tests reference removed nx-plugin integration)
/* after a new release of the nx-verdaccio plugin we can enable the test again. For now, it is too flaky to be productive. (5.jan.2025) */
describe.todo('create-cli-init', () => {
const workspaceRoot = path.join(E2E_ENVIRONMENTS_DIR, nxTargetProject());
Expand Down
5 changes: 5 additions & 0 deletions packages/create-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ const argv = await yargs(hideBin(process.argv))
default: false,
describe: 'Skip prompts and use defaults',
})
.option('config-format', {
type: 'string',
choices: ['ts', 'js', 'mjs'],
describe: 'Config file format (default: auto-detected from project)',
})
.parse();

// TODO: #1244 — provide plugin bindings from registry
Expand Down
55 changes: 49 additions & 6 deletions packages/create-cli/src/lib/setup/codegen.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
ConfigFileFormat,
ImportDeclarationStructure,
PluginCodegenResult,
} from './types.js';
Expand Down Expand Up @@ -44,7 +45,7 @@ function formatImport({
return `import ${type}${from}'${moduleSpecifier}';`;
}

function collectImports(
function collectTsImports(
plugins: PluginCodegenResult[],
): ImportDeclarationStructure[] {
return [
Expand All @@ -53,12 +54,19 @@ function collectImports(
].toSorted((a, b) => a.moduleSpecifier.localeCompare(b.moduleSpecifier));
}

export function generateConfigSource(plugins: PluginCodegenResult[]): string {
const builder = new CodeBuilder();
function collectJsImports(
plugins: PluginCodegenResult[],
): ImportDeclarationStructure[] {
return plugins
.flatMap(({ imports }) => imports)
.map(({ isTypeOnly: _, ...rest }) => rest)
.toSorted((a, b) => a.moduleSpecifier.localeCompare(b.moduleSpecifier));
}

builder.addLines(collectImports(plugins).map(formatImport));
builder.addEmptyLine();
builder.addLine('export default {');
function addPlugins(
builder: CodeBuilder,
plugins: PluginCodegenResult[],
): void {
if (plugins.length === 0) {
builder.addLine('plugins: [],', 1);
} else {
Expand All @@ -69,7 +77,42 @@ export function generateConfigSource(plugins: PluginCodegenResult[]): string {
);
builder.addLine('],', 1);
}
}

export function generateConfigSource(
plugins: PluginCodegenResult[],
format: ConfigFileFormat,
): string {
return format === 'ts'
? generateTsConfig(plugins)
: generateJsConfig(plugins);
}

function generateTsConfig(plugins: PluginCodegenResult[]): string {
const builder = new CodeBuilder();

builder.addLines(collectTsImports(plugins).map(formatImport));
builder.addEmptyLine();
builder.addLine('export default {');
addPlugins(builder, plugins);
builder.addLine('} satisfies CoreConfig;');

return builder.toString();
}

function generateJsConfig(plugins: PluginCodegenResult[]): string {
const builder = new CodeBuilder();

const pluginImports = collectJsImports(plugins);
if (pluginImports.length > 0) {
builder.addLines(pluginImports.map(formatImport));
builder.addEmptyLine();
}

builder.addLine("/** @type {import('@code-pushup/models').CoreConfig} */");
builder.addLine('export default {');
addPlugins(builder, plugins);
builder.addLine('};');

return builder.toString();
}
244 changes: 163 additions & 81 deletions packages/create-cli/src/lib/setup/codegen.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,102 +2,184 @@ import { generateConfigSource } from './codegen.js';
import type { PluginCodegenResult } from './types.js';

describe('generateConfigSource', () => {
it('should generate config with empty plugins array', () => {
expect(generateConfigSource([])).toMatchInlineSnapshot(`
"import type { CoreConfig } from '@code-pushup/models';

export default {
plugins: [],
} satisfies CoreConfig;
"
`);
});
describe('TypeScript format', () => {
it('should generate config with empty plugins array', () => {
expect(generateConfigSource([], 'ts')).toMatchInlineSnapshot(`
"import type { CoreConfig } from '@code-pushup/models';

it('should generate config with a single plugin', () => {
const plugin: PluginCodegenResult = {
imports: [
{
moduleSpecifier: '@code-pushup/eslint-plugin',
defaultImport: 'eslintPlugin',
},
],
pluginInit: 'await eslintPlugin()',
};
export default {
plugins: [],
} satisfies CoreConfig;
"
`);
});

it('should generate config with a single plugin', () => {
const plugin: PluginCodegenResult = {
imports: [
{
moduleSpecifier: '@code-pushup/eslint-plugin',
defaultImport: 'eslintPlugin',
},
],
pluginInit: 'await eslintPlugin()',
};

expect(generateConfigSource([plugin])).toMatchInlineSnapshot(`
"import eslintPlugin from '@code-pushup/eslint-plugin';
import type { CoreConfig } from '@code-pushup/models';
expect(generateConfigSource([plugin], 'ts')).toMatchInlineSnapshot(`
"import eslintPlugin from '@code-pushup/eslint-plugin';
import type { CoreConfig } from '@code-pushup/models';

export default {
plugins: [
await eslintPlugin(),
export default {
plugins: [
await eslintPlugin(),
],
} satisfies CoreConfig;
"
`);
});

it('should generate config with combined default and named imports', () => {
const plugin: PluginCodegenResult = {
imports: [
{
moduleSpecifier: '@code-pushup/eslint-plugin',
defaultImport: 'eslintPlugin',
namedImports: ['eslintConfigFromAllNxProjects'],
},
],
} satisfies CoreConfig;
"
`);
});
pluginInit:
'await eslintPlugin({ eslintrc: eslintConfigFromAllNxProjects() })',
};

expect(generateConfigSource([plugin], 'ts')).toMatchInlineSnapshot(`
"import eslintPlugin, { eslintConfigFromAllNxProjects } from '@code-pushup/eslint-plugin';
import type { CoreConfig } from '@code-pushup/models';

it('should generate config with combined default and named imports', () => {
const plugin: PluginCodegenResult = {
imports: [
export default {
plugins: [
await eslintPlugin({ eslintrc: eslintConfigFromAllNxProjects() }),
],
} satisfies CoreConfig;
"
`);
});

it('should generate config with multiple plugins', () => {
const plugins: PluginCodegenResult[] = [
{
moduleSpecifier: '@code-pushup/eslint-plugin',
defaultImport: 'eslintPlugin',
namedImports: ['eslintConfigFromAllNxProjects'],
imports: [
{
moduleSpecifier: '@code-pushup/eslint-plugin',
defaultImport: 'eslintPlugin',
},
],
pluginInit: 'await eslintPlugin()',
},
],
pluginInit:
'await eslintPlugin({ eslintrc: eslintConfigFromAllNxProjects() })',
};

expect(generateConfigSource([plugin])).toMatchInlineSnapshot(`
"import eslintPlugin, { eslintConfigFromAllNxProjects } from '@code-pushup/eslint-plugin';
import type { CoreConfig } from '@code-pushup/models';

export default {
plugins: [
await eslintPlugin({ eslintrc: eslintConfigFromAllNxProjects() }),
],
} satisfies CoreConfig;
"
`);
{
imports: [
{
moduleSpecifier: '@code-pushup/coverage-plugin',
defaultImport: 'coveragePlugin',
},
],
pluginInit:
"await coveragePlugin({ reports: [{ resultsPath: 'coverage/lcov.info', pathToProject: '' }] })",
},
];

expect(generateConfigSource(plugins, 'ts')).toMatchInlineSnapshot(`
"import coveragePlugin from '@code-pushup/coverage-plugin';
import eslintPlugin from '@code-pushup/eslint-plugin';
import type { CoreConfig } from '@code-pushup/models';

export default {
plugins: [
await eslintPlugin(),
await coveragePlugin({ reports: [{ resultsPath: 'coverage/lcov.info', pathToProject: '' }] }),
],
} satisfies CoreConfig;
"
`);
});
});

it('should generate config with multiple plugins', () => {
const plugins: PluginCodegenResult[] = [
{
describe('JavaScript format', () => {
it('should generate JS config with empty plugins array', () => {
expect(generateConfigSource([], 'js')).toMatchInlineSnapshot(`
"/** @type {import('@code-pushup/models').CoreConfig} */
export default {
plugins: [],
};
"
`);
});

it('should generate JS config with a single plugin', () => {
const plugin: PluginCodegenResult = {
imports: [
{
moduleSpecifier: '@code-pushup/eslint-plugin',
defaultImport: 'eslintPlugin',
},
],
pluginInit: 'await eslintPlugin()',
},
{
imports: [
{
moduleSpecifier: '@code-pushup/coverage-plugin',
defaultImport: 'coveragePlugin',
},
],
pluginInit:
"await coveragePlugin({ reports: [{ resultsPath: 'coverage/lcov.info', pathToProject: '' }] })",
},
];

expect(generateConfigSource(plugins)).toMatchInlineSnapshot(`
"import coveragePlugin from '@code-pushup/coverage-plugin';
import eslintPlugin from '@code-pushup/eslint-plugin';
import type { CoreConfig } from '@code-pushup/models';

export default {
plugins: [
await eslintPlugin(),
await coveragePlugin({ reports: [{ resultsPath: 'coverage/lcov.info', pathToProject: '' }] }),
],
} satisfies CoreConfig;
"
`);
};

expect(generateConfigSource([plugin], 'js')).toMatchInlineSnapshot(`
"import eslintPlugin from '@code-pushup/eslint-plugin';

/** @type {import('@code-pushup/models').CoreConfig} */
export default {
plugins: [
await eslintPlugin(),
],
};
"
`);
});

it('should generate JS config with multiple plugins', () => {
const plugins: PluginCodegenResult[] = [
{
imports: [
{
moduleSpecifier: '@code-pushup/eslint-plugin',
defaultImport: 'eslintPlugin',
},
],
pluginInit: 'await eslintPlugin()',
},
{
imports: [
{
moduleSpecifier: '@code-pushup/coverage-plugin',
defaultImport: 'coveragePlugin',
},
],
pluginInit:
"await coveragePlugin({ reports: [{ resultsPath: 'coverage/lcov.info', pathToProject: '' }] })",
},
];

expect(generateConfigSource(plugins, 'js')).toMatchInlineSnapshot(`
"import coveragePlugin from '@code-pushup/coverage-plugin';
import eslintPlugin from '@code-pushup/eslint-plugin';

/** @type {import('@code-pushup/models').CoreConfig} */
export default {
plugins: [
await eslintPlugin(),
await coveragePlugin({ reports: [{ resultsPath: 'coverage/lcov.info', pathToProject: '' }] }),
],
};
"
`);
});

it('should treat mjs format identically to js format', () => {
expect(generateConfigSource([], 'mjs')).toBe(
generateConfigSource([], 'js'),
);
});
});
});
Loading