@inspecto-dev/cli 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.turbo/turbo-test.log +10594 -4044
  3. package/CHANGELOG.md +28 -0
  4. package/dist/bin.js +36 -2
  5. package/dist/{chunk-LJOKPCPD.js → chunk-7ABJRH3F.js} +1701 -182
  6. package/dist/index.d.ts +69 -4
  7. package/dist/index.js +7 -1
  8. package/package.json +3 -3
  9. package/src/bin.ts +49 -1
  10. package/src/commands/dev-config.ts +109 -0
  11. package/src/commands/doctor.ts +189 -9
  12. package/src/commands/init.ts +10 -3
  13. package/src/commands/integration-automation.ts +2 -0
  14. package/src/commands/integration-host-ide.ts +18 -15
  15. package/src/commands/integration-install.ts +100 -5
  16. package/src/commands/onboard.ts +80 -15
  17. package/src/detect/build-tool.ts +212 -15
  18. package/src/detect/framework.ts +3 -0
  19. package/src/detect/package-manager.ts +1 -1
  20. package/src/index.ts +1 -0
  21. package/src/inject/gitignore.ts +13 -2
  22. package/src/instructions.ts +33 -7
  23. package/src/onboarding/apply.ts +255 -28
  24. package/src/onboarding/nextjs-guidance.ts +257 -0
  25. package/src/onboarding/nuxt-guidance.ts +129 -0
  26. package/src/onboarding/planner.ts +337 -10
  27. package/src/onboarding/session.ts +127 -31
  28. package/src/onboarding/target-resolution.ts +79 -3
  29. package/src/onboarding/umi-guidance.ts +139 -0
  30. package/src/types.ts +58 -3
  31. package/tests/apply.test.ts +553 -0
  32. package/tests/build-tool.test.ts +199 -0
  33. package/tests/dev-config.test.ts +73 -0
  34. package/tests/doctor.test.ts +130 -0
  35. package/tests/init.test.ts +17 -0
  36. package/tests/install-wrapper.test.ts +56 -0
  37. package/tests/instructions.test.ts +10 -6
  38. package/tests/integration-host-ide.test.ts +20 -0
  39. package/tests/integration-install.test.ts +193 -0
  40. package/tests/nextjs-guidance.test.ts +128 -0
  41. package/tests/nuxt-guidance.test.ts +67 -0
  42. package/tests/onboard.test.ts +511 -0
  43. package/tests/plan.test.ts +283 -21
  44. package/tests/runner-script.test.ts +120 -1
  45. package/tests/session-resolve.test.ts +116 -0
  46. package/tests/session.test.ts +120 -0
@@ -14,7 +14,7 @@ describe('planner orchestration', () => {
14
14
  vi.resetAllMocks()
15
15
  })
16
16
 
