dsh-claude-slides 2.7.0 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +1 -1
- package/lib/index.js +10 -4
- package/package.json +1 -1
- package/skills/claude-slides/SKILL.md +1 -0
- package/skills/claude-slides/scripts/artifact.mjs +230 -49
- package/skills/claude-slides/scripts/editor/assets/{index-S9woRl4b.js → index-kN-1njux.js} +129 -129
- package/skills/claude-slides/scripts/editor/assets/{woff2-decoder-BXtJbtte.js → woff2-decoder-P1USYjds.js} +1 -1
- package/skills/claude-slides/scripts/editor/index.html +1 -1
- package/skills/claude-slides/scripts/editor/node/compiler.cjs +80 -15
- package/skills/claude-slides/scripts/server.mjs +265 -101
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// claude-slides: local editor server (prebuilt; do not edit).
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs12 from "node:fs";
|
|
5
|
+
import path12 from "node:path";
|
|
6
6
|
|
|
7
7
|
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID } from "node:crypto";
|
|
8
|
-
import
|
|
8
|
+
import fs11 from "node:fs";
|
|
9
9
|
import fsp4 from "node:fs/promises";
|
|
10
10
|
import http from "node:http";
|
|
11
|
-
import
|
|
11
|
+
import path11 from "node:path";
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
13
|
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
@@ -1496,9 +1496,155 @@ ${sections || '<p class="muted">This copy has no tokens, components or guide.</p
|
|
|
1496
1496
|
`;
|
|
1497
1497
|
}
|
|
1498
1498
|
|
|
1499
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
1500
1499
|
import fs5 from "node:fs";
|
|
1501
1500
|
import path5 from "node:path";
|
|
1501
|
+
var FOLDER2 = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
1502
|
+
var MAX_FILES = 2e3;
|
|
1503
|
+
var MAX_FILE_BYTES2 = 64 * 1024 * 1024;
|
|
1504
|
+
var DESIGN_SYSTEM_FILE = "design-system.json";
|
|
1505
|
+
function readRecords(project, indexFile) {
|
|
1506
|
+
let index;
|
|
1507
|
+
try {
|
|
1508
|
+
index = JSON.parse(fs5.readFileSync(path5.join(project, indexFile), "utf8"));
|
|
1509
|
+
} catch {
|
|
1510
|
+
return [];
|
|
1511
|
+
}
|
|
1512
|
+
const list = index?.designSystems;
|
|
1513
|
+
const records = Array.isArray(list) ? list : list && typeof list === "object" ? Object.values(list) : [];
|
|
1514
|
+
const out = [];
|
|
1515
|
+
for (const raw of records) {
|
|
1516
|
+
const r = raw;
|
|
1517
|
+
if (typeof r?.namespace !== "string" || !FOLDER2.test(r.namespace)) continue;
|
|
1518
|
+
out.push({
|
|
1519
|
+
namespace: r.namespace,
|
|
1520
|
+
title: typeof r.title === "string" ? r.title : r.namespace,
|
|
1521
|
+
artifact: typeof r.artifact === "string" ? r.artifact : ""
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
return out;
|
|
1525
|
+
}
|
|
1526
|
+
function plain(p, want) {
|
|
1527
|
+
const stat = fs5.lstatSync(p, { throwIfNoEntry: false });
|
|
1528
|
+
return Boolean(stat && !stat.isSymbolicLink() && (want === "file" ? stat.isFile() : stat.isDirectory()));
|
|
1529
|
+
}
|
|
1530
|
+
function walk(dir, prefix = "", out = []) {
|
|
1531
|
+
for (const entry of fs5.readdirSync(path5.join(dir, prefix), { withFileTypes: true })) {
|
|
1532
|
+
if (out.length >= MAX_FILES) break;
|
|
1533
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1534
|
+
if (entry.isDirectory()) walk(dir, rel, out);
|
|
1535
|
+
else if (entry.isFile()) out.push(rel);
|
|
1536
|
+
}
|
|
1537
|
+
return out;
|
|
1538
|
+
}
|
|
1539
|
+
var projectOf = (candidate) => path5.basename(candidate) === "project" ? candidate : path5.join(candidate, "project");
|
|
1540
|
+
function sourceProject(artifact, root, from) {
|
|
1541
|
+
const given = from ?? artifact;
|
|
1542
|
+
if (!given) return { reason: "the record names no source folder", kind: "none" };
|
|
1543
|
+
if (!from && /^[a-z][a-z0-9+.-]*:\/\//i.test(given)) return { reason: `its source is ${given}, which is not a folder on this machine`, kind: "remote" };
|
|
1544
|
+
const candidates = path5.isAbsolute(given) ? [given] : [path5.resolve(given), path5.resolve(root, given), path5.resolve(root, "..", given)];
|
|
1545
|
+
for (const candidate of candidates) {
|
|
1546
|
+
const project = projectOf(candidate);
|
|
1547
|
+
if (plain(project, "dir")) return { project };
|
|
1548
|
+
}
|
|
1549
|
+
if (!from && !path5.isAbsolute(given)) {
|
|
1550
|
+
let dir = path5.resolve(root, "..");
|
|
1551
|
+
for (let up = path5.dirname(dir); up !== dir; up = path5.dirname(dir)) {
|
|
1552
|
+
dir = up;
|
|
1553
|
+
const project = projectOf(path5.resolve(dir, given));
|
|
1554
|
+
if (plain(path5.join(project, DESIGN_SYSTEM_FILE), "file")) return { project };
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return { reason: `its source folder ${given} was not found${from ? "" : ` (tried from ${process.cwd()} and the folders above the artifact); pass --from <folder>`}`, kind: "not-found" };
|
|
1558
|
+
}
|
|
1559
|
+
function compareSystem(project, root, record, from) {
|
|
1560
|
+
const diff = { namespace: record.namespace, title: record.title, changed: [], removed: [] };
|
|
1561
|
+
const found = sourceProject(record.artifact, root, from);
|
|
1562
|
+
if (!found.project) return { ...diff, skipped: found.reason, skippedKind: found.kind };
|
|
1563
|
+
diff.source = path5.dirname(found.project);
|
|
1564
|
+
const copy = path5.join(project, "ds", record.namespace);
|
|
1565
|
+
if (!plain(copy, "dir")) return { ...diff, missing: true };
|
|
1566
|
+
for (const rel of walk(copy)) {
|
|
1567
|
+
const mine = path5.join(copy, rel);
|
|
1568
|
+
const theirs = path5.join(found.project, rel);
|
|
1569
|
+
if (!plain(mine, "file")) continue;
|
|
1570
|
+
if (!plain(theirs, "file")) {
|
|
1571
|
+
diff.removed.push(rel);
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
const a = fs5.statSync(mine);
|
|
1575
|
+
const b = fs5.statSync(theirs);
|
|
1576
|
+
if (a.size > MAX_FILE_BYTES2 || b.size > MAX_FILE_BYTES2) continue;
|
|
1577
|
+
if (a.size !== b.size || !fs5.readFileSync(mine).equals(fs5.readFileSync(theirs))) diff.changed.push(rel);
|
|
1578
|
+
}
|
|
1579
|
+
return diff;
|
|
1580
|
+
}
|
|
1581
|
+
function systemStatus(project, root, indexFile, namespace) {
|
|
1582
|
+
const record = readRecords(project, indexFile).find((r) => r.namespace === namespace);
|
|
1583
|
+
if (!record) return null;
|
|
1584
|
+
const diff = compareSystem(project, root, record);
|
|
1585
|
+
if (diff.skippedKind) return { state: "unknown", changed: 0, removed: 0, reason: diff.skippedKind };
|
|
1586
|
+
if (diff.missing) return { state: "unknown", changed: 0, removed: 0, reason: "missing" };
|
|
1587
|
+
return { state: diff.changed.length > 0 ? "behind" : "current", changed: diff.changed.length, removed: diff.removed.length };
|
|
1588
|
+
}
|
|
1589
|
+
var DS_SYNC = /^__ds-sync\/([a-z0-9][a-z0-9_-]{0,63})$/;
|
|
1590
|
+
function pick(records, namespace) {
|
|
1591
|
+
if (namespace === void 0) return records;
|
|
1592
|
+
const one = records.filter((r) => r.namespace === namespace);
|
|
1593
|
+
if (one.length === 0) {
|
|
1594
|
+
const names = records.map((r) => r.namespace);
|
|
1595
|
+
throw new Error(`No designSystems record has namespace ${JSON.stringify(namespace)}${names.length ? ` (it has: ${names.join(", ")})` : ""}.`);
|
|
1596
|
+
}
|
|
1597
|
+
return one;
|
|
1598
|
+
}
|
|
1599
|
+
function compareSystems(project, root, indexFile, namespace, from) {
|
|
1600
|
+
return pick(readRecords(project, indexFile), namespace).map((r) => compareSystem(project, root, r, from));
|
|
1601
|
+
}
|
|
1602
|
+
function stamp(project, indexFile, namespaces, now) {
|
|
1603
|
+
const file = path5.join(project, indexFile);
|
|
1604
|
+
const text = fs5.readFileSync(file, "utf8");
|
|
1605
|
+
const index = JSON.parse(text);
|
|
1606
|
+
const list = index.designSystems;
|
|
1607
|
+
const records = Array.isArray(list) ? list : list && typeof list === "object" ? Object.values(list) : [];
|
|
1608
|
+
for (const r of records) {
|
|
1609
|
+
const record = r;
|
|
1610
|
+
if (record && typeof record.namespace === "string" && namespaces.has(record.namespace)) record.copiedAt = now;
|
|
1611
|
+
}
|
|
1612
|
+
const indent = /^\{\n/.test(text) ? /\n( +|\t)"/.exec(text)?.[1] ?? 2 : void 0;
|
|
1613
|
+
const out = JSON.stringify(index, null, indent);
|
|
1614
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1615
|
+
fs5.writeFileSync(tmp, text.endsWith("\n") ? `${out}
|
|
1616
|
+
` : out);
|
|
1617
|
+
fs5.renameSync(tmp, file);
|
|
1618
|
+
}
|
|
1619
|
+
function syncSystems(project, root, indexFile, opts) {
|
|
1620
|
+
const diffs = compareSystems(project, root, indexFile, opts.namespace, opts.from);
|
|
1621
|
+
const results = [];
|
|
1622
|
+
const synced = /* @__PURE__ */ new Set();
|
|
1623
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1624
|
+
for (const diff of diffs) {
|
|
1625
|
+
const result = { ...diff, copied: [] };
|
|
1626
|
+
if (!opts.check && diff.source && !diff.missing) {
|
|
1627
|
+
for (const rel of diff.changed) {
|
|
1628
|
+
const target = path5.join(project, "ds", diff.namespace, rel);
|
|
1629
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
1630
|
+
fs5.copyFileSync(path5.join(diff.source, "project", rel), tmp);
|
|
1631
|
+
fs5.renameSync(tmp, target);
|
|
1632
|
+
result.copied.push(rel);
|
|
1633
|
+
}
|
|
1634
|
+
if (diff.changed.length > 0) synced.add(diff.namespace);
|
|
1635
|
+
}
|
|
1636
|
+
results.push(result);
|
|
1637
|
+
}
|
|
1638
|
+
if (synced.size > 0) {
|
|
1639
|
+
stamp(project, indexFile, synced, now);
|
|
1640
|
+
for (const r of results) if (synced.has(r.namespace)) r.copiedAt = now;
|
|
1641
|
+
}
|
|
1642
|
+
return results;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
1646
|
+
import fs6 from "node:fs";
|
|
1647
|
+
import path6 from "node:path";
|
|
1502
1648
|
var HISTORY_DIR = "history";
|
|
1503
1649
|
var INDEX = "index.json";
|
|
1504
1650
|
var OBJECTS = "objects";
|
|
@@ -1513,13 +1659,13 @@ var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
|
1513
1659
|
var SHA = /^[0-9a-f]{64}$/;
|
|
1514
1660
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1515
1661
|
function writeAtomic2(file, data) {
|
|
1516
|
-
|
|
1517
|
-
const tmp =
|
|
1662
|
+
fs6.mkdirSync(path6.dirname(file), { recursive: true });
|
|
1663
|
+
const tmp = path6.join(path6.dirname(file), `.${path6.basename(file)}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
|
|
1518
1664
|
try {
|
|
1519
|
-
|
|
1520
|
-
|
|
1665
|
+
fs6.writeFileSync(tmp, data);
|
|
1666
|
+
fs6.renameSync(tmp, file);
|
|
1521
1667
|
} catch (error) {
|
|
1522
|
-
|
|
1668
|
+
fs6.rmSync(tmp, { force: true });
|
|
1523
1669
|
throw error;
|
|
1524
1670
|
}
|
|
1525
1671
|
}
|
|
@@ -1600,10 +1746,10 @@ var History = class {
|
|
|
1600
1746
|
/** path → sha: what the files hold after the newest version. */
|
|
1601
1747
|
headState;
|
|
1602
1748
|
constructor(root) {
|
|
1603
|
-
this.dir =
|
|
1749
|
+
this.dir = path6.join(root, HISTORY_DIR);
|
|
1604
1750
|
let raw = null;
|
|
1605
1751
|
try {
|
|
1606
|
-
raw = JSON.parse(
|
|
1752
|
+
raw = JSON.parse(fs6.readFileSync(path6.join(this.dir, INDEX), "utf8"));
|
|
1607
1753
|
} catch {
|
|
1608
1754
|
}
|
|
1609
1755
|
this.index = parseIndex(raw);
|
|
@@ -1647,7 +1793,7 @@ var History = class {
|
|
|
1647
1793
|
read(sha) {
|
|
1648
1794
|
if (!SHA.test(sha)) return null;
|
|
1649
1795
|
try {
|
|
1650
|
-
return
|
|
1796
|
+
return fs6.readFileSync(path6.join(this.dir, OBJECTS, sha));
|
|
1651
1797
|
} catch {
|
|
1652
1798
|
return null;
|
|
1653
1799
|
}
|
|
@@ -1661,7 +1807,7 @@ var History = class {
|
|
|
1661
1807
|
size(sha) {
|
|
1662
1808
|
if (!SHA.test(sha)) return null;
|
|
1663
1809
|
try {
|
|
1664
|
-
return
|
|
1810
|
+
return fs6.statSync(path6.join(this.dir, OBJECTS, sha)).size;
|
|
1665
1811
|
} catch {
|
|
1666
1812
|
return null;
|
|
1667
1813
|
}
|
|
@@ -1795,8 +1941,8 @@ var History = class {
|
|
|
1795
1941
|
return out;
|
|
1796
1942
|
}
|
|
1797
1943
|
putObject(sha, bytes) {
|
|
1798
|
-
const file =
|
|
1799
|
-
if (
|
|
1944
|
+
const file = path6.join(this.dir, OBJECTS, sha);
|
|
1945
|
+
if (fs6.existsSync(file)) return;
|
|
1800
1946
|
writeAtomic2(file, bytes);
|
|
1801
1947
|
}
|
|
1802
1948
|
/** Past MAX_VERSIONS the oldest fold into the base; old notices go; contents no one holds are removed. */
|
|
@@ -1833,14 +1979,14 @@ var History = class {
|
|
|
1833
1979
|
}
|
|
1834
1980
|
let names;
|
|
1835
1981
|
try {
|
|
1836
|
-
names =
|
|
1982
|
+
names = fs6.readdirSync(path6.join(this.dir, OBJECTS));
|
|
1837
1983
|
} catch {
|
|
1838
1984
|
return;
|
|
1839
1985
|
}
|
|
1840
|
-
for (const name of names) if (SHA.test(name) && !keep.has(name))
|
|
1986
|
+
for (const name of names) if (SHA.test(name) && !keep.has(name)) fs6.rmSync(path6.join(this.dir, OBJECTS, name), { force: true });
|
|
1841
1987
|
}
|
|
1842
1988
|
save() {
|
|
1843
|
-
writeAtomic2(
|
|
1989
|
+
writeAtomic2(path6.join(this.dir, INDEX), `${JSON.stringify(this.index)}
|
|
1844
1990
|
`);
|
|
1845
1991
|
}
|
|
1846
1992
|
};
|
|
@@ -1910,19 +2056,19 @@ function diffContents(before, after) {
|
|
|
1910
2056
|
}
|
|
1911
2057
|
|
|
1912
2058
|
import { createHash as createHash2 } from "node:crypto";
|
|
1913
|
-
import
|
|
2059
|
+
import fs10 from "node:fs";
|
|
1914
2060
|
import os4 from "node:os";
|
|
1915
|
-
import
|
|
2061
|
+
import path10 from "node:path";
|
|
1916
2062
|
|
|
1917
|
-
import
|
|
2063
|
+
import fs8 from "node:fs";
|
|
1918
2064
|
import fsp2 from "node:fs/promises";
|
|
1919
|
-
import
|
|
2065
|
+
import path8 from "node:path";
|
|
1920
2066
|
import { promisify } from "node:util";
|
|
1921
2067
|
import zlib from "node:zlib";
|
|
1922
2068
|
|
|
1923
2069
|
import crypto from "node:crypto";
|
|
1924
|
-
import
|
|
1925
|
-
import
|
|
2070
|
+
import fs7 from "node:fs";
|
|
2071
|
+
import path7 from "node:path";
|
|
1926
2072
|
var GUEST_ROLES = ["editor", "commenter", "viewer"];
|
|
1927
2073
|
var isGuestRole = (value) => typeof value === "string" && GUEST_ROLES.includes(value);
|
|
1928
2074
|
var SHARING_FILE = ".sharing.json";
|
|
@@ -1949,13 +2095,13 @@ var Sharing = class _Sharing {
|
|
|
1949
2095
|
file;
|
|
1950
2096
|
state;
|
|
1951
2097
|
constructor(root) {
|
|
1952
|
-
this.file =
|
|
2098
|
+
this.file = path7.join(root, SHARING_FILE);
|
|
1953
2099
|
this.state = _Sharing.read(this.file);
|
|
1954
2100
|
}
|
|
1955
2101
|
static read(file) {
|
|
1956
2102
|
let raw;
|
|
1957
2103
|
try {
|
|
1958
|
-
raw = JSON.parse(
|
|
2104
|
+
raw = JSON.parse(fs7.readFileSync(file, "utf8"));
|
|
1959
2105
|
} catch {
|
|
1960
2106
|
return fresh();
|
|
1961
2107
|
}
|
|
@@ -1987,9 +2133,9 @@ var Sharing = class _Sharing {
|
|
|
1987
2133
|
}
|
|
1988
2134
|
save() {
|
|
1989
2135
|
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
1990
|
-
|
|
2136
|
+
fs7.writeFileSync(tmp, `${JSON.stringify(this.state, null, 2)}
|
|
1991
2137
|
`, { mode: 384 });
|
|
1992
|
-
|
|
2138
|
+
fs7.renameSync(tmp, this.file);
|
|
1993
2139
|
}
|
|
1994
2140
|
/** The role whose link has this token, while that link is on. */
|
|
1995
2141
|
linkRole(tokenGiven) {
|
|
@@ -2071,10 +2217,10 @@ var ArchiveTooLarge = class extends Error {
|
|
|
2071
2217
|
code = "TOO_LARGE";
|
|
2072
2218
|
};
|
|
2073
2219
|
async function archiveEntries(root, skip = []) {
|
|
2074
|
-
const folder =
|
|
2075
|
-
const skipped = new Set(skip.map((file) =>
|
|
2220
|
+
const folder = path8.basename(path8.resolve(root)) || "artifact";
|
|
2221
|
+
const skipped = new Set(skip.map((file) => path8.resolve(file)));
|
|
2076
2222
|
const entries = [];
|
|
2077
|
-
const
|
|
2223
|
+
const walk2 = async (dir, rel) => {
|
|
2078
2224
|
let names;
|
|
2079
2225
|
try {
|
|
2080
2226
|
names = await fsp2.readdir(dir, { withFileTypes: true });
|
|
@@ -2085,17 +2231,17 @@ async function archiveEntries(root, skip = []) {
|
|
|
2085
2231
|
for (const dirent of names.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
2086
2232
|
const name = dirent.name;
|
|
2087
2233
|
if (JUNK_FILES.has(name) || isTemp(name) || !rel && (LOCAL_FILES.has(name) || LOCAL_DIRS.has(name) && dirent.isDirectory())) continue;
|
|
2088
|
-
const file =
|
|
2234
|
+
const file = path8.join(dir, name);
|
|
2089
2235
|
if (skipped.has(file)) continue;
|
|
2090
2236
|
const inner = rel ? `${rel}/${name}` : name;
|
|
2091
|
-
if (dirent.isDirectory()) await
|
|
2237
|
+
if (dirent.isDirectory()) await walk2(file, inner);
|
|
2092
2238
|
else if (dirent.isFile()) {
|
|
2093
2239
|
const stat = await fsp2.stat(file).catch(() => null);
|
|
2094
2240
|
if (stat?.isFile()) entries.push({ name: `${folder}/${inner}`, file, size: stat.size, mode: stat.mode & 511, mtime: stat.mtime });
|
|
2095
2241
|
}
|
|
2096
2242
|
}
|
|
2097
2243
|
};
|
|
2098
|
-
await
|
|
2244
|
+
await walk2(path8.resolve(root), "");
|
|
2099
2245
|
if (entries.length > MAX_ENTRIES) throw new ArchiveTooLarge(`The folder holds ${entries.length} files, more than a zip holds (${MAX_ENTRIES}); copy the folder itself instead.`);
|
|
2100
2246
|
const bound = entries.reduce((sum, entry) => sum + entry.size + 2 * (46 + Buffer.byteLength(entry.name)), 22);
|
|
2101
2247
|
if (bound > MAX_BYTES) throw new ArchiveTooLarge(`The folder holds ${(bound / 2 ** 30).toFixed(1)} GiB, more than a zip holds (4 GiB); copy the folder itself instead.`);
|
|
@@ -2144,7 +2290,7 @@ async function writeZip(entries, write) {
|
|
|
2144
2290
|
const crc = crc32(data);
|
|
2145
2291
|
let method = 0;
|
|
2146
2292
|
let body = data;
|
|
2147
|
-
if (data.length > 0 && !STORED.has(
|
|
2293
|
+
if (data.length > 0 && !STORED.has(path8.extname(entry.name).toLowerCase())) {
|
|
2148
2294
|
const deflated = await deflateRaw(data);
|
|
2149
2295
|
if (deflated.length < data.length) {
|
|
2150
2296
|
method = 8;
|
|
@@ -2198,12 +2344,12 @@ async function writeZip(entries, write) {
|
|
|
2198
2344
|
}
|
|
2199
2345
|
function archiveFileName(root, now = /* @__PURE__ */ new Date()) {
|
|
2200
2346
|
const day = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2201
|
-
return `${
|
|
2347
|
+
return `${path8.basename(path8.resolve(root)) || "artifact"}-${day}.zip`;
|
|
2202
2348
|
}
|
|
2203
2349
|
async function writeArchive(root, out) {
|
|
2204
|
-
const file =
|
|
2350
|
+
const file = path8.resolve(out);
|
|
2205
2351
|
const entries = await archiveEntries(root, [file]);
|
|
2206
|
-
const tmp =
|
|
2352
|
+
const tmp = path8.join(path8.dirname(file), `.${path8.basename(file)}.${process.pid}.tmp`);
|
|
2207
2353
|
const handle = await fsp2.open(tmp, "w");
|
|
2208
2354
|
let bytes;
|
|
2209
2355
|
try {
|
|
@@ -2221,10 +2367,10 @@ async function writeArchive(root, out) {
|
|
|
2221
2367
|
return { file, files: entries.length, bytes };
|
|
2222
2368
|
}
|
|
2223
2369
|
async function archiveCommand(root, out) {
|
|
2224
|
-
const where = out ?
|
|
2225
|
-
const target =
|
|
2370
|
+
const where = out ? path8.resolve(out) : path8.dirname(path8.resolve(root));
|
|
2371
|
+
const target = fs8.statSync(where, { throwIfNoEntry: false })?.isDirectory() ? path8.join(where, archiveFileName(root)) : where;
|
|
2226
2372
|
const { file, files, bytes } = await writeArchive(root, target);
|
|
2227
|
-
return { file, folder:
|
|
2373
|
+
return { file, folder: path8.basename(path8.resolve(root)), files, bytes, leftOut: [...LOCAL_FILES] };
|
|
2228
2374
|
}
|
|
2229
2375
|
function attachment(name) {
|
|
2230
2376
|
const ascii = name.replace(/[^\x20-\x7e]|["\\]/g, "_");
|
|
@@ -2272,31 +2418,31 @@ async function sendArchive(request, response, root) {
|
|
|
2272
2418
|
}
|
|
2273
2419
|
}
|
|
2274
2420
|
|
|
2275
|
-
import
|
|
2421
|
+
import fs9 from "node:fs";
|
|
2276
2422
|
import fsp3 from "node:fs/promises";
|
|
2277
2423
|
import os3 from "node:os";
|
|
2278
|
-
import
|
|
2424
|
+
import path9 from "node:path";
|
|
2279
2425
|
var MAX_BYTES2 = 4294967295;
|
|
2280
2426
|
function shown(file) {
|
|
2281
2427
|
const home = os3.homedir();
|
|
2282
|
-
return home && (file === home || file.startsWith(home +
|
|
2428
|
+
return home && (file === home || file.startsWith(home + path9.sep)) ? `~${file.slice(home.length)}` : file;
|
|
2283
2429
|
}
|
|
2284
2430
|
function saved(file, bytes) {
|
|
2285
|
-
return { path: file, name:
|
|
2431
|
+
return { path: file, name: path9.basename(file), folder: path9.dirname(file), shown: shown(file), bytes };
|
|
2286
2432
|
}
|
|
2287
2433
|
function safeFileName(raw) {
|
|
2288
2434
|
let name = (raw.replace(/\\/g, "/").split("/").pop() ?? "").replace(/[\u0000-\u001f\u007f<>:"|?*]/g, "_").replace(/^[.\s]+|[.\s]+$/g, "");
|
|
2289
2435
|
if (name.length > 200) {
|
|
2290
|
-
const ext =
|
|
2436
|
+
const ext = path9.extname(name).slice(0, 16);
|
|
2291
2437
|
name = name.slice(0, 200 - ext.length) + ext;
|
|
2292
2438
|
}
|
|
2293
2439
|
return name || "download";
|
|
2294
2440
|
}
|
|
2295
2441
|
async function openFree(dir, name) {
|
|
2296
|
-
const ext =
|
|
2442
|
+
const ext = path9.extname(name);
|
|
2297
2443
|
const stem = name.slice(0, name.length - ext.length);
|
|
2298
2444
|
for (let n = 0; ; n++) {
|
|
2299
|
-
const file =
|
|
2445
|
+
const file = path9.join(dir, n === 0 ? name : `${stem} (${n})${ext}`);
|
|
2300
2446
|
try {
|
|
2301
2447
|
return { file, handle: await fsp3.open(file, "wx") };
|
|
2302
2448
|
} catch (error) {
|
|
@@ -2315,7 +2461,7 @@ async function saveDownload(request, response, root) {
|
|
|
2315
2461
|
let handle = null;
|
|
2316
2462
|
let bytes = 0;
|
|
2317
2463
|
try {
|
|
2318
|
-
const dir =
|
|
2464
|
+
const dir = path9.join(root, EXPORTS_DIR);
|
|
2319
2465
|
await fsp3.mkdir(dir, { recursive: true });
|
|
2320
2466
|
({ file, handle } = await openFree(dir, name));
|
|
2321
2467
|
for await (const chunk of request) {
|
|
@@ -2340,7 +2486,7 @@ async function saveArchive(request, response, root) {
|
|
|
2340
2486
|
request.resume();
|
|
2341
2487
|
try {
|
|
2342
2488
|
const { file } = await archiveCommand(root, void 0);
|
|
2343
|
-
sendJson2(response, 200, saved(file,
|
|
2489
|
+
sendJson2(response, 200, saved(file, fs9.statSync(file).size));
|
|
2344
2490
|
} catch (error) {
|
|
2345
2491
|
const tooLarge = error instanceof ArchiveTooLarge;
|
|
2346
2492
|
sendJson2(response, tooLarge ? 413 : 500, { code: tooLarge ? "too_large" : "upstream_error", message: error instanceof Error ? error.message : String(error) });
|
|
@@ -2381,7 +2527,7 @@ function localOwner() {
|
|
|
2381
2527
|
}
|
|
2382
2528
|
return { id: "local", name, email: `${name}@localhost` };
|
|
2383
2529
|
}
|
|
2384
|
-
var guestCookieName = (kind, root) => `${kind.replace(/-/g, "_")}_guest_${createHash2("sha256").update(
|
|
2530
|
+
var guestCookieName = (kind, root) => `${kind.replace(/-/g, "_")}_guest_${createHash2("sha256").update(path10.resolve(root)).digest("hex").slice(0, 12)}`;
|
|
2385
2531
|
var ShareHost = class {
|
|
2386
2532
|
sharing;
|
|
2387
2533
|
options;
|
|
@@ -2546,7 +2692,7 @@ var ShareHost = class {
|
|
|
2546
2692
|
}
|
|
2547
2693
|
if (method === "GET" && p === "/__share/invite") return sendJson2(response, 200, this.invites(viewer, request.headers.host ?? "")), true;
|
|
2548
2694
|
if (method === "GET" && p === "/__share/ui.js") {
|
|
2549
|
-
|
|
2695
|
+
fs10.stat(this.options.uiFile, (error, stat) => {
|
|
2550
2696
|
if (error || !stat.isFile()) return sendJson2(response, 404, { code: "not_found", message: "The Share panel is missing." });
|
|
2551
2697
|
sendFile(request, response, this.options.uiFile, { "Content-Type": "text/javascript; charset=utf-8", "Content-Length": stat.size, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
2552
2698
|
});
|
|
@@ -2566,7 +2712,7 @@ var ShareHost = class {
|
|
|
2566
2712
|
*/
|
|
2567
2713
|
headTags(_viewer, query = "") {
|
|
2568
2714
|
let tags = `<script src="/__share/boot.js${query.replace(/[<>"']/g, "").replace(/&/g, "&")}"></script>`;
|
|
2569
|
-
if (
|
|
2715
|
+
if (fs10.existsSync(this.options.uiFile)) tags += '<script type="module" src="/__share/ui.js"></script>';
|
|
2570
2716
|
return tags;
|
|
2571
2717
|
}
|
|
2572
2718
|
/** `html` with the head tags first in its <head>; null when it has none. */
|
|
@@ -2958,7 +3104,7 @@ function noticeView(n, label, lostAt, gone) {
|
|
|
2958
3104
|
...n.lost.sha === null ? { deleted: true } : {}
|
|
2959
3105
|
};
|
|
2960
3106
|
}
|
|
2961
|
-
var
|
|
3107
|
+
var plain2 = (text) => ({ en: text, zh: text });
|
|
2962
3108
|
|
|
2963
3109
|
var PEER_ID = /^[A-Za-z0-9_-]{8,64}$/;
|
|
2964
3110
|
function agentWhere(main2, count, label) {
|
|
@@ -3266,7 +3412,7 @@ async function collectFiles(root, limit) {
|
|
|
3266
3412
|
const seen = /* @__PURE__ */ new Set();
|
|
3267
3413
|
let truncated = false;
|
|
3268
3414
|
const visit = async (relDir, descend) => {
|
|
3269
|
-
const dir = relDir === "" ? root :
|
|
3415
|
+
const dir = relDir === "" ? root : path11.join(root, ...relDir.split("/"));
|
|
3270
3416
|
let entries;
|
|
3271
3417
|
try {
|
|
3272
3418
|
entries = await fsp4.readdir(dir, { withFileTypes: true });
|
|
@@ -3297,8 +3443,8 @@ async function collectFiles(root, limit) {
|
|
|
3297
3443
|
return { rels, truncated };
|
|
3298
3444
|
}
|
|
3299
3445
|
async function writeFileAtomic(file, bytes) {
|
|
3300
|
-
await fsp4.mkdir(
|
|
3301
|
-
const tmp =
|
|
3446
|
+
await fsp4.mkdir(path11.dirname(file), { recursive: true });
|
|
3447
|
+
const tmp = path11.join(path11.dirname(file), `.${path11.basename(file)}.${randomUUID()}.tmp`);
|
|
3302
3448
|
try {
|
|
3303
3449
|
await fsp4.writeFile(tmp, bytes);
|
|
3304
3450
|
await fsp4.rename(tmp, file);
|
|
@@ -3354,11 +3500,11 @@ var Store = class {
|
|
|
3354
3500
|
async diskPath(rel) {
|
|
3355
3501
|
const realRoot = await fsp4.realpath(this.root).catch(() => null);
|
|
3356
3502
|
if (realRoot === null) return null;
|
|
3357
|
-
const file =
|
|
3358
|
-
for (let dir =
|
|
3503
|
+
const file = path11.join(realRoot, ...rel.split("/"));
|
|
3504
|
+
for (let dir = path11.dirname(file); ; dir = path11.dirname(dir)) {
|
|
3359
3505
|
const real = await fsp4.realpath(dir).catch(() => null);
|
|
3360
3506
|
if (real === null) {
|
|
3361
|
-
if (
|
|
3507
|
+
if (path11.dirname(dir) === dir) return null;
|
|
3362
3508
|
continue;
|
|
3363
3509
|
}
|
|
3364
3510
|
if (real !== dir) return null;
|
|
@@ -3588,7 +3734,7 @@ var Store = class {
|
|
|
3588
3734
|
}
|
|
3589
3735
|
async findBlobFile(id) {
|
|
3590
3736
|
try {
|
|
3591
|
-
const names = await fsp4.readdir(
|
|
3737
|
+
const names = await fsp4.readdir(path11.join(this.root, LAYOUT.blobDir));
|
|
3592
3738
|
const name = names.find((n) => n.startsWith(`${id}.`) && !n.endsWith(".tmp"));
|
|
3593
3739
|
if (name === void 0) return null;
|
|
3594
3740
|
const file = await this.diskPath(`${LAYOUT.blobDir}/${name}`);
|
|
@@ -3643,13 +3789,13 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3643
3789
|
const directories = /* @__PURE__ */ new Map();
|
|
3644
3790
|
const visit = (rel, descend) => {
|
|
3645
3791
|
if (directories.size >= maxDirectories) return;
|
|
3646
|
-
const dir = rel === "" ? store.root :
|
|
3647
|
-
if (directories.has(dir) || !
|
|
3648
|
-
const info =
|
|
3792
|
+
const dir = rel === "" ? store.root : path11.join(store.root, ...rel.split("/"));
|
|
3793
|
+
if (directories.has(dir) || !fs11.existsSync(dir)) return;
|
|
3794
|
+
const info = fs11.lstatSync(dir);
|
|
3649
3795
|
if (!info.isDirectory()) return;
|
|
3650
3796
|
directories.set(dir, `${info.dev}:${info.ino}`);
|
|
3651
3797
|
if (!descend) return;
|
|
3652
|
-
for (const entry of
|
|
3798
|
+
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
3653
3799
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
3654
3800
|
const child = rel === "" ? entry.name : `${rel}/${entry.name}`;
|
|
3655
3801
|
if (LAYOUT.descendInto(child)) visit(child, true);
|
|
@@ -3666,19 +3812,19 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3666
3812
|
if (watchers.has(dir)) continue;
|
|
3667
3813
|
let watcher;
|
|
3668
3814
|
try {
|
|
3669
|
-
watcher =
|
|
3815
|
+
watcher = fs11.watch(dir, (event, name) => {
|
|
3670
3816
|
if (stopped) return;
|
|
3671
3817
|
if (name) {
|
|
3672
|
-
const changed =
|
|
3818
|
+
const changed = path11.join(dir, String(name));
|
|
3673
3819
|
if (event === "rename" && watchers.has(changed)) {
|
|
3674
3820
|
for (const [watchedDir, item] of watchers) {
|
|
3675
|
-
if (watchedDir === changed || watchedDir.startsWith(changed +
|
|
3821
|
+
if (watchedDir === changed || watchedDir.startsWith(changed + path11.sep)) {
|
|
3676
3822
|
item.watcher.close();
|
|
3677
3823
|
watchers.delete(watchedDir);
|
|
3678
3824
|
}
|
|
3679
3825
|
}
|
|
3680
3826
|
}
|
|
3681
|
-
const rel =
|
|
3827
|
+
const rel = path11.relative(store.root, changed).split(path11.sep).join("/");
|
|
3682
3828
|
if (isIgnoredPath(rel) || !LAYOUT.watches(rel)) return;
|
|
3683
3829
|
}
|
|
3684
3830
|
schedule();
|
|
@@ -3714,7 +3860,7 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3714
3860
|
const { rels } = await collectFiles(store.root, LAYOUT.maxFiles);
|
|
3715
3861
|
const parts = [];
|
|
3716
3862
|
for (const rel of rels) {
|
|
3717
|
-
const info = await fsp4.lstat(
|
|
3863
|
+
const info = await fsp4.lstat(path11.join(store.root, ...rel.split("/"))).catch(() => null);
|
|
3718
3864
|
parts.push(info ? `${rel}\0${info.size}\0${info.mtimeMs}\0${info.ino}` : `${rel}\0-`);
|
|
3719
3865
|
}
|
|
3720
3866
|
return parts.join("\n");
|
|
@@ -3745,11 +3891,11 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3745
3891
|
var publishError = (response, status, code, message) => sendJson2(response, status, { code, message });
|
|
3746
3892
|
async function startServer({ root, port = DEFAULT_PORTS[KIND], host = "127.0.0.1", log = (line) => process.stderr.write(`${line}
|
|
3747
3893
|
`), agentHook, lan = false, portGiven = false }) {
|
|
3748
|
-
const project =
|
|
3749
|
-
const
|
|
3894
|
+
const project = path11.join(root, "project");
|
|
3895
|
+
const stamp2 = (line) => log(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${line}`);
|
|
3750
3896
|
const projectDir = await fsp4.realpath(project);
|
|
3751
3897
|
const history = new History(root);
|
|
3752
|
-
const store = new Store(projectDir, history,
|
|
3898
|
+
const store = new Store(projectDir, history, stamp2);
|
|
3753
3899
|
const owner = localOwner();
|
|
3754
3900
|
const share = new ShareHost({
|
|
3755
3901
|
root,
|
|
@@ -3757,9 +3903,9 @@ async function startServer({ root, port = DEFAULT_PORTS[KIND], host = "127.0.0.1
|
|
|
3757
3903
|
owner,
|
|
3758
3904
|
noun: NOUNS[KIND] ?? { en: NOUN, zh: NOUN, zhThis: NOUN },
|
|
3759
3905
|
title: () => artifactTitle(root, projectDir),
|
|
3760
|
-
log:
|
|
3906
|
+
log: stamp2,
|
|
3761
3907
|
createServer: () => http.createServer(handle),
|
|
3762
|
-
uiFile:
|
|
3908
|
+
uiFile: path11.join(EDITOR_DIR, "share-ui.js"),
|
|
3763
3909
|
// The History panel's address and what this person may do there; on a version's page (?at=n), which version.
|
|
3764
3910
|
globals: (viewer, query) => {
|
|
3765
3911
|
const at = Number(query.get("at") ?? "");
|
|
@@ -3768,7 +3914,7 @@ async function startServer({ root, port = DEFAULT_PORTS[KIND], host = "127.0.0.1
|
|
|
3768
3914
|
});
|
|
3769
3915
|
const comments = createCommentsService({
|
|
3770
3916
|
root,
|
|
3771
|
-
log:
|
|
3917
|
+
log: stamp2,
|
|
3772
3918
|
locate: commentLocation ? (input) => commentLocation(input, projectDir) : void 0,
|
|
3773
3919
|
agentHook,
|
|
3774
3920
|
userName: owner.name,
|
|
@@ -3807,7 +3953,7 @@ data: ${JSON.stringify(event)}
|
|
|
3807
3953
|
}
|
|
3808
3954
|
return (p) => {
|
|
3809
3955
|
const rel = p.replace(/^project\//, "");
|
|
3810
|
-
return named?.(rel) ??
|
|
3956
|
+
return named?.(rel) ?? plain2(rel);
|
|
3811
3957
|
};
|
|
3812
3958
|
};
|
|
3813
3959
|
const agentWorked = (paths) => {
|
|
@@ -3838,16 +3984,16 @@ data: ${JSON.stringify(event)}
|
|
|
3838
3984
|
// A notice whose overwrite named the version its saver's edits started from: what was lost goes back by line into
|
|
3839
3985
|
// the file as it is now, the lost lines winning where both changed them; a deleted file gets all of it back.
|
|
3840
3986
|
recover: (_path, base, lost, now) => now === null ? lost : mergeTextBytes(base, lost, now),
|
|
3841
|
-
log:
|
|
3987
|
+
log: stamp2
|
|
3842
3988
|
});
|
|
3843
3989
|
const stopWatch = watchArtifact(
|
|
3844
3990
|
store,
|
|
3845
3991
|
(event) => {
|
|
3846
|
-
|
|
3992
|
+
stamp2(`changed on disk: ${event.paths.join(", ")}${clients.size ? "" : " (no page open)"}`);
|
|
3847
3993
|
changed({ ...event, by: authorOf(AGENT) });
|
|
3848
3994
|
agentWorked(event.paths);
|
|
3849
3995
|
},
|
|
3850
|
-
(error) =>
|
|
3996
|
+
(error) => stamp2(`watching failed: ${errorText(error)}`)
|
|
3851
3997
|
);
|
|
3852
3998
|
const events = async (request, response, viewer) => {
|
|
3853
3999
|
const given = new URL(request.url ?? "/", "http://local").searchParams.get("client") ?? "";
|
|
@@ -3910,7 +4056,7 @@ data: ${JSON.stringify(event)}
|
|
|
3910
4056
|
const parts = rest === "" ? ["index.html"] : rest.split("/");
|
|
3911
4057
|
const invalid = parts.some((p) => p === "" || p === "." || p === ".." || p.startsWith(".") || p.includes("\\")) || parts[0] === "node";
|
|
3912
4058
|
const realDist = invalid ? null : await fsp4.realpath(EDITOR_DIR).catch(() => null);
|
|
3913
|
-
const file = realDist === null ? null :
|
|
4059
|
+
const file = realDist === null ? null : path11.join(realDist, ...parts);
|
|
3914
4060
|
const real = file === null ? null : await fsp4.realpath(file).catch(() => null);
|
|
3915
4061
|
const info = real === null ? null : await fsp4.lstat(real).catch(() => null);
|
|
3916
4062
|
if (file === null || real !== file || !info?.isFile()) {
|
|
@@ -3921,7 +4067,7 @@ data: ${JSON.stringify(event)}
|
|
|
3921
4067
|
response.writeHead(404, { "Cache-Control": "no-store" }).end();
|
|
3922
4068
|
return;
|
|
3923
4069
|
}
|
|
3924
|
-
const ext =
|
|
4070
|
+
const ext = path11.extname(file).toLowerCase();
|
|
3925
4071
|
const type = STATIC_TYPES[ext] ?? "application/octet-stream";
|
|
3926
4072
|
if (ext === ".html") {
|
|
3927
4073
|
const key = parts.join("/");
|
|
@@ -3976,7 +4122,7 @@ data: ${JSON.stringify(event)}
|
|
|
3976
4122
|
const outcome = await store.publish(files, whoOf(viewer), over, merged, CLIENT_ID.test(page) ? page : "");
|
|
3977
4123
|
const names = Object.keys(files).map((p) => p.replace(/^project\//, ""));
|
|
3978
4124
|
const made = outcome.version ? ` as version ${outcome.version.n}` : "";
|
|
3979
|
-
|
|
4125
|
+
stamp2(`saved from the page by ${viewer.user.name} (${outcome.status}${outcome.body.code ? ` ${outcome.body.code}` : ""})${made}: ${names.join(", ")}`);
|
|
3980
4126
|
sendJson2(response, outcome.status, outcome.body);
|
|
3981
4127
|
if (outcome.status === 200 && clients.size > 0) {
|
|
3982
4128
|
const from = String(request.headers["x-deck-client"] ?? "");
|
|
@@ -3997,7 +4143,7 @@ data: ${JSON.stringify(event)}
|
|
|
3997
4143
|
return;
|
|
3998
4144
|
}
|
|
3999
4145
|
const result = await store.putBlob(bytes, contentType);
|
|
4000
|
-
|
|
4146
|
+
stamp2(`uploaded from the page: ${result.url} (${contentType}, ${bytes.length} bytes)`);
|
|
4001
4147
|
sendJson2(response, 200, result);
|
|
4002
4148
|
};
|
|
4003
4149
|
const session = (viewer) => {
|
|
@@ -4110,6 +4256,24 @@ data: ${JSON.stringify(event)}
|
|
|
4110
4256
|
else await upload(request, response);
|
|
4111
4257
|
return;
|
|
4112
4258
|
}
|
|
4259
|
+
const dsSync = DESIGN_SYSTEMS ? DS_SYNC.exec(rest) : null;
|
|
4260
|
+
if (dsSync) {
|
|
4261
|
+
const folder = dsSync[1];
|
|
4262
|
+
const status = systemStatus(projectDir, root, INDEX_FILE, folder);
|
|
4263
|
+
if (method === "POST") {
|
|
4264
|
+
request.resume();
|
|
4265
|
+
if (!sameOrigin(request)) return publishError(response, 403, "not_writer", "cross-origin request");
|
|
4266
|
+
if (viewer.guest) return publishError(response, 403, "not_owner", "Only the owner of this computer can sync a design system from its source.");
|
|
4267
|
+
if (status === null) return sendJson2(response, 404, { error: "not_found" });
|
|
4268
|
+
const [result] = syncSystems(projectDir, root, INDEX_FILE, { namespace: folder });
|
|
4269
|
+
return sendJson2(response, 200, { copied: result.copied.length, ...systemStatus(projectDir, root, INDEX_FILE, folder), canSync: true });
|
|
4270
|
+
}
|
|
4271
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
4272
|
+
response.writeHead(405, { Allow: "GET, HEAD, POST" }).end();
|
|
4273
|
+
return;
|
|
4274
|
+
}
|
|
4275
|
+
return status === null ? sendJson2(response, 404, { error: "not_found" }, { "Cache-Control": "no-store" }) : sendJson2(response, 200, { ...status, canSync: !viewer.guest }, { "Cache-Control": "no-store" });
|
|
4276
|
+
}
|
|
4113
4277
|
if (method !== "GET" && method !== "HEAD") {
|
|
4114
4278
|
response.writeHead(405, { Allow: "GET, HEAD" }).end();
|
|
4115
4279
|
return;
|
|
@@ -4165,7 +4329,7 @@ data: ${JSON.stringify(event)}
|
|
|
4165
4329
|
if (!response.headersSent) publishError(response, 413, "too_large", "request body is too large");
|
|
4166
4330
|
return;
|
|
4167
4331
|
}
|
|
4168
|
-
|
|
4332
|
+
stamp2(`request failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
4169
4333
|
if (!response.headersSent) publishError(response, 500, "upstream_error", "internal error");
|
|
4170
4334
|
else response.destroy();
|
|
4171
4335
|
}
|
|
@@ -4228,17 +4392,17 @@ data: ${JSON.stringify(event)}
|
|
|
4228
4392
|
throw error;
|
|
4229
4393
|
});
|
|
4230
4394
|
const url = `${serverOrigin(host, actualPort)}${editorPath}`;
|
|
4231
|
-
|
|
4395
|
+
stamp2(`serving ${project} at ${url}`);
|
|
4232
4396
|
const lanUrls = share.lanUrls();
|
|
4233
4397
|
return { url, port: actualPort, grant, close, ...lanUrls.length ? { lan: lanUrls } : {} };
|
|
4234
4398
|
}
|
|
4235
4399
|
function artifactTitle(root, projectDir) {
|
|
4236
4400
|
try {
|
|
4237
|
-
const index = JSON.parse(
|
|
4401
|
+
const index = JSON.parse(fs11.readFileSync(path11.join(projectDir, INDEX_FILE), "utf8"));
|
|
4238
4402
|
for (const value of [index.title, index.name]) if (typeof value === "string" && value.trim() && value.trim() !== "project") return value.trim();
|
|
4239
4403
|
} catch {
|
|
4240
4404
|
}
|
|
4241
|
-
return
|
|
4405
|
+
return path11.basename(root);
|
|
4242
4406
|
}
|
|
4243
4407
|
var runningServer2 = (root) => runningServer(root, KIND, ROUTES.session);
|
|
4244
4408
|
async function serveForeground({ root, port, host, agentHook, lan, portGiven }) {
|
|
@@ -4259,18 +4423,18 @@ async function main(argv) {
|
|
|
4259
4423
|
}
|
|
4260
4424
|
if (positional.length !== 1 || flags.has("help")) {
|
|
4261
4425
|
process.stderr.write(
|
|
4262
|
-
`Usage: node ${
|
|
4426
|
+
`Usage: node ${path11.basename(SCRIPT)} <root> [--port n] [--host h] [--lan] [--agent-hook "<command>"]
|
|
4263
4427
|
<root> holds project/${INDEX_FILE}.
|
|
4264
4428
|
`
|
|
4265
4429
|
);
|
|
4266
4430
|
return 2;
|
|
4267
4431
|
}
|
|
4268
|
-
let root =
|
|
4269
|
-
if (
|
|
4270
|
-
root =
|
|
4432
|
+
let root = path11.resolve(positional[0]);
|
|
4433
|
+
if (path11.basename(root) === "project" && fs11.existsSync(path11.join(root, INDEX_FILE)) && !fs11.existsSync(path11.join(root, "project"))) {
|
|
4434
|
+
root = path11.dirname(root);
|
|
4271
4435
|
}
|
|
4272
|
-
if (!
|
|
4273
|
-
process.stderr.write(`${JSON.stringify({ error: "NOT_FOUND", message: `${
|
|
4436
|
+
if (!fs11.statSync(path11.join(root, "project", INDEX_FILE), { throwIfNoEntry: false })?.isFile()) {
|
|
4437
|
+
process.stderr.write(`${JSON.stringify({ error: "NOT_FOUND", message: `${path11.join(root, "project", INDEX_FILE)} does not exist.` })}
|
|
4274
4438
|
`);
|
|
4275
4439
|
return 2;
|
|
4276
4440
|
}
|
|
@@ -4294,7 +4458,7 @@ async function main(argv) {
|
|
|
4294
4458
|
}
|
|
4295
4459
|
function isMain(moduleUrl) {
|
|
4296
4460
|
try {
|
|
4297
|
-
return Boolean(process.argv[1]) && pathToFileURL(
|
|
4461
|
+
return Boolean(process.argv[1]) && pathToFileURL(fs11.realpathSync(process.argv[1])).href === moduleUrl;
|
|
4298
4462
|
} catch {
|
|
4299
4463
|
return false;
|
|
4300
4464
|
}
|
|
@@ -4311,7 +4475,7 @@ function createArtifactServer(kind, moduleUrl) {
|
|
|
4311
4475
|
kindLabels = kind.labels;
|
|
4312
4476
|
kindAgentContext = kind.agentContext;
|
|
4313
4477
|
SCRIPT = fileURLToPath(moduleUrl);
|
|
4314
|
-
EDITOR_DIR =
|
|
4478
|
+
EDITOR_DIR = path11.join(path11.dirname(SCRIPT), "editor");
|
|
4315
4479
|
const runIfMain = () => {
|
|
4316
4480
|
if (!isMain(moduleUrl)) return;
|
|
4317
4481
|
main(process.argv.slice(2)).then(
|
|
@@ -4362,19 +4526,19 @@ function commentLocation2(input, projectDir) {
|
|
|
4362
4526
|
let slideId = match[2];
|
|
4363
4527
|
if (!slideId) {
|
|
4364
4528
|
try {
|
|
4365
|
-
const order = JSON.parse(
|
|
4529
|
+
const order = JSON.parse(fs12.readFileSync(path12.join(projectDir, INDEX_FILE2), "utf8")).order;
|
|
4366
4530
|
if (Array.isArray(order) && typeof order[slide - 1] === "string") slideId = order[slide - 1];
|
|
4367
4531
|
} catch {
|
|
4368
4532
|
}
|
|
4369
4533
|
}
|
|
4370
4534
|
const file = slideId ? projectFile(`slides/${slideId}.html`) : void 0;
|
|
4371
|
-
if (slideId && file &&
|
|
4535
|
+
if (slideId && file && fs12.existsSync(path12.join(projectDir, "slides", `${slideId}.html`))) return { file, slide, slideId };
|
|
4372
4536
|
return { file: "project/deck.json", slide };
|
|
4373
4537
|
}
|
|
4374
4538
|
function labels(projectDir) {
|
|
4375
4539
|
let order = [];
|
|
4376
4540
|
try {
|
|
4377
|
-
const listed = JSON.parse(
|
|
4541
|
+
const listed = JSON.parse(fs12.readFileSync(path12.join(projectDir, INDEX_FILE2), "utf8")).order;
|
|
4378
4542
|
if (Array.isArray(listed)) order = listed.filter((id) => typeof id === "string");
|
|
4379
4543
|
} catch {
|
|
4380
4544
|
}
|
|
@@ -4394,7 +4558,7 @@ function agentContext(projectDir, rels) {
|
|
|
4394
4558
|
if (!slideId) return null;
|
|
4395
4559
|
let order = [];
|
|
4396
4560
|
try {
|
|
4397
|
-
const listed = JSON.parse(
|
|
4561
|
+
const listed = JSON.parse(fs12.readFileSync(path12.join(projectDir, INDEX_FILE2), "utf8")).order;
|
|
4398
4562
|
if (Array.isArray(listed)) order = listed.filter((id) => typeof id === "string");
|
|
4399
4563
|
} catch {
|
|
4400
4564
|
}
|