@youtyan/code-viewer 0.9.1 → 0.9.2

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