@inspecto-dev/cli 0.2.0-alpha.0 → 0.2.0-alpha.2
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 +5 -5
- package/CHANGELOG.md +22 -0
- package/README.md +6 -6
- package/dist/bin.js +50 -68
- package/dist/{chunk-4RR7PTRN.js → chunk-V57BJXGZ.js} +615 -435
- 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 +41 -28
- package/src/commands/init.ts +106 -226
- package/src/commands/teardown.ts +13 -23
- package/src/detect/build-tool.ts +89 -21
- 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,75 @@ 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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
59
|
+
|
|
60
|
+
const hasUnsupportedFramework = frameworkResult.unsupported.length > 0
|
|
61
|
+
const hasNoFramework =
|
|
62
|
+
frameworkResult.supported.length === 0 && frameworkResult.unsupported.length === 0
|
|
63
|
+
|
|
64
|
+
if (hasUnsupportedFramework || hasNoFramework) {
|
|
65
|
+
if (hasUnsupportedFramework) {
|
|
66
|
+
const names = frameworkResult.unsupported.map(f => f.name).join(', ')
|
|
67
|
+
log.warn(`Detected ${names} — not supported in v1 (React / Vue only)`)
|
|
68
|
+
} else {
|
|
69
|
+
log.warn('No frontend framework detected')
|
|
70
|
+
log.hint('Inspecto current version supports React and Vue projects')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!options.force) {
|
|
74
|
+
const shouldContinue = await promptUnsupportedFrameworkContinue()
|
|
75
|
+
if (!shouldContinue) {
|
|
76
|
+
log.warn('Initialization aborted.')
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
log.warn('Continuing anyway (--force)')
|
|
81
|
+
}
|
|
193
82
|
}
|
|
194
83
|
|
|
195
|
-
// Build tool detection
|
|
196
|
-
|
|
84
|
+
// Build tool detection
|
|
85
|
+
let manualConfigRequiredFor = ''
|
|
197
86
|
if (buildResult.supported.length > 0) {
|
|
198
87
|
buildResult.supported.forEach(bt => log.success(`Detected: ${bt.label}`))
|
|
199
88
|
}
|
|
200
89
|
if (buildResult.unsupported.length > 0) {
|
|
201
90
|
const names = buildResult.unsupported.join(', ')
|
|
202
|
-
|
|
203
|
-
log.
|
|
204
|
-
log.hint('
|
|
91
|
+
manualConfigRequiredFor = buildResult.unsupported[0] || ''
|
|
92
|
+
log.warn(`Detected ${names} — automatic plugin injection is not supported in current version`)
|
|
93
|
+
log.hint('You can still manually configure it by modifying your configuration file')
|
|
205
94
|
}
|
|
206
95
|
if (buildResult.supported.length === 0 && buildResult.unsupported.length === 0) {
|
|
207
96
|
log.warn('No recognized build tool detected')
|
|
208
|
-
log.hint('
|
|
97
|
+
log.hint('current version supports: Vite, Webpack, Rspack, esbuild, Rollup')
|
|
209
98
|
log.hint('Dependency will be installed but plugin injection will be skipped')
|
|
99
|
+
log.hint(
|
|
100
|
+
'Please refer to the manual setup guide: https://inspecto.dev/docs/getting-started/manual-setup',
|
|
101
|
+
)
|
|
210
102
|
}
|
|
211
103
|
|
|
212
104
|
// IDE detection
|
|
213
|
-
const ideProbe = await detectIDE(root)
|
|
214
105
|
let selectedIDE: { ide: string; supported: boolean } | null = null
|
|
215
106
|
|
|
216
107
|
if (ideProbe.detected.length === 0) {
|
|
217
108
|
log.error('No IDE detected in current project')
|
|
218
109
|
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
110
|
} else if (ideProbe.detected.length === 1) {
|
|
222
111
|
selectedIDE = ideProbe.detected[0]!
|
|
223
112
|
} else {
|
|
224
|
-
// Has multiple
|
|
225
113
|
selectedIDE = await promptIDEChoice(ideProbe.detected)
|
|
226
114
|
}
|
|
227
115
|
|
|
@@ -238,24 +126,21 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
238
126
|
}
|
|
239
127
|
|
|
240
128
|
// AI Tool detection
|
|
241
|
-
|
|
242
|
-
let selectedAITool: AIToolDetection | null = null
|
|
129
|
+
let selectedProvider: ProviderDetection | null = null
|
|
243
130
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (aiToolProbe.detected.length === 0) {
|
|
131
|
+
if (!options.provider) {
|
|
132
|
+
if (providerProbe.detected.length === 0) {
|
|
247
133
|
log.warn('No supported AI tools detected')
|
|
248
134
|
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: ${
|
|
135
|
+
} else if (providerProbe.detected.length === 1) {
|
|
136
|
+
selectedProvider = providerProbe.detected[0]!
|
|
137
|
+
if (selectedProvider.supported) {
|
|
138
|
+
log.success(`Detected AI tool: ${selectedProvider.label}`)
|
|
253
139
|
}
|
|
254
140
|
} else {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
log.success(`Selected AI tool: ${selectedAITool.label}`)
|
|
141
|
+
selectedProvider = await promptProviderChoice(providerProbe.detected)
|
|
142
|
+
if (selectedProvider) {
|
|
143
|
+
log.success(`Selected provider: ${selectedProvider.label}`)
|
|
259
144
|
}
|
|
260
145
|
}
|
|
261
146
|
}
|
|
@@ -265,7 +150,7 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
265
150
|
if (options.skipInstall) {
|
|
266
151
|
log.warn('Skipping dependency installation (--skip-install)')
|
|
267
152
|
} else {
|
|
268
|
-
const installCmd = getInstallCommand(pm, '@inspecto-dev/plugin')
|
|
153
|
+
const installCmd = getInstallCommand(pm, '@inspecto-dev/plugin @inspecto-dev/core')
|
|
269
154
|
if (options.dryRun) {
|
|
270
155
|
log.dryRun(`Would run: ${installCmd}`)
|
|
271
156
|
} else {
|
|
@@ -274,18 +159,13 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
274
159
|
if (result.stderr && result.stderr.toLowerCase().includes('error')) {
|
|
275
160
|
throw new Error(result.stderr)
|
|
276
161
|
}
|
|
277
|
-
log.success('Installed @inspecto-dev/plugin as
|
|
278
|
-
mutations.push({
|
|
279
|
-
|
|
280
|
-
name: '@inspecto-dev/plugin',
|
|
281
|
-
dev: true,
|
|
282
|
-
})
|
|
162
|
+
log.success('Installed @inspecto-dev/plugin and @inspecto-dev/core as devDependencies')
|
|
163
|
+
mutations.push({ type: 'dependency_added', name: '@inspecto-dev/plugin', dev: true })
|
|
164
|
+
mutations.push({ type: 'dependency_added', name: '@inspecto-dev/core', dev: true })
|
|
283
165
|
} catch (err: any) {
|
|
284
166
|
installFailed = true
|
|
285
167
|
log.error(`Failed to install dependency: ${err?.message || 'Unknown error'}`)
|
|
286
168
|
log.hint(`Run manually: ${installCmd}`)
|
|
287
|
-
// We do not return here to allow the rest of the setup to continue,
|
|
288
|
-
// but we will show a warning at the end instead of the success message.
|
|
289
169
|
}
|
|
290
170
|
}
|
|
291
171
|
}
|
|
@@ -314,68 +194,58 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
314
194
|
|
|
315
195
|
// ---- Step 5: Generate default settings ----
|
|
316
196
|
const settingsDir = path.join(root, '.inspecto')
|
|
317
|
-
const
|
|
318
|
-
const
|
|
197
|
+
const settingsFileName = options.shared ? 'settings.json' : 'settings.local.json'
|
|
198
|
+
const promptsFileName = options.shared ? 'prompts.json' : 'prompts.local.json'
|
|
199
|
+
|
|
200
|
+
const settingsPath = path.join(settingsDir, settingsFileName)
|
|
201
|
+
const promptsPath = path.join(settingsDir, promptsFileName)
|
|
319
202
|
|
|
320
203
|
if (await exists(settingsPath)) {
|
|
321
|
-
// Attempt to read the existing settings file to see if it's valid JSON
|
|
322
204
|
const existingSettings = await readJSON(settingsPath)
|
|
323
205
|
if (existingSettings === null) {
|
|
324
|
-
log.warn(
|
|
206
|
+
log.warn(`.inspecto/${settingsFileName} exists but contains invalid JSON`)
|
|
325
207
|
log.hint('Please fix the syntax errors manually, or delete it and re-run init')
|
|
326
208
|
} else {
|
|
327
|
-
log.success(
|
|
209
|
+
log.success(`.inspecto/${settingsFileName} already exists (skipped)`)
|
|
328
210
|
}
|
|
329
211
|
} else {
|
|
330
|
-
// Only omit properties that can be auto-inferred
|
|
331
|
-
// The schema allows empty objects or just specifying the IDE/prefer
|
|
332
212
|
const defaultSettings: Record<string, unknown> = {}
|
|
333
213
|
|
|
334
214
|
if (selectedIDE && selectedIDE.supported) {
|
|
335
|
-
defaultSettings.ide =
|
|
215
|
+
defaultSettings.ide =
|
|
216
|
+
selectedIDE.ide.toLowerCase() === 'vscode' ? 'vscode' : selectedIDE.ide.toLowerCase()
|
|
336
217
|
}
|
|
337
218
|
|
|
338
|
-
if (options.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
defaultSettings.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
defaultSettings.providers = {
|
|
347
|
-
[selectedAITool.id]: {
|
|
348
|
-
type: selectedAITool.preferredMode,
|
|
349
|
-
},
|
|
350
|
-
}
|
|
351
|
-
}
|
|
219
|
+
if (options.provider) {
|
|
220
|
+
const tool = options.provider
|
|
221
|
+
const mode = tool === 'coco' ? 'cli' : 'extension'
|
|
222
|
+
defaultSettings['provider.default'] = `${tool}.${mode}`
|
|
223
|
+
} else if (selectedProvider) {
|
|
224
|
+
const toolId = selectedProvider.id as string
|
|
225
|
+
const mode = selectedProvider.preferredMode === 'cli' ? 'cli' : 'extension'
|
|
226
|
+
defaultSettings['provider.default'] = `${toolId}.${mode}`
|
|
352
227
|
}
|
|
353
228
|
|
|
354
229
|
if (options.dryRun) {
|
|
355
|
-
log.dryRun(
|
|
230
|
+
log.dryRun(`Would create .inspecto/${settingsFileName}`)
|
|
356
231
|
} else {
|
|
357
232
|
await writeJSON(settingsPath, defaultSettings)
|
|
358
|
-
log.success(
|
|
359
|
-
mutations.push({ type: 'file_created', path:
|
|
233
|
+
log.success(`Created .inspecto/${settingsFileName}`)
|
|
234
|
+
mutations.push({ type: 'file_created', path: `.inspecto/${settingsFileName}` })
|
|
360
235
|
}
|
|
361
236
|
}
|
|
362
237
|
|
|
363
|
-
// Generate prompts.json to disable low-frequency intents by default
|
|
364
238
|
if (await exists(promptsPath)) {
|
|
365
|
-
log.success(
|
|
239
|
+
log.success(`.inspecto/${promptsFileName} already exists (skipped)`)
|
|
366
240
|
} else {
|
|
367
|
-
const defaultPrompts = [
|
|
368
|
-
{ id: 'code-review', enabled: false },
|
|
369
|
-
{ id: 'generate-test', enabled: false },
|
|
370
|
-
{ id: 'performance', enabled: false },
|
|
371
|
-
]
|
|
241
|
+
const defaultPrompts: unknown[] = []
|
|
372
242
|
|
|
373
243
|
if (options.dryRun) {
|
|
374
|
-
log.dryRun(
|
|
244
|
+
log.dryRun(`Would create .inspecto/${promptsFileName}`)
|
|
375
245
|
} else {
|
|
376
246
|
await writeJSON(promptsPath, defaultPrompts)
|
|
377
|
-
log.success(
|
|
378
|
-
mutations.push({ type: 'file_created', path:
|
|
247
|
+
log.success(`Created .inspecto/${promptsFileName}`)
|
|
248
|
+
mutations.push({ type: 'file_created', path: `.inspecto/${promptsFileName}` })
|
|
379
249
|
}
|
|
380
250
|
}
|
|
381
251
|
|
|
@@ -401,19 +271,17 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
401
271
|
await writeJSON(path.join(settingsDir, 'install.lock'), lock)
|
|
402
272
|
}
|
|
403
273
|
|
|
404
|
-
// ---- Step 8: Install
|
|
405
|
-
// Only attempt if IDE is VS Code or not detected (might be VS Code)
|
|
274
|
+
// ---- Step 8: Install IDE extension ----
|
|
406
275
|
const shouldInstallExt =
|
|
407
276
|
!options.noExtension && (!selectedIDE || (selectedIDE && selectedIDE.supported))
|
|
408
|
-
|
|
409
277
|
let manualExtensionInstallNeeded = false
|
|
410
278
|
|
|
411
279
|
if (options.noExtension) {
|
|
412
|
-
log.warn('Skipping
|
|
280
|
+
log.warn('Skipping IDE extension (--no-extension)')
|
|
413
281
|
} else if (!shouldInstallExt) {
|
|
414
|
-
// Unsupported IDE detected — skip extension
|
|
282
|
+
// Unsupported IDE detected — skip extension
|
|
415
283
|
} else {
|
|
416
|
-
const extMutation = await installExtension(options.dryRun)
|
|
284
|
+
const extMutation = await installExtension(options.dryRun, selectedIDE?.ide)
|
|
417
285
|
if (extMutation && !options.dryRun) {
|
|
418
286
|
mutations.push(extMutation)
|
|
419
287
|
|
|
@@ -436,10 +304,22 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
436
304
|
if (options.dryRun) {
|
|
437
305
|
log.blank()
|
|
438
306
|
log.warn('Dry run complete. No files were modified.')
|
|
439
|
-
} else if (
|
|
307
|
+
} else if (
|
|
308
|
+
installFailed ||
|
|
309
|
+
injectionFailed ||
|
|
310
|
+
manualExtensionInstallNeeded ||
|
|
311
|
+
manualConfigRequiredFor
|
|
312
|
+
) {
|
|
440
313
|
log.blank()
|
|
441
314
|
log.warn('Setup completed with some manual steps required.')
|
|
442
|
-
|
|
315
|
+
|
|
316
|
+
if (manualConfigRequiredFor === 'Nuxt') {
|
|
317
|
+
printNuxtManualInstructions()
|
|
318
|
+
} else if (manualConfigRequiredFor === 'Next.js') {
|
|
319
|
+
printNextJsManualInstructions()
|
|
320
|
+
} else {
|
|
321
|
+
log.hint('Please check the logs above and complete the manual steps.')
|
|
322
|
+
}
|
|
443
323
|
log.blank()
|
|
444
324
|
} else {
|
|
445
325
|
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
|
}
|
package/src/detect/build-tool.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { BuildTool, BuildToolDetection } from '../types.js'
|
|
|
11
11
|
interface PackageJSON {
|
|
12
12
|
dependencies?: Record<string, string>
|
|
13
13
|
devDependencies?: Record<string, string>
|
|
14
|
+
scripts?: Record<string, string>
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/** Supported build tools in v1 */
|
|
@@ -37,7 +38,7 @@ const SUPPORTED_PATTERNS: { tool: BuildTool; files: string[]; label: string }[]
|
|
|
37
38
|
},
|
|
38
39
|
{
|
|
39
40
|
tool: 'esbuild',
|
|
40
|
-
files: ['esbuild.config.js', 'esbuild.config.ts', 'esbuild.config.mjs'],
|
|
41
|
+
files: ['esbuild.config.js', 'esbuild.config.ts', 'esbuild.config.mjs', 'build.js', 'build.ts'],
|
|
41
42
|
label: 'esbuild',
|
|
42
43
|
},
|
|
43
44
|
{
|
|
@@ -73,39 +74,106 @@ export async function detectBuildTools(root: string): Promise<BuildToolResult> {
|
|
|
73
74
|
const pkg = await readJSON<PackageJSON>(path.join(root, 'package.json'))
|
|
74
75
|
const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies }
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
const supportedChecks = SUPPORTED_PATTERNS.map(async pattern => {
|
|
78
|
+
// 1. Check if the package.json has a dependency for this tool
|
|
79
|
+
const hasDep =
|
|
80
|
+
pattern.tool === 'rspack'
|
|
81
|
+
? !!(allDeps['@rspack/cli'] || allDeps['@rspack/core'])
|
|
82
|
+
: pattern.tool === 'webpack'
|
|
83
|
+
? !!(allDeps['webpack'] || allDeps['webpack-cli'])
|
|
84
|
+
: pattern.tool === 'rsbuild'
|
|
85
|
+
? !!allDeps['@rsbuild/core']
|
|
86
|
+
: !!allDeps[pattern.tool]
|
|
87
|
+
|
|
88
|
+
// 2. Look for config files
|
|
89
|
+
let detectedFile = ''
|
|
90
|
+
|
|
91
|
+
// For esbuild, dependency is strictly required
|
|
92
|
+
if (pattern.tool === 'esbuild' && !hasDep) {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
for (const file of pattern.files) {
|
|
78
97
|
if (await exists(path.join(root, file))) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
detectedFile = file
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 3. For esbuild and rollup, if they are in dependencies but no standard config is found,
|
|
104
|
+
// we still consider them detected (as they are often used with custom scripts)
|
|
105
|
+
if (hasDep && !detectedFile && (pattern.tool === 'esbuild' || pattern.tool === 'rollup')) {
|
|
106
|
+
// Look at npm scripts to guess the build file
|
|
107
|
+
const scripts = pkg?.scripts || {}
|
|
108
|
+
for (const [_, cmd] of Object.entries(scripts)) {
|
|
109
|
+
if (cmd.includes('node ')) {
|
|
110
|
+
const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/)
|
|
111
|
+
if (match && match[1]) {
|
|
112
|
+
if (await exists(path.join(root, match[1]))) {
|
|
113
|
+
detectedFile = match[1]
|
|
114
|
+
break
|
|
115
|
+
}
|
|
87
116
|
}
|
|
117
|
+
} else if (cmd.includes(`${pattern.tool} `)) {
|
|
118
|
+
detectedFile = 'package.json (scripts)'
|
|
119
|
+
break
|
|
88
120
|
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (detectedFile) {
|
|
125
|
+
let isLegacyRspack = false
|
|
126
|
+
let isLegacyWebpack = false
|
|
89
127
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
if (pattern.tool === 'rspack') {
|
|
129
|
+
const version = allDeps['@rspack/cli'] || allDeps['@rspack/core']
|
|
130
|
+
if (
|
|
131
|
+
version &&
|
|
132
|
+
(version.includes('0.3.') || version.includes('0.2.') || version.includes('0.1.'))
|
|
133
|
+
) {
|
|
134
|
+
isLegacyRspack = true
|
|
135
|
+
}
|
|
136
|
+
} else if (pattern.tool === 'webpack') {
|
|
137
|
+
const version = allDeps['webpack'] || allDeps['webpack-cli']
|
|
138
|
+
if ((version && version.includes('^4')) || version?.startsWith('4.')) {
|
|
139
|
+
isLegacyWebpack = true
|
|
140
|
+
}
|
|
97
141
|
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
tool: pattern.tool,
|
|
145
|
+
configPath: detectedFile,
|
|
146
|
+
label: `${pattern.label} (${detectedFile})${isLegacyRspack ? ' [Legacy]' : ''}${isLegacyWebpack ? ' [Webpack 4]' : ''}`,
|
|
147
|
+
isLegacyRspack,
|
|
148
|
+
isLegacyWebpack,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return null
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const supportedResults = await Promise.all(supportedChecks)
|
|
156
|
+
for (const result of supportedResults) {
|
|
157
|
+
if (result) {
|
|
158
|
+
supported.push(result)
|
|
98
159
|
}
|
|
99
160
|
}
|
|
100
161
|
|
|
101
|
-
|
|
102
|
-
if (!(meta.dep in allDeps))
|
|
162
|
+
const unsupportedChecks = UNSUPPORTED_META.map(async meta => {
|
|
163
|
+
if (!(meta.dep in allDeps)) return null
|
|
103
164
|
for (const file of meta.files) {
|
|
104
165
|
if (await exists(path.join(root, file))) {
|
|
105
|
-
|
|
106
|
-
break
|
|
166
|
+
return meta.name
|
|
107
167
|
}
|
|
108
168
|
}
|
|
169
|
+
return null
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const unsupportedResults = await Promise.all(unsupportedChecks)
|
|
173
|
+
for (const result of unsupportedResults) {
|
|
174
|
+
if (result) {
|
|
175
|
+
unsupported.push(result)
|
|
176
|
+
}
|
|
109
177
|
}
|
|
110
178
|
|
|
111
179
|
return { supported, unsupported }
|