gutterpress 0.11.22 → 0.11.23-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{README-3nj919rm.md → README-8ef336pp.md} +1 -1
- package/dist/api/index.js +4 -4
- package/dist/{build-53bw14h7.js → build-09cd1n7s.js} +8 -8
- package/dist/{chapter-01-rkpyvjna.md → chapter-01-gsbjt584.md} +1 -1
- package/dist/chapter-01-s71xrf5q.md +69 -0
- package/dist/{chapter-01-7jwqg1ct.md → chapter-01-wgjfz4w6.md} +1 -1
- package/dist/checks/source/character-table.d.ts +12 -6
- package/dist/{cli-ke86zsz6.js → cli-5hkmp2wy.js} +70 -40
- package/dist/{cli-kx4hbnx2.js → cli-b0vyjvmt.js} +1 -1
- package/dist/{cli-1hxxwbaq.js → cli-ccp7sxz3.js} +1 -1
- package/dist/{cli-zj5xaepv.js → cli-dd115fa8.js} +22 -6
- package/dist/{cli-8kg0wjdt.js → cli-dtckwda1.js} +1 -1
- package/dist/{cli-1ngqzg8s.js → cli-fe43evmf.js} +21 -5
- package/dist/{cli-ntwqmdnw.js → cli-h17e3mtz.js} +462 -4
- package/dist/{cli-cjftezxr.js → cli-nr6j6300.js} +2 -1
- package/dist/cli.js +12 -12
- package/dist/{doctor-xka1frnx.js → doctor-nddspx4n.js} +3 -3
- package/dist/{engine-p93f06gj.js → engine-892swzzc.js} +2 -2
- package/dist/{engine-0ndvy8vj.js → engine-yjzknt0g.js} +2 -2
- package/dist/{ext-dwfqyy1j.js → ext-zjpen4yx.js} +9 -9
- package/dist/{extension-restore-7vp3vh77.js → extension-restore-jvs60x9b.js} +3 -3
- package/dist/{extension-restore-ct1wk344.js → extension-restore-w8xt4kbq.js} +5 -5
- package/dist/{index-53nrm5j1.js → index-0w2azsk1.js} +1 -1
- package/dist/{index-4v7e621e.js → index-2jpk25en.js} +464 -5
- package/dist/{index-vay3ff1f.js → index-dsvk7q6s.js} +21 -5
- package/dist/{index-6cmn2wpd.js → index-m4hhzg3c.js} +21 -5
- package/dist/{index-kh7fayjv.js → index-zrhyb967.js} +328 -311
- package/dist/index.js +5 -5
- package/dist/lib/build-runner.d.ts +1 -1
- package/dist/lib/extension-manager.d.ts +1 -1
- package/dist/lib/extension-specifier.d.ts +1 -1
- package/dist/lib/markdown/assemble.d.ts +7 -1
- package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
- package/dist/lib/markdown/index.d.ts +4 -0
- package/dist/lib/markdown/math.d.ts +43 -0
- package/dist/lib/markdown/renderer.d.ts +11 -8
- package/dist/lib/markdown/table.d.ts +46 -0
- package/dist/lib/markdown/typography.d.ts +12 -0
- package/dist/lib/presets.d.ts +1 -1
- package/dist/lib/project-scaffold.d.ts +1 -1
- package/dist/lib/project-templates.d.ts +9 -1
- package/dist/manifest-8f10dr4f.yaml +17 -0
- package/dist/{manifest.schema-399bx3vc.json → manifest.schema-9aeaered.json} +8 -1
- package/dist/{new-1pawj2qf.js → new-xhq8wczn.js} +8 -8
- package/dist/package-zttkgydq.json +10 -0
- package/dist/{plugins-fr4fnzhb.js → plugins-9en8m9y0.js} +3 -3
- package/dist/{plugins-g4dgw7mn.js → plugins-gf0gkjmv.js} +1 -1
- package/dist/{preflight-ept9q57t.js → preflight-negnxg7b.js} +8 -8
- package/dist/preview/file-watcher.d.ts +1 -0
- package/dist/{preview-fxpnjv7k.js → preview-cep2s9cg.js} +8 -8
- package/dist/{publish-zhqqsgmr.js → publish-xery6qc6.js} +8 -8
- package/dist/render.js +470 -11
- package/dist/schema/manifest.types.d.ts +8 -0
- package/dist/theme-9c58td2d.css +76 -0
- package/dist/{validate-t68gvckq.js → validate-3d0sgf4g.js} +8 -8
- package/package.json +2 -1
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
TARGETS,
|
|
9
9
|
TARGET_IDS,
|
|
10
10
|
UsageError,
|
|
11
|
+
addBuiltInStyleSet,
|
|
11
12
|
addExtension,
|
|
12
13
|
applyDefaultPdfStrictChecks,
|
|
13
14
|
ensureProjectGitignore,
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
uniqueExtensionId,
|
|
30
31
|
withFetchTimeout,
|
|
31
32
|
writeManifestDoc
|
|
32
|
-
} from "./index-
|
|
33
|
+
} from "./index-m4hhzg3c.js";
|
|
33
34
|
import {
|
|
34
35
|
EXTENSION_MANIFEST_FILENAME,
|
|
35
36
|
GUTTERPRESS_CSS,
|
|
@@ -64,7 +65,7 @@ import {
|
|
|
64
65
|
setLogLevel,
|
|
65
66
|
slugify,
|
|
66
67
|
warn
|
|
67
|
-
} from "./index-
|
|
68
|
+
} from "./index-2jpk25en.js";
|
|
68
69
|
import {
|
|
69
70
|
blackTextToGray
|
|
70
71
|
} from "./index-ht1bcqdd.js";
|
|
@@ -78,7 +79,7 @@ import {
|
|
|
78
79
|
import {
|
|
79
80
|
BuildError,
|
|
80
81
|
getAssetPath
|
|
81
|
-
} from "./index-
|
|
82
|
+
} from "./index-dsvk7q6s.js";
|
|
82
83
|
import {
|
|
83
84
|
countLinkAnnotations,
|
|
84
85
|
getImageResolutions,
|
|
@@ -279,11 +280,12 @@ async function assembleBookHtml(opts) {
|
|
|
279
280
|
const title = opts.title ?? "Document";
|
|
280
281
|
const projectCss = opts.projectCss ?? "";
|
|
281
282
|
const pluginCss = opts.pluginCss ?? "";
|
|
283
|
+
const language = opts.language ?? "en";
|
|
282
284
|
const files = opts.files;
|
|
283
285
|
if (files.length === 0) {
|
|
284
286
|
throw new Error("No markdown files to render");
|
|
285
287
|
}
|
|
286
|
-
const md = createMarkdownRenderer(opts.plugins);
|
|
288
|
+
const md = createMarkdownRenderer(opts.plugins, { language });
|
|
287
289
|
let bodyContent = "";
|
|
288
290
|
const imageRefs = new Set;
|
|
289
291
|
for (const file of files) {
|
|
@@ -295,7 +297,7 @@ async function assembleBookHtml(opts) {
|
|
|
295
297
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
296
298
|
throw new Error(`Failed to read file ${file}: ${errorMsg}`);
|
|
297
299
|
}
|
|
298
|
-
const env = {};
|
|
300
|
+
const env = { docId: slugify(chapterId) };
|
|
299
301
|
if (opts.annotateSourceChapters)
|
|
300
302
|
env.sourceChapter = chapterId;
|
|
301
303
|
const rendered = md.render(content, env);
|
|
@@ -341,7 +343,7 @@ ${projectCss.trim()}` : null
|
|
|
341
343
|
const lastPage = opts.print && lastPageMode(opts.print);
|
|
342
344
|
const printAttrs = (opts.print?.signature && opts.print.signature > 1 ? ` data-gp-signature="${opts.print.signature}"` : "") + (lastPage ? ` data-gp-last-page="${lastPage}"` : "");
|
|
343
345
|
return `<!DOCTYPE html>
|
|
344
|
-
<html lang="
|
|
346
|
+
<html lang="${language}"${printAttrs}>
|
|
345
347
|
<head>
|
|
346
348
|
<meta charset="UTF-8">
|
|
347
349
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">${opts.printArea ? `
|
|
@@ -801,6 +803,7 @@ ${css.trim()}
|
|
|
801
803
|
projectCss: inlined.css,
|
|
802
804
|
preloadImages: cssAssetCopies.map((c) => c.to),
|
|
803
805
|
title: opts.title,
|
|
806
|
+
language: opts.language,
|
|
804
807
|
print: opts.print,
|
|
805
808
|
printArea: opts.printArea,
|
|
806
809
|
plugins: opts.plugins,
|
|
@@ -814,6 +817,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
|
|
|
814
817
|
await mkdir(outDir, { recursive: true });
|
|
815
818
|
const html = await renderChapters(inputDir, {
|
|
816
819
|
title: opts.title,
|
|
820
|
+
language: opts.language,
|
|
817
821
|
print: opts.print,
|
|
818
822
|
printArea: opts.printArea,
|
|
819
823
|
styles: opts.styles,
|
|
@@ -4023,7 +4027,7 @@ async function collectKnownClasses(ctx, plugins) {
|
|
|
4023
4027
|
var check26 = {
|
|
4024
4028
|
id: "source.markdown.layout-markers",
|
|
4025
4029
|
name: "Layout Markers",
|
|
4026
|
-
description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand,
|
|
4030
|
+
description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, any unknown gp-* class and any other class no stylesheet mentions, a table row with more cells than its table has columns, and a formula that could not be typeset",
|
|
4027
4031
|
category: "source",
|
|
4028
4032
|
phase: "pre-build",
|
|
4029
4033
|
async run(ctx) {
|
|
@@ -4161,14 +4165,14 @@ registerCheck(check27);
|
|
|
4161
4165
|
import { readFile as readFile17 } from "node:fs/promises";
|
|
4162
4166
|
|
|
4163
4167
|
// src/checks/source/character-table.ts
|
|
4164
|
-
var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\
|
|
4168
|
+
var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)" + "|(?<=[\\p{L}\\p{N}])\\u2019|\\u2019(?![\\p{L}\\p{N}])", "gu");
|
|
4165
4169
|
var NAMED = {
|
|
4166
4170
|
8212: ["Em dash", "typographer", "---"],
|
|
4167
4171
|
8211: ["En dash", "typographer", "--"],
|
|
4168
|
-
8220: ["
|
|
4169
|
-
8221: ["
|
|
4170
|
-
8216: ["
|
|
4171
|
-
8217: ["
|
|
4172
|
+
8220: ["Left curly quote", "typographer", '"'],
|
|
4173
|
+
8221: ["Right curly quote", "typographer", '"'],
|
|
4174
|
+
8216: ["Left curly single quote", "typographer", "'"],
|
|
4175
|
+
8217: ["Right curly single quote (apostrophe)", "typographer", "'"],
|
|
4172
4176
|
8230: ["Ellipsis character", "typographer", "..."],
|
|
4173
4177
|
700: ["Modifier-letter apostrophe", "lookalike", "'"],
|
|
4174
4178
|
180: ["Acute accent", "lookalike", "'"],
|
|
@@ -5017,7 +5021,7 @@ function reportMissingTools(result) {
|
|
|
5017
5021
|
const affected = checkIds.filter((id) => result.skippedChecks.includes(id));
|
|
5018
5022
|
if (affected.length === 0)
|
|
5019
5023
|
continue;
|
|
5020
|
-
log.warn(`Tool "${tool}" not found
|
|
5024
|
+
log.warn(`Tool "${tool}" not found; skipping: ${affected.join(", ")}`);
|
|
5021
5025
|
}
|
|
5022
5026
|
}
|
|
5023
5027
|
|
|
@@ -5519,6 +5523,7 @@ async function renderBook(ctx) {
|
|
|
5519
5523
|
const cssAssets = [];
|
|
5520
5524
|
const htmlFile = await renderChaptersToFile(renderDir, workDir, {
|
|
5521
5525
|
title: config.title,
|
|
5526
|
+
language: config.language,
|
|
5522
5527
|
print: config.print,
|
|
5523
5528
|
printArea: { ...config.page, preset: config.preset, spec: config.spec },
|
|
5524
5529
|
styles: config.styles,
|
|
@@ -5673,7 +5678,7 @@ class PdfOutput {
|
|
|
5673
5678
|
const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
|
|
5674
5679
|
await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
|
|
5675
5680
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
5676
|
-
const { buildNativePdf } = await import("./engine-
|
|
5681
|
+
const { buildNativePdf } = await import("./engine-yjzknt0g.js");
|
|
5677
5682
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
5678
5683
|
title: config.title,
|
|
5679
5684
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -5946,6 +5951,7 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
5946
5951
|
const { plugins, pluginStyles } = await loadPluginsWithCss(config.extensions, inputPath, (ref, err) => warn(`Skipping plugin "${ref}" in preview — ${err.message}`));
|
|
5947
5952
|
return renderChapters(inputPath, {
|
|
5948
5953
|
title: config.title ?? "Document",
|
|
5954
|
+
language: config.language,
|
|
5949
5955
|
print: config.print,
|
|
5950
5956
|
printArea: config.printArea,
|
|
5951
5957
|
styles: config.styles,
|
|
@@ -7209,9 +7215,9 @@ async function getSystemDiagnostics() {
|
|
|
7209
7215
|
};
|
|
7210
7216
|
}
|
|
7211
7217
|
// src/lib/project-scaffold.ts
|
|
7212
|
-
import { access, copyFile, cp, mkdir as
|
|
7213
|
-
import { constants as
|
|
7214
|
-
import
|
|
7218
|
+
import { access as access2, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir7, readFile as readFile26, stat as stat5, writeFile as writeFile9 } from "node:fs/promises";
|
|
7219
|
+
import { constants as FS2, existsSync as existsSync10 } from "node:fs";
|
|
7220
|
+
import path15 from "node:path";
|
|
7215
7221
|
|
|
7216
7222
|
// src/lib/page-size.ts
|
|
7217
7223
|
import { existsSync as existsSync8 } from "node:fs";
|
|
@@ -7373,13 +7379,262 @@ async function setPageSetup(projectDir, choice) {
|
|
|
7373
7379
|
return readPageSetup(projectDir);
|
|
7374
7380
|
}
|
|
7375
7381
|
|
|
7382
|
+
// src/lib/project-templates.ts
|
|
7383
|
+
import { cp, mkdir as mkdir6, readFile as readFile25, readdir as readdir6, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
|
|
7384
|
+
import { constants as FS, existsSync as existsSync9 } from "node:fs";
|
|
7385
|
+
import { access } from "node:fs/promises";
|
|
7386
|
+
import path14 from "node:path";
|
|
7387
|
+
import { isMap as isMap2, isScalar, isSeq, parseDocument } from "yaml";
|
|
7388
|
+
var BUILT_IN_TEMPLATE_IDS = [
|
|
7389
|
+
"book",
|
|
7390
|
+
"zine",
|
|
7391
|
+
"technical",
|
|
7392
|
+
"paper"
|
|
7393
|
+
];
|
|
7394
|
+
async function readTemplateChoices(manifestPath) {
|
|
7395
|
+
try {
|
|
7396
|
+
const text = await readFile25(manifestPath, "utf8");
|
|
7397
|
+
const doc = parseDocument(text);
|
|
7398
|
+
const out = {};
|
|
7399
|
+
const preset = doc.get("preset");
|
|
7400
|
+
if (typeof preset === "string" && preset)
|
|
7401
|
+
out.preset = preset;
|
|
7402
|
+
const targets = doc.get("targets", true);
|
|
7403
|
+
if (isSeq(targets)) {
|
|
7404
|
+
out.targets = targets.items.map((i) => scalarString(i)).filter((s) => s !== null);
|
|
7405
|
+
}
|
|
7406
|
+
return out;
|
|
7407
|
+
} catch {
|
|
7408
|
+
return {};
|
|
7409
|
+
}
|
|
7410
|
+
}
|
|
7411
|
+
var BUILT_IN_META = {
|
|
7412
|
+
book: {
|
|
7413
|
+
label: "Book",
|
|
7414
|
+
description: "A clean starting point for a novel, memoir, or any long-form book."
|
|
7415
|
+
},
|
|
7416
|
+
zine: {
|
|
7417
|
+
label: "Zine",
|
|
7418
|
+
description: "A short, personal zine made to be printed, folded, and shared."
|
|
7419
|
+
},
|
|
7420
|
+
technical: {
|
|
7421
|
+
label: "Technical document",
|
|
7422
|
+
description: "A manual or guide with steps, reference tables, and code examples."
|
|
7423
|
+
},
|
|
7424
|
+
paper: {
|
|
7425
|
+
label: "Academic paper",
|
|
7426
|
+
description: "A journal-style article with LaTeX math, numbered equations, figures, tables, notes and references.",
|
|
7427
|
+
look: "academic"
|
|
7428
|
+
}
|
|
7429
|
+
};
|
|
7430
|
+
async function listBuiltInTemplates() {
|
|
7431
|
+
return Promise.all(BUILT_IN_TEMPLATE_IDS.map(async (id) => ({
|
|
7432
|
+
id,
|
|
7433
|
+
label: BUILT_IN_META[id].label,
|
|
7434
|
+
description: BUILT_IN_META[id].description,
|
|
7435
|
+
kind: "builtin",
|
|
7436
|
+
...await readTemplateChoices(await getAssetPath(`templates/${id}/manifest.yaml`))
|
|
7437
|
+
})));
|
|
7438
|
+
}
|
|
7439
|
+
var SKIP_ENTRIES = new Set([
|
|
7440
|
+
".git",
|
|
7441
|
+
"node_modules",
|
|
7442
|
+
"out",
|
|
7443
|
+
"dist",
|
|
7444
|
+
"build",
|
|
7445
|
+
".gutterpress",
|
|
7446
|
+
".DS_Store"
|
|
7447
|
+
]);
|
|
7448
|
+
var VENDOR_DIR = "shared";
|
|
7449
|
+
function collectEscaping(nodes, getValue, escapes) {
|
|
7450
|
+
const out = [];
|
|
7451
|
+
for (const node of nodes) {
|
|
7452
|
+
const value = getValue(node);
|
|
7453
|
+
if (value !== null && escapes(value))
|
|
7454
|
+
out.push({ node, value });
|
|
7455
|
+
}
|
|
7456
|
+
return out;
|
|
7457
|
+
}
|
|
7458
|
+
function extensionEntryPath(node) {
|
|
7459
|
+
const use = isMap2(node) ? scalarString(node.get("use", true)) : scalarString(node);
|
|
7460
|
+
return use !== null && isPathSpecifier(use) ? use : null;
|
|
7461
|
+
}
|
|
7462
|
+
function dropEscaping(doc, seq, key, escaping) {
|
|
7463
|
+
if (!isSeq(seq))
|
|
7464
|
+
return;
|
|
7465
|
+
const drop = new Set(escaping.map((e) => e.node));
|
|
7466
|
+
seq.items = seq.items.filter((node) => !drop.has(node));
|
|
7467
|
+
if (seq.items.length === 0)
|
|
7468
|
+
doc.delete(key);
|
|
7469
|
+
}
|
|
7470
|
+
function rewriteEntry(node, newPath) {
|
|
7471
|
+
if (isMap2(node))
|
|
7472
|
+
node.set("use", newPath);
|
|
7473
|
+
else if (isScalar(node))
|
|
7474
|
+
node.value = newPath;
|
|
7475
|
+
}
|
|
7476
|
+
function commonAncestorDir(absFiles) {
|
|
7477
|
+
const dirSegs = absFiles.map((f) => path14.resolve(f).split(path14.sep).slice(0, -1));
|
|
7478
|
+
if (dirSegs.length === 0)
|
|
7479
|
+
return "";
|
|
7480
|
+
let common = dirSegs[0];
|
|
7481
|
+
for (const segs of dirSegs.slice(1)) {
|
|
7482
|
+
let i = 0;
|
|
7483
|
+
while (i < common.length && i < segs.length && common[i] === segs[i])
|
|
7484
|
+
i++;
|
|
7485
|
+
common = common.slice(0, i);
|
|
7486
|
+
}
|
|
7487
|
+
return common.join(path14.sep) || path14.sep;
|
|
7488
|
+
}
|
|
7489
|
+
function pickVendorDir(templateDir, base) {
|
|
7490
|
+
const preferred = path14.basename(base) || VENDOR_DIR;
|
|
7491
|
+
if (!existsSync9(path14.join(templateDir, preferred)))
|
|
7492
|
+
return preferred;
|
|
7493
|
+
for (let n = 1;; n++) {
|
|
7494
|
+
const candidate = `${preferred}-${n}`;
|
|
7495
|
+
if (!existsSync9(path14.join(templateDir, candidate)))
|
|
7496
|
+
return candidate;
|
|
7497
|
+
}
|
|
7498
|
+
}
|
|
7499
|
+
var TEMPLATE_META_FILE = ".gutterpress-template.json";
|
|
7500
|
+
async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
|
|
7501
|
+
const { doc, file } = await loadManifestDoc(templateDir);
|
|
7502
|
+
const escapes = (p) => escapesProjectRoot(sourceProjectDir, path14.resolve(sourceProjectDir, p));
|
|
7503
|
+
const stylesSeq = doc.get("styles", true);
|
|
7504
|
+
const extensionsSeq = doc.get("extensions", true);
|
|
7505
|
+
const escapingStyles = collectEscaping(isSeq(stylesSeq) ? stylesSeq.items : [], scalarString, escapes);
|
|
7506
|
+
const escapingExtensions = collectEscaping(isSeq(extensionsSeq) ? extensionsSeq.items : [], extensionEntryPath, escapes);
|
|
7507
|
+
if (escapingStyles.length === 0 && escapingExtensions.length === 0) {
|
|
7508
|
+
return { vendoredRefs: [], excludedRefs: [] };
|
|
7509
|
+
}
|
|
7510
|
+
if (mode === "exclude") {
|
|
7511
|
+
const excludedRefs = [...escapingStyles, ...escapingExtensions].map((e) => e.value);
|
|
7512
|
+
dropEscaping(doc, stylesSeq, "styles", escapingStyles);
|
|
7513
|
+
dropEscaping(doc, extensionsSeq, "extensions", escapingExtensions);
|
|
7514
|
+
await writeManifestDoc(file, doc);
|
|
7515
|
+
return { vendoredRefs: [], excludedRefs };
|
|
7516
|
+
}
|
|
7517
|
+
const styleClosure = await collectStyleDependencies(sourceProjectDir, escapingStyles.map((s) => s.value));
|
|
7518
|
+
const extensionFiles = escapingExtensions.map((p) => path14.resolve(sourceProjectDir, p.value));
|
|
7519
|
+
const externalFiles = [...new Set([...styleClosure, ...extensionFiles])].filter((f) => escapesProjectRoot(sourceProjectDir, f));
|
|
7520
|
+
const base = commonAncestorDir(externalFiles);
|
|
7521
|
+
const vendorDir = pickVendorDir(templateDir, base);
|
|
7522
|
+
await Promise.all(externalFiles.filter((abs) => existsSync9(abs)).map(async (abs) => {
|
|
7523
|
+
const dest = path14.join(templateDir, vendorDir, path14.relative(base, abs));
|
|
7524
|
+
await mkdir6(path14.dirname(dest), { recursive: true });
|
|
7525
|
+
await cp(abs, dest, { recursive: true });
|
|
7526
|
+
}));
|
|
7527
|
+
const bookLocal = (value) => toPosixPath(path14.join(vendorDir, path14.relative(base, path14.resolve(sourceProjectDir, value))));
|
|
7528
|
+
const vendoredRefs = [];
|
|
7529
|
+
for (const { node, value } of escapingStyles) {
|
|
7530
|
+
const rewritten = bookLocal(value);
|
|
7531
|
+
rewriteEntry(node, rewritten);
|
|
7532
|
+
vendoredRefs.push(rewritten);
|
|
7533
|
+
}
|
|
7534
|
+
for (const { node, value } of escapingExtensions) {
|
|
7535
|
+
const rewritten = bookLocal(value);
|
|
7536
|
+
rewriteEntry(node, `./${rewritten}`);
|
|
7537
|
+
vendoredRefs.push(rewritten);
|
|
7538
|
+
}
|
|
7539
|
+
await writeManifestDoc(file, doc);
|
|
7540
|
+
return { vendoredRefs, excludedRefs: [] };
|
|
7541
|
+
}
|
|
7542
|
+
async function saveProjectAsTemplate(options) {
|
|
7543
|
+
const { projectDir, name, templatesRoot } = options;
|
|
7544
|
+
const id = slugify(name);
|
|
7545
|
+
if (!id)
|
|
7546
|
+
throw new Error(`Could not derive a template id from "${name}".`);
|
|
7547
|
+
const dir = path14.join(templatesRoot, id);
|
|
7548
|
+
let exists = true;
|
|
7549
|
+
try {
|
|
7550
|
+
await access(dir, FS.F_OK);
|
|
7551
|
+
} catch {
|
|
7552
|
+
exists = false;
|
|
7553
|
+
}
|
|
7554
|
+
if (exists) {
|
|
7555
|
+
throw new Error(`A template named "${name}" already exists. Choose a different name.`);
|
|
7556
|
+
}
|
|
7557
|
+
await mkdir6(dir, { recursive: true });
|
|
7558
|
+
const entries = await readdir6(projectDir, { withFileTypes: true });
|
|
7559
|
+
for (const entry of entries) {
|
|
7560
|
+
if (SKIP_ENTRIES.has(entry.name))
|
|
7561
|
+
continue;
|
|
7562
|
+
if (entry.name === TEMPLATE_META_FILE)
|
|
7563
|
+
continue;
|
|
7564
|
+
await cp(path14.join(projectDir, entry.name), path14.join(dir, entry.name), { recursive: true });
|
|
7565
|
+
}
|
|
7566
|
+
const manifestName = MANIFEST_FILENAMES.find((name2) => entries.some((entry) => entry.name === name2));
|
|
7567
|
+
const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
|
|
7568
|
+
if (manifestName)
|
|
7569
|
+
await retokeniseManifest(path14.join(dir, manifestName));
|
|
7570
|
+
await writeFile8(path14.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
|
|
7571
|
+
return {
|
|
7572
|
+
id,
|
|
7573
|
+
label: name,
|
|
7574
|
+
description: "Your saved template.",
|
|
7575
|
+
kind: "custom",
|
|
7576
|
+
dir,
|
|
7577
|
+
...shared
|
|
7578
|
+
};
|
|
7579
|
+
}
|
|
7580
|
+
async function retokeniseManifest(manifestPath) {
|
|
7581
|
+
let text;
|
|
7582
|
+
try {
|
|
7583
|
+
text = await readFile25(manifestPath, "utf8");
|
|
7584
|
+
} catch {
|
|
7585
|
+
return;
|
|
7586
|
+
}
|
|
7587
|
+
text = text.replace(/^title:\s*.*$/m, 'title: "{{TITLE}}"').replace(/^(authors:[^\n]*\n[ \t]*-[ \t]*).*$/m, '$1"{{AUTHOR}}"');
|
|
7588
|
+
await writeFile8(manifestPath, text, "utf8");
|
|
7589
|
+
}
|
|
7590
|
+
async function listCustomTemplates(templatesRoot) {
|
|
7591
|
+
let entries;
|
|
7592
|
+
try {
|
|
7593
|
+
entries = await readdir6(templatesRoot, { withFileTypes: true });
|
|
7594
|
+
} catch {
|
|
7595
|
+
return [];
|
|
7596
|
+
}
|
|
7597
|
+
const out = [];
|
|
7598
|
+
for (const entry of entries) {
|
|
7599
|
+
if (!entry.isDirectory())
|
|
7600
|
+
continue;
|
|
7601
|
+
const dir = path14.join(templatesRoot, entry.name);
|
|
7602
|
+
let label = prettify(entry.name);
|
|
7603
|
+
try {
|
|
7604
|
+
const meta = JSON.parse(await readFile25(path14.join(dir, TEMPLATE_META_FILE), "utf8"));
|
|
7605
|
+
if (typeof meta.label === "string" && meta.label)
|
|
7606
|
+
label = meta.label;
|
|
7607
|
+
} catch {}
|
|
7608
|
+
out.push({
|
|
7609
|
+
id: entry.name,
|
|
7610
|
+
label,
|
|
7611
|
+
description: "Your saved template.",
|
|
7612
|
+
kind: "custom",
|
|
7613
|
+
dir,
|
|
7614
|
+
...await readTemplateChoices(path14.join(dir, MANIFEST_FILENAMES[0]))
|
|
7615
|
+
});
|
|
7616
|
+
}
|
|
7617
|
+
out.sort((a, b) => a.label.localeCompare(b.label));
|
|
7618
|
+
return out;
|
|
7619
|
+
}
|
|
7620
|
+
async function importTemplateFromFolder(options) {
|
|
7621
|
+
const { sourceDir, templatesRoot } = options;
|
|
7622
|
+
const srcStat = await stat4(sourceDir);
|
|
7623
|
+
if (!srcStat.isDirectory()) {
|
|
7624
|
+
throw new Error("The chosen path is not a folder.");
|
|
7625
|
+
}
|
|
7626
|
+
const name = options.name?.trim() || prettify(path14.basename(sourceDir));
|
|
7627
|
+
return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
|
|
7628
|
+
}
|
|
7629
|
+
|
|
7376
7630
|
// src/lib/project-scaffold.ts
|
|
7377
7631
|
function starterOverridesCss() {
|
|
7378
7632
|
return `/**
|
|
7379
7633
|
* Your book's own styles.
|
|
7380
7634
|
*
|
|
7381
7635
|
* This file loads AFTER every extension, so any rule you add here overrides
|
|
7382
|
-
* a look's defaults. A new book has no look until you add one
|
|
7636
|
+
* a look's defaults. A new book has no look until you add one (the paper
|
|
7637
|
+
* template adds the academic look for you):
|
|
7383
7638
|
* gutterpress ext add clean-book <book> --look (a built-in look)
|
|
7384
7639
|
* gutterpress ext add <package> <book> (a look from npm)
|
|
7385
7640
|
* A book has one look at a time; adding another turns the current one off.
|
|
@@ -7443,18 +7698,18 @@ async function scaffoldProject(options) {
|
|
|
7443
7698
|
throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
|
|
7444
7699
|
}
|
|
7445
7700
|
const parentDir = options.parentDir;
|
|
7446
|
-
if (!parentDir || !
|
|
7701
|
+
if (!parentDir || !path15.isAbsolute(parentDir)) {
|
|
7447
7702
|
throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
|
|
7448
7703
|
}
|
|
7449
7704
|
try {
|
|
7450
|
-
await
|
|
7705
|
+
await access2(parentDir, FS2.W_OK);
|
|
7451
7706
|
} catch {
|
|
7452
7707
|
throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
7453
7708
|
}
|
|
7454
|
-
const projectDir =
|
|
7709
|
+
const projectDir = path15.join(parentDir, folderName);
|
|
7455
7710
|
let targetExists = true;
|
|
7456
7711
|
try {
|
|
7457
|
-
await
|
|
7712
|
+
await access2(projectDir, FS2.F_OK);
|
|
7458
7713
|
} catch {
|
|
7459
7714
|
targetExists = false;
|
|
7460
7715
|
}
|
|
@@ -7466,23 +7721,23 @@ async function scaffoldProject(options) {
|
|
|
7466
7721
|
const preset = customTemplateDir && options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
|
|
7467
7722
|
const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
|
|
7468
7723
|
try {
|
|
7469
|
-
await
|
|
7724
|
+
await mkdir7(projectDir, { recursive: true });
|
|
7470
7725
|
if (customTemplateDir) {
|
|
7471
|
-
const entries = await
|
|
7726
|
+
const entries = await readdir7(customTemplateDir, { withFileTypes: true });
|
|
7472
7727
|
for (const entry of entries) {
|
|
7473
7728
|
if (entry.name === ".gutterpress-template.json")
|
|
7474
7729
|
continue;
|
|
7475
|
-
await
|
|
7730
|
+
await cp2(path15.join(customTemplateDir, entry.name), path15.join(projectDir, entry.name), { recursive: true });
|
|
7476
7731
|
}
|
|
7477
|
-
await
|
|
7732
|
+
await mkdir7(path15.join(projectDir, "assets"), { recursive: true });
|
|
7478
7733
|
} else {
|
|
7479
7734
|
const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
|
|
7480
7735
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
7481
|
-
await
|
|
7482
|
-
await copyFile(tplManifest,
|
|
7483
|
-
await copyFile(tplChapter,
|
|
7484
|
-
await
|
|
7485
|
-
await
|
|
7736
|
+
await mkdir7(path15.join(projectDir, "assets"), { recursive: true });
|
|
7737
|
+
await copyFile(tplManifest, path15.join(projectDir, MANIFEST_FILENAMES[0]));
|
|
7738
|
+
await copyFile(tplChapter, path15.join(projectDir, "chapter-01.md"));
|
|
7739
|
+
await mkdir7(path15.join(projectDir, "styles"), { recursive: true });
|
|
7740
|
+
await writeFile9(path15.join(projectDir, "styles", "book.css"), starterOverridesCss(), "utf8");
|
|
7486
7741
|
}
|
|
7487
7742
|
} catch (e) {
|
|
7488
7743
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not create the book files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7492,15 +7747,18 @@ async function scaffoldProject(options) {
|
|
|
7492
7747
|
} catch (e) {
|
|
7493
7748
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
|
|
7494
7749
|
}
|
|
7495
|
-
const manifestPath = MANIFEST_FILENAMES.map((name2) =>
|
|
7750
|
+
const manifestPath = MANIFEST_FILENAMES.map((name2) => path15.join(projectDir, name2)).find(existsSync10) ?? path15.join(projectDir, MANIFEST_FILENAMES[0]);
|
|
7496
7751
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
7497
7752
|
const substitutions = {
|
|
7498
7753
|
"{{TITLE}}": escapeYamlScalar(name),
|
|
7499
7754
|
"{{AUTHOR}}": escapeYamlScalar(author)
|
|
7500
7755
|
};
|
|
7501
|
-
let openFile =
|
|
7756
|
+
let openFile = path15.join(projectDir, "chapter-01.md");
|
|
7502
7757
|
try {
|
|
7503
7758
|
await fillTemplateFile(manifestPath, substitutions);
|
|
7759
|
+
const look = customTemplateDir ? undefined : BUILT_IN_META[template].look;
|
|
7760
|
+
if (look)
|
|
7761
|
+
await addBuiltInStyleSet(projectDir, look);
|
|
7504
7762
|
if (preset) {
|
|
7505
7763
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
7506
7764
|
doc.set("preset", preset);
|
|
@@ -7511,7 +7769,7 @@ async function scaffoldProject(options) {
|
|
|
7511
7769
|
}
|
|
7512
7770
|
const firstSource = await firstSourceFile(manifestPath);
|
|
7513
7771
|
if (firstSource)
|
|
7514
|
-
openFile =
|
|
7772
|
+
openFile = path15.join(projectDir, firstSource);
|
|
7515
7773
|
await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
|
|
7516
7774
|
} catch (e) {
|
|
7517
7775
|
throw new CreateProjectErrorImpl("scaffold-io", `Could not finalize the book files: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7533,7 +7791,7 @@ async function scaffoldProject(options) {
|
|
|
7533
7791
|
versionHistory = "none";
|
|
7534
7792
|
const { findEnclosingRepoDir } = await import("./project-source-ekcyp63q.js");
|
|
7535
7793
|
const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
|
|
7536
|
-
versionHistoryError = enclosingRepoDir ? `This book was added to ${
|
|
7794
|
+
versionHistoryError = enclosingRepoDir ? `This book was added to ${path15.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
|
|
7537
7795
|
}
|
|
7538
7796
|
}
|
|
7539
7797
|
const result = {
|
|
@@ -7553,38 +7811,38 @@ function prettifyFolderName(base) {
|
|
|
7553
7811
|
}
|
|
7554
7812
|
async function adoptFolder(options) {
|
|
7555
7813
|
const dir = options.dir;
|
|
7556
|
-
if (typeof dir !== "string" || !
|
|
7814
|
+
if (typeof dir !== "string" || !path15.isAbsolute(dir)) {
|
|
7557
7815
|
throw new CreateProjectErrorImpl("scaffold-io", "An absolute folder path is required.");
|
|
7558
7816
|
}
|
|
7559
7817
|
let st;
|
|
7560
7818
|
try {
|
|
7561
|
-
st = await
|
|
7819
|
+
st = await stat5(dir);
|
|
7562
7820
|
} catch {
|
|
7563
7821
|
throw new CreateProjectErrorImpl("scaffold-io", `Folder not found: ${dir}`);
|
|
7564
7822
|
}
|
|
7565
7823
|
if (!st.isDirectory()) {
|
|
7566
7824
|
throw new CreateProjectErrorImpl("scaffold-io", `Not a folder: ${dir}`);
|
|
7567
7825
|
}
|
|
7568
|
-
if (MANIFEST_FILENAMES.some((name) =>
|
|
7826
|
+
if (MANIFEST_FILENAMES.some((name) => existsSync10(path15.join(dir, name)))) {
|
|
7569
7827
|
throw new CreateProjectErrorImpl("target-exists", "This folder is already a Gutterpress book.");
|
|
7570
7828
|
}
|
|
7571
7829
|
const template = options.template ?? "book";
|
|
7572
|
-
const title = (options.title ?? "").trim() || prettifyFolderName(
|
|
7830
|
+
const title = (options.title ?? "").trim() || prettifyFolderName(path15.basename(dir));
|
|
7573
7831
|
const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
|
|
7574
7832
|
let mdFiles;
|
|
7575
7833
|
try {
|
|
7576
|
-
const entries = await
|
|
7834
|
+
const entries = await readdir7(dir, { withFileTypes: true });
|
|
7577
7835
|
mdFiles = entries.filter((e) => e.isFile() && /\.(md|markdown)$/i.test(e.name)).map((e) => e.name).sort((a, b) => a.localeCompare(b));
|
|
7578
7836
|
if (mdFiles.length === 0) {
|
|
7579
7837
|
const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
|
|
7580
|
-
await copyFile(tplChapter,
|
|
7581
|
-
await fillTemplateFile(
|
|
7838
|
+
await copyFile(tplChapter, path15.join(dir, "chapter-01.md"));
|
|
7839
|
+
await fillTemplateFile(path15.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
|
|
7582
7840
|
mdFiles = ["chapter-01.md"];
|
|
7583
7841
|
}
|
|
7584
|
-
await
|
|
7585
|
-
const bookCssPath =
|
|
7586
|
-
if (!
|
|
7587
|
-
await
|
|
7842
|
+
await mkdir7(path15.join(dir, "styles"), { recursive: true });
|
|
7843
|
+
const bookCssPath = path15.join(dir, "styles", "book.css");
|
|
7844
|
+
if (!existsSync10(bookCssPath)) {
|
|
7845
|
+
await writeFile9(bookCssPath, starterOverridesCss(), "utf8");
|
|
7588
7846
|
}
|
|
7589
7847
|
const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
|
|
7590
7848
|
`);
|
|
@@ -7600,8 +7858,8 @@ ${filesYaml}
|
|
|
7600
7858
|
styles:
|
|
7601
7859
|
- styles/book.css
|
|
7602
7860
|
`;
|
|
7603
|
-
await
|
|
7604
|
-
await
|
|
7861
|
+
await writeFile9(path15.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
|
|
7862
|
+
await mkdir7(path15.join(dir, "assets"), { recursive: true });
|
|
7605
7863
|
await ensureProjectGitignore(dir);
|
|
7606
7864
|
} catch (e) {
|
|
7607
7865
|
if (e instanceof CreateProjectErrorImpl)
|
|
@@ -7628,8 +7886,8 @@ styles:
|
|
|
7628
7886
|
}
|
|
7629
7887
|
const result = {
|
|
7630
7888
|
projectDir: dir,
|
|
7631
|
-
manifestPath:
|
|
7632
|
-
openFile:
|
|
7889
|
+
manifestPath: path15.join(dir, MANIFEST_FILENAMES[0]),
|
|
7890
|
+
openFile: path15.join(dir, mdFiles[0]),
|
|
7633
7891
|
versionHistory
|
|
7634
7892
|
};
|
|
7635
7893
|
if (versionHistoryError !== undefined)
|
|
@@ -7639,7 +7897,7 @@ styles:
|
|
|
7639
7897
|
async function firstSourceFile(manifestPath) {
|
|
7640
7898
|
let text;
|
|
7641
7899
|
try {
|
|
7642
|
-
text = await
|
|
7900
|
+
text = await readFile26(manifestPath, "utf8");
|
|
7643
7901
|
} catch {
|
|
7644
7902
|
return;
|
|
7645
7903
|
}
|
|
@@ -7651,16 +7909,16 @@ async function firstSourceFile(manifestPath) {
|
|
|
7651
7909
|
return m ? m[1].trim() : undefined;
|
|
7652
7910
|
}
|
|
7653
7911
|
async function fillTemplateFile(filePath, substitutions) {
|
|
7654
|
-
let text = await
|
|
7912
|
+
let text = await readFile26(filePath, "utf8");
|
|
7655
7913
|
for (const [token, value] of Object.entries(substitutions)) {
|
|
7656
7914
|
text = text.split(token).join(value);
|
|
7657
7915
|
}
|
|
7658
|
-
await
|
|
7916
|
+
await writeFile9(filePath, text, "utf8");
|
|
7659
7917
|
}
|
|
7660
7918
|
// src/lib/extension-scaffold.ts
|
|
7661
|
-
import { access as
|
|
7662
|
-
import { constants as
|
|
7663
|
-
import
|
|
7919
|
+
import { access as access3, mkdir as mkdir8, readFile as readFile27, writeFile as writeFile10 } from "node:fs/promises";
|
|
7920
|
+
import { constants as FS3 } from "node:fs";
|
|
7921
|
+
import path16 from "node:path";
|
|
7664
7922
|
var EXTENSION_KINDS = ["plugin", "theme"];
|
|
7665
7923
|
var RESERVED_PREFIX = "gp-";
|
|
7666
7924
|
var TEMPLATE_FILES = {
|
|
@@ -7738,18 +7996,18 @@ async function scaffoldExtension(options) {
|
|
|
7738
7996
|
}
|
|
7739
7997
|
const prefix = resolveExtensionPrefix(slug, options.prefix);
|
|
7740
7998
|
const parentDir = options.parentDir;
|
|
7741
|
-
if (!parentDir || !
|
|
7999
|
+
if (!parentDir || !path16.isAbsolute(parentDir)) {
|
|
7742
8000
|
throw new ExtensionScaffoldError("parent-not-writable", "The save location must be an absolute path.");
|
|
7743
8001
|
}
|
|
7744
8002
|
try {
|
|
7745
|
-
await
|
|
8003
|
+
await access3(parentDir, FS3.W_OK);
|
|
7746
8004
|
} catch {
|
|
7747
8005
|
throw new ExtensionScaffoldError("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
|
|
7748
8006
|
}
|
|
7749
|
-
const extensionDir =
|
|
8007
|
+
const extensionDir = path16.join(parentDir, slug);
|
|
7750
8008
|
let targetExists = true;
|
|
7751
8009
|
try {
|
|
7752
|
-
await
|
|
8010
|
+
await access3(extensionDir, FS3.F_OK);
|
|
7753
8011
|
} catch {
|
|
7754
8012
|
targetExists = false;
|
|
7755
8013
|
}
|
|
@@ -7770,14 +8028,14 @@ async function scaffoldExtension(options) {
|
|
|
7770
8028
|
const entries = TEMPLATE_FILES[kind];
|
|
7771
8029
|
const written = [];
|
|
7772
8030
|
try {
|
|
7773
|
-
await
|
|
8031
|
+
await mkdir8(extensionDir, { recursive: true });
|
|
7774
8032
|
for (const [assetName, relDest] of entries) {
|
|
7775
8033
|
const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
|
|
7776
|
-
const dest =
|
|
7777
|
-
await
|
|
7778
|
-
const text = await
|
|
8034
|
+
const dest = path16.join(extensionDir, relDest);
|
|
8035
|
+
await mkdir8(path16.dirname(dest), { recursive: true });
|
|
8036
|
+
const text = await readFile27(src, "utf8");
|
|
7779
8037
|
const table = relDest.endsWith(".json") ? jsonSafe : plain;
|
|
7780
|
-
await
|
|
8038
|
+
await writeFile10(dest, substitute(text, table), "utf8");
|
|
7781
8039
|
written.push(relDest);
|
|
7782
8040
|
}
|
|
7783
8041
|
} catch (e) {
|
|
@@ -7785,11 +8043,11 @@ async function scaffoldExtension(options) {
|
|
|
7785
8043
|
}
|
|
7786
8044
|
return {
|
|
7787
8045
|
extensionDir,
|
|
7788
|
-
manifestPath:
|
|
8046
|
+
manifestPath: path16.join(extensionDir, "package.json"),
|
|
7789
8047
|
kind,
|
|
7790
8048
|
slug,
|
|
7791
8049
|
prefix,
|
|
7792
|
-
openFile:
|
|
8050
|
+
openFile: path16.join(extensionDir, OPEN_FILE[kind]),
|
|
7793
8051
|
files: written
|
|
7794
8052
|
};
|
|
7795
8053
|
}
|
|
@@ -7800,247 +8058,6 @@ function substitute(text, table) {
|
|
|
7800
8058
|
}
|
|
7801
8059
|
return out;
|
|
7802
8060
|
}
|
|
7803
|
-
// src/lib/project-templates.ts
|
|
7804
|
-
import { cp as cp2, mkdir as mkdir8, readFile as readFile27, readdir as readdir7, stat as stat5, writeFile as writeFile10 } from "node:fs/promises";
|
|
7805
|
-
import { constants as FS3, existsSync as existsSync10 } from "node:fs";
|
|
7806
|
-
import { access as access3 } from "node:fs/promises";
|
|
7807
|
-
import path16 from "node:path";
|
|
7808
|
-
import { isMap as isMap2, isScalar, isSeq, parseDocument } from "yaml";
|
|
7809
|
-
var BUILT_IN_TEMPLATE_IDS = [
|
|
7810
|
-
"book",
|
|
7811
|
-
"zine",
|
|
7812
|
-
"technical"
|
|
7813
|
-
];
|
|
7814
|
-
async function readTemplateChoices(manifestPath) {
|
|
7815
|
-
try {
|
|
7816
|
-
const text = await readFile27(manifestPath, "utf8");
|
|
7817
|
-
const doc = parseDocument(text);
|
|
7818
|
-
const out = {};
|
|
7819
|
-
const preset = doc.get("preset");
|
|
7820
|
-
if (typeof preset === "string" && preset)
|
|
7821
|
-
out.preset = preset;
|
|
7822
|
-
const targets = doc.get("targets", true);
|
|
7823
|
-
if (isSeq(targets)) {
|
|
7824
|
-
out.targets = targets.items.map((i) => scalarString(i)).filter((s) => s !== null);
|
|
7825
|
-
}
|
|
7826
|
-
return out;
|
|
7827
|
-
} catch {
|
|
7828
|
-
return {};
|
|
7829
|
-
}
|
|
7830
|
-
}
|
|
7831
|
-
var BUILT_IN_META = {
|
|
7832
|
-
book: {
|
|
7833
|
-
label: "Book",
|
|
7834
|
-
description: "A clean starting point for a novel, memoir, or any long-form book."
|
|
7835
|
-
},
|
|
7836
|
-
zine: {
|
|
7837
|
-
label: "Zine",
|
|
7838
|
-
description: "A short, personal zine made to be printed, folded, and shared."
|
|
7839
|
-
},
|
|
7840
|
-
technical: {
|
|
7841
|
-
label: "Technical document",
|
|
7842
|
-
description: "A manual or guide with steps, reference tables, and code examples."
|
|
7843
|
-
}
|
|
7844
|
-
};
|
|
7845
|
-
async function listBuiltInTemplates() {
|
|
7846
|
-
return Promise.all(BUILT_IN_TEMPLATE_IDS.map(async (id) => ({
|
|
7847
|
-
id,
|
|
7848
|
-
label: BUILT_IN_META[id].label,
|
|
7849
|
-
description: BUILT_IN_META[id].description,
|
|
7850
|
-
kind: "builtin",
|
|
7851
|
-
...await readTemplateChoices(await getAssetPath(`templates/${id}/manifest.yaml`))
|
|
7852
|
-
})));
|
|
7853
|
-
}
|
|
7854
|
-
var SKIP_ENTRIES = new Set([
|
|
7855
|
-
".git",
|
|
7856
|
-
"node_modules",
|
|
7857
|
-
"out",
|
|
7858
|
-
"dist",
|
|
7859
|
-
"build",
|
|
7860
|
-
".gutterpress",
|
|
7861
|
-
".DS_Store"
|
|
7862
|
-
]);
|
|
7863
|
-
var VENDOR_DIR = "shared";
|
|
7864
|
-
function collectEscaping(nodes, getValue, escapes) {
|
|
7865
|
-
const out = [];
|
|
7866
|
-
for (const node of nodes) {
|
|
7867
|
-
const value = getValue(node);
|
|
7868
|
-
if (value !== null && escapes(value))
|
|
7869
|
-
out.push({ node, value });
|
|
7870
|
-
}
|
|
7871
|
-
return out;
|
|
7872
|
-
}
|
|
7873
|
-
function extensionEntryPath(node) {
|
|
7874
|
-
const use = isMap2(node) ? scalarString(node.get("use", true)) : scalarString(node);
|
|
7875
|
-
return use !== null && isPathSpecifier(use) ? use : null;
|
|
7876
|
-
}
|
|
7877
|
-
function dropEscaping(doc, seq, key, escaping) {
|
|
7878
|
-
if (!isSeq(seq))
|
|
7879
|
-
return;
|
|
7880
|
-
const drop = new Set(escaping.map((e) => e.node));
|
|
7881
|
-
seq.items = seq.items.filter((node) => !drop.has(node));
|
|
7882
|
-
if (seq.items.length === 0)
|
|
7883
|
-
doc.delete(key);
|
|
7884
|
-
}
|
|
7885
|
-
function rewriteEntry(node, newPath) {
|
|
7886
|
-
if (isMap2(node))
|
|
7887
|
-
node.set("use", newPath);
|
|
7888
|
-
else if (isScalar(node))
|
|
7889
|
-
node.value = newPath;
|
|
7890
|
-
}
|
|
7891
|
-
function commonAncestorDir(absFiles) {
|
|
7892
|
-
const dirSegs = absFiles.map((f) => path16.resolve(f).split(path16.sep).slice(0, -1));
|
|
7893
|
-
if (dirSegs.length === 0)
|
|
7894
|
-
return "";
|
|
7895
|
-
let common = dirSegs[0];
|
|
7896
|
-
for (const segs of dirSegs.slice(1)) {
|
|
7897
|
-
let i = 0;
|
|
7898
|
-
while (i < common.length && i < segs.length && common[i] === segs[i])
|
|
7899
|
-
i++;
|
|
7900
|
-
common = common.slice(0, i);
|
|
7901
|
-
}
|
|
7902
|
-
return common.join(path16.sep) || path16.sep;
|
|
7903
|
-
}
|
|
7904
|
-
function pickVendorDir(templateDir, base) {
|
|
7905
|
-
const preferred = path16.basename(base) || VENDOR_DIR;
|
|
7906
|
-
if (!existsSync10(path16.join(templateDir, preferred)))
|
|
7907
|
-
return preferred;
|
|
7908
|
-
for (let n = 1;; n++) {
|
|
7909
|
-
const candidate = `${preferred}-${n}`;
|
|
7910
|
-
if (!existsSync10(path16.join(templateDir, candidate)))
|
|
7911
|
-
return candidate;
|
|
7912
|
-
}
|
|
7913
|
-
}
|
|
7914
|
-
var TEMPLATE_META_FILE = ".gutterpress-template.json";
|
|
7915
|
-
async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
|
|
7916
|
-
const { doc, file } = await loadManifestDoc(templateDir);
|
|
7917
|
-
const escapes = (p) => escapesProjectRoot(sourceProjectDir, path16.resolve(sourceProjectDir, p));
|
|
7918
|
-
const stylesSeq = doc.get("styles", true);
|
|
7919
|
-
const extensionsSeq = doc.get("extensions", true);
|
|
7920
|
-
const escapingStyles = collectEscaping(isSeq(stylesSeq) ? stylesSeq.items : [], scalarString, escapes);
|
|
7921
|
-
const escapingExtensions = collectEscaping(isSeq(extensionsSeq) ? extensionsSeq.items : [], extensionEntryPath, escapes);
|
|
7922
|
-
if (escapingStyles.length === 0 && escapingExtensions.length === 0) {
|
|
7923
|
-
return { vendoredRefs: [], excludedRefs: [] };
|
|
7924
|
-
}
|
|
7925
|
-
if (mode === "exclude") {
|
|
7926
|
-
const excludedRefs = [...escapingStyles, ...escapingExtensions].map((e) => e.value);
|
|
7927
|
-
dropEscaping(doc, stylesSeq, "styles", escapingStyles);
|
|
7928
|
-
dropEscaping(doc, extensionsSeq, "extensions", escapingExtensions);
|
|
7929
|
-
await writeManifestDoc(file, doc);
|
|
7930
|
-
return { vendoredRefs: [], excludedRefs };
|
|
7931
|
-
}
|
|
7932
|
-
const styleClosure = await collectStyleDependencies(sourceProjectDir, escapingStyles.map((s) => s.value));
|
|
7933
|
-
const extensionFiles = escapingExtensions.map((p) => path16.resolve(sourceProjectDir, p.value));
|
|
7934
|
-
const externalFiles = [...new Set([...styleClosure, ...extensionFiles])].filter((f) => escapesProjectRoot(sourceProjectDir, f));
|
|
7935
|
-
const base = commonAncestorDir(externalFiles);
|
|
7936
|
-
const vendorDir = pickVendorDir(templateDir, base);
|
|
7937
|
-
await Promise.all(externalFiles.filter((abs) => existsSync10(abs)).map(async (abs) => {
|
|
7938
|
-
const dest = path16.join(templateDir, vendorDir, path16.relative(base, abs));
|
|
7939
|
-
await mkdir8(path16.dirname(dest), { recursive: true });
|
|
7940
|
-
await cp2(abs, dest, { recursive: true });
|
|
7941
|
-
}));
|
|
7942
|
-
const bookLocal = (value) => toPosixPath(path16.join(vendorDir, path16.relative(base, path16.resolve(sourceProjectDir, value))));
|
|
7943
|
-
const vendoredRefs = [];
|
|
7944
|
-
for (const { node, value } of escapingStyles) {
|
|
7945
|
-
const rewritten = bookLocal(value);
|
|
7946
|
-
rewriteEntry(node, rewritten);
|
|
7947
|
-
vendoredRefs.push(rewritten);
|
|
7948
|
-
}
|
|
7949
|
-
for (const { node, value } of escapingExtensions) {
|
|
7950
|
-
const rewritten = bookLocal(value);
|
|
7951
|
-
rewriteEntry(node, `./${rewritten}`);
|
|
7952
|
-
vendoredRefs.push(rewritten);
|
|
7953
|
-
}
|
|
7954
|
-
await writeManifestDoc(file, doc);
|
|
7955
|
-
return { vendoredRefs, excludedRefs: [] };
|
|
7956
|
-
}
|
|
7957
|
-
async function saveProjectAsTemplate(options) {
|
|
7958
|
-
const { projectDir, name, templatesRoot } = options;
|
|
7959
|
-
const id = slugify(name);
|
|
7960
|
-
if (!id)
|
|
7961
|
-
throw new Error(`Could not derive a template id from "${name}".`);
|
|
7962
|
-
const dir = path16.join(templatesRoot, id);
|
|
7963
|
-
let exists = true;
|
|
7964
|
-
try {
|
|
7965
|
-
await access3(dir, FS3.F_OK);
|
|
7966
|
-
} catch {
|
|
7967
|
-
exists = false;
|
|
7968
|
-
}
|
|
7969
|
-
if (exists) {
|
|
7970
|
-
throw new Error(`A template named "${name}" already exists. Choose a different name.`);
|
|
7971
|
-
}
|
|
7972
|
-
await mkdir8(dir, { recursive: true });
|
|
7973
|
-
const entries = await readdir7(projectDir, { withFileTypes: true });
|
|
7974
|
-
for (const entry of entries) {
|
|
7975
|
-
if (SKIP_ENTRIES.has(entry.name))
|
|
7976
|
-
continue;
|
|
7977
|
-
if (entry.name === TEMPLATE_META_FILE)
|
|
7978
|
-
continue;
|
|
7979
|
-
await cp2(path16.join(projectDir, entry.name), path16.join(dir, entry.name), { recursive: true });
|
|
7980
|
-
}
|
|
7981
|
-
const manifestName = MANIFEST_FILENAMES.find((name2) => entries.some((entry) => entry.name === name2));
|
|
7982
|
-
const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
|
|
7983
|
-
if (manifestName)
|
|
7984
|
-
await retokeniseManifest(path16.join(dir, manifestName));
|
|
7985
|
-
await writeFile10(path16.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
|
|
7986
|
-
return {
|
|
7987
|
-
id,
|
|
7988
|
-
label: name,
|
|
7989
|
-
description: "Your saved template.",
|
|
7990
|
-
kind: "custom",
|
|
7991
|
-
dir,
|
|
7992
|
-
...shared
|
|
7993
|
-
};
|
|
7994
|
-
}
|
|
7995
|
-
async function retokeniseManifest(manifestPath) {
|
|
7996
|
-
let text;
|
|
7997
|
-
try {
|
|
7998
|
-
text = await readFile27(manifestPath, "utf8");
|
|
7999
|
-
} catch {
|
|
8000
|
-
return;
|
|
8001
|
-
}
|
|
8002
|
-
text = text.replace(/^title:\s*.*$/m, 'title: "{{TITLE}}"').replace(/^(authors:[^\n]*\n[ \t]*-[ \t]*).*$/m, '$1"{{AUTHOR}}"');
|
|
8003
|
-
await writeFile10(manifestPath, text, "utf8");
|
|
8004
|
-
}
|
|
8005
|
-
async function listCustomTemplates(templatesRoot) {
|
|
8006
|
-
let entries;
|
|
8007
|
-
try {
|
|
8008
|
-
entries = await readdir7(templatesRoot, { withFileTypes: true });
|
|
8009
|
-
} catch {
|
|
8010
|
-
return [];
|
|
8011
|
-
}
|
|
8012
|
-
const out = [];
|
|
8013
|
-
for (const entry of entries) {
|
|
8014
|
-
if (!entry.isDirectory())
|
|
8015
|
-
continue;
|
|
8016
|
-
const dir = path16.join(templatesRoot, entry.name);
|
|
8017
|
-
let label = prettify(entry.name);
|
|
8018
|
-
try {
|
|
8019
|
-
const meta = JSON.parse(await readFile27(path16.join(dir, TEMPLATE_META_FILE), "utf8"));
|
|
8020
|
-
if (typeof meta.label === "string" && meta.label)
|
|
8021
|
-
label = meta.label;
|
|
8022
|
-
} catch {}
|
|
8023
|
-
out.push({
|
|
8024
|
-
id: entry.name,
|
|
8025
|
-
label,
|
|
8026
|
-
description: "Your saved template.",
|
|
8027
|
-
kind: "custom",
|
|
8028
|
-
dir,
|
|
8029
|
-
...await readTemplateChoices(path16.join(dir, MANIFEST_FILENAMES[0]))
|
|
8030
|
-
});
|
|
8031
|
-
}
|
|
8032
|
-
out.sort((a, b) => a.label.localeCompare(b.label));
|
|
8033
|
-
return out;
|
|
8034
|
-
}
|
|
8035
|
-
async function importTemplateFromFolder(options) {
|
|
8036
|
-
const { sourceDir, templatesRoot } = options;
|
|
8037
|
-
const srcStat = await stat5(sourceDir);
|
|
8038
|
-
if (!srcStat.isDirectory()) {
|
|
8039
|
-
throw new Error("The chosen path is not a folder.");
|
|
8040
|
-
}
|
|
8041
|
-
const name = options.name?.trim() || prettify(path16.basename(sourceDir));
|
|
8042
|
-
return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
|
|
8043
|
-
}
|
|
8044
8061
|
// src/lib/snippets.ts
|
|
8045
8062
|
import { readdir as readdir8, readFile as readFile28, writeFile as writeFile11, mkdir as mkdir9, rm } from "node:fs/promises";
|
|
8046
8063
|
import path17 from "node:path";
|
|
@@ -12629,7 +12646,7 @@ async function resolvePublishProvider(id, book) {
|
|
|
12629
12646
|
const ext = await bookExtensions(book);
|
|
12630
12647
|
const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
|
|
12631
12648
|
if (npmEntry) {
|
|
12632
|
-
const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-
|
|
12649
|
+
const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-jvs60x9b.js");
|
|
12633
12650
|
await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
|
|
12634
12651
|
}
|
|
12635
12652
|
const publishers = await discover(ext);
|
|
@@ -12667,7 +12684,7 @@ function infoFor(p) {
|
|
|
12667
12684
|
}
|
|
12668
12685
|
function pluginProvider(p) {
|
|
12669
12686
|
let loading;
|
|
12670
|
-
const load = () => loading ??= import("./plugins-
|
|
12687
|
+
const load = () => loading ??= import("./plugins-gf0gkjmv.js").then((m) => m.loadPublishFunction(p));
|
|
12671
12688
|
return {
|
|
12672
12689
|
info: infoFor(p),
|
|
12673
12690
|
async authenticate() {
|
|
@@ -13064,4 +13081,4 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
|
|
|
13064
13081
|
await writeManifestDoc(file, doc);
|
|
13065
13082
|
return settingsFromDoc(doc);
|
|
13066
13083
|
}
|
|
13067
|
-
export { SYNC_SNAPSHOT_MESSAGE, resolveActiveStyles, listProjectStyles, getChecks, getCheckById, resolveCheckSelectors, formatReport, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, readPageSetup, setPageSetup,
|
|
13084
|
+
export { SYNC_SNAPSHOT_MESSAGE, resolveActiveStyles, listProjectStyles, getChecks, getCheckById, resolveCheckSelectors, formatReport, ruleRemoteUrls, ruleRiskyProps, ruleSyntax, checkCss, inspectImage, runChecks, checkToolAvailability, reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, defaultConfigDir, FileTokenStore, extractUrlCredential, getSystemDiagnostics, readPageSetup, setPageSetup, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, SNIPPETS_DIR, extractVariables, substituteVariables, listSnippets, saveSnippet, deleteSnippet, listMergedSnippets, listMarkerComponents, listLocationVariants, importExtensionFromFile, importExtensionFromUrl, MAX_SEARCH_BYTES, SEARCH_KEYWORDS, searchNpmExtensions, setManifestFields, readManifestFields, setActiveStyles, VERSION_CHANGES_SHOWN, describeVersions, listOnlineVersions, compareVersionFile, exportVersionTree, 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, syncProject, refreshRemoteCopies, checkRemoteUpdates, isValidRepositoryName, setUpOnlineBackup, sanitizeCloneFolderName, cloneRepository, isSshRemoteUrl, testRemoteAccess, knownForgeTokenUrl, connectGenericHost, parseRemoteOrigin, forgeKindForHost, diagnoseProjectRemote, REPAIR_SNAPSHOT_MESSAGE, repairOnlineBackup, scorchedEarth, listPublishProviders, publishProviderFor, listBookPublishProviders, resolvePublishProvider, resolvePublishFormat, resolvePublishRequest, runPublish, connectPublishProvider, disconnectPublishCredential, PublishSelectionsStore, readPublishSettings, setPublishProviderConfig };
|