@inspecto-dev/cli 0.3.3 → 0.3.4
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 +6 -6
- package/.turbo/turbo-test.log +5450 -3335
- package/CHANGELOG.md +20 -0
- package/dist/bin.js +5 -2
- package/dist/{chunk-LJOKPCPD.js → chunk-2MOEVONN.js} +481 -49
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/bin.ts +4 -1
- package/src/commands/doctor.ts +27 -0
- package/src/commands/integration-install.ts +99 -4
- package/src/commands/onboard.ts +14 -0
- package/src/detect/build-tool.ts +198 -10
- package/src/onboarding/apply.ts +120 -27
- package/src/onboarding/planner.ts +80 -4
- package/src/onboarding/session.ts +11 -5
- package/src/onboarding/target-resolution.ts +78 -2
- package/src/types.ts +7 -0
- package/tests/apply.test.ts +234 -0
- package/tests/build-tool.test.ts +199 -0
- package/tests/doctor.test.ts +41 -0
- package/tests/install-wrapper.test.ts +56 -0
- package/tests/integration-install.test.ts +128 -0
- package/tests/onboard.test.ts +95 -0
- package/tests/plan.test.ts +102 -0
package/src/onboarding/apply.ts
CHANGED
|
@@ -50,6 +50,46 @@ interface ApplySpinner {
|
|
|
50
50
|
fail(text: string): void
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function normalizeSupportedIde(ide?: string): string | undefined {
|
|
54
|
+
if (!ide) return undefined
|
|
55
|
+
return ide.toLowerCase() === 'vscode' ? 'vscode' : ide.toLowerCase()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readInheritedSettingsDefaults(
|
|
59
|
+
repoRoot: string,
|
|
60
|
+
projectRoot: string,
|
|
61
|
+
settingsFileName: string,
|
|
62
|
+
): Promise<{ ide?: string; providerDefault?: string }> {
|
|
63
|
+
if (path.resolve(repoRoot) === path.resolve(projectRoot)) {
|
|
64
|
+
return {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const inheritedSettingsPath = path.join(repoRoot, '.inspecto', settingsFileName)
|
|
68
|
+
if (!(await exists(inheritedSettingsPath))) {
|
|
69
|
+
return {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const inheritedSettings = await readJSON<Record<string, unknown>>(inheritedSettingsPath)
|
|
73
|
+
if (!inheritedSettings || typeof inheritedSettings !== 'object') {
|
|
74
|
+
return {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const inheritedDefaults: { ide?: string; providerDefault?: string } = {}
|
|
78
|
+
|
|
79
|
+
if (typeof inheritedSettings.ide === 'string') {
|
|
80
|
+
const normalizedIde = normalizeSupportedIde(inheritedSettings.ide)
|
|
81
|
+
if (normalizedIde) {
|
|
82
|
+
inheritedDefaults.ide = normalizedIde
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof inheritedSettings['provider.default'] === 'string') {
|
|
87
|
+
inheritedDefaults.providerDefault = inheritedSettings['provider.default']
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return inheritedDefaults
|
|
91
|
+
}
|
|
92
|
+
|
|
53
93
|
export interface ApplyOnboardingResult {
|
|
54
94
|
status: CommandStatus
|
|
55
95
|
mutations: Mutation[]
|
|
@@ -91,13 +131,16 @@ function resultStatus(nextSteps: string[]): CommandStatus {
|
|
|
91
131
|
return nextSteps.length > 0 ? 'warning' : 'ok'
|
|
92
132
|
}
|
|
93
133
|
|
|
94
|
-
function manualPlanSteps(plan: PlanResult): string[] {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
134
|
+
function manualPlanSteps(plan: PlanResult, includeBlockers = true): string[] {
|
|
135
|
+
const steps = plan.actions
|
|
136
|
+
.filter(action => action.type === 'manual_step')
|
|
137
|
+
.map(action => action.description)
|
|
138
|
+
|
|
139
|
+
if (!includeBlockers) {
|
|
140
|
+
return steps
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return [...plan.blockers.map(blocker => blocker.message), ...steps]
|
|
101
144
|
}
|
|
102
145
|
|
|
103
146
|
export async function applyOnboardingPlan(
|
|
@@ -163,6 +206,10 @@ async function applyOnboardingPlanInternal(
|
|
|
163
206
|
input: ApplyOnboardingInput,
|
|
164
207
|
): Promise<ApplyOnboardingResult> {
|
|
165
208
|
const reporter = createReporter(input.options.quiet)
|
|
209
|
+
const additiveManualPlan =
|
|
210
|
+
input.plan?.strategy === 'manual' &&
|
|
211
|
+
input.allowManualPlanApply &&
|
|
212
|
+
input.plan.blockers.length === 0
|
|
166
213
|
|
|
167
214
|
if (input.plan && input.plan.strategy !== 'supported' && !input.allowManualPlanApply) {
|
|
168
215
|
return {
|
|
@@ -172,7 +219,7 @@ async function applyOnboardingPlanInternal(
|
|
|
172
219
|
installFailed: false,
|
|
173
220
|
injectionFailed: false,
|
|
174
221
|
manualExtensionInstallNeeded: false,
|
|
175
|
-
nextSteps: manualPlanSteps(input.plan),
|
|
222
|
+
nextSteps: manualPlanSteps(input.plan, true),
|
|
176
223
|
},
|
|
177
224
|
}
|
|
178
225
|
}
|
|
@@ -183,6 +230,11 @@ async function applyOnboardingPlanInternal(
|
|
|
183
230
|
const promptsFileName = input.options.shared ? 'prompts.json' : 'prompts.local.json'
|
|
184
231
|
const settingsPath = path.join(settingsDir, settingsFileName)
|
|
185
232
|
const promptsPath = path.join(settingsDir, promptsFileName)
|
|
233
|
+
const inheritedDefaults = await readInheritedSettingsDefaults(
|
|
234
|
+
input.repoRoot,
|
|
235
|
+
input.projectRoot,
|
|
236
|
+
settingsFileName,
|
|
237
|
+
)
|
|
186
238
|
const runtimePackages = resolveRuntimePackages()
|
|
187
239
|
const installCmd = getInstallCommand(input.packageManager, runtimePackages.installSpec)
|
|
188
240
|
const nextSteps: string[] = []
|
|
@@ -217,17 +269,19 @@ async function applyOnboardingPlanInternal(
|
|
|
217
269
|
}
|
|
218
270
|
|
|
219
271
|
let injectionFailed = Boolean(input.injectionSkippedRequiresManualConfig)
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
272
|
+
if (!additiveManualPlan) {
|
|
273
|
+
for (const target of input.supportedBuildTargets) {
|
|
274
|
+
const result = await injectPlugin(
|
|
275
|
+
input.repoRoot,
|
|
276
|
+
target,
|
|
277
|
+
input.options.dryRun,
|
|
278
|
+
input.options.quiet ?? false,
|
|
279
|
+
)
|
|
280
|
+
if (result.success) {
|
|
281
|
+
mutations.push(...result.mutations)
|
|
282
|
+
} else {
|
|
283
|
+
injectionFailed = true
|
|
284
|
+
}
|
|
231
285
|
}
|
|
232
286
|
}
|
|
233
287
|
|
|
@@ -238,20 +292,56 @@ async function applyOnboardingPlanInternal(
|
|
|
238
292
|
reporter.hint('Please fix the syntax errors manually, or delete it and re-run init')
|
|
239
293
|
nextSteps.push(`Fix .inspecto/${settingsFileName} or delete it and rerun Inspecto setup.`)
|
|
240
294
|
} else {
|
|
241
|
-
|
|
295
|
+
const mergedSettings =
|
|
296
|
+
existingSettings && typeof existingSettings === 'object'
|
|
297
|
+
? { ...(existingSettings as Record<string, unknown>) }
|
|
298
|
+
: {}
|
|
299
|
+
let settingsChanged = false
|
|
300
|
+
|
|
301
|
+
const desiredIde =
|
|
302
|
+
inheritedDefaults.ide ??
|
|
303
|
+
(input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : undefined)
|
|
304
|
+
|
|
305
|
+
if (desiredIde && !mergedSettings.ide) {
|
|
306
|
+
mergedSettings.ide = desiredIde
|
|
307
|
+
settingsChanged = true
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault
|
|
311
|
+
if (desiredProviderDefault && !mergedSettings['provider.default']) {
|
|
312
|
+
mergedSettings['provider.default'] = desiredProviderDefault
|
|
313
|
+
settingsChanged = true
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (settingsChanged) {
|
|
317
|
+
if (input.options.dryRun) {
|
|
318
|
+
reporter.dryRun(`Would update .inspecto/${settingsFileName}`)
|
|
319
|
+
} else {
|
|
320
|
+
await writeJSON(settingsPath, mergedSettings)
|
|
321
|
+
reporter.success(`Updated .inspecto/${settingsFileName} with missing defaults`)
|
|
322
|
+
mutations.push({
|
|
323
|
+
type: 'file_modified',
|
|
324
|
+
path: `.inspecto/${settingsFileName}`,
|
|
325
|
+
description: 'Merged missing Inspecto defaults into existing settings',
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
reporter.success(`.inspecto/${settingsFileName} already exists (skipped)`)
|
|
330
|
+
}
|
|
242
331
|
}
|
|
243
332
|
} else {
|
|
244
333
|
const defaultSettings: Record<string, unknown> = {}
|
|
334
|
+
const desiredIde =
|
|
335
|
+
inheritedDefaults.ide ??
|
|
336
|
+
(input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : undefined)
|
|
337
|
+
const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault
|
|
245
338
|
|
|
246
|
-
if (
|
|
247
|
-
defaultSettings.ide =
|
|
248
|
-
input.selectedIDE.ide.toLowerCase() === 'vscode'
|
|
249
|
-
? 'vscode'
|
|
250
|
-
: input.selectedIDE.ide.toLowerCase()
|
|
339
|
+
if (desiredIde) {
|
|
340
|
+
defaultSettings.ide = desiredIde
|
|
251
341
|
}
|
|
252
342
|
|
|
253
|
-
if (
|
|
254
|
-
defaultSettings['provider.default'] =
|
|
343
|
+
if (desiredProviderDefault) {
|
|
344
|
+
defaultSettings['provider.default'] = desiredProviderDefault
|
|
255
345
|
}
|
|
256
346
|
|
|
257
347
|
if (input.options.dryRun) {
|
|
@@ -340,6 +430,9 @@ async function applyOnboardingPlanInternal(
|
|
|
340
430
|
if (manualExtensionInstallNeeded) {
|
|
341
431
|
nextSteps.push('Install the Inspecto IDE extension manually')
|
|
342
432
|
}
|
|
433
|
+
if (additiveManualPlan && input.plan) {
|
|
434
|
+
nextSteps.push(...manualPlanSteps(input.plan, false))
|
|
435
|
+
}
|
|
343
436
|
if (input.manualConfigRequiredFor === 'Nuxt') {
|
|
344
437
|
nextSteps.push(
|
|
345
438
|
'Nuxt detected—please follow the Nuxt instructions printed above to finish setup.',
|
|
@@ -71,6 +71,31 @@ function buildToolBlockers(context: OnboardingContext): CommandMessage[] {
|
|
|
71
71
|
return [message('missing-build-tool', 'No supported build tool detected')]
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function buildToolWarnings(context: OnboardingContext): CommandMessage[] {
|
|
75
|
+
const warnings: CommandMessage[] = []
|
|
76
|
+
|
|
77
|
+
if (context.buildTools.supported.length === 1) {
|
|
78
|
+
const buildTool = context.buildTools.supported[0]!
|
|
79
|
+
if (buildTool.tool === 'rspack' && buildTool.isLegacyRspack) {
|
|
80
|
+
warnings.push(
|
|
81
|
+
message(
|
|
82
|
+
'legacy-rspack-requires-manual-config',
|
|
83
|
+
`Legacy Rspack detected at ${buildTool.configPath}. Inspecto must use the legacy Rspack plugin entry and manual config steps.`,
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
} else if (buildTool.tool === 'webpack' && buildTool.isLegacyWebpack) {
|
|
87
|
+
warnings.push(
|
|
88
|
+
message(
|
|
89
|
+
'legacy-webpack4-requires-manual-config',
|
|
90
|
+
`Webpack 4 detected at ${buildTool.configPath}. Inspecto must use the legacy Webpack 4 plugin entry and manual config steps.`,
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return warnings
|
|
97
|
+
}
|
|
98
|
+
|
|
74
99
|
function frameworkBlockers(context: OnboardingContext): CommandMessage[] {
|
|
75
100
|
if (context.frameworks.supported.length > 0) {
|
|
76
101
|
return []
|
|
@@ -149,6 +174,36 @@ function manualBuildToolActions(context: OnboardingContext): PlanResult['actions
|
|
|
149
174
|
]
|
|
150
175
|
}
|
|
151
176
|
|
|
177
|
+
const buildTool = context.buildTools.supported[0]
|
|
178
|
+
if (buildTool?.tool === 'rspack' && buildTool.isLegacyRspack) {
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
type: 'install_dependency',
|
|
182
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
183
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: 'manual_step',
|
|
187
|
+
target: buildTool.configPath,
|
|
188
|
+
description: `Update ${buildTool.configPath} to import \`rspackPlugin\` from \`@inspecto-dev/plugin/legacy/rspack\` and add it to the Rspack plugins array.`,
|
|
189
|
+
},
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
if (buildTool?.tool === 'webpack' && buildTool.isLegacyWebpack) {
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
type: 'install_dependency',
|
|
196
|
+
target: '@inspecto-dev/plugin @inspecto-dev/core',
|
|
197
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
type: 'manual_step',
|
|
201
|
+
target: buildTool.configPath,
|
|
202
|
+
description: `Update ${buildTool.configPath} to import \`webpackPlugin\` from \`@inspecto-dev/plugin/legacy/webpack4\` and add it to the Webpack plugins array.`,
|
|
203
|
+
},
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
|
|
152
207
|
return [
|
|
153
208
|
{
|
|
154
209
|
type: 'manual_step',
|
|
@@ -183,7 +238,10 @@ function manualFrameworkActions(context: OnboardingContext): PlanResult['actions
|
|
|
183
238
|
|
|
184
239
|
export async function createDetectionResult(root: string): Promise<DetectionResult> {
|
|
185
240
|
const context = await buildOnboardingContext(root)
|
|
186
|
-
const warnings = uniqueMessages([
|
|
241
|
+
const warnings = uniqueMessages([
|
|
242
|
+
...unsupportedEnvironmentWarnings(context),
|
|
243
|
+
...buildToolWarnings(context),
|
|
244
|
+
])
|
|
187
245
|
|
|
188
246
|
const buildToolResult = buildToolBlockers(context)
|
|
189
247
|
const frameworkResult = frameworkBlockers(context)
|
|
@@ -209,18 +267,36 @@ export async function createDetectionResult(root: string): Promise<DetectionResu
|
|
|
209
267
|
}
|
|
210
268
|
|
|
211
269
|
export function createPlanResult(context: OnboardingContext): PlanResult {
|
|
212
|
-
const warnings = uniqueMessages(
|
|
270
|
+
const warnings = uniqueMessages([
|
|
271
|
+
...unsupportedEnvironmentWarnings(context),
|
|
272
|
+
...buildToolWarnings(context),
|
|
273
|
+
])
|
|
213
274
|
const blockers = uniqueMessages([...buildToolBlockers(context), ...frameworkBlockers(context)])
|
|
214
275
|
const actions: PlanResult['actions'] = []
|
|
215
276
|
|
|
216
277
|
let strategy: PlanResult['strategy'] = 'supported'
|
|
217
278
|
|
|
218
|
-
|
|
279
|
+
const hasLegacyRspackManualPlan =
|
|
280
|
+
context.buildTools.supported.length === 1 &&
|
|
281
|
+
context.buildTools.supported[0]?.tool === 'rspack' &&
|
|
282
|
+
context.buildTools.supported[0]?.isLegacyRspack
|
|
283
|
+
const hasLegacyWebpackManualPlan =
|
|
284
|
+
context.buildTools.supported.length === 1 &&
|
|
285
|
+
context.buildTools.supported[0]?.tool === 'webpack' &&
|
|
286
|
+
context.buildTools.supported[0]?.isLegacyWebpack
|
|
287
|
+
|
|
288
|
+
if (blockers.length > 0 || hasLegacyRspackManualPlan || hasLegacyWebpackManualPlan) {
|
|
219
289
|
strategy = 'manual'
|
|
220
290
|
if (
|
|
221
291
|
context.buildTools.unsupported.length > 0 ||
|
|
222
292
|
context.buildTools.supported.length === 0 ||
|
|
223
|
-
context.buildTools.supported.length > 1
|
|
293
|
+
context.buildTools.supported.length > 1 ||
|
|
294
|
+
context.buildTools.supported.some(
|
|
295
|
+
buildTool => buildTool.tool === 'rspack' && buildTool.isLegacyRspack,
|
|
296
|
+
) ||
|
|
297
|
+
context.buildTools.supported.some(
|
|
298
|
+
buildTool => buildTool.tool === 'webpack' && buildTool.isLegacyWebpack,
|
|
299
|
+
)
|
|
224
300
|
) {
|
|
225
301
|
actions.push(...manualBuildToolActions(context))
|
|
226
302
|
}
|
|
@@ -126,8 +126,9 @@ async function detectFrameworkSupportByPackage(
|
|
|
126
126
|
|
|
127
127
|
async function buildTargetedContext(
|
|
128
128
|
rootContext: OnboardingContext,
|
|
129
|
-
packagePath: string,
|
|
129
|
+
target: { id?: string; packagePath: string },
|
|
130
130
|
): Promise<OnboardingContext> {
|
|
131
|
+
const packagePath = normalizePackagePath(target.packagePath)
|
|
131
132
|
const projectRoot = packagePath ? path.join(rootContext.root, packagePath) : rootContext.root
|
|
132
133
|
const [frameworks, ides, providers] = await Promise.all([
|
|
133
134
|
detectFrameworks(projectRoot),
|
|
@@ -140,7 +141,11 @@ async function buildTargetedContext(
|
|
|
140
141
|
packageManager: rootContext.packageManager,
|
|
141
142
|
buildTools: {
|
|
142
143
|
supported: rootContext.buildTools.supported.filter(item => {
|
|
143
|
-
|
|
144
|
+
const itemPackagePath = normalizePackagePath(item.packagePath)
|
|
145
|
+
if (target.id) {
|
|
146
|
+
return `${itemPackagePath || '.'}:${item.tool}:${item.configPath}` === target.id
|
|
147
|
+
}
|
|
148
|
+
return itemPackagePath === packagePath
|
|
144
149
|
}),
|
|
145
150
|
unsupported: [],
|
|
146
151
|
},
|
|
@@ -327,7 +332,8 @@ export async function resolveOnboardingSession(
|
|
|
327
332
|
if (target.status === 'needs_selection') {
|
|
328
333
|
const plan = createPlanResult(rootContext)
|
|
329
334
|
const summary: OnboardingSummary = {
|
|
330
|
-
headline:
|
|
335
|
+
headline:
|
|
336
|
+
'Inspecto needs one build target selection before setup so it knows which local dev build should receive the plugin and settings.',
|
|
331
337
|
changes: [],
|
|
332
338
|
risks: [],
|
|
333
339
|
manualFollowUp: [],
|
|
@@ -344,8 +350,7 @@ export async function resolveOnboardingSession(
|
|
|
344
350
|
}
|
|
345
351
|
}
|
|
346
352
|
|
|
347
|
-
const
|
|
348
|
-
const context = await buildTargetedContext(rootContext, packagePath)
|
|
353
|
+
const context = await buildTargetedContext(rootContext, target.selected!)
|
|
349
354
|
const verification = await buildVerification(context.root, context.packageManager)
|
|
350
355
|
const plan = createPlanResult(context)
|
|
351
356
|
const summary = buildOnboardingSummary(plan, context.root)
|
|
@@ -409,6 +414,7 @@ export async function applyResolvedOnboardingSession(
|
|
|
409
414
|
selectedIDE: session.selectedIDE,
|
|
410
415
|
providerDefault: session.providerDefault,
|
|
411
416
|
plan: session.plan,
|
|
417
|
+
allowManualPlanApply: session.plan.strategy === 'manual' && session.plan.blockers.length === 0,
|
|
412
418
|
})
|
|
413
419
|
|
|
414
420
|
const diagnostics = buildExecutionDiagnostics(session, applyResult)
|
|
@@ -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
|
+
isLegacyRspack: buildTool.isLegacyRspack,
|
|
67
|
+
isLegacyWebpack: buildTool.isLegacyWebpack,
|
|
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
|
}
|
|
@@ -95,6 +165,8 @@ export function resolveOnboardingTarget(
|
|
|
95
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
|
}
|
package/src/types.ts
CHANGED
|
@@ -63,9 +63,14 @@ 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
|
}
|
|
@@ -75,6 +80,8 @@ export interface OnboardingTargetResolution {
|
|
|
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 {
|