gutterpress 0.11.23-alpha.1 → 0.11.23-alpha.2

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 (62) hide show
  1. package/README.md +21 -22
  2. package/dist/api/index.d.ts +4 -4
  3. package/dist/api/index.js +16 -22
  4. package/dist/{build-09cd1n7s.js → build-encgnv0c.js} +9 -10
  5. package/dist/{cli-habkc5ng.js → cli-a35w7wzg.js} +5 -4
  6. package/dist/{cli-b0vyjvmt.js → cli-aqh6brew.js} +1 -1
  7. package/dist/{cli-ccp7sxz3.js → cli-evg8sbht.js} +1 -1
  8. package/dist/{cli-nr6j6300.js → cli-kh8v4vvw.js} +1 -1
  9. package/dist/{cli-h17e3mtz.js → cli-pwwfxdgb.js} +385 -10
  10. package/dist/{cli-dtckwda1.js → cli-pz4bn98t.js} +185 -9
  11. package/dist/{cli-dd115fa8.js → cli-sdmv4bba.js} +47 -123
  12. package/dist/{cli-5hkmp2wy.js → cli-xq6djqbj.js} +55 -88
  13. package/dist/{index-r7jcsk6h.js → cli-y28j019d.js} +19 -10
  14. package/dist/cli.js +12 -12
  15. package/dist/{doctor-nddspx4n.js → doctor-5gtpgpd1.js} +3 -3
  16. package/dist/{engine-892swzzc.js → engine-mc5br8g8.js} +1 -2
  17. package/dist/{engine-yjzknt0g.js → engine-tn13gk0b.js} +3 -3
  18. package/dist/{ext-zjpen4yx.js → ext-wn2a5afq.js} +18 -33
  19. package/dist/{extension-restore-w8xt4kbq.js → extension-restore-g8jnmmzq.js} +5 -6
  20. package/dist/{extension-restore-jvs60x9b.js → extension-restore-s3x1618g.js} +4 -4
  21. package/dist/{index-apc8w9w8.js → index-0skhjx3e.js} +180 -1
  22. package/dist/{index-2jpk25en.js → index-2bjcghze.js} +385 -10
  23. package/dist/index-46ycxe6r.js +18 -0
  24. package/dist/{index-0w2azsk1.js → index-4x6y8krm.js} +2 -4
  25. package/dist/{cli-h3phdstf.js → index-5x42amh8.js} +19 -10
  26. package/dist/{index-93pqkvty.js → index-h6yk5tjx.js} +5 -4
  27. package/dist/{index-zrhyb967.js → index-j5z86r4a.js} +335 -349
  28. package/dist/{index-m4hhzg3c.js → index-x3ema2w3.js} +59 -134
  29. package/dist/index.js +17 -23
  30. package/dist/lib/extension-manager.d.ts +16 -43
  31. package/dist/lib/extension-scaffold.d.ts +3 -3
  32. package/dist/lib/extension-specifier.d.ts +23 -6
  33. package/dist/lib/markdown/math.d.ts +1 -1
  34. package/dist/lib/npm-plugin-installer.d.ts +1 -1
  35. package/dist/lib/plugin-vendor.d.ts +11 -3
  36. package/dist/lib/project-gitignore.d.ts +2 -2
  37. package/dist/lib/project-scaffold.d.ts +5 -7
  38. package/dist/lib/project-templates.d.ts +0 -3
  39. package/dist/lib/snippets.d.ts +2 -1
  40. package/dist/lib/style-resolver.d.ts +1 -2
  41. package/dist/{manifest-8f10dr4f.yaml → manifest-eagfhba6.yaml} +2 -0
  42. package/dist/{manifest.schema-9aeaered.json → manifest.schema-t7nx2stg.json} +5 -5
  43. package/dist/{new-xhq8wczn.js → new-mhs46pw3.js} +22 -21
  44. package/dist/{plugins-9en8m9y0.js → plugins-1sxe0dkz.js} +4 -4
  45. package/dist/{plugins-gf0gkjmv.js → plugins-2cek4mz1.js} +2 -2
  46. package/dist/{preflight-negnxg7b.js → preflight-3k2b1x8e.js} +9 -10
  47. package/dist/{preview-cep2s9cg.js → preview-6p9v6rht.js} +9 -10
  48. package/dist/{publish-xery6qc6.js → publish-qgsngy8s.js} +9 -10
  49. package/dist/{source-provider-qbfknhyy.js → source-provider-98k5deda.js} +2 -2
  50. package/dist/{source-provider-2r74a7wp.js → source-provider-knmcam50.js} +2 -2
  51. package/dist/{validate-3d0sgf4g.js → validate-0v7xw6wa.js} +9 -10
  52. package/package.json +1 -1
  53. package/dist/cli-fe43evmf.js +0 -212
  54. package/dist/index-dsvk7q6s.js +0 -229
  55. package/dist/package-97gm31sg.json +0 -10
  56. package/dist/package-gag6ykwn.json +0 -10
  57. package/dist/package-nrhkwf2m.json +0 -10
  58. package/dist/package-zttkgydq.json +0 -10
  59. package/dist/theme-9c58td2d.css +0 -76
  60. package/dist/theme-h5recz6c.css +0 -61
  61. package/dist/theme-j2bagrfx.css +0 -59
  62. package/dist/theme-nn6d53zy.css +0 -71
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  DEBOUNCE,
3
- EXTENSIONS_DIR,
4
3
  FriendlyHttpError,
5
4
  MANIFEST_FILENAMES,
6
5
  PRESETS,
