mellos-mapping 0.22.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -4
- package/README.zh-CN.md +26 -4
- package/dist/hook-session-start.mjs +3 -1
- package/dist/mmap.mjs +1 -1
- package/dist/preview.mjs +41 -2
- package/dist/server.mjs +857 -412
- package/dist/store-paths.mjs +29 -1
- package/dist/terminal-worker.mjs +444 -300
- package/dist/watch.mjs +563 -339
- package/dist/web/app.css +106 -2
- package/dist/web/app.js +78 -0
- package/dist/web/index.html +1 -1
- package/dist/web/terminal.css +103 -0
- package/dist/web/terminal.html +1 -1
- package/dist/web/terminal.js +78 -0
- package/dist/web.mjs +314 -67
- package/docs/codex.md +7 -1
- package/docs/map-api.md +148 -0
- package/lib/domain/context.d.ts +11 -0
- package/lib/domain/context.js +27 -0
- package/lib/domain/text.js +11 -0
- package/lib/domain/types.d.ts +3 -0
- package/lib/store/format.js +18 -3
- package/lib/store/project.d.ts +2 -0
- package/lib/store/project.js +29 -0
- package/lib/store/store.d.ts +6 -1
- package/lib/store/store.js +6 -1
- package/lib/store/transaction.d.ts +12 -0
- package/lib/store/transaction.js +91 -0
- package/package.json +4 -2
- package/scripts/codex-register.mjs +1 -1
- package/scripts/mmap.mjs +1 -1
package/dist/terminal-worker.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { PassThrough, Writable } from "node:stream";
|
|
3
3
|
|
|
4
4
|
// src/watch/watch.ts
|
|
5
|
-
import { realpathSync, statSync as statSync2 } from "node:fs";
|
|
6
|
-
import { dirname as
|
|
5
|
+
import { realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
|
|
6
|
+
import { dirname as dirname9, join as join8 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// src/domain/types.ts
|
|
9
9
|
var ok = (value) => ({ ok: true, value });
|
|
@@ -234,6 +234,392 @@ function updateNode(map, input2) {
|
|
|
234
234
|
return ok({ ...map, nodes: map.nodes.map((n) => n.id === input2.id ? updated : n) });
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// src/store/project.ts
|
|
238
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
239
|
+
import { dirname, join, resolve } from "node:path";
|
|
240
|
+
import { homedir, tmpdir } from "node:os";
|
|
241
|
+
function resolveProjectDirectory(cwd, stopAt = [homedir(), tmpdir()]) {
|
|
242
|
+
let start = resolve(cwd);
|
|
243
|
+
try {
|
|
244
|
+
start = realpathSync(start);
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
let dir = start;
|
|
248
|
+
const boundaries = new Set(stopAt.map((path) => {
|
|
249
|
+
try {
|
|
250
|
+
return realpathSync(path);
|
|
251
|
+
} catch {
|
|
252
|
+
return resolve(path);
|
|
253
|
+
}
|
|
254
|
+
}));
|
|
255
|
+
while (true) {
|
|
256
|
+
if (dir !== start && boundaries.has(dir)) return start;
|
|
257
|
+
if (existsSync(join(dir, ".git")) || existsSync(join(dir, ".mellos", "map.json")) || existsSync(join(dir, ".mellos", "pages"))) return dir;
|
|
258
|
+
const parent = dirname(dir);
|
|
259
|
+
if (parent === dir) return start;
|
|
260
|
+
dir = parent;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/store/transaction.ts
|
|
265
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
266
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
267
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
268
|
+
|
|
269
|
+
// src/domain/context.ts
|
|
270
|
+
function sourceError(raw) {
|
|
271
|
+
if (!Array.isArray(raw) || raw.length > 100) return "sources must be an array of at most 100 file references";
|
|
272
|
+
for (const item of raw) {
|
|
273
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return "source must be an object";
|
|
274
|
+
const s = item;
|
|
275
|
+
if (Object.keys(s).some((k) => k !== "path" && k !== "sha256")) return "unknown source field";
|
|
276
|
+
if (typeof s.path !== "string" || s.path.length > 1024 || !s.path || /[\u0000-\u001f\u007f-\u009f\\:]/.test(s.path) || s.path.startsWith("/") || s.path.split("/").some((p) => !p || p === "." || p === "..")) return "source path must be relative to the project, with forward slashes and no traversal";
|
|
277
|
+
if (s.sha256 !== void 0 && (typeof s.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(s.sha256))) return "source sha256 must be a lowercase SHA256 hash";
|
|
278
|
+
}
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
function contextError(raw) {
|
|
282
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return "context must be an object";
|
|
283
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
284
|
+
if (key !== "summary" && key !== "next") return "unknown context field";
|
|
285
|
+
if (typeof value !== "string" || value.length > 2e3 || /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/.test(value)) return "context fields must be text of at most 2000 characters";
|
|
286
|
+
}
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/domain/text.ts
|
|
291
|
+
var NO_CONTROLS = /^[^\u0000-\u001f\u007f-\u009f]*$/;
|
|
292
|
+
var NO_CONTROLS_TEXT = "one line of text; control characters (ESC, newline, tab) are not allowed";
|
|
293
|
+
var NO_CONTROLS_BUT_BREAKS = /^[^\u0000-\u0008\u000b-\u001f\u007f-\u009f]*$/;
|
|
294
|
+
var NO_CONTROLS_BUT_BREAKS_TEXT = "text with optional newlines (\\n) and tabs; other control characters (ESC, BEL, CR) are not allowed";
|
|
295
|
+
function mapTextError(map) {
|
|
296
|
+
if (map.context !== void 0) {
|
|
297
|
+
const error2 = contextError(map.context);
|
|
298
|
+
if (error2) return error2;
|
|
299
|
+
}
|
|
300
|
+
const check = (field, value, multiline = false) => value === void 0 || (multiline ? NO_CONTROLS_BUT_BREAKS : NO_CONTROLS).test(value) ? void 0 : `${field}: ${multiline ? NO_CONTROLS_BUT_BREAKS_TEXT : NO_CONTROLS_TEXT}`;
|
|
301
|
+
let error = check("title", map.title);
|
|
302
|
+
if (error) return error;
|
|
303
|
+
for (const [i, layer] of map.layers.entries()) {
|
|
304
|
+
error = check(`layers[${i}].name`, layer.name);
|
|
305
|
+
if (error) return error;
|
|
306
|
+
}
|
|
307
|
+
for (const name of ["lanes", "groups"]) {
|
|
308
|
+
for (const [i, item] of map[name].entries()) {
|
|
309
|
+
error = check(`${name}[${i}].label`, item.label);
|
|
310
|
+
if (error) return error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
for (const [i, node] of map.nodes.entries()) {
|
|
314
|
+
if (node.sources !== void 0) {
|
|
315
|
+
const error2 = sourceError(node.sources);
|
|
316
|
+
if (error2) return `nodes[${i}]: ${error2}`;
|
|
317
|
+
}
|
|
318
|
+
for (const name of ["label", "evidence", "detail"]) {
|
|
319
|
+
error = check(`nodes[${i}].${name}`, node[name], name !== "label");
|
|
320
|
+
if (error) return error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (const [i, edge] of map.edges.entries()) {
|
|
324
|
+
error = check(`edges[${i}].label`, edge.label);
|
|
325
|
+
if (error) return error;
|
|
326
|
+
}
|
|
327
|
+
return void 0;
|
|
328
|
+
}
|
|
329
|
+
function terminalText(text, multiline = false) {
|
|
330
|
+
return text.replace(multiline ? /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g : /[\u0000-\u001f\u007f-\u009f]/g, "?");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/store/format.ts
|
|
334
|
+
var STATE_FILE_VERSION = 1;
|
|
335
|
+
function makePageId(raw) {
|
|
336
|
+
return ID_RULE.test(raw) ? ok(raw) : err({ kind: "invalid-id", raw, rule: ID_RULE_TEXT });
|
|
337
|
+
}
|
|
338
|
+
function describeStoreError(e) {
|
|
339
|
+
switch (e.kind) {
|
|
340
|
+
case "not-found":
|
|
341
|
+
return `no map file at ${e.path}`;
|
|
342
|
+
case "malformed-json":
|
|
343
|
+
return `map file ${e.path} is not valid JSON: ${e.detail}`;
|
|
344
|
+
case "bad-shape":
|
|
345
|
+
return `map file ${e.path} has an unexpected shape: ${e.detail}`;
|
|
346
|
+
case "invariant-violation":
|
|
347
|
+
return `map file ${e.path} violates a structural invariant: ${describeMapError(e.violation)}`;
|
|
348
|
+
case "save-failed":
|
|
349
|
+
return `could not write ${e.path}: ${e.detail}`;
|
|
350
|
+
case "delete-failed":
|
|
351
|
+
return `could not delete ${e.path}: ${e.detail}`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function isRecord(v) {
|
|
355
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
356
|
+
}
|
|
357
|
+
function describeValue(v) {
|
|
358
|
+
if (v === void 0) return "missing";
|
|
359
|
+
if (v === null) return "null";
|
|
360
|
+
if (Array.isArray(v)) return "an array";
|
|
361
|
+
return `a ${typeof v}`;
|
|
362
|
+
}
|
|
363
|
+
function badShape(path, where, expected, got) {
|
|
364
|
+
return err({ kind: "bad-shape", path, detail: `${where} is ${describeValue(got)}, expected ${expected}` });
|
|
365
|
+
}
|
|
366
|
+
function arrayField(raw, key, path, presence) {
|
|
367
|
+
const v = raw[key];
|
|
368
|
+
if (Array.isArray(v)) return ok(v);
|
|
369
|
+
if (v === void 0 && presence === "optional") return ok([]);
|
|
370
|
+
return badShape(path, `"${key}"`, "an array", v);
|
|
371
|
+
}
|
|
372
|
+
function requiredString(rec, key, where, path) {
|
|
373
|
+
const v = rec[key];
|
|
374
|
+
return typeof v === "string" ? ok(v) : badShape(path, `${where}.${key}`, "a string", v);
|
|
375
|
+
}
|
|
376
|
+
function optionalString(rec, key, where, path) {
|
|
377
|
+
const v = rec[key];
|
|
378
|
+
if (v === void 0) return ok(void 0);
|
|
379
|
+
return typeof v === "string" ? ok(v) : badShape(path, `${where}.${key}`, "a string", v);
|
|
380
|
+
}
|
|
381
|
+
function parseMap(raw, path) {
|
|
382
|
+
if (!isRecord(raw)) return err({ kind: "bad-shape", path, detail: "root is not an object" });
|
|
383
|
+
if (raw["version"] !== STATE_FILE_VERSION && raw["version"] !== 2) {
|
|
384
|
+
return err({ kind: "bad-shape", path, detail: `version is ${String(raw["version"])}, expected ${STATE_FILE_VERSION} or 2` });
|
|
385
|
+
}
|
|
386
|
+
const layers = arrayField(raw, "layers", path, "required");
|
|
387
|
+
if (!layers.ok) return layers;
|
|
388
|
+
const nodes = arrayField(raw, "nodes", path, "required");
|
|
389
|
+
if (!nodes.ok) return nodes;
|
|
390
|
+
const edges = arrayField(raw, "edges", path, "required");
|
|
391
|
+
if (!edges.ok) return edges;
|
|
392
|
+
const lanes = arrayField(raw, "lanes", path, "optional");
|
|
393
|
+
if (!lanes.ok) return lanes;
|
|
394
|
+
const groups = arrayField(raw, "groups", path, "optional");
|
|
395
|
+
if (!groups.ok) return groups;
|
|
396
|
+
let map = EMPTY_MAP;
|
|
397
|
+
if (raw["context"] !== void 0) {
|
|
398
|
+
const error = contextError(raw["context"]);
|
|
399
|
+
if (error) return err({ kind: "bad-shape", path, detail: error });
|
|
400
|
+
map = { ...map, context: raw["context"] };
|
|
401
|
+
}
|
|
402
|
+
const title = optionalString(raw, "title", "map", path);
|
|
403
|
+
if (!title.ok) return title;
|
|
404
|
+
if (title.value !== void 0) map = setTitle(map, title.value);
|
|
405
|
+
const rawKind = optionalString(raw, "kind", "map", path);
|
|
406
|
+
if (!rawKind.ok) return rawKind;
|
|
407
|
+
if (rawKind.value !== void 0) {
|
|
408
|
+
const kind = makeMapKind(rawKind.value);
|
|
409
|
+
if (!kind.ok) return err({ kind: "invariant-violation", path, violation: kind.error });
|
|
410
|
+
map = setKind(map, kind.value);
|
|
411
|
+
}
|
|
412
|
+
for (const [i, rawLayer] of layers.value.entries()) {
|
|
413
|
+
const where = `layers[${i}]`;
|
|
414
|
+
if (!isRecord(rawLayer)) return badShape(path, where, "an object", rawLayer);
|
|
415
|
+
const rawId = requiredString(rawLayer, "id", where, path);
|
|
416
|
+
if (!rawId.ok) return rawId;
|
|
417
|
+
const id = makeLayerId(rawId.value);
|
|
418
|
+
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
419
|
+
const name = requiredString(rawLayer, "name", where, path);
|
|
420
|
+
if (!name.ok) return name;
|
|
421
|
+
const rawRank = rawLayer["rank"];
|
|
422
|
+
if (typeof rawRank !== "number") return badShape(path, `${where}.rank`, "a number", rawRank);
|
|
423
|
+
const rank = makeRank(rawRank);
|
|
424
|
+
if (!rank.ok) return err({ kind: "invariant-violation", path, violation: rank.error });
|
|
425
|
+
const next = declareLayer(map, { id: id.value, name: name.value, rank: rank.value });
|
|
426
|
+
if (!next.ok) return err({ kind: "invariant-violation", path, violation: next.error });
|
|
427
|
+
map = next.value;
|
|
428
|
+
}
|
|
429
|
+
for (const [i, rawLane] of lanes.value.entries()) {
|
|
430
|
+
const where = `lanes[${i}]`;
|
|
431
|
+
if (!isRecord(rawLane)) return badShape(path, where, "an object", rawLane);
|
|
432
|
+
const rawId = requiredString(rawLane, "id", where, path);
|
|
433
|
+
if (!rawId.ok) return rawId;
|
|
434
|
+
const id = makeLaneId(rawId.value);
|
|
435
|
+
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
436
|
+
const label = requiredString(rawLane, "label", where, path);
|
|
437
|
+
if (!label.ok) return label;
|
|
438
|
+
const declared = declareLane(map, { id: id.value, label: label.value });
|
|
439
|
+
if (!declared.ok) return err({ kind: "invariant-violation", path, violation: declared.error });
|
|
440
|
+
map = declared.value;
|
|
441
|
+
}
|
|
442
|
+
for (const [i, rawGroup] of groups.value.entries()) {
|
|
443
|
+
const where = `groups[${i}]`;
|
|
444
|
+
if (!isRecord(rawGroup)) return badShape(path, where, "an object", rawGroup);
|
|
445
|
+
const rawId = requiredString(rawGroup, "id", where, path);
|
|
446
|
+
if (!rawId.ok) return rawId;
|
|
447
|
+
const id = makeGroupId(rawId.value);
|
|
448
|
+
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
449
|
+
const rawLayer = requiredString(rawGroup, "layer", where, path);
|
|
450
|
+
if (!rawLayer.ok) return rawLayer;
|
|
451
|
+
const layer = makeLayerId(rawLayer.value);
|
|
452
|
+
if (!layer.ok) return err({ kind: "invariant-violation", path, violation: layer.error });
|
|
453
|
+
const label = requiredString(rawGroup, "label", where, path);
|
|
454
|
+
if (!label.ok) return label;
|
|
455
|
+
const declared = declareGroup(map, { id: id.value, label: label.value, layer: layer.value });
|
|
456
|
+
if (!declared.ok) return err({ kind: "invariant-violation", path, violation: declared.error });
|
|
457
|
+
map = declared.value;
|
|
458
|
+
}
|
|
459
|
+
for (const [i, rawNode] of nodes.value.entries()) {
|
|
460
|
+
const where = `nodes[${i}]`;
|
|
461
|
+
if (!isRecord(rawNode)) return badShape(path, where, "an object", rawNode);
|
|
462
|
+
const rawId = requiredString(rawNode, "id", where, path);
|
|
463
|
+
if (!rawId.ok) return rawId;
|
|
464
|
+
const id = makeNodeId(rawId.value);
|
|
465
|
+
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
466
|
+
const rawLayer = requiredString(rawNode, "layer", where, path);
|
|
467
|
+
if (!rawLayer.ok) return rawLayer;
|
|
468
|
+
const layer = makeLayerId(rawLayer.value);
|
|
469
|
+
if (!layer.ok) return err({ kind: "invariant-violation", path, violation: layer.error });
|
|
470
|
+
const rawStatus = requiredString(rawNode, "status", where, path);
|
|
471
|
+
if (!rawStatus.ok) return rawStatus;
|
|
472
|
+
const status = makeNodeStatus(rawStatus.value);
|
|
473
|
+
if (!status.ok) return err({ kind: "invariant-violation", path, violation: status.error });
|
|
474
|
+
const label = requiredString(rawNode, "label", where, path);
|
|
475
|
+
if (!label.ok) return label;
|
|
476
|
+
const detail = optionalString(rawNode, "detail", where, path);
|
|
477
|
+
if (!detail.ok) return detail;
|
|
478
|
+
const rawGroup = optionalString(rawNode, "group", where, path);
|
|
479
|
+
if (!rawGroup.ok) return rawGroup;
|
|
480
|
+
let group;
|
|
481
|
+
if (rawGroup.value !== void 0) {
|
|
482
|
+
const made = makeGroupId(rawGroup.value);
|
|
483
|
+
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
484
|
+
group = made.value;
|
|
485
|
+
}
|
|
486
|
+
const rawNodeKind = optionalString(rawNode, "kind", where, path);
|
|
487
|
+
if (!rawNodeKind.ok) return rawNodeKind;
|
|
488
|
+
let nodeKind;
|
|
489
|
+
if (rawNodeKind.value !== void 0) {
|
|
490
|
+
const made = makeNodeKind(rawNodeKind.value);
|
|
491
|
+
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
492
|
+
nodeKind = made.value;
|
|
493
|
+
}
|
|
494
|
+
const rawLane = optionalString(rawNode, "lane", where, path);
|
|
495
|
+
if (!rawLane.ok) return rawLane;
|
|
496
|
+
let lane;
|
|
497
|
+
if (rawLane.value !== void 0) {
|
|
498
|
+
const made = makeLaneId(rawLane.value);
|
|
499
|
+
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
500
|
+
lane = made.value;
|
|
501
|
+
}
|
|
502
|
+
const rawSubmap = optionalString(rawNode, "submap", where, path);
|
|
503
|
+
if (!rawSubmap.ok) return rawSubmap;
|
|
504
|
+
let submap;
|
|
505
|
+
if (rawSubmap.value !== void 0) {
|
|
506
|
+
const made = makeSubmapRef(rawSubmap.value);
|
|
507
|
+
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
508
|
+
submap = made.value;
|
|
509
|
+
}
|
|
510
|
+
const declared = declareNode(map, {
|
|
511
|
+
id: id.value,
|
|
512
|
+
label: label.value,
|
|
513
|
+
layer: layer.value,
|
|
514
|
+
status: status.value,
|
|
515
|
+
...detail.value !== void 0 ? { detail: detail.value } : {},
|
|
516
|
+
...group !== void 0 ? { group } : {},
|
|
517
|
+
...nodeKind !== void 0 ? { kind: nodeKind } : {},
|
|
518
|
+
...lane !== void 0 ? { lane } : {},
|
|
519
|
+
...submap !== void 0 ? { submap } : {}
|
|
520
|
+
});
|
|
521
|
+
if (!declared.ok) return err({ kind: "invariant-violation", path, violation: declared.error });
|
|
522
|
+
map = declared.value;
|
|
523
|
+
const evidence = optionalString(rawNode, "evidence", where, path);
|
|
524
|
+
if (!evidence.ok) return evidence;
|
|
525
|
+
if (evidence.value !== void 0) {
|
|
526
|
+
const updated = updateNode(map, { id: id.value, evidence: evidence.value });
|
|
527
|
+
if (!updated.ok) return err({ kind: "invariant-violation", path, violation: updated.error });
|
|
528
|
+
map = updated.value;
|
|
529
|
+
}
|
|
530
|
+
if (rawNode["sources"] !== void 0) {
|
|
531
|
+
const error = sourceError(rawNode["sources"]);
|
|
532
|
+
if (error) return err({ kind: "bad-shape", path, detail: `${where}: ${error}` });
|
|
533
|
+
map = { ...map, nodes: map.nodes.map((n) => n.id === id.value ? { ...n, sources: rawNode["sources"] } : n) };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
for (const [i, rawEdge] of edges.value.entries()) {
|
|
537
|
+
const where = `edges[${i}]`;
|
|
538
|
+
if (!isRecord(rawEdge)) return badShape(path, where, "an object", rawEdge);
|
|
539
|
+
const rawFrom = requiredString(rawEdge, "from", where, path);
|
|
540
|
+
if (!rawFrom.ok) return rawFrom;
|
|
541
|
+
const from = makeNodeId(rawFrom.value);
|
|
542
|
+
if (!from.ok) return err({ kind: "invariant-violation", path, violation: from.error });
|
|
543
|
+
const rawTo = requiredString(rawEdge, "to", where, path);
|
|
544
|
+
if (!rawTo.ok) return rawTo;
|
|
545
|
+
const to = makeNodeId(rawTo.value);
|
|
546
|
+
if (!to.ok) return err({ kind: "invariant-violation", path, violation: to.error });
|
|
547
|
+
const label = optionalString(rawEdge, "label", where, path);
|
|
548
|
+
if (!label.ok) return label;
|
|
549
|
+
const linked = linkNodes(map, from.value, to.value, label.value);
|
|
550
|
+
if (!linked.ok) return err({ kind: "invariant-violation", path, violation: linked.error });
|
|
551
|
+
map = linked.value;
|
|
552
|
+
}
|
|
553
|
+
const textError = mapTextError(map);
|
|
554
|
+
return textError ? err({ kind: "bad-shape", path, detail: textError }) : ok(map);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/store/transaction.ts
|
|
558
|
+
var LedgerError = class extends Error {
|
|
559
|
+
constructor(code, message, details = {}) {
|
|
560
|
+
super(message);
|
|
561
|
+
this.code = code;
|
|
562
|
+
this.details = details;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
function storeDirectory(file) {
|
|
566
|
+
const dir = dirname2(file);
|
|
567
|
+
return dir.endsWith("/pages") || dir.endsWith("\\pages") ? dirname2(dir) : dir;
|
|
568
|
+
}
|
|
569
|
+
function deadOwner(lock) {
|
|
570
|
+
try {
|
|
571
|
+
const owner = JSON.parse(readFileSync(join2(lock, "owner.json"), "utf8"));
|
|
572
|
+
if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0) return false;
|
|
573
|
+
try {
|
|
574
|
+
process.kill(owner.pid, 0);
|
|
575
|
+
return false;
|
|
576
|
+
} catch (e) {
|
|
577
|
+
return e.code === "ESRCH";
|
|
578
|
+
}
|
|
579
|
+
} catch {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function withStoreLock(file, action) {
|
|
584
|
+
const dir = storeDirectory(file);
|
|
585
|
+
mkdirSync(dir, { recursive: true });
|
|
586
|
+
const lock = join2(dir, ".write-lock");
|
|
587
|
+
const owner = join2(lock, "owner.json");
|
|
588
|
+
const token = randomUUID();
|
|
589
|
+
try {
|
|
590
|
+
mkdirSync(lock);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
if (error.code !== "EEXIST") throw error;
|
|
593
|
+
if (!deadOwner(lock)) throw new LedgerError("BUSY", `Another writer owns ${lock}; retry after it completes. An orphan without owner metadata needs manual inspection.`);
|
|
594
|
+
try {
|
|
595
|
+
mkdirSync(join2(lock, ".reap"));
|
|
596
|
+
} catch {
|
|
597
|
+
throw new LedgerError("BUSY", "Another process is recovering the writer lock.");
|
|
598
|
+
}
|
|
599
|
+
if (!deadOwner(lock)) {
|
|
600
|
+
rmSync(join2(lock, ".reap"), { recursive: true, force: true });
|
|
601
|
+
throw new LedgerError("BUSY", "Writer ownership changed.");
|
|
602
|
+
}
|
|
603
|
+
rmSync(lock, { recursive: true });
|
|
604
|
+
try {
|
|
605
|
+
mkdirSync(lock);
|
|
606
|
+
} catch {
|
|
607
|
+
throw new LedgerError("BUSY", "Another writer acquired the recovered lock.");
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
let initialized = false;
|
|
611
|
+
try {
|
|
612
|
+
writeFileSync(owner, JSON.stringify({ pid: process.pid, token }), { flag: "wx" });
|
|
613
|
+
initialized = true;
|
|
614
|
+
return action();
|
|
615
|
+
} finally {
|
|
616
|
+
try {
|
|
617
|
+
if (!initialized || JSON.parse(readFileSync(owner, "utf8")).token === token) rmSync(lock, { recursive: true });
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
237
623
|
// src/semantics/vocabulary.ts
|
|
238
624
|
var STATUS_GLYPHS = {
|
|
239
625
|
planned: ["\xB7", "."],
|
|
@@ -478,41 +864,6 @@ function flipForSequence(map) {
|
|
|
478
864
|
};
|
|
479
865
|
}
|
|
480
866
|
|
|
481
|
-
// src/domain/text.ts
|
|
482
|
-
var NO_CONTROLS = /^[^\u0000-\u001f\u007f-\u009f]*$/;
|
|
483
|
-
var NO_CONTROLS_TEXT = "one line of text; control characters (ESC, newline, tab) are not allowed";
|
|
484
|
-
var NO_CONTROLS_BUT_BREAKS = /^[^\u0000-\u0008\u000b-\u001f\u007f-\u009f]*$/;
|
|
485
|
-
var NO_CONTROLS_BUT_BREAKS_TEXT = "text with optional newlines (\\n) and tabs; other control characters (ESC, BEL, CR) are not allowed";
|
|
486
|
-
function mapTextError(map) {
|
|
487
|
-
const check = (field, value, multiline = false) => value === void 0 || (multiline ? NO_CONTROLS_BUT_BREAKS : NO_CONTROLS).test(value) ? void 0 : `${field}: ${multiline ? NO_CONTROLS_BUT_BREAKS_TEXT : NO_CONTROLS_TEXT}`;
|
|
488
|
-
let error = check("title", map.title);
|
|
489
|
-
if (error) return error;
|
|
490
|
-
for (const [i, layer] of map.layers.entries()) {
|
|
491
|
-
error = check(`layers[${i}].name`, layer.name);
|
|
492
|
-
if (error) return error;
|
|
493
|
-
}
|
|
494
|
-
for (const name of ["lanes", "groups"]) {
|
|
495
|
-
for (const [i, item] of map[name].entries()) {
|
|
496
|
-
error = check(`${name}[${i}].label`, item.label);
|
|
497
|
-
if (error) return error;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
for (const [i, node] of map.nodes.entries()) {
|
|
501
|
-
for (const name of ["label", "evidence", "detail"]) {
|
|
502
|
-
error = check(`nodes[${i}].${name}`, node[name], name !== "label");
|
|
503
|
-
if (error) return error;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
for (const [i, edge] of map.edges.entries()) {
|
|
507
|
-
error = check(`edges[${i}].label`, edge.label);
|
|
508
|
-
if (error) return error;
|
|
509
|
-
}
|
|
510
|
-
return void 0;
|
|
511
|
-
}
|
|
512
|
-
function terminalText(text, multiline = false) {
|
|
513
|
-
return text.replace(multiline ? /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g : /[\u0000-\u001f\u007f-\u009f]/g, "?");
|
|
514
|
-
}
|
|
515
|
-
|
|
516
867
|
// src/render/width.ts
|
|
517
868
|
var WIDE_RANGES = [
|
|
518
869
|
[4352, 4447],
|
|
@@ -1396,223 +1747,9 @@ function paint(scene, opts) {
|
|
|
1396
1747
|
return { canvas, hits };
|
|
1397
1748
|
}
|
|
1398
1749
|
|
|
1399
|
-
// src/store/format.ts
|
|
1400
|
-
var STATE_FILE_VERSION = 1;
|
|
1401
|
-
function makePageId(raw) {
|
|
1402
|
-
return ID_RULE.test(raw) ? ok(raw) : err({ kind: "invalid-id", raw, rule: ID_RULE_TEXT });
|
|
1403
|
-
}
|
|
1404
|
-
function describeStoreError(e) {
|
|
1405
|
-
switch (e.kind) {
|
|
1406
|
-
case "not-found":
|
|
1407
|
-
return `no map file at ${e.path}`;
|
|
1408
|
-
case "malformed-json":
|
|
1409
|
-
return `map file ${e.path} is not valid JSON: ${e.detail}`;
|
|
1410
|
-
case "bad-shape":
|
|
1411
|
-
return `map file ${e.path} has an unexpected shape: ${e.detail}`;
|
|
1412
|
-
case "invariant-violation":
|
|
1413
|
-
return `map file ${e.path} violates a structural invariant: ${describeMapError(e.violation)}`;
|
|
1414
|
-
case "save-failed":
|
|
1415
|
-
return `could not write ${e.path}: ${e.detail}`;
|
|
1416
|
-
case "delete-failed":
|
|
1417
|
-
return `could not delete ${e.path}: ${e.detail}`;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
function isRecord(v) {
|
|
1421
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1422
|
-
}
|
|
1423
|
-
function describeValue(v) {
|
|
1424
|
-
if (v === void 0) return "missing";
|
|
1425
|
-
if (v === null) return "null";
|
|
1426
|
-
if (Array.isArray(v)) return "an array";
|
|
1427
|
-
return `a ${typeof v}`;
|
|
1428
|
-
}
|
|
1429
|
-
function badShape(path, where, expected, got) {
|
|
1430
|
-
return err({ kind: "bad-shape", path, detail: `${where} is ${describeValue(got)}, expected ${expected}` });
|
|
1431
|
-
}
|
|
1432
|
-
function arrayField(raw, key, path, presence) {
|
|
1433
|
-
const v = raw[key];
|
|
1434
|
-
if (Array.isArray(v)) return ok(v);
|
|
1435
|
-
if (v === void 0 && presence === "optional") return ok([]);
|
|
1436
|
-
return badShape(path, `"${key}"`, "an array", v);
|
|
1437
|
-
}
|
|
1438
|
-
function requiredString(rec, key, where, path) {
|
|
1439
|
-
const v = rec[key];
|
|
1440
|
-
return typeof v === "string" ? ok(v) : badShape(path, `${where}.${key}`, "a string", v);
|
|
1441
|
-
}
|
|
1442
|
-
function optionalString(rec, key, where, path) {
|
|
1443
|
-
const v = rec[key];
|
|
1444
|
-
if (v === void 0) return ok(void 0);
|
|
1445
|
-
return typeof v === "string" ? ok(v) : badShape(path, `${where}.${key}`, "a string", v);
|
|
1446
|
-
}
|
|
1447
|
-
function parseMap(raw, path) {
|
|
1448
|
-
if (!isRecord(raw)) return err({ kind: "bad-shape", path, detail: "root is not an object" });
|
|
1449
|
-
if (raw["version"] !== STATE_FILE_VERSION) {
|
|
1450
|
-
return err({ kind: "bad-shape", path, detail: `version is ${String(raw["version"])}, expected ${STATE_FILE_VERSION}` });
|
|
1451
|
-
}
|
|
1452
|
-
const layers = arrayField(raw, "layers", path, "required");
|
|
1453
|
-
if (!layers.ok) return layers;
|
|
1454
|
-
const nodes = arrayField(raw, "nodes", path, "required");
|
|
1455
|
-
if (!nodes.ok) return nodes;
|
|
1456
|
-
const edges = arrayField(raw, "edges", path, "required");
|
|
1457
|
-
if (!edges.ok) return edges;
|
|
1458
|
-
const lanes = arrayField(raw, "lanes", path, "optional");
|
|
1459
|
-
if (!lanes.ok) return lanes;
|
|
1460
|
-
const groups = arrayField(raw, "groups", path, "optional");
|
|
1461
|
-
if (!groups.ok) return groups;
|
|
1462
|
-
let map = EMPTY_MAP;
|
|
1463
|
-
const title = optionalString(raw, "title", "map", path);
|
|
1464
|
-
if (!title.ok) return title;
|
|
1465
|
-
if (title.value !== void 0) map = setTitle(map, title.value);
|
|
1466
|
-
const rawKind = optionalString(raw, "kind", "map", path);
|
|
1467
|
-
if (!rawKind.ok) return rawKind;
|
|
1468
|
-
if (rawKind.value !== void 0) {
|
|
1469
|
-
const kind = makeMapKind(rawKind.value);
|
|
1470
|
-
if (!kind.ok) return err({ kind: "invariant-violation", path, violation: kind.error });
|
|
1471
|
-
map = setKind(map, kind.value);
|
|
1472
|
-
}
|
|
1473
|
-
for (const [i, rawLayer] of layers.value.entries()) {
|
|
1474
|
-
const where = `layers[${i}]`;
|
|
1475
|
-
if (!isRecord(rawLayer)) return badShape(path, where, "an object", rawLayer);
|
|
1476
|
-
const rawId = requiredString(rawLayer, "id", where, path);
|
|
1477
|
-
if (!rawId.ok) return rawId;
|
|
1478
|
-
const id = makeLayerId(rawId.value);
|
|
1479
|
-
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
1480
|
-
const name = requiredString(rawLayer, "name", where, path);
|
|
1481
|
-
if (!name.ok) return name;
|
|
1482
|
-
const rawRank = rawLayer["rank"];
|
|
1483
|
-
if (typeof rawRank !== "number") return badShape(path, `${where}.rank`, "a number", rawRank);
|
|
1484
|
-
const rank = makeRank(rawRank);
|
|
1485
|
-
if (!rank.ok) return err({ kind: "invariant-violation", path, violation: rank.error });
|
|
1486
|
-
const next = declareLayer(map, { id: id.value, name: name.value, rank: rank.value });
|
|
1487
|
-
if (!next.ok) return err({ kind: "invariant-violation", path, violation: next.error });
|
|
1488
|
-
map = next.value;
|
|
1489
|
-
}
|
|
1490
|
-
for (const [i, rawLane] of lanes.value.entries()) {
|
|
1491
|
-
const where = `lanes[${i}]`;
|
|
1492
|
-
if (!isRecord(rawLane)) return badShape(path, where, "an object", rawLane);
|
|
1493
|
-
const rawId = requiredString(rawLane, "id", where, path);
|
|
1494
|
-
if (!rawId.ok) return rawId;
|
|
1495
|
-
const id = makeLaneId(rawId.value);
|
|
1496
|
-
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
1497
|
-
const label = requiredString(rawLane, "label", where, path);
|
|
1498
|
-
if (!label.ok) return label;
|
|
1499
|
-
const declared = declareLane(map, { id: id.value, label: label.value });
|
|
1500
|
-
if (!declared.ok) return err({ kind: "invariant-violation", path, violation: declared.error });
|
|
1501
|
-
map = declared.value;
|
|
1502
|
-
}
|
|
1503
|
-
for (const [i, rawGroup] of groups.value.entries()) {
|
|
1504
|
-
const where = `groups[${i}]`;
|
|
1505
|
-
if (!isRecord(rawGroup)) return badShape(path, where, "an object", rawGroup);
|
|
1506
|
-
const rawId = requiredString(rawGroup, "id", where, path);
|
|
1507
|
-
if (!rawId.ok) return rawId;
|
|
1508
|
-
const id = makeGroupId(rawId.value);
|
|
1509
|
-
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
1510
|
-
const rawLayer = requiredString(rawGroup, "layer", where, path);
|
|
1511
|
-
if (!rawLayer.ok) return rawLayer;
|
|
1512
|
-
const layer = makeLayerId(rawLayer.value);
|
|
1513
|
-
if (!layer.ok) return err({ kind: "invariant-violation", path, violation: layer.error });
|
|
1514
|
-
const label = requiredString(rawGroup, "label", where, path);
|
|
1515
|
-
if (!label.ok) return label;
|
|
1516
|
-
const declared = declareGroup(map, { id: id.value, label: label.value, layer: layer.value });
|
|
1517
|
-
if (!declared.ok) return err({ kind: "invariant-violation", path, violation: declared.error });
|
|
1518
|
-
map = declared.value;
|
|
1519
|
-
}
|
|
1520
|
-
for (const [i, rawNode] of nodes.value.entries()) {
|
|
1521
|
-
const where = `nodes[${i}]`;
|
|
1522
|
-
if (!isRecord(rawNode)) return badShape(path, where, "an object", rawNode);
|
|
1523
|
-
const rawId = requiredString(rawNode, "id", where, path);
|
|
1524
|
-
if (!rawId.ok) return rawId;
|
|
1525
|
-
const id = makeNodeId(rawId.value);
|
|
1526
|
-
if (!id.ok) return err({ kind: "invariant-violation", path, violation: id.error });
|
|
1527
|
-
const rawLayer = requiredString(rawNode, "layer", where, path);
|
|
1528
|
-
if (!rawLayer.ok) return rawLayer;
|
|
1529
|
-
const layer = makeLayerId(rawLayer.value);
|
|
1530
|
-
if (!layer.ok) return err({ kind: "invariant-violation", path, violation: layer.error });
|
|
1531
|
-
const rawStatus = requiredString(rawNode, "status", where, path);
|
|
1532
|
-
if (!rawStatus.ok) return rawStatus;
|
|
1533
|
-
const status = makeNodeStatus(rawStatus.value);
|
|
1534
|
-
if (!status.ok) return err({ kind: "invariant-violation", path, violation: status.error });
|
|
1535
|
-
const label = requiredString(rawNode, "label", where, path);
|
|
1536
|
-
if (!label.ok) return label;
|
|
1537
|
-
const detail = optionalString(rawNode, "detail", where, path);
|
|
1538
|
-
if (!detail.ok) return detail;
|
|
1539
|
-
const rawGroup = optionalString(rawNode, "group", where, path);
|
|
1540
|
-
if (!rawGroup.ok) return rawGroup;
|
|
1541
|
-
let group;
|
|
1542
|
-
if (rawGroup.value !== void 0) {
|
|
1543
|
-
const made = makeGroupId(rawGroup.value);
|
|
1544
|
-
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
1545
|
-
group = made.value;
|
|
1546
|
-
}
|
|
1547
|
-
const rawNodeKind = optionalString(rawNode, "kind", where, path);
|
|
1548
|
-
if (!rawNodeKind.ok) return rawNodeKind;
|
|
1549
|
-
let nodeKind;
|
|
1550
|
-
if (rawNodeKind.value !== void 0) {
|
|
1551
|
-
const made = makeNodeKind(rawNodeKind.value);
|
|
1552
|
-
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
1553
|
-
nodeKind = made.value;
|
|
1554
|
-
}
|
|
1555
|
-
const rawLane = optionalString(rawNode, "lane", where, path);
|
|
1556
|
-
if (!rawLane.ok) return rawLane;
|
|
1557
|
-
let lane;
|
|
1558
|
-
if (rawLane.value !== void 0) {
|
|
1559
|
-
const made = makeLaneId(rawLane.value);
|
|
1560
|
-
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
1561
|
-
lane = made.value;
|
|
1562
|
-
}
|
|
1563
|
-
const rawSubmap = optionalString(rawNode, "submap", where, path);
|
|
1564
|
-
if (!rawSubmap.ok) return rawSubmap;
|
|
1565
|
-
let submap;
|
|
1566
|
-
if (rawSubmap.value !== void 0) {
|
|
1567
|
-
const made = makeSubmapRef(rawSubmap.value);
|
|
1568
|
-
if (!made.ok) return err({ kind: "invariant-violation", path, violation: made.error });
|
|
1569
|
-
submap = made.value;
|
|
1570
|
-
}
|
|
1571
|
-
const declared = declareNode(map, {
|
|
1572
|
-
id: id.value,
|
|
1573
|
-
label: label.value,
|
|
1574
|
-
layer: layer.value,
|
|
1575
|
-
status: status.value,
|
|
1576
|
-
...detail.value !== void 0 ? { detail: detail.value } : {},
|
|
1577
|
-
...group !== void 0 ? { group } : {},
|
|
1578
|
-
...nodeKind !== void 0 ? { kind: nodeKind } : {},
|
|
1579
|
-
...lane !== void 0 ? { lane } : {},
|
|
1580
|
-
...submap !== void 0 ? { submap } : {}
|
|
1581
|
-
});
|
|
1582
|
-
if (!declared.ok) return err({ kind: "invariant-violation", path, violation: declared.error });
|
|
1583
|
-
map = declared.value;
|
|
1584
|
-
const evidence = optionalString(rawNode, "evidence", where, path);
|
|
1585
|
-
if (!evidence.ok) return evidence;
|
|
1586
|
-
if (evidence.value !== void 0) {
|
|
1587
|
-
const updated = updateNode(map, { id: id.value, evidence: evidence.value });
|
|
1588
|
-
if (!updated.ok) return err({ kind: "invariant-violation", path, violation: updated.error });
|
|
1589
|
-
map = updated.value;
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
for (const [i, rawEdge] of edges.value.entries()) {
|
|
1593
|
-
const where = `edges[${i}]`;
|
|
1594
|
-
if (!isRecord(rawEdge)) return badShape(path, where, "an object", rawEdge);
|
|
1595
|
-
const rawFrom = requiredString(rawEdge, "from", where, path);
|
|
1596
|
-
if (!rawFrom.ok) return rawFrom;
|
|
1597
|
-
const from = makeNodeId(rawFrom.value);
|
|
1598
|
-
if (!from.ok) return err({ kind: "invariant-violation", path, violation: from.error });
|
|
1599
|
-
const rawTo = requiredString(rawEdge, "to", where, path);
|
|
1600
|
-
if (!rawTo.ok) return rawTo;
|
|
1601
|
-
const to = makeNodeId(rawTo.value);
|
|
1602
|
-
if (!to.ok) return err({ kind: "invariant-violation", path, violation: to.error });
|
|
1603
|
-
const label = optionalString(rawEdge, "label", where, path);
|
|
1604
|
-
if (!label.ok) return label;
|
|
1605
|
-
const linked = linkNodes(map, from.value, to.value, label.value);
|
|
1606
|
-
if (!linked.ok) return err({ kind: "invariant-violation", path, violation: linked.error });
|
|
1607
|
-
map = linked.value;
|
|
1608
|
-
}
|
|
1609
|
-
const textError = mapTextError(map);
|
|
1610
|
-
return textError ? err({ kind: "bad-shape", path, detail: textError }) : ok(map);
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
1750
|
// src/store/atomic.ts
|
|
1614
|
-
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1615
|
-
import { dirname } from "node:path";
|
|
1751
|
+
import { mkdirSync as mkdirSync2, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1752
|
+
import { dirname as dirname3 } from "node:path";
|
|
1616
1753
|
var RENAME_BACKOFF_STEP_MS = 10;
|
|
1617
1754
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOENT"]);
|
|
1618
1755
|
function sleepSync(ms) {
|
|
@@ -1620,7 +1757,7 @@ function sleepSync(ms) {
|
|
|
1620
1757
|
}
|
|
1621
1758
|
function discardTemp(tmp) {
|
|
1622
1759
|
try {
|
|
1623
|
-
|
|
1760
|
+
rmSync2(tmp, { force: true });
|
|
1624
1761
|
} catch {
|
|
1625
1762
|
}
|
|
1626
1763
|
}
|
|
@@ -1630,8 +1767,8 @@ function errnoOf(e) {
|
|
|
1630
1767
|
function writeAtomic(path, contents, maxAttempts) {
|
|
1631
1768
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
1632
1769
|
try {
|
|
1633
|
-
|
|
1634
|
-
|
|
1770
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
1771
|
+
writeFileSync2(tmp, contents, "utf8");
|
|
1635
1772
|
} catch (e) {
|
|
1636
1773
|
discardTemp(tmp);
|
|
1637
1774
|
return err({ kind: "save-failed", path, detail: `writing the temp file failed: ${errnoOf(e)}` });
|
|
@@ -1654,13 +1791,13 @@ function writeAtomic(path, contents, maxAttempts) {
|
|
|
1654
1791
|
}
|
|
1655
1792
|
|
|
1656
1793
|
// src/store/pages.ts
|
|
1657
|
-
import { existsSync, readdirSync, rmSync as
|
|
1658
|
-
import { basename, dirname as
|
|
1794
|
+
import { existsSync as existsSync2, readdirSync, rmSync as rmSync3 } from "node:fs";
|
|
1795
|
+
import { basename, dirname as dirname4, join as join3 } from "node:path";
|
|
1659
1796
|
var STORE_DIR_NAME = ".mellos";
|
|
1660
|
-
var STATE_FILE_RELATIVE_PATH =
|
|
1797
|
+
var STATE_FILE_RELATIVE_PATH = join3(STORE_DIR_NAME, "map.json");
|
|
1661
1798
|
var PAGES_DIR_NAME = "pages";
|
|
1662
1799
|
function pageFilePath(defaultFile, page) {
|
|
1663
|
-
return page === void 0 ? defaultFile :
|
|
1800
|
+
return page === void 0 ? defaultFile : join3(dirname4(defaultFile), PAGES_DIR_NAME, `${page}.json`);
|
|
1664
1801
|
}
|
|
1665
1802
|
function pageIdOfFile(defaultFile, path) {
|
|
1666
1803
|
if (path === defaultFile) return void 0;
|
|
@@ -1669,20 +1806,20 @@ function pageIdOfFile(defaultFile, path) {
|
|
|
1669
1806
|
}
|
|
1670
1807
|
function listPageFiles(defaultFile) {
|
|
1671
1808
|
const out = [];
|
|
1672
|
-
if (
|
|
1809
|
+
if (existsSync2(defaultFile)) out.push(defaultFile);
|
|
1673
1810
|
let entries = [];
|
|
1674
1811
|
try {
|
|
1675
|
-
entries = readdirSync(
|
|
1812
|
+
entries = readdirSync(join3(dirname4(defaultFile), PAGES_DIR_NAME));
|
|
1676
1813
|
} catch {
|
|
1677
1814
|
}
|
|
1678
1815
|
for (const e of entries.sort()) {
|
|
1679
|
-
if (e.endsWith(".json")) out.push(
|
|
1816
|
+
if (e.endsWith(".json")) out.push(join3(dirname4(defaultFile), PAGES_DIR_NAME, e));
|
|
1680
1817
|
}
|
|
1681
1818
|
return out;
|
|
1682
1819
|
}
|
|
1683
1820
|
function deletePageFile(path) {
|
|
1684
1821
|
try {
|
|
1685
|
-
|
|
1822
|
+
rmSync3(path, { force: true });
|
|
1686
1823
|
return ok(void 0);
|
|
1687
1824
|
} catch (e) {
|
|
1688
1825
|
return err({ kind: "delete-failed", path, detail: errnoOf(e) });
|
|
@@ -1690,8 +1827,8 @@ function deletePageFile(path) {
|
|
|
1690
1827
|
}
|
|
1691
1828
|
|
|
1692
1829
|
// src/store/channels.ts
|
|
1693
|
-
import { existsSync as
|
|
1694
|
-
import { dirname as
|
|
1830
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync as rmSync5 } from "node:fs";
|
|
1831
|
+
import { dirname as dirname6, join as join5 } from "node:path";
|
|
1695
1832
|
|
|
1696
1833
|
// src/store/json-text.ts
|
|
1697
1834
|
function isRecord2(v) {
|
|
@@ -1702,16 +1839,16 @@ function stripBom(text) {
|
|
|
1702
1839
|
}
|
|
1703
1840
|
|
|
1704
1841
|
// src/store/viewers.ts
|
|
1705
|
-
import { readdirSync as readdirSync2, readFileSync, statSync, rmSync as
|
|
1706
|
-
import { dirname as
|
|
1842
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync, rmSync as rmSync4 } from "node:fs";
|
|
1843
|
+
import { dirname as dirname5, join as join4 } from "node:path";
|
|
1707
1844
|
var VIEWERS_DIR_NAME = "viewers";
|
|
1708
1845
|
var VIEWER_FILE_VERSION = 1;
|
|
1709
1846
|
var VIEWER_HEARTBEAT_MS = 1e3;
|
|
1710
1847
|
function viewersDirPath(defaultFile) {
|
|
1711
|
-
return
|
|
1848
|
+
return join4(dirname5(defaultFile), VIEWERS_DIR_NAME);
|
|
1712
1849
|
}
|
|
1713
1850
|
function viewerFilePath(defaultFile, pid) {
|
|
1714
|
-
return
|
|
1851
|
+
return join4(viewersDirPath(defaultFile), `${pid}.json`);
|
|
1715
1852
|
}
|
|
1716
1853
|
function publishViewer(defaultFile, pid, report) {
|
|
1717
1854
|
const body = { version: VIEWER_FILE_VERSION, page: report.page ?? null, follow: report.follow, owner: report.owner };
|
|
@@ -1720,7 +1857,7 @@ function publishViewer(defaultFile, pid, report) {
|
|
|
1720
1857
|
}
|
|
1721
1858
|
function retireViewer(defaultFile, pid) {
|
|
1722
1859
|
try {
|
|
1723
|
-
|
|
1860
|
+
rmSync4(viewerFilePath(defaultFile, pid), { force: true });
|
|
1724
1861
|
} catch {
|
|
1725
1862
|
}
|
|
1726
1863
|
}
|
|
@@ -1731,21 +1868,21 @@ function focusFilePath(defaultFile, pid) {
|
|
|
1731
1868
|
return paneChannelPath(defaultFile, FOCUS_FILE_NAME, pid);
|
|
1732
1869
|
}
|
|
1733
1870
|
function paneChannelPath(defaultFile, channel, pid) {
|
|
1734
|
-
if (pid === void 0) return
|
|
1871
|
+
if (pid === void 0) return join5(dirname6(defaultFile), channel);
|
|
1735
1872
|
if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error("Invalid pane process id");
|
|
1736
|
-
return
|
|
1873
|
+
return join5(viewersDirPath(defaultFile), `${pid}.${channel}`);
|
|
1737
1874
|
}
|
|
1738
1875
|
function takeFocusRequest(defaultFile, pid) {
|
|
1739
1876
|
const targeted = pid === void 0 ? void 0 : focusFilePath(defaultFile, pid);
|
|
1740
|
-
const path = targeted !== void 0 &&
|
|
1877
|
+
const path = targeted !== void 0 && existsSync3(targeted) ? targeted : focusFilePath(defaultFile);
|
|
1741
1878
|
let raw;
|
|
1742
1879
|
try {
|
|
1743
|
-
raw =
|
|
1880
|
+
raw = readFileSync3(path, "utf8");
|
|
1744
1881
|
} catch {
|
|
1745
1882
|
return void 0;
|
|
1746
1883
|
}
|
|
1747
1884
|
try {
|
|
1748
|
-
|
|
1885
|
+
rmSync5(path, { force: true });
|
|
1749
1886
|
} catch {
|
|
1750
1887
|
}
|
|
1751
1888
|
let parsed;
|
|
@@ -1767,10 +1904,10 @@ function quitFilePath(defaultFile, pid) {
|
|
|
1767
1904
|
}
|
|
1768
1905
|
function takeQuitRequest(defaultFile, pid) {
|
|
1769
1906
|
const targeted = pid === void 0 ? void 0 : quitFilePath(defaultFile, pid);
|
|
1770
|
-
const path = targeted !== void 0 &&
|
|
1907
|
+
const path = targeted !== void 0 && existsSync3(targeted) ? targeted : quitFilePath(defaultFile);
|
|
1771
1908
|
let raw;
|
|
1772
1909
|
try {
|
|
1773
|
-
raw =
|
|
1910
|
+
raw = readFileSync3(path, "utf8");
|
|
1774
1911
|
} catch {
|
|
1775
1912
|
return false;
|
|
1776
1913
|
}
|
|
@@ -1785,45 +1922,45 @@ function takeQuitRequest(defaultFile, pid) {
|
|
|
1785
1922
|
}
|
|
1786
1923
|
function sweepQuitRequest(defaultFile, pid) {
|
|
1787
1924
|
try {
|
|
1788
|
-
|
|
1925
|
+
rmSync5(quitFilePath(defaultFile, pid), { force: true });
|
|
1789
1926
|
} catch {
|
|
1790
1927
|
}
|
|
1791
1928
|
}
|
|
1792
1929
|
|
|
1793
1930
|
// src/store/policy.ts
|
|
1794
|
-
import { dirname as
|
|
1931
|
+
import { dirname as dirname7, join as join6 } from "node:path";
|
|
1795
1932
|
var CONFIG_FILE_NAME = "config.json";
|
|
1796
1933
|
function configFilePath(defaultFile) {
|
|
1797
|
-
return
|
|
1934
|
+
return join6(dirname7(defaultFile), CONFIG_FILE_NAME);
|
|
1798
1935
|
}
|
|
1799
1936
|
|
|
1800
1937
|
// src/store/migration.ts
|
|
1801
|
-
import { existsSync as
|
|
1802
|
-
import { dirname as
|
|
1803
|
-
var LEGACY_STATE_FILE_RELATIVE_PATH =
|
|
1938
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync as renameSync2 } from "node:fs";
|
|
1939
|
+
import { dirname as dirname8, join as join7 } from "node:path";
|
|
1940
|
+
var LEGACY_STATE_FILE_RELATIVE_PATH = join7(".claude", "mellos-mapping.json");
|
|
1804
1941
|
var LEGACY_PAGES_DIR_NAME = "mellos-mapping.pages";
|
|
1805
1942
|
var LEGACY_CONFIG_FILE_NAME = "mellos-mapping.config.json";
|
|
1806
1943
|
function migrateLegacyStore(defaultFile) {
|
|
1807
|
-
const projectRoot =
|
|
1808
|
-
const legacyDefault =
|
|
1809
|
-
const legacyPages =
|
|
1810
|
-
const legacyConfig =
|
|
1811
|
-
const hasLegacy =
|
|
1812
|
-
const hasCurrent =
|
|
1944
|
+
const projectRoot = dirname8(dirname8(defaultFile));
|
|
1945
|
+
const legacyDefault = join7(projectRoot, LEGACY_STATE_FILE_RELATIVE_PATH);
|
|
1946
|
+
const legacyPages = join7(dirname8(legacyDefault), LEGACY_PAGES_DIR_NAME);
|
|
1947
|
+
const legacyConfig = join7(dirname8(legacyDefault), LEGACY_CONFIG_FILE_NAME);
|
|
1948
|
+
const hasLegacy = existsSync4(legacyDefault) || existsSync4(legacyPages) || existsSync4(legacyConfig);
|
|
1949
|
+
const hasCurrent = existsSync4(defaultFile) || existsSync4(join7(dirname8(defaultFile), PAGES_DIR_NAME)) || existsSync4(configFilePath(defaultFile));
|
|
1813
1950
|
if (!hasLegacy || hasCurrent) return false;
|
|
1814
|
-
|
|
1815
|
-
if (
|
|
1816
|
-
if (
|
|
1817
|
-
if (
|
|
1951
|
+
mkdirSync3(dirname8(defaultFile), { recursive: true });
|
|
1952
|
+
if (existsSync4(legacyDefault)) renameSync2(legacyDefault, defaultFile);
|
|
1953
|
+
if (existsSync4(legacyPages)) renameSync2(legacyPages, join7(dirname8(defaultFile), PAGES_DIR_NAME));
|
|
1954
|
+
if (existsSync4(legacyConfig)) renameSync2(legacyConfig, configFilePath(defaultFile));
|
|
1818
1955
|
return true;
|
|
1819
1956
|
}
|
|
1820
1957
|
|
|
1821
1958
|
// src/store/maps.ts
|
|
1822
|
-
import { readFileSync as
|
|
1959
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
1823
1960
|
function loadMapFile(path) {
|
|
1824
1961
|
let text;
|
|
1825
1962
|
try {
|
|
1826
|
-
text =
|
|
1963
|
+
text = readFileSync4(path, "utf8");
|
|
1827
1964
|
} catch (e) {
|
|
1828
1965
|
const code = e.code;
|
|
1829
1966
|
if (code === "ENOENT") return err({ kind: "not-found", path });
|
|
@@ -2275,7 +2412,7 @@ function describeArgsError(e) {
|
|
|
2275
2412
|
}
|
|
2276
2413
|
}
|
|
2277
2414
|
function parseArgs(argv, cwd) {
|
|
2278
|
-
let file =
|
|
2415
|
+
let file = join8(resolveProjectDirectory(cwd), STATE_FILE_RELATIVE_PATH);
|
|
2279
2416
|
let intervalMs = POLL_INTERVAL_DEFAULT_MS;
|
|
2280
2417
|
let unicode = true;
|
|
2281
2418
|
let color = true;
|
|
@@ -2557,7 +2694,7 @@ function waitingInfo(s, width) {
|
|
|
2557
2694
|
const clock = s.elapsedMs !== void 0 ? ` \xB7 waiting ${elapsedLabel(s.elapsedMs)}` : "";
|
|
2558
2695
|
const lines = [
|
|
2559
2696
|
`watching ${s.defaultFile}`,
|
|
2560
|
-
` and ${
|
|
2697
|
+
` and ${join8(s.pagesDir, "*.json")}`,
|
|
2561
2698
|
`polling every ${s.intervalMs} ms${clock}`
|
|
2562
2699
|
];
|
|
2563
2700
|
for (const b of s.broken) lines.push(`! ${b}`);
|
|
@@ -2759,6 +2896,7 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2759
2896
|
lastHits = [];
|
|
2760
2897
|
} else {
|
|
2761
2898
|
const windowed = rendered.value;
|
|
2899
|
+
if (map.nodes.length > 0) io.onMapRendered?.();
|
|
2762
2900
|
const maxX = Math.max(0, windowed.contentWidth - viewW);
|
|
2763
2901
|
const maxY = Math.max(0, windowed.contentHeight - viewH);
|
|
2764
2902
|
if (view.offsetX > maxX || view.offsetY > maxY || view.offsetX < 0 || view.offsetY < 0) {
|
|
@@ -2780,7 +2918,7 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2780
2918
|
const info = waitingInfo(
|
|
2781
2919
|
{
|
|
2782
2920
|
defaultFile: cfg.file,
|
|
2783
|
-
pagesDir:
|
|
2921
|
+
pagesDir: join8(dirname9(cfg.file), PAGES_DIR_NAME),
|
|
2784
2922
|
intervalMs: cfg.intervalMs,
|
|
2785
2923
|
elapsedMs: interactive ? Date.now() - startedAt : void 0,
|
|
2786
2924
|
broken: pane.pages.flatMap(
|
|
@@ -2928,7 +3066,13 @@ the map pane stopped: ${e instanceof Error ? e.stack ?? e.message : String(e)}
|
|
|
2928
3066
|
return;
|
|
2929
3067
|
}
|
|
2930
3068
|
const file = asked.request.file;
|
|
2931
|
-
|
|
3069
|
+
let removed;
|
|
3070
|
+
try {
|
|
3071
|
+
removed = withStoreLock(file, () => deletePageFile(file));
|
|
3072
|
+
} catch (error) {
|
|
3073
|
+
flash = { text: String(error), until: now + FLASH_NOTICE_MS };
|
|
3074
|
+
return;
|
|
3075
|
+
}
|
|
2932
3076
|
flash = removed.ok ? { text: `deleted ${pageName(file)}`, until: now + FLASH_ACK_MS } : { text: describeStoreError(removed.error), until: now + FLASH_NOTICE_MS };
|
|
2933
3077
|
tick();
|
|
2934
3078
|
};
|