@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.
- package/README.md +14 -2
- package/dist/code-viewer.js +792 -183
- package/package.json +1 -1
- package/skills/code-viewer-query/SKILL.md +3 -1
- package/web/app.js +302 -121
- package/web/style.css +3 -0
package/dist/code-viewer.js
CHANGED
|
@@ -96,7 +96,7 @@ var init_sqlite_driver = __esm(() => {
|
|
|
96
96
|
|
|
97
97
|
// web-src/server/json-store.ts
|
|
98
98
|
import { randomBytes } from "node:crypto";
|
|
99
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
99
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
100
100
|
import { dirname } from "node:path";
|
|
101
101
|
function isEnoent(err) {
|
|
102
102
|
return err?.code === "ENOENT";
|
|
@@ -140,8 +140,15 @@ function createJsonFileStore(options) {
|
|
|
140
140
|
}
|
|
141
141
|
await mkdir(dirname(file), { recursive: true });
|
|
142
142
|
const tmp = tmpPath(file);
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
try {
|
|
144
|
+
await writeFile(tmp, content, "utf8");
|
|
145
|
+
await rename(tmp, file);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
await unlink(tmp).catch(() => {
|
|
148
|
+
return;
|
|
149
|
+
});
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
145
152
|
}
|
|
146
153
|
async function load(root) {
|
|
147
154
|
const pendingWrite = queues.get(options.filePath(root));
|
|
@@ -841,6 +848,7 @@ function runSync(args, cwd, options = {}) {
|
|
|
841
848
|
encoding: "buffer",
|
|
842
849
|
stdio: ["ignore", "pipe", "pipe"],
|
|
843
850
|
timeout: options.timeout,
|
|
851
|
+
maxBuffer: options.maxBuffer ?? 64 * 1024 * 1024,
|
|
844
852
|
killSignal: "SIGKILL"
|
|
845
853
|
});
|
|
846
854
|
return {
|
|
@@ -849,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
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
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 :
|
|
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
|
|
2056
|
-
|
|
2057
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
7846
|
-
|
|
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
|
|
8002
|
+
function createGlobPathMatcher(query) {
|
|
7891
8003
|
const regex = globToRegExp(query);
|
|
7892
|
-
|
|
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
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
const
|
|
7901
|
-
if (
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
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
|
-
|
|
7915
|
-
|
|
7916
|
-
}
|
|
7917
|
-
function rankPathMatches(query, items) {
|
|
7918
|
-
if (isGlobPathQuery(query)) {
|
|
7919
|
-
return items.map((item) => {
|
|
7920
|
-
const match = globMatchPath(query, item.path);
|
|
7921
|
-
return match ? {
|
|
7922
|
-
item,
|
|
7923
|
-
score: match.score,
|
|
7924
|
-
ranges: match.ranges,
|
|
7925
|
-
mode: "glob"
|
|
7926
|
-
} : null;
|
|
7927
|
-
}).filter((item) => item !== null).sort((a, b) => b.score - a.score || a.item.path.localeCompare(b.item.path));
|
|
7928
|
-
}
|
|
7929
|
-
return rankFuzzyPaths(query, items).map((item) => ({
|
|
7930
|
-
...item,
|
|
7931
|
-
mode: "fuzzy"
|
|
7932
|
-
}));
|
|
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
|
|
8123
|
+
if (!line)
|
|
8024
8124
|
continue;
|
|
8025
|
-
|
|
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
|
|
9734
|
+
function parseDockerJsonLines(stdout) {
|
|
9633
9735
|
const output = stdout.trim();
|
|
9634
|
-
|
|
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
|
|
9655
|
-
throwIfAborted(signal,
|
|
9761
|
+
function runDockerCliAsync(args, opts) {
|
|
9762
|
+
throwIfAborted(opts.signal, opts.abortMessage);
|
|
9656
9763
|
if (spawnSyncImpl !== spawnSync2) {
|
|
9657
|
-
const
|
|
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
|
|
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:
|
|
9679
|
-
timeoutMessage:
|
|
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
|
-
|
|
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
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
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 =
|
|
13428
|
+
const fd = openSync2(fullPath, "r");
|
|
13155
13429
|
try {
|
|
13156
|
-
|
|
13430
|
+
readSync2(fd, buf, 0, 16, 0);
|
|
13157
13431
|
} finally {
|
|
13158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
23153
|
-
if (
|
|
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
|
|
23161
|
-
|
|
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 =
|
|
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
|
-
|
|
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")
|