@inspecto-dev/cli 0.2.0-alpha.3 → 0.2.0-alpha.5

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @inspecto-dev/cli@0.2.0-alpha.2 build /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
3
+ > @inspecto-dev/cli@0.2.0-alpha.4 build /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
4
4
  > tsup
5
5
 
6
6
  CLI Building entry: src/bin.ts, src/index.ts
@@ -10,11 +10,11 @@
10
10
  CLI Target: node18
11
11
  CLI Cleaning output folder
12
12
  ESM Build start
13
- ESM dist/index.js 109.00 B
13
+ ESM dist/chunk-MIHQGC3L.js 58.03 KB
14
14
  ESM dist/bin.js 2.64 KB
15
- ESM dist/chunk-HIL6365F.js 53.63 KB
16
- ESM ⚡️ Build success in 26ms
15
+ ESM dist/index.js 109.00 B
16
+ ESM ⚡️ Build success in 32ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 1180ms
18
+ DTS ⚡️ Build success in 1096ms
19
19
  DTS dist/bin.d.ts 13.00 B
20
20
  DTS dist/index.d.ts 1.18 KB
@@ -1,15 +1,16 @@
1
1
 
2
- > @inspecto-dev/cli@0.2.0-alpha.2 test /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
2
+ > @inspecto-dev/cli@0.2.0-alpha.4 test /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
3
3
  > vitest run --passWithNoTests
4
4
 
5
5
 
6
6
  RUN v1.6.1 /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
7
7
 
8
- ✓ tests/ide.test.ts (6 tests) 3ms
9
- ✓ tests/framework.test.ts (5 tests) 2ms
8
+ ✓ tests/framework.test.ts (5 tests) 4ms
9
+ ✓ tests/build-tool.test.ts (2 tests) 9ms
10
+ ✓ tests/ide.test.ts (6 tests) 8ms
10
11
 
11
- Test Files 2 passed (2)
12
- Tests 11 passed (11)
13
- Start at 19:47:34
14
- Duration 213ms (transform 48ms, setup 0ms, collect 65ms, tests 5ms, environment 0ms, prepare 121ms)
12
+ Test Files 3 passed (3)
13
+ Tests 13 passed (13)
14
+ Start at 15:41:01
15
+ Duration 771ms (transform 133ms, setup 1ms, collect 221ms, tests 21ms, environment 0ms, prepare 367ms)
15
16
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @inspecto-dev/cli
2
2
 
3
+ ## 0.2.0-alpha.5
4
+
5
+ ### Minor Changes
6
+
7
+ - release alpha test version
8
+
9
+ ## 0.2.0-alpha.4
10
+
11
+ ### Minor Changes
12
+
13
+ - release alpha test version
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies
18
+ - @inspecto-dev/types@0.2.0-alpha.4
19
+
3
20
  ## 0.2.0-alpha.3
4
21
 
5
22
  ### Minor Changes
package/dist/bin.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  init,
4
4
  log,
5
5
  teardown
6
- } from "./chunk-HIL6365F.js";
6
+ } from "./chunk-MIHQGC3L.js";
7
7
 
8
8
  // src/bin.ts
9
9
  import { cac } from "cac";
@@ -205,7 +205,14 @@ async function getResolvedPackageVersion(pkgName, root) {
205
205
  var SUPPORTED_PATTERNS = [
206
206
  {
207
207
  tool: "vite",
208
- files: ["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"],
208
+ files: [
209
+ "vite.config.ts",
210
+ "vite.config.js",
211
+ "vite.config.mts",
212
+ "vite.config.mjs",
213
+ "vite.config.cjs",
214
+ "vite.config.cts"
215
+ ],
209
216
  label: "Vite"
210
217
  },
211
218
  {
@@ -241,117 +248,154 @@ var UNSUPPORTED_META = [
241
248
  { name: "Astro", dep: "astro", files: ["astro.config.mjs", "astro.config.ts"] },
242
249
  { name: "SvelteKit", dep: "@sveltejs/kit", files: ["svelte.config.js", "svelte.config.ts"] }
243
250
  ];
244
- async function detectBuildTools(root) {
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 }];
259
+ }
260
+ return packagePaths.map((pkg) => ({
261
+ packagePath: pkg,
262
+ absolutePath: pkg ? path3.join(root, pkg) : root
263
+ }));
264
+ }
265
+ async function detectBuildTools(root, packagePaths) {
245
266
  const supported = [];
246
- const unsupported = [];
247
- const pkg = await readJSON(path3.join(root, "package.json"));
248
- const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies };
249
- const supportedChecks = SUPPORTED_PATTERNS.map(async (pattern) => {
250
- let hasDep;
251
- let resolvedVersion = null;
252
- if (pattern.tool === "rspack") {
253
- const depName = allDeps["@rspack/cli"] ? "@rspack/cli" : "@rspack/core";
254
- hasDep = !!allDeps["@rspack/cli"] || !!allDeps["@rspack/core"] || isPackageResolvable("@rspack/core", root);
255
- if (hasDep) {
256
- resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("@rspack/core", root);
257
- }
258
- } else if (pattern.tool === "webpack") {
259
- const depName = allDeps["webpack"] ? "webpack" : "webpack-cli";
260
- hasDep = !!allDeps["webpack"] || !!allDeps["webpack-cli"] || isPackageResolvable("webpack", root);
261
- if (hasDep) {
262
- resolvedVersion = allDeps[depName] || await getResolvedPackageVersion("webpack", root);
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);
263
287
  }
264
- } else if (pattern.tool === "rsbuild") {
265
- hasDep = !!allDeps["@rsbuild/core"] || isPackageResolvable("@rsbuild/core", root);
266
- } else {
267
- hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, root);
268
288
  }
269
- let detectedFile = "";
270
- if (pattern.tool === "esbuild" && !hasDep) {
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
+ }
271
296
  return null;
272
- }
273
- for (const file of pattern.files) {
274
- if (await exists(path3.join(root, file))) {
275
- detectedFile = file;
276
- break;
297
+ });
298
+ const unsupportedResults = await Promise.all(unsupportedChecks);
299
+ for (const result of unsupportedResults) {
300
+ if (result) {
301
+ unsupported.add(result);
277
302
  }
278
303
  }
