@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,583 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { apply } from '../src/commands/apply.js'
|
|
3
|
+
import { applyOnboardingPlan } from '../src/onboarding/apply.js'
|
|
4
|
+
import * as onboardingContext from '../src/onboarding/context.js'
|
|
5
|
+
import * as planner from '../src/onboarding/planner.js'
|
|
6
|
+
import * as packageManagerUtils from '../src/detect/package-manager.js'
|
|
7
|
+
import * as fsUtils from '../src/utils/fs.js'
|
|
8
|
+
import * as execUtils from '../src/utils/exec.js'
|
|
9
|
+
import * as astInjectorUtils from '../src/inject/ast-injector.js'
|
|
10
|
+
import * as gitignoreUtils from '../src/inject/gitignore.js'
|
|
11
|
+
import * as extensionUtils from '../src/inject/extension.js'
|
|
12
|
+
import type { BuildToolDetection, OnboardingContext, PlanResult } from '../src/types.js'
|
|
13
|
+
|
|
14
|
+
vi.mock('../src/onboarding/context.js', () => ({
|
|
15
|
+
buildOnboardingContext: vi.fn(),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
vi.mock('../src/onboarding/planner.js', async () => {
|
|
19
|
+
const actual = await vi.importActual('../src/onboarding/planner.js')
|
|
20
|
+
return {
|
|
21
|
+
...actual,
|
|
22
|
+
createPlanResult: vi.fn(),
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
vi.mock('../src/detect/package-manager.js', async () => {
|
|
27
|
+
const actual = await vi.importActual('../src/detect/package-manager.js')
|
|
28
|
+
return {
|
|
29
|
+
...actual,
|
|
30
|
+
getInstallCommand: vi.fn(),
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
vi.mock('../src/utils/fs.js', () => ({
|
|
35
|
+
exists: vi.fn(),
|
|
36
|
+
readJSON: vi.fn(),
|
|
37
|
+
writeJSON: vi.fn(),
|
|
38
|
+
}))
|
|
39
|
+
|
|
40
|
+
vi.mock('../src/utils/exec.js', () => ({
|
|
41
|
+
shell: vi.fn(),
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
vi.mock('../src/detect/build-tool.js', async () => {
|
|
45
|
+
const actual = await vi.importActual('../src/detect/build-tool.js')
|
|
46
|
+
return {
|
|
47
|
+
...actual,
|
|
48
|
+
resolveInjectionTarget: vi.fn(),
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
vi.mock('../src/inject/ast-injector.js', () => ({
|
|
53
|
+
injectPlugin: vi.fn(),
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
vi.mock('../src/inject/gitignore.js', () => ({
|
|
57
|
+
updateGitignore: vi.fn(),
|
|
58
|
+
}))
|
|
59
|
+
|
|
60
|
+
vi.mock('../src/inject/extension.js', () => ({
|
|
61
|
+
installExtension: vi.fn(),
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
describe('apply onboarding flow', () => {
|
|
65
|
+
const supportedBuild: BuildToolDetection = {
|
|
66
|
+
tool: 'vite',
|
|
67
|
+
configPath: 'vite.config.ts',
|
|
68
|
+
label: 'Vite (vite.config.ts)',
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
vi.resetAllMocks()
|
|
73
|
+
vi.spyOn(process, 'cwd').mockReturnValue('/repo')
|
|
74
|
+
vi.mocked(packageManagerUtils.getInstallCommand).mockReturnValue(
|
|
75
|
+
'pnpm add -D @inspecto-dev/plugin @inspecto-dev/core',
|
|
76
|
+
)
|
|
77
|
+
vi.mocked(execUtils.shell).mockResolvedValue({ stdout: '', stderr: '' })
|
|
78
|
+
vi.mocked(astInjectorUtils.injectPlugin).mockResolvedValue({
|
|
79
|
+
success: true,
|
|
80
|
+
mutations: [{ type: 'file_modified', path: 'vite.config.ts' }],
|
|
81
|
+
})
|
|
82
|
+
vi.mocked(fsUtils.exists).mockResolvedValue(false)
|
|
83
|
+
vi.mocked(fsUtils.readJSON).mockResolvedValue(null)
|
|
84
|
+
vi.mocked(gitignoreUtils.updateGitignore).mockResolvedValue()
|
|
85
|
+
vi.mocked(extensionUtils.installExtension).mockResolvedValue({
|
|
86
|
+
type: 'extension_installed',
|
|
87
|
+
id: 'inspecto.inspecto',
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('applies the supported setup path and returns structured mutations and post-install state', async () => {
|
|
92
|
+
const result = await applyOnboardingPlan({
|
|
93
|
+
repoRoot: '/repo',
|
|
94
|
+
projectRoot: '/repo',
|
|
95
|
+
packageManager: 'pnpm',
|
|
96
|
+
supportedBuildTargets: [supportedBuild],
|
|
97
|
+
options: {
|
|
98
|
+
shared: false,
|
|
99
|
+
skipInstall: false,
|
|
100
|
+
dryRun: false,
|
|
101
|
+
noExtension: false,
|
|
102
|
+
},
|
|
103
|
+
selectedIDE: { ide: 'vscode', supported: true },
|
|
104
|
+
providerDefault: 'codex.extension',
|
|
105
|
+
plan: {
|
|
106
|
+
status: 'ok',
|
|
107
|
+
warnings: [],
|
|
108
|
+
blockers: [],
|
|
109
|
+
strategy: 'supported',
|
|
110
|
+
actions: [],
|
|
111
|
+
defaults: {
|
|
112
|
+
shared: false,
|
|
113
|
+
extension: true,
|
|
114
|
+
provider: 'codex',
|
|
115
|
+
ide: 'vscode',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
expect(execUtils.shell).toHaveBeenCalledWith(
|
|
121
|
+
'pnpm add -D @inspecto-dev/plugin @inspecto-dev/core',
|
|
122
|
+
'/repo',
|
|
123
|
+
)
|
|
124
|
+
expect(astInjectorUtils.injectPlugin).toHaveBeenCalledWith('/repo', supportedBuild, false, false)
|
|
125
|
+
expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/settings.local.json', {
|
|
126
|
+
ide: 'vscode',
|
|
127
|
+
'provider.default': 'codex.extension',
|
|
128
|
+
})
|
|
129
|
+
expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/prompts.local.json', [])
|
|
130
|
+
expect(fsUtils.writeJSON).toHaveBeenCalledWith(
|
|
131
|
+
'/repo/.inspecto/install.lock',
|
|
132
|
+
expect.objectContaining({
|
|
133
|
+
version: '1.0.0',
|
|
134
|
+
mutations: expect.arrayContaining([
|
|
135
|
+
{ type: 'dependency_added', name: '@inspecto-dev/plugin', dev: true },
|
|
136
|
+
{ type: 'dependency_added', name: '@inspecto-dev/core', dev: true },
|
|
137
|
+
{ type: 'file_modified', path: 'vite.config.ts' },
|
|
138
|
+
{ type: 'file_created', path: '.inspecto/settings.local.json' },
|
|
139
|
+
{ type: 'file_created', path: '.inspecto/prompts.local.json' },
|
|
140
|
+
{
|
|
141
|
+
type: 'file_modified',
|
|
142
|
+
path: '.gitignore',
|
|
143
|
+
description: 'Appended .inspecto/ ignore rules',
|
|
144
|
+
},
|
|
145
|
+
{ type: 'extension_installed', id: 'inspecto.inspecto' },
|
|
146
|
+
]),
|
|
147
|
+
}),
|
|
148
|
+
)
|
|
149
|
+
expect(result).toMatchObject({
|
|
150
|
+
status: 'ok',
|
|
151
|
+
mutations: expect.arrayContaining([
|
|
152
|
+
{ type: 'dependency_added', name: '@inspecto-dev/plugin', dev: true },
|
|
153
|
+
{ type: 'extension_installed', id: 'inspecto.inspecto' },
|
|
154
|
+
]),
|
|
155
|
+
postInstall: {
|
|
156
|
+
installFailed: false,
|
|
157
|
+
injectionFailed: false,
|
|
158
|
+
manualExtensionInstallNeeded: false,
|
|
159
|
+
nextSteps: [],
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('uses onboarding context and planner output and prints JSON from the apply command', async () => {
|
|
165
|
+
const context: OnboardingContext = {
|
|
166
|
+
root: '/repo',
|
|
167
|
+
packageManager: 'pnpm',
|
|
168
|
+
buildTools: {
|
|
169
|
+
supported: [supportedBuild],
|
|
170
|
+
unsupported: [],
|
|
171
|
+
},
|
|
172
|
+
frameworks: {
|
|
173
|
+
supported: ['react'],
|
|
174
|
+
unsupported: [],
|
|
175
|
+
},
|
|
176
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
177
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const planResult: PlanResult = {
|
|
181
|
+
status: 'ok',
|
|
182
|
+
warnings: [],
|
|
183
|
+
blockers: [],
|
|
184
|
+
strategy: 'supported',
|
|
185
|
+
actions: [
|
|
186
|
+
{
|
|
187
|
+
type: 'install_dependency',
|
|
188
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
189
|
+
description: 'Install dependencies.',
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
defaults: {
|
|
193
|
+
provider: 'codex',
|
|
194
|
+
ide: 'vscode',
|
|
195
|
+
shared: false,
|
|
196
|
+
extension: true,
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
201
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
202
|
+
|
|
203
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
204
|
+
|
|
205
|
+
const result = await apply({ json: true })
|
|
206
|
+
|
|
207
|
+
expect(onboardingContext.buildOnboardingContext).toHaveBeenCalledWith('/repo')
|
|
208
|
+
expect(planner.createPlanResult).toHaveBeenCalledWith(context)
|
|
209
|
+
expect(consoleSpy).toHaveBeenCalledTimes(1)
|
|
210
|
+
expect(consoleSpy).toHaveBeenCalledWith(JSON.stringify(result, null, 2))
|
|
211
|
+
expect(result.plan).toEqual(planResult)
|
|
212
|
+
expect(result.status).toBe('ok')
|
|
213
|
+
expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/settings.local.json', {
|
|
214
|
+
ide: 'vscode',
|
|
215
|
+
'provider.default': 'codex.cli',
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('does not run side effects for manual plans and returns manual next steps', async () => {
|
|
220
|
+
const context: OnboardingContext = {
|
|
221
|
+
root: '/repo',
|
|
222
|
+
packageManager: 'pnpm',
|
|
223
|
+
buildTools: {
|
|
224
|
+
supported: [],
|
|
225
|
+
unsupported: ['Next.js'],
|
|
226
|
+
},
|
|
227
|
+
frameworks: {
|
|
228
|
+
supported: ['react'],
|
|
229
|
+
unsupported: [],
|
|
230
|
+
},
|
|
231
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
232
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const planResult: PlanResult = {
|
|
236
|
+
status: 'blocked',
|
|
237
|
+
warnings: [],
|
|
238
|
+
blockers: [
|
|
239
|
+
{
|
|
240
|
+
code: 'unsupported-build-tool',
|
|
241
|
+
message: 'Detected unsupported build tool(s): Next.js',
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
strategy: 'manual',
|
|
245
|
+
actions: [
|
|
246
|
+
{
|
|
247
|
+
type: 'manual_step',
|
|
248
|
+
target: 'Next.js',
|
|
249
|
+
description: 'Follow the printed Next.js instructions.',
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
defaults: {
|
|
253
|
+
provider: 'codex',
|
|
254
|
+
ide: 'vscode',
|
|
255
|
+
shared: true,
|
|
256
|
+
extension: true,
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
261
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
262
|
+
|
|
263
|
+
const result = await apply({ json: true })
|
|
264
|
+
|
|
265
|
+
expect(execUtils.shell).not.toHaveBeenCalled()
|
|
266
|
+
expect(astInjectorUtils.injectPlugin).not.toHaveBeenCalled()
|
|
267
|
+
expect(gitignoreUtils.updateGitignore).not.toHaveBeenCalled()
|
|
268
|
+
expect(extensionUtils.installExtension).not.toHaveBeenCalled()
|
|
269
|
+
expect(fsUtils.writeJSON).not.toHaveBeenCalled()
|
|
270
|
+
expect(result.status).toBe('blocked')
|
|
271
|
+
expect(result.postInstall.nextSteps).toEqual([
|
|
272
|
+
'Detected unsupported build tool(s): Next.js',
|
|
273
|
+
'Follow the printed Next.js instructions.',
|
|
274
|
+
])
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('preserves warning status from the plan when execution adds no new next steps', async () => {
|
|
278
|
+
const context: OnboardingContext = {
|
|
279
|
+
root: '/repo',
|
|
280
|
+
packageManager: 'pnpm',
|
|
281
|
+
buildTools: {
|
|
282
|
+
supported: [supportedBuild],
|
|
283
|
+
unsupported: [],
|
|
284
|
+
},
|
|
285
|
+
frameworks: {
|
|
286
|
+
supported: ['react'],
|
|
287
|
+
unsupported: ['svelte'],
|
|
288
|
+
},
|
|
289
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
290
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const planResult: PlanResult = {
|
|
294
|
+
status: 'warning',
|
|
295
|
+
warnings: [
|
|
296
|
+
{
|
|
297
|
+
code: 'unsupported-framework-present',
|
|
298
|
+
message: 'Unsupported framework(s) also detected: svelte',
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
blockers: [],
|
|
302
|
+
strategy: 'supported',
|
|
303
|
+
actions: [
|
|
304
|
+
{
|
|
305
|
+
type: 'install_dependency',
|
|
306
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
307
|
+
description: 'Install dependencies.',
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
defaults: {
|
|
311
|
+
provider: 'codex',
|
|
312
|
+
ide: 'vscode',
|
|
313
|
+
shared: true,
|
|
314
|
+
extension: true,
|
|
315
|
+
},
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
319
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
320
|
+
|
|
321
|
+
const result = await apply({ json: true })
|
|
322
|
+
|
|
323
|
+
expect(result.status).toBe('warning')
|
|
324
|
+
expect(result.postInstall.nextSteps).toEqual([])
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('blocks apply when multiple supported build targets are detected from the repo root', async () => {
|
|
328
|
+
const context: OnboardingContext = {
|
|
329
|
+
root: '/repo',
|
|
330
|
+
packageManager: 'pnpm',
|
|
331
|
+
buildTools: {
|
|
332
|
+
supported: [
|
|
333
|
+
{
|
|
334
|
+
tool: 'vite',
|
|
335
|
+
configPath: 'apps/web/vite.config.ts',
|
|
336
|
+
label: 'Vite (apps/web/vite.config.ts)',
|
|
337
|
+
packagePath: 'apps/web',
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
tool: 'webpack',
|
|
341
|
+
configPath: 'apps/admin/webpack.config.js',
|
|
342
|
+
label: 'Webpack (apps/admin/webpack.config.js)',
|
|
343
|
+
packagePath: 'apps/admin',
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
unsupported: [],
|
|
347
|
+
},
|
|
348
|
+
frameworks: {
|
|
349
|
+
supported: ['react'],
|
|
350
|
+
unsupported: [],
|
|
351
|
+
},
|
|
352
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
353
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const planResult: PlanResult = {
|
|
357
|
+
status: 'blocked',
|
|
358
|
+
warnings: [],
|
|
359
|
+
blockers: [
|
|
360
|
+
{
|
|
361
|
+
code: 'multiple-supported-build-targets',
|
|
362
|
+
message:
|
|
363
|
+
'Multiple supported build targets detected: apps/web, apps/admin. Run inspecto apply from a single app/package root until explicit target selection is available.',
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
strategy: 'manual',
|
|
367
|
+
actions: [
|
|
368
|
+
{
|
|
369
|
+
type: 'manual_step',
|
|
370
|
+
target: 'apps/web, apps/admin',
|
|
371
|
+
description:
|
|
372
|
+
'Run inspecto apply from the target app/package root. Root-level apply is blocked when multiple supported targets are detected.',
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
defaults: {
|
|
376
|
+
provider: 'codex',
|
|
377
|
+
ide: 'vscode',
|
|
378
|
+
shared: true,
|
|
379
|
+
extension: true,
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
384
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
385
|
+
|
|
386
|
+
const result = await apply({ json: true })
|
|
387
|
+
|
|
388
|
+
expect(execUtils.shell).not.toHaveBeenCalled()
|
|
389
|
+
expect(astInjectorUtils.injectPlugin).not.toHaveBeenCalled()
|
|
390
|
+
expect(fsUtils.writeJSON).not.toHaveBeenCalled()
|
|
391
|
+
expect(result.status).toBe('blocked')
|
|
392
|
+
expect(result.postInstall.nextSteps).toEqual([
|
|
393
|
+
'Multiple supported build targets detected: apps/web, apps/admin. Run inspecto apply from a single app/package root until explicit target selection is available.',
|
|
394
|
+
'Run inspecto apply from the target app/package root. Root-level apply is blocked when multiple supported targets are detected.',
|
|
395
|
+
])
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('prints planner warnings in plain-text apply output', async () => {
|
|
399
|
+
const context: OnboardingContext = {
|
|
400
|
+
root: '/repo',
|
|
401
|
+
packageManager: 'pnpm',
|
|
402
|
+
buildTools: {
|
|
403
|
+
supported: [supportedBuild],
|
|
404
|
+
unsupported: [],
|
|
405
|
+
},
|
|
406
|
+
frameworks: {
|
|
407
|
+
supported: ['react'],
|
|
408
|
+
unsupported: ['svelte'],
|
|
409
|
+
},
|
|
410
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
411
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const planResult: PlanResult = {
|
|
415
|
+
status: 'warning',
|
|
416
|
+
warnings: [
|
|
417
|
+
{
|
|
418
|
+
code: 'unsupported-framework-present',
|
|
419
|
+
message: 'Unsupported framework(s) also detected: svelte',
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
blockers: [],
|
|
423
|
+
strategy: 'supported',
|
|
424
|
+
actions: [
|
|
425
|
+
{
|
|
426
|
+
type: 'install_dependency',
|
|
427
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
428
|
+
description: 'Install dependencies.',
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
defaults: {
|
|
432
|
+
provider: 'codex',
|
|
433
|
+
ide: 'vscode',
|
|
434
|
+
shared: true,
|
|
435
|
+
extension: true,
|
|
436
|
+
},
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
440
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
441
|
+
|
|
442
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
443
|
+
|
|
444
|
+
await apply()
|
|
445
|
+
|
|
446
|
+
const output = consoleSpy.mock.calls.flatMap(call => call.map(value => String(value)))
|
|
447
|
+
expect(output.some(line => line.includes('Status: warning'))).toBe(true)
|
|
448
|
+
expect(
|
|
449
|
+
output.some(line => line.includes('Unsupported framework(s) also detected: svelte')),
|
|
450
|
+
).toBe(true)
|
|
451
|
+
expect(
|
|
452
|
+
output.some(line => line.includes('Ready! Hold Alt + Click any element to inspect.')),
|
|
453
|
+
).toBe(false)
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('prints manual apply blockers and next steps without duplicating blocker text', async () => {
|
|
457
|
+
const context: OnboardingContext = {
|
|
458
|
+
root: '/repo',
|
|
459
|
+
packageManager: 'pnpm',
|
|
460
|
+
buildTools: {
|
|
461
|
+
supported: [],
|
|
462
|
+
unsupported: ['Next.js'],
|
|
463
|
+
},
|
|
464
|
+
frameworks: {
|
|
465
|
+
supported: ['react'],
|
|
466
|
+
unsupported: [],
|
|
467
|
+
},
|
|
468
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
469
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const planResult: PlanResult = {
|
|
473
|
+
status: 'blocked',
|
|
474
|
+
warnings: [],
|
|
475
|
+
blockers: [
|
|
476
|
+
{
|
|
477
|
+
code: 'unsupported-build-tool',
|
|
478
|
+
message: 'Detected unsupported build tool(s): Next.js',
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
strategy: 'manual',
|
|
482
|
+
actions: [
|
|
483
|
+
{
|
|
484
|
+
type: 'manual_step',
|
|
485
|
+
target: 'Next.js',
|
|
486
|
+
description: 'Follow the printed Next.js instructions.',
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
defaults: {
|
|
490
|
+
provider: 'codex',
|
|
491
|
+
ide: 'vscode',
|
|
492
|
+
shared: true,
|
|
493
|
+
extension: true,
|
|
494
|
+
},
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
498
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
499
|
+
|
|
500
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
501
|
+
|
|
502
|
+
await apply()
|
|
503
|
+
|
|
504
|
+
const output = consoleSpy.mock.calls.map(call => call.join(' ')).join('\n')
|
|
505
|
+
|
|
506
|
+
expect(output.match(/Detected unsupported build tool\(s\): Next\.js/g)).toHaveLength(1)
|
|
507
|
+
expect(output.match(/Follow the printed Next\.js instructions\./g)).toHaveLength(1)
|
|
508
|
+
expect(output).toContain('Manual Steps Required')
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
it('treats malformed existing settings as a manual follow-up instead of ok', async () => {
|
|
512
|
+
vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
|
|
513
|
+
return filePath === '/repo/.inspecto/settings.local.json'
|
|
514
|
+
})
|
|
515
|
+
vi.mocked(fsUtils.readJSON).mockResolvedValue(null)
|
|
516
|
+
|
|
517
|
+
const result = await applyOnboardingPlan({
|
|
518
|
+
repoRoot: '/repo',
|
|
519
|
+
projectRoot: '/repo',
|
|
520
|
+
packageManager: 'pnpm',
|
|
521
|
+
supportedBuildTargets: [],
|
|
522
|
+
options: {
|
|
523
|
+
shared: false,
|
|
524
|
+
skipInstall: true,
|
|
525
|
+
dryRun: false,
|
|
526
|
+
noExtension: true,
|
|
527
|
+
},
|
|
528
|
+
selectedIDE: { ide: 'vscode', supported: true },
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
expect(result.status).toBe('warning')
|
|
532
|
+
expect(result.postInstall.nextSteps).toContain(
|
|
533
|
+
'Fix .inspecto/settings.local.json or delete it and rerun Inspecto setup.',
|
|
534
|
+
)
|
|
535
|
+
expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/prompts.local.json', [])
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
it('prints the same short 3-step success guide when apply finishes cleanly', async () => {
|
|
539
|
+
const context: OnboardingContext = {
|
|
540
|
+
root: '/repo',
|
|
541
|
+
packageManager: 'pnpm',
|
|
542
|
+
buildTools: {
|
|
543
|
+
supported: [supportedBuild],
|
|
544
|
+
unsupported: [],
|
|
545
|
+
},
|
|
546
|
+
frameworks: {
|
|
547
|
+
supported: ['react'],
|
|
548
|
+
unsupported: [],
|
|
549
|
+
},
|
|
550
|
+
ides: [{ ide: 'vscode', supported: true }],
|
|
551
|
+
providers: [{ id: 'codex', label: 'Codex CLI', supported: true, preferredMode: 'cli' }],
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const planResult: PlanResult = {
|
|
555
|
+
status: 'ok',
|
|
556
|
+
warnings: [],
|
|
557
|
+
blockers: [],
|
|
558
|
+
strategy: 'supported',
|
|
559
|
+
actions: [],
|
|
560
|
+
defaults: {
|
|
561
|
+
provider: 'codex',
|
|
562
|
+
ide: 'vscode',
|
|
563
|
+
shared: false,
|
|
564
|
+
extension: true,
|
|
565
|
+
},
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
vi.mocked(onboardingContext.buildOnboardingContext).mockResolvedValue(context)
|
|
569
|
+
vi.mocked(planner.createPlanResult).mockReturnValue(planResult)
|
|
570
|
+
|
|
571
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
572
|
+
|
|
573
|
+
await apply()
|
|
574
|
+
|
|
575
|
+
const output = consoleSpy.mock.calls.flatMap(call => call.map(value => String(value)))
|
|
576
|
+
expect(output.some(line => line.includes('Ready! Inspecto is set up.'))).toBe(true)
|
|
577
|
+
expect(output.some(line => line.includes('1. Start or restart your dev server.'))).toBe(true)
|
|
578
|
+
expect(output.some(line => line.includes('2. Open your app in the browser.'))).toBe(true)
|
|
579
|
+
expect(output.some(line => line.includes('3. Hold Alt + Click any element to inspect.'))).toBe(
|
|
580
|
+
true,
|
|
581
|
+
)
|
|
582
|
+
})
|
|
583
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { injectPlugin } from '../src/inject/ast-injector.js'
|
|
3
|
+
import * as fsUtils from '../src/utils/fs.js'
|
|
4
|
+
import { log } from '../src/utils/logger.js'
|
|
5
|
+
|
|
6
|
+
vi.mock('../src/utils/fs.js', () => ({
|
|
7
|
+
exists: vi.fn(),
|
|
8
|
+
readFile: vi.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
vi.mock('../src/utils/logger.js', () => ({
|
|
12
|
+
log: {
|
|
13
|
+
warn: vi.fn(),
|
|
14
|
+
hint: vi.fn(),
|
|
15
|
+
blank: vi.fn(),
|
|
16
|
+
error: vi.fn(),
|
|
17
|
+
copyableCodeBlock: vi.fn(),
|
|
18
|
+
success: vi.fn(),
|
|
19
|
+
dryRun: vi.fn(),
|
|
20
|
+
},
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
vi.mock('magicast', () => ({
|
|
24
|
+
loadFile: vi.fn(),
|
|
25
|
+
writeFile: vi.fn(),
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
describe('injectPlugin manual instructions', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.resetAllMocks()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('prints copyable manual instructions when automatic config cannot read the config file', async () => {
|
|
34
|
+
vi.mocked(fsUtils.readFile).mockResolvedValue(null)
|
|
35
|
+
|
|
36
|
+
await injectPlugin(
|
|
37
|
+
'/repo',
|
|
38
|
+
{
|
|
39
|
+
tool: 'vite',
|
|
40
|
+
configPath: 'vite.config.ts',
|
|
41
|
+
label: 'Vite (vite.config.ts)',
|
|
42
|
+
},
|
|
43
|
+
false,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
expect(log.copyableCodeBlock).toHaveBeenCalledWith(
|
|
47
|
+
expect.arrayContaining(["import { vitePlugin as inspecto } from '@inspecto-dev/plugin'"]),
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
})
|
package/tests/build-tool.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as fsUtils from '../src/utils/fs.js'
|
|
|
5
5
|
vi.mock('../src/utils/fs.js', () => ({
|
|
6
6
|
exists: vi.fn(),
|
|
7
7
|
readJSON: vi.fn(),
|
|
8
|
+
readFile: vi.fn(),
|
|
8
9
|
}))
|
|
9
10
|
|
|
10
11
|
describe('detectBuildTools', () => {
|
|
@@ -28,9 +29,7 @@ describe('detectBuildTools', () => {
|
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
const result = await detectBuildTools('/repo', ['packages/app'])
|
|
31
|
-
expect(result.supported).
|
|
32
|
-
expect(result.supported[0]?.configPath).toBe('packages/app/vite.config.ts')
|
|
33
|
-
expect(result.supported[0]?.packagePath).toBe('packages/app')
|
|
32
|
+
expect(result.supported.some(det => det.tool === 'vite')).toBe(true)
|
|
34
33
|
})
|
|
35
34
|
|
|
36
35
|
it('detects vite.config.cjs at the repo root', async () => {
|
|
@@ -40,7 +39,6 @@ describe('detectBuildTools', () => {
|
|
|
40
39
|
)
|
|
41
40
|
|
|
42
41
|
const result = await detectBuildTools('/repo')
|
|
43
|
-
expect(result.supported).
|
|
44
|
-
expect(result.supported[0]?.configPath).toBe('vite.config.cjs')
|
|
42
|
+
expect(result.supported.some(det => det.tool === 'vite')).toBe(true)
|
|
45
43
|
})
|
|
46
44
|
})
|