dsh-claude-design 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-design/SKILL.md +2 -1
- package/skills/claude-design/scripts/artifact.mjs +230 -49
- package/skills/claude-design/scripts/editor/assets/{index-DyKwtQBI.js → index-CYLuwRoc.js} +178 -178
- package/skills/claude-design/scripts/editor/index.html +1 -1
- package/skills/claude-design/scripts/server.mjs +263 -99
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// claude-design: 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) {
|
|
@@ -3258,7 +3404,7 @@ async function collectFiles(root, limit) {
|
|
|
3258
3404
|
const seen = /* @__PURE__ */ new Set();
|
|
3259
3405
|
let truncated = false;
|
|
3260
3406
|
const visit = async (relDir, descend) => {
|
|
3261
|
-
const dir = relDir === "" ? root :
|
|
3407
|
+
const dir = relDir === "" ? root : path11.join(root, ...relDir.split("/"));
|
|
3262
3408
|
let entries;
|
|
3263
3409
|
try {
|
|
3264
3410
|
entries = await fsp4.readdir(dir, { withFileTypes: true });
|
|
@@ -3289,8 +3435,8 @@ async function collectFiles(root, limit) {
|
|
|
3289
3435
|
return { rels, truncated };
|
|
3290
3436
|
}
|
|
3291
3437
|
async function writeFileAtomic(file, bytes) {
|
|
3292
|
-
await fsp4.mkdir(
|
|
3293
|
-
const tmp =
|
|
3438
|
+
await fsp4.mkdir(path11.dirname(file), { recursive: true });
|
|
3439
|
+
const tmp = path11.join(path11.dirname(file), `.${path11.basename(file)}.${randomUUID()}.tmp`);
|
|
3294
3440
|
try {
|
|
3295
3441
|
await fsp4.writeFile(tmp, bytes);
|
|
3296
3442
|
await fsp4.rename(tmp, file);
|
|
@@ -3346,11 +3492,11 @@ var Store = class {
|
|
|
3346
3492
|
async diskPath(rel) {
|
|
3347
3493
|
const realRoot = await fsp4.realpath(this.root).catch(() => null);
|
|
3348
3494
|
if (realRoot === null) return null;
|
|
3349
|
-
const file =
|
|
3350
|
-
for (let dir =
|
|
3495
|
+
const file = path11.join(realRoot, ...rel.split("/"));
|
|
3496
|
+
for (let dir = path11.dirname(file); ; dir = path11.dirname(dir)) {
|
|
3351
3497
|
const real = await fsp4.realpath(dir).catch(() => null);
|
|
3352
3498
|
if (real === null) {
|
|
3353
|
-
if (
|
|
3499
|
+
if (path11.dirname(dir) === dir) return null;
|
|
3354
3500
|
continue;
|
|
3355
3501
|
}
|
|
3356
3502
|
if (real !== dir) return null;
|
|
@@ -3580,7 +3726,7 @@ var Store = class {
|
|
|
3580
3726
|
}
|
|
3581
3727
|
async findBlobFile(id) {
|
|
3582
3728
|
try {
|
|
3583
|
-
const names = await fsp4.readdir(
|
|
3729
|
+
const names = await fsp4.readdir(path11.join(this.root, LAYOUT.blobDir));
|
|
3584
3730
|
const name = names.find((n) => n.startsWith(`${id}.`) && !n.endsWith(".tmp"));
|
|
3585
3731
|
if (name === void 0) return null;
|
|
3586
3732
|
const file = await this.diskPath(`${LAYOUT.blobDir}/${name}`);
|
|
@@ -3635,13 +3781,13 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3635
3781
|
const directories = /* @__PURE__ */ new Map();
|
|
3636
3782
|
const visit = (rel, descend) => {
|
|
3637
3783
|
if (directories.size >= maxDirectories) return;
|
|
3638
|
-
const dir = rel === "" ? store.root :
|
|
3639
|
-
if (directories.has(dir) || !
|
|
3640
|
-
const info =
|
|
3784
|
+
const dir = rel === "" ? store.root : path11.join(store.root, ...rel.split("/"));
|
|
3785
|
+
if (directories.has(dir) || !fs11.existsSync(dir)) return;
|
|
3786
|
+
const info = fs11.lstatSync(dir);
|
|
3641
3787
|
if (!info.isDirectory()) return;
|
|
3642
3788
|
directories.set(dir, `${info.dev}:${info.ino}`);
|
|
3643
3789
|
if (!descend) return;
|
|
3644
|
-
for (const entry of
|
|
3790
|
+
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
3645
3791
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
3646
3792
|
const child = rel === "" ? entry.name : `${rel}/${entry.name}`;
|
|
3647
3793
|
if (LAYOUT.descendInto(child)) visit(child, true);
|
|
@@ -3658,19 +3804,19 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3658
3804
|
if (watchers.has(dir)) continue;
|
|
3659
3805
|
let watcher;
|
|
3660
3806
|
try {
|
|
3661
|
-
watcher =
|
|
3807
|
+
watcher = fs11.watch(dir, (event, name) => {
|
|
3662
3808
|
if (stopped) return;
|
|
3663
3809
|
if (name) {
|
|
3664
|
-
const changed =
|
|
3810
|
+
const changed = path11.join(dir, String(name));
|
|
3665
3811
|
if (event === "rename" && watchers.has(changed)) {
|
|
3666
3812
|
for (const [watchedDir, item] of watchers) {
|
|
3667
|
-
if (watchedDir === changed || watchedDir.startsWith(changed +
|
|
3813
|
+
if (watchedDir === changed || watchedDir.startsWith(changed + path11.sep)) {
|
|
3668
3814
|
item.watcher.close();
|
|
3669
3815
|
watchers.delete(watchedDir);
|
|
3670
3816
|
}
|
|
3671
3817
|
}
|
|
3672
3818
|
}
|
|
3673
|
-
const rel =
|
|
3819
|
+
const rel = path11.relative(store.root, changed).split(path11.sep).join("/");
|
|
3674
3820
|
if (isIgnoredPath(rel) || !LAYOUT.watches(rel)) return;
|
|
3675
3821
|
}
|
|
3676
3822
|
schedule();
|
|
@@ -3706,7 +3852,7 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3706
3852
|
const { rels } = await collectFiles(store.root, LAYOUT.maxFiles);
|
|
3707
3853
|
const parts = [];
|
|
3708
3854
|
for (const rel of rels) {
|
|
3709
|
-
const info = await fsp4.lstat(
|
|
3855
|
+
const info = await fsp4.lstat(path11.join(store.root, ...rel.split("/"))).catch(() => null);
|
|
3710
3856
|
parts.push(info ? `${rel}\0${info.size}\0${info.mtimeMs}\0${info.ino}` : `${rel}\0-`);
|
|
3711
3857
|
}
|
|
3712
3858
|
return parts.join("\n");
|
|
@@ -3737,11 +3883,11 @@ function watchArtifact(store, onChange, onError) {
|
|
|
3737
3883
|
var publishError = (response, status, code, message) => sendJson2(response, status, { code, message });
|
|
3738
3884
|
async function startServer({ root, port = DEFAULT_PORTS[KIND], host = "127.0.0.1", log = (line) => process.stderr.write(`${line}
|
|
3739
3885
|
`), agentHook, lan = false, portGiven = false }) {
|
|
3740
|
-
const project =
|
|
3741
|
-
const
|
|
3886
|
+
const project = path11.join(root, "project");
|
|
3887
|
+
const stamp2 = (line) => log(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${line}`);
|
|
3742
3888
|
const projectDir = await fsp4.realpath(project);
|
|
3743
3889
|
const history = new History(root);
|
|
3744
|
-
const store = new Store(projectDir, history,
|
|
3890
|
+
const store = new Store(projectDir, history, stamp2);
|
|
3745
3891
|
const owner = localOwner();
|
|
3746
3892
|
const share = new ShareHost({
|
|
3747
3893
|
root,
|
|
@@ -3749,9 +3895,9 @@ async function startServer({ root, port = DEFAULT_PORTS[KIND], host = "127.0.0.1
|
|
|
3749
3895
|
owner,
|
|
3750
3896
|
noun: NOUNS[KIND] ?? { en: NOUN, zh: NOUN, zhThis: NOUN },
|
|
3751
3897
|
title: () => artifactTitle(root, projectDir),
|
|
3752
|
-
log:
|
|
3898
|
+
log: stamp2,
|
|
3753
3899
|
createServer: () => http.createServer(handle),
|
|
3754
|
-
uiFile:
|
|
3900
|
+
uiFile: path11.join(EDITOR_DIR, "share-ui.js"),
|
|
3755
3901
|
// The History panel's address and what this person may do there; on a version's page (?at=n), which version.
|
|
3756
3902
|
globals: (viewer, query) => {
|
|
3757
3903
|
const at = Number(query.get("at") ?? "");
|
|
@@ -3760,7 +3906,7 @@ async function startServer({ root, port = DEFAULT_PORTS[KIND], host = "127.0.0.1
|
|
|
3760
3906
|
});
|
|
3761
3907
|
const comments = createCommentsService({
|
|
3762
3908
|
root,
|
|
3763
|
-
log:
|
|
3909
|
+
log: stamp2,
|
|
3764
3910
|
locate: commentLocation ? (input) => commentLocation(input, projectDir) : void 0,
|
|
3765
3911
|
agentHook,
|
|
3766
3912
|
userName: owner.name,
|
|
@@ -3799,7 +3945,7 @@ data: ${JSON.stringify(event)}
|
|
|
3799
3945
|
}
|
|
3800
3946
|
return (p) => {
|
|
3801
3947
|
const rel = p.replace(/^project\//, "");
|
|
3802
|
-
return named?.(rel) ??
|
|
3948
|
+
return named?.(rel) ?? plain2(rel);
|
|
3803
3949
|
};
|
|
3804
3950
|
};
|
|
3805
3951
|
const agentWorked = (paths) => {
|
|
@@ -3830,16 +3976,16 @@ data: ${JSON.stringify(event)}
|
|
|
3830
3976
|
// A notice whose overwrite named the version its saver's edits started from: what was lost goes back by line into
|
|
3831
3977
|
// the file as it is now, the lost lines winning where both changed them; a deleted file gets all of it back.
|
|
3832
3978
|
recover: (_path, base, lost, now) => now === null ? lost : mergeTextBytes(base, lost, now),
|
|
3833
|
-
log:
|
|
3979
|
+
log: stamp2
|
|
3834
3980
|
});
|
|
3835
3981
|
const stopWatch = watchArtifact(
|
|
3836
3982
|
store,
|
|
3837
3983
|
(event) => {
|
|
3838
|
-
|
|
3984
|
+
stamp2(`changed on disk: ${event.paths.join(", ")}${clients.size ? "" : " (no page open)"}`);
|
|
3839
3985
|
changed({ ...event, by: authorOf(AGENT) });
|
|
3840
3986
|
agentWorked(event.paths);
|
|
3841
3987
|
},
|
|
3842
|
-
(error) =>
|
|
3988
|
+
(error) => stamp2(`watching failed: ${errorText(error)}`)
|
|
3843
3989
|
);
|
|
3844
3990
|
const events = async (request, response, viewer) => {
|
|
3845
3991
|
const given = new URL(request.url ?? "/", "http://local").searchParams.get("client") ?? "";
|
|
@@ -3902,7 +4048,7 @@ data: ${JSON.stringify(event)}
|
|
|
3902
4048
|
const parts = rest === "" ? ["index.html"] : rest.split("/");
|
|
3903
4049
|
const invalid = parts.some((p) => p === "" || p === "." || p === ".." || p.startsWith(".") || p.includes("\\")) || parts[0] === "node";
|
|
3904
4050
|
const realDist = invalid ? null : await fsp4.realpath(EDITOR_DIR).catch(() => null);
|
|
3905
|
-
const file = realDist === null ? null :
|
|
4051
|
+
const file = realDist === null ? null : path11.join(realDist, ...parts);
|
|
3906
4052
|
const real = file === null ? null : await fsp4.realpath(file).catch(() => null);
|
|
3907
4053
|
const info = real === null ? null : await fsp4.lstat(real).catch(() => null);
|
|
3908
4054
|
if (file === null || real !== file || !info?.isFile()) {
|
|
@@ -3913,7 +4059,7 @@ data: ${JSON.stringify(event)}
|
|
|
3913
4059
|
response.writeHead(404, { "Cache-Control": "no-store" }).end();
|
|
3914
4060
|
return;
|
|
3915
4061
|
}
|
|
3916
|
-
const ext =
|
|
4062
|
+
const ext = path11.extname(file).toLowerCase();
|
|
3917
4063
|
const type = STATIC_TYPES[ext] ?? "application/octet-stream";
|
|
3918
4064
|
if (ext === ".html") {
|
|
3919
4065
|
const key = parts.join("/");
|
|
@@ -3968,7 +4114,7 @@ data: ${JSON.stringify(event)}
|
|
|
3968
4114
|
const outcome = await store.publish(files, whoOf(viewer), over, merged, CLIENT_ID.test(page) ? page : "");
|
|
3969
4115
|
const names = Object.keys(files).map((p) => p.replace(/^project\//, ""));
|
|
3970
4116
|
const made = outcome.version ? ` as version ${outcome.version.n}` : "";
|
|
3971
|
-
|
|
4117
|
+
stamp2(`saved from the page by ${viewer.user.name} (${outcome.status}${outcome.body.code ? ` ${outcome.body.code}` : ""})${made}: ${names.join(", ")}`);
|
|
3972
4118
|
sendJson2(response, outcome.status, outcome.body);
|
|
3973
4119
|
if (outcome.status === 200 && clients.size > 0) {
|
|
3974
4120
|
const from = String(request.headers["x-deck-client"] ?? "");
|
|
@@ -3989,7 +4135,7 @@ data: ${JSON.stringify(event)}
|
|
|
3989
4135
|
return;
|
|
3990
4136
|
}
|
|
3991
4137
|
const result = await store.putBlob(bytes, contentType);
|
|
3992
|
-
|
|
4138
|
+
stamp2(`uploaded from the page: ${result.url} (${contentType}, ${bytes.length} bytes)`);
|
|
3993
4139
|
sendJson2(response, 200, result);
|
|
3994
4140
|
};
|
|
3995
4141
|
const session = (viewer) => {
|
|
@@ -4102,6 +4248,24 @@ data: ${JSON.stringify(event)}
|
|
|
4102
4248
|
else await upload(request, response);
|
|
4103
4249
|
return;
|
|
4104
4250
|
}
|
|
4251
|
+
const dsSync = DESIGN_SYSTEMS ? DS_SYNC.exec(rest) : null;
|
|
4252
|
+
if (dsSync) {
|
|
4253
|
+
const folder = dsSync[1];
|
|
4254
|
+
const status = systemStatus(projectDir, root, INDEX_FILE, folder);
|
|
4255
|
+
if (method === "POST") {
|
|
4256
|
+
request.resume();
|
|
4257
|
+
if (!sameOrigin(request)) return publishError(response, 403, "not_writer", "cross-origin request");
|
|
4258
|
+
if (viewer.guest) return publishError(response, 403, "not_owner", "Only the owner of this computer can sync a design system from its source.");
|
|
4259
|
+
if (status === null) return sendJson2(response, 404, { error: "not_found" });
|
|
4260
|
+
const [result] = syncSystems(projectDir, root, INDEX_FILE, { namespace: folder });
|
|
4261
|
+
return sendJson2(response, 200, { copied: result.copied.length, ...systemStatus(projectDir, root, INDEX_FILE, folder), canSync: true });
|
|
4262
|
+
}
|
|
4263
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
4264
|
+
response.writeHead(405, { Allow: "GET, HEAD, POST" }).end();
|
|
4265
|
+
return;
|
|
4266
|
+
}
|
|
4267
|
+
return status === null ? sendJson2(response, 404, { error: "not_found" }, { "Cache-Control": "no-store" }) : sendJson2(response, 200, { ...status, canSync: !viewer.guest }, { "Cache-Control": "no-store" });
|
|
4268
|
+
}
|
|
4105
4269
|
if (method !== "GET" && method !== "HEAD") {
|
|
4106
4270
|
response.writeHead(405, { Allow: "GET, HEAD" }).end();
|
|
4107
4271
|
return;
|
|
@@ -4157,7 +4321,7 @@ data: ${JSON.stringify(event)}
|
|
|
4157
4321
|
if (!response.headersSent) publishError(response, 413, "too_large", "request body is too large");
|
|
4158
4322
|
return;
|
|
4159
4323
|
}
|
|
4160
|
-
|
|
4324
|
+
stamp2(`request failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
4161
4325
|
if (!response.headersSent) publishError(response, 500, "upstream_error", "internal error");
|
|
4162
4326
|
else response.destroy();
|
|
4163
4327
|
}
|
|
@@ -4220,17 +4384,17 @@ data: ${JSON.stringify(event)}
|
|
|
4220
4384
|
throw error;
|
|
4221
4385
|
});
|
|
4222
4386
|
const url = `${serverOrigin(host, actualPort)}${editorPath}`;
|
|
4223
|
-
|
|
4387
|
+
stamp2(`serving ${project} at ${url}`);
|
|
4224
4388
|
const lanUrls = share.lanUrls();
|
|
4225
4389
|
return { url, port: actualPort, grant, close, ...lanUrls.length ? { lan: lanUrls } : {} };
|
|
4226
4390
|
}
|
|
4227
4391
|
function artifactTitle(root, projectDir) {
|
|
4228
4392
|
try {
|
|
4229
|
-
const index = JSON.parse(
|
|
4393
|
+
const index = JSON.parse(fs11.readFileSync(path11.join(projectDir, INDEX_FILE), "utf8"));
|
|
4230
4394
|
for (const value of [index.title, index.name]) if (typeof value === "string" && value.trim() && value.trim() !== "project") return value.trim();
|
|
4231
4395
|
} catch {
|
|
4232
4396
|
}
|
|
4233
|
-
return
|
|
4397
|
+
return path11.basename(root);
|
|
4234
4398
|
}
|
|
4235
4399
|
var runningServer2 = (root) => runningServer(root, KIND, ROUTES.session);
|
|
4236
4400
|
async function serveForeground({ root, port, host, agentHook, lan, portGiven }) {
|
|
@@ -4251,18 +4415,18 @@ async function main(argv) {
|
|
|
4251
4415
|
}
|
|
4252
4416
|
if (positional.length !== 1 || flags.has("help")) {
|
|
4253
4417
|
process.stderr.write(
|
|
4254
|
-
`Usage: node ${
|
|
4418
|
+
`Usage: node ${path11.basename(SCRIPT)} <root> [--port n] [--host h] [--lan] [--agent-hook "<command>"]
|
|
4255
4419
|
<root> holds project/${INDEX_FILE}.
|
|
4256
4420
|
`
|
|
4257
4421
|
);
|
|
4258
4422
|
return 2;
|
|
4259
4423
|
}
|
|
4260
|
-
let root =
|
|
4261
|
-
if (
|
|
4262
|
-
root =
|
|
4424
|
+
let root = path11.resolve(positional[0]);
|
|
4425
|
+
if (path11.basename(root) === "project" && fs11.existsSync(path11.join(root, INDEX_FILE)) && !fs11.existsSync(path11.join(root, "project"))) {
|
|
4426
|
+
root = path11.dirname(root);
|
|
4263
4427
|
}
|
|
4264
|
-
if (!
|
|
4265
|
-
process.stderr.write(`${JSON.stringify({ error: "NOT_FOUND", message: `${
|
|
4428
|
+
if (!fs11.statSync(path11.join(root, "project", INDEX_FILE), { throwIfNoEntry: false })?.isFile()) {
|
|
4429
|
+
process.stderr.write(`${JSON.stringify({ error: "NOT_FOUND", message: `${path11.join(root, "project", INDEX_FILE)} does not exist.` })}
|
|
4266
4430
|
`);
|
|
4267
4431
|
return 2;
|
|
4268
4432
|
}
|
|
@@ -4286,7 +4450,7 @@ async function main(argv) {
|
|
|
4286
4450
|
}
|
|
4287
4451
|
function isMain(moduleUrl) {
|
|
4288
4452
|
try {
|
|
4289
|
-
return Boolean(process.argv[1]) && pathToFileURL(
|
|
4453
|
+
return Boolean(process.argv[1]) && pathToFileURL(fs11.realpathSync(process.argv[1])).href === moduleUrl;
|
|
4290
4454
|
} catch {
|
|
4291
4455
|
return false;
|
|
4292
4456
|
}
|
|
@@ -4303,7 +4467,7 @@ function createArtifactServer(kind, moduleUrl) {
|
|
|
4303
4467
|
kindLabels = kind.labels;
|
|
4304
4468
|
kindAgentContext = kind.agentContext;
|
|
4305
4469
|
SCRIPT = fileURLToPath(moduleUrl);
|
|
4306
|
-
EDITOR_DIR =
|
|
4470
|
+
EDITOR_DIR = path11.join(path11.dirname(SCRIPT), "editor");
|
|
4307
4471
|
const runIfMain = () => {
|
|
4308
4472
|
if (!isMain(moduleUrl)) return;
|
|
4309
4473
|
main(process.argv.slice(2)).then(
|
|
@@ -4353,7 +4517,7 @@ function commentLocation2(input, projectDir) {
|
|
|
4353
4517
|
});
|
|
4354
4518
|
let boards = [];
|
|
4355
4519
|
try {
|
|
4356
|
-
const listed = JSON.parse(
|
|
4520
|
+
const listed = JSON.parse(fs12.readFileSync(path12.join(projectDir, INDEX_FILE2), "utf8")).boards;
|
|
4357
4521
|
if (listed && typeof listed === "object") boards = Object.keys(listed);
|
|
4358
4522
|
} catch {
|
|
4359
4523
|
}
|
|
@@ -4364,7 +4528,7 @@ function commentLocation2(input, projectDir) {
|
|
|
4364
4528
|
function labels(projectDir) {
|
|
4365
4529
|
const titles = /* @__PURE__ */ new Map();
|
|
4366
4530
|
try {
|
|
4367
|
-
const listed = JSON.parse(
|
|
4531
|
+
const listed = JSON.parse(fs12.readFileSync(path12.join(projectDir, INDEX_FILE2), "utf8")).boards;
|
|
4368
4532
|
if (listed && typeof listed === "object") {
|
|
4369
4533
|
for (const [file, board] of Object.entries(listed)) {
|
|
4370
4534
|
const title = board?.title;
|