@inspecto-dev/cli 0.3.4 → 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 (44) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.turbo/turbo-test.log +10036 -5601
  3. package/CHANGELOG.md +8 -0
  4. package/dist/bin.js +32 -1
  5. package/dist/{chunk-2MOEVONN.js → chunk-7ABJRH3F.js} +1232 -145
  6. package/dist/index.d.ts +62 -4
  7. package/dist/index.js +7 -1
  8. package/package.json +2 -2
  9. package/src/bin.ts +45 -0
  10. package/src/commands/dev-config.ts +109 -0
  11. package/src/commands/doctor.ts +162 -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 +1 -1
  16. package/src/commands/onboard.ts +72 -21
  17. package/src/detect/build-tool.ts +14 -5
  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 +137 -3
  24. package/src/onboarding/nextjs-guidance.ts +257 -0
  25. package/src/onboarding/nuxt-guidance.ts +129 -0
  26. package/src/onboarding/planner.ts +257 -6
  27. package/src/onboarding/session.ts +117 -27
  28. package/src/onboarding/target-resolution.ts +3 -3
  29. package/src/onboarding/umi-guidance.ts +139 -0
  30. package/src/types.ts +51 -3
  31. package/tests/apply.test.ts +319 -0
  32. package/tests/dev-config.test.ts +73 -0
  33. package/tests/doctor.test.ts +89 -0
  34. package/tests/init.test.ts +17 -0
  35. package/tests/instructions.test.ts +10 -6
  36. package/tests/integration-host-ide.test.ts +20 -0
  37. package/tests/integration-install.test.ts +65 -0
  38. package/tests/nextjs-guidance.test.ts +128 -0
  39. package/tests/nuxt-guidance.test.ts +67 -0
  40. package/tests/onboard.test.ts +416 -0
  41. package/tests/plan.test.ts +181 -21
  42. package/tests/runner-script.test.ts +120 -1
  43. package/tests/session-resolve.test.ts +116 -0
  44. package/tests/session.test.ts +120 -0
