@youtyan/code-viewer 0.2.7 → 0.2.9
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 +197 -129
- package/dist/code-viewer.js +670 -521
- package/package.json +2 -1
- package/web/app.js +2690 -432
- package/web/index.html +27 -11
- package/web/style.css +830 -96
package/dist/code-viewer.js
CHANGED
|
@@ -3726,286 +3726,10 @@ var init_search = __esm(() => {
|
|
|
3726
3726
|
DEFAULT_EXCLUDE_NAMES = [".DS_Store"];
|
|
3727
3727
|
});
|
|
3728
3728
|
|
|
3729
|
-
// web-src/server/worktree-watcher.ts
|
|
3730
|
-
import {
|
|
3731
|
-
lstatSync as lstatSync3,
|
|
3732
|
-
readdirSync as nodeReaddirSync,
|
|
3733
|
-
watch as nodeWatch
|
|
3734
|
-
} from "node:fs";
|
|
3735
|
-
import { join as join7, relative } from "node:path";
|
|
3736
|
-
function normalizeRelativePath(path) {
|
|
3737
|
-
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
3738
|
-
}
|
|
3739
|
-
function isInsideRoot(root, path) {
|
|
3740
|
-
const rel = relative(root, path).replace(/\\/g, "/");
|
|
3741
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
3742
|
-
}
|
|
3743
|
-
function startWorktreeUpdateWatch(options) {
|
|
3744
|
-
const watch = options.watch || nodeWatch;
|
|
3745
|
-
const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
|
|
3746
|
-
const isDirectory = options.isDirectory || ((path) => {
|
|
3747
|
-
try {
|
|
3748
|
-
return lstatSync3(path).isDirectory();
|
|
3749
|
-
} catch {
|
|
3750
|
-
return false;
|
|
3751
|
-
}
|
|
3752
|
-
});
|
|
3753
|
-
const directorySignature = options.directorySignature || ((path) => {
|
|
3754
|
-
try {
|
|
3755
|
-
const stats = lstatSync3(path);
|
|
3756
|
-
if (!stats.isDirectory())
|
|
3757
|
-
return null;
|
|
3758
|
-
return `${stats.dev}:${stats.ino}`;
|
|
3759
|
-
} catch {
|
|
3760
|
-
return null;
|
|
3761
|
-
}
|
|
3762
|
-
});
|
|
3763
|
-
const setTimer = options.setTimeoutFn || setTimeout;
|
|
3764
|
-
const clearTimer = options.clearTimeoutFn || clearTimeout;
|
|
3765
|
-
const debounceMs = options.debounceMs ?? 250;
|
|
3766
|
-
const maxWatchedDirectories = Math.max(1, Math.floor(options.maxWatchedDirectories ?? DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT));
|
|
3767
|
-
const watchers = new Map;
|
|
3768
|
-
const signatures = new Map;
|
|
3769
|
-
const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
|
|
3770
|
-
const initialScanQueue = [];
|
|
3771
|
-
let initialScanTimer = null;
|
|
3772
|
-
const pendingPathInspections = new Map;
|
|
3773
|
-
let pathInspectionTimer = null;
|
|
3774
|
-
let timer = null;
|
|
3775
|
-
const pendingChangedPaths = new Set;
|
|
3776
|
-
let watchLimitReported = false;
|
|
3777
|
-
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
3778
|
-
const directoryRelativePath = (dir) => normalizeRelativePath(relative(options.root, dir));
|
|
3779
|
-
const ignoredDirectory = (dir) => {
|
|
3780
|
-
const rel = directoryRelativePath(dir);
|
|
3781
|
-
return Boolean(rel && ignored(rel));
|
|
3782
|
-
};
|
|
3783
|
-
const scheduleUpdate = (changedPath) => {
|
|
3784
|
-
if (changedPath)
|
|
3785
|
-
pendingChangedPaths.add(changedPath);
|
|
3786
|
-
if (timer)
|
|
3787
|
-
clearTimer(timer);
|
|
3788
|
-
timer = setTimer(() => {
|
|
3789
|
-
timer = null;
|
|
3790
|
-
const paths = pendingChangedPaths.size ? [...pendingChangedPaths] : undefined;
|
|
3791
|
-
pendingChangedPaths.clear();
|
|
3792
|
-
options.onUpdate(paths);
|
|
3793
|
-
}, debounceMs);
|
|
3794
|
-
};
|
|
3795
|
-
const reportWatchLimit = () => {
|
|
3796
|
-
if (watchLimitReported)
|
|
3797
|
-
return;
|
|
3798
|
-
watchLimitReported = true;
|
|
3799
|
-
options.onError?.(new Error(`worktree watcher cap reached (${maxWatchedDirectories}); subsequent changes may be missed`));
|
|
3800
|
-
};
|
|
3801
|
-
const closeSubtree = (dir) => {
|
|
3802
|
-
for (const [watchedDir, watcher] of [...watchers]) {
|
|
3803
|
-
if (watchedDir !== dir && !watchedDir.startsWith(`${dir}/`))
|
|
3804
|
-
continue;
|
|
3805
|
-
try {
|
|
3806
|
-
watcher.close?.();
|
|
3807
|
-
} catch {}
|
|
3808
|
-
watchers.delete(watchedDir);
|
|
3809
|
-
signatures.delete(watchedDir);
|
|
3810
|
-
}
|
|
3811
|
-
};
|
|
3812
|
-
const closeAll = () => {
|
|
3813
|
-
if (initialScanTimer) {
|
|
3814
|
-
clearTimer(initialScanTimer);
|
|
3815
|
-
initialScanTimer = null;
|
|
3816
|
-
}
|
|
3817
|
-
if (pathInspectionTimer) {
|
|
3818
|
-
clearTimer(pathInspectionTimer);
|
|
3819
|
-
pathInspectionTimer = null;
|
|
3820
|
-
}
|
|
3821
|
-
initialScanQueue.length = 0;
|
|
3822
|
-
pendingPathInspections.clear();
|
|
3823
|
-
for (const watcher of [...watchers.values()]) {
|
|
3824
|
-
try {
|
|
3825
|
-
watcher.close?.();
|
|
3826
|
-
} catch {}
|
|
3827
|
-
}
|
|
3828
|
-
watchers.clear();
|
|
3829
|
-
signatures.clear();
|
|
3830
|
-
};
|
|
3831
|
-
const readChildDirectories = (dir) => {
|
|
3832
|
-
let entries;
|
|
3833
|
-
try {
|
|
3834
|
-
entries = readDirs(dir);
|
|
3835
|
-
} catch (error) {
|
|
3836
|
-
options.onError?.(error);
|
|
3837
|
-
return [];
|
|
3838
|
-
}
|
|
3839
|
-
const children = [];
|
|
3840
|
-
for (const entry of entries) {
|
|
3841
|
-
if (!entry.isDirectory())
|
|
3842
|
-
continue;
|
|
3843
|
-
const child = join7(dir, entry.name);
|
|
3844
|
-
if (ignoredDirectory(child))
|
|
3845
|
-
continue;
|
|
3846
|
-
children.push(child);
|
|
3847
|
-
}
|
|
3848
|
-
return children;
|
|
3849
|
-
};
|
|
3850
|
-
const processInitialScanQueue = () => {
|
|
3851
|
-
initialScanTimer = null;
|
|
3852
|
-
if (watchers.size >= maxWatchedDirectories) {
|
|
3853
|
-
reportWatchLimit();
|
|
3854
|
-
initialScanQueue.length = 0;
|
|
3855
|
-
return;
|
|
3856
|
-
}
|
|
3857
|
-
const next = initialScanQueue.shift();
|
|
3858
|
-
if (next)
|
|
3859
|
-
watchDirectory(next, true);
|
|
3860
|
-
if (watchers.size >= maxWatchedDirectories) {
|
|
3861
|
-
reportWatchLimit();
|
|
3862
|
-
initialScanQueue.length = 0;
|
|
3863
|
-
}
|
|
3864
|
-
if (initialScanQueue.length)
|
|
3865
|
-
initialScanTimer = setTimer(processInitialScanQueue, 50);
|
|
3866
|
-
};
|
|
3867
|
-
const queueInitialChildren = (dir) => {
|
|
3868
|
-
const remaining = maxWatchedDirectories - watchers.size;
|
|
3869
|
-
if (remaining <= 0) {
|
|
3870
|
-
reportWatchLimit();
|
|
3871
|
-
return;
|
|
3872
|
-
}
|
|
3873
|
-
const children = readChildDirectories(dir);
|
|
3874
|
-
if (children.length > remaining)
|
|
3875
|
-
reportWatchLimit();
|
|
3876
|
-
initialScanQueue.push(...children.slice(0, remaining));
|
|
3877
|
-
if (!initialScanTimer)
|
|
3878
|
-
initialScanTimer = setTimer(processInitialScanQueue, 5000);
|
|
3879
|
-
};
|
|
3880
|
-
const processChangedPath = (changed, fullChangedPath) => {
|
|
3881
|
-
const known = watchers.has(fullChangedPath);
|
|
3882
|
-
if (isDirectory(fullChangedPath)) {
|
|
3883
|
-
if (known) {
|
|
3884
|
-
const signature = directorySignature(fullChangedPath);
|
|
3885
|
-
if (signature && signature !== signatures.get(fullChangedPath)) {
|
|
3886
|
-
closeSubtree(fullChangedPath);
|
|
3887
|
-
watchDirectory(fullChangedPath, initialScanAsync);
|
|
3888
|
-
}
|
|
3889
|
-
scheduleUpdate(changed);
|
|
3890
|
-
return;
|
|
3891
|
-
}
|
|
3892
|
-
watchDirectory(fullChangedPath, initialScanAsync);
|
|
3893
|
-
} else if (known) {
|
|
3894
|
-
closeSubtree(fullChangedPath);
|
|
3895
|
-
}
|
|
3896
|
-
scheduleUpdate(changed);
|
|
3897
|
-
};
|
|
3898
|
-
const processPathInspections = () => {
|
|
3899
|
-
pathInspectionTimer = null;
|
|
3900
|
-
const entries = [...pendingPathInspections];
|
|
3901
|
-
pendingPathInspections.clear();
|
|
3902
|
-
for (const [changed, fullChangedPath] of entries) {
|
|
3903
|
-
processChangedPath(changed, fullChangedPath);
|
|
3904
|
-
}
|
|
3905
|
-
};
|
|
3906
|
-
const queuePathInspection = (changed, fullChangedPath) => {
|
|
3907
|
-
pendingPathInspections.set(changed, fullChangedPath);
|
|
3908
|
-
if (!pathInspectionTimer)
|
|
3909
|
-
pathInspectionTimer = setTimer(processPathInspections, 25);
|
|
3910
|
-
};
|
|
3911
|
-
const watchDirectory = (dir, initialScan = false) => {
|
|
3912
|
-
if (watchers.has(dir))
|
|
3913
|
-
return;
|
|
3914
|
-
if (watchers.size >= maxWatchedDirectories) {
|
|
3915
|
-
reportWatchLimit();
|
|
3916
|
-
return;
|
|
3917
|
-
}
|
|
3918
|
-
const rel = directoryRelativePath(dir);
|
|
3919
|
-
if (rel && ignored(rel))
|
|
3920
|
-
return;
|
|
3921
|
-
try {
|
|
3922
|
-
const watcher = watch(dir, { persistent: false }, (_event, filename) => {
|
|
3923
|
-
if (!filename) {
|
|
3924
|
-
scheduleUpdate();
|
|
3925
|
-
return;
|
|
3926
|
-
}
|
|
3927
|
-
const changed = normalizeRelativePath(join7(rel, filename.toString()));
|
|
3928
|
-
if (ignored(changed))
|
|
3929
|
-
return;
|
|
3930
|
-
const fullChangedPath = join7(options.root, changed);
|
|
3931
|
-
if (!isInsideRoot(options.root, fullChangedPath))
|
|
3932
|
-
return;
|
|
3933
|
-
if (initialScanAsync) {
|
|
3934
|
-
queuePathInspection(changed, fullChangedPath);
|
|
3935
|
-
return;
|
|
3936
|
-
}
|
|
3937
|
-
processChangedPath(changed, fullChangedPath);
|
|
3938
|
-
}) || {};
|
|
3939
|
-
watchers.set(dir, watcher);
|
|
3940
|
-
const signature = directorySignature(dir);
|
|
3941
|
-
if (signature)
|
|
3942
|
-
signatures.set(dir, signature);
|
|
3943
|
-
watcher.on?.("error", () => {
|
|
3944
|
-
if (watchers.get(dir) === watcher) {
|
|
3945
|
-
watchers.delete(dir);
|
|
3946
|
-
signatures.delete(dir);
|
|
3947
|
-
}
|
|
3948
|
-
});
|
|
3949
|
-
watcher.on?.("close", () => {
|
|
3950
|
-
if (watchers.get(dir) === watcher) {
|
|
3951
|
-
watchers.delete(dir);
|
|
3952
|
-
signatures.delete(dir);
|
|
3953
|
-
}
|
|
3954
|
-
});
|
|
3955
|
-
} catch (error) {
|
|
3956
|
-
options.onError?.(error);
|
|
3957
|
-
return;
|
|
3958
|
-
}
|
|
3959
|
-
if (initialScanAsync && initialScan) {
|
|
3960
|
-
queueInitialChildren(dir);
|
|
3961
|
-
return;
|
|
3962
|
-
}
|
|
3963
|
-
if (watchers.size >= maxWatchedDirectories) {
|
|
3964
|
-
reportWatchLimit();
|
|
3965
|
-
return;
|
|
3966
|
-
}
|
|
3967
|
-
for (const child of readChildDirectories(dir))
|
|
3968
|
-
watchDirectory(child);
|
|
3969
|
-
};
|
|
3970
|
-
watchDirectory(options.root, true);
|
|
3971
|
-
return { started: watchers.size > 0, close: closeAll };
|
|
3972
|
-
}
|
|
3973
|
-
var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT = 256;
|
|
3974
|
-
var init_worktree_watcher = __esm(() => {
|
|
3975
|
-
init_search();
|
|
3976
|
-
});
|
|
3977
|
-
|
|
3978
|
-
// web-src/core/control-chars.ts
|
|
3979
|
-
function hasControlCharacter(value) {
|
|
3980
|
-
for (const ch of value) {
|
|
3981
|
-
const code = ch.charCodeAt(0);
|
|
3982
|
-
if (code < 32 || code === 127)
|
|
3983
|
-
return true;
|
|
3984
|
-
}
|
|
3985
|
-
return false;
|
|
3986
|
-
}
|
|
3987
|
-
|
|
3988
|
-
// web-src/core/id.ts
|
|
3989
|
-
function bytesToHex(bytes) {
|
|
3990
|
-
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
3991
|
-
}
|
|
3992
|
-
function makeId(prefix) {
|
|
3993
|
-
const cryptoApi = globalThis.crypto;
|
|
3994
|
-
if (typeof cryptoApi?.randomUUID === "function") {
|
|
3995
|
-
return `${prefix}-${cryptoApi.randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
3996
|
-
}
|
|
3997
|
-
if (typeof cryptoApi?.getRandomValues === "function") {
|
|
3998
|
-
const bytes = new Uint8Array(8);
|
|
3999
|
-
cryptoApi.getRandomValues(bytes);
|
|
4000
|
-
return `${prefix}-${bytesToHex(bytes)}`;
|
|
4001
|
-
}
|
|
4002
|
-
return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
4003
|
-
}
|
|
4004
|
-
|
|
4005
3729
|
// web-src/server/state-store.ts
|
|
4006
|
-
import { join as
|
|
3730
|
+
import { join as join7 } from "node:path";
|
|
4007
3731
|
function codeViewerPath(root, fileName) {
|
|
4008
|
-
return
|
|
3732
|
+
return join7(root, CODE_VIEWER_DIR2, fileName);
|
|
4009
3733
|
}
|
|
4010
3734
|
function isRecord(value) {
|
|
4011
3735
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -4065,7 +3789,12 @@ function emptySettings() {
|
|
|
4065
3789
|
return { version: 1 };
|
|
4066
3790
|
}
|
|
4067
3791
|
function emptyViewState() {
|
|
4068
|
-
return {
|
|
3792
|
+
return {
|
|
3793
|
+
version: 1,
|
|
3794
|
+
collapsedDirs: [],
|
|
3795
|
+
lazyExpandedDirs: [],
|
|
3796
|
+
viewedFiles: []
|
|
3797
|
+
};
|
|
4069
3798
|
}
|
|
4070
3799
|
function emptyDbUiState() {
|
|
4071
3800
|
return { version: 1, columnWidths: {} };
|
|
@@ -4138,6 +3867,9 @@ function sanitizeSettings(raw) {
|
|
|
4138
3867
|
});
|
|
4139
3868
|
if (scopeExcludeNames)
|
|
4140
3869
|
out.scopeExcludeNames = scopeExcludeNames;
|
|
3870
|
+
const uploadEnabled = optionalBoolean(raw.uploadEnabled);
|
|
3871
|
+
if (uploadEnabled !== undefined)
|
|
3872
|
+
out.uploadEnabled = uploadEnabled;
|
|
4141
3873
|
if (isRecord(raw.range)) {
|
|
4142
3874
|
const from = optionalString(raw.range.from, MAX_REF_LEN);
|
|
4143
3875
|
const to = optionalString(raw.range.to, MAX_REF_LEN);
|
|
@@ -4171,6 +3903,12 @@ function sanitizeViewState(raw) {
|
|
|
4171
3903
|
keepLast: true,
|
|
4172
3904
|
sort: false
|
|
4173
3905
|
}) ?? [],
|
|
3906
|
+
lazyExpandedDirs: normalizeStringList(raw.lazyExpandedDirs, {
|
|
3907
|
+
maxItems: MAX_VIEW_ITEMS,
|
|
3908
|
+
maxLen: MAX_KEY_LEN,
|
|
3909
|
+
keepLast: true,
|
|
3910
|
+
sort: false
|
|
3911
|
+
}) ?? [],
|
|
4174
3912
|
viewedFiles: normalizeStringList(raw.viewedFiles, {
|
|
4175
3913
|
maxItems: MAX_VIEW_ITEMS,
|
|
4176
3914
|
maxLen: MAX_KEY_LEN,
|
|
@@ -4184,6 +3922,7 @@ function mergeViewState(current, patch) {
|
|
|
4184
3922
|
return current;
|
|
4185
3923
|
const base = sanitizeViewState({ ...current, version: 1 });
|
|
4186
3924
|
const collapsedDirs = new Set(base.collapsedDirs);
|
|
3925
|
+
const lazyExpandedDirs = new Set(base.lazyExpandedDirs);
|
|
4187
3926
|
const viewedFiles = new Set(base.viewedFiles);
|
|
4188
3927
|
const addedCollapsedDirs = normalizeStringList(patch.addedCollapsedDirs, {
|
|
4189
3928
|
maxItems: MAX_VIEW_ITEMS,
|
|
@@ -4191,8 +3930,10 @@ function mergeViewState(current, patch) {
|
|
|
4191
3930
|
keepLast: true,
|
|
4192
3931
|
sort: false
|
|
4193
3932
|
});
|
|
4194
|
-
for (const path of addedCollapsedDirs || [])
|
|
3933
|
+
for (const path of addedCollapsedDirs || []) {
|
|
4195
3934
|
collapsedDirs.add(path);
|
|
3935
|
+
lazyExpandedDirs.delete(path);
|
|
3936
|
+
}
|
|
4196
3937
|
const removedCollapsedDirs = normalizeStringList(patch.removedCollapsedDirs, {
|
|
4197
3938
|
maxItems: MAX_VIEW_ITEMS,
|
|
4198
3939
|
maxLen: MAX_KEY_LEN,
|
|
@@ -4200,167 +3941,506 @@ function mergeViewState(current, patch) {
|
|
|
4200
3941
|
});
|
|
4201
3942
|
for (const path of removedCollapsedDirs || [])
|
|
4202
3943
|
collapsedDirs.delete(path);
|
|
3944
|
+
const addedLazyExpandedDirs = normalizeStringList(patch.addedLazyExpandedDirs, {
|
|
3945
|
+
maxItems: MAX_VIEW_ITEMS,
|
|
3946
|
+
maxLen: MAX_KEY_LEN,
|
|
3947
|
+
keepLast: true,
|
|
3948
|
+
sort: false
|
|
3949
|
+
});
|
|
3950
|
+
for (const path of addedLazyExpandedDirs || []) {
|
|
3951
|
+
if (!collapsedDirs.has(path))
|
|
3952
|
+
lazyExpandedDirs.add(path);
|
|
3953
|
+
}
|
|
3954
|
+
const removedLazyExpandedDirs = normalizeStringList(patch.removedLazyExpandedDirs, {
|
|
3955
|
+
maxItems: MAX_VIEW_ITEMS,
|
|
3956
|
+
maxLen: MAX_KEY_LEN,
|
|
3957
|
+
sort: false
|
|
3958
|
+
});
|
|
3959
|
+
for (const path of removedLazyExpandedDirs || [])
|
|
3960
|
+
lazyExpandedDirs.delete(path);
|
|
4203
3961
|
const addedViewedFiles = normalizeStringList(patch.addedViewedFiles, {
|
|
4204
3962
|
maxItems: MAX_VIEW_ITEMS,
|
|
4205
3963
|
maxLen: MAX_KEY_LEN,
|
|
4206
3964
|
keepLast: true,
|
|
4207
3965
|
sort: false
|
|
4208
3966
|
});
|
|
4209
|
-
for (const path of addedViewedFiles || [])
|
|
4210
|
-
viewedFiles.add(path);
|
|
4211
|
-
const removedViewedFiles = normalizeStringList(patch.removedViewedFiles, {
|
|
4212
|
-
maxItems: MAX_VIEW_ITEMS,
|
|
4213
|
-
maxLen: MAX_KEY_LEN,
|
|
4214
|
-
sort: false
|
|
3967
|
+
for (const path of addedViewedFiles || [])
|
|
3968
|
+
viewedFiles.add(path);
|
|
3969
|
+
const removedViewedFiles = normalizeStringList(patch.removedViewedFiles, {
|
|
3970
|
+
maxItems: MAX_VIEW_ITEMS,
|
|
3971
|
+
maxLen: MAX_KEY_LEN,
|
|
3972
|
+
sort: false
|
|
3973
|
+
});
|
|
3974
|
+
for (const path of removedViewedFiles || [])
|
|
3975
|
+
viewedFiles.delete(path);
|
|
3976
|
+
return sanitizeViewState({
|
|
3977
|
+
version: 1,
|
|
3978
|
+
collapsedDirs: [...collapsedDirs],
|
|
3979
|
+
lazyExpandedDirs: [...lazyExpandedDirs],
|
|
3980
|
+
viewedFiles: [...viewedFiles]
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
function safeObjectKey(value) {
|
|
3984
|
+
if (!value || value.length > MAX_KEY_LEN || value.includes("\x00"))
|
|
3985
|
+
return null;
|
|
3986
|
+
return value;
|
|
3987
|
+
}
|
|
3988
|
+
function sanitizeDbUiPrefs(raw) {
|
|
3989
|
+
if (!isRecord(raw))
|
|
3990
|
+
return;
|
|
3991
|
+
const out = {};
|
|
3992
|
+
for (const key of DB_UI_BOOL_PREF_KEYS) {
|
|
3993
|
+
const v = raw[key];
|
|
3994
|
+
if (v === true || v === false)
|
|
3995
|
+
out[key] = v;
|
|
3996
|
+
}
|
|
3997
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
3998
|
+
}
|
|
3999
|
+
function sanitizeDbUiState(raw) {
|
|
4000
|
+
if (!isRecord(raw))
|
|
4001
|
+
return emptyDbUiState();
|
|
4002
|
+
const prefs = sanitizeDbUiPrefs(raw.prefs);
|
|
4003
|
+
if (!isRecord(raw.columnWidths)) {
|
|
4004
|
+
return prefs ? { ...emptyDbUiState(), prefs } : emptyDbUiState();
|
|
4005
|
+
}
|
|
4006
|
+
const columnWidths = {};
|
|
4007
|
+
let dbCount = 0;
|
|
4008
|
+
for (const [dbIdRaw, tablesRaw] of Object.entries(raw.columnWidths)) {
|
|
4009
|
+
if (dbCount >= MAX_DB_UI_DBS)
|
|
4010
|
+
break;
|
|
4011
|
+
const dbId = safeObjectKey(dbIdRaw);
|
|
4012
|
+
if (!dbId || !isRecord(tablesRaw))
|
|
4013
|
+
continue;
|
|
4014
|
+
const tables = {};
|
|
4015
|
+
let tableCount = 0;
|
|
4016
|
+
for (const [tableRaw, columnsRaw] of Object.entries(tablesRaw)) {
|
|
4017
|
+
if (tableCount >= MAX_DB_UI_TABLES)
|
|
4018
|
+
break;
|
|
4019
|
+
const table = safeObjectKey(tableRaw);
|
|
4020
|
+
if (!table || !isRecord(columnsRaw))
|
|
4021
|
+
continue;
|
|
4022
|
+
const columns = {};
|
|
4023
|
+
let columnCount = 0;
|
|
4024
|
+
for (const [columnRaw, widthRaw] of Object.entries(columnsRaw)) {
|
|
4025
|
+
if (columnCount >= MAX_DB_UI_COLUMNS)
|
|
4026
|
+
break;
|
|
4027
|
+
const column = safeObjectKey(columnRaw);
|
|
4028
|
+
const width = optionalNumber(widthRaw, 60, 1200);
|
|
4029
|
+
if (!column || width === undefined)
|
|
4030
|
+
continue;
|
|
4031
|
+
columns[column] = width;
|
|
4032
|
+
columnCount++;
|
|
4033
|
+
}
|
|
4034
|
+
if (Object.keys(columns).length === 0)
|
|
4035
|
+
continue;
|
|
4036
|
+
tables[table] = columns;
|
|
4037
|
+
tableCount++;
|
|
4038
|
+
}
|
|
4039
|
+
if (Object.keys(tables).length === 0)
|
|
4040
|
+
continue;
|
|
4041
|
+
columnWidths[dbId] = tables;
|
|
4042
|
+
dbCount++;
|
|
4043
|
+
}
|
|
4044
|
+
const out = { version: 1, columnWidths };
|
|
4045
|
+
if (prefs)
|
|
4046
|
+
out.prefs = prefs;
|
|
4047
|
+
return out;
|
|
4048
|
+
}
|
|
4049
|
+
function mergeDbUiPrefs(current, patch) {
|
|
4050
|
+
if (!isRecord(patch))
|
|
4051
|
+
return current;
|
|
4052
|
+
const next = { ...current ?? {} };
|
|
4053
|
+
for (const key of DB_UI_BOOL_PREF_KEYS) {
|
|
4054
|
+
const v = patch[key];
|
|
4055
|
+
if (v === null)
|
|
4056
|
+
delete next[key];
|
|
4057
|
+
else if (v === true || v === false)
|
|
4058
|
+
next[key] = v;
|
|
4059
|
+
}
|
|
4060
|
+
return Object.keys(next).length > 0 ? next : undefined;
|
|
4061
|
+
}
|
|
4062
|
+
function mergeDbUiState(current, patch) {
|
|
4063
|
+
if (!isRecord(patch))
|
|
4064
|
+
return current;
|
|
4065
|
+
const mergedPrefs = "prefs" in patch ? mergeDbUiPrefs(current.prefs, patch.prefs) : current.prefs;
|
|
4066
|
+
if (!isRecord(patch.columnWidths)) {
|
|
4067
|
+
const merged = { ...current, version: 1 };
|
|
4068
|
+
if (mergedPrefs)
|
|
4069
|
+
merged.prefs = mergedPrefs;
|
|
4070
|
+
else
|
|
4071
|
+
delete merged.prefs;
|
|
4072
|
+
return sanitizeDbUiState(merged);
|
|
4073
|
+
}
|
|
4074
|
+
const columnWidths = {
|
|
4075
|
+
...current.columnWidths
|
|
4076
|
+
};
|
|
4077
|
+
for (const [dbId, tablesRaw] of Object.entries(patch.columnWidths)) {
|
|
4078
|
+
if (tablesRaw === null) {
|
|
4079
|
+
delete columnWidths[dbId];
|
|
4080
|
+
continue;
|
|
4081
|
+
}
|
|
4082
|
+
if (!isRecord(tablesRaw))
|
|
4083
|
+
continue;
|
|
4084
|
+
const tables = { ...columnWidths[dbId] || {} };
|
|
4085
|
+
for (const [table, columnsRaw] of Object.entries(tablesRaw)) {
|
|
4086
|
+
if (columnsRaw === null) {
|
|
4087
|
+
delete tables[table];
|
|
4088
|
+
continue;
|
|
4089
|
+
}
|
|
4090
|
+
if (!isRecord(columnsRaw))
|
|
4091
|
+
continue;
|
|
4092
|
+
const columns = { ...tables[table] || {} };
|
|
4093
|
+
for (const [column, widthRaw] of Object.entries(columnsRaw)) {
|
|
4094
|
+
if (widthRaw === null)
|
|
4095
|
+
delete columns[column];
|
|
4096
|
+
else
|
|
4097
|
+
columns[column] = widthRaw;
|
|
4098
|
+
}
|
|
4099
|
+
tables[table] = columns;
|
|
4100
|
+
}
|
|
4101
|
+
columnWidths[dbId] = tables;
|
|
4102
|
+
}
|
|
4103
|
+
return sanitizeDbUiState({
|
|
4104
|
+
...current,
|
|
4105
|
+
...patch,
|
|
4106
|
+
columnWidths,
|
|
4107
|
+
prefs: mergedPrefs,
|
|
4108
|
+
version: 1
|
|
4109
|
+
});
|
|
4110
|
+
}
|
|
4111
|
+
async function loadAppSettingsState(root) {
|
|
4112
|
+
return settingsStore.load(root);
|
|
4113
|
+
}
|
|
4114
|
+
async function patchAppSettingsState(root, patch) {
|
|
4115
|
+
return settingsStore.update(root, (state) => {
|
|
4116
|
+
const next = mergeSettings(state, patch);
|
|
4117
|
+
return { state: next, result: next };
|
|
4118
|
+
});
|
|
4119
|
+
}
|
|
4120
|
+
async function loadViewState(root) {
|
|
4121
|
+
return viewStateStore.load(root);
|
|
4122
|
+
}
|
|
4123
|
+
async function patchViewState(root, patch) {
|
|
4124
|
+
return viewStateStore.update(root, (state) => {
|
|
4125
|
+
const next = mergeViewState(state, patch);
|
|
4126
|
+
return { state: next, result: next };
|
|
4127
|
+
});
|
|
4128
|
+
}
|
|
4129
|
+
async function loadDbUiState(root) {
|
|
4130
|
+
return dbUiStore.load(root);
|
|
4131
|
+
}
|
|
4132
|
+
async function patchDbUiState(root, patch) {
|
|
4133
|
+
return dbUiStore.update(root, (state) => {
|
|
4134
|
+
const next = mergeDbUiState(state, patch);
|
|
4135
|
+
return { state: next, result: next };
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
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", MAX_SETTINGS_BYTES = 200000, MAX_VIEW_STATE_BYTES = 1e6, MAX_DB_UI_BYTES = 1e6, 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, DB_UI_BOOL_PREF_KEYS, settingsStore, viewStateStore, dbUiStore;
|
|
4139
|
+
var init_state_store = __esm(() => {
|
|
4140
|
+
init_json_store();
|
|
4141
|
+
DB_UI_BOOL_PREF_KEYS = ["s3TooltipEnabled", "inferFkRails"];
|
|
4142
|
+
settingsStore = createJsonFileStore({
|
|
4143
|
+
filePath: (root) => codeViewerPath(root, SETTINGS_FILE_NAME),
|
|
4144
|
+
empty: emptySettings,
|
|
4145
|
+
sanitize: sanitizeSettings,
|
|
4146
|
+
maxBytes: MAX_SETTINGS_BYTES,
|
|
4147
|
+
backupSuffix: "corrupt",
|
|
4148
|
+
sizeErrorMessage: "settings state too large"
|
|
4215
4149
|
});
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4150
|
+
viewStateStore = createJsonFileStore({
|
|
4151
|
+
filePath: (root) => codeViewerPath(root, VIEW_STATE_FILE_NAME),
|
|
4152
|
+
empty: emptyViewState,
|
|
4153
|
+
sanitize: sanitizeViewState,
|
|
4154
|
+
maxBytes: MAX_VIEW_STATE_BYTES,
|
|
4155
|
+
backupSuffix: "corrupt",
|
|
4156
|
+
sizeErrorMessage: "view state too large"
|
|
4157
|
+
});
|
|
4158
|
+
dbUiStore = createJsonFileStore({
|
|
4159
|
+
filePath: (root) => codeViewerPath(root, DB_UI_FILE_NAME),
|
|
4160
|
+
empty: emptyDbUiState,
|
|
4161
|
+
sanitize: sanitizeDbUiState,
|
|
4162
|
+
maxBytes: MAX_DB_UI_BYTES,
|
|
4163
|
+
backupSuffix: "corrupt",
|
|
4164
|
+
sizeErrorMessage: "db UI state too large"
|
|
4222
4165
|
});
|
|
4166
|
+
});
|
|
4167
|
+
|
|
4168
|
+
// web-src/server/worktree-watcher.ts
|
|
4169
|
+
import {
|
|
4170
|
+
lstatSync as lstatSync3,
|
|
4171
|
+
readdirSync as nodeReaddirSync,
|
|
4172
|
+
watch as nodeWatch
|
|
4173
|
+
} from "node:fs";
|
|
4174
|
+
import { join as join8, relative } from "node:path";
|
|
4175
|
+
function normalizeRelativePath(path) {
|
|
4176
|
+
return path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
4223
4177
|
}
|
|
4224
|
-
function
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
return value;
|
|
4178
|
+
function isInsideRoot(root, path) {
|
|
4179
|
+
const rel = relative(root, path).replace(/\\/g, "/");
|
|
4180
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
4228
4181
|
}
|
|
4229
|
-
function
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
const
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4182
|
+
function startWorktreeUpdateWatch(options) {
|
|
4183
|
+
const watch = options.watch || nodeWatch;
|
|
4184
|
+
const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
|
|
4185
|
+
const isDirectory = options.isDirectory || ((path) => {
|
|
4186
|
+
try {
|
|
4187
|
+
return lstatSync3(path).isDirectory();
|
|
4188
|
+
} catch {
|
|
4189
|
+
return false;
|
|
4190
|
+
}
|
|
4191
|
+
});
|
|
4192
|
+
const directorySignature = options.directorySignature || ((path) => {
|
|
4193
|
+
try {
|
|
4194
|
+
const stats = lstatSync3(path);
|
|
4195
|
+
if (!stats.isDirectory())
|
|
4196
|
+
return null;
|
|
4197
|
+
return `${stats.dev}:${stats.ino}`;
|
|
4198
|
+
} catch {
|
|
4199
|
+
return null;
|
|
4200
|
+
}
|
|
4201
|
+
});
|
|
4202
|
+
const setTimer = options.setTimeoutFn || setTimeout;
|
|
4203
|
+
const clearTimer = options.clearTimeoutFn || clearTimeout;
|
|
4204
|
+
const debounceMs = options.debounceMs ?? 250;
|
|
4205
|
+
const maxWatchedDirectories = Math.max(1, Math.floor(options.maxWatchedDirectories ?? DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT));
|
|
4206
|
+
const watchers = new Map;
|
|
4207
|
+
const signatures = new Map;
|
|
4208
|
+
const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
|
|
4209
|
+
const initialScanQueue = [];
|
|
4210
|
+
let initialScanTimer = null;
|
|
4211
|
+
const pendingPathInspections = new Map;
|
|
4212
|
+
let pathInspectionTimer = null;
|
|
4213
|
+
let timer = null;
|
|
4214
|
+
const pendingChangedPaths = new Set;
|
|
4215
|
+
let watchLimitReported = false;
|
|
4216
|
+
const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
|
|
4217
|
+
const directoryRelativePath = (dir) => normalizeRelativePath(relative(options.root, dir));
|
|
4218
|
+
const ignoredDirectory = (dir) => {
|
|
4219
|
+
const rel = directoryRelativePath(dir);
|
|
4220
|
+
return Boolean(rel && ignored(rel));
|
|
4221
|
+
};
|
|
4222
|
+
const scheduleUpdate = (changedPath) => {
|
|
4223
|
+
if (changedPath)
|
|
4224
|
+
pendingChangedPaths.add(changedPath);
|
|
4225
|
+
if (timer)
|
|
4226
|
+
clearTimer(timer);
|
|
4227
|
+
timer = setTimer(() => {
|
|
4228
|
+
timer = null;
|
|
4229
|
+
const paths = pendingChangedPaths.size ? [...pendingChangedPaths] : undefined;
|
|
4230
|
+
pendingChangedPaths.clear();
|
|
4231
|
+
options.onUpdate(paths);
|
|
4232
|
+
}, debounceMs);
|
|
4233
|
+
};
|
|
4234
|
+
const reportWatchLimit = () => {
|
|
4235
|
+
if (watchLimitReported)
|
|
4236
|
+
return;
|
|
4237
|
+
watchLimitReported = true;
|
|
4238
|
+
options.onWatchLimit?.(maxWatchedDirectories);
|
|
4239
|
+
options.onError?.(new Error(`worktree watcher cap reached (${maxWatchedDirectories}); subsequent changes may be missed`));
|
|
4240
|
+
};
|
|
4241
|
+
const closeSubtree = (dir) => {
|
|
4242
|
+
for (const [watchedDir, watcher] of [...watchers]) {
|
|
4243
|
+
if (watchedDir !== dir && !watchedDir.startsWith(`${dir}/`))
|
|
4247
4244
|
continue;
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4245
|
+
try {
|
|
4246
|
+
watcher.close?.();
|
|
4247
|
+
} catch {}
|
|
4248
|
+
watchers.delete(watchedDir);
|
|
4249
|
+
signatures.delete(watchedDir);
|
|
4250
|
+
}
|
|
4251
|
+
};
|
|
4252
|
+
const closeAll = () => {
|
|
4253
|
+
if (initialScanTimer) {
|
|
4254
|
+
clearTimer(initialScanTimer);
|
|
4255
|
+
initialScanTimer = null;
|
|
4256
|
+
}
|
|
4257
|
+
if (pathInspectionTimer) {
|
|
4258
|
+
clearTimer(pathInspectionTimer);
|
|
4259
|
+
pathInspectionTimer = null;
|
|
4260
|
+
}
|
|
4261
|
+
initialScanQueue.length = 0;
|
|
4262
|
+
pendingPathInspections.clear();
|
|
4263
|
+
for (const watcher of [...watchers.values()]) {
|
|
4264
|
+
try {
|
|
4265
|
+
watcher.close?.();
|
|
4266
|
+
} catch {}
|
|
4267
|
+
}
|
|
4268
|
+
watchers.clear();
|
|
4269
|
+
signatures.clear();
|
|
4270
|
+
};
|
|
4271
|
+
const readChildDirectories = (dir) => {
|
|
4272
|
+
let entries;
|
|
4273
|
+
try {
|
|
4274
|
+
entries = readDirs(dir);
|
|
4275
|
+
} catch (error) {
|
|
4276
|
+
options.onError?.(error);
|
|
4277
|
+
return [];
|
|
4278
|
+
}
|
|
4279
|
+
const children = [];
|
|
4280
|
+
for (const entry of entries) {
|
|
4281
|
+
if (!entry.isDirectory())
|
|
4261
4282
|
continue;
|
|
4262
|
-
|
|
4263
|
-
|
|
4283
|
+
const child = join8(dir, entry.name);
|
|
4284
|
+
if (ignoredDirectory(child))
|
|
4285
|
+
continue;
|
|
4286
|
+
children.push(child);
|
|
4287
|
+
}
|
|
4288
|
+
return children;
|
|
4289
|
+
};
|
|
4290
|
+
const processInitialScanQueue = () => {
|
|
4291
|
+
initialScanTimer = null;
|
|
4292
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
4293
|
+
reportWatchLimit();
|
|
4294
|
+
initialScanQueue.length = 0;
|
|
4295
|
+
return;
|
|
4296
|
+
}
|
|
4297
|
+
const next = initialScanQueue.shift();
|
|
4298
|
+
if (next)
|
|
4299
|
+
watchDirectory(next, true);
|
|
4300
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
4301
|
+
reportWatchLimit();
|
|
4302
|
+
initialScanQueue.length = 0;
|
|
4303
|
+
}
|
|
4304
|
+
if (initialScanQueue.length)
|
|
4305
|
+
initialScanTimer = setTimer(processInitialScanQueue, 50);
|
|
4306
|
+
};
|
|
4307
|
+
const queueInitialChildren = (dir) => {
|
|
4308
|
+
const remaining = maxWatchedDirectories - watchers.size;
|
|
4309
|
+
if (remaining <= 0) {
|
|
4310
|
+
reportWatchLimit();
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
const children = readChildDirectories(dir);
|
|
4314
|
+
if (children.length > remaining)
|
|
4315
|
+
reportWatchLimit();
|
|
4316
|
+
initialScanQueue.push(...children.slice(0, remaining));
|
|
4317
|
+
if (!initialScanTimer)
|
|
4318
|
+
initialScanTimer = setTimer(processInitialScanQueue, 5000);
|
|
4319
|
+
};
|
|
4320
|
+
const processChangedPath = (changed, fullChangedPath) => {
|
|
4321
|
+
const known = watchers.has(fullChangedPath);
|
|
4322
|
+
if (isDirectory(fullChangedPath)) {
|
|
4323
|
+
if (known) {
|
|
4324
|
+
const signature = directorySignature(fullChangedPath);
|
|
4325
|
+
if (signature && signature !== signatures.get(fullChangedPath)) {
|
|
4326
|
+
closeSubtree(fullChangedPath);
|
|
4327
|
+
watchDirectory(fullChangedPath, initialScanAsync);
|
|
4328
|
+
}
|
|
4329
|
+
scheduleUpdate(changed);
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
watchDirectory(fullChangedPath, initialScanAsync);
|
|
4333
|
+
} else if (known) {
|
|
4334
|
+
closeSubtree(fullChangedPath);
|
|
4335
|
+
}
|
|
4336
|
+
scheduleUpdate(changed);
|
|
4337
|
+
};
|
|
4338
|
+
const processPathInspections = () => {
|
|
4339
|
+
pathInspectionTimer = null;
|
|
4340
|
+
const entries = [...pendingPathInspections];
|
|
4341
|
+
pendingPathInspections.clear();
|
|
4342
|
+
for (const [changed, fullChangedPath] of entries) {
|
|
4343
|
+
processChangedPath(changed, fullChangedPath);
|
|
4264
4344
|
}
|
|
4265
|
-
if (Object.keys(tables).length === 0)
|
|
4266
|
-
continue;
|
|
4267
|
-
columnWidths[dbId] = tables;
|
|
4268
|
-
dbCount++;
|
|
4269
|
-
}
|
|
4270
|
-
return { version: 1, columnWidths };
|
|
4271
|
-
}
|
|
4272
|
-
function mergeDbUiState(current, patch) {
|
|
4273
|
-
if (!isRecord(patch))
|
|
4274
|
-
return current;
|
|
4275
|
-
if (!isRecord(patch.columnWidths)) {
|
|
4276
|
-
return sanitizeDbUiState({ ...current, ...patch, version: 1 });
|
|
4277
|
-
}
|
|
4278
|
-
const columnWidths = {
|
|
4279
|
-
...current.columnWidths
|
|
4280
4345
|
};
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4346
|
+
const queuePathInspection = (changed, fullChangedPath) => {
|
|
4347
|
+
pendingPathInspections.set(changed, fullChangedPath);
|
|
4348
|
+
if (!pathInspectionTimer)
|
|
4349
|
+
pathInspectionTimer = setTimer(processPathInspections, 25);
|
|
4350
|
+
};
|
|
4351
|
+
const watchDirectory = (dir, initialScan = false) => {
|
|
4352
|
+
if (watchers.has(dir))
|
|
4353
|
+
return;
|
|
4354
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
4355
|
+
reportWatchLimit();
|
|
4356
|
+
return;
|
|
4285
4357
|
}
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4358
|
+
const rel = directoryRelativePath(dir);
|
|
4359
|
+
if (rel && ignored(rel))
|
|
4360
|
+
return;
|
|
4361
|
+
try {
|
|
4362
|
+
const watcher = watch(dir, { persistent: false }, (_event, filename) => {
|
|
4363
|
+
if (!filename) {
|
|
4364
|
+
scheduleUpdate();
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
const changed = normalizeRelativePath(join8(rel, filename.toString()));
|
|
4368
|
+
if (ignored(changed))
|
|
4369
|
+
return;
|
|
4370
|
+
const fullChangedPath = join8(options.root, changed);
|
|
4371
|
+
if (!isInsideRoot(options.root, fullChangedPath))
|
|
4372
|
+
return;
|
|
4373
|
+
if (initialScanAsync) {
|
|
4374
|
+
queuePathInspection(changed, fullChangedPath);
|
|
4375
|
+
return;
|
|
4376
|
+
}
|
|
4377
|
+
processChangedPath(changed, fullChangedPath);
|
|
4378
|
+
}) || {};
|
|
4379
|
+
watchers.set(dir, watcher);
|
|
4380
|
+
const signature = directorySignature(dir);
|
|
4381
|
+
if (signature)
|
|
4382
|
+
signatures.set(dir, signature);
|
|
4383
|
+
watcher.on?.("error", () => {
|
|
4384
|
+
if (watchers.get(dir) === watcher) {
|
|
4385
|
+
watchers.delete(dir);
|
|
4386
|
+
signatures.delete(dir);
|
|
4387
|
+
}
|
|
4388
|
+
});
|
|
4389
|
+
watcher.on?.("close", () => {
|
|
4390
|
+
if (watchers.get(dir) === watcher) {
|
|
4391
|
+
watchers.delete(dir);
|
|
4392
|
+
signatures.delete(dir);
|
|
4393
|
+
}
|
|
4394
|
+
});
|
|
4395
|
+
} catch (error) {
|
|
4396
|
+
options.onError?.(error);
|
|
4397
|
+
return;
|
|
4304
4398
|
}
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
}
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
}
|
|
4318
|
-
async function loadViewState(root) {
|
|
4319
|
-
return viewStateStore.load(root);
|
|
4399
|
+
if (initialScanAsync && initialScan) {
|
|
4400
|
+
queueInitialChildren(dir);
|
|
4401
|
+
return;
|
|
4402
|
+
}
|
|
4403
|
+
if (watchers.size >= maxWatchedDirectories) {
|
|
4404
|
+
reportWatchLimit();
|
|
4405
|
+
return;
|
|
4406
|
+
}
|
|
4407
|
+
for (const child of readChildDirectories(dir))
|
|
4408
|
+
watchDirectory(child);
|
|
4409
|
+
};
|
|
4410
|
+
watchDirectory(options.root, true);
|
|
4411
|
+
return { started: watchers.size > 0, close: closeAll };
|
|
4320
4412
|
}
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4413
|
+
var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT = 256;
|
|
4414
|
+
var init_worktree_watcher = __esm(() => {
|
|
4415
|
+
init_search();
|
|
4416
|
+
});
|
|
4417
|
+
|
|
4418
|
+
// web-src/core/control-chars.ts
|
|
4419
|
+
function hasControlCharacter(value) {
|
|
4420
|
+
for (const ch of value) {
|
|
4421
|
+
const code = ch.charCodeAt(0);
|
|
4422
|
+
if (code < 32 || code === 127)
|
|
4423
|
+
return true;
|
|
4424
|
+
}
|
|
4425
|
+
return false;
|
|
4326
4426
|
}
|
|
4327
|
-
|
|
4328
|
-
|
|
4427
|
+
|
|
4428
|
+
// web-src/core/id.ts
|
|
4429
|
+
function bytesToHex(bytes) {
|
|
4430
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4329
4431
|
}
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
return {
|
|
4334
|
-
}
|
|
4432
|
+
function makeId(prefix) {
|
|
4433
|
+
const cryptoApi = globalThis.crypto;
|
|
4434
|
+
if (typeof cryptoApi?.randomUUID === "function") {
|
|
4435
|
+
return `${prefix}-${cryptoApi.randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
4436
|
+
}
|
|
4437
|
+
if (typeof cryptoApi?.getRandomValues === "function") {
|
|
4438
|
+
const bytes = new Uint8Array(8);
|
|
4439
|
+
cryptoApi.getRandomValues(bytes);
|
|
4440
|
+
return `${prefix}-${bytesToHex(bytes)}`;
|
|
4441
|
+
}
|
|
4442
|
+
return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
4335
4443
|
}
|
|
4336
|
-
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", MAX_SETTINGS_BYTES = 200000, MAX_VIEW_STATE_BYTES = 1e6, MAX_DB_UI_BYTES = 1e6, 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, settingsStore, viewStateStore, dbUiStore;
|
|
4337
|
-
var init_state_store = __esm(() => {
|
|
4338
|
-
init_json_store();
|
|
4339
|
-
settingsStore = createJsonFileStore({
|
|
4340
|
-
filePath: (root) => codeViewerPath(root, SETTINGS_FILE_NAME),
|
|
4341
|
-
empty: emptySettings,
|
|
4342
|
-
sanitize: sanitizeSettings,
|
|
4343
|
-
maxBytes: MAX_SETTINGS_BYTES,
|
|
4344
|
-
backupSuffix: "corrupt",
|
|
4345
|
-
sizeErrorMessage: "settings state too large"
|
|
4346
|
-
});
|
|
4347
|
-
viewStateStore = createJsonFileStore({
|
|
4348
|
-
filePath: (root) => codeViewerPath(root, VIEW_STATE_FILE_NAME),
|
|
4349
|
-
empty: emptyViewState,
|
|
4350
|
-
sanitize: sanitizeViewState,
|
|
4351
|
-
maxBytes: MAX_VIEW_STATE_BYTES,
|
|
4352
|
-
backupSuffix: "corrupt",
|
|
4353
|
-
sizeErrorMessage: "view state too large"
|
|
4354
|
-
});
|
|
4355
|
-
dbUiStore = createJsonFileStore({
|
|
4356
|
-
filePath: (root) => codeViewerPath(root, DB_UI_FILE_NAME),
|
|
4357
|
-
empty: emptyDbUiState,
|
|
4358
|
-
sanitize: sanitizeDbUiState,
|
|
4359
|
-
maxBytes: MAX_DB_UI_BYTES,
|
|
4360
|
-
backupSuffix: "corrupt",
|
|
4361
|
-
sizeErrorMessage: "db UI state too large"
|
|
4362
|
-
});
|
|
4363
|
-
});
|
|
4364
4444
|
|
|
4365
4445
|
// web-src/server/database/adapters/abort.ts
|
|
4366
4446
|
function abortError(message = "operation aborted") {
|
|
@@ -4528,25 +4608,23 @@ function sanitizeIdentifier(name, kind = "sqlite") {
|
|
|
4528
4608
|
return `\`${name.replace(/`/g, "``")}\``;
|
|
4529
4609
|
return `"${name.replace(/"/g, '""')}"`;
|
|
4530
4610
|
}
|
|
4531
|
-
function escapeSqlString(value) {
|
|
4532
|
-
|
|
4611
|
+
function escapeSqlString(value, kind) {
|
|
4612
|
+
const escaped = kind === "mysql" ? value.replace(/\\/g, "\\\\").replace(/'/g, "''") : value.replace(/'/g, "''");
|
|
4613
|
+
return `'${escaped}'`;
|
|
4533
4614
|
}
|
|
4534
|
-
function buildFilterWhere(grouped, kind) {
|
|
4615
|
+
function buildFilterWhere(grouped, kind, exact) {
|
|
4535
4616
|
const whereParts = [];
|
|
4536
4617
|
const params = [];
|
|
4537
4618
|
const useParams = kind === "sqlite";
|
|
4619
|
+
const castOf = (column) => kind === "mysql" ? `CAST(${sanitizeIdentifier(column, kind)} AS CHAR)` : `CAST(${sanitizeIdentifier(column, kind)} AS TEXT)`;
|
|
4538
4620
|
for (const [value, cols] of grouped) {
|
|
4539
|
-
const likeVal = useParams ? "?" : escapeSqlString(`%${value}
|
|
4621
|
+
const likeVal = useParams ? "?" : escapeSqlString(`%${value}%`, kind);
|
|
4540
4622
|
if (cols.length === 1) {
|
|
4541
|
-
|
|
4542
|
-
whereParts.push(`${cast} LIKE ${likeVal}`);
|
|
4623
|
+
whereParts.push(`${castOf(cols[0])} LIKE ${likeVal}`);
|
|
4543
4624
|
if (useParams)
|
|
4544
4625
|
params.push(`%${value}%`);
|
|
4545
4626
|
} else {
|
|
4546
|
-
const orParts = cols.map((column) => {
|
|
4547
|
-
const cast = kind === "mysql" ? `CAST(${sanitizeIdentifier(column, kind)} AS CHAR)` : `CAST(${sanitizeIdentifier(column, kind)} AS TEXT)`;
|
|
4548
|
-
return `${cast} LIKE ${likeVal}`;
|
|
4549
|
-
});
|
|
4627
|
+
const orParts = cols.map((column) => `${castOf(column)} LIKE ${likeVal}`);
|
|
4550
4628
|
whereParts.push(`(${orParts.join(" OR ")})`);
|
|
4551
4629
|
if (useParams) {
|
|
4552
4630
|
for (let i = 0;i < cols.length; i++)
|
|
@@ -4554,6 +4632,12 @@ function buildFilterWhere(grouped, kind) {
|
|
|
4554
4632
|
}
|
|
4555
4633
|
}
|
|
4556
4634
|
}
|
|
4635
|
+
for (const cond of exact ?? []) {
|
|
4636
|
+
const rhs = useParams ? "?" : escapeSqlString(cond.value, kind);
|
|
4637
|
+
whereParts.push(`${castOf(cond.column)} = ${rhs}`);
|
|
4638
|
+
if (useParams)
|
|
4639
|
+
params.push(cond.value);
|
|
4640
|
+
}
|
|
4557
4641
|
return { where: whereParts.join(" AND "), params, useParams };
|
|
4558
4642
|
}
|
|
4559
4643
|
function filterGroupedColumns(grouped, columnNames) {
|
|
@@ -4566,6 +4650,12 @@ function filterGroupedColumns(grouped, columnNames) {
|
|
|
4566
4650
|
}
|
|
4567
4651
|
return filtered;
|
|
4568
4652
|
}
|
|
4653
|
+
function filterExactColumns(exact, columnNames) {
|
|
4654
|
+
if (!exact || exact.length === 0)
|
|
4655
|
+
return [];
|
|
4656
|
+
const validColumns = new Set(columnNames);
|
|
4657
|
+
return exact.filter((cond) => validColumns.has(cond.column));
|
|
4658
|
+
}
|
|
4569
4659
|
function filterOrderByColumns(orderBy, columnNames) {
|
|
4570
4660
|
if (!orderBy)
|
|
4571
4661
|
return;
|
|
@@ -4869,6 +4959,8 @@ function buildExecArgs(config, sql) {
|
|
|
4869
4959
|
"-A",
|
|
4870
4960
|
"-F",
|
|
4871
4961
|
"\t",
|
|
4962
|
+
"-R",
|
|
4963
|
+
PG_RECORD_SEPARATOR,
|
|
4872
4964
|
"-v",
|
|
4873
4965
|
"ON_ERROR_STOP=1",
|
|
4874
4966
|
"-c",
|
|
@@ -5044,11 +5136,14 @@ function splitTsvLine(line, decodeFields) {
|
|
|
5044
5136
|
const fields = line.split("\t");
|
|
5045
5137
|
return decodeFields ? fields.map(decodeMysqlBatchField) : fields;
|
|
5046
5138
|
}
|
|
5047
|
-
function
|
|
5048
|
-
|
|
5139
|
+
function stripFinalRecordSeparator(text, recordSeparator) {
|
|
5140
|
+
return text.endsWith(recordSeparator) ? text.slice(0, -recordSeparator.length) : text;
|
|
5141
|
+
}
|
|
5142
|
+
function parseTsvOutput(stdout, hasHeader, recordSeparator) {
|
|
5143
|
+
const text = recordSeparator ? stripFinalRecordSeparator(stdout, recordSeparator) : stripFinalLineBreak(stdout);
|
|
5049
5144
|
if (text.length === 0)
|
|
5050
5145
|
return { columns: [], rows: [] };
|
|
5051
|
-
const lines = text.split(/\r?\n/);
|
|
5146
|
+
const lines = recordSeparator ? text.split(recordSeparator) : text.split(/\r?\n/);
|
|
5052
5147
|
if (lines.length === 0)
|
|
5053
5148
|
return { columns: [], rows: [] };
|
|
5054
5149
|
if (hasHeader) {
|
|
@@ -5115,13 +5210,17 @@ function createTableMetaCache(now = () => Date.now()) {
|
|
|
5115
5210
|
}
|
|
5116
5211
|
};
|
|
5117
5212
|
}
|
|
5213
|
+
function observeBackgroundRejection(promise) {
|
|
5214
|
+
promise.catch(() => {});
|
|
5215
|
+
return promise;
|
|
5216
|
+
}
|
|
5118
5217
|
function createDockerAdapter(config) {
|
|
5119
5218
|
async function execAsync(sql, signal) {
|
|
5120
5219
|
const result = await execInContainerAsync(config, sql, 1e4, signal);
|
|
5121
5220
|
if (result.code !== 0) {
|
|
5122
5221
|
throw new Error(result.stderr.trim() || "query failed");
|
|
5123
5222
|
}
|
|
5124
|
-
return parseTsvOutput(result.stdout, config.kind === "mysql");
|
|
5223
|
+
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
5125
5224
|
}
|
|
5126
5225
|
function toDbValue(val) {
|
|
5127
5226
|
if (val === "NULL" || val === "\\N")
|
|
@@ -5214,25 +5313,18 @@ function createDockerAdapter(config) {
|
|
|
5214
5313
|
},
|
|
5215
5314
|
async getIndexesAsync(signal) {
|
|
5216
5315
|
let sql;
|
|
5316
|
+
const INDEX_COL_SEP = "\x1F";
|
|
5217
5317
|
if (config.kind === "postgresql") {
|
|
5218
|
-
sql = `SELECT
|
|
5318
|
+
sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\x1f' ORDER BY k.ord), '') FROM pg_index ix JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = ${postgresSchemaLiteral()} AND i.relname NOT LIKE 'pg_%' GROUP BY i.relname, t.relname, ix.indisunique ORDER BY t.relname, i.relname`;
|
|
5219
5319
|
} else {
|
|
5220
|
-
sql = `SELECT
|
|
5320
|
+
sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\x1F') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
|
|
5221
5321
|
}
|
|
5222
5322
|
const result = await execAsync(sql, signal);
|
|
5223
|
-
if (config.kind === "postgresql") {
|
|
5224
|
-
return result.rows.map((row) => ({
|
|
5225
|
-
name: row[0],
|
|
5226
|
-
table: row[1],
|
|
5227
|
-
columns: [],
|
|
5228
|
-
unique: false
|
|
5229
|
-
}));
|
|
5230
|
-
}
|
|
5231
5323
|
return result.rows.map((row) => ({
|
|
5232
5324
|
name: row[0],
|
|
5233
5325
|
table: row[1],
|
|
5234
|
-
|
|
5235
|
-
|
|
5326
|
+
unique: row[2] === "1",
|
|
5327
|
+
columns: row[3] ? row[3].split(INDEX_COL_SEP).filter((s) => s.length > 0) : []
|
|
5236
5328
|
}));
|
|
5237
5329
|
},
|
|
5238
5330
|
async getForeignKeysAsync(signal) {
|
|
@@ -5386,7 +5478,7 @@ function createDockerAdapter(config) {
|
|
|
5386
5478
|
const id = tableIdentifier(table);
|
|
5387
5479
|
const countSql = `SELECT COUNT(*) AS cnt FROM ${id}`;
|
|
5388
5480
|
const columnsPromise = tableMetaCache.getColumns(table, () => fetchColumnsAsyncUncached(table, signal));
|
|
5389
|
-
const totalRowsPromise = tableMetaCache.getRowCount(table, async () => rowCountFromResult(await execAsync(countSql, signal)));
|
|
5481
|
+
const totalRowsPromise = observeBackgroundRejection(tableMetaCache.getRowCount(table, async () => rowCountFromResult(await execAsync(countSql, signal))));
|
|
5390
5482
|
let columns;
|
|
5391
5483
|
try {
|
|
5392
5484
|
columns = await columnsPromise;
|
|
@@ -5422,7 +5514,7 @@ function createDockerAdapter(config) {
|
|
|
5422
5514
|
}
|
|
5423
5515
|
const columnNames = columns.map((column) => column.name);
|
|
5424
5516
|
const order = buildOrderClause(filterOrderByColumns(options.orderBy, columnNames), config.kind);
|
|
5425
|
-
const where = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), config.kind).where;
|
|
5517
|
+
const where = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), config.kind, filterExactColumns(options.exact, columnNames)).where;
|
|
5426
5518
|
const whereClause = where ? ` WHERE ${where}` : "";
|
|
5427
5519
|
const countSql = `SELECT COUNT(*) AS cnt FROM ${id}${whereClause}`;
|
|
5428
5520
|
const countResultPromise = execAsync(countSql, signal);
|
|
@@ -5602,7 +5694,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
5602
5694
|
return fallback;
|
|
5603
5695
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
5604
5696
|
}
|
|
5605
|
-
const parsed = parseTsvOutput(result.stdout, kind === "mysql");
|
|
5697
|
+
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
5606
5698
|
const dbs = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
5607
5699
|
const value = dbs.length > 0 ? dbs : fallbackDockerDatabases(defaultDb);
|
|
5608
5700
|
return setDockerDatabasesCache(cacheKey, value, value.length > 0 ? DOCKER_DATABASES_POSITIVE_TTL_MS : DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
@@ -5643,7 +5735,7 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
5643
5735
|
if (result.code !== 0) {
|
|
5644
5736
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
5645
5737
|
}
|
|
5646
|
-
const parsed = parseTsvOutput(result.stdout, false);
|
|
5738
|
+
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR);
|
|
5647
5739
|
const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
5648
5740
|
const value = schemas.length > 0 ? schemas : ["public"];
|
|
5649
5741
|
return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
|
|
@@ -5667,7 +5759,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
5667
5759
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
5668
5760
|
});
|
|
5669
5761
|
}
|
|
5670
|
-
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, MYSQL_SPATIAL_TYPES;
|
|
5762
|
+
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES;
|
|
5671
5763
|
var init_docker = __esm(() => {
|
|
5672
5764
|
init_sql_snapshot();
|
|
5673
5765
|
init_docker_utils();
|
|
@@ -5866,7 +5958,7 @@ function createSqliteAdapter(db) {
|
|
|
5866
5958
|
async getFilteredTablePageWithMeta(table, options) {
|
|
5867
5959
|
const columns = queryColumns(db, table);
|
|
5868
5960
|
const columnNames = columns.map((column) => column.name);
|
|
5869
|
-
const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite");
|
|
5961
|
+
const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite", filterExactColumns(options.exact, columnNames));
|
|
5870
5962
|
const order = buildOrderClause2(filterOrderByColumns(options.orderBy, columnNames));
|
|
5871
5963
|
const tableId = sanitizeIdentifier(table);
|
|
5872
5964
|
const whereClause = filter.where ? ` WHERE ${filter.where}` : "";
|
|
@@ -5877,7 +5969,7 @@ function createSqliteAdapter(db) {
|
|
|
5877
5969
|
columns,
|
|
5878
5970
|
rows: result.rows,
|
|
5879
5971
|
rowCount: result.rowCount,
|
|
5880
|
-
totalRows: countRow?.cnt ?? 0
|
|
5972
|
+
totalRows: Number(countRow?.cnt ?? 0)
|
|
5881
5973
|
};
|
|
5882
5974
|
},
|
|
5883
5975
|
executeReadonlyQuery(sql, params, maxRows = 1000) {
|
|
@@ -8566,9 +8658,16 @@ function parseBuckets(xml) {
|
|
|
8566
8658
|
};
|
|
8567
8659
|
}).filter((bucket) => bucket.name).sort((a, b) => a.name.localeCompare(b.name));
|
|
8568
8660
|
}
|
|
8661
|
+
function decodeS3UrlEncoded(value) {
|
|
8662
|
+
try {
|
|
8663
|
+
return decodeURIComponent(value);
|
|
8664
|
+
} catch {
|
|
8665
|
+
return value;
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8569
8668
|
function parseObjects(xml) {
|
|
8570
8669
|
const objects = xmlBlocks(xml, "Contents").map((block) => {
|
|
8571
|
-
const key = xmlText(block, "Key") || "";
|
|
8670
|
+
const key = decodeS3UrlEncoded(xmlText(block, "Key") || "");
|
|
8572
8671
|
const updatedAt = toIsoDate(xmlText(block, "LastModified"));
|
|
8573
8672
|
return {
|
|
8574
8673
|
key,
|
|
@@ -8578,8 +8677,10 @@ function parseObjects(xml) {
|
|
|
8578
8677
|
...xmlText(block, "StorageClass") ? { storageClass: xmlText(block, "StorageClass") } : {}
|
|
8579
8678
|
};
|
|
8580
8679
|
}).filter((object) => object.key);
|
|
8680
|
+
const commonPrefixes = xmlBlocks(xml, "CommonPrefixes").map((block) => decodeS3UrlEncoded(xmlText(block, "Prefix") || "")).filter(Boolean);
|
|
8581
8681
|
return {
|
|
8582
8682
|
objects,
|
|
8683
|
+
commonPrefixes,
|
|
8583
8684
|
nextToken: xmlText(xml, "NextContinuationToken"),
|
|
8584
8685
|
truncated: xmlText(xml, "IsTruncated") === "true"
|
|
8585
8686
|
};
|
|
@@ -8661,8 +8762,10 @@ function createS3Adapter(config) {
|
|
|
8661
8762
|
bucket: opts.bucket,
|
|
8662
8763
|
query: {
|
|
8663
8764
|
"list-type": "2",
|
|
8765
|
+
"encoding-type": "url",
|
|
8664
8766
|
"max-keys": String(Math.min(1000, Math.max(1, opts.maxKeys ?? 200))),
|
|
8665
8767
|
...opts.prefix ? { prefix: opts.prefix } : {},
|
|
8768
|
+
...opts.delimiter ? { delimiter: opts.delimiter } : {},
|
|
8666
8769
|
...opts.continuationToken ? { "continuation-token": opts.continuationToken } : {}
|
|
8667
8770
|
},
|
|
8668
8771
|
signal: opts.signal
|
|
@@ -9020,6 +9123,42 @@ ${s3ObjectName(object.key)}`.toLowerCase();
|
|
|
9020
9123
|
return s3ErrorResponse(err, "list s3 objects");
|
|
9021
9124
|
}
|
|
9022
9125
|
}
|
|
9126
|
+
async function handleFolder(cwd, req, url, omitDirNames) {
|
|
9127
|
+
const r = await resolveS3(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
9128
|
+
if (r instanceof Response)
|
|
9129
|
+
return r;
|
|
9130
|
+
const bucket = validateBucket(url.searchParams.get("bucket"));
|
|
9131
|
+
if (bucket instanceof Response)
|
|
9132
|
+
return bucket;
|
|
9133
|
+
const prefix = validateOptionalText(url.searchParams.get("prefix"), "prefix", 2048);
|
|
9134
|
+
if (prefix instanceof Response)
|
|
9135
|
+
return prefix;
|
|
9136
|
+
const token = validateOptionalText(url.searchParams.get("token"), "token", 4096);
|
|
9137
|
+
if (token instanceof Response)
|
|
9138
|
+
return token;
|
|
9139
|
+
try {
|
|
9140
|
+
const page = await r.explorer.listObjects({
|
|
9141
|
+
bucket,
|
|
9142
|
+
prefix,
|
|
9143
|
+
delimiter: "/",
|
|
9144
|
+
continuationToken: token || undefined,
|
|
9145
|
+
maxKeys: MAX_OBJECT_LIMIT,
|
|
9146
|
+
signal: req.signal
|
|
9147
|
+
});
|
|
9148
|
+
const objects = page.objects.filter((object) => object.key !== prefix);
|
|
9149
|
+
const body = {
|
|
9150
|
+
dbId: r.dbId,
|
|
9151
|
+
bucket,
|
|
9152
|
+
prefix,
|
|
9153
|
+
folders: page.commonPrefixes ?? [],
|
|
9154
|
+
objects,
|
|
9155
|
+
...page.nextToken ? { nextToken: page.nextToken } : {}
|
|
9156
|
+
};
|
|
9157
|
+
return json(body);
|
|
9158
|
+
} catch (err) {
|
|
9159
|
+
return s3ErrorResponse(err, "list s3 folder");
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9023
9162
|
async function handleHead(cwd, req, url, omitDirNames) {
|
|
9024
9163
|
const r = await resolveS3(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
9025
9164
|
if (r instanceof Response)
|
|
@@ -9105,6 +9244,10 @@ async function handleS3Route(req, url, cwd, sideEffectAllowed, omitDirNames) {
|
|
|
9105
9244
|
methods: ["GET"],
|
|
9106
9245
|
handler: () => handleObjects(cwd, req, url, omitDirNames)
|
|
9107
9246
|
},
|
|
9247
|
+
"/_db/s3/folder": {
|
|
9248
|
+
methods: ["GET"],
|
|
9249
|
+
handler: () => handleFolder(cwd, req, url, omitDirNames)
|
|
9250
|
+
},
|
|
9108
9251
|
"/_db/s3/head": {
|
|
9109
9252
|
methods: ["GET"],
|
|
9110
9253
|
handler: () => handleHead(cwd, req, url, omitDirNames)
|
|
@@ -9799,6 +9942,9 @@ function sanitize(input) {
|
|
|
9799
9942
|
const sidebarWidth = sanitizeCssSize(tab.sidebarWidth);
|
|
9800
9943
|
if (sidebarWidth !== undefined)
|
|
9801
9944
|
out.sidebarWidth = sidebarWidth;
|
|
9945
|
+
const relatedPanelHeight = sanitizeCssSize(tab.relatedPanelHeight);
|
|
9946
|
+
if (relatedPanelHeight !== undefined)
|
|
9947
|
+
out.relatedPanelHeight = relatedPanelHeight;
|
|
9802
9948
|
const redis = sanitizeRedis(tab.redis);
|
|
9803
9949
|
if (redis !== undefined)
|
|
9804
9950
|
out.redis = redis;
|
|
@@ -10039,19 +10185,25 @@ async function handleSchema(cwd, url, omitDirNames, signal) {
|
|
|
10039
10185
|
linkedAbort.cleanup();
|
|
10040
10186
|
}
|
|
10041
10187
|
}
|
|
10042
|
-
function
|
|
10043
|
-
const raw = url.searchParams.get(
|
|
10188
|
+
function parseColumnValuePairs(url, param) {
|
|
10189
|
+
const raw = url.searchParams.get(param);
|
|
10044
10190
|
if (!raw)
|
|
10045
10191
|
return [];
|
|
10046
10192
|
try {
|
|
10047
10193
|
const parsed = JSON.parse(raw);
|
|
10048
10194
|
if (!Array.isArray(parsed))
|
|
10049
10195
|
return [];
|
|
10050
|
-
return parsed.filter((f) => !!f && typeof f === "object" && typeof f.column === "string" && typeof f.value === "string");
|
|
10196
|
+
return parsed.filter((f) => !!f && typeof f === "object" && typeof f.column === "string" && typeof f.value === "string").filter((f) => f.column.length <= MAX_FILTER_COLUMN_LEN && f.value.length <= MAX_FILTER_VALUE_LEN).slice(0, MAX_COLUMN_VALUE_PAIRS);
|
|
10051
10197
|
} catch {
|
|
10052
10198
|
return [];
|
|
10053
10199
|
}
|
|
10054
10200
|
}
|
|
10201
|
+
function parseFilters(url) {
|
|
10202
|
+
return parseColumnValuePairs(url, "filters");
|
|
10203
|
+
}
|
|
10204
|
+
function parseExactConditions(url) {
|
|
10205
|
+
return parseColumnValuePairs(url, "eq");
|
|
10206
|
+
}
|
|
10055
10207
|
function groupFiltersByValue(filters) {
|
|
10056
10208
|
const grouped = new Map;
|
|
10057
10209
|
for (const filter of filters) {
|
|
@@ -10082,14 +10234,16 @@ async function handleTable(cwd, url, omitDirNames, signal) {
|
|
|
10082
10234
|
];
|
|
10083
10235
|
}
|
|
10084
10236
|
const filters = parseFilters(url);
|
|
10237
|
+
const exact = parseExactConditions(url);
|
|
10085
10238
|
try {
|
|
10086
10239
|
const adapter = await getAdapter(r, cwd, signal);
|
|
10087
|
-
if (filters.length > 0) {
|
|
10240
|
+
if (filters.length > 0 || exact.length > 0) {
|
|
10088
10241
|
const meta2 = await adapter.getFilteredTablePageWithMeta(table, {
|
|
10089
10242
|
offset,
|
|
10090
10243
|
limit,
|
|
10091
10244
|
orderBy,
|
|
10092
|
-
grouped: groupFiltersByValue(filters)
|
|
10245
|
+
grouped: groupFiltersByValue(filters),
|
|
10246
|
+
...exact.length > 0 ? { exact } : {}
|
|
10093
10247
|
}, signal);
|
|
10094
10248
|
const colNames2 = new Set(meta2.columns.map((c) => c.name));
|
|
10095
10249
|
if (sortCol && !colNames2.has(sortCol)) {
|
|
@@ -10363,6 +10517,7 @@ async function handleExport(cwd, url, omitDirNames, signal) {
|
|
|
10363
10517
|
];
|
|
10364
10518
|
}
|
|
10365
10519
|
const filters = parseFilters(url);
|
|
10520
|
+
const exact = parseExactConditions(url);
|
|
10366
10521
|
try {
|
|
10367
10522
|
const adapter = await getAdapter(r, cwd, signal);
|
|
10368
10523
|
const db = asAsync(adapter);
|
|
@@ -10373,12 +10528,13 @@ async function handleExport(cwd, url, omitDirNames, signal) {
|
|
|
10373
10528
|
return textError(`invalid sort column: ${sortCol}`, 400);
|
|
10374
10529
|
}
|
|
10375
10530
|
let rawRows;
|
|
10376
|
-
if (filters.length > 0) {
|
|
10531
|
+
if (filters.length > 0 || exact.length > 0) {
|
|
10377
10532
|
const meta = await adapter.getFilteredTablePageWithMeta(table, {
|
|
10378
10533
|
offset: 0,
|
|
10379
10534
|
limit: EXPORT_MAX_ROWS,
|
|
10380
10535
|
orderBy,
|
|
10381
|
-
grouped: groupFiltersByValue(filters)
|
|
10536
|
+
grouped: groupFiltersByValue(filters),
|
|
10537
|
+
...exact.length > 0 ? { exact } : {}
|
|
10382
10538
|
}, signal);
|
|
10383
10539
|
rawRows = meta.rows;
|
|
10384
10540
|
} else {
|
|
@@ -11015,7 +11171,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
11015
11171
|
}
|
|
11016
11172
|
}, sideEffectAllowed, wrapResponse, (err) => handleError("database", "handle database request", err));
|
|
11017
11173
|
}
|
|
11018
|
-
var initialized = false, dockerAdapterCache, MAX_SCHEMA_NAME_LEN2 = 1024, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_DB_UI_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
|
|
11174
|
+
var initialized = false, dockerAdapterCache, MAX_SCHEMA_NAME_LEN2 = 1024, MAX_COLUMN_VALUE_PAIRS = 64, MAX_FILTER_COLUMN_LEN = 128, MAX_FILTER_VALUE_LEN = 4096, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_DB_UI_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
|
|
11019
11175
|
var init_handle = __esm(() => {
|
|
11020
11176
|
init_state_store();
|
|
11021
11177
|
init_docker();
|
|
@@ -11082,12 +11238,20 @@ async function parseJsonBody(req) {
|
|
|
11082
11238
|
async function handleSettingsGet(cwd) {
|
|
11083
11239
|
return jsonLoadResponse(() => loadAppSettingsState(cwd), "state", "failed to load settings state");
|
|
11084
11240
|
}
|
|
11085
|
-
async function handleSettingsPatch(cwd, req) {
|
|
11241
|
+
async function handleSettingsPatch(cwd, req, onChange) {
|
|
11086
11242
|
const body = await parseJsonBody(req);
|
|
11087
11243
|
if (body instanceof Response)
|
|
11088
11244
|
return body;
|
|
11089
11245
|
try {
|
|
11090
|
-
|
|
11246
|
+
const next = await patchAppSettingsState(cwd, body);
|
|
11247
|
+
if (onChange) {
|
|
11248
|
+
try {
|
|
11249
|
+
onChange(next);
|
|
11250
|
+
} catch (notifyErr) {
|
|
11251
|
+
console.warn("[code-viewer] settings change notify failed:", notifyErr);
|
|
11252
|
+
}
|
|
11253
|
+
}
|
|
11254
|
+
return json(next);
|
|
11091
11255
|
} catch (err) {
|
|
11092
11256
|
const message = err instanceof Error ? err.message : String(err);
|
|
11093
11257
|
if (message === "settings state too large")
|
|
@@ -11113,12 +11277,12 @@ async function handleViewPatch(cwd, req) {
|
|
|
11113
11277
|
return textError("failed to save view state", 500);
|
|
11114
11278
|
}
|
|
11115
11279
|
}
|
|
11116
|
-
async function handleStateRoute(req, url, cwd, sideEffectAllowed) {
|
|
11280
|
+
async function handleStateRoute(req, url, cwd, sideEffectAllowed, options = {}) {
|
|
11117
11281
|
return dispatchRoutes(req, url, {
|
|
11118
11282
|
"/_state/settings": {
|
|
11119
11283
|
methods: ["GET", "PATCH"],
|
|
11120
11284
|
sideEffect: (method) => method !== "GET",
|
|
11121
|
-
handler: () => req.method === "GET" ? handleSettingsGet(cwd) : handleSettingsPatch(cwd, req)
|
|
11285
|
+
handler: () => req.method === "GET" ? handleSettingsGet(cwd) : handleSettingsPatch(cwd, req, options.onSettingsChange)
|
|
11122
11286
|
},
|
|
11123
11287
|
"/_state/view": {
|
|
11124
11288
|
methods: ["GET", "PATCH"],
|
|
@@ -11214,16 +11378,30 @@ Examples:
|
|
|
11214
11378
|
}
|
|
11215
11379
|
if (rest.length)
|
|
11216
11380
|
cliArgs = rest;
|
|
11217
|
-
|
|
11218
|
-
const configScopeExcludeNames = loadProjectConfigScopeExcludeNames();
|
|
11219
|
-
uploadDisabledByConfig = loadProjectConfigUploadDisabled();
|
|
11381
|
+
warnIfLegacyConfigPresent();
|
|
11220
11382
|
if (scopeOmitDirCliOverride) {
|
|
11221
11383
|
scopeOmitDirNames = scopeOmitDirCliOverride;
|
|
11222
|
-
} else if (configScopeOmitDirs) {
|
|
11223
|
-
scopeOmitDirNames = configScopeOmitDirs;
|
|
11224
11384
|
}
|
|
11225
|
-
|
|
11226
|
-
|
|
11385
|
+
}
|
|
11386
|
+
function warnIfLegacyConfigPresent() {
|
|
11387
|
+
try {
|
|
11388
|
+
if (existsSync6(join13(cwd, ".code-viewer.json"))) {
|
|
11389
|
+
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.");
|
|
11390
|
+
}
|
|
11391
|
+
} catch {}
|
|
11392
|
+
}
|
|
11393
|
+
function applyPersistedSettings(state) {
|
|
11394
|
+
if (!scopeOmitDirCliOverride && Array.isArray(state.scopeOmitDirs) && state.scopeOmitDirs.length > 0) {
|
|
11395
|
+
scopeOmitDirNames = state.scopeOmitDirs;
|
|
11396
|
+
} else if (!scopeOmitDirCliOverride) {
|
|
11397
|
+
scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
|
|
11398
|
+
}
|
|
11399
|
+
if (Array.isArray(state.scopeExcludeNames) && state.scopeExcludeNames.length > 0) {
|
|
11400
|
+
scopeExcludeNames = state.scopeExcludeNames;
|
|
11401
|
+
} else {
|
|
11402
|
+
scopeExcludeNames = DEFAULT_EXCLUDE_NAMES;
|
|
11403
|
+
}
|
|
11404
|
+
uploadEnabled = state.uploadEnabled !== false;
|
|
11227
11405
|
}
|
|
11228
11406
|
function json2(data, init = {}) {
|
|
11229
11407
|
return new Response(JSON.stringify(data), {
|
|
@@ -11525,45 +11703,6 @@ function parseScopeExcludeNamesQuery(value) {
|
|
|
11525
11703
|
}
|
|
11526
11704
|
return normalizeScopeExcludeNames(names);
|
|
11527
11705
|
}
|
|
11528
|
-
function loadProjectConfig() {
|
|
11529
|
-
const full = join13(cwd, ".code-viewer.json");
|
|
11530
|
-
if (!existsSync6(full))
|
|
11531
|
-
return null;
|
|
11532
|
-
let realCwd;
|
|
11533
|
-
let realConfig;
|
|
11534
|
-
try {
|
|
11535
|
-
realCwd = realpathSync4(cwd);
|
|
11536
|
-
realConfig = realpathSync4(full);
|
|
11537
|
-
} catch {
|
|
11538
|
-
return null;
|
|
11539
|
-
}
|
|
11540
|
-
if (dirname3(realConfig) !== realCwd || basename3(realConfig) !== ".code-viewer.json")
|
|
11541
|
-
return null;
|
|
11542
|
-
try {
|
|
11543
|
-
const parsed = JSON.parse(readFileSync4(realConfig, "utf8"));
|
|
11544
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && "version" in parsed && parsed.version !== 1)
|
|
11545
|
-
return null;
|
|
11546
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
11547
|
-
} catch {
|
|
11548
|
-
return null;
|
|
11549
|
-
}
|
|
11550
|
-
}
|
|
11551
|
-
function loadProjectConfigUploadDisabled() {
|
|
11552
|
-
const config = loadProjectConfig();
|
|
11553
|
-
return config?.upload?.enabled === false;
|
|
11554
|
-
}
|
|
11555
|
-
function loadProjectConfigScopeOmitDirs() {
|
|
11556
|
-
const config = loadProjectConfig();
|
|
11557
|
-
if (!config?.scope || !Array.isArray(config.scope.omitDirs))
|
|
11558
|
-
return null;
|
|
11559
|
-
return normalizeScopeOmitDirNames(config.scope.omitDirs);
|
|
11560
|
-
}
|
|
11561
|
-
function loadProjectConfigScopeExcludeNames() {
|
|
11562
|
-
const config = loadProjectConfig();
|
|
11563
|
-
if (!config?.scope || !Array.isArray(config.scope.excludeNames))
|
|
11564
|
-
return null;
|
|
11565
|
-
return normalizeScopeExcludeNames(config.scope.excludeNames);
|
|
11566
|
-
}
|
|
11567
11706
|
function scopeOmitDirNamesFromQuery(url) {
|
|
11568
11707
|
if (!url.searchParams.has("omit_dirs"))
|
|
11569
11708
|
return scopeOmitDirNames;
|
|
@@ -11731,7 +11870,7 @@ function handleTree(url) {
|
|
|
11731
11870
|
branch: currentBranch(cwd) || undefined,
|
|
11732
11871
|
entries: recursive ? entries : entries.map((entry) => attachTreeEntryMetadata(target, entry)),
|
|
11733
11872
|
readme: readReadme(target, path),
|
|
11734
|
-
upload_enabled:
|
|
11873
|
+
upload_enabled: uploadEnabled && (target === "worktree" || target === "")
|
|
11735
11874
|
});
|
|
11736
11875
|
}
|
|
11737
11876
|
function handleSettings() {
|
|
@@ -12293,8 +12432,8 @@ function uploadOpenFlags() {
|
|
|
12293
12432
|
return constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW || 0);
|
|
12294
12433
|
}
|
|
12295
12434
|
async function handleUploadFiles(req) {
|
|
12296
|
-
if (
|
|
12297
|
-
return text("upload disabled by
|
|
12435
|
+
if (!uploadEnabled)
|
|
12436
|
+
return text("upload disabled by viewer settings", 403);
|
|
12298
12437
|
if (req.method !== "POST")
|
|
12299
12438
|
return text("method not allowed", 405);
|
|
12300
12439
|
if (!sideEffectRequestAllowed(req))
|
|
@@ -12847,9 +12986,6 @@ async function handleAnnotations(req) {
|
|
|
12847
12986
|
}
|
|
12848
12987
|
return text("invalid action", 400);
|
|
12849
12988
|
}
|
|
12850
|
-
function isCodeViewerInternalPath(path) {
|
|
12851
|
-
return path.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
|
|
12852
|
-
}
|
|
12853
12989
|
function sendSse(event, data = "tick") {
|
|
12854
12990
|
const payload = enc.encode(`event: ${event}
|
|
12855
12991
|
data: ${data}
|
|
@@ -12897,7 +13033,7 @@ async function shutdown(exitCode = 0) {
|
|
|
12897
13033
|
}
|
|
12898
13034
|
process.exit(exitCode);
|
|
12899
13035
|
}
|
|
12900
|
-
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames,
|
|
13036
|
+
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, uploadEnabled = true, rgAvailableCache = null, enc, sseClients, sseKeepalives, fileCache, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
12901
13037
|
var init_preview = __esm(async () => {
|
|
12902
13038
|
init_routes();
|
|
12903
13039
|
init_annotations();
|
|
@@ -12909,6 +13045,7 @@ var init_preview = __esm(async () => {
|
|
|
12909
13045
|
init_runtime();
|
|
12910
13046
|
init_search();
|
|
12911
13047
|
init_server_registry();
|
|
13048
|
+
init_state_store();
|
|
12912
13049
|
init_worktree_watcher();
|
|
12913
13050
|
WEB_ROOT = join13(ROOT, "web");
|
|
12914
13051
|
VERSION = JSON.parse(readFileSync4(join13(ROOT, "package.json"), "utf8")).version;
|
|
@@ -12968,7 +13105,9 @@ var init_preview = __esm(async () => {
|
|
|
12968
13105
|
lineIndexCache = new Map;
|
|
12969
13106
|
blobLineIndexCache = new Map;
|
|
12970
13107
|
blobBytesCache = new Map;
|
|
13108
|
+
isCodeViewerInternalPath = isToolInternalPath;
|
|
12971
13109
|
parseCli();
|
|
13110
|
+
applyPersistedSettings(await loadAppSettingsState(cwd));
|
|
12972
13111
|
server = await startServer({
|
|
12973
13112
|
hostname: "127.0.0.1",
|
|
12974
13113
|
port: listenPort,
|
|
@@ -13017,7 +13156,7 @@ var init_preview = __esm(async () => {
|
|
|
13017
13156
|
}
|
|
13018
13157
|
if (url.pathname.startsWith("/_state/")) {
|
|
13019
13158
|
const { handleStateRoute: handleStateRoute2 } = await Promise.resolve().then(() => (init_state_route(), exports_state_route));
|
|
13020
|
-
const stateResponse = await handleStateRoute2(req, url, cwd, sideEffectRequestAllowed);
|
|
13159
|
+
const stateResponse = await handleStateRoute2(req, url, cwd, sideEffectRequestAllowed, { onSettingsChange: applyPersistedSettings });
|
|
13021
13160
|
if (stateResponse)
|
|
13022
13161
|
return stateResponse;
|
|
13023
13162
|
}
|
|
@@ -13042,6 +13181,12 @@ var init_preview = __esm(async () => {
|
|
|
13042
13181
|
data: ok
|
|
13043
13182
|
|
|
13044
13183
|
`));
|
|
13184
|
+
if (watchLimitReached !== null) {
|
|
13185
|
+
controller.enqueue(enc.encode(`event: watch-limit
|
|
13186
|
+
data: ${watchLimitReached}
|
|
13187
|
+
|
|
13188
|
+
`));
|
|
13189
|
+
}
|
|
13045
13190
|
keepalive = setInterval(() => {
|
|
13046
13191
|
try {
|
|
13047
13192
|
controller.enqueue(enc.encode(`: ping
|
|
@@ -13111,6 +13256,10 @@ data: ok
|
|
|
13111
13256
|
initialScanMode: "async",
|
|
13112
13257
|
maxWatchedDirectories: worktreeWatchDirectoryLimitFromEnv(),
|
|
13113
13258
|
onUpdate: triggerUpdate,
|
|
13259
|
+
onWatchLimit: (limit) => {
|
|
13260
|
+
watchLimitReached = limit;
|
|
13261
|
+
sendSse("watch-limit", String(limit));
|
|
13262
|
+
},
|
|
13114
13263
|
onError: (error) => {
|
|
13115
13264
|
const message = error instanceof Error ? error.message : String(error);
|
|
13116
13265
|
console.warn(`code-viewer worktree watch skipped: ${message}`);
|