@wrongstack/tools 0.5.7 → 0.6.1
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/builtin.js +69 -62
- package/dist/builtin.js.map +1 -1
- package/dist/exec.js +9 -2
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js.map +1 -1
- package/dist/grep.js +9 -4
- package/dist/grep.js.map +1 -1
- package/dist/index.js +69 -62
- package/dist/index.js.map +1 -1
- package/dist/logs.js +9 -4
- package/dist/logs.js.map +1 -1
- package/dist/outdated.js +2 -7
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +69 -62
- package/dist/pack.js.map +1 -1
- package/dist/replace.js +9 -4
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js +2 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs4 from 'fs/promises';
|
|
2
|
+
import { stat } from 'fs/promises';
|
|
2
3
|
import * as path from 'path';
|
|
3
4
|
import { dirname } from 'path';
|
|
4
5
|
import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, buildChildEnv, stripAnsi, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan } from '@wrongstack/core';
|
|
@@ -8,12 +9,7 @@ import * as dns from 'dns/promises';
|
|
|
8
9
|
import * as net from 'net';
|
|
9
10
|
import { statSync } from 'fs';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
13
|
-
}) : x)(function(x) {
|
|
14
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
15
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
16
|
-
});
|
|
12
|
+
// src/read.ts
|
|
17
13
|
function resolvePath(input, ctx) {
|
|
18
14
|
return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
|
|
19
15
|
}
|
|
@@ -67,17 +63,17 @@ var readTool = {
|
|
|
67
63
|
async execute(input, ctx) {
|
|
68
64
|
if (!input?.path) throw new Error("read: path is required");
|
|
69
65
|
const absPath = safeResolve(input.path, ctx);
|
|
70
|
-
let
|
|
66
|
+
let stat10;
|
|
71
67
|
try {
|
|
72
|
-
|
|
68
|
+
stat10 = await fs4.stat(absPath);
|
|
73
69
|
} catch (err) {
|
|
74
70
|
const code = err.code;
|
|
75
71
|
if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
|
|
76
72
|
throw new Error(`read: failed to stat "${input.path}": ${err instanceof Error ? err.message : String(err)}`);
|
|
77
73
|
}
|
|
78
|
-
if (!
|
|
79
|
-
if (
|
|
80
|
-
throw new Error(`read: file too large (${
|
|
74
|
+
if (!stat10.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
|
|
75
|
+
if (stat10.size > MAX_BYTES) {
|
|
76
|
+
throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES})`);
|
|
81
77
|
}
|
|
82
78
|
const buf = await fs4.readFile(absPath);
|
|
83
79
|
if (isBinaryBuffer(buf)) {
|
|
@@ -89,14 +85,14 @@ var readTool = {
|
|
|
89
85
|
const offset = Math.max(1, input.offset ?? 1);
|
|
90
86
|
const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
|
|
91
87
|
if (limit === 0) {
|
|
92
|
-
ctx.recordRead(absPath,
|
|
88
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
93
89
|
return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
|
|
94
90
|
}
|
|
95
91
|
const slice = allLines.slice(offset - 1, offset - 1 + limit);
|
|
96
92
|
const truncated = offset - 1 + slice.length < total;
|
|
97
93
|
const width = String(offset + slice.length - 1).length;
|
|
98
94
|
const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
|
|
99
|
-
ctx.recordRead(absPath,
|
|
95
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
100
96
|
return {
|
|
101
97
|
text: numbered,
|
|
102
98
|
total_lines: total,
|
|
@@ -128,12 +124,12 @@ var writeTool = {
|
|
|
128
124
|
let existed = false;
|
|
129
125
|
let prev = "";
|
|
130
126
|
try {
|
|
131
|
-
const
|
|
132
|
-
existed =
|
|
127
|
+
const stat11 = await fs4.stat(absPath);
|
|
128
|
+
existed = stat11.isFile();
|
|
133
129
|
if (existed) {
|
|
134
130
|
if (!ctx.hasRead(absPath)) {
|
|
135
131
|
prev = await fs4.readFile(absPath, "utf8");
|
|
136
|
-
ctx.recordRead(absPath,
|
|
132
|
+
ctx.recordRead(absPath, stat11.mtimeMs);
|
|
137
133
|
} else {
|
|
138
134
|
prev = await fs4.readFile(absPath, "utf8");
|
|
139
135
|
}
|
|
@@ -146,8 +142,8 @@ var writeTool = {
|
|
|
146
142
|
await atomicWrite(absPath, input.content);
|
|
147
143
|
const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
|
|
148
144
|
+ (new file, ${input.content.split("\n").length} lines)`;
|
|
149
|
-
const
|
|
150
|
-
ctx.recordRead(absPath,
|
|
145
|
+
const stat10 = await fs4.stat(absPath);
|
|
146
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
151
147
|
ctx.session.recordFileChange({
|
|
152
148
|
path: absPath,
|
|
153
149
|
action: existed ? "modified" : "created",
|
|
@@ -186,13 +182,13 @@ var editTool = {
|
|
|
186
182
|
if (input.new_string === void 0) throw new Error("edit: new_string is required");
|
|
187
183
|
if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
|
|
188
184
|
const absPath = safeResolve(input.path, ctx);
|
|
189
|
-
const
|
|
185
|
+
const stat10 = await fs4.stat(absPath).catch((err) => {
|
|
190
186
|
if (err.code === "ENOENT") {
|
|
191
187
|
throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
|
|
192
188
|
}
|
|
193
189
|
throw err;
|
|
194
190
|
});
|
|
195
|
-
if (!
|
|
191
|
+
if (!stat10.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
|
|
196
192
|
if (!ctx.hasRead(absPath)) {
|
|
197
193
|
throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
|
|
198
194
|
}
|
|
@@ -281,12 +277,17 @@ function findSimilarity(haystack, needle) {
|
|
|
281
277
|
}
|
|
282
278
|
|
|
283
279
|
// src/_regex.ts
|
|
284
|
-
var MAX_PATTERN_LEN =
|
|
280
|
+
var MAX_PATTERN_LEN = 256;
|
|
285
281
|
var DANGEROUS_PATTERNS = [
|
|
286
|
-
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
287
282
|
// (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
|
|
288
|
-
/(\(
|
|
289
|
-
|
|
283
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
284
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/,
|
|
285
|
+
// Adjacent quantifiers: a++ a*+
|
|
286
|
+
/[+*]{2,}/,
|
|
287
|
+
// Quantifier on alternation with length 2+
|
|
288
|
+
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
289
|
+
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
290
|
+
/[\(\[][^)\]]*[+*][^)\]]*[\)\]][^)]*\?\??/
|
|
290
291
|
];
|
|
291
292
|
function compileUserRegex(pattern, flags) {
|
|
292
293
|
if (typeof pattern !== "string") {
|
|
@@ -379,8 +380,8 @@ var replaceTool = {
|
|
|
379
380
|
}
|
|
380
381
|
const rel = path.relative(ctx.projectRoot, realPath);
|
|
381
382
|
if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
|
|
382
|
-
const
|
|
383
|
-
if (!
|
|
383
|
+
const stat10 = await fs4.stat(realPath).catch(() => null);
|
|
384
|
+
if (!stat10 || !stat10.isFile()) continue;
|
|
384
385
|
let content;
|
|
385
386
|
try {
|
|
386
387
|
const buf = await fs4.readFile(realPath);
|
|
@@ -405,7 +406,7 @@ var replaceTool = {
|
|
|
405
406
|
totalReplacements += count;
|
|
406
407
|
if (!dryRun) {
|
|
407
408
|
const newContent = toStyle(newContentLf, style);
|
|
408
|
-
await atomicWrite(realPath, newContent, { mode:
|
|
409
|
+
await atomicWrite(realPath, newContent, { mode: stat10.mode & 511 });
|
|
409
410
|
}
|
|
410
411
|
const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
|
|
411
412
|
fromFile: absPath,
|
|
@@ -435,8 +436,8 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
|
|
|
435
436
|
const resolved = [];
|
|
436
437
|
for (const p of parts) {
|
|
437
438
|
const absPath = safeResolve(p, ctx);
|
|
438
|
-
const
|
|
439
|
-
if (
|
|
439
|
+
const stat10 = await fs4.stat(absPath).catch(() => null);
|
|
440
|
+
if (stat10?.isFile()) {
|
|
440
441
|
resolved.push(absPath);
|
|
441
442
|
}
|
|
442
443
|
}
|
|
@@ -495,8 +496,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
495
496
|
if (DEFAULT_IGNORE.includes(e.name)) continue;
|
|
496
497
|
const full = path.join(dir, e.name);
|
|
497
498
|
try {
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
499
|
+
const stat10 = await fs4.lstat(full);
|
|
500
|
+
if (stat10.isSymbolicLink()) continue;
|
|
500
501
|
} catch {
|
|
501
502
|
continue;
|
|
502
503
|
}
|
|
@@ -812,8 +813,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
812
813
|
if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
|
|
813
814
|
if (globRe) globRe.lastIndex = 0;
|
|
814
815
|
try {
|
|
815
|
-
const
|
|
816
|
-
if (
|
|
816
|
+
const stat10 = await fs4.stat(full);
|
|
817
|
+
if (stat10.size > 1e6) continue;
|
|
817
818
|
const head = await fs4.readFile(full);
|
|
818
819
|
if (isBinaryBuffer(head)) continue;
|
|
819
820
|
const text = head.toString("utf8");
|
|
@@ -1485,14 +1486,21 @@ var BLOCKED_ARG_PATTERNS = {
|
|
|
1485
1486
|
// go run could execute arbitrary .go files; -ldflags could inject build-time code
|
|
1486
1487
|
go: [/^-ldflags$/],
|
|
1487
1488
|
// bun --preload is similar to node --require
|
|
1488
|
-
bun: [/^--preload$/],
|
|
1489
|
+
bun: [/^--preload$/, /^run$/, /^bunx$/, /^create$/, /^init$/],
|
|
1489
1490
|
// docker build/run can create containers with host access;
|
|
1490
1491
|
// only allow read-only commands (ps, images, version)
|
|
1491
1492
|
docker: [/^build$/, /^run$/, /^exec$/, /^push$/, /^pull$/],
|
|
1492
1493
|
// find -exec/-ok/-execdir execute arbitrary commands
|
|
1493
1494
|
find: [/^-exec$/, /^-exec;$/, /^-ok$/, /^-ok;$/, /^-execdir$/, /^-execdir;$/, /^-exec=/, /^-ok=/, /^-execdir=/],
|
|
1494
1495
|
// rm -rf / is catastrophic — block root and home targets
|
|
1495
|
-
rm: [/^\/$/, /^\/\*$/, /^~$/]
|
|
1496
|
+
rm: [/^\/$/, /^\/\*$/, /^~$/],
|
|
1497
|
+
// npm run/exec/create/pack/publish can execute arbitrary scripts or publish malware
|
|
1498
|
+
npm: [/^run$/, /^exec$/, /^create$/, /^init$/, /^pack$/, /^publish$/, /^deploy$/],
|
|
1499
|
+
// pnpm run/dlx/exec/create can execute arbitrary scripts
|
|
1500
|
+
pnpm: [/^run$/, /^dlx$/, /^exec$/, /^create$/, /^init$/, /^pack$/, /^publish$/, /^deploy$/],
|
|
1501
|
+
// npx should only be used for --version; any package name is a vector for
|
|
1502
|
+
// malicious package execution (typosquatting, dependency confusion)
|
|
1503
|
+
npx: [/^[^\s]+$/]
|
|
1496
1504
|
};
|
|
1497
1505
|
function validateArgs(cmd, args) {
|
|
1498
1506
|
const blocked = BLOCKED_ARG_PATTERNS[cmd];
|
|
@@ -2412,8 +2420,8 @@ function findGitDir(cwd, projectRoot) {
|
|
|
2412
2420
|
let dir = cwd;
|
|
2413
2421
|
for (let i = 0; i < 20; i++) {
|
|
2414
2422
|
try {
|
|
2415
|
-
const
|
|
2416
|
-
if (
|
|
2423
|
+
const stat10 = statSync(`${dir}/.git`);
|
|
2424
|
+
if (stat10.isDirectory()) return dir;
|
|
2417
2425
|
} catch {
|
|
2418
2426
|
}
|
|
2419
2427
|
if (dir === root) break;
|
|
@@ -2798,8 +2806,8 @@ function findGitDir2(cwd) {
|
|
|
2798
2806
|
let dir = cwd;
|
|
2799
2807
|
for (let i = 0; i < 20; i++) {
|
|
2800
2808
|
try {
|
|
2801
|
-
const
|
|
2802
|
-
if (
|
|
2809
|
+
const stat10 = statSync(path.join(dir, ".git"));
|
|
2810
|
+
if (stat10.isDirectory()) return dir;
|
|
2803
2811
|
} catch {
|
|
2804
2812
|
}
|
|
2805
2813
|
const parent = path.dirname(dir);
|
|
@@ -2838,8 +2846,8 @@ async function fileDiff(input, ctx, signal) {
|
|
|
2838
2846
|
const results = [];
|
|
2839
2847
|
for (const file of files) {
|
|
2840
2848
|
const absPath = safeResolve(file, ctx);
|
|
2841
|
-
const
|
|
2842
|
-
if (!
|
|
2849
|
+
const stat10 = await fs4.stat(absPath).catch(() => null);
|
|
2850
|
+
if (!stat10?.isFile()) continue;
|
|
2843
2851
|
const content = await fs4.readFile(absPath, "utf8");
|
|
2844
2852
|
const lines = content.split(/\r?\n/);
|
|
2845
2853
|
results.push(`--- ${file}
|
|
@@ -3181,11 +3189,11 @@ var lintTool = {
|
|
|
3181
3189
|
}
|
|
3182
3190
|
};
|
|
3183
3191
|
async function detectLinter(cwd) {
|
|
3184
|
-
const { stat:
|
|
3192
|
+
const { stat: stat10 } = await import('fs/promises');
|
|
3185
3193
|
const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
|
|
3186
3194
|
for (const f of checks) {
|
|
3187
3195
|
try {
|
|
3188
|
-
await
|
|
3196
|
+
await stat10(`${cwd}/${f}`);
|
|
3189
3197
|
if (f.includes("biome")) return "biome";
|
|
3190
3198
|
if (f.includes("eslint")) return "eslint";
|
|
3191
3199
|
if (f.includes("tslint")) return "tslint";
|
|
@@ -3280,13 +3288,13 @@ var formatTool = {
|
|
|
3280
3288
|
}
|
|
3281
3289
|
};
|
|
3282
3290
|
async function detectFixer(cwd) {
|
|
3283
|
-
const { stat:
|
|
3291
|
+
const { stat: stat10 } = await import('fs/promises');
|
|
3284
3292
|
try {
|
|
3285
|
-
await
|
|
3293
|
+
await stat10(`${cwd}/biome.json`);
|
|
3286
3294
|
return "biome";
|
|
3287
3295
|
} catch {
|
|
3288
3296
|
try {
|
|
3289
|
-
await
|
|
3297
|
+
await stat10(`${cwd}/.prettierrc`);
|
|
3290
3298
|
return "prettier";
|
|
3291
3299
|
} catch {
|
|
3292
3300
|
return "biome";
|
|
@@ -3362,11 +3370,11 @@ var typecheckTool = {
|
|
|
3362
3370
|
}
|
|
3363
3371
|
};
|
|
3364
3372
|
async function findTsConfig(cwd) {
|
|
3365
|
-
const { stat:
|
|
3373
|
+
const { stat: stat10 } = await import('fs/promises');
|
|
3366
3374
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
3367
3375
|
for (const f of candidates) {
|
|
3368
3376
|
try {
|
|
3369
|
-
const s = await
|
|
3377
|
+
const s = await stat10(path.join(cwd, f));
|
|
3370
3378
|
if (s.isFile()) return path.join(cwd, f);
|
|
3371
3379
|
} catch {
|
|
3372
3380
|
}
|
|
@@ -3443,11 +3451,11 @@ var testTool = {
|
|
|
3443
3451
|
}
|
|
3444
3452
|
};
|
|
3445
3453
|
async function detectRunner(cwd) {
|
|
3446
|
-
const { stat:
|
|
3454
|
+
const { stat: stat10 } = await import('fs/promises');
|
|
3447
3455
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
3448
3456
|
for (const f of candidates) {
|
|
3449
3457
|
try {
|
|
3450
|
-
await
|
|
3458
|
+
await stat10(path.join(cwd, f));
|
|
3451
3459
|
if (f.includes("vitest")) return "vitest";
|
|
3452
3460
|
if (f.includes("jest")) return "jest";
|
|
3453
3461
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -3618,13 +3626,13 @@ var installTool = {
|
|
|
3618
3626
|
}
|
|
3619
3627
|
};
|
|
3620
3628
|
async function detectPackageManager(cwd) {
|
|
3621
|
-
const { stat:
|
|
3629
|
+
const { stat: stat10 } = await import('fs/promises');
|
|
3622
3630
|
try {
|
|
3623
|
-
await
|
|
3631
|
+
await stat10(`${cwd}/pnpm-lock.yaml`);
|
|
3624
3632
|
return "pnpm";
|
|
3625
3633
|
} catch {
|
|
3626
3634
|
try {
|
|
3627
|
-
await
|
|
3635
|
+
await stat10(`${cwd}/yarn.lock`);
|
|
3628
3636
|
return "yarn";
|
|
3629
3637
|
} catch {
|
|
3630
3638
|
return "npm";
|
|
@@ -3683,14 +3691,14 @@ var auditTool = {
|
|
|
3683
3691
|
}
|
|
3684
3692
|
};
|
|
3685
3693
|
async function detectManager(cwd) {
|
|
3686
|
-
const { stat:
|
|
3694
|
+
const { stat: stat10 } = await import('fs/promises');
|
|
3687
3695
|
try {
|
|
3688
|
-
await
|
|
3696
|
+
await stat10(`${cwd}/pnpm-lock.yaml`);
|
|
3689
3697
|
return "pnpm";
|
|
3690
3698
|
} catch {
|
|
3691
3699
|
}
|
|
3692
3700
|
try {
|
|
3693
|
-
await
|
|
3701
|
+
await stat10(`${cwd}/yarn.lock`);
|
|
3694
3702
|
return "yarn";
|
|
3695
3703
|
} catch {
|
|
3696
3704
|
}
|
|
@@ -3778,14 +3786,13 @@ var outdatedTool = {
|
|
|
3778
3786
|
}
|
|
3779
3787
|
};
|
|
3780
3788
|
async function detectManager2(cwd) {
|
|
3781
|
-
const { stat: stat9 } = __require("fs/promises");
|
|
3782
3789
|
try {
|
|
3783
|
-
await
|
|
3790
|
+
await stat(`${cwd}/pnpm-lock.yaml`);
|
|
3784
3791
|
return "pnpm";
|
|
3785
3792
|
} catch {
|
|
3786
3793
|
}
|
|
3787
3794
|
try {
|
|
3788
|
-
await
|
|
3795
|
+
await stat(`${cwd}/yarn.lock`);
|
|
3789
3796
|
return "yarn";
|
|
3790
3797
|
} catch {
|
|
3791
3798
|
}
|
|
@@ -4121,8 +4128,8 @@ async function resolveFiles2(filesInput, cwd) {
|
|
|
4121
4128
|
for (const f of files) {
|
|
4122
4129
|
const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
|
|
4123
4130
|
try {
|
|
4124
|
-
const
|
|
4125
|
-
if (
|
|
4131
|
+
const stat10 = await fs4.stat(absPath);
|
|
4132
|
+
if (stat10.isFile()) resolved.push(absPath);
|
|
4126
4133
|
} catch {
|
|
4127
4134
|
}
|
|
4128
4135
|
}
|
|
@@ -4345,7 +4352,7 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
|
4345
4352
|
const fullPath = target;
|
|
4346
4353
|
if (!dryRun) {
|
|
4347
4354
|
await fs4.mkdir(path.dirname(fullPath), { recursive: true });
|
|
4348
|
-
await
|
|
4355
|
+
await atomicWrite(fullPath, substituteVars(content, name, vars));
|
|
4349
4356
|
}
|
|
4350
4357
|
files.push(resolvedPath);
|
|
4351
4358
|
filesCreated++;
|