@inspecto-dev/cli 0.2.0-alpha.1 → 0.2.0-alpha.3
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 +20 -19
- package/.turbo/turbo-test.log +4 -4
- package/CHANGELOG.md +22 -0
- package/README.md +5 -5
- package/dist/bin.js +50 -68
- package/dist/{chunk-DBXT75QF.js → chunk-HIL6365F.js} +718 -443
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/src/bin.ts +84 -70
- package/src/commands/doctor.ts +38 -25
- package/src/commands/init.ts +107 -228
- package/src/commands/teardown.ts +13 -23
- package/src/detect/build-tool.ts +169 -21
- package/src/detect/framework.ts +71 -9
- package/src/detect/ide.ts +42 -21
- package/src/detect/package-manager.ts +10 -3
- package/src/detect/provider.ts +151 -0
- package/src/inject/ast-injector.ts +70 -231
- package/src/inject/extension.ts +49 -34
- package/src/inject/gitignore.ts +1 -1
- package/src/inject/strategies/esbuild.ts +35 -0
- package/src/inject/strategies/index.ts +16 -0
- package/src/inject/strategies/rollup.ts +35 -0
- package/src/inject/strategies/rsbuild.ts +29 -0
- package/src/inject/strategies/rspack.ts +34 -0
- package/src/inject/strategies/types.ts +35 -0
- package/src/inject/strategies/vite.ts +30 -0
- package/src/inject/strategies/webpack.ts +36 -0
- package/src/instructions.ts +55 -0
- package/src/prompts.ts +115 -0
- package/src/types.ts +4 -1
- package/tests/ide.test.ts +3 -3
- package/src/detect/ai-tool.ts +0 -127
package/src/commands/init.ts
CHANGED
|
@@ -14,149 +14,18 @@ import { detectPackageManager, getInstallCommand } from '../detect/package-manag
|
|
|
14
14
|
import { detectBuildTools, resolveInjectionTarget } from '../detect/build-tool.js'
|
|
15
15
|
import { detectFrameworks } from '../detect/framework.js'
|
|
16
16
|
import { detectIDE } from '../detect/ide.js'
|
|
17
|
-
import {
|
|
17
|
+
import { detectProviders, type ProviderDetection } from '../detect/provider.js'
|
|
18
18
|
import { injectPlugin } from '../inject/ast-injector.js'
|
|
19
19
|
import { updateGitignore } from '../inject/gitignore.js'
|
|
20
20
|
import { installExtension } from '../inject/extension.js'
|
|
21
|
-
import type { InitOptions, InstallLock, Mutation
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!process.stdin.isTTY) {
|
|
30
|
-
log.warn('Multiple IDEs detected but stdin is not interactive')
|
|
31
|
-
log.hint(`Using: ${detections[0]!.ide} (first match)`)
|
|
32
|
-
return detections[0]!
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
console.log()
|
|
36
|
-
console.log(' ? Detected multiple IDEs:')
|
|
37
|
-
detections.forEach((d, i) => {
|
|
38
|
-
const status = d.supported ? ' (supported)' : ' (unsupported/limited)'
|
|
39
|
-
console.log(` ${i + 1}. ${d.ide}${status}`)
|
|
40
|
-
})
|
|
41
|
-
console.log()
|
|
42
|
-
|
|
43
|
-
return new Promise(resolve => {
|
|
44
|
-
process.stdout.write(' > Your choice: ')
|
|
45
|
-
|
|
46
|
-
// We must resume stdin in case it was paused by a previous prompt
|
|
47
|
-
process.stdin.resume()
|
|
48
|
-
process.stdin.setEncoding('utf-8')
|
|
49
|
-
|
|
50
|
-
const onData = (data: Buffer) => {
|
|
51
|
-
const choice = parseInt(String(data).trim(), 10)
|
|
52
|
-
|
|
53
|
-
// Cleanup the listener to avoid memory leaks or multiple fires
|
|
54
|
-
process.stdin.off('data', onData)
|
|
55
|
-
process.stdin.pause() // Pause so the CLI can exit when done
|
|
56
|
-
|
|
57
|
-
if (choice >= 1 && choice <= detections.length) {
|
|
58
|
-
resolve(detections[choice - 1]!)
|
|
59
|
-
} else {
|
|
60
|
-
resolve(null)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
process.stdin.on('data', onData)
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Interactive prompt for AI tool choice.
|
|
69
|
-
*/
|
|
70
|
-
async function promptAIToolChoice(detections: AIToolDetection[]): Promise<AIToolDetection | null> {
|
|
71
|
-
if (!process.stdin.isTTY) {
|
|
72
|
-
log.warn('Multiple AI tools detected but stdin is not interactive')
|
|
73
|
-
log.hint(`Using: ${detections[0]!.label} (first match)`)
|
|
74
|
-
return detections[0]!
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
console.log()
|
|
78
|
-
console.log(' ? Detected multiple AI tools:')
|
|
79
|
-
detections.forEach((d, i) => {
|
|
80
|
-
// Map toolModes array to human-readable labels
|
|
81
|
-
const modeLabels = d.toolModes.map(mode =>
|
|
82
|
-
mode === 'plugin' ? 'VS Code Extension' : 'Terminal CLI',
|
|
83
|
-
)
|
|
84
|
-
const modeStr = modeLabels.join(' & ')
|
|
85
|
-
|
|
86
|
-
const status = d.supported ? ` (supported ${modeStr})` : ` (unsupported/limited)`
|
|
87
|
-
console.log(` ${i + 1}. ${d.label}${status}`)
|
|
88
|
-
})
|
|
89
|
-
console.log()
|
|
90
|
-
|
|
91
|
-
return new Promise(resolve => {
|
|
92
|
-
process.stdout.write(' > Your choice: ')
|
|
93
|
-
|
|
94
|
-
// We must resume stdin in case it was paused by a previous prompt
|
|
95
|
-
process.stdin.resume()
|
|
96
|
-
process.stdin.setEncoding('utf-8')
|
|
97
|
-
|
|
98
|
-
const onData = (data: Buffer) => {
|
|
99
|
-
const choice = parseInt(String(data).trim(), 10)
|
|
100
|
-
|
|
101
|
-
// Cleanup the listener to avoid memory leaks or multiple fires
|
|
102
|
-
process.stdin.off('data', onData)
|
|
103
|
-
process.stdin.pause() // Pause so the CLI can exit when done
|
|
104
|
-
|
|
105
|
-
if (choice >= 1 && choice <= detections.length) {
|
|
106
|
-
resolve(detections[choice - 1]!)
|
|
107
|
-
} else {
|
|
108
|
-
resolve(null)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
process.stdin.on('data', onData)
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function promptConfigChoice(
|
|
117
|
-
detections: BuildToolDetection[],
|
|
118
|
-
): Promise<BuildToolDetection | null> {
|
|
119
|
-
if (!process.stdin.isTTY) {
|
|
120
|
-
log.warn('Multiple config files detected but stdin is not interactive')
|
|
121
|
-
log.hint(`Using: ${detections[0]!.label} (first match)`)
|
|
122
|
-
return detections[0]!
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log()
|
|
126
|
-
console.log(' ? Detected multiple build tool configs:')
|
|
127
|
-
detections.forEach((d, i) => {
|
|
128
|
-
// Determine recommendation based on whether it seems like the "main" one
|
|
129
|
-
// We can infer Rsbuild > Rspack > Vite > Webpack as a priority if needed,
|
|
130
|
-
// but without package.json scripts analysis, we just highlight them plainly.
|
|
131
|
-
console.log(` ${i + 1}. ${d.label}`)
|
|
132
|
-
})
|
|
133
|
-
console.log(` ${detections.length + 1}. Skip (I'll configure manually)`)
|
|
134
|
-
console.log()
|
|
135
|
-
|
|
136
|
-
return new Promise(resolve => {
|
|
137
|
-
process.stdout.write(' > Your choice: ')
|
|
138
|
-
|
|
139
|
-
// We must resume stdin in case it was paused by a previous prompt
|
|
140
|
-
process.stdin.resume()
|
|
141
|
-
process.stdin.setEncoding('utf-8')
|
|
142
|
-
|
|
143
|
-
const onData = (data: Buffer) => {
|
|
144
|
-
const choice = parseInt(String(data).trim(), 10)
|
|
145
|
-
|
|
146
|
-
// Cleanup the listener to avoid memory leaks or multiple fires
|
|
147
|
-
process.stdin.off('data', onData)
|
|
148
|
-
process.stdin.pause() // Pause so the CLI can exit when done
|
|
149
|
-
|
|
150
|
-
if (choice >= 1 && choice <= detections.length) {
|
|
151
|
-
resolve(detections[choice - 1]!)
|
|
152
|
-
} else {
|
|
153
|
-
resolve(null)
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
process.stdin.on('data', onData)
|
|
158
|
-
})
|
|
159
|
-
}
|
|
21
|
+
import type { InitOptions, InstallLock, Mutation } from '../types.js'
|
|
22
|
+
import {
|
|
23
|
+
promptIDEChoice,
|
|
24
|
+
promptProviderChoice,
|
|
25
|
+
promptConfigChoice,
|
|
26
|
+
promptUnsupportedFrameworkContinue,
|
|
27
|
+
} from '../prompts.js'
|
|
28
|
+
import { printNextJsManualInstructions, printNuxtManualInstructions } from '../instructions.js'
|
|
160
29
|
|
|
161
30
|
export async function init(options: InitOptions): Promise<void> {
|
|
162
31
|
const root = process.cwd()
|
|
@@ -172,56 +41,79 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
172
41
|
}
|
|
173
42
|
|
|
174
43
|
// ---- Step 2: Detect environment ----
|
|
44
|
+
const [pm, frameworkResult, buildResult, ideProbe, providerProbe] = await Promise.all([
|
|
45
|
+
detectPackageManager(root),
|
|
46
|
+
detectFrameworks(root),
|
|
47
|
+
detectBuildTools(root),
|
|
48
|
+
detectIDE(root),
|
|
49
|
+
detectProviders(root),
|
|
50
|
+
])
|
|
175
51
|
|
|
176
52
|
// Package manager
|
|
177
|
-
const pm = await detectPackageManager(root)
|
|
178
53
|
log.success(`Detected package manager: ${pm}`)
|
|
179
54
|
|
|
180
|
-
// Framework
|
|
181
|
-
const frameworkResult = await detectFrameworks(root)
|
|
55
|
+
// Framework verification
|
|
182
56
|
if (frameworkResult.supported.length > 0) {
|
|
183
57
|
log.success(`Detected framework: ${frameworkResult.supported.join(', ')}`)
|
|
184
58
|
}
|
|
185
|
-
|
|
59
|
+
|
|
60
|
+
const isSupported = frameworkResult.supported.length > 0
|
|
61
|
+
const hasUnsupported = frameworkResult.unsupported.length > 0
|
|
62
|
+
|
|
63
|
+
if (!isSupported) {
|
|
64
|
+
if (hasUnsupported) {
|
|
65
|
+
const names = frameworkResult.unsupported.map(f => f.name).join(', ')
|
|
66
|
+
log.warn(`Detected ${names} — not supported in v1 (React / Vue only)`)
|
|
67
|
+
} else {
|
|
68
|
+
log.warn('No frontend framework detected')
|
|
69
|
+
log.hint('Inspecto current version supports React and Vue projects')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!options.force) {
|
|
73
|
+
const shouldContinue = await promptUnsupportedFrameworkContinue()
|
|
74
|
+
if (!shouldContinue) {
|
|
75
|
+
log.warn('Initialization aborted.')
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
log.warn('Continuing anyway (--force)')
|
|
80
|
+
}
|
|
81
|
+
} else if (hasUnsupported) {
|
|
186
82
|
const names = frameworkResult.unsupported.map(f => f.name).join(', ')
|
|
187
|
-
log.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (frameworkResult.supported.length === 0 && frameworkResult.unsupported.length === 0) {
|
|
191
|
-
log.warn('No frontend framework detected')
|
|
192
|
-
log.hint('Inspecto v1 supports React and Vue projects')
|
|
83
|
+
log.hint(
|
|
84
|
+
`Note: Inspecto will be configured for ${frameworkResult.supported.join(', ')}. Other detected frameworks (${names}) will be ignored.`,
|
|
85
|
+
)
|
|
193
86
|
}
|
|
194
87
|
|
|
195
|
-
// Build tool detection
|
|
196
|
-
|
|
88
|
+
// Build tool detection
|
|
89
|
+
let manualConfigRequiredFor = ''
|
|
197
90
|
if (buildResult.supported.length > 0) {
|
|
198
91
|
buildResult.supported.forEach(bt => log.success(`Detected: ${bt.label}`))
|
|
199
92
|
}
|
|
200
93
|
if (buildResult.unsupported.length > 0) {
|
|
201
94
|
const names = buildResult.unsupported.join(', ')
|
|
202
|
-
|
|
203
|
-
log.
|
|
204
|
-
log.hint('
|
|
95
|
+
manualConfigRequiredFor = buildResult.unsupported[0] || ''
|
|
96
|
+
log.warn(`Detected ${names} — automatic plugin injection is not supported in current version`)
|
|
97
|
+
log.hint('You can still manually configure it by modifying your configuration file')
|
|
205
98
|
}
|
|
206
99
|
if (buildResult.supported.length === 0 && buildResult.unsupported.length === 0) {
|
|
207
100
|
log.warn('No recognized build tool detected')
|
|
208
|
-
log.hint('
|
|
101
|
+
log.hint('current version supports: Vite, Webpack, Rspack, esbuild, Rollup')
|
|
209
102
|
log.hint('Dependency will be installed but plugin injection will be skipped')
|
|
103
|
+
log.hint(
|
|
104
|
+
'Please refer to the manual setup guide: https://inspecto-dev.github.io/inspecto/guide/manual-installation',
|
|
105
|
+
)
|
|
210
106
|
}
|
|
211
107
|
|
|
212
108
|
// IDE detection
|
|
213
|
-
const ideProbe = await detectIDE(root)
|
|
214
109
|
let selectedIDE: { ide: string; supported: boolean } | null = null
|
|
215
110
|
|
|
216
111
|
if (ideProbe.detected.length === 0) {
|
|
217
112
|
log.error('No IDE detected in current project')
|
|
218
113
|
log.hint('Please open this project in a supported IDE (like VS Code)')
|
|
219
|
-
// We could potentially block here, but we will allow the rest of the CLI to run
|
|
220
|
-
// for settings generation and build config injection.
|
|
221
114
|
} else if (ideProbe.detected.length === 1) {
|
|
222
115
|
selectedIDE = ideProbe.detected[0]!
|
|
223
116
|
} else {
|
|
224
|
-
// Has multiple
|
|
225
117
|
selectedIDE = await promptIDEChoice(ideProbe.detected)
|
|
226
118
|
}
|
|
227
119
|
|
|
@@ -238,24 +130,21 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
238
130
|
}
|
|
239
131
|
|
|
240
132
|
// AI Tool detection
|
|
241
|
-
|
|
242
|
-
let selectedAITool: AIToolDetection | null = null
|
|
133
|
+
let selectedProvider: ProviderDetection | null = null
|
|
243
134
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (aiToolProbe.detected.length === 0) {
|
|
135
|
+
if (!options.provider) {
|
|
136
|
+
if (providerProbe.detected.length === 0) {
|
|
247
137
|
log.warn('No supported AI tools detected')
|
|
248
138
|
log.hint('Inspecto works best with Claude Code, Trae CLI, or GitHub Copilot')
|
|
249
|
-
} else if (
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
log.success(`Detected AI tool: ${
|
|
139
|
+
} else if (providerProbe.detected.length === 1) {
|
|
140
|
+
selectedProvider = providerProbe.detected[0]!
|
|
141
|
+
if (selectedProvider.supported) {
|
|
142
|
+
log.success(`Detected AI tool: ${selectedProvider.label}`)
|
|
253
143
|
}
|
|
254
144
|
} else {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
log.success(`Selected AI tool: ${selectedAITool.label}`)
|
|
145
|
+
selectedProvider = await promptProviderChoice(providerProbe.detected)
|
|
146
|
+
if (selectedProvider) {
|
|
147
|
+
log.success(`Selected provider: ${selectedProvider.label}`)
|
|
259
148
|
}
|
|
260
149
|
}
|
|
261
150
|
}
|
|
@@ -275,22 +164,12 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
275
164
|
throw new Error(result.stderr)
|
|
276
165
|
}
|
|
277
166
|
log.success('Installed @inspecto-dev/plugin and @inspecto-dev/core as devDependencies')
|
|
278
|
-
mutations.push({
|
|
279
|
-
|
|
280
|
-
name: '@inspecto-dev/plugin',
|
|
281
|
-
dev: true,
|
|
282
|
-
})
|
|
283
|
-
mutations.push({
|
|
284
|
-
type: 'dependency_added',
|
|
285
|
-
name: '@inspecto-dev/core',
|
|
286
|
-
dev: true,
|
|
287
|
-
})
|
|
167
|
+
mutations.push({ type: 'dependency_added', name: '@inspecto-dev/plugin', dev: true })
|
|
168
|
+
mutations.push({ type: 'dependency_added', name: '@inspecto-dev/core', dev: true })
|
|
288
169
|
} catch (err: any) {
|
|
289
170
|
installFailed = true
|
|
290
171
|
log.error(`Failed to install dependency: ${err?.message || 'Unknown error'}`)
|
|
291
172
|
log.hint(`Run manually: ${installCmd}`)
|
|
292
|
-
// We do not return here to allow the rest of the setup to continue,
|
|
293
|
-
// but we will show a warning at the end instead of the success message.
|
|
294
173
|
}
|
|
295
174
|
}
|
|
296
175
|
}
|
|
@@ -319,68 +198,58 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
319
198
|
|
|
320
199
|
// ---- Step 5: Generate default settings ----
|
|
321
200
|
const settingsDir = path.join(root, '.inspecto')
|
|
322
|
-
const
|
|
323
|
-
const
|
|
201
|
+
const settingsFileName = options.shared ? 'settings.json' : 'settings.local.json'
|
|
202
|
+
const promptsFileName = options.shared ? 'prompts.json' : 'prompts.local.json'
|
|
203
|
+
|
|
204
|
+
const settingsPath = path.join(settingsDir, settingsFileName)
|
|
205
|
+
const promptsPath = path.join(settingsDir, promptsFileName)
|
|
324
206
|
|
|
325
207
|
if (await exists(settingsPath)) {
|
|
326
|
-
// Attempt to read the existing settings file to see if it's valid JSON
|
|
327
208
|
const existingSettings = await readJSON(settingsPath)
|
|
328
209
|
if (existingSettings === null) {
|
|
329
|
-
log.warn(
|
|
210
|
+
log.warn(`.inspecto/${settingsFileName} exists but contains invalid JSON`)
|
|
330
211
|
log.hint('Please fix the syntax errors manually, or delete it and re-run init')
|
|
331
212
|
} else {
|
|
332
|
-
log.success(
|
|
213
|
+
log.success(`.inspecto/${settingsFileName} already exists (skipped)`)
|
|
333
214
|
}
|
|
334
215
|
} else {
|
|
335
|
-
// Only omit properties that can be auto-inferred
|
|
336
|
-
// The schema allows empty objects or just specifying the IDE/prefer
|
|
337
216
|
const defaultSettings: Record<string, unknown> = {}
|
|
338
217
|
|
|
339
218
|
if (selectedIDE && selectedIDE.supported) {
|
|
340
|
-
defaultSettings.ide =
|
|
219
|
+
defaultSettings.ide =
|
|
220
|
+
selectedIDE.ide.toLowerCase() === 'vscode' ? 'vscode' : selectedIDE.ide.toLowerCase()
|
|
341
221
|
}
|
|
342
222
|
|
|
343
|
-
if (options.
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
defaultSettings.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
defaultSettings.providers = {
|
|
352
|
-
[selectedAITool.id]: {
|
|
353
|
-
type: selectedAITool.preferredMode,
|
|
354
|
-
},
|
|
355
|
-
}
|
|
356
|
-
}
|
|
223
|
+
if (options.provider) {
|
|
224
|
+
const tool = options.provider
|
|
225
|
+
const mode = tool === 'coco' ? 'cli' : 'extension'
|
|
226
|
+
defaultSettings['provider.default'] = `${tool}.${mode}`
|
|
227
|
+
} else if (selectedProvider) {
|
|
228
|
+
const toolId = selectedProvider.id as string
|
|
229
|
+
const mode = selectedProvider.preferredMode === 'cli' ? 'cli' : 'extension'
|
|
230
|
+
defaultSettings['provider.default'] = `${toolId}.${mode}`
|
|
357
231
|
}
|
|
358
232
|
|
|
359
233
|
if (options.dryRun) {
|
|
360
|
-
log.dryRun(
|
|
234
|
+
log.dryRun(`Would create .inspecto/${settingsFileName}`)
|
|
361
235
|
} else {
|
|
362
236
|
await writeJSON(settingsPath, defaultSettings)
|
|
363
|
-
log.success(
|
|
364
|
-
mutations.push({ type: 'file_created', path:
|
|
237
|
+
log.success(`Created .inspecto/${settingsFileName}`)
|
|
238
|
+
mutations.push({ type: 'file_created', path: `.inspecto/${settingsFileName}` })
|
|
365
239
|
}
|
|
366
240
|
}
|
|
367
241
|
|
|
368
|
-
// Generate prompts.json to disable low-frequency intents by default
|
|
369
242
|
if (await exists(promptsPath)) {
|
|
370
|
-
log.success(
|
|
243
|
+
log.success(`.inspecto/${promptsFileName} already exists (skipped)`)
|
|
371
244
|
} else {
|
|
372
|
-
const defaultPrompts = [
|
|
373
|
-
{ id: 'code-review', enabled: false },
|
|
374
|
-
{ id: 'generate-test', enabled: false },
|
|
375
|
-
{ id: 'performance', enabled: false },
|
|
376
|
-
]
|
|
245
|
+
const defaultPrompts: unknown[] = []
|
|
377
246
|
|
|
378
247
|
if (options.dryRun) {
|
|
379
|
-
log.dryRun(
|
|
248
|
+
log.dryRun(`Would create .inspecto/${promptsFileName}`)
|
|
380
249
|
} else {
|
|
381
250
|
await writeJSON(promptsPath, defaultPrompts)
|
|
382
|
-
log.success(
|
|
383
|
-
mutations.push({ type: 'file_created', path:
|
|
251
|
+
log.success(`Created .inspecto/${promptsFileName}`)
|
|
252
|
+
mutations.push({ type: 'file_created', path: `.inspecto/${promptsFileName}` })
|
|
384
253
|
}
|
|
385
254
|
}
|
|
386
255
|
|
|
@@ -406,19 +275,17 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
406
275
|
await writeJSON(path.join(settingsDir, 'install.lock'), lock)
|
|
407
276
|
}
|
|
408
277
|
|
|
409
|
-
// ---- Step 8: Install
|
|
410
|
-
// Only attempt if IDE is VS Code or not detected (might be VS Code)
|
|
278
|
+
// ---- Step 8: Install IDE extension ----
|
|
411
279
|
const shouldInstallExt =
|
|
412
280
|
!options.noExtension && (!selectedIDE || (selectedIDE && selectedIDE.supported))
|
|
413
|
-
|
|
414
281
|
let manualExtensionInstallNeeded = false
|
|
415
282
|
|
|
416
283
|
if (options.noExtension) {
|
|
417
|
-
log.warn('Skipping
|
|
284
|
+
log.warn('Skipping IDE extension (--no-extension)')
|
|
418
285
|
} else if (!shouldInstallExt) {
|
|
419
|
-
// Unsupported IDE detected — skip extension
|
|
286
|
+
// Unsupported IDE detected — skip extension
|
|
420
287
|
} else {
|
|
421
|
-
const extMutation = await installExtension(options.dryRun)
|
|
288
|
+
const extMutation = await installExtension(options.dryRun, selectedIDE?.ide)
|
|
422
289
|
if (extMutation && !options.dryRun) {
|
|
423
290
|
mutations.push(extMutation)
|
|
424
291
|
|
|
@@ -441,10 +308,22 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
441
308
|
if (options.dryRun) {
|
|
442
309
|
log.blank()
|
|
443
310
|
log.warn('Dry run complete. No files were modified.')
|
|
444
|
-
} else if (
|
|
311
|
+
} else if (
|
|
312
|
+
installFailed ||
|
|
313
|
+
injectionFailed ||
|
|
314
|
+
manualExtensionInstallNeeded ||
|
|
315
|
+
manualConfigRequiredFor
|
|
316
|
+
) {
|
|
445
317
|
log.blank()
|
|
446
318
|
log.warn('Setup completed with some manual steps required.')
|
|
447
|
-
|
|
319
|
+
|
|
320
|
+
if (manualConfigRequiredFor === 'Nuxt') {
|
|
321
|
+
printNuxtManualInstructions()
|
|
322
|
+
} else if (manualConfigRequiredFor === 'Next.js') {
|
|
323
|
+
printNextJsManualInstructions()
|
|
324
|
+
} else {
|
|
325
|
+
log.hint('Please check the logs above and complete the manual steps.')
|
|
326
|
+
}
|
|
448
327
|
log.blank()
|
|
449
328
|
} else {
|
|
450
329
|
log.ready('Ready! Hold Alt + Click any element to inspect.')
|
package/src/commands/teardown.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ============================================================
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { log } from '../utils/logger.js'
|
|
6
|
-
import { exists, readJSON,
|
|
6
|
+
import { exists, readJSON, removeDir } from '../utils/fs.js'
|
|
7
7
|
import { shell } from '../utils/exec.js'
|
|
8
8
|
import { detectPackageManager, getUninstallCommand } from '../detect/package-manager.js'
|
|
9
9
|
import { cleanGitignore } from '../inject/gitignore.js'
|
|
@@ -43,7 +43,7 @@ export async function teardown(): Promise<void> {
|
|
|
43
43
|
log.success('Cleaned .gitignore entries')
|
|
44
44
|
|
|
45
45
|
// Warn about config file
|
|
46
|
-
log.warn('Cannot restore build config
|
|
46
|
+
log.warn('Cannot restore build config auto-magically')
|
|
47
47
|
log.hint('Please manually remove the inspecto() plugin from your build config')
|
|
48
48
|
|
|
49
49
|
log.blank()
|
|
@@ -57,27 +57,17 @@ export async function teardown(): Promise<void> {
|
|
|
57
57
|
for (const mutation of lock.mutations) {
|
|
58
58
|
switch (mutation.type) {
|
|
59
59
|
case 'file_modified': {
|
|
60
|
-
if (mutation.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await copyFile(backupPath, targetPath)
|
|
72
|
-
await removeFile(backupPath)
|
|
73
|
-
log.success(`Restored ${mutation.path} from backup`)
|
|
74
|
-
} else {
|
|
75
|
-
log.warn(`Backup not found: ${mutation.backup}`)
|
|
76
|
-
log.hint(`Please manually remove inspecto from ${mutation.path}`)
|
|
77
|
-
}
|
|
78
|
-
} else if (mutation.path && mutation.path !== '.gitignore') {
|
|
79
|
-
log.warn(`Cannot auto-restore ${mutation.path} (no backup recorded)`)
|
|
80
|
-
log.hint(`Please manually remove the inspecto() plugin from ${mutation.path}`)
|
|
60
|
+
if (!mutation.path) break
|
|
61
|
+
|
|
62
|
+
if (mutation.path === '.gitignore') {
|
|
63
|
+
// gitignore is cleaned up at the end of teardown, so we can just log here
|
|
64
|
+
log.success('Cleaned .gitignore entries')
|
|
65
|
+
} else if (mutation.path) {
|
|
66
|
+
// We no longer create .bak files in the new AST approach.
|
|
67
|
+
// In a future update, we can use magicast to auto-remove the plugin from AST.
|
|
68
|
+
// For now, we print a manual warning since we did not backup the file.
|
|
69
|
+
log.warn(`Cannot auto-restore ${mutation.path}`)
|
|
70
|
+
log.hint(`Please manually remove the inspecto plugin from ${mutation.path}`)
|
|
81
71
|
}
|
|
82
72
|
break
|
|
83
73
|
}
|