@inspecto-dev/cli 0.2.0-alpha.4 → 0.2.0-alpha.6
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 +8 -8
- package/.turbo/turbo-test.log +16 -21
- package/CHANGELOG.md +12 -0
- package/README.md +58 -11
- package/bin/inspecto.js +5 -1
- package/dist/bin.d.ts +5 -1
- package/dist/bin.js +89 -50
- package/dist/{chunk-EUCQCD3Y.js → chunk-PDDFPQJS.js} +1954 -1053
- package/dist/index.d.ts +128 -2
- package/dist/index.js +15 -3
- package/package.json +2 -1
- package/src/bin.ts +139 -67
- package/src/commands/apply.ts +114 -0
- package/src/commands/detect.ts +59 -0
- package/src/commands/doctor.ts +225 -72
- package/src/commands/init.ts +106 -183
- package/src/commands/plan.ts +41 -0
- package/src/detect/build-tool.ts +107 -3
- package/src/index.ts +13 -2
- package/src/inject/ast-injector.ts +20 -9
- package/src/inject/extension.ts +3 -1
- package/src/inject/strategies/vite.ts +2 -1
- package/src/instructions.ts +60 -46
- package/src/onboarding/apply.ts +325 -0
- package/src/onboarding/context.ts +36 -0
- package/src/onboarding/planner.ts +278 -0
- package/src/prompts.ts +54 -11
- package/src/types.ts +95 -0
- package/src/utils/fs.ts +2 -1
- package/src/utils/logger.ts +9 -0
- package/src/utils/output.ts +40 -0
- package/tests/apply.test.ts +537 -0
- package/tests/ast-injector.test.ts +50 -0
- package/tests/build-tool.test.ts +3 -5
- package/tests/detect.test.ts +94 -0
- package/tests/doctor.test.ts +224 -0
- package/tests/init.test.ts +333 -0
- package/tests/instructions.test.ts +61 -0
- package/tests/logger.test.ts +100 -0
- package/tests/plan.test.ts +713 -0
- package/tests/workspace-build-tool.test.ts +75 -0
|
@@ -46,14 +46,55 @@ var log = {
|
|
|
46
46
|
console.log(` ${pc.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}`);
|
|
47
47
|
console.log();
|
|
48
48
|
},
|
|
49
|
+
/** Copy-friendly code block without box characters */
|
|
50
|
+
copyableCodeBlock(lines) {
|
|
51
|
+
console.log();
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
console.log(` ${line}`);
|
|
54
|
+
}
|
|
55
|
+
console.log();
|
|
56
|
+
},
|
|
49
57
|
/** Dry-run prefix */
|
|
50
58
|
dryRun(text) {
|
|
51
59
|
console.log(` ${pc.blue("[dry-run]")} ${text}`);
|
|
52
60
|
}
|
|
53
61
|
};
|
|
54
62
|
|
|
55
|
-
// src/
|
|
56
|
-
|
|
63
|
+
// src/utils/output.ts
|
|
64
|
+
function writeCommandOutput(result, json, renderText) {
|
|
65
|
+
if (json) {
|
|
66
|
+
console.log(JSON.stringify(result, null, 2));
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
renderText(result);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function reportCommandError(error, options = {}) {
|
|
73
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
74
|
+
const stack = error instanceof Error ? error.stack : void 0;
|
|
75
|
+
if (options.json) {
|
|
76
|
+
const payload = {
|
|
77
|
+
status: "error",
|
|
78
|
+
error: {
|
|
79
|
+
message: message2,
|
|
80
|
+
...options.debug && stack ? { stack } : {}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
console.error(JSON.stringify(payload, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
log.error(message2);
|
|
87
|
+
if (options.debug && stack) {
|
|
88
|
+
console.error(stack);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/onboarding/apply.ts
|
|
93
|
+
import path5 from "path";
|
|
94
|
+
import ora from "ora";
|
|
95
|
+
|
|
96
|
+
// src/detect/package-manager.ts
|
|
97
|
+
import path2 from "path";
|
|
57
98
|
|
|
58
99
|
// src/utils/fs.ts
|
|
59
100
|
import fs from "fs/promises";
|
|
@@ -85,7 +126,8 @@ async function removeDir(dirPath) {
|
|
|
85
126
|
}
|
|
86
127
|
async function readJSON(filePath) {
|
|
87
128
|
const text = await readFile(filePath);
|
|
88
|
-
if (
|
|
129
|
+
if (text === null) return null;
|
|
130
|
+
if (text.trim() === "") return {};
|
|
89
131
|
try {
|
|
90
132
|
return JSON.parse(text);
|
|
91
133
|
} catch {
|
|
@@ -96,39 +138,7 @@ async function writeJSON(filePath, data) {
|
|
|
96
138
|
await writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
97
139
|
}
|
|
98
140
|
|
|
99
|
-
// src/utils/exec.ts
|
|
100
|
-
import { execFile, exec as execCb } from "child_process";
|
|
101
|
-
import { promisify } from "util";
|
|
102
|
-
var execFileAsync = promisify(execFile);
|
|
103
|
-
var execAsync = promisify(execCb);
|
|
104
|
-
async function run(command, args, cwd) {
|
|
105
|
-
const result = await execFileAsync(command, args, {
|
|
106
|
-
cwd,
|
|
107
|
-
timeout: 6e4,
|
|
108
|
-
env: { ...process.env }
|
|
109
|
-
});
|
|
110
|
-
return { stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
111
|
-
}
|
|
112
|
-
async function shell(command, cwd) {
|
|
113
|
-
const result = await execAsync(command, {
|
|
114
|
-
cwd,
|
|
115
|
-
timeout: 6e4,
|
|
116
|
-
env: { ...process.env }
|
|
117
|
-
});
|
|
118
|
-
return { stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
119
|
-
}
|
|
120
|
-
async function which(bin) {
|
|
121
|
-
try {
|
|
122
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
123
|
-
await run(cmd, [bin]);
|
|
124
|
-
return true;
|
|
125
|
-
} catch {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
141
|
// src/detect/package-manager.ts
|
|
131
|
-
import path2 from "path";
|
|
132
142
|
async function detectPackageManager(root) {
|
|
133
143
|
const checks = [
|
|
134
144
|
["bun.lockb", "bun"],
|
|
@@ -175,262 +185,700 @@ function getUninstallCommand(pm, pkg) {
|
|
|
175
185
|
}
|
|
176
186
|
}
|
|
177
187
|
|
|
178
|
-
// src/
|
|
188
|
+
// src/inject/ast-injector.ts
|
|
179
189
|
import path3 from "path";
|
|
180
|
-
import {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
require2.resolve(pkgName, { paths: [root] });
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
} catch {
|
|
192
|
-
return false;
|
|
190
|
+
import { loadFile, writeFile as writeAstFile } from "magicast";
|
|
191
|
+
|
|
192
|
+
// src/inject/strategies/vite.ts
|
|
193
|
+
import { addVitePlugin } from "magicast/helpers";
|
|
194
|
+
var ViteStrategy = class {
|
|
195
|
+
name = "Vite";
|
|
196
|
+
supports(tool) {
|
|
197
|
+
return tool === "vite";
|
|
193
198
|
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return pkg?.version || null;
|
|
201
|
-
} catch {
|
|
202
|
-
return null;
|
|
199
|
+
inject({ mod, detection }) {
|
|
200
|
+
addVitePlugin(mod, {
|
|
201
|
+
from: "@inspecto-dev/plugin",
|
|
202
|
+
constructor: "inspecto",
|
|
203
|
+
imported: "vitePlugin"
|
|
204
|
+
});
|
|
203
205
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"vite.config.cts"
|
|
215
|
-
],
|
|
216
|
-
label: "Vite"
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
tool: "rspack",
|
|
220
|
-
files: ["rspack.config.js", "rspack.config.ts", "rspack.config.mjs"],
|
|
221
|
-
label: "Rspack"
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
tool: "rsbuild",
|
|
225
|
-
files: ["rsbuild.config.js", "rsbuild.config.ts", "rsbuild.config.mjs"],
|
|
226
|
-
label: "Rsbuild"
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
tool: "webpack",
|
|
230
|
-
files: ["webpack.config.js", "webpack.config.ts", "webpack.config.mjs", "webpack.config.cjs"],
|
|
231
|
-
label: "Webpack"
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
tool: "esbuild",
|
|
235
|
-
files: ["esbuild.config.js", "esbuild.config.ts", "esbuild.config.mjs", "build.js", "build.ts"],
|
|
236
|
-
label: "esbuild"
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
tool: "rollup",
|
|
240
|
-
files: ["rollup.config.js", "rollup.config.ts", "rollup.config.mjs"],
|
|
241
|
-
label: "Rollup"
|
|
206
|
+
getManualInstructions(detection, reason) {
|
|
207
|
+
return [
|
|
208
|
+
`import { vitePlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
209
|
+
"",
|
|
210
|
+
"// Add to your plugins array:",
|
|
211
|
+
`plugins: [`,
|
|
212
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
213
|
+
` ...otherPlugins`,
|
|
214
|
+
`].filter(Boolean)`
|
|
215
|
+
];
|
|
242
216
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
{
|
|
249
|
-
|
|
250
|
-
];
|
|
251
|
-
function normalizeRelativePath(root, filePath) {
|
|
252
|
-
const relative = path3.relative(root, filePath);
|
|
253
|
-
const normalized = relative.split(path3.sep).join("/");
|
|
254
|
-
return normalized || path3.basename(filePath);
|
|
255
|
-
}
|
|
256
|
-
function createTargets(root, packagePaths) {
|
|
257
|
-
if (!packagePaths || packagePaths.length === 0) {
|
|
258
|
-
return [{ packagePath: "", absolutePath: root }];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// src/inject/strategies/webpack.ts
|
|
220
|
+
var WebpackStrategy = class {
|
|
221
|
+
name = "Webpack";
|
|
222
|
+
supports(tool) {
|
|
223
|
+
return tool === "webpack";
|
|
259
224
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
absolutePath: pkg ? path3.join(root, pkg) : root
|
|
263
|
-
}));
|
|
264
|
-
}
|
|
265
|
-
async function detectBuildTools(root, packagePaths) {
|
|
266
|
-
const supported = [];
|
|
267
|
-
const unsupported = /* @__PURE__ */ new Set();
|
|
268
|
-
const targets = createTargets(root, packagePaths);
|
|
269
|
-
for (const target of targets) {
|
|
270
|
-
const pkg = await readJSON(path3.join(target.absolutePath, "package.json"));
|
|
271
|
-
const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
272
|
-
const scripts = pkg?.scripts || {};
|
|
273
|
-
const supportedChecks = SUPPORTED_PATTERNS.map(
|
|
274
|
-
(pattern) => detectPattern({
|
|
275
|
-
pattern,
|
|
276
|
-
workspaceRoot: root,
|
|
277
|
-
targetRoot: target.absolutePath,
|
|
278
|
-
packagePath: target.packagePath,
|
|
279
|
-
allDeps,
|
|
280
|
-
scripts
|
|
281
|
-
})
|
|
282
|
-
);
|
|
283
|
-
const supportedResults = await Promise.all(supportedChecks);
|
|
284
|
-
for (const result of supportedResults) {
|
|
285
|
-
if (result) {
|
|
286
|
-
supported.push(result);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const unsupportedChecks = UNSUPPORTED_META.map(async (meta) => {
|
|
290
|
-
if (!(meta.dep in allDeps)) return null;
|
|
291
|
-
for (const file of meta.files) {
|
|
292
|
-
if (await exists(path3.join(target.absolutePath, file))) {
|
|
293
|
-
return meta.name;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return null;
|
|
297
|
-
});
|
|
298
|
-
const unsupportedResults = await Promise.all(unsupportedChecks);
|
|
299
|
-
for (const result of unsupportedResults) {
|
|
300
|
-
if (result) {
|
|
301
|
-
unsupported.add(result);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
225
|
+
inject(options) {
|
|
226
|
+
throw new Error("Webpack requires manual plugin configuration");
|
|
304
227
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("@rspack/core", targetRoot);
|
|
322
|
-
}
|
|
323
|
-
} else if (pattern.tool === "webpack") {
|
|
324
|
-
const depName = allDeps["webpack"] ? "webpack" : "webpack-cli";
|
|
325
|
-
hasDep = !!allDeps["webpack"] || !!allDeps["webpack-cli"] || isPackageResolvable("webpack", targetRoot);
|
|
326
|
-
if (hasDep) {
|
|
327
|
-
resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("webpack", targetRoot);
|
|
328
|
-
}
|
|
329
|
-
} else if (pattern.tool === "rsbuild") {
|
|
330
|
-
hasDep = !!allDeps["@rsbuild/core"] || isPackageResolvable("@rsbuild/core", targetRoot);
|
|
331
|
-
} else {
|
|
332
|
-
hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, targetRoot);
|
|
228
|
+
getManualInstructions(detection, reason) {
|
|
229
|
+
const importPkg = detection.isLegacyWebpack ? "@inspecto-dev/plugin/legacy/webpack4" : "@inspecto-dev/plugin";
|
|
230
|
+
const pluginName = detection.isLegacyWebpack ? "webpack4Plugin" : "webpackPlugin";
|
|
231
|
+
const pluginCall = detection.isLegacyWebpack ? `process.env.NODE_ENV !== 'production' && inspecto({
|
|
232
|
+
pathType: 'absolute',
|
|
233
|
+
escapeTags: ['Transition', 'AnimatePresence'],
|
|
234
|
+
})` : `process.env.NODE_ENV !== 'production' && inspecto()`;
|
|
235
|
+
return [
|
|
236
|
+
`import { ${pluginName} as inspecto } from '${importPkg}'`,
|
|
237
|
+
"",
|
|
238
|
+
"// Add to your plugins array:",
|
|
239
|
+
`plugins: [`,
|
|
240
|
+
` ${pluginCall},`,
|
|
241
|
+
` ...otherPlugins`,
|
|
242
|
+
`].filter(Boolean)`
|
|
243
|
+
];
|
|
333
244
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/inject/strategies/rspack.ts
|
|
248
|
+
var RspackStrategy = class {
|
|
249
|
+
name = "Rspack";
|
|
250
|
+
supports(tool) {
|
|
251
|
+
return tool === "rspack";
|
|
337
252
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
detectedFile = file;
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
253
|
+
inject(options) {
|
|
254
|
+
throw new Error("Rspack requires manual plugin configuration");
|
|
343
255
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
256
|
+
getManualInstructions(detection, reason) {
|
|
257
|
+
const importPkg = detection.isLegacyRspack ? "@inspecto-dev/plugin/legacy/rspack" : "@inspecto-dev/plugin";
|
|
258
|
+
const pluginCall = detection.isLegacyRspack ? `process.env.NODE_ENV !== 'production' && inspecto({
|
|
259
|
+
pathType: 'absolute',
|
|
260
|
+
escapeTags: ['Transition', 'AnimatePresence'],
|
|
261
|
+
})` : `process.env.NODE_ENV !== 'production' && inspecto()`;
|
|
262
|
+
return [
|
|
263
|
+
`import { rspackPlugin as inspecto } from '${importPkg}'`,
|
|
264
|
+
"",
|
|
265
|
+
"// Add to your plugins array:",
|
|
266
|
+
`plugins: [`,
|
|
267
|
+
` ${pluginCall},`,
|
|
268
|
+
` ...otherPlugins`,
|
|
269
|
+
`].filter(Boolean)`
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/inject/strategies/rsbuild.ts
|
|
275
|
+
var RsbuildStrategy = class {
|
|
276
|
+
name = "Rsbuild";
|
|
277
|
+
supports(tool) {
|
|
278
|
+
return tool === "rsbuild";
|
|
279
|
+
}
|
|
280
|
+
inject(options) {
|
|
281
|
+
throw new Error("Rsbuild requires manual plugin configuration due to nested structure");
|
|
282
|
+
}
|
|
283
|
+
getManualInstructions(detection, reason) {
|
|
284
|
+
return [
|
|
285
|
+
`import { rspackPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
286
|
+
"",
|
|
287
|
+
"// Add to tools.rspack:",
|
|
288
|
+
`tools: {`,
|
|
289
|
+
` rspack: {`,
|
|
290
|
+
` plugins: [`,
|
|
291
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
292
|
+
` ]`,
|
|
293
|
+
` }`,
|
|
294
|
+
`}`
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/inject/strategies/esbuild.ts
|
|
300
|
+
var EsbuildStrategy = class {
|
|
301
|
+
name = "esbuild";
|
|
302
|
+
supports(tool) {
|
|
303
|
+
return tool === "esbuild";
|
|
304
|
+
}
|
|
305
|
+
inject(options) {
|
|
306
|
+
throw new Error("Esbuild requires manual plugin configuration");
|
|
307
|
+
}
|
|
308
|
+
getManualInstructions(detection, reason) {
|
|
309
|
+
return [
|
|
310
|
+
`1. Update your esbuild config (${detection.configPath}):`,
|
|
311
|
+
`import { esbuildPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
312
|
+
"",
|
|
313
|
+
"// Add to your plugins array:",
|
|
314
|
+
`plugins: [`,
|
|
315
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
316
|
+
` ...otherPlugins`,
|
|
317
|
+
`].filter(Boolean)`,
|
|
318
|
+
"",
|
|
319
|
+
"2. Initialize the client in your app entry (e.g., main.js / index.js):",
|
|
320
|
+
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
321
|
+
"",
|
|
322
|
+
"// Call this before your app renders",
|
|
323
|
+
`if (process.env.NODE_ENV !== 'production') {`,
|
|
324
|
+
` mountInspector()`,
|
|
325
|
+
`}`
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/inject/strategies/rollup.ts
|
|
331
|
+
var RollupStrategy = class {
|
|
332
|
+
name = "Rollup";
|
|
333
|
+
supports(tool) {
|
|
334
|
+
return tool === "rollup";
|
|
335
|
+
}
|
|
336
|
+
inject(options) {
|
|
337
|
+
throw new Error("Rollup requires manual plugin configuration");
|
|
338
|
+
}
|
|
339
|
+
getManualInstructions(detection, reason) {
|
|
340
|
+
return [
|
|
341
|
+
`1. Update your rollup config (${detection.configPath}):`,
|
|
342
|
+
`import { rollupPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
343
|
+
"",
|
|
344
|
+
"// Add to your plugins array:",
|
|
345
|
+
`plugins: [`,
|
|
346
|
+
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
347
|
+
` ...otherPlugins`,
|
|
348
|
+
`].filter(Boolean)`,
|
|
349
|
+
"",
|
|
350
|
+
"2. Initialize the client in your app entry (e.g., main.js / index.js):",
|
|
351
|
+
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
352
|
+
"",
|
|
353
|
+
"// Call this before your app renders",
|
|
354
|
+
`if (process.env.NODE_ENV !== 'production') {`,
|
|
355
|
+
` mountInspector()`,
|
|
356
|
+
`}`
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/inject/strategies/index.ts
|
|
362
|
+
var STRATEGIES = [
|
|
363
|
+
new ViteStrategy(),
|
|
364
|
+
new WebpackStrategy(),
|
|
365
|
+
new RspackStrategy(),
|
|
366
|
+
new RsbuildStrategy(),
|
|
367
|
+
new EsbuildStrategy(),
|
|
368
|
+
new RollupStrategy()
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
// src/inject/ast-injector.ts
|
|
372
|
+
function printManualInstructions(strategy, detection, reason) {
|
|
373
|
+
log.warn(`Could not automatically configure ${detection.configPath}`);
|
|
374
|
+
log.hint(`(reason: ${reason})`);
|
|
375
|
+
log.blank();
|
|
376
|
+
log.hint("Please add the following manually:");
|
|
377
|
+
if (strategy) {
|
|
378
|
+
const instructions = strategy.getManualInstructions(detection, reason);
|
|
379
|
+
log.copyableCodeBlock(instructions);
|
|
380
|
+
} else {
|
|
381
|
+
log.error(`Unsupported build tool: ${detection.tool}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function isAlreadyInjected(content) {
|
|
385
|
+
const normalized = content.replace(/\s+/g, " ");
|
|
386
|
+
const importPlugin = /import\s+(.+?)\s+from\s+['"]@inspecto-dev\/plugin['"]/g;
|
|
387
|
+
const requirePlugin = /require\(['"]@inspecto-dev\/plugin['"]\)/;
|
|
388
|
+
const legacyImport = /import\s+.*ai-dev-inspector/.test(normalized);
|
|
389
|
+
const legacyRequire = /require\(['"]ai-dev-inspector['"]\)/.test(normalized);
|
|
390
|
+
if (legacyImport || legacyRequire || requirePlugin.test(normalized)) return true;
|
|
391
|
+
let match;
|
|
392
|
+
importPlugin.lastIndex = 0;
|
|
393
|
+
while (match = importPlugin.exec(normalized)) {
|
|
394
|
+
const importClause = match[1] || "";
|
|
395
|
+
if (/inspecto/.test(importClause) || /vitePlugin/.test(importClause)) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
async function injectPlugin(root, detection, dryRun) {
|
|
402
|
+
const configPath = path3.join(root, detection.configPath);
|
|
403
|
+
const mutations = [];
|
|
404
|
+
const strategy = STRATEGIES.find((s) => s.supports(detection.tool));
|
|
405
|
+
const content = await readFile(configPath);
|
|
406
|
+
if (!content) {
|
|
407
|
+
printManualInstructions(strategy, detection, "config file not readable");
|
|
408
|
+
return { success: false, mutations, failureReason: "config file not readable" };
|
|
409
|
+
}
|
|
410
|
+
if (isAlreadyInjected(content)) {
|
|
411
|
+
log.success(`Plugin already configured in ${detection.configPath} (skipped)`);
|
|
412
|
+
mutations.push({
|
|
413
|
+
type: "file_modified",
|
|
414
|
+
path: detection.configPath,
|
|
415
|
+
description: "Previously configured inspecto() plugin"
|
|
416
|
+
});
|
|
417
|
+
return { success: true, mutations };
|
|
418
|
+
}
|
|
419
|
+
if (!strategy) {
|
|
420
|
+
printManualInstructions(
|
|
421
|
+
strategy,
|
|
422
|
+
detection,
|
|
423
|
+
`No injection strategy found for ${detection.tool}`
|
|
424
|
+
);
|
|
425
|
+
return { success: false, mutations, failureReason: "No strategy found" };
|
|
426
|
+
}
|
|
427
|
+
if (dryRun) {
|
|
428
|
+
log.dryRun(`Would automatically configure plugin in ${detection.configPath}`);
|
|
429
|
+
return { success: true, mutations: [] };
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
const mod = await loadFile(configPath);
|
|
433
|
+
strategy.inject({
|
|
434
|
+
mod,
|
|
435
|
+
detection
|
|
436
|
+
});
|
|
437
|
+
await writeAstFile(mod, configPath);
|
|
438
|
+
mutations.push({
|
|
439
|
+
type: "file_modified",
|
|
440
|
+
path: detection.configPath,
|
|
441
|
+
description: "Automatically configured inspecto() plugin"
|
|
442
|
+
});
|
|
443
|
+
log.success(`Configured plugin in ${detection.configPath}`);
|
|
444
|
+
return { success: true, mutations };
|
|
445
|
+
} catch (err) {
|
|
446
|
+
printManualInstructions(
|
|
447
|
+
strategy,
|
|
448
|
+
detection,
|
|
449
|
+
`Automatic configuration unavailable: ${err instanceof Error ? err.message : String(err)}`
|
|
450
|
+
);
|
|
451
|
+
return {
|
|
452
|
+
success: false,
|
|
453
|
+
mutations,
|
|
454
|
+
failureReason: "Automatic configuration unavailable"
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/utils/exec.ts
|
|
460
|
+
import { execFile, exec as execCb } from "child_process";
|
|
461
|
+
import { promisify } from "util";
|
|
462
|
+
var execFileAsync = promisify(execFile);
|
|
463
|
+
var execAsync = promisify(execCb);
|
|
464
|
+
async function run(command, args, cwd) {
|
|
465
|
+
const result = await execFileAsync(command, args, {
|
|
466
|
+
cwd,
|
|
467
|
+
timeout: 6e4,
|
|
468
|
+
env: { ...process.env }
|
|
469
|
+
});
|
|
470
|
+
return { stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
471
|
+
}
|
|
472
|
+
async function shell(command, cwd) {
|
|
473
|
+
const result = await execAsync(command, {
|
|
474
|
+
cwd,
|
|
475
|
+
timeout: 6e4,
|
|
476
|
+
env: { ...process.env }
|
|
477
|
+
});
|
|
478
|
+
return { stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
479
|
+
}
|
|
480
|
+
async function which(bin) {
|
|
481
|
+
try {
|
|
482
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
483
|
+
await run(cmd, [bin]);
|
|
484
|
+
return true;
|
|
485
|
+
} catch {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/inject/extension.ts
|
|
491
|
+
var EXTENSION_ID = "inspecto.inspecto";
|
|
492
|
+
var VSCODE_PATHS = {
|
|
493
|
+
darwin: [
|
|
494
|
+
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
495
|
+
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders",
|
|
496
|
+
`${process.env.HOME}/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code`
|
|
497
|
+
],
|
|
498
|
+
linux: ["/usr/bin/code", "/usr/share/code/bin/code", "/snap/bin/code", "/usr/bin/code-insiders"],
|
|
499
|
+
win32: [
|
|
500
|
+
`${process.env.LOCALAPPDATA}\\Programs\\Microsoft VS Code\\bin\\code.cmd`,
|
|
501
|
+
`${process.env.LOCALAPPDATA}\\Programs\\Microsoft VS Code Insiders\\bin\\code-insiders.cmd`,
|
|
502
|
+
`${process.env.PROGRAMFILES}\\Microsoft VS Code\\bin\\code.cmd`
|
|
503
|
+
]
|
|
504
|
+
};
|
|
505
|
+
async function findVSCodeBinary() {
|
|
506
|
+
const platform = process.platform;
|
|
507
|
+
const candidates = VSCODE_PATHS[platform] || [];
|
|
508
|
+
for (const candidate of candidates) {
|
|
509
|
+
if (await exists(candidate)) {
|
|
510
|
+
return candidate;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (await which("code-insiders")) {
|
|
514
|
+
return "code-insiders";
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
async function tryOpenURI(uri) {
|
|
519
|
+
try {
|
|
520
|
+
const platform = process.platform;
|
|
521
|
+
if (platform === "win32") {
|
|
522
|
+
await shell(`cmd /c start "" "${uri}"`);
|
|
523
|
+
} else {
|
|
524
|
+
const openCmd = platform === "darwin" ? "open" : "xdg-open";
|
|
525
|
+
await shell(`${openCmd} "${uri}"`);
|
|
526
|
+
}
|
|
527
|
+
return true;
|
|
528
|
+
} catch {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function installExtension(dryRun, ide) {
|
|
533
|
+
if (dryRun) {
|
|
534
|
+
log.dryRun("Would attempt to install VS Code extension");
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
const isVSCode = !ide || ide === "vscode";
|
|
538
|
+
if (isVSCode) {
|
|
539
|
+
if (await which("code")) {
|
|
540
|
+
try {
|
|
541
|
+
await run("code", ["--install-extension", EXTENSION_ID]);
|
|
542
|
+
log.success("VS Code extension installed via CLI");
|
|
543
|
+
return { type: "extension_installed", id: EXTENSION_ID };
|
|
544
|
+
} catch {
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const codePath = await findVSCodeBinary();
|
|
548
|
+
if (codePath) {
|
|
549
|
+
try {
|
|
550
|
+
await run(codePath, ["--install-extension", EXTENSION_ID]);
|
|
551
|
+
log.success("VS Code extension installed via binary path");
|
|
552
|
+
log.info(
|
|
553
|
+
'Tip: Add "code" to your PATH to help Inspecto detect other AI tools in the future'
|
|
554
|
+
);
|
|
555
|
+
return { type: "extension_installed", id: EXTENSION_ID };
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const uri = `vscode:extension/${EXTENSION_ID}`;
|
|
560
|
+
if (await tryOpenURI(uri)) {
|
|
561
|
+
log.warn("Opened extension page in VS Code");
|
|
562
|
+
log.hint('Please click "Install" in the opened VS Code window to complete setup.');
|
|
563
|
+
return { type: "extension_installed", id: EXTENSION_ID, manual_action_required: true };
|
|
564
|
+
}
|
|
565
|
+
log.warn("Could not auto-install VS Code extension");
|
|
566
|
+
log.hint("Please install it manually to enable Inspector features:");
|
|
567
|
+
log.hint(" 1. Open VS Code");
|
|
568
|
+
log.hint(" 2. Press Ctrl+Shift+X (or Cmd+Shift+X)");
|
|
569
|
+
log.hint(' 3. Search for "Inspecto"');
|
|
570
|
+
log.hint(` Or visit: https://marketplace.visualstudio.com/items?itemName=${EXTENSION_ID}`);
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
log.warn(`Could not auto-install extension for ${ide}`);
|
|
574
|
+
log.hint("Please install it manually to enable Inspector features:");
|
|
575
|
+
log.hint(" 1. Download the latest .vsix file (Open VSX: https://open-vsx.org/extension/inspecto/inspecto)");
|
|
576
|
+
log.hint(` 2. Open ${ide}`);
|
|
577
|
+
log.hint(" 3. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)");
|
|
578
|
+
log.hint(' 4. Type and select "Extensions: Install from VSIX..."');
|
|
579
|
+
log.hint(" 5. Select the downloaded .vsix file");
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
async function isExtensionInstalled() {
|
|
583
|
+
try {
|
|
584
|
+
if (await which("code")) {
|
|
585
|
+
const { stdout } = await run("code", ["--list-extensions"]);
|
|
586
|
+
return stdout.toLowerCase().includes(EXTENSION_ID);
|
|
587
|
+
}
|
|
588
|
+
const codePath = await findVSCodeBinary();
|
|
589
|
+
if (codePath) {
|
|
590
|
+
const { stdout } = await run(codePath, ["--list-extensions"]);
|
|
591
|
+
return stdout.toLowerCase().includes(EXTENSION_ID);
|
|
592
|
+
}
|
|
593
|
+
return false;
|
|
594
|
+
} catch {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/inject/gitignore.ts
|
|
600
|
+
import path4 from "path";
|
|
601
|
+
var DEFAULT_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
|
|
602
|
+
var SHARED_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
|
|
603
|
+
async function updateGitignore(root, shared, dryRun) {
|
|
604
|
+
const gitignorePath = path4.join(root, ".gitignore");
|
|
605
|
+
let content = await readFile(gitignorePath) ?? "";
|
|
606
|
+
const desiredRules = shared ? SHARED_RULES : DEFAULT_RULES;
|
|
607
|
+
const hasGlobalRule = content.match(/^\.inspecto\/\s*$/m) !== null;
|
|
608
|
+
if (hasGlobalRule) {
|
|
609
|
+
content = content.replace(/^\.inspecto\/\s*$/gm, SHARED_RULES.join("\n"));
|
|
610
|
+
if (!dryRun) {
|
|
611
|
+
await writeFile(gitignorePath, content);
|
|
612
|
+
}
|
|
613
|
+
log.success("Updated .gitignore: .inspecto/ is no longer fully ignored");
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const missingRules = desiredRules.filter((rule) => !content.includes(rule));
|
|
617
|
+
if (missingRules.length === 0) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const section = "\n# Inspecto\n" + missingRules.join("\n") + "\n";
|
|
621
|
+
content = content.trimEnd() + "\n" + section;
|
|
622
|
+
if (dryRun) {
|
|
623
|
+
log.dryRun(`Would update .gitignore with: ${missingRules.join(", ")}`);
|
|
624
|
+
} else {
|
|
625
|
+
await writeFile(gitignorePath, content);
|
|
626
|
+
log.success("Updated .gitignore");
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async function cleanGitignore(root) {
|
|
630
|
+
const gitignorePath = path4.join(root, ".gitignore");
|
|
631
|
+
const content = await readFile(gitignorePath);
|
|
632
|
+
if (!content) return;
|
|
633
|
+
const cleaned = content.replace(/^# Inspecto\s*$/m, "").replace(/^\.inspecto\/?\s*$/gm, "").replace(/^\.inspecto\/install\.lock\s*$/gm, "").replace(/^\.inspecto\/cache\.json\s*$/gm, "").replace(/^\.inspecto\/\*\.local\.json\s*$/gm, "").replace(/\n{3,}/g, "\n\n");
|
|
634
|
+
await writeFile(gitignorePath, cleaned);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/onboarding/apply.ts
|
|
638
|
+
function resultStatus(nextSteps) {
|
|
639
|
+
return nextSteps.length > 0 ? "warning" : "ok";
|
|
640
|
+
}
|
|
641
|
+
function manualPlanSteps(plan2) {
|
|
642
|
+
return [
|
|
643
|
+
...plan2.blockers.map((blocker) => blocker.message),
|
|
644
|
+
...plan2.actions.filter((action) => action.type === "manual_step").map((action) => action.description)
|
|
645
|
+
];
|
|
646
|
+
}
|
|
647
|
+
async function applyOnboardingPlan(input) {
|
|
648
|
+
return applyOnboardingPlanInternal(input);
|
|
649
|
+
}
|
|
650
|
+
function createReporter(quiet = false) {
|
|
651
|
+
if (quiet) {
|
|
652
|
+
return {
|
|
653
|
+
warn() {
|
|
654
|
+
},
|
|
655
|
+
success() {
|
|
656
|
+
},
|
|
657
|
+
error() {
|
|
658
|
+
},
|
|
659
|
+
hint() {
|
|
660
|
+
},
|
|
661
|
+
dryRun() {
|
|
370
662
|
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
warn(text) {
|
|
667
|
+
log.warn(text);
|
|
668
|
+
},
|
|
669
|
+
success(text) {
|
|
670
|
+
log.success(text);
|
|
671
|
+
},
|
|
672
|
+
error(text) {
|
|
673
|
+
log.error(text);
|
|
674
|
+
},
|
|
675
|
+
hint(text) {
|
|
676
|
+
log.hint(text);
|
|
677
|
+
},
|
|
678
|
+
dryRun(text) {
|
|
679
|
+
log.dryRun(text);
|
|
371
680
|
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function createSpinner(text, quiet = false) {
|
|
684
|
+
if (quiet) {
|
|
685
|
+
return {
|
|
686
|
+
start() {
|
|
687
|
+
},
|
|
688
|
+
succeed() {
|
|
689
|
+
},
|
|
690
|
+
fail() {
|
|
691
|
+
}
|
|
692
|
+
};
|
|
372
693
|
}
|
|
373
|
-
|
|
374
|
-
|
|
694
|
+
const spinner = ora(text);
|
|
695
|
+
return {
|
|
696
|
+
start() {
|
|
697
|
+
spinner.start();
|
|
698
|
+
},
|
|
699
|
+
succeed(successText) {
|
|
700
|
+
spinner.succeed(successText);
|
|
701
|
+
},
|
|
702
|
+
fail(failureText) {
|
|
703
|
+
spinner.fail(failureText);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
async function applyOnboardingPlanInternal(input) {
|
|
708
|
+
const reporter = createReporter(input.options.quiet);
|
|
709
|
+
if (input.plan && input.plan.strategy !== "supported" && !input.allowManualPlanApply) {
|
|
710
|
+
return {
|
|
711
|
+
status: input.plan.status,
|
|
712
|
+
mutations: [],
|
|
713
|
+
postInstall: {
|
|
714
|
+
installFailed: false,
|
|
715
|
+
injectionFailed: false,
|
|
716
|
+
manualExtensionInstallNeeded: false,
|
|
717
|
+
nextSteps: manualPlanSteps(input.plan)
|
|
718
|
+
}
|
|
719
|
+
};
|
|
375
720
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
721
|
+
const mutations = [];
|
|
722
|
+
const settingsDir = path5.join(input.projectRoot, ".inspecto");
|
|
723
|
+
const settingsFileName = input.options.shared ? "settings.json" : "settings.local.json";
|
|
724
|
+
const promptsFileName = input.options.shared ? "prompts.json" : "prompts.local.json";
|
|
725
|
+
const settingsPath = path5.join(settingsDir, settingsFileName);
|
|
726
|
+
const promptsPath = path5.join(settingsDir, promptsFileName);
|
|
727
|
+
const installCmd = getInstallCommand(
|
|
728
|
+
input.packageManager,
|
|
729
|
+
"@inspecto-dev/plugin @inspecto-dev/core"
|
|
730
|
+
);
|
|
731
|
+
const nextSteps = [];
|
|
732
|
+
let installFailed = false;
|
|
733
|
+
if (input.options.skipInstall) {
|
|
734
|
+
reporter.warn("Skipping dependency installation (--skip-install)");
|
|
735
|
+
} else if (input.options.dryRun) {
|
|
736
|
+
reporter.dryRun(`Would run: ${installCmd}`);
|
|
737
|
+
} else {
|
|
738
|
+
const spinner = createSpinner(
|
|
739
|
+
`Installing devDependencies via: ${installCmd}`,
|
|
740
|
+
input.options.quiet
|
|
741
|
+
);
|
|
742
|
+
try {
|
|
743
|
+
spinner.start();
|
|
744
|
+
await shell(installCmd, input.projectRoot);
|
|
745
|
+
spinner.succeed("Dependencies installed successfully");
|
|
746
|
+
reporter.success("Installed @inspecto-dev/plugin and @inspecto-dev/core as devDependencies");
|
|
747
|
+
mutations.push({ type: "dependency_added", name: "@inspecto-dev/plugin", dev: true });
|
|
748
|
+
mutations.push({ type: "dependency_added", name: "@inspecto-dev/core", dev: true });
|
|
749
|
+
} catch (error) {
|
|
750
|
+
spinner.fail("Dependency installation failed");
|
|
751
|
+
installFailed = true;
|
|
752
|
+
reporter.error(`Failed to install dependency: ${error?.message || "Unknown error"}`);
|
|
753
|
+
reporter.hint(`Run manually in ${input.projectRoot}: ${installCmd}`);
|
|
754
|
+
reporter.hint(
|
|
755
|
+
"Setup will continue without dependencies, but Inspecto may not run until installation succeeds."
|
|
756
|
+
);
|
|
382
757
|
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
758
|
+
}
|
|
759
|
+
let injectionFailed = Boolean(input.injectionSkippedRequiresManualConfig);
|
|
760
|
+
for (const target of input.supportedBuildTargets) {
|
|
761
|
+
const result = await injectPlugin(input.repoRoot, target, input.options.dryRun);
|
|
762
|
+
if (result.success) {
|
|
763
|
+
mutations.push(...result.mutations);
|
|
764
|
+
} else {
|
|
765
|
+
injectionFailed = true;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (await exists(settingsPath)) {
|
|
769
|
+
const existingSettings = await readJSON(settingsPath);
|
|
770
|
+
if (existingSettings === null) {
|
|
771
|
+
reporter.warn(`.inspecto/${settingsFileName} exists but contains invalid JSON`);
|
|
772
|
+
reporter.hint("Please fix the syntax errors manually, or delete it and re-run init");
|
|
773
|
+
nextSteps.push(`Fix .inspecto/${settingsFileName} or delete it and rerun Inspecto setup.`);
|
|
774
|
+
} else {
|
|
775
|
+
reporter.success(`.inspecto/${settingsFileName} already exists (skipped)`);
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
const defaultSettings = {};
|
|
779
|
+
if (input.selectedIDE?.supported) {
|
|
780
|
+
defaultSettings.ide = input.selectedIDE.ide.toLowerCase() === "vscode" ? "vscode" : input.selectedIDE.ide.toLowerCase();
|
|
781
|
+
}
|
|
782
|
+
if (input.providerDefault) {
|
|
783
|
+
defaultSettings["provider.default"] = input.providerDefault;
|
|
784
|
+
}
|
|
785
|
+
if (input.options.dryRun) {
|
|
786
|
+
reporter.dryRun(`Would create .inspecto/${settingsFileName}`);
|
|
787
|
+
} else {
|
|
788
|
+
await writeJSON(settingsPath, defaultSettings);
|
|
789
|
+
reporter.success(`Created .inspecto/${settingsFileName}`);
|
|
790
|
+
mutations.push({ type: "file_created", path: `.inspecto/${settingsFileName}` });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (await exists(promptsPath)) {
|
|
794
|
+
reporter.success(`.inspecto/${promptsFileName} already exists (skipped)`);
|
|
795
|
+
} else if (input.options.dryRun) {
|
|
796
|
+
reporter.dryRun(`Would create .inspecto/${promptsFileName}`);
|
|
797
|
+
} else {
|
|
798
|
+
await writeJSON(promptsPath, []);
|
|
799
|
+
reporter.success(`Created .inspecto/${promptsFileName}`);
|
|
800
|
+
mutations.push({ type: "file_created", path: `.inspecto/${promptsFileName}` });
|
|
801
|
+
}
|
|
802
|
+
if (!input.options.dryRun) {
|
|
803
|
+
await updateGitignore(input.projectRoot, input.options.shared, input.options.dryRun);
|
|
804
|
+
mutations.push({
|
|
805
|
+
type: "file_modified",
|
|
806
|
+
path: ".gitignore",
|
|
807
|
+
description: "Appended .inspecto/ ignore rules"
|
|
808
|
+
});
|
|
809
|
+
} else {
|
|
810
|
+
reporter.dryRun("Would update .gitignore");
|
|
811
|
+
}
|
|
812
|
+
if (!input.options.dryRun && mutations.length > 0) {
|
|
813
|
+
const lock = {
|
|
814
|
+
version: "1.0.0",
|
|
815
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
816
|
+
mutations
|
|
817
|
+
};
|
|
818
|
+
await writeJSON(path5.join(settingsDir, "install.lock"), lock);
|
|
819
|
+
}
|
|
820
|
+
const shouldInstallExt = !input.options.noExtension && (!input.selectedIDE || input.selectedIDE.supported);
|
|
821
|
+
let manualExtensionInstallNeeded = false;
|
|
822
|
+
if (input.options.noExtension) {
|
|
823
|
+
reporter.warn("Skipping IDE extension (--no-extension)");
|
|
824
|
+
} else if (shouldInstallExt) {
|
|
825
|
+
const extMutation = await installExtension(input.options.dryRun, input.selectedIDE?.ide);
|
|
826
|
+
if (extMutation && !input.options.dryRun) {
|
|
827
|
+
mutations.push(extMutation);
|
|
828
|
+
if (extMutation.manual_action_required) {
|
|
829
|
+
manualExtensionInstallNeeded = true;
|
|
830
|
+
}
|
|
831
|
+
const lockPath = path5.join(settingsDir, "install.lock");
|
|
832
|
+
const lock = await readJSON(lockPath);
|
|
833
|
+
if (lock) {
|
|
834
|
+
lock.mutations = mutations;
|
|
835
|
+
await writeJSON(lockPath, lock);
|
|
836
|
+
}
|
|
837
|
+
} else if (extMutation === null && !input.options.dryRun) {
|
|
838
|
+
manualExtensionInstallNeeded = true;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (!input.options.dryRun) {
|
|
842
|
+
if (installFailed) {
|
|
843
|
+
nextSteps.push(`Install dependencies manually in ${input.projectRoot}: ${installCmd}`);
|
|
844
|
+
}
|
|
845
|
+
if (injectionFailed) {
|
|
846
|
+
nextSteps.push(
|
|
847
|
+
"Plugin injection skipped. Follow manual instructions printed above to update your config."
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
if (manualExtensionInstallNeeded) {
|
|
851
|
+
nextSteps.push("Install the Inspecto IDE extension manually");
|
|
852
|
+
}
|
|
853
|
+
if (input.manualConfigRequiredFor === "Nuxt") {
|
|
854
|
+
nextSteps.push(
|
|
855
|
+
"Nuxt detected\u2014please follow the Nuxt instructions printed above to finish setup."
|
|
856
|
+
);
|
|
857
|
+
} else if (input.manualConfigRequiredFor === "Next.js") {
|
|
858
|
+
nextSteps.push(
|
|
859
|
+
"Next.js detected\u2014please follow the Next.js instructions printed above to finish setup."
|
|
860
|
+
);
|
|
387
861
|
}
|
|
388
862
|
}
|
|
389
|
-
const absoluteConfig = path3.join(targetRoot, detectedFile);
|
|
390
|
-
const relativeConfig = normalizeRelativePath(workspaceRoot, absoluteConfig);
|
|
391
863
|
return {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
864
|
+
status: resultStatus(nextSteps),
|
|
865
|
+
mutations,
|
|
866
|
+
postInstall: {
|
|
867
|
+
installFailed,
|
|
868
|
+
injectionFailed,
|
|
869
|
+
manualExtensionInstallNeeded,
|
|
870
|
+
nextSteps
|
|
871
|
+
}
|
|
398
872
|
};
|
|
399
873
|
}
|
|
400
|
-
function resolveInjectionTarget(detections) {
|
|
401
|
-
if (detections.length === 0) return null;
|
|
402
|
-
if (detections.length === 1) return detections[0];
|
|
403
|
-
return "ambiguous";
|
|
404
|
-
}
|
|
405
874
|
|
|
406
|
-
// src/detect/
|
|
407
|
-
import
|
|
408
|
-
import
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
nuxt: "vue",
|
|
412
|
-
"@remix-run/react": "react",
|
|
413
|
-
"@remix-run/dev": "react",
|
|
414
|
-
"@vue/nuxt": "vue",
|
|
415
|
-
"vite-plugin-vue": "vue",
|
|
416
|
-
"@vitejs/plugin-vue": "vue",
|
|
417
|
-
"@vitejs/plugin-react": "react",
|
|
418
|
-
"@vitejs/plugin-react-swc": "react"
|
|
419
|
-
};
|
|
420
|
-
var SUPPORTED_FRAMEWORKS = [
|
|
421
|
-
{ framework: "react", deps: ["react", "react-dom"] },
|
|
422
|
-
{ framework: "vue", deps: ["vue"] }
|
|
423
|
-
];
|
|
424
|
-
var UNSUPPORTED_FRAMEWORKS = [
|
|
425
|
-
{ name: "Solid", dep: "solid-js" },
|
|
426
|
-
{ name: "Svelte", dep: "svelte" },
|
|
427
|
-
{ name: "Angular", dep: "@angular/core" },
|
|
428
|
-
{ name: "Preact", dep: "preact" },
|
|
429
|
-
{ name: "Lit", dep: "lit" }
|
|
430
|
-
];
|
|
431
|
-
function isPackageResolvable2(pkgName, root) {
|
|
875
|
+
// src/detect/build-tool.ts
|
|
876
|
+
import path6 from "path";
|
|
877
|
+
import fs2 from "fs/promises";
|
|
878
|
+
import { createRequire } from "module";
|
|
879
|
+
function isPackageResolvable(pkgName, root) {
|
|
432
880
|
try {
|
|
433
|
-
const require2 =
|
|
881
|
+
const require2 = createRequire(path6.join(root, "package.json"));
|
|
434
882
|
try {
|
|
435
883
|
require2.resolve(`${pkgName}/package.json`, { paths: [root] });
|
|
436
884
|
return true;
|
|
@@ -442,579 +890,878 @@ function isPackageResolvable2(pkgName, root) {
|
|
|
442
890
|
return false;
|
|
443
891
|
}
|
|
444
892
|
}
|
|
445
|
-
async function
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const unsupported = [];
|
|
454
|
-
const isTest = root.includes("/mock/root");
|
|
455
|
-
for (const [metaPkg, framework] of Object.entries(META_FRAMEWORK_MAP)) {
|
|
456
|
-
if (metaPkg in allDeps || !isTest && isPackageResolvable2(metaPkg, root)) {
|
|
457
|
-
supportedSet.add(framework);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
for (const { framework, deps } of SUPPORTED_FRAMEWORKS) {
|
|
461
|
-
if (supportedSet.has(framework)) continue;
|
|
462
|
-
for (const dep of deps) {
|
|
463
|
-
if (dep in allDeps || !isTest && isPackageResolvable2(dep, root)) {
|
|
464
|
-
supportedSet.add(framework);
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
for (const fw of UNSUPPORTED_FRAMEWORKS) {
|
|
470
|
-
if (fw.dep in allDeps || !isTest && isPackageResolvable2(fw.dep, root)) {
|
|
471
|
-
unsupported.push(fw);
|
|
472
|
-
}
|
|
893
|
+
async function getResolvedPackageVersion(pkgName, root) {
|
|
894
|
+
try {
|
|
895
|
+
const require2 = createRequire(path6.join(root, "package.json"));
|
|
896
|
+
const pkgJsonPath = require2.resolve(`${pkgName}/package.json`, { paths: [root] });
|
|
897
|
+
const pkg = await readJSON(pkgJsonPath);
|
|
898
|
+
return pkg?.version || null;
|
|
899
|
+
} catch {
|
|
900
|
+
return null;
|
|
473
901
|
}
|
|
474
|
-
return {
|
|
475
|
-
supported: Array.from(supportedSet),
|
|
476
|
-
unsupported
|
|
477
|
-
};
|
|
478
902
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
903
|
+
var SUPPORTED_PATTERNS = [
|
|
904
|
+
{
|
|
905
|
+
tool: "vite",
|
|
906
|
+
files: [
|
|
907
|
+
"vite.config.ts",
|
|
908
|
+
"vite.config.js",
|
|
909
|
+
"vite.config.mts",
|
|
910
|
+
"vite.config.mjs",
|
|
911
|
+
"vite.config.cjs",
|
|
912
|
+
"vite.config.cts"
|
|
913
|
+
],
|
|
914
|
+
label: "Vite"
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
tool: "rspack",
|
|
918
|
+
files: ["rspack.config.js", "rspack.config.ts", "rspack.config.mjs"],
|
|
919
|
+
label: "Rspack"
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
tool: "rsbuild",
|
|
923
|
+
files: ["rsbuild.config.js", "rsbuild.config.ts", "rsbuild.config.mjs"],
|
|
924
|
+
label: "Rsbuild"
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
tool: "webpack",
|
|
928
|
+
files: ["webpack.config.js", "webpack.config.ts", "webpack.config.mjs", "webpack.config.cjs"],
|
|
929
|
+
label: "Webpack"
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
tool: "esbuild",
|
|
933
|
+
files: ["esbuild.config.js", "esbuild.config.ts", "esbuild.config.mjs", "build.js", "build.ts"],
|
|
934
|
+
label: "esbuild"
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
tool: "rollup",
|
|
938
|
+
files: ["rollup.config.js", "rollup.config.ts", "rollup.config.mjs"],
|
|
939
|
+
label: "Rollup"
|
|
490
940
|
}
|
|
491
|
-
|
|
492
|
-
|
|
941
|
+
];
|
|
942
|
+
var UNSUPPORTED_META = [
|
|
943
|
+
{ name: "Next.js", dep: "next", files: ["next.config.mjs", "next.config.js", "next.config.ts"] },
|
|
944
|
+
{ name: "Nuxt", dep: "nuxt", files: ["nuxt.config.ts", "nuxt.config.js"] },
|
|
945
|
+
{ name: "Remix", dep: "@remix-run/dev", files: ["remix.config.js", "remix.config.ts"] },
|
|
946
|
+
{ name: "Astro", dep: "astro", files: ["astro.config.mjs", "astro.config.ts"] },
|
|
947
|
+
{ name: "SvelteKit", dep: "@sveltejs/kit", files: ["svelte.config.js", "svelte.config.ts"] }
|
|
948
|
+
];
|
|
949
|
+
function normalizeRelativePath(root, filePath) {
|
|
950
|
+
const relative = path6.relative(root, filePath);
|
|
951
|
+
const normalized = relative.split(path6.sep).join("/");
|
|
952
|
+
return normalized || path6.basename(filePath);
|
|
953
|
+
}
|
|
954
|
+
function createTargets(root, packagePaths) {
|
|
955
|
+
if (!packagePaths || packagePaths.length === 0) {
|
|
956
|
+
return [{ packagePath: "", absolutePath: root }];
|
|
493
957
|
}
|
|
494
|
-
|
|
495
|
-
|
|
958
|
+
return packagePaths.map((pkg) => ({
|
|
959
|
+
packagePath: pkg,
|
|
960
|
+
absolutePath: pkg ? path6.join(root, pkg) : root
|
|
961
|
+
}));
|
|
962
|
+
}
|
|
963
|
+
async function getWorkspacePackagePatterns(root) {
|
|
964
|
+
const pkg = await readJSON(
|
|
965
|
+
path6.join(root, "package.json")
|
|
966
|
+
);
|
|
967
|
+
const workspaces = pkg?.workspaces;
|
|
968
|
+
if (Array.isArray(workspaces)) {
|
|
969
|
+
return workspaces;
|
|
970
|
+
}
|
|
971
|
+
if (workspaces && Array.isArray(workspaces.packages)) {
|
|
972
|
+
return workspaces.packages;
|
|
973
|
+
}
|
|
974
|
+
const pnpmWorkspace = await readFile(path6.join(root, "pnpm-workspace.yaml"));
|
|
975
|
+
if (!pnpmWorkspace) {
|
|
976
|
+
return [];
|
|
977
|
+
}
|
|
978
|
+
const patterns = [];
|
|
979
|
+
for (const line of pnpmWorkspace.split("\n")) {
|
|
980
|
+
const match = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/);
|
|
981
|
+
if (match?.[1]) {
|
|
982
|
+
patterns.push(match[1]);
|
|
983
|
+
}
|
|
496
984
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (hasTrae && !detected.has("Trae")) {
|
|
504
|
-
detected.set("Trae", { ide: "trae", supported: true });
|
|
985
|
+
return patterns;
|
|
986
|
+
}
|
|
987
|
+
async function expandWorkspacePattern(root, pattern) {
|
|
988
|
+
const normalized = pattern.replace(/\\/g, "/").replace(/\/$/, "");
|
|
989
|
+
if (!normalized || normalized.startsWith("!")) {
|
|
990
|
+
return [];
|
|
505
991
|
}
|
|
506
|
-
if (
|
|
507
|
-
|
|
992
|
+
if (!normalized.includes("*")) {
|
|
993
|
+
return await exists(path6.join(root, normalized)) ? [normalized] : [];
|
|
508
994
|
}
|
|
509
|
-
|
|
510
|
-
|
|
995
|
+
const starIndex = normalized.indexOf("*");
|
|
996
|
+
const baseDir = normalized.slice(0, starIndex).replace(/\/$/, "");
|
|
997
|
+
const suffix = normalized.slice(starIndex + 1);
|
|
998
|
+
if (suffix && suffix !== "") {
|
|
999
|
+
return [];
|
|
511
1000
|
}
|
|
512
|
-
|
|
513
|
-
|
|
1001
|
+
const absoluteBaseDir = path6.join(root, baseDir);
|
|
1002
|
+
if (!await exists(absoluteBaseDir)) {
|
|
1003
|
+
return [];
|
|
514
1004
|
}
|
|
515
|
-
|
|
516
|
-
|
|
1005
|
+
try {
|
|
1006
|
+
const entries = await fs2.readdir(absoluteBaseDir, { withFileTypes: true });
|
|
1007
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => path6.posix.join(baseDir, entry.name));
|
|
1008
|
+
} catch {
|
|
1009
|
+
return [];
|
|
517
1010
|
}
|
|
518
|
-
return {
|
|
519
|
-
detected: Array.from(detected.values())
|
|
520
|
-
};
|
|
521
1011
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
{ id: "claude-code", extId: "anthropic.claude-code", label: "Claude Code", supported: true },
|
|
533
|
-
{ id: "copilot", extId: "github.copilot", label: "GitHub Copilot", supported: true },
|
|
534
|
-
{ id: "codex", extId: "openai.chatgpt", label: "Codex (ChatGPT)", supported: true },
|
|
535
|
-
{ id: "gemini", extId: "google.geminicodeassist", label: "Gemini Code Assist", supported: true }
|
|
536
|
-
];
|
|
537
|
-
async function detectProviders(root) {
|
|
538
|
-
const detectedMap = /* @__PURE__ */ new Map();
|
|
539
|
-
const cliChecks = KNOWN_CLI_TOOLS.map(async (tool) => {
|
|
540
|
-
if (await which(tool.bin)) {
|
|
541
|
-
detectedMap.set(tool.id, {
|
|
542
|
-
id: tool.id,
|
|
543
|
-
label: tool.label,
|
|
544
|
-
supported: tool.supported,
|
|
545
|
-
providerModes: ["cli"],
|
|
546
|
-
preferredMode: "cli"
|
|
547
|
-
});
|
|
1012
|
+
async function detectWorkspaceTargets(root) {
|
|
1013
|
+
const patterns = await getWorkspacePackagePatterns(root);
|
|
1014
|
+
if (patterns.length === 0) {
|
|
1015
|
+
return [];
|
|
1016
|
+
}
|
|
1017
|
+
const packagePaths = /* @__PURE__ */ new Set();
|
|
1018
|
+
for (const pattern of patterns) {
|
|
1019
|
+
const expanded = await expandWorkspacePattern(root, pattern);
|
|
1020
|
+
for (const packagePath of expanded) {
|
|
1021
|
+
packagePaths.add(packagePath);
|
|
548
1022
|
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1023
|
+
}
|
|
1024
|
+
return Array.from(packagePaths).map((packagePath) => ({
|
|
1025
|
+
packagePath,
|
|
1026
|
+
absolutePath: path6.join(root, packagePath)
|
|
1027
|
+
}));
|
|
1028
|
+
}
|
|
1029
|
+
async function detectBuildTools(root, packagePaths) {
|
|
1030
|
+
const supported = [];
|
|
1031
|
+
const unsupported = /* @__PURE__ */ new Set();
|
|
1032
|
+
const explicitTargets = createTargets(root, packagePaths);
|
|
1033
|
+
const workspaceTargets = !packagePaths || packagePaths.length === 0 ? await detectWorkspaceTargets(root) : [];
|
|
1034
|
+
const targets = workspaceTargets.length > 0 ? workspaceTargets : explicitTargets;
|
|
1035
|
+
for (const target of targets) {
|
|
1036
|
+
const pkg = await readJSON(path6.join(target.absolutePath, "package.json"));
|
|
1037
|
+
const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
1038
|
+
const scripts = pkg?.scripts || {};
|
|
1039
|
+
const supportedChecks = SUPPORTED_PATTERNS.map(
|
|
1040
|
+
(pattern) => detectPattern({
|
|
1041
|
+
pattern,
|
|
1042
|
+
workspaceRoot: root,
|
|
1043
|
+
targetRoot: target.absolutePath,
|
|
1044
|
+
packagePath: target.packagePath,
|
|
1045
|
+
allDeps,
|
|
1046
|
+
scripts
|
|
1047
|
+
})
|
|
1048
|
+
);
|
|
1049
|
+
const supportedResults = await Promise.all(supportedChecks);
|
|
1050
|
+
for (const result of supportedResults) {
|
|
1051
|
+
if (result) {
|
|
1052
|
+
supported.push(result);
|
|
558
1053
|
}
|
|
559
|
-
} catch {
|
|
560
1054
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (globalExtExists) {
|
|
567
|
-
try {
|
|
568
|
-
const { readdir } = await import("fs/promises");
|
|
569
|
-
installedExtensionFolders = await readdir(globalExtDir);
|
|
570
|
-
const obsoletePath = path6.join(globalExtDir, ".obsolete");
|
|
571
|
-
if (await exists(obsoletePath)) {
|
|
572
|
-
try {
|
|
573
|
-
const obsoleteData = await readJSON(obsoletePath);
|
|
574
|
-
if (obsoleteData) {
|
|
575
|
-
const obsoleteKeys = Object.keys(obsoleteData);
|
|
576
|
-
installedExtensionFolders = installedExtensionFolders.filter((folder) => {
|
|
577
|
-
return !obsoleteKeys.includes(folder);
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
} catch {
|
|
1055
|
+
const unsupportedChecks = UNSUPPORTED_META.map(async (meta) => {
|
|
1056
|
+
if (!(meta.dep in allDeps)) return null;
|
|
1057
|
+
for (const file of meta.files) {
|
|
1058
|
+
if (await exists(path6.join(target.absolutePath, file))) {
|
|
1059
|
+
return meta.name;
|
|
581
1060
|
}
|
|
582
1061
|
}
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
isInstalled = true;
|
|
590
|
-
} else if (installedExtensionFolders.some((f) => {
|
|
591
|
-
const lower = f.toLowerCase();
|
|
592
|
-
return lower === plugin.extId.toLowerCase() || lower.startsWith(plugin.extId.toLowerCase() + "-");
|
|
593
|
-
})) {
|
|
594
|
-
isInstalled = true;
|
|
595
|
-
}
|
|
596
|
-
if (isInstalled) {
|
|
597
|
-
const existing = detectedMap.get(plugin.id);
|
|
598
|
-
if (existing) {
|
|
599
|
-
existing.providerModes.push("extension");
|
|
600
|
-
existing.preferredMode = "extension";
|
|
601
|
-
} else {
|
|
602
|
-
detectedMap.set(plugin.id, {
|
|
603
|
-
id: plugin.id,
|
|
604
|
-
label: plugin.label,
|
|
605
|
-
supported: plugin.supported,
|
|
606
|
-
providerModes: ["extension"],
|
|
607
|
-
preferredMode: "extension"
|
|
608
|
-
});
|
|
1062
|
+
return null;
|
|
1063
|
+
});
|
|
1064
|
+
const unsupportedResults = await Promise.all(unsupportedChecks);
|
|
1065
|
+
for (const result of unsupportedResults) {
|
|
1066
|
+
if (result) {
|
|
1067
|
+
unsupported.add(result);
|
|
609
1068
|
}
|
|
610
1069
|
}
|
|
611
1070
|
}
|
|
612
|
-
return {
|
|
1071
|
+
return { supported, unsupported: Array.from(unsupported) };
|
|
613
1072
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
`].filter(Boolean)`
|
|
641
|
-
];
|
|
642
|
-
}
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
// src/inject/strategies/webpack.ts
|
|
646
|
-
var WebpackStrategy = class {
|
|
647
|
-
name = "Webpack";
|
|
648
|
-
supports(tool) {
|
|
649
|
-
return tool === "webpack";
|
|
650
|
-
}
|
|
651
|
-
inject(options) {
|
|
652
|
-
throw new Error("Webpack requires manual plugin configuration");
|
|
653
|
-
}
|
|
654
|
-
getManualInstructions(detection, reason) {
|
|
655
|
-
const importPkg = detection.isLegacyWebpack ? "@inspecto-dev/plugin/legacy/webpack4" : "@inspecto-dev/plugin";
|
|
656
|
-
const pluginName = detection.isLegacyWebpack ? "webpack4Plugin" : "webpackPlugin";
|
|
657
|
-
const pluginCall = detection.isLegacyWebpack ? `process.env.NODE_ENV !== 'production' && inspecto({
|
|
658
|
-
pathType: 'absolute',
|
|
659
|
-
escapeTags: ['Transition', 'AnimatePresence'],
|
|
660
|
-
})` : `process.env.NODE_ENV !== 'production' && inspecto()`;
|
|
661
|
-
return [
|
|
662
|
-
`import { ${pluginName} as inspecto } from '${importPkg}'`,
|
|
663
|
-
"",
|
|
664
|
-
"// Add to your plugins array:",
|
|
665
|
-
`plugins: [`,
|
|
666
|
-
` ${pluginCall},`,
|
|
667
|
-
` ...otherPlugins`,
|
|
668
|
-
`].filter(Boolean)`
|
|
669
|
-
];
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
// src/inject/strategies/rspack.ts
|
|
674
|
-
var RspackStrategy = class {
|
|
675
|
-
name = "Rspack";
|
|
676
|
-
supports(tool) {
|
|
677
|
-
return tool === "rspack";
|
|
1073
|
+
async function detectPattern({
|
|
1074
|
+
pattern,
|
|
1075
|
+
workspaceRoot,
|
|
1076
|
+
targetRoot,
|
|
1077
|
+
packagePath,
|
|
1078
|
+
allDeps,
|
|
1079
|
+
scripts
|
|
1080
|
+
}) {
|
|
1081
|
+
let hasDep;
|
|
1082
|
+
let resolvedVersion = null;
|
|
1083
|
+
if (pattern.tool === "rspack") {
|
|
1084
|
+
const depName = allDeps["@rspack/cli"] ? "@rspack/cli" : "@rspack/core";
|
|
1085
|
+
hasDep = !!allDeps["@rspack/cli"] || !!allDeps["@rspack/core"] || isPackageResolvable("@rspack/core", targetRoot);
|
|
1086
|
+
if (hasDep) {
|
|
1087
|
+
resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("@rspack/core", targetRoot);
|
|
1088
|
+
}
|
|
1089
|
+
} else if (pattern.tool === "webpack") {
|
|
1090
|
+
const depName = allDeps["webpack"] ? "webpack" : "webpack-cli";
|
|
1091
|
+
hasDep = !!allDeps["webpack"] || !!allDeps["webpack-cli"] || isPackageResolvable("webpack", targetRoot);
|
|
1092
|
+
if (hasDep) {
|
|
1093
|
+
resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("webpack", targetRoot);
|
|
1094
|
+
}
|
|
1095
|
+
} else if (pattern.tool === "rsbuild") {
|
|
1096
|
+
hasDep = !!allDeps["@rsbuild/core"] || isPackageResolvable("@rsbuild/core", targetRoot);
|
|
1097
|
+
} else {
|
|
1098
|
+
hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, targetRoot);
|
|
678
1099
|
}
|
|
679
|
-
|
|
680
|
-
|
|
1100
|
+
let detectedFile = "";
|
|
1101
|
+
let inferredFromScripts = false;
|
|
1102
|
+
if (pattern.tool === "esbuild" && !hasDep) {
|
|
1103
|
+
return null;
|
|
681
1104
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
})` : `process.env.NODE_ENV !== 'production' && inspecto()`;
|
|
688
|
-
return [
|
|
689
|
-
`import { rspackPlugin as inspecto } from '${importPkg}'`,
|
|
690
|
-
"",
|
|
691
|
-
"// Add to your plugins array:",
|
|
692
|
-
`plugins: [`,
|
|
693
|
-
` ${pluginCall},`,
|
|
694
|
-
` ...otherPlugins`,
|
|
695
|
-
`].filter(Boolean)`
|
|
696
|
-
];
|
|
1105
|
+
for (const file of pattern.files) {
|
|
1106
|
+
if (await exists(path6.join(targetRoot, file))) {
|
|
1107
|
+
detectedFile = file;
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
697
1110
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1111
|
+
if (hasDep && !detectedFile && (pattern.tool === "esbuild" || pattern.tool === "rollup" || pattern.tool === "webpack" || pattern.tool === "rspack" || pattern.tool === "rsbuild")) {
|
|
1112
|
+
for (const cmd of Object.values(scripts)) {
|
|
1113
|
+
if (cmd.includes("node ")) {
|
|
1114
|
+
const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/);
|
|
1115
|
+
if (match && match[1]) {
|
|
1116
|
+
if (await exists(path6.join(targetRoot, match[1]))) {
|
|
1117
|
+
if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
|
|
1118
|
+
detectedFile = match[1];
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
} else if (cmd.includes(`${pattern.tool} `)) {
|
|
1124
|
+
if (pattern.tool === "webpack" || pattern.tool === "rspack") {
|
|
1125
|
+
const configMatch = cmd.match(/--config\s+([^\s]+)/);
|
|
1126
|
+
if (configMatch && configMatch[1]) {
|
|
1127
|
+
if (await exists(path6.join(targetRoot, configMatch[1]))) {
|
|
1128
|
+
detectedFile = configMatch[1];
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (!detectedFile) {
|
|
1134
|
+
inferredFromScripts = true;
|
|
1135
|
+
detectedFile = "package.json (scripts)";
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
705
1140
|
}
|
|
706
|
-
|
|
707
|
-
|
|
1141
|
+
if (!detectedFile) {
|
|
1142
|
+
if (hasDep && (pattern.tool === "rollup" || pattern.tool === "webpack" || pattern.tool === "rspack" || pattern.tool === "esbuild")) {
|
|
1143
|
+
return {
|
|
1144
|
+
tool: pattern.tool,
|
|
1145
|
+
configPath: "package.json (dependency)",
|
|
1146
|
+
label: `${pattern.label} (detected via dependency)`,
|
|
1147
|
+
packagePath: packagePath || void 0
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
return null;
|
|
708
1151
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
];
|
|
1152
|
+
let isLegacyRspack = false;
|
|
1153
|
+
let isLegacyWebpack = false;
|
|
1154
|
+
if (pattern.tool === "rspack") {
|
|
1155
|
+
const version = resolvedVersion;
|
|
1156
|
+
if (version && (version.includes("0.3.") || version.includes("0.2.") || version.includes("0.1."))) {
|
|
1157
|
+
isLegacyRspack = true;
|
|
1158
|
+
}
|
|
1159
|
+
} else if (pattern.tool === "webpack") {
|
|
1160
|
+
const version = resolvedVersion;
|
|
1161
|
+
if (version && version.includes("^4") || version?.startsWith("4.")) {
|
|
1162
|
+
isLegacyWebpack = true;
|
|
1163
|
+
}
|
|
722
1164
|
}
|
|
723
|
-
|
|
1165
|
+
const absoluteConfig = path6.join(targetRoot, detectedFile);
|
|
1166
|
+
const relativeConfig = normalizeRelativePath(workspaceRoot, absoluteConfig);
|
|
1167
|
+
return {
|
|
1168
|
+
tool: pattern.tool,
|
|
1169
|
+
configPath: relativeConfig,
|
|
1170
|
+
label: `${pattern.label} (${relativeConfig})${isLegacyRspack ? " [Legacy]" : ""}${isLegacyWebpack ? " [Webpack 4]" : ""}${inferredFromScripts ? " [Scripts Detected]" : ""}`,
|
|
1171
|
+
isLegacyRspack,
|
|
1172
|
+
isLegacyWebpack,
|
|
1173
|
+
packagePath: packagePath || void 0
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function resolveInjectionTarget(detections) {
|
|
1177
|
+
if (detections.length === 0) return null;
|
|
1178
|
+
if (detections.length === 1) return detections[0];
|
|
1179
|
+
return "ambiguous";
|
|
1180
|
+
}
|
|
724
1181
|
|
|
725
|
-
// src/
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1182
|
+
// src/detect/framework.ts
|
|
1183
|
+
import path7 from "path";
|
|
1184
|
+
import { createRequire as createRequire2 } from "module";
|
|
1185
|
+
var META_FRAMEWORK_MAP = {
|
|
1186
|
+
next: "react",
|
|
1187
|
+
nuxt: "vue",
|
|
1188
|
+
"@remix-run/react": "react",
|
|
1189
|
+
"@remix-run/dev": "react",
|
|
1190
|
+
"@vue/nuxt": "vue",
|
|
1191
|
+
"vite-plugin-vue": "vue",
|
|
1192
|
+
"@vitejs/plugin-vue": "vue",
|
|
1193
|
+
"@vitejs/plugin-react": "react",
|
|
1194
|
+
"@vitejs/plugin-react-swc": "react"
|
|
1195
|
+
};
|
|
1196
|
+
var SUPPORTED_FRAMEWORKS = [
|
|
1197
|
+
{ framework: "react", deps: ["react", "react-dom"] },
|
|
1198
|
+
{ framework: "vue", deps: ["vue"] }
|
|
1199
|
+
];
|
|
1200
|
+
var UNSUPPORTED_FRAMEWORKS = [
|
|
1201
|
+
{ name: "Solid", dep: "solid-js" },
|
|
1202
|
+
{ name: "Svelte", dep: "svelte" },
|
|
1203
|
+
{ name: "Angular", dep: "@angular/core" },
|
|
1204
|
+
{ name: "Preact", dep: "preact" },
|
|
1205
|
+
{ name: "Lit", dep: "lit" }
|
|
1206
|
+
];
|
|
1207
|
+
function isPackageResolvable2(pkgName, root) {
|
|
1208
|
+
try {
|
|
1209
|
+
const require2 = createRequire2(path7.join(root, "package.json"));
|
|
1210
|
+
try {
|
|
1211
|
+
require2.resolve(`${pkgName}/package.json`, { paths: [root] });
|
|
1212
|
+
return true;
|
|
1213
|
+
} catch {
|
|
1214
|
+
require2.resolve(pkgName, { paths: [root] });
|
|
1215
|
+
return true;
|
|
1216
|
+
}
|
|
1217
|
+
} catch {
|
|
1218
|
+
return false;
|
|
730
1219
|
}
|
|
731
|
-
|
|
732
|
-
|
|
1220
|
+
}
|
|
1221
|
+
async function detectFrameworks(root) {
|
|
1222
|
+
const pkg = await readJSON(path7.join(root, "package.json"));
|
|
1223
|
+
const allDeps = {
|
|
1224
|
+
...pkg?.dependencies || {},
|
|
1225
|
+
...pkg?.devDependencies || {},
|
|
1226
|
+
...pkg?.peerDependencies || {}
|
|
1227
|
+
};
|
|
1228
|
+
const supportedSet = /* @__PURE__ */ new Set();
|
|
1229
|
+
const unsupported = [];
|
|
1230
|
+
const isTest = root.includes("/mock/root");
|
|
1231
|
+
for (const [metaPkg, framework] of Object.entries(META_FRAMEWORK_MAP)) {
|
|
1232
|
+
if (metaPkg in allDeps || !isTest && isPackageResolvable2(metaPkg, root)) {
|
|
1233
|
+
supportedSet.add(framework);
|
|
1234
|
+
}
|
|
733
1235
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
` ...otherPlugins`,
|
|
743
|
-
`].filter(Boolean)`,
|
|
744
|
-
"",
|
|
745
|
-
"2. Initialize the client in your app entry (e.g., main.js / index.js):",
|
|
746
|
-
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
747
|
-
"",
|
|
748
|
-
"// Call this before your app renders",
|
|
749
|
-
`if (process.env.NODE_ENV !== 'production') {`,
|
|
750
|
-
` mountInspector()`,
|
|
751
|
-
`}`
|
|
752
|
-
];
|
|
1236
|
+
for (const { framework, deps } of SUPPORTED_FRAMEWORKS) {
|
|
1237
|
+
if (supportedSet.has(framework)) continue;
|
|
1238
|
+
for (const dep of deps) {
|
|
1239
|
+
if (dep in allDeps || !isTest && isPackageResolvable2(dep, root)) {
|
|
1240
|
+
supportedSet.add(framework);
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
753
1244
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
name = "Rollup";
|
|
759
|
-
supports(tool) {
|
|
760
|
-
return tool === "rollup";
|
|
1245
|
+
for (const fw of UNSUPPORTED_FRAMEWORKS) {
|
|
1246
|
+
if (fw.dep in allDeps || !isTest && isPackageResolvable2(fw.dep, root)) {
|
|
1247
|
+
unsupported.push(fw);
|
|
1248
|
+
}
|
|
761
1249
|
}
|
|
762
|
-
|
|
763
|
-
|
|
1250
|
+
return {
|
|
1251
|
+
supported: Array.from(supportedSet),
|
|
1252
|
+
unsupported
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// src/detect/ide.ts
|
|
1257
|
+
import path8 from "path";
|
|
1258
|
+
var SUPPORTED_IDE = "vscode";
|
|
1259
|
+
async function detectIDE(root) {
|
|
1260
|
+
const detected = /* @__PURE__ */ new Map();
|
|
1261
|
+
if (process.env.CURSOR_TRACE_DIR || process.env.CURSOR_CHANNEL) {
|
|
1262
|
+
detected.set("Cursor", { ide: "cursor", supported: true });
|
|
764
1263
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
`1. Update your rollup config (${detection.configPath}):`,
|
|
768
|
-
`import { rollupPlugin as inspecto } from '@inspecto-dev/plugin'`,
|
|
769
|
-
"",
|
|
770
|
-
"// Add to your plugins array:",
|
|
771
|
-
`plugins: [`,
|
|
772
|
-
` process.env.NODE_ENV !== 'production' && inspecto(),`,
|
|
773
|
-
` ...otherPlugins`,
|
|
774
|
-
`].filter(Boolean)`,
|
|
775
|
-
"",
|
|
776
|
-
"2. Initialize the client in your app entry (e.g., main.js / index.js):",
|
|
777
|
-
`import { mountInspector } from '@inspecto-dev/core'`,
|
|
778
|
-
"",
|
|
779
|
-
"// Call this before your app renders",
|
|
780
|
-
`if (process.env.NODE_ENV !== 'production') {`,
|
|
781
|
-
` mountInspector()`,
|
|
782
|
-
`}`
|
|
783
|
-
];
|
|
1264
|
+
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")) {
|
|
1265
|
+
detected.set("Trae", { ide: "trae", supported: true });
|
|
784
1266
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
// src/inject/strategies/index.ts
|
|
788
|
-
var STRATEGIES = [
|
|
789
|
-
new ViteStrategy(),
|
|
790
|
-
new WebpackStrategy(),
|
|
791
|
-
new RspackStrategy(),
|
|
792
|
-
new RsbuildStrategy(),
|
|
793
|
-
new EsbuildStrategy(),
|
|
794
|
-
new RollupStrategy()
|
|
795
|
-
];
|
|
796
|
-
|
|
797
|
-
// src/inject/ast-injector.ts
|
|
798
|
-
function printManualInstructions(strategy, detection, reason) {
|
|
799
|
-
log.warn(`Could not automatically configure ${detection.configPath}`);
|
|
800
|
-
log.hint(`(reason: ${reason})`);
|
|
801
|
-
log.blank();
|
|
802
|
-
log.hint("Please add the following manually:");
|
|
803
|
-
if (strategy) {
|
|
804
|
-
const instructions = strategy.getManualInstructions(detection, reason);
|
|
805
|
-
log.codeBlock(instructions);
|
|
806
|
-
} else {
|
|
807
|
-
log.error(`Unsupported build tool: ${detection.tool}`);
|
|
1267
|
+
if (process.env.ZED_TERM) {
|
|
1268
|
+
detected.set("Zed", { ide: "Zed", supported: false });
|
|
808
1269
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
return /import\s+.*@inspecto-dev\/plugin/.test(content) || /require\(['"]@inspecto-dev\/plugin['"]\)/.test(content) || /import\s+.*ai-dev-inspector/.test(content) || // Legacy support
|
|
812
|
-
/require\(['"]ai-dev-inspector['"]\)/.test(content);
|
|
813
|
-
}
|
|
814
|
-
async function injectPlugin(root, detection, dryRun) {
|
|
815
|
-
const configPath = path7.join(root, detection.configPath);
|
|
816
|
-
const mutations = [];
|
|
817
|
-
const strategy = STRATEGIES.find((s) => s.supports(detection.tool));
|
|
818
|
-
const content = await readFile(configPath);
|
|
819
|
-
if (!content) {
|
|
820
|
-
printManualInstructions(strategy, detection, "config file not readable");
|
|
821
|
-
return { success: false, mutations, failureReason: "config file not readable" };
|
|
1270
|
+
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")) {
|
|
1271
|
+
detected.set("Windsurf", { ide: "Windsurf", supported: false });
|
|
822
1272
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1273
|
+
const [hasTrae, hasCursor, hasVscode, hasIdea] = await Promise.all([
|
|
1274
|
+
exists(path8.join(root, ".trae")),
|
|
1275
|
+
exists(path8.join(root, ".cursor")),
|
|
1276
|
+
exists(path8.join(root, ".vscode")),
|
|
1277
|
+
exists(path8.join(root, ".idea"))
|
|
1278
|
+
]);
|
|
1279
|
+
if (hasTrae && !detected.has("Trae")) {
|
|
1280
|
+
detected.set("Trae", { ide: "trae", supported: true });
|
|
831
1281
|
}
|
|
832
|
-
if (!
|
|
833
|
-
|
|
834
|
-
strategy,
|
|
835
|
-
detection,
|
|
836
|
-
`No injection strategy found for ${detection.tool}`
|
|
837
|
-
);
|
|
838
|
-
return { success: false, mutations, failureReason: "No strategy found" };
|
|
1282
|
+
if (hasCursor && !detected.has("Cursor")) {
|
|
1283
|
+
detected.set("Cursor", { ide: "cursor", supported: true });
|
|
839
1284
|
}
|
|
840
|
-
if (
|
|
841
|
-
|
|
842
|
-
return { success: true, mutations: [] };
|
|
1285
|
+
if (hasVscode && !detected.has("vscode")) {
|
|
1286
|
+
detected.set("vscode", { ide: SUPPORTED_IDE, supported: true });
|
|
843
1287
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
strategy.inject({
|
|
847
|
-
mod,
|
|
848
|
-
detection
|
|
849
|
-
});
|
|
850
|
-
await writeAstFile(mod, configPath);
|
|
851
|
-
mutations.push({
|
|
852
|
-
type: "file_modified",
|
|
853
|
-
path: detection.configPath,
|
|
854
|
-
description: "Automatically configured inspecto() plugin"
|
|
855
|
-
});
|
|
856
|
-
log.success(`Configured plugin in ${detection.configPath}`);
|
|
857
|
-
return { success: true, mutations };
|
|
858
|
-
} catch (err) {
|
|
859
|
-
printManualInstructions(
|
|
860
|
-
strategy,
|
|
861
|
-
detection,
|
|
862
|
-
`Automatic configuration unavailable: ${err instanceof Error ? err.message : String(err)}`
|
|
863
|
-
);
|
|
864
|
-
return {
|
|
865
|
-
success: false,
|
|
866
|
-
mutations,
|
|
867
|
-
failureReason: "Automatic configuration unavailable"
|
|
868
|
-
};
|
|
1288
|
+
if (hasIdea && !detected.has("JetBrains IDE")) {
|
|
1289
|
+
detected.set("JetBrains IDE", { ide: "JetBrains IDE", supported: false });
|
|
869
1290
|
}
|
|
1291
|
+
if (detected.size === 0 && process.env.TERM_PROGRAM === "vscode") {
|
|
1292
|
+
detected.set("vscode", { ide: SUPPORTED_IDE, supported: true });
|
|
1293
|
+
}
|
|
1294
|
+
return {
|
|
1295
|
+
detected: Array.from(detected.values())
|
|
1296
|
+
};
|
|
870
1297
|
}
|
|
871
1298
|
|
|
872
|
-
// src/
|
|
873
|
-
import
|
|
874
|
-
var
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1299
|
+
// src/detect/provider.ts
|
|
1300
|
+
import path9 from "path";
|
|
1301
|
+
var KNOWN_CLI_TOOLS = [
|
|
1302
|
+
{ id: "claude-code", bin: "claude", label: "Claude Code", supported: true },
|
|
1303
|
+
{ id: "coco", bin: "coco", label: "Trae CLI (Coco)", supported: true },
|
|
1304
|
+
{ id: "codex", bin: "codex", label: "Codex CLI", supported: true },
|
|
1305
|
+
{ id: "gemini", bin: "gemini", label: "Gemini CLI", supported: true }
|
|
1306
|
+
];
|
|
1307
|
+
var KNOWN_IDE_PLUGINS = [
|
|
1308
|
+
{ id: "claude-code", extId: "anthropic.claude-code", label: "Claude Code", supported: true },
|
|
1309
|
+
{ id: "copilot", extId: "github.copilot", label: "GitHub Copilot", supported: true },
|
|
1310
|
+
{ id: "codex", extId: "openai.chatgpt", label: "Codex (ChatGPT)", supported: true },
|
|
1311
|
+
{ id: "gemini", extId: "google.geminicodeassist", label: "Gemini Code Assist", supported: true }
|
|
1312
|
+
];
|
|
1313
|
+
async function detectProviders(root) {
|
|
1314
|
+
const detectedMap = /* @__PURE__ */ new Map();
|
|
1315
|
+
const cliChecks = KNOWN_CLI_TOOLS.map(async (tool) => {
|
|
1316
|
+
if (await which(tool.bin)) {
|
|
1317
|
+
detectedMap.set(tool.id, {
|
|
1318
|
+
id: tool.id,
|
|
1319
|
+
label: tool.label,
|
|
1320
|
+
supported: tool.supported,
|
|
1321
|
+
providerModes: ["cli"],
|
|
1322
|
+
preferredMode: "cli"
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
await Promise.all(cliChecks);
|
|
1327
|
+
const extensionsJsonPath = path9.join(root, ".vscode", "extensions.json");
|
|
1328
|
+
let recommendedExts = [];
|
|
1329
|
+
if (await exists(extensionsJsonPath)) {
|
|
1330
|
+
try {
|
|
1331
|
+
const extData = await readJSON(extensionsJsonPath);
|
|
1332
|
+
if (extData && Array.isArray(extData.recommendations)) {
|
|
1333
|
+
recommendedExts = extData.recommendations.map((e) => e.toLowerCase());
|
|
1334
|
+
}
|
|
1335
|
+
} catch {
|
|
885
1336
|
}
|
|
886
|
-
log.success("Updated .gitignore: .inspecto/ is no longer fully ignored");
|
|
887
|
-
return;
|
|
888
1337
|
}
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
1338
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
1339
|
+
const globalExtDir = path9.join(homeDir, ".vscode", "extensions");
|
|
1340
|
+
const globalExtExists = await exists(globalExtDir);
|
|
1341
|
+
let installedExtensionFolders = [];
|
|
1342
|
+
if (globalExtExists) {
|
|
1343
|
+
try {
|
|
1344
|
+
const { readdir } = await import("fs/promises");
|
|
1345
|
+
installedExtensionFolders = await readdir(globalExtDir);
|
|
1346
|
+
const obsoletePath = path9.join(globalExtDir, ".obsolete");
|
|
1347
|
+
if (await exists(obsoletePath)) {
|
|
1348
|
+
try {
|
|
1349
|
+
const obsoleteData = await readJSON(obsoletePath);
|
|
1350
|
+
if (obsoleteData) {
|
|
1351
|
+
const obsoleteKeys = Object.keys(obsoleteData);
|
|
1352
|
+
installedExtensionFolders = installedExtensionFolders.filter((folder) => {
|
|
1353
|
+
return !obsoleteKeys.includes(folder);
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
} catch {
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
892
1361
|
}
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1362
|
+
for (const plugin of KNOWN_IDE_PLUGINS) {
|
|
1363
|
+
let isInstalled = false;
|
|
1364
|
+
if (recommendedExts.includes(plugin.extId.toLowerCase())) {
|
|
1365
|
+
isInstalled = true;
|
|
1366
|
+
} else if (installedExtensionFolders.some((f) => {
|
|
1367
|
+
const lower = f.toLowerCase();
|
|
1368
|
+
return lower === plugin.extId.toLowerCase() || lower.startsWith(plugin.extId.toLowerCase() + "-");
|
|
1369
|
+
})) {
|
|
1370
|
+
isInstalled = true;
|
|
1371
|
+
}
|
|
1372
|
+
if (isInstalled) {
|
|
1373
|
+
const existing = detectedMap.get(plugin.id);
|
|
1374
|
+
if (existing) {
|
|
1375
|
+
existing.providerModes.push("extension");
|
|
1376
|
+
existing.preferredMode = "extension";
|
|
1377
|
+
} else {
|
|
1378
|
+
detectedMap.set(plugin.id, {
|
|
1379
|
+
id: plugin.id,
|
|
1380
|
+
label: plugin.label,
|
|
1381
|
+
supported: plugin.supported,
|
|
1382
|
+
providerModes: ["extension"],
|
|
1383
|
+
preferredMode: "extension"
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
900
1387
|
}
|
|
1388
|
+
return { detected: Array.from(detectedMap.values()) };
|
|
901
1389
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1390
|
+
|
|
1391
|
+
// src/onboarding/context.ts
|
|
1392
|
+
async function buildOnboardingContext(root) {
|
|
1393
|
+
const [packageManager, buildTools, frameworks, ides, providers] = await Promise.all([
|
|
1394
|
+
detectPackageManager(root),
|
|
1395
|
+
detectBuildTools(root),
|
|
1396
|
+
detectFrameworks(root),
|
|
1397
|
+
detectIDE(root),
|
|
1398
|
+
detectProviders(root)
|
|
1399
|
+
]);
|
|
1400
|
+
return {
|
|
1401
|
+
root,
|
|
1402
|
+
packageManager,
|
|
1403
|
+
buildTools: {
|
|
1404
|
+
supported: buildTools.supported,
|
|
1405
|
+
unsupported: buildTools.unsupported
|
|
1406
|
+
},
|
|
1407
|
+
frameworks: {
|
|
1408
|
+
supported: frameworks.supported,
|
|
1409
|
+
unsupported: frameworks.unsupported.map((item) => item.name)
|
|
1410
|
+
},
|
|
1411
|
+
ides: ides.detected.map(({ ide, supported }) => ({ ide, supported })),
|
|
1412
|
+
providers: providers.detected.map(({ id, label, supported, preferredMode }) => ({
|
|
1413
|
+
id,
|
|
1414
|
+
label,
|
|
1415
|
+
supported,
|
|
1416
|
+
preferredMode
|
|
1417
|
+
}))
|
|
1418
|
+
};
|
|
908
1419
|
}
|
|
909
1420
|
|
|
910
|
-
// src/
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1421
|
+
// src/onboarding/planner.ts
|
|
1422
|
+
function message(code, message2) {
|
|
1423
|
+
return { code, message: message2 };
|
|
1424
|
+
}
|
|
1425
|
+
function uniqueMessages(messages) {
|
|
1426
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1427
|
+
return messages.filter((item) => {
|
|
1428
|
+
const key = `${item.code}:${item.message}`;
|
|
1429
|
+
if (seen.has(key)) return false;
|
|
1430
|
+
seen.add(key);
|
|
1431
|
+
return true;
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
function detectionStatus(warnings, blockers) {
|
|
1435
|
+
if (blockers.length > 0) return "blocked";
|
|
1436
|
+
if (warnings.length > 0) return "warning";
|
|
1437
|
+
return "ok";
|
|
1438
|
+
}
|
|
1439
|
+
function planStatus(warnings, blockers) {
|
|
1440
|
+
if (blockers.length > 0) return "blocked";
|
|
1441
|
+
if (warnings.length > 0) return "warning";
|
|
1442
|
+
return "ok";
|
|
1443
|
+
}
|
|
1444
|
+
function supportedIde(context) {
|
|
1445
|
+
return context.ides.find((ide) => ide.supported)?.ide;
|
|
1446
|
+
}
|
|
1447
|
+
function supportedProvider(context) {
|
|
1448
|
+
return context.providers.find((provider) => provider.supported)?.id;
|
|
1449
|
+
}
|
|
1450
|
+
function buildToolBlockers(context) {
|
|
1451
|
+
if (context.buildTools.unsupported.length > 0) {
|
|
1452
|
+
return [
|
|
1453
|
+
message(
|
|
1454
|
+
"unsupported-build-tool",
|
|
1455
|
+
`Detected unsupported build tool(s): ${context.buildTools.unsupported.join(", ")}`
|
|
1456
|
+
)
|
|
1457
|
+
];
|
|
1458
|
+
}
|
|
1459
|
+
if (context.buildTools.supported.length > 0) {
|
|
1460
|
+
if (context.buildTools.supported.length === 1) {
|
|
1461
|
+
return [];
|
|
931
1462
|
}
|
|
1463
|
+
const targets = context.buildTools.supported.map((target) => target.packagePath ?? target.configPath).join(", ");
|
|
1464
|
+
return [
|
|
1465
|
+
message(
|
|
1466
|
+
"multiple-supported-build-targets",
|
|
1467
|
+
`Multiple supported build targets detected: ${targets}. Run inspecto apply from a single app/package root until explicit target selection is available.`
|
|
1468
|
+
)
|
|
1469
|
+
];
|
|
932
1470
|
}
|
|
933
|
-
|
|
934
|
-
|
|
1471
|
+
return [message("missing-build-tool", "No supported build tool detected")];
|
|
1472
|
+
}
|
|
1473
|
+
function frameworkBlockers(context) {
|
|
1474
|
+
if (context.frameworks.supported.length > 0) {
|
|
1475
|
+
return [];
|
|
935
1476
|
}
|
|
936
|
-
|
|
1477
|
+
if (context.frameworks.unsupported.length > 0) {
|
|
1478
|
+
return [
|
|
1479
|
+
message(
|
|
1480
|
+
"unsupported-framework",
|
|
1481
|
+
`Detected unsupported framework(s): ${context.frameworks.unsupported.join(", ")}`
|
|
1482
|
+
)
|
|
1483
|
+
];
|
|
1484
|
+
}
|
|
1485
|
+
return [message("missing-framework", "No supported frontend framework detected")];
|
|
937
1486
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1487
|
+
function unsupportedEnvironmentWarnings(context) {
|
|
1488
|
+
const warnings = [];
|
|
1489
|
+
if (context.frameworks.unsupported.length > 0 && context.frameworks.supported.length > 0) {
|
|
1490
|
+
warnings.push(
|
|
1491
|
+
message(
|
|
1492
|
+
"unsupported-framework-present",
|
|
1493
|
+
`Unsupported framework(s) also detected: ${context.frameworks.unsupported.join(", ")}`
|
|
1494
|
+
)
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
const unsupportedIdes = context.ides.filter((ide) => !ide.supported).map((ide) => ide.ide);
|
|
1498
|
+
if (unsupportedIdes.length > 0) {
|
|
1499
|
+
warnings.push(message("unsupported-ide", `Unsupported IDE(s) detected: ${unsupportedIdes.join(", ")}`));
|
|
1500
|
+
}
|
|
1501
|
+
const unsupportedProviders = context.providers.filter((provider) => !provider.supported).map((provider) => provider.label);
|
|
1502
|
+
if (unsupportedProviders.length > 0) {
|
|
1503
|
+
warnings.push(
|
|
1504
|
+
message(
|
|
1505
|
+
"unsupported-provider",
|
|
1506
|
+
`Unsupported provider(s) detected: ${unsupportedProviders.join(", ")}`
|
|
1507
|
+
)
|
|
1508
|
+
);
|
|
950
1509
|
}
|
|
1510
|
+
return warnings;
|
|
951
1511
|
}
|
|
952
|
-
|
|
953
|
-
if (
|
|
954
|
-
|
|
955
|
-
|
|
1512
|
+
function manualBuildToolActions(context) {
|
|
1513
|
+
if (context.buildTools.unsupported.length > 0) {
|
|
1514
|
+
return [
|
|
1515
|
+
{
|
|
1516
|
+
type: "manual_step",
|
|
1517
|
+
target: context.buildTools.unsupported.join(", "),
|
|
1518
|
+
description: "Inspecto cannot auto-configure this build stack yet. Follow the manual setup guide for the detected framework or build tool."
|
|
1519
|
+
}
|
|
1520
|
+
];
|
|
956
1521
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
} catch {
|
|
1522
|
+
if (context.buildTools.supported.length > 1) {
|
|
1523
|
+
const targets = context.buildTools.supported.map((target) => target.packagePath ?? target.configPath).join(", ");
|
|
1524
|
+
return [
|
|
1525
|
+
{
|
|
1526
|
+
type: "manual_step",
|
|
1527
|
+
target: targets,
|
|
1528
|
+
description: "Run inspecto apply from the target app/package root. Root-level apply is blocked when multiple supported targets are detected."
|
|
965
1529
|
}
|
|
1530
|
+
];
|
|
1531
|
+
}
|
|
1532
|
+
return [
|
|
1533
|
+
{
|
|
1534
|
+
type: "manual_step",
|
|
1535
|
+
target: context.root,
|
|
1536
|
+
description: "No supported build tool was detected. Add a supported build config before trying Inspecto again."
|
|
966
1537
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
)
|
|
975
|
-
|
|
976
|
-
} catch {
|
|
1538
|
+
];
|
|
1539
|
+
}
|
|
1540
|
+
function manualFrameworkActions(context) {
|
|
1541
|
+
if (context.frameworks.unsupported.length > 0) {
|
|
1542
|
+
return [
|
|
1543
|
+
{
|
|
1544
|
+
type: "manual_step",
|
|
1545
|
+
target: context.frameworks.unsupported.join(", "),
|
|
1546
|
+
description: "Inspecto cannot auto-configure this framework yet. Follow the manual setup guide for the detected framework."
|
|
977
1547
|
}
|
|
1548
|
+
];
|
|
1549
|
+
}
|
|
1550
|
+
return [
|
|
1551
|
+
{
|
|
1552
|
+
type: "manual_step",
|
|
1553
|
+
target: context.root,
|
|
1554
|
+
description: "No supported frontend framework was detected. Add a supported React or Vue app before trying Inspecto again."
|
|
978
1555
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1556
|
+
];
|
|
1557
|
+
}
|
|
1558
|
+
async function createDetectionResult(root) {
|
|
1559
|
+
const context = await buildOnboardingContext(root);
|
|
1560
|
+
const warnings = uniqueMessages([...unsupportedEnvironmentWarnings(context)]);
|
|
1561
|
+
const buildToolResult = buildToolBlockers(context);
|
|
1562
|
+
const frameworkResult = frameworkBlockers(context);
|
|
1563
|
+
const blockers = uniqueMessages([...buildToolResult, ...frameworkResult]);
|
|
1564
|
+
return {
|
|
1565
|
+
status: detectionStatus(warnings, blockers),
|
|
1566
|
+
warnings,
|
|
1567
|
+
blockers,
|
|
1568
|
+
project: {
|
|
1569
|
+
root: context.root,
|
|
1570
|
+
packageManager: context.packageManager
|
|
1571
|
+
},
|
|
1572
|
+
environment: {
|
|
1573
|
+
frameworks: context.frameworks.supported,
|
|
1574
|
+
unsupportedFrameworks: context.frameworks.unsupported,
|
|
1575
|
+
buildTools: context.buildTools.supported,
|
|
1576
|
+
unsupportedBuildTools: context.buildTools.unsupported,
|
|
1577
|
+
ides: context.ides,
|
|
1578
|
+
providers: context.providers
|
|
984
1579
|
}
|
|
985
|
-
|
|
986
|
-
log.hint("Please install it manually to enable Inspector features:");
|
|
987
|
-
log.hint(" 1. Open VS Code");
|
|
988
|
-
log.hint(" 2. Press Ctrl+Shift+X (or Cmd+Shift+X)");
|
|
989
|
-
log.hint(' 3. Search for "Inspecto"');
|
|
990
|
-
log.hint(` Or visit: https://marketplace.visualstudio.com/items?itemName=${EXTENSION_ID}`);
|
|
991
|
-
return null;
|
|
992
|
-
}
|
|
993
|
-
log.warn(`Could not auto-install extension for ${ide}`);
|
|
994
|
-
log.hint("Please install it manually to enable Inspector features:");
|
|
995
|
-
log.hint(" 1. Download the latest .vsix file from Inspecto releases");
|
|
996
|
-
log.hint(` 2. Open ${ide}`);
|
|
997
|
-
log.hint(" 3. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)");
|
|
998
|
-
log.hint(' 4. Type and select "Extensions: Install from VSIX..."');
|
|
999
|
-
log.hint(" 5. Select the downloaded .vsix file");
|
|
1000
|
-
return null;
|
|
1580
|
+
};
|
|
1001
1581
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1582
|
+
function createPlanResult(context) {
|
|
1583
|
+
const warnings = uniqueMessages(unsupportedEnvironmentWarnings(context));
|
|
1584
|
+
const blockers = uniqueMessages([...buildToolBlockers(context), ...frameworkBlockers(context)]);
|
|
1585
|
+
const actions = [];
|
|
1586
|
+
let strategy = "supported";
|
|
1587
|
+
if (blockers.length > 0) {
|
|
1588
|
+
strategy = "manual";
|
|
1589
|
+
if (context.buildTools.unsupported.length > 0 || context.buildTools.supported.length === 0 || context.buildTools.supported.length > 1) {
|
|
1590
|
+
actions.push(...manualBuildToolActions(context));
|
|
1007
1591
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const { stdout } = await run(codePath, ["--list-extensions"]);
|
|
1011
|
-
return stdout.toLowerCase().includes(EXTENSION_ID);
|
|
1592
|
+
if (frameworkBlockers(context).length > 0) {
|
|
1593
|
+
actions.push(...manualFrameworkActions(context));
|
|
1012
1594
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1595
|
+
} else {
|
|
1596
|
+
actions.push({
|
|
1597
|
+
type: "install_dependency",
|
|
1598
|
+
target: "@inspecto-dev/plugin @inspecto-dev/core",
|
|
1599
|
+
description: `Install the Inspecto runtime packages with ${context.packageManager}.`
|
|
1600
|
+
});
|
|
1601
|
+
for (const buildTool of context.buildTools.supported) {
|
|
1602
|
+
actions.push({
|
|
1603
|
+
type: "modify_file",
|
|
1604
|
+
target: buildTool.configPath,
|
|
1605
|
+
description: `Inject the Inspecto plugin into ${buildTool.label}.`
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
const ide2 = supportedIde(context);
|
|
1609
|
+
if (ide2 === "vscode") {
|
|
1610
|
+
actions.push({
|
|
1611
|
+
type: "install_extension",
|
|
1612
|
+
target: "vscode",
|
|
1613
|
+
description: "Install the Inspecto VS Code extension."
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
const defaults = {
|
|
1618
|
+
shared: false,
|
|
1619
|
+
extension: supportedIde(context) === "vscode"
|
|
1620
|
+
};
|
|
1621
|
+
const provider = supportedProvider(context);
|
|
1622
|
+
if (provider) {
|
|
1623
|
+
defaults.provider = provider;
|
|
1624
|
+
}
|
|
1625
|
+
const ide = supportedIde(context);
|
|
1626
|
+
if (ide) {
|
|
1627
|
+
defaults.ide = ide;
|
|
1628
|
+
}
|
|
1629
|
+
return {
|
|
1630
|
+
status: planStatus(warnings, blockers),
|
|
1631
|
+
warnings,
|
|
1632
|
+
blockers,
|
|
1633
|
+
strategy,
|
|
1634
|
+
actions,
|
|
1635
|
+
defaults
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// src/commands/apply.ts
|
|
1640
|
+
function getProviderDefault(providerId, preferredMode) {
|
|
1641
|
+
if (!providerId) return void 0;
|
|
1642
|
+
const mode = preferredMode ?? (providerId === "coco" ? "cli" : "extension");
|
|
1643
|
+
return `${providerId}.${mode}`;
|
|
1644
|
+
}
|
|
1645
|
+
function statusRank(status) {
|
|
1646
|
+
switch (status) {
|
|
1647
|
+
case "error":
|
|
1648
|
+
return 3;
|
|
1649
|
+
case "blocked":
|
|
1650
|
+
return 2;
|
|
1651
|
+
case "warning":
|
|
1652
|
+
return 1;
|
|
1653
|
+
case "ok":
|
|
1654
|
+
default:
|
|
1655
|
+
return 0;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
function mergeStatus(planStatus2, applyStatus) {
|
|
1659
|
+
return statusRank(planStatus2) >= statusRank(applyStatus) ? planStatus2 : applyStatus;
|
|
1660
|
+
}
|
|
1661
|
+
function printApplyResult(result) {
|
|
1662
|
+
const manualSteps = result.postInstall.nextSteps.filter(
|
|
1663
|
+
(step) => !result.plan.blockers.some((blocker) => blocker.message === step)
|
|
1664
|
+
);
|
|
1665
|
+
log.header("Inspecto Apply");
|
|
1666
|
+
log.info(`Status: ${result.status}`);
|
|
1667
|
+
log.info(`Strategy: ${result.plan.strategy}`);
|
|
1668
|
+
for (const blocker of result.plan.blockers) {
|
|
1669
|
+
log.error(blocker.message);
|
|
1670
|
+
}
|
|
1671
|
+
for (const warning of result.plan.warnings) {
|
|
1672
|
+
log.warn(warning.message);
|
|
1673
|
+
}
|
|
1674
|
+
if (manualSteps.length > 0 || result.plan.blockers.length > 0) {
|
|
1675
|
+
log.blank();
|
|
1676
|
+
log.warn("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Manual Steps Required \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1677
|
+
manualSteps.forEach((step) => log.error(step));
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
if (result.plan.warnings.length > 0) {
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
log.ready("Ready! Hold Alt + Click any element to inspect.");
|
|
1684
|
+
}
|
|
1685
|
+
async function apply(options = {}) {
|
|
1686
|
+
const root = process.cwd();
|
|
1687
|
+
const context = await buildOnboardingContext(root);
|
|
1688
|
+
const plan2 = createPlanResult(context);
|
|
1689
|
+
const selectedProvider = context.providers.find((provider) => provider.id === plan2.defaults.provider) ?? null;
|
|
1690
|
+
const selectedIDE = context.ides.find((ide) => ide.ide === plan2.defaults.ide) ?? context.ides.find((ide) => ide.supported) ?? null;
|
|
1691
|
+
const applyResult = await applyOnboardingPlan({
|
|
1692
|
+
repoRoot: root,
|
|
1693
|
+
projectRoot: root,
|
|
1694
|
+
packageManager: context.packageManager,
|
|
1695
|
+
supportedBuildTargets: context.buildTools.supported,
|
|
1696
|
+
options: {
|
|
1697
|
+
shared: options.shared ?? plan2.defaults.shared,
|
|
1698
|
+
skipInstall: options.skipInstall ?? false,
|
|
1699
|
+
dryRun: options.dryRun ?? false,
|
|
1700
|
+
noExtension: options.noExtension ?? !plan2.defaults.extension,
|
|
1701
|
+
quiet: options.json ?? false
|
|
1702
|
+
},
|
|
1703
|
+
selectedIDE,
|
|
1704
|
+
providerDefault: getProviderDefault(plan2.defaults.provider, selectedProvider?.preferredMode),
|
|
1705
|
+
plan: plan2
|
|
1706
|
+
});
|
|
1707
|
+
const result = {
|
|
1708
|
+
...applyResult,
|
|
1709
|
+
status: mergeStatus(plan2.status, applyResult.status),
|
|
1710
|
+
plan: plan2
|
|
1711
|
+
};
|
|
1712
|
+
return writeCommandOutput(result, options.json ?? false, printApplyResult);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
// src/commands/detect.ts
|
|
1716
|
+
function printDetectionResult(result) {
|
|
1717
|
+
const suppressedCodes = /* @__PURE__ */ new Set([
|
|
1718
|
+
"unsupported-build-tool",
|
|
1719
|
+
"unsupported-build-tool-present",
|
|
1720
|
+
"unsupported-framework",
|
|
1721
|
+
"unsupported-framework-present"
|
|
1722
|
+
]);
|
|
1723
|
+
log.header("Inspecto Detect");
|
|
1724
|
+
log.info(`Status: ${result.status}`);
|
|
1725
|
+
log.info(`Root: ${result.project.root}`);
|
|
1726
|
+
log.info(`Package manager: ${result.project.packageManager}`);
|
|
1727
|
+
if (result.environment.frameworks.length > 0) {
|
|
1728
|
+
log.success(`Supported frameworks: ${result.environment.frameworks.join(", ")}`);
|
|
1729
|
+
}
|
|
1730
|
+
if (result.environment.unsupportedFrameworks.length > 0) {
|
|
1731
|
+
log.warn(`Unsupported frameworks: ${result.environment.unsupportedFrameworks.join(", ")}`);
|
|
1732
|
+
}
|
|
1733
|
+
if (result.environment.buildTools.length > 0) {
|
|
1734
|
+
log.success(
|
|
1735
|
+
`Supported build tools: ${result.environment.buildTools.map((tool) => tool.label).join(", ")}`
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
if (result.environment.unsupportedBuildTools.length > 0) {
|
|
1739
|
+
log.warn(`Unsupported build tools: ${result.environment.unsupportedBuildTools.join(", ")}`);
|
|
1740
|
+
}
|
|
1741
|
+
const supportedIdes = result.environment.ides.filter((ide) => ide.supported).map((ide) => ide.ide);
|
|
1742
|
+
if (supportedIdes.length > 0) {
|
|
1743
|
+
log.success(`Supported IDEs: ${supportedIdes.join(", ")}`);
|
|
1744
|
+
}
|
|
1745
|
+
const supportedProviders = result.environment.providers.filter((provider) => provider.supported).map((provider) => provider.label);
|
|
1746
|
+
if (supportedProviders.length > 0) {
|
|
1747
|
+
log.success(`Supported providers: ${supportedProviders.join(", ")}`);
|
|
1748
|
+
}
|
|
1749
|
+
for (const blocker of result.blockers) {
|
|
1750
|
+
if (suppressedCodes.has(blocker.code)) continue;
|
|
1751
|
+
log.error(blocker.message);
|
|
1752
|
+
}
|
|
1753
|
+
for (const warning of result.warnings) {
|
|
1754
|
+
if (suppressedCodes.has(warning.code)) continue;
|
|
1755
|
+
log.warn(warning.message);
|
|
1016
1756
|
}
|
|
1017
1757
|
}
|
|
1758
|
+
async function detect(json = false) {
|
|
1759
|
+
const result = await createDetectionResult(process.cwd());
|
|
1760
|
+
return writeCommandOutput(result, json, printDetectionResult);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/commands/init.ts
|
|
1764
|
+
import path10 from "path";
|
|
1018
1765
|
|
|
1019
1766
|
// src/prompts.ts
|
|
1020
1767
|
import prompts from "prompts";
|
|
@@ -1055,9 +1802,9 @@ async function promptProviderChoice(detections) {
|
|
|
1055
1802
|
title: `${d.label} ${d.supported ? `(supported ${modeStr})` : "(unsupported/limited)"}`,
|
|
1056
1803
|
value: i
|
|
1057
1804
|
};
|
|
1058
|
-
})
|
|
1805
|
+
}).concat({ title: "Skip (configure later)", value: -1 })
|
|
1059
1806
|
});
|
|
1060
|
-
if (choice === void 0) return null;
|
|
1807
|
+
if (choice === void 0 || choice === -1) return null;
|
|
1061
1808
|
return detections[choice];
|
|
1062
1809
|
}
|
|
1063
1810
|
async function promptConfigChoice(detections) {
|
|
@@ -1083,6 +1830,33 @@ async function promptConfigChoice(detections) {
|
|
|
1083
1830
|
if (choice === void 0 || choice === -1) return null;
|
|
1084
1831
|
return detections[choice];
|
|
1085
1832
|
}
|
|
1833
|
+
async function promptMonorepoPackageChoice(detections) {
|
|
1834
|
+
const uniquePackages = Array.from(
|
|
1835
|
+
new Map(
|
|
1836
|
+
detections.filter((d) => !!d.packagePath).map((d) => [d.packagePath, d])
|
|
1837
|
+
).values()
|
|
1838
|
+
);
|
|
1839
|
+
if (uniquePackages.length === 0) {
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
if (!process.stdin.isTTY) {
|
|
1843
|
+
log.error("Monorepo root detected, but stdin is not interactive.");
|
|
1844
|
+
log.hint("Re-run `inspecto init` inside a specific app directory, or pass --packages <app-path>.");
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1847
|
+
const { choice } = await prompts({
|
|
1848
|
+
type: "select",
|
|
1849
|
+
name: "choice",
|
|
1850
|
+
message: "Monorepo root detected. Choose the app to initialize:",
|
|
1851
|
+
choices: uniquePackages.map((d, i) => ({
|
|
1852
|
+
title: `${d.packagePath} (${d.tool})`,
|
|
1853
|
+
description: d.configPath,
|
|
1854
|
+
value: i
|
|
1855
|
+
}))
|
|
1856
|
+
});
|
|
1857
|
+
if (choice === void 0) return null;
|
|
1858
|
+
return uniquePackages[choice].packagePath;
|
|
1859
|
+
}
|
|
1086
1860
|
async function promptUnsupportedFrameworkContinue() {
|
|
1087
1861
|
if (!process.stdin.isTTY) {
|
|
1088
1862
|
log.error("Unsupported framework detected in non-interactive environment.");
|
|
@@ -1101,60 +1875,75 @@ async function promptUnsupportedFrameworkContinue() {
|
|
|
1101
1875
|
// src/instructions.ts
|
|
1102
1876
|
function printNuxtManualInstructions() {
|
|
1103
1877
|
log.blank();
|
|
1104
|
-
log.hint("
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1878
|
+
log.hint("Nuxt requires manual setup in the current version.");
|
|
1879
|
+
log.hint("1. Update `nuxt.config.ts` to register the Inspecto Vite plugin:");
|
|
1880
|
+
log.copyableCodeBlock([
|
|
1881
|
+
"import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
1882
|
+
"",
|
|
1883
|
+
"export default defineNuxtConfig({",
|
|
1884
|
+
" vite: {",
|
|
1885
|
+
" plugins: [inspecto()],",
|
|
1886
|
+
" },",
|
|
1887
|
+
"})"
|
|
1888
|
+
]);
|
|
1889
|
+
log.hint("2. Create `plugins/inspecto.client.ts` to mount `@inspecto-dev/core` in development:");
|
|
1890
|
+
log.copyableCodeBlock([
|
|
1891
|
+
"export default defineNuxtPlugin(() => {",
|
|
1892
|
+
" if (import.meta.dev) {",
|
|
1893
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
1894
|
+
" mountInspector()",
|
|
1895
|
+
" })",
|
|
1896
|
+
" }",
|
|
1897
|
+
"})"
|
|
1898
|
+
]);
|
|
1899
|
+
log.hint("3. Restart your Nuxt dev server after updating the config.");
|
|
1123
1900
|
}
|
|
1124
1901
|
function printNextJsManualInstructions() {
|
|
1125
1902
|
log.blank();
|
|
1126
|
-
log.hint("
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
},
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1903
|
+
log.hint("Next.js requires manual setup in the current version.");
|
|
1904
|
+
log.hint("1. Update `next.config.mjs` to register the Inspecto webpack plugin:");
|
|
1905
|
+
log.copyableCodeBlock([
|
|
1906
|
+
"import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
|
|
1907
|
+
"",
|
|
1908
|
+
"/** @type {import('next').NextConfig} */",
|
|
1909
|
+
"const nextConfig = {",
|
|
1910
|
+
" webpack: (config, { dev, isServer }) => {",
|
|
1911
|
+
" if (dev && !isServer) {",
|
|
1912
|
+
" config.plugins.push(inspecto())",
|
|
1913
|
+
" }",
|
|
1914
|
+
" return config",
|
|
1915
|
+
" },",
|
|
1916
|
+
"}",
|
|
1917
|
+
"",
|
|
1918
|
+
"export default nextConfig"
|
|
1919
|
+
]);
|
|
1920
|
+
log.hint(
|
|
1921
|
+
"2. Initialize `@inspecto-dev/core` from a client component such as `app/layout.tsx` or `pages/_app.tsx`:"
|
|
1922
|
+
);
|
|
1923
|
+
log.copyableCodeBlock([
|
|
1924
|
+
"'use client'",
|
|
1925
|
+
"",
|
|
1926
|
+
"import { useEffect } from 'react'",
|
|
1927
|
+
"",
|
|
1928
|
+
"export default function RootLayout({ children }) {",
|
|
1929
|
+
" useEffect(() => {",
|
|
1930
|
+
" if (process.env.NODE_ENV !== 'production') {",
|
|
1931
|
+
" import('@inspecto-dev/core').then(({ mountInspector }) => {",
|
|
1932
|
+
" mountInspector({ serverUrl: 'http://127.0.0.1:5678' })",
|
|
1933
|
+
" })",
|
|
1934
|
+
" }",
|
|
1935
|
+
" }, [])",
|
|
1936
|
+
"",
|
|
1937
|
+
" return <html><body>{children}</body></html>",
|
|
1938
|
+
"}"
|
|
1939
|
+
]);
|
|
1940
|
+
log.hint("3. Restart your Next.js dev server after updating the config.");
|
|
1153
1941
|
}
|
|
1154
1942
|
|
|
1155
1943
|
// src/commands/init.ts
|
|
1156
1944
|
async function init(options) {
|
|
1157
|
-
const
|
|
1945
|
+
const repoRoot = process.cwd();
|
|
1946
|
+
let projectRoot = repoRoot;
|
|
1158
1947
|
const mutations = [];
|
|
1159
1948
|
const normalizedPackages = normalizePackageList(options.packages);
|
|
1160
1949
|
const verifiedPackages = [];
|
|
@@ -1163,7 +1952,7 @@ async function init(options) {
|
|
|
1163
1952
|
verifiedPackages.push(pkg);
|
|
1164
1953
|
continue;
|
|
1165
1954
|
}
|
|
1166
|
-
const absolutePath =
|
|
1955
|
+
const absolutePath = path10.join(repoRoot, pkg);
|
|
1167
1956
|
if (await exists(absolutePath)) {
|
|
1168
1957
|
verifiedPackages.push(pkg);
|
|
1169
1958
|
} else {
|
|
@@ -1176,21 +1965,54 @@ async function init(options) {
|
|
|
1176
1965
|
return;
|
|
1177
1966
|
}
|
|
1178
1967
|
log.header("Inspecto Setup");
|
|
1179
|
-
if (!await exists(
|
|
1968
|
+
if (!await exists(path10.join(repoRoot, "package.json"))) {
|
|
1180
1969
|
log.error("No package.json found in current directory");
|
|
1181
1970
|
log.hint("Run this command from your project root");
|
|
1182
1971
|
return;
|
|
1183
1972
|
}
|
|
1184
|
-
const
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1973
|
+
const pm = await detectPackageManager(repoRoot);
|
|
1974
|
+
let buildResult = await detectBuildTools(
|
|
1975
|
+
repoRoot,
|
|
1976
|
+
verifiedPackages.length > 0 ? verifiedPackages : void 0
|
|
1977
|
+
);
|
|
1978
|
+
if (verifiedPackages.length === 0) {
|
|
1979
|
+
const monorepoTargets = Array.from(
|
|
1980
|
+
new Set(
|
|
1981
|
+
buildResult.supported.map((d) => d.packagePath).filter((value) => !!value)
|
|
1982
|
+
)
|
|
1983
|
+
);
|
|
1984
|
+
if (monorepoTargets.length > 1) {
|
|
1985
|
+
log.warn("Monorepo root detected with multiple candidate apps.");
|
|
1986
|
+
const selectedPackage = await promptMonorepoPackageChoice(buildResult.supported);
|
|
1987
|
+
if (!selectedPackage) {
|
|
1988
|
+
log.hint("Run `inspecto init` inside the target app, or pass --packages <app-path>.");
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
projectRoot = path10.join(repoRoot, selectedPackage);
|
|
1992
|
+
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
1993
|
+
log.info(`Continuing initialization in ${selectedPackage}`);
|
|
1994
|
+
} else if (monorepoTargets.length === 1) {
|
|
1995
|
+
const [selectedPackage] = monorepoTargets;
|
|
1996
|
+
projectRoot = path10.join(repoRoot, selectedPackage);
|
|
1997
|
+
buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
|
|
1998
|
+
log.warn(`Monorepo root detected. Using the only candidate app: ${selectedPackage}`);
|
|
1999
|
+
log.hint("Run `inspecto init` inside that app next time to skip this prompt.");
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
const [frameworkResult, ideProbe, providerProbe] = await Promise.all([
|
|
2003
|
+
detectFrameworks(projectRoot),
|
|
2004
|
+
detectIDE(projectRoot),
|
|
2005
|
+
detectProviders(projectRoot)
|
|
1190
2006
|
]);
|
|
1191
2007
|
log.success(`Detected package manager: ${pm}`);
|
|
1192
2008
|
if (frameworkResult.supported.length > 0) {
|
|
1193
|
-
|
|
2009
|
+
const frameworks = frameworkResult.supported.join(", ");
|
|
2010
|
+
log.success(`Detected framework: ${frameworks}`);
|
|
2011
|
+
if (frameworkResult.unsupported.length > 0) {
|
|
2012
|
+
log.hint(
|
|
2013
|
+
`Other frameworks detected (${frameworkResult.unsupported.map((f) => f.name).join(", ")}) will be skipped in this setup.`
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
1194
2016
|
}
|
|
1195
2017
|
const isSupported = frameworkResult.supported.length > 0;
|
|
1196
2018
|
const hasUnsupported = frameworkResult.unsupported.length > 0;
|
|
@@ -1219,7 +2041,9 @@ async function init(options) {
|
|
|
1219
2041
|
}
|
|
1220
2042
|
let manualConfigRequiredFor = "";
|
|
1221
2043
|
if (verifiedPackages.length > 0 && buildResult.supported.length === 0) {
|
|
1222
|
-
log.warn(
|
|
2044
|
+
log.warn(
|
|
2045
|
+
`No supported build configs detected for: ${verifiedPackages.map((pkg) => pkg ? pkg : ".").join(", ")}`
|
|
2046
|
+
);
|
|
1223
2047
|
log.hint("Double-check the --packages values or run without the flag to scan the repo root");
|
|
1224
2048
|
}
|
|
1225
2049
|
if (buildResult.supported.length > 0) {
|
|
@@ -1230,6 +2054,12 @@ async function init(options) {
|
|
|
1230
2054
|
manualConfigRequiredFor = buildResult.unsupported[0] || "";
|
|
1231
2055
|
log.warn(`Detected ${names} \u2014 automatic plugin injection is not supported in current version`);
|
|
1232
2056
|
log.hint("You can still manually configure it by modifying your configuration file");
|
|
2057
|
+
if (buildResult.unsupported.includes("Next.js")) {
|
|
2058
|
+
printNextJsManualInstructions();
|
|
2059
|
+
}
|
|
2060
|
+
if (buildResult.unsupported.includes("Nuxt")) {
|
|
2061
|
+
printNuxtManualInstructions();
|
|
2062
|
+
}
|
|
1233
2063
|
}
|
|
1234
2064
|
if (buildResult.supported.length === 0 && buildResult.unsupported.length === 0) {
|
|
1235
2065
|
log.warn("No recognized build tool detected");
|
|
@@ -1260,6 +2090,7 @@ async function init(options) {
|
|
|
1260
2090
|
}
|
|
1261
2091
|
}
|
|
1262
2092
|
let selectedProvider = null;
|
|
2093
|
+
const explicitProvider = options.provider ? providerProbe.detected.find((provider) => provider.id === options.provider) ?? null : null;
|
|
1263
2094
|
if (!options.provider) {
|
|
1264
2095
|
if (providerProbe.detected.length === 0) {
|
|
1265
2096
|
log.warn("No supported AI tools detected");
|
|
@@ -1270,36 +2101,17 @@ async function init(options) {
|
|
|
1270
2101
|
log.success(`Detected AI tool: ${selectedProvider.label}`);
|
|
1271
2102
|
}
|
|
1272
2103
|
} else {
|
|
2104
|
+
log.info("Multiple providers detected, waiting for your selection...");
|
|
1273
2105
|
selectedProvider = await promptProviderChoice(providerProbe.detected);
|
|
1274
2106
|
if (selectedProvider) {
|
|
1275
2107
|
log.success(`Selected provider: ${selectedProvider.label}`);
|
|
2108
|
+
} else {
|
|
2109
|
+
log.warn("No provider selected. You can set provider.default later in .inspecto/settings.");
|
|
1276
2110
|
}
|
|
1277
2111
|
}
|
|
1278
2112
|
}
|
|
1279
|
-
let
|
|
1280
|
-
|
|
1281
|
-
log.warn("Skipping dependency installation (--skip-install)");
|
|
1282
|
-
} else {
|
|
1283
|
-
const installCmd = getInstallCommand(pm, "@inspecto-dev/plugin @inspecto-dev/core");
|
|
1284
|
-
if (options.dryRun) {
|
|
1285
|
-
log.dryRun(`Would run: ${installCmd}`);
|
|
1286
|
-
} else {
|
|
1287
|
-
try {
|
|
1288
|
-
const result = await shell(installCmd, root);
|
|
1289
|
-
if (result.stderr && result.stderr.toLowerCase().includes("error")) {
|
|
1290
|
-
throw new Error(result.stderr);
|
|
1291
|
-
}
|
|
1292
|
-
log.success("Installed @inspecto-dev/plugin and @inspecto-dev/core as devDependencies");
|
|
1293
|
-
mutations.push({ type: "dependency_added", name: "@inspecto-dev/plugin", dev: true });
|
|
1294
|
-
mutations.push({ type: "dependency_added", name: "@inspecto-dev/core", dev: true });
|
|
1295
|
-
} catch (err) {
|
|
1296
|
-
installFailed = true;
|
|
1297
|
-
log.error(`Failed to install dependency: ${err?.message || "Unknown error"}`);
|
|
1298
|
-
log.hint(`Run manually: ${installCmd}`);
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
let injectionFailed = false;
|
|
2113
|
+
let injectionSkippedRequiresManualConfig = false;
|
|
2114
|
+
const supportedBuildTargets = [];
|
|
1303
2115
|
if (buildResult.supported.length > 0) {
|
|
1304
2116
|
if (verifiedPackages.length > 0) {
|
|
1305
2117
|
const targets = buildResult.supported.filter(
|
|
@@ -1315,137 +2127,54 @@ async function init(options) {
|
|
|
1315
2127
|
log.hint("Check the package paths or run without --packages to inspect the repo root");
|
|
1316
2128
|
}
|
|
1317
2129
|
if (targets.length === 0) {
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
for (const target of targets) {
|
|
1321
|
-
const result = await injectPlugin(root, target, options.dryRun);
|
|
1322
|
-
if (result.success) {
|
|
1323
|
-
mutations.push(...result.mutations);
|
|
1324
|
-
} else {
|
|
1325
|
-
injectionFailed = true;
|
|
1326
|
-
}
|
|
2130
|
+
injectionSkippedRequiresManualConfig = true;
|
|
1327
2131
|
}
|
|
2132
|
+
supportedBuildTargets.push(...targets);
|
|
1328
2133
|
} else {
|
|
1329
2134
|
let target = resolveInjectionTarget(buildResult.supported);
|
|
1330
2135
|
if (target === "ambiguous") {
|
|
1331
2136
|
target = await promptConfigChoice(buildResult.supported);
|
|
1332
2137
|
}
|
|
1333
2138
|
if (target) {
|
|
1334
|
-
|
|
1335
|
-
if (result.success) {
|
|
1336
|
-
mutations.push(...result.mutations);
|
|
1337
|
-
} else {
|
|
1338
|
-
injectionFailed = true;
|
|
1339
|
-
}
|
|
2139
|
+
supportedBuildTargets.push(target);
|
|
1340
2140
|
} else {
|
|
1341
|
-
|
|
2141
|
+
injectionSkippedRequiresManualConfig = true;
|
|
1342
2142
|
log.warn("Skipping plugin injection (manual configuration required)");
|
|
1343
2143
|
}
|
|
1344
2144
|
}
|
|
1345
2145
|
}
|
|
1346
|
-
const
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
const tool = options.provider;
|
|
1366
|
-
const mode = tool === "coco" ? "cli" : "extension";
|
|
1367
|
-
defaultSettings["provider.default"] = `${tool}.${mode}`;
|
|
1368
|
-
} else if (selectedProvider) {
|
|
1369
|
-
const toolId = selectedProvider.id;
|
|
1370
|
-
const mode = selectedProvider.preferredMode === "cli" ? "cli" : "extension";
|
|
1371
|
-
defaultSettings["provider.default"] = `${toolId}.${mode}`;
|
|
1372
|
-
}
|
|
1373
|
-
if (options.dryRun) {
|
|
1374
|
-
log.dryRun(`Would create .inspecto/${settingsFileName}`);
|
|
1375
|
-
} else {
|
|
1376
|
-
await writeJSON(settingsPath, defaultSettings);
|
|
1377
|
-
log.success(`Created .inspecto/${settingsFileName}`);
|
|
1378
|
-
mutations.push({ type: "file_created", path: `.inspecto/${settingsFileName}` });
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
if (await exists(promptsPath)) {
|
|
1382
|
-
log.success(`.inspecto/${promptsFileName} already exists (skipped)`);
|
|
1383
|
-
} else {
|
|
1384
|
-
const defaultPrompts = [];
|
|
1385
|
-
if (options.dryRun) {
|
|
1386
|
-
log.dryRun(`Would create .inspecto/${promptsFileName}`);
|
|
1387
|
-
} else {
|
|
1388
|
-
await writeJSON(promptsPath, defaultPrompts);
|
|
1389
|
-
log.success(`Created .inspecto/${promptsFileName}`);
|
|
1390
|
-
mutations.push({ type: "file_created", path: `.inspecto/${promptsFileName}` });
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
if (!options.dryRun) {
|
|
1394
|
-
await updateGitignore(root, options.shared, options.dryRun);
|
|
1395
|
-
mutations.push({
|
|
1396
|
-
type: "file_modified",
|
|
1397
|
-
path: ".gitignore",
|
|
1398
|
-
description: "Appended .inspecto/ ignore rules"
|
|
1399
|
-
});
|
|
1400
|
-
} else {
|
|
1401
|
-
log.dryRun("Would update .gitignore");
|
|
1402
|
-
}
|
|
1403
|
-
if (!options.dryRun && mutations.length > 0) {
|
|
1404
|
-
const lock = {
|
|
1405
|
-
version: "1.0.0",
|
|
1406
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1407
|
-
mutations
|
|
1408
|
-
};
|
|
1409
|
-
await writeJSON(path9.join(settingsDir, "install.lock"), lock);
|
|
1410
|
-
}
|
|
1411
|
-
const shouldInstallExt = !options.noExtension && (!selectedIDE || selectedIDE && selectedIDE.supported);
|
|
1412
|
-
let manualExtensionInstallNeeded = false;
|
|
1413
|
-
if (options.noExtension) {
|
|
1414
|
-
log.warn("Skipping IDE extension (--no-extension)");
|
|
1415
|
-
} else if (!shouldInstallExt) {
|
|
1416
|
-
} else {
|
|
1417
|
-
const extMutation = await installExtension(options.dryRun, selectedIDE?.ide);
|
|
1418
|
-
if (extMutation && !options.dryRun) {
|
|
1419
|
-
mutations.push(extMutation);
|
|
1420
|
-
if (extMutation.manual_action_required) {
|
|
1421
|
-
manualExtensionInstallNeeded = true;
|
|
1422
|
-
}
|
|
1423
|
-
const lockPath = path9.join(settingsDir, "install.lock");
|
|
1424
|
-
const lock = await readJSON(lockPath);
|
|
1425
|
-
if (lock) {
|
|
1426
|
-
lock.mutations = mutations;
|
|
1427
|
-
await writeJSON(lockPath, lock);
|
|
1428
|
-
}
|
|
1429
|
-
} else if (extMutation === null && !options.dryRun) {
|
|
1430
|
-
manualExtensionInstallNeeded = true;
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
2146
|
+
const providerDefault = options.provider ? `${options.provider}.${explicitProvider?.preferredMode ?? (options.provider === "coco" ? "cli" : "extension")}` : selectedProvider ? `${selectedProvider.id}.${selectedProvider.preferredMode === "cli" ? "cli" : "extension"}` : void 0;
|
|
2147
|
+
const applyResult = await applyOnboardingPlan({
|
|
2148
|
+
repoRoot,
|
|
2149
|
+
projectRoot,
|
|
2150
|
+
packageManager: pm,
|
|
2151
|
+
supportedBuildTargets,
|
|
2152
|
+
options: {
|
|
2153
|
+
shared: options.shared,
|
|
2154
|
+
skipInstall: options.skipInstall,
|
|
2155
|
+
dryRun: options.dryRun,
|
|
2156
|
+
noExtension: options.noExtension
|
|
2157
|
+
},
|
|
2158
|
+
selectedIDE,
|
|
2159
|
+
providerDefault,
|
|
2160
|
+
manualConfigRequiredFor,
|
|
2161
|
+
injectionSkippedRequiresManualConfig,
|
|
2162
|
+
allowManualPlanApply: true
|
|
2163
|
+
});
|
|
2164
|
+
mutations.push(...applyResult.mutations);
|
|
1433
2165
|
if (options.dryRun) {
|
|
1434
2166
|
log.blank();
|
|
1435
2167
|
log.warn("Dry run complete. No files were modified.");
|
|
1436
|
-
} else
|
|
2168
|
+
} else {
|
|
1437
2169
|
log.blank();
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
2170
|
+
if (applyResult.postInstall.nextSteps.length > 0) {
|
|
2171
|
+
log.warn("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 Manual Steps Required \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2172
|
+
applyResult.postInstall.nextSteps.forEach((step) => log.error(step));
|
|
2173
|
+
log.hint("Complete the items above.");
|
|
2174
|
+
log.blank();
|
|
1443
2175
|
} else {
|
|
1444
|
-
log.
|
|
2176
|
+
log.ready("Ready! Hold Alt + Click any element to inspect.");
|
|
1445
2177
|
}
|
|
1446
|
-
log.blank();
|
|
1447
|
-
} else {
|
|
1448
|
-
log.ready("Ready! Hold Alt + Click any element to inspect.");
|
|
1449
2178
|
}
|
|
1450
2179
|
}
|
|
1451
2180
|
function normalizePackageList(packages) {
|
|
@@ -1471,15 +2200,66 @@ function matchesAnyPackage(detection, packages) {
|
|
|
1471
2200
|
}
|
|
1472
2201
|
|
|
1473
2202
|
// src/commands/doctor.ts
|
|
1474
|
-
import
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
2203
|
+
import path11 from "path";
|
|
2204
|
+
function createDiagnostic(code, status, message2, hints = [], details) {
|
|
2205
|
+
return {
|
|
2206
|
+
code,
|
|
2207
|
+
status,
|
|
2208
|
+
message: message2,
|
|
2209
|
+
hints,
|
|
2210
|
+
...details ? { details } : {}
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
function doctorStatus(errors, warnings) {
|
|
2214
|
+
if (errors > 0) return "blocked";
|
|
2215
|
+
if (warnings > 0) return "warning";
|
|
2216
|
+
return "ok";
|
|
2217
|
+
}
|
|
2218
|
+
function printDoctorResult(result) {
|
|
1478
2219
|
log.header("Inspecto Doctor");
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
2220
|
+
for (const check of result.checks) {
|
|
2221
|
+
if (check.status === "ok") {
|
|
2222
|
+
log.success(check.message);
|
|
2223
|
+
} else if (check.status === "warning") {
|
|
2224
|
+
log.warn(check.message);
|
|
2225
|
+
} else {
|
|
2226
|
+
log.error(check.message);
|
|
2227
|
+
}
|
|
2228
|
+
for (const hint of check.hints) {
|
|
2229
|
+
log.hint(hint);
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
log.blank();
|
|
2233
|
+
if (result.summary.errors === 0 && result.summary.warnings === 0) {
|
|
2234
|
+
log.success("All checks passed. Hold Alt + Click to start!");
|
|
2235
|
+
} else {
|
|
2236
|
+
const parts = [];
|
|
2237
|
+
if (result.summary.errors > 0) parts.push(`${result.summary.errors} error(s)`);
|
|
2238
|
+
if (result.summary.warnings > 0) parts.push(`${result.summary.warnings} warning(s)`);
|
|
2239
|
+
console.log(
|
|
2240
|
+
` ${parts.join(", ")}. ${result.summary.errors > 0 ? "Fix the errors above to get started." : ""}`
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2243
|
+
log.blank();
|
|
2244
|
+
}
|
|
2245
|
+
async function collectDoctorResult(root = process.cwd()) {
|
|
2246
|
+
const checks = [];
|
|
2247
|
+
if (!await exists(path11.join(root, "package.json"))) {
|
|
2248
|
+
const diagnostic = createDiagnostic(
|
|
2249
|
+
"missing-package-json",
|
|
2250
|
+
"error",
|
|
2251
|
+
"No package.json found",
|
|
2252
|
+
["Run this command from your project root"]
|
|
2253
|
+
);
|
|
2254
|
+
checks.push(diagnostic);
|
|
2255
|
+
return {
|
|
2256
|
+
status: "blocked",
|
|
2257
|
+
summary: { errors: 1, warnings: 0 },
|
|
2258
|
+
project: { root },
|
|
2259
|
+
errors: [diagnostic],
|
|
2260
|
+
warnings: [],
|
|
2261
|
+
checks
|
|
2262
|
+
};
|
|
1483
2263
|
}
|
|
1484
2264
|
const [ideProbe, frameworkResult, providerProbe, pm, buildResult, extInstalled] = await Promise.all([
|
|
1485
2265
|
detectIDE(root),
|
|
@@ -1490,34 +2270,68 @@ async function doctor() {
|
|
|
1490
2270
|
isExtensionInstalled()
|
|
1491
2271
|
]);
|
|
1492
2272
|
if (ideProbe.detected.length === 0) {
|
|
1493
|
-
|
|
1494
|
-
result.warnings++;
|
|
2273
|
+
checks.push(createDiagnostic("ide-not-detected", "warning", "IDE: not detected"));
|
|
1495
2274
|
} else {
|
|
1496
2275
|
const hasSupported = ideProbe.detected.some((d) => d.supported);
|
|
1497
2276
|
if (hasSupported) {
|
|
1498
|
-
|
|
1499
|
-
|
|
2277
|
+
checks.push(
|
|
2278
|
+
createDiagnostic(
|
|
2279
|
+
"ide-supported",
|
|
2280
|
+
"ok",
|
|
2281
|
+
`IDE: ${ideProbe.detected.filter((d) => d.supported).map((d) => d.ide).join(", ")}`,
|
|
2282
|
+
[],
|
|
2283
|
+
{
|
|
2284
|
+
detected: ideProbe.detected
|
|
2285
|
+
}
|
|
2286
|
+
)
|
|
1500
2287
|
);
|
|
1501
2288
|
} else {
|
|
1502
2289
|
const names = ideProbe.detected.map((d) => d.ide).join(", ");
|
|
1503
|
-
|
|
1504
|
-
|
|
2290
|
+
checks.push(
|
|
2291
|
+
createDiagnostic(
|
|
2292
|
+
"ide-unsupported",
|
|
2293
|
+
"warning",
|
|
2294
|
+
`IDE: ${names} (not supported in v1, VS Code, Cursor, Trae only)`,
|
|
2295
|
+
[],
|
|
2296
|
+
{
|
|
2297
|
+
detected: ideProbe.detected
|
|
2298
|
+
}
|
|
2299
|
+
)
|
|
2300
|
+
);
|
|
1505
2301
|
}
|
|
1506
2302
|
}
|
|
1507
2303
|
if (frameworkResult.supported.length > 0) {
|
|
1508
|
-
|
|
2304
|
+
checks.push(
|
|
2305
|
+
createDiagnostic(
|
|
2306
|
+
"framework-supported",
|
|
2307
|
+
"ok",
|
|
2308
|
+
`Framework: ${frameworkResult.supported.join(", ")}`
|
|
2309
|
+
)
|
|
2310
|
+
);
|
|
1509
2311
|
} else if (frameworkResult.unsupported.length > 0) {
|
|
1510
2312
|
const names = frameworkResult.unsupported.map((f) => f.name).join(", ");
|
|
1511
|
-
|
|
1512
|
-
|
|
2313
|
+
checks.push(
|
|
2314
|
+
createDiagnostic(
|
|
2315
|
+
"framework-unsupported",
|
|
2316
|
+
"warning",
|
|
2317
|
+
`Framework: ${names} (not supported in v1, React/Vue only)`
|
|
2318
|
+
)
|
|
2319
|
+
);
|
|
1513
2320
|
} else {
|
|
1514
|
-
|
|
1515
|
-
|
|
2321
|
+
checks.push(
|
|
2322
|
+
createDiagnostic(
|
|
2323
|
+
"framework-not-detected",
|
|
2324
|
+
"warning",
|
|
2325
|
+
"Framework: not detected (React / Vue expected)"
|
|
2326
|
+
)
|
|
2327
|
+
);
|
|
1516
2328
|
}
|
|
1517
2329
|
if (providerProbe.detected.length === 0) {
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
2330
|
+
checks.push(
|
|
2331
|
+
createDiagnostic("provider-missing", "warning", "Provider: none detected", [
|
|
2332
|
+
"Inspecto works best with Claude Code, Trae CLI, or GitHub Copilot"
|
|
2333
|
+
])
|
|
2334
|
+
);
|
|
1521
2335
|
} else {
|
|
1522
2336
|
const aiNames = providerProbe.detected.map((d) => {
|
|
1523
2337
|
const modeLabels = d.providerModes.map(
|
|
@@ -1525,58 +2339,80 @@ async function doctor() {
|
|
|
1525
2339
|
);
|
|
1526
2340
|
return `${d.label} (${modeLabels.join(" & ")})`;
|
|
1527
2341
|
}).join(", ");
|
|
1528
|
-
|
|
2342
|
+
checks.push(createDiagnostic("provider-detected", "ok", `Provider: ${aiNames}`));
|
|
1529
2343
|
}
|
|
1530
|
-
const pluginPath =
|
|
2344
|
+
const pluginPath = path11.join(root, "node_modules", "@inspecto-dev", "plugin");
|
|
1531
2345
|
if (await exists(pluginPath)) {
|
|
1532
|
-
const pkgJson = await readJSON(
|
|
2346
|
+
const pkgJson = await readJSON(path11.join(pluginPath, "package.json"));
|
|
1533
2347
|
const version = pkgJson?.version ?? "unknown";
|
|
1534
|
-
|
|
2348
|
+
checks.push(
|
|
2349
|
+
createDiagnostic("plugin-installed", "ok", `@inspecto-dev/plugin@${version} installed`, [], {
|
|
2350
|
+
version
|
|
2351
|
+
})
|
|
2352
|
+
);
|
|
1535
2353
|
} else {
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
2354
|
+
checks.push(
|
|
2355
|
+
createDiagnostic("plugin-missing", "error", "@inspecto-dev/plugin not installed", [
|
|
2356
|
+
`Fix: ${getInstallCommand(pm, "@inspecto-dev/plugin")}`
|
|
2357
|
+
])
|
|
2358
|
+
);
|
|
1540
2359
|
}
|
|
1541
2360
|
if (buildResult.supported.length > 0) {
|
|
1542
2361
|
let injected = false;
|
|
1543
2362
|
for (const bt of buildResult.supported) {
|
|
1544
|
-
const content = await readFile(
|
|
2363
|
+
const content = await readFile(path11.join(root, bt.configPath));
|
|
1545
2364
|
if (content && content.includes("@inspecto-dev/plugin")) {
|
|
1546
|
-
|
|
2365
|
+
checks.push(
|
|
2366
|
+
createDiagnostic("plugin-configured", "ok", `Plugin configured in ${bt.configPath}`, [], {
|
|
2367
|
+
configPath: bt.configPath,
|
|
2368
|
+
buildTool: bt.tool
|
|
2369
|
+
})
|
|
2370
|
+
);
|
|
1547
2371
|
injected = true;
|
|
1548
2372
|
break;
|
|
1549
2373
|
}
|
|
1550
2374
|
}
|
|
1551
2375
|
if (!injected) {
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
2376
|
+
checks.push(
|
|
2377
|
+
createDiagnostic("plugin-not-configured", "error", "Plugin not configured in any build config", [
|
|
2378
|
+
"Fix: npx @inspecto-dev/cli init"
|
|
2379
|
+
])
|
|
2380
|
+
);
|
|
1555
2381
|
}
|
|
1556
2382
|
} else if (buildResult.unsupported.length > 0) {
|
|
1557
2383
|
const names = buildResult.unsupported.join(", ");
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
2384
|
+
checks.push(
|
|
2385
|
+
createDiagnostic(`build-tool-unsupported`, "warning", `Build tool: ${names} (not supported in v1)`, [
|
|
2386
|
+
"current version supports: Vite, Webpack, Rspack, esbuild, Rollup"
|
|
2387
|
+
])
|
|
2388
|
+
);
|
|
1561
2389
|
} else {
|
|
1562
|
-
|
|
1563
|
-
result.warnings++;
|
|
2390
|
+
checks.push(createDiagnostic("build-tool-missing", "warning", "No recognized build config found"));
|
|
1564
2391
|
}
|
|
1565
2392
|
if (extInstalled) {
|
|
1566
|
-
|
|
2393
|
+
checks.push(createDiagnostic("extension-installed", "ok", "VS Code extension detected"));
|
|
1567
2394
|
} else {
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1570
|
-
|
|
2395
|
+
const hasVSCode = ideProbe.detected.some((d) => d.supported && d.ide === "vscode");
|
|
2396
|
+
const hasSupportedNonVSCode = ideProbe.detected.some((d) => d.supported && d.ide !== "vscode");
|
|
2397
|
+
if (hasSupportedNonVSCode && !hasVSCode) {
|
|
2398
|
+
checks.push(
|
|
2399
|
+
createDiagnostic(
|
|
2400
|
+
"extension-not-applicable",
|
|
2401
|
+
"warning",
|
|
2402
|
+
"VS Code extension not applicable (non-VS Code IDE)"
|
|
2403
|
+
)
|
|
2404
|
+
);
|
|
1571
2405
|
} else {
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
2406
|
+
checks.push(
|
|
2407
|
+
createDiagnostic("extension-missing", "error", "VS Code extension not found", [
|
|
2408
|
+
"Fix: code --install-extension inspecto.inspecto",
|
|
2409
|
+
"Or: https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto"
|
|
2410
|
+
])
|
|
2411
|
+
);
|
|
1576
2412
|
}
|
|
1577
2413
|
}
|
|
1578
|
-
const settingsJsonPath =
|
|
1579
|
-
const settingsLocalPath =
|
|
2414
|
+
const settingsJsonPath = path11.join(root, ".inspecto", "settings.json");
|
|
2415
|
+
const settingsLocalPath = path11.join(root, ".inspecto", "settings.local.json");
|
|
1580
2416
|
const hasSettingsJson = await exists(settingsJsonPath);
|
|
1581
2417
|
const hasSettingsLocal = await exists(settingsLocalPath);
|
|
1582
2418
|
if (hasSettingsJson || hasSettingsLocal) {
|
|
@@ -1584,48 +2420,108 @@ async function doctor() {
|
|
|
1584
2420
|
const fileName = hasSettingsLocal ? "settings.local.json" : "settings.json";
|
|
1585
2421
|
const settings = await readJSON(targetPath);
|
|
1586
2422
|
if (settings) {
|
|
1587
|
-
|
|
2423
|
+
checks.push(createDiagnostic("settings-valid", "ok", `.inspecto/${fileName} valid`));
|
|
1588
2424
|
} else {
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
2425
|
+
checks.push(
|
|
2426
|
+
createDiagnostic(
|
|
2427
|
+
"settings-invalid-json",
|
|
2428
|
+
"error",
|
|
2429
|
+
`.inspecto/${fileName} has invalid JSON`,
|
|
2430
|
+
[
|
|
2431
|
+
"Fix: Manually correct the syntax errors, or delete the file and re-run npx @inspecto-dev/cli init"
|
|
2432
|
+
],
|
|
2433
|
+
{
|
|
2434
|
+
fileName
|
|
2435
|
+
}
|
|
2436
|
+
)
|
|
1592
2437
|
);
|
|
1593
|
-
result.errors++;
|
|
1594
2438
|
}
|
|
1595
2439
|
} else {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2440
|
+
checks.push(
|
|
2441
|
+
createDiagnostic(
|
|
2442
|
+
"settings-missing",
|
|
2443
|
+
"warning",
|
|
2444
|
+
"No .inspecto/settings.json or settings.local.json found (using defaults)",
|
|
2445
|
+
["Optional: npx @inspecto-dev/cli init"]
|
|
2446
|
+
)
|
|
2447
|
+
);
|
|
1599
2448
|
}
|
|
1600
|
-
const gitignoreContent = await readFile(
|
|
2449
|
+
const gitignoreContent = await readFile(path11.join(root, ".gitignore"));
|
|
1601
2450
|
if (gitignoreContent) {
|
|
1602
2451
|
const hasLockIgnore = gitignoreContent.includes(".inspecto/install.lock") || gitignoreContent.includes(".inspecto/");
|
|
1603
2452
|
if (!hasLockIgnore) {
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2453
|
+
checks.push(
|
|
2454
|
+
createDiagnostic(
|
|
2455
|
+
"gitignore-missing-install-lock",
|
|
2456
|
+
"warning",
|
|
2457
|
+
".inspecto/install.lock not in .gitignore",
|
|
2458
|
+
["install.lock contains local machine state and should not be committed"]
|
|
2459
|
+
)
|
|
2460
|
+
);
|
|
1607
2461
|
}
|
|
1608
2462
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
2463
|
+
const errors = checks.filter((check) => check.status === "error");
|
|
2464
|
+
const warnings = checks.filter((check) => check.status === "warning");
|
|
2465
|
+
return {
|
|
2466
|
+
status: doctorStatus(errors.length, warnings.length),
|
|
2467
|
+
summary: {
|
|
2468
|
+
errors: errors.length,
|
|
2469
|
+
warnings: warnings.length
|
|
2470
|
+
},
|
|
2471
|
+
project: {
|
|
2472
|
+
root,
|
|
2473
|
+
packageManager: pm
|
|
2474
|
+
},
|
|
2475
|
+
errors,
|
|
2476
|
+
warnings,
|
|
2477
|
+
checks
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
async function doctor(options = {}) {
|
|
2481
|
+
const json = typeof options === "boolean" ? options : options.json ?? false;
|
|
2482
|
+
const result = await collectDoctorResult(process.cwd());
|
|
2483
|
+
return writeCommandOutput(result, json, printDoctorResult);
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// src/commands/plan.ts
|
|
2487
|
+
function printPlanResult(result) {
|
|
2488
|
+
log.header("Inspecto Plan");
|
|
2489
|
+
log.info(`Status: ${result.status}`);
|
|
2490
|
+
log.info(`Strategy: ${result.strategy}`);
|
|
2491
|
+
if (result.defaults.provider) {
|
|
2492
|
+
log.info(`Default provider: ${result.defaults.provider}`);
|
|
2493
|
+
}
|
|
2494
|
+
if (result.defaults.ide) {
|
|
2495
|
+
log.info(`Default IDE: ${result.defaults.ide}`);
|
|
2496
|
+
}
|
|
2497
|
+
log.info(`Shared mode: ${result.defaults.shared ? "enabled" : "disabled"}`);
|
|
2498
|
+
log.info(`Extension mode: ${result.defaults.extension ? "enabled" : "disabled"}`);
|
|
2499
|
+
if (result.actions.length > 0) {
|
|
2500
|
+
log.blank();
|
|
2501
|
+
log.info("Actions:");
|
|
2502
|
+
for (const action of result.actions) {
|
|
2503
|
+
log.hint(`${action.type}: ${action.target} \u2014 ${action.description}`);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
for (const blocker of result.blockers) {
|
|
2507
|
+
log.error(blocker.message);
|
|
2508
|
+
}
|
|
2509
|
+
for (const warning of result.warnings) {
|
|
2510
|
+
log.warn(warning.message);
|
|
1619
2511
|
}
|
|
1620
|
-
|
|
2512
|
+
}
|
|
2513
|
+
async function plan(json = false) {
|
|
2514
|
+
const context = await buildOnboardingContext(process.cwd());
|
|
2515
|
+
const result = createPlanResult(context);
|
|
2516
|
+
return writeCommandOutput(result, json, printPlanResult);
|
|
1621
2517
|
}
|
|
1622
2518
|
|
|
1623
2519
|
// src/commands/teardown.ts
|
|
1624
|
-
import
|
|
2520
|
+
import path12 from "path";
|
|
1625
2521
|
async function teardown() {
|
|
1626
2522
|
const root = process.cwd();
|
|
1627
2523
|
log.header("Inspecto Teardown");
|
|
1628
|
-
const lockPath =
|
|
2524
|
+
const lockPath = path12.join(root, ".inspecto", "install.lock");
|
|
1629
2525
|
const lock = await readJSON(lockPath);
|
|
1630
2526
|
if (!lock) {
|
|
1631
2527
|
log.warn("No .inspecto/install.lock found. Running in best-effort mode.");
|
|
@@ -1638,8 +2534,8 @@ async function teardown() {
|
|
|
1638
2534
|
} catch {
|
|
1639
2535
|
log.warn("Could not remove @inspecto-dev/plugin (may not be installed)");
|
|
1640
2536
|
}
|
|
1641
|
-
if (await exists(
|
|
1642
|
-
await removeDir(
|
|
2537
|
+
if (await exists(path12.join(root, ".inspecto"))) {
|
|
2538
|
+
await removeDir(path12.join(root, ".inspecto"));
|
|
1643
2539
|
log.success("Deleted .inspecto/ directory");
|
|
1644
2540
|
}
|
|
1645
2541
|
await cleanGitignore(root);
|
|
@@ -1688,7 +2584,7 @@ async function teardown() {
|
|
|
1688
2584
|
}
|
|
1689
2585
|
}
|
|
1690
2586
|
}
|
|
1691
|
-
await removeDir(
|
|
2587
|
+
await removeDir(path12.join(root, ".inspecto"));
|
|
1692
2588
|
log.success("Deleted .inspecto/ directory");
|
|
1693
2589
|
await cleanGitignore(root);
|
|
1694
2590
|
log.blank();
|
|
@@ -1697,8 +2593,13 @@ async function teardown() {
|
|
|
1697
2593
|
}
|
|
1698
2594
|
|
|
1699
2595
|
export {
|
|
1700
|
-
|
|
2596
|
+
writeCommandOutput,
|
|
2597
|
+
reportCommandError,
|
|
2598
|
+
apply,
|
|
2599
|
+
detect,
|
|
1701
2600
|
init,
|
|
2601
|
+
collectDoctorResult,
|
|
1702
2602
|
doctor,
|
|
2603
|
+
plan,
|
|
1703
2604
|
teardown
|
|
1704
2605
|
};
|