@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
|
@@ -1,181 +1,53 @@
|
|
|
1
1
|
// ============================================================
|
|
2
2
|
// src/inject/ast-injector.ts — Safe plugin injection (v1)
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// Meta-framework special strategies (Next.js, Nuxt) deferred.
|
|
4
|
+
// Implements the Strategy Pattern to delegate AST manipulation
|
|
5
|
+
// and manual instruction generation to specific build tool strategies.
|
|
7
6
|
//
|
|
8
7
|
// Safety protocol:
|
|
9
|
-
// 1.
|
|
8
|
+
// 1. Parse config file safely
|
|
10
9
|
// 2. Idempotency check (skip if already injected)
|
|
11
|
-
// 3.
|
|
12
|
-
// 4.
|
|
13
|
-
// 5.
|
|
10
|
+
// 3. Find matching strategy
|
|
11
|
+
// 4. Delegate injection to strategy
|
|
12
|
+
// 5. Write modified configuration back to file
|
|
13
|
+
// 6. Fallback to manual instructions if automatic modification fails
|
|
14
14
|
// ============================================================
|
|
15
15
|
|
|
16
16
|
import path from 'node:path'
|
|
17
|
-
import {
|
|
17
|
+
import { loadFile, writeFile as writeAstFile } from 'magicast'
|
|
18
|
+
import { exists, readFile } from '../utils/fs.js'
|
|
18
19
|
import { log } from '../utils/logger.js'
|
|
19
|
-
import type {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const IMPORT_MAP: Record<BuildTool, string> = {
|
|
24
|
-
vite: `import { vitePlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
25
|
-
webpack: `import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
26
|
-
rspack: `import { rspackPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
27
|
-
rsbuild: `import { rspackPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
28
|
-
esbuild: `import { esbuildPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
29
|
-
rollup: `import { rollupPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getImportStatement(tool: BuildTool, isLegacyRspack?: boolean): string {
|
|
33
|
-
if (tool === 'rspack' && isLegacyRspack) {
|
|
34
|
-
return `import { rspackPlugin as inspecto } from '@inspecto-dev/plugin/legacy/rspack'`
|
|
35
|
-
}
|
|
36
|
-
return IMPORT_MAP[tool]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getPluginExpression(isLegacyRspack?: boolean): string {
|
|
40
|
-
if (isLegacyRspack) {
|
|
41
|
-
return `process.env.NODE_ENV !== 'production' && inspecto({
|
|
42
|
-
pathType: 'absolute',
|
|
43
|
-
escapeTags: ['Transition', 'AnimatePresence'],
|
|
44
|
-
}) as any`
|
|
45
|
-
}
|
|
46
|
-
return `process.env.NODE_ENV !== 'production' && inspecto()`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ---- Manual fallback instructions ----
|
|
20
|
+
import type { BuildToolDetection, Mutation } from '../types.js'
|
|
21
|
+
import { STRATEGIES } from './strategies/index.js'
|
|
22
|
+
import type { InjectStrategy } from './strategies/types.js'
|
|
50
23
|
|
|
51
24
|
function printManualInstructions(
|
|
52
|
-
|
|
53
|
-
|
|
25
|
+
strategy: InjectStrategy | undefined,
|
|
26
|
+
detection: BuildToolDetection,
|
|
54
27
|
reason: string,
|
|
55
|
-
isLegacyRspack?: boolean,
|
|
56
28
|
) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
log.warn(`Could not safely auto-inject into ${configPath}`)
|
|
29
|
+
log.warn(`Could not automatically configure ${detection.configPath}`)
|
|
60
30
|
log.hint(`(reason: ${reason})`)
|
|
61
31
|
log.blank()
|
|
62
32
|
log.hint('Please add the following manually:')
|
|
63
33
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'',
|
|
68
|
-
'// Add to tools.rspack:',
|
|
69
|
-
`tools: {`,
|
|
70
|
-
` rspack: {`,
|
|
71
|
-
` plugins: [`,
|
|
72
|
-
` ${getPluginExpression(isLegacyRspack)},`,
|
|
73
|
-
` ]`,
|
|
74
|
-
` }`,
|
|
75
|
-
`}`,
|
|
76
|
-
])
|
|
34
|
+
if (strategy) {
|
|
35
|
+
const instructions = strategy.getManualInstructions(detection, reason)
|
|
36
|
+
log.codeBlock(instructions)
|
|
77
37
|
} else {
|
|
78
|
-
log.
|
|
79
|
-
getImportStatement(tool, isLegacyRspack),
|
|
80
|
-
'',
|
|
81
|
-
'// Add to your plugins array:',
|
|
82
|
-
`plugins: [`,
|
|
83
|
-
` ${getPluginExpression(isLegacyRspack)},`,
|
|
84
|
-
` ...otherPlugins`,
|
|
85
|
-
`].filter(Boolean)`,
|
|
86
|
-
])
|
|
38
|
+
log.error(`Unsupported build tool: ${detection.tool}`)
|
|
87
39
|
}
|
|
88
40
|
}
|
|
89
41
|
|
|
90
|
-
// ---- Injection helpers ----
|
|
91
|
-
|
|
92
42
|
/** Check if inspecto is already injected (idempotency). */
|
|
93
43
|
function isAlreadyInjected(content: string): boolean {
|
|
44
|
+
// Use regex to avoid false positives in comments or variables
|
|
94
45
|
return (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
content
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
/** Inject import statement after the last existing import. */
|
|
102
|
-
function injectImport(content: string, importStmt: string): string {
|
|
103
|
-
const importRegex = /^import\s.+$/gm
|
|
104
|
-
let lastImportEnd = 0
|
|
105
|
-
let match: RegExpExecArray | null
|
|
106
|
-
|
|
107
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
108
|
-
const lineEnd = content.indexOf('\n', match.index)
|
|
109
|
-
if (lineEnd > lastImportEnd) {
|
|
110
|
-
lastImportEnd = lineEnd
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Also handle CJS require patterns
|
|
115
|
-
if (lastImportEnd === 0) {
|
|
116
|
-
const requireRegex = /^(?:const|let|var)\s.+=\s*require\(.+\).*$/gm
|
|
117
|
-
while ((match = requireRegex.exec(content)) !== null) {
|
|
118
|
-
const lineEnd = content.indexOf('\n', match.index)
|
|
119
|
-
if (lineEnd > lastImportEnd) {
|
|
120
|
-
lastImportEnd = lineEnd
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (lastImportEnd > 0) {
|
|
126
|
-
return content.slice(0, lastImportEnd) + '\n' + importStmt + content.slice(lastImportEnd)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// No imports found, add at the beginning
|
|
130
|
-
return importStmt + '\n\n' + content
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Core injection strategy.
|
|
135
|
-
* For most tools: find `plugins: [` and insert after `[`.
|
|
136
|
-
* For rsbuild: find `tools: { rspack: { plugins: [` or try to add it.
|
|
137
|
-
*/
|
|
138
|
-
function injectIntoPluginsArray(content: string, detection: BuildToolDetection): string | null {
|
|
139
|
-
const tool = detection.tool
|
|
140
|
-
if (tool === 'rsbuild') {
|
|
141
|
-
// rsbuild needs to inject into tools.rspack.plugins or tools.rspack(config, { appendPlugins })
|
|
142
|
-
// Due to the complexity of rsbuild config, we'll try a simple regex for `tools: { rspack: { plugins: [`
|
|
143
|
-
// If that fails, we fallback to manual instructions which are explicitly for rsbuild.
|
|
144
|
-
|
|
145
|
-
// Check if tools.rspack exists
|
|
146
|
-
if (!content.includes('tools:') && !content.includes('rspack:')) {
|
|
147
|
-
// Very basic config, we can try to inject before the closing brace of defineConfig
|
|
148
|
-
const exportRegex = /(export default defineConfig\(\{)/
|
|
149
|
-
const match = exportRegex.exec(content)
|
|
150
|
-
if (match) {
|
|
151
|
-
const insertPos = match.index + match[0].length
|
|
152
|
-
const pluginExpr = `\n tools: {\n rspack: {\n plugins: [\n ${getPluginExpression(detection.isLegacyRspack)}\n ]\n }\n },`
|
|
153
|
-
return content.slice(0, insertPos) + pluginExpr + content.slice(insertPos)
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Otherwise it's too complex for simple regex, force manual degradation
|
|
158
|
-
return null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const pluginsRegex = /(plugins\s*:\s*\[)/
|
|
162
|
-
const match = pluginsRegex.exec(content)
|
|
163
|
-
if (!match) return null
|
|
164
|
-
|
|
165
|
-
const insertPos = match.index + match[0].length
|
|
166
|
-
const pluginExpr = `\n ${getPluginExpression(detection.isLegacyRspack)},`
|
|
167
|
-
|
|
168
|
-
return content.slice(0, insertPos) + pluginExpr + content.slice(insertPos)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Basic bracket-balance validation. */
|
|
172
|
-
function validateBrackets(content: string): boolean {
|
|
173
|
-
const openBraces = (content.match(/\{/g) || []).length
|
|
174
|
-
const closeBraces = (content.match(/\}/g) || []).length
|
|
175
|
-
const openBrackets = (content.match(/\[/g) || []).length
|
|
176
|
-
const closeBrackets = (content.match(/\]/g) || []).length
|
|
177
|
-
|
|
178
|
-
return Math.abs(openBraces - closeBraces) <= 1 && Math.abs(openBrackets - closeBrackets) <= 1
|
|
46
|
+
/import\s+.*@inspecto-dev\/plugin/.test(content) ||
|
|
47
|
+
/require\(['"]@inspecto-dev\/plugin['"]\)/.test(content) ||
|
|
48
|
+
/import\s+.*ai-dev-inspector/.test(content) || // Legacy support
|
|
49
|
+
/require\(['"]ai-dev-inspector['"]\)/.test(content)
|
|
50
|
+
)
|
|
179
51
|
}
|
|
180
52
|
|
|
181
53
|
// ---- Main injection orchestrator ----
|
|
@@ -192,109 +64,76 @@ export async function injectPlugin(
|
|
|
192
64
|
dryRun: boolean,
|
|
193
65
|
): Promise<InjectionResult> {
|
|
194
66
|
const configPath = path.join(root, detection.configPath)
|
|
195
|
-
const backupPath = configPath + '.bak'
|
|
196
67
|
const mutations: Mutation[] = []
|
|
197
68
|
|
|
198
|
-
|
|
69
|
+
const strategy = STRATEGIES.find(s => s.supports(detection.tool))
|
|
70
|
+
|
|
71
|
+
// Step 1: Read config file to check existence and idempotency
|
|
199
72
|
const content = await readFile(configPath)
|
|
200
73
|
if (!content) {
|
|
201
|
-
printManualInstructions(
|
|
202
|
-
detection.tool,
|
|
203
|
-
detection.configPath,
|
|
204
|
-
'config file not readable',
|
|
205
|
-
detection.isLegacyRspack,
|
|
206
|
-
)
|
|
74
|
+
printManualInstructions(strategy, detection, 'config file not readable')
|
|
207
75
|
return { success: false, mutations, failureReason: 'config file not readable' }
|
|
208
76
|
}
|
|
209
77
|
|
|
210
78
|
// Step 2: Idempotency check
|
|
211
79
|
if (isAlreadyInjected(content)) {
|
|
212
|
-
log.success(`Plugin already
|
|
213
|
-
|
|
214
|
-
// We still want to record the mutation so teardown knows how to clean it up
|
|
215
|
-
// However, if the user manually added it, we shouldn't overwrite their code with a .bak on teardown.
|
|
216
|
-
// To handle this, we register a special 'plugin_injected' mutation instead of 'file_modified',
|
|
217
|
-
// which tells teardown to use AST-removal instead of .bak restoration (if we implement it)
|
|
218
|
-
// For now, we will assume if it's already there and a .bak exists, we track it for restoration.
|
|
219
|
-
if (await exists(backupPath)) {
|
|
220
|
-
mutations.push({
|
|
221
|
-
type: 'file_modified',
|
|
222
|
-
path: detection.configPath,
|
|
223
|
-
backup: detection.configPath + '.bak',
|
|
224
|
-
description: 'Previously injected inspecto() plugin',
|
|
225
|
-
})
|
|
226
|
-
} else {
|
|
227
|
-
// If no backup exists, we just mark it as modified but without backup
|
|
228
|
-
// This tells teardown it was touched but cannot be safely rolled back
|
|
229
|
-
mutations.push({
|
|
230
|
-
type: 'file_modified',
|
|
231
|
-
path: detection.configPath,
|
|
232
|
-
description: 'Previously injected inspecto() plugin (no backup)',
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return { success: true, mutations }
|
|
237
|
-
}
|
|
80
|
+
log.success(`Plugin already configured in ${detection.configPath} (skipped)`)
|
|
238
81
|
|
|
239
|
-
// Step 3: Backup
|
|
240
|
-
if (!dryRun) {
|
|
241
|
-
await copyFile(configPath, backupPath)
|
|
242
82
|
mutations.push({
|
|
243
83
|
type: 'file_modified',
|
|
244
84
|
path: detection.configPath,
|
|
245
|
-
|
|
246
|
-
description: 'Injected inspecto() plugin',
|
|
85
|
+
description: 'Previously configured inspecto() plugin',
|
|
247
86
|
})
|
|
87
|
+
|
|
88
|
+
return { success: true, mutations }
|
|
248
89
|
}
|
|
249
|
-
log.success(`Backed up ${detection.configPath} → ${detection.configPath}.bak`)
|
|
250
90
|
|
|
251
|
-
|
|
252
|
-
const injected = injectIntoPluginsArray(content, detection)
|
|
253
|
-
if (!injected) {
|
|
91
|
+
if (!strategy) {
|
|
254
92
|
printManualInstructions(
|
|
255
|
-
|
|
256
|
-
detection
|
|
257
|
-
|
|
258
|
-
detection.isLegacyRspack,
|
|
93
|
+
strategy,
|
|
94
|
+
detection,
|
|
95
|
+
`No injection strategy found for ${detection.tool}`,
|
|
259
96
|
)
|
|
260
|
-
return {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
97
|
+
return { success: false, mutations, failureReason: 'No strategy found' }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Step 3: Automatic configuration
|
|
101
|
+
if (dryRun) {
|
|
102
|
+
log.dryRun(`Would automatically configure plugin in ${detection.configPath}`)
|
|
103
|
+
return { success: true, mutations: [] }
|
|
265
104
|
}
|
|
266
105
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const modifiedContent = injectImport(injected, importStmt)
|
|
106
|
+
try {
|
|
107
|
+
const mod = await loadFile(configPath)
|
|
270
108
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
109
|
+
// Delegate to strategy
|
|
110
|
+
strategy.inject({
|
|
111
|
+
mod,
|
|
112
|
+
detection,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Step 4: Write modified config back to file
|
|
116
|
+
await writeAstFile(mod, configPath)
|
|
117
|
+
|
|
118
|
+
mutations.push({
|
|
119
|
+
type: 'file_modified',
|
|
120
|
+
path: detection.configPath,
|
|
121
|
+
description: 'Automatically configured inspecto() plugin',
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
log.success(`Configured plugin in ${detection.configPath}`)
|
|
125
|
+
return { success: true, mutations }
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// Graceback degradation
|
|
278
128
|
printManualInstructions(
|
|
279
|
-
|
|
280
|
-
detection
|
|
281
|
-
|
|
282
|
-
detection.isLegacyRspack,
|
|
129
|
+
strategy,
|
|
130
|
+
detection,
|
|
131
|
+
`Automatic configuration unavailable: ${err instanceof Error ? err.message : String(err)}`,
|
|
283
132
|
)
|
|
284
133
|
return {
|
|
285
134
|
success: false,
|
|
286
|
-
mutations
|
|
287
|
-
failureReason: '
|
|
135
|
+
mutations,
|
|
136
|
+
failureReason: 'Automatic configuration unavailable',
|
|
288
137
|
}
|
|
289
138
|
}
|
|
290
|
-
|
|
291
|
-
// Step 7: Write
|
|
292
|
-
if (dryRun) {
|
|
293
|
-
log.dryRun(`Would inject plugin into ${detection.configPath}`)
|
|
294
|
-
} else {
|
|
295
|
-
await writeFile(configPath, modifiedContent)
|
|
296
|
-
log.success(`Injected plugin into ${detection.configPath}`)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return { success: true, mutations }
|
|
300
139
|
}
|
package/src/inject/extension.ts
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
// Level 4: Print manual installation instructions
|
|
10
10
|
// ============================================================
|
|
11
11
|
|
|
12
|
-
import path from 'node:path'
|
|
13
12
|
import { which, run, shell } from '../utils/exec.js'
|
|
14
13
|
import { exists } from '../utils/fs.js'
|
|
15
14
|
import { log } from '../utils/logger.js'
|
|
@@ -67,51 +66,67 @@ async function tryOpenURI(uri: string): Promise<boolean> {
|
|
|
67
66
|
/**
|
|
68
67
|
* Attempt to install the VS Code extension using waterfall degradation.
|
|
69
68
|
*/
|
|
70
|
-
export async function installExtension(dryRun: boolean): Promise<Mutation | null> {
|
|
69
|
+
export async function installExtension(dryRun: boolean, ide?: string): Promise<Mutation | null> {
|
|
71
70
|
if (dryRun) {
|
|
72
71
|
log.dryRun('Would attempt to install VS Code extension')
|
|
73
72
|
return null
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
const isVSCode = !ide || ide === 'vscode'
|
|
76
|
+
|
|
77
|
+
if (isVSCode) {
|
|
78
|
+
// Level 1: Direct `code` command
|
|
79
|
+
if (await which('code')) {
|
|
80
|
+
try {
|
|
81
|
+
await run('code', ['--install-extension', EXTENSION_ID])
|
|
82
|
+
log.success('VS Code extension installed via CLI')
|
|
83
|
+
return { type: 'extension_installed', id: EXTENSION_ID }
|
|
84
|
+
} catch {
|
|
85
|
+
// Fall through to next level
|
|
86
|
+
}
|
|
84
87
|
}
|
|
85
|
-
}
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
// Level 2: Find VS Code binary at known paths
|
|
90
|
+
const codePath = await findVSCodeBinary()
|
|
91
|
+
if (codePath) {
|
|
92
|
+
try {
|
|
93
|
+
await run(codePath, ['--install-extension', EXTENSION_ID])
|
|
94
|
+
log.success('VS Code extension installed via binary path')
|
|
95
|
+
log.info(
|
|
96
|
+
'Tip: Add "code" to your PATH to help Inspecto detect other AI tools in the future',
|
|
97
|
+
)
|
|
98
|
+
return { type: 'extension_installed', id: EXTENSION_ID }
|
|
99
|
+
} catch {
|
|
100
|
+
// Fall through to next level
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Level 3: URI scheme
|
|
105
|
+
const uri = `vscode:extension/${EXTENSION_ID}`
|
|
106
|
+
if (await tryOpenURI(uri)) {
|
|
107
|
+
log.warn('Opened extension page in VS Code')
|
|
108
|
+
log.hint('Please click "Install" in the opened VS Code window to complete setup.')
|
|
109
|
+
return { type: 'extension_installed', id: EXTENSION_ID, manual_action_required: true }
|
|
97
110
|
}
|
|
98
|
-
}
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
log.
|
|
104
|
-
log.hint('
|
|
105
|
-
|
|
112
|
+
// Level 4: Manual fallback
|
|
113
|
+
log.warn('Could not auto-install VS Code extension')
|
|
114
|
+
log.hint('Please install it manually to enable Inspector features:')
|
|
115
|
+
log.hint(' 1. Open VS Code')
|
|
116
|
+
log.hint(' 2. Press Ctrl+Shift+X (or Cmd+Shift+X)')
|
|
117
|
+
log.hint(' 3. Search for "Inspecto"')
|
|
118
|
+
log.hint(` Or visit: https://marketplace.visualstudio.com/items?itemName=${EXTENSION_ID}`)
|
|
119
|
+
return null
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
//
|
|
109
|
-
log.warn(
|
|
122
|
+
// Other IDEs: Prompt to install via VSIX
|
|
123
|
+
log.warn(`Could not auto-install extension for ${ide}`)
|
|
110
124
|
log.hint('Please install it manually to enable Inspector features:')
|
|
111
|
-
log.hint(' 1.
|
|
112
|
-
log.hint(
|
|
113
|
-
log.hint(' 3.
|
|
114
|
-
log.hint(
|
|
125
|
+
log.hint(' 1. Download the latest .vsix file from Inspecto releases')
|
|
126
|
+
log.hint(` 2. Open ${ide}`)
|
|
127
|
+
log.hint(' 3. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)')
|
|
128
|
+
log.hint(' 4. Type and select "Extensions: Install from VSIX..."')
|
|
129
|
+
log.hint(' 5. Select the downloaded .vsix file')
|
|
115
130
|
return null
|
|
116
131
|
}
|
|
117
132
|
|
package/src/inject/gitignore.ts
CHANGED
|
@@ -34,7 +34,7 @@ export async function updateGitignore(
|
|
|
34
34
|
if (!dryRun) {
|
|
35
35
|
await writeFile(gitignorePath, content)
|
|
36
36
|
}
|
|
37
|
-
log.success('Updated .gitignore: .inspecto/
|
|
37
|
+
log.success('Updated .gitignore: .inspecto/ is no longer fully ignored')
|
|
38
38
|
return
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { InjectStrategy, InjectOptions } from './types.js'
|
|
2
|
+
import type { BuildTool, BuildToolDetection } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
export class EsbuildStrategy implements InjectStrategy {
|
|
5
|
+
name = 'esbuild'
|
|
6
|
+
|
|
7
|
+
supports(tool: BuildTool): boolean {
|
|
8
|
+
return tool === 'esbuild'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
inject(options: InjectOptions): void {
|
|
12
|
+
throw new Error('Esbuild requires manual plugin configuration')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
|
|
16
|
+
return [
|
|
17
|
+
`1. Update your esbuild config (${detection.configPath}):`,
|
|
18
|
+
`import { esbuildPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
19
|
+
'',
|
|
20
|
+
'// Add to your plugins array:',
|
|
21
|
+
`plugins: [`,
|
|
22
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
23
|
+
` ...otherPlugins`,
|
|
24
|
+
`].filter(Boolean)`,
|
|
25
|
+
'',
|
|
26
|
+
'2. Initialize the client in your app entry (e.g., main.js / index.js):',
|
|
27
|
+
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
28
|
+
'',
|
|
29
|
+
'// Call this before your app renders',
|
|
30
|
+
`if (process.env.NODE_ENV !== 'production') {`,
|
|
31
|
+
` mountInspector()`,
|
|
32
|
+
`}`,
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ViteStrategy } from './vite.js'
|
|
2
|
+
import { WebpackStrategy } from './webpack.js'
|
|
3
|
+
import { RspackStrategy } from './rspack.js'
|
|
4
|
+
import { RsbuildStrategy } from './rsbuild.js'
|
|
5
|
+
import { EsbuildStrategy } from './esbuild.js'
|
|
6
|
+
import { RollupStrategy } from './rollup.js'
|
|
7
|
+
import type { InjectStrategy } from './types.js'
|
|
8
|
+
|
|
9
|
+
export const STRATEGIES: InjectStrategy[] = [
|
|
10
|
+
new ViteStrategy(),
|
|
11
|
+
new WebpackStrategy(),
|
|
12
|
+
new RspackStrategy(),
|
|
13
|
+
new RsbuildStrategy(),
|
|
14
|
+
new EsbuildStrategy(),
|
|
15
|
+
new RollupStrategy(),
|
|
16
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { InjectStrategy, InjectOptions } from './types.js'
|
|
2
|
+
import type { BuildTool, BuildToolDetection } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
export class RollupStrategy implements InjectStrategy {
|
|
5
|
+
name = 'Rollup'
|
|
6
|
+
|
|
7
|
+
supports(tool: BuildTool): boolean {
|
|
8
|
+
return tool === 'rollup'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
inject(options: InjectOptions): void {
|
|
12
|
+
throw new Error('Rollup requires manual plugin configuration')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
|
|
16
|
+
return [
|
|
17
|
+
`1. Update your rollup config (${detection.configPath}):`,
|
|
18
|
+
`import { rollupPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
19
|
+
'',
|
|
20
|
+
'// Add to your plugins array:',
|
|
21
|
+
`plugins: [`,
|
|
22
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
23
|
+
` ...otherPlugins`,
|
|
24
|
+
`].filter(Boolean)`,
|
|
25
|
+
'',
|
|
26
|
+
'2. Initialize the client in your app entry (e.g., main.js / index.js):',
|
|
27
|
+
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
28
|
+
'',
|
|
29
|
+
'// Call this before your app renders',
|
|
30
|
+
`if (process.env.NODE_ENV !== 'production') {`,
|
|
31
|
+
` mountInspector()`,
|
|
32
|
+
`}`,
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { InjectStrategy, InjectOptions } from './types.js'
|
|
2
|
+
import type { BuildTool, BuildToolDetection } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
export class RsbuildStrategy implements InjectStrategy {
|
|
5
|
+
name = 'Rsbuild'
|
|
6
|
+
|
|
7
|
+
supports(tool: BuildTool): boolean {
|
|
8
|
+
return tool === 'rsbuild'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
inject(options: InjectOptions): void {
|
|
12
|
+
throw new Error('Rsbuild requires manual plugin configuration due to nested structure')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
|
|
16
|
+
return [
|
|
17
|
+
`import { rspackPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
18
|
+
'',
|
|
19
|
+
'// Add to tools.rspack:',
|
|
20
|
+
`tools: {`,
|
|
21
|
+
` rspack: {`,
|
|
22
|
+
` plugins: [`,
|
|
23
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
24
|
+
` ]`,
|
|
25
|
+
` }`,
|
|
26
|
+
`}`,
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { InjectStrategy, InjectOptions } from './types.js'
|
|
2
|
+
import type { BuildTool, BuildToolDetection } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
export class RspackStrategy implements InjectStrategy {
|
|
5
|
+
name = 'Rspack'
|
|
6
|
+
|
|
7
|
+
supports(tool: BuildTool): boolean {
|
|
8
|
+
return tool === 'rspack'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
inject(options: InjectOptions): void {
|
|
12
|
+
throw new Error('Rspack requires manual plugin configuration')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getManualInstructions(detection: BuildToolDetection, reason: string): string[] {
|
|
16
|
+
const importPkg = detection.isLegacyRspack
|
|
17
|
+
? '@inspecto-dev/plugin/legacy/rspack'
|
|
18
|
+
: '@inspecto-dev/plugin'
|
|
19
|
+
|
|
20
|
+
const pluginCall = detection.isLegacyRspack
|
|
21
|
+
? `process.env.NODE_ENV !== 'production' && inspecto({\n pathType: 'absolute',\n escapeTags: ['Transition', 'AnimatePresence'],\n })`
|
|
22
|
+
: `process.env.NODE_ENV !== 'production' && inspecto()`
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
`import { rspackPlugin as inspecto } from '${importPkg}'`,
|
|
26
|
+
'',
|
|
27
|
+
'// Add to your plugins array:',
|
|
28
|
+
`plugins: [`,
|
|
29
|
+
` ${pluginCall},`,
|
|
30
|
+
` ...otherPlugins`,
|
|
31
|
+
`].filter(Boolean)`,
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ProxifiedModule } from 'magicast'
|
|
2
|
+
import type { BuildTool, BuildToolDetection } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
export interface InjectOptions {
|
|
5
|
+
/** The magicast proxified module */
|
|
6
|
+
mod: ProxifiedModule<any>
|
|
7
|
+
/** The build tool detection result */
|
|
8
|
+
detection: BuildToolDetection
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface InjectStrategy {
|
|
12
|
+
/** Name of the strategy for logging */
|
|
13
|
+
name: string
|
|
14
|
+
|
|
15
|
+
/** Check if this strategy can handle the given build tool */
|
|
16
|
+
supports(tool: BuildTool): boolean
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Perform the AST injection.
|
|
20
|
+
* Note: Some tools (like webpack) might throw an error here to trigger manual fallback
|
|
21
|
+
* because AST manipulation for them is too complex/brittle in v1.
|
|
22
|
+
*/
|
|
23
|
+
inject(options: InjectOptions): void
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Perform the AST removal (for teardown).
|
|
27
|
+
* Returns true if successful, false if it couldn't be removed automatically.
|
|
28
|
+
*/
|
|
29
|
+
remove?(options: InjectOptions): boolean
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Return manual fallback instructions if automatic injection fails or is unsupported.
|
|
33
|
+
*/
|
|
34
|
+
getManualInstructions(detection: BuildToolDetection, reason: string): string[]
|
|
35
|
+
}
|