@youtyan/code-viewer 0.2.8 → 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 +604 -459
- 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,6 +3941,23 @@ 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,
|
|
@@ -4218,6 +3976,7 @@ function mergeViewState(current, patch) {
|
|
|
4218
3976
|
return sanitizeViewState({
|
|
4219
3977
|
version: 1,
|
|
4220
3978
|
collapsedDirs: [...collapsedDirs],
|
|
3979
|
+
lazyExpandedDirs: [...lazyExpandedDirs],
|
|
4221
3980
|
viewedFiles: [...viewedFiles]
|
|
4222
3981
|
});
|
|
4223
3982
|
}
|
|
@@ -4226,9 +3985,24 @@ function safeObjectKey(value) {
|
|
|
4226
3985
|
return null;
|
|
4227
3986
|
return value;
|
|
4228
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
|
+
}
|
|
4229
3999
|
function sanitizeDbUiState(raw) {
|
|
4230
|
-
if (!isRecord(raw)
|
|
4000
|
+
if (!isRecord(raw))
|
|
4231
4001
|
return emptyDbUiState();
|
|
4002
|
+
const prefs = sanitizeDbUiPrefs(raw.prefs);
|
|
4003
|
+
if (!isRecord(raw.columnWidths)) {
|
|
4004
|
+
return prefs ? { ...emptyDbUiState(), prefs } : emptyDbUiState();
|
|
4005
|
+
}
|
|
4232
4006
|
const columnWidths = {};
|
|
4233
4007
|
let dbCount = 0;
|
|
4234
4008
|
for (const [dbIdRaw, tablesRaw] of Object.entries(raw.columnWidths)) {
|
|
@@ -4267,100 +4041,406 @@ function sanitizeDbUiState(raw) {
|
|
|
4267
4041
|
columnWidths[dbId] = tables;
|
|
4268
4042
|
dbCount++;
|
|
4269
4043
|
}
|
|
4270
|
-
|
|
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;
|
|
4271
4061
|
}
|
|
4272
4062
|
function mergeDbUiState(current, patch) {
|
|
4273
4063
|
if (!isRecord(patch))
|
|
4274
4064
|
return current;
|
|
4065
|
+
const mergedPrefs = "prefs" in patch ? mergeDbUiPrefs(current.prefs, patch.prefs) : current.prefs;
|
|
4275
4066
|
if (!isRecord(patch.columnWidths)) {
|
|
4276
|
-
|
|
4067
|
+
const merged = { ...current, version: 1 };
|
|
4068
|
+
if (mergedPrefs)
|
|
4069
|
+
merged.prefs = mergedPrefs;
|
|
4070
|
+
else
|
|
4071
|
+
delete merged.prefs;
|
|
4072
|
+
return sanitizeDbUiState(merged);
|
|
4277
4073
|
}
|
|
4278
4074
|
const columnWidths = {
|
|
4279
4075
|
...current.columnWidths
|
|
4280
4076
|
};
|
|
4281
|
-
for (const [dbId, tablesRaw] of Object.entries(patch.columnWidths)) {
|
|
4282
|
-
if (tablesRaw === null) {
|
|
4283
|
-
delete columnWidths[dbId];
|
|
4284
|
-
continue;
|
|
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"
|
|
4149
|
+
});
|
|
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"
|
|
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(/^\/+/, "");
|
|
4177
|
+
}
|
|
4178
|
+
function isInsideRoot(root, path) {
|
|
4179
|
+
const rel = relative(root, path).replace(/\\/g, "/");
|
|
4180
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
4181
|
+
}
|
|
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}/`))
|
|
4244
|
+
continue;
|
|
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())
|
|
4282
|
+
continue;
|
|
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);
|
|
4344
|
+
}
|
|
4345
|
+
};
|
|
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) {
|
|
@@ -5125,7 +5220,7 @@ function createDockerAdapter(config) {
|
|
|
5125
5220
|
if (result.code !== 0) {
|
|
5126
5221
|
throw new Error(result.stderr.trim() || "query failed");
|
|
5127
5222
|
}
|
|
5128
|
-
return parseTsvOutput(result.stdout, config.kind === "mysql");
|
|
5223
|
+
return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
5129
5224
|
}
|
|
5130
5225
|
function toDbValue(val) {
|
|
5131
5226
|
if (val === "NULL" || val === "\\N")
|
|
@@ -5218,25 +5313,18 @@ function createDockerAdapter(config) {
|
|
|
5218
5313
|
},
|
|
5219
5314
|
async getIndexesAsync(signal) {
|
|
5220
5315
|
let sql;
|
|
5316
|
+
const INDEX_COL_SEP = "\x1F";
|
|
5221
5317
|
if (config.kind === "postgresql") {
|
|
5222
|
-
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`;
|
|
5223
5319
|
} else {
|
|
5224
|
-
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`;
|
|
5225
5321
|
}
|
|
5226
5322
|
const result = await execAsync(sql, signal);
|
|
5227
|
-
if (config.kind === "postgresql") {
|
|
5228
|
-
return result.rows.map((row) => ({
|
|
5229
|
-
name: row[0],
|
|
5230
|
-
table: row[1],
|
|
5231
|
-
columns: [],
|
|
5232
|
-
unique: false
|
|
5233
|
-
}));
|
|
5234
|
-
}
|
|
5235
5323
|
return result.rows.map((row) => ({
|
|
5236
5324
|
name: row[0],
|
|
5237
5325
|
table: row[1],
|
|
5238
|
-
|
|
5239
|
-
|
|
5326
|
+
unique: row[2] === "1",
|
|
5327
|
+
columns: row[3] ? row[3].split(INDEX_COL_SEP).filter((s) => s.length > 0) : []
|
|
5240
5328
|
}));
|
|
5241
5329
|
},
|
|
5242
5330
|
async getForeignKeysAsync(signal) {
|
|
@@ -5426,7 +5514,7 @@ function createDockerAdapter(config) {
|
|
|
5426
5514
|
}
|
|
5427
5515
|
const columnNames = columns.map((column) => column.name);
|
|
5428
5516
|
const order = buildOrderClause(filterOrderByColumns(options.orderBy, columnNames), config.kind);
|
|
5429
|
-
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;
|
|
5430
5518
|
const whereClause = where ? ` WHERE ${where}` : "";
|
|
5431
5519
|
const countSql = `SELECT COUNT(*) AS cnt FROM ${id}${whereClause}`;
|
|
5432
5520
|
const countResultPromise = execAsync(countSql, signal);
|
|
@@ -5606,7 +5694,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
|
|
|
5606
5694
|
return fallback;
|
|
5607
5695
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
5608
5696
|
}
|
|
5609
|
-
const parsed = parseTsvOutput(result.stdout, kind === "mysql");
|
|
5697
|
+
const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
|
|
5610
5698
|
const dbs = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
5611
5699
|
const value = dbs.length > 0 ? dbs : fallbackDockerDatabases(defaultDb);
|
|
5612
5700
|
return setDockerDatabasesCache(cacheKey, value, value.length > 0 ? DOCKER_DATABASES_POSITIVE_TTL_MS : DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
@@ -5647,7 +5735,7 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
5647
5735
|
if (result.code !== 0) {
|
|
5648
5736
|
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
5649
5737
|
}
|
|
5650
|
-
const parsed = parseTsvOutput(result.stdout, false);
|
|
5738
|
+
const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR);
|
|
5651
5739
|
const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
5652
5740
|
const value = schemas.length > 0 ? schemas : ["public"];
|
|
5653
5741
|
return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
|
|
@@ -5671,7 +5759,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
|
|
|
5671
5759
|
...kind === "postgresql" && schema ? { schema } : {}
|
|
5672
5760
|
});
|
|
5673
5761
|
}
|
|
5674
|
-
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;
|
|
5675
5763
|
var init_docker = __esm(() => {
|
|
5676
5764
|
init_sql_snapshot();
|
|
5677
5765
|
init_docker_utils();
|
|
@@ -5870,7 +5958,7 @@ function createSqliteAdapter(db) {
|
|
|
5870
5958
|
async getFilteredTablePageWithMeta(table, options) {
|
|
5871
5959
|
const columns = queryColumns(db, table);
|
|
5872
5960
|
const columnNames = columns.map((column) => column.name);
|
|
5873
|
-
const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite");
|
|
5961
|
+
const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite", filterExactColumns(options.exact, columnNames));
|
|
5874
5962
|
const order = buildOrderClause2(filterOrderByColumns(options.orderBy, columnNames));
|
|
5875
5963
|
const tableId = sanitizeIdentifier(table);
|
|
5876
5964
|
const whereClause = filter.where ? ` WHERE ${filter.where}` : "";
|
|
@@ -5881,7 +5969,7 @@ function createSqliteAdapter(db) {
|
|
|
5881
5969
|
columns,
|
|
5882
5970
|
rows: result.rows,
|
|
5883
5971
|
rowCount: result.rowCount,
|
|
5884
|
-
totalRows: countRow?.cnt ?? 0
|
|
5972
|
+
totalRows: Number(countRow?.cnt ?? 0)
|
|
5885
5973
|
};
|
|
5886
5974
|
},
|
|
5887
5975
|
executeReadonlyQuery(sql, params, maxRows = 1000) {
|
|
@@ -8570,9 +8658,16 @@ function parseBuckets(xml) {
|
|
|
8570
8658
|
};
|
|
8571
8659
|
}).filter((bucket) => bucket.name).sort((a, b) => a.name.localeCompare(b.name));
|
|
8572
8660
|
}
|
|
8661
|
+
function decodeS3UrlEncoded(value) {
|
|
8662
|
+
try {
|
|
8663
|
+
return decodeURIComponent(value);
|
|
8664
|
+
} catch {
|
|
8665
|
+
return value;
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8573
8668
|
function parseObjects(xml) {
|
|
8574
8669
|
const objects = xmlBlocks(xml, "Contents").map((block) => {
|
|
8575
|
-
const key = xmlText(block, "Key") || "";
|
|
8670
|
+
const key = decodeS3UrlEncoded(xmlText(block, "Key") || "");
|
|
8576
8671
|
const updatedAt = toIsoDate(xmlText(block, "LastModified"));
|
|
8577
8672
|
return {
|
|
8578
8673
|
key,
|
|
@@ -8582,8 +8677,10 @@ function parseObjects(xml) {
|
|
|
8582
8677
|
...xmlText(block, "StorageClass") ? { storageClass: xmlText(block, "StorageClass") } : {}
|
|
8583
8678
|
};
|
|
8584
8679
|
}).filter((object) => object.key);
|
|
8680
|
+
const commonPrefixes = xmlBlocks(xml, "CommonPrefixes").map((block) => decodeS3UrlEncoded(xmlText(block, "Prefix") || "")).filter(Boolean);
|
|
8585
8681
|
return {
|
|
8586
8682
|
objects,
|
|
8683
|
+
commonPrefixes,
|
|
8587
8684
|
nextToken: xmlText(xml, "NextContinuationToken"),
|
|
8588
8685
|
truncated: xmlText(xml, "IsTruncated") === "true"
|
|
8589
8686
|
};
|
|
@@ -8665,8 +8762,10 @@ function createS3Adapter(config) {
|
|
|
8665
8762
|
bucket: opts.bucket,
|
|
8666
8763
|
query: {
|
|
8667
8764
|
"list-type": "2",
|
|
8765
|
+
"encoding-type": "url",
|
|
8668
8766
|
"max-keys": String(Math.min(1000, Math.max(1, opts.maxKeys ?? 200))),
|
|
8669
8767
|
...opts.prefix ? { prefix: opts.prefix } : {},
|
|
8768
|
+
...opts.delimiter ? { delimiter: opts.delimiter } : {},
|
|
8670
8769
|
...opts.continuationToken ? { "continuation-token": opts.continuationToken } : {}
|
|
8671
8770
|
},
|
|
8672
8771
|
signal: opts.signal
|
|
@@ -9024,6 +9123,42 @@ ${s3ObjectName(object.key)}`.toLowerCase();
|
|
|
9024
9123
|
return s3ErrorResponse(err, "list s3 objects");
|
|
9025
9124
|
}
|
|
9026
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
|
+
}
|
|
9027
9162
|
async function handleHead(cwd, req, url, omitDirNames) {
|
|
9028
9163
|
const r = await resolveS3(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
|
|
9029
9164
|
if (r instanceof Response)
|
|
@@ -9109,6 +9244,10 @@ async function handleS3Route(req, url, cwd, sideEffectAllowed, omitDirNames) {
|
|
|
9109
9244
|
methods: ["GET"],
|
|
9110
9245
|
handler: () => handleObjects(cwd, req, url, omitDirNames)
|
|
9111
9246
|
},
|
|
9247
|
+
"/_db/s3/folder": {
|
|
9248
|
+
methods: ["GET"],
|
|
9249
|
+
handler: () => handleFolder(cwd, req, url, omitDirNames)
|
|
9250
|
+
},
|
|
9112
9251
|
"/_db/s3/head": {
|
|
9113
9252
|
methods: ["GET"],
|
|
9114
9253
|
handler: () => handleHead(cwd, req, url, omitDirNames)
|
|
@@ -9803,6 +9942,9 @@ function sanitize(input) {
|
|
|
9803
9942
|
const sidebarWidth = sanitizeCssSize(tab.sidebarWidth);
|
|
9804
9943
|
if (sidebarWidth !== undefined)
|
|
9805
9944
|
out.sidebarWidth = sidebarWidth;
|
|
9945
|
+
const relatedPanelHeight = sanitizeCssSize(tab.relatedPanelHeight);
|
|
9946
|
+
if (relatedPanelHeight !== undefined)
|
|
9947
|
+
out.relatedPanelHeight = relatedPanelHeight;
|
|
9806
9948
|
const redis = sanitizeRedis(tab.redis);
|
|
9807
9949
|
if (redis !== undefined)
|
|
9808
9950
|
out.redis = redis;
|
|
@@ -10043,19 +10185,25 @@ async function handleSchema(cwd, url, omitDirNames, signal) {
|
|
|
10043
10185
|
linkedAbort.cleanup();
|
|
10044
10186
|
}
|
|
10045
10187
|
}
|
|
10046
|
-
function
|
|
10047
|
-
const raw = url.searchParams.get(
|
|
10188
|
+
function parseColumnValuePairs(url, param) {
|
|
10189
|
+
const raw = url.searchParams.get(param);
|
|
10048
10190
|
if (!raw)
|
|
10049
10191
|
return [];
|
|
10050
10192
|
try {
|
|
10051
10193
|
const parsed = JSON.parse(raw);
|
|
10052
10194
|
if (!Array.isArray(parsed))
|
|
10053
10195
|
return [];
|
|
10054
|
-
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);
|
|
10055
10197
|
} catch {
|
|
10056
10198
|
return [];
|
|
10057
10199
|
}
|
|
10058
10200
|
}
|
|
10201
|
+
function parseFilters(url) {
|
|
10202
|
+
return parseColumnValuePairs(url, "filters");
|
|
10203
|
+
}
|
|
10204
|
+
function parseExactConditions(url) {
|
|
10205
|
+
return parseColumnValuePairs(url, "eq");
|
|
10206
|
+
}
|
|
10059
10207
|
function groupFiltersByValue(filters) {
|
|
10060
10208
|
const grouped = new Map;
|
|
10061
10209
|
for (const filter of filters) {
|
|
@@ -10086,14 +10234,16 @@ async function handleTable(cwd, url, omitDirNames, signal) {
|
|
|
10086
10234
|
];
|
|
10087
10235
|
}
|
|
10088
10236
|
const filters = parseFilters(url);
|
|
10237
|
+
const exact = parseExactConditions(url);
|
|
10089
10238
|
try {
|
|
10090
10239
|
const adapter = await getAdapter(r, cwd, signal);
|
|
10091
|
-
if (filters.length > 0) {
|
|
10240
|
+
if (filters.length > 0 || exact.length > 0) {
|
|
10092
10241
|
const meta2 = await adapter.getFilteredTablePageWithMeta(table, {
|
|
10093
10242
|
offset,
|
|
10094
10243
|
limit,
|
|
10095
10244
|
orderBy,
|
|
10096
|
-
grouped: groupFiltersByValue(filters)
|
|
10245
|
+
grouped: groupFiltersByValue(filters),
|
|
10246
|
+
...exact.length > 0 ? { exact } : {}
|
|
10097
10247
|
}, signal);
|
|
10098
10248
|
const colNames2 = new Set(meta2.columns.map((c) => c.name));
|
|
10099
10249
|
if (sortCol && !colNames2.has(sortCol)) {
|
|
@@ -10367,6 +10517,7 @@ async function handleExport(cwd, url, omitDirNames, signal) {
|
|
|
10367
10517
|
];
|
|
10368
10518
|
}
|
|
10369
10519
|
const filters = parseFilters(url);
|
|
10520
|
+
const exact = parseExactConditions(url);
|
|
10370
10521
|
try {
|
|
10371
10522
|
const adapter = await getAdapter(r, cwd, signal);
|
|
10372
10523
|
const db = asAsync(adapter);
|
|
@@ -10377,12 +10528,13 @@ async function handleExport(cwd, url, omitDirNames, signal) {
|
|
|
10377
10528
|
return textError(`invalid sort column: ${sortCol}`, 400);
|
|
10378
10529
|
}
|
|
10379
10530
|
let rawRows;
|
|
10380
|
-
if (filters.length > 0) {
|
|
10531
|
+
if (filters.length > 0 || exact.length > 0) {
|
|
10381
10532
|
const meta = await adapter.getFilteredTablePageWithMeta(table, {
|
|
10382
10533
|
offset: 0,
|
|
10383
10534
|
limit: EXPORT_MAX_ROWS,
|
|
10384
10535
|
orderBy,
|
|
10385
|
-
grouped: groupFiltersByValue(filters)
|
|
10536
|
+
grouped: groupFiltersByValue(filters),
|
|
10537
|
+
...exact.length > 0 ? { exact } : {}
|
|
10386
10538
|
}, signal);
|
|
10387
10539
|
rawRows = meta.rows;
|
|
10388
10540
|
} else {
|
|
@@ -11019,7 +11171,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
11019
11171
|
}
|
|
11020
11172
|
}, sideEffectAllowed, wrapResponse, (err) => handleError("database", "handle database request", err));
|
|
11021
11173
|
}
|
|
11022
|
-
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;
|
|
11023
11175
|
var init_handle = __esm(() => {
|
|
11024
11176
|
init_state_store();
|
|
11025
11177
|
init_docker();
|
|
@@ -11086,12 +11238,20 @@ async function parseJsonBody(req) {
|
|
|
11086
11238
|
async function handleSettingsGet(cwd) {
|
|
11087
11239
|
return jsonLoadResponse(() => loadAppSettingsState(cwd), "state", "failed to load settings state");
|
|
11088
11240
|
}
|
|
11089
|
-
async function handleSettingsPatch(cwd, req) {
|
|
11241
|
+
async function handleSettingsPatch(cwd, req, onChange) {
|
|
11090
11242
|
const body = await parseJsonBody(req);
|
|
11091
11243
|
if (body instanceof Response)
|
|
11092
11244
|
return body;
|
|
11093
11245
|
try {
|
|
11094
|
-
|
|
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);
|
|
11095
11255
|
} catch (err) {
|
|
11096
11256
|
const message = err instanceof Error ? err.message : String(err);
|
|
11097
11257
|
if (message === "settings state too large")
|
|
@@ -11117,12 +11277,12 @@ async function handleViewPatch(cwd, req) {
|
|
|
11117
11277
|
return textError("failed to save view state", 500);
|
|
11118
11278
|
}
|
|
11119
11279
|
}
|
|
11120
|
-
async function handleStateRoute(req, url, cwd, sideEffectAllowed) {
|
|
11280
|
+
async function handleStateRoute(req, url, cwd, sideEffectAllowed, options = {}) {
|
|
11121
11281
|
return dispatchRoutes(req, url, {
|
|
11122
11282
|
"/_state/settings": {
|
|
11123
11283
|
methods: ["GET", "PATCH"],
|
|
11124
11284
|
sideEffect: (method) => method !== "GET",
|
|
11125
|
-
handler: () => req.method === "GET" ? handleSettingsGet(cwd) : handleSettingsPatch(cwd, req)
|
|
11285
|
+
handler: () => req.method === "GET" ? handleSettingsGet(cwd) : handleSettingsPatch(cwd, req, options.onSettingsChange)
|
|
11126
11286
|
},
|
|
11127
11287
|
"/_state/view": {
|
|
11128
11288
|
methods: ["GET", "PATCH"],
|
|
@@ -11218,16 +11378,30 @@ Examples:
|
|
|
11218
11378
|
}
|
|
11219
11379
|
if (rest.length)
|
|
11220
11380
|
cliArgs = rest;
|
|
11221
|
-
|
|
11222
|
-
const configScopeExcludeNames = loadProjectConfigScopeExcludeNames();
|
|
11223
|
-
uploadDisabledByConfig = loadProjectConfigUploadDisabled();
|
|
11381
|
+
warnIfLegacyConfigPresent();
|
|
11224
11382
|
if (scopeOmitDirCliOverride) {
|
|
11225
11383
|
scopeOmitDirNames = scopeOmitDirCliOverride;
|
|
11226
|
-
} else if (configScopeOmitDirs) {
|
|
11227
|
-
scopeOmitDirNames = configScopeOmitDirs;
|
|
11228
11384
|
}
|
|
11229
|
-
|
|
11230
|
-
|
|
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;
|
|
11231
11405
|
}
|
|
11232
11406
|
function json2(data, init = {}) {
|
|
11233
11407
|
return new Response(JSON.stringify(data), {
|
|
@@ -11529,45 +11703,6 @@ function parseScopeExcludeNamesQuery(value) {
|
|
|
11529
11703
|
}
|
|
11530
11704
|
return normalizeScopeExcludeNames(names);
|
|
11531
11705
|
}
|
|
11532
|
-
function loadProjectConfig() {
|
|
11533
|
-
const full = join13(cwd, ".code-viewer.json");
|
|
11534
|
-
if (!existsSync6(full))
|
|
11535
|
-
return null;
|
|
11536
|
-
let realCwd;
|
|
11537
|
-
let realConfig;
|
|
11538
|
-
try {
|
|
11539
|
-
realCwd = realpathSync4(cwd);
|
|
11540
|
-
realConfig = realpathSync4(full);
|
|
11541
|
-
} catch {
|
|
11542
|
-
return null;
|
|
11543
|
-
}
|
|
11544
|
-
if (dirname3(realConfig) !== realCwd || basename3(realConfig) !== ".code-viewer.json")
|
|
11545
|
-
return null;
|
|
11546
|
-
try {
|
|
11547
|
-
const parsed = JSON.parse(readFileSync4(realConfig, "utf8"));
|
|
11548
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && "version" in parsed && parsed.version !== 1)
|
|
11549
|
-
return null;
|
|
11550
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
11551
|
-
} catch {
|
|
11552
|
-
return null;
|
|
11553
|
-
}
|
|
11554
|
-
}
|
|
11555
|
-
function loadProjectConfigUploadDisabled() {
|
|
11556
|
-
const config = loadProjectConfig();
|
|
11557
|
-
return config?.upload?.enabled === false;
|
|
11558
|
-
}
|
|
11559
|
-
function loadProjectConfigScopeOmitDirs() {
|
|
11560
|
-
const config = loadProjectConfig();
|
|
11561
|
-
if (!config?.scope || !Array.isArray(config.scope.omitDirs))
|
|
11562
|
-
return null;
|
|
11563
|
-
return normalizeScopeOmitDirNames(config.scope.omitDirs);
|
|
11564
|
-
}
|
|
11565
|
-
function loadProjectConfigScopeExcludeNames() {
|
|
11566
|
-
const config = loadProjectConfig();
|
|
11567
|
-
if (!config?.scope || !Array.isArray(config.scope.excludeNames))
|
|
11568
|
-
return null;
|
|
11569
|
-
return normalizeScopeExcludeNames(config.scope.excludeNames);
|
|
11570
|
-
}
|
|
11571
11706
|
function scopeOmitDirNamesFromQuery(url) {
|
|
11572
11707
|
if (!url.searchParams.has("omit_dirs"))
|
|
11573
11708
|
return scopeOmitDirNames;
|
|
@@ -11735,7 +11870,7 @@ function handleTree(url) {
|
|
|
11735
11870
|
branch: currentBranch(cwd) || undefined,
|
|
11736
11871
|
entries: recursive ? entries : entries.map((entry) => attachTreeEntryMetadata(target, entry)),
|
|
11737
11872
|
readme: readReadme(target, path),
|
|
11738
|
-
upload_enabled:
|
|
11873
|
+
upload_enabled: uploadEnabled && (target === "worktree" || target === "")
|
|
11739
11874
|
});
|
|
11740
11875
|
}
|
|
11741
11876
|
function handleSettings() {
|
|
@@ -12297,8 +12432,8 @@ function uploadOpenFlags() {
|
|
|
12297
12432
|
return constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW || 0);
|
|
12298
12433
|
}
|
|
12299
12434
|
async function handleUploadFiles(req) {
|
|
12300
|
-
if (
|
|
12301
|
-
return text("upload disabled by
|
|
12435
|
+
if (!uploadEnabled)
|
|
12436
|
+
return text("upload disabled by viewer settings", 403);
|
|
12302
12437
|
if (req.method !== "POST")
|
|
12303
12438
|
return text("method not allowed", 405);
|
|
12304
12439
|
if (!sideEffectRequestAllowed(req))
|
|
@@ -12851,9 +12986,6 @@ async function handleAnnotations(req) {
|
|
|
12851
12986
|
}
|
|
12852
12987
|
return text("invalid action", 400);
|
|
12853
12988
|
}
|
|
12854
|
-
function isCodeViewerInternalPath(path) {
|
|
12855
|
-
return path.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
|
|
12856
|
-
}
|
|
12857
12989
|
function sendSse(event, data = "tick") {
|
|
12858
12990
|
const payload = enc.encode(`event: ${event}
|
|
12859
12991
|
data: ${data}
|
|
@@ -12901,7 +13033,7 @@ async function shutdown(exitCode = 0) {
|
|
|
12901
13033
|
}
|
|
12902
13034
|
process.exit(exitCode);
|
|
12903
13035
|
}
|
|
12904
|
-
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;
|
|
12905
13037
|
var init_preview = __esm(async () => {
|
|
12906
13038
|
init_routes();
|
|
12907
13039
|
init_annotations();
|
|
@@ -12913,6 +13045,7 @@ var init_preview = __esm(async () => {
|
|
|
12913
13045
|
init_runtime();
|
|
12914
13046
|
init_search();
|
|
12915
13047
|
init_server_registry();
|
|
13048
|
+
init_state_store();
|
|
12916
13049
|
init_worktree_watcher();
|
|
12917
13050
|
WEB_ROOT = join13(ROOT, "web");
|
|
12918
13051
|
VERSION = JSON.parse(readFileSync4(join13(ROOT, "package.json"), "utf8")).version;
|
|
@@ -12972,7 +13105,9 @@ var init_preview = __esm(async () => {
|
|
|
12972
13105
|
lineIndexCache = new Map;
|
|
12973
13106
|
blobLineIndexCache = new Map;
|
|
12974
13107
|
blobBytesCache = new Map;
|
|
13108
|
+
isCodeViewerInternalPath = isToolInternalPath;
|
|
12975
13109
|
parseCli();
|
|
13110
|
+
applyPersistedSettings(await loadAppSettingsState(cwd));
|
|
12976
13111
|
server = await startServer({
|
|
12977
13112
|
hostname: "127.0.0.1",
|
|
12978
13113
|
port: listenPort,
|
|
@@ -13021,7 +13156,7 @@ var init_preview = __esm(async () => {
|
|
|
13021
13156
|
}
|
|
13022
13157
|
if (url.pathname.startsWith("/_state/")) {
|
|
13023
13158
|
const { handleStateRoute: handleStateRoute2 } = await Promise.resolve().then(() => (init_state_route(), exports_state_route));
|
|
13024
|
-
const stateResponse = await handleStateRoute2(req, url, cwd, sideEffectRequestAllowed);
|
|
13159
|
+
const stateResponse = await handleStateRoute2(req, url, cwd, sideEffectRequestAllowed, { onSettingsChange: applyPersistedSettings });
|
|
13025
13160
|
if (stateResponse)
|
|
13026
13161
|
return stateResponse;
|
|
13027
13162
|
}
|
|
@@ -13046,6 +13181,12 @@ var init_preview = __esm(async () => {
|
|
|
13046
13181
|
data: ok
|
|
13047
13182
|
|
|
13048
13183
|
`));
|
|
13184
|
+
if (watchLimitReached !== null) {
|
|
13185
|
+
controller.enqueue(enc.encode(`event: watch-limit
|
|
13186
|
+
data: ${watchLimitReached}
|
|
13187
|
+
|
|
13188
|
+
`));
|
|
13189
|
+
}
|
|
13049
13190
|
keepalive = setInterval(() => {
|
|
13050
13191
|
try {
|
|
13051
13192
|
controller.enqueue(enc.encode(`: ping
|
|
@@ -13115,6 +13256,10 @@ data: ok
|
|
|
13115
13256
|
initialScanMode: "async",
|
|
13116
13257
|
maxWatchedDirectories: worktreeWatchDirectoryLimitFromEnv(),
|
|
13117
13258
|
onUpdate: triggerUpdate,
|
|
13259
|
+
onWatchLimit: (limit) => {
|
|
13260
|
+
watchLimitReached = limit;
|
|
13261
|
+
sendSse("watch-limit", String(limit));
|
|
13262
|
+
},
|
|
13118
13263
|
onError: (error) => {
|
|
13119
13264
|
const message = error instanceof Error ? error.message : String(error);
|
|
13120
13265
|
console.warn(`code-viewer worktree watch skipped: ${message}`);
|