279
- if (hasDep && !detectedFile && (pattern.tool === "esbuild" || pattern.tool === "rollup" || pattern.tool === "webpack" || pattern.tool === "rspack" || pattern.tool === "rsbuild")) {
280
- const scripts = pkg?.scripts || {};
281
- for (const cmd of Object.values(scripts)) {
282
- if (cmd.includes("node ")) {
283
- const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/);
284
- if (match && match[1]) {
285
- if (await exists(path3.join(root, match[1]))) {
286
- if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
287
- detectedFile = match[1];
288
- break;
289
- }
304
+ }
305
+ return { supported, unsupported: Array.from(unsupported) };
306
+ }
307
+ async function detectPattern({
308
+ pattern,
309
+ workspaceRoot,
310
+ targetRoot,
311
+ packagePath,
312
+ allDeps,
313
+ scripts
314
+ }) {
315
+ let hasDep;
316
+ let resolvedVersion = null;
317
+ if (pattern.tool === "rspack") {
318
+ const depName = allDeps["@rspack/cli"] ? "@rspack/cli" : "@rspack/core";
319
+ hasDep = !!allDeps["@rspack/cli"] || !!allDeps["@rspack/core"] || isPackageResolvable("@rspack/core", targetRoot);
320
+ if (hasDep) {
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);
333
+ }
334
+ let detectedFile = "";
335
+ if (pattern.tool === "esbuild" && !hasDep) {
336
+ return null;
337
+ }
338
+ for (const file of pattern.files) {
339
+ if (await exists(path3.join(targetRoot, file))) {
340
+ detectedFile = file;
341
+ break;
342
+ }
343
+ }
344
+ if (hasDep && !detectedFile && (pattern.tool === "esbuild" || pattern.tool === "rollup" || pattern.tool === "webpack" || pattern.tool === "rspack" || pattern.tool === "rsbuild")) {
345
+ for (const cmd of Object.values(scripts)) {
346
+ if (cmd.includes("node ")) {
347
+ const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/);
348
+ if (match && match[1]) {
349
+ if (await exists(path3.join(targetRoot, match[1]))) {
350
+ if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
351
+ detectedFile = match[1];
352
+ break;
290
353
  }
291
354
  }
292
- } else if (cmd.includes(`${pattern.tool} `)) {
293
- if (pattern.tool === "webpack" || pattern.tool === "rspack") {
294
- const configMatch = cmd.match(/--config\s+([^\s]+)/);
295
- if (configMatch && configMatch[1]) {
296
- if (await exists(path3.join(root, configMatch[1]))) {
297
- detectedFile = configMatch[1];
298
- break;
299
- }
355
+ }
356
+ } else if (cmd.includes(`${pattern.tool} `)) {
357
+ if (pattern.tool === "webpack" || pattern.tool === "rspack") {
358
+ const configMatch = cmd.match(/--config\s+([^\s]+)/);
359
+ if (configMatch && configMatch[1]) {
360
+ if (await exists(path3.join(targetRoot, configMatch[1]))) {
361
+ detectedFile = configMatch[1];
362
+ break;
300
363
  }
301
364
  }
302
- if (!detectedFile) {
303
- detectedFile = "package.json (scripts)";
304
- break;
305
- }
306
365
  }
307
- }
308
- }
309
- if (detectedFile) {
310
- let isLegacyRspack = false;
311
- let isLegacyWebpack = false;
312
- if (pattern.tool === "rspack") {
313
- const version = resolvedVersion;
314
- if (version && (version.includes("0.3.") || version.includes("0.2.") || version.includes("0.1."))) {
315
- isLegacyRspack = true;
316
- }
317
- } else if (pattern.tool === "webpack") {
318
- const version = resolvedVersion;
319
- if (version && version.includes("^4") || version?.startsWith("4.")) {
320
- isLegacyWebpack = true;
366
+ if (!detectedFile) {
367
+ detectedFile = "package.json (scripts)";
368
+ break;
321
369
  }
322
370
  }
323
- return {
324
- tool: pattern.tool,
325
- configPath: detectedFile,
326
- label: `${pattern.label} (${detectedFile})${isLegacyRspack ? " [Legacy]" : ""}${isLegacyWebpack ? " [Webpack 4]" : ""}`,
327
- isLegacyRspack,
328
- isLegacyWebpack
329
- };
330
371
  }
372
+ }
373
+ if (!detectedFile) {
331
374
  return null;
332
- });
333
- const supportedResults = await Promise.all(supportedChecks);
334
- for (const result of supportedResults) {
335
- if (result) {
336
- supported.push(result);
337
- }
338
375
  }
339
- const unsupportedChecks = UNSUPPORTED_META.map(async (meta) => {
340
- if (!(meta.dep in allDeps)) return null;
341
- for (const file of meta.files) {
342
- if (await exists(path3.join(root, file))) {
343
- return meta.name;
344
- }
376
+ let isLegacyRspack = false;
377
+ let isLegacyWebpack = false;
378
+ if (pattern.tool === "rspack") {
379
+ const version = resolvedVersion;
380
+ if (version && (version.includes("0.3.") || version.includes("0.2.") || version.includes("0.1."))) {
381
+ isLegacyRspack = true;
345
382
  }
346
- return null;
347
- });
348
- const unsupportedResults = await Promise.all(unsupportedChecks);
349
- for (const result of unsupportedResults) {
350
- if (result) {
351
- unsupported.push(result);
383
+ } else if (pattern.tool === "webpack") {
384
+ const version = resolvedVersion;
385
+ if (version && version.includes("^4") || version?.startsWith("4.")) {
386
+ isLegacyWebpack = true;
352
387
  }
353
388
  }
354
- return { supported, unsupported };
389
+ const absoluteConfig = path3.join(targetRoot, detectedFile);
390
+ const relativeConfig = normalizeRelativePath(workspaceRoot, absoluteConfig);
391
+ return {
392
+ tool: pattern.tool,
393
+ configPath: relativeConfig,
394
+ label: `${pattern.label} (${relativeConfig})${isLegacyRspack ? " [Legacy]" : ""}${isLegacyWebpack ? " [Webpack 4]" : ""}`,
395
+ isLegacyRspack,
396
+ isLegacyWebpack,
397
+ packagePath: packagePath || void 0
398
+ };
355
399
  }
356
400
  function resolveInjectionTarget(detections) {
357
401
  if (detections.length === 0) return null;
@@ -582,7 +626,8 @@ var ViteStrategy = class {
582
626
  inject({ mod, detection }) {
583
627
  addVitePlugin(mod, {
584
628
  from: "@inspecto-dev/plugin",
585
- constructor: "vitePlugin"
629
+ constructor: "inspecto",
630
+ imported: "vitePlugin"
586
631
  });
587
632
  }
588
633
  getManualInstructions(detection, reason) {
@@ -764,8 +809,21 @@ function printManualInstructions(strategy, detection, reason) {
764
809
  }
765
810
  }
766
811
  function isAlreadyInjected(content) {
767
- return /import\s+.*@inspecto-dev\/plugin/.test(content) || /require\(['"]@inspecto-dev\/plugin['"]\)/.test(content) || /import\s+.*ai-dev-inspector/.test(content) || // Legacy support
768
- /require\(['"]ai-dev-inspector['"]\)/.test(content);
812
+ const normalized = content.replace(/\s+/g, " ");
813
+ const importPlugin = /import\s+(.+?)\s+from\s+['"]@inspecto-dev\/plugin['"]/g;
814
+ const requirePlugin = /require\(['"]@inspecto-dev\/plugin['"]\)/;
815
+ const legacyImport = /import\s+.*ai-dev-inspector/.test(normalized);
816
+ const legacyRequire = /require\(['"]ai-dev-inspector['"]\)/.test(normalized);
817
+ if (legacyImport || legacyRequire || requirePlugin.test(normalized)) return true;
818
+ let match;
819
+ importPlugin.lastIndex = 0;
820
+ while (match = importPlugin.exec(normalized)) {
821
+ const importClause = match[1] || "";
822
+ if (/inspecto/.test(importClause) || /vitePlugin/.test(importClause)) {
823
+ return true;
824
+ }
825
+ }
826
+ return false;
769
827
  }
770
828
  async function injectPlugin(root, detection, dryRun) {
771
829
  const configPath = path7.join(root, detection.configPath);
@@ -894,8 +952,12 @@ async function findVSCodeBinary() {
894
952
  async function tryOpenURI(uri) {
895
953
  try {
896
954
  const platform = process.platform;
897
- const openCmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
898
- await shell(`${openCmd} "${uri}"`);
955
+ if (platform === "win32") {
956
+ await shell(`cmd /c start "" "${uri}"`);
957
+ } else {
958
+ const openCmd = platform === "darwin" ? "open" : "xdg-open";
959
+ await shell(`${openCmd} "${uri}"`);
960
+ }
899
961
  return true;
900
962
  } catch {
901
963
  return false;
@@ -1108,6 +1170,25 @@ function printNextJsManualInstructions() {
1108
1170
  async function init(options) {
1109
1171
  const root = process.cwd();
1110
1172
  const mutations = [];
1173
+ const normalizedPackages = normalizePackageList(options.packages);
1174
+ const verifiedPackages = [];
1175
+ for (const pkg of normalizedPackages) {
1176
+ if (!pkg) {
1177
+ verifiedPackages.push(pkg);
1178
+ continue;
1179
+ }
1180
+ const absolutePath = path9.join(root, pkg);
1181
+ if (await exists(absolutePath)) {
1182
+ verifiedPackages.push(pkg);
1183
+ } else {
1184
+ log.warn(`Package path "${pkg}" not found (skipping)`);
1185
+ log.hint("Ensure --packages values are relative to the project root");
1186
+ }
1187
+ }
1188
+ if (normalizedPackages.length > 0 && verifiedPackages.length === 0) {
1189
+ log.error("No valid packages found from --packages input");
1190
+ return;
1191
+ }
1111
1192
  log.header("Inspecto Setup");
1112
1193
  if (!await exists(path9.join(root, "package.json"))) {
1113
1194
  log.error("No package.json found in current directory");
@@ -1117,7 +1198,7 @@ async function init(options) {
1117
1198
  const [pm, frameworkResult, buildResult, ideProbe, providerProbe] = await Promise.all([
1118
1199
  detectPackageManager(root),
1119
1200
  detectFrameworks(root),
1120
- detectBuildTools(root),
1201
+ detectBuildTools(root, verifiedPackages.length > 0 ? verifiedPackages : void 0),
1121
1202
  detectIDE(root),
1122
1203
  detectProviders(root)
1123
1204
  ]);
@@ -1151,6 +1232,12 @@ async function init(options) {
1151
1232
  );
1152
1233
  }
1153
1234
  let manualConfigRequiredFor = "";
1235
+ if (verifiedPackages.length > 0 && buildResult.supported.length === 0) {
1236
+ log.warn(
1237
+ `No supported build configs detected for: ${verifiedPackages.map((pkg) => pkg ? pkg : ".").join(", ")}`
1238
+ );
1239
+ log.hint("Double-check the --packages values or run without the flag to scan the repo root");
1240
+ }
1154
1241
  if (buildResult.supported.length > 0) {
1155
1242
  buildResult.supported.forEach((bt) => log.success(`Detected: ${bt.label}`));
1156
1243
  }
@@ -1230,20 +1317,46 @@ async function init(options) {
1230
1317
  }
1231
1318
  let injectionFailed = false;
1232
1319
  if (buildResult.supported.length > 0) {
1233
- let target = resolveInjectionTarget(buildResult.supported);
1234
- if (target === "ambiguous") {
1235
- target = await promptConfigChoice(buildResult.supported);
1236
- }
1237
- if (target) {
1238
- const result = await injectPlugin(root, target, options.dryRun);
1239
- if (result.success) {
1240
- mutations.push(...result.mutations);
1241
- } else {
1320
+ if (verifiedPackages.length > 0) {
1321
+ const targets = buildResult.supported.filter(
1322
+ (detection) => matchesAnyPackage(detection, verifiedPackages)
1323
+ );
1324
+ const unmatchedPackages = verifiedPackages.filter(
1325
+ (pkg) => !buildResult.supported.some((detection) => matchesPackage(detection, pkg))
1326
+ );
1327
+ if (unmatchedPackages.length > 0) {
1328
+ log.warn(
1329
+ `No supported build configs detected for: ${unmatchedPackages.map((pkg) => pkg ? pkg : ".").join(", ")}`
1330
+ );
1331
+ log.hint("Check the package paths or run without --packages to inspect the repo root");
1332
+ }
1333
+ if (targets.length === 0) {
1242
1334
  injectionFailed = true;
1243
1335
  }
1336
+ for (const target of targets) {
1337
+ const result = await injectPlugin(root, target, options.dryRun);
1338
+ if (result.success) {
1339
+ mutations.push(...result.mutations);
1340
+ } else {
1341
+ injectionFailed = true;
1342
+ }
1343
+ }
1244
1344
  } else {
1245
- injectionFailed = true;
1246
- log.warn("Skipping plugin injection (manual configuration required)");
1345
+ let target = resolveInjectionTarget(buildResult.supported);
1346
+ if (target === "ambiguous") {
1347
+ target = await promptConfigChoice(buildResult.supported);
1348
+ }
1349
+ if (target) {
1350
+ const result = await injectPlugin(root, target, options.dryRun);
1351
+ if (result.success) {
1352
+ mutations.push(...result.mutations);
1353
+ } else {
1354
+ injectionFailed = true;
1355
+ }
1356
+ } else {
1357
+ injectionFailed = true;
1358
+ log.warn("Skipping plugin injection (manual configuration required)");
1359
+ }
1247
1360
  }
1248
1361
  }
1249
1362
  const settingsDir = path9.join(root, ".inspecto");
@@ -1351,6 +1464,27 @@ async function init(options) {
1351
1464
  log.ready("Ready! Hold Alt + Click any element to inspect.");
1352
1465
  }
1353
1466
  }
1467
+ function normalizePackageList(packages) {
1468
+ if (!packages || packages.length === 0) return [];
1469
+ const normalized = packages.map((pkg) => {
1470
+ const trimmed = pkg.trim();
1471
+ if (trimmed === "") return null;
1472
+ if (trimmed === "." || trimmed === "./") return "";
1473
+ return trimmed.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
1474
+ }).filter((value) => value !== null);
1475
+ return Array.from(new Set(normalized));
1476
+ }
1477
+ function matchesPackage(detection, pkg) {
1478
+ const configPath = detection.configPath.replace(/\\/g, "/");
1479
+ if (!pkg) {
1480
+ return !configPath.includes("/");
1481
+ }
1482
+ return configPath === pkg || configPath.startsWith(`${pkg}/`);
1483
+ }
1484
+ function matchesAnyPackage(detection, packages) {
1485
+ if (packages.length === 0) return true;
1486
+ return packages.some((pkg) => matchesPackage(detection, pkg));
1487
+ }
1354
1488
 
1355
1489
  // src/commands/doctor.ts
1356
1490
  import path10 from "path";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  doctor,
3
3
  init,
4
4
  teardown
5
- } from "./chunk-HIL6365F.js";
5
+ } from "./chunk-MIHQGC3L.js";
6
6
  export {
7
7
  doctor,
8
8
  init,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inspecto-dev/cli",
3
- "version": "0.2.0-alpha.3",
3
+ "version": "0.2.0-alpha.5",
4
4
  "description": "CLI tools for Inspecto onboarding and lifecycle management",
5
5
  "keywords": [
6
6
  "inspecto",
@@ -19,7 +19,7 @@
19
19
  "magicast": "^0.5.2",
20
20
  "picocolors": "^1.0.0",
21
21
  "prompts": "^2.4.2",
22
- "@inspecto-dev/types": "0.2.0-alpha.3"
22
+ "@inspecto-dev/types": "0.2.0-alpha.4"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^20.0.0",
@@ -18,7 +18,7 @@ import { detectProviders, type ProviderDetection } from '../detect/provider.js'
18
18
  import { injectPlugin } from '../inject/ast-injector.js'
19
19
  import { updateGitignore } from '../inject/gitignore.js'
20
20
  import { installExtension } from '../inject/extension.js'
21
- import type { InitOptions, InstallLock, Mutation } from '../types.js'
21
+ import type { InitOptions, InstallLock, Mutation, BuildToolDetection } from '../types.js'
22
22
  import {
23
23
  promptIDEChoice,
24
24
  promptProviderChoice,
@@ -30,6 +30,29 @@ import { printNextJsManualInstructions, printNuxtManualInstructions } from '../i
30
30
  export async function init(options: InitOptions): Promise<void> {
31
31
  const root = process.cwd()
32
32
  const mutations: Mutation[] = []
33
+ const normalizedPackages = normalizePackageList(options.packages)
34
+
35
+ const verifiedPackages: string[] = []
36
+ for (const pkg of normalizedPackages) {
37
+ if (!pkg) {
38
+ // Empty string represents the workspace root
39
+ verifiedPackages.push(pkg)
40
+ continue
41
+ }
42
+
43
+ const absolutePath = path.join(root, pkg)
44
+ if (await exists(absolutePath)) {
45
+ verifiedPackages.push(pkg)
46
+ } else {
47
+ log.warn(`Package path "${pkg}" not found (skipping)`)
48
+ log.hint('Ensure --packages values are relative to the project root')
49
+ }
50
+ }
51
+
52
+ if (normalizedPackages.length > 0 && verifiedPackages.length === 0) {
53
+ log.error('No valid packages found from --packages input')
54
+ return
55
+ }
33
56
 
34
57
  log.header('Inspecto Setup')
35
58
 
@@ -44,7 +67,7 @@ export async function init(options: InitOptions): Promise<void> {
44
67
  const [pm, frameworkResult, buildResult, ideProbe, providerProbe] = await Promise.all([
45
68
  detectPackageManager(root),
46
69
  detectFrameworks(root),
47
- detectBuildTools(root),
70
+ detectBuildTools(root, verifiedPackages.length > 0 ? verifiedPackages : undefined),
48
71
  detectIDE(root),
49
72
  detectProviders(root),
50
73
  ])
@@ -87,6 +110,13 @@ export async function init(options: InitOptions): Promise<void> {
87
110
 
88
111
  // Build tool detection
89
112
  let manualConfigRequiredFor = ''
113
+ if (verifiedPackages.length > 0 && buildResult.supported.length === 0) {
114
+ log.warn(
115
+ `No supported build configs detected for: ${verifiedPackages.map(pkg => (pkg ? pkg : '.')).join(', ')}`,
116
+ )
117
+ log.hint('Double-check the --packages values or run without the flag to scan the repo root')
118
+ }
119
+
90
120
  if (buildResult.supported.length > 0) {
91
121
  buildResult.supported.forEach(bt => log.success(`Detected: ${bt.label}`))
92
122
  }
@@ -177,22 +207,54 @@ export async function init(options: InitOptions): Promise<void> {
177
207
  // ---- Step 4: Inject plugin into build config ----
178
208
  let injectionFailed = false
179
209
  if (buildResult.supported.length > 0) {
180
- let target = resolveInjectionTarget(buildResult.supported)
210
+ if (verifiedPackages.length > 0) {
211
+ const targets = buildResult.supported.filter(detection =>
212
+ matchesAnyPackage(detection, verifiedPackages),
213
+ )
181
214
 
182
- if (target === 'ambiguous') {
183
- target = await promptConfigChoice(buildResult.supported)
184
- }
215
+ const unmatchedPackages = verifiedPackages.filter(
216
+ pkg => !buildResult.supported.some(detection => matchesPackage(detection, pkg)),
217
+ )
185
218
 
186
- if (target) {
187
- const result = await injectPlugin(root, target, options.dryRun)
188
- if (result.success) {
189
- mutations.push(...result.mutations)
190
- } else {
219
+ if (unmatchedPackages.length > 0) {
220
+ log.warn(
221
+ `No supported build configs detected for: ${unmatchedPackages
222
+ .map(pkg => (pkg ? pkg : '.'))
223
+ .join(', ')}`,
224
+ )
225
+ log.hint('Check the package paths or run without --packages to inspect the repo root')
226
+ }
227
+
228
+ if (targets.length === 0) {
191
229
  injectionFailed = true
192
230
  }
231
+
232
+ for (const target of targets) {
233
+ const result = await injectPlugin(root, target, options.dryRun)
234
+ if (result.success) {
235
+ mutations.push(...result.mutations)
236
+ } else {
237
+ injectionFailed = true
238
+ }
239
+ }
193
240
  } else {
194
- injectionFailed = true
195
- log.warn('Skipping plugin injection (manual configuration required)')
241
+ let target = resolveInjectionTarget(buildResult.supported)
242
+
243
+ if (target === 'ambiguous') {
244
+ target = await promptConfigChoice(buildResult.supported)
245
+ }
246
+
247
+ if (target) {
248
+ const result = await injectPlugin(root, target, options.dryRun)
249
+ if (result.success) {
250
+ mutations.push(...result.mutations)
251
+ } else {
252
+ injectionFailed = true
253
+ }
254
+ } else {
255
+ injectionFailed = true
256
+ log.warn('Skipping plugin injection (manual configuration required)')
257
+ }
196
258
  }
197
259
  }
198
260
 
@@ -329,3 +391,33 @@ export async function init(options: InitOptions): Promise<void> {
329
391
  log.ready('Ready! Hold Alt + Click any element to inspect.')
330
392
  }
331
393
  }
394
+
395
+ function normalizePackageList(packages?: string[]): string[] {
396
+ if (!packages || packages.length === 0) return []
397
+
398
+ const normalized = packages
399
+ .map(pkg => {
400
+ const trimmed = pkg.trim()
401
+ if (trimmed === '') return null
402
+ if (trimmed === '.' || trimmed === './') return ''
403
+
404
+ return trimmed.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '')
405
+ })
406
+ .filter((value): value is string => value !== null)
407
+
408
+ return Array.from(new Set(normalized))
409
+ }
410
+
411
+ function matchesPackage(detection: BuildToolDetection, pkg: string): boolean {
412
+ const configPath = detection.configPath.replace(/\\/g, '/')
413
+ if (!pkg) {
414
+ return !configPath.includes('/')
415
+ }
416
+
417
+ return configPath === pkg || configPath.startsWith(`${pkg}/`)
418
+ }
419
+
420
+ function matchesAnyPackage(detection: BuildToolDetection, packages: string[]): boolean {
421
+ if (packages.length === 0) return true
422
+ return packages.some(pkg => matchesPackage(detection, pkg))
423
+ }
@@ -53,7 +53,14 @@ async function getResolvedPackageVersion(pkgName: string, root: string): Promise
53
53
  const SUPPORTED_PATTERNS: { tool: BuildTool; files: string[]; label: string }[] = [
54
54
  {
55
55
  tool: 'vite',
56
- files: ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.mjs'],
56
+ files: [
57
+ 'vite.config.ts',
58
+ 'vite.config.js',
59
+ 'vite.config.mts',
60
+ 'vite.config.mjs',
61
+ 'vite.config.cjs',
62
+ 'vite.config.cts',
63
+ ],
57
64
  label: 'Vite',
58
65
  },
59
66
  {
@@ -92,171 +99,223 @@ const UNSUPPORTED_META: { name: string; dep: string; files: string[] }[] = [
92
99
  { name: 'SvelteKit', dep: '@sveltejs/kit', files: ['svelte.config.js', 'svelte.config.ts'] },
93
100
  ]
94
101
 
102
+ interface DetectionTarget {
103
+ /** Relative path provided via --packages ('' for repo root) */
104
+ packagePath: string
105
+ /** Absolute path to run detection from */
106
+ absolutePath: string
107
+ }
108
+
95
109
  export interface BuildToolResult {
96
110
  supported: BuildToolDetection[]
97
111
  unsupported: string[]
98
112
  }
99
113
 
114
+ function normalizeRelativePath(root: string, filePath: string): string {
115
+ const relative = path.relative(root, filePath)
116
+ const normalized = relative.split(path.sep).join('/')
117
+ return normalized || path.basename(filePath)
118
+ }
119
+
120
+ function createTargets(root: string, packagePaths?: string[]): DetectionTarget[] {
121
+ if (!packagePaths || packagePaths.length === 0) {
122
+ return [{ packagePath: '', absolutePath: root }]
123
+ }
124
+
125
+ return packagePaths.map(pkg => ({
126
+ packagePath: pkg,
127
+ absolutePath: pkg ? path.join(root, pkg) : root,
128
+ }))
129
+ }
130
+
100
131
  /**
101
132
  * Detect all build tools / meta-frameworks.
102
133
  * Returns supported tools and recognized-but-unsupported meta-frameworks.
103
134
  */
104
- export async function detectBuildTools(root: string): Promise<BuildToolResult> {
135
+ export async function detectBuildTools(
136
+ root: string,
137
+ packagePaths?: string[],
138
+ ): Promise<BuildToolResult> {
105
139
  const supported: BuildToolDetection[] = []
106
- const unsupported: string[] = []
107
-
108
- // Detect supported build tools (by config file)
109
- const pkg = await readJSON<PackageJSON>(path.join(root, 'package.json'))
110
- const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies }
111
-
112
- const supportedChecks = SUPPORTED_PATTERNS.map(async pattern => {
113
- // 1. Check if the package.json has a dependency for this tool
114
- let hasDep: boolean
115
- let resolvedVersion: string | null = null
116
-
117
- if (pattern.tool === 'rspack') {
118
- const depName = allDeps['@rspack/cli'] ? '@rspack/cli' : '@rspack/core'
119
- hasDep =
120
- !!allDeps['@rspack/cli'] ||
121
- !!allDeps['@rspack/core'] ||
122
- isPackageResolvable('@rspack/core', root)
123
-
124
- if (hasDep) {
125
- resolvedVersion =
126
- allDeps[depName] || (await getResolvedPackageVersion('@rspack/core', root))
140
+ const unsupported = new Set<string>()
141
+ const targets = createTargets(root, packagePaths)
142
+
143
+ for (const target of targets) {
144
+ const pkg = await readJSON<PackageJSON>(path.join(target.absolutePath, 'package.json'))
145
+ const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies }
146
+ const scripts = pkg?.scripts || {}
147
+
148
+ const supportedChecks = SUPPORTED_PATTERNS.map(pattern =>
149
+ detectPattern({
150
+ pattern,
151
+ workspaceRoot: root,
152
+ targetRoot: target.absolutePath,
153
+ packagePath: target.packagePath,
154
+ allDeps,
155
+ scripts,
156
+ }),
157
+ )
158
+
159
+ const supportedResults = await Promise.all(supportedChecks)
160
+ for (const result of supportedResults) {
161
+ if (result) {
162
+ supported.push(result)
127
163
  }
128
- } else if (pattern.tool === 'webpack') {
129
- const depName = allDeps['webpack'] ? 'webpack' : 'webpack-cli'
130
- hasDep =
131
- !!allDeps['webpack'] || !!allDeps['webpack-cli'] || isPackageResolvable('webpack', root)
164
+ }
132
165
 
133
- if (hasDep) {
134
- resolvedVersion = allDeps[depName] || (await getResolvedPackageVersion('webpack', root))
166
+ const unsupportedChecks = UNSUPPORTED_META.map(async meta => {
167
+ if (!(meta.dep in allDeps)) return null
168
+ for (const file of meta.files) {
169
+ if (await exists(path.join(target.absolutePath, file))) {
170
+ return meta.name
171
+ }
172
+ }
173
+ return null
174
+ })
175
+
176
+ const unsupportedResults = await Promise.all(unsupportedChecks)
177
+ for (const result of unsupportedResults) {
178
+ if (result) {
179
+ unsupported.add(result)
135
180
  }
136
- } else if (pattern.tool === 'rsbuild') {
137
- hasDep = !!allDeps['@rsbuild/core'] || isPackageResolvable('@rsbuild/core', root)
138
- } else {
139
- hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, root)
140
181
  }
182
+ }
141
183
 
142
- // 2. Look for config files
143
- let detectedFile = ''
184
+ return { supported, unsupported: Array.from(unsupported) }
185
+ }
144
186
 
145
- // For esbuild, dependency is strictly required
146
- if (pattern.tool === 'esbuild' && !hasDep) {
147
- return null
187
+ interface PatternContext {
188
+ pattern: { tool: BuildTool; files: string[]; label: string }
189
+ workspaceRoot: string
190
+ targetRoot: string
191
+ packagePath: string
192
+ allDeps: Record<string, string | undefined>
193
+ scripts: Record<string, string>
194
+ }
195
+
196
+ async function detectPattern({
197
+ pattern,
198
+ workspaceRoot,
199
+ targetRoot,
200
+ packagePath,
201
+ allDeps,
202
+ scripts,
203
+ }: PatternContext): Promise<BuildToolDetection | null> {
204
+ let hasDep: boolean
205
+ let resolvedVersion: string | null = null
206
+
207
+ if (pattern.tool === 'rspack') {
208
+ const depName = allDeps['@rspack/cli'] ? '@rspack/cli' : '@rspack/core'
209
+ hasDep =
210
+ !!allDeps['@rspack/cli'] ||
211
+ !!allDeps['@rspack/core'] ||
212
+ isPackageResolvable('@rspack/core', targetRoot)
213
+
214
+ if (hasDep) {
215
+ resolvedVersion =
216
+ allDeps[depName] || (await getResolvedPackageVersion('@rspack/core', targetRoot))
148
217
  }
218
+ } else if (pattern.tool === 'webpack') {
219
+ const depName = allDeps['webpack'] ? 'webpack' : 'webpack-cli'
220
+ hasDep =
221
+ !!allDeps['webpack'] || !!allDeps['webpack-cli'] || isPackageResolvable('webpack', targetRoot)
149
222
 
150
- for (const file of pattern.files) {
151
- if (await exists(path.join(root, file))) {
152
- detectedFile = file
153
- break
154
- }
223
+ if (hasDep) {
224
+ resolvedVersion = allDeps[depName] || (await getResolvedPackageVersion('webpack', targetRoot))
155
225
  }
226
+ } else if (pattern.tool === 'rsbuild') {
227
+ hasDep = !!allDeps['@rsbuild/core'] || isPackageResolvable('@rsbuild/core', targetRoot)
228
+ } else {
229
+ hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, targetRoot)
230
+ }
156
231
 
157
- // 3. For esbuild, rollup, and webpack, if they are in dependencies but no standard config is found,
158
- // we still consider them detected (as they are often used with custom scripts or config file names)
159
- if (
160
- hasDep &&
161
- !detectedFile &&
162
- (pattern.tool === 'esbuild' ||
163
- pattern.tool === 'rollup' ||
164
- pattern.tool === 'webpack' ||
165
- pattern.tool === 'rspack' ||
166
- pattern.tool === 'rsbuild')
167
- ) {
168
- // Look at npm scripts to guess the build file
169
- const scripts = pkg?.scripts || {}
170
- for (const cmd of Object.values(scripts)) {
171
- if (cmd.includes('node ')) {
172
- const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/)
173
- if (match && match[1]) {
174
- if (await exists(path.join(root, match[1]))) {
175
- // Only fallback to a bare node script if the script mentions the tool name somewhere
176
- // or if it's explicitly inside a directory named after the tool (like /rspack-scripts/)
177
- if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
178
- detectedFile = match[1]
179
- break
180
- }
232
+ let detectedFile = ''
233
+
234
+ if (pattern.tool === 'esbuild' && !hasDep) {
235
+ return null
236
+ }
237
+
238
+ for (const file of pattern.files) {
239
+ if (await exists(path.join(targetRoot, file))) {
240
+ detectedFile = file
241
+ break
242
+ }
243
+ }
244
+
245
+ if (
246
+ hasDep &&
247
+ !detectedFile &&
248
+ (pattern.tool === 'esbuild' ||
249
+ pattern.tool === 'rollup' ||
250
+ pattern.tool === 'webpack' ||
251
+ pattern.tool === 'rspack' ||
252
+ pattern.tool === 'rsbuild')
253
+ ) {
254
+ for (const cmd of Object.values(scripts)) {
255
+ if (cmd.includes('node ')) {
256
+ const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/)
257
+ if (match && match[1]) {
258
+ if (await exists(path.join(targetRoot, match[1]))) {
259
+ if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
260
+ detectedFile = match[1]
261
+ break
181
262
  }
182
263
  }
183
- } else if (cmd.includes(`${pattern.tool} `)) {
184
- // If we see webpack/rspack in a script but didn't find the exact file above,
185
- // let's try to extract a custom --config flag if provided
186
- if (pattern.tool === 'webpack' || pattern.tool === 'rspack') {
187
- const configMatch = cmd.match(/--config\s+([^\s]+)/)
188
- if (configMatch && configMatch[1]) {
189
- if (await exists(path.join(root, configMatch[1]))) {
190
- detectedFile = configMatch[1]
191
- break
192
- }
264
+ }
265
+ } else if (cmd.includes(`${pattern.tool} `)) {
266
+ if (pattern.tool === 'webpack' || pattern.tool === 'rspack') {
267
+ const configMatch = cmd.match(/--config\s+([^\s]+)/)
268
+ if (configMatch && configMatch[1]) {
269
+ if (await exists(path.join(targetRoot, configMatch[1]))) {
270
+ detectedFile = configMatch[1]
271
+ break
193
272
  }
194
273
  }
195
-
196
- if (!detectedFile) {
197
- detectedFile = 'package.json (scripts)'
198
- break
199
- }
200
274
  }
201
- }
202
- }
203
275
 
204
- if (detectedFile) {
205
- let isLegacyRspack = false
206
- let isLegacyWebpack = false
207
-
208
- if (pattern.tool === 'rspack') {
209
- const version = resolvedVersion
210
- if (
211
- version &&
212
- (version.includes('0.3.') || version.includes('0.2.') || version.includes('0.1.'))
213
- ) {
214
- isLegacyRspack = true
215
- }
216
- } else if (pattern.tool === 'webpack') {
217
- const version = resolvedVersion
218
- if ((version && version.includes('^4')) || version?.startsWith('4.')) {
219
- isLegacyWebpack = true
276
+ if (!detectedFile) {
277
+ detectedFile = 'package.json (scripts)'
278
+ break
220
279
  }
221
280
  }
222
-
223
- return {
224
- tool: pattern.tool,
225
- configPath: detectedFile,
226
- label: `${pattern.label} (${detectedFile})${isLegacyRspack ? ' [Legacy]' : ''}${isLegacyWebpack ? ' [Webpack 4]' : ''}`,
227
- isLegacyRspack,
228
- isLegacyWebpack,
229
- }
230
281
  }
282
+ }
231
283
 
284
+ if (!detectedFile) {
232
285
  return null
233
- })
234
-
235
- const supportedResults = await Promise.all(supportedChecks)
236
- for (const result of supportedResults) {
237
- if (result) {
238
- supported.push(result)
239
- }
240
286
  }
241
287
 
242
- const unsupportedChecks = UNSUPPORTED_META.map(async meta => {
243
- if (!(meta.dep in allDeps)) return null
244
- for (const file of meta.files) {
245
- if (await exists(path.join(root, file))) {
246
- return meta.name
247
- }
248
- }
249
- return null
250
- })
288
+ let isLegacyRspack = false
289
+ let isLegacyWebpack = false
251
290
 
252
- const unsupportedResults = await Promise.all(unsupportedChecks)
253
- for (const result of unsupportedResults) {
254
- if (result) {
255
- unsupported.push(result)
291
+ if (pattern.tool === 'rspack') {
292
+ const version = resolvedVersion
293
+ if (
294
+ version &&
295
+ (version.includes('0.3.') || version.includes('0.2.') || version.includes('0.1.'))
296
+ ) {
297
+ isLegacyRspack = true
298
+ }
299
+ } else if (pattern.tool === 'webpack') {
300
+ const version = resolvedVersion
301
+ if ((version && version.includes('^4')) || version?.startsWith('4.')) {
302
+ isLegacyWebpack = true
256
303
  }
257
304
  }
258
305
 
259
- return { supported, unsupported }
306
+ const absoluteConfig = path.join(targetRoot, detectedFile)
307
+ const relativeConfig = normalizeRelativePath(workspaceRoot, absoluteConfig)
308
+
309
+ return {
310
+ tool: pattern.tool,
311
+ configPath: relativeConfig,
312
+ label: `${pattern.label} (${relativeConfig})${isLegacyRspack ? ' [Legacy]' : ''}${
313
+ isLegacyWebpack ? ' [Webpack 4]' : ''
314
+ }`,
315
+ isLegacyRspack,
316
+ isLegacyWebpack,
317
+ packagePath: packagePath || undefined,
318
+ }
260
319
  }
261
320
 
262
321
  /**
@@ -41,13 +41,24 @@ function printManualInstructions(
41
41
 
42
42
  /** Check if inspecto is already injected (idempotency). */
43
43
  function isAlreadyInjected(content: string): boolean {
44
- // Use regex to avoid false positives in comments or variables
45
- return (
46
- /import\s+.*@inspecto-dev\/plugin/.test(content) ||
47
- /require\(['"]@inspecto-dev\/plugin['"]\)/.test(content) ||
48
- /import\s+.*ai-dev-inspector/.test(content) || // Legacy support
49
- /require\(['"]ai-dev-inspector['"]\)/.test(content)
50
- )
44
+ const normalized = content.replace(/\s+/g, ' ')
45
+ const importPlugin = /import\s+(.+?)\s+from\s+['"]@inspecto-dev\/plugin['"]/g
46
+ const requirePlugin = /require\(['"]@inspecto-dev\/plugin['"]\)/
47
+ const legacyImport = /import\s+.*ai-dev-inspector/.test(normalized)
48
+ const legacyRequire = /require\(['"]ai-dev-inspector['"]\)/.test(normalized)
49
+
50
+ if (legacyImport || legacyRequire || requirePlugin.test(normalized)) return true
51
+
52
+ let match: RegExpExecArray | null
53
+ importPlugin.lastIndex = 0
54
+ while ((match = importPlugin.exec(normalized))) {
55
+ const importClause = match[1] || ''
56
+ if (/inspecto/.test(importClause) || /vitePlugin/.test(importClause)) {
57
+ return true
58
+ }
59
+ }
60
+
61
+ return false
51
62
  }
52
63
 
53
64
  // ---- Main injection orchestrator ----
@@ -54,9 +54,12 @@ async function findVSCodeBinary(): Promise<string | null> {
54
54
  async function tryOpenURI(uri: string): Promise<boolean> {
55
55
  try {
56
56
  const platform = process.platform
57
- const openCmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open'
58
-
59
- await shell(`${openCmd} "${uri}"`)
57
+ if (platform === 'win32') {
58
+ await shell(`cmd /c start "" "${uri}"`)
59
+ } else {
60
+ const openCmd = platform === 'darwin' ? 'open' : 'xdg-open'
61
+ await shell(`${openCmd} "${uri}"`)
62
+ }
60
63
  return true
61
64
  } catch {
62
65
  return false
@@ -12,7 +12,8 @@ export class ViteStrategy implements InjectStrategy {
12
12
  inject({ mod, detection }: InjectOptions): void {
13
13
  addVitePlugin(mod, {
14
14
  from: '@inspecto-dev/plugin',
15
- constructor: 'vitePlugin',
15
+ constructor: 'inspecto',
16
+ imported: 'vitePlugin',
16
17
  })
17
18
  }
18
19
 
package/src/types.ts CHANGED
@@ -18,6 +18,8 @@ export interface BuildToolDetection {
18
18
  isLegacyRspack?: boolean
19
19
  /** Whether this is Webpack 4.x */
20
20
  isLegacyWebpack?: boolean
21
+ /** Relative package path when using --packages */
22
+ packagePath?: string
21
23
  }
22
24
 
23
25
  /** Options passed to `inspecto init` */
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { detectBuildTools } from '../src/detect/build-tool.js'
3
+ import * as fsUtils from '../src/utils/fs.js'
4
+
5
+ vi.mock('../src/utils/fs.js', () => ({
6
+ exists: vi.fn(),
7
+ readJSON: vi.fn(),
8
+ }))
9
+
10
+ describe('detectBuildTools', () => {
11
+ beforeEach(() => {
12
+ vi.resetAllMocks()
13
+ })
14
+
15
+ it('detects Vite config inside a specified package path', async () => {
16
+ vi.mocked(fsUtils.readJSON).mockImplementation(async filePath => {
17
+ if (filePath.includes('packages/app/package.json')) {
18
+ return { devDependencies: { vite: '^5.0.0' } }
19
+ }
20
+ if (filePath.endsWith('/package.json')) {
21
+ return {}
22
+ }
23
+ return null
24
+ })
25
+
26
+ vi.mocked(fsUtils.exists).mockImplementation(async filePath =>
27
+ filePath.includes('packages/app/vite.config.ts'),
28
+ )
29
+
30
+ const result = await detectBuildTools('/repo', ['packages/app'])
31
+ expect(result.supported).toHaveLength(1)
32
+ expect(result.supported[0]?.configPath).toBe('packages/app/vite.config.ts')
33
+ expect(result.supported[0]?.packagePath).toBe('packages/app')
34
+ })
35
+
36
+ it('detects vite.config.cjs at the repo root', async () => {
37
+ vi.mocked(fsUtils.readJSON).mockResolvedValue({ devDependencies: { vite: '^5.0.0' } })
38
+ vi.mocked(fsUtils.exists).mockImplementation(async filePath =>
39
+ filePath.endsWith('vite.config.cjs'),
40
+ )
41
+
42
+ const result = await detectBuildTools('/repo')
43
+ expect(result.supported).toHaveLength(1)
44
+ expect(result.supported[0]?.configPath).toBe('vite.config.cjs')
45
+ })
46
+ })