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.
Files changed (57) hide show
  1. package/README.md +5 -5
  2. package/dist/{README-3nj919rm.md → README-8ef336pp.md} +1 -1
  3. package/dist/api/index.js +4 -4
  4. package/dist/{build-53bw14h7.js → build-09cd1n7s.js} +8 -8
  5. package/dist/{chapter-01-rkpyvjna.md → chapter-01-gsbjt584.md} +1 -1
  6. package/dist/chapter-01-s71xrf5q.md +69 -0
  7. package/dist/{chapter-01-7jwqg1ct.md → chapter-01-wgjfz4w6.md} +1 -1
  8. package/dist/checks/source/character-table.d.ts +12 -6
  9. package/dist/{cli-ke86zsz6.js → cli-5hkmp2wy.js} +70 -40
  10. package/dist/{cli-kx4hbnx2.js → cli-b0vyjvmt.js} +1 -1
  11. package/dist/{cli-1hxxwbaq.js → cli-ccp7sxz3.js} +1 -1
  12. package/dist/{cli-zj5xaepv.js → cli-dd115fa8.js} +22 -6
  13. package/dist/{cli-8kg0wjdt.js → cli-dtckwda1.js} +1 -1
  14. package/dist/{cli-1ngqzg8s.js → cli-fe43evmf.js} +21 -5
  15. package/dist/{cli-ntwqmdnw.js → cli-h17e3mtz.js} +462 -4
  16. package/dist/{cli-cjftezxr.js → cli-nr6j6300.js} +2 -1
  17. package/dist/cli.js +12 -12
  18. package/dist/{doctor-xka1frnx.js → doctor-nddspx4n.js} +3 -3
  19. package/dist/{engine-p93f06gj.js → engine-892swzzc.js} +2 -2
  20. package/dist/{engine-0ndvy8vj.js → engine-yjzknt0g.js} +2 -2
  21. package/dist/{ext-dwfqyy1j.js → ext-zjpen4yx.js} +9 -9
  22. package/dist/{extension-restore-7vp3vh77.js → extension-restore-jvs60x9b.js} +3 -3
  23. package/dist/{extension-restore-ct1wk344.js → extension-restore-w8xt4kbq.js} +5 -5
  24. package/dist/{index-53nrm5j1.js → index-0w2azsk1.js} +1 -1
  25. package/dist/{index-4v7e621e.js → index-2jpk25en.js} +464 -5
  26. package/dist/{index-vay3ff1f.js → index-dsvk7q6s.js} +21 -5
  27. package/dist/{index-6cmn2wpd.js → index-m4hhzg3c.js} +21 -5
  28. package/dist/{index-kh7fayjv.js → index-zrhyb967.js} +328 -311
  29. package/dist/index.js +5 -5
  30. package/dist/lib/build-runner.d.ts +1 -1
  31. package/dist/lib/extension-manager.d.ts +1 -1
  32. package/dist/lib/extension-specifier.d.ts +1 -1
  33. package/dist/lib/markdown/assemble.d.ts +7 -1
  34. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  35. package/dist/lib/markdown/index.d.ts +4 -0
  36. package/dist/lib/markdown/math.d.ts +43 -0
  37. package/dist/lib/markdown/renderer.d.ts +11 -8
  38. package/dist/lib/markdown/table.d.ts +46 -0
  39. package/dist/lib/markdown/typography.d.ts +12 -0
  40. package/dist/lib/presets.d.ts +1 -1
  41. package/dist/lib/project-scaffold.d.ts +1 -1
  42. package/dist/lib/project-templates.d.ts +9 -1
  43. package/dist/manifest-8f10dr4f.yaml +17 -0
  44. package/dist/{manifest.schema-399bx3vc.json → manifest.schema-9aeaered.json} +8 -1
  45. package/dist/{new-1pawj2qf.js → new-xhq8wczn.js} +8 -8
  46. package/dist/package-zttkgydq.json +10 -0
  47. package/dist/{plugins-fr4fnzhb.js → plugins-9en8m9y0.js} +3 -3
  48. package/dist/{plugins-g4dgw7mn.js → plugins-gf0gkjmv.js} +1 -1
  49. package/dist/{preflight-ept9q57t.js → preflight-negnxg7b.js} +8 -8
  50. package/dist/preview/file-watcher.d.ts +1 -0
  51. package/dist/{preview-fxpnjv7k.js → preview-cep2s9cg.js} +8 -8
  52. package/dist/{publish-zhqqsgmr.js → publish-xery6qc6.js} +8 -8
  53. package/dist/render.js +470 -11
  54. package/dist/schema/manifest.types.d.ts +8 -0
  55. package/dist/theme-9c58td2d.css +76 -0
  56. package/dist/{validate-t68gvckq.js → validate-3d0sgf4g.js} +8 -8
  57. 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-6cmn2wpd.js";
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-4v7e621e.js";
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-vay3ff1f.js";
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="en"${printAttrs}>
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, plus any unknown gp-* class and any other class no stylesheet mentions",
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\\u2019\\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)", "gu");
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: ["Opening curly quote", "typographer", '"'],
4169
- 8221: ["Closing curly quote", "typographer", '"'],
4170
- 8216: ["Opening curly single quote", "typographer", "'"],
4171
- 8217: ["Curly apostrophe", "typographer", "'"],
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 — skipping: ${affected.join(", ")}`);
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-0ndvy8vj.js");
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 mkdir6, readdir as readdir6, readFile as readFile25, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
7213
- import { constants as FS, existsSync as existsSync9 } from "node:fs";
7214
- import path14 from "node:path";
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 || !path14.isAbsolute(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 access(parentDir, FS.W_OK);
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 = path14.join(parentDir, folderName);
7709
+ const projectDir = path15.join(parentDir, folderName);
7455
7710
  let targetExists = true;
7456
7711
  try {
7457
- await access(projectDir, FS.F_OK);
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 mkdir6(projectDir, { recursive: true });
7724
+ await mkdir7(projectDir, { recursive: true });
7470
7725
  if (customTemplateDir) {
7471
- const entries = await readdir6(customTemplateDir, { withFileTypes: true });
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 cp(path14.join(customTemplateDir, entry.name), path14.join(projectDir, entry.name), { recursive: true });
7730
+ await cp2(path15.join(customTemplateDir, entry.name), path15.join(projectDir, entry.name), { recursive: true });
7476
7731
  }
7477
- await mkdir6(path14.join(projectDir, "assets"), { recursive: true });
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 mkdir6(path14.join(projectDir, "assets"), { recursive: true });
7482
- await copyFile(tplManifest, path14.join(projectDir, MANIFEST_FILENAMES[0]));
7483
- await copyFile(tplChapter, path14.join(projectDir, "chapter-01.md"));
7484
- await mkdir6(path14.join(projectDir, "styles"), { recursive: true });
7485
- await writeFile8(path14.join(projectDir, "styles", "book.css"), starterOverridesCss(), "utf8");
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) => path14.join(projectDir, name2)).find(existsSync9) ?? path14.join(projectDir, MANIFEST_FILENAMES[0]);
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 = path14.join(projectDir, "chapter-01.md");
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 = path14.join(projectDir, firstSource);
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 ${path14.basename(enclosingRepoDir)}'s shared version history; save a snapshot to record it.` : e instanceof Error ? e.message : String(e);
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" || !path14.isAbsolute(dir)) {
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 stat4(dir);
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) => existsSync9(path14.join(dir, 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(path14.basename(dir));
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 readdir6(dir, { withFileTypes: true });
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, path14.join(dir, "chapter-01.md"));
7581
- await fillTemplateFile(path14.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
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 mkdir6(path14.join(dir, "styles"), { recursive: true });
7585
- const bookCssPath = path14.join(dir, "styles", "book.css");
7586
- if (!existsSync9(bookCssPath)) {
7587
- await writeFile8(bookCssPath, starterOverridesCss(), "utf8");
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 writeFile8(path14.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
7604
- await mkdir6(path14.join(dir, "assets"), { recursive: true });
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: path14.join(dir, MANIFEST_FILENAMES[0]),
7632
- openFile: path14.join(dir, mdFiles[0]),
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 readFile25(manifestPath, "utf8");
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 readFile25(filePath, "utf8");
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 writeFile8(filePath, text, "utf8");
7916
+ await writeFile9(filePath, text, "utf8");
7659
7917
  }
7660
7918
  // src/lib/extension-scaffold.ts
7661
- import { access as access2, mkdir as mkdir7, readFile as readFile26, writeFile as writeFile9 } from "node:fs/promises";
7662
- import { constants as FS2 } from "node:fs";
7663
- import path15 from "node:path";
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 || !path15.isAbsolute(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 access2(parentDir, FS2.W_OK);
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 = path15.join(parentDir, slug);
8007
+ const extensionDir = path16.join(parentDir, slug);
7750
8008
  let targetExists = true;
7751
8009
  try {
7752
- await access2(extensionDir, FS2.F_OK);
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 mkdir7(extensionDir, { recursive: true });
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 = path15.join(extensionDir, relDest);
7777
- await mkdir7(path15.dirname(dest), { recursive: true });
7778
- const text = await readFile26(src, "utf8");
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 writeFile9(dest, substitute(text, table), "utf8");
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: path15.join(extensionDir, "package.json"),
8046
+ manifestPath: path16.join(extensionDir, "package.json"),
7789
8047
  kind,
7790
8048
  slug,
7791
8049
  prefix,
7792
- openFile: path15.join(extensionDir, OPEN_FILE[kind]),
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-7vp3vh77.js");
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-g4dgw7mn.js").then((m) => m.loadPublishFunction(p));
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, slugifyProjectName, escapeYamlScalar, scaffoldProject, adoptFolder, EXTENSION_KINDS, RESERVED_PREFIX, resolveExtensionPrefix, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, listBuiltInTemplates, saveProjectAsTemplate, listCustomTemplates, importTemplateFromFolder, 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 };
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 };