@bendyline/docblocks-cli 2.6.2 → 2.7.1
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 +3858 -3403
- package/dist/bin.js +787 -116
- package/dist/index.d.ts +56 -1
- package/dist/index.js +706 -46
- package/package.json +8 -8
package/dist/bin.js
CHANGED
|
@@ -166,7 +166,7 @@ var init_cancellation = __esm({
|
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
// src/mcp/artifact-store.ts
|
|
169
|
-
import { createHash, randomUUID as
|
|
169
|
+
import { createHash, randomUUID as randomUUID3 } from "crypto";
|
|
170
170
|
import { mkdtemp as mkdtemp2, open as open3, rm as rm2 } from "fs/promises";
|
|
171
171
|
import { join as join2 } from "path";
|
|
172
172
|
import { tmpdir } from "os";
|
|
@@ -216,9 +216,9 @@ function immutableArtifactRef(ref) {
|
|
|
216
216
|
);
|
|
217
217
|
return Object.freeze({ ...ref, appliedOptions, engineVersions });
|
|
218
218
|
}
|
|
219
|
-
async function writeFileCancellable(
|
|
219
|
+
async function writeFileCancellable(path10, bytes, signal) {
|
|
220
220
|
throwIfAborted4(signal);
|
|
221
|
-
const handle = await open3(
|
|
221
|
+
const handle = await open3(path10, "wx");
|
|
222
222
|
try {
|
|
223
223
|
let offset = 0;
|
|
224
224
|
while (offset < bytes.byteLength) {
|
|
@@ -233,9 +233,9 @@ async function writeFileCancellable(path9, bytes, signal) {
|
|
|
233
233
|
await handle.close();
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
-
async function readFileCancellable(
|
|
236
|
+
async function readFileCancellable(path10, options) {
|
|
237
237
|
throwIfAborted4(options.signal);
|
|
238
|
-
const handle = await open3(
|
|
238
|
+
const handle = await open3(path10, "r");
|
|
239
239
|
try {
|
|
240
240
|
const stats = await handle.stat();
|
|
241
241
|
throwIfAborted4(options.signal);
|
|
@@ -275,7 +275,7 @@ async function hashBytesCancellable(bytes, signal) {
|
|
|
275
275
|
return hash.digest("hex");
|
|
276
276
|
}
|
|
277
277
|
async function removeFilesBestEffort(paths) {
|
|
278
|
-
await Promise.allSettled(paths.map((
|
|
278
|
+
await Promise.allSettled(paths.map((path10) => rm2(path10, { force: true })));
|
|
279
279
|
}
|
|
280
280
|
async function waitForIoDrain(leases, timeoutMs) {
|
|
281
281
|
if (leases.length === 0) return;
|
|
@@ -388,8 +388,8 @@ var init_artifact_store = __esm({
|
|
|
388
388
|
throw new Error("MCP artifact exceeds the configured per-artifact limit");
|
|
389
389
|
}
|
|
390
390
|
const sha2562 = await hashBytesCancellable(bytes, signal);
|
|
391
|
-
const id =
|
|
392
|
-
const
|
|
391
|
+
const id = randomUUID3();
|
|
392
|
+
const path10 = join2(await this.directory, `${id}.artifact`);
|
|
393
393
|
throwIfAborted4(signal);
|
|
394
394
|
const created = /* @__PURE__ */ new Date();
|
|
395
395
|
const ref = immutableArtifactRef({
|
|
@@ -424,7 +424,7 @@ var init_artifact_store = __esm({
|
|
|
424
424
|
});
|
|
425
425
|
await removeFilesBestEffort(acquisition.expiredPaths);
|
|
426
426
|
try {
|
|
427
|
-
await this.dependencies.writeFile(
|
|
427
|
+
await this.dependencies.writeFile(path10, bytes, acquisition.io.controller.signal);
|
|
428
428
|
throwIfAborted4(acquisition.io.controller.signal);
|
|
429
429
|
await this.lock(() => {
|
|
430
430
|
this.assertActive();
|
|
@@ -432,7 +432,7 @@ var init_artifact_store = __esm({
|
|
|
432
432
|
this.releaseReservationLocked(reservation);
|
|
433
433
|
this.records.set(id, {
|
|
434
434
|
ref,
|
|
435
|
-
path:
|
|
435
|
+
path: path10,
|
|
436
436
|
conversionReport: null,
|
|
437
437
|
reportBytes: 0,
|
|
438
438
|
activeReaders: 0,
|
|
@@ -443,7 +443,7 @@ var init_artifact_store = __esm({
|
|
|
443
443
|
return ref;
|
|
444
444
|
} catch (caught) {
|
|
445
445
|
await this.lock(() => this.releaseReservationLocked(reservation));
|
|
446
|
-
await rm2(
|
|
446
|
+
await rm2(path10, { force: true }).catch(() => void 0);
|
|
447
447
|
throw caught;
|
|
448
448
|
} finally {
|
|
449
449
|
this.completeIoLease(acquisition.io);
|
|
@@ -556,8 +556,8 @@ var init_artifact_store = __esm({
|
|
|
556
556
|
for (const [id, stored] of this.records) {
|
|
557
557
|
const expiresAt = stored.ref.expiresAt ? Date.parse(stored.ref.expiresAt) : Number.POSITIVE_INFINITY;
|
|
558
558
|
if (expiresAt > now) continue;
|
|
559
|
-
const
|
|
560
|
-
if (
|
|
559
|
+
const path10 = this.removeRecordLocked(id, stored);
|
|
560
|
+
if (path10) paths.push(path10);
|
|
561
561
|
}
|
|
562
562
|
return paths;
|
|
563
563
|
}
|
|
@@ -697,8 +697,8 @@ var init_artifact_store = __esm({
|
|
|
697
697
|
|
|
698
698
|
// src/mcp/authority.ts
|
|
699
699
|
import { lstat as lstat3, link as link2, open as open4, realpath as realpath4, rename as rename2, stat as stat5, unlink } from "fs/promises";
|
|
700
|
-
import
|
|
701
|
-
import { createHash as createHash2, randomUUID as
|
|
700
|
+
import path8 from "path";
|
|
701
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
702
702
|
import { MCP_WIRE_LIMITS as MCP_WIRE_LIMITS2 } from "@bendyline/docblocks/mcp";
|
|
703
703
|
async function resolveRoots(values) {
|
|
704
704
|
const roots = [];
|
|
@@ -718,7 +718,7 @@ function rootIdFor(physicalPath) {
|
|
|
718
718
|
return `root-${createHash2("sha256").update(normalized).digest("hex").slice(0, 16)}`;
|
|
719
719
|
}
|
|
720
720
|
function rootLabel(physicalPath) {
|
|
721
|
-
const candidate =
|
|
721
|
+
const candidate = path8.basename(physicalPath) || "root";
|
|
722
722
|
let label = "";
|
|
723
723
|
for (const character of candidate) {
|
|
724
724
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
@@ -729,7 +729,7 @@ function rootLabel(physicalPath) {
|
|
|
729
729
|
return label || "root";
|
|
730
730
|
}
|
|
731
731
|
function parseRootRelativePath(value) {
|
|
732
|
-
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)) {
|
|
733
733
|
throw new Error("Invalid MCP root-relative path");
|
|
734
734
|
}
|
|
735
735
|
const segments = value.split("/");
|
|
@@ -747,7 +747,7 @@ async function nearestExistingPath(candidate) {
|
|
|
747
747
|
await lstat3(current);
|
|
748
748
|
return current;
|
|
749
749
|
} catch {
|
|
750
|
-
const parent =
|
|
750
|
+
const parent = path8.dirname(current);
|
|
751
751
|
if (parent === current) throw new Error("MCP output has no accessible parent directory");
|
|
752
752
|
current = parent;
|
|
753
753
|
}
|
|
@@ -757,7 +757,7 @@ function validatePath(value) {
|
|
|
757
757
|
if (!value || value.length > MAX_PATH_CHARACTERS || value.includes("\0")) {
|
|
758
758
|
throw new Error("Invalid MCP filesystem path");
|
|
759
759
|
}
|
|
760
|
-
return
|
|
760
|
+
return path8.resolve(value);
|
|
761
761
|
}
|
|
762
762
|
function validatePortableFilename(value) {
|
|
763
763
|
const stem = value.split(".", 1)[0]?.toLowerCase() ?? "";
|
|
@@ -774,7 +774,7 @@ function hasUnsafeFilenameCharacter(value) {
|
|
|
774
774
|
}
|
|
775
775
|
function samePath(left, right) {
|
|
776
776
|
const normalize = (value) => {
|
|
777
|
-
const resolved =
|
|
777
|
+
const resolved = path8.resolve(value);
|
|
778
778
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
779
779
|
};
|
|
780
780
|
return normalize(left) === normalize(right);
|
|
@@ -836,7 +836,7 @@ async function hashFileBounded(filePath, maximumBytes, signal, afterChunk) {
|
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
838
|
async function readMaterializationParentIdentity(target, writeRoots) {
|
|
839
|
-
const parent =
|
|
839
|
+
const parent = path8.dirname(target);
|
|
840
840
|
let lexical;
|
|
841
841
|
let physical;
|
|
842
842
|
let info;
|
|
@@ -980,7 +980,7 @@ var init_authority = __esm({
|
|
|
980
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."
|
|
981
981
|
);
|
|
982
982
|
}
|
|
983
|
-
const candidate =
|
|
983
|
+
const candidate = path8.join(root.physical, ...parseRootRelativePath(workspacePath));
|
|
984
984
|
const physical = await realpath4(candidate).catch(() => null);
|
|
985
985
|
if (!physical || !isPathInside(root.physical, physical)) {
|
|
986
986
|
throw new Error("MCP read path is outside the selected root");
|
|
@@ -996,9 +996,9 @@ var init_authority = __esm({
|
|
|
996
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."
|
|
997
997
|
);
|
|
998
998
|
}
|
|
999
|
-
const candidate =
|
|
999
|
+
const candidate = path8.join(root.physical, ...parseRootRelativePath(workspacePath));
|
|
1000
1000
|
const authorized = await this.authorizeWrite(candidate);
|
|
1001
|
-
const physicalParent = await realpath4(
|
|
1001
|
+
const physicalParent = await realpath4(path8.dirname(authorized));
|
|
1002
1002
|
if (!isPathInside(root.physical, physicalParent)) {
|
|
1003
1003
|
throw new Error("MCP write path is outside the selected root");
|
|
1004
1004
|
}
|
|
@@ -1017,7 +1017,7 @@ var init_authority = __esm({
|
|
|
1017
1017
|
}
|
|
1018
1018
|
async authorizeWrite(requestedPath) {
|
|
1019
1019
|
const candidate = validatePath(requestedPath);
|
|
1020
|
-
const filename =
|
|
1020
|
+
const filename = path8.basename(candidate);
|
|
1021
1021
|
validatePortableFilename(filename);
|
|
1022
1022
|
const existing = await lstat3(candidate).catch(() => null);
|
|
1023
1023
|
if (existing?.isSymbolicLink()) {
|
|
@@ -1026,16 +1026,16 @@ var init_authority = __esm({
|
|
|
1026
1026
|
if (existing && !existing.isFile()) {
|
|
1027
1027
|
throw new Error("MCP output target must be a regular file");
|
|
1028
1028
|
}
|
|
1029
|
-
const nearest = await nearestExistingPath(existing ? candidate :
|
|
1029
|
+
const nearest = await nearestExistingPath(existing ? candidate : path8.dirname(candidate));
|
|
1030
1030
|
const physicalTarget = existing ? await realpath4(candidate) : null;
|
|
1031
|
-
const physicalParent = physicalTarget ?
|
|
1031
|
+
const physicalParent = physicalTarget ? path8.dirname(physicalTarget) : await realpath4(nearest);
|
|
1032
1032
|
if (!this.writeRoots.some((root) => isPathInside(root.physical, physicalParent))) {
|
|
1033
1033
|
throw new Error("MCP write path is outside the configured roots");
|
|
1034
1034
|
}
|
|
1035
|
-
if (!existing && !samePath(nearest,
|
|
1035
|
+
if (!existing && !samePath(nearest, path8.dirname(candidate))) {
|
|
1036
1036
|
throw new Error("MCP output parent directory does not exist");
|
|
1037
1037
|
}
|
|
1038
|
-
return physicalTarget ??
|
|
1038
|
+
return physicalTarget ?? path8.join(physicalParent, filename);
|
|
1039
1039
|
}
|
|
1040
1040
|
/** Publish bounded bytes through an operation-owned sibling file. */
|
|
1041
1041
|
async materializeBytes(requestedPath, content, maximumBytes, options = {}) {
|
|
@@ -1079,9 +1079,9 @@ var init_authority = __esm({
|
|
|
1079
1079
|
);
|
|
1080
1080
|
if (actual !== expectedSha256) throw new Error("MCP output hash precondition failed");
|
|
1081
1081
|
}
|
|
1082
|
-
const temporary =
|
|
1083
|
-
|
|
1084
|
-
`.${
|
|
1082
|
+
const temporary = path8.join(
|
|
1083
|
+
path8.dirname(target),
|
|
1084
|
+
`.${path8.basename(target)}.${process.pid}.${randomUUID4()}.tmp`
|
|
1085
1085
|
);
|
|
1086
1086
|
let handle = null;
|
|
1087
1087
|
try {
|
|
@@ -1181,7 +1181,7 @@ import { createHash as createHash3 } from "crypto";
|
|
|
1181
1181
|
import { createRequire } from "module";
|
|
1182
1182
|
import { readFileSync } from "fs";
|
|
1183
1183
|
import { readdir, readFile as readFile2, stat as stat6 } from "fs/promises";
|
|
1184
|
-
import
|
|
1184
|
+
import path9 from "path";
|
|
1185
1185
|
function getPackageVersion() {
|
|
1186
1186
|
const pkg = readPackageJson();
|
|
1187
1187
|
if (typeof pkg?.version === "string" && pkg.version.length > 0) {
|
|
@@ -1217,10 +1217,10 @@ async function computeRuntimeVersion(packageName, resolutionSpecifier) {
|
|
|
1217
1217
|
try {
|
|
1218
1218
|
const entry = require2.resolve(resolutionSpecifier);
|
|
1219
1219
|
const root = await findPackageRoot(entry, packageName);
|
|
1220
|
-
const files = await collectRuntimeFiles(
|
|
1220
|
+
const files = await collectRuntimeFiles(path9.join(root, "dist"));
|
|
1221
1221
|
const hash = createHash3("sha256");
|
|
1222
1222
|
for (const file of files) {
|
|
1223
|
-
const relative =
|
|
1223
|
+
const relative = path9.relative(root, file).split(path9.sep).join("/");
|
|
1224
1224
|
hash.update(relative);
|
|
1225
1225
|
hash.update("\0");
|
|
1226
1226
|
hash.update(await readFile2(file));
|
|
@@ -1240,16 +1240,16 @@ function readPackageJson() {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
}
|
|
1242
1242
|
async function findPackageRoot(entry, packageName) {
|
|
1243
|
-
let directory =
|
|
1243
|
+
let directory = path9.dirname(entry);
|
|
1244
1244
|
for (let depth = 0; depth < 12; depth += 1) {
|
|
1245
1245
|
try {
|
|
1246
1246
|
const parsed = JSON.parse(
|
|
1247
|
-
await readFile2(
|
|
1247
|
+
await readFile2(path9.join(directory, "package.json"), "utf8")
|
|
1248
1248
|
);
|
|
1249
1249
|
if (parsed.name === packageName) return directory;
|
|
1250
1250
|
} catch {
|
|
1251
1251
|
}
|
|
1252
|
-
const parent =
|
|
1252
|
+
const parent = path9.dirname(directory);
|
|
1253
1253
|
if (parent === directory) break;
|
|
1254
1254
|
directory = parent;
|
|
1255
1255
|
}
|
|
@@ -1262,7 +1262,7 @@ async function collectRuntimeFiles(directory) {
|
|
|
1262
1262
|
while (pending.length > 0) {
|
|
1263
1263
|
const current = pending.pop();
|
|
1264
1264
|
for (const entry of await readdir(current, { withFileTypes: true })) {
|
|
1265
|
-
const candidate =
|
|
1265
|
+
const candidate = path9.join(current, entry.name);
|
|
1266
1266
|
if (entry.isDirectory()) {
|
|
1267
1267
|
pending.push(candidate);
|
|
1268
1268
|
} else if (entry.isFile() && /\.(?:js|json|css|wasm)$/iu.test(entry.name)) {
|
|
@@ -1346,9 +1346,9 @@ var init_output_bounds = __esm({
|
|
|
1346
1346
|
|
|
1347
1347
|
// src/mcp/document-service.ts
|
|
1348
1348
|
import { createHash as createHash5 } from "crypto";
|
|
1349
|
-
import { basename as
|
|
1349
|
+
import { basename as basename4, extname as extname3 } from "path";
|
|
1350
1350
|
import { MCP_WIRE_LIMITS as MCP_WIRE_LIMITS4 } from "@bendyline/docblocks/mcp";
|
|
1351
|
-
import { decodeUtf8Text as
|
|
1351
|
+
import { decodeUtf8Text as decodeUtf8Text4, tryParseWorkspacePath as tryParseWorkspacePath2 } from "@bendyline/docblocks/filesystem";
|
|
1352
1352
|
async function readAssetManifest(container, signal) {
|
|
1353
1353
|
const bytes = await container.readFile(ASSET_MANIFEST_PATH);
|
|
1354
1354
|
if (!bytes) return /* @__PURE__ */ new Map();
|
|
@@ -1358,7 +1358,7 @@ async function readAssetManifest(container, signal) {
|
|
|
1358
1358
|
}
|
|
1359
1359
|
try {
|
|
1360
1360
|
const parsed = JSON.parse(
|
|
1361
|
-
|
|
1361
|
+
decodeUtf8Text4(bytes, {
|
|
1362
1362
|
label: "The document asset manifest",
|
|
1363
1363
|
path: ASSET_MANIFEST_PATH
|
|
1364
1364
|
})
|
|
@@ -1372,12 +1372,12 @@ async function readAssetManifest(container, signal) {
|
|
|
1372
1372
|
if (!isExactRecord(candidate, ["path", "mimeType", "altText", "credit", "license"])) {
|
|
1373
1373
|
throw new Error("The document asset manifest contains an invalid entry");
|
|
1374
1374
|
}
|
|
1375
|
-
const
|
|
1376
|
-
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)) {
|
|
1377
1377
|
throw new Error("The document asset manifest contains invalid metadata");
|
|
1378
1378
|
}
|
|
1379
|
-
entries.set(
|
|
1380
|
-
path:
|
|
1379
|
+
entries.set(path10, {
|
|
1380
|
+
path: path10,
|
|
1381
1381
|
mimeType: candidate.mimeType,
|
|
1382
1382
|
altText: candidate.altText,
|
|
1383
1383
|
credit: candidate.credit,
|
|
@@ -1513,7 +1513,7 @@ function throwIfAborted5(signal) {
|
|
|
1513
1513
|
throwIfAborted(signal, "MCP operation was cancelled");
|
|
1514
1514
|
}
|
|
1515
1515
|
function baseNameFromName(name) {
|
|
1516
|
-
const leaf =
|
|
1516
|
+
const leaf = basename4(name.replace(/\\/gu, "/")) || "document";
|
|
1517
1517
|
const extension = extname3(leaf);
|
|
1518
1518
|
return (extension ? leaf.slice(0, -extension.length) : leaf) || "document";
|
|
1519
1519
|
}
|
|
@@ -1605,7 +1605,7 @@ var init_document_service = __esm({
|
|
|
1605
1605
|
const physical = await this.authority.authorizeRootRead(source.rootId, source.path);
|
|
1606
1606
|
const bytes = await this.authority.readFile(physical, signal);
|
|
1607
1607
|
throwIfAborted5(signal);
|
|
1608
|
-
const filename =
|
|
1608
|
+
const filename = basename4(source.path);
|
|
1609
1609
|
return {
|
|
1610
1610
|
bytes,
|
|
1611
1611
|
filename,
|
|
@@ -1662,7 +1662,7 @@ var init_document_service = __esm({
|
|
|
1662
1662
|
case "file": {
|
|
1663
1663
|
const physical = await this.authority.authorizeRootRead(source.rootId, source.path);
|
|
1664
1664
|
const bytes = await this.authority.readFile(physical, signal);
|
|
1665
|
-
const name =
|
|
1665
|
+
const name = basename4(source.path);
|
|
1666
1666
|
return {
|
|
1667
1667
|
source: { kind: "bytes", data: bytes, filename: name },
|
|
1668
1668
|
sourceFormat: normalizeFormat(source.format ?? formatFromName(name)),
|
|
@@ -2670,7 +2670,7 @@ var init_intelligence = __esm({
|
|
|
2670
2670
|
// src/mcp/preview-service.ts
|
|
2671
2671
|
import { mkdtemp as mkdtemp3, readFile as readFile3, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
2672
2672
|
import { tmpdir as tmpdir2 } from "os";
|
|
2673
|
-
import { basename as
|
|
2673
|
+
import { basename as basename5, extname as extname4, join as join3 } from "path";
|
|
2674
2674
|
import { DOCBLOCKS_MCP_WIRE_VERSION as DOCBLOCKS_MCP_WIRE_VERSION2 } from "@bendyline/docblocks/mcp";
|
|
2675
2675
|
async function previewEngineVersions() {
|
|
2676
2676
|
return [
|
|
@@ -2908,7 +2908,7 @@ function thumbnailFilter(width, height) {
|
|
|
2908
2908
|
return `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`;
|
|
2909
2909
|
}
|
|
2910
2910
|
function baseNameFromFilename(filename) {
|
|
2911
|
-
const leaf =
|
|
2911
|
+
const leaf = basename5(filename.replace(/\\/gu, "/")) || "video";
|
|
2912
2912
|
const extension = extname4(leaf);
|
|
2913
2913
|
return (extension ? leaf.slice(0, -extension.length) : leaf) || "video";
|
|
2914
2914
|
}
|
|
@@ -3706,7 +3706,7 @@ async function convertPreparedDocument(artifacts, prepared, request, signal, pro
|
|
|
3706
3706
|
}
|
|
3707
3707
|
}
|
|
3708
3708
|
function createConversionCacheKey(prepared, request, target, fidelity, engineVersions) {
|
|
3709
|
-
const
|
|
3709
|
+
const canonical2 = canonicalJson({
|
|
3710
3710
|
source: {
|
|
3711
3711
|
sha256: prepared.sourceSha256,
|
|
3712
3712
|
format: prepared.sourceFormat,
|
|
@@ -3727,7 +3727,7 @@ function createConversionCacheKey(prepared, request, target, fidelity, engineVer
|
|
|
3727
3727
|
title: request.title ?? null
|
|
3728
3728
|
}
|
|
3729
3729
|
});
|
|
3730
|
-
return createHash6("sha256").update(
|
|
3730
|
+
return createHash6("sha256").update(canonical2).digest("hex");
|
|
3731
3731
|
}
|
|
3732
3732
|
async function findCompletedConversion(artifacts, key) {
|
|
3733
3733
|
const cache = completedConversionCache(artifacts);
|
|
@@ -4305,10 +4305,10 @@ function registerAgenticTools(server, context) {
|
|
|
4305
4305
|
async ({ source, targets, themeId, transformId, autoTemplates, title }, extra) => {
|
|
4306
4306
|
try {
|
|
4307
4307
|
return await context.runOperation(extra.signal, async (operationSignal) => {
|
|
4308
|
-
const
|
|
4308
|
+
const canonical2 = requireDocumentSource(source);
|
|
4309
4309
|
const documents = new DocumentService(await context.authority, context.artifacts);
|
|
4310
4310
|
await sendProgress(extra, 0, targets.length + 1, "Resolving document");
|
|
4311
|
-
const prepared = await documents.prepare(
|
|
4311
|
+
const prepared = await documents.prepare(canonical2, operationSignal);
|
|
4312
4312
|
const results = await convertPreparedDocument(
|
|
4313
4313
|
context.artifacts,
|
|
4314
4314
|
prepared,
|
|
@@ -4350,9 +4350,9 @@ function registerAgenticTools(server, context) {
|
|
|
4350
4350
|
async ({ source }, extra) => {
|
|
4351
4351
|
try {
|
|
4352
4352
|
return await context.runOperation(extra.signal, async (operationSignal) => {
|
|
4353
|
-
const
|
|
4353
|
+
const canonical2 = requireDocumentSource(source);
|
|
4354
4354
|
const documents = new DocumentService(await context.authority, context.artifacts);
|
|
4355
|
-
const prepared = await documents.prepare(
|
|
4355
|
+
const prepared = await documents.prepare(canonical2, operationSignal);
|
|
4356
4356
|
const [result] = await convertPreparedDocument(
|
|
4357
4357
|
context.artifacts,
|
|
4358
4358
|
prepared,
|
|
@@ -4385,7 +4385,7 @@ function registerAgenticTools(server, context) {
|
|
|
4385
4385
|
async ({ artifactUri, destination }, extra) => {
|
|
4386
4386
|
try {
|
|
4387
4387
|
return await context.runOperation(extra.signal, async (operationSignal) => {
|
|
4388
|
-
const
|
|
4388
|
+
const canonical2 = requireWire(
|
|
4389
4389
|
parseMaterializationOptions(destination),
|
|
4390
4390
|
"materialization options"
|
|
4391
4391
|
);
|
|
@@ -4395,15 +4395,15 @@ function registerAgenticTools(server, context) {
|
|
|
4395
4395
|
context.artifacts.read(artifactUri, operationSignal)
|
|
4396
4396
|
]);
|
|
4397
4397
|
throwIfAborted5(operationSignal);
|
|
4398
|
-
const outputPath = await authority.authorizeRootWrite(
|
|
4398
|
+
const outputPath = await authority.authorizeRootWrite(canonical2.rootId, canonical2.path);
|
|
4399
4399
|
await authority.materializeBytes(outputPath, bytes, bytes.byteLength, {
|
|
4400
|
-
ifExists:
|
|
4401
|
-
expectedSha256:
|
|
4400
|
+
ifExists: canonical2.ifExists,
|
|
4401
|
+
expectedSha256: canonical2.expectedSha256 ?? void 0,
|
|
4402
4402
|
signal: operationSignal
|
|
4403
4403
|
});
|
|
4404
4404
|
const payload = {
|
|
4405
4405
|
artifact,
|
|
4406
|
-
destination: { rootId:
|
|
4406
|
+
destination: { rootId: canonical2.rootId, path: canonical2.path },
|
|
4407
4407
|
sha256: artifact.sha256
|
|
4408
4408
|
};
|
|
4409
4409
|
return textAndStructured(payload);
|
|
@@ -4710,6 +4710,7 @@ function registerTemplateTools(server, context) {
|
|
|
4710
4710
|
title: boundNullableWireText(block.title, MCP_WIRE_LIMITS7.labelCharacters),
|
|
4711
4711
|
profile: {
|
|
4712
4712
|
...profile,
|
|
4713
|
+
hasCompoundBody: profile.hasCompoundBody ?? false,
|
|
4713
4714
|
hasAsciiDiagram: profile.hasAsciiDiagram ?? false,
|
|
4714
4715
|
hasTimeline: profile.hasTimeline ?? false,
|
|
4715
4716
|
hasTree: profile.hasTree ?? false
|
|
@@ -4866,6 +4867,7 @@ function registerTemplateTools(server, context) {
|
|
|
4866
4867
|
title: boundNullableWireText(block.title, MCP_WIRE_LIMITS7.labelCharacters),
|
|
4867
4868
|
profile: {
|
|
4868
4869
|
...profile,
|
|
4870
|
+
hasCompoundBody: profile.hasCompoundBody ?? false,
|
|
4869
4871
|
hasAsciiDiagram: profile.hasAsciiDiagram ?? false,
|
|
4870
4872
|
hasTimeline: profile.hasTimeline ?? false,
|
|
4871
4873
|
hasTree: profile.hasTree ?? false
|
|
@@ -6158,12 +6160,12 @@ var init_server = __esm({
|
|
|
6158
6160
|
});
|
|
6159
6161
|
|
|
6160
6162
|
// src/program.ts
|
|
6161
|
-
import { Command as
|
|
6163
|
+
import { Command as Command10 } from "commander";
|
|
6162
6164
|
|
|
6163
6165
|
// src/commands/build.ts
|
|
6164
6166
|
import { Command } from "commander";
|
|
6165
6167
|
import { mkdir, opendir, readFile, stat as stat2, writeFile } from "fs/promises";
|
|
6166
|
-
import
|
|
6168
|
+
import path5 from "path";
|
|
6167
6169
|
|
|
6168
6170
|
// src/render-html.ts
|
|
6169
6171
|
init_contained_file();
|
|
@@ -6172,7 +6174,7 @@ init_node_error();
|
|
|
6172
6174
|
import { realpath, stat } from "fs/promises";
|
|
6173
6175
|
import path3 from "path";
|
|
6174
6176
|
import { markdownToDoc } from "@bendyline/squisq/doc";
|
|
6175
|
-
import { parseMarkdown } from "@bendyline/squisq/markdown";
|
|
6177
|
+
import { parseMarkdown, readFrontmatterThemeId } from "@bendyline/squisq/markdown";
|
|
6176
6178
|
import { collectImagePaths, docToHtml } from "@bendyline/squisq-formats/html";
|
|
6177
6179
|
import { PLAYER_BUNDLE } from "@bendyline/squisq-react/standalone-source";
|
|
6178
6180
|
|
|
@@ -6194,25 +6196,35 @@ var DEFAULT_MAX_ASSET_BYTES = 50 * 1024 * 1024;
|
|
|
6194
6196
|
var MAX_SINGLE_ASSET_BYTES = 20 * 1024 * 1024;
|
|
6195
6197
|
async function renderMarkdownHtml(markdown, options) {
|
|
6196
6198
|
await assertKnownThemeId(options.themeId);
|
|
6199
|
+
const warn = options.onWarning ?? ((message) => console.warn(message));
|
|
6197
6200
|
const markdownDoc = parseMarkdown(markdown);
|
|
6198
6201
|
const doc = markdownToDoc(markdownDoc);
|
|
6202
|
+
const themeId = options.themeId ?? (readFrontmatterThemeId(markdownDoc.frontmatter) ? void 0 : await usableFallbackThemeId(options.fallbackThemeId, warn));
|
|
6199
6203
|
const images = options.sourcePath && options.assetRoot ? await readReferencedImages(
|
|
6200
6204
|
doc,
|
|
6201
6205
|
options.sourcePath,
|
|
6202
6206
|
options.assetRoot,
|
|
6203
6207
|
options.maxAssetBytes ?? DEFAULT_MAX_ASSET_BYTES,
|
|
6204
6208
|
options.allowReferencedAsset,
|
|
6205
|
-
|
|
6209
|
+
warn
|
|
6206
6210
|
) : void 0;
|
|
6207
6211
|
return docToHtml(doc, {
|
|
6208
6212
|
playerScript: PLAYER_BUNDLE,
|
|
6209
6213
|
images,
|
|
6210
6214
|
title: options.title,
|
|
6211
6215
|
mode: options.mode ?? "static",
|
|
6212
|
-
themeId
|
|
6216
|
+
themeId,
|
|
6213
6217
|
showCodeCopyButton: true
|
|
6214
6218
|
});
|
|
6215
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
|
+
}
|
|
6216
6228
|
async function readReferencedImages(doc, sourcePath, assetRoot, maxAssetBytes, allowReferencedAsset, warn) {
|
|
6217
6229
|
const images = /* @__PURE__ */ new Map();
|
|
6218
6230
|
const root = await realpathIfPresent(path3.resolve(assetRoot));
|
|
@@ -6310,7 +6322,564 @@ function positiveLimit(value, fallback, label) {
|
|
|
6310
6322
|
|
|
6311
6323
|
// src/commands/build.ts
|
|
6312
6324
|
init_node_error();
|
|
6313
|
-
|
|
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";
|
|
6314
6883
|
var DEFAULT_MAX_BUILD_ENTRIES = 1e5;
|
|
6315
6884
|
var DEFAULT_MAX_BUILD_DEPTH = 64;
|
|
6316
6885
|
var DEFAULT_MAX_BUILD_INPUT_BYTES = 20 * 1024 * 1024;
|
|
@@ -6319,8 +6888,8 @@ var DEFAULT_MAX_BUILD_OUTPUT_BYTES = 128 * 1024 * 1024;
|
|
|
6319
6888
|
var DEFAULT_MAX_BUILD_TOTAL_OUTPUT_BYTES = 1024 * 1024 * 1024;
|
|
6320
6889
|
async function runBuild(opts) {
|
|
6321
6890
|
await assertKnownThemeId(opts.theme);
|
|
6322
|
-
const inputDir =
|
|
6323
|
-
const outputDir =
|
|
6891
|
+
const inputDir = path5.resolve(opts.input);
|
|
6892
|
+
const outputDir = path5.resolve(opts.output);
|
|
6324
6893
|
let inputStat;
|
|
6325
6894
|
try {
|
|
6326
6895
|
inputStat = await stat2(inputDir);
|
|
@@ -6329,6 +6898,8 @@ async function runBuild(opts) {
|
|
|
6329
6898
|
throw new Error(`Input directory not found: ${inputDir}`);
|
|
6330
6899
|
}
|
|
6331
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();
|
|
6332
6903
|
const markdownFiles = await listMarkdownFiles(inputDir, {
|
|
6333
6904
|
maxEntries: positiveLimit(opts.maxEntries, DEFAULT_MAX_BUILD_ENTRIES, "build entry"),
|
|
6334
6905
|
maxDepth: positiveLimit(opts.maxDepth, DEFAULT_MAX_BUILD_DEPTH, "build depth")
|
|
@@ -6343,8 +6914,8 @@ async function runBuild(opts) {
|
|
|
6343
6914
|
let totalInputBytes = 0;
|
|
6344
6915
|
let totalOutputBytes = 0;
|
|
6345
6916
|
for (const sourcePath of markdownFiles) {
|
|
6346
|
-
const relativePath =
|
|
6347
|
-
const outputPath =
|
|
6917
|
+
const relativePath = path5.relative(inputDir, sourcePath);
|
|
6918
|
+
const outputPath = path5.join(outputDir, replaceMarkdownExtension(relativePath));
|
|
6348
6919
|
const sourceBytes = await readFile(sourcePath);
|
|
6349
6920
|
if (sourceBytes.byteLength > byteLimits.maxInputBytes) {
|
|
6350
6921
|
throw new Error(
|
|
@@ -6357,13 +6928,15 @@ async function runBuild(opts) {
|
|
|
6357
6928
|
`Build inputs exceed the ${byteLimits.maxTotalInputBytes}-byte aggregate limit.`
|
|
6358
6929
|
);
|
|
6359
6930
|
}
|
|
6360
|
-
const source =
|
|
6931
|
+
const source = decodeUtf8Text2(sourceBytes, { label: "Build input", path: sourcePath });
|
|
6361
6932
|
const html = await renderMarkdownHtml(source, {
|
|
6362
6933
|
title: titleFromPath(relativePath),
|
|
6363
6934
|
sourcePath,
|
|
6364
6935
|
assetRoot: inputDir,
|
|
6365
6936
|
themeId: opts.theme,
|
|
6366
|
-
|
|
6937
|
+
fallbackThemeId,
|
|
6938
|
+
mode: "static",
|
|
6939
|
+
onWarning: warn
|
|
6367
6940
|
});
|
|
6368
6941
|
const outputBytes = Buffer.byteLength(html, "utf8");
|
|
6369
6942
|
if (outputBytes > byteLimits.maxOutputBytes) {
|
|
@@ -6377,18 +6950,22 @@ async function runBuild(opts) {
|
|
|
6377
6950
|
`Build outputs exceed the ${byteLimits.maxTotalOutputBytes}-byte aggregate limit.`
|
|
6378
6951
|
);
|
|
6379
6952
|
}
|
|
6380
|
-
await mkdir(
|
|
6953
|
+
await mkdir(path5.dirname(outputPath), { recursive: true });
|
|
6381
6954
|
await writeFile(outputPath, html, "utf-8");
|
|
6382
6955
|
builtFiles.push(outputPath);
|
|
6383
6956
|
}
|
|
6384
6957
|
return { inputDir, outputDir, builtFiles };
|
|
6385
6958
|
}
|
|
6386
|
-
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) => {
|
|
6387
6963
|
try {
|
|
6388
6964
|
const result = await runBuild({
|
|
6389
6965
|
input: opts.input,
|
|
6390
6966
|
output: opts.output,
|
|
6391
6967
|
theme: opts.theme,
|
|
6968
|
+
ignoreWorkspaceSettings: opts.ignoreWorkspaceSettings,
|
|
6392
6969
|
maxInputBytes: parseOptionalByteLimit("--max-input-bytes", opts.maxInputBytes),
|
|
6393
6970
|
maxTotalInputBytes: parseOptionalByteLimit(
|
|
6394
6971
|
"--max-total-input-bytes",
|
|
@@ -6472,7 +7049,7 @@ async function listMarkdownFiles(root, limits) {
|
|
|
6472
7049
|
if (entryCount > limits.maxEntries) {
|
|
6473
7050
|
throw new Error(`Build traversal exceeded ${limits.maxEntries} filesystem entries.`);
|
|
6474
7051
|
}
|
|
6475
|
-
const absolutePath =
|
|
7052
|
+
const absolutePath = path5.join(current.directory, entry.name);
|
|
6476
7053
|
if (entry.isDirectory()) {
|
|
6477
7054
|
if (current.depth >= limits.maxDepth) {
|
|
6478
7055
|
throw new Error(`Build traversal exceeded a depth of ${limits.maxDepth}.`);
|
|
@@ -6492,7 +7069,7 @@ function replaceMarkdownExtension(filePath) {
|
|
|
6492
7069
|
return filePath.replace(/\.(md|markdown)$/i, ".html");
|
|
6493
7070
|
}
|
|
6494
7071
|
function titleFromPath(filePath) {
|
|
6495
|
-
const baseName =
|
|
7072
|
+
const baseName = path5.basename(filePath).replace(/\.(md|markdown)$/i, "");
|
|
6496
7073
|
return baseName || "DocBlocks Document";
|
|
6497
7074
|
}
|
|
6498
7075
|
|
|
@@ -6501,11 +7078,11 @@ import { Command as Command2 } from "commander";
|
|
|
6501
7078
|
import { createServer } from "http";
|
|
6502
7079
|
import { realpath as realpath2, stat as stat3 } from "fs/promises";
|
|
6503
7080
|
import { isIP } from "net";
|
|
6504
|
-
import
|
|
7081
|
+
import path7 from "path";
|
|
6505
7082
|
init_contained_file();
|
|
6506
7083
|
|
|
6507
7084
|
// src/preview-policy.ts
|
|
6508
|
-
import
|
|
7085
|
+
import path6 from "path";
|
|
6509
7086
|
var PREVIEW_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6510
7087
|
".css",
|
|
6511
7088
|
".gif",
|
|
@@ -6551,14 +7128,14 @@ var SENSITIVE_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
|
6551
7128
|
"id_rsa"
|
|
6552
7129
|
]);
|
|
6553
7130
|
function isAllowedPreviewPath(rootPath, requestedPath, physicalPath, purpose) {
|
|
6554
|
-
const root =
|
|
6555
|
-
const requested =
|
|
6556
|
-
const physical =
|
|
7131
|
+
const root = path6.resolve(rootPath);
|
|
7132
|
+
const requested = path6.resolve(requestedPath);
|
|
7133
|
+
const physical = path6.resolve(physicalPath);
|
|
6557
7134
|
for (const candidate of /* @__PURE__ */ new Set([requested, physical])) {
|
|
6558
|
-
const relative =
|
|
7135
|
+
const relative = path6.relative(root, candidate);
|
|
6559
7136
|
if (!isContainedRelativePath(relative) || containsSensitiveSegment(relative)) return false;
|
|
6560
7137
|
if (purpose !== "directory") {
|
|
6561
|
-
const extension =
|
|
7138
|
+
const extension = path6.extname(path6.basename(candidate)).toLowerCase();
|
|
6562
7139
|
const allowedExtensions = purpose === "embedded-image" ? EMBEDDED_IMAGE_EXTENSIONS : PREVIEW_FILE_EXTENSIONS;
|
|
6563
7140
|
if (!allowedExtensions.has(extension)) return false;
|
|
6564
7141
|
}
|
|
@@ -6568,15 +7145,15 @@ function isAllowedPreviewPath(rootPath, requestedPath, physicalPath, purpose) {
|
|
|
6568
7145
|
function containsSensitiveSegment(relativePath) {
|
|
6569
7146
|
return relativePath.split(/[\\/]+/).filter(Boolean).some((segment) => {
|
|
6570
7147
|
const lower = segment.toLowerCase();
|
|
6571
|
-
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));
|
|
6572
7149
|
});
|
|
6573
7150
|
}
|
|
6574
7151
|
function isContainedRelativePath(relativePath) {
|
|
6575
|
-
return relativePath === "" || !relativePath.startsWith(`..${
|
|
7152
|
+
return relativePath === "" || !relativePath.startsWith(`..${path6.sep}`) && relativePath !== ".." && !path6.isAbsolute(relativePath);
|
|
6576
7153
|
}
|
|
6577
7154
|
|
|
6578
7155
|
// src/commands/serve.ts
|
|
6579
|
-
import { decodeUtf8Text as
|
|
7156
|
+
import { decodeUtf8Text as decodeUtf8Text3 } from "@bendyline/docblocks/filesystem";
|
|
6580
7157
|
init_node_error();
|
|
6581
7158
|
var DEFAULT_HOST = "127.0.0.1";
|
|
6582
7159
|
var DEFAULT_MAX_FILE_BYTES = 50 * 1024 * 1024;
|
|
@@ -6584,7 +7161,7 @@ var DEFAULT_MAX_CONCURRENT_READS = 16;
|
|
|
6584
7161
|
var MAX_ALLOWED_HOSTS = 32;
|
|
6585
7162
|
async function startPreviewServer(opts) {
|
|
6586
7163
|
await assertKnownThemeId(opts.theme);
|
|
6587
|
-
const requestedRoot =
|
|
7164
|
+
const requestedRoot = path7.resolve(opts.dir);
|
|
6588
7165
|
let root;
|
|
6589
7166
|
try {
|
|
6590
7167
|
root = await realpath2(requestedRoot);
|
|
@@ -6595,6 +7172,8 @@ async function startPreviewServer(opts) {
|
|
|
6595
7172
|
const rootStat = await stat3(root);
|
|
6596
7173
|
if (!rootStat.isDirectory()) throw new Error(`Preview root is not a directory: ${root}`);
|
|
6597
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);
|
|
6598
7177
|
const allowedHosts = validateAllowedHosts(opts.allowedHosts ?? []);
|
|
6599
7178
|
const maxFileBytes = positiveLimit(
|
|
6600
7179
|
opts.maxFileBytes,
|
|
@@ -6624,7 +7203,7 @@ async function startPreviewServer(opts) {
|
|
|
6624
7203
|
return;
|
|
6625
7204
|
}
|
|
6626
7205
|
activeRequests += 1;
|
|
6627
|
-
handlePreviewRequest(req, res, root, opts.theme, maxFileBytes).catch((error) => {
|
|
7206
|
+
handlePreviewRequest(req, res, root, { themeId: opts.theme, themeFallback, warn }, maxFileBytes).catch((error) => {
|
|
6628
7207
|
reportPreviewFailure(req, error);
|
|
6629
7208
|
if (!res.headersSent) sendText(res, 500, "Internal server error", req.method === "HEAD");
|
|
6630
7209
|
else res.end();
|
|
@@ -6643,13 +7222,17 @@ async function startPreviewServer(opts) {
|
|
|
6643
7222
|
const port = typeof address === "object" && address ? address.port : opts.port;
|
|
6644
7223
|
return { server, root, host, url: `http://${urlHost(host)}:${port}/` };
|
|
6645
7224
|
}
|
|
6646
|
-
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(
|
|
6647
7229
|
async (opts) => {
|
|
6648
7230
|
try {
|
|
6649
7231
|
const server = await startPreviewServer({
|
|
6650
7232
|
port: parsePort(opts.port),
|
|
6651
7233
|
dir: opts.dir,
|
|
6652
7234
|
theme: opts.theme,
|
|
7235
|
+
ignoreWorkspaceSettings: opts.ignoreWorkspaceSettings,
|
|
6653
7236
|
host: opts.host,
|
|
6654
7237
|
allowNetwork: opts.allowNetwork,
|
|
6655
7238
|
allowedHosts: opts.allowHost
|
|
@@ -6662,7 +7245,7 @@ var serveCommand = new Command2("serve").description("Start a local dev server f
|
|
|
6662
7245
|
}
|
|
6663
7246
|
}
|
|
6664
7247
|
);
|
|
6665
|
-
async function handlePreviewRequest(req, res, root,
|
|
7248
|
+
async function handlePreviewRequest(req, res, root, render, maxFileBytes) {
|
|
6666
7249
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
6667
7250
|
sendText(res, 405, "Method not allowed");
|
|
6668
7251
|
return;
|
|
@@ -6686,16 +7269,18 @@ async function handlePreviewRequest(req, res, root, themeId, maxFileBytes) {
|
|
|
6686
7269
|
return;
|
|
6687
7270
|
}
|
|
6688
7271
|
if (target.markdown) {
|
|
6689
|
-
const markdown =
|
|
7272
|
+
const markdown = decodeUtf8Text3(await readContainedFile(root, target.filePath, maxFileBytes), {
|
|
6690
7273
|
label: "Preview document",
|
|
6691
7274
|
path: target.filePath
|
|
6692
7275
|
});
|
|
6693
7276
|
const html = await renderMarkdownHtml(markdown, {
|
|
6694
|
-
title:
|
|
7277
|
+
title: path7.basename(target.filePath).replace(/\.(md|markdown)$/i, ""),
|
|
6695
7278
|
sourcePath: target.filePath,
|
|
6696
7279
|
assetRoot: root,
|
|
6697
|
-
themeId,
|
|
7280
|
+
themeId: render.themeId,
|
|
7281
|
+
fallbackThemeId: render.themeFallback ? await render.themeFallback.resolve() : void 0,
|
|
6698
7282
|
mode: "static",
|
|
7283
|
+
onWarning: render.warn,
|
|
6699
7284
|
allowReferencedAsset: ({ assetRoot, requestedPath, physicalPath }) => isAllowedPreviewPath(assetRoot, requestedPath, physicalPath, "embedded-image")
|
|
6700
7285
|
});
|
|
6701
7286
|
sendBody(res, 200, html, "text/html; charset=utf-8", headOnly);
|
|
@@ -6708,7 +7293,7 @@ async function resolveServeTarget(root, requestUrl, maxFileBytes = DEFAULT_MAX_F
|
|
|
6708
7293
|
if (requestUrl.length > 8192 || requestUrl.includes("\0")) return { kind: "bad-request" };
|
|
6709
7294
|
let physicalRoot;
|
|
6710
7295
|
try {
|
|
6711
|
-
physicalRoot = await realpath2(
|
|
7296
|
+
physicalRoot = await realpath2(path7.resolve(root));
|
|
6712
7297
|
} catch (error) {
|
|
6713
7298
|
if (isMissingPathError2(error)) return { kind: "missing" };
|
|
6714
7299
|
if (isPermissionError(error)) return { kind: "forbidden" };
|
|
@@ -6717,7 +7302,7 @@ async function resolveServeTarget(root, requestUrl, maxFileBytes = DEFAULT_MAX_F
|
|
|
6717
7302
|
const decodedPath = decodeRequestPath(requestUrl);
|
|
6718
7303
|
if (!decodedPath) return { kind: "bad-request" };
|
|
6719
7304
|
const requestedPath = decodedPath.replace(/^\/+/, "");
|
|
6720
|
-
const candidate =
|
|
7305
|
+
const candidate = path7.resolve(physicalRoot, requestedPath);
|
|
6721
7306
|
if (!isAllowedPreviewPath(physicalRoot, candidate, candidate, "directory")) {
|
|
6722
7307
|
return { kind: "forbidden" };
|
|
6723
7308
|
}
|
|
@@ -6777,7 +7362,7 @@ async function inspectTarget(root, candidate, maxFileBytes) {
|
|
|
6777
7362
|
}
|
|
6778
7363
|
async function directoryIndexTarget(root, dirPath, maxFileBytes) {
|
|
6779
7364
|
for (const fileName of ["index.html", "index.htm", "index.md", "index.markdown"]) {
|
|
6780
|
-
const target = await inspectTarget(root,
|
|
7365
|
+
const target = await inspectTarget(root, path7.join(dirPath, fileName), maxFileBytes);
|
|
6781
7366
|
if (target.kind === "file" || target.kind === "forbidden" || target.kind === "too-large") {
|
|
6782
7367
|
return target;
|
|
6783
7368
|
}
|
|
@@ -6912,7 +7497,7 @@ function urlHost(host) {
|
|
|
6912
7497
|
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
6913
7498
|
}
|
|
6914
7499
|
function contentTypeFor(filePath) {
|
|
6915
|
-
const ext =
|
|
7500
|
+
const ext = path7.extname(filePath).toLowerCase();
|
|
6916
7501
|
switch (ext) {
|
|
6917
7502
|
case ".html":
|
|
6918
7503
|
case ".htm":
|
|
@@ -6979,8 +7564,8 @@ import {
|
|
|
6979
7564
|
rm,
|
|
6980
7565
|
stat as stat4
|
|
6981
7566
|
} from "fs/promises";
|
|
6982
|
-
import { randomUUID } from "crypto";
|
|
6983
|
-
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";
|
|
6984
7569
|
import { Command as Command3 } from "commander";
|
|
6985
7570
|
init_link_support();
|
|
6986
7571
|
init_node_error();
|
|
@@ -7057,7 +7642,7 @@ async function runConvert(inputPath, opts) {
|
|
|
7057
7642
|
throwIfAborted2(opts.signal);
|
|
7058
7643
|
const resolvedInput = resolve(inputPath);
|
|
7059
7644
|
const outputDir = opts.outputDir ? resolve(opts.outputDir) : dirname(resolvedInput);
|
|
7060
|
-
const inputBasename =
|
|
7645
|
+
const inputBasename = basename2(resolvedInput);
|
|
7061
7646
|
const inputExt = extname(inputBasename);
|
|
7062
7647
|
const baseName = inputExt ? inputBasename.slice(0, -inputExt.length) : inputBasename;
|
|
7063
7648
|
const maxInputBytes = positiveLimit(
|
|
@@ -7199,7 +7784,7 @@ async function runConvert(inputPath, opts) {
|
|
|
7199
7784
|
if (totalOutputBytes > maxTotalOutputBytes) {
|
|
7200
7785
|
throw new Error(`Conversion outputs exceed the ${maxTotalOutputBytes}-byte aggregate limit.`);
|
|
7201
7786
|
}
|
|
7202
|
-
const suggestedFilename =
|
|
7787
|
+
const suggestedFilename = basename2(conversion.suggestedFilename);
|
|
7203
7788
|
const outPath = join(outputDir, suggestedFilename);
|
|
7204
7789
|
const outputPathKey = process.platform === "win32" ? outPath.toLowerCase() : outPath;
|
|
7205
7790
|
if (outputPathKeys.has(outputPathKey)) {
|
|
@@ -7315,7 +7900,7 @@ async function publishConversionBatch(outputs, outputDir, allowOverwrite, signal
|
|
|
7315
7900
|
throwIfAborted2(signal);
|
|
7316
7901
|
const output = outputs[index];
|
|
7317
7902
|
if (!output) continue;
|
|
7318
|
-
const stagedPath = join(stagingDirectory, `${index}-${
|
|
7903
|
+
const stagedPath = join(stagingDirectory, `${index}-${randomUUID2()}.stage`);
|
|
7319
7904
|
await writePrivateFile(stagedPath, output.bytes);
|
|
7320
7905
|
stagedOutputs.push({ outputPath: output.path, stagedPath, bytes: output.bytes });
|
|
7321
7906
|
}
|
|
@@ -7345,7 +7930,7 @@ async function publishConversionBatch(outputs, outputDir, allowOverwrite, signal
|
|
|
7345
7930
|
throw new Error(`Refusing to replace a non-file destination: ${output.outputPath}`);
|
|
7346
7931
|
}
|
|
7347
7932
|
if (existing) {
|
|
7348
|
-
state.backupPath = join(stagingDirectory, `${index}-${
|
|
7933
|
+
state.backupPath = join(stagingDirectory, `${index}-${randomUUID2()}.backup`);
|
|
7349
7934
|
await rename(output.outputPath, state.backupPath);
|
|
7350
7935
|
}
|
|
7351
7936
|
await rename(output.stagedPath, output.outputPath);
|
|
@@ -7373,8 +7958,8 @@ async function publishConversionBatch(outputs, outputDir, allowOverwrite, signal
|
|
|
7373
7958
|
}
|
|
7374
7959
|
}
|
|
7375
7960
|
}
|
|
7376
|
-
async function writePrivateFile(
|
|
7377
|
-
const handle = await open2(
|
|
7961
|
+
async function writePrivateFile(path10, bytes) {
|
|
7962
|
+
const handle = await open2(path10, "wx", 384);
|
|
7378
7963
|
try {
|
|
7379
7964
|
await handle.writeFile(bytes);
|
|
7380
7965
|
await handle.sync();
|
|
@@ -7433,9 +8018,9 @@ async function assertReplaceableOutputs(outputPaths) {
|
|
|
7433
8018
|
}
|
|
7434
8019
|
}
|
|
7435
8020
|
}
|
|
7436
|
-
async function lstatIfPresent(
|
|
8021
|
+
async function lstatIfPresent(path10) {
|
|
7437
8022
|
try {
|
|
7438
|
-
return await lstat(
|
|
8023
|
+
return await lstat(path10);
|
|
7439
8024
|
} catch (error) {
|
|
7440
8025
|
if (isNodeErrorCode(error, ["ENOENT", "ENOTDIR"])) return null;
|
|
7441
8026
|
throw error;
|
|
@@ -7454,11 +8039,11 @@ function throwIfAborted2(signal) {
|
|
|
7454
8039
|
}
|
|
7455
8040
|
async function applyTransformToMarkdown(markdownDoc, container, transformStyle, themeId) {
|
|
7456
8041
|
const { markdownToDoc: markdownToDoc3, docToMarkdown } = await import("@bendyline/squisq/doc");
|
|
7457
|
-
const { readFrontmatterThemeId } = await import("@bendyline/squisq/markdown");
|
|
8042
|
+
const { readFrontmatterThemeId: readFrontmatterThemeId2 } = await import("@bendyline/squisq/markdown");
|
|
7458
8043
|
const { applyTransform, extractDocImages } = await import("@bendyline/squisq/transform");
|
|
7459
8044
|
const doc = markdownToDoc3(markdownDoc);
|
|
7460
8045
|
const images = extractDocImages(doc.blocks);
|
|
7461
|
-
const effectiveTheme = themeId ??
|
|
8046
|
+
const effectiveTheme = themeId ?? readFrontmatterThemeId2(markdownDoc.frontmatter);
|
|
7462
8047
|
const result = applyTransform(doc, transformStyle, { themeId: effectiveTheme, images });
|
|
7463
8048
|
const projected = projectTransformOntoMarkdownSource(doc, result.doc);
|
|
7464
8049
|
const transformedMarkdown = docToMarkdown(projected);
|
|
@@ -7649,7 +8234,7 @@ function isConversionErrorMetadata(error) {
|
|
|
7649
8234
|
// src/commands/video.ts
|
|
7650
8235
|
init_cancellation();
|
|
7651
8236
|
import { lstat as lstat2, mkdir as mkdir3 } from "fs/promises";
|
|
7652
|
-
import { dirname as dirname2, basename as
|
|
8237
|
+
import { dirname as dirname2, basename as basename3, extname as extname2, resolve as resolve2 } from "path";
|
|
7653
8238
|
import { Command as Command4 } from "commander";
|
|
7654
8239
|
import {
|
|
7655
8240
|
resolveDimensions,
|
|
@@ -7691,7 +8276,7 @@ async function runVideo(inputPath, opts, onProgress, dependencies = {}) {
|
|
|
7691
8276
|
width: opts.width,
|
|
7692
8277
|
height: opts.height
|
|
7693
8278
|
});
|
|
7694
|
-
const inputBasename =
|
|
8279
|
+
const inputBasename = basename3(resolvedInput);
|
|
7695
8280
|
const inputExt = extname2(inputBasename);
|
|
7696
8281
|
const baseName = inputExt ? inputBasename.slice(0, -inputExt.length) : inputBasename;
|
|
7697
8282
|
const outputPath = opts.output ? resolve2(opts.output) : resolve2(dirname2(resolvedInput), `${baseName}.mp4`);
|
|
@@ -7982,7 +8567,7 @@ var transformsCommand = new Command7("transforms").description("List all availab
|
|
|
7982
8567
|
import { open as open5 } from "fs/promises";
|
|
7983
8568
|
import { resolve as resolve3 } from "path";
|
|
7984
8569
|
import { Command as Command8 } from "commander";
|
|
7985
|
-
import { decodeUtf8Text as
|
|
8570
|
+
import { decodeUtf8Text as decodeUtf8Text5 } from "@bendyline/docblocks/filesystem";
|
|
7986
8571
|
init_node_error();
|
|
7987
8572
|
var DEFAULT_MAX_PARSE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
7988
8573
|
var DEFAULT_MAX_PARSE_OUTPUT_BYTES = 128 * 1024 * 1024;
|
|
@@ -7999,7 +8584,7 @@ async function runParse(inputPath, options = {}) {
|
|
|
7999
8584
|
"parse output byte"
|
|
8000
8585
|
);
|
|
8001
8586
|
const bytes = await readBoundedFile(resolvedInput, maxInputBytes);
|
|
8002
|
-
const content =
|
|
8587
|
+
const content = decodeUtf8Text5(bytes, { label: "Parse input", path: resolvedInput });
|
|
8003
8588
|
const { parseMarkdown: parseMarkdown2 } = await import("@bendyline/squisq/markdown");
|
|
8004
8589
|
const markdownDoc = parseMarkdown2(content);
|
|
8005
8590
|
const stats = {
|
|
@@ -8062,9 +8647,95 @@ var parseCommand = new Command8("parse").description("Parse a markdown file and
|
|
|
8062
8647
|
}
|
|
8063
8648
|
});
|
|
8064
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
|
+
|
|
8065
8736
|
// src/program.ts
|
|
8066
8737
|
init_version();
|
|
8067
|
-
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);
|
|
8068
8739
|
|
|
8069
8740
|
// src/bin.ts
|
|
8070
8741
|
program.parseAsync().catch((error) => {
|