17
- it('blocks unsupported build stacks and returns a manual action', () => {
17
+ it('returns guided onboarding actions for Next.js projects', () => {
18
18
  const context: OnboardingContext = {
19
19
  root: '/repo',
20
20
  packageManager: 'pnpm',
@@ -32,28 +32,114 @@ describe('planner orchestration', () => {
32
32
 
33
33
  const result = planner.createPlanResult(context)
34
34
 
35
- expect(result.status).toBe('blocked')
36
- expect(result.strategy).toBe('manual')
37
- expect(result.blockers).toEqual([
38
- {
39
- code: 'unsupported-build-tool',
40
- message: 'Detected unsupported build tool(s): Next.js',
35
+ expect(result.status).toBe('warning')
36
+ expect(result.strategy).toBe('guided')
37
+ expect(result.blockers).toEqual([])
38
+ expect(result.framework).toBe('react')
39
+ expect(result.metaFramework).toBe('Next.js')
40
+ expect(result.autoApplied).toEqual(['dependencies', 'inspecto_settings'])
41
+ expect(result.pendingSteps).toEqual(
42
+ expect.arrayContaining([
43
+ expect.stringContaining('Review the generated Next.js patch plan'),
44
+ expect.stringContaining('Complete the remaining client-side mount step'),
45
+ ]),
46
+ )
47
+ expect(result.assistantPrompt).toContain('Complete the remaining Inspecto onboarding')
48
+ expect(result.actions).toEqual(
49
+ expect.arrayContaining([
50
+ {
51
+ type: 'install_dependency',
52
+ target: '@inspecto-dev/plugin @inspecto-dev/core',
53
+ description: 'Install the Inspecto runtime packages with pnpm.',
54
+ },
55
+ {
56
+ type: 'install_extension',
57
+ target: 'vscode',
58
+ description: 'Install the Inspecto VS Code extension.',
59
+ },
60
+ {
61
+ type: 'generate_patch_plan',
62
+ target: 'next.config',
63
+ description: 'Generate a guided patch plan for the Next.js Inspecto webpack integration.',
64
+ },
65
+ {
66
+ type: 'manual_confirmation',
67
+ target: '/repo',
68
+ description:
69
+ 'Complete the remaining client-side Inspecto mount step in your assistant or editor.',
70
+ },
71
+ ]),
72
+ )
73
+ })
74
+
75
+ it('returns guided onboarding actions for Nuxt projects', () => {
76
+ const context: OnboardingContext = {
77
+ root: '/repo',
78
+ packageManager: 'pnpm',
79
+ buildTools: {
80
+ supported: [],
81
+ unsupported: ['Nuxt'],
41
82
  },
42
- ])
43
- expect(result.actions).toEqual([
44
- {
45
- type: 'manual_step',
46
- target: 'Next.js',
47
- description:
48
- 'Inspecto cannot auto-configure this build stack yet. Follow the manual setup guide for the detected framework or build tool.',
83
+ frameworks: {
84
+ supported: ['vue'],
85
+ unsupported: [],
49
86
  },
50
- ])
51
- expect(result.defaults).toEqual({
52
- provider: 'codex',
53
- ide: 'vscode',
54
- shared: false,
55
- extension: true,
56
- })
87
+ ides: [{ ide: 'vscode', supported: true }],
88
+ providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
89
+ }
90
+
91
+ const result = planner.createPlanResult(context)
92
+
93
+ expect(result.status).toBe('warning')
94
+ expect(result.strategy).toBe('guided')
95
+ expect(result.blockers).toEqual([])
96
+ expect(result.framework).toBe('vue')
97
+ expect(result.metaFramework).toBe('Nuxt')
98
+ expect(result.pendingSteps).toEqual(
99
+ expect.arrayContaining([
100
+ expect.stringContaining('Review the generated Nuxt patch plan'),
101
+ expect.stringContaining('Complete the remaining Nuxt client plugin mount step'),
102
+ ]),
103
+ )
104
+ expect(result.actions).toEqual(
105
+ expect.arrayContaining([
106
+ {
107
+ type: 'generate_patch_plan',
108
+ target: 'nuxt.config',
109
+ description: 'Generate a guided patch plan for the Nuxt Inspecto Vite integration.',
110
+ },
111
+ ]),
112
+ )
113
+ })
114
+
115
+ it('keeps Next.js in guided mode when additional unsupported build tools are also detected', () => {
116
+ const context: OnboardingContext = {
117
+ root: '/repo',
118
+ packageManager: 'pnpm',
119
+ buildTools: {
120
+ supported: [],
121
+ unsupported: ['Next.js', 'CustomStack'],
122
+ },
123
+ frameworks: {
124
+ supported: ['react'],
125
+ unsupported: [],
126
+ },
127
+ ides: [{ ide: 'vscode', supported: true }],
128
+ providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
129
+ }
130
+
131
+ const result = planner.createPlanResult(context)
132
+
133
+ expect(result.strategy).toBe('guided')
134
+ expect(result.blockers).toEqual([])
135
+ expect(result.warnings).toEqual(
136
+ expect.arrayContaining([
137
+ {
138
+ code: 'additional-unsupported-build-tool',
139
+ message: 'Additional unsupported build tool also detected: CustomStack',
140
+ },
141
+ ]),
142
+ )
57
143
  })
58
144
 
59
145
  it('blocks mixed supported and unsupported build tools instead of taking the supported auto path', () => {
@@ -269,6 +355,182 @@ describe('planner orchestration', () => {
269
355
  ])
270
356
  })
271
357
 
358
+ it('returns a legacy rspack partial-manual strategy when the selected build target is legacy rspack', () => {
359
+ const context: OnboardingContext = {
360
+ root: '/repo/finder',
361
+ packageManager: 'pnpm',
362
+ buildTools: {
363
+ supported: [
364
+ {
365
+ tool: 'rspack',
366
+ configPath: 'finder/rspack-config/rspack.config.dev.ts',
367
+ label: 'Rspack (finder/rspack-config/rspack.config.dev.ts) [Legacy]',
368
+ packagePath: 'finder',
369
+ isLegacyRspack: true,
370
+ },
371
+ ],
372
+ unsupported: [],
373
+ },
374
+ frameworks: {
375
+ supported: ['react'],
376
+ unsupported: [],
377
+ },
378
+ ides: [{ ide: 'trae', supported: true }],
379
+ providers: [{ id: 'coco', label: 'Coco CLI', supported: true, preferredMode: 'cli' }],
380
+ }
381
+
382
+ const result = planner.createPlanResult(context)
383
+
384
+ expect(result.status).toBe('warning')
385
+ expect(result.strategy).toBe('manual')
386
+ expect(result.blockers).toEqual([])
387
+ expect(result.warnings).toEqual([
388
+ {
389
+ code: 'legacy-rspack-requires-manual-config',
390
+ message:
391
+ 'Legacy Rspack detected at finder/rspack-config/rspack.config.dev.ts. Inspecto must use the legacy Rspack plugin entry and manual config steps.',
392
+ },
393
+ ])
394
+ expect(result.actions).toEqual([
395
+ {
396
+ type: 'install_dependency',
397
+ target: '@inspecto-dev/plugin @inspecto-dev/core',
398
+ description: 'Install the Inspecto runtime packages with pnpm.',
399
+ },
400
+ {
401
+ type: 'manual_step',
402
+ target: 'finder/rspack-config/rspack.config.dev.ts',
403
+ description:
404
+ 'Update finder/rspack-config/rspack.config.dev.ts to import `rspackPlugin` from `@inspecto-dev/plugin/legacy/rspack` and add it to the Rspack plugins array.',
405
+ },
406
+ ])
407
+ })
408
+
409
+ it('plans Inspecto extension installation for Cursor by default', () => {
410
+ const context: OnboardingContext = {
411
+ root: '/repo',
412
+ packageManager: 'pnpm',
413
+ buildTools: {
414
+ supported: [
415
+ {
416
+ tool: 'vite',
417
+ configPath: 'vite.config.ts',
418
+ label: 'Vite (vite.config.ts)',
419
+ },
420
+ ],
421
+ unsupported: [],
422
+ },
423
+ frameworks: {
424
+ supported: ['react'],
425
+ unsupported: [],
426
+ },
427
+ ides: [{ ide: 'cursor', supported: true }],
428
+ providers: [{ id: 'copilot', label: 'Copilot', supported: true, preferredMode: 'extension' }],
429
+ }
430
+
431
+ const result = planner.createPlanResult(context)
432
+
433
+ expect(result.actions).toContainEqual({
434
+ type: 'install_extension',
435
+ target: 'cursor',
436
+ description: 'Install the Inspecto Cursor extension.',
437
+ })
438
+ expect(result.defaults).toMatchObject({
439
+ provider: 'copilot',
440
+ ide: 'cursor',
441
+ shared: false,
442
+ extension: true,
443
+ })
444
+ })
445
+
446
+ it('plans Inspecto extension installation for Trae CN by default', () => {
447
+ const context: OnboardingContext = {
448
+ root: '/repo',
449
+ packageManager: 'pnpm',
450
+ buildTools: {
451
+ supported: [
452
+ {
453
+ tool: 'vite',
454
+ configPath: 'vite.config.ts',
455
+ label: 'Vite (vite.config.ts)',
456
+ },
457
+ ],
458
+ unsupported: [],
459
+ },
460
+ frameworks: {
461
+ supported: ['react'],
462
+ unsupported: [],
463
+ },
464
+ ides: [{ ide: 'trae-cn', supported: true }],
465
+ providers: [{ id: 'gemini', label: 'Gemini', supported: true, preferredMode: 'extension' }],
466
+ }
467
+
468
+ const result = planner.createPlanResult(context)
469
+
470
+ expect(result.actions).toContainEqual({
471
+ type: 'install_extension',
472
+ target: 'trae-cn',
473
+ description: 'Install the Inspecto Trae CN extension.',
474
+ })
475
+ expect(result.defaults).toMatchObject({
476
+ provider: 'gemini',
477
+ ide: 'trae-cn',
478
+ shared: false,
479
+ extension: true,
480
+ })
481
+ })
482
+
483
+ it('returns a webpack 4 partial-manual strategy when the selected build target is legacy webpack', () => {
484
+ const context: OnboardingContext = {
485
+ root: '/repo/app',
486
+ packageManager: 'pnpm',
487
+ buildTools: {
488
+ supported: [
489
+ {
490
+ tool: 'webpack',
491
+ configPath: 'app/webpack.config.js',
492
+ label: 'Webpack (app/webpack.config.js) [Webpack 4]',
493
+ packagePath: 'app',
494
+ isLegacyWebpack: true,
495
+ },
496
+ ],
497
+ unsupported: [],
498
+ },
499
+ frameworks: {
500
+ supported: ['react'],
501
+ unsupported: [],
502
+ },
503
+ ides: [{ ide: 'cursor', supported: true }],
504
+ providers: [{ id: 'copilot', label: 'Copilot', supported: true, preferredMode: 'extension' }],
505
+ }
506
+
507
+ const result = planner.createPlanResult(context)
508
+
509
+ expect(result.status).toBe('warning')
510
+ expect(result.strategy).toBe('manual')
511
+ expect(result.blockers).toEqual([])
512
+ expect(result.warnings).toEqual([
513
+ {
514
+ code: 'legacy-webpack4-requires-manual-config',
515
+ message:
516
+ 'Webpack 4 detected at app/webpack.config.js. Inspecto must use the legacy Webpack 4 plugin entry and manual config steps.',
517
+ },
518
+ ])
519
+ expect(result.actions).toEqual([
520
+ {
521
+ type: 'install_dependency',
522
+ target: '@inspecto-dev/plugin @inspecto-dev/core',
523
+ description: 'Install the Inspecto runtime packages with pnpm.',
524
+ },
525
+ {
526
+ type: 'manual_step',
527
+ target: 'app/webpack.config.js',
528
+ description:
529
+ 'Update app/webpack.config.js to import `webpackPlugin` from `@inspecto-dev/plugin/legacy/webpack4` and add it to the Webpack plugins array.',
530
+ },
531
+ ])
532
+ })
533
+
272
534
  it('blocks root-level apply planning when multiple supported build targets are detected', () => {
273
535
  const context: OnboardingContext = {
274
536
  root: '/repo',
@@ -19,7 +19,7 @@ async function writeExecutable(filePath: string, content: string): Promise<void>
19
19
  }
20
20
 
21
21
  function runRunner(scriptPath: string, cwd: string, pathEnv: string): string {
22
- const result = spawnSync(scriptPath, ['onboard', '--json'], {
22
+ const result = spawnSync('bash', [scriptPath, 'onboard', '--json'], {
23
23
  cwd,
24
24
  env: {
25
25
  ...process.env,
@@ -32,6 +32,41 @@ function runRunner(scriptPath: string, cwd: string, pathEnv: string): string {
32
32
  return (result.stdout + result.stderr).trim()
33
33
  }
34
34
 
35
+ function runRunnerWithEnv(
36
+ scriptPath: string,
37
+ cwd: string,
38
+ pathEnv: string,
39
+ extraEnv: Record<string, string>,
40
+ ): string {
41
+ const result = spawnSync('bash', [scriptPath, 'onboard', '--json'], {
42
+ cwd,
43
+ env: {
44
+ ...process.env,
45
+ PATH: pathEnv,
46
+ ...extraEnv,
47
+ },
48
+ encoding: 'utf8',
49
+ })
50
+
51
+ expect(result.status).toBe(0)
52
+ return (result.stdout + result.stderr).trim()
53
+ }
54
+
55
+ async function writeJson(filePath: string, data: unknown): Promise<void> {
56
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
57
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8')
58
+ }
59
+
60
+ async function writeNodeEntrypoint(filePath: string, content: string): Promise<void> {
61
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
62
+ await fs.writeFile(
63
+ filePath,
64
+ `#!/usr/bin/env node\nconsole.log(${JSON.stringify(content)} + process.argv.slice(2).join(' '))\n`,
65
+ 'utf8',
66
+ )
67
+ await fs.chmod(filePath, 0o755)
68
+ }
69
+
35
70
  describe('skill runner scripts', () => {
36
71
  afterEach(async () => {
37
72
  await Promise.all(TEMP_DIRS.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true })))
@@ -69,4 +104,88 @@ exit 99
69
104
  expect(output).not.toContain('pnpm-should-not-run')
70
105
  },
71
106
  )
107
+
108
+ it.each([
109
+ 'skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
110
+ 'skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
111
+ 'skills/inspecto-onboarding-trae/scripts/run-inspecto.sh',
112
+ 'skills/inspecto-onboarding-core/scripts/run-inspecto.sh',
113
+ ])('prefers .inspecto/dev.json cliBin before global fallback: %s', async scriptRelativePath => {
114
+ const workspaceRoot = path.resolve(__dirname, '../../..')
115
+ const scriptPath = path.join(workspaceRoot, scriptRelativePath)
116
+ const tempDir = await makeTempDir()
117
+ const fakeBin = path.join(tempDir, 'bin')
118
+ const fakeCli = path.join(tempDir, 'inspecto-dev-bin.js')
119
+
120
+ await writeNodeEntrypoint(fakeCli, 'dev-config-cli:')
121
+ await writeJson(path.join(tempDir, '.inspecto/dev.json'), {
122
+ cliBin: fakeCli,
123
+ })
124
+ await writeExecutable(
125
+ path.join(fakeBin, 'inspecto'),
126
+ `#!/usr/bin/env bash
127
+ echo "installed-inspecto:$*"
128
+ `,
129
+ )
130
+
131
+ const output = runRunner(scriptPath, tempDir, `${fakeBin}:${process.env.PATH ?? ''}`)
132
+ expect(output).toContain('dev-config-cli:onboard --json')
133
+ expect(output).not.toContain('installed-inspecto:onboard --json')
134
+ })
135
+
136
+ it.each([
137
+ 'skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
138
+ 'skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
139
+ 'skills/inspecto-onboarding-trae/scripts/run-inspecto.sh',
140
+ 'skills/inspecto-onboarding-core/scripts/run-inspecto.sh',
141
+ ])('prefers .inspecto/dev.json devRepo before global fallback: %s', async scriptRelativePath => {
142
+ const workspaceRoot = path.resolve(__dirname, '../../..')
143
+ const scriptPath = path.join(workspaceRoot, scriptRelativePath)
144
+ const tempDir = await makeTempDir()
145
+ const fakeBin = path.join(tempDir, 'bin')
146
+ const fakeRepoDist = path.join(tempDir, 'repo/packages/cli/dist')
147
+
148
+ await writeNodeEntrypoint(path.join(fakeRepoDist, 'bin.js'), 'dev-config-repo:')
149
+ await writeJson(path.join(tempDir, '.inspecto/dev.json'), {
150
+ devRepo: path.join(tempDir, 'repo'),
151
+ })
152
+ await writeExecutable(
153
+ path.join(fakeBin, 'inspecto'),
154
+ `#!/usr/bin/env bash
155
+ echo "installed-inspecto:$*"
156
+ `,
157
+ )
158
+
159
+ const output = runRunner(scriptPath, tempDir, `${fakeBin}:${process.env.PATH ?? ''}`)
160
+ expect(output).toContain('dev-config-repo:onboard --json')
161
+ expect(output).not.toContain('installed-inspecto:onboard --json')
162
+ })
163
+
164
+ it.each([
165
+ 'skills/inspecto-onboarding-codex/scripts/run-inspecto.sh',
166
+ 'skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh',
167
+ 'skills/inspecto-onboarding-trae/scripts/run-inspecto.sh',
168
+ 'skills/inspecto-onboarding-core/scripts/run-inspecto.sh',
169
+ ])('prefers env vars over .inspecto/dev.json: %s', async scriptRelativePath => {
170
+ const workspaceRoot = path.resolve(__dirname, '../../..')
171
+ const scriptPath = path.join(workspaceRoot, scriptRelativePath)
172
+ const tempDir = await makeTempDir()
173
+ const fakeBin = path.join(tempDir, 'bin')
174
+ const envCli = path.join(tempDir, 'env-bin.js')
175
+ const configCli = path.join(tempDir, 'config-bin.js')
176
+
177
+ await writeNodeEntrypoint(envCli, 'env-cli:')
178
+ await writeNodeEntrypoint(configCli, 'config-cli:')
179
+ await writeJson(path.join(tempDir, '.inspecto/dev.json'), {
180
+ cliBin: configCli,
181
+ })
182
+ await fs.mkdir(fakeBin, { recursive: true })
183
+
184
+ const output = runRunnerWithEnv(scriptPath, tempDir, `${fakeBin}:${process.env.PATH ?? ''}`, {
185
+ INSPECTO_CLI_BIN: envCli,
186
+ })
187
+
188
+ expect(output).toContain('env-cli:onboard --json')
189
+ expect(output).not.toContain('config-cli:onboard --json')
190
+ })
72
191
  })
@@ -0,0 +1,116 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { resolveOnboardingSession } from '../src/onboarding/session.js'
3
+
4
+ const { buildOnboardingContextMock, readJSONMock } = vi.hoisted(() => ({
5
+ buildOnboardingContextMock: vi.fn(),
6
+ readJSONMock: vi.fn(),
7
+ }))
8
+
9
+ vi.mock('../src/onboarding/context.js', () => ({
10
+ buildOnboardingContext: buildOnboardingContextMock,
11
+ }))
12
+
13
+ vi.mock('../src/utils/fs.js', async () => {
14
+ const actual = await vi.importActual('../src/utils/fs.js')
15
+ return {
16
+ ...actual,
17
+ readJSON: readJSONMock,
18
+ }
19
+ })
20
+
21
+ describe('resolveOnboardingSession for Next.js guided onboarding', () => {
22
+ beforeEach(() => {
23
+ vi.resetAllMocks()
24
+ readJSONMock.mockImplementation(async filePath => {
25
+ if (String(filePath).endsWith('package.json')) {
26
+ return { scripts: { dev: 'next dev' } }
27
+ }
28
+ return null
29
+ })
30
+ })
31
+
32
+ it('returns partial_success instead of error for Next.js projects', async () => {
33
+ buildOnboardingContextMock.mockResolvedValue({
34
+ root: '/repo',
35
+ packageManager: 'pnpm',
36
+ buildTools: {
37
+ supported: [],
38
+ unsupported: ['Next.js'],
39
+ },
40
+ frameworks: {
41
+ supported: ['react'],
42
+ unsupported: [],
43
+ },
44
+ ides: [{ ide: 'vscode', supported: true }],
45
+ providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
46
+ })
47
+
48
+ const result = await resolveOnboardingSession('/repo')
49
+
50
+ expect(result.status).toBe('partial_success')
51
+ expect(result.target).toEqual({
52
+ status: 'guided',
53
+ candidates: [],
54
+ reason: 'Guided onboarding is available for Next.js.',
55
+ })
56
+ expect(result.plan.strategy).toBe('guided')
57
+ expect(result.framework).toBe('react')
58
+ expect(result.metaFramework).toBe('Next.js')
59
+ expect(result.pendingSteps).toEqual(
60
+ expect.arrayContaining([expect.stringContaining('Review the generated Next.js patch plan')]),
61
+ )
62
+ })
63
+
64
+ it('returns partial_success instead of error for Nuxt projects', async () => {
65
+ buildOnboardingContextMock.mockResolvedValue({
66
+ root: '/repo',
67
+ packageManager: 'pnpm',
68
+ buildTools: {
69
+ supported: [],
70
+ unsupported: ['Nuxt'],
71
+ },
72
+ frameworks: {
73
+ supported: ['vue'],
74
+ unsupported: [],
75
+ },
76
+ ides: [{ ide: 'vscode', supported: true }],
77
+ providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
78
+ })
79
+
80
+ const result = await resolveOnboardingSession('/repo')
81
+
82
+ expect(result.status).toBe('partial_success')
83
+ expect(result.plan.strategy).toBe('guided')
84
+ expect(result.framework).toBe('vue')
85
+ expect(result.metaFramework).toBe('Nuxt')
86
+ expect(result.pendingSteps).toEqual(
87
+ expect.arrayContaining([expect.stringContaining('Review the generated Nuxt patch plan')]),
88
+ )
89
+ })
90
+
91
+ it('keeps Next.js guided onboarding when extra unsupported build tools are present', async () => {
92
+ buildOnboardingContextMock.mockResolvedValue({
93
+ root: '/repo',
94
+ packageManager: 'pnpm',
95
+ buildTools: {
96
+ supported: [],
97
+ unsupported: ['Next.js', 'CustomStack'],
98
+ },
99
+ frameworks: {
100
+ supported: ['react'],
101
+ unsupported: [],
102
+ },
103
+ ides: [{ ide: 'vscode', supported: true }],
104
+ providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
105
+ })
106
+
107
+ const result = await resolveOnboardingSession('/repo')
108
+
109
+ expect(result.status).toBe('partial_success')
110
+ expect(result.plan.strategy).toBe('guided')
111
+ expect(result.target.status).toBe('guided')
112
+ expect(result.summary.risks).toEqual(
113
+ expect.arrayContaining(['Additional unsupported build tool also detected: CustomStack']),
114
+ )
115
+ })
116
+ })
@@ -0,0 +1,120 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { applyResolvedOnboardingSession } from '../src/onboarding/session.js'
3
+ import type { ApplyOnboardingResult } from '../src/onboarding/apply.js'
4
+ import type { ResolvedOnboardingSession } from '../src/types.js'
5
+
6
+ const { applyOnboardingPlanMock, readJSONMock } = vi.hoisted(() => ({
7
+ applyOnboardingPlanMock: vi.fn(),
8
+ readJSONMock: vi.fn(),
9
+ }))
10
+
11
+ vi.mock('../src/onboarding/apply.js', () => ({
12
+ applyOnboardingPlan: applyOnboardingPlanMock,
13
+ }))
14
+
15
+ vi.mock('../src/utils/fs.js', async () => {
16
+ const actual = await vi.importActual('../src/utils/fs.js')
17
+ return {
18
+ ...actual,
19
+ readJSON: readJSONMock,
20
+ }
21
+ })
22
+
23
+ describe('onboarding session extension guidance', () => {
24
+ beforeEach(() => {
25
+ vi.resetAllMocks()
26
+ readJSONMock.mockResolvedValue({
27
+ scripts: {
28
+ dev: 'vite',
29
+ },
30
+ })
31
+ })
32
+
33
+ it('returns Cursor-specific manual extension guidance when onboarding still needs manual completion', async () => {
34
+ const session: ResolvedOnboardingSession = {
35
+ status: 'success',
36
+ target: {
37
+ status: 'resolved',
38
+ selected: {
39
+ packagePath: '',
40
+ configPath: 'vite.config.ts',
41
+ buildTool: 'vite',
42
+ frameworks: ['react'],
43
+ automaticInjection: true,
44
+ },
45
+ candidates: [
46
+ {
47
+ packagePath: '',
48
+ configPath: 'vite.config.ts',
49
+ buildTool: 'vite',
50
+ frameworks: ['react'],
51
+ automaticInjection: true,
52
+ },
53
+ ],
54
+ reason: 'Only one supported target was detected.',
55
+ },
56
+ summary: {
57
+ headline: 'Inspecto is ready to onboard /repo.',
58
+ changes: ['Install dependencies.'],
59
+ risks: [],
60
+ manualFollowUp: [],
61
+ },
62
+ confirmation: { required: false },
63
+ verification: {
64
+ available: true,
65
+ devCommand: 'pnpm dev',
66
+ message: 'Start the local dev server with `pnpm dev` to verify Inspecto in the browser.',
67
+ },
68
+ context: {
69
+ root: '/repo',
70
+ packageManager: 'pnpm',
71
+ buildTools: { supported: [], unsupported: [] },
72
+ frameworks: { supported: ['react'], unsupported: [] },
73
+ ides: [{ ide: 'cursor', supported: true }],
74
+ providers: [
75
+ { id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'extension' },
76
+ ],
77
+ },
78
+ plan: {
79
+ status: 'ok',
80
+ warnings: [],
81
+ blockers: [],
82
+ strategy: 'supported',
83
+ actions: [],
84
+ defaults: {
85
+ provider: 'codex',
86
+ ide: 'cursor',
87
+ shared: false,
88
+ extension: true,
89
+ },
90
+ },
91
+ projectRoot: '/repo',
92
+ selectedIDE: { ide: 'cursor', supported: true },
93
+ providerDefault: 'codex.extension',
94
+ }
95
+
96
+ const applyResult: ApplyOnboardingResult = {
97
+ status: 'warning',
98
+ mutations: [],
99
+ postInstall: {
100
+ installFailed: false,
101
+ injectionFailed: false,
102
+ manualExtensionInstallNeeded: true,
103
+ nextSteps: ['Install the Inspecto IDE extension manually'],
104
+ },
105
+ }
106
+
107
+ applyOnboardingPlanMock.mockResolvedValue(applyResult)
108
+
109
+ const result = await applyResolvedOnboardingSession(session, {})
110
+
111
+ expect(result.status).toBe('partial_success')
112
+ expect(result.ideExtension).toMatchObject({
113
+ required: true,
114
+ installed: false,
115
+ manualRequired: true,
116
+ installCommand: 'cursor --install-extension inspecto.inspecto',
117
+ openVsxUrl: 'https://open-vsx.org/extension/inspecto/inspecto',
118
+ })
119
+ })
120
+ })