@vibgrate/cli 1.0.79 → 1.0.81

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.
@@ -2,7 +2,6 @@ import {
2
2
  FileCache,
3
3
  Semaphore,
4
4
  ensureDir,
5
- findCsprojFiles,
6
5
  findFiles,
7
6
  findPackageJsonFiles,
8
7
  findSolutionFiles,
@@ -578,7 +577,7 @@ function formatArchitectureDiagram(arch) {
578
577
  lines.push("");
579
578
  if (arch.layers.length > 0) {
580
579
  for (const layer of arch.layers) {
581
- const risk = layer.riskLevel === "low" ? chalk.green("low") : layer.riskLevel === "moderate" ? chalk.yellow("moderate") : chalk.red("high");
580
+ const risk = layer.riskLevel === "none" ? chalk.dim("none") : layer.riskLevel === "low" ? chalk.green("low") : layer.riskLevel === "moderate" ? chalk.yellow("moderate") : chalk.red("high");
582
581
  lines.push(` ${chalk.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
583
582
  }
584
583
  lines.push("");
@@ -2344,11 +2343,17 @@ function parseTfmMajor(tfm) {
2344
2343
  if (fxMatch) return null;
2345
2344
  return null;
2346
2345
  }
2347
- function parseCsproj(xml, filePath) {
2346
+ function isDotnetProjectFile(name) {
2347
+ return name.endsWith(".csproj") || name.endsWith(".vbproj");
2348
+ }
2349
+ function stripDotnetProjectExtension(filePath) {
2350
+ return path5.basename(filePath).replace(/\.(cs|vb)proj$/i, "");
2351
+ }
2352
+ function parseDotnetProjectFile(xml, filePath) {
2348
2353
  const parsed = parser.parse(xml);
2349
2354
  const project = parsed?.Project;
2350
2355
  if (!project) {
2351
- return { targetFrameworks: [], packageReferences: [], projectReferences: [], projectName: path5.basename(filePath, ".csproj") };
2356
+ return { targetFrameworks: [], packageReferences: [], projectReferences: [], projectName: stripDotnetProjectExtension(filePath) };
2352
2357
  }
2353
2358
  const propertyGroups = Array.isArray(project.PropertyGroup) ? project.PropertyGroup : project.PropertyGroup ? [project.PropertyGroup] : [];
2354
2359
  const targetFrameworks = [];
@@ -2385,34 +2390,34 @@ function parseCsproj(xml, filePath) {
2385
2390
  targetFrameworks: [...new Set(targetFrameworks)],
2386
2391
  packageReferences,
2387
2392
  projectReferences,
2388
- projectName: path5.basename(filePath, ".csproj")
2393
+ projectName: stripDotnetProjectExtension(filePath)
2389
2394
  };
2390
2395
  }
2391
2396
  async function scanDotnetProjects(rootDir, nugetCache, cache, projectScanTimeout) {
2392
- const csprojFiles = cache ? await cache.findCsprojFiles(rootDir) : await findCsprojFiles(rootDir);
2397
+ const projectFiles = cache ? await cache.findFiles(rootDir, isDotnetProjectFile) : await findFiles(rootDir, isDotnetProjectFile);
2393
2398
  const slnFiles = cache ? await cache.findSolutionFiles(rootDir) : await findSolutionFiles(rootDir);
2394
- const slnCsprojPaths = /* @__PURE__ */ new Set();
2399
+ const slnProjectPaths = /* @__PURE__ */ new Set();
2395
2400
  for (const slnPath of slnFiles) {
2396
2401
  try {
2397
2402
  const slnContent = cache ? await cache.readTextFile(slnPath) : await readTextFile(slnPath);
2398
2403
  const slnDir = path5.dirname(slnPath);
2399
- const projectRegex = /Project\("[^"]*"\)\s*=\s*"[^"]*",\s*"([^"]+\.csproj)"/g;
2404
+ const projectRegex = /Project\("[^"]*"\)\s*=\s*"[^"]*",\s*"([^"]+\.(?:cs|vb)proj)"/g;
2400
2405
  let match;
2401
2406
  while ((match = projectRegex.exec(slnContent)) !== null) {
2402
2407
  if (match[1]) {
2403
2408
  const csprojPath = path5.resolve(slnDir, match[1].replace(/\\/g, "/"));
2404
- slnCsprojPaths.add(csprojPath);
2409
+ slnProjectPaths.add(csprojPath);
2405
2410
  }
2406
2411
  }
2407
2412
  } catch {
2408
2413
  }
2409
2414
  }
2410
- const allCsprojFiles = /* @__PURE__ */ new Set([...csprojFiles, ...slnCsprojPaths]);
2415
+ const allCsprojFiles = /* @__PURE__ */ new Set([...projectFiles, ...slnProjectPaths]);
2411
2416
  const results = [];
2412
2417
  const STUCK_TIMEOUT_MS = projectScanTimeout ?? cache?.projectScanTimeout ?? 18e4;
2413
2418
  for (const csprojPath of allCsprojFiles) {
2414
2419
  try {
2415
- const scanPromise = scanOneCsproj(csprojPath, rootDir, nugetCache, cache);
2420
+ const scanPromise = scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache);
2416
2421
  const result = await withTimeout(scanPromise, STUCK_TIMEOUT_MS);
2417
2422
  if (result.ok) {
2418
2423
  results.push(result.value);
@@ -2433,9 +2438,9 @@ async function scanDotnetProjects(rootDir, nugetCache, cache, projectScanTimeout
2433
2438
  }
2434
2439
  return results;
2435
2440
  }
2436
- async function scanOneCsproj(csprojPath, rootDir, nugetCache, cache) {
2441
+ async function scanOneDotnetProjectFile(csprojPath, rootDir, nugetCache, cache) {
2437
2442
  const xml = cache ? await cache.readTextFile(csprojPath) : await readTextFile(csprojPath);
2438
- const data = parseCsproj(xml, csprojPath);
2443
+ const data = parseDotnetProjectFile(xml, csprojPath);
2439
2444
  const csprojDir = path5.dirname(csprojPath);
2440
2445
  const primaryTfm = data.targetFrameworks[0];
2441
2446
  let runtimeMajorsBehind;
@@ -2516,7 +2521,7 @@ async function scanOneCsproj(csprojPath, rootDir, nugetCache, cache) {
2516
2521
  const projectReferences = data.projectReferences.map((refPath) => {
2517
2522
  const absRefPath = path5.resolve(csprojDir, refPath);
2518
2523
  const relRefPath = normalizePath(path5.relative(rootDir, path5.dirname(absRefPath)));
2519
- const refName = path5.basename(absRefPath, ".csproj");
2524
+ const refName = stripDotnetProjectExtension(absRefPath);
2520
2525
  return {
2521
2526
  path: relRefPath || ".",
2522
2527
  name: refName,
@@ -7425,6 +7430,10 @@ var ScanProgress = class {
7425
7430
  lines.push(` ${ROBOT[2]}`);
7426
7431
  lines.push(` ${ROBOT[3]} ${chalk4.dim(this.rootDir)}`);
7427
7432
  lines.push("");
7433
+ for (const step of this.steps) {
7434
+ lines.push(this.renderStep(step));
7435
+ }
7436
+ lines.push("");
7428
7437
  const totalWeight = this.steps.reduce((sum, s) => sum + (s.weight ?? 1), 0);
7429
7438
  let completedWeight = 0;
7430
7439
  for (const step of this.steps) {
@@ -7452,10 +7461,6 @@ var ScanProgress = class {
7452
7461
  const treePart = this.stats.treeSummary ? chalk4.dim(` \xB7 ${this.stats.treeSummary.totalFiles.toLocaleString()} files \xB7 ${this.stats.treeSummary.totalDirs.toLocaleString()} dirs`) : "";
7453
7462
  lines.push(` ${bar} ${chalk4.bold.white(`${pct}%`)} ${chalk4.dim(elapsedStr)}${etaStr}${treePart}`);
7454
7463
  lines.push("");
7455
- for (const step of this.steps) {
7456
- lines.push(this.renderStep(step));
7457
- }
7458
- lines.push("");
7459
7464
  lines.push(this.renderStats());
7460
7465
  lines.push("");
7461
7466
  const content = lines.join("\n") + "\n";
@@ -7918,7 +7923,7 @@ async function scanPlatformMatrix(rootDir, cache) {
7918
7923
  }
7919
7924
  result.nativeModules.sort();
7920
7925
  result.osAssumptions = [...osAssumptions].sort();
7921
- const csprojFiles = cache ? await cache.findCsprojFiles(rootDir) : await findFiles(rootDir, (name) => name.endsWith(".csproj"));
7926
+ const csprojFiles = cache ? await cache.findFiles(rootDir, (name) => name.endsWith(".csproj") || name.endsWith(".vbproj")) : await findFiles(rootDir, (name) => name.endsWith(".csproj") || name.endsWith(".vbproj"));
7922
7927
  const tfms = /* @__PURE__ */ new Set();
7923
7928
  for (const csprojPath of csprojFiles) {
7924
7929
  try {
@@ -10018,7 +10023,7 @@ function classifyFile(filePath, archetype) {
10018
10023
  }
10019
10024
  function computeLayerDrift(packages) {
10020
10025
  if (packages.length === 0) {
10021
- return { score: 100, riskLevel: "low" };
10026
+ return { score: 0, riskLevel: "none" };
10022
10027
  }
10023
10028
  let current = 0;
10024
10029
  let oneBehind = 0;
@@ -10036,7 +10041,7 @@ function computeLayerDrift(packages) {
10036
10041
  }
10037
10042
  }
10038
10043
  const known = current + oneBehind + twoPlusBehind;
10039
- if (known === 0) return { score: 100, riskLevel: "low" };
10044
+ if (known === 0) return { score: 0, riskLevel: "none" };
10040
10045
  const currentPct = current / known;
10041
10046
  const onePct = oneBehind / known;
10042
10047
  const twoPct = twoPlusBehind / known;
@@ -11702,7 +11707,7 @@ async function discoverSolutions(rootDir, fileCache) {
11702
11707
  const rootBasename = path32.basename(rootDir);
11703
11708
  const relSolutionPath = [rootBasename, path32.relative(rootDir, solutionFile).replace(/\\/g, "/")].join("/");
11704
11709
  const projectPaths = /* @__PURE__ */ new Set();
11705
- const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.csproj)"/g;
11710
+ const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.(?:cs|vb)proj)"/g;
11706
11711
  let match;
11707
11712
  while ((match = projectRegex.exec(content)) !== null) {
11708
11713
  const projectRelative = match[2];
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  baselineCommand
4
- } from "./chunk-DKSPLRJV.js";
4
+ } from "./chunk-DEPG5EIH.js";
5
5
  import {
6
6
  VERSION,
7
7
  computeHmac,
@@ -12,7 +12,7 @@ import {
12
12
  pushCommand,
13
13
  scanCommand,
14
14
  writeDefaultConfig
15
- } from "./chunk-TKNDQ337.js";
15
+ } from "./chunk-W6TXQI4D.js";
16
16
  import {
17
17
  Semaphore,
18
18
  ensureDir,
@@ -42,7 +42,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
42
42
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
43
43
  }
44
44
  if (opts.baseline) {
45
- const { runBaseline } = await import("./baseline-NRKROYVK.js");
45
+ const { runBaseline } = await import("./baseline-5AZAIOQ6.js");
46
46
  await runBaseline(rootDir);
47
47
  }
48
48
  console.log("");
@@ -172,6 +172,32 @@ async function writeCache(data) {
172
172
  }
173
173
 
174
174
  // src/commands/update.ts
175
+ function detectGlobalInstall() {
176
+ const execPath = process.argv[1] || "";
177
+ if (execPath.includes("/lib/node_modules/") || execPath.includes("\\node_modules\\")) {
178
+ if (!execPath.includes(process.cwd())) {
179
+ if (execPath.includes("pnpm")) return "pnpm";
180
+ if (execPath.includes("yarn")) return "yarn";
181
+ if (execPath.includes("bun")) return "bun";
182
+ return "npm";
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ function getGlobalUpdateCommand(pm, pkg, version) {
188
+ const spec = `${pkg}@${version}`;
189
+ switch (pm) {
190
+ case "pnpm":
191
+ return `pnpm add -g ${spec}`;
192
+ case "yarn":
193
+ return `yarn global add ${spec}`;
194
+ case "bun":
195
+ return `bun add -g ${spec}`;
196
+ case "npm":
197
+ default:
198
+ return `npm install -g ${spec}`;
199
+ }
200
+ }
175
201
  async function detectPackageManager(cwd) {
176
202
  if (await pathExists(path4.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
177
203
  if (await pathExists(path4.join(cwd, "bun.lockb"))) return "bun";
@@ -202,7 +228,7 @@ async function isDevDependency(cwd) {
202
228
  return true;
203
229
  }
204
230
  }
205
- var updateCommand = new Command3("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").action(async (opts) => {
231
+ var updateCommand = new Command3("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--global", "Update global installation").action(async (opts) => {
206
232
  console.log(chalk3.dim(`Current version: ${VERSION}`));
207
233
  console.log(chalk3.dim("Checking npm registry..."));
208
234
  const latest = await fetchLatestVersion();
@@ -221,10 +247,18 @@ var updateCommand = new Command3("update").description("Update vibgrate to the l
221
247
  return;
222
248
  }
223
249
  const cwd = process.cwd();
224
- const pm = opts.pm || await detectPackageManager(cwd);
225
- const isDev = await isDevDependency(cwd);
226
- const cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
227
- console.log(chalk3.dim(`Using ${pm}: ${cmd}`));
250
+ const globalPm = detectGlobalInstall();
251
+ const isGlobal = opts.global || globalPm !== null;
252
+ const pm = opts.pm || (globalPm ?? await detectPackageManager(cwd));
253
+ let cmd;
254
+ if (isGlobal) {
255
+ cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
256
+ console.log(chalk3.dim(`Updating global installation with ${pm}: ${cmd}`));
257
+ } else {
258
+ const isDev = await isDevDependency(cwd);
259
+ cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
260
+ console.log(chalk3.dim(`Using ${pm}: ${cmd}`));
261
+ }
228
262
  try {
229
263
  execSync(cmd, { cwd, stdio: "inherit" });
230
264
  console.log(chalk3.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
@@ -417,7 +451,7 @@ import * as path6 from "path";
417
451
  import * as os2 from "os";
418
452
  import * as fs2 from "fs/promises";
419
453
  import { existsSync } from "fs";
420
- import { spawn } from "child_process";
454
+ import { spawn, spawnSync } from "child_process";
421
455
  import { Command as Command5 } from "commander";
422
456
  import chalk5 from "chalk";
423
457
  var EXIT_SUCCESS = 0;
@@ -440,7 +474,9 @@ var LANGUAGE_EXTENSIONS = {
440
474
  go: /* @__PURE__ */ new Set([".go"]),
441
475
  python: /* @__PURE__ */ new Set([".py"]),
442
476
  java: /* @__PURE__ */ new Set([".java"]),
443
- csharp: /* @__PURE__ */ new Set([".cs"])
477
+ csharp: /* @__PURE__ */ new Set([".cs"]),
478
+ cplusplus: /* @__PURE__ */ new Set([".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx", ".h", ".ixx", ".vcxproj"]),
479
+ vbnet: /* @__PURE__ */ new Set([".vb"])
444
480
  };
445
481
  var SUPPORTED_LANGUAGES = new Set(Object.keys(LANGUAGE_EXTENSIONS));
446
482
  var SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -489,6 +525,13 @@ var TEST_PATTERNS = [
489
525
  function isTestPath(relPath) {
490
526
  return TEST_PATTERNS.some((p) => p.test(relPath));
491
527
  }
528
+ function normalizeLanguageToken(language) {
529
+ const normalized = language.trim().toLowerCase();
530
+ if (normalized === "vb.net" || normalized === "visualbasic" || normalized === "visual-basic") {
531
+ return "vbnet";
532
+ }
533
+ return normalized;
534
+ }
492
535
  async function detectLanguages(rootDir, includeTests) {
493
536
  const counts = /* @__PURE__ */ new Map();
494
537
  async function walk(dir, relBase) {
@@ -582,14 +625,81 @@ var NODE_WORKER_TEXT_LANGS = /* @__PURE__ */ new Set([
582
625
  "php",
583
626
  "dart",
584
627
  "scala",
585
- "cobol"
628
+ "cobol",
629
+ "cplusplus"
586
630
  ]);
587
631
  var NODE_WORKER_ALL_LANGS = /* @__PURE__ */ new Set([
588
632
  ...NODE_WORKER_AST_LANGS,
589
633
  ...NODE_WORKER_TEXT_LANGS
590
634
  ]);
591
- var NATIVE_AST_LANGS = /* @__PURE__ */ new Set(["go", "python", "java", "csharp"]);
635
+ var NATIVE_AST_LANGS = /* @__PURE__ */ new Set(["go", "python", "java", "csharp", "vbnet"]);
592
636
  var ALL_WORKER_LANGS = /* @__PURE__ */ new Set([...NODE_WORKER_ALL_LANGS, ...NATIVE_AST_LANGS]);
637
+ var NATIVE_RUNTIME_REQUIREMENTS = {
638
+ go: {
639
+ command: "go",
640
+ displayName: "Go toolchain",
641
+ installGuideByPlatform: {
642
+ darwin: "brew install go",
643
+ linux: "Install Go 1.22+ from https://go.dev/dl/ or your distro package manager.",
644
+ win32: "winget install GoLang.Go"
645
+ },
646
+ docsUrl: "https://go.dev/doc/install"
647
+ },
648
+ python: {
649
+ command: "python3",
650
+ displayName: "Python 3 runtime",
651
+ installGuideByPlatform: {
652
+ darwin: "brew install python",
653
+ linux: "Install Python 3.10+ from your distro package manager (apt/dnf/pacman).",
654
+ win32: "winget install Python.Python.3.12"
655
+ },
656
+ docsUrl: "https://www.python.org/downloads/"
657
+ },
658
+ java: {
659
+ command: "java",
660
+ displayName: "Java 17+ runtime",
661
+ installGuideByPlatform: {
662
+ darwin: "brew install --cask temurin",
663
+ linux: "Install OpenJDK 17+ (for example: apt install openjdk-17-jre).",
664
+ win32: "winget install EclipseAdoptium.Temurin.17.JRE"
665
+ },
666
+ docsUrl: "https://adoptium.net/"
667
+ },
668
+ csharp: {
669
+ command: "dotnet",
670
+ displayName: ".NET SDK/runtime",
671
+ installGuideByPlatform: {
672
+ darwin: "brew install --cask dotnet-sdk",
673
+ linux: "Install .NET 8 SDK/runtime from https://dotnet.microsoft.com/download.",
674
+ win32: "winget install Microsoft.DotNet.SDK.8"
675
+ },
676
+ docsUrl: "https://dotnet.microsoft.com/download"
677
+ },
678
+ vbnet: {
679
+ command: "dotnet",
680
+ displayName: ".NET SDK/runtime",
681
+ installGuideByPlatform: {
682
+ darwin: "brew install --cask dotnet-sdk",
683
+ linux: "Install .NET 8 SDK/runtime from https://dotnet.microsoft.com/download.",
684
+ win32: "winget install Microsoft.DotNet.SDK.8"
685
+ },
686
+ docsUrl: "https://dotnet.microsoft.com/download"
687
+ }
688
+ };
689
+ function commandExistsOnPath(command) {
690
+ const probeArgs = process.platform === "win32" ? ["/c", command, "--version"] : ["--version"];
691
+ const probeCmd = process.platform === "win32" ? "cmd" : command;
692
+ const result = spawnSync(probeCmd, probeArgs, { stdio: "ignore" });
693
+ return !result.error && result.status === 0;
694
+ }
695
+ function buildNativeInstallHint(language) {
696
+ const req = NATIVE_RUNTIME_REQUIREMENTS[language];
697
+ if (!req) {
698
+ return "See HCS runtime setup docs for prerequisites: https://vibgrate.com/help";
699
+ }
700
+ const platformHint = req.installGuideByPlatform[process.platform] ?? `Install ${req.displayName} and ensure '${req.command}' is available on PATH.`;
701
+ return `${platformHint} More details: ${req.docsUrl}`;
702
+ }
593
703
  async function runNodeWorker(rootDir, language, opts) {
594
704
  const workerBin = resolveHcsWorkerBin();
595
705
  const args = [];
@@ -666,6 +776,29 @@ async function runNodeWorker(rootDir, language, opts) {
666
776
  });
667
777
  });
668
778
  }
779
+ function resolveDotnetPublishedWorker(workersDir) {
780
+ const candidatesByPlatform = {
781
+ win32: ["hcs-worker-win-x64.exe"],
782
+ linux: ["hcs-worker-linux-x64"],
783
+ darwin: ["hcs-worker-osx-arm64", "hcs-worker-osx-x64"],
784
+ aix: [],
785
+ android: [],
786
+ freebsd: [],
787
+ haiku: [],
788
+ openbsd: [],
789
+ cygwin: [],
790
+ netbsd: [],
791
+ sunos: []
792
+ };
793
+ const candidates = candidatesByPlatform[process.platform] ?? [];
794
+ for (const filename of candidates) {
795
+ const fullPath = path6.join(workersDir, filename);
796
+ if (existsSync(fullPath)) {
797
+ return fullPath;
798
+ }
799
+ }
800
+ return null;
801
+ }
669
802
  function resolveNativeWorker(language, projectDir) {
670
803
  const base = import.meta.dirname ?? path6.dirname(new URL(import.meta.url).pathname);
671
804
  const hcsFromBundle = path6.resolve(base, "..", "..", "vibgrate-hcs");
@@ -716,7 +849,12 @@ function resolveNativeWorker(language, projectDir) {
716
849
  }
717
850
  return null;
718
851
  }
719
- case "csharp": {
852
+ case "csharp":
853
+ case "vbnet": {
854
+ const publishedWorker = resolveDotnetPublishedWorker(workersDir);
855
+ if (publishedWorker) {
856
+ return { cmd: publishedWorker, args: ["--project", projectDir, "--output", "ndjson"] };
857
+ }
720
858
  const dll = path6.join(workersDir, "VibgrateHcsWorker.dll");
721
859
  if (existsSync(dll)) {
722
860
  return { cmd: "dotnet", args: [dll, "--project", projectDir, "--output", "ndjson"] };
@@ -741,7 +879,8 @@ async function runNativeWorker(rootDir, language, opts) {
741
879
  language,
742
880
  facts: [],
743
881
  errors: [
744
- `[error] No native worker found for '${language}'. Install the toolchain (${language === "go" ? "Go" : language === "python" ? "Python 3" : language === "java" ? "Java 17+" : "dotnet SDK"}) or build the worker binary.`
882
+ `[error] No native worker found for '${language}'. Build/package workers for this platform or use source mode.`,
883
+ `[hint] ${buildNativeInstallHint(language)}`
745
884
  ],
746
885
  exitCode: EXIT_PARSE_FAILURE
747
886
  };
@@ -755,6 +894,21 @@ async function runNativeWorker(rootDir, language, opts) {
755
894
  process.stderr.write(chalk5.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
756
895
  `));
757
896
  }
897
+ const runtimeReq = NATIVE_RUNTIME_REQUIREMENTS[language];
898
+ const usesPathCommand = runtimeReq && spec.cmd === runtimeReq.command;
899
+ if (usesPathCommand && !commandExistsOnPath(spec.cmd)) {
900
+ resolve6({
901
+ language,
902
+ facts: [],
903
+ errors: [
904
+ `[error] Required runtime command '${spec.cmd}' is not available on PATH.`,
905
+ `[hint] ${buildNativeInstallHint(language)}`,
906
+ "[hint] You can also package native workers into dist/workers to avoid local toolchain requirements."
907
+ ],
908
+ exitCode: EXIT_PARSE_FAILURE
909
+ });
910
+ return;
911
+ }
758
912
  const child = spawn(spec.cmd, spec.args, {
759
913
  cwd: spec.cwd ?? rootDir,
760
914
  stdio: ["ignore", "pipe", "pipe"]
@@ -799,7 +953,7 @@ async function runNativeWorker(rootDir, language, opts) {
799
953
  clearTimeout(timer);
800
954
  const isNotFound = err.code === "ENOENT";
801
955
  errors.push(
802
- isNotFound ? `[error] '${spec.cmd}' not found. Is the required toolchain installed and on PATH?` : `[error] Failed to spawn native worker: ${err.message}`
956
+ isNotFound ? `[error] '${spec.cmd}' not found. ${buildNativeInstallHint(language)}` : `[error] Failed to spawn native worker: ${err.message}`
803
957
  );
804
958
  resolve6({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
805
959
  });
@@ -1012,7 +1166,7 @@ var extractCommand = new Command5("extract").description("Analyze source code an
1012
1166
  }
1013
1167
  let targetLanguages;
1014
1168
  if (opts.language) {
1015
- targetLanguages = opts.language.split(",").map((l) => l.trim().toLowerCase()).filter(Boolean);
1169
+ targetLanguages = opts.language.split(",").map((l) => normalizeLanguageToken(l)).filter(Boolean);
1016
1170
  for (const lang of targetLanguages) {
1017
1171
  if (!SUPPORTED_LANGUAGES.has(lang)) {
1018
1172
  process.stderr.write(chalk5.red(`Unknown language: "${lang}"