@youtyan/code-viewer 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/code-viewer.js +1200 -718
- package/package.json +5 -4
- package/web/app.js +1302 -113
- package/web/index.html +8 -2
- package/web/style.css +328 -0
- package/web/vendor/THIRD_PARTY_NOTICES.txt +24 -0
- package/web/yaml.js +6134 -0
package/dist/code-viewer.js
CHANGED
|
@@ -129,6 +129,19 @@ function makeTimedId(prefix) {
|
|
|
129
129
|
}
|
|
130
130
|
var BASE36_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
131
131
|
|
|
132
|
+
// web-src/core/tools.ts
|
|
133
|
+
function isToolId(value) {
|
|
134
|
+
return TOOL_IDS.includes(value);
|
|
135
|
+
}
|
|
136
|
+
var TOOL_IDS, MIN_TOOLS_SHEET_WIDTH = 480, MAX_TOOLS_SHEET_WIDTH = 4000;
|
|
137
|
+
var init_tools = __esm(() => {
|
|
138
|
+
TOOL_IDS = [
|
|
139
|
+
"markdown",
|
|
140
|
+
"mermaid",
|
|
141
|
+
"json"
|
|
142
|
+
];
|
|
143
|
+
});
|
|
144
|
+
|
|
132
145
|
// web-src/core/routes.ts
|
|
133
146
|
function assertNever(value) {
|
|
134
147
|
throw new Error(`unhandled route: ${JSON.stringify(value)}`);
|
|
@@ -232,6 +245,7 @@ function buildRoute(route) {
|
|
|
232
245
|
}
|
|
233
246
|
var SPA_PATHS, APP_ENTRY_PATHS;
|
|
234
247
|
var init_routes = __esm(() => {
|
|
248
|
+
init_tools();
|
|
235
249
|
SPA_PATHS = [
|
|
236
250
|
"/todif",
|
|
237
251
|
"/todiff",
|
|
@@ -9763,249 +9777,720 @@ var init_agent_help = __esm(() => {
|
|
|
9763
9777
|
];
|
|
9764
9778
|
});
|
|
9765
9779
|
|
|
9766
|
-
// web-src/server/
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
}
|
|
9776
|
-
if (typeof value === "object") {
|
|
9777
|
-
try {
|
|
9778
|
-
return JSON.stringify(value);
|
|
9779
|
-
} catch {
|
|
9780
|
-
return String(value);
|
|
9781
|
-
}
|
|
9782
|
-
}
|
|
9783
|
-
return value;
|
|
9780
|
+
// web-src/server/worktree-watcher.ts
|
|
9781
|
+
import {
|
|
9782
|
+
lstatSync as lstatSync3,
|
|
9783
|
+
readdirSync as nodeReaddirSync,
|
|
9784
|
+
watch as nodeWatch
|
|
9785
|
+
} from "node:fs";
|
|
9786
|
+
import { join as join9, relative as relative4 } from "node:path";
|
|
9787
|
+
function supportsNativeRecursiveWatch(platform) {
|
|
9788
|
+
return platform === "darwin" || platform === "win32";
|
|
9784
9789
|
}
|
|
9785
|
-
function
|
|
9786
|
-
return
|
|
9790
|
+
function normalizeRelativePath(path) {
|
|
9791
|
+
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
9787
9792
|
}
|
|
9788
|
-
function
|
|
9789
|
-
|
|
9793
|
+
function isInsideRoot(root, path) {
|
|
9794
|
+
const rel = relative4(root, path).replace(/\\/g, "/");
|
|
9795
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
9790
9796
|
}
|
|
9791
|
-
function
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
const
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
return null;
|
|
9799
|
-
if (v === "true" || v === "t" || v === "1")
|
|
9800
|
-
return true;
|
|
9801
|
-
if (v === "false" || v === "f" || v === "0")
|
|
9797
|
+
function startWorktreeUpdateWatch(options) {
|
|
9798
|
+
const watch = options.watch || nodeWatch;
|
|
9799
|
+
const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
|
|
9800
|
+
const isDirectory = options.isDirectory || ((path) => {
|
|
9801
|
+
try {
|
|
9802
|
+
return lstatSync3(path).isDirectory();
|
|
9803
|
+
} catch {
|
|
9802
9804
|
return false;
|
|
9803
|
-
|
|
9804
|
-
}
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9805
|
+
}
|
|
9806
|
+
});
|
|
9807
|
+
const directorySignature = options.directorySignature || ((path) => {
|
|
9808
|
+
try {
|
|
9809
|
+
const stats = lstatSync3(path);
|
|
9810
|
+
if (!stats.isDirectory())
|
|
9811
|
+
return null;
|
|
9812
|
+
return `${stats.dev}:${stats.ino}`;
|
|
9813
|
+
} catch {
|
|
9808
9814
|
return null;
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
const
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
const
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
if (
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9815
|
+
}
|
|
9816
|
+
});
|
|
9817
|
+
const setTimer = options.setTimeoutFn || setTimeout;
|
|
9818
|
+
const clearTimer = options.clearTimeoutFn || clearTimeout;
|
|
9819
|
+
const debounceMs = options.debounceMs ?? 250;
|
|
9820
|
+
const recursive = options.recursive === true;
|
|
9821
|
+
const maxWatchedDirectories = Math.max(1, Math.floor(options.maxWatchedDirectories ?? DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT));
|
|
9822
|
+
const watchers = new Map;
|
|
9823
|
+
const signatures = new Map;
|
|
9824
|
+
const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
|
|
9825
|
+
const initialScanQueue = [];
|
|
9826
|
+
let initialScanTimer = null;
|
|
9827
|
+
let processingInitialScan = false;
|
|
9828
|
+
const pendingPathInspections = new Map;
|
|
9829
|
+
let pathInspectionTimer = null;
|
|
9830
|
+
let timer = null;
|
|
9831
|
+
const pendingChangedPaths = new Set;
|
|
9832
|
+
let fullUpdatePending = false;
|
|
9833
|
+
let watchLimitReported = false;
|
|
9834
|
+
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
9835
|
+
const directoryRelativePath = (dir) => normalizeRelativePath(relative4(options.root, dir));
|
|
9836
|
+
const ignoredDirectory = (dir) => {
|
|
9837
|
+
const rel = directoryRelativePath(dir);
|
|
9838
|
+
return Boolean(rel && ignored(rel));
|
|
9839
|
+
};
|
|
9840
|
+
const scheduleUpdate = (changedPath) => {
|
|
9841
|
+
if (changedPath)
|
|
9842
|
+
pendingChangedPaths.add(changedPath);
|
|
9843
|
+
else
|
|
9844
|
+
fullUpdatePending = true;
|
|
9845
|
+
if (timer)
|
|
9846
|
+
clearTimer(timer);
|
|
9847
|
+
timer = setTimer(() => {
|
|
9848
|
+
timer = null;
|
|
9849
|
+
const paths = !fullUpdatePending && pendingChangedPaths.size ? [...pendingChangedPaths] : undefined;
|
|
9850
|
+
pendingChangedPaths.clear();
|
|
9851
|
+
fullUpdatePending = false;
|
|
9852
|
+
options.onUpdate(paths);
|
|
9853
|
+
}, debounceMs);
|
|
9854
|
+
};
|
|
9855
|
+
const reportWatchLimit = () => {
|
|
9856
|
+
if (watchLimitReported)
|
|
9857
|
+
return;
|
|
9858
|
+
watchLimitReported = true;
|
|
9859
|
+
options.onWatchLimit?.(maxWatchedDirectories);
|
|
9860
|
+
options.onError?.(new Error(`worktree watcher cap reached (${maxWatchedDirectories}); subsequent changes may be missed`));
|
|
9861
|
+
};
|
|
9862
|
+
const closeSubtree = (dir) => {
|
|
9863
|
+
for (const [watchedDir, watcher] of [...watchers]) {
|
|
9864
|
+
if (watchedDir !== dir && !watchedDir.startsWith(`${dir}/`))
|
|
9865
|
+
continue;
|
|
9866
|
+
try {
|
|
9867
|
+
watcher.close?.();
|
|
9868
|
+
} catch {}
|
|
9869
|
+
watchers.delete(watchedDir);
|
|
9870
|
+
signatures.delete(watchedDir);
|
|
9871
|
+
}
|
|
9872
|
+
};
|
|
9873
|
+
const closeAll = () => {
|
|
9874
|
+
if (initialScanTimer) {
|
|
9875
|
+
clearTimer(initialScanTimer);
|
|
9876
|
+
initialScanTimer = null;
|
|
9877
|
+
}
|
|
9878
|
+
if (pathInspectionTimer) {
|
|
9879
|
+
clearTimer(pathInspectionTimer);
|
|
9880
|
+
pathInspectionTimer = null;
|
|
9881
|
+
}
|
|
9882
|
+
initialScanQueue.length = 0;
|
|
9883
|
+
pendingPathInspections.clear();
|
|
9884
|
+
for (const watcher of [...watchers.values()]) {
|
|
9885
|
+
try {
|
|
9886
|
+
watcher.close?.();
|
|
9887
|
+
} catch {}
|
|
9888
|
+
}
|
|
9889
|
+
watchers.clear();
|
|
9890
|
+
signatures.clear();
|
|
9891
|
+
};
|
|
9892
|
+
const readChildDirectories = (dir) => {
|
|
9893
|
+
let entries;
|
|
9894
|
+
try {
|
|
9895
|
+
entries = readDirs(dir);
|
|
9896
|
+
} catch (error) {
|
|
9897
|
+
options.onError?.(error);
|
|
9898
|
+
return [];
|
|
9899
|
+
}
|
|
9900
|
+
const children = [];
|
|
9901
|
+
for (const entry of entries) {
|
|
9902
|
+
if (!entry.isDirectory())
|
|
9903
|
+
continue;
|
|
9904
|
+
const child = join9(dir, entry.name);
|
|
9905
|
+
if (ignoredDirectory(child))
|
|
9906
|
+
continue;
|
|
9907
|
+
children.push(child);
|
|
9908
|
+
}
|
|
9909
|
+
return children;
|
|
9910
|
+
};
|
|
9911
|
+
const processInitialScanQueue = () => {
|
|
9912
|
+
initialScanTimer = null;
|
|
9913
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
9914
|
+
reportWatchLimit();
|
|
9915
|
+
initialScanQueue.length = 0;
|
|
9916
|
+
return;
|
|
9917
|
+
}
|
|
9918
|
+
const next = initialScanQueue.shift();
|
|
9919
|
+
if (next) {
|
|
9920
|
+
processingInitialScan = true;
|
|
9921
|
+
try {
|
|
9922
|
+
watchDirectory(next, true);
|
|
9923
|
+
} finally {
|
|
9924
|
+
processingInitialScan = false;
|
|
9849
9925
|
}
|
|
9850
9926
|
}
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
if (
|
|
9856
|
-
|
|
9857
|
-
}
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
const
|
|
9865
|
-
if (
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
}
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
}
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
}
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9927
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
9928
|
+
reportWatchLimit();
|
|
9929
|
+
initialScanQueue.length = 0;
|
|
9930
|
+
}
|
|
9931
|
+
if (initialScanQueue.length)
|
|
9932
|
+
initialScanTimer = setTimer(processInitialScanQueue, 50);
|
|
9933
|
+
};
|
|
9934
|
+
const queueInitialChildren = (dir) => {
|
|
9935
|
+
const remaining = maxWatchedDirectories - watchers.size;
|
|
9936
|
+
if (remaining <= 0) {
|
|
9937
|
+
reportWatchLimit();
|
|
9938
|
+
return;
|
|
9939
|
+
}
|
|
9940
|
+
const children = readChildDirectories(dir);
|
|
9941
|
+
if (children.length > remaining)
|
|
9942
|
+
reportWatchLimit();
|
|
9943
|
+
initialScanQueue.push(...children.slice(0, remaining));
|
|
9944
|
+
if (!initialScanTimer && !processingInitialScan)
|
|
9945
|
+
initialScanTimer = setTimer(processInitialScanQueue, 5000);
|
|
9946
|
+
};
|
|
9947
|
+
const processChangedPath = (changed, fullChangedPath) => {
|
|
9948
|
+
const known = watchers.has(fullChangedPath);
|
|
9949
|
+
if (isDirectory(fullChangedPath)) {
|
|
9950
|
+
if (known) {
|
|
9951
|
+
const signature = directorySignature(fullChangedPath);
|
|
9952
|
+
if (signature && signature !== signatures.get(fullChangedPath)) {
|
|
9953
|
+
closeSubtree(fullChangedPath);
|
|
9954
|
+
watchDirectory(fullChangedPath, initialScanAsync);
|
|
9955
|
+
}
|
|
9956
|
+
scheduleUpdate(changed);
|
|
9957
|
+
return;
|
|
9958
|
+
}
|
|
9959
|
+
watchDirectory(fullChangedPath, initialScanAsync);
|
|
9960
|
+
} else if (known) {
|
|
9961
|
+
closeSubtree(fullChangedPath);
|
|
9962
|
+
}
|
|
9963
|
+
scheduleUpdate(changed);
|
|
9964
|
+
};
|
|
9965
|
+
const processPathInspections = () => {
|
|
9966
|
+
pathInspectionTimer = null;
|
|
9967
|
+
const entries = [...pendingPathInspections];
|
|
9968
|
+
pendingPathInspections.clear();
|
|
9969
|
+
for (const [changed, fullChangedPath] of entries) {
|
|
9970
|
+
processChangedPath(changed, fullChangedPath);
|
|
9971
|
+
}
|
|
9972
|
+
};
|
|
9973
|
+
const queuePathInspection = (changed, fullChangedPath) => {
|
|
9974
|
+
pendingPathInspections.set(changed, fullChangedPath);
|
|
9975
|
+
if (!pathInspectionTimer)
|
|
9976
|
+
pathInspectionTimer = setTimer(processPathInspections, 25);
|
|
9977
|
+
};
|
|
9978
|
+
const watchDirectory = (dir, initialScan = false) => {
|
|
9979
|
+
if (watchers.has(dir))
|
|
9980
|
+
return;
|
|
9981
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
9982
|
+
reportWatchLimit();
|
|
9983
|
+
return;
|
|
9984
|
+
}
|
|
9985
|
+
const rel = directoryRelativePath(dir);
|
|
9986
|
+
if (rel && ignored(rel))
|
|
9987
|
+
return;
|
|
9988
|
+
try {
|
|
9989
|
+
const watcher = watch(dir, { persistent: false, recursive }, (_event, filename) => {
|
|
9990
|
+
if (!filename) {
|
|
9991
|
+
scheduleUpdate();
|
|
9992
|
+
return;
|
|
9993
|
+
}
|
|
9994
|
+
const changed = normalizeRelativePath(join9(rel, filename.toString()));
|
|
9995
|
+
if (ignored(changed))
|
|
9996
|
+
return;
|
|
9997
|
+
const fullChangedPath = join9(options.root, changed);
|
|
9998
|
+
if (!isInsideRoot(options.root, fullChangedPath))
|
|
9999
|
+
return;
|
|
10000
|
+
if (recursive) {
|
|
10001
|
+
scheduleUpdate(changed);
|
|
10002
|
+
return;
|
|
10003
|
+
}
|
|
10004
|
+
if (initialScanAsync) {
|
|
10005
|
+
queuePathInspection(changed, fullChangedPath);
|
|
10006
|
+
return;
|
|
10007
|
+
}
|
|
10008
|
+
processChangedPath(changed, fullChangedPath);
|
|
10009
|
+
}) || {};
|
|
10010
|
+
watchers.set(dir, watcher);
|
|
10011
|
+
const signature = directorySignature(dir);
|
|
10012
|
+
if (signature)
|
|
10013
|
+
signatures.set(dir, signature);
|
|
10014
|
+
watcher.on?.("error", () => {
|
|
10015
|
+
if (watchers.get(dir) === watcher) {
|
|
10016
|
+
watchers.delete(dir);
|
|
10017
|
+
signatures.delete(dir);
|
|
10018
|
+
}
|
|
10019
|
+
});
|
|
10020
|
+
watcher.on?.("close", () => {
|
|
10021
|
+
if (watchers.get(dir) === watcher) {
|
|
10022
|
+
watchers.delete(dir);
|
|
10023
|
+
signatures.delete(dir);
|
|
10024
|
+
}
|
|
10025
|
+
});
|
|
10026
|
+
} catch (error) {
|
|
10027
|
+
options.onError?.(error);
|
|
10028
|
+
return;
|
|
10029
|
+
}
|
|
10030
|
+
if (recursive)
|
|
10031
|
+
return;
|
|
10032
|
+
if (initialScanAsync && initialScan) {
|
|
10033
|
+
queueInitialChildren(dir);
|
|
10034
|
+
return;
|
|
10035
|
+
}
|
|
10036
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
10037
|
+
reportWatchLimit();
|
|
10038
|
+
return;
|
|
10039
|
+
}
|
|
10040
|
+
for (const child of readChildDirectories(dir))
|
|
10041
|
+
watchDirectory(child);
|
|
10042
|
+
};
|
|
10043
|
+
watchDirectory(options.root, true);
|
|
10044
|
+
return { started: watchers.size > 0, close: closeAll };
|
|
9905
10045
|
}
|
|
9906
|
-
|
|
9907
|
-
|
|
10046
|
+
var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT = 1024, MIN_WORKTREE_WATCH_DIRECTORY_LIMIT = 1, MAX_WORKTREE_WATCH_DIRECTORY_LIMIT = 65536;
|
|
10047
|
+
var init_worktree_watcher = __esm(() => {
|
|
10048
|
+
init_search();
|
|
10049
|
+
});
|
|
10050
|
+
|
|
10051
|
+
// web-src/server/watch-child.ts
|
|
10052
|
+
var exports_watch_child = {};
|
|
10053
|
+
__export(exports_watch_child, {
|
|
10054
|
+
runWatchChild: () => runWatchChild,
|
|
10055
|
+
parsePorcelainV2: () => parsePorcelainV2
|
|
10056
|
+
});
|
|
10057
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10058
|
+
import { createReadStream as createReadStream2, lstatSync as lstatSync4 } from "node:fs";
|
|
10059
|
+
import { join as join10 } from "node:path";
|
|
10060
|
+
function send(message) {
|
|
10061
|
+
process.stdout.write(`${JSON.stringify(message)}
|
|
10062
|
+
`);
|
|
9908
10063
|
}
|
|
9909
|
-
function
|
|
9910
|
-
return
|
|
10064
|
+
function readConfigLine() {
|
|
10065
|
+
return new Promise((resolve2, reject) => {
|
|
10066
|
+
let buffer = "";
|
|
10067
|
+
const stdin = process.stdin;
|
|
10068
|
+
stdin.setEncoding("utf8");
|
|
10069
|
+
const onData = (chunk) => {
|
|
10070
|
+
buffer += chunk;
|
|
10071
|
+
const newline = buffer.indexOf(`
|
|
10072
|
+
`);
|
|
10073
|
+
if (newline === -1)
|
|
10074
|
+
return;
|
|
10075
|
+
stdin.off("data", onData);
|
|
10076
|
+
stdin.off("end", onEnd);
|
|
10077
|
+
resolve2(buffer.slice(0, newline));
|
|
10078
|
+
};
|
|
10079
|
+
const onEnd = () => {
|
|
10080
|
+
stdin.off("data", onData);
|
|
10081
|
+
reject(new Error("watch child: stdin closed before config arrived"));
|
|
10082
|
+
};
|
|
10083
|
+
stdin.on("data", onData);
|
|
10084
|
+
stdin.on("end", onEnd);
|
|
10085
|
+
});
|
|
9911
10086
|
}
|
|
9912
|
-
function
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
const sql = `INSERT INTO ${sanitizeIdentifier(table, kind)} (${cols.join(", ")}) VALUES (${placeholders.join(", ")})`;
|
|
9921
|
-
return { sql, params };
|
|
10087
|
+
function hashFile(path) {
|
|
10088
|
+
return new Promise((resolve2) => {
|
|
10089
|
+
const hash = createHash2("sha256");
|
|
10090
|
+
const stream = createReadStream2(path);
|
|
10091
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
10092
|
+
stream.on("end", () => resolve2(hash.digest("hex")));
|
|
10093
|
+
stream.on("error", () => resolve2("unreadable"));
|
|
10094
|
+
});
|
|
9922
10095
|
}
|
|
9923
|
-
function
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
10096
|
+
function parsePorcelainV2(raw) {
|
|
10097
|
+
const records = raw.split("\x00");
|
|
10098
|
+
let head = "";
|
|
10099
|
+
const entries = [];
|
|
10100
|
+
for (let i = 0;i < records.length; i++) {
|
|
10101
|
+
const record = records[i];
|
|
10102
|
+
if (!record)
|
|
10103
|
+
continue;
|
|
10104
|
+
if (record.startsWith("# branch.oid ")) {
|
|
10105
|
+
head = record.slice("# branch.oid ".length);
|
|
10106
|
+
continue;
|
|
10107
|
+
}
|
|
10108
|
+
if (record.startsWith("# "))
|
|
10109
|
+
continue;
|
|
10110
|
+
const kind = record[0];
|
|
10111
|
+
if (kind === "1" || kind === "2" || kind === "u") {
|
|
10112
|
+
const metaCount = kind === "1" ? 8 : kind === "2" ? 9 : 10;
|
|
10113
|
+
const status = record.split(" ").slice(0, metaCount).join(" ");
|
|
10114
|
+
entries.push({ path: record.slice(status.length + 1), status });
|
|
10115
|
+
if (kind === "2")
|
|
10116
|
+
i++;
|
|
10117
|
+
continue;
|
|
10118
|
+
}
|
|
10119
|
+
if (kind === "?" || kind === "!") {
|
|
10120
|
+
entries.push({ path: record.slice(2), status: kind });
|
|
10121
|
+
}
|
|
9929
10122
|
}
|
|
9930
|
-
|
|
9931
|
-
const params = [];
|
|
9932
|
-
const setSql = formatWriteComparisons(set, columnTypes, kind, useParams, params, ", ");
|
|
9933
|
-
const whereSql = formatWriteComparisons(pk, columnTypes, kind, useParams, params, " AND ");
|
|
9934
|
-
const sql = `UPDATE ${sanitizeIdentifier(table, kind)} SET ${setSql} WHERE ${whereSql}`;
|
|
9935
|
-
return { sql, params };
|
|
10123
|
+
return { head, entries };
|
|
9936
10124
|
}
|
|
9937
|
-
function
|
|
9938
|
-
|
|
9939
|
-
|
|
10125
|
+
async function readWorktreeSnapshot(config) {
|
|
10126
|
+
const result = await runAsync([
|
|
10127
|
+
"git",
|
|
10128
|
+
"--no-optional-locks",
|
|
10129
|
+
"-c",
|
|
10130
|
+
"core.fsmonitor=false",
|
|
10131
|
+
"status",
|
|
10132
|
+
"--porcelain=v2",
|
|
10133
|
+
"-z",
|
|
10134
|
+
"--branch",
|
|
10135
|
+
"--untracked-files=all"
|
|
10136
|
+
], config.root, { timeout: 60000 });
|
|
10137
|
+
if (result.code !== 0)
|
|
10138
|
+
return null;
|
|
10139
|
+
const parsed = parsePorcelainV2(result.stdout);
|
|
10140
|
+
const entries = new Map;
|
|
10141
|
+
for (const entry of parsed.entries) {
|
|
10142
|
+
if (!entry.path)
|
|
10143
|
+
continue;
|
|
10144
|
+
if (isSkippableSearchPath(entry.path, config.omitDirNames, config.excludeNames))
|
|
10145
|
+
continue;
|
|
10146
|
+
entries.set(entry.path, await pathSignature(config.root, entry));
|
|
9940
10147
|
}
|
|
9941
|
-
|
|
9942
|
-
const params = [];
|
|
9943
|
-
const whereSql = formatWriteComparisons(pk, columnTypes, kind, useParams, params, " AND ");
|
|
9944
|
-
const sql = `DELETE FROM ${sanitizeIdentifier(table, kind)} WHERE ${whereSql}`;
|
|
9945
|
-
return { sql, params };
|
|
10148
|
+
return { head: parsed.head, entries };
|
|
9946
10149
|
}
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
throw new Error(`${label} must be an array`);
|
|
10150
|
+
async function pathSignature(root, entry) {
|
|
10151
|
+
const full = join10(root, entry.path);
|
|
10152
|
+
let stats;
|
|
10153
|
+
try {
|
|
10154
|
+
stats = lstatSync4(full);
|
|
10155
|
+
} catch {
|
|
10156
|
+
return `${entry.status}:absent`;
|
|
9955
10157
|
}
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
}
|
|
10158
|
+
const base = `${entry.status}:${stats.size}:${stats.mtimeMs}:${stats.mode}`;
|
|
10159
|
+
if (stats.isFile() && stats.size <= MAX_HASHED_FILE_BYTES) {
|
|
10160
|
+
return `${base}:${await hashFile(full)}`;
|
|
9960
10161
|
}
|
|
9961
|
-
return
|
|
10162
|
+
return base;
|
|
9962
10163
|
}
|
|
9963
|
-
function
|
|
9964
|
-
if (
|
|
9965
|
-
|
|
10164
|
+
function snapshotDiff(previous, next) {
|
|
10165
|
+
if (previous.head !== next.head)
|
|
10166
|
+
return { full: true, paths: [] };
|
|
10167
|
+
const paths = [];
|
|
10168
|
+
for (const [path, signature] of next.entries) {
|
|
10169
|
+
if (previous.entries.get(path) !== signature)
|
|
10170
|
+
paths.push(path);
|
|
9966
10171
|
}
|
|
9967
|
-
|
|
9968
|
-
|
|
10172
|
+
for (const path of previous.entries.keys()) {
|
|
10173
|
+
if (!next.entries.has(path))
|
|
10174
|
+
paths.push(path);
|
|
9969
10175
|
}
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
const
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
10176
|
+
return { full: false, paths };
|
|
10177
|
+
}
|
|
10178
|
+
async function runWatchChild() {
|
|
10179
|
+
const config = JSON.parse(await readConfigLine());
|
|
10180
|
+
process.stdin.on("end", () => process.exit(0));
|
|
10181
|
+
process.stdin.on("close", () => process.exit(0));
|
|
10182
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
10183
|
+
process.on(signal, () => process.exit(0));
|
|
10184
|
+
}
|
|
10185
|
+
let watching = false;
|
|
10186
|
+
if (!config.pollOnly) {
|
|
10187
|
+
const recursive = supportsNativeRecursiveWatch(process.platform);
|
|
10188
|
+
const watch = startWorktreeUpdateWatch({
|
|
10189
|
+
root: config.root,
|
|
10190
|
+
omitDirNames: config.omitDirNames,
|
|
10191
|
+
excludeNames: config.excludeNames,
|
|
10192
|
+
recursive,
|
|
10193
|
+
initialScanMode: recursive ? "sync" : "async",
|
|
10194
|
+
maxWatchedDirectories: config.maxWatchedDirectories,
|
|
10195
|
+
debounceMs: config.debounceMs,
|
|
10196
|
+
onUpdate: (paths) => send({ type: "update", paths }),
|
|
10197
|
+
onWatchLimit: (limit) => send({ type: "watch-limit", limit }),
|
|
10198
|
+
onError: (error) => send({
|
|
10199
|
+
type: "warn",
|
|
10200
|
+
message: error instanceof Error ? error.message : String(error)
|
|
10201
|
+
})
|
|
10202
|
+
});
|
|
10203
|
+
watching = watch.started;
|
|
10204
|
+
}
|
|
10205
|
+
send({ type: "ready", watching, pollOnly: config.pollOnly });
|
|
10206
|
+
setInterval(() => send({ type: "heartbeat" }), config.heartbeatIntervalMs).unref?.();
|
|
10207
|
+
setInterval(() => {
|
|
10208
|
+
try {
|
|
10209
|
+
process.kill(config.parentPid, 0);
|
|
10210
|
+
} catch {
|
|
10211
|
+
process.exit(0);
|
|
9984
10212
|
}
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
10213
|
+
}, config.heartbeatIntervalMs);
|
|
10214
|
+
let snapshot = null;
|
|
10215
|
+
let baselineEstablished = false;
|
|
10216
|
+
const poll = async () => {
|
|
10217
|
+
const next = await readWorktreeSnapshot(config);
|
|
10218
|
+
if (!next)
|
|
10219
|
+
return;
|
|
10220
|
+
if (!snapshot) {
|
|
10221
|
+
snapshot = next;
|
|
10222
|
+
if (baselineEstablished)
|
|
10223
|
+
send({ type: "update" });
|
|
10224
|
+
baselineEstablished = true;
|
|
10225
|
+
return;
|
|
9990
10226
|
}
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
}
|
|
9995
|
-
|
|
9996
|
-
throw new Error(`primary key column cannot be null: ${cell.column}`);
|
|
9997
|
-
}
|
|
10227
|
+
const diff = snapshotDiff(snapshot, next);
|
|
10228
|
+
snapshot = next;
|
|
10229
|
+
if (diff.full) {
|
|
10230
|
+
send({ type: "update" });
|
|
10231
|
+
return;
|
|
9998
10232
|
}
|
|
10233
|
+
if (diff.paths.length)
|
|
10234
|
+
send({ type: "update", paths: diff.paths });
|
|
9999
10235
|
};
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
}
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10236
|
+
setInterval(() => {
|
|
10237
|
+
poll().catch((error) => send({
|
|
10238
|
+
type: "warn",
|
|
10239
|
+
message: error instanceof Error ? error.message : String(error)
|
|
10240
|
+
}));
|
|
10241
|
+
}, config.pollIntervalMs);
|
|
10242
|
+
}
|
|
10243
|
+
var MAX_HASHED_FILE_BYTES;
|
|
10244
|
+
var init_watch_child = __esm(() => {
|
|
10245
|
+
init_runtime();
|
|
10246
|
+
init_search();
|
|
10247
|
+
init_worktree_watcher();
|
|
10248
|
+
MAX_HASHED_FILE_BYTES = 4 * 1024 * 1024;
|
|
10249
|
+
});
|
|
10250
|
+
|
|
10251
|
+
// web-src/server/database/serialize.ts
|
|
10252
|
+
function serializeDbValue(value) {
|
|
10253
|
+
if (value === null || value === undefined)
|
|
10254
|
+
return null;
|
|
10255
|
+
if (typeof value === "bigint") {
|
|
10256
|
+
return value >= MIN_SAFE && value <= MAX_SAFE ? Number(value) : value.toString();
|
|
10257
|
+
}
|
|
10258
|
+
if (value instanceof Uint8Array) {
|
|
10259
|
+
return `<blob ${value.byteLength} bytes>`;
|
|
10260
|
+
}
|
|
10261
|
+
if (typeof value === "object") {
|
|
10262
|
+
try {
|
|
10263
|
+
return JSON.stringify(value);
|
|
10264
|
+
} catch {
|
|
10265
|
+
return String(value);
|
|
10266
|
+
}
|
|
10267
|
+
}
|
|
10268
|
+
return value;
|
|
10269
|
+
}
|
|
10270
|
+
function serializeDbRow(row) {
|
|
10271
|
+
return row.map(serializeDbValue);
|
|
10272
|
+
}
|
|
10273
|
+
function serializeDbRows(rows) {
|
|
10274
|
+
return rows.map(serializeDbRow);
|
|
10275
|
+
}
|
|
10276
|
+
function coerceDbValue(value, columnType) {
|
|
10277
|
+
if (value === null)
|
|
10278
|
+
return null;
|
|
10279
|
+
const t = (columnType || "").toLowerCase();
|
|
10280
|
+
if (/bool/.test(t)) {
|
|
10281
|
+
const v = value.trim().toLowerCase();
|
|
10282
|
+
if (v === "")
|
|
10283
|
+
return null;
|
|
10284
|
+
if (v === "true" || v === "t" || v === "1")
|
|
10285
|
+
return true;
|
|
10286
|
+
if (v === "false" || v === "f" || v === "0")
|
|
10287
|
+
return false;
|
|
10288
|
+
return value;
|
|
10289
|
+
}
|
|
10290
|
+
if (/int|serial|real|floa|doub|numeric|decimal|number/.test(t)) {
|
|
10291
|
+
const trimmed = value.trim();
|
|
10292
|
+
if (trimmed === "")
|
|
10293
|
+
return null;
|
|
10294
|
+
const n = Number(trimmed);
|
|
10295
|
+
if (Number.isFinite(n) && String(n) === trimmed)
|
|
10296
|
+
return n;
|
|
10297
|
+
return value;
|
|
10298
|
+
}
|
|
10299
|
+
return value;
|
|
10300
|
+
}
|
|
10301
|
+
var MIN_SAFE, MAX_SAFE;
|
|
10302
|
+
var init_serialize = __esm(() => {
|
|
10303
|
+
MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
|
|
10304
|
+
MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
10305
|
+
});
|
|
10306
|
+
|
|
10307
|
+
// web-src/server/database/sql-utils.ts
|
|
10308
|
+
function sanitizeIdentifier(name, kind = "sqlite") {
|
|
10309
|
+
if (kind === "mysql")
|
|
10310
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
10311
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
10312
|
+
}
|
|
10313
|
+
function escapeSqlString(value, kind) {
|
|
10314
|
+
const escaped = kind === "mysql" ? value.replace(/\\/g, "\\\\").replace(/'/g, "''") : value.replace(/'/g, "''");
|
|
10315
|
+
return `'${escaped}'`;
|
|
10316
|
+
}
|
|
10317
|
+
function buildFilterWhere(grouped, kind, exact) {
|
|
10318
|
+
const whereParts = [];
|
|
10319
|
+
const params = [];
|
|
10320
|
+
const useParams = kind === "sqlite";
|
|
10321
|
+
const castOf = (column) => kind === "mysql" ? `CAST(${sanitizeIdentifier(column, kind)} AS CHAR)` : `CAST(${sanitizeIdentifier(column, kind)} AS TEXT)`;
|
|
10322
|
+
for (const [value, cols] of grouped) {
|
|
10323
|
+
const likeVal = useParams ? "?" : escapeSqlString(`%${value}%`, kind);
|
|
10324
|
+
if (cols.length === 1) {
|
|
10325
|
+
whereParts.push(`${castOf(cols[0])} LIKE ${likeVal}`);
|
|
10326
|
+
if (useParams)
|
|
10327
|
+
params.push(`%${value}%`);
|
|
10328
|
+
} else {
|
|
10329
|
+
const orParts = cols.map((column) => `${castOf(column)} LIKE ${likeVal}`);
|
|
10330
|
+
whereParts.push(`(${orParts.join(" OR ")})`);
|
|
10331
|
+
if (useParams) {
|
|
10332
|
+
for (let i = 0;i < cols.length; i++)
|
|
10333
|
+
params.push(`%${value}%`);
|
|
10334
|
+
}
|
|
10335
|
+
}
|
|
10336
|
+
}
|
|
10337
|
+
for (const cond of exact ?? []) {
|
|
10338
|
+
const rhs = useParams ? "?" : escapeSqlString(cond.value, kind);
|
|
10339
|
+
whereParts.push(`${castOf(cond.column)} = ${rhs}`);
|
|
10340
|
+
if (useParams)
|
|
10341
|
+
params.push(cond.value);
|
|
10342
|
+
}
|
|
10343
|
+
return { where: whereParts.join(" AND "), params, useParams };
|
|
10344
|
+
}
|
|
10345
|
+
function filterGroupedColumns(grouped, columnNames) {
|
|
10346
|
+
const validColumns = new Set(columnNames);
|
|
10347
|
+
const filtered = new Map;
|
|
10348
|
+
for (const [value, columns] of grouped) {
|
|
10349
|
+
const valid = columns.filter((column) => validColumns.has(column));
|
|
10350
|
+
if (valid.length > 0)
|
|
10351
|
+
filtered.set(value, valid);
|
|
10352
|
+
}
|
|
10353
|
+
return filtered;
|
|
10354
|
+
}
|
|
10355
|
+
function filterExactColumns(exact, columnNames) {
|
|
10356
|
+
if (!exact || exact.length === 0)
|
|
10357
|
+
return [];
|
|
10358
|
+
const validColumns = new Set(columnNames);
|
|
10359
|
+
return exact.filter((cond) => validColumns.has(cond.column));
|
|
10360
|
+
}
|
|
10361
|
+
function filterOrderByColumns(orderBy, columnNames) {
|
|
10362
|
+
if (!orderBy)
|
|
10363
|
+
return;
|
|
10364
|
+
const validColumns = new Set(columnNames);
|
|
10365
|
+
const filtered = orderBy.filter((order) => validColumns.has(order.column));
|
|
10366
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
10367
|
+
}
|
|
10368
|
+
function buildOrderClause(orderBy, kind = "sqlite") {
|
|
10369
|
+
if (!orderBy?.length)
|
|
10370
|
+
return "";
|
|
10371
|
+
const parts = orderBy.map((o) => `${sanitizeIdentifier(o.column, kind)} ${o.direction === "desc" ? "DESC" : "ASC"}`);
|
|
10372
|
+
return ` ORDER BY ${parts.join(", ")}`;
|
|
10373
|
+
}
|
|
10374
|
+
function useParamsFor(kind) {
|
|
10375
|
+
return kind === "sqlite";
|
|
10376
|
+
}
|
|
10377
|
+
function placeValue(coerced, kind, useParams, params) {
|
|
10378
|
+
if (useParams) {
|
|
10379
|
+
params.push(typeof coerced === "boolean" ? coerced ? 1 : 0 : coerced);
|
|
10380
|
+
return "?";
|
|
10381
|
+
}
|
|
10382
|
+
if (coerced === null)
|
|
10383
|
+
return "NULL";
|
|
10384
|
+
if (typeof coerced === "number")
|
|
10385
|
+
return String(coerced);
|
|
10386
|
+
if (typeof coerced === "boolean")
|
|
10387
|
+
return coerced ? "TRUE" : "FALSE";
|
|
10388
|
+
const text = coerced instanceof Uint8Array ? new TextDecoder().decode(coerced) : String(coerced);
|
|
10389
|
+
return escapeSqlString(text, kind);
|
|
10390
|
+
}
|
|
10391
|
+
function coerceCell(cell, columnType) {
|
|
10392
|
+
return coerceDbValue(cell.value, columnType);
|
|
10393
|
+
}
|
|
10394
|
+
function formatWriteComparisons(cells, columnTypes, kind, useParams, params, separator) {
|
|
10395
|
+
return cells.map((cell) => `${sanitizeIdentifier(cell.column, kind)} = ${placeValue(coerceCell(cell, columnTypes.get(cell.column) ?? "TEXT"), kind, useParams, params)}`).join(separator);
|
|
10396
|
+
}
|
|
10397
|
+
function buildInsertSql(table, cells, columnTypes, kind) {
|
|
10398
|
+
if (cells.length === 0) {
|
|
10399
|
+
throw new Error("insert requires at least one column value");
|
|
10400
|
+
}
|
|
10401
|
+
const useParams = useParamsFor(kind);
|
|
10402
|
+
const params = [];
|
|
10403
|
+
const cols = cells.map((c) => sanitizeIdentifier(c.column, kind));
|
|
10404
|
+
const placeholders = cells.map((c) => placeValue(coerceCell(c, columnTypes.get(c.column) ?? "TEXT"), kind, useParams, params));
|
|
10405
|
+
const sql = `INSERT INTO ${sanitizeIdentifier(table, kind)} (${cols.join(", ")}) VALUES (${placeholders.join(", ")})`;
|
|
10406
|
+
return { sql, params };
|
|
10407
|
+
}
|
|
10408
|
+
function buildUpdateSql(table, set, pk, columnTypes, kind) {
|
|
10409
|
+
if (set.length === 0) {
|
|
10410
|
+
throw new Error("update requires at least one column to set");
|
|
10411
|
+
}
|
|
10412
|
+
if (pk.length === 0) {
|
|
10413
|
+
throw new Error("update requires a primary key condition");
|
|
10414
|
+
}
|
|
10415
|
+
const useParams = useParamsFor(kind);
|
|
10416
|
+
const params = [];
|
|
10417
|
+
const setSql = formatWriteComparisons(set, columnTypes, kind, useParams, params, ", ");
|
|
10418
|
+
const whereSql = formatWriteComparisons(pk, columnTypes, kind, useParams, params, " AND ");
|
|
10419
|
+
const sql = `UPDATE ${sanitizeIdentifier(table, kind)} SET ${setSql} WHERE ${whereSql}`;
|
|
10420
|
+
return { sql, params };
|
|
10421
|
+
}
|
|
10422
|
+
function buildDeleteSql(table, pk, columnTypes, kind) {
|
|
10423
|
+
if (pk.length === 0) {
|
|
10424
|
+
throw new Error("delete requires a primary key condition");
|
|
10425
|
+
}
|
|
10426
|
+
const useParams = useParamsFor(kind);
|
|
10427
|
+
const params = [];
|
|
10428
|
+
const whereSql = formatWriteComparisons(pk, columnTypes, kind, useParams, params, " AND ");
|
|
10429
|
+
const sql = `DELETE FROM ${sanitizeIdentifier(table, kind)} WHERE ${whereSql}`;
|
|
10430
|
+
return { sql, params };
|
|
10431
|
+
}
|
|
10432
|
+
var init_sql_utils = __esm(() => {
|
|
10433
|
+
init_serialize();
|
|
10434
|
+
});
|
|
10435
|
+
|
|
10436
|
+
// web-src/server/database/mutate.ts
|
|
10437
|
+
function assertCells(cells, label) {
|
|
10438
|
+
if (!Array.isArray(cells)) {
|
|
10439
|
+
throw new Error(`${label} must be an array`);
|
|
10440
|
+
}
|
|
10441
|
+
for (const cell of cells) {
|
|
10442
|
+
if (!cell || typeof cell !== "object" || typeof cell.column !== "string" || cell.value !== null && typeof cell.value !== "string") {
|
|
10443
|
+
throw new Error(`${label} contains an invalid cell`);
|
|
10444
|
+
}
|
|
10445
|
+
}
|
|
10446
|
+
return cells;
|
|
10447
|
+
}
|
|
10448
|
+
function buildMutationStatements(table, mutations, columns, kind) {
|
|
10449
|
+
if (!Array.isArray(mutations) || mutations.length === 0) {
|
|
10450
|
+
throw new Error("no mutations provided");
|
|
10451
|
+
}
|
|
10452
|
+
if (mutations.length > MAX_MUTATIONS) {
|
|
10453
|
+
throw new Error(`too many mutations (max ${MAX_MUTATIONS})`);
|
|
10454
|
+
}
|
|
10455
|
+
const columnTypes = new Map(columns.map((c) => [c.name, c.type]));
|
|
10456
|
+
const columnNames = new Set(columns.map((c) => c.name));
|
|
10457
|
+
const pkColumns = columns.filter((c) => c.primaryKey).map((c) => c.name);
|
|
10458
|
+
const pkNames = new Set(pkColumns);
|
|
10459
|
+
const requireKnownColumns = (cells, label) => {
|
|
10460
|
+
for (const cell of cells) {
|
|
10461
|
+
if (!columnNames.has(cell.column)) {
|
|
10462
|
+
throw new Error(`unknown column: ${cell.column}`);
|
|
10463
|
+
}
|
|
10464
|
+
}
|
|
10465
|
+
};
|
|
10466
|
+
const requirePrimaryKey = (pk) => {
|
|
10467
|
+
if (pkColumns.length === 0) {
|
|
10468
|
+
throw new Error("table has no primary key; row update/delete is not supported");
|
|
10469
|
+
}
|
|
10470
|
+
const provided = new Set(pk.map((c) => c.column));
|
|
10471
|
+
for (const name of pkColumns) {
|
|
10472
|
+
if (!provided.has(name)) {
|
|
10473
|
+
throw new Error(`missing primary key column: ${name}`);
|
|
10474
|
+
}
|
|
10475
|
+
}
|
|
10476
|
+
for (const cell of pk) {
|
|
10477
|
+
if (!pkNames.has(cell.column)) {
|
|
10478
|
+
throw new Error(`not a primary key column: ${cell.column}`);
|
|
10479
|
+
}
|
|
10480
|
+
if (cell.value === null) {
|
|
10481
|
+
throw new Error(`primary key column cannot be null: ${cell.column}`);
|
|
10482
|
+
}
|
|
10483
|
+
}
|
|
10484
|
+
};
|
|
10485
|
+
const statements = [];
|
|
10486
|
+
for (const mutation of mutations) {
|
|
10487
|
+
if (!mutation || typeof mutation !== "object") {
|
|
10488
|
+
throw new Error("invalid mutation");
|
|
10489
|
+
}
|
|
10490
|
+
if (mutation.kind === "insert") {
|
|
10491
|
+
const values = assertCells(mutation.values, "insert values");
|
|
10492
|
+
requireKnownColumns(values, "insert values");
|
|
10493
|
+
statements.push(buildInsertSql(table, values, columnTypes, kind));
|
|
10009
10494
|
} else if (mutation.kind === "update") {
|
|
10010
10495
|
const pk = assertCells(mutation.pk, "update pk");
|
|
10011
10496
|
const values = assertCells(mutation.values, "update values");
|
|
@@ -10028,7 +10513,7 @@ var init_mutate = __esm(() => {
|
|
|
10028
10513
|
});
|
|
10029
10514
|
|
|
10030
10515
|
// web-src/server/database/sources/sql-snapshot.ts
|
|
10031
|
-
import { createHash as
|
|
10516
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
10032
10517
|
function normalizeRawValue(v) {
|
|
10033
10518
|
if (v === null)
|
|
10034
10519
|
return "\\N";
|
|
@@ -10048,7 +10533,7 @@ function rowToPayloadJson(columns, row) {
|
|
|
10048
10533
|
}
|
|
10049
10534
|
function computeRowHash(columns, row) {
|
|
10050
10535
|
const parts = columns.map((_, i) => normalizeRawValue(row[i]));
|
|
10051
|
-
return
|
|
10536
|
+
return createHash3("sha256").update(parts.join("\t")).digest("hex");
|
|
10052
10537
|
}
|
|
10053
10538
|
function buildRowKeyJson(pkColumns, allColumns, row, rowIndex) {
|
|
10054
10539
|
if (pkColumns.length === 0) {
|
|
@@ -12119,7 +12604,7 @@ __export(exports_redis, {
|
|
|
12119
12604
|
canonicalizeRedisSnapshotContainer: () => canonicalizeRedisSnapshotContainer,
|
|
12120
12605
|
__setRedisClientFactoryForTest: () => __setRedisClientFactoryForTest
|
|
12121
12606
|
});
|
|
12122
|
-
import { createHash as
|
|
12607
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
12123
12608
|
import { createClient } from "@redis/client";
|
|
12124
12609
|
function __setRedisClientFactoryForTest(factory) {
|
|
12125
12610
|
createRedisClientImpl = factory ?? ((options) => createClient(options));
|
|
@@ -12496,7 +12981,7 @@ function createRedisAdapter(config) {
|
|
|
12496
12981
|
if (type === "none") {
|
|
12497
12982
|
return {
|
|
12498
12983
|
payload: { type: "none" },
|
|
12499
|
-
fullHash:
|
|
12984
|
+
fullHash: createHash4("sha256").update("").digest("hex")
|
|
12500
12985
|
};
|
|
12501
12986
|
}
|
|
12502
12987
|
if (type === "string")
|
|
@@ -12513,7 +12998,7 @@ function createRedisAdapter(config) {
|
|
|
12513
12998
|
return snapshotFetchStream(db, hexKey, signal);
|
|
12514
12999
|
return {
|
|
12515
13000
|
payload: { type: "none" },
|
|
12516
|
-
fullHash:
|
|
13001
|
+
fullHash: createHash4("sha256").update("").digest("hex")
|
|
12517
13002
|
};
|
|
12518
13003
|
}
|
|
12519
13004
|
async function evalHex(db, luaBody, extraArgv, label, signal) {
|
|
@@ -12525,7 +13010,7 @@ function createRedisAdapter(config) {
|
|
|
12525
13010
|
}
|
|
12526
13011
|
async function snapshotFetchString(db, hexKey, signal) {
|
|
12527
13012
|
const fullSize = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('STRLEN', fromhex(ARGV[1]))`, [hexKey], "STRLEN", signal)) || 0;
|
|
12528
|
-
const hasher =
|
|
13013
|
+
const hasher = createHash4("sha256");
|
|
12529
13014
|
let previewBytes = Buffer.alloc(0);
|
|
12530
13015
|
for (let offset = 0;offset < fullSize; offset += REDIS_STRING_BYTE_LIMIT) {
|
|
12531
13016
|
if (signal?.aborted)
|
|
@@ -12558,7 +13043,7 @@ function createRedisAdapter(config) {
|
|
|
12558
13043
|
}
|
|
12559
13044
|
async function snapshotFetchList(db, hexKey, signal) {
|
|
12560
13045
|
const total = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('LLEN', fromhex(ARGV[1]))`, [hexKey], "LLEN", signal)) || 0;
|
|
12561
|
-
const hasher =
|
|
13046
|
+
const hasher = createHash4("sha256");
|
|
12562
13047
|
let previewItems = [];
|
|
12563
13048
|
for (let offset = 0;offset < total; offset += REDIS_COLLECTION_LIMIT) {
|
|
12564
13049
|
if (signal?.aborted)
|
|
@@ -12600,7 +13085,7 @@ function createRedisAdapter(config) {
|
|
|
12600
13085
|
uniquePairs.push(p);
|
|
12601
13086
|
}
|
|
12602
13087
|
uniquePairs.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
12603
|
-
const hasher =
|
|
13088
|
+
const hasher = createHash4("sha256");
|
|
12604
13089
|
for (const [f, v] of uniquePairs) {
|
|
12605
13090
|
hashHexItem(hasher, f);
|
|
12606
13091
|
hashHexItem(hasher, v);
|
|
@@ -12628,7 +13113,7 @@ function createRedisAdapter(config) {
|
|
|
12628
13113
|
} while (cursor !== "0");
|
|
12629
13114
|
const dedup = Array.from(new Set(allMembers));
|
|
12630
13115
|
dedup.sort();
|
|
12631
|
-
const hasher =
|
|
13116
|
+
const hasher = createHash4("sha256");
|
|
12632
13117
|
for (const m of dedup)
|
|
12633
13118
|
hashHexItem(hasher, m);
|
|
12634
13119
|
const previewMembers = dedup.slice(0, REDIS_COLLECTION_LIMIT).map(decodeHexItem);
|
|
@@ -12642,7 +13127,7 @@ function createRedisAdapter(config) {
|
|
|
12642
13127
|
}
|
|
12643
13128
|
async function snapshotFetchZset(db, hexKey, signal) {
|
|
12644
13129
|
const total = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('ZCARD', fromhex(ARGV[1]))`, [hexKey], "ZCARD", signal)) || 0;
|
|
12645
|
-
const hasher =
|
|
13130
|
+
const hasher = createHash4("sha256");
|
|
12646
13131
|
let previewMembers = [];
|
|
12647
13132
|
for (let offset = 0;offset < total; offset += REDIS_COLLECTION_LIMIT) {
|
|
12648
13133
|
if (signal?.aborted)
|
|
@@ -12672,7 +13157,7 @@ function createRedisAdapter(config) {
|
|
|
12672
13157
|
}
|
|
12673
13158
|
async function snapshotFetchStream(db, hexKey, signal) {
|
|
12674
13159
|
const total = Number(await evalHex(db, `${LUA_HEX_KEY_PRELUDE} return redis.call('XLEN', fromhex(ARGV[1]))`, [hexKey], "XLEN", signal)) || 0;
|
|
12675
|
-
const hasher =
|
|
13160
|
+
const hasher = createHash4("sha256");
|
|
12676
13161
|
let previewEntries = [];
|
|
12677
13162
|
let startId = "-";
|
|
12678
13163
|
const seenIds = new Set;
|
|
@@ -13264,7 +13749,7 @@ var init_raw_file_headers = __esm(() => {
|
|
|
13264
13749
|
|
|
13265
13750
|
// web-src/server/database/adapters/s3.ts
|
|
13266
13751
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
13267
|
-
import { createHash as
|
|
13752
|
+
import { createHash as createHash5, createHmac } from "node:crypto";
|
|
13268
13753
|
function createS3RequestDeadline() {
|
|
13269
13754
|
const timeoutMs = s3RequestTimeoutMs;
|
|
13270
13755
|
return {
|
|
@@ -13294,10 +13779,10 @@ function hmac(key, value) {
|
|
|
13294
13779
|
return createHmac("sha256", key).update(value, "utf8").digest();
|
|
13295
13780
|
}
|
|
13296
13781
|
function sha256(value) {
|
|
13297
|
-
return
|
|
13782
|
+
return createHash5("sha256").update(value, "utf8").digest("hex");
|
|
13298
13783
|
}
|
|
13299
13784
|
function sha256Bytes(value) {
|
|
13300
|
-
return
|
|
13785
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
13301
13786
|
}
|
|
13302
13787
|
function encodeRfc3986(value) {
|
|
13303
13788
|
return encodeURIComponent(value).replace(/[!'()*]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
@@ -14304,7 +14789,7 @@ import {
|
|
|
14304
14789
|
statSync as statSync5
|
|
14305
14790
|
} from "node:fs";
|
|
14306
14791
|
import { lstat, open as open2, readdir, readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
14307
|
-
import { basename, join as
|
|
14792
|
+
import { basename, join as join11, relative as relative5 } from "node:path";
|
|
14308
14793
|
function isSqliteFile(fullPath) {
|
|
14309
14794
|
try {
|
|
14310
14795
|
const stat3 = statSync5(fullPath);
|
|
@@ -14375,7 +14860,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
14375
14860
|
return;
|
|
14376
14861
|
if (omitSet.has(entry.toLowerCase()))
|
|
14377
14862
|
continue;
|
|
14378
|
-
const full =
|
|
14863
|
+
const full = join11(dir, entry);
|
|
14379
14864
|
let entryStat;
|
|
14380
14865
|
try {
|
|
14381
14866
|
entryStat = await lstat(full);
|
|
@@ -14392,7 +14877,7 @@ async function discoverSqliteFilesAsync(cwd, omitDirNames, signal) {
|
|
|
14392
14877
|
continue;
|
|
14393
14878
|
if (!await isSqliteFileAsync(full))
|
|
14394
14879
|
continue;
|
|
14395
|
-
const rel =
|
|
14880
|
+
const rel = relative5(cwd, full);
|
|
14396
14881
|
if (rel.startsWith("..") || rel.startsWith("/"))
|
|
14397
14882
|
continue;
|
|
14398
14883
|
results.push({
|
|
@@ -14419,7 +14904,7 @@ function validateDbPath(cwd, dbPath) {
|
|
|
14419
14904
|
const parts = dbPath.split(/[\\/]+/);
|
|
14420
14905
|
if (parts.some((p) => p === ".." || p.toLowerCase() === ".git" || p.toLowerCase() === ".code-viewer"))
|
|
14421
14906
|
return null;
|
|
14422
|
-
const full =
|
|
14907
|
+
const full = join11(cwd, dbPath);
|
|
14423
14908
|
if (!existsSync6(full))
|
|
14424
14909
|
return null;
|
|
14425
14910
|
let realCwd;
|
|
@@ -14430,7 +14915,7 @@ function validateDbPath(cwd, dbPath) {
|
|
|
14430
14915
|
} catch {
|
|
14431
14916
|
return null;
|
|
14432
14917
|
}
|
|
14433
|
-
const rel =
|
|
14918
|
+
const rel = relative5(realCwd, realFull);
|
|
14434
14919
|
if (rel === "" || rel.startsWith("..") || rel.startsWith("/"))
|
|
14435
14920
|
return null;
|
|
14436
14921
|
if (!isSqliteFile(realFull))
|
|
@@ -14591,7 +15076,7 @@ function resolveEnvValue(raw, composeDirEnv = {}) {
|
|
|
14591
15076
|
}
|
|
14592
15077
|
async function readDotenvAsync(composeDir) {
|
|
14593
15078
|
try {
|
|
14594
|
-
const content = await readFile2(
|
|
15079
|
+
const content = await readFile2(join11(composeDir, ".env"), "utf-8");
|
|
14595
15080
|
return parseDotenvContent(content);
|
|
14596
15081
|
} catch {
|
|
14597
15082
|
return {};
|
|
@@ -14713,7 +15198,7 @@ function parseComposeContent(content, filepath, composeDir, cwd, composeDirEnv,
|
|
|
14713
15198
|
for (let match = serviceRegex.exec(servicesBlock);match !== null; match = serviceRegex.exec(servicesBlock)) {
|
|
14714
15199
|
servicePositions.push({ name: match[1], start: match.index });
|
|
14715
15200
|
}
|
|
14716
|
-
const relDir =
|
|
15201
|
+
const relDir = relative5(cwd, composeDir);
|
|
14717
15202
|
const isRoot = relDir === "" || relDir === ".";
|
|
14718
15203
|
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
14719
15204
|
const filename = basename(filepath);
|
|
@@ -14818,7 +15303,7 @@ async function walkForMarkerFileAsync(dir, depth, omitSet, hasCapacity, visitDir
|
|
|
14818
15303
|
return;
|
|
14819
15304
|
if (omitSet.has(entry.toLowerCase()))
|
|
14820
15305
|
continue;
|
|
14821
|
-
const full =
|
|
15306
|
+
const full = join11(dir, entry);
|
|
14822
15307
|
let entryStat;
|
|
14823
15308
|
try {
|
|
14824
15309
|
entryStat = await lstat(full);
|
|
@@ -14845,7 +15330,7 @@ async function discoverDockerDatabasesAsync(cwd, omitDirNames = [], signal) {
|
|
|
14845
15330
|
omitSet.add("node_modules");
|
|
14846
15331
|
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_DOCKER_SERVICES, async (dir) => {
|
|
14847
15332
|
for (const filename of COMPOSE_FILENAMES) {
|
|
14848
|
-
const filepath =
|
|
15333
|
+
const filepath = join11(dir, filename);
|
|
14849
15334
|
if (await pathExistsAsync(filepath)) {
|
|
14850
15335
|
await parseComposeFileAsync(filepath, dir, cwd, results);
|
|
14851
15336
|
break;
|
|
@@ -14956,410 +15441,153 @@ function isSafeDockerRelDir(value) {
|
|
|
14956
15441
|
}
|
|
14957
15442
|
function isSafeDockerDatabaseName(value) {
|
|
14958
15443
|
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)
|
|
15056
|
-
return null;
|
|
15057
|
-
projectId = rest.slice(0, atIdx);
|
|
15058
|
-
try {
|
|
15059
|
-
relDir = decodeURIComponent(rest.slice(atIdx + 1));
|
|
15060
|
-
} catch {
|
|
15061
|
-
return null;
|
|
15062
|
-
}
|
|
15063
|
-
if (!isSafeDockerRelDir(relDir))
|
|
15064
|
-
return null;
|
|
15065
|
-
} else {
|
|
15066
|
-
projectId = rest;
|
|
15067
|
-
}
|
|
15068
|
-
if (!isSafeDockerServiceName(projectId))
|
|
15069
|
-
return null;
|
|
15070
|
-
return { projectId, relDir };
|
|
15071
|
-
}
|
|
15072
|
-
async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal) {
|
|
15073
|
-
const parsed = parseSupabaseDbId(dbId);
|
|
15074
|
-
if (!parsed)
|
|
15075
|
-
return null;
|
|
15076
|
-
const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
|
|
15077
|
-
return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
|
|
15078
|
-
}
|
|
15079
|
-
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;
|
|
15080
|
-
var init_discovery = __esm(() => {
|
|
15081
|
-
SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
|
|
15082
|
-
sqliteDiscoveryCache = new Map;
|
|
15083
|
-
COMPOSE_FILENAMES = [
|
|
15084
|
-
"docker-compose.yml",
|
|
15085
|
-
"docker-compose.yaml",
|
|
15086
|
-
"compose.yml",
|
|
15087
|
-
"compose.yaml"
|
|
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(/^\/+/, "");
|
|
15111
|
-
}
|
|
15112
|
-
function isInsideRoot(root, path) {
|
|
15113
|
-
const rel = relative5(root, path).replace(/\\/g, "/");
|
|
15114
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
15115
|
-
}
|
|
15116
|
-
function startWorktreeUpdateWatch(options) {
|
|
15117
|
-
const watch = options.watch || nodeWatch;
|
|
15118
|
-
const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
|
|
15119
|
-
const isDirectory = options.isDirectory || ((path) => {
|
|
15120
|
-
try {
|
|
15121
|
-
return lstatSync3(path).isDirectory();
|
|
15122
|
-
} catch {
|
|
15123
|
-
return false;
|
|
15124
|
-
}
|
|
15125
|
-
});
|
|
15126
|
-
const directorySignature = options.directorySignature || ((path) => {
|
|
15127
|
-
try {
|
|
15128
|
-
const stats = lstatSync3(path);
|
|
15129
|
-
if (!stats.isDirectory())
|
|
15130
|
-
return null;
|
|
15131
|
-
return `${stats.dev}:${stats.ino}`;
|
|
15132
|
-
} catch {
|
|
15133
|
-
return null;
|
|
15134
|
-
}
|
|
15135
|
-
});
|
|
15136
|
-
const setTimer = options.setTimeoutFn || setTimeout;
|
|
15137
|
-
const clearTimer = options.clearTimeoutFn || clearTimeout;
|
|
15138
|
-
const debounceMs = options.debounceMs ?? 250;
|
|
15139
|
-
const maxWatchedDirectories = Math.max(1, Math.floor(options.maxWatchedDirectories ?? DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT));
|
|
15140
|
-
const watchers = new Map;
|
|
15141
|
-
const signatures = new Map;
|
|
15142
|
-
const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
|
|
15143
|
-
const initialScanQueue = [];
|
|
15144
|
-
let initialScanTimer = null;
|
|
15145
|
-
let processingInitialScan = false;
|
|
15146
|
-
const pendingPathInspections = new Map;
|
|
15147
|
-
let pathInspectionTimer = null;
|
|
15148
|
-
let timer = null;
|
|
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);
|
|
15276
|
-
}
|
|
15277
|
-
scheduleUpdate(changed);
|
|
15278
|
-
};
|
|
15279
|
-
const processPathInspections = () => {
|
|
15280
|
-
pathInspectionTimer = null;
|
|
15281
|
-
const entries = [...pendingPathInspections];
|
|
15282
|
-
pendingPathInspections.clear();
|
|
15283
|
-
for (const [changed, fullChangedPath] of entries) {
|
|
15284
|
-
processChangedPath(changed, fullChangedPath);
|
|
15285
|
-
}
|
|
15286
|
-
};
|
|
15287
|
-
const queuePathInspection = (changed, fullChangedPath) => {
|
|
15288
|
-
pendingPathInspections.set(changed, fullChangedPath);
|
|
15289
|
-
if (!pathInspectionTimer)
|
|
15290
|
-
pathInspectionTimer = setTimer(processPathInspections, 25);
|
|
15291
|
-
};
|
|
15292
|
-
const watchDirectory = (dir, initialScan = false) => {
|
|
15293
|
-
if (watchers.has(dir))
|
|
15294
|
-
return;
|
|
15295
|
-
if (watchers.size >= maxWatchedDirectories) {
|
|
15296
|
-
reportWatchLimit();
|
|
15297
|
-
return;
|
|
15298
|
-
}
|
|
15299
|
-
const rel = directoryRelativePath(dir);
|
|
15300
|
-
if (rel && ignored(rel))
|
|
15301
|
-
return;
|
|
15302
|
-
try {
|
|
15303
|
-
const watcher = watch(dir, { persistent: false }, (_event, filename) => {
|
|
15304
|
-
if (!filename) {
|
|
15305
|
-
scheduleUpdate();
|
|
15306
|
-
return;
|
|
15307
|
-
}
|
|
15308
|
-
const changed = normalizeRelativePath(join10(rel, filename.toString()));
|
|
15309
|
-
if (ignored(changed))
|
|
15310
|
-
return;
|
|
15311
|
-
const fullChangedPath = join10(options.root, changed);
|
|
15312
|
-
if (!isInsideRoot(options.root, fullChangedPath))
|
|
15313
|
-
return;
|
|
15314
|
-
if (initialScanAsync) {
|
|
15315
|
-
queuePathInspection(changed, fullChangedPath);
|
|
15316
|
-
return;
|
|
15317
|
-
}
|
|
15318
|
-
processChangedPath(changed, fullChangedPath);
|
|
15319
|
-
}) || {};
|
|
15320
|
-
watchers.set(dir, watcher);
|
|
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
|
-
}
|
|
15335
|
-
});
|
|
15336
|
-
} catch (error) {
|
|
15337
|
-
options.onError?.(error);
|
|
15338
|
-
return;
|
|
15444
|
+
return true;
|
|
15445
|
+
if (value === "")
|
|
15446
|
+
return false;
|
|
15447
|
+
if (hasControlCharacter(value))
|
|
15448
|
+
return false;
|
|
15449
|
+
return /^[A-Za-z0-9_$.-]+$/.test(value);
|
|
15450
|
+
}
|
|
15451
|
+
function cloneSupabaseDiscoveryResult(result) {
|
|
15452
|
+
return result.map((entry) => ({ ...entry }));
|
|
15453
|
+
}
|
|
15454
|
+
function parseSupabaseConfigToml(content) {
|
|
15455
|
+
let section = null;
|
|
15456
|
+
let projectId = null;
|
|
15457
|
+
let dbPort = null;
|
|
15458
|
+
for (const rawLine of content.split(`
|
|
15459
|
+
`)) {
|
|
15460
|
+
const line = rawLine.trim();
|
|
15461
|
+
if (!line || line.startsWith("#"))
|
|
15462
|
+
continue;
|
|
15463
|
+
const sectionMatch = line.match(/^\[([^\]]+)\]$/);
|
|
15464
|
+
if (sectionMatch) {
|
|
15465
|
+
section = sectionMatch[1];
|
|
15466
|
+
continue;
|
|
15339
15467
|
}
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15468
|
+
const kvMatch = line.match(/^([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.+)$/);
|
|
15469
|
+
if (!kvMatch)
|
|
15470
|
+
continue;
|
|
15471
|
+
const value = stripScalarSyntax(kvMatch[2]);
|
|
15472
|
+
if (section === null && kvMatch[1] === "project_id") {
|
|
15473
|
+
projectId = value;
|
|
15474
|
+
} else if (section === "db" && kvMatch[1] === "port") {
|
|
15475
|
+
dbPort = value;
|
|
15343
15476
|
}
|
|
15344
|
-
|
|
15345
|
-
|
|
15477
|
+
}
|
|
15478
|
+
if (!projectId || !isSafeDockerServiceName(projectId))
|
|
15479
|
+
return null;
|
|
15480
|
+
return {
|
|
15481
|
+
projectId,
|
|
15482
|
+
dbPort: dbPort && /^\d+$/.test(dbPort) ? dbPort : DEFAULT_SUPABASE_DB_PORT
|
|
15483
|
+
};
|
|
15484
|
+
}
|
|
15485
|
+
async function discoverSupabaseCliProjectsAsync(cwd, omitDirNames = [], signal) {
|
|
15486
|
+
const cacheKey = discoveryCacheKey(cwd, omitDirNames);
|
|
15487
|
+
const now = Date.now();
|
|
15488
|
+
const cached = supabaseDiscoveryCache.get(cacheKey);
|
|
15489
|
+
if (cached && cached.expiresAt > now) {
|
|
15490
|
+
return cloneSupabaseDiscoveryResult(cached.result);
|
|
15491
|
+
}
|
|
15492
|
+
const omitSet = new Set(omitDirNames.map((d) => d.toLowerCase()));
|
|
15493
|
+
omitSet.add(".git");
|
|
15494
|
+
omitSet.add("node_modules");
|
|
15495
|
+
const results = [];
|
|
15496
|
+
await walkForMarkerFileAsync(cwd, 0, omitSet, () => results.length < MAX_SUPABASE_PROJECTS, async (dir) => {
|
|
15497
|
+
const configPath = join11(dir, "supabase", "config.toml");
|
|
15498
|
+
if (!await pathExistsAsync(configPath))
|
|
15346
15499
|
return;
|
|
15500
|
+
try {
|
|
15501
|
+
const content = await readFile2(configPath, "utf-8");
|
|
15502
|
+
const parsed = parseSupabaseConfigToml(content);
|
|
15503
|
+
if (!parsed)
|
|
15504
|
+
return;
|
|
15505
|
+
const relDir = relative5(cwd, dir);
|
|
15506
|
+
const isRoot = relDir === "" || relDir === ".";
|
|
15507
|
+
const relDirSlash = relDir.replace(/\\/g, "/");
|
|
15508
|
+
const id = isRoot ? `supabase:${parsed.projectId}` : `supabase:${parsed.projectId}@${encodeURIComponent(relDirSlash)}`;
|
|
15509
|
+
const labelPath = isRoot ? "" : ` — ${relDirSlash}`;
|
|
15510
|
+
results.push({
|
|
15511
|
+
id,
|
|
15512
|
+
path: isRoot ? "supabase/config.toml" : `${relDirSlash}/supabase/config.toml`,
|
|
15513
|
+
name: `${parsed.projectId} (Supabase CLI, postgres@127.0.0.1:${parsed.dbPort}/postgres${labelPath})`,
|
|
15514
|
+
sizeBytes: 0,
|
|
15515
|
+
kind: "postgresql",
|
|
15516
|
+
projectId: parsed.projectId,
|
|
15517
|
+
relDirSlash,
|
|
15518
|
+
dbPort: parsed.dbPort
|
|
15519
|
+
});
|
|
15520
|
+
} catch {}
|
|
15521
|
+
}, signal);
|
|
15522
|
+
if (signal?.aborted)
|
|
15523
|
+
return cloneSupabaseDiscoveryResult(results);
|
|
15524
|
+
supabaseDiscoveryCache.set(cacheKey, {
|
|
15525
|
+
expiresAt: now + SUPABASE_DISCOVERY_TTL_MS,
|
|
15526
|
+
result: cloneSupabaseDiscoveryResult(results)
|
|
15527
|
+
});
|
|
15528
|
+
return cloneSupabaseDiscoveryResult(results);
|
|
15529
|
+
}
|
|
15530
|
+
function parseSupabaseDbId(dbId) {
|
|
15531
|
+
if (!dbId.startsWith("supabase:"))
|
|
15532
|
+
return null;
|
|
15533
|
+
const rest = dbId.slice("supabase:".length);
|
|
15534
|
+
if (!rest)
|
|
15535
|
+
return null;
|
|
15536
|
+
const atIdx = rest.indexOf("@");
|
|
15537
|
+
let projectId;
|
|
15538
|
+
let relDir = "";
|
|
15539
|
+
if (atIdx >= 0) {
|
|
15540
|
+
if (rest.indexOf("@", atIdx + 1) >= 0)
|
|
15541
|
+
return null;
|
|
15542
|
+
projectId = rest.slice(0, atIdx);
|
|
15543
|
+
try {
|
|
15544
|
+
relDir = decodeURIComponent(rest.slice(atIdx + 1));
|
|
15545
|
+
} catch {
|
|
15546
|
+
return null;
|
|
15347
15547
|
}
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
}
|
|
15351
|
-
|
|
15352
|
-
|
|
15548
|
+
if (!isSafeDockerRelDir(relDir))
|
|
15549
|
+
return null;
|
|
15550
|
+
} else {
|
|
15551
|
+
projectId = rest;
|
|
15552
|
+
}
|
|
15553
|
+
if (!isSafeDockerServiceName(projectId))
|
|
15554
|
+
return null;
|
|
15555
|
+
return { projectId, relDir };
|
|
15353
15556
|
}
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15557
|
+
async function findSupabaseCliProjectByDbIdAsync(cwd, dbId, omitDirNames, signal) {
|
|
15558
|
+
const parsed = parseSupabaseDbId(dbId);
|
|
15559
|
+
if (!parsed)
|
|
15560
|
+
return null;
|
|
15561
|
+
const projects = await discoverSupabaseCliProjectsAsync(cwd, omitDirNames, signal);
|
|
15562
|
+
return projects.find((p) => p.projectId === parsed.projectId && p.relDirSlash === parsed.relDir) || null;
|
|
15563
|
+
}
|
|
15564
|
+
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;
|
|
15565
|
+
var init_discovery = __esm(() => {
|
|
15566
|
+
SQLITE_EXTENSIONS = new Set([".db", ".sqlite", ".sqlite3", ".s3db"]);
|
|
15567
|
+
sqliteDiscoveryCache = new Map;
|
|
15568
|
+
COMPOSE_FILENAMES = [
|
|
15569
|
+
"docker-compose.yml",
|
|
15570
|
+
"docker-compose.yaml",
|
|
15571
|
+
"compose.yml",
|
|
15572
|
+
"compose.yaml"
|
|
15573
|
+
];
|
|
15574
|
+
dockerDiscoveryCache = new Map;
|
|
15575
|
+
DB_KIND_VALUES = new Set([
|
|
15576
|
+
"sqlite",
|
|
15577
|
+
"postgresql",
|
|
15578
|
+
"mysql",
|
|
15579
|
+
"redis",
|
|
15580
|
+
"elasticsearch",
|
|
15581
|
+
"s3",
|
|
15582
|
+
"dynamodb"
|
|
15583
|
+
]);
|
|
15584
|
+
supabaseDiscoveryCache = new Map;
|
|
15357
15585
|
});
|
|
15358
15586
|
|
|
15359
15587
|
// web-src/server/state-store.ts
|
|
15360
|
-
import { join as
|
|
15588
|
+
import { join as join12 } from "node:path";
|
|
15361
15589
|
function codeViewerPath(root, fileName) {
|
|
15362
|
-
return
|
|
15590
|
+
return join12(root, CODE_VIEWER_DIR2, fileName);
|
|
15363
15591
|
}
|
|
15364
15592
|
function isRecord(value) {
|
|
15365
15593
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -15807,35 +16035,105 @@ function mergeDbUiState(current, patch) {
|
|
|
15807
16035
|
version: 1
|
|
15808
16036
|
});
|
|
15809
16037
|
}
|
|
16038
|
+
function emptyToolsState() {
|
|
16039
|
+
return { version: 1 };
|
|
16040
|
+
}
|
|
16041
|
+
function sanitizeToolsState(raw) {
|
|
16042
|
+
if (!isRecord(raw))
|
|
16043
|
+
return emptyToolsState();
|
|
16044
|
+
const out = { version: 1 };
|
|
16045
|
+
if (isToolId(raw.activeTool))
|
|
16046
|
+
out.activeTool = raw.activeTool;
|
|
16047
|
+
const width = optionalNumber(raw.width, MIN_TOOLS_SHEET_WIDTH, MAX_TOOLS_SHEET_WIDTH);
|
|
16048
|
+
if (width !== undefined)
|
|
16049
|
+
out.width = width;
|
|
16050
|
+
if (isRecord(raw.drafts)) {
|
|
16051
|
+
const drafts = {};
|
|
16052
|
+
for (const id of TOOL_IDS) {
|
|
16053
|
+
const draft = raw.drafts[id];
|
|
16054
|
+
if (typeof draft !== "string")
|
|
16055
|
+
continue;
|
|
16056
|
+
if (draft.length === 0 || draft.length > MAX_TOOL_DRAFT_LEN)
|
|
16057
|
+
continue;
|
|
16058
|
+
drafts[id] = draft;
|
|
16059
|
+
}
|
|
16060
|
+
if (Object.keys(drafts).length > 0)
|
|
16061
|
+
out.drafts = drafts;
|
|
16062
|
+
}
|
|
16063
|
+
return out;
|
|
16064
|
+
}
|
|
16065
|
+
function mergeToolsState(current, patch) {
|
|
16066
|
+
if (!isRecord(patch))
|
|
16067
|
+
return current;
|
|
16068
|
+
const next = { ...current, version: 1 };
|
|
16069
|
+
if ("activeTool" in patch) {
|
|
16070
|
+
if (patch.activeTool === null)
|
|
16071
|
+
delete next.activeTool;
|
|
16072
|
+
else if (isToolId(patch.activeTool))
|
|
16073
|
+
next.activeTool = patch.activeTool;
|
|
16074
|
+
}
|
|
16075
|
+
if ("width" in patch) {
|
|
16076
|
+
if (patch.width === null)
|
|
16077
|
+
delete next.width;
|
|
16078
|
+
else {
|
|
16079
|
+
const width = optionalNumber(patch.width, MIN_TOOLS_SHEET_WIDTH, MAX_TOOLS_SHEET_WIDTH);
|
|
16080
|
+
if (width !== undefined)
|
|
16081
|
+
next.width = width;
|
|
16082
|
+
}
|
|
16083
|
+
}
|
|
16084
|
+
if (isRecord(patch.drafts)) {
|
|
16085
|
+
const drafts = { ...next.drafts ?? {} };
|
|
16086
|
+
for (const id of TOOL_IDS) {
|
|
16087
|
+
if (!(id in patch.drafts))
|
|
16088
|
+
continue;
|
|
16089
|
+
const value = patch.drafts[id];
|
|
16090
|
+
if (value === null || value === "") {
|
|
16091
|
+
delete drafts[id];
|
|
16092
|
+
continue;
|
|
16093
|
+
}
|
|
16094
|
+
if (typeof value !== "string")
|
|
16095
|
+
continue;
|
|
16096
|
+
if (value.length > MAX_TOOL_DRAFT_LEN)
|
|
16097
|
+
throw new Error("tools state too large");
|
|
16098
|
+
drafts[id] = value;
|
|
16099
|
+
}
|
|
16100
|
+
next.drafts = drafts;
|
|
16101
|
+
}
|
|
16102
|
+
return sanitizeToolsState(next);
|
|
16103
|
+
}
|
|
16104
|
+
function patchJsonState(store, root, patch, merge) {
|
|
16105
|
+
return store.update(root, (state) => {
|
|
16106
|
+
const next = merge(state, patch);
|
|
16107
|
+
return { state: next, result: next };
|
|
16108
|
+
});
|
|
16109
|
+
}
|
|
15810
16110
|
async function loadAppSettingsState(root) {
|
|
15811
16111
|
return settingsStore.load(root);
|
|
15812
16112
|
}
|
|
15813
16113
|
async function patchAppSettingsState(root, patch) {
|
|
15814
|
-
return settingsStore
|
|
15815
|
-
const next = mergeSettings(state, patch);
|
|
15816
|
-
return { state: next, result: next };
|
|
15817
|
-
});
|
|
16114
|
+
return patchJsonState(settingsStore, root, patch, mergeSettings);
|
|
15818
16115
|
}
|
|
15819
16116
|
async function loadViewState(root) {
|
|
15820
16117
|
return viewStateStore.load(root);
|
|
15821
16118
|
}
|
|
15822
16119
|
async function patchViewState(root, patch) {
|
|
15823
|
-
return viewStateStore
|
|
15824
|
-
const next = mergeViewState(state, patch);
|
|
15825
|
-
return { state: next, result: next };
|
|
15826
|
-
});
|
|
16120
|
+
return patchJsonState(viewStateStore, root, patch, mergeViewState);
|
|
15827
16121
|
}
|
|
15828
16122
|
async function loadDbUiState(root) {
|
|
15829
16123
|
return dbUiStore.load(root);
|
|
15830
16124
|
}
|
|
15831
16125
|
async function patchDbUiState(root, patch) {
|
|
15832
|
-
return dbUiStore
|
|
15833
|
-
const next = mergeDbUiState(state, patch);
|
|
15834
|
-
return { state: next, result: next };
|
|
15835
|
-
});
|
|
16126
|
+
return patchJsonState(dbUiStore, root, patch, mergeDbUiState);
|
|
15836
16127
|
}
|
|
15837
|
-
|
|
16128
|
+
async function loadToolsState(root) {
|
|
16129
|
+
return toolsStore.load(root);
|
|
16130
|
+
}
|
|
16131
|
+
async function patchToolsState(root, patch) {
|
|
16132
|
+
return patchJsonState(toolsStore, root, patch, mergeToolsState);
|
|
16133
|
+
}
|
|
16134
|
+
var CODE_VIEWER_DIR2 = ".code-viewer", SETTINGS_FILE_NAME = "settings.json", VIEW_STATE_FILE_NAME = "view-state.json", DB_UI_FILE_NAME = "db-ui.json", TOOLS_FILE_NAME = "tools.json", MAX_SETTINGS_BYTES = 200000, MAX_VIEW_STATE_BYTES = 1e6, MAX_DB_UI_BYTES = 1e6, MAX_TOOL_DRAFT_LEN = 200000, MAX_TOOLS_BYTES = 4000000, MAX_REF_LEN = 1024, MAX_KEY_LEN = 2048, MAX_VIEW_ITEMS = 20000, MAX_DB_UI_DBS = 200, MAX_DB_UI_TABLES = 500, MAX_DB_UI_COLUMNS = 1000, MAX_DB_UI_EXPANDED_SCOPES = 500, DB_UI_BOOL_PREF_KEYS, settingsStore, viewStateStore, dbUiStore, toolsStore;
|
|
15838
16135
|
var init_state_store = __esm(() => {
|
|
16136
|
+
init_tools();
|
|
15839
16137
|
init_json_store();
|
|
15840
16138
|
init_worktree_watcher();
|
|
15841
16139
|
DB_UI_BOOL_PREF_KEYS = ["s3TooltipEnabled", "inferFkRails"];
|
|
@@ -15863,6 +16161,14 @@ var init_state_store = __esm(() => {
|
|
|
15863
16161
|
backupSuffix: "corrupt",
|
|
15864
16162
|
sizeErrorMessage: "db UI state too large"
|
|
15865
16163
|
});
|
|
16164
|
+
toolsStore = createJsonFileStore({
|
|
16165
|
+
filePath: (root) => codeViewerPath(root, TOOLS_FILE_NAME),
|
|
16166
|
+
empty: emptyToolsState,
|
|
16167
|
+
sanitize: sanitizeToolsState,
|
|
16168
|
+
maxBytes: MAX_TOOLS_BYTES,
|
|
16169
|
+
backupSuffix: "corrupt",
|
|
16170
|
+
sizeErrorMessage: "tools state too large"
|
|
16171
|
+
});
|
|
15866
16172
|
});
|
|
15867
16173
|
|
|
15868
16174
|
// web-src/server/database/adapters/async-facade.ts
|
|
@@ -16239,7 +16545,7 @@ var init_d1 = __esm(() => {
|
|
|
16239
16545
|
|
|
16240
16546
|
// web-src/server/database/adapters/dynamodb.ts
|
|
16241
16547
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
16242
|
-
import { createHash as
|
|
16548
|
+
import { createHash as createHash6, createHmac as createHmac2 } from "node:crypto";
|
|
16243
16549
|
function createDynamoDbRequestDeadline() {
|
|
16244
16550
|
const timeoutMs = dynamoDbRequestTimeoutMs;
|
|
16245
16551
|
return { expiresAt: Date.now() + timeoutMs, timeoutMs };
|
|
@@ -16263,7 +16569,7 @@ function hmac2(key, value) {
|
|
|
16263
16569
|
return createHmac2("sha256", key).update(value, "utf8").digest();
|
|
16264
16570
|
}
|
|
16265
16571
|
function sha2562(value) {
|
|
16266
|
-
return
|
|
16572
|
+
return createHash6("sha256").update(value, "utf8").digest("hex");
|
|
16267
16573
|
}
|
|
16268
16574
|
function amzDate2(date = new Date) {
|
|
16269
16575
|
const iso = date.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
@@ -16908,7 +17214,7 @@ var init_credential_store = __esm(() => {
|
|
|
16908
17214
|
// web-src/server/database/connections-store.ts
|
|
16909
17215
|
import { randomUUID } from "node:crypto";
|
|
16910
17216
|
import { chmod } from "node:fs/promises";
|
|
16911
|
-
import { join as
|
|
17217
|
+
import { join as join13 } from "node:path";
|
|
16912
17218
|
function secretKey(cwd, id) {
|
|
16913
17219
|
return `${cwd}\x00${id}`;
|
|
16914
17220
|
}
|
|
@@ -16959,7 +17265,7 @@ function withRuntimeSecrets(cwd, connection) {
|
|
|
16959
17265
|
};
|
|
16960
17266
|
}
|
|
16961
17267
|
function connectionsFilePath(root) {
|
|
16962
|
-
return
|
|
17268
|
+
return join13(root, ".code-viewer", CONNECTIONS_FILE_NAME);
|
|
16963
17269
|
}
|
|
16964
17270
|
function emptyState() {
|
|
16965
17271
|
return { version: 1, connections: [] };
|
|
@@ -18771,9 +19077,9 @@ var init_handle_s3 = __esm(() => {
|
|
|
18771
19077
|
});
|
|
18772
19078
|
|
|
18773
19079
|
// web-src/server/database/query-history.ts
|
|
18774
|
-
import { join as
|
|
19080
|
+
import { join as join14 } from "node:path";
|
|
18775
19081
|
function historyFilePath(root) {
|
|
18776
|
-
return
|
|
19082
|
+
return join14(root, CODE_VIEWER_DIR3, HISTORY_FILE_NAME);
|
|
18777
19083
|
}
|
|
18778
19084
|
function emptyState2() {
|
|
18779
19085
|
return { version: 1, entries: [] };
|
|
@@ -18921,11 +19227,11 @@ var init_query_history = __esm(() => {
|
|
|
18921
19227
|
});
|
|
18922
19228
|
|
|
18923
19229
|
// web-src/server/database/snapshot-store.ts
|
|
18924
|
-
import { createHash as
|
|
19230
|
+
import { createHash as createHash7, randomBytes as randomBytes2 } from "node:crypto";
|
|
18925
19231
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
18926
|
-
import { join as
|
|
19232
|
+
import { join as join15 } from "node:path";
|
|
18927
19233
|
async function getStoreDb(cwd) {
|
|
18928
|
-
const dbPath =
|
|
19234
|
+
const dbPath = join15(cwd, CODE_VIEWER_DIR4, SNAPSHOT_DB_NAME);
|
|
18929
19235
|
if (storeDb && storeDbPath === dbPath)
|
|
18930
19236
|
return storeDb;
|
|
18931
19237
|
if (storeDb) {
|
|
@@ -18933,7 +19239,7 @@ async function getStoreDb(cwd) {
|
|
|
18933
19239
|
storeDb.close();
|
|
18934
19240
|
} catch {}
|
|
18935
19241
|
}
|
|
18936
|
-
mkdirSync3(
|
|
19242
|
+
mkdirSync3(join15(cwd, CODE_VIEWER_DIR4), { recursive: true });
|
|
18937
19243
|
const DbClass = await loadSqliteClass();
|
|
18938
19244
|
storeDb = new DbClass(dbPath);
|
|
18939
19245
|
storeDbPath = dbPath;
|
|
@@ -18952,7 +19258,7 @@ function makeId2(prefix) {
|
|
|
18952
19258
|
return `${prefix}-${randomBytes2(8).toString("hex")}`;
|
|
18953
19259
|
}
|
|
18954
19260
|
function hashPayload(payloadJson) {
|
|
18955
|
-
return
|
|
19261
|
+
return createHash7("sha256").update(payloadJson).digest("hex");
|
|
18956
19262
|
}
|
|
18957
19263
|
function hashLengthPrefixed(hasher, value) {
|
|
18958
19264
|
hasher.update(`${Buffer.byteLength(value, "utf8")}:`);
|
|
@@ -19027,7 +19333,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
|
|
|
19027
19333
|
db.exec("BEGIN");
|
|
19028
19334
|
try {
|
|
19029
19335
|
for (const row of rows) {
|
|
19030
|
-
const rowKeyHash =
|
|
19336
|
+
const rowKeyHash = createHash7("sha256").update(row.rowKeyJson).digest("hex");
|
|
19031
19337
|
const payloadHash = hashPayload(row.payloadJson);
|
|
19032
19338
|
insertRow.run(revisionId, rowKeyHash, row.rowKeyJson, row.rowHash, payloadHash);
|
|
19033
19339
|
insertPayload.run(payloadHash, row.payloadJson);
|
|
@@ -19041,7 +19347,7 @@ async function addSnapshotTableRows(cwd, revisionId, rows) {
|
|
|
19041
19347
|
}
|
|
19042
19348
|
}
|
|
19043
19349
|
function computeRevisionTableHash(db, revisionId) {
|
|
19044
|
-
const hasher =
|
|
19350
|
+
const hasher = createHash7("sha256");
|
|
19045
19351
|
hashLengthPrefixed(hasher, `snapshot-table-v${SNAPSHOT_TABLE_HASH_VERSION}`);
|
|
19046
19352
|
let rowCount = 0;
|
|
19047
19353
|
let last;
|
|
@@ -19539,9 +19845,9 @@ var init_snapshot_runner = __esm(() => {
|
|
|
19539
19845
|
});
|
|
19540
19846
|
|
|
19541
19847
|
// web-src/server/database/tabs-store.ts
|
|
19542
|
-
import { join as
|
|
19848
|
+
import { join as join16 } from "node:path";
|
|
19543
19849
|
function tabsFilePath(root) {
|
|
19544
|
-
return
|
|
19850
|
+
return join16(root, CODE_VIEWER_DIR5, TABS_FILE_NAME);
|
|
19545
19851
|
}
|
|
19546
19852
|
function emptyState3() {
|
|
19547
19853
|
return { version: 1, tabs: [], activeTabId: null };
|
|
@@ -21495,7 +21801,7 @@ var init_handle = __esm(() => {
|
|
|
21495
21801
|
|
|
21496
21802
|
// web-src/server/doctor.ts
|
|
21497
21803
|
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync6, statSync as statSync6 } from "node:fs";
|
|
21498
|
-
import { dirname as dirname5, join as
|
|
21804
|
+
import { dirname as dirname5, join as join17, relative as relative6 } from "node:path";
|
|
21499
21805
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21500
21806
|
function statusWorse(a, b) {
|
|
21501
21807
|
const rank = { ok: 0, warn: 1, error: 2 };
|
|
@@ -21589,7 +21895,7 @@ function findCodeViewerPackageJson() {
|
|
|
21589
21895
|
cursor = dirname5(process.argv[1] || ".");
|
|
21590
21896
|
}
|
|
21591
21897
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
21592
|
-
const candidate =
|
|
21898
|
+
const candidate = join17(cursor, "package.json");
|
|
21593
21899
|
try {
|
|
21594
21900
|
const raw = readFileSync6(candidate, "utf8");
|
|
21595
21901
|
const pkg = JSON.parse(raw);
|
|
@@ -21685,7 +21991,7 @@ async function checkSqlite(cwd) {
|
|
|
21685
21991
|
return { id: "sqlite", title: "SQLite driver", rows };
|
|
21686
21992
|
}
|
|
21687
21993
|
async function trySnapshotDbOpen(cwd) {
|
|
21688
|
-
const dbPath =
|
|
21994
|
+
const dbPath = join17(cwd, SNAPSHOT_DB_REL);
|
|
21689
21995
|
try {
|
|
21690
21996
|
statSync6(dbPath);
|
|
21691
21997
|
} catch {
|
|
@@ -21706,7 +22012,7 @@ async function trySnapshotDbOpen(cwd) {
|
|
|
21706
22012
|
}
|
|
21707
22013
|
}
|
|
21708
22014
|
function checkSnapshotStore(cwd) {
|
|
21709
|
-
const dbPath =
|
|
22015
|
+
const dbPath = join17(cwd, SNAPSHOT_DB_REL);
|
|
21710
22016
|
const dir = dirname5(dbPath);
|
|
21711
22017
|
let dirStatus = "ok";
|
|
21712
22018
|
let dirDetail = dir;
|
|
@@ -22725,12 +23031,12 @@ function startDevAssetReload(options) {
|
|
|
22725
23031
|
var init_dev_assets = () => {};
|
|
22726
23032
|
|
|
22727
23033
|
// web-src/server/journal.ts
|
|
22728
|
-
import { join as
|
|
23034
|
+
import { join as join18 } from "node:path";
|
|
22729
23035
|
function dailyJournalFilePath(root) {
|
|
22730
|
-
return
|
|
23036
|
+
return join18(root, CODE_VIEWER_DIR, DAILY_JOURNAL_FILE_NAME);
|
|
22731
23037
|
}
|
|
22732
23038
|
function journalTasksFilePath(root) {
|
|
22733
|
-
return
|
|
23039
|
+
return join18(root, CODE_VIEWER_DIR, JOURNAL_TASKS_FILE_NAME);
|
|
22734
23040
|
}
|
|
22735
23041
|
function emptyDailyJournalState() {
|
|
22736
23042
|
return { version: 1, entries: [] };
|
|
@@ -23343,7 +23649,7 @@ var init_journal2 = __esm(() => {
|
|
|
23343
23649
|
// web-src/server/search-service.ts
|
|
23344
23650
|
import { existsSync as existsSync7, realpathSync as realpathSync6 } from "node:fs";
|
|
23345
23651
|
import { lstat as lstat2, readFile as readFile3 } from "node:fs/promises";
|
|
23346
|
-
import { join as
|
|
23652
|
+
import { join as join19, relative as relative7 } from "node:path";
|
|
23347
23653
|
async function rgAvailableAsync(cwd) {
|
|
23348
23654
|
if (rgAvailableCache !== null)
|
|
23349
23655
|
return rgAvailableCache;
|
|
@@ -23373,7 +23679,7 @@ function safeWorktreePath(env, path) {
|
|
|
23373
23679
|
return null;
|
|
23374
23680
|
if (isGitInternalPath(path))
|
|
23375
23681
|
return null;
|
|
23376
|
-
const full =
|
|
23682
|
+
const full = join19(env.cwd, path);
|
|
23377
23683
|
if (!existsSync7(full))
|
|
23378
23684
|
return null;
|
|
23379
23685
|
let realCwd;
|
|
@@ -23566,7 +23872,7 @@ var init_search_service = __esm(() => {
|
|
|
23566
23872
|
|
|
23567
23873
|
// web-src/server/mcp.ts
|
|
23568
23874
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
23569
|
-
import { join as
|
|
23875
|
+
import { join as join20 } from "node:path";
|
|
23570
23876
|
function defaultMcpTools(options = {}) {
|
|
23571
23877
|
return [
|
|
23572
23878
|
{
|
|
@@ -25040,7 +25346,7 @@ var init_mcp = __esm(() => {
|
|
|
25040
25346
|
init_search_cli();
|
|
25041
25347
|
init_search_service();
|
|
25042
25348
|
init_status_cli();
|
|
25043
|
-
PACKAGE_VERSION = JSON.parse(readFileSync7(
|
|
25349
|
+
PACKAGE_VERSION = JSON.parse(readFileSync7(join20(ROOT, "package.json"), "utf8")).version;
|
|
25044
25350
|
MCP_SERVER_INFO = {
|
|
25045
25351
|
name: "code-viewer",
|
|
25046
25352
|
title: "code-viewer",
|
|
@@ -25048,55 +25354,219 @@ var init_mcp = __esm(() => {
|
|
|
25048
25354
|
};
|
|
25049
25355
|
});
|
|
25050
25356
|
|
|
25357
|
+
// web-src/server/watch-supervisor.ts
|
|
25358
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
25359
|
+
import { join as join21 } from "node:path";
|
|
25360
|
+
function watchChildCommand() {
|
|
25361
|
+
const entry = process.argv[1] ?? "";
|
|
25362
|
+
const script = entry.endsWith(".ts") ? join21(import.meta.dir, "cli.ts") : entry;
|
|
25363
|
+
return [process.argv[0], script, "watch-child"];
|
|
25364
|
+
}
|
|
25365
|
+
function startWatchSupervisor(options) {
|
|
25366
|
+
const spawnChild = options.spawnFn || spawn3;
|
|
25367
|
+
const now = options.nowFn || Date.now;
|
|
25368
|
+
const setTimer = options.setTimeoutFn || setTimeout;
|
|
25369
|
+
const setRepeating = options.setIntervalFn || setInterval;
|
|
25370
|
+
const clearRepeating = options.clearIntervalFn || clearInterval;
|
|
25371
|
+
const heartbeatMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
|
|
25372
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
25373
|
+
const command = options.command ?? watchChildCommand();
|
|
25374
|
+
let child = null;
|
|
25375
|
+
let closed = false;
|
|
25376
|
+
let watchFailures = 0;
|
|
25377
|
+
let pollOnly = false;
|
|
25378
|
+
let sawReady = false;
|
|
25379
|
+
let lastSignalAt = now();
|
|
25380
|
+
let watchdog = null;
|
|
25381
|
+
const report = (error) => options.onError?.(error);
|
|
25382
|
+
const abandon = (victim) => {
|
|
25383
|
+
victim.removeAllListeners?.();
|
|
25384
|
+
victim.stdout?.removeAllListeners?.();
|
|
25385
|
+
victim.stderr?.removeAllListeners?.();
|
|
25386
|
+
try {
|
|
25387
|
+
victim.kill("SIGTERM");
|
|
25388
|
+
} catch {}
|
|
25389
|
+
const grace = setTimer(() => {
|
|
25390
|
+
try {
|
|
25391
|
+
victim.kill("SIGKILL");
|
|
25392
|
+
} catch {}
|
|
25393
|
+
}, KILL_GRACE_MS);
|
|
25394
|
+
grace.unref?.();
|
|
25395
|
+
};
|
|
25396
|
+
const handleMessage = (message) => {
|
|
25397
|
+
lastSignalAt = now();
|
|
25398
|
+
if (message.type === "heartbeat")
|
|
25399
|
+
return;
|
|
25400
|
+
if (message.type === "ready") {
|
|
25401
|
+
if (message.pollOnly || message.watching) {
|
|
25402
|
+
sawReady = true;
|
|
25403
|
+
watchFailures = 0;
|
|
25404
|
+
} else {
|
|
25405
|
+
watchFailures++;
|
|
25406
|
+
}
|
|
25407
|
+
return;
|
|
25408
|
+
}
|
|
25409
|
+
if (message.type === "update") {
|
|
25410
|
+
options.onUpdate(message.paths);
|
|
25411
|
+
return;
|
|
25412
|
+
}
|
|
25413
|
+
if (message.type === "watch-limit") {
|
|
25414
|
+
options.onWatchLimit?.(message.limit);
|
|
25415
|
+
return;
|
|
25416
|
+
}
|
|
25417
|
+
if (message.type === "warn")
|
|
25418
|
+
report(new Error(message.message));
|
|
25419
|
+
};
|
|
25420
|
+
const start = () => {
|
|
25421
|
+
if (closed)
|
|
25422
|
+
return;
|
|
25423
|
+
if (!pollOnly && watchFailures >= WATCH_FAILURES_BEFORE_POLL_ONLY) {
|
|
25424
|
+
pollOnly = true;
|
|
25425
|
+
options.onPollOnly?.();
|
|
25426
|
+
}
|
|
25427
|
+
const config = {
|
|
25428
|
+
root: options.root,
|
|
25429
|
+
omitDirNames: options.omitDirNames,
|
|
25430
|
+
excludeNames: options.excludeNames,
|
|
25431
|
+
maxWatchedDirectories: options.maxWatchedDirectories,
|
|
25432
|
+
debounceMs: options.debounceMs ?? 250,
|
|
25433
|
+
pollIntervalMs,
|
|
25434
|
+
heartbeatIntervalMs: heartbeatMs,
|
|
25435
|
+
parentPid: process.pid,
|
|
25436
|
+
pollOnly
|
|
25437
|
+
};
|
|
25438
|
+
let spawned;
|
|
25439
|
+
try {
|
|
25440
|
+
spawned = spawnChild(command[0], command.slice(1), {
|
|
25441
|
+
cwd: options.root,
|
|
25442
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25443
|
+
});
|
|
25444
|
+
} catch (error) {
|
|
25445
|
+
report(error);
|
|
25446
|
+
return;
|
|
25447
|
+
}
|
|
25448
|
+
child = spawned;
|
|
25449
|
+
sawReady = false;
|
|
25450
|
+
lastSignalAt = now();
|
|
25451
|
+
try {
|
|
25452
|
+
spawned.stdin?.write(`${JSON.stringify(config)}
|
|
25453
|
+
`);
|
|
25454
|
+
} catch (error) {
|
|
25455
|
+
report(error);
|
|
25456
|
+
}
|
|
25457
|
+
let buffer = "";
|
|
25458
|
+
spawned.stdout?.setEncoding?.("utf8");
|
|
25459
|
+
spawned.stdout?.on("data", (chunk) => {
|
|
25460
|
+
buffer += chunk;
|
|
25461
|
+
let newline = buffer.indexOf(`
|
|
25462
|
+
`);
|
|
25463
|
+
while (newline !== -1) {
|
|
25464
|
+
const line = buffer.slice(0, newline).trim();
|
|
25465
|
+
buffer = buffer.slice(newline + 1);
|
|
25466
|
+
if (line) {
|
|
25467
|
+
try {
|
|
25468
|
+
handleMessage(JSON.parse(line));
|
|
25469
|
+
} catch {}
|
|
25470
|
+
}
|
|
25471
|
+
newline = buffer.indexOf(`
|
|
25472
|
+
`);
|
|
25473
|
+
}
|
|
25474
|
+
});
|
|
25475
|
+
spawned.stderr?.setEncoding?.("utf8");
|
|
25476
|
+
spawned.stderr?.on("data", (chunk) => {
|
|
25477
|
+
const text = String(chunk).trim();
|
|
25478
|
+
if (text)
|
|
25479
|
+
console.warn(`[code-viewer] watch child: ${text}`);
|
|
25480
|
+
});
|
|
25481
|
+
spawned.on("error", (error) => report(error));
|
|
25482
|
+
spawned.on("exit", () => {
|
|
25483
|
+
if (closed || child !== spawned)
|
|
25484
|
+
return;
|
|
25485
|
+
if (!sawReady)
|
|
25486
|
+
watchFailures++;
|
|
25487
|
+
child = null;
|
|
25488
|
+
const retry = setTimer(start, heartbeatMs);
|
|
25489
|
+
retry.unref?.();
|
|
25490
|
+
});
|
|
25491
|
+
};
|
|
25492
|
+
start();
|
|
25493
|
+
watchdog = setRepeating(() => {
|
|
25494
|
+
if (closed || !child)
|
|
25495
|
+
return;
|
|
25496
|
+
if (now() - lastSignalAt < heartbeatMs * HEARTBEAT_MISS_LIMIT)
|
|
25497
|
+
return;
|
|
25498
|
+
const victim = child;
|
|
25499
|
+
child = null;
|
|
25500
|
+
watchFailures++;
|
|
25501
|
+
report(new Error("watch child stopped reporting; restarting"));
|
|
25502
|
+
abandon(victim);
|
|
25503
|
+
start();
|
|
25504
|
+
}, heartbeatMs);
|
|
25505
|
+
watchdog.unref?.();
|
|
25506
|
+
return {
|
|
25507
|
+
close: () => {
|
|
25508
|
+
closed = true;
|
|
25509
|
+
if (watchdog)
|
|
25510
|
+
clearRepeating(watchdog);
|
|
25511
|
+
watchdog = null;
|
|
25512
|
+
const victim = child;
|
|
25513
|
+
child = null;
|
|
25514
|
+
if (victim)
|
|
25515
|
+
abandon(victim);
|
|
25516
|
+
},
|
|
25517
|
+
pollOnly: () => pollOnly
|
|
25518
|
+
};
|
|
25519
|
+
}
|
|
25520
|
+
var DEFAULT_HEARTBEAT_MS = 5000, DEFAULT_POLL_MS = 15000, HEARTBEAT_MISS_LIMIT = 3, KILL_GRACE_MS = 2000, WATCH_FAILURES_BEFORE_POLL_ONLY = 2;
|
|
25521
|
+
var init_watch_supervisor = () => {};
|
|
25522
|
+
|
|
25051
25523
|
// web-src/server/state-route.ts
|
|
25052
25524
|
var exports_state_route = {};
|
|
25053
25525
|
__export(exports_state_route, {
|
|
25054
25526
|
handleStateRoute: () => handleStateRoute
|
|
25055
25527
|
});
|
|
25056
|
-
async function parseJsonBody(req) {
|
|
25057
|
-
return parseBoundedJsonBody(req,
|
|
25528
|
+
async function parseJsonBody(req, maxBytes) {
|
|
25529
|
+
return parseBoundedJsonBody(req, maxBytes, "state body too large");
|
|
25058
25530
|
}
|
|
25059
|
-
async function
|
|
25060
|
-
|
|
25061
|
-
}
|
|
25062
|
-
async function handleSettingsPatch(cwd, req, onChange) {
|
|
25063
|
-
const body = await parseJsonBody(req);
|
|
25531
|
+
async function handleStatePatch(cwd, req, patchState, tooLargeMessage, saveFailedMessage, maxBodyBytes, onChange) {
|
|
25532
|
+
const body = await parseJsonBody(req, maxBodyBytes);
|
|
25064
25533
|
if (body instanceof Response)
|
|
25065
25534
|
return body;
|
|
25066
25535
|
try {
|
|
25067
|
-
const next = await
|
|
25536
|
+
const next = await patchState(cwd, body);
|
|
25068
25537
|
if (onChange) {
|
|
25069
25538
|
try {
|
|
25070
25539
|
onChange(next);
|
|
25071
25540
|
} catch (notifyErr) {
|
|
25072
|
-
console.warn("[code-viewer]
|
|
25541
|
+
console.warn("[code-viewer] state change notify failed:", notifyErr);
|
|
25073
25542
|
}
|
|
25074
25543
|
}
|
|
25075
25544
|
return json(next);
|
|
25076
25545
|
} catch (err) {
|
|
25077
25546
|
const message = err instanceof Error ? err.message : String(err);
|
|
25078
|
-
if (message ===
|
|
25547
|
+
if (message === tooLargeMessage)
|
|
25079
25548
|
return textError(message, 413);
|
|
25080
25549
|
console.error("[code-viewer] state error:", err);
|
|
25081
|
-
return textError(
|
|
25550
|
+
return textError(saveFailedMessage, 500);
|
|
25082
25551
|
}
|
|
25083
25552
|
}
|
|
25553
|
+
async function handleSettingsGet(cwd) {
|
|
25554
|
+
return jsonLoadResponse(() => loadAppSettingsState(cwd), "state", "failed to load settings state");
|
|
25555
|
+
}
|
|
25556
|
+
async function handleSettingsPatch(cwd, req, onChange) {
|
|
25557
|
+
return handleStatePatch(cwd, req, patchAppSettingsState, "settings state too large", "failed to save settings state", MAX_STATE_PATCH_BODY_BYTES, onChange);
|
|
25558
|
+
}
|
|
25084
25559
|
async function handleViewGet(cwd) {
|
|
25085
25560
|
return jsonLoadResponse(() => loadViewState(cwd), "state", "failed to load view state");
|
|
25086
25561
|
}
|
|
25087
25562
|
async function handleViewPatch(cwd, req) {
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
if (message === "view state too large")
|
|
25096
|
-
return textError(message, 413);
|
|
25097
|
-
console.error("[code-viewer] state error:", err);
|
|
25098
|
-
return textError("failed to save view state", 500);
|
|
25099
|
-
}
|
|
25563
|
+
return handleStatePatch(cwd, req, patchViewState, "view state too large", "failed to save view state", MAX_STATE_PATCH_BODY_BYTES);
|
|
25564
|
+
}
|
|
25565
|
+
async function handleToolsGet(cwd) {
|
|
25566
|
+
return jsonLoadResponse(() => loadToolsState(cwd), "state", "failed to load tools state");
|
|
25567
|
+
}
|
|
25568
|
+
async function handleToolsPatch(cwd, req) {
|
|
25569
|
+
return handleStatePatch(cwd, req, patchToolsState, "tools state too large", "failed to save tools state", MAX_TOOLS_PATCH_BODY_BYTES);
|
|
25100
25570
|
}
|
|
25101
25571
|
async function handleStateRoute(req, url, cwd, sideEffectAllowed, options = {}) {
|
|
25102
25572
|
return dispatchRoutes(req, url, {
|
|
@@ -25109,10 +25579,15 @@ async function handleStateRoute(req, url, cwd, sideEffectAllowed, options = {})
|
|
|
25109
25579
|
methods: ["GET", "PATCH"],
|
|
25110
25580
|
sideEffect: (method) => method !== "GET",
|
|
25111
25581
|
handler: () => req.method === "GET" ? handleViewGet(cwd) : handleViewPatch(cwd, req)
|
|
25582
|
+
},
|
|
25583
|
+
"/_state/tools": {
|
|
25584
|
+
methods: ["GET", "PATCH"],
|
|
25585
|
+
sideEffect: (method) => method !== "GET",
|
|
25586
|
+
handler: () => req.method === "GET" ? handleToolsGet(cwd) : handleToolsPatch(cwd, req)
|
|
25112
25587
|
}
|
|
25113
25588
|
}, sideEffectAllowed, (res) => res, (err) => handleError("state", "handle state request", err));
|
|
25114
25589
|
}
|
|
25115
|
-
var MAX_STATE_PATCH_BODY_BYTES = 1e6;
|
|
25590
|
+
var MAX_STATE_PATCH_BODY_BYTES = 1e6, MAX_TOOLS_PATCH_BODY_BYTES = 4000000;
|
|
25116
25591
|
var init_state_route = __esm(() => {
|
|
25117
25592
|
init_handle_shared();
|
|
25118
25593
|
init_state_store();
|
|
@@ -25124,7 +25599,7 @@ import {
|
|
|
25124
25599
|
closeSync as closeSync2,
|
|
25125
25600
|
constants as constants3,
|
|
25126
25601
|
existsSync as existsSync8,
|
|
25127
|
-
lstatSync as
|
|
25602
|
+
lstatSync as lstatSync5,
|
|
25128
25603
|
mkdirSync as mkdirSync4,
|
|
25129
25604
|
openSync as openSync2,
|
|
25130
25605
|
readFileSync as readFileSync8,
|
|
@@ -25136,7 +25611,7 @@ import {
|
|
|
25136
25611
|
writeFileSync as writeFileSync2
|
|
25137
25612
|
} from "node:fs";
|
|
25138
25613
|
import { homedir as homedir3 } from "node:os";
|
|
25139
|
-
import { basename as basename3, dirname as dirname6, extname as extname2, join as
|
|
25614
|
+
import { basename as basename3, dirname as dirname6, extname as extname2, join as join22, relative as relative8 } from "node:path";
|
|
25140
25615
|
function parseCli() {
|
|
25141
25616
|
const rest = [];
|
|
25142
25617
|
for (let i = 2;i < process.argv.length; i++) {
|
|
@@ -25258,7 +25733,7 @@ Examples:
|
|
|
25258
25733
|
}
|
|
25259
25734
|
function warnIfLegacyConfigPresent() {
|
|
25260
25735
|
try {
|
|
25261
|
-
if (existsSync8(
|
|
25736
|
+
if (existsSync8(join22(cwd, ".code-viewer.json"))) {
|
|
25262
25737
|
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
25738
|
}
|
|
25264
25739
|
} catch {}
|
|
@@ -25338,6 +25813,7 @@ function staticFile(pathname) {
|
|
|
25338
25813
|
"/app.js": ["app.js", "application/javascript; charset=utf-8"],
|
|
25339
25814
|
"/mermaid.js": ["mermaid.js", "application/javascript; charset=utf-8"],
|
|
25340
25815
|
"/shiki.js": ["shiki.js", "application/javascript; charset=utf-8"],
|
|
25816
|
+
"/yaml.js": ["yaml.js", "application/javascript; charset=utf-8"],
|
|
25341
25817
|
"/vendor/diff2html/diff2html.min.css": [
|
|
25342
25818
|
"vendor/diff2html/diff2html.min.css",
|
|
25343
25819
|
"text/css; charset=utf-8"
|
|
@@ -25365,7 +25841,7 @@ function staticFile(pathname) {
|
|
|
25365
25841
|
const spec = map[pathname];
|
|
25366
25842
|
if (!spec)
|
|
25367
25843
|
return null;
|
|
25368
|
-
const full =
|
|
25844
|
+
const full = join22(WEB_ROOT, spec[0]);
|
|
25369
25845
|
if (!existsSync8(full))
|
|
25370
25846
|
return text("not found", 404);
|
|
25371
25847
|
return new Response(readFileSync8(full), {
|
|
@@ -25623,7 +26099,7 @@ function safeWorktreePath2(path) {
|
|
|
25623
26099
|
return safeWorktreePath(currentSearchEnv(), path);
|
|
25624
26100
|
}
|
|
25625
26101
|
function worktreePath(path) {
|
|
25626
|
-
return
|
|
26102
|
+
return join22(cwd, path);
|
|
25627
26103
|
}
|
|
25628
26104
|
function safeOpenWorktreePath(path) {
|
|
25629
26105
|
if (path === "") {
|
|
@@ -25814,7 +26290,8 @@ async function handleSettings() {
|
|
|
25814
26290
|
watch_limit_effective: scopeWatchLimit,
|
|
25815
26291
|
watch_limit_default: DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
25816
26292
|
watch_limit_min: MIN_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
25817
|
-
watch_limit_max: MAX_WORKTREE_WATCH_DIRECTORY_LIMIT
|
|
26293
|
+
watch_limit_max: MAX_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
26294
|
+
watch_recursive: supportsNativeRecursiveWatch(process.platform)
|
|
25818
26295
|
}
|
|
25819
26296
|
});
|
|
25820
26297
|
}
|
|
@@ -25957,7 +26434,7 @@ async function handleLog(url) {
|
|
|
25957
26434
|
}
|
|
25958
26435
|
function blamePathKey(p) {
|
|
25959
26436
|
try {
|
|
25960
|
-
const st = statSync7(
|
|
26437
|
+
const st = statSync7(join22(cwd, p));
|
|
25961
26438
|
return `${st.mtimeMs}:${st.size}`;
|
|
25962
26439
|
} catch {
|
|
25963
26440
|
return "missing";
|
|
@@ -26496,7 +26973,7 @@ async function handleUploadFiles(req) {
|
|
|
26496
26973
|
total += file.size;
|
|
26497
26974
|
if (total > MAX_UPLOAD_TOTAL_BYTES)
|
|
26498
26975
|
return text("upload too large", 413);
|
|
26499
|
-
const target =
|
|
26976
|
+
const target = join22(realDir, safeName);
|
|
26500
26977
|
if (relative8(realDir, dirname6(target)) !== "")
|
|
26501
26978
|
return text("invalid filename", 400);
|
|
26502
26979
|
if (existsSync8(target))
|
|
@@ -26618,9 +27095,9 @@ function triggerUpdate(changedPaths) {
|
|
|
26618
27095
|
sendSse("update", data);
|
|
26619
27096
|
}
|
|
26620
27097
|
function moveMacPathIntoTrash(path) {
|
|
26621
|
-
const trashDir =
|
|
27098
|
+
const trashDir = join22(homedir3(), ".Trash");
|
|
26622
27099
|
const base = basename3(path) || "code-viewer-trash-item";
|
|
26623
|
-
const target =
|
|
27100
|
+
const target = join22(trashDir, `${base}-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
26624
27101
|
try {
|
|
26625
27102
|
mkdirSync4(trashDir, { recursive: true });
|
|
26626
27103
|
renameSync(path, target);
|
|
@@ -26630,7 +27107,7 @@ function moveMacPathIntoTrash(path) {
|
|
|
26630
27107
|
}
|
|
26631
27108
|
}
|
|
26632
27109
|
async function movePathToTrash(path) {
|
|
26633
|
-
|
|
27110
|
+
lstatSync5(path);
|
|
26634
27111
|
if (process.platform === "darwin") {
|
|
26635
27112
|
return moveMacPathIntoTrash(path);
|
|
26636
27113
|
}
|
|
@@ -26662,7 +27139,7 @@ async function restoreTrashPath(originalPath, trashPath) {
|
|
|
26662
27139
|
if (!existsSync8(trashPath))
|
|
26663
27140
|
return { ok: false, error: "trash item not found" };
|
|
26664
27141
|
try {
|
|
26665
|
-
const trashRoot =
|
|
27142
|
+
const trashRoot = join22(homedir3(), ".Trash");
|
|
26666
27143
|
const trashRelative = relative8(trashRoot, trashPath);
|
|
26667
27144
|
if (trashRelative === "" || trashRelative.startsWith("..") || trashRelative.startsWith("/") || trashRelative.startsWith("\\"))
|
|
26668
27145
|
return { ok: false, error: "invalid trash handle" };
|
|
@@ -26818,7 +27295,7 @@ async function handleCreateDirectory(req) {
|
|
|
26818
27295
|
const targetPath = dir ? `${dir}/${name}` : name;
|
|
26819
27296
|
if (!safeRepoPath(targetPath) || isGitInternalPath(targetPath))
|
|
26820
27297
|
return text("invalid target", 400);
|
|
26821
|
-
const target =
|
|
27298
|
+
const target = join22(parent, name);
|
|
26822
27299
|
if (existsSync8(target))
|
|
26823
27300
|
return text("already exists", 409);
|
|
26824
27301
|
try {
|
|
@@ -27367,18 +27844,19 @@ async function shutdown(exitCode = 0) {
|
|
|
27367
27844
|
}
|
|
27368
27845
|
function startScopedWorktreeWatch() {
|
|
27369
27846
|
watchLimitReached = null;
|
|
27370
|
-
return
|
|
27847
|
+
return startWatchSupervisor({
|
|
27371
27848
|
root: cwd,
|
|
27372
27849
|
omitDirNames: scopeOmitDirNames,
|
|
27373
27850
|
excludeNames: scopeExcludeNames,
|
|
27374
|
-
watch,
|
|
27375
|
-
initialScanMode: "async",
|
|
27376
27851
|
maxWatchedDirectories: scopeWatchLimit,
|
|
27377
27852
|
onUpdate: triggerUpdate,
|
|
27378
27853
|
onWatchLimit: (limit) => {
|
|
27379
27854
|
watchLimitReached = limit;
|
|
27380
27855
|
sendSse("watch-limit", String(limit));
|
|
27381
27856
|
},
|
|
27857
|
+
onPollOnly: () => {
|
|
27858
|
+
console.warn("[code-viewer] file watching is unavailable; updates now come from periodic polling");
|
|
27859
|
+
},
|
|
27382
27860
|
onError: (error) => {
|
|
27383
27861
|
const message = error instanceof Error ? error.message : String(error);
|
|
27384
27862
|
console.warn(`code-viewer worktree watch skipped: ${message}`);
|
|
@@ -27421,9 +27899,10 @@ var init_preview = __esm(async () => {
|
|
|
27421
27899
|
init_search_service();
|
|
27422
27900
|
init_server_registry();
|
|
27423
27901
|
init_state_store();
|
|
27902
|
+
init_watch_supervisor();
|
|
27424
27903
|
init_worktree_watcher();
|
|
27425
|
-
WEB_ROOT =
|
|
27426
|
-
VERSION = JSON.parse(readFileSync8(
|
|
27904
|
+
WEB_ROOT = join22(ROOT, "web");
|
|
27905
|
+
VERSION = JSON.parse(readFileSync8(join22(ROOT, "package.json"), "utf8")).version;
|
|
27427
27906
|
DEFAULT_ARGS = ["HEAD"];
|
|
27428
27907
|
WATCHED_ASSET_FILES = ["index.html", "style.css", "app.js"];
|
|
27429
27908
|
LINE_INDEX_MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
@@ -27719,6 +28198,9 @@ if (process.argv[2] === "agent-help") {
|
|
|
27719
28198
|
} else if (process.argv[2] === "skill") {
|
|
27720
28199
|
const { runSkillCli: runSkillCli2 } = await Promise.resolve().then(() => (init_skill_cli(), exports_skill_cli));
|
|
27721
28200
|
runSkillCli2(process.argv.slice(3));
|
|
28201
|
+
} else if (process.argv[2] === "watch-child") {
|
|
28202
|
+
const { runWatchChild: runWatchChild2 } = await Promise.resolve().then(() => (init_watch_child(), exports_watch_child));
|
|
28203
|
+
await runWatchChild2();
|
|
27722
28204
|
} else if (process.argv[2] === "doctor") {
|
|
27723
28205
|
const { runDoctorCli: runDoctorCli2 } = await Promise.resolve().then(() => (init_doctor_cli(), exports_doctor_cli));
|
|
27724
28206
|
await runDoctorCli2(process.argv.slice(3));
|