@youtyan/code-viewer 0.6.9 → 0.6.10

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.
@@ -96,7 +96,7 @@ var init_sqlite_driver = __esm(() => {
96
96
 
97
97
  // web-src/server/json-store.ts
98
98
  import { randomBytes } from "node:crypto";
99
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
99
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
100
100
  import { dirname } from "node:path";
101
101
  function isEnoent(err) {
102
102
  return err?.code === "ENOENT";
@@ -140,8 +140,15 @@ function createJsonFileStore(options) {
140
140
  }
141
141
  await mkdir(dirname(file), { recursive: true });
142
142
  const tmp = tmpPath(file);
143
- await writeFile(tmp, content, "utf8");
144
- await rename(tmp, file);
143
+ try {
144
+ await writeFile(tmp, content, "utf8");
145
+ await rename(tmp, file);
146
+ } catch (err) {
147
+ await unlink(tmp).catch(() => {
148
+ return;
149
+ });
150
+ throw err;
151
+ }
145
152
  }
146
153
  async function load(root) {
147
154
  const pendingWrite = queues.get(options.filePath(root));
@@ -841,6 +848,7 @@ function runSync(args, cwd, options = {}) {
841
848
  encoding: "buffer",
842
849
  stdio: ["ignore", "pipe", "pipe"],
843
850
  timeout: options.timeout,
851
+ maxBuffer: options.maxBuffer ?? 64 * 1024 * 1024,
844
852
  killSignal: "SIGKILL"
845
853
  });
846
854
  return {
@@ -849,20 +857,6 @@ function runSync(args, cwd, options = {}) {
849
857
  stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
850
858
  };
851
859
  }
852
- function runBytesSync(args, cwd, options = {}) {
853
- const proc = spawnSync(args[0], args.slice(1), {
854
- cwd,
855
- encoding: "buffer",
856
- stdio: ["ignore", "pipe", "pipe"],
857
- timeout: options.timeout,
858
- killSignal: "SIGKILL"
859
- });
860
- return {
861
- code: proc.status ?? (proc.error ? 1 : 0),
862
- stdout: new Uint8Array(proc.stdout || new Uint8Array),
863
- stderr: appendProcessError(new TextDecoder().decode(proc.stderr || new Uint8Array), proc.error)
864
- };
865
- }
866
860
  function spawnDetached(args) {
867
861
  const child = spawn(args[0], args.slice(1), {
868
862
  detached: true,
@@ -1035,10 +1029,13 @@ var init_runtime = () => {};
1035
1029
 
1036
1030
  // web-src/server/git.ts
1037
1031
  import {
1032
+ closeSync,
1038
1033
  existsSync,
1039
1034
  lstatSync,
1035
+ openSync,
1040
1036
  readdirSync,
1041
1037
  readFileSync,
1038
+ readSync,
1042
1039
  statSync as statSync2
1043
1040
  } from "node:fs";
1044
1041
  import { join as join3 } from "node:path";
@@ -1061,11 +1058,6 @@ function run(args, cwd) {
1061
1058
  timeout: GIT_COMMAND_TIMEOUT_MS
1062
1059
  });
1063
1060
  }
1064
- function runBytes(args, cwd) {
1065
- return runBytesSync(resolveGitArgs(args), cwd, {
1066
- timeout: GIT_COMMAND_TIMEOUT_MS
1067
- });
1068
- }
1069
1061
  function resolveGitArgs(args) {
1070
1062
  if (args[0] !== "git")
1071
1063
  return args;
@@ -1135,9 +1127,6 @@ function statusPorcelainForPath(path, cwd) {
1135
1127
  function show(ref, path, cwd) {
1136
1128
  return run(["git", "show", `${ref}:${path}`], cwd);
1137
1129
  }
1138
- function showBytes(ref, path, cwd) {
1139
- return runBytes(["git", "show", `${ref}:${path}`], cwd);
1140
- }
1141
1130
  function catFileBlobStream(oid, cwd) {
1142
1131
  return spawnStream(resolveGitArgs(["git", "cat-file", "blob", oid]), cwd);
1143
1132
  }
@@ -1961,21 +1950,19 @@ function listTreeResult(ref, path, cwd, options = {}) {
1961
1950
  function untrackedMeta(cwd) {
1962
1951
  return untracked(cwd).flatMap((path) => {
1963
1952
  const full = join3(cwd, path);
1964
- let binary = false;
1965
- let lines = 0;
1966
1953
  let fileExists = false;
1967
1954
  try {
1968
1955
  fileExists = existsSync(full) && statSync2(full).isFile();
1969
1956
  } catch {
1970
1957
  fileExists = false;
1971
1958
  }
1959
+ let scan;
1972
1960
  if (fileExists) {
1973
- const data = readFileSync(full);
1974
- const probe = data.subarray(0, 8192);
1975
- binary = probe.includes(0);
1976
- if (!binary)
1977
- lines = data.toString("utf8").split(`
1978
- `).length - 1;
1961
+ try {
1962
+ scan = scanFileBinaryAndNewlines(full);
1963
+ } catch {
1964
+ return [];
1965
+ }
1979
1966
  } else {
1980
1967
  return [];
1981
1968
  }
@@ -1983,14 +1970,40 @@ function untrackedMeta(cwd) {
1983
1970
  {
1984
1971
  path,
1985
1972
  status: "A",
1986
- additions: binary ? 0 : lines,
1973
+ additions: scan.binary ? 0 : scan.newlines,
1987
1974
  deletions: 0,
1988
- binary,
1975
+ binary: scan.binary,
1989
1976
  untracked: true
1990
1977
  }
1991
1978
  ];
1992
1979
  });
1993
1980
  }
1981
+ function scanFileBinaryAndNewlines(full) {
1982
+ const fd = openSync(full, "r");
1983
+ const buffer = Buffer.allocUnsafe(64 * 1024);
1984
+ let newlines = 0;
1985
+ let inspected = 0;
1986
+ try {
1987
+ while (true) {
1988
+ const read = readSync(fd, buffer, 0, buffer.length, null);
1989
+ if (read <= 0)
1990
+ break;
1991
+ const binaryProbeBytes = Math.min(read, Math.max(0, 8192 - inspected));
1992
+ for (let i = 0;i < binaryProbeBytes; i++) {
1993
+ if (buffer[i] === 0)
1994
+ return { binary: true, newlines: 0 };
1995
+ }
1996
+ inspected += read;
1997
+ for (let i = 0;i < read; i++) {
1998
+ if (buffer[i] === 10)
1999
+ newlines++;
2000
+ }
2001
+ }
2002
+ } finally {
2003
+ closeSync(fd);
2004
+ }
2005
+ return { binary: false, newlines };
2006
+ }
1994
2007
  function fileMetaResult(args, cwd, includeUntracked = false) {
1995
2008
  const ns = nameStatusResult(args, cwd);
1996
2009
  if (ns.error)
@@ -2052,13 +2065,17 @@ function untrackedFileDiff(extras, path, cwd) {
2052
2065
  function splitHunks(diffText) {
2053
2066
  if (!diffText)
2054
2067
  return { header: "", hunks: [] };
2055
- const first = diffText.startsWith("@@") ? 0 : diffText.indexOf(`
2056
- @@`) + 1;
2057
- if (first <= 0)
2068
+ const startsWithHunk = diffText.startsWith("@@");
2069
+ const first = startsWithHunk ? 0 : diffText.indexOf(`
2070
+ @@`);
2071
+ if (first < 0)
2072
+ return { header: diffText, hunks: [] };
2073
+ const hunkStart = startsWithHunk ? 0 : first + 1;
2074
+ if (hunkStart >= diffText.length)
2058
2075
  return { header: diffText, hunks: [] };
2059
- const header = diffText.slice(0, first);
2076
+ const header = diffText.slice(0, hunkStart);
2060
2077
  const hunks = [];
2061
- let cur = first;
2078
+ let cur = hunkStart;
2062
2079
  while (cur < diffText.length) {
2063
2080
  const next = diffText.indexOf(`
2064
2081
  @@`, cur + 1);
@@ -7839,11 +7856,106 @@ function computeFuzzyMatch(query, path) {
7839
7856
  tier
7840
7857
  };
7841
7858
  }
7842
- function rankFuzzyPaths(query, items) {
7843
- return items.map((item) => {
7859
+ function rankFuzzyPaths(query, items, limit) {
7860
+ const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
7861
+ const compare = (a, b) => b.tier - a.tier || b.score - a.score || a.item.path.localeCompare(b.item.path);
7862
+ if (!bounded) {
7863
+ return items.map((item) => {
7864
+ const match = computeFuzzyMatch(query, item.path);
7865
+ return match ? { item, score: match.score, ranges: match.ranges, tier: match.tier } : null;
7866
+ }).filter((item) => item !== null).sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
7867
+ }
7868
+ const top = [];
7869
+ for (const item of items) {
7844
7870
  const match = computeFuzzyMatch(query, item.path);
7845
- return match ? { item, score: match.score, ranges: match.ranges, tier: match.tier } : null;
7846
- }).filter((item) => item !== null).sort((a, b) => b.tier - a.tier || b.score - a.score || a.item.path.localeCompare(b.item.path)).map(({ item, score, ranges }) => ({ item, score, ranges }));
7871
+ if (!match)
7872
+ continue;
7873
+ const ranked = {
7874
+ item,
7875
+ score: match.score,
7876
+ ranges: match.ranges,
7877
+ tier: match.tier
7878
+ };
7879
+ pushBoundedTop(top, ranked, bounded, compare);
7880
+ }
7881
+ return top.sort(compare).map(({ item, score, ranges }) => ({ item, score, ranges }));
7882
+ }
7883
+ function pushBoundedTop(heap, item, limit, compareBestFirst) {
7884
+ const isWorse = (a, b) => compareBestFirst(a, b) > 0;
7885
+ const siftUp = (index) => {
7886
+ while (index > 0) {
7887
+ const parent = Math.floor((index - 1) / 2);
7888
+ if (!isWorse(heap[index], heap[parent]))
7889
+ break;
7890
+ [heap[index], heap[parent]] = [heap[parent], heap[index]];
7891
+ index = parent;
7892
+ }
7893
+ };
7894
+ const siftDown = (index) => {
7895
+ while (true) {
7896
+ const left = index * 2 + 1;
7897
+ const right = left + 1;
7898
+ let worst = index;
7899
+ if (left < heap.length && isWorse(heap[left], heap[worst]))
7900
+ worst = left;
7901
+ if (right < heap.length && isWorse(heap[right], heap[worst]))
7902
+ worst = right;
7903
+ if (worst === index)
7904
+ break;
7905
+ [heap[index], heap[worst]] = [heap[worst], heap[index]];
7906
+ index = worst;
7907
+ }
7908
+ };
7909
+ if (heap.length < limit) {
7910
+ heap.push(item);
7911
+ siftUp(heap.length - 1);
7912
+ return;
7913
+ }
7914
+ if (compareBestFirst(item, heap[0]) >= 0)
7915
+ return;
7916
+ heap[0] = item;
7917
+ siftDown(0);
7918
+ }
7919
+ function rankGlobPathMatches(query, items, limit) {
7920
+ const matchPath = createGlobPathMatcher(query);
7921
+ if (!matchPath)
7922
+ return [];
7923
+ const bounded = Number.isInteger(limit) && limit !== undefined && limit > 0 ? Math.floor(limit) : 0;
7924
+ const compare = (a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path);
7925
+ if (!bounded) {
7926
+ return items.map((item) => {
7927
+ const match = matchPath(item.path);
7928
+ return match ? {
7929
+ item,
7930
+ score: match.score,
7931
+ ranges: match.ranges,
7932
+ mode: "glob"
7933
+ } : null;
7934
+ }).filter((item) => item !== null).sort(compare);
7935
+ }
7936
+ const top = [];
7937
+ for (const item of items) {
7938
+ const match = matchPath(item.path);
7939
+ if (!match)
7940
+ continue;
7941
+ const ranked = {
7942
+ item,
7943
+ score: match.score,
7944
+ ranges: match.ranges,
7945
+ mode: "glob"
7946
+ };
7947
+ pushBoundedTop(top, ranked, bounded, compare);
7948
+ }
7949
+ return top.sort(compare);
7950
+ }
7951
+ function rankPathMatches(query, items, limit) {
7952
+ if (isGlobPathQuery(query)) {
7953
+ return rankGlobPathMatches(query, items, limit);
7954
+ }
7955
+ return rankFuzzyPaths(query, items, limit).map((item) => ({
7956
+ ...item,
7957
+ mode: "fuzzy"
7958
+ }));
7847
7959
  }
7848
7960
  function isGlobPathQuery(query) {
7849
7961
  return /[*?]/.test(query.trim());
@@ -7887,49 +7999,37 @@ function globToRegExp(query) {
7887
7999
  return null;
7888
8000
  }
7889
8001
  }
7890
- function globMatchPath(query, path) {
8002
+ function createGlobPathMatcher(query) {
7891
8003
  const regex = globToRegExp(query);
7892
- const baseStart = basenameStart(path);
7893
- const basename = path.slice(baseStart);
7894
- if (!regex || !regex.test(path) && (query.includes("/") || !regex.test(basename)))
8004
+ if (!regex)
7895
8005
  return null;
7896
8006
  const literal = query.replace(/[*?[\]]+/g, " ").trim().split(/\s+/).filter(Boolean);
7897
- const ranges = [];
7898
- const lowerPath = path.toLowerCase();
7899
- for (const part of literal) {
7900
- const start = lowerPath.indexOf(part.toLowerCase());
7901
- if (start >= 0)
7902
- ranges.push({ start, end: start + part.length });
7903
- }
7904
- ranges.sort((a, b) => a.start - b.start || a.end - b.end);
7905
- const mergedRanges = [];
7906
- for (const range of ranges) {
7907
- const last = mergedRanges[mergedRanges.length - 1];
7908
- if (last && last.end >= range.start) {
7909
- last.end = Math.max(last.end, range.end);
7910
- } else {
7911
- mergedRanges.push({ ...range });
8007
+ const suffix = query.replace(/^\*+/, "").toLowerCase();
8008
+ return (path) => {
8009
+ const baseStart = basenameStart(path);
8010
+ const basename = path.slice(baseStart);
8011
+ if (!regex.test(path) && (query.includes("/") || !regex.test(basename)))
8012
+ return null;
8013
+ const ranges = [];
8014
+ const lowerPath = path.toLowerCase();
8015
+ for (const part of literal) {
8016
+ const start = lowerPath.indexOf(part.toLowerCase());
8017
+ if (start >= 0)
8018
+ ranges.push({ start, end: start + part.length });
8019
+ }
8020
+ ranges.sort((a, b) => a.start - b.start || a.end - b.end);
8021
+ const mergedRanges = [];
8022
+ for (const range of ranges) {
8023
+ const last = mergedRanges[mergedRanges.length - 1];
8024
+ if (last && last.end >= range.start) {
8025
+ last.end = Math.max(last.end, range.end);
8026
+ } else {
8027
+ mergedRanges.push({ ...range });
8028
+ }
7912
8029
  }
7913
- }
7914
- const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(query.replace(/^\*+/, "").toLowerCase()) ? 50 : 0);
7915
- return { score, ranges: mergedRanges };
7916
- }
7917
- function rankPathMatches(query, items) {
7918
- if (isGlobPathQuery(query)) {
7919
- return items.map((item) => {
7920
- const match = globMatchPath(query, item.path);
7921
- return match ? {
7922
- item,
7923
- score: match.score,
7924
- ranges: match.ranges,
7925
- mode: "glob"
7926
- } : null;
7927
- }).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
7928
- }
7929
- return rankFuzzyPaths(query, items).map((item) => ({
7930
- ...item,
7931
- mode: "fuzzy"
7932
- }));
8030
+ const score = 1000 - Math.min(path.length, 200) + (path.slice(baseStart).toLowerCase().endsWith(suffix) ? 50 : 0);
8031
+ return { score, ranges: mergedRanges };
8032
+ };
7933
8033
  }
7934
8034
 
7935
8035
  // web-src/server/search.ts
@@ -8020,9 +8120,11 @@ function parseRgOutput(stdout, max, omitDirNames = [], excludeNames = []) {
8020
8120
  const matches = [];
8021
8121
  for (const line of stdout.split(`
8022
8122
  `)) {
8023
- if (!line || matches.length >= max)
8123
+ if (!line)
8024
8124
  continue;
8025
- const parsed = /^(.*):(\d+):(\d+):(.*)$/.exec(line);
8125
+ if (matches.length >= max)
8126
+ break;
8127
+ const parsed = /^(.*?):(\d+):(\d+):(.*)$/.exec(line);
8026
8128
  if (!parsed)
8027
8129
  continue;
8028
8130
  const path = parsed[1];
@@ -13308,10 +13410,10 @@ function hasControlCharacter(value) {
13308
13410
 
13309
13411
  // web-src/server/database/discovery.ts
13310
13412
  import {
13311
- closeSync,
13413
+ closeSync as closeSync2,
13312
13414
  existsSync as existsSync6,
13313
- openSync,
13314
- readSync,
13415
+ openSync as openSync2,
13416
+ readSync as readSync2,
13315
13417
  realpathSync as realpathSync4,
13316
13418
  statSync as statSync4
13317
13419
  } from "node:fs";
@@ -13323,11 +13425,11 @@ function isSqliteFile(fullPath) {
13323
13425
  if (!stat2.isFile() || stat2.size < 16)
13324
13426
  return false;
13325
13427
  const buf = Buffer.alloc(16);
13326
- const fd = openSync(fullPath, "r");
13428
+ const fd = openSync2(fullPath, "r");
13327
13429
  try {
13328
- readSync(fd, buf, 0, 16, 0);
13430
+ readSync2(fd, buf, 0, 16, 0);
13329
13431
  } finally {
13330
- closeSync(fd);
13432
+ closeSync2(fd);
13331
13433
  }
13332
13434
  return buf.toString("utf8", 0, 16) === SQLITE_MAGIC;
13333
13435
  } catch {
@@ -20643,6 +20745,21 @@ function rgAvailable(cwd) {
20643
20745
  rgAvailableCache = proc.code === 0;
20644
20746
  return rgAvailableCache;
20645
20747
  }
20748
+ async function rgAvailableAsync(cwd) {
20749
+ if (rgAvailableCache !== null)
20750
+ return rgAvailableCache;
20751
+ const proc = await spawnTextAsync({
20752
+ command: commandForExternal("rg"),
20753
+ args: ["--version"],
20754
+ cwd,
20755
+ timeoutMs: 5000,
20756
+ abortMessage: "rg version aborted",
20757
+ timeoutMessage: "rg version timed out after 5000ms",
20758
+ rejectOnError: false
20759
+ });
20760
+ rgAvailableCache = proc.code === 0;
20761
+ return rgAvailableCache;
20762
+ }
20646
20763
  function isExcludedScopePath(path, excludeNames) {
20647
20764
  return path.split(/[\\/]+/).some((part) => excludeNames.some((name) => part.toLowerCase() === name.toLowerCase()));
20648
20765
  }
@@ -20744,6 +20861,44 @@ function grepWorktree(env, req) {
20744
20861
  matches
20745
20862
  };
20746
20863
  }
20864
+ async function grepWorktreeAsync(env, req) {
20865
+ const paths = filterCallerPaths(env, req.paths);
20866
+ if (await rgAvailableAsync(env.cwd)) {
20867
+ const safePaths = paths.filter((path) => safeWorktreePath(env, path));
20868
+ const args = buildRgArgs(req.query, req.max, safePaths, req.regex, env.omitDirNames, env.excludeNames);
20869
+ const proc = await spawnTextAsync({
20870
+ command: commandForExternal("rg"),
20871
+ args: args.slice(1),
20872
+ cwd: env.cwd,
20873
+ timeoutMs: 5000,
20874
+ abortMessage: "grep aborted",
20875
+ timeoutMessage: "grep timed out after 5000ms",
20876
+ rejectOnError: false
20877
+ });
20878
+ const matches2 = parseRgOutput(proc.stdout, req.max, env.omitDirNames, env.excludeNames).filter((match) => isSafePath(match.path) && !isGitInternalPath(match.path) && !isSkippableSearchPath(match.path, env.omitDirNames, env.excludeNames) && !!safeWorktreePath(env, match.path));
20879
+ return {
20880
+ ref: "worktree",
20881
+ engine: "rg",
20882
+ truncated: matches2.length >= req.max,
20883
+ matches: matches2
20884
+ };
20885
+ }
20886
+ if (req.regex) {
20887
+ return {
20888
+ ref: "worktree",
20889
+ engine: "fallback",
20890
+ truncated: false,
20891
+ matches: []
20892
+ };
20893
+ }
20894
+ const matches = grepWorktreeFallback(env, req.query, req.max, paths);
20895
+ return {
20896
+ ref: "worktree",
20897
+ engine: "fallback",
20898
+ truncated: matches.length >= req.max,
20899
+ matches
20900
+ };
20901
+ }
20747
20902
  function grepTreeRef(env, req) {
20748
20903
  const safePaths = filterCallerPaths(env, req.paths);
20749
20904
  const args = [
@@ -20772,6 +20927,40 @@ function grepTreeRef(env, req) {
20772
20927
  matches
20773
20928
  };
20774
20929
  }
20930
+ async function grepTreeRefAsync(env, req) {
20931
+ const safePaths = filterCallerPaths(env, req.paths);
20932
+ const args = [
20933
+ "-c",
20934
+ "core.quotepath=false",
20935
+ "grep",
20936
+ "-n",
20937
+ "--column",
20938
+ "-i",
20939
+ req.regex ? "-E" : "-F",
20940
+ "--no-color",
20941
+ "-e",
20942
+ req.query,
20943
+ req.ref,
20944
+ "--",
20945
+ ...safePaths
20946
+ ];
20947
+ const proc = await spawnTextAsync({
20948
+ command: commandForExternal("git"),
20949
+ args,
20950
+ cwd: env.cwd,
20951
+ timeoutMs: 5000,
20952
+ abortMessage: "git grep aborted",
20953
+ timeoutMessage: "git grep timed out after 5000ms",
20954
+ rejectOnError: false
20955
+ });
20956
+ const matches = parseGitGrepOutput(proc.stdout, req.ref, req.max, env.omitDirNames, env.excludeNames).slice(0, req.max);
20957
+ return {
20958
+ ref: req.ref,
20959
+ engine: "git",
20960
+ truncated: matches.length >= req.max,
20961
+ matches
20962
+ };
20963
+ }
20775
20964
  function grepRepo(env, req) {
20776
20965
  const isWorktree = req.ref === "worktree" || req.ref === "";
20777
20966
  if (!isWorktree) {
@@ -20800,6 +20989,34 @@ function grepRepo(env, req) {
20800
20989
  }
20801
20990
  return { ok: true, value: grepTreeRef(env, req) };
20802
20991
  }
20992
+ async function grepRepoAsync(env, req) {
20993
+ const isWorktree = req.ref === "worktree" || req.ref === "";
20994
+ if (!isWorktree) {
20995
+ const refCheck = verifyTreeRefResult(req.ref, env.cwd);
20996
+ if (refCheck.ok !== true) {
20997
+ return {
20998
+ ok: false,
20999
+ error: refCheck.error,
21000
+ status: refCheck.status
21001
+ };
21002
+ }
21003
+ }
21004
+ if (!req.query.trim()) {
21005
+ return {
21006
+ ok: true,
21007
+ value: {
21008
+ ref: req.ref,
21009
+ engine: req.ref === "worktree" ? "fallback" : "git",
21010
+ truncated: false,
21011
+ matches: []
21012
+ }
21013
+ };
21014
+ }
21015
+ if (isWorktree) {
21016
+ return { ok: true, value: await grepWorktreeAsync(env, req) };
21017
+ }
21018
+ return { ok: true, value: await grepTreeRefAsync(env, req) };
21019
+ }
20803
21020
  function listRepoFiles(env, ref, generation) {
20804
21021
  if (ref !== "worktree" && ref !== "") {
20805
21022
  const refCheck = verifyTreeRefResult(ref, env.cwd);
@@ -20829,6 +21046,7 @@ function listRepoFiles(env, ref, generation) {
20829
21046
  var rgAvailableCache = null;
20830
21047
  var init_search_service = __esm(() => {
20831
21048
  init_command_resolver();
21049
+ init_spawn_runner();
20832
21050
  init_git();
20833
21051
  init_runtime();
20834
21052
  init_search();
@@ -22391,12 +22609,12 @@ var init_state_route = __esm(() => {
22391
22609
  // web-src/server/preview.ts
22392
22610
  var exports_preview = {};
22393
22611
  import {
22394
- closeSync as closeSync2,
22612
+ closeSync as closeSync3,
22395
22613
  constants as constants3,
22396
22614
  existsSync as existsSync8,
22397
22615
  lstatSync as lstatSync5,
22398
22616
  mkdirSync as mkdirSync4,
22399
- openSync as openSync2,
22617
+ openSync as openSync3,
22400
22618
  readFileSync as readFileSync9,
22401
22619
  realpathSync as realpathSync6,
22402
22620
  renameSync,
@@ -23076,9 +23294,15 @@ function handleFiles2(url) {
23076
23294
  if (result.ok !== true)
23077
23295
  return text(result.error, result.status ?? 400);
23078
23296
  fileListCache.set(key, { generation, body: result.value });
23297
+ while (fileListCache.size > MAX_TIMED_CACHE_ENTRIES) {
23298
+ const oldest = fileListCache.keys().next().value;
23299
+ if (oldest === undefined)
23300
+ break;
23301
+ fileListCache.delete(oldest);
23302
+ }
23079
23303
  return json2(result.value);
23080
23304
  }
23081
- function handleGrep(url) {
23305
+ async function handleGrep(url) {
23082
23306
  const query = url.searchParams.get("q") || "";
23083
23307
  const ref = url.searchParams.get("ref") || "worktree";
23084
23308
  const max = normalizeGrepMax(url.searchParams.get("max"));
@@ -23090,7 +23314,7 @@ function handleGrep(url) {
23090
23314
  const excludeNames = scopeExcludeNamesFromQuery(url);
23091
23315
  const paths = url.searchParams.getAll("path");
23092
23316
  const regex = url.searchParams.get("regex") === "1";
23093
- const result = grepRepo(currentSearchEnv(omitDirNames, excludeNames), {
23317
+ const result = await grepRepoAsync(currentSearchEnv(omitDirNames, excludeNames), {
23094
23318
  query,
23095
23319
  ref,
23096
23320
  paths,
@@ -23269,16 +23493,16 @@ function handleFileDiff(url) {
23269
23493
  if (isUntracked) {
23270
23494
  const res = untrackedFileDiff(extras, path, cwd);
23271
23495
  diffText = res.stdout || "";
23272
- if (res.code !== 0) {
23273
- errText = res.stderr;
23274
- errStatus = res.status;
23496
+ if (res.code !== 0 && !(res.code === 1 && diffText)) {
23497
+ errText = res.stderr || "diff failed";
23498
+ errStatus = res.status ?? 500;
23275
23499
  }
23276
23500
  } else {
23277
23501
  const res = fileDiffText([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
23278
23502
  diffText = res.stdout || "";
23279
23503
  if (res.code !== 0) {
23280
- errText = res.stderr;
23281
- errStatus = res.status;
23504
+ errText = res.stderr || "diff failed";
23505
+ errStatus = res.status ?? 500;
23282
23506
  }
23283
23507
  }
23284
23508
  if (!errText)
@@ -23454,6 +23678,7 @@ async function handleFileRange(url) {
23454
23678
  const full = safeWorktreePath2(path);
23455
23679
  if (!full)
23456
23680
  return text("no file", 404);
23681
+ const responseGeneration = generation;
23457
23682
  const result = await collectIndexedWorktreeLineRange(full, start, end);
23458
23683
  const body = {
23459
23684
  path,
@@ -23463,10 +23688,11 @@ async function handleFileRange(url) {
23463
23688
  lines: result.lines,
23464
23689
  total: result.total,
23465
23690
  complete: result.complete,
23466
- generation
23691
+ generation: responseGeneration
23467
23692
  };
23468
23693
  return json2(body);
23469
23694
  } else {
23695
+ const responseGeneration = generation;
23470
23696
  const refCheck = verifyTreeRefResult(ref, cwd);
23471
23697
  if (refCheck.ok !== true)
23472
23698
  return text(refCheck.error, refCheck.status ?? 400);
@@ -23487,34 +23713,64 @@ async function handleFileRange(url) {
23487
23713
  lines: result.lines,
23488
23714
  total: result.total,
23489
23715
  complete: result.complete,
23490
- generation
23716
+ generation: responseGeneration
23491
23717
  };
23492
23718
  return json2(body);
23493
23719
  }
23494
23720
  }
23495
- function handleRawFile(req, url) {
23721
+ async function handleRawFile(req, url) {
23496
23722
  const path = url.searchParams.get("path") || "";
23497
23723
  if (!safePath(path))
23498
23724
  return text("forbidden", 403);
23499
23725
  const ref = url.searchParams.get("ref") || "worktree";
23500
- let body;
23501
23726
  if (ref !== "worktree" && ref !== "") {
23502
23727
  const refCheck = verifyTreeRefResult(ref, cwd);
23503
23728
  if (refCheck.ok !== true)
23504
23729
  return text(refCheck.error, refCheck.status ?? 400);
23505
- const size = rawFileSize(path, ref);
23506
- if (size == null)
23730
+ const oid = objectId(ref, path, cwd);
23731
+ if (oid.code !== 0 || !oid.oid)
23507
23732
  return text("not in ref", 404);
23733
+ const sizeResult = objectByteSize(oid.oid, cwd);
23734
+ if (sizeResult.code !== 0)
23735
+ return text("cannot read ref", 500);
23736
+ const size = sizeResult.size;
23508
23737
  const metadata = gitFileMetadata(ref, path, size);
23738
+ const rangeResult = req.headers.get("range") ? parseHttpByteRange(req.headers.get("range"), size) : null;
23739
+ if (rangeResult?.kind === "unsatisfiable") {
23740
+ return new Response(null, {
23741
+ status: 416,
23742
+ headers: {
23743
+ ...rawFileHeaders(path, { size, metadata }),
23744
+ "Content-Range": `bytes */${size}`,
23745
+ "Content-Length": "0"
23746
+ }
23747
+ });
23748
+ }
23749
+ if (rangeResult?.kind === "range") {
23750
+ const range = rangeResult.range;
23751
+ if (req.method === "HEAD") {
23752
+ return new Response(null, {
23753
+ status: 206,
23754
+ headers: rawFileHeaders(path, { size, range, metadata })
23755
+ });
23756
+ }
23757
+ const shown2 = catFileBlobStream(oid.oid, cwd);
23758
+ const bytes = await collectByteRangeFromStream(shown2.stream, range.start, range.end + 1);
23759
+ const code = await shown2.exited;
23760
+ if (code !== 0)
23761
+ return text("not in ref", 404);
23762
+ const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
23763
+ return new Response(body, {
23764
+ status: 206,
23765
+ headers: rawFileHeaders(path, { size, range, metadata })
23766
+ });
23767
+ }
23509
23768
  if (req.method === "HEAD")
23510
23769
  return new Response(null, {
23511
23770
  headers: rawFileHeaders(path, { size, metadata })
23512
23771
  });
23513
- const res = showBytes(ref, path, cwd);
23514
- if (res.code !== 0)
23515
- return text("not in ref", 404);
23516
- body = res.stdout.buffer.slice(res.stdout.byteOffset, res.stdout.byteOffset + res.stdout.byteLength);
23517
- return new Response(body, {
23772
+ const shown = catFileBlobStream(oid.oid, cwd);
23773
+ return new Response(shown.stream, {
23518
23774
  headers: rawFileHeaders(path, { size, metadata })
23519
23775
  });
23520
23776
  } else {
@@ -23664,11 +23920,11 @@ async function handleUploadFiles(req) {
23664
23920
  const written = [];
23665
23921
  try {
23666
23922
  for (const upload of uploads) {
23667
- const fd = openSync2(upload.target, uploadOpenFlags(), 420);
23923
+ const fd = openSync3(upload.target, uploadOpenFlags(), 420);
23668
23924
  try {
23669
23925
  writeFileSync2(fd, new Uint8Array(await upload.file.arrayBuffer()));
23670
23926
  } finally {
23671
- closeSync2(fd);
23927
+ closeSync3(fd);
23672
23928
  }
23673
23929
  written.push(upload.target);
23674
23930
  }
@@ -24666,7 +24922,7 @@ var init_preview = __esm(async () => {
24666
24922
  if (url.pathname === "/_files")
24667
24923
  return handleFiles2(url);
24668
24924
  if (url.pathname === "/_grep")
24669
- return handleGrep(url);
24925
+ return await handleGrep(url);
24670
24926
  if (url.pathname === "/_commits")
24671
24927
  return handleRefCommits(url);
24672
24928
  if (url.pathname === "/_log")
@@ -24678,7 +24934,7 @@ var init_preview = __esm(async () => {
24678
24934
  if (url.pathname === "/file_range")
24679
24935
  return handleFileRange(url);
24680
24936
  if (url.pathname === "/_file")
24681
- return handleRawFile(req, url);
24937
+ return await handleRawFile(req, url);
24682
24938
  if (url.pathname === "/_open_path")
24683
24939
  return handleOpenPath(req);
24684
24940
  if (url.pathname === "/_trash_path")