@wrongstack/tools 0.269.0 → 0.272.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 +10 -5
- package/dist/audit.js.map +1 -1
- package/dist/{background-indexer-CJ5JiV5i.d.ts → background-indexer-BoTUw0EM.d.ts} +1 -1
- package/dist/bash.js +591 -11
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +857 -174
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.d.ts +13 -3
- package/dist/codebase-index/index.js +129 -74
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/codebase-index/worker.js +126 -71
- package/dist/codebase-index/worker.js.map +1 -1
- package/dist/exec.js +5 -2
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js +24 -1
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +10 -5
- package/dist/format.js.map +1 -1
- package/dist/grep.js +55 -33
- package/dist/grep.js.map +1 -1
- package/dist/index.d.ts +317 -3
- package/dist/index.js +1416 -202
- package/dist/index.js.map +1 -1
- package/dist/install.js +10 -5
- package/dist/install.js.map +1 -1
- package/dist/lint.js +10 -5
- package/dist/lint.js.map +1 -1
- package/dist/pack.js +857 -174
- package/dist/pack.js.map +1 -1
- package/dist/process-registry.js +5 -2
- package/dist/process-registry.js.map +1 -1
- package/dist/read.js +2 -2
- package/dist/read.js.map +1 -1
- package/dist/search.js.map +1 -1
- package/dist/test.js +10 -5
- package/dist/test.js.map +1 -1
- package/dist/typecheck.js +10 -5
- package/dist/typecheck.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { C as CircuitOpenError,
|
|
1
|
+
import { I as IndexResult, S as Symbol, F as FileMeta, a as SymbolKind, b as SymbolLang, c as SearchResult, d as IndexStats, R as Ref } from '../background-indexer-BoTUw0EM.js';
|
|
2
|
+
export { C as CircuitOpenError, e as CircuitSnapshot, f as CircuitState, g as FileSymbols, h as IndexCircuitBreaker, i as IndexTimeoutError, j as SCHEMA_VERSION, k as cancelPendingReindexes, l as codebaseIndexStats, m as codebaseIndexTool, n as codebaseSearchTool, o as codebaseStatsTool, p as enqueueReindex, q as getIndexState, r as indexCircuitBreaker, s as isIndexReady, t as isIndexableFile, u as isIndexing, v as onIndexStateChange, w as resetIndexCircuitBreaker, x as runStartupIndex, y as searchCodebaseIndex, z as shutdownCodebaseIndexHost } from '../background-indexer-BoTUw0EM.js';
|
|
3
3
|
import { Context } from '@wrongstack/core';
|
|
4
4
|
|
|
5
5
|
interface IndexerOptions {
|
|
@@ -70,7 +70,17 @@ declare class IndexStore {
|
|
|
70
70
|
indexDir?: string | undefined;
|
|
71
71
|
});
|
|
72
72
|
private initSchema;
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Insert symbols, assigning IDs atomically inside `BEGIN IMMEDIATE` /
|
|
75
|
+
* `COMMIT`. The ID allocation (`SELECT MAX(id)`) and all `INSERT`s share
|
|
76
|
+
* the same transaction, preventing UNIQUE constraint violations when two
|
|
77
|
+
* processes index concurrently (each would see a different `MAX(id)` and
|
|
78
|
+
* neither can insert with the other's IDs).
|
|
79
|
+
*
|
|
80
|
+
* @returns The symbols array with `id` fields populated so the caller can
|
|
81
|
+
* use them for refs without re-reading from the DB.
|
|
82
|
+
*/
|
|
83
|
+
insertSymbols(symbols: Symbol[]): Symbol[];
|
|
74
84
|
deleteSymbolsForFile(file: string): void;
|
|
75
85
|
/**
|
|
76
86
|
* Remove every trace of a file (refs, symbols, FTS rows, file meta). Used
|
|
@@ -2,13 +2,12 @@ import { resolveWstackPaths, expectDefined, compileGlob, truncate } from '@wrong
|
|
|
2
2
|
import { toErrorMessage } from '@wrongstack/core/utils';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
|
-
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
6
5
|
import * as path4 from 'node:path';
|
|
7
6
|
import { fileURLToPath } from 'node:url';
|
|
8
7
|
import { Worker } from 'node:worker_threads';
|
|
9
|
-
import * as
|
|
8
|
+
import * as fs6 from 'node:fs/promises';
|
|
10
9
|
import * as ts from 'typescript';
|
|
11
|
-
import { execFileSync,
|
|
10
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
12
11
|
import * as os from 'node:os';
|
|
13
12
|
|
|
14
13
|
// src/codebase-index/writer.ts
|
|
@@ -428,33 +427,53 @@ var IndexStore = class {
|
|
|
428
427
|
}
|
|
429
428
|
}
|
|
430
429
|
// ─── Symbol CRUD ─────────────────────────────────────────────────────────────
|
|
431
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Insert symbols, assigning IDs atomically inside `BEGIN IMMEDIATE` /
|
|
432
|
+
* `COMMIT`. The ID allocation (`SELECT MAX(id)`) and all `INSERT`s share
|
|
433
|
+
* the same transaction, preventing UNIQUE constraint violations when two
|
|
434
|
+
* processes index concurrently (each would see a different `MAX(id)` and
|
|
435
|
+
* neither can insert with the other's IDs).
|
|
436
|
+
*
|
|
437
|
+
* @returns The symbols array with `id` fields populated so the caller can
|
|
438
|
+
* use them for refs without re-reading from the DB.
|
|
439
|
+
*/
|
|
440
|
+
insertSymbols(symbols) {
|
|
432
441
|
return this.runWithRetry(() => {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
stmt.run(
|
|
441
|
-
id,
|
|
442
|
-
s.lang,
|
|
443
|
-
s.kind,
|
|
444
|
-
s.name,
|
|
445
|
-
s.file,
|
|
446
|
-
s.line,
|
|
447
|
-
s.col,
|
|
448
|
-
s.signature,
|
|
449
|
-
s.docComment,
|
|
450
|
-
s.scope,
|
|
451
|
-
s.text,
|
|
452
|
-
s.file
|
|
442
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
443
|
+
try {
|
|
444
|
+
const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
|
|
445
|
+
let nextId = (maxRows[0]?.m ?? 0) + 1;
|
|
446
|
+
const stmt = this.db.prepare(
|
|
447
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
|
|
448
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
453
449
|
);
|
|
454
|
-
ftsStmt
|
|
455
|
-
|
|
450
|
+
const ftsStmt = this.ftsAvailable ? this.db.prepare("INSERT INTO symbols_fts(rowid, text) VALUES (?, ?)") : null;
|
|
451
|
+
const result = [];
|
|
452
|
+
for (const s of symbols) {
|
|
453
|
+
const id = nextId++;
|
|
454
|
+
stmt.run(
|
|
455
|
+
id,
|
|
456
|
+
s.lang,
|
|
457
|
+
s.kind,
|
|
458
|
+
s.name,
|
|
459
|
+
s.file,
|
|
460
|
+
s.line,
|
|
461
|
+
s.col,
|
|
462
|
+
s.signature,
|
|
463
|
+
s.docComment,
|
|
464
|
+
s.scope,
|
|
465
|
+
s.text,
|
|
466
|
+
s.file
|
|
467
|
+
);
|
|
468
|
+
ftsStmt?.run(id, buildIndexableText(s.name, s.signature, s.docComment));
|
|
469
|
+
result.push({ ...s, id });
|
|
470
|
+
}
|
|
471
|
+
this.db.exec("COMMIT");
|
|
472
|
+
return result;
|
|
473
|
+
} catch (err) {
|
|
474
|
+
this.db.exec("ROLLBACK");
|
|
475
|
+
throw err;
|
|
456
476
|
}
|
|
457
|
-
return id;
|
|
458
477
|
});
|
|
459
478
|
}
|
|
460
479
|
deleteSymbolsForFile(file) {
|
|
@@ -1008,10 +1027,10 @@ function detectLang(file) {
|
|
|
1008
1027
|
if (idx < 0) return null;
|
|
1009
1028
|
return extToLang(file.slice(idx));
|
|
1010
1029
|
}
|
|
1011
|
-
function parseSymbols2(opts) {
|
|
1030
|
+
async function parseSymbols2(opts) {
|
|
1012
1031
|
const { file, content, lang } = opts;
|
|
1013
1032
|
try {
|
|
1014
|
-
return syncGoParse(file, content, lang);
|
|
1033
|
+
return await syncGoParse(file, content, lang);
|
|
1015
1034
|
} catch {
|
|
1016
1035
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
1017
1036
|
}
|
|
@@ -1248,19 +1267,34 @@ func formatType(t ast.Expr) string {
|
|
|
1248
1267
|
}
|
|
1249
1268
|
}
|
|
1250
1269
|
`;
|
|
1251
|
-
function syncGoParse(filePath, content, lang) {
|
|
1270
|
+
async function syncGoParse(filePath, content, lang) {
|
|
1252
1271
|
const tmpDir = path4.join(os.tmpdir(), "ws-go-parse");
|
|
1253
1272
|
try {
|
|
1254
|
-
|
|
1273
|
+
await fs6.mkdir(tmpDir, { recursive: true });
|
|
1255
1274
|
const scriptPath = path4.join(tmpDir, "parse.go");
|
|
1256
|
-
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
timeout: 15e3,
|
|
1260
|
-
encoding: "utf8",
|
|
1275
|
+
await fs6.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
1276
|
+
const proc = spawn("go", ["run", scriptPath], {
|
|
1277
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1261
1278
|
windowsHide: true
|
|
1262
1279
|
});
|
|
1263
|
-
|
|
1280
|
+
let stdout = "";
|
|
1281
|
+
proc.stdout?.on("data", (chunk) => {
|
|
1282
|
+
stdout += chunk.toString();
|
|
1283
|
+
});
|
|
1284
|
+
proc.stdin?.write(content);
|
|
1285
|
+
proc.stdin?.end();
|
|
1286
|
+
const { code } = await Promise.race([
|
|
1287
|
+
new Promise((resolve2) => {
|
|
1288
|
+
proc.on("close", (c) => resolve2({ code: c }));
|
|
1289
|
+
}),
|
|
1290
|
+
new Promise(
|
|
1291
|
+
(_, reject) => setTimeout(() => {
|
|
1292
|
+
proc.kill("SIGKILL");
|
|
1293
|
+
reject(new Error("timeout"));
|
|
1294
|
+
}, 15e3)
|
|
1295
|
+
)
|
|
1296
|
+
]).catch(() => ({ code: -1 }));
|
|
1297
|
+
if (code !== 0 || !stdout.trim()) {
|
|
1264
1298
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1265
1299
|
}
|
|
1266
1300
|
const raw = JSON.parse(stdout.trim());
|
|
@@ -1282,10 +1316,10 @@ function syncGoParse(filePath, content, lang) {
|
|
|
1282
1316
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1283
1317
|
}
|
|
1284
1318
|
}
|
|
1285
|
-
function parseSymbols3(opts) {
|
|
1319
|
+
async function parseSymbols3(opts) {
|
|
1286
1320
|
const { file, lang } = opts;
|
|
1287
1321
|
try {
|
|
1288
|
-
return syncPyParse(file, lang);
|
|
1322
|
+
return await syncPyParse(file, lang);
|
|
1289
1323
|
} catch {
|
|
1290
1324
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
1291
1325
|
}
|
|
@@ -1494,18 +1528,32 @@ visitor.visit(tree)
|
|
|
1494
1528
|
|
|
1495
1529
|
print(json.dumps([s.to_dict() for s in syms]))
|
|
1496
1530
|
`;
|
|
1497
|
-
function syncPyParse(filePath, lang) {
|
|
1531
|
+
async function syncPyParse(filePath, lang) {
|
|
1498
1532
|
try {
|
|
1499
1533
|
const tmpDir = path4.join(os.tmpdir(), "ws-py-parse");
|
|
1500
|
-
|
|
1534
|
+
await fs6.mkdir(tmpDir, { recursive: true });
|
|
1501
1535
|
const scriptPath = path4.join(tmpDir, "parse.py");
|
|
1502
|
-
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
encoding: "utf8",
|
|
1536
|
+
await fs6.writeFile(scriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
1537
|
+
const proc = spawn("python", [scriptPath, filePath], {
|
|
1538
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1506
1539
|
windowsHide: true
|
|
1507
1540
|
});
|
|
1508
|
-
|
|
1541
|
+
let stdout = "";
|
|
1542
|
+
proc.stdout?.on("data", (chunk) => {
|
|
1543
|
+
stdout += chunk.toString();
|
|
1544
|
+
});
|
|
1545
|
+
const { code } = await Promise.race([
|
|
1546
|
+
new Promise((resolve2) => {
|
|
1547
|
+
proc.on("close", (c) => resolve2({ code: c }));
|
|
1548
|
+
}),
|
|
1549
|
+
new Promise(
|
|
1550
|
+
(_, reject) => setTimeout(() => {
|
|
1551
|
+
proc.kill("SIGKILL");
|
|
1552
|
+
reject(new Error("timeout"));
|
|
1553
|
+
}, 15e3)
|
|
1554
|
+
)
|
|
1555
|
+
]).catch(() => ({ code: -1 }));
|
|
1556
|
+
if (code !== 0 || !stdout.trim()) {
|
|
1509
1557
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1510
1558
|
}
|
|
1511
1559
|
const raw = JSON.parse(stdout.trim());
|
|
@@ -1527,11 +1575,11 @@ function syncPyParse(filePath, lang) {
|
|
|
1527
1575
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1528
1576
|
}
|
|
1529
1577
|
}
|
|
1530
|
-
function parseSymbols4(opts) {
|
|
1578
|
+
async function parseSymbols4(opts) {
|
|
1531
1579
|
const { file, content, lang } = opts;
|
|
1532
1580
|
const nativeAvailable = checkNativeParser();
|
|
1533
1581
|
if (nativeAvailable) {
|
|
1534
|
-
const result = tryNativeParse(file, content);
|
|
1582
|
+
const result = await tryNativeParse(file, content);
|
|
1535
1583
|
if (result) return result;
|
|
1536
1584
|
}
|
|
1537
1585
|
return regexParse({ file, content, lang });
|
|
@@ -1561,25 +1609,34 @@ function checkNativeParser() {
|
|
|
1561
1609
|
return false;
|
|
1562
1610
|
}
|
|
1563
1611
|
}
|
|
1564
|
-
function tryNativeParse(file, content) {
|
|
1612
|
+
async function tryNativeParse(file, content) {
|
|
1565
1613
|
try {
|
|
1566
1614
|
const toolsDir = path4.join(process.cwd(), "tools");
|
|
1567
1615
|
const crateDir = path4.join(toolsDir, "syn-parser");
|
|
1568
1616
|
const tmpFile = path4.join(crateDir, "src", "input.rs");
|
|
1569
|
-
|
|
1570
|
-
const
|
|
1571
|
-
|
|
1572
|
-
["
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1617
|
+
await fs6.writeFile(tmpFile, content, "utf8");
|
|
1618
|
+
const proc = spawn("cargo", ["run", "--manifest-path", path4.join(toolsDir, "Cargo.toml")], {
|
|
1619
|
+
cwd: process.cwd(),
|
|
1620
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1621
|
+
windowsHide: true
|
|
1622
|
+
});
|
|
1623
|
+
let stdout = "";
|
|
1624
|
+
proc.stdout?.on("data", (chunk) => {
|
|
1625
|
+
stdout += chunk.toString();
|
|
1626
|
+
});
|
|
1627
|
+
const { code } = await Promise.race([
|
|
1628
|
+
new Promise((resolve2) => {
|
|
1629
|
+
proc.on("close", (c) => resolve2({ code: c }));
|
|
1630
|
+
}),
|
|
1631
|
+
new Promise(
|
|
1632
|
+
(_, reject) => setTimeout(() => {
|
|
1633
|
+
proc.kill("SIGKILL");
|
|
1634
|
+
reject(new Error("timeout"));
|
|
1635
|
+
}, 15e3)
|
|
1636
|
+
)
|
|
1637
|
+
]).catch(() => ({ code: -1 }));
|
|
1638
|
+
if (code === 0 && stdout.trim()) {
|
|
1639
|
+
const symbols = JSON.parse(stdout.trim());
|
|
1583
1640
|
return {
|
|
1584
1641
|
file,
|
|
1585
1642
|
lang: "rs",
|
|
@@ -2048,7 +2105,7 @@ function compileGitignore(lines) {
|
|
|
2048
2105
|
async function loadGitignoreMatcher(projectRoot) {
|
|
2049
2106
|
let lines = [];
|
|
2050
2107
|
try {
|
|
2051
|
-
const raw = await
|
|
2108
|
+
const raw = await fs6.readFile(path4.join(projectRoot, ".gitignore"), "utf8");
|
|
2052
2109
|
lines = raw.split("\n");
|
|
2053
2110
|
} catch {
|
|
2054
2111
|
}
|
|
@@ -2106,7 +2163,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
2106
2163
|
}
|
|
2107
2164
|
let entries;
|
|
2108
2165
|
try {
|
|
2109
|
-
entries = await
|
|
2166
|
+
entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
2110
2167
|
} catch {
|
|
2111
2168
|
return;
|
|
2112
2169
|
}
|
|
@@ -2204,7 +2261,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2204
2261
|
batchFiles.map(async (file) => {
|
|
2205
2262
|
let stat2;
|
|
2206
2263
|
try {
|
|
2207
|
-
stat2 = await
|
|
2264
|
+
stat2 = await fs6.stat(file, statOpts);
|
|
2208
2265
|
} catch (e) {
|
|
2209
2266
|
if (isAbortError(e)) throw e;
|
|
2210
2267
|
return { file, stat: null, lang: "", parsed: null, error: `stat error: ${e instanceof Error ? e.message : String(e)}` };
|
|
@@ -2218,7 +2275,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2218
2275
|
}
|
|
2219
2276
|
let content;
|
|
2220
2277
|
try {
|
|
2221
|
-
content = await
|
|
2278
|
+
content = await fs6.readFile(file, { encoding: "utf8", signal });
|
|
2222
2279
|
} catch (e) {
|
|
2223
2280
|
if (isAbortError(e)) throw e;
|
|
2224
2281
|
return { file, stat: stat2, lang, parsed: null, error: `read error: ${e instanceof Error ? e.message : String(e)}` };
|
|
@@ -2268,9 +2325,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2268
2325
|
filesIndexed++;
|
|
2269
2326
|
continue;
|
|
2270
2327
|
}
|
|
2271
|
-
const
|
|
2272
|
-
const symbolsWithIds = parsed.symbols.map((s, i) => ({ ...s, id: nextId + i }));
|
|
2273
|
-
store.insertSymbols(symbolsWithIds, nextId);
|
|
2328
|
+
const symbolsWithIds = store.insertSymbols(parsed.symbols);
|
|
2274
2329
|
const count = symbolsWithIds.length;
|
|
2275
2330
|
symbolsIndexed += count;
|
|
2276
2331
|
langStats[lang] = (langStats[lang] ?? 0) + count;
|
|
@@ -2299,7 +2354,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
2299
2354
|
}
|
|
2300
2355
|
for (const [file_] of existingMeta) {
|
|
2301
2356
|
try {
|
|
2302
|
-
await
|
|
2357
|
+
await fs6.stat(file_);
|
|
2303
2358
|
} catch {
|
|
2304
2359
|
store.deleteFile(file_);
|
|
2305
2360
|
}
|
|
@@ -2572,10 +2627,10 @@ function debounceKey(indexDir, file) {
|
|
|
2572
2627
|
function isIndexableFile(filePath) {
|
|
2573
2628
|
return detectLang(filePath) !== null;
|
|
2574
2629
|
}
|
|
2575
|
-
function
|
|
2630
|
+
function isRecoverableConstraintError(err) {
|
|
2576
2631
|
if (err instanceof Error) {
|
|
2577
2632
|
const msg = err.message.toLowerCase();
|
|
2578
|
-
return msg.includes("unique constraint") || msg.includes("
|
|
2633
|
+
return msg.includes("unique constraint") || msg.includes("constraint failed");
|
|
2579
2634
|
}
|
|
2580
2635
|
return false;
|
|
2581
2636
|
}
|
|
@@ -2608,7 +2663,7 @@ async function runStartupIndex(opts) {
|
|
|
2608
2663
|
return result;
|
|
2609
2664
|
} catch (err) {
|
|
2610
2665
|
_lastError = err instanceof Error ? err.message : String(err);
|
|
2611
|
-
if (
|
|
2666
|
+
if (isRecoverableConstraintError(err) && !opts.force) {
|
|
2612
2667
|
_lastError = null;
|
|
2613
2668
|
const rebuildResult = await runStartupIndex({
|
|
2614
2669
|
...opts,
|