@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
|
@@ -2,6 +2,11 @@ import path from 'node:path'
|
|
|
2
2
|
import { detectFrameworks } from '../detect/framework.js'
|
|
3
3
|
import { detectIDE } from '../detect/ide.js'
|
|
4
4
|
import { detectProviders } from '../detect/provider.js'
|
|
5
|
+
import {
|
|
6
|
+
getHostIdeBinaryName,
|
|
7
|
+
isSupportedHostIde,
|
|
8
|
+
type SupportedHostIde,
|
|
9
|
+
} from '../integrations/capabilities.js'
|
|
5
10
|
import { applyOnboardingPlan, type ApplyOnboardingResult } from './apply.js'
|
|
6
11
|
import { buildOnboardingContext } from './context.js'
|
|
7
12
|
import { createPlanResult, planManualFollowUp } from './planner.js'
|
|
@@ -61,9 +66,10 @@ async function buildVerification(
|
|
|
61
66
|
projectRoot: string,
|
|
62
67
|
packageManager: OnboardingContext['packageManager'],
|
|
63
68
|
): Promise<OnboardingVerification> {
|
|
64
|
-
const packageJson = await readJSON<{
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
const packageJson = await readJSON<{
|
|
70
|
+
scripts?: Record<string, string>
|
|
71
|
+
dependencies?: Record<string, string>
|
|
72
|
+
}>(path.join(projectRoot, 'package.json'))
|
|
67
73
|
|
|
68
74
|
if (packageJson?.scripts?.dev) {
|
|
69
75
|
const devCommand = getVerificationCommand(packageManager)
|
|
@@ -74,13 +80,34 @@ async function buildVerification(
|
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
if (packageJson?.scripts?.start && packageJson?.dependencies?.next) {
|
|
84
|
+
const devCommand = packageManager === 'bun' ? 'bunx next dev' : 'npx next dev'
|
|
85
|
+
return {
|
|
86
|
+
available: true,
|
|
87
|
+
devCommand,
|
|
88
|
+
message: `Start the local dev server with \`${devCommand}\` to verify Inspecto in the browser.`,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
return {
|
|
78
93
|
available: false,
|
|
79
94
|
message: 'Start your normal local dev server command to verify Inspecto in the browser.',
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
|
|
98
|
+
function buildExtensionInstallCommand(ide?: string): string {
|
|
99
|
+
if (ide && isSupportedHostIde(ide)) {
|
|
100
|
+
const binaryName = getHostIdeBinaryName(ide as SupportedHostIde)
|
|
101
|
+
if (binaryName) {
|
|
102
|
+
return `${binaryName} --install-extension inspecto.inspecto`
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return 'code --install-extension inspecto.inspecto'
|
|
107
|
+
}
|
|
108
|
+
|
|
83
109
|
function buildIdeExtensionStatus(input: {
|
|
110
|
+
ide?: string
|
|
84
111
|
required: boolean
|
|
85
112
|
installed: boolean
|
|
86
113
|
manualRequired: boolean
|
|
@@ -97,8 +124,12 @@ function buildIdeExtensionStatus(input: {
|
|
|
97
124
|
required: true,
|
|
98
125
|
installed: input.installed,
|
|
99
126
|
manualRequired: input.manualRequired,
|
|
100
|
-
installCommand:
|
|
101
|
-
|
|
127
|
+
installCommand: buildExtensionInstallCommand(input.ide),
|
|
128
|
+
...(input.ide === 'vscode'
|
|
129
|
+
? {
|
|
130
|
+
marketplaceUrl: 'https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto',
|
|
131
|
+
}
|
|
132
|
+
: {}),
|
|
102
133
|
openVsxUrl: 'https://open-vsx.org/extension/inspecto/inspecto',
|
|
103
134
|
}
|
|
104
135
|
}
|
|
@@ -126,8 +157,9 @@ async function detectFrameworkSupportByPackage(
|
|
|
126
157
|
|
|
127
158
|
async function buildTargetedContext(
|
|
128
159
|
rootContext: OnboardingContext,
|
|
129
|
-
packagePath: string,
|
|
160
|
+
target: { id?: string; packagePath: string },
|
|
130
161
|
): Promise<OnboardingContext> {
|
|
162
|
+
const packagePath = normalizePackagePath(target.packagePath)
|
|
131
163
|
const projectRoot = packagePath ? path.join(rootContext.root, packagePath) : rootContext.root
|
|
132
164
|
const [frameworks, ides, providers] = await Promise.all([
|
|
133
165
|
detectFrameworks(projectRoot),
|
|
@@ -140,7 +172,11 @@ async function buildTargetedContext(
|
|
|
140
172
|
packageManager: rootContext.packageManager,
|
|
141
173
|
buildTools: {
|
|
142
174
|
supported: rootContext.buildTools.supported.filter(item => {
|
|
143
|
-
|
|
175
|
+
const itemPackagePath = normalizePackagePath(item.packagePath)
|
|
176
|
+
if (target.id) {
|
|
177
|
+
return `${itemPackagePath || '.'}:${item.tool}:${item.configPath}` === target.id
|
|
178
|
+
}
|
|
179
|
+
return itemPackagePath === packagePath
|
|
144
180
|
}),
|
|
145
181
|
unsupported: [],
|
|
146
182
|
},
|
|
@@ -160,7 +196,12 @@ async function buildTargetedContext(
|
|
|
160
196
|
|
|
161
197
|
function buildOnboardingSummary(plan: PlanResult, projectRoot: string): OnboardingSummary {
|
|
162
198
|
const changes = plan.actions
|
|
163
|
-
.filter(
|
|
199
|
+
.filter(
|
|
200
|
+
action =>
|
|
201
|
+
!['manual_step', 'generate_patch_plan', 'generate_file', 'manual_confirmation'].includes(
|
|
202
|
+
action.type,
|
|
203
|
+
),
|
|
204
|
+
)
|
|
164
205
|
.map(action => action.description)
|
|
165
206
|
const risks = [...plan.warnings.map(item => item.message)]
|
|
166
207
|
const manualFollowUp = planManualFollowUp(plan)
|
|
@@ -230,18 +271,28 @@ function buildPreApplyResult(
|
|
|
230
271
|
}
|
|
231
272
|
: undefined
|
|
232
273
|
|
|
274
|
+
const ideExtension = buildIdeExtensionStatus({
|
|
275
|
+
...(session.selectedIDE?.ide ? { ide: session.selectedIDE.ide } : {}),
|
|
276
|
+
required: session.plan.defaults.extension,
|
|
277
|
+
installed: false,
|
|
278
|
+
manualRequired: session.plan.defaults.extension,
|
|
279
|
+
})
|
|
280
|
+
|
|
233
281
|
return {
|
|
234
282
|
status,
|
|
235
283
|
target: session.target,
|
|
236
284
|
summary: session.summary,
|
|
237
285
|
confirmation: session.confirmation,
|
|
238
|
-
ideExtension
|
|
239
|
-
required: session.plan.defaults.extension,
|
|
240
|
-
installed: false,
|
|
241
|
-
manualRequired: session.plan.defaults.extension,
|
|
242
|
-
}),
|
|
286
|
+
ideExtension,
|
|
243
287
|
verification: session.verification,
|
|
244
|
-
|
|
288
|
+
...(session.framework ? { framework: session.framework } : {}),
|
|
289
|
+
...(session.metaFramework ? { metaFramework: session.metaFramework } : {}),
|
|
290
|
+
...(session.routerMode ? { routerMode: session.routerMode } : {}),
|
|
291
|
+
...(session.autoApplied ? { autoApplied: session.autoApplied } : {}),
|
|
292
|
+
...(session.pendingSteps ? { pendingSteps: session.pendingSteps } : {}),
|
|
293
|
+
...(session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {}),
|
|
294
|
+
...(session.patches ? { patches: session.patches } : {}),
|
|
295
|
+
...(diagnostics ? { diagnostics } : {}),
|
|
245
296
|
}
|
|
246
297
|
}
|
|
247
298
|
|
|
@@ -258,8 +309,8 @@ function buildExecutionResult(
|
|
|
258
309
|
installedDependencies: applyResult.mutations
|
|
259
310
|
.map(item => item.name)
|
|
260
311
|
.filter((value): value is string => !!value),
|
|
261
|
-
selectedProviderDefault: session.providerDefault,
|
|
262
|
-
selectedIDE: session.selectedIDE
|
|
312
|
+
...(session.providerDefault ? { selectedProviderDefault: session.providerDefault } : {}),
|
|
313
|
+
...(session.selectedIDE?.ide ? { selectedIDE: session.selectedIDE.ide } : {}),
|
|
263
314
|
mutations: applyResult.mutations,
|
|
264
315
|
}
|
|
265
316
|
}
|
|
@@ -307,27 +358,48 @@ export async function resolveOnboardingSession(
|
|
|
307
358
|
repoRoot: root,
|
|
308
359
|
buildTools: rootContext.buildTools.supported,
|
|
309
360
|
frameworkSupportByPackage,
|
|
310
|
-
selectedPackagePath: options.target,
|
|
361
|
+
...(options.target ? { selectedPackagePath: options.target } : {}),
|
|
311
362
|
})
|
|
312
363
|
|
|
313
|
-
|
|
364
|
+
const isGuided = rootContext.buildTools.unsupported.some(t =>
|
|
365
|
+
['Next.js', 'Nuxt', 'Umi'].includes(t),
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if (target.candidates.length === 0 || isGuided) {
|
|
314
369
|
const plan = createPlanResult(rootContext)
|
|
370
|
+
const guidedStatus = plan.strategy === 'guided' ? 'partial_success' : 'error'
|
|
371
|
+
const resolvedTarget =
|
|
372
|
+
plan.strategy === 'guided'
|
|
373
|
+
? {
|
|
374
|
+
status: 'guided' as const,
|
|
375
|
+
candidates: [],
|
|
376
|
+
reason: `Guided onboarding is available for ${plan.metaFramework ?? 'this project'}.`,
|
|
377
|
+
}
|
|
378
|
+
: target
|
|
315
379
|
return {
|
|
316
|
-
status:
|
|
317
|
-
target,
|
|
380
|
+
status: guidedStatus,
|
|
381
|
+
target: resolvedTarget,
|
|
318
382
|
summary: buildOnboardingSummary(plan, root),
|
|
319
383
|
confirmation: { required: false },
|
|
320
384
|
verification: rootVerification,
|
|
321
385
|
context: rootContext,
|
|
322
386
|
plan,
|
|
323
387
|
projectRoot: root,
|
|
388
|
+
...(plan.framework ? { framework: plan.framework } : {}),
|
|
389
|
+
...(plan.metaFramework ? { metaFramework: plan.metaFramework } : {}),
|
|
390
|
+
...(plan.routerMode ? { routerMode: plan.routerMode } : {}),
|
|
391
|
+
...(plan.autoApplied ? { autoApplied: plan.autoApplied } : {}),
|
|
392
|
+
...(plan.pendingSteps ? { pendingSteps: plan.pendingSteps } : {}),
|
|
393
|
+
...(plan.assistantPrompt ? { assistantPrompt: plan.assistantPrompt } : {}),
|
|
394
|
+
...(plan.patches ? { patches: plan.patches } : {}),
|
|
324
395
|
}
|
|
325
396
|
}
|
|
326
397
|
|
|
327
398
|
if (target.status === 'needs_selection') {
|
|
328
399
|
const plan = createPlanResult(rootContext)
|
|
329
400
|
const summary: OnboardingSummary = {
|
|
330
|
-
headline:
|
|
401
|
+
headline:
|
|
402
|
+
'Inspecto needs one build target selection before setup so it knows which local dev build should receive the plugin and settings.',
|
|
331
403
|
changes: [],
|
|
332
404
|
risks: [],
|
|
333
405
|
manualFollowUp: [],
|
|
@@ -344,8 +416,7 @@ export async function resolveOnboardingSession(
|
|
|
344
416
|
}
|
|
345
417
|
}
|
|
346
418
|
|
|
347
|
-
const
|
|
348
|
-
const context = await buildTargetedContext(rootContext, packagePath)
|
|
419
|
+
const context = await buildTargetedContext(rootContext, target.selected!)
|
|
349
420
|
const verification = await buildVerification(context.root, context.packageManager)
|
|
350
421
|
const plan = createPlanResult(context)
|
|
351
422
|
const summary = buildOnboardingSummary(plan, context.root)
|
|
@@ -375,6 +446,11 @@ export async function resolveOnboardingSession(
|
|
|
375
446
|
status = 'partial_success'
|
|
376
447
|
}
|
|
377
448
|
|
|
449
|
+
const providerDefault = getProviderDefault(
|
|
450
|
+
plan.defaults.provider,
|
|
451
|
+
selectedProvider?.preferredMode,
|
|
452
|
+
)
|
|
453
|
+
|
|
378
454
|
return {
|
|
379
455
|
status,
|
|
380
456
|
target,
|
|
@@ -385,7 +461,14 @@ export async function resolveOnboardingSession(
|
|
|
385
461
|
plan,
|
|
386
462
|
projectRoot: context.root,
|
|
387
463
|
selectedIDE,
|
|
388
|
-
providerDefault:
|
|
464
|
+
...(providerDefault ? { providerDefault } : {}),
|
|
465
|
+
...(plan.framework ? { framework: plan.framework } : {}),
|
|
466
|
+
...(plan.metaFramework ? { metaFramework: plan.metaFramework } : {}),
|
|
467
|
+
...(plan.routerMode ? { routerMode: plan.routerMode } : {}),
|
|
468
|
+
...(plan.autoApplied ? { autoApplied: plan.autoApplied } : {}),
|
|
469
|
+
...(plan.pendingSteps ? { pendingSteps: plan.pendingSteps } : {}),
|
|
470
|
+
...(plan.assistantPrompt ? { assistantPrompt: plan.assistantPrompt } : {}),
|
|
471
|
+
...(plan.patches ? { patches: plan.patches } : {}),
|
|
389
472
|
}
|
|
390
473
|
}
|
|
391
474
|
|
|
@@ -409,6 +492,9 @@ export async function applyResolvedOnboardingSession(
|
|
|
409
492
|
selectedIDE: session.selectedIDE,
|
|
410
493
|
providerDefault: session.providerDefault,
|
|
411
494
|
plan: session.plan,
|
|
495
|
+
allowManualPlanApply:
|
|
496
|
+
(session.plan.strategy === 'manual' || session.plan.strategy === 'guided') &&
|
|
497
|
+
session.plan.blockers.length === 0,
|
|
412
498
|
})
|
|
413
499
|
|
|
414
500
|
const diagnostics = buildExecutionDiagnostics(session, applyResult)
|
|
@@ -419,20 +505,30 @@ export async function applyResolvedOnboardingSession(
|
|
|
419
505
|
? 'partial_success'
|
|
420
506
|
: 'success'
|
|
421
507
|
|
|
508
|
+
const ideExtension = buildIdeExtensionStatus({
|
|
509
|
+
...(session.selectedIDE?.ide ? { ide: session.selectedIDE.ide } : {}),
|
|
510
|
+
required: session.plan.defaults.extension,
|
|
511
|
+
installed:
|
|
512
|
+
session.plan.defaults.extension && !applyResult.postInstall.manualExtensionInstallNeeded,
|
|
513
|
+
manualRequired: applyResult.postInstall.manualExtensionInstallNeeded,
|
|
514
|
+
})
|
|
515
|
+
|
|
422
516
|
return {
|
|
423
517
|
status,
|
|
424
518
|
target: session.target,
|
|
425
519
|
summary: session.summary,
|
|
426
520
|
confirmation: session.confirmation,
|
|
427
|
-
ideExtension
|
|
428
|
-
required: session.plan.defaults.extension,
|
|
429
|
-
installed:
|
|
430
|
-
session.plan.defaults.extension && !applyResult.postInstall.manualExtensionInstallNeeded,
|
|
431
|
-
manualRequired: applyResult.postInstall.manualExtensionInstallNeeded,
|
|
432
|
-
}),
|
|
521
|
+
ideExtension,
|
|
433
522
|
verification,
|
|
434
523
|
result: buildExecutionResult(session, applyResult),
|
|
435
|
-
|
|
524
|
+
...(session.framework ? { framework: session.framework } : {}),
|
|
525
|
+
...(session.metaFramework ? { metaFramework: session.metaFramework } : {}),
|
|
526
|
+
...(session.routerMode ? { routerMode: session.routerMode } : {}),
|
|
527
|
+
...(session.autoApplied ? { autoApplied: session.autoApplied } : {}),
|
|
528
|
+
...(session.pendingSteps ? { pendingSteps: session.pendingSteps } : {}),
|
|
529
|
+
...(session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {}),
|
|
530
|
+
...(session.patches ? { patches: session.patches } : {}),
|
|
531
|
+
...(diagnostics ? { diagnostics } : {}),
|
|
436
532
|
}
|
|
437
533
|
}
|
|
438
534
|
|
|
@@ -16,11 +16,36 @@ interface RankedCandidate {
|
|
|
16
16
|
score: number
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function buildCandidateId(candidate: {
|
|
20
|
+
packagePath: string
|
|
21
|
+
buildTool: string
|
|
22
|
+
configPath: string
|
|
23
|
+
}): string {
|
|
24
|
+
return [candidate.packagePath || '.', candidate.buildTool, candidate.configPath].join(':')
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
function normalizePackagePath(packagePath?: string): string {
|
|
20
28
|
if (!packagePath || packagePath === '.') return ''
|
|
21
29
|
return packagePath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '')
|
|
22
30
|
}
|
|
23
31
|
|
|
32
|
+
function normalizeTargetValue(target?: string): string {
|
|
33
|
+
if (!target) return ''
|
|
34
|
+
return target.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildSelectionPurpose(): string {
|
|
38
|
+
return 'Choose the build target that runs your local development build so Inspecto can attach the right plugin and settings.'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildSelectionInstructions(hasCandidates: boolean): string {
|
|
42
|
+
if (!hasCandidates) {
|
|
43
|
+
return 'If auto-detection missed your build entrypoint, rerun with --target <configPath> using the config file or wrapper script your dev command actually starts.'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return 'Rerun with --target <candidateId> using one returned candidateId. The CLI also accepts an exact configPath from the candidate list as a compatibility fallback.'
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
function looksLikeAppPackage(packagePath: string): boolean {
|
|
25
50
|
if (!packagePath) return true
|
|
26
51
|
return /(^|\/)(app|apps|web|client|frontend|site)(\/|$)/i.test(packagePath)
|
|
@@ -33,13 +58,19 @@ function looksLikeAuxiliaryPackage(packagePath: string): boolean {
|
|
|
33
58
|
function buildCandidates(input: ResolveOnboardingTargetInput): OnboardingTargetCandidate[] {
|
|
34
59
|
return input.buildTools.map(buildTool => {
|
|
35
60
|
const packagePath = normalizePackagePath(buildTool.packagePath)
|
|
36
|
-
|
|
61
|
+
const candidate: OnboardingTargetCandidate = {
|
|
37
62
|
packagePath,
|
|
38
63
|
configPath: buildTool.configPath,
|
|
64
|
+
label: buildTool.label,
|
|
39
65
|
buildTool: buildTool.tool,
|
|
66
|
+
...(buildTool.isLegacyRspack ? { isLegacyRspack: true } : {}),
|
|
67
|
+
...(buildTool.isLegacyWebpack ? { isLegacyWebpack: true } : {}),
|
|
40
68
|
frameworks: input.frameworkSupportByPackage[packagePath] ?? [],
|
|
41
69
|
automaticInjection: true,
|
|
42
70
|
}
|
|
71
|
+
candidate.id = buildCandidateId(candidate)
|
|
72
|
+
candidate.candidateId = candidate.id
|
|
73
|
+
return candidate
|
|
43
74
|
})
|
|
44
75
|
}
|
|
45
76
|
|
|
@@ -73,18 +104,57 @@ export function resolveOnboardingTarget(
|
|
|
73
104
|
status: 'needs_selection',
|
|
74
105
|
candidates,
|
|
75
106
|
reason: 'No supported targets were detected.',
|
|
107
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
108
|
+
selectionInstructions: buildSelectionInstructions(false),
|
|
76
109
|
}
|
|
77
110
|
}
|
|
78
111
|
|
|
79
112
|
const explicitlySelected = normalizePackagePath(input.selectedPackagePath)
|
|
113
|
+
const explicitlySelectedValue = normalizeTargetValue(input.selectedPackagePath)
|
|
80
114
|
if (input.selectedPackagePath !== undefined) {
|
|
81
|
-
const
|
|
115
|
+
const selectedById = candidates.find(
|
|
116
|
+
candidate =>
|
|
117
|
+
candidate.id === input.selectedPackagePath ||
|
|
118
|
+
candidate.candidateId === input.selectedPackagePath,
|
|
119
|
+
)
|
|
120
|
+
if (selectedById) {
|
|
121
|
+
return {
|
|
122
|
+
status: 'resolved',
|
|
123
|
+
selected: selectedById,
|
|
124
|
+
candidates,
|
|
125
|
+
reason: `Using the explicitly selected target: ${selectedById.configPath}.`,
|
|
126
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
127
|
+
selectionInstructions: buildSelectionInstructions(true),
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const selectedByConfigPath = candidates.find(
|
|
132
|
+
candidate => normalizeTargetValue(candidate.configPath) === explicitlySelectedValue,
|
|
133
|
+
)
|
|
134
|
+
if (selectedByConfigPath) {
|
|
135
|
+
return {
|
|
136
|
+
status: 'resolved',
|
|
137
|
+
selected: selectedByConfigPath,
|
|
138
|
+
candidates,
|
|
139
|
+
reason: `Using the explicitly selected config path: ${selectedByConfigPath.configPath}.`,
|
|
140
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
141
|
+
selectionInstructions: buildSelectionInstructions(true),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const matchingPackageCandidates = candidates.filter(
|
|
146
|
+
candidate => candidate.packagePath === explicitlySelected,
|
|
147
|
+
)
|
|
148
|
+
const selected =
|
|
149
|
+
matchingPackageCandidates.length === 1 ? matchingPackageCandidates[0] : undefined
|
|
82
150
|
if (selected) {
|
|
83
151
|
return {
|
|
84
152
|
status: 'resolved',
|
|
85
153
|
selected,
|
|
86
154
|
candidates,
|
|
87
155
|
reason: `Using the explicitly selected target: ${selected.packagePath || '.'}.`,
|
|
156
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
157
|
+
selectionInstructions: buildSelectionInstructions(true),
|
|
88
158
|
}
|
|
89
159
|
}
|
|
90
160
|
}
|
|
@@ -92,9 +162,11 @@ export function resolveOnboardingTarget(
|
|
|
92
162
|
if (candidates.length === 1) {
|
|
93
163
|
return {
|
|
94
164
|
status: 'resolved',
|
|
95
|
-
selected: candidates[0]
|
|
165
|
+
selected: candidates[0]!,
|
|
96
166
|
candidates,
|
|
97
167
|
reason: 'Only one supported target was detected.',
|
|
168
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
169
|
+
selectionInstructions: buildSelectionInstructions(true),
|
|
98
170
|
}
|
|
99
171
|
}
|
|
100
172
|
|
|
@@ -104,6 +176,8 @@ export function resolveOnboardingTarget(
|
|
|
104
176
|
status: 'needs_selection',
|
|
105
177
|
candidates,
|
|
106
178
|
reason: 'Multiple supported targets look equally plausible.',
|
|
179
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
180
|
+
selectionInstructions: buildSelectionInstructions(true),
|
|
107
181
|
}
|
|
108
182
|
}
|
|
109
183
|
|
|
@@ -112,5 +186,7 @@ export function resolveOnboardingTarget(
|
|
|
112
186
|
selected: ranked[0]!.candidate,
|
|
113
187
|
candidates,
|
|
114
188
|
reason: `Preselected ${ranked[0]!.candidate.packagePath || '.'} because it has the strongest supported app signal.`,
|
|
189
|
+
selectionPurpose: buildSelectionPurpose(),
|
|
190
|
+
selectionInstructions: buildSelectionInstructions(true),
|
|
115
191
|
}
|
|
116
192
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import type { OnboardingPatchPlan } from '../types.js'
|
|
4
|
+
|
|
5
|
+
export interface UmiGuidance {
|
|
6
|
+
framework: 'react'
|
|
7
|
+
metaFramework: 'Umi'
|
|
8
|
+
autoApplied: string[]
|
|
9
|
+
pendingSteps: string[]
|
|
10
|
+
assistantPrompt: string
|
|
11
|
+
patches: OnboardingPatchPlan[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const UMI_CONFIG_CANDIDATES = [
|
|
15
|
+
'.umirc.ts',
|
|
16
|
+
'.umirc.js',
|
|
17
|
+
'config/config.ts',
|
|
18
|
+
'config/config.js',
|
|
19
|
+
] as const
|
|
20
|
+
|
|
21
|
+
function findFirstExisting(root: string, candidates: readonly string[]): string | undefined {
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
if (fs.existsSync(path.join(root, candidate))) {
|
|
24
|
+
return candidate
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readConfig(root: string, relativePath?: string): string {
|
|
32
|
+
if (!relativePath) return ''
|
|
33
|
+
const filePath = path.join(root, relativePath)
|
|
34
|
+
if (!fs.existsSync(filePath)) return ''
|
|
35
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function detectPatchShape(source: string): {
|
|
39
|
+
status: OnboardingPatchPlan['status']
|
|
40
|
+
reason: OnboardingPatchPlan['reason']
|
|
41
|
+
confidence: OnboardingPatchPlan['confidence']
|
|
42
|
+
} {
|
|
43
|
+
if (/defineConfig\s*\(\s*\{[\s\S]*\}\s*\)/m.test(source)) {
|
|
44
|
+
return {
|
|
45
|
+
status: 'planned',
|
|
46
|
+
reason: 'umi_config_object_export',
|
|
47
|
+
confidence: 'high',
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (source.trim().length === 0) {
|
|
52
|
+
return {
|
|
53
|
+
status: 'manual_patch_required',
|
|
54
|
+
reason: 'umi_config_missing',
|
|
55
|
+
confidence: 'low',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
status: 'manual_patch_required',
|
|
61
|
+
reason: 'umi_config_complex_shape',
|
|
62
|
+
confidence: 'medium',
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildUmiConfigSnippet(): string {
|
|
67
|
+
return [
|
|
68
|
+
"import { defineConfig } from 'umi'",
|
|
69
|
+
"import { webpack4Plugin } from '@inspecto-dev/plugin/legacy/webpack4'",
|
|
70
|
+
'',
|
|
71
|
+
'export default defineConfig({',
|
|
72
|
+
' chainWebpack(memo) {',
|
|
73
|
+
" if (process.env.NODE_ENV === 'development') {",
|
|
74
|
+
" memo.plugin('inspecto').use(webpack4Plugin())",
|
|
75
|
+
' }',
|
|
76
|
+
' },',
|
|
77
|
+
'})',
|
|
78
|
+
].join('\n')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildUmiMountSnippet(): string {
|
|
82
|
+
return [
|
|
83
|
+
"import { useEffect } from 'react'",
|
|
84
|
+
'',
|
|
85
|
+
'export function rootContainer(container: React.ReactNode) {',
|
|
86
|
+
' return (',
|
|
87
|
+
' <InspectoWrapper>{container}</InspectoWrapper>',
|
|
88
|
+
' )',
|
|
89
|
+
'}',
|
|
90
|
+
'',
|
|
91
|
+
'function InspectoWrapper({ children }: { children: React.ReactNode }) {',
|
|
92
|
+
' useEffect(() => {',
|
|
93
|
+
" if (process.env.NODE_ENV !== 'production') {",
|
|
94
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
95
|
+
' mountInspector({',
|
|
96
|
+
" serverUrl: 'http://127.0.0.1:' + ((window as any).__AI_INSPECTOR_PORT__ || 5678),",
|
|
97
|
+
' })',
|
|
98
|
+
' })',
|
|
99
|
+
' }',
|
|
100
|
+
' }, [])',
|
|
101
|
+
'',
|
|
102
|
+
' return <>{children}</>',
|
|
103
|
+
'}',
|
|
104
|
+
].join('\n')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function createUmiGuidance(root: string): UmiGuidance {
|
|
108
|
+
const configPath = findFirstExisting(root, UMI_CONFIG_CANDIDATES) ?? '.umirc.ts'
|
|
109
|
+
const configSource = readConfig(root, findFirstExisting(root, UMI_CONFIG_CANDIDATES))
|
|
110
|
+
const patchShape = detectPatchShape(configSource)
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
framework: 'react',
|
|
114
|
+
metaFramework: 'Umi',
|
|
115
|
+
autoApplied: ['dependencies', 'inspecto_settings'],
|
|
116
|
+
pendingSteps: [
|
|
117
|
+
`Review the generated Umi patch plan for ${configPath}.`,
|
|
118
|
+
'Complete the remaining client-side mount step in src/app.tsx.',
|
|
119
|
+
],
|
|
120
|
+
assistantPrompt:
|
|
121
|
+
'Complete the remaining Inspecto onboarding for this Umi project. Review the generated patch plan, keep existing app behavior unchanged, and finish the client-side mount step safely.',
|
|
122
|
+
patches: [
|
|
123
|
+
{
|
|
124
|
+
path: configPath,
|
|
125
|
+
status: patchShape.status,
|
|
126
|
+
reason: patchShape.reason,
|
|
127
|
+
confidence: patchShape.confidence,
|
|
128
|
+
snippet: buildUmiConfigSnippet(),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
path: 'src/app.tsx',
|
|
132
|
+
status: 'manual_patch_required',
|
|
133
|
+
reason: 'umi_app_mount',
|
|
134
|
+
confidence: 'medium',
|
|
135
|
+
snippet: buildUmiMountSnippet(),
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -63,18 +63,25 @@ export interface OnboardingContext {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export interface OnboardingTargetCandidate {
|
|
66
|
+
id?: string
|
|
67
|
+
candidateId?: string
|
|
66
68
|
packagePath: string
|
|
67
69
|
configPath: string
|
|
70
|
+
label?: string
|
|
68
71
|
buildTool: BuildTool
|
|
72
|
+
isLegacyRspack?: boolean
|
|
73
|
+
isLegacyWebpack?: boolean
|
|
69
74
|
frameworks: string[]
|
|
70
75
|
automaticInjection: boolean
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
export interface OnboardingTargetResolution {
|
|
74
|
-
status: 'resolved' | 'needs_selection'
|
|
79
|
+
status: 'resolved' | 'needs_selection' | 'guided'
|
|
75
80
|
selected?: OnboardingTargetCandidate
|
|
76
81
|
candidates: OnboardingTargetCandidate[]
|
|
77
82
|
reason: string
|
|
83
|
+
selectionPurpose?: string
|
|
84
|
+
selectionInstructions?: string
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
export interface OnboardingSummary {
|
|
@@ -84,6 +91,14 @@ export interface OnboardingSummary {
|
|
|
84
91
|
manualFollowUp: string[]
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
export interface OnboardingPatchPlan {
|
|
95
|
+
path: string
|
|
96
|
+
status: 'planned' | 'manual_patch_required'
|
|
97
|
+
reason: string
|
|
98
|
+
snippet: string
|
|
99
|
+
confidence: 'high' | 'medium' | 'low'
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
export interface OnboardingConfirmation {
|
|
88
103
|
required: boolean
|
|
89
104
|
reason?: string
|
|
@@ -119,6 +134,16 @@ export interface OnboardingVerification {
|
|
|
119
134
|
message: string
|
|
120
135
|
}
|
|
121
136
|
|
|
137
|
+
export interface OnboardingAssistantHandoff {
|
|
138
|
+
framework?: string
|
|
139
|
+
metaFramework?: string
|
|
140
|
+
routerMode?: 'app' | 'pages' | 'mixed' | 'unknown'
|
|
141
|
+
autoApplied?: string[]
|
|
142
|
+
pendingSteps?: string[]
|
|
143
|
+
assistantPrompt?: string
|
|
144
|
+
patches?: OnboardingPatchPlan[]
|
|
145
|
+
}
|
|
146
|
+
|
|
122
147
|
export interface ResolvedOnboardingSession {
|
|
123
148
|
status: OnboardStatus
|
|
124
149
|
target: OnboardingTargetResolution
|
|
@@ -130,6 +155,14 @@ export interface ResolvedOnboardingSession {
|
|
|
130
155
|
projectRoot: string
|
|
131
156
|
selectedIDE?: { ide: string; supported: boolean } | null
|
|
132
157
|
providerDefault?: string
|
|
158
|
+
framework?: string
|
|
159
|
+
metaFramework?: string
|
|
160
|
+
routerMode?: 'app' | 'pages' | 'mixed' | 'unknown'
|
|
161
|
+
autoApplied?: string[]
|
|
162
|
+
pendingSteps?: string[]
|
|
163
|
+
assistantPrompt?: string
|
|
164
|
+
patches?: OnboardingPatchPlan[]
|
|
165
|
+
handoff?: OnboardingAssistantHandoff
|
|
133
166
|
}
|
|
134
167
|
|
|
135
168
|
export interface OnboardCommandResult {
|
|
@@ -141,6 +174,14 @@ export interface OnboardCommandResult {
|
|
|
141
174
|
verification?: OnboardingVerification
|
|
142
175
|
result?: OnboardingExecutionResult
|
|
143
176
|
diagnostics?: OnboardingDiagnostics
|
|
177
|
+
framework?: string
|
|
178
|
+
metaFramework?: string
|
|
179
|
+
routerMode?: 'app' | 'pages' | 'mixed' | 'unknown'
|
|
180
|
+
autoApplied?: string[]
|
|
181
|
+
pendingSteps?: string[]
|
|
182
|
+
assistantPrompt?: string
|
|
183
|
+
patches?: OnboardingPatchPlan[]
|
|
184
|
+
handoff?: OnboardingAssistantHandoff
|
|
144
185
|
}
|
|
145
186
|
|
|
146
187
|
/** Machine-readable detection output for skill-first onboarding */
|
|
@@ -167,9 +208,16 @@ export interface PlanResult {
|
|
|
167
208
|
status: CommandStatus
|
|
168
209
|
warnings: CommandMessage[]
|
|
169
210
|
blockers: CommandMessage[]
|
|
170
|
-
strategy: 'supported' | 'manual' | 'unsupported'
|
|
211
|
+
strategy: 'supported' | 'guided' | 'manual' | 'unsupported'
|
|
171
212
|
actions: Array<{
|
|
172
|
-
type:
|
|
213
|
+
type:
|
|
214
|
+
| 'install_dependency'
|
|
215
|
+
| 'modify_file'
|
|
216
|
+
| 'install_extension'
|
|
217
|
+
| 'manual_step'
|
|
218
|
+
| 'generate_patch_plan'
|
|
219
|
+
| 'generate_file'
|
|
220
|
+
| 'manual_confirmation'
|
|
173
221
|
target: string
|
|
174
222
|
description: string
|
|
175
223
|
}>
|
|
@@ -179,6 +227,13 @@ export interface PlanResult {
|
|
|
179
227
|
shared: boolean
|
|
180
228
|
extension: boolean
|
|
181
229
|
}
|
|
230
|
+
framework?: string
|
|
231
|
+
metaFramework?: string
|
|
232
|
+
routerMode?: 'app' | 'pages' | 'mixed' | 'unknown'
|
|
233
|
+
autoApplied?: string[]
|
|
234
|
+
pendingSteps?: string[]
|
|
235
|
+
assistantPrompt?: string
|
|
236
|
+
patches?: OnboardingPatchPlan[]
|
|
182
237
|
}
|
|
183
238
|
|
|
184
239
|
/** A single doctor diagnostic check/result */
|