@@ -8,13 +7,11 @@ import {
8
7
  TARGETS,
9
8
  TARGET_IDS,
10
9
  UsageError,
11
- addBuiltInStyleSet,
12
10
  addExtension,
13
11
  applyDefaultPdfStrictChecks,
14
12
  ensureProjectGitignore,
15
13
  ensureSeq,
16
14
  extensionConfigFor,
17
- isPathSpecifier,
18
15
  listProjectExtensions,
19
16
  loadManifest,
20
17
  loadManifestDoc,
@@ -30,7 +27,7 @@ import {
30
27
  uniqueExtensionId,
31
28
  withFetchTimeout,
32
29
  writeManifestDoc
33
- } from "./index-m4hhzg3c.js";
30
+ } from "./index-x3ema2w3.js";
34
31
  import {
35
32
  EXTENSION_MANIFEST_FILENAME,
36
33
  GUTTERPRESS_CSS,
@@ -52,6 +49,7 @@ import {
52
49
  extensionEntry,
53
50
  extensionStyleList,
54
51
  info,
52
+ isPathSpecifier,
55
53
  loadPlugin,
56
54
  loadPlugins,
57
55
  loadPluginsWithCss,
@@ -65,21 +63,21 @@ import {
65
63
  setLogLevel,
66
64
  slugify,
67
65
  warn
68
- } from "./index-2jpk25en.js";
66
+ } from "./index-2bjcghze.js";
69
67
  import {
70
68
  blackTextToGray
71
69
  } from "./index-ht1bcqdd.js";
72
70
  import {
73
71
  MARGIN_BOX_IGNORED_PROPERTIES,
74
72
  extract,
73
+ getAssetPath,
75
74
  lastPageMode,
76
75
  parseSize,
77
76
  resolvePage
78
- } from "./index-apc8w9w8.js";
77
+ } from "./index-0skhjx3e.js";
79
78
  import {
80
- BuildError,
81
- getAssetPath
82
- } from "./index-dsvk7q6s.js";
79
+ BuildError
80
+ } from "./index-46ycxe6r.js";
83
81
  import {
84
82
  countLinkAnnotations,
85
83
  getImageResolutions,
@@ -117,12 +115,13 @@ import {
117
115
  snapshotStagingMarkerPath,
118
116
  snapshotWorkingTreeUnlocked,
119
117
  withRepoLock
120
- } from "./index-93pqkvty.js";
118
+ } from "./index-h6yk5tjx.js";
121
119
  import {
120
+ EXTENSIONS_DIR,
122
121
  resolveVendoredPluginInstallRoot,
123
122
  toPosixPath,
124
123
  vendoredNpmPluginPackageDir
125
- } from "./index-r7jcsk6h.js";
124
+ } from "./index-5x42amh8.js";
126
125
  import {
127
126
  detectProjectSource,
128
127
  repoSubPath,
@@ -364,13 +363,7 @@ import { existsSync } from "node:fs";
364
363
  import { readdir } from "node:fs/promises";
365
364
  import path from "node:path";
366
365
  var STYLES_SUBDIR = "styles";
367
- var FALLBACK_PRIORITY = [
368
- "styles/book.css",
369
- "css/print.css",
370
- "css/index.css",
371
- "css/style.css",
372
- "css/main.css"
373
- ];
366
+ var DEFAULT_STYLESHEET = "styles/book.css";
374
367
  function relDisplay(projectDir, absPath) {
375
368
  return path.relative(projectDir, absPath).split(path.sep).join("/");
376
369
  }
@@ -408,10 +401,8 @@ async function resolveActiveStyles(projectDir, manifestStyles) {
408
401
  const manifest = configured.filter((s) => typeof s === "string" && s.trim().length > 0);
409
402
  if (manifest.length > 0)
410
403
  return manifest;
411
- for (const rel of FALLBACK_PRIORITY) {
412
- if (existsSync(path.join(projectDir, rel)))
413
- return [rel];
414
- }
404
+ if (existsSync(path.join(projectDir, DEFAULT_STYLESHEET)))
405
+ return [DEFAULT_STYLESHEET];
415
406
  const discovered = await discoverCssFiles(projectDir);
416
407
  if (discovered.length > 0) {
417
408
  const first = discovered.map((abs) => relDisplay(projectDir, abs)).sort((a, b) => a.localeCompare(b))[0];
@@ -5678,7 +5669,7 @@ class PdfOutput {
5678
5669
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5679
5670
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5680
5671
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5681
- const { buildNativePdf } = await import("./engine-yjzknt0g.js");
5672
+ const { buildNativePdf } = await import("./engine-tn13gk0b.js");
5682
5673
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5683
5674
  title: config.title,
5684
5675
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -6029,7 +6020,7 @@ function isGeneratedProjectPath(relative) {
6029
6020
  const segment = segments[i];
6030
6021
  if (segment === "dist" || segment === "node_modules")
6031
6022
  return true;
6032
- if (segment === "plugins" && segments[i + 1] === "npm")
6023
+ if ((segment === "extensions" || segment === "plugins") && segments[i + 1] === "npm")
6033
6024
  return true;
6034
6025
  }
6035
6026
  return false;
@@ -7215,9 +7206,9 @@ async function getSystemDiagnostics() {
7215
7206
  };
7216
7207
  }
7217
7208
  // src/lib/project-scaffold.ts
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";
7209
+ import { access, copyFile, cp, mkdir as mkdir6, readdir as readdir6, readFile as readFile25, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
7210
+ import { constants as FS, existsSync as existsSync9 } from "node:fs";
7211
+ import path14 from "node:path";
7221
7212
 
7222
7213
  // src/lib/page-size.ts
7223
7214
  import { existsSync as existsSync8 } from "node:fs";
@@ -7379,265 +7370,16 @@ async function setPageSetup(projectDir, choice) {
7379
7370
  return readPageSetup(projectDir);
7380
7371
  }
7381
7372
 
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
-
7630
7373
  // src/lib/project-scaffold.ts
7631
7374
  function starterOverridesCss() {
7632
7375
  return `/**
7633
7376
  * Your book's own styles.
7634
7377
  *
7635
7378
  * This file loads AFTER every extension, so any rule you add here overrides
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):
7638
- * gutterpress ext add clean-book <book> --look (a built-in look)
7639
- * gutterpress ext add <package> <book> (a look from npm)
7640
- * A book has one look at a time; adding another turns the current one off.
7379
+ * a look's defaults. A new book has no look until its template or you add
7380
+ * one:
7381
+ * gutterpress ext add academic <book> (a built-in look)
7382
+ * gutterpress ext add <package> <book> (a look from npm)
7641
7383
  */
7642
7384
  `;
7643
7385
  }
@@ -7658,9 +7400,8 @@ function escapeYamlScalar(value) {
7658
7400
  }
7659
7401
  var DEFAULT_AUTHOR = "Anonymous";
7660
7402
  function requirePreset(preset, customPage) {
7661
- if (!preset || !PRESET_IDS.includes(preset)) {
7662
- const got = preset ? ` (got "${preset}")` : "";
7663
- throw new CreateProjectErrorImpl("preset-required", `A preset is required to create a book${got}. Choose one of: ${PRESET_IDS.join(", ")}.`);
7403
+ if (!PRESET_IDS.includes(preset)) {
7404
+ throw new CreateProjectErrorImpl("preset-required", `Unknown preset "${preset}". Choose one of: ${PRESET_IDS.join(", ")}.`);
7664
7405
  }
7665
7406
  if (preset === "custom") {
7666
7407
  const missing = [];
@@ -7698,18 +7439,18 @@ async function scaffoldProject(options) {
7698
7439
  throw new CreateProjectErrorImpl("invalid-name", `Could not derive a valid folder name from "${name}".`);
7699
7440
  }
7700
7441
  const parentDir = options.parentDir;
7701
- if (!parentDir || !path15.isAbsolute(parentDir)) {
7442
+ if (!parentDir || !path14.isAbsolute(parentDir)) {
7702
7443
  throw new CreateProjectErrorImpl("parent-not-writable", "The save location must be an absolute path.");
7703
7444
  }
7704
7445
  try {
7705
- await access2(parentDir, FS2.W_OK);
7446
+ await access(parentDir, FS.W_OK);
7706
7447
  } catch {
7707
7448
  throw new CreateProjectErrorImpl("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
7708
7449
  }
7709
- const projectDir = path15.join(parentDir, folderName);
7450
+ const projectDir = path14.join(parentDir, folderName);
7710
7451
  let targetExists = true;
7711
7452
  try {
7712
- await access2(projectDir, FS2.F_OK);
7453
+ await access(projectDir, FS.F_OK);
7713
7454
  } catch {
7714
7455
  targetExists = false;
7715
7456
  }
@@ -7718,26 +7459,26 @@ async function scaffoldProject(options) {
7718
7459
  }
7719
7460
  const template = options.template ?? "book";
7720
7461
  const customTemplateDir = options.templateDir;
7721
- const preset = customTemplateDir && options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
7462
+ const preset = options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
7722
7463
  const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
7723
7464
  try {
7724
- await mkdir7(projectDir, { recursive: true });
7465
+ await mkdir6(projectDir, { recursive: true });
7725
7466
  if (customTemplateDir) {
7726
- const entries = await readdir7(customTemplateDir, { withFileTypes: true });
7467
+ const entries = await readdir6(customTemplateDir, { withFileTypes: true });
7727
7468
  for (const entry of entries) {
7728
7469
  if (entry.name === ".gutterpress-template.json")
7729
7470
  continue;
7730
- await cp2(path15.join(customTemplateDir, entry.name), path15.join(projectDir, entry.name), { recursive: true });
7471
+ await cp(path14.join(customTemplateDir, entry.name), path14.join(projectDir, entry.name), { recursive: true });
7731
7472
  }
7732
- await mkdir7(path15.join(projectDir, "assets"), { recursive: true });
7473
+ await mkdir6(path14.join(projectDir, "assets"), { recursive: true });
7733
7474
  } else {
7734
7475
  const tplManifest = await getAssetPath(`templates/${template}/manifest.yaml`);
7735
7476
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
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");
7477
+ await mkdir6(path14.join(projectDir, "assets"), { recursive: true });
7478
+ await copyFile(tplManifest, path14.join(projectDir, MANIFEST_FILENAMES[0]));
7479
+ await copyFile(tplChapter, path14.join(projectDir, "chapter-01.md"));
7480
+ await mkdir6(path14.join(projectDir, "styles"), { recursive: true });
7481
+ await writeFile8(path14.join(projectDir, "styles", "book.css"), starterOverridesCss(), "utf8");
7741
7482
  }
7742
7483
  } catch (e) {
7743
7484
  throw new CreateProjectErrorImpl("scaffold-io", `Could not create the book files: ${e instanceof Error ? e.message : String(e)}`);
@@ -7747,29 +7488,28 @@ async function scaffoldProject(options) {
7747
7488
  } catch (e) {
7748
7489
  throw new CreateProjectErrorImpl("scaffold-io", `Could not write .gitignore: ${e instanceof Error ? e.message : String(e)}`);
7749
7490
  }
7750
- const manifestPath = MANIFEST_FILENAMES.map((name2) => path15.join(projectDir, name2)).find(existsSync10) ?? path15.join(projectDir, MANIFEST_FILENAMES[0]);
7491
+ const manifestPath = MANIFEST_FILENAMES.map((name2) => path14.join(projectDir, name2)).find(existsSync9) ?? path14.join(projectDir, MANIFEST_FILENAMES[0]);
7751
7492
  const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
7752
7493
  const substitutions = {
7753
7494
  "{{TITLE}}": escapeYamlScalar(name),
7754
7495
  "{{AUTHOR}}": escapeYamlScalar(author)
7755
7496
  };
7756
- let openFile = path15.join(projectDir, "chapter-01.md");
7497
+ let openFile = path14.join(projectDir, "chapter-01.md");
7757
7498
  try {
7758
7499
  await fillTemplateFile(manifestPath, substitutions);
7759
- const look = customTemplateDir ? undefined : BUILT_IN_META[template].look;
7760
- if (look)
7761
- await addBuiltInStyleSet(projectDir, look);
7762
- if (preset) {
7500
+ if (preset || options.customPage) {
7763
7501
  const { doc, file } = await loadManifestDoc(projectDir);
7764
- doc.set("preset", preset);
7765
- doc.set("targets", doc.createNode(targets ?? []));
7502
+ if (preset) {
7503
+ doc.set("preset", preset);
7504
+ doc.set("targets", doc.createNode(targets ?? []));
7505
+ }
7766
7506
  if (options.customPage)
7767
7507
  writePageBounds(doc, options.customPage);
7768
7508
  await writeManifestDoc(file, doc);
7769
7509
  }
7770
7510
  const firstSource = await firstSourceFile(manifestPath);
7771
7511
  if (firstSource)
7772
- openFile = path15.join(projectDir, firstSource);
7512
+ openFile = path14.join(projectDir, firstSource);
7773
7513
  await fillTemplateFile(openFile, { "{{TITLE}}": name }).catch(() => {});
7774
7514
  } catch (e) {
7775
7515
  throw new CreateProjectErrorImpl("scaffold-io", `Could not finalize the book files: ${e instanceof Error ? e.message : String(e)}`);
@@ -7779,7 +7519,7 @@ async function scaffoldProject(options) {
7779
7519
  let versionHistoryError;
7780
7520
  if (requested === "local-git") {
7781
7521
  try {
7782
- const { providerFor } = await import("./source-provider-qbfknhyy.js");
7522
+ const { providerFor } = await import("./source-provider-98k5deda.js");
7783
7523
  const provider = providerFor({ type: "local-folder", path: projectDir });
7784
7524
  await provider.initVersionHistory({
7785
7525
  projectDir,
@@ -7791,7 +7531,7 @@ async function scaffoldProject(options) {
7791
7531
  versionHistory = "none";
7792
7532
  const { findEnclosingRepoDir } = await import("./project-source-ekcyp63q.js");
7793
7533
  const enclosingRepoDir = await findEnclosingRepoDir(projectDir);
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);
7534
+ 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);
7795
7535
  }
7796
7536
  }
7797
7537
  const result = {
@@ -7811,38 +7551,38 @@ function prettifyFolderName(base) {
7811
7551
  }
7812
7552
  async function adoptFolder(options) {
7813
7553
  const dir = options.dir;
7814
- if (typeof dir !== "string" || !path15.isAbsolute(dir)) {
7554
+ if (typeof dir !== "string" || !path14.isAbsolute(dir)) {
7815
7555
  throw new CreateProjectErrorImpl("scaffold-io", "An absolute folder path is required.");
7816
7556
  }
7817
7557
  let st;
7818
7558
  try {
7819
- st = await stat5(dir);
7559
+ st = await stat4(dir);
7820
7560
  } catch {
7821
7561
  throw new CreateProjectErrorImpl("scaffold-io", `Folder not found: ${dir}`);
7822
7562
  }
7823
7563
  if (!st.isDirectory()) {
7824
7564
  throw new CreateProjectErrorImpl("scaffold-io", `Not a folder: ${dir}`);
7825
7565
  }
7826
- if (MANIFEST_FILENAMES.some((name) => existsSync10(path15.join(dir, name)))) {
7566
+ if (MANIFEST_FILENAMES.some((name) => existsSync9(path14.join(dir, name)))) {
7827
7567
  throw new CreateProjectErrorImpl("target-exists", "This folder is already a Gutterpress book.");
7828
7568
  }
7829
7569
  const template = options.template ?? "book";
7830
- const title = (options.title ?? "").trim() || prettifyFolderName(path15.basename(dir));
7570
+ const title = (options.title ?? "").trim() || prettifyFolderName(path14.basename(dir));
7831
7571
  const author = (options.author ?? "").trim() || DEFAULT_AUTHOR;
7832
7572
  let mdFiles;
7833
7573
  try {
7834
- const entries = await readdir7(dir, { withFileTypes: true });
7574
+ const entries = await readdir6(dir, { withFileTypes: true });
7835
7575
  mdFiles = entries.filter((e) => e.isFile() && /\.(md|markdown)$/i.test(e.name)).map((e) => e.name).sort((a, b) => a.localeCompare(b));
7836
7576
  if (mdFiles.length === 0) {
7837
7577
  const tplChapter = await getAssetPath(`templates/${template}/chapter-01.md`);
7838
- await copyFile(tplChapter, path15.join(dir, "chapter-01.md"));
7839
- await fillTemplateFile(path15.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
7578
+ await copyFile(tplChapter, path14.join(dir, "chapter-01.md"));
7579
+ await fillTemplateFile(path14.join(dir, "chapter-01.md"), { "{{TITLE}}": title }).catch(() => {});
7840
7580
  mdFiles = ["chapter-01.md"];
7841
7581
  }
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");
7582
+ await mkdir6(path14.join(dir, "styles"), { recursive: true });
7583
+ const bookCssPath = path14.join(dir, "styles", "book.css");
7584
+ if (!existsSync9(bookCssPath)) {
7585
+ await writeFile8(bookCssPath, starterOverridesCss(), "utf8");
7846
7586
  }
7847
7587
  const filesYaml = mdFiles.map((f) => ` - "${escapeYamlScalar(f)}"`).join(`
7848
7588
  `);
@@ -7858,8 +7598,8 @@ ${filesYaml}
7858
7598
  styles:
7859
7599
  - styles/book.css
7860
7600
  `;
7861
- await writeFile9(path15.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
7862
- await mkdir7(path15.join(dir, "assets"), { recursive: true });
7601
+ await writeFile8(path14.join(dir, MANIFEST_FILENAMES[0]), manifest, "utf8");
7602
+ await mkdir6(path14.join(dir, "assets"), { recursive: true });
7863
7603
  await ensureProjectGitignore(dir);
7864
7604
  } catch (e) {
7865
7605
  if (e instanceof CreateProjectErrorImpl)
@@ -7871,7 +7611,7 @@ styles:
7871
7611
  let versionHistoryError;
7872
7612
  if (requested === "local-git") {
7873
7613
  try {
7874
- const { providerFor } = await import("./source-provider-qbfknhyy.js");
7614
+ const { providerFor } = await import("./source-provider-98k5deda.js");
7875
7615
  const provider = providerFor({ type: "local-folder", path: dir });
7876
7616
  await provider.initVersionHistory({
7877
7617
  projectDir: dir,
@@ -7886,8 +7626,8 @@ styles:
7886
7626
  }
7887
7627
  const result = {
7888
7628
  projectDir: dir,
7889
- manifestPath: path15.join(dir, MANIFEST_FILENAMES[0]),
7890
- openFile: path15.join(dir, mdFiles[0]),
7629
+ manifestPath: path14.join(dir, MANIFEST_FILENAMES[0]),
7630
+ openFile: path14.join(dir, mdFiles[0]),
7891
7631
  versionHistory
7892
7632
  };
7893
7633
  if (versionHistoryError !== undefined)
@@ -7897,7 +7637,7 @@ styles:
7897
7637
  async function firstSourceFile(manifestPath) {
7898
7638
  let text;
7899
7639
  try {
7900
- text = await readFile26(manifestPath, "utf8");
7640
+ text = await readFile25(manifestPath, "utf8");
7901
7641
  } catch {
7902
7642
  return;
7903
7643
  }
@@ -7909,17 +7649,17 @@ async function firstSourceFile(manifestPath) {
7909
7649
  return m ? m[1].trim() : undefined;
7910
7650
  }
7911
7651
  async function fillTemplateFile(filePath, substitutions) {
7912
- let text = await readFile26(filePath, "utf8");
7652
+ let text = await readFile25(filePath, "utf8");
7913
7653
  for (const [token, value] of Object.entries(substitutions)) {
7914
7654
  text = text.split(token).join(value);
7915
7655
  }
7916
- await writeFile9(filePath, text, "utf8");
7656
+ await writeFile8(filePath, text, "utf8");
7917
7657
  }
7918
7658
  // src/lib/extension-scaffold.ts
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";
7922
- var EXTENSION_KINDS = ["plugin", "theme"];
7659
+ import { access as access2, mkdir as mkdir7, readFile as readFile26, writeFile as writeFile9 } from "node:fs/promises";
7660
+ import { constants as FS2 } from "node:fs";
7661
+ import path15 from "node:path";
7662
+ var EXTENSION_KINDS = ["plugin", "look"];
7923
7663
  var RESERVED_PREFIX = "gp-";
7924
7664
  var TEMPLATE_FILES = {
7925
7665
  plugin: [
@@ -7932,7 +7672,7 @@ var TEMPLATE_FILES = {
7932
7672
  ["test/expected.html", "test/expected.html"],
7933
7673
  ["test/plugin.test.js.tpl", "test/plugin.test.js"]
7934
7674
  ],
7935
- theme: [
7675
+ look: [
7936
7676
  ["package.json", "package.json"],
7937
7677
  ["README.md", "README.md"],
7938
7678
  ["components.yaml", "components.yaml"],
@@ -7947,11 +7687,11 @@ var TEMPLATE_FILES = {
7947
7687
  };
7948
7688
  var OPEN_FILE = {
7949
7689
  plugin: "plugin.js",
7950
- theme: "styles/tokens.css"
7690
+ look: "styles/tokens.css"
7951
7691
  };
7952
7692
  var DEFAULT_DESCRIPTION = {
7953
7693
  plugin: "A Gutterpress plugin.",
7954
- theme: "A Gutterpress theme."
7694
+ look: "A Gutterpress look."
7955
7695
  };
7956
7696
  var DEFAULT_AUTHOR2 = "Anonymous";
7957
7697
 
@@ -7983,7 +7723,7 @@ function escapeJsonScalar(value) {
7983
7723
  }
7984
7724
  async function scaffoldExtension(options) {
7985
7725
  const kind = options.kind;
7986
- if (kind !== "plugin" && kind !== "theme") {
7726
+ if (kind !== "plugin" && kind !== "look") {
7987
7727
  throw new ExtensionScaffoldError("invalid-name", `Unknown extension kind "${String(kind)}". Choose one of: ${EXTENSION_KINDS.join(", ")}.`);
7988
7728
  }
7989
7729
  const name = (options.name ?? "").trim();
@@ -7996,18 +7736,18 @@ async function scaffoldExtension(options) {
7996
7736
  }
7997
7737
  const prefix = resolveExtensionPrefix(slug, options.prefix);
7998
7738
  const parentDir = options.parentDir;
7999
- if (!parentDir || !path16.isAbsolute(parentDir)) {
7739
+ if (!parentDir || !path15.isAbsolute(parentDir)) {
8000
7740
  throw new ExtensionScaffoldError("parent-not-writable", "The save location must be an absolute path.");
8001
7741
  }
8002
7742
  try {
8003
- await access3(parentDir, FS3.W_OK);
7743
+ await access2(parentDir, FS2.W_OK);
8004
7744
  } catch {
8005
7745
  throw new ExtensionScaffoldError("parent-not-writable", `The chosen save location can't be written to: ${parentDir}`);
8006
7746
  }
8007
- const extensionDir = path16.join(parentDir, slug);
7747
+ const extensionDir = path15.join(parentDir, slug);
8008
7748
  let targetExists = true;
8009
7749
  try {
8010
- await access3(extensionDir, FS3.F_OK);
7750
+ await access2(extensionDir, FS2.F_OK);
8011
7751
  } catch {
8012
7752
  targetExists = false;
8013
7753
  }
@@ -8028,14 +7768,14 @@ async function scaffoldExtension(options) {
8028
7768
  const entries = TEMPLATE_FILES[kind];
8029
7769
  const written = [];
8030
7770
  try {
8031
- await mkdir8(extensionDir, { recursive: true });
7771
+ await mkdir7(extensionDir, { recursive: true });
8032
7772
  for (const [assetName, relDest] of entries) {
8033
7773
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
8034
- const dest = path16.join(extensionDir, relDest);
8035
- await mkdir8(path16.dirname(dest), { recursive: true });
8036
- const text = await readFile27(src, "utf8");
7774
+ const dest = path15.join(extensionDir, relDest);
7775
+ await mkdir7(path15.dirname(dest), { recursive: true });
7776
+ const text = await readFile26(src, "utf8");
8037
7777
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
8038
- await writeFile10(dest, substitute(text, table), "utf8");
7778
+ await writeFile9(dest, substitute(text, table), "utf8");
8039
7779
  written.push(relDest);
8040
7780
  }
8041
7781
  } catch (e) {
@@ -8043,11 +7783,11 @@ async function scaffoldExtension(options) {
8043
7783
  }
8044
7784
  return {
8045
7785
  extensionDir,
8046
- manifestPath: path16.join(extensionDir, "package.json"),
7786
+ manifestPath: path15.join(extensionDir, "package.json"),
8047
7787
  kind,
8048
7788
  slug,
8049
7789
  prefix,
8050
- openFile: path16.join(extensionDir, OPEN_FILE[kind]),
7790
+ openFile: path15.join(extensionDir, OPEN_FILE[kind]),
8051
7791
  files: written
8052
7792
  };
8053
7793
  }
@@ -8058,6 +7798,252 @@ function substitute(text, table) {
8058
7798
  }
8059
7799
  return out;
8060
7800
  }
7801
+ // src/lib/project-templates.ts
7802
+ import { cp as cp2, mkdir as mkdir8, readFile as readFile27, readdir as readdir7, stat as stat5, writeFile as writeFile10 } from "node:fs/promises";
7803
+ import { constants as FS3, existsSync as existsSync10 } from "node:fs";
7804
+ import { access as access3 } from "node:fs/promises";
7805
+ import path16 from "node:path";
7806
+ import { isMap as isMap2, isScalar, isSeq, parseDocument } from "yaml";
7807
+ var BUILT_IN_TEMPLATE_IDS = [
7808
+ "book",
7809
+ "zine",
7810
+ "technical",
7811
+ "paper"
7812
+ ];
7813
+ async function readTemplateChoices(manifestPath) {
7814
+ try {
7815
+ const text = await readFile27(manifestPath, "utf8");
7816
+ const doc = parseDocument(text);
7817
+ const out = {};
7818
+ const preset = doc.get("preset");
7819
+ if (typeof preset === "string" && preset)
7820
+ out.preset = preset;
7821
+ const targets = doc.get("targets", true);
7822
+ if (isSeq(targets)) {
7823
+ out.targets = targets.items.map((i) => scalarString(i)).filter((s) => s !== null);
7824
+ }
7825
+ return out;
7826
+ } catch {
7827
+ return {};
7828
+ }
7829
+ }
7830
+ var BUILT_IN_META = {
7831
+ book: {
7832
+ label: "Book",
7833
+ description: "A clean starting point for a novel, memoir, or any long-form book."
7834
+ },
7835
+ zine: {
7836
+ label: "Zine",
7837
+ description: "A short, personal zine made to be printed, folded, and shared."
7838
+ },
7839
+ technical: {
7840
+ label: "Technical document",
7841
+ description: "A manual or guide with steps, reference tables, and code examples."
7842
+ },
7843
+ paper: {
7844
+ label: "Academic paper",
7845
+ description: "A journal-style article with LaTeX math, numbered equations, figures, tables, notes and references."
7846
+ }
7847
+ };
7848
+ async function listBuiltInTemplates() {
7849
+ return Promise.all(BUILT_IN_TEMPLATE_IDS.map(async (id) => ({
7850
+ id,
7851
+ label: BUILT_IN_META[id].label,
7852
+ description: BUILT_IN_META[id].description,
7853
+ kind: "builtin",
7854
+ ...await readTemplateChoices(await getAssetPath(`templates/${id}/manifest.yaml`))
7855
+ })));
7856
+ }
7857
+ var SKIP_ENTRIES = new Set([
7858
+ ".git",
7859
+ "node_modules",
7860
+ "out",
7861
+ "dist",
7862
+ "build",
7863
+ ".gutterpress",
7864
+ ".DS_Store"
7865
+ ]);
7866
+ var VENDOR_DIR = "shared";
7867
+ function collectEscaping(nodes, getValue, escapes) {
7868
+ const out = [];
7869
+ for (const node of nodes) {
7870
+ const value = getValue(node);
7871
+ if (value !== null && escapes(value))
7872
+ out.push({ node, value });
7873
+ }
7874
+ return out;
7875
+ }
7876
+ function extensionEntryPath(node) {
7877
+ const use = isMap2(node) ? scalarString(node.get("use", true)) : scalarString(node);
7878
+ return use !== null && isPathSpecifier(use) ? use : null;
7879
+ }
7880
+ function dropEscaping(doc, seq, key, escaping) {
7881
+ if (!isSeq(seq))
7882
+ return;
7883
+ const drop = new Set(escaping.map((e) => e.node));
7884
+ seq.items = seq.items.filter((node) => !drop.has(node));
7885
+ if (seq.items.length === 0)
7886
+ doc.delete(key);
7887
+ }
7888
+ function rewriteEntry(node, newPath) {
7889
+ if (isMap2(node))
7890
+ node.set("use", newPath);
7891
+ else if (isScalar(node))
7892
+ node.value = newPath;
7893
+ }
7894
+ function commonAncestorDir(absFiles) {
7895
+ const dirSegs = absFiles.map((f) => path16.resolve(f).split(path16.sep).slice(0, -1));
7896
+ if (dirSegs.length === 0)
7897
+ return "";
7898
+ let common = dirSegs[0];
7899
+ for (const segs of dirSegs.slice(1)) {
7900
+ let i = 0;
7901
+ while (i < common.length && i < segs.length && common[i] === segs[i])
7902
+ i++;
7903
+ common = common.slice(0, i);
7904
+ }
7905
+ return common.join(path16.sep) || path16.sep;
7906
+ }
7907
+ function pickVendorDir(templateDir, base) {
7908
+ const preferred = path16.basename(base) || VENDOR_DIR;
7909
+ if (!existsSync10(path16.join(templateDir, preferred)))
7910
+ return preferred;
7911
+ for (let n = 1;; n++) {
7912
+ const candidate = `${preferred}-${n}`;
7913
+ if (!existsSync10(path16.join(templateDir, candidate)))
7914
+ return candidate;
7915
+ }
7916
+ }
7917
+ var TEMPLATE_META_FILE = ".gutterpress-template.json";
7918
+ async function reconcileSharedRefs(templateDir, sourceProjectDir, mode) {
7919
+ const { doc, file } = await loadManifestDoc(templateDir);
7920
+ const escapes = (p) => escapesProjectRoot(sourceProjectDir, path16.resolve(sourceProjectDir, p));
7921
+ const stylesSeq = doc.get("styles", true);
7922
+ const extensionsSeq = doc.get("extensions", true);
7923
+ const escapingStyles = collectEscaping(isSeq(stylesSeq) ? stylesSeq.items : [], scalarString, escapes);
7924
+ const escapingExtensions = collectEscaping(isSeq(extensionsSeq) ? extensionsSeq.items : [], extensionEntryPath, escapes);
7925
+ if (escapingStyles.length === 0 && escapingExtensions.length === 0) {
7926
+ return { vendoredRefs: [], excludedRefs: [] };
7927
+ }
7928
+ if (mode === "exclude") {
7929
+ const excludedRefs = [...escapingStyles, ...escapingExtensions].map((e) => e.value);
7930
+ dropEscaping(doc, stylesSeq, "styles", escapingStyles);
7931
+ dropEscaping(doc, extensionsSeq, "extensions", escapingExtensions);
7932
+ await writeManifestDoc(file, doc);
7933
+ return { vendoredRefs: [], excludedRefs };
7934
+ }
7935
+ const styleClosure = await collectStyleDependencies(sourceProjectDir, escapingStyles.map((s) => s.value));
7936
+ const extensionFiles = escapingExtensions.map((p) => path16.resolve(sourceProjectDir, p.value));
7937
+ const externalFiles = [...new Set([...styleClosure, ...extensionFiles])].filter((f) => escapesProjectRoot(sourceProjectDir, f));
7938
+ const base = commonAncestorDir(externalFiles);
7939
+ const vendorDir = pickVendorDir(templateDir, base);
7940
+ await Promise.all(externalFiles.filter((abs) => existsSync10(abs)).map(async (abs) => {
7941
+ const dest = path16.join(templateDir, vendorDir, path16.relative(base, abs));
7942
+ await mkdir8(path16.dirname(dest), { recursive: true });
7943
+ await cp2(abs, dest, { recursive: true });
7944
+ }));
7945
+ const bookLocal = (value) => toPosixPath(path16.join(vendorDir, path16.relative(base, path16.resolve(sourceProjectDir, value))));
7946
+ const vendoredRefs = [];
7947
+ for (const { node, value } of escapingStyles) {
7948
+ const rewritten = bookLocal(value);
7949
+ rewriteEntry(node, rewritten);
7950
+ vendoredRefs.push(rewritten);
7951
+ }
7952
+ for (const { node, value } of escapingExtensions) {
7953
+ const rewritten = bookLocal(value);
7954
+ rewriteEntry(node, `./${rewritten}`);
7955
+ vendoredRefs.push(rewritten);
7956
+ }
7957
+ await writeManifestDoc(file, doc);
7958
+ return { vendoredRefs, excludedRefs: [] };
7959
+ }
7960
+ async function saveProjectAsTemplate(options) {
7961
+ const { projectDir, name, templatesRoot } = options;
7962
+ const id = slugify(name);
7963
+ if (!id)
7964
+ throw new Error(`Could not derive a template id from "${name}".`);
7965
+ const dir = path16.join(templatesRoot, id);
7966
+ let exists = true;
7967
+ try {
7968
+ await access3(dir, FS3.F_OK);
7969
+ } catch {
7970
+ exists = false;
7971
+ }
7972
+ if (exists) {
7973
+ throw new Error(`A template named "${name}" already exists. Choose a different name.`);
7974
+ }
7975
+ await mkdir8(dir, { recursive: true });
7976
+ const entries = await readdir7(projectDir, { withFileTypes: true });
7977
+ for (const entry of entries) {
7978
+ if (SKIP_ENTRIES.has(entry.name))
7979
+ continue;
7980
+ if (entry.name === TEMPLATE_META_FILE)
7981
+ continue;
7982
+ await cp2(path16.join(projectDir, entry.name), path16.join(dir, entry.name), { recursive: true });
7983
+ }
7984
+ const manifestName = MANIFEST_FILENAMES.find((name2) => entries.some((entry) => entry.name === name2));
7985
+ const shared = manifestName ? await reconcileSharedRefs(dir, projectDir, options.sharedRefs ?? "vendor") : { vendoredRefs: [], excludedRefs: [] };
7986
+ if (manifestName)
7987
+ await retokeniseManifest(path16.join(dir, manifestName));
7988
+ await writeFile10(path16.join(dir, TEMPLATE_META_FILE), JSON.stringify({ label: name }, null, 2), "utf8");
7989
+ return {
7990
+ id,
7991
+ label: name,
7992
+ description: "Your saved template.",
7993
+ kind: "custom",
7994
+ dir,
7995
+ ...shared
7996
+ };
7997
+ }
7998
+ async function retokeniseManifest(manifestPath) {
7999
+ let text;
8000
+ try {
8001
+ text = await readFile27(manifestPath, "utf8");
8002
+ } catch {
8003
+ return;
8004
+ }
8005
+ text = text.replace(/^title:\s*.*$/m, 'title: "{{TITLE}}"').replace(/^(authors:[^\n]*\n[ \t]*-[ \t]*).*$/m, '$1"{{AUTHOR}}"');
8006
+ await writeFile10(manifestPath, text, "utf8");
8007
+ }
8008
+ async function listCustomTemplates(templatesRoot) {
8009
+ let entries;
8010
+ try {
8011
+ entries = await readdir7(templatesRoot, { withFileTypes: true });
8012
+ } catch {
8013
+ return [];
8014
+ }
8015
+ const out = [];
8016
+ for (const entry of entries) {
8017
+ if (!entry.isDirectory())
8018
+ continue;
8019
+ const dir = path16.join(templatesRoot, entry.name);
8020
+ let label = prettify(entry.name);
8021
+ try {
8022
+ const meta = JSON.parse(await readFile27(path16.join(dir, TEMPLATE_META_FILE), "utf8"));
8023
+ if (typeof meta.label === "string" && meta.label)
8024
+ label = meta.label;
8025
+ } catch {}
8026
+ out.push({
8027
+ id: entry.name,
8028
+ label,
8029
+ description: "Your saved template.",
8030
+ kind: "custom",
8031
+ dir,
8032
+ ...await readTemplateChoices(path16.join(dir, MANIFEST_FILENAMES[0]))
8033
+ });
8034
+ }
8035
+ out.sort((a, b) => a.label.localeCompare(b.label));
8036
+ return out;
8037
+ }
8038
+ async function importTemplateFromFolder(options) {
8039
+ const { sourceDir, templatesRoot } = options;
8040
+ const srcStat = await stat5(sourceDir);
8041
+ if (!srcStat.isDirectory()) {
8042
+ throw new Error("The chosen path is not a folder.");
8043
+ }
8044
+ const name = options.name?.trim() || prettify(path16.basename(sourceDir));
8045
+ return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
8046
+ }
8061
8047
  // src/lib/snippets.ts
8062
8048
  import { readdir as readdir8, readFile as readFile28, writeFile as writeFile11, mkdir as mkdir9, rm } from "node:fs/promises";
8063
8049
  import path17 from "node:path";
@@ -12646,7 +12632,7 @@ async function resolvePublishProvider(id, book) {
12646
12632
  const ext = await bookExtensions(book);
12647
12633
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
12648
12634
  if (npmEntry) {
12649
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-jvs60x9b.js");
12635
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-s3x1618g.js");
12650
12636
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
12651
12637
  }
12652
12638
  const publishers = await discover(ext);
@@ -12684,7 +12670,7 @@ function infoFor(p) {
12684
12670
  }
12685
12671
  function pluginProvider(p) {
12686
12672
  let loading;
12687
- const load = () => loading ??= import("./plugins-gf0gkjmv.js").then((m) => m.loadPublishFunction(p));
12673
+ const load = () => loading ??= import("./plugins-2cek4mz1.js").then((m) => m.loadPublishFunction(p));
12688
12674
  return {
12689
12675
  info: infoFor(p),
12690
12676
  async authenticate() {
@@ -13081,4 +13067,4 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
13081
13067
  await writeManifestDoc(file, doc);
13082
13068
  return settingsFromDoc(doc);
13083
13069
  }
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 };
13070
+ 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 };