Skip to content

Commit cb13678

Browse files
committed
refactor(create-cli): group plugin prompts
1 parent a81815f commit cb13678

File tree

10 files changed

+137
-111
lines changed

10 files changed

+137
-111
lines changed

packages/create-cli/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Each plugin exposes its own configuration keys that can be passed as CLI argumen
3535
| ------------------------- | --------- | ------------- | --------------------- |
3636
| **`--eslint.eslintrc`** | `string` | auto-detected | Path to ESLint config |
3737
| **`--eslint.patterns`** | `string` | `src` or `.` | File patterns to lint |
38-
| **`--eslint.categories`** | `boolean` | `true` | Add ESLint categories |
38+
| **`--eslint.categories`** | `boolean` | `true` | Add categories |
3939

4040
#### Coverage
4141

@@ -47,37 +47,37 @@ Each plugin exposes its own configuration keys that can be passed as CLI argumen
4747
| **`--coverage.testCommand`** | `string` | auto-detected | Command to run tests |
4848
| **`--coverage.types`** | `('function'` \| `'branch'` \| `'line')[]` | all | Coverage types to measure |
4949
| **`--coverage.continueOnFail`** | `boolean` | `true` | Continue if test command fails |
50-
| **`--coverage.categories`** | `boolean` | `true` | Add Code coverage categories |
50+
| **`--coverage.categories`** | `boolean` | `true` | Add categories |
5151

5252
#### JS Packages
5353

54-
| Option | Type | Default | Description |
55-
| ------------------------------------ | ---------------------------------------------------------- | ------------- | -------------------------- |
56-
| **`--js-packages.packageManager`** | `'npm'` \| `'yarn-classic'` \| `'yarn-modern'` \| `'pnpm'` | auto-detected | Package manager |
57-
| **`--js-packages.checks`** | `('audit'` \| `'outdated')[]` | both | Checks to run |
58-
| **`--js-packages.dependencyGroups`** | `('prod'` \| `'dev'` \| `'optional')[]` | `prod`, `dev` | Dependency groups |
59-
| **`--js-packages.categories`** | `boolean` | `true` | Add JS packages categories |
54+
| Option | Type | Default | Description |
55+
| ------------------------------------ | ---------------------------------------------------------- | ------------- | ----------------- |
56+
| **`--js-packages.packageManager`** | `'npm'` \| `'yarn-classic'` \| `'yarn-modern'` \| `'pnpm'` | auto-detected | Package manager |
57+
| **`--js-packages.checks`** | `('audit'` \| `'outdated')[]` | both | Checks to run |
58+
| **`--js-packages.dependencyGroups`** | `('prod'` \| `'dev'` \| `'optional')[]` | `prod`, `dev` | Dependency groups |
59+
| **`--js-packages.categories`** | `boolean` | `true` | Add categories |
6060

6161
#### TypeScript
6262

63-
| Option | Type | Default | Description |
64-
| ----------------------------- | --------- | ------------- | ------------------------- |
65-
| **`--typescript.tsconfig`** | `string` | auto-detected | TypeScript config file |
66-
| **`--typescript.categories`** | `boolean` | `true` | Add TypeScript categories |
63+
| Option | Type | Default | Description |
64+
| ----------------------------- | --------- | ------------- | ---------------------- |
65+
| **`--typescript.tsconfig`** | `string` | auto-detected | TypeScript config file |
66+
| **`--typescript.categories`** | `boolean` | `true` | Add categories |
6767

6868
#### Lighthouse
6969

7070
| Option | Type | Default | Description |
7171
| ----------------------------- | ---------------------------------------------------------------- | ----------------------- | ------------------------------- |
7272
| **`--lighthouse.urls`** | `string \| string[]` | `http://localhost:4200` | Target URL(s) (comma-separated) |
73-
| **`--lighthouse.categories`** | `('performance'` \| `'a11y'` \| `'best-practices'` \| `'seo')[]` | all | Lighthouse categories |
73+
| **`--lighthouse.categories`** | `('performance'` \| `'a11y'` \| `'best-practices'` \| `'seo')[]` | all | Categories |
7474

7575
#### JSDocs
7676

