@dimina-kit/devtools 0.4.0-dev.20260706154125 → 0.4.0-dev.20260707070110

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.
@@ -827,7 +827,7 @@ function registerProjectFsIpc(ctx) {
827
827
  // src/main/app/app.ts
828
828
  import { app as app15, BrowserWindow as BrowserWindow8, nativeImage, session as session4 } from "electron";
829
829
  import fs12 from "fs";
830
- import path23 from "path";
830
+ import path24 from "path";
831
831
 
832
832
  // src/main/utils/paths.ts
833
833
  import fs3 from "fs";
@@ -3043,8 +3043,8 @@ function createHostToolbarView(ctx, reconciler, deps) {
3043
3043
  hide() {
3044
3044
  hideHostToolbar();
3045
3045
  },
3046
- setPreloadPath(path24) {
3047
- hostToolbarPreloadOverride = path24;
3046
+ setPreloadPath(path25) {
3047
+ hostToolbarPreloadOverride = path25;
3048
3048
  },
3049
3049
  setHeightMode(mode) {
3050
3050
  if (mode !== "auto" && !(Number.isFinite(mode.fixed) && mode.fixed >= 0)) {
@@ -10227,13 +10227,13 @@ function createRenderInspector(options = {}) {
10227
10227
  import { toDisposable as toDisposable6 } from "@dimina-kit/electron-deck/main";
10228
10228
 
10229
10229
  // src/main/services/simulator-temp-files/store.ts
10230
- function registerTempFile(store, path24, mime, bytes) {
10230
+ function registerTempFile(store, path25, mime, bytes) {
10231
10231
  const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
10232
- store.delete(path24);
10233
- store.set(path24, { bytes: buf, mime });
10232
+ store.delete(path25);
10233
+ store.set(path25, { bytes: buf, mime });
10234
10234
  }
10235
- function revokeTempFile(store, path24) {
10236
- store.delete(path24);
10235
+ function revokeTempFile(store, path25) {
10236
+ store.delete(path25);
10237
10237
  }
10238
10238
  function revokeAllTempFiles(store) {
10239
10239
  store.clear();
@@ -10661,10 +10661,10 @@ function setupSimulatorTempFiles(simSession) {
10661
10661
  const store = /* @__PURE__ */ new Map();
10662
10662
  const pendingWaiters = /* @__PURE__ */ new Map();
10663
10663
  let disposed = false;
10664
- function drainWaiters(path24) {
10665
- const list = pendingWaiters.get(path24);
10664
+ function drainWaiters(path25) {
10665
+ const list = pendingWaiters.get(path25);
10666
10666
  if (!list) return;
10667
- pendingWaiters.delete(path24);
10667
+ pendingWaiters.delete(path25);
10668
10668
  for (const fn of list) fn();
10669
10669
  }
10670
10670
  function drainAllWaiters() {
@@ -10677,15 +10677,15 @@ function setupSimulatorTempFiles(simSession) {
10677
10677
  const registry = new IpcRegistry(simulatorOnlyPolicy);
10678
10678
  registry.on("simulator:temp-file:write", (_event, payload) => {
10679
10679
  if (disposed) return;
10680
- const { path: path24, mime, bytes } = payload;
10681
- registerTempFile(store, path24, mime, bytes);
10680
+ const { path: path25, mime, bytes } = payload;
10681
+ registerTempFile(store, path25, mime, bytes);
10682
10682
  enforceStoreCap(store);
10683
- drainWaiters(path24);
10683
+ drainWaiters(path25);
10684
10684
  });
10685
10685
  registry.on("simulator:temp-file:revoke", (_event, payload) => {
10686
10686
  if (disposed) return;
10687
- const { path: path24 } = payload;
10688
- revokeTempFile(store, path24);
10687
+ const { path: path25 } = payload;
10688
+ revokeTempFile(store, path25);
10689
10689
  });
10690
10690
  registry.on("simulator:temp-file:revoke-all", () => {
10691
10691
  if (disposed) return;
@@ -10829,9 +10829,97 @@ function setupSimulatorSessionPolicy() {
10829
10829
 
10830
10830
  // src/main/services/workbench-coi-server.ts
10831
10831
  import http2 from "node:http";
10832
- import nodeFs2 from "node:fs";
10832
+ import nodeFs3 from "node:fs";
10833
10833
  import fs11 from "node:fs/promises";
10834
+ import path23 from "node:path";
10835
+
10836
+ // src/main/services/fs-watch-sse.ts
10837
+ import nodeFs2 from "node:fs";
10834
10838
  import path22 from "node:path";
10839
+
10840
+ // src/main/services/http-json.ts
10841
+ function jsonRes(res, code, obj) {
10842
+ const body = Buffer.from(JSON.stringify(obj));
10843
+ res.writeHead(code, { "Content-Type": "application/json; charset=utf-8", "Content-Length": body.length });
10844
+ res.end(body);
10845
+ }
10846
+
10847
+ // src/main/services/fs-watch-sse.ts
10848
+ var WATCH_DEBOUNCE_MS = 80;
10849
+ function shouldReportWatchPath(rel) {
10850
+ if (rel === "" || rel.startsWith("..") || path22.isAbsolute(rel)) return false;
10851
+ return !rel.split("/").some((segment) => SKIP_DIRS.has(segment));
10852
+ }
10853
+ function handleFsWatchRequest(req, res, getProjectRoot) {
10854
+ const watchedRoot = getProjectRoot();
10855
+ if (!watchedRoot) {
10856
+ jsonRes(res, 409, { error: "No active project", code: "ENOACTIVE" });
10857
+ return;
10858
+ }
10859
+ let watcher;
10860
+ try {
10861
+ watcher = nodeFs2.watch(watchedRoot, { recursive: true }, onChange);
10862
+ } catch (e) {
10863
+ jsonRes(res, 500, { error: String(e) });
10864
+ return;
10865
+ }
10866
+ res.writeHead(200, {
10867
+ "Content-Type": "text/event-stream; charset=utf-8",
10868
+ "Cache-Control": "no-store",
10869
+ Connection: "keep-alive"
10870
+ });
10871
+ let pending = /* @__PURE__ */ new Set();
10872
+ let debounceTimer = null;
10873
+ let liveCheck = null;
10874
+ let closed = false;
10875
+ function cleanup() {
10876
+ if (closed) return;
10877
+ closed = true;
10878
+ if (debounceTimer) clearTimeout(debounceTimer);
10879
+ if (liveCheck) clearInterval(liveCheck);
10880
+ watcher.close();
10881
+ }
10882
+ function flush() {
10883
+ if (closed || pending.size === 0) return;
10884
+ const paths = [...pending];
10885
+ pending = /* @__PURE__ */ new Set();
10886
+ res.write(`data: ${JSON.stringify({ paths })}
10887
+
10888
+ `);
10889
+ }
10890
+ function onChange(_event, filename) {
10891
+ const rel = filename ? filename.toString().split(path22.sep).join("/") : ".";
10892
+ if (rel !== "." && !shouldReportWatchPath(rel)) return;
10893
+ pending.add(rel);
10894
+ if (!debounceTimer) {
10895
+ debounceTimer = setTimeout(() => {
10896
+ debounceTimer = null;
10897
+ flush();
10898
+ }, WATCH_DEBOUNCE_MS);
10899
+ }
10900
+ }
10901
+ watcher.on("error", () => {
10902
+ if (closed) return;
10903
+ res.write(`data: ${JSON.stringify({ watcherDead: true })}
10904
+
10905
+ `);
10906
+ cleanup();
10907
+ res.end();
10908
+ });
10909
+ liveCheck = setInterval(() => {
10910
+ if (closed) return;
10911
+ if (getProjectRoot() !== watchedRoot) {
10912
+ res.write(`data: ${JSON.stringify({ watcherDead: true })}
10913
+
10914
+ `);
10915
+ cleanup();
10916
+ res.end();
10917
+ }
10918
+ }, WATCH_DEBOUNCE_MS);
10919
+ req.on("close", cleanup);
10920
+ }
10921
+
10922
+ // src/main/services/workbench-coi-server.ts
10835
10923
  var MAX_FS_WRITE_BYTES = 32 * 1024 * 1024;
10836
10924
  var MIME = {
10837
10925
  ".html": "text/html; charset=utf-8",
@@ -10855,8 +10943,8 @@ function setIsolationHeaders(res) {
10855
10943
  }
10856
10944
  function containedStaticPath(root, pathname) {
10857
10945
  const rel = pathname.replace(/^\/+/, "") || "index.html";
10858
- const resolved = path22.resolve(root, rel);
10859
- if (resolved !== root && !resolved.startsWith(root + path22.sep)) return null;
10946
+ const resolved = path23.resolve(root, rel);
10947
+ if (resolved !== root && !resolved.startsWith(root + path23.sep)) return null;
10860
10948
  return resolved;
10861
10949
  }
10862
10950
  function serveStaticFile(res, root, pathname) {
@@ -10867,23 +10955,23 @@ function serveStaticFile(res, root, pathname) {
10867
10955
  return;
10868
10956
  }
10869
10957
  Promise.all([fs11.realpath(root), fs11.realpath(candidate)]).then(([realRoot, realFile]) => {
10870
- if (realFile !== realRoot && !realFile.startsWith(realRoot + path22.sep)) {
10958
+ if (realFile !== realRoot && !realFile.startsWith(realRoot + path23.sep)) {
10871
10959
  res.writeHead(403);
10872
10960
  res.end("Forbidden");
10873
10961
  return;
10874
10962
  }
10875
- nodeFs2.stat(realFile, (err2, stat) => {
10963
+ nodeFs3.stat(realFile, (err2, stat) => {
10876
10964
  if (err2 || !stat.isFile()) {
10877
10965
  res.writeHead(404);
10878
10966
  res.end("Not Found");
10879
10967
  return;
10880
10968
  }
10881
10969
  res.writeHead(200, {
10882
- "Content-Type": MIME[path22.extname(realFile)] ?? "application/octet-stream",
10970
+ "Content-Type": MIME[path23.extname(realFile)] ?? "application/octet-stream",
10883
10971
  "Content-Length": stat.size,
10884
10972
  "Cache-Control": "no-store"
10885
10973
  });
10886
- nodeFs2.createReadStream(realFile).pipe(res);
10974
+ nodeFs3.createReadStream(realFile).pipe(res);
10887
10975
  });
10888
10976
  }).catch(() => {
10889
10977
  if (!res.headersSent) {
@@ -10892,11 +10980,6 @@ function serveStaticFile(res, root, pathname) {
10892
10980
  }
10893
10981
  });
10894
10982
  }
10895
- function jsonRes(res, code, obj) {
10896
- const body = Buffer.from(JSON.stringify(obj));
10897
- res.writeHead(code, { "Content-Type": "application/json; charset=utf-8", "Content-Length": body.length });
10898
- res.end(body);
10899
- }
10900
10983
  var BodyTooLargeError = class extends Error {
10901
10984
  constructor() {
10902
10985
  super("request body exceeds limit");
@@ -10952,6 +11035,7 @@ function fsErrorStatus(e) {
10952
11035
  if (code === "ENOACTIVE") return 409;
10953
11036
  if (code === "EACCES" || code === "EINVAL") return 403;
10954
11037
  if (code === "ENOENT") return 404;
11038
+ if (code === "EISDIR") return 404;
10955
11039
  return 500;
10956
11040
  }
10957
11041
  async function fsStat(ctx) {
@@ -10960,7 +11044,8 @@ async function fsStat(ctx) {
10960
11044
  }
10961
11045
  async function fsReaddir(ctx) {
10962
11046
  const entries = await readdirWithin(ctx.projectRoot, ctx.rel);
10963
- jsonRes(ctx.res, 200, entries.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
11047
+ const visible = entries.filter((e) => !(e.isDirectory() && SKIP_DIRS.has(e.name)));
11048
+ jsonRes(ctx.res, 200, visible.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
10964
11049
  }
10965
11050
  async function fsRead(ctx) {
10966
11051
  const buf = await readFileBufferWithin(ctx.projectRoot, ctx.rel);
@@ -11032,7 +11117,7 @@ async function listFilesRecursive(dir, base = "") {
11032
11117
  for (const e of entries) {
11033
11118
  const rel = base ? `${base}/${e.name}` : e.name;
11034
11119
  if (e.isDirectory()) {
11035
- out.push(...await listFilesRecursive(path22.join(dir, e.name), rel));
11120
+ out.push(...await listFilesRecursive(path23.join(dir, e.name), rel));
11036
11121
  } else if (e.isFile()) {
11037
11122
  out.push(rel);
11038
11123
  }
@@ -11049,9 +11134,9 @@ async function readContributedExtensions(extensionsDir) {
11049
11134
  }
11050
11135
  for (const e of entries) {
11051
11136
  if (!e.isDirectory()) continue;
11052
- const extDir = path22.join(extensionsDir, e.name);
11137
+ const extDir = path23.join(extensionsDir, e.name);
11053
11138
  try {
11054
- const pkgRaw = await fs11.readFile(path22.join(extDir, "package.json"), "utf8");
11139
+ const pkgRaw = await fs11.readFile(path23.join(extDir, "package.json"), "utf8");
11055
11140
  const files = await listFilesRecursive(extDir);
11056
11141
  out.push({ dir: e.name, packageJson: JSON.parse(pkgRaw), files });
11057
11142
  } catch {
@@ -11060,11 +11145,15 @@ async function readContributedExtensions(extensionsDir) {
11060
11145
  return out;
11061
11146
  }
11062
11147
  async function startWorkbenchCoiServer(options) {
11063
- const root = path22.resolve(options.rootDir);
11064
- const contribRoot = options.extensionsDir ? path22.resolve(options.extensionsDir) : null;
11148
+ const root = path23.resolve(options.rootDir);
11149
+ const contribRoot = options.extensionsDir ? path23.resolve(options.extensionsDir) : null;
11065
11150
  const server = http2.createServer((req, res) => {
11066
11151
  setIsolationHeaders(res);
11067
11152
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
11153
+ if (url.pathname === "/__fs/watch") {
11154
+ handleFsWatchRequest(req, res, options.getProjectRoot);
11155
+ return;
11156
+ }
11068
11157
  if (url.pathname.startsWith("/__fs/")) {
11069
11158
  handleFsRequest(req, res, options.getProjectRoot(), url).catch((e) => {
11070
11159
  if (!res.headersSent) jsonRes(res, 500, { error: String(e) });
@@ -11279,7 +11368,7 @@ async function disposeContext(ctx) {
11279
11368
  function createConfiguredMainWindow(config, rendererDir2) {
11280
11369
  const mainWindow = createMainWindow({
11281
11370
  title: config.appName ?? "Dimina DevTools",
11282
- indexHtml: path23.join(rendererDir2, "entries/main/index.html"),
11371
+ indexHtml: path24.join(rendererDir2, "entries/main/index.html"),
11283
11372
  width: config.window?.width,
11284
11373
  height: config.window?.height,
11285
11374
  minWidth: config.window?.minWidth,
@@ -11558,8 +11647,8 @@ async function createDevtoolsRuntime(config = {}) {
11558
11647
  bridge: context.bridge
11559
11648
  }));
11560
11649
  }
11561
- const bundleDir = config.editorViewConfig?.bundleDir ?? path23.join(devtoolsPackageRoot, "dist/vscode-workbench");
11562
- if (!fs12.existsSync(path23.join(bundleDir, "index.html"))) {
11650
+ const bundleDir = config.editorViewConfig?.bundleDir ?? path24.join(devtoolsPackageRoot, "dist/vscode-workbench");
11651
+ if (!fs12.existsSync(path24.join(bundleDir, "index.html"))) {
11563
11652
  console.warn(
11564
11653
  `[workbench] editor bundle not found at ${bundleDir} \u2014 skipping embedded editor assembly`
11565
11654
  );
@@ -1,5 +1,9 @@
1
1
  import type { WorkbenchContext } from '../services/workbench-context.js';
2
2
  import type { Disposable } from '@dimina-kit/electron-deck/main';
3
+ /** Dependency/VCS/build directories invisible to every project-tree consumer:
4
+ * IPC `listFiles`, the `/__fs/readdir` bridge (so the workbench mirror and the
5
+ * WAL ledger seed never walk into them), and the `/__fs/watch` change stream. */
6
+ export declare const SKIP_DIRS: ReadonlySet<string>;
3
7
  /**
4
8
  * Synchronous, string-level guard: reject empty root (ENOACTIVE), NUL bytes
5
9
  * and empty paths (EINVAL), and lexical `..` escapes (EACCES) by resolving
@@ -49,7 +49,10 @@ import { validate } from '../utils/ipc-schema.js';
49
49
  import { ProjectFsChannel } from '../../shared/ipc-channels.js';
50
50
  /** Cap for `listFiles` — huge monorepos would otherwise stall the renderer. */
51
51
  const LIST_FILE_LIMIT = 5000;
52
- const SKIP_DIRS = new Set([
52
+ /** Dependency/VCS/build directories invisible to every project-tree consumer:
53
+ * IPC `listFiles`, the `/__fs/readdir` bridge (so the workbench mirror and the
54
+ * WAL ledger seed never walk into them), and the `/__fs/watch` change stream. */
55
+ export const SKIP_DIRS = new Set([
53
56
  'node_modules', '.git', '.svn', '.hg', 'dist', 'build',
54
57
  '.next', '.cache', '.turbo', '.parcel-cache', '.nuxt', '.output', '.vite',
55
58
  ]);
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `GET /__fs/watch`: SSE stream of the active project root's disk changes, for
3
+ * the editor's memfs↔disk sync engine (devtools-fs-core-feasibility.md §7).
4
+ * Read-only — it adds no write surface, which is why workbench-coi-server.ts
5
+ * dispatches it before (and outside) the mutating-action guard of the generic
6
+ * `/__fs/*` handler. Split into its own module purely to keep each file within
7
+ * the repo size gate; the COI server is its only consumer.
8
+ *
9
+ * One `fs.watch(root, {recursive:true})` per connection (darwin supports
10
+ * `recursive`), changes batched over an 80ms window and pushed as
11
+ * `data: {"paths":["rel", ...]}` with POSIX-separated root-relative paths.
12
+ * `getProjectRoot` (not a resolved string) is threaded through so a project
13
+ * close/switch mid-stream is detected by re-polling the SAME source
14
+ * `handleFsRequest` uses — no separate close event is invented. Watcher errors
15
+ * (e.g. EMFILE) push `data: {"watcherDead":true}` then end the stream; no
16
+ * active project at connect time is a 409, matching the ENOACTIVE semantics of
17
+ * every other `/__fs` action.
18
+ */
19
+ import type http from 'node:http';
20
+ /** `false` for build/vcs/dependency churn, the empty string, and any path that
21
+ * (defensively — `fs.watch`'s filename is always root-relative) escapes the root.
22
+ * Matches SKIP_DIRS as a path SEGMENT at any depth — the same set `/__fs/readdir`
23
+ * omits, so the watcher never reports paths the editor mirror cannot see (a
24
+ * prefix match would let `packages/x/node_modules/...` through). The one
25
+ * accepted edge: a plain FILE named exactly like a skip dir (e.g. `dist`)
26
+ * is also unreported, though readdir would list it. */
27
+ declare function shouldReportWatchPath(rel: string): boolean;
28
+ export declare const __testing: {
29
+ shouldReportWatchPath: typeof shouldReportWatchPath;
30
+ };
31
+ export declare function handleFsWatchRequest(req: http.IncomingMessage, res: http.ServerResponse, getProjectRoot: () => string): void;
32
+ export {};
33
+ //# sourceMappingURL=fs-watch-sse.d.ts.map
@@ -0,0 +1,99 @@
1
+ import nodeFs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { SKIP_DIRS } from '../ipc/project-fs.js';
4
+ import { jsonRes } from './http-json.js';
5
+ /** Debounce window for batching change notifications (ms). */
6
+ const WATCH_DEBOUNCE_MS = 80;
7
+ /** `false` for build/vcs/dependency churn, the empty string, and any path that
8
+ * (defensively — `fs.watch`'s filename is always root-relative) escapes the root.
9
+ * Matches SKIP_DIRS as a path SEGMENT at any depth — the same set `/__fs/readdir`
10
+ * omits, so the watcher never reports paths the editor mirror cannot see (a
11
+ * prefix match would let `packages/x/node_modules/...` through). The one
12
+ * accepted edge: a plain FILE named exactly like a skip dir (e.g. `dist`)
13
+ * is also unreported, though readdir would list it. */
14
+ function shouldReportWatchPath(rel) {
15
+ if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel))
16
+ return false;
17
+ return !rel.split('/').some((segment) => SKIP_DIRS.has(segment));
18
+ }
19
+ export const __testing = { shouldReportWatchPath };
20
+ export function handleFsWatchRequest(req, res, getProjectRoot) {
21
+ const watchedRoot = getProjectRoot();
22
+ if (!watchedRoot) {
23
+ jsonRes(res, 409, { error: 'No active project', code: 'ENOACTIVE' });
24
+ return;
25
+ }
26
+ let watcher;
27
+ try {
28
+ watcher = nodeFs.watch(watchedRoot, { recursive: true }, onChange);
29
+ }
30
+ catch (e) {
31
+ jsonRes(res, 500, { error: String(e) });
32
+ return;
33
+ }
34
+ res.writeHead(200, {
35
+ 'Content-Type': 'text/event-stream; charset=utf-8',
36
+ 'Cache-Control': 'no-store',
37
+ Connection: 'keep-alive',
38
+ });
39
+ let pending = new Set();
40
+ let debounceTimer = null;
41
+ let liveCheck = null;
42
+ let closed = false;
43
+ function cleanup() {
44
+ if (closed)
45
+ return;
46
+ closed = true;
47
+ if (debounceTimer)
48
+ clearTimeout(debounceTimer);
49
+ if (liveCheck)
50
+ clearInterval(liveCheck);
51
+ watcher.close();
52
+ }
53
+ function flush() {
54
+ if (closed || pending.size === 0)
55
+ return;
56
+ const paths = [...pending];
57
+ pending = new Set();
58
+ res.write(`data: ${JSON.stringify({ paths })}\n\n`);
59
+ }
60
+ function onChange(_event, filename) {
61
+ // FSEvents overflow delivers a null filename ("something changed,
62
+ // rescan"). Dropping it loses data; report '.' and let the client's
63
+ // watch-batch expansion (wal-audit.ts) reconcile the whole tree.
64
+ const rel = filename ? filename.toString().split(path.sep).join('/') : '.';
65
+ if (rel !== '.' && !shouldReportWatchPath(rel))
66
+ return;
67
+ pending.add(rel);
68
+ if (!debounceTimer) {
69
+ debounceTimer = setTimeout(() => {
70
+ debounceTimer = null;
71
+ flush();
72
+ }, WATCH_DEBOUNCE_MS);
73
+ }
74
+ }
75
+ watcher.on('error', () => {
76
+ if (closed)
77
+ return;
78
+ res.write(`data: ${JSON.stringify({ watcherDead: true })}\n\n`);
79
+ cleanup();
80
+ res.end();
81
+ });
82
+ // The active project root has no dedicated close/switch event to subscribe
83
+ // to; poll the same getter `handleFsRequest` uses, on the debounce cadence,
84
+ // so a closed or switched project tears this stream down promptly. Push
85
+ // `watcherDead` BEFORE ending so the client gets a deterministic
86
+ // end-of-channel signal instead of depending on how its EventSource
87
+ // classifies the reconnect-then-409 that would follow a bare close.
88
+ liveCheck = setInterval(() => {
89
+ if (closed)
90
+ return;
91
+ if (getProjectRoot() !== watchedRoot) {
92
+ res.write(`data: ${JSON.stringify({ watcherDead: true })}\n\n`);
93
+ cleanup();
94
+ res.end();
95
+ }
96
+ }, WATCH_DEBOUNCE_MS);
97
+ req.on('close', cleanup);
98
+ }
99
+ //# sourceMappingURL=fs-watch-sse.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Minimal JSON responder shared by the workbench COI server and its
3
+ * `/__fs/watch` SSE module (fs-watch-sse.ts). Lives in its own module because
4
+ * workbench-coi-server.ts imports fs-watch-sse.ts — either file owning it
5
+ * would force an import cycle for the other.
6
+ */
7
+ import type http from 'node:http';
8
+ export declare function jsonRes(res: http.ServerResponse, code: number, obj: unknown): void;
9
+ //# sourceMappingURL=http-json.d.ts.map
@@ -0,0 +1,6 @@
1
+ export function jsonRes(res, code, obj) {
2
+ const body = Buffer.from(JSON.stringify(obj));
3
+ res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': body.length });
4
+ res.end(body);
5
+ }
6
+ //# sourceMappingURL=http-json.js.map
@@ -26,12 +26,19 @@
26
26
  * method and a same-origin (or origin-less) request, so an arbitrary localhost
27
27
  * page cannot drive a destructive `/__fs` call. GET serves only the read-only
28
28
  * stat/readdir/read actions.
29
+ *
30
+ * `/__fs/watch` is a read-only SSE stream of the active project's disk changes
31
+ * (devtools-fs-core-feasibility.md §7), decoupled from the compile session so
32
+ * the editor's memfs↔disk sync engine has a source that stays live regardless
33
+ * of compile state; see {@link handleFsWatchRequest}.
29
34
  */
30
35
  import http from 'node:http';
31
36
  import nodeFs from 'node:fs';
32
37
  import fs from 'node:fs/promises';
33
38
  import path from 'node:path';
34
- import { readFileBufferWithin, writeFileWithin, statWithin, readdirWithin, mkdirWithin, deleteWithin, renameWithin, } from '../ipc/project-fs.js';
39
+ import { readFileBufferWithin, writeFileWithin, statWithin, readdirWithin, mkdirWithin, deleteWithin, renameWithin, SKIP_DIRS, } from '../ipc/project-fs.js';
40
+ import { handleFsWatchRequest } from './fs-watch-sse.js';
41
+ import { jsonRes } from './http-json.js';
35
42
  /** Max `/__fs/write` body; enough to save large source files without OOM. */
36
43
  const MAX_FS_WRITE_BYTES = 32 * 1024 * 1024;
37
44
  const MIME = {
@@ -103,11 +110,6 @@ function serveStaticFile(res, root, pathname) {
103
110
  res.end('Not Found');
104
111
  } });
105
112
  }
106
- function jsonRes(res, code, obj) {
107
- const body = Buffer.from(JSON.stringify(obj));
108
- res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': body.length });
109
- res.end(body);
110
- }
111
113
  /** Thrown by {@link readBody} when the accumulated body exceeds the cap. */
112
114
  class BodyTooLargeError extends Error {
113
115
  constructor() {
@@ -186,6 +188,12 @@ function fsErrorStatus(e) {
186
188
  return 403;
187
189
  if (code === 'ENOENT')
188
190
  return 404;
191
+ // Reading a path that is (now) a DIRECTORY: as a *file* it does not exist.
192
+ // 404 lets the sync engine's not-found discipline retire the stale ledger
193
+ // FILE record when an external change replaces a file with a same-named
194
+ // directory — a 500 would make it skip forever ("transient failure").
195
+ if (code === 'EISDIR')
196
+ return 404;
189
197
  return 500;
190
198
  }
191
199
  async function fsStat(ctx) {
@@ -194,7 +202,12 @@ async function fsStat(ctx) {
194
202
  }
195
203
  async function fsReaddir(ctx) {
196
204
  const entries = await readdirWithin(ctx.projectRoot, ctx.rel);
197
- jsonRes(ctx.res, 200, entries.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
205
+ // Directories in SKIP_DIRS (node_modules, .git, dist, ...) are omitted at
206
+ // every level: the workbench mirror and the WAL ledger seed walk whatever
207
+ // this returns, so listing them would pull entire dependency trees into the
208
+ // editor memfs and the OPFS ledger. Same-named FILES stay visible.
209
+ const visible = entries.filter((e) => !(e.isDirectory() && SKIP_DIRS.has(e.name)));
210
+ jsonRes(ctx.res, 200, visible.map((e) => [e.name, e.isDirectory() ? 2 : 1]));
198
211
  }
199
212
  async function fsRead(ctx) {
200
213
  const buf = await readFileBufferWithin(ctx.projectRoot, ctx.rel);
@@ -323,6 +336,12 @@ export async function startWorkbenchCoiServer(options) {
323
336
  const server = http.createServer((req, res) => {
324
337
  setIsolationHeaders(res);
325
338
  const url = new URL(req.url ?? '/', 'http://127.0.0.1');
339
+ // Special-cased ahead of the generic `/__fs/*` dispatch: unlike every other
340
+ // action, this is a long-lived SSE stream, not a one-shot JSON response.
341
+ if (url.pathname === '/__fs/watch') {
342
+ handleFsWatchRequest(req, res, options.getProjectRoot);
343
+ return;
344
+ }
326
345
  if (url.pathname.startsWith('/__fs/')) {
327
346
  handleFsRequest(req, res, options.getProjectRoot(), url).catch((e) => {
328
347
  if (!res.headersSent)
@@ -1768,7 +1768,13 @@ var yi = /* @__PURE__ */ new WeakMap(), bi = /* @__PURE__ */ Symbol("_vte"), xi
1768
1768
  r ? (o !== "svg" && wi(r) ? o = "svg" : o !== "mathml" && Ti(r) && (o = "mathml"), i && i.isCE && (i.ce._teleportTargets || (i.ce._teleportTargets = /* @__PURE__ */ new Set())).add(r), n || (b(e, r, a), ji(e, !1))) : process.env.NODE_ENV !== "production" && !n && z("Invalid Teleport target on mount:", r, `(${typeof r})`);
1769
1769
  }, S = (e) => {
1770
1770
  let t = () => {
1771
- yi.get(e) === t && (yi.delete(e), Si(e.props) && (b(e, _(e.el) || n, e.anchor), ji(e, !0)), x(e));
1771
+ if (yi.get(e) === t) {
1772
+ if (yi.delete(e), Si(e.props)) {
1773
+ let t = _(e.el) || n;
1774
+ b(e, t, e.anchor), ji(e, !0);
1775
+ }
1776
+ x(e);
1777
+ }
1772
1778
  };
1773
1779
  yi.set(e, t), Ko(t, a);
1774
1780
  };
@@ -2913,7 +2919,10 @@ function Jo(e, t) {
2913
2919
  }
2914
2920
  }
2915
2921
  }, C = (e, t, n, r, i, a, o, s, c = 0) => {
2916
- for (let l = c; l < e.length; l++) h(null, e[l] = s ? Vs(e[l]) : Bs(e[l]), t, n, r, i, a, o, s);
2922
+ for (let l = c; l < e.length; l++) {
2923
+ let c = e[l] = s ? Vs(e[l]) : Bs(e[l]);
2924
+ h(null, c, t, n, r, i, a, o, s);
2925
+ }
2917
2926
  }, ne = (e, t, n, r, i, o, s) => {
2918
2927
  let c = t.el = e.el;
2919
2928
  process.env.NODE_ENV !== "production" && (c.__vnode = t);
@@ -2936,8 +2945,8 @@ function Jo(e, t) {
2936
2945
  }, r);
2937
2946
  }, re = (e, t, n, r, i, a, o) => {
2938
2947
  for (let s = 0; s < t.length; s++) {
2939
- let c = e[s], l = t[s];
2940
- h(c, l, c.el && (c.type === W || !Os(c, l) || c.shapeFlag & 198) ? d(c.el) : n, null, r, i, a, o, !0);
2948
+ let c = e[s], l = t[s], u = c.el && (c.type === W || !Os(c, l) || c.shapeFlag & 198) ? d(c.el) : n;
2949
+ h(c, l, u, null, r, i, a, o, !0);
2941
2950
  }
2942
2951
  }, w = (e, t, n, r, i) => {
2943
2952
  if (t !== n) {
@@ -6485,7 +6494,10 @@ var Ru = {
6485
6494
  let e = c();
6486
6495
  if (t.value.style = o.indicatorStyle, v.default && v.default()[0].children.length > 0) {
6487
6496
  let e = r.value.querySelector(":first-child");
6488
- e && (m.value = window.getComputedStyle(e).lineHeight || "34px", t.value.style.height = m.value);
6497
+ if (e) {
6498
+ let n = window.getComputedStyle(e);
6499
+ m.value = n.lineHeight || "34px", t.value.style.height = m.value;
6500
+ }
6489
6501
  }
6490
6502
  x = t.value.offsetHeight;
6491
6503
  let s = (e - x) / 2;
@@ -7331,19 +7343,25 @@ var Ru = {
7331
7343
  function be() {
7332
7344
  C &&= (cancelAnimationFrame(C), void 0);
7333
7345
  let e = p.value.getBoundingClientRect();
7334
- t.vertical ? Q("transition", {
7335
- info: n,
7336
- detail: {
7337
- dx: 0,
7338
- dy: te - e.y
7339
- }
7340
- }) : Q("transition", {
7341
- info: n,
7342
- detail: {
7343
- dx: ee - e.x,
7344
- dy: 0
7345
- }
7346
- });
7346
+ if (t.vertical) {
7347
+ let t = te - e.y;
7348
+ Q("transition", {
7349
+ info: n,
7350
+ detail: {
7351
+ dx: 0,
7352
+ dy: t
7353
+ }
7354
+ });
7355
+ } else {
7356
+ let t = ee - e.x;
7357
+ Q("transition", {
7358
+ info: n,
7359
+ detail: {
7360
+ dx: t,
7361
+ dy: 0
7362
+ }
7363
+ });
7364
+ }
7347
7365
  }
7348
7366
  function N() {
7349
7367
  xe(), t.autoplay && i.value > T.value && (ne = setInterval(() => {
@@ -0,0 +1 @@
1
+ import{n as e}from"./index-DyxjKeLl.js";var t=e(((e,t)=>{t.exports={}}));export default t();