@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.
- package/.turbo/turbo-build.log +7 -7
- package/.turbo/turbo-test.log +10594 -4044
- package/CHANGELOG.md +28 -0
- package/dist/bin.js +36 -2
- package/dist/{chunk-LJOKPCPD.js → chunk-7ABJRH3F.js} +1701 -182
- package/dist/index.d.ts +69 -4
- package/dist/index.js +7 -1
- package/package.json +3 -3
- package/src/bin.ts +49 -1
- package/src/commands/dev-config.ts +109 -0
- package/src/commands/doctor.ts +189 -9
- package/src/commands/init.ts +10 -3
- package/src/commands/integration-automation.ts +2 -0
- package/src/commands/integration-host-ide.ts +18 -15
- package/src/commands/integration-install.ts +100 -5
- package/src/commands/onboard.ts +80 -15
- package/src/detect/build-tool.ts +212 -15
- package/src/detect/framework.ts +3 -0
- package/src/detect/package-manager.ts +1 -1
- package/src/index.ts +1 -0
- package/src/inject/gitignore.ts +13 -2
- package/src/instructions.ts +33 -7
- package/src/onboarding/apply.ts +255 -28
- package/src/onboarding/nextjs-guidance.ts +257 -0
- package/src/onboarding/nuxt-guidance.ts +129 -0
- package/src/onboarding/planner.ts +337 -10
- package/src/onboarding/session.ts +127 -31
- package/src/onboarding/target-resolution.ts +79 -3
- package/src/onboarding/umi-guidance.ts +139 -0
- package/src/types.ts +58 -3
- package/tests/apply.test.ts +553 -0
- package/tests/build-tool.test.ts +199 -0
- package/tests/dev-config.test.ts +73 -0
- package/tests/doctor.test.ts +130 -0
- package/tests/init.test.ts +17 -0
- package/tests/install-wrapper.test.ts +56 -0
- package/tests/instructions.test.ts +10 -6
- package/tests/integration-host-ide.test.ts +20 -0
- package/tests/integration-install.test.ts +193 -0
- package/tests/nextjs-guidance.test.ts +128 -0
- package/tests/nuxt-guidance.test.ts +67 -0
- package/tests/onboard.test.ts +511 -0
- package/tests/plan.test.ts +283 -21
- package/tests/runner-script.test.ts +120 -1
- package/tests/session-resolve.test.ts +116 -0
- package/tests/session.test.ts +120 -0
package/tests/plan.test.ts
CHANGED
|
@@ -14,7 +14,7 @@ describe('planner orchestration', () => {
|
|
|
14
14
|
vi.resetAllMocks()
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
it('
|
|
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('
|
|
36
|
-
expect(result.strategy).toBe('
|
|
37
|
-
expect(result.blockers).toEqual([
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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,
|
|
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
|
+
})
|