@youtyan/code-viewer 0.6.10 → 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.
@@ -857,6 +857,88 @@ function runSync(args, cwd, options = {}) {
857
857
  stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
858
858
  };
859
859
  }
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
+ });
928
+ });
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;
941
+ }
860
942
  function spawnDetached(args) {
861
943
  const child = spawn(args[0], args.slice(1), {
862
944
  detached: true,
@@ -1058,6 +1140,11 @@ function run(args, cwd) {
1058
1140
  timeout: GIT_COMMAND_TIMEOUT_MS
1059
1141
  });
1060
1142
  }
1143
+ function runGitAsync(args, cwd) {
1144
+ return runAsync(resolveGitArgs(args), cwd, {
1145
+ timeout: GIT_COMMAND_TIMEOUT_MS
1146
+ });
1147
+ }
1061
1148
  function resolveGitArgs(args) {
1062
1149
  if (args[0] !== "git")
1063
1150
  return args;
@@ -1081,6 +1168,10 @@ function runGitRefLookup(args, cwd) {
1081
1168
  const res = run(args, cwd);
1082
1169
  return res.code === 0 ? res.stdout.trimEnd() : null;
1083
1170
  }
1171
+ async function runGitRefLookupAsync(args, cwd) {
1172
+ const res = await runGitAsync(args, cwd);
1173
+ return res.code === 0 ? res.stdout.trimEnd() : null;
1174
+ }
1084
1175
  function repoRoot(cwd) {
1085
1176
  return runGitRefLookup(["git", "rev-parse", "--show-toplevel"], cwd);
1086
1177
  }
@@ -1099,14 +1190,17 @@ function repoRootResult(cwd) {
1099
1190
  function currentBranch(cwd) {
1100
1191
  return runGitRefLookup(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd);
1101
1192
  }
1102
- function verifyCommit(ref, cwd) {
1103
- const res = run(["git", "rev-parse", "--verify", `${ref}^{commit}`], cwd);
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);
1104
1198
  if (res.code === 0)
1105
1199
  return { ok: true, sha: res.stdout.trim() };
1106
1200
  return { ok: false, error: gitFailureMessage(res, "unknown ref") };
1107
1201
  }
1108
- function statusPorcelainForPath(path, cwd) {
1109
- const res = run([
1202
+ async function statusPorcelainForPathAsync(path, cwd) {
1203
+ const res = await runGitAsync([
1110
1204
  "git",
1111
1205
  "-c",
1112
1206
  "core.quotepath=false",
@@ -1127,63 +1221,63 @@ function statusPorcelainForPath(path, cwd) {
1127
1221
  function show(ref, path, cwd) {
1128
1222
  return run(["git", "show", `${ref}:${path}`], cwd);
1129
1223
  }
1224
+ function showAsync(ref, path, cwd) {
1225
+ return runGitAsync(["git", "show", `${ref}:${path}`], cwd);
1226
+ }
1130
1227
  function catFileBlobStream(oid, cwd) {
1131
1228
  return spawnStream(resolveGitArgs(["git", "cat-file", "blob", oid]), cwd);
1132
1229
  }
1133
- function objectSize(ref, path, cwd) {
1134
- const res = run(["git", "cat-file", "-s", `${ref}:${path}`], cwd);
1230
+ async function objectSizeAsync(ref, path, cwd) {
1231
+ const res = await runGitAsync(["git", "cat-file", "-s", `${ref}:${path}`], cwd);
1135
1232
  return {
1136
1233
  code: res.code,
1137
1234
  size: Number(res.stdout.trim()) || 0,
1138
1235
  stderr: res.stderr
1139
1236
  };
1140
1237
  }
1141
- function objectByteSize(oid, cwd) {
1142
- const res = run(["git", "cat-file", "-s", oid], cwd);
1238
+ async function objectByteSizeAsync(oid, cwd) {
1239
+ const res = await runGitAsync(["git", "cat-file", "-s", oid], cwd);
1143
1240
  return {
1144
1241
  code: res.code,
1145
1242
  size: Number(res.stdout.trim()) || 0,
1146
1243
  stderr: res.stderr
1147
1244
  };
1148
1245
  }
1149
- function lastCommitDateForPath(ref, path, cwd) {
1246
+ async function lastCommitDateForPathAsync(ref, path, cwd) {
1150
1247
  const args = ["git", "log", "-1", "--format=%cI", ref, "--", path];
1151
- const res = run(args, cwd);
1248
+ const res = await runGitAsync(args, cwd);
1152
1249
  if (res.code !== 0)
1153
1250
  return null;
1154
1251
  return res.stdout.trim() || null;
1155
1252
  }
1156
- function objectId(ref, path, cwd) {
1157
- const res = run(["git", "rev-parse", "--verify", `${ref}:${path}`], cwd);
1253
+ async function objectIdAsync(ref, path, cwd) {
1254
+ const res = await runGitAsync(["git", "rev-parse", "--verify", `${ref}:${path}`], cwd);
1158
1255
  const oid = res.stdout.trim();
1159
1256
  if (res.code !== 0 || !oid)
1160
1257
  return { code: res.code || 1, oid: "", stderr: res.stderr };
1161
- const type = run(["git", "cat-file", "-t", oid], cwd);
1258
+ const type = await runGitAsync(["git", "cat-file", "-t", oid], cwd);
1162
1259
  if (type.code !== 0 || type.stdout.trim() !== "blob")
1163
1260
  return { code: 1, oid: "", stderr: type.stderr };
1164
1261
  return { code: 0, oid, stderr: "" };
1165
1262
  }
1166
- function verifyTreeRef(ref, cwd) {
1167
- return verifyTreeRefResult(ref, cwd).ok;
1168
- }
1169
- function verifyTreeRefResult(ref, cwd) {
1263
+ async function verifyTreeRefResultAsync(ref, cwd) {
1170
1264
  if (!ref || ref === "worktree")
1171
1265
  return { ok: false, error: "invalid target", status: 400 };
1172
1266
  if (ref.startsWith("-"))
1173
1267
  return { ok: false, error: "invalid target", status: 400 };
1174
- const res = run(["git", "rev-parse", "--verify", `${ref}^{tree}`], cwd);
1268
+ const res = await runGitAsync(["git", "rev-parse", "--verify", `${ref}^{tree}`], cwd);
1175
1269
  if (res.code === 0)
1176
1270
  return { ok: true };
1177
1271
  return { ok: false, ...gitFailureResult(res, "invalid target") };
1178
1272
  }
1179
- function refsResult(cwd) {
1273
+ async function refsResultAsync(cwd) {
1180
1274
  const out = {
1181
1275
  branches: [],
1182
1276
  tags: [],
1183
1277
  commits: [],
1184
1278
  current: ""
1185
1279
  };
1186
- const branches = run([
1280
+ const branches = await runGitAsync([
1187
1281
  "git",
1188
1282
  "for-each-ref",
1189
1283
  "--sort=-committerdate",
@@ -1203,7 +1297,7 @@ function refsResult(cwd) {
1203
1297
  out.branches.push({ name, when });
1204
1298
  }
1205
1299
  }
1206
- const tags = run([
1300
+ const tags = await runGitAsync([
1207
1301
  "git",
1208
1302
  "for-each-ref",
1209
1303
  "--sort=-creatordate",
@@ -1222,7 +1316,7 @@ function refsResult(cwd) {
1222
1316
  out.tags.push({ name, when });
1223
1317
  }
1224
1318
  }
1225
- const commits = refCommitPageResult(cwd, {
1319
+ const commits = await refCommitPageResultAsync(cwd, {
1226
1320
  query: "",
1227
1321
  max: DEFAULT_REF_COMMIT_LIMIT
1228
1322
  });
@@ -1230,7 +1324,7 @@ function refsResult(cwd) {
1230
1324
  return { refs: out, error: commits.error, status: commits.status };
1231
1325
  }
1232
1326
  out.commits = commits.commits;
1233
- out.current = currentBranch(cwd) || "";
1327
+ out.current = await currentBranchAsync(cwd) || "";
1234
1328
  return { refs: out };
1235
1329
  }
1236
1330
  function clampCommitLimit(max) {
@@ -1284,22 +1378,22 @@ function mergeCommitResults(limit, ...groups) {
1284
1378
  }
1285
1379
  return merged;
1286
1380
  }
1287
- function runCommitLogResult(cwd, args) {
1288
- const commits = run(args, cwd);
1381
+ async function runCommitLogResultAsync(cwd, args) {
1382
+ const commits = await runGitAsync(args, cwd);
1289
1383
  if (commits.code === 0)
1290
1384
  return { commits: parseCommitLog(commits.stdout) };
1291
1385
  if (isCommandNotFoundResult("git", commits))
1292
1386
  return { commits: [], ...gitFailureResult(commits, "git log failed") };
1293
1387
  return { commits: [] };
1294
1388
  }
1295
- function refCommitPageResult(cwd, options = {}) {
1389
+ async function refCommitPageResultAsync(cwd, options = {}) {
1296
1390
  const limit = clampCommitLimit(options.max ?? DEFAULT_REF_COMMIT_LIMIT);
1297
1391
  const skip = clampCommitSkip(options.skip ?? 0);
1298
1392
  const fetchLimit = limit + 1;
1299
1393
  const hashMatches = [];
1300
1394
  const trimmed = (options.query || "").trim().slice(0, 200).replace(/\0/g, "");
1301
1395
  if (skip === 0 && /^[0-9a-f]{4,40}$/i.test(trimmed)) {
1302
- const verified = run(["git", "rev-parse", "--verify", `${trimmed}^{commit}`], cwd);
1396
+ const verified = await runGitAsync(["git", "rev-parse", "--verify", `${trimmed}^{commit}`], cwd);
1303
1397
  if (verified.code !== 0 && isCommandNotFoundResult("git", verified)) {
1304
1398
  return {
1305
1399
  commits: [],
@@ -1307,7 +1401,7 @@ function refCommitPageResult(cwd, options = {}) {
1307
1401
  ...gitFailureResult(verified, "unknown ref")
1308
1402
  };
1309
1403
  }
1310
- const single = run([
1404
+ const single = await runGitAsync([
1311
1405
  "git",
1312
1406
  "log",
1313
1407
  "-z",
@@ -1327,7 +1421,7 @@ function refCommitPageResult(cwd, options = {}) {
1327
1421
  }
1328
1422
  }
1329
1423
  if (!trimmed) {
1330
- const result = runCommitLogResult(cwd, commitLogArgs(fetchLimit, skip));
1424
+ const result = await runCommitLogResultAsync(cwd, commitLogArgs(fetchLimit, skip));
1331
1425
  if (result.error) {
1332
1426
  return {
1333
1427
  commits: [],
@@ -1342,11 +1436,19 @@ function refCommitPageResult(cwd, options = {}) {
1342
1436
  hasMore: commits.length > limit
1343
1437
  };
1344
1438
  }
1345
- const subjectMatches = runCommitLogResult(cwd, [
1346
- ...commitLogArgs(fetchLimit, skip),
1347
- "--regexp-ignore-case",
1348
- "--fixed-strings",
1349
- `--grep=${trimmed}`
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
+ ])
1350
1452
  ]);
1351
1453
  if (subjectMatches.error) {
1352
1454
  return {
@@ -1356,12 +1458,6 @@ function refCommitPageResult(cwd, options = {}) {
1356
1458
  status: subjectMatches.status
1357
1459
  };
1358
1460
  }
1359
- const authorMatches = runCommitLogResult(cwd, [
1360
- ...commitLogArgs(fetchLimit, skip),
1361
- "--regexp-ignore-case",
1362
- "--fixed-strings",
1363
- `--author=${trimmed}`
1364
- ]);
1365
1461
  if (authorMatches.error) {
1366
1462
  return {
1367
1463
  commits: [],
@@ -1397,6 +1493,12 @@ function remoteWebUrl(cwd) {
1397
1493
  return null;
1398
1494
  return parseRemoteWebUrl(res.stdout.trim());
1399
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
+ }
1400
1502
  function parseHistoryLog(stdout) {
1401
1503
  const parts = stdout.split("\x00");
1402
1504
  const commits = [];
@@ -1513,6 +1615,60 @@ function commitHistory(cwd, options) {
1513
1615
  const hasMore = parsed.length > limit;
1514
1616
  return { commits: hasMore ? parsed.slice(0, limit) : parsed, hasMore };
1515
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
+ }
1516
1672
  function nameStatusResult(args, cwd) {
1517
1673
  const res = run([
1518
1674
  "git",
@@ -1557,6 +1713,50 @@ function nameStatusResult(args, cwd) {
1557
1713
  }
1558
1714
  return { files };
1559
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
+ }
1560
1760
  function numstatZResult(args, cwd) {
1561
1761
  const res = run([
1562
1762
  "git",
@@ -1600,6 +1800,49 @@ function numstatZResult(args, cwd) {
1600
1800
  }
1601
1801
  return { files };
1602
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
+ }
1603
1846
  function pathHasSegment(path, segment) {
1604
1847
  const target = segment.toLowerCase();
1605
1848
  return path.split(/[\\/]+/).some((part) => part.toLowerCase() === target);
@@ -1742,6 +1985,98 @@ function blame(cwd, options) {
1742
1985
  lines.sort((a, b) => a.lineNo - b.lineNo);
1743
1986
  return { lines, commits };
1744
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
+ }
1745
2080
  function untracked(cwd, path = "") {
1746
2081
  const args = ["git", "ls-files", "--others", "--exclude-standard"];
1747
2082
  if (path)
@@ -1752,6 +2087,16 @@ function untracked(cwd, path = "") {
1752
2087
  return res.stdout.split(`
1753
2088
  `).filter(Boolean).filter((entry) => !isToolInternalPath(entry));
1754
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
+ }
1755
2100
  function normalizeTreePath(path) {
1756
2101
  return path.replace(/^\/+|\/+$/g, "");
1757
2102
  }
@@ -1779,6 +2124,18 @@ function worktreeSubmodulePaths(cwd) {
1779
2124
  return split >= 0 ? normalizeTreePath(line.slice(split + 1)) : "";
1780
2125
  }).filter(Boolean));
1781
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
+ }
1782
2139
  function worktreeEntryFromDirent(base, dir, name, isDirectory, omitDirNames, excludeNames, submodulePaths) {
1783
2140
  if (excludeNames.has(name.toLowerCase()))
1784
2141
  return {
@@ -1876,6 +2233,93 @@ function worktreeFilesystemEntries(cwd, path, recursive, omitDirNames = DEFAULT_
1876
2233
  walk(root, base, 0);
1877
2234
  return combineDirectAndRecursiveFiles(directEntries, fileEntries.sort((a, b) => a.path.localeCompare(b.path)));
1878
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
+ }
1879
2323
  function hasDotGitEntry(dir) {
1880
2324
  try {
1881
2325
  lstatSync(join3(dir, ".git"));
@@ -1884,7 +2328,36 @@ function hasDotGitEntry(dir) {
1884
2328
  return !!err && typeof err === "object" && "code" in err && err.code !== "ENOENT";
1885
2329
  }
1886
2330
  }
1887
- function gitTreeEntries(ref, path, cwd, recursive) {
2331
+ function gitTreeEntries(ref, path, cwd, recursive) {
2332
+ const base = normalizeTreePath(path);
2333
+ const args = ["git", "-c", "core.quotepath=false", "ls-tree"];
2334
+ if (recursive)
2335
+ args.push("-r");
2336
+ args.push("-z", "--full-tree", ref, "--");
2337
+ if (base)
2338
+ args.push(`${base}/`);
2339
+ const res = run(args, cwd);
2340
+ if (res.code !== 0)
2341
+ return { code: res.code, entries: [], stderr: res.stderr };
2342
+ const allowedTypes = recursive ? "blob|commit" : "tree|blob|commit";
2343
+ let entries = res.stdout.split("\x00").filter(Boolean).map((rec) => {
2344
+ const match = rec.match(new RegExp(`^\\d+\\s+(${allowedTypes})\\s+[0-9a-fA-F]+\\t(.+)$`));
2345
+ if (!match)
2346
+ return null;
2347
+ const entryPath = match[2];
2348
+ return {
2349
+ name: entryPath.split("/").pop() || entryPath,
2350
+ path: entryPath,
2351
+ type: match[1]
2352
+ };
2353
+ }).filter((entry) => !!entry);
2354
+ if (recursive)
2355
+ entries.sort((a, b) => a.path.localeCompare(b.path));
2356
+ else
2357
+ entries = sortTreeEntries(entries);
2358
+ return { code: 0, entries, stderr: "" };
2359
+ }
2360
+ async function gitTreeEntriesAsync(ref, path, cwd, recursive) {
1888
2361
  const base = normalizeTreePath(path);
1889
2362
  const args = ["git", "-c", "core.quotepath=false", "ls-tree"];
1890
2363
  if (recursive)
@@ -1892,7 +2365,7 @@ function gitTreeEntries(ref, path, cwd, recursive) {
1892
2365
  args.push("-z", "--full-tree", ref, "--");
1893
2366
  if (base)
1894
2367
  args.push(`${base}/`);
1895
- const res = run(args, cwd);
2368
+ const res = await runGitAsync(args, cwd);
1896
2369
  if (res.code !== 0)
1897
2370
  return { code: res.code, entries: [], stderr: res.stderr };
1898
2371
  const allowedTypes = recursive ? "blob|commit" : "tree|blob|commit";
@@ -1941,8 +2414,29 @@ function listTree(ref, path, cwd, options = {}) {
1941
2414
  stderr: ""
1942
2415
  };
1943
2416
  }
1944
- function listTreeResult(ref, path, cwd, options = {}) {
1945
- const result = listTree(ref, path, cwd, options);
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);
1946
2440
  if (result.code === 0)
1947
2441
  return { entries: result.entries };
1948
2442
  return { entries: [], ...gitFailureResult(result, "git ls-tree failed") };
@@ -1978,6 +2472,38 @@ function untrackedMeta(cwd) {
1978
2472
  ];
1979
2473
  });
1980
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;
2486
+ if (fileExists) {
2487
+ try {
2488
+ scan = scanFileBinaryAndNewlines(full);
2489
+ } catch {
2490
+ return [];
2491
+ }
2492
+ } else {
2493
+ return [];
2494
+ }
2495
+ return [
2496
+ {
2497
+ path,
2498
+ status: "A",
2499
+ additions: scan.binary ? 0 : scan.newlines,
2500
+ deletions: 0,
2501
+ binary: scan.binary,
2502
+ untracked: true
2503
+ }
2504
+ ];
2505
+ });
2506
+ }
1981
2507
  function scanFileBinaryAndNewlines(full) {
1982
2508
  const fd = openSync(full, "r");
1983
2509
  const buffer = Buffer.allocUnsafe(64 * 1024);
@@ -2025,6 +2551,27 @@ function fileMetaResult(args, cwd, includeUntracked = false) {
2025
2551
  files: includeUntracked ? files.concat(untrackedMeta(cwd)) : files
2026
2552
  };
2027
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
+ }
2028
2575
  function fileDiffText(args, path, cwd) {
2029
2576
  const paths = Array.isArray(path) ? path : [path];
2030
2577
  const res = run([
@@ -2044,6 +2591,25 @@ function fileDiffText(args, path, cwd) {
2044
2591
  }
2045
2592
  return res;
2046
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
+ }
2047
2613
  function untrackedFileDiff(extras, path, cwd) {
2048
2614
  const res = run([
2049
2615
  "git",
@@ -2062,6 +2628,24 @@ function untrackedFileDiff(extras, path, cwd) {
2062
2628
  }
2063
2629
  return res;
2064
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
+ }
2065
2649
  function splitHunks(diffText) {
2066
2650
  if (!diffText)
2067
2651
  return { header: "", hunks: [] };
@@ -3399,11 +3983,16 @@ __export(exports_file_cli, {
3399
3983
  sliceLines: () => sliceLines,
3400
3984
  safeWorktreePathFromRoot: () => safeWorktreePathFromRoot,
3401
3985
  runFileCli: () => runFileCli,
3986
+ readShowTextAsync: () => readShowTextAsync,
3402
3987
  readShowText: () => readShowText,
3403
3988
  parseFileArgs: () => parseFileArgs,
3989
+ buildFileShowReportAsync: () => buildFileShowReportAsync,
3404
3990
  buildFileShowReport: () => buildFileShowReport,
3991
+ buildFileHistoryReportAsync: () => buildFileHistoryReportAsync,
3405
3992
  buildFileHistoryReport: () => buildFileHistoryReport,
3993
+ buildFileDiffReportAsync: () => buildFileDiffReportAsync,
3406
3994
  buildFileDiffReport: () => buildFileDiffReport,
3995
+ buildFileBlameReportAsync: () => buildFileBlameReportAsync,
3407
3996
  buildFileBlameReport: () => buildFileBlameReport,
3408
3997
  FILE_HISTORY_HARD_CAP: () => FILE_HISTORY_HARD_CAP,
3409
3998
  FILE_HELP: () => FILE_HELP,
@@ -3723,6 +4312,19 @@ function buildFileBlameReport(root, command) {
3723
4312
  result
3724
4313
  };
3725
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
+ }
3726
4328
  function buildFileHistoryReport(root, command) {
3727
4329
  const result = commitHistory(root, {
3728
4330
  ref: command.ref,
@@ -3740,6 +4342,23 @@ function buildFileHistoryReport(root, command) {
3740
4342
  result
3741
4343
  };
3742
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
+ }
3743
4362
  function runBlame(root, command) {
3744
4363
  const report = buildFileBlameReport(root, command);
3745
4364
  if (command.json) {
@@ -3828,6 +4447,28 @@ function readShowText(root, command) {
3828
4447
  return { code: 1, stdout: "", stderr: "file not readable" };
3829
4448
  }
3830
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
+ }
3831
4472
  function buildFileShowReport(root, command) {
3832
4473
  const res = readShowText(root, command);
3833
4474
  if (res.code !== 0) {
@@ -3855,6 +4496,33 @@ function buildFileShowReport(root, command) {
3855
4496
  `)
3856
4497
  };
3857
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
+ }
3858
4526
  function runShow(root, command) {
3859
4527
  const report = buildFileShowReport(root, command);
3860
4528
  if (report.error !== undefined) {
@@ -3937,6 +4605,64 @@ function buildFileDiffReport(root, command) {
3937
4605
  ...errText ? { error: errText } : {}
3938
4606
  };
3939
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
+ }
3940
4666
  function runDiff(root, command) {
3941
4667
  const report = buildFileDiffReport(root, command);
3942
4668
  if (command.json) {
@@ -4423,8 +5149,8 @@ function buildGithubIssueViewArgs(options) {
4423
5149
  args.push("--repo", repo);
4424
5150
  return args;
4425
5151
  }
4426
- function readGithubIssueList(options) {
4427
- const proc = runSync(buildGithubIssueListArgs(options), options.cwd, {
5152
+ async function readGithubIssueListAsync(options) {
5153
+ const proc = await runAsync(buildGithubIssueListArgs(options), options.cwd, {
4428
5154
  timeout: 30000
4429
5155
  });
4430
5156
  if (proc.code !== 0) {
@@ -4437,8 +5163,8 @@ function readGithubIssueList(options) {
4437
5163
  throw new GithubIssueListError("failed to parse gh issue list output");
4438
5164
  }
4439
5165
  }
4440
- function readGithubIssue(options) {
4441
- const proc = runSync(buildGithubIssueViewArgs(options), options.cwd, {
5166
+ async function readGithubIssueAsync(options) {
5167
+ const proc = await runAsync(buildGithubIssueViewArgs(options), options.cwd, {
4442
5168
  timeout: 30000
4443
5169
  });
4444
5170
  if (proc.code !== 0) {
@@ -5092,7 +5818,7 @@ async function runJournalCli(argv) {
5092
5818
  console.error(commandConfig.error);
5093
5819
  process.exit(1);
5094
5820
  }
5095
- const issues = readGithubIssueList({
5821
+ const issues = await readGithubIssueListAsync({
5096
5822
  cwd: root,
5097
5823
  repo: command.repo,
5098
5824
  labels: command.ghLabels,
@@ -5116,7 +5842,7 @@ async function runJournalCli(argv) {
5116
5842
  console.error(commandConfig.error);
5117
5843
  process.exit(1);
5118
5844
  }
5119
- const issue = readGithubIssue({
5845
+ const issue = await readGithubIssueAsync({
5120
5846
  cwd: root,
5121
5847
  number: command.issueNumber,
5122
5848
  repo: command.repo
@@ -13551,15 +14277,26 @@ function validateDbPath(cwd, dbPath) {
13551
14277
  return null;
13552
14278
  return realFull;
13553
14279
  }
13554
- function serviceListIncludesS3(value) {
14280
+ function serviceListIncludesAwsService(value, service) {
13555
14281
  if (value === undefined || value.trim() === "")
13556
14282
  return true;
13557
- return value.split(/[,\s]+/).map((part) => part.trim().toLowerCase()).filter(Boolean).includes("s3");
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");
13558
14295
  }
13559
14296
  function imageLooksLikeMinio(image) {
13560
14297
  return /(^|\/)minio(?::|\/|$)/.test(image.toLowerCase());
13561
14298
  }
13562
- function detectDbKind(image, env = {}) {
14299
+ function detectDbKind(image, _env = {}) {
13563
14300
  const lower = image.toLowerCase();
13564
14301
  if (lower.includes("postgres"))
13565
14302
  return "postgresql";
@@ -13571,9 +14308,6 @@ function detectDbKind(image, env = {}) {
13571
14308
  return "elasticsearch";
13572
14309
  if (imageLooksLikeMinio(lower))
13573
14310
  return "s3";
13574
- if (lower.includes("localstack/localstack")) {
13575
- return serviceListIncludesS3(env.SERVICES) ? "s3" : null;
13576
- }
13577
14311
  return null;
13578
14312
  }
13579
14313
  function detectDbKindFromEnv(env) {
@@ -13586,10 +14320,33 @@ function detectDbKindFromEnv(env) {
13586
14320
  if (env.MINIO_ROOT_USER || env.MINIO_ROOT_PASSWORD || env.MINIO_ACCESS_KEY || env.MINIO_SECRET_KEY) {
13587
14321
  return "s3";
13588
14322
  }
13589
- if (env.SERVICES && serviceListIncludesS3(env.SERVICES))
13590
- return "s3";
13591
14323
  return null;
13592
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
+ }
13593
14350
  function detectDbKindFromContainerPort(port) {
13594
14351
  switch (port) {
13595
14352
  case "3306":
@@ -13634,8 +14391,10 @@ function defaultPortFor(kind, image, env = {}) {
13634
14391
  return "9200";
13635
14392
  case "s3": {
13636
14393
  const lower = image?.toLowerCase() || "";
13637
- return lower.includes("localstack/localstack") || env.SERVICES !== undefined && serviceListIncludesS3(env.SERVICES) ? "4566" : "9000";
14394
+ return lower.includes("localstack/localstack") || env.SERVICES !== undefined && serviceListIncludesAwsService(env.SERVICES, "s3") ? "4566" : "9000";
13638
14395
  }
14396
+ case "dynamodb":
14397
+ return "4566";
13639
14398
  default:
13640
14399
  return "";
13641
14400
  }
@@ -13722,7 +14481,7 @@ function parseComposePortMappings(serviceBlock, composeDirEnv = {}) {
13722
14481
  const trimmed = line.trim();
13723
14482
  if (!trimmed.startsWith("-"))
13724
14483
  continue;
13725
- const value = resolveEnvValue(trimmed.slice(1).trim().replace(/^["']|["']$/g, "").split(/\s+#/)[0].split("/")[0].trim(), composeDirEnv);
14484
+ const value = resolveEnvValue(trimmed.slice(1).trim(), composeDirEnv).split("/")[0].trim();
13726
14485
  if (!value || value.includes("target:"))
13727
14486
  continue;
13728
14487
  const parts = value.split(":");
@@ -13807,40 +14566,45 @@ function parseComposeContent(content, filepath, composeDir, cwd, composeDirEnv,
13807
14566
  const env = parseComposeEnv(svcBlock, composeDirEnv);
13808
14567
  const containerPort = parseComposeContainerPort(svcBlock);
13809
14568
  const profiled = /^\s+profiles:/m.test(svcBlock);
13810
- const kind = (image ? detectDbKind(image, env) : null) ?? detectDbKindFromEnv(env) ?? detectDbKindFromContainerPort(containerPort) ?? detectDbKindFromServiceName(svc.name);
13811
- if (!kind)
13812
- continue;
13813
- const defaultPort = defaultPortFor(kind, image, env);
13814
- const serviceContainerPort = kind === "s3" ? defaultPort : containerPort || defaultPort;
13815
- const publishedHostPort = parseComposeHostPortForContainer(svcBlock, serviceContainerPort, composeDirEnv) || parseComposeHostPortForContainer(svcBlock, defaultPort, composeDirEnv) || parseComposePorts(svcBlock, composeDirEnv);
13816
- const hostPort = kind === "s3" ? publishedHostPort || undefined : publishedHostPort || defaultPort;
13817
- const imageLabel = image ?? `build:${kind}`;
13818
- const id = isRoot ? `docker:${svc.name}` : `docker:${svc.name}@${encodeURIComponent(relDirSlash)}`;
13819
- const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
13820
- let label;
13821
- if (kind === "redis" || kind === "elasticsearch" || kind === "s3") {
13822
- const endpointLabel = hostPort ? `localhost:${hostPort}` : `container:${serviceContainerPort}`;
13823
- label = `${svc.name} (${imageLabel}, ${endpointLabel}${labelPath})`;
13824
- } else {
13825
- const dbName = env.POSTGRES_DB || env.MYSQL_DATABASE || env.MARIADB_DATABASE || svc.name;
13826
- const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
13827
- label = `${svc.name} (${imageLabel}, ${user}@localhost:${hostPort}/${dbName}${labelPath})`;
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
+ });
13828
14607
  }
13829
- results.push({
13830
- id,
13831
- path: isRoot ? filename : `${relDirSlash}/${filename}`,
13832
- name: label,
13833
- sizeBytes: 0,
13834
- kind,
13835
- serviceName: svc.name,
13836
- ...image ? { image } : {},
13837
- env,
13838
- composeDir,
13839
- relDirSlash,
13840
- ...hostPort ? { hostPort } : {},
13841
- containerPort: serviceContainerPort,
13842
- ...profiled ? { profiled: true } : {}
13843
- });
13844
14608
  }
13845
14609
  }
13846
14610
  async function parseComposeFileAsync(filepath, composeDir, cwd, results) {
@@ -13938,12 +14702,24 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
13938
14702
  });
13939
14703
  return cloneDockerDiscoveryResult(results);
13940
14704
  }
14705
+ function isDbKind(value) {
14706
+ return DB_KIND_VALUES.has(value);
14707
+ }
13941
14708
  function parseDockerDbId(dbId) {
13942
14709
  if (!dbId.startsWith("docker:"))
13943
14710
  return null;
13944
14711
  let rest = dbId.slice(7);
13945
14712
  if (!rest)
13946
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
+ }
13947
14723
  let database;
13948
14724
  const atIdx = rest.indexOf("@");
13949
14725
  if (atIdx >= 0) {
@@ -13967,7 +14743,8 @@ function parseDockerDbId(dbId) {
13967
14743
  return {
13968
14744
  serviceName,
13969
14745
  relDir,
13970
- database
14746
+ database,
14747
+ kind
13971
14748
  };
13972
14749
  } catch {
13973
14750
  return null;
@@ -13982,23 +14759,27 @@ function parseDockerDbId(dbId) {
13982
14759
  return null;
13983
14760
  if (!isSafeDockerDatabaseName(database))
13984
14761
  return null;
13985
- return { serviceName: rest, relDir: "", database };
14762
+ return { serviceName: rest, relDir: "", database, kind };
13986
14763
  }
13987
14764
  function canonicalizeDockerDbId(dbId) {
13988
14765
  const parsed = parseDockerDbId(dbId);
13989
14766
  if (!parsed)
13990
14767
  return null;
13991
14768
  const database = parsed.database ? `:${parsed.database}` : "";
14769
+ const kindSuffix = parsed.kind ? `#${parsed.kind}` : "";
13992
14770
  if (!parsed.relDir)
13993
- return `docker:${parsed.serviceName}${database}`;
13994
- 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}`;
13995
14773
  }
13996
14774
  async function findDockerServiceByDbIdAsync(cwd, dbId, kind, omitDirNames, signal) {
13997
14775
  const parsed = parseDockerDbId(dbId);
13998
14776
  if (!parsed)
13999
14777
  return null;
14778
+ if (kind && parsed.kind && parsed.kind !== kind)
14779
+ return null;
14780
+ const effectiveKind = parsed.kind ?? kind;
14000
14781
  const services = await discoverDockerDatabasesAsync(cwd, omitDirNames, signal);
14001
- return services.find((d) => d.serviceName === parsed.serviceName && d.relDirSlash === parsed.relDir && (!kind || d.kind === kind)) || null;
14782
+ return services.find((d) => d.serviceName === parsed.serviceName && d.relDirSlash === parsed.relDir && (!effectiveKind || d.kind === effectiveKind)) || null;
14002
14783
  }
14003
14784
  function isSafeDockerServiceName(value) {
14004
14785
  return /^[A-Za-z0-9_-]+$/.test(value);
@@ -14133,7 +14914,7 @@ async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal
14133
14914
  const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
14134
14915
  return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
14135
14916
  }
14136
- 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;
14137
14918
  var init_discovery = __esm(() => {
14138
14919
  SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
14139
14920
  sqliteDiscoveryCache = new Map;
@@ -14144,6 +14925,15 @@ var init_discovery = __esm(() => {
14144
14925
  "compose.yaml"
14145
14926
  ];
14146
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
+ ]);
14147
14937
  supabaseDiscoveryCache = new Map;
14148
14938
  });
14149
14939
 
@@ -15131,14 +15921,476 @@ async function searchTableAsync(adapter, table, columns, term, maxHits, includeN
15131
15921
  throw err;
15132
15922
  }
15133
15923
  }
15134
- return hits;
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) : {};
16280
+ }
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
+ };
15135
16361
  }
15136
- function getPrimaryKeyColumnsFromColumns(columns) {
15137
- return columns.filter((c) => c.primaryKey).map((c) => c.name);
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
+ };
15138
16373
  }
15139
- var init_global_search = __esm(() => {
15140
- init_serialize();
15141
- init_sql_utils();
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;
15142
16394
  });
15143
16395
 
15144
16396
  // web-src/server/database/handle-shared.ts
@@ -15441,6 +16693,247 @@ var init_handle_shared = __esm(() => {
15441
16693
  logQueue = Promise.resolve();
15442
16694
  });
15443
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
+
15444
16937
  // web-src/server/database/handle-elasticsearch.ts
15445
16938
  var exports_handle_elasticsearch = {};
15446
16939
  __export(exports_handle_elasticsearch, {
@@ -15906,7 +17399,7 @@ function validateKey(value) {
15906
17399
  }
15907
17400
  return value;
15908
17401
  }
15909
- function validateOptionalText(value, name, maxLen) {
17402
+ function validateOptionalText2(value, name, maxLen) {
15910
17403
  if (!value)
15911
17404
  return "";
15912
17405
  if (value.length > maxLen || hasControlCharacter(value)) {
@@ -15914,7 +17407,7 @@ function validateOptionalText(value, name, maxLen) {
15914
17407
  }
15915
17408
  return value;
15916
17409
  }
15917
- function parseLimit2(url) {
17410
+ function parseLimit3(url) {
15918
17411
  const raw = Number(url.searchParams.get("limit") || DEFAULT_OBJECT_LIMIT);
15919
17412
  return Math.min(MAX_OBJECT_LIMIT, Math.max(1, Number.isFinite(raw) ? raw : DEFAULT_OBJECT_LIMIT));
15920
17413
  }
@@ -15999,13 +17492,13 @@ async function handleObjects(cwd, req, url, omitDirNames) {
15999
17492
  const bucket = validateBucket(url.searchParams.get("bucket"));
16000
17493
  if (bucket instanceof Response)
16001
17494
  return bucket;
16002
- const prefix = validateOptionalText(url.searchParams.get("prefix"), "prefix", 2048);
17495
+ const prefix = validateOptionalText2(url.searchParams.get("prefix"), "prefix", 2048);
16003
17496
  if (prefix instanceof Response)
16004
17497
  return prefix;
16005
- const search = validateOptionalText(url.searchParams.get("q"), "q", 512);
17498
+ const search = validateOptionalText2(url.searchParams.get("q"), "q", 512);
16006
17499
  if (search instanceof Response)
16007
17500
  return search;
16008
- const token = validateOptionalText(url.searchParams.get("token"), "token", 4096);
17501
+ const token = validateOptionalText2(url.searchParams.get("token"), "token", 4096);
16009
17502
  if (token instanceof Response)
16010
17503
  return token;
16011
17504
  const mode = parseMode(url);
@@ -16014,7 +17507,7 @@ async function handleObjects(cwd, req, url, omitDirNames) {
16014
17507
  const sort = parseSort2(url);
16015
17508
  if (sort instanceof Response)
16016
17509
  return sort;
16017
- const limit = parseLimit2(url);
17510
+ const limit = parseLimit3(url);
16018
17511
  try {
16019
17512
  if (mode === "prefix") {
16020
17513
  const effectivePrefix = search || prefix;
@@ -16137,10 +17630,10 @@ async function handleFolder(cwd, req, url, omitDirNames) {
16137
17630
  const bucket = validateBucket(url.searchParams.get("bucket"));
16138
17631
  if (bucket instanceof Response)
16139
17632
  return bucket;
16140
- const prefix = validateOptionalText(url.searchParams.get("prefix"), "prefix", 2048);
17633
+ const prefix = validateOptionalText2(url.searchParams.get("prefix"), "prefix", 2048);
16141
17634
  if (prefix instanceof Response)
16142
17635
  return prefix;
16143
- const token = validateOptionalText(url.searchParams.get("token"), "token", 4096);
17636
+ const token = validateOptionalText2(url.searchParams.get("token"), "token", 4096);
16144
17637
  if (token instanceof Response)
16145
17638
  return token;
16146
17639
  try {
@@ -16488,7 +17981,7 @@ var init_query_history = __esm(() => {
16488
17981
  });
16489
17982
 
16490
17983
  // web-src/server/database/snapshot-store.ts
16491
- import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
17984
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
16492
17985
  import { mkdirSync as mkdirSync3 } from "node:fs";
16493
17986
  import { join as join12 } from "node:path";
16494
17987
  async function getStoreDb(cwd) {
@@ -16519,7 +18012,7 @@ function makeId2(prefix) {
16519
18012
  return `${prefix}-${randomBytes2(8).toString("hex")}`;
16520
18013
  }
16521
18014
  function hashPayload(payloadJson) {
16522
- return createHash5("sha256").update(payloadJson).digest("hex");
18015
+ return createHash6("sha256").update(payloadJson).digest("hex");
16523
18016
  }
16524
18017
  function hashLengthPrefixed(hasher, value) {
16525
18018
  hasher.update(`${Buffer.byteLength(value, "utf8")}:`);
@@ -16594,7 +18087,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
16594
18087
  db.exec("BEGIN");
16595
18088
  try {
16596
18089
  for (const row of rows) {
16597
- const rowKeyHash = createHash5("sha256").update(row.rowKeyJson).digest("hex");
18090
+ const rowKeyHash = createHash6("sha256").update(row.rowKeyJson).digest("hex");
16598
18091
  const payloadHash = hashPayload(row.payloadJson);
16599
18092
  insertRow.run(revisionId, rowKeyHash, row.rowKeyJson, row.rowHash, payloadHash);
16600
18093
  insertPayload.run(payloadHash, row.payloadJson);
@@ -16608,7 +18101,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
16608
18101
  }
16609
18102
  }
16610
18103
  function computeRevisionTableHash(db, revisionId) {
16611
- const hasher = createHash5("sha256");
18104
+ const hasher = createHash6("sha256");
16612
18105
  hashLengthPrefixed(hasher, `snapshot-table-v${SNAPSHOT_TABLE_HASH_VERSION}`);
16613
18106
  let rowCount = 0;
16614
18107
  let last;
@@ -17202,6 +18695,34 @@ function sanitizeS3(v) {
17202
18695
  return;
17203
18696
  return out;
17204
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
+ }
17205
18726
  function sanitize(input) {
17206
18727
  if (!input || typeof input !== "object")
17207
18728
  return emptyState2();
@@ -17227,7 +18748,7 @@ function sanitize(input) {
17227
18748
  if (isToolInternalDbId(dbId))
17228
18749
  continue;
17229
18750
  const schema = sanitizeOptionalString(tab.schema, MAX_SCHEMA_NAME_LEN);
17230
- const table = sanitizeOptionalString(tab.table, MAX_TABLE_NAME_LEN) ?? null;
18751
+ const table = sanitizeOptionalString(tab.table, MAX_TABLE_NAME_LEN2) ?? null;
17231
18752
  const view = typeof tab.view === "string" && VALID_VIEWS.has(tab.view) ? tab.view : "data";
17232
18753
  const out = { id, dbId, table, view };
17233
18754
  if (schema !== undefined)
@@ -17258,6 +18779,9 @@ function sanitize(input) {
17258
18779
  const s3 = sanitizeS3(tab.s3);
17259
18780
  if (s3 !== undefined)
17260
18781
  out.s3 = s3;
18782
+ const dynamodb = sanitizeDynamodb(tab.dynamodb);
18783
+ if (dynamodb !== undefined)
18784
+ out.dynamodb = dynamodb;
17261
18785
  tabs.push(out);
17262
18786
  }
17263
18787
  let activeTabId = sanitizeOptionalString(obj.activeTabId, MAX_TAB_ID_LEN) ?? null;
@@ -17272,7 +18796,7 @@ async function loadTabsAsync(cwd) {
17272
18796
  async function saveTabsAsync(cwd, state) {
17273
18797
  return tabsStore.save(cwd, state);
17274
18798
  }
17275
- 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_LEN = 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_CSS_SIZE_LEN = 16, VALID_VIEWS, tabsStore;
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;
17276
18800
  var init_tabs_store = __esm(() => {
17277
18801
  init_json_store();
17278
18802
  VALID_VIEWS = new Set([
@@ -17396,6 +18920,9 @@ async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
17396
18920
  if (info.kind === "s3") {
17397
18921
  return textError("s3 services must use the /_db/s3/* routes", 400);
17398
18922
  }
18923
+ if (info.kind === "dynamodb") {
18924
+ return textError("dynamodb services must use the /_db/dynamodb/* routes", 400);
18925
+ }
17399
18926
  const resolved2 = parsed.database ? { ...info, database: parsed.database } : info;
17400
18927
  const requestedSchema = normalizeSchemaParam(schemaParam);
17401
18928
  if (requestedSchema instanceof Response)
@@ -17639,7 +19166,7 @@ function groupFiltersByValue(filters) {
17639
19166
  }
17640
19167
  return grouped;
17641
19168
  }
17642
- async function handleTable(cwd, url, omitDirNames, signal) {
19169
+ async function handleTable2(cwd, url, omitDirNames, signal) {
17643
19170
  const r = await resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"), signal);
17644
19171
  if (r instanceof Response)
17645
19172
  return r;
@@ -18630,6 +20157,10 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
18630
20157
  const { handleS3Route: handleS3Route2 } = await Promise.resolve().then(() => (init_handle_s3(), exports_handle_s3));
18631
20158
  return handleS3Route2(req, url, cwd, sideEffectAllowed, omitDirNames);
18632
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
+ }
18633
20164
  const start = Date.now();
18634
20165
  const method = req.method;
18635
20166
  const wrapResponse = (res) => {
@@ -18653,7 +20184,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
18653
20184
  },
18654
20185
  "/_db/table": {
18655
20186
  methods: ["GET"],
18656
- handler: () => handleTable(cwd, url, omitDirNames, req.signal)
20187
+ handler: () => handleTable2(cwd, url, omitDirNames, req.signal)
18657
20188
  },
18658
20189
  "/_db/table-count": {
18659
20190
  methods: ["GET"],
@@ -18768,6 +20299,7 @@ var init_handle = __esm(() => {
18768
20299
  init_connection_pool();
18769
20300
  init_discovery();
18770
20301
  init_global_search();
20302
+ init_handle_dynamodb();
18771
20303
  init_handle_elasticsearch();
18772
20304
  init_handle_redis();
18773
20305
  init_handle_s3();
@@ -18797,7 +20329,8 @@ var init_handle = __esm(() => {
18797
20329
  },
18798
20330
  redis: closeRedisAdapter,
18799
20331
  elasticsearch: closeElasticsearchAdapter,
18800
- s3: closeS3Adapter
20332
+ s3: closeS3Adapter,
20333
+ dynamodb: closeDynamoDbAdapter
18801
20334
  };
18802
20335
  SNAPSHOT_DOCKER_SOURCE_REGISTRY = {
18803
20336
  redis: async (info, requestedContainers, signal) => {
@@ -20738,13 +22271,6 @@ var init_journal2 = __esm(() => {
20738
22271
  // web-src/server/search-service.ts
20739
22272
  import { existsSync as existsSync7, lstatSync as lstatSync4, readFileSync as readFileSync7, realpathSync as realpathSync5 } from "node:fs";
20740
22273
  import { join as join17, relative as relative6 } from "node:path";
20741
- function rgAvailable(cwd) {
20742
- if (rgAvailableCache !== null)
20743
- return rgAvailableCache;
20744
- const proc = runSync([commandForExternal("rg"), "--version"], cwd);
20745
- rgAvailableCache = proc.code === 0;
20746
- return rgAvailableCache;
20747
- }
20748
22274
  async function rgAvailableAsync(cwd) {
20749
22275
  if (rgAvailableCache !== null)
20750
22276
  return rgAvailableCache;
@@ -20829,38 +22355,6 @@ function grepWorktreeFallback(env, query, max, paths) {
20829
22355
  }
20830
22356
  return matches;
20831
22357
  }
20832
- function grepWorktree(env, req) {
20833
- const paths = filterCallerPaths(env, req.paths);
20834
- if (rgAvailable(env.cwd)) {
20835
- const safePaths = paths.filter((path) => safeWorktreePath(env, path));
20836
- const args = buildRgArgs(req.query, req.max, safePaths, req.regex, env.omitDirNames, env.excludeNames);
20837
- args[0] = commandForExternal("rg");
20838
- const proc = runSync(args, env.cwd, { timeout: 5000 });
20839
- const stdout = proc.stdout;
20840
- const matches2 = parseRgOutput(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));
20841
- return {
20842
- ref: "worktree",
20843
- engine: "rg",
20844
- truncated: matches2.length >= req.max,
20845
- matches: matches2
20846
- };
20847
- }
20848
- if (req.regex) {
20849
- return {
20850
- ref: "worktree",
20851
- engine: "fallback",
20852
- truncated: false,
20853
- matches: []
20854
- };
20855
- }
20856
- const matches = grepWorktreeFallback(env, req.query, req.max, paths);
20857
- return {
20858
- ref: "worktree",
20859
- engine: "fallback",
20860
- truncated: matches.length >= req.max,
20861
- matches
20862
- };
20863
- }
20864
22358
  async function grepWorktreeAsync(env, req) {
20865
22359
  const paths = filterCallerPaths(env, req.paths);
20866
22360
  if (await rgAvailableAsync(env.cwd)) {
@@ -20899,34 +22393,6 @@ async function grepWorktreeAsync(env, req) {
20899
22393
  matches
20900
22394
  };
20901
22395
  }
20902
- function grepTreeRef(env, req) {
20903
- const safePaths = filterCallerPaths(env, req.paths);
20904
- const args = [
20905
- commandForExternal("git"),
20906
- "-c",
20907
- "core.quotepath=false",
20908
- "grep",
20909
- "-n",
20910
- "--column",
20911
- "-i",
20912
- req.regex ? "-E" : "-F",
20913
- "--no-color",
20914
- "-e",
20915
- req.query,
20916
- req.ref,
20917
- "--",
20918
- ...safePaths
20919
- ];
20920
- const proc = runSync(args, env.cwd, { timeout: 5000 });
20921
- const stdout = proc.stdout;
20922
- const matches = parseGitGrepOutput(stdout, req.ref, req.max, env.omitDirNames, env.excludeNames).slice(0, req.max);
20923
- return {
20924
- ref: req.ref,
20925
- engine: "git",
20926
- truncated: matches.length >= req.max,
20927
- matches
20928
- };
20929
- }
20930
22396
  async function grepTreeRefAsync(env, req) {
20931
22397
  const safePaths = filterCallerPaths(env, req.paths);
20932
22398
  const args = [
@@ -20961,38 +22427,10 @@ async function grepTreeRefAsync(env, req) {
20961
22427
  matches
20962
22428
  };
20963
22429
  }
20964
- function grepRepo(env, req) {
20965
- const isWorktree = req.ref === "worktree" || req.ref === "";
20966
- if (!isWorktree) {
20967
- const refCheck = verifyTreeRefResult(req.ref, env.cwd);
20968
- if (refCheck.ok !== true) {
20969
- return {
20970
- ok: false,
20971
- error: refCheck.error,
20972
- status: refCheck.status
20973
- };
20974
- }
20975
- }
20976
- if (!req.query.trim()) {
20977
- return {
20978
- ok: true,
20979
- value: {
20980
- ref: req.ref,
20981
- engine: req.ref === "worktree" ? "fallback" : "git",
20982
- truncated: false,
20983
- matches: []
20984
- }
20985
- };
20986
- }
20987
- if (isWorktree) {
20988
- return { ok: true, value: grepWorktree(env, req) };
20989
- }
20990
- return { ok: true, value: grepTreeRef(env, req) };
20991
- }
20992
22430
  async function grepRepoAsync(env, req) {
20993
22431
  const isWorktree = req.ref === "worktree" || req.ref === "";
20994
22432
  if (!isWorktree) {
20995
- const refCheck = verifyTreeRefResult(req.ref, env.cwd);
22433
+ const refCheck = await verifyTreeRefResultAsync(req.ref, env.cwd);
20996
22434
  if (refCheck.ok !== true) {
20997
22435
  return {
20998
22436
  ok: false,
@@ -21017,9 +22455,9 @@ async function grepRepoAsync(env, req) {
21017
22455
  }
21018
22456
  return { ok: true, value: await grepTreeRefAsync(env, req) };
21019
22457
  }
21020
- function listRepoFiles(env, ref, generation) {
22458
+ async function listRepoFilesAsync(env, ref, generation) {
21021
22459
  if (ref !== "worktree" && ref !== "") {
21022
- const refCheck = verifyTreeRefResult(ref, env.cwd);
22460
+ const refCheck = await verifyTreeRefResultAsync(ref, env.cwd);
21023
22461
  if (refCheck.ok !== true) {
21024
22462
  return {
21025
22463
  ok: false,
@@ -21029,7 +22467,7 @@ function listRepoFiles(env, ref, generation) {
21029
22467
  }
21030
22468
  }
21031
22469
  const effectiveRef = ref || "worktree";
21032
- const tree = listTreeResult(effectiveRef, "", env.cwd, {
22470
+ const tree = await listTreeResultAsync(effectiveRef, "", env.cwd, {
21033
22471
  recursive: true,
21034
22472
  omitDirNames: env.omitDirNames,
21035
22473
  excludeNames: env.excludeNames
@@ -21048,7 +22486,6 @@ var init_search_service = __esm(() => {
21048
22486
  init_command_resolver();
21049
22487
  init_spawn_runner();
21050
22488
  init_git();
21051
- init_runtime();
21052
22489
  init_search();
21053
22490
  });
21054
22491
 
@@ -21652,7 +23089,7 @@ function validateMcpIntegerLimit(raw, fallback, min, max, flag) {
21652
23089
  }
21653
23090
  return { ok: true, value: raw };
21654
23091
  }
21655
- function runFileShowTool(input, defaultCwd) {
23092
+ async function runFileShowTool(input, defaultCwd) {
21656
23093
  const params = isPlainObject(input) ? input : {};
21657
23094
  const pathRaw = params.path;
21658
23095
  if (typeof pathRaw !== "string") {
@@ -21705,7 +23142,7 @@ function runFileShowTool(input, defaultCwd) {
21705
23142
  json: true
21706
23143
  };
21707
23144
  try {
21708
- const report = buildFileShowReport(resolved.root, command);
23145
+ const report = await buildFileShowReportAsync(resolved.root, command);
21709
23146
  return {
21710
23147
  text: JSON.stringify(report, null, 2),
21711
23148
  isError: report.error !== undefined
@@ -21715,7 +23152,7 @@ function runFileShowTool(input, defaultCwd) {
21715
23152
  return { text: `file show failed: ${detail}`, isError: true };
21716
23153
  }
21717
23154
  }
21718
- function runFileBlameTool(input, defaultCwd) {
23155
+ async function runFileBlameTool(input, defaultCwd) {
21719
23156
  const params = isPlainObject(input) ? input : {};
21720
23157
  const pathRaw = params.path;
21721
23158
  if (typeof pathRaw !== "string") {
@@ -21752,7 +23189,7 @@ function runFileBlameTool(input, defaultCwd) {
21752
23189
  json: true
21753
23190
  };
21754
23191
  try {
21755
- const report = buildFileBlameReport(resolved.root, command);
23192
+ const report = await buildFileBlameReportAsync(resolved.root, command);
21756
23193
  return {
21757
23194
  text: JSON.stringify(report, null, 2),
21758
23195
  isError: report.result.error !== undefined
@@ -21762,7 +23199,7 @@ function runFileBlameTool(input, defaultCwd) {
21762
23199
  return { text: `file blame failed: ${detail}`, isError: true };
21763
23200
  }
21764
23201
  }
21765
- function runFileHistoryTool(input, defaultCwd) {
23202
+ async function runFileHistoryTool(input, defaultCwd) {
21766
23203
  const params = isPlainObject(input) ? input : {};
21767
23204
  const pathRaw = params.path;
21768
23205
  if (typeof pathRaw !== "string") {
@@ -21815,7 +23252,7 @@ function runFileHistoryTool(input, defaultCwd) {
21815
23252
  json: true
21816
23253
  };
21817
23254
  try {
21818
- const report = buildFileHistoryReport(resolved.root, command);
23255
+ const report = await buildFileHistoryReportAsync(resolved.root, command);
21819
23256
  return {
21820
23257
  text: JSON.stringify(report, null, 2),
21821
23258
  isError: report.result.error !== undefined
@@ -21825,7 +23262,7 @@ function runFileHistoryTool(input, defaultCwd) {
21825
23262
  return { text: `file history failed: ${detail}`, isError: true };
21826
23263
  }
21827
23264
  }
21828
- function runFileDiffTool(input, defaultCwd) {
23265
+ async function runFileDiffTool(input, defaultCwd) {
21829
23266
  const params = isPlainObject(input) ? input : {};
21830
23267
  const pathRaw = params.path;
21831
23268
  if (typeof pathRaw !== "string") {
@@ -21929,7 +23366,7 @@ function runFileDiffTool(input, defaultCwd) {
21929
23366
  json: true
21930
23367
  };
21931
23368
  try {
21932
- const report = buildFileDiffReport(resolved.root, command);
23369
+ const report = await buildFileDiffReportAsync(resolved.root, command);
21933
23370
  return {
21934
23371
  text: JSON.stringify(report, null, 2),
21935
23372
  isError: report.error !== undefined
@@ -21939,7 +23376,7 @@ function runFileDiffTool(input, defaultCwd) {
21939
23376
  return { text: `file diff failed: ${detail}`, isError: true };
21940
23377
  }
21941
23378
  }
21942
- function runSearchFilesTool(input, defaultCwd) {
23379
+ async function runSearchFilesTool(input, defaultCwd) {
21943
23380
  const params = isPlainObject(input) ? input : {};
21944
23381
  const termRaw = params.term;
21945
23382
  if (typeof termRaw !== "string") {
@@ -21974,7 +23411,7 @@ function runSearchFilesTool(input, defaultCwd) {
21974
23411
  omitDirNames: [],
21975
23412
  excludeNames: DEFAULT_EXCLUDE_NAMES
21976
23413
  };
21977
- const listResult = listRepoFiles(env, refParsed.ref, 1);
23414
+ const listResult = await listRepoFilesAsync(env, refParsed.ref, 1);
21978
23415
  if (listResult.ok !== true) {
21979
23416
  return { text: listResult.error, isError: true };
21980
23417
  }
@@ -22001,7 +23438,7 @@ function runSearchFilesTool(input, defaultCwd) {
22001
23438
  };
22002
23439
  return { text: JSON.stringify(payload, null, 2) };
22003
23440
  }
22004
- function runSearchCodeTool(input, defaultCwd) {
23441
+ async function runSearchCodeTool(input, defaultCwd) {
22005
23442
  const params = isPlainObject(input) ? input : {};
22006
23443
  const termRaw = params.term;
22007
23444
  if (typeof termRaw !== "string") {
@@ -22053,7 +23490,7 @@ function runSearchCodeTool(input, defaultCwd) {
22053
23490
  omitDirNames: [],
22054
23491
  excludeNames: DEFAULT_EXCLUDE_NAMES
22055
23492
  };
22056
- const result = grepRepo(env, {
23493
+ const result = await grepRepoAsync(env, {
22057
23494
  query: termRaw,
22058
23495
  ref: refParsed.ref,
22059
23496
  paths,
@@ -22731,6 +24168,7 @@ Examples:
22731
24168
  process.exit(1);
22732
24169
  }
22733
24170
  const candidate = repoRoot(cwd);
24171
+ cwdHasGitRepository = !!candidate;
22734
24172
  if (cwdWasExplicit) {
22735
24173
  if (candidate === cwd)
22736
24174
  cwd = candidate;
@@ -22946,23 +24384,23 @@ function fileToMeta(file, range, extraQs) {
22946
24384
  untracked: file.untracked || false
22947
24385
  };
22948
24386
  }
22949
- function computePayload(extras, range, pathFilter = "") {
24387
+ async function computePayload(extras, range, pathFilter = "", responseGeneration = generation) {
22950
24388
  if (isSameWorktreeRange(range)) {
22951
24389
  return {
22952
24390
  files: [],
22953
24391
  totals: { files: 0, additions: 0, deletions: 0 },
22954
24392
  range: "worktree .. worktree",
22955
24393
  project: basename3(cwd),
22956
- branch: currentBranch(cwd) || undefined,
22957
- generation
24394
+ branch: await currentBranchMetadata(),
24395
+ generation: responseGeneration
22958
24396
  };
22959
24397
  }
22960
24398
  const { args, refs } = buildRangeArgs(range);
22961
24399
  const fullArgs = [...extras, ...args];
22962
- const metaResult = fileMetaResult(fullArgs, cwd, false);
24400
+ const metaResult = await fileMetaResultAsync(fullArgs, cwd, false);
22963
24401
  const files = metaResult.files;
22964
24402
  if (!metaResult.error && includeUntracked(range, refs)) {
22965
- files.push(...untrackedMeta(cwd));
24403
+ files.push(...await untrackedMetaAsync(cwd));
22966
24404
  }
22967
24405
  const filteredFiles = pathFilter ? files.filter((file) => file.path === pathFilter || file.old_path === pathFilter) : files;
22968
24406
  filteredFiles.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
@@ -22989,12 +24427,13 @@ function computePayload(extras, range, pathFilter = "") {
22989
24427
  totals,
22990
24428
  range: label || "HEAD",
22991
24429
  project: basename3(cwd),
22992
- branch: currentBranch(cwd) || undefined,
22993
- generation,
24430
+ branch: await currentBranchMetadata(),
24431
+ generation: responseGeneration,
22994
24432
  ...metaResult.error ? { error: metaResult.error } : {}
22995
24433
  };
22996
24434
  }
22997
- function handleDiffJson(url) {
24435
+ async function handleDiffJson(url) {
24436
+ const responseGeneration = generation;
22998
24437
  const extras = [];
22999
24438
  if (url.searchParams.get("ignore_ws") === "1")
23000
24439
  extras.push("-w");
@@ -23008,45 +24447,40 @@ function handleDiffJson(url) {
23008
24447
  if (path && !safePath(path))
23009
24448
  return text("invalid path", 400);
23010
24449
  const key = `${range.from}|${range.to}|${url.searchParams.get("ignore_ws") || ""}|${url.searchParams.get("ignore_blank") || ""}|${path}`;
23011
- if (url.searchParams.get("nocache") === "1") {
23012
- const payload2 = computePayload(extras, range, path);
23013
- const sig = JSON.stringify({ ...payload2, generation: undefined });
23014
- const cached2 = metaCache.get(key);
23015
- if (!cached2 || cached2.sig !== sig) {
23016
- generation++;
23017
- payload2.generation = generation;
23018
- metaCache.clear();
23019
- fileCache.clear();
23020
- }
23021
- const body2 = JSON.stringify(payload2);
23022
- setTimedCacheEntry(metaCache, key, { body: body2, sig });
23023
- 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, {
23024
24454
  headers: {
23025
24455
  "Content-Type": "application/json; charset=utf-8",
23026
24456
  "Cache-Control": "no-store"
23027
24457
  }
23028
24458
  });
23029
- }
23030
- const cached = metaCache.get(key);
23031
- if (cacheFresh(cached))
23032
- return new Response(cached.body, {
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, {
23033
24475
  headers: {
23034
24476
  "Content-Type": "application/json; charset=utf-8",
23035
24477
  "Cache-Control": "no-store"
23036
24478
  }
23037
24479
  });
23038
- const payload = computePayload(extras, range, path);
23039
- const body = JSON.stringify(payload);
23040
- setTimedCacheEntry(metaCache, key, {
23041
- body,
23042
- sig: JSON.stringify({ ...payload, generation: undefined })
23043
- });
23044
- return new Response(body, {
23045
- headers: {
23046
- "Content-Type": "application/json; charset=utf-8",
23047
- "Cache-Control": "no-store"
23048
- }
23049
- });
24480
+ } finally {
24481
+ if (latestDiffMetaRequest.get(key) === requestSequence)
24482
+ latestDiffMetaRequest.delete(key);
24483
+ }
23050
24484
  }
23051
24485
  function safeRepoPath(path) {
23052
24486
  return path === "" || safePath(path);
@@ -23144,16 +24578,16 @@ function worktreeFileMetadata(path, knownSize) {
23144
24578
  return {};
23145
24579
  }
23146
24580
  }
23147
- function gitFileMetadata(ref, path, knownSize) {
23148
- const size = knownSize ?? rawFileSize(path, ref);
23149
- const commitUpdatedAt = lastCommitDateForPath(ref, path, cwd) || undefined;
24581
+ async function gitFileMetadata(ref, path, knownSize) {
24582
+ const size = knownSize ?? await rawFileSize(path, ref);
24583
+ const commitUpdatedAt = await lastCommitDateForPathAsync(ref, path, cwd) || undefined;
23150
24584
  return {
23151
24585
  size: size == null ? undefined : size,
23152
24586
  updated_at: commitUpdatedAt,
23153
24587
  commit_updated_at: commitUpdatedAt
23154
24588
  };
23155
24589
  }
23156
- function directoryMetadata(target, path) {
24590
+ async function directoryMetadata(target, path) {
23157
24591
  if (target === "worktree" || target === "") {
23158
24592
  const full = path === "" ? safeOpenWorktreePath("") : safeWorktreePath2(path);
23159
24593
  if (!full)
@@ -23168,22 +24602,22 @@ function directoryMetadata(target, path) {
23168
24602
  return {};
23169
24603
  }
23170
24604
  }
23171
- const commitUpdatedAt = lastCommitDateForPath(target, path || ".", cwd) || undefined;
24605
+ const commitUpdatedAt = await lastCommitDateForPathAsync(target, path || ".", cwd) || undefined;
23172
24606
  return { updated_at: commitUpdatedAt, commit_updated_at: commitUpdatedAt };
23173
24607
  }
23174
- function fileMetadataForTarget(target, path) {
24608
+ async function fileMetadataForTarget(target, path) {
23175
24609
  return target === "worktree" || target === "" ? worktreeFileMetadata(path) : gitFileMetadata(target, path);
23176
24610
  }
23177
- function attachTreeEntryMetadata(target, entry) {
24611
+ async function attachTreeEntryMetadata(target, entry) {
23178
24612
  if (entry.type === "tree")
23179
- return { ...entry, ...directoryMetadata(target, entry.path) };
24613
+ return { ...entry, ...await directoryMetadata(target, entry.path) };
23180
24614
  if (entry.type === "commit" && !entry.submodule && (target === "worktree" || target === ""))
23181
- return { ...entry, ...directoryMetadata(target, entry.path) };
24615
+ return { ...entry, ...await directoryMetadata(target, entry.path) };
23182
24616
  if (entry.type !== "blob")
23183
24617
  return entry;
23184
- return { ...entry, ...fileMetadataForTarget(target, entry.path) };
24618
+ return { ...entry, ...await fileMetadataForTarget(target, entry.path) };
23185
24619
  }
23186
- function readReadme(target, dirPath) {
24620
+ async function readReadme(target, dirPath) {
23187
24621
  const candidates = ["README.md", "readme.md", "README.markdown", "README"];
23188
24622
  for (const name of candidates) {
23189
24623
  const path = dirPath ? `${dirPath}/${name}` : name;
@@ -23197,13 +24631,13 @@ function readReadme(target, dirPath) {
23197
24631
  continue;
23198
24632
  }
23199
24633
  }
23200
- const res = show(target, path, cwd);
24634
+ const res = await showAsync(target, path, cwd);
23201
24635
  if (res.code === 0)
23202
24636
  return { path, text: res.stdout };
23203
24637
  }
23204
24638
  return null;
23205
24639
  }
23206
- function handleTree(url) {
24640
+ async function handleTree(url) {
23207
24641
  const target = url.searchParams.get("ref") || url.searchParams.get("target") || "worktree";
23208
24642
  const path = (url.searchParams.get("path") || "").replace(/^\/+|\/+$/g, "");
23209
24643
  if (!safeRepoPath(path))
@@ -23211,7 +24645,7 @@ function handleTree(url) {
23211
24645
  if ((target === "worktree" || target === "") && isGitInternalPath(path))
23212
24646
  return text("forbidden", 403);
23213
24647
  if (target !== "worktree") {
23214
- const refCheck = verifyTreeRefResult(target, cwd);
24648
+ const refCheck = await verifyTreeRefResultAsync(target, cwd);
23215
24649
  if (refCheck.ok !== true)
23216
24650
  return text(refCheck.error, refCheck.status ?? 400);
23217
24651
  }
@@ -23221,7 +24655,7 @@ function handleTree(url) {
23221
24655
  if (invalidScopeExcludeNamesQuery(url))
23222
24656
  return text("invalid exclude names", 400);
23223
24657
  const excludeNames = scopeExcludeNamesFromQuery(url);
23224
- const tree = listTreeResult(target, path, cwd, {
24658
+ const tree = await listTreeResultAsync(target, path, cwd, {
23225
24659
  recursive,
23226
24660
  omitDirNames: scopeOmitDirNamesFromQuery(url),
23227
24661
  excludeNames
@@ -23233,17 +24667,17 @@ function handleTree(url) {
23233
24667
  ref: target,
23234
24668
  path,
23235
24669
  project: basename3(cwd),
23236
- branch: currentBranch(cwd) || undefined,
23237
- entries: recursive ? entries : entries.map((entry) => attachTreeEntryMetadata(target, entry)),
23238
- 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),
23239
24673
  upload_enabled: uploadEnabled && (target === "worktree" || target === "")
23240
24674
  });
23241
24675
  }
23242
- function handleSettings() {
24676
+ async function handleSettings() {
23243
24677
  return json2({
23244
24678
  project: basename3(cwd),
23245
- branch: currentBranch(cwd) || undefined,
23246
- repo_web_url: remoteWebUrl(cwd),
24679
+ branch: await currentBranchMetadata(),
24680
+ repo_web_url: cwdHasGitRepository ? await remoteWebUrlAsync(cwd) : null,
23247
24681
  scope: {
23248
24682
  omit_dirs_effective: scopeOmitDirNames,
23249
24683
  omit_dirs_built_in: DEFAULT_WORKTREE_OMIT_DIR_NAMES,
@@ -23278,7 +24712,13 @@ function currentSearchEnv(omitOverride, excludeOverride) {
23278
24712
  excludeNames: excludeOverride ?? scopeExcludeNames
23279
24713
  };
23280
24714
  }
23281
- function handleFiles2(url) {
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;
23282
24722
  const target = url.searchParams.get("ref") || url.searchParams.get("target") || "worktree";
23283
24723
  if (invalidScopeOmitDirNamesQuery(url))
23284
24724
  return text("invalid omit dirs", 400);
@@ -23290,10 +24730,15 @@ function handleFiles2(url) {
23290
24730
  const cached = fileListCache.get(key);
23291
24731
  if (cached && cached.generation === generation)
23292
24732
  return json2(cached.body);
23293
- const result = listRepoFiles(currentSearchEnv(omitDirNames, excludeNames), target, generation);
24733
+ const result = await listRepoFilesAsync(currentSearchEnv(omitDirNames, excludeNames), target, responseGeneration);
23294
24734
  if (result.ok !== true)
23295
24735
  return text(result.error, result.status ?? 400);
23296
- fileListCache.set(key, { generation, body: result.value });
24736
+ if (responseGeneration !== generation)
24737
+ return json2(result.value);
24738
+ fileListCache.set(key, {
24739
+ generation: responseGeneration,
24740
+ body: result.value
24741
+ });
23297
24742
  while (fileListCache.size > MAX_TIMED_CACHE_ENTRIES) {
23298
24743
  const oldest = fileListCache.keys().next().value;
23299
24744
  if (oldest === undefined)
@@ -23325,25 +24770,26 @@ async function handleGrep(url) {
23325
24770
  return text(result.error, result.status ?? 400);
23326
24771
  return json2(result.value);
23327
24772
  }
23328
- function handleRefCommits(url) {
24773
+ async function handleRefCommits(url) {
23329
24774
  const query = url.searchParams.get("q") || "";
23330
24775
  const parsedMax = Number(url.searchParams.get("max") || "");
23331
24776
  const parsedSkip = Number(url.searchParams.get("skip") || "0");
23332
24777
  const max = Number.isFinite(parsedMax) && parsedMax > 0 ? parsedMax : undefined;
23333
24778
  const skip = Number.isFinite(parsedSkip) && parsedSkip > 0 ? parsedSkip : undefined;
23334
- const result = refCommitPageResult(cwd, { query, max, skip });
24779
+ const result = await refCommitPageResultAsync(cwd, { query, max, skip });
23335
24780
  if (result.error)
23336
24781
  return text(result.error, result.status ?? 500);
23337
24782
  return json2({ commits: result.commits, hasMore: result.hasMore });
23338
24783
  }
23339
- function handleLog(url) {
24784
+ async function handleLog(url) {
24785
+ const responseGeneration = generation;
23340
24786
  const ref = url.searchParams.get("ref") || "HEAD";
23341
24787
  const skip = Number(url.searchParams.get("skip") || "0");
23342
24788
  const limit = Number(url.searchParams.get("limit") || "50");
23343
24789
  const path = url.searchParams.get("path") || "";
23344
24790
  if (path && !safePath(path))
23345
24791
  return text("invalid path", 400);
23346
- const result = commitHistory(cwd, {
24792
+ const result = await commitHistoryAsync(cwd, {
23347
24793
  ref,
23348
24794
  skip: Number.isFinite(skip) ? skip : 0,
23349
24795
  limit: Number.isFinite(limit) ? limit : 50,
@@ -23356,7 +24802,7 @@ function handleLog(url) {
23356
24802
  let commits = result.commits;
23357
24803
  let hasWorktree = false;
23358
24804
  if (wantsWorktreeHead) {
23359
- const status = statusPorcelainForPath(path, cwd);
24805
+ const status = await statusPorcelainForPathAsync(path, cwd);
23360
24806
  if (status.ok && status.stdout.length > 0) {
23361
24807
  const parts = status.stdout.split("\x00").filter(Boolean);
23362
24808
  if (parts.length > 0) {
@@ -23378,7 +24824,7 @@ function handleLog(url) {
23378
24824
  return json2({
23379
24825
  commits,
23380
24826
  hasMore: result.hasMore,
23381
- generation,
24827
+ generation: responseGeneration,
23382
24828
  ...hasWorktree ? { hasWorktree: true } : {}
23383
24829
  });
23384
24830
  }
@@ -23401,7 +24847,8 @@ function rememberBlame(key, value) {
23401
24847
  blameCache.delete(oldest.value);
23402
24848
  }
23403
24849
  }
23404
- function handleFileBlame(url) {
24850
+ async function handleFileBlame(url) {
24851
+ const responseGeneration = generation;
23405
24852
  const path = url.searchParams.get("path") || "";
23406
24853
  if (!safePath(path))
23407
24854
  return text("invalid path", 400);
@@ -23416,7 +24863,7 @@ function handleFileBlame(url) {
23416
24863
  if (base === "worktree") {
23417
24864
  cacheKey = `worktree|${path}|${blamePathKey(path)}`;
23418
24865
  } else {
23419
- const resolved = verifyCommit(normalized.ref, cwd);
24866
+ const resolved = await verifyCommitAsync(normalized.ref, cwd);
23420
24867
  if (resolved.ok === false) {
23421
24868
  const status = resolved.error === commandNotFoundDetail("git") ? 503 : 400;
23422
24869
  return text(resolved.error || "unknown ref", status);
@@ -23429,16 +24876,21 @@ function handleFileBlame(url) {
23429
24876
  blameCache.delete(cacheKey);
23430
24877
  blameCache.set(cacheKey, cached);
23431
24878
  }
23432
- return json2({ ...cached, base, ref, generation });
24879
+ return json2({ ...cached, base, ref, generation: responseGeneration });
23433
24880
  }
23434
- const result = blame(cwd, { path, ref: normalized.ref, base });
24881
+ const result = await blameAsync(cwd, {
24882
+ path,
24883
+ ref: normalized.ref,
24884
+ base
24885
+ });
23435
24886
  if (result.error && result.status)
23436
24887
  return text(result.error, result.status);
23437
- if (!result.error)
24888
+ if (!result.error && responseGeneration === generation)
23438
24889
  rememberBlame(cacheKey, result);
23439
- return json2({ ...result, base, ref, generation });
24890
+ return json2({ ...result, base, ref, generation: responseGeneration });
23440
24891
  }
23441
- function handleFileDiff(url) {
24892
+ async function handleFileDiff(url) {
24893
+ const responseGeneration = generation;
23442
24894
  const path = url.searchParams.get("path") || "";
23443
24895
  if (!safePath(path))
23444
24896
  return text("invalid path", 400);
@@ -23464,7 +24916,7 @@ function handleFileDiff(url) {
23464
24916
  line_count: 0,
23465
24917
  truncated: false,
23466
24918
  binary: false,
23467
- generation
24919
+ generation: responseGeneration
23468
24920
  });
23469
24921
  }
23470
24922
  const { args } = buildRangeArgs(range);
@@ -23491,21 +24943,21 @@ function handleFileDiff(url) {
23491
24943
  diffText = cached.diffText;
23492
24944
  } else {
23493
24945
  if (isUntracked) {
23494
- const res = untrackedFileDiff(extras, path, cwd);
24946
+ const res = await untrackedFileDiffAsync(extras, path, cwd);
23495
24947
  diffText = res.stdout || "";
23496
24948
  if (res.code !== 0 && !(res.code === 1 && diffText)) {
23497
24949
  errText = res.stderr || "diff failed";
23498
24950
  errStatus = res.status ?? 500;
23499
24951
  }
23500
24952
  } else {
23501
- const res = fileDiffText([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
24953
+ const res = await fileDiffTextAsync([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
23502
24954
  diffText = res.stdout || "";
23503
24955
  if (res.code !== 0) {
23504
24956
  errText = res.stderr || "diff failed";
23505
24957
  errStatus = res.status ?? 500;
23506
24958
  }
23507
24959
  }
23508
- if (!errText)
24960
+ if (!errText && responseGeneration === generation)
23509
24961
  setTimedCacheEntry(fileCache, cacheKey, { diffText });
23510
24962
  }
23511
24963
  if (errStatus)
@@ -23524,7 +24976,7 @@ function handleFileDiff(url) {
23524
24976
  truncated: mode === "preview" && (truncated.totalHunks > truncated.renderedHunks || truncated.lineTruncated),
23525
24977
  binary: diffText.includes("Binary files"),
23526
24978
  error: errText,
23527
- generation
24979
+ generation: responseGeneration
23528
24980
  };
23529
24981
  return json2(body);
23530
24982
  }
@@ -23693,13 +25145,13 @@ async function handleFileRange(url) {
23693
25145
  return json2(body);
23694
25146
  } else {
23695
25147
  const responseGeneration = generation;
23696
- const refCheck = verifyTreeRefResult(ref, cwd);
25148
+ const refCheck = await verifyTreeRefResultAsync(ref, cwd);
23697
25149
  if (refCheck.ok !== true)
23698
25150
  return text(refCheck.error, refCheck.status ?? 400);
23699
- const oid = objectId(ref, path, cwd);
25151
+ const oid = await objectIdAsync(ref, path, cwd);
23700
25152
  if (oid.code !== 0 || !oid.oid)
23701
25153
  return text("not in ref", 404);
23702
- const size = objectByteSize(oid.oid, cwd);
25154
+ const size = await objectByteSizeAsync(oid.oid, cwd);
23703
25155
  if (size.code !== 0)
23704
25156
  return text("cannot read ref", 500);
23705
25157
  const result = await collectIndexedGitBlobLineRange(path, oid.oid, size.size, start, end);
@@ -23724,17 +25176,17 @@ async function handleRawFile(req, url) {
23724
25176
  return text("forbidden", 403);
23725
25177
  const ref = url.searchParams.get("ref") || "worktree";
23726
25178
  if (ref !== "worktree" && ref !== "") {
23727
- const refCheck = verifyTreeRefResult(ref, cwd);
25179
+ const refCheck = await verifyTreeRefResultAsync(ref, cwd);
23728
25180
  if (refCheck.ok !== true)
23729
25181
  return text(refCheck.error, refCheck.status ?? 400);
23730
- const oid = objectId(ref, path, cwd);
25182
+ const oid = await objectIdAsync(ref, path, cwd);
23731
25183
  if (oid.code !== 0 || !oid.oid)
23732
25184
  return text("not in ref", 404);
23733
- const sizeResult = objectByteSize(oid.oid, cwd);
25185
+ const sizeResult = await objectByteSizeAsync(oid.oid, cwd);
23734
25186
  if (sizeResult.code !== 0)
23735
25187
  return text("cannot read ref", 500);
23736
25188
  const size = sizeResult.size;
23737
- const metadata = gitFileMetadata(ref, path, size);
25189
+ const metadata = await gitFileMetadata(ref, path, size);
23738
25190
  const rangeResult = req.headers.get("range") ? parseHttpByteRange(req.headers.get("range"), size) : null;
23739
25191
  if (rangeResult?.kind === "unsatisfiable") {
23740
25192
  return new Response(null, {
@@ -23777,7 +25229,7 @@ async function handleRawFile(req, url) {
23777
25229
  const full = safeWorktreePath2(path);
23778
25230
  if (!full)
23779
25231
  return text("not found", 404);
23780
- const size = rawFileSize(path, ref);
25232
+ const size = await rawFileSize(path, ref);
23781
25233
  if (size == null)
23782
25234
  return text("not found", 404);
23783
25235
  const metadata = worktreeFileMetadata(path, size);
@@ -23814,11 +25266,12 @@ async function handleRawFile(req, url) {
23814
25266
  });
23815
25267
  }
23816
25268
  }
23817
- function rawFileSize(path, ref) {
25269
+ async function rawFileSize(path, ref) {
23818
25270
  if (ref !== "worktree" && ref !== "") {
23819
- if (!verifyTreeRef(ref, cwd))
25271
+ const refCheck = await verifyTreeRefResultAsync(ref, cwd);
25272
+ if (refCheck.ok !== true)
23820
25273
  return null;
23821
- const res = objectSize(ref, path, cwd);
25274
+ const res = await objectSizeAsync(ref, path, cwd);
23822
25275
  return res.code === 0 ? res.size : null;
23823
25276
  }
23824
25277
  const full = safeWorktreePath2(path);
@@ -24043,13 +25496,13 @@ function moveMacPathIntoTrash(path) {
24043
25496
  return { ok: false, error: String(error) };
24044
25497
  }
24045
25498
  }
24046
- function movePathToTrash(path) {
25499
+ async function movePathToTrash(path) {
24047
25500
  lstatSync5(path);
24048
25501
  if (process.platform === "darwin") {
24049
25502
  return moveMacPathIntoTrash(path);
24050
25503
  }
24051
25504
  if (process.platform === "win32") {
24052
- const res = runSync([
25505
+ const res = await runAsync([
24053
25506
  "powershell.exe",
24054
25507
  "-NoProfile",
24055
25508
  "-NonInteractive",
@@ -24062,7 +25515,7 @@ function movePathToTrash(path) {
24062
25515
  }
24063
25516
  return { ok: false, error: "trash unsupported" };
24064
25517
  }
24065
- function restoreTrashPath(originalPath, trashPath) {
25518
+ async function restoreTrashPath(originalPath, trashPath) {
24066
25519
  const parent = parentRepoPath(originalPath);
24067
25520
  const parentFullPath = safeOpenWorktreePath(parent);
24068
25521
  if (!parentFullPath)
@@ -24088,7 +25541,7 @@ function restoreTrashPath(originalPath, trashPath) {
24088
25541
  }
24089
25542
  }
24090
25543
  if (process.platform === "win32") {
24091
- const res = runSync([
25544
+ const res = await runAsync([
24092
25545
  "powershell.exe",
24093
25546
  "-NoProfile",
24094
25547
  "-NonInteractive",
@@ -24171,7 +25624,7 @@ async function handleTrashPath(req) {
24171
25624
  const originalFullPath = safeWorktreePath2(path);
24172
25625
  if (!originalFullPath)
24173
25626
  return text("not found", 404);
24174
- const moved = movePathToTrash(worktreePath(path));
25627
+ const moved = await movePathToTrash(worktreePath(path));
24175
25628
  if (!moved.ok)
24176
25629
  return text(moved.error || "trash failed", 500);
24177
25630
  const undo = {
@@ -24265,7 +25718,7 @@ async function handleRestoreTrash(req) {
24265
25718
  return text("invalid restore target", 400);
24266
25719
  if (isGitInternalPath(originalPath))
24267
25720
  return text("forbidden", 403);
24268
- const restored = restoreTrashPath(originalPath, trashPath || undefined);
25721
+ const restored = await restoreTrashPath(originalPath, trashPath || undefined);
24269
25722
  if (!restored.ok)
24270
25723
  return text(restored.error || "undo failed", 409);
24271
25724
  triggerUpdate();
@@ -24399,7 +25852,7 @@ async function handleJournal(req) {
24399
25852
  const labels = bodyStringList(body, "labels");
24400
25853
  if (label)
24401
25854
  labels.push(label);
24402
- const issues = readGithubIssueList({
25855
+ const issues = await readGithubIssueListAsync({
24403
25856
  cwd,
24404
25857
  repo: bodyString(body, "repo"),
24405
25858
  labels,
@@ -24803,7 +26256,7 @@ function restartWorktreeWatch() {
24803
26256
  }
24804
26257
  worktreeWatch = startScopedWorktreeWatch();
24805
26258
  }
24806
- 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;
24807
26260
  var init_preview = __esm(async () => {
24808
26261
  init_journal();
24809
26262
  init_routes();
@@ -24881,6 +26334,7 @@ var init_preview = __esm(async () => {
24881
26334
  fileCache = new Map;
24882
26335
  blameCache = new Map;
24883
26336
  metaCache = new Map;
26337
+ latestDiffMetaRequest = new Map;
24884
26338
  fileListCache = new Map;
24885
26339
  lineIndexCache = new Map;
24886
26340
  blobLineIndexCache = new Map;
@@ -24897,6 +26351,7 @@ var init_preview = __esm(async () => {
24897
26351
  isCodeViewerInternalPath = isToolInternalPath;
24898
26352
  parseCli();
24899
26353
  applyPersistedSettings(await loadAppSettingsState(cwd));
26354
+ databaseHandleModule = Promise.resolve().then(() => (init_handle(), exports_handle));
24900
26355
  server = await startServer({
24901
26356
  hostname: "127.0.0.1",
24902
26357
  port: listenPort,
@@ -24908,9 +26363,9 @@ var init_preview = __esm(async () => {
24908
26363
  if (staticResponse)
24909
26364
  return staticResponse;
24910
26365
  if (url.pathname === "/diff.json")
24911
- return handleDiffJson(url);
26366
+ return await handleDiffJson(url);
24912
26367
  if (url.pathname === "/_settings")
24913
- return handleSettings();
26368
+ return await handleSettings();
24914
26369
  if (url.pathname === "/_doctor")
24915
26370
  return handleDoctor({
24916
26371
  cwd,
@@ -24918,19 +26373,19 @@ var init_preview = __esm(async () => {
24918
26373
  listenPort
24919
26374
  });
24920
26375
  if (url.pathname === "/_tree")
24921
- return handleTree(url);
26376
+ return await handleTree(url);
24922
26377
  if (url.pathname === "/_files")
24923
- return handleFiles2(url);
26378
+ return await handleFiles2(url);
24924
26379
  if (url.pathname === "/_grep")
24925
26380
  return await handleGrep(url);
24926
26381
  if (url.pathname === "/_commits")
24927
- return handleRefCommits(url);
26382
+ return await handleRefCommits(url);
24928
26383
  if (url.pathname === "/_log")
24929
- return handleLog(url);
26384
+ return await handleLog(url);
24930
26385
  if (url.pathname === "/_file_blame")
24931
- return handleFileBlame(url);
26386
+ return await handleFileBlame(url);
24932
26387
  if (url.pathname === "/file_diff")
24933
- return handleFileDiff(url);
26388
+ return await handleFileDiff(url);
24934
26389
  if (url.pathname === "/file_range")
24935
26390
  return handleFileRange(url);
24936
26391
  if (url.pathname === "/_file")
@@ -24946,7 +26401,7 @@ var init_preview = __esm(async () => {
24946
26401
  if (url.pathname === "/_upload_files")
24947
26402
  return handleUploadFiles(req);
24948
26403
  if (url.pathname.startsWith("/_db/")) {
24949
- const { handleDatabaseRoute: handleDatabaseRoute2 } = await Promise.resolve().then(() => (init_handle(), exports_handle));
26404
+ const { handleDatabaseRoute: handleDatabaseRoute2 } = await databaseHandleModule;
24950
26405
  const dbResponse = await handleDatabaseRoute2(req, url, cwd, scopeOmitDirNames, sideEffectRequestAllowed, sendSse);
24951
26406
  if (dbResponse)
24952
26407
  return dbResponse;
@@ -24964,7 +26419,7 @@ var init_preview = __esm(async () => {
24964
26419
  if (url.pathname === "/_annotations")
24965
26420
  return handleAnnotations(req);
24966
26421
  if (url.pathname === "/_refs") {
24967
- const result = refsResult(cwd);
26422
+ const result = await refsResultAsync(cwd);
24968
26423
  if (result.error)
24969
26424
  return text(result.error, result.status ?? 500);
24970
26425
  return json2(result.refs);