Skip to content

Commit 4ea61b2

Browse files
committed
refactor(create-cli): rework mode prompting
1 parent 1d1818b commit 4ea61b2

File tree

3 files changed

+100
-35
lines changed

3 files changed

+100
-35
lines changed

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

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,67 @@ import {
44
MONOREPO_TOOL_DETECTORS,
55
type MonorepoTool,
66
type WorkspacePackage,
7+
detectMonorepoTool,
78
hasScript,
89
listPackages,
910
listWorkspaces,
1011
loadNxProjectGraph,
12+
logger,
1113
readPnpmWorkspacePatterns,
1214
toUnixPath,
1315
} from '@code-pushup/utils';
14-
import {
15-
type CliArgs,
16-
SETUP_MODES,
17-
type SetupMode,
18-
type Tree,
19-
type WizardProject,
16+
import type {
17+
CliArgs,
18+
ConfigContext,
19+
SetupMode,
20+
Tree,
21+
WizardProject,
2022
} from './types.js';
2123

2224
const TARGET_NAME = 'code-pushup';
2325

2426
export async function promptSetupMode(
25-
tool: MonorepoTool | null,
27+
targetDir: string,
2628
cliArgs: CliArgs,
27-
): Promise<SetupMode> {
28-
if (isSetupMode(cliArgs.mode)) {
29-
return cliArgs.mode;
30-
}
31-
const mode = tool ? 'monorepo' : 'standalone';
32-
if (cliArgs.yes) {
33-
return mode;
29+
): Promise<ConfigContext> {
30+
switch (cliArgs.mode) {
31+
case 'standalone':
32+
return toContext(cliArgs.mode, null);
33+
case 'monorepo': {
34+
const tool = await detectMonorepoTool(targetDir);
35+
return toContext(cliArgs.mode, tool);
36+
}
37+
case undefined: {
38+
const tool = await detectMonorepoTool(targetDir);
39+
const mode = cliArgs.yes ? inferMode(tool) : await promptMode(tool);
40+
return toContext(mode, tool);
41+
}
3442
}
43+
}
44+
45+
async function promptMode(tool: MonorepoTool | null): Promise<SetupMode> {
3546
return select<SetupMode>({
3647
message: 'Setup mode:',
3748
choices: [
3849
{ name: 'Standalone (single config)', value: 'standalone' },
3950
{ name: 'Monorepo (per-project configs)', value: 'monorepo' },
4051
],
41-
default: mode,
52+
default: inferMode(tool),
4253
});
4354
}
4455

56+
function inferMode(tool: MonorepoTool | null): SetupMode {
57+
return tool ? 'monorepo' : 'standalone';
58+
}
59+
60+
function toContext(mode: SetupMode, tool: MonorepoTool | null): ConfigContext {
61+
if (mode === 'monorepo' && tool == null) {
62+
logger.warn('No monorepo tool detected, falling back to standalone mode.');
63+
return { mode: 'standalone', tool: null };
64+
}
65+
return { mode, tool };
66+
}
67+
4568
export async function listProjects(
4669
cwd: string,
4770
tool: MonorepoTool,
@@ -157,8 +180,3 @@ function toProject(cwd: string, pkg: WorkspacePackage): WizardProject {
157180
relativeDir: toUnixPath(path.relative(cwd, pkg.directory)),
158181
};
159182
}
160-
161-
function isSetupMode(value: string | undefined): value is SetupMode {
162-
const validValues: readonly string[] = SETUP_MODES;
163-
return value != null && validValues.includes(value);
164-
}

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

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { select } from '@inquirer/prompts';
22
import { vol } from 'memfs';
33
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
4+
import { logger } from '@code-pushup/utils';
45
import { addCodePushUpCommand, promptSetupMode } from './monorepo.js';
56
import type { WizardProject } from './types.js';
67
import { createTree } from './virtual-fs.js';
@@ -10,45 +11,93 @@ vi.mock('@inquirer/prompts', () => ({
1011
}));
1112

1213
describe('promptSetupMode', () => {
13-
it('should return CLI arg when --mode is provided', async () => {
14-
await expect(promptSetupMode('nx', { mode: 'standalone' })).resolves.toBe(
15-
'standalone',
14+
it('should skip detection when --mode standalone is provided', async () => {
15+
await expect(
16+
promptSetupMode(MEMFS_VOLUME, { mode: 'standalone' }),
17+
).resolves.toStrictEqual({ mode: 'standalone', tool: null });
18+
expect(select).not.toHaveBeenCalled();
19+
});
20+
21+
it('should detect tool when --mode monorepo is provided', async () => {
22+
vol.fromJSON(
23+
{ 'pnpm-workspace.yaml': 'packages:\n - packages/*' },
24+
MEMFS_VOLUME,
1625
);
26+
27+
await expect(
28+
promptSetupMode(MEMFS_VOLUME, { mode: 'monorepo' }),
29+
).resolves.toStrictEqual({ mode: 'monorepo', tool: 'pnpm' });
1730
expect(select).not.toHaveBeenCalled();
1831
});
1932

33+
it('should fall back to standalone with warning when --mode monorepo but no tool detected', async () => {
34+
vol.fromJSON({ 'package.json': '{}' }, MEMFS_VOLUME);
35+
36+
await expect(
37+
promptSetupMode(MEMFS_VOLUME, { mode: 'monorepo' }),
38+
).resolves.toStrictEqual({ mode: 'standalone', tool: null });
39+
expect(logger.warn).toHaveBeenCalledWith(
40+
expect.stringContaining('falling back to standalone'),
41+
);
42+
});
43+
2044
it('should auto-select monorepo when --yes and tool detected', async () => {
21-
await expect(promptSetupMode('nx', { yes: true })).resolves.toBe(
22-
'monorepo',
45+
vol.fromJSON(
46+
{ 'pnpm-workspace.yaml': 'packages:\n - packages/*' },
47+
MEMFS_VOLUME,
2348
);
49+
50+
await expect(
51+
promptSetupMode(MEMFS_VOLUME, { yes: true }),
52+
).resolves.toStrictEqual({ mode: 'monorepo', tool: 'pnpm' });
2453
expect(select).not.toHaveBeenCalled();
2554
});
2655

2756
it('should auto-select standalone when --yes and no tool', async () => {
28-
await expect(promptSetupMode(null, { yes: true })).resolves.toBe(
29-
'standalone',
30-
);
57+
vol.fromJSON({ 'package.json': '{}' }, MEMFS_VOLUME);
58+
59+
await expect(
60+
promptSetupMode(MEMFS_VOLUME, { yes: true }),
61+
).resolves.toStrictEqual({ mode: 'standalone', tool: null });
3162
});
3263

3364
it('should prompt interactively with monorepo pre-selected when tool detected', async () => {
65+
vol.fromJSON(
66+
{ 'pnpm-workspace.yaml': 'packages:\n - packages/*' },
67+
MEMFS_VOLUME,
68+
);
3469
vi.mocked(select).mockResolvedValue('monorepo');
3570

36-
await promptSetupMode('pnpm', {});
71+
await promptSetupMode(MEMFS_VOLUME, {});
3772

3873
expect(select).toHaveBeenCalledWith(
3974
expect.objectContaining({ default: 'monorepo' }),
4075
);
4176
});
4277

43-
it('should prompt interactively with standalone pre-selected when no tool detected', async () => {
78+
it('should prompt interactively with standalone pre-selected when no tool', async () => {
79+
vol.fromJSON({ 'package.json': '{}' }, MEMFS_VOLUME);
4480
vi.mocked(select).mockResolvedValue('standalone');
4581

46-
await promptSetupMode(null, {});
82+
await promptSetupMode(MEMFS_VOLUME, {});
4783

4884
expect(select).toHaveBeenCalledWith(
4985
expect.objectContaining({ default: 'standalone' }),
5086
);
5187
});
88+
89+
it('should fall back to standalone with warning when user selects monorepo but no tool detected', async () => {
90+
vol.fromJSON({ 'package.json': '{}' }, MEMFS_VOLUME);
91+
vi.mocked(select).mockResolvedValue('monorepo');
92+
93+
await expect(promptSetupMode(MEMFS_VOLUME, {})).resolves.toStrictEqual({
94+
mode: 'standalone',
95+
tool: null,
96+
});
97+
expect(logger.warn).toHaveBeenCalledWith(
98+
expect.stringContaining('falling back to standalone'),
99+
);
100+
});
52101
});
53102

54103
describe('addCodePushUpCommand', () => {

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import path from 'node:path';
22
import {
33
type MonorepoTool,
44
asyncSequential,
5-
detectMonorepoTool,
65
formatAsciiTable,
76
getGitRoot,
87
logger,
@@ -49,8 +48,7 @@ export async function runSetupWizard(
4948
): Promise<void> {
5049
const targetDir = cliArgs['target-dir'] ?? process.cwd();
5150

52-
const tool = await detectMonorepoTool(targetDir);
53-
const mode = await promptSetupMode(tool, cliArgs);
51+
const { mode, tool } = await promptSetupMode(targetDir, cliArgs);
5452
const selectedBindings = await promptPluginSelection(
5553
bindings,
5654
targetDir,
@@ -66,7 +64,7 @@ export async function runSetupWizard(
6664
selectedBindings,
6765
async binding => ({
6866
scope: binding.scope ?? 'project',
69-
result: await resolveBinding(binding, cliArgs, { tool, mode }),
67+
result: await resolveBinding(binding, cliArgs, { mode, tool }),
7068
}),
7169
);
7270

0 commit comments

Comments
 (0)