@wrongstack/plugins 0.295.0 → 0.296.2
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/accessibility-auditor/index.d.ts.map +1 -1
- package/dist/accessibility-auditor.js +31 -3
- package/dist/agent-handoff/index.d.ts.map +1 -1
- package/dist/agent-handoff.js +30 -1
- package/dist/auto-doc/index.d.ts.map +1 -1
- package/dist/auto-doc.js +3 -1
- package/dist/auto-i18n-extractor.js +5 -0
- package/dist/branch-guard/index.d.ts.map +1 -1
- package/dist/branch-guard.js +116 -3
- package/dist/changelog-writer/index.d.ts.map +1 -1
- package/dist/changelog-writer.js +137 -3
- package/dist/checkpoint/index.d.ts +2 -0
- package/dist/checkpoint/index.d.ts.map +1 -1
- package/dist/checkpoint.js +34 -8
- package/dist/code-metrics.js +5 -0
- package/dist/commit-validator/index.d.ts.map +1 -1
- package/dist/commit-validator.js +35 -8
- package/dist/context-pins/index.d.ts.map +1 -1
- package/dist/context-pins.js +6 -5
- package/dist/cost-tracker/index.d.ts.map +1 -1
- package/dist/cost-tracker.js +101 -1
- package/dist/cron/index.d.ts.map +1 -1
- package/dist/cron.js +1 -0
- package/dist/dead-code-detector/index.d.ts.map +1 -1
- package/dist/dead-code-detector.js +9 -1
- package/dist/dependency-vulnerability-gate/index.d.ts.map +1 -1
- package/dist/dependency-vulnerability-gate.js +43 -8
- package/dist/diff-summary/index.d.ts.map +1 -1
- package/dist/diff-summary.js +112 -2
- package/dist/doc-sync-guard.js +17 -1
- package/dist/duplicate-code-detector/index.d.ts.map +1 -1
- package/dist/duplicate-code-detector.js +102 -2
- package/dist/feature-flag-tracker/index.d.ts.map +1 -1
- package/dist/feature-flag-tracker.js +30 -4
- package/dist/file-watcher/index.d.ts.map +1 -1
- package/dist/file-watcher.js +6 -7
- package/dist/format-on-save/index.d.ts +2 -0
- package/dist/format-on-save/index.d.ts.map +1 -1
- package/dist/format-on-save.js +288 -59
- package/dist/git-autocommit/index.d.ts +12 -0
- package/dist/git-autocommit/index.d.ts.map +1 -1
- package/dist/git-autocommit.js +58 -4
- package/dist/import-organizer/index.d.ts.map +1 -1
- package/dist/import-organizer.js +152 -21
- package/dist/index.js +1413 -590
- package/dist/injection-shield/index.d.ts +16 -0
- package/dist/injection-shield/index.d.ts.map +1 -1
- package/dist/injection-shield.js +12 -4
- package/dist/interface-contract-guard/index.d.ts.map +1 -1
- package/dist/interface-contract-guard.js +60 -26
- package/dist/knowledge-graph/index.d.ts.map +1 -1
- package/dist/knowledge-graph.js +7 -6
- package/dist/lint-gate/index.d.ts.map +1 -1
- package/dist/lint-gate.js +115 -2
- package/dist/loop-breaker/index.d.ts.map +1 -1
- package/dist/loop-breaker.js +15 -5
- package/dist/migration-planner.js +15 -1
- package/dist/notify-hub/index.d.ts.map +1 -1
- package/dist/notify-hub.js +41 -4
- package/dist/pr-drafter/index.d.ts.map +1 -1
- package/dist/pr-drafter.js +20 -1
- package/dist/prompt-firewall/index.d.ts.map +1 -1
- package/dist/prompt-firewall.js +140 -11
- package/dist/refactor-suggester/index.d.ts.map +1 -1
- package/dist/refactor-suggester.js +119 -4
- package/dist/runtime/bounded-map.d.ts +86 -0
- package/dist/runtime/bounded-map.d.ts.map +1 -0
- package/dist/runtime/credential-patterns.d.ts +42 -0
- package/dist/runtime/credential-patterns.d.ts.map +1 -0
- package/dist/runtime/handles.d.ts +46 -0
- package/dist/runtime/handles.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/local-bin.d.ts +120 -0
- package/dist/runtime/local-bin.d.ts.map +1 -0
- package/dist/runtime/safe-json.d.ts +25 -0
- package/dist/runtime/safe-json.d.ts.map +1 -0
- package/dist/runtime.js +309 -22
- package/dist/schema-evolution-guard.js +5 -0
- package/dist/secret-scanner/index.d.ts.map +1 -1
- package/dist/secret-scanner.js +194 -50
- package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
- package/dist/security-hotspot-scanner.js +132 -1
- package/dist/semantic-search-indexer/index.d.ts.map +1 -1
- package/dist/semantic-search-indexer.js +7 -1
- package/dist/semver-bump/index.d.ts.map +1 -1
- package/dist/semver-bump.js +21 -2
- package/dist/session-recap/index.d.ts.map +1 -1
- package/dist/session-recap.js +18 -1
- package/dist/smart-rename/index.d.ts +2 -0
- package/dist/smart-rename/index.d.ts.map +1 -1
- package/dist/smart-rename.js +16 -2
- package/dist/spec-linker/index.d.ts.map +1 -1
- package/dist/spec-linker.js +19 -2
- package/dist/template-engine/index.d.ts.map +1 -1
- package/dist/template-engine.js +45 -3
- package/dist/test-coverage-gate.js +17 -1
- package/dist/test-runner-gate/index.d.ts.map +1 -1
- package/dist/test-runner-gate.js +142 -9
- package/dist/todo-listener/index.d.ts.map +1 -1
- package/dist/todo-listener.js +42 -2
- package/dist/todo-tracker/index.d.ts.map +1 -1
- package/dist/todo-tracker.js +3 -4
- package/dist/token-budget/index.d.ts.map +1 -1
- package/dist/token-budget.js +31 -19
- package/dist/token-throttle/index.d.ts.map +1 -1
- package/dist/token-throttle.js +17 -2
- package/dist/type-gate/index.d.ts.map +1 -1
- package/dist/type-gate.js +108 -17
- package/package.json +3 -3
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wrongstack/plugins — project-local binary resolution.
|
|
3
|
+
*
|
|
4
|
+
* Why this module exists
|
|
5
|
+
* ----------------------
|
|
6
|
+
* Several plugins need to run a Node CLI that ships with the project
|
|
7
|
+
* (`biome`, `eslint`, `tsc`, `vitest`, …). The obvious spelling —
|
|
8
|
+
* `execFile('npx', ['biome', …])` — is wrong in three separate ways:
|
|
9
|
+
*
|
|
10
|
+
* 1. **It does not run on Windows at all.** `npx`/`pnpm`/`npm` are
|
|
11
|
+
* `.cmd` shims there, and `execFile`/`spawn` without a shell do not
|
|
12
|
+
* consult `PATHEXT`. The call fails `ENOENT`, plugins swallow the
|
|
13
|
+
* error as "tool not installed", and the feature silently no-ops on
|
|
14
|
+
* every Windows machine.
|
|
15
|
+
* 2. **It is slow.** `npx` re-resolves the package on each invocation,
|
|
16
|
+
* and for a missing package it may try to *download* one — on a hook
|
|
17
|
+
* that fires after every write.
|
|
18
|
+
* 3. **It is a weaker sandbox.** The shim is a shell script; arguments
|
|
19
|
+
* cross a `cmd.exe` boundary on Windows (the BatBadBut class).
|
|
20
|
+
*
|
|
21
|
+
* `lint-gate` and `test-flake-detector` already solved this locally by
|
|
22
|
+
* resolving the package's `bin` entry through `createRequire` and running
|
|
23
|
+
* it as `process.execPath <entry>`. That is the correct pattern: no shell,
|
|
24
|
+
* no shim, no network, identical on every platform. This module promotes
|
|
25
|
+
* that proven approach to a shared helper so every plugin gets it.
|
|
26
|
+
*
|
|
27
|
+
* Fallback: when the package is genuinely absent from the project,
|
|
28
|
+
* `resolveNodeBin` returns `null` and the caller decides whether to
|
|
29
|
+
* degrade gracefully or fall back to a PATH lookup via
|
|
30
|
+
* `resolveWin32Command` (exported here for that purpose).
|
|
31
|
+
*/
|
|
32
|
+
import { resolveWin32Command } from '@wrongstack/tools/win32';
|
|
33
|
+
export { resolveWin32Command };
|
|
34
|
+
/** A spawn-ready invocation, already adjusted for the host platform. */
|
|
35
|
+
export interface ExecInvocation {
|
|
36
|
+
cmd: string;
|
|
37
|
+
args: string[];
|
|
38
|
+
/** Only ever true on the Windows `.cmd`/`.bat` shim path. */
|
|
39
|
+
windowsVerbatimArguments: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Turn a `(command, args)` pair into something `execFile`/`spawn` can
|
|
43
|
+
* actually launch on this platform.
|
|
44
|
+
*
|
|
45
|
+
* On Windows, `npx`/`npm`/`pnpm`/`biome`/`tsc`/… ship as `.cmd` wrappers.
|
|
46
|
+
* `execFile` and `spawn` without a shell ignore `PATHEXT`, so a bare
|
|
47
|
+
* `execFile('npx', …)` fails `ENOENT` — and because every plugin treats a
|
|
48
|
+
* spawn failure as "tool not installed", the feature silently no-ops on
|
|
49
|
+
* every Windows machine. This resolves the real path first and, for a
|
|
50
|
+
* `.cmd`/`.bat` shim, routes through `cmd.exe` with per-argument quoting
|
|
51
|
+
* and a metacharacter guard (the BatBadBut argument-injection class), so
|
|
52
|
+
* a dynamic path argument still cannot chain a second command.
|
|
53
|
+
*
|
|
54
|
+
* On non-Windows, and for real `.exe` binaries, this is a passthrough.
|
|
55
|
+
*
|
|
56
|
+
* Throws when an argument carries a `cmd.exe` metacharacter on the shim
|
|
57
|
+
* path — callers should treat that as "skip", never as "run anyway".
|
|
58
|
+
*
|
|
59
|
+
* Prefer {@link resolveNodeBin} when the target is a Node CLI that the
|
|
60
|
+
* project depends on: `node <bin-entry>` needs no shim at all.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveExecInvocation(command: string, args?: readonly string[]): ExecInvocation;
|
|
63
|
+
/**
|
|
64
|
+
* Locate `cmd` on `PATH`, or return `null` if it is not there.
|
|
65
|
+
*
|
|
66
|
+
* This is the existence check `resolveWin32Command` deliberately does not
|
|
67
|
+
* provide: that function returns its input unchanged when nothing matches,
|
|
68
|
+
* so a caller cannot distinguish "found `biome`" from "gave up and handed
|
|
69
|
+
* back the string `biome`". Treating the passthrough as success makes a
|
|
70
|
+
* missing tool look installed — the caller then spawns it, gets `ENOENT`,
|
|
71
|
+
* and (because plugins read a spawn failure as "not installed") silently
|
|
72
|
+
* does nothing while reporting itself healthy.
|
|
73
|
+
*
|
|
74
|
+
* Returns the resolved path on success. Walks `PATH` directly, applying
|
|
75
|
+
* `PATHEXT` suffixes on Windows for a bare name and checking the execute
|
|
76
|
+
* bit on POSIX.
|
|
77
|
+
*/
|
|
78
|
+
export declare function findOnPath(cmd: string): string | null;
|
|
79
|
+
/** A resolved, directly-spawnable invocation. Never a shell or a shim. */
|
|
80
|
+
export interface ResolvedNodeBin {
|
|
81
|
+
/** Always `process.execPath` — the Node binary running this process. */
|
|
82
|
+
cmd: string;
|
|
83
|
+
/** `[binEntryPath, ...extraArgs]`. */
|
|
84
|
+
args: string[];
|
|
85
|
+
/** Absolute path of the package's bin entry, for diagnostics. */
|
|
86
|
+
entry: string;
|
|
87
|
+
}
|
|
88
|
+
/** Drop every cached resolution. Call from `teardown()`. */
|
|
89
|
+
export declare function clearLocalBinCache(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve a project-local Node CLI to a `process.execPath <entry>`
|
|
92
|
+
* invocation.
|
|
93
|
+
*
|
|
94
|
+
* @param packageName npm package to resolve, e.g. `@biomejs/biome`.
|
|
95
|
+
* @param binName which `bin` key to prefer when the package declares
|
|
96
|
+
* several. Falls back to the sole/first entry.
|
|
97
|
+
* @param cwd project root whose `package.json` anchors resolution.
|
|
98
|
+
* @param extraArgs arguments appended after the bin entry.
|
|
99
|
+
*
|
|
100
|
+
* Returns `null` when the package is not installed, declares no `bin`, or
|
|
101
|
+
* the declared entry escapes its own package directory (a tampered
|
|
102
|
+
* `package.json` must not become an arbitrary-file execution primitive).
|
|
103
|
+
*/
|
|
104
|
+
export declare function resolveNodeBin(packageName: string, binName: string, cwd: string, extraArgs?: readonly string[]): ResolvedNodeBin | null;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the first installed package from `candidates`.
|
|
107
|
+
*
|
|
108
|
+
* Used by plugins that accept several equivalent tools (biome *or*
|
|
109
|
+
* eslint, vitest *or* jest) and want the first one the project actually
|
|
110
|
+
* has, without probing each with a subprocess.
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolveFirstNodeBin(candidates: readonly {
|
|
113
|
+
packageName: string;
|
|
114
|
+
binName: string;
|
|
115
|
+
args?: readonly string[];
|
|
116
|
+
}[], cwd: string): (ResolvedNodeBin & {
|
|
117
|
+
packageName: string;
|
|
118
|
+
binName: string;
|
|
119
|
+
}) | null;
|
|
120
|
+
//# sourceMappingURL=local-bin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-bin.d.ts","sourceRoot":"","sources":["../../src/runtime/local-bin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,EAA+B,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE3F,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,6DAA6D;IAC7D,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,SAAS,MAAM,EAAO,GAC3B,cAAc,CAWhB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoCrD;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;CACf;AAqCD,4DAA4D;AAC5D,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,SAAS,MAAM,EAAO,GAChC,eAAe,GAAG,IAAI,CAwCxB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,EACzF,GAAG,EAAE,MAAM,GACV,CAAC,eAAe,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAMrE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wrongstack/plugins — serialisation that cannot take the process down.
|
|
3
|
+
*
|
|
4
|
+
* Plugins routinely stringify values they did not construct: event-bus
|
|
5
|
+
* payloads, tool results, mailbox bodies. `JSON.stringify` throws on a
|
|
6
|
+
* circular reference and on `BigInt`, and several of these call sites sit
|
|
7
|
+
* inside detached (fire-and-forget) work where a throw becomes an
|
|
8
|
+
* unhandled rejection rather than a handled failure.
|
|
9
|
+
*
|
|
10
|
+
* `safeJsonStringify` never throws. A value it cannot represent is
|
|
11
|
+
* replaced with a marker, so the surrounding feature degrades to "this
|
|
12
|
+
* field is unreadable" instead of failing — or crashing — outright.
|
|
13
|
+
*/
|
|
14
|
+
/** Placeholder substituted for a value that cannot be serialised. */
|
|
15
|
+
export declare const UNSERIALIZABLE = "[unserializable]";
|
|
16
|
+
/**
|
|
17
|
+
* `JSON.stringify` with cycle handling and a total-failure fallback.
|
|
18
|
+
*
|
|
19
|
+
* @param value anything
|
|
20
|
+
* @param indent passed through to `JSON.stringify` (e.g. `2` to pretty-print)
|
|
21
|
+
* @returns a JSON string, or a marker string when the value resists
|
|
22
|
+
* serialisation entirely. Never throws.
|
|
23
|
+
*/
|
|
24
|
+
export declare function safeJsonStringify(value: unknown, indent?: number): string;
|
|
25
|
+
//# sourceMappingURL=safe-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-json.d.ts","sourceRoot":"","sources":["../../src/runtime/safe-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qEAAqE;AACrE,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAEjD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAyBzE"}
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/runtime/index.ts
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
4
|
-
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import { basename, extname as extname2, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
5
5
|
|
|
6
6
|
// src/runtime/llm.ts
|
|
7
7
|
function stripOuterMarkdownFence(text) {
|
|
@@ -49,6 +49,265 @@ async function runOptionalPluginLlm(request) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/runtime/local-bin.ts
|
|
53
|
+
import { createRequire } from "node:module";
|
|
54
|
+
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
55
|
+
import { accessSync, constants, readFileSync } from "node:fs";
|
|
56
|
+
import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
|
|
57
|
+
function resolveExecInvocation(command, args = []) {
|
|
58
|
+
const resolved = resolveWin32Command(command);
|
|
59
|
+
const normalizedResolved = resolved.toLowerCase();
|
|
60
|
+
const needsShell = process.platform === "win32" && (normalizedResolved.endsWith(".cmd") || normalizedResolved.endsWith(".bat"));
|
|
61
|
+
if (needsShell) {
|
|
62
|
+
const shim = buildWin32CmdShimInvocation(resolved, args);
|
|
63
|
+
return { cmd: shim.command, args: shim.args, windowsVerbatimArguments: true };
|
|
64
|
+
}
|
|
65
|
+
return { cmd: resolved, args: [...args], windowsVerbatimArguments: false };
|
|
66
|
+
}
|
|
67
|
+
function findOnPath(cmd) {
|
|
68
|
+
if (!cmd) return null;
|
|
69
|
+
const exists = (p) => {
|
|
70
|
+
try {
|
|
71
|
+
accessSync(p, constants.X_OK);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
78
|
+
return exists(cmd) ? resolve(cmd) : null;
|
|
79
|
+
}
|
|
80
|
+
const suffixes = process.platform === "win32" && extname(cmd) === "" ? (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
81
|
+
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
82
|
+
if (!dir) continue;
|
|
83
|
+
const base = join(dir, cmd);
|
|
84
|
+
for (const suffix of suffixes) {
|
|
85
|
+
const candidate = `${base}${suffix}`;
|
|
86
|
+
if (exists(candidate)) return candidate;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function isInside(parent, candidate) {
|
|
92
|
+
const rel = relative(parent, candidate);
|
|
93
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
|
|
94
|
+
}
|
|
95
|
+
var binCache = /* @__PURE__ */ new Map();
|
|
96
|
+
var BIN_CACHE_MAX = 64;
|
|
97
|
+
var NEGATIVE_BIN_CACHE_TTL_MS = 5e3;
|
|
98
|
+
function cachePut(key, value) {
|
|
99
|
+
while (binCache.size >= BIN_CACHE_MAX) {
|
|
100
|
+
const oldest = binCache.keys().next().value;
|
|
101
|
+
if (oldest === void 0) break;
|
|
102
|
+
binCache.delete(oldest);
|
|
103
|
+
}
|
|
104
|
+
binCache.set(key, { value, cachedAt: Date.now() });
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
function clearLocalBinCache() {
|
|
108
|
+
binCache.clear();
|
|
109
|
+
}
|
|
110
|
+
function resolveNodeBin(packageName, binName, cwd, extraArgs = []) {
|
|
111
|
+
const key = `${packageName}|${binName}|${cwd}`;
|
|
112
|
+
const cached = binCache.get(key);
|
|
113
|
+
if (cached !== void 0) {
|
|
114
|
+
if (cached.value !== null || Date.now() - cached.cachedAt < NEGATIVE_BIN_CACHE_TTL_MS) {
|
|
115
|
+
return cached.value === null ? null : { ...cached.value, args: [cached.value.entry, ...extraArgs] };
|
|
116
|
+
}
|
|
117
|
+
binCache.delete(key);
|
|
118
|
+
}
|
|
119
|
+
let resolved = null;
|
|
120
|
+
try {
|
|
121
|
+
const requireFromProject = createRequire(resolve(cwd, "package.json"));
|
|
122
|
+
const packagePath = requireFromProject.resolve(`${packageName}/package.json`);
|
|
123
|
+
const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
124
|
+
const relativeBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.[binName] ?? Object.values(packageJson.bin ?? {})[0];
|
|
125
|
+
if (relativeBin && !isAbsolute(relativeBin)) {
|
|
126
|
+
const packageDir = dirname(packagePath);
|
|
127
|
+
const entry = resolve(packageDir, relativeBin);
|
|
128
|
+
if (isInside(packageDir, entry)) {
|
|
129
|
+
resolved = { cmd: process.execPath, args: [entry], entry };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
resolved = null;
|
|
134
|
+
}
|
|
135
|
+
cachePut(key, resolved);
|
|
136
|
+
return resolved === null ? null : { ...resolved, args: [resolved.entry, ...extraArgs] };
|
|
137
|
+
}
|
|
138
|
+
function resolveFirstNodeBin(candidates, cwd) {
|
|
139
|
+
for (const c of candidates) {
|
|
140
|
+
const hit = resolveNodeBin(c.packageName, c.binName, cwd, c.args ?? []);
|
|
141
|
+
if (hit) return { ...hit, packageName: c.packageName, binName: c.binName };
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/runtime/bounded-map.ts
|
|
147
|
+
var BoundedMap = class {
|
|
148
|
+
map = /* @__PURE__ */ new Map();
|
|
149
|
+
max;
|
|
150
|
+
ttlMs;
|
|
151
|
+
now;
|
|
152
|
+
/** Entries dropped to stay under `max`. Surfaced by plugin health(). */
|
|
153
|
+
evictions = 0;
|
|
154
|
+
constructor(options) {
|
|
155
|
+
const normalizedMax = Math.floor(options.max);
|
|
156
|
+
this.max = Number.isSafeInteger(normalizedMax) ? Math.max(1, normalizedMax) : 1;
|
|
157
|
+
this.ttlMs = options.ttlMs;
|
|
158
|
+
this.now = options.now ?? Date.now;
|
|
159
|
+
}
|
|
160
|
+
expired(entry) {
|
|
161
|
+
return this.ttlMs !== void 0 && this.now() - entry.storedAt > this.ttlMs;
|
|
162
|
+
}
|
|
163
|
+
get(key) {
|
|
164
|
+
const entry = this.map.get(key);
|
|
165
|
+
if (entry === void 0) return void 0;
|
|
166
|
+
if (this.expired(entry)) {
|
|
167
|
+
this.map.delete(key);
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
this.map.delete(key);
|
|
171
|
+
this.map.set(key, entry);
|
|
172
|
+
return entry.value;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Read without promoting the key to most-recently-used. Use for
|
|
176
|
+
* diagnostics that must not perturb the eviction order.
|
|
177
|
+
*/
|
|
178
|
+
peek(key) {
|
|
179
|
+
const entry = this.map.get(key);
|
|
180
|
+
if (entry === void 0 || this.expired(entry)) return void 0;
|
|
181
|
+
return entry.value;
|
|
182
|
+
}
|
|
183
|
+
has(key) {
|
|
184
|
+
const entry = this.map.get(key);
|
|
185
|
+
if (entry === void 0) return false;
|
|
186
|
+
if (this.expired(entry)) {
|
|
187
|
+
this.map.delete(key);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
set(key, value) {
|
|
193
|
+
this.map.delete(key);
|
|
194
|
+
this.map.set(key, { value, storedAt: this.now() });
|
|
195
|
+
while (this.map.size > this.max) {
|
|
196
|
+
const coldest = this.map.keys().next().value;
|
|
197
|
+
if (coldest === void 0) break;
|
|
198
|
+
this.map.delete(coldest);
|
|
199
|
+
this.evictions += 1;
|
|
200
|
+
}
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
delete(key) {
|
|
204
|
+
return this.map.delete(key);
|
|
205
|
+
}
|
|
206
|
+
clear() {
|
|
207
|
+
this.map.clear();
|
|
208
|
+
this.evictions = 0;
|
|
209
|
+
}
|
|
210
|
+
get size() {
|
|
211
|
+
return this.map.size;
|
|
212
|
+
}
|
|
213
|
+
/** How many entries have been dropped to respect `max`, since the last clear. */
|
|
214
|
+
get evictionCount() {
|
|
215
|
+
return this.evictions;
|
|
216
|
+
}
|
|
217
|
+
/** Drop every expired entry. Cheap enough to call from a status tool. */
|
|
218
|
+
prune() {
|
|
219
|
+
if (this.ttlMs === void 0) return 0;
|
|
220
|
+
let removed = 0;
|
|
221
|
+
for (const [key, entry] of this.map) {
|
|
222
|
+
if (this.expired(entry)) {
|
|
223
|
+
this.map.delete(key);
|
|
224
|
+
removed += 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return removed;
|
|
228
|
+
}
|
|
229
|
+
/** Live (non-expired) entries, coldest first. */
|
|
230
|
+
*entries() {
|
|
231
|
+
for (const [key, entry] of this.map) {
|
|
232
|
+
if (!this.expired(entry)) yield [key, entry.value];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
[Symbol.iterator]() {
|
|
236
|
+
return this.entries();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var BoundedSet = class {
|
|
240
|
+
inner;
|
|
241
|
+
constructor(options) {
|
|
242
|
+
this.inner = new BoundedMap(options);
|
|
243
|
+
}
|
|
244
|
+
has(value) {
|
|
245
|
+
return this.inner.has(value);
|
|
246
|
+
}
|
|
247
|
+
add(value) {
|
|
248
|
+
this.inner.set(value, true);
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
delete(value) {
|
|
252
|
+
return this.inner.delete(value);
|
|
253
|
+
}
|
|
254
|
+
clear() {
|
|
255
|
+
this.inner.clear();
|
|
256
|
+
}
|
|
257
|
+
get size() {
|
|
258
|
+
return this.inner.size;
|
|
259
|
+
}
|
|
260
|
+
/** How many entries have been dropped to respect `max`, since the last clear. */
|
|
261
|
+
get evictionCount() {
|
|
262
|
+
return this.inner.evictionCount;
|
|
263
|
+
}
|
|
264
|
+
*values() {
|
|
265
|
+
for (const [key] of this.inner) yield key;
|
|
266
|
+
}
|
|
267
|
+
[Symbol.iterator]() {
|
|
268
|
+
return this.values();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/runtime/safe-json.ts
|
|
273
|
+
var UNSERIALIZABLE = "[unserializable]";
|
|
274
|
+
function safeJsonStringify(value, indent) {
|
|
275
|
+
try {
|
|
276
|
+
const stack = [];
|
|
277
|
+
const out = JSON.stringify(
|
|
278
|
+
value,
|
|
279
|
+
function replacer(_key, val) {
|
|
280
|
+
if (typeof val === "bigint") return `${val.toString()}n`;
|
|
281
|
+
if (val === null || typeof val !== "object") return val;
|
|
282
|
+
while (stack.length > 0 && stack[stack.length - 1] !== this) stack.pop();
|
|
283
|
+
if (stack.includes(val)) return "[circular]";
|
|
284
|
+
stack.push(val);
|
|
285
|
+
return val;
|
|
286
|
+
},
|
|
287
|
+
indent
|
|
288
|
+
);
|
|
289
|
+
return out ?? String(value);
|
|
290
|
+
} catch {
|
|
291
|
+
return UNSERIALIZABLE;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/runtime/handles.ts
|
|
296
|
+
function releaseHandle(off) {
|
|
297
|
+
if (off) {
|
|
298
|
+
try {
|
|
299
|
+
off();
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
function releaseHandles(state, keys) {
|
|
306
|
+
for (const key of keys) {
|
|
307
|
+
state[key] = releaseHandle(state[key]);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
52
311
|
// src/runtime/index.ts
|
|
53
312
|
var META_CHARS = /["'`;&|<>\r\n]/;
|
|
54
313
|
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
@@ -63,9 +322,9 @@ function safeSplit(command) {
|
|
|
63
322
|
function withinProjectPath(projectRoot, candidate) {
|
|
64
323
|
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
65
324
|
if (hasLeadingDash(candidate)) return false;
|
|
66
|
-
const resolved =
|
|
67
|
-
const rel =
|
|
68
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
325
|
+
const resolved = isAbsolute2(candidate) ? resolve2(candidate) : resolve2(projectRoot, candidate);
|
|
326
|
+
const rel = relative2(projectRoot, resolved);
|
|
327
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
|
|
69
328
|
}
|
|
70
329
|
function everyFlagAllowed(allowed, args) {
|
|
71
330
|
for (const arg of args) {
|
|
@@ -77,14 +336,14 @@ function everyFlagAllowed(allowed, args) {
|
|
|
77
336
|
}
|
|
78
337
|
function sanitizeRunnerPath(value, options = {}) {
|
|
79
338
|
if (!value || hasLeadingDash(value)) return null;
|
|
80
|
-
const projectRoot =
|
|
339
|
+
const projectRoot = resolve2(options.projectRoot ?? process.cwd());
|
|
81
340
|
if (!withinProjectPath(projectRoot, value)) return null;
|
|
82
|
-
return
|
|
341
|
+
return isAbsolute2(value) ? resolve2(value) : resolve2(projectRoot, value);
|
|
83
342
|
}
|
|
84
343
|
function resolveRunnerCommand(runtime, command, options = {}) {
|
|
85
344
|
const tokens = safeSplit(command);
|
|
86
345
|
if (!tokens || tokens.length === 0) return null;
|
|
87
|
-
const projectRoot =
|
|
346
|
+
const projectRoot = resolve2(options.projectRoot ?? process.cwd());
|
|
88
347
|
const launcher = runtime.packageManager;
|
|
89
348
|
const [head, second, ...rest] = tokens;
|
|
90
349
|
if (!head) return null;
|
|
@@ -115,7 +374,7 @@ function resolveRunnerCommand(runtime, command, options = {}) {
|
|
|
115
374
|
return { cmd: head, args: [second, exe, ...tail], display };
|
|
116
375
|
}
|
|
117
376
|
}
|
|
118
|
-
if (
|
|
377
|
+
if (isAbsolute2(head)) {
|
|
119
378
|
if (!withinProjectPath(projectRoot, head)) return null;
|
|
120
379
|
const base = basename(head);
|
|
121
380
|
if (base !== runtime.executable && base !== launcher) return null;
|
|
@@ -136,7 +395,7 @@ function runRunnerCommand(argv, options) {
|
|
|
136
395
|
});
|
|
137
396
|
}
|
|
138
397
|
return new Promise((resolvePromise) => {
|
|
139
|
-
const projectRoot =
|
|
398
|
+
const projectRoot = resolve2(options.projectRoot ?? process.cwd());
|
|
140
399
|
const trimmedCwd = options.cwd.trim();
|
|
141
400
|
if (!withinProjectPath(projectRoot, trimmedCwd)) {
|
|
142
401
|
resolvePromise({
|
|
@@ -154,21 +413,37 @@ function runRunnerCommand(argv, options) {
|
|
|
154
413
|
const stderrChunks = [];
|
|
155
414
|
let stdoutBytes = 0;
|
|
156
415
|
let stderrBytes = 0;
|
|
157
|
-
|
|
416
|
+
const onAbort = () => {
|
|
158
417
|
timedOut = true;
|
|
159
|
-
}
|
|
418
|
+
};
|
|
419
|
+
let invocation;
|
|
420
|
+
try {
|
|
421
|
+
invocation = resolveExecInvocation(argv[0], argv.slice(1));
|
|
422
|
+
} catch (err) {
|
|
423
|
+
resolvePromise({
|
|
424
|
+
code: null,
|
|
425
|
+
stdout: "",
|
|
426
|
+
stderr: `runtime helper: ${err instanceof Error ? err.message : String(err)}`,
|
|
427
|
+
timedOut: false,
|
|
428
|
+
spawnError: true
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
160
433
|
const child = execFile(
|
|
161
|
-
|
|
162
|
-
|
|
434
|
+
invocation.cmd,
|
|
435
|
+
invocation.args,
|
|
163
436
|
{
|
|
164
437
|
cwd: trimmedCwd,
|
|
165
438
|
timeout: options.timeoutMs,
|
|
166
439
|
signal: options.signal,
|
|
167
440
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
168
441
|
windowsHide: true,
|
|
169
|
-
shell: false
|
|
442
|
+
shell: false,
|
|
443
|
+
...invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
|
|
170
444
|
},
|
|
171
445
|
(err) => {
|
|
446
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
172
447
|
if (timedOut) {
|
|
173
448
|
resolvePromise({
|
|
174
449
|
code: null,
|
|
@@ -241,14 +516,14 @@ async function probeRunner(runtime, probeArg = "--version", options) {
|
|
|
241
516
|
}
|
|
242
517
|
function withinProject(p) {
|
|
243
518
|
const cwd = process.cwd();
|
|
244
|
-
return withinProjectPath(cwd, p) ||
|
|
519
|
+
return withinProjectPath(cwd, p) || relative2(cwd, p) === ".";
|
|
245
520
|
}
|
|
246
521
|
function locateRunnerEntry(runtime, projectRoot) {
|
|
247
|
-
const root =
|
|
522
|
+
const root = resolve2(projectRoot);
|
|
248
523
|
const candidates = [
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
524
|
+
resolve2(root, "node_modules", ".bin", runtime.executable),
|
|
525
|
+
resolve2(root, "node_modules", ".bin", `${runtime.executable}.cmd`),
|
|
526
|
+
resolve2(root, "node_modules", ".bin", `${runtime.executable}.ps1`)
|
|
252
527
|
];
|
|
253
528
|
for (const c of candidates) {
|
|
254
529
|
if (existsSync(c)) return c;
|
|
@@ -278,7 +553,7 @@ function collectSourceFiles(root, opts) {
|
|
|
278
553
|
entries.sort();
|
|
279
554
|
for (const entry of entries) {
|
|
280
555
|
if (excludeSet.has(entry)) continue;
|
|
281
|
-
const full =
|
|
556
|
+
const full = resolve2(dir, entry);
|
|
282
557
|
let st;
|
|
283
558
|
try {
|
|
284
559
|
st = statSync(full);
|
|
@@ -321,7 +596,7 @@ async function collectSourceFilesAsync(root, opts) {
|
|
|
321
596
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
322
597
|
for (const entry of entries) {
|
|
323
598
|
if (excludeSet.has(entry.name)) continue;
|
|
324
|
-
const full =
|
|
599
|
+
const full = resolve2(dir, entry.name);
|
|
325
600
|
if (entry.isDirectory()) {
|
|
326
601
|
await walk(full, depth + 1);
|
|
327
602
|
} else if (entry.isFile() && matchesExtension(full, opts.extensions)) {
|
|
@@ -333,18 +608,30 @@ async function collectSourceFilesAsync(root, opts) {
|
|
|
333
608
|
return files;
|
|
334
609
|
}
|
|
335
610
|
function matchesExtension(p, exts) {
|
|
336
|
-
return exts.includes(
|
|
611
|
+
return exts.includes(extname2(p).toLowerCase());
|
|
337
612
|
}
|
|
338
613
|
export {
|
|
614
|
+
BoundedMap,
|
|
615
|
+
BoundedSet,
|
|
616
|
+
UNSERIALIZABLE,
|
|
617
|
+
clearLocalBinCache,
|
|
339
618
|
collectSourceFiles,
|
|
340
619
|
collectSourceFilesAsync,
|
|
620
|
+
findOnPath,
|
|
341
621
|
locateRunnerEntry,
|
|
342
622
|
matchesExtension,
|
|
343
623
|
parseLlmJsonObject,
|
|
344
624
|
probeRunner,
|
|
625
|
+
releaseHandle,
|
|
626
|
+
releaseHandles,
|
|
627
|
+
resolveExecInvocation,
|
|
628
|
+
resolveFirstNodeBin,
|
|
629
|
+
resolveNodeBin,
|
|
345
630
|
resolveRunnerCommand,
|
|
631
|
+
resolveWin32Command,
|
|
346
632
|
runOptionalPluginLlm,
|
|
347
633
|
runRunnerCommand,
|
|
634
|
+
safeJsonStringify,
|
|
348
635
|
sanitizeRunnerPath,
|
|
349
636
|
stripOuterMarkdownFence,
|
|
350
637
|
withinProject
|
|
@@ -4,6 +4,11 @@ import { basename as basename2 } from "node:path";
|
|
|
4
4
|
|
|
5
5
|
// src/runtime/index.ts
|
|
6
6
|
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
// src/runtime/local-bin.ts
|
|
9
|
+
import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
|
|
10
|
+
|
|
11
|
+
// src/runtime/index.ts
|
|
7
12
|
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
13
|
function hasLeadingDash(arg) {
|
|
9
14
|
return arg.length > 0 && arg.startsWith("-");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secret-scanner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secret-scanner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAymBrD,QAAA,MAAM,MAAM,EAAE,MAgOb,CAAC;eAEa,MAAM"}
|