@wrongstack/tools 0.284.0 → 0.285.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_concurrency.d.ts +16 -0
- package/dist/_concurrency.d.ts.map +1 -0
- package/dist/_danger-detect.d.ts +54 -0
- package/dist/_danger-detect.d.ts.map +1 -0
- package/dist/_edit-match.d.ts +86 -0
- package/dist/_edit-match.d.ts.map +1 -0
- package/dist/_env.d.ts +6 -0
- package/dist/_env.d.ts.map +1 -0
- package/dist/_fetch-guard.d.ts +31 -0
- package/dist/_fetch-guard.d.ts.map +1 -0
- package/dist/_output-spool.d.ts +38 -0
- package/dist/_output-spool.d.ts.map +1 -0
- package/dist/_redact-command.d.ts +6 -0
- package/dist/_redact-command.d.ts.map +1 -0
- package/dist/_regex.d.ts +39 -0
- package/dist/_regex.d.ts.map +1 -0
- package/dist/_session-shell.d.ts +73 -0
- package/dist/_session-shell.d.ts.map +1 -0
- package/dist/_shell-pick.d.ts +158 -0
- package/dist/_shell-pick.d.ts.map +1 -0
- package/dist/_spawn-stream.d.ts +32 -0
- package/dist/_spawn-stream.d.ts.map +1 -0
- package/dist/_syntax-check.d.ts +16 -0
- package/dist/_syntax-check.d.ts.map +1 -0
- package/dist/_util.d.ts +68 -0
- package/dist/_util.d.ts.map +1 -0
- package/dist/_win32-resolve.d.ts +42 -0
- package/dist/_win32-resolve.d.ts.map +1 -0
- package/dist/audit.d.ts +4 -5
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +2743 -28
- package/dist/audit.js.map +7 -1
- package/dist/bash-kill-guard.d.ts +62 -0
- package/dist/bash-kill-guard.d.ts.map +1 -0
- package/dist/bash.d.ts +4 -5
- package/dist/bash.d.ts.map +1 -0
- package/dist/bash.js +59 -30
- package/dist/bash.js.map +7 -1
- package/dist/batch-tool-use.d.ts +4 -5
- package/dist/batch-tool-use.d.ts.map +1 -0
- package/dist/batch-tool-use.js +3 -3
- package/dist/batch-tool-use.js.map +7 -1
- package/dist/builtin.d.ts +7 -9
- package/dist/builtin.d.ts.map +1 -0
- package/dist/builtin.js +5282 -1019
- package/dist/builtin.js.map +7 -1
- package/dist/circuit-breaker.d.ts +4 -5
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +3 -3
- package/dist/circuit-breaker.js.map +7 -1
- package/dist/codebase-index/background-indexer.d.ts +116 -0
- package/dist/codebase-index/background-indexer.d.ts.map +1 -0
- package/dist/codebase-index/bm25.d.ts +40 -0
- package/dist/codebase-index/bm25.d.ts.map +1 -0
- package/dist/codebase-index/circuit-breaker.d.ts +94 -0
- package/dist/codebase-index/circuit-breaker.d.ts.map +1 -0
- package/dist/codebase-index/codebase-index-tool.d.ts +17 -0
- package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -0
- package/dist/codebase-index/codebase-search-tool.d.ts +43 -0
- package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts +23 -0
- package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -0
- package/dist/codebase-index/gitignore.d.ts +22 -0
- package/dist/codebase-index/gitignore.d.ts.map +1 -0
- package/dist/codebase-index/go-parser.d.ts +16 -0
- package/dist/codebase-index/go-parser.d.ts.map +1 -0
- package/dist/codebase-index/index-service.d.ts +25 -0
- package/dist/codebase-index/index-service.d.ts.map +1 -0
- package/dist/codebase-index/index.d.ts +23 -271
- package/dist/codebase-index/index.d.ts.map +1 -0
- package/dist/codebase-index/index.js +136 -68
- package/dist/codebase-index/index.js.map +7 -1
- package/dist/codebase-index/indexer.d.ts +28 -0
- package/dist/codebase-index/indexer.d.ts.map +1 -0
- package/dist/codebase-index/json-parser.d.ts +8 -0
- package/dist/codebase-index/json-parser.d.ts.map +1 -0
- package/dist/codebase-index/lsp-kind.d.ts +53 -0
- package/dist/codebase-index/lsp-kind.d.ts.map +1 -0
- package/dist/codebase-index/py-parser.d.ts +16 -0
- package/dist/codebase-index/py-parser.d.ts.map +1 -0
- package/dist/codebase-index/refs-extractor.d.ts +25 -0
- package/dist/codebase-index/refs-extractor.d.ts.map +1 -0
- package/dist/codebase-index/rs-parser.d.ts +8 -0
- package/dist/codebase-index/rs-parser.d.ts.map +1 -0
- package/dist/codebase-index/schema.d.ts +82 -0
- package/dist/codebase-index/schema.d.ts.map +1 -0
- package/dist/codebase-index/ts-parser.d.ts +30 -0
- package/dist/codebase-index/ts-parser.d.ts.map +1 -0
- package/dist/codebase-index/worker-protocol.d.ts +77 -0
- package/dist/codebase-index/worker-protocol.d.ts.map +1 -0
- package/dist/codebase-index/worker.d.ts +16 -2
- package/dist/codebase-index/worker.d.ts.map +1 -0
- package/dist/codebase-index/worker.js +103 -65
- package/dist/codebase-index/worker.js.map +7 -1
- package/dist/codebase-index/writer.d.ts +189 -0
- package/dist/codebase-index/writer.d.ts.map +1 -0
- package/dist/codebase-index/yaml-parser.d.ts +8 -0
- package/dist/codebase-index/yaml-parser.d.ts.map +1 -0
- package/dist/design.d.ts +38 -0
- package/dist/design.d.ts.map +1 -0
- package/dist/design.js +270 -0
- package/dist/design.js.map +7 -0
- package/dist/diff.d.ts +4 -5
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +14 -12
- package/dist/diff.js.map +7 -1
- package/dist/document.d.ts +4 -5
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +8 -7
- package/dist/document.js.map +7 -1
- package/dist/edit.d.ts +5 -36
- package/dist/edit.d.ts.map +1 -0
- package/dist/edit.js +28 -16
- package/dist/edit.js.map +7 -1
- package/dist/exec.d.ts +67 -2
- package/dist/exec.d.ts.map +1 -0
- package/dist/exec.js +50 -25
- package/dist/exec.js.map +7 -1
- package/dist/fetch.d.ts +4 -32
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +89 -77
- package/dist/fetch.js.map +7 -1
- package/dist/format.d.ts +4 -5
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +2415 -28
- package/dist/format.js.map +7 -1
- package/dist/git.d.ts +4 -5
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +10 -9
- package/dist/git.js.map +7 -1
- package/dist/glob.d.ts +4 -5
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +15 -12
- package/dist/glob.js.map +7 -1
- package/dist/grep.d.ts +4 -5
- package/dist/grep.d.ts.map +1 -0
- package/dist/grep.js +13 -10
- package/dist/grep.js.map +7 -1
- package/dist/index.d.ts +19 -724
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6088 -1690
- package/dist/index.js.map +7 -1
- package/dist/install.d.ts +4 -5
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +2680 -31
- package/dist/install.js.map +7 -1
- package/dist/json.d.ts +4 -5
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +13 -11
- package/dist/json.js.map +7 -1
- package/dist/kanban.d.ts +107 -0
- package/dist/kanban.d.ts.map +1 -0
- package/dist/kanban.js +845 -0
- package/dist/kanban.js.map +7 -0
- package/dist/languages/detect.d.ts +3 -0
- package/dist/languages/detect.d.ts.map +1 -0
- package/dist/languages/diagnostics.d.ts +15 -0
- package/dist/languages/diagnostics.d.ts.map +1 -0
- package/dist/languages/execute-tool.d.ts +34 -0
- package/dist/languages/execute-tool.d.ts.map +1 -0
- package/dist/languages/execute.d.ts +18 -0
- package/dist/languages/execute.d.ts.map +1 -0
- package/dist/languages/index.d.ts +12 -0
- package/dist/languages/index.d.ts.map +1 -0
- package/dist/languages/index.js +4457 -0
- package/dist/languages/index.js.map +7 -0
- package/dist/languages/legacy-bridge.d.ts +29 -0
- package/dist/languages/legacy-bridge.d.ts.map +1 -0
- package/dist/languages/package-tool.d.ts +29 -0
- package/dist/languages/package-tool.d.ts.map +1 -0
- package/dist/languages/plan.d.ts +4 -0
- package/dist/languages/plan.d.ts.map +1 -0
- package/dist/languages/profile-helpers.d.ts +14 -0
- package/dist/languages/profile-helpers.d.ts.map +1 -0
- package/dist/languages/profiles/additional.d.ts +3 -0
- package/dist/languages/profiles/additional.d.ts.map +1 -0
- package/dist/languages/profiles/primary.d.ts +3 -0
- package/dist/languages/profiles/primary.d.ts.map +1 -0
- package/dist/languages/registry.d.ts +11 -0
- package/dist/languages/registry.d.ts.map +1 -0
- package/dist/languages/tool.d.ts +32 -0
- package/dist/languages/tool.d.ts.map +1 -0
- package/dist/languages/types.d.ts +207 -0
- package/dist/languages/types.d.ts.map +1 -0
- package/dist/lint.d.ts +4 -5
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +2415 -27
- package/dist/lint.js.map +7 -1
- package/dist/logs.d.ts +4 -5
- package/dist/logs.d.ts.map +1 -0
- package/dist/logs.js +16 -10
- package/dist/logs.js.map +7 -1
- package/dist/memory.d.ts +7 -8
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +7 -5
- package/dist/memory.js.map +7 -1
- package/dist/mode.d.ts +4 -5
- package/dist/mode.d.ts.map +1 -0
- package/dist/mode.js +3 -3
- package/dist/mode.js.map +7 -1
- package/dist/next-steps.d.ts +5 -6
- package/dist/next-steps.d.ts.map +1 -0
- package/dist/next-steps.js +5 -4
- package/dist/next-steps.js.map +7 -1
- package/dist/outdated.d.ts +4 -5
- package/dist/outdated.d.ts.map +1 -0
- package/dist/outdated.js +4777 -68
- package/dist/outdated.js.map +7 -1
- package/dist/pack.d.ts +3 -6
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +5275 -1019
- package/dist/pack.js.map +7 -1
- package/dist/patch.d.ts +4 -5
- package/dist/patch.d.ts.map +1 -0
- package/dist/patch.js +18 -16
- package/dist/patch.js.map +7 -1
- package/dist/plan.d.ts +55 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +280 -0
- package/dist/plan.js.map +7 -0
- package/dist/process-guardian.d.ts +100 -0
- package/dist/process-guardian.d.ts.map +1 -0
- package/dist/process-registry-persistent.d.ts +121 -0
- package/dist/process-registry-persistent.d.ts.map +1 -0
- package/dist/process-registry.d.ts +13 -17
- package/dist/process-registry.d.ts.map +1 -0
- package/dist/process-registry.js +15 -9
- package/dist/process-registry.js.map +7 -1
- package/dist/ps-slash.d.ts +106 -0
- package/dist/ps-slash.d.ts.map +1 -0
- package/dist/ps-slash.js +1291 -0
- package/dist/ps-slash.js.map +7 -0
- package/dist/read.d.ts +4 -5
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +13 -11
- package/dist/read.js.map +7 -1
- package/dist/replace.d.ts +4 -5
- package/dist/replace.d.ts.map +1 -0
- package/dist/replace.js +26 -13
- package/dist/replace.js.map +7 -1
- package/dist/scaffold.d.ts +4 -5
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +16 -14
- package/dist/scaffold.js.map +7 -1
- package/dist/search.d.ts +5 -6
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +24 -25
- package/dist/search.js.map +7 -1
- package/dist/set-working-dir.d.ts +18 -0
- package/dist/set-working-dir.d.ts.map +1 -0
- package/dist/shim/node-sqlite.d.ts +3 -0
- package/dist/shim/node-sqlite.d.ts.map +1 -0
- package/dist/skill.d.ts +45 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +175 -0
- package/dist/skill.js.map +7 -0
- package/dist/spawn-background.d.ts +80 -0
- package/dist/spawn-background.d.ts.map +1 -0
- package/dist/task.d.ts +35 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +380 -0
- package/dist/task.js.map +7 -0
- package/dist/test.d.ts +4 -5
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +2419 -27
- package/dist/test.js.map +7 -1
- package/dist/todo.d.ts +4 -5
- package/dist/todo.d.ts.map +1 -0
- package/dist/todo.js +5 -5
- package/dist/todo.js.map +7 -1
- package/dist/tool-diff.d.ts +16 -17
- package/dist/tool-diff.d.ts.map +1 -0
- package/dist/tool-diff.js +11 -3
- package/dist/tool-diff.js.map +7 -1
- package/dist/tool-help.d.ts +4 -5
- package/dist/tool-help.d.ts.map +1 -0
- package/dist/tool-help.js +3 -3
- package/dist/tool-help.js.map +7 -1
- package/dist/tool-icon-map.d.ts +45 -0
- package/dist/tool-icon-map.d.ts.map +1 -0
- package/dist/tool-icons.d.ts +5 -6
- package/dist/tool-icons.d.ts.map +1 -0
- package/dist/tool-icons.js +16 -4
- package/dist/tool-icons.js.map +7 -1
- package/dist/tool-search.d.ts +4 -5
- package/dist/tool-search.d.ts.map +1 -0
- package/dist/tool-search.js +3 -3
- package/dist/tool-search.js.map +7 -1
- package/dist/tool-summary.d.ts +4 -5
- package/dist/tool-summary.d.ts.map +1 -0
- package/dist/tool-summary.js +5 -3
- package/dist/tool-summary.js.map +7 -1
- package/dist/tool-use.d.ts +4 -5
- package/dist/tool-use.d.ts.map +1 -0
- package/dist/tool-use.js +3 -3
- package/dist/tool-use.js.map +7 -1
- package/dist/tree.d.ts +4 -5
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +11 -9
- package/dist/tree.js.map +7 -1
- package/dist/typecheck.d.ts +4 -5
- package/dist/typecheck.d.ts.map +1 -0
- package/dist/typecheck.js +2416 -28
- package/dist/typecheck.js.map +7 -1
- package/dist/write.d.ts +4 -5
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +21 -16
- package/dist/write.js.map +7 -1
- package/package.json +34 -6
- package/dist/background-indexer-BeDBxfSh.d.ts +0 -371
- package/dist/exec-2OKoT6tk.d.ts +0 -121
package/dist/install.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import * as Core from '@wrongstack/core';
|
|
2
|
-
import { recordPackageAction, detectPackageEcosystem, buildChildEnv, expectDefined, wstackGlobalRoot } from '@wrongstack/core';
|
|
3
|
-
import * as path3 from 'node:path';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { spawn } from 'node:child_process';
|
|
6
|
-
import * as fs from 'node:fs';
|
|
7
|
-
import { mkdirSync, createWriteStream } from 'node:fs';
|
|
8
|
-
import * as fsp from 'node:fs/promises';
|
|
9
|
-
import * as os from 'node:os';
|
|
10
|
-
|
|
11
1
|
// src/install.ts
|
|
2
|
+
import { join as join7 } from "node:path";
|
|
3
|
+
import { detectPackageEcosystem, recordPackageAction } from "@wrongstack/core";
|
|
4
|
+
|
|
5
|
+
// src/_spawn-stream.ts
|
|
6
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7
|
+
import { buildChildEnv } from "@wrongstack/core";
|
|
8
|
+
|
|
9
|
+
// src/_output-spool.ts
|
|
10
|
+
import { createWriteStream, mkdirSync } from "node:fs";
|
|
11
|
+
import * as fsp from "node:fs/promises";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { wstackGlobalRoot } from "@wrongstack/core";
|
|
12
14
|
var SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
13
15
|
var SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;
|
|
14
16
|
var sweepStarted = false;
|
|
15
17
|
function toolOutputDir() {
|
|
16
|
-
return
|
|
18
|
+
return path.join(wstackGlobalRoot(), "tool-output");
|
|
17
19
|
}
|
|
18
20
|
function sweepOldSpoolFiles(dir) {
|
|
19
21
|
if (sweepStarted) return;
|
|
@@ -23,7 +25,7 @@ function sweepOldSpoolFiles(dir) {
|
|
|
23
25
|
const now = Date.now();
|
|
24
26
|
for (const name of await fsp.readdir(dir)) {
|
|
25
27
|
if (!name.endsWith(".log")) continue;
|
|
26
|
-
const p =
|
|
28
|
+
const p = path.join(dir, name);
|
|
27
29
|
try {
|
|
28
30
|
const st = await fsp.stat(p);
|
|
29
31
|
if (now - st.mtimeMs > SPOOL_RETENTION_MS) await fsp.unlink(p);
|
|
@@ -40,7 +42,7 @@ function spoolNote(info) {
|
|
|
40
42
|
[output truncated \u2014 full ${info.bytes} bytes at ${info.path}${dropped}; read/grep that file selectively instead of re-running with more output]`;
|
|
41
43
|
}
|
|
42
44
|
function createOutputSpool(opts) {
|
|
43
|
-
const threshold = opts.thresholdBytes;
|
|
45
|
+
const threshold = opts.thresholdBytes ?? 32768;
|
|
44
46
|
const safeTool = opts.tool.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
45
47
|
let head = "";
|
|
46
48
|
let headBytes = 0;
|
|
@@ -58,7 +60,7 @@ function createOutputSpool(opts) {
|
|
|
58
60
|
sweepOldSpoolFiles(dir);
|
|
59
61
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
60
62
|
const rand = Math.random().toString(36).slice(2, 6);
|
|
61
|
-
filePath =
|
|
63
|
+
filePath = path.join(dir, `${stamp}-${safeTool}-${rand}.log`);
|
|
62
64
|
stream = createWriteStream(filePath, { flags: "w", encoding: "utf8" });
|
|
63
65
|
stream.on("error", () => {
|
|
64
66
|
failed = true;
|
|
@@ -111,6 +113,10 @@ function createOutputSpool(opts) {
|
|
|
111
113
|
};
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
// src/process-registry.ts
|
|
117
|
+
import { spawn } from "node:child_process";
|
|
118
|
+
import * as os from "node:os";
|
|
119
|
+
|
|
114
120
|
// src/circuit-breaker.ts
|
|
115
121
|
var DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;
|
|
116
122
|
var DEFAULT_SLOW_CALL_THRESHOLD_MS = 18e4;
|
|
@@ -303,7 +309,8 @@ var CircuitBreaker = class {
|
|
|
303
309
|
}
|
|
304
310
|
};
|
|
305
311
|
|
|
306
|
-
// src/
|
|
312
|
+
// src/_redact-command.ts
|
|
313
|
+
import { expectDefined } from "@wrongstack/core";
|
|
307
314
|
var SENSITIVE_FLAG_PATTERNS = [
|
|
308
315
|
// --flag=value or --flag "value" (value captured up to next space or comma)
|
|
309
316
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
@@ -333,6 +340,8 @@ function redactCommand(cmd) {
|
|
|
333
340
|
}
|
|
334
341
|
return result;
|
|
335
342
|
}
|
|
343
|
+
|
|
344
|
+
// src/process-registry.ts
|
|
336
345
|
var DEFAULT_GRACE_MS = 2e3;
|
|
337
346
|
var WIN32_TASKKILL_TIMEOUT_MS = 5e3;
|
|
338
347
|
function killWin32Tree(pid, opts = {}) {
|
|
@@ -602,7 +611,8 @@ var ProcessRegistryImpl = class {
|
|
|
602
611
|
if (liveRealChild && killWin32Tree(pid, {
|
|
603
612
|
timeoutMs: Math.max(graceMs, WIN32_TASKKILL_TIMEOUT_MS),
|
|
604
613
|
onSettled: directFallback
|
|
605
|
-
}))
|
|
614
|
+
})) {
|
|
615
|
+
} else {
|
|
606
616
|
try {
|
|
607
617
|
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
608
618
|
} catch {
|
|
@@ -689,15 +699,19 @@ function getProcessRegistry() {
|
|
|
689
699
|
}
|
|
690
700
|
return _registry;
|
|
691
701
|
}
|
|
702
|
+
|
|
703
|
+
// src/_win32-resolve.ts
|
|
704
|
+
import * as fs from "node:fs";
|
|
705
|
+
import * as path2 from "node:path";
|
|
692
706
|
function resolveWin32Command(cmd) {
|
|
693
707
|
if (process.platform !== "win32") return cmd;
|
|
694
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
708
|
+
if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
|
|
695
709
|
return cmd;
|
|
696
710
|
}
|
|
697
711
|
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
698
|
-
const pathDirs = (process.env["PATH"] ?? "").split(
|
|
712
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
|
|
699
713
|
for (const dir of pathDirs) {
|
|
700
|
-
const base =
|
|
714
|
+
const base = path2.join(dir, cmd);
|
|
701
715
|
for (const ext of pathext) {
|
|
702
716
|
const full = `${base}${ext}`;
|
|
703
717
|
try {
|
|
@@ -735,7 +749,7 @@ function quoteWin32CmdArg(arg) {
|
|
|
735
749
|
// src/_spawn-stream.ts
|
|
736
750
|
var isWin = process.platform === "win32";
|
|
737
751
|
async function* spawnStream(opts) {
|
|
738
|
-
const max = opts.maxBytes;
|
|
752
|
+
const max = opts.maxBytes ?? 2e5;
|
|
739
753
|
const flushAt = opts.flushBytes ?? 4 * 1024;
|
|
740
754
|
const maxQueue = opts.maxQueueSize ?? 500;
|
|
741
755
|
let stdout = "";
|
|
@@ -748,7 +762,7 @@ async function* spawnStream(opts) {
|
|
|
748
762
|
const shim = needsShell ? buildWin32CmdShimInvocation(resolved, opts.args) : null;
|
|
749
763
|
const cmd = shim?.command ?? resolved;
|
|
750
764
|
const args = shim?.args ?? opts.args;
|
|
751
|
-
const child =
|
|
765
|
+
const child = spawn2(cmd, args, {
|
|
752
766
|
cwd: opts.cwd,
|
|
753
767
|
env: buildChildEnv(),
|
|
754
768
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -841,8 +855,8 @@ async function* spawnStream(opts) {
|
|
|
841
855
|
try {
|
|
842
856
|
for (; ; ) {
|
|
843
857
|
while (queue.length === 0) {
|
|
844
|
-
await new Promise((
|
|
845
|
-
waiter =
|
|
858
|
+
await new Promise((resolve6) => {
|
|
859
|
+
waiter = resolve6;
|
|
846
860
|
});
|
|
847
861
|
}
|
|
848
862
|
const chunk = queue.shift();
|
|
@@ -896,15 +910,19 @@ async function* spawnStream(opts) {
|
|
|
896
910
|
}
|
|
897
911
|
}
|
|
898
912
|
}
|
|
913
|
+
|
|
914
|
+
// src/_util.ts
|
|
915
|
+
import * as path3 from "node:path";
|
|
916
|
+
import * as Core from "@wrongstack/core";
|
|
899
917
|
async function detectPackageManager(cwd) {
|
|
900
|
-
const { stat:
|
|
918
|
+
const { stat: stat5 } = await import("node:fs/promises");
|
|
901
919
|
try {
|
|
902
|
-
await
|
|
920
|
+
await stat5(`${cwd}/pnpm-lock.yaml`);
|
|
903
921
|
return "pnpm";
|
|
904
922
|
} catch {
|
|
905
923
|
}
|
|
906
924
|
try {
|
|
907
|
-
await
|
|
925
|
+
await stat5(`${cwd}/yarn.lock`);
|
|
908
926
|
return "yarn";
|
|
909
927
|
} catch {
|
|
910
928
|
}
|
|
@@ -1002,6 +1020,2608 @@ function normalizeCommandOutput(raw, opts = {}) {
|
|
|
1002
1020
|
return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);
|
|
1003
1021
|
}
|
|
1004
1022
|
|
|
1023
|
+
// src/languages/legacy-bridge.ts
|
|
1024
|
+
import * as fs5 from "node:fs/promises";
|
|
1025
|
+
import * as path8 from "node:path";
|
|
1026
|
+
|
|
1027
|
+
// src/languages/detect.ts
|
|
1028
|
+
import { createHash } from "node:crypto";
|
|
1029
|
+
import * as fs2 from "node:fs/promises";
|
|
1030
|
+
import * as path4 from "node:path";
|
|
1031
|
+
|
|
1032
|
+
// src/languages/profile-helpers.ts
|
|
1033
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
1034
|
+
var DEFAULT_OUTPUT_LIMIT_BYTES = 2e5;
|
|
1035
|
+
function processPlan(ctx, operation, command, args, options) {
|
|
1036
|
+
return {
|
|
1037
|
+
profileId: ctx.workspace.language,
|
|
1038
|
+
workspaceId: ctx.workspace.id,
|
|
1039
|
+
operation,
|
|
1040
|
+
kind: "process",
|
|
1041
|
+
command,
|
|
1042
|
+
args: Object.freeze([...args]),
|
|
1043
|
+
cwd: ctx.workspace.root,
|
|
1044
|
+
env: Object.freeze({}),
|
|
1045
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
1046
|
+
outputLimitBytes: DEFAULT_OUTPUT_LIMIT_BYTES,
|
|
1047
|
+
mutating: options.mutating ?? false,
|
|
1048
|
+
network: options.network ?? false,
|
|
1049
|
+
executesProjectCode: options.executesProjectCode ?? false,
|
|
1050
|
+
reason: options.reason,
|
|
1051
|
+
evidence: ctx.workspace.evidence,
|
|
1052
|
+
parser: options.parser
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function internalPlan(ctx, operation, parser, reason) {
|
|
1056
|
+
return {
|
|
1057
|
+
profileId: ctx.workspace.language,
|
|
1058
|
+
workspaceId: ctx.workspace.id,
|
|
1059
|
+
operation,
|
|
1060
|
+
kind: "internal",
|
|
1061
|
+
command: null,
|
|
1062
|
+
args: Object.freeze([]),
|
|
1063
|
+
cwd: ctx.workspace.root,
|
|
1064
|
+
env: Object.freeze({}),
|
|
1065
|
+
timeoutMs: 5e3,
|
|
1066
|
+
outputLimitBytes: 32768,
|
|
1067
|
+
mutating: false,
|
|
1068
|
+
network: false,
|
|
1069
|
+
executesProjectCode: false,
|
|
1070
|
+
reason,
|
|
1071
|
+
evidence: ctx.workspace.evidence,
|
|
1072
|
+
parser
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
function unavailable(ctx, operation, reason) {
|
|
1076
|
+
return {
|
|
1077
|
+
status: "unavailable",
|
|
1078
|
+
profileId: ctx.workspace.language,
|
|
1079
|
+
workspaceId: ctx.workspace.id,
|
|
1080
|
+
operation,
|
|
1081
|
+
reason
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function packageNames(ctx) {
|
|
1085
|
+
return ctx.options.packages ?? [];
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// src/languages/profiles/additional.ts
|
|
1089
|
+
var IGNORES = Object.freeze([
|
|
1090
|
+
".git",
|
|
1091
|
+
".wrongstack",
|
|
1092
|
+
"node_modules",
|
|
1093
|
+
"vendor",
|
|
1094
|
+
"dist",
|
|
1095
|
+
"build",
|
|
1096
|
+
"coverage"
|
|
1097
|
+
]);
|
|
1098
|
+
function pythonProfile() {
|
|
1099
|
+
return {
|
|
1100
|
+
id: "python",
|
|
1101
|
+
displayName: "Python",
|
|
1102
|
+
extensions: Object.freeze([".py", ".pyi"]),
|
|
1103
|
+
lspLanguageIds: Object.freeze(["python"]),
|
|
1104
|
+
detectors: Object.freeze([
|
|
1105
|
+
{ kind: "config", filename: "pyproject.toml", weight: 90 },
|
|
1106
|
+
{ kind: "config", filename: "setup.py", weight: 70 },
|
|
1107
|
+
{ kind: "config", filename: "setup.cfg", weight: 60 },
|
|
1108
|
+
{ kind: "manifest", filename: "requirements.txt", weight: 55 },
|
|
1109
|
+
{ kind: "manifest", filename: "Pipfile", weight: 50 },
|
|
1110
|
+
{ kind: "lockfile", filename: "poetry.lock", weight: 30 },
|
|
1111
|
+
{ kind: "lockfile", filename: "Pipfile.lock", weight: 30 },
|
|
1112
|
+
{ kind: "lockfile", filename: "uv.lock", weight: 30 }
|
|
1113
|
+
]),
|
|
1114
|
+
ignoredDirectories: IGNORES,
|
|
1115
|
+
packageManagers: Object.freeze(["pip", "poetry", "pipenv", "uv"]),
|
|
1116
|
+
executables: Object.freeze(["python", "python3", "ruff", "black", "pytest", "mypy", "pyright"]),
|
|
1117
|
+
operations: Object.freeze({
|
|
1118
|
+
syntax: async (ctx) => {
|
|
1119
|
+
const py = ctx.options.target ? "python3" : "python3";
|
|
1120
|
+
return ctx.options.target ? processPlan(ctx, "syntax", py, ["-m", "py_compile", ctx.options.target], {
|
|
1121
|
+
parser: "python",
|
|
1122
|
+
reason: "Compile the target file to check for syntax errors."
|
|
1123
|
+
}) : unavailable(ctx, "syntax", "Python syntax check requires an explicit target file.");
|
|
1124
|
+
},
|
|
1125
|
+
semantic: async (ctx) => processPlan(ctx, "semantic", "mypy", [".", "--no-error-summary"], {
|
|
1126
|
+
parser: "mypy",
|
|
1127
|
+
reason: "Run mypy type checking on the workspace.",
|
|
1128
|
+
executesProjectCode: true
|
|
1129
|
+
}),
|
|
1130
|
+
lint: async (ctx) => processPlan(ctx, "lint", "ruff", ["check", "."], {
|
|
1131
|
+
parser: "ruff",
|
|
1132
|
+
reason: "Run the Ruff linter on the workspace."
|
|
1133
|
+
}),
|
|
1134
|
+
"format-check": async (ctx) => processPlan(ctx, "format-check", "ruff", ["format", "--check", "."], {
|
|
1135
|
+
parser: "ruff",
|
|
1136
|
+
reason: "Check Python formatting without writing files."
|
|
1137
|
+
}),
|
|
1138
|
+
"format-write": async (ctx) => processPlan(ctx, "format-write", "ruff", ["format", "."], {
|
|
1139
|
+
parser: "ruff",
|
|
1140
|
+
reason: "Format Python source files.",
|
|
1141
|
+
mutating: true
|
|
1142
|
+
}),
|
|
1143
|
+
test: async (ctx) => processPlan(
|
|
1144
|
+
ctx,
|
|
1145
|
+
"test",
|
|
1146
|
+
"pytest",
|
|
1147
|
+
[...ctx.options.filter ? ["-k", ctx.options.filter] : [], "."],
|
|
1148
|
+
{
|
|
1149
|
+
parser: "pytest",
|
|
1150
|
+
reason: ctx.options.filter ? "Run filtered Python tests." : "Run all Python tests.",
|
|
1151
|
+
mutating: true,
|
|
1152
|
+
executesProjectCode: true
|
|
1153
|
+
}
|
|
1154
|
+
),
|
|
1155
|
+
build: async (ctx) => unavailable(ctx, "build", "Python is interpreted; use semantic or test instead."),
|
|
1156
|
+
"debug-compile": async (ctx) => processPlan(ctx, "debug-compile", "mypy", [".", "--no-error-summary"], {
|
|
1157
|
+
parser: "mypy",
|
|
1158
|
+
reason: "Collect mypy type diagnostics.",
|
|
1159
|
+
executesProjectCode: true
|
|
1160
|
+
}),
|
|
1161
|
+
"package-install": async (ctx) => processPlan(ctx, "package-install", "pip", ["install", "--no-cache-dir"], {
|
|
1162
|
+
parser: "package-text",
|
|
1163
|
+
reason: "Install dependencies from requirements.",
|
|
1164
|
+
mutating: true,
|
|
1165
|
+
network: true
|
|
1166
|
+
}),
|
|
1167
|
+
"package-add": async (ctx) => {
|
|
1168
|
+
const names = packageNames(ctx);
|
|
1169
|
+
return names.length === 0 ? unavailable(ctx, "package-add", "At least one package name is required.") : processPlan(ctx, "package-add", "pip", ["install", ...names], {
|
|
1170
|
+
parser: "package-text",
|
|
1171
|
+
reason: "Install specified Python packages.",
|
|
1172
|
+
mutating: true,
|
|
1173
|
+
network: true
|
|
1174
|
+
});
|
|
1175
|
+
},
|
|
1176
|
+
"package-audit": async (ctx) => processPlan(ctx, "package-audit", "pip", ["audit"], {
|
|
1177
|
+
parser: "pip-audit",
|
|
1178
|
+
reason: "Audit Python dependencies for vulnerabilities.",
|
|
1179
|
+
network: true
|
|
1180
|
+
}),
|
|
1181
|
+
run: async (ctx) => {
|
|
1182
|
+
const entries = ["main.py", "app.py", "__main__.py", "manage.py"];
|
|
1183
|
+
const first = (await Promise.all(
|
|
1184
|
+
entries.map((name) => ctx.pathExists(name).then((ok) => ok ? name : void 0))
|
|
1185
|
+
)).find(Boolean);
|
|
1186
|
+
const args = first ? [first] : [];
|
|
1187
|
+
return processPlan(ctx, "run", "python3", args, {
|
|
1188
|
+
parser: "command-text",
|
|
1189
|
+
reason: first ? `Run the Python entry point ${first}.` : "Run the Python project (no common entry point detected \u2014 add the module path manually).",
|
|
1190
|
+
executesProjectCode: true,
|
|
1191
|
+
mutating: true
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
})
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
function hasGradleEvidence(ctx) {
|
|
1198
|
+
return ctx.workspace.evidence.some(
|
|
1199
|
+
(e) => e.kind === "manifest" && (e.value === "build.gradle" || e.value === "build.gradle.kts" || e.value === "settings.gradle") || e.kind === "lockfile" && e.value === "gradle.lockfile"
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
async function gradleRunner(ctx) {
|
|
1203
|
+
if (await ctx.pathExists("gradlew")) return "gradlew";
|
|
1204
|
+
return "gradle";
|
|
1205
|
+
}
|
|
1206
|
+
function javaProfile() {
|
|
1207
|
+
return {
|
|
1208
|
+
id: "java",
|
|
1209
|
+
displayName: "Java / Kotlin",
|
|
1210
|
+
extensions: Object.freeze([".java", ".kt", ".kts", ".scala"]),
|
|
1211
|
+
lspLanguageIds: Object.freeze(["java", "kotlin"]),
|
|
1212
|
+
detectors: Object.freeze([
|
|
1213
|
+
{ kind: "manifest", filename: "pom.xml", weight: 90 },
|
|
1214
|
+
{ kind: "manifest", filename: "build.gradle", weight: 85 },
|
|
1215
|
+
{ kind: "manifest", filename: "build.gradle.kts", weight: 85 },
|
|
1216
|
+
{ kind: "manifest", filename: "settings.gradle", weight: 50 },
|
|
1217
|
+
{ kind: "lockfile", filename: "gradle.lockfile", weight: 30 }
|
|
1218
|
+
]),
|
|
1219
|
+
ignoredDirectories: IGNORES,
|
|
1220
|
+
packageManagers: Object.freeze(["maven", "gradle"]),
|
|
1221
|
+
executables: Object.freeze(["mvn", "gradle", "gradlew", "java", "kotlinc"]),
|
|
1222
|
+
operations: Object.freeze({
|
|
1223
|
+
semantic: async (ctx) => {
|
|
1224
|
+
const isGradle = hasGradleEvidence(ctx);
|
|
1225
|
+
const runner = isGradle ? await gradleRunner(ctx) : "mvn";
|
|
1226
|
+
const args = isGradle ? ["compileJava"] : ["compile", "-q"];
|
|
1227
|
+
const reason = isGradle ? "Compile the Gradle project." : "Compile the Maven project.";
|
|
1228
|
+
return processPlan(ctx, "semantic", runner, args, {
|
|
1229
|
+
parser: isGradle ? "gradle" : "maven",
|
|
1230
|
+
reason,
|
|
1231
|
+
mutating: true,
|
|
1232
|
+
executesProjectCode: true
|
|
1233
|
+
});
|
|
1234
|
+
},
|
|
1235
|
+
lint: async (ctx) => unavailable(
|
|
1236
|
+
ctx,
|
|
1237
|
+
"lint",
|
|
1238
|
+
"Java linting requires a configured checkstyle or spotbugs plugin."
|
|
1239
|
+
),
|
|
1240
|
+
"format-check": async (ctx) => unavailable(
|
|
1241
|
+
ctx,
|
|
1242
|
+
"format-check",
|
|
1243
|
+
"Java formatting check requires a configured spotless or google-java-format plugin."
|
|
1244
|
+
),
|
|
1245
|
+
test: async (ctx) => {
|
|
1246
|
+
const isGradle = hasGradleEvidence(ctx);
|
|
1247
|
+
const runner = isGradle ? await gradleRunner(ctx) : "mvn";
|
|
1248
|
+
const args = isGradle ? ["test"] : ["test", "-q"];
|
|
1249
|
+
const reason = isGradle ? "Run Gradle tests." : "Run Maven tests.";
|
|
1250
|
+
return processPlan(ctx, "test", runner, args, {
|
|
1251
|
+
parser: isGradle ? "gradle" : "maven",
|
|
1252
|
+
reason,
|
|
1253
|
+
mutating: true,
|
|
1254
|
+
executesProjectCode: true
|
|
1255
|
+
});
|
|
1256
|
+
},
|
|
1257
|
+
build: async (ctx) => {
|
|
1258
|
+
const isGradle = hasGradleEvidence(ctx);
|
|
1259
|
+
const runner = isGradle ? await gradleRunner(ctx) : "mvn";
|
|
1260
|
+
const args = isGradle ? ["build"] : ["package", "-q", "-DskipTests"];
|
|
1261
|
+
const reason = isGradle ? "Build the Gradle project." : "Build the Maven project, skipping tests.";
|
|
1262
|
+
return processPlan(ctx, "build", runner, args, {
|
|
1263
|
+
parser: isGradle ? "gradle" : "maven",
|
|
1264
|
+
reason,
|
|
1265
|
+
mutating: true,
|
|
1266
|
+
executesProjectCode: true
|
|
1267
|
+
});
|
|
1268
|
+
},
|
|
1269
|
+
run: async (ctx) => {
|
|
1270
|
+
if (!hasGradleEvidence(ctx))
|
|
1271
|
+
return unavailable(
|
|
1272
|
+
ctx,
|
|
1273
|
+
"run",
|
|
1274
|
+
"Maven run requires exec-maven-plugin configuration. Use `mvn exec:java -q` manually."
|
|
1275
|
+
);
|
|
1276
|
+
const runner = await gradleRunner(ctx);
|
|
1277
|
+
return processPlan(ctx, "run", runner, ["run"], {
|
|
1278
|
+
parser: "command-text",
|
|
1279
|
+
reason: `Run the Gradle project entry point via ${runner}.`,
|
|
1280
|
+
executesProjectCode: true,
|
|
1281
|
+
mutating: true
|
|
1282
|
+
});
|
|
1283
|
+
},
|
|
1284
|
+
"package-install": async (ctx) => unavailable(
|
|
1285
|
+
ctx,
|
|
1286
|
+
"package-install",
|
|
1287
|
+
"JVM dependency installation happens through build or dependency:get."
|
|
1288
|
+
),
|
|
1289
|
+
"package-add": async (ctx) => {
|
|
1290
|
+
const names = packageNames(ctx);
|
|
1291
|
+
return names.length === 0 ? unavailable(ctx, "package-add", "At least one dependency coordinate is required.") : processPlan(ctx, "package-add", "mvn", ["dependency:get", `-Dartifact=${names[0]}`], {
|
|
1292
|
+
parser: "maven",
|
|
1293
|
+
reason: "Resolve and fetch a Maven dependency.",
|
|
1294
|
+
mutating: true,
|
|
1295
|
+
network: true
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
})
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
function rubyProfile() {
|
|
1302
|
+
return {
|
|
1303
|
+
id: "ruby",
|
|
1304
|
+
displayName: "Ruby",
|
|
1305
|
+
extensions: Object.freeze([".rb"]),
|
|
1306
|
+
lspLanguageIds: Object.freeze(["ruby"]),
|
|
1307
|
+
detectors: Object.freeze([
|
|
1308
|
+
{ kind: "manifest", filename: "Gemfile", weight: 90 },
|
|
1309
|
+
{ kind: "config", filename: ".ruby-version", weight: 40 },
|
|
1310
|
+
{ kind: "lockfile", filename: "Gemfile.lock", weight: 30 }
|
|
1311
|
+
]),
|
|
1312
|
+
ignoredDirectories: IGNORES,
|
|
1313
|
+
packageManagers: Object.freeze(["gem", "bundler"]),
|
|
1314
|
+
executables: Object.freeze(["ruby", "gem", "bundle", "rubocop", "rspec"]),
|
|
1315
|
+
operations: Object.freeze({
|
|
1316
|
+
syntax: async (ctx) => ctx.options.target ? processPlan(ctx, "syntax", "ruby", ["-c", ctx.options.target], {
|
|
1317
|
+
parser: "ruby",
|
|
1318
|
+
reason: "Check the target Ruby file for syntax errors."
|
|
1319
|
+
}) : unavailable(ctx, "syntax", "Ruby syntax check requires an explicit target file."),
|
|
1320
|
+
lint: async (ctx) => processPlan(ctx, "lint", "rubocop", ["--format=json"], {
|
|
1321
|
+
parser: "rubocop",
|
|
1322
|
+
reason: "Run RuboCop linter.",
|
|
1323
|
+
executesProjectCode: true
|
|
1324
|
+
}),
|
|
1325
|
+
"format-write": async (ctx) => processPlan(ctx, "format-write", "rubocop", ["--auto-correct"], {
|
|
1326
|
+
parser: "rubocop",
|
|
1327
|
+
reason: "Auto-correct RuboCop violations.",
|
|
1328
|
+
mutating: true,
|
|
1329
|
+
executesProjectCode: true
|
|
1330
|
+
}),
|
|
1331
|
+
test: async (ctx) => processPlan(ctx, "test", "rspec", [], {
|
|
1332
|
+
parser: "rspec",
|
|
1333
|
+
reason: "Run the RSpec test suite.",
|
|
1334
|
+
mutating: true,
|
|
1335
|
+
executesProjectCode: true
|
|
1336
|
+
}),
|
|
1337
|
+
"package-install": async (ctx) => processPlan(ctx, "package-install", "bundle", ["install"], {
|
|
1338
|
+
parser: "package-text",
|
|
1339
|
+
reason: "Install Ruby gem dependencies.",
|
|
1340
|
+
mutating: true,
|
|
1341
|
+
network: true,
|
|
1342
|
+
executesProjectCode: true
|
|
1343
|
+
}),
|
|
1344
|
+
"package-add": async (ctx) => {
|
|
1345
|
+
const names = packageNames(ctx);
|
|
1346
|
+
return names.length === 0 ? unavailable(ctx, "package-add", "At least one gem name is required.") : processPlan(ctx, "package-add", "gem", ["install", ...names], {
|
|
1347
|
+
parser: "package-text",
|
|
1348
|
+
reason: "Install specified Ruby gems.",
|
|
1349
|
+
mutating: true,
|
|
1350
|
+
network: true
|
|
1351
|
+
});
|
|
1352
|
+
},
|
|
1353
|
+
"package-audit": async (ctx) => processPlan(ctx, "package-audit", "bundle", ["audit", "--format=json"], {
|
|
1354
|
+
parser: "bundler-audit",
|
|
1355
|
+
reason: "Audit Ruby gems for vulnerabilities.",
|
|
1356
|
+
network: true
|
|
1357
|
+
}),
|
|
1358
|
+
run: async (ctx) => {
|
|
1359
|
+
const entries = ["main.rb", "app.rb", "server.rb", "config.ru"];
|
|
1360
|
+
const first = (await Promise.all(
|
|
1361
|
+
entries.map((name) => ctx.pathExists(name).then((ok) => ok ? name : void 0))
|
|
1362
|
+
)).find(Boolean);
|
|
1363
|
+
const hasGemfile = await ctx.pathExists("Gemfile");
|
|
1364
|
+
const cmd = hasGemfile ? "bundle" : "ruby";
|
|
1365
|
+
const args = hasGemfile ? ["exec", "ruby", first ?? ""] : [first ?? ""];
|
|
1366
|
+
return processPlan(ctx, "run", cmd, args, {
|
|
1367
|
+
parser: "command-text",
|
|
1368
|
+
reason: first ? `Run the Ruby entry point ${first}.` : "Run the Ruby project (no common entry point detected \u2014 add the file path manually).",
|
|
1369
|
+
executesProjectCode: true,
|
|
1370
|
+
mutating: true
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
})
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
function cProfile() {
|
|
1377
|
+
return {
|
|
1378
|
+
id: "c",
|
|
1379
|
+
displayName: "C / C++",
|
|
1380
|
+
extensions: Object.freeze([".c", ".h"]),
|
|
1381
|
+
lspLanguageIds: Object.freeze(["c"]),
|
|
1382
|
+
detectors: Object.freeze([
|
|
1383
|
+
{ kind: "manifest", filename: "CMakeLists.txt", weight: 85 },
|
|
1384
|
+
{ kind: "manifest", filename: "Makefile", weight: 60 },
|
|
1385
|
+
{ kind: "config", suffix: ".cmake", weight: 50 }
|
|
1386
|
+
]),
|
|
1387
|
+
ignoredDirectories: IGNORES,
|
|
1388
|
+
packageManagers: Object.freeze([]),
|
|
1389
|
+
executables: Object.freeze(["cc", "gcc", "clang", "cmake", "make"]),
|
|
1390
|
+
operations: Object.freeze({
|
|
1391
|
+
semantic: async (ctx) => processPlan(ctx, "semantic", "cmake", ["--build", ".", "--target", "all"], {
|
|
1392
|
+
parser: "cmake",
|
|
1393
|
+
reason: "Build C project to collect compiler diagnostics.",
|
|
1394
|
+
mutating: true,
|
|
1395
|
+
executesProjectCode: true
|
|
1396
|
+
}),
|
|
1397
|
+
test: async (ctx) => unavailable(
|
|
1398
|
+
ctx,
|
|
1399
|
+
"test",
|
|
1400
|
+
"C test execution requires a configured test runner (ctest, etc.)."
|
|
1401
|
+
),
|
|
1402
|
+
build: async (ctx) => processPlan(ctx, "build", "cmake", ["--build", "."], {
|
|
1403
|
+
parser: "cmake",
|
|
1404
|
+
reason: "Build the C project.",
|
|
1405
|
+
mutating: true,
|
|
1406
|
+
executesProjectCode: true
|
|
1407
|
+
})
|
|
1408
|
+
})
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
function cppProfile() {
|
|
1412
|
+
return {
|
|
1413
|
+
...cProfile(),
|
|
1414
|
+
id: "cpp",
|
|
1415
|
+
displayName: "C++",
|
|
1416
|
+
extensions: Object.freeze([".cpp", ".cc", ".cxx", ".hpp", ".hxx"]),
|
|
1417
|
+
lspLanguageIds: Object.freeze(["cpp"]),
|
|
1418
|
+
detectors: Object.freeze([
|
|
1419
|
+
{ kind: "manifest", filename: "CMakeLists.txt", weight: 85 },
|
|
1420
|
+
{ kind: "manifest", filename: "Makefile", weight: 50 }
|
|
1421
|
+
]),
|
|
1422
|
+
executables: Object.freeze(["c++", "g++", "clang++", "cmake", "make"])
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
function swiftProfile() {
|
|
1426
|
+
return {
|
|
1427
|
+
id: "swift",
|
|
1428
|
+
displayName: "Swift",
|
|
1429
|
+
extensions: Object.freeze([".swift"]),
|
|
1430
|
+
lspLanguageIds: Object.freeze(["swift"]),
|
|
1431
|
+
detectors: Object.freeze([{ kind: "manifest", filename: "Package.swift", weight: 90 }]),
|
|
1432
|
+
ignoredDirectories: IGNORES,
|
|
1433
|
+
packageManagers: Object.freeze(["swift"]),
|
|
1434
|
+
executables: Object.freeze(["swift", "swiftc"]),
|
|
1435
|
+
operations: Object.freeze({
|
|
1436
|
+
semantic: async (ctx) => processPlan(ctx, "semantic", "swift", ["build"], {
|
|
1437
|
+
parser: "swift",
|
|
1438
|
+
reason: "Build Swift package to collect compiler diagnostics.",
|
|
1439
|
+
mutating: true,
|
|
1440
|
+
executesProjectCode: true
|
|
1441
|
+
}),
|
|
1442
|
+
test: async (ctx) => processPlan(ctx, "test", "swift", ["test"], {
|
|
1443
|
+
parser: "swift-test",
|
|
1444
|
+
reason: "Run Swift tests.",
|
|
1445
|
+
mutating: true,
|
|
1446
|
+
executesProjectCode: true
|
|
1447
|
+
}),
|
|
1448
|
+
build: async (ctx) => processPlan(ctx, "build", "swift", ["build", "-c", "release"], {
|
|
1449
|
+
parser: "swift",
|
|
1450
|
+
reason: "Build the Swift package in release mode.",
|
|
1451
|
+
mutating: true,
|
|
1452
|
+
executesProjectCode: true
|
|
1453
|
+
}),
|
|
1454
|
+
run: async (ctx) => processPlan(ctx, "run", "swift", ["run"], {
|
|
1455
|
+
parser: "command-text",
|
|
1456
|
+
reason: "Run the Swift package entry point.",
|
|
1457
|
+
executesProjectCode: true
|
|
1458
|
+
})
|
|
1459
|
+
})
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
function dartProfile() {
|
|
1463
|
+
return {
|
|
1464
|
+
id: "dart",
|
|
1465
|
+
displayName: "Dart / Flutter",
|
|
1466
|
+
extensions: Object.freeze([".dart"]),
|
|
1467
|
+
lspLanguageIds: Object.freeze(["dart"]),
|
|
1468
|
+
detectors: Object.freeze([
|
|
1469
|
+
{ kind: "manifest", filename: "pubspec.yaml", weight: 90 },
|
|
1470
|
+
{ kind: "lockfile", filename: "pubspec.lock", weight: 30 }
|
|
1471
|
+
]),
|
|
1472
|
+
ignoredDirectories: IGNORES,
|
|
1473
|
+
packageManagers: Object.freeze(["pub"]),
|
|
1474
|
+
executables: Object.freeze(["dart", "flutter"]),
|
|
1475
|
+
operations: Object.freeze({
|
|
1476
|
+
semantic: async (ctx) => processPlan(ctx, "semantic", "dart", ["analyze"], {
|
|
1477
|
+
parser: "dart-analyze",
|
|
1478
|
+
reason: "Run Dart static analysis."
|
|
1479
|
+
}),
|
|
1480
|
+
"format-check": async (ctx) => processPlan(
|
|
1481
|
+
ctx,
|
|
1482
|
+
"format-check",
|
|
1483
|
+
"dart",
|
|
1484
|
+
["format", "--output=none", "--set-exit-if-changed", "."],
|
|
1485
|
+
{
|
|
1486
|
+
parser: "dart-format",
|
|
1487
|
+
reason: "Check Dart formatting without writing files."
|
|
1488
|
+
}
|
|
1489
|
+
),
|
|
1490
|
+
"format-write": async (ctx) => processPlan(ctx, "format-write", "dart", ["format", "."], {
|
|
1491
|
+
parser: "dart-format",
|
|
1492
|
+
reason: "Format Dart source files.",
|
|
1493
|
+
mutating: true
|
|
1494
|
+
}),
|
|
1495
|
+
test: async (ctx) => processPlan(ctx, "test", "dart", ["test"], {
|
|
1496
|
+
parser: "dart-test",
|
|
1497
|
+
reason: "Run Dart tests.",
|
|
1498
|
+
mutating: true,
|
|
1499
|
+
executesProjectCode: true
|
|
1500
|
+
}),
|
|
1501
|
+
"package-install": async (ctx) => processPlan(ctx, "package-install", "dart", ["pub", "get"], {
|
|
1502
|
+
parser: "package-text",
|
|
1503
|
+
reason: "Fetch Dart package dependencies.",
|
|
1504
|
+
mutating: true,
|
|
1505
|
+
network: true
|
|
1506
|
+
}),
|
|
1507
|
+
"package-outdated": async (ctx) => processPlan(ctx, "package-outdated", "dart", ["pub", "outdated"], {
|
|
1508
|
+
parser: "dart-outdated",
|
|
1509
|
+
reason: "Check for outdated Dart packages.",
|
|
1510
|
+
network: true
|
|
1511
|
+
}),
|
|
1512
|
+
run: async (ctx) => processPlan(ctx, "run", "dart", ["run"], {
|
|
1513
|
+
parser: "command-text",
|
|
1514
|
+
reason: "Run the Dart / Flutter project entry point.",
|
|
1515
|
+
executesProjectCode: true
|
|
1516
|
+
})
|
|
1517
|
+
})
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
function denoProfile() {
|
|
1521
|
+
return {
|
|
1522
|
+
id: "deno",
|
|
1523
|
+
displayName: "Deno",
|
|
1524
|
+
extensions: Object.freeze([".ts", ".tsx", ".js", ".jsx"]),
|
|
1525
|
+
lspLanguageIds: Object.freeze(["typescript", "javascript"]),
|
|
1526
|
+
detectors: Object.freeze([
|
|
1527
|
+
{ kind: "config", filename: "deno.json", weight: 90 },
|
|
1528
|
+
{ kind: "config", filename: "deno.jsonc", weight: 90 },
|
|
1529
|
+
{ kind: "config", filename: "import_map.json", weight: 60 }
|
|
1530
|
+
]),
|
|
1531
|
+
ignoredDirectories: IGNORES,
|
|
1532
|
+
packageManagers: Object.freeze([]),
|
|
1533
|
+
executables: Object.freeze(["deno"]),
|
|
1534
|
+
// .ts/.js extensions are shared with the TypeScript/JavaScript profiles;
|
|
1535
|
+
// only a deno.json(c)/import_map.json detector hit may establish a workspace.
|
|
1536
|
+
sourceFallback: false,
|
|
1537
|
+
operations: Object.freeze({
|
|
1538
|
+
test: async (ctx) => processPlan(ctx, "test", "deno", ["test"], {
|
|
1539
|
+
parser: "deno-test",
|
|
1540
|
+
reason: "Run Deno tests.",
|
|
1541
|
+
mutating: true,
|
|
1542
|
+
executesProjectCode: true
|
|
1543
|
+
}),
|
|
1544
|
+
run: async (ctx) => processPlan(ctx, "run", "deno", ["run", "--allow-all", "main.ts"], {
|
|
1545
|
+
parser: "command-text",
|
|
1546
|
+
reason: "Run the Deno project entry point with full permission.",
|
|
1547
|
+
executesProjectCode: true,
|
|
1548
|
+
mutating: true
|
|
1549
|
+
})
|
|
1550
|
+
})
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
function elixirProfile() {
|
|
1554
|
+
return {
|
|
1555
|
+
id: "elixir",
|
|
1556
|
+
displayName: "Elixir",
|
|
1557
|
+
extensions: Object.freeze([".ex", ".exs"]),
|
|
1558
|
+
lspLanguageIds: Object.freeze(["elixir"]),
|
|
1559
|
+
detectors: Object.freeze([
|
|
1560
|
+
{ kind: "manifest", filename: "mix.exs", weight: 90 },
|
|
1561
|
+
{ kind: "lockfile", filename: "mix.lock", weight: 30 }
|
|
1562
|
+
]),
|
|
1563
|
+
ignoredDirectories: IGNORES,
|
|
1564
|
+
packageManagers: Object.freeze(["mix"]),
|
|
1565
|
+
executables: Object.freeze(["mix", "elixir"]),
|
|
1566
|
+
operations: Object.freeze({
|
|
1567
|
+
semantic: async (ctx) => unavailable(ctx, "semantic", "Elixir compilation is handled by the build operation."),
|
|
1568
|
+
lint: async (ctx) => unavailable(ctx, "lint", "Elixir linting requires the credo package."),
|
|
1569
|
+
test: async (ctx) => processPlan(ctx, "test", "mix", ["test"], {
|
|
1570
|
+
parser: "mix-test",
|
|
1571
|
+
reason: "Run ExUnit tests.",
|
|
1572
|
+
mutating: true,
|
|
1573
|
+
executesProjectCode: true
|
|
1574
|
+
}),
|
|
1575
|
+
build: async (ctx) => processPlan(ctx, "build", "mix", ["compile"], {
|
|
1576
|
+
parser: "mix",
|
|
1577
|
+
reason: "Compile the Mix project.",
|
|
1578
|
+
mutating: true,
|
|
1579
|
+
executesProjectCode: true
|
|
1580
|
+
}),
|
|
1581
|
+
run: async (ctx) => processPlan(ctx, "run", "mix", ["run"], {
|
|
1582
|
+
parser: "command-text",
|
|
1583
|
+
reason: "Run the Mix project entry point.",
|
|
1584
|
+
executesProjectCode: true
|
|
1585
|
+
}),
|
|
1586
|
+
"package-install": async (ctx) => processPlan(ctx, "package-install", "mix", ["deps.get"], {
|
|
1587
|
+
parser: "package-text",
|
|
1588
|
+
reason: "Fetch Elixir dependencies.",
|
|
1589
|
+
mutating: true,
|
|
1590
|
+
network: true
|
|
1591
|
+
})
|
|
1592
|
+
})
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function shellProfile() {
|
|
1596
|
+
return {
|
|
1597
|
+
id: "shell",
|
|
1598
|
+
displayName: "Shell",
|
|
1599
|
+
extensions: Object.freeze([".sh", ".bash"]),
|
|
1600
|
+
lspLanguageIds: Object.freeze(["shellscript"]),
|
|
1601
|
+
detectors: Object.freeze([{ kind: "config", filename: "ShellCheckrc", weight: 50 }]),
|
|
1602
|
+
ignoredDirectories: IGNORES,
|
|
1603
|
+
packageManagers: Object.freeze([]),
|
|
1604
|
+
executables: Object.freeze(["bash", "sh", "shellcheck", "shfmt"]),
|
|
1605
|
+
operations: Object.freeze({
|
|
1606
|
+
syntax: async (ctx) => ctx.options.target ? processPlan(ctx, "syntax", "bash", ["-n", ctx.options.target], {
|
|
1607
|
+
parser: "shell",
|
|
1608
|
+
reason: "Check the shell script for syntax errors."
|
|
1609
|
+
}) : unavailable(ctx, "syntax", "Shell syntax check requires an explicit target file."),
|
|
1610
|
+
lint: async (ctx) => ctx.options.target ? processPlan(ctx, "lint", "shellcheck", ["--format=json", ctx.options.target], {
|
|
1611
|
+
parser: "shellcheck",
|
|
1612
|
+
reason: "Run ShellCheck on the target script."
|
|
1613
|
+
}) : unavailable(ctx, "lint", "Shell linting requires an explicit target file."),
|
|
1614
|
+
"format-check": async (ctx) => ctx.options.target ? processPlan(ctx, "format-check", "shfmt", ["-d", ctx.options.target], {
|
|
1615
|
+
parser: "shell",
|
|
1616
|
+
reason: "Check shell formatting without writing."
|
|
1617
|
+
}) : unavailable(
|
|
1618
|
+
ctx,
|
|
1619
|
+
"format-check",
|
|
1620
|
+
"Shell formatting check requires an explicit target file."
|
|
1621
|
+
),
|
|
1622
|
+
"format-write": async (ctx) => ctx.options.target ? processPlan(ctx, "format-write", "shfmt", ["-w", ctx.options.target], {
|
|
1623
|
+
parser: "shell",
|
|
1624
|
+
reason: "Format the shell script.",
|
|
1625
|
+
mutating: true
|
|
1626
|
+
}) : unavailable(ctx, "format-write", "Shell formatting requires an explicit target file.")
|
|
1627
|
+
})
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
var ADDITIONAL_LANGUAGE_PROFILES = Object.freeze([
|
|
1631
|
+
pythonProfile(),
|
|
1632
|
+
javaProfile(),
|
|
1633
|
+
rubyProfile(),
|
|
1634
|
+
cProfile(),
|
|
1635
|
+
cppProfile(),
|
|
1636
|
+
swiftProfile(),
|
|
1637
|
+
dartProfile(),
|
|
1638
|
+
denoProfile(),
|
|
1639
|
+
elixirProfile(),
|
|
1640
|
+
shellProfile()
|
|
1641
|
+
]);
|
|
1642
|
+
|
|
1643
|
+
// src/languages/profiles/primary.ts
|
|
1644
|
+
var COMMON_IGNORES = Object.freeze([
|
|
1645
|
+
".git",
|
|
1646
|
+
".wrongstack",
|
|
1647
|
+
"node_modules",
|
|
1648
|
+
"vendor",
|
|
1649
|
+
"target",
|
|
1650
|
+
"bin",
|
|
1651
|
+
"obj",
|
|
1652
|
+
"dist",
|
|
1653
|
+
"build",
|
|
1654
|
+
"coverage"
|
|
1655
|
+
]);
|
|
1656
|
+
function nodeManager(ctx) {
|
|
1657
|
+
const lockfiles = new Set(
|
|
1658
|
+
ctx.workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.value.toLowerCase())
|
|
1659
|
+
);
|
|
1660
|
+
if (lockfiles.size > 1 && !ctx.workspace.packageManager) return void 0;
|
|
1661
|
+
return ctx.workspace.packageManager ?? "npm";
|
|
1662
|
+
}
|
|
1663
|
+
function scriptPlan(ctx, operation, script) {
|
|
1664
|
+
const manager = nodeManager(ctx);
|
|
1665
|
+
if (!manager)
|
|
1666
|
+
return unavailable(
|
|
1667
|
+
ctx,
|
|
1668
|
+
operation,
|
|
1669
|
+
"Conflicting Node lockfiles make the package manager ambiguous."
|
|
1670
|
+
);
|
|
1671
|
+
const args = manager === "npm" ? ["run", script] : [script];
|
|
1672
|
+
return processPlan(ctx, operation, manager, args, {
|
|
1673
|
+
parser: "command-text",
|
|
1674
|
+
reason: `Run the detected ${manager} ${script} script for this workspace.`,
|
|
1675
|
+
mutating: true,
|
|
1676
|
+
executesProjectCode: true
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
function nodeExec(ctx, executable, args) {
|
|
1680
|
+
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1681
|
+
if (manager === "pnpm") return { command: "pnpm", args: ["exec", executable, ...args] };
|
|
1682
|
+
if (manager === "yarn") return { command: "yarn", args: ["exec", executable, ...args] };
|
|
1683
|
+
if (manager === "bun") return { command: "bun", args: ["x", executable, ...args] };
|
|
1684
|
+
return { command: "npx", args: ["--no-install", executable, ...args] };
|
|
1685
|
+
}
|
|
1686
|
+
function typescriptProfile() {
|
|
1687
|
+
return {
|
|
1688
|
+
id: "typescript",
|
|
1689
|
+
displayName: "TypeScript",
|
|
1690
|
+
extensions: Object.freeze([".ts", ".tsx", ".mts", ".cts"]),
|
|
1691
|
+
lspLanguageIds: Object.freeze(["typescript", "typescriptreact"]),
|
|
1692
|
+
detectors: Object.freeze([
|
|
1693
|
+
{ kind: "config", filename: "tsconfig.json", weight: 90 },
|
|
1694
|
+
{ kind: "manifest", filename: "package.json", weight: 55 },
|
|
1695
|
+
{ kind: "lockfile", filename: "pnpm-lock.yaml", weight: 30 },
|
|
1696
|
+
{ kind: "lockfile", filename: "yarn.lock", weight: 30 },
|
|
1697
|
+
{ kind: "lockfile", filename: "package-lock.json", weight: 30 },
|
|
1698
|
+
{ kind: "lockfile", filename: "bun.lock", weight: 30 },
|
|
1699
|
+
{ kind: "lockfile", filename: "bun.lockb", weight: 30 }
|
|
1700
|
+
]),
|
|
1701
|
+
ignoredDirectories: COMMON_IGNORES,
|
|
1702
|
+
packageManagers: Object.freeze(["pnpm", "yarn", "bun", "npm"]),
|
|
1703
|
+
executables: Object.freeze(["pnpm", "yarn", "bun", "npx", "npm"]),
|
|
1704
|
+
operations: Object.freeze({
|
|
1705
|
+
syntax: async (ctx) => internalPlan(
|
|
1706
|
+
ctx,
|
|
1707
|
+
"syntax",
|
|
1708
|
+
"typescript-parser",
|
|
1709
|
+
"Parse the target with the TypeScript compiler API."
|
|
1710
|
+
),
|
|
1711
|
+
semantic: async (ctx) => {
|
|
1712
|
+
const run = nodeExec(ctx, "tsc", ["--noEmit", "--pretty", "false"]);
|
|
1713
|
+
return processPlan(ctx, "semantic", run.command, run.args, {
|
|
1714
|
+
parser: "typescript",
|
|
1715
|
+
reason: "Run the workspace TypeScript compiler without emitting files.",
|
|
1716
|
+
executesProjectCode: true
|
|
1717
|
+
});
|
|
1718
|
+
},
|
|
1719
|
+
lint: async (ctx) => {
|
|
1720
|
+
const run = nodeExec(ctx, "biome", ["lint", "."]);
|
|
1721
|
+
return processPlan(ctx, "lint", run.command, run.args, {
|
|
1722
|
+
parser: "biome",
|
|
1723
|
+
reason: "Run the project-local Biome linter.",
|
|
1724
|
+
executesProjectCode: true
|
|
1725
|
+
});
|
|
1726
|
+
},
|
|
1727
|
+
"format-check": async (ctx) => {
|
|
1728
|
+
const run = nodeExec(ctx, "biome", ["format", "--check", "."]);
|
|
1729
|
+
return processPlan(ctx, "format-check", run.command, run.args, {
|
|
1730
|
+
parser: "biome",
|
|
1731
|
+
reason: "Check formatting with the project-local Biome formatter.",
|
|
1732
|
+
executesProjectCode: true
|
|
1733
|
+
});
|
|
1734
|
+
},
|
|
1735
|
+
"format-write": async (ctx) => {
|
|
1736
|
+
const run = nodeExec(ctx, "biome", ["format", "--write", "."]);
|
|
1737
|
+
return processPlan(ctx, "format-write", run.command, run.args, {
|
|
1738
|
+
parser: "biome",
|
|
1739
|
+
reason: "Format the workspace with the project-local Biome formatter.",
|
|
1740
|
+
mutating: true,
|
|
1741
|
+
executesProjectCode: true
|
|
1742
|
+
});
|
|
1743
|
+
},
|
|
1744
|
+
test: async (ctx) => ctx.options.filter || ctx.options.coverage ? unavailable(
|
|
1745
|
+
ctx,
|
|
1746
|
+
"test",
|
|
1747
|
+
"The detected package script does not expose deterministic filter or coverage adapters."
|
|
1748
|
+
) : scriptPlan(ctx, "test", "test"),
|
|
1749
|
+
build: async (ctx) => scriptPlan(ctx, "build", "build"),
|
|
1750
|
+
run: async (ctx) => scriptPlan(ctx, "run", "dev"),
|
|
1751
|
+
"debug-compile": async (ctx) => {
|
|
1752
|
+
const run = nodeExec(ctx, "tsc", ["--noEmit", "--pretty", "false"]);
|
|
1753
|
+
return processPlan(ctx, "debug-compile", run.command, run.args, {
|
|
1754
|
+
parser: "typescript",
|
|
1755
|
+
reason: "Collect deterministic TypeScript compiler diagnostics.",
|
|
1756
|
+
executesProjectCode: true
|
|
1757
|
+
});
|
|
1758
|
+
},
|
|
1759
|
+
"package-install": async (ctx) => {
|
|
1760
|
+
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1761
|
+
const args = manager === "yarn" ? ["install", "--ignore-scripts"] : ["install", "--ignore-scripts"];
|
|
1762
|
+
return processPlan(ctx, "package-install", manager, args, {
|
|
1763
|
+
parser: "package-text",
|
|
1764
|
+
reason: `Restore declared dependencies with ${manager} and lifecycle scripts disabled.`,
|
|
1765
|
+
mutating: true,
|
|
1766
|
+
network: true,
|
|
1767
|
+
executesProjectCode: false
|
|
1768
|
+
});
|
|
1769
|
+
},
|
|
1770
|
+
"package-add": async (ctx) => {
|
|
1771
|
+
const names = packageNames(ctx);
|
|
1772
|
+
if (names.length === 0)
|
|
1773
|
+
return unavailable(ctx, "package-add", "At least one package name is required.");
|
|
1774
|
+
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1775
|
+
const args = manager === "npm" ? ["install", "--ignore-scripts", ...names] : ["add", "--ignore-scripts", ...names];
|
|
1776
|
+
return processPlan(ctx, "package-add", manager, args, {
|
|
1777
|
+
parser: "package-text",
|
|
1778
|
+
reason: `Add validated packages with ${manager} and lifecycle scripts disabled.`,
|
|
1779
|
+
mutating: true,
|
|
1780
|
+
network: true
|
|
1781
|
+
});
|
|
1782
|
+
},
|
|
1783
|
+
"package-audit": async (ctx) => {
|
|
1784
|
+
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1785
|
+
return processPlan(ctx, "package-audit", manager, ["audit", "--json"], {
|
|
1786
|
+
parser: "npm-audit",
|
|
1787
|
+
reason: `Audit dependencies with the detected ${manager} package manager.`,
|
|
1788
|
+
network: true
|
|
1789
|
+
});
|
|
1790
|
+
},
|
|
1791
|
+
"package-outdated": async (ctx) => {
|
|
1792
|
+
const manager = ctx.workspace.packageManager ?? "npm";
|
|
1793
|
+
return processPlan(ctx, "package-outdated", manager, ["outdated", "--json"], {
|
|
1794
|
+
parser: "npm-outdated",
|
|
1795
|
+
reason: `Check outdated dependencies with ${manager}.`,
|
|
1796
|
+
network: true
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
})
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
function javascriptProfile() {
|
|
1803
|
+
const ts = typescriptProfile();
|
|
1804
|
+
return {
|
|
1805
|
+
...ts,
|
|
1806
|
+
id: "javascript",
|
|
1807
|
+
displayName: "JavaScript",
|
|
1808
|
+
extensions: Object.freeze([".js", ".jsx", ".mjs", ".cjs"]),
|
|
1809
|
+
lspLanguageIds: Object.freeze(["javascript", "javascriptreact"]),
|
|
1810
|
+
detectors: Object.freeze([
|
|
1811
|
+
{ kind: "config", filename: "jsconfig.json", weight: 90 },
|
|
1812
|
+
{ kind: "manifest", filename: "package.json", weight: 70 },
|
|
1813
|
+
{ kind: "lockfile", filename: "pnpm-lock.yaml", weight: 30 },
|
|
1814
|
+
{ kind: "lockfile", filename: "yarn.lock", weight: 30 },
|
|
1815
|
+
{ kind: "lockfile", filename: "package-lock.json", weight: 30 },
|
|
1816
|
+
{ kind: "lockfile", filename: "bun.lock", weight: 30 },
|
|
1817
|
+
{ kind: "lockfile", filename: "bun.lockb", weight: 30 }
|
|
1818
|
+
]),
|
|
1819
|
+
operations: Object.freeze({
|
|
1820
|
+
...ts.operations,
|
|
1821
|
+
syntax: async (ctx) => internalPlan(
|
|
1822
|
+
ctx,
|
|
1823
|
+
"syntax",
|
|
1824
|
+
"typescript-parser",
|
|
1825
|
+
"Parse JavaScript with the TypeScript compiler API."
|
|
1826
|
+
)
|
|
1827
|
+
})
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
var goProfile = {
|
|
1831
|
+
id: "go",
|
|
1832
|
+
displayName: "Go",
|
|
1833
|
+
extensions: Object.freeze([".go"]),
|
|
1834
|
+
lspLanguageIds: Object.freeze(["go"]),
|
|
1835
|
+
detectors: Object.freeze([
|
|
1836
|
+
{ kind: "manifest", filename: "go.mod", weight: 90 },
|
|
1837
|
+
{ kind: "manifest", filename: "go.work", weight: 95 },
|
|
1838
|
+
{ kind: "lockfile", filename: "go.sum", weight: 30 }
|
|
1839
|
+
]),
|
|
1840
|
+
ignoredDirectories: COMMON_IGNORES,
|
|
1841
|
+
packageManagers: Object.freeze(["go"]),
|
|
1842
|
+
executables: Object.freeze(["go", "gofmt"]),
|
|
1843
|
+
operations: Object.freeze({
|
|
1844
|
+
syntax: async (ctx) => {
|
|
1845
|
+
if (!ctx.target)
|
|
1846
|
+
return unavailable(ctx, "syntax", "Go syntax planning requires a target file.");
|
|
1847
|
+
return processPlan(ctx, "syntax", "gofmt", ["-e", "-d", ctx.target], {
|
|
1848
|
+
parser: "gofmt",
|
|
1849
|
+
reason: "Parse the target and report syntax errors without writing it."
|
|
1850
|
+
});
|
|
1851
|
+
},
|
|
1852
|
+
semantic: async (ctx) => processPlan(ctx, "semantic", "go", ["test", "-run", "^$", "./..."], {
|
|
1853
|
+
parser: "go-test",
|
|
1854
|
+
reason: "Compile all Go packages without selecting tests.",
|
|
1855
|
+
mutating: true,
|
|
1856
|
+
executesProjectCode: true
|
|
1857
|
+
}),
|
|
1858
|
+
lint: async (ctx) => processPlan(ctx, "lint", "go", ["vet", "./..."], {
|
|
1859
|
+
parser: "go-compiler",
|
|
1860
|
+
reason: "Run the standard Go vet checks.",
|
|
1861
|
+
mutating: true,
|
|
1862
|
+
executesProjectCode: true
|
|
1863
|
+
}),
|
|
1864
|
+
"format-check": async (ctx) => ctx.target ? processPlan(ctx, "format-check", "gofmt", ["-d", ctx.target], {
|
|
1865
|
+
parser: "gofmt",
|
|
1866
|
+
reason: "Report Go formatting differences for the target without writing it."
|
|
1867
|
+
}) : unavailable(ctx, "format-check", "Go formatting requires an explicit target file."),
|
|
1868
|
+
"format-write": async (ctx) => ctx.target ? processPlan(ctx, "format-write", "gofmt", ["-w", ctx.target], {
|
|
1869
|
+
parser: "gofmt",
|
|
1870
|
+
reason: "Format the target Go source file.",
|
|
1871
|
+
mutating: true
|
|
1872
|
+
}) : unavailable(ctx, "format-write", "Go formatting requires an explicit target file."),
|
|
1873
|
+
test: async (ctx) => processPlan(
|
|
1874
|
+
ctx,
|
|
1875
|
+
"test",
|
|
1876
|
+
"go",
|
|
1877
|
+
["test", ...ctx.options.filter ? ["-run", ctx.options.filter] : [], "./..."],
|
|
1878
|
+
{
|
|
1879
|
+
parser: "go-test",
|
|
1880
|
+
reason: ctx.options.filter ? "Run filtered Go tests." : "Run all Go tests.",
|
|
1881
|
+
mutating: true,
|
|
1882
|
+
executesProjectCode: true
|
|
1883
|
+
}
|
|
1884
|
+
),
|
|
1885
|
+
build: async (ctx) => processPlan(ctx, "build", "go", ["build", "./..."], {
|
|
1886
|
+
parser: "go-compiler",
|
|
1887
|
+
reason: "Build all Go packages.",
|
|
1888
|
+
mutating: true,
|
|
1889
|
+
executesProjectCode: true
|
|
1890
|
+
}),
|
|
1891
|
+
run: async (ctx) => processPlan(ctx, "run", "go", ["run", "."], {
|
|
1892
|
+
parser: "command-text",
|
|
1893
|
+
reason: "Run the Go module entry point.",
|
|
1894
|
+
executesProjectCode: true
|
|
1895
|
+
}),
|
|
1896
|
+
"debug-race": async (ctx) => processPlan(ctx, "debug-race", "go", ["test", "-race", "./..."], {
|
|
1897
|
+
parser: "go-test",
|
|
1898
|
+
reason: "Collect Go race-detector evidence.",
|
|
1899
|
+
mutating: true,
|
|
1900
|
+
executesProjectCode: true
|
|
1901
|
+
}),
|
|
1902
|
+
"package-install": async (ctx) => processPlan(ctx, "package-install", "go", ["mod", "download"], {
|
|
1903
|
+
parser: "go-module",
|
|
1904
|
+
reason: "Download the dependencies declared by go.mod.",
|
|
1905
|
+
mutating: true,
|
|
1906
|
+
network: true
|
|
1907
|
+
}),
|
|
1908
|
+
"package-add": async (ctx) => {
|
|
1909
|
+
const names = packageNames(ctx);
|
|
1910
|
+
if (names.length === 0)
|
|
1911
|
+
return unavailable(ctx, "package-add", "At least one Go module is required.");
|
|
1912
|
+
return processPlan(ctx, "package-add", "go", ["get", ...names], {
|
|
1913
|
+
parser: "go-module",
|
|
1914
|
+
reason: "Add validated Go modules.",
|
|
1915
|
+
mutating: true,
|
|
1916
|
+
network: true
|
|
1917
|
+
});
|
|
1918
|
+
},
|
|
1919
|
+
"package-update": async (ctx) => processPlan(ctx, "package-update", "go", ["get", "-u", "./..."], {
|
|
1920
|
+
parser: "go-module",
|
|
1921
|
+
reason: "Update dependencies of all Go packages.",
|
|
1922
|
+
mutating: true,
|
|
1923
|
+
network: true
|
|
1924
|
+
})
|
|
1925
|
+
})
|
|
1926
|
+
};
|
|
1927
|
+
var rustProfile = {
|
|
1928
|
+
id: "rust",
|
|
1929
|
+
displayName: "Rust",
|
|
1930
|
+
extensions: Object.freeze([".rs"]),
|
|
1931
|
+
lspLanguageIds: Object.freeze(["rust"]),
|
|
1932
|
+
detectors: Object.freeze([
|
|
1933
|
+
{ kind: "manifest", filename: "Cargo.toml", weight: 90 },
|
|
1934
|
+
{ kind: "lockfile", filename: "Cargo.lock", weight: 30 }
|
|
1935
|
+
]),
|
|
1936
|
+
ignoredDirectories: COMMON_IGNORES,
|
|
1937
|
+
packageManagers: Object.freeze(["cargo"]),
|
|
1938
|
+
executables: Object.freeze(["cargo"]),
|
|
1939
|
+
operations: Object.freeze({
|
|
1940
|
+
syntax: async (ctx) => processPlan(ctx, "syntax", "cargo", ["check", "--message-format=json"], {
|
|
1941
|
+
parser: "cargo-json",
|
|
1942
|
+
reason: "Check Rust syntax and semantics with Cargo.",
|
|
1943
|
+
mutating: true,
|
|
1944
|
+
executesProjectCode: true
|
|
1945
|
+
}),
|
|
1946
|
+
semantic: async (ctx) => processPlan(ctx, "semantic", "cargo", ["check", "--message-format=json"], {
|
|
1947
|
+
parser: "cargo-json",
|
|
1948
|
+
reason: "Collect Rust compiler diagnostics with Cargo check.",
|
|
1949
|
+
mutating: true,
|
|
1950
|
+
executesProjectCode: true
|
|
1951
|
+
}),
|
|
1952
|
+
lint: async (ctx) => processPlan(ctx, "lint", "cargo", ["clippy", "--message-format=json"], {
|
|
1953
|
+
parser: "cargo-json",
|
|
1954
|
+
reason: "Run Clippy for this crate.",
|
|
1955
|
+
mutating: true,
|
|
1956
|
+
executesProjectCode: true
|
|
1957
|
+
}),
|
|
1958
|
+
"format-check": async (ctx) => processPlan(ctx, "format-check", "cargo", ["fmt", "--check"], {
|
|
1959
|
+
parser: "cargo-fmt",
|
|
1960
|
+
reason: "Check Rust formatting without writing files."
|
|
1961
|
+
}),
|
|
1962
|
+
"format-write": async (ctx) => processPlan(ctx, "format-write", "cargo", ["fmt"], {
|
|
1963
|
+
parser: "cargo-fmt",
|
|
1964
|
+
reason: "Format Rust source files in the workspace.",
|
|
1965
|
+
mutating: true
|
|
1966
|
+
}),
|
|
1967
|
+
"test-compile": async (ctx) => processPlan(ctx, "test-compile", "cargo", ["test", "--no-run", "--message-format=json"], {
|
|
1968
|
+
parser: "cargo-json",
|
|
1969
|
+
reason: "Compile Rust tests without running them.",
|
|
1970
|
+
mutating: true,
|
|
1971
|
+
executesProjectCode: true
|
|
1972
|
+
}),
|
|
1973
|
+
test: async (ctx) => processPlan(
|
|
1974
|
+
ctx,
|
|
1975
|
+
"test",
|
|
1976
|
+
"cargo",
|
|
1977
|
+
["test", ...ctx.options.filter ? [ctx.options.filter] : []],
|
|
1978
|
+
{
|
|
1979
|
+
parser: "cargo-test",
|
|
1980
|
+
reason: ctx.options.filter ? "Run filtered Rust tests." : "Run Rust tests.",
|
|
1981
|
+
mutating: true,
|
|
1982
|
+
executesProjectCode: true
|
|
1983
|
+
}
|
|
1984
|
+
),
|
|
1985
|
+
build: async (ctx) => processPlan(ctx, "build", "cargo", ["build", "--message-format=json"], {
|
|
1986
|
+
parser: "cargo-json",
|
|
1987
|
+
reason: "Build the Rust workspace.",
|
|
1988
|
+
mutating: true,
|
|
1989
|
+
executesProjectCode: true
|
|
1990
|
+
}),
|
|
1991
|
+
run: async (ctx) => processPlan(ctx, "run", "cargo", ["run"], {
|
|
1992
|
+
parser: "command-text",
|
|
1993
|
+
reason: "Run the Rust workspace entry point.",
|
|
1994
|
+
executesProjectCode: true
|
|
1995
|
+
}),
|
|
1996
|
+
"package-install": async (ctx) => processPlan(ctx, "package-install", "cargo", ["fetch", "--locked"], {
|
|
1997
|
+
parser: "cargo-json",
|
|
1998
|
+
reason: "Fetch locked Rust dependencies.",
|
|
1999
|
+
mutating: true,
|
|
2000
|
+
network: true
|
|
2001
|
+
}),
|
|
2002
|
+
"package-add": async (ctx) => {
|
|
2003
|
+
const names = packageNames(ctx);
|
|
2004
|
+
return names.length === 0 ? unavailable(ctx, "package-add", "At least one crate is required.") : processPlan(ctx, "package-add", "cargo", ["add", ...names], {
|
|
2005
|
+
parser: "cargo-text",
|
|
2006
|
+
reason: "Add validated Rust crates.",
|
|
2007
|
+
mutating: true,
|
|
2008
|
+
network: true
|
|
2009
|
+
});
|
|
2010
|
+
},
|
|
2011
|
+
"package-remove": async (ctx) => {
|
|
2012
|
+
const names = packageNames(ctx);
|
|
2013
|
+
return names.length === 0 ? unavailable(ctx, "package-remove", "At least one crate is required.") : processPlan(ctx, "package-remove", "cargo", ["remove", ...names], {
|
|
2014
|
+
parser: "cargo-text",
|
|
2015
|
+
reason: "Remove validated Rust crates.",
|
|
2016
|
+
mutating: true
|
|
2017
|
+
});
|
|
2018
|
+
},
|
|
2019
|
+
"package-update": async (ctx) => processPlan(ctx, "package-update", "cargo", ["update"], {
|
|
2020
|
+
parser: "cargo-text",
|
|
2021
|
+
reason: "Update the Cargo lockfile.",
|
|
2022
|
+
mutating: true,
|
|
2023
|
+
network: true
|
|
2024
|
+
}),
|
|
2025
|
+
"package-audit": async (ctx) => processPlan(ctx, "package-audit", "cargo", ["audit", "--json"], {
|
|
2026
|
+
parser: "cargo-audit",
|
|
2027
|
+
reason: "Audit Rust dependencies when cargo-audit is installed.",
|
|
2028
|
+
network: true
|
|
2029
|
+
})
|
|
2030
|
+
})
|
|
2031
|
+
};
|
|
2032
|
+
var phpProfile = {
|
|
2033
|
+
id: "php",
|
|
2034
|
+
displayName: "PHP",
|
|
2035
|
+
extensions: Object.freeze([".php"]),
|
|
2036
|
+
lspLanguageIds: Object.freeze(["php"]),
|
|
2037
|
+
detectors: Object.freeze([
|
|
2038
|
+
{ kind: "manifest", filename: "composer.json", weight: 90 },
|
|
2039
|
+
{ kind: "lockfile", filename: "composer.lock", weight: 30 },
|
|
2040
|
+
{ kind: "config", filename: "phpunit.xml", weight: 25 },
|
|
2041
|
+
{ kind: "config", filename: "phpunit.xml.dist", weight: 25 }
|
|
2042
|
+
]),
|
|
2043
|
+
ignoredDirectories: COMMON_IGNORES,
|
|
2044
|
+
packageManagers: Object.freeze(["composer"]),
|
|
2045
|
+
executables: Object.freeze(["php", "composer"]),
|
|
2046
|
+
operations: Object.freeze({
|
|
2047
|
+
syntax: async (ctx) => ctx.target ? processPlan(ctx, "syntax", "php", ["-l", ctx.target], {
|
|
2048
|
+
parser: "php-lint",
|
|
2049
|
+
reason: "Lint the target PHP file without executing it."
|
|
2050
|
+
}) : unavailable(ctx, "syntax", "PHP syntax planning requires a target file."),
|
|
2051
|
+
semantic: async (ctx) => unavailable(
|
|
2052
|
+
ctx,
|
|
2053
|
+
"semantic",
|
|
2054
|
+
"No configured PHPStan or Psalm adapter was detected in Phase 1."
|
|
2055
|
+
),
|
|
2056
|
+
test: async (ctx) => processPlan(
|
|
2057
|
+
ctx,
|
|
2058
|
+
"test",
|
|
2059
|
+
"php",
|
|
2060
|
+
["vendor/bin/phpunit", ...ctx.options.filter ? ["--filter", ctx.options.filter] : []],
|
|
2061
|
+
{
|
|
2062
|
+
parser: "phpunit",
|
|
2063
|
+
reason: ctx.options.filter ? "Run filtered tests with the project-local PHPUnit runner." : "Run the project-local PHPUnit test runner.",
|
|
2064
|
+
executesProjectCode: true
|
|
2065
|
+
}
|
|
2066
|
+
),
|
|
2067
|
+
"package-install": async (ctx) => processPlan(
|
|
2068
|
+
ctx,
|
|
2069
|
+
"package-install",
|
|
2070
|
+
"composer",
|
|
2071
|
+
["install", "--no-interaction", "--no-scripts"],
|
|
2072
|
+
{
|
|
2073
|
+
parser: "composer",
|
|
2074
|
+
reason: "Restore Composer dependencies without scripts.",
|
|
2075
|
+
mutating: true,
|
|
2076
|
+
network: true
|
|
2077
|
+
}
|
|
2078
|
+
),
|
|
2079
|
+
"package-add": async (ctx) => {
|
|
2080
|
+
const names = packageNames(ctx);
|
|
2081
|
+
return names.length === 0 ? unavailable(ctx, "package-add", "At least one Composer package is required.") : processPlan(
|
|
2082
|
+
ctx,
|
|
2083
|
+
"package-add",
|
|
2084
|
+
"composer",
|
|
2085
|
+
["require", "--no-interaction", "--no-scripts", ...names],
|
|
2086
|
+
{
|
|
2087
|
+
parser: "composer",
|
|
2088
|
+
reason: "Add validated Composer packages without scripts.",
|
|
2089
|
+
mutating: true,
|
|
2090
|
+
network: true
|
|
2091
|
+
}
|
|
2092
|
+
);
|
|
2093
|
+
},
|
|
2094
|
+
"package-remove": async (ctx) => {
|
|
2095
|
+
const names = packageNames(ctx);
|
|
2096
|
+
return names.length === 0 ? unavailable(ctx, "package-remove", "At least one Composer package is required.") : processPlan(
|
|
2097
|
+
ctx,
|
|
2098
|
+
"package-remove",
|
|
2099
|
+
"composer",
|
|
2100
|
+
["remove", "--no-interaction", "--no-scripts", ...names],
|
|
2101
|
+
{
|
|
2102
|
+
parser: "composer",
|
|
2103
|
+
reason: "Remove validated Composer packages without scripts.",
|
|
2104
|
+
mutating: true
|
|
2105
|
+
}
|
|
2106
|
+
);
|
|
2107
|
+
},
|
|
2108
|
+
"package-update": async (ctx) => processPlan(
|
|
2109
|
+
ctx,
|
|
2110
|
+
"package-update",
|
|
2111
|
+
"composer",
|
|
2112
|
+
["update", "--no-interaction", "--no-scripts"],
|
|
2113
|
+
{
|
|
2114
|
+
parser: "composer",
|
|
2115
|
+
reason: "Update Composer dependencies without scripts.",
|
|
2116
|
+
mutating: true,
|
|
2117
|
+
network: true
|
|
2118
|
+
}
|
|
2119
|
+
),
|
|
2120
|
+
"package-audit": async (ctx) => processPlan(ctx, "package-audit", "composer", ["audit", "--format=json"], {
|
|
2121
|
+
parser: "composer-audit",
|
|
2122
|
+
reason: "Audit Composer dependencies.",
|
|
2123
|
+
network: true
|
|
2124
|
+
}),
|
|
2125
|
+
"package-outdated": async (ctx) => processPlan(ctx, "package-outdated", "composer", ["outdated", "--format=json"], {
|
|
2126
|
+
parser: "composer-outdated",
|
|
2127
|
+
reason: "Check outdated Composer dependencies.",
|
|
2128
|
+
network: true
|
|
2129
|
+
})
|
|
2130
|
+
})
|
|
2131
|
+
};
|
|
2132
|
+
var csharpProfile = {
|
|
2133
|
+
id: "csharp",
|
|
2134
|
+
displayName: "C# / .NET",
|
|
2135
|
+
extensions: Object.freeze([".cs"]),
|
|
2136
|
+
lspLanguageIds: Object.freeze(["csharp"]),
|
|
2137
|
+
detectors: Object.freeze([
|
|
2138
|
+
{ kind: "config", filename: "global.json", weight: 25 },
|
|
2139
|
+
{ kind: "manifest", suffix: ".slnx", weight: 95 },
|
|
2140
|
+
{ kind: "manifest", suffix: ".sln", weight: 95 },
|
|
2141
|
+
{ kind: "manifest", suffix: ".csproj", weight: 90 },
|
|
2142
|
+
{ kind: "manifest", suffix: ".fsproj", weight: 90 },
|
|
2143
|
+
{ kind: "lockfile", filename: "packages.lock.json", weight: 30 }
|
|
2144
|
+
]),
|
|
2145
|
+
ignoredDirectories: COMMON_IGNORES,
|
|
2146
|
+
packageManagers: Object.freeze(["dotnet"]),
|
|
2147
|
+
executables: Object.freeze(["dotnet"]),
|
|
2148
|
+
operations: Object.freeze({
|
|
2149
|
+
syntax: async (ctx) => processPlan(ctx, "syntax", "dotnet", ["build", "--no-restore"], {
|
|
2150
|
+
parser: "dotnet-build",
|
|
2151
|
+
reason: "Use the nearest project or solution to collect C# syntax diagnostics.",
|
|
2152
|
+
mutating: true,
|
|
2153
|
+
executesProjectCode: true
|
|
2154
|
+
}),
|
|
2155
|
+
semantic: async (ctx) => processPlan(ctx, "semantic", "dotnet", ["build", "--no-restore"], {
|
|
2156
|
+
parser: "dotnet-build",
|
|
2157
|
+
reason: "Build without restoring to collect .NET compiler diagnostics.",
|
|
2158
|
+
mutating: true,
|
|
2159
|
+
executesProjectCode: true
|
|
2160
|
+
}),
|
|
2161
|
+
lint: async (ctx) => processPlan(ctx, "lint", "dotnet", ["format", "--verify-no-changes", "--no-restore"], {
|
|
2162
|
+
parser: "dotnet-format",
|
|
2163
|
+
reason: "Verify .NET formatting and analyzers without writing source files.",
|
|
2164
|
+
mutating: true,
|
|
2165
|
+
executesProjectCode: true
|
|
2166
|
+
}),
|
|
2167
|
+
"format-check": async (ctx) => processPlan(
|
|
2168
|
+
ctx,
|
|
2169
|
+
"format-check",
|
|
2170
|
+
"dotnet",
|
|
2171
|
+
["format", "--verify-no-changes", "--no-restore"],
|
|
2172
|
+
{
|
|
2173
|
+
parser: "dotnet-format",
|
|
2174
|
+
reason: "Verify .NET formatting without source writes.",
|
|
2175
|
+
mutating: true,
|
|
2176
|
+
executesProjectCode: true
|
|
2177
|
+
}
|
|
2178
|
+
),
|
|
2179
|
+
"format-write": async (ctx) => processPlan(ctx, "format-write", "dotnet", ["format", "--no-restore"], {
|
|
2180
|
+
parser: "dotnet-format",
|
|
2181
|
+
reason: "Format .NET source files without restoring packages.",
|
|
2182
|
+
mutating: true,
|
|
2183
|
+
executesProjectCode: true
|
|
2184
|
+
}),
|
|
2185
|
+
test: async (ctx) => processPlan(
|
|
2186
|
+
ctx,
|
|
2187
|
+
"test",
|
|
2188
|
+
"dotnet",
|
|
2189
|
+
["test", "--no-restore", ...ctx.options.filter ? ["--filter", ctx.options.filter] : []],
|
|
2190
|
+
{
|
|
2191
|
+
parser: "dotnet-test",
|
|
2192
|
+
reason: ctx.options.filter ? "Run filtered .NET tests without restoring packages." : "Run .NET tests without restoring packages.",
|
|
2193
|
+
mutating: true,
|
|
2194
|
+
executesProjectCode: true
|
|
2195
|
+
}
|
|
2196
|
+
),
|
|
2197
|
+
build: async (ctx) => processPlan(ctx, "build", "dotnet", ["build", "--no-restore"], {
|
|
2198
|
+
parser: "dotnet-build",
|
|
2199
|
+
reason: "Build the nearest .NET project or solution without restoring packages.",
|
|
2200
|
+
mutating: true,
|
|
2201
|
+
executesProjectCode: true
|
|
2202
|
+
}),
|
|
2203
|
+
run: async (ctx) => processPlan(ctx, "run", "dotnet", ["run", "--no-restore"], {
|
|
2204
|
+
parser: "command-text",
|
|
2205
|
+
reason: "Run the .NET project entry point.",
|
|
2206
|
+
mutating: true,
|
|
2207
|
+
executesProjectCode: true
|
|
2208
|
+
}),
|
|
2209
|
+
"package-install": async (ctx) => processPlan(ctx, "package-install", "dotnet", ["restore", "--locked-mode"], {
|
|
2210
|
+
parser: "dotnet-restore",
|
|
2211
|
+
reason: "Restore locked .NET dependencies.",
|
|
2212
|
+
mutating: true,
|
|
2213
|
+
network: true,
|
|
2214
|
+
executesProjectCode: true
|
|
2215
|
+
}),
|
|
2216
|
+
"package-add": async (ctx) => {
|
|
2217
|
+
const names = packageNames(ctx);
|
|
2218
|
+
return names.length === 0 ? unavailable(ctx, "package-add", "At least one NuGet package is required.") : processPlan(ctx, "package-add", "dotnet", ["add", "package", ...names], {
|
|
2219
|
+
parser: "dotnet-package",
|
|
2220
|
+
reason: "Add validated NuGet packages.",
|
|
2221
|
+
mutating: true,
|
|
2222
|
+
network: true,
|
|
2223
|
+
executesProjectCode: true
|
|
2224
|
+
});
|
|
2225
|
+
},
|
|
2226
|
+
"package-remove": async (ctx) => {
|
|
2227
|
+
const names = packageNames(ctx);
|
|
2228
|
+
return names.length === 0 ? unavailable(ctx, "package-remove", "At least one NuGet package is required.") : processPlan(ctx, "package-remove", "dotnet", ["remove", "package", ...names], {
|
|
2229
|
+
parser: "dotnet-package",
|
|
2230
|
+
reason: "Remove validated NuGet packages.",
|
|
2231
|
+
mutating: true
|
|
2232
|
+
});
|
|
2233
|
+
},
|
|
2234
|
+
"package-audit": async (ctx) => processPlan(
|
|
2235
|
+
ctx,
|
|
2236
|
+
"package-audit",
|
|
2237
|
+
"dotnet",
|
|
2238
|
+
["list", "package", "--vulnerable", "--format", "json"],
|
|
2239
|
+
{ parser: "dotnet-package", reason: "List vulnerable NuGet dependencies.", network: true }
|
|
2240
|
+
),
|
|
2241
|
+
"package-outdated": async (ctx) => processPlan(
|
|
2242
|
+
ctx,
|
|
2243
|
+
"package-outdated",
|
|
2244
|
+
"dotnet",
|
|
2245
|
+
["list", "package", "--outdated", "--format", "json"],
|
|
2246
|
+
{ parser: "dotnet-package", reason: "List outdated NuGet dependencies.", network: true }
|
|
2247
|
+
)
|
|
2248
|
+
})
|
|
2249
|
+
};
|
|
2250
|
+
var PRIMARY_LANGUAGE_PROFILES = Object.freeze([
|
|
2251
|
+
Object.freeze(typescriptProfile()),
|
|
2252
|
+
Object.freeze(javascriptProfile()),
|
|
2253
|
+
Object.freeze(goProfile),
|
|
2254
|
+
Object.freeze(rustProfile),
|
|
2255
|
+
Object.freeze(phpProfile),
|
|
2256
|
+
Object.freeze(csharpProfile)
|
|
2257
|
+
]);
|
|
2258
|
+
|
|
2259
|
+
// src/languages/registry.ts
|
|
2260
|
+
var PROFILE_ID_RE = /^[a-z][a-z0-9-]{0,63}$/;
|
|
2261
|
+
var EXECUTABLE_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
|
|
2262
|
+
var KNOWN_OPERATIONS = /* @__PURE__ */ new Set([
|
|
2263
|
+
"syntax",
|
|
2264
|
+
"semantic",
|
|
2265
|
+
"lint",
|
|
2266
|
+
"format-check",
|
|
2267
|
+
"format-write",
|
|
2268
|
+
"test-compile",
|
|
2269
|
+
"test",
|
|
2270
|
+
"build",
|
|
2271
|
+
"run",
|
|
2272
|
+
"debug-compile",
|
|
2273
|
+
"debug-test",
|
|
2274
|
+
"debug-runtime",
|
|
2275
|
+
"debug-race",
|
|
2276
|
+
"package-install",
|
|
2277
|
+
"package-add",
|
|
2278
|
+
"package-remove",
|
|
2279
|
+
"package-update",
|
|
2280
|
+
"package-audit",
|
|
2281
|
+
"package-outdated"
|
|
2282
|
+
]);
|
|
2283
|
+
var LanguageProfileRegistry = class {
|
|
2284
|
+
#profiles = /* @__PURE__ */ new Map();
|
|
2285
|
+
constructor(profiles = []) {
|
|
2286
|
+
for (const profile of profiles) this.register(profile);
|
|
2287
|
+
}
|
|
2288
|
+
register(profile) {
|
|
2289
|
+
const errors = validateLanguageProfile(profile);
|
|
2290
|
+
if (errors.length > 0) {
|
|
2291
|
+
throw new Error(`Invalid language profile "${profile.id}": ${errors.join("; ")}`);
|
|
2292
|
+
}
|
|
2293
|
+
if (this.#profiles.has(profile.id)) {
|
|
2294
|
+
throw new Error(`Language profile "${profile.id}" is already registered`);
|
|
2295
|
+
}
|
|
2296
|
+
this.#profiles.set(profile.id, freezeProfile(profile));
|
|
2297
|
+
}
|
|
2298
|
+
get(id) {
|
|
2299
|
+
return this.#profiles.get(id);
|
|
2300
|
+
}
|
|
2301
|
+
list() {
|
|
2302
|
+
return Object.freeze([...this.#profiles.values()]);
|
|
2303
|
+
}
|
|
2304
|
+
};
|
|
2305
|
+
function validateLanguageProfile(profile) {
|
|
2306
|
+
const errors = [];
|
|
2307
|
+
if (!PROFILE_ID_RE.test(profile.id)) errors.push("id must be a lowercase stable identifier");
|
|
2308
|
+
if (!profile.displayName.trim()) errors.push("displayName is required");
|
|
2309
|
+
if (profile.extensions.length === 0) errors.push("at least one extension is required");
|
|
2310
|
+
for (const ext of profile.extensions) {
|
|
2311
|
+
if (!/^\.[a-z0-9+.-]+$/i.test(ext)) errors.push(`invalid extension "${ext}"`);
|
|
2312
|
+
}
|
|
2313
|
+
if (profile.detectors.length === 0) errors.push("at least one detector is required");
|
|
2314
|
+
for (const detector of profile.detectors) {
|
|
2315
|
+
if ((detector.filename ? 1 : 0) + (detector.suffix ? 1 : 0) !== 1) {
|
|
2316
|
+
errors.push("each detector must declare exactly one of filename or suffix");
|
|
2317
|
+
}
|
|
2318
|
+
if (!Number.isFinite(detector.weight) || detector.weight <= 0 || detector.weight > 100) {
|
|
2319
|
+
errors.push("detector weights must be in the range 1..100");
|
|
2320
|
+
}
|
|
2321
|
+
const marker = detector.filename ?? detector.suffix ?? "";
|
|
2322
|
+
if (marker.includes("/") || marker.includes("\\") || marker.includes("\0")) {
|
|
2323
|
+
errors.push(`detector marker "${marker}" must be a basename or suffix`);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (profile.executables.length === 0) errors.push("at least one executable is required");
|
|
2327
|
+
for (const executable of profile.executables) {
|
|
2328
|
+
if (!EXECUTABLE_RE.test(executable)) errors.push(`invalid executable token "${executable}"`);
|
|
2329
|
+
}
|
|
2330
|
+
for (const operation of Object.keys(profile.operations)) {
|
|
2331
|
+
if (!KNOWN_OPERATIONS.has(operation)) {
|
|
2332
|
+
errors.push(`unknown operation "${operation}"`);
|
|
2333
|
+
}
|
|
2334
|
+
if (typeof profile.operations[operation] !== "function") {
|
|
2335
|
+
errors.push(`operation "${operation}" must be a resolver function`);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return [...new Set(errors)];
|
|
2339
|
+
}
|
|
2340
|
+
function freezeProfile(profile) {
|
|
2341
|
+
const detectors = Object.freeze(profile.detectors.map((item) => Object.freeze({ ...item })));
|
|
2342
|
+
const operations = Object.freeze({ ...profile.operations });
|
|
2343
|
+
return Object.freeze({
|
|
2344
|
+
...profile,
|
|
2345
|
+
extensions: Object.freeze([...profile.extensions]),
|
|
2346
|
+
lspLanguageIds: Object.freeze([...profile.lspLanguageIds]),
|
|
2347
|
+
detectors,
|
|
2348
|
+
ignoredDirectories: Object.freeze([...profile.ignoredDirectories]),
|
|
2349
|
+
packageManagers: Object.freeze([...profile.packageManagers]),
|
|
2350
|
+
executables: Object.freeze([...profile.executables]),
|
|
2351
|
+
operations
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
var languageProfileRegistry = new LanguageProfileRegistry([
|
|
2355
|
+
...PRIMARY_LANGUAGE_PROFILES,
|
|
2356
|
+
...ADDITIONAL_LANGUAGE_PROFILES
|
|
2357
|
+
]);
|
|
2358
|
+
|
|
2359
|
+
// src/languages/detect.ts
|
|
2360
|
+
var DEFAULT_LIMITS = { maxDepth: 6, maxEntries: 5e3 };
|
|
2361
|
+
var SOURCE_WEIGHT = 5;
|
|
2362
|
+
var SOURCE_WEIGHT_CAP = 25;
|
|
2363
|
+
var TARGET_WEIGHT = 100;
|
|
2364
|
+
var GLOBAL_IGNORES = /* @__PURE__ */ new Set([
|
|
2365
|
+
".git",
|
|
2366
|
+
".wrongstack",
|
|
2367
|
+
"node_modules",
|
|
2368
|
+
"vendor",
|
|
2369
|
+
"target",
|
|
2370
|
+
"bin",
|
|
2371
|
+
"obj",
|
|
2372
|
+
"dist",
|
|
2373
|
+
"build",
|
|
2374
|
+
"out",
|
|
2375
|
+
"coverage",
|
|
2376
|
+
".cache",
|
|
2377
|
+
".idea",
|
|
2378
|
+
".vscode",
|
|
2379
|
+
".next",
|
|
2380
|
+
".nuxt"
|
|
2381
|
+
]);
|
|
2382
|
+
async function detectLanguageWorkspaces(options) {
|
|
2383
|
+
const projectRoot = await canonicalDirectory(options.projectRoot);
|
|
2384
|
+
const cwdInput = options.cwd ? path4.isAbsolute(options.cwd) ? options.cwd : path4.resolve(projectRoot, options.cwd) : projectRoot;
|
|
2385
|
+
const cwd = await canonicalInside(cwdInput, projectRoot, "cwd");
|
|
2386
|
+
const target = options.target ? await canonicalInside(resolveFrom(cwd, options.target), projectRoot, "target") : void 0;
|
|
2387
|
+
const profiles = (options.profiles ?? languageProfileRegistry.list()).filter((profile) => !options.language || profile.id === options.language).slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
2388
|
+
const limits = normalizeLimits(options.limits);
|
|
2389
|
+
const state = {
|
|
2390
|
+
entries: 0,
|
|
2391
|
+
truncated: false,
|
|
2392
|
+
sourcePaths: /* @__PURE__ */ new Map(),
|
|
2393
|
+
candidates: /* @__PURE__ */ new Map()
|
|
2394
|
+
};
|
|
2395
|
+
await scanDirectory(projectRoot, 0, profiles, limits, state, options.signal);
|
|
2396
|
+
addSourceFallbacks(projectRoot, profiles, state);
|
|
2397
|
+
if (target) addTargetEvidence(target, projectRoot, profiles, state);
|
|
2398
|
+
const workspaces = await Promise.all(
|
|
2399
|
+
[...state.candidates.values()].map((candidate) => finalizeCandidate(candidate, projectRoot))
|
|
2400
|
+
);
|
|
2401
|
+
workspaces.sort(compareWorkspaces);
|
|
2402
|
+
return {
|
|
2403
|
+
projectRoot,
|
|
2404
|
+
scannedEntries: state.entries,
|
|
2405
|
+
truncated: state.truncated,
|
|
2406
|
+
workspaces
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
async function scanDirectory(directory, depth, profiles, limits, state, signal) {
|
|
2410
|
+
signal?.throwIfAborted();
|
|
2411
|
+
if (depth > limits.maxDepth || state.entries >= limits.maxEntries) {
|
|
2412
|
+
state.truncated = true;
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
let entries;
|
|
2416
|
+
try {
|
|
2417
|
+
entries = await fs2.readdir(directory, { withFileTypes: true });
|
|
2418
|
+
} catch {
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
2422
|
+
for (const entry of entries) {
|
|
2423
|
+
signal?.throwIfAborted();
|
|
2424
|
+
if (state.entries >= limits.maxEntries) {
|
|
2425
|
+
state.truncated = true;
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
state.entries++;
|
|
2429
|
+
const fullPath = path4.join(directory, entry.name);
|
|
2430
|
+
if (entry.isSymbolicLink()) continue;
|
|
2431
|
+
if (entry.isDirectory()) {
|
|
2432
|
+
if (shouldIgnoreDirectory(entry.name, profiles)) continue;
|
|
2433
|
+
if (depth >= limits.maxDepth) {
|
|
2434
|
+
state.truncated = true;
|
|
2435
|
+
continue;
|
|
2436
|
+
}
|
|
2437
|
+
await scanDirectory(fullPath, depth + 1, profiles, limits, state, signal);
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (!entry.isFile()) continue;
|
|
2441
|
+
collectFileEvidence(directory, fullPath, entry.name, profiles, state);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
|
|
2445
|
+
const lower = basename4.toLowerCase();
|
|
2446
|
+
const extension = path4.extname(lower);
|
|
2447
|
+
for (const profile of profiles) {
|
|
2448
|
+
const detector = profile.detectors.find(
|
|
2449
|
+
(rule) => rule.filename ? lower === rule.filename.toLowerCase() : lower.endsWith(rule.suffix.toLowerCase())
|
|
2450
|
+
);
|
|
2451
|
+
if (detector) {
|
|
2452
|
+
const candidate = getCandidate(state, profile, directory);
|
|
2453
|
+
candidate.evidence.push({
|
|
2454
|
+
kind: detector.kind,
|
|
2455
|
+
path: fullPath,
|
|
2456
|
+
value: basename4,
|
|
2457
|
+
weight: detector.weight
|
|
2458
|
+
});
|
|
2459
|
+
if (detector.kind === "manifest" || detector.kind === "config") {
|
|
2460
|
+
candidate.manifests.push(fullPath);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (profile.extensions.includes(extension)) {
|
|
2464
|
+
const paths = state.sourcePaths.get(profile.id) ?? [];
|
|
2465
|
+
if (paths.length < SOURCE_WEIGHT_CAP / SOURCE_WEIGHT) paths.push(fullPath);
|
|
2466
|
+
state.sourcePaths.set(profile.id, paths);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
function addSourceFallbacks(projectRoot, profiles, state) {
|
|
2471
|
+
for (const profile of profiles) {
|
|
2472
|
+
const sources = state.sourcePaths.get(profile.id) ?? [];
|
|
2473
|
+
if (sources.length === 0) continue;
|
|
2474
|
+
const profileCandidates = [...state.candidates.values()].filter(
|
|
2475
|
+
(item) => item.profile.id === profile.id
|
|
2476
|
+
);
|
|
2477
|
+
for (const source of sources) {
|
|
2478
|
+
const containing = profileCandidates.filter((candidate2) => isInside(source, candidate2.root)).sort(
|
|
2479
|
+
(a, b) => pathDepth(b.root, projectRoot) - pathDepth(a.root, projectRoot) || a.root.localeCompare(b.root)
|
|
2480
|
+
);
|
|
2481
|
+
const candidate = containing[0] ?? (profile.sourceFallback === false ? void 0 : getCandidate(state, profile, projectRoot));
|
|
2482
|
+
if (!candidate) continue;
|
|
2483
|
+
candidate.evidence.push({
|
|
2484
|
+
kind: "source",
|
|
2485
|
+
path: source,
|
|
2486
|
+
value: path4.extname(source),
|
|
2487
|
+
weight: SOURCE_WEIGHT
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
function addTargetEvidence(target, projectRoot, profiles, state) {
|
|
2493
|
+
const extension = path4.extname(target).toLowerCase();
|
|
2494
|
+
for (const profile of profiles) {
|
|
2495
|
+
if (!profile.extensions.includes(extension)) continue;
|
|
2496
|
+
const candidates = [...state.candidates.values()].filter(
|
|
2497
|
+
(candidate2) => candidate2.profile.id === profile.id && isInside(target, candidate2.root)
|
|
2498
|
+
);
|
|
2499
|
+
const candidate = candidates.length > 0 ? candidates.sort(
|
|
2500
|
+
(a, b) => pathDepth(b.root, projectRoot) - pathDepth(a.root, projectRoot) || a.root.localeCompare(b.root)
|
|
2501
|
+
)[0] : profile.sourceFallback === false ? void 0 : getCandidate(state, profile, path4.dirname(target));
|
|
2502
|
+
if (!candidate) continue;
|
|
2503
|
+
candidate.evidence.push({
|
|
2504
|
+
kind: "target",
|
|
2505
|
+
path: target,
|
|
2506
|
+
value: extension,
|
|
2507
|
+
weight: TARGET_WEIGHT
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
async function finalizeCandidate(candidate, projectRoot) {
|
|
2512
|
+
const evidence = dedupeEvidence(candidate.evidence).sort(compareEvidence);
|
|
2513
|
+
const manifests = [...new Set(candidate.manifests)].sort();
|
|
2514
|
+
const confidence = Math.min(1, evidence.reduce((sum, item) => sum + item.weight, 0) / 100);
|
|
2515
|
+
const packageManager = await detectPackageManager2(candidate.profile, candidate.root, evidence);
|
|
2516
|
+
const id = createHash("sha256").update(`${candidate.profile.id}\0${path4.relative(projectRoot, candidate.root)}`).digest("hex").slice(0, 16);
|
|
2517
|
+
return Object.freeze({
|
|
2518
|
+
id,
|
|
2519
|
+
language: candidate.profile.id,
|
|
2520
|
+
root: candidate.root,
|
|
2521
|
+
confidence,
|
|
2522
|
+
evidence: Object.freeze(evidence.map((item) => Object.freeze(item))),
|
|
2523
|
+
...packageManager ? { packageManager } : {},
|
|
2524
|
+
manifests: Object.freeze(manifests),
|
|
2525
|
+
capabilities: Object.freeze(
|
|
2526
|
+
Object.keys(candidate.profile.operations).sort()
|
|
2527
|
+
)
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
async function detectPackageManager2(profile, root, evidence) {
|
|
2531
|
+
if (profile.packageManagers.length === 1) return profile.packageManagers[0];
|
|
2532
|
+
if (profile.id !== "typescript" && profile.id !== "javascript") return void 0;
|
|
2533
|
+
let declared;
|
|
2534
|
+
try {
|
|
2535
|
+
const pkg = JSON.parse(await fs2.readFile(path4.join(root, "package.json"), "utf8"));
|
|
2536
|
+
if (typeof pkg.packageManager === "string") {
|
|
2537
|
+
const manager = pkg.packageManager.split("@")[0];
|
|
2538
|
+
if (manager && profile.packageManagers.includes(manager)) declared = manager;
|
|
2539
|
+
}
|
|
2540
|
+
} catch {
|
|
2541
|
+
}
|
|
2542
|
+
const lockManagers = /* @__PURE__ */ new Set();
|
|
2543
|
+
for (const item of evidence) {
|
|
2544
|
+
const name = path4.basename(item.path).toLowerCase();
|
|
2545
|
+
if (name === "pnpm-lock.yaml") lockManagers.add("pnpm");
|
|
2546
|
+
else if (name === "yarn.lock") lockManagers.add("yarn");
|
|
2547
|
+
else if (name === "bun.lock" || name === "bun.lockb") lockManagers.add("bun");
|
|
2548
|
+
else if (name === "package-lock.json") lockManagers.add("npm");
|
|
2549
|
+
}
|
|
2550
|
+
if (declared && (lockManagers.size === 0 || lockManagers.size === 1 && lockManagers.has(declared))) {
|
|
2551
|
+
return declared;
|
|
2552
|
+
}
|
|
2553
|
+
if (lockManagers.size === 1) return [...lockManagers][0];
|
|
2554
|
+
if (lockManagers.size > 1) return void 0;
|
|
2555
|
+
return declared ?? "npm";
|
|
2556
|
+
}
|
|
2557
|
+
function getCandidate(state, profile, root) {
|
|
2558
|
+
const key = `${profile.id}\0${root}`;
|
|
2559
|
+
let candidate = state.candidates.get(key);
|
|
2560
|
+
if (!candidate) {
|
|
2561
|
+
candidate = { profile, root, evidence: [], manifests: [] };
|
|
2562
|
+
state.candidates.set(key, candidate);
|
|
2563
|
+
}
|
|
2564
|
+
return candidate;
|
|
2565
|
+
}
|
|
2566
|
+
function shouldIgnoreDirectory(name, profiles) {
|
|
2567
|
+
if (GLOBAL_IGNORES.has(name) || name.startsWith(".")) return true;
|
|
2568
|
+
return profiles.some((profile) => profile.ignoredDirectories.includes(name));
|
|
2569
|
+
}
|
|
2570
|
+
function normalizeLimits(input) {
|
|
2571
|
+
const maxDepth = Math.max(
|
|
2572
|
+
0,
|
|
2573
|
+
Math.min(12, Math.trunc(input?.maxDepth ?? DEFAULT_LIMITS.maxDepth))
|
|
2574
|
+
);
|
|
2575
|
+
const maxEntries = Math.max(
|
|
2576
|
+
1,
|
|
2577
|
+
Math.min(5e4, Math.trunc(input?.maxEntries ?? DEFAULT_LIMITS.maxEntries))
|
|
2578
|
+
);
|
|
2579
|
+
return { maxDepth, maxEntries };
|
|
2580
|
+
}
|
|
2581
|
+
async function canonicalDirectory(input) {
|
|
2582
|
+
const resolved = path4.resolve(input);
|
|
2583
|
+
const real = await fs2.realpath(resolved);
|
|
2584
|
+
const stat5 = await fs2.stat(real);
|
|
2585
|
+
if (!stat5.isDirectory()) throw new Error(`Project root is not a directory: ${input}`);
|
|
2586
|
+
return real;
|
|
2587
|
+
}
|
|
2588
|
+
async function canonicalInside(input, root, label) {
|
|
2589
|
+
const resolved = path4.resolve(input);
|
|
2590
|
+
let real;
|
|
2591
|
+
try {
|
|
2592
|
+
real = await fs2.realpath(resolved);
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
if (error.code !== "ENOENT") throw error;
|
|
2595
|
+
const parent = await fs2.realpath(path4.dirname(resolved));
|
|
2596
|
+
real = path4.join(parent, path4.basename(resolved));
|
|
2597
|
+
}
|
|
2598
|
+
if (!isInside(real, root)) throw new Error(`${label} is outside project root: ${input}`);
|
|
2599
|
+
return real;
|
|
2600
|
+
}
|
|
2601
|
+
function resolveFrom(cwd, input) {
|
|
2602
|
+
return path4.isAbsolute(input) ? input : path4.resolve(cwd, input);
|
|
2603
|
+
}
|
|
2604
|
+
function isInside(candidate, root) {
|
|
2605
|
+
const relative5 = path4.relative(root, candidate);
|
|
2606
|
+
return relative5 === "" || !relative5.startsWith("..") && !path4.isAbsolute(relative5);
|
|
2607
|
+
}
|
|
2608
|
+
function pathDepth(candidate, root) {
|
|
2609
|
+
const relative5 = path4.relative(root, candidate);
|
|
2610
|
+
return relative5 === "" ? 0 : relative5.split(path4.sep).length;
|
|
2611
|
+
}
|
|
2612
|
+
function dedupeEvidence(items) {
|
|
2613
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2614
|
+
return items.filter((item) => {
|
|
2615
|
+
const key = `${item.kind}\0${item.path}\0${item.value}\0${item.weight}`;
|
|
2616
|
+
if (seen.has(key)) return false;
|
|
2617
|
+
seen.add(key);
|
|
2618
|
+
return true;
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
function compareEvidence(a, b) {
|
|
2622
|
+
return a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind) || b.weight - a.weight;
|
|
2623
|
+
}
|
|
2624
|
+
function compareWorkspaces(a, b) {
|
|
2625
|
+
return b.confidence - a.confidence || a.language.localeCompare(b.language) || a.root.localeCompare(b.root) || a.id.localeCompare(b.id);
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// src/languages/diagnostics.ts
|
|
2629
|
+
import * as path5 from "node:path";
|
|
2630
|
+
var MAX_DIAGNOSTICS = 200;
|
|
2631
|
+
function parseLanguageDiagnostics(parser, stdout, stderr, workspaceRoot) {
|
|
2632
|
+
const text = `${stdout}${stdout && stderr ? "\n" : ""}${stderr}`;
|
|
2633
|
+
let diagnostics;
|
|
2634
|
+
switch (parser) {
|
|
2635
|
+
case "typescript":
|
|
2636
|
+
diagnostics = parseTypeScript(text, workspaceRoot);
|
|
2637
|
+
break;
|
|
2638
|
+
case "cargo-json":
|
|
2639
|
+
diagnostics = parseCargoJson(text, workspaceRoot);
|
|
2640
|
+
break;
|
|
2641
|
+
case "php-lint":
|
|
2642
|
+
diagnostics = parsePhpLint(text, workspaceRoot);
|
|
2643
|
+
break;
|
|
2644
|
+
case "dotnet-build":
|
|
2645
|
+
case "dotnet-format":
|
|
2646
|
+
case "dotnet-test":
|
|
2647
|
+
diagnostics = parseDotnet(text, workspaceRoot);
|
|
2648
|
+
break;
|
|
2649
|
+
case "go-test":
|
|
2650
|
+
case "go-compiler":
|
|
2651
|
+
case "gofmt":
|
|
2652
|
+
diagnostics = parseGo(text, workspaceRoot);
|
|
2653
|
+
break;
|
|
2654
|
+
case "biome":
|
|
2655
|
+
diagnostics = parseBiome(text, workspaceRoot);
|
|
2656
|
+
break;
|
|
2657
|
+
default:
|
|
2658
|
+
diagnostics = parseGeneric(text, parser, workspaceRoot);
|
|
2659
|
+
break;
|
|
2660
|
+
}
|
|
2661
|
+
const sorted = dedupeDiagnostics(diagnostics).sort(compareDiagnostics);
|
|
2662
|
+
const omitted = Math.max(0, sorted.length - MAX_DIAGNOSTICS);
|
|
2663
|
+
const kept = Object.freeze(sorted.slice(0, MAX_DIAGNOSTICS).map((item) => Object.freeze(item)));
|
|
2664
|
+
return {
|
|
2665
|
+
diagnostics: kept,
|
|
2666
|
+
omitted,
|
|
2667
|
+
summary: summarize(kept)
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
function parseTypeScript(text, root) {
|
|
2671
|
+
const diagnostics = [];
|
|
2672
|
+
const regex = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s*(.+)$/gm;
|
|
2673
|
+
for (const match of text.matchAll(regex)) {
|
|
2674
|
+
diagnostics.push({
|
|
2675
|
+
severity: match[4] === "warning" ? "warning" : "error",
|
|
2676
|
+
code: match[5],
|
|
2677
|
+
message: match[6].trim(),
|
|
2678
|
+
file: normalizeDiagnosticPath(match[1], root),
|
|
2679
|
+
range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
|
|
2680
|
+
source: "typescript"
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
return diagnostics;
|
|
2684
|
+
}
|
|
2685
|
+
function parseCargoJson(text, root) {
|
|
2686
|
+
const diagnostics = [];
|
|
2687
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2688
|
+
if (!line.trim().startsWith("{")) continue;
|
|
2689
|
+
try {
|
|
2690
|
+
const value = JSON.parse(line);
|
|
2691
|
+
if (value.reason !== "compiler-message" || !value.message?.message) continue;
|
|
2692
|
+
const primary = value.message.spans?.find((span) => span.is_primary) ?? value.message.spans?.[0];
|
|
2693
|
+
diagnostics.push({
|
|
2694
|
+
severity: normalizeSeverity(value.message.level),
|
|
2695
|
+
...value.message.code?.code ? { code: value.message.code.code } : {},
|
|
2696
|
+
message: value.message.message,
|
|
2697
|
+
...primary?.file_name ? { file: normalizeDiagnosticPath(primary.file_name, root) } : {},
|
|
2698
|
+
...primary?.line_start ? { range: { start: { line: primary.line_start, column: primary.column_start ?? 1 } } } : {},
|
|
2699
|
+
source: "rustc"
|
|
2700
|
+
});
|
|
2701
|
+
} catch {
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
return diagnostics;
|
|
2705
|
+
}
|
|
2706
|
+
function parseGo(text, root) {
|
|
2707
|
+
return parseLinePattern(
|
|
2708
|
+
text,
|
|
2709
|
+
/^(.*?\.go):(\d+):(\d+):\s*(.+)$/gm,
|
|
2710
|
+
root,
|
|
2711
|
+
"go",
|
|
2712
|
+
(_match, message) => ({ message, severity: /warning/i.test(message) ? "warning" : "error" })
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
function parsePhpLint(text, root) {
|
|
2716
|
+
const diagnostics = [];
|
|
2717
|
+
const regex = /(?:PHP\s+)?(?:Parse|Fatal) error:\s*(.+?)\s+in\s+(.+?)\s+on line\s+(\d+)/gi;
|
|
2718
|
+
for (const match of text.matchAll(regex)) {
|
|
2719
|
+
diagnostics.push({
|
|
2720
|
+
severity: "error",
|
|
2721
|
+
message: match[1].trim(),
|
|
2722
|
+
file: normalizeDiagnosticPath(match[2], root),
|
|
2723
|
+
range: { start: { line: toPositiveInt(match[3]), column: 1 } },
|
|
2724
|
+
source: "php"
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
return diagnostics;
|
|
2728
|
+
}
|
|
2729
|
+
function parseDotnet(text, root) {
|
|
2730
|
+
const diagnostics = [];
|
|
2731
|
+
const regex = /^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+([A-Z]+\d+):\s*(.+?)(?:\s+\[.+\])?$/gm;
|
|
2732
|
+
for (const match of text.matchAll(regex)) {
|
|
2733
|
+
diagnostics.push({
|
|
2734
|
+
severity: match[4] === "warning" ? "warning" : "error",
|
|
2735
|
+
code: match[5],
|
|
2736
|
+
message: match[6].trim(),
|
|
2737
|
+
file: normalizeDiagnosticPath(match[1], root),
|
|
2738
|
+
range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
|
|
2739
|
+
source: "dotnet"
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
return diagnostics;
|
|
2743
|
+
}
|
|
2744
|
+
function parseBiome(text, root) {
|
|
2745
|
+
const diagnostics = [];
|
|
2746
|
+
const regex = /^(.+?):(\d+):(\d+)\s+(lint\/[^\s]+|format)\s+(.+)$/gm;
|
|
2747
|
+
for (const match of text.matchAll(regex)) {
|
|
2748
|
+
diagnostics.push({
|
|
2749
|
+
severity: "warning",
|
|
2750
|
+
code: match[4],
|
|
2751
|
+
message: match[5].trim(),
|
|
2752
|
+
file: normalizeDiagnosticPath(match[1], root),
|
|
2753
|
+
range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
|
|
2754
|
+
source: "biome"
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
return diagnostics;
|
|
2758
|
+
}
|
|
2759
|
+
function parseGeneric(text, source, root) {
|
|
2760
|
+
return parseLinePattern(
|
|
2761
|
+
text,
|
|
2762
|
+
/^(.+?):(\d+):(\d+):\s*(?:(error|warning|info):\s*)?(.+)$/gm,
|
|
2763
|
+
root,
|
|
2764
|
+
source,
|
|
2765
|
+
(match, fallback) => ({
|
|
2766
|
+
severity: normalizeSeverity(match[4]),
|
|
2767
|
+
message: match[5]?.trim() || fallback
|
|
2768
|
+
})
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
function parsePackageReports(parser, stdout, stderr) {
|
|
2772
|
+
const text = `${stdout}${stdout && stderr ? "\n" : ""}${stderr}`;
|
|
2773
|
+
switch (parser) {
|
|
2774
|
+
case "npm-audit":
|
|
2775
|
+
return parseNpmAudit(text);
|
|
2776
|
+
case "npm-outdated":
|
|
2777
|
+
return parseNpmOutdated(text);
|
|
2778
|
+
case "cargo-audit":
|
|
2779
|
+
return parseCargoAudit(text);
|
|
2780
|
+
case "composer-audit":
|
|
2781
|
+
return parseComposerAudit(text);
|
|
2782
|
+
case "composer-outdated":
|
|
2783
|
+
return parseComposerOutdated(text);
|
|
2784
|
+
case "dotnet-package":
|
|
2785
|
+
return parseDotnetPackage(text);
|
|
2786
|
+
default:
|
|
2787
|
+
return { diagnostics: [], vulnerabilities: [], outdated: [] };
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
function parseNpmAudit(text) {
|
|
2791
|
+
const advisories = [];
|
|
2792
|
+
const diagnostics = [];
|
|
2793
|
+
let root;
|
|
2794
|
+
try {
|
|
2795
|
+
root = JSON.parse(text);
|
|
2796
|
+
} catch {
|
|
2797
|
+
return { diagnostics, vulnerabilities: advisories, outdated: [] };
|
|
2798
|
+
}
|
|
2799
|
+
const vulnerabilities = root?.vulnerabilities ?? {};
|
|
2800
|
+
const advisoriesRecord = root?.advisories ?? vulnerabilities;
|
|
2801
|
+
for (const [id, value] of Object.entries(advisoriesRecord)) {
|
|
2802
|
+
const advisory = value;
|
|
2803
|
+
const name = advisory.module_name ?? advisory.package_name ?? advisory.name ?? id;
|
|
2804
|
+
advisories.push({
|
|
2805
|
+
package: name,
|
|
2806
|
+
...advisory.title ? { advisory: advisory.title } : { advisory: id },
|
|
2807
|
+
severity: mapSeverity(advisory.severity),
|
|
2808
|
+
...advisory.patched_versions ? { fixedIn: advisory.patched_versions } : {},
|
|
2809
|
+
...advisory.url ? { url: advisory.url } : {}
|
|
2810
|
+
});
|
|
2811
|
+
diagnostics.push({
|
|
2812
|
+
severity: mapSeverity(advisory.severity) === "critical" || mapSeverity(advisory.severity) === "high" ? "error" : "warning",
|
|
2813
|
+
code: id,
|
|
2814
|
+
message: advisory.title ?? `Vulnerability reported for ${name}.`,
|
|
2815
|
+
source: "npm-audit"
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
return { diagnostics, vulnerabilities: advisories, outdated: [] };
|
|
2819
|
+
}
|
|
2820
|
+
function parseNpmOutdated(text) {
|
|
2821
|
+
const diagnostics = [];
|
|
2822
|
+
const outdated = [];
|
|
2823
|
+
let payload = {};
|
|
2824
|
+
try {
|
|
2825
|
+
payload = JSON.parse(text);
|
|
2826
|
+
} catch {
|
|
2827
|
+
return { diagnostics, vulnerabilities: [], outdated };
|
|
2828
|
+
}
|
|
2829
|
+
for (const [name, info] of Object.entries(payload)) {
|
|
2830
|
+
const entry = info;
|
|
2831
|
+
if (!entry.latest || entry.latest === entry.current) continue;
|
|
2832
|
+
outdated.push({
|
|
2833
|
+
name,
|
|
2834
|
+
previous: entry.current,
|
|
2835
|
+
resolved: entry.latest,
|
|
2836
|
+
kind: mapOutdatedKind(entry.type)
|
|
2837
|
+
});
|
|
2838
|
+
diagnostics.push({
|
|
2839
|
+
severity: "info",
|
|
2840
|
+
code: "outdated",
|
|
2841
|
+
message: `${name}: ${entry.current ?? "?"} \u2192 ${entry.latest}`,
|
|
2842
|
+
source: "npm-outdated"
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2845
|
+
return { diagnostics, vulnerabilities: [], outdated };
|
|
2846
|
+
}
|
|
2847
|
+
function parseCargoAudit(text) {
|
|
2848
|
+
const diagnostics = [];
|
|
2849
|
+
const vulnerabilities = [];
|
|
2850
|
+
let root;
|
|
2851
|
+
try {
|
|
2852
|
+
root = JSON.parse(text);
|
|
2853
|
+
} catch {
|
|
2854
|
+
return { diagnostics, vulnerabilities, outdated: [] };
|
|
2855
|
+
}
|
|
2856
|
+
const findings = root.vulnerabilities?.found;
|
|
2857
|
+
if (!Array.isArray(findings)) return { diagnostics, vulnerabilities, outdated: [] };
|
|
2858
|
+
for (const finding of findings) {
|
|
2859
|
+
const item = finding;
|
|
2860
|
+
const name = item.package ?? "unknown";
|
|
2861
|
+
const advisory = item.id ?? item.advisory?.id ?? "cargo-audit";
|
|
2862
|
+
vulnerabilities.push({
|
|
2863
|
+
package: name,
|
|
2864
|
+
...item.title ? { advisory: item.title } : { advisory },
|
|
2865
|
+
severity: mapSeverity(item.severity),
|
|
2866
|
+
...item.patched_versions && item.patched_versions.length > 0 ? { fixedIn: item.patched_versions[0] } : {},
|
|
2867
|
+
...item.url?.short ? { url: item.url.short } : {}
|
|
2868
|
+
});
|
|
2869
|
+
diagnostics.push({
|
|
2870
|
+
severity: mapSeverity(item.severity) === "critical" || mapSeverity(item.severity) === "high" ? "error" : "warning",
|
|
2871
|
+
code: advisory,
|
|
2872
|
+
message: item.title ?? `${name} reported by cargo-audit.`,
|
|
2873
|
+
source: "cargo-audit"
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
return { diagnostics, vulnerabilities, outdated: [] };
|
|
2877
|
+
}
|
|
2878
|
+
function parseComposerAudit(text) {
|
|
2879
|
+
const diagnostics = [];
|
|
2880
|
+
const vulnerabilities = [];
|
|
2881
|
+
const lines = text.split(/\r?\n/);
|
|
2882
|
+
for (const line of lines) {
|
|
2883
|
+
if (!line.trim().startsWith("{")) continue;
|
|
2884
|
+
try {
|
|
2885
|
+
const entry = JSON.parse(line);
|
|
2886
|
+
if (!entry.package) continue;
|
|
2887
|
+
vulnerabilities.push({
|
|
2888
|
+
package: entry.package,
|
|
2889
|
+
...entry.advisory ? { advisory: entry.advisory } : {},
|
|
2890
|
+
...entry.title ? { advisory: entry.title } : { advisory: entry.advisory ?? "composer-audit" },
|
|
2891
|
+
severity: mapSeverity(entry.severity),
|
|
2892
|
+
...entry.affectedVersions ? { fixedIn: entry.affectedVersions } : {},
|
|
2893
|
+
...entry.url ? { url: entry.url } : {}
|
|
2894
|
+
});
|
|
2895
|
+
diagnostics.push({
|
|
2896
|
+
severity: "warning",
|
|
2897
|
+
code: entry.advisory ?? "composer-audit",
|
|
2898
|
+
message: entry.title ?? `${entry.package} reported by composer audit.`,
|
|
2899
|
+
source: "composer-audit"
|
|
2900
|
+
});
|
|
2901
|
+
} catch {
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
return { diagnostics, vulnerabilities, outdated: [] };
|
|
2905
|
+
}
|
|
2906
|
+
function parseComposerOutdated(text) {
|
|
2907
|
+
const diagnostics = [];
|
|
2908
|
+
const outdated = [];
|
|
2909
|
+
const lines = text.split(/\r?\n/);
|
|
2910
|
+
for (const line of lines) {
|
|
2911
|
+
if (!line.trim().startsWith("{")) continue;
|
|
2912
|
+
try {
|
|
2913
|
+
const entry = JSON.parse(line);
|
|
2914
|
+
if (!entry.name || entry.version === entry.latest) continue;
|
|
2915
|
+
outdated.push({
|
|
2916
|
+
name: entry.name,
|
|
2917
|
+
previous: entry.version,
|
|
2918
|
+
resolved: entry.latest ?? entry.version
|
|
2919
|
+
});
|
|
2920
|
+
diagnostics.push({
|
|
2921
|
+
severity: "info",
|
|
2922
|
+
code: "outdated",
|
|
2923
|
+
message: `${entry.name}: ${entry.version ?? "?"} \u2192 ${entry.latest ?? "?"}`,
|
|
2924
|
+
source: "composer-outdated"
|
|
2925
|
+
});
|
|
2926
|
+
} catch {
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
return { diagnostics, vulnerabilities: [], outdated };
|
|
2930
|
+
}
|
|
2931
|
+
function parseDotnetPackage(text) {
|
|
2932
|
+
const diagnostics = [];
|
|
2933
|
+
const vulnerabilities = [];
|
|
2934
|
+
const outdated = [];
|
|
2935
|
+
let root;
|
|
2936
|
+
try {
|
|
2937
|
+
root = JSON.parse(text);
|
|
2938
|
+
} catch {
|
|
2939
|
+
return { diagnostics, vulnerabilities, outdated };
|
|
2940
|
+
}
|
|
2941
|
+
const projects = root.projects;
|
|
2942
|
+
if (!Array.isArray(projects)) return { diagnostics, vulnerabilities, outdated };
|
|
2943
|
+
for (const project of projects) {
|
|
2944
|
+
const packages = Array.isArray(project.packages) ? project.packages : [];
|
|
2945
|
+
for (const pkg of packages) {
|
|
2946
|
+
const name = typeof pkg.name === "string" ? pkg.name : "unknown";
|
|
2947
|
+
const requested = typeof pkg.requestedVersion === "string" ? pkg.requestedVersion : void 0;
|
|
2948
|
+
const resolved = typeof pkg.resolvedVersion === "string" ? pkg.resolvedVersion : void 0;
|
|
2949
|
+
const vulnerabilitiesRaw = Array.isArray(pkg.vulnerabilities) ? pkg.vulnerabilities : [];
|
|
2950
|
+
for (const vuln of vulnerabilitiesRaw) {
|
|
2951
|
+
const advisory = typeof vuln.advisoryUrl === "string" ? vuln.advisoryUrl : "dotnet-vulnerable";
|
|
2952
|
+
vulnerabilities.push({
|
|
2953
|
+
package: name,
|
|
2954
|
+
advisory,
|
|
2955
|
+
severity: mapSeverity(typeof vuln.severity === "string" ? vuln.severity : void 0)
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
if (vulnerabilitiesRaw.length > 0) {
|
|
2959
|
+
diagnostics.push({
|
|
2960
|
+
severity: "warning",
|
|
2961
|
+
code: "dotnet-vulnerable",
|
|
2962
|
+
message: `${name} has ${vulnerabilitiesRaw.length} known vulnerability entry/entries.`,
|
|
2963
|
+
source: "dotnet-package"
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
if (requested && resolved && requested.startsWith(">") && requested.split(">")[1].split(".").slice(0, 2).join(".") !== resolved.split(".").slice(0, 2).join(".")) {
|
|
2967
|
+
outdated.push({ name, requested, resolved });
|
|
2968
|
+
diagnostics.push({
|
|
2969
|
+
severity: "info",
|
|
2970
|
+
code: "outdated",
|
|
2971
|
+
message: `${name}: ${requested} \u2192 ${resolved}`,
|
|
2972
|
+
source: "dotnet-package"
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
return { diagnostics, vulnerabilities, outdated };
|
|
2978
|
+
}
|
|
2979
|
+
function mapSeverity(value) {
|
|
2980
|
+
switch (value?.toLowerCase()) {
|
|
2981
|
+
case "critical":
|
|
2982
|
+
case "high":
|
|
2983
|
+
return "high";
|
|
2984
|
+
case "medium":
|
|
2985
|
+
case "moderate":
|
|
2986
|
+
return "moderate";
|
|
2987
|
+
case "low":
|
|
2988
|
+
return "low";
|
|
2989
|
+
case "unknown":
|
|
2990
|
+
return "unknown";
|
|
2991
|
+
default:
|
|
2992
|
+
return "unknown";
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
function mapOutdatedKind(value) {
|
|
2996
|
+
switch (value?.toLowerCase()) {
|
|
2997
|
+
case "devdependencies":
|
|
2998
|
+
case "development":
|
|
2999
|
+
return "development";
|
|
3000
|
+
case "optionaldependencies":
|
|
3001
|
+
case "optional":
|
|
3002
|
+
return "optional";
|
|
3003
|
+
default:
|
|
3004
|
+
return "runtime";
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
function parseLinePattern(text, regex, root, source, details) {
|
|
3008
|
+
const diagnostics = [];
|
|
3009
|
+
for (const match of text.matchAll(regex)) {
|
|
3010
|
+
const parsed = details(match, match.at(-1)?.trim() ?? "Diagnostic");
|
|
3011
|
+
diagnostics.push({
|
|
3012
|
+
severity: parsed.severity,
|
|
3013
|
+
message: parsed.message,
|
|
3014
|
+
file: normalizeDiagnosticPath(match[1], root),
|
|
3015
|
+
range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
|
|
3016
|
+
source
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
return diagnostics;
|
|
3020
|
+
}
|
|
3021
|
+
function normalizeDiagnosticPath(value, root) {
|
|
3022
|
+
const clean = value.trim().replace(/^['"]|['"]$/g, "");
|
|
3023
|
+
return path5.resolve(root, clean);
|
|
3024
|
+
}
|
|
3025
|
+
function normalizeSeverity(value) {
|
|
3026
|
+
if (value === "warning" || value === "warn") return "warning";
|
|
3027
|
+
if (value === "info" || value === "note" || value === "help") return "info";
|
|
3028
|
+
if (value === "hint") return "hint";
|
|
3029
|
+
return "error";
|
|
3030
|
+
}
|
|
3031
|
+
function toPositiveInt(value) {
|
|
3032
|
+
return Math.max(1, Number.parseInt(value ?? "1", 10) || 1);
|
|
3033
|
+
}
|
|
3034
|
+
function summarize(diagnostics) {
|
|
3035
|
+
return {
|
|
3036
|
+
errors: diagnostics.filter((item) => item.severity === "error").length,
|
|
3037
|
+
warnings: diagnostics.filter((item) => item.severity === "warning").length,
|
|
3038
|
+
infos: diagnostics.filter((item) => item.severity === "info" || item.severity === "hint").length
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
function dedupeDiagnostics(items) {
|
|
3042
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3043
|
+
return items.filter((item) => {
|
|
3044
|
+
const key = [
|
|
3045
|
+
item.source,
|
|
3046
|
+
item.code ?? "",
|
|
3047
|
+
item.file ?? "",
|
|
3048
|
+
item.range?.start.line ?? 0,
|
|
3049
|
+
item.range?.start.column ?? 0,
|
|
3050
|
+
item.message
|
|
3051
|
+
].join("\0");
|
|
3052
|
+
if (seen.has(key)) return false;
|
|
3053
|
+
seen.add(key);
|
|
3054
|
+
return true;
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
function compareDiagnostics(a, b) {
|
|
3058
|
+
return (a.file ?? "").localeCompare(b.file ?? "") || (a.range?.start.line ?? 0) - (b.range?.start.line ?? 0) || (a.range?.start.column ?? 0) - (b.range?.start.column ?? 0) || severityRank(a.severity) - severityRank(b.severity) || (a.code ?? "").localeCompare(b.code ?? "") || a.message.localeCompare(b.message);
|
|
3059
|
+
}
|
|
3060
|
+
function severityRank(value) {
|
|
3061
|
+
return value === "error" ? 0 : value === "warning" ? 1 : value === "info" ? 2 : 3;
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
// src/languages/execute.ts
|
|
3065
|
+
import * as fs4 from "node:fs/promises";
|
|
3066
|
+
import * as path7 from "node:path";
|
|
3067
|
+
|
|
3068
|
+
// src/languages/plan.ts
|
|
3069
|
+
import * as fs3 from "node:fs/promises";
|
|
3070
|
+
import * as path6 from "node:path";
|
|
3071
|
+
var MAX_ARGUMENTS = 128;
|
|
3072
|
+
var MAX_ARGUMENT_LENGTH = 4096;
|
|
3073
|
+
var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3074
|
+
var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
|
|
3075
|
+
var GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
|
|
3076
|
+
async function planLanguageOperation(options) {
|
|
3077
|
+
validateOperationOptions(options.operation, options.operationOptions?.packages);
|
|
3078
|
+
const detection = await detectLanguageWorkspaces(options);
|
|
3079
|
+
const candidates = detection.workspaces.filter(
|
|
3080
|
+
(workspace) => workspace.capabilities.includes(options.operation)
|
|
3081
|
+
);
|
|
3082
|
+
const selected = selectWorkspace(candidates, options);
|
|
3083
|
+
if (selected.status !== "selected") return selected.result;
|
|
3084
|
+
const profile = (options.profiles ?? languageProfileRegistry.list()).find(
|
|
3085
|
+
(item) => item.id === selected.workspace.language
|
|
3086
|
+
);
|
|
3087
|
+
if (!profile) {
|
|
3088
|
+
return {
|
|
3089
|
+
status: "not_found",
|
|
3090
|
+
reason: `Profile ${selected.workspace.language} is unavailable.`,
|
|
3091
|
+
candidates
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
if ((profile.id === "typescript" || profile.id === "javascript") && options.operation !== "syntax" && selected.workspace.packageManager === void 0 && new Set(
|
|
3095
|
+
selected.workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.value.toLowerCase())
|
|
3096
|
+
).size > 1) {
|
|
3097
|
+
return {
|
|
3098
|
+
status: "unavailable",
|
|
3099
|
+
workspace: selected.workspace,
|
|
3100
|
+
unavailable: {
|
|
3101
|
+
status: "unavailable",
|
|
3102
|
+
profileId: profile.id,
|
|
3103
|
+
workspaceId: selected.workspace.id,
|
|
3104
|
+
operation: options.operation,
|
|
3105
|
+
reason: "Conflicting Node lockfiles make the package manager ambiguous."
|
|
3106
|
+
}
|
|
3107
|
+
};
|
|
3108
|
+
}
|
|
3109
|
+
const resolver = profile.operations[options.operation];
|
|
3110
|
+
if (!resolver) {
|
|
3111
|
+
return {
|
|
3112
|
+
status: "unavailable",
|
|
3113
|
+
workspace: selected.workspace,
|
|
3114
|
+
unavailable: {
|
|
3115
|
+
status: "unavailable",
|
|
3116
|
+
profileId: profile.id,
|
|
3117
|
+
workspaceId: selected.workspace.id,
|
|
3118
|
+
operation: options.operation,
|
|
3119
|
+
reason: `${profile.displayName} does not define ${options.operation}.`
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
const canonicalCwd = options.cwd ? path6.isAbsolute(options.cwd) ? options.cwd : path6.resolve(detection.projectRoot, options.cwd) : detection.projectRoot;
|
|
3124
|
+
const target = options.target ? await canonicalTarget(canonicalCwd, options.target, detection.projectRoot) : void 0;
|
|
3125
|
+
const ctx = {
|
|
3126
|
+
projectRoot: detection.projectRoot,
|
|
3127
|
+
workspace: selected.workspace,
|
|
3128
|
+
...target ? { target } : {},
|
|
3129
|
+
mode: options.mode ?? "standard",
|
|
3130
|
+
options: options.operationOptions ?? {},
|
|
3131
|
+
platform: process.platform,
|
|
3132
|
+
pathExists: async (candidate) => {
|
|
3133
|
+
try {
|
|
3134
|
+
await fs3.access(path6.resolve(detection.projectRoot, candidate));
|
|
3135
|
+
return true;
|
|
3136
|
+
} catch {
|
|
3137
|
+
return false;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
};
|
|
3141
|
+
const result = await resolver(ctx);
|
|
3142
|
+
if ("status" in result) {
|
|
3143
|
+
return { status: "unavailable", workspace: selected.workspace, unavailable: result };
|
|
3144
|
+
}
|
|
3145
|
+
const errors = validateCommandPlan(result, profile, detection.projectRoot);
|
|
3146
|
+
if (errors.length > 0) {
|
|
3147
|
+
return {
|
|
3148
|
+
status: "unavailable",
|
|
3149
|
+
workspace: selected.workspace,
|
|
3150
|
+
unavailable: {
|
|
3151
|
+
status: "unavailable",
|
|
3152
|
+
profileId: profile.id,
|
|
3153
|
+
workspaceId: selected.workspace.id,
|
|
3154
|
+
operation: options.operation,
|
|
3155
|
+
reason: `Generated plan failed validation: ${errors.join("; ")}`
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
return { status: "planned", workspace: selected.workspace, plan: Object.freeze(result) };
|
|
3160
|
+
}
|
|
3161
|
+
function validateCommandPlan(plan, profile, projectRoot) {
|
|
3162
|
+
const errors = [];
|
|
3163
|
+
if (plan.profileId !== profile.id) errors.push("profile id does not match");
|
|
3164
|
+
if (!isInside2(plan.cwd, projectRoot)) errors.push("cwd is outside project root");
|
|
3165
|
+
if (plan.args.length > MAX_ARGUMENTS) errors.push(`argument count exceeds ${MAX_ARGUMENTS}`);
|
|
3166
|
+
if (plan.args.some((arg) => arg.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(arg))) {
|
|
3167
|
+
errors.push("arguments contain an invalid or oversized value");
|
|
3168
|
+
}
|
|
3169
|
+
if (!Number.isFinite(plan.timeoutMs) || plan.timeoutMs < 1 || plan.timeoutMs > 6e5) {
|
|
3170
|
+
errors.push("timeout is outside 1..600000ms");
|
|
3171
|
+
}
|
|
3172
|
+
if (!Number.isFinite(plan.outputLimitBytes) || plan.outputLimitBytes < 1 || plan.outputLimitBytes > 1e6) {
|
|
3173
|
+
errors.push("output limit is outside 1..1000000 bytes");
|
|
3174
|
+
}
|
|
3175
|
+
if (plan.kind === "internal") {
|
|
3176
|
+
if (plan.command !== null || plan.args.length !== 0)
|
|
3177
|
+
errors.push("internal plans cannot declare a command");
|
|
3178
|
+
} else {
|
|
3179
|
+
if (!plan.command || !profile.executables.includes(plan.command)) {
|
|
3180
|
+
errors.push(`executable "${plan.command ?? ""}" is not allowlisted by the profile`);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
if (Object.keys(plan.env).length > 0)
|
|
3184
|
+
errors.push("Phase 1 plans cannot override environment variables");
|
|
3185
|
+
return errors;
|
|
3186
|
+
}
|
|
3187
|
+
function selectWorkspace(candidates, options) {
|
|
3188
|
+
if (candidates.length === 0) {
|
|
3189
|
+
return {
|
|
3190
|
+
status: "result",
|
|
3191
|
+
result: {
|
|
3192
|
+
status: "not_found",
|
|
3193
|
+
reason: `No workspace supports ${options.operation}.`,
|
|
3194
|
+
candidates: []
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
if (options.workspace) {
|
|
3199
|
+
const requested = path6.isAbsolute(options.workspace) ? path6.resolve(options.workspace) : path6.resolve(options.projectRoot, options.workspace);
|
|
3200
|
+
const matches = candidates.filter(
|
|
3201
|
+
(item) => item.id === options.workspace || path6.resolve(item.root) === requested
|
|
3202
|
+
);
|
|
3203
|
+
if (matches.length === 1) return { status: "selected", workspace: matches[0] };
|
|
3204
|
+
return {
|
|
3205
|
+
status: "result",
|
|
3206
|
+
result: {
|
|
3207
|
+
status: "not_found",
|
|
3208
|
+
reason: `Requested workspace "${options.workspace}" was not detected.`,
|
|
3209
|
+
candidates: [...candidates]
|
|
3210
|
+
}
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
if (options.target) {
|
|
3214
|
+
const base = options.cwd ? path6.isAbsolute(options.cwd) ? path6.resolve(options.cwd) : path6.resolve(options.projectRoot, options.cwd) : path6.resolve(options.projectRoot);
|
|
3215
|
+
const target = path6.isAbsolute(options.target) ? path6.resolve(options.target) : path6.resolve(base, options.target);
|
|
3216
|
+
const targeted = candidates.filter((item) => item.evidence.some((evidence) => evidence.kind === "target")).sort(
|
|
3217
|
+
(a, b) => workspaceDepth(b.root, options.projectRoot) - workspaceDepth(a.root, options.projectRoot) || compareCandidates(a, b)
|
|
3218
|
+
);
|
|
3219
|
+
if (targeted[0]) return { status: "selected", workspace: targeted[0] };
|
|
3220
|
+
const containing = candidates.filter((item) => isInside2(target, item.root)).sort(
|
|
3221
|
+
(a, b) => workspaceDepth(b.root, options.projectRoot) - workspaceDepth(a.root, options.projectRoot) || compareCandidates(a, b)
|
|
3222
|
+
);
|
|
3223
|
+
if (containing[0]) return { status: "selected", workspace: containing[0] };
|
|
3224
|
+
}
|
|
3225
|
+
const sorted = [...candidates].sort(compareCandidates);
|
|
3226
|
+
const first = sorted[0];
|
|
3227
|
+
const firstDepth = workspaceDepth(first.root, options.projectRoot);
|
|
3228
|
+
const tied = sorted.filter(
|
|
3229
|
+
(item) => item.confidence === first.confidence && workspaceDepth(item.root, options.projectRoot) === firstDepth
|
|
3230
|
+
);
|
|
3231
|
+
if (tied.length > 1) {
|
|
3232
|
+
return {
|
|
3233
|
+
status: "result",
|
|
3234
|
+
result: {
|
|
3235
|
+
status: "ambiguous",
|
|
3236
|
+
reason: "Multiple workspaces have equal confidence; provide target, language, or workspace.",
|
|
3237
|
+
candidates: tied
|
|
3238
|
+
}
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
return { status: "selected", workspace: first };
|
|
3242
|
+
}
|
|
3243
|
+
function compareCandidates(a, b) {
|
|
3244
|
+
return b.confidence - a.confidence || a.language.localeCompare(b.language) || a.root.localeCompare(b.root);
|
|
3245
|
+
}
|
|
3246
|
+
function workspaceDepth(workspaceRoot, projectRoot) {
|
|
3247
|
+
const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
|
|
3248
|
+
return relative5 === "" ? 0 : relative5.split(path6.sep).length;
|
|
3249
|
+
}
|
|
3250
|
+
function validateOperationOptions(operation, packages) {
|
|
3251
|
+
if (!operation.startsWith("package-") || !packages) return;
|
|
3252
|
+
for (const value of packages) {
|
|
3253
|
+
if (!value || value.length > 214 || value.startsWith("-") || /[\r\n\0;&|`$<>\\]/.test(value)) {
|
|
3254
|
+
throw new Error(`Invalid package identifier "${value}"`);
|
|
3255
|
+
}
|
|
3256
|
+
if (value.startsWith(".") || value.startsWith("/") || /^[A-Za-z]:/.test(value) || value.includes("://")) {
|
|
3257
|
+
throw new Error(`Package paths and URLs are not supported: "${value}"`);
|
|
3258
|
+
}
|
|
3259
|
+
if (!PACKAGE_NAME_RE.test(value) && !COMPOSER_PACKAGE_RE.test(value) && !GO_MODULE_RE.test(value)) {
|
|
3260
|
+
throw new Error(`Invalid package identifier "${value}"`);
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
async function canonicalTarget(cwd, target, projectRoot) {
|
|
3265
|
+
const resolved = path6.isAbsolute(target) ? path6.resolve(target) : path6.resolve(cwd, target);
|
|
3266
|
+
let real;
|
|
3267
|
+
try {
|
|
3268
|
+
real = await fs3.realpath(resolved);
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
if (error.code !== "ENOENT") throw error;
|
|
3271
|
+
const parent = await fs3.realpath(path6.dirname(resolved));
|
|
3272
|
+
real = path6.join(parent, path6.basename(resolved));
|
|
3273
|
+
}
|
|
3274
|
+
if (!isInside2(real, projectRoot)) throw new Error(`target is outside project root: ${target}`);
|
|
3275
|
+
return real;
|
|
3276
|
+
}
|
|
3277
|
+
function isInside2(candidate, root) {
|
|
3278
|
+
const relative5 = path6.relative(path6.resolve(root), path6.resolve(candidate));
|
|
3279
|
+
return relative5 === "" || !relative5.startsWith("..") && !path6.isAbsolute(relative5);
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// src/languages/execute.ts
|
|
3283
|
+
async function validatePlanRealpaths(plan, projectRoot) {
|
|
3284
|
+
const realRoot = await fs4.realpath(projectRoot);
|
|
3285
|
+
let realCwd;
|
|
3286
|
+
try {
|
|
3287
|
+
realCwd = await fs4.realpath(plan.cwd);
|
|
3288
|
+
} catch (error) {
|
|
3289
|
+
return `Plan cwd is unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
3290
|
+
}
|
|
3291
|
+
if (!isRealInside(realCwd, realRoot)) return "Plan cwd resolves outside project root.";
|
|
3292
|
+
for (const argument of plan.args) {
|
|
3293
|
+
if (!path7.isAbsolute(argument)) continue;
|
|
3294
|
+
try {
|
|
3295
|
+
const realArgument = await fs4.realpath(argument);
|
|
3296
|
+
if (!isRealInside(realArgument, realRoot)) {
|
|
3297
|
+
return `Plan argument resolves outside project root: ${argument}`;
|
|
3298
|
+
}
|
|
3299
|
+
} catch (error) {
|
|
3300
|
+
if (error.code !== "ENOENT") {
|
|
3301
|
+
return `Plan argument could not be resolved safely: ${argument}`;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
return void 0;
|
|
3306
|
+
}
|
|
3307
|
+
function isRealInside(candidate, root) {
|
|
3308
|
+
const relative5 = path7.relative(root, candidate);
|
|
3309
|
+
return relative5 === "" || !relative5.startsWith("..") && !path7.isAbsolute(relative5);
|
|
3310
|
+
}
|
|
3311
|
+
async function* executePackagePlan(options) {
|
|
3312
|
+
const { plan, workspace } = options;
|
|
3313
|
+
const startedAt = Date.now();
|
|
3314
|
+
if (options.signal.aborted) {
|
|
3315
|
+
yield { type: "log", text: "Run cancelled before execution." };
|
|
3316
|
+
return terminalPackageResult(options, "cancelled", startedAt);
|
|
3317
|
+
}
|
|
3318
|
+
const profile = languageProfileRegistry.get(plan.profileId);
|
|
3319
|
+
if (!profile) {
|
|
3320
|
+
yield { type: "warning", text: `Unknown language profile: ${plan.profileId}` };
|
|
3321
|
+
return terminalPackageResult(options, "unavailable", startedAt, "Unknown language profile.");
|
|
3322
|
+
}
|
|
3323
|
+
if (workspace.id !== plan.workspaceId || workspace.language !== plan.profileId) {
|
|
3324
|
+
return terminalPackageResult(
|
|
3325
|
+
options,
|
|
3326
|
+
"unavailable",
|
|
3327
|
+
startedAt,
|
|
3328
|
+
"Plan and workspace identity do not match."
|
|
3329
|
+
);
|
|
3330
|
+
}
|
|
3331
|
+
const validation = validateCommandPlan(plan, profile, options.projectRoot);
|
|
3332
|
+
if (validation.length > 0) {
|
|
3333
|
+
return terminalPackageResult(
|
|
3334
|
+
options,
|
|
3335
|
+
"unavailable",
|
|
3336
|
+
startedAt,
|
|
3337
|
+
`Plan failed execution validation: ${validation.join("; ")}`
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3340
|
+
const containmentError = await validatePlanRealpaths(plan, options.projectRoot);
|
|
3341
|
+
if (containmentError) {
|
|
3342
|
+
return terminalPackageResult(options, "unavailable", startedAt, containmentError);
|
|
3343
|
+
}
|
|
3344
|
+
const manifestsBefore = await snapshotPaths(workspace.manifests);
|
|
3345
|
+
const lockfilePaths = collectLockfilePaths(workspace);
|
|
3346
|
+
const lockfilesBefore = await snapshotPaths(lockfilePaths);
|
|
3347
|
+
const manifestSizesBefore = await snapshotSizes(workspace.manifests);
|
|
3348
|
+
const lockfileSizesBefore = await snapshotSizes(lockfilePaths);
|
|
3349
|
+
yield {
|
|
3350
|
+
type: "log",
|
|
3351
|
+
text: plan.kind === "process" ? `${plan.command} ${plan.args.join(" ")}` : `${plan.parser}: ${plan.operation}`,
|
|
3352
|
+
data: {
|
|
3353
|
+
language: plan.profileId,
|
|
3354
|
+
operation: plan.operation,
|
|
3355
|
+
workspace: workspace.root,
|
|
3356
|
+
packages: [...options.packages]
|
|
3357
|
+
}
|
|
3358
|
+
};
|
|
3359
|
+
const timeoutController = new AbortController();
|
|
3360
|
+
const timer = setTimeout(
|
|
3361
|
+
() => timeoutController.abort(new Error("language package plan timed out")),
|
|
3362
|
+
plan.timeoutMs
|
|
3363
|
+
);
|
|
3364
|
+
timer.unref?.();
|
|
3365
|
+
const signal = AbortSignal.any([options.signal, timeoutController.signal]);
|
|
3366
|
+
let spawned;
|
|
3367
|
+
let run;
|
|
3368
|
+
let status = "unavailable";
|
|
3369
|
+
let error;
|
|
3370
|
+
try {
|
|
3371
|
+
if (plan.kind !== "process") {
|
|
3372
|
+
return terminalPackageResult(
|
|
3373
|
+
options,
|
|
3374
|
+
"unavailable",
|
|
3375
|
+
startedAt,
|
|
3376
|
+
"Package operations require an executable plan; internal plans cannot mutate the manifest."
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
const stream = spawnStream({
|
|
3380
|
+
cmd: plan.command,
|
|
3381
|
+
args: [...plan.args],
|
|
3382
|
+
cwd: plan.cwd,
|
|
3383
|
+
signal,
|
|
3384
|
+
maxBytes: plan.outputLimitBytes
|
|
3385
|
+
});
|
|
3386
|
+
for (; ; ) {
|
|
3387
|
+
const next = await stream.next();
|
|
3388
|
+
if (next.done) {
|
|
3389
|
+
spawned = next.value;
|
|
3390
|
+
break;
|
|
3391
|
+
}
|
|
3392
|
+
yield next.value;
|
|
3393
|
+
}
|
|
3394
|
+
} catch (error_) {
|
|
3395
|
+
error = error_ instanceof Error ? error_.message : String(error_);
|
|
3396
|
+
} finally {
|
|
3397
|
+
clearTimeout(timer);
|
|
3398
|
+
}
|
|
3399
|
+
if (spawned) {
|
|
3400
|
+
const parsed = parseLanguageDiagnostics(
|
|
3401
|
+
plan.parser,
|
|
3402
|
+
spawned.stdout,
|
|
3403
|
+
spawned.stderr,
|
|
3404
|
+
workspace.root
|
|
3405
|
+
);
|
|
3406
|
+
const timedOut = timeoutController.signal.aborted && !options.signal.aborted;
|
|
3407
|
+
const cancelled = options.signal.aborted;
|
|
3408
|
+
const spawnUnavailable = Boolean(
|
|
3409
|
+
spawned.error && /ENOENT|not found|cannot find/i.test(spawned.error)
|
|
3410
|
+
);
|
|
3411
|
+
status = timedOut ? "timed_out" : cancelled ? "cancelled" : spawnUnavailable ? "unavailable" : spawned.exitCode === 0 ? "passed" : "failed";
|
|
3412
|
+
error ??= spawned.error;
|
|
3413
|
+
const raw = [spawned.stdout, spawned.stderr, spawned.error].filter(Boolean).join("\n");
|
|
3414
|
+
run = {
|
|
3415
|
+
status,
|
|
3416
|
+
language: plan.profileId,
|
|
3417
|
+
workspace,
|
|
3418
|
+
plan,
|
|
3419
|
+
exitCode: spawned.exitCode,
|
|
3420
|
+
durationMs: Date.now() - startedAt,
|
|
3421
|
+
diagnostics: parsed.diagnostics,
|
|
3422
|
+
omittedDiagnostics: parsed.omitted,
|
|
3423
|
+
summary: parsed.summary,
|
|
3424
|
+
output: normalizeCommandOutput(raw, { maxBytes: plan.outputLimitBytes }),
|
|
3425
|
+
truncated: spawned.truncated,
|
|
3426
|
+
...spawned.spoolPath ? { spoolPath: spawned.spoolPath } : {},
|
|
3427
|
+
...spawned.error ? { error: spawned.error } : {}
|
|
3428
|
+
};
|
|
3429
|
+
} else {
|
|
3430
|
+
status = options.signal.aborted ? "cancelled" : timeoutController.signal.aborted ? "timed_out" : "failed";
|
|
3431
|
+
}
|
|
3432
|
+
if (status !== "passed") {
|
|
3433
|
+
return terminalPackageResult(
|
|
3434
|
+
options,
|
|
3435
|
+
status,
|
|
3436
|
+
startedAt,
|
|
3437
|
+
error,
|
|
3438
|
+
run,
|
|
3439
|
+
manifestsBefore,
|
|
3440
|
+
lockfilesBefore
|
|
3441
|
+
);
|
|
3442
|
+
}
|
|
3443
|
+
const manifestsAfter = await snapshotPaths(workspace.manifests);
|
|
3444
|
+
const lockfilesAfter = await snapshotPaths(lockfilePaths);
|
|
3445
|
+
const manifestSizesAfter = await snapshotSizes(workspace.manifests);
|
|
3446
|
+
const lockfileSizesAfter = await snapshotSizes(lockfilePaths);
|
|
3447
|
+
const manifestsChanged = await changedPaths(
|
|
3448
|
+
manifestsBefore,
|
|
3449
|
+
manifestsAfter,
|
|
3450
|
+
manifestSizesBefore,
|
|
3451
|
+
manifestSizesAfter
|
|
3452
|
+
);
|
|
3453
|
+
const lockfilesChanged = await changedPaths(
|
|
3454
|
+
lockfilesBefore,
|
|
3455
|
+
lockfilesAfter,
|
|
3456
|
+
lockfileSizesBefore,
|
|
3457
|
+
lockfileSizesAfter
|
|
3458
|
+
);
|
|
3459
|
+
const reports = parsePackageReports(plan.parser, spawned?.stdout ?? "", spawned?.stderr ?? "");
|
|
3460
|
+
const mutations = packageMutationsFromInputs(options.packages, reports.outdated);
|
|
3461
|
+
return {
|
|
3462
|
+
workspace,
|
|
3463
|
+
language: plan.profileId,
|
|
3464
|
+
operation: plan.operation,
|
|
3465
|
+
status,
|
|
3466
|
+
...run ? { run } : {},
|
|
3467
|
+
durationMs: Date.now() - startedAt,
|
|
3468
|
+
manifestsBefore,
|
|
3469
|
+
lockfilesBefore,
|
|
3470
|
+
manifestsAfter,
|
|
3471
|
+
lockfilesAfter,
|
|
3472
|
+
mutations,
|
|
3473
|
+
vulnerabilities: reports.vulnerabilities,
|
|
3474
|
+
outdated: reports.outdated,
|
|
3475
|
+
manifestsChanged: Object.freeze(manifestsChanged),
|
|
3476
|
+
lockfilesChanged: Object.freeze(lockfilesChanged)
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
function packageMutationsFromInputs(inputs, outdated) {
|
|
3480
|
+
const outdatedNames = new Set(outdated.map((entry) => entry.name));
|
|
3481
|
+
return inputs.filter((name) => !outdatedNames.has(name)).map((name) => ({ name, requested: name, kind: "runtime" }));
|
|
3482
|
+
}
|
|
3483
|
+
async function snapshotPaths(paths) {
|
|
3484
|
+
const existing = [];
|
|
3485
|
+
for (const candidate of paths) {
|
|
3486
|
+
try {
|
|
3487
|
+
const stat5 = await fs4.stat(candidate);
|
|
3488
|
+
if (!stat5.isFile()) continue;
|
|
3489
|
+
existing.push(candidate);
|
|
3490
|
+
} catch {
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
return Object.freeze(existing);
|
|
3494
|
+
}
|
|
3495
|
+
async function changedPaths(before, after, beforeSizes, afterSizes) {
|
|
3496
|
+
const beforeSet = new Set(before);
|
|
3497
|
+
const afterSet = new Set(after);
|
|
3498
|
+
const changed = /* @__PURE__ */ new Set();
|
|
3499
|
+
for (const path9 of after) {
|
|
3500
|
+
if (!beforeSet.has(path9)) changed.add(path9);
|
|
3501
|
+
}
|
|
3502
|
+
for (const path9 of before) {
|
|
3503
|
+
if (!afterSet.has(path9)) changed.add(path9);
|
|
3504
|
+
}
|
|
3505
|
+
if (beforeSizes && afterSizes) {
|
|
3506
|
+
for (const path9 of after) {
|
|
3507
|
+
if (beforeSizes.get(path9) !== afterSizes.get(path9)) changed.add(path9);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
return [...changed].sort();
|
|
3511
|
+
}
|
|
3512
|
+
async function snapshotSizes(paths) {
|
|
3513
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
3514
|
+
for (const candidate of paths) {
|
|
3515
|
+
try {
|
|
3516
|
+
const stat5 = await fs4.stat(candidate);
|
|
3517
|
+
if (stat5.isFile()) sizes.set(candidate, stat5.size);
|
|
3518
|
+
} catch {
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
return sizes;
|
|
3522
|
+
}
|
|
3523
|
+
function collectLockfilePaths(workspace) {
|
|
3524
|
+
const explicit = workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.path);
|
|
3525
|
+
const detected = LOCKFILE_NAMES_BY_PROFILE.get(workspace.language)?.map(
|
|
3526
|
+
(name) => path7.join(workspace.root, name)
|
|
3527
|
+
) ?? [];
|
|
3528
|
+
const merged = /* @__PURE__ */ new Set();
|
|
3529
|
+
for (const candidate of [...explicit, ...detected]) merged.add(candidate);
|
|
3530
|
+
return Object.freeze([...merged]);
|
|
3531
|
+
}
|
|
3532
|
+
var LOCKFILE_NAMES_BY_PROFILE = /* @__PURE__ */ new Map([
|
|
3533
|
+
["typescript", ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]],
|
|
3534
|
+
["javascript", ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]],
|
|
3535
|
+
["go", ["go.sum"]],
|
|
3536
|
+
["rust", ["Cargo.lock"]],
|
|
3537
|
+
["php", ["composer.lock"]],
|
|
3538
|
+
["csharp", ["packages.lock.json"]]
|
|
3539
|
+
]);
|
|
3540
|
+
function terminalPackageResult(options, status, startedAt, error, run, manifestsBefore = [], lockfilesBefore = []) {
|
|
3541
|
+
return {
|
|
3542
|
+
workspace: options.workspace,
|
|
3543
|
+
language: options.plan.profileId,
|
|
3544
|
+
operation: options.plan.operation,
|
|
3545
|
+
status,
|
|
3546
|
+
...run ? { run } : {},
|
|
3547
|
+
durationMs: Date.now() - startedAt,
|
|
3548
|
+
manifestsBefore,
|
|
3549
|
+
lockfilesBefore,
|
|
3550
|
+
manifestsAfter: manifestsBefore,
|
|
3551
|
+
lockfilesAfter: lockfilesBefore,
|
|
3552
|
+
manifestsChanged: Object.freeze([]),
|
|
3553
|
+
lockfilesChanged: Object.freeze([]),
|
|
3554
|
+
mutations: Object.freeze([]),
|
|
3555
|
+
vulnerabilities: Object.freeze([]),
|
|
3556
|
+
outdated: Object.freeze([]),
|
|
3557
|
+
...error ? { error } : {}
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
// src/languages/legacy-bridge.ts
|
|
3562
|
+
var NON_JS_MARKERS = [
|
|
3563
|
+
{ filename: "go.mod", language: "go" },
|
|
3564
|
+
{ filename: "go.work", language: "go" },
|
|
3565
|
+
{ filename: "Cargo.toml", language: "rust" },
|
|
3566
|
+
{ filename: "composer.json", language: "php" }
|
|
3567
|
+
];
|
|
3568
|
+
var NON_JS_SUFFIXES = [
|
|
3569
|
+
{ suffix: ".csproj", language: "csharp" },
|
|
3570
|
+
{ suffix: ".fsproj", language: "csharp" }
|
|
3571
|
+
];
|
|
3572
|
+
async function detectNonJsEcosystem(cwd, projectRoot) {
|
|
3573
|
+
try {
|
|
3574
|
+
let dir = path8.resolve(cwd);
|
|
3575
|
+
const root = path8.resolve(projectRoot);
|
|
3576
|
+
for (let depth = 0; depth <= 4 && dir.startsWith(root); depth++) {
|
|
3577
|
+
for (const marker of NON_JS_MARKERS) {
|
|
3578
|
+
try {
|
|
3579
|
+
const s = await fs5.stat(path8.join(dir, marker.filename));
|
|
3580
|
+
if (s.isFile()) return marker.language;
|
|
3581
|
+
} catch {
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
try {
|
|
3585
|
+
const entries = await fs5.readdir(dir);
|
|
3586
|
+
for (const entry of entries) {
|
|
3587
|
+
for (const suffix of NON_JS_SUFFIXES) {
|
|
3588
|
+
if (entry.toLowerCase().endsWith(suffix.suffix)) return suffix.language;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
} catch {
|
|
3592
|
+
}
|
|
3593
|
+
if (dir === root) break;
|
|
3594
|
+
dir = path8.dirname(dir);
|
|
3595
|
+
}
|
|
3596
|
+
} catch {
|
|
3597
|
+
}
|
|
3598
|
+
return null;
|
|
3599
|
+
}
|
|
3600
|
+
async function tryLegacyPackageOperation(operation, ctx, packages = []) {
|
|
3601
|
+
const language = await detectNonJsEcosystem(ctx.cwd, ctx.projectRoot);
|
|
3602
|
+
if (!language) return null;
|
|
3603
|
+
const planResult = await planLanguageOperation({
|
|
3604
|
+
projectRoot: ctx.projectRoot,
|
|
3605
|
+
cwd: ctx.cwd,
|
|
3606
|
+
operation,
|
|
3607
|
+
language,
|
|
3608
|
+
signal: ctx.signal,
|
|
3609
|
+
...packages.length > 0 ? { operationOptions: { packages: [...packages] } } : {}
|
|
3610
|
+
});
|
|
3611
|
+
if (planResult.status !== "planned") return null;
|
|
3612
|
+
const runner = executePackagePlan({
|
|
3613
|
+
projectRoot: ctx.projectRoot,
|
|
3614
|
+
workspace: planResult.workspace,
|
|
3615
|
+
plan: planResult.plan,
|
|
3616
|
+
packages,
|
|
3617
|
+
signal: ctx.signal
|
|
3618
|
+
});
|
|
3619
|
+
for (; ; ) {
|
|
3620
|
+
const next = await runner.next();
|
|
3621
|
+
if (next.done) return { language, outcome: next.value };
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
|
|
1005
3625
|
// src/install.ts
|
|
1006
3626
|
var installTool = {
|
|
1007
3627
|
name: "install",
|
|
@@ -1056,6 +3676,35 @@ var installTool = {
|
|
|
1056
3676
|
},
|
|
1057
3677
|
async *executeStream(input, ctx, opts) {
|
|
1058
3678
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
3679
|
+
if (!input.global) {
|
|
3680
|
+
const pkgList2 = input.packages ? Array.isArray(input.packages) ? input.packages : input.packages.split(",").map((p) => p.trim()) : [];
|
|
3681
|
+
const bridge = await tryLegacyPackageOperation(
|
|
3682
|
+
"package-install",
|
|
3683
|
+
{
|
|
3684
|
+
cwd,
|
|
3685
|
+
projectRoot: ctx.projectRoot,
|
|
3686
|
+
signal: opts.signal
|
|
3687
|
+
},
|
|
3688
|
+
pkgList2
|
|
3689
|
+
);
|
|
3690
|
+
if (bridge?.outcome) {
|
|
3691
|
+
const outcome = bridge.outcome;
|
|
3692
|
+
const run = outcome.run;
|
|
3693
|
+
yield {
|
|
3694
|
+
type: "final",
|
|
3695
|
+
output: {
|
|
3696
|
+
exit_code: run?.exitCode ?? 0,
|
|
3697
|
+
packages: [],
|
|
3698
|
+
output: normalizeCommandOutput(
|
|
3699
|
+
run?.output || outcome.manifestsChanged.join(", ") || "ok"
|
|
3700
|
+
),
|
|
3701
|
+
truncated: run?.truncated ?? false,
|
|
3702
|
+
dry_run: input.dry_run ?? false
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
1059
3708
|
const pkgManager = await detectPackageManager(cwd);
|
|
1060
3709
|
yield { type: "log", text: `Resolving with ${pkgManager}\u2026`, data: { phase: "resolve" } };
|
|
1061
3710
|
const save = input.save === "dev" ? "-D" : input.save === "optional" ? "-O" : "";
|
|
@@ -1149,13 +3798,13 @@ function resolveManifestPath(cwd, pkgManager) {
|
|
|
1149
3798
|
case "pnpm":
|
|
1150
3799
|
case "yarn":
|
|
1151
3800
|
case "npm":
|
|
1152
|
-
return
|
|
3801
|
+
return join7(cwd, "package.json");
|
|
1153
3802
|
/* v8 ignore next 2 -- pkgManager is always pnpm/yarn/npm; the default is defensive. */
|
|
1154
3803
|
default:
|
|
1155
|
-
return
|
|
3804
|
+
return join7(cwd, "package.json");
|
|
1156
3805
|
}
|
|
1157
3806
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
3807
|
+
export {
|
|
3808
|
+
installTool
|
|
3809
|
+
};
|
|
1160
3810
|
//# sourceMappingURL=install.js.map
|
|
1161
|
-
//# sourceMappingURL=install.js.map
|