@@ -0,0 +1,257 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import type { OnboardingPatchPlan } from '../types.js'
4
+
5
+ type RouterMode = 'app' | 'pages' | 'mixed' | 'unknown'
6
+
7
+ export interface NextJsGuidance {
8
+ framework: 'react'
9
+ metaFramework: 'Next.js'
10
+ routerMode: RouterMode
11
+ autoApplied: string[]
12
+ pendingSteps: string[]
13
+ assistantPrompt: string
14
+ patches: OnboardingPatchPlan[]
15
+ }
16
+
17
+ const NEXT_CONFIG_CANDIDATES = ['next.config.ts', 'next.config.mjs', 'next.config.js'] as const
18
+ const APP_ROUTER_LAYOUTS = [
19
+ 'app/layout.tsx',
20
+ 'app/layout.jsx',
21
+ 'src/app/layout.tsx',
22
+ 'src/app/layout.jsx',
23
+ ] as const
24
+ const PAGES_ROUTER_APPS = [
25
+ 'pages/_app.tsx',
26
+ 'pages/_app.jsx',
27
+ 'src/pages/_app.tsx',
28
+ 'src/pages/_app.jsx',
29
+ ] as const
30
+ const PACKAGE_JSON_PATH = 'package.json'
31
+
32
+ function findFirstExisting(root: string, candidates: readonly string[]): string | undefined {
33
+ for (const candidate of candidates) {
34
+ if (fs.existsSync(path.join(root, candidate))) {
35
+ return candidate
36
+ }
37
+ }
38
+
39
+ return undefined
40
+ }
41
+
42
+ function detectRouterMode(root: string): RouterMode {
43
+ const hasAppRouter = Boolean(findFirstExisting(root, APP_ROUTER_LAYOUTS))
44
+ const hasPagesRouter = Boolean(findFirstExisting(root, PAGES_ROUTER_APPS))
45
+
46
+ if (hasAppRouter && hasPagesRouter) return 'mixed'
47
+ if (hasAppRouter) return 'app'
48
+ if (hasPagesRouter) return 'pages'
49
+ return 'unknown'
50
+ }
51
+
52
+ function detectNextConfigPath(root: string): string | undefined {
53
+ return findFirstExisting(root, NEXT_CONFIG_CANDIDATES)
54
+ }
55
+
56
+ function readConfig(root: string, relativePath?: string): string {
57
+ if (!relativePath) return ''
58
+ const filePath = path.join(root, relativePath)
59
+ if (!fs.existsSync(filePath)) return ''
60
+ return fs.readFileSync(filePath, 'utf8')
61
+ }
62
+
63
+ function detectPatchShape(source: string): {
64
+ status: OnboardingPatchPlan['status']
65
+ reason: OnboardingPatchPlan['reason']
66
+ confidence: OnboardingPatchPlan['confidence']
67
+ } {
68
+ if (
69
+ /export\s+default\s*\{[\s\S]*\}/m.test(source) ||
70
+ /module\.exports\s*=\s*\{[\s\S]*\}/m.test(source) ||
71
+ /const\s+[A-Za-z0-9_$]+\s*(?::[^=]+)?=\s*\{[\s\S]*\}\s*;?\s*export\s+default\s+[A-Za-z0-9_$]+\s*;?/m.test(
72
+ source,
73
+ ) ||
74
+ /\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{[\s\S]*\}/m.test(
75
+ source,
76
+ )
77
+ ) {
78
+ return {
79
+ status: 'planned',
80
+ reason: 'next_config_object_export',
81
+ confidence: 'high',
82
+ }
83
+ }
84
+
85
+ if (
86
+ /module\.exports\s*=\s*[A-Za-z0-9_$]+\s*\(/m.test(source) ||
87
+ /export\s+default\s+[A-Za-z0-9_$]+\s*\(/m.test(source)
88
+ ) {
89
+ return {
90
+ status: 'manual_patch_required',
91
+ reason: 'next_config_wrapped_export',
92
+ confidence: 'medium',
93
+ }
94
+ }
95
+
96
+ if (source.trim().length === 0) {
97
+ return {
98
+ status: 'manual_patch_required',
99
+ reason: 'next_config_missing',
100
+ confidence: 'low',
101
+ }
102
+ }
103
+
104
+ return {
105
+ status: 'manual_patch_required',
106
+ reason: 'next_config_complex_shape',
107
+ confidence: 'medium',
108
+ }
109
+ }
110
+
111
+ function buildPatchSnippet(): string {
112
+ return [
113
+ "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
114
+ '',
115
+ 'webpack(config, { dev, isServer }) {',
116
+ ' if (dev) {',
117
+ ' config.plugins.push(inspecto())',
118
+ ' }',
119
+ ' return config',
120
+ '}',
121
+ ].join('\n')
122
+ }
123
+
124
+ function buildAppRouterMountSnippet(): string {
125
+ return [
126
+ "import { useEffect } from 'react'",
127
+ '',
128
+ 'function InspectoProvider() {',
129
+ ' useEffect(() => {',
130
+ " if (process.env.NODE_ENV !== 'production') {",
131
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
132
+ ' mountInspector()',
133
+ ' })',
134
+ ' }',
135
+ ' }, [])',
136
+ '',
137
+ ' return null',
138
+ '}',
139
+ '',
140
+ 'Render <InspectoProvider /> from app/layout.tsx inside the <body> tree.',
141
+ ].join('\n')
142
+ }
143
+
144
+ function buildPagesRouterMountSnippet(): string {
145
+ return [
146
+ "import { useEffect } from 'react'",
147
+ '',
148
+ 'useEffect(() => {',
149
+ " if (process.env.NODE_ENV !== 'production') {",
150
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
151
+ ' mountInspector()',
152
+ ' })',
153
+ ' }',
154
+ '}, [])',
155
+ '',
156
+ 'Add this effect to pages/_app.tsx without changing the existing app wrapper behavior.',
157
+ ].join('\n')
158
+ }
159
+
160
+ function detectWebpackDevScriptPatch(root: string): OnboardingPatchPlan | undefined {
161
+ const packageJsonSource = readConfig(root, PACKAGE_JSON_PATH)
162
+ if (!packageJsonSource) {
163
+ return undefined
164
+ }
165
+
166
+ try {
167
+ const packageJson = JSON.parse(packageJsonSource) as { scripts?: Record<string, string> }
168
+ const devScript = packageJson.scripts?.dev
169
+ if (!devScript || !/\bnext\s+dev\b/.test(devScript) || /--webpack\b/.test(devScript)) {
170
+ return undefined
171
+ }
172
+
173
+ return {
174
+ path: PACKAGE_JSON_PATH,
175
+ status: 'manual_patch_required',
176
+ reason: 'next_dev_script_requires_webpack',
177
+ confidence: 'medium',
178
+ snippet: '"dev": "next dev --webpack"',
179
+ }
180
+ } catch {
181
+ return undefined
182
+ }
183
+ }
184
+
185
+ function buildPendingSteps(
186
+ routerMode: RouterMode,
187
+ configPath: string,
188
+ needsWebpackDevScript: boolean,
189
+ ): string[] {
190
+ const routerHint =
191
+ routerMode === 'app'
192
+ ? 'Complete the remaining client-side mount step for your App Router entry.'
193
+ : routerMode === 'pages'
194
+ ? 'Complete the remaining client-side mount step for your Pages Router entry.'
195
+ : routerMode === 'mixed'
196
+ ? 'Complete the remaining client-side mount step for the router entry your team actually uses in development.'
197
+ : 'Complete the remaining client-side mount step in the appropriate Next.js entry file.'
198
+
199
+ return [
200
+ `Review the generated Next.js patch plan for ${configPath}.`,
201
+ ...(needsWebpackDevScript
202
+ ? ['Update the Next.js dev script to use webpack mode for Inspecto validation.']
203
+ : []),
204
+ 'Keep the Inspecto webpack plugin enabled for both server and client development compilers in App Router projects.',
205
+ routerHint,
206
+ ]
207
+ }
208
+
209
+ export function createNextJsGuidance(root: string): NextJsGuidance {
210
+ const configPath = detectNextConfigPath(root) ?? 'next.config.js'
211
+ const configSource = readConfig(root, detectNextConfigPath(root))
212
+ const patchShape = detectPatchShape(configSource)
213
+ const routerMode = detectRouterMode(root)
214
+ const webpackDevScriptPatch = detectWebpackDevScriptPatch(root)
215
+
216
+ return {
217
+ framework: 'react',
218
+ metaFramework: 'Next.js',
219
+ routerMode,
220
+ autoApplied: ['dependencies', 'inspecto_settings'],
221
+ pendingSteps: buildPendingSteps(routerMode, configPath, Boolean(webpackDevScriptPatch)),
222
+ assistantPrompt:
223
+ 'Complete the remaining Inspecto onboarding for this Next.js project. Use the generated patches directly, keep existing app behavior unchanged, avoid unrelated documentation searches unless a patch is insufficient, and finish the client-side mount step safely.',
224
+ patches: [
225
+ {
226
+ path: configPath,
227
+ status: patchShape.status,
228
+ reason: patchShape.reason,
229
+ confidence: patchShape.confidence,
230
+ snippet: buildPatchSnippet(),
231
+ },
232
+ ...(routerMode === 'app' || routerMode === 'mixed'
233
+ ? [
234
+ {
235
+ path: findFirstExisting(root, APP_ROUTER_LAYOUTS) ?? 'app/layout.tsx',
236
+ status: 'manual_patch_required' as const,
237
+ reason: 'next_app_router_mount',
238
+ confidence: 'medium' as const,
239
+ snippet: buildAppRouterMountSnippet(),
240
+ },
241
+ ]
242
+ : []),
243
+ ...(routerMode === 'pages' || routerMode === 'mixed'
244
+ ? [
245
+ {
246
+ path: findFirstExisting(root, PAGES_ROUTER_APPS) ?? 'pages/_app.tsx',
247
+ status: 'manual_patch_required' as const,
248
+ reason: 'next_pages_router_mount',
249
+ confidence: 'medium' as const,
250
+ snippet: buildPagesRouterMountSnippet(),
251
+ },
252
+ ]
253
+ : []),
254
+ ...(webpackDevScriptPatch ? [webpackDevScriptPatch] : []),
255
+ ],
256
+ }
257
+ }
@@ -0,0 +1,129 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import type { OnboardingPatchPlan } from '../types.js'
4
+
5
+ export interface NuxtGuidance {
6
+ framework: 'vue'
7
+ metaFramework: 'Nuxt'
8
+ autoApplied: string[]
9
+ pendingSteps: string[]
10
+ assistantPrompt: string
11
+ patches: OnboardingPatchPlan[]
12
+ }
13
+
14
+ const NUXT_CONFIG_CANDIDATES = ['nuxt.config.ts', 'nuxt.config.js'] as const
15
+
16
+ function findFirstExisting(root: string, candidates: readonly string[]): string | undefined {
17
+ for (const candidate of candidates) {
18
+ if (fs.existsSync(path.join(root, candidate))) {
19
+ return candidate
20
+ }
21
+ }
22
+
23
+ return undefined
24
+ }
25
+
26
+ function readConfig(root: string, relativePath?: string): string {
27
+ if (!relativePath) return ''
28
+ const filePath = path.join(root, relativePath)
29
+ if (!fs.existsSync(filePath)) return ''
30
+ return fs.readFileSync(filePath, 'utf8')
31
+ }
32
+
33
+ function detectPatchShape(source: string): {
34
+ status: OnboardingPatchPlan['status']
35
+ reason: OnboardingPatchPlan['reason']
36
+ confidence: OnboardingPatchPlan['confidence']
37
+ } {
38
+ if (/with[A-Za-z0-9_$]*\s*\(\s*defineNuxtConfig/m.test(source)) {
39
+ return {
40
+ status: 'manual_patch_required',
41
+ reason: 'nuxt_config_wrapped_export',
42
+ confidence: 'medium',
43
+ }
44
+ }
45
+
46
+ if (/defineNuxtConfig\s*\(\s*\{[\s\S]*\}\s*\)/m.test(source)) {
47
+ return {
48
+ status: 'planned',
49
+ reason: 'nuxt_config_object_export',
50
+ confidence: 'high',
51
+ }
52
+ }
53
+
54
+ if (source.trim().length === 0) {
55
+ return {
56
+ status: 'manual_patch_required',
57
+ reason: 'nuxt_config_missing',
58
+ confidence: 'low',
59
+ }
60
+ }
61
+
62
+ return {
63
+ status: 'manual_patch_required',
64
+ reason: 'nuxt_config_complex_shape',
65
+ confidence: 'medium',
66
+ }
67
+ }
68
+
69
+ function buildNuxtConfigSnippet(): string {
70
+ return [
71
+ "import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
72
+ '',
73
+ 'export default defineNuxtConfig({',
74
+ ' vite: {',
75
+ ' plugins: [inspecto()],',
76
+ ' },',
77
+ '})',
78
+ ].join('\n')
79
+ }
80
+
81
+ function buildNuxtPluginSnippet(): string {
82
+ return [
83
+ 'export default defineNuxtPlugin(() => {',
84
+ ' if (import.meta.dev) {',
85
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
86
+ ' mountInspector()',
87
+ ' })',
88
+ ' }',
89
+ '})',
90
+ ].join('\n')
91
+ }
92
+
93
+ export function createNuxtGuidance(root: string): NuxtGuidance {
94
+ const configPath = findFirstExisting(root, NUXT_CONFIG_CANDIDATES) ?? 'nuxt.config.ts'
95
+ const configSource = readConfig(root, findFirstExisting(root, NUXT_CONFIG_CANDIDATES))
96
+ const patchShape = detectPatchShape(configSource)
97
+
98
+ const hasSrcDir =
99
+ fs.existsSync(path.join(root, 'src')) && fs.statSync(path.join(root, 'src')).isDirectory()
100
+ const pluginPath = hasSrcDir ? 'src/plugins/inspecto.client.ts' : 'plugins/inspecto.client.ts'
101
+
102
+ return {
103
+ framework: 'vue',
104
+ metaFramework: 'Nuxt',
105
+ autoApplied: ['dependencies', 'inspecto_settings'],
106
+ pendingSteps: [
107
+ `Review the generated Nuxt patch plan for ${configPath}.`,
108
+ `Complete the remaining Nuxt client plugin mount step in ${pluginPath}.`,
109
+ ],
110
+ assistantPrompt:
111
+ 'Complete the remaining Inspecto onboarding for this Nuxt project. Review the generated patch plan, keep existing app behavior unchanged, and finish the client plugin mount step safely.',
112
+ patches: [
113
+ {
114
+ path: configPath,
115
+ status: patchShape.status,
116
+ reason: patchShape.reason,
117
+ confidence: patchShape.confidence,
118
+ snippet: buildNuxtConfigSnippet(),
119
+ },
120
+ {
121
+ path: pluginPath,
122
+ status: 'manual_patch_required',
123
+ reason: 'nuxt_client_plugin_mount',
124
+ confidence: 'medium',
125
+ snippet: buildNuxtPluginSnippet(),
126
+ },
127
+ ],
128
+ }
129
+ }
@@ -1,4 +1,8 @@
1
1
  import { buildOnboardingContext } from './context.js'
2
+ import { getHostIdeLabel, isSupportedHostIde } from '../integrations/capabilities.js'
3
+ import { createNextJsGuidance } from './nextjs-guidance.js'
4
+ import { createNuxtGuidance } from './nuxt-guidance.js'
5
+ import { createUmiGuidance } from './umi-guidance.js'
2
6
  import type {
3
7
  CommandMessage,
4
8
  CommandStatus,
@@ -37,11 +41,19 @@ function supportedIde(context: OnboardingContext): string | undefined {
37
41
  return context.ides.find(ide => ide.supported)?.ide
38
42
  }
39
43
 
44
+ function shouldInstallInspectoExtension(ide?: string): boolean {
45
+ return Boolean(ide && isSupportedHostIde(ide))
46
+ }
47
+
40
48
  function supportedProvider(context: OnboardingContext): string | undefined {
41
49
  return context.providers.find(provider => provider.supported)?.id
42
50
  }
43
51
 
44
52
  function buildToolBlockers(context: OnboardingContext): CommandMessage[] {
53
+ if (isGuidedMetaFrameworkScenario(context)) {
54
+ return []
55
+ }
56
+
45
57
  if (context.buildTools.unsupported.length > 0) {
46
58
  return [
47
59
  message(
@@ -214,6 +226,77 @@ function manualBuildToolActions(context: OnboardingContext): PlanResult['actions
214
226
  ]
215
227
  }
216
228
 
229
+ function hasUnsupportedBuildTool(context: OnboardingContext, buildTool: string): boolean {
230
+ return context.buildTools.unsupported.includes(buildTool)
231
+ }
232
+
233
+ function isGuidedNextJsScenario(context: OnboardingContext): boolean {
234
+ return (
235
+ context.buildTools.supported.length === 0 &&
236
+ hasUnsupportedBuildTool(context, 'Next.js') &&
237
+ context.frameworks.supported.includes('react')
238
+ )
239
+ }
240
+
241
+ function isGuidedNuxtScenario(context: OnboardingContext): boolean {
242
+ return (
243
+ context.buildTools.supported.length === 0 &&
244
+ hasUnsupportedBuildTool(context, 'Nuxt') &&
245
+ context.frameworks.supported.includes('vue')
246
+ )
247
+ }
248
+
249
+ function isGuidedUmiScenario(context: OnboardingContext): boolean {
250
+ return hasUnsupportedBuildTool(context, 'Umi') && context.frameworks.supported.includes('react')
251
+ }
252
+
253
+ function isGuidedMetaFrameworkScenario(context: OnboardingContext): boolean {
254
+ return (
255
+ isGuidedNextJsScenario(context) || isGuidedNuxtScenario(context) || isGuidedUmiScenario(context)
256
+ )
257
+ }
258
+
259
+ function guidedBuildToolWarnings(
260
+ context: OnboardingContext,
261
+ guidedBuildTool: string,
262
+ ): CommandMessage[] {
263
+ return context.buildTools.unsupported
264
+ .filter(buildTool => buildTool !== guidedBuildTool)
265
+ .map(buildTool =>
266
+ message(
267
+ 'additional-unsupported-build-tool',
268
+ `Additional unsupported build tool also detected: ${buildTool}`,
269
+ ),
270
+ )
271
+ }
272
+
273
+ function guidedFrameworkWarnings(context: OnboardingContext, framework: string): CommandMessage[] {
274
+ return context.frameworks.unsupported
275
+ .filter(item => item !== framework)
276
+ .map(item =>
277
+ message(
278
+ 'additional-unsupported-framework',
279
+ `Additional unsupported framework also detected: ${item}`,
280
+ ),
281
+ )
282
+ }
283
+
284
+ function buildGuidedWarnings(
285
+ context: OnboardingContext,
286
+ guidedBuildTool: string,
287
+ guidedFramework: string,
288
+ ): CommandMessage[] {
289
+ return uniqueMessages([
290
+ ...guidedBuildToolWarnings(context, guidedBuildTool),
291
+ ...guidedFrameworkWarnings(context, guidedFramework),
292
+ ...unsupportedEnvironmentWarnings(context),
293
+ ]).filter(
294
+ warning =>
295
+ warning.message !==
296
+ `Unsupported framework(s) also detected: ${context.frameworks.unsupported.join(', ')}`,
297
+ )
298
+ }
299
+
217
300
  function manualFrameworkActions(context: OnboardingContext): PlanResult['actions'] {
218
301
  if (context.frameworks.unsupported.length > 0) {
219
302
  return [
@@ -267,6 +350,170 @@ export async function createDetectionResult(root: string): Promise<DetectionResu
267
350
  }
268
351
 
269
352
  export function createPlanResult(context: OnboardingContext): PlanResult {
353
+ if (isGuidedNextJsScenario(context)) {
354
+ const ide = supportedIde(context)
355
+ const provider = supportedProvider(context)
356
+ const guidance = createNextJsGuidance(context.root)
357
+ const actions: PlanResult['actions'] = [
358
+ {
359
+ type: 'install_dependency',
360
+ target: '@inspecto-dev/plugin @inspecto-dev/core',
361
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`,
362
+ },
363
+ ]
364
+
365
+ if (shouldInstallInspectoExtension(ide) && ide) {
366
+ actions.push({
367
+ type: 'install_extension',
368
+ target: ide,
369
+ description: `Install the Inspecto ${getHostIdeLabel(ide as Parameters<typeof getHostIdeLabel>[0])} extension.`,
370
+ })
371
+ }
372
+
373
+ actions.push(
374
+ {
375
+ type: 'generate_patch_plan',
376
+ target: 'next.config',
377
+ description: 'Generate a guided patch plan for the Next.js Inspecto webpack integration.',
378
+ },
379
+ {
380
+ type: 'manual_confirmation',
381
+ target: context.root,
382
+ description:
383
+ 'Complete the remaining client-side Inspecto mount step in your assistant or editor.',
384
+ },
385
+ )
386
+
387
+ const defaults: PlanResult['defaults'] = {
388
+ shared: false,
389
+ extension: shouldInstallInspectoExtension(ide),
390
+ ...(provider ? { provider } : {}),
391
+ ...(ide ? { ide } : {}),
392
+ }
393
+
394
+ return {
395
+ status: 'warning',
396
+ warnings: buildGuidedWarnings(context, 'Next.js', 'react'),
397
+ blockers: [],
398
+ strategy: 'guided',
399
+ actions,
400
+ defaults,
401
+ framework: guidance.framework,
402
+ metaFramework: guidance.metaFramework,
403
+ routerMode: guidance.routerMode,
404
+ autoApplied: guidance.autoApplied,
405
+ pendingSteps: guidance.pendingSteps,
406
+ assistantPrompt: guidance.assistantPrompt,
407
+ patches: guidance.patches,
408
+ }
409
+ }
410
+
411
+ if (isGuidedNuxtScenario(context)) {
412
+ const ide = supportedIde(context)
413
+ const provider = supportedProvider(context)
414
+ const guidance = createNuxtGuidance(context.root)
415
+ const actions: PlanResult['actions'] = [
416
+ {
417
+ type: 'install_dependency',
418
+ target: '@inspecto-dev/plugin @inspecto-dev/core',
419
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`,
420
+ },
421
+ ]
422
+
423
+ if (shouldInstallInspectoExtension(ide) && ide) {
424
+ actions.push({
425
+ type: 'install_extension',
426
+ target: ide,
427
+ description: `Install the Inspecto ${getHostIdeLabel(ide as Parameters<typeof getHostIdeLabel>[0])} extension.`,
428
+ })
429
+ }
430
+
431
+ actions.push(
432
+ {
433
+ type: 'generate_patch_plan',
434
+ target: 'nuxt.config',
435
+ description: 'Generate a guided patch plan for the Nuxt Inspecto Vite integration.',
436
+ },
437
+ {
438
+ type: 'manual_confirmation',
439
+ target: context.root,
440
+ description:
441
+ 'Complete the remaining Nuxt client plugin mount step in your assistant or editor.',
442
+ },
443
+ )
444
+
445
+ const defaults: PlanResult['defaults'] = {
446
+ shared: false,
447
+ extension: shouldInstallInspectoExtension(ide),
448
+ ...(provider ? { provider } : {}),
449
+ ...(ide ? { ide } : {}),
450
+ }
451
+
452
+ return {
453
+ status: 'warning',
454
+ warnings: buildGuidedWarnings(context, 'Nuxt', 'vue'),
455
+ blockers: [],
456
+ strategy: 'guided',
457
+ actions,
458
+ defaults,
459
+ framework: guidance.framework,
460
+ metaFramework: guidance.metaFramework,
461
+ autoApplied: guidance.autoApplied,
462
+ pendingSteps: guidance.pendingSteps,
463
+ assistantPrompt: guidance.assistantPrompt,
464
+ patches: guidance.patches,
465
+ }
466
+ }
467
+
468
+ if (isGuidedUmiScenario(context)) {
469
+ const ide = supportedIde(context)
470
+ const provider = supportedProvider(context)
471
+ const guidance = createUmiGuidance(context.root)
472
+ const actions: PlanResult['actions'] = [
473
+ {
474
+ type: 'install_dependency',
475
+ target: '@inspecto-dev/plugin @inspecto-dev/core',
476
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`,
477
+ },
478
+ ]
479
+
480
+ if (shouldInstallInspectoExtension(ide) && ide) {
481
+ actions.push({
482
+ type: 'install_extension',
483
+ target: ide,
484
+ description: `Install the Inspecto ${getHostIdeLabel(ide as Parameters<typeof getHostIdeLabel>[0])} extension.`,
485
+ })
486
+ }
487
+
488
+ actions.push({
489
+ type: 'generate_patch_plan',
490
+ target: 'umi.config',
491
+ description: 'Generate a guided patch plan for the Umi Inspecto webpack integration.',
492
+ })
493
+
494
+ const defaults: PlanResult['defaults'] = {
495
+ shared: false,
496
+ extension: shouldInstallInspectoExtension(ide),
497
+ ...(provider ? { provider } : {}),
498
+ ...(ide ? { ide } : {}),
499
+ }
500
+
501
+ return {
502
+ status: 'warning',
503
+ warnings: buildGuidedWarnings(context, 'Umi', 'react'),
504
+ blockers: [],
505
+ strategy: 'guided',
506
+ actions,
507
+ defaults,
508
+ framework: guidance.framework,
509
+ metaFramework: guidance.metaFramework,
510
+ autoApplied: guidance.autoApplied,
511
+ pendingSteps: guidance.pendingSteps,
512
+ assistantPrompt: guidance.assistantPrompt,
513
+ patches: guidance.patches,
514
+ }
515
+ }
516
+
270
517
  const warnings = uniqueMessages([
271
518
  ...unsupportedEnvironmentWarnings(context),
272
519
  ...buildToolWarnings(context),
@@ -319,18 +566,19 @@ export function createPlanResult(context: OnboardingContext): PlanResult {
319
566
  }
320
567
 
321
568
  const ide = supportedIde(context)
322
- if (ide === 'vscode') {
569
+ if (shouldInstallInspectoExtension(ide) && ide) {
323
570
  actions.push({
324
571
  type: 'install_extension',
325
- target: 'vscode',
326
- description: 'Install the Inspecto VS Code extension.',
572
+ target: ide,
573
+ description: `Install the Inspecto ${getHostIdeLabel(ide as Parameters<typeof getHostIdeLabel>[0])} extension.`,
327
574
  })
328
575
  }
329
576
  }
330
577
 
578
+ const ide = supportedIde(context)
331
579
  const defaults: PlanResult['defaults'] = {
332
580
  shared: false,
333
- extension: supportedIde(context) === 'vscode',
581
+ extension: shouldInstallInspectoExtension(ide),
334
582
  }
335
583
 
336
584
  const provider = supportedProvider(context)
@@ -338,7 +586,6 @@ export function createPlanResult(context: OnboardingContext): PlanResult {
338
586
  defaults.provider = provider
339
587
  }
340
588
 
341
- const ide = supportedIde(context)
342
589
  if (ide) {
343
590
  defaults.ide = ide
344
591
  }
@@ -355,6 +602,10 @@ export function createPlanResult(context: OnboardingContext): PlanResult {
355
602
 
356
603
  export function planManualFollowUp(result: PlanResult): string[] {
357
604
  return result.actions
358
- .filter(action => action.type === 'manual_step')
605
+ .filter(action =>
606
+ ['manual_step', 'generate_patch_plan', 'generate_file', 'manual_confirmation'].includes(
607
+ action.type,
608
+ ),
609
+ )
359
610
  .map(action => action.description)
360
611
  }