@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
|
@@ -77,15 +77,6 @@ async function writeFile(filePath, content) {
|
|
|
77
77
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
78
78
|
await fs.writeFile(filePath, content, "utf-8");
|
|
79
79
|
}
|
|
80
|
-
async function copyFile(src, dest) {
|
|
81
|
-
await fs.copyFile(src, dest);
|
|
82
|
-
}
|
|
83
|
-
async function removeFile(filePath) {
|
|
84
|
-
try {
|
|
85
|
-
await fs.unlink(filePath);
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
80
|
async function removeDir(dirPath) {
|
|
90
81
|
try {
|
|
91
82
|
await fs.rm(dirPath, { recursive: true, force: true });
|
|
@@ -146,9 +137,15 @@ async function detectPackageManager(root) {
|
|
|
146
137
|
["yarn.lock", "yarn"],
|
|
147
138
|
["package-lock.json", "npm"]
|
|
148
139
|
];
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
140
|
+
const results = await Promise.all(
|
|
141
|
+
checks.map(async ([file, pm]) => {
|
|
142
|
+
const isExist = await exists(path2.join(root, file));
|
|
143
|
+
return { isExist, pm };
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
for (const result of results) {
|
|
147
|
+
if (result.isExist) {
|
|
148
|
+
return result.pm;
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
151
|
return "npm";
|
|
@@ -180,6 +177,31 @@ function getUninstallCommand(pm, pkg) {
|
|
|
180
177
|
|
|
181
178
|
// src/detect/build-tool.ts
|
|
182
179
|
import path3 from "path";
|
|
180
|
+
import { createRequire } from "module";
|
|
181
|
+
function isPackageResolvable(pkgName, root) {
|
|
182
|
+
try {
|
|
183
|
+
const require2 = createRequire(path3.join(root, "package.json"));
|
|
184
|
+
try {
|
|
185
|
+
require2.resolve(`${pkgName}/package.json`, { paths: [root] });
|
|
186
|
+
return true;
|
|
187
|
+
} catch {
|
|
188
|
+
require2.resolve(pkgName, { paths: [root] });
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function getResolvedPackageVersion(pkgName, root) {
|
|
196
|
+
try {
|
|
197
|
+
const require2 = createRequire(path3.join(root, "package.json"));
|
|
198
|
+
const pkgJsonPath = require2.resolve(`${pkgName}/package.json`, { paths: [root] });
|
|
199
|
+
const pkg = await readJSON(pkgJsonPath);
|
|
200
|
+
return pkg?.version || null;
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
183
205
|
var SUPPORTED_PATTERNS = [
|
|
184
206
|
{
|
|
185
207
|
tool: "vite",
|
|
@@ -203,7 +225,7 @@ var SUPPORTED_PATTERNS = [
|
|
|
203
225
|
},
|
|
204
226
|
{
|
|
205
227
|
tool: "esbuild",
|
|
206
|
-
files: ["esbuild.config.js", "esbuild.config.ts", "esbuild.config.mjs"],
|
|
228
|
+
files: ["esbuild.config.js", "esbuild.config.ts", "esbuild.config.mjs", "build.js", "build.ts"],
|
|
207
229
|
label: "esbuild"
|
|
208
230
|
},
|
|
209
231
|
{
|
|
@@ -224,34 +246,110 @@ async function detectBuildTools(root) {
|
|
|
224
246
|
const unsupported = [];
|
|
225
247
|
const pkg = await readJSON(path3.join(root, "package.json"));
|
|
226
248
|
const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
227
|
-
|
|
249
|
+
const supportedChecks = SUPPORTED_PATTERNS.map(async (pattern) => {
|
|
250
|
+
let hasDep;
|
|
251
|
+
let resolvedVersion = null;
|
|
252
|
+
if (pattern.tool === "rspack") {
|
|
253
|
+
const depName = allDeps["@rspack/cli"] ? "@rspack/cli" : "@rspack/core";
|
|
254
|
+
hasDep = !!allDeps["@rspack/cli"] || !!allDeps["@rspack/core"] || isPackageResolvable("@rspack/core", root);
|
|
255
|
+
if (hasDep) {
|
|
256
|
+
resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("@rspack/core", root);
|
|
257
|
+
}
|
|
258
|
+
} else if (pattern.tool === "webpack") {
|
|
259
|
+
const depName = allDeps["webpack"] ? "webpack" : "webpack-cli";
|
|
260
|
+
hasDep = !!allDeps["webpack"] || !!allDeps["webpack-cli"] || isPackageResolvable("webpack", root);
|
|
261
|
+
if (hasDep) {
|
|
262
|
+
resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("webpack", root);
|
|
263
|
+
}
|
|
264
|
+
} else if (pattern.tool === "rsbuild") {
|
|
265
|
+
hasDep = !!allDeps["@rsbuild/core"] || isPackageResolvable("@rsbuild/core", root);
|
|
266
|
+
} else {
|
|
267
|
+
hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, root);
|
|
268
|
+
}
|
|
269
|
+
let detectedFile = "";
|
|
270
|
+
if (pattern.tool === "esbuild" && !hasDep) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
228
273
|
for (const file of pattern.files) {
|
|
229
274
|
if (await exists(path3.join(root, file))) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
275
|
+
detectedFile = file;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (hasDep && !detectedFile && (pattern.tool === "esbuild" || pattern.tool === "rollup" || pattern.tool === "webpack" || pattern.tool === "rspack" || pattern.tool === "rsbuild")) {
|
|
280
|
+
const scripts = pkg?.scripts || {};
|
|
281
|
+
for (const cmd of Object.values(scripts)) {
|
|
282
|
+
if (cmd.includes("node ")) {
|
|
283
|
+
const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/);
|
|
284
|
+
if (match && match[1]) {
|
|
285
|
+
if (await exists(path3.join(root, match[1]))) {
|
|
286
|
+
if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
|
|
287
|
+
detectedFile = match[1];
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} else if (cmd.includes(`${pattern.tool} `)) {
|
|
293
|
+
if (pattern.tool === "webpack" || pattern.tool === "rspack") {
|
|
294
|
+
const configMatch = cmd.match(/--config\s+([^\s]+)/);
|
|
295
|
+
if (configMatch && configMatch[1]) {
|
|
296
|
+
if (await exists(path3.join(root, configMatch[1]))) {
|
|
297
|
+
detectedFile = configMatch[1];
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!detectedFile) {
|
|
303
|
+
detectedFile = "package.json (scripts)";
|
|
304
|
+
break;
|
|
235
305
|
}
|
|
236
306
|
}
|
|
237
|
-
supported.push({
|
|
238
|
-
tool: pattern.tool,
|
|
239
|
-
configPath: file,
|
|
240
|
-
label: `${pattern.label} (${file})${isLegacyRspack ? " [Legacy]" : ""}`,
|
|
241
|
-
isLegacyRspack
|
|
242
|
-
});
|
|
243
|
-
break;
|
|
244
307
|
}
|
|
245
308
|
}
|
|
309
|
+
if (detectedFile) {
|
|
310
|
+
let isLegacyRspack = false;
|
|
311
|
+
let isLegacyWebpack = false;
|
|
312
|
+
if (pattern.tool === "rspack") {
|
|
313
|
+
const version = resolvedVersion;
|
|
314
|
+
if (version && (version.includes("0.3.") || version.includes("0.2.") || version.includes("0.1."))) {
|
|
315
|
+
isLegacyRspack = true;
|
|
316
|
+
}
|
|
317
|
+
} else if (pattern.tool === "webpack") {
|
|
318
|
+
const version = resolvedVersion;
|
|
319
|
+
if (version && version.includes("^4") || version?.startsWith("4.")) {
|
|
320
|
+
isLegacyWebpack = true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
tool: pattern.tool,
|
|
325
|
+
configPath: detectedFile,
|
|
326
|
+
label: `${pattern.label} (${detectedFile})${isLegacyRspack ? " [Legacy]" : ""}${isLegacyWebpack ? " [Webpack 4]" : ""}`,
|
|
327
|
+
isLegacyRspack,
|
|
328
|
+
isLegacyWebpack
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
});
|
|
333
|
+
const supportedResults = await Promise.all(supportedChecks);
|
|
334
|
+
for (const result of supportedResults) {
|
|
335
|
+
if (result) {
|
|
336
|
+
supported.push(result);
|
|
337
|
+
}
|
|
246
338
|
}
|
|
247
|
-
|
|
248
|
-
if (!(meta.dep in allDeps))
|
|
339
|
+
const unsupportedChecks = UNSUPPORTED_META.map(async (meta) => {
|
|
340
|
+
if (!(meta.dep in allDeps)) return null;
|
|
249
341
|
for (const file of meta.files) {
|
|
250
342
|
if (await exists(path3.join(root, file))) {
|
|
251
|
-
|
|
252
|
-
break;
|
|
343
|
+
return meta.name;
|
|
253
344
|
}
|
|
254
345
|
}
|
|
346
|
+
return null;
|
|
347
|
+
});
|
|
348
|
+
const unsupportedResults = await Promise.all(unsupportedChecks);
|
|
349
|
+
for (const result of unsupportedResults) {
|
|
350
|
+
if (result) {
|
|
351
|
+
unsupported.push(result);
|
|
352
|
+
}
|
|
255
353
|
}
|
|
256
354
|
return { supported, unsupported };
|
|
257
355
|
}
|
|
@@ -263,6 +361,18 @@ function resolveInjectionTarget(detections) {
|
|
|
263
361
|
|
|
264
362
|
// src/detect/framework.ts
|
|
265
363
|
import path4 from "path";
|
|
364
|
+
import { createRequire as createRequire2 } from "module";
|
|
365
|
+
var META_FRAMEWORK_MAP = {
|
|
366
|
+
next: "react",
|
|
367
|
+
nuxt: "vue",
|
|
368
|
+
"@remix-run/react": "react",
|
|
369
|
+
"@remix-run/dev": "react",
|
|
370
|
+
"@vue/nuxt": "vue",
|
|
371
|
+
"vite-plugin-vue": "vue",
|
|
372
|
+
"@vitejs/plugin-vue": "vue",
|
|
373
|
+
"@vitejs/plugin-react": "react",
|
|
374
|
+
"@vitejs/plugin-react-swc": "react"
|
|
375
|
+
};
|
|
266
376
|
var SUPPORTED_FRAMEWORKS = [
|
|
267
377
|
{ framework: "react", deps: ["react", "react-dom"] },
|
|
268
378
|
{ framework: "vue", deps: ["vue"] }
|
|
@@ -274,26 +384,53 @@ var UNSUPPORTED_FRAMEWORKS = [
|
|
|
274
384
|
{ name: "Preact", dep: "preact" },
|
|
275
385
|
{ name: "Lit", dep: "lit" }
|
|
276
386
|
];
|
|
387
|
+
function isPackageResolvable2(pkgName, root) {
|
|
388
|
+
try {
|
|
389
|
+
const require2 = createRequire2(path4.join(root, "package.json"));
|
|
390
|
+
try {
|
|
391
|
+
require2.resolve(`${pkgName}/package.json`, { paths: [root] });
|
|
392
|
+
return true;
|
|
393
|
+
} catch {
|
|
394
|
+
require2.resolve(pkgName, { paths: [root] });
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
} catch {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
277
401
|
async function detectFrameworks(root) {
|
|
278
402
|
const pkg = await readJSON(path4.join(root, "package.json"));
|
|
279
|
-
if (!pkg) return { supported: [], unsupported: [] };
|
|
280
403
|
const allDeps = {
|
|
281
|
-
...pkg
|
|
282
|
-
...pkg
|
|
404
|
+
...pkg?.dependencies || {},
|
|
405
|
+
...pkg?.devDependencies || {},
|
|
406
|
+
...pkg?.peerDependencies || {}
|
|
283
407
|
};
|
|
284
|
-
const
|
|
408
|
+
const supportedSet = /* @__PURE__ */ new Set();
|
|
409
|
+
const unsupported = [];
|
|
410
|
+
const isTest = root.includes("/mock/root");
|
|
411
|
+
for (const [metaPkg, framework] of Object.entries(META_FRAMEWORK_MAP)) {
|
|
412
|
+
if (metaPkg in allDeps || !isTest && isPackageResolvable2(metaPkg, root)) {
|
|
413
|
+
supportedSet.add(framework);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
285
416
|
for (const { framework, deps } of SUPPORTED_FRAMEWORKS) {
|
|
286
|
-
if (
|
|
287
|
-
|
|
417
|
+
if (supportedSet.has(framework)) continue;
|
|
418
|
+
for (const dep of deps) {
|
|
419
|
+
if (dep in allDeps || !isTest && isPackageResolvable2(dep, root)) {
|
|
420
|
+
supportedSet.add(framework);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
288
423
|
}
|
|
289
424
|
}
|
|
290
|
-
const unsupported = [];
|
|
291
425
|
for (const fw of UNSUPPORTED_FRAMEWORKS) {
|
|
292
|
-
if (fw.dep in allDeps) {
|
|
426
|
+
if (fw.dep in allDeps || !isTest && isPackageResolvable2(fw.dep, root)) {
|
|
293
427
|
unsupported.push(fw);
|
|
294
428
|
}
|
|
295
429
|
}
|
|
296
|
-
return {
|
|
430
|
+
return {
|
|
431
|
+
supported: Array.from(supportedSet),
|
|
432
|
+
unsupported
|
|
433
|
+
};
|
|
297
434
|
}
|
|
298
435
|
|
|
299
436
|
// src/detect/ide.ts
|
|
@@ -302,39 +439,44 @@ var SUPPORTED_IDE = "vscode";
|
|
|
302
439
|
async function detectIDE(root) {
|
|
303
440
|
const detected = /* @__PURE__ */ new Map();
|
|
304
441
|
if (process.env.CURSOR_TRACE_DIR || process.env.CURSOR_CHANNEL) {
|
|
305
|
-
detected.set("Cursor", { ide: "
|
|
442
|
+
detected.set("Cursor", { ide: "cursor", supported: true });
|
|
306
443
|
}
|
|
307
444
|
if (process.env.TRAE_APP_DIR || process.env.__CFBundleIdentifier === "com.byteocean.trae" || process.env.COCO_IDE_PLUGIN_TYPE === "Trae" || process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes("trae")) {
|
|
308
|
-
detected.set("Trae", { ide: "
|
|
445
|
+
detected.set("Trae", { ide: "trae", supported: true });
|
|
309
446
|
}
|
|
310
447
|
if (process.env.ZED_TERM) {
|
|
311
448
|
detected.set("Zed", { ide: "Zed", supported: false });
|
|
312
449
|
}
|
|
313
|
-
if (process.env.
|
|
314
|
-
|
|
315
|
-
detected.set("vscode", { ide: SUPPORTED_IDE, supported: true });
|
|
316
|
-
}
|
|
450
|
+
if (process.env.WINDSURF_APP_DIR || process.env.WINDSURF_CHANNEL || process.env.__CFBundleIdentifier === "com.codeium.windsurf" || process.env.npm_config_user_agent && process.env.npm_config_user_agent.includes("windsurf")) {
|
|
451
|
+
detected.set("Windsurf", { ide: "Windsurf", supported: false });
|
|
317
452
|
}
|
|
318
|
-
|
|
319
|
-
|
|
453
|
+
const [hasTrae, hasCursor, hasVscode, hasIdea] = await Promise.all([
|
|
454
|
+
exists(path5.join(root, ".trae")),
|
|
455
|
+
exists(path5.join(root, ".cursor")),
|
|
456
|
+
exists(path5.join(root, ".vscode")),
|
|
457
|
+
exists(path5.join(root, ".idea"))
|
|
458
|
+
]);
|
|
459
|
+
if (hasTrae && !detected.has("Trae")) {
|
|
460
|
+
detected.set("Trae", { ide: "trae", supported: true });
|
|
320
461
|
}
|
|
321
|
-
if (
|
|
322
|
-
detected.set("Cursor", { ide: "
|
|
462
|
+
if (hasCursor && !detected.has("Cursor")) {
|
|
463
|
+
detected.set("Cursor", { ide: "cursor", supported: true });
|
|
323
464
|
}
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
detected.set("vscode", { ide: SUPPORTED_IDE, supported: true });
|
|
327
|
-
}
|
|
465
|
+
if (hasVscode && !detected.has("vscode")) {
|
|
466
|
+
detected.set("vscode", { ide: SUPPORTED_IDE, supported: true });
|
|
328
467
|
}
|
|
329
|
-
if (
|
|
468
|
+
if (hasIdea && !detected.has("JetBrains IDE")) {
|
|
330
469
|
detected.set("JetBrains IDE", { ide: "JetBrains IDE", supported: false });
|
|
331
470
|
}
|
|
471
|
+
if (detected.size === 0 && process.env.TERM_PROGRAM === "vscode") {
|
|
472
|
+
detected.set("vscode", { ide: SUPPORTED_IDE, supported: true });
|
|
473
|
+
}
|
|
332
474
|
return {
|
|
333
475
|
detected: Array.from(detected.values())
|
|
334
476
|
};
|
|
335
477
|
}
|
|
336
478
|
|
|
337
|
-
// src/detect/
|
|
479
|
+
// src/detect/provider.ts
|
|
338
480
|
import path6 from "path";
|
|
339
481
|
var KNOWN_CLI_TOOLS = [
|
|
340
482
|
{ id: "claude-code", bin: "claude", label: "Claude Code", supported: true },
|
|
@@ -344,23 +486,24 @@ var KNOWN_CLI_TOOLS = [
|
|
|
344
486
|
];
|
|
345
487
|
var KNOWN_IDE_PLUGINS = [
|
|
346
488
|
{ id: "claude-code", extId: "anthropic.claude-code", label: "Claude Code", supported: true },
|
|
347
|
-
{ id: "
|
|
489
|
+
{ id: "copilot", extId: "github.copilot", label: "GitHub Copilot", supported: true },
|
|
348
490
|
{ id: "codex", extId: "openai.chatgpt", label: "Codex (ChatGPT)", supported: true },
|
|
349
491
|
{ id: "gemini", extId: "google.geminicodeassist", label: "Gemini Code Assist", supported: true }
|
|
350
492
|
];
|
|
351
|
-
async function
|
|
493
|
+
async function detectProviders(root) {
|
|
352
494
|
const detectedMap = /* @__PURE__ */ new Map();
|
|
353
|
-
|
|
495
|
+
const cliChecks = KNOWN_CLI_TOOLS.map(async (tool) => {
|
|
354
496
|
if (await which(tool.bin)) {
|
|
355
497
|
detectedMap.set(tool.id, {
|
|
356
498
|
id: tool.id,
|
|
357
499
|
label: tool.label,
|
|
358
500
|
supported: tool.supported,
|
|
359
|
-
|
|
501
|
+
providerModes: ["cli"],
|
|
360
502
|
preferredMode: "cli"
|
|
361
503
|
});
|
|
362
504
|
}
|
|
363
|
-
}
|
|
505
|
+
});
|
|
506
|
+
await Promise.all(cliChecks);
|
|
364
507
|
const extensionsJsonPath = path6.join(root, ".vscode", "extensions.json");
|
|
365
508
|
let recommendedExts = [];
|
|
366
509
|
if (await exists(extensionsJsonPath)) {
|
|
@@ -375,35 +518,49 @@ async function detectAITools(root) {
|
|
|
375
518
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
376
519
|
const globalExtDir = path6.join(homeDir, ".vscode", "extensions");
|
|
377
520
|
const globalExtExists = await exists(globalExtDir);
|
|
521
|
+
let installedExtensionFolders = [];
|
|
522
|
+
if (globalExtExists) {
|
|
523
|
+
try {
|
|
524
|
+
const { readdir } = await import("fs/promises");
|
|
525
|
+
installedExtensionFolders = await readdir(globalExtDir);
|
|
526
|
+
const obsoletePath = path6.join(globalExtDir, ".obsolete");
|
|
527
|
+
if (await exists(obsoletePath)) {
|
|
528
|
+
try {
|
|
529
|
+
const obsoleteData = await readJSON(obsoletePath);
|
|
530
|
+
if (obsoleteData) {
|
|
531
|
+
const obsoleteKeys = Object.keys(obsoleteData);
|
|
532
|
+
installedExtensionFolders = installedExtensionFolders.filter((folder) => {
|
|
533
|
+
return !obsoleteKeys.includes(folder);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
}
|
|
378
542
|
for (const plugin of KNOWN_IDE_PLUGINS) {
|
|
379
543
|
let isInstalled = false;
|
|
380
544
|
if (recommendedExts.includes(plugin.extId.toLowerCase())) {
|
|
381
545
|
isInstalled = true;
|
|
382
|
-
} else if (
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const lower = f.toLowerCase();
|
|
388
|
-
return lower === plugin.extId.toLowerCase() || lower.startsWith(plugin.extId.toLowerCase() + "-");
|
|
389
|
-
})) {
|
|
390
|
-
isInstalled = true;
|
|
391
|
-
}
|
|
392
|
-
} catch {
|
|
393
|
-
}
|
|
546
|
+
} else if (installedExtensionFolders.some((f) => {
|
|
547
|
+
const lower = f.toLowerCase();
|
|
548
|
+
return lower === plugin.extId.toLowerCase() || lower.startsWith(plugin.extId.toLowerCase() + "-");
|
|
549
|
+
})) {
|
|
550
|
+
isInstalled = true;
|
|
394
551
|
}
|
|
395
552
|
if (isInstalled) {
|
|
396
553
|
const existing = detectedMap.get(plugin.id);
|
|
397
554
|
if (existing) {
|
|
398
|
-
existing.
|
|
399
|
-
existing.preferredMode = "
|
|
555
|
+
existing.providerModes.push("extension");
|
|
556
|
+
existing.preferredMode = "extension";
|
|
400
557
|
} else {
|
|
401
558
|
detectedMap.set(plugin.id, {
|
|
402
559
|
id: plugin.id,
|
|
403
560
|
label: plugin.label,
|
|
404
561
|
supported: plugin.supported,
|
|
405
|
-
|
|
406
|
-
preferredMode: "
|
|
562
|
+
providerModes: ["extension"],
|
|
563
|
+
preferredMode: "extension"
|
|
407
564
|
});
|
|
408
565
|
}
|
|
409
566
|
}
|
|
@@ -413,207 +570,259 @@ async function detectAITools(root) {
|
|
|
413
570
|
|
|
414
571
|
// src/inject/ast-injector.ts
|
|
415
572
|
import path7 from "path";
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
573
|
+
import { loadFile, writeFile as writeAstFile } from "magicast";
|
|
574
|
+
|
|
575
|
+
// src/inject/strategies/vite.ts
|
|
576
|
+
import { addVitePlugin } from "magicast/helpers";
|
|
577
|
+
var ViteStrategy = class {
|
|
578
|
+
name = "Vite";
|
|
579
|
+
supports(tool) {
|
|
580
|
+
return tool === "vite";
|
|
581
|
+
}
|
|
582
|
+
inject({ mod, detection }) {
|
|
583
|
+
addVitePlugin(mod, {
|
|
584
|
+
from: "@inspecto-dev/plugin",
|
|
585
|
+
constructor: "vitePlugin"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
getManualInstructions(detection, reason) {
|
|
589
|
+
return [
|
|
590
|
+
`import { vitePlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
591
|
+
"",
|
|
592
|
+
"// Add to your plugins array:",
|
|
593
|
+
`plugins: [`,
|
|
594
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
595
|
+
` ...otherPlugins`,
|
|
596
|
+
`].filter(Boolean)`
|
|
597
|
+
];
|
|
598
|
+
}
|
|
423
599
|
};
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
600
|
+
|
|
601
|
+
// src/inject/strategies/webpack.ts
|
|
602
|
+
var WebpackStrategy = class {
|
|
603
|
+
name = "Webpack";
|
|
604
|
+
supports(tool) {
|
|
605
|
+
return tool === "webpack";
|
|
606
|
+
}
|
|
607
|
+
inject(options) {
|
|
608
|
+
throw new Error("Webpack requires manual plugin configuration");
|
|
609
|
+
}
|
|
610
|
+
getManualInstructions(detection, reason) {
|
|
611
|
+
const importPkg = detection.isLegacyWebpack ? "@inspecto-dev/plugin/legacy/webpack4" : "@inspecto-dev/plugin";
|
|
612
|
+
const pluginName = detection.isLegacyWebpack ? "webpack4Plugin" : "webpackPlugin";
|
|
613
|
+
const pluginCall = detection.isLegacyWebpack ? `process.env.NODE_ENV !== 'production' && inspecto({
|
|
614
|
+
pathType: 'absolute',
|
|
615
|
+
escapeTags: ['Transition', 'AnimatePresence'],
|
|
616
|
+
})` : `process.env.NODE_ENV !== 'production' && inspecto()`;
|
|
617
|
+
return [
|
|
618
|
+
`import { ${pluginName} as inspecto } from '${importPkg}'`,
|
|
619
|
+
"",
|
|
620
|
+
"// Add to your plugins array:",
|
|
621
|
+
`plugins: [`,
|
|
622
|
+
` ${pluginCall},`,
|
|
623
|
+
` ...otherPlugins`,
|
|
624
|
+
`].filter(Boolean)`
|
|
625
|
+
];
|
|
427
626
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/inject/strategies/rspack.ts
|
|
630
|
+
var RspackStrategy = class {
|
|
631
|
+
name = "Rspack";
|
|
632
|
+
supports(tool) {
|
|
633
|
+
return tool === "rspack";
|
|
634
|
+
}
|
|
635
|
+
inject(options) {
|
|
636
|
+
throw new Error("Rspack requires manual plugin configuration");
|
|
637
|
+
}
|
|
638
|
+
getManualInstructions(detection, reason) {
|
|
639
|
+
const importPkg = detection.isLegacyRspack ? "@inspecto-dev/plugin/legacy/rspack" : "@inspecto-dev/plugin";
|
|
640
|
+
const pluginCall = detection.isLegacyRspack ? `process.env.NODE_ENV !== 'production' && inspecto({
|
|
433
641
|
pathType: 'absolute',
|
|
434
642
|
escapeTags: ['Transition', 'AnimatePresence'],
|
|
435
|
-
})
|
|
643
|
+
})` : `process.env.NODE_ENV !== 'production' && inspecto()`;
|
|
644
|
+
return [
|
|
645
|
+
`import { rspackPlugin as inspecto } from '${importPkg}'`,
|
|
646
|
+
"",
|
|
647
|
+
"// Add to your plugins array:",
|
|
648
|
+
`plugins: [`,
|
|
649
|
+
` ${pluginCall},`,
|
|
650
|
+
` ...otherPlugins`,
|
|
651
|
+
`].filter(Boolean)`
|
|
652
|
+
];
|
|
436
653
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
// src/inject/strategies/rsbuild.ts
|
|
657
|
+
var RsbuildStrategy = class {
|
|
658
|
+
name = "Rsbuild";
|
|
659
|
+
supports(tool) {
|
|
660
|
+
return tool === "rsbuild";
|
|
661
|
+
}
|
|
662
|
+
inject(options) {
|
|
663
|
+
throw new Error("Rsbuild requires manual plugin configuration due to nested structure");
|
|
664
|
+
}
|
|
665
|
+
getManualInstructions(detection, reason) {
|
|
666
|
+
return [
|
|
667
|
+
`import { rspackPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
448
668
|
"",
|
|
449
669
|
"// Add to tools.rspack:",
|
|
450
670
|
`tools: {`,
|
|
451
671
|
` rspack: {`,
|
|
452
672
|
` plugins: [`,
|
|
453
|
-
`
|
|
673
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
454
674
|
` ]`,
|
|
455
675
|
` }`,
|
|
456
676
|
`}`
|
|
457
|
-
]
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
677
|
+
];
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/inject/strategies/esbuild.ts
|
|
682
|
+
var EsbuildStrategy = class {
|
|
683
|
+
name = "esbuild";
|
|
684
|
+
supports(tool) {
|
|
685
|
+
return tool === "esbuild";
|
|
686
|
+
}
|
|
687
|
+
inject(options) {
|
|
688
|
+
throw new Error("Esbuild requires manual plugin configuration");
|
|
689
|
+
}
|
|
690
|
+
getManualInstructions(detection, reason) {
|
|
691
|
+
return [
|
|
692
|
+
`1. Update your esbuild config (${detection.configPath}):`,
|
|
693
|
+
`import { esbuildPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
461
694
|
"",
|
|
462
695
|
"// Add to your plugins array:",
|
|
463
696
|
`plugins: [`,
|
|
464
|
-
`
|
|
697
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
465
698
|
` ...otherPlugins`,
|
|
466
|
-
`].filter(Boolean)
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
let match;
|
|
477
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
478
|
-
const lineEnd = content.indexOf("\n", match.index);
|
|
479
|
-
if (lineEnd > lastImportEnd) {
|
|
480
|
-
lastImportEnd = lineEnd;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (lastImportEnd === 0) {
|
|
484
|
-
const requireRegex = /^(?:const|let|var)\s.+=\s*require\(.+\).*$/gm;
|
|
485
|
-
while ((match = requireRegex.exec(content)) !== null) {
|
|
486
|
-
const lineEnd = content.indexOf("\n", match.index);
|
|
487
|
-
if (lineEnd > lastImportEnd) {
|
|
488
|
-
lastImportEnd = lineEnd;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
699
|
+
`].filter(Boolean)`,
|
|
700
|
+
"",
|
|
701
|
+
"2. Initialize the client in your app entry (e.g., main.js / index.js):",
|
|
702
|
+
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
703
|
+
"",
|
|
704
|
+
"// Call this before your app renders",
|
|
705
|
+
`if (process.env.NODE_ENV !== 'production') {`,
|
|
706
|
+
` mountInspector()`,
|
|
707
|
+
`}`
|
|
708
|
+
];
|
|
491
709
|
}
|
|
492
|
-
|
|
493
|
-
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// src/inject/strategies/rollup.ts
|
|
713
|
+
var RollupStrategy = class {
|
|
714
|
+
name = "Rollup";
|
|
715
|
+
supports(tool) {
|
|
716
|
+
return tool === "rollup";
|
|
717
|
+
}
|
|
718
|
+
inject(options) {
|
|
719
|
+
throw new Error("Rollup requires manual plugin configuration");
|
|
720
|
+
}
|
|
721
|
+
getManualInstructions(detection, reason) {
|
|
722
|
+
return [
|
|
723
|
+
`1. Update your rollup config (${detection.configPath}):`,
|
|
724
|
+
`import { rollupPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
725
|
+
"",
|
|
726
|
+
"// Add to your plugins array:",
|
|
727
|
+
`plugins: [`,
|
|
728
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
729
|
+
` ...otherPlugins`,
|
|
730
|
+
`].filter(Boolean)`,
|
|
731
|
+
"",
|
|
732
|
+
"2. Initialize the client in your app entry (e.g., main.js / index.js):",
|
|
733
|
+
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
734
|
+
"",
|
|
735
|
+
"// Call this before your app renders",
|
|
736
|
+
`if (process.env.NODE_ENV !== 'production') {`,
|
|
737
|
+
` mountInspector()`,
|
|
738
|
+
`}`
|
|
739
|
+
];
|
|
494
740
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
// src/inject/strategies/index.ts
|
|
744
|
+
var STRATEGIES = [
|
|
745
|
+
new ViteStrategy(),
|
|
746
|
+
new WebpackStrategy(),
|
|
747
|
+
new RspackStrategy(),
|
|
748
|
+
new RsbuildStrategy(),
|
|
749
|
+
new EsbuildStrategy(),
|
|
750
|
+
new RollupStrategy()
|
|
751
|
+
];
|
|
752
|
+
|
|
753
|
+
// src/inject/ast-injector.ts
|
|
754
|
+
function printManualInstructions(strategy, detection, reason) {
|
|
755
|
+
log.warn(`Could not automatically configure ${detection.configPath}`);
|
|
756
|
+
log.hint(`(reason: ${reason})`);
|
|
757
|
+
log.blank();
|
|
758
|
+
log.hint("Please add the following manually:");
|
|
759
|
+
if (strategy) {
|
|
760
|
+
const instructions = strategy.getManualInstructions(detection, reason);
|
|
761
|
+
log.codeBlock(instructions);
|
|
762
|
+
} else {
|
|
763
|
+
log.error(`Unsupported build tool: ${detection.tool}`);
|
|
517
764
|
}
|
|
518
|
-
const pluginsRegex = /(plugins\s*:\s*\[)/;
|
|
519
|
-
const match = pluginsRegex.exec(content);
|
|
520
|
-
if (!match) return null;
|
|
521
|
-
const insertPos = match.index + match[0].length;
|
|
522
|
-
const pluginExpr = `
|
|
523
|
-
${getPluginExpression(detection.isLegacyRspack)},`;
|
|
524
|
-
return content.slice(0, insertPos) + pluginExpr + content.slice(insertPos);
|
|
525
765
|
}
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const openBrackets = (content.match(/\[/g) || []).length;
|
|
530
|
-
const closeBrackets = (content.match(/\]/g) || []).length;
|
|
531
|
-
return Math.abs(openBraces - closeBraces) <= 1 && Math.abs(openBrackets - closeBrackets) <= 1;
|
|
766
|
+
function isAlreadyInjected(content) {
|
|
767
|
+
return /import\s+.*@inspecto-dev\/plugin/.test(content) || /require\(['"]@inspecto-dev\/plugin['"]\)/.test(content) || /import\s+.*ai-dev-inspector/.test(content) || // Legacy support
|
|
768
|
+
/require\(['"]ai-dev-inspector['"]\)/.test(content);
|
|
532
769
|
}
|
|
533
770
|
async function injectPlugin(root, detection, dryRun) {
|
|
534
771
|
const configPath = path7.join(root, detection.configPath);
|
|
535
|
-
const backupPath = configPath + ".bak";
|
|
536
772
|
const mutations = [];
|
|
773
|
+
const strategy = STRATEGIES.find((s) => s.supports(detection.tool));
|
|
537
774
|
const content = await readFile(configPath);
|
|
538
775
|
if (!content) {
|
|
539
|
-
printManualInstructions(
|
|
540
|
-
detection.tool,
|
|
541
|
-
detection.configPath,
|
|
542
|
-
"config file not readable",
|
|
543
|
-
detection.isLegacyRspack
|
|
544
|
-
);
|
|
776
|
+
printManualInstructions(strategy, detection, "config file not readable");
|
|
545
777
|
return { success: false, mutations, failureReason: "config file not readable" };
|
|
546
778
|
}
|
|
547
779
|
if (isAlreadyInjected(content)) {
|
|
548
|
-
log.success(`Plugin already
|
|
549
|
-
if (await exists(backupPath)) {
|
|
550
|
-
mutations.push({
|
|
551
|
-
type: "file_modified",
|
|
552
|
-
path: detection.configPath,
|
|
553
|
-
backup: detection.configPath + ".bak",
|
|
554
|
-
description: "Previously injected inspecto() plugin"
|
|
555
|
-
});
|
|
556
|
-
} else {
|
|
557
|
-
mutations.push({
|
|
558
|
-
type: "file_modified",
|
|
559
|
-
path: detection.configPath,
|
|
560
|
-
description: "Previously injected inspecto() plugin (no backup)"
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
return { success: true, mutations };
|
|
564
|
-
}
|
|
565
|
-
if (!dryRun) {
|
|
566
|
-
await copyFile(configPath, backupPath);
|
|
780
|
+
log.success(`Plugin already configured in ${detection.configPath} (skipped)`);
|
|
567
781
|
mutations.push({
|
|
568
782
|
type: "file_modified",
|
|
569
783
|
path: detection.configPath,
|
|
570
|
-
|
|
571
|
-
description: "Injected inspecto() plugin"
|
|
784
|
+
description: "Previously configured inspecto() plugin"
|
|
572
785
|
});
|
|
786
|
+
return { success: true, mutations };
|
|
573
787
|
}
|
|
574
|
-
|
|
575
|
-
const injected = injectIntoPluginsArray(content, detection);
|
|
576
|
-
if (!injected) {
|
|
788
|
+
if (!strategy) {
|
|
577
789
|
printManualInstructions(
|
|
578
|
-
|
|
579
|
-
detection
|
|
580
|
-
|
|
581
|
-
detection.isLegacyRspack
|
|
790
|
+
strategy,
|
|
791
|
+
detection,
|
|
792
|
+
`No injection strategy found for ${detection.tool}`
|
|
582
793
|
);
|
|
583
|
-
return {
|
|
584
|
-
success: false,
|
|
585
|
-
mutations,
|
|
586
|
-
failureReason: "could not locate plugins array"
|
|
587
|
-
};
|
|
794
|
+
return { success: false, mutations, failureReason: "No strategy found" };
|
|
588
795
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
796
|
+
if (dryRun) {
|
|
797
|
+
log.dryRun(`Would automatically configure plugin in ${detection.configPath}`);
|
|
798
|
+
return { success: true, mutations: [] };
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const mod = await loadFile(configPath);
|
|
802
|
+
strategy.inject({
|
|
803
|
+
mod,
|
|
804
|
+
detection
|
|
805
|
+
});
|
|
806
|
+
await writeAstFile(mod, configPath);
|
|
807
|
+
mutations.push({
|
|
808
|
+
type: "file_modified",
|
|
809
|
+
path: detection.configPath,
|
|
810
|
+
description: "Automatically configured inspecto() plugin"
|
|
811
|
+
});
|
|
812
|
+
log.success(`Configured plugin in ${detection.configPath}`);
|
|
813
|
+
return { success: true, mutations };
|
|
814
|
+
} catch (err) {
|
|
597
815
|
printManualInstructions(
|
|
598
|
-
|
|
599
|
-
detection
|
|
600
|
-
|
|
601
|
-
detection.isLegacyRspack
|
|
816
|
+
strategy,
|
|
817
|
+
detection,
|
|
818
|
+
`Automatic configuration unavailable: ${err instanceof Error ? err.message : String(err)}`
|
|
602
819
|
);
|
|
603
820
|
return {
|
|
604
821
|
success: false,
|
|
605
|
-
mutations
|
|
606
|
-
|
|
607
|
-
failureReason: "injection produced invalid syntax"
|
|
822
|
+
mutations,
|
|
823
|
+
failureReason: "Automatic configuration unavailable"
|
|
608
824
|
};
|
|
609
825
|
}
|
|
610
|
-
if (dryRun) {
|
|
611
|
-
log.dryRun(`Would inject plugin into ${detection.configPath}`);
|
|
612
|
-
} else {
|
|
613
|
-
await writeFile(configPath, modifiedContent);
|
|
614
|
-
log.success(`Injected plugin into ${detection.configPath}`);
|
|
615
|
-
}
|
|
616
|
-
return { success: true, mutations };
|
|
617
826
|
}
|
|
618
827
|
|
|
619
828
|
// src/inject/gitignore.ts
|
|
@@ -630,7 +839,7 @@ async function updateGitignore(root, shared, dryRun) {
|
|
|
630
839
|
if (!dryRun) {
|
|
631
840
|
await writeFile(gitignorePath, content);
|
|
632
841
|
}
|
|
633
|
-
log.success("Updated .gitignore: .inspecto/
|
|
842
|
+
log.success("Updated .gitignore: .inspecto/ is no longer fully ignored");
|
|
634
843
|
return;
|
|
635
844
|
}
|
|
636
845
|
const missingRules = desiredRules.filter((rule) => !content.includes(rule));
|
|
@@ -692,41 +901,54 @@ async function tryOpenURI(uri) {
|
|
|
692
901
|
return false;
|
|
693
902
|
}
|
|
694
903
|
}
|
|
695
|
-
async function installExtension(dryRun) {
|
|
904
|
+
async function installExtension(dryRun, ide) {
|
|
696
905
|
if (dryRun) {
|
|
697
906
|
log.dryRun("Would attempt to install VS Code extension");
|
|
698
907
|
return null;
|
|
699
908
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
909
|
+
const isVSCode = !ide || ide === "vscode";
|
|
910
|
+
if (isVSCode) {
|
|
911
|
+
if (await which("code")) {
|
|
912
|
+
try {
|
|
913
|
+
await run("code", ["--install-extension", EXTENSION_ID]);
|
|
914
|
+
log.success("VS Code extension installed via CLI");
|
|
915
|
+
return { type: "extension_installed", id: EXTENSION_ID };
|
|
916
|
+
} catch {
|
|
917
|
+
}
|
|
706
918
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
919
|
+
const codePath = await findVSCodeBinary();
|
|
920
|
+
if (codePath) {
|
|
921
|
+
try {
|
|
922
|
+
await run(codePath, ["--install-extension", EXTENSION_ID]);
|
|
923
|
+
log.success("VS Code extension installed via binary path");
|
|
924
|
+
log.info(
|
|
925
|
+
'Tip: Add "code" to your PATH to help Inspecto detect other AI tools in the future'
|
|
926
|
+
);
|
|
927
|
+
return { type: "extension_installed", id: EXTENSION_ID };
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
716
930
|
}
|
|
931
|
+
const uri = `vscode:extension/${EXTENSION_ID}`;
|
|
932
|
+
if (await tryOpenURI(uri)) {
|
|
933
|
+
log.warn("Opened extension page in VS Code");
|
|
934
|
+
log.hint('Please click "Install" in the opened VS Code window to complete setup.');
|
|
935
|
+
return { type: "extension_installed", id: EXTENSION_ID, manual_action_required: true };
|
|
936
|
+
}
|
|
937
|
+
log.warn("Could not auto-install VS Code extension");
|
|
938
|
+
log.hint("Please install it manually to enable Inspector features:");
|
|
939
|
+
log.hint(" 1. Open VS Code");
|
|
940
|
+
log.hint(" 2. Press Ctrl+Shift+X (or Cmd+Shift+X)");
|
|
941
|
+
log.hint(' 3. Search for "Inspecto"');
|
|
942
|
+
log.hint(` Or visit: https://marketplace.visualstudio.com/items?itemName=${EXTENSION_ID}`);
|
|
943
|
+
return null;
|
|
717
944
|
}
|
|
718
|
-
|
|
719
|
-
if (await tryOpenURI(uri)) {
|
|
720
|
-
log.warn("Opened extension page in VS Code");
|
|
721
|
-
log.hint('Please click "Install" in the opened VS Code window to complete setup.');
|
|
722
|
-
return { type: "extension_installed", id: EXTENSION_ID, manual_action_required: true };
|
|
723
|
-
}
|
|
724
|
-
log.warn("Could not auto-install VS Code extension");
|
|
945
|
+
log.warn(`Could not auto-install extension for ${ide}`);
|
|
725
946
|
log.hint("Please install it manually to enable Inspector features:");
|
|
726
|
-
log.hint(" 1.
|
|
727
|
-
log.hint(
|
|
728
|
-
log.hint(
|
|
729
|
-
log.hint(
|
|
947
|
+
log.hint(" 1. Download the latest .vsix file from Inspecto releases");
|
|
948
|
+
log.hint(` 2. Open ${ide}`);
|
|
949
|
+
log.hint(" 3. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)");
|
|
950
|
+
log.hint(' 4. Type and select "Extensions: Install from VSIX..."');
|
|
951
|
+
log.hint(" 5. Select the downloaded .vsix file");
|
|
730
952
|
return null;
|
|
731
953
|
}
|
|
732
954
|
async function isExtensionInstalled() {
|
|
@@ -746,70 +968,49 @@ async function isExtensionInstalled() {
|
|
|
746
968
|
}
|
|
747
969
|
}
|
|
748
970
|
|
|
749
|
-
// src/
|
|
971
|
+
// src/prompts.ts
|
|
972
|
+
import prompts from "prompts";
|
|
750
973
|
async function promptIDEChoice(detections) {
|
|
751
974
|
if (!process.stdin.isTTY) {
|
|
752
975
|
log.warn("Multiple IDEs detected but stdin is not interactive");
|
|
753
976
|
log.hint(`Using: ${detections[0].ide} (first match)`);
|
|
754
977
|
return detections[0];
|
|
755
978
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
process.stdout.write(" > Your choice: ");
|
|
765
|
-
process.stdin.resume();
|
|
766
|
-
process.stdin.setEncoding("utf-8");
|
|
767
|
-
const onData = (data) => {
|
|
768
|
-
const choice = parseInt(String(data).trim(), 10);
|
|
769
|
-
process.stdin.off("data", onData);
|
|
770
|
-
process.stdin.pause();
|
|
771
|
-
if (choice >= 1 && choice <= detections.length) {
|
|
772
|
-
resolve(detections[choice - 1]);
|
|
773
|
-
} else {
|
|
774
|
-
resolve(null);
|
|
775
|
-
}
|
|
776
|
-
};
|
|
777
|
-
process.stdin.on("data", onData);
|
|
979
|
+
const { choice } = await prompts({
|
|
980
|
+
type: "select",
|
|
981
|
+
name: "choice",
|
|
982
|
+
message: "Detected multiple IDEs, please choose one:",
|
|
983
|
+
choices: detections.map((d, i) => ({
|
|
984
|
+
title: `${d.ide} ${d.supported ? "(supported)" : "(unsupported/limited)"}`,
|
|
985
|
+
value: i
|
|
986
|
+
}))
|
|
778
987
|
});
|
|
988
|
+
if (choice === void 0) return null;
|
|
989
|
+
return detections[choice];
|
|
779
990
|
}
|
|
780
|
-
async function
|
|
991
|
+
async function promptProviderChoice(detections) {
|
|
781
992
|
if (!process.stdin.isTTY) {
|
|
782
993
|
log.warn("Multiple AI tools detected but stdin is not interactive");
|
|
783
994
|
log.hint(`Using: ${detections[0].label} (first match)`);
|
|
784
995
|
return detections[0];
|
|
785
996
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
process.stdin.setEncoding("utf-8");
|
|
801
|
-
const onData = (data) => {
|
|
802
|
-
const choice = parseInt(String(data).trim(), 10);
|
|
803
|
-
process.stdin.off("data", onData);
|
|
804
|
-
process.stdin.pause();
|
|
805
|
-
if (choice >= 1 && choice <= detections.length) {
|
|
806
|
-
resolve(detections[choice - 1]);
|
|
807
|
-
} else {
|
|
808
|
-
resolve(null);
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
process.stdin.on("data", onData);
|
|
997
|
+
const { choice } = await prompts({
|
|
998
|
+
type: "select",
|
|
999
|
+
name: "choice",
|
|
1000
|
+
message: "Detected multiple providers, please choose one:",
|
|
1001
|
+
choices: detections.map((d, i) => {
|
|
1002
|
+
const modeLabels = d.providerModes.map(
|
|
1003
|
+
(mode) => mode === "extension" ? "VS Code Extension" : "Terminal CLI"
|
|
1004
|
+
);
|
|
1005
|
+
const modeStr = modeLabels.join(" & ");
|
|
1006
|
+
return {
|
|
1007
|
+
title: `${d.label} ${d.supported ? `(supported ${modeStr})` : "(unsupported/limited)"}`,
|
|
1008
|
+
value: i
|
|
1009
|
+
};
|
|
1010
|
+
})
|
|
812
1011
|
});
|
|
1012
|
+
if (choice === void 0) return null;
|
|
1013
|
+
return detections[choice];
|
|
813
1014
|
}
|
|
814
1015
|
async function promptConfigChoice(detections) {
|
|
815
1016
|
if (!process.stdin.isTTY) {
|
|
@@ -817,30 +1018,93 @@ async function promptConfigChoice(detections) {
|
|
|
817
1018
|
log.hint(`Using: ${detections[0].label} (first match)`);
|
|
818
1019
|
return detections[0];
|
|
819
1020
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1021
|
+
const choices = detections.map((d, i) => ({
|
|
1022
|
+
title: d.label,
|
|
1023
|
+
value: i
|
|
1024
|
+
}));
|
|
1025
|
+
choices.push({
|
|
1026
|
+
title: "Skip (I'll configure manually)",
|
|
1027
|
+
value: -1
|
|
824
1028
|
});
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
process.stdin.setEncoding("utf-8");
|
|
831
|
-
const onData = (data) => {
|
|
832
|
-
const choice = parseInt(String(data).trim(), 10);
|
|
833
|
-
process.stdin.off("data", onData);
|
|
834
|
-
process.stdin.pause();
|
|
835
|
-
if (choice >= 1 && choice <= detections.length) {
|
|
836
|
-
resolve(detections[choice - 1]);
|
|
837
|
-
} else {
|
|
838
|
-
resolve(null);
|
|
839
|
-
}
|
|
840
|
-
};
|
|
841
|
-
process.stdin.on("data", onData);
|
|
1029
|
+
const { choice } = await prompts({
|
|
1030
|
+
type: "select",
|
|
1031
|
+
name: "choice",
|
|
1032
|
+
message: "Detected multiple build tool configs, please choose one to inject:",
|
|
1033
|
+
choices
|
|
842
1034
|
});
|
|
1035
|
+
if (choice === void 0 || choice === -1) return null;
|
|
1036
|
+
return detections[choice];
|
|
1037
|
+
}
|
|
1038
|
+
async function promptUnsupportedFrameworkContinue() {
|
|
1039
|
+
if (!process.stdin.isTTY) {
|
|
1040
|
+
log.error("Unsupported framework detected in non-interactive environment.");
|
|
1041
|
+
log.hint("Use --force to skip this check and continue anyway.");
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
const { confirm } = await prompts({
|
|
1045
|
+
type: "confirm",
|
|
1046
|
+
name: "confirm",
|
|
1047
|
+
message: "Inspecto may not work properly. Do you want to continue anyway?",
|
|
1048
|
+
initial: false
|
|
1049
|
+
});
|
|
1050
|
+
return !!confirm;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/instructions.ts
|
|
1054
|
+
function printNuxtManualInstructions() {
|
|
1055
|
+
log.blank();
|
|
1056
|
+
log.hint("To enable Inspecto in Nuxt, update your nuxt.config.ts:");
|
|
1057
|
+
console.log(`\x1B[36m
|
|
1058
|
+
import { vitePlugin as inspecto } from '@inspecto-dev/plugin'
|
|
1059
|
+
export default defineNuxtConfig({
|
|
1060
|
+
vite: {
|
|
1061
|
+
plugins: [inspecto()]
|
|
1062
|
+
}
|
|
1063
|
+
})
|
|
1064
|
+
\x1B[0m`);
|
|
1065
|
+
log.hint("And create a Nuxt plugin at plugins/inspecto.client.ts:");
|
|
1066
|
+
console.log(`\x1B[36m
|
|
1067
|
+
export default defineNuxtPlugin(() => {
|
|
1068
|
+
if (import.meta.dev) {
|
|
1069
|
+
import('@inspecto-dev/core').then(({ mountInspector }) => {
|
|
1070
|
+
mountInspector()
|
|
1071
|
+
})
|
|
1072
|
+
}
|
|
1073
|
+
})
|
|
1074
|
+
\x1B[0m`);
|
|
1075
|
+
}
|
|
1076
|
+
function printNextJsManualInstructions() {
|
|
1077
|
+
log.blank();
|
|
1078
|
+
log.hint("To enable Inspecto in Next.js, update your next.config.mjs:");
|
|
1079
|
+
console.log(`\x1B[36m
|
|
1080
|
+
import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'
|
|
1081
|
+
const nextConfig = {
|
|
1082
|
+
webpack: (config, { dev, isServer }) => {
|
|
1083
|
+
if (dev && !isServer) config.plugins.push(inspecto())
|
|
1084
|
+
return config
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
export default nextConfig
|
|
1088
|
+
\x1B[0m`);
|
|
1089
|
+
log.hint("And initialize the client dynamically in your app/layout.tsx (or pages/_app.tsx):");
|
|
1090
|
+
console.log(`\x1B[36m
|
|
1091
|
+
'use client'
|
|
1092
|
+
import { useEffect } from 'react'
|
|
1093
|
+
|
|
1094
|
+
export default function RootLayout({ children }) {
|
|
1095
|
+
useEffect(() => {
|
|
1096
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1097
|
+
import('@inspecto-dev/core').then(({ mountInspector }) => {
|
|
1098
|
+
mountInspector({ serverUrl: 'http://127.0.0.1:5678' })
|
|
1099
|
+
})
|
|
1100
|
+
}
|
|
1101
|
+
}, [])
|
|
1102
|
+
return <html><body>{children}</body></html>
|
|
1103
|
+
}
|
|
1104
|
+
\x1B[0m`);
|
|
843
1105
|
}
|
|
1106
|
+
|
|
1107
|
+
// src/commands/init.ts
|
|
844
1108
|
async function init(options) {
|
|
845
1109
|
const root = process.cwd();
|
|
846
1110
|
const mutations = [];
|
|
@@ -850,37 +1114,60 @@ async function init(options) {
|
|
|
850
1114
|
log.hint("Run this command from your project root");
|
|
851
1115
|
return;
|
|
852
1116
|
}
|
|
853
|
-
const pm = await
|
|
1117
|
+
const [pm, frameworkResult, buildResult, ideProbe, providerProbe] = await Promise.all([
|
|
1118
|
+
detectPackageManager(root),
|
|
1119
|
+
detectFrameworks(root),
|
|
1120
|
+
detectBuildTools(root),
|
|
1121
|
+
detectIDE(root),
|
|
1122
|
+
detectProviders(root)
|
|
1123
|
+
]);
|
|
854
1124
|
log.success(`Detected package manager: ${pm}`);
|
|
855
|
-
const frameworkResult = await detectFrameworks(root);
|
|
856
1125
|
if (frameworkResult.supported.length > 0) {
|
|
857
1126
|
log.success(`Detected framework: ${frameworkResult.supported.join(", ")}`);
|
|
858
1127
|
}
|
|
859
|
-
|
|
1128
|
+
const isSupported = frameworkResult.supported.length > 0;
|
|
1129
|
+
const hasUnsupported = frameworkResult.unsupported.length > 0;
|
|
1130
|
+
if (!isSupported) {
|
|
1131
|
+
if (hasUnsupported) {
|
|
1132
|
+
const names = frameworkResult.unsupported.map((f) => f.name).join(", ");
|
|
1133
|
+
log.warn(`Detected ${names} \u2014 not supported in v1 (React / Vue only)`);
|
|
1134
|
+
} else {
|
|
1135
|
+
log.warn("No frontend framework detected");
|
|
1136
|
+
log.hint("Inspecto current version supports React and Vue projects");
|
|
1137
|
+
}
|
|
1138
|
+
if (!options.force) {
|
|
1139
|
+
const shouldContinue = await promptUnsupportedFrameworkContinue();
|
|
1140
|
+
if (!shouldContinue) {
|
|
1141
|
+
log.warn("Initialization aborted.");
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
} else {
|
|
1145
|
+
log.warn("Continuing anyway (--force)");
|
|
1146
|
+
}
|
|
1147
|
+
} else if (hasUnsupported) {
|
|
860
1148
|
const names = frameworkResult.unsupported.map((f) => f.name).join(", ");
|
|
861
|
-
log.
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (frameworkResult.supported.length === 0 && frameworkResult.unsupported.length === 0) {
|
|
865
|
-
log.warn("No frontend framework detected");
|
|
866
|
-
log.hint("Inspecto v1 supports React and Vue projects");
|
|
1149
|
+
log.hint(
|
|
1150
|
+
`Note: Inspecto will be configured for ${frameworkResult.supported.join(", ")}. Other detected frameworks (${names}) will be ignored.`
|
|
1151
|
+
);
|
|
867
1152
|
}
|
|
868
|
-
|
|
1153
|
+
let manualConfigRequiredFor = "";
|
|
869
1154
|
if (buildResult.supported.length > 0) {
|
|
870
1155
|
buildResult.supported.forEach((bt) => log.success(`Detected: ${bt.label}`));
|
|
871
1156
|
}
|
|
872
1157
|
if (buildResult.unsupported.length > 0) {
|
|
873
1158
|
const names = buildResult.unsupported.join(", ");
|
|
874
|
-
|
|
875
|
-
log.
|
|
876
|
-
log.hint("
|
|
1159
|
+
manualConfigRequiredFor = buildResult.unsupported[0] || "";
|
|
1160
|
+
log.warn(`Detected ${names} \u2014 automatic plugin injection is not supported in current version`);
|
|
1161
|
+
log.hint("You can still manually configure it by modifying your configuration file");
|
|
877
1162
|
}
|
|
878
1163
|
if (buildResult.supported.length === 0 && buildResult.unsupported.length === 0) {
|
|
879
1164
|
log.warn("No recognized build tool detected");
|
|
880
|
-
log.hint("
|
|
1165
|
+
log.hint("current version supports: Vite, Webpack, Rspack, esbuild, Rollup");
|
|
881
1166
|
log.hint("Dependency will be installed but plugin injection will be skipped");
|
|
1167
|
+
log.hint(
|
|
1168
|
+
"Please refer to the manual setup guide: https://inspecto-dev.github.io/inspecto/guide/manual-installation"
|
|
1169
|
+
);
|
|
882
1170
|
}
|
|
883
|
-
const ideProbe = await detectIDE(root);
|
|
884
1171
|
let selectedIDE = null;
|
|
885
1172
|
if (ideProbe.detected.length === 0) {
|
|
886
1173
|
log.error("No IDE detected in current project");
|
|
@@ -901,21 +1188,20 @@ async function init(options) {
|
|
|
901
1188
|
log.hint(`Features may be severely limited or unavailable in ${selectedIDE.ide}.`);
|
|
902
1189
|
}
|
|
903
1190
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
if (aiToolProbe.detected.length === 0) {
|
|
1191
|
+
let selectedProvider = null;
|
|
1192
|
+
if (!options.provider) {
|
|
1193
|
+
if (providerProbe.detected.length === 0) {
|
|
908
1194
|
log.warn("No supported AI tools detected");
|
|
909
1195
|
log.hint("Inspecto works best with Claude Code, Trae CLI, or GitHub Copilot");
|
|
910
|
-
} else if (
|
|
911
|
-
|
|
912
|
-
if (
|
|
913
|
-
log.success(`Detected AI tool: ${
|
|
1196
|
+
} else if (providerProbe.detected.length === 1) {
|
|
1197
|
+
selectedProvider = providerProbe.detected[0];
|
|
1198
|
+
if (selectedProvider.supported) {
|
|
1199
|
+
log.success(`Detected AI tool: ${selectedProvider.label}`);
|
|
914
1200
|
}
|
|
915
1201
|
} else {
|
|
916
|
-
|
|
917
|
-
if (
|
|
918
|
-
log.success(`Selected
|
|
1202
|
+
selectedProvider = await promptProviderChoice(providerProbe.detected);
|
|
1203
|
+
if (selectedProvider) {
|
|
1204
|
+
log.success(`Selected provider: ${selectedProvider.label}`);
|
|
919
1205
|
}
|
|
920
1206
|
}
|
|
921
1207
|
}
|
|
@@ -933,16 +1219,8 @@ async function init(options) {
|
|
|
933
1219
|
throw new Error(result.stderr);
|
|
934
1220
|
}
|
|
935
1221
|
log.success("Installed @inspecto-dev/plugin and @inspecto-dev/core as devDependencies");
|
|
936
|
-
mutations.push({
|
|
937
|
-
|
|
938
|
-
name: "@inspecto-dev/plugin",
|
|
939
|
-
dev: true
|
|
940
|
-
});
|
|
941
|
-
mutations.push({
|
|
942
|
-
type: "dependency_added",
|
|
943
|
-
name: "@inspecto-dev/core",
|
|
944
|
-
dev: true
|
|
945
|
-
});
|
|
1222
|
+
mutations.push({ type: "dependency_added", name: "@inspecto-dev/plugin", dev: true });
|
|
1223
|
+
mutations.push({ type: "dependency_added", name: "@inspecto-dev/core", dev: true });
|
|
946
1224
|
} catch (err) {
|
|
947
1225
|
installFailed = true;
|
|
948
1226
|
log.error(`Failed to install dependency: ${err?.message || "Unknown error"}`);
|
|
@@ -969,55 +1247,50 @@ async function init(options) {
|
|
|
969
1247
|
}
|
|
970
1248
|
}
|
|
971
1249
|
const settingsDir = path9.join(root, ".inspecto");
|
|
972
|
-
const
|
|
973
|
-
const
|
|
1250
|
+
const settingsFileName = options.shared ? "settings.json" : "settings.local.json";
|
|
1251
|
+
const promptsFileName = options.shared ? "prompts.json" : "prompts.local.json";
|
|
1252
|
+
const settingsPath = path9.join(settingsDir, settingsFileName);
|
|
1253
|
+
const promptsPath = path9.join(settingsDir, promptsFileName);
|
|
974
1254
|
if (await exists(settingsPath)) {
|
|
975
1255
|
const existingSettings = await readJSON(settingsPath);
|
|
976
1256
|
if (existingSettings === null) {
|
|
977
|
-
log.warn(
|
|
1257
|
+
log.warn(`.inspecto/${settingsFileName} exists but contains invalid JSON`);
|
|
978
1258
|
log.hint("Please fix the syntax errors manually, or delete it and re-run init");
|
|
979
1259
|
} else {
|
|
980
|
-
log.success(
|
|
1260
|
+
log.success(`.inspecto/${settingsFileName} already exists (skipped)`);
|
|
981
1261
|
}
|
|
982
1262
|
} else {
|
|
983
1263
|
const defaultSettings = {};
|
|
984
1264
|
if (selectedIDE && selectedIDE.supported) {
|
|
985
|
-
defaultSettings.ide = selectedIDE.ide === "vscode" ? "vscode" : selectedIDE.ide;
|
|
1265
|
+
defaultSettings.ide = selectedIDE.ide.toLowerCase() === "vscode" ? "vscode" : selectedIDE.ide.toLowerCase();
|
|
986
1266
|
}
|
|
987
|
-
if (options.
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
defaultSettings.
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
};
|
|
997
|
-
}
|
|
1267
|
+
if (options.provider) {
|
|
1268
|
+
const tool = options.provider;
|
|
1269
|
+
const mode = tool === "coco" ? "cli" : "extension";
|
|
1270
|
+
defaultSettings["provider.default"] = `${tool}.${mode}`;
|
|
1271
|
+
} else if (selectedProvider) {
|
|
1272
|
+
const toolId = selectedProvider.id;
|
|
1273
|
+
const mode = selectedProvider.preferredMode === "cli" ? "cli" : "extension";
|
|
1274
|
+
defaultSettings["provider.default"] = `${toolId}.${mode}`;
|
|
998
1275
|
}
|
|
999
1276
|
if (options.dryRun) {
|
|
1000
|
-
log.dryRun(
|
|
1277
|
+
log.dryRun(`Would create .inspecto/${settingsFileName}`);
|
|
1001
1278
|
} else {
|
|
1002
1279
|
await writeJSON(settingsPath, defaultSettings);
|
|
1003
|
-
log.success(
|
|
1004
|
-
mutations.push({ type: "file_created", path:
|
|
1280
|
+
log.success(`Created .inspecto/${settingsFileName}`);
|
|
1281
|
+
mutations.push({ type: "file_created", path: `.inspecto/${settingsFileName}` });
|
|
1005
1282
|
}
|
|
1006
1283
|
}
|
|
1007
1284
|
if (await exists(promptsPath)) {
|
|
1008
|
-
log.success(
|
|
1285
|
+
log.success(`.inspecto/${promptsFileName} already exists (skipped)`);
|
|
1009
1286
|
} else {
|
|
1010
|
-
const defaultPrompts = [
|
|
1011
|
-
{ id: "code-review", enabled: false },
|
|
1012
|
-
{ id: "generate-test", enabled: false },
|
|
1013
|
-
{ id: "performance", enabled: false }
|
|
1014
|
-
];
|
|
1287
|
+
const defaultPrompts = [];
|
|
1015
1288
|
if (options.dryRun) {
|
|
1016
|
-
log.dryRun(
|
|
1289
|
+
log.dryRun(`Would create .inspecto/${promptsFileName}`);
|
|
1017
1290
|
} else {
|
|
1018
1291
|
await writeJSON(promptsPath, defaultPrompts);
|
|
1019
|
-
log.success(
|
|
1020
|
-
mutations.push({ type: "file_created", path:
|
|
1292
|
+
log.success(`Created .inspecto/${promptsFileName}`);
|
|
1293
|
+
mutations.push({ type: "file_created", path: `.inspecto/${promptsFileName}` });
|
|
1021
1294
|
}
|
|
1022
1295
|
}
|
|
1023
1296
|
if (!options.dryRun) {
|
|
@@ -1041,10 +1314,10 @@ async function init(options) {
|
|
|
1041
1314
|
const shouldInstallExt = !options.noExtension && (!selectedIDE || selectedIDE && selectedIDE.supported);
|
|
1042
1315
|
let manualExtensionInstallNeeded = false;
|
|
1043
1316
|
if (options.noExtension) {
|
|
1044
|
-
log.warn("Skipping
|
|
1317
|
+
log.warn("Skipping IDE extension (--no-extension)");
|
|
1045
1318
|
} else if (!shouldInstallExt) {
|
|
1046
1319
|
} else {
|
|
1047
|
-
const extMutation = await installExtension(options.dryRun);
|
|
1320
|
+
const extMutation = await installExtension(options.dryRun, selectedIDE?.ide);
|
|
1048
1321
|
if (extMutation && !options.dryRun) {
|
|
1049
1322
|
mutations.push(extMutation);
|
|
1050
1323
|
if (extMutation.manual_action_required) {
|
|
@@ -1063,10 +1336,16 @@ async function init(options) {
|
|
|
1063
1336
|
if (options.dryRun) {
|
|
1064
1337
|
log.blank();
|
|
1065
1338
|
log.warn("Dry run complete. No files were modified.");
|
|
1066
|
-
} else if (installFailed || injectionFailed || manualExtensionInstallNeeded) {
|
|
1339
|
+
} else if (installFailed || injectionFailed || manualExtensionInstallNeeded || manualConfigRequiredFor) {
|
|
1067
1340
|
log.blank();
|
|
1068
1341
|
log.warn("Setup completed with some manual steps required.");
|
|
1069
|
-
|
|
1342
|
+
if (manualConfigRequiredFor === "Nuxt") {
|
|
1343
|
+
printNuxtManualInstructions();
|
|
1344
|
+
} else if (manualConfigRequiredFor === "Next.js") {
|
|
1345
|
+
printNextJsManualInstructions();
|
|
1346
|
+
} else {
|
|
1347
|
+
log.hint("Please check the logs above and complete the manual steps.");
|
|
1348
|
+
}
|
|
1070
1349
|
log.blank();
|
|
1071
1350
|
} else {
|
|
1072
1351
|
log.ready("Ready! Hold Alt + Click any element to inspect.");
|
|
@@ -1084,7 +1363,14 @@ async function doctor() {
|
|
|
1084
1363
|
log.hint("Run this command from your project root");
|
|
1085
1364
|
return;
|
|
1086
1365
|
}
|
|
1087
|
-
const ideProbe = await
|
|
1366
|
+
const [ideProbe, frameworkResult, providerProbe, pm, buildResult, extInstalled] = await Promise.all([
|
|
1367
|
+
detectIDE(root),
|
|
1368
|
+
detectFrameworks(root),
|
|
1369
|
+
detectProviders(root),
|
|
1370
|
+
detectPackageManager(root),
|
|
1371
|
+
detectBuildTools(root),
|
|
1372
|
+
isExtensionInstalled()
|
|
1373
|
+
]);
|
|
1088
1374
|
if (ideProbe.detected.length === 0) {
|
|
1089
1375
|
log.warn("IDE: not detected");
|
|
1090
1376
|
result.warnings++;
|
|
@@ -1096,11 +1382,10 @@ async function doctor() {
|
|
|
1096
1382
|
);
|
|
1097
1383
|
} else {
|
|
1098
1384
|
const names = ideProbe.detected.map((d) => d.ide).join(", ");
|
|
1099
|
-
log.warn(`IDE: ${names} (not supported in v1, VS Code only)`);
|
|
1385
|
+
log.warn(`IDE: ${names} (not supported in v1, VS Code, Cursor, Trae only)`);
|
|
1100
1386
|
result.warnings++;
|
|
1101
1387
|
}
|
|
1102
1388
|
}
|
|
1103
|
-
const frameworkResult = await detectFrameworks(root);
|
|
1104
1389
|
if (frameworkResult.supported.length > 0) {
|
|
1105
1390
|
log.success(`Framework: ${frameworkResult.supported.join(", ")}`);
|
|
1106
1391
|
} else if (frameworkResult.unsupported.length > 0) {
|
|
@@ -1111,57 +1396,54 @@ async function doctor() {
|
|
|
1111
1396
|
log.warn("Framework: not detected (React / Vue expected)");
|
|
1112
1397
|
result.warnings++;
|
|
1113
1398
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
log.warn("AI Tool: none detected");
|
|
1399
|
+
if (providerProbe.detected.length === 0) {
|
|
1400
|
+
log.warn("Provider: none detected");
|
|
1117
1401
|
log.hint("Inspecto works best with Claude Code, Trae CLI, or GitHub Copilot");
|
|
1118
1402
|
result.warnings++;
|
|
1119
1403
|
} else {
|
|
1120
|
-
const aiNames =
|
|
1121
|
-
const modeLabels = d.
|
|
1122
|
-
(mode) => mode === "
|
|
1404
|
+
const aiNames = providerProbe.detected.map((d) => {
|
|
1405
|
+
const modeLabels = d.providerModes.map(
|
|
1406
|
+
(mode) => mode === "extension" ? "VS Code Extension" : "Terminal CLI"
|
|
1123
1407
|
);
|
|
1124
1408
|
return `${d.label} (${modeLabels.join(" & ")})`;
|
|
1125
1409
|
}).join(", ");
|
|
1126
|
-
log.success(`
|
|
1410
|
+
log.success(`Provider: ${aiNames}`);
|
|
1127
1411
|
}
|
|
1128
|
-
const pluginPath = path10.join(root, "node_modules", "@inspecto", "plugin");
|
|
1412
|
+
const pluginPath = path10.join(root, "node_modules", "@inspecto-dev", "plugin");
|
|
1129
1413
|
if (await exists(pluginPath)) {
|
|
1130
1414
|
const pkgJson = await readJSON(path10.join(pluginPath, "package.json"));
|
|
1131
1415
|
const version = pkgJson?.version ?? "unknown";
|
|
1132
1416
|
log.success(`@inspecto-dev/plugin@${version} installed`);
|
|
1133
1417
|
} else {
|
|
1134
1418
|
log.error("@inspecto-dev/plugin not installed");
|
|
1135
|
-
const
|
|
1136
|
-
log.hint(`Fix: ${getInstallCommand(
|
|
1419
|
+
const pm2 = await detectPackageManager(root);
|
|
1420
|
+
log.hint(`Fix: ${getInstallCommand(pm2, "@inspecto-dev/plugin")}`);
|
|
1137
1421
|
result.errors++;
|
|
1138
1422
|
}
|
|
1139
|
-
const buildResult = await detectBuildTools(root);
|
|
1140
1423
|
if (buildResult.supported.length > 0) {
|
|
1141
1424
|
let injected = false;
|
|
1142
1425
|
for (const bt of buildResult.supported) {
|
|
1143
1426
|
const content = await readFile(path10.join(root, bt.configPath));
|
|
1144
1427
|
if (content && content.includes("@inspecto-dev/plugin")) {
|
|
1145
|
-
log.success(`Plugin
|
|
1428
|
+
log.success(`Plugin configured in ${bt.configPath}`);
|
|
1146
1429
|
injected = true;
|
|
1147
1430
|
break;
|
|
1148
1431
|
}
|
|
1149
1432
|
}
|
|
1150
1433
|
if (!injected) {
|
|
1151
|
-
log.error("Plugin not
|
|
1434
|
+
log.error("Plugin not configured in any build config");
|
|
1152
1435
|
log.hint("Fix: npx @inspecto-dev/cli init");
|
|
1153
1436
|
result.errors++;
|
|
1154
1437
|
}
|
|
1155
1438
|
} else if (buildResult.unsupported.length > 0) {
|
|
1156
1439
|
const names = buildResult.unsupported.join(", ");
|
|
1157
1440
|
log.warn(`Build tool: ${names} (not supported in v1)`);
|
|
1158
|
-
log.hint("
|
|
1441
|
+
log.hint("current version supports: Vite, Webpack, Rspack, esbuild, Rollup");
|
|
1159
1442
|
result.warnings++;
|
|
1160
1443
|
} else {
|
|
1161
1444
|
log.warn("No recognized build config found");
|
|
1162
1445
|
result.warnings++;
|
|
1163
1446
|
}
|
|
1164
|
-
const extInstalled = await isExtensionInstalled();
|
|
1165
1447
|
if (extInstalled) {
|
|
1166
1448
|
log.success("VS Code extension detected");
|
|
1167
1449
|
} else {
|
|
@@ -1175,20 +1457,25 @@ async function doctor() {
|
|
|
1175
1457
|
result.errors++;
|
|
1176
1458
|
}
|
|
1177
1459
|
}
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1460
|
+
const settingsJsonPath = path10.join(root, ".inspecto", "settings.json");
|
|
1461
|
+
const settingsLocalPath = path10.join(root, ".inspecto", "settings.local.json");
|
|
1462
|
+
const hasSettingsJson = await exists(settingsJsonPath);
|
|
1463
|
+
const hasSettingsLocal = await exists(settingsLocalPath);
|
|
1464
|
+
if (hasSettingsJson || hasSettingsLocal) {
|
|
1465
|
+
const targetPath = hasSettingsLocal ? settingsLocalPath : settingsJsonPath;
|
|
1466
|
+
const fileName = hasSettingsLocal ? "settings.local.json" : "settings.json";
|
|
1467
|
+
const settings = await readJSON(targetPath);
|
|
1181
1468
|
if (settings) {
|
|
1182
|
-
log.success(
|
|
1469
|
+
log.success(`.inspecto/${fileName} valid`);
|
|
1183
1470
|
} else {
|
|
1184
|
-
log.error(
|
|
1471
|
+
log.error(`.inspecto/${fileName} has invalid JSON`);
|
|
1185
1472
|
log.hint(
|
|
1186
1473
|
"Fix: Manually correct the syntax errors, or delete the file and re-run npx @inspecto-dev/cli init"
|
|
1187
1474
|
);
|
|
1188
1475
|
result.errors++;
|
|
1189
1476
|
}
|
|
1190
1477
|
} else {
|
|
1191
|
-
log.warn(".inspecto/settings.json
|
|
1478
|
+
log.warn("No .inspecto/settings.json or settings.local.json found (using defaults)");
|
|
1192
1479
|
log.hint("Optional: npx @inspecto-dev/cli init");
|
|
1193
1480
|
result.warnings++;
|
|
1194
1481
|
}
|
|
@@ -1239,7 +1526,7 @@ async function teardown() {
|
|
|
1239
1526
|
}
|
|
1240
1527
|
await cleanGitignore(root);
|
|
1241
1528
|
log.success("Cleaned .gitignore entries");
|
|
1242
|
-
log.warn("Cannot restore build config
|
|
1529
|
+
log.warn("Cannot restore build config auto-magically");
|
|
1243
1530
|
log.hint("Please manually remove the inspecto() plugin from your build config");
|
|
1244
1531
|
log.blank();
|
|
1245
1532
|
return;
|
|
@@ -1249,24 +1536,12 @@ async function teardown() {
|
|
|
1249
1536
|
for (const mutation of lock.mutations) {
|
|
1250
1537
|
switch (mutation.type) {
|
|
1251
1538
|
case "file_modified": {
|
|
1252
|
-
if (mutation.
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
await removeFile(backupPath);
|
|
1259
|
-
} else if (await exists(backupPath)) {
|
|
1260
|
-
await copyFile(backupPath, targetPath);
|
|
1261
|
-
await removeFile(backupPath);
|
|
1262
|
-
log.success(`Restored ${mutation.path} from backup`);
|
|
1263
|
-
} else {
|
|
1264
|
-
log.warn(`Backup not found: ${mutation.backup}`);
|
|
1265
|
-
log.hint(`Please manually remove inspecto from ${mutation.path}`);
|
|
1266
|
-
}
|
|
1267
|
-
} else if (mutation.path && mutation.path !== ".gitignore") {
|
|
1268
|
-
log.warn(`Cannot auto-restore ${mutation.path} (no backup recorded)`);
|
|
1269
|
-
log.hint(`Please manually remove the inspecto() plugin from ${mutation.path}`);
|
|
1539
|
+
if (!mutation.path) break;
|
|
1540
|
+
if (mutation.path === ".gitignore") {
|
|
1541
|
+
log.success("Cleaned .gitignore entries");
|
|
1542
|
+
} else if (mutation.path) {
|
|
1543
|
+
log.warn(`Cannot auto-restore ${mutation.path}`);
|
|
1544
|
+
log.hint(`Please manually remove the inspecto plugin from ${mutation.path}`);
|
|
1270
1545
|
}
|
|
1271
1546
|
break;
|
|
1272
1547
|
}
|