gutterpress 0.10.7 → 0.10.8
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 +10 -3
- package/dist/README-aaqapam1.md +186 -0
- package/dist/README-mdq34cmb.md +155 -0
- package/dist/api/index.d.ts +6 -2
- package/dist/api/index.js +30 -2
- package/dist/{audit-19e2431j.js → audit-m3ce9pvq.js} +4 -4
- package/dist/base-ypqh9n7f.css +123 -0
- package/dist/book-ted3tzrp.css +58 -0
- package/dist/{build-gqqew2vr.js → build-qnecb3a1.js} +4 -4
- package/dist/callout-hv56m16h.md +7 -0
- package/dist/{cli-kzf64c2g.js → cli-1hx14cgk.js} +946 -401
- package/dist/{cli-s8q42x3r.js → cli-4vtgt2ng.js} +78 -2
- package/dist/{cli-h8ejbtyr.js → cli-vcv5ewqj.js} +1 -1
- package/dist/{cli-pvk2s9d7.js → cli-vxf151ea.js} +1 -1
- package/dist/cli.js +16 -16
- package/dist/components-0mmswvvm.yaml +51 -0
- package/dist/components-aam0fe2z.css +94 -0
- package/dist/{doctor-3ccz35yz.js → doctor-qvqye1wd.js} +2 -2
- package/dist/{engine-pb6h089e.js → engine-daad33qn.js} +1 -1
- package/dist/{engine-88mynx9a.js → engine-db8632w6.js} +2 -2
- package/dist/expected-ycmacem8.html +9 -0
- package/dist/fixture-2qnyqgb5.md +10 -0
- package/dist/gutterpress-m98e2w2d.json +8 -0
- package/dist/gutterpress-x2ycdsb8.json +17 -0
- package/dist/{index-837htyjy.js → index-9pbaznww.js} +78 -2
- package/dist/{index-r4ny4fyr.js → index-dfw8hn8q.js} +1147 -523
- package/dist/{index-a0xpm188.js → index-rpt4vtby.js} +1 -1
- package/dist/index.js +31 -3
- package/dist/lib/build-runner.d.ts +52 -0
- package/dist/lib/extension-manifest.d.ts +164 -0
- package/dist/lib/extension-scaffold.d.ts +65 -0
- package/dist/lib/lint-runner.d.ts +12 -0
- package/dist/lib/markdown/assemble.d.ts +12 -0
- package/dist/lib/markdown/markers.d.ts +20 -0
- package/dist/lib/markdown/plugins.d.ts +1 -1
- package/dist/lib/markdown/renderer.d.ts +99 -0
- package/dist/lib/snippets.d.ts +121 -5
- package/dist/lib/theme-import.d.ts +8 -4
- package/dist/lib/theme-manager.d.ts +52 -39
- package/dist/lib/validation-exec.d.ts +12 -0
- package/dist/{lint-mpz7vfc8.js → lint-3p1s8agk.js} +4 -4
- package/dist/{manifest.schema-kwnrwv8b.json → manifest.schema-1q35yxs9.json} +2 -2
- package/dist/{new-nbrpsw7t.js → new-xffarp40.js} +111 -6
- package/dist/package-rm9d2tht.json +29 -0
- package/dist/page-rules-nsj3aw42.css +128 -0
- package/dist/page-templates-7md4ev7d.css +60 -0
- package/dist/{plugin-gg5mk26h.js → plugin-b05ck7zd.js} +4 -4
- package/dist/plugin-m2w7g47v.css +114 -0
- package/dist/plugin.js-aszz0zgh.tpl +172 -0
- package/dist/plugin.test.js-mv0ghv5n.tpl +232 -0
- package/dist/{preflight-jnmd4mg2.js → preflight-z6xpty9w.js} +4 -4
- package/dist/{preview-hsr9d12d.js → preview-xa1n7why.js} +4 -4
- package/dist/{publish-pcrrn6fd.js → publish-zgmr99kd.js} +4 -4
- package/dist/render.js +275 -8
- package/dist/term-box-4y0w7y32.md +7 -0
- package/dist/{theme-6fa0yjzq.js → theme-m9dtxkhj.js} +4 -4
- package/dist/tokens-e4xk3x4v.css +86 -0
- package/dist/{validate-7n25fx0g.js → validate-43hqgzz5.js} +4 -4
- package/package.json +1 -1
|
@@ -13,21 +13,22 @@ import {
|
|
|
13
13
|
stampCreator,
|
|
14
14
|
stripAnnotations,
|
|
15
15
|
warn
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-vxf151ea.js";
|
|
17
17
|
import {
|
|
18
18
|
DEBOUNCE,
|
|
19
19
|
UsageError,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-vcv5ewqj.js";
|
|
22
22
|
import {
|
|
23
23
|
MARGIN_BOX_IGNORED_PROPERTIES,
|
|
24
24
|
RENDER_TIMEOUT_MS,
|
|
25
25
|
REQUIRED_MILESTONE,
|
|
26
26
|
closeBrowser,
|
|
27
27
|
getAssetPath,
|
|
28
|
+
getAssetsDir,
|
|
28
29
|
getBrowser,
|
|
29
30
|
prewarmBrowser
|
|
30
|
-
} from "./cli-
|
|
31
|
+
} from "./cli-4vtgt2ng.js";
|
|
31
32
|
import {
|
|
32
33
|
INSTALL_HINTS,
|
|
33
34
|
execCapture,
|
|
@@ -1078,7 +1079,7 @@ async function verifyVendoredPlugin(projectDir, expectedName, expectedVersion) {
|
|
|
1078
1079
|
|
|
1079
1080
|
// src/lib/lint-runner.ts
|
|
1080
1081
|
import { resolve as resolve4 } from "node:path";
|
|
1081
|
-
import { readFile as
|
|
1082
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1082
1083
|
|
|
1083
1084
|
// src/lib/printsafe.ts
|
|
1084
1085
|
import postcss from "postcss";
|
|
@@ -1379,8 +1380,8 @@ async function resolveActiveStyles(projectDir, manifestStyles) {
|
|
|
1379
1380
|
|
|
1380
1381
|
// src/lib/markdown/plugins.ts
|
|
1381
1382
|
import { createHash as createHash2 } from "node:crypto";
|
|
1382
|
-
import { existsSync as
|
|
1383
|
-
import { cp, link, mkdir, mkdtemp, readFile as
|
|
1383
|
+
import { existsSync as existsSync5, lstatSync, realpathSync, rmSync, statSync as statSync2, unlinkSync } from "node:fs";
|
|
1384
|
+
import { cp, link, mkdir, mkdtemp, readFile as readFile4, rm, unlink, writeFile } from "node:fs/promises";
|
|
1384
1385
|
import { tmpdir } from "node:os";
|
|
1385
1386
|
import { resolve as resolve3, join, dirname as dirname2, basename, extname, relative, isAbsolute } from "node:path";
|
|
1386
1387
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -1404,6 +1405,70 @@ function resolveDeclaredStyles(rawStyles, baseDir, subject) {
|
|
|
1404
1405
|
});
|
|
1405
1406
|
}
|
|
1406
1407
|
|
|
1408
|
+
// src/lib/extension-manifest.ts
|
|
1409
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1410
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1411
|
+
import path3 from "node:path";
|
|
1412
|
+
var EXTENSION_MANIFEST_FILENAME = "gutterpress.json";
|
|
1413
|
+
var LEGACY_THEME_MANIFEST_FILENAME = "theme.json";
|
|
1414
|
+
async function readJsonMetaFile(jsonPath) {
|
|
1415
|
+
try {
|
|
1416
|
+
const parsed = JSON.parse(await readFile3(jsonPath, "utf8"));
|
|
1417
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1418
|
+
} catch {
|
|
1419
|
+
return {};
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
async function readExtensionMeta(dir) {
|
|
1423
|
+
const extensionPath = path3.join(dir, EXTENSION_MANIFEST_FILENAME);
|
|
1424
|
+
if (existsSync4(extensionPath)) {
|
|
1425
|
+
return readJsonMetaFile(extensionPath);
|
|
1426
|
+
}
|
|
1427
|
+
return readJsonMetaFile(path3.join(dir, LEGACY_THEME_MANIFEST_FILENAME));
|
|
1428
|
+
}
|
|
1429
|
+
function declaredList(value) {
|
|
1430
|
+
return Array.isArray(value) ? value.filter((s) => typeof s === "string" && s.trim().length > 0) : [];
|
|
1431
|
+
}
|
|
1432
|
+
function extensionStyleList(meta) {
|
|
1433
|
+
return declaredList(meta.styles);
|
|
1434
|
+
}
|
|
1435
|
+
function extensionEngineStyleList(meta) {
|
|
1436
|
+
return declaredList(meta.engineStyles?.native);
|
|
1437
|
+
}
|
|
1438
|
+
function pathEscapesFolder(rel) {
|
|
1439
|
+
return path3.isAbsolute(rel) || rel.split(/[\\/]/).includes("..");
|
|
1440
|
+
}
|
|
1441
|
+
function assertExtensionContained(meta) {
|
|
1442
|
+
const declared = [
|
|
1443
|
+
...extensionStyleList(meta),
|
|
1444
|
+
...extensionEngineStyleList(meta),
|
|
1445
|
+
...meta.markdown ? [meta.markdown] : [],
|
|
1446
|
+
...meta.components ? [meta.components] : [],
|
|
1447
|
+
...meta.snippets ? [meta.snippets] : [],
|
|
1448
|
+
...meta.tokensFile ? [meta.tokensFile] : []
|
|
1449
|
+
];
|
|
1450
|
+
for (const rel of declared) {
|
|
1451
|
+
if (pathEscapesFolder(rel)) {
|
|
1452
|
+
throw new Error(`Extension metadata declares "${rel}" outside its own folder; an extension must be self-contained.`);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
function resolveExtension(dir, meta, subject) {
|
|
1457
|
+
const styles = resolveDeclaredStyles(extensionStyleList(meta), dir, subject);
|
|
1458
|
+
const engineStyles = resolveDeclaredStyles(extensionEngineStyleList(meta), dir, subject);
|
|
1459
|
+
const [markdown] = resolveDeclaredStyles(meta.markdown ? [meta.markdown] : undefined, dir, subject) ?? [];
|
|
1460
|
+
const [components] = resolveDeclaredStyles(meta.components ? [meta.components] : undefined, dir, subject) ?? [];
|
|
1461
|
+
const [snippets] = resolveDeclaredStyles(meta.snippets ? [meta.snippets] : undefined, dir, subject) ?? [];
|
|
1462
|
+
return {
|
|
1463
|
+
...markdown ? { markdown } : {},
|
|
1464
|
+
...styles ? { styles } : {},
|
|
1465
|
+
...engineStyles ? { engineStyles } : {},
|
|
1466
|
+
...meta.tokensFile?.trim() ? { tokensFile: meta.tokensFile.trim() } : {},
|
|
1467
|
+
...components ? { components } : {},
|
|
1468
|
+
...snippets ? { snippets } : {}
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1407
1472
|
// src/lib/markdown/renderer.ts
|
|
1408
1473
|
import MarkdownIt from "markdown-it";
|
|
1409
1474
|
import markdownItAttrs from "markdown-it-attrs";
|
|
@@ -1461,7 +1526,7 @@ function escapeHtml(s) {
|
|
|
1461
1526
|
function escapeAttr(s) {
|
|
1462
1527
|
return String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
1463
1528
|
}
|
|
1464
|
-
function parseMarkerLine(line) {
|
|
1529
|
+
function parseMarkerLine(line, declaredWords) {
|
|
1465
1530
|
const trimmed = line.trim();
|
|
1466
1531
|
if (!trimmed.startsWith("@"))
|
|
1467
1532
|
return null;
|
|
@@ -1495,9 +1560,9 @@ function parseMarkerLine(line) {
|
|
|
1495
1560
|
tokens[i] = unwrapAttrBraces(tokens[i]);
|
|
1496
1561
|
const head = tokens[0];
|
|
1497
1562
|
const kind = head.slice(1);
|
|
1498
|
-
if (!KNOWN_KINDS.includes(kind))
|
|
1563
|
+
if (!KNOWN_KINDS.includes(kind) && !(declaredWords && declaredWords.has(kind)))
|
|
1499
1564
|
return null;
|
|
1500
|
-
if (kind === "page-break" || kind === "column-break" || kind === "
|
|
1565
|
+
if (kind === "page-break" || kind === "column-break" || kind === "continue" || kind.startsWith("end-")) {
|
|
1501
1566
|
return { kind, name: null, attrs: {} };
|
|
1502
1567
|
}
|
|
1503
1568
|
const body = tokens.slice(1);
|
|
@@ -1600,12 +1665,14 @@ function attachDataAttrs(token, kind, name, attrs) {
|
|
|
1600
1665
|
if (name) {
|
|
1601
1666
|
if (kind === "chapter")
|
|
1602
1667
|
token.attrSet("data-chapter-label", name);
|
|
1603
|
-
if (kind === "spread")
|
|
1668
|
+
else if (kind === "spread")
|
|
1604
1669
|
token.attrSet("data-spread", name);
|
|
1605
|
-
if (kind === "page")
|
|
1670
|
+
else if (kind === "page")
|
|
1606
1671
|
token.attrSet("data-page", name);
|
|
1607
|
-
if (kind === "section")
|
|
1672
|
+
else if (kind === "section")
|
|
1608
1673
|
token.attrSet("data-section", name);
|
|
1674
|
+
else
|
|
1675
|
+
token.attrSet(`data-${kind}`, name);
|
|
1609
1676
|
}
|
|
1610
1677
|
if (attrs.template)
|
|
1611
1678
|
token.attrSet("data-template", attrs.template);
|
|
@@ -1621,16 +1688,193 @@ function attachDataAttrs(token, kind, name, attrs) {
|
|
|
1621
1688
|
token.attrSet(`data-${k}`, v);
|
|
1622
1689
|
}
|
|
1623
1690
|
}
|
|
1691
|
+
var DECLARED_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
1692
|
+
function validateDeclaredMarkerName(name, pluginName) {
|
|
1693
|
+
if (!DECLARED_NAME_RE.test(name)) {
|
|
1694
|
+
throw new Error(`Plugin "${pluginName}" declares marker "@${name}" with an invalid name — marker names must be ` + `lower-case letters, digits and hyphens, starting with a letter (like core's own "page-break").`);
|
|
1695
|
+
}
|
|
1696
|
+
if (name.startsWith("end-")) {
|
|
1697
|
+
throw new Error(`Plugin "${pluginName}" declares marker "@${name}", but names starting with "end-" are reserved ` + `for the auto-derived closer of another declared marker (a container named "${name.slice(4)}" ` + `would collide with it). Rename this marker.`);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
var TAG_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
1701
|
+
function resolveContainerShape(name, pluginName, decl) {
|
|
1702
|
+
const tag = decl.tag !== undefined ? decl.tag : "div";
|
|
1703
|
+
if (typeof tag !== "string" || !TAG_NAME_RE.test(tag)) {
|
|
1704
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an invalid \`tag\` (${JSON.stringify(decl.tag)}) — ` + `must be a lower-case HTML tag name.`);
|
|
1705
|
+
}
|
|
1706
|
+
const classBase = decl.class !== undefined ? decl.class : "";
|
|
1707
|
+
if (typeof classBase !== "string") {
|
|
1708
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`class\` that is not a string.`);
|
|
1709
|
+
}
|
|
1710
|
+
let variants;
|
|
1711
|
+
if (decl.variants !== undefined) {
|
|
1712
|
+
if (typeof decl.variants !== "object" || decl.variants === null || Array.isArray(decl.variants)) {
|
|
1713
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has \`variants\` that is not a plain object.`);
|
|
1714
|
+
}
|
|
1715
|
+
variants = {};
|
|
1716
|
+
for (const [variantName, variantClass] of Object.entries(decl.variants)) {
|
|
1717
|
+
if (typeof variantClass !== "string") {
|
|
1718
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" variant "${variantName}" is not a string.`);
|
|
1719
|
+
}
|
|
1720
|
+
variants[variantName] = variantClass;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
let label;
|
|
1724
|
+
if (decl.label !== undefined) {
|
|
1725
|
+
if (typeof decl.label !== "object" || decl.label === null || Array.isArray(decl.label)) {
|
|
1726
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`label\` that is not an object.`);
|
|
1727
|
+
}
|
|
1728
|
+
if (typeof decl.label.class !== "string" || !decl.label.class) {
|
|
1729
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`label.class\` that is not a non-empty string.`);
|
|
1730
|
+
}
|
|
1731
|
+
const match = typeof decl.label.from === "string" ? /^attr:([A-Za-z_][A-Za-z0-9_-]*)$/.exec(decl.label.from) : null;
|
|
1732
|
+
if (!match) {
|
|
1733
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`label.from\` (${JSON.stringify(decl.label.from)}) that is not "attr:<name>" — that is the only supported form today.`);
|
|
1734
|
+
}
|
|
1735
|
+
const labelTag = decl.label.tag !== undefined ? decl.label.tag : "div";
|
|
1736
|
+
if (typeof labelTag !== "string" || !TAG_NAME_RE.test(labelTag)) {
|
|
1737
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an invalid \`label.tag\` (${JSON.stringify(decl.label.tag)}).`);
|
|
1738
|
+
}
|
|
1739
|
+
label = { class: decl.label.class, attr: match[1], tag: labelTag };
|
|
1740
|
+
}
|
|
1741
|
+
let autoCloseAtEof = false;
|
|
1742
|
+
if (decl.autoCloseAt !== undefined) {
|
|
1743
|
+
if (!Array.isArray(decl.autoCloseAt)) {
|
|
1744
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an \`autoCloseAt\` that is not an array.`);
|
|
1745
|
+
}
|
|
1746
|
+
for (const value of decl.autoCloseAt) {
|
|
1747
|
+
if (value !== "eof") {
|
|
1748
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" declares an unsupported \`autoCloseAt\` value ` + `${JSON.stringify(value)} — only "eof" is currently supported.`);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
autoCloseAtEof = decl.autoCloseAt.includes("eof");
|
|
1752
|
+
}
|
|
1753
|
+
return { tag, classBase, variants, label, autoCloseAtEof };
|
|
1754
|
+
}
|
|
1755
|
+
function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
|
|
1756
|
+
const pluginName = originOf.get(name);
|
|
1757
|
+
if (typeof rawDecl !== "object" || rawDecl === null || Array.isArray(rawDecl)) {
|
|
1758
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" is not a plain object.`);
|
|
1759
|
+
}
|
|
1760
|
+
if (rawDecl.deprecated !== undefined) {
|
|
1761
|
+
if (typeof rawDecl.deprecated !== "string" || !rawDecl.deprecated) {
|
|
1762
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`deprecated\` value that is not a non-empty string.`);
|
|
1763
|
+
}
|
|
1764
|
+
return { name, deprecated: rawDecl.deprecated };
|
|
1765
|
+
}
|
|
1766
|
+
if (rawDecl.alias !== undefined) {
|
|
1767
|
+
if (typeof rawDecl.alias !== "string" || !rawDecl.alias) {
|
|
1768
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an \`alias\` value that is not a non-empty string.`);
|
|
1769
|
+
}
|
|
1770
|
+
const targetRaw = rawRegistry.get(rawDecl.alias);
|
|
1771
|
+
if (!targetRaw) {
|
|
1772
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases unknown marker "@${rawDecl.alias}" — no ` + `loaded plugin declares it.`);
|
|
1773
|
+
}
|
|
1774
|
+
if (targetRaw.deprecated !== undefined) {
|
|
1775
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases "@${rawDecl.alias}", which is deprecated: ` + `${targetRaw.deprecated}`);
|
|
1776
|
+
}
|
|
1777
|
+
if (targetRaw.alias !== undefined) {
|
|
1778
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases "@${rawDecl.alias}", which is itself an alias. ` + `Chained aliases are not supported — alias the base marker directly.`);
|
|
1779
|
+
}
|
|
1780
|
+
const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
|
|
1781
|
+
if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
|
|
1782
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
|
|
1783
|
+
}
|
|
1784
|
+
if (preset.variant !== undefined && typeof preset.variant !== "string") {
|
|
1785
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
|
|
1786
|
+
}
|
|
1787
|
+
const container = resolveContainerShape(rawDecl.alias, originOf.get(rawDecl.alias), targetRaw);
|
|
1788
|
+
return { name, baseKind: rawDecl.alias, presetVariant: preset.variant, ...container };
|
|
1789
|
+
}
|
|
1790
|
+
return { name, baseKind: name, ...resolveContainerShape(name, pluginName, rawDecl) };
|
|
1791
|
+
}
|
|
1792
|
+
function buildDeclaredMarkerRegistry(sources) {
|
|
1793
|
+
const rawRegistry = new Map;
|
|
1794
|
+
const originOf = new Map;
|
|
1795
|
+
for (const { pluginName, markers } of sources) {
|
|
1796
|
+
if (!markers)
|
|
1797
|
+
continue;
|
|
1798
|
+
for (const [name, rawDecl] of Object.entries(markers)) {
|
|
1799
|
+
validateDeclaredMarkerName(name, pluginName);
|
|
1800
|
+
if (KNOWN_KINDS.includes(name)) {
|
|
1801
|
+
throw new Error(`Plugin "${pluginName}" declares marker "@${name}", which is a core Gutterpress marker name ` + `(${KNOWN_KINDS.map((k) => `@${k}`).join(", ")}). Core marker names cannot be shadowed — ` + `rename the plugin's marker.`);
|
|
1802
|
+
}
|
|
1803
|
+
if (rawRegistry.has(name)) {
|
|
1804
|
+
throw new Error(`Marker "@${name}" is declared by both plugin "${originOf.get(name)}" and plugin "${pluginName}". ` + `Declared marker names must be unique across every loaded plugin — rename one of them.`);
|
|
1805
|
+
}
|
|
1806
|
+
rawRegistry.set(name, rawDecl);
|
|
1807
|
+
originOf.set(name, pluginName);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const resolved = new Map;
|
|
1811
|
+
for (const [name, rawDecl] of rawRegistry) {
|
|
1812
|
+
resolved.set(name, resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf));
|
|
1813
|
+
}
|
|
1814
|
+
return resolved;
|
|
1815
|
+
}
|
|
1816
|
+
function declaredKindWords(declaredMarkers) {
|
|
1817
|
+
if (!declaredMarkers)
|
|
1818
|
+
return null;
|
|
1819
|
+
const words = new Set;
|
|
1820
|
+
for (const name of declaredMarkers.keys()) {
|
|
1821
|
+
words.add(name);
|
|
1822
|
+
words.add(`end-${name}`);
|
|
1823
|
+
}
|
|
1824
|
+
return words;
|
|
1825
|
+
}
|
|
1826
|
+
function nearestDeclaredMarkerName(word, declaredMarkers) {
|
|
1827
|
+
const w = word.toLowerCase();
|
|
1828
|
+
const threshold = Math.max(2, Math.floor(w.length / 3));
|
|
1829
|
+
let best = null;
|
|
1830
|
+
let bestDistance = Infinity;
|
|
1831
|
+
for (const known of declaredMarkers.keys()) {
|
|
1832
|
+
const d = editDistance(w, known);
|
|
1833
|
+
if (d < bestDistance) {
|
|
1834
|
+
bestDistance = d;
|
|
1835
|
+
best = known;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
return best !== null && bestDistance > 0 && bestDistance <= threshold ? best : null;
|
|
1839
|
+
}
|
|
1840
|
+
function scanForUnknownDeclaredMarkers(state, declaredMarkers) {
|
|
1841
|
+
if (!declaredMarkers)
|
|
1842
|
+
return;
|
|
1843
|
+
for (let tokenIndex = 0;tokenIndex < state.tokens.length; tokenIndex++) {
|
|
1844
|
+
const tok = state.tokens[tokenIndex];
|
|
1845
|
+
if (tok.type !== "inline" || !tok.map)
|
|
1846
|
+
continue;
|
|
1847
|
+
const opener = state.tokens[tokenIndex - 1];
|
|
1848
|
+
if (!opener || opener.type !== "paragraph_open")
|
|
1849
|
+
continue;
|
|
1850
|
+
const lines = tok.content.split(`
|
|
1851
|
+
`);
|
|
1852
|
+
for (let i = 0;i < lines.length; i++) {
|
|
1853
|
+
const m = /^@([A-Za-z][A-Za-z0-9-]*)(?=\s|$)/.exec(lines[i].trim());
|
|
1854
|
+
if (!m)
|
|
1855
|
+
continue;
|
|
1856
|
+
const word = m[1];
|
|
1857
|
+
if (KNOWN_KINDS.includes(word) || declaredMarkers.has(word))
|
|
1858
|
+
continue;
|
|
1859
|
+
const suggestion = nearestDeclaredMarkerName(word, declaredMarkers);
|
|
1860
|
+
if (!suggestion)
|
|
1861
|
+
continue;
|
|
1862
|
+
warn2(state.env, tok.map[0] + i + 1, "unknown_marker", `Unknown marker "@${word}". Did you mean "@${suggestion}"?`, null);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1624
1866
|
function plugin(md, pluginOptions = {}) {
|
|
1625
1867
|
const options = {
|
|
1626
1868
|
preferPagesInSpreads: false,
|
|
1627
1869
|
...pluginOptions
|
|
1628
1870
|
};
|
|
1871
|
+
const declaredMarkers = options.declaredMarkers instanceof Map && options.declaredMarkers.size > 0 ? options.declaredMarkers : null;
|
|
1872
|
+
const declaredWords = declaredKindWords(declaredMarkers);
|
|
1629
1873
|
function markerBlock(state, startLine, endLine, silent) {
|
|
1630
1874
|
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
1631
1875
|
const max = state.eMarks[startLine];
|
|
1632
1876
|
const line = state.src.slice(pos, max);
|
|
1633
|
-
const parsed = parseMarkerLine(line);
|
|
1877
|
+
const parsed = parseMarkerLine(line, declaredWords);
|
|
1634
1878
|
if (!parsed)
|
|
1635
1879
|
return false;
|
|
1636
1880
|
if (silent)
|
|
@@ -1687,10 +1931,64 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1687
1931
|
}
|
|
1688
1932
|
}
|
|
1689
1933
|
md.core.ruler.after("block", "layout_transform", function(state) {
|
|
1934
|
+
scanForUnknownDeclaredMarkers(state, declaredMarkers);
|
|
1690
1935
|
if (!state.env.__layoutMarkersUsed)
|
|
1691
1936
|
return;
|
|
1692
1937
|
scanForMistypedMarkers(state);
|
|
1693
1938
|
const out = [];
|
|
1939
|
+
const declaredFrames = [];
|
|
1940
|
+
function declaredFrameIndex(kind) {
|
|
1941
|
+
for (let i = declaredFrames.length - 1;i >= 0; i--) {
|
|
1942
|
+
if (declaredFrames[i].kind === kind)
|
|
1943
|
+
return i;
|
|
1944
|
+
}
|
|
1945
|
+
return -1;
|
|
1946
|
+
}
|
|
1947
|
+
function closeDeclaredFrame(kind) {
|
|
1948
|
+
const at = declaredFrameIndex(kind);
|
|
1949
|
+
if (at === -1)
|
|
1950
|
+
return false;
|
|
1951
|
+
while (declaredFrames.length > at) {
|
|
1952
|
+
const frame = declaredFrames.pop();
|
|
1953
|
+
out.push(new state.Token("layout_component_close", frame.tag, -1));
|
|
1954
|
+
}
|
|
1955
|
+
return true;
|
|
1956
|
+
}
|
|
1957
|
+
function drainDeclaredFrames() {
|
|
1958
|
+
while (declaredFrames.length) {
|
|
1959
|
+
const frame = declaredFrames.pop();
|
|
1960
|
+
out.push(new state.Token("layout_component_close", frame.tag, -1));
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
function drainDeclaredFramesAtEof() {
|
|
1964
|
+
while (declaredFrames.length) {
|
|
1965
|
+
const frame = declaredFrames.pop();
|
|
1966
|
+
if (!frame.decl.autoCloseAtEof) {
|
|
1967
|
+
warn2(state.env, frame.line || 0, "declared_marker_eof_close", `An open @${frame.kind} reached end-of-document; closing it automatically. Add ` + `@end-${frame.kind} to close it explicitly, or declare autoCloseAt: ["eof"] on this ` + `marker if running to end-of-document is expected.`, null);
|
|
1968
|
+
}
|
|
1969
|
+
out.push(new state.Token("layout_component_close", frame.tag, -1));
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
function openDeclaredMarker(meta, decl) {
|
|
1973
|
+
const t = new state.Token("layout_component_open", decl.tag, 1);
|
|
1974
|
+
t.meta = { line: meta.__line };
|
|
1975
|
+
const variant = meta.name || decl.presetVariant || null;
|
|
1976
|
+
const variantClass = variant && decl.variants && decl.variants[variant] || "";
|
|
1977
|
+
const baseClass = [decl.classBase, variantClass].filter(Boolean).join(" ");
|
|
1978
|
+
addClasses(t, baseClass, meta.attrs && meta.attrs.class ? meta.attrs.class : "");
|
|
1979
|
+
attachDataAttrs(t, decl.baseKind, variant, meta.attrs || {});
|
|
1980
|
+
out.push(t);
|
|
1981
|
+
declaredFrames.push({ kind: decl.baseKind, tag: decl.tag, decl, line: meta.__line });
|
|
1982
|
+
if (decl.label) {
|
|
1983
|
+
const value = meta.attrs && meta.attrs[decl.label.attr];
|
|
1984
|
+
if (value) {
|
|
1985
|
+
const labelToken = new state.Token("html_block", "", 0);
|
|
1986
|
+
labelToken.content = `<${decl.label.tag} class="${escapeAttr(decl.label.class)}">${escapeHtml(value)}</${decl.label.tag}>
|
|
1987
|
+
`;
|
|
1988
|
+
out.push(labelToken);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1694
1992
|
const stack = {
|
|
1695
1993
|
frames: [],
|
|
1696
1994
|
get(kind) {
|
|
@@ -1840,6 +2138,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1840
2138
|
const kind = meta.kind;
|
|
1841
2139
|
const line = meta.__line || 0;
|
|
1842
2140
|
if (kind === "chapter") {
|
|
2141
|
+
drainDeclaredFrames();
|
|
1843
2142
|
stack.close("chapter");
|
|
1844
2143
|
openChapter(meta);
|
|
1845
2144
|
continue;
|
|
@@ -1848,16 +2147,19 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1848
2147
|
if (stack.has("spread")) {
|
|
1849
2148
|
warn2(state.env, line, "nested_spread", "@spread encountered while another spread is open; closing the previous spread automatically.", meta);
|
|
1850
2149
|
}
|
|
2150
|
+
drainDeclaredFrames();
|
|
1851
2151
|
stack.close("spread");
|
|
1852
2152
|
openSpread(meta);
|
|
1853
2153
|
continue;
|
|
1854
2154
|
}
|
|
1855
2155
|
if (kind === "page") {
|
|
2156
|
+
drainDeclaredFrames();
|
|
1856
2157
|
stack.close("page");
|
|
1857
2158
|
openPage(meta);
|
|
1858
2159
|
continue;
|
|
1859
2160
|
}
|
|
1860
2161
|
if (kind === "section") {
|
|
2162
|
+
drainDeclaredFrames();
|
|
1861
2163
|
warnIfEmptyDecoratedSection("section", line);
|
|
1862
2164
|
stack.close("section");
|
|
1863
2165
|
const spread = stack.get("spread");
|
|
@@ -1873,6 +2175,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1873
2175
|
warn2(state.env, line, "continue_without_section", "@continue used without an open @section; ignoring marker.", meta);
|
|
1874
2176
|
continue;
|
|
1875
2177
|
}
|
|
2178
|
+
drainDeclaredFrames();
|
|
1876
2179
|
const contMeta = {
|
|
1877
2180
|
name: section.meta.name,
|
|
1878
2181
|
attrs: { ...section.meta.attrs || {} }
|
|
@@ -1905,15 +2208,42 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1905
2208
|
continue;
|
|
1906
2209
|
}
|
|
1907
2210
|
if (kind === "end-section") {
|
|
2211
|
+
drainDeclaredFrames();
|
|
1908
2212
|
warnIfEmptyDecoratedSection("end-section", line);
|
|
1909
2213
|
stack.close("section");
|
|
1910
2214
|
continue;
|
|
1911
2215
|
}
|
|
2216
|
+
if (declaredMarkers) {
|
|
2217
|
+
const openDecl = declaredMarkers.get(kind);
|
|
2218
|
+
if (openDecl) {
|
|
2219
|
+
if (openDecl.deprecated) {
|
|
2220
|
+
warn2(state.env, line, "deprecated_marker", `@${kind} is deprecated: ${openDecl.deprecated}`, meta);
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
closeDeclaredFrame(openDecl.baseKind);
|
|
2224
|
+
openDeclaredMarker(meta, openDecl);
|
|
2225
|
+
continue;
|
|
2226
|
+
}
|
|
2227
|
+
if (kind.startsWith("end-")) {
|
|
2228
|
+
const closeDecl = declaredMarkers.get(kind.slice(4));
|
|
2229
|
+
if (closeDecl) {
|
|
2230
|
+
if (closeDecl.deprecated) {
|
|
2231
|
+
warn2(state.env, line, "deprecated_marker", `@${kind} is deprecated: ${closeDecl.deprecated}`, meta);
|
|
2232
|
+
continue;
|
|
2233
|
+
}
|
|
2234
|
+
if (!closeDeclaredFrame(closeDecl.baseKind)) {
|
|
2235
|
+
warn2(state.env, line, "declared_marker_close_without_open", `@${kind} used without an open @${closeDecl.baseKind}; ignoring marker.`, meta);
|
|
2236
|
+
}
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
1912
2241
|
}
|
|
1913
2242
|
const eofSpread = stack.get("spread");
|
|
1914
2243
|
if (eofSpread && !eofSpread.sawAnyPage) {
|
|
1915
2244
|
warn2(state.env, 0, "spread_eof_close", "An open @spread reached end-of-document; closing it automatically.", null);
|
|
1916
2245
|
}
|
|
2246
|
+
drainDeclaredFramesAtEof();
|
|
1917
2247
|
stack.closeAll();
|
|
1918
2248
|
state.tokens = out;
|
|
1919
2249
|
});
|
|
@@ -2903,7 +3233,9 @@ function createMarkdownRenderer(customPlugins) {
|
|
|
2903
3233
|
md.use(unwrapPlugin(markdownItFootnote));
|
|
2904
3234
|
md.use(unwrapPlugin(markdownItDeflist));
|
|
2905
3235
|
md.use(unwrapPlugin(markdownItSourceMap));
|
|
2906
|
-
|
|
3236
|
+
const declaredMarkerSources = (customPlugins ?? []).filter((p) => !!p.markers).map((p) => ({ pluginName: p.name, markers: p.markers }));
|
|
3237
|
+
const declaredMarkers = buildDeclaredMarkerRegistry(declaredMarkerSources);
|
|
3238
|
+
md.use(plugin, { declaredMarkers });
|
|
2907
3239
|
md.use(gpPinScope);
|
|
2908
3240
|
registerImageRule(md);
|
|
2909
3241
|
if (customPlugins && customPlugins.length > 0) {
|
|
@@ -3226,7 +3558,7 @@ async function rewriteReachableModuleImports(entryPath, tree) {
|
|
|
3226
3558
|
const pkg = packageOwningFile(tree, absolute);
|
|
3227
3559
|
if (!pkg)
|
|
3228
3560
|
throw new Error(`Reachable plugin module is outside the verified package tree: ${absolute}`);
|
|
3229
|
-
const source = await
|
|
3561
|
+
const source = await readFile4(absolute, "utf8");
|
|
3230
3562
|
const [imports] = parseModuleImports(source, absolute);
|
|
3231
3563
|
const replacements = [];
|
|
3232
3564
|
const targets = new Set;
|
|
@@ -3457,12 +3789,22 @@ function validateStylesExport(value, pluginRef) {
|
|
|
3457
3789
|
}
|
|
3458
3790
|
return value.length > 0 ? value : undefined;
|
|
3459
3791
|
}
|
|
3792
|
+
function validateMarkersExport(value, pluginRef) {
|
|
3793
|
+
if (value === undefined)
|
|
3794
|
+
return;
|
|
3795
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3796
|
+
throw new Error(`Plugin "${pluginRef}" exports \`markers\` that is not a plain object.`);
|
|
3797
|
+
}
|
|
3798
|
+
const table = value;
|
|
3799
|
+
return Object.keys(table).length > 0 ? table : undefined;
|
|
3800
|
+
}
|
|
3460
3801
|
function extractPluginExports(pluginModule, pluginRef, exportName) {
|
|
3461
3802
|
const mod = pluginModule !== null && (typeof pluginModule === "object" || typeof pluginModule === "function") ? pluginModule : {};
|
|
3462
3803
|
let plugin2;
|
|
3463
3804
|
let metadata = mod.metadata;
|
|
3464
3805
|
let css = mod.css;
|
|
3465
3806
|
let styles = validateStylesExport(mod.styles, pluginRef);
|
|
3807
|
+
let markers = validateMarkersExport(mod.markers, pluginRef);
|
|
3466
3808
|
if (exportName && typeof mod[exportName] === "function") {
|
|
3467
3809
|
plugin2 = mod[exportName];
|
|
3468
3810
|
} else if (exportName) {
|
|
@@ -3478,11 +3820,12 @@ function extractPluginExports(pluginModule, pluginRef, exportName) {
|
|
|
3478
3820
|
metadata = inner.metadata ?? metadata;
|
|
3479
3821
|
css = inner.css ?? css;
|
|
3480
3822
|
styles = validateStylesExport(inner.styles, pluginRef) ?? styles;
|
|
3823
|
+
markers = validateMarkersExport(inner.markers, pluginRef) ?? markers;
|
|
3481
3824
|
}
|
|
3482
3825
|
if (typeof plugin2 !== "function") {
|
|
3483
3826
|
throw new Error(`Plugin "${pluginRef}" does not export a valid plugin function. Expected \`export default function (md, options) { ... }\` or CommonJS \`module.exports = function (md, options) { ... }\`.`);
|
|
3484
3827
|
}
|
|
3485
|
-
return { plugin: plugin2, metadata, css, styles };
|
|
3828
|
+
return { plugin: plugin2, metadata, css, styles, markers };
|
|
3486
3829
|
}
|
|
3487
3830
|
var pathPluginCache = new Map;
|
|
3488
3831
|
var liveShadowPaths = new Set;
|
|
@@ -3553,6 +3896,48 @@ function resolvePluginStyles(rawStyles, moduleDir, pluginRef) {
|
|
|
3553
3896
|
}
|
|
3554
3897
|
return resolveDeclaredStyles(rawStyles, moduleDir, `Plugin "${pluginRef}"`);
|
|
3555
3898
|
}
|
|
3899
|
+
function noopPlugin() {}
|
|
3900
|
+
function extensionMetadata(meta) {
|
|
3901
|
+
if (!meta.name && !meta.description && !meta.author)
|
|
3902
|
+
return;
|
|
3903
|
+
return {
|
|
3904
|
+
...meta.name ? { name: meta.name } : {},
|
|
3905
|
+
...meta.description ? { description: meta.description } : {},
|
|
3906
|
+
...meta.author ? { author: meta.author } : {}
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3909
|
+
async function loadExtensionFromDir(extensionDir, config, pluginRef) {
|
|
3910
|
+
const meta = await readExtensionMeta(extensionDir);
|
|
3911
|
+
assertExtensionContained(meta);
|
|
3912
|
+
const resolved = resolveExtension(extensionDir, meta, `Plugin "${pluginRef}"`);
|
|
3913
|
+
const extensionStyles = [...resolved.styles ?? [], ...resolved.engineStyles ?? []];
|
|
3914
|
+
const name = config.name ?? meta.name ?? pluginRef;
|
|
3915
|
+
if (!resolved.markdown && extensionStyles.length === 0) {
|
|
3916
|
+
throw new Error(`Extension folder "${pluginRef}" declares neither \`markdown\` nor \`styles\`/` + "`engineStyles` (in its gutterpress.json or theme.json) — there is nothing to " + "load. Point `path` at a JS file directly for a plain plugin, or add a metadata file declaring at least one.");
|
|
3917
|
+
}
|
|
3918
|
+
if (!resolved.markdown) {
|
|
3919
|
+
return {
|
|
3920
|
+
name,
|
|
3921
|
+
plugin: noopPlugin,
|
|
3922
|
+
...extensionMetadata(meta) ? { metadata: extensionMetadata(meta) } : {},
|
|
3923
|
+
...extensionStyles.length > 0 ? { styles: extensionStyles } : {},
|
|
3924
|
+
options: config.options
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3927
|
+
const pluginModule = await loadCachedPathPluginModule(resolved.markdown);
|
|
3928
|
+
const { plugin: plugin2, metadata, css, styles: rawStyles, markers } = extractPluginExports(pluginModule, pluginRef, config.export);
|
|
3929
|
+
const ownStyles = resolvePluginStyles(rawStyles, dirname2(resolved.markdown), pluginRef);
|
|
3930
|
+
const styles = [...extensionStyles, ...ownStyles ?? []];
|
|
3931
|
+
return {
|
|
3932
|
+
name,
|
|
3933
|
+
plugin: plugin2,
|
|
3934
|
+
metadata: metadata ?? extensionMetadata(meta),
|
|
3935
|
+
css,
|
|
3936
|
+
...styles.length > 0 ? { styles } : {},
|
|
3937
|
+
...markers ? { markers } : {},
|
|
3938
|
+
options: config.options
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3556
3941
|
async function loadPlugin(config, baseDir) {
|
|
3557
3942
|
const pluginRef = config.path ?? config.name ?? "(unspecified)";
|
|
3558
3943
|
let pluginModule;
|
|
@@ -3568,10 +3953,21 @@ async function loadPlugin(config, baseDir) {
|
|
|
3568
3953
|
options: config.options
|
|
3569
3954
|
};
|
|
3570
3955
|
}
|
|
3956
|
+
if (config.path) {
|
|
3957
|
+
const candidatePath = resolve3(baseDir, config.path);
|
|
3958
|
+
if (existsSync5(candidatePath) && statSync2(candidatePath).isDirectory()) {
|
|
3959
|
+
try {
|
|
3960
|
+
return await loadExtensionFromDir(candidatePath, config, pluginRef);
|
|
3961
|
+
} catch (error2) {
|
|
3962
|
+
const errorMsg = error2 instanceof Error ? error2.message : String(error2);
|
|
3963
|
+
throw new Error(`Failed to load plugin "${pluginRef}": ${errorMsg}`);
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3571
3967
|
try {
|
|
3572
3968
|
if (config.path) {
|
|
3573
3969
|
const pluginPath = resolve3(baseDir, config.path);
|
|
3574
|
-
if (!
|
|
3970
|
+
if (!existsSync5(pluginPath)) {
|
|
3575
3971
|
throw new Error(`Plugin file not found: ${pluginPath} (resolved from manifest entry path="${config.path}")`);
|
|
3576
3972
|
}
|
|
3577
3973
|
pluginModule = await loadCachedPathPluginModule(pluginPath);
|
|
@@ -3587,7 +3983,7 @@ async function loadPlugin(config, baseDir) {
|
|
|
3587
3983
|
const errorMsg = error2 instanceof Error ? error2.message : String(error2);
|
|
3588
3984
|
throw new Error(`Failed to load plugin "${pluginRef}": ${errorMsg}`);
|
|
3589
3985
|
}
|
|
3590
|
-
const { plugin: plugin2, metadata, css, styles: rawStyles } = extractPluginExports(pluginModule, pluginRef, config.export);
|
|
3986
|
+
const { plugin: plugin2, metadata, css, styles: rawStyles, markers } = extractPluginExports(pluginModule, pluginRef, config.export);
|
|
3591
3987
|
const styles = resolvePluginStyles(rawStyles, moduleDir, pluginRef);
|
|
3592
3988
|
return {
|
|
3593
3989
|
name: pluginName,
|
|
@@ -3595,6 +3991,7 @@ async function loadPlugin(config, baseDir) {
|
|
|
3595
3991
|
metadata,
|
|
3596
3992
|
css,
|
|
3597
3993
|
styles,
|
|
3994
|
+
markers,
|
|
3598
3995
|
options: config.options
|
|
3599
3996
|
};
|
|
3600
3997
|
}
|
|
@@ -3638,7 +4035,10 @@ async function runLint(opts = {}) {
|
|
|
3638
4035
|
} else {
|
|
3639
4036
|
const relStyles = await resolveActiveStyles(manifestDir, resolved.styles);
|
|
3640
4037
|
const projectFiles = relStyles.map((rel) => resolve4(manifestDir, rel));
|
|
3641
|
-
|
|
4038
|
+
let pluginStylePaths = opts.pluginStylePaths;
|
|
4039
|
+
if (pluginStylePaths === undefined) {
|
|
4040
|
+
({ pluginStylePaths } = await loadPluginsWithCss(resolved.plugins, manifestDir, (ref, err) => log.warn(`Skipping plugin "${ref}" for lint — ${err.message}`)));
|
|
4041
|
+
}
|
|
3642
4042
|
files = [...projectFiles, ...pluginStylePaths].filter((f) => !f.endsWith(".min.css"));
|
|
3643
4043
|
}
|
|
3644
4044
|
if (files.length === 0) {
|
|
@@ -3652,7 +4052,7 @@ async function runLint(opts = {}) {
|
|
|
3652
4052
|
for (const file of files) {
|
|
3653
4053
|
let css;
|
|
3654
4054
|
try {
|
|
3655
|
-
css = await
|
|
4055
|
+
css = await readFile5(file, "utf8");
|
|
3656
4056
|
} catch (err) {
|
|
3657
4057
|
log.error(` ${file}`);
|
|
3658
4058
|
log.error(` cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3791,7 +4191,7 @@ function inspectionFailed(checkId, message, extra = {}) {
|
|
|
3791
4191
|
}
|
|
3792
4192
|
|
|
3793
4193
|
// src/lib/pdf-inspect.ts
|
|
3794
|
-
import { stat, readFile as
|
|
4194
|
+
import { stat, readFile as readFile6 } from "node:fs/promises";
|
|
3795
4195
|
import { getDocumentProxy } from "unpdf";
|
|
3796
4196
|
import * as pdfjs from "unpdf/pdfjs";
|
|
3797
4197
|
function getOps(mod) {
|
|
@@ -3827,17 +4227,17 @@ function evictLru() {
|
|
|
3827
4227
|
destroyEntryAfterGrace(evicted);
|
|
3828
4228
|
}
|
|
3829
4229
|
}
|
|
3830
|
-
async function loadPdf(
|
|
4230
|
+
async function loadPdf(path4) {
|
|
3831
4231
|
let st;
|
|
3832
4232
|
try {
|
|
3833
|
-
st = await stat(
|
|
4233
|
+
st = await stat(path4);
|
|
3834
4234
|
} catch {
|
|
3835
4235
|
return null;
|
|
3836
4236
|
}
|
|
3837
|
-
const hit = docCache.get(
|
|
4237
|
+
const hit = docCache.get(path4);
|
|
3838
4238
|
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
|
|
3839
|
-
docCache.delete(
|
|
3840
|
-
docCache.set(
|
|
4239
|
+
docCache.delete(path4);
|
|
4240
|
+
docCache.set(path4, hit);
|
|
3841
4241
|
try {
|
|
3842
4242
|
return await hit.doc;
|
|
3843
4243
|
} catch {
|
|
@@ -3846,15 +4246,15 @@ async function loadPdf(path3) {
|
|
|
3846
4246
|
}
|
|
3847
4247
|
if (hit)
|
|
3848
4248
|
destroyEntry(hit);
|
|
3849
|
-
const docPromise =
|
|
3850
|
-
docCache.delete(
|
|
3851
|
-
docCache.set(
|
|
4249
|
+
const docPromise = readFile6(path4).then((buf) => getDocumentProxy(new Uint8Array(buf)));
|
|
4250
|
+
docCache.delete(path4);
|
|
4251
|
+
docCache.set(path4, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
|
|
3852
4252
|
evictLru();
|
|
3853
4253
|
try {
|
|
3854
4254
|
return await docPromise;
|
|
3855
4255
|
} catch {
|
|
3856
|
-
if (docCache.get(
|
|
3857
|
-
docCache.delete(
|
|
4256
|
+
if (docCache.get(path4)?.doc === docPromise)
|
|
4257
|
+
docCache.delete(path4);
|
|
3858
4258
|
return null;
|
|
3859
4259
|
}
|
|
3860
4260
|
}
|
|
@@ -4305,9 +4705,9 @@ var check3 = {
|
|
|
4305
4705
|
registerCheck(check3);
|
|
4306
4706
|
|
|
4307
4707
|
// src/lib/pdf-parse.ts
|
|
4308
|
-
import { readFile as
|
|
4708
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
4309
4709
|
async function readPdfBytes(pdfPath) {
|
|
4310
|
-
return
|
|
4710
|
+
return readFile7(pdfPath, "latin1");
|
|
4311
4711
|
}
|
|
4312
4712
|
function parseInkCov(out) {
|
|
4313
4713
|
const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
|
|
@@ -4878,21 +5278,21 @@ var check15 = {
|
|
|
4878
5278
|
registerCheck(check15);
|
|
4879
5279
|
|
|
4880
5280
|
// src/checks/source/markdownlint.ts
|
|
4881
|
-
import { readFile as
|
|
5281
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
4882
5282
|
import { lint } from "markdownlint/sync";
|
|
4883
5283
|
import { parse as parseYaml2 } from "yaml";
|
|
4884
5284
|
|
|
4885
5285
|
// src/checks/source/config-file.ts
|
|
4886
|
-
import { existsSync as
|
|
5286
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
4887
5287
|
import { resolve as resolve5 } from "node:path";
|
|
4888
5288
|
function findConfigFile(inputDir, names, explicit) {
|
|
4889
5289
|
if (explicit) {
|
|
4890
5290
|
const p = resolve5(inputDir, explicit);
|
|
4891
|
-
return
|
|
5291
|
+
return existsSync6(p) ? p : null;
|
|
4892
5292
|
}
|
|
4893
5293
|
for (const name of names) {
|
|
4894
5294
|
const p = resolve5(inputDir, name);
|
|
4895
|
-
if (
|
|
5295
|
+
if (existsSync6(p))
|
|
4896
5296
|
return p;
|
|
4897
5297
|
}
|
|
4898
5298
|
return null;
|
|
@@ -4911,7 +5311,7 @@ function stripJsonComments(s) {
|
|
|
4911
5311
|
return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
4912
5312
|
}
|
|
4913
5313
|
async function loadConfig(configPath) {
|
|
4914
|
-
const raw = await
|
|
5314
|
+
const raw = await readFile8(configPath, "utf8");
|
|
4915
5315
|
let parsed;
|
|
4916
5316
|
if (configPath.endsWith(".json")) {
|
|
4917
5317
|
parsed = JSON.parse(raw);
|
|
@@ -4970,8 +5370,8 @@ var check16 = {
|
|
|
4970
5370
|
registerCheck(check16);
|
|
4971
5371
|
|
|
4972
5372
|
// src/checks/source/htmlhint.ts
|
|
4973
|
-
import { existsSync as
|
|
4974
|
-
import { readFile as
|
|
5373
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
5374
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
4975
5375
|
import htmlhint from "htmlhint";
|
|
4976
5376
|
var { HTMLHint } = htmlhint;
|
|
4977
5377
|
var CONFIG_NAMES2 = [".htmlhintrc"];
|
|
@@ -4985,14 +5385,14 @@ var check17 = {
|
|
|
4985
5385
|
const sourceConfig = ctx.config.validate.source;
|
|
4986
5386
|
if (sourceConfig.htmlhint === false)
|
|
4987
5387
|
return [];
|
|
4988
|
-
if (!ctx.htmlPath || !
|
|
5388
|
+
if (!ctx.htmlPath || !existsSync7(ctx.htmlPath))
|
|
4989
5389
|
return [];
|
|
4990
5390
|
const configPath = typeof sourceConfig.htmlhint === "string" ? sourceConfig.htmlhint : null;
|
|
4991
5391
|
const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
|
|
4992
5392
|
if (!resolvedConfig && !configPath)
|
|
4993
5393
|
return [];
|
|
4994
|
-
const ruleset = resolvedConfig ? JSON.parse(await
|
|
4995
|
-
const html = await
|
|
5394
|
+
const ruleset = resolvedConfig ? JSON.parse(await readFile9(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
|
|
5395
|
+
const html = await readFile9(ctx.htmlPath, "utf8");
|
|
4996
5396
|
const messages = HTMLHint.verify(html, ruleset);
|
|
4997
5397
|
return messages.map((m) => ({
|
|
4998
5398
|
checkId: check17.id,
|
|
@@ -5007,7 +5407,7 @@ var check17 = {
|
|
|
5007
5407
|
registerCheck(check17);
|
|
5008
5408
|
|
|
5009
5409
|
// src/checks/source/stylelint.ts
|
|
5010
|
-
import { readFile as
|
|
5410
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
5011
5411
|
var check18 = {
|
|
5012
5412
|
id: "source.stylelint",
|
|
5013
5413
|
name: "Print-safety CSS",
|
|
@@ -5025,7 +5425,7 @@ var check18 = {
|
|
|
5025
5425
|
for (const file of files) {
|
|
5026
5426
|
let css;
|
|
5027
5427
|
try {
|
|
5028
|
-
css = await
|
|
5428
|
+
css = await readFile10(file, "utf8");
|
|
5029
5429
|
} catch {
|
|
5030
5430
|
continue;
|
|
5031
5431
|
}
|
|
@@ -5046,12 +5446,12 @@ var check18 = {
|
|
|
5046
5446
|
registerCheck(check18);
|
|
5047
5447
|
|
|
5048
5448
|
// src/checks/source/css-ownership.ts
|
|
5049
|
-
import { readFile as
|
|
5449
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
5050
5450
|
import postcss2 from "postcss";
|
|
5051
5451
|
import { parse as parseYaml3 } from "yaml";
|
|
5052
5452
|
var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
|
|
5053
5453
|
async function loadContract(configPath) {
|
|
5054
|
-
const raw = await
|
|
5454
|
+
const raw = await readFile11(configPath, "utf8");
|
|
5055
5455
|
const parsed = parseYaml3(raw);
|
|
5056
5456
|
if (parsed && typeof parsed === "object")
|
|
5057
5457
|
return parsed;
|
|
@@ -5202,7 +5602,7 @@ var check19 = {
|
|
|
5202
5602
|
continue;
|
|
5203
5603
|
let css;
|
|
5204
5604
|
try {
|
|
5205
|
-
css = await
|
|
5605
|
+
css = await readFile11(absFile, "utf8");
|
|
5206
5606
|
} catch {
|
|
5207
5607
|
continue;
|
|
5208
5608
|
}
|
|
@@ -5244,14 +5644,14 @@ var check19 = {
|
|
|
5244
5644
|
registerCheck(check19);
|
|
5245
5645
|
|
|
5246
5646
|
// src/checks/source/local-refs.ts
|
|
5247
|
-
import { existsSync as
|
|
5248
|
-
import { readFile as
|
|
5647
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
5648
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
5249
5649
|
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
5250
5650
|
|
|
5251
5651
|
// src/lib/asset-inline.ts
|
|
5252
5652
|
import { createHash as createHash3 } from "node:crypto";
|
|
5253
|
-
import { readFile as
|
|
5254
|
-
import
|
|
5653
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
5654
|
+
import path4 from "node:path";
|
|
5255
5655
|
import postcss3 from "postcss";
|
|
5256
5656
|
var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
|
|
5257
5657
|
var HASHED_ASSET_DIR = "assets";
|
|
@@ -5292,19 +5692,19 @@ function decodeRef(ref) {
|
|
|
5292
5692
|
}
|
|
5293
5693
|
}
|
|
5294
5694
|
function toPosix(p) {
|
|
5295
|
-
return p.split(
|
|
5695
|
+
return p.split(path4.sep).join("/");
|
|
5296
5696
|
}
|
|
5297
5697
|
function escapesProjectRoot(projectDir, absPath) {
|
|
5298
|
-
const rel =
|
|
5299
|
-
return rel === ".." || rel.startsWith(`..${
|
|
5698
|
+
const rel = path4.relative(projectDir, absPath);
|
|
5699
|
+
return rel === ".." || rel.startsWith(`..${path4.sep}`) || path4.isAbsolute(rel);
|
|
5300
5700
|
}
|
|
5301
5701
|
function proseImageRefError(ref, projectDir) {
|
|
5302
5702
|
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
5303
|
-
if (/^file:/i.test(cleaned) ||
|
|
5703
|
+
if (/^file:/i.test(cleaned) || path4.isAbsolute(cleaned)) {
|
|
5304
5704
|
return `Image reference must be relative to the project: ${ref}
|
|
5305
5705
|
` + ` Copy the file into your project folder and reference it from there.`;
|
|
5306
5706
|
}
|
|
5307
|
-
if (escapesProjectRoot(projectDir,
|
|
5707
|
+
if (escapesProjectRoot(projectDir, path4.resolve(projectDir, cleaned))) {
|
|
5308
5708
|
return `Image reference points outside the project: ${ref}
|
|
5309
5709
|
` + ` Copy the file into your project folder and reference it from there.`;
|
|
5310
5710
|
}
|
|
@@ -5315,7 +5715,7 @@ function mimeFor(ext) {
|
|
|
5315
5715
|
}
|
|
5316
5716
|
async function readOrThrow(absPath, kind, referencedFrom) {
|
|
5317
5717
|
try {
|
|
5318
|
-
return await
|
|
5718
|
+
return await readFile12(absPath);
|
|
5319
5719
|
} catch {
|
|
5320
5720
|
throw new BuildError(`Missing ${kind}: ${absPath}
|
|
5321
5721
|
referenced from ${referencedFrom}
|
|
@@ -5374,12 +5774,12 @@ function importTarget(atRule) {
|
|
|
5374
5774
|
return target;
|
|
5375
5775
|
}
|
|
5376
5776
|
async function inlineOne(cssPath, copies, warnings, seen) {
|
|
5377
|
-
const abs =
|
|
5777
|
+
const abs = path4.resolve(cssPath);
|
|
5378
5778
|
if (seen.has(abs))
|
|
5379
5779
|
return "";
|
|
5380
5780
|
seen.add(abs);
|
|
5381
5781
|
const source = await readOrThrow(abs, "stylesheet", "the project manifest");
|
|
5382
|
-
const cssDir =
|
|
5782
|
+
const cssDir = path4.dirname(abs);
|
|
5383
5783
|
let root;
|
|
5384
5784
|
try {
|
|
5385
5785
|
root = postcss3.parse(source.toString("utf-8"), { from: abs });
|
|
@@ -5401,8 +5801,8 @@ async function inlineOne(cssPath, copies, warnings, seen) {
|
|
|
5401
5801
|
return null;
|
|
5402
5802
|
}
|
|
5403
5803
|
const relPath = stripUrlSuffix(decodeRef(raw));
|
|
5404
|
-
const absAsset =
|
|
5405
|
-
const ext =
|
|
5804
|
+
const absAsset = path4.resolve(cssDir, relPath);
|
|
5805
|
+
const ext = path4.extname(absAsset).toLowerCase();
|
|
5406
5806
|
if (FONT_EXTS.has(ext)) {
|
|
5407
5807
|
const bytes2 = await readOrThrow(absAsset, "font file", abs);
|
|
5408
5808
|
return dataUri(bytes2, ext);
|
|
@@ -5420,7 +5820,7 @@ async function inlineOne(cssPath, copies, warnings, seen) {
|
|
|
5420
5820
|
imports.push({ node, target });
|
|
5421
5821
|
});
|
|
5422
5822
|
for (const { node, target } of imports) {
|
|
5423
|
-
const importedAbs =
|
|
5823
|
+
const importedAbs = path4.resolve(cssDir, stripUrlSuffix(decodeRef(target)));
|
|
5424
5824
|
const inlined = await inlineOne(importedAbs, copies, warnings, seen);
|
|
5425
5825
|
node.replaceWith(wrapImportConditions(node, postcss3.parse(inlined, { from: importedAbs })));
|
|
5426
5826
|
}
|
|
@@ -5432,10 +5832,10 @@ async function inlineStyles(projectDir, stylePaths) {
|
|
|
5432
5832
|
const seen = new Set;
|
|
5433
5833
|
const parts = [];
|
|
5434
5834
|
for (const rel of stylePaths) {
|
|
5435
|
-
const abs =
|
|
5835
|
+
const abs = path4.resolve(projectDir, rel);
|
|
5436
5836
|
const css = await inlineOne(abs, copies, warnings, seen);
|
|
5437
5837
|
if (css.trim().length > 0) {
|
|
5438
|
-
parts.push(`/* ${toPosix(
|
|
5838
|
+
parts.push(`/* ${toPosix(path4.relative(projectDir, abs))} */
|
|
5439
5839
|
${css.trim()}`);
|
|
5440
5840
|
}
|
|
5441
5841
|
}
|
|
@@ -5451,14 +5851,14 @@ async function collectStyleDependencies(projectDir, stylePaths) {
|
|
|
5451
5851
|
const found = new Set;
|
|
5452
5852
|
const visited = new Set;
|
|
5453
5853
|
async function walk(cssPath) {
|
|
5454
|
-
const abs =
|
|
5854
|
+
const abs = path4.resolve(cssPath);
|
|
5455
5855
|
if (visited.has(abs))
|
|
5456
5856
|
return;
|
|
5457
5857
|
visited.add(abs);
|
|
5458
5858
|
found.add(abs);
|
|
5459
5859
|
let source;
|
|
5460
5860
|
try {
|
|
5461
|
-
source = await
|
|
5861
|
+
source = await readFile12(abs, "utf-8");
|
|
5462
5862
|
} catch {
|
|
5463
5863
|
return;
|
|
5464
5864
|
}
|
|
@@ -5468,14 +5868,14 @@ async function collectStyleDependencies(projectDir, stylePaths) {
|
|
|
5468
5868
|
} catch {
|
|
5469
5869
|
return;
|
|
5470
5870
|
}
|
|
5471
|
-
const cssDir =
|
|
5871
|
+
const cssDir = path4.dirname(abs);
|
|
5472
5872
|
root.walkDecls((decl) => {
|
|
5473
5873
|
if (!decl.value.includes("url("))
|
|
5474
5874
|
return;
|
|
5475
5875
|
for (const raw of extractCssUrls(decl.value)) {
|
|
5476
5876
|
if (!raw || isNonFilesystemRef(raw))
|
|
5477
5877
|
continue;
|
|
5478
|
-
found.add(
|
|
5878
|
+
found.add(path4.resolve(cssDir, stripUrlSuffix(decodeRef(raw))));
|
|
5479
5879
|
}
|
|
5480
5880
|
});
|
|
5481
5881
|
const imports = [];
|
|
@@ -5485,11 +5885,11 @@ async function collectStyleDependencies(projectDir, stylePaths) {
|
|
|
5485
5885
|
imports.push(target);
|
|
5486
5886
|
});
|
|
5487
5887
|
for (const target of imports) {
|
|
5488
|
-
await walk(
|
|
5888
|
+
await walk(path4.resolve(cssDir, stripUrlSuffix(decodeRef(target))));
|
|
5489
5889
|
}
|
|
5490
5890
|
}
|
|
5491
5891
|
for (const rel of stylePaths) {
|
|
5492
|
-
await walk(
|
|
5892
|
+
await walk(path4.resolve(projectDir, rel));
|
|
5493
5893
|
}
|
|
5494
5894
|
return [...found];
|
|
5495
5895
|
}
|
|
@@ -5506,8 +5906,8 @@ async function planImageCopies(projectDir, refs) {
|
|
|
5506
5906
|
continue;
|
|
5507
5907
|
}
|
|
5508
5908
|
const cleaned = stripUrlSuffix(decodeRef(ref));
|
|
5509
|
-
const abs =
|
|
5510
|
-
const rel =
|
|
5909
|
+
const abs = path4.resolve(projectDir, cleaned);
|
|
5910
|
+
const rel = path4.relative(projectDir, abs);
|
|
5511
5911
|
const dest = toPosix(rel);
|
|
5512
5912
|
copies.set(dest, { from: abs, to: dest });
|
|
5513
5913
|
destinations.set(ref, dest);
|
|
@@ -5528,18 +5928,18 @@ async function inlineShapeUrls(html, baseDir) {
|
|
|
5528
5928
|
if (!raw || isNonFilesystemRef(raw))
|
|
5529
5929
|
continue;
|
|
5530
5930
|
const cleaned = stripUrlSuffix(decodeRef(raw));
|
|
5531
|
-
if (
|
|
5931
|
+
if (path4.isAbsolute(cleaned))
|
|
5532
5932
|
continue;
|
|
5533
|
-
const abs =
|
|
5933
|
+
const abs = path4.resolve(baseDir, cleaned);
|
|
5534
5934
|
if (escapesProjectRoot(baseDir, abs))
|
|
5535
5935
|
continue;
|
|
5536
5936
|
let bytes;
|
|
5537
5937
|
try {
|
|
5538
|
-
bytes = await
|
|
5938
|
+
bytes = await readFile12(abs);
|
|
5539
5939
|
} catch {
|
|
5540
5940
|
continue;
|
|
5541
5941
|
}
|
|
5542
|
-
const mime = mimeFor(
|
|
5942
|
+
const mime = mimeFor(path4.extname(abs).toLowerCase());
|
|
5543
5943
|
const dataUri2 = `data:${mime};base64,${bytes.toString("base64")}`;
|
|
5544
5944
|
replacements.set(m[0], `--gp-shape:url("${dataUri2}")`);
|
|
5545
5945
|
}
|
|
@@ -5629,7 +6029,7 @@ var check20 = {
|
|
|
5629
6029
|
const collectRenderedLocalRefs = createRenderedLocalRefCollector(plugins);
|
|
5630
6030
|
for (const file of files) {
|
|
5631
6031
|
try {
|
|
5632
|
-
const content = await
|
|
6032
|
+
const content = await readFile13(file, "utf8");
|
|
5633
6033
|
for (const { ref, kind, line } of collectRenderedLocalRefs(content)) {
|
|
5634
6034
|
if (isNonFilesystemRef(ref))
|
|
5635
6035
|
continue;
|
|
@@ -5671,15 +6071,15 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
|
|
|
5671
6071
|
const rootRelative = resolve6(projectRoot, cleaned);
|
|
5672
6072
|
switch (kind) {
|
|
5673
6073
|
case "image":
|
|
5674
|
-
return
|
|
6074
|
+
return existsSync8(rootRelative);
|
|
5675
6075
|
case "link":
|
|
5676
|
-
return
|
|
6076
|
+
return existsSync8(fileRelative);
|
|
5677
6077
|
}
|
|
5678
6078
|
}
|
|
5679
6079
|
registerCheck(check20);
|
|
5680
6080
|
|
|
5681
6081
|
// src/checks/source/accessibility-alt-text.ts
|
|
5682
|
-
import { readFile as
|
|
6082
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
5683
6083
|
var check21 = {
|
|
5684
6084
|
id: "source.accessibility.alt-text",
|
|
5685
6085
|
name: "Image Alt Text",
|
|
@@ -5693,7 +6093,7 @@ var check21 = {
|
|
|
5693
6093
|
const results = [];
|
|
5694
6094
|
for (const file of files) {
|
|
5695
6095
|
try {
|
|
5696
|
-
const content = await
|
|
6096
|
+
const content = await readFile14(file, "utf8");
|
|
5697
6097
|
const lines = content.split(`
|
|
5698
6098
|
`);
|
|
5699
6099
|
let inFence = false;
|
|
@@ -5737,7 +6137,7 @@ function extractImageAlts(line) {
|
|
|
5737
6137
|
registerCheck(check21);
|
|
5738
6138
|
|
|
5739
6139
|
// src/checks/source/accessibility-heading-order.ts
|
|
5740
|
-
import { readFile as
|
|
6140
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
5741
6141
|
var check22 = {
|
|
5742
6142
|
id: "source.accessibility.heading-order",
|
|
5743
6143
|
name: "Heading Order",
|
|
@@ -5751,7 +6151,7 @@ var check22 = {
|
|
|
5751
6151
|
const results = [];
|
|
5752
6152
|
for (const file of files) {
|
|
5753
6153
|
try {
|
|
5754
|
-
const content = await
|
|
6154
|
+
const content = await readFile15(file, "utf8");
|
|
5755
6155
|
const lines = content.split(`
|
|
5756
6156
|
`);
|
|
5757
6157
|
let inFence = false;
|
|
@@ -5787,7 +6187,7 @@ var check22 = {
|
|
|
5787
6187
|
registerCheck(check22);
|
|
5788
6188
|
|
|
5789
6189
|
// src/checks/source/layout-markers.ts
|
|
5790
|
-
import { readFile as
|
|
6190
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
5791
6191
|
var check23 = {
|
|
5792
6192
|
id: "source.markdown.layout-markers",
|
|
5793
6193
|
name: "Layout Markers",
|
|
@@ -5805,7 +6205,7 @@ var check23 = {
|
|
|
5805
6205
|
const md = createMarkdownRenderer(plugins);
|
|
5806
6206
|
for (const file of files) {
|
|
5807
6207
|
try {
|
|
5808
|
-
const content = await
|
|
6208
|
+
const content = await readFile16(file, "utf8");
|
|
5809
6209
|
const env = {};
|
|
5810
6210
|
md.render(content, env);
|
|
5811
6211
|
for (const w of env.layoutWarnings ?? []) {
|
|
@@ -5827,8 +6227,8 @@ var check23 = {
|
|
|
5827
6227
|
registerCheck(check23);
|
|
5828
6228
|
|
|
5829
6229
|
// src/checks/source/merge-markers.ts
|
|
5830
|
-
import { readFile as
|
|
5831
|
-
import
|
|
6230
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
6231
|
+
import path5 from "node:path";
|
|
5832
6232
|
|
|
5833
6233
|
// src/checks/asset/extensions.ts
|
|
5834
6234
|
var RASTER_INSPECTABLE_EXTS = [
|
|
@@ -5869,7 +6269,7 @@ var check24 = {
|
|
|
5869
6269
|
for (const file of textFiles) {
|
|
5870
6270
|
let content;
|
|
5871
6271
|
try {
|
|
5872
|
-
content = await
|
|
6272
|
+
content = await readFile17(file, "utf8");
|
|
5873
6273
|
} catch {
|
|
5874
6274
|
results.push(inspectionFailed(check24.id, `Could not read source file: ${file}`, { file }));
|
|
5875
6275
|
continue;
|
|
@@ -5912,7 +6312,7 @@ var check24 = {
|
|
|
5912
6312
|
ignore: [...ASSET_SCAN_IGNORE_GLOBS]
|
|
5913
6313
|
});
|
|
5914
6314
|
for (const sibling of siblings.sort()) {
|
|
5915
|
-
const rel =
|
|
6315
|
+
const rel = path5.relative(ctx.inputDir, sibling).split(path5.sep).join("/");
|
|
5916
6316
|
results.push(finding(check24.id, {
|
|
5917
6317
|
severity: "warning",
|
|
5918
6318
|
code: "kept-both-versions",
|
|
@@ -5932,27 +6332,27 @@ registerCheck(check24);
|
|
|
5932
6332
|
import { stat as stat3 } from "node:fs/promises";
|
|
5933
6333
|
|
|
5934
6334
|
// src/lib/image-inspect.ts
|
|
5935
|
-
import { stat as stat2, readFile as
|
|
6335
|
+
import { stat as stat2, readFile as readFile18 } from "node:fs/promises";
|
|
5936
6336
|
var DEFAULT_DPI = 72;
|
|
5937
6337
|
var cache = new Map;
|
|
5938
|
-
async function inspectImage(
|
|
6338
|
+
async function inspectImage(path6) {
|
|
5939
6339
|
let st;
|
|
5940
6340
|
try {
|
|
5941
|
-
st = await stat2(
|
|
6341
|
+
st = await stat2(path6);
|
|
5942
6342
|
} catch {
|
|
5943
6343
|
return null;
|
|
5944
6344
|
}
|
|
5945
|
-
const hit = cache.get(
|
|
6345
|
+
const hit = cache.get(path6);
|
|
5946
6346
|
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
|
|
5947
6347
|
return hit.info;
|
|
5948
6348
|
let info2 = null;
|
|
5949
6349
|
try {
|
|
5950
|
-
const buf = await
|
|
6350
|
+
const buf = await readFile18(path6);
|
|
5951
6351
|
info2 = parseImage(buf);
|
|
5952
6352
|
} catch {
|
|
5953
6353
|
info2 = null;
|
|
5954
6354
|
}
|
|
5955
|
-
cache.set(
|
|
6355
|
+
cache.set(path6, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
|
|
5956
6356
|
return info2;
|
|
5957
6357
|
}
|
|
5958
6358
|
function parseImage(b) {
|
|
@@ -6406,7 +6806,7 @@ var check30 = {
|
|
|
6406
6806
|
registerCheck(check30);
|
|
6407
6807
|
|
|
6408
6808
|
// src/checks/asset/font-license.ts
|
|
6409
|
-
import { existsSync as
|
|
6809
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
6410
6810
|
import { resolve as resolve7 } from "node:path";
|
|
6411
6811
|
var LICENSE_NAMES = [
|
|
6412
6812
|
"LICENSE",
|
|
@@ -6442,7 +6842,7 @@ var check31 = {
|
|
|
6442
6842
|
}
|
|
6443
6843
|
const results = [];
|
|
6444
6844
|
for (const fontDir of fontDirs) {
|
|
6445
|
-
const hasLicense = LICENSE_NAMES.some((name) =>
|
|
6845
|
+
const hasLicense = LICENSE_NAMES.some((name) => existsSync9(resolve7(fontDir, name)));
|
|
6446
6846
|
if (!hasLicense) {
|
|
6447
6847
|
results.push({
|
|
6448
6848
|
checkId: check31.id,
|
|
@@ -6507,7 +6907,7 @@ var check32 = {
|
|
|
6507
6907
|
registerCheck(check32);
|
|
6508
6908
|
|
|
6509
6909
|
// src/checks/heuristic/section-density.ts
|
|
6510
|
-
import { readFile as
|
|
6910
|
+
import { readFile as readFile19 } from "node:fs/promises";
|
|
6511
6911
|
var check33 = {
|
|
6512
6912
|
id: "heuristic.chunking.section-density",
|
|
6513
6913
|
name: "Section Density",
|
|
@@ -6524,7 +6924,7 @@ var check33 = {
|
|
|
6524
6924
|
const results = [];
|
|
6525
6925
|
for (const file of files) {
|
|
6526
6926
|
try {
|
|
6527
|
-
const content = await
|
|
6927
|
+
const content = await readFile19(file, "utf8");
|
|
6528
6928
|
const lines = content.split(`
|
|
6529
6929
|
`);
|
|
6530
6930
|
let currentSectionStart = 0;
|
|
@@ -6689,11 +7089,11 @@ function reportMissingTools(result) {
|
|
|
6689
7089
|
}
|
|
6690
7090
|
|
|
6691
7091
|
// src/lib/validation-exec.ts
|
|
6692
|
-
import { existsSync as
|
|
7092
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
6693
7093
|
import { dirname as dirname4, join as join3, resolve as resolve8 } from "node:path";
|
|
6694
7094
|
|
|
6695
7095
|
// src/lib/output-paths.ts
|
|
6696
|
-
import
|
|
7096
|
+
import path6 from "node:path";
|
|
6697
7097
|
|
|
6698
7098
|
// src/lib/slug.ts
|
|
6699
7099
|
function slugify(name, fallback = "") {
|
|
@@ -6711,7 +7111,7 @@ function bookSlug(title) {
|
|
|
6711
7111
|
return slugify(title ?? "", "book");
|
|
6712
7112
|
}
|
|
6713
7113
|
function resolveOutputDir(manifestDir, title) {
|
|
6714
|
-
return
|
|
7114
|
+
return path6.resolve(manifestDir, DIST_DIRNAME, bookSlug(title));
|
|
6715
7115
|
}
|
|
6716
7116
|
function artifactName(title, format) {
|
|
6717
7117
|
return `${bookSlug(title)}-${format}.pdf`;
|
|
@@ -6721,7 +7121,7 @@ function artifactName(title, format) {
|
|
|
6721
7121
|
var BOOK_HTML_FILENAME = "book.html";
|
|
6722
7122
|
|
|
6723
7123
|
// src/lib/markdown/index.ts
|
|
6724
|
-
import { readdir as readdir3, readFile as
|
|
7124
|
+
import { readdir as readdir3, readFile as readFile20, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
6725
7125
|
|
|
6726
7126
|
// src/lib/remote-auth/sync-messages.ts
|
|
6727
7127
|
function isOnlineSibling(filepath) {
|
|
@@ -6849,7 +7249,7 @@ async function renderChapters(inputDir, opts = {}) {
|
|
|
6849
7249
|
}
|
|
6850
7250
|
return assembleBookHtml({
|
|
6851
7251
|
files,
|
|
6852
|
-
readText: (relPath) =>
|
|
7252
|
+
readText: (relPath) => readFile20(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
|
|
6853
7253
|
projectCss: inlined.css,
|
|
6854
7254
|
preloadImages: [...pluginInlined.copies.map((c) => c.to), ...inlined.copies.map((c) => c.to)],
|
|
6855
7255
|
title: opts.title,
|
|
@@ -7214,7 +7614,7 @@ async function executeValidation(args) {
|
|
|
7214
7614
|
const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
|
|
7215
7615
|
const pdfPath = typeof args.pdf === "string" ? resolve8(args.pdf) : undefined;
|
|
7216
7616
|
const inputDir = typeof args.input === "string" ? resolve8(args.input) : undefined;
|
|
7217
|
-
if (pdfPath && !
|
|
7617
|
+
if (pdfPath && !existsSync10(pdfPath)) {
|
|
7218
7618
|
throw new Error(`File not found: ${pdfPath}`);
|
|
7219
7619
|
}
|
|
7220
7620
|
const categories = resolveCategoryArg(typeof args.category === "string" ? args.category : undefined);
|
|
@@ -7245,10 +7645,15 @@ async function executeValidation(args) {
|
|
|
7245
7645
|
const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
|
|
7246
7646
|
markdownFiles = relMarkdown.map((f) => join3(manifestDir, canonicalChapterId(f)));
|
|
7247
7647
|
const relStyles = await resolveActiveStyles(manifestDir, config.styles);
|
|
7248
|
-
|
|
7648
|
+
const projectCssFiles = relStyles.map((rel) => resolve8(manifestDir, rel));
|
|
7649
|
+
let pluginStylePaths = args.pluginStylePaths;
|
|
7650
|
+
if (pluginStylePaths === undefined) {
|
|
7651
|
+
({ pluginStylePaths } = await loadPluginsWithCss(config.plugins, manifestDir, (ref, err) => log.warn(`Skipping plugin "${ref}" for validation — ${err.message}`)));
|
|
7652
|
+
}
|
|
7653
|
+
cssFiles = [...projectCssFiles, ...pluginStylePaths].filter((f) => !f.endsWith(".min.css"));
|
|
7249
7654
|
assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
|
|
7250
7655
|
const possibleHtml = join3(outDir, BOOK_HTML_FILENAME);
|
|
7251
|
-
if (
|
|
7656
|
+
if (existsSync10(possibleHtml)) {
|
|
7252
7657
|
htmlPath = possibleHtml;
|
|
7253
7658
|
}
|
|
7254
7659
|
}
|
|
@@ -7341,7 +7746,7 @@ function buildPdfSummaryLines(results) {
|
|
|
7341
7746
|
}
|
|
7342
7747
|
|
|
7343
7748
|
// src/lib/build-runner.ts
|
|
7344
|
-
import
|
|
7749
|
+
import path9 from "node:path";
|
|
7345
7750
|
import fs2 from "node:fs";
|
|
7346
7751
|
import fsp2 from "node:fs/promises";
|
|
7347
7752
|
import os2 from "node:os";
|
|
@@ -7349,9 +7754,9 @@ import { randomBytes } from "node:crypto";
|
|
|
7349
7754
|
|
|
7350
7755
|
// src/lib/build-fingerprint.ts
|
|
7351
7756
|
import * as fs from "node:fs";
|
|
7352
|
-
import { mkdir as mkdir3, readFile as
|
|
7757
|
+
import { mkdir as mkdir3, readFile as readFile21, writeFile as writeFile3 } from "node:fs/promises";
|
|
7353
7758
|
import { createHash as createHash4 } from "node:crypto";
|
|
7354
|
-
import
|
|
7759
|
+
import path7 from "node:path";
|
|
7355
7760
|
import git from "isomorphic-git";
|
|
7356
7761
|
var FINGERPRINT_FILENAME = "build-fingerprint.json";
|
|
7357
7762
|
var VERSION_TIMEOUT_MS = 4000;
|
|
@@ -7400,7 +7805,7 @@ async function getGitRevision(sourceDir) {
|
|
|
7400
7805
|
const candidateDirs = [sourceDir, process.cwd()].filter((v) => Boolean(v));
|
|
7401
7806
|
const seen = new Set;
|
|
7402
7807
|
for (const dir of candidateDirs) {
|
|
7403
|
-
const abs =
|
|
7808
|
+
const abs = path7.resolve(dir);
|
|
7404
7809
|
if (seen.has(abs))
|
|
7405
7810
|
continue;
|
|
7406
7811
|
seen.add(abs);
|
|
@@ -7436,8 +7841,8 @@ async function getEngineBundleHash() {
|
|
|
7436
7841
|
getAssetPath("engine/gutterpress-agent.js")
|
|
7437
7842
|
]);
|
|
7438
7843
|
const [viewerBytes, agentBytes] = await Promise.all([
|
|
7439
|
-
|
|
7440
|
-
|
|
7844
|
+
readFile21(viewerPath),
|
|
7845
|
+
readFile21(agentPath)
|
|
7441
7846
|
]);
|
|
7442
7847
|
const hash = createHash4("sha256");
|
|
7443
7848
|
hash.update(viewerBytes);
|
|
@@ -7485,8 +7890,8 @@ function sanitizeArgs(args) {
|
|
|
7485
7890
|
return out;
|
|
7486
7891
|
}
|
|
7487
7892
|
async function writeBuildFingerprint(input) {
|
|
7488
|
-
const outputDir =
|
|
7489
|
-
const outPath =
|
|
7893
|
+
const outputDir = path7.resolve(input.outputDir);
|
|
7894
|
+
const outPath = path7.join(outputDir, FINGERPRINT_FILENAME);
|
|
7490
7895
|
const [tools, sourceRevision] = await Promise.all([
|
|
7491
7896
|
getToolVersions(),
|
|
7492
7897
|
getGitRevision(input.sourceDir)
|
|
@@ -7502,7 +7907,7 @@ async function writeBuildFingerprint(input) {
|
|
|
7502
7907
|
iccPath: input.pdfx.iccPath,
|
|
7503
7908
|
stripAnnotations: input.pdfx.stripAnnotations
|
|
7504
7909
|
},
|
|
7505
|
-
outputDir: input.recordedOutputDir ?
|
|
7910
|
+
outputDir: input.recordedOutputDir ? path7.resolve(input.recordedOutputDir) : outputDir
|
|
7506
7911
|
},
|
|
7507
7912
|
sourceRevision,
|
|
7508
7913
|
tools
|
|
@@ -7578,7 +7983,7 @@ function computeGates(format, opts, config) {
|
|
|
7578
7983
|
}
|
|
7579
7984
|
|
|
7580
7985
|
// src/lib/build-staging.ts
|
|
7581
|
-
import
|
|
7986
|
+
import path8 from "node:path";
|
|
7582
7987
|
import os from "node:os";
|
|
7583
7988
|
import fsp from "node:fs/promises";
|
|
7584
7989
|
|
|
@@ -7761,8 +8166,8 @@ function placeholderPng(width = 640, height = 480, cell = 32) {
|
|
|
7761
8166
|
|
|
7762
8167
|
// src/lib/build-staging.ts
|
|
7763
8168
|
async function shipViewerHtml(htmlFile, outDir) {
|
|
7764
|
-
await fsp.mkdir(
|
|
7765
|
-
await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"),
|
|
8169
|
+
await fsp.mkdir(path8.join(outDir, "engine"), { recursive: true });
|
|
8170
|
+
await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path8.join(outDir, "engine/gutterpress-viewer.js"));
|
|
7766
8171
|
const tag = ` <script src="engine/gutterpress-viewer.js"></script>
|
|
7767
8172
|
`;
|
|
7768
8173
|
const html = await fsp.readFile(htmlFile, "utf-8");
|
|
@@ -7791,18 +8196,18 @@ async function stageBookAssets(options) {
|
|
|
7791
8196
|
return { missing: [...missingPlaceholders.keys()].sort() };
|
|
7792
8197
|
}
|
|
7793
8198
|
async function copyReferencedAssets(copies, outDir) {
|
|
7794
|
-
const dirs = new Set(copies.map((c) =>
|
|
8199
|
+
const dirs = new Set(copies.map((c) => path8.dirname(path8.resolve(outDir, c.to))));
|
|
7795
8200
|
await Promise.all([...dirs].map((d) => fsp.mkdir(d, { recursive: true })));
|
|
7796
8201
|
const missing = new Map;
|
|
7797
8202
|
await Promise.all(copies.map(async (c) => {
|
|
7798
|
-
const dest =
|
|
8203
|
+
const dest = path8.resolve(outDir, c.to);
|
|
7799
8204
|
try {
|
|
7800
8205
|
await fsp.copyFile(c.from, dest);
|
|
7801
8206
|
} catch (err) {
|
|
7802
8207
|
if (err?.code === "ENOENT") {
|
|
7803
8208
|
const placeholder = placeholderOutputPath(c.to);
|
|
7804
|
-
const placeholderDest =
|
|
7805
|
-
await fsp.mkdir(
|
|
8209
|
+
const placeholderDest = path8.resolve(outDir, placeholder);
|
|
8210
|
+
await fsp.mkdir(path8.dirname(placeholderDest), { recursive: true });
|
|
7806
8211
|
await fsp.writeFile(placeholderDest, placeholderPng());
|
|
7807
8212
|
missing.set(c.to, placeholder);
|
|
7808
8213
|
return;
|
|
@@ -7813,7 +8218,7 @@ async function copyReferencedAssets(copies, outDir) {
|
|
|
7813
8218
|
return missing;
|
|
7814
8219
|
}
|
|
7815
8220
|
async function createStageRoot() {
|
|
7816
|
-
return fsp.mkdtemp(
|
|
8221
|
+
return fsp.mkdtemp(path8.join(os.tmpdir(), "gutterpress-stage-"));
|
|
7817
8222
|
}
|
|
7818
8223
|
|
|
7819
8224
|
// src/lib/build-runner.ts
|
|
@@ -7821,18 +8226,18 @@ function splitOutPath(outArg, format) {
|
|
|
7821
8226
|
if (typeof outArg !== "string" || outArg.length === 0) {
|
|
7822
8227
|
return { outDir: undefined, pdfFileOverride: null };
|
|
7823
8228
|
}
|
|
7824
|
-
const resolved =
|
|
8229
|
+
const resolved = path9.resolve(outArg);
|
|
7825
8230
|
if ((format === "pdf" || format === "pdfx") && resolved.toLowerCase().endsWith(".pdf")) {
|
|
7826
|
-
return { outDir:
|
|
8231
|
+
return { outDir: path9.dirname(resolved), pdfFileOverride: resolved };
|
|
7827
8232
|
}
|
|
7828
8233
|
return { outDir: resolved, pdfFileOverride: null };
|
|
7829
8234
|
}
|
|
7830
8235
|
function layoutWarningKey(file, line, message) {
|
|
7831
|
-
return `${
|
|
8236
|
+
return `${path9.resolve(file)}\x00${line ?? 0}\x00${message}`;
|
|
7832
8237
|
}
|
|
7833
8238
|
async function resolveBuildContext(opts) {
|
|
7834
8239
|
const { format } = opts;
|
|
7835
|
-
const inputDir =
|
|
8240
|
+
const inputDir = path9.resolve(opts.inputDir);
|
|
7836
8241
|
const { manifest, manifestDir, manifestPath } = await loadManifestWithPath(opts.manifestPath ?? inputDir, { explicit: opts.manifestPath !== undefined });
|
|
7837
8242
|
if (manifestPath === null) {
|
|
7838
8243
|
throw new UsageError(`No project manifest found in ${inputDir}. Looked for ${MANIFEST_FILENAMES.join(" or ")}. ` + "Run from your project folder or pass that folder with `gutterpress build <project-dir>`. " + "For a custom manifest filename, pass `--manifest <path>`.");
|
|
@@ -7847,10 +8252,10 @@ async function resolveBuildContext(opts) {
|
|
|
7847
8252
|
pdfx: pdfxConfigOverride,
|
|
7848
8253
|
engine: opts.engine
|
|
7849
8254
|
}, manifest);
|
|
7850
|
-
const target = opts.pdfFileOverride ? { kind: "file", file:
|
|
7851
|
-
const outDir = target.kind === "file" ?
|
|
8255
|
+
const target = opts.pdfFileOverride ? { kind: "file", file: path9.resolve(opts.pdfFileOverride) } : opts.outDir ? { kind: "directory", dir: opts.outDir } : { kind: "project", dir: resolveOutputDir(manifestDir, config.title) };
|
|
8256
|
+
const outDir = target.kind === "file" ? path9.dirname(target.file) : target.dir;
|
|
7852
8257
|
const gates = computeGates(format, opts, config);
|
|
7853
|
-
const workDir = target.kind === "project" ?
|
|
8258
|
+
const workDir = target.kind === "project" ? path9.join(path9.dirname(target.dir), `.${path9.basename(target.dir)}-build-${randomBytes(6).toString("hex")}`) : path9.join(os2.tmpdir(), `gutterpress-build-${randomBytes(6).toString("hex")}`);
|
|
7854
8259
|
return {
|
|
7855
8260
|
opts,
|
|
7856
8261
|
format,
|
|
@@ -7862,15 +8267,32 @@ async function resolveBuildContext(opts) {
|
|
|
7862
8267
|
manifestDir,
|
|
7863
8268
|
config,
|
|
7864
8269
|
gates,
|
|
7865
|
-
prevalidatedLayoutWarningKeys: new Set
|
|
8270
|
+
prevalidatedLayoutWarningKeys: new Set,
|
|
8271
|
+
plugins: null
|
|
7866
8272
|
};
|
|
7867
8273
|
}
|
|
8274
|
+
async function loadBuildPlugins(ctx) {
|
|
8275
|
+
if (ctx.plugins)
|
|
8276
|
+
return ctx.plugins;
|
|
8277
|
+
const { config, renderDir } = ctx;
|
|
8278
|
+
if (config.plugins.length > 0) {
|
|
8279
|
+
log.info(`Loading ${config.plugins.length} plugin(s)...`);
|
|
8280
|
+
}
|
|
8281
|
+
const loaded = await loadPluginsWithCss(config.plugins, renderDir);
|
|
8282
|
+
if (loaded.plugins && loaded.plugins.length > 0) {
|
|
8283
|
+
log.success(`Loaded ${loaded.plugins.length} plugin(s)`);
|
|
8284
|
+
}
|
|
8285
|
+
ctx.plugins = loaded;
|
|
8286
|
+
return loaded;
|
|
8287
|
+
}
|
|
7868
8288
|
async function runQualityGates(ctx) {
|
|
7869
8289
|
const { gates, opts, renderDir } = ctx;
|
|
8290
|
+
const pluginStylePaths = gates.lint || gates.preValidate ? (await loadBuildPlugins(ctx)).pluginStylePaths : undefined;
|
|
7870
8291
|
if (gates.lint) {
|
|
7871
8292
|
log.info("Lint: CSS print-safety");
|
|
7872
8293
|
const lintResult = await runLint({
|
|
7873
|
-
manifest: opts.manifestPath ?? renderDir
|
|
8294
|
+
manifest: opts.manifestPath ?? renderDir,
|
|
8295
|
+
pluginStylePaths
|
|
7874
8296
|
});
|
|
7875
8297
|
if (!lintResult.ok) {
|
|
7876
8298
|
throw new BuildError("CSS lint failed", 2);
|
|
@@ -7881,7 +8303,8 @@ async function runQualityGates(ctx) {
|
|
|
7881
8303
|
const result = await executeAndReport({
|
|
7882
8304
|
input: renderDir,
|
|
7883
8305
|
phase: "pre-build",
|
|
7884
|
-
manifest: opts.manifestPath
|
|
8306
|
+
manifest: opts.manifestPath,
|
|
8307
|
+
pluginStylePaths
|
|
7885
8308
|
}, "text");
|
|
7886
8309
|
for (const finding2 of result.execution.report.results) {
|
|
7887
8310
|
if (finding2.checkId === "source.markdown.layout-markers" && finding2.file && finding2.code !== "inspect-failed") {
|
|
@@ -7900,13 +8323,7 @@ async function renderBook(ctx) {
|
|
|
7900
8323
|
} else {
|
|
7901
8324
|
log.info("Using all .md files in alphabetical order");
|
|
7902
8325
|
}
|
|
7903
|
-
|
|
7904
|
-
log.info(`Loading ${config.plugins.length} plugin(s)...`);
|
|
7905
|
-
}
|
|
7906
|
-
const { plugins, pluginCss, pluginStylePaths } = await loadPluginsWithCss(config.plugins, renderDir);
|
|
7907
|
-
if (plugins && plugins.length > 0) {
|
|
7908
|
-
log.success(`Loaded ${plugins.length} plugin(s)`);
|
|
7909
|
-
}
|
|
8326
|
+
const { plugins, pluginCss, pluginStylePaths } = await loadBuildPlugins(ctx);
|
|
7910
8327
|
const imageRefs = [];
|
|
7911
8328
|
const cssAssets = [];
|
|
7912
8329
|
const htmlFile = await renderChaptersToFile(renderDir, workDir, {
|
|
@@ -7918,7 +8335,7 @@ async function renderBook(ctx) {
|
|
|
7918
8335
|
pluginStylePaths,
|
|
7919
8336
|
onChapterWarnings: (file, warnings) => {
|
|
7920
8337
|
for (const w of warnings) {
|
|
7921
|
-
const key = layoutWarningKey(
|
|
8338
|
+
const key = layoutWarningKey(path9.resolve(renderDir, file), w.line, w.message);
|
|
7922
8339
|
if (!ctx.prevalidatedLayoutWarningKeys.has(key)) {
|
|
7923
8340
|
log.warn(` ${file}, line ${w.line}: ${w.message}`);
|
|
7924
8341
|
}
|
|
@@ -7956,9 +8373,9 @@ async function renderBook(ctx) {
|
|
|
7956
8373
|
return htmlFile;
|
|
7957
8374
|
}
|
|
7958
8375
|
async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
|
|
7959
|
-
const iccCandidates =
|
|
8376
|
+
const iccCandidates = path9.isAbsolute(icc) ? [icc] : [path9.resolve(manifestDir, icc), path9.resolve(icc)];
|
|
7960
8377
|
let effectiveIccPath = iccCandidates.find((candidate) => fs2.existsSync(candidate)) ?? null;
|
|
7961
|
-
if (!effectiveIccPath && !explicitIccPath &&
|
|
8378
|
+
if (!effectiveIccPath && !explicitIccPath && path9.basename(icc) === "CGATS21_CRPC1.icc") {
|
|
7962
8379
|
effectiveIccPath = await getAssetPath("profiles/CGATS21_CRPC1.icc");
|
|
7963
8380
|
}
|
|
7964
8381
|
if (!effectiveIccPath) {
|
|
@@ -7974,13 +8391,13 @@ async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName
|
|
|
7974
8391
|
});
|
|
7975
8392
|
await publishBuild(ctx, artifactName2);
|
|
7976
8393
|
const delivered = ctx.target.kind !== "file";
|
|
7977
|
-
const fingerprintPath = delivered ?
|
|
8394
|
+
const fingerprintPath = delivered ? path9.join(ctx.outDir, path9.basename(workFingerprint)) : null;
|
|
7978
8395
|
log.success(wroteMessage);
|
|
7979
8396
|
if (fingerprintPath)
|
|
7980
8397
|
log.info(`Fingerprint: ${fingerprintPath}`);
|
|
7981
8398
|
return {
|
|
7982
8399
|
outDir: ctx.outDir,
|
|
7983
|
-
htmlPath: delivered ?
|
|
8400
|
+
htmlPath: delivered ? path9.join(ctx.outDir, path9.relative(ctx.workDir, paths.htmlPath)) : null,
|
|
7984
8401
|
pdfPath: paths.pdfPath,
|
|
7985
8402
|
fingerprintPath,
|
|
7986
8403
|
diagnostics
|
|
@@ -7991,8 +8408,8 @@ async function publishBuild(ctx, artifactName2) {
|
|
|
7991
8408
|
if (target.kind === "file") {
|
|
7992
8409
|
if (!artifactName2)
|
|
7993
8410
|
throw new BuildError("No artifact to write for this format", 1);
|
|
7994
|
-
await fsp2.mkdir(
|
|
7995
|
-
await fsp2.copyFile(
|
|
8411
|
+
await fsp2.mkdir(path9.dirname(target.file), { recursive: true });
|
|
8412
|
+
await fsp2.copyFile(path9.join(workDir, artifactName2), target.file);
|
|
7996
8413
|
return;
|
|
7997
8414
|
}
|
|
7998
8415
|
if (target.kind === "directory") {
|
|
@@ -8001,12 +8418,12 @@ async function publishBuild(ctx, artifactName2) {
|
|
|
8001
8418
|
return;
|
|
8002
8419
|
}
|
|
8003
8420
|
for (const name of await listPdfs(target.dir)) {
|
|
8004
|
-
if (!fs2.existsSync(
|
|
8005
|
-
await fsp2.rename(
|
|
8421
|
+
if (!fs2.existsSync(path9.join(workDir, name))) {
|
|
8422
|
+
await fsp2.rename(path9.join(target.dir, name), path9.join(workDir, name));
|
|
8006
8423
|
}
|
|
8007
8424
|
}
|
|
8008
8425
|
await fsp2.rm(target.dir, { recursive: true, force: true });
|
|
8009
|
-
await fsp2.mkdir(
|
|
8426
|
+
await fsp2.mkdir(path9.dirname(target.dir), { recursive: true });
|
|
8010
8427
|
await fsp2.rename(workDir, target.dir);
|
|
8011
8428
|
}
|
|
8012
8429
|
async function listPdfs(dir) {
|
|
@@ -8022,7 +8439,7 @@ class HtmlOutput {
|
|
|
8022
8439
|
const { workDir, outDir, inputDir, config, opts } = ctx;
|
|
8023
8440
|
log.info("Shipping self-contained HTML + viewer bundle (native engine)");
|
|
8024
8441
|
await shipViewerHtml(htmlFile, workDir);
|
|
8025
|
-
await fsp2.writeFile(
|
|
8442
|
+
await fsp2.writeFile(path9.join(workDir, "index.html"), `<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=book.html"><title>Gutterpress</title></head><body></body></html>
|
|
8026
8443
|
`, "utf-8");
|
|
8027
8444
|
return finalizeBuild(ctx, {
|
|
8028
8445
|
command: "build",
|
|
@@ -8035,7 +8452,7 @@ class HtmlOutput {
|
|
|
8035
8452
|
iccPath: null,
|
|
8036
8453
|
stripAnnotations: null
|
|
8037
8454
|
}
|
|
8038
|
-
}, `Wrote: ${
|
|
8455
|
+
}, `Wrote: ${path9.join(outDir, BOOK_HTML)}`, { htmlPath: htmlFile, pdfPath: null });
|
|
8039
8456
|
}
|
|
8040
8457
|
}
|
|
8041
8458
|
|
|
@@ -8044,14 +8461,14 @@ class PdfOutput {
|
|
|
8044
8461
|
const { workDir, outDir, inputDir, config, opts, format, manifestDir, gates } = ctx;
|
|
8045
8462
|
const pdfxMode = format === "pdfx" ? opts.pdfxFlavor ?? config.pdfx.flavor : undefined;
|
|
8046
8463
|
const pdfName = artifactName(config.title, pdfxMode ? "pdfx" : "pdf");
|
|
8047
|
-
const pdfFile =
|
|
8048
|
-
const reportedPdf = ctx.target.kind === "file" ? ctx.target.file :
|
|
8464
|
+
const pdfFile = path9.join(workDir, pdfName);
|
|
8465
|
+
const reportedPdf = ctx.target.kind === "file" ? ctx.target.file : path9.join(outDir, pdfName);
|
|
8049
8466
|
const stage = await createStageRoot();
|
|
8050
8467
|
try {
|
|
8051
|
-
const rawPdf = pdfxMode ?
|
|
8052
|
-
await fsp2.mkdir(
|
|
8468
|
+
const rawPdf = pdfxMode ? path9.join(stage, "raw.pdf") : path9.resolve(pdfFile);
|
|
8469
|
+
await fsp2.mkdir(path9.dirname(path9.resolve(pdfFile)), { recursive: true });
|
|
8053
8470
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
8054
|
-
const { buildNativePdf } = await import("./engine-
|
|
8471
|
+
const { buildNativePdf } = await import("./engine-daad33qn.js");
|
|
8055
8472
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
8056
8473
|
title: config.title,
|
|
8057
8474
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -8082,7 +8499,7 @@ class PdfOutput {
|
|
|
8082
8499
|
await stripAnnotations(rawPdf, stage);
|
|
8083
8500
|
}
|
|
8084
8501
|
log.info(`Converting to CMYK PDF/X (${pdfxMode})`);
|
|
8085
|
-
await convertToPdfxCmyk(rawPdf,
|
|
8502
|
+
await convertToPdfxCmyk(rawPdf, path9.resolve(pdfFile), {
|
|
8086
8503
|
iccPath: effectiveIccPath,
|
|
8087
8504
|
pdfx: pdfxMode,
|
|
8088
8505
|
title: config.title,
|
|
@@ -8171,10 +8588,10 @@ function openPath(filePath) {
|
|
|
8171
8588
|
}
|
|
8172
8589
|
|
|
8173
8590
|
// src/preview/lifecycle.ts
|
|
8174
|
-
import
|
|
8591
|
+
import path11 from "path";
|
|
8175
8592
|
import { tmpdir as tmpdir2 } from "os";
|
|
8176
8593
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
8177
|
-
import { readdir as readdir4, readFile as
|
|
8594
|
+
import { readdir as readdir4, readFile as readFile22, writeFile as writeFile4 } from "node:fs/promises";
|
|
8178
8595
|
|
|
8179
8596
|
// src/utils/file-utils.ts
|
|
8180
8597
|
import { promises as fs3 } from "fs";
|
|
@@ -8196,7 +8613,7 @@ async function mkdir4(dirPath) {
|
|
|
8196
8613
|
// src/preview/file-watcher.ts
|
|
8197
8614
|
import { watch } from "chokidar";
|
|
8198
8615
|
import fsp3 from "node:fs/promises";
|
|
8199
|
-
import
|
|
8616
|
+
import path10 from "path";
|
|
8200
8617
|
var EMPTY_BOOK_HTML = `<!doctype html>
|
|
8201
8618
|
<html lang="en">
|
|
8202
8619
|
<head>
|
|
@@ -8250,7 +8667,7 @@ function injectPreviewScripts(html, pageIsolateChapters) {
|
|
|
8250
8667
|
async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
8251
8668
|
if (!inputPath) {
|
|
8252
8669
|
cssAssets.clear();
|
|
8253
|
-
await fsp3.writeFile(
|
|
8670
|
+
await fsp3.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
|
|
8254
8671
|
return;
|
|
8255
8672
|
}
|
|
8256
8673
|
const nextAssets = new Map;
|
|
@@ -8265,7 +8682,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
|
8265
8682
|
cssAssets.clear();
|
|
8266
8683
|
for (const [to, from] of nextAssets)
|
|
8267
8684
|
cssAssets.set(to, from);
|
|
8268
|
-
await fsp3.writeFile(
|
|
8685
|
+
await fsp3.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, false), "utf-8");
|
|
8269
8686
|
}
|
|
8270
8687
|
async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
8271
8688
|
const html = await renderPreviewBook(inputPath, config, {
|
|
@@ -8277,12 +8694,12 @@ async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
|
8277
8694
|
function describeChanges(changes, inputResolved) {
|
|
8278
8695
|
const files = [];
|
|
8279
8696
|
for (const [changedPath, event] of changes) {
|
|
8280
|
-
const relative2 =
|
|
8281
|
-
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${
|
|
8697
|
+
const relative2 = path10.relative(inputResolved, path10.resolve(changedPath));
|
|
8698
|
+
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path10.sep}`) || path10.isAbsolute(relative2))
|
|
8282
8699
|
continue;
|
|
8283
8700
|
files.push({
|
|
8284
8701
|
relativePath: canonicalChapterId(relative2),
|
|
8285
|
-
ext:
|
|
8702
|
+
ext: path10.extname(changedPath).toLowerCase(),
|
|
8286
8703
|
event
|
|
8287
8704
|
});
|
|
8288
8705
|
}
|
|
@@ -8334,23 +8751,23 @@ function isIgnoredWatchPath(candidatePath, projectRoot) {
|
|
|
8334
8751
|
return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
|
|
8335
8752
|
}
|
|
8336
8753
|
function isExternalWatchCandidate(candidatePath, targets) {
|
|
8337
|
-
const candidate =
|
|
8754
|
+
const candidate = path10.resolve(candidatePath);
|
|
8338
8755
|
for (const rawTarget of targets) {
|
|
8339
|
-
const target =
|
|
8340
|
-
const rel =
|
|
8341
|
-
if (rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
8756
|
+
const target = path10.resolve(rawTarget);
|
|
8757
|
+
const rel = path10.relative(candidate, target);
|
|
8758
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel))
|
|
8342
8759
|
return true;
|
|
8343
8760
|
}
|
|
8344
8761
|
return false;
|
|
8345
8762
|
}
|
|
8346
8763
|
async function nearestExistingDirectory(start) {
|
|
8347
|
-
let current =
|
|
8764
|
+
let current = path10.resolve(start);
|
|
8348
8765
|
while (true) {
|
|
8349
8766
|
try {
|
|
8350
8767
|
if ((await fsp3.stat(current)).isDirectory())
|
|
8351
8768
|
return current;
|
|
8352
8769
|
} catch {}
|
|
8353
|
-
const parent =
|
|
8770
|
+
const parent = path10.dirname(current);
|
|
8354
8771
|
if (parent === current)
|
|
8355
8772
|
return current;
|
|
8356
8773
|
current = parent;
|
|
@@ -8359,12 +8776,12 @@ async function nearestExistingDirectory(start) {
|
|
|
8359
8776
|
async function externalWatchRoots(targets) {
|
|
8360
8777
|
const roots = new Set;
|
|
8361
8778
|
for (const target of targets) {
|
|
8362
|
-
roots.add(await nearestExistingDirectory(
|
|
8779
|
+
roots.add(await nearestExistingDirectory(path10.dirname(path10.resolve(target))));
|
|
8363
8780
|
}
|
|
8364
8781
|
return [...roots];
|
|
8365
8782
|
}
|
|
8366
8783
|
async function externalWatchTargets(projectDir, config) {
|
|
8367
|
-
const root =
|
|
8784
|
+
const root = path10.resolve(projectDir);
|
|
8368
8785
|
let canonicalRoot = root;
|
|
8369
8786
|
try {
|
|
8370
8787
|
canonicalRoot = await fsp3.realpath(root);
|
|
@@ -8372,21 +8789,21 @@ async function externalWatchTargets(projectDir, config) {
|
|
|
8372
8789
|
const styles = await resolveActiveStyles(root, config.styles);
|
|
8373
8790
|
const candidates = [
|
|
8374
8791
|
...await collectStyleDependencies(root, styles),
|
|
8375
|
-
...(config.plugins ?? []).map((p) => p.path).filter((p) => !!p).map((p) =>
|
|
8792
|
+
...(config.plugins ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path10.resolve(root, p))
|
|
8376
8793
|
];
|
|
8377
8794
|
const external = new Set;
|
|
8378
8795
|
for (const abs of candidates) {
|
|
8379
|
-
const authoredPath =
|
|
8380
|
-
const authoredRel =
|
|
8381
|
-
if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${
|
|
8796
|
+
const authoredPath = path10.resolve(abs);
|
|
8797
|
+
const authoredRel = path10.relative(root, authoredPath);
|
|
8798
|
+
if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path10.sep}`) || path10.isAbsolute(authoredRel))) {
|
|
8382
8799
|
external.add(authoredPath);
|
|
8383
8800
|
}
|
|
8384
8801
|
let canonicalPath = authoredPath;
|
|
8385
8802
|
try {
|
|
8386
8803
|
canonicalPath = await fsp3.realpath(canonicalPath);
|
|
8387
8804
|
} catch {}
|
|
8388
|
-
const rel =
|
|
8389
|
-
if (rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
8805
|
+
const rel = path10.relative(canonicalRoot, canonicalPath);
|
|
8806
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel))
|
|
8390
8807
|
continue;
|
|
8391
8808
|
external.add(canonicalPath);
|
|
8392
8809
|
}
|
|
@@ -8413,7 +8830,7 @@ async function addAndAwaitWatch(watcher, roots) {
|
|
|
8413
8830
|
}
|
|
8414
8831
|
}
|
|
8415
8832
|
function createFileWatcher(state) {
|
|
8416
|
-
const inputResolved =
|
|
8833
|
+
const inputResolved = path10.resolve(state.currentInputPath);
|
|
8417
8834
|
const watcher = watch(inputResolved, {
|
|
8418
8835
|
persistent: true,
|
|
8419
8836
|
ignoreInitial: true,
|
|
@@ -8453,7 +8870,7 @@ function createFileWatcher(state) {
|
|
|
8453
8870
|
async function recordChange(event, filePath) {
|
|
8454
8871
|
if (closed)
|
|
8455
8872
|
return;
|
|
8456
|
-
const target =
|
|
8873
|
+
const target = path10.resolve(filePath);
|
|
8457
8874
|
const settled = settledWrites.get(target);
|
|
8458
8875
|
if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
|
|
8459
8876
|
const diskContent = await fsp3.readFile(target, "utf8").catch(() => null);
|
|
@@ -8474,7 +8891,7 @@ function createFileWatcher(state) {
|
|
|
8474
8891
|
recordChange(event, filePath);
|
|
8475
8892
|
});
|
|
8476
8893
|
externalWatcher.on("all", (event, filePath) => {
|
|
8477
|
-
const target =
|
|
8894
|
+
const target = path10.resolve(filePath);
|
|
8478
8895
|
if (!desiredExternals.has(target))
|
|
8479
8896
|
return;
|
|
8480
8897
|
if (event === "add" && suppressInitialExternalAdds.delete(target))
|
|
@@ -8482,8 +8899,8 @@ function createFileWatcher(state) {
|
|
|
8482
8899
|
recordChange(event, target);
|
|
8483
8900
|
});
|
|
8484
8901
|
function isWatchedSource(target) {
|
|
8485
|
-
const rel =
|
|
8486
|
-
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
8902
|
+
const rel = path10.relative(inputResolved, target);
|
|
8903
|
+
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel);
|
|
8487
8904
|
if (inProject)
|
|
8488
8905
|
return !isIgnoredWatchPath(target, inputResolved);
|
|
8489
8906
|
return desiredExternals.has(target);
|
|
@@ -8508,7 +8925,7 @@ function createFileWatcher(state) {
|
|
|
8508
8925
|
state.notifySettledWrite = (filePath, writtenContent) => {
|
|
8509
8926
|
if (closed)
|
|
8510
8927
|
return;
|
|
8511
|
-
const target =
|
|
8928
|
+
const target = path10.resolve(filePath);
|
|
8512
8929
|
if (isWatchedSource(target)) {
|
|
8513
8930
|
acceptSettledWrite(target, writtenContent);
|
|
8514
8931
|
return;
|
|
@@ -8529,7 +8946,7 @@ function createFileWatcher(state) {
|
|
|
8529
8946
|
return;
|
|
8530
8947
|
let nextTargets;
|
|
8531
8948
|
try {
|
|
8532
|
-
nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) =>
|
|
8949
|
+
nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path10.resolve(target)));
|
|
8533
8950
|
} catch (err) {
|
|
8534
8951
|
debug(`Could not resolve external watch targets: ${err}`);
|
|
8535
8952
|
return;
|
|
@@ -8661,7 +9078,7 @@ async function stopFileWatcher(state) {
|
|
|
8661
9078
|
}
|
|
8662
9079
|
|
|
8663
9080
|
// src/preview/lifecycle.ts
|
|
8664
|
-
var TEMP_DIR_BASE =
|
|
9081
|
+
var TEMP_DIR_BASE = path11.join(tmpdir2(), "gutterpress-preview");
|
|
8665
9082
|
var PID_FILE_NAME = ".gutterpress.pid";
|
|
8666
9083
|
function isProcessAlive(pid) {
|
|
8667
9084
|
if (!Number.isFinite(pid) || pid <= 0)
|
|
@@ -8681,10 +9098,10 @@ async function cleanupOrphanTempDirs() {
|
|
|
8681
9098
|
return;
|
|
8682
9099
|
}
|
|
8683
9100
|
for (const entry of entries) {
|
|
8684
|
-
const dir =
|
|
8685
|
-
const pidFile =
|
|
9101
|
+
const dir = path11.join(TEMP_DIR_BASE, entry);
|
|
9102
|
+
const pidFile = path11.join(dir, PID_FILE_NAME);
|
|
8686
9103
|
try {
|
|
8687
|
-
const raw = await
|
|
9104
|
+
const raw = await readFile22(pidFile, "utf8");
|
|
8688
9105
|
const pid = parseInt(raw.trim(), 10);
|
|
8689
9106
|
if (!isProcessAlive(pid)) {
|
|
8690
9107
|
debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
|
|
@@ -8696,10 +9113,10 @@ async function cleanupOrphanTempDirs() {
|
|
|
8696
9113
|
async function initializePreviewDirectories() {
|
|
8697
9114
|
await cleanupOrphanTempDirs();
|
|
8698
9115
|
const tempDirSuffix = randomBytes2(8).toString("hex");
|
|
8699
|
-
const tempDir =
|
|
9116
|
+
const tempDir = path11.join(TEMP_DIR_BASE, tempDirSuffix);
|
|
8700
9117
|
await mkdir4(tempDir);
|
|
8701
9118
|
debug(`Created temporary directory: ${tempDir}`);
|
|
8702
|
-
await writeFile4(
|
|
9119
|
+
await writeFile4(path11.join(tempDir, PID_FILE_NAME), `${process.pid}
|
|
8703
9120
|
`, "utf8");
|
|
8704
9121
|
return tempDir;
|
|
8705
9122
|
}
|
|
@@ -8790,12 +9207,12 @@ function createServerState(inputPath, tempDir, config, options) {
|
|
|
8790
9207
|
import http from "node:http";
|
|
8791
9208
|
import net from "node:net";
|
|
8792
9209
|
import { createHash as createHash6, randomUUID } from "node:crypto";
|
|
8793
|
-
import { readFile as
|
|
8794
|
-
import
|
|
9210
|
+
import { readFile as readFile23, stat as stat4 } from "node:fs/promises";
|
|
9211
|
+
import path13 from "path";
|
|
8795
9212
|
import { WebSocket, WebSocketServer } from "ws";
|
|
8796
9213
|
|
|
8797
9214
|
// src/lib/static-serve.ts
|
|
8798
|
-
import
|
|
9215
|
+
import path12 from "node:path";
|
|
8799
9216
|
var STATIC_MIME = {
|
|
8800
9217
|
".html": "text/html; charset=utf-8",
|
|
8801
9218
|
".htm": "text/html; charset=utf-8",
|
|
@@ -8817,9 +9234,9 @@ var STATIC_MIME = {
|
|
|
8817
9234
|
".otf": "font/otf"
|
|
8818
9235
|
};
|
|
8819
9236
|
function resolveWithinRoot(relPath, root) {
|
|
8820
|
-
const resolvedRoot =
|
|
8821
|
-
const candidate =
|
|
8822
|
-
if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot +
|
|
9237
|
+
const resolvedRoot = path12.resolve(root);
|
|
9238
|
+
const candidate = path12.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
|
|
9239
|
+
if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path12.sep)) {
|
|
8823
9240
|
return null;
|
|
8824
9241
|
}
|
|
8825
9242
|
return candidate;
|
|
@@ -9072,7 +9489,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
9072
9489
|
try {
|
|
9073
9490
|
const s = await stat4(filePath);
|
|
9074
9491
|
if (s.isDirectory())
|
|
9075
|
-
filePath =
|
|
9492
|
+
filePath = path13.join(filePath, "index.html");
|
|
9076
9493
|
} catch {
|
|
9077
9494
|
res.writeHead(404);
|
|
9078
9495
|
res.end("Not Found");
|
|
@@ -9080,7 +9497,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
9080
9497
|
}
|
|
9081
9498
|
let data;
|
|
9082
9499
|
try {
|
|
9083
|
-
data = await
|
|
9500
|
+
data = await readFile23(filePath);
|
|
9084
9501
|
} catch {
|
|
9085
9502
|
res.writeHead(404);
|
|
9086
9503
|
res.end("Not Found");
|
|
@@ -9093,7 +9510,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
9093
9510
|
res.end(withHmr);
|
|
9094
9511
|
return;
|
|
9095
9512
|
}
|
|
9096
|
-
const ct = STATIC_MIME[
|
|
9513
|
+
const ct = STATIC_MIME[path13.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
9097
9514
|
res.writeHead(200, { "Content-Type": ct, "Cache-Control": cacheControl, ...extraHeaders });
|
|
9098
9515
|
res.end(data);
|
|
9099
9516
|
}
|
|
@@ -9103,7 +9520,7 @@ var EMBEDDED_CACHE_CONTROL = "public, no-cache";
|
|
|
9103
9520
|
var EMBEDDED_ETAG = `"gutterpress-${PACKAGE_VERSION}"`;
|
|
9104
9521
|
var PROJECT_ASSET_CACHE_CONTROL = "private, no-cache";
|
|
9105
9522
|
async function serveRevalidatedStatic(req, absPath, res, validators) {
|
|
9106
|
-
const extension =
|
|
9523
|
+
const extension = path13.extname(absPath).toLowerCase();
|
|
9107
9524
|
if (extension === ".html" || extension === ".htm") {
|
|
9108
9525
|
await serveStatic(absPath, res);
|
|
9109
9526
|
return;
|
|
@@ -9130,7 +9547,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
|
|
|
9130
9547
|
let data;
|
|
9131
9548
|
if (!validator || validator.stamp !== stamp) {
|
|
9132
9549
|
try {
|
|
9133
|
-
data = await
|
|
9550
|
+
data = await readFile23(absPath);
|
|
9134
9551
|
} catch {
|
|
9135
9552
|
await serveStatic(absPath, res);
|
|
9136
9553
|
return;
|
|
@@ -9269,7 +9686,7 @@ async function createPreviewServer(state, port) {
|
|
|
9269
9686
|
const file = rawFile ? canonicalChapterId(rawFile) : "";
|
|
9270
9687
|
const revision = Number(url.searchParams.get("revision"));
|
|
9271
9688
|
const configuredFiles = state.config.source?.files;
|
|
9272
|
-
if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision ||
|
|
9689
|
+
if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision || path13.extname(file).toLowerCase() !== ".md" || hasDotSegment(file) || !resolveWithinRoot(file, state.currentInputPath) || Array.isArray(configuredFiles) && configuredFiles.length > 0 && !configuredFiles.some((configured) => canonicalChapterId(configured) === file)) {
|
|
9273
9690
|
res.writeHead(400);
|
|
9274
9691
|
res.end("Bad Request");
|
|
9275
9692
|
return;
|
|
@@ -9469,9 +9886,9 @@ async function startPreviewServer(options) {
|
|
|
9469
9886
|
}
|
|
9470
9887
|
|
|
9471
9888
|
// src/lib/theme-manager.ts
|
|
9472
|
-
import { cp as cp2, mkdir as mkdir6, readFile as
|
|
9473
|
-
import { existsSync as
|
|
9474
|
-
import
|
|
9889
|
+
import { cp as cp2, mkdir as mkdir6, readFile as readFile25, readdir as readdir5, stat as stat5, writeFile as writeFile5, rm as rm3 } from "node:fs/promises";
|
|
9890
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
9891
|
+
import path15 from "node:path";
|
|
9475
9892
|
import { isSeq as isSeq2, Scalar } from "yaml";
|
|
9476
9893
|
|
|
9477
9894
|
// src/lib/fetch-timeout.ts
|
|
@@ -9508,20 +9925,20 @@ async function withFetchTimeout(options, run) {
|
|
|
9508
9925
|
}
|
|
9509
9926
|
|
|
9510
9927
|
// src/lib/manifest-doc.ts
|
|
9511
|
-
import { existsSync as
|
|
9928
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
9512
9929
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
9513
|
-
import { mkdir as mkdir5, open, readFile as
|
|
9514
|
-
import
|
|
9930
|
+
import { mkdir as mkdir5, open, readFile as readFile24, rename, rm as rm2 } from "node:fs/promises";
|
|
9931
|
+
import path14 from "node:path";
|
|
9515
9932
|
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
9516
9933
|
function resolveManifestPath(projectDir) {
|
|
9517
|
-
const existing = MANIFEST_FILENAMES.find((name) =>
|
|
9518
|
-
return
|
|
9934
|
+
const existing = MANIFEST_FILENAMES.find((name) => existsSync11(path14.join(projectDir, name)));
|
|
9935
|
+
return path14.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
|
|
9519
9936
|
}
|
|
9520
9937
|
async function loadManifestDoc(projectDir) {
|
|
9521
9938
|
const file = resolveManifestPath(projectDir);
|
|
9522
9939
|
let text = "";
|
|
9523
9940
|
try {
|
|
9524
|
-
text = await
|
|
9941
|
+
text = await readFile24(file, "utf8");
|
|
9525
9942
|
} catch (error2) {
|
|
9526
9943
|
if (error2.code !== "ENOENT")
|
|
9527
9944
|
throw error2;
|
|
@@ -9529,9 +9946,9 @@ async function loadManifestDoc(projectDir) {
|
|
|
9529
9946
|
return { doc: parseDocument(text), file };
|
|
9530
9947
|
}
|
|
9531
9948
|
async function writeManifestDoc(file, doc) {
|
|
9532
|
-
const dir =
|
|
9949
|
+
const dir = path14.dirname(file);
|
|
9533
9950
|
await mkdir5(dir, { recursive: true });
|
|
9534
|
-
const temporary =
|
|
9951
|
+
const temporary = path14.join(dir, `.${path14.basename(file)}.tmp-${process.pid}-${randomUUID2()}`);
|
|
9535
9952
|
try {
|
|
9536
9953
|
const handle = await open(temporary, "wx", 438);
|
|
9537
9954
|
try {
|
|
@@ -9597,29 +10014,13 @@ var BUILT_IN_FALLBACK_META = {
|
|
|
9597
10014
|
description: "Clean sans-serif manual with clear hierarchy and code styling."
|
|
9598
10015
|
}
|
|
9599
10016
|
};
|
|
9600
|
-
|
|
9601
|
-
try {
|
|
9602
|
-
const text = await readFile24(jsonPath, "utf8");
|
|
9603
|
-
const parsed = JSON.parse(text);
|
|
9604
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
9605
|
-
} catch {
|
|
9606
|
-
return {};
|
|
9607
|
-
}
|
|
9608
|
-
}
|
|
10017
|
+
var readThemeMeta = readExtensionMeta;
|
|
9609
10018
|
function themeStyleList(meta) {
|
|
9610
|
-
const declared =
|
|
10019
|
+
const declared = extensionStyleList(meta);
|
|
9611
10020
|
return declared.length > 0 ? declared : ["theme.css"];
|
|
9612
10021
|
}
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
if (path14.isAbsolute(rel) || rel.split(/[\\/]/).includes("..")) {
|
|
9616
|
-
throw new Error(`theme.json declares stylesheet "${rel}" outside the theme folder; a theme must be self-contained.`);
|
|
9617
|
-
}
|
|
9618
|
-
}
|
|
9619
|
-
}
|
|
9620
|
-
function themeEngineStyleList(meta) {
|
|
9621
|
-
return Array.isArray(meta.engineStyles?.native) ? meta.engineStyles.native.filter((s) => typeof s === "string" && s.trim().length > 0) : [];
|
|
9622
|
-
}
|
|
10022
|
+
var assertThemeSheetsContained = assertExtensionContained;
|
|
10023
|
+
var themeEngineStyleList = extensionEngineStyleList;
|
|
9623
10024
|
function themeInfo(id, kind, meta) {
|
|
9624
10025
|
const styles = themeStyleList(meta);
|
|
9625
10026
|
return {
|
|
@@ -9630,7 +10031,10 @@ function themeInfo(id, kind, meta) {
|
|
|
9630
10031
|
kind,
|
|
9631
10032
|
preview: meta.preview ?? null,
|
|
9632
10033
|
styles,
|
|
9633
|
-
tokensFile: meta.tokensFile?.trim() || styles[0]
|
|
10034
|
+
tokensFile: meta.tokensFile?.trim() || styles[0],
|
|
10035
|
+
markdown: meta.markdown?.trim() || undefined,
|
|
10036
|
+
components: meta.components?.trim() || undefined,
|
|
10037
|
+
snippets: meta.snippets?.trim() || undefined
|
|
9634
10038
|
};
|
|
9635
10039
|
}
|
|
9636
10040
|
function themeSlug(name) {
|
|
@@ -9640,14 +10044,14 @@ function themeDirFor(projectDir, id) {
|
|
|
9640
10044
|
if (typeof id !== "string" || id.length === 0 || themeSlug(id) !== id) {
|
|
9641
10045
|
throw new Error(`Invalid theme id "${id}".`);
|
|
9642
10046
|
}
|
|
9643
|
-
return
|
|
10047
|
+
return path15.join(projectDir, THEMES_DIR, id);
|
|
9644
10048
|
}
|
|
9645
10049
|
async function listBuiltInThemes() {
|
|
9646
10050
|
const out = [];
|
|
9647
10051
|
for (const id of BUILT_IN_THEME_IDS) {
|
|
9648
10052
|
let meta = {};
|
|
9649
10053
|
try {
|
|
9650
|
-
meta = await readThemeMeta(await
|
|
10054
|
+
meta = await readThemeMeta(path15.join(await getAssetsDir(), THEMES_DIR, id));
|
|
9651
10055
|
} catch {
|
|
9652
10056
|
meta = {};
|
|
9653
10057
|
}
|
|
@@ -9665,20 +10069,19 @@ async function resolveBuiltInTheme(id) {
|
|
|
9665
10069
|
throw new Error(`Unknown built-in theme: "${id}".`);
|
|
9666
10070
|
}
|
|
9667
10071
|
const cssPath = await getAssetPath(`${THEMES_DIR}/${id}/theme.css`);
|
|
9668
|
-
|
|
9669
|
-
if (!existsSync11(cssPath)) {
|
|
10072
|
+
if (!existsSync12(cssPath)) {
|
|
9670
10073
|
throw new Error(`Built-in theme "${id}" is missing its theme.css.`);
|
|
9671
10074
|
}
|
|
9672
|
-
const meta = await readThemeMeta(
|
|
10075
|
+
const meta = await readThemeMeta(path15.join(await getAssetsDir(), THEMES_DIR, id));
|
|
9673
10076
|
if (!meta.name)
|
|
9674
10077
|
meta.name = BUILT_IN_FALLBACK_META[id]?.name;
|
|
9675
10078
|
if (!meta.description) {
|
|
9676
10079
|
meta.description = BUILT_IN_FALLBACK_META[id]?.description;
|
|
9677
10080
|
}
|
|
9678
|
-
return { info: themeInfo(id, "builtin", meta), cssPath, dir:
|
|
10081
|
+
return { info: themeInfo(id, "builtin", meta), cssPath, dir: path15.dirname(cssPath) };
|
|
9679
10082
|
}
|
|
9680
10083
|
async function listProjectThemes(projectDir) {
|
|
9681
|
-
const root =
|
|
10084
|
+
const root = path15.join(projectDir, THEMES_DIR);
|
|
9682
10085
|
let entries;
|
|
9683
10086
|
try {
|
|
9684
10087
|
entries = await readdir5(root, { withFileTypes: true });
|
|
@@ -9689,10 +10092,10 @@ async function listProjectThemes(projectDir) {
|
|
|
9689
10092
|
for (const entry of entries) {
|
|
9690
10093
|
if (!entry.isDirectory())
|
|
9691
10094
|
continue;
|
|
9692
|
-
const dir =
|
|
9693
|
-
const meta = await readThemeMeta(
|
|
10095
|
+
const dir = path15.join(root, entry.name);
|
|
10096
|
+
const meta = await readThemeMeta(dir);
|
|
9694
10097
|
const primary = themeStyleList(meta)[0];
|
|
9695
|
-
if (!
|
|
10098
|
+
if (!existsSync12(path15.join(dir, primary)))
|
|
9696
10099
|
continue;
|
|
9697
10100
|
out.push(themeInfo(entry.name, "project", meta));
|
|
9698
10101
|
}
|
|
@@ -9743,10 +10146,10 @@ async function getActiveTheme(projectDir) {
|
|
|
9743
10146
|
if (!m)
|
|
9744
10147
|
continue;
|
|
9745
10148
|
const id = m[1];
|
|
9746
|
-
const dir =
|
|
9747
|
-
const meta = await readThemeMeta(
|
|
10149
|
+
const dir = path15.join(projectDir, THEMES_DIR, id);
|
|
10150
|
+
const meta = await readThemeMeta(dir);
|
|
9748
10151
|
const primary = themeStyleList(meta)[0];
|
|
9749
|
-
if (!
|
|
10152
|
+
if (!existsSync12(path15.join(dir, primary)))
|
|
9750
10153
|
continue;
|
|
9751
10154
|
return themeInfo(id, "project", meta);
|
|
9752
10155
|
}
|
|
@@ -9784,9 +10187,9 @@ async function getPreviousTheme(projectDir) {
|
|
|
9784
10187
|
if (!id)
|
|
9785
10188
|
return null;
|
|
9786
10189
|
const dir = themeDirFor(projectDir, id);
|
|
9787
|
-
const meta = await readThemeMeta(
|
|
10190
|
+
const meta = await readThemeMeta(dir);
|
|
9788
10191
|
const primary = themeStyleList(meta)[0];
|
|
9789
|
-
if (!
|
|
10192
|
+
if (!existsSync12(path15.join(dir, primary)))
|
|
9790
10193
|
return null;
|
|
9791
10194
|
const active = await getActiveTheme(projectDir);
|
|
9792
10195
|
if (active && active.id === id)
|
|
@@ -9806,23 +10209,23 @@ async function applyTheme(projectDir, target) {
|
|
|
9806
10209
|
if (target.kind === "builtin") {
|
|
9807
10210
|
const resolved = await resolveBuiltInTheme(target.id);
|
|
9808
10211
|
let destId = target.id;
|
|
9809
|
-
if (
|
|
10212
|
+
if (existsSync12(path15.join(projectDir, THEMES_DIR, destId, "theme.css"))) {
|
|
9810
10213
|
destId = await uniqueThemeId(projectDir, target.id);
|
|
9811
10214
|
}
|
|
9812
|
-
const destDir =
|
|
10215
|
+
const destDir = path15.join(projectDir, THEMES_DIR, destId);
|
|
9813
10216
|
await mkdir6(destDir, { recursive: true });
|
|
9814
10217
|
await cp2(resolved.dir, destDir, { recursive: true });
|
|
9815
|
-
meta = await readThemeMeta(
|
|
10218
|
+
meta = await readThemeMeta(destDir);
|
|
9816
10219
|
assertThemeSheetsContained(meta);
|
|
9817
10220
|
resolveDeclaredStyles(themeStyleList(meta), destDir, `Theme "${destId}"`);
|
|
9818
10221
|
resolveDeclaredStyles(themeEngineStyleList(meta), destDir, `Theme "${destId}"`);
|
|
9819
10222
|
info2 = themeInfo(destId, "project", meta);
|
|
9820
10223
|
} else {
|
|
9821
10224
|
const dir = themeDirFor(projectDir, target.id);
|
|
9822
|
-
meta = await readThemeMeta(
|
|
10225
|
+
meta = await readThemeMeta(dir);
|
|
9823
10226
|
assertThemeSheetsContained(meta);
|
|
9824
10227
|
const primary = themeStyleList(meta)[0];
|
|
9825
|
-
if (!
|
|
10228
|
+
if (!existsSync12(path15.join(dir, primary))) {
|
|
9826
10229
|
throw new Error(`Theme "${target.id}" is not present in this project.`);
|
|
9827
10230
|
}
|
|
9828
10231
|
resolveDeclaredStyles(themeStyleList(meta), dir, `Theme "${target.id}"`);
|
|
@@ -9833,10 +10236,10 @@ async function applyTheme(projectDir, target) {
|
|
|
9833
10236
|
return info2;
|
|
9834
10237
|
}
|
|
9835
10238
|
async function uniqueThemeId(projectDir, base) {
|
|
9836
|
-
const root =
|
|
10239
|
+
const root = path15.join(projectDir, THEMES_DIR);
|
|
9837
10240
|
let id = themeSlug(base);
|
|
9838
10241
|
let n = 2;
|
|
9839
|
-
while (
|
|
10242
|
+
while (existsSync12(path15.join(root, id))) {
|
|
9840
10243
|
id = `${themeSlug(base)}-${n++}`;
|
|
9841
10244
|
}
|
|
9842
10245
|
return id;
|
|
@@ -9851,20 +10254,20 @@ async function importThemeFromFolder(projectDir, sourceDir) {
|
|
|
9851
10254
|
if (!info2.isDirectory()) {
|
|
9852
10255
|
throw new Error("The chosen path is not a folder.");
|
|
9853
10256
|
}
|
|
9854
|
-
const meta = await readThemeMeta(
|
|
10257
|
+
const meta = await readThemeMeta(sourceDir);
|
|
9855
10258
|
assertThemeSheetsContained(meta);
|
|
9856
10259
|
const primary = themeStyleList(meta)[0];
|
|
9857
|
-
if (!
|
|
10260
|
+
if (!existsSync12(path15.join(sourceDir, primary))) {
|
|
9858
10261
|
throw new Error(primary === "theme.css" ? "A theme folder must contain a theme.css file." : `A theme folder must contain its declared stylesheet: ${primary}`);
|
|
9859
10262
|
}
|
|
9860
10263
|
resolveDeclaredStyles(themeStyleList(meta), sourceDir, "The theme folder");
|
|
9861
10264
|
resolveDeclaredStyles(themeEngineStyleList(meta), sourceDir, "The theme folder");
|
|
9862
|
-
const base = meta.name ||
|
|
10265
|
+
const base = meta.name || path15.basename(sourceDir);
|
|
9863
10266
|
const id = await uniqueThemeId(projectDir, base);
|
|
9864
|
-
const destDir =
|
|
9865
|
-
await mkdir6(
|
|
10267
|
+
const destDir = path15.join(projectDir, THEMES_DIR, id);
|
|
10268
|
+
await mkdir6(path15.dirname(destDir), { recursive: true });
|
|
9866
10269
|
await cp2(sourceDir, destDir, { recursive: true });
|
|
9867
|
-
return themeInfo(id, "project", await readThemeMeta(
|
|
10270
|
+
return themeInfo(id, "project", await readThemeMeta(destDir));
|
|
9868
10271
|
}
|
|
9869
10272
|
function looksLikeCssUrl(url) {
|
|
9870
10273
|
try {
|
|
@@ -9911,7 +10314,7 @@ async function importThemeFromUrl(projectDir, url) {
|
|
|
9911
10314
|
let baseName;
|
|
9912
10315
|
if (looksLikeCssUrl(trimmed)) {
|
|
9913
10316
|
css = await fetchText(trimmed);
|
|
9914
|
-
baseName =
|
|
10317
|
+
baseName = path15.basename(new URL(trimmed, "https://x/").pathname, ".css") || "theme";
|
|
9915
10318
|
} else {
|
|
9916
10319
|
const base = trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
9917
10320
|
css = await fetchText(`${base}theme.css`);
|
|
@@ -9921,41 +10324,41 @@ async function importThemeFromUrl(projectDir, url) {
|
|
|
9921
10324
|
if (parsed && typeof parsed === "object")
|
|
9922
10325
|
meta = parsed;
|
|
9923
10326
|
} catch {}
|
|
9924
|
-
baseName = meta.name ||
|
|
10327
|
+
baseName = meta.name || path15.basename(new URL(base, "https://x/").pathname.replace(/\/$/, "")) || "theme";
|
|
9925
10328
|
}
|
|
9926
10329
|
if (!css.trim()) {
|
|
9927
10330
|
throw new Error(`Fetched theme CSS from ${trimmed} was empty.`);
|
|
9928
10331
|
}
|
|
9929
10332
|
assertLooksLikeCss(css, trimmed);
|
|
9930
10333
|
const id = await uniqueThemeId(projectDir, meta.name || baseName);
|
|
9931
|
-
const destDir =
|
|
10334
|
+
const destDir = path15.join(projectDir, THEMES_DIR, id);
|
|
9932
10335
|
await mkdir6(destDir, { recursive: true });
|
|
9933
|
-
await writeFile5(
|
|
10336
|
+
await writeFile5(path15.join(destDir, "theme.css"), css, "utf8");
|
|
9934
10337
|
const finalMeta = {
|
|
9935
10338
|
name: meta.name || prettify(id),
|
|
9936
10339
|
author: meta.author,
|
|
9937
10340
|
description: meta.description,
|
|
9938
10341
|
preview: meta.preview ?? null
|
|
9939
10342
|
};
|
|
9940
|
-
await writeFile5(
|
|
10343
|
+
await writeFile5(path15.join(destDir, "theme.json"), JSON.stringify(finalMeta, null, 2), "utf8");
|
|
9941
10344
|
return themeInfo(id, "project", finalMeta);
|
|
9942
10345
|
}
|
|
9943
10346
|
async function readThemeCss(projectDir, source) {
|
|
9944
10347
|
if (source.kind === "builtin") {
|
|
9945
10348
|
const resolved = await resolveBuiltInTheme(source.id);
|
|
9946
|
-
return
|
|
10349
|
+
return readFile25(resolved.cssPath, "utf8");
|
|
9947
10350
|
}
|
|
9948
10351
|
if (!projectDir)
|
|
9949
10352
|
throw new Error("A project is required to read a project theme.");
|
|
9950
10353
|
const dir = themeDirFor(projectDir, source.id);
|
|
9951
|
-
const meta = await readThemeMeta(
|
|
10354
|
+
const meta = await readThemeMeta(dir);
|
|
9952
10355
|
const sheets = themeStyleList(meta);
|
|
9953
10356
|
const parts = [];
|
|
9954
10357
|
for (const rel of sheets) {
|
|
9955
|
-
const sheetPath =
|
|
9956
|
-
if (!
|
|
10358
|
+
const sheetPath = path15.join(dir, rel);
|
|
10359
|
+
if (!existsSync12(sheetPath))
|
|
9957
10360
|
continue;
|
|
9958
|
-
parts.push(await
|
|
10361
|
+
parts.push(await readFile25(sheetPath, "utf8"));
|
|
9959
10362
|
}
|
|
9960
10363
|
if (parts.length === 0) {
|
|
9961
10364
|
throw new Error(`Theme "${source.id}" has no ${sheets[0]} in this project.`);
|
|
@@ -9966,7 +10369,7 @@ async function readThemeCss(projectDir, source) {
|
|
|
9966
10369
|
}
|
|
9967
10370
|
async function removeProjectTheme(projectDir, id) {
|
|
9968
10371
|
const dir = themeDirFor(projectDir, id);
|
|
9969
|
-
if (
|
|
10372
|
+
if (existsSync12(dir)) {
|
|
9970
10373
|
await rm3(dir, { recursive: true, force: true });
|
|
9971
10374
|
}
|
|
9972
10375
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
@@ -10004,7 +10407,7 @@ async function detectLegacyForkedTheme(projectDir, active) {
|
|
|
10004
10407
|
return null;
|
|
10005
10408
|
let bookCss;
|
|
10006
10409
|
try {
|
|
10007
|
-
bookCss = await
|
|
10410
|
+
bookCss = await readFile25(path15.join(projectDir, "styles", "book.css"), "utf8");
|
|
10008
10411
|
} catch {
|
|
10009
10412
|
return null;
|
|
10010
10413
|
}
|
|
@@ -10024,9 +10427,9 @@ async function detectLegacyForkedTheme(projectDir, active) {
|
|
|
10024
10427
|
}
|
|
10025
10428
|
|
|
10026
10429
|
// src/lib/project-scaffold.ts
|
|
10027
|
-
import { access, copyFile, cp as cp3, mkdir as mkdir7, readdir as readdir6, readFile as
|
|
10028
|
-
import { constants as FS, existsSync as
|
|
10029
|
-
import
|
|
10430
|
+
import { access, copyFile, cp as cp3, mkdir as mkdir7, readdir as readdir6, readFile as readFile26, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
|
|
10431
|
+
import { constants as FS, existsSync as existsSync13 } from "node:fs";
|
|
10432
|
+
import path16 from "node:path";
|
|
10030
10433
|
var STARTER_THEME_FOR_TEMPLATE = {
|
|
10031
10434
|
book: "clean-book",
|
|
10032
10435
|
zine: "zine",
|
|
@@ -10106,7 +10509,7 @@ async function scaffoldProject(options) {
|
|
|
10106
10509
|
throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
10107
10510
|
}
|
|
10108
10511
|
const parentDir = options.parentDir;
|
|
10109
|
-
if (!parentDir || !
|
|
10512
|
+
if (!parentDir || !path16.isAbsolute(parentDir)) {
|
|
10110
10513
|
throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
|
|
10111
10514
|
}
|
|
10112
10515
|
try {
|
|
@@ -10114,7 +10517,7 @@ async function scaffoldProject(options) {
|
|
|
10114
10517
|
} catch {
|
|
10115
10518
|
throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
10116
10519
|
}
|
|
10117
|
-
const projectDir =
|
|
10520
|
+
const projectDir = path16.join(parentDir, folderName);
|
|
10118
10521
|
let targetExists = true;
|
|
10119
10522
|
try {
|
|
10120
10523
|
await access(projectDir, FS.F_OK);
|
|
@@ -10136,17 +10539,17 @@ async function scaffoldProject(options) {
|
|
|
10136
10539
|
for (const entry of entries) {
|
|
10137
10540
|
if (entry.name === ".gutterpress-template.json")
|
|
10138
10541
|
continue;
|
|
10139
|
-
await cp3(
|
|
10542
|
+
await cp3(path16.join(customTemplateDir, entry.name), path16.join(projectDir, entry.name), { recursive: true });
|
|
10140
10543
|
}
|
|
10141
|
-
await mkdir7(
|
|
10544
|
+
await mkdir7(path16.join(projectDir, "assets"), { recursive: true });
|
|
10142
10545
|
} else {
|
|
10143
10546
|
const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
|
|
10144
10547
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
10145
|
-
await mkdir7(
|
|
10146
|
-
await copyFile(tplManifest,
|
|
10147
|
-
await copyFile(tplChapter,
|
|
10148
|
-
await mkdir7(
|
|
10149
|
-
await writeFile6(
|
|
10548
|
+
await mkdir7(path16.join(projectDir, "assets"), { recursive: true });
|
|
10549
|
+
await copyFile(tplManifest, path16.join(projectDir, MANIFEST_FILENAMES[0]));
|
|
10550
|
+
await copyFile(tplChapter, path16.join(projectDir, "chapter-01.md"));
|
|
10551
|
+
await mkdir7(path16.join(projectDir, "styles"), { recursive: true });
|
|
10552
|
+
await writeFile6(path16.join(projectDir, "styles", "book.css"), starterOverridesCss(starterThemeId), "utf8");
|
|
10150
10553
|
}
|
|
10151
10554
|
} catch (e) {
|
|
10152
10555
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not create the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -10156,13 +10559,13 @@ async function scaffoldProject(options) {
|
|
|
10156
10559
|
} catch (e) {
|
|
10157
10560
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
|
|
10158
10561
|
}
|
|
10159
|
-
const manifestPath = MANIFEST_FILENAMES.map((name2) =>
|
|
10562
|
+
const manifestPath = MANIFEST_FILENAMES.map((name2) => path16.join(projectDir, name2)).find(existsSync13) ?? path16.join(projectDir, MANIFEST_FILENAMES[0]);
|
|
10160
10563
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
10161
10564
|
const substitutions = {
|
|
10162
10565
|
"{{TITLE}}": escapeYamlScalar(name),
|
|
10163
10566
|
"{{AUTHOR}}": escapeYamlScalar(author)
|
|
10164
10567
|
};
|
|
10165
|
-
let openFile =
|
|
10568
|
+
let openFile = path16.join(projectDir, "chapter-01.md");
|
|
10166
10569
|
try {
|
|
10167
10570
|
await fillTemplateFile(manifestPath, substitutions);
|
|
10168
10571
|
if (!customTemplateDir) {
|
|
@@ -10186,7 +10589,7 @@ async function scaffoldProject(options) {
|
|
|
10186
10589
|
}
|
|
10187
10590
|
const firstSource = await firstSourceFile(manifestPath);
|
|
10188
10591
|
if (firstSource)
|
|
10189
|
-
openFile =
|
|
10592
|
+
openFile = path16.join(projectDir, firstSource);
|
|
10190
10593
|
await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
|
|
10191
10594
|
} catch (e) {
|
|
10192
10595
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not finalise the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -10208,7 +10611,7 @@ async function scaffoldProject(options) {
|
|
|
10208
10611
|
versionHistory = "none";
|
|
10209
10612
|
const { findEnclosingRepoDir } = await import("./project-source-ekcyp63q.js");
|
|
10210
10613
|
const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
|
|
10211
|
-
versionHistoryError = enclosingRepoDir ? `This book was added to ${
|
|
10614
|
+
versionHistoryError = enclosingRepoDir ? `This book was added to ${path16.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
|
|
10212
10615
|
}
|
|
10213
10616
|
}
|
|
10214
10617
|
const result = {
|
|
@@ -10223,10 +10626,10 @@ async function scaffoldProject(options) {
|
|
|
10223
10626
|
return result;
|
|
10224
10627
|
}
|
|
10225
10628
|
async function ensureGitignoreHasDist(projectDir) {
|
|
10226
|
-
const gitignorePath =
|
|
10629
|
+
const gitignorePath = path16.join(projectDir, ".gitignore");
|
|
10227
10630
|
let existing = null;
|
|
10228
10631
|
try {
|
|
10229
|
-
existing = await
|
|
10632
|
+
existing = await readFile26(gitignorePath, "utf8");
|
|
10230
10633
|
} catch {}
|
|
10231
10634
|
if (existing === null) {
|
|
10232
10635
|
await writeFile6(gitignorePath, `dist/
|
|
@@ -10246,7 +10649,7 @@ async function ensureGitignoreHasDist(projectDir) {
|
|
|
10246
10649
|
async function firstSourceFile(manifestPath) {
|
|
10247
10650
|
let text;
|
|
10248
10651
|
try {
|
|
10249
|
-
text = await
|
|
10652
|
+
text = await readFile26(manifestPath, "utf8");
|
|
10250
10653
|
} catch {
|
|
10251
10654
|
return;
|
|
10252
10655
|
}
|
|
@@ -10258,13 +10661,158 @@ async function firstSourceFile(manifestPath) {
|
|
|
10258
10661
|
return m ? m[1].trim() : undefined;
|
|
10259
10662
|
}
|
|
10260
10663
|
async function fillTemplateFile(filePath, substitutions) {
|
|
10261
|
-
let text = await
|
|
10664
|
+
let text = await readFile26(filePath, "utf8");
|
|
10262
10665
|
for (const [token, value] of Object.entries(substitutions)) {
|
|
10263
10666
|
text = text.split(token).join(value);
|
|
10264
10667
|
}
|
|
10265
10668
|
await writeFile6(filePath, text, "utf8");
|
|
10266
10669
|
}
|
|
10267
10670
|
|
|
10671
|
+
// src/lib/extension-scaffold.ts
|
|
10672
|
+
import { access as access2, mkdir as mkdir8, readFile as readFile27, writeFile as writeFile7 } from "node:fs/promises";
|
|
10673
|
+
import { constants as FS2 } from "node:fs";
|
|
10674
|
+
import path17 from "node:path";
|
|
10675
|
+
var EXTENSION_KINDS = ["plugin", "theme"];
|
|
10676
|
+
var RESERVED_PREFIX = "gp-";
|
|
10677
|
+
var TEMPLATE_FILES = {
|
|
10678
|
+
plugin: [
|
|
10679
|
+
["gutterpress.json", "gutterpress.json"],
|
|
10680
|
+
["package.json", "package.json"],
|
|
10681
|
+
["README.md", "README.md"],
|
|
10682
|
+
["plugin.js.tpl", "plugin.js"],
|
|
10683
|
+
["styles/plugin.css", "styles/plugin.css"],
|
|
10684
|
+
["snippets/term-box.md", "snippets/term-box.md"],
|
|
10685
|
+
["test/fixture.md", "test/fixture.md"],
|
|
10686
|
+
["test/expected.html", "test/expected.html"],
|
|
10687
|
+
["test/plugin.test.js.tpl", "test/plugin.test.js"]
|
|
10688
|
+
],
|
|
10689
|
+
theme: [
|
|
10690
|
+
["gutterpress.json", "gutterpress.json"],
|
|
10691
|
+
["README.md", "README.md"],
|
|
10692
|
+
["components.yaml", "components.yaml"],
|
|
10693
|
+
["snippets/callout.md", "snippets/callout.md"],
|
|
10694
|
+
["styles/tokens.css", "styles/tokens.css"],
|
|
10695
|
+
["styles/base.css", "styles/base.css"],
|
|
10696
|
+
["styles/components.css", "styles/components.css"],
|
|
10697
|
+
["styles/page-templates.css", "styles/page-templates.css"],
|
|
10698
|
+
["styles/page-rules.css", "styles/page-rules.css"],
|
|
10699
|
+
["styles/book.css", "styles/book.css"]
|
|
10700
|
+
]
|
|
10701
|
+
};
|
|
10702
|
+
var OPEN_FILE = {
|
|
10703
|
+
plugin: "plugin.js",
|
|
10704
|
+
theme: "styles/tokens.css"
|
|
10705
|
+
};
|
|
10706
|
+
var DEFAULT_DESCRIPTION = {
|
|
10707
|
+
plugin: "A Gutterpress plugin.",
|
|
10708
|
+
theme: "A Gutterpress theme."
|
|
10709
|
+
};
|
|
10710
|
+
var DEFAULT_AUTHOR2 = "Anonymous";
|
|
10711
|
+
|
|
10712
|
+
class ExtensionScaffoldError extends Error {
|
|
10713
|
+
code;
|
|
10714
|
+
constructor(code, message) {
|
|
10715
|
+
super(message);
|
|
10716
|
+
this.name = "CreateProjectError";
|
|
10717
|
+
this.code = code;
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
function resolveExtensionPrefix(slug, requested) {
|
|
10721
|
+
const raw = (requested ?? `${slug}-`).trim();
|
|
10722
|
+
const prefix = raw.endsWith("-") ? raw : `${raw}-`;
|
|
10723
|
+
if (!/^[a-z][a-z0-9-]*-$/.test(prefix)) {
|
|
10724
|
+
throw new ExtensionScaffoldError("invalid-name", `"${raw}" is not a usable prefix. Use lower-case letters, digits and hyphens, ` + `starting with a letter — for example "${slug}-" or "fn-".`);
|
|
10725
|
+
}
|
|
10726
|
+
if (prefix === RESERVED_PREFIX) {
|
|
10727
|
+
throw new ExtensionScaffoldError("invalid-name", `"${RESERVED_PREFIX}" is reserved for Gutterpress core's own classes and custom ` + `properties. Taking it would silently override them — choose your own prefix.`);
|
|
10728
|
+
}
|
|
10729
|
+
return prefix;
|
|
10730
|
+
}
|
|
10731
|
+
function identifierFor(slug) {
|
|
10732
|
+
const camel = slug.replace(/-([a-z0-9])/g, (_m, c) => c.toUpperCase());
|
|
10733
|
+
return /^[0-9]/.test(camel) ? `_${camel}` : camel;
|
|
10734
|
+
}
|
|
10735
|
+
function escapeJsonScalar(value) {
|
|
10736
|
+
return JSON.stringify(value).slice(1, -1);
|
|
10737
|
+
}
|
|
10738
|
+
async function scaffoldExtension(options) {
|
|
10739
|
+
const kind = options.kind;
|
|
10740
|
+
if (kind !== "plugin" && kind !== "theme") {
|
|
10741
|
+
throw new ExtensionScaffoldError("invalid-name", `Unknown extension kind "${String(kind)}". Choose one of: ${EXTENSION_KINDS.join(", ")}.`);
|
|
10742
|
+
}
|
|
10743
|
+
const name = (options.name ?? "").trim();
|
|
10744
|
+
if (!name) {
|
|
10745
|
+
throw new ExtensionScaffoldError("invalid-name", "A name is required.");
|
|
10746
|
+
}
|
|
10747
|
+
const slug = (options.folderName ?? slugify(name)).trim();
|
|
10748
|
+
if (!slug || slug !== slugify(slug)) {
|
|
10749
|
+
throw new ExtensionScaffoldError("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
10750
|
+
}
|
|
10751
|
+
const prefix = resolveExtensionPrefix(slug, options.prefix);
|
|
10752
|
+
const parentDir = options.parentDir;
|
|
10753
|
+
if (!parentDir || !path17.isAbsolute(parentDir)) {
|
|
10754
|
+
throw new ExtensionScaffoldError("parent-not-writable", "The save location must be an absolute path.");
|
|
10755
|
+
}
|
|
10756
|
+
try {
|
|
10757
|
+
await access2(parentDir, FS2.W_OK);
|
|
10758
|
+
} catch {
|
|
10759
|
+
throw new ExtensionScaffoldError("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
10760
|
+
}
|
|
10761
|
+
const extensionDir = path17.join(parentDir, slug);
|
|
10762
|
+
let targetExists = true;
|
|
10763
|
+
try {
|
|
10764
|
+
await access2(extensionDir, FS2.F_OK);
|
|
10765
|
+
} catch {
|
|
10766
|
+
targetExists = false;
|
|
10767
|
+
}
|
|
10768
|
+
if (targetExists) {
|
|
10769
|
+
throw new ExtensionScaffoldError("target-exists", `A folder named "${slug}" already exists here. Choose a different name or location.`);
|
|
10770
|
+
}
|
|
10771
|
+
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR2;
|
|
10772
|
+
const description = (options.description ?? "").trim() || DEFAULT_DESCRIPTION[kind];
|
|
10773
|
+
const plain = {
|
|
10774
|
+
"{{NAME}}": name,
|
|
10775
|
+
"{{SLUG}}": slug,
|
|
10776
|
+
"{{PREFIX}}": prefix,
|
|
10777
|
+
"{{AUTHOR}}": author,
|
|
10778
|
+
"{{DESCRIPTION}}": description,
|
|
10779
|
+
"{{IDENT}}": identifierFor(slug)
|
|
10780
|
+
};
|
|
10781
|
+
const jsonSafe = Object.fromEntries(Object.entries(plain).map(([token, value]) => [token, escapeJsonScalar(value)]));
|
|
10782
|
+
const entries = TEMPLATE_FILES[kind];
|
|
10783
|
+
const written = [];
|
|
10784
|
+
try {
|
|
10785
|
+
await mkdir8(extensionDir, { recursive: true });
|
|
10786
|
+
for (const [assetName, relDest] of entries) {
|
|
10787
|
+
const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
|
|
10788
|
+
const dest = path17.join(extensionDir, relDest);
|
|
10789
|
+
await mkdir8(path17.dirname(dest), { recursive: true });
|
|
10790
|
+
const text = await readFile27(src, "utf8");
|
|
10791
|
+
const table = relDest.endsWith(".json") ? jsonSafe : plain;
|
|
10792
|
+
await writeFile7(dest, substitute(text, table), "utf8");
|
|
10793
|
+
written.push(relDest);
|
|
10794
|
+
}
|
|
10795
|
+
} catch (e) {
|
|
10796
|
+
throw new ExtensionScaffoldError("scaffold-io", `Could not create the ${kind} files: ${e instanceof Error ? e.message : String(e)}`);
|
|
10797
|
+
}
|
|
10798
|
+
return {
|
|
10799
|
+
extensionDir,
|
|
10800
|
+
manifestPath: path17.join(extensionDir, "gutterpress.json"),
|
|
10801
|
+
kind,
|
|
10802
|
+
slug,
|
|
10803
|
+
prefix,
|
|
10804
|
+
openFile: path17.join(extensionDir, OPEN_FILE[kind]),
|
|
10805
|
+
files: written
|
|
10806
|
+
};
|
|
10807
|
+
}
|
|
10808
|
+
function substitute(text, table) {
|
|
10809
|
+
let out = text;
|
|
10810
|
+
for (const [token, value] of Object.entries(table)) {
|
|
10811
|
+
out = out.split(token).join(value);
|
|
10812
|
+
}
|
|
10813
|
+
return out;
|
|
10814
|
+
}
|
|
10815
|
+
|
|
10268
10816
|
// src/lib/project-templates.ts
|
|
10269
10817
|
import { isMap, isScalar, isSeq as isSeq3, parseDocument as parseDocument2 } from "yaml";
|
|
10270
10818
|
var BUILT_IN_TEMPLATE_IDS = [
|
|
@@ -10283,7 +10831,7 @@ var SKIP_ENTRIES = new Set([
|
|
|
10283
10831
|
]);
|
|
10284
10832
|
|
|
10285
10833
|
// src/lib/plugin-manager.ts
|
|
10286
|
-
import
|
|
10834
|
+
import path19 from "node:path";
|
|
10287
10835
|
import { isSeq as isSeq4, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar as Scalar2 } from "yaml";
|
|
10288
10836
|
|
|
10289
10837
|
// src/lib/npm-plugin-installer.ts
|
|
@@ -10291,15 +10839,15 @@ import { createHash as createHash7, randomUUID as randomUUID3, timingSafeEqual }
|
|
|
10291
10839
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
10292
10840
|
import {
|
|
10293
10841
|
lstat as lstat2,
|
|
10294
|
-
mkdir as
|
|
10842
|
+
mkdir as mkdir9,
|
|
10295
10843
|
mkdtemp as mkdtemp2,
|
|
10296
10844
|
open as open2,
|
|
10297
|
-
readFile as
|
|
10845
|
+
readFile as readFile28,
|
|
10298
10846
|
realpath as realpath2,
|
|
10299
10847
|
rename as rename2,
|
|
10300
10848
|
rm as rm4
|
|
10301
10849
|
} from "node:fs/promises";
|
|
10302
|
-
import
|
|
10850
|
+
import path18 from "node:path";
|
|
10303
10851
|
import { Transform } from "node:stream";
|
|
10304
10852
|
import { pipeline } from "node:stream/promises";
|
|
10305
10853
|
import { createGunzip } from "node:zlib";
|
|
@@ -10344,8 +10892,8 @@ function mb(bytes) {
|
|
|
10344
10892
|
return `${Math.ceil(bytes / 1024 / 1024)}MB`;
|
|
10345
10893
|
}
|
|
10346
10894
|
function isContained2(root, candidate) {
|
|
10347
|
-
const relative2 =
|
|
10348
|
-
return relative2 === "" || !relative2.startsWith("..") && !
|
|
10895
|
+
const relative2 = path18.relative(root, candidate);
|
|
10896
|
+
return relative2 === "" || !relative2.startsWith("..") && !path18.isAbsolute(relative2);
|
|
10349
10897
|
}
|
|
10350
10898
|
function mergeLimits(overrides) {
|
|
10351
10899
|
const limits = { ...DEFAULT_LIMITS, ...overrides };
|
|
@@ -10579,7 +11127,7 @@ async function extractTarball(ctx, archivePath, packageDir, packageName) {
|
|
|
10579
11127
|
"OldGnuLongPath",
|
|
10580
11128
|
"OldExtendedHeader"
|
|
10581
11129
|
]);
|
|
10582
|
-
await
|
|
11130
|
+
await mkdir9(packageDir, { recursive: true });
|
|
10583
11131
|
const unpack = extract({
|
|
10584
11132
|
cwd: packageDir,
|
|
10585
11133
|
strip: 1,
|
|
@@ -10652,13 +11200,13 @@ async function extractTarball(ctx, archivePath, packageDir, packageName) {
|
|
|
10652
11200
|
throw rejected;
|
|
10653
11201
|
}
|
|
10654
11202
|
async function readInstalledManifest(packageDir, selected) {
|
|
10655
|
-
const packageJsonPath =
|
|
11203
|
+
const packageJsonPath = path18.join(packageDir, "package.json");
|
|
10656
11204
|
let manifest = null;
|
|
10657
11205
|
try {
|
|
10658
11206
|
const info2 = await lstat2(packageJsonPath);
|
|
10659
11207
|
if (!info2.isFile() || info2.isSymbolicLink())
|
|
10660
11208
|
throw new Error("not a regular file");
|
|
10661
|
-
manifest = object2(JSON.parse(await
|
|
11209
|
+
manifest = object2(JSON.parse(await readFile28(packageJsonPath, "utf8")));
|
|
10662
11210
|
} catch (error2) {
|
|
10663
11211
|
throw new SecurityInstallError(`Downloaded ${selected.name}@${selected.version} has no valid package.json (${error2 instanceof Error ? error2.message : String(error2)}).`);
|
|
10664
11212
|
}
|
|
@@ -10728,10 +11276,10 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
10728
11276
|
throw new SecurityInstallError(`Dependency graph exceeds ${ctx.limits.totalPackages} packages.`);
|
|
10729
11277
|
}
|
|
10730
11278
|
const relativePath = relativePackagePath(parentPackagePath, name);
|
|
10731
|
-
const packageDir =
|
|
10732
|
-
const archivePath =
|
|
11279
|
+
const packageDir = path18.join(ctx.stageRoot, ...relativePath.split("/"));
|
|
11280
|
+
const archivePath = path18.join(ctx.downloadsDir, `${ctx.archiveCounter++}.tgz`);
|
|
10733
11281
|
try {
|
|
10734
|
-
await
|
|
11282
|
+
await mkdir9(path18.dirname(packageDir), { recursive: true });
|
|
10735
11283
|
await downloadTarball(ctx, selected, archivePath);
|
|
10736
11284
|
await extractTarball(ctx, archivePath, packageDir, selected.name);
|
|
10737
11285
|
const manifest = await readInstalledManifest(packageDir, selected);
|
|
@@ -10826,14 +11374,14 @@ async function ensureDirectory(pathname) {
|
|
|
10826
11374
|
} catch (error2) {
|
|
10827
11375
|
if (error2.code !== "ENOENT")
|
|
10828
11376
|
throw error2;
|
|
10829
|
-
await
|
|
11377
|
+
await mkdir9(pathname);
|
|
10830
11378
|
}
|
|
10831
11379
|
}
|
|
10832
11380
|
async function prepareVendorParent(projectDir, name) {
|
|
10833
|
-
const plugins =
|
|
10834
|
-
const npm =
|
|
10835
|
-
const packageParent =
|
|
10836
|
-
await
|
|
11381
|
+
const plugins = path18.join(projectDir, PLUGINS_DIR);
|
|
11382
|
+
const npm = path18.join(plugins, VENDORED_NPM_DIR);
|
|
11383
|
+
const packageParent = path18.dirname(vendoredNpmPluginRoot(projectDir, name, "0.0.0"));
|
|
11384
|
+
await mkdir9(projectDir, { recursive: true });
|
|
10837
11385
|
await ensureDirectory(plugins);
|
|
10838
11386
|
await ensureDirectory(npm);
|
|
10839
11387
|
await ensureDirectory(packageParent);
|
|
@@ -10860,15 +11408,15 @@ function publicReceipt(record) {
|
|
|
10860
11408
|
};
|
|
10861
11409
|
}
|
|
10862
11410
|
async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
10863
|
-
const requestedProjectDir =
|
|
10864
|
-
await
|
|
11411
|
+
const requestedProjectDir = path18.resolve(projectDirInput);
|
|
11412
|
+
await mkdir9(requestedProjectDir, { recursive: true });
|
|
10865
11413
|
const projectDir = await realpath2(requestedProjectDir);
|
|
10866
11414
|
const requested = parseNpmPluginSpec(packageSpec);
|
|
10867
11415
|
const npmRoot = await prepareVendorParent(projectDir, requested.name);
|
|
10868
|
-
const container = await mkdtemp2(
|
|
10869
|
-
const stageRoot =
|
|
10870
|
-
const downloadsDir =
|
|
10871
|
-
await Promise.all([
|
|
11416
|
+
const container = await mkdtemp2(path18.join(npmRoot, ".install-"));
|
|
11417
|
+
const stageRoot = path18.join(container, "root");
|
|
11418
|
+
const downloadsDir = path18.join(container, "downloads");
|
|
11419
|
+
await Promise.all([mkdir9(stageRoot), mkdir9(downloadsDir)]);
|
|
10872
11420
|
const ctx = {
|
|
10873
11421
|
fetch: options.fetch ?? globalThis.fetch,
|
|
10874
11422
|
signal: options.signal,
|
|
@@ -10913,7 +11461,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
10913
11461
|
skipped: ctx.skipped,
|
|
10914
11462
|
tree: { algorithm: "sha256", ...tree }
|
|
10915
11463
|
};
|
|
10916
|
-
const receiptFile = await open2(
|
|
11464
|
+
const receiptFile = await open2(path18.join(stageRoot, VENDOR_RECEIPT_FILE), "wx");
|
|
10917
11465
|
try {
|
|
10918
11466
|
await receiptFile.writeFile(`${JSON.stringify(receipt, null, 2)}
|
|
10919
11467
|
`, "utf8");
|
|
@@ -10922,7 +11470,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
10922
11470
|
await receiptFile.close();
|
|
10923
11471
|
}
|
|
10924
11472
|
finalRoot = vendoredNpmPluginRoot(projectDir, rootRecord.name, rootRecord.version);
|
|
10925
|
-
const realFinalParent = await realpath2(
|
|
11473
|
+
const realFinalParent = await realpath2(path18.dirname(finalRoot));
|
|
10926
11474
|
if (!isContained2(projectDir, realFinalParent)) {
|
|
10927
11475
|
throw new Error("Plugin install destination resolves outside the project.");
|
|
10928
11476
|
}
|
|
@@ -10971,7 +11519,7 @@ async function rollbackNpmPluginInstall(installed) {
|
|
|
10971
11519
|
// src/lib/plugin-manager.ts
|
|
10972
11520
|
var pluginMutationQueues = new Map;
|
|
10973
11521
|
function withPluginMutationLock(projectDir, mutation) {
|
|
10974
|
-
const resolved =
|
|
11522
|
+
const resolved = path19.resolve(projectDir);
|
|
10975
11523
|
const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
10976
11524
|
const previous = pluginMutationQueues.get(key) ?? Promise.resolve();
|
|
10977
11525
|
const run = previous.then(mutation, mutation);
|
|
@@ -11171,10 +11719,10 @@ async function addNpmPluginUnlocked(projectDir, packageSpec, options) {
|
|
|
11171
11719
|
}
|
|
11172
11720
|
|
|
11173
11721
|
// src/lib/theme-import.ts
|
|
11174
|
-
import { existsSync as
|
|
11175
|
-
import { mkdir as
|
|
11722
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
11723
|
+
import { mkdir as mkdir10, mkdtemp as mkdtemp3, readFile as readFile29, rm as rm5, writeFile as writeFile8 } from "node:fs/promises";
|
|
11176
11724
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
11177
|
-
import
|
|
11725
|
+
import path20 from "node:path";
|
|
11178
11726
|
import { unzipSync } from "fflate";
|
|
11179
11727
|
var MAX_THEME_ARCHIVE_BYTES = 25 * 1024 * 1024;
|
|
11180
11728
|
var MAX_THEME_UNZIPPED_BYTES = 25 * 1024 * 1024;
|
|
@@ -11207,7 +11755,7 @@ function classifyThemeCssFindings(findings) {
|
|
|
11207
11755
|
const warnings = reject ? [] : findings.filter((f) => f.rule !== ruleSyntax);
|
|
11208
11756
|
return { reject, warnings };
|
|
11209
11757
|
}
|
|
11210
|
-
var KNOWN_THEME_FILES = new Set(["theme.css", "theme.json"]);
|
|
11758
|
+
var KNOWN_THEME_FILES = new Set(["theme.css", "theme.json", EXTENSION_MANIFEST_FILENAME]);
|
|
11211
11759
|
var ALLOWED_ASSET_EXTS = new Set([
|
|
11212
11760
|
".woff",
|
|
11213
11761
|
".woff2",
|
|
@@ -11223,8 +11771,12 @@ var ALLOWED_ASSET_EXTS = new Set([
|
|
|
11223
11771
|
".avif",
|
|
11224
11772
|
".css"
|
|
11225
11773
|
]);
|
|
11226
|
-
function unexpectedThemeFiles(relPaths) {
|
|
11774
|
+
function unexpectedThemeFiles(relPaths, declaredExtras = []) {
|
|
11775
|
+
const extras = declaredExtras.map((p) => p.replace(/\\/g, "/"));
|
|
11776
|
+
const isDeclared = (p) => extras.some((extra) => p === extra || p.startsWith(`${extra}/`));
|
|
11227
11777
|
return relPaths.map((p) => p.replace(/\\/g, "/")).filter((p) => p.length > 0 && !p.endsWith("/")).filter((p) => {
|
|
11778
|
+
if (isDeclared(p))
|
|
11779
|
+
return false;
|
|
11228
11780
|
const base = p.split("/").pop().toLowerCase();
|
|
11229
11781
|
if (KNOWN_THEME_FILES.has(base))
|
|
11230
11782
|
return false;
|
|
@@ -11236,20 +11788,12 @@ function unexpectedThemeFiles(relPaths) {
|
|
|
11236
11788
|
function mb2(n) {
|
|
11237
11789
|
return `${Math.round(n / (1024 * 1024))}MB`;
|
|
11238
11790
|
}
|
|
11239
|
-
async function readThemeJson(p) {
|
|
11240
|
-
try {
|
|
11241
|
-
const parsed = JSON.parse(await readFile27(p, "utf8"));
|
|
11242
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
11243
|
-
} catch {
|
|
11244
|
-
return {};
|
|
11245
|
-
}
|
|
11246
|
-
}
|
|
11247
11791
|
async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
11248
|
-
const cssPath =
|
|
11249
|
-
if (!
|
|
11792
|
+
const cssPath = path20.join(sourceDir, "theme.css");
|
|
11793
|
+
if (!existsSync14(cssPath)) {
|
|
11250
11794
|
throw new Error("A theme must contain a theme.css file.");
|
|
11251
11795
|
}
|
|
11252
|
-
const css = await
|
|
11796
|
+
const css = await readFile29(cssPath, "utf8");
|
|
11253
11797
|
const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
|
|
11254
11798
|
if (reject) {
|
|
11255
11799
|
throw new Error(`theme.css could not be parsed — ${reject.message}`);
|
|
@@ -11258,29 +11802,29 @@ async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
|
11258
11802
|
for (const w of printFindings) {
|
|
11259
11803
|
warnings.push({ code: "print-safety", message: w.message });
|
|
11260
11804
|
}
|
|
11261
|
-
const
|
|
11262
|
-
const
|
|
11263
|
-
|
|
11264
|
-
if (!jsonExists) {
|
|
11805
|
+
const metaExists = existsSync14(path20.join(sourceDir, EXTENSION_MANIFEST_FILENAME)) || existsSync14(path20.join(sourceDir, "theme.json"));
|
|
11806
|
+
const meta = metaExists ? await readExtensionMeta(sourceDir) : {};
|
|
11807
|
+
if (!metaExists) {
|
|
11265
11808
|
warnings.push({
|
|
11266
11809
|
code: "no-theme-json",
|
|
11267
|
-
message: "No theme.json found — the theme's name was taken from the file/folder name."
|
|
11810
|
+
message: "No gutterpress.json or theme.json found — the theme's name was taken from the file/folder name."
|
|
11268
11811
|
});
|
|
11269
11812
|
} else if (!meta.name || !meta.name.trim()) {
|
|
11270
11813
|
warnings.push({
|
|
11271
11814
|
code: "unnamed-theme",
|
|
11272
|
-
message: `
|
|
11815
|
+
message: `The metadata file has no "name" — the theme's name was taken from the file/folder name.`
|
|
11273
11816
|
});
|
|
11274
11817
|
}
|
|
11275
11818
|
assertThemeSheetsContained(meta);
|
|
11819
|
+
const metaFilename = existsSync14(path20.join(sourceDir, EXTENSION_MANIFEST_FILENAME)) ? EXTENSION_MANIFEST_FILENAME : "theme.json";
|
|
11276
11820
|
const extraSheets = [
|
|
11277
11821
|
...new Set([...themeStyleList(meta), ...themeEngineStyleList(meta)])
|
|
11278
11822
|
].filter((rel) => rel !== "theme.css");
|
|
11279
|
-
const extraSheetPaths = resolveDeclaredStyles(extraSheets, sourceDir,
|
|
11823
|
+
const extraSheetPaths = resolveDeclaredStyles(extraSheets, sourceDir, metaFilename) ?? [];
|
|
11280
11824
|
for (let i = 0;i < extraSheets.length; i++) {
|
|
11281
11825
|
const rel = extraSheets[i];
|
|
11282
11826
|
const sheetPath = extraSheetPaths[i];
|
|
11283
|
-
const sheetCss = await
|
|
11827
|
+
const sheetCss = await readFile29(sheetPath, "utf8");
|
|
11284
11828
|
const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
|
|
11285
11829
|
if (sheetReject) {
|
|
11286
11830
|
throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
|
|
@@ -11289,7 +11833,8 @@ async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
|
11289
11833
|
warnings.push({ code: "print-safety", message: `${rel}: ${w.message}` });
|
|
11290
11834
|
}
|
|
11291
11835
|
}
|
|
11292
|
-
const
|
|
11836
|
+
const declaredExtras = [meta.markdown, meta.components, meta.snippets].filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
11837
|
+
const extra = unexpectedThemeFiles(relPaths, declaredExtras);
|
|
11293
11838
|
if (extra.length > 0) {
|
|
11294
11839
|
const shown = extra.slice(0, 6).join(", ");
|
|
11295
11840
|
warnings.push({
|
|
@@ -11341,7 +11886,7 @@ async function importThemeFromZip(projectDir, archive) {
|
|
|
11341
11886
|
throw new Error("No theme.css found in the package. A theme needs a theme.css at the package root or inside a single folder.");
|
|
11342
11887
|
}
|
|
11343
11888
|
const prefix = root === "" ? "" : `${root}/`;
|
|
11344
|
-
const tmp = await mkdtemp3(
|
|
11889
|
+
const tmp = await mkdtemp3(path20.join(tmpdir3(), "gutterpress-theme-"));
|
|
11345
11890
|
try {
|
|
11346
11891
|
const relPaths = [];
|
|
11347
11892
|
for (const [name, data] of Object.entries(files)) {
|
|
@@ -11353,13 +11898,13 @@ async function importThemeFromZip(projectDir, archive) {
|
|
|
11353
11898
|
const rel = prefix ? norm.slice(prefix.length) : norm;
|
|
11354
11899
|
if (!rel || rel.split("/").some((seg) => seg === ".."))
|
|
11355
11900
|
continue;
|
|
11356
|
-
const dest =
|
|
11357
|
-
if (dest !== tmp && !dest.startsWith(tmp +
|
|
11901
|
+
const dest = path20.join(tmp, rel);
|
|
11902
|
+
if (dest !== tmp && !dest.startsWith(tmp + path20.sep)) {
|
|
11358
11903
|
throw new Error(`The package contains an unsafe path and was rejected: ${name}`);
|
|
11359
11904
|
}
|
|
11360
11905
|
relPaths.push(rel);
|
|
11361
|
-
await
|
|
11362
|
-
await
|
|
11906
|
+
await mkdir10(path20.dirname(dest), { recursive: true });
|
|
11907
|
+
await writeFile8(dest, data);
|
|
11363
11908
|
}
|
|
11364
11909
|
return await finalizeThemeImport(projectDir, tmp, relPaths);
|
|
11365
11910
|
} finally {
|
|
@@ -11372,10 +11917,10 @@ async function importThemeFromCssText(projectDir, css, name) {
|
|
|
11372
11917
|
throw new Error(`The CSS could not be parsed — ${reject.message}`);
|
|
11373
11918
|
}
|
|
11374
11919
|
const displayName = name.trim() || "Imported theme";
|
|
11375
|
-
const tmp = await mkdtemp3(
|
|
11920
|
+
const tmp = await mkdtemp3(path20.join(tmpdir3(), "gutterpress-theme-"));
|
|
11376
11921
|
try {
|
|
11377
|
-
await
|
|
11378
|
-
await
|
|
11922
|
+
await writeFile8(path20.join(tmp, "theme.css"), css, "utf8");
|
|
11923
|
+
await writeFile8(path20.join(tmp, "theme.json"), JSON.stringify({ name: displayName }, null, 2), "utf8");
|
|
11379
11924
|
const result = await finalizeThemeImport(projectDir, tmp, ["theme.css", "theme.json"]);
|
|
11380
11925
|
result.warnings.unshift({
|
|
11381
11926
|
code: "no-theme-json",
|
|
@@ -11387,14 +11932,14 @@ async function importThemeFromCssText(projectDir, css, name) {
|
|
|
11387
11932
|
}
|
|
11388
11933
|
}
|
|
11389
11934
|
async function importThemeFromFile(projectDir, filePath) {
|
|
11390
|
-
const ext =
|
|
11935
|
+
const ext = path20.extname(filePath).toLowerCase();
|
|
11391
11936
|
if (ext === ".zip") {
|
|
11392
|
-
const buf = await
|
|
11937
|
+
const buf = await readFile29(filePath);
|
|
11393
11938
|
return importThemeFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
11394
11939
|
}
|
|
11395
11940
|
if (ext === ".css") {
|
|
11396
|
-
const css = await
|
|
11397
|
-
return importThemeFromCssText(projectDir, css, prettify(
|
|
11941
|
+
const css = await readFile29(filePath, "utf8");
|
|
11942
|
+
return importThemeFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
|
|
11398
11943
|
}
|
|
11399
11944
|
throw new Error("Choose a .zip theme package or a .css stylesheet.");
|
|
11400
11945
|
}
|
|
@@ -11903,25 +12448,25 @@ ${tail}` : ""}`);
|
|
|
11903
12448
|
};
|
|
11904
12449
|
|
|
11905
12450
|
// src/lib/publish/providers/guided.ts
|
|
11906
|
-
import { copyFile as copyFile2, mkdir as
|
|
11907
|
-
import
|
|
12451
|
+
import { copyFile as copyFile2, mkdir as mkdir11, writeFile as writeFile9 } from "node:fs/promises";
|
|
12452
|
+
import path21 from "node:path";
|
|
11908
12453
|
async function stageGuidedPackage(req, providerId, listingLines) {
|
|
11909
|
-
const packageDir =
|
|
11910
|
-
await
|
|
11911
|
-
await copyFile2(req.artifact.path,
|
|
12454
|
+
const packageDir = path21.join(path21.dirname(req.artifact.path), "publish", providerId);
|
|
12455
|
+
await mkdir11(packageDir, { recursive: true });
|
|
12456
|
+
await copyFile2(req.artifact.path, path21.join(packageDir, path21.basename(req.artifact.path)));
|
|
11912
12457
|
const { title, authors } = req.project;
|
|
11913
12458
|
const sheet = [
|
|
11914
12459
|
`# ${title || "Untitled"}`,
|
|
11915
12460
|
"",
|
|
11916
12461
|
`- **Authors:** ${authors.length ? authors.join(", ") : "(not set)"}`,
|
|
11917
|
-
`- **File:** ${
|
|
12462
|
+
`- **File:** ${path21.basename(req.artifact.path)}`,
|
|
11918
12463
|
...listingLines,
|
|
11919
12464
|
"",
|
|
11920
12465
|
"Copy these details into the platform's listing form when uploading.",
|
|
11921
12466
|
""
|
|
11922
12467
|
].join(`
|
|
11923
12468
|
`);
|
|
11924
|
-
await
|
|
12469
|
+
await writeFile9(path21.join(packageDir, "LISTING.md"), sheet, "utf8");
|
|
11925
12470
|
return packageDir;
|
|
11926
12471
|
}
|
|
11927
12472
|
|
|
@@ -11984,9 +12529,9 @@ var drivethrurpgProvider = {
|
|
|
11984
12529
|
};
|
|
11985
12530
|
|
|
11986
12531
|
// src/lib/publish/providers/gdrive.ts
|
|
11987
|
-
import { mkdtemp as mkdtemp4, readFile as
|
|
12532
|
+
import { mkdtemp as mkdtemp4, readFile as readFile30, readdir as readdir7, rm as rm6, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
|
|
11988
12533
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
11989
|
-
import
|
|
12534
|
+
import path22 from "node:path";
|
|
11990
12535
|
import { Zip, ZipDeflate } from "fflate";
|
|
11991
12536
|
|
|
11992
12537
|
// src/lib/publish/google-drive.ts
|
|
@@ -12371,34 +12916,34 @@ function zipEntriesNonBlocking(entries, opts) {
|
|
|
12371
12916
|
async function collectZipEntries(root, dir, out, onWarn) {
|
|
12372
12917
|
const entries = await readdir7(dir, { withFileTypes: true });
|
|
12373
12918
|
for (const entry of entries) {
|
|
12374
|
-
const full =
|
|
12919
|
+
const full = path22.join(dir, entry.name);
|
|
12375
12920
|
if (entry.isDirectory()) {
|
|
12376
12921
|
await collectZipEntries(root, full, out, onWarn);
|
|
12377
12922
|
} else if (entry.isFile()) {
|
|
12378
|
-
const rel =
|
|
12379
|
-
out[rel] = await
|
|
12923
|
+
const rel = path22.relative(root, full).split(path22.sep).join("/");
|
|
12924
|
+
out[rel] = await readFile30(full);
|
|
12380
12925
|
} else if (entry.isSymbolicLink()) {
|
|
12381
12926
|
const resolved = await stat7(full).catch(() => null);
|
|
12382
12927
|
if (resolved?.isFile()) {
|
|
12383
|
-
const rel =
|
|
12384
|
-
out[rel] = await
|
|
12928
|
+
const rel = path22.relative(root, full).split(path22.sep).join("/");
|
|
12929
|
+
out[rel] = await readFile30(full);
|
|
12385
12930
|
} else if (resolved?.isDirectory()) {
|
|
12386
12931
|
await collectZipEntries(root, full, out, onWarn);
|
|
12387
12932
|
} else {
|
|
12388
|
-
onWarn?.(`Skipped "${
|
|
12933
|
+
onWarn?.(`Skipped "${path22.relative(root, full)}" in the website export — it's a broken symlink.`);
|
|
12389
12934
|
}
|
|
12390
12935
|
} else {
|
|
12391
|
-
onWarn?.(`Skipped "${
|
|
12936
|
+
onWarn?.(`Skipped "${path22.relative(root, full)}" in the website export — not a file, folder, or symlink.`);
|
|
12392
12937
|
}
|
|
12393
12938
|
}
|
|
12394
12939
|
}
|
|
12395
|
-
async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl =
|
|
12940
|
+
async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeFile10) {
|
|
12396
12941
|
const entries = {};
|
|
12397
12942
|
await collectZipEntries(exportDir, exportDir, entries, onWarn);
|
|
12398
12943
|
const fileName = `${bookSlug(title)}-website.zip`;
|
|
12399
|
-
const tmpRoot = await mkdtemp4(
|
|
12944
|
+
const tmpRoot = await mkdtemp4(path22.join(tmpdir4(), "gutterpress-gdrive-"));
|
|
12400
12945
|
try {
|
|
12401
|
-
const filePath =
|
|
12946
|
+
const filePath = path22.join(tmpRoot, fileName);
|
|
12402
12947
|
const zipped = await zipEntriesNonBlocking(entries, { level: 6 });
|
|
12403
12948
|
await writeArchiveImpl(filePath, zipped);
|
|
12404
12949
|
return {
|
|
@@ -12446,7 +12991,7 @@ var gdriveProvider = {
|
|
|
12446
12991
|
const isHtml = req.artifact.format === "html";
|
|
12447
12992
|
const source = isHtml ? await zipHtmlExport(req.artifact.path, req.project.title, (msg) => req.deps.onProgress?.(msg)) : {
|
|
12448
12993
|
filePath: req.artifact.path,
|
|
12449
|
-
fileName:
|
|
12994
|
+
fileName: path22.basename(req.artifact.path),
|
|
12450
12995
|
mimeType: "application/pdf",
|
|
12451
12996
|
cleanup: async () => {}
|
|
12452
12997
|
};
|
|
@@ -12496,8 +13041,8 @@ var gdriveProvider = {
|
|
|
12496
13041
|
};
|
|
12497
13042
|
|
|
12498
13043
|
// src/lib/publish/butler.ts
|
|
12499
|
-
import { chmod, mkdir as
|
|
12500
|
-
import
|
|
13044
|
+
import { chmod, mkdir as mkdir12, stat as stat8, writeFile as writeFile11 } from "node:fs/promises";
|
|
13045
|
+
import path23 from "node:path";
|
|
12501
13046
|
import { unzipSync as unzipSync2 } from "fflate";
|
|
12502
13047
|
var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
|
|
12503
13048
|
function butlerBrothChannel(platform = process.platform, arch = process.arch) {
|
|
@@ -12534,8 +13079,8 @@ async function ensureButler(deps) {
|
|
|
12534
13079
|
}
|
|
12535
13080
|
if (await commandExists("butler", runCommand, deps.env))
|
|
12536
13081
|
return "butler";
|
|
12537
|
-
const cacheDir =
|
|
12538
|
-
const cached =
|
|
13082
|
+
const cacheDir = path23.join(deps.configDir ?? defaultConfigDir(), "tools", "butler");
|
|
13083
|
+
const cached = path23.join(cacheDir, butlerBinaryName());
|
|
12539
13084
|
if (await fileExists2(cached))
|
|
12540
13085
|
return cached;
|
|
12541
13086
|
const channel = butlerBrothChannel();
|
|
@@ -12557,14 +13102,14 @@ async function ensureButler(deps) {
|
|
|
12557
13102
|
return new Uint8Array(await response.arrayBuffer());
|
|
12558
13103
|
});
|
|
12559
13104
|
const files = unzipSync2(archive);
|
|
12560
|
-
await
|
|
13105
|
+
await mkdir12(cacheDir, { recursive: true });
|
|
12561
13106
|
let foundBinary = false;
|
|
12562
13107
|
for (const [name, data] of Object.entries(files)) {
|
|
12563
13108
|
if (name.endsWith("/"))
|
|
12564
13109
|
continue;
|
|
12565
|
-
const target =
|
|
12566
|
-
await
|
|
12567
|
-
if (
|
|
13110
|
+
const target = path23.join(cacheDir, path23.basename(name));
|
|
13111
|
+
await writeFile11(target, data);
|
|
13112
|
+
if (path23.basename(name) === butlerBinaryName()) {
|
|
12568
13113
|
await chmod(target, 493).catch(() => {});
|
|
12569
13114
|
foundBinary = true;
|
|
12570
13115
|
}
|
|
@@ -12994,7 +13539,7 @@ function publishProviderFor(id) {
|
|
|
12994
13539
|
|
|
12995
13540
|
// src/lib/publish/run-publish.ts
|
|
12996
13541
|
import { readdir as readdir8, stat as stat9 } from "node:fs/promises";
|
|
12997
|
-
import
|
|
13542
|
+
import path24 from "node:path";
|
|
12998
13543
|
function resolvePublishFormat(info8, providerConfig) {
|
|
12999
13544
|
if (info8.formats && info8.formats.length > 0) {
|
|
13000
13545
|
const configured = typeof providerConfig.format === "string" ? providerConfig.format.trim() : "";
|
|
@@ -13015,9 +13560,9 @@ async function resolvePublishRequest(options, deps) {
|
|
|
13015
13560
|
const effectiveDeps = effectiveAccount ? { ...deps, credentialAccount: effectiveAccount } : deps;
|
|
13016
13561
|
const outDir = resolveOutputDir(manifestDir, config.title);
|
|
13017
13562
|
const effectiveFormat = resolvePublishFormat(provider.info, providerConfig);
|
|
13018
|
-
const defaultArtifact = effectiveFormat === "pdf" ?
|
|
13563
|
+
const defaultArtifact = effectiveFormat === "pdf" ? path24.join(outDir, artifactName(config.title, "pdf")) : outDir;
|
|
13019
13564
|
const artifact = {
|
|
13020
|
-
path: options.artifactPath ?
|
|
13565
|
+
path: options.artifactPath ? path24.resolve(options.projectDir, options.artifactPath) : defaultArtifact,
|
|
13021
13566
|
format: effectiveFormat
|
|
13022
13567
|
};
|
|
13023
13568
|
return {
|
|
@@ -13081,7 +13626,7 @@ async function artifactIssues(artifact) {
|
|
|
13081
13626
|
var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
|
|
13082
13627
|
async function htmlDirIssues(dir) {
|
|
13083
13628
|
const issues = [];
|
|
13084
|
-
const isThere = async (rel) => stat9(
|
|
13629
|
+
const isThere = async (rel) => stat9(path24.join(dir, rel)).then(() => true, () => false);
|
|
13085
13630
|
if (!await isThere(BOOK_HTML)) {
|
|
13086
13631
|
issues.push({
|
|
13087
13632
|
severity: "error",
|
|
@@ -13313,17 +13858,17 @@ import git6 from "isomorphic-git";
|
|
|
13313
13858
|
import git5 from "isomorphic-git";
|
|
13314
13859
|
import diff3Merge from "diff3";
|
|
13315
13860
|
// src/lib/publish/selections.ts
|
|
13316
|
-
import { chmod as chmod2, mkdir as
|
|
13317
|
-
import
|
|
13861
|
+
import { chmod as chmod2, mkdir as mkdir13, readFile as readFile31, writeFile as writeFile12 } from "node:fs/promises";
|
|
13862
|
+
import path25 from "node:path";
|
|
13318
13863
|
class PublishSelectionsStore {
|
|
13319
13864
|
filePath;
|
|
13320
13865
|
queue = Promise.resolve();
|
|
13321
13866
|
constructor(filePath) {
|
|
13322
|
-
this.filePath = filePath ??
|
|
13867
|
+
this.filePath = filePath ?? path25.join(defaultConfigDir(), "publish-selections.json");
|
|
13323
13868
|
}
|
|
13324
13869
|
async read() {
|
|
13325
13870
|
try {
|
|
13326
|
-
const raw = await
|
|
13871
|
+
const raw = await readFile31(this.filePath, "utf8");
|
|
13327
13872
|
const parsed = JSON.parse(raw);
|
|
13328
13873
|
if (parsed && typeof parsed === "object") {
|
|
13329
13874
|
return {
|
|
@@ -13336,8 +13881,8 @@ class PublishSelectionsStore {
|
|
|
13336
13881
|
return { version: 1, global: {}, projects: {} };
|
|
13337
13882
|
}
|
|
13338
13883
|
async write(data) {
|
|
13339
|
-
await
|
|
13340
|
-
await
|
|
13884
|
+
await mkdir13(path25.dirname(this.filePath), { recursive: true });
|
|
13885
|
+
await writeFile12(this.filePath, JSON.stringify(data, null, 2), {
|
|
13341
13886
|
encoding: "utf8",
|
|
13342
13887
|
mode: 384
|
|
13343
13888
|
});
|
|
@@ -13402,4 +13947,4 @@ function normalizeProjectKey(projectDir) {
|
|
|
13402
13947
|
}
|
|
13403
13948
|
// src/lib/publish/manifest-publish.ts
|
|
13404
13949
|
import { isMap as isMap4 } from "yaml";
|
|
13405
|
-
export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, loadManifestWithPath, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, BUILT_IN_THEME_IDS, listBuiltInThemes, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, removeProjectTheme, detectLegacyForkedTheme, scaffoldProject, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, importThemeFromFile, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listPublishProviders, publishProviderFor, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };
|
|
13950
|
+
export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, loadManifestWithPath, parseNpmPluginSpec, runLint, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, BUILT_IN_THEME_IDS, listBuiltInThemes, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, removeProjectTheme, detectLegacyForkedTheme, scaffoldProject, EXTENSION_KINDS, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, addNpmPlugin, importThemeFromFile, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listPublishProviders, publishProviderFor, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };
|