@youtyan/code-viewer 0.6.9 → 0.7.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/README.md +21 -13
- package/dist/code-viewer.js +2083 -372
- package/package.json +1 -1
- package/web/app.js +1102 -135
- package/web/style.css +182 -0
package/dist/code-viewer.js
CHANGED
|
@@ -96,7 +96,7 @@ var init_sqlite_driver = __esm(() => {
|
|
|
96
96
|
|
|
97
97
|
// web-src/server/json-store.ts
|
|
98
98
|
import { randomBytes } from "node:crypto";
|
|
99
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
99
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
100
100
|
import { dirname } from "node:path";
|
|
101
101
|
function isEnoent(err) {
|
|
102
102
|
return err?.code === "ENOENT";
|
|
@@ -140,8 +140,15 @@ function createJsonFileStore(options) {
|
|
|
140
140
|
}
|
|
141
141
|
await mkdir(dirname(file), { recursive: true });
|
|
142
142
|
const tmp = tmpPath(file);
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
try {
|
|
144
|
+
await writeFile(tmp, content, "utf8");
|
|
145
|
+
await rename(tmp, file);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
await unlink(tmp).catch(() => {
|
|
148
|
+
return;
|
|
149
|
+
});
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
145
152
|
}
|
|
146
153
|
async function load(root) {
|
|
147
154
|
const pendingWrite = queues.get(options.filePath(root));
|
|
@@ -841,6 +848,7 @@ function runSync(args, cwd, options = {}) {
|
|
|
841
848
|
encoding: "buffer",
|
|
842
849
|
stdio: ["ignore", "pipe", "pipe"],
|
|
843
850
|
timeout: options.timeout,
|
|
851
|
+
maxBuffer: options.maxBuffer ?? 64 * 1024 * 1024,
|
|
844
852
|
killSignal: "SIGKILL"
|
|
845
853
|
});
|
|
846
854
|
return {
|
|
@@ -849,19 +857,87 @@ function runSync(args, cwd, options = {}) {
|
|
|
849
857
|
stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
|
|
850
858
|
};
|
|
851
859
|
}
|
|
852
|
-
function
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
860
|
+
function runAsync(args, cwd, options = {}) {
|
|
861
|
+
return runBytesAsync(args, cwd, options).then((proc) => ({
|
|
862
|
+
code: proc.code,
|
|
863
|
+
stdout: new TextDecoder().decode(proc.stdout),
|
|
864
|
+
stderr: proc.stderr
|
|
865
|
+
}));
|
|
866
|
+
}
|
|
867
|
+
function runBytesAsync(args, cwd, options = {}) {
|
|
868
|
+
const maxBuffer = options.maxBuffer ?? 64 * 1024 * 1024;
|
|
869
|
+
return new Promise((resolve) => {
|
|
870
|
+
const proc = spawn(args[0], args.slice(1), {
|
|
871
|
+
cwd,
|
|
872
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
873
|
+
});
|
|
874
|
+
const stdoutChunks = [];
|
|
875
|
+
const stderrChunks = [];
|
|
876
|
+
let stdoutBytes = 0;
|
|
877
|
+
let stderrBytes = 0;
|
|
878
|
+
let settled = false;
|
|
879
|
+
let timedOut = false;
|
|
880
|
+
let bufferExceeded = false;
|
|
881
|
+
let processError;
|
|
882
|
+
const killSignal = "SIGKILL";
|
|
883
|
+
const timer = options.timeout === undefined ? null : setTimeout(() => {
|
|
884
|
+
timedOut = true;
|
|
885
|
+
proc.kill(killSignal);
|
|
886
|
+
}, options.timeout);
|
|
887
|
+
const finish = (code) => {
|
|
888
|
+
if (settled)
|
|
889
|
+
return;
|
|
890
|
+
settled = true;
|
|
891
|
+
if (timer)
|
|
892
|
+
clearTimeout(timer);
|
|
893
|
+
let stderr = new TextDecoder().decode(concatBytes(stderrChunks));
|
|
894
|
+
if (timedOut) {
|
|
895
|
+
stderr = appendProcessError(stderr, new Error(`spawn ${args[0]} ETIMEDOUT`));
|
|
896
|
+
} else if (bufferExceeded) {
|
|
897
|
+
stderr = appendProcessError(stderr, new Error("stdout maxBuffer exceeded"));
|
|
898
|
+
} else {
|
|
899
|
+
stderr = appendProcessError(stderr, processError);
|
|
900
|
+
}
|
|
901
|
+
resolve({
|
|
902
|
+
code,
|
|
903
|
+
stdout: concatBytes(stdoutChunks),
|
|
904
|
+
stderr
|
|
905
|
+
});
|
|
906
|
+
};
|
|
907
|
+
const collect = (chunks, onBytes) => (chunk) => {
|
|
908
|
+
const bytes = new Uint8Array(chunk);
|
|
909
|
+
chunks.push(bytes);
|
|
910
|
+
onBytes(bytes.byteLength);
|
|
911
|
+
if (!bufferExceeded && (stdoutBytes > maxBuffer || stderrBytes > maxBuffer)) {
|
|
912
|
+
bufferExceeded = true;
|
|
913
|
+
proc.kill(killSignal);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
proc.stdout?.on("data", collect(stdoutChunks, (length) => {
|
|
917
|
+
stdoutBytes += length;
|
|
918
|
+
}));
|
|
919
|
+
proc.stderr?.on("data", collect(stderrChunks, (length) => {
|
|
920
|
+
stderrBytes += length;
|
|
921
|
+
}));
|
|
922
|
+
proc.on("error", (err) => {
|
|
923
|
+
processError = err;
|
|
924
|
+
});
|
|
925
|
+
proc.on("close", (code) => {
|
|
926
|
+
finish(timedOut || bufferExceeded ? 1 : code ?? (processError ? 1 : 0));
|
|
927
|
+
});
|
|
859
928
|
});
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
929
|
+
}
|
|
930
|
+
function concatBytes(chunks) {
|
|
931
|
+
if (chunks.length === 0)
|
|
932
|
+
return new Uint8Array;
|
|
933
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
934
|
+
const out = new Uint8Array(total);
|
|
935
|
+
let offset = 0;
|
|
936
|
+
for (const chunk of chunks) {
|
|
937
|
+
out.set(chunk, offset);
|
|
938
|
+
offset += chunk.byteLength;
|
|
939
|
+
}
|
|
940
|
+
return out;
|
|
865
941
|
}
|
|
866
942
|
function spawnDetached(args) {
|
|
867
943
|
const child = spawn(args[0], args.slice(1), {
|
|
@@ -1035,10 +1111,13 @@ var init_runtime = () => {};
|
|
|
1035
1111
|
|
|
1036
1112
|
// web-src/server/git.ts
|
|
1037
1113
|
import {
|
|
1114
|
+
closeSync,
|
|
1038
1115
|
existsSync,
|
|
1039
1116
|
lstatSync,
|
|
1117
|
+
openSync,
|
|
1040
1118
|
readdirSync,
|
|
1041
1119
|
readFileSync,
|
|
1120
|
+
readSync,
|
|
1042
1121
|
statSync as statSync2
|
|
1043
1122
|
} from "node:fs";
|
|
1044
1123
|
import { join as join3 } from "node:path";
|
|
@@ -1061,8 +1140,8 @@ function run(args, cwd) {
|
|
|
1061
1140
|
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1062
1141
|
});
|
|
1063
1142
|
}
|
|
1064
|
-
function
|
|
1065
|
-
return
|
|
1143
|
+
function runGitAsync(args, cwd) {
|
|
1144
|
+
return runAsync(resolveGitArgs(args), cwd, {
|
|
1066
1145
|
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1067
1146
|
});
|
|
1068
1147
|
}
|
|
@@ -1089,6 +1168,10 @@ function runGitRefLookup(args, cwd) {
|
|
|
1089
1168
|
const res = run(args, cwd);
|
|
1090
1169
|
return res.code === 0 ? res.stdout.trimEnd() : null;
|
|
1091
1170
|
}
|
|
1171
|
+
async function runGitRefLookupAsync(args, cwd) {
|
|
1172
|
+
const res = await runGitAsync(args, cwd);
|
|
1173
|
+
return res.code === 0 ? res.stdout.trimEnd() : null;
|
|
1174
|
+
}
|
|
1092
1175
|
function repoRoot(cwd) {
|
|
1093
1176
|
return runGitRefLookup(["git", "rev-parse", "--show-toplevel"], cwd);
|
|
1094
1177
|
}
|
|
@@ -1107,14 +1190,17 @@ function repoRootResult(cwd) {
|
|
|
1107
1190
|
function currentBranch(cwd) {
|
|
1108
1191
|
return runGitRefLookup(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
1109
1192
|
}
|
|
1110
|
-
function
|
|
1111
|
-
|
|
1193
|
+
function currentBranchAsync(cwd) {
|
|
1194
|
+
return runGitRefLookupAsync(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
1195
|
+
}
|
|
1196
|
+
async function verifyCommitAsync(ref, cwd) {
|
|
1197
|
+
const res = await runGitAsync(["git", "rev-parse", "--verify", `${ref}^{commit}`], cwd);
|
|
1112
1198
|
if (res.code === 0)
|
|
1113
1199
|
return { ok: true, sha: res.stdout.trim() };
|
|
1114
1200
|
return { ok: false, error: gitFailureMessage(res, "unknown ref") };
|
|
1115
1201
|
}
|
|
1116
|
-
function
|
|
1117
|
-
const res =
|
|
1202
|
+
async function statusPorcelainForPathAsync(path, cwd) {
|
|
1203
|
+
const res = await runGitAsync([
|
|
1118
1204
|
"git",
|
|
1119
1205
|
"-c",
|
|
1120
1206
|
"core.quotepath=false",
|
|
@@ -1135,66 +1221,63 @@ function statusPorcelainForPath(path, cwd) {
|
|
|
1135
1221
|
function show(ref, path, cwd) {
|
|
1136
1222
|
return run(["git", "show", `${ref}:${path}`], cwd);
|
|
1137
1223
|
}
|
|
1138
|
-
function
|
|
1139
|
-
return
|
|
1224
|
+
function showAsync(ref, path, cwd) {
|
|
1225
|
+
return runGitAsync(["git", "show", `${ref}:${path}`], cwd);
|
|
1140
1226
|
}
|
|
1141
1227
|
function catFileBlobStream(oid, cwd) {
|
|
1142
1228
|
return spawnStream(resolveGitArgs(["git", "cat-file", "blob", oid]), cwd);
|
|
1143
1229
|
}
|
|
1144
|
-
function
|
|
1145
|
-
const res =
|
|
1230
|
+
async function objectSizeAsync(ref, path, cwd) {
|
|
1231
|
+
const res = await runGitAsync(["git", "cat-file", "-s", `${ref}:${path}`], cwd);
|
|
1146
1232
|
return {
|
|
1147
1233
|
code: res.code,
|
|
1148
1234
|
size: Number(res.stdout.trim()) || 0,
|
|
1149
1235
|
stderr: res.stderr
|
|
1150
1236
|
};
|
|
1151
1237
|
}
|
|
1152
|
-
function
|
|
1153
|
-
const res =
|
|
1238
|
+
async function objectByteSizeAsync(oid, cwd) {
|
|
1239
|
+
const res = await runGitAsync(["git", "cat-file", "-s", oid], cwd);
|
|
1154
1240
|
return {
|
|
1155
1241
|
code: res.code,
|
|
1156
1242
|
size: Number(res.stdout.trim()) || 0,
|
|
1157
1243
|
stderr: res.stderr
|
|
1158
1244
|
};
|
|
1159
1245
|
}
|
|
1160
|
-
function
|
|
1246
|
+
async function lastCommitDateForPathAsync(ref, path, cwd) {
|
|
1161
1247
|
const args = ["git", "log", "-1", "--format=%cI", ref, "--", path];
|
|
1162
|
-
const res =
|
|
1248
|
+
const res = await runGitAsync(args, cwd);
|
|
1163
1249
|
if (res.code !== 0)
|
|
1164
1250
|
return null;
|
|
1165
1251
|
return res.stdout.trim() || null;
|
|
1166
1252
|
}
|
|
1167
|
-
function
|
|
1168
|
-
const res =
|
|
1253
|
+
async function objectIdAsync(ref, path, cwd) {
|
|
1254
|
+
const res = await runGitAsync(["git", "rev-parse", "--verify", `${ref}:${path}`], cwd);
|
|
1169
1255
|
const oid = res.stdout.trim();
|
|
1170
1256
|
if (res.code !== 0 || !oid)
|
|
1171
1257
|
return { code: res.code || 1, oid: "", stderr: res.stderr };
|
|
1172
|
-
const type =
|
|
1258
|
+
const type = await runGitAsync(["git", "cat-file", "-t", oid], cwd);
|
|
1173
1259
|
if (type.code !== 0 || type.stdout.trim() !== "blob")
|
|
1174
1260
|
return { code: 1, oid: "", stderr: type.stderr };
|
|
1175
1261
|
return { code: 0, oid, stderr: "" };
|
|
1176
1262
|
}
|
|
1177
|
-
function
|
|
1178
|
-
return verifyTreeRefResult(ref, cwd).ok;
|
|
1179
|
-
}
|
|
1180
|
-
function verifyTreeRefResult(ref, cwd) {
|
|
1263
|
+
async function verifyTreeRefResultAsync(ref, cwd) {
|
|
1181
1264
|
if (!ref || ref === "worktree")
|
|
1182
1265
|
return { ok: false, error: "invalid target", status: 400 };
|
|
1183
1266
|
if (ref.startsWith("-"))
|
|
1184
1267
|
return { ok: false, error: "invalid target", status: 400 };
|
|
1185
|
-
const res =
|
|
1268
|
+
const res = await runGitAsync(["git", "rev-parse", "--verify", `${ref}^{tree}`], cwd);
|
|
1186
1269
|
if (res.code === 0)
|
|
1187
1270
|
return { ok: true };
|
|
1188
1271
|
return { ok: false, ...gitFailureResult(res, "invalid target") };
|
|
1189
1272
|
}
|
|
1190
|
-
function
|
|
1273
|
+
async function refsResultAsync(cwd) {
|
|
1191
1274
|
const out = {
|
|
1192
1275
|
branches: [],
|
|
1193
1276
|
tags: [],
|
|
1194
1277
|
commits: [],
|
|
1195
1278
|
current: ""
|
|
1196
1279
|
};
|
|
1197
|
-
const branches =
|
|
1280
|
+
const branches = await runGitAsync([
|
|
1198
1281
|
"git",
|
|
1199
1282
|
"for-each-ref",
|
|
1200
1283
|
"--sort=-committerdate",
|
|
@@ -1214,7 +1297,7 @@ function refsResult(cwd) {
|
|
|
1214
1297
|
out.branches.push({ name, when });
|
|
1215
1298
|
}
|
|
1216
1299
|
}
|
|
1217
|
-
const tags =
|
|
1300
|
+
const tags = await runGitAsync([
|
|
1218
1301
|
"git",
|
|
1219
1302
|
"for-each-ref",
|
|
1220
1303
|
"--sort=-creatordate",
|
|
@@ -1233,7 +1316,7 @@ function refsResult(cwd) {
|
|
|
1233
1316
|
out.tags.push({ name, when });
|
|
1234
1317
|
}
|
|
1235
1318
|
}
|
|
1236
|
-
const commits =
|
|
1319
|
+
const commits = await refCommitPageResultAsync(cwd, {
|
|
1237
1320
|
query: "",
|
|
1238
1321
|
max: DEFAULT_REF_COMMIT_LIMIT
|
|
1239
1322
|
});
|
|
@@ -1241,7 +1324,7 @@ function refsResult(cwd) {
|
|
|
1241
1324
|
return { refs: out, error: commits.error, status: commits.status };
|
|
1242
1325
|
}
|
|
1243
1326
|
out.commits = commits.commits;
|
|
1244
|
-
out.current =
|
|
1327
|
+
out.current = await currentBranchAsync(cwd) || "";
|
|
1245
1328
|
return { refs: out };
|
|
1246
1329
|
}
|
|
1247
1330
|
function clampCommitLimit(max) {
|
|
@@ -1295,22 +1378,22 @@ function mergeCommitResults(limit, ...groups) {
|
|
|
1295
1378
|
}
|
|
1296
1379
|
return merged;
|
|
1297
1380
|
}
|
|
1298
|
-
function
|
|
1299
|
-
const commits =
|
|
1381
|
+
async function runCommitLogResultAsync(cwd, args) {
|
|
1382
|
+
const commits = await runGitAsync(args, cwd);
|
|
1300
1383
|
if (commits.code === 0)
|
|
1301
1384
|
return { commits: parseCommitLog(commits.stdout) };
|
|
1302
1385
|
if (isCommandNotFoundResult("git", commits))
|
|
1303
1386
|
return { commits: [], ...gitFailureResult(commits, "git log failed") };
|
|
1304
1387
|
return { commits: [] };
|
|
1305
1388
|
}
|
|
1306
|
-
function
|
|
1389
|
+
async function refCommitPageResultAsync(cwd, options = {}) {
|
|
1307
1390
|
const limit = clampCommitLimit(options.max ?? DEFAULT_REF_COMMIT_LIMIT);
|
|
1308
1391
|
const skip = clampCommitSkip(options.skip ?? 0);
|
|
1309
1392
|
const fetchLimit = limit + 1;
|
|
1310
1393
|
const hashMatches = [];
|
|
1311
1394
|
const trimmed = (options.query || "").trim().slice(0, 200).replace(/\0/g, "");
|
|
1312
1395
|
if (skip === 0 && /^[0-9a-f]{4,40}$/i.test(trimmed)) {
|
|
1313
|
-
const verified =
|
|
1396
|
+
const verified = await runGitAsync(["git", "rev-parse", "--verify", `${trimmed}^{commit}`], cwd);
|
|
1314
1397
|
if (verified.code !== 0 && isCommandNotFoundResult("git", verified)) {
|
|
1315
1398
|
return {
|
|
1316
1399
|
commits: [],
|
|
@@ -1318,7 +1401,7 @@ function refCommitPageResult(cwd, options = {}) {
|
|
|
1318
1401
|
...gitFailureResult(verified, "unknown ref")
|
|
1319
1402
|
};
|
|
1320
1403
|
}
|
|
1321
|
-
const single =
|
|
1404
|
+
const single = await runGitAsync([
|
|
1322
1405
|
"git",
|
|
1323
1406
|
"log",
|
|
1324
1407
|
"-z",
|
|
@@ -1338,7 +1421,7 @@ function refCommitPageResult(cwd, options = {}) {
|
|
|
1338
1421
|
}
|
|
1339
1422
|
}
|
|
1340
1423
|
if (!trimmed) {
|
|
1341
|
-
const result =
|
|
1424
|
+
const result = await runCommitLogResultAsync(cwd, commitLogArgs(fetchLimit, skip));
|
|
1342
1425
|
if (result.error) {
|
|
1343
1426
|
return {
|
|
1344
1427
|
commits: [],
|
|
@@ -1353,11 +1436,19 @@ function refCommitPageResult(cwd, options = {}) {
|
|
|
1353
1436
|
hasMore: commits.length > limit
|
|
1354
1437
|
};
|
|
1355
1438
|
}
|
|
1356
|
-
const subjectMatches =
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1439
|
+
const [subjectMatches, authorMatches] = await Promise.all([
|
|
1440
|
+
runCommitLogResultAsync(cwd, [
|
|
1441
|
+
...commitLogArgs(fetchLimit, skip),
|
|
1442
|
+
"--regexp-ignore-case",
|
|
1443
|
+
"--fixed-strings",
|
|
1444
|
+
`--grep=${trimmed}`
|
|
1445
|
+
]),
|
|
1446
|
+
runCommitLogResultAsync(cwd, [
|
|
1447
|
+
...commitLogArgs(fetchLimit, skip),
|
|
1448
|
+
"--regexp-ignore-case",
|
|
1449
|
+
"--fixed-strings",
|
|
1450
|
+
`--author=${trimmed}`
|
|
1451
|
+
])
|
|
1361
1452
|
]);
|
|
1362
1453
|
if (subjectMatches.error) {
|
|
1363
1454
|
return {
|
|
@@ -1367,12 +1458,6 @@ function refCommitPageResult(cwd, options = {}) {
|
|
|
1367
1458
|
status: subjectMatches.status
|
|
1368
1459
|
};
|
|
1369
1460
|
}
|
|
1370
|
-
const authorMatches = runCommitLogResult(cwd, [
|
|
1371
|
-
...commitLogArgs(fetchLimit, skip),
|
|
1372
|
-
"--regexp-ignore-case",
|
|
1373
|
-
"--fixed-strings",
|
|
1374
|
-
`--author=${trimmed}`
|
|
1375
|
-
]);
|
|
1376
1461
|
if (authorMatches.error) {
|
|
1377
1462
|
return {
|
|
1378
1463
|
commits: [],
|
|
@@ -1408,6 +1493,12 @@ function remoteWebUrl(cwd) {
|
|
|
1408
1493
|
return null;
|
|
1409
1494
|
return parseRemoteWebUrl(res.stdout.trim());
|
|
1410
1495
|
}
|
|
1496
|
+
async function remoteWebUrlAsync(cwd) {
|
|
1497
|
+
const res = await runGitAsync(["git", "remote", "get-url", "origin"], cwd);
|
|
1498
|
+
if (res.code !== 0)
|
|
1499
|
+
return null;
|
|
1500
|
+
return parseRemoteWebUrl(res.stdout.trim());
|
|
1501
|
+
}
|
|
1411
1502
|
function parseHistoryLog(stdout) {
|
|
1412
1503
|
const parts = stdout.split("\x00");
|
|
1413
1504
|
const commits = [];
|
|
@@ -1524,6 +1615,60 @@ function commitHistory(cwd, options) {
|
|
|
1524
1615
|
const hasMore = parsed.length > limit;
|
|
1525
1616
|
return { commits: hasMore ? parsed.slice(0, limit) : parsed, hasMore };
|
|
1526
1617
|
}
|
|
1618
|
+
async function commitHistoryAsync(cwd, options) {
|
|
1619
|
+
const ref = (options.ref || "HEAD").trim();
|
|
1620
|
+
if (!ref || ref.startsWith("-") || ref.includes("\x00"))
|
|
1621
|
+
return { commits: [], hasMore: false, error: "invalid ref" };
|
|
1622
|
+
const verified = await runGitAsync(["git", "rev-parse", "--verify", `${ref}^{commit}`], cwd);
|
|
1623
|
+
if (verified.code !== 0)
|
|
1624
|
+
return {
|
|
1625
|
+
commits: [],
|
|
1626
|
+
hasMore: false,
|
|
1627
|
+
...gitFailureResult(verified, "unknown ref")
|
|
1628
|
+
};
|
|
1629
|
+
const skip = Math.max(0, Math.floor(options.skip) || 0);
|
|
1630
|
+
const limit = Math.max(1, Math.min(Math.floor(options.limit) || 1, MAX_HISTORY_LIMIT));
|
|
1631
|
+
const { filterArgs, pathspec, shaTerm } = historyQueryArgs(options.query || "");
|
|
1632
|
+
const pathFilter = (options.path || "").trim();
|
|
1633
|
+
const pathArgs = [];
|
|
1634
|
+
if (pathFilter && !pathFilter.includes("\x00") && !pathFilter.startsWith("-")) {
|
|
1635
|
+
if (!pathFilter.endsWith("/"))
|
|
1636
|
+
pathArgs.push("--follow");
|
|
1637
|
+
pathArgs.push("--", pathFilter);
|
|
1638
|
+
}
|
|
1639
|
+
const res = await runGitAsync([
|
|
1640
|
+
"git",
|
|
1641
|
+
"log",
|
|
1642
|
+
"-z",
|
|
1643
|
+
`--skip=${skip}`,
|
|
1644
|
+
`--max-count=${limit + 1}`,
|
|
1645
|
+
`--format=${HISTORY_FORMAT}`,
|
|
1646
|
+
...filterArgs,
|
|
1647
|
+
verified.stdout.trim(),
|
|
1648
|
+
...pathspec,
|
|
1649
|
+
...pathArgs
|
|
1650
|
+
], cwd);
|
|
1651
|
+
if (res.code !== 0)
|
|
1652
|
+
return {
|
|
1653
|
+
commits: [],
|
|
1654
|
+
hasMore: false,
|
|
1655
|
+
...gitFailureResult(res, "git log failed")
|
|
1656
|
+
};
|
|
1657
|
+
let parsed = parseHistoryLog(res.stdout);
|
|
1658
|
+
if (shaTerm && skip === 0) {
|
|
1659
|
+
const bySha = await runGitAsync(["git", "rev-parse", "--verify", `${shaTerm}^{commit}`], cwd);
|
|
1660
|
+
const sha = bySha.code === 0 ? bySha.stdout.trim() : "";
|
|
1661
|
+
if (sha) {
|
|
1662
|
+
const single = await runGitAsync(["git", "log", "-z", "-1", `--format=${HISTORY_FORMAT}`, sha], cwd);
|
|
1663
|
+
if (single.code === 0) {
|
|
1664
|
+
const hit = parseHistoryLog(single.stdout);
|
|
1665
|
+
parsed = [...hit, ...parsed.filter((c) => c.sha !== sha)];
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
const hasMore = parsed.length > limit;
|
|
1670
|
+
return { commits: hasMore ? parsed.slice(0, limit) : parsed, hasMore };
|
|
1671
|
+
}
|
|
1527
1672
|
function nameStatusResult(args, cwd) {
|
|
1528
1673
|
const res = run([
|
|
1529
1674
|
"git",
|
|
@@ -1568,6 +1713,50 @@ function nameStatusResult(args, cwd) {
|
|
|
1568
1713
|
}
|
|
1569
1714
|
return { files };
|
|
1570
1715
|
}
|
|
1716
|
+
async function nameStatusResultAsync(args, cwd) {
|
|
1717
|
+
const res = await runGitAsync([
|
|
1718
|
+
"git",
|
|
1719
|
+
"-c",
|
|
1720
|
+
"core.quotepath=false",
|
|
1721
|
+
"diff",
|
|
1722
|
+
"--no-color",
|
|
1723
|
+
"--no-ext-diff",
|
|
1724
|
+
"--find-renames",
|
|
1725
|
+
"--name-status",
|
|
1726
|
+
"-z",
|
|
1727
|
+
...args
|
|
1728
|
+
], cwd);
|
|
1729
|
+
if (res.code !== 0) {
|
|
1730
|
+
return {
|
|
1731
|
+
files: [],
|
|
1732
|
+
error: gitFailureMessage(res, "git diff --name-status failed")
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
const parts = res.stdout.split("\x00");
|
|
1736
|
+
const files = [];
|
|
1737
|
+
for (let i = 0;i < parts.length; ) {
|
|
1738
|
+
const status = parts[i++];
|
|
1739
|
+
if (!status)
|
|
1740
|
+
break;
|
|
1741
|
+
const kind = status[0];
|
|
1742
|
+
if (kind === "R" || kind === "C") {
|
|
1743
|
+
const oldPath = parts[i++] || "";
|
|
1744
|
+
const path = parts[i++] || "";
|
|
1745
|
+
if (path)
|
|
1746
|
+
files.push({
|
|
1747
|
+
status: kind,
|
|
1748
|
+
old_path: oldPath,
|
|
1749
|
+
path,
|
|
1750
|
+
similarity: Number(status.slice(1)) || undefined
|
|
1751
|
+
});
|
|
1752
|
+
} else {
|
|
1753
|
+
const path = parts[i++] || "";
|
|
1754
|
+
if (path)
|
|
1755
|
+
files.push({ status: kind, path });
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return { files };
|
|
1759
|
+
}
|
|
1571
1760
|
function numstatZResult(args, cwd) {
|
|
1572
1761
|
const res = run([
|
|
1573
1762
|
"git",
|
|
@@ -1611,6 +1800,49 @@ function numstatZResult(args, cwd) {
|
|
|
1611
1800
|
}
|
|
1612
1801
|
return { files };
|
|
1613
1802
|
}
|
|
1803
|
+
async function numstatZResultAsync(args, cwd) {
|
|
1804
|
+
const res = await runGitAsync([
|
|
1805
|
+
"git",
|
|
1806
|
+
"-c",
|
|
1807
|
+
"core.quotepath=false",
|
|
1808
|
+
"diff",
|
|
1809
|
+
"--no-color",
|
|
1810
|
+
"--no-ext-diff",
|
|
1811
|
+
"--find-renames",
|
|
1812
|
+
"--numstat",
|
|
1813
|
+
"-z",
|
|
1814
|
+
...args
|
|
1815
|
+
], cwd);
|
|
1816
|
+
if (res.code !== 0) {
|
|
1817
|
+
return {
|
|
1818
|
+
files: [],
|
|
1819
|
+
error: gitFailureMessage(res, "git diff --numstat failed")
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
const parts = res.stdout.split("\x00");
|
|
1823
|
+
const files = [];
|
|
1824
|
+
for (let i = 0;i < parts.length; ) {
|
|
1825
|
+
const rec = parts[i++];
|
|
1826
|
+
if (!rec)
|
|
1827
|
+
break;
|
|
1828
|
+
const match = rec.match(/^(\S+)\t(\S+)\t(.*)$/);
|
|
1829
|
+
if (!match)
|
|
1830
|
+
break;
|
|
1831
|
+
const [, add, del, rest] = match;
|
|
1832
|
+
const binary = add === "-" && del === "-";
|
|
1833
|
+
const additions = binary ? 0 : Number(add) || 0;
|
|
1834
|
+
const deletions = binary ? 0 : Number(del) || 0;
|
|
1835
|
+
if (rest === "") {
|
|
1836
|
+
const oldPath = parts[i++] || "";
|
|
1837
|
+
const path = parts[i++] || "";
|
|
1838
|
+
if (path)
|
|
1839
|
+
files.push({ old_path: oldPath, path, additions, deletions, binary });
|
|
1840
|
+
} else {
|
|
1841
|
+
files.push({ path: rest, additions, deletions, binary });
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return { files };
|
|
1845
|
+
}
|
|
1614
1846
|
function pathHasSegment(path, segment) {
|
|
1615
1847
|
const target = segment.toLowerCase();
|
|
1616
1848
|
return path.split(/[\\/]+/).some((part) => part.toLowerCase() === target);
|
|
@@ -1753,6 +1985,98 @@ function blame(cwd, options) {
|
|
|
1753
1985
|
lines.sort((a, b) => a.lineNo - b.lineNo);
|
|
1754
1986
|
return { lines, commits };
|
|
1755
1987
|
}
|
|
1988
|
+
async function blameAsync(cwd, options) {
|
|
1989
|
+
const path = options.path;
|
|
1990
|
+
if (!path || path.includes("\x00") || path.startsWith("-")) {
|
|
1991
|
+
return { lines: [], commits: {}, error: "invalid path" };
|
|
1992
|
+
}
|
|
1993
|
+
const normalized = normalizeBlameRef(options.ref, options.base);
|
|
1994
|
+
const args = ["git", "blame", "--porcelain"];
|
|
1995
|
+
if (normalized.base === "HEAD") {
|
|
1996
|
+
if (normalized.ref.startsWith("-") || normalized.ref.includes("\x00"))
|
|
1997
|
+
return { lines: [], commits: {}, error: "invalid ref" };
|
|
1998
|
+
args.push(normalized.ref);
|
|
1999
|
+
}
|
|
2000
|
+
args.push("--", path);
|
|
2001
|
+
const res = await runGitAsync(args, cwd);
|
|
2002
|
+
if (res.code !== 0) {
|
|
2003
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
2004
|
+
return {
|
|
2005
|
+
lines: [],
|
|
2006
|
+
commits: {},
|
|
2007
|
+
error: commandNotFoundDetail("git"),
|
|
2008
|
+
status: 503
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
if (normalized.base === "worktree") {
|
|
2012
|
+
return syntheticUncommittedBlameFromWorktree(cwd, path);
|
|
2013
|
+
}
|
|
2014
|
+
return {
|
|
2015
|
+
lines: [],
|
|
2016
|
+
commits: {},
|
|
2017
|
+
error: res.stderr.trim() || "blame failed"
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
const lines = [];
|
|
2021
|
+
const commits = {};
|
|
2022
|
+
const rawLines = res.stdout.split(`
|
|
2023
|
+
`);
|
|
2024
|
+
let i = 0;
|
|
2025
|
+
while (i < rawLines.length) {
|
|
2026
|
+
const headerLine = rawLines[i];
|
|
2027
|
+
if (!headerLine) {
|
|
2028
|
+
i++;
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
2031
|
+
const headerMatch = /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/.exec(headerLine);
|
|
2032
|
+
if (!headerMatch) {
|
|
2033
|
+
i++;
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
const sha = headerMatch[1];
|
|
2037
|
+
const finalLine = Number(headerMatch[3]);
|
|
2038
|
+
i++;
|
|
2039
|
+
let commit = commits[sha];
|
|
2040
|
+
if (!commit) {
|
|
2041
|
+
commit = {
|
|
2042
|
+
sha,
|
|
2043
|
+
author: "",
|
|
2044
|
+
authorMail: "",
|
|
2045
|
+
authorTime: 0,
|
|
2046
|
+
summary: "",
|
|
2047
|
+
isUncommitted: sha === BLAME_ZERO_SHA
|
|
2048
|
+
};
|
|
2049
|
+
commits[sha] = commit;
|
|
2050
|
+
}
|
|
2051
|
+
while (i < rawLines.length && !rawLines[i].startsWith("\t")) {
|
|
2052
|
+
const metaLine = rawLines[i++];
|
|
2053
|
+
if (!metaLine)
|
|
2054
|
+
continue;
|
|
2055
|
+
const sp = metaLine.indexOf(" ");
|
|
2056
|
+
const key = sp >= 0 ? metaLine.slice(0, sp) : metaLine;
|
|
2057
|
+
const val = sp >= 0 ? metaLine.slice(sp + 1) : "";
|
|
2058
|
+
if (key === "author" && !commit.author)
|
|
2059
|
+
commit.author = val;
|
|
2060
|
+
else if (key === "author-mail" && !commit.authorMail)
|
|
2061
|
+
commit.authorMail = val.replace(/^</, "").replace(/>$/, "");
|
|
2062
|
+
else if (key === "author-time" && !commit.authorTime)
|
|
2063
|
+
commit.authorTime = Number(val) || 0;
|
|
2064
|
+
else if (key === "summary" && !commit.summary)
|
|
2065
|
+
commit.summary = val;
|
|
2066
|
+
}
|
|
2067
|
+
if (i < rawLines.length && rawLines[i].startsWith("\t"))
|
|
2068
|
+
i++;
|
|
2069
|
+
if (Number.isFinite(finalLine) && finalLine > 0) {
|
|
2070
|
+
lines.push({
|
|
2071
|
+
lineNo: finalLine,
|
|
2072
|
+
sha,
|
|
2073
|
+
isUncommitted: sha === BLAME_ZERO_SHA
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
lines.sort((a, b) => a.lineNo - b.lineNo);
|
|
2078
|
+
return { lines, commits };
|
|
2079
|
+
}
|
|
1756
2080
|
function untracked(cwd, path = "") {
|
|
1757
2081
|
const args = ["git", "ls-files", "--others", "--exclude-standard"];
|
|
1758
2082
|
if (path)
|
|
@@ -1763,6 +2087,16 @@ function untracked(cwd, path = "") {
|
|
|
1763
2087
|
return res.stdout.split(`
|
|
1764
2088
|
`).filter(Boolean).filter((entry) => !isToolInternalPath(entry));
|
|
1765
2089
|
}
|
|
2090
|
+
async function untrackedAsync(cwd, path = "") {
|
|
2091
|
+
const args = ["git", "ls-files", "--others", "--exclude-standard"];
|
|
2092
|
+
if (path)
|
|
2093
|
+
args.push("--", `${path}/`);
|
|
2094
|
+
const res = await runGitAsync(args, cwd);
|
|
2095
|
+
if (res.code !== 0)
|
|
2096
|
+
return [];
|
|
2097
|
+
return res.stdout.split(`
|
|
2098
|
+
`).filter(Boolean).filter((entry) => !isToolInternalPath(entry));
|
|
2099
|
+
}
|
|
1766
2100
|
function normalizeTreePath(path) {
|
|
1767
2101
|
return path.replace(/^\/+|\/+$/g, "");
|
|
1768
2102
|
}
|
|
@@ -1790,6 +2124,18 @@ function worktreeSubmodulePaths(cwd) {
|
|
|
1790
2124
|
return split >= 0 ? normalizeTreePath(line.slice(split + 1)) : "";
|
|
1791
2125
|
}).filter(Boolean));
|
|
1792
2126
|
}
|
|
2127
|
+
async function worktreeSubmodulePathsAsync(cwd) {
|
|
2128
|
+
if (!existsSync(join3(cwd, ".gitmodules")))
|
|
2129
|
+
return new Set;
|
|
2130
|
+
const res = await runGitAsync(["git", "config", "--file", ".gitmodules", "--get-regexp", "\\.path$"], cwd);
|
|
2131
|
+
if (res.code !== 0)
|
|
2132
|
+
return new Set;
|
|
2133
|
+
return new Set(res.stdout.split(`
|
|
2134
|
+
`).map((line) => {
|
|
2135
|
+
const split = line.indexOf(" ");
|
|
2136
|
+
return split >= 0 ? normalizeTreePath(line.slice(split + 1)) : "";
|
|
2137
|
+
}).filter(Boolean));
|
|
2138
|
+
}
|
|
1793
2139
|
function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames, submodulePaths) {
|
|
1794
2140
|
if (excludeNames.has(name.toLowerCase()))
|
|
1795
2141
|
return {
|
|
@@ -1887,6 +2233,93 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
|
|
|
1887
2233
|
walk(root, base, 0);
|
|
1888
2234
|
return combineDirectAndRecursiveFiles(directEntries, fileEntries.sort((a, b) => a.path.localeCompare(b.path)));
|
|
1889
2235
|
}
|
|
2236
|
+
async function worktreeFilesystemEntriesAsync(cwd, path, recursive, omitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES, excludeNames = []) {
|
|
2237
|
+
const base = normalizeTreePath(path);
|
|
2238
|
+
const root = join3(cwd, base);
|
|
2239
|
+
const omitDirNameSet = new Set(omitDirNames);
|
|
2240
|
+
const excludeNameSet = new Set(excludeNames.map((name) => name.toLowerCase()));
|
|
2241
|
+
const submodulePaths = await worktreeSubmodulePathsAsync(cwd);
|
|
2242
|
+
let directEntries;
|
|
2243
|
+
try {
|
|
2244
|
+
const dirents = readdirSync(root, { withFileTypes: true });
|
|
2245
|
+
directEntries = sortTreeEntries(dirents.map((entry) => worktreeEntryFromDirent(base, root, entry.name, entry.isDirectory(), omitDirNameSet, excludeNameSet, submodulePaths)).filter((entry) => entry.path));
|
|
2246
|
+
} catch {
|
|
2247
|
+
return [];
|
|
2248
|
+
}
|
|
2249
|
+
if (!recursive)
|
|
2250
|
+
return directEntries;
|
|
2251
|
+
const fileEntries = [];
|
|
2252
|
+
let truncated = false;
|
|
2253
|
+
const pushRecursiveEntry = (entry) => {
|
|
2254
|
+
if (fileEntries.length >= WORKTREE_RECURSIVE_ENTRY_LIMIT) {
|
|
2255
|
+
if (!truncated) {
|
|
2256
|
+
fileEntries.push({
|
|
2257
|
+
name: "more...",
|
|
2258
|
+
path: "__code_viewer_truncated__",
|
|
2259
|
+
type: "tree",
|
|
2260
|
+
children_omitted: true,
|
|
2261
|
+
children_omitted_reason: "truncated"
|
|
2262
|
+
});
|
|
2263
|
+
truncated = true;
|
|
2264
|
+
}
|
|
2265
|
+
return false;
|
|
2266
|
+
}
|
|
2267
|
+
fileEntries.push(entry);
|
|
2268
|
+
return true;
|
|
2269
|
+
};
|
|
2270
|
+
let visitedDirs = 0;
|
|
2271
|
+
const yieldIfNeeded = async () => {
|
|
2272
|
+
visitedDirs++;
|
|
2273
|
+
if (visitedDirs % 25 === 0) {
|
|
2274
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
const walk = async (dir, prefix, depth) => {
|
|
2278
|
+
if (truncated)
|
|
2279
|
+
return;
|
|
2280
|
+
if (depth >= WORKTREE_RECURSIVE_DEPTH_LIMIT)
|
|
2281
|
+
return;
|
|
2282
|
+
await yieldIfNeeded();
|
|
2283
|
+
let entries;
|
|
2284
|
+
try {
|
|
2285
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
2286
|
+
} catch {
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
for (const entry of entries) {
|
|
2290
|
+
if (excludeNameSet.has(entry.name.toLowerCase()))
|
|
2291
|
+
continue;
|
|
2292
|
+
const entryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2293
|
+
const full = join3(dir, entry.name);
|
|
2294
|
+
if (entry.isDirectory()) {
|
|
2295
|
+
const omittedReason = omittedWorktreeDirectoryReason(entry.name, omitDirNameSet);
|
|
2296
|
+
if (omittedReason) {
|
|
2297
|
+
if (!pushRecursiveEntry({
|
|
2298
|
+
name: entry.name,
|
|
2299
|
+
path: entryPath,
|
|
2300
|
+
type: "tree",
|
|
2301
|
+
children_omitted: true,
|
|
2302
|
+
children_omitted_reason: omittedReason
|
|
2303
|
+
}))
|
|
2304
|
+
return;
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
if (hasDotGitEntry(full))
|
|
2308
|
+
continue;
|
|
2309
|
+
await walk(full, entryPath, depth + 1);
|
|
2310
|
+
} else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
2311
|
+
if (!pushRecursiveEntry({
|
|
2312
|
+
name: entry.name,
|
|
2313
|
+
path: entryPath,
|
|
2314
|
+
type: "blob"
|
|
2315
|
+
}))
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
await walk(root, base, 0);
|
|
2321
|
+
return combineDirectAndRecursiveFiles(directEntries, fileEntries.sort((a, b) => a.path.localeCompare(b.path)));
|
|
2322
|
+
}
|
|
1890
2323
|
function hasDotGitEntry(dir) {
|
|
1891
2324
|
try {
|
|
1892
2325
|
lstatSync(join3(dir, ".git"));
|
|
@@ -1924,6 +2357,35 @@ function gitTreeEntries(ref, path, cwd, recursive) {
|
|
|
1924
2357
|
entries = sortTreeEntries(entries);
|
|
1925
2358
|
return { code: 0, entries, stderr: "" };
|
|
1926
2359
|
}
|
|
2360
|
+
async function gitTreeEntriesAsync(ref, path, cwd, recursive) {
|
|
2361
|
+
const base = normalizeTreePath(path);
|
|
2362
|
+
const args = ["git", "-c", "core.quotepath=false", "ls-tree"];
|
|
2363
|
+
if (recursive)
|
|
2364
|
+
args.push("-r");
|
|
2365
|
+
args.push("-z", "--full-tree", ref, "--");
|
|
2366
|
+
if (base)
|
|
2367
|
+
args.push(`${base}/`);
|
|
2368
|
+
const res = await runGitAsync(args, cwd);
|
|
2369
|
+
if (res.code !== 0)
|
|
2370
|
+
return { code: res.code, entries: [], stderr: res.stderr };
|
|
2371
|
+
const allowedTypes = recursive ? "blob|commit" : "tree|blob|commit";
|
|
2372
|
+
let entries = res.stdout.split("\x00").filter(Boolean).map((rec) => {
|
|
2373
|
+
const match = rec.match(new RegExp(`^\\d+\\s+(${allowedTypes})\\s+[0-9a-fA-F]+\\t(.+)$`));
|
|
2374
|
+
if (!match)
|
|
2375
|
+
return null;
|
|
2376
|
+
const entryPath = match[2];
|
|
2377
|
+
return {
|
|
2378
|
+
name: entryPath.split("/").pop() || entryPath,
|
|
2379
|
+
path: entryPath,
|
|
2380
|
+
type: match[1]
|
|
2381
|
+
};
|
|
2382
|
+
}).filter((entry) => !!entry);
|
|
2383
|
+
if (recursive)
|
|
2384
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
2385
|
+
else
|
|
2386
|
+
entries = sortTreeEntries(entries);
|
|
2387
|
+
return { code: 0, entries, stderr: "" };
|
|
2388
|
+
}
|
|
1927
2389
|
function combineDirectAndRecursiveFiles(directEntries, fileEntries) {
|
|
1928
2390
|
const seen = new Set(directEntries.map((entry) => entry.path));
|
|
1929
2391
|
return [
|
|
@@ -1952,8 +2414,29 @@ function listTree(ref, path, cwd, options = {}) {
|
|
|
1952
2414
|
stderr: ""
|
|
1953
2415
|
};
|
|
1954
2416
|
}
|
|
1955
|
-
function
|
|
1956
|
-
const
|
|
2417
|
+
async function listTreeAsync(ref, path, cwd, options = {}) {
|
|
2418
|
+
const base = normalizeTreePath(path);
|
|
2419
|
+
if (ref === "worktree") {
|
|
2420
|
+
return {
|
|
2421
|
+
code: 0,
|
|
2422
|
+
entries: await worktreeFilesystemEntriesAsync(cwd, base, !!options.recursive, options.omitDirNames, options.excludeNames),
|
|
2423
|
+
stderr: ""
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
const direct = await gitTreeEntriesAsync(ref, base, cwd, false);
|
|
2427
|
+
if (direct.code !== 0 || !options.recursive)
|
|
2428
|
+
return direct;
|
|
2429
|
+
const recursive = await gitTreeEntriesAsync(ref, base, cwd, true);
|
|
2430
|
+
if (recursive.code !== 0)
|
|
2431
|
+
return recursive;
|
|
2432
|
+
return {
|
|
2433
|
+
code: 0,
|
|
2434
|
+
entries: combineDirectAndRecursiveFiles(direct.entries, recursive.entries),
|
|
2435
|
+
stderr: ""
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
async function listTreeResultAsync(ref, path, cwd, options = {}) {
|
|
2439
|
+
const result = await listTreeAsync(ref, path, cwd, options);
|
|
1957
2440
|
if (result.code === 0)
|
|
1958
2441
|
return { entries: result.entries };
|
|
1959
2442
|
return { entries: [], ...gitFailureResult(result, "git ls-tree failed") };
|
|
@@ -1961,21 +2444,51 @@ function listTreeResult(ref, path, cwd, options = {}) {
|
|
|
1961
2444
|
function untrackedMeta(cwd) {
|
|
1962
2445
|
return untracked(cwd).flatMap((path) => {
|
|
1963
2446
|
const full = join3(cwd, path);
|
|
1964
|
-
let binary = false;
|
|
1965
|
-
let lines = 0;
|
|
1966
2447
|
let fileExists = false;
|
|
1967
2448
|
try {
|
|
1968
2449
|
fileExists = existsSync(full) && statSync2(full).isFile();
|
|
1969
2450
|
} catch {
|
|
1970
2451
|
fileExists = false;
|
|
1971
2452
|
}
|
|
2453
|
+
let scan;
|
|
2454
|
+
if (fileExists) {
|
|
2455
|
+
try {
|
|
2456
|
+
scan = scanFileBinaryAndNewlines(full);
|
|
2457
|
+
} catch {
|
|
2458
|
+
return [];
|
|
2459
|
+
}
|
|
2460
|
+
} else {
|
|
2461
|
+
return [];
|
|
2462
|
+
}
|
|
2463
|
+
return [
|
|
2464
|
+
{
|
|
2465
|
+
path,
|
|
2466
|
+
status: "A",
|
|
2467
|
+
additions: scan.binary ? 0 : scan.newlines,
|
|
2468
|
+
deletions: 0,
|
|
2469
|
+
binary: scan.binary,
|
|
2470
|
+
untracked: true
|
|
2471
|
+
}
|
|
2472
|
+
];
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
async function untrackedMetaAsync(cwd) {
|
|
2476
|
+
const paths = await untrackedAsync(cwd);
|
|
2477
|
+
return paths.flatMap((path) => {
|
|
2478
|
+
const full = join3(cwd, path);
|
|
2479
|
+
let fileExists = false;
|
|
2480
|
+
try {
|
|
2481
|
+
fileExists = existsSync(full) && statSync2(full).isFile();
|
|
2482
|
+
} catch {
|
|
2483
|
+
fileExists = false;
|
|
2484
|
+
}
|
|
2485
|
+
let scan;
|
|
1972
2486
|
if (fileExists) {
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
`).length - 1;
|
|
2487
|
+
try {
|
|
2488
|
+
scan = scanFileBinaryAndNewlines(full);
|
|
2489
|
+
} catch {
|
|
2490
|
+
return [];
|
|
2491
|
+
}
|
|
1979
2492
|
} else {
|
|
1980
2493
|
return [];
|
|
1981
2494
|
}
|
|
@@ -1983,14 +2496,40 @@ function untrackedMeta(cwd) {
|
|
|
1983
2496
|
{
|
|
1984
2497
|
path,
|
|
1985
2498
|
status: "A",
|
|
1986
|
-
additions: binary ? 0 :
|
|
2499
|
+
additions: scan.binary ? 0 : scan.newlines,
|
|
1987
2500
|
deletions: 0,
|
|
1988
|
-
binary,
|
|
2501
|
+
binary: scan.binary,
|
|
1989
2502
|
untracked: true
|
|
1990
2503
|
}
|
|
1991
2504
|
];
|
|
1992
2505
|
});
|
|
1993
2506
|
}
|
|
2507
|
+
function scanFileBinaryAndNewlines(full) {
|
|
2508
|
+
const fd = openSync(full, "r");
|
|
2509
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
2510
|
+
let newlines = 0;
|
|
2511
|
+
let inspected = 0;
|
|
2512
|
+
try {
|
|
2513
|
+
while (true) {
|
|
2514
|
+
const read = readSync(fd, buffer, 0, buffer.length, null);
|
|
2515
|
+
if (read <= 0)
|
|
2516
|
+
break;
|
|
2517
|
+
const binaryProbeBytes = Math.min(read, Math.max(0, 8192 - inspected));
|
|
2518
|
+
for (let i = 0;i < binaryProbeBytes; i++) {
|
|
2519
|
+
if (buffer[i] === 0)
|
|
2520
|
+
return { binary: true, newlines: 0 };
|
|
2521
|
+
}
|
|
2522
|
+
inspected += read;
|
|
2523
|
+
for (let i = 0;i < read; i++) {
|
|
2524
|
+
if (buffer[i] === 10)
|
|
2525
|
+
newlines++;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
} finally {
|
|
2529
|
+
closeSync(fd);
|
|
2530
|
+
}
|
|
2531
|
+
return { binary: false, newlines };
|
|
2532
|
+
}
|
|
1994
2533
|
function fileMetaResult(args, cwd, includeUntracked = false) {
|
|
1995
2534
|
const ns = nameStatusResult(args, cwd);
|
|
1996
2535
|
if (ns.error)
|
|
@@ -2012,6 +2551,27 @@ function fileMetaResult(args, cwd, includeUntracked = false) {
|
|
|
2012
2551
|
files: includeUntracked ? files.concat(untrackedMeta(cwd)) : files
|
|
2013
2552
|
};
|
|
2014
2553
|
}
|
|
2554
|
+
async function fileMetaResultAsync(args, cwd, includeUntracked = false) {
|
|
2555
|
+
const ns = await nameStatusResultAsync(args, cwd);
|
|
2556
|
+
if (ns.error)
|
|
2557
|
+
return { files: [], error: ns.error };
|
|
2558
|
+
const nm = await numstatZResultAsync(args, cwd);
|
|
2559
|
+
if (nm.error)
|
|
2560
|
+
return { files: [], error: nm.error };
|
|
2561
|
+
const byPath = new Map(nm.files.map((file) => [file.path, file]));
|
|
2562
|
+
const files = ns.files.map((file) => {
|
|
2563
|
+
const stats = byPath.get(file.path);
|
|
2564
|
+
return {
|
|
2565
|
+
...file,
|
|
2566
|
+
additions: stats?.additions || 0,
|
|
2567
|
+
deletions: stats?.deletions || 0,
|
|
2568
|
+
binary: stats?.binary || false
|
|
2569
|
+
};
|
|
2570
|
+
});
|
|
2571
|
+
return {
|
|
2572
|
+
files: includeUntracked ? files.concat(await untrackedMetaAsync(cwd)) : files
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2015
2575
|
function fileDiffText(args, path, cwd) {
|
|
2016
2576
|
const paths = Array.isArray(path) ? path : [path];
|
|
2017
2577
|
const res = run([
|
|
@@ -2031,6 +2591,25 @@ function fileDiffText(args, path, cwd) {
|
|
|
2031
2591
|
}
|
|
2032
2592
|
return res;
|
|
2033
2593
|
}
|
|
2594
|
+
async function fileDiffTextAsync(args, path, cwd) {
|
|
2595
|
+
const paths = Array.isArray(path) ? path : [path];
|
|
2596
|
+
const res = await runGitAsync([
|
|
2597
|
+
"git",
|
|
2598
|
+
"-c",
|
|
2599
|
+
"core.quotepath=false",
|
|
2600
|
+
"diff",
|
|
2601
|
+
"--no-color",
|
|
2602
|
+
"--no-ext-diff",
|
|
2603
|
+
"--find-renames",
|
|
2604
|
+
...args,
|
|
2605
|
+
"--",
|
|
2606
|
+
...paths
|
|
2607
|
+
], cwd);
|
|
2608
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
2609
|
+
return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
|
|
2610
|
+
}
|
|
2611
|
+
return res;
|
|
2612
|
+
}
|
|
2034
2613
|
function untrackedFileDiff(extras, path, cwd) {
|
|
2035
2614
|
const res = run([
|
|
2036
2615
|
"git",
|
|
@@ -2049,16 +2628,38 @@ function untrackedFileDiff(extras, path, cwd) {
|
|
|
2049
2628
|
}
|
|
2050
2629
|
return res;
|
|
2051
2630
|
}
|
|
2631
|
+
async function untrackedFileDiffAsync(extras, path, cwd) {
|
|
2632
|
+
const res = await runGitAsync([
|
|
2633
|
+
"git",
|
|
2634
|
+
"-c",
|
|
2635
|
+
"core.quotepath=false",
|
|
2636
|
+
"diff",
|
|
2637
|
+
"--no-color",
|
|
2638
|
+
"--no-ext-diff",
|
|
2639
|
+
"--no-index",
|
|
2640
|
+
...extras,
|
|
2641
|
+
"/dev/null",
|
|
2642
|
+
path
|
|
2643
|
+
], cwd);
|
|
2644
|
+
if (isCommandNotFoundResult("git", res)) {
|
|
2645
|
+
return { ...res, stderr: commandNotFoundDetail("git"), status: 503 };
|
|
2646
|
+
}
|
|
2647
|
+
return res;
|
|
2648
|
+
}
|
|
2052
2649
|
function splitHunks(diffText) {
|
|
2053
2650
|
if (!diffText)
|
|
2054
2651
|
return { header: "", hunks: [] };
|
|
2055
|
-
const
|
|
2056
|
-
|
|
2057
|
-
|
|
2652
|
+
const startsWithHunk = diffText.startsWith("@@");
|
|
2653
|
+
const first = startsWithHunk ? 0 : diffText.indexOf(`
|
|
2654
|
+
@@`);
|
|
2655
|
+
if (first < 0)
|
|
2656
|
+
return { header: diffText, hunks: [] };
|
|
2657
|
+
const hunkStart = startsWithHunk ? 0 : first + 1;
|
|
2658
|
+
if (hunkStart >= diffText.length)
|
|
2058
2659
|
return { header: diffText, hunks: [] };
|
|
2059
|
-
const header = diffText.slice(0,
|
|
2660
|
+
const header = diffText.slice(0, hunkStart);
|
|
2060
2661
|
const hunks = [];
|
|
2061
|
-
let cur =
|
|
2662
|
+
let cur = hunkStart;
|
|
2062
2663
|
while (cur < diffText.length) {
|
|
2063
2664
|
const next = diffText.indexOf(`
|
|
2064
2665
|
@@`, cur + 1);
|
|
@@ -3382,11 +3983,16 @@ __export(exports_file_cli, {
|
|
|
3382
3983
|
sliceLines: () => sliceLines,
|
|
3383
3984
|
safeWorktreePathFromRoot: () => safeWorktreePathFromRoot,
|
|
3384
3985
|
runFileCli: () => runFileCli,
|
|
3986
|
+
readShowTextAsync: () => readShowTextAsync,
|
|
3385
3987
|
readShowText: () => readShowText,
|
|
3386
3988
|
parseFileArgs: () => parseFileArgs,
|
|
3989
|
+
buildFileShowReportAsync: () => buildFileShowReportAsync,
|
|
3387
3990
|
buildFileShowReport: () => buildFileShowReport,
|
|
3991
|
+
buildFileHistoryReportAsync: () => buildFileHistoryReportAsync,
|
|
3388
3992
|
buildFileHistoryReport: () => buildFileHistoryReport,
|
|
3993
|
+
buildFileDiffReportAsync: () => buildFileDiffReportAsync,
|
|
3389
3994
|
buildFileDiffReport: () => buildFileDiffReport,
|
|
3995
|
+
buildFileBlameReportAsync: () => buildFileBlameReportAsync,
|
|
3390
3996
|
buildFileBlameReport: () => buildFileBlameReport,
|
|
3391
3997
|
FILE_HISTORY_HARD_CAP: () => FILE_HISTORY_HARD_CAP,
|
|
3392
3998
|
FILE_HELP: () => FILE_HELP,
|
|
@@ -3706,6 +4312,19 @@ function buildFileBlameReport(root, command) {
|
|
|
3706
4312
|
result
|
|
3707
4313
|
};
|
|
3708
4314
|
}
|
|
4315
|
+
async function buildFileBlameReportAsync(root, command) {
|
|
4316
|
+
const result = await blameAsync(root, {
|
|
4317
|
+
path: command.path,
|
|
4318
|
+
ref: command.ref,
|
|
4319
|
+
base: command.base
|
|
4320
|
+
});
|
|
4321
|
+
return {
|
|
4322
|
+
path: command.path,
|
|
4323
|
+
ref: command.ref,
|
|
4324
|
+
base: command.base,
|
|
4325
|
+
result
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
3709
4328
|
function buildFileHistoryReport(root, command) {
|
|
3710
4329
|
const result = commitHistory(root, {
|
|
3711
4330
|
ref: command.ref,
|
|
@@ -3723,6 +4342,23 @@ function buildFileHistoryReport(root, command) {
|
|
|
3723
4342
|
result
|
|
3724
4343
|
};
|
|
3725
4344
|
}
|
|
4345
|
+
async function buildFileHistoryReportAsync(root, command) {
|
|
4346
|
+
const result = await commitHistoryAsync(root, {
|
|
4347
|
+
ref: command.ref,
|
|
4348
|
+
skip: command.skip,
|
|
4349
|
+
limit: command.limit,
|
|
4350
|
+
query: command.query,
|
|
4351
|
+
path: command.path
|
|
4352
|
+
});
|
|
4353
|
+
return {
|
|
4354
|
+
path: command.path,
|
|
4355
|
+
ref: command.ref,
|
|
4356
|
+
limit: command.limit,
|
|
4357
|
+
skip: command.skip,
|
|
4358
|
+
...command.query !== undefined ? { query: command.query } : {},
|
|
4359
|
+
result
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
3726
4362
|
function runBlame(root, command) {
|
|
3727
4363
|
const report = buildFileBlameReport(root, command);
|
|
3728
4364
|
if (command.json) {
|
|
@@ -3811,6 +4447,28 @@ function readShowText(root, command) {
|
|
|
3811
4447
|
return { code: 1, stdout: "", stderr: "file not readable" };
|
|
3812
4448
|
}
|
|
3813
4449
|
}
|
|
4450
|
+
async function readShowTextAsync(root, command) {
|
|
4451
|
+
if (command.ref !== "worktree" && command.ref !== "") {
|
|
4452
|
+
return showAsync(command.ref, command.path, root);
|
|
4453
|
+
}
|
|
4454
|
+
const full = safeWorktreePathFromRoot(root, command.path);
|
|
4455
|
+
if (!full) {
|
|
4456
|
+
return {
|
|
4457
|
+
code: 1,
|
|
4458
|
+
stdout: "",
|
|
4459
|
+
stderr: "file not found or forbidden"
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
try {
|
|
4463
|
+
const stat = statSync3(full);
|
|
4464
|
+
if (!stat.isFile()) {
|
|
4465
|
+
return { code: 1, stdout: "", stderr: "not a file" };
|
|
4466
|
+
}
|
|
4467
|
+
return { code: 0, stdout: readFileSync4(full, "utf8"), stderr: "" };
|
|
4468
|
+
} catch {
|
|
4469
|
+
return { code: 1, stdout: "", stderr: "file not readable" };
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
3814
4472
|
function buildFileShowReport(root, command) {
|
|
3815
4473
|
const res = readShowText(root, command);
|
|
3816
4474
|
if (res.code !== 0) {
|
|
@@ -3838,6 +4496,33 @@ function buildFileShowReport(root, command) {
|
|
|
3838
4496
|
`)
|
|
3839
4497
|
};
|
|
3840
4498
|
}
|
|
4499
|
+
async function buildFileShowReportAsync(root, command) {
|
|
4500
|
+
const res = await readShowTextAsync(root, command);
|
|
4501
|
+
if (res.code !== 0) {
|
|
4502
|
+
const detail = res.stderr.trim() || `git show exited with code ${res.code}`;
|
|
4503
|
+
return {
|
|
4504
|
+
path: command.path,
|
|
4505
|
+
ref: command.ref,
|
|
4506
|
+
...command.start !== undefined ? { start: command.start } : {},
|
|
4507
|
+
...command.end !== undefined ? { end: command.end } : {},
|
|
4508
|
+
totalLines: 0,
|
|
4509
|
+
complete: false,
|
|
4510
|
+
text: "",
|
|
4511
|
+
error: detail
|
|
4512
|
+
};
|
|
4513
|
+
}
|
|
4514
|
+
const sliced = sliceLines(res.stdout, command.start, command.end);
|
|
4515
|
+
return {
|
|
4516
|
+
path: command.path,
|
|
4517
|
+
ref: command.ref,
|
|
4518
|
+
...command.start !== undefined ? { start: command.start } : {},
|
|
4519
|
+
...command.end !== undefined ? { end: command.end } : {},
|
|
4520
|
+
totalLines: sliced.total,
|
|
4521
|
+
complete: sliced.complete,
|
|
4522
|
+
text: sliced.lines.join(`
|
|
4523
|
+
`)
|
|
4524
|
+
};
|
|
4525
|
+
}
|
|
3841
4526
|
function runShow(root, command) {
|
|
3842
4527
|
const report = buildFileShowReport(root, command);
|
|
3843
4528
|
if (report.error !== undefined) {
|
|
@@ -3920,6 +4605,64 @@ function buildFileDiffReport(root, command) {
|
|
|
3920
4605
|
...errText ? { error: errText } : {}
|
|
3921
4606
|
};
|
|
3922
4607
|
}
|
|
4608
|
+
async function buildFileDiffReportAsync(root, command) {
|
|
4609
|
+
const base = {
|
|
4610
|
+
path: command.path,
|
|
4611
|
+
...command.oldPath !== undefined ? { old_path: command.oldPath } : {},
|
|
4612
|
+
from: command.untracked ? "/dev/null" : command.from,
|
|
4613
|
+
to: command.to,
|
|
4614
|
+
untracked: command.untracked,
|
|
4615
|
+
ignore_ws: command.ignoreWs,
|
|
4616
|
+
ignore_blank: command.ignoreBlank,
|
|
4617
|
+
mode: command.mode,
|
|
4618
|
+
max_hunks: command.mode === "preview" ? command.maxHunks : null,
|
|
4619
|
+
max_lines: command.mode === "preview" ? command.maxLines : null,
|
|
4620
|
+
diff: "",
|
|
4621
|
+
hunk_count: 0,
|
|
4622
|
+
rendered_hunk_count: 0,
|
|
4623
|
+
line_count: 0,
|
|
4624
|
+
truncated: false,
|
|
4625
|
+
binary: false
|
|
4626
|
+
};
|
|
4627
|
+
if (!command.untracked && isSameWorktreeRange({ from: command.from, to: command.to })) {
|
|
4628
|
+
return base;
|
|
4629
|
+
}
|
|
4630
|
+
const extras = [];
|
|
4631
|
+
if (command.ignoreWs)
|
|
4632
|
+
extras.push("-w");
|
|
4633
|
+
if (command.ignoreBlank)
|
|
4634
|
+
extras.push("--ignore-blank-lines");
|
|
4635
|
+
let diffText = "";
|
|
4636
|
+
let errText = "";
|
|
4637
|
+
if (command.untracked) {
|
|
4638
|
+
const res = await untrackedFileDiffAsync(extras, command.path, root);
|
|
4639
|
+
diffText = res.stdout || "";
|
|
4640
|
+
if (res.code !== 0)
|
|
4641
|
+
errText = res.stderr.trim();
|
|
4642
|
+
} else {
|
|
4643
|
+
const args = [
|
|
4644
|
+
...extras,
|
|
4645
|
+
...buildFileDiffRangeArgs(command.from, command.to)
|
|
4646
|
+
];
|
|
4647
|
+
const paths = command.oldPath !== undefined ? [command.oldPath, command.path] : command.path;
|
|
4648
|
+
const res = await fileDiffTextAsync(args, paths, root);
|
|
4649
|
+
diffText = res.stdout || "";
|
|
4650
|
+
if (res.code !== 0)
|
|
4651
|
+
errText = res.stderr.trim();
|
|
4652
|
+
}
|
|
4653
|
+
const truncated = command.mode === "preview" ? truncateToNHunks(diffText, command.maxHunks, command.maxLines) : truncateToNHunks(diffText, 1e9);
|
|
4654
|
+
const previewTruncated = command.mode === "preview" && (truncated.totalHunks > truncated.renderedHunks || truncated.lineTruncated);
|
|
4655
|
+
return {
|
|
4656
|
+
...base,
|
|
4657
|
+
diff: truncated.text,
|
|
4658
|
+
hunk_count: truncated.totalHunks,
|
|
4659
|
+
rendered_hunk_count: truncated.renderedHunks,
|
|
4660
|
+
line_count: truncated.lineCount,
|
|
4661
|
+
truncated: previewTruncated,
|
|
4662
|
+
binary: diffText.includes("Binary files"),
|
|
4663
|
+
...errText ? { error: errText } : {}
|
|
4664
|
+
};
|
|
4665
|
+
}
|
|
3923
4666
|
function runDiff(root, command) {
|
|
3924
4667
|
const report = buildFileDiffReport(root, command);
|
|
3925
4668
|
if (command.json) {
|
|
@@ -4406,8 +5149,8 @@ function buildGithubIssueViewArgs(options) {
|
|
|
4406
5149
|
args.push("--repo", repo);
|
|
4407
5150
|
return args;
|
|
4408
5151
|
}
|
|
4409
|
-
function
|
|
4410
|
-
const proc =
|
|
5152
|
+
async function readGithubIssueListAsync(options) {
|
|
5153
|
+
const proc = await runAsync(buildGithubIssueListArgs(options), options.cwd, {
|
|
4411
5154
|
timeout: 30000
|
|
4412
5155
|
});
|
|
4413
5156
|
if (proc.code !== 0) {
|
|
@@ -4420,8 +5163,8 @@ function readGithubIssueList(options) {
|
|
|
4420
5163
|
throw new GithubIssueListError("failed to parse gh issue list output");
|
|
4421
5164
|
}
|
|
4422
5165
|
}
|
|
4423
|
-
function
|
|
4424
|
-
const proc =
|
|
5166
|
+
async function readGithubIssueAsync(options) {
|
|
5167
|
+
const proc = await runAsync(buildGithubIssueViewArgs(options), options.cwd, {
|
|
4425
5168
|
timeout: 30000
|
|
4426
5169
|
});
|
|
4427
5170
|
if (proc.code !== 0) {
|
|
@@ -5075,7 +5818,7 @@ async function runJournalCli(argv) {
|
|
|
5075
5818
|
console.error(commandConfig.error);
|
|
5076
5819
|
process.exit(1);
|
|
5077
5820
|
}
|
|
5078
|
-
const issues =
|
|
5821
|
+
const issues = await readGithubIssueListAsync({
|
|
5079
5822
|
cwd: root,
|
|
5080
5823
|
repo: command.repo,
|
|
5081
5824
|
labels: command.ghLabels,
|
|
@@ -5099,7 +5842,7 @@ async function runJournalCli(argv) {
|
|
|
5099
5842
|
console.error(commandConfig.error);
|
|
5100
5843
|
process.exit(1);
|
|
5101
5844
|
}
|
|
5102
|
-
const issue =
|
|
5845
|
+
const issue = await readGithubIssueAsync({
|
|
5103
5846
|
cwd: root,
|
|
5104
5847
|
number: command.issueNumber,
|
|
5105
5848
|
repo: command.repo
|
|
@@ -7839,11 +8582,106 @@ function computeFuzzyMatch(query, path) {
|
|
|
7839
8582
|
tier
|
|
7840
8583
|
};
|
|
7841
8584
|
}
|
|
7842
|
-
function rankFuzzyPaths(query, items) {
|
|
7843
|
-
|
|
8585
|
+
function rankFuzzyPaths(query, items, limit) {
|
|
8586
|
+
const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
|
|
8587
|
+
const compare = (a, b) => b.tier - a.tier || b.score - a.score || a.item.path.localeCompare(b.item.path);
|
|
8588
|
+
if (!bounded) {
|
|
8589
|
+
return items.map((item) => {
|
|
8590
|
+
const match = computeFuzzyMatch(query, item.path);
|
|
8591
|
+
return match ? { item, score: match.score, ranges: match.ranges, tier: match.tier } : null;
|
|
8592
|
+
}).filter((item) => item !== null).sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
|
|
8593
|
+
}
|
|
8594
|
+
const top = [];
|
|
8595
|
+
for (const item of items) {
|
|
7844
8596
|
const match = computeFuzzyMatch(query, item.path);
|
|
7845
|
-
|
|
7846
|
-
|
|
8597
|
+
if (!match)
|
|
8598
|
+
continue;
|
|
8599
|
+
const ranked = {
|
|
8600
|
+
item,
|
|
8601
|
+
score: match.score,
|
|
8602
|
+
ranges: match.ranges,
|
|
8603
|
+
tier: match.tier
|
|
8604
|
+
};
|
|
8605
|
+
pushBoundedTop(top, ranked, bounded, compare);
|
|
8606
|
+
}
|
|
8607
|
+
return top.sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
|
|
8608
|
+
}
|
|
8609
|
+
function pushBoundedTop(heap, item, limit, compareBestFirst) {
|
|
8610
|
+
const isWorse = (a, b) => compareBestFirst(a, b) > 0;
|
|
8611
|
+
const siftUp = (index) => {
|
|
8612
|
+
while (index > 0) {
|
|
8613
|
+
const parent = Math.floor((index - 1) / 2);
|
|
8614
|
+
if (!isWorse(heap[index], heap[parent]))
|
|
8615
|
+
break;
|
|
8616
|
+
[heap[index], heap[parent]] = [heap[parent], heap[index]];
|
|
8617
|
+
index = parent;
|
|
8618
|
+
}
|
|
8619
|
+
};
|
|
8620
|
+
const siftDown = (index) => {
|
|
8621
|
+
while (true) {
|
|
8622
|
+
const left = index * 2 + 1;
|
|
8623
|
+
const right = left + 1;
|
|
8624
|
+
let worst = index;
|
|
8625
|
+
if (left < heap.length && isWorse(heap[left], heap[worst]))
|
|
8626
|
+
worst = left;
|
|
8627
|
+
if (right < heap.length && isWorse(heap[right], heap[worst]))
|
|
8628
|
+
worst = right;
|
|
8629
|
+
if (worst === index)
|
|
8630
|
+
break;
|
|
8631
|
+
[heap[index], heap[worst]] = [heap[worst], heap[index]];
|
|
8632
|
+
index = worst;
|
|
8633
|
+
}
|
|
8634
|
+
};
|
|
8635
|
+
if (heap.length < limit) {
|
|
8636
|
+
heap.push(item);
|
|
8637
|
+
siftUp(heap.length - 1);
|
|
8638
|
+
return;
|
|
8639
|
+
}
|
|
8640
|
+
if (compareBestFirst(item, heap[0]) >= 0)
|
|
8641
|
+
return;
|
|
8642
|
+
heap[0] = item;
|
|
8643
|
+
siftDown(0);
|
|
8644
|
+
}
|
|
8645
|
+
function rankGlobPathMatches(query, items, limit) {
|
|
8646
|
+
const matchPath = createGlobPathMatcher(query);
|
|
8647
|
+
if (!matchPath)
|
|
8648
|
+
return [];
|
|
8649
|
+
const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
|
|
8650
|
+
const compare = (a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path);
|
|
8651
|
+
if (!bounded) {
|
|
8652
|
+
return items.map((item) => {
|
|
8653
|
+
const match = matchPath(item.path);
|
|
8654
|
+
return match ? {
|
|
8655
|
+
item,
|
|
8656
|
+
score: match.score,
|
|
8657
|
+
ranges: match.ranges,
|
|
8658
|
+
mode: "glob"
|
|
8659
|
+
} : null;
|
|
8660
|
+
}).filter((item) => item !== null).sort(compare);
|
|
8661
|
+
}
|
|
8662
|
+
const top = [];
|
|
8663
|
+
for (const item of items) {
|
|
8664
|
+
const match = matchPath(item.path);
|
|
8665
|
+
if (!match)
|
|
8666
|
+
continue;
|
|
8667
|
+
const ranked = {
|
|
8668
|
+
item,
|
|
8669
|
+
score: match.score,
|
|
8670
|
+
ranges: match.ranges,
|
|
8671
|
+
mode: "glob"
|
|
8672
|
+
};
|
|
8673
|
+
pushBoundedTop(top, ranked, bounded, compare);
|
|
8674
|
+
}
|
|
8675
|
+
return top.sort(compare);
|
|
8676
|
+
}
|
|
8677
|
+
function rankPathMatches(query, items, limit) {
|
|
8678
|
+
if (isGlobPathQuery(query)) {
|
|
8679
|
+
return rankGlobPathMatches(query, items, limit);
|
|
8680
|
+
}
|
|
8681
|
+
return rankFuzzyPaths(query, items, limit).map((item) => ({
|
|
8682
|
+
...item,
|
|
8683
|
+
mode: "fuzzy"
|
|
8684
|
+
}));
|
|
7847
8685
|
}
|
|
7848
8686
|
function isGlobPathQuery(query) {
|
|
7849
8687
|
return /[*?]/.test(query.trim());
|
|
@@ -7887,49 +8725,37 @@ function globToRegExp(query) {
|
|
|
7887
8725
|
return null;
|
|
7888
8726
|
}
|
|
7889
8727
|
}
|
|
7890
|
-
function
|
|
8728
|
+
function createGlobPathMatcher(query) {
|
|
7891
8729
|
const regex = globToRegExp(query);
|
|
7892
|
-
|
|
7893
|
-
const basename = path.slice(baseStart);
|
|
7894
|
-
if (!regex || !regex.test(path) && (query.includes("/") || !regex.test(basename)))
|
|
8730
|
+
if (!regex)
|
|
7895
8731
|
return null;
|
|
7896
8732
|
const literal = query.replace(/[*?[\]]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
7897
|
-
const
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
const
|
|
7901
|
-
if (
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
8733
|
+
const suffix = query.replace(/^\*+/, "").toLowerCase();
|
|
8734
|
+
return (path) => {
|
|
8735
|
+
const baseStart = basenameStart(path);
|
|
8736
|
+
const basename = path.slice(baseStart);
|
|
8737
|
+
if (!regex.test(path) && (query.includes("/") || !regex.test(basename)))
|
|
8738
|
+
return null;
|
|
8739
|
+
const ranges = [];
|
|
8740
|
+
const lowerPath = path.toLowerCase();
|
|
8741
|
+
for (const part of literal) {
|
|
8742
|
+
const start = lowerPath.indexOf(part.toLowerCase());
|
|
8743
|
+
if (start >= 0)
|
|
8744
|
+
ranges.push({ start, end: start + part.length });
|
|
8745
|
+
}
|
|
8746
|
+
ranges.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
8747
|
+
const mergedRanges = [];
|
|
8748
|
+
for (const range of ranges) {
|
|
8749
|
+
const last = mergedRanges[mergedRanges.length - 1];
|
|
8750
|
+
if (last && last.end >= range.start) {
|
|
8751
|
+
last.end = Math.max(last.end, range.end);
|
|
8752
|
+
} else {
|
|
8753
|
+
mergedRanges.push({ ...range });
|
|
8754
|
+
}
|
|
7912
8755
|
}
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
}
|
|
7917
|
-
function rankPathMatches(query, items) {
|
|
7918
|
-
if (isGlobPathQuery(query)) {
|
|
7919
|
-
return items.map((item) => {
|
|
7920
|
-
const match = globMatchPath(query, item.path);
|
|
7921
|
-
return match ? {
|
|
7922
|
-
item,
|
|
7923
|
-
score: match.score,
|
|
7924
|
-
ranges: match.ranges,
|
|
7925
|
-
mode: "glob"
|
|
7926
|
-
} : null;
|
|
7927
|
-
}).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
|
|
7928
|
-
}
|
|
7929
|
-
return rankFuzzyPaths(query, items).map((item) => ({
|
|
7930
|
-
...item,
|
|
7931
|
-
mode: "fuzzy"
|
|
7932
|
-
}));
|
|
8756
|
+
const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(suffix) ? 50 : 0);
|
|
8757
|
+
return { score, ranges: mergedRanges };
|
|
8758
|
+
};
|
|
7933
8759
|
}
|
|
7934
8760
|
|
|
7935
8761
|
// web-src/server/search.ts
|
|
@@ -8020,9 +8846,11 @@ function parseRgOutput(stdout, max, omitDirNames = [], excludeNames = []) {
|
|
|
8020
8846
|
const matches = [];
|
|
8021
8847
|
for (const line of stdout.split(`
|
|
8022
8848
|
`)) {
|
|
8023
|
-
if (!line
|
|
8849
|
+
if (!line)
|
|
8024
8850
|
continue;
|
|
8025
|
-
|
|
8851
|
+
if (matches.length >= max)
|
|
8852
|
+
break;
|
|
8853
|
+
const parsed = /^(.*?):(\d+):(\d+):(.*)$/.exec(line);
|
|
8026
8854
|
if (!parsed)
|
|
8027
8855
|
continue;
|
|
8028
8856
|
const path = parsed[1];
|
|
@@ -13308,10 +14136,10 @@ function hasControlCharacter(value) {
|
|
|
13308
14136
|
|
|
13309
14137
|
// web-src/server/database/discovery.ts
|
|
13310
14138
|
import {
|
|
13311
|
-
closeSync,
|
|
14139
|
+
closeSync as closeSync2,
|
|
13312
14140
|
existsSync as existsSync6,
|
|
13313
|
-
openSync,
|
|
13314
|
-
readSync,
|
|
14141
|
+
openSync as openSync2,
|
|
14142
|
+
readSync as readSync2,
|
|
13315
14143
|
realpathSync as realpathSync4,
|
|
13316
14144
|
statSync as statSync4
|
|
13317
14145
|
} from "node:fs";
|
|
@@ -13323,11 +14151,11 @@ function isSqliteFile(fullPath) {
|
|
|
13323
14151
|
if (!stat2.isFile() || stat2.size < 16)
|
|
13324
14152
|
return false;
|
|
13325
14153
|
const buf = Buffer.alloc(16);
|
|
13326
|
-
const fd =
|
|
14154
|
+
const fd = openSync2(fullPath, "r");
|
|
13327
14155
|
try {
|
|
13328
|
-
|
|
14156
|
+
readSync2(fd, buf, 0, 16, 0);
|
|
13329
14157
|
} finally {
|
|
13330
|
-
|
|
14158
|
+
closeSync2(fd);
|
|
13331
14159
|
}
|
|
13332
14160
|
return buf.toString("utf8", 0, 16) === SQLITE_MAGIC;
|
|
13333
14161
|
} catch {
|
|
@@ -13449,15 +14277,26 @@ function validateDbPath(cwd, dbPath) {
|
|
|
13449
14277
|
return null;
|
|
13450
14278
|
return realFull;
|
|
13451
14279
|
}
|
|
13452
|
-
function
|
|
14280
|
+
function serviceListIncludesAwsService(value, service) {
|
|
13453
14281
|
if (value === undefined || value.trim() === "")
|
|
13454
14282
|
return true;
|
|
13455
|
-
return value.split(/[,\s]+/).map((part) => part.trim().toLowerCase()).filter(Boolean).includes(
|
|
14283
|
+
return value.split(/[,\s]+/).map((part) => part.trim().toLowerCase()).filter(Boolean).includes(service);
|
|
14284
|
+
}
|
|
14285
|
+
function detectAwsKindsFromServices(services) {
|
|
14286
|
+
const kinds = [];
|
|
14287
|
+
if (serviceListIncludesAwsService(services, "s3"))
|
|
14288
|
+
kinds.push("s3");
|
|
14289
|
+
if (serviceListIncludesAwsService(services, "dynamodb"))
|
|
14290
|
+
kinds.push("dynamodb");
|
|
14291
|
+
return kinds;
|
|
14292
|
+
}
|
|
14293
|
+
function isLocalstackImage(image) {
|
|
14294
|
+
return !!image && image.toLowerCase().includes("localstack/localstack");
|
|
13456
14295
|
}
|
|
13457
14296
|
function imageLooksLikeMinio(image) {
|
|
13458
14297
|
return /(^|\/)minio(?::|\/|$)/.test(image.toLowerCase());
|
|
13459
14298
|
}
|
|
13460
|
-
function detectDbKind(image,
|
|
14299
|
+
function detectDbKind(image, _env = {}) {
|
|
13461
14300
|
const lower = image.toLowerCase();
|
|
13462
14301
|
if (lower.includes("postgres"))
|
|
13463
14302
|
return "postgresql";
|
|
@@ -13469,9 +14308,6 @@ function detectDbKind(image, env = {}) {
|
|
|
13469
14308
|
return "elasticsearch";
|
|
13470
14309
|
if (imageLooksLikeMinio(lower))
|
|
13471
14310
|
return "s3";
|
|
13472
|
-
if (lower.includes("localstack/localstack")) {
|
|
13473
|
-
return serviceListIncludesS3(env.SERVICES) ? "s3" : null;
|
|
13474
|
-
}
|
|
13475
14311
|
return null;
|
|
13476
14312
|
}
|
|
13477
14313
|
function detectDbKindFromEnv(env) {
|
|
@@ -13484,10 +14320,33 @@ function detectDbKindFromEnv(env) {
|
|
|
13484
14320
|
if (env.MINIO_ROOT_USER || env.MINIO_ROOT_PASSWORD || env.MINIO_ACCESS_KEY || env.MINIO_SECRET_KEY) {
|
|
13485
14321
|
return "s3";
|
|
13486
14322
|
}
|
|
13487
|
-
if (env.SERVICES && serviceListIncludesS3(env.SERVICES))
|
|
13488
|
-
return "s3";
|
|
13489
14323
|
return null;
|
|
13490
14324
|
}
|
|
14325
|
+
function detectDbKindsForService(image, env, containerPort, serviceName) {
|
|
14326
|
+
if (isLocalstackImage(image) || env.SERVICES !== undefined) {
|
|
14327
|
+
return detectAwsKindsFromServices(env.SERVICES);
|
|
14328
|
+
}
|
|
14329
|
+
const single = (image ? detectDbKind(image, env) : null) ?? detectDbKindFromEnv(env) ?? detectDbKindFromContainerPort(containerPort) ?? detectDbKindFromServiceName(serviceName);
|
|
14330
|
+
return single ? [single] : [];
|
|
14331
|
+
}
|
|
14332
|
+
function dbKindDisplayName(kind) {
|
|
14333
|
+
switch (kind) {
|
|
14334
|
+
case "s3":
|
|
14335
|
+
return "S3";
|
|
14336
|
+
case "dynamodb":
|
|
14337
|
+
return "DynamoDB";
|
|
14338
|
+
case "redis":
|
|
14339
|
+
return "Redis";
|
|
14340
|
+
case "elasticsearch":
|
|
14341
|
+
return "Elasticsearch";
|
|
14342
|
+
case "postgresql":
|
|
14343
|
+
return "PostgreSQL";
|
|
14344
|
+
case "mysql":
|
|
14345
|
+
return "MySQL";
|
|
14346
|
+
case "sqlite":
|
|
14347
|
+
return "SQLite";
|
|
14348
|
+
}
|
|
14349
|
+
}
|
|
13491
14350
|
function detectDbKindFromContainerPort(port) {
|
|
13492
14351
|
switch (port) {
|
|
13493
14352
|
case "3306":
|
|
@@ -13532,8 +14391,10 @@ function defaultPortFor(kind, image, env = {}) {
|
|
|
13532
14391
|
return "9200";
|
|
13533
14392
|
case "s3": {
|
|
13534
14393
|
const lower = image?.toLowerCase() || "";
|
|
13535
|
-
return lower.includes("localstack/localstack") || env.SERVICES !== undefined &&
|
|
14394
|
+
return lower.includes("localstack/localstack") || env.SERVICES !== undefined && serviceListIncludesAwsService(env.SERVICES, "s3") ? "4566" : "9000";
|
|
13536
14395
|
}
|
|
14396
|
+
case "dynamodb":
|
|
14397
|
+
return "4566";
|
|
13537
14398
|
default:
|
|
13538
14399
|
return "";
|
|
13539
14400
|
}
|
|
@@ -13620,7 +14481,7 @@ function parseComposePortMappings(serviceBlock, composeDirEnv = {}) {
|
|
|
13620
14481
|
const trimmed = line.trim();
|
|
13621
14482
|
if (!trimmed.startsWith("-"))
|
|
13622
14483
|
continue;
|
|
13623
|
-
const value = resolveEnvValue(trimmed.slice(1).trim()
|
|
14484
|
+
const value = resolveEnvValue(trimmed.slice(1).trim(), composeDirEnv).split("/")[0].trim();
|
|
13624
14485
|
if (!value || value.includes("target:"))
|
|
13625
14486
|
continue;
|
|
13626
14487
|
const parts = value.split(":");
|
|
@@ -13705,40 +14566,45 @@ function parseComposeContent(content, filepath, composeDir, cwd, composeDirEnv,
|
|
|
13705
14566
|
const env = parseComposeEnv(svcBlock, composeDirEnv);
|
|
13706
14567
|
const containerPort = parseComposeContainerPort(svcBlock);
|
|
13707
14568
|
const profiled = /^\s+profiles:/m.test(svcBlock);
|
|
13708
|
-
const
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
|
-
const
|
|
13721
|
-
label
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
14569
|
+
const kinds = detectDbKindsForService(image, env, containerPort, svc.name);
|
|
14570
|
+
const isMultiKind = kinds.length > 1;
|
|
14571
|
+
for (const kind of kinds) {
|
|
14572
|
+
if (results.length >= MAX_DOCKER_SERVICES)
|
|
14573
|
+
return;
|
|
14574
|
+
const defaultPort = defaultPortFor(kind, image, env);
|
|
14575
|
+
const serviceContainerPort = kind === "s3" || kind === "dynamodb" ? defaultPort : containerPort || defaultPort;
|
|
14576
|
+
const publishedHostPort = parseComposeHostPortForContainer(svcBlock, serviceContainerPort, composeDirEnv) || parseComposeHostPortForContainer(svcBlock, defaultPort, composeDirEnv) || parseComposePorts(svcBlock, composeDirEnv);
|
|
14577
|
+
const hostPort = kind === "s3" || kind === "dynamodb" ? publishedHostPort || undefined : publishedHostPort || defaultPort;
|
|
14578
|
+
const imageLabel = image ?? `build:${kind}`;
|
|
14579
|
+
const kindSuffix = isMultiKind ? `#${kind}` : "";
|
|
14580
|
+
const id = isRoot ? `docker:${svc.name}${kindSuffix}` : `docker:${svc.name}@${encodeURIComponent(relDirSlash)}${kindSuffix}`;
|
|
14581
|
+
const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
|
|
14582
|
+
let label;
|
|
14583
|
+
if (kind === "redis" || kind === "elasticsearch" || kind === "s3" || kind === "dynamodb") {
|
|
14584
|
+
const endpointLabel = hostPort ? `localhost:${hostPort}` : `container:${serviceContainerPort}`;
|
|
14585
|
+
const svcLabel = isMultiKind ? `${svc.name} / ${dbKindDisplayName(kind)}` : svc.name;
|
|
14586
|
+
label = `${svcLabel} (${imageLabel}, ${endpointLabel}${labelPath})`;
|
|
14587
|
+
} else {
|
|
14588
|
+
const dbName = env.POSTGRES_DB || env.MYSQL_DATABASE || env.MARIADB_DATABASE || svc.name;
|
|
14589
|
+
const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
|
|
14590
|
+
label = `${svc.name} (${imageLabel}, ${user}@localhost:${hostPort}/${dbName}${labelPath})`;
|
|
14591
|
+
}
|
|
14592
|
+
results.push({
|
|
14593
|
+
id,
|
|
14594
|
+
path: isRoot ? filename : `${relDirSlash}/${filename}`,
|
|
14595
|
+
name: label,
|
|
14596
|
+
sizeBytes: 0,
|
|
14597
|
+
kind,
|
|
14598
|
+
serviceName: svc.name,
|
|
14599
|
+
...image ? { image } : {},
|
|
14600
|
+
env,
|
|
14601
|
+
composeDir,
|
|
14602
|
+
relDirSlash,
|
|
14603
|
+
...hostPort ? { hostPort } : {},
|
|
14604
|
+
containerPort: serviceContainerPort,
|
|
14605
|
+
...profiled ? { profiled: true } : {}
|
|
14606
|
+
});
|
|
13726
14607
|
}
|
|
13727
|
-
results.push({
|
|
13728
|
-
id,
|
|
13729
|
-
path: isRoot ? filename : `${relDirSlash}/${filename}`,
|
|
13730
|
-
name: label,
|
|
13731
|
-
sizeBytes: 0,
|
|
13732
|
-
kind,
|
|
13733
|
-
serviceName: svc.name,
|
|
13734
|
-
...image ? { image } : {},
|
|
13735
|
-
env,
|
|
13736
|
-
composeDir,
|
|
13737
|
-
relDirSlash,
|
|
13738
|
-
...hostPort ? { hostPort } : {},
|
|
13739
|
-
containerPort: serviceContainerPort,
|
|
13740
|
-
...profiled ? { profiled: true } : {}
|
|
13741
|
-
});
|
|
13742
14608
|
}
|
|
13743
14609
|
}
|
|
13744
14610
|
async function parseComposeFileAsync(filepath, composeDir, cwd, results) {
|
|
@@ -13836,12 +14702,24 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
13836
14702
|
});
|
|
13837
14703
|
return cloneDockerDiscoveryResult(results);
|
|
13838
14704
|
}
|
|
14705
|
+
function isDbKind(value) {
|
|
14706
|
+
return DB_KIND_VALUES.has(value);
|
|
14707
|
+
}
|
|
13839
14708
|
function parseDockerDbId(dbId) {
|
|
13840
14709
|
if (!dbId.startsWith("docker:"))
|
|
13841
14710
|
return null;
|
|
13842
14711
|
let rest = dbId.slice(7);
|
|
13843
14712
|
if (!rest)
|
|
13844
14713
|
return null;
|
|
14714
|
+
let kind;
|
|
14715
|
+
const hashIdx = rest.indexOf("#");
|
|
14716
|
+
if (hashIdx >= 0) {
|
|
14717
|
+
const kindPart = rest.slice(hashIdx + 1);
|
|
14718
|
+
if (!isDbKind(kindPart))
|
|
14719
|
+
return null;
|
|
14720
|
+
kind = kindPart;
|
|
14721
|
+
rest = rest.slice(0, hashIdx);
|
|
14722
|
+
}
|
|
13845
14723
|
let database;
|
|
13846
14724
|
const atIdx = rest.indexOf("@");
|
|
13847
14725
|
if (atIdx >= 0) {
|
|
@@ -13865,7 +14743,8 @@ function parseDockerDbId(dbId) {
|
|
|
13865
14743
|
return {
|
|
13866
14744
|
serviceName,
|
|
13867
14745
|
relDir,
|
|
13868
|
-
database
|
|
14746
|
+
database,
|
|
14747
|
+
kind
|
|
13869
14748
|
};
|
|
13870
14749
|
} catch {
|
|
13871
14750
|
return null;
|
|
@@ -13880,23 +14759,27 @@ function parseDockerDbId(dbId) {
|
|
|
13880
14759
|
return null;
|
|
13881
14760
|
if (!isSafeDockerDatabaseName(database))
|
|
13882
14761
|
return null;
|
|
13883
|
-
return { serviceName: rest, relDir: "", database };
|
|
14762
|
+
return { serviceName: rest, relDir: "", database, kind };
|
|
13884
14763
|
}
|
|
13885
14764
|
function canonicalizeDockerDbId(dbId) {
|
|
13886
14765
|
const parsed = parseDockerDbId(dbId);
|
|
13887
14766
|
if (!parsed)
|
|
13888
14767
|
return null;
|
|
13889
14768
|
const database = parsed.database ? `:${parsed.database}` : "";
|
|
14769
|
+
const kindSuffix = parsed.kind ? `#${parsed.kind}` : "";
|
|
13890
14770
|
if (!parsed.relDir)
|
|
13891
|
-
return `docker:${parsed.serviceName}${database}`;
|
|
13892
|
-
return `docker:${parsed.serviceName}@${encodeURIComponent(parsed.relDir)}${database}`;
|
|
14771
|
+
return `docker:${parsed.serviceName}${database}${kindSuffix}`;
|
|
14772
|
+
return `docker:${parsed.serviceName}@${encodeURIComponent(parsed.relDir)}${database}${kindSuffix}`;
|
|
13893
14773
|
}
|
|
13894
14774
|
async function findDockerServiceByDbIdAsync(cwd, dbId, kind, omitDirNames, signal) {
|
|
13895
14775
|
const parsed = parseDockerDbId(dbId);
|
|
13896
14776
|
if (!parsed)
|
|
13897
14777
|
return null;
|
|
14778
|
+
if (kind && parsed.kind && parsed.kind !== kind)
|
|
14779
|
+
return null;
|
|
14780
|
+
const effectiveKind = parsed.kind ?? kind;
|
|
13898
14781
|
const services = await discoverDockerDatabasesAsync(cwd, omitDirNames, signal);
|
|
13899
|
-
return services.find((d) => d.serviceName === parsed.serviceName && d.relDirSlash === parsed.relDir && (!
|
|
14782
|
+
return services.find((d) => d.serviceName === parsed.serviceName && d.relDirSlash === parsed.relDir && (!effectiveKind || d.kind === effectiveKind)) || null;
|
|
13900
14783
|
}
|
|
13901
14784
|
function isSafeDockerServiceName(value) {
|
|
13902
14785
|
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
@@ -14031,7 +14914,7 @@ async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal
|
|
|
14031
14914
|
const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
|
|
14032
14915
|
return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
|
|
14033
14916
|
}
|
|
14034
|
-
var SQLITE_EXTENSIONS, SQLITE_MAGIC = "SQLite format 3\x00", MAX_SCAN_DEPTH = 3, MAX_ENTRIES = 50, DOCKER_DISCOVERY_TTL_MS = 5000, SQLITE_DISCOVERY_TTL_MS = 5000, sqliteDiscoveryCache, COMPOSE_FILENAMES, MAX_DOCKER_SERVICES = 30, dockerDiscoveryCache, MAX_SUPABASE_PROJECTS = 30, SUPABASE_DISCOVERY_TTL_MS = 5000, DEFAULT_SUPABASE_DB_PORT = "54322", supabaseDiscoveryCache;
|
|
14917
|
+
var SQLITE_EXTENSIONS, SQLITE_MAGIC = "SQLite format 3\x00", MAX_SCAN_DEPTH = 3, MAX_ENTRIES = 50, DOCKER_DISCOVERY_TTL_MS = 5000, SQLITE_DISCOVERY_TTL_MS = 5000, sqliteDiscoveryCache, COMPOSE_FILENAMES, MAX_DOCKER_SERVICES = 30, dockerDiscoveryCache, DB_KIND_VALUES, MAX_SUPABASE_PROJECTS = 30, SUPABASE_DISCOVERY_TTL_MS = 5000, DEFAULT_SUPABASE_DB_PORT = "54322", supabaseDiscoveryCache;
|
|
14035
14918
|
var init_discovery = __esm(() => {
|
|
14036
14919
|
SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
|
|
14037
14920
|
sqliteDiscoveryCache = new Map;
|
|
@@ -14042,6 +14925,15 @@ var init_discovery = __esm(() => {
|
|
|
14042
14925
|
"compose.yaml"
|
|
14043
14926
|
];
|
|
14044
14927
|
dockerDiscoveryCache = new Map;
|
|
14928
|
+
DB_KIND_VALUES = new Set([
|
|
14929
|
+
"sqlite",
|
|
14930
|
+
"postgresql",
|
|
14931
|
+
"mysql",
|
|
14932
|
+
"redis",
|
|
14933
|
+
"elasticsearch",
|
|
14934
|
+
"s3",
|
|
14935
|
+
"dynamodb"
|
|
14936
|
+
]);
|
|
14045
14937
|
supabaseDiscoveryCache = new Map;
|
|
14046
14938
|
});
|
|
14047
14939
|
|
|
@@ -15024,19 +15916,481 @@ async function searchTableAsync(adapter, table, columns, term, maxHits, includeN
|
|
|
15024
15916
|
rowPreview: serializeDbRow(row)
|
|
15025
15917
|
});
|
|
15026
15918
|
}
|
|
15027
|
-
} catch (err) {
|
|
15028
|
-
if (isAbortLikeError(err, signal))
|
|
15029
|
-
throw err;
|
|
15030
|
-
}
|
|
15919
|
+
} catch (err) {
|
|
15920
|
+
if (isAbortLikeError(err, signal))
|
|
15921
|
+
throw err;
|
|
15922
|
+
}
|
|
15923
|
+
}
|
|
15924
|
+
return hits;
|
|
15925
|
+
}
|
|
15926
|
+
function getPrimaryKeyColumnsFromColumns(columns) {
|
|
15927
|
+
return columns.filter((c) => c.primaryKey).map((c) => c.name);
|
|
15928
|
+
}
|
|
15929
|
+
var init_global_search = __esm(() => {
|
|
15930
|
+
init_serialize();
|
|
15931
|
+
init_sql_utils();
|
|
15932
|
+
});
|
|
15933
|
+
|
|
15934
|
+
// web-src/server/database/adapters/dynamodb.ts
|
|
15935
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
15936
|
+
import { createHash as createHash5, createHmac as createHmac2 } from "node:crypto";
|
|
15937
|
+
function createDynamoDbRequestDeadline() {
|
|
15938
|
+
const timeoutMs = dynamoDbRequestTimeoutMs;
|
|
15939
|
+
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
15940
|
+
}
|
|
15941
|
+
function createDynamoDbDockerCurlDeadline() {
|
|
15942
|
+
const timeoutMs = dynamoDbDockerCurlTimeoutMs;
|
|
15943
|
+
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
15944
|
+
}
|
|
15945
|
+
function createDynamoDbTransportDeadline(config) {
|
|
15946
|
+
return config.dockerContainerName ? createDynamoDbDockerCurlDeadline() : createDynamoDbRequestDeadline();
|
|
15947
|
+
}
|
|
15948
|
+
function dynamoDbTimeoutError(deadline) {
|
|
15949
|
+
return new DynamoDbHttpError(503, `DynamoDB request timed out after ${deadline?.timeoutMs ?? dynamoDbRequestTimeoutMs}ms`);
|
|
15950
|
+
}
|
|
15951
|
+
function remainingDynamoDbTimeoutMs(deadline) {
|
|
15952
|
+
if (!deadline)
|
|
15953
|
+
return dynamoDbRequestTimeoutMs;
|
|
15954
|
+
return Math.max(0, deadline.expiresAt - Date.now());
|
|
15955
|
+
}
|
|
15956
|
+
function hmac2(key, value) {
|
|
15957
|
+
return createHmac2("sha256", key).update(value, "utf8").digest();
|
|
15958
|
+
}
|
|
15959
|
+
function sha2562(value) {
|
|
15960
|
+
return createHash5("sha256").update(value, "utf8").digest("hex");
|
|
15961
|
+
}
|
|
15962
|
+
function amzDate2(date = new Date) {
|
|
15963
|
+
const iso = date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
15964
|
+
return { dateStamp: iso.slice(0, 8), amzDate: iso };
|
|
15965
|
+
}
|
|
15966
|
+
function signingKey2(secret, dateStamp, region) {
|
|
15967
|
+
const kDate = hmac2(`AWS4${secret}`, dateStamp);
|
|
15968
|
+
const kRegion = hmac2(kDate, region);
|
|
15969
|
+
const kService = hmac2(kRegion, "dynamodb");
|
|
15970
|
+
return hmac2(kService, "aws4_request");
|
|
15971
|
+
}
|
|
15972
|
+
function compareCodeUnit2(a, b) {
|
|
15973
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
15974
|
+
}
|
|
15975
|
+
function signedHeadersString2(headers) {
|
|
15976
|
+
return Object.keys(headers).map((key) => key.toLowerCase()).sort(compareCodeUnit2).join(";");
|
|
15977
|
+
}
|
|
15978
|
+
function canonicalHeaders2(headers) {
|
|
15979
|
+
return Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value.trim().replace(/\s+/g, " ")]).sort(([a], [b]) => compareCodeUnit2(a, b)).map(([key, value]) => `${key}:${value}
|
|
15980
|
+
`).join("");
|
|
15981
|
+
}
|
|
15982
|
+
function splitCurlHeadersAndBody2(bytes) {
|
|
15983
|
+
const buffer = Buffer.from(bytes);
|
|
15984
|
+
let idx = buffer.indexOf(`\r
|
|
15985
|
+
\r
|
|
15986
|
+
`);
|
|
15987
|
+
let sepLen = 4;
|
|
15988
|
+
if (idx < 0) {
|
|
15989
|
+
idx = buffer.indexOf(`
|
|
15990
|
+
|
|
15991
|
+
`);
|
|
15992
|
+
sepLen = 2;
|
|
15993
|
+
}
|
|
15994
|
+
if (idx < 0)
|
|
15995
|
+
return { headerText: "", body: bytes };
|
|
15996
|
+
return {
|
|
15997
|
+
headerText: buffer.subarray(0, idx).toString("utf8"),
|
|
15998
|
+
body: new Uint8Array(buffer.subarray(idx + sepLen))
|
|
15999
|
+
};
|
|
16000
|
+
}
|
|
16001
|
+
function responseFromCurlOutput2(bytes) {
|
|
16002
|
+
const { headerText, body } = splitCurlHeadersAndBody2(bytes);
|
|
16003
|
+
const headerBlocks = headerText.split(/\r?\n\r?\n/).map((block2) => block2.trim()).filter(Boolean);
|
|
16004
|
+
const block = headerBlocks[headerBlocks.length - 1] || "";
|
|
16005
|
+
const lines = block.split(/\r?\n/).filter(Boolean);
|
|
16006
|
+
const statusMatch = lines[0]?.match(/^HTTP\/\S+\s+(\d+)/i);
|
|
16007
|
+
const status = statusMatch ? Number(statusMatch[1]) : 502;
|
|
16008
|
+
const headers = new Headers;
|
|
16009
|
+
for (const line of lines.slice(1)) {
|
|
16010
|
+
const idx = line.indexOf(":");
|
|
16011
|
+
if (idx <= 0)
|
|
16012
|
+
continue;
|
|
16013
|
+
headers.append(line.slice(0, idx).trim(), line.slice(idx + 1).trim());
|
|
16014
|
+
}
|
|
16015
|
+
const arrayBuffer = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
|
|
16016
|
+
return new Response(arrayBuffer, { status, headers });
|
|
16017
|
+
}
|
|
16018
|
+
function curlConfigQuote2(value) {
|
|
16019
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/[\r\n]+/g, " ")}"`;
|
|
16020
|
+
}
|
|
16021
|
+
function curlHeaderConfig2(headers) {
|
|
16022
|
+
const lines = [];
|
|
16023
|
+
headers.forEach((value, key) => {
|
|
16024
|
+
lines.push(`header = ${curlConfigQuote2(`${key}: ${value}`)}`);
|
|
16025
|
+
});
|
|
16026
|
+
return `${lines.join(`
|
|
16027
|
+
`)}
|
|
16028
|
+
`;
|
|
16029
|
+
}
|
|
16030
|
+
function dockerCurlCommand2(opts) {
|
|
16031
|
+
const shellScript = [
|
|
16032
|
+
"set -eu",
|
|
16033
|
+
'headers_file="$(mktemp)"',
|
|
16034
|
+
'body_file="$(mktemp)"',
|
|
16035
|
+
`trap 'rm -f "$headers_file" "$body_file"' EXIT`,
|
|
16036
|
+
`while IFS= read -r line; do [ "$line" = "${DOCKER_CURL_BODY_MARKER}" ] && break; printf '%s\\n' "$line" >> "$headers_file"; done`,
|
|
16037
|
+
'cat > "$body_file"',
|
|
16038
|
+
`curl -sS -X POST -D - -o - -K "$headers_file" --data-binary "@$body_file" ${curlConfigQuote2(opts.url)}`
|
|
16039
|
+
].join(`
|
|
16040
|
+
`);
|
|
16041
|
+
return {
|
|
16042
|
+
args: ["exec", "-i", opts.containerName, "sh", "-c", shellScript],
|
|
16043
|
+
input: Buffer.from(`${curlHeaderConfig2(opts.headers)}${DOCKER_CURL_BODY_MARKER}
|
|
16044
|
+
${opts.body}`, "utf8")
|
|
16045
|
+
};
|
|
16046
|
+
}
|
|
16047
|
+
function guardedDynamoDbTransport(signal, operation, deadline) {
|
|
16048
|
+
if (signal?.aborted) {
|
|
16049
|
+
return Promise.reject(new DynamoDbHttpError(503, "DynamoDB HTTP transport aborted"));
|
|
16050
|
+
}
|
|
16051
|
+
const timeoutMs = remainingDynamoDbTimeoutMs(deadline);
|
|
16052
|
+
if (timeoutMs <= 0)
|
|
16053
|
+
return Promise.reject(dynamoDbTimeoutError(deadline));
|
|
16054
|
+
const controller = new AbortController;
|
|
16055
|
+
let timedOut = false;
|
|
16056
|
+
let settled = false;
|
|
16057
|
+
let timer;
|
|
16058
|
+
let cleanupParent = () => {
|
|
16059
|
+
return;
|
|
16060
|
+
};
|
|
16061
|
+
const abort = (err, reject) => {
|
|
16062
|
+
if (settled)
|
|
16063
|
+
return;
|
|
16064
|
+
settled = true;
|
|
16065
|
+
controller.abort(err);
|
|
16066
|
+
reject(err);
|
|
16067
|
+
};
|
|
16068
|
+
const guarded = new Promise((resolve2, reject) => {
|
|
16069
|
+
const onParentAbort = () => abort(new DynamoDbHttpError(503, "DynamoDB HTTP transport aborted"), reject);
|
|
16070
|
+
if (signal) {
|
|
16071
|
+
signal.addEventListener("abort", onParentAbort, { once: true });
|
|
16072
|
+
cleanupParent = () => signal.removeEventListener("abort", onParentAbort);
|
|
16073
|
+
}
|
|
16074
|
+
timer = setTimeout(() => {
|
|
16075
|
+
timedOut = true;
|
|
16076
|
+
abort(dynamoDbTimeoutError(deadline), reject);
|
|
16077
|
+
}, timeoutMs);
|
|
16078
|
+
operation(controller.signal).then((value) => {
|
|
16079
|
+
if (settled)
|
|
16080
|
+
return;
|
|
16081
|
+
settled = true;
|
|
16082
|
+
resolve2(value);
|
|
16083
|
+
}, (err) => {
|
|
16084
|
+
if (settled)
|
|
16085
|
+
return;
|
|
16086
|
+
settled = true;
|
|
16087
|
+
if (timedOut) {
|
|
16088
|
+
reject(dynamoDbTimeoutError(deadline));
|
|
16089
|
+
} else if (signal?.aborted) {
|
|
16090
|
+
reject(new DynamoDbHttpError(503, "DynamoDB HTTP transport aborted"));
|
|
16091
|
+
} else {
|
|
16092
|
+
reject(err);
|
|
16093
|
+
}
|
|
16094
|
+
});
|
|
16095
|
+
});
|
|
16096
|
+
return guarded.finally(() => {
|
|
16097
|
+
if (timer)
|
|
16098
|
+
clearTimeout(timer);
|
|
16099
|
+
cleanupParent();
|
|
16100
|
+
});
|
|
16101
|
+
}
|
|
16102
|
+
async function readStreamChunkWithTimeout2(reader, signal, deadline) {
|
|
16103
|
+
return guardedDynamoDbTransport(signal, (transportSignal) => {
|
|
16104
|
+
const cancelRead = () => {
|
|
16105
|
+
reader.cancel(transportSignal.reason).catch(() => {
|
|
16106
|
+
return;
|
|
16107
|
+
});
|
|
16108
|
+
};
|
|
16109
|
+
if (transportSignal.aborted) {
|
|
16110
|
+
cancelRead();
|
|
16111
|
+
} else {
|
|
16112
|
+
transportSignal.addEventListener("abort", cancelRead, { once: true });
|
|
16113
|
+
}
|
|
16114
|
+
return reader.read().finally(() => {
|
|
16115
|
+
transportSignal.removeEventListener("abort", cancelRead);
|
|
16116
|
+
});
|
|
16117
|
+
}, deadline);
|
|
16118
|
+
}
|
|
16119
|
+
async function readResponseBytesWithTimeout2(res, signal, deadline) {
|
|
16120
|
+
if (!res.body)
|
|
16121
|
+
return new Uint8Array;
|
|
16122
|
+
const reader = res.body.getReader();
|
|
16123
|
+
const chunks = [];
|
|
16124
|
+
let total = 0;
|
|
16125
|
+
try {
|
|
16126
|
+
for (;; ) {
|
|
16127
|
+
const { done, value } = await readStreamChunkWithTimeout2(reader, signal, deadline);
|
|
16128
|
+
if (done)
|
|
16129
|
+
break;
|
|
16130
|
+
if (!value?.byteLength)
|
|
16131
|
+
continue;
|
|
16132
|
+
chunks.push(value);
|
|
16133
|
+
total += value.byteLength;
|
|
16134
|
+
}
|
|
16135
|
+
} finally {
|
|
16136
|
+
try {
|
|
16137
|
+
reader.releaseLock();
|
|
16138
|
+
} catch {}
|
|
16139
|
+
}
|
|
16140
|
+
if (chunks.length === 1)
|
|
16141
|
+
return chunks[0];
|
|
16142
|
+
const bytes = new Uint8Array(total);
|
|
16143
|
+
let offset = 0;
|
|
16144
|
+
for (const chunk of chunks) {
|
|
16145
|
+
bytes.set(chunk, offset);
|
|
16146
|
+
offset += chunk.byteLength;
|
|
16147
|
+
}
|
|
16148
|
+
return bytes;
|
|
16149
|
+
}
|
|
16150
|
+
async function readResponseTextWithTimeout2(res, signal, deadline) {
|
|
16151
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(await readResponseBytesWithTimeout2(res, signal, deadline));
|
|
16152
|
+
}
|
|
16153
|
+
async function dockerCurlFetch2(opts) {
|
|
16154
|
+
const { args, input } = dockerCurlCommand2(opts);
|
|
16155
|
+
if (spawnSyncImplIsTestOverride2) {
|
|
16156
|
+
const proc2 = spawnSyncImpl4(dockerCommand(), args, {
|
|
16157
|
+
encoding: "buffer",
|
|
16158
|
+
input,
|
|
16159
|
+
timeout: dynamoDbDockerCurlTimeoutMs,
|
|
16160
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
16161
|
+
});
|
|
16162
|
+
if ((proc2.status ?? 1) !== 0) {
|
|
16163
|
+
const stderr = new TextDecoder().decode(proc2.stderr || new Uint8Array).concat(proc2.error ? `
|
|
16164
|
+
${proc2.error.message}` : "").replace(/\s+/g, " ").trim();
|
|
16165
|
+
throwIfDockerCommandUnavailableResult({
|
|
16166
|
+
code: proc2.status ?? 1,
|
|
16167
|
+
stderr
|
|
16168
|
+
});
|
|
16169
|
+
throw new DynamoDbHttpError(503, `DynamoDB HTTP transport failed via docker exec${stderr ? `: ${stderr.slice(0, 240)}` : ""}`);
|
|
16170
|
+
}
|
|
16171
|
+
return responseFromCurlOutput2(new Uint8Array(proc2.stdout || new Uint8Array));
|
|
16172
|
+
}
|
|
16173
|
+
if (opts.signal?.aborted) {
|
|
16174
|
+
throw new DynamoDbHttpError(503, "DynamoDB HTTP transport aborted");
|
|
16175
|
+
}
|
|
16176
|
+
const proc = await spawnCollectAsync({
|
|
16177
|
+
command: dockerCommand(),
|
|
16178
|
+
args,
|
|
16179
|
+
input,
|
|
16180
|
+
timeoutMs: dynamoDbDockerCurlTimeoutMs,
|
|
16181
|
+
signal: opts.signal,
|
|
16182
|
+
abortMessage: "DynamoDB HTTP transport aborted",
|
|
16183
|
+
timeoutMessage: `docker exec curl timed out after ${dynamoDbDockerCurlTimeoutMs}ms`,
|
|
16184
|
+
rejectOnError: false
|
|
16185
|
+
});
|
|
16186
|
+
if (proc.code !== 0) {
|
|
16187
|
+
const stderr = new TextDecoder().decode(proc.stderr).replace(/\s+/g, " ").trim();
|
|
16188
|
+
throwIfDockerCommandUnavailableResult({ code: proc.code, stderr });
|
|
16189
|
+
throw new DynamoDbHttpError(503, `DynamoDB HTTP transport failed via docker exec${stderr ? `: ${stderr.slice(0, 240)}` : ""}`);
|
|
16190
|
+
}
|
|
16191
|
+
return responseFromCurlOutput2(new Uint8Array(proc.stdout));
|
|
16192
|
+
}
|
|
16193
|
+
function sanitizeDynamoDbError(status, text) {
|
|
16194
|
+
let message = "";
|
|
16195
|
+
try {
|
|
16196
|
+
const parsed = JSON.parse(text);
|
|
16197
|
+
const type = typeof parsed.__type === "string" ? parsed.__type.split("#").pop() || parsed.__type : "";
|
|
16198
|
+
const bodyMessage = typeof parsed.message === "string" ? parsed.message : typeof parsed.Message === "string" ? parsed.Message : "";
|
|
16199
|
+
message = [type, bodyMessage].filter(Boolean).join(": ");
|
|
16200
|
+
} catch {
|
|
16201
|
+
message = text.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
16202
|
+
}
|
|
16203
|
+
return new DynamoDbHttpError(status, `DynamoDB HTTP ${status}: ${message || "request failed"}`);
|
|
16204
|
+
}
|
|
16205
|
+
function assertTableName(tableName) {
|
|
16206
|
+
if (!/^[A-Za-z0-9_.-]{3,255}$/.test(tableName)) {
|
|
16207
|
+
throw new Error("invalid DynamoDB table name");
|
|
16208
|
+
}
|
|
16209
|
+
}
|
|
16210
|
+
function asObject(value) {
|
|
16211
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
16212
|
+
}
|
|
16213
|
+
function asStringArray(value) {
|
|
16214
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
16215
|
+
}
|
|
16216
|
+
function asNumber(value) {
|
|
16217
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
16218
|
+
}
|
|
16219
|
+
function createDynamoDbAdapter(config) {
|
|
16220
|
+
async function signedJsonRequest(action, body, signal, deadline = createDynamoDbTransportDeadline(config)) {
|
|
16221
|
+
const requestBody = JSON.stringify(body);
|
|
16222
|
+
const res = await guardedDynamoDbTransport(signal, (transportSignal) => {
|
|
16223
|
+
const endpoint = new URL(config.endpoint);
|
|
16224
|
+
const { dateStamp, amzDate: requestDate } = amzDate2();
|
|
16225
|
+
const payloadHash = requestBody ? sha2562(requestBody) : EMPTY_SHA2562;
|
|
16226
|
+
const headers = {
|
|
16227
|
+
"content-type": DYNAMODB_JSON_CONTENT_TYPE,
|
|
16228
|
+
host: endpoint.host,
|
|
16229
|
+
"x-amz-content-sha256": payloadHash,
|
|
16230
|
+
"x-amz-date": requestDate,
|
|
16231
|
+
"x-amz-target": `DynamoDB_20120810.${action}`,
|
|
16232
|
+
...config.sessionToken ? { "x-amz-security-token": config.sessionToken } : {}
|
|
16233
|
+
};
|
|
16234
|
+
const signedNames = signedHeadersString2(headers);
|
|
16235
|
+
const canonicalRequest = [
|
|
16236
|
+
"POST",
|
|
16237
|
+
"/",
|
|
16238
|
+
"",
|
|
16239
|
+
canonicalHeaders2(headers),
|
|
16240
|
+
signedNames,
|
|
16241
|
+
payloadHash
|
|
16242
|
+
].join(`
|
|
16243
|
+
`);
|
|
16244
|
+
const scope = `${dateStamp}/${config.region}/dynamodb/aws4_request`;
|
|
16245
|
+
const stringToSign = [
|
|
16246
|
+
"AWS4-HMAC-SHA256",
|
|
16247
|
+
requestDate,
|
|
16248
|
+
scope,
|
|
16249
|
+
sha2562(canonicalRequest)
|
|
16250
|
+
].join(`
|
|
16251
|
+
`);
|
|
16252
|
+
const signature = createHmac2("sha256", signingKey2(config.secretAccessKey, dateStamp, config.region)).update(stringToSign, "utf8").digest("hex");
|
|
16253
|
+
const requestHeaders = new Headers;
|
|
16254
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
16255
|
+
if (key !== "host")
|
|
16256
|
+
requestHeaders.set(key, value);
|
|
16257
|
+
}
|
|
16258
|
+
requestHeaders.set("Authorization", `AWS4-HMAC-SHA256 Credential=${config.accessKeyId}/${scope}, SignedHeaders=${signedNames}, Signature=${signature}`);
|
|
16259
|
+
const url = `${config.endpoint.replace(/\/$/, "")}/`;
|
|
16260
|
+
if (config.dockerContainerName) {
|
|
16261
|
+
return dockerCurlFetch2({
|
|
16262
|
+
containerName: config.dockerContainerName,
|
|
16263
|
+
url,
|
|
16264
|
+
headers: requestHeaders,
|
|
16265
|
+
body: requestBody,
|
|
16266
|
+
signal: transportSignal
|
|
16267
|
+
});
|
|
16268
|
+
}
|
|
16269
|
+
return fetch(url, {
|
|
16270
|
+
method: "POST",
|
|
16271
|
+
headers: requestHeaders,
|
|
16272
|
+
body: requestBody,
|
|
16273
|
+
signal: transportSignal
|
|
16274
|
+
});
|
|
16275
|
+
}, deadline);
|
|
16276
|
+
const text = await readResponseTextWithTimeout2(res, signal, deadline);
|
|
16277
|
+
if (!res.ok)
|
|
16278
|
+
throw sanitizeDynamoDbError(res.status, text);
|
|
16279
|
+
return text ? JSON.parse(text) : {};
|
|
15031
16280
|
}
|
|
15032
|
-
|
|
16281
|
+
async function listTablesAsync(opts) {
|
|
16282
|
+
const body = {
|
|
16283
|
+
...opts?.limit ? { Limit: Math.min(100, Math.max(1, opts.limit)) } : {},
|
|
16284
|
+
...opts?.exclusiveStartTableName ? { ExclusiveStartTableName: opts.exclusiveStartTableName } : {}
|
|
16285
|
+
};
|
|
16286
|
+
const raw = await signedJsonRequest("ListTables", body, opts?.signal);
|
|
16287
|
+
return {
|
|
16288
|
+
tableNames: asStringArray(raw.TableNames),
|
|
16289
|
+
...typeof raw.LastEvaluatedTableName === "string" ? { lastEvaluatedTableName: raw.LastEvaluatedTableName } : {}
|
|
16290
|
+
};
|
|
16291
|
+
}
|
|
16292
|
+
async function describeTableAsync(tableName, signal) {
|
|
16293
|
+
assertTableName(tableName);
|
|
16294
|
+
const raw = await signedJsonRequest("DescribeTable", { TableName: tableName }, signal);
|
|
16295
|
+
return asObject(raw.Table);
|
|
16296
|
+
}
|
|
16297
|
+
async function scanAsync(opts) {
|
|
16298
|
+
assertTableName(opts.tableName);
|
|
16299
|
+
const raw = await signedJsonRequest("Scan", {
|
|
16300
|
+
TableName: opts.tableName,
|
|
16301
|
+
...opts.limit ? { Limit: Math.min(1000, Math.max(1, opts.limit)) } : {},
|
|
16302
|
+
...opts.exclusiveStartKey ? { ExclusiveStartKey: opts.exclusiveStartKey } : {},
|
|
16303
|
+
...opts.indexName ? { IndexName: opts.indexName } : {},
|
|
16304
|
+
...opts.projectionExpression ? { ProjectionExpression: opts.projectionExpression } : {},
|
|
16305
|
+
...opts.filterExpression ? { FilterExpression: opts.filterExpression } : {},
|
|
16306
|
+
...opts.expressionAttributeNames ? { ExpressionAttributeNames: opts.expressionAttributeNames } : {},
|
|
16307
|
+
...opts.expressionAttributeValues ? { ExpressionAttributeValues: opts.expressionAttributeValues } : {}
|
|
16308
|
+
}, opts.signal);
|
|
16309
|
+
return {
|
|
16310
|
+
items: Array.isArray(raw.Items) ? raw.Items : [],
|
|
16311
|
+
count: asNumber(raw.Count),
|
|
16312
|
+
scannedCount: asNumber(raw.ScannedCount),
|
|
16313
|
+
...raw.LastEvaluatedKey ? { lastEvaluatedKey: asObject(raw.LastEvaluatedKey) } : {}
|
|
16314
|
+
};
|
|
16315
|
+
}
|
|
16316
|
+
async function queryAsync(opts) {
|
|
16317
|
+
assertTableName(opts.tableName);
|
|
16318
|
+
const raw = await signedJsonRequest("Query", {
|
|
16319
|
+
TableName: opts.tableName,
|
|
16320
|
+
KeyConditionExpression: opts.keyConditionExpression,
|
|
16321
|
+
...opts.limit ? { Limit: Math.min(1000, Math.max(1, opts.limit)) } : {},
|
|
16322
|
+
...opts.exclusiveStartKey ? { ExclusiveStartKey: opts.exclusiveStartKey } : {},
|
|
16323
|
+
...opts.indexName ? { IndexName: opts.indexName } : {},
|
|
16324
|
+
...opts.projectionExpression ? { ProjectionExpression: opts.projectionExpression } : {},
|
|
16325
|
+
...opts.filterExpression ? { FilterExpression: opts.filterExpression } : {},
|
|
16326
|
+
...opts.expressionAttributeNames ? { ExpressionAttributeNames: opts.expressionAttributeNames } : {},
|
|
16327
|
+
...opts.expressionAttributeValues ? { ExpressionAttributeValues: opts.expressionAttributeValues } : {},
|
|
16328
|
+
...opts.scanIndexForward !== undefined ? { ScanIndexForward: opts.scanIndexForward } : {}
|
|
16329
|
+
}, opts.signal);
|
|
16330
|
+
return {
|
|
16331
|
+
items: Array.isArray(raw.Items) ? raw.Items : [],
|
|
16332
|
+
count: asNumber(raw.Count),
|
|
16333
|
+
scannedCount: asNumber(raw.ScannedCount),
|
|
16334
|
+
...raw.LastEvaluatedKey ? { lastEvaluatedKey: asObject(raw.LastEvaluatedKey) } : {}
|
|
16335
|
+
};
|
|
16336
|
+
}
|
|
16337
|
+
async function getItemAsync(opts) {
|
|
16338
|
+
assertTableName(opts.tableName);
|
|
16339
|
+
const raw = await signedJsonRequest("GetItem", {
|
|
16340
|
+
TableName: opts.tableName,
|
|
16341
|
+
Key: opts.key,
|
|
16342
|
+
...opts.projectionExpression ? { ProjectionExpression: opts.projectionExpression } : {},
|
|
16343
|
+
...opts.expressionAttributeNames ? { ExpressionAttributeNames: opts.expressionAttributeNames } : {},
|
|
16344
|
+
...opts.consistentRead !== undefined ? { ConsistentRead: opts.consistentRead } : {}
|
|
16345
|
+
}, opts.signal);
|
|
16346
|
+
return {
|
|
16347
|
+
...raw.Item ? { item: asObject(raw.Item) } : {},
|
|
16348
|
+
...raw.ConsumedCapacity ? { consumedCapacity: raw.ConsumedCapacity } : {}
|
|
16349
|
+
};
|
|
16350
|
+
}
|
|
16351
|
+
return {
|
|
16352
|
+
kind: "dynamodb",
|
|
16353
|
+
model: "document",
|
|
16354
|
+
close() {},
|
|
16355
|
+
listTablesAsync,
|
|
16356
|
+
describeTableAsync,
|
|
16357
|
+
scanAsync,
|
|
16358
|
+
queryAsync,
|
|
16359
|
+
getItemAsync
|
|
16360
|
+
};
|
|
15033
16361
|
}
|
|
15034
|
-
function
|
|
15035
|
-
|
|
16362
|
+
async function dynamoDbConfigFromDockerInfoAsync(info, signal) {
|
|
16363
|
+
const env = info.env;
|
|
16364
|
+
const dockerContainerName = info.hostPort ? undefined : await resolveRunningComposeContainerNameOrThrowAsync(info.serviceName, info.composeDir, signal);
|
|
16365
|
+
return {
|
|
16366
|
+
endpoint: info.hostPort ? `http://localhost:${info.hostPort}` : `http://127.0.0.1:${info.containerPort}`,
|
|
16367
|
+
...dockerContainerName ? { dockerContainerName } : {},
|
|
16368
|
+
region: env.AWS_REGION || env.AWS_DEFAULT_REGION || "us-east-1",
|
|
16369
|
+
accessKeyId: env.AWS_ACCESS_KEY_ID || "test",
|
|
16370
|
+
secretAccessKey: env.AWS_SECRET_ACCESS_KEY || "test",
|
|
16371
|
+
...env.AWS_SESSION_TOKEN ? { sessionToken: env.AWS_SESSION_TOKEN } : {}
|
|
16372
|
+
};
|
|
15036
16373
|
}
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
16374
|
+
async function openDynamoDbExplorerAsync(info, signal) {
|
|
16375
|
+
return createDynamoDbAdapter(await dynamoDbConfigFromDockerInfoAsync(info, signal));
|
|
16376
|
+
}
|
|
16377
|
+
function isDynamoDbHttpError(err) {
|
|
16378
|
+
return err instanceof DynamoDbHttpError;
|
|
16379
|
+
}
|
|
16380
|
+
var spawnSyncImpl4, spawnSyncImplIsTestOverride2 = false, DynamoDbHttpError, EMPTY_SHA2562 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", DEFAULT_DYNAMODB_REQUEST_TIMEOUT_MS = 5000, DEFAULT_DYNAMODB_DOCKER_CURL_TIMEOUT_MS = 30000, DYNAMODB_JSON_CONTENT_TYPE = "application/x-amz-json-1.0", DOCKER_CURL_BODY_MARKER = "__CODE_VIEWER_DYNAMODB_BODY__", dynamoDbRequestTimeoutMs, dynamoDbDockerCurlTimeoutMs;
|
|
16381
|
+
var init_dynamodb = __esm(() => {
|
|
16382
|
+
init_docker_utils();
|
|
16383
|
+
init_spawn_runner();
|
|
16384
|
+
spawnSyncImpl4 = spawnSync5;
|
|
16385
|
+
DynamoDbHttpError = class DynamoDbHttpError extends Error {
|
|
16386
|
+
status;
|
|
16387
|
+
constructor(status, message) {
|
|
16388
|
+
super(message);
|
|
16389
|
+
this.status = status;
|
|
16390
|
+
}
|
|
16391
|
+
};
|
|
16392
|
+
dynamoDbRequestTimeoutMs = DEFAULT_DYNAMODB_REQUEST_TIMEOUT_MS;
|
|
16393
|
+
dynamoDbDockerCurlTimeoutMs = DEFAULT_DYNAMODB_DOCKER_CURL_TIMEOUT_MS;
|
|
15040
16394
|
});
|
|
15041
16395
|
|
|
15042
16396
|
// web-src/server/database/handle-shared.ts
|
|
@@ -15339,6 +16693,247 @@ var init_handle_shared = __esm(() => {
|
|
|
15339
16693
|
logQueue = Promise.resolve();
|
|
15340
16694
|
});
|
|
15341
16695
|
|
|
16696
|
+
// web-src/server/database/handle-dynamodb.ts
|
|
16697
|
+
var exports_handle_dynamodb = {};
|
|
16698
|
+
__export(exports_handle_dynamodb, {
|
|
16699
|
+
handleDynamoDbRoute: () => handleDynamoDbRoute,
|
|
16700
|
+
closeDynamoDbAdapter: () => closeDynamoDbAdapter
|
|
16701
|
+
});
|
|
16702
|
+
function closeDynamoDbAdapter(dbId) {
|
|
16703
|
+
dynamoDbAdapterCache.close(dbId);
|
|
16704
|
+
}
|
|
16705
|
+
function resolveDynamoDb(cwd, dbParam, signal, omitDirNames) {
|
|
16706
|
+
return resolveDockerExplorerAsync(cwd, dbParam, "dynamodb", dynamoDbAdapterCache, (info) => openDynamoDbExplorerAsync(info), omitDirNames, signal);
|
|
16707
|
+
}
|
|
16708
|
+
function dynamoDbErrorResponse(err, action, signal) {
|
|
16709
|
+
if (isAbortLikeError(err, signal)) {
|
|
16710
|
+
return handleError("dynamodb", action, err, signal);
|
|
16711
|
+
}
|
|
16712
|
+
if (isDynamoDbHttpError(err))
|
|
16713
|
+
return textError(err.message, err.status);
|
|
16714
|
+
return handleError("dynamodb", action, err, signal);
|
|
16715
|
+
}
|
|
16716
|
+
function validateTableName(value) {
|
|
16717
|
+
if (!value)
|
|
16718
|
+
return textError("missing table parameter", 400);
|
|
16719
|
+
if (value.length < 3 || value.length > MAX_TABLE_NAME_LEN || hasControlCharacter(value) || !/^[A-Za-z0-9_.-]+$/.test(value)) {
|
|
16720
|
+
return textError("invalid table parameter", 400);
|
|
16721
|
+
}
|
|
16722
|
+
return value;
|
|
16723
|
+
}
|
|
16724
|
+
function validateOptionalText(value, name, maxLen = MAX_EXPRESSION_LEN) {
|
|
16725
|
+
if (value === null || value === "")
|
|
16726
|
+
return;
|
|
16727
|
+
if (value.length > maxLen || hasControlCharacter(value)) {
|
|
16728
|
+
return textError(`invalid ${name} parameter`, 400);
|
|
16729
|
+
}
|
|
16730
|
+
return value;
|
|
16731
|
+
}
|
|
16732
|
+
function parseLimit2(url, param, defaultValue) {
|
|
16733
|
+
const raw = Number(url.searchParams.get(param) || defaultValue);
|
|
16734
|
+
return Math.min(param === "limit" ? MAX_ITEMS_LIMIT : MAX_TABLES_LIMIT, Math.max(1, Number.isFinite(raw) ? raw : defaultValue));
|
|
16735
|
+
}
|
|
16736
|
+
function parseJsonParam(value, name) {
|
|
16737
|
+
if (value === null || value === "")
|
|
16738
|
+
return;
|
|
16739
|
+
if (value.length > MAX_JSON_PARAM_LEN || hasControlCharacter(value)) {
|
|
16740
|
+
return textError(`invalid ${name} parameter`, 400);
|
|
16741
|
+
}
|
|
16742
|
+
try {
|
|
16743
|
+
return JSON.parse(value);
|
|
16744
|
+
} catch {
|
|
16745
|
+
return textError(`invalid ${name} parameter`, 400);
|
|
16746
|
+
}
|
|
16747
|
+
}
|
|
16748
|
+
function parseKey(value, name) {
|
|
16749
|
+
const parsed = parseJsonParam(value, name);
|
|
16750
|
+
if (parsed instanceof Response)
|
|
16751
|
+
return parsed;
|
|
16752
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
16753
|
+
return textError(`missing ${name} parameter`, 400);
|
|
16754
|
+
}
|
|
16755
|
+
return parsed;
|
|
16756
|
+
}
|
|
16757
|
+
function parseExpressionAttributeNames(url) {
|
|
16758
|
+
const parsed = parseJsonParam(url.searchParams.get("expressionAttributeNames"), "expressionAttributeNames");
|
|
16759
|
+
if (parsed instanceof Response || parsed === undefined)
|
|
16760
|
+
return parsed;
|
|
16761
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.values(parsed).some((value) => typeof value !== "string")) {
|
|
16762
|
+
return textError("invalid expressionAttributeNames parameter", 400);
|
|
16763
|
+
}
|
|
16764
|
+
return parsed;
|
|
16765
|
+
}
|
|
16766
|
+
function parseExpressionAttributeValues(url) {
|
|
16767
|
+
const parsed = parseJsonParam(url.searchParams.get("expressionAttributeValues"), "expressionAttributeValues");
|
|
16768
|
+
if (parsed instanceof Response || parsed === undefined)
|
|
16769
|
+
return parsed;
|
|
16770
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
16771
|
+
return textError("invalid expressionAttributeValues parameter", 400);
|
|
16772
|
+
}
|
|
16773
|
+
return parsed;
|
|
16774
|
+
}
|
|
16775
|
+
async function handleTables(req, cwd, url, omitDirNames) {
|
|
16776
|
+
const r = await resolveDynamoDb(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
16777
|
+
if (r instanceof Response)
|
|
16778
|
+
return r;
|
|
16779
|
+
const exclusiveStartTableName = validateOptionalText(url.searchParams.get("exclusiveStartTableName"), "exclusiveStartTableName", MAX_TABLE_NAME_LEN);
|
|
16780
|
+
if (exclusiveStartTableName instanceof Response)
|
|
16781
|
+
return exclusiveStartTableName;
|
|
16782
|
+
try {
|
|
16783
|
+
const result = await r.explorer.listTablesAsync({
|
|
16784
|
+
limit: parseLimit2(url, "tablesLimit", MAX_TABLES_LIMIT),
|
|
16785
|
+
...exclusiveStartTableName ? { exclusiveStartTableName } : {},
|
|
16786
|
+
signal: req.signal
|
|
16787
|
+
});
|
|
16788
|
+
const body = { dbId: r.dbId, ...result };
|
|
16789
|
+
return json(body);
|
|
16790
|
+
} catch (err) {
|
|
16791
|
+
return dynamoDbErrorResponse(err, "list dynamodb tables", req.signal);
|
|
16792
|
+
}
|
|
16793
|
+
}
|
|
16794
|
+
async function handleTable(req, cwd, url, omitDirNames) {
|
|
16795
|
+
const r = await resolveDynamoDb(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
16796
|
+
if (r instanceof Response)
|
|
16797
|
+
return r;
|
|
16798
|
+
const tableName = validateTableName(url.searchParams.get("table"));
|
|
16799
|
+
if (tableName instanceof Response)
|
|
16800
|
+
return tableName;
|
|
16801
|
+
try {
|
|
16802
|
+
const table = await r.explorer.describeTableAsync(tableName, req.signal);
|
|
16803
|
+
const body = { dbId: r.dbId, table };
|
|
16804
|
+
return json(body);
|
|
16805
|
+
} catch (err) {
|
|
16806
|
+
return dynamoDbErrorResponse(err, "describe dynamodb table", req.signal);
|
|
16807
|
+
}
|
|
16808
|
+
}
|
|
16809
|
+
async function handleItems(req, cwd, url, omitDirNames) {
|
|
16810
|
+
const r = await resolveDynamoDb(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
16811
|
+
if (r instanceof Response)
|
|
16812
|
+
return r;
|
|
16813
|
+
const tableName = validateTableName(url.searchParams.get("table"));
|
|
16814
|
+
if (tableName instanceof Response)
|
|
16815
|
+
return tableName;
|
|
16816
|
+
const mode = url.searchParams.get("mode") || "scan";
|
|
16817
|
+
if (mode !== "scan" && mode !== "query") {
|
|
16818
|
+
return textError("invalid mode parameter", 400);
|
|
16819
|
+
}
|
|
16820
|
+
const exclusiveStartKey = parseJsonParam(url.searchParams.get("exclusiveStartKey"), "exclusiveStartKey");
|
|
16821
|
+
if (exclusiveStartKey instanceof Response)
|
|
16822
|
+
return exclusiveStartKey;
|
|
16823
|
+
const expressionAttributeNames = parseExpressionAttributeNames(url);
|
|
16824
|
+
if (expressionAttributeNames instanceof Response)
|
|
16825
|
+
return expressionAttributeNames;
|
|
16826
|
+
const expressionAttributeValues = parseExpressionAttributeValues(url);
|
|
16827
|
+
if (expressionAttributeValues instanceof Response)
|
|
16828
|
+
return expressionAttributeValues;
|
|
16829
|
+
const indexName = validateOptionalText(url.searchParams.get("index"), "index");
|
|
16830
|
+
if (indexName instanceof Response)
|
|
16831
|
+
return indexName;
|
|
16832
|
+
const projectionExpression = validateOptionalText(url.searchParams.get("projectionExpression"), "projectionExpression");
|
|
16833
|
+
if (projectionExpression instanceof Response)
|
|
16834
|
+
return projectionExpression;
|
|
16835
|
+
const filterExpression = validateOptionalText(url.searchParams.get("filterExpression"), "filterExpression");
|
|
16836
|
+
if (filterExpression instanceof Response)
|
|
16837
|
+
return filterExpression;
|
|
16838
|
+
const keyConditionExpression = validateOptionalText(url.searchParams.get("keyConditionExpression"), "keyConditionExpression");
|
|
16839
|
+
if (keyConditionExpression instanceof Response)
|
|
16840
|
+
return keyConditionExpression;
|
|
16841
|
+
if (mode === "query" && !keyConditionExpression) {
|
|
16842
|
+
return textError("missing keyConditionExpression parameter", 400);
|
|
16843
|
+
}
|
|
16844
|
+
try {
|
|
16845
|
+
const common = {
|
|
16846
|
+
tableName,
|
|
16847
|
+
limit: parseLimit2(url, "limit", DEFAULT_ITEMS_LIMIT),
|
|
16848
|
+
...exclusiveStartKey ? { exclusiveStartKey } : {},
|
|
16849
|
+
...indexName ? { indexName } : {},
|
|
16850
|
+
...projectionExpression ? { projectionExpression } : {},
|
|
16851
|
+
...filterExpression ? { filterExpression } : {},
|
|
16852
|
+
...expressionAttributeNames ? { expressionAttributeNames } : {},
|
|
16853
|
+
...expressionAttributeValues ? { expressionAttributeValues } : {},
|
|
16854
|
+
signal: req.signal
|
|
16855
|
+
};
|
|
16856
|
+
const result = mode === "query" ? await r.explorer.queryAsync({
|
|
16857
|
+
...common,
|
|
16858
|
+
keyConditionExpression,
|
|
16859
|
+
scanIndexForward: url.searchParams.get("scanIndexForward") !== "false"
|
|
16860
|
+
}) : await r.explorer.scanAsync(common);
|
|
16861
|
+
const body = {
|
|
16862
|
+
dbId: r.dbId,
|
|
16863
|
+
tableName,
|
|
16864
|
+
mode,
|
|
16865
|
+
...result
|
|
16866
|
+
};
|
|
16867
|
+
return json(body);
|
|
16868
|
+
} catch (err) {
|
|
16869
|
+
return dynamoDbErrorResponse(err, "read dynamodb items", req.signal);
|
|
16870
|
+
}
|
|
16871
|
+
}
|
|
16872
|
+
async function handleItem(req, cwd, url, omitDirNames) {
|
|
16873
|
+
const r = await resolveDynamoDb(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
16874
|
+
if (r instanceof Response)
|
|
16875
|
+
return r;
|
|
16876
|
+
const tableName = validateTableName(url.searchParams.get("table"));
|
|
16877
|
+
if (tableName instanceof Response)
|
|
16878
|
+
return tableName;
|
|
16879
|
+
const key = parseKey(url.searchParams.get("key"), "key");
|
|
16880
|
+
if (key instanceof Response)
|
|
16881
|
+
return key;
|
|
16882
|
+
const expressionAttributeNames = parseExpressionAttributeNames(url);
|
|
16883
|
+
if (expressionAttributeNames instanceof Response)
|
|
16884
|
+
return expressionAttributeNames;
|
|
16885
|
+
const projectionExpression = validateOptionalText(url.searchParams.get("projectionExpression"), "projectionExpression");
|
|
16886
|
+
if (projectionExpression instanceof Response)
|
|
16887
|
+
return projectionExpression;
|
|
16888
|
+
try {
|
|
16889
|
+
const result = await r.explorer.getItemAsync({
|
|
16890
|
+
tableName,
|
|
16891
|
+
key,
|
|
16892
|
+
...projectionExpression ? { projectionExpression } : {},
|
|
16893
|
+
...expressionAttributeNames ? { expressionAttributeNames } : {},
|
|
16894
|
+
consistentRead: url.searchParams.get("consistentRead") === "true",
|
|
16895
|
+
signal: req.signal
|
|
16896
|
+
});
|
|
16897
|
+
const body = {
|
|
16898
|
+
dbId: r.dbId,
|
|
16899
|
+
tableName,
|
|
16900
|
+
key,
|
|
16901
|
+
...result
|
|
16902
|
+
};
|
|
16903
|
+
return json(body);
|
|
16904
|
+
} catch (err) {
|
|
16905
|
+
return dynamoDbErrorResponse(err, "read dynamodb item", req.signal);
|
|
16906
|
+
}
|
|
16907
|
+
}
|
|
16908
|
+
async function handleDynamoDbRoute(req, url, cwd, sideEffectAllowed, omitDirNames) {
|
|
16909
|
+
const wrap = createQueryStrippedLogger("dynamodb", req, url);
|
|
16910
|
+
return dispatchRoutes(req, url, {
|
|
16911
|
+
"/_db/dynamodb/tables": {
|
|
16912
|
+
methods: ["GET"],
|
|
16913
|
+
handler: () => handleTables(req, cwd, url, omitDirNames)
|
|
16914
|
+
},
|
|
16915
|
+
"/_db/dynamodb/table": {
|
|
16916
|
+
methods: ["GET"],
|
|
16917
|
+
handler: () => handleTable(req, cwd, url, omitDirNames)
|
|
16918
|
+
},
|
|
16919
|
+
"/_db/dynamodb/items": {
|
|
16920
|
+
methods: ["GET"],
|
|
16921
|
+
handler: () => handleItems(req, cwd, url, omitDirNames)
|
|
16922
|
+
},
|
|
16923
|
+
"/_db/dynamodb/item": {
|
|
16924
|
+
methods: ["GET"],
|
|
16925
|
+
handler: () => handleItem(req, cwd, url, omitDirNames)
|
|
16926
|
+
}
|
|
16927
|
+
}, sideEffectAllowed, wrap, (err) => handleError("dynamodb", "handle dynamodb request", err, req.signal));
|
|
16928
|
+
}
|
|
16929
|
+
var dynamoDbAdapterCache, DEFAULT_ITEMS_LIMIT = 100, MAX_ITEMS_LIMIT = 1000, MAX_TABLES_LIMIT = 100, MAX_TABLE_NAME_LEN = 255, MAX_EXPRESSION_LEN = 4096, MAX_JSON_PARAM_LEN;
|
|
16930
|
+
var init_handle_dynamodb = __esm(() => {
|
|
16931
|
+
init_dynamodb();
|
|
16932
|
+
init_handle_shared();
|
|
16933
|
+
dynamoDbAdapterCache = createDockerAdapterCache();
|
|
16934
|
+
MAX_JSON_PARAM_LEN = 64 * 1024;
|
|
16935
|
+
});
|
|
16936
|
+
|
|
15342
16937
|
// web-src/server/database/handle-elasticsearch.ts
|
|
15343
16938
|
var exports_handle_elasticsearch = {};
|
|
15344
16939
|
__export(exports_handle_elasticsearch, {
|
|
@@ -15804,7 +17399,7 @@ function validateKey(value) {
|
|
|
15804
17399
|
}
|
|
15805
17400
|
return value;
|
|
15806
17401
|
}
|
|
15807
|
-
function
|
|
17402
|
+
function validateOptionalText2(value, name, maxLen) {
|
|
15808
17403
|
if (!value)
|
|
15809
17404
|
return "";
|
|
15810
17405
|
if (value.length > maxLen || hasControlCharacter(value)) {
|
|
@@ -15812,7 +17407,7 @@ function validateOptionalText(value, name, maxLen) {
|
|
|
15812
17407
|
}
|
|
15813
17408
|
return value;
|
|
15814
17409
|
}
|
|
15815
|
-
function
|
|
17410
|
+
function parseLimit3(url) {
|
|
15816
17411
|
const raw = Number(url.searchParams.get("limit") || DEFAULT_OBJECT_LIMIT);
|
|
15817
17412
|
return Math.min(MAX_OBJECT_LIMIT, Math.max(1, Number.isFinite(raw) ? raw : DEFAULT_OBJECT_LIMIT));
|
|
15818
17413
|
}
|
|
@@ -15897,13 +17492,13 @@ async function handleObjects(cwd, req, url, omitDirNames) {
|
|
|
15897
17492
|
const bucket = validateBucket(url.searchParams.get("bucket"));
|
|
15898
17493
|
if (bucket instanceof Response)
|
|
15899
17494
|
return bucket;
|
|
15900
|
-
const prefix =
|
|
17495
|
+
const prefix = validateOptionalText2(url.searchParams.get("prefix"), "prefix", 2048);
|
|
15901
17496
|
if (prefix instanceof Response)
|
|
15902
17497
|
return prefix;
|
|
15903
|
-
const search =
|
|
17498
|
+
const search = validateOptionalText2(url.searchParams.get("q"), "q", 512);
|
|
15904
17499
|
if (search instanceof Response)
|
|
15905
17500
|
return search;
|
|
15906
|
-
const token =
|
|
17501
|
+
const token = validateOptionalText2(url.searchParams.get("token"), "token", 4096);
|
|
15907
17502
|
if (token instanceof Response)
|
|
15908
17503
|
return token;
|
|
15909
17504
|
const mode = parseMode(url);
|
|
@@ -15912,7 +17507,7 @@ async function handleObjects(cwd, req, url, omitDirNames) {
|
|
|
15912
17507
|
const sort = parseSort2(url);
|
|
15913
17508
|
if (sort instanceof Response)
|
|
15914
17509
|
return sort;
|
|
15915
|
-
const limit =
|
|
17510
|
+
const limit = parseLimit3(url);
|
|
15916
17511
|
try {
|
|
15917
17512
|
if (mode === "prefix") {
|
|
15918
17513
|
const effectivePrefix = search || prefix;
|
|
@@ -16035,10 +17630,10 @@ async function handleFolder(cwd, req, url, omitDirNames) {
|
|
|
16035
17630
|
const bucket = validateBucket(url.searchParams.get("bucket"));
|
|
16036
17631
|
if (bucket instanceof Response)
|
|
16037
17632
|
return bucket;
|
|
16038
|
-
const prefix =
|
|
17633
|
+
const prefix = validateOptionalText2(url.searchParams.get("prefix"), "prefix", 2048);
|
|
16039
17634
|
if (prefix instanceof Response)
|
|
16040
17635
|
return prefix;
|
|
16041
|
-
const token =
|
|
17636
|
+
const token = validateOptionalText2(url.searchParams.get("token"), "token", 4096);
|
|
16042
17637
|
if (token instanceof Response)
|
|
16043
17638
|
return token;
|
|
16044
17639
|
try {
|
|
@@ -16386,7 +17981,7 @@ var init_query_history = __esm(() => {
|
|
|
16386
17981
|
});
|
|
16387
17982
|
|
|
16388
17983
|
// web-src/server/database/snapshot-store.ts
|
|
16389
|
-
import { createHash as
|
|
17984
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
16390
17985
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
16391
17986
|
import { join as join12 } from "node:path";
|
|
16392
17987
|
async function getStoreDb(cwd) {
|
|
@@ -16417,7 +18012,7 @@ function makeId2(prefix) {
|
|
|
16417
18012
|
return `${prefix}-${randomBytes2(8).toString("hex")}`;
|
|
16418
18013
|
}
|
|
16419
18014
|
function hashPayload(payloadJson) {
|
|
16420
|
-
return
|
|
18015
|
+
return createHash6("sha256").update(payloadJson).digest("hex");
|
|
16421
18016
|
}
|
|
16422
18017
|
function hashLengthPrefixed(hasher, value) {
|
|
16423
18018
|
hasher.update(`${Buffer.byteLength(value, "utf8")}:`);
|
|
@@ -16492,7 +18087,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
|
|
|
16492
18087
|
db.exec("BEGIN");
|
|
16493
18088
|
try {
|
|
16494
18089
|
for (const row of rows) {
|
|
16495
|
-
const rowKeyHash =
|
|
18090
|
+
const rowKeyHash = createHash6("sha256").update(row.rowKeyJson).digest("hex");
|
|
16496
18091
|
const payloadHash = hashPayload(row.payloadJson);
|
|
16497
18092
|
insertRow.run(revisionId, rowKeyHash, row.rowKeyJson, row.rowHash, payloadHash);
|
|
16498
18093
|
insertPayload.run(payloadHash, row.payloadJson);
|
|
@@ -16506,7 +18101,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
|
|
|
16506
18101
|
}
|
|
16507
18102
|
}
|
|
16508
18103
|
function computeRevisionTableHash(db, revisionId) {
|
|
16509
|
-
const hasher =
|
|
18104
|
+
const hasher = createHash6("sha256");
|
|
16510
18105
|
hashLengthPrefixed(hasher, `snapshot-table-v${SNAPSHOT_TABLE_HASH_VERSION}`);
|
|
16511
18106
|
let rowCount = 0;
|
|
16512
18107
|
let last;
|
|
@@ -17100,6 +18695,34 @@ function sanitizeS3(v) {
|
|
|
17100
18695
|
return;
|
|
17101
18696
|
return out;
|
|
17102
18697
|
}
|
|
18698
|
+
function sanitizeDynamodb(v) {
|
|
18699
|
+
if (!v || typeof v !== "object")
|
|
18700
|
+
return;
|
|
18701
|
+
const r = v;
|
|
18702
|
+
const out = {};
|
|
18703
|
+
const table = sanitizeOptionalString(r.table, MAX_DYNAMODB_TABLE_LEN);
|
|
18704
|
+
if (table !== undefined)
|
|
18705
|
+
out.table = table;
|
|
18706
|
+
if (r.mode === "scan" || r.mode === "query")
|
|
18707
|
+
out.mode = r.mode;
|
|
18708
|
+
const keyConditionExpression = sanitizeOptionalString(r.keyConditionExpression, MAX_DYNAMODB_EXPRESSION_LEN);
|
|
18709
|
+
if (keyConditionExpression !== undefined)
|
|
18710
|
+
out.keyConditionExpression = keyConditionExpression;
|
|
18711
|
+
const filterExpression = sanitizeOptionalString(r.filterExpression, MAX_DYNAMODB_EXPRESSION_LEN);
|
|
18712
|
+
if (filterExpression !== undefined)
|
|
18713
|
+
out.filterExpression = filterExpression;
|
|
18714
|
+
const expressionAttributeValues = sanitizeOptionalString(r.expressionAttributeValues, MAX_DYNAMODB_ATTRIBUTE_VALUES_LEN);
|
|
18715
|
+
if (expressionAttributeValues !== undefined)
|
|
18716
|
+
out.expressionAttributeValues = expressionAttributeValues;
|
|
18717
|
+
if (typeof r.scanIndexForward === "boolean")
|
|
18718
|
+
out.scanIndexForward = r.scanIndexForward;
|
|
18719
|
+
const itemKey = sanitizeOptionalString(r.itemKey, MAX_DYNAMODB_ITEM_KEY_LEN);
|
|
18720
|
+
if (itemKey !== undefined)
|
|
18721
|
+
out.itemKey = itemKey;
|
|
18722
|
+
if (out.table === undefined && out.mode === undefined && out.keyConditionExpression === undefined && out.filterExpression === undefined && out.expressionAttributeValues === undefined && out.scanIndexForward === undefined && out.itemKey === undefined)
|
|
18723
|
+
return;
|
|
18724
|
+
return out;
|
|
18725
|
+
}
|
|
17103
18726
|
function sanitize(input) {
|
|
17104
18727
|
if (!input || typeof input !== "object")
|
|
17105
18728
|
return emptyState2();
|
|
@@ -17125,7 +18748,7 @@ function sanitize(input) {
|
|
|
17125
18748
|
if (isToolInternalDbId(dbId))
|
|
17126
18749
|
continue;
|
|
17127
18750
|
const schema = sanitizeOptionalString(tab.schema, MAX_SCHEMA_NAME_LEN);
|
|
17128
|
-
const table = sanitizeOptionalString(tab.table,
|
|
18751
|
+
const table = sanitizeOptionalString(tab.table, MAX_TABLE_NAME_LEN2) ?? null;
|
|
17129
18752
|
const view = typeof tab.view === "string" && VALID_VIEWS.has(tab.view) ? tab.view : "data";
|
|
17130
18753
|
const out = { id, dbId, table, view };
|
|
17131
18754
|
if (schema !== undefined)
|
|
@@ -17156,6 +18779,9 @@ function sanitize(input) {
|
|
|
17156
18779
|
const s3 = sanitizeS3(tab.s3);
|
|
17157
18780
|
if (s3 !== undefined)
|
|
17158
18781
|
out.s3 = s3;
|
|
18782
|
+
const dynamodb = sanitizeDynamodb(tab.dynamodb);
|
|
18783
|
+
if (dynamodb !== undefined)
|
|
18784
|
+
out.dynamodb = dynamodb;
|
|
17159
18785
|
tabs.push(out);
|
|
17160
18786
|
}
|
|
17161
18787
|
let activeTabId = sanitizeOptionalString(obj.activeTabId, MAX_TAB_ID_LEN) ?? null;
|
|
@@ -17170,7 +18796,7 @@ async function loadTabsAsync(cwd) {
|
|
|
17170
18796
|
async function saveTabsAsync(cwd, state) {
|
|
17171
18797
|
return tabsStore.save(cwd, state);
|
|
17172
18798
|
}
|
|
17173
|
-
var CODE_VIEWER_DIR5 = ".code-viewer", TABS_FILE_NAME = "tabs.json", MAX_TABS = 64, MAX_JSON_BYTES2 = 1e6, MAX_SQL_DRAFT_LEN = 16000, MAX_ES_QUERY_LEN = 16000, MAX_TAB_ID_LEN = 128, MAX_DB_ID_LEN2 = 2048, MAX_SCHEMA_NAME_LEN = 512,
|
|
18799
|
+
var CODE_VIEWER_DIR5 = ".code-viewer", TABS_FILE_NAME = "tabs.json", MAX_TABS = 64, MAX_JSON_BYTES2 = 1e6, MAX_SQL_DRAFT_LEN = 16000, MAX_ES_QUERY_LEN = 16000, MAX_TAB_ID_LEN = 128, MAX_DB_ID_LEN2 = 2048, MAX_SCHEMA_NAME_LEN = 512, MAX_TABLE_NAME_LEN2 = 512, MAX_REDIS_KEY_LEN = 1024, MAX_REDIS_KEY_FILTER_LEN = 512, MAX_INDEX_NAME_LEN = 256, MAX_S3_BUCKET_LEN = 256, MAX_S3_KEY_LEN = 2048, MAX_S3_QUERY_LEN = 2048, MAX_DYNAMODB_TABLE_LEN = 255, MAX_DYNAMODB_EXPRESSION_LEN = 4096, MAX_DYNAMODB_ATTRIBUTE_VALUES_LEN = 16000, MAX_DYNAMODB_ITEM_KEY_LEN = 4096, MAX_CSS_SIZE_LEN = 16, VALID_VIEWS, tabsStore;
|
|
17174
18800
|
var init_tabs_store = __esm(() => {
|
|
17175
18801
|
init_json_store();
|
|
17176
18802
|
VALID_VIEWS = new Set([
|
|
@@ -17294,6 +18920,9 @@ async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
|
|
|
17294
18920
|
if (info.kind === "s3") {
|
|
17295
18921
|
return textError("s3 services must use the /_db/s3/* routes", 400);
|
|
17296
18922
|
}
|
|
18923
|
+
if (info.kind === "dynamodb") {
|
|
18924
|
+
return textError("dynamodb services must use the /_db/dynamodb/* routes", 400);
|
|
18925
|
+
}
|
|
17297
18926
|
const resolved2 = parsed.database ? { ...info, database: parsed.database } : info;
|
|
17298
18927
|
const requestedSchema = normalizeSchemaParam(schemaParam);
|
|
17299
18928
|
if (requestedSchema instanceof Response)
|
|
@@ -17537,7 +19166,7 @@ function groupFiltersByValue(filters) {
|
|
|
17537
19166
|
}
|
|
17538
19167
|
return grouped;
|
|
17539
19168
|
}
|
|
17540
|
-
async function
|
|
19169
|
+
async function handleTable2(cwd, url, omitDirNames, signal) {
|
|
17541
19170
|
const r = await resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"), signal);
|
|
17542
19171
|
if (r instanceof Response)
|
|
17543
19172
|
return r;
|
|
@@ -18528,6 +20157,10 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
18528
20157
|
const { handleS3Route: handleS3Route2 } = await Promise.resolve().then(() => (init_handle_s3(), exports_handle_s3));
|
|
18529
20158
|
return handleS3Route2(req, url, cwd, sideEffectAllowed, omitDirNames);
|
|
18530
20159
|
}
|
|
20160
|
+
if (url.pathname.startsWith("/_db/dynamodb/")) {
|
|
20161
|
+
const { handleDynamoDbRoute: handleDynamoDbRoute2 } = await Promise.resolve().then(() => (init_handle_dynamodb(), exports_handle_dynamodb));
|
|
20162
|
+
return handleDynamoDbRoute2(req, url, cwd, sideEffectAllowed, omitDirNames);
|
|
20163
|
+
}
|
|
18531
20164
|
const start = Date.now();
|
|
18532
20165
|
const method = req.method;
|
|
18533
20166
|
const wrapResponse = (res) => {
|
|
@@ -18551,7 +20184,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
18551
20184
|
},
|
|
18552
20185
|
"/_db/table": {
|
|
18553
20186
|
methods: ["GET"],
|
|
18554
|
-
handler: () =>
|
|
20187
|
+
handler: () => handleTable2(cwd, url, omitDirNames, req.signal)
|
|
18555
20188
|
},
|
|
18556
20189
|
"/_db/table-count": {
|
|
18557
20190
|
methods: ["GET"],
|
|
@@ -18666,6 +20299,7 @@ var init_handle = __esm(() => {
|
|
|
18666
20299
|
init_connection_pool();
|
|
18667
20300
|
init_discovery();
|
|
18668
20301
|
init_global_search();
|
|
20302
|
+
init_handle_dynamodb();
|
|
18669
20303
|
init_handle_elasticsearch();
|
|
18670
20304
|
init_handle_redis();
|
|
18671
20305
|
init_handle_s3();
|
|
@@ -18695,7 +20329,8 @@ var init_handle = __esm(() => {
|
|
|
18695
20329
|
},
|
|
18696
20330
|
redis: closeRedisAdapter,
|
|
18697
20331
|
elasticsearch: closeElasticsearchAdapter,
|
|
18698
|
-
s3: closeS3Adapter
|
|
20332
|
+
s3: closeS3Adapter,
|
|
20333
|
+
dynamodb: closeDynamoDbAdapter
|
|
18699
20334
|
};
|
|
18700
20335
|
SNAPSHOT_DOCKER_SOURCE_REGISTRY = {
|
|
18701
20336
|
redis: async (info, requestedContainers, signal) => {
|
|
@@ -20636,10 +22271,18 @@ var init_journal2 = __esm(() => {
|
|
|
20636
22271
|
// web-src/server/search-service.ts
|
|
20637
22272
|
import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync5 } from "node:fs";
|
|
20638
22273
|
import { join as join17, relative as relative6 } from "node:path";
|
|
20639
|
-
function
|
|
22274
|
+
async function rgAvailableAsync(cwd) {
|
|
20640
22275
|
if (rgAvailableCache !== null)
|
|
20641
22276
|
return rgAvailableCache;
|
|
20642
|
-
const proc =
|
|
22277
|
+
const proc = await spawnTextAsync({
|
|
22278
|
+
command: commandForExternal("rg"),
|
|
22279
|
+
args: ["--version"],
|
|
22280
|
+
cwd,
|
|
22281
|
+
timeoutMs: 5000,
|
|
22282
|
+
abortMessage: "rg version aborted",
|
|
22283
|
+
timeoutMessage: "rg version timed out after 5000ms",
|
|
22284
|
+
rejectOnError: false
|
|
22285
|
+
});
|
|
20643
22286
|
rgAvailableCache = proc.code === 0;
|
|
20644
22287
|
return rgAvailableCache;
|
|
20645
22288
|
}
|
|
@@ -20712,15 +22355,21 @@ function grepWorktreeFallback(env, query, max, paths) {
|
|
|
20712
22355
|
}
|
|
20713
22356
|
return matches;
|
|
20714
22357
|
}
|
|
20715
|
-
function
|
|
22358
|
+
async function grepWorktreeAsync(env, req) {
|
|
20716
22359
|
const paths = filterCallerPaths(env, req.paths);
|
|
20717
|
-
if (
|
|
22360
|
+
if (await rgAvailableAsync(env.cwd)) {
|
|
20718
22361
|
const safePaths = paths.filter((path) => safeWorktreePath(env, path));
|
|
20719
22362
|
const args = buildRgArgs(req.query, req.max, safePaths, req.regex, env.omitDirNames, env.excludeNames);
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
22363
|
+
const proc = await spawnTextAsync({
|
|
22364
|
+
command: commandForExternal("rg"),
|
|
22365
|
+
args: args.slice(1),
|
|
22366
|
+
cwd: env.cwd,
|
|
22367
|
+
timeoutMs: 5000,
|
|
22368
|
+
abortMessage: "grep aborted",
|
|
22369
|
+
timeoutMessage: "grep timed out after 5000ms",
|
|
22370
|
+
rejectOnError: false
|
|
22371
|
+
});
|
|
22372
|
+
const matches2 = parseRgOutput(proc.stdout, req.max, env.omitDirNames, env.excludeNames).filter((match) => isSafePath(match.path) && !isGitInternalPath(match.path) && !isSkippableSearchPath(match.path, env.omitDirNames, env.excludeNames) && !!safeWorktreePath(env, match.path));
|
|
20724
22373
|
return {
|
|
20725
22374
|
ref: "worktree",
|
|
20726
22375
|
engine: "rg",
|
|
@@ -20744,10 +22393,9 @@ function grepWorktree(env, req) {
|
|
|
20744
22393
|
matches
|
|
20745
22394
|
};
|
|
20746
22395
|
}
|
|
20747
|
-
function
|
|
22396
|
+
async function grepTreeRefAsync(env, req) {
|
|
20748
22397
|
const safePaths = filterCallerPaths(env, req.paths);
|
|
20749
22398
|
const args = [
|
|
20750
|
-
commandForExternal("git"),
|
|
20751
22399
|
"-c",
|
|
20752
22400
|
"core.quotepath=false",
|
|
20753
22401
|
"grep",
|
|
@@ -20762,9 +22410,16 @@ function grepTreeRef(env, req) {
|
|
|
20762
22410
|
"--",
|
|
20763
22411
|
...safePaths
|
|
20764
22412
|
];
|
|
20765
|
-
const proc =
|
|
20766
|
-
|
|
20767
|
-
|
|
22413
|
+
const proc = await spawnTextAsync({
|
|
22414
|
+
command: commandForExternal("git"),
|
|
22415
|
+
args,
|
|
22416
|
+
cwd: env.cwd,
|
|
22417
|
+
timeoutMs: 5000,
|
|
22418
|
+
abortMessage: "git grep aborted",
|
|
22419
|
+
timeoutMessage: "git grep timed out after 5000ms",
|
|
22420
|
+
rejectOnError: false
|
|
22421
|
+
});
|
|
22422
|
+
const matches = parseGitGrepOutput(proc.stdout, req.ref, req.max, env.omitDirNames, env.excludeNames).slice(0, req.max);
|
|
20768
22423
|
return {
|
|
20769
22424
|
ref: req.ref,
|
|
20770
22425
|
engine: "git",
|
|
@@ -20772,10 +22427,10 @@ function grepTreeRef(env, req) {
|
|
|
20772
22427
|
matches
|
|
20773
22428
|
};
|
|
20774
22429
|
}
|
|
20775
|
-
function
|
|
22430
|
+
async function grepRepoAsync(env, req) {
|
|
20776
22431
|
const isWorktree = req.ref === "worktree" || req.ref === "";
|
|
20777
22432
|
if (!isWorktree) {
|
|
20778
|
-
const refCheck =
|
|
22433
|
+
const refCheck = await verifyTreeRefResultAsync(req.ref, env.cwd);
|
|
20779
22434
|
if (refCheck.ok !== true) {
|
|
20780
22435
|
return {
|
|
20781
22436
|
ok: false,
|
|
@@ -20796,13 +22451,13 @@ function grepRepo(env, req) {
|
|
|
20796
22451
|
};
|
|
20797
22452
|
}
|
|
20798
22453
|
if (isWorktree) {
|
|
20799
|
-
return { ok: true, value:
|
|
22454
|
+
return { ok: true, value: await grepWorktreeAsync(env, req) };
|
|
20800
22455
|
}
|
|
20801
|
-
return { ok: true, value:
|
|
22456
|
+
return { ok: true, value: await grepTreeRefAsync(env, req) };
|
|
20802
22457
|
}
|
|
20803
|
-
function
|
|
22458
|
+
async function listRepoFilesAsync(env, ref, generation) {
|
|
20804
22459
|
if (ref !== "worktree" && ref !== "") {
|
|
20805
|
-
const refCheck =
|
|
22460
|
+
const refCheck = await verifyTreeRefResultAsync(ref, env.cwd);
|
|
20806
22461
|
if (refCheck.ok !== true) {
|
|
20807
22462
|
return {
|
|
20808
22463
|
ok: false,
|
|
@@ -20812,7 +22467,7 @@ function listRepoFiles(env, ref, generation) {
|
|
|
20812
22467
|
}
|
|
20813
22468
|
}
|
|
20814
22469
|
const effectiveRef = ref || "worktree";
|
|
20815
|
-
const tree =
|
|
22470
|
+
const tree = await listTreeResultAsync(effectiveRef, "", env.cwd, {
|
|
20816
22471
|
recursive: true,
|
|
20817
22472
|
omitDirNames: env.omitDirNames,
|
|
20818
22473
|
excludeNames: env.excludeNames
|
|
@@ -20829,8 +22484,8 @@ function listRepoFiles(env, ref, generation) {
|
|
|
20829
22484
|
var rgAvailableCache = null;
|
|
20830
22485
|
var init_search_service = __esm(() => {
|
|
20831
22486
|
init_command_resolver();
|
|
22487
|
+
init_spawn_runner();
|
|
20832
22488
|
init_git();
|
|
20833
|
-
init_runtime();
|
|
20834
22489
|
init_search();
|
|
20835
22490
|
});
|
|
20836
22491
|
|
|
@@ -21434,7 +23089,7 @@ function validateMcpIntegerLimit(raw, fallback, min, max, flag) {
|
|
|
21434
23089
|
}
|
|
21435
23090
|
return { ok: true, value: raw };
|
|
21436
23091
|
}
|
|
21437
|
-
function runFileShowTool(input, defaultCwd) {
|
|
23092
|
+
async function runFileShowTool(input, defaultCwd) {
|
|
21438
23093
|
const params = isPlainObject(input) ? input : {};
|
|
21439
23094
|
const pathRaw = params.path;
|
|
21440
23095
|
if (typeof pathRaw !== "string") {
|
|
@@ -21487,7 +23142,7 @@ function runFileShowTool(input, defaultCwd) {
|
|
|
21487
23142
|
json: true
|
|
21488
23143
|
};
|
|
21489
23144
|
try {
|
|
21490
|
-
const report =
|
|
23145
|
+
const report = await buildFileShowReportAsync(resolved.root, command);
|
|
21491
23146
|
return {
|
|
21492
23147
|
text: JSON.stringify(report, null, 2),
|
|
21493
23148
|
isError: report.error !== undefined
|
|
@@ -21497,7 +23152,7 @@ function runFileShowTool(input, defaultCwd) {
|
|
|
21497
23152
|
return { text: `file show failed: ${detail}`, isError: true };
|
|
21498
23153
|
}
|
|
21499
23154
|
}
|
|
21500
|
-
function runFileBlameTool(input, defaultCwd) {
|
|
23155
|
+
async function runFileBlameTool(input, defaultCwd) {
|
|
21501
23156
|
const params = isPlainObject(input) ? input : {};
|
|
21502
23157
|
const pathRaw = params.path;
|
|
21503
23158
|
if (typeof pathRaw !== "string") {
|
|
@@ -21534,7 +23189,7 @@ function runFileBlameTool(input, defaultCwd) {
|
|
|
21534
23189
|
json: true
|
|
21535
23190
|
};
|
|
21536
23191
|
try {
|
|
21537
|
-
const report =
|
|
23192
|
+
const report = await buildFileBlameReportAsync(resolved.root, command);
|
|
21538
23193
|
return {
|
|
21539
23194
|
text: JSON.stringify(report, null, 2),
|
|
21540
23195
|
isError: report.result.error !== undefined
|
|
@@ -21544,7 +23199,7 @@ function runFileBlameTool(input, defaultCwd) {
|
|
|
21544
23199
|
return { text: `file blame failed: ${detail}`, isError: true };
|
|
21545
23200
|
}
|
|
21546
23201
|
}
|
|
21547
|
-
function runFileHistoryTool(input, defaultCwd) {
|
|
23202
|
+
async function runFileHistoryTool(input, defaultCwd) {
|
|
21548
23203
|
const params = isPlainObject(input) ? input : {};
|
|
21549
23204
|
const pathRaw = params.path;
|
|
21550
23205
|
if (typeof pathRaw !== "string") {
|
|
@@ -21597,7 +23252,7 @@ function runFileHistoryTool(input, defaultCwd) {
|
|
|
21597
23252
|
json: true
|
|
21598
23253
|
};
|
|
21599
23254
|
try {
|
|
21600
|
-
const report =
|
|
23255
|
+
const report = await buildFileHistoryReportAsync(resolved.root, command);
|
|
21601
23256
|
return {
|
|
21602
23257
|
text: JSON.stringify(report, null, 2),
|
|
21603
23258
|
isError: report.result.error !== undefined
|
|
@@ -21607,7 +23262,7 @@ function runFileHistoryTool(input, defaultCwd) {
|
|
|
21607
23262
|
return { text: `file history failed: ${detail}`, isError: true };
|
|
21608
23263
|
}
|
|
21609
23264
|
}
|
|
21610
|
-
function runFileDiffTool(input, defaultCwd) {
|
|
23265
|
+
async function runFileDiffTool(input, defaultCwd) {
|
|
21611
23266
|
const params = isPlainObject(input) ? input : {};
|
|
21612
23267
|
const pathRaw = params.path;
|
|
21613
23268
|
if (typeof pathRaw !== "string") {
|
|
@@ -21711,7 +23366,7 @@ function runFileDiffTool(input, defaultCwd) {
|
|
|
21711
23366
|
json: true
|
|
21712
23367
|
};
|
|
21713
23368
|
try {
|
|
21714
|
-
const report =
|
|
23369
|
+
const report = await buildFileDiffReportAsync(resolved.root, command);
|
|
21715
23370
|
return {
|
|
21716
23371
|
text: JSON.stringify(report, null, 2),
|
|
21717
23372
|
isError: report.error !== undefined
|
|
@@ -21721,7 +23376,7 @@ function runFileDiffTool(input, defaultCwd) {
|
|
|
21721
23376
|
return { text: `file diff failed: ${detail}`, isError: true };
|
|
21722
23377
|
}
|
|
21723
23378
|
}
|
|
21724
|
-
function runSearchFilesTool(input, defaultCwd) {
|
|
23379
|
+
async function runSearchFilesTool(input, defaultCwd) {
|
|
21725
23380
|
const params = isPlainObject(input) ? input : {};
|
|
21726
23381
|
const termRaw = params.term;
|
|
21727
23382
|
if (typeof termRaw !== "string") {
|
|
@@ -21756,7 +23411,7 @@ function runSearchFilesTool(input, defaultCwd) {
|
|
|
21756
23411
|
omitDirNames: [],
|
|
21757
23412
|
excludeNames: DEFAULT_EXCLUDE_NAMES
|
|
21758
23413
|
};
|
|
21759
|
-
const listResult =
|
|
23414
|
+
const listResult = await listRepoFilesAsync(env, refParsed.ref, 1);
|
|
21760
23415
|
if (listResult.ok !== true) {
|
|
21761
23416
|
return { text: listResult.error, isError: true };
|
|
21762
23417
|
}
|
|
@@ -21783,7 +23438,7 @@ function runSearchFilesTool(input, defaultCwd) {
|
|
|
21783
23438
|
};
|
|
21784
23439
|
return { text: JSON.stringify(payload, null, 2) };
|
|
21785
23440
|
}
|
|
21786
|
-
function runSearchCodeTool(input, defaultCwd) {
|
|
23441
|
+
async function runSearchCodeTool(input, defaultCwd) {
|
|
21787
23442
|
const params = isPlainObject(input) ? input : {};
|
|
21788
23443
|
const termRaw = params.term;
|
|
21789
23444
|
if (typeof termRaw !== "string") {
|
|
@@ -21835,7 +23490,7 @@ function runSearchCodeTool(input, defaultCwd) {
|
|
|
21835
23490
|
omitDirNames: [],
|
|
21836
23491
|
excludeNames: DEFAULT_EXCLUDE_NAMES
|
|
21837
23492
|
};
|
|
21838
|
-
const result =
|
|
23493
|
+
const result = await grepRepoAsync(env, {
|
|
21839
23494
|
query: termRaw,
|
|
21840
23495
|
ref: refParsed.ref,
|
|
21841
23496
|
paths,
|
|
@@ -22391,12 +24046,12 @@ var init_state_route = __esm(() => {
|
|
|
22391
24046
|
// web-src/server/preview.ts
|
|
22392
24047
|
var exports_preview = {};
|
|
22393
24048
|
import {
|
|
22394
|
-
closeSync as
|
|
24049
|
+
closeSync as closeSync3,
|
|
22395
24050
|
constants as constants3,
|
|
22396
24051
|
existsSync as existsSync8,
|
|
22397
24052
|
lstatSync as lstatSync5,
|
|
22398
24053
|
mkdirSync as mkdirSync4,
|
|
22399
|
-
openSync as
|
|
24054
|
+
openSync as openSync3,
|
|
22400
24055
|
readFileSync as readFileSync9,
|
|
22401
24056
|
realpathSync as realpathSync6,
|
|
22402
24057
|
renameSync,
|
|
@@ -22513,6 +24168,7 @@ Examples:
|
|
|
22513
24168
|
process.exit(1);
|
|
22514
24169
|
}
|
|
22515
24170
|
const candidate = repoRoot(cwd);
|
|
24171
|
+
cwdHasGitRepository = !!candidate;
|
|
22516
24172
|
if (cwdWasExplicit) {
|
|
22517
24173
|
if (candidate === cwd)
|
|
22518
24174
|
cwd = candidate;
|
|
@@ -22728,23 +24384,23 @@ function fileToMeta(file, range, extraQs) {
|
|
|
22728
24384
|
untracked: file.untracked || false
|
|
22729
24385
|
};
|
|
22730
24386
|
}
|
|
22731
|
-
function computePayload(extras, range, pathFilter = "") {
|
|
24387
|
+
async function computePayload(extras, range, pathFilter = "", responseGeneration = generation) {
|
|
22732
24388
|
if (isSameWorktreeRange(range)) {
|
|
22733
24389
|
return {
|
|
22734
24390
|
files: [],
|
|
22735
24391
|
totals: { files: 0, additions: 0, deletions: 0 },
|
|
22736
24392
|
range: "worktree .. worktree",
|
|
22737
24393
|
project: basename3(cwd),
|
|
22738
|
-
branch:
|
|
22739
|
-
generation
|
|
24394
|
+
branch: await currentBranchMetadata(),
|
|
24395
|
+
generation: responseGeneration
|
|
22740
24396
|
};
|
|
22741
24397
|
}
|
|
22742
24398
|
const { args, refs } = buildRangeArgs(range);
|
|
22743
24399
|
const fullArgs = [...extras, ...args];
|
|
22744
|
-
const metaResult =
|
|
24400
|
+
const metaResult = await fileMetaResultAsync(fullArgs, cwd, false);
|
|
22745
24401
|
const files = metaResult.files;
|
|
22746
24402
|
if (!metaResult.error && includeUntracked(range, refs)) {
|
|
22747
|
-
files.push(...
|
|
24403
|
+
files.push(...await untrackedMetaAsync(cwd));
|
|
22748
24404
|
}
|
|
22749
24405
|
const filteredFiles = pathFilter ? files.filter((file) => file.path === pathFilter || file.old_path === pathFilter) : files;
|
|
22750
24406
|
filteredFiles.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
@@ -22771,12 +24427,13 @@ function computePayload(extras, range, pathFilter = "") {
|
|
|
22771
24427
|
totals,
|
|
22772
24428
|
range: label || "HEAD",
|
|
22773
24429
|
project: basename3(cwd),
|
|
22774
|
-
branch:
|
|
22775
|
-
generation,
|
|
24430
|
+
branch: await currentBranchMetadata(),
|
|
24431
|
+
generation: responseGeneration,
|
|
22776
24432
|
...metaResult.error ? { error: metaResult.error } : {}
|
|
22777
24433
|
};
|
|
22778
24434
|
}
|
|
22779
|
-
function handleDiffJson(url) {
|
|
24435
|
+
async function handleDiffJson(url) {
|
|
24436
|
+
const responseGeneration = generation;
|
|
22780
24437
|
const extras = [];
|
|
22781
24438
|
if (url.searchParams.get("ignore_ws") === "1")
|
|
22782
24439
|
extras.push("-w");
|
|
@@ -22790,45 +24447,40 @@ function handleDiffJson(url) {
|
|
|
22790
24447
|
if (path && !safePath(path))
|
|
22791
24448
|
return text("invalid path", 400);
|
|
22792
24449
|
const key = `${range.from}|${range.to}|${url.searchParams.get("ignore_ws") || ""}|${url.searchParams.get("ignore_blank") || ""}|${path}`;
|
|
22793
|
-
|
|
22794
|
-
|
|
22795
|
-
|
|
22796
|
-
|
|
22797
|
-
if (!cached2 || cached2.sig !== sig) {
|
|
22798
|
-
generation++;
|
|
22799
|
-
payload2.generation = generation;
|
|
22800
|
-
metaCache.clear();
|
|
22801
|
-
fileCache.clear();
|
|
22802
|
-
}
|
|
22803
|
-
const body2 = JSON.stringify(payload2);
|
|
22804
|
-
setTimedCacheEntry(metaCache, key, { body: body2, sig });
|
|
22805
|
-
return new Response(body2, {
|
|
24450
|
+
const noCache = url.searchParams.get("nocache") === "1";
|
|
24451
|
+
const cached = metaCache.get(key);
|
|
24452
|
+
if (!noCache && cacheFresh(cached))
|
|
24453
|
+
return new Response(cached.body, {
|
|
22806
24454
|
headers: {
|
|
22807
24455
|
"Content-Type": "application/json; charset=utf-8",
|
|
22808
24456
|
"Cache-Control": "no-store"
|
|
22809
24457
|
}
|
|
22810
24458
|
});
|
|
22811
|
-
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
24459
|
+
const requestSequence = ++diffMetaRequestSequence;
|
|
24460
|
+
latestDiffMetaRequest.set(key, requestSequence);
|
|
24461
|
+
try {
|
|
24462
|
+
const payload = await computePayload(extras, range, path, responseGeneration);
|
|
24463
|
+
if (latestDiffMetaRequest.get(key) !== requestSequence || responseGeneration !== generation)
|
|
24464
|
+
return json2(payload);
|
|
24465
|
+
const sig = JSON.stringify({ ...payload, generation: undefined });
|
|
24466
|
+
if (noCache && (!cached || cached.sig !== sig)) {
|
|
24467
|
+
generation++;
|
|
24468
|
+
payload.generation = generation;
|
|
24469
|
+
metaCache.clear();
|
|
24470
|
+
fileCache.clear();
|
|
24471
|
+
}
|
|
24472
|
+
const body = JSON.stringify(payload);
|
|
24473
|
+
setTimedCacheEntry(metaCache, key, { body, sig });
|
|
24474
|
+
return new Response(body, {
|
|
22815
24475
|
headers: {
|
|
22816
24476
|
"Content-Type": "application/json; charset=utf-8",
|
|
22817
24477
|
"Cache-Control": "no-store"
|
|
22818
24478
|
}
|
|
22819
24479
|
});
|
|
22820
|
-
|
|
22821
|
-
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
sig: JSON.stringify({ ...payload, generation: undefined })
|
|
22825
|
-
});
|
|
22826
|
-
return new Response(body, {
|
|
22827
|
-
headers: {
|
|
22828
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
22829
|
-
"Cache-Control": "no-store"
|
|
22830
|
-
}
|
|
22831
|
-
});
|
|
24480
|
+
} finally {
|
|
24481
|
+
if (latestDiffMetaRequest.get(key) === requestSequence)
|
|
24482
|
+
latestDiffMetaRequest.delete(key);
|
|
24483
|
+
}
|
|
22832
24484
|
}
|
|
22833
24485
|
function safeRepoPath(path) {
|
|
22834
24486
|
return path === "" || safePath(path);
|
|
@@ -22926,16 +24578,16 @@ function worktreeFileMetadata(path, knownSize) {
|
|
|
22926
24578
|
return {};
|
|
22927
24579
|
}
|
|
22928
24580
|
}
|
|
22929
|
-
function gitFileMetadata(ref, path, knownSize) {
|
|
22930
|
-
const size = knownSize ?? rawFileSize(path, ref);
|
|
22931
|
-
const commitUpdatedAt =
|
|
24581
|
+
async function gitFileMetadata(ref, path, knownSize) {
|
|
24582
|
+
const size = knownSize ?? await rawFileSize(path, ref);
|
|
24583
|
+
const commitUpdatedAt = await lastCommitDateForPathAsync(ref, path, cwd) || undefined;
|
|
22932
24584
|
return {
|
|
22933
24585
|
size: size == null ? undefined : size,
|
|
22934
24586
|
updated_at: commitUpdatedAt,
|
|
22935
24587
|
commit_updated_at: commitUpdatedAt
|
|
22936
24588
|
};
|
|
22937
24589
|
}
|
|
22938
|
-
function directoryMetadata(target, path) {
|
|
24590
|
+
async function directoryMetadata(target, path) {
|
|
22939
24591
|
if (target === "worktree" || target === "") {
|
|
22940
24592
|
const full = path === "" ? safeOpenWorktreePath("") : safeWorktreePath2(path);
|
|
22941
24593
|
if (!full)
|
|
@@ -22950,22 +24602,22 @@ function directoryMetadata(target, path) {
|
|
|
22950
24602
|
return {};
|
|
22951
24603
|
}
|
|
22952
24604
|
}
|
|
22953
|
-
const commitUpdatedAt =
|
|
24605
|
+
const commitUpdatedAt = await lastCommitDateForPathAsync(target, path || ".", cwd) || undefined;
|
|
22954
24606
|
return { updated_at: commitUpdatedAt, commit_updated_at: commitUpdatedAt };
|
|
22955
24607
|
}
|
|
22956
|
-
function fileMetadataForTarget(target, path) {
|
|
24608
|
+
async function fileMetadataForTarget(target, path) {
|
|
22957
24609
|
return target === "worktree" || target === "" ? worktreeFileMetadata(path) : gitFileMetadata(target, path);
|
|
22958
24610
|
}
|
|
22959
|
-
function attachTreeEntryMetadata(target, entry) {
|
|
24611
|
+
async function attachTreeEntryMetadata(target, entry) {
|
|
22960
24612
|
if (entry.type === "tree")
|
|
22961
|
-
return { ...entry, ...directoryMetadata(target, entry.path) };
|
|
24613
|
+
return { ...entry, ...await directoryMetadata(target, entry.path) };
|
|
22962
24614
|
if (entry.type === "commit" && !entry.submodule && (target === "worktree" || target === ""))
|
|
22963
|
-
return { ...entry, ...directoryMetadata(target, entry.path) };
|
|
24615
|
+
return { ...entry, ...await directoryMetadata(target, entry.path) };
|
|
22964
24616
|
if (entry.type !== "blob")
|
|
22965
24617
|
return entry;
|
|
22966
|
-
return { ...entry, ...fileMetadataForTarget(target, entry.path) };
|
|
24618
|
+
return { ...entry, ...await fileMetadataForTarget(target, entry.path) };
|
|
22967
24619
|
}
|
|
22968
|
-
function readReadme(target, dirPath) {
|
|
24620
|
+
async function readReadme(target, dirPath) {
|
|
22969
24621
|
const candidates = ["README.md", "readme.md", "README.markdown", "README"];
|
|
22970
24622
|
for (const name of candidates) {
|
|
22971
24623
|
const path = dirPath ? `${dirPath}/${name}` : name;
|
|
@@ -22979,13 +24631,13 @@ function readReadme(target, dirPath) {
|
|
|
22979
24631
|
continue;
|
|
22980
24632
|
}
|
|
22981
24633
|
}
|
|
22982
|
-
const res =
|
|
24634
|
+
const res = await showAsync(target, path, cwd);
|
|
22983
24635
|
if (res.code === 0)
|
|
22984
24636
|
return { path, text: res.stdout };
|
|
22985
24637
|
}
|
|
22986
24638
|
return null;
|
|
22987
24639
|
}
|
|
22988
|
-
function handleTree(url) {
|
|
24640
|
+
async function handleTree(url) {
|
|
22989
24641
|
const target = url.searchParams.get("ref") || url.searchParams.get("target") || "worktree";
|
|
22990
24642
|
const path = (url.searchParams.get("path") || "").replace(/^\/+|\/+$/g, "");
|
|
22991
24643
|
if (!safeRepoPath(path))
|
|
@@ -22993,7 +24645,7 @@ function handleTree(url) {
|
|
|
22993
24645
|
if ((target === "worktree" || target === "") && isGitInternalPath(path))
|
|
22994
24646
|
return text("forbidden", 403);
|
|
22995
24647
|
if (target !== "worktree") {
|
|
22996
|
-
const refCheck =
|
|
24648
|
+
const refCheck = await verifyTreeRefResultAsync(target, cwd);
|
|
22997
24649
|
if (refCheck.ok !== true)
|
|
22998
24650
|
return text(refCheck.error, refCheck.status ?? 400);
|
|
22999
24651
|
}
|
|
@@ -23003,7 +24655,7 @@ function handleTree(url) {
|
|
|
23003
24655
|
if (invalidScopeExcludeNamesQuery(url))
|
|
23004
24656
|
return text("invalid exclude names", 400);
|
|
23005
24657
|
const excludeNames = scopeExcludeNamesFromQuery(url);
|
|
23006
|
-
const tree =
|
|
24658
|
+
const tree = await listTreeResultAsync(target, path, cwd, {
|
|
23007
24659
|
recursive,
|
|
23008
24660
|
omitDirNames: scopeOmitDirNamesFromQuery(url),
|
|
23009
24661
|
excludeNames
|
|
@@ -23015,17 +24667,17 @@ function handleTree(url) {
|
|
|
23015
24667
|
ref: target,
|
|
23016
24668
|
path,
|
|
23017
24669
|
project: basename3(cwd),
|
|
23018
|
-
branch:
|
|
23019
|
-
entries: recursive ? entries : entries.map((entry) => attachTreeEntryMetadata(target, entry)),
|
|
23020
|
-
readme: readReadme(target, path),
|
|
24670
|
+
branch: await currentBranchMetadata(),
|
|
24671
|
+
entries: recursive ? entries : await Promise.all(entries.map((entry) => attachTreeEntryMetadata(target, entry))),
|
|
24672
|
+
readme: await readReadme(target, path),
|
|
23021
24673
|
upload_enabled: uploadEnabled && (target === "worktree" || target === "")
|
|
23022
24674
|
});
|
|
23023
24675
|
}
|
|
23024
|
-
function handleSettings() {
|
|
24676
|
+
async function handleSettings() {
|
|
23025
24677
|
return json2({
|
|
23026
24678
|
project: basename3(cwd),
|
|
23027
|
-
branch:
|
|
23028
|
-
repo_web_url:
|
|
24679
|
+
branch: await currentBranchMetadata(),
|
|
24680
|
+
repo_web_url: cwdHasGitRepository ? await remoteWebUrlAsync(cwd) : null,
|
|
23029
24681
|
scope: {
|
|
23030
24682
|
omit_dirs_effective: scopeOmitDirNames,
|
|
23031
24683
|
omit_dirs_built_in: DEFAULT_WORKTREE_OMIT_DIR_NAMES,
|
|
@@ -23060,7 +24712,13 @@ function currentSearchEnv(omitOverride, excludeOverride) {
|
|
|
23060
24712
|
excludeNames: excludeOverride ?? scopeExcludeNames
|
|
23061
24713
|
};
|
|
23062
24714
|
}
|
|
23063
|
-
function
|
|
24715
|
+
async function currentBranchMetadata() {
|
|
24716
|
+
if (!cwdHasGitRepository)
|
|
24717
|
+
return;
|
|
24718
|
+
return await currentBranchAsync(cwd) || undefined;
|
|
24719
|
+
}
|
|
24720
|
+
async function handleFiles2(url) {
|
|
24721
|
+
const responseGeneration = generation;
|
|
23064
24722
|
const target = url.searchParams.get("ref") || url.searchParams.get("target") || "worktree";
|
|
23065
24723
|
if (invalidScopeOmitDirNamesQuery(url))
|
|
23066
24724
|
return text("invalid omit dirs", 400);
|
|
@@ -23072,13 +24730,24 @@ function handleFiles2(url) {
|
|
|
23072
24730
|
const cached = fileListCache.get(key);
|
|
23073
24731
|
if (cached && cached.generation === generation)
|
|
23074
24732
|
return json2(cached.body);
|
|
23075
|
-
const result =
|
|
24733
|
+
const result = await listRepoFilesAsync(currentSearchEnv(omitDirNames, excludeNames), target, responseGeneration);
|
|
23076
24734
|
if (result.ok !== true)
|
|
23077
24735
|
return text(result.error, result.status ?? 400);
|
|
23078
|
-
|
|
24736
|
+
if (responseGeneration !== generation)
|
|
24737
|
+
return json2(result.value);
|
|
24738
|
+
fileListCache.set(key, {
|
|
24739
|
+
generation: responseGeneration,
|
|
24740
|
+
body: result.value
|
|
24741
|
+
});
|
|
24742
|
+
while (fileListCache.size > MAX_TIMED_CACHE_ENTRIES) {
|
|
24743
|
+
const oldest = fileListCache.keys().next().value;
|
|
24744
|
+
if (oldest === undefined)
|
|
24745
|
+
break;
|
|
24746
|
+
fileListCache.delete(oldest);
|
|
24747
|
+
}
|
|
23079
24748
|
return json2(result.value);
|
|
23080
24749
|
}
|
|
23081
|
-
function handleGrep(url) {
|
|
24750
|
+
async function handleGrep(url) {
|
|
23082
24751
|
const query = url.searchParams.get("q") || "";
|
|
23083
24752
|
const ref = url.searchParams.get("ref") || "worktree";
|
|
23084
24753
|
const max = normalizeGrepMax(url.searchParams.get("max"));
|
|
@@ -23090,7 +24759,7 @@ function handleGrep(url) {
|
|
|
23090
24759
|
const excludeNames = scopeExcludeNamesFromQuery(url);
|
|
23091
24760
|
const paths = url.searchParams.getAll("path");
|
|
23092
24761
|
const regex = url.searchParams.get("regex") === "1";
|
|
23093
|
-
const result =
|
|
24762
|
+
const result = await grepRepoAsync(currentSearchEnv(omitDirNames, excludeNames), {
|
|
23094
24763
|
query,
|
|
23095
24764
|
ref,
|
|
23096
24765
|
paths,
|
|
@@ -23101,25 +24770,26 @@ function handleGrep(url) {
|
|
|
23101
24770
|
return text(result.error, result.status ?? 400);
|
|
23102
24771
|
return json2(result.value);
|
|
23103
24772
|
}
|
|
23104
|
-
function handleRefCommits(url) {
|
|
24773
|
+
async function handleRefCommits(url) {
|
|
23105
24774
|
const query = url.searchParams.get("q") || "";
|
|
23106
24775
|
const parsedMax = Number(url.searchParams.get("max") || "");
|
|
23107
24776
|
const parsedSkip = Number(url.searchParams.get("skip") || "0");
|
|
23108
24777
|
const max = Number.isFinite(parsedMax) && parsedMax > 0 ? parsedMax : undefined;
|
|
23109
24778
|
const skip = Number.isFinite(parsedSkip) && parsedSkip > 0 ? parsedSkip : undefined;
|
|
23110
|
-
const result =
|
|
24779
|
+
const result = await refCommitPageResultAsync(cwd, { query, max, skip });
|
|
23111
24780
|
if (result.error)
|
|
23112
24781
|
return text(result.error, result.status ?? 500);
|
|
23113
24782
|
return json2({ commits: result.commits, hasMore: result.hasMore });
|
|
23114
24783
|
}
|
|
23115
|
-
function handleLog(url) {
|
|
24784
|
+
async function handleLog(url) {
|
|
24785
|
+
const responseGeneration = generation;
|
|
23116
24786
|
const ref = url.searchParams.get("ref") || "HEAD";
|
|
23117
24787
|
const skip = Number(url.searchParams.get("skip") || "0");
|
|
23118
24788
|
const limit = Number(url.searchParams.get("limit") || "50");
|
|
23119
24789
|
const path = url.searchParams.get("path") || "";
|
|
23120
24790
|
if (path && !safePath(path))
|
|
23121
24791
|
return text("invalid path", 400);
|
|
23122
|
-
const result =
|
|
24792
|
+
const result = await commitHistoryAsync(cwd, {
|
|
23123
24793
|
ref,
|
|
23124
24794
|
skip: Number.isFinite(skip) ? skip : 0,
|
|
23125
24795
|
limit: Number.isFinite(limit) ? limit : 50,
|
|
@@ -23132,7 +24802,7 @@ function handleLog(url) {
|
|
|
23132
24802
|
let commits = result.commits;
|
|
23133
24803
|
let hasWorktree = false;
|
|
23134
24804
|
if (wantsWorktreeHead) {
|
|
23135
|
-
const status =
|
|
24805
|
+
const status = await statusPorcelainForPathAsync(path, cwd);
|
|
23136
24806
|
if (status.ok && status.stdout.length > 0) {
|
|
23137
24807
|
const parts = status.stdout.split("\x00").filter(Boolean);
|
|
23138
24808
|
if (parts.length > 0) {
|
|
@@ -23154,7 +24824,7 @@ function handleLog(url) {
|
|
|
23154
24824
|
return json2({
|
|
23155
24825
|
commits,
|
|
23156
24826
|
hasMore: result.hasMore,
|
|
23157
|
-
generation,
|
|
24827
|
+
generation: responseGeneration,
|
|
23158
24828
|
...hasWorktree ? { hasWorktree: true } : {}
|
|
23159
24829
|
});
|
|
23160
24830
|
}
|
|
@@ -23177,7 +24847,8 @@ function rememberBlame(key, value) {
|
|
|
23177
24847
|
blameCache.delete(oldest.value);
|
|
23178
24848
|
}
|
|
23179
24849
|
}
|
|
23180
|
-
function handleFileBlame(url) {
|
|
24850
|
+
async function handleFileBlame(url) {
|
|
24851
|
+
const responseGeneration = generation;
|
|
23181
24852
|
const path = url.searchParams.get("path") || "";
|
|
23182
24853
|
if (!safePath(path))
|
|
23183
24854
|
return text("invalid path", 400);
|
|
@@ -23192,7 +24863,7 @@ function handleFileBlame(url) {
|
|
|
23192
24863
|
if (base === "worktree") {
|
|
23193
24864
|
cacheKey = `worktree|${path}|${blamePathKey(path)}`;
|
|
23194
24865
|
} else {
|
|
23195
|
-
const resolved =
|
|
24866
|
+
const resolved = await verifyCommitAsync(normalized.ref, cwd);
|
|
23196
24867
|
if (resolved.ok === false) {
|
|
23197
24868
|
const status = resolved.error === commandNotFoundDetail("git") ? 503 : 400;
|
|
23198
24869
|
return text(resolved.error || "unknown ref", status);
|
|
@@ -23205,16 +24876,21 @@ function handleFileBlame(url) {
|
|
|
23205
24876
|
blameCache.delete(cacheKey);
|
|
23206
24877
|
blameCache.set(cacheKey, cached);
|
|
23207
24878
|
}
|
|
23208
|
-
return json2({ ...cached, base, ref, generation });
|
|
24879
|
+
return json2({ ...cached, base, ref, generation: responseGeneration });
|
|
23209
24880
|
}
|
|
23210
|
-
const result =
|
|
24881
|
+
const result = await blameAsync(cwd, {
|
|
24882
|
+
path,
|
|
24883
|
+
ref: normalized.ref,
|
|
24884
|
+
base
|
|
24885
|
+
});
|
|
23211
24886
|
if (result.error && result.status)
|
|
23212
24887
|
return text(result.error, result.status);
|
|
23213
|
-
if (!result.error)
|
|
24888
|
+
if (!result.error && responseGeneration === generation)
|
|
23214
24889
|
rememberBlame(cacheKey, result);
|
|
23215
|
-
return json2({ ...result, base, ref, generation });
|
|
24890
|
+
return json2({ ...result, base, ref, generation: responseGeneration });
|
|
23216
24891
|
}
|
|
23217
|
-
function handleFileDiff(url) {
|
|
24892
|
+
async function handleFileDiff(url) {
|
|
24893
|
+
const responseGeneration = generation;
|
|
23218
24894
|
const path = url.searchParams.get("path") || "";
|
|
23219
24895
|
if (!safePath(path))
|
|
23220
24896
|
return text("invalid path", 400);
|
|
@@ -23240,7 +24916,7 @@ function handleFileDiff(url) {
|
|
|
23240
24916
|
line_count: 0,
|
|
23241
24917
|
truncated: false,
|
|
23242
24918
|
binary: false,
|
|
23243
|
-
generation
|
|
24919
|
+
generation: responseGeneration
|
|
23244
24920
|
});
|
|
23245
24921
|
}
|
|
23246
24922
|
const { args } = buildRangeArgs(range);
|
|
@@ -23267,21 +24943,21 @@ function handleFileDiff(url) {
|
|
|
23267
24943
|
diffText = cached.diffText;
|
|
23268
24944
|
} else {
|
|
23269
24945
|
if (isUntracked) {
|
|
23270
|
-
const res =
|
|
24946
|
+
const res = await untrackedFileDiffAsync(extras, path, cwd);
|
|
23271
24947
|
diffText = res.stdout || "";
|
|
23272
|
-
if (res.code !== 0) {
|
|
23273
|
-
errText = res.stderr;
|
|
23274
|
-
errStatus = res.status;
|
|
24948
|
+
if (res.code !== 0 && !(res.code === 1 && diffText)) {
|
|
24949
|
+
errText = res.stderr || "diff failed";
|
|
24950
|
+
errStatus = res.status ?? 500;
|
|
23275
24951
|
}
|
|
23276
24952
|
} else {
|
|
23277
|
-
const res =
|
|
24953
|
+
const res = await fileDiffTextAsync([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
|
|
23278
24954
|
diffText = res.stdout || "";
|
|
23279
24955
|
if (res.code !== 0) {
|
|
23280
|
-
errText = res.stderr;
|
|
23281
|
-
errStatus = res.status;
|
|
24956
|
+
errText = res.stderr || "diff failed";
|
|
24957
|
+
errStatus = res.status ?? 500;
|
|
23282
24958
|
}
|
|
23283
24959
|
}
|
|
23284
|
-
if (!errText)
|
|
24960
|
+
if (!errText && responseGeneration === generation)
|
|
23285
24961
|
setTimedCacheEntry(fileCache, cacheKey, { diffText });
|
|
23286
24962
|
}
|
|
23287
24963
|
if (errStatus)
|
|
@@ -23300,7 +24976,7 @@ function handleFileDiff(url) {
|
|
|
23300
24976
|
truncated: mode === "preview" && (truncated.totalHunks > truncated.renderedHunks || truncated.lineTruncated),
|
|
23301
24977
|
binary: diffText.includes("Binary files"),
|
|
23302
24978
|
error: errText,
|
|
23303
|
-
generation
|
|
24979
|
+
generation: responseGeneration
|
|
23304
24980
|
};
|
|
23305
24981
|
return json2(body);
|
|
23306
24982
|
}
|
|
@@ -23454,6 +25130,7 @@ async function handleFileRange(url) {
|
|
|
23454
25130
|
const full = safeWorktreePath2(path);
|
|
23455
25131
|
if (!full)
|
|
23456
25132
|
return text("no file", 404);
|
|
25133
|
+
const responseGeneration = generation;
|
|
23457
25134
|
const result = await collectIndexedWorktreeLineRange(full, start, end);
|
|
23458
25135
|
const body = {
|
|
23459
25136
|
path,
|
|
@@ -23463,17 +25140,18 @@ async function handleFileRange(url) {
|
|
|
23463
25140
|
lines: result.lines,
|
|
23464
25141
|
total: result.total,
|
|
23465
25142
|
complete: result.complete,
|
|
23466
|
-
generation
|
|
25143
|
+
generation: responseGeneration
|
|
23467
25144
|
};
|
|
23468
25145
|
return json2(body);
|
|
23469
25146
|
} else {
|
|
23470
|
-
const
|
|
25147
|
+
const responseGeneration = generation;
|
|
25148
|
+
const refCheck = await verifyTreeRefResultAsync(ref, cwd);
|
|
23471
25149
|
if (refCheck.ok !== true)
|
|
23472
25150
|
return text(refCheck.error, refCheck.status ?? 400);
|
|
23473
|
-
const oid =
|
|
25151
|
+
const oid = await objectIdAsync(ref, path, cwd);
|
|
23474
25152
|
if (oid.code !== 0 || !oid.oid)
|
|
23475
25153
|
return text("not in ref", 404);
|
|
23476
|
-
const size =
|
|
25154
|
+
const size = await objectByteSizeAsync(oid.oid, cwd);
|
|
23477
25155
|
if (size.code !== 0)
|
|
23478
25156
|
return text("cannot read ref", 500);
|
|
23479
25157
|
const result = await collectIndexedGitBlobLineRange(path, oid.oid, size.size, start, end);
|
|
@@ -23487,41 +25165,71 @@ async function handleFileRange(url) {
|
|
|
23487
25165
|
lines: result.lines,
|
|
23488
25166
|
total: result.total,
|
|
23489
25167
|
complete: result.complete,
|
|
23490
|
-
generation
|
|
25168
|
+
generation: responseGeneration
|
|
23491
25169
|
};
|
|
23492
25170
|
return json2(body);
|
|
23493
25171
|
}
|
|
23494
25172
|
}
|
|
23495
|
-
function handleRawFile(req, url) {
|
|
25173
|
+
async function handleRawFile(req, url) {
|
|
23496
25174
|
const path = url.searchParams.get("path") || "";
|
|
23497
25175
|
if (!safePath(path))
|
|
23498
25176
|
return text("forbidden", 403);
|
|
23499
25177
|
const ref = url.searchParams.get("ref") || "worktree";
|
|
23500
|
-
let body;
|
|
23501
25178
|
if (ref !== "worktree" && ref !== "") {
|
|
23502
|
-
const refCheck =
|
|
25179
|
+
const refCheck = await verifyTreeRefResultAsync(ref, cwd);
|
|
23503
25180
|
if (refCheck.ok !== true)
|
|
23504
25181
|
return text(refCheck.error, refCheck.status ?? 400);
|
|
23505
|
-
const
|
|
23506
|
-
if (
|
|
25182
|
+
const oid = await objectIdAsync(ref, path, cwd);
|
|
25183
|
+
if (oid.code !== 0 || !oid.oid)
|
|
23507
25184
|
return text("not in ref", 404);
|
|
23508
|
-
const
|
|
25185
|
+
const sizeResult = await objectByteSizeAsync(oid.oid, cwd);
|
|
25186
|
+
if (sizeResult.code !== 0)
|
|
25187
|
+
return text("cannot read ref", 500);
|
|
25188
|
+
const size = sizeResult.size;
|
|
25189
|
+
const metadata = await gitFileMetadata(ref, path, size);
|
|
25190
|
+
const rangeResult = req.headers.get("range") ? parseHttpByteRange(req.headers.get("range"), size) : null;
|
|
25191
|
+
if (rangeResult?.kind === "unsatisfiable") {
|
|
25192
|
+
return new Response(null, {
|
|
25193
|
+
status: 416,
|
|
25194
|
+
headers: {
|
|
25195
|
+
...rawFileHeaders(path, { size, metadata }),
|
|
25196
|
+
"Content-Range": `bytes */${size}`,
|
|
25197
|
+
"Content-Length": "0"
|
|
25198
|
+
}
|
|
25199
|
+
});
|
|
25200
|
+
}
|
|
25201
|
+
if (rangeResult?.kind === "range") {
|
|
25202
|
+
const range = rangeResult.range;
|
|
25203
|
+
if (req.method === "HEAD") {
|
|
25204
|
+
return new Response(null, {
|
|
25205
|
+
status: 206,
|
|
25206
|
+
headers: rawFileHeaders(path, { size, range, metadata })
|
|
25207
|
+
});
|
|
25208
|
+
}
|
|
25209
|
+
const shown2 = catFileBlobStream(oid.oid, cwd);
|
|
25210
|
+
const bytes = await collectByteRangeFromStream(shown2.stream, range.start, range.end + 1);
|
|
25211
|
+
const code = await shown2.exited;
|
|
25212
|
+
if (code !== 0)
|
|
25213
|
+
return text("not in ref", 404);
|
|
25214
|
+
const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
25215
|
+
return new Response(body, {
|
|
25216
|
+
status: 206,
|
|
25217
|
+
headers: rawFileHeaders(path, { size, range, metadata })
|
|
25218
|
+
});
|
|
25219
|
+
}
|
|
23509
25220
|
if (req.method === "HEAD")
|
|
23510
25221
|
return new Response(null, {
|
|
23511
25222
|
headers: rawFileHeaders(path, { size, metadata })
|
|
23512
25223
|
});
|
|
23513
|
-
const
|
|
23514
|
-
|
|
23515
|
-
return text("not in ref", 404);
|
|
23516
|
-
body = res.stdout.buffer.slice(res.stdout.byteOffset, res.stdout.byteOffset + res.stdout.byteLength);
|
|
23517
|
-
return new Response(body, {
|
|
25224
|
+
const shown = catFileBlobStream(oid.oid, cwd);
|
|
25225
|
+
return new Response(shown.stream, {
|
|
23518
25226
|
headers: rawFileHeaders(path, { size, metadata })
|
|
23519
25227
|
});
|
|
23520
25228
|
} else {
|
|
23521
25229
|
const full = safeWorktreePath2(path);
|
|
23522
25230
|
if (!full)
|
|
23523
25231
|
return text("not found", 404);
|
|
23524
|
-
const size = rawFileSize(path, ref);
|
|
25232
|
+
const size = await rawFileSize(path, ref);
|
|
23525
25233
|
if (size == null)
|
|
23526
25234
|
return text("not found", 404);
|
|
23527
25235
|
const metadata = worktreeFileMetadata(path, size);
|
|
@@ -23558,11 +25266,12 @@ function handleRawFile(req, url) {
|
|
|
23558
25266
|
});
|
|
23559
25267
|
}
|
|
23560
25268
|
}
|
|
23561
|
-
function rawFileSize(path, ref) {
|
|
25269
|
+
async function rawFileSize(path, ref) {
|
|
23562
25270
|
if (ref !== "worktree" && ref !== "") {
|
|
23563
|
-
|
|
25271
|
+
const refCheck = await verifyTreeRefResultAsync(ref, cwd);
|
|
25272
|
+
if (refCheck.ok !== true)
|
|
23564
25273
|
return null;
|
|
23565
|
-
const res =
|
|
25274
|
+
const res = await objectSizeAsync(ref, path, cwd);
|
|
23566
25275
|
return res.code === 0 ? res.size : null;
|
|
23567
25276
|
}
|
|
23568
25277
|
const full = safeWorktreePath2(path);
|
|
@@ -23664,11 +25373,11 @@ async function handleUploadFiles(req) {
|
|
|
23664
25373
|
const written = [];
|
|
23665
25374
|
try {
|
|
23666
25375
|
for (const upload of uploads) {
|
|
23667
|
-
const fd =
|
|
25376
|
+
const fd = openSync3(upload.target, uploadOpenFlags(), 420);
|
|
23668
25377
|
try {
|
|
23669
25378
|
writeFileSync2(fd, new Uint8Array(await upload.file.arrayBuffer()));
|
|
23670
25379
|
} finally {
|
|
23671
|
-
|
|
25380
|
+
closeSync3(fd);
|
|
23672
25381
|
}
|
|
23673
25382
|
written.push(upload.target);
|
|
23674
25383
|
}
|
|
@@ -23787,13 +25496,13 @@ function moveMacPathIntoTrash(path) {
|
|
|
23787
25496
|
return { ok: false, error: String(error) };
|
|
23788
25497
|
}
|
|
23789
25498
|
}
|
|
23790
|
-
function movePathToTrash(path) {
|
|
25499
|
+
async function movePathToTrash(path) {
|
|
23791
25500
|
lstatSync5(path);
|
|
23792
25501
|
if (process.platform === "darwin") {
|
|
23793
25502
|
return moveMacPathIntoTrash(path);
|
|
23794
25503
|
}
|
|
23795
25504
|
if (process.platform === "win32") {
|
|
23796
|
-
const res =
|
|
25505
|
+
const res = await runAsync([
|
|
23797
25506
|
"powershell.exe",
|
|
23798
25507
|
"-NoProfile",
|
|
23799
25508
|
"-NonInteractive",
|
|
@@ -23806,7 +25515,7 @@ function movePathToTrash(path) {
|
|
|
23806
25515
|
}
|
|
23807
25516
|
return { ok: false, error: "trash unsupported" };
|
|
23808
25517
|
}
|
|
23809
|
-
function restoreTrashPath(originalPath, trashPath) {
|
|
25518
|
+
async function restoreTrashPath(originalPath, trashPath) {
|
|
23810
25519
|
const parent = parentRepoPath(originalPath);
|
|
23811
25520
|
const parentFullPath = safeOpenWorktreePath(parent);
|
|
23812
25521
|
if (!parentFullPath)
|
|
@@ -23832,7 +25541,7 @@ function restoreTrashPath(originalPath, trashPath) {
|
|
|
23832
25541
|
}
|
|
23833
25542
|
}
|
|
23834
25543
|
if (process.platform === "win32") {
|
|
23835
|
-
const res =
|
|
25544
|
+
const res = await runAsync([
|
|
23836
25545
|
"powershell.exe",
|
|
23837
25546
|
"-NoProfile",
|
|
23838
25547
|
"-NonInteractive",
|
|
@@ -23915,7 +25624,7 @@ async function handleTrashPath(req) {
|
|
|
23915
25624
|
const originalFullPath = safeWorktreePath2(path);
|
|
23916
25625
|
if (!originalFullPath)
|
|
23917
25626
|
return text("not found", 404);
|
|
23918
|
-
const moved = movePathToTrash(worktreePath(path));
|
|
25627
|
+
const moved = await movePathToTrash(worktreePath(path));
|
|
23919
25628
|
if (!moved.ok)
|
|
23920
25629
|
return text(moved.error || "trash failed", 500);
|
|
23921
25630
|
const undo = {
|
|
@@ -24009,7 +25718,7 @@ async function handleRestoreTrash(req) {
|
|
|
24009
25718
|
return text("invalid restore target", 400);
|
|
24010
25719
|
if (isGitInternalPath(originalPath))
|
|
24011
25720
|
return text("forbidden", 403);
|
|
24012
|
-
const restored = restoreTrashPath(originalPath, trashPath || undefined);
|
|
25721
|
+
const restored = await restoreTrashPath(originalPath, trashPath || undefined);
|
|
24013
25722
|
if (!restored.ok)
|
|
24014
25723
|
return text(restored.error || "undo failed", 409);
|
|
24015
25724
|
triggerUpdate();
|
|
@@ -24143,7 +25852,7 @@ async function handleJournal(req) {
|
|
|
24143
25852
|
const labels = bodyStringList(body, "labels");
|
|
24144
25853
|
if (label)
|
|
24145
25854
|
labels.push(label);
|
|
24146
|
-
const issues =
|
|
25855
|
+
const issues = await readGithubIssueListAsync({
|
|
24147
25856
|
cwd,
|
|
24148
25857
|
repo: bodyString(body, "repo"),
|
|
24149
25858
|
labels,
|
|
@@ -24547,7 +26256,7 @@ function restartWorktreeWatch() {
|
|
|
24547
26256
|
}
|
|
24548
26257
|
worktreeWatch = startScopedWorktreeWatch();
|
|
24549
26258
|
}
|
|
24550
|
-
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, commandOverrides, cwdWasExplicit = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, JournalRequestError, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
26259
|
+
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, commandOverrides, cwdWasExplicit = false, cwdHasGitRepository = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, enc, sseClients, sseKeepalives, fileCache, blameCache, BLAME_CACHE_MAX = 64, metaCache, diffMetaRequestSequence = 0, latestDiffMetaRequest, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, safePath, MCP_MAX_BODY_BYTES = 1048576, MCP_INSTRUCTIONS, JournalRequestError, isCodeViewerInternalPath, watchLimitReached = null, databaseHandleModule, server, worktreeWatch = null, shuttingDown = false;
|
|
24551
26260
|
var init_preview = __esm(async () => {
|
|
24552
26261
|
init_journal();
|
|
24553
26262
|
init_routes();
|
|
@@ -24625,6 +26334,7 @@ var init_preview = __esm(async () => {
|
|
|
24625
26334
|
fileCache = new Map;
|
|
24626
26335
|
blameCache = new Map;
|
|
24627
26336
|
metaCache = new Map;
|
|
26337
|
+
latestDiffMetaRequest = new Map;
|
|
24628
26338
|
fileListCache = new Map;
|
|
24629
26339
|
lineIndexCache = new Map;
|
|
24630
26340
|
blobLineIndexCache = new Map;
|
|
@@ -24641,6 +26351,7 @@ var init_preview = __esm(async () => {
|
|
|
24641
26351
|
isCodeViewerInternalPath = isToolInternalPath;
|
|
24642
26352
|
parseCli();
|
|
24643
26353
|
applyPersistedSettings(await loadAppSettingsState(cwd));
|
|
26354
|
+
databaseHandleModule = Promise.resolve().then(() => (init_handle(), exports_handle));
|
|
24644
26355
|
server = await startServer({
|
|
24645
26356
|
hostname: "127.0.0.1",
|
|
24646
26357
|
port: listenPort,
|
|
@@ -24652,9 +26363,9 @@ var init_preview = __esm(async () => {
|
|
|
24652
26363
|
if (staticResponse)
|
|
24653
26364
|
return staticResponse;
|
|
24654
26365
|
if (url.pathname === "/diff.json")
|
|
24655
|
-
return handleDiffJson(url);
|
|
26366
|
+
return await handleDiffJson(url);
|
|
24656
26367
|
if (url.pathname === "/_settings")
|
|
24657
|
-
return handleSettings();
|
|
26368
|
+
return await handleSettings();
|
|
24658
26369
|
if (url.pathname === "/_doctor")
|
|
24659
26370
|
return handleDoctor({
|
|
24660
26371
|
cwd,
|
|
@@ -24662,23 +26373,23 @@ var init_preview = __esm(async () => {
|
|
|
24662
26373
|
listenPort
|
|
24663
26374
|
});
|
|
24664
26375
|
if (url.pathname === "/_tree")
|
|
24665
|
-
return handleTree(url);
|
|
26376
|
+
return await handleTree(url);
|
|
24666
26377
|
if (url.pathname === "/_files")
|
|
24667
|
-
return handleFiles2(url);
|
|
26378
|
+
return await handleFiles2(url);
|
|
24668
26379
|
if (url.pathname === "/_grep")
|
|
24669
|
-
return handleGrep(url);
|
|
26380
|
+
return await handleGrep(url);
|
|
24670
26381
|
if (url.pathname === "/_commits")
|
|
24671
|
-
return handleRefCommits(url);
|
|
26382
|
+
return await handleRefCommits(url);
|
|
24672
26383
|
if (url.pathname === "/_log")
|
|
24673
|
-
return handleLog(url);
|
|
26384
|
+
return await handleLog(url);
|
|
24674
26385
|
if (url.pathname === "/_file_blame")
|
|
24675
|
-
return handleFileBlame(url);
|
|
26386
|
+
return await handleFileBlame(url);
|
|
24676
26387
|
if (url.pathname === "/file_diff")
|
|
24677
|
-
return handleFileDiff(url);
|
|
26388
|
+
return await handleFileDiff(url);
|
|
24678
26389
|
if (url.pathname === "/file_range")
|
|
24679
26390
|
return handleFileRange(url);
|
|
24680
26391
|
if (url.pathname === "/_file")
|
|
24681
|
-
return handleRawFile(req, url);
|
|
26392
|
+
return await handleRawFile(req, url);
|
|
24682
26393
|
if (url.pathname === "/_open_path")
|
|
24683
26394
|
return handleOpenPath(req);
|
|
24684
26395
|
if (url.pathname === "/_trash_path")
|
|
@@ -24690,7 +26401,7 @@ var init_preview = __esm(async () => {
|
|
|
24690
26401
|
if (url.pathname === "/_upload_files")
|
|
24691
26402
|
return handleUploadFiles(req);
|
|
24692
26403
|
if (url.pathname.startsWith("/_db/")) {
|
|
24693
|
-
const { handleDatabaseRoute: handleDatabaseRoute2 } = await
|
|
26404
|
+
const { handleDatabaseRoute: handleDatabaseRoute2 } = await databaseHandleModule;
|
|
24694
26405
|
const dbResponse = await handleDatabaseRoute2(req, url, cwd, scopeOmitDirNames, sideEffectRequestAllowed, sendSse);
|
|
24695
26406
|
if (dbResponse)
|
|
24696
26407
|
return dbResponse;
|
|
@@ -24708,7 +26419,7 @@ var init_preview = __esm(async () => {
|
|
|
24708
26419
|
if (url.pathname === "/_annotations")
|
|
24709
26420
|
return handleAnnotations(req);
|
|
24710
26421
|
if (url.pathname === "/_refs") {
|
|
24711
|
-
const result =
|
|
26422
|
+
const result = await refsResultAsync(cwd);
|
|
24712
26423
|
if (result.error)
|
|
24713
26424
|
return text(result.error, result.status ?? 500);
|
|
24714
26425
|
return json2(result.refs);
|