@youtyan/code-viewer 0.9.1 → 0.9.2
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/dist/code-viewer.js +899 -513
- package/package.json +1 -1
- package/web/app.js +26 -6
- package/web/index.html +1 -1
package/dist/code-viewer.js
CHANGED
|
@@ -9763,6 +9763,477 @@ var init_agent_help = __esm(() => {
|
|
|
9763
9763
|
];
|
|
9764
9764
|
});
|
|
9765
9765
|
|
|
9766
|
+
// web-src/server/worktree-watcher.ts
|
|
9767
|
+
import {
|
|
9768
|
+
lstatSync as lstatSync3,
|
|
9769
|
+
readdirSync as nodeReaddirSync,
|
|
9770
|
+
watch as nodeWatch
|
|
9771
|
+
} from "node:fs";
|
|
9772
|
+
import { join as join9, relative as relative4 } from "node:path";
|
|
9773
|
+
function supportsNativeRecursiveWatch(platform) {
|
|
9774
|
+
return platform === "darwin" || platform === "win32";
|
|
9775
|
+
}
|
|
9776
|
+
function normalizeRelativePath(path) {
|
|
9777
|
+
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
9778
|
+
}
|
|
9779
|
+
function isInsideRoot(root, path) {
|
|
9780
|
+
const rel = relative4(root, path).replace(/\\/g, "/");
|
|
9781
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
9782
|
+
}
|
|
9783
|
+
function startWorktreeUpdateWatch(options) {
|
|
9784
|
+
const watch = options.watch || nodeWatch;
|
|
9785
|
+
const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
|
|
9786
|
+
const isDirectory = options.isDirectory || ((path) => {
|
|
9787
|
+
try {
|
|
9788
|
+
return lstatSync3(path).isDirectory();
|
|
9789
|
+
} catch {
|
|
9790
|
+
return false;
|
|
9791
|
+
}
|
|
9792
|
+
});
|
|
9793
|
+
const directorySignature = options.directorySignature || ((path) => {
|
|
9794
|
+
try {
|
|
9795
|
+
const stats = lstatSync3(path);
|
|
9796
|
+
if (!stats.isDirectory())
|
|
9797
|
+
return null;
|
|
9798
|
+
return `${stats.dev}:${stats.ino}`;
|
|
9799
|
+
} catch {
|
|
9800
|
+
return null;
|
|
9801
|
+
}
|
|
9802
|
+
});
|
|
9803
|
+
const setTimer = options.setTimeoutFn || setTimeout;
|
|
9804
|
+
const clearTimer = options.clearTimeoutFn || clearTimeout;
|
|
9805
|
+
const debounceMs = options.debounceMs ?? 250;
|
|
9806
|
+
const recursive = options.recursive === true;
|
|
9807
|
+
const maxWatchedDirectories = Math.max(1, Math.floor(options.maxWatchedDirectories ?? DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT));
|
|
9808
|
+
const watchers = new Map;
|
|
9809
|
+
const signatures = new Map;
|
|
9810
|
+
const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
|
|
9811
|
+
const initialScanQueue = [];
|
|
9812
|
+
let initialScanTimer = null;
|
|
9813
|
+
let processingInitialScan = false;
|
|
9814
|
+
const pendingPathInspections = new Map;
|
|
9815
|
+
let pathInspectionTimer = null;
|
|
9816
|
+
let timer = null;
|
|
9817
|
+
const pendingChangedPaths = new Set;
|
|
9818
|
+
let fullUpdatePending = false;
|
|
9819
|
+
let watchLimitReported = false;
|
|
9820
|
+
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
9821
|
+
const directoryRelativePath = (dir) => normalizeRelativePath(relative4(options.root, dir));
|
|
9822
|
+
const ignoredDirectory = (dir) => {
|
|
9823
|
+
const rel = directoryRelativePath(dir);
|
|
9824
|
+
return Boolean(rel && ignored(rel));
|
|
9825
|
+
};
|
|
9826
|
+
const scheduleUpdate = (changedPath) => {
|
|
9827
|
+
if (changedPath)
|
|
9828
|
+
pendingChangedPaths.add(changedPath);
|
|
9829
|
+
else
|
|
9830
|
+
fullUpdatePending = true;
|
|
9831
|
+
if (timer)
|
|
9832
|
+
clearTimer(timer);
|
|
9833
|
+
timer = setTimer(() => {
|
|
9834
|
+
timer = null;
|
|
9835
|
+
const paths = !fullUpdatePending && pendingChangedPaths.size ? [...pendingChangedPaths] : undefined;
|
|
9836
|
+
pendingChangedPaths.clear();
|
|
9837
|
+
fullUpdatePending = false;
|
|
9838
|
+
options.onUpdate(paths);
|
|
9839
|
+
}, debounceMs);
|
|
9840
|
+
};
|
|
9841
|
+
const reportWatchLimit = () => {
|
|
9842
|
+
if (watchLimitReported)
|
|
9843
|
+
return;
|
|
9844
|
+
watchLimitReported = true;
|
|
9845
|
+
options.onWatchLimit?.(maxWatchedDirectories);
|
|
9846
|
+
options.onError?.(new Error(`worktree watcher cap reached (${maxWatchedDirectories}); subsequent changes may be missed`));
|
|
9847
|
+
};
|
|
9848
|
+
const closeSubtree = (dir) => {
|
|
9849
|
+
for (const [watchedDir, watcher] of [...watchers]) {
|
|
9850
|
+
if (watchedDir !== dir && !watchedDir.startsWith(`${dir}/`))
|
|
9851
|
+
continue;
|
|
9852
|
+
try {
|
|
9853
|
+
watcher.close?.();
|
|
9854
|
+
} catch {}
|
|
9855
|
+
watchers.delete(watchedDir);
|
|
9856
|
+
signatures.delete(watchedDir);
|
|
9857
|
+
}
|
|
9858
|
+
};
|
|
9859
|
+
const closeAll = () => {
|
|
9860
|
+
if (initialScanTimer) {
|
|
9861
|
+
clearTimer(initialScanTimer);
|
|
9862
|
+
initialScanTimer = null;
|
|
9863
|
+
}
|
|
9864
|
+
if (pathInspectionTimer) {
|
|
9865
|
+
clearTimer(pathInspectionTimer);
|
|
9866
|
+
pathInspectionTimer = null;
|
|
9867
|
+
}
|
|
9868
|
+
initialScanQueue.length = 0;
|
|
9869
|
+
pendingPathInspections.clear();
|
|
9870
|
+
for (const watcher of [...watchers.values()]) {
|
|
9871
|
+
try {
|
|
9872
|
+
watcher.close?.();
|
|
9873
|
+
} catch {}
|
|
9874
|
+
}
|
|
9875
|
+
watchers.clear();
|
|
9876
|
+
signatures.clear();
|
|
9877
|
+
};
|
|
9878
|
+
const readChildDirectories = (dir) => {
|
|
9879
|
+
let entries;
|
|
9880
|
+
try {
|
|
9881
|
+
entries = readDirs(dir);
|
|
9882
|
+
} catch (error) {
|
|
9883
|
+
options.onError?.(error);
|
|
9884
|
+
return [];
|
|
9885
|
+
}
|
|
9886
|
+
const children = [];
|
|
9887
|
+
for (const entry of entries) {
|
|
9888
|
+
if (!entry.isDirectory())
|
|
9889
|
+
continue;
|
|
9890
|
+
const child = join9(dir, entry.name);
|
|
9891
|
+
if (ignoredDirectory(child))
|
|
9892
|
+
continue;
|
|
9893
|
+
children.push(child);
|
|
9894
|
+
}
|
|
9895
|
+
return children;
|
|
9896
|
+
};
|
|
9897
|
+
const processInitialScanQueue = () => {
|
|
9898
|
+
initialScanTimer = null;
|
|
9899
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
9900
|
+
reportWatchLimit();
|
|
9901
|
+
initialScanQueue.length = 0;
|
|
9902
|
+
return;
|
|
9903
|
+
}
|
|
9904
|
+
const next = initialScanQueue.shift();
|
|
9905
|
+
if (next) {
|
|
9906
|
+
processingInitialScan = true;
|
|
9907
|
+
try {
|
|
9908
|
+
watchDirectory(next, true);
|
|
9909
|
+
} finally {
|
|
9910
|
+
processingInitialScan = false;
|
|
9911
|
+
}
|
|
9912
|
+
}
|
|
9913
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
9914
|
+
reportWatchLimit();
|
|
9915
|
+
initialScanQueue.length = 0;
|
|
9916
|
+
}
|
|
9917
|
+
if (initialScanQueue.length)
|
|
9918
|
+
initialScanTimer = setTimer(processInitialScanQueue, 50);
|
|
9919
|
+
};
|
|
9920
|
+
const queueInitialChildren = (dir) => {
|
|
9921
|
+
const remaining = maxWatchedDirectories - watchers.size;
|
|
9922
|
+
if (remaining <= 0) {
|
|
9923
|
+
reportWatchLimit();
|
|
9924
|
+
return;
|
|
9925
|
+
}
|
|
9926
|
+
const children = readChildDirectories(dir);
|
|
9927
|
+
if (children.length > remaining)
|
|
9928
|
+
reportWatchLimit();
|
|
9929
|
+
initialScanQueue.push(...children.slice(0, remaining));
|
|
9930
|
+
if (!initialScanTimer && !processingInitialScan)
|
|
9931
|
+
initialScanTimer = setTimer(processInitialScanQueue, 5000);
|
|
9932
|
+
};
|
|
9933
|
+
const processChangedPath = (changed, fullChangedPath) => {
|
|
9934
|
+
const known = watchers.has(fullChangedPath);
|
|
9935
|
+
if (isDirectory(fullChangedPath)) {
|
|
9936
|
+
if (known) {
|
|
9937
|
+
const signature = directorySignature(fullChangedPath);
|
|
9938
|
+
if (signature && signature !== signatures.get(fullChangedPath)) {
|
|
9939
|
+
closeSubtree(fullChangedPath);
|
|
9940
|
+
watchDirectory(fullChangedPath, initialScanAsync);
|
|
9941
|
+
}
|
|
9942
|
+
scheduleUpdate(changed);
|
|
9943
|
+
return;
|
|
9944
|
+
}
|
|
9945
|
+
watchDirectory(fullChangedPath, initialScanAsync);
|
|
9946
|
+
} else if (known) {
|
|
9947
|
+
closeSubtree(fullChangedPath);
|
|
9948
|
+
}
|
|
9949
|
+
scheduleUpdate(changed);
|
|
9950
|
+
};
|
|
9951
|
+
const processPathInspections = () => {
|
|
9952
|
+
pathInspectionTimer = null;
|
|
9953
|
+
const entries = [...pendingPathInspections];
|
|
9954
|
+
pendingPathInspections.clear();
|
|
9955
|
+
for (const [changed, fullChangedPath] of entries) {
|
|
9956
|
+
processChangedPath(changed, fullChangedPath);
|
|
9957
|
+
}
|
|
9958
|
+
};
|
|
9959
|
+
const queuePathInspection = (changed, fullChangedPath) => {
|
|
9960
|
+
pendingPathInspections.set(changed, fullChangedPath);
|
|
9961
|
+
if (!pathInspectionTimer)
|
|
9962
|
+
pathInspectionTimer = setTimer(processPathInspections, 25);
|
|
9963
|
+
};
|
|
9964
|
+
const watchDirectory = (dir, initialScan = false) => {
|
|
9965
|
+
if (watchers.has(dir))
|
|
9966
|
+
return;
|
|
9967
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
9968
|
+
reportWatchLimit();
|
|
9969
|
+
return;
|
|
9970
|
+
}
|
|
9971
|
+
const rel = directoryRelativePath(dir);
|
|
9972
|
+
if (rel && ignored(rel))
|
|
9973
|
+
return;
|
|
9974
|
+
try {
|
|
9975
|
+
const watcher = watch(dir, { persistent: false, recursive }, (_event, filename) => {
|
|
9976
|
+
if (!filename) {
|
|
9977
|
+
scheduleUpdate();
|
|
9978
|
+
return;
|
|
9979
|
+
}
|
|
9980
|
+
const changed = normalizeRelativePath(join9(rel, filename.toString()));
|
|
9981
|
+
if (ignored(changed))
|
|
9982
|
+
return;
|
|
9983
|
+
const fullChangedPath = join9(options.root, changed);
|
|
9984
|
+
if (!isInsideRoot(options.root, fullChangedPath))
|
|
9985
|
+
return;
|
|
9986
|
+
if (recursive) {
|
|
9987
|
+
scheduleUpdate(changed);
|
|
9988
|
+
return;
|
|
9989
|
+
}
|
|
9990
|
+
if (initialScanAsync) {
|
|
9991
|
+
queuePathInspection(changed, fullChangedPath);
|
|
9992
|
+
return;
|
|
9993
|
+
}
|
|
9994
|
+
processChangedPath(changed, fullChangedPath);
|
|
9995
|
+
}) || {};
|
|
9996
|
+
watchers.set(dir, watcher);
|
|
9997
|
+
const signature = directorySignature(dir);
|
|
9998
|
+
if (signature)
|
|
9999
|
+
signatures.set(dir, signature);
|
|
10000
|
+
watcher.on?.("error", () => {
|
|
10001
|
+
if (watchers.get(dir) === watcher) {
|
|
10002
|
+
watchers.delete(dir);
|
|
10003
|
+
signatures.delete(dir);
|
|
10004
|
+
}
|
|
10005
|
+
});
|
|
10006
|
+
watcher.on?.("close", () => {
|
|
10007
|
+
if (watchers.get(dir) === watcher) {
|
|
10008
|
+
watchers.delete(dir);
|
|
10009
|
+
signatures.delete(dir);
|
|
10010
|
+
}
|
|
10011
|
+
});
|
|
10012
|
+
} catch (error) {
|
|
10013
|
+
options.onError?.(error);
|
|
10014
|
+
return;
|
|
10015
|
+
}
|
|
10016
|
+
if (recursive)
|
|
10017
|
+
return;
|
|
10018
|
+
if (initialScanAsync && initialScan) {
|
|
10019
|
+
queueInitialChildren(dir);
|
|
10020
|
+
return;
|
|
10021
|
+
}
|
|
10022
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
10023
|
+
reportWatchLimit();
|
|
10024
|
+
return;
|
|
10025
|
+
}
|
|
10026
|
+
for (const child of readChildDirectories(dir))
|
|
10027
|
+
watchDirectory(child);
|
|
10028
|
+
};
|
|
10029
|
+
watchDirectory(options.root, true);
|
|
10030
|
+
return { started: watchers.size > 0, close: closeAll };
|
|
10031
|
+
}
|
|
10032
|
+
var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT = 1024, MIN_WORKTREE_WATCH_DIRECTORY_LIMIT = 1, MAX_WORKTREE_WATCH_DIRECTORY_LIMIT = 65536;
|
|
10033
|
+
var init_worktree_watcher = __esm(() => {
|
|
10034
|
+
init_search();
|
|
10035
|
+
});
|
|
10036
|
+
|
|
10037
|
+
// web-src/server/watch-child.ts
|
|
10038
|
+
var exports_watch_child = {};
|
|
10039
|
+
__export(exports_watch_child, {
|
|
10040
|
+
runWatchChild: () => runWatchChild,
|
|
10041
|
+
parsePorcelainV2: () => parsePorcelainV2
|
|
10042
|
+
});
|
|
10043
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10044
|
+
import { createReadStream as createReadStream2, lstatSync as lstatSync4 } from "node:fs";
|
|
10045
|
+
import { join as join10 } from "node:path";
|
|
10046
|
+
function send(message) {
|
|
10047
|
+
process.stdout.write(`${JSON.stringify(message)}
|
|
10048
|
+
`);
|
|
10049
|
+
}
|
|
10050
|
+
function readConfigLine() {
|
|
10051
|
+
return new Promise((resolve2, reject) => {
|
|
10052
|
+
let buffer = "";
|
|
10053
|
+
const stdin = process.stdin;
|
|
10054
|
+
stdin.setEncoding("utf8");
|
|
10055
|
+
const onData = (chunk) => {
|
|
10056
|
+
buffer += chunk;
|
|
10057
|
+
const newline = buffer.indexOf(`
|
|
10058
|
+
`);
|
|
10059
|
+
if (newline === -1)
|
|
10060
|
+
return;
|
|
10061
|
+
stdin.off("data", onData);
|
|
10062
|
+
stdin.off("end", onEnd);
|
|
10063
|
+
resolve2(buffer.slice(0, newline));
|
|
10064
|
+
};
|
|
10065
|
+
const onEnd = () => {
|
|
10066
|
+
stdin.off("data", onData);
|
|
10067
|
+
reject(new Error("watch child: stdin closed before config arrived"));
|
|
10068
|
+
};
|
|
10069
|
+
stdin.on("data", onData);
|
|
10070
|
+
stdin.on("end", onEnd);
|
|
10071
|
+
});
|
|
10072
|
+
}
|
|
10073
|
+
function hashFile(path) {
|
|
10074
|
+
return new Promise((resolve2) => {
|
|
10075
|
+
const hash = createHash2("sha256");
|
|
10076
|
+
const stream = createReadStream2(path);
|
|
10077
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
10078
|
+
stream.on("end", () => resolve2(hash.digest("hex")));
|
|
10079
|
+
stream.on("error", () => resolve2("unreadable"));
|
|
10080
|
+
});
|
|
10081
|
+
}
|
|
10082
|
+
function parsePorcelainV2(raw) {
|
|
10083
|
+
const records = raw.split("\x00");
|
|
10084
|
+
let head = "";
|
|
10085
|
+
const entries = [];
|
|
10086
|
+
for (let i = 0;i < records.length; i++) {
|
|
10087
|
+
const record = records[i];
|
|
10088
|
+
if (!record)
|
|
10089
|
+
continue;
|
|
10090
|
+
if (record.startsWith("# branch.oid ")) {
|
|
10091
|
+
head = record.slice("# branch.oid ".length);
|
|
10092
|
+
continue;
|
|
10093
|
+
}
|
|
10094
|
+
if (record.startsWith("# "))
|
|
10095
|
+
continue;
|
|
10096
|
+
const kind = record[0];
|
|
10097
|
+
if (kind === "1" || kind === "2" || kind === "u") {
|
|
10098
|
+
const metaCount = kind === "1" ? 8 : kind === "2" ? 9 : 10;
|
|
10099
|
+
const status = record.split(" ").slice(0, metaCount).join(" ");
|
|
10100
|
+
entries.push({ path: record.slice(status.length + 1), status });
|
|
10101
|
+
if (kind === "2")
|
|
10102
|
+
i++;
|
|
10103
|
+
continue;
|
|
10104
|
+
}
|
|
10105
|
+
if (kind === "?" || kind === "!") {
|
|
10106
|
+
entries.push({ path: record.slice(2), status: kind });
|
|
10107
|
+
}
|
|
10108
|
+
}
|
|
10109
|
+
return { head, entries };
|
|
10110
|
+
}
|
|
10111
|
+
async function readWorktreeSnapshot(config) {
|
|
10112
|
+
const result = await runAsync([
|
|
10113
|
+
"git",
|
|
10114
|
+
"--no-optional-locks",
|
|
10115
|
+
"-c",
|
|
10116
|
+
"core.fsmonitor=false",
|
|
10117
|
+
"status",
|
|
10118
|
+
"--porcelain=v2",
|
|
10119
|
+
"-z",
|
|
10120
|
+
"--branch",
|
|
10121
|
+
"--untracked-files=all"
|
|
10122
|
+
], config.root, { timeout: 60000 });
|
|
10123
|
+
if (result.code !== 0)
|
|
10124
|
+
return null;
|
|
10125
|
+
const parsed = parsePorcelainV2(result.stdout);
|
|
10126
|
+
const entries = new Map;
|
|
10127
|
+
for (const entry of parsed.entries) {
|
|
10128
|
+
if (!entry.path)
|
|
10129
|
+
continue;
|
|
10130
|
+
if (isSkippableSearchPath(entry.path, config.omitDirNames, config.excludeNames))
|
|
10131
|
+
continue;
|
|
10132
|
+
entries.set(entry.path, await pathSignature(config.root, entry));
|
|
10133
|
+
}
|
|
10134
|
+
return { head: parsed.head, entries };
|
|
10135
|
+
}
|
|
10136
|
+
async function pathSignature(root, entry) {
|
|
10137
|
+
const full = join10(root, entry.path);
|
|
10138
|
+
let stats;
|
|
10139
|
+
try {
|
|
10140
|
+
stats = lstatSync4(full);
|
|
10141
|
+
} catch {
|
|
10142
|
+
return `${entry.status}:absent`;
|
|
10143
|
+
}
|
|
10144
|
+
const base = `${entry.status}:${stats.size}:${stats.mtimeMs}:${stats.mode}`;
|
|
10145
|
+
if (stats.isFile() && stats.size <= MAX_HASHED_FILE_BYTES) {
|
|
10146
|
+
return `${base}:${await hashFile(full)}`;
|
|
10147
|
+
}
|
|
10148
|
+
return base;
|
|
10149
|
+
}
|
|
10150
|
+
function snapshotDiff(previous, next) {
|
|
10151
|
+
if (previous.head !== next.head)
|
|
10152
|
+
return { full: true, paths: [] };
|
|
10153
|
+
const paths = [];
|
|
10154
|
+
for (const [path, signature] of next.entries) {
|
|
10155
|
+
if (previous.entries.get(path) !== signature)
|
|
10156
|
+
paths.push(path);
|
|
10157
|
+
}
|
|
10158
|
+
for (const path of previous.entries.keys()) {
|
|
10159
|
+
if (!next.entries.has(path))
|
|
10160
|
+
paths.push(path);
|
|
10161
|
+
}
|
|
10162
|
+
return { full: false, paths };
|
|
10163
|
+
}
|
|
10164
|
+
async function runWatchChild() {
|
|
10165
|
+
const config = JSON.parse(await readConfigLine());
|
|
10166
|
+
process.stdin.on("end", () => process.exit(0));
|
|
10167
|
+
process.stdin.on("close", () => process.exit(0));
|
|
10168
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
10169
|
+
process.on(signal, () => process.exit(0));
|
|
10170
|
+
}
|
|
10171
|
+
let watching = false;
|
|
10172
|
+
if (!config.pollOnly) {
|
|
10173
|
+
const recursive = supportsNativeRecursiveWatch(process.platform);
|
|
10174
|
+
const watch = startWorktreeUpdateWatch({
|
|
10175
|
+
root: config.root,
|
|
10176
|
+
omitDirNames: config.omitDirNames,
|
|
10177
|
+
excludeNames: config.excludeNames,
|
|
10178
|
+
recursive,
|
|
10179
|
+
initialScanMode: recursive ? "sync" : "async",
|
|
10180
|
+
maxWatchedDirectories: config.maxWatchedDirectories,
|
|
10181
|
+
debounceMs: config.debounceMs,
|
|
10182
|
+
onUpdate: (paths) => send({ type: "update", paths }),
|
|
10183
|
+
onWatchLimit: (limit) => send({ type: "watch-limit", limit }),
|
|
10184
|
+
onError: (error) => send({
|
|
10185
|
+
type: "warn",
|
|
10186
|
+
message: error instanceof Error ? error.message : String(error)
|
|
10187
|
+
})
|
|
10188
|
+
});
|
|
10189
|
+
watching = watch.started;
|
|
10190
|
+
}
|
|
10191
|
+
send({ type: "ready", watching, pollOnly: config.pollOnly });
|
|
10192
|
+
setInterval(() => send({ type: "heartbeat" }), config.heartbeatIntervalMs).unref?.();
|
|
10193
|
+
setInterval(() => {
|
|
10194
|
+
try {
|
|
10195
|
+
process.kill(config.parentPid, 0);
|
|
10196
|
+
} catch {
|
|
10197
|
+
process.exit(0);
|
|
10198
|
+
}
|
|
10199
|
+
}, config.heartbeatIntervalMs);
|
|
10200
|
+
let snapshot = null;
|
|
10201
|
+
let baselineEstablished = false;
|
|
10202
|
+
const poll = async () => {
|
|
10203
|
+
const next = await readWorktreeSnapshot(config);
|
|
10204
|
+
if (!next)
|
|
10205
|
+
return;
|
|
10206
|
+
if (!snapshot) {
|
|
10207
|
+
snapshot = next;
|
|
10208
|
+
if (baselineEstablished)
|
|
10209
|
+
send({ type: "update" });
|
|
10210
|
+
baselineEstablished = true;
|
|
10211
|
+
return;
|
|
10212
|
+
}
|
|
10213
|
+
const diff = snapshotDiff(snapshot, next);
|
|
10214
|
+
snapshot = next;
|
|
10215
|
+
if (diff.full) {
|
|
10216
|
+
send({ type: "update" });
|
|
10217
|
+
return;
|
|
10218
|
+
}
|
|
10219
|
+
if (diff.paths.length)
|
|
10220
|
+
send({ type: "update", paths: diff.paths });
|
|
10221
|
+
};
|
|
10222
|
+
setInterval(() => {
|
|
10223
|
+
poll().catch((error) => send({
|
|
10224
|
+
type: "warn",
|
|
10225
|
+
message: error instanceof Error ? error.message : String(error)
|
|
10226
|
+
}));
|
|
10227
|
+
}, config.pollIntervalMs);
|
|
10228
|
+
}
|
|
10229
|
+
var MAX_HASHED_FILE_BYTES;
|
|
10230
|
+
var init_watch_child = __esm(() => {
|
|
10231
|
+
init_runtime();
|
|
10232
|
+
init_search();
|
|
10233
|
+
init_worktree_watcher();
|
|
10234
|
+
MAX_HASHED_FILE_BYTES = 4 * 1024 * 1024;
|
|
10235
|
+
});
|
|
10236
|
+
|
|
9766
10237
|
// web-src/server/database/serialize.ts
|
|
9767
10238
|
function serializeDbValue(value) {
|
|
9768
10239
|
if (value === null || value === undefined)
|
|
@@ -10028,7 +10499,7 @@ var init_mutate = __esm(() => {
|
|
|
10028
10499
|
});
|
|
10029
10500
|
|
|
10030
10501
|
// web-src/server/database/sources/sql-snapshot.ts
|
|
10031
|
-
import { createHash as
|
|
10502
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
10032
10503
|
function normalizeRawValue(v) {
|
|
10033
10504
|
if (v === null)
|
|
10034
10505
|
return "\\N";
|
|
@@ -10048,7 +10519,7 @@ function rowToPayloadJson(columns, row) {
|
|
|
10048
10519
|
}
|
|
10049
10520
|
function computeRowHash(columns, row) {
|
|
10050
10521
|
const parts = columns.map((_, i) => normalizeRawValue(row[i]));
|
|
10051
|
-
return
|
|
10522
|
+
return createHash3("sha256").update(parts.join("\t")).digest("hex");
|
|
10052
10523
|
}
|
|
10053
10524
|
function buildRowKeyJson(pkColumns, allColumns, row, rowIndex) {
|
|
10054
10525
|
if (pkColumns.length === 0) {
|
|
@@ -12119,7 +12590,7 @@ __export(exports_redis, {
|
|
|
12119
12590
|
canonicalizeRedisSnapshotContainer: () => canonicalizeRedisSnapshotContainer,
|
|
12120
12591
|
__setRedisClientFactoryForTest: () => __setRedisClientFactoryForTest
|
|
12121
12592
|
});
|
|
12122
|
-
import { createHash as
|
|
12593
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
12123
12594
|
import { createClient } from "@redis/client";
|
|
12124
12595
|
function __setRedisClientFactoryForTest(factory) {
|
|
12125
12596
|
createRedisClientImpl = factory ?? ((options) => createClient(options));
|
|
@@ -12496,7 +12967,7 @@ function createRedisAdapter(config) {
|
|
|
12496
12967
|
if (type === "none") {
|
|
12497
12968
|
return {
|
|
12498
12969
|
payload: { type: "none" },
|
|
12499
|
-
fullHash:
|
|
12970
|
+
fullHash: createHash4("sha256").update("").digest("hex")
|
|
12500
12971
|
};
|
|
12501
12972
|
}
|
|
12502
12973
|
if (type === "string")
|
|
@@ -12513,7 +12984,7 @@ function createRedisAdapter(config) {
|
|
|
12513
12984
|
return snapshotFetchStream(db, hexKey, signal);
|
|
12514
12985
|
return {
|
|
12515
12986
|
payload: { type: "none" },
|
|
12516
|
-
fullHash:
|
|
12987
|
+
fullHash: createHash4("sha256").update("").digest("hex")
|
|
12517
12988
|
};
|
|
12518
12989
|
}
|
|
12519
12990
|
async function evalHex(db, luaBody, extraArgv, label, signal) {
|
|
@@ -12525,7 +12996,7 @@ function createRedisAdapter(config) {
|
|
|
12525
12996
|
}
|
|
12526
12997
|
async function snapshotFetchString(db, hexKey, signal) {
|
|
12527
12998
|
const fullSize = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('STRLEN', fromhex(ARGV[1]))`, [hexKey], "STRLEN", signal)) || 0;
|
|
12528
|
-
const hasher =
|
|
12999
|
+
const hasher = createHash4("sha256");
|
|
12529
13000
|
let previewBytes = Buffer.alloc(0);
|
|
12530
13001
|
for (let offset = 0;offset < fullSize; offset += REDIS_STRING_BYTE_LIMIT) {
|
|
12531
13002
|
if (signal?.aborted)
|
|
@@ -12558,7 +13029,7 @@ function createRedisAdapter(config) {
|
|
|
12558
13029
|
}
|
|
12559
13030
|
async function snapshotFetchList(db, hexKey, signal) {
|
|
12560
13031
|
const total = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('LLEN', fromhex(ARGV[1]))`, [hexKey], "LLEN", signal)) || 0;
|
|
12561
|
-
const hasher =
|
|
13032
|
+
const hasher = createHash4("sha256");
|
|
12562
13033
|
let previewItems = [];
|
|
12563
13034
|
for (let offset = 0;offset < total; offset += REDIS_COLLECTION_LIMIT) {
|
|
12564
13035
|
if (signal?.aborted)
|
|
@@ -12600,7 +13071,7 @@ function createRedisAdapter(config) {
|
|
|
12600
13071
|
uniquePairs.push(p);
|
|
12601
13072
|
}
|
|
12602
13073
|
uniquePairs.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
12603
|
-
const hasher =
|
|
13074
|
+
const hasher = createHash4("sha256");
|
|
12604
13075
|
for (const [f, v] of uniquePairs) {
|
|
12605
13076
|
hashHexItem(hasher, f);
|
|
12606
13077
|
hashHexItem(hasher, v);
|
|
@@ -12628,7 +13099,7 @@ function createRedisAdapter(config) {
|
|
|
12628
13099
|
} while (cursor !== "0");
|
|
12629
13100
|
const dedup = Array.from(new Set(allMembers));
|
|
12630
13101
|
dedup.sort();
|
|
12631
|
-
const hasher =
|
|
13102
|
+
const hasher = createHash4("sha256");
|
|
12632
13103
|
for (const m of dedup)
|
|
12633
13104
|
hashHexItem(hasher, m);
|
|
12634
13105
|
const previewMembers = dedup.slice(0, REDIS_COLLECTION_LIMIT).map(decodeHexItem);
|
|
@@ -12642,7 +13113,7 @@ function createRedisAdapter(config) {
|
|
|
12642
13113
|
}
|
|
12643
13114
|
async function snapshotFetchZset(db, hexKey, signal) {
|
|
12644
13115
|
const total = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('ZCARD', fromhex(ARGV[1]))`, [hexKey], "ZCARD", signal)) || 0;
|
|
12645
|
-
const hasher =
|
|
13116
|
+
const hasher = createHash4("sha256");
|
|
12646
13117
|
let previewMembers = [];
|
|
12647
13118
|
for (let offset = 0;offset < total; offset += REDIS_COLLECTION_LIMIT) {
|
|
12648
13119
|
if (signal?.aborted)
|
|
@@ -12672,7 +13143,7 @@ function createRedisAdapter(config) {
|
|
|
12672
13143
|
}
|
|
12673
13144
|
async function snapshotFetchStream(db, hexKey, signal) {
|
|
12674
13145
|
const total = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('XLEN', fromhex(ARGV[1]))`, [hexKey], "XLEN", signal)) || 0;
|
|
12675
|
-
const hasher =
|
|
13146
|
+
const hasher = createHash4("sha256");
|
|
12676
13147
|
let previewEntries = [];
|
|
12677
13148
|
let startId = "-";
|
|
12678
13149
|
const seenIds = new Set;
|
|
@@ -13264,7 +13735,7 @@ var init_raw_file_headers = __esm(() => {
|
|
|
13264
13735
|
|
|
13265
13736
|
// web-src/server/database/adapters/s3.ts
|
|
13266
13737
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
13267
|
-
import { createHash as
|
|
13738
|
+
import { createHash as createHash5, createHmac } from "node:crypto";
|
|
13268
13739
|
function createS3RequestDeadline() {
|
|
13269
13740
|
const timeoutMs = s3RequestTimeoutMs;
|
|
13270
13741
|
return {
|
|
@@ -13294,10 +13765,10 @@ function hmac(key, value) {
|
|
|
13294
13765
|
return createHmac("sha256", key).update(value, "utf8").digest();
|
|
13295
13766
|
}
|
|
13296
13767
|
function sha256(value) {
|
|
13297
|
-
return
|
|
13768
|
+
return createHash5("sha256").update(value, "utf8").digest("hex");
|
|
13298
13769
|
}
|
|
13299
13770
|
function sha256Bytes(value) {
|
|
13300
|
-
return
|
|
13771
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
13301
13772
|
}
|
|
13302
13773
|
function encodeRfc3986(value) {
|
|
13303
13774
|
return encodeURIComponent(value).replace(/[!'()*]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
@@ -14304,7 +14775,7 @@ import {
|
|
|
14304
14775
|
statSync as statSync5
|
|
14305
14776
|
} from "node:fs";
|
|
14306
14777
|
import { lstat, open as open2, readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
14307
|
-
import { basename, join as
|
|
14778
|
+
import { basename, join as join11, relative as relative5 } from "node:path";
|
|
14308
14779
|
function isSqliteFile(fullPath) {
|
|
14309
14780
|
try {
|
|
14310
14781
|
const stat3 = statSync5(fullPath);
|
|
@@ -14375,7 +14846,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
14375
14846
|
return;
|
|
14376
14847
|
if (omitSet.has(entry.toLowerCase()))
|
|
14377
14848
|
continue;
|
|
14378
|
-
const full =
|
|
14849
|
+
const full = join11(dir, entry);
|
|
14379
14850
|
let entryStat;
|
|
14380
14851
|
try {
|
|
14381
14852
|
entryStat = await lstat(full);
|
|
@@ -14392,7 +14863,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
14392
14863
|
continue;
|
|
14393
14864
|
if (!await isSqliteFileAsync(full))
|
|
14394
14865
|
continue;
|
|
14395
|
-
const rel =
|
|
14866
|
+
const rel = relative5(cwd, full);
|
|
14396
14867
|
if (rel.startsWith("..") || rel.startsWith("/"))
|
|
14397
14868
|
continue;
|
|
14398
14869
|
results.push({
|
|
@@ -14419,7 +14890,7 @@ function validateDbPath(cwd, dbPath) {
|
|
|
14419
14890
|
const parts = dbPath.split(/[\\/]+/);
|
|
14420
14891
|
if (parts.some((p) => p === ".." || p.toLowerCase() === ".git" || p.toLowerCase() === ".code-viewer"))
|
|
14421
14892
|
return null;
|
|
14422
|
-
const full =
|
|
14893
|
+
const full = join11(cwd, dbPath);
|
|
14423
14894
|
if (!existsSync6(full))
|
|
14424
14895
|
return null;
|
|
14425
14896
|
let realCwd;
|
|
@@ -14430,7 +14901,7 @@ function validateDbPath(cwd, dbPath) {
|
|
|
14430
14901
|
} catch {
|
|
14431
14902
|
return null;
|
|
14432
14903
|
}
|
|
14433
|
-
const rel =
|
|
14904
|
+
const rel = relative5(realCwd, realFull);
|
|
14434
14905
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/"))
|
|
14435
14906
|
return null;
|
|
14436
14907
|
if (!isSqliteFile(realFull))
|
|
@@ -14591,7 +15062,7 @@ function resolveEnvValue(raw, composeDirEnv = {}) {
|
|
|
14591
15062
|
}
|
|
14592
15063
|
async function readDotenvAsync(composeDir) {
|
|
14593
15064
|
try {
|
|
14594
|
-
const content = await readFile2(
|
|
15065
|
+
const content = await readFile2(join11(composeDir, ".env"), "utf-8");
|
|
14595
15066
|
return parseDotenvContent(content);
|
|
14596
15067
|
} catch {
|
|
14597
15068
|
return {};
|
|
@@ -14713,7 +15184,7 @@ function parseComposeContent(content, filepath, composeDir, cwd, composeDirEnv,
|
|
|
14713
15184
|
for (let match = serviceRegex.exec(servicesBlock);match !== null; match = serviceRegex.exec(servicesBlock)) {
|
|
14714
15185
|
servicePositions.push({ name: match[1], start: match.index });
|
|
14715
15186
|
}
|
|
14716
|
-
const relDir =
|
|
15187
|
+
const relDir = relative5(cwd, composeDir);
|
|
14717
15188
|
const isRoot = relDir === "" || relDir === ".";
|
|
14718
15189
|
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
14719
15190
|
const filename = basename(filepath);
|
|
@@ -14818,7 +15289,7 @@ async function walkForMarkerFileAsync(dir, depth, omitSet, hasCapacity, visitDir
|
|
|
14818
15289
|
return;
|
|
14819
15290
|
if (omitSet.has(entry.toLowerCase()))
|
|
14820
15291
|
continue;
|
|
14821
|
-
const full =
|
|
15292
|
+
const full = join11(dir, entry);
|
|
14822
15293
|
let entryStat;
|
|
14823
15294
|
try {
|
|
14824
15295
|
entryStat = await lstat(full);
|
|
@@ -14845,7 +15316,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
14845
15316
|
omitSet.add("node_modules");
|
|
14846
15317
|
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_DOCKER_SERVICES, async (dir) => {
|
|
14847
15318
|
for (const filename of COMPOSE_FILENAMES) {
|
|
14848
|
-
const filepath =
|
|
15319
|
+
const filepath = join11(dir, filename);
|
|
14849
15320
|
if (await pathExistsAsync(filepath)) {
|
|
14850
15321
|
await parseComposeFileAsync(filepath, dir, cwd, results);
|
|
14851
15322
|
break;
|
|
@@ -14889,477 +15360,220 @@ function parseDockerDbId(dbId) {
|
|
|
14889
15360
|
return null;
|
|
14890
15361
|
const afterAt = rest.slice(atIdx + 1);
|
|
14891
15362
|
const colonIdx2 = afterAt.indexOf(":");
|
|
14892
|
-
if (colonIdx2 >= 0) {
|
|
14893
|
-
database = afterAt.slice(colonIdx2 + 1);
|
|
14894
|
-
rest = `${rest.slice(0, atIdx)}@${afterAt.slice(0, colonIdx2)}`;
|
|
14895
|
-
}
|
|
14896
|
-
const [serviceName, encodedRel] = rest.split("@");
|
|
14897
|
-
if (!isSafeDockerServiceName(serviceName))
|
|
14898
|
-
return null;
|
|
14899
|
-
if (!isSafeDockerDatabaseName(database))
|
|
14900
|
-
return null;
|
|
14901
|
-
try {
|
|
14902
|
-
const relDir = decodeURIComponent(encodedRel || "");
|
|
14903
|
-
if (!isSafeDockerRelDir(relDir))
|
|
14904
|
-
return null;
|
|
14905
|
-
return {
|
|
14906
|
-
serviceName,
|
|
14907
|
-
relDir,
|
|
14908
|
-
database,
|
|
14909
|
-
kind
|
|
14910
|
-
};
|
|
14911
|
-
} catch {
|
|
14912
|
-
return null;
|
|
14913
|
-
}
|
|
14914
|
-
}
|
|
14915
|
-
const colonIdx = rest.indexOf(":");
|
|
14916
|
-
if (colonIdx >= 0) {
|
|
14917
|
-
database = rest.slice(colonIdx + 1);
|
|
14918
|
-
rest = rest.slice(0, colonIdx);
|
|
14919
|
-
}
|
|
14920
|
-
if (!isSafeDockerServiceName(rest))
|
|
14921
|
-
return null;
|
|
14922
|
-
if (!isSafeDockerDatabaseName(database))
|
|
14923
|
-
return null;
|
|
14924
|
-
return { serviceName: rest, relDir: "", database, kind };
|
|
14925
|
-
}
|
|
14926
|
-
function canonicalizeDockerDbId(dbId) {
|
|
14927
|
-
const parsed = parseDockerDbId(dbId);
|
|
14928
|
-
if (!parsed)
|
|
14929
|
-
return null;
|
|
14930
|
-
const database = parsed.database ? `:${parsed.database}` : "";
|
|
14931
|
-
const kindSuffix = parsed.kind ? `#${parsed.kind}` : "";
|
|
14932
|
-
if (!parsed.relDir)
|
|
14933
|
-
return `docker:${parsed.serviceName}${database}${kindSuffix}`;
|
|
14934
|
-
return `docker:${parsed.serviceName}@${encodeURIComponent(parsed.relDir)}${database}${kindSuffix}`;
|
|
14935
|
-
}
|
|
14936
|
-
async function findDockerServiceByDbIdAsync(cwd, dbId, kind, omitDirNames, signal) {
|
|
14937
|
-
const parsed = parseDockerDbId(dbId);
|
|
14938
|
-
if (!parsed)
|
|
14939
|
-
return null;
|
|
14940
|
-
if (kind && parsed.kind && parsed.kind !== kind)
|
|
14941
|
-
return null;
|
|
14942
|
-
const effectiveKind = parsed.kind ?? kind;
|
|
14943
|
-
const services = await discoverDockerDatabasesAsync(cwd, omitDirNames, signal);
|
|
14944
|
-
return services.find((d) => d.serviceName === parsed.serviceName && d.relDirSlash === parsed.relDir && (!effectiveKind || d.kind === effectiveKind)) || null;
|
|
14945
|
-
}
|
|
14946
|
-
function isSafeDockerServiceName(value) {
|
|
14947
|
-
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
14948
|
-
}
|
|
14949
|
-
function isSafeDockerRelDir(value) {
|
|
14950
|
-
if (value === "")
|
|
14951
|
-
return true;
|
|
14952
|
-
if (!/^[A-Za-z0-9_./-]+$/.test(value))
|
|
14953
|
-
return false;
|
|
14954
|
-
const parts = value.split("/");
|
|
14955
|
-
return parts.every((p) => p !== "" && p !== "." && p !== "..");
|
|
14956
|
-
}
|
|
14957
|
-
function isSafeDockerDatabaseName(value) {
|
|
14958
|
-
if (value === undefined)
|
|
14959
|
-
return true;
|
|
14960
|
-
if (value === "")
|
|
14961
|
-
return false;
|
|
14962
|
-
if (hasControlCharacter(value))
|
|
14963
|
-
return false;
|
|
14964
|
-
return /^[A-Za-z0-9_$.-]+$/.test(value);
|
|
14965
|
-
}
|
|
14966
|
-
function cloneSupabaseDiscoveryResult(result) {
|
|
14967
|
-
return result.map((entry) => ({ ...entry }));
|
|
14968
|
-
}
|
|
14969
|
-
function parseSupabaseConfigToml(content) {
|
|
14970
|
-
let section = null;
|
|
14971
|
-
let projectId = null;
|
|
14972
|
-
let dbPort = null;
|
|
14973
|
-
for (const rawLine of content.split(`
|
|
14974
|
-
`)) {
|
|
14975
|
-
const line = rawLine.trim();
|
|
14976
|
-
if (!line || line.startsWith("#"))
|
|
14977
|
-
continue;
|
|
14978
|
-
const sectionMatch = line.match(/^\[([^\]]+)\]$/);
|
|
14979
|
-
if (sectionMatch) {
|
|
14980
|
-
section = sectionMatch[1];
|
|
14981
|
-
continue;
|
|
14982
|
-
}
|
|
14983
|
-
const kvMatch = line.match(/^([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.+)$/);
|
|
14984
|
-
if (!kvMatch)
|
|
14985
|
-
continue;
|
|
14986
|
-
const value = stripScalarSyntax(kvMatch[2]);
|
|
14987
|
-
if (section === null && kvMatch[1] === "project_id") {
|
|
14988
|
-
projectId = value;
|
|
14989
|
-
} else if (section === "db" && kvMatch[1] === "port") {
|
|
14990
|
-
dbPort = value;
|
|
14991
|
-
}
|
|
14992
|
-
}
|
|
14993
|
-
if (!projectId || !isSafeDockerServiceName(projectId))
|
|
14994
|
-
return null;
|
|
14995
|
-
return {
|
|
14996
|
-
projectId,
|
|
14997
|
-
dbPort: dbPort && /^\d+$/.test(dbPort) ? dbPort : DEFAULT_SUPABASE_DB_PORT
|
|
14998
|
-
};
|
|
14999
|
-
}
|
|
15000
|
-
async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal) {
|
|
15001
|
-
const cacheKey = discoveryCacheKey(cwd, omitDirNames);
|
|
15002
|
-
const now = Date.now();
|
|
15003
|
-
const cached = supabaseDiscoveryCache.get(cacheKey);
|
|
15004
|
-
if (cached && cached.expiresAt > now) {
|
|
15005
|
-
return cloneSupabaseDiscoveryResult(cached.result);
|
|
15006
|
-
}
|
|
15007
|
-
const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
|
|
15008
|
-
omitSet.add(".git");
|
|
15009
|
-
omitSet.add("node_modules");
|
|
15010
|
-
const results = [];
|
|
15011
|
-
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_SUPABASE_PROJECTS, async (dir) => {
|
|
15012
|
-
const configPath = join9(dir, "supabase", "config.toml");
|
|
15013
|
-
if (!await pathExistsAsync(configPath))
|
|
15014
|
-
return;
|
|
15015
|
-
try {
|
|
15016
|
-
const content = await readFile2(configPath, "utf-8");
|
|
15017
|
-
const parsed = parseSupabaseConfigToml(content);
|
|
15018
|
-
if (!parsed)
|
|
15019
|
-
return;
|
|
15020
|
-
const relDir = relative4(cwd, dir);
|
|
15021
|
-
const isRoot = relDir === "" || relDir === ".";
|
|
15022
|
-
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
15023
|
-
const id = isRoot ? `supabase:${parsed.projectId}` : `supabase:${parsed.projectId}@${encodeURIComponent(relDirSlash)}`;
|
|
15024
|
-
const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
|
|
15025
|
-
results.push({
|
|
15026
|
-
id,
|
|
15027
|
-
path: isRoot ? "supabase/config.toml" : `${relDirSlash}/supabase/config.toml`,
|
|
15028
|
-
name: `${parsed.projectId} (Supabase CLI, postgres@127.0.0.1:${parsed.dbPort}/postgres${labelPath})`,
|
|
15029
|
-
sizeBytes: 0,
|
|
15030
|
-
kind: "postgresql",
|
|
15031
|
-
projectId: parsed.projectId,
|
|
15032
|
-
relDirSlash,
|
|
15033
|
-
dbPort: parsed.dbPort
|
|
15034
|
-
});
|
|
15035
|
-
} catch {}
|
|
15036
|
-
}, signal);
|
|
15037
|
-
if (signal?.aborted)
|
|
15038
|
-
return cloneSupabaseDiscoveryResult(results);
|
|
15039
|
-
supabaseDiscoveryCache.set(cacheKey, {
|
|
15040
|
-
expiresAt: now + SUPABASE_DISCOVERY_TTL_MS,
|
|
15041
|
-
result: cloneSupabaseDiscoveryResult(results)
|
|
15042
|
-
});
|
|
15043
|
-
return cloneSupabaseDiscoveryResult(results);
|
|
15044
|
-
}
|
|
15045
|
-
function parseSupabaseDbId(dbId) {
|
|
15046
|
-
if (!dbId.startsWith("supabase:"))
|
|
15047
|
-
return null;
|
|
15048
|
-
const rest = dbId.slice("supabase:".length);
|
|
15049
|
-
if (!rest)
|
|
15050
|
-
return null;
|
|
15051
|
-
const atIdx = rest.indexOf("@");
|
|
15052
|
-
let projectId;
|
|
15053
|
-
let relDir = "";
|
|
15054
|
-
if (atIdx >= 0) {
|
|
15055
|
-
if (rest.indexOf("@", atIdx + 1) >= 0)
|
|
15363
|
+
if (colonIdx2 >= 0) {
|
|
15364
|
+
database = afterAt.slice(colonIdx2 + 1);
|
|
15365
|
+
rest = `${rest.slice(0, atIdx)}@${afterAt.slice(0, colonIdx2)}`;
|
|
15366
|
+
}
|
|
15367
|
+
const [serviceName, encodedRel] = rest.split("@");
|
|
15368
|
+
if (!isSafeDockerServiceName(serviceName))
|
|
15369
|
+
return null;
|
|
15370
|
+
if (!isSafeDockerDatabaseName(database))
|
|
15056
15371
|
return null;
|
|
15057
|
-
projectId = rest.slice(0, atIdx);
|
|
15058
15372
|
try {
|
|
15059
|
-
relDir = decodeURIComponent(
|
|
15373
|
+
const relDir = decodeURIComponent(encodedRel || "");
|
|
15374
|
+
if (!isSafeDockerRelDir(relDir))
|
|
15375
|
+
return null;
|
|
15376
|
+
return {
|
|
15377
|
+
serviceName,
|
|
15378
|
+
relDir,
|
|
15379
|
+
database,
|
|
15380
|
+
kind
|
|
15381
|
+
};
|
|
15060
15382
|
} catch {
|
|
15061
15383
|
return null;
|
|
15062
15384
|
}
|
|
15063
|
-
if (!isSafeDockerRelDir(relDir))
|
|
15064
|
-
return null;
|
|
15065
|
-
} else {
|
|
15066
|
-
projectId = rest;
|
|
15067
15385
|
}
|
|
15068
|
-
|
|
15386
|
+
const colonIdx = rest.indexOf(":");
|
|
15387
|
+
if (colonIdx >= 0) {
|
|
15388
|
+
database = rest.slice(colonIdx + 1);
|
|
15389
|
+
rest = rest.slice(0, colonIdx);
|
|
15390
|
+
}
|
|
15391
|
+
if (!isSafeDockerServiceName(rest))
|
|
15069
15392
|
return null;
|
|
15070
|
-
|
|
15393
|
+
if (!isSafeDockerDatabaseName(database))
|
|
15394
|
+
return null;
|
|
15395
|
+
return { serviceName: rest, relDir: "", database, kind };
|
|
15071
15396
|
}
|
|
15072
|
-
|
|
15073
|
-
const parsed =
|
|
15397
|
+
function canonicalizeDockerDbId(dbId) {
|
|
15398
|
+
const parsed = parseDockerDbId(dbId);
|
|
15074
15399
|
if (!parsed)
|
|
15075
15400
|
return null;
|
|
15076
|
-
const
|
|
15077
|
-
|
|
15401
|
+
const database = parsed.database ? `:${parsed.database}` : "";
|
|
15402
|
+
const kindSuffix = parsed.kind ? `#${parsed.kind}` : "";
|
|
15403
|
+
if (!parsed.relDir)
|
|
15404
|
+
return `docker:${parsed.serviceName}${database}${kindSuffix}`;
|
|
15405
|
+
return `docker:${parsed.serviceName}@${encodeURIComponent(parsed.relDir)}${database}${kindSuffix}`;
|
|
15078
15406
|
}
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
];
|
|
15089
|
-
dockerDiscoveryCache = new Map;
|
|
15090
|
-
DB_KIND_VALUES = new Set([
|
|
15091
|
-
"sqlite",
|
|
15092
|
-
"postgresql",
|
|
15093
|
-
"mysql",
|
|
15094
|
-
"redis",
|
|
15095
|
-
"elasticsearch",
|
|
15096
|
-
"s3",
|
|
15097
|
-
"dynamodb"
|
|
15098
|
-
]);
|
|
15099
|
-
supabaseDiscoveryCache = new Map;
|
|
15100
|
-
});
|
|
15101
|
-
|
|
15102
|
-
// web-src/server/worktree-watcher.ts
|
|
15103
|
-
import {
|
|
15104
|
-
lstatSync as lstatSync3,
|
|
15105
|
-
readdirSync as nodeReaddirSync,
|
|
15106
|
-
watch as nodeWatch
|
|
15107
|
-
} from "node:fs";
|
|
15108
|
-
import { join as join10, relative as relative5 } from "node:path";
|
|
15109
|
-
function normalizeRelativePath(path) {
|
|
15110
|
-
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
15407
|
+
async function findDockerServiceByDbIdAsync(cwd, dbId, kind, omitDirNames, signal) {
|
|
15408
|
+
const parsed = parseDockerDbId(dbId);
|
|
15409
|
+
if (!parsed)
|
|
15410
|
+
return null;
|
|
15411
|
+
if (kind && parsed.kind && parsed.kind !== kind)
|
|
15412
|
+
return null;
|
|
15413
|
+
const effectiveKind = parsed.kind ?? kind;
|
|
15414
|
+
const services = await discoverDockerDatabasesAsync(cwd, omitDirNames, signal);
|
|
15415
|
+
return services.find((d) => d.serviceName === parsed.serviceName && d.relDirSlash === parsed.relDir && (!effectiveKind || d.kind === effectiveKind)) || null;
|
|
15111
15416
|
}
|
|
15112
|
-
function
|
|
15113
|
-
|
|
15114
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
15417
|
+
function isSafeDockerServiceName(value) {
|
|
15418
|
+
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
15115
15419
|
}
|
|
15116
|
-
function
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
|
|
15127
|
-
|
|
15128
|
-
|
|
15129
|
-
|
|
15130
|
-
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
const
|
|
15141
|
-
|
|
15142
|
-
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
const pendingChangedPaths = new Set;
|
|
15150
|
-
let watchLimitReported = false;
|
|
15151
|
-
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
15152
|
-
const directoryRelativePath = (dir) => normalizeRelativePath(relative5(options.root, dir));
|
|
15153
|
-
const ignoredDirectory = (dir) => {
|
|
15154
|
-
const rel = directoryRelativePath(dir);
|
|
15155
|
-
return Boolean(rel && ignored(rel));
|
|
15156
|
-
};
|
|
15157
|
-
const scheduleUpdate = (changedPath) => {
|
|
15158
|
-
if (changedPath)
|
|
15159
|
-
pendingChangedPaths.add(changedPath);
|
|
15160
|
-
if (timer)
|
|
15161
|
-
clearTimer(timer);
|
|
15162
|
-
timer = setTimer(() => {
|
|
15163
|
-
timer = null;
|
|
15164
|
-
const paths = pendingChangedPaths.size ? [...pendingChangedPaths] : undefined;
|
|
15165
|
-
pendingChangedPaths.clear();
|
|
15166
|
-
options.onUpdate(paths);
|
|
15167
|
-
}, debounceMs);
|
|
15168
|
-
};
|
|
15169
|
-
const reportWatchLimit = () => {
|
|
15170
|
-
if (watchLimitReported)
|
|
15171
|
-
return;
|
|
15172
|
-
watchLimitReported = true;
|
|
15173
|
-
options.onWatchLimit?.(maxWatchedDirectories);
|
|
15174
|
-
options.onError?.(new Error(`worktree watcher cap reached (${maxWatchedDirectories}); subsequent changes may be missed`));
|
|
15175
|
-
};
|
|
15176
|
-
const closeSubtree = (dir) => {
|
|
15177
|
-
for (const [watchedDir, watcher] of [...watchers]) {
|
|
15178
|
-
if (watchedDir !== dir && !watchedDir.startsWith(`${dir}/`))
|
|
15179
|
-
continue;
|
|
15180
|
-
try {
|
|
15181
|
-
watcher.close?.();
|
|
15182
|
-
} catch {}
|
|
15183
|
-
watchers.delete(watchedDir);
|
|
15184
|
-
signatures.delete(watchedDir);
|
|
15185
|
-
}
|
|
15186
|
-
};
|
|
15187
|
-
const closeAll = () => {
|
|
15188
|
-
if (initialScanTimer) {
|
|
15189
|
-
clearTimer(initialScanTimer);
|
|
15190
|
-
initialScanTimer = null;
|
|
15191
|
-
}
|
|
15192
|
-
if (pathInspectionTimer) {
|
|
15193
|
-
clearTimer(pathInspectionTimer);
|
|
15194
|
-
pathInspectionTimer = null;
|
|
15195
|
-
}
|
|
15196
|
-
initialScanQueue.length = 0;
|
|
15197
|
-
pendingPathInspections.clear();
|
|
15198
|
-
for (const watcher of [...watchers.values()]) {
|
|
15199
|
-
try {
|
|
15200
|
-
watcher.close?.();
|
|
15201
|
-
} catch {}
|
|
15202
|
-
}
|
|
15203
|
-
watchers.clear();
|
|
15204
|
-
signatures.clear();
|
|
15205
|
-
};
|
|
15206
|
-
const readChildDirectories = (dir) => {
|
|
15207
|
-
let entries;
|
|
15208
|
-
try {
|
|
15209
|
-
entries = readDirs(dir);
|
|
15210
|
-
} catch (error) {
|
|
15211
|
-
options.onError?.(error);
|
|
15212
|
-
return [];
|
|
15213
|
-
}
|
|
15214
|
-
const children = [];
|
|
15215
|
-
for (const entry of entries) {
|
|
15216
|
-
if (!entry.isDirectory())
|
|
15217
|
-
continue;
|
|
15218
|
-
const child = join10(dir, entry.name);
|
|
15219
|
-
if (ignoredDirectory(child))
|
|
15220
|
-
continue;
|
|
15221
|
-
children.push(child);
|
|
15222
|
-
}
|
|
15223
|
-
return children;
|
|
15224
|
-
};
|
|
15225
|
-
const processInitialScanQueue = () => {
|
|
15226
|
-
initialScanTimer = null;
|
|
15227
|
-
if (watchers.size >= maxWatchedDirectories) {
|
|
15228
|
-
reportWatchLimit();
|
|
15229
|
-
initialScanQueue.length = 0;
|
|
15230
|
-
return;
|
|
15231
|
-
}
|
|
15232
|
-
const next = initialScanQueue.shift();
|
|
15233
|
-
if (next) {
|
|
15234
|
-
processingInitialScan = true;
|
|
15235
|
-
try {
|
|
15236
|
-
watchDirectory(next, true);
|
|
15237
|
-
} finally {
|
|
15238
|
-
processingInitialScan = false;
|
|
15239
|
-
}
|
|
15240
|
-
}
|
|
15241
|
-
if (watchers.size >= maxWatchedDirectories) {
|
|
15242
|
-
reportWatchLimit();
|
|
15243
|
-
initialScanQueue.length = 0;
|
|
15244
|
-
}
|
|
15245
|
-
if (initialScanQueue.length)
|
|
15246
|
-
initialScanTimer = setTimer(processInitialScanQueue, 50);
|
|
15247
|
-
};
|
|
15248
|
-
const queueInitialChildren = (dir) => {
|
|
15249
|
-
const remaining = maxWatchedDirectories - watchers.size;
|
|
15250
|
-
if (remaining <= 0) {
|
|
15251
|
-
reportWatchLimit();
|
|
15252
|
-
return;
|
|
15253
|
-
}
|
|
15254
|
-
const children = readChildDirectories(dir);
|
|
15255
|
-
if (children.length > remaining)
|
|
15256
|
-
reportWatchLimit();
|
|
15257
|
-
initialScanQueue.push(...children.slice(0, remaining));
|
|
15258
|
-
if (!initialScanTimer && !processingInitialScan)
|
|
15259
|
-
initialScanTimer = setTimer(processInitialScanQueue, 5000);
|
|
15260
|
-
};
|
|
15261
|
-
const processChangedPath = (changed, fullChangedPath) => {
|
|
15262
|
-
const known = watchers.has(fullChangedPath);
|
|
15263
|
-
if (isDirectory(fullChangedPath)) {
|
|
15264
|
-
if (known) {
|
|
15265
|
-
const signature = directorySignature(fullChangedPath);
|
|
15266
|
-
if (signature && signature !== signatures.get(fullChangedPath)) {
|
|
15267
|
-
closeSubtree(fullChangedPath);
|
|
15268
|
-
watchDirectory(fullChangedPath, initialScanAsync);
|
|
15269
|
-
}
|
|
15270
|
-
scheduleUpdate(changed);
|
|
15271
|
-
return;
|
|
15272
|
-
}
|
|
15273
|
-
watchDirectory(fullChangedPath, initialScanAsync);
|
|
15274
|
-
} else if (known) {
|
|
15275
|
-
closeSubtree(fullChangedPath);
|
|
15420
|
+
function isSafeDockerRelDir(value) {
|
|
15421
|
+
if (value === "")
|
|
15422
|
+
return true;
|
|
15423
|
+
if (!/^[A-Za-z0-9_./-]+$/.test(value))
|
|
15424
|
+
return false;
|
|
15425
|
+
const parts = value.split("/");
|
|
15426
|
+
return parts.every((p) => p !== "" && p !== "." && p !== "..");
|
|
15427
|
+
}
|
|
15428
|
+
function isSafeDockerDatabaseName(value) {
|
|
15429
|
+
if (value === undefined)
|
|
15430
|
+
return true;
|
|
15431
|
+
if (value === "")
|
|
15432
|
+
return false;
|
|
15433
|
+
if (hasControlCharacter(value))
|
|
15434
|
+
return false;
|
|
15435
|
+
return /^[A-Za-z0-9_$.-]+$/.test(value);
|
|
15436
|
+
}
|
|
15437
|
+
function cloneSupabaseDiscoveryResult(result) {
|
|
15438
|
+
return result.map((entry) => ({ ...entry }));
|
|
15439
|
+
}
|
|
15440
|
+
function parseSupabaseConfigToml(content) {
|
|
15441
|
+
let section = null;
|
|
15442
|
+
let projectId = null;
|
|
15443
|
+
let dbPort = null;
|
|
15444
|
+
for (const rawLine of content.split(`
|
|
15445
|
+
`)) {
|
|
15446
|
+
const line = rawLine.trim();
|
|
15447
|
+
if (!line || line.startsWith("#"))
|
|
15448
|
+
continue;
|
|
15449
|
+
const sectionMatch = line.match(/^\[([^\]]+)\]$/);
|
|
15450
|
+
if (sectionMatch) {
|
|
15451
|
+
section = sectionMatch[1];
|
|
15452
|
+
continue;
|
|
15276
15453
|
}
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15454
|
+
const kvMatch = line.match(/^([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.+)$/);
|
|
15455
|
+
if (!kvMatch)
|
|
15456
|
+
continue;
|
|
15457
|
+
const value = stripScalarSyntax(kvMatch[2]);
|
|
15458
|
+
if (section === null && kvMatch[1] === "project_id") {
|
|
15459
|
+
projectId = value;
|
|
15460
|
+
} else if (section === "db" && kvMatch[1] === "port") {
|
|
15461
|
+
dbPort = value;
|
|
15285
15462
|
}
|
|
15463
|
+
}
|
|
15464
|
+
if (!projectId || !isSafeDockerServiceName(projectId))
|
|
15465
|
+
return null;
|
|
15466
|
+
return {
|
|
15467
|
+
projectId,
|
|
15468
|
+
dbPort: dbPort && /^\d+$/.test(dbPort) ? dbPort : DEFAULT_SUPABASE_DB_PORT
|
|
15286
15469
|
};
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
|
|
15470
|
+
}
|
|
15471
|
+
async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal) {
|
|
15472
|
+
const cacheKey = discoveryCacheKey(cwd, omitDirNames);
|
|
15473
|
+
const now = Date.now();
|
|
15474
|
+
const cached = supabaseDiscoveryCache.get(cacheKey);
|
|
15475
|
+
if (cached && cached.expiresAt > now) {
|
|
15476
|
+
return cloneSupabaseDiscoveryResult(cached.result);
|
|
15477
|
+
}
|
|
15478
|
+
const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
|
|
15479
|
+
omitSet.add(".git");
|
|
15480
|
+
omitSet.add("node_modules");
|
|
15481
|
+
const results = [];
|
|
15482
|
+
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_SUPABASE_PROJECTS, async (dir) => {
|
|
15483
|
+
const configPath = join11(dir, "supabase", "config.toml");
|
|
15484
|
+
if (!await pathExistsAsync(configPath))
|
|
15301
15485
|
return;
|
|
15302
15486
|
try {
|
|
15303
|
-
const
|
|
15304
|
-
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
const signature = directorySignature(dir);
|
|
15322
|
-
if (signature)
|
|
15323
|
-
signatures.set(dir, signature);
|
|
15324
|
-
watcher.on?.("error", () => {
|
|
15325
|
-
if (watchers.get(dir) === watcher) {
|
|
15326
|
-
watchers.delete(dir);
|
|
15327
|
-
signatures.delete(dir);
|
|
15328
|
-
}
|
|
15329
|
-
});
|
|
15330
|
-
watcher.on?.("close", () => {
|
|
15331
|
-
if (watchers.get(dir) === watcher) {
|
|
15332
|
-
watchers.delete(dir);
|
|
15333
|
-
signatures.delete(dir);
|
|
15334
|
-
}
|
|
15487
|
+
const content = await readFile2(configPath, "utf-8");
|
|
15488
|
+
const parsed = parseSupabaseConfigToml(content);
|
|
15489
|
+
if (!parsed)
|
|
15490
|
+
return;
|
|
15491
|
+
const relDir = relative5(cwd, dir);
|
|
15492
|
+
const isRoot = relDir === "" || relDir === ".";
|
|
15493
|
+
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
15494
|
+
const id = isRoot ? `supabase:${parsed.projectId}` : `supabase:${parsed.projectId}@${encodeURIComponent(relDirSlash)}`;
|
|
15495
|
+
const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
|
|
15496
|
+
results.push({
|
|
15497
|
+
id,
|
|
15498
|
+
path: isRoot ? "supabase/config.toml" : `${relDirSlash}/supabase/config.toml`,
|
|
15499
|
+
name: `${parsed.projectId} (Supabase CLI, postgres@127.0.0.1:${parsed.dbPort}/postgres${labelPath})`,
|
|
15500
|
+
sizeBytes: 0,
|
|
15501
|
+
kind: "postgresql",
|
|
15502
|
+
projectId: parsed.projectId,
|
|
15503
|
+
relDirSlash,
|
|
15504
|
+
dbPort: parsed.dbPort
|
|
15335
15505
|
});
|
|
15336
|
-
} catch
|
|
15337
|
-
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15506
|
+
} catch {}
|
|
15507
|
+
}, signal);
|
|
15508
|
+
if (signal?.aborted)
|
|
15509
|
+
return cloneSupabaseDiscoveryResult(results);
|
|
15510
|
+
supabaseDiscoveryCache.set(cacheKey, {
|
|
15511
|
+
expiresAt: now + SUPABASE_DISCOVERY_TTL_MS,
|
|
15512
|
+
result: cloneSupabaseDiscoveryResult(results)
|
|
15513
|
+
});
|
|
15514
|
+
return cloneSupabaseDiscoveryResult(results);
|
|
15515
|
+
}
|
|
15516
|
+
function parseSupabaseDbId(dbId) {
|
|
15517
|
+
if (!dbId.startsWith("supabase:"))
|
|
15518
|
+
return null;
|
|
15519
|
+
const rest = dbId.slice("supabase:".length);
|
|
15520
|
+
if (!rest)
|
|
15521
|
+
return null;
|
|
15522
|
+
const atIdx = rest.indexOf("@");
|
|
15523
|
+
let projectId;
|
|
15524
|
+
let relDir = "";
|
|
15525
|
+
if (atIdx >= 0) {
|
|
15526
|
+
if (rest.indexOf("@", atIdx + 1) >= 0)
|
|
15527
|
+
return null;
|
|
15528
|
+
projectId = rest.slice(0, atIdx);
|
|
15529
|
+
try {
|
|
15530
|
+
relDir = decodeURIComponent(rest.slice(atIdx + 1));
|
|
15531
|
+
} catch {
|
|
15532
|
+
return null;
|
|
15347
15533
|
}
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
}
|
|
15351
|
-
|
|
15352
|
-
|
|
15534
|
+
if (!isSafeDockerRelDir(relDir))
|
|
15535
|
+
return null;
|
|
15536
|
+
} else {
|
|
15537
|
+
projectId = rest;
|
|
15538
|
+
}
|
|
15539
|
+
if (!isSafeDockerServiceName(projectId))
|
|
15540
|
+
return null;
|
|
15541
|
+
return { projectId, relDir };
|
|
15353
15542
|
}
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15543
|
+
async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal) {
|
|
15544
|
+
const parsed = parseSupabaseDbId(dbId);
|
|
15545
|
+
if (!parsed)
|
|
15546
|
+
return null;
|
|
15547
|
+
const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
|
|
15548
|
+
return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
|
|
15549
|
+
}
|
|
15550
|
+
var SQLITE_EXTENSIONS, SQLITE_MAGIC = "SQLite format 3\x00", MAX_SCAN_DEPTH = 3, MAX_ENTRIES = 50, DOCKER_DISCOVERY_TTL_MS = 5000, SQLITE_DISCOVERY_TTL_MS = 5000, sqliteDiscoveryCache, COMPOSE_FILENAMES, MAX_DOCKER_SERVICES = 30, dockerDiscoveryCache, DB_KIND_VALUES, MAX_SUPABASE_PROJECTS = 30, SUPABASE_DISCOVERY_TTL_MS = 5000, DEFAULT_SUPABASE_DB_PORT = "54322", supabaseDiscoveryCache;
|
|
15551
|
+
var init_discovery = __esm(() => {
|
|
15552
|
+
SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
|
|
15553
|
+
sqliteDiscoveryCache = new Map;
|
|
15554
|
+
COMPOSE_FILENAMES = [
|
|
15555
|
+
"docker-compose.yml",
|
|
15556
|
+
"docker-compose.yaml",
|
|
15557
|
+
"compose.yml",
|
|
15558
|
+
"compose.yaml"
|
|
15559
|
+
];
|
|
15560
|
+
dockerDiscoveryCache = new Map;
|
|
15561
|
+
DB_KIND_VALUES = new Set([
|
|
15562
|
+
"sqlite",
|
|
15563
|
+
"postgresql",
|
|
15564
|
+
"mysql",
|
|
15565
|
+
"redis",
|
|
15566
|
+
"elasticsearch",
|
|
15567
|
+
"s3",
|
|
15568
|
+
"dynamodb"
|
|
15569
|
+
]);
|
|
15570
|
+
supabaseDiscoveryCache = new Map;
|
|
15357
15571
|
});
|
|
15358
15572
|
|
|
15359
15573
|
// web-src/server/state-store.ts
|
|
15360
|
-
import { join as
|
|
15574
|
+
import { join as join12 } from "node:path";
|
|
15361
15575
|
function codeViewerPath(root, fileName) {
|
|
15362
|
-
return
|
|
15576
|
+
return join12(root, CODE_VIEWER_DIR2, fileName);
|
|
15363
15577
|
}
|
|
15364
15578
|
function isRecord(value) {
|
|
15365
15579
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -16239,7 +16453,7 @@ var init_d1 = __esm(() => {
|
|
|
16239
16453
|
|
|
16240
16454
|
// web-src/server/database/adapters/dynamodb.ts
|
|
16241
16455
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
16242
|
-
import { createHash as
|
|
16456
|
+
import { createHash as createHash6, createHmac as createHmac2 } from "node:crypto";
|
|
16243
16457
|
function createDynamoDbRequestDeadline() {
|
|
16244
16458
|
const timeoutMs = dynamoDbRequestTimeoutMs;
|
|
16245
16459
|
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
@@ -16263,7 +16477,7 @@ function hmac2(key, value) {
|
|
|
16263
16477
|
return createHmac2("sha256", key).update(value, "utf8").digest();
|
|
16264
16478
|
}
|
|
16265
16479
|
function sha2562(value) {
|
|
16266
|
-
return
|
|
16480
|
+
return createHash6("sha256").update(value, "utf8").digest("hex");
|
|
16267
16481
|
}
|
|
16268
16482
|
function amzDate2(date = new Date) {
|
|
16269
16483
|
const iso = date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
@@ -16908,7 +17122,7 @@ var init_credential_store = __esm(() => {
|
|
|
16908
17122
|
// web-src/server/database/connections-store.ts
|
|
16909
17123
|
import { randomUUID } from "node:crypto";
|
|
16910
17124
|
import { chmod } from "node:fs/promises";
|
|
16911
|
-
import { join as
|
|
17125
|
+
import { join as join13 } from "node:path";
|
|
16912
17126
|
function secretKey(cwd, id) {
|
|
16913
17127
|
return `${cwd}\x00${id}`;
|
|
16914
17128
|
}
|
|
@@ -16959,7 +17173,7 @@ function withRuntimeSecrets(cwd, connection) {
|
|
|
16959
17173
|
};
|
|
16960
17174
|
}
|
|
16961
17175
|
function connectionsFilePath(root) {
|
|
16962
|
-
return
|
|
17176
|
+
return join13(root, ".code-viewer", CONNECTIONS_FILE_NAME);
|
|
16963
17177
|
}
|
|
16964
17178
|
function emptyState() {
|
|
16965
17179
|
return { version: 1, connections: [] };
|
|
@@ -18771,9 +18985,9 @@ var init_handle_s3 = __esm(() => {
|
|
|
18771
18985
|
});
|
|
18772
18986
|
|
|
18773
18987
|
// web-src/server/database/query-history.ts
|
|
18774
|
-
import { join as
|
|
18988
|
+
import { join as join14 } from "node:path";
|
|
18775
18989
|
function historyFilePath(root) {
|
|
18776
|
-
return
|
|
18990
|
+
return join14(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
|
|
18777
18991
|
}
|
|
18778
18992
|
function emptyState2() {
|
|
18779
18993
|
return { version: 1, entries: [] };
|
|
@@ -18921,11 +19135,11 @@ var init_query_history = __esm(() => {
|
|
|
18921
19135
|
});
|
|
18922
19136
|
|
|
18923
19137
|
// web-src/server/database/snapshot-store.ts
|
|
18924
|
-
import { createHash as
|
|
19138
|
+
import { createHash as createHash7, randomBytes as randomBytes2 } from "node:crypto";
|
|
18925
19139
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
18926
|
-
import { join as
|
|
19140
|
+
import { join as join15 } from "node:path";
|
|
18927
19141
|
async function getStoreDb(cwd) {
|
|
18928
|
-
const dbPath =
|
|
19142
|
+
const dbPath = join15(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
|
|
18929
19143
|
if (storeDb && storeDbPath === dbPath)
|
|
18930
19144
|
return storeDb;
|
|
18931
19145
|
if (storeDb) {
|
|
@@ -18933,7 +19147,7 @@ async function getStoreDb(cwd) {
|
|
|
18933
19147
|
storeDb.close();
|
|
18934
19148
|
} catch {}
|
|
18935
19149
|
}
|
|
18936
|
-
mkdirSync3(
|
|
19150
|
+
mkdirSync3(join15(cwd, CODE_VIEWER_DIR4), { recursive: true });
|
|
18937
19151
|
const DbClass = await loadSqliteClass();
|
|
18938
19152
|
storeDb = new DbClass(dbPath);
|
|
18939
19153
|
storeDbPath = dbPath;
|
|
@@ -18952,7 +19166,7 @@ function makeId2(prefix) {
|
|
|
18952
19166
|
return `${prefix}-${randomBytes2(8).toString("hex")}`;
|
|
18953
19167
|
}
|
|
18954
19168
|
function hashPayload(payloadJson) {
|
|
18955
|
-
return
|
|
19169
|
+
return createHash7("sha256").update(payloadJson).digest("hex");
|
|
18956
19170
|
}
|
|
18957
19171
|
function hashLengthPrefixed(hasher, value) {
|
|
18958
19172
|
hasher.update(`${Buffer.byteLength(value, "utf8")}:`);
|
|
@@ -19027,7 +19241,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
|
|
|
19027
19241
|
db.exec("BEGIN");
|
|
19028
19242
|
try {
|
|
19029
19243
|
for (const row of rows) {
|
|
19030
|
-
const rowKeyHash =
|
|
19244
|
+
const rowKeyHash = createHash7("sha256").update(row.rowKeyJson).digest("hex");
|
|
19031
19245
|
const payloadHash = hashPayload(row.payloadJson);
|
|
19032
19246
|
insertRow.run(revisionId, rowKeyHash, row.rowKeyJson, row.rowHash, payloadHash);
|
|
19033
19247
|
insertPayload.run(payloadHash, row.payloadJson);
|
|
@@ -19041,7 +19255,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
|
|
|
19041
19255
|
}
|
|
19042
19256
|
}
|
|
19043
19257
|
function computeRevisionTableHash(db, revisionId) {
|
|
19044
|
-
const hasher =
|
|
19258
|
+
const hasher = createHash7("sha256");
|
|
19045
19259
|
hashLengthPrefixed(hasher, `snapshot-table-v${SNAPSHOT_TABLE_HASH_VERSION}`);
|
|
19046
19260
|
let rowCount = 0;
|
|
19047
19261
|
let last;
|
|
@@ -19539,9 +19753,9 @@ var init_snapshot_runner = __esm(() => {
|
|
|
19539
19753
|
});
|
|
19540
19754
|
|
|
19541
19755
|
// web-src/server/database/tabs-store.ts
|
|
19542
|
-
import { join as
|
|
19756
|
+
import { join as join16 } from "node:path";
|
|
19543
19757
|
function tabsFilePath(root) {
|
|
19544
|
-
return
|
|
19758
|
+
return join16(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
|
|
19545
19759
|
}
|
|
19546
19760
|
function emptyState3() {
|
|
19547
19761
|
return { version: 1, tabs: [], activeTabId: null };
|
|
@@ -21495,7 +21709,7 @@ var init_handle = __esm(() => {
|
|
|
21495
21709
|
|
|
21496
21710
|
// web-src/server/doctor.ts
|
|
21497
21711
|
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync6 } from "node:fs";
|
|
21498
|
-
import { dirname as dirname5, join as
|
|
21712
|
+
import { dirname as dirname5, join as join17, relative as relative6 } from "node:path";
|
|
21499
21713
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21500
21714
|
function statusWorse(a, b) {
|
|
21501
21715
|
const rank = { ok: 0, warn: 1, error: 2 };
|
|
@@ -21589,7 +21803,7 @@ function findCodeViewerPackageJson() {
|
|
|
21589
21803
|
cursor = dirname5(process.argv[1] || ".");
|
|
21590
21804
|
}
|
|
21591
21805
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
21592
|
-
const candidate =
|
|
21806
|
+
const candidate = join17(cursor, "package.json");
|
|
21593
21807
|
try {
|
|
21594
21808
|
const raw = readFileSync6(candidate, "utf8");
|
|
21595
21809
|
const pkg = JSON.parse(raw);
|
|
@@ -21685,7 +21899,7 @@ async function checkSqlite(cwd) {
|
|
|
21685
21899
|
return { id: "sqlite", title: "SQLite driver", rows };
|
|
21686
21900
|
}
|
|
21687
21901
|
async function trySnapshotDbOpen(cwd) {
|
|
21688
|
-
const dbPath =
|
|
21902
|
+
const dbPath = join17(cwd, SNAPSHOT_DB_REL);
|
|
21689
21903
|
try {
|
|
21690
21904
|
statSync6(dbPath);
|
|
21691
21905
|
} catch {
|
|
@@ -21706,7 +21920,7 @@ async function trySnapshotDbOpen(cwd) {
|
|
|
21706
21920
|
}
|
|
21707
21921
|
}
|
|
21708
21922
|
function checkSnapshotStore(cwd) {
|
|
21709
|
-
const dbPath =
|
|
21923
|
+
const dbPath = join17(cwd, SNAPSHOT_DB_REL);
|
|
21710
21924
|
const dir = dirname5(dbPath);
|
|
21711
21925
|
let dirStatus = "ok";
|
|
21712
21926
|
let dirDetail = dir;
|
|
@@ -22725,12 +22939,12 @@ function startDevAssetReload(options) {
|
|
|
22725
22939
|
var init_dev_assets = () => {};
|
|
22726
22940
|
|
|
22727
22941
|
// web-src/server/journal.ts
|
|
22728
|
-
import { join as
|
|
22942
|
+
import { join as join18 } from "node:path";
|
|
22729
22943
|
function dailyJournalFilePath(root) {
|
|
22730
|
-
return
|
|
22944
|
+
return join18(root, CODE_VIEWER_DIR, DAILY_JOURNAL_FILE_NAME);
|
|
22731
22945
|
}
|
|
22732
22946
|
function journalTasksFilePath(root) {
|
|
22733
|
-
return
|
|
22947
|
+
return join18(root, CODE_VIEWER_DIR, JOURNAL_TASKS_FILE_NAME);
|
|
22734
22948
|
}
|
|
22735
22949
|
function emptyDailyJournalState() {
|
|
22736
22950
|
return { version: 1, entries: [] };
|
|
@@ -23343,7 +23557,7 @@ var init_journal2 = __esm(() => {
|
|
|
23343
23557
|
// web-src/server/search-service.ts
|
|
23344
23558
|
import { existsSync as existsSync7, realpathSync as realpathSync6 } from "node:fs";
|
|
23345
23559
|
import { lstat as lstat2, readFile as readFile3 } from "node:fs/promises";
|
|
23346
|
-
import { join as
|
|
23560
|
+
import { join as join19, relative as relative7 } from "node:path";
|
|
23347
23561
|
async function rgAvailableAsync(cwd) {
|
|
23348
23562
|
if (rgAvailableCache !== null)
|
|
23349
23563
|
return rgAvailableCache;
|
|
@@ -23373,7 +23587,7 @@ function safeWorktreePath(env, path) {
|
|
|
23373
23587
|
return null;
|
|
23374
23588
|
if (isGitInternalPath(path))
|
|
23375
23589
|
return null;
|
|
23376
|
-
const full =
|
|
23590
|
+
const full = join19(env.cwd, path);
|
|
23377
23591
|
if (!existsSync7(full))
|
|
23378
23592
|
return null;
|
|
23379
23593
|
let realCwd;
|
|
@@ -23566,7 +23780,7 @@ var init_search_service = __esm(() => {
|
|
|
23566
23780
|
|
|
23567
23781
|
// web-src/server/mcp.ts
|
|
23568
23782
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
23569
|
-
import { join as
|
|
23783
|
+
import { join as join20 } from "node:path";
|
|
23570
23784
|
function defaultMcpTools(options = {}) {
|
|
23571
23785
|
return [
|
|
23572
23786
|
{
|
|
@@ -25040,7 +25254,7 @@ var init_mcp = __esm(() => {
|
|
|
25040
25254
|
init_search_cli();
|
|
25041
25255
|
init_search_service();
|
|
25042
25256
|
init_status_cli();
|
|
25043
|
-
PACKAGE_VERSION = JSON.parse(readFileSync7(
|
|
25257
|
+
PACKAGE_VERSION = JSON.parse(readFileSync7(join20(ROOT, "package.json"), "utf8")).version;
|
|
25044
25258
|
MCP_SERVER_INFO = {
|
|
25045
25259
|
name: "code-viewer",
|
|
25046
25260
|
title: "code-viewer",
|
|
@@ -25048,6 +25262,172 @@ var init_mcp = __esm(() => {
|
|
|
25048
25262
|
};
|
|
25049
25263
|
});
|
|
25050
25264
|
|
|
25265
|
+
// web-src/server/watch-supervisor.ts
|
|
25266
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
25267
|
+
import { join as join21 } from "node:path";
|
|
25268
|
+
function watchChildCommand() {
|
|
25269
|
+
const entry = process.argv[1] ?? "";
|
|
25270
|
+
const script = entry.endsWith(".ts") ? join21(import.meta.dir, "cli.ts") : entry;
|
|
25271
|
+
return [process.argv[0], script, "watch-child"];
|
|
25272
|
+
}
|
|
25273
|
+
function startWatchSupervisor(options) {
|
|
25274
|
+
const spawnChild = options.spawnFn || spawn3;
|
|
25275
|
+
const now = options.nowFn || Date.now;
|
|
25276
|
+
const setTimer = options.setTimeoutFn || setTimeout;
|
|
25277
|
+
const setRepeating = options.setIntervalFn || setInterval;
|
|
25278
|
+
const clearRepeating = options.clearIntervalFn || clearInterval;
|
|
25279
|
+
const heartbeatMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
25280
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
25281
|
+
const command = options.command ?? watchChildCommand();
|
|
25282
|
+
let child = null;
|
|
25283
|
+
let closed = false;
|
|
25284
|
+
let watchFailures = 0;
|
|
25285
|
+
let pollOnly = false;
|
|
25286
|
+
let sawReady = false;
|
|
25287
|
+
let lastSignalAt = now();
|
|
25288
|
+
let watchdog = null;
|
|
25289
|
+
const report = (error) => options.onError?.(error);
|
|
25290
|
+
const abandon = (victim) => {
|
|
25291
|
+
victim.removeAllListeners?.();
|
|
25292
|
+
victim.stdout?.removeAllListeners?.();
|
|
25293
|
+
victim.stderr?.removeAllListeners?.();
|
|
25294
|
+
try {
|
|
25295
|
+
victim.kill("SIGTERM");
|
|
25296
|
+
} catch {}
|
|
25297
|
+
const grace = setTimer(() => {
|
|
25298
|
+
try {
|
|
25299
|
+
victim.kill("SIGKILL");
|
|
25300
|
+
} catch {}
|
|
25301
|
+
}, KILL_GRACE_MS);
|
|
25302
|
+
grace.unref?.();
|
|
25303
|
+
};
|
|
25304
|
+
const handleMessage = (message) => {
|
|
25305
|
+
lastSignalAt = now();
|
|
25306
|
+
if (message.type === "heartbeat")
|
|
25307
|
+
return;
|
|
25308
|
+
if (message.type === "ready") {
|
|
25309
|
+
if (message.pollOnly || message.watching) {
|
|
25310
|
+
sawReady = true;
|
|
25311
|
+
watchFailures = 0;
|
|
25312
|
+
} else {
|
|
25313
|
+
watchFailures++;
|
|
25314
|
+
}
|
|
25315
|
+
return;
|
|
25316
|
+
}
|
|
25317
|
+
if (message.type === "update") {
|
|
25318
|
+
options.onUpdate(message.paths);
|
|
25319
|
+
return;
|
|
25320
|
+
}
|
|
25321
|
+
if (message.type === "watch-limit") {
|
|
25322
|
+
options.onWatchLimit?.(message.limit);
|
|
25323
|
+
return;
|
|
25324
|
+
}
|
|
25325
|
+
if (message.type === "warn")
|
|
25326
|
+
report(new Error(message.message));
|
|
25327
|
+
};
|
|
25328
|
+
const start = () => {
|
|
25329
|
+
if (closed)
|
|
25330
|
+
return;
|
|
25331
|
+
if (!pollOnly && watchFailures >= WATCH_FAILURES_BEFORE_POLL_ONLY) {
|
|
25332
|
+
pollOnly = true;
|
|
25333
|
+
options.onPollOnly?.();
|
|
25334
|
+
}
|
|
25335
|
+
const config = {
|
|
25336
|
+
root: options.root,
|
|
25337
|
+
omitDirNames: options.omitDirNames,
|
|
25338
|
+
excludeNames: options.excludeNames,
|
|
25339
|
+
maxWatchedDirectories: options.maxWatchedDirectories,
|
|
25340
|
+
debounceMs: options.debounceMs ?? 250,
|
|
25341
|
+
pollIntervalMs,
|
|
25342
|
+
heartbeatIntervalMs: heartbeatMs,
|
|
25343
|
+
parentPid: process.pid,
|
|
25344
|
+
pollOnly
|
|
25345
|
+
};
|
|
25346
|
+
let spawned;
|
|
25347
|
+
try {
|
|
25348
|
+
spawned = spawnChild(command[0], command.slice(1), {
|
|
25349
|
+
cwd: options.root,
|
|
25350
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25351
|
+
});
|
|
25352
|
+
} catch (error) {
|
|
25353
|
+
report(error);
|
|
25354
|
+
return;
|
|
25355
|
+
}
|
|
25356
|
+
child = spawned;
|
|
25357
|
+
sawReady = false;
|
|
25358
|
+
lastSignalAt = now();
|
|
25359
|
+
try {
|
|
25360
|
+
spawned.stdin?.write(`${JSON.stringify(config)}
|
|
25361
|
+
`);
|
|
25362
|
+
} catch (error) {
|
|
25363
|
+
report(error);
|
|
25364
|
+
}
|
|
25365
|
+
let buffer = "";
|
|
25366
|
+
spawned.stdout?.setEncoding?.("utf8");
|
|
25367
|
+
spawned.stdout?.on("data", (chunk) => {
|
|
25368
|
+
buffer += chunk;
|
|
25369
|
+
let newline = buffer.indexOf(`
|
|
25370
|
+
`);
|
|
25371
|
+
while (newline !== -1) {
|
|
25372
|
+
const line = buffer.slice(0, newline).trim();
|
|
25373
|
+
buffer = buffer.slice(newline + 1);
|
|
25374
|
+
if (line) {
|
|
25375
|
+
try {
|
|
25376
|
+
handleMessage(JSON.parse(line));
|
|
25377
|
+
} catch {}
|
|
25378
|
+
}
|
|
25379
|
+
newline = buffer.indexOf(`
|
|
25380
|
+
`);
|
|
25381
|
+
}
|
|
25382
|
+
});
|
|
25383
|
+
spawned.stderr?.setEncoding?.("utf8");
|
|
25384
|
+
spawned.stderr?.on("data", (chunk) => {
|
|
25385
|
+
const text = String(chunk).trim();
|
|
25386
|
+
if (text)
|
|
25387
|
+
console.warn(`[code-viewer] watch child: ${text}`);
|
|
25388
|
+
});
|
|
25389
|
+
spawned.on("error", (error) => report(error));
|
|
25390
|
+
spawned.on("exit", () => {
|
|
25391
|
+
if (closed || child !== spawned)
|
|
25392
|
+
return;
|
|
25393
|
+
if (!sawReady)
|
|
25394
|
+
watchFailures++;
|
|
25395
|
+
child = null;
|
|
25396
|
+
const retry = setTimer(start, heartbeatMs);
|
|
25397
|
+
retry.unref?.();
|
|
25398
|
+
});
|
|
25399
|
+
};
|
|
25400
|
+
start();
|
|
25401
|
+
watchdog = setRepeating(() => {
|
|
25402
|
+
if (closed || !child)
|
|
25403
|
+
return;
|
|
25404
|
+
if (now() - lastSignalAt < heartbeatMs * HEARTBEAT_MISS_LIMIT)
|
|
25405
|
+
return;
|
|
25406
|
+
const victim = child;
|
|
25407
|
+
child = null;
|
|
25408
|
+
watchFailures++;
|
|
25409
|
+
report(new Error("watch child stopped reporting; restarting"));
|
|
25410
|
+
abandon(victim);
|
|
25411
|
+
start();
|
|
25412
|
+
}, heartbeatMs);
|
|
25413
|
+
watchdog.unref?.();
|
|
25414
|
+
return {
|
|
25415
|
+
close: () => {
|
|
25416
|
+
closed = true;
|
|
25417
|
+
if (watchdog)
|
|
25418
|
+
clearRepeating(watchdog);
|
|
25419
|
+
watchdog = null;
|
|
25420
|
+
const victim = child;
|
|
25421
|
+
child = null;
|
|
25422
|
+
if (victim)
|
|
25423
|
+
abandon(victim);
|
|
25424
|
+
},
|
|
25425
|
+
pollOnly: () => pollOnly
|
|
25426
|
+
};
|
|
25427
|
+
}
|
|
25428
|
+
var DEFAULT_HEARTBEAT_MS = 5000, DEFAULT_POLL_MS = 15000, HEARTBEAT_MISS_LIMIT = 3, KILL_GRACE_MS = 2000, WATCH_FAILURES_BEFORE_POLL_ONLY = 2;
|
|
25429
|
+
var init_watch_supervisor = () => {};
|
|
25430
|
+
|
|
25051
25431
|
// web-src/server/state-route.ts
|
|
25052
25432
|
var exports_state_route = {};
|
|
25053
25433
|
__export(exports_state_route, {
|
|
@@ -25124,7 +25504,7 @@ import {
|
|
|
25124
25504
|
closeSync as closeSync2,
|
|
25125
25505
|
constants as constants3,
|
|
25126
25506
|
existsSync as existsSync8,
|
|
25127
|
-
lstatSync as
|
|
25507
|
+
lstatSync as lstatSync5,
|
|
25128
25508
|
mkdirSync as mkdirSync4,
|
|
25129
25509
|
openSync as openSync2,
|
|
25130
25510
|
readFileSync as readFileSync8,
|
|
@@ -25136,7 +25516,7 @@ import {
|
|
|
25136
25516
|
writeFileSync as writeFileSync2
|
|
25137
25517
|
} from "node:fs";
|
|
25138
25518
|
import { homedir as homedir3 } from "node:os";
|
|
25139
|
-
import { basename as basename3, dirname as dirname6, extname as extname2, join as
|
|
25519
|
+
import { basename as basename3, dirname as dirname6, extname as extname2, join as join22, relative as relative8 } from "node:path";
|
|
25140
25520
|
function parseCli() {
|
|
25141
25521
|
const rest = [];
|
|
25142
25522
|
for (let i = 2;i < process.argv.length; i++) {
|
|
@@ -25258,7 +25638,7 @@ Examples:
|
|
|
25258
25638
|
}
|
|
25259
25639
|
function warnIfLegacyConfigPresent() {
|
|
25260
25640
|
try {
|
|
25261
|
-
if (existsSync8(
|
|
25641
|
+
if (existsSync8(join22(cwd, ".code-viewer.json"))) {
|
|
25262
25642
|
console.warn("[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed.");
|
|
25263
25643
|
}
|
|
25264
25644
|
} catch {}
|
|
@@ -25365,7 +25745,7 @@ function staticFile(pathname) {
|
|
|
25365
25745
|
const spec = map[pathname];
|
|
25366
25746
|
if (!spec)
|
|
25367
25747
|
return null;
|
|
25368
|
-
const full =
|
|
25748
|
+
const full = join22(WEB_ROOT, spec[0]);
|
|
25369
25749
|
if (!existsSync8(full))
|
|
25370
25750
|
return text("not found", 404);
|
|
25371
25751
|
return new Response(readFileSync8(full), {
|
|
@@ -25623,7 +26003,7 @@ function safeWorktreePath2(path) {
|
|
|
25623
26003
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
25624
26004
|
}
|
|
25625
26005
|
function worktreePath(path) {
|
|
25626
|
-
return
|
|
26006
|
+
return join22(cwd, path);
|
|
25627
26007
|
}
|
|
25628
26008
|
function safeOpenWorktreePath(path) {
|
|
25629
26009
|
if (path === "") {
|
|
@@ -25814,7 +26194,8 @@ async function handleSettings() {
|
|
|
25814
26194
|
watch_limit_effective: scopeWatchLimit,
|
|
25815
26195
|
watch_limit_default: DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
25816
26196
|
watch_limit_min: MIN_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
25817
|
-
watch_limit_max: MAX_WORKTREE_WATCH_DIRECTORY_LIMIT
|
|
26197
|
+
watch_limit_max: MAX_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
26198
|
+
watch_recursive: supportsNativeRecursiveWatch(process.platform)
|
|
25818
26199
|
}
|
|
25819
26200
|
});
|
|
25820
26201
|
}
|
|
@@ -25957,7 +26338,7 @@ async function handleLog(url) {
|
|
|
25957
26338
|
}
|
|
25958
26339
|
function blamePathKey(p) {
|
|
25959
26340
|
try {
|
|
25960
|
-
const st = statSync7(
|
|
26341
|
+
const st = statSync7(join22(cwd, p));
|
|
25961
26342
|
return `${st.mtimeMs}:${st.size}`;
|
|
25962
26343
|
} catch {
|
|
25963
26344
|
return "missing";
|
|
@@ -26496,7 +26877,7 @@ async function handleUploadFiles(req) {
|
|
|
26496
26877
|
total += file.size;
|
|
26497
26878
|
if (total > MAX_UPLOAD_TOTAL_BYTES)
|
|
26498
26879
|
return text("upload too large", 413);
|
|
26499
|
-
const target =
|
|
26880
|
+
const target = join22(realDir, safeName);
|
|
26500
26881
|
if (relative8(realDir, dirname6(target)) !== "")
|
|
26501
26882
|
return text("invalid filename", 400);
|
|
26502
26883
|
if (existsSync8(target))
|
|
@@ -26618,9 +26999,9 @@ function triggerUpdate(changedPaths) {
|
|
|
26618
26999
|
sendSse("update", data);
|
|
26619
27000
|
}
|
|
26620
27001
|
function moveMacPathIntoTrash(path) {
|
|
26621
|
-
const trashDir =
|
|
27002
|
+
const trashDir = join22(homedir3(), ".Trash");
|
|
26622
27003
|
const base = basename3(path) || "code-viewer-trash-item";
|
|
26623
|
-
const target =
|
|
27004
|
+
const target = join22(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
26624
27005
|
try {
|
|
26625
27006
|
mkdirSync4(trashDir, { recursive: true });
|
|
26626
27007
|
renameSync(path, target);
|
|
@@ -26630,7 +27011,7 @@ function moveMacPathIntoTrash(path) {
|
|
|
26630
27011
|
}
|
|
26631
27012
|
}
|
|
26632
27013
|
async function movePathToTrash(path) {
|
|
26633
|
-
|
|
27014
|
+
lstatSync5(path);
|
|
26634
27015
|
if (process.platform === "darwin") {
|
|
26635
27016
|
return moveMacPathIntoTrash(path);
|
|
26636
27017
|
}
|
|
@@ -26662,7 +27043,7 @@ async function restoreTrashPath(originalPath, trashPath) {
|
|
|
26662
27043
|
if (!existsSync8(trashPath))
|
|
26663
27044
|
return { ok: false, error: "trash item not found" };
|
|
26664
27045
|
try {
|
|
26665
|
-
const trashRoot =
|
|
27046
|
+
const trashRoot = join22(homedir3(), ".Trash");
|
|
26666
27047
|
const trashRelative = relative8(trashRoot, trashPath);
|
|
26667
27048
|
if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
|
|
26668
27049
|
return { ok: false, error: "invalid trash handle" };
|
|
@@ -26818,7 +27199,7 @@ async function handleCreateDirectory(req) {
|
|
|
26818
27199
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
26819
27200
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
26820
27201
|
return text("invalid target", 400);
|
|
26821
|
-
const target =
|
|
27202
|
+
const target = join22(parent, name);
|
|
26822
27203
|
if (existsSync8(target))
|
|
26823
27204
|
return text("already exists", 409);
|
|
26824
27205
|
try {
|
|
@@ -27367,18 +27748,19 @@ async function shutdown(exitCode = 0) {
|
|
|
27367
27748
|
}
|
|
27368
27749
|
function startScopedWorktreeWatch() {
|
|
27369
27750
|
watchLimitReached = null;
|
|
27370
|
-
return
|
|
27751
|
+
return startWatchSupervisor({
|
|
27371
27752
|
root: cwd,
|
|
27372
27753
|
omitDirNames: scopeOmitDirNames,
|
|
27373
27754
|
excludeNames: scopeExcludeNames,
|
|
27374
|
-
watch,
|
|
27375
|
-
initialScanMode: "async",
|
|
27376
27755
|
maxWatchedDirectories: scopeWatchLimit,
|
|
27377
27756
|
onUpdate: triggerUpdate,
|
|
27378
27757
|
onWatchLimit: (limit) => {
|
|
27379
27758
|
watchLimitReached = limit;
|
|
27380
27759
|
sendSse("watch-limit", String(limit));
|
|
27381
27760
|
},
|
|
27761
|
+
onPollOnly: () => {
|
|
27762
|
+
console.warn("[code-viewer] file watching is unavailable; updates now come from periodic polling");
|
|
27763
|
+
},
|
|
27382
27764
|
onError: (error) => {
|
|
27383
27765
|
const message = error instanceof Error ? error.message : String(error);
|
|
27384
27766
|
console.warn(`code-viewer worktree watch skipped: ${message}`);
|
|
@@ -27421,9 +27803,10 @@ var init_preview = __esm(async () => {
|
|
|
27421
27803
|
init_search_service();
|
|
27422
27804
|
init_server_registry();
|
|
27423
27805
|
init_state_store();
|
|
27806
|
+
init_watch_supervisor();
|
|
27424
27807
|
init_worktree_watcher();
|
|
27425
|
-
WEB_ROOT =
|
|
27426
|
-
VERSION = JSON.parse(readFileSync8(
|
|
27808
|
+
WEB_ROOT = join22(ROOT, "web");
|
|
27809
|
+
VERSION = JSON.parse(readFileSync8(join22(ROOT, "package.json"), "utf8")).version;
|
|
27427
27810
|
DEFAULT_ARGS = ["HEAD"];
|
|
27428
27811
|
WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
|
|
27429
27812
|
LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
@@ -27719,6 +28102,9 @@ if (process.argv[2] === "agent-help") {
|
|
|
27719
28102
|
} else if (process.argv[2] === "skill") {
|
|
27720
28103
|
const { runSkillCli: runSkillCli2 } = await Promise.resolve().then(() => (init_skill_cli(), exports_skill_cli));
|
|
27721
28104
|
runSkillCli2(process.argv.slice(3));
|
|
28105
|
+
} else if (process.argv[2] === "watch-child") {
|
|
28106
|
+
const { runWatchChild: runWatchChild2 } = await Promise.resolve().then(() => (init_watch_child(), exports_watch_child));
|
|
28107
|
+
await runWatchChild2();
|
|
27722
28108
|
} else if (process.argv[2] === "doctor") {
|
|
27723
28109
|
const { runDoctorCli: runDoctorCli2 } = await Promise.resolve().then(() => (init_doctor_cli(), exports_doctor_cli));
|
|
27724
28110
|
await runDoctorCli2(process.argv.slice(3));
|