@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
package/src/inject/gitignore.ts
CHANGED
|
@@ -6,10 +6,20 @@ import { readFile, writeFile } from '../utils/fs.js'
|
|
|
6
6
|
import { log } from '../utils/logger.js'
|
|
7
7
|
|
|
8
8
|
/** Rules for default mode: fine-grained rules only */
|
|
9
|
-
const DEFAULT_RULES = [
|
|
9
|
+
const DEFAULT_RULES = [
|
|
10
|
+
'.inspecto/install.lock',
|
|
11
|
+
'.inspecto/cache.json',
|
|
12
|
+
'.inspecto/*.local.json',
|
|
13
|
+
'.inspecto/dev.json',
|
|
14
|
+
]
|
|
10
15
|
|
|
11
16
|
/** Rules for shared mode: same as default in current design to allow settings.json */
|
|
12
|
-
const SHARED_RULES = [
|
|
17
|
+
const SHARED_RULES = [
|
|
18
|
+
'.inspecto/install.lock',
|
|
19
|
+
'.inspecto/cache.json',
|
|
20
|
+
'.inspecto/*.local.json',
|
|
21
|
+
'.inspecto/dev.json',
|
|
22
|
+
]
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* Update .gitignore based on the init mode.
|
|
@@ -77,6 +87,7 @@ export async function cleanGitignore(root: string): Promise<void> {
|
|
|
77
87
|
.replace(/^\.inspecto\/install\.lock\s*$/gm, '')
|
|
78
88
|
.replace(/^\.inspecto\/cache\.json\s*$/gm, '')
|
|
79
89
|
.replace(/^\.inspecto\/\*\.local\.json\s*$/gm, '')
|
|
90
|
+
.replace(/^\.inspecto\/dev\.json\s*$/gm, '')
|
|
80
91
|
.replace(/\n{3,}/g, '\n\n') // Collapse excess blank lines
|
|
81
92
|
|
|
82
93
|
await writeFile(gitignorePath, cleaned)
|
package/src/instructions.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { log } from './utils/logger.js'
|
|
|
2
2
|
|
|
3
3
|
export function printNuxtManualInstructions() {
|
|
4
4
|
log.blank()
|
|
5
|
-
log.hint(
|
|
5
|
+
log.hint(
|
|
6
|
+
'Nuxt supports guided setup in the current version. Inspecto can prepare the config patch, but the client plugin mount step still needs review.',
|
|
7
|
+
)
|
|
6
8
|
log.hint('1. Update `nuxt.config.ts` to register the Inspecto Vite plugin:')
|
|
7
9
|
log.copyableCodeBlock([
|
|
8
10
|
"import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
@@ -13,7 +15,7 @@ export function printNuxtManualInstructions() {
|
|
|
13
15
|
' },',
|
|
14
16
|
'})',
|
|
15
17
|
])
|
|
16
|
-
log.hint('2.
|
|
18
|
+
log.hint('2. Complete the remaining client plugin mount step in `plugins/inspecto.client.ts`:')
|
|
17
19
|
log.copyableCodeBlock([
|
|
18
20
|
'export default defineNuxtPlugin(() => {',
|
|
19
21
|
' if (import.meta.dev) {',
|
|
@@ -23,12 +25,33 @@ export function printNuxtManualInstructions() {
|
|
|
23
25
|
' }',
|
|
24
26
|
'})',
|
|
25
27
|
])
|
|
26
|
-
log.hint('3. Restart your Nuxt dev server after
|
|
28
|
+
log.hint('3. Restart your Nuxt dev server after applying the guided patches.')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function printUmiManualInstructions() {
|
|
32
|
+
log.blank()
|
|
33
|
+
log.hint('Umi supports guided setup in the current version.')
|
|
34
|
+
log.hint('1. Update `config/config.ts` or `.umirc.ts` to register the Inspecto webpack plugin:')
|
|
35
|
+
log.copyableCodeBlock([
|
|
36
|
+
"import { defineConfig } from 'umi'",
|
|
37
|
+
"import { webpack4Plugin } from '@inspecto-dev/plugin/legacy/webpack4'",
|
|
38
|
+
'',
|
|
39
|
+
'export default defineConfig({',
|
|
40
|
+
' chainWebpack(memo) {',
|
|
41
|
+
" if (process.env.NODE_ENV === 'development') {",
|
|
42
|
+
" memo.plugin('inspecto').use(webpack4Plugin())",
|
|
43
|
+
' }',
|
|
44
|
+
' },',
|
|
45
|
+
'})',
|
|
46
|
+
])
|
|
47
|
+
log.hint('2. Restart your Umi dev server after applying the guided patches.')
|
|
27
48
|
}
|
|
28
49
|
|
|
29
50
|
export function printNextJsManualInstructions() {
|
|
30
51
|
log.blank()
|
|
31
|
-
log.hint(
|
|
52
|
+
log.hint(
|
|
53
|
+
'Next.js supports guided setup in the current version. Inspecto can prepare the config patch, but the client-side mount step still needs review.',
|
|
54
|
+
)
|
|
32
55
|
log.hint('1. Update `next.config.mjs` to register the Inspecto webpack plugin:')
|
|
33
56
|
log.copyableCodeBlock([
|
|
34
57
|
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
@@ -36,7 +59,7 @@ export function printNextJsManualInstructions() {
|
|
|
36
59
|
"/** @type {import('next').NextConfig} */",
|
|
37
60
|
'const nextConfig = {',
|
|
38
61
|
' webpack: (config, { dev, isServer }) => {',
|
|
39
|
-
' if (dev
|
|
62
|
+
' if (dev) {',
|
|
40
63
|
' config.plugins.push(inspecto())',
|
|
41
64
|
' }',
|
|
42
65
|
' return config',
|
|
@@ -46,7 +69,10 @@ export function printNextJsManualInstructions() {
|
|
|
46
69
|
'export default nextConfig',
|
|
47
70
|
])
|
|
48
71
|
log.hint(
|
|
49
|
-
'
|
|
72
|
+
'Keep the plugin enabled for both server and client development compilers so App Router server components also receive Inspecto transforms.',
|
|
73
|
+
)
|
|
74
|
+
log.hint(
|
|
75
|
+
'2. Complete the remaining client-side mount step in `app/layout.tsx` or `pages/_app.tsx`:',
|
|
50
76
|
)
|
|
51
77
|
log.copyableCodeBlock([
|
|
52
78
|
"'use client'",
|
|
@@ -65,5 +91,5 @@ export function printNextJsManualInstructions() {
|
|
|
65
91
|
' return <html><body>{children}</body></html>',
|
|
66
92
|
'}',
|
|
67
93
|
])
|
|
68
|
-
log.hint('3. Restart your Next.js dev server after
|
|
94
|
+
log.hint('3. Restart your Next.js dev server after applying the guided patches.')
|
|
69
95
|
}
|
package/src/onboarding/apply.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { injectPlugin } from '../inject/ast-injector.js'
|
|
|
5
5
|
import { installExtension } from '../inject/extension.js'
|
|
6
6
|
import { updateGitignore } from '../inject/gitignore.js'
|
|
7
7
|
import { shell } from '../utils/exec.js'
|
|
8
|
-
import { exists, readJSON, writeJSON } from '../utils/fs.js'
|
|
8
|
+
import { exists, readFile, readJSON, writeFile, writeJSON } from '../utils/fs.js'
|
|
9
9
|
import { log } from '../utils/logger.js'
|
|
10
10
|
import type {
|
|
11
11
|
BuildToolDetection,
|
|
@@ -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,141 @@ 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 =>
|
|
137
|
+
['manual_step', 'generate_patch_plan', 'generate_file', 'manual_confirmation'].includes(
|
|
138
|
+
action.type,
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
.map(action => action.description)
|
|
142
|
+
|
|
143
|
+
if (!includeBlockers) {
|
|
144
|
+
return steps
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return [...plan.blockers.map(blocker => blocker.message), ...steps]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function applyGuidedPatchContent(source: string, snippet: string): string {
|
|
151
|
+
if (source.includes("import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'")) {
|
|
152
|
+
return source
|
|
153
|
+
}
|
|
154
|
+
if (source.includes("import { vitePlugin as inspecto } from '@inspecto-dev/plugin'")) {
|
|
155
|
+
return source
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const trimmedSource = source.trimEnd()
|
|
159
|
+
if (/export\s+default\s*\{/m.test(source)) {
|
|
160
|
+
const nextSource = trimmedSource.replace(
|
|
161
|
+
/export\s+default\s*\{/m,
|
|
162
|
+
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\nexport default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },",
|
|
163
|
+
)
|
|
164
|
+
return `${nextSource}\n`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (/module\.exports\s*=\s*\{/m.test(source)) {
|
|
168
|
+
const nextSource = trimmedSource.replace(
|
|
169
|
+
/module\.exports\s*=\s*\{/m,
|
|
170
|
+
"const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\nmodule.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },",
|
|
171
|
+
)
|
|
172
|
+
return `${nextSource}\n`
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const objectExportVariableMatch = source.match(
|
|
176
|
+
/(const\s+([A-Za-z0-9_$]+)\s*(?::[^=]+)?=\s*\{[\s\S]*?\}\s*;?\s*)export\s+default\s+\2\s*;?/m,
|
|
177
|
+
)
|
|
178
|
+
const variableDeclaration = objectExportVariableMatch?.[1]
|
|
179
|
+
const variableName = objectExportVariableMatch?.[2]
|
|
180
|
+
if (variableDeclaration && variableName) {
|
|
181
|
+
const nextDeclaration = variableDeclaration.replace(
|
|
182
|
+
/=\s*\{/m,
|
|
183
|
+
'= {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },',
|
|
184
|
+
)
|
|
185
|
+
const nextSource = source.replace(
|
|
186
|
+
objectExportVariableMatch[0],
|
|
187
|
+
`import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\n${nextDeclaration}export default ${variableName}`,
|
|
188
|
+
)
|
|
189
|
+
return `${nextSource.trimEnd()}\n`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (/defineNuxtConfig\s*\(\s*\{/m.test(source)) {
|
|
193
|
+
const trimmedSource = source.trimEnd()
|
|
194
|
+
const nextSource = trimmedSource.replace(
|
|
195
|
+
/defineNuxtConfig\s*\(\s*\{/m,
|
|
196
|
+
'defineNuxtConfig({\n vite: {\n plugins: [inspecto()],\n },',
|
|
197
|
+
)
|
|
198
|
+
return `import { vitePlugin as inspecto } from '@inspecto-dev/plugin'\n\n${nextSource}\n`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const jsdocMatch = source.match(
|
|
202
|
+
/\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{/m,
|
|
203
|
+
)
|
|
204
|
+
if (jsdocMatch) {
|
|
205
|
+
const isEsm = jsdocMatch[1] === 'export default'
|
|
206
|
+
const importStatement = isEsm
|
|
207
|
+
? "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\n"
|
|
208
|
+
: "const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\n"
|
|
209
|
+
|
|
210
|
+
const replacementPattern = isEsm ? /export\s+default\s*\{/m : /module\.exports\s*=\s*\{/m
|
|
211
|
+
|
|
212
|
+
const injectConfig = isEsm
|
|
213
|
+
? 'export default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },'
|
|
214
|
+
: 'module.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },'
|
|
215
|
+
|
|
216
|
+
const nextSource = source.replace(replacementPattern, injectConfig)
|
|
217
|
+
return `${importStatement}${nextSource.trimEnd()}\n`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return `${trimmedSource}\n\n${snippet}\n`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function applyGuidedPlanPatches(
|
|
224
|
+
input: ApplyOnboardingInput,
|
|
225
|
+
mutations: Mutation[],
|
|
226
|
+
reporter: ApplyReporter,
|
|
227
|
+
): Promise<string[]> {
|
|
228
|
+
const nextSteps: string[] = []
|
|
229
|
+
|
|
230
|
+
if (!input.plan || input.plan.strategy !== 'guided' || !input.plan.patches?.length) {
|
|
231
|
+
return nextSteps
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const patch of input.plan.patches) {
|
|
235
|
+
if (
|
|
236
|
+
patch.status !== 'planned' ||
|
|
237
|
+
(!patch.reason.startsWith('next_config_') && !patch.reason.startsWith('nuxt_config_'))
|
|
238
|
+
) {
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const patchPath = path.join(input.projectRoot, patch.path)
|
|
243
|
+
const existingContent = await readFile(patchPath)
|
|
244
|
+
if (existingContent === null) {
|
|
245
|
+
nextSteps.push(`Could not auto-apply the guided patch for ${patch.path}.`)
|
|
246
|
+
continue
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const nextContent = applyGuidedPatchContent(existingContent, patch.snippet)
|
|
250
|
+
if (nextContent === existingContent) {
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (input.options.dryRun) {
|
|
255
|
+
reporter.dryRun(`Would apply guided patch to ${patch.path}`)
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await writeFile(patchPath, nextContent)
|
|
260
|
+
mutations.push({
|
|
261
|
+
type: 'file_modified',
|
|
262
|
+
path: patch.path,
|
|
263
|
+
description: 'Automatically configured Inspecto guided Next.js patch',
|
|
264
|
+
})
|
|
265
|
+
reporter.success(`Applied guided patch to ${patch.path}`)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return nextSteps
|
|
101
269
|
}
|
|
102
270
|
|
|
103
271
|
export async function applyOnboardingPlan(
|
|
@@ -163,6 +331,10 @@ async function applyOnboardingPlanInternal(
|
|
|
163
331
|
input: ApplyOnboardingInput,
|
|
164
332
|
): Promise<ApplyOnboardingResult> {
|
|
165
333
|
const reporter = createReporter(input.options.quiet)
|
|
334
|
+
const additiveManualPlan =
|
|
335
|
+
(input.plan?.strategy === 'manual' || input.plan?.strategy === 'guided') &&
|
|
336
|
+
input.allowManualPlanApply &&
|
|
337
|
+
input.plan.blockers.length === 0
|
|
166
338
|
|
|
167
339
|
if (input.plan && input.plan.strategy !== 'supported' && !input.allowManualPlanApply) {
|
|
168
340
|
return {
|
|
@@ -172,7 +344,7 @@ async function applyOnboardingPlanInternal(
|
|
|
172
344
|
installFailed: false,
|
|
173
345
|
injectionFailed: false,
|
|
174
346
|
manualExtensionInstallNeeded: false,
|
|
175
|
-
nextSteps: manualPlanSteps(input.plan),
|
|
347
|
+
nextSteps: manualPlanSteps(input.plan, true),
|
|
176
348
|
},
|
|
177
349
|
}
|
|
178
350
|
}
|
|
@@ -183,6 +355,11 @@ async function applyOnboardingPlanInternal(
|
|
|
183
355
|
const promptsFileName = input.options.shared ? 'prompts.json' : 'prompts.local.json'
|
|
184
356
|
const settingsPath = path.join(settingsDir, settingsFileName)
|
|
185
357
|
const promptsPath = path.join(settingsDir, promptsFileName)
|
|
358
|
+
const inheritedDefaults = await readInheritedSettingsDefaults(
|
|
359
|
+
input.repoRoot,
|
|
360
|
+
input.projectRoot,
|
|
361
|
+
settingsFileName,
|
|
362
|
+
)
|
|
186
363
|
const runtimePackages = resolveRuntimePackages()
|
|
187
364
|
const installCmd = getInstallCommand(input.packageManager, runtimePackages.installSpec)
|
|
188
365
|
const nextSteps: string[] = []
|
|
@@ -209,6 +386,11 @@ async function applyOnboardingPlanInternal(
|
|
|
209
386
|
spinner.fail('Dependency installation failed')
|
|
210
387
|
installFailed = true
|
|
211
388
|
reporter.error(`Failed to install dependency: ${error?.message || 'Unknown error'}`)
|
|
389
|
+
if (error?.stderr) {
|
|
390
|
+
reporter.error(`Details: ${error.stderr}`)
|
|
391
|
+
} else if (error?.stdout) {
|
|
392
|
+
reporter.error(`Details: ${error.stdout}`)
|
|
393
|
+
}
|
|
212
394
|
reporter.hint(`Run manually in ${input.projectRoot}: ${installCmd}`)
|
|
213
395
|
reporter.hint(
|
|
214
396
|
'Setup will continue without dependencies, but Inspecto may not run until installation succeeds.',
|
|
@@ -217,20 +399,26 @@ async function applyOnboardingPlanInternal(
|
|
|
217
399
|
}
|
|
218
400
|
|
|
219
401
|
let injectionFailed = Boolean(input.injectionSkippedRequiresManualConfig)
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
402
|
+
if (!additiveManualPlan) {
|
|
403
|
+
for (const target of input.supportedBuildTargets) {
|
|
404
|
+
const result = await injectPlugin(
|
|
405
|
+
input.repoRoot,
|
|
406
|
+
target,
|
|
407
|
+
input.options.dryRun,
|
|
408
|
+
input.options.quiet ?? false,
|
|
409
|
+
)
|
|
410
|
+
if (result.success) {
|
|
411
|
+
mutations.push(...result.mutations)
|
|
412
|
+
} else {
|
|
413
|
+
injectionFailed = true
|
|
414
|
+
}
|
|
231
415
|
}
|
|
232
416
|
}
|
|
233
417
|
|
|
418
|
+
if (additiveManualPlan) {
|
|
419
|
+
nextSteps.push(...(await applyGuidedPlanPatches(input, mutations, reporter)))
|
|
420
|
+
}
|
|
421
|
+
|
|
234
422
|
if (await exists(settingsPath)) {
|
|
235
423
|
const existingSettings = await readJSON(settingsPath)
|
|
236
424
|
if (existingSettings === null) {
|
|
@@ -238,20 +426,56 @@ async function applyOnboardingPlanInternal(
|
|
|
238
426
|
reporter.hint('Please fix the syntax errors manually, or delete it and re-run init')
|
|
239
427
|
nextSteps.push(`Fix .inspecto/${settingsFileName} or delete it and rerun Inspecto setup.`)
|
|
240
428
|
} else {
|
|
241
|
-
|
|
429
|
+
const mergedSettings =
|
|
430
|
+
existingSettings && typeof existingSettings === 'object'
|
|
431
|
+
? { ...(existingSettings as Record<string, unknown>) }
|
|
432
|
+
: {}
|
|
433
|
+
let settingsChanged = false
|
|
434
|
+
|
|
435
|
+
const desiredIde =
|
|
436
|
+
inheritedDefaults.ide ??
|
|
437
|
+
(input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : undefined)
|
|
438
|
+
|
|
439
|
+
if (desiredIde && !mergedSettings.ide) {
|
|
440
|
+
mergedSettings.ide = desiredIde
|
|
441
|
+
settingsChanged = true
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault
|
|
445
|
+
if (desiredProviderDefault && !mergedSettings['provider.default']) {
|
|
446
|
+
mergedSettings['provider.default'] = desiredProviderDefault
|
|
447
|
+
settingsChanged = true
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (settingsChanged) {
|
|
451
|
+
if (input.options.dryRun) {
|
|
452
|
+
reporter.dryRun(`Would update .inspecto/${settingsFileName}`)
|
|
453
|
+
} else {
|
|
454
|
+
await writeJSON(settingsPath, mergedSettings)
|
|
455
|
+
reporter.success(`Updated .inspecto/${settingsFileName} with missing defaults`)
|
|
456
|
+
mutations.push({
|
|
457
|
+
type: 'file_modified',
|
|
458
|
+
path: `.inspecto/${settingsFileName}`,
|
|
459
|
+
description: 'Merged missing Inspecto defaults into existing settings',
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
reporter.success(`.inspecto/${settingsFileName} already exists (skipped)`)
|
|
464
|
+
}
|
|
242
465
|
}
|
|
243
466
|
} else {
|
|
244
467
|
const defaultSettings: Record<string, unknown> = {}
|
|
468
|
+
const desiredIde =
|
|
469
|
+
inheritedDefaults.ide ??
|
|
470
|
+
(input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : undefined)
|
|
471
|
+
const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault
|
|
245
472
|
|
|
246
|
-
if (
|
|
247
|
-
defaultSettings.ide =
|
|
248
|
-
input.selectedIDE.ide.toLowerCase() === 'vscode'
|
|
249
|
-
? 'vscode'
|
|
250
|
-
: input.selectedIDE.ide.toLowerCase()
|
|
473
|
+
if (desiredIde) {
|
|
474
|
+
defaultSettings.ide = desiredIde
|
|
251
475
|
}
|
|
252
476
|
|
|
253
|
-
if (
|
|
254
|
-
defaultSettings['provider.default'] =
|
|
477
|
+
if (desiredProviderDefault) {
|
|
478
|
+
defaultSettings['provider.default'] = desiredProviderDefault
|
|
255
479
|
}
|
|
256
480
|
|
|
257
481
|
if (input.options.dryRun) {
|
|
@@ -340,6 +564,9 @@ async function applyOnboardingPlanInternal(
|
|
|
340
564
|
if (manualExtensionInstallNeeded) {
|
|
341
565
|
nextSteps.push('Install the Inspecto IDE extension manually')
|
|
342
566
|
}
|
|
567
|
+
if (additiveManualPlan && input.plan) {
|
|
568
|
+
nextSteps.push(...manualPlanSteps(input.plan, false))
|
|
569
|
+
}
|
|
343
570
|
if (input.manualConfigRequiredFor === 'Nuxt') {
|
|
344
571
|
nextSteps.push(
|
|
345
572
|
'Nuxt detected—please follow the Nuxt instructions printed above to finish setup.',
|