7777
| Option | Type | Default | Description |
7878
| ------------------------- | -------------------- | -------------------------------------------- | -------------------------------------- |
7979
| **`--jsdocs.patterns`** | `string \| string[]` | `src/**/*.ts, src/**/*.js, !**/node_modules` | Source file patterns (comma-separated) |
80-
| **`--jsdocs.categories`** | `boolean` | `true` | Add JSDocs categories |
80+
| **`--jsdocs.categories`** | `boolean` | `true` | Add categories |
8181

8282
#### Axe
8383

@@ -86,7 +86,7 @@ Each plugin exposes its own configuration keys that can be passed as CLI argumen
8686
| **`--axe.urls`** | `string \| string[]` | `http://localhost:4200` | Target URL(s) (comma-separated) |
8787
| **`--axe.preset`** | `'wcag21aa'` \| `'wcag22aa'` \| `'best-practice'` \| `'all'` | `wcag21aa` | Accessibility preset |
8888
| **`--axe.setupScript`** | `boolean` | `false` | Create setup script for auth-protected app |
89-
| **`--axe.categories`** | `boolean` | `true` | Add Axe categories |
89+
| **`--axe.categories`** | `boolean` | `true` | Add categories |
9090

9191
### Examples
9292

packages/create-cli/src/lib/setup/wizard.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@ export async function runSetupWizard(
8686
logChanges(tree.listChanges());
8787

8888
if (cliArgs['dry-run']) {
89+
logger.newline();
8990
logger.info('Dry run — no files written.');
9091
return;
9192
}
9293

9394
await tree.flush();
9495

96+
logger.newline();
9597
logger.info('Setup complete.');
9698
logger.newline();
9799
logNextSteps([
@@ -101,17 +103,23 @@ export async function runSetupWizard(
101103
}
102104

103105
async function resolveBinding(
104-
binding: PluginSetupBinding,
106+
{ title, prompts, generateConfig }: PluginSetupBinding,
105107
cliArgs: CliArgs,
106108
targetDir: string,
107109
tree: Pick<Tree, 'read' | 'write'>,
108110
): Promise<PluginCodegenResult> {
109-
const descriptors = binding.prompts ? await binding.prompts(targetDir) : [];
110-
const answers =
111-
descriptors.length > 0
112-
? await promptPluginOptions(descriptors, cliArgs)
113-
: {};
114-
return binding.generateConfig(answers, tree);
111+
if (!prompts) {
112+
return generateConfig({}, tree);
113+
}
114+
return logger.group(`Configuring ${title}`, async () => {
115+
const descriptors = await prompts(targetDir);
116+
const answers =
117+
descriptors.length > 0
118+
? await promptPluginOptions(descriptors, cliArgs)
119+
: {};
120+
const result = generateConfig(answers, tree);
121+
return { message: `Configured ${title}`, result };
122+
});
115123
}
116124

117125
async function writeStandaloneConfig(

packages/create-cli/src/lib/setup/wizard.unit.test.ts

Lines changed: 69 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,26 @@ vi.mock('./monorepo.js', async importOriginal => ({
1818
addCodePushUpCommand: vi.fn().mockResolvedValue(undefined),
1919
}));
2020

21-
const TEST_BINDING: PluginSetupBinding = {
22-
slug: 'test-plugin',
23-
title: 'Test Plugin',
24-
packageName: '@code-pushup/test-plugin',
25-
isRecommended: () => Promise.resolve(true),
26-
generateConfig: () => ({
27-
imports: [
28-
{
29-
moduleSpecifier: '@code-pushup/test-plugin',
30-
defaultImport: 'testPlugin',
31-
},
32-
],
33-
pluginInit: ['testPlugin(),'],
34-
}),
35-
};
21+
function createBinding(
22+
overrides?: Partial<PluginSetupBinding>,
23+
): PluginSetupBinding {
24+
return {
25+
slug: 'test-plugin',
26+
title: 'Test Plugin',
27+
packageName: '@code-pushup/test-plugin',
28+
isRecommended: () => Promise.resolve(true),
29+
generateConfig: () => ({
30+
imports: [
31+
{
32+
moduleSpecifier: '@code-pushup/test-plugin',
33+
defaultImport: 'testPlugin',
34+
},
35+
],
36+
pluginInit: ['testPlugin(),'],
37+
}),
38+
...overrides,
39+
};
40+
}
3641

3742
describe('runSetupWizard', () => {
3843
describe('TypeScript config', () => {
@@ -41,7 +46,7 @@ describe('runSetupWizard', () => {
4146
});
4247

4348
it('should generate ts config and log success', async () => {
44-
await runSetupWizard([TEST_BINDING], {
49+
await runSetupWizard([createBinding()], {
4550
yes: true,
4651
'target-dir': MEMFS_VOLUME,
4752
});
@@ -67,7 +72,7 @@ describe('runSetupWizard', () => {
6772
});
6873

6974
it('should log dry-run message without writing files', async () => {
70-
await runSetupWizard([TEST_BINDING], {
75+
await runSetupWizard([createBinding()], {
7176
yes: true,
7277
'dry-run': true,
7378
'target-dir': MEMFS_VOLUME,
@@ -109,7 +114,7 @@ describe('runSetupWizard', () => {
109114
});
110115

111116
it('should generate .mjs config when js format is auto-detected', async () => {
112-
await runSetupWizard([TEST_BINDING], {
117+
await runSetupWizard([createBinding()], {
113118
yes: true,
114119
'target-dir': MEMFS_VOLUME,
115120
});
@@ -136,7 +141,7 @@ describe('runSetupWizard', () => {
136141
MEMFS_VOLUME,
137142
);
138143

139-
await runSetupWizard([TEST_BINDING], {
144+
await runSetupWizard([createBinding()], {
140145
yes: true,
141146
'config-format': 'js',
142147
'target-dir': MEMFS_VOLUME,
@@ -159,40 +164,35 @@ describe('runSetupWizard', () => {
159164
});
160165
});
161166

162-
describe('Monorepo config', () => {
163-
const PROJECT_BINDING: PluginSetupBinding = {
164-
slug: 'test-plugin',
165-
title: 'Test Plugin',
166-
packageName: '@code-pushup/test-plugin',
167-
isRecommended: () => Promise.resolve(true),
168-
generateConfig: () => ({
169-
imports: [
170-
{
171-
moduleSpecifier: '@code-pushup/test-plugin',
172-
defaultImport: 'testPlugin',
173-
},
167+
it('should group only plugins with prompts, each under its title', async () => {
168+
vol.fromJSON({ 'tsconfig.json': '{}' }, MEMFS_VOLUME);
169+
const withPrompts = (title: string) =>
170+
createBinding({
171+
title,
172+
prompts: async () => [
173+
{ key: 'x', message: 'X:', type: 'input', default: '' },
174174
],
175-
pluginInit: ['testPlugin(),'],
176-
}),
177-
};
178-
179-
const ROOT_BINDING: PluginSetupBinding = {
180-
slug: 'root-plugin',
181-
title: 'Root Plugin',
182-
packageName: '@code-pushup/root-plugin',
183-
scope: 'root',
184-
isRecommended: () => Promise.resolve(true),
185-
generateConfig: () => ({
186-
imports: [
187-
{
188-
moduleSpecifier: '@code-pushup/root-plugin',
189-
defaultImport: 'rootPlugin',
190-
},
191-
],
192-
pluginInit: ['rootPlugin(),'],
193-
}),
194-
};
175+
});
195176

177+
await runSetupWizard(
178+
[withPrompts('Alpha'), createBinding(), withPrompts('Beta')],
179+
{ yes: true, 'target-dir': MEMFS_VOLUME },
180+
);
181+
182+
expect(logger.group).toHaveBeenCalledTimes(2);
183+
expect(logger.group).toHaveBeenNthCalledWith(
184+
1,
185+
'Configuring Alpha',
186+
expect.any(Function),
187+
);
188+
expect(logger.group).toHaveBeenNthCalledWith(
189+
2,
190+
'Configuring Beta',
191+
expect.any(Function),
192+
);
193+
});
194+
195+
describe('Monorepo config', () => {
196196
beforeEach(() => {
197197
vol.fromJSON(
198198
{
@@ -216,7 +216,7 @@ describe('runSetupWizard', () => {
216216
});
217217

218218
it('should generate preset and per-project configs', async () => {
219-
await runSetupWizard([PROJECT_BINDING], {
219+
await runSetupWizard([createBinding()], {
220220
yes: true,
221221
mode: 'monorepo',
222222
'target-dir': MEMFS_VOLUME,
@@ -269,7 +269,23 @@ describe('runSetupWizard', () => {
269269
});
270270

271271
it('should generate root config for root-scoped plugins', async () => {
272-
await runSetupWizard([PROJECT_BINDING, ROOT_BINDING], {
272+
const rootBinding = createBinding({
273+
slug: 'root-plugin',
274+
title: 'Root Plugin',
275+
packageName: '@code-pushup/root-plugin',
276+
scope: 'root',
277+
generateConfig: () => ({
278+
imports: [
279+
{
280+
moduleSpecifier: '@code-pushup/root-plugin',
281+
defaultImport: 'rootPlugin',
282+
},
283+
],
284+
pluginInit: ['rootPlugin(),'],
285+
}),
286+
});
287+
288+
await runSetupWizard([createBinding(), rootBinding], {
273289
yes: true,
274290
mode: 'monorepo',
275291
'target-dir': MEMFS_VOLUME,

packages/plugin-axe/src/lib/binding.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ export const axeSetupBinding = {
6060
prompts: async () => [
6161
{
6262
key: 'axe.urls',
63-
message: 'Target URL(s) (comma-separated)',
63+
message: 'Target URL(s) (comma-separated):',
6464
type: 'input',
6565
default: DEFAULT_URL,
6666
},
6767
{
6868
key: 'axe.preset',
69-
message: 'Accessibility preset',
69+
message: 'Accessibility preset:',
7070
type: 'select',
7171
choices: [...PRESET_CHOICES],
7272
default: AXE_DEFAULT_PRESET,
@@ -79,7 +79,7 @@ export const axeSetupBinding = {
7979
},
8080
{
8181
key: 'axe.categories',
82-
message: 'Add Axe categories?',
82+
message: 'Add categories?',
8383
type: 'confirm',
8484
default: true,
8585
},

packages/plugin-coverage/src/lib/binding.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,32 +81,32 @@ export const coverageSetupBinding = {
8181
return [
8282
{
8383
key: 'coverage.framework',
84-
message: 'Test framework',
84+
message: 'Test framework:',
8585
type: 'select',
8686
choices: [...FRAMEWORKS],
8787
default: framework,
8888
},
8989
{
9090
key: 'coverage.configFile',
91-
message: 'Path to test config file',
91+
message: 'Path to test config file:',
9292
type: 'input',
9393
default: configFile ?? '',
9494
},
9595
{
9696
key: 'coverage.reportPath',
97-
message: 'Path to LCOV report file',
97+
message: 'Path to LCOV report file:',
9898
type: 'input',
9999
default: framework === 'other' ? '' : DEFAULT_REPORT_PATH,
100100
},
101101
{
102102
key: 'coverage.testCommand',
103-
message: 'Command to run tests with coverage',
103+
message: 'Command to run tests with coverage:',
104104
type: 'input',
105105
default: defaultTestCommand(framework),
106106
},
107107
{
108108
key: 'coverage.types',
109-
message: 'Coverage types to measure',
109+
message: 'Coverage types:',
110110
type: 'checkbox',
111111
choices: ALL_COVERAGE_TYPES.map(type => ({
112112
name: pluralize(type),
@@ -122,7 +122,7 @@ export const coverageSetupBinding = {
122122
},
123123
{
124124
key: 'coverage.categories',
125-
message: 'Add Code coverage categories?',
125+
message: 'Add categories?',
126126
type: 'confirm',
127127
default: true,
128128
},

packages/plugin-eslint/src/lib/binding.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,21 +71,21 @@ export const eslintSetupBinding = {
7171
prompts: async (targetDir: string) => [
7272
{
7373
key: 'eslint.eslintrc',
74-
message: 'Path to ESLint config',
74+
message: 'Path to ESLint config:',
7575
type: 'input',
7676
default: (await detectEslintConfig(targetDir)) ?? '',
7777
},
7878
{
7979
key: 'eslint.patterns',
80-
message: 'File patterns to lint',
80+
message: 'File patterns to lint:',
8181
type: 'input',
8282
default: (await directoryExists(path.join(targetDir, 'src')))
8383
? 'src'
8484
: DEFAULT_PATTERN,
8585
},
8686
{
8787
key: 'eslint.categories',
88-
message: 'Add ESLint categories?',
88+
message: 'Add categories?',
8989
type: 'confirm',
9090
default: true,
9191
},

0 commit comments

Comments
 (0)