@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.
- package/dist/main/index.bundle.js +126 -37
- package/dist/main/ipc/project-fs.d.ts +4 -0
- package/dist/main/ipc/project-fs.js +4 -1
- package/dist/main/services/fs-watch-sse.d.ts +33 -0
- package/dist/main/services/fs-watch-sse.js +99 -0
- package/dist/main/services/http-json.d.ts +9 -0
- package/dist/main/services/http-json.js +6 -0
- package/dist/main/services/workbench-coi-server.js +26 -7
- package/dist/native-host/render/render.js +36 -18
- package/dist/vscode-workbench/assets/__vite-browser-external-CjtF3dcZ.js +1 -0
- package/dist/vscode-workbench/assets/{dist-TpGpmMGi.js → dist-wJUFfrkb.js} +3 -3
- package/dist/vscode-workbench/assets/fs-core-worker-urls-CLvv1Q_0.js +1 -0
- package/dist/vscode-workbench/assets/fs-core.worker-VoMLk-PY.js +1 -0
- package/dist/vscode-workbench/assets/fs-query.worker-fXLkQK9l.js +2 -0
- package/dist/vscode-workbench/assets/{iconv-lite-umd-C9tiCAJa.js → iconv-lite-umd-O-GPtKy4.js} +1 -1
- package/dist/vscode-workbench/assets/{index-DJ1HyMZ7.js → index-DyxjKeLl.js} +433 -433
- package/dist/vscode-workbench/assets/{jschardet-DE1jV513.js → jschardet-BorbxV40.js} +1 -1
- package/dist/vscode-workbench/index.html +1 -1
- package/package.json +4 -4
- package/dist/vscode-workbench/assets/__vite-browser-external-CXi6Kz9W.js +0 -1
|
@@ -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
|
|
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(
|
|
3047
|
-
hostToolbarPreloadOverride =
|
|
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,
|
|
10230
|
+
function registerTempFile(store, path25, mime, bytes) {
|
|
10231
10231
|
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
|
|
10232
|
-
store.delete(
|
|
10233
|
-
store.set(
|
|
10232
|
+
store.delete(path25);
|
|
10233
|
+
store.set(path25, { bytes: buf, mime });
|
|
10234
10234
|
}
|
|
10235
|
-
function revokeTempFile(store,
|
|
10236
|
-
store.delete(
|
|
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(
|
|
10665
|
-
const list = pendingWaiters.get(
|
|
10664
|
+
function drainWaiters(path25) {
|
|
10665
|
+
const list = pendingWaiters.get(path25);
|
|
10666
10666
|
if (!list) return;
|
|
10667
|
-
pendingWaiters.delete(
|
|
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:
|
|
10681
|
-
registerTempFile(store,
|
|
10680
|
+
const { path: path25, mime, bytes } = payload;
|
|
10681
|
+
registerTempFile(store, path25, mime, bytes);
|
|
10682
10682
|
enforceStoreCap(store);
|
|
10683
|
-
drainWaiters(
|
|
10683
|
+
drainWaiters(path25);
|
|
10684
10684
|
});
|
|
10685
10685
|
registry.on("simulator:temp-file:revoke", (_event, payload) => {
|
|
10686
10686
|
if (disposed) return;
|
|
10687
|
-
const { path:
|
|
10688
|
-
revokeTempFile(store,
|
|
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
|
|
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 =
|
|
10859
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
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 +
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
11137
|
+
const extDir = path23.join(extensionsDir, e.name);
|
|
11053
11138
|
try {
|
|
11054
|
-
const pkgRaw = await fs11.readFile(
|
|
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 =
|
|
11064
|
-
const contribRoot = options.extensionsDir ?
|
|
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:
|
|
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 ??
|
|
11562
|
-
if (!fs12.existsSync(
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
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++)
|
|
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,
|
|
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
|
-
|
|
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
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
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();
|