Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
48 changes: 3 additions & 45 deletions packages/create-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#! /usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { axeSetupBinding } from '@code-pushup/axe-plugin';
import { coverageSetupBinding } from '@code-pushup/coverage-plugin';
Expand All @@ -8,13 +7,8 @@ import { jsPackagesSetupBinding } from '@code-pushup/js-packages-plugin';
import { jsDocsSetupBinding } from '@code-pushup/jsdocs-plugin';
import { lighthouseSetupBinding } from '@code-pushup/lighthouse-plugin';
import { typescriptSetupBinding } from '@code-pushup/typescript-plugin';
import { parsePluginSlugs, validatePluginSlugs } from './lib/setup/plugins.js';
import {
CI_PROVIDERS,
CONFIG_FILE_FORMATS,
type PluginSetupBinding,
SETUP_MODES,
} from './lib/setup/types.js';
import { yargsCli } from './lib/setup/cli-args.js';
import type { PluginSetupBinding } from './lib/setup/types.js';
import { runSetupWizard } from './lib/setup/wizard.js';

const bindings: PluginSetupBinding[] = [
Expand All @@ -27,42 +21,6 @@ const bindings: PluginSetupBinding[] = [
jsDocsSetupBinding,
];

const argv = await yargs(hideBin(process.argv))
.option('dry-run', {
type: 'boolean',
default: false,
describe: 'Preview changes without writing files',
})
.option('yes', {
alias: 'y',
type: 'boolean',
default: false,
describe: 'Skip prompts and use defaults',
})
.option('config-format', {
type: 'string',
choices: CONFIG_FILE_FORMATS,
describe: 'Config file format (default: auto-detected from project)',
})
.option('plugins', {
type: 'string',
describe: 'Comma-separated plugin slugs to include (e.g. eslint,coverage)',
coerce: parsePluginSlugs,
})
.option('mode', {
type: 'string',
choices: SETUP_MODES,
describe: 'Setup mode (default: auto-detected from project)',
})
.option('ci', {
type: 'string',
choices: CI_PROVIDERS,
describe: 'CI/CD integration (github, gitlab, or none)',
})
.check(parsed => {
validatePluginSlugs(bindings, parsed.plugins);
return true;
})
.parse();
const argv = await yargsCli(bindings).parse(hideBin(process.argv));

await runSetupWizard(bindings, argv);
2 changes: 1 addition & 1 deletion packages/create-cli/src/lib/setup/ci.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { select } from '@inquirer/prompts';
import { vol } from 'memfs';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import { createTree } from '@code-pushup/utils';
import { promptCiProvider, resolveCi } from './ci.js';
import type { ConfigContext } from './types.js';
import { createTree } from './virtual-fs.js';

vi.mock('@inquirer/prompts', () => ({
select: vi.fn(),
Expand Down
53 changes: 53 additions & 0 deletions packages/create-cli/src/lib/setup/cli-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import yargs, { type Argv } from 'yargs';
import { parsePluginSlugs, validatePluginSlugs } from './plugins.js';
import {
CI_PROVIDERS,
CONFIG_FILE_FORMATS,
type PluginSetupBinding,
SETUP_MODES,
} from './types.js';

export function yargsCli(bindings: PluginSetupBinding[]): Argv {
return yargs()
.scriptName('create-cli')
.usage('$0 [options]')
.parserConfiguration({ 'dot-notation': false })
.option('dry-run', {
type: 'boolean',
default: false,
describe: 'Preview changes without writing files',
})
.option('yes', {
alias: 'y',
type: 'boolean',
default: false,
describe: 'Skip prompts and use defaults',
})
.option('config-format', {
type: 'string',
choices: CONFIG_FILE_FORMATS,
describe: 'Config file format (default: auto-detected from project)',
})
.option('plugins', {
type: 'string',
describe:
'Comma-separated plugin slugs to include (e.g. eslint,coverage)',
coerce: parsePluginSlugs,
})
.option('mode', {
type: 'string',
choices: SETUP_MODES,
describe: 'Setup mode (default: auto-detected from project)',
})
.option('ci', {
type: 'string',
choices: CI_PROVIDERS,
describe: 'CI/CD integration (github, gitlab, or none)',
})
.check(parsed => {
validatePluginSlugs(bindings, parsed.plugins);
return true;
})
.help()
.version();
}
34 changes: 34 additions & 0 deletions packages/create-cli/src/lib/setup/cli-args.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { yargsCli } from './cli-args.js';
import type { PluginSetupBinding } from './types.js';

const bareBindings: PluginSetupBinding[] = [
{
slug: 'eslint',
title: 'ESLint',
packageName: '@code-pushup/eslint-plugin',
generateConfig: () => ({ imports: [], pluginInit: [] }),
},
];

describe('yargsCli', () => {
it('should expose --eslint.patterns as a flat key', async () => {
const argv = await yargsCli(bareBindings).parse([
'--eslint.patterns',
'src',
]);

expect(argv['eslint.patterns']).toBe('src');
});

it('should expose --no-eslint.categories as a flat false', async () => {
const argv = await yargsCli(bareBindings).parse(['--no-eslint.categories']);

expect(argv['eslint.categories']).toBeFalse();
});

it('should expose --eslint.categories without a value as a flat true', async () => {
const argv = await yargsCli(bareBindings).parse(['--eslint.categories']);

expect(argv['eslint.categories']).toBeTrue();
});
});
2 changes: 1 addition & 1 deletion packages/create-cli/src/lib/setup/gitignore.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { vol } from 'memfs';
import { readFile } from 'node:fs/promises';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import { createTree } from '@code-pushup/utils';
import { resolveGitignore } from './gitignore.js';
import { createTree } from './virtual-fs.js';

describe('resolveGitignore', () => {
it('should create .gitignore with comment when it does not exist', async () => {
Expand Down
3 changes: 1 addition & 2 deletions packages/create-cli/src/lib/setup/monorepo.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { select } from '@inquirer/prompts';
import { vol } from 'memfs';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import { logger } from '@code-pushup/utils';
import { createTree, logger } from '@code-pushup/utils';
import { addCodePushUpCommand, promptSetupMode } from './monorepo.js';
import type { WizardProject } from './types.js';
import { createTree } from './virtual-fs.js';

vi.mock('@inquirer/prompts', () => ({
select: vi.fn(),
Expand Down
33 changes: 4 additions & 29 deletions packages/create-cli/src/lib/setup/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import type { PluginCodegenResult } from '@code-pushup/models';
import type { MonorepoTool } from '@code-pushup/utils';
import type { MonorepoTool, Tree } from '@code-pushup/utils';

export type {
CategoryCodegenConfig,
ImportDeclarationStructure,
PluginAnswer,
PluginCodegenInput,
PluginCodegenResult,
PluginPromptDescriptor,
PluginSetupBinding,
PluginSetupTree,
} from '@code-pushup/models';

export type { FileChange, FileSystemAdapter, Tree } from '@code-pushup/utils';

export const CI_PROVIDERS = ['github', 'gitlab', 'none'] as const;
export type CiProvider = (typeof CI_PROVIDERS)[number];

Expand Down Expand Up @@ -58,31 +61,3 @@ export type WriteContext = {
configFilename: string;
isEsm: boolean;
};

/** A single file operation recorded by the virtual tree. */
export type FileChange = {
path: string;
type: 'CREATE' | 'UPDATE';
content: string;
};

/** Virtual file system that buffers writes in memory until flushed to disk. */
export type Tree = {
root: string;
exists: (filePath: string) => Promise<boolean>;
read: (filePath: string) => Promise<string | null>;
write: (filePath: string, content: string) => Promise<void>;
listChanges: () => FileChange[];
flush: () => Promise<void>;
};

/** Abstraction over `node:fs` used by the virtual tree for disk I/O. */
export type FileSystemAdapter = {
readFile: (path: string, encoding: 'utf8') => Promise<string>;
writeFile: (path: string, content: string) => Promise<void>;
exists: (path: string) => Promise<boolean>;
mkdir: (
path: string,
options: { recursive: true },
) => Promise<string | undefined>;
};
67 changes: 0 additions & 67 deletions packages/create-cli/src/lib/setup/virtual-fs.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/create-cli/src/lib/setup/wizard.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const TEST_BINDINGS: PluginSetupBinding[] = [
default: 'alpha.config.js',
},
],
generateConfig(answers) {
generateConfig({ answers }) {
const configPath = answers['alpha.path'] ?? 'alpha.config.js';
return {
imports: [
Expand Down
11 changes: 8 additions & 3 deletions packages/create-cli/src/lib/setup/wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'node:path';
import {
type MonorepoTool,
asyncSequential,
createTree,
formatAsciiTable,
getGitRoot,
logger,
Expand Down Expand Up @@ -35,7 +36,6 @@ import type {
Tree,
WriteContext,
} from './types.js';
import { createTree } from './virtual-fs.js';

/**
* Runs the interactive setup wizard that generates a Code PushUp config file.
Expand Down Expand Up @@ -93,7 +93,12 @@ async function resolveBinding(
tree: Pick<Tree, 'read' | 'write'>,
): Promise<PluginCodegenResult> {
if (!binding.prompts) {
return binding.generateConfig({}, tree);
return binding.generateConfig({
tree,
targetDir,
cliArgs,
answers: {},
});
}
logger.newline();
logger.info(ansis.bold(binding.title));
Expand All @@ -102,7 +107,7 @@ async function resolveBinding(
descriptors.length > 0
? await promptPluginOptions(descriptors, cliArgs)
: {};
return binding.generateConfig(answers, tree);
return binding.generateConfig({ tree, targetDir, cliArgs, answers });
}

async function writeStandaloneConfig(
Expand Down
1 change: 1 addition & 0 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export type {
CategoryCodegenConfig,
ImportDeclarationStructure,
PluginAnswer,
PluginCodegenInput,
PluginCodegenResult,
PluginDeclarationStructure,
PluginPromptDescriptor,
Expand Down
10 changes: 8 additions & 2 deletions packages/models/src/lib/plugin-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ export type PluginSetupTree = {
write: (path: string, content: string) => Promise<void>;
};

export type PluginCodegenInput = {
answers: Record<string, PluginAnswer>;
tree: PluginSetupTree;
targetDir: string;
cliArgs: Record<string, unknown>;
};

/**
* Defines how a plugin integrates with the setup wizard.
*
Expand All @@ -88,7 +95,6 @@ export type PluginSetupBinding = {
prompts?: (targetDir: string) => Promise<PluginPromptDescriptor[]>;
isRecommended?: (targetDir: string) => Promise<boolean>;
generateConfig: (
answers: Record<string, PluginAnswer>,
tree: PluginSetupTree,
input: PluginCodegenInput,
) => PluginCodegenResult | Promise<PluginCodegenResult>;
};
Loading
Loading