@youtyan/code-viewer 0.6.8 → 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)
2058
2072
  return { header: diffText, hunks: [] };
2059
- const header = diffText.slice(0, first);
2073
+ const hunkStart = startsWithHunk ? 0 : first + 1;
2074
+ if (hunkStart >= diffText.length)
2075
+ return { header: diffText, hunks: [] };
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];
@@ -9612,7 +9714,7 @@ var init_spawn_runner = () => {};
9612
9714
  // web-src/server/database/adapters/docker-utils.ts
9613
9715
  import { spawnSync as spawnSync2 } from "node:child_process";
9614
9716
  function isDockerComposeServiceUnavailableError(err) {
9615
- return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError;
9717
+ return err instanceof DockerComposeServiceUnavailableError || err instanceof DockerCommandUnavailableError || err instanceof SupabaseDbContainerUnavailableError;
9616
9718
  }
9617
9719
  function dockerCommand() {
9618
9720
  return commandForExternal("docker");
@@ -9629,10 +9731,15 @@ function throwIfCachedComposeDockerCommandUnavailable(cwd) {
9629
9731
  const failure = composePsCache.get(cwd)?.error || "";
9630
9732
  throwIfDockerCommandUnavailableResult({ code: 1, stderr: failure });
9631
9733
  }
9632
- function parseComposePsOutput(stdout) {
9734
+ function parseDockerJsonLines(stdout) {
9633
9735
  const output = stdout.trim();
9634
- const containers = output.startsWith("[") ? JSON.parse(output) : output.split(`
9736
+ if (!output)
9737
+ return [];
9738
+ return output.startsWith("[") ? JSON.parse(output) : output.split(`
9635
9739
  `).filter(Boolean).map((line) => JSON.parse(line));
9740
+ }
9741
+ function parseComposePsOutput(stdout) {
9742
+ const containers = parseDockerJsonLines(stdout);
9636
9743
  const byService = new Map;
9637
9744
  for (const container of containers) {
9638
9745
  if (container.Service && container.Name && container.State === "running") {
@@ -9651,15 +9758,14 @@ function cacheComposePsFailure(cwd, result, now) {
9651
9758
  error: stderr
9652
9759
  });
9653
9760
  }
9654
- function runComposePsAsync(cwd, signal) {
9655
- throwIfAborted(signal, "docker compose ps aborted");
9761
+ function runDockerCliAsync(args, opts) {
9762
+ throwIfAborted(opts.signal, opts.abortMessage);
9656
9763
  if (spawnSyncImpl !== spawnSync2) {
9657
- const command = dockerCommand();
9658
- const proc = spawnSyncImpl(command, ["compose", "ps", "--format", "json", "--status", "running"], {
9764
+ const proc = spawnSyncImpl(dockerCommand(), args, {
9659
9765
  encoding: "utf8",
9660
9766
  timeout: 5000,
9661
9767
  stdio: ["ignore", "pipe", "pipe"],
9662
- cwd
9768
+ ...opts.cwd ? { cwd: opts.cwd } : {}
9663
9769
  });
9664
9770
  return Promise.resolve({
9665
9771
  stdout: String(proc.stdout || ""),
@@ -9670,16 +9776,24 @@ ${proc.error.message}` : ""}`,
9670
9776
  }
9671
9777
  return spawnTextAsync({
9672
9778
  command: dockerCommand(),
9673
- args: ["compose", "ps", "--format", "json", "--status", "running"],
9674
- cwd,
9779
+ args,
9780
+ ...opts.cwd ? { cwd: opts.cwd } : {},
9675
9781
  timeoutMs: 5000,
9676
- signal,
9782
+ signal: opts.signal,
9677
9783
  killSignal: "SIGKILL",
9678
- abortMessage: "docker compose ps aborted",
9679
- timeoutMessage: "docker compose ps timed out",
9784
+ abortMessage: opts.abortMessage,
9785
+ timeoutMessage: opts.timeoutMessage,
9680
9786
  rejectOnError: false
9681
9787
  });
9682
9788
  }
9789
+ function runComposePsAsync(cwd, signal) {
9790
+ return runDockerCliAsync(["compose", "ps", "--format", "json", "--status", "running"], {
9791
+ cwd,
9792
+ signal,
9793
+ abortMessage: "docker compose ps aborted",
9794
+ timeoutMessage: "docker compose ps timed out"
9795
+ });
9796
+ }
9683
9797
  async function resolveRunningComposeContainerNameAsync(serviceName, cwd, signal) {
9684
9798
  throwIfAborted(signal, "docker compose ps aborted");
9685
9799
  const now = Date.now();
@@ -9777,7 +9891,128 @@ async function resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd,
9777
9891
  }
9778
9892
  return containerName;
9779
9893
  }
9780
- var COMPOSE_CONTAINER_NAME_POSITIVE_TTL_MS = 30000, COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS = 3000, COMPOSE_PS_FAILURE_TTL_MS = 15000, composePsCache, composePsPending, spawnSyncImpl, DockerComposeServiceUnavailableError, DockerCommandUnavailableError;
9894
+ function supabaseDbContainerName(projectId) {
9895
+ return `supabase_db_${projectId}`;
9896
+ }
9897
+ function parseDockerPsOutput(stdout) {
9898
+ return parseDockerJsonLines(stdout);
9899
+ }
9900
+ function runDockerPsAsync(args, signal) {
9901
+ return runDockerCliAsync(args, {
9902
+ signal,
9903
+ abortMessage: "docker ps aborted",
9904
+ timeoutMessage: "docker ps timed out"
9905
+ });
9906
+ }
9907
+ async function resolveRunningSupabaseDbContainerAsync(projectId, signal) {
9908
+ throwIfAborted(signal, "docker ps aborted");
9909
+ const containerName = supabaseDbContainerName(projectId);
9910
+ const now = Date.now();
9911
+ const cached = supabaseContainerCache.get(projectId);
9912
+ if (cached) {
9913
+ if (cached.containerName && cached.positiveExpiresAt > now) {
9914
+ return cached.containerName;
9915
+ }
9916
+ if (!cached.containerName && cached.negativeExpiresAt > now) {
9917
+ return null;
9918
+ }
9919
+ }
9920
+ let pending = supabaseContainerPending.get(projectId);
9921
+ if (!pending) {
9922
+ const controller = new AbortController;
9923
+ pending = {
9924
+ controller,
9925
+ refs: 0,
9926
+ done: false,
9927
+ promise: (async () => {
9928
+ const startedAt = Date.now();
9929
+ const cacheMiss = (negativeTtlMs) => {
9930
+ supabaseContainerCache.set(projectId, {
9931
+ containerName: null,
9932
+ positiveExpiresAt: startedAt,
9933
+ negativeExpiresAt: startedAt + negativeTtlMs
9934
+ });
9935
+ return null;
9936
+ };
9937
+ let proc;
9938
+ try {
9939
+ proc = await runDockerPsAsync([
9940
+ "ps",
9941
+ "--filter",
9942
+ `name=^/${containerName}$`,
9943
+ "--filter",
9944
+ `label=com.supabase.cli.project=${projectId}`,
9945
+ "--filter",
9946
+ "status=running",
9947
+ "--format",
9948
+ "json"
9949
+ ], controller.signal);
9950
+ } catch (err) {
9951
+ if (isAbortLikeError(err, controller.signal))
9952
+ throw err;
9953
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9954
+ }
9955
+ if (proc.code !== 0) {
9956
+ throwIfDockerCommandUnavailableResult(proc);
9957
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9958
+ }
9959
+ let containers;
9960
+ try {
9961
+ containers = parseDockerPsOutput(proc.stdout);
9962
+ } catch {
9963
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9964
+ }
9965
+ const match = containers.some((c) => (c.Names || "").replace(/^\//, "") === containerName);
9966
+ if (!match)
9967
+ return cacheMiss(SUPABASE_CONTAINER_NEGATIVE_TTL_MS);
9968
+ supabaseContainerCache.set(projectId, {
9969
+ containerName,
9970
+ positiveExpiresAt: startedAt + SUPABASE_CONTAINER_POSITIVE_TTL_MS,
9971
+ negativeExpiresAt: startedAt
9972
+ });
9973
+ return containerName;
9974
+ })().finally(() => {
9975
+ if (supabaseContainerPending.get(projectId) === pending) {
9976
+ supabaseContainerPending.delete(projectId);
9977
+ }
9978
+ if (pending)
9979
+ pending.done = true;
9980
+ })
9981
+ };
9982
+ supabaseContainerPending.set(projectId, pending);
9983
+ }
9984
+ pending.refs++;
9985
+ let released = false;
9986
+ const release = () => {
9987
+ if (released)
9988
+ return;
9989
+ released = true;
9990
+ pending.refs--;
9991
+ if (pending.refs <= 0 && !pending.done) {
9992
+ pending.controller.abort();
9993
+ }
9994
+ };
9995
+ signal?.addEventListener("abort", release, { once: true });
9996
+ if (signal?.aborted) {
9997
+ signal.removeEventListener("abort", release);
9998
+ release();
9999
+ throwIfAborted(signal, "docker ps aborted");
10000
+ }
10001
+ try {
10002
+ return await pending.promise;
10003
+ } finally {
10004
+ signal?.removeEventListener("abort", release);
10005
+ release();
10006
+ }
10007
+ }
10008
+ async function resolveRunningSupabaseDbContainerOrThrowAsync(projectId, signal) {
10009
+ const containerName = await resolveRunningSupabaseDbContainerAsync(projectId, signal);
10010
+ if (!containerName) {
10011
+ throw new SupabaseDbContainerUnavailableError(projectId);
10012
+ }
10013
+ return containerName;
10014
+ }
10015
+ var COMPOSE_CONTAINER_NAME_POSITIVE_TTL_MS = 30000, COMPOSE_CONTAINER_NAME_NEGATIVE_TTL_MS = 3000, COMPOSE_PS_FAILURE_TTL_MS = 15000, composePsCache, composePsPending, spawnSyncImpl, DockerComposeServiceUnavailableError, DockerCommandUnavailableError, SupabaseDbContainerUnavailableError, SUPABASE_CONTAINER_POSITIVE_TTL_MS = 15000, SUPABASE_CONTAINER_NEGATIVE_TTL_MS = 3000, supabaseContainerCache, supabaseContainerPending;
9781
10016
  var init_docker_utils = __esm(() => {
9782
10017
  init_command_resolver();
9783
10018
  init_spawn_runner();
@@ -9802,6 +10037,17 @@ var init_docker_utils = __esm(() => {
9802
10037
  this.name = "DockerCommandUnavailableError";
9803
10038
  }
9804
10039
  };
10040
+ SupabaseDbContainerUnavailableError = class SupabaseDbContainerUnavailableError extends Error {
10041
+ projectId;
10042
+ status = 503;
10043
+ constructor(projectId) {
10044
+ super(`Supabase local DB container for project "${projectId}" is not running. Start it with: supabase start`);
10045
+ this.name = "SupabaseDbContainerUnavailableError";
10046
+ this.projectId = projectId;
10047
+ }
10048
+ };
10049
+ supabaseContainerCache = new Map;
10050
+ supabaseContainerPending = new Map;
9805
10051
  });
9806
10052
 
9807
10053
  // web-src/server/database/adapters/sql-capture.ts
@@ -10674,13 +10920,9 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
10674
10920
  throwIfCachedComposeDockerCommandUnavailable(cwd);
10675
10921
  return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
10676
10922
  }
10677
- const config = {
10678
- kind,
10679
- containerName,
10680
- user,
10681
- password,
10682
- database
10683
- };
10923
+ return fetchPostgresSchemasViaContainerAsync({ kind, containerName, user, password, database }, cacheKey, now, signal);
10924
+ }
10925
+ async function fetchPostgresSchemasViaContainerAsync(config, cacheKey, now, signal) {
10684
10926
  try {
10685
10927
  const sql = `SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema') AND schema_name NOT LIKE 'pg_toast%' AND schema_name NOT LIKE 'pg_temp_%' AND schema_name NOT LIKE 'pg_toast_temp_%' AND has_schema_privilege(schema_name, 'USAGE') ORDER BY CASE WHEN schema_name = 'public' THEN 0 ELSE 1 END, schema_name`;
10686
10928
  const result = await execInContainerAsync(config, sql, 1e4, signal);
@@ -10699,6 +10941,38 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
10699
10941
  return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
10700
10942
  }
10701
10943
  }
10944
+ function supabaseSchemasCacheKey(projectId) {
10945
+ return `supabase\x00${projectId}`;
10946
+ }
10947
+ async function listSupabaseSchemasAsync(projectId, signal) {
10948
+ const cacheKey = supabaseSchemasCacheKey(projectId);
10949
+ const now = Date.now();
10950
+ const cached = dockerSchemasCache.get(cacheKey);
10951
+ if (cached && cached.expiresAt > now)
10952
+ return [...cached.value];
10953
+ const containerName = await resolveRunningSupabaseDbContainerAsync(projectId, signal);
10954
+ if (!containerName) {
10955
+ return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
10956
+ }
10957
+ return fetchPostgresSchemasViaContainerAsync({
10958
+ kind: "postgresql",
10959
+ containerName,
10960
+ user: SUPABASE_LOCAL_DB_USER,
10961
+ password: SUPABASE_LOCAL_DB_PASSWORD,
10962
+ database: SUPABASE_LOCAL_DB_NAME
10963
+ }, cacheKey, now, signal);
10964
+ }
10965
+ async function openSupabaseDockerAdapterAsync(projectId, schema, signal) {
10966
+ const containerName = await resolveRunningSupabaseDbContainerOrThrowAsync(projectId, signal);
10967
+ return createDockerAdapter({
10968
+ kind: "postgresql",
10969
+ containerName,
10970
+ user: SUPABASE_LOCAL_DB_USER,
10971
+ password: SUPABASE_LOCAL_DB_PASSWORD,
10972
+ database: SUPABASE_LOCAL_DB_NAME,
10973
+ ...schema ? { schema } : {}
10974
+ });
10975
+ }
10702
10976
  async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatabase, schema, signal) {
10703
10977
  const containerName = await resolveRunningComposeContainerNameOrThrowAsync(serviceName, cwd, signal);
10704
10978
  const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
@@ -10713,7 +10987,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
10713
10987
  ...kind === "postgresql" && schema ? { schema } : {}
10714
10988
  });
10715
10989
  }
10716
- var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES;
10990
+ var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES, SUPABASE_LOCAL_DB_USER = "postgres", SUPABASE_LOCAL_DB_PASSWORD = "postgres", SUPABASE_LOCAL_DB_NAME = "postgres";
10717
10991
  var init_docker = __esm(() => {
10718
10992
  init_mutate();
10719
10993
  init_sql_snapshot();
@@ -13136,10 +13410,10 @@ function hasControlCharacter(value) {
13136
13410
 
13137
13411
  // web-src/server/database/discovery.ts
13138
13412
  import {
13139
- closeSync,
13413
+ closeSync as closeSync2,
13140
13414
  existsSync as existsSync6,
13141
- openSync,
13142
- readSync,
13415
+ openSync as openSync2,
13416
+ readSync as readSync2,
13143
13417
  realpathSync as realpathSync4,
13144
13418
  statSync as statSync4
13145
13419
  } from "node:fs";
@@ -13151,11 +13425,11 @@ function isSqliteFile(fullPath) {
13151
13425
  if (!stat2.isFile() || stat2.size < 16)
13152
13426
  return false;
13153
13427
  const buf = Buffer.alloc(16);
13154
- const fd = openSync(fullPath, "r");
13428
+ const fd = openSync2(fullPath, "r");
13155
13429
  try {
13156
- readSync(fd, buf, 0, 16, 0);
13430
+ readSync2(fd, buf, 0, 16, 0);
13157
13431
  } finally {
13158
- closeSync(fd);
13432
+ closeSync2(fd);
13159
13433
  }
13160
13434
  return buf.toString("utf8", 0, 16) === SQLITE_MAGIC;
13161
13435
  } catch {
@@ -13599,6 +13873,39 @@ async function pathExistsAsync(path) {
13599
13873
  return false;
13600
13874
  }
13601
13875
  }
13876
+ async function walkForMarkerFileAsync(dir, depth, omitSet, hasCapacity, visitDir, signal) {
13877
+ if (signal?.aborted || !hasCapacity())
13878
+ return;
13879
+ if (depth > MAX_SCAN_DEPTH)
13880
+ return;
13881
+ await visitDir(dir);
13882
+ if (signal?.aborted || !hasCapacity())
13883
+ return;
13884
+ let entries;
13885
+ try {
13886
+ entries = await readdir(dir);
13887
+ } catch {
13888
+ return;
13889
+ }
13890
+ for (const entry of entries) {
13891
+ if (signal?.aborted || !hasCapacity())
13892
+ return;
13893
+ if (omitSet.has(entry.toLowerCase()))
13894
+ continue;
13895
+ const full = join8(dir, entry);
13896
+ let entryStat;
13897
+ try {
13898
+ entryStat = await lstat(full);
13899
+ } catch {
13900
+ continue;
13901
+ }
13902
+ if (entryStat.isSymbolicLink())
13903
+ continue;
13904
+ if (entryStat.isDirectory()) {
13905
+ await walkForMarkerFileAsync(full, depth + 1, omitSet, hasCapacity, visitDir, signal);
13906
+ }
13907
+ }
13908
+ }
13602
13909
  async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
13603
13910
  const cacheKey = dockerDiscoveryCacheKey(cwd, omitDirNames);
13604
13911
  const now = Date.now();
@@ -13610,13 +13917,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
13610
13917
  const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
13611
13918
  omitSet.add(".git");
13612
13919
  omitSet.add("node_modules");
13613
- async function scan(dir, depth) {
13614
- if (signal?.aborted)
13615
- return;
13616
- if (results.length >= MAX_DOCKER_SERVICES)
13617
- return;
13618
- if (depth > MAX_SCAN_DEPTH)
13619
- return;
13920
+ await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_DOCKER_SERVICES, async (dir) => {
13620
13921
  for (const filename of COMPOSE_FILENAMES) {
13621
13922
  const filepath = join8(dir, filename);
13622
13923
  if (await pathExistsAsync(filepath)) {
@@ -13624,37 +13925,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
13624
13925
  break;
13625
13926
  }
13626
13927
  }
13627
- if (signal?.aborted)
13628
- return;
13629
- if (results.length >= MAX_DOCKER_SERVICES)
13630
- return;
13631
- let entries;
13632
- try {
13633
- entries = await readdir(dir);
13634
- } catch {
13635
- return;
13636
- }
13637
- for (const entry of entries) {
13638
- if (signal?.aborted)
13639
- return;
13640
- if (results.length >= MAX_DOCKER_SERVICES)
13641
- return;
13642
- if (omitSet.has(entry.toLowerCase()))
13643
- continue;
13644
- const full = join8(dir, entry);
13645
- let entryStat;
13646
- try {
13647
- entryStat = await lstat(full);
13648
- } catch {
13649
- continue;
13650
- }
13651
- if (entryStat.isSymbolicLink())
13652
- continue;
13653
- if (entryStat.isDirectory())
13654
- await scan(full, depth + 1);
13655
- }
13656
- }
13657
- await scan(cwd, 0);
13928
+ }, signal);
13658
13929
  if (signal?.aborted)
13659
13930
  return cloneDockerDiscoveryResult(results);
13660
13931
  if (results.length >= MAX_DOCKER_SERVICES) {
@@ -13749,7 +14020,120 @@ function isSafeDockerDatabaseName(value) {
13749
14020
  return false;
13750
14021
  return /^[A-Za-z0-9_$.-]+$/.test(value);
13751
14022
  }
13752
- 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;
14023
+ function cloneSupabaseDiscoveryResult(result) {
14024
+ return result.map((entry) => ({ ...entry }));
14025
+ }
14026
+ function parseSupabaseConfigToml(content) {
14027
+ let section = null;
14028
+ let projectId = null;
14029
+ let dbPort = null;
14030
+ for (const rawLine of content.split(`
14031
+ `)) {
14032
+ const line = rawLine.trim();
14033
+ if (!line || line.startsWith("#"))
14034
+ continue;
14035
+ const sectionMatch = line.match(/^\[([^\]]+)\]$/);
14036
+ if (sectionMatch) {
14037
+ section = sectionMatch[1];
14038
+ continue;
14039
+ }
14040
+ const kvMatch = line.match(/^([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.+)$/);
14041
+ if (!kvMatch)
14042
+ continue;
14043
+ const value = stripScalarSyntax(kvMatch[2]);
14044
+ if (section === null && kvMatch[1] === "project_id") {
14045
+ projectId = value;
14046
+ } else if (section === "db" && kvMatch[1] === "port") {
14047
+ dbPort = value;
14048
+ }
14049
+ }
14050
+ if (!projectId || !isSafeDockerServiceName(projectId))
14051
+ return null;
14052
+ return {
14053
+ projectId,
14054
+ dbPort: dbPort && /^\d+$/.test(dbPort) ? dbPort : DEFAULT_SUPABASE_DB_PORT
14055
+ };
14056
+ }
14057
+ async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal) {
14058
+ const cacheKey = discoveryCacheKey(cwd, omitDirNames);
14059
+ const now = Date.now();
14060
+ const cached = supabaseDiscoveryCache.get(cacheKey);
14061
+ if (cached && cached.expiresAt > now) {
14062
+ return cloneSupabaseDiscoveryResult(cached.result);
14063
+ }
14064
+ const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
14065
+ omitSet.add(".git");
14066
+ omitSet.add("node_modules");
14067
+ const results = [];
14068
+ await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_SUPABASE_PROJECTS, async (dir) => {
14069
+ const configPath = join8(dir, "supabase", "config.toml");
14070
+ if (!await pathExistsAsync(configPath))
14071
+ return;
14072
+ try {
14073
+ const content = await readFile2(configPath, "utf-8");
14074
+ const parsed = parseSupabaseConfigToml(content);
14075
+ if (!parsed)
14076
+ return;
14077
+ const relDir = relative3(cwd, dir);
14078
+ const isRoot = relDir === "" || relDir === ".";
14079
+ const relDirSlash = relDir.replace(/\\/g, "/");
14080
+ const id = isRoot ? `supabase:${parsed.projectId}` : `supabase:${parsed.projectId}@${encodeURIComponent(relDirSlash)}`;
14081
+ const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
14082
+ results.push({
14083
+ id,
14084
+ path: isRoot ? "supabase/config.toml" : `${relDirSlash}/supabase/config.toml`,
14085
+ name: `${parsed.projectId} (Supabase CLI, postgres@127.0.0.1:${parsed.dbPort}/postgres${labelPath})`,
14086
+ sizeBytes: 0,
14087
+ kind: "postgresql",
14088
+ projectId: parsed.projectId,
14089
+ relDirSlash,
14090
+ dbPort: parsed.dbPort
14091
+ });
14092
+ } catch {}
14093
+ }, signal);
14094
+ if (signal?.aborted)
14095
+ return cloneSupabaseDiscoveryResult(results);
14096
+ supabaseDiscoveryCache.set(cacheKey, {
14097
+ expiresAt: now + SUPABASE_DISCOVERY_TTL_MS,
14098
+ result: cloneSupabaseDiscoveryResult(results)
14099
+ });
14100
+ return cloneSupabaseDiscoveryResult(results);
14101
+ }
14102
+ function parseSupabaseDbId(dbId) {
14103
+ if (!dbId.startsWith("supabase:"))
14104
+ return null;
14105
+ const rest = dbId.slice("supabase:".length);
14106
+ if (!rest)
14107
+ return null;
14108
+ const atIdx = rest.indexOf("@");
14109
+ let projectId;
14110
+ let relDir = "";
14111
+ if (atIdx >= 0) {
14112
+ if (rest.indexOf("@", atIdx + 1) >= 0)
14113
+ return null;
14114
+ projectId = rest.slice(0, atIdx);
14115
+ try {
14116
+ relDir = decodeURIComponent(rest.slice(atIdx + 1));
14117
+ } catch {
14118
+ return null;
14119
+ }
14120
+ if (!isSafeDockerRelDir(relDir))
14121
+ return null;
14122
+ } else {
14123
+ projectId = rest;
14124
+ }
14125
+ if (!isSafeDockerServiceName(projectId))
14126
+ return null;
14127
+ return { projectId, relDir };
14128
+ }
14129
+ async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal) {
14130
+ const parsed = parseSupabaseDbId(dbId);
14131
+ if (!parsed)
14132
+ return null;
14133
+ const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
14134
+ return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
14135
+ }
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;
13753
14137
  var init_discovery = __esm(() => {
13754
14138
  SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
13755
14139
  sqliteDiscoveryCache = new Map;
@@ -13760,6 +14144,7 @@ var init_discovery = __esm(() => {
13760
14144
  "compose.yaml"
13761
14145
  ];
13762
14146
  dockerDiscoveryCache = new Map;
14147
+ supabaseDiscoveryCache = new Map;
13763
14148
  });
13764
14149
 
13765
14150
  // web-src/core/id.ts
@@ -16754,8 +17139,9 @@ function sanitizeCssSize(v) {
16754
17139
  return isValidCssSize(v) ? v : undefined;
16755
17140
  }
16756
17141
  function isToolInternalDbId(dbId) {
16757
- if (!dbId || dbId.startsWith("docker:"))
17142
+ if (!dbId || dbId.startsWith("docker:") || dbId.startsWith("supabase:")) {
16758
17143
  return false;
17144
+ }
16759
17145
  return dbId.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
16760
17146
  }
16761
17147
  function sanitizeRedis(v) {
@@ -16934,6 +17320,11 @@ async function getAdapter(r, _cwd, signal) {
16934
17320
  const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
16935
17321
  return dockerAdapterCache.getOrOpenAsync(cacheKey, () => openDockerAdapterAsync(docker.serviceName, docker.kind, docker.env, docker.composeDir, docker.database, r.schema, signal));
16936
17322
  }
17323
+ if (r.supabase) {
17324
+ const projectId = r.supabase.projectId;
17325
+ const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
17326
+ return dockerAdapterCache.getOrOpenAsync(cacheKey, () => openSupabaseDockerAdapterAsync(projectId, r.schema, signal));
17327
+ }
16937
17328
  return getConnection(r.resolved);
16938
17329
  }
16939
17330
  function sanitizeFilename(name) {
@@ -16960,9 +17351,35 @@ async function resolvePostgresSchema(info, requestedSchema, signal) {
16960
17351
  return "public";
16961
17352
  return schemas[0] || "public";
16962
17353
  }
17354
+ async function resolveSupabaseSchema(info, requestedSchema, signal) {
17355
+ if (requestedSchema)
17356
+ return requestedSchema;
17357
+ const schemas = await listSupabaseSchemasAsync(info.projectId, signal);
17358
+ if (schemas.includes("public"))
17359
+ return "public";
17360
+ return schemas[0] || "public";
17361
+ }
16963
17362
  async function resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal) {
16964
17363
  if (!dbParam)
16965
17364
  return textError("missing db parameter", 400);
17365
+ if (dbParam.startsWith("supabase:")) {
17366
+ const parsed = parseSupabaseDbId(dbParam);
17367
+ if (!parsed)
17368
+ return textError("invalid supabase db id", 400);
17369
+ const info = await findSupabaseCliProjectByDbIdAsync(cwd, dbParam, omitDirNames, signal);
17370
+ if (!info)
17371
+ return textError("supabase project not found", 404);
17372
+ const requestedSchema = normalizeSchemaParam(schemaParam);
17373
+ if (requestedSchema instanceof Response)
17374
+ return requestedSchema;
17375
+ const schema = await resolveSupabaseSchema(info, requestedSchema, signal);
17376
+ return {
17377
+ resolved: dbParam,
17378
+ dbId: dbParam,
17379
+ supabase: info,
17380
+ ...schema ? { schema } : {}
17381
+ };
17382
+ }
16966
17383
  if (dbParam.startsWith("docker:")) {
16967
17384
  const parsed = parseDockerDbId(dbParam);
16968
17385
  if (!parsed)
@@ -17049,17 +17466,22 @@ async function expandDockerServicesForFiles(dockerServices, listDockerDatabases,
17049
17466
  }
17050
17467
  async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_DB_FILE_DISCOVERY_DEPS) {
17051
17468
  ensureInit();
17052
- const [sqliteSettled, dockerSettled] = await Promise.allSettled([
17469
+ const [sqliteSettled, dockerSettled, supabaseSettled] = await Promise.allSettled([
17053
17470
  deps.discoverSqliteFiles(cwd, omitDirNames, signal),
17054
- deps.discoverDockerDatabases(cwd, omitDirNames, signal)
17471
+ deps.discoverDockerDatabases(cwd, omitDirNames, signal),
17472
+ deps.discoverSupabaseCliProjects(cwd, omitDirNames, signal)
17055
17473
  ]);
17056
17474
  if (sqliteSettled.status === "rejected") {
17057
17475
  throw sqliteSettled.reason;
17058
17476
  }
17477
+ if (supabaseSettled.status === "rejected") {
17478
+ throw supabaseSettled.reason;
17479
+ }
17059
17480
  if (dockerSettled.status === "rejected" && isAbortLikeError(dockerSettled.reason, signal)) {
17060
17481
  throw dockerSettled.reason;
17061
17482
  }
17062
17483
  const sqliteFiles = sqliteSettled.value;
17484
+ const supabaseProjects = supabaseSettled.value;
17063
17485
  const dockerServices = dockerSettled.status === "fulfilled" ? dockerSettled.value : [];
17064
17486
  const dockerErrors = [];
17065
17487
  if (dockerSettled.status === "rejected") {
@@ -17077,7 +17499,8 @@ async function createDbFilesResponse(cwd, omitDirNames, signal, deps = DEFAULT_D
17077
17499
  sizeBytes: f.sizeBytes,
17078
17500
  kind: "sqlite"
17079
17501
  })),
17080
- ...dockerEntries.map(toFileInfo)
17502
+ ...dockerEntries.map(toFileInfo),
17503
+ ...supabaseProjects.map(toFileInfo)
17081
17504
  ],
17082
17505
  ...dockerTruncated ? { truncated: true } : {},
17083
17506
  ...dockerErrors.length > 0 ? { dockerError: dockerErrors.join("; ") } : {}
@@ -17091,6 +17514,17 @@ async function createDbSchemasResponse(cwd, dbParam, schemaParam, omitDirNames,
17091
17514
  const r = await resolveDb(cwd, dbParam, omitDirNames, schemaParam, signal);
17092
17515
  if (r instanceof Response)
17093
17516
  return { ok: false, response: r };
17517
+ if (r.supabase) {
17518
+ const projectId = r.supabase.projectId;
17519
+ const { result: schemas2, executedSql: executedSql2 } = await captureSql(() => listSupabaseSchemasAsync(projectId, signal));
17520
+ const body2 = {
17521
+ dbId: r.dbId,
17522
+ schemas: schemas2.map((name) => ({ name })),
17523
+ selectedSchema: r.schema,
17524
+ executedSql: executedSql2
17525
+ };
17526
+ return { ok: true, value: body2 };
17527
+ }
17094
17528
  if (!r.docker || r.docker.kind !== "postgresql") {
17095
17529
  const body2 = { dbId: r.dbId, schemas: [] };
17096
17530
  return { ok: true, value: body2 };
@@ -18132,7 +18566,7 @@ async function handleClose(cwd, req, omitDirNames) {
18132
18566
  const r = await resolveDb(cwd, body.db, omitDirNames, undefined, req.signal);
18133
18567
  if (r instanceof Response)
18134
18568
  return r;
18135
- if (r.docker) {
18569
+ if (r.docker || r.supabase) {
18136
18570
  dockerAdapterCache.close(r.dbId);
18137
18571
  dockerAdapterCache.closePrefix(`${r.dbId}\x00`);
18138
18572
  } else {
@@ -18347,7 +18781,8 @@ var init_handle = __esm(() => {
18347
18781
  DEFAULT_DB_FILE_DISCOVERY_DEPS = {
18348
18782
  discoverSqliteFiles: discoverSqliteFilesAsync,
18349
18783
  discoverDockerDatabases: discoverDockerDatabasesAsync,
18350
- listDockerDatabases: listDockerDatabasesAsync
18784
+ listDockerDatabases: listDockerDatabasesAsync,
18785
+ discoverSupabaseCliProjects: discoverSupabaseCliProjectsAsync
18351
18786
  };
18352
18787
  searchJobs = new Map;
18353
18788
  snapshotJobs = new Map;
@@ -19085,6 +19520,9 @@ async function checkDiscovery(cwd, scopeOmitDirNames, signal) {
19085
19520
  };
19086
19521
  }
19087
19522
  async function defaultDatastoreProbe(file, cwd, signal) {
19523
+ if (file.id.startsWith("supabase:")) {
19524
+ return probeSupabaseSource(file, cwd, signal);
19525
+ }
19088
19526
  switch (file.kind) {
19089
19527
  case "sqlite":
19090
19528
  return probeSqliteSource(file, cwd, signal);
@@ -19135,6 +19573,23 @@ async function probeDockerSqlSource(file, cwd, signal) {
19135
19573
  } catch {}
19136
19574
  }
19137
19575
  }
19576
+ async function probeSupabaseSource(file, cwd, signal) {
19577
+ const parsed = parseSupabaseDbId(file.id);
19578
+ if (!parsed)
19579
+ throw new Error("invalid supabase db id");
19580
+ const info = await findSupabaseCliProjectByDbIdAsync(cwd, file.id, undefined, signal);
19581
+ if (!info)
19582
+ throw new Error("supabase project not found");
19583
+ const adapter = await openSupabaseDockerAdapterAsync(info.projectId, undefined, signal);
19584
+ try {
19585
+ signal.throwIfAborted();
19586
+ await adapter.getTablesAsync(signal);
19587
+ } finally {
19588
+ try {
19589
+ adapter.close();
19590
+ } catch {}
19591
+ }
19592
+ }
19138
19593
  async function probeRedisSource(file, cwd, signal) {
19139
19594
  const info = await findDockerServiceByDbIdAsync(cwd, file.id, "redis", undefined, signal);
19140
19595
  if (!info)
@@ -20290,6 +20745,21 @@ function rgAvailable(cwd) {
20290
20745
  rgAvailableCache = proc.code === 0;
20291
20746
  return rgAvailableCache;
20292
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
+ }
20293
20763
  function isExcludedScopePath(path, excludeNames) {
20294
20764
  return path.split(/[\\/]+/).some((part) => excludeNames.some((name) => part.toLowerCase() === name.toLowerCase()));
20295
20765
  }
@@ -20391,6 +20861,44 @@ function grepWorktree(env, req) {
20391
20861
  matches
20392
20862
  };
20393
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
+ }
20394
20902
  function grepTreeRef(env, req) {
20395
20903
  const safePaths = filterCallerPaths(env, req.paths);
20396
20904
  const args = [
@@ -20419,6 +20927,40 @@ function grepTreeRef(env, req) {
20419
20927
  matches
20420
20928
  };
20421
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
+ }
20422
20964
  function grepRepo(env, req) {
20423
20965
  const isWorktree = req.ref === "worktree" || req.ref === "";
20424
20966
  if (!isWorktree) {
@@ -20447,6 +20989,34 @@ function grepRepo(env, req) {
20447
20989
  }
20448
20990
  return { ok: true, value: grepTreeRef(env, req) };
20449
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
+ }
20450
21020
  function listRepoFiles(env, ref, generation) {
20451
21021
  if (ref !== "worktree" && ref !== "") {
20452
21022
  const refCheck = verifyTreeRefResult(ref, env.cwd);
@@ -20476,6 +21046,7 @@ function listRepoFiles(env, ref, generation) {
20476
21046
  var rgAvailableCache = null;
20477
21047
  var init_search_service = __esm(() => {
20478
21048
  init_command_resolver();
21049
+ init_spawn_runner();
20479
21050
  init_git();
20480
21051
  init_runtime();
20481
21052
  init_search();
@@ -21734,7 +22305,7 @@ function validateMcpOptionalDbId(raw) {
21734
22305
  const parsed = validateMcpDbId(raw);
21735
22306
  if (parsed.ok !== true)
21736
22307
  return parsed;
21737
- if (!parsed.value.startsWith("docker:")) {
22308
+ if (!parsed.value.startsWith("docker:") && !parsed.value.startsWith("supabase:")) {
21738
22309
  const pathError = validateMcpPath(parsed.value);
21739
22310
  if (pathError)
21740
22311
  return { ok: false, error: "invalid database path" };
@@ -22038,12 +22609,12 @@ var init_state_route = __esm(() => {
22038
22609
  // web-src/server/preview.ts
22039
22610
  var exports_preview = {};
22040
22611
  import {
22041
- closeSync as closeSync2,
22612
+ closeSync as closeSync3,
22042
22613
  constants as constants3,
22043
22614
  existsSync as existsSync8,
22044
22615
  lstatSync as lstatSync5,
22045
22616
  mkdirSync as mkdirSync4,
22046
- openSync as openSync2,
22617
+ openSync as openSync3,
22047
22618
  readFileSync as readFileSync9,
22048
22619
  realpathSync as realpathSync6,
22049
22620
  renameSync,
@@ -22723,9 +23294,15 @@ function handleFiles2(url) {
22723
23294
  if (result.ok !== true)
22724
23295
  return text(result.error, result.status ?? 400);
22725
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
+ }
22726
23303
  return json2(result.value);
22727
23304
  }
22728
- function handleGrep(url) {
23305
+ async function handleGrep(url) {
22729
23306
  const query = url.searchParams.get("q") || "";
22730
23307
  const ref = url.searchParams.get("ref") || "worktree";
22731
23308
  const max = normalizeGrepMax(url.searchParams.get("max"));
@@ -22737,7 +23314,7 @@ function handleGrep(url) {
22737
23314
  const excludeNames = scopeExcludeNamesFromQuery(url);
22738
23315
  const paths = url.searchParams.getAll("path");
22739
23316
  const regex = url.searchParams.get("regex") === "1";
22740
- const result = grepRepo(currentSearchEnv(omitDirNames, excludeNames), {
23317
+ const result = await grepRepoAsync(currentSearchEnv(omitDirNames, excludeNames), {
22741
23318
  query,
22742
23319
  ref,
22743
23320
  paths,
@@ -22916,16 +23493,16 @@ function handleFileDiff(url) {
22916
23493
  if (isUntracked) {
22917
23494
  const res = untrackedFileDiff(extras, path, cwd);
22918
23495
  diffText = res.stdout || "";
22919
- if (res.code !== 0) {
22920
- errText = res.stderr;
22921
- errStatus = res.status;
23496
+ if (res.code !== 0 && !(res.code === 1 && diffText)) {
23497
+ errText = res.stderr || "diff failed";
23498
+ errStatus = res.status ?? 500;
22922
23499
  }
22923
23500
  } else {
22924
23501
  const res = fileDiffText([...extras, ...args], oldPath ? [oldPath, path] : path, cwd);
22925
23502
  diffText = res.stdout || "";
22926
23503
  if (res.code !== 0) {
22927
- errText = res.stderr;
22928
- errStatus = res.status;
23504
+ errText = res.stderr || "diff failed";
23505
+ errStatus = res.status ?? 500;
22929
23506
  }
22930
23507
  }
22931
23508
  if (!errText)
@@ -23101,6 +23678,7 @@ async function handleFileRange(url) {
23101
23678
  const full = safeWorktreePath2(path);
23102
23679
  if (!full)
23103
23680
  return text("no file", 404);
23681
+ const responseGeneration = generation;
23104
23682
  const result = await collectIndexedWorktreeLineRange(full, start, end);
23105
23683
  const body = {
23106
23684
  path,
@@ -23110,10 +23688,11 @@ async function handleFileRange(url) {
23110
23688
  lines: result.lines,
23111
23689
  total: result.total,
23112
23690
  complete: result.complete,
23113
- generation
23691
+ generation: responseGeneration
23114
23692
  };
23115
23693
  return json2(body);
23116
23694
  } else {
23695
+ const responseGeneration = generation;
23117
23696
  const refCheck = verifyTreeRefResult(ref, cwd);
23118
23697
  if (refCheck.ok !== true)
23119
23698
  return text(refCheck.error, refCheck.status ?? 400);
@@ -23134,34 +23713,64 @@ async function handleFileRange(url) {
23134
23713
  lines: result.lines,
23135
23714
  total: result.total,
23136
23715
  complete: result.complete,
23137
- generation
23716
+ generation: responseGeneration
23138
23717
  };
23139
23718
  return json2(body);
23140
23719
  }
23141
23720
  }
23142
- function handleRawFile(req, url) {
23721
+ async function handleRawFile(req, url) {
23143
23722
  const path = url.searchParams.get("path") || "";
23144
23723
  if (!safePath(path))
23145
23724
  return text("forbidden", 403);
23146
23725
  const ref = url.searchParams.get("ref") || "worktree";
23147
- let body;
23148
23726
  if (ref !== "worktree" && ref !== "") {
23149
23727
  const refCheck = verifyTreeRefResult(ref, cwd);
23150
23728
  if (refCheck.ok !== true)
23151
23729
  return text(refCheck.error, refCheck.status ?? 400);
23152
- const size = rawFileSize(path, ref);
23153
- if (size == null)
23730
+ const oid = objectId(ref, path, cwd);
23731
+ if (oid.code !== 0 || !oid.oid)
23154
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;
23155
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
+ }
23156
23768
  if (req.method === "HEAD")
23157
23769
  return new Response(null, {
23158
23770
  headers: rawFileHeaders(path, { size, metadata })
23159
23771
  });
23160
- const res = showBytes(ref, path, cwd);
23161
- if (res.code !== 0)
23162
- return text("not in ref", 404);
23163
- body = res.stdout.buffer.slice(res.stdout.byteOffset, res.stdout.byteOffset + res.stdout.byteLength);
23164
- return new Response(body, {
23772
+ const shown = catFileBlobStream(oid.oid, cwd);
23773
+ return new Response(shown.stream, {
23165
23774
  headers: rawFileHeaders(path, { size, metadata })
23166
23775
  });
23167
23776
  } else {
@@ -23311,11 +23920,11 @@ async function handleUploadFiles(req) {
23311
23920
  const written = [];
23312
23921
  try {
23313
23922
  for (const upload of uploads) {
23314
- const fd = openSync2(upload.target, uploadOpenFlags(), 420);
23923
+ const fd = openSync3(upload.target, uploadOpenFlags(), 420);
23315
23924
  try {
23316
23925
  writeFileSync2(fd, new Uint8Array(await upload.file.arrayBuffer()));
23317
23926
  } finally {
23318
- closeSync2(fd);
23927
+ closeSync3(fd);
23319
23928
  }
23320
23929
  written.push(upload.target);
23321
23930
  }
@@ -24313,7 +24922,7 @@ var init_preview = __esm(async () => {
24313
24922
  if (url.pathname === "/_files")
24314
24923
  return handleFiles2(url);
24315
24924
  if (url.pathname === "/_grep")
24316
- return handleGrep(url);
24925
+ return await handleGrep(url);
24317
24926
  if (url.pathname === "/_commits")
24318
24927
  return handleRefCommits(url);
24319
24928
  if (url.pathname === "/_log")
@@ -24325,7 +24934,7 @@ var init_preview = __esm(async () => {
24325
24934
  if (url.pathname === "/file_range")
24326
24935
  return handleFileRange(url);
24327
24936
  if (url.pathname === "/_file")
24328
- return handleRawFile(req, url);
24937
+ return await handleRawFile(req, url);
24329
24938
  if (url.pathname === "/_open_path")
24330
24939
  return handleOpenPath(req);
24331
24940
  if (url.pathname === "/_trash_path")