@inspecto-dev/cli 0.2.0-alpha.5 → 0.3.0-alpha.1
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 +19 -20
- package/CHANGELOG.md +22 -0
- package/README.md +93 -11
- package/bin/inspecto.js +5 -1
- package/dist/bin.d.ts +5 -1
- package/dist/bin.js +530 -49
- package/dist/chunk-FZS2TLXQ.js +3140 -0
- package/dist/index.d.ts +233 -2
- package/dist/index.js +17 -3
- package/package.json +3 -2
- package/src/bin.ts +286 -66
- package/src/commands/apply.ts +118 -0
- package/src/commands/detect.ts +59 -0
- package/src/commands/doctor.ts +225 -72
- package/src/commands/init.ts +143 -183
- package/src/commands/integration-install.ts +452 -0
- package/src/commands/onboard.ts +50 -0
- package/src/commands/plan.ts +41 -0
- package/src/detect/build-tool.ts +107 -3
- package/src/index.ts +17 -2
- package/src/inject/ast-injector.ts +17 -6
- package/src/inject/extension.ts +40 -22
- package/src/inject/gitignore.ts +10 -3
- package/src/instructions.ts +60 -46
- package/src/onboarding/apply.ts +364 -0
- package/src/onboarding/context.ts +36 -0
- package/src/onboarding/planner.ts +284 -0
- package/src/onboarding/session.ts +434 -0
- package/src/onboarding/target-resolution.ts +116 -0
- package/src/prompts.ts +54 -11
- package/src/types.ts +184 -0
- package/src/utils/fs.ts +2 -1
- package/src/utils/logger.ts +9 -0
- package/src/utils/output.ts +40 -0
- package/tests/apply.test.ts +583 -0
- package/tests/ast-injector.test.ts +50 -0
- package/tests/build-tool.test.ts +3 -5
- package/tests/detect.test.ts +94 -0
- package/tests/doctor.test.ts +224 -0
- package/tests/init.test.ts +364 -0
- package/tests/install-wrapper.test.ts +76 -0
- package/tests/instructions.test.ts +61 -0
- package/tests/integration-install.test.ts +294 -0
- package/tests/logger.test.ts +100 -0
- package/tests/onboard.test.ts +258 -0
- package/tests/plan.test.ts +713 -0
- package/tests/workspace-build-tool.test.ts +75 -0
- package/.turbo/turbo-test.log +0 -16
- package/dist/chunk-MIHQGC3L.js +0 -1720
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import * as onboardingContext from '../src/onboarding/context.js'
|
|
3
|
+
import * as planner from '../src/onboarding/planner.js'
|
|
4
|
+
import { detect } from '../src/commands/detect.js'
|
|
5
|
+
import { plan } from '../src/commands/plan.js'
|
|
6
|
+
import type { OnboardingContext } from '../src/types.js'
|
|
7
|
+
|
|
8
|
+
vi.mock('../src/onboarding/context.js', () => ({
|
|
9
|
+
buildOnboardingContext: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
describe('planner orchestration', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetAllMocks()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('blocks unsupported build stacks and returns a manual action', () => {
|
|
18
|
+
const context: OnboardingContext = {
|
|
19
|
+
root: '/repo',
|
|
20
|
+
packageManager: 'pnpm',
|
|
21
|
+
buildTools: {
|
|
22
|
+
supported: [],
|
|
23
|
+
unsupported: ['Next.js'],
|
|
24
|
+
},
|
|
25
|
+
frameworks: {
|
|
26
|
+
supported: ['react'],
|
|
27
|
+
unsupported: [],
|
|
28
|
+
},
|
|
29
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
30
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const result = planner.createPlanResult(context)
|
|
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',
|
|
41
|
+
},
|
|
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.',
|
|
49
|
+
},
|
|
50
|
+
])
|
|
51
|
+
expect(result.defaults).toEqual({
|
|
52
|
+
provider: 'codex',
|
|
53
|
+
ide: 'vscode',
|
|
54
|
+
shared: false,
|
|
55
|
+
extension: true,
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('blocks mixed supported and unsupported build tools instead of taking the supported auto path', () => {
|
|
60
|
+
const context: OnboardingContext = {
|
|
61
|
+
root: '/repo',
|
|
62
|
+
packageManager: 'pnpm',
|
|
63
|
+
buildTools: {
|
|
64
|
+
supported: [
|
|
65
|
+
{
|
|
66
|
+
tool: 'vite',
|
|
67
|
+
configPath: 'vite.config.ts',
|
|
68
|
+
label: 'Vite (vite.config.ts)',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
unsupported: ['Next.js'],
|
|
72
|
+
},
|
|
73
|
+
frameworks: {
|
|
74
|
+
supported: ['react'],
|
|
75
|
+
unsupported: [],
|
|
76
|
+
},
|
|
77
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
78
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = planner.createPlanResult(context)
|
|
82
|
+
|
|
83
|
+
expect(result.status).toBe('blocked')
|
|
84
|
+
expect(result.strategy).toBe('manual')
|
|
85
|
+
expect(result.blockers).toEqual([
|
|
86
|
+
{
|
|
87
|
+
code: 'unsupported-build-tool',
|
|
88
|
+
message: 'Detected unsupported build tool(s): Next.js',
|
|
89
|
+
},
|
|
90
|
+
])
|
|
91
|
+
expect(result.actions).toEqual([
|
|
92
|
+
{
|
|
93
|
+
type: 'manual_step',
|
|
94
|
+
target: 'Next.js',
|
|
95
|
+
description:
|
|
96
|
+
'Inspecto cannot auto-configure this build stack yet. Follow the manual setup guide for the detected framework or build tool.',
|
|
97
|
+
},
|
|
98
|
+
])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('blocks when no supported build tool is detected', () => {
|
|
102
|
+
const context: OnboardingContext = {
|
|
103
|
+
root: '/repo',
|
|
104
|
+
packageManager: 'pnpm',
|
|
105
|
+
buildTools: {
|
|
106
|
+
supported: [],
|
|
107
|
+
unsupported: [],
|
|
108
|
+
},
|
|
109
|
+
frameworks: {
|
|
110
|
+
supported: ['react'],
|
|
111
|
+
unsupported: [],
|
|
112
|
+
},
|
|
113
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
114
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = planner.createPlanResult(context)
|
|
118
|
+
|
|
119
|
+
expect(result.status).toBe('blocked')
|
|
120
|
+
expect(result.strategy).toBe('manual')
|
|
121
|
+
expect(result.blockers).toEqual([
|
|
122
|
+
{
|
|
123
|
+
code: 'missing-build-tool',
|
|
124
|
+
message: 'No supported build tool detected',
|
|
125
|
+
},
|
|
126
|
+
])
|
|
127
|
+
expect(result.actions).toEqual([
|
|
128
|
+
{
|
|
129
|
+
type: 'manual_step',
|
|
130
|
+
target: '/repo',
|
|
131
|
+
description:
|
|
132
|
+
'No supported build tool was detected. Add a supported build config before trying Inspecto again.',
|
|
133
|
+
},
|
|
134
|
+
])
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('blocks on an unsupported framework and uses framework-specific manual guidance', () => {
|
|
138
|
+
const context: OnboardingContext = {
|
|
139
|
+
root: '/repo',
|
|
140
|
+
packageManager: 'pnpm',
|
|
141
|
+
buildTools: {
|
|
142
|
+
supported: [
|
|
143
|
+
{
|
|
144
|
+
tool: 'vite',
|
|
145
|
+
configPath: 'vite.config.ts',
|
|
146
|
+
label: 'Vite (vite.config.ts)',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
unsupported: [],
|
|
150
|
+
},
|
|
151
|
+
frameworks: {
|
|
152
|
+
supported: [],
|
|
153
|
+
unsupported: ['Svelte'],
|
|
154
|
+
},
|
|
155
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
156
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result = planner.createPlanResult(context)
|
|
160
|
+
|
|
161
|
+
expect(result.status).toBe('blocked')
|
|
162
|
+
expect(result.strategy).toBe('manual')
|
|
163
|
+
expect(result.blockers).toEqual([
|
|
164
|
+
{
|
|
165
|
+
code: 'unsupported-framework',
|
|
166
|
+
message: 'Detected unsupported framework(s): Svelte',
|
|
167
|
+
},
|
|
168
|
+
])
|
|
169
|
+
expect(result.actions).toEqual([
|
|
170
|
+
{
|
|
171
|
+
type: 'manual_step',
|
|
172
|
+
target: 'Svelte',
|
|
173
|
+
description:
|
|
174
|
+
'Inspecto cannot auto-configure this framework yet. Follow the manual setup guide for the detected framework.',
|
|
175
|
+
},
|
|
176
|
+
])
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('keeps framework findings as warnings when a supported framework and unsupported build tool are both present', () => {
|
|
180
|
+
const context: OnboardingContext = {
|
|
181
|
+
root: '/repo',
|
|
182
|
+
packageManager: 'pnpm',
|
|
183
|
+
buildTools: {
|
|
184
|
+
supported: [
|
|
185
|
+
{
|
|
186
|
+
tool: 'vite',
|
|
187
|
+
configPath: 'vite.config.ts',
|
|
188
|
+
label: 'Vite (vite.config.ts)',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
unsupported: ['Next.js'],
|
|
192
|
+
},
|
|
193
|
+
frameworks: {
|
|
194
|
+
supported: ['react'],
|
|
195
|
+
unsupported: ['Svelte'],
|
|
196
|
+
},
|
|
197
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
198
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const result = planner.createPlanResult(context)
|
|
202
|
+
|
|
203
|
+
expect(result.status).toBe('blocked')
|
|
204
|
+
expect(result.strategy).toBe('manual')
|
|
205
|
+
expect(result.blockers).toEqual([
|
|
206
|
+
{
|
|
207
|
+
code: 'unsupported-build-tool',
|
|
208
|
+
message: 'Detected unsupported build tool(s): Next.js',
|
|
209
|
+
},
|
|
210
|
+
])
|
|
211
|
+
expect(result.warnings).toEqual([
|
|
212
|
+
{
|
|
213
|
+
code: 'unsupported-framework-present',
|
|
214
|
+
message: 'Unsupported framework(s) also detected: Svelte',
|
|
215
|
+
},
|
|
216
|
+
])
|
|
217
|
+
expect(result.actions).toEqual([
|
|
218
|
+
{
|
|
219
|
+
type: 'manual_step',
|
|
220
|
+
target: 'Next.js',
|
|
221
|
+
description:
|
|
222
|
+
'Inspecto cannot auto-configure this build stack yet. Follow the manual setup guide for the detected framework or build tool.',
|
|
223
|
+
},
|
|
224
|
+
])
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('returns supported setup actions when the stack is supported', () => {
|
|
228
|
+
const context: OnboardingContext = {
|
|
229
|
+
root: '/repo',
|
|
230
|
+
packageManager: 'pnpm',
|
|
231
|
+
buildTools: {
|
|
232
|
+
supported: [
|
|
233
|
+
{
|
|
234
|
+
tool: 'vite',
|
|
235
|
+
configPath: 'vite.config.ts',
|
|
236
|
+
label: 'Vite (vite.config.ts)',
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
unsupported: [],
|
|
240
|
+
},
|
|
241
|
+
frameworks: {
|
|
242
|
+
supported: ['react'],
|
|
243
|
+
unsupported: [],
|
|
244
|
+
},
|
|
245
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
246
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const result = planner.createPlanResult(context)
|
|
250
|
+
|
|
251
|
+
expect(result.status).toBe('ok')
|
|
252
|
+
expect(result.strategy).toBe('supported')
|
|
253
|
+
expect(result.actions).toEqual([
|
|
254
|
+
{
|
|
255
|
+
type: 'install_dependency',
|
|
256
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
257
|
+
description: 'Install the Inspecto runtime packages with pnpm.',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
type: 'modify_file',
|
|
261
|
+
target: 'vite.config.ts',
|
|
262
|
+
description: 'Inject the Inspecto plugin into Vite (vite.config.ts).',
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
type: 'install_extension',
|
|
266
|
+
target: 'vscode',
|
|
267
|
+
description: 'Install the Inspecto VS Code extension.',
|
|
268
|
+
},
|
|
269
|
+
])
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('blocks root-level apply planning when multiple supported build targets are detected', () => {
|
|
273
|
+
const context: OnboardingContext = {
|
|
274
|
+
root: '/repo',
|
|
275
|
+
packageManager: 'pnpm',
|
|
276
|
+
buildTools: {
|
|
277
|
+
supported: [
|
|
278
|
+
{
|
|
279
|
+
tool: 'vite',
|
|
280
|
+
configPath: 'apps/web/vite.config.ts',
|
|
281
|
+
label: 'Vite (apps/web/vite.config.ts)',
|
|
282
|
+
packagePath: 'apps/web',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
tool: 'webpack',
|
|
286
|
+
configPath: 'apps/admin/webpack.config.js',
|
|
287
|
+
label: 'Webpack (apps/admin/webpack.config.js)',
|
|
288
|
+
packagePath: 'apps/admin',
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
unsupported: [],
|
|
292
|
+
},
|
|
293
|
+
frameworks: {
|
|
294
|
+
supported: ['react'],
|
|
295
|
+
unsupported: [],
|
|
296
|
+
},
|
|
297
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
298
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const result = planner.createPlanResult(context)
|
|
302
|
+
|
|
303
|
+
expect(result.status).toBe('blocked')
|
|
304
|
+
expect(result.strategy).toBe('manual')
|
|
305
|
+
expect(result.blockers).toEqual([
|
|
306
|
+
{
|
|
307
|
+
code: 'multiple-supported-build-targets',
|
|
308
|
+
message:
|
|
309
|
+
'Multiple supported build targets detected: apps/web, apps/admin. Run inspecto apply from a single app/package root until explicit target selection is available.',
|
|
310
|
+
},
|
|
311
|
+
])
|
|
312
|
+
expect(result.actions).toEqual([
|
|
313
|
+
{
|
|
314
|
+
type: 'manual_step',
|
|
315
|
+
target: 'apps/web, apps/admin',
|
|
316
|
+
description:
|
|
317
|
+
'Run inspecto apply from the target app/package root. Root-level apply is blocked when multiple supported targets are detected.',
|
|
318
|
+
},
|
|
319
|
+
])
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('creates a detection result from the shared onboarding context', async () => {
|
|
323
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue({
|
|
324
|
+
root: '/repo',
|
|
325
|
+
packageManager: 'pnpm',
|
|
326
|
+
buildTools: {
|
|
327
|
+
supported: [
|
|
328
|
+
{
|
|
329
|
+
tool: 'vite',
|
|
330
|
+
configPath: 'vite.config.ts',
|
|
331
|
+
label: 'Vite (vite.config.ts)',
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
unsupported: ['Next.js'],
|
|
335
|
+
},
|
|
336
|
+
frameworks: {
|
|
337
|
+
supported: ['react'],
|
|
338
|
+
unsupported: ['Svelte'],
|
|
339
|
+
},
|
|
340
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
341
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const result = await planner.createDetectionResult('/repo')
|
|
345
|
+
|
|
346
|
+
expect(result.status).toBe('blocked')
|
|
347
|
+
expect(result.project).toEqual({
|
|
348
|
+
root: '/repo',
|
|
349
|
+
packageManager: 'pnpm',
|
|
350
|
+
})
|
|
351
|
+
expect(result.environment).toEqual({
|
|
352
|
+
frameworks: ['react'],
|
|
353
|
+
unsupportedFrameworks: ['Svelte'],
|
|
354
|
+
buildTools: [
|
|
355
|
+
{
|
|
356
|
+
tool: 'vite',
|
|
357
|
+
configPath: 'vite.config.ts',
|
|
358
|
+
label: 'Vite (vite.config.ts)',
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
unsupportedBuildTools: ['Next.js'],
|
|
362
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
363
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
364
|
+
})
|
|
365
|
+
expect(result.blockers).toEqual([
|
|
366
|
+
{
|
|
367
|
+
code: 'unsupported-build-tool',
|
|
368
|
+
message: 'Detected unsupported build tool(s): Next.js',
|
|
369
|
+
},
|
|
370
|
+
])
|
|
371
|
+
expect(result.warnings).toEqual([
|
|
372
|
+
{
|
|
373
|
+
code: 'unsupported-framework-present',
|
|
374
|
+
message: 'Unsupported framework(s) also detected: Svelte',
|
|
375
|
+
},
|
|
376
|
+
])
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('detects through the detect command in JSON mode', async () => {
|
|
380
|
+
const detectionResult = {
|
|
381
|
+
status: 'blocked',
|
|
382
|
+
warnings: [{ code: 'w1', message: 'warn' }],
|
|
383
|
+
blockers: [{ code: 'b1', message: 'block' }],
|
|
384
|
+
project: { root: '/repo', packageManager: 'pnpm' },
|
|
385
|
+
environment: {
|
|
386
|
+
frameworks: ['react'],
|
|
387
|
+
unsupportedFrameworks: ['Svelte'],
|
|
388
|
+
buildTools: [],
|
|
389
|
+
unsupportedBuildTools: ['Next.js'],
|
|
390
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
391
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true }],
|
|
392
|
+
},
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const detectSpy = vi
|
|
396
|
+
.spyOn(planner, 'createDetectionResult')
|
|
397
|
+
.mockResolvedValue(detectionResult as never)
|
|
398
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
|
399
|
+
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue('/repo')
|
|
400
|
+
|
|
401
|
+
const result = await detect(true)
|
|
402
|
+
|
|
403
|
+
expect(detectSpy).toHaveBeenCalledWith('/repo')
|
|
404
|
+
expect(logSpy).toHaveBeenCalledWith(JSON.stringify(detectionResult, null, 2))
|
|
405
|
+
expect(result).toEqual(detectionResult)
|
|
406
|
+
|
|
407
|
+
cwdSpy.mockRestore()
|
|
408
|
+
logSpy.mockRestore()
|
|
409
|
+
detectSpy.mockRestore()
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
it('prints unsupported environment findings only once in plain-text detect mode', async () => {
|
|
413
|
+
const detectionResult = {
|
|
414
|
+
status: 'blocked',
|
|
415
|
+
warnings: [
|
|
416
|
+
{
|
|
417
|
+
code: 'unsupported-framework-present',
|
|
418
|
+
message: 'Unsupported framework(s) also detected: Svelte',
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
blockers: [
|
|
422
|
+
{
|
|
423
|
+
code: 'unsupported-build-tool',
|
|
424
|
+
message: 'Detected unsupported build tool(s): Next.js',
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
code: 'unsupported-framework',
|
|
428
|
+
message: 'Detected unsupported framework(s): Svelte',
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
project: { root: '/repo', packageManager: 'pnpm' },
|
|
432
|
+
environment: {
|
|
433
|
+
frameworks: ['react'],
|
|
434
|
+
unsupportedFrameworks: ['Svelte'],
|
|
435
|
+
buildTools: [],
|
|
436
|
+
unsupportedBuildTools: ['Next.js'],
|
|
437
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
438
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true }],
|
|
439
|
+
},
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const detectSpy = vi
|
|
443
|
+
.spyOn(planner, 'createDetectionResult')
|
|
444
|
+
.mockResolvedValue(detectionResult as never)
|
|
445
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
|
446
|
+
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue('/repo')
|
|
447
|
+
|
|
448
|
+
await detect(false)
|
|
449
|
+
|
|
450
|
+
const output = logSpy.mock.calls.map(call => call.join(' ')).join('\n')
|
|
451
|
+
|
|
452
|
+
expect(detectSpy).toHaveBeenCalledWith('/repo')
|
|
453
|
+
expect(output).toContain('Unsupported frameworks: Svelte')
|
|
454
|
+
expect(output).toContain('Unsupported build tools: Next.js')
|
|
455
|
+
expect(output).not.toContain('Detected unsupported framework(s): Svelte')
|
|
456
|
+
expect(output).not.toContain('Detected unsupported build tool(s): Next.js')
|
|
457
|
+
expect(output.match(/Unsupported frameworks: Svelte/g)).toHaveLength(1)
|
|
458
|
+
expect(output.match(/Unsupported build tools: Next.js/g)).toHaveLength(1)
|
|
459
|
+
expect(output.match(/Svelte/g)).toHaveLength(1)
|
|
460
|
+
|
|
461
|
+
cwdSpy.mockRestore()
|
|
462
|
+
logSpy.mockRestore()
|
|
463
|
+
detectSpy.mockRestore()
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
it('plans through the plan command in JSON mode', async () => {
|
|
467
|
+
const context: OnboardingContext = {
|
|
468
|
+
root: '/repo',
|
|
469
|
+
packageManager: 'pnpm',
|
|
470
|
+
buildTools: {
|
|
471
|
+
supported: [
|
|
472
|
+
{
|
|
473
|
+
tool: 'vite',
|
|
474
|
+
configPath: 'vite.config.ts',
|
|
475
|
+
label: 'Vite (vite.config.ts)',
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
unsupported: [],
|
|
479
|
+
},
|
|
480
|
+
frameworks: {
|
|
481
|
+
supported: ['react'],
|
|
482
|
+
unsupported: [],
|
|
483
|
+
},
|
|
484
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
485
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const planResult = {
|
|
489
|
+
status: 'ok',
|
|
490
|
+
warnings: [],
|
|
491
|
+
blockers: [],
|
|
492
|
+
strategy: 'supported',
|
|
493
|
+
actions: [
|
|
494
|
+
{
|
|
495
|
+
type: 'install_dependency',
|
|
496
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
497
|
+
description: 'Install the Inspecto runtime packages with pnpm.',
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
defaults: {
|
|
501
|
+
provider: 'codex',
|
|
502
|
+
ide: 'vscode',
|
|
503
|
+
shared: false,
|
|
504
|
+
extension: true,
|
|
505
|
+
},
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const buildContextSpy = vi
|
|
509
|
+
.spyOn(onboardingContext, 'buildOnboardingContext')
|
|
510
|
+
.mockResolvedValue(context)
|
|
511
|
+
const planSpy = vi.spyOn(planner, 'createPlanResult').mockReturnValue(planResult as never)
|
|
512
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
|
513
|
+
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue('/repo')
|
|
514
|
+
|
|
515
|
+
const result = await plan(true)
|
|
516
|
+
|
|
517
|
+
expect(buildContextSpy).toHaveBeenCalledWith('/repo')
|
|
518
|
+
expect(planSpy).toHaveBeenCalledWith(context)
|
|
519
|
+
expect(logSpy).toHaveBeenCalledWith(JSON.stringify(planResult, null, 2))
|
|
520
|
+
expect(result).toEqual(planResult)
|
|
521
|
+
|
|
522
|
+
cwdSpy.mockRestore()
|
|
523
|
+
logSpy.mockRestore()
|
|
524
|
+
planSpy.mockRestore()
|
|
525
|
+
buildContextSpy.mockRestore()
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('prints a representative supported plan in plain text without duplicating status or action details', async () => {
|
|
529
|
+
const context: OnboardingContext = {
|
|
530
|
+
root: '/repo',
|
|
531
|
+
packageManager: 'pnpm',
|
|
532
|
+
buildTools: {
|
|
533
|
+
supported: [
|
|
534
|
+
{
|
|
535
|
+
tool: 'vite',
|
|
536
|
+
configPath: 'vite.config.ts',
|
|
537
|
+
label: 'Vite (vite.config.ts)',
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
unsupported: [],
|
|
541
|
+
},
|
|
542
|
+
frameworks: {
|
|
543
|
+
supported: ['react'],
|
|
544
|
+
unsupported: [],
|
|
545
|
+
},
|
|
546
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
547
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const planResult = {
|
|
551
|
+
status: 'ok',
|
|
552
|
+
warnings: [],
|
|
553
|
+
blockers: [],
|
|
554
|
+
strategy: 'supported',
|
|
555
|
+
actions: [
|
|
556
|
+
{
|
|
557
|
+
type: 'install_dependency',
|
|
558
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
559
|
+
description: 'Install the Inspecto runtime packages with pnpm.',
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
type: 'modify_file',
|
|
563
|
+
target: 'vite.config.ts',
|
|
564
|
+
description: 'Inject the Inspecto plugin into Vite (vite.config.ts).',
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
type: 'install_extension',
|
|
568
|
+
target: 'vscode',
|
|
569
|
+
description: 'Install the Inspecto VS Code extension.',
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
defaults: {
|
|
573
|
+
provider: 'codex',
|
|
574
|
+
ide: 'vscode',
|
|
575
|
+
shared: false,
|
|
576
|
+
extension: true,
|
|
577
|
+
},
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const buildContextSpy = vi
|
|
581
|
+
.spyOn(onboardingContext, 'buildOnboardingContext')
|
|
582
|
+
.mockResolvedValue(context)
|
|
583
|
+
const planSpy = vi.spyOn(planner, 'createPlanResult').mockReturnValue(planResult as never)
|
|
584
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
|
585
|
+
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue('/repo')
|
|
586
|
+
|
|
587
|
+
await plan(false)
|
|
588
|
+
|
|
589
|
+
const output = logSpy.mock.calls.map(call => call.join(' ')).join('\n')
|
|
590
|
+
|
|
591
|
+
expect(buildContextSpy).toHaveBeenCalledWith('/repo')
|
|
592
|
+
expect(planSpy).toHaveBeenCalledWith(context)
|
|
593
|
+
expect(output).toContain('Status: ok')
|
|
594
|
+
expect(output).toContain('Strategy: supported')
|
|
595
|
+
expect(output).toContain('Default provider: codex')
|
|
596
|
+
expect(output).toContain('Default IDE: vscode')
|
|
597
|
+
expect(output).toContain('Shared mode: disabled')
|
|
598
|
+
expect(output).toContain('Extension mode: enabled')
|
|
599
|
+
expect(output).toContain('Actions:')
|
|
600
|
+
expect(output).toContain('install_dependency: @inspecto-dev/plugin @inspecto-dev/core')
|
|
601
|
+
expect(output).toContain('modify_file: vite.config.ts')
|
|
602
|
+
expect(output).toContain('install_extension: vscode')
|
|
603
|
+
expect(output).toContain('Install the Inspecto runtime packages with pnpm.')
|
|
604
|
+
expect(output).toContain('Inject the Inspecto plugin into Vite (vite.config.ts).')
|
|
605
|
+
expect(output).toContain('Install the Inspecto VS Code extension.')
|
|
606
|
+
expect(output).not.toContain('Detected unsupported')
|
|
607
|
+
expect(output.match(/Status: ok/g)).toHaveLength(1)
|
|
608
|
+
expect(output.match(/Strategy: supported/g)).toHaveLength(1)
|
|
609
|
+
|
|
610
|
+
cwdSpy.mockRestore()
|
|
611
|
+
logSpy.mockRestore()
|
|
612
|
+
planSpy.mockRestore()
|
|
613
|
+
buildContextSpy.mockRestore()
|
|
614
|
+
})
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
describe('cli entrypoint wiring', () => {
|
|
618
|
+
beforeEach(() => {
|
|
619
|
+
vi.resetModules()
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
it('registers and dispatches detect, plan, and apply commands', async () => {
|
|
623
|
+
const doctorCommand = vi.fn().mockResolvedValue(undefined)
|
|
624
|
+
const detectCommand = vi.fn().mockResolvedValue(undefined)
|
|
625
|
+
const planCommand = vi.fn().mockResolvedValue(undefined)
|
|
626
|
+
const applyCommand = vi.fn().mockResolvedValue(undefined)
|
|
627
|
+
|
|
628
|
+
vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
|
|
629
|
+
vi.doMock('../src/commands/doctor.js', () => ({ doctor: doctorCommand }))
|
|
630
|
+
vi.doMock('../src/commands/teardown.js', () => ({ teardown: vi.fn() }))
|
|
631
|
+
vi.doMock('../src/commands/detect.js', () => ({ detect: detectCommand }))
|
|
632
|
+
vi.doMock('../src/commands/plan.js', () => ({ plan: planCommand }))
|
|
633
|
+
vi.doMock('../src/commands/apply.js', () => ({ apply: applyCommand }))
|
|
634
|
+
|
|
635
|
+
const { createCli, runCli } = await import('../src/bin.js')
|
|
636
|
+
const cli = createCli()
|
|
637
|
+
const commandNames = (cli as { commands: Array<{ rawName: string }> }).commands.map(command =>
|
|
638
|
+
command.rawName.replace(/\s.*$/, ''),
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
expect(commandNames).toEqual(
|
|
642
|
+
expect.arrayContaining(['init', 'doctor', 'teardown', 'detect', 'plan', 'apply']),
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
await runCli(['node', 'inspecto', 'doctor', '--json'])
|
|
646
|
+
await runCli(['node', 'inspecto', 'detect', '--json'])
|
|
647
|
+
await runCli(['node', 'inspecto', 'plan', '--json'])
|
|
648
|
+
await runCli(['node', 'inspecto', 'apply', '--json'])
|
|
649
|
+
|
|
650
|
+
expect(doctorCommand).toHaveBeenCalledWith({ json: true })
|
|
651
|
+
expect(detectCommand).toHaveBeenCalledWith(true)
|
|
652
|
+
expect(planCommand).toHaveBeenCalledWith(true)
|
|
653
|
+
expect(applyCommand).toHaveBeenCalledWith({ json: true })
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
it('forwards parsed apply flags without relying on raw argv checks', async () => {
|
|
657
|
+
const applyCommand = vi.fn().mockResolvedValue(undefined)
|
|
658
|
+
|
|
659
|
+
vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
|
|
660
|
+
vi.doMock('../src/commands/doctor.js', () => ({ doctor: vi.fn() }))
|
|
661
|
+
vi.doMock('../src/commands/teardown.js', () => ({ teardown: vi.fn() }))
|
|
662
|
+
vi.doMock('../src/commands/detect.js', () => ({ detect: vi.fn() }))
|
|
663
|
+
vi.doMock('../src/commands/plan.js', () => ({ plan: vi.fn() }))
|
|
664
|
+
vi.doMock('../src/commands/apply.js', () => ({ apply: applyCommand }))
|
|
665
|
+
|
|
666
|
+
const { runCli } = await import('../src/bin.js')
|
|
667
|
+
|
|
668
|
+
await runCli([
|
|
669
|
+
'node',
|
|
670
|
+
'inspecto',
|
|
671
|
+
'apply',
|
|
672
|
+
'--shared',
|
|
673
|
+
'--skip-install',
|
|
674
|
+
'--dry-run',
|
|
675
|
+
'--no-extension',
|
|
676
|
+
])
|
|
677
|
+
|
|
678
|
+
expect(applyCommand).toHaveBeenCalledWith({
|
|
679
|
+
json: false,
|
|
680
|
+
shared: true,
|
|
681
|
+
skipInstall: true,
|
|
682
|
+
dryRun: true,
|
|
683
|
+
noExtension: true,
|
|
684
|
+
})
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
it('emits JSON-safe errors for json commands on failure', async () => {
|
|
688
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
689
|
+
const processExitSpy = vi
|
|
690
|
+
.spyOn(process, 'exit')
|
|
691
|
+
.mockImplementation((() => undefined) as (code?: string | number | null | undefined) => never)
|
|
692
|
+
|
|
693
|
+
vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
|
|
694
|
+
vi.doMock('../src/commands/doctor.js', () => ({ doctor: vi.fn() }))
|
|
695
|
+
vi.doMock('../src/commands/teardown.js', () => ({ teardown: vi.fn() }))
|
|
696
|
+
vi.doMock('../src/commands/detect.js', () => ({ detect: vi.fn() }))
|
|
697
|
+
vi.doMock('../src/commands/plan.js', () => ({ plan: vi.fn() }))
|
|
698
|
+
vi.doMock('../src/commands/apply.js', () => ({ apply: vi.fn() }))
|
|
699
|
+
|
|
700
|
+
const { runCli } = await import('../src/bin.js')
|
|
701
|
+
|
|
702
|
+
await runCli(['node', 'inspecto', 'apply', '--json', '--wat'])
|
|
703
|
+
|
|
704
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1)
|
|
705
|
+
expect(JSON.parse(String(consoleErrorSpy.mock.calls[0]?.[0]))).toMatchObject({
|
|
706
|
+
status: 'error',
|
|
707
|
+
error: {
|
|
708
|
+
message: expect.stringContaining('--wat'),
|
|
709
|
+
},
|
|
710
|
+
})
|
|
711
|
+
expect(processExitSpy).toHaveBeenCalledWith(1)
|
|
712
|
+
})
|
|
713
|
+
})
|