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

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.3 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
@@ -11,10 +11,10 @@
11
11
  CLI Cleaning output folder
12
12
  ESM Build start
13
13
  ESM dist/index.js 109.00 B
14
+ ESM dist/chunk-EUCQCD3Y.js 57.56 KB
14
15
  ESM dist/bin.js 2.64 KB
15
- ESM dist/chunk-HIL6365F.js 53.63 KB
16
- ESM ⚡️ Build success in 26ms
16
+ ESM ⚡️ Build success in 136ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 1180ms
18
+ DTS ⚡️ Build success in 1464ms
19
19
  DTS dist/bin.d.ts 13.00 B
20
20
  DTS dist/index.d.ts 1.18 KB
@@ -1,15 +1,21 @@
1
-
2
- > @inspecto-dev/cli@0.2.0-alpha.2 test /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
3
- > vitest run --passWithNoTests
4
-
5
-
6
- RUN v1.6.1 /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
7
-
8
- ✓ tests/ide.test.ts (6 tests) 3ms
9
- tests/framework.test.ts (5 tests) 2ms
10
-
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)
15
-
1
+
2
+ 
3
+ > @inspecto-dev/cli@0.2.0-alpha.3 test /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
4
+ > vitest run --passWithNoTests
5
+
6
+
7
+  RUN  v1.6.1 /Users/bytedance/Works/hugo.felix/inspecto/packages/cli
8
+
9
+ [?25l ✓ tests/framework.test.ts (5)
10
+ ✓ tests/build-tool.test.ts (2)
11
+ ✓ tests/ide.test.ts (6)
12
+  ✓ tests/framework.test.ts (5)
13
+ ✓ tests/build-tool.test.ts (2)
14
+ ✓ tests/ide.test.ts (6)
15
+
16
+  Test Files  3 passed (3)
17
+  Tests  13 passed (13)
18
+  Start at  11:27:40
19
+  Duration  548ms (transform 85ms, setup 0ms, collect 131ms, tests 15ms, environment 0ms, prepare 374ms)
20
+
21
+ [?25h[?25h
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @inspecto-dev/cli
2
2
 
3
+ ## 0.2.0-alpha.4
4
+
5
+ ### Minor Changes
6
+
7
+ - release alpha test version
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @inspecto-dev/types@0.2.0-alpha.4
13
+
3
14
  ## 0.2.0-alpha.3
4
15
 
5
16
  ### 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-EUCQCD3Y.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
- }
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
365
  }
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;
@@ -894,8 +938,12 @@ async function findVSCodeBinary() {
894
938
  async function tryOpenURI(uri) {
895
939
  try {
896
940
  const platform = process.platform;
897
- const openCmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
898
- await shell(`${openCmd} "${uri}"`);
941
+ if (platform === "win32") {
942
+ await shell(`cmd /c start "" "${uri}"`);
943
+ } else {
944
+ const openCmd = platform === "darwin" ? "open" : "xdg-open";
945
+ await shell(`${openCmd} "${uri}"`);
946
+ }
899
947
  return true;
900
948
  } catch {
901
949
  return false;
@@ -1108,6 +1156,25 @@ function printNextJsManualInstructions() {
1108
1156
  async function init(options) {
1109
1157
  const root = process.cwd();
1110
1158
  const mutations = [];
1159
+ const normalizedPackages = normalizePackageList(options.packages);
1160
+ const verifiedPackages = [];
1161
+ for (const pkg of normalizedPackages) {
1162
+ if (!pkg) {
1163
+ verifiedPackages.push(pkg);
1164
+ continue;
1165
+ }
1166
+ const absolutePath = path9.join(root, pkg);
1167
+ if (await exists(absolutePath)) {
1168
+ verifiedPackages.push(pkg);
1169
+ } else {
1170
+ log.warn(`Package path "${pkg}" not found (skipping)`);
1171
+ log.hint("Ensure --packages values are relative to the project root");
1172
+ }
1173
+ }
1174
+ if (normalizedPackages.length > 0 && verifiedPackages.length === 0) {
1175
+ log.error("No valid packages found from --packages input");
1176
+ return;
1177
+ }
1111
1178
  log.header("Inspecto Setup");
1112
1179
  if (!await exists(path9.join(root, "package.json"))) {
1113
1180
  log.error("No package.json found in current directory");
@@ -1117,7 +1184,7 @@ async function init(options) {
1117
1184
  const [pm, frameworkResult, buildResult, ideProbe, providerProbe] = await Promise.all([
1118
1185
  detectPackageManager(root),
1119
1186
  detectFrameworks(root),
1120
- detectBuildTools(root),
1187
+ detectBuildTools(root, verifiedPackages.length > 0 ? verifiedPackages : void 0),
1121
1188
  detectIDE(root),
1122
1189
  detectProviders(root)
1123
1190
  ]);
@@ -1151,6 +1218,10 @@ async function init(options) {
1151
1218
  );
1152
1219
  }
1153
1220
  let manualConfigRequiredFor = "";
1221
+ if (verifiedPackages.length > 0 && buildResult.supported.length === 0) {
1222
+ log.warn(`No supported build configs detected for: ${verifiedPackages.map((pkg) => pkg ? pkg : ".").join(", ")}`);
1223
+ log.hint("Double-check the --packages values or run without the flag to scan the repo root");
1224
+ }
1154
1225
  if (buildResult.supported.length > 0) {
1155
1226
  buildResult.supported.forEach((bt) => log.success(`Detected: ${bt.label}`));
1156
1227
  }
@@ -1230,20 +1301,46 @@ async function init(options) {
1230
1301
  }
1231
1302
  let injectionFailed = false;
1232
1303
  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 {
1304
+ if (verifiedPackages.length > 0) {
1305
+ const targets = buildResult.supported.filter(
1306
+ (detection) => matchesAnyPackage(detection, verifiedPackages)
1307
+ );
1308
+ const unmatchedPackages = verifiedPackages.filter(
1309
+ (pkg) => !buildResult.supported.some((detection) => matchesPackage(detection, pkg))
1310
+ );
1311
+ if (unmatchedPackages.length > 0) {
1312
+ log.warn(
1313
+ `No supported build configs detected for: ${unmatchedPackages.map((pkg) => pkg ? pkg : ".").join(", ")}`
1314
+ );
1315
+ log.hint("Check the package paths or run without --packages to inspect the repo root");
1316
+ }
1317
+ if (targets.length === 0) {
1242
1318
  injectionFailed = true;
1243
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
+ }
1327
+ }
1244
1328
  } else {
1245
- injectionFailed = true;
1246
- log.warn("Skipping plugin injection (manual configuration required)");
1329
+ let target = resolveInjectionTarget(buildResult.supported);
1330
+ if (target === "ambiguous") {
1331
+ target = await promptConfigChoice(buildResult.supported);
1332
+ }
1333
+ if (target) {
1334
+ const result = await injectPlugin(root, target, options.dryRun);
1335
+ if (result.success) {
1336
+ mutations.push(...result.mutations);
1337
+ } else {
1338
+ injectionFailed = true;
1339
+ }
1340
+ } else {
1341
+ injectionFailed = true;
1342
+ log.warn("Skipping plugin injection (manual configuration required)");
1343
+ }
1247
1344
  }
1248
1345
  }
1249
1346
  const settingsDir = path9.join(root, ".inspecto");
@@ -1351,6 +1448,27 @@ async function init(options) {
1351
1448
  log.ready("Ready! Hold Alt + Click any element to inspect.");
1352
1449
  }
1353
1450
  }
1451
+ function normalizePackageList(packages) {
1452
+ if (!packages || packages.length === 0) return [];
1453
+ const normalized = packages.map((pkg) => {
1454
+ const trimmed = pkg.trim();
1455
+ if (trimmed === "") return null;
1456
+ if (trimmed === "." || trimmed === "./") return "";
1457
+ return trimmed.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
1458
+ }).filter((value) => value !== null);
1459
+ return Array.from(new Set(normalized));
1460
+ }
1461
+ function matchesPackage(detection, pkg) {
1462
+ const configPath = detection.configPath.replace(/\\/g, "/");
1463
+ if (!pkg) {
1464
+ return !configPath.includes("/");
1465
+ }
1466
+ return configPath === pkg || configPath.startsWith(`${pkg}/`);
1467
+ }
1468
+ function matchesAnyPackage(detection, packages) {
1469
+ if (packages.length === 0) return true;
1470
+ return packages.some((pkg) => matchesPackage(detection, pkg));
1471
+ }
1354
1472
 
1355
1473
  // src/commands/doctor.ts
1356
1474
  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-EUCQCD3Y.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.4",
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
  /**
@@ -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
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
+ })