@bendyline/docblocks-cli 2.6.1 → 2.7.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 +27 -15
- package/THIRD_PARTY_NOTICES.txt +3865 -3409
- package/dist/bin.js +794 -118
- package/dist/index.d.ts +56 -1
- package/dist/index.js +706 -46
- package/package.json +8 -8
package/dist/bin.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __esm = (fn, res) => function __init() {
|
|
5
|
-
|
|
4
|
+
var __esm = (fn, res, err) => function __init() {
|
|
5
|
+
if (err) throw err[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err = [e], e;
|
|
10
|
+
}
|
|
6
11
|
};
|
|
7
12
|
var __export = (target, all) => {
|
|
8
13
|
for (var name in all)
|
|
@@ -161,7 +166,7 @@ var init_cancellation = __esm({
|
|
|
161
166
|
});
|
|
162
167
|
|
|
163
168
|
// src/mcp/artifact-store.ts
|
|
164
|
-
import { createHash, randomUUID as
|
|
169
|
+
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
165
170
|
import { mkdtemp as mkdtemp2, open as open3, rm as rm2 } from "fs/promises";
|
|
166
171
|
import { join as join2 } from "path";
|
|
167
172
|
import { tmpdir } from "os";
|
|
@@ -211,9 +216,9 @@ function immutableArtifactRef(ref) {
|
|
|
211
216
|
);
|
|
212
217
|
return Object.freeze({ ...ref, appliedOptions, engineVersions });
|
|
213
218
|
}
|
|
214
|
-
async function writeFileCancellable(
|
|
219
|
+
async function writeFileCancellable(path10, bytes, signal) {
|
|
215
220
|
throwIfAborted4(signal);
|
|
216
|
-
const handle = await open3(
|
|
221
|
+
const handle = await open3(path10, "wx");
|
|
217
222
|
try {
|
|
218
223
|
let offset = 0;
|
|
219
224
|
while (offset < bytes.byteLength) {
|
|
@@ -228,9 +233,9 @@ async function writeFileCancellable(path9, bytes, signal) {
|
|
|
228
233
|
await handle.close();
|
|
229
234
|
}
|
|
230
235
|
}
|
|
231
|
-
async function readFileCancellable(
|
|
236
|
+
async function readFileCancellable(path10, options) {
|
|
232
237
|
throwIfAborted4(options.signal);
|
|
233
|
-
const handle = await open3(
|
|
238
|
+
const handle = await open3(path10, "r");
|
|
234
239
|
try {
|
|
235
240
|
const stats = await handle.stat();
|
|
236
241
|
throwIfAborted4(options.signal);
|
|
@@ -270,7 +275,7 @@ async function hashBytesCancellable(bytes, signal) {
|
|
|
270
275
|
return hash.digest("hex");
|
|
271
276
|
}
|
|
272
277
|
async function removeFilesBestEffort(paths) {
|
|
273
|
-
await Promise.allSettled(paths.map((
|
|
278
|
+
await Promise.allSettled(paths.map((path10) => rm2(path10, { force: true })));
|
|
274
279
|
}
|
|
275
280
|
async function waitForIoDrain(leases, timeoutMs) {
|
|
276
281
|
if (leases.length === 0) return;
|
|
@@ -383,8 +388,8 @@ var init_artifact_store = __esm({
|
|
|
383
388
|
throw new Error("MCP artifact exceeds the configured per-artifact limit");
|
|
384
389
|
}
|
|
385
390
|
const sha2562 = await hashBytesCancellable(bytes, signal);
|
|
386
|
-
const id =
|
|
387
|
-
const
|
|
391
|
+
const id = randomUUID3();
|
|
392
|
+
const path10 = join2(await this.directory, `${id}.artifact`);
|
|
388
393
|
throwIfAborted4(signal);
|
|
389
394
|
const created = /* @__PURE__ */ new Date();
|
|
390
395
|
const ref = immutableArtifactRef({
|
|
@@ -419,7 +424,7 @@ var init_artifact_store = __esm({
|
|
|
419
424
|
});
|
|
420
425
|
await removeFilesBestEffort(acquisition.expiredPaths);
|
|
421
426
|
try {
|
|
422
|
-
await this.dependencies.writeFile(
|
|
427
|
+
await this.dependencies.writeFile(path10, bytes, acquisition.io.controller.signal);
|
|
423
428
|
throwIfAborted4(acquisition.io.controller.signal);
|
|
424
429
|
await this.lock(() => {
|
|
425
430
|
this.assertActive();
|
|
@@ -427,7 +432,7 @@ var init_artifact_store = __esm({
|
|
|
427
432
|
this.releaseReservationLocked(reservation);
|
|
428
433
|
this.records.set(id, {
|
|
429
434
|
ref,
|
|
430
|
-
path:
|
|
435
|
+
path: path10,
|
|
431
436
|
conversionReport: null,
|
|
432
437
|
reportBytes: 0,
|
|
433
438
|
activeReaders: 0,
|
|
@@ -438,7 +443,7 @@ var init_artifact_store = __esm({
|
|
|
438
443
|
return ref;
|
|
439
444
|
} catch (caught) {
|
|
440
445
|
await this.lock(() => this.releaseReservationLocked(reservation));
|
|
441
|
-
await rm2(
|
|
446
|
+
await rm2(path10, { force: true }).catch(() => void 0);
|
|
442
447
|
throw caught;
|
|
443
448
|
} finally {
|
|
444
449
|
this.completeIoLease(acquisition.io);
|
|
@@ -551,8 +556,8 @@ var init_artifact_store = __esm({
|
|
|
551
556
|
for (const [id, stored] of this.records) {
|
|
552
557
|
const expiresAt = stored.ref.expiresAt ? Date.parse(stored.ref.expiresAt) : Number.POSITIVE_INFINITY;
|
|
553
558
|
if (expiresAt > now) continue;
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
559
|
+
const path10 = this.removeRecordLocked(id, stored);
|
|
560
|
+
if (path10) paths.push(path10);
|
|
556
561
|
}
|
|
557
562
|
return paths;
|
|
558
563
|
}
|
|
@@ -692,8 +697,8 @@ var init_artifact_store = __esm({
|
|
|
692
697
|
|
|
693
698
|
// src/mcp/authority.ts
|
|
694
699
|
import { lstat as lstat3, link as link2, open as open4, realpath as realpath4, rename as rename2, stat as stat5, unlink } from "fs/promises";
|
|
695
|
-
import
|
|
696
|
-
import { createHash as createHash2, randomUUID as
|
|
700
|
+
import path8 from "path";
|
|
701
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
697
702
|
import { MCP_WIRE_LIMITS as MCP_WIRE_LIMITS2 } from "@bendyline/docblocks/mcp";
|
|
698
703
|
async function resolveRoots(values) {
|
|
699
704
|
const roots = [];
|
|
@@ -713,7 +718,7 @@ function rootIdFor(physicalPath) {
|
|
|
713
718
|
return `root-${createHash2("sha256").update(normalized).digest("hex").slice(0, 16)}`;
|
|
714
719
|
}
|
|
715
720
|
function rootLabel(physicalPath) {
|
|
716
|
-
const candidate =
|
|
721
|
+
const candidate = path8.basename(physicalPath) || "root";
|
|
717
722
|
let label = "";
|
|
718
723
|
for (const character of candidate) {
|
|
719
724
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
@@ -724,7 +729,7 @@ function rootLabel(physicalPath) {
|
|
|
724
729
|
return label || "root";
|
|
725
730
|
}
|
|
726
731
|
function parseRootRelativePath(value) {
|
|
727
|
-
if (!value || value.length > MAX_PATH_CHARACTERS || value.includes("\0") || value.includes("\\") ||
|
|
732
|
+
if (!value || value.length > MAX_PATH_CHARACTERS || value.includes("\0") || value.includes("\\") || path8.posix.isAbsolute(value) || /^[a-zA-Z]:/u.test(value)) {
|
|
728
733
|
throw new Error("Invalid MCP root-relative path");
|
|
729
734
|
}
|
|
730
735
|
const segments = value.split("/");
|
|
@@ -742,7 +747,7 @@ async function nearestExistingPath(candidate) {
|
|
|
742
747
|
await lstat3(current);
|
|
743
748
|
return current;
|
|
744
749
|
} catch {
|
|
745
|
-
const parent =
|
|
750
|
+
const parent = path8.dirname(current);
|
|
746
751
|
if (parent === current) throw new Error("MCP output has no accessible parent directory");
|
|
747
752
|
current = parent;
|
|
748
753
|
}
|
|
@@ -752,7 +757,7 @@ function validatePath(value) {
|
|
|
752
757
|
if (!value || value.length > MAX_PATH_CHARACTERS || value.includes("\0")) {
|
|
753
758
|
throw new Error("Invalid MCP filesystem path");
|
|
754
759
|
}
|
|
755
|
-
return
|
|
760
|
+
return path8.resolve(value);
|
|
756
761
|
}
|
|
757
762
|
function validatePortableFilename(value) {
|
|
758
763
|
const stem = value.split(".", 1)[0]?.toLowerCase() ?? "";
|
|
@@ -769,7 +774,7 @@ function hasUnsafeFilenameCharacter(value) {
|
|
|
769
774
|
}
|
|
770
775
|
function samePath(left, right) {
|
|
771
776
|
const normalize = (value) => {
|
|
772
|
-
const resolved =
|
|
777
|
+
const resolved = path8.resolve(value);
|
|
773
778
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
774
779
|
};
|
|
775
780
|
return normalize(left) === normalize(right);
|
|
@@ -831,7 +836,7 @@ async function hashFileBounded(filePath, maximumBytes, signal, afterChunk) {
|
|
|
831
836
|
}
|
|
832
837
|
}
|
|
833
838
|
async function readMaterializationParentIdentity(target, writeRoots) {
|
|
834
|
-
const parent =
|
|
839
|
+
const parent = path8.dirname(target);
|
|
835
840
|
let lexical;
|
|
836
841
|
let physical;
|
|
837
842
|
let info;
|
|
@@ -975,7 +980,7 @@ var init_authority = __esm({
|
|
|
975
980
|
"Call list_roots and copy a returned read-enabled root id. If no roots are listed, use an inline markdown or bundle source, or restart the server with --allow-read."
|
|
976
981
|
);
|
|
977
982
|
}
|
|
978
|
-
const candidate =
|
|
983
|
+
const candidate = path8.join(root.physical, ...parseRootRelativePath(workspacePath));
|
|
979
984
|
const physical = await realpath4(candidate).catch(() => null);
|
|
980
985
|
if (!physical || !isPathInside(root.physical, physical)) {
|
|
981
986
|
throw new Error("MCP read path is outside the selected root");
|
|
@@ -991,9 +996,9 @@ var init_authority = __esm({
|
|
|
991
996
|
"Call list_roots and copy a returned write-enabled root id. If none is writable, keep the result as a session artifact or restart the server with --allow-write."
|
|
992
997
|
);
|
|
993
998
|
}
|
|
994
|
-
const candidate =
|
|
999
|
+
const candidate = path8.join(root.physical, ...parseRootRelativePath(workspacePath));
|
|
995
1000
|
const authorized = await this.authorizeWrite(candidate);
|
|
996
|
-
const physicalParent = await realpath4(
|
|
1001
|
+
const physicalParent = await realpath4(path8.dirname(authorized));
|
|
997
1002
|
if (!isPathInside(root.physical, physicalParent)) {
|
|
998
1003
|
throw new Error("MCP write path is outside the selected root");
|
|
999
1004
|
}
|
|
@@ -1012,7 +1017,7 @@ var init_authority = __esm({
|
|
|
1012
1017
|
}
|
|
1013
1018
|
async authorizeWrite(requestedPath) {
|
|
1014
1019
|
const candidate = validatePath(requestedPath);
|
|
1015
|
-
const filename =
|
|
1020
|
+
const filename = path8.basename(candidate);
|
|
1016
1021
|
validatePortableFilename(filename);
|
|
1017
1022
|
const existing = await lstat3(candidate).catch(() => null);
|
|
1018
1023
|
if (existing?.isSymbolicLink()) {
|
|
@@ -1021,16 +1026,16 @@ var init_authority = __esm({
|
|
|
1021
1026
|
if (existing && !existing.isFile()) {
|
|
1022
1027
|
throw new Error("MCP output target must be a regular file");
|
|
1023
1028
|
}
|
|
1024
|
-
const nearest = await nearestExistingPath(existing ? candidate :
|
|
1029
|
+
const nearest = await nearestExistingPath(existing ? candidate : path8.dirname(candidate));
|
|
1025
1030
|
const physicalTarget = existing ? await realpath4(candidate) : null;
|
|
1026
|
-
const physicalParent = physicalTarget ?
|
|
1031
|
+
const physicalParent = physicalTarget ? path8.dirname(physicalTarget) : await realpath4(nearest);
|
|
1027
1032
|
if (!this.writeRoots.some((root) => isPathInside(root.physical, physicalParent))) {
|
|
1028
1033
|
throw new Error("MCP write path is outside the configured roots");
|
|
1029
1034
|
}
|
|
1030
|
-
if (!existing && !samePath(nearest,
|
|
1035
|
+
if (!existing && !samePath(nearest, path8.dirname(candidate))) {
|
|
1031
1036
|
throw new Error("MCP output parent directory does not exist");
|
|
1032
1037
|
}
|
|
1033
|
-
return physicalTarget ??
|
|
1038
|
+
return physicalTarget ?? path8.join(physicalParent, filename);
|
|
1034
1039
|
}
|
|
1035
1040
|
/** Publish bounded bytes through an operation-owned sibling file. */
|
|
1036
1041
|
async materializeBytes(requestedPath, content, maximumBytes, options = {}) {
|
|
@@ -1074,9 +1079,9 @@ var init_authority = __esm({
|
|
|
1074
1079
|
);
|
|
1075
1080
|
if (actual !== expectedSha256) throw new Error("MCP output hash precondition failed");
|
|
1076
1081
|
}
|
|
1077
|
-
const temporary =
|
|
1078
|
-
|
|
1079
|
-
`.${
|
|
1082
|
+
const temporary = path8.join(
|
|
1083
|
+
path8.dirname(target),
|
|
1084
|
+
`.${path8.basename(target)}.${process.pid}.${randomUUID4()}.tmp`
|
|
1080
1085
|
);
|
|
1081
1086
|
let handle = null;
|
|
1082
1087
|
try {
|
|
@@ -1176,7 +1181,7 @@ import { createHash as createHash3 } from "crypto";
|
|
|
1176
1181
|
import { createRequire } from "module";
|
|
1177
1182
|
import { readFileSync } from "fs";
|
|
1178
1183
|
import { readdir, readFile as readFile2, stat as stat6 } from "fs/promises";
|
|
1179
|
-
import
|
|
1184
|
+
import path9 from "path";
|
|
1180
1185
|
function getPackageVersion() {
|
|
1181
1186
|
const pkg = readPackageJson();
|
|
1182
1187
|
if (typeof pkg?.version === "string" && pkg.version.length > 0) {
|
|
@@ -1212,10 +1217,10 @@ async function computeRuntimeVersion(packageName, resolutionSpecifier) {
|
|
|
1212
1217
|
try {
|
|
1213
1218
|
const entry = require2.resolve(resolutionSpecifier);
|
|
1214
1219
|
const root = await findPackageRoot(entry, packageName);
|
|
1215
|
-
const files = await collectRuntimeFiles(
|
|
1220
|
+
const files = await collectRuntimeFiles(path9.join(root, "dist"));
|
|
1216
1221
|
const hash = createHash3("sha256");
|
|
1217
1222
|
for (const file of files) {
|
|
1218
|
-
const relative =
|
|
1223
|
+
const relative = path9.relative(root, file).split(path9.sep).join("/");
|
|
1219
1224
|
hash.update(relative);
|
|
1220
1225
|
hash.update("\0");
|
|
1221
1226
|
hash.update(await readFile2(file));
|
|
@@ -1235,16 +1240,16 @@ function readPackageJson() {
|
|
|
1235
1240
|
}
|
|
1236
1241
|
}
|
|
1237
1242
|
async function findPackageRoot(entry, packageName) {
|
|
1238
|
-
let directory =
|
|
1243
|
+
let directory = path9.dirname(entry);
|
|
1239
1244
|
for (let depth = 0; depth < 12; depth += 1) {
|
|
1240
1245
|
try {
|
|
1241
1246
|
const parsed = JSON.parse(
|
|
1242
|
-
await readFile2(
|
|
1247
|
+
await readFile2(path9.join(directory, "package.json"), "utf8")
|
|
1243
1248
|
);
|
|
1244
1249
|
if (parsed.name === packageName) return directory;
|
|
1245
1250
|
} catch {
|
|
1246
1251
|
}
|
|
1247
|
-
const parent =
|
|
1252
|
+
const parent = path9.dirname(directory);
|
|
1248
1253
|
if (parent === directory) break;
|
|
1249
1254
|
directory = parent;
|
|
1250
1255
|
}
|
|
@@ -1257,7 +1262,7 @@ async function collectRuntimeFiles(directory) {
|
|
|
1257
1262
|
while (pending.length > 0) {
|
|
1258
1263
|
const current = pending.pop();
|
|
1259
1264
|
for (const entry of await readdir(current, { withFileTypes: true })) {
|
|
1260
|
-
const candidate =
|
|
1265
|
+
const candidate = path9.join(current, entry.name);
|
|
1261
1266
|
if (entry.isDirectory()) {
|
|
1262
1267
|
pending.push(candidate);
|
|
1263
1268
|
} else if (entry.isFile() && /\.(?:js|json|css|wasm)$/iu.test(entry.name)) {
|
|
@@ -1341,9 +1346,9 @@ var init_output_bounds = __esm({
|
|
|
1341
1346
|
|
|
1342
1347
|
// src/mcp/document-service.ts
|
|
1343
1348
|
import { createHash as createHash5 } from "crypto";
|
|
1344
|
-
import { basename as
|
|
1349
|
+
import { basename as basename4, extname as extname3 } from "path";
|
|
1345
1350
|
import { MCP_WIRE_LIMITS as MCP_WIRE_LIMITS4 } from "@bendyline/docblocks/mcp";
|
|
1346
|
-
import { decodeUtf8Text as
|
|
1351
|
+
import { decodeUtf8Text as decodeUtf8Text4, tryParseWorkspacePath as tryParseWorkspacePath2 } from "@bendyline/docblocks/filesystem";
|
|
1347
1352
|
async function readAssetManifest(container, signal) {
|
|
1348
1353
|
const bytes = await container.readFile(ASSET_MANIFEST_PATH);
|
|
1349
1354
|
if (!bytes) return /* @__PURE__ */ new Map();
|
|
@@ -1353,7 +1358,7 @@ async function readAssetManifest(container, signal) {
|
|
|
1353
1358
|
}
|
|
1354
1359
|
try {
|
|
1355
1360
|
const parsed = JSON.parse(
|
|
1356
|
-
|
|
1361
|
+
decodeUtf8Text4(bytes, {
|
|
1357
1362
|
label: "The document asset manifest",
|
|
1358
1363
|
path: ASSET_MANIFEST_PATH
|
|
1359
1364
|
})
|
|
@@ -1367,12 +1372,12 @@ async function readAssetManifest(container, signal) {
|
|
|
1367
1372
|
if (!isExactRecord(candidate, ["path", "mimeType", "altText", "credit", "license"])) {
|
|
1368
1373
|
throw new Error("The document asset manifest contains an invalid entry");
|
|
1369
1374
|
}
|
|
1370
|
-
const
|
|
1371
|
-
if (
|
|
1375
|
+
const path10 = tryParseWorkspacePath2(candidate.path);
|
|
1376
|
+
if (path10 === null || path10 === "" || path10 === "index.md" || path10.startsWith(".docblocks/") || !isNullableMimeType(candidate.mimeType) || !isNullableMetadata(candidate.altText) || !isNullableMetadata(candidate.credit) || !isNullableMetadata(candidate.license) || entries.has(path10)) {
|
|
1372
1377
|
throw new Error("The document asset manifest contains invalid metadata");
|
|
1373
1378
|
}
|
|
1374
|
-
entries.set(
|
|
1375
|
-
path:
|
|
1379
|
+
entries.set(path10, {
|
|
1380
|
+
path: path10,
|
|
1376
1381
|
mimeType: candidate.mimeType,
|
|
1377
1382
|
altText: candidate.altText,
|
|
1378
1383
|
credit: candidate.credit,
|
|
@@ -1508,7 +1513,7 @@ function throwIfAborted5(signal) {
|
|
|
1508
1513
|
throwIfAborted(signal, "MCP operation was cancelled");
|
|
1509
1514
|
}
|
|
1510
1515
|
function baseNameFromName(name) {
|
|
1511
|
-
const leaf =
|
|
1516
|
+
const leaf = basename4(name.replace(/\\/gu, "/")) || "document";
|
|
1512
1517
|
const extension = extname3(leaf);
|
|
1513
1518
|
return (extension ? leaf.slice(0, -extension.length) : leaf) || "document";
|
|
1514
1519
|
}
|
|
@@ -1600,7 +1605,7 @@ var init_document_service = __esm({
|
|
|
1600
1605
|
const physical = await this.authority.authorizeRootRead(source.rootId, source.path);
|
|
1601
1606
|
const bytes = await this.authority.readFile(physical, signal);
|
|
1602
1607
|
throwIfAborted5(signal);
|
|
1603
|
-
const filename =
|
|
1608
|
+
const filename = basename4(source.path);
|
|
1604
1609
|
return {
|
|
1605
1610
|
bytes,
|
|
1606
1611
|
filename,
|
|
@@ -1657,7 +1662,7 @@ var init_document_service = __esm({
|
|
|
1657
1662
|
case "file": {
|
|
1658
1663
|
const physical = await this.authority.authorizeRootRead(source.rootId, source.path);
|
|
1659
1664
|
const bytes = await this.authority.readFile(physical, signal);
|
|
1660
|
-
const name =
|
|
1665
|
+
const name = basename4(source.path);
|
|
1661
1666
|
return {
|
|
1662
1667
|
source: { kind: "bytes", data: bytes, filename: name },
|
|
1663
1668
|
sourceFormat: normalizeFormat(source.format ?? formatFromName(name)),
|
|
@@ -2665,7 +2670,7 @@ var init_intelligence = __esm({
|
|
|
2665
2670
|
// src/mcp/preview-service.ts
|
|
2666
2671
|
import { mkdtemp as mkdtemp3, readFile as readFile3, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
2667
2672
|
import { tmpdir as tmpdir2 } from "os";
|
|
2668
|
-
import { basename as
|
|
2673
|
+
import { basename as basename5, extname as extname4, join as join3 } from "path";
|
|
2669
2674
|
import { DOCBLOCKS_MCP_WIRE_VERSION as DOCBLOCKS_MCP_WIRE_VERSION2 } from "@bendyline/docblocks/mcp";
|
|
2670
2675
|
async function previewEngineVersions() {
|
|
2671
2676
|
return [
|
|
@@ -2903,7 +2908,7 @@ function thumbnailFilter(width, height) {
|
|
|
2903
2908
|
return `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`;
|
|
2904
2909
|
}
|
|
2905
2910
|
function baseNameFromFilename(filename) {
|
|
2906
|
-
const leaf =
|
|
2911
|
+
const leaf = basename5(filename.replace(/\\/gu, "/")) || "video";
|
|
2907
2912
|
const extension = extname4(leaf);
|
|
2908
2913
|
return (extension ? leaf.slice(0, -extension.length) : leaf) || "video";
|
|
2909
2914
|
}
|
|
@@ -3701,7 +3706,7 @@ async function convertPreparedDocument(artifacts, prepared, request, signal, pro
|
|
|
3701
3706
|
}
|
|
3702
3707
|
}
|
|
3703
3708
|
function createConversionCacheKey(prepared, request, target, fidelity, engineVersions) {
|
|
3704
|
-
const
|
|
3709
|
+
const canonical2 = canonicalJson({
|
|
3705
3710
|
source: {
|
|
3706
3711
|
sha256: prepared.sourceSha256,
|
|
3707
3712
|
format: prepared.sourceFormat,
|
|
@@ -3722,7 +3727,7 @@ function createConversionCacheKey(prepared, request, target, fidelity, engineVer
|
|
|
3722
3727
|
title: request.title ?? null
|
|
3723
3728
|
}
|
|
3724
3729
|
});
|
|
3725
|
-
return createHash6("sha256").update(
|
|
3730
|
+
return createHash6("sha256").update(canonical2).digest("hex");
|
|
3726
3731
|
}
|
|
3727
3732
|
async function findCompletedConversion(artifacts, key) {
|
|
3728
3733
|
const cache = completedConversionCache(artifacts);
|
|
@@ -4300,10 +4305,10 @@ function registerAgenticTools(server, context) {
|
|
|
4300
4305
|
async ({ source, targets, themeId, transformId, autoTemplates, title }, extra) => {
|
|
4301
4306
|
try {
|
|
4302
4307
|
return await context.runOperation(extra.signal, async (operationSignal) => {
|
|
4303
|
-
const
|
|
4308
|
+
const canonical2 = requireDocumentSource(source);
|
|
4304
4309
|
const documents = new DocumentService(await context.authority, context.artifacts);
|
|
4305
4310
|
await sendProgress(extra, 0, targets.length + 1, "Resolving document");
|
|
4306
|
-
const prepared = await documents.prepare(
|
|
4311
|
+
const prepared = await documents.prepare(canonical2, operationSignal);
|
|
4307
4312
|
const results = await convertPreparedDocument(
|
|
4308
4313
|
context.artifacts,
|
|
4309
4314
|
prepared,
|
|
@@ -4345,9 +4350,9 @@ function registerAgenticTools(server, context) {
|
|
|
4345
4350
|
async ({ source }, extra) => {
|
|
4346
4351
|
try {
|
|
4347
4352
|
return await context.runOperation(extra.signal, async (operationSignal) => {
|
|
4348
|
-
const
|
|
4353
|
+
const canonical2 = requireDocumentSource(source);
|
|
4349
4354
|
const documents = new DocumentService(await context.authority, context.artifacts);
|
|
4350
|
-
const prepared = await documents.prepare(
|
|
4355
|
+
const prepared = await documents.prepare(canonical2, operationSignal);
|
|
4351
4356
|
const [result] = await convertPreparedDocument(
|
|
4352
4357
|
context.artifacts,
|
|
4353
4358
|
prepared,
|
|
@@ -4380,7 +4385,7 @@ function registerAgenticTools(server, context) {
|
|
|
4380
4385
|
async ({ artifactUri, destination }, extra) => {
|
|
4381
4386
|
try {
|
|
4382
4387
|
return await context.runOperation(extra.signal, async (operationSignal) => {
|
|
4383
|
-
const
|
|
4388
|
+
const canonical2 = requireWire(
|
|
4384
4389
|
parseMaterializationOptions(destination),
|
|
4385
4390
|
"materialization options"
|
|
4386
4391
|
);
|
|
@@ -4390,15 +4395,15 @@ function registerAgenticTools(server, context) {
|
|
|
4390
4395
|
context.artifacts.read(artifactUri, operationSignal)
|
|
4391
4396
|
]);
|
|
4392
4397
|
throwIfAborted5(operationSignal);
|
|
4393
|
-
const outputPath = await authority.authorizeRootWrite(
|
|
4398
|
+
const outputPath = await authority.authorizeRootWrite(canonical2.rootId, canonical2.path);
|
|
4394
4399
|
await authority.materializeBytes(outputPath, bytes, bytes.byteLength, {
|
|
4395
|
-
ifExists:
|
|
4396
|
-
expectedSha256:
|
|
4400
|
+
ifExists: canonical2.ifExists,
|
|
4401
|
+
expectedSha256: canonical2.expectedSha256 ?? void 0,
|
|
4397
4402
|
signal: operationSignal
|
|
4398
4403
|
});
|
|
4399
4404
|
const payload = {
|
|
4400
4405
|
artifact,
|
|
4401
|
-
destination: { rootId:
|
|
4406
|
+
destination: { rootId: canonical2.rootId, path: canonical2.path },
|
|
4402
4407
|
sha256: artifact.sha256
|
|
4403
4408
|
};
|
|
4404
4409
|
return textAndStructured(payload);
|
|
@@ -4705,6 +4710,7 @@ function registerTemplateTools(server, context) {
|
|
|
4705
4710
|
title: boundNullableWireText(block.title, MCP_WIRE_LIMITS7.labelCharacters),
|
|
4706
4711
|
profile: {
|
|
4707
4712
|
...profile,
|
|
4713
|
+
hasCompoundBody: profile.hasCompoundBody ?? false,
|
|
4708
4714
|
hasAsciiDiagram: profile.hasAsciiDiagram ?? false,
|
|
4709
4715
|
hasTimeline: profile.hasTimeline ?? false,
|
|
4710
4716
|
hasTree: profile.hasTree ?? false
|
|
@@ -4861,6 +4867,7 @@ function registerTemplateTools(server, context) {
|
|
|
4861
4867
|
title: boundNullableWireText(block.title, MCP_WIRE_LIMITS7.labelCharacters),
|
|
4862
4868
|
profile: {
|
|
4863
4869
|
...profile,
|
|
4870
|
+
hasCompoundBody: profile.hasCompoundBody ?? false,
|
|
4864
4871
|
hasAsciiDiagram: profile.hasAsciiDiagram ?? false,
|
|
4865
4872
|
hasTimeline: profile.hasTimeline ?? false,
|
|
4866
4873
|
hasTree: profile.hasTree ?? false
|
|
@@ -6153,12 +6160,12 @@ var init_server = __esm({
|
|
|
6153
6160
|
});
|
|
6154
6161
|
|
|
6155
6162
|
// src/program.ts
|
|
6156
|
-
import { Command as
|
|
6163
|
+
import { Command as Command10 } from "commander";
|
|
6157
6164
|
|
|
6158
6165
|
// src/commands/build.ts
|
|
6159
6166
|
import { Command } from "commander";
|
|
6160
6167
|
import { mkdir, opendir, readFile, stat as stat2, writeFile } from "fs/promises";
|
|
6161
|
-
import
|
|
6168
|
+
import path5 from "path";
|
|
6162
6169
|
|
|
6163
6170
|
// src/render-html.ts
|
|
6164
6171
|
init_contained_file();
|
|
@@ -6167,7 +6174,7 @@ init_node_error();
|
|
|
6167
6174
|
import { realpath, stat } from "fs/promises";
|
|
6168
6175
|
import path3 from "path";
|
|
6169
6176
|
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
6170
|
-
import { parseMarkdown } from "@bendyline/squisq/markdown";
|
|
6177
|
+
import { parseMarkdown, readFrontmatterThemeId } from "@bendyline/squisq/markdown";
|
|
6171
6178
|
import { collectImagePaths, docToHtml } from "@bendyline/squisq-formats/html";
|
|
6172
6179
|
import { PLAYER_BUNDLE } from "@bendyline/squisq-react/standalone-source";
|
|
6173
6180
|
|
|
@@ -6189,25 +6196,35 @@ var DEFAULT_MAX_ASSET_BYTES = 50 * 1024 * 1024;
|
|
|
6189
6196
|
var MAX_SINGLE_ASSET_BYTES = 20 * 1024 * 1024;
|
|
6190
6197
|
async function renderMarkdownHtml(markdown, options) {
|
|
6191
6198
|
await assertKnownThemeId(options.themeId);
|
|
6199
|
+
const warn = options.onWarning ?? ((message) => console.warn(message));
|
|
6192
6200
|
const markdownDoc = parseMarkdown(markdown);
|
|
6193
6201
|
const doc = markdownToDoc(markdownDoc);
|
|
6202
|
+
const themeId = options.themeId ?? (readFrontmatterThemeId(markdownDoc.frontmatter) ? void 0 : await usableFallbackThemeId(options.fallbackThemeId, warn));
|
|
6194
6203
|
const images = options.sourcePath && options.assetRoot ? await readReferencedImages(
|
|
6195
6204
|
doc,
|
|
6196
6205
|
options.sourcePath,
|
|
6197
6206
|
options.assetRoot,
|
|
6198
6207
|
options.maxAssetBytes ?? DEFAULT_MAX_ASSET_BYTES,
|
|
6199
6208
|
options.allowReferencedAsset,
|
|
6200
|
-
|
|
6209
|
+
warn
|
|
6201
6210
|
) : void 0;
|
|
6202
6211
|
return docToHtml(doc, {
|
|
6203
6212
|
playerScript: PLAYER_BUNDLE,
|
|
6204
6213
|
images,
|
|
6205
6214
|
title: options.title,
|
|
6206
6215
|
mode: options.mode ?? "static",
|
|
6207
|
-
themeId
|
|
6216
|
+
themeId,
|
|
6208
6217
|
showCodeCopyButton: true
|
|
6209
6218
|
});
|
|
6210
6219
|
}
|
|
6220
|
+
async function usableFallbackThemeId(fallbackThemeId, warn) {
|
|
6221
|
+
if (!fallbackThemeId) return void 0;
|
|
6222
|
+
if ((await getAvailableThemeIds()).includes(fallbackThemeId)) return fallbackThemeId;
|
|
6223
|
+
warn(
|
|
6224
|
+
`Ignoring unknown fallback theme "${fallbackThemeId}"; the document's default theme applies.`
|
|
6225
|
+
);
|
|
6226
|
+
return void 0;
|
|
6227
|
+
}
|
|
6211
6228
|
async function readReferencedImages(doc, sourcePath, assetRoot, maxAssetBytes, allowReferencedAsset, warn) {
|
|
6212
6229
|
const images = /* @__PURE__ */ new Map();
|
|
6213
6230
|
const root = await realpathIfPresent(path3.resolve(assetRoot));
|
|
@@ -6305,7 +6322,564 @@ function positiveLimit(value, fallback, label) {
|
|
|
6305
6322
|
|
|
6306
6323
|
// src/commands/build.ts
|
|
6307
6324
|
init_node_error();
|
|
6308
|
-
|
|
6325
|
+
|
|
6326
|
+
// src/internal/workspace-settings.ts
|
|
6327
|
+
import { decodeUtf8Text, parseWorkspacePath as parseWorkspacePath2 } from "@bendyline/docblocks/filesystem";
|
|
6328
|
+
import {
|
|
6329
|
+
WORKSPACE_SETTINGS_LIMITS,
|
|
6330
|
+
WORKSPACE_SETTINGS_PATH,
|
|
6331
|
+
parseWorkspaceSettingsText,
|
|
6332
|
+
resolveWorkspaceDefaultThemeId
|
|
6333
|
+
} from "@bendyline/docblocks/workspace-settings";
|
|
6334
|
+
|
|
6335
|
+
// src/internal/workspace-io.ts
|
|
6336
|
+
init_link_support();
|
|
6337
|
+
import { constants as fsConstants } from "fs";
|
|
6338
|
+
import fs2 from "fs/promises";
|
|
6339
|
+
import { randomUUID } from "crypto";
|
|
6340
|
+
import path4 from "path";
|
|
6341
|
+
import {
|
|
6342
|
+
FsError,
|
|
6343
|
+
mapNodeErrorCodeToFsErrorCode,
|
|
6344
|
+
parseFileSystemVersion,
|
|
6345
|
+
parseWorkspacePath,
|
|
6346
|
+
tryParseWorkspacePath
|
|
6347
|
+
} from "@bendyline/docblocks/filesystem";
|
|
6348
|
+
init_node_error();
|
|
6349
|
+
init_paths();
|
|
6350
|
+
var NODE_WORKSPACE_IO_LIMITS = {
|
|
6351
|
+
maxFileBytes: 256 * 1024 * 1024,
|
|
6352
|
+
maxDirectoryEntries: 1e5
|
|
6353
|
+
};
|
|
6354
|
+
var LISTING_STAT_CONCURRENCY = 16;
|
|
6355
|
+
var TEMPORARY_PREFIX = ".docblocks-tmp-";
|
|
6356
|
+
async function createNodeWorkspaceIO(rootDir, options = {}) {
|
|
6357
|
+
const requested = path4.resolve(rootDir);
|
|
6358
|
+
let root;
|
|
6359
|
+
try {
|
|
6360
|
+
root = await fs2.realpath(requested);
|
|
6361
|
+
} catch (error) {
|
|
6362
|
+
if (isNodeErrorCode(error, ["ENOENT", "ENOTDIR"])) {
|
|
6363
|
+
throw new FsError("not-found", `Workspace folder not found: ${requested}`, {
|
|
6364
|
+
operation: "stat"
|
|
6365
|
+
});
|
|
6366
|
+
}
|
|
6367
|
+
throw toFsError(error, "stat", "");
|
|
6368
|
+
}
|
|
6369
|
+
let info;
|
|
6370
|
+
try {
|
|
6371
|
+
info = await fs2.stat(root);
|
|
6372
|
+
} catch (error) {
|
|
6373
|
+
throw toFsError(error, "stat", "");
|
|
6374
|
+
}
|
|
6375
|
+
if (!info.isDirectory()) {
|
|
6376
|
+
throw new FsError("type-mismatch", `Workspace root is not a folder: ${requested}`, {
|
|
6377
|
+
operation: "stat"
|
|
6378
|
+
});
|
|
6379
|
+
}
|
|
6380
|
+
return new NodeWorkspaceIO(root, {
|
|
6381
|
+
maxFileBytes: positiveLimit(
|
|
6382
|
+
options.maxFileBytes,
|
|
6383
|
+
NODE_WORKSPACE_IO_LIMITS.maxFileBytes,
|
|
6384
|
+
"workspace file byte"
|
|
6385
|
+
),
|
|
6386
|
+
maxDirectoryEntries: positiveLimit(
|
|
6387
|
+
options.maxDirectoryEntries,
|
|
6388
|
+
NODE_WORKSPACE_IO_LIMITS.maxDirectoryEntries,
|
|
6389
|
+
"workspace folder entry"
|
|
6390
|
+
)
|
|
6391
|
+
});
|
|
6392
|
+
}
|
|
6393
|
+
var NodeWorkspaceIO = class {
|
|
6394
|
+
constructor(root, limits) {
|
|
6395
|
+
this.root = root;
|
|
6396
|
+
this.limits = limits;
|
|
6397
|
+
}
|
|
6398
|
+
async stat(input) {
|
|
6399
|
+
const workspacePath = canonical(input, "stat");
|
|
6400
|
+
try {
|
|
6401
|
+
const located = await this.locate(workspacePath, "stat");
|
|
6402
|
+
if (!located) return null;
|
|
6403
|
+
const info = await lstatOrNull(located);
|
|
6404
|
+
if (!info) return null;
|
|
6405
|
+
return snapshotFor(workspacePath, assertSupportedKind(info, workspacePath, "stat"), info);
|
|
6406
|
+
} catch (error) {
|
|
6407
|
+
throw toFsError(error, "stat", workspacePath);
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
async readFile(input) {
|
|
6411
|
+
const workspacePath = canonical(input, "read");
|
|
6412
|
+
try {
|
|
6413
|
+
const located = await this.locate(workspacePath, "read");
|
|
6414
|
+
if (!located) return null;
|
|
6415
|
+
const before = await lstatOrNull(located);
|
|
6416
|
+
if (!before) return null;
|
|
6417
|
+
if (assertSupportedKind(before, workspacePath, "read") !== "file") {
|
|
6418
|
+
throw new FsError("type-mismatch", `"${workspacePath}" is a folder, not a file.`, {
|
|
6419
|
+
operation: "read",
|
|
6420
|
+
path: workspacePath
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6423
|
+
this.assertFileSize(before.size, workspacePath, "read");
|
|
6424
|
+
return await this.readLocatedFile(located, before, workspacePath);
|
|
6425
|
+
} catch (error) {
|
|
6426
|
+
if (isNodeErrorCode(error, ["ENOENT", "ENOTDIR"])) return null;
|
|
6427
|
+
throw toFsError(error, "read", workspacePath);
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
6430
|
+
async readDirectory(input) {
|
|
6431
|
+
const workspacePath = canonical(input, "list");
|
|
6432
|
+
try {
|
|
6433
|
+
let directory = this.root;
|
|
6434
|
+
if (workspacePath) {
|
|
6435
|
+
const located = await this.locate(workspacePath, "list");
|
|
6436
|
+
const info = located ? await lstatOrNull(located) : null;
|
|
6437
|
+
if (!located || !info) {
|
|
6438
|
+
throw new FsError("not-found", `Folder "${workspacePath}" does not exist.`, {
|
|
6439
|
+
operation: "list",
|
|
6440
|
+
path: workspacePath
|
|
6441
|
+
});
|
|
6442
|
+
}
|
|
6443
|
+
if (assertSupportedKind(info, workspacePath, "list") !== "directory") {
|
|
6444
|
+
throw new FsError("type-mismatch", `"${workspacePath}" is a file, not a folder.`, {
|
|
6445
|
+
operation: "list",
|
|
6446
|
+
path: workspacePath
|
|
6447
|
+
});
|
|
6448
|
+
}
|
|
6449
|
+
directory = located;
|
|
6450
|
+
}
|
|
6451
|
+
const names = await this.listNames(directory, workspacePath);
|
|
6452
|
+
const entries = [];
|
|
6453
|
+
for (let index = 0; index < names.length; index += LISTING_STAT_CONCURRENCY) {
|
|
6454
|
+
const batch = names.slice(index, index + LISTING_STAT_CONCURRENCY);
|
|
6455
|
+
const snapshots = await Promise.all(
|
|
6456
|
+
batch.map(async (name) => {
|
|
6457
|
+
const info = await lstatOrNull(path4.join(directory, name));
|
|
6458
|
+
if (!info || info.isSymbolicLink()) return null;
|
|
6459
|
+
const kind = info.isFile() ? "file" : info.isDirectory() ? "directory" : null;
|
|
6460
|
+
if (!kind) return null;
|
|
6461
|
+
const childPath = parseWorkspacePath(workspacePath ? `${workspacePath}/${name}` : name);
|
|
6462
|
+
return snapshotFor(childPath, kind, info);
|
|
6463
|
+
})
|
|
6464
|
+
);
|
|
6465
|
+
for (const snapshot of snapshots) if (snapshot) entries.push(snapshot);
|
|
6466
|
+
}
|
|
6467
|
+
return Object.freeze(entries.sort(compareSnapshots));
|
|
6468
|
+
} catch (error) {
|
|
6469
|
+
throw toFsError(error, "list", workspacePath);
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
async writeFile(input, data, options = {}) {
|
|
6473
|
+
const workspacePath = canonical(input, "write");
|
|
6474
|
+
if (!workspacePath) {
|
|
6475
|
+
throw new FsError("invalid-path", "The workspace root cannot be written.", {
|
|
6476
|
+
operation: "write",
|
|
6477
|
+
path: workspacePath
|
|
6478
|
+
});
|
|
6479
|
+
}
|
|
6480
|
+
const bytes = ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
6481
|
+
const mode = options.mode ?? "upsert";
|
|
6482
|
+
const expectedVersion = options.expectedVersion;
|
|
6483
|
+
let temporary = null;
|
|
6484
|
+
try {
|
|
6485
|
+
this.assertFileSize(BigInt(bytes.byteLength), workspacePath, "write");
|
|
6486
|
+
const parent = await this.ensureParent(workspacePath, options.createParents === true);
|
|
6487
|
+
const target = path4.join(parent, basename(workspacePath));
|
|
6488
|
+
let current = await lstatOrNull(target);
|
|
6489
|
+
assertWritable(current, workspacePath, mode, expectedVersion);
|
|
6490
|
+
temporary = path4.join(parent, `${TEMPORARY_PREFIX}${randomUUID()}`);
|
|
6491
|
+
const handle = await fs2.open(temporary, "wx", 438);
|
|
6492
|
+
try {
|
|
6493
|
+
await handle.writeFile(bytes);
|
|
6494
|
+
if (current?.isFile()) await handle.chmod(Number(current.mode & 0o7777n));
|
|
6495
|
+
} finally {
|
|
6496
|
+
await handle.close();
|
|
6497
|
+
}
|
|
6498
|
+
current = await lstatOrNull(target);
|
|
6499
|
+
assertWritable(current, workspacePath, mode, expectedVersion);
|
|
6500
|
+
if (!current && (mode === "create" || expectedVersion === null)) {
|
|
6501
|
+
await publishExclusive(temporary, target, bytes, workspacePath, mode);
|
|
6502
|
+
} else {
|
|
6503
|
+
await fs2.rename(temporary, target);
|
|
6504
|
+
temporary = null;
|
|
6505
|
+
}
|
|
6506
|
+
const written = await fs2.lstat(target, { bigint: true });
|
|
6507
|
+
const snapshot = snapshotFor(workspacePath, "file", written);
|
|
6508
|
+
if (snapshot.kind !== "file") throw new Error("Unreachable: wrote a non-file entry.");
|
|
6509
|
+
return snapshot;
|
|
6510
|
+
} catch (error) {
|
|
6511
|
+
throw toFsError(error, "write", workspacePath);
|
|
6512
|
+
} finally {
|
|
6513
|
+
if (temporary) await fs2.rm(temporary, { force: true }).catch(() => void 0);
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
/**
|
|
6517
|
+
* Physical absolute path for `workspacePath`, with its parent proven to be
|
|
6518
|
+
* inside the root, or null when a parent is missing or is a file.
|
|
6519
|
+
*/
|
|
6520
|
+
async locate(workspacePath, operation) {
|
|
6521
|
+
if (!workspacePath) return this.root;
|
|
6522
|
+
const segments = workspacePath.split("/");
|
|
6523
|
+
const name = segments.pop();
|
|
6524
|
+
let parent;
|
|
6525
|
+
try {
|
|
6526
|
+
parent = await fs2.realpath(path4.join(this.root, ...segments));
|
|
6527
|
+
} catch (error) {
|
|
6528
|
+
if (isNodeErrorCode(error, ["ENOENT", "ENOTDIR"])) return null;
|
|
6529
|
+
throw error;
|
|
6530
|
+
}
|
|
6531
|
+
if (!isPathInside(this.root, parent)) throw escapeError(workspacePath, operation);
|
|
6532
|
+
return path4.join(parent, name);
|
|
6533
|
+
}
|
|
6534
|
+
/** Resolve (and optionally create) the physical parent folder of a write. */
|
|
6535
|
+
async ensureParent(workspacePath, createParents) {
|
|
6536
|
+
const segments = workspacePath.split("/").slice(0, -1);
|
|
6537
|
+
let current = this.root;
|
|
6538
|
+
let walked = "";
|
|
6539
|
+
for (const segment of segments) {
|
|
6540
|
+
walked = walked ? `${walked}/${segment}` : segment;
|
|
6541
|
+
const next = path4.join(current, segment);
|
|
6542
|
+
let info = await lstatOrNull(next);
|
|
6543
|
+
if (!info) {
|
|
6544
|
+
if (!createParents) {
|
|
6545
|
+
throw new FsError("not-found", `Parent folder "${walked}" does not exist.`, {
|
|
6546
|
+
operation: "write",
|
|
6547
|
+
path: workspacePath
|
|
6548
|
+
});
|
|
6549
|
+
}
|
|
6550
|
+
try {
|
|
6551
|
+
await fs2.mkdir(next);
|
|
6552
|
+
} catch (error) {
|
|
6553
|
+
if (!isNodeErrorCode(error, "EEXIST")) throw error;
|
|
6554
|
+
}
|
|
6555
|
+
info = await fs2.lstat(next, { bigint: true });
|
|
6556
|
+
}
|
|
6557
|
+
if (info.isSymbolicLink()) {
|
|
6558
|
+
const physical = await fs2.realpath(next);
|
|
6559
|
+
if (!isPathInside(this.root, physical)) throw escapeError(workspacePath, "write");
|
|
6560
|
+
if (!(await fs2.stat(physical)).isDirectory()) throw parentFileError(walked, workspacePath);
|
|
6561
|
+
current = physical;
|
|
6562
|
+
} else if (info.isDirectory()) {
|
|
6563
|
+
current = next;
|
|
6564
|
+
} else {
|
|
6565
|
+
throw parentFileError(walked, workspacePath);
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
6568
|
+
const physicalParent = await fs2.realpath(current);
|
|
6569
|
+
if (!isPathInside(this.root, physicalParent)) throw escapeError(workspacePath, "write");
|
|
6570
|
+
return physicalParent;
|
|
6571
|
+
}
|
|
6572
|
+
async listNames(directory, workspacePath) {
|
|
6573
|
+
const names = [];
|
|
6574
|
+
let seen = 0;
|
|
6575
|
+
const handle = await fs2.opendir(directory);
|
|
6576
|
+
for await (const dirent of handle) {
|
|
6577
|
+
seen += 1;
|
|
6578
|
+
if (seen > this.limits.maxDirectoryEntries) {
|
|
6579
|
+
throw new FsError(
|
|
6580
|
+
"not-supported",
|
|
6581
|
+
`Folder "${workspacePath || "."}" has more than ${this.limits.maxDirectoryEntries} entries.`,
|
|
6582
|
+
{ operation: "list", path: workspacePath }
|
|
6583
|
+
);
|
|
6584
|
+
}
|
|
6585
|
+
if (dirent.isSymbolicLink()) continue;
|
|
6586
|
+
if (tryParseWorkspacePath(dirent.name) !== dirent.name) continue;
|
|
6587
|
+
names.push(dirent.name);
|
|
6588
|
+
}
|
|
6589
|
+
return names;
|
|
6590
|
+
}
|
|
6591
|
+
async readLocatedFile(located, before, workspacePath) {
|
|
6592
|
+
const noFollow = typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
|
|
6593
|
+
const handle = await fs2.open(located, fsConstants.O_RDONLY | noFollow);
|
|
6594
|
+
try {
|
|
6595
|
+
const opened = await handle.stat({ bigint: true });
|
|
6596
|
+
if (!opened.isFile() || opened.ino !== before.ino) throw changedError(workspacePath);
|
|
6597
|
+
const physical = await fs2.realpath(located);
|
|
6598
|
+
if (!isPathInside(this.root, physical)) throw escapeError(workspacePath, "read");
|
|
6599
|
+
this.assertFileSize(opened.size, workspacePath, "read");
|
|
6600
|
+
const size = Number(opened.size);
|
|
6601
|
+
const buffer = new Uint8Array(size);
|
|
6602
|
+
let offset = 0;
|
|
6603
|
+
while (offset < size) {
|
|
6604
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
6605
|
+
if (bytesRead === 0) break;
|
|
6606
|
+
offset += bytesRead;
|
|
6607
|
+
}
|
|
6608
|
+
const probe = new Uint8Array(1);
|
|
6609
|
+
const grew = (await handle.read(probe, 0, 1, offset)).bytesRead > 0;
|
|
6610
|
+
const after = await handle.stat({ bigint: true });
|
|
6611
|
+
if (grew || offset !== size || !sameFileState(opened, after)) {
|
|
6612
|
+
throw changedError(workspacePath);
|
|
6613
|
+
}
|
|
6614
|
+
const entry = snapshotFor(workspacePath, "file", opened);
|
|
6615
|
+
if (entry.kind !== "file") throw new Error("Unreachable: read a non-file entry.");
|
|
6616
|
+
return Object.freeze({ entry, data: buffer.buffer });
|
|
6617
|
+
} finally {
|
|
6618
|
+
await handle.close();
|
|
6619
|
+
}
|
|
6620
|
+
}
|
|
6621
|
+
assertFileSize(size, workspacePath, operation) {
|
|
6622
|
+
if (size > BigInt(this.limits.maxFileBytes)) {
|
|
6623
|
+
throw new FsError(
|
|
6624
|
+
"not-supported",
|
|
6625
|
+
`"${workspacePath}" is larger than the ${this.limits.maxFileBytes}-byte workspace file limit.`,
|
|
6626
|
+
{ operation, path: workspacePath }
|
|
6627
|
+
);
|
|
6628
|
+
}
|
|
6629
|
+
}
|
|
6630
|
+
};
|
|
6631
|
+
function canonical(input, operation) {
|
|
6632
|
+
try {
|
|
6633
|
+
return parseWorkspacePath(input);
|
|
6634
|
+
} catch (error) {
|
|
6635
|
+
throw toFsError(error, operation, input);
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
function basename(workspacePath) {
|
|
6639
|
+
const slash = workspacePath.lastIndexOf("/");
|
|
6640
|
+
return slash < 0 ? workspacePath : workspacePath.slice(slash + 1);
|
|
6641
|
+
}
|
|
6642
|
+
async function lstatOrNull(absolutePath) {
|
|
6643
|
+
try {
|
|
6644
|
+
return await fs2.lstat(absolutePath, { bigint: true });
|
|
6645
|
+
} catch (error) {
|
|
6646
|
+
if (isNodeErrorCode(error, ["ENOENT", "ENOTDIR"])) return null;
|
|
6647
|
+
throw error;
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
function assertSupportedKind(info, workspacePath, operation) {
|
|
6651
|
+
if (info.isSymbolicLink()) {
|
|
6652
|
+
throw new FsError(
|
|
6653
|
+
"not-supported",
|
|
6654
|
+
`"${workspacePath}" is a symbolic link; workspace outputs never follow links.`,
|
|
6655
|
+
{ operation, path: workspacePath }
|
|
6656
|
+
);
|
|
6657
|
+
}
|
|
6658
|
+
if (info.isFile()) return "file";
|
|
6659
|
+
if (info.isDirectory()) return "directory";
|
|
6660
|
+
throw new FsError("not-supported", `"${workspacePath}" is not a regular file or folder.`, {
|
|
6661
|
+
operation,
|
|
6662
|
+
path: workspacePath
|
|
6663
|
+
});
|
|
6664
|
+
}
|
|
6665
|
+
function versionFor(kind, info) {
|
|
6666
|
+
return parseFileSystemVersion(
|
|
6667
|
+
`node-v1:${kind}:ino=${info.ino}:size=${kind === "file" ? info.size : 0n}:mtime-ns=${info.mtimeNs}`
|
|
6668
|
+
);
|
|
6669
|
+
}
|
|
6670
|
+
function snapshotFor(workspacePath, kind, info) {
|
|
6671
|
+
const base = {
|
|
6672
|
+
path: workspacePath,
|
|
6673
|
+
name: basename(workspacePath),
|
|
6674
|
+
version: versionFor(kind, info),
|
|
6675
|
+
lastModified: info.mtime.toISOString()
|
|
6676
|
+
};
|
|
6677
|
+
if (kind === "directory") return Object.freeze({ ...base, kind, size: null });
|
|
6678
|
+
const size = Number(info.size);
|
|
6679
|
+
if (!Number.isSafeInteger(size)) {
|
|
6680
|
+
throw new FsError("not-supported", `"${workspacePath}" is too large to describe.`, {
|
|
6681
|
+
path: workspacePath
|
|
6682
|
+
});
|
|
6683
|
+
}
|
|
6684
|
+
return Object.freeze({ ...base, kind, size });
|
|
6685
|
+
}
|
|
6686
|
+
function sameFileState(left, right) {
|
|
6687
|
+
return left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs;
|
|
6688
|
+
}
|
|
6689
|
+
function assertWritable(current, workspacePath, mode, expectedVersion) {
|
|
6690
|
+
if (current) {
|
|
6691
|
+
const kind = assertSupportedKind(current, workspacePath, "write");
|
|
6692
|
+
if (kind === "directory") {
|
|
6693
|
+
throw new FsError("type-mismatch", `"${workspacePath}" is a folder; it cannot be replaced.`, {
|
|
6694
|
+
operation: "write",
|
|
6695
|
+
path: workspacePath
|
|
6696
|
+
});
|
|
6697
|
+
}
|
|
6698
|
+
if (mode === "create") {
|
|
6699
|
+
throw new FsError("already-exists", `"${workspacePath}" already exists.`, {
|
|
6700
|
+
operation: "write",
|
|
6701
|
+
path: workspacePath
|
|
6702
|
+
});
|
|
6703
|
+
}
|
|
6704
|
+
} else if (mode === "replace") {
|
|
6705
|
+
throw new FsError("not-found", `"${workspacePath}" does not exist.`, {
|
|
6706
|
+
operation: "write",
|
|
6707
|
+
path: workspacePath
|
|
6708
|
+
});
|
|
6709
|
+
}
|
|
6710
|
+
if (expectedVersion === void 0) return;
|
|
6711
|
+
const matches = expectedVersion === null ? !current : !!current && versionFor("file", current) === expectedVersion;
|
|
6712
|
+
if (!matches) {
|
|
6713
|
+
throw new FsError("conflict", `"${workspacePath}" changed since it was read.`, {
|
|
6714
|
+
operation: "write",
|
|
6715
|
+
path: workspacePath,
|
|
6716
|
+
retryable: true
|
|
6717
|
+
});
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
async function publishExclusive(temporary, target, bytes, workspacePath, mode) {
|
|
6721
|
+
try {
|
|
6722
|
+
await fs2.link(temporary, target);
|
|
6723
|
+
return;
|
|
6724
|
+
} catch (error) {
|
|
6725
|
+
if (isNodeErrorCode(error, "EEXIST")) throw existsRaceError(workspacePath, mode);
|
|
6726
|
+
if (!isLinkUnsupportedError(error)) throw error;
|
|
6727
|
+
}
|
|
6728
|
+
let handle;
|
|
6729
|
+
try {
|
|
6730
|
+
handle = await fs2.open(target, "wx", 438);
|
|
6731
|
+
} catch (error) {
|
|
6732
|
+
if (isNodeErrorCode(error, "EEXIST")) throw existsRaceError(workspacePath, mode);
|
|
6733
|
+
throw error;
|
|
6734
|
+
}
|
|
6735
|
+
try {
|
|
6736
|
+
await handle.writeFile(bytes);
|
|
6737
|
+
} finally {
|
|
6738
|
+
await handle.close();
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
function existsRaceError(workspacePath, mode) {
|
|
6742
|
+
return mode === "create" ? new FsError("already-exists", `"${workspacePath}" already exists.`, {
|
|
6743
|
+
operation: "write",
|
|
6744
|
+
path: workspacePath
|
|
6745
|
+
}) : new FsError("conflict", `"${workspacePath}" was created by another writer.`, {
|
|
6746
|
+
operation: "write",
|
|
6747
|
+
path: workspacePath,
|
|
6748
|
+
retryable: true
|
|
6749
|
+
});
|
|
6750
|
+
}
|
|
6751
|
+
function escapeError(workspacePath, operation) {
|
|
6752
|
+
return new FsError(
|
|
6753
|
+
"path-escape",
|
|
6754
|
+
`"${workspacePath}" resolves outside the workspace folder through a symbolic link.`,
|
|
6755
|
+
{ operation, path: workspacePath }
|
|
6756
|
+
);
|
|
6757
|
+
}
|
|
6758
|
+
function parentFileError(parent, workspacePath) {
|
|
6759
|
+
return new FsError("type-mismatch", `"${parent}" is a file, not a folder.`, {
|
|
6760
|
+
operation: "write",
|
|
6761
|
+
path: workspacePath
|
|
6762
|
+
});
|
|
6763
|
+
}
|
|
6764
|
+
function changedError(workspacePath) {
|
|
6765
|
+
return new FsError("conflict", `"${workspacePath}" changed while it was being read.`, {
|
|
6766
|
+
operation: "read",
|
|
6767
|
+
path: workspacePath,
|
|
6768
|
+
retryable: true
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
function compareSnapshots(left, right) {
|
|
6772
|
+
if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
|
|
6773
|
+
return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
|
6774
|
+
}
|
|
6775
|
+
function toFsError(error, operation, workspacePath) {
|
|
6776
|
+
if (error instanceof FsError) return error;
|
|
6777
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6778
|
+
const code = nodeErrorCode(error);
|
|
6779
|
+
if (code === "ELOOP") {
|
|
6780
|
+
return new FsError(
|
|
6781
|
+
"not-supported",
|
|
6782
|
+
`"${workspacePath}" is a symbolic link; workspace outputs never follow links.`,
|
|
6783
|
+
{ operation, path: workspacePath }
|
|
6784
|
+
);
|
|
6785
|
+
}
|
|
6786
|
+
if (code) {
|
|
6787
|
+
return new FsError(
|
|
6788
|
+
mapNodeErrorCodeToFsErrorCode(code),
|
|
6789
|
+
`Could not ${OPERATION_VERBS[operation]} "${workspacePath || "."}": ${message}`,
|
|
6790
|
+
{ operation, path: workspacePath }
|
|
6791
|
+
);
|
|
6792
|
+
}
|
|
6793
|
+
return new FsError("io", message, { operation, path: workspacePath });
|
|
6794
|
+
}
|
|
6795
|
+
function nodeErrorCode(error) {
|
|
6796
|
+
if (!(error instanceof Error) || !("code" in error)) return null;
|
|
6797
|
+
const { code } = error;
|
|
6798
|
+
return typeof code === "string" ? code : null;
|
|
6799
|
+
}
|
|
6800
|
+
var OPERATION_VERBS = {
|
|
6801
|
+
"parse-path": "parse",
|
|
6802
|
+
read: "read",
|
|
6803
|
+
stat: "inspect",
|
|
6804
|
+
list: "list",
|
|
6805
|
+
write: "write",
|
|
6806
|
+
"create-directory": "create",
|
|
6807
|
+
remove: "remove",
|
|
6808
|
+
move: "move",
|
|
6809
|
+
snapshot: "snapshot",
|
|
6810
|
+
watch: "watch",
|
|
6811
|
+
dispose: "close"
|
|
6812
|
+
};
|
|
6813
|
+
|
|
6814
|
+
// src/internal/workspace-settings.ts
|
|
6815
|
+
var MAX_SETTINGS_FILE_BYTES = WORKSPACE_SETTINGS_LIMITS.maxFileBytes + 3;
|
|
6816
|
+
async function readWorkspaceSettings(io) {
|
|
6817
|
+
const settingsPath = parseWorkspacePath2(WORKSPACE_SETTINGS_PATH);
|
|
6818
|
+
const info = await io.stat(settingsPath);
|
|
6819
|
+
if (!info) return { status: "missing" };
|
|
6820
|
+
if (info.kind !== "file") {
|
|
6821
|
+
return { status: "invalid", message: `${WORKSPACE_SETTINGS_PATH} is a folder, not a file.` };
|
|
6822
|
+
}
|
|
6823
|
+
if (info.size > MAX_SETTINGS_FILE_BYTES) return tooLarge();
|
|
6824
|
+
const read = await io.readFile(settingsPath);
|
|
6825
|
+
if (!read) return { status: "missing" };
|
|
6826
|
+
if (read.data.byteLength > MAX_SETTINGS_FILE_BYTES) return tooLarge();
|
|
6827
|
+
let text;
|
|
6828
|
+
try {
|
|
6829
|
+
text = decodeUtf8Text(read.data, {
|
|
6830
|
+
label: "Workspace settings",
|
|
6831
|
+
path: WORKSPACE_SETTINGS_PATH
|
|
6832
|
+
});
|
|
6833
|
+
} catch (error) {
|
|
6834
|
+
return {
|
|
6835
|
+
status: "invalid",
|
|
6836
|
+
message: error instanceof Error ? error.message : `${WORKSPACE_SETTINGS_PATH} is not UTF-8.`
|
|
6837
|
+
};
|
|
6838
|
+
}
|
|
6839
|
+
return parseWorkspaceSettingsText(text);
|
|
6840
|
+
}
|
|
6841
|
+
function describeWorkspaceSettingsProblem(message) {
|
|
6842
|
+
return message.includes(WORKSPACE_SETTINGS_PATH) ? message : `${WORKSPACE_SETTINGS_PATH}: ${message}`;
|
|
6843
|
+
}
|
|
6844
|
+
function tooLarge() {
|
|
6845
|
+
return {
|
|
6846
|
+
status: "invalid",
|
|
6847
|
+
message: `${WORKSPACE_SETTINGS_PATH} is larger than ${WORKSPACE_SETTINGS_LIMITS.maxFileBytes / 1024} KiB.`
|
|
6848
|
+
};
|
|
6849
|
+
}
|
|
6850
|
+
async function createWorkspaceThemeFallback(rootDir, warn) {
|
|
6851
|
+
const io = await createNodeWorkspaceIO(rootDir);
|
|
6852
|
+
let lastWarning = null;
|
|
6853
|
+
const report = (message) => {
|
|
6854
|
+
if (message !== null && message !== lastWarning) warn(message);
|
|
6855
|
+
lastWarning = message;
|
|
6856
|
+
};
|
|
6857
|
+
return {
|
|
6858
|
+
async resolve() {
|
|
6859
|
+
const result = await readWorkspaceSettings(io);
|
|
6860
|
+
if (result.status === "missing") {
|
|
6861
|
+
report(null);
|
|
6862
|
+
return void 0;
|
|
6863
|
+
}
|
|
6864
|
+
if (result.status !== "ok") {
|
|
6865
|
+
report(`Ignoring workspace settings. ${describeWorkspaceSettingsProblem(result.message)}`);
|
|
6866
|
+
return void 0;
|
|
6867
|
+
}
|
|
6868
|
+
const themeId = resolveWorkspaceDefaultThemeId(result.settings);
|
|
6869
|
+
if (themeId && !(await getAvailableThemeIds()).includes(themeId)) {
|
|
6870
|
+
report(
|
|
6871
|
+
`Ignoring workspace default theme "${themeId}" from ${WORKSPACE_SETTINGS_PATH}: it is not a known theme.`
|
|
6872
|
+
);
|
|
6873
|
+
return void 0;
|
|
6874
|
+
}
|
|
6875
|
+
report(null);
|
|
6876
|
+
return themeId;
|
|
6877
|
+
}
|
|
6878
|
+
};
|
|
6879
|
+
}
|
|
6880
|
+
|
|
6881
|
+
// src/commands/build.ts
|
|
6882
|
+
import { decodeUtf8Text as decodeUtf8Text2 } from "@bendyline/docblocks/filesystem";
|
|
6309
6883
|
var DEFAULT_MAX_BUILD_ENTRIES = 1e5;
|
|
6310
6884
|
var DEFAULT_MAX_BUILD_DEPTH = 64;
|
|
6311
6885
|
var DEFAULT_MAX_BUILD_INPUT_BYTES = 20 * 1024 * 1024;
|
|
@@ -6314,8 +6888,8 @@ var DEFAULT_MAX_BUILD_OUTPUT_BYTES = 128 * 1024 * 1024;
|
|
|
6314
6888
|
var DEFAULT_MAX_BUILD_TOTAL_OUTPUT_BYTES = 1024 * 1024 * 1024;
|
|
6315
6889
|
async function runBuild(opts) {
|
|
6316
6890
|
await assertKnownThemeId(opts.theme);
|
|
6317
|
-
const inputDir =
|
|
6318
|
-
const outputDir =
|
|
6891
|
+
const inputDir = path5.resolve(opts.input);
|
|
6892
|
+
const outputDir = path5.resolve(opts.output);
|
|
6319
6893
|
let inputStat;
|
|
6320
6894
|
try {
|
|
6321
6895
|
inputStat = await stat2(inputDir);
|
|
@@ -6324,6 +6898,8 @@ async function runBuild(opts) {
|
|
|
6324
6898
|
throw new Error(`Input directory not found: ${inputDir}`);
|
|
6325
6899
|
}
|
|
6326
6900
|
if (!inputStat.isDirectory()) throw new Error(`Input is not a directory: ${inputDir}`);
|
|
6901
|
+
const warn = opts.onWarning ?? ((message) => console.warn(message));
|
|
6902
|
+
const fallbackThemeId = opts.theme || opts.ignoreWorkspaceSettings ? void 0 : await (await createWorkspaceThemeFallback(inputDir, warn)).resolve();
|
|
6327
6903
|
const markdownFiles = await listMarkdownFiles(inputDir, {
|
|
6328
6904
|
maxEntries: positiveLimit(opts.maxEntries, DEFAULT_MAX_BUILD_ENTRIES, "build entry"),
|
|
6329
6905
|
maxDepth: positiveLimit(opts.maxDepth, DEFAULT_MAX_BUILD_DEPTH, "build depth")
|
|
@@ -6338,8 +6914,8 @@ async function runBuild(opts) {
|
|
|
6338
6914
|
let totalInputBytes = 0;
|
|
6339
6915
|
let totalOutputBytes = 0;
|
|
6340
6916
|
for (const sourcePath of markdownFiles) {
|
|
6341
|
-
const relativePath =
|
|
6342
|
-
const outputPath =
|
|
6917
|
+
const relativePath = path5.relative(inputDir, sourcePath);
|
|
6918
|
+
const outputPath = path5.join(outputDir, replaceMarkdownExtension(relativePath));
|
|
6343
6919
|
const sourceBytes = await readFile(sourcePath);
|
|
6344
6920
|
if (sourceBytes.byteLength > byteLimits.maxInputBytes) {
|
|
6345
6921
|
throw new Error(
|
|
@@ -6352,13 +6928,15 @@ async function runBuild(opts) {
|
|
|
6352
6928
|
`Build inputs exceed the ${byteLimits.maxTotalInputBytes}-byte aggregate limit.`
|
|
6353
6929
|
);
|
|
6354
6930
|
}
|
|
6355
|
-
const source =
|
|
6931
|
+
const source = decodeUtf8Text2(sourceBytes, { label: "Build input", path: sourcePath });
|
|
6356
6932
|
const html = await renderMarkdownHtml(source, {
|
|
6357
6933
|
title: titleFromPath(relativePath),
|
|
6358
6934
|
sourcePath,
|
|
6359
6935
|
assetRoot: inputDir,
|
|
6360
6936
|
themeId: opts.theme,
|
|
6361
|
-
|
|
6937
|
+
fallbackThemeId,
|
|
6938
|
+
mode: "static",
|
|
6939
|
+
onWarning: warn
|
|
6362
6940
|
});
|
|
6363
6941
|
const outputBytes = Buffer.byteLength(html, "utf8");
|
|
6364
6942
|
if (outputBytes > byteLimits.maxOutputBytes) {
|
|
@@ -6372,18 +6950,22 @@ async function runBuild(opts) {
|
|
|
6372
6950
|
`Build outputs exceed the ${byteLimits.maxTotalOutputBytes}-byte aggregate limit.`
|
|
6373
6951
|
);
|
|
6374
6952
|
}
|
|
6375
|
-
await mkdir(
|
|
6953
|
+
await mkdir(path5.dirname(outputPath), { recursive: true });
|
|
6376
6954
|
await writeFile(outputPath, html, "utf-8");
|
|
6377
6955
|
builtFiles.push(outputPath);
|
|
6378
6956
|
}
|
|
6379
6957
|
return { inputDir, outputDir, builtFiles };
|
|
6380
6958
|
}
|
|
6381
|
-
var buildCommand = new Command("build").description("Build markdown files into HTML output").option("-i, --input <dir>", "input directory", ".").option("-o, --output <dir>", "output directory", "dist").option("-t, --theme <id>", "Squisq theme ID to apply").option(
|
|
6959
|
+
var buildCommand = new Command("build").description("Build markdown files into HTML output").option("-i, --input <dir>", "input directory", ".").option("-o, --output <dir>", "output directory", "dist").option("-t, --theme <id>", "Squisq theme ID to apply").option(
|
|
6960
|
+
"--ignore-workspace-settings",
|
|
6961
|
+
"do not apply the default theme from <input>/.docblocks/workspace.json"
|
|
6962
|
+
).option("--max-input-bytes <bytes>", "maximum bytes in one Markdown input").option("--max-total-input-bytes <bytes>", "maximum aggregate Markdown input bytes").option("--max-output-bytes <bytes>", "maximum bytes in one generated HTML file").option("--max-total-output-bytes <bytes>", "maximum aggregate generated HTML bytes").option("--allow-large-build", "disable default byte budgets for a trusted bulk build").action(async (opts) => {
|
|
6382
6963
|
try {
|
|
6383
6964
|
const result = await runBuild({
|
|
6384
6965
|
input: opts.input,
|
|
6385
6966
|
output: opts.output,
|
|
6386
6967
|
theme: opts.theme,
|
|
6968
|
+
ignoreWorkspaceSettings: opts.ignoreWorkspaceSettings,
|
|
6387
6969
|
maxInputBytes: parseOptionalByteLimit("--max-input-bytes", opts.maxInputBytes),
|
|
6388
6970
|
maxTotalInputBytes: parseOptionalByteLimit(
|
|
6389
6971
|
"--max-total-input-bytes",
|
|
@@ -6467,7 +7049,7 @@ async function listMarkdownFiles(root, limits) {
|
|
|
6467
7049
|
if (entryCount > limits.maxEntries) {
|
|
6468
7050
|
throw new Error(`Build traversal exceeded ${limits.maxEntries} filesystem entries.`);
|
|
6469
7051
|
}
|
|
6470
|
-
const absolutePath =
|
|
7052
|
+
const absolutePath = path5.join(current.directory, entry.name);
|
|
6471
7053
|
if (entry.isDirectory()) {
|
|
6472
7054
|
if (current.depth >= limits.maxDepth) {
|
|
6473
7055
|
throw new Error(`Build traversal exceeded a depth of ${limits.maxDepth}.`);
|
|
@@ -6487,7 +7069,7 @@ function replaceMarkdownExtension(filePath) {
|
|
|
6487
7069
|
return filePath.replace(/\.(md|markdown)$/i, ".html");
|
|
6488
7070
|
}
|
|
6489
7071
|
function titleFromPath(filePath) {
|
|
6490
|
-
const baseName =
|
|
7072
|
+
const baseName = path5.basename(filePath).replace(/\.(md|markdown)$/i, "");
|
|
6491
7073
|
return baseName || "DocBlocks Document";
|
|
6492
7074
|
}
|
|
6493
7075
|
|
|
@@ -6496,11 +7078,11 @@ import { Command as Command2 } from "commander";
|
|
|
6496
7078
|
import { createServer } from "http";
|
|
6497
7079
|
import { realpath as realpath2, stat as stat3 } from "fs/promises";
|
|
6498
7080
|
import { isIP } from "net";
|
|
6499
|
-
import
|
|
7081
|
+
import path7 from "path";
|
|
6500
7082
|
init_contained_file();
|
|
6501
7083
|
|
|
6502
7084
|
// src/preview-policy.ts
|
|
6503
|
-
import
|
|
7085
|
+
import path6 from "path";
|
|
6504
7086
|
var PREVIEW_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6505
7087
|
".css",
|
|
6506
7088
|
".gif",
|
|
@@ -6546,14 +7128,14 @@ var SENSITIVE_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
|
6546
7128
|
"id_rsa"
|
|
6547
7129
|
]);
|
|
6548
7130
|
function isAllowedPreviewPath(rootPath, requestedPath, physicalPath, purpose) {
|
|
6549
|
-
const root =
|
|
6550
|
-
const requested =
|
|
6551
|
-
const physical =
|
|
7131
|
+
const root = path6.resolve(rootPath);
|
|
7132
|
+
const requested = path6.resolve(requestedPath);
|
|
7133
|
+
const physical = path6.resolve(physicalPath);
|
|
6552
7134
|
for (const candidate of /* @__PURE__ */ new Set([requested, physical])) {
|
|
6553
|
-
const relative =
|
|
7135
|
+
const relative = path6.relative(root, candidate);
|
|
6554
7136
|
if (!isContainedRelativePath(relative) || containsSensitiveSegment(relative)) return false;
|
|
6555
7137
|
if (purpose !== "directory") {
|
|
6556
|
-
const extension =
|
|
7138
|
+
const extension = path6.extname(path6.basename(candidate)).toLowerCase();
|
|
6557
7139
|
const allowedExtensions = purpose === "embedded-image" ? EMBEDDED_IMAGE_EXTENSIONS : PREVIEW_FILE_EXTENSIONS;
|
|
6558
7140
|
if (!allowedExtensions.has(extension)) return false;
|
|
6559
7141
|
}
|
|
@@ -6563,15 +7145,15 @@ function isAllowedPreviewPath(rootPath, requestedPath, physicalPath, purpose) {
|
|
|
6563
7145
|
function containsSensitiveSegment(relativePath) {
|
|
6564
7146
|
return relativePath.split(/[\\/]+/).filter(Boolean).some((segment) => {
|
|
6565
7147
|
const lower = segment.toLowerCase();
|
|
6566
|
-
return lower.startsWith(".") || SENSITIVE_FILE_NAMES.has(lower) || SENSITIVE_FILE_EXTENSIONS.has(
|
|
7148
|
+
return lower.startsWith(".") || SENSITIVE_FILE_NAMES.has(lower) || SENSITIVE_FILE_EXTENSIONS.has(path6.extname(lower));
|
|
6567
7149
|
});
|
|
6568
7150
|
}
|
|
6569
7151
|
function isContainedRelativePath(relativePath) {
|
|
6570
|
-
return relativePath === "" || !relativePath.startsWith(`..${
|
|
7152
|
+
return relativePath === "" || !relativePath.startsWith(`..${path6.sep}`) && relativePath !== ".." && !path6.isAbsolute(relativePath);
|
|
6571
7153
|
}
|
|
6572
7154
|
|
|
6573
7155
|
// src/commands/serve.ts
|
|
6574
|
-
import { decodeUtf8Text as
|
|
7156
|
+
import { decodeUtf8Text as decodeUtf8Text3 } from "@bendyline/docblocks/filesystem";
|
|
6575
7157
|
init_node_error();
|
|
6576
7158
|
var DEFAULT_HOST = "127.0.0.1";
|
|
6577
7159
|
var DEFAULT_MAX_FILE_BYTES = 50 * 1024 * 1024;
|
|
@@ -6579,7 +7161,7 @@ var DEFAULT_MAX_CONCURRENT_READS = 16;
|
|
|
6579
7161
|
var MAX_ALLOWED_HOSTS = 32;
|
|
6580
7162
|
async function startPreviewServer(opts) {
|
|
6581
7163
|
await assertKnownThemeId(opts.theme);
|
|
6582
|
-
const requestedRoot =
|
|
7164
|
+
const requestedRoot = path7.resolve(opts.dir);
|
|
6583
7165
|
let root;
|
|
6584
7166
|
try {
|
|
6585
7167
|
root = await realpath2(requestedRoot);
|
|
@@ -6590,6 +7172,8 @@ async function startPreviewServer(opts) {
|
|
|
6590
7172
|
const rootStat = await stat3(root);
|
|
6591
7173
|
if (!rootStat.isDirectory()) throw new Error(`Preview root is not a directory: ${root}`);
|
|
6592
7174
|
const host = validateHost(opts.host ?? DEFAULT_HOST, opts.allowNetwork === true);
|
|
7175
|
+
const warn = opts.onWarning ?? ((message) => console.warn(message));
|
|
7176
|
+
const themeFallback = opts.theme || opts.ignoreWorkspaceSettings ? null : await createWorkspaceThemeFallback(root, warn);
|
|
6593
7177
|
const allowedHosts = validateAllowedHosts(opts.allowedHosts ?? []);
|
|
6594
7178
|
const maxFileBytes = positiveLimit(
|
|
6595
7179
|
opts.maxFileBytes,
|
|
@@ -6619,7 +7203,7 @@ async function startPreviewServer(opts) {
|
|
|
6619
7203
|
return;
|
|
6620
7204
|
}
|
|
6621
7205
|
activeRequests += 1;
|
|
6622
|
-
handlePreviewRequest(req, res, root, opts.theme, maxFileBytes).catch((error) => {
|
|
7206
|
+
handlePreviewRequest(req, res, root, { themeId: opts.theme, themeFallback, warn }, maxFileBytes).catch((error) => {
|
|
6623
7207
|
reportPreviewFailure(req, error);
|
|
6624
7208
|
if (!res.headersSent) sendText(res, 500, "Internal server error", req.method === "HEAD");
|
|
6625
7209
|
else res.end();
|
|
@@ -6638,13 +7222,17 @@ async function startPreviewServer(opts) {
|
|
|
6638
7222
|
const port = typeof address === "object" && address ? address.port : opts.port;
|
|
6639
7223
|
return { server, root, host, url: `http://${urlHost(host)}:${port}/` };
|
|
6640
7224
|
}
|
|
6641
|
-
var serveCommand = new Command2("serve").description("Start a local dev server for previewing markdown files").option("-p, --port <port>", "port to listen on", "3000").option("-d, --dir <dir>", "directory to serve", ".").option("-t, --theme <id>", "Squisq theme ID to apply").option(
|
|
7225
|
+
var serveCommand = new Command2("serve").description("Start a local dev server for previewing markdown files").option("-p, --port <port>", "port to listen on", "3000").option("-d, --dir <dir>", "directory to serve", ".").option("-t, --theme <id>", "Squisq theme ID to apply").option(
|
|
7226
|
+
"--ignore-workspace-settings",
|
|
7227
|
+
"do not apply the default theme from <dir>/.docblocks/workspace.json"
|
|
7228
|
+
).option("--host <host>", "interface to bind (loopback by default)", DEFAULT_HOST).option("--allow-network", "allow a non-loopback --host").option("--allow-host <host...>", "additional Host header names this server answers to").action(
|
|
6642
7229
|
async (opts) => {
|
|
6643
7230
|
try {
|
|
6644
7231
|
const server = await startPreviewServer({
|
|
6645
7232
|
port: parsePort(opts.port),
|
|
6646
7233
|
dir: opts.dir,
|
|
6647
7234
|
theme: opts.theme,
|
|
7235
|
+
ignoreWorkspaceSettings: opts.ignoreWorkspaceSettings,
|
|
6648
7236
|
host: opts.host,
|
|
6649
7237
|
allowNetwork: opts.allowNetwork,
|
|
6650
7238
|
allowedHosts: opts.allowHost
|
|
@@ -6657,7 +7245,7 @@ var serveCommand = new Command2("serve").description("Start a local dev server f
|
|
|
6657
7245
|
}
|
|
6658
7246
|
}
|
|
6659
7247
|
);
|
|
6660
|
-
async function handlePreviewRequest(req, res, root,
|
|
7248
|
+
async function handlePreviewRequest(req, res, root, render, maxFileBytes) {
|
|
6661
7249
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
6662
7250
|
sendText(res, 405, "Method not allowed");
|
|
6663
7251
|
return;
|
|
@@ -6681,16 +7269,18 @@ async function handlePreviewRequest(req, res, root, themeId, maxFileBytes) {
|
|
|
6681
7269
|
return;
|
|
6682
7270
|
}
|
|
6683
7271
|
if (target.markdown) {
|
|
6684
|
-
const markdown =
|
|
7272
|
+
const markdown = decodeUtf8Text3(await readContainedFile(root, target.filePath, maxFileBytes), {
|
|
6685
7273
|
label: "Preview document",
|
|
6686
7274
|
path: target.filePath
|
|
6687
7275
|
});
|
|
6688
7276
|
const html = await renderMarkdownHtml(markdown, {
|
|
6689
|
-
title:
|
|
7277
|
+
title: path7.basename(target.filePath).replace(/\.(md|markdown)$/i, ""),
|
|
6690
7278
|
sourcePath: target.filePath,
|
|
6691
7279
|
assetRoot: root,
|
|
6692
|
-
themeId,
|
|
7280
|
+
themeId: render.themeId,
|
|
7281
|
+
fallbackThemeId: render.themeFallback ? await render.themeFallback.resolve() : void 0,
|
|
6693
7282
|
mode: "static",
|
|
7283
|
+
onWarning: render.warn,
|
|
6694
7284
|
allowReferencedAsset: ({ assetRoot, requestedPath, physicalPath }) => isAllowedPreviewPath(assetRoot, requestedPath, physicalPath, "embedded-image")
|
|
6695
7285
|
});
|
|
6696
7286
|
sendBody(res, 200, html, "text/html; charset=utf-8", headOnly);
|
|
@@ -6703,7 +7293,7 @@ async function resolveServeTarget(root, requestUrl, maxFileBytes = DEFAULT_MAX_F
|
|
|
6703
7293
|
if (requestUrl.length > 8192 || requestUrl.includes("\0")) return { kind: "bad-request" };
|
|
6704
7294
|
let physicalRoot;
|
|
6705
7295
|
try {
|
|
6706
|
-
physicalRoot = await realpath2(
|
|
7296
|
+
physicalRoot = await realpath2(path7.resolve(root));
|
|
6707
7297
|
} catch (error) {
|
|
6708
7298
|
if (isMissingPathError2(error)) return { kind: "missing" };
|
|
6709
7299
|
if (isPermissionError(error)) return { kind: "forbidden" };
|
|
@@ -6712,7 +7302,7 @@ async function resolveServeTarget(root, requestUrl, maxFileBytes = DEFAULT_MAX_F
|
|
|
6712
7302
|
const decodedPath = decodeRequestPath(requestUrl);
|
|
6713
7303
|
if (!decodedPath) return { kind: "bad-request" };
|
|
6714
7304
|
const requestedPath = decodedPath.replace(/^\/+/, "");
|
|
6715
|
-
const candidate =
|
|
7305
|
+
const candidate = path7.resolve(physicalRoot, requestedPath);
|
|
6716
7306
|
if (!isAllowedPreviewPath(physicalRoot, candidate, candidate, "directory")) {
|
|
6717
7307
|
return { kind: "forbidden" };
|
|
6718
7308
|
}
|
|
@@ -6772,7 +7362,7 @@ async function inspectTarget(root, candidate, maxFileBytes) {
|
|
|
6772
7362
|
}
|
|
6773
7363
|
async function directoryIndexTarget(root, dirPath, maxFileBytes) {
|
|
6774
7364
|
for (const fileName of ["index.html", "index.htm", "index.md", "index.markdown"]) {
|
|
6775
|
-
const target = await inspectTarget(root,
|
|
7365
|
+
const target = await inspectTarget(root, path7.join(dirPath, fileName), maxFileBytes);
|
|
6776
7366
|
if (target.kind === "file" || target.kind === "forbidden" || target.kind === "too-large") {
|
|
6777
7367
|
return target;
|
|
6778
7368
|
}
|
|
@@ -6907,7 +7497,7 @@ function urlHost(host) {
|
|
|
6907
7497
|
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
6908
7498
|
}
|
|
6909
7499
|
function contentTypeFor(filePath) {
|
|
6910
|
-
const ext =
|
|
7500
|
+
const ext = path7.extname(filePath).toLowerCase();
|
|
6911
7501
|
switch (ext) {
|
|
6912
7502
|
case ".html":
|
|
6913
7503
|
case ".htm":
|
|
@@ -6974,8 +7564,8 @@ import {
|
|
|
6974
7564
|
rm,
|
|
6975
7565
|
stat as stat4
|
|
6976
7566
|
} from "fs/promises";
|
|
6977
|
-
import { randomUUID } from "crypto";
|
|
6978
|
-
import { dirname, basename, extname, join, resolve } from "path";
|
|
7567
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
7568
|
+
import { dirname, basename as basename2, extname, join, resolve } from "path";
|
|
6979
7569
|
import { Command as Command3 } from "commander";
|
|
6980
7570
|
init_link_support();
|
|
6981
7571
|
init_node_error();
|
|
@@ -7052,7 +7642,7 @@ async function runConvert(inputPath, opts) {
|
|
|
7052
7642
|
throwIfAborted2(opts.signal);
|
|
7053
7643
|
const resolvedInput = resolve(inputPath);
|
|
7054
7644
|
const outputDir = opts.outputDir ? resolve(opts.outputDir) : dirname(resolvedInput);
|
|
7055
|
-
const inputBasename =
|
|
7645
|
+
const inputBasename = basename2(resolvedInput);
|
|
7056
7646
|
const inputExt = extname(inputBasename);
|
|
7057
7647
|
const baseName = inputExt ? inputBasename.slice(0, -inputExt.length) : inputBasename;
|
|
7058
7648
|
const maxInputBytes = positiveLimit(
|
|
@@ -7194,7 +7784,7 @@ async function runConvert(inputPath, opts) {
|
|
|
7194
7784
|
if (totalOutputBytes > maxTotalOutputBytes) {
|
|
7195
7785
|
throw new Error(`Conversion outputs exceed the ${maxTotalOutputBytes}-byte aggregate limit.`);
|
|
7196
7786
|
}
|
|
7197
|
-
const suggestedFilename =
|
|
7787
|
+
const suggestedFilename = basename2(conversion.suggestedFilename);
|
|
7198
7788
|
const outPath = join(outputDir, suggestedFilename);
|
|
7199
7789
|
const outputPathKey = process.platform === "win32" ? outPath.toLowerCase() : outPath;
|
|
7200
7790
|
if (outputPathKeys.has(outputPathKey)) {
|
|
@@ -7310,7 +7900,7 @@ async function publishConversionBatch(outputs, outputDir, allowOverwrite, signal
|
|
|
7310
7900
|
throwIfAborted2(signal);
|
|
7311
7901
|
const output = outputs[index];
|
|
7312
7902
|
if (!output) continue;
|
|
7313
|
-
const stagedPath = join(stagingDirectory, `${index}-${
|
|
7903
|
+
const stagedPath = join(stagingDirectory, `${index}-${randomUUID2()}.stage`);
|
|
7314
7904
|
await writePrivateFile(stagedPath, output.bytes);
|
|
7315
7905
|
stagedOutputs.push({ outputPath: output.path, stagedPath, bytes: output.bytes });
|
|
7316
7906
|
}
|
|
@@ -7340,7 +7930,7 @@ async function publishConversionBatch(outputs, outputDir, allowOverwrite, signal
|
|
|
7340
7930
|
throw new Error(`Refusing to replace a non-file destination: ${output.outputPath}`);
|
|
7341
7931
|
}
|
|
7342
7932
|
if (existing) {
|
|
7343
|
-
state.backupPath = join(stagingDirectory, `${index}-${
|
|
7933
|
+
state.backupPath = join(stagingDirectory, `${index}-${randomUUID2()}.backup`);
|
|
7344
7934
|
await rename(output.outputPath, state.backupPath);
|
|
7345
7935
|
}
|
|
7346
7936
|
await rename(output.stagedPath, output.outputPath);
|
|
@@ -7368,8 +7958,8 @@ async function publishConversionBatch(outputs, outputDir, allowOverwrite, signal
|
|
|
7368
7958
|
}
|
|
7369
7959
|
}
|
|
7370
7960
|
}
|
|
7371
|
-
async function writePrivateFile(
|
|
7372
|
-
const handle = await open2(
|
|
7961
|
+
async function writePrivateFile(path10, bytes) {
|
|
7962
|
+
const handle = await open2(path10, "wx", 384);
|
|
7373
7963
|
try {
|
|
7374
7964
|
await handle.writeFile(bytes);
|
|
7375
7965
|
await handle.sync();
|
|
@@ -7428,9 +8018,9 @@ async function assertReplaceableOutputs(outputPaths) {
|
|
|
7428
8018
|
}
|
|
7429
8019
|
}
|
|
7430
8020
|
}
|
|
7431
|
-
async function lstatIfPresent(
|
|
8021
|
+
async function lstatIfPresent(path10) {
|
|
7432
8022
|
try {
|
|
7433
|
-
return await lstat(
|
|
8023
|
+
return await lstat(path10);
|
|
7434
8024
|
} catch (error) {
|
|
7435
8025
|
if (isNodeErrorCode(error, ["ENOENT", "ENOTDIR"])) return null;
|
|
7436
8026
|
throw error;
|
|
@@ -7449,11 +8039,11 @@ function throwIfAborted2(signal) {
|
|
|
7449
8039
|
}
|
|
7450
8040
|
async function applyTransformToMarkdown(markdownDoc, container, transformStyle, themeId) {
|
|
7451
8041
|
const { markdownToDoc: markdownToDoc3, docToMarkdown } = await import("@bendyline/squisq/doc");
|
|
7452
|
-
const { readFrontmatterThemeId } = await import("@bendyline/squisq/markdown");
|
|
8042
|
+
const { readFrontmatterThemeId: readFrontmatterThemeId2 } = await import("@bendyline/squisq/markdown");
|
|
7453
8043
|
const { applyTransform, extractDocImages } = await import("@bendyline/squisq/transform");
|
|
7454
8044
|
const doc = markdownToDoc3(markdownDoc);
|
|
7455
8045
|
const images = extractDocImages(doc.blocks);
|
|
7456
|
-
const effectiveTheme = themeId ??
|
|
8046
|
+
const effectiveTheme = themeId ?? readFrontmatterThemeId2(markdownDoc.frontmatter);
|
|
7457
8047
|
const result = applyTransform(doc, transformStyle, { themeId: effectiveTheme, images });
|
|
7458
8048
|
const projected = projectTransformOntoMarkdownSource(doc, result.doc);
|
|
7459
8049
|
const transformedMarkdown = docToMarkdown(projected);
|
|
@@ -7644,7 +8234,7 @@ function isConversionErrorMetadata(error) {
|
|
|
7644
8234
|
// src/commands/video.ts
|
|
7645
8235
|
init_cancellation();
|
|
7646
8236
|
import { lstat as lstat2, mkdir as mkdir3 } from "fs/promises";
|
|
7647
|
-
import { dirname as dirname2, basename as
|
|
8237
|
+
import { dirname as dirname2, basename as basename3, extname as extname2, resolve as resolve2 } from "path";
|
|
7648
8238
|
import { Command as Command4 } from "commander";
|
|
7649
8239
|
import {
|
|
7650
8240
|
resolveDimensions,
|
|
@@ -7686,7 +8276,7 @@ async function runVideo(inputPath, opts, onProgress, dependencies = {}) {
|
|
|
7686
8276
|
width: opts.width,
|
|
7687
8277
|
height: opts.height
|
|
7688
8278
|
});
|
|
7689
|
-
const inputBasename =
|
|
8279
|
+
const inputBasename = basename3(resolvedInput);
|
|
7690
8280
|
const inputExt = extname2(inputBasename);
|
|
7691
8281
|
const baseName = inputExt ? inputBasename.slice(0, -inputExt.length) : inputBasename;
|
|
7692
8282
|
const outputPath = opts.output ? resolve2(opts.output) : resolve2(dirname2(resolvedInput), `${baseName}.mp4`);
|
|
@@ -7977,7 +8567,7 @@ var transformsCommand = new Command7("transforms").description("List all availab
|
|
|
7977
8567
|
import { open as open5 } from "fs/promises";
|
|
7978
8568
|
import { resolve as resolve3 } from "path";
|
|
7979
8569
|
import { Command as Command8 } from "commander";
|
|
7980
|
-
import { decodeUtf8Text as
|
|
8570
|
+
import { decodeUtf8Text as decodeUtf8Text5 } from "@bendyline/docblocks/filesystem";
|
|
7981
8571
|
init_node_error();
|
|
7982
8572
|
var DEFAULT_MAX_PARSE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
7983
8573
|
var DEFAULT_MAX_PARSE_OUTPUT_BYTES = 128 * 1024 * 1024;
|
|
@@ -7994,7 +8584,7 @@ async function runParse(inputPath, options = {}) {
|
|
|
7994
8584
|
"parse output byte"
|
|
7995
8585
|
);
|
|
7996
8586
|
const bytes = await readBoundedFile(resolvedInput, maxInputBytes);
|
|
7997
|
-
const content =
|
|
8587
|
+
const content = decodeUtf8Text5(bytes, { label: "Parse input", path: resolvedInput });
|
|
7998
8588
|
const { parseMarkdown: parseMarkdown2 } = await import("@bendyline/squisq/markdown");
|
|
7999
8589
|
const markdownDoc = parseMarkdown2(content);
|
|
8000
8590
|
const stats = {
|
|
@@ -8057,9 +8647,95 @@ var parseCommand = new Command8("parse").description("Parse a markdown file and
|
|
|
8057
8647
|
}
|
|
8058
8648
|
});
|
|
8059
8649
|
|
|
8650
|
+
// src/commands/workspace.ts
|
|
8651
|
+
import { Command as Command9 } from "commander";
|
|
8652
|
+
import { FsError as FsError2 } from "@bendyline/docblocks/filesystem";
|
|
8653
|
+
import {
|
|
8654
|
+
WORKSPACE_SETTINGS_PATH as WORKSPACE_SETTINGS_PATH2,
|
|
8655
|
+
refreshWorkspaceOutputs,
|
|
8656
|
+
resolveCatalogOutputs
|
|
8657
|
+
} from "@bendyline/docblocks/workspace-settings";
|
|
8658
|
+
import { htmlToMarkdownDocSync, markdownDocToPlainHtml } from "@bendyline/squisq-formats/html";
|
|
8659
|
+
async function runWorkspaceRefresh(options = {}) {
|
|
8660
|
+
const io = await createNodeWorkspaceIO(options.dir ?? ".");
|
|
8661
|
+
const dryRun = options.dryRun === true;
|
|
8662
|
+
const settingsRead = await readWorkspaceSettings(io);
|
|
8663
|
+
if (settingsRead.status === "missing") {
|
|
8664
|
+
return { root: io.root, status: "missing", documentCount: 0, outputs: [], dryRun };
|
|
8665
|
+
}
|
|
8666
|
+
if (settingsRead.status !== "ok") {
|
|
8667
|
+
throw new Error(describeWorkspaceSettingsProblem(settingsRead.message));
|
|
8668
|
+
}
|
|
8669
|
+
const settings = settingsRead.settings;
|
|
8670
|
+
const resolved = resolveCatalogOutputs(settings);
|
|
8671
|
+
if (!resolved) {
|
|
8672
|
+
return { root: io.root, status: "disabled", documentCount: 0, outputs: [], dryRun };
|
|
8673
|
+
}
|
|
8674
|
+
const warn = options.onWarning ?? ((message) => console.warn(message));
|
|
8675
|
+
const catalogThemeId = resolved.html?.themeId;
|
|
8676
|
+
if (catalogThemeId && !(await getAvailableThemeIds()).includes(catalogThemeId)) {
|
|
8677
|
+
warn(
|
|
8678
|
+
`Catalog theme "${catalogThemeId}" from ${WORKSPACE_SETTINGS_PATH2} is not a known theme; the page renders with the standard theme.`
|
|
8679
|
+
);
|
|
8680
|
+
}
|
|
8681
|
+
const refresh = () => refreshWorkspaceOutputs(io, settings, renderer, {
|
|
8682
|
+
signal: options.signal,
|
|
8683
|
+
force: options.force === true,
|
|
8684
|
+
dryRun,
|
|
8685
|
+
limits: options.limits
|
|
8686
|
+
});
|
|
8687
|
+
let result;
|
|
8688
|
+
try {
|
|
8689
|
+
result = await refresh();
|
|
8690
|
+
} catch (error) {
|
|
8691
|
+
if (!(error instanceof FsError2) || error.code !== "conflict") throw error;
|
|
8692
|
+
result = await refresh();
|
|
8693
|
+
}
|
|
8694
|
+
return {
|
|
8695
|
+
root: io.root,
|
|
8696
|
+
status: "refreshed",
|
|
8697
|
+
documentCount: result.documentCount,
|
|
8698
|
+
outputs: result.outputs,
|
|
8699
|
+
dryRun
|
|
8700
|
+
};
|
|
8701
|
+
}
|
|
8702
|
+
var renderer = {
|
|
8703
|
+
renderCatalogHtml: (document2, { title, themeId }) => markdownDocToPlainHtml(document2, { title, themeId }),
|
|
8704
|
+
importHtml: (html) => htmlToMarkdownDocSync(html)
|
|
8705
|
+
};
|
|
8706
|
+
function describeWorkspaceOutput(output) {
|
|
8707
|
+
const status = output.status === "would-write" ? "would write" : output.status;
|
|
8708
|
+
return output.message ? `${status} ${output.path}: ${output.message}` : `${status} ${output.path}`;
|
|
8709
|
+
}
|
|
8710
|
+
var refreshCommand = new Command9("refresh").description(`Regenerate the catalog outputs enabled in <dir>/${WORKSPACE_SETTINGS_PATH2}`).argument("[dir]", "workspace root", ".").option("--force", "rewrite the catalog page even when its inputs are unchanged").option("--dry-run", "report what would change without writing anything").action(async (dir, opts) => {
|
|
8711
|
+
try {
|
|
8712
|
+
const result = await runWorkspaceRefresh({ dir, force: opts.force, dryRun: opts.dryRun });
|
|
8713
|
+
if (result.status === "missing") {
|
|
8714
|
+
console.error(
|
|
8715
|
+
`No workspace settings found at ${WORKSPACE_SETTINGS_PATH2} in ${result.root}; no outputs are enabled.`
|
|
8716
|
+
);
|
|
8717
|
+
return;
|
|
8718
|
+
}
|
|
8719
|
+
if (result.status === "disabled") {
|
|
8720
|
+
console.error(`${WORKSPACE_SETTINGS_PATH2} enables no catalog outputs; nothing to refresh.`);
|
|
8721
|
+
return;
|
|
8722
|
+
}
|
|
8723
|
+
for (const output of result.outputs) console.error(describeWorkspaceOutput(output));
|
|
8724
|
+
console.error(
|
|
8725
|
+
`${result.dryRun ? "Dry run: " : ""}${result.documentCount} document(s) cataloged in ${result.root}.`
|
|
8726
|
+
);
|
|
8727
|
+
if (result.outputs.some((output) => output.status === "blocked")) process.exitCode = 1;
|
|
8728
|
+
} catch (err) {
|
|
8729
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8730
|
+
console.error(`Error: ${message}`);
|
|
8731
|
+
process.exitCode = 1;
|
|
8732
|
+
}
|
|
8733
|
+
});
|
|
8734
|
+
var workspaceCommand = new Command9("workspace").description(`Maintain workspace outputs configured in ${WORKSPACE_SETTINGS_PATH2}`).addCommand(refreshCommand);
|
|
8735
|
+
|
|
8060
8736
|
// src/program.ts
|
|
8061
8737
|
init_version();
|
|
8062
|
-
var program = new
|
|
8738
|
+
var program = new Command10().name("docblocks").description("Build, preview, convert, render, inspect, and automate documents").version(getPackageVersion()).addCommand(buildCommand).addCommand(serveCommand).addCommand(convertCommand).addCommand(videoCommand).addCommand(mcpCommand).addCommand(themesCommand).addCommand(transformsCommand).addCommand(parseCommand).addCommand(workspaceCommand);
|
|
8063
8739
|
|
|
8064
8740
|
// src/bin.ts
|
|
8065
8741
|
program.parseAsync().catch((error) => {
|