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
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
findTool,
|
|
11
11
|
fullInstallHint,
|
|
12
12
|
getAssetPath,
|
|
13
|
+
getAssetsDir,
|
|
13
14
|
getBrowser,
|
|
14
15
|
isToolAvailable,
|
|
15
16
|
prewarmBrowser,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
resolveChromiumExecutable,
|
|
18
19
|
run,
|
|
19
20
|
spawnCapture
|
|
20
|
-
} from "./index-
|
|
21
|
+
} from "./index-9pbaznww.js";
|
|
21
22
|
import {
|
|
22
23
|
gitFs,
|
|
23
24
|
gitScopeFor,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
} from "./index-37x76zdn.js";
|
|
39
40
|
|
|
40
41
|
// src/lib/build-runner.ts
|
|
41
|
-
import
|
|
42
|
+
import path9 from "node:path";
|
|
42
43
|
import fs2 from "node:fs";
|
|
43
44
|
import fsp2 from "node:fs/promises";
|
|
44
45
|
import os2 from "node:os";
|
|
@@ -643,7 +644,7 @@ function escapeHtml(s) {
|
|
|
643
644
|
function escapeAttr(s) {
|
|
644
645
|
return String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
645
646
|
}
|
|
646
|
-
function parseMarkerLine(line) {
|
|
647
|
+
function parseMarkerLine(line, declaredWords) {
|
|
647
648
|
const trimmed = line.trim();
|
|
648
649
|
if (!trimmed.startsWith("@"))
|
|
649
650
|
return null;
|
|
@@ -677,9 +678,9 @@ function parseMarkerLine(line) {
|
|
|
677
678
|
tokens[i] = unwrapAttrBraces(tokens[i]);
|
|
678
679
|
const head = tokens[0];
|
|
679
680
|
const kind = head.slice(1);
|
|
680
|
-
if (!KNOWN_KINDS.includes(kind))
|
|
681
|
+
if (!KNOWN_KINDS.includes(kind) && !(declaredWords && declaredWords.has(kind)))
|
|
681
682
|
return null;
|
|
682
|
-
if (kind === "page-break" || kind === "column-break" || kind === "
|
|
683
|
+
if (kind === "page-break" || kind === "column-break" || kind === "continue" || kind.startsWith("end-")) {
|
|
683
684
|
return { kind, name: null, attrs: {} };
|
|
684
685
|
}
|
|
685
686
|
const body = tokens.slice(1);
|
|
@@ -782,12 +783,14 @@ function attachDataAttrs(token, kind, name, attrs) {
|
|
|
782
783
|
if (name) {
|
|
783
784
|
if (kind === "chapter")
|
|
784
785
|
token.attrSet("data-chapter-label", name);
|
|
785
|
-
if (kind === "spread")
|
|
786
|
+
else if (kind === "spread")
|
|
786
787
|
token.attrSet("data-spread", name);
|
|
787
|
-
if (kind === "page")
|
|
788
|
+
else if (kind === "page")
|
|
788
789
|
token.attrSet("data-page", name);
|
|
789
|
-
if (kind === "section")
|
|
790
|
+
else if (kind === "section")
|
|
790
791
|
token.attrSet("data-section", name);
|
|
792
|
+
else
|
|
793
|
+
token.attrSet(`data-${kind}`, name);
|
|
791
794
|
}
|
|
792
795
|
if (attrs.template)
|
|
793
796
|
token.attrSet("data-template", attrs.template);
|
|
@@ -803,16 +806,193 @@ function attachDataAttrs(token, kind, name, attrs) {
|
|
|
803
806
|
token.attrSet(`data-${k}`, v);
|
|
804
807
|
}
|
|
805
808
|
}
|
|
809
|
+
var DECLARED_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
810
|
+
function validateDeclaredMarkerName(name, pluginName) {
|
|
811
|
+
if (!DECLARED_NAME_RE.test(name)) {
|
|
812
|
+
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").`);
|
|
813
|
+
}
|
|
814
|
+
if (name.startsWith("end-")) {
|
|
815
|
+
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.`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
var TAG_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
819
|
+
function resolveContainerShape(name, pluginName, decl) {
|
|
820
|
+
const tag = decl.tag !== undefined ? decl.tag : "div";
|
|
821
|
+
if (typeof tag !== "string" || !TAG_NAME_RE.test(tag)) {
|
|
822
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an invalid \`tag\` (${JSON.stringify(decl.tag)}) — ` + `must be a lower-case HTML tag name.`);
|
|
823
|
+
}
|
|
824
|
+
const classBase = decl.class !== undefined ? decl.class : "";
|
|
825
|
+
if (typeof classBase !== "string") {
|
|
826
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`class\` that is not a string.`);
|
|
827
|
+
}
|
|
828
|
+
let variants;
|
|
829
|
+
if (decl.variants !== undefined) {
|
|
830
|
+
if (typeof decl.variants !== "object" || decl.variants === null || Array.isArray(decl.variants)) {
|
|
831
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has \`variants\` that is not a plain object.`);
|
|
832
|
+
}
|
|
833
|
+
variants = {};
|
|
834
|
+
for (const [variantName, variantClass] of Object.entries(decl.variants)) {
|
|
835
|
+
if (typeof variantClass !== "string") {
|
|
836
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" variant "${variantName}" is not a string.`);
|
|
837
|
+
}
|
|
838
|
+
variants[variantName] = variantClass;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
let label;
|
|
842
|
+
if (decl.label !== undefined) {
|
|
843
|
+
if (typeof decl.label !== "object" || decl.label === null || Array.isArray(decl.label)) {
|
|
844
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`label\` that is not an object.`);
|
|
845
|
+
}
|
|
846
|
+
if (typeof decl.label.class !== "string" || !decl.label.class) {
|
|
847
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`label.class\` that is not a non-empty string.`);
|
|
848
|
+
}
|
|
849
|
+
const match = typeof decl.label.from === "string" ? /^attr:([A-Za-z_][A-Za-z0-9_-]*)$/.exec(decl.label.from) : null;
|
|
850
|
+
if (!match) {
|
|
851
|
+
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.`);
|
|
852
|
+
}
|
|
853
|
+
const labelTag = decl.label.tag !== undefined ? decl.label.tag : "div";
|
|
854
|
+
if (typeof labelTag !== "string" || !TAG_NAME_RE.test(labelTag)) {
|
|
855
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an invalid \`label.tag\` (${JSON.stringify(decl.label.tag)}).`);
|
|
856
|
+
}
|
|
857
|
+
label = { class: decl.label.class, attr: match[1], tag: labelTag };
|
|
858
|
+
}
|
|
859
|
+
let autoCloseAtEof = false;
|
|
860
|
+
if (decl.autoCloseAt !== undefined) {
|
|
861
|
+
if (!Array.isArray(decl.autoCloseAt)) {
|
|
862
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an \`autoCloseAt\` that is not an array.`);
|
|
863
|
+
}
|
|
864
|
+
for (const value of decl.autoCloseAt) {
|
|
865
|
+
if (value !== "eof") {
|
|
866
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" declares an unsupported \`autoCloseAt\` value ` + `${JSON.stringify(value)} — only "eof" is currently supported.`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
autoCloseAtEof = decl.autoCloseAt.includes("eof");
|
|
870
|
+
}
|
|
871
|
+
return { tag, classBase, variants, label, autoCloseAtEof };
|
|
872
|
+
}
|
|
873
|
+
function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
|
|
874
|
+
const pluginName = originOf.get(name);
|
|
875
|
+
if (typeof rawDecl !== "object" || rawDecl === null || Array.isArray(rawDecl)) {
|
|
876
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" is not a plain object.`);
|
|
877
|
+
}
|
|
878
|
+
if (rawDecl.deprecated !== undefined) {
|
|
879
|
+
if (typeof rawDecl.deprecated !== "string" || !rawDecl.deprecated) {
|
|
880
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`deprecated\` value that is not a non-empty string.`);
|
|
881
|
+
}
|
|
882
|
+
return { name, deprecated: rawDecl.deprecated };
|
|
883
|
+
}
|
|
884
|
+
if (rawDecl.alias !== undefined) {
|
|
885
|
+
if (typeof rawDecl.alias !== "string" || !rawDecl.alias) {
|
|
886
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has an \`alias\` value that is not a non-empty string.`);
|
|
887
|
+
}
|
|
888
|
+
const targetRaw = rawRegistry.get(rawDecl.alias);
|
|
889
|
+
if (!targetRaw) {
|
|
890
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases unknown marker "@${rawDecl.alias}" — no ` + `loaded plugin declares it.`);
|
|
891
|
+
}
|
|
892
|
+
if (targetRaw.deprecated !== undefined) {
|
|
893
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases "@${rawDecl.alias}", which is deprecated: ` + `${targetRaw.deprecated}`);
|
|
894
|
+
}
|
|
895
|
+
if (targetRaw.alias !== undefined) {
|
|
896
|
+
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.`);
|
|
897
|
+
}
|
|
898
|
+
const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
|
|
899
|
+
if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
|
|
900
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
|
|
901
|
+
}
|
|
902
|
+
if (preset.variant !== undefined && typeof preset.variant !== "string") {
|
|
903
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
|
|
904
|
+
}
|
|
905
|
+
const container = resolveContainerShape(rawDecl.alias, originOf.get(rawDecl.alias), targetRaw);
|
|
906
|
+
return { name, baseKind: rawDecl.alias, presetVariant: preset.variant, ...container };
|
|
907
|
+
}
|
|
908
|
+
return { name, baseKind: name, ...resolveContainerShape(name, pluginName, rawDecl) };
|
|
909
|
+
}
|
|
910
|
+
function buildDeclaredMarkerRegistry(sources) {
|
|
911
|
+
const rawRegistry = new Map;
|
|
912
|
+
const originOf = new Map;
|
|
913
|
+
for (const { pluginName, markers } of sources) {
|
|
914
|
+
if (!markers)
|
|
915
|
+
continue;
|
|
916
|
+
for (const [name, rawDecl] of Object.entries(markers)) {
|
|
917
|
+
validateDeclaredMarkerName(name, pluginName);
|
|
918
|
+
if (KNOWN_KINDS.includes(name)) {
|
|
919
|
+
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.`);
|
|
920
|
+
}
|
|
921
|
+
if (rawRegistry.has(name)) {
|
|
922
|
+
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.`);
|
|
923
|
+
}
|
|
924
|
+
rawRegistry.set(name, rawDecl);
|
|
925
|
+
originOf.set(name, pluginName);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
const resolved = new Map;
|
|
929
|
+
for (const [name, rawDecl] of rawRegistry) {
|
|
930
|
+
resolved.set(name, resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf));
|
|
931
|
+
}
|
|
932
|
+
return resolved;
|
|
933
|
+
}
|
|
934
|
+
function declaredKindWords(declaredMarkers) {
|
|
935
|
+
if (!declaredMarkers)
|
|
936
|
+
return null;
|
|
937
|
+
const words = new Set;
|
|
938
|
+
for (const name of declaredMarkers.keys()) {
|
|
939
|
+
words.add(name);
|
|
940
|
+
words.add(`end-${name}`);
|
|
941
|
+
}
|
|
942
|
+
return words;
|
|
943
|
+
}
|
|
944
|
+
function nearestDeclaredMarkerName(word, declaredMarkers) {
|
|
945
|
+
const w = word.toLowerCase();
|
|
946
|
+
const threshold = Math.max(2, Math.floor(w.length / 3));
|
|
947
|
+
let best = null;
|
|
948
|
+
let bestDistance = Infinity;
|
|
949
|
+
for (const known of declaredMarkers.keys()) {
|
|
950
|
+
const d = editDistance(w, known);
|
|
951
|
+
if (d < bestDistance) {
|
|
952
|
+
bestDistance = d;
|
|
953
|
+
best = known;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return best !== null && bestDistance > 0 && bestDistance <= threshold ? best : null;
|
|
957
|
+
}
|
|
958
|
+
function scanForUnknownDeclaredMarkers(state, declaredMarkers) {
|
|
959
|
+
if (!declaredMarkers)
|
|
960
|
+
return;
|
|
961
|
+
for (let tokenIndex = 0;tokenIndex < state.tokens.length; tokenIndex++) {
|
|
962
|
+
const tok = state.tokens[tokenIndex];
|
|
963
|
+
if (tok.type !== "inline" || !tok.map)
|
|
964
|
+
continue;
|
|
965
|
+
const opener = state.tokens[tokenIndex - 1];
|
|
966
|
+
if (!opener || opener.type !== "paragraph_open")
|
|
967
|
+
continue;
|
|
968
|
+
const lines = tok.content.split(`
|
|
969
|
+
`);
|
|
970
|
+
for (let i = 0;i < lines.length; i++) {
|
|
971
|
+
const m = /^@([A-Za-z][A-Za-z0-9-]*)(?=\s|$)/.exec(lines[i].trim());
|
|
972
|
+
if (!m)
|
|
973
|
+
continue;
|
|
974
|
+
const word = m[1];
|
|
975
|
+
if (KNOWN_KINDS.includes(word) || declaredMarkers.has(word))
|
|
976
|
+
continue;
|
|
977
|
+
const suggestion = nearestDeclaredMarkerName(word, declaredMarkers);
|
|
978
|
+
if (!suggestion)
|
|
979
|
+
continue;
|
|
980
|
+
warn(state.env, tok.map[0] + i + 1, "unknown_marker", `Unknown marker "@${word}". Did you mean "@${suggestion}"?`, null);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
806
984
|
function plugin(md, pluginOptions = {}) {
|
|
807
985
|
const options = {
|
|
808
986
|
preferPagesInSpreads: false,
|
|
809
987
|
...pluginOptions
|
|
810
988
|
};
|
|
989
|
+
const declaredMarkers = options.declaredMarkers instanceof Map && options.declaredMarkers.size > 0 ? options.declaredMarkers : null;
|
|
990
|
+
const declaredWords = declaredKindWords(declaredMarkers);
|
|
811
991
|
function markerBlock(state, startLine, endLine, silent) {
|
|
812
992
|
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
813
993
|
const max = state.eMarks[startLine];
|
|
814
994
|
const line = state.src.slice(pos, max);
|
|
815
|
-
const parsed = parseMarkerLine(line);
|
|
995
|
+
const parsed = parseMarkerLine(line, declaredWords);
|
|
816
996
|
if (!parsed)
|
|
817
997
|
return false;
|
|
818
998
|
if (silent)
|
|
@@ -869,10 +1049,64 @@ function plugin(md, pluginOptions = {}) {
|
|
|
869
1049
|
}
|
|
870
1050
|
}
|
|
871
1051
|
md.core.ruler.after("block", "layout_transform", function(state) {
|
|
1052
|
+
scanForUnknownDeclaredMarkers(state, declaredMarkers);
|
|
872
1053
|
if (!state.env.__layoutMarkersUsed)
|
|
873
1054
|
return;
|
|
874
1055
|
scanForMistypedMarkers(state);
|
|
875
1056
|
const out = [];
|
|
1057
|
+
const declaredFrames = [];
|
|
1058
|
+
function declaredFrameIndex(kind) {
|
|
1059
|
+
for (let i = declaredFrames.length - 1;i >= 0; i--) {
|
|
1060
|
+
if (declaredFrames[i].kind === kind)
|
|
1061
|
+
return i;
|
|
1062
|
+
}
|
|
1063
|
+
return -1;
|
|
1064
|
+
}
|
|
1065
|
+
function closeDeclaredFrame(kind) {
|
|
1066
|
+
const at = declaredFrameIndex(kind);
|
|
1067
|
+
if (at === -1)
|
|
1068
|
+
return false;
|
|
1069
|
+
while (declaredFrames.length > at) {
|
|
1070
|
+
const frame = declaredFrames.pop();
|
|
1071
|
+
out.push(new state.Token("layout_component_close", frame.tag, -1));
|
|
1072
|
+
}
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
function drainDeclaredFrames() {
|
|
1076
|
+
while (declaredFrames.length) {
|
|
1077
|
+
const frame = declaredFrames.pop();
|
|
1078
|
+
out.push(new state.Token("layout_component_close", frame.tag, -1));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function drainDeclaredFramesAtEof() {
|
|
1082
|
+
while (declaredFrames.length) {
|
|
1083
|
+
const frame = declaredFrames.pop();
|
|
1084
|
+
if (!frame.decl.autoCloseAtEof) {
|
|
1085
|
+
warn(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);
|
|
1086
|
+
}
|
|
1087
|
+
out.push(new state.Token("layout_component_close", frame.tag, -1));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
function openDeclaredMarker(meta, decl) {
|
|
1091
|
+
const t = new state.Token("layout_component_open", decl.tag, 1);
|
|
1092
|
+
t.meta = { line: meta.__line };
|
|
1093
|
+
const variant = meta.name || decl.presetVariant || null;
|
|
1094
|
+
const variantClass = variant && decl.variants && decl.variants[variant] || "";
|
|
1095
|
+
const baseClass = [decl.classBase, variantClass].filter(Boolean).join(" ");
|
|
1096
|
+
addClasses(t, baseClass, meta.attrs && meta.attrs.class ? meta.attrs.class : "");
|
|
1097
|
+
attachDataAttrs(t, decl.baseKind, variant, meta.attrs || {});
|
|
1098
|
+
out.push(t);
|
|
1099
|
+
declaredFrames.push({ kind: decl.baseKind, tag: decl.tag, decl, line: meta.__line });
|
|
1100
|
+
if (decl.label) {
|
|
1101
|
+
const value = meta.attrs && meta.attrs[decl.label.attr];
|
|
1102
|
+
if (value) {
|
|
1103
|
+
const labelToken = new state.Token("html_block", "", 0);
|
|
1104
|
+
labelToken.content = `<${decl.label.tag} class="${escapeAttr(decl.label.class)}">${escapeHtml(value)}</${decl.label.tag}>
|
|
1105
|
+
`;
|
|
1106
|
+
out.push(labelToken);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
876
1110
|
const stack = {
|
|
877
1111
|
frames: [],
|
|
878
1112
|
get(kind) {
|
|
@@ -1022,6 +1256,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1022
1256
|
const kind = meta.kind;
|
|
1023
1257
|
const line = meta.__line || 0;
|
|
1024
1258
|
if (kind === "chapter") {
|
|
1259
|
+
drainDeclaredFrames();
|
|
1025
1260
|
stack.close("chapter");
|
|
1026
1261
|
openChapter(meta);
|
|
1027
1262
|
continue;
|
|
@@ -1030,16 +1265,19 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1030
1265
|
if (stack.has("spread")) {
|
|
1031
1266
|
warn(state.env, line, "nested_spread", "@spread encountered while another spread is open; closing the previous spread automatically.", meta);
|
|
1032
1267
|
}
|
|
1268
|
+
drainDeclaredFrames();
|
|
1033
1269
|
stack.close("spread");
|
|
1034
1270
|
openSpread(meta);
|
|
1035
1271
|
continue;
|
|
1036
1272
|
}
|
|
1037
1273
|
if (kind === "page") {
|
|
1274
|
+
drainDeclaredFrames();
|
|
1038
1275
|
stack.close("page");
|
|
1039
1276
|
openPage(meta);
|
|
1040
1277
|
continue;
|
|
1041
1278
|
}
|
|
1042
1279
|
if (kind === "section") {
|
|
1280
|
+
drainDeclaredFrames();
|
|
1043
1281
|
warnIfEmptyDecoratedSection("section", line);
|
|
1044
1282
|
stack.close("section");
|
|
1045
1283
|
const spread = stack.get("spread");
|
|
@@ -1055,6 +1293,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1055
1293
|
warn(state.env, line, "continue_without_section", "@continue used without an open @section; ignoring marker.", meta);
|
|
1056
1294
|
continue;
|
|
1057
1295
|
}
|
|
1296
|
+
drainDeclaredFrames();
|
|
1058
1297
|
const contMeta = {
|
|
1059
1298
|
name: section.meta.name,
|
|
1060
1299
|
attrs: { ...section.meta.attrs || {} }
|
|
@@ -1087,15 +1326,42 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1087
1326
|
continue;
|
|
1088
1327
|
}
|
|
1089
1328
|
if (kind === "end-section") {
|
|
1329
|
+
drainDeclaredFrames();
|
|
1090
1330
|
warnIfEmptyDecoratedSection("end-section", line);
|
|
1091
1331
|
stack.close("section");
|
|
1092
1332
|
continue;
|
|
1093
1333
|
}
|
|
1334
|
+
if (declaredMarkers) {
|
|
1335
|
+
const openDecl = declaredMarkers.get(kind);
|
|
1336
|
+
if (openDecl) {
|
|
1337
|
+
if (openDecl.deprecated) {
|
|
1338
|
+
warn(state.env, line, "deprecated_marker", `@${kind} is deprecated: ${openDecl.deprecated}`, meta);
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
closeDeclaredFrame(openDecl.baseKind);
|
|
1342
|
+
openDeclaredMarker(meta, openDecl);
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
if (kind.startsWith("end-")) {
|
|
1346
|
+
const closeDecl = declaredMarkers.get(kind.slice(4));
|
|
1347
|
+
if (closeDecl) {
|
|
1348
|
+
if (closeDecl.deprecated) {
|
|
1349
|
+
warn(state.env, line, "deprecated_marker", `@${kind} is deprecated: ${closeDecl.deprecated}`, meta);
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
if (!closeDeclaredFrame(closeDecl.baseKind)) {
|
|
1353
|
+
warn(state.env, line, "declared_marker_close_without_open", `@${kind} used without an open @${closeDecl.baseKind}; ignoring marker.`, meta);
|
|
1354
|
+
}
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1094
1359
|
}
|
|
1095
1360
|
const eofSpread = stack.get("spread");
|
|
1096
1361
|
if (eofSpread && !eofSpread.sawAnyPage) {
|
|
1097
1362
|
warn(state.env, 0, "spread_eof_close", "An open @spread reached end-of-document; closing it automatically.", null);
|
|
1098
1363
|
}
|
|
1364
|
+
drainDeclaredFramesAtEof();
|
|
1099
1365
|
stack.closeAll();
|
|
1100
1366
|
state.tokens = out;
|
|
1101
1367
|
});
|
|
@@ -2154,7 +2420,9 @@ function createMarkdownRenderer(customPlugins) {
|
|
|
2154
2420
|
md.use(unwrapPlugin(markdownItFootnote));
|
|
2155
2421
|
md.use(unwrapPlugin(markdownItDeflist));
|
|
2156
2422
|
md.use(unwrapPlugin(markdownItSourceMap));
|
|
2157
|
-
|
|
2423
|
+
const declaredMarkerSources = (customPlugins ?? []).filter((p) => !!p.markers).map((p) => ({ pluginName: p.name, markers: p.markers }));
|
|
2424
|
+
const declaredMarkers = buildDeclaredMarkerRegistry(declaredMarkerSources);
|
|
2425
|
+
md.use(plugin, { declaredMarkers });
|
|
2158
2426
|
md.use(gpPinScope);
|
|
2159
2427
|
registerImageRule(md);
|
|
2160
2428
|
if (customPlugins && customPlugins.length > 0) {
|
|
@@ -2734,8 +3002,8 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
|
|
|
2734
3002
|
|
|
2735
3003
|
// src/lib/markdown/plugins.ts
|
|
2736
3004
|
import { createHash as createHash3 } from "node:crypto";
|
|
2737
|
-
import { existsSync as
|
|
2738
|
-
import { cp, link, mkdir as mkdir2, mkdtemp, readFile as
|
|
3005
|
+
import { existsSync as existsSync5, lstatSync, realpathSync, rmSync, statSync as statSync2, unlinkSync } from "node:fs";
|
|
3006
|
+
import { cp, link, mkdir as mkdir2, mkdtemp, readFile as readFile6, rm, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
2739
3007
|
import { tmpdir } from "node:os";
|
|
2740
3008
|
import { resolve as resolve3, join as join2, dirname as dirname2, basename, extname, relative, isAbsolute } from "node:path";
|
|
2741
3009
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -3327,6 +3595,70 @@ function resolveDeclaredStyles(rawStyles, baseDir, subject) {
|
|
|
3327
3595
|
return abs;
|
|
3328
3596
|
});
|
|
3329
3597
|
}
|
|
3598
|
+
|
|
3599
|
+
// src/lib/extension-manifest.ts
|
|
3600
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3601
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
3602
|
+
import path4 from "node:path";
|
|
3603
|
+
var EXTENSION_MANIFEST_FILENAME = "gutterpress.json";
|
|
3604
|
+
var LEGACY_THEME_MANIFEST_FILENAME = "theme.json";
|
|
3605
|
+
async function readJsonMetaFile(jsonPath) {
|
|
3606
|
+
try {
|
|
3607
|
+
const parsed = JSON.parse(await readFile5(jsonPath, "utf8"));
|
|
3608
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
3609
|
+
} catch {
|
|
3610
|
+
return {};
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
async function readExtensionMeta(dir) {
|
|
3614
|
+
const extensionPath = path4.join(dir, EXTENSION_MANIFEST_FILENAME);
|
|
3615
|
+
if (existsSync4(extensionPath)) {
|
|
3616
|
+
return readJsonMetaFile(extensionPath);
|
|
3617
|
+
}
|
|
3618
|
+
return readJsonMetaFile(path4.join(dir, LEGACY_THEME_MANIFEST_FILENAME));
|
|
3619
|
+
}
|
|
3620
|
+
function declaredList(value) {
|
|
3621
|
+
return Array.isArray(value) ? value.filter((s) => typeof s === "string" && s.trim().length > 0) : [];
|
|
3622
|
+
}
|
|
3623
|
+
function extensionStyleList(meta) {
|
|
3624
|
+
return declaredList(meta.styles);
|
|
3625
|
+
}
|
|
3626
|
+
function extensionEngineStyleList(meta) {
|
|
3627
|
+
return declaredList(meta.engineStyles?.native);
|
|
3628
|
+
}
|
|
3629
|
+
function pathEscapesFolder(rel) {
|
|
3630
|
+
return path4.isAbsolute(rel) || rel.split(/[\\/]/).includes("..");
|
|
3631
|
+
}
|
|
3632
|
+
function assertExtensionContained(meta) {
|
|
3633
|
+
const declared = [
|
|
3634
|
+
...extensionStyleList(meta),
|
|
3635
|
+
...extensionEngineStyleList(meta),
|
|
3636
|
+
...meta.markdown ? [meta.markdown] : [],
|
|
3637
|
+
...meta.components ? [meta.components] : [],
|
|
3638
|
+
...meta.snippets ? [meta.snippets] : [],
|
|
3639
|
+
...meta.tokensFile ? [meta.tokensFile] : []
|
|
3640
|
+
];
|
|
3641
|
+
for (const rel of declared) {
|
|
3642
|
+
if (pathEscapesFolder(rel)) {
|
|
3643
|
+
throw new Error(`Extension metadata declares "${rel}" outside its own folder; an extension must be self-contained.`);
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
function resolveExtension(dir, meta, subject) {
|
|
3648
|
+
const styles = resolveDeclaredStyles(extensionStyleList(meta), dir, subject);
|
|
3649
|
+
const engineStyles = resolveDeclaredStyles(extensionEngineStyleList(meta), dir, subject);
|
|
3650
|
+
const [markdown] = resolveDeclaredStyles(meta.markdown ? [meta.markdown] : undefined, dir, subject) ?? [];
|
|
3651
|
+
const [components] = resolveDeclaredStyles(meta.components ? [meta.components] : undefined, dir, subject) ?? [];
|
|
3652
|
+
const [snippets] = resolveDeclaredStyles(meta.snippets ? [meta.snippets] : undefined, dir, subject) ?? [];
|
|
3653
|
+
return {
|
|
3654
|
+
...markdown ? { markdown } : {},
|
|
3655
|
+
...styles ? { styles } : {},
|
|
3656
|
+
...engineStyles ? { engineStyles } : {},
|
|
3657
|
+
...meta.tokensFile?.trim() ? { tokensFile: meta.tokensFile.trim() } : {},
|
|
3658
|
+
...components ? { components } : {},
|
|
3659
|
+
...snippets ? { snippets } : {}
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3330
3662
|
// src/lib/markdown/plugins.ts
|
|
3331
3663
|
var moduleInternals = Module;
|
|
3332
3664
|
var vendorCjsTrees = new Map;
|
|
@@ -3617,7 +3949,7 @@ async function rewriteReachableModuleImports(entryPath, tree) {
|
|
|
3617
3949
|
const pkg = packageOwningFile(tree, absolute);
|
|
3618
3950
|
if (!pkg)
|
|
3619
3951
|
throw new Error(`Reachable plugin module is outside the verified package tree: ${absolute}`);
|
|
3620
|
-
const source = await
|
|
3952
|
+
const source = await readFile6(absolute, "utf8");
|
|
3621
3953
|
const [imports] = parseModuleImports(source, absolute);
|
|
3622
3954
|
const replacements = [];
|
|
3623
3955
|
const targets = new Set;
|
|
@@ -3848,12 +4180,22 @@ function validateStylesExport(value, pluginRef) {
|
|
|
3848
4180
|
}
|
|
3849
4181
|
return value.length > 0 ? value : undefined;
|
|
3850
4182
|
}
|
|
4183
|
+
function validateMarkersExport(value, pluginRef) {
|
|
4184
|
+
if (value === undefined)
|
|
4185
|
+
return;
|
|
4186
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
4187
|
+
throw new Error(`Plugin "${pluginRef}" exports \`markers\` that is not a plain object.`);
|
|
4188
|
+
}
|
|
4189
|
+
const table = value;
|
|
4190
|
+
return Object.keys(table).length > 0 ? table : undefined;
|
|
4191
|
+
}
|
|
3851
4192
|
function extractPluginExports(pluginModule, pluginRef, exportName) {
|
|
3852
4193
|
const mod = pluginModule !== null && (typeof pluginModule === "object" || typeof pluginModule === "function") ? pluginModule : {};
|
|
3853
4194
|
let plugin2;
|
|
3854
4195
|
let metadata = mod.metadata;
|
|
3855
4196
|
let css = mod.css;
|
|
3856
4197
|
let styles = validateStylesExport(mod.styles, pluginRef);
|
|
4198
|
+
let markers = validateMarkersExport(mod.markers, pluginRef);
|
|
3857
4199
|
if (exportName && typeof mod[exportName] === "function") {
|
|
3858
4200
|
plugin2 = mod[exportName];
|
|
3859
4201
|
} else if (exportName) {
|
|
@@ -3869,11 +4211,12 @@ function extractPluginExports(pluginModule, pluginRef, exportName) {
|
|
|
3869
4211
|
metadata = inner.metadata ?? metadata;
|
|
3870
4212
|
css = inner.css ?? css;
|
|
3871
4213
|
styles = validateStylesExport(inner.styles, pluginRef) ?? styles;
|
|
4214
|
+
markers = validateMarkersExport(inner.markers, pluginRef) ?? markers;
|
|
3872
4215
|
}
|
|
3873
4216
|
if (typeof plugin2 !== "function") {
|
|
3874
4217
|
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) { ... }\`.`);
|
|
3875
4218
|
}
|
|
3876
|
-
return { plugin: plugin2, metadata, css, styles };
|
|
4219
|
+
return { plugin: plugin2, metadata, css, styles, markers };
|
|
3877
4220
|
}
|
|
3878
4221
|
var pathPluginCache = new Map;
|
|
3879
4222
|
var liveShadowPaths = new Set;
|
|
@@ -3944,6 +4287,48 @@ function resolvePluginStyles(rawStyles, moduleDir, pluginRef) {
|
|
|
3944
4287
|
}
|
|
3945
4288
|
return resolveDeclaredStyles(rawStyles, moduleDir, `Plugin "${pluginRef}"`);
|
|
3946
4289
|
}
|
|
4290
|
+
function noopPlugin() {}
|
|
4291
|
+
function extensionMetadata(meta) {
|
|
4292
|
+
if (!meta.name && !meta.description && !meta.author)
|
|
4293
|
+
return;
|
|
4294
|
+
return {
|
|
4295
|
+
...meta.name ? { name: meta.name } : {},
|
|
4296
|
+
...meta.description ? { description: meta.description } : {},
|
|
4297
|
+
...meta.author ? { author: meta.author } : {}
|
|
4298
|
+
};
|
|
4299
|
+
}
|
|
4300
|
+
async function loadExtensionFromDir(extensionDir, config, pluginRef) {
|
|
4301
|
+
const meta = await readExtensionMeta(extensionDir);
|
|
4302
|
+
assertExtensionContained(meta);
|
|
4303
|
+
const resolved = resolveExtension(extensionDir, meta, `Plugin "${pluginRef}"`);
|
|
4304
|
+
const extensionStyles = [...resolved.styles ?? [], ...resolved.engineStyles ?? []];
|
|
4305
|
+
const name = config.name ?? meta.name ?? pluginRef;
|
|
4306
|
+
if (!resolved.markdown && extensionStyles.length === 0) {
|
|
4307
|
+
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.");
|
|
4308
|
+
}
|
|
4309
|
+
if (!resolved.markdown) {
|
|
4310
|
+
return {
|
|
4311
|
+
name,
|
|
4312
|
+
plugin: noopPlugin,
|
|
4313
|
+
...extensionMetadata(meta) ? { metadata: extensionMetadata(meta) } : {},
|
|
4314
|
+
...extensionStyles.length > 0 ? { styles: extensionStyles } : {},
|
|
4315
|
+
options: config.options
|
|
4316
|
+
};
|
|
4317
|
+
}
|
|
4318
|
+
const pluginModule = await loadCachedPathPluginModule(resolved.markdown);
|
|
4319
|
+
const { plugin: plugin2, metadata, css, styles: rawStyles, markers } = extractPluginExports(pluginModule, pluginRef, config.export);
|
|
4320
|
+
const ownStyles = resolvePluginStyles(rawStyles, dirname2(resolved.markdown), pluginRef);
|
|
4321
|
+
const styles = [...extensionStyles, ...ownStyles ?? []];
|
|
4322
|
+
return {
|
|
4323
|
+
name,
|
|
4324
|
+
plugin: plugin2,
|
|
4325
|
+
metadata: metadata ?? extensionMetadata(meta),
|
|
4326
|
+
css,
|
|
4327
|
+
...styles.length > 0 ? { styles } : {},
|
|
4328
|
+
...markers ? { markers } : {},
|
|
4329
|
+
options: config.options
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
3947
4332
|
async function loadPlugin(config, baseDir) {
|
|
3948
4333
|
const pluginRef = config.path ?? config.name ?? "(unspecified)";
|
|
3949
4334
|
let pluginModule;
|
|
@@ -3959,10 +4344,21 @@ async function loadPlugin(config, baseDir) {
|
|
|
3959
4344
|
options: config.options
|
|
3960
4345
|
};
|
|
3961
4346
|
}
|
|
4347
|
+
if (config.path) {
|
|
4348
|
+
const candidatePath = resolve3(baseDir, config.path);
|
|
4349
|
+
if (existsSync5(candidatePath) && statSync2(candidatePath).isDirectory()) {
|
|
4350
|
+
try {
|
|
4351
|
+
return await loadExtensionFromDir(candidatePath, config, pluginRef);
|
|
4352
|
+
} catch (error2) {
|
|
4353
|
+
const errorMsg = error2 instanceof Error ? error2.message : String(error2);
|
|
4354
|
+
throw new Error(`Failed to load plugin "${pluginRef}": ${errorMsg}`);
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
3962
4358
|
try {
|
|
3963
4359
|
if (config.path) {
|
|
3964
4360
|
const pluginPath = resolve3(baseDir, config.path);
|
|
3965
|
-
if (!
|
|
4361
|
+
if (!existsSync5(pluginPath)) {
|
|
3966
4362
|
throw new Error(`Plugin file not found: ${pluginPath} (resolved from manifest entry path="${config.path}")`);
|
|
3967
4363
|
}
|
|
3968
4364
|
pluginModule = await loadCachedPathPluginModule(pluginPath);
|
|
@@ -3978,7 +4374,7 @@ async function loadPlugin(config, baseDir) {
|
|
|
3978
4374
|
const errorMsg = error2 instanceof Error ? error2.message : String(error2);
|
|
3979
4375
|
throw new Error(`Failed to load plugin "${pluginRef}": ${errorMsg}`);
|
|
3980
4376
|
}
|
|
3981
|
-
const { plugin: plugin2, metadata, css, styles: rawStyles } = extractPluginExports(pluginModule, pluginRef, config.export);
|
|
4377
|
+
const { plugin: plugin2, metadata, css, styles: rawStyles, markers } = extractPluginExports(pluginModule, pluginRef, config.export);
|
|
3982
4378
|
const styles = resolvePluginStyles(rawStyles, moduleDir, pluginRef);
|
|
3983
4379
|
return {
|
|
3984
4380
|
name: pluginName,
|
|
@@ -3986,6 +4382,7 @@ async function loadPlugin(config, baseDir) {
|
|
|
3986
4382
|
metadata,
|
|
3987
4383
|
css,
|
|
3988
4384
|
styles,
|
|
4385
|
+
markers,
|
|
3989
4386
|
options: config.options
|
|
3990
4387
|
};
|
|
3991
4388
|
}
|
|
@@ -4017,7 +4414,7 @@ async function loadPluginsWithCss(configs, baseDir, onError) {
|
|
|
4017
4414
|
}
|
|
4018
4415
|
|
|
4019
4416
|
// src/lib/output-paths.ts
|
|
4020
|
-
import
|
|
4417
|
+
import path5 from "node:path";
|
|
4021
4418
|
|
|
4022
4419
|
// src/lib/slug.ts
|
|
4023
4420
|
function slugify(name, fallback = "") {
|
|
@@ -4035,15 +4432,15 @@ function bookSlug(title) {
|
|
|
4035
4432
|
return slugify(title ?? "", "book");
|
|
4036
4433
|
}
|
|
4037
4434
|
function resolveOutputDir(manifestDir, title) {
|
|
4038
|
-
return
|
|
4435
|
+
return path5.resolve(manifestDir, DIST_DIRNAME, bookSlug(title));
|
|
4039
4436
|
}
|
|
4040
4437
|
function artifactName(title, format) {
|
|
4041
4438
|
return `${bookSlug(title)}-${format}.pdf`;
|
|
4042
4439
|
}
|
|
4043
4440
|
|
|
4044
4441
|
// src/lib/ghostscript.ts
|
|
4045
|
-
import { existsSync as
|
|
4046
|
-
import { readFile as
|
|
4442
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
4443
|
+
import { readFile as readFile7, writeFile as writeFile3, unlink as unlink2, rename, readdir as readdir4 } from "node:fs/promises";
|
|
4047
4444
|
import { join as join3, basename as basename2, resolve as resolve4 } from "node:path";
|
|
4048
4445
|
var WINDOWS_GHOSTSCRIPT_NAMES = ["gswin64c", "gswin32c", "gs"];
|
|
4049
4446
|
var POSIX_GHOSTSCRIPT_NAMES = ["gs"];
|
|
@@ -4077,7 +4474,7 @@ async function findWindowsGhostscriptInstall(env) {
|
|
|
4077
4474
|
for (const version of versions) {
|
|
4078
4475
|
for (const executable of ["gswin64c.exe", "gswin32c.exe"]) {
|
|
4079
4476
|
const candidate = join3(installRoot, version.name, "bin", executable);
|
|
4080
|
-
if (
|
|
4477
|
+
if (existsSync6(candidate))
|
|
4081
4478
|
return candidate;
|
|
4082
4479
|
}
|
|
4083
4480
|
}
|
|
@@ -4086,7 +4483,7 @@ async function findWindowsGhostscriptInstall(env) {
|
|
|
4086
4483
|
}
|
|
4087
4484
|
async function resolveGhostscript(targetPlatform = process.platform, env = process.env) {
|
|
4088
4485
|
const override = envValue(env, "GHOSTSCRIPT_PATH");
|
|
4089
|
-
if (override &&
|
|
4486
|
+
if (override && existsSync6(override))
|
|
4090
4487
|
return override;
|
|
4091
4488
|
const names = targetPlatform === "win32" ? WINDOWS_GHOSTSCRIPT_NAMES : POSIX_GHOSTSCRIPT_NAMES;
|
|
4092
4489
|
for (const name of names) {
|
|
@@ -4145,7 +4542,7 @@ async function stripAnnotations(pdfPath, stagingDir) {
|
|
|
4145
4542
|
await unlink2(tmp).catch(() => {});
|
|
4146
4543
|
}
|
|
4147
4544
|
const { PDFDocument, PDFName } = await import("pdf-lib");
|
|
4148
|
-
const bytes = await
|
|
4545
|
+
const bytes = await readFile7(pdfPath);
|
|
4149
4546
|
const doc = await PDFDocument.load(bytes, { updateMetadata: false });
|
|
4150
4547
|
const annots = PDFName.of("Annots");
|
|
4151
4548
|
for (const page of doc.getPages())
|
|
@@ -4220,7 +4617,7 @@ ${docInfoConformance}/DOCINFO pdfmark
|
|
|
4220
4617
|
}
|
|
4221
4618
|
async function stampCreator(pdfPath) {
|
|
4222
4619
|
const { PDFDocument } = await import("pdf-lib");
|
|
4223
|
-
const bytes = await
|
|
4620
|
+
const bytes = await readFile7(pdfPath);
|
|
4224
4621
|
const doc = await PDFDocument.load(bytes, { updateMetadata: false });
|
|
4225
4622
|
doc.setCreator("gutterpress");
|
|
4226
4623
|
const out = await doc.save();
|
|
@@ -4271,14 +4668,14 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
|
|
|
4271
4668
|
|
|
4272
4669
|
// src/lib/build-fingerprint.ts
|
|
4273
4670
|
import * as fs from "node:fs";
|
|
4274
|
-
import { mkdir as mkdir3, readFile as
|
|
4671
|
+
import { mkdir as mkdir3, readFile as readFile8, writeFile as writeFile4 } from "node:fs/promises";
|
|
4275
4672
|
import { createHash as createHash4 } from "node:crypto";
|
|
4276
|
-
import
|
|
4673
|
+
import path6 from "node:path";
|
|
4277
4674
|
import git from "isomorphic-git";
|
|
4278
4675
|
// package.json
|
|
4279
4676
|
var package_default = {
|
|
4280
4677
|
name: "gutterpress",
|
|
4281
|
-
version: "0.10.
|
|
4678
|
+
version: "0.10.8",
|
|
4282
4679
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
4283
4680
|
author: "itlackey",
|
|
4284
4681
|
license: "MPL-2.0",
|
|
@@ -4435,7 +4832,7 @@ async function getGitRevision(sourceDir) {
|
|
|
4435
4832
|
const candidateDirs = [sourceDir, process.cwd()].filter((v) => Boolean(v));
|
|
4436
4833
|
const seen = new Set;
|
|
4437
4834
|
for (const dir of candidateDirs) {
|
|
4438
|
-
const abs =
|
|
4835
|
+
const abs = path6.resolve(dir);
|
|
4439
4836
|
if (seen.has(abs))
|
|
4440
4837
|
continue;
|
|
4441
4838
|
seen.add(abs);
|
|
@@ -4471,8 +4868,8 @@ async function getEngineBundleHash() {
|
|
|
4471
4868
|
getAssetPath("engine/gutterpress-agent.js")
|
|
4472
4869
|
]);
|
|
4473
4870
|
const [viewerBytes, agentBytes] = await Promise.all([
|
|
4474
|
-
|
|
4475
|
-
|
|
4871
|
+
readFile8(viewerPath),
|
|
4872
|
+
readFile8(agentPath)
|
|
4476
4873
|
]);
|
|
4477
4874
|
const hash = createHash4("sha256");
|
|
4478
4875
|
hash.update(viewerBytes);
|
|
@@ -4520,8 +4917,8 @@ function sanitizeArgs(args) {
|
|
|
4520
4917
|
return out;
|
|
4521
4918
|
}
|
|
4522
4919
|
async function writeBuildFingerprint(input) {
|
|
4523
|
-
const outputDir =
|
|
4524
|
-
const outPath =
|
|
4920
|
+
const outputDir = path6.resolve(input.outputDir);
|
|
4921
|
+
const outPath = path6.join(outputDir, FINGERPRINT_FILENAME);
|
|
4525
4922
|
const [tools, sourceRevision] = await Promise.all([
|
|
4526
4923
|
getToolVersions(),
|
|
4527
4924
|
getGitRevision(input.sourceDir)
|
|
@@ -4537,7 +4934,7 @@ async function writeBuildFingerprint(input) {
|
|
|
4537
4934
|
iccPath: input.pdfx.iccPath,
|
|
4538
4935
|
stripAnnotations: input.pdfx.stripAnnotations
|
|
4539
4936
|
},
|
|
4540
|
-
outputDir: input.recordedOutputDir ?
|
|
4937
|
+
outputDir: input.recordedOutputDir ? path6.resolve(input.recordedOutputDir) : outputDir
|
|
4541
4938
|
},
|
|
4542
4939
|
sourceRevision,
|
|
4543
4940
|
tools
|
|
@@ -4549,7 +4946,7 @@ async function writeBuildFingerprint(input) {
|
|
|
4549
4946
|
|
|
4550
4947
|
// src/lib/lint-runner.ts
|
|
4551
4948
|
import { resolve as resolve5 } from "node:path";
|
|
4552
|
-
import { readFile as
|
|
4949
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
4553
4950
|
|
|
4554
4951
|
// src/lib/printsafe.ts
|
|
4555
4952
|
import postcss2 from "postcss";
|
|
@@ -4790,7 +5187,10 @@ async function runLint(opts = {}) {
|
|
|
4790
5187
|
} else {
|
|
4791
5188
|
const relStyles = await resolveActiveStyles(manifestDir, resolved.styles);
|
|
4792
5189
|
const projectFiles = relStyles.map((rel) => resolve5(manifestDir, rel));
|
|
4793
|
-
|
|
5190
|
+
let pluginStylePaths = opts.pluginStylePaths;
|
|
5191
|
+
if (pluginStylePaths === undefined) {
|
|
5192
|
+
({ pluginStylePaths } = await loadPluginsWithCss(resolved.plugins, manifestDir, (ref, err) => log.warn(`Skipping plugin "${ref}" for lint — ${err.message}`)));
|
|
5193
|
+
}
|
|
4794
5194
|
files = [...projectFiles, ...pluginStylePaths].filter((f) => !f.endsWith(".min.css"));
|
|
4795
5195
|
}
|
|
4796
5196
|
if (files.length === 0) {
|
|
@@ -4804,7 +5204,7 @@ async function runLint(opts = {}) {
|
|
|
4804
5204
|
for (const file of files) {
|
|
4805
5205
|
let css;
|
|
4806
5206
|
try {
|
|
4807
|
-
css = await
|
|
5207
|
+
css = await readFile9(file, "utf8");
|
|
4808
5208
|
} catch (err) {
|
|
4809
5209
|
log.error(` ${file}`);
|
|
4810
5210
|
log.error(` cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4837,7 +5237,7 @@ async function runLint(opts = {}) {
|
|
|
4837
5237
|
}
|
|
4838
5238
|
|
|
4839
5239
|
// src/lib/validation-exec.ts
|
|
4840
|
-
import { existsSync as
|
|
5240
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
4841
5241
|
import { dirname as dirname4, join as join4, resolve as resolve9 } from "node:path";
|
|
4842
5242
|
|
|
4843
5243
|
// src/checks/formatter.ts
|
|
@@ -5008,7 +5408,7 @@ function inspectionFailed(checkId, message, extra = {}) {
|
|
|
5008
5408
|
}
|
|
5009
5409
|
|
|
5010
5410
|
// src/lib/pdf-inspect.ts
|
|
5011
|
-
import { stat, readFile as
|
|
5411
|
+
import { stat, readFile as readFile10 } from "node:fs/promises";
|
|
5012
5412
|
import { getDocumentProxy } from "unpdf";
|
|
5013
5413
|
import * as pdfjs from "unpdf/pdfjs";
|
|
5014
5414
|
function getOps(mod) {
|
|
@@ -5044,17 +5444,17 @@ function evictLru() {
|
|
|
5044
5444
|
destroyEntryAfterGrace(evicted);
|
|
5045
5445
|
}
|
|
5046
5446
|
}
|
|
5047
|
-
async function loadPdf(
|
|
5447
|
+
async function loadPdf(path7) {
|
|
5048
5448
|
let st;
|
|
5049
5449
|
try {
|
|
5050
|
-
st = await stat(
|
|
5450
|
+
st = await stat(path7);
|
|
5051
5451
|
} catch {
|
|
5052
5452
|
return null;
|
|
5053
5453
|
}
|
|
5054
|
-
const hit = docCache.get(
|
|
5454
|
+
const hit = docCache.get(path7);
|
|
5055
5455
|
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
|
|
5056
|
-
docCache.delete(
|
|
5057
|
-
docCache.set(
|
|
5456
|
+
docCache.delete(path7);
|
|
5457
|
+
docCache.set(path7, hit);
|
|
5058
5458
|
try {
|
|
5059
5459
|
return await hit.doc;
|
|
5060
5460
|
} catch {
|
|
@@ -5063,15 +5463,15 @@ async function loadPdf(path6) {
|
|
|
5063
5463
|
}
|
|
5064
5464
|
if (hit)
|
|
5065
5465
|
destroyEntry(hit);
|
|
5066
|
-
const docPromise =
|
|
5067
|
-
docCache.delete(
|
|
5068
|
-
docCache.set(
|
|
5466
|
+
const docPromise = readFile10(path7).then((buf) => getDocumentProxy(new Uint8Array(buf)));
|
|
5467
|
+
docCache.delete(path7);
|
|
5468
|
+
docCache.set(path7, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
|
|
5069
5469
|
evictLru();
|
|
5070
5470
|
try {
|
|
5071
5471
|
return await docPromise;
|
|
5072
5472
|
} catch {
|
|
5073
|
-
if (docCache.get(
|
|
5074
|
-
docCache.delete(
|
|
5473
|
+
if (docCache.get(path7)?.doc === docPromise)
|
|
5474
|
+
docCache.delete(path7);
|
|
5075
5475
|
return null;
|
|
5076
5476
|
}
|
|
5077
5477
|
}
|
|
@@ -5522,9 +5922,9 @@ var check3 = {
|
|
|
5522
5922
|
registerCheck(check3);
|
|
5523
5923
|
|
|
5524
5924
|
// src/lib/pdf-parse.ts
|
|
5525
|
-
import { readFile as
|
|
5925
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
5526
5926
|
async function readPdfBytes(pdfPath) {
|
|
5527
|
-
return
|
|
5927
|
+
return readFile11(pdfPath, "latin1");
|
|
5528
5928
|
}
|
|
5529
5929
|
function parseInkCov(out) {
|
|
5530
5930
|
const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
|
|
@@ -6095,21 +6495,21 @@ var check15 = {
|
|
|
6095
6495
|
registerCheck(check15);
|
|
6096
6496
|
|
|
6097
6497
|
// src/checks/source/markdownlint.ts
|
|
6098
|
-
import { readFile as
|
|
6498
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
6099
6499
|
import { lint } from "markdownlint/sync";
|
|
6100
6500
|
import { parse as parseYaml2 } from "yaml";
|
|
6101
6501
|
|
|
6102
6502
|
// src/checks/source/config-file.ts
|
|
6103
|
-
import { existsSync as
|
|
6503
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
6104
6504
|
import { resolve as resolve6 } from "node:path";
|
|
6105
6505
|
function findConfigFile(inputDir, names, explicit) {
|
|
6106
6506
|
if (explicit) {
|
|
6107
6507
|
const p = resolve6(inputDir, explicit);
|
|
6108
|
-
return
|
|
6508
|
+
return existsSync7(p) ? p : null;
|
|
6109
6509
|
}
|
|
6110
6510
|
for (const name of names) {
|
|
6111
6511
|
const p = resolve6(inputDir, name);
|
|
6112
|
-
if (
|
|
6512
|
+
if (existsSync7(p))
|
|
6113
6513
|
return p;
|
|
6114
6514
|
}
|
|
6115
6515
|
return null;
|
|
@@ -6128,7 +6528,7 @@ function stripJsonComments(s) {
|
|
|
6128
6528
|
return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
6129
6529
|
}
|
|
6130
6530
|
async function loadConfig(configPath) {
|
|
6131
|
-
const raw = await
|
|
6531
|
+
const raw = await readFile12(configPath, "utf8");
|
|
6132
6532
|
let parsed;
|
|
6133
6533
|
if (configPath.endsWith(".json")) {
|
|
6134
6534
|
parsed = JSON.parse(raw);
|
|
@@ -6187,8 +6587,8 @@ var check16 = {
|
|
|
6187
6587
|
registerCheck(check16);
|
|
6188
6588
|
|
|
6189
6589
|
// src/checks/source/htmlhint.ts
|
|
6190
|
-
import { existsSync as
|
|
6191
|
-
import { readFile as
|
|
6590
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
6591
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
6192
6592
|
import htmlhint from "htmlhint";
|
|
6193
6593
|
var { HTMLHint } = htmlhint;
|
|
6194
6594
|
var CONFIG_NAMES2 = [".htmlhintrc"];
|
|
@@ -6202,14 +6602,14 @@ var check17 = {
|
|
|
6202
6602
|
const sourceConfig = ctx.config.validate.source;
|
|
6203
6603
|
if (sourceConfig.htmlhint === false)
|
|
6204
6604
|
return [];
|
|
6205
|
-
if (!ctx.htmlPath || !
|
|
6605
|
+
if (!ctx.htmlPath || !existsSync8(ctx.htmlPath))
|
|
6206
6606
|
return [];
|
|
6207
6607
|
const configPath = typeof sourceConfig.htmlhint === "string" ? sourceConfig.htmlhint : null;
|
|
6208
6608
|
const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
|
|
6209
6609
|
if (!resolvedConfig && !configPath)
|
|
6210
6610
|
return [];
|
|
6211
|
-
const ruleset = resolvedConfig ? JSON.parse(await
|
|
6212
|
-
const html = await
|
|
6611
|
+
const ruleset = resolvedConfig ? JSON.parse(await readFile13(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
|
|
6612
|
+
const html = await readFile13(ctx.htmlPath, "utf8");
|
|
6213
6613
|
const messages = HTMLHint.verify(html, ruleset);
|
|
6214
6614
|
return messages.map((m) => ({
|
|
6215
6615
|
checkId: check17.id,
|
|
@@ -6224,7 +6624,7 @@ var check17 = {
|
|
|
6224
6624
|
registerCheck(check17);
|
|
6225
6625
|
|
|
6226
6626
|
// src/checks/source/stylelint.ts
|
|
6227
|
-
import { readFile as
|
|
6627
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
6228
6628
|
var check18 = {
|
|
6229
6629
|
id: "source.stylelint",
|
|
6230
6630
|
name: "Print-safety CSS",
|
|
@@ -6242,7 +6642,7 @@ var check18 = {
|
|
|
6242
6642
|
for (const file of files) {
|
|
6243
6643
|
let css;
|
|
6244
6644
|
try {
|
|
6245
|
-
css = await
|
|
6645
|
+
css = await readFile14(file, "utf8");
|
|
6246
6646
|
} catch {
|
|
6247
6647
|
continue;
|
|
6248
6648
|
}
|
|
@@ -6263,12 +6663,12 @@ var check18 = {
|
|
|
6263
6663
|
registerCheck(check18);
|
|
6264
6664
|
|
|
6265
6665
|
// src/checks/source/css-ownership.ts
|
|
6266
|
-
import { readFile as
|
|
6666
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
6267
6667
|
import postcss3 from "postcss";
|
|
6268
6668
|
import { parse as parseYaml3 } from "yaml";
|
|
6269
6669
|
var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
|
|
6270
6670
|
async function loadContract(configPath) {
|
|
6271
|
-
const raw = await
|
|
6671
|
+
const raw = await readFile15(configPath, "utf8");
|
|
6272
6672
|
const parsed = parseYaml3(raw);
|
|
6273
6673
|
if (parsed && typeof parsed === "object")
|
|
6274
6674
|
return parsed;
|
|
@@ -6419,7 +6819,7 @@ var check19 = {
|
|
|
6419
6819
|
continue;
|
|
6420
6820
|
let css;
|
|
6421
6821
|
try {
|
|
6422
|
-
css = await
|
|
6822
|
+
css = await readFile15(absFile, "utf8");
|
|
6423
6823
|
} catch {
|
|
6424
6824
|
continue;
|
|
6425
6825
|
}
|
|
@@ -6461,8 +6861,8 @@ var check19 = {
|
|
|
6461
6861
|
registerCheck(check19);
|
|
6462
6862
|
|
|
6463
6863
|
// src/checks/source/local-refs.ts
|
|
6464
|
-
import { existsSync as
|
|
6465
|
-
import { readFile as
|
|
6864
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
6865
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
6466
6866
|
import { dirname as dirname3, resolve as resolve7 } from "node:path";
|
|
6467
6867
|
|
|
6468
6868
|
// src/checks/source/local-ref-parser.ts
|
|
@@ -6542,7 +6942,7 @@ var check20 = {
|
|
|
6542
6942
|
const collectRenderedLocalRefs = createRenderedLocalRefCollector(plugins);
|
|
6543
6943
|
for (const file of files) {
|
|
6544
6944
|
try {
|
|
6545
|
-
const content = await
|
|
6945
|
+
const content = await readFile16(file, "utf8");
|
|
6546
6946
|
for (const { ref, kind, line } of collectRenderedLocalRefs(content)) {
|
|
6547
6947
|
if (isNonFilesystemRef(ref))
|
|
6548
6948
|
continue;
|
|
@@ -6584,15 +6984,15 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
|
|
|
6584
6984
|
const rootRelative = resolve7(projectRoot, cleaned);
|
|
6585
6985
|
switch (kind) {
|
|
6586
6986
|
case "image":
|
|
6587
|
-
return
|
|
6987
|
+
return existsSync9(rootRelative);
|
|
6588
6988
|
case "link":
|
|
6589
|
-
return
|
|
6989
|
+
return existsSync9(fileRelative);
|
|
6590
6990
|
}
|
|
6591
6991
|
}
|
|
6592
6992
|
registerCheck(check20);
|
|
6593
6993
|
|
|
6594
6994
|
// src/checks/source/accessibility-alt-text.ts
|
|
6595
|
-
import { readFile as
|
|
6995
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
6596
6996
|
var check21 = {
|
|
6597
6997
|
id: "source.accessibility.alt-text",
|
|
6598
6998
|
name: "Image Alt Text",
|
|
@@ -6606,7 +7006,7 @@ var check21 = {
|
|
|
6606
7006
|
const results = [];
|
|
6607
7007
|
for (const file of files) {
|
|
6608
7008
|
try {
|
|
6609
|
-
const content = await
|
|
7009
|
+
const content = await readFile17(file, "utf8");
|
|
6610
7010
|
const lines = content.split(`
|
|
6611
7011
|
`);
|
|
6612
7012
|
let inFence = false;
|
|
@@ -6650,7 +7050,7 @@ function extractImageAlts(line) {
|
|
|
6650
7050
|
registerCheck(check21);
|
|
6651
7051
|
|
|
6652
7052
|
// src/checks/source/accessibility-heading-order.ts
|
|
6653
|
-
import { readFile as
|
|
7053
|
+
import { readFile as readFile18 } from "node:fs/promises";
|
|
6654
7054
|
var check22 = {
|
|
6655
7055
|
id: "source.accessibility.heading-order",
|
|
6656
7056
|
name: "Heading Order",
|
|
@@ -6664,7 +7064,7 @@ var check22 = {
|
|
|
6664
7064
|
const results = [];
|
|
6665
7065
|
for (const file of files) {
|
|
6666
7066
|
try {
|
|
6667
|
-
const content = await
|
|
7067
|
+
const content = await readFile18(file, "utf8");
|
|
6668
7068
|
const lines = content.split(`
|
|
6669
7069
|
`);
|
|
6670
7070
|
let inFence = false;
|
|
@@ -6700,7 +7100,7 @@ var check22 = {
|
|
|
6700
7100
|
registerCheck(check22);
|
|
6701
7101
|
|
|
6702
7102
|
// src/checks/source/layout-markers.ts
|
|
6703
|
-
import { readFile as
|
|
7103
|
+
import { readFile as readFile19 } from "node:fs/promises";
|
|
6704
7104
|
var check23 = {
|
|
6705
7105
|
id: "source.markdown.layout-markers",
|
|
6706
7106
|
name: "Layout Markers",
|
|
@@ -6718,7 +7118,7 @@ var check23 = {
|
|
|
6718
7118
|
const md = createMarkdownRenderer(plugins);
|
|
6719
7119
|
for (const file of files) {
|
|
6720
7120
|
try {
|
|
6721
|
-
const content = await
|
|
7121
|
+
const content = await readFile19(file, "utf8");
|
|
6722
7122
|
const env = {};
|
|
6723
7123
|
md.render(content, env);
|
|
6724
7124
|
for (const w of env.layoutWarnings ?? []) {
|
|
@@ -6740,8 +7140,8 @@ var check23 = {
|
|
|
6740
7140
|
registerCheck(check23);
|
|
6741
7141
|
|
|
6742
7142
|
// src/checks/source/merge-markers.ts
|
|
6743
|
-
import { readFile as
|
|
6744
|
-
import
|
|
7143
|
+
import { readFile as readFile20 } from "node:fs/promises";
|
|
7144
|
+
import path7 from "node:path";
|
|
6745
7145
|
|
|
6746
7146
|
// src/checks/asset/extensions.ts
|
|
6747
7147
|
var RASTER_INSPECTABLE_EXTS = [
|
|
@@ -6782,7 +7182,7 @@ var check24 = {
|
|
|
6782
7182
|
for (const file of textFiles) {
|
|
6783
7183
|
let content;
|
|
6784
7184
|
try {
|
|
6785
|
-
content = await
|
|
7185
|
+
content = await readFile20(file, "utf8");
|
|
6786
7186
|
} catch {
|
|
6787
7187
|
results.push(inspectionFailed(check24.id, `Could not read source file: ${file}`, { file }));
|
|
6788
7188
|
continue;
|
|
@@ -6825,7 +7225,7 @@ var check24 = {
|
|
|
6825
7225
|
ignore: [...ASSET_SCAN_IGNORE_GLOBS]
|
|
6826
7226
|
});
|
|
6827
7227
|
for (const sibling of siblings.sort()) {
|
|
6828
|
-
const rel =
|
|
7228
|
+
const rel = path7.relative(ctx.inputDir, sibling).split(path7.sep).join("/");
|
|
6829
7229
|
results.push(finding(check24.id, {
|
|
6830
7230
|
severity: "warning",
|
|
6831
7231
|
code: "kept-both-versions",
|
|
@@ -6845,27 +7245,27 @@ registerCheck(check24);
|
|
|
6845
7245
|
import { stat as stat3 } from "node:fs/promises";
|
|
6846
7246
|
|
|
6847
7247
|
// src/lib/image-inspect.ts
|
|
6848
|
-
import { stat as stat2, readFile as
|
|
7248
|
+
import { stat as stat2, readFile as readFile21 } from "node:fs/promises";
|
|
6849
7249
|
var DEFAULT_DPI = 72;
|
|
6850
7250
|
var cache = new Map;
|
|
6851
|
-
async function inspectImage(
|
|
7251
|
+
async function inspectImage(path8) {
|
|
6852
7252
|
let st;
|
|
6853
7253
|
try {
|
|
6854
|
-
st = await stat2(
|
|
7254
|
+
st = await stat2(path8);
|
|
6855
7255
|
} catch {
|
|
6856
7256
|
return null;
|
|
6857
7257
|
}
|
|
6858
|
-
const hit = cache.get(
|
|
7258
|
+
const hit = cache.get(path8);
|
|
6859
7259
|
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size)
|
|
6860
7260
|
return hit.info;
|
|
6861
7261
|
let info2 = null;
|
|
6862
7262
|
try {
|
|
6863
|
-
const buf = await
|
|
7263
|
+
const buf = await readFile21(path8);
|
|
6864
7264
|
info2 = parseImage(buf);
|
|
6865
7265
|
} catch {
|
|
6866
7266
|
info2 = null;
|
|
6867
7267
|
}
|
|
6868
|
-
cache.set(
|
|
7268
|
+
cache.set(path8, { mtimeMs: st.mtimeMs, size: st.size, info: info2 });
|
|
6869
7269
|
return info2;
|
|
6870
7270
|
}
|
|
6871
7271
|
function parseImage(b) {
|
|
@@ -7319,7 +7719,7 @@ var check30 = {
|
|
|
7319
7719
|
registerCheck(check30);
|
|
7320
7720
|
|
|
7321
7721
|
// src/checks/asset/font-license.ts
|
|
7322
|
-
import { existsSync as
|
|
7722
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
7323
7723
|
import { resolve as resolve8 } from "node:path";
|
|
7324
7724
|
var LICENSE_NAMES = [
|
|
7325
7725
|
"LICENSE",
|
|
@@ -7355,7 +7755,7 @@ var check31 = {
|
|
|
7355
7755
|
}
|
|
7356
7756
|
const results = [];
|
|
7357
7757
|
for (const fontDir of fontDirs) {
|
|
7358
|
-
const hasLicense = LICENSE_NAMES.some((name) =>
|
|
7758
|
+
const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve8(fontDir, name)));
|
|
7359
7759
|
if (!hasLicense) {
|
|
7360
7760
|
results.push({
|
|
7361
7761
|
checkId: check31.id,
|
|
@@ -7420,7 +7820,7 @@ var check32 = {
|
|
|
7420
7820
|
registerCheck(check32);
|
|
7421
7821
|
|
|
7422
7822
|
// src/checks/heuristic/section-density.ts
|
|
7423
|
-
import { readFile as
|
|
7823
|
+
import { readFile as readFile22 } from "node:fs/promises";
|
|
7424
7824
|
var check33 = {
|
|
7425
7825
|
id: "heuristic.chunking.section-density",
|
|
7426
7826
|
name: "Section Density",
|
|
@@ -7437,7 +7837,7 @@ var check33 = {
|
|
|
7437
7837
|
const results = [];
|
|
7438
7838
|
for (const file of files) {
|
|
7439
7839
|
try {
|
|
7440
|
-
const content = await
|
|
7840
|
+
const content = await readFile22(file, "utf8");
|
|
7441
7841
|
const lines = content.split(`
|
|
7442
7842
|
`);
|
|
7443
7843
|
let currentSectionStart = 0;
|
|
@@ -7877,7 +8277,7 @@ async function executeValidation(args) {
|
|
|
7877
8277
|
const targetIds = resolveTargets(parseCsv(typeof args.target === "string" ? args.target : undefined), config.targets);
|
|
7878
8278
|
const pdfPath = typeof args.pdf === "string" ? resolve9(args.pdf) : undefined;
|
|
7879
8279
|
const inputDir = typeof args.input === "string" ? resolve9(args.input) : undefined;
|
|
7880
|
-
if (pdfPath && !
|
|
8280
|
+
if (pdfPath && !existsSync11(pdfPath)) {
|
|
7881
8281
|
throw new Error(`File not found: ${pdfPath}`);
|
|
7882
8282
|
}
|
|
7883
8283
|
const categories = resolveCategoryArg(typeof args.category === "string" ? args.category : undefined);
|
|
@@ -7908,10 +8308,15 @@ async function executeValidation(args) {
|
|
|
7908
8308
|
const relMarkdown = await resolveActiveMarkdownFiles(manifestDir, config.source.files);
|
|
7909
8309
|
markdownFiles = relMarkdown.map((f) => join4(manifestDir, canonicalChapterId(f)));
|
|
7910
8310
|
const relStyles = await resolveActiveStyles(manifestDir, config.styles);
|
|
7911
|
-
|
|
8311
|
+
const projectCssFiles = relStyles.map((rel) => resolve9(manifestDir, rel));
|
|
8312
|
+
let pluginStylePaths = args.pluginStylePaths;
|
|
8313
|
+
if (pluginStylePaths === undefined) {
|
|
8314
|
+
({ pluginStylePaths } = await loadPluginsWithCss(config.plugins, manifestDir, (ref, err) => log.warn(`Skipping plugin "${ref}" for validation — ${err.message}`)));
|
|
8315
|
+
}
|
|
8316
|
+
cssFiles = [...projectCssFiles, ...pluginStylePaths].filter((f) => !f.endsWith(".min.css"));
|
|
7912
8317
|
assetDirs = [inputDir, ...await sharedAssetDirs(manifestDir, relStyles, inputDir)];
|
|
7913
8318
|
const possibleHtml = join4(outDir, BOOK_HTML_FILENAME);
|
|
7914
|
-
if (
|
|
8319
|
+
if (existsSync11(possibleHtml)) {
|
|
7915
8320
|
htmlPath = possibleHtml;
|
|
7916
8321
|
}
|
|
7917
8322
|
}
|
|
@@ -8069,7 +8474,7 @@ function computeGates(format, opts, config) {
|
|
|
8069
8474
|
}
|
|
8070
8475
|
|
|
8071
8476
|
// src/lib/build-staging.ts
|
|
8072
|
-
import
|
|
8477
|
+
import path8 from "node:path";
|
|
8073
8478
|
import os from "node:os";
|
|
8074
8479
|
import fsp from "node:fs/promises";
|
|
8075
8480
|
|
|
@@ -8252,8 +8657,8 @@ function placeholderPng(width = 640, height = 480, cell = 32) {
|
|
|
8252
8657
|
|
|
8253
8658
|
// src/lib/build-staging.ts
|
|
8254
8659
|
async function shipViewerHtml(htmlFile, outDir) {
|
|
8255
|
-
await fsp.mkdir(
|
|
8256
|
-
await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"),
|
|
8660
|
+
await fsp.mkdir(path8.join(outDir, "engine"), { recursive: true });
|
|
8661
|
+
await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path8.join(outDir, "engine/gutterpress-viewer.js"));
|
|
8257
8662
|
const tag = ` <script src="engine/gutterpress-viewer.js"></script>
|
|
8258
8663
|
`;
|
|
8259
8664
|
const html = await fsp.readFile(htmlFile, "utf-8");
|
|
@@ -8282,18 +8687,18 @@ async function stageBookAssets(options) {
|
|
|
8282
8687
|
return { missing: [...missingPlaceholders.keys()].sort() };
|
|
8283
8688
|
}
|
|
8284
8689
|
async function copyReferencedAssets(copies, outDir) {
|
|
8285
|
-
const dirs = new Set(copies.map((c) =>
|
|
8690
|
+
const dirs = new Set(copies.map((c) => path8.dirname(path8.resolve(outDir, c.to))));
|
|
8286
8691
|
await Promise.all([...dirs].map((d) => fsp.mkdir(d, { recursive: true })));
|
|
8287
8692
|
const missing = new Map;
|
|
8288
8693
|
await Promise.all(copies.map(async (c) => {
|
|
8289
|
-
const dest =
|
|
8694
|
+
const dest = path8.resolve(outDir, c.to);
|
|
8290
8695
|
try {
|
|
8291
8696
|
await fsp.copyFile(c.from, dest);
|
|
8292
8697
|
} catch (err) {
|
|
8293
8698
|
if (err?.code === "ENOENT") {
|
|
8294
8699
|
const placeholder = placeholderOutputPath(c.to);
|
|
8295
|
-
const placeholderDest =
|
|
8296
|
-
await fsp.mkdir(
|
|
8700
|
+
const placeholderDest = path8.resolve(outDir, placeholder);
|
|
8701
|
+
await fsp.mkdir(path8.dirname(placeholderDest), { recursive: true });
|
|
8297
8702
|
await fsp.writeFile(placeholderDest, placeholderPng());
|
|
8298
8703
|
missing.set(c.to, placeholder);
|
|
8299
8704
|
return;
|
|
@@ -8304,7 +8709,7 @@ async function copyReferencedAssets(copies, outDir) {
|
|
|
8304
8709
|
return missing;
|
|
8305
8710
|
}
|
|
8306
8711
|
async function createStageRoot() {
|
|
8307
|
-
return fsp.mkdtemp(
|
|
8712
|
+
return fsp.mkdtemp(path8.join(os.tmpdir(), "gutterpress-stage-"));
|
|
8308
8713
|
}
|
|
8309
8714
|
|
|
8310
8715
|
// src/lib/build-runner.ts
|
|
@@ -8312,18 +8717,18 @@ function splitOutPath(outArg, format) {
|
|
|
8312
8717
|
if (typeof outArg !== "string" || outArg.length === 0) {
|
|
8313
8718
|
return { outDir: undefined, pdfFileOverride: null };
|
|
8314
8719
|
}
|
|
8315
|
-
const resolved =
|
|
8720
|
+
const resolved = path9.resolve(outArg);
|
|
8316
8721
|
if ((format === "pdf" || format === "pdfx") && resolved.toLowerCase().endsWith(".pdf")) {
|
|
8317
|
-
return { outDir:
|
|
8722
|
+
return { outDir: path9.dirname(resolved), pdfFileOverride: resolved };
|
|
8318
8723
|
}
|
|
8319
8724
|
return { outDir: resolved, pdfFileOverride: null };
|
|
8320
8725
|
}
|
|
8321
8726
|
function layoutWarningKey(file, line, message) {
|
|
8322
|
-
return `${
|
|
8727
|
+
return `${path9.resolve(file)}\x00${line ?? 0}\x00${message}`;
|
|
8323
8728
|
}
|
|
8324
8729
|
async function resolveBuildContext(opts) {
|
|
8325
8730
|
const { format } = opts;
|
|
8326
|
-
const inputDir =
|
|
8731
|
+
const inputDir = path9.resolve(opts.inputDir);
|
|
8327
8732
|
const { manifest, manifestDir, manifestPath } = await loadManifestWithPath(opts.manifestPath ?? inputDir, { explicit: opts.manifestPath !== undefined });
|
|
8328
8733
|
if (manifestPath === null) {
|
|
8329
8734
|
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>`.");
|
|
@@ -8338,10 +8743,10 @@ async function resolveBuildContext(opts) {
|
|
|
8338
8743
|
pdfx: pdfxConfigOverride,
|
|
8339
8744
|
engine: opts.engine
|
|
8340
8745
|
}, manifest);
|
|
8341
|
-
const target = opts.pdfFileOverride ? { kind: "file", file:
|
|
8342
|
-
const outDir = target.kind === "file" ?
|
|
8746
|
+
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) };
|
|
8747
|
+
const outDir = target.kind === "file" ? path9.dirname(target.file) : target.dir;
|
|
8343
8748
|
const gates = computeGates(format, opts, config);
|
|
8344
|
-
const workDir = target.kind === "project" ?
|
|
8749
|
+
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")}`);
|
|
8345
8750
|
return {
|
|
8346
8751
|
opts,
|
|
8347
8752
|
format,
|
|
@@ -8353,15 +8758,32 @@ async function resolveBuildContext(opts) {
|
|
|
8353
8758
|
manifestDir,
|
|
8354
8759
|
config,
|
|
8355
8760
|
gates,
|
|
8356
|
-
prevalidatedLayoutWarningKeys: new Set
|
|
8761
|
+
prevalidatedLayoutWarningKeys: new Set,
|
|
8762
|
+
plugins: null
|
|
8357
8763
|
};
|
|
8358
8764
|
}
|
|
8765
|
+
async function loadBuildPlugins(ctx) {
|
|
8766
|
+
if (ctx.plugins)
|
|
8767
|
+
return ctx.plugins;
|
|
8768
|
+
const { config, renderDir } = ctx;
|
|
8769
|
+
if (config.plugins.length > 0) {
|
|
8770
|
+
log.info(`Loading ${config.plugins.length} plugin(s)...`);
|
|
8771
|
+
}
|
|
8772
|
+
const loaded = await loadPluginsWithCss(config.plugins, renderDir);
|
|
8773
|
+
if (loaded.plugins && loaded.plugins.length > 0) {
|
|
8774
|
+
log.success(`Loaded ${loaded.plugins.length} plugin(s)`);
|
|
8775
|
+
}
|
|
8776
|
+
ctx.plugins = loaded;
|
|
8777
|
+
return loaded;
|
|
8778
|
+
}
|
|
8359
8779
|
async function runQualityGates(ctx) {
|
|
8360
8780
|
const { gates, opts, renderDir } = ctx;
|
|
8781
|
+
const pluginStylePaths = gates.lint || gates.preValidate ? (await loadBuildPlugins(ctx)).pluginStylePaths : undefined;
|
|
8361
8782
|
if (gates.lint) {
|
|
8362
8783
|
log.info("Lint: CSS print-safety");
|
|
8363
8784
|
const lintResult = await runLint({
|
|
8364
|
-
manifest: opts.manifestPath ?? renderDir
|
|
8785
|
+
manifest: opts.manifestPath ?? renderDir,
|
|
8786
|
+
pluginStylePaths
|
|
8365
8787
|
});
|
|
8366
8788
|
if (!lintResult.ok) {
|
|
8367
8789
|
throw new BuildError("CSS lint failed", 2);
|
|
@@ -8372,7 +8794,8 @@ async function runQualityGates(ctx) {
|
|
|
8372
8794
|
const result = await executeAndReport({
|
|
8373
8795
|
input: renderDir,
|
|
8374
8796
|
phase: "pre-build",
|
|
8375
|
-
manifest: opts.manifestPath
|
|
8797
|
+
manifest: opts.manifestPath,
|
|
8798
|
+
pluginStylePaths
|
|
8376
8799
|
}, "text");
|
|
8377
8800
|
for (const finding2 of result.execution.report.results) {
|
|
8378
8801
|
if (finding2.checkId === "source.markdown.layout-markers" && finding2.file && finding2.code !== "inspect-failed") {
|
|
@@ -8391,13 +8814,7 @@ async function renderBook(ctx) {
|
|
|
8391
8814
|
} else {
|
|
8392
8815
|
log.info("Using all .md files in alphabetical order");
|
|
8393
8816
|
}
|
|
8394
|
-
|
|
8395
|
-
log.info(`Loading ${config.plugins.length} plugin(s)...`);
|
|
8396
|
-
}
|
|
8397
|
-
const { plugins, pluginCss, pluginStylePaths } = await loadPluginsWithCss(config.plugins, renderDir);
|
|
8398
|
-
if (plugins && plugins.length > 0) {
|
|
8399
|
-
log.success(`Loaded ${plugins.length} plugin(s)`);
|
|
8400
|
-
}
|
|
8817
|
+
const { plugins, pluginCss, pluginStylePaths } = await loadBuildPlugins(ctx);
|
|
8401
8818
|
const imageRefs = [];
|
|
8402
8819
|
const cssAssets = [];
|
|
8403
8820
|
const htmlFile = await renderChaptersToFile(renderDir, workDir, {
|
|
@@ -8409,7 +8826,7 @@ async function renderBook(ctx) {
|
|
|
8409
8826
|
pluginStylePaths,
|
|
8410
8827
|
onChapterWarnings: (file, warnings) => {
|
|
8411
8828
|
for (const w of warnings) {
|
|
8412
|
-
const key = layoutWarningKey(
|
|
8829
|
+
const key = layoutWarningKey(path9.resolve(renderDir, file), w.line, w.message);
|
|
8413
8830
|
if (!ctx.prevalidatedLayoutWarningKeys.has(key)) {
|
|
8414
8831
|
log.warn(` ${file}, line ${w.line}: ${w.message}`);
|
|
8415
8832
|
}
|
|
@@ -8447,9 +8864,9 @@ async function renderBook(ctx) {
|
|
|
8447
8864
|
return htmlFile;
|
|
8448
8865
|
}
|
|
8449
8866
|
async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
|
|
8450
|
-
const iccCandidates =
|
|
8867
|
+
const iccCandidates = path9.isAbsolute(icc) ? [icc] : [path9.resolve(manifestDir, icc), path9.resolve(icc)];
|
|
8451
8868
|
let effectiveIccPath = iccCandidates.find((candidate) => fs2.existsSync(candidate)) ?? null;
|
|
8452
|
-
if (!effectiveIccPath && !explicitIccPath &&
|
|
8869
|
+
if (!effectiveIccPath && !explicitIccPath && path9.basename(icc) === "CGATS21_CRPC1.icc") {
|
|
8453
8870
|
effectiveIccPath = await getAssetPath("profiles/CGATS21_CRPC1.icc");
|
|
8454
8871
|
}
|
|
8455
8872
|
if (!effectiveIccPath) {
|
|
@@ -8465,13 +8882,13 @@ async function finalizeBuild(ctx, fingerprint, wroteMessage, paths, artifactName
|
|
|
8465
8882
|
});
|
|
8466
8883
|
await publishBuild(ctx, artifactName2);
|
|
8467
8884
|
const delivered = ctx.target.kind !== "file";
|
|
8468
|
-
const fingerprintPath = delivered ?
|
|
8885
|
+
const fingerprintPath = delivered ? path9.join(ctx.outDir, path9.basename(workFingerprint)) : null;
|
|
8469
8886
|
log.success(wroteMessage);
|
|
8470
8887
|
if (fingerprintPath)
|
|
8471
8888
|
log.info(`Fingerprint: ${fingerprintPath}`);
|
|
8472
8889
|
return {
|
|
8473
8890
|
outDir: ctx.outDir,
|
|
8474
|
-
htmlPath: delivered ?
|
|
8891
|
+
htmlPath: delivered ? path9.join(ctx.outDir, path9.relative(ctx.workDir, paths.htmlPath)) : null,
|
|
8475
8892
|
pdfPath: paths.pdfPath,
|
|
8476
8893
|
fingerprintPath,
|
|
8477
8894
|
diagnostics
|
|
@@ -8482,8 +8899,8 @@ async function publishBuild(ctx, artifactName2) {
|
|
|
8482
8899
|
if (target.kind === "file") {
|
|
8483
8900
|
if (!artifactName2)
|
|
8484
8901
|
throw new BuildError("No artifact to write for this format", 1);
|
|
8485
|
-
await fsp2.mkdir(
|
|
8486
|
-
await fsp2.copyFile(
|
|
8902
|
+
await fsp2.mkdir(path9.dirname(target.file), { recursive: true });
|
|
8903
|
+
await fsp2.copyFile(path9.join(workDir, artifactName2), target.file);
|
|
8487
8904
|
return;
|
|
8488
8905
|
}
|
|
8489
8906
|
if (target.kind === "directory") {
|
|
@@ -8492,12 +8909,12 @@ async function publishBuild(ctx, artifactName2) {
|
|
|
8492
8909
|
return;
|
|
8493
8910
|
}
|
|
8494
8911
|
for (const name of await listPdfs(target.dir)) {
|
|
8495
|
-
if (!fs2.existsSync(
|
|
8496
|
-
await fsp2.rename(
|
|
8912
|
+
if (!fs2.existsSync(path9.join(workDir, name))) {
|
|
8913
|
+
await fsp2.rename(path9.join(target.dir, name), path9.join(workDir, name));
|
|
8497
8914
|
}
|
|
8498
8915
|
}
|
|
8499
8916
|
await fsp2.rm(target.dir, { recursive: true, force: true });
|
|
8500
|
-
await fsp2.mkdir(
|
|
8917
|
+
await fsp2.mkdir(path9.dirname(target.dir), { recursive: true });
|
|
8501
8918
|
await fsp2.rename(workDir, target.dir);
|
|
8502
8919
|
}
|
|
8503
8920
|
async function listPdfs(dir) {
|
|
@@ -8513,7 +8930,7 @@ class HtmlOutput {
|
|
|
8513
8930
|
const { workDir, outDir, inputDir, config, opts } = ctx;
|
|
8514
8931
|
log.info("Shipping self-contained HTML + viewer bundle (native engine)");
|
|
8515
8932
|
await shipViewerHtml(htmlFile, workDir);
|
|
8516
|
-
await fsp2.writeFile(
|
|
8933
|
+
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>
|
|
8517
8934
|
`, "utf-8");
|
|
8518
8935
|
return finalizeBuild(ctx, {
|
|
8519
8936
|
command: "build",
|
|
@@ -8526,7 +8943,7 @@ class HtmlOutput {
|
|
|
8526
8943
|
iccPath: null,
|
|
8527
8944
|
stripAnnotations: null
|
|
8528
8945
|
}
|
|
8529
|
-
}, `Wrote: ${
|
|
8946
|
+
}, `Wrote: ${path9.join(outDir, BOOK_HTML)}`, { htmlPath: htmlFile, pdfPath: null });
|
|
8530
8947
|
}
|
|
8531
8948
|
}
|
|
8532
8949
|
|
|
@@ -8535,14 +8952,14 @@ class PdfOutput {
|
|
|
8535
8952
|
const { workDir, outDir, inputDir, config, opts, format, manifestDir, gates } = ctx;
|
|
8536
8953
|
const pdfxMode = format === "pdfx" ? opts.pdfxFlavor ?? config.pdfx.flavor : undefined;
|
|
8537
8954
|
const pdfName = artifactName(config.title, pdfxMode ? "pdfx" : "pdf");
|
|
8538
|
-
const pdfFile =
|
|
8539
|
-
const reportedPdf = ctx.target.kind === "file" ? ctx.target.file :
|
|
8955
|
+
const pdfFile = path9.join(workDir, pdfName);
|
|
8956
|
+
const reportedPdf = ctx.target.kind === "file" ? ctx.target.file : path9.join(outDir, pdfName);
|
|
8540
8957
|
const stage = await createStageRoot();
|
|
8541
8958
|
try {
|
|
8542
|
-
const rawPdf = pdfxMode ?
|
|
8543
|
-
await fsp2.mkdir(
|
|
8959
|
+
const rawPdf = pdfxMode ? path9.join(stage, "raw.pdf") : path9.resolve(pdfFile);
|
|
8960
|
+
await fsp2.mkdir(path9.dirname(path9.resolve(pdfFile)), { recursive: true });
|
|
8544
8961
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
8545
|
-
const { buildNativePdf } = await import("./engine-
|
|
8962
|
+
const { buildNativePdf } = await import("./engine-db8632w6.js");
|
|
8546
8963
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
8547
8964
|
title: config.title,
|
|
8548
8965
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -8573,7 +8990,7 @@ class PdfOutput {
|
|
|
8573
8990
|
await stripAnnotations(rawPdf, stage);
|
|
8574
8991
|
}
|
|
8575
8992
|
log.info(`Converting to CMYK PDF/X (${pdfxMode})`);
|
|
8576
|
-
await convertToPdfxCmyk(rawPdf,
|
|
8993
|
+
await convertToPdfxCmyk(rawPdf, path9.resolve(pdfFile), {
|
|
8577
8994
|
iccPath: effectiveIccPath,
|
|
8578
8995
|
pdfx: pdfxMode,
|
|
8579
8996
|
title: config.title,
|
|
@@ -8633,10 +9050,10 @@ async function runBuild(opts) {
|
|
|
8633
9050
|
}
|
|
8634
9051
|
}
|
|
8635
9052
|
// src/preview/lifecycle.ts
|
|
8636
|
-
import
|
|
9053
|
+
import path11 from "path";
|
|
8637
9054
|
import { tmpdir as tmpdir2 } from "os";
|
|
8638
9055
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
8639
|
-
import { readdir as readdir5, readFile as
|
|
9056
|
+
import { readdir as readdir5, readFile as readFile23, writeFile as writeFile5 } from "node:fs/promises";
|
|
8640
9057
|
|
|
8641
9058
|
// src/utils/file-utils.ts
|
|
8642
9059
|
import { promises as fs3 } from "fs";
|
|
@@ -8658,7 +9075,7 @@ async function mkdir4(dirPath) {
|
|
|
8658
9075
|
// src/preview/file-watcher.ts
|
|
8659
9076
|
import { watch } from "chokidar";
|
|
8660
9077
|
import fsp3 from "node:fs/promises";
|
|
8661
|
-
import
|
|
9078
|
+
import path10 from "path";
|
|
8662
9079
|
var EMPTY_BOOK_HTML = `<!doctype html>
|
|
8663
9080
|
<html lang="en">
|
|
8664
9081
|
<head>
|
|
@@ -8712,7 +9129,7 @@ function injectPreviewScripts(html, pageIsolateChapters) {
|
|
|
8712
9129
|
async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
8713
9130
|
if (!inputPath) {
|
|
8714
9131
|
cssAssets.clear();
|
|
8715
|
-
await fsp3.writeFile(
|
|
9132
|
+
await fsp3.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), EMPTY_BOOK_HTML, "utf-8");
|
|
8716
9133
|
return;
|
|
8717
9134
|
}
|
|
8718
9135
|
const nextAssets = new Map;
|
|
@@ -8727,7 +9144,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets) {
|
|
|
8727
9144
|
cssAssets.clear();
|
|
8728
9145
|
for (const [to, from] of nextAssets)
|
|
8729
9146
|
cssAssets.set(to, from);
|
|
8730
|
-
await fsp3.writeFile(
|
|
9147
|
+
await fsp3.writeFile(path10.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, false), "utf-8");
|
|
8731
9148
|
}
|
|
8732
9149
|
async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
8733
9150
|
const html = await renderPreviewBook(inputPath, config, {
|
|
@@ -8739,12 +9156,12 @@ async function renderChapterPreviewHtml(inputPath, file, config) {
|
|
|
8739
9156
|
function describeChanges(changes, inputResolved) {
|
|
8740
9157
|
const files = [];
|
|
8741
9158
|
for (const [changedPath, event] of changes) {
|
|
8742
|
-
const relative2 =
|
|
8743
|
-
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${
|
|
9159
|
+
const relative2 = path10.relative(inputResolved, path10.resolve(changedPath));
|
|
9160
|
+
if (relative2 === "" || relative2 === ".." || relative2.startsWith(`..${path10.sep}`) || path10.isAbsolute(relative2))
|
|
8744
9161
|
continue;
|
|
8745
9162
|
files.push({
|
|
8746
9163
|
relativePath: canonicalChapterId(relative2),
|
|
8747
|
-
ext:
|
|
9164
|
+
ext: path10.extname(changedPath).toLowerCase(),
|
|
8748
9165
|
event
|
|
8749
9166
|
});
|
|
8750
9167
|
}
|
|
@@ -8796,23 +9213,23 @@ function isIgnoredWatchPath(candidatePath, projectRoot) {
|
|
|
8796
9213
|
return isDotPathUnderRoot(candidatePath, projectRoot) || isGeneratedProjectPath(relative2);
|
|
8797
9214
|
}
|
|
8798
9215
|
function isExternalWatchCandidate(candidatePath, targets) {
|
|
8799
|
-
const candidate =
|
|
9216
|
+
const candidate = path10.resolve(candidatePath);
|
|
8800
9217
|
for (const rawTarget of targets) {
|
|
8801
|
-
const target =
|
|
8802
|
-
const rel =
|
|
8803
|
-
if (rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
9218
|
+
const target = path10.resolve(rawTarget);
|
|
9219
|
+
const rel = path10.relative(candidate, target);
|
|
9220
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel))
|
|
8804
9221
|
return true;
|
|
8805
9222
|
}
|
|
8806
9223
|
return false;
|
|
8807
9224
|
}
|
|
8808
9225
|
async function nearestExistingDirectory(start) {
|
|
8809
|
-
let current =
|
|
9226
|
+
let current = path10.resolve(start);
|
|
8810
9227
|
while (true) {
|
|
8811
9228
|
try {
|
|
8812
9229
|
if ((await fsp3.stat(current)).isDirectory())
|
|
8813
9230
|
return current;
|
|
8814
9231
|
} catch {}
|
|
8815
|
-
const parent =
|
|
9232
|
+
const parent = path10.dirname(current);
|
|
8816
9233
|
if (parent === current)
|
|
8817
9234
|
return current;
|
|
8818
9235
|
current = parent;
|
|
@@ -8821,12 +9238,12 @@ async function nearestExistingDirectory(start) {
|
|
|
8821
9238
|
async function externalWatchRoots(targets) {
|
|
8822
9239
|
const roots = new Set;
|
|
8823
9240
|
for (const target of targets) {
|
|
8824
|
-
roots.add(await nearestExistingDirectory(
|
|
9241
|
+
roots.add(await nearestExistingDirectory(path10.dirname(path10.resolve(target))));
|
|
8825
9242
|
}
|
|
8826
9243
|
return [...roots];
|
|
8827
9244
|
}
|
|
8828
9245
|
async function externalWatchTargets(projectDir, config) {
|
|
8829
|
-
const root =
|
|
9246
|
+
const root = path10.resolve(projectDir);
|
|
8830
9247
|
let canonicalRoot = root;
|
|
8831
9248
|
try {
|
|
8832
9249
|
canonicalRoot = await fsp3.realpath(root);
|
|
@@ -8834,21 +9251,21 @@ async function externalWatchTargets(projectDir, config) {
|
|
|
8834
9251
|
const styles = await resolveActiveStyles(root, config.styles);
|
|
8835
9252
|
const candidates = [
|
|
8836
9253
|
...await collectStyleDependencies(root, styles),
|
|
8837
|
-
...(config.plugins ?? []).map((p) => p.path).filter((p) => !!p).map((p) =>
|
|
9254
|
+
...(config.plugins ?? []).map((p) => p.path).filter((p) => !!p).map((p) => path10.resolve(root, p))
|
|
8838
9255
|
];
|
|
8839
9256
|
const external = new Set;
|
|
8840
9257
|
for (const abs of candidates) {
|
|
8841
|
-
const authoredPath =
|
|
8842
|
-
const authoredRel =
|
|
8843
|
-
if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${
|
|
9258
|
+
const authoredPath = path10.resolve(abs);
|
|
9259
|
+
const authoredRel = path10.relative(root, authoredPath);
|
|
9260
|
+
if (authoredRel !== "" && (authoredRel === ".." || authoredRel.startsWith(`..${path10.sep}`) || path10.isAbsolute(authoredRel))) {
|
|
8844
9261
|
external.add(authoredPath);
|
|
8845
9262
|
}
|
|
8846
9263
|
let canonicalPath = authoredPath;
|
|
8847
9264
|
try {
|
|
8848
9265
|
canonicalPath = await fsp3.realpath(canonicalPath);
|
|
8849
9266
|
} catch {}
|
|
8850
|
-
const rel =
|
|
8851
|
-
if (rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
9267
|
+
const rel = path10.relative(canonicalRoot, canonicalPath);
|
|
9268
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel))
|
|
8852
9269
|
continue;
|
|
8853
9270
|
external.add(canonicalPath);
|
|
8854
9271
|
}
|
|
@@ -8875,7 +9292,7 @@ async function addAndAwaitWatch(watcher, roots) {
|
|
|
8875
9292
|
}
|
|
8876
9293
|
}
|
|
8877
9294
|
function createFileWatcher(state) {
|
|
8878
|
-
const inputResolved =
|
|
9295
|
+
const inputResolved = path10.resolve(state.currentInputPath);
|
|
8879
9296
|
const watcher = watch(inputResolved, {
|
|
8880
9297
|
persistent: true,
|
|
8881
9298
|
ignoreInitial: true,
|
|
@@ -8915,7 +9332,7 @@ function createFileWatcher(state) {
|
|
|
8915
9332
|
async function recordChange(event, filePath) {
|
|
8916
9333
|
if (closed)
|
|
8917
9334
|
return;
|
|
8918
|
-
const target =
|
|
9335
|
+
const target = path10.resolve(filePath);
|
|
8919
9336
|
const settled = settledWrites.get(target);
|
|
8920
9337
|
if (settled && event !== "unlink" && Date.now() <= settled.expiresAt) {
|
|
8921
9338
|
const diskContent = await fsp3.readFile(target, "utf8").catch(() => null);
|
|
@@ -8936,7 +9353,7 @@ function createFileWatcher(state) {
|
|
|
8936
9353
|
recordChange(event, filePath);
|
|
8937
9354
|
});
|
|
8938
9355
|
externalWatcher.on("all", (event, filePath) => {
|
|
8939
|
-
const target =
|
|
9356
|
+
const target = path10.resolve(filePath);
|
|
8940
9357
|
if (!desiredExternals.has(target))
|
|
8941
9358
|
return;
|
|
8942
9359
|
if (event === "add" && suppressInitialExternalAdds.delete(target))
|
|
@@ -8944,8 +9361,8 @@ function createFileWatcher(state) {
|
|
|
8944
9361
|
recordChange(event, target);
|
|
8945
9362
|
});
|
|
8946
9363
|
function isWatchedSource(target) {
|
|
8947
|
-
const rel =
|
|
8948
|
-
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
9364
|
+
const rel = path10.relative(inputResolved, target);
|
|
9365
|
+
const inProject = rel === "" || rel !== ".." && !rel.startsWith(`..${path10.sep}`) && !path10.isAbsolute(rel);
|
|
8949
9366
|
if (inProject)
|
|
8950
9367
|
return !isIgnoredWatchPath(target, inputResolved);
|
|
8951
9368
|
return desiredExternals.has(target);
|
|
@@ -8970,7 +9387,7 @@ function createFileWatcher(state) {
|
|
|
8970
9387
|
state.notifySettledWrite = (filePath, writtenContent) => {
|
|
8971
9388
|
if (closed)
|
|
8972
9389
|
return;
|
|
8973
|
-
const target =
|
|
9390
|
+
const target = path10.resolve(filePath);
|
|
8974
9391
|
if (isWatchedSource(target)) {
|
|
8975
9392
|
acceptSettledWrite(target, writtenContent);
|
|
8976
9393
|
return;
|
|
@@ -8991,7 +9408,7 @@ function createFileWatcher(state) {
|
|
|
8991
9408
|
return;
|
|
8992
9409
|
let nextTargets;
|
|
8993
9410
|
try {
|
|
8994
|
-
nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) =>
|
|
9411
|
+
nextTargets = new Set((await externalWatchTargets(inputResolved, state.config)).map((target) => path10.resolve(target)));
|
|
8995
9412
|
} catch (err) {
|
|
8996
9413
|
debug(`Could not resolve external watch targets: ${err}`);
|
|
8997
9414
|
return;
|
|
@@ -9123,7 +9540,7 @@ async function stopFileWatcher(state) {
|
|
|
9123
9540
|
}
|
|
9124
9541
|
|
|
9125
9542
|
// src/preview/lifecycle.ts
|
|
9126
|
-
var TEMP_DIR_BASE =
|
|
9543
|
+
var TEMP_DIR_BASE = path11.join(tmpdir2(), "gutterpress-preview");
|
|
9127
9544
|
var PID_FILE_NAME = ".gutterpress.pid";
|
|
9128
9545
|
function isProcessAlive(pid) {
|
|
9129
9546
|
if (!Number.isFinite(pid) || pid <= 0)
|
|
@@ -9143,10 +9560,10 @@ async function cleanupOrphanTempDirs() {
|
|
|
9143
9560
|
return;
|
|
9144
9561
|
}
|
|
9145
9562
|
for (const entry of entries) {
|
|
9146
|
-
const dir =
|
|
9147
|
-
const pidFile =
|
|
9563
|
+
const dir = path11.join(TEMP_DIR_BASE, entry);
|
|
9564
|
+
const pidFile = path11.join(dir, PID_FILE_NAME);
|
|
9148
9565
|
try {
|
|
9149
|
-
const raw = await
|
|
9566
|
+
const raw = await readFile23(pidFile, "utf8");
|
|
9150
9567
|
const pid = parseInt(raw.trim(), 10);
|
|
9151
9568
|
if (!isProcessAlive(pid)) {
|
|
9152
9569
|
debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
|
|
@@ -9158,10 +9575,10 @@ async function cleanupOrphanTempDirs() {
|
|
|
9158
9575
|
async function initializePreviewDirectories() {
|
|
9159
9576
|
await cleanupOrphanTempDirs();
|
|
9160
9577
|
const tempDirSuffix = randomBytes2(8).toString("hex");
|
|
9161
|
-
const tempDir =
|
|
9578
|
+
const tempDir = path11.join(TEMP_DIR_BASE, tempDirSuffix);
|
|
9162
9579
|
await mkdir4(tempDir);
|
|
9163
9580
|
debug(`Created temporary directory: ${tempDir}`);
|
|
9164
|
-
await writeFile5(
|
|
9581
|
+
await writeFile5(path11.join(tempDir, PID_FILE_NAME), `${process.pid}
|
|
9165
9582
|
`, "utf8");
|
|
9166
9583
|
return tempDir;
|
|
9167
9584
|
}
|
|
@@ -9252,8 +9669,8 @@ function createServerState(inputPath, tempDir, config, options) {
|
|
|
9252
9669
|
import http from "node:http";
|
|
9253
9670
|
import net from "node:net";
|
|
9254
9671
|
import { createHash as createHash6, randomUUID } from "node:crypto";
|
|
9255
|
-
import { readFile as
|
|
9256
|
-
import
|
|
9672
|
+
import { readFile as readFile24, stat as stat4 } from "node:fs/promises";
|
|
9673
|
+
import path13 from "path";
|
|
9257
9674
|
import { WebSocket, WebSocketServer } from "ws";
|
|
9258
9675
|
|
|
9259
9676
|
// src/lib/open-path.ts
|
|
@@ -9285,7 +9702,7 @@ function openPath(filePath) {
|
|
|
9285
9702
|
}
|
|
9286
9703
|
|
|
9287
9704
|
// src/lib/static-serve.ts
|
|
9288
|
-
import
|
|
9705
|
+
import path12 from "node:path";
|
|
9289
9706
|
var STATIC_MIME = {
|
|
9290
9707
|
".html": "text/html; charset=utf-8",
|
|
9291
9708
|
".htm": "text/html; charset=utf-8",
|
|
@@ -9307,9 +9724,9 @@ var STATIC_MIME = {
|
|
|
9307
9724
|
".otf": "font/otf"
|
|
9308
9725
|
};
|
|
9309
9726
|
function resolveWithinRoot(relPath, root) {
|
|
9310
|
-
const resolvedRoot =
|
|
9311
|
-
const candidate =
|
|
9312
|
-
if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot +
|
|
9727
|
+
const resolvedRoot = path12.resolve(root);
|
|
9728
|
+
const candidate = path12.resolve(resolvedRoot, "." + (relPath.startsWith("/") ? relPath : "/" + relPath));
|
|
9729
|
+
if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path12.sep)) {
|
|
9313
9730
|
return null;
|
|
9314
9731
|
}
|
|
9315
9732
|
return candidate;
|
|
@@ -9562,7 +9979,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
9562
9979
|
try {
|
|
9563
9980
|
const s = await stat4(filePath);
|
|
9564
9981
|
if (s.isDirectory())
|
|
9565
|
-
filePath =
|
|
9982
|
+
filePath = path13.join(filePath, "index.html");
|
|
9566
9983
|
} catch {
|
|
9567
9984
|
res.writeHead(404);
|
|
9568
9985
|
res.end("Not Found");
|
|
@@ -9570,7 +9987,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
9570
9987
|
}
|
|
9571
9988
|
let data;
|
|
9572
9989
|
try {
|
|
9573
|
-
data = await
|
|
9990
|
+
data = await readFile24(filePath);
|
|
9574
9991
|
} catch {
|
|
9575
9992
|
res.writeHead(404);
|
|
9576
9993
|
res.end("Not Found");
|
|
@@ -9583,7 +10000,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
|
|
|
9583
10000
|
res.end(withHmr);
|
|
9584
10001
|
return;
|
|
9585
10002
|
}
|
|
9586
|
-
const ct = STATIC_MIME[
|
|
10003
|
+
const ct = STATIC_MIME[path13.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
9587
10004
|
res.writeHead(200, { "Content-Type": ct, "Cache-Control": cacheControl, ...extraHeaders });
|
|
9588
10005
|
res.end(data);
|
|
9589
10006
|
}
|
|
@@ -9593,7 +10010,7 @@ var EMBEDDED_CACHE_CONTROL = "public, no-cache";
|
|
|
9593
10010
|
var EMBEDDED_ETAG = `"gutterpress-${PACKAGE_VERSION}"`;
|
|
9594
10011
|
var PROJECT_ASSET_CACHE_CONTROL = "private, no-cache";
|
|
9595
10012
|
async function serveRevalidatedStatic(req, absPath, res, validators) {
|
|
9596
|
-
const extension =
|
|
10013
|
+
const extension = path13.extname(absPath).toLowerCase();
|
|
9597
10014
|
if (extension === ".html" || extension === ".htm") {
|
|
9598
10015
|
await serveStatic(absPath, res);
|
|
9599
10016
|
return;
|
|
@@ -9620,7 +10037,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
|
|
|
9620
10037
|
let data;
|
|
9621
10038
|
if (!validator || validator.stamp !== stamp) {
|
|
9622
10039
|
try {
|
|
9623
|
-
data = await
|
|
10040
|
+
data = await readFile24(absPath);
|
|
9624
10041
|
} catch {
|
|
9625
10042
|
await serveStatic(absPath, res);
|
|
9626
10043
|
return;
|
|
@@ -9759,7 +10176,7 @@ async function createPreviewServer(state, port) {
|
|
|
9759
10176
|
const file = rawFile ? canonicalChapterId(rawFile) : "";
|
|
9760
10177
|
const revision = Number(url.searchParams.get("revision"));
|
|
9761
10178
|
const configuredFiles = state.config.source?.files;
|
|
9762
|
-
if (!file || !state.currentInputPath || !incrementalPreviewEnabled() || !Number.isSafeInteger(revision) || revision !== reloadRevision ||
|
|
10179
|
+
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)) {
|
|
9763
10180
|
res.writeHead(400);
|
|
9764
10181
|
res.end("Bad Request");
|
|
9765
10182
|
return;
|
|
@@ -9961,9 +10378,9 @@ async function startPreviewServer(options) {
|
|
|
9961
10378
|
import { platform, arch, release } from "node:os";
|
|
9962
10379
|
|
|
9963
10380
|
// src/lib/remote-auth/token-store.ts
|
|
9964
|
-
import { chmod, mkdir as mkdir5, readFile as
|
|
10381
|
+
import { chmod, mkdir as mkdir5, readFile as readFile25, rename as rename2, writeFile as writeFile6 } from "node:fs/promises";
|
|
9965
10382
|
import os3 from "node:os";
|
|
9966
|
-
import
|
|
10383
|
+
import path14 from "node:path";
|
|
9967
10384
|
function credentialHostKey(hostOrUrl) {
|
|
9968
10385
|
const trimmed = String(hostOrUrl ?? "").trim();
|
|
9969
10386
|
if (!trimmed)
|
|
@@ -9990,22 +10407,22 @@ function defaultConfigDir() {
|
|
|
9990
10407
|
if (override)
|
|
9991
10408
|
return override;
|
|
9992
10409
|
if (process.platform === "win32" && process.env.APPDATA) {
|
|
9993
|
-
return
|
|
10410
|
+
return path14.join(process.env.APPDATA, "gutterpress");
|
|
9994
10411
|
}
|
|
9995
10412
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
9996
|
-
return
|
|
10413
|
+
return path14.join(xdg || path14.join(os3.homedir(), ".config"), "gutterpress");
|
|
9997
10414
|
}
|
|
9998
10415
|
|
|
9999
10416
|
class FileTokenStore {
|
|
10000
10417
|
filePath;
|
|
10001
10418
|
queue = Promise.resolve();
|
|
10002
10419
|
constructor(filePath) {
|
|
10003
|
-
this.filePath = filePath ??
|
|
10420
|
+
this.filePath = filePath ?? path14.join(defaultConfigDir(), "credentials.json");
|
|
10004
10421
|
}
|
|
10005
10422
|
async read() {
|
|
10006
10423
|
let raw;
|
|
10007
10424
|
try {
|
|
10008
|
-
raw = await
|
|
10425
|
+
raw = await readFile25(this.filePath, "utf8");
|
|
10009
10426
|
} catch (e) {
|
|
10010
10427
|
if (e?.code === "ENOENT") {
|
|
10011
10428
|
return { version: 1, credentials: {} };
|
|
@@ -10025,7 +10442,7 @@ class FileTokenStore {
|
|
|
10025
10442
|
return { version: 1, credentials: {} };
|
|
10026
10443
|
}
|
|
10027
10444
|
async write(data) {
|
|
10028
|
-
await mkdir5(
|
|
10445
|
+
await mkdir5(path14.dirname(this.filePath), { recursive: true });
|
|
10029
10446
|
const tmpPath = `${this.filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
10030
10447
|
await writeFile6(tmpPath, JSON.stringify(data, null, 2), {
|
|
10031
10448
|
encoding: "utf8",
|
|
@@ -10174,18 +10591,18 @@ async function getSystemDiagnostics() {
|
|
|
10174
10591
|
installHint: INSTALL_HINTS3[t.hintKey]
|
|
10175
10592
|
};
|
|
10176
10593
|
}
|
|
10177
|
-
const [
|
|
10594
|
+
const [path15, available] = await Promise.all([
|
|
10178
10595
|
findTool(t.bin),
|
|
10179
10596
|
isToolAvailable(t.bin)
|
|
10180
10597
|
]);
|
|
10181
|
-
const found = available || !!
|
|
10598
|
+
const found = available || !!path15;
|
|
10182
10599
|
const version = found ? await getVersion(t.bin) : undefined;
|
|
10183
10600
|
return {
|
|
10184
10601
|
id: t.id,
|
|
10185
10602
|
name: t.name,
|
|
10186
10603
|
bin: t.bin,
|
|
10187
10604
|
found,
|
|
10188
|
-
path:
|
|
10605
|
+
path: path15,
|
|
10189
10606
|
version,
|
|
10190
10607
|
usedBy: t.usedBy,
|
|
10191
10608
|
installHint: INSTALL_HINTS3[t.hintKey]
|
|
@@ -10205,25 +10622,25 @@ async function getSystemDiagnostics() {
|
|
|
10205
10622
|
};
|
|
10206
10623
|
}
|
|
10207
10624
|
// src/lib/project-scaffold.ts
|
|
10208
|
-
import { access, copyFile, cp as cp3, mkdir as mkdir8, readdir as readdir7, readFile as
|
|
10209
|
-
import { constants as FS, existsSync as
|
|
10210
|
-
import
|
|
10625
|
+
import { access, copyFile, cp as cp3, mkdir as mkdir8, readdir as readdir7, readFile as readFile28, stat as stat6, writeFile as writeFile8 } from "node:fs/promises";
|
|
10626
|
+
import { constants as FS, existsSync as existsSync14 } from "node:fs";
|
|
10627
|
+
import path17 from "node:path";
|
|
10211
10628
|
|
|
10212
10629
|
// src/lib/manifest-doc.ts
|
|
10213
|
-
import { existsSync as
|
|
10630
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
10214
10631
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10215
|
-
import { mkdir as mkdir6, open, readFile as
|
|
10216
|
-
import
|
|
10632
|
+
import { mkdir as mkdir6, open, readFile as readFile26, rename as rename3, rm as rm2 } from "node:fs/promises";
|
|
10633
|
+
import path15 from "node:path";
|
|
10217
10634
|
import { parseDocument, isSeq, YAMLSeq } from "yaml";
|
|
10218
10635
|
function resolveManifestPath(projectDir) {
|
|
10219
|
-
const existing = MANIFEST_FILENAMES.find((name) =>
|
|
10220
|
-
return
|
|
10636
|
+
const existing = MANIFEST_FILENAMES.find((name) => existsSync12(path15.join(projectDir, name)));
|
|
10637
|
+
return path15.join(projectDir, existing ?? MANIFEST_FILENAMES[0]);
|
|
10221
10638
|
}
|
|
10222
10639
|
async function loadManifestDoc(projectDir) {
|
|
10223
10640
|
const file = resolveManifestPath(projectDir);
|
|
10224
10641
|
let text = "";
|
|
10225
10642
|
try {
|
|
10226
|
-
text = await
|
|
10643
|
+
text = await readFile26(file, "utf8");
|
|
10227
10644
|
} catch (error2) {
|
|
10228
10645
|
if (error2.code !== "ENOENT")
|
|
10229
10646
|
throw error2;
|
|
@@ -10231,9 +10648,9 @@ async function loadManifestDoc(projectDir) {
|
|
|
10231
10648
|
return { doc: parseDocument(text), file };
|
|
10232
10649
|
}
|
|
10233
10650
|
async function writeManifestDoc(file, doc) {
|
|
10234
|
-
const dir =
|
|
10651
|
+
const dir = path15.dirname(file);
|
|
10235
10652
|
await mkdir6(dir, { recursive: true });
|
|
10236
|
-
const temporary =
|
|
10653
|
+
const temporary = path15.join(dir, `.${path15.basename(file)}.tmp-${process.pid}-${randomUUID2()}`);
|
|
10237
10654
|
try {
|
|
10238
10655
|
const handle = await open(temporary, "wx", 438);
|
|
10239
10656
|
try {
|
|
@@ -10279,9 +10696,9 @@ function scalarString(item) {
|
|
|
10279
10696
|
}
|
|
10280
10697
|
|
|
10281
10698
|
// src/lib/theme-manager.ts
|
|
10282
|
-
import { cp as cp2, mkdir as mkdir7, readFile as
|
|
10283
|
-
import { existsSync as
|
|
10284
|
-
import
|
|
10699
|
+
import { cp as cp2, mkdir as mkdir7, readFile as readFile27, readdir as readdir6, stat as stat5, writeFile as writeFile7, rm as rm3 } from "node:fs/promises";
|
|
10700
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
10701
|
+
import path16 from "node:path";
|
|
10285
10702
|
import { isSeq as isSeq2, Scalar } from "yaml";
|
|
10286
10703
|
|
|
10287
10704
|
// src/lib/fetch-timeout.ts
|
|
@@ -10338,29 +10755,13 @@ var BUILT_IN_FALLBACK_META = {
|
|
|
10338
10755
|
description: "Clean sans-serif manual with clear hierarchy and code styling."
|
|
10339
10756
|
}
|
|
10340
10757
|
};
|
|
10341
|
-
|
|
10342
|
-
try {
|
|
10343
|
-
const text = await readFile26(jsonPath, "utf8");
|
|
10344
|
-
const parsed = JSON.parse(text);
|
|
10345
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
10346
|
-
} catch {
|
|
10347
|
-
return {};
|
|
10348
|
-
}
|
|
10349
|
-
}
|
|
10758
|
+
var readThemeMeta = readExtensionMeta;
|
|
10350
10759
|
function themeStyleList(meta) {
|
|
10351
|
-
const declared =
|
|
10760
|
+
const declared = extensionStyleList(meta);
|
|
10352
10761
|
return declared.length > 0 ? declared : ["theme.css"];
|
|
10353
10762
|
}
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
if (path15.isAbsolute(rel) || rel.split(/[\\/]/).includes("..")) {
|
|
10357
|
-
throw new Error(`theme.json declares stylesheet "${rel}" outside the theme folder; a theme must be self-contained.`);
|
|
10358
|
-
}
|
|
10359
|
-
}
|
|
10360
|
-
}
|
|
10361
|
-
function themeEngineStyleList(meta) {
|
|
10362
|
-
return Array.isArray(meta.engineStyles?.native) ? meta.engineStyles.native.filter((s) => typeof s === "string" && s.trim().length > 0) : [];
|
|
10363
|
-
}
|
|
10763
|
+
var assertThemeSheetsContained = assertExtensionContained;
|
|
10764
|
+
var themeEngineStyleList = extensionEngineStyleList;
|
|
10364
10765
|
function themeInfo(id, kind, meta) {
|
|
10365
10766
|
const styles = themeStyleList(meta);
|
|
10366
10767
|
return {
|
|
@@ -10371,7 +10772,10 @@ function themeInfo(id, kind, meta) {
|
|
|
10371
10772
|
kind,
|
|
10372
10773
|
preview: meta.preview ?? null,
|
|
10373
10774
|
styles,
|
|
10374
|
-
tokensFile: meta.tokensFile?.trim() || styles[0]
|
|
10775
|
+
tokensFile: meta.tokensFile?.trim() || styles[0],
|
|
10776
|
+
markdown: meta.markdown?.trim() || undefined,
|
|
10777
|
+
components: meta.components?.trim() || undefined,
|
|
10778
|
+
snippets: meta.snippets?.trim() || undefined
|
|
10375
10779
|
};
|
|
10376
10780
|
}
|
|
10377
10781
|
function themeSlug(name) {
|
|
@@ -10381,14 +10785,14 @@ function themeDirFor(projectDir, id) {
|
|
|
10381
10785
|
if (typeof id !== "string" || id.length === 0 || themeSlug(id) !== id) {
|
|
10382
10786
|
throw new Error(`Invalid theme id "${id}".`);
|
|
10383
10787
|
}
|
|
10384
|
-
return
|
|
10788
|
+
return path16.join(projectDir, THEMES_DIR, id);
|
|
10385
10789
|
}
|
|
10386
10790
|
async function listBuiltInThemes() {
|
|
10387
10791
|
const out = [];
|
|
10388
10792
|
for (const id of BUILT_IN_THEME_IDS) {
|
|
10389
10793
|
let meta = {};
|
|
10390
10794
|
try {
|
|
10391
|
-
meta = await readThemeMeta(await
|
|
10795
|
+
meta = await readThemeMeta(path16.join(await getAssetsDir(), THEMES_DIR, id));
|
|
10392
10796
|
} catch {
|
|
10393
10797
|
meta = {};
|
|
10394
10798
|
}
|
|
@@ -10406,20 +10810,19 @@ async function resolveBuiltInTheme(id) {
|
|
|
10406
10810
|
throw new Error(`Unknown built-in theme: "${id}".`);
|
|
10407
10811
|
}
|
|
10408
10812
|
const cssPath = await getAssetPath(`${THEMES_DIR}/${id}/theme.css`);
|
|
10409
|
-
|
|
10410
|
-
if (!existsSync12(cssPath)) {
|
|
10813
|
+
if (!existsSync13(cssPath)) {
|
|
10411
10814
|
throw new Error(`Built-in theme "${id}" is missing its theme.css.`);
|
|
10412
10815
|
}
|
|
10413
|
-
const meta = await readThemeMeta(
|
|
10816
|
+
const meta = await readThemeMeta(path16.join(await getAssetsDir(), THEMES_DIR, id));
|
|
10414
10817
|
if (!meta.name)
|
|
10415
10818
|
meta.name = BUILT_IN_FALLBACK_META[id]?.name;
|
|
10416
10819
|
if (!meta.description) {
|
|
10417
10820
|
meta.description = BUILT_IN_FALLBACK_META[id]?.description;
|
|
10418
10821
|
}
|
|
10419
|
-
return { info: themeInfo(id, "builtin", meta), cssPath, dir:
|
|
10822
|
+
return { info: themeInfo(id, "builtin", meta), cssPath, dir: path16.dirname(cssPath) };
|
|
10420
10823
|
}
|
|
10421
10824
|
async function listProjectThemes(projectDir) {
|
|
10422
|
-
const root =
|
|
10825
|
+
const root = path16.join(projectDir, THEMES_DIR);
|
|
10423
10826
|
let entries;
|
|
10424
10827
|
try {
|
|
10425
10828
|
entries = await readdir6(root, { withFileTypes: true });
|
|
@@ -10430,10 +10833,10 @@ async function listProjectThemes(projectDir) {
|
|
|
10430
10833
|
for (const entry of entries) {
|
|
10431
10834
|
if (!entry.isDirectory())
|
|
10432
10835
|
continue;
|
|
10433
|
-
const dir =
|
|
10434
|
-
const meta = await readThemeMeta(
|
|
10836
|
+
const dir = path16.join(root, entry.name);
|
|
10837
|
+
const meta = await readThemeMeta(dir);
|
|
10435
10838
|
const primary = themeStyleList(meta)[0];
|
|
10436
|
-
if (!
|
|
10839
|
+
if (!existsSync13(path16.join(dir, primary)))
|
|
10437
10840
|
continue;
|
|
10438
10841
|
out.push(themeInfo(entry.name, "project", meta));
|
|
10439
10842
|
}
|
|
@@ -10484,10 +10887,10 @@ async function getActiveTheme(projectDir) {
|
|
|
10484
10887
|
if (!m)
|
|
10485
10888
|
continue;
|
|
10486
10889
|
const id = m[1];
|
|
10487
|
-
const dir =
|
|
10488
|
-
const meta = await readThemeMeta(
|
|
10890
|
+
const dir = path16.join(projectDir, THEMES_DIR, id);
|
|
10891
|
+
const meta = await readThemeMeta(dir);
|
|
10489
10892
|
const primary = themeStyleList(meta)[0];
|
|
10490
|
-
if (!
|
|
10893
|
+
if (!existsSync13(path16.join(dir, primary)))
|
|
10491
10894
|
continue;
|
|
10492
10895
|
return themeInfo(id, "project", meta);
|
|
10493
10896
|
}
|
|
@@ -10525,9 +10928,9 @@ async function getPreviousTheme(projectDir) {
|
|
|
10525
10928
|
if (!id)
|
|
10526
10929
|
return null;
|
|
10527
10930
|
const dir = themeDirFor(projectDir, id);
|
|
10528
|
-
const meta = await readThemeMeta(
|
|
10931
|
+
const meta = await readThemeMeta(dir);
|
|
10529
10932
|
const primary = themeStyleList(meta)[0];
|
|
10530
|
-
if (!
|
|
10933
|
+
if (!existsSync13(path16.join(dir, primary)))
|
|
10531
10934
|
return null;
|
|
10532
10935
|
const active = await getActiveTheme(projectDir);
|
|
10533
10936
|
if (active && active.id === id)
|
|
@@ -10547,23 +10950,23 @@ async function applyTheme(projectDir, target) {
|
|
|
10547
10950
|
if (target.kind === "builtin") {
|
|
10548
10951
|
const resolved = await resolveBuiltInTheme(target.id);
|
|
10549
10952
|
let destId = target.id;
|
|
10550
|
-
if (
|
|
10953
|
+
if (existsSync13(path16.join(projectDir, THEMES_DIR, destId, "theme.css"))) {
|
|
10551
10954
|
destId = await uniqueThemeId(projectDir, target.id);
|
|
10552
10955
|
}
|
|
10553
|
-
const destDir =
|
|
10956
|
+
const destDir = path16.join(projectDir, THEMES_DIR, destId);
|
|
10554
10957
|
await mkdir7(destDir, { recursive: true });
|
|
10555
10958
|
await cp2(resolved.dir, destDir, { recursive: true });
|
|
10556
|
-
meta = await readThemeMeta(
|
|
10959
|
+
meta = await readThemeMeta(destDir);
|
|
10557
10960
|
assertThemeSheetsContained(meta);
|
|
10558
10961
|
resolveDeclaredStyles(themeStyleList(meta), destDir, `Theme "${destId}"`);
|
|
10559
10962
|
resolveDeclaredStyles(themeEngineStyleList(meta), destDir, `Theme "${destId}"`);
|
|
10560
10963
|
info2 = themeInfo(destId, "project", meta);
|
|
10561
10964
|
} else {
|
|
10562
10965
|
const dir = themeDirFor(projectDir, target.id);
|
|
10563
|
-
meta = await readThemeMeta(
|
|
10966
|
+
meta = await readThemeMeta(dir);
|
|
10564
10967
|
assertThemeSheetsContained(meta);
|
|
10565
10968
|
const primary = themeStyleList(meta)[0];
|
|
10566
|
-
if (!
|
|
10969
|
+
if (!existsSync13(path16.join(dir, primary))) {
|
|
10567
10970
|
throw new Error(`Theme "${target.id}" is not present in this project.`);
|
|
10568
10971
|
}
|
|
10569
10972
|
resolveDeclaredStyles(themeStyleList(meta), dir, `Theme "${target.id}"`);
|
|
@@ -10574,10 +10977,10 @@ async function applyTheme(projectDir, target) {
|
|
|
10574
10977
|
return info2;
|
|
10575
10978
|
}
|
|
10576
10979
|
async function uniqueThemeId(projectDir, base) {
|
|
10577
|
-
const root =
|
|
10980
|
+
const root = path16.join(projectDir, THEMES_DIR);
|
|
10578
10981
|
let id = themeSlug(base);
|
|
10579
10982
|
let n = 2;
|
|
10580
|
-
while (
|
|
10983
|
+
while (existsSync13(path16.join(root, id))) {
|
|
10581
10984
|
id = `${themeSlug(base)}-${n++}`;
|
|
10582
10985
|
}
|
|
10583
10986
|
return id;
|
|
@@ -10592,20 +10995,20 @@ async function importThemeFromFolder(projectDir, sourceDir) {
|
|
|
10592
10995
|
if (!info2.isDirectory()) {
|
|
10593
10996
|
throw new Error("The chosen path is not a folder.");
|
|
10594
10997
|
}
|
|
10595
|
-
const meta = await readThemeMeta(
|
|
10998
|
+
const meta = await readThemeMeta(sourceDir);
|
|
10596
10999
|
assertThemeSheetsContained(meta);
|
|
10597
11000
|
const primary = themeStyleList(meta)[0];
|
|
10598
|
-
if (!
|
|
11001
|
+
if (!existsSync13(path16.join(sourceDir, primary))) {
|
|
10599
11002
|
throw new Error(primary === "theme.css" ? "A theme folder must contain a theme.css file." : `A theme folder must contain its declared stylesheet: ${primary}`);
|
|
10600
11003
|
}
|
|
10601
11004
|
resolveDeclaredStyles(themeStyleList(meta), sourceDir, "The theme folder");
|
|
10602
11005
|
resolveDeclaredStyles(themeEngineStyleList(meta), sourceDir, "The theme folder");
|
|
10603
|
-
const base = meta.name ||
|
|
11006
|
+
const base = meta.name || path16.basename(sourceDir);
|
|
10604
11007
|
const id = await uniqueThemeId(projectDir, base);
|
|
10605
|
-
const destDir =
|
|
10606
|
-
await mkdir7(
|
|
11008
|
+
const destDir = path16.join(projectDir, THEMES_DIR, id);
|
|
11009
|
+
await mkdir7(path16.dirname(destDir), { recursive: true });
|
|
10607
11010
|
await cp2(sourceDir, destDir, { recursive: true });
|
|
10608
|
-
return themeInfo(id, "project", await readThemeMeta(
|
|
11011
|
+
return themeInfo(id, "project", await readThemeMeta(destDir));
|
|
10609
11012
|
}
|
|
10610
11013
|
function looksLikeCssUrl(url) {
|
|
10611
11014
|
try {
|
|
@@ -10652,7 +11055,7 @@ async function importThemeFromUrl(projectDir, url) {
|
|
|
10652
11055
|
let baseName;
|
|
10653
11056
|
if (looksLikeCssUrl(trimmed)) {
|
|
10654
11057
|
css = await fetchText(trimmed);
|
|
10655
|
-
baseName =
|
|
11058
|
+
baseName = path16.basename(new URL(trimmed, "https://x/").pathname, ".css") || "theme";
|
|
10656
11059
|
} else {
|
|
10657
11060
|
const base = trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
10658
11061
|
css = await fetchText(`${base}theme.css`);
|
|
@@ -10662,41 +11065,41 @@ async function importThemeFromUrl(projectDir, url) {
|
|
|
10662
11065
|
if (parsed && typeof parsed === "object")
|
|
10663
11066
|
meta = parsed;
|
|
10664
11067
|
} catch {}
|
|
10665
|
-
baseName = meta.name ||
|
|
11068
|
+
baseName = meta.name || path16.basename(new URL(base, "https://x/").pathname.replace(/\/$/, "")) || "theme";
|
|
10666
11069
|
}
|
|
10667
11070
|
if (!css.trim()) {
|
|
10668
11071
|
throw new Error(`Fetched theme CSS from ${trimmed} was empty.`);
|
|
10669
11072
|
}
|
|
10670
11073
|
assertLooksLikeCss(css, trimmed);
|
|
10671
11074
|
const id = await uniqueThemeId(projectDir, meta.name || baseName);
|
|
10672
|
-
const destDir =
|
|
11075
|
+
const destDir = path16.join(projectDir, THEMES_DIR, id);
|
|
10673
11076
|
await mkdir7(destDir, { recursive: true });
|
|
10674
|
-
await writeFile7(
|
|
11077
|
+
await writeFile7(path16.join(destDir, "theme.css"), css, "utf8");
|
|
10675
11078
|
const finalMeta = {
|
|
10676
11079
|
name: meta.name || prettify(id),
|
|
10677
11080
|
author: meta.author,
|
|
10678
11081
|
description: meta.description,
|
|
10679
11082
|
preview: meta.preview ?? null
|
|
10680
11083
|
};
|
|
10681
|
-
await writeFile7(
|
|
11084
|
+
await writeFile7(path16.join(destDir, "theme.json"), JSON.stringify(finalMeta, null, 2), "utf8");
|
|
10682
11085
|
return themeInfo(id, "project", finalMeta);
|
|
10683
11086
|
}
|
|
10684
11087
|
async function readThemeCss(projectDir, source) {
|
|
10685
11088
|
if (source.kind === "builtin") {
|
|
10686
11089
|
const resolved = await resolveBuiltInTheme(source.id);
|
|
10687
|
-
return
|
|
11090
|
+
return readFile27(resolved.cssPath, "utf8");
|
|
10688
11091
|
}
|
|
10689
11092
|
if (!projectDir)
|
|
10690
11093
|
throw new Error("A project is required to read a project theme.");
|
|
10691
11094
|
const dir = themeDirFor(projectDir, source.id);
|
|
10692
|
-
const meta = await readThemeMeta(
|
|
11095
|
+
const meta = await readThemeMeta(dir);
|
|
10693
11096
|
const sheets = themeStyleList(meta);
|
|
10694
11097
|
const parts = [];
|
|
10695
11098
|
for (const rel of sheets) {
|
|
10696
|
-
const sheetPath =
|
|
10697
|
-
if (!
|
|
11099
|
+
const sheetPath = path16.join(dir, rel);
|
|
11100
|
+
if (!existsSync13(sheetPath))
|
|
10698
11101
|
continue;
|
|
10699
|
-
parts.push(await
|
|
11102
|
+
parts.push(await readFile27(sheetPath, "utf8"));
|
|
10700
11103
|
}
|
|
10701
11104
|
if (parts.length === 0) {
|
|
10702
11105
|
throw new Error(`Theme "${source.id}" has no ${sheets[0]} in this project.`);
|
|
@@ -10707,7 +11110,7 @@ async function readThemeCss(projectDir, source) {
|
|
|
10707
11110
|
}
|
|
10708
11111
|
async function removeProjectTheme(projectDir, id) {
|
|
10709
11112
|
const dir = themeDirFor(projectDir, id);
|
|
10710
|
-
if (
|
|
11113
|
+
if (existsSync13(dir)) {
|
|
10711
11114
|
await rm3(dir, { recursive: true, force: true });
|
|
10712
11115
|
}
|
|
10713
11116
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
@@ -10745,7 +11148,7 @@ async function detectLegacyForkedTheme(projectDir, active) {
|
|
|
10745
11148
|
return null;
|
|
10746
11149
|
let bookCss;
|
|
10747
11150
|
try {
|
|
10748
|
-
bookCss = await
|
|
11151
|
+
bookCss = await readFile27(path16.join(projectDir, "styles", "book.css"), "utf8");
|
|
10749
11152
|
} catch {
|
|
10750
11153
|
return null;
|
|
10751
11154
|
}
|
|
@@ -10844,7 +11247,7 @@ async function scaffoldProject(options) {
|
|
|
10844
11247
|
throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
10845
11248
|
}
|
|
10846
11249
|
const parentDir = options.parentDir;
|
|
10847
|
-
if (!parentDir || !
|
|
11250
|
+
if (!parentDir || !path17.isAbsolute(parentDir)) {
|
|
10848
11251
|
throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
|
|
10849
11252
|
}
|
|
10850
11253
|
try {
|
|
@@ -10852,7 +11255,7 @@ async function scaffoldProject(options) {
|
|
|
10852
11255
|
} catch {
|
|
10853
11256
|
throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
10854
11257
|
}
|
|
10855
|
-
const projectDir =
|
|
11258
|
+
const projectDir = path17.join(parentDir, folderName);
|
|
10856
11259
|
let targetExists = true;
|
|
10857
11260
|
try {
|
|
10858
11261
|
await access(projectDir, FS.F_OK);
|
|
@@ -10874,17 +11277,17 @@ async function scaffoldProject(options) {
|
|
|
10874
11277
|
for (const entry of entries) {
|
|
10875
11278
|
if (entry.name === ".gutterpress-template.json")
|
|
10876
11279
|
continue;
|
|
10877
|
-
await cp3(
|
|
11280
|
+
await cp3(path17.join(customTemplateDir, entry.name), path17.join(projectDir, entry.name), { recursive: true });
|
|
10878
11281
|
}
|
|
10879
|
-
await mkdir8(
|
|
11282
|
+
await mkdir8(path17.join(projectDir, "assets"), { recursive: true });
|
|
10880
11283
|
} else {
|
|
10881
11284
|
const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
|
|
10882
11285
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
10883
|
-
await mkdir8(
|
|
10884
|
-
await copyFile(tplManifest,
|
|
10885
|
-
await copyFile(tplChapter,
|
|
10886
|
-
await mkdir8(
|
|
10887
|
-
await writeFile8(
|
|
11286
|
+
await mkdir8(path17.join(projectDir, "assets"), { recursive: true });
|
|
11287
|
+
await copyFile(tplManifest, path17.join(projectDir, MANIFEST_FILENAMES[0]));
|
|
11288
|
+
await copyFile(tplChapter, path17.join(projectDir, "chapter-01.md"));
|
|
11289
|
+
await mkdir8(path17.join(projectDir, "styles"), { recursive: true });
|
|
11290
|
+
await writeFile8(path17.join(projectDir, "styles", "book.css"), starterOverridesCss(starterThemeId), "utf8");
|
|
10888
11291
|
}
|
|
10889
11292
|
} catch (e) {
|
|
10890
11293
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not create the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -10894,13 +11297,13 @@ async function scaffoldProject(options) {
|
|
|
10894
11297
|
} catch (e) {
|
|
10895
11298
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
|
|
10896
11299
|
}
|
|
10897
|
-
const manifestPath = MANIFEST_FILENAMES.map((name2) =>
|
|
11300
|
+
const manifestPath = MANIFEST_FILENAMES.map((name2) => path17.join(projectDir, name2)).find(existsSync14) ?? path17.join(projectDir, MANIFEST_FILENAMES[0]);
|
|
10898
11301
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
10899
11302
|
const substitutions = {
|
|
10900
11303
|
"{{TITLE}}": escapeYamlScalar(name),
|
|
10901
11304
|
"{{AUTHOR}}": escapeYamlScalar(author)
|
|
10902
11305
|
};
|
|
10903
|
-
let openFile =
|
|
11306
|
+
let openFile = path17.join(projectDir, "chapter-01.md");
|
|
10904
11307
|
try {
|
|
10905
11308
|
await fillTemplateFile(manifestPath, substitutions);
|
|
10906
11309
|
if (!customTemplateDir) {
|
|
@@ -10924,7 +11327,7 @@ async function scaffoldProject(options) {
|
|
|
10924
11327
|
}
|
|
10925
11328
|
const firstSource = await firstSourceFile(manifestPath);
|
|
10926
11329
|
if (firstSource)
|
|
10927
|
-
openFile =
|
|
11330
|
+
openFile = path17.join(projectDir, firstSource);
|
|
10928
11331
|
await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
|
|
10929
11332
|
} catch (e) {
|
|
10930
11333
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not finalise the project files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -10946,7 +11349,7 @@ async function scaffoldProject(options) {
|
|
|
10946
11349
|
versionHistory = "none";
|
|
10947
11350
|
const { findEnclosingRepoDir } = await import("./project-source-ekcyp63q.js");
|
|
10948
11351
|
const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
|
|
10949
|
-
versionHistoryError = enclosingRepoDir ? `This book was added to ${
|
|
11352
|
+
versionHistoryError = enclosingRepoDir ? `This book was added to ${path17.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
|
|
10950
11353
|
}
|
|
10951
11354
|
}
|
|
10952
11355
|
const result = {
|
|
@@ -10966,7 +11369,7 @@ function prettifyFolderName(base) {
|
|
|
10966
11369
|
}
|
|
10967
11370
|
async function adoptFolder(options) {
|
|
10968
11371
|
const dir = options.dir;
|
|
10969
|
-
if (typeof dir !== "string" || !
|
|
11372
|
+
if (typeof dir !== "string" || !path17.isAbsolute(dir)) {
|
|
10970
11373
|
throw new CreateProjectErrorImpl("scaffold-io", "An absolute folder path is required.");
|
|
10971
11374
|
}
|
|
10972
11375
|
let st;
|
|
@@ -10978,11 +11381,11 @@ async function adoptFolder(options) {
|
|
|
10978
11381
|
if (!st.isDirectory()) {
|
|
10979
11382
|
throw new CreateProjectErrorImpl("scaffold-io", `Not a folder: ${dir}`);
|
|
10980
11383
|
}
|
|
10981
|
-
if (MANIFEST_FILENAMES.some((name) =>
|
|
11384
|
+
if (MANIFEST_FILENAMES.some((name) => existsSync14(path17.join(dir, name)))) {
|
|
10982
11385
|
throw new CreateProjectErrorImpl("target-exists", "This folder is already a Gutterpress project.");
|
|
10983
11386
|
}
|
|
10984
11387
|
const template = options.template ?? "book";
|
|
10985
|
-
const title = (options.title ?? "").trim() || prettifyFolderName(
|
|
11388
|
+
const title = (options.title ?? "").trim() || prettifyFolderName(path17.basename(dir));
|
|
10986
11389
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
10987
11390
|
let mdFiles;
|
|
10988
11391
|
try {
|
|
@@ -10990,14 +11393,14 @@ async function adoptFolder(options) {
|
|
|
10990
11393
|
mdFiles = entries.filter((e) => e.isFile() && /\.(md|markdown)$/i.test(e.name)).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
10991
11394
|
if (mdFiles.length === 0) {
|
|
10992
11395
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
10993
|
-
await copyFile(tplChapter,
|
|
10994
|
-
await fillTemplateFile(
|
|
11396
|
+
await copyFile(tplChapter, path17.join(dir, "chapter-01.md"));
|
|
11397
|
+
await fillTemplateFile(path17.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
|
|
10995
11398
|
mdFiles = ["chapter-01.md"];
|
|
10996
11399
|
}
|
|
10997
11400
|
const themeId = STARTER_THEME_FOR_TEMPLATE[template] ?? "clean-book";
|
|
10998
|
-
await mkdir8(
|
|
10999
|
-
const bookCssPath =
|
|
11000
|
-
const hadOwnStylesheet =
|
|
11401
|
+
await mkdir8(path17.join(dir, "styles"), { recursive: true });
|
|
11402
|
+
const bookCssPath = path17.join(dir, "styles", "book.css");
|
|
11403
|
+
const hadOwnStylesheet = existsSync14(bookCssPath);
|
|
11001
11404
|
if (!hadOwnStylesheet) {
|
|
11002
11405
|
await writeFile8(bookCssPath, starterOverridesCss(themeId), "utf8");
|
|
11003
11406
|
}
|
|
@@ -11015,8 +11418,8 @@ ${filesYaml}
|
|
|
11015
11418
|
styles:
|
|
11016
11419
|
- styles/book.css
|
|
11017
11420
|
`;
|
|
11018
|
-
await writeFile8(
|
|
11019
|
-
await mkdir8(
|
|
11421
|
+
await writeFile8(path17.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
|
|
11422
|
+
await mkdir8(path17.join(dir, "assets"), { recursive: true });
|
|
11020
11423
|
if (!hadOwnStylesheet) {
|
|
11021
11424
|
await applyTheme(dir, { kind: "builtin", id: themeId });
|
|
11022
11425
|
}
|
|
@@ -11046,8 +11449,8 @@ styles:
|
|
|
11046
11449
|
}
|
|
11047
11450
|
const result = {
|
|
11048
11451
|
projectDir: dir,
|
|
11049
|
-
manifestPath:
|
|
11050
|
-
openFile:
|
|
11452
|
+
manifestPath: path17.join(dir, MANIFEST_FILENAMES[0]),
|
|
11453
|
+
openFile: path17.join(dir, mdFiles[0]),
|
|
11051
11454
|
versionHistory
|
|
11052
11455
|
};
|
|
11053
11456
|
if (versionHistoryError !== undefined)
|
|
@@ -11055,10 +11458,10 @@ styles:
|
|
|
11055
11458
|
return result;
|
|
11056
11459
|
}
|
|
11057
11460
|
async function ensureGitignoreHasDist(projectDir) {
|
|
11058
|
-
const gitignorePath =
|
|
11461
|
+
const gitignorePath = path17.join(projectDir, ".gitignore");
|
|
11059
11462
|
let existing = null;
|
|
11060
11463
|
try {
|
|
11061
|
-
existing = await
|
|
11464
|
+
existing = await readFile28(gitignorePath, "utf8");
|
|
11062
11465
|
} catch {}
|
|
11063
11466
|
if (existing === null) {
|
|
11064
11467
|
await writeFile8(gitignorePath, `dist/
|
|
@@ -11078,7 +11481,7 @@ async function ensureGitignoreHasDist(projectDir) {
|
|
|
11078
11481
|
async function firstSourceFile(manifestPath) {
|
|
11079
11482
|
let text;
|
|
11080
11483
|
try {
|
|
11081
|
-
text = await
|
|
11484
|
+
text = await readFile28(manifestPath, "utf8");
|
|
11082
11485
|
} catch {
|
|
11083
11486
|
return;
|
|
11084
11487
|
}
|
|
@@ -11090,17 +11493,161 @@ async function firstSourceFile(manifestPath) {
|
|
|
11090
11493
|
return m ? m[1].trim() : undefined;
|
|
11091
11494
|
}
|
|
11092
11495
|
async function fillTemplateFile(filePath, substitutions) {
|
|
11093
|
-
let text = await
|
|
11496
|
+
let text = await readFile28(filePath, "utf8");
|
|
11094
11497
|
for (const [token, value] of Object.entries(substitutions)) {
|
|
11095
11498
|
text = text.split(token).join(value);
|
|
11096
11499
|
}
|
|
11097
11500
|
await writeFile8(filePath, text, "utf8");
|
|
11098
11501
|
}
|
|
11502
|
+
// src/lib/extension-scaffold.ts
|
|
11503
|
+
import { access as access2, mkdir as mkdir9, readFile as readFile29, writeFile as writeFile9 } from "node:fs/promises";
|
|
11504
|
+
import { constants as FS2 } from "node:fs";
|
|
11505
|
+
import path18 from "node:path";
|
|
11506
|
+
var EXTENSION_KINDS = ["plugin", "theme"];
|
|
11507
|
+
var RESERVED_PREFIX = "gp-";
|
|
11508
|
+
var TEMPLATE_FILES = {
|
|
11509
|
+
plugin: [
|
|
11510
|
+
["gutterpress.json", "gutterpress.json"],
|
|
11511
|
+
["package.json", "package.json"],
|
|
11512
|
+
["README.md", "README.md"],
|
|
11513
|
+
["plugin.js.tpl", "plugin.js"],
|
|
11514
|
+
["styles/plugin.css", "styles/plugin.css"],
|
|
11515
|
+
["snippets/term-box.md", "snippets/term-box.md"],
|
|
11516
|
+
["test/fixture.md", "test/fixture.md"],
|
|
11517
|
+
["test/expected.html", "test/expected.html"],
|
|
11518
|
+
["test/plugin.test.js.tpl", "test/plugin.test.js"]
|
|
11519
|
+
],
|
|
11520
|
+
theme: [
|
|
11521
|
+
["gutterpress.json", "gutterpress.json"],
|
|
11522
|
+
["README.md", "README.md"],
|
|
11523
|
+
["components.yaml", "components.yaml"],
|
|
11524
|
+
["snippets/callout.md", "snippets/callout.md"],
|
|
11525
|
+
["styles/tokens.css", "styles/tokens.css"],
|
|
11526
|
+
["styles/base.css", "styles/base.css"],
|
|
11527
|
+
["styles/components.css", "styles/components.css"],
|
|
11528
|
+
["styles/page-templates.css", "styles/page-templates.css"],
|
|
11529
|
+
["styles/page-rules.css", "styles/page-rules.css"],
|
|
11530
|
+
["styles/book.css", "styles/book.css"]
|
|
11531
|
+
]
|
|
11532
|
+
};
|
|
11533
|
+
var OPEN_FILE = {
|
|
11534
|
+
plugin: "plugin.js",
|
|
11535
|
+
theme: "styles/tokens.css"
|
|
11536
|
+
};
|
|
11537
|
+
var DEFAULT_DESCRIPTION = {
|
|
11538
|
+
plugin: "A Gutterpress plugin.",
|
|
11539
|
+
theme: "A Gutterpress theme."
|
|
11540
|
+
};
|
|
11541
|
+
var DEFAULT_AUTHOR2 = "Anonymous";
|
|
11542
|
+
|
|
11543
|
+
class ExtensionScaffoldError extends Error {
|
|
11544
|
+
code;
|
|
11545
|
+
constructor(code, message) {
|
|
11546
|
+
super(message);
|
|
11547
|
+
this.name = "CreateProjectError";
|
|
11548
|
+
this.code = code;
|
|
11549
|
+
}
|
|
11550
|
+
}
|
|
11551
|
+
function resolveExtensionPrefix(slug, requested) {
|
|
11552
|
+
const raw = (requested ?? `${slug}-`).trim();
|
|
11553
|
+
const prefix = raw.endsWith("-") ? raw : `${raw}-`;
|
|
11554
|
+
if (!/^[a-z][a-z0-9-]*-$/.test(prefix)) {
|
|
11555
|
+
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-".`);
|
|
11556
|
+
}
|
|
11557
|
+
if (prefix === RESERVED_PREFIX) {
|
|
11558
|
+
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.`);
|
|
11559
|
+
}
|
|
11560
|
+
return prefix;
|
|
11561
|
+
}
|
|
11562
|
+
function identifierFor(slug) {
|
|
11563
|
+
const camel = slug.replace(/-([a-z0-9])/g, (_m, c) => c.toUpperCase());
|
|
11564
|
+
return /^[0-9]/.test(camel) ? `_${camel}` : camel;
|
|
11565
|
+
}
|
|
11566
|
+
function escapeJsonScalar(value) {
|
|
11567
|
+
return JSON.stringify(value).slice(1, -1);
|
|
11568
|
+
}
|
|
11569
|
+
async function scaffoldExtension(options) {
|
|
11570
|
+
const kind = options.kind;
|
|
11571
|
+
if (kind !== "plugin" && kind !== "theme") {
|
|
11572
|
+
throw new ExtensionScaffoldError("invalid-name", `Unknown extension kind "${String(kind)}". Choose one of: ${EXTENSION_KINDS.join(", ")}.`);
|
|
11573
|
+
}
|
|
11574
|
+
const name = (options.name ?? "").trim();
|
|
11575
|
+
if (!name) {
|
|
11576
|
+
throw new ExtensionScaffoldError("invalid-name", "A name is required.");
|
|
11577
|
+
}
|
|
11578
|
+
const slug = (options.folderName ?? slugify(name)).trim();
|
|
11579
|
+
if (!slug || slug !== slugify(slug)) {
|
|
11580
|
+
throw new ExtensionScaffoldError("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
11581
|
+
}
|
|
11582
|
+
const prefix = resolveExtensionPrefix(slug, options.prefix);
|
|
11583
|
+
const parentDir = options.parentDir;
|
|
11584
|
+
if (!parentDir || !path18.isAbsolute(parentDir)) {
|
|
11585
|
+
throw new ExtensionScaffoldError("parent-not-writable", "The save location must be an absolute path.");
|
|
11586
|
+
}
|
|
11587
|
+
try {
|
|
11588
|
+
await access2(parentDir, FS2.W_OK);
|
|
11589
|
+
} catch {
|
|
11590
|
+
throw new ExtensionScaffoldError("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
11591
|
+
}
|
|
11592
|
+
const extensionDir = path18.join(parentDir, slug);
|
|
11593
|
+
let targetExists = true;
|
|
11594
|
+
try {
|
|
11595
|
+
await access2(extensionDir, FS2.F_OK);
|
|
11596
|
+
} catch {
|
|
11597
|
+
targetExists = false;
|
|
11598
|
+
}
|
|
11599
|
+
if (targetExists) {
|
|
11600
|
+
throw new ExtensionScaffoldError("target-exists", `A folder named "${slug}" already exists here. Choose a different name or location.`);
|
|
11601
|
+
}
|
|
11602
|
+
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR2;
|
|
11603
|
+
const description = (options.description ?? "").trim() || DEFAULT_DESCRIPTION[kind];
|
|
11604
|
+
const plain = {
|
|
11605
|
+
"{{NAME}}": name,
|
|
11606
|
+
"{{SLUG}}": slug,
|
|
11607
|
+
"{{PREFIX}}": prefix,
|
|
11608
|
+
"{{AUTHOR}}": author,
|
|
11609
|
+
"{{DESCRIPTION}}": description,
|
|
11610
|
+
"{{IDENT}}": identifierFor(slug)
|
|
11611
|
+
};
|
|
11612
|
+
const jsonSafe = Object.fromEntries(Object.entries(plain).map(([token, value]) => [token, escapeJsonScalar(value)]));
|
|
11613
|
+
const entries = TEMPLATE_FILES[kind];
|
|
11614
|
+
const written = [];
|
|
11615
|
+
try {
|
|
11616
|
+
await mkdir9(extensionDir, { recursive: true });
|
|
11617
|
+
for (const [assetName, relDest] of entries) {
|
|
11618
|
+
const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
|
|
11619
|
+
const dest = path18.join(extensionDir, relDest);
|
|
11620
|
+
await mkdir9(path18.dirname(dest), { recursive: true });
|
|
11621
|
+
const text = await readFile29(src, "utf8");
|
|
11622
|
+
const table = relDest.endsWith(".json") ? jsonSafe : plain;
|
|
11623
|
+
await writeFile9(dest, substitute(text, table), "utf8");
|
|
11624
|
+
written.push(relDest);
|
|
11625
|
+
}
|
|
11626
|
+
} catch (e) {
|
|
11627
|
+
throw new ExtensionScaffoldError("scaffold-io", `Could not create the ${kind} files: ${e instanceof Error ? e.message : String(e)}`);
|
|
11628
|
+
}
|
|
11629
|
+
return {
|
|
11630
|
+
extensionDir,
|
|
11631
|
+
manifestPath: path18.join(extensionDir, "gutterpress.json"),
|
|
11632
|
+
kind,
|
|
11633
|
+
slug,
|
|
11634
|
+
prefix,
|
|
11635
|
+
openFile: path18.join(extensionDir, OPEN_FILE[kind]),
|
|
11636
|
+
files: written
|
|
11637
|
+
};
|
|
11638
|
+
}
|
|
11639
|
+
function substitute(text, table) {
|
|
11640
|
+
let out = text;
|
|
11641
|
+
for (const [token, value] of Object.entries(table)) {
|
|
11642
|
+
out = out.split(token).join(value);
|
|
11643
|
+
}
|
|
11644
|
+
return out;
|
|
11645
|
+
}
|
|
11099
11646
|
// src/lib/project-templates.ts
|
|
11100
|
-
import { cp as cp4, mkdir as
|
|
11101
|
-
import { constants as
|
|
11102
|
-
import { access as
|
|
11103
|
-
import
|
|
11647
|
+
import { cp as cp4, mkdir as mkdir10, readFile as readFile30, readdir as readdir8, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
|
|
11648
|
+
import { constants as FS3, existsSync as existsSync15 } from "node:fs";
|
|
11649
|
+
import { access as access3 } from "node:fs/promises";
|
|
11650
|
+
import path19 from "node:path";
|
|
11104
11651
|
import { isMap, isScalar, isSeq as isSeq3, parseDocument as parseDocument2 } from "yaml";
|
|
11105
11652
|
var BUILT_IN_TEMPLATE_IDS = [
|
|
11106
11653
|
"book",
|
|
@@ -11109,7 +11656,7 @@ var BUILT_IN_TEMPLATE_IDS = [
|
|
|
11109
11656
|
];
|
|
11110
11657
|
async function readTemplateChoices(manifestPath) {
|
|
11111
11658
|
try {
|
|
11112
|
-
const text = await
|
|
11659
|
+
const text = await readFile30(manifestPath, "utf8");
|
|
11113
11660
|
const doc = parseDocument2(text);
|
|
11114
11661
|
const out = {};
|
|
11115
11662
|
const preset = doc.get("preset");
|
|
@@ -11184,7 +11731,7 @@ function rewriteEntry(node, newPath) {
|
|
|
11184
11731
|
node.value = newPath;
|
|
11185
11732
|
}
|
|
11186
11733
|
function commonAncestorDir(absFiles) {
|
|
11187
|
-
const dirSegs = absFiles.map((f) =>
|
|
11734
|
+
const dirSegs = absFiles.map((f) => path19.resolve(f).split(path19.sep).slice(0, -1));
|
|
11188
11735
|
if (dirSegs.length === 0)
|
|
11189
11736
|
return "";
|
|
11190
11737
|
let common = dirSegs[0];
|
|
@@ -11194,22 +11741,22 @@ function commonAncestorDir(absFiles) {
|
|
|
11194
11741
|
i++;
|
|
11195
11742
|
common = common.slice(0, i);
|
|
11196
11743
|
}
|
|
11197
|
-
return common.join(
|
|
11744
|
+
return common.join(path19.sep) || path19.sep;
|
|
11198
11745
|
}
|
|
11199
11746
|
function pickVendorDir(templateDir, base) {
|
|
11200
|
-
const preferred =
|
|
11201
|
-
if (!
|
|
11747
|
+
const preferred = path19.basename(base) || VENDOR_DIR;
|
|
11748
|
+
if (!existsSync15(path19.join(templateDir, preferred)))
|
|
11202
11749
|
return preferred;
|
|
11203
11750
|
for (let n = 1;; n++) {
|
|
11204
11751
|
const candidate = `${preferred}-${n}`;
|
|
11205
|
-
if (!
|
|
11752
|
+
if (!existsSync15(path19.join(templateDir, candidate)))
|
|
11206
11753
|
return candidate;
|
|
11207
11754
|
}
|
|
11208
11755
|
}
|
|
11209
11756
|
var TEMPLATE_META_FILE = ".gutterpress-template.json";
|
|
11210
11757
|
async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
|
|
11211
11758
|
const { doc, file } = await loadManifestDoc(templateDir);
|
|
11212
|
-
const escapes = (p) => escapesProjectRoot(sourceProjectDir,
|
|
11759
|
+
const escapes = (p) => escapesProjectRoot(sourceProjectDir, path19.resolve(sourceProjectDir, p));
|
|
11213
11760
|
const stylesSeq = doc.get("styles", true);
|
|
11214
11761
|
const pluginsSeq = doc.get("plugins", true);
|
|
11215
11762
|
const escapingStyles = collectEscaping(isSeq3(stylesSeq) ? stylesSeq.items : [], scalarString, escapes);
|
|
@@ -11225,16 +11772,16 @@ async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
|
|
|
11225
11772
|
return { vendoredRefs: [], excludedRefs };
|
|
11226
11773
|
}
|
|
11227
11774
|
const styleClosure = await collectStyleDependencies(sourceProjectDir, escapingStyles.map((s) => s.value));
|
|
11228
|
-
const pluginFiles = escapingPlugins.map((p) =>
|
|
11775
|
+
const pluginFiles = escapingPlugins.map((p) => path19.resolve(sourceProjectDir, p.value));
|
|
11229
11776
|
const externalFiles = [...new Set([...styleClosure, ...pluginFiles])].filter((f) => escapesProjectRoot(sourceProjectDir, f));
|
|
11230
11777
|
const base = commonAncestorDir(externalFiles);
|
|
11231
11778
|
const vendorDir = pickVendorDir(templateDir, base);
|
|
11232
|
-
await Promise.all(externalFiles.filter((abs) =>
|
|
11233
|
-
const dest =
|
|
11234
|
-
await
|
|
11779
|
+
await Promise.all(externalFiles.filter((abs) => existsSync15(abs)).map(async (abs) => {
|
|
11780
|
+
const dest = path19.join(templateDir, vendorDir, path19.relative(base, abs));
|
|
11781
|
+
await mkdir10(path19.dirname(dest), { recursive: true });
|
|
11235
11782
|
await cp4(abs, dest);
|
|
11236
11783
|
}));
|
|
11237
|
-
const bookLocal = (value) => toPosixPath(
|
|
11784
|
+
const bookLocal = (value) => toPosixPath(path19.join(vendorDir, path19.relative(base, path19.resolve(sourceProjectDir, value))));
|
|
11238
11785
|
const vendoredRefs = [];
|
|
11239
11786
|
for (const { node, value } of [...escapingStyles, ...escapingPlugins]) {
|
|
11240
11787
|
const rewritten = bookLocal(value);
|
|
@@ -11249,30 +11796,30 @@ async function saveProjectAsTemplate(options) {
|
|
|
11249
11796
|
const id = slugify(name);
|
|
11250
11797
|
if (!id)
|
|
11251
11798
|
throw new Error(`Could not derive a template id from "${name}".`);
|
|
11252
|
-
const dir =
|
|
11799
|
+
const dir = path19.join(templatesRoot, id);
|
|
11253
11800
|
let exists = true;
|
|
11254
11801
|
try {
|
|
11255
|
-
await
|
|
11802
|
+
await access3(dir, FS3.F_OK);
|
|
11256
11803
|
} catch {
|
|
11257
11804
|
exists = false;
|
|
11258
11805
|
}
|
|
11259
11806
|
if (exists) {
|
|
11260
11807
|
throw new Error(`A template named "${name}" already exists. Choose a different name.`);
|
|
11261
11808
|
}
|
|
11262
|
-
await
|
|
11809
|
+
await mkdir10(dir, { recursive: true });
|
|
11263
11810
|
const entries = await readdir8(projectDir, { withFileTypes: true });
|
|
11264
11811
|
for (const entry of entries) {
|
|
11265
11812
|
if (SKIP_ENTRIES.has(entry.name))
|
|
11266
11813
|
continue;
|
|
11267
11814
|
if (entry.name === TEMPLATE_META_FILE)
|
|
11268
11815
|
continue;
|
|
11269
|
-
await cp4(
|
|
11816
|
+
await cp4(path19.join(projectDir, entry.name), path19.join(dir, entry.name), { recursive: true });
|
|
11270
11817
|
}
|
|
11271
11818
|
const manifestName = MANIFEST_FILENAMES.find((name2) => entries.some((entry) => entry.name === name2));
|
|
11272
11819
|
const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
|
|
11273
11820
|
if (manifestName)
|
|
11274
|
-
await retokeniseManifest(
|
|
11275
|
-
await
|
|
11821
|
+
await retokeniseManifest(path19.join(dir, manifestName));
|
|
11822
|
+
await writeFile10(path19.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
|
|
11276
11823
|
return {
|
|
11277
11824
|
id,
|
|
11278
11825
|
label: name,
|
|
@@ -11285,12 +11832,12 @@ async function saveProjectAsTemplate(options) {
|
|
|
11285
11832
|
async function retokeniseManifest(manifestPath) {
|
|
11286
11833
|
let text;
|
|
11287
11834
|
try {
|
|
11288
|
-
text = await
|
|
11835
|
+
text = await readFile30(manifestPath, "utf8");
|
|
11289
11836
|
} catch {
|
|
11290
11837
|
return;
|
|
11291
11838
|
}
|
|
11292
11839
|
text = text.replace(/^title:\s*.*$/m, 'title: "{{TITLE}}"').replace(/^(authors:[^\n]*\n[ \t]*-[ \t]*).*$/m, '$1"{{AUTHOR}}"');
|
|
11293
|
-
await
|
|
11840
|
+
await writeFile10(manifestPath, text, "utf8");
|
|
11294
11841
|
}
|
|
11295
11842
|
async function listCustomTemplates(templatesRoot) {
|
|
11296
11843
|
let entries;
|
|
@@ -11303,10 +11850,10 @@ async function listCustomTemplates(templatesRoot) {
|
|
|
11303
11850
|
for (const entry of entries) {
|
|
11304
11851
|
if (!entry.isDirectory())
|
|
11305
11852
|
continue;
|
|
11306
|
-
const dir =
|
|
11853
|
+
const dir = path19.join(templatesRoot, entry.name);
|
|
11307
11854
|
let label = prettify(entry.name);
|
|
11308
11855
|
try {
|
|
11309
|
-
const meta = JSON.parse(await
|
|
11856
|
+
const meta = JSON.parse(await readFile30(path19.join(dir, TEMPLATE_META_FILE), "utf8"));
|
|
11310
11857
|
if (typeof meta.label === "string" && meta.label)
|
|
11311
11858
|
label = meta.label;
|
|
11312
11859
|
} catch {}
|
|
@@ -11316,7 +11863,7 @@ async function listCustomTemplates(templatesRoot) {
|
|
|
11316
11863
|
description: "Your saved template.",
|
|
11317
11864
|
kind: "custom",
|
|
11318
11865
|
dir,
|
|
11319
|
-
...await readTemplateChoices(
|
|
11866
|
+
...await readTemplateChoices(path19.join(dir, MANIFEST_FILENAMES[0]))
|
|
11320
11867
|
});
|
|
11321
11868
|
}
|
|
11322
11869
|
out.sort((a, b) => a.label.localeCompare(b.label));
|
|
@@ -11328,84 +11875,17 @@ async function importTemplateFromFolder(options) {
|
|
|
11328
11875
|
if (!srcStat.isDirectory()) {
|
|
11329
11876
|
throw new Error("The chosen path is not a folder.");
|
|
11330
11877
|
}
|
|
11331
|
-
const name = options.name?.trim() || prettify(
|
|
11878
|
+
const name = options.name?.trim() || prettify(path19.basename(sourceDir));
|
|
11332
11879
|
return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
|
|
11333
11880
|
}
|
|
11334
11881
|
// src/lib/snippets.ts
|
|
11335
|
-
import { readdir as readdir9, readFile as
|
|
11336
|
-
import
|
|
11337
|
-
|
|
11338
|
-
var PLACEHOLDER_RE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
11339
|
-
function extractVariables(template) {
|
|
11340
|
-
const seen = new Set;
|
|
11341
|
-
const order = [];
|
|
11342
|
-
for (const m of template.matchAll(PLACEHOLDER_RE)) {
|
|
11343
|
-
const name = m[1];
|
|
11344
|
-
if (!seen.has(name)) {
|
|
11345
|
-
seen.add(name);
|
|
11346
|
-
order.push(name);
|
|
11347
|
-
}
|
|
11348
|
-
}
|
|
11349
|
-
return order;
|
|
11350
|
-
}
|
|
11351
|
-
function substituteVariables(template, values) {
|
|
11352
|
-
return template.replace(PLACEHOLDER_RE, (_full, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : "");
|
|
11353
|
-
}
|
|
11354
|
-
function resolveSnippetPath(projectDir, fileName) {
|
|
11355
|
-
const dir = path18.resolve(projectDir, SNIPPETS_DIR);
|
|
11356
|
-
const full = path18.resolve(dir, fileName);
|
|
11357
|
-
if (full !== path18.join(dir, path18.basename(fileName)) || path18.dirname(full) !== dir) {
|
|
11358
|
-
throw new Error(`Unsafe snippet filename: ${fileName}`);
|
|
11359
|
-
}
|
|
11360
|
-
return full;
|
|
11361
|
-
}
|
|
11362
|
-
async function listSnippets(projectDir) {
|
|
11363
|
-
const dir = path18.join(projectDir, SNIPPETS_DIR);
|
|
11364
|
-
let names;
|
|
11365
|
-
try {
|
|
11366
|
-
names = await readdir9(dir);
|
|
11367
|
-
} catch {
|
|
11368
|
-
return [];
|
|
11369
|
-
}
|
|
11370
|
-
const entries = [];
|
|
11371
|
-
for (const fileName of names) {
|
|
11372
|
-
if (!fileName.toLowerCase().endsWith(".md"))
|
|
11373
|
-
continue;
|
|
11374
|
-
let body = "";
|
|
11375
|
-
try {
|
|
11376
|
-
body = await readFile29(path18.join(dir, fileName), "utf8");
|
|
11377
|
-
} catch {
|
|
11378
|
-
continue;
|
|
11379
|
-
}
|
|
11380
|
-
entries.push({
|
|
11381
|
-
name: prettify(fileName.replace(/\.md$/i, "")),
|
|
11382
|
-
fileName,
|
|
11383
|
-
variables: extractVariables(body)
|
|
11384
|
-
});
|
|
11385
|
-
}
|
|
11386
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
11387
|
-
return entries;
|
|
11388
|
-
}
|
|
11389
|
-
async function readSnippet(projectDir, fileName) {
|
|
11390
|
-
return readFile29(resolveSnippetPath(projectDir, fileName), "utf8");
|
|
11391
|
-
}
|
|
11392
|
-
async function saveSnippet(projectDir, name, body) {
|
|
11393
|
-
const stem = slugify(name);
|
|
11394
|
-
if (!stem)
|
|
11395
|
-
throw new Error(`Could not derive a filename from "${name}".`);
|
|
11396
|
-
const fileName = `${stem}.md`;
|
|
11397
|
-
const dir = path18.join(projectDir, SNIPPETS_DIR);
|
|
11398
|
-
await mkdir10(dir, { recursive: true });
|
|
11399
|
-
await writeFile10(resolveSnippetPath(projectDir, fileName), body, "utf8");
|
|
11400
|
-
return { name, fileName, variables: extractVariables(body) };
|
|
11401
|
-
}
|
|
11402
|
-
async function deleteSnippet(projectDir, fileName) {
|
|
11403
|
-
await rm4(resolveSnippetPath(projectDir, fileName), { force: true });
|
|
11404
|
-
}
|
|
11882
|
+
import { readdir as readdir9, readFile as readFile32, writeFile as writeFile11, mkdir as mkdir13, rm as rm6, stat as stat9 } from "node:fs/promises";
|
|
11883
|
+
import path22 from "node:path";
|
|
11884
|
+
|
|
11405
11885
|
// src/lib/plugin-manager.ts
|
|
11406
11886
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
11407
|
-
import { cp as cp5, lstat as lstat3, mkdir as mkdir12, rename as rename5, rm as
|
|
11408
|
-
import
|
|
11887
|
+
import { cp as cp5, lstat as lstat3, mkdir as mkdir12, rename as rename5, rm as rm5, stat as stat8 } from "node:fs/promises";
|
|
11888
|
+
import path21 from "node:path";
|
|
11409
11889
|
import { isSeq as isSeq4, isMap as isMap2, isScalar as isScalar2, YAMLMap, Scalar as Scalar2 } from "yaml";
|
|
11410
11890
|
|
|
11411
11891
|
// src/lib/npm-plugin-installer.ts
|
|
@@ -11416,12 +11896,12 @@ import {
|
|
|
11416
11896
|
mkdir as mkdir11,
|
|
11417
11897
|
mkdtemp as mkdtemp2,
|
|
11418
11898
|
open as open2,
|
|
11419
|
-
readFile as
|
|
11899
|
+
readFile as readFile31,
|
|
11420
11900
|
realpath as realpath2,
|
|
11421
11901
|
rename as rename4,
|
|
11422
|
-
rm as
|
|
11902
|
+
rm as rm4
|
|
11423
11903
|
} from "node:fs/promises";
|
|
11424
|
-
import
|
|
11904
|
+
import path20 from "node:path";
|
|
11425
11905
|
import { Transform } from "node:stream";
|
|
11426
11906
|
import { pipeline } from "node:stream/promises";
|
|
11427
11907
|
import { createGunzip } from "node:zlib";
|
|
@@ -11466,8 +11946,8 @@ function mb(bytes) {
|
|
|
11466
11946
|
return `${Math.ceil(bytes / 1024 / 1024)}MB`;
|
|
11467
11947
|
}
|
|
11468
11948
|
function isContained2(root, candidate) {
|
|
11469
|
-
const relative2 =
|
|
11470
|
-
return relative2 === "" || !relative2.startsWith("..") && !
|
|
11949
|
+
const relative2 = path20.relative(root, candidate);
|
|
11950
|
+
return relative2 === "" || !relative2.startsWith("..") && !path20.isAbsolute(relative2);
|
|
11471
11951
|
}
|
|
11472
11952
|
function mergeLimits(overrides) {
|
|
11473
11953
|
const limits = { ...DEFAULT_LIMITS, ...overrides };
|
|
@@ -11774,13 +12254,13 @@ async function extractTarball(ctx, archivePath, packageDir, packageName) {
|
|
|
11774
12254
|
throw rejected;
|
|
11775
12255
|
}
|
|
11776
12256
|
async function readInstalledManifest(packageDir, selected) {
|
|
11777
|
-
const packageJsonPath =
|
|
12257
|
+
const packageJsonPath = path20.join(packageDir, "package.json");
|
|
11778
12258
|
let manifest = null;
|
|
11779
12259
|
try {
|
|
11780
12260
|
const info2 = await lstat2(packageJsonPath);
|
|
11781
12261
|
if (!info2.isFile() || info2.isSymbolicLink())
|
|
11782
12262
|
throw new Error("not a regular file");
|
|
11783
|
-
manifest = object2(JSON.parse(await
|
|
12263
|
+
manifest = object2(JSON.parse(await readFile31(packageJsonPath, "utf8")));
|
|
11784
12264
|
} catch (error2) {
|
|
11785
12265
|
throw new SecurityInstallError(`Downloaded ${selected.name}@${selected.version} has no valid package.json (${error2 instanceof Error ? error2.message : String(error2)}).`);
|
|
11786
12266
|
}
|
|
@@ -11850,17 +12330,17 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
11850
12330
|
throw new SecurityInstallError(`Dependency graph exceeds ${ctx.limits.totalPackages} packages.`);
|
|
11851
12331
|
}
|
|
11852
12332
|
const relativePath = relativePackagePath(parentPackagePath, name);
|
|
11853
|
-
const packageDir =
|
|
11854
|
-
const archivePath =
|
|
12333
|
+
const packageDir = path20.join(ctx.stageRoot, ...relativePath.split("/"));
|
|
12334
|
+
const archivePath = path20.join(ctx.downloadsDir, `${ctx.archiveCounter++}.tgz`);
|
|
11855
12335
|
try {
|
|
11856
|
-
await mkdir11(
|
|
12336
|
+
await mkdir11(path20.dirname(packageDir), { recursive: true });
|
|
11857
12337
|
await downloadTarball(ctx, selected, archivePath);
|
|
11858
12338
|
await extractTarball(ctx, archivePath, packageDir, selected.name);
|
|
11859
12339
|
const manifest = await readInstalledManifest(packageDir, selected);
|
|
11860
12340
|
if (!platformAllowed(manifest.os, process.platform) || !platformAllowed(manifest.cpu, process.arch)) {
|
|
11861
12341
|
const reason = `${name}@${selected.version} does not support ${process.platform}/${process.arch}`;
|
|
11862
12342
|
if (optional) {
|
|
11863
|
-
await
|
|
12343
|
+
await rm4(packageDir, { recursive: true, force: true });
|
|
11864
12344
|
return { skipped: reason };
|
|
11865
12345
|
}
|
|
11866
12346
|
throw new PackageUnavailableError(reason);
|
|
@@ -11929,14 +12409,14 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
11929
12409
|
}
|
|
11930
12410
|
return { path: relativePath };
|
|
11931
12411
|
} catch (error2) {
|
|
11932
|
-
await
|
|
12412
|
+
await rm4(packageDir, { recursive: true, force: true });
|
|
11933
12413
|
if (optional && isOptionalAvailabilityError(error2)) {
|
|
11934
12414
|
discardOptionalSubtree();
|
|
11935
12415
|
return { skipped: error2 instanceof Error ? error2.message : String(error2) };
|
|
11936
12416
|
}
|
|
11937
12417
|
throw error2;
|
|
11938
12418
|
} finally {
|
|
11939
|
-
await
|
|
12419
|
+
await rm4(archivePath, { force: true });
|
|
11940
12420
|
}
|
|
11941
12421
|
}
|
|
11942
12422
|
async function ensureDirectory(pathname) {
|
|
@@ -11952,9 +12432,9 @@ async function ensureDirectory(pathname) {
|
|
|
11952
12432
|
}
|
|
11953
12433
|
}
|
|
11954
12434
|
async function prepareVendorParent(projectDir, name) {
|
|
11955
|
-
const plugins =
|
|
11956
|
-
const npm =
|
|
11957
|
-
const packageParent =
|
|
12435
|
+
const plugins = path20.join(projectDir, PLUGINS_DIR);
|
|
12436
|
+
const npm = path20.join(plugins, VENDORED_NPM_DIR);
|
|
12437
|
+
const packageParent = path20.dirname(vendoredNpmPluginRoot(projectDir, name, "0.0.0"));
|
|
11958
12438
|
await mkdir11(projectDir, { recursive: true });
|
|
11959
12439
|
await ensureDirectory(plugins);
|
|
11960
12440
|
await ensureDirectory(npm);
|
|
@@ -11982,14 +12462,14 @@ function publicReceipt(record) {
|
|
|
11982
12462
|
};
|
|
11983
12463
|
}
|
|
11984
12464
|
async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
11985
|
-
const requestedProjectDir =
|
|
12465
|
+
const requestedProjectDir = path20.resolve(projectDirInput);
|
|
11986
12466
|
await mkdir11(requestedProjectDir, { recursive: true });
|
|
11987
12467
|
const projectDir = await realpath2(requestedProjectDir);
|
|
11988
12468
|
const requested = parseNpmPluginSpec(packageSpec);
|
|
11989
12469
|
const npmRoot = await prepareVendorParent(projectDir, requested.name);
|
|
11990
|
-
const container = await mkdtemp2(
|
|
11991
|
-
const stageRoot =
|
|
11992
|
-
const downloadsDir =
|
|
12470
|
+
const container = await mkdtemp2(path20.join(npmRoot, ".install-"));
|
|
12471
|
+
const stageRoot = path20.join(container, "root");
|
|
12472
|
+
const downloadsDir = path20.join(container, "downloads");
|
|
11993
12473
|
await Promise.all([mkdir11(stageRoot), mkdir11(downloadsDir)]);
|
|
11994
12474
|
const ctx = {
|
|
11995
12475
|
fetch: options.fetch ?? globalThis.fetch,
|
|
@@ -12035,7 +12515,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
12035
12515
|
skipped: ctx.skipped,
|
|
12036
12516
|
tree: { algorithm: "sha256", ...tree }
|
|
12037
12517
|
};
|
|
12038
|
-
const receiptFile = await open2(
|
|
12518
|
+
const receiptFile = await open2(path20.join(stageRoot, VENDOR_RECEIPT_FILE), "wx");
|
|
12039
12519
|
try {
|
|
12040
12520
|
await receiptFile.writeFile(`${JSON.stringify(receipt, null, 2)}
|
|
12041
12521
|
`, "utf8");
|
|
@@ -12044,7 +12524,7 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
12044
12524
|
await receiptFile.close();
|
|
12045
12525
|
}
|
|
12046
12526
|
finalRoot = vendoredNpmPluginRoot(projectDir, rootRecord.name, rootRecord.version);
|
|
12047
|
-
const realFinalParent = await realpath2(
|
|
12527
|
+
const realFinalParent = await realpath2(path20.dirname(finalRoot));
|
|
12048
12528
|
if (!isContained2(projectDir, realFinalParent)) {
|
|
12049
12529
|
throw new Error("Plugin install destination resolves outside the project.");
|
|
12050
12530
|
}
|
|
@@ -12076,16 +12556,16 @@ async function installNpmPlugin(projectDirInput, packageSpec, options = {}) {
|
|
|
12076
12556
|
warnings: [...ctx.warnings]
|
|
12077
12557
|
};
|
|
12078
12558
|
} finally {
|
|
12079
|
-
await
|
|
12559
|
+
await rm4(container, { recursive: true, force: true }).catch(() => {});
|
|
12080
12560
|
}
|
|
12081
12561
|
}
|
|
12082
12562
|
async function finalizeNpmPluginInstall(installed) {
|
|
12083
12563
|
if (installed.backupRoot) {
|
|
12084
|
-
await
|
|
12564
|
+
await rm4(installed.backupRoot, { recursive: true, force: true });
|
|
12085
12565
|
}
|
|
12086
12566
|
}
|
|
12087
12567
|
async function rollbackNpmPluginInstall(installed) {
|
|
12088
|
-
await
|
|
12568
|
+
await rm4(installed.installRoot, { recursive: true, force: true });
|
|
12089
12569
|
if (installed.backupRoot)
|
|
12090
12570
|
await rename4(installed.backupRoot, installed.installRoot);
|
|
12091
12571
|
}
|
|
@@ -12093,7 +12573,7 @@ async function rollbackNpmPluginInstall(installed) {
|
|
|
12093
12573
|
// src/lib/plugin-manager.ts
|
|
12094
12574
|
var pluginMutationQueues = new Map;
|
|
12095
12575
|
function withPluginMutationLock(projectDir, mutation) {
|
|
12096
|
-
const resolved =
|
|
12576
|
+
const resolved = path21.resolve(projectDir);
|
|
12097
12577
|
const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
12098
12578
|
const previous = pluginMutationQueues.get(key) ?? Promise.resolve();
|
|
12099
12579
|
const run2 = previous.then(mutation, mutation);
|
|
@@ -12213,7 +12693,7 @@ async function setPluginEnabledUnlocked(projectDir, ref, enabled) {
|
|
|
12213
12693
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
12214
12694
|
const seq = doc.get("plugins", true);
|
|
12215
12695
|
if (!isSeq4(seq)) {
|
|
12216
|
-
throw new Error(`No plugins are configured in ${
|
|
12696
|
+
throw new Error(`No plugins are configured in ${path21.basename(file)}.`);
|
|
12217
12697
|
}
|
|
12218
12698
|
const idx = indexOfRef(seq, ref);
|
|
12219
12699
|
if (idx < 0) {
|
|
@@ -12331,12 +12811,12 @@ async function addLocalPluginUnlocked(projectDir, sourcePath) {
|
|
|
12331
12811
|
} catch {
|
|
12332
12812
|
throw new Error(`Plugin source not found: ${sourcePath}`);
|
|
12333
12813
|
}
|
|
12334
|
-
const base =
|
|
12335
|
-
const destDir =
|
|
12814
|
+
const base = path21.basename(sourcePath);
|
|
12815
|
+
const destDir = path21.join(projectDir, PLUGINS_DIR);
|
|
12336
12816
|
await mkdir12(destDir, { recursive: true });
|
|
12337
|
-
const dest =
|
|
12338
|
-
const staging =
|
|
12339
|
-
const backup =
|
|
12817
|
+
const dest = path21.join(destDir, base);
|
|
12818
|
+
const staging = path21.join(destDir, `.import-${randomUUID4()}`);
|
|
12819
|
+
const backup = path21.join(destDir, `.backup-${randomUUID4()}`);
|
|
12340
12820
|
let hasBackup = false;
|
|
12341
12821
|
try {
|
|
12342
12822
|
if (info2.isDirectory()) {
|
|
@@ -12363,16 +12843,16 @@ async function addLocalPluginUnlocked(projectDir, sourcePath) {
|
|
|
12363
12843
|
try {
|
|
12364
12844
|
await appendPlugin(projectDir, relPath, new Scalar2(relPath));
|
|
12365
12845
|
} catch (error2) {
|
|
12366
|
-
await
|
|
12846
|
+
await rm5(dest, { recursive: true, force: true });
|
|
12367
12847
|
if (hasBackup)
|
|
12368
12848
|
await rename5(backup, dest);
|
|
12369
12849
|
throw error2;
|
|
12370
12850
|
}
|
|
12371
12851
|
if (hasBackup)
|
|
12372
|
-
await
|
|
12852
|
+
await rm5(backup, { recursive: true, force: true }).catch(() => {});
|
|
12373
12853
|
return { ref: relPath, kind: "local", enabled: true, path: relPath };
|
|
12374
12854
|
} finally {
|
|
12375
|
-
await
|
|
12855
|
+
await rm5(staging, { recursive: true, force: true }).catch(() => {});
|
|
12376
12856
|
}
|
|
12377
12857
|
}
|
|
12378
12858
|
async function validateProjectPlugins(projectDir) {
|
|
@@ -12410,11 +12890,158 @@ async function validateProjectPlugins(projectDir) {
|
|
|
12410
12890
|
}
|
|
12411
12891
|
return out;
|
|
12412
12892
|
}
|
|
12893
|
+
|
|
12894
|
+
// src/lib/snippets.ts
|
|
12895
|
+
var SNIPPETS_DIR = "snippets";
|
|
12896
|
+
var PLACEHOLDER_RE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
|
|
12897
|
+
function extractVariables(template) {
|
|
12898
|
+
const seen = new Set;
|
|
12899
|
+
const order = [];
|
|
12900
|
+
for (const m of template.matchAll(PLACEHOLDER_RE)) {
|
|
12901
|
+
const name = m[1];
|
|
12902
|
+
if (!seen.has(name)) {
|
|
12903
|
+
seen.add(name);
|
|
12904
|
+
order.push(name);
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
return order;
|
|
12908
|
+
}
|
|
12909
|
+
function substituteVariables(template, values) {
|
|
12910
|
+
return template.replace(PLACEHOLDER_RE, (_full, name) => Object.prototype.hasOwnProperty.call(values, name) ? values[name] : "");
|
|
12911
|
+
}
|
|
12912
|
+
function resolveSafeChildFile(dir, fileName) {
|
|
12913
|
+
const full = path22.resolve(dir, fileName);
|
|
12914
|
+
if (full !== path22.join(dir, path22.basename(fileName)) || path22.dirname(full) !== dir) {
|
|
12915
|
+
throw new Error(`Unsafe snippet filename: ${fileName}`);
|
|
12916
|
+
}
|
|
12917
|
+
return full;
|
|
12918
|
+
}
|
|
12919
|
+
function resolveSnippetPath(projectDir, fileName) {
|
|
12920
|
+
return resolveSafeChildFile(path22.resolve(projectDir, SNIPPETS_DIR), fileName);
|
|
12921
|
+
}
|
|
12922
|
+
async function scanSnippetFiles(dir) {
|
|
12923
|
+
let names;
|
|
12924
|
+
try {
|
|
12925
|
+
names = await readdir9(dir);
|
|
12926
|
+
} catch {
|
|
12927
|
+
return [];
|
|
12928
|
+
}
|
|
12929
|
+
const entries = [];
|
|
12930
|
+
for (const fileName of names) {
|
|
12931
|
+
if (!fileName.toLowerCase().endsWith(".md"))
|
|
12932
|
+
continue;
|
|
12933
|
+
let body = "";
|
|
12934
|
+
try {
|
|
12935
|
+
body = await readFile32(path22.join(dir, fileName), "utf8");
|
|
12936
|
+
} catch {
|
|
12937
|
+
continue;
|
|
12938
|
+
}
|
|
12939
|
+
entries.push({
|
|
12940
|
+
name: prettify(fileName.replace(/\.md$/i, "")),
|
|
12941
|
+
fileName,
|
|
12942
|
+
variables: extractVariables(body)
|
|
12943
|
+
});
|
|
12944
|
+
}
|
|
12945
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
12946
|
+
return entries;
|
|
12947
|
+
}
|
|
12948
|
+
async function listSnippets(projectDir) {
|
|
12949
|
+
const files = await scanSnippetFiles(path22.join(projectDir, SNIPPETS_DIR));
|
|
12950
|
+
return files.map((file) => ({ ...file, source: { kind: "project" } }));
|
|
12951
|
+
}
|
|
12952
|
+
async function readSnippet(projectDir, fileName) {
|
|
12953
|
+
return readFile32(resolveSnippetPath(projectDir, fileName), "utf8");
|
|
12954
|
+
}
|
|
12955
|
+
async function saveSnippet(projectDir, name, body) {
|
|
12956
|
+
const stem = slugify(name);
|
|
12957
|
+
if (!stem)
|
|
12958
|
+
throw new Error(`Could not derive a filename from "${name}".`);
|
|
12959
|
+
const fileName = `${stem}.md`;
|
|
12960
|
+
const dir = path22.join(projectDir, SNIPPETS_DIR);
|
|
12961
|
+
await mkdir13(dir, { recursive: true });
|
|
12962
|
+
await writeFile11(resolveSnippetPath(projectDir, fileName), body, "utf8");
|
|
12963
|
+
return { name, fileName, variables: extractVariables(body), source: { kind: "project" } };
|
|
12964
|
+
}
|
|
12965
|
+
async function deleteSnippet(projectDir, fileName) {
|
|
12966
|
+
await rm6(resolveSnippetPath(projectDir, fileName), { force: true });
|
|
12967
|
+
}
|
|
12968
|
+
async function listInstalledExtensions(projectDir) {
|
|
12969
|
+
const out = [];
|
|
12970
|
+
const activeTheme = await getActiveTheme(projectDir);
|
|
12971
|
+
const themeSnippets = activeTheme?.snippets?.trim();
|
|
12972
|
+
if (activeTheme && themeSnippets) {
|
|
12973
|
+
out.push({
|
|
12974
|
+
ref: activeTheme.id,
|
|
12975
|
+
kind: "theme",
|
|
12976
|
+
dir: path22.join(projectDir, THEMES_DIR, activeTheme.id),
|
|
12977
|
+
name: activeTheme.name,
|
|
12978
|
+
snippetsRel: themeSnippets
|
|
12979
|
+
});
|
|
12980
|
+
}
|
|
12981
|
+
for (const entry of await listProjectPlugins(projectDir)) {
|
|
12982
|
+
if (entry.kind !== "local" || !entry.enabled)
|
|
12983
|
+
continue;
|
|
12984
|
+
const dir = path22.resolve(projectDir, entry.ref);
|
|
12985
|
+
let isExtensionDir = false;
|
|
12986
|
+
try {
|
|
12987
|
+
isExtensionDir = (await stat9(dir)).isDirectory();
|
|
12988
|
+
} catch {
|
|
12989
|
+
continue;
|
|
12990
|
+
}
|
|
12991
|
+
if (!isExtensionDir)
|
|
12992
|
+
continue;
|
|
12993
|
+
const meta = await readExtensionMeta(dir);
|
|
12994
|
+
const snippetsRel = meta.snippets?.trim();
|
|
12995
|
+
if (!snippetsRel)
|
|
12996
|
+
continue;
|
|
12997
|
+
out.push({
|
|
12998
|
+
ref: entry.ref,
|
|
12999
|
+
kind: "plugin",
|
|
13000
|
+
dir,
|
|
13001
|
+
name: meta.name?.trim() || prettify(path22.basename(dir)),
|
|
13002
|
+
snippetsRel
|
|
13003
|
+
});
|
|
13004
|
+
}
|
|
13005
|
+
return out;
|
|
13006
|
+
}
|
|
13007
|
+
function extensionSnippetsDir(ext) {
|
|
13008
|
+
if (pathEscapesFolder(ext.snippetsRel))
|
|
13009
|
+
return null;
|
|
13010
|
+
return path22.join(ext.dir, ext.snippetsRel);
|
|
13011
|
+
}
|
|
13012
|
+
async function listMergedSnippets(projectDir) {
|
|
13013
|
+
const project = await listSnippets(projectDir);
|
|
13014
|
+
const projectFileNames = new Set(project.map((entry) => entry.fileName.toLowerCase()));
|
|
13015
|
+
const merged = [...project];
|
|
13016
|
+
const extensions = (await listInstalledExtensions(projectDir)).sort((a, b) => a.name.localeCompare(b.name));
|
|
13017
|
+
for (const ext of extensions) {
|
|
13018
|
+
const dir = extensionSnippetsDir(ext);
|
|
13019
|
+
if (!dir)
|
|
13020
|
+
continue;
|
|
13021
|
+
for (const file of await scanSnippetFiles(dir)) {
|
|
13022
|
+
if (projectFileNames.has(file.fileName.toLowerCase()))
|
|
13023
|
+
continue;
|
|
13024
|
+
merged.push({ ...file, source: { kind: ext.kind, ref: ext.ref, name: ext.name } });
|
|
13025
|
+
}
|
|
13026
|
+
}
|
|
13027
|
+
return merged;
|
|
13028
|
+
}
|
|
13029
|
+
async function readExtensionSnippet(projectDir, source, fileName) {
|
|
13030
|
+
const ext = (await listInstalledExtensions(projectDir)).find((candidate) => candidate.kind === source.kind && candidate.ref === source.ref);
|
|
13031
|
+
if (!ext) {
|
|
13032
|
+
throw new Error(`Extension "${source.ref}" is not installed or is no longer active.`);
|
|
13033
|
+
}
|
|
13034
|
+
const dir = extensionSnippetsDir(ext);
|
|
13035
|
+
if (!dir) {
|
|
13036
|
+
throw new Error(`Extension "${source.ref}" does not declare a usable snippets folder.`);
|
|
13037
|
+
}
|
|
13038
|
+
return readFile32(resolveSafeChildFile(dir, fileName), "utf8");
|
|
13039
|
+
}
|
|
12413
13040
|
// src/lib/theme-import.ts
|
|
12414
|
-
import { existsSync as
|
|
12415
|
-
import { mkdir as
|
|
13041
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
13042
|
+
import { mkdir as mkdir14, mkdtemp as mkdtemp3, readFile as readFile33, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
|
|
12416
13043
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
12417
|
-
import
|
|
13044
|
+
import path23 from "node:path";
|
|
12418
13045
|
import { unzipSync } from "fflate";
|
|
12419
13046
|
var MAX_THEME_ARCHIVE_BYTES = 25 * 1024 * 1024;
|
|
12420
13047
|
var MAX_THEME_UNZIPPED_BYTES = 25 * 1024 * 1024;
|
|
@@ -12447,7 +13074,7 @@ function classifyThemeCssFindings(findings) {
|
|
|
12447
13074
|
const warnings = reject ? [] : findings.filter((f) => f.rule !== ruleSyntax);
|
|
12448
13075
|
return { reject, warnings };
|
|
12449
13076
|
}
|
|
12450
|
-
var KNOWN_THEME_FILES = new Set(["theme.css", "theme.json"]);
|
|
13077
|
+
var KNOWN_THEME_FILES = new Set(["theme.css", "theme.json", EXTENSION_MANIFEST_FILENAME]);
|
|
12451
13078
|
var ALLOWED_ASSET_EXTS = new Set([
|
|
12452
13079
|
".woff",
|
|
12453
13080
|
".woff2",
|
|
@@ -12463,8 +13090,12 @@ var ALLOWED_ASSET_EXTS = new Set([
|
|
|
12463
13090
|
".avif",
|
|
12464
13091
|
".css"
|
|
12465
13092
|
]);
|
|
12466
|
-
function unexpectedThemeFiles(relPaths) {
|
|
13093
|
+
function unexpectedThemeFiles(relPaths, declaredExtras = []) {
|
|
13094
|
+
const extras = declaredExtras.map((p) => p.replace(/\\/g, "/"));
|
|
13095
|
+
const isDeclared = (p) => extras.some((extra) => p === extra || p.startsWith(`${extra}/`));
|
|
12467
13096
|
return relPaths.map((p) => p.replace(/\\/g, "/")).filter((p) => p.length > 0 && !p.endsWith("/")).filter((p) => {
|
|
13097
|
+
if (isDeclared(p))
|
|
13098
|
+
return false;
|
|
12468
13099
|
const base = p.split("/").pop().toLowerCase();
|
|
12469
13100
|
if (KNOWN_THEME_FILES.has(base))
|
|
12470
13101
|
return false;
|
|
@@ -12476,20 +13107,12 @@ function unexpectedThemeFiles(relPaths) {
|
|
|
12476
13107
|
function mb2(n) {
|
|
12477
13108
|
return `${Math.round(n / (1024 * 1024))}MB`;
|
|
12478
13109
|
}
|
|
12479
|
-
async function readThemeJson(p) {
|
|
12480
|
-
try {
|
|
12481
|
-
const parsed = JSON.parse(await readFile31(p, "utf8"));
|
|
12482
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
12483
|
-
} catch {
|
|
12484
|
-
return {};
|
|
12485
|
-
}
|
|
12486
|
-
}
|
|
12487
13110
|
async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
12488
|
-
const cssPath =
|
|
12489
|
-
if (!
|
|
13111
|
+
const cssPath = path23.join(sourceDir, "theme.css");
|
|
13112
|
+
if (!existsSync16(cssPath)) {
|
|
12490
13113
|
throw new Error("A theme must contain a theme.css file.");
|
|
12491
13114
|
}
|
|
12492
|
-
const css = await
|
|
13115
|
+
const css = await readFile33(cssPath, "utf8");
|
|
12493
13116
|
const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
|
|
12494
13117
|
if (reject) {
|
|
12495
13118
|
throw new Error(`theme.css could not be parsed — ${reject.message}`);
|
|
@@ -12498,29 +13121,29 @@ async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
|
12498
13121
|
for (const w of printFindings) {
|
|
12499
13122
|
warnings.push({ code: "print-safety", message: w.message });
|
|
12500
13123
|
}
|
|
12501
|
-
const
|
|
12502
|
-
const
|
|
12503
|
-
|
|
12504
|
-
if (!jsonExists) {
|
|
13124
|
+
const metaExists = existsSync16(path23.join(sourceDir, EXTENSION_MANIFEST_FILENAME)) || existsSync16(path23.join(sourceDir, "theme.json"));
|
|
13125
|
+
const meta = metaExists ? await readExtensionMeta(sourceDir) : {};
|
|
13126
|
+
if (!metaExists) {
|
|
12505
13127
|
warnings.push({
|
|
12506
13128
|
code: "no-theme-json",
|
|
12507
|
-
message: "No theme.json found — the theme's name was taken from the file/folder name."
|
|
13129
|
+
message: "No gutterpress.json or theme.json found — the theme's name was taken from the file/folder name."
|
|
12508
13130
|
});
|
|
12509
13131
|
} else if (!meta.name || !meta.name.trim()) {
|
|
12510
13132
|
warnings.push({
|
|
12511
13133
|
code: "unnamed-theme",
|
|
12512
|
-
message: `
|
|
13134
|
+
message: `The metadata file has no "name" — the theme's name was taken from the file/folder name.`
|
|
12513
13135
|
});
|
|
12514
13136
|
}
|
|
12515
13137
|
assertThemeSheetsContained(meta);
|
|
13138
|
+
const metaFilename = existsSync16(path23.join(sourceDir, EXTENSION_MANIFEST_FILENAME)) ? EXTENSION_MANIFEST_FILENAME : "theme.json";
|
|
12516
13139
|
const extraSheets = [
|
|
12517
13140
|
...new Set([...themeStyleList(meta), ...themeEngineStyleList(meta)])
|
|
12518
13141
|
].filter((rel) => rel !== "theme.css");
|
|
12519
|
-
const extraSheetPaths = resolveDeclaredStyles(extraSheets, sourceDir,
|
|
13142
|
+
const extraSheetPaths = resolveDeclaredStyles(extraSheets, sourceDir, metaFilename) ?? [];
|
|
12520
13143
|
for (let i = 0;i < extraSheets.length; i++) {
|
|
12521
13144
|
const rel = extraSheets[i];
|
|
12522
13145
|
const sheetPath = extraSheetPaths[i];
|
|
12523
|
-
const sheetCss = await
|
|
13146
|
+
const sheetCss = await readFile33(sheetPath, "utf8");
|
|
12524
13147
|
const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
|
|
12525
13148
|
if (sheetReject) {
|
|
12526
13149
|
throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
|
|
@@ -12529,7 +13152,8 @@ async function finalizeThemeImport(projectDir, sourceDir, relPaths) {
|
|
|
12529
13152
|
warnings.push({ code: "print-safety", message: `${rel}: ${w.message}` });
|
|
12530
13153
|
}
|
|
12531
13154
|
}
|
|
12532
|
-
const
|
|
13155
|
+
const declaredExtras = [meta.markdown, meta.components, meta.snippets].filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
13156
|
+
const extra = unexpectedThemeFiles(relPaths, declaredExtras);
|
|
12533
13157
|
if (extra.length > 0) {
|
|
12534
13158
|
const shown = extra.slice(0, 6).join(", ");
|
|
12535
13159
|
warnings.push({
|
|
@@ -12581,7 +13205,7 @@ async function importThemeFromZip(projectDir, archive) {
|
|
|
12581
13205
|
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.");
|
|
12582
13206
|
}
|
|
12583
13207
|
const prefix = root === "" ? "" : `${root}/`;
|
|
12584
|
-
const tmp = await mkdtemp3(
|
|
13208
|
+
const tmp = await mkdtemp3(path23.join(tmpdir3(), "gutterpress-theme-"));
|
|
12585
13209
|
try {
|
|
12586
13210
|
const relPaths = [];
|
|
12587
13211
|
for (const [name, data] of Object.entries(files)) {
|
|
@@ -12593,13 +13217,13 @@ async function importThemeFromZip(projectDir, archive) {
|
|
|
12593
13217
|
const rel = prefix ? norm.slice(prefix.length) : norm;
|
|
12594
13218
|
if (!rel || rel.split("/").some((seg) => seg === ".."))
|
|
12595
13219
|
continue;
|
|
12596
|
-
const dest =
|
|
12597
|
-
if (dest !== tmp && !dest.startsWith(tmp +
|
|
13220
|
+
const dest = path23.join(tmp, rel);
|
|
13221
|
+
if (dest !== tmp && !dest.startsWith(tmp + path23.sep)) {
|
|
12598
13222
|
throw new Error(`The package contains an unsafe path and was rejected: ${name}`);
|
|
12599
13223
|
}
|
|
12600
13224
|
relPaths.push(rel);
|
|
12601
|
-
await
|
|
12602
|
-
await
|
|
13225
|
+
await mkdir14(path23.dirname(dest), { recursive: true });
|
|
13226
|
+
await writeFile12(dest, data);
|
|
12603
13227
|
}
|
|
12604
13228
|
return await finalizeThemeImport(projectDir, tmp, relPaths);
|
|
12605
13229
|
} finally {
|
|
@@ -12612,10 +13236,10 @@ async function importThemeFromCssText(projectDir, css, name) {
|
|
|
12612
13236
|
throw new Error(`The CSS could not be parsed — ${reject.message}`);
|
|
12613
13237
|
}
|
|
12614
13238
|
const displayName = name.trim() || "Imported theme";
|
|
12615
|
-
const tmp = await mkdtemp3(
|
|
13239
|
+
const tmp = await mkdtemp3(path23.join(tmpdir3(), "gutterpress-theme-"));
|
|
12616
13240
|
try {
|
|
12617
|
-
await
|
|
12618
|
-
await
|
|
13241
|
+
await writeFile12(path23.join(tmp, "theme.css"), css, "utf8");
|
|
13242
|
+
await writeFile12(path23.join(tmp, "theme.json"), JSON.stringify({ name: displayName }, null, 2), "utf8");
|
|
12619
13243
|
const result = await finalizeThemeImport(projectDir, tmp, ["theme.css", "theme.json"]);
|
|
12620
13244
|
result.warnings.unshift({
|
|
12621
13245
|
code: "no-theme-json",
|
|
@@ -12627,14 +13251,14 @@ async function importThemeFromCssText(projectDir, css, name) {
|
|
|
12627
13251
|
}
|
|
12628
13252
|
}
|
|
12629
13253
|
async function importThemeFromFile(projectDir, filePath) {
|
|
12630
|
-
const ext =
|
|
13254
|
+
const ext = path23.extname(filePath).toLowerCase();
|
|
12631
13255
|
if (ext === ".zip") {
|
|
12632
|
-
const buf = await
|
|
13256
|
+
const buf = await readFile33(filePath);
|
|
12633
13257
|
return importThemeFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
12634
13258
|
}
|
|
12635
13259
|
if (ext === ".css") {
|
|
12636
|
-
const css = await
|
|
12637
|
-
return importThemeFromCssText(projectDir, css, prettify(
|
|
13260
|
+
const css = await readFile33(filePath, "utf8");
|
|
13261
|
+
return importThemeFromCssText(projectDir, css, prettify(path23.basename(filePath, ".css")));
|
|
12638
13262
|
}
|
|
12639
13263
|
throw new Error("Choose a .zip theme package or a .css stylesheet.");
|
|
12640
13264
|
}
|
|
@@ -13337,8 +13961,8 @@ async function listGitHubBranches(credential, owner, repo, options = {}) {
|
|
|
13337
13961
|
return branches;
|
|
13338
13962
|
}
|
|
13339
13963
|
// src/lib/remote-auth/clone.ts
|
|
13340
|
-
import { mkdir as
|
|
13341
|
-
import
|
|
13964
|
+
import { mkdir as mkdir15, readdir as readdir10, rm as rm8 } from "node:fs/promises";
|
|
13965
|
+
import path24 from "node:path";
|
|
13342
13966
|
import git3 from "isomorphic-git";
|
|
13343
13967
|
|
|
13344
13968
|
// src/lib/remote-auth/git-http.ts
|
|
@@ -13669,7 +14293,7 @@ function friendlyCloneError(e) {
|
|
|
13669
14293
|
}
|
|
13670
14294
|
async function cloneRepository(options) {
|
|
13671
14295
|
const { dir, onProgress, tokenStore } = options;
|
|
13672
|
-
if (!
|
|
14296
|
+
if (!path24.isAbsolute(dir)) {
|
|
13673
14297
|
throw new Error("cloneRepository requires an absolute destination path.");
|
|
13674
14298
|
}
|
|
13675
14299
|
let parsed;
|
|
@@ -13689,7 +14313,7 @@ async function cloneRepository(options) {
|
|
|
13689
14313
|
return withRepoLock(dir, async () => {
|
|
13690
14314
|
await assertCloneTarget(dir);
|
|
13691
14315
|
const dirExistedBefore = gitFs.existsSync(dir);
|
|
13692
|
-
await
|
|
14316
|
+
await mkdir15(dir, { recursive: true });
|
|
13693
14317
|
try {
|
|
13694
14318
|
await git3.clone({
|
|
13695
14319
|
fs: gitFs,
|
|
@@ -13981,8 +14605,8 @@ async function diagnoseProjectRemote(projectDir, options = {}) {
|
|
|
13981
14605
|
import git6 from "isomorphic-git";
|
|
13982
14606
|
|
|
13983
14607
|
// src/lib/remote-auth/converge-merge.ts
|
|
13984
|
-
import { mkdir as
|
|
13985
|
-
import
|
|
14608
|
+
import { mkdir as mkdir16, unlink as unlink3, writeFile as writeFile13 } from "node:fs/promises";
|
|
14609
|
+
import path25 from "node:path";
|
|
13986
14610
|
import git5 from "isomorphic-git";
|
|
13987
14611
|
import diff3Merge from "diff3";
|
|
13988
14612
|
function isMergeConflictError(e) {
|
|
@@ -14029,9 +14653,9 @@ async function readBlobOrNull(dir, cache2, oid, filepath) {
|
|
|
14029
14653
|
}
|
|
14030
14654
|
}
|
|
14031
14655
|
async function writeTreeFile(dir, filepath, content) {
|
|
14032
|
-
const abs =
|
|
14033
|
-
await
|
|
14034
|
-
await
|
|
14656
|
+
const abs = path25.join(dir, filepath);
|
|
14657
|
+
await mkdir16(path25.dirname(abs), { recursive: true });
|
|
14658
|
+
await writeFile13(abs, content);
|
|
14035
14659
|
}
|
|
14036
14660
|
async function bothAddedPaths(dir, cache2, ourTip, theirTip, allowUnrelated) {
|
|
14037
14661
|
const bases = await git5.findMergeBase({ fs: gitFs, dir, cache: cache2, oids: [ourTip, theirTip] });
|
|
@@ -14127,7 +14751,7 @@ async function convergeMerge(params) {
|
|
|
14127
14751
|
}
|
|
14128
14752
|
for (const p of paths.deleteByTheirs) {
|
|
14129
14753
|
const ourBlob = await readBlobOrNull(dir, cache2, ourTip, p);
|
|
14130
|
-
await unlink3(
|
|
14754
|
+
await unlink3(path25.join(dir, p)).catch((e) => {
|
|
14131
14755
|
if (e?.code !== "ENOENT")
|
|
14132
14756
|
throw e;
|
|
14133
14757
|
});
|
|
@@ -14368,8 +14992,8 @@ async function syncProject(options) {
|
|
|
14368
14992
|
});
|
|
14369
14993
|
}
|
|
14370
14994
|
// src/lib/publish/run-publish.ts
|
|
14371
|
-
import { readdir as readdir12, stat as
|
|
14372
|
-
import
|
|
14995
|
+
import { readdir as readdir12, stat as stat12 } from "node:fs/promises";
|
|
14996
|
+
import path29 from "node:path";
|
|
14373
14997
|
|
|
14374
14998
|
// src/lib/publish/command-runner.ts
|
|
14375
14999
|
var CAPTURE_LIMIT = 64 * 1024;
|
|
@@ -14518,25 +15142,25 @@ ${tail}` : ""}`);
|
|
|
14518
15142
|
};
|
|
14519
15143
|
|
|
14520
15144
|
// src/lib/publish/providers/guided.ts
|
|
14521
|
-
import { copyFile as copyFile2, mkdir as
|
|
14522
|
-
import
|
|
15145
|
+
import { copyFile as copyFile2, mkdir as mkdir17, writeFile as writeFile14 } from "node:fs/promises";
|
|
15146
|
+
import path26 from "node:path";
|
|
14523
15147
|
async function stageGuidedPackage(req, providerId, listingLines) {
|
|
14524
|
-
const packageDir =
|
|
14525
|
-
await
|
|
14526
|
-
await copyFile2(req.artifact.path,
|
|
15148
|
+
const packageDir = path26.join(path26.dirname(req.artifact.path), "publish", providerId);
|
|
15149
|
+
await mkdir17(packageDir, { recursive: true });
|
|
15150
|
+
await copyFile2(req.artifact.path, path26.join(packageDir, path26.basename(req.artifact.path)));
|
|
14527
15151
|
const { title, authors } = req.project;
|
|
14528
15152
|
const sheet = [
|
|
14529
15153
|
`# ${title || "Untitled"}`,
|
|
14530
15154
|
"",
|
|
14531
15155
|
`- **Authors:** ${authors.length ? authors.join(", ") : "(not set)"}`,
|
|
14532
|
-
`- **File:** ${
|
|
15156
|
+
`- **File:** ${path26.basename(req.artifact.path)}`,
|
|
14533
15157
|
...listingLines,
|
|
14534
15158
|
"",
|
|
14535
15159
|
"Copy these details into the platform's listing form when uploading.",
|
|
14536
15160
|
""
|
|
14537
15161
|
].join(`
|
|
14538
15162
|
`);
|
|
14539
|
-
await
|
|
15163
|
+
await writeFile14(path26.join(packageDir, "LISTING.md"), sheet, "utf8");
|
|
14540
15164
|
return packageDir;
|
|
14541
15165
|
}
|
|
14542
15166
|
|
|
@@ -14599,9 +15223,9 @@ var drivethrurpgProvider = {
|
|
|
14599
15223
|
};
|
|
14600
15224
|
|
|
14601
15225
|
// src/lib/publish/providers/gdrive.ts
|
|
14602
|
-
import { mkdtemp as mkdtemp4, readFile as
|
|
15226
|
+
import { mkdtemp as mkdtemp4, readFile as readFile34, readdir as readdir11, rm as rm9, stat as stat10, writeFile as writeFile15 } from "node:fs/promises";
|
|
14603
15227
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
14604
|
-
import
|
|
15228
|
+
import path27 from "node:path";
|
|
14605
15229
|
import { Zip, ZipDeflate } from "fflate";
|
|
14606
15230
|
|
|
14607
15231
|
// src/lib/publish/google-drive.ts
|
|
@@ -14986,34 +15610,34 @@ function zipEntriesNonBlocking(entries, opts) {
|
|
|
14986
15610
|
async function collectZipEntries(root, dir, out, onWarn) {
|
|
14987
15611
|
const entries = await readdir11(dir, { withFileTypes: true });
|
|
14988
15612
|
for (const entry of entries) {
|
|
14989
|
-
const full =
|
|
15613
|
+
const full = path27.join(dir, entry.name);
|
|
14990
15614
|
if (entry.isDirectory()) {
|
|
14991
15615
|
await collectZipEntries(root, full, out, onWarn);
|
|
14992
15616
|
} else if (entry.isFile()) {
|
|
14993
|
-
const rel =
|
|
14994
|
-
out[rel] = await
|
|
15617
|
+
const rel = path27.relative(root, full).split(path27.sep).join("/");
|
|
15618
|
+
out[rel] = await readFile34(full);
|
|
14995
15619
|
} else if (entry.isSymbolicLink()) {
|
|
14996
|
-
const resolved = await
|
|
15620
|
+
const resolved = await stat10(full).catch(() => null);
|
|
14997
15621
|
if (resolved?.isFile()) {
|
|
14998
|
-
const rel =
|
|
14999
|
-
out[rel] = await
|
|
15622
|
+
const rel = path27.relative(root, full).split(path27.sep).join("/");
|
|
15623
|
+
out[rel] = await readFile34(full);
|
|
15000
15624
|
} else if (resolved?.isDirectory()) {
|
|
15001
15625
|
await collectZipEntries(root, full, out, onWarn);
|
|
15002
15626
|
} else {
|
|
15003
|
-
onWarn?.(`Skipped "${
|
|
15627
|
+
onWarn?.(`Skipped "${path27.relative(root, full)}" in the website export — it's a broken symlink.`);
|
|
15004
15628
|
}
|
|
15005
15629
|
} else {
|
|
15006
|
-
onWarn?.(`Skipped "${
|
|
15630
|
+
onWarn?.(`Skipped "${path27.relative(root, full)}" in the website export — not a file, folder, or symlink.`);
|
|
15007
15631
|
}
|
|
15008
15632
|
}
|
|
15009
15633
|
}
|
|
15010
|
-
async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl =
|
|
15634
|
+
async function zipHtmlExport(exportDir, title, onWarn, writeArchiveImpl = writeFile15) {
|
|
15011
15635
|
const entries = {};
|
|
15012
15636
|
await collectZipEntries(exportDir, exportDir, entries, onWarn);
|
|
15013
15637
|
const fileName = `${bookSlug(title)}-website.zip`;
|
|
15014
|
-
const tmpRoot = await mkdtemp4(
|
|
15638
|
+
const tmpRoot = await mkdtemp4(path27.join(tmpdir4(), "gutterpress-gdrive-"));
|
|
15015
15639
|
try {
|
|
15016
|
-
const filePath =
|
|
15640
|
+
const filePath = path27.join(tmpRoot, fileName);
|
|
15017
15641
|
const zipped = await zipEntriesNonBlocking(entries, { level: 6 });
|
|
15018
15642
|
await writeArchiveImpl(filePath, zipped);
|
|
15019
15643
|
return {
|
|
@@ -15061,12 +15685,12 @@ var gdriveProvider = {
|
|
|
15061
15685
|
const isHtml = req.artifact.format === "html";
|
|
15062
15686
|
const source = isHtml ? await zipHtmlExport(req.artifact.path, req.project.title, (msg) => req.deps.onProgress?.(msg)) : {
|
|
15063
15687
|
filePath: req.artifact.path,
|
|
15064
|
-
fileName:
|
|
15688
|
+
fileName: path27.basename(req.artifact.path),
|
|
15065
15689
|
mimeType: "application/pdf",
|
|
15066
15690
|
cleanup: async () => {}
|
|
15067
15691
|
};
|
|
15068
15692
|
try {
|
|
15069
|
-
const artifactStat = await
|
|
15693
|
+
const artifactStat = await stat10(source.filePath);
|
|
15070
15694
|
const about = await driveAbout(fetchImpl, accessToken);
|
|
15071
15695
|
if (about.quota.limitBytes != null && about.quota.freeBytes != null) {
|
|
15072
15696
|
if (artifactStat.size > about.quota.freeBytes) {
|
|
@@ -15111,8 +15735,8 @@ var gdriveProvider = {
|
|
|
15111
15735
|
};
|
|
15112
15736
|
|
|
15113
15737
|
// src/lib/publish/butler.ts
|
|
15114
|
-
import { chmod as chmod2, mkdir as
|
|
15115
|
-
import
|
|
15738
|
+
import { chmod as chmod2, mkdir as mkdir18, stat as stat11, writeFile as writeFile16 } from "node:fs/promises";
|
|
15739
|
+
import path28 from "node:path";
|
|
15116
15740
|
import { unzipSync as unzipSync2 } from "fflate";
|
|
15117
15741
|
var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
|
|
15118
15742
|
function butlerBrothChannel(platform2 = process.platform, arch2 = process.arch) {
|
|
@@ -15132,7 +15756,7 @@ function butlerBinaryName(platform2 = process.platform) {
|
|
|
15132
15756
|
}
|
|
15133
15757
|
async function fileExists2(p) {
|
|
15134
15758
|
try {
|
|
15135
|
-
return (await
|
|
15759
|
+
return (await stat11(p)).isFile();
|
|
15136
15760
|
} catch {
|
|
15137
15761
|
return false;
|
|
15138
15762
|
}
|
|
@@ -15149,8 +15773,8 @@ async function ensureButler(deps) {
|
|
|
15149
15773
|
}
|
|
15150
15774
|
if (await commandExists("butler", runCommand, deps.env))
|
|
15151
15775
|
return "butler";
|
|
15152
|
-
const cacheDir =
|
|
15153
|
-
const cached =
|
|
15776
|
+
const cacheDir = path28.join(deps.configDir ?? defaultConfigDir(), "tools", "butler");
|
|
15777
|
+
const cached = path28.join(cacheDir, butlerBinaryName());
|
|
15154
15778
|
if (await fileExists2(cached))
|
|
15155
15779
|
return cached;
|
|
15156
15780
|
const channel = butlerBrothChannel();
|
|
@@ -15172,14 +15796,14 @@ async function ensureButler(deps) {
|
|
|
15172
15796
|
return new Uint8Array(await response.arrayBuffer());
|
|
15173
15797
|
});
|
|
15174
15798
|
const files = unzipSync2(archive);
|
|
15175
|
-
await
|
|
15799
|
+
await mkdir18(cacheDir, { recursive: true });
|
|
15176
15800
|
let foundBinary = false;
|
|
15177
15801
|
for (const [name, data] of Object.entries(files)) {
|
|
15178
15802
|
if (name.endsWith("/"))
|
|
15179
15803
|
continue;
|
|
15180
|
-
const target =
|
|
15181
|
-
await
|
|
15182
|
-
if (
|
|
15804
|
+
const target = path28.join(cacheDir, path28.basename(name));
|
|
15805
|
+
await writeFile16(target, data);
|
|
15806
|
+
if (path28.basename(name) === butlerBinaryName()) {
|
|
15183
15807
|
await chmod2(target, 493).catch(() => {});
|
|
15184
15808
|
foundBinary = true;
|
|
15185
15809
|
}
|
|
@@ -15628,9 +16252,9 @@ async function resolvePublishRequest(options, deps) {
|
|
|
15628
16252
|
const effectiveDeps = effectiveAccount ? { ...deps, credentialAccount: effectiveAccount } : deps;
|
|
15629
16253
|
const outDir = resolveOutputDir(manifestDir, config.title);
|
|
15630
16254
|
const effectiveFormat = resolvePublishFormat(provider.info, providerConfig);
|
|
15631
|
-
const defaultArtifact = effectiveFormat === "pdf" ?
|
|
16255
|
+
const defaultArtifact = effectiveFormat === "pdf" ? path29.join(outDir, artifactName(config.title, "pdf")) : outDir;
|
|
15632
16256
|
const artifact = {
|
|
15633
|
-
path: options.artifactPath ?
|
|
16257
|
+
path: options.artifactPath ? path29.resolve(options.projectDir, options.artifactPath) : defaultArtifact,
|
|
15634
16258
|
format: effectiveFormat
|
|
15635
16259
|
};
|
|
15636
16260
|
return {
|
|
@@ -15648,7 +16272,7 @@ var PDF_HINT = "Build the PDF first (gutterpress build, or export it from the ap
|
|
|
15648
16272
|
var HTML_HINT = "Build the website export first (gutterpress build --format html).";
|
|
15649
16273
|
async function artifactIssues(artifact) {
|
|
15650
16274
|
try {
|
|
15651
|
-
const s = await
|
|
16275
|
+
const s = await stat12(artifact.path);
|
|
15652
16276
|
if (artifact.format === "pdf" && !s.isFile()) {
|
|
15653
16277
|
return [
|
|
15654
16278
|
{
|
|
@@ -15694,7 +16318,7 @@ async function artifactIssues(artifact) {
|
|
|
15694
16318
|
var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
|
|
15695
16319
|
async function htmlDirIssues(dir) {
|
|
15696
16320
|
const issues = [];
|
|
15697
|
-
const isThere = async (rel) =>
|
|
16321
|
+
const isThere = async (rel) => stat12(path29.join(dir, rel)).then(() => true, () => false);
|
|
15698
16322
|
if (!await isThere(BOOK_HTML)) {
|
|
15699
16323
|
issues.push({
|
|
15700
16324
|
severity: "error",
|
|
@@ -15813,17 +16437,17 @@ async function disconnectPublishCredential(key, deps, options = {}) {
|
|
|
15813
16437
|
}
|
|
15814
16438
|
}
|
|
15815
16439
|
// src/lib/publish/selections.ts
|
|
15816
|
-
import { chmod as chmod3, mkdir as
|
|
15817
|
-
import
|
|
16440
|
+
import { chmod as chmod3, mkdir as mkdir19, readFile as readFile35, writeFile as writeFile17 } from "node:fs/promises";
|
|
16441
|
+
import path30 from "node:path";
|
|
15818
16442
|
class PublishSelectionsStore {
|
|
15819
16443
|
filePath;
|
|
15820
16444
|
queue = Promise.resolve();
|
|
15821
16445
|
constructor(filePath) {
|
|
15822
|
-
this.filePath = filePath ??
|
|
16446
|
+
this.filePath = filePath ?? path30.join(defaultConfigDir(), "publish-selections.json");
|
|
15823
16447
|
}
|
|
15824
16448
|
async read() {
|
|
15825
16449
|
try {
|
|
15826
|
-
const raw = await
|
|
16450
|
+
const raw = await readFile35(this.filePath, "utf8");
|
|
15827
16451
|
const parsed = JSON.parse(raw);
|
|
15828
16452
|
if (parsed && typeof parsed === "object") {
|
|
15829
16453
|
return {
|
|
@@ -15836,8 +16460,8 @@ class PublishSelectionsStore {
|
|
|
15836
16460
|
return { version: 1, global: {}, projects: {} };
|
|
15837
16461
|
}
|
|
15838
16462
|
async write(data) {
|
|
15839
|
-
await
|
|
15840
|
-
await
|
|
16463
|
+
await mkdir19(path30.dirname(this.filePath), { recursive: true });
|
|
16464
|
+
await writeFile17(this.filePath, JSON.stringify(data, null, 2), {
|
|
15841
16465
|
encoding: "utf8",
|
|
15842
16466
|
mode: 384
|
|
15843
16467
|
});
|
|
@@ -15939,4 +16563,4 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
|
|
|
15939
16563
|
await writeManifestDoc(file, doc);
|
|
15940
16564
|
return settingsFromDoc(doc);
|
|
15941
16565
|
}
|
|
15942
|
-
export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, SYNC_SNAPSHOT_MESSAGE, log, resolveActiveStyles, listProjectStyles, PLUGINS_DIR, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, runLint, formatReport, getChecks, getCheckById, resolveCheckSelectors, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, THEMES_DIR, BUILT_IN_THEME_IDS, listBuiltInThemes, resolveBuiltInTheme, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, readThemeCss, removeProjectTheme, detectLegacyForkedTheme, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder,
|
|
16566
|
+
export { PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, SYNC_SNAPSHOT_MESSAGE, log, resolveActiveStyles, listProjectStyles, PLUGINS_DIR, EXTENSION_MANIFEST_FILENAME, LEGACY_THEME_MANIFEST_FILENAME, readExtensionMeta, extensionStyleList, extensionEngineStyleList, pathEscapesFolder, assertExtensionContained, resolveExtension, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, runLint, formatReport, getChecks, getCheckById, resolveCheckSelectors, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, THEMES_DIR, BUILT_IN_THEME_IDS, listBuiltInThemes, resolveBuiltInTheme, listProjectThemes, getActiveTheme, getPreviousTheme, revertTheme, applyTheme, importThemeFromFolder, importThemeFromUrl, readThemeCss, removeProjectTheme, detectLegacyForkedTheme, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, RECOMMENDED_PLUGINS, listProjectPlugins, setPluginEnabled, addNpmPlugin, addLocalPlugin, validateProjectPlugins, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, readSnippet, saveSnippet, deleteSnippet, listMergedSnippets, readExtensionSnippet, importThemeFromFile, setManifestFields, readManifestFields, setActiveStyles, AUTO_SNAPSHOT_MIN_MINUTES, AUTO_SNAPSHOT_MAX_MINUTES, AUTO_SNAPSHOT_DEFAULT_MINUTES, AUTO_SYNC_MIN_MINUTES, AUTO_SYNC_MAX_MINUTES, AUTO_SYNC_DEFAULT_MINUTES, AUTO_SYNC_PUSH_INTERVAL_MINUTES, autoSnapshotDelayMs, autoSyncDelayMs, isGitInternalPath, resolveGitHubClientId, GITHUB_HOST, GitHubAuthProvider, GDRIVE_HOST, resolveGoogleClientId, resolveGoogleClientSecret, GOOGLE_NOT_CONFIGURED_MESSAGE, requireGoogleClientCredentials, pkceChallengeFromVerifier, GoogleAuthProvider, revokeGoogleCredential, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listGitHubRepositories, listRepoBooks, listGitHubBranches, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, knownForgeTokenUrl, connectGenericHost, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, syncProject, listPublishProviders, publishProviderFor, resolvePublishFormat, resolvePublishRequest, runPublish, connectPublishProvider, disconnectPublishCredential, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig };
|