@wrongstack/tools 0.141.0 → 0.148.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/audit.js +34 -8
- package/dist/audit.js.map +1 -1
- package/dist/builtin.js +140 -110
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.js +1 -1
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/exec.js +26 -2
- package/dist/exec.js.map +1 -1
- package/dist/format.js +34 -8
- package/dist/format.js.map +1 -1
- package/dist/grep.js +1 -1
- package/dist/grep.js.map +1 -1
- package/dist/index.js +48 -18
- package/dist/index.js.map +1 -1
- package/dist/install.js +34 -8
- package/dist/install.js.map +1 -1
- package/dist/lint.js +34 -8
- package/dist/lint.js.map +1 -1
- package/dist/logs.js +1 -1
- package/dist/logs.js.map +1 -1
- package/dist/outdated.js +24 -1
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +140 -110
- package/dist/pack.js.map +1 -1
- package/dist/replace.js +1 -1
- package/dist/replace.js.map +1 -1
- package/dist/test.js +37 -11
- package/dist/test.js.map +1 -1
- package/dist/typecheck.js +36 -10
- package/dist/typecheck.js.map +1 -1
- package/package.json +2 -2
package/dist/builtin.js
CHANGED
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
import { spawn, execFileSync, spawnSync } from 'node:child_process';
|
|
2
2
|
import * as Core from '@wrongstack/core';
|
|
3
3
|
import { buildChildEnv, expectDefined, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan, loadTasks, emptyTaskFile, saveTasks, computeTaskItemProgress, formatTaskList, resolveWstackPaths } from '@wrongstack/core';
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import { statSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import * as path2 from 'node:path';
|
|
6
7
|
import { resolve, sep, dirname } from 'node:path';
|
|
8
|
+
import * as fs13 from 'node:fs/promises';
|
|
7
9
|
import * as os from 'node:os';
|
|
8
10
|
import { createRequire } from 'node:module';
|
|
9
|
-
import * as fs from 'node:fs';
|
|
10
|
-
import { statSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
11
11
|
import * as ts from 'typescript';
|
|
12
12
|
import * as dns from 'node:dns/promises';
|
|
13
13
|
import * as net from 'node:net';
|
|
14
14
|
import { Agent } from 'undici';
|
|
15
15
|
|
|
16
|
+
// src/_spawn-stream.ts
|
|
17
|
+
function resolveWin32Command(cmd) {
|
|
18
|
+
if (process.platform !== "win32") return cmd;
|
|
19
|
+
if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd)) {
|
|
20
|
+
return cmd;
|
|
21
|
+
}
|
|
22
|
+
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
23
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
|
|
24
|
+
for (const dir of pathDirs) {
|
|
25
|
+
const base = path2.join(dir, cmd);
|
|
26
|
+
for (const ext of pathext) {
|
|
27
|
+
const full = `${base}${ext}`;
|
|
28
|
+
try {
|
|
29
|
+
fs.accessSync(full, fs.constants.X_OK);
|
|
30
|
+
return full;
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return cmd;
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
// src/_spawn-stream.ts
|
|
17
39
|
async function* spawnStream(opts) {
|
|
18
40
|
const max = opts.maxBytes ?? 2e5;
|
|
@@ -21,11 +43,14 @@ async function* spawnStream(opts) {
|
|
|
21
43
|
let stderr = "";
|
|
22
44
|
let pending = "";
|
|
23
45
|
let error;
|
|
24
|
-
const
|
|
46
|
+
const cmd = resolveWin32Command(opts.cmd);
|
|
47
|
+
const needsShell = process.platform === "win32" && (cmd.endsWith(".cmd") || cmd.endsWith(".bat"));
|
|
48
|
+
const child = spawn(cmd, opts.args, {
|
|
25
49
|
cwd: opts.cwd,
|
|
26
50
|
signal: opts.signal,
|
|
27
51
|
env: buildChildEnv(),
|
|
28
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
52
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
53
|
+
...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
|
|
29
54
|
});
|
|
30
55
|
const queue = [];
|
|
31
56
|
let waiter;
|
|
@@ -107,13 +132,13 @@ async function detectPackageManager(cwd) {
|
|
|
107
132
|
return "npm";
|
|
108
133
|
}
|
|
109
134
|
function resolvePath(input, ctx) {
|
|
110
|
-
return
|
|
135
|
+
return path2.isAbsolute(input) ? path2.normalize(input) : path2.resolve(ctx.cwd, input);
|
|
111
136
|
}
|
|
112
137
|
function ensureInsideRoot(absPath, ctx) {
|
|
113
|
-
const root =
|
|
114
|
-
const target =
|
|
115
|
-
const rel =
|
|
116
|
-
if (rel.startsWith("..") ||
|
|
138
|
+
const root = path2.resolve(ctx.projectRoot);
|
|
139
|
+
const target = path2.resolve(absPath);
|
|
140
|
+
const rel = path2.relative(root, target);
|
|
141
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
117
142
|
throw new Error(`Path "${absPath}" is outside project root "${root}"`);
|
|
118
143
|
}
|
|
119
144
|
return target;
|
|
@@ -122,23 +147,23 @@ function safeResolve(input, ctx) {
|
|
|
122
147
|
return ensureInsideRoot(resolvePath(input, ctx), ctx);
|
|
123
148
|
}
|
|
124
149
|
async function assertRealInsideRoot(absPath, ctx) {
|
|
125
|
-
const realRoot = await
|
|
150
|
+
const realRoot = await fs13.realpath(ctx.projectRoot).catch(() => path2.resolve(ctx.projectRoot));
|
|
126
151
|
let probe = absPath;
|
|
127
152
|
for (; ; ) {
|
|
128
153
|
let real;
|
|
129
154
|
try {
|
|
130
|
-
real = await
|
|
155
|
+
real = await fs13.realpath(probe);
|
|
131
156
|
} catch (err) {
|
|
132
157
|
if (err.code === "ENOENT") {
|
|
133
|
-
const parent =
|
|
158
|
+
const parent = path2.dirname(probe);
|
|
134
159
|
if (parent === probe) return;
|
|
135
160
|
probe = parent;
|
|
136
161
|
continue;
|
|
137
162
|
}
|
|
138
163
|
throw err;
|
|
139
164
|
}
|
|
140
|
-
const rel =
|
|
141
|
-
if (rel.startsWith("..") ||
|
|
165
|
+
const rel = path2.relative(realRoot, real);
|
|
166
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
142
167
|
throw new Error(
|
|
143
168
|
`Path "${absPath}" resolves through a symlink outside project root "${realRoot}"`
|
|
144
169
|
);
|
|
@@ -1142,7 +1167,7 @@ var IndexStore = class {
|
|
|
1142
1167
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
1143
1168
|
fs.mkdirSync(this.indexDir, { recursive: true });
|
|
1144
1169
|
const Database = loadDatabaseSync();
|
|
1145
|
-
this.db = new Database(
|
|
1170
|
+
this.db = new Database(path2.join(this.indexDir, DB_FILE));
|
|
1146
1171
|
this.initSchema();
|
|
1147
1172
|
}
|
|
1148
1173
|
initSchema() {
|
|
@@ -1440,7 +1465,7 @@ var IndexStore = class {
|
|
|
1440
1465
|
}));
|
|
1441
1466
|
}
|
|
1442
1467
|
sizeBytes() {
|
|
1443
|
-
const dbPath =
|
|
1468
|
+
const dbPath = path2.join(this.indexDir, DB_FILE);
|
|
1444
1469
|
try {
|
|
1445
1470
|
return fs.statSync(dbPath).size;
|
|
1446
1471
|
} catch {
|
|
@@ -1879,10 +1904,10 @@ func formatType(t ast.Expr) string {
|
|
|
1879
1904
|
}
|
|
1880
1905
|
`;
|
|
1881
1906
|
function syncGoParse(filePath, content, lang) {
|
|
1882
|
-
const tmpDir =
|
|
1907
|
+
const tmpDir = path2.join(os.tmpdir(), "ws-go-parse");
|
|
1883
1908
|
try {
|
|
1884
1909
|
mkdirSync(tmpDir, { recursive: true });
|
|
1885
|
-
const scriptPath =
|
|
1910
|
+
const scriptPath = path2.join(tmpDir, "parse.go");
|
|
1886
1911
|
writeFileSync(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
1887
1912
|
const stdout = execFileSync("go", ["run", scriptPath], {
|
|
1888
1913
|
input: content,
|
|
@@ -2126,9 +2151,9 @@ print(json.dumps([s.to_dict() for s in syms]))
|
|
|
2126
2151
|
`;
|
|
2127
2152
|
function syncPyParse(filePath, lang) {
|
|
2128
2153
|
try {
|
|
2129
|
-
const tmpDir =
|
|
2154
|
+
const tmpDir = path2.join(os.tmpdir(), "ws-py-parse");
|
|
2130
2155
|
mkdirSync(tmpDir, { recursive: true });
|
|
2131
|
-
const scriptPath =
|
|
2156
|
+
const scriptPath = path2.join(tmpDir, "parse.py");
|
|
2132
2157
|
writeFileSync(scriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
2133
2158
|
const stdout = execFileSync("python", [scriptPath, filePath], {
|
|
2134
2159
|
timeout: 15e3,
|
|
@@ -2169,7 +2194,7 @@ function parseSymbols4(opts) {
|
|
|
2169
2194
|
function checkNativeParser() {
|
|
2170
2195
|
try {
|
|
2171
2196
|
execFileSync("rustc", ["--version"], { stdio: "pipe" });
|
|
2172
|
-
const toolsDir =
|
|
2197
|
+
const toolsDir = path2.join(process.cwd(), "tools");
|
|
2173
2198
|
try {
|
|
2174
2199
|
execFileSync(
|
|
2175
2200
|
"cargo",
|
|
@@ -2179,7 +2204,7 @@ function checkNativeParser() {
|
|
|
2179
2204
|
"--format-version",
|
|
2180
2205
|
"1",
|
|
2181
2206
|
"--manifest-path",
|
|
2182
|
-
|
|
2207
|
+
path2.join(toolsDir, "Cargo.toml")
|
|
2183
2208
|
],
|
|
2184
2209
|
{ stdio: "pipe" }
|
|
2185
2210
|
);
|
|
@@ -2193,13 +2218,13 @@ function checkNativeParser() {
|
|
|
2193
2218
|
}
|
|
2194
2219
|
function tryNativeParse(file, content) {
|
|
2195
2220
|
try {
|
|
2196
|
-
const toolsDir =
|
|
2197
|
-
const crateDir =
|
|
2198
|
-
const tmpFile =
|
|
2221
|
+
const toolsDir = path2.join(process.cwd(), "tools");
|
|
2222
|
+
const crateDir = path2.join(toolsDir, "syn-parser");
|
|
2223
|
+
const tmpFile = path2.join(crateDir, "src", "input.rs");
|
|
2199
2224
|
writeFileSync(tmpFile, content, "utf8");
|
|
2200
2225
|
const result = spawnSync(
|
|
2201
2226
|
"cargo",
|
|
2202
|
-
["run", "--manifest-path",
|
|
2227
|
+
["run", "--manifest-path", path2.join(toolsDir, "Cargo.toml")],
|
|
2203
2228
|
{
|
|
2204
2229
|
cwd: process.cwd(),
|
|
2205
2230
|
encoding: "utf8",
|
|
@@ -2297,7 +2322,7 @@ function parseSymbols5(opts) {
|
|
|
2297
2322
|
function regexParse2(opts) {
|
|
2298
2323
|
const { file, content, lang } = opts;
|
|
2299
2324
|
const symbols = [];
|
|
2300
|
-
const basename2 =
|
|
2325
|
+
const basename2 = path2.basename(file).toLowerCase();
|
|
2301
2326
|
const isPackageJson = basename2 === "package.json";
|
|
2302
2327
|
const isTsconfig = basename2 === "tsconfig.json" || basename2 === "tsconfig.build.json";
|
|
2303
2328
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
@@ -2323,11 +2348,11 @@ function regexParse2(opts) {
|
|
|
2323
2348
|
const line = lineFromOffset(offset);
|
|
2324
2349
|
symbols.push(
|
|
2325
2350
|
makeSymbol({
|
|
2326
|
-
name:
|
|
2351
|
+
name: path2.basename(file),
|
|
2327
2352
|
kind: "object",
|
|
2328
2353
|
line,
|
|
2329
2354
|
col: 0,
|
|
2330
|
-
signature: `"${
|
|
2355
|
+
signature: `"${path2.basename(file)}" = { ... }`,
|
|
2331
2356
|
file,
|
|
2332
2357
|
lang
|
|
2333
2358
|
})
|
|
@@ -2681,7 +2706,7 @@ function compileGitignore(lines) {
|
|
|
2681
2706
|
async function loadGitignoreMatcher(projectRoot) {
|
|
2682
2707
|
let lines = [];
|
|
2683
2708
|
try {
|
|
2684
|
-
const raw = await
|
|
2709
|
+
const raw = await fs13.readFile(path2.join(projectRoot, ".gitignore"), "utf8");
|
|
2685
2710
|
lines = raw.split("\n");
|
|
2686
2711
|
} catch {
|
|
2687
2712
|
}
|
|
@@ -2752,20 +2777,20 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
|
|
|
2752
2777
|
const walk = async (dir) => {
|
|
2753
2778
|
let entries;
|
|
2754
2779
|
try {
|
|
2755
|
-
entries = await
|
|
2780
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
2756
2781
|
} catch {
|
|
2757
2782
|
return;
|
|
2758
2783
|
}
|
|
2759
2784
|
for (const e of entries) {
|
|
2760
2785
|
if (ignoreSet.has(e.name)) continue;
|
|
2761
|
-
const full =
|
|
2762
|
-
const rel =
|
|
2786
|
+
const full = path2.join(dir, e.name);
|
|
2787
|
+
const rel = path2.relative(projectRoot, full).replace(/\\/g, "/");
|
|
2763
2788
|
if (e.isDirectory()) {
|
|
2764
2789
|
if (isGitIgnored(rel, true)) continue;
|
|
2765
2790
|
await walk(full);
|
|
2766
2791
|
} else if (e.isFile()) {
|
|
2767
2792
|
if (isGitIgnored(rel, false)) continue;
|
|
2768
|
-
const ext =
|
|
2793
|
+
const ext = path2.extname(e.name);
|
|
2769
2794
|
for (const { ext: extName, pat } of globs) {
|
|
2770
2795
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
2771
2796
|
results.push(full);
|
|
@@ -2810,7 +2835,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2810
2835
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
2811
2836
|
let files;
|
|
2812
2837
|
if (opts.files && opts.files.length > 0) {
|
|
2813
|
-
files = opts.files.map((f) =>
|
|
2838
|
+
files = opts.files.map((f) => path2.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path2.relative(projectRoot, f).replace(/\\/g, "/"), false));
|
|
2814
2839
|
} else {
|
|
2815
2840
|
files = await findSourceFiles(projectRoot, ignore, isGitIgnored);
|
|
2816
2841
|
}
|
|
@@ -2834,7 +2859,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2834
2859
|
}
|
|
2835
2860
|
let stat10;
|
|
2836
2861
|
try {
|
|
2837
|
-
stat10 = await
|
|
2862
|
+
stat10 = await fs13.stat(file);
|
|
2838
2863
|
} catch {
|
|
2839
2864
|
store.deleteFile(file);
|
|
2840
2865
|
continue;
|
|
@@ -2853,7 +2878,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2853
2878
|
store.deleteSymbolsForFile(file);
|
|
2854
2879
|
let content;
|
|
2855
2880
|
try {
|
|
2856
|
-
content = await
|
|
2881
|
+
content = await fs13.readFile(file, "utf8");
|
|
2857
2882
|
} catch (e) {
|
|
2858
2883
|
errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2859
2884
|
continue;
|
|
@@ -2903,7 +2928,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2903
2928
|
}
|
|
2904
2929
|
for (const [file_] of existingMeta) {
|
|
2905
2930
|
try {
|
|
2906
|
-
await
|
|
2931
|
+
await fs13.stat(file_);
|
|
2907
2932
|
} catch {
|
|
2908
2933
|
store.deleteFile(file_);
|
|
2909
2934
|
}
|
|
@@ -2964,7 +2989,7 @@ function tokenise(text) {
|
|
|
2964
2989
|
return sanitised.toLowerCase().split(" ").filter(Boolean);
|
|
2965
2990
|
}
|
|
2966
2991
|
function splitName(name) {
|
|
2967
|
-
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_
|
|
2992
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
2968
2993
|
}
|
|
2969
2994
|
function buildIndexableText(name, signature, docComment) {
|
|
2970
2995
|
return [splitName(name), name, signature, docComment].filter(Boolean).join(" ");
|
|
@@ -3294,11 +3319,11 @@ function findGitDir(cwd) {
|
|
|
3294
3319
|
let dir = cwd;
|
|
3295
3320
|
for (let i = 0; i < 20; i++) {
|
|
3296
3321
|
try {
|
|
3297
|
-
const stat10 = statSync(
|
|
3322
|
+
const stat10 = statSync(path2.join(dir, ".git"));
|
|
3298
3323
|
if (stat10.isDirectory()) return dir;
|
|
3299
3324
|
} catch {
|
|
3300
3325
|
}
|
|
3301
|
-
const parent =
|
|
3326
|
+
const parent = path2.dirname(dir);
|
|
3302
3327
|
if (parent === dir) break;
|
|
3303
3328
|
dir = parent;
|
|
3304
3329
|
}
|
|
@@ -3338,9 +3363,9 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
3338
3363
|
const results = [];
|
|
3339
3364
|
for (const file of files) {
|
|
3340
3365
|
const absPath = safeResolve(file, ctx);
|
|
3341
|
-
const stat10 = await
|
|
3366
|
+
const stat10 = await fs13.stat(absPath).catch(() => null);
|
|
3342
3367
|
if (!stat10?.isFile()) continue;
|
|
3343
|
-
const content = await
|
|
3368
|
+
const content = await fs13.readFile(absPath, "utf8");
|
|
3344
3369
|
const lines = content.split(/\r?\n/);
|
|
3345
3370
|
results.push(formatWithLineNumbers(file, lines));
|
|
3346
3371
|
}
|
|
@@ -3402,7 +3427,7 @@ var documentTool = {
|
|
|
3402
3427
|
const fileList = input.files ? await resolveFiles(Array.isArray(input.files) ? input.files.join(",") : input.files, cwd) : input.path ? [safeResolve(input.path, ctx)] : [];
|
|
3403
3428
|
for (const absPath of fileList) {
|
|
3404
3429
|
try {
|
|
3405
|
-
const content = await
|
|
3430
|
+
const content = await fs13.readFile(absPath, "utf8");
|
|
3406
3431
|
filesProcessed++;
|
|
3407
3432
|
const processed = processFile(
|
|
3408
3433
|
content,
|
|
@@ -3438,7 +3463,7 @@ async function resolveFiles(filesInput, cwd) {
|
|
|
3438
3463
|
for (const f of files) {
|
|
3439
3464
|
const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
|
|
3440
3465
|
try {
|
|
3441
|
-
const stat10 = await
|
|
3466
|
+
const stat10 = await fs13.stat(absPath);
|
|
3442
3467
|
if (stat10.isFile()) resolved.push(absPath);
|
|
3443
3468
|
} catch {
|
|
3444
3469
|
}
|
|
@@ -3530,7 +3555,7 @@ var editTool = {
|
|
|
3530
3555
|
if (input.new_string === void 0) throw new Error("edit: new_string is required");
|
|
3531
3556
|
if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
|
|
3532
3557
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
3533
|
-
const stat10 = await
|
|
3558
|
+
const stat10 = await fs13.stat(absPath).catch((err) => {
|
|
3534
3559
|
if (err.code === "ENOENT") {
|
|
3535
3560
|
throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
|
|
3536
3561
|
}
|
|
@@ -3540,8 +3565,8 @@ var editTool = {
|
|
|
3540
3565
|
if (!ctx.hasRead(absPath)) {
|
|
3541
3566
|
throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
|
|
3542
3567
|
}
|
|
3543
|
-
const original = await
|
|
3544
|
-
const updated = await
|
|
3568
|
+
const original = await fs13.readFile(absPath, "utf8");
|
|
3569
|
+
const updated = await fs13.stat(absPath);
|
|
3545
3570
|
const mtimeTolerance = process.platform === "win32" ? 2e3 : 1;
|
|
3546
3571
|
const lastReadMtime = ctx.lastReadMtime(absPath);
|
|
3547
3572
|
if (lastReadMtime !== void 0 && updated.mtimeMs > lastReadMtime + mtimeTolerance) {
|
|
@@ -3581,7 +3606,7 @@ var editTool = {
|
|
|
3581
3606
|
const newFileLf = input.replace_all ? fileLf.split(oldLf).join(newLf) : fileLf.replace(oldLf, newLf);
|
|
3582
3607
|
const newFile = toStyle(newFileLf, style);
|
|
3583
3608
|
await atomicWrite(absPath, newFile, { mode: updated.mode & 511 });
|
|
3584
|
-
const written = await
|
|
3609
|
+
const written = await fs13.stat(absPath);
|
|
3585
3610
|
ctx.recordRead(absPath, written.mtimeMs);
|
|
3586
3611
|
ctx.session.recordFileChange({
|
|
3587
3612
|
path: absPath,
|
|
@@ -3806,9 +3831,9 @@ var execTool = {
|
|
|
3806
3831
|
allowed: false
|
|
3807
3832
|
};
|
|
3808
3833
|
}
|
|
3809
|
-
const requestedCwd = input.cwd ?
|
|
3810
|
-
const rel =
|
|
3811
|
-
if (rel.startsWith("..") ||
|
|
3834
|
+
const requestedCwd = input.cwd ? path2.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
|
|
3835
|
+
const rel = path2.relative(ctx.projectRoot, requestedCwd);
|
|
3836
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
3812
3837
|
return {
|
|
3813
3838
|
command: cmd,
|
|
3814
3839
|
args,
|
|
@@ -3830,11 +3855,14 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
3830
3855
|
let stderr = "";
|
|
3831
3856
|
let killed = false;
|
|
3832
3857
|
const startedAt = Date.now();
|
|
3833
|
-
const
|
|
3858
|
+
const resolved = resolveWin32Command(cmd);
|
|
3859
|
+
const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
3860
|
+
const child = spawn(resolved, args, {
|
|
3834
3861
|
cwd,
|
|
3835
3862
|
signal,
|
|
3836
3863
|
env: buildChildEnv(sessionId),
|
|
3837
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3864
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3865
|
+
...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
|
|
3838
3866
|
});
|
|
3839
3867
|
const registry = getProcessRegistry();
|
|
3840
3868
|
const pid = child.pid;
|
|
@@ -4617,7 +4645,7 @@ var globTool = {
|
|
|
4617
4645
|
}
|
|
4618
4646
|
let entries;
|
|
4619
4647
|
try {
|
|
4620
|
-
entries = await
|
|
4648
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
4621
4649
|
} catch {
|
|
4622
4650
|
return;
|
|
4623
4651
|
}
|
|
@@ -4626,14 +4654,14 @@ var globTool = {
|
|
|
4626
4654
|
if (DEFAULT_IGNORE2.includes(name)) continue;
|
|
4627
4655
|
if (ignored.includes(name)) continue;
|
|
4628
4656
|
const rel = relPrefix ? `${relPrefix}/${name}` : name;
|
|
4629
|
-
const full =
|
|
4657
|
+
const full = path2.join(dir, name);
|
|
4630
4658
|
if (e.isDirectory()) {
|
|
4631
4659
|
await walk(full, rel);
|
|
4632
4660
|
if (truncated) return;
|
|
4633
4661
|
} else if (e.isFile()) {
|
|
4634
4662
|
if (re.test(rel) || re.test(name)) {
|
|
4635
4663
|
try {
|
|
4636
|
-
const st = await
|
|
4664
|
+
const st = await fs13.stat(full);
|
|
4637
4665
|
results.push({ rel: full, mtime: st.mtimeMs });
|
|
4638
4666
|
if (results.length >= limit) {
|
|
4639
4667
|
truncated = true;
|
|
@@ -4652,7 +4680,7 @@ var globTool = {
|
|
|
4652
4680
|
};
|
|
4653
4681
|
async function readGitignore(dir) {
|
|
4654
4682
|
try {
|
|
4655
|
-
const raw = await
|
|
4683
|
+
const raw = await fs13.readFile(path2.join(dir, ".gitignore"), "utf8");
|
|
4656
4684
|
return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
4657
4685
|
} catch {
|
|
4658
4686
|
return [];
|
|
@@ -4670,7 +4698,7 @@ var DANGEROUS_PATTERNS = [
|
|
|
4670
4698
|
// Quantifier on alternation with length 2+
|
|
4671
4699
|
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
4672
4700
|
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
4673
|
-
/[
|
|
4701
|
+
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
4674
4702
|
];
|
|
4675
4703
|
function compileUserRegex(pattern, flags) {
|
|
4676
4704
|
if (typeof pattern !== "string") {
|
|
@@ -4936,7 +4964,7 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
4936
4964
|
if (stopped || signal.aborted) return;
|
|
4937
4965
|
let entries;
|
|
4938
4966
|
try {
|
|
4939
|
-
entries = await
|
|
4967
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
4940
4968
|
} catch {
|
|
4941
4969
|
return;
|
|
4942
4970
|
}
|
|
@@ -4944,16 +4972,16 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
4944
4972
|
if (stopped) return;
|
|
4945
4973
|
if (DEFAULT_IGNORE3.includes(e.name)) continue;
|
|
4946
4974
|
if (e.isSymbolicLink()) continue;
|
|
4947
|
-
const full =
|
|
4975
|
+
const full = path2.join(dir, e.name);
|
|
4948
4976
|
if (e.isDirectory()) {
|
|
4949
4977
|
await walk(full);
|
|
4950
4978
|
} else if (e.isFile()) {
|
|
4951
4979
|
if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
|
|
4952
4980
|
if (globRe) globRe.lastIndex = 0;
|
|
4953
4981
|
try {
|
|
4954
|
-
const stat10 = await
|
|
4982
|
+
const stat10 = await fs13.stat(full);
|
|
4955
4983
|
if (stat10.size > 1e6) continue;
|
|
4956
|
-
const head = await
|
|
4984
|
+
const head = await fs13.readFile(full);
|
|
4957
4985
|
if (isBinaryBuffer(head)) continue;
|
|
4958
4986
|
const text = head.toString("utf8");
|
|
4959
4987
|
const lines = text.split(/\r?\n/);
|
|
@@ -5134,7 +5162,7 @@ var jsonTool = {
|
|
|
5134
5162
|
let raw;
|
|
5135
5163
|
if (input.file) {
|
|
5136
5164
|
try {
|
|
5137
|
-
raw = await
|
|
5165
|
+
raw = await fs13.readFile(input.file, "utf8");
|
|
5138
5166
|
} catch {
|
|
5139
5167
|
return { data: null, formatted: "", type: "unknown", error: `Could not read file` };
|
|
5140
5168
|
}
|
|
@@ -5172,8 +5200,8 @@ var jsonTool = {
|
|
|
5172
5200
|
};
|
|
5173
5201
|
}
|
|
5174
5202
|
};
|
|
5175
|
-
function query(data,
|
|
5176
|
-
const parts =
|
|
5203
|
+
function query(data, path20) {
|
|
5204
|
+
const parts = path20.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
5177
5205
|
let current = data;
|
|
5178
5206
|
for (const part of parts) {
|
|
5179
5207
|
if (current === null || current === void 0) return void 0;
|
|
@@ -5444,7 +5472,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
5444
5472
|
}
|
|
5445
5473
|
var DOCKER_LOGS_TIMEOUT_MS = 3e3;
|
|
5446
5474
|
var MAX_TAIL_LINES = 1e5;
|
|
5447
|
-
async function fileLogs(
|
|
5475
|
+
async function fileLogs(path20, lines, filterRe, stream) {
|
|
5448
5476
|
const { createInterface } = await import('node:readline');
|
|
5449
5477
|
const { createReadStream } = await import('node:fs');
|
|
5450
5478
|
const entries = [];
|
|
@@ -5453,7 +5481,7 @@ async function fileLogs(path19, lines, filterRe, stream) {
|
|
|
5453
5481
|
let writeIdx = 0;
|
|
5454
5482
|
let totalLines = 0;
|
|
5455
5483
|
const rl = createInterface({
|
|
5456
|
-
input: createReadStream(
|
|
5484
|
+
input: createReadStream(path20),
|
|
5457
5485
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
5458
5486
|
});
|
|
5459
5487
|
for await (const line of rl) {
|
|
@@ -5474,7 +5502,7 @@ async function fileLogs(path19, lines, filterRe, stream) {
|
|
|
5474
5502
|
if (parsed) entries.push(parsed);
|
|
5475
5503
|
}
|
|
5476
5504
|
return {
|
|
5477
|
-
source:
|
|
5505
|
+
source: path20,
|
|
5478
5506
|
entries,
|
|
5479
5507
|
total: entries.length,
|
|
5480
5508
|
truncated: totalLines > effLines,
|
|
@@ -5557,7 +5585,9 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
5557
5585
|
let stdout = "";
|
|
5558
5586
|
let stderr = "";
|
|
5559
5587
|
const MAX = 1e5;
|
|
5560
|
-
const
|
|
5588
|
+
const resolved = resolveWin32Command(manager);
|
|
5589
|
+
const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
5590
|
+
const child = spawn(resolved, args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {} });
|
|
5561
5591
|
child.stdout?.on("data", (c) => {
|
|
5562
5592
|
if (stdout.length < MAX) stdout += c.toString();
|
|
5563
5593
|
});
|
|
@@ -5641,9 +5671,9 @@ var patchTool = {
|
|
|
5641
5671
|
for (const t of targets) {
|
|
5642
5672
|
const stripped = stripPathComponents(t, strip);
|
|
5643
5673
|
if (!stripped) continue;
|
|
5644
|
-
const candidate =
|
|
5645
|
-
const rel =
|
|
5646
|
-
if (rel.startsWith("..") ||
|
|
5674
|
+
const candidate = path2.resolve(dir, stripped);
|
|
5675
|
+
const rel = path2.relative(ctx.projectRoot, candidate);
|
|
5676
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
5647
5677
|
return {
|
|
5648
5678
|
applied: 0,
|
|
5649
5679
|
rejected: 1,
|
|
@@ -5653,12 +5683,12 @@ var patchTool = {
|
|
|
5653
5683
|
};
|
|
5654
5684
|
}
|
|
5655
5685
|
}
|
|
5656
|
-
const tmpDir = await
|
|
5686
|
+
const tmpDir = await fs13.mkdtemp(path2.join(os.tmpdir(), ".wstack_patch_"));
|
|
5657
5687
|
try {
|
|
5658
|
-
await
|
|
5688
|
+
await fs13.chmod(tmpDir, 448).catch(() => {
|
|
5659
5689
|
});
|
|
5660
|
-
const patchFile =
|
|
5661
|
-
await
|
|
5690
|
+
const patchFile = path2.join(tmpDir, "in.diff");
|
|
5691
|
+
await fs13.writeFile(patchFile, input.patch, { mode: 384 });
|
|
5662
5692
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
5663
5693
|
const result = await runPatch(args, dir, opts.signal);
|
|
5664
5694
|
if (result.exitCode !== 0 && !dryRun) {
|
|
@@ -5679,7 +5709,7 @@ var patchTool = {
|
|
|
5679
5709
|
message: result.stdout || "patch applied"
|
|
5680
5710
|
};
|
|
5681
5711
|
} finally {
|
|
5682
|
-
await
|
|
5712
|
+
await fs13.rm(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
5683
5713
|
});
|
|
5684
5714
|
}
|
|
5685
5715
|
}
|
|
@@ -5922,7 +5952,7 @@ var readTool = {
|
|
|
5922
5952
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
5923
5953
|
let stat10;
|
|
5924
5954
|
try {
|
|
5925
|
-
stat10 = await
|
|
5955
|
+
stat10 = await fs13.stat(absPath);
|
|
5926
5956
|
} catch (err) {
|
|
5927
5957
|
const code = err.code;
|
|
5928
5958
|
if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
|
|
@@ -5934,7 +5964,7 @@ var readTool = {
|
|
|
5934
5964
|
if (stat10.size > MAX_BYTES2) {
|
|
5935
5965
|
throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES2})`);
|
|
5936
5966
|
}
|
|
5937
|
-
const buf = await
|
|
5967
|
+
const buf = await fs13.readFile(absPath);
|
|
5938
5968
|
if (isBinaryBuffer(buf)) {
|
|
5939
5969
|
throw new Error(`read: "${input.path}" appears to be binary`);
|
|
5940
5970
|
}
|
|
@@ -6002,11 +6032,11 @@ var replaceTool = {
|
|
|
6002
6032
|
const dryRun = input.dry_run ?? false;
|
|
6003
6033
|
const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
|
|
6004
6034
|
const fileList = await resolveFiles2(filesInput, ctx, globRe);
|
|
6005
|
-
const realRoot = await
|
|
6035
|
+
const realRoot = await fs13.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
6006
6036
|
const results = [];
|
|
6007
6037
|
let totalReplacements = 0;
|
|
6008
6038
|
for (const absPath of fileList) {
|
|
6009
|
-
const lstat2 = await
|
|
6039
|
+
const lstat2 = await fs13.lstat(absPath).catch((err) => {
|
|
6010
6040
|
if (err.code === "ENOENT") return null;
|
|
6011
6041
|
throw err;
|
|
6012
6042
|
});
|
|
@@ -6014,17 +6044,17 @@ var replaceTool = {
|
|
|
6014
6044
|
if (lstat2.isSymbolicLink()) continue;
|
|
6015
6045
|
let realPath;
|
|
6016
6046
|
try {
|
|
6017
|
-
realPath = await
|
|
6047
|
+
realPath = await fs13.realpath(absPath);
|
|
6018
6048
|
} catch {
|
|
6019
6049
|
continue;
|
|
6020
6050
|
}
|
|
6021
|
-
const rel =
|
|
6022
|
-
if (rel.startsWith("..") ||
|
|
6023
|
-
const stat10 = await
|
|
6051
|
+
const rel = path2.relative(realRoot, realPath);
|
|
6052
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) continue;
|
|
6053
|
+
const stat10 = await fs13.stat(realPath).catch(() => null);
|
|
6024
6054
|
if (!stat10 || !stat10.isFile()) continue;
|
|
6025
6055
|
let content;
|
|
6026
6056
|
try {
|
|
6027
|
-
const buf = await
|
|
6057
|
+
const buf = await fs13.readFile(realPath);
|
|
6028
6058
|
if (isBinaryBuffer(buf)) continue;
|
|
6029
6059
|
content = buf.toString("utf8");
|
|
6030
6060
|
} catch {
|
|
@@ -6076,7 +6106,7 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
|
|
|
6076
6106
|
const resolved = [];
|
|
6077
6107
|
for (const p of parts) {
|
|
6078
6108
|
const absPath = safeResolve(p, ctx);
|
|
6079
|
-
const stat10 = await
|
|
6109
|
+
const stat10 = await fs13.stat(absPath).catch(() => null);
|
|
6080
6110
|
if (stat10?.isFile()) {
|
|
6081
6111
|
resolved.push(absPath);
|
|
6082
6112
|
}
|
|
@@ -6131,15 +6161,15 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
6131
6161
|
const walk = async (dir) => {
|
|
6132
6162
|
let entries;
|
|
6133
6163
|
try {
|
|
6134
|
-
entries = await
|
|
6164
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
6135
6165
|
} catch {
|
|
6136
6166
|
return;
|
|
6137
6167
|
}
|
|
6138
6168
|
for (const e of entries) {
|
|
6139
6169
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
6140
|
-
const full =
|
|
6170
|
+
const full = path2.join(dir, e.name);
|
|
6141
6171
|
try {
|
|
6142
|
-
const stat10 = await
|
|
6172
|
+
const stat10 = await fs13.lstat(full);
|
|
6143
6173
|
if (stat10.isSymbolicLink()) continue;
|
|
6144
6174
|
} catch {
|
|
6145
6175
|
continue;
|
|
@@ -6307,16 +6337,16 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
6307
6337
|
let filesCreated = 0;
|
|
6308
6338
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
6309
6339
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
6310
|
-
const joinedPath =
|
|
6311
|
-
const root =
|
|
6312
|
-
const target =
|
|
6313
|
-
const rel =
|
|
6314
|
-
if (rel.startsWith("..") ||
|
|
6340
|
+
const joinedPath = path2.join(cwd, resolvedPath);
|
|
6341
|
+
const root = path2.resolve(ctx.projectRoot);
|
|
6342
|
+
const target = path2.resolve(joinedPath);
|
|
6343
|
+
const rel = path2.relative(root, target);
|
|
6344
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
6315
6345
|
throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
|
|
6316
6346
|
}
|
|
6317
6347
|
const fullPath = target;
|
|
6318
6348
|
if (!dryRun) {
|
|
6319
|
-
await
|
|
6349
|
+
await fs13.mkdir(path2.dirname(fullPath), { recursive: true });
|
|
6320
6350
|
await atomicWrite(fullPath, substituteVars(content, name, vars));
|
|
6321
6351
|
}
|
|
6322
6352
|
files.push(resolvedPath);
|
|
@@ -6742,7 +6772,7 @@ var testTool = {
|
|
|
6742
6772
|
type: "final",
|
|
6743
6773
|
output: {
|
|
6744
6774
|
runner: "none",
|
|
6745
|
-
exit_code:
|
|
6775
|
+
exit_code: 0,
|
|
6746
6776
|
tests_run: 0,
|
|
6747
6777
|
passed: 0,
|
|
6748
6778
|
failed: 0,
|
|
@@ -6772,14 +6802,14 @@ async function detectRunner(cwd) {
|
|
|
6772
6802
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
6773
6803
|
for (const f of candidates) {
|
|
6774
6804
|
try {
|
|
6775
|
-
await stat10(
|
|
6805
|
+
await stat10(path2.join(cwd, f));
|
|
6776
6806
|
if (f.includes("vitest")) return "vitest";
|
|
6777
6807
|
if (f.includes("jest")) return "jest";
|
|
6778
6808
|
if (f.includes("mocha")) return "mocha";
|
|
6779
6809
|
} catch {
|
|
6780
6810
|
}
|
|
6781
6811
|
}
|
|
6782
|
-
return
|
|
6812
|
+
return null;
|
|
6783
6813
|
}
|
|
6784
6814
|
function buildArgs2(runner, input) {
|
|
6785
6815
|
const args = [];
|
|
@@ -7318,7 +7348,7 @@ var treeTool = {
|
|
|
7318
7348
|
}
|
|
7319
7349
|
};
|
|
7320
7350
|
async function walkDir(dir, depth, opts) {
|
|
7321
|
-
const entries = await
|
|
7351
|
+
const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
7322
7352
|
const filtered = entries.filter((e) => {
|
|
7323
7353
|
if (!opts.showHidden && e.name.startsWith(".")) return false;
|
|
7324
7354
|
if (opts.exclude.has(e.name)) return false;
|
|
@@ -7348,7 +7378,7 @@ async function walkDir(dir, depth, opts) {
|
|
|
7348
7378
|
opts.lines.push(opts.prefix + branch + displayName);
|
|
7349
7379
|
if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
|
|
7350
7380
|
const childPrefix = opts.prefix + connector;
|
|
7351
|
-
await walkDir(
|
|
7381
|
+
await walkDir(path2.join(dir, entry.name), depth + 1, {
|
|
7352
7382
|
...opts,
|
|
7353
7383
|
prefix: childPrefix,
|
|
7354
7384
|
isLast
|
|
@@ -7431,8 +7461,8 @@ async function findTsConfig(cwd) {
|
|
|
7431
7461
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
7432
7462
|
for (const f of candidates) {
|
|
7433
7463
|
try {
|
|
7434
|
-
const s = await stat10(
|
|
7435
|
-
if (s.isFile()) return
|
|
7464
|
+
const s = await stat10(path2.join(cwd, f));
|
|
7465
|
+
if (s.isFile()) return path2.join(cwd, f);
|
|
7436
7466
|
} catch {
|
|
7437
7467
|
}
|
|
7438
7468
|
}
|
|
@@ -7468,14 +7498,14 @@ var writeTool = {
|
|
|
7468
7498
|
let existed = false;
|
|
7469
7499
|
let prev = "";
|
|
7470
7500
|
try {
|
|
7471
|
-
const stat11 = await
|
|
7501
|
+
const stat11 = await fs13.stat(absPath);
|
|
7472
7502
|
existed = stat11.isFile();
|
|
7473
7503
|
if (existed) {
|
|
7474
7504
|
if (!ctx.hasRead(absPath)) {
|
|
7475
|
-
prev = await
|
|
7505
|
+
prev = await fs13.readFile(absPath, "utf8");
|
|
7476
7506
|
ctx.recordRead(absPath, stat11.mtimeMs);
|
|
7477
7507
|
} else {
|
|
7478
|
-
prev = await
|
|
7508
|
+
prev = await fs13.readFile(absPath, "utf8");
|
|
7479
7509
|
}
|
|
7480
7510
|
}
|
|
7481
7511
|
} catch (err) {
|
|
@@ -7486,7 +7516,7 @@ var writeTool = {
|
|
|
7486
7516
|
await atomicWrite(absPath, input.content);
|
|
7487
7517
|
const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
|
|
7488
7518
|
+ (new file, ${input.content.split("\n").length} lines)`;
|
|
7489
|
-
const stat10 = await
|
|
7519
|
+
const stat10 = await fs13.stat(absPath);
|
|
7490
7520
|
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
7491
7521
|
ctx.session.recordFileChange({
|
|
7492
7522
|
path: absPath,
|