gutterpress 0.11.22 → 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 (76) hide show
  1. package/README.md +23 -24
  2. package/dist/{README-3nj919rm.md → README-8ef336pp.md} +1 -1
  3. package/dist/api/index.d.ts +4 -4
  4. package/dist/api/index.js +16 -22
  5. package/dist/{build-53bw14h7.js → build-encgnv0c.js} +9 -10
  6. package/dist/{chapter-01-rkpyvjna.md → chapter-01-gsbjt584.md} +1 -1
  7. package/dist/chapter-01-s71xrf5q.md +69 -0
  8. package/dist/{chapter-01-7jwqg1ct.md → chapter-01-wgjfz4w6.md} +1 -1
  9. package/dist/checks/source/character-table.d.ts +12 -6
  10. package/dist/{cli-habkc5ng.js → cli-a35w7wzg.js} +5 -4
  11. package/dist/{cli-kx4hbnx2.js → cli-aqh6brew.js} +1 -1
  12. package/dist/{cli-1hxxwbaq.js → cli-evg8sbht.js} +1 -1
  13. package/dist/{cli-cjftezxr.js → cli-kh8v4vvw.js} +2 -1
  14. package/dist/{cli-ntwqmdnw.js → cli-pwwfxdgb.js} +846 -13
  15. package/dist/{cli-8kg0wjdt.js → cli-pz4bn98t.js} +185 -9
  16. package/dist/{cli-zj5xaepv.js → cli-sdmv4bba.js} +57 -117
  17. package/dist/{cli-ke86zsz6.js → cli-xq6djqbj.js} +51 -54
  18. package/dist/{index-r7jcsk6h.js → cli-y28j019d.js} +19 -10
  19. package/dist/cli.js +12 -12
  20. package/dist/{doctor-xka1frnx.js → doctor-5gtpgpd1.js} +3 -3
  21. package/dist/{engine-p93f06gj.js → engine-mc5br8g8.js} +1 -2
  22. package/dist/{engine-0ndvy8vj.js → engine-tn13gk0b.js} +3 -3
  23. package/dist/{ext-dwfqyy1j.js → ext-wn2a5afq.js} +18 -33
  24. package/dist/{extension-restore-ct1wk344.js → extension-restore-g8jnmmzq.js} +5 -6
  25. package/dist/{extension-restore-7vp3vh77.js → extension-restore-s3x1618g.js} +4 -4
  26. package/dist/{index-apc8w9w8.js → index-0skhjx3e.js} +180 -1
  27. package/dist/{index-4v7e621e.js → index-2bjcghze.js} +847 -13
  28. package/dist/index-46ycxe6r.js +18 -0
  29. package/dist/{index-53nrm5j1.js → index-4x6y8krm.js} +2 -4
  30. package/dist/{cli-h3phdstf.js → index-5x42amh8.js} +19 -10
  31. package/dist/{index-93pqkvty.js → index-h6yk5tjx.js} +5 -4
  32. package/dist/{index-kh7fayjv.js → index-j5z86r4a.js} +57 -54
  33. package/dist/{index-6cmn2wpd.js → index-x3ema2w3.js} +69 -128
  34. package/dist/index.js +17 -23
  35. package/dist/lib/build-runner.d.ts +1 -1
  36. package/dist/lib/extension-manager.d.ts +16 -43
  37. package/dist/lib/extension-scaffold.d.ts +3 -3
  38. package/dist/lib/extension-specifier.d.ts +23 -6
  39. package/dist/lib/markdown/assemble.d.ts +7 -1
  40. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  41. package/dist/lib/markdown/index.d.ts +4 -0
  42. package/dist/lib/markdown/math.d.ts +43 -0
  43. package/dist/lib/markdown/renderer.d.ts +11 -8
  44. package/dist/lib/markdown/table.d.ts +46 -0
  45. package/dist/lib/markdown/typography.d.ts +12 -0
  46. package/dist/lib/npm-plugin-installer.d.ts +1 -1
  47. package/dist/lib/plugin-vendor.d.ts +11 -3
  48. package/dist/lib/presets.d.ts +1 -1
  49. package/dist/lib/project-gitignore.d.ts +2 -2
  50. package/dist/lib/project-scaffold.d.ts +6 -8
  51. package/dist/lib/project-templates.d.ts +6 -1
  52. package/dist/lib/snippets.d.ts +2 -1
  53. package/dist/lib/style-resolver.d.ts +1 -2
  54. package/dist/manifest-eagfhba6.yaml +19 -0
  55. package/dist/{manifest.schema-399bx3vc.json → manifest.schema-t7nx2stg.json} +12 -5
  56. package/dist/{new-1pawj2qf.js → new-mhs46pw3.js} +22 -21
  57. package/dist/{plugins-fr4fnzhb.js → plugins-1sxe0dkz.js} +4 -4
  58. package/dist/{plugins-g4dgw7mn.js → plugins-2cek4mz1.js} +2 -2
  59. package/dist/{preflight-ept9q57t.js → preflight-3k2b1x8e.js} +9 -10
  60. package/dist/preview/file-watcher.d.ts +1 -0
  61. package/dist/{preview-fxpnjv7k.js → preview-6p9v6rht.js} +9 -10
  62. package/dist/{publish-zhqqsgmr.js → publish-qgsngy8s.js} +9 -10
  63. package/dist/render.js +470 -11
  64. package/dist/schema/manifest.types.d.ts +8 -0
  65. package/dist/{source-provider-qbfknhyy.js → source-provider-98k5deda.js} +2 -2
  66. package/dist/{source-provider-2r74a7wp.js → source-provider-knmcam50.js} +2 -2
  67. package/dist/{validate-t68gvckq.js → validate-0v7xw6wa.js} +9 -10
  68. package/package.json +2 -1
  69. package/dist/cli-1ngqzg8s.js +0 -196
  70. package/dist/index-vay3ff1f.js +0 -213
  71. package/dist/package-97gm31sg.json +0 -10
  72. package/dist/package-gag6ykwn.json +0 -10
  73. package/dist/package-nrhkwf2m.json +0 -10
  74. package/dist/theme-h5recz6c.css +0 -61
  75. package/dist/theme-j2bagrfx.css +0 -59
  76. package/dist/theme-nn6d53zy.css +0 -71
@@ -0,0 +1,18 @@
1
+ // src/lib/build-error.ts
2
+ var EXIT_CODES = {
3
+ OK: 0,
4
+ FINDINGS: 1,
5
+ USAGE: 2,
6
+ PIPELINE: 3
7
+ };
8
+
9
+ class BuildError extends Error {
10
+ exitCode;
11
+ constructor(message, exitCode = EXIT_CODES.PIPELINE, options) {
12
+ super(message, options);
13
+ this.name = "BuildError";
14
+ this.exitCode = exitCode;
15
+ }
16
+ }
17
+
18
+ export { EXIT_CODES, BuildError };
@@ -3,6 +3,7 @@ import {
3
3
  cssQuote,
4
4
  extract,
5
5
  generatedContentCss,
6
+ getAssetPath,
6
7
  isIgnoredMarginBoxProperty,
7
8
  isRectoVersoBreak,
8
9
  leaderMarker,
@@ -18,10 +19,7 @@ import {
18
19
  stringValueAt,
19
20
  toFolioPage,
20
21
  wantsRecto
21
- } from "./index-apc8w9w8.js";
22
- import {
23
- getAssetPath
24
- } from "./index-vay3ff1f.js";
22
+ } from "./index-0skhjx3e.js";
25
23
  import {
26
24
  launchChromium
27
25
  } from "./index-bt0qws5c.js";
@@ -2,7 +2,8 @@
2
2
  import { lstat, readFile, realpath } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { exports as resolveExports } from "resolve.exports";
5
- var PLUGINS_DIR = "plugins";
5
+ var EXTENSIONS_DIR = "extensions";
6
+ var LEGACY_PLUGINS_DIR = "plugins";
6
7
  var VENDORED_NPM_DIR = "npm";
7
8
  var NPM_SEGMENT = /^[a-z0-9][a-z0-9._~-]*$/;
8
9
  var EXACT_VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
@@ -51,7 +52,10 @@ function encodePathPart(value) {
51
52
  return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
52
53
  }
53
54
  function vendoredNpmPluginRoot(projectDir, name, version) {
54
- return path.join(projectDir, PLUGINS_DIR, VENDORED_NPM_DIR, encodePathPart(name), encodePathPart(version));
55
+ return path.join(projectDir, EXTENSIONS_DIR, VENDORED_NPM_DIR, encodePathPart(name), encodePathPart(version));
56
+ }
57
+ function vendoredNpmPluginRoots(projectDir, name, version) {
58
+ return [EXTENSIONS_DIR, LEGACY_PLUGINS_DIR].map((parent) => path.join(projectDir, parent, VENDORED_NPM_DIR, encodePathPart(name), encodePathPart(version)));
55
59
  }
56
60
  function vendoredNpmPluginPackageDir(installRoot, name) {
57
61
  return path.join(installRoot, "node_modules", ...name.split("/"));
@@ -175,15 +179,20 @@ async function resolvePackageEntry(packageDir, packageJson) {
175
179
  }
176
180
  async function resolveVendoredPluginInstallRoot(projectDir, expectedName, expectedVersion) {
177
181
  const requestedProjectRoot = path.resolve(projectDir);
178
- const requestedInstallRoot = vendoredNpmPluginRoot(requestedProjectRoot, expectedName, expectedVersion);
182
+ let requestedInstallRoot = "";
179
183
  let installInfo;
180
- try {
181
- installInfo = await lstat(requestedInstallRoot);
182
- } catch (error) {
183
- if (error.code === "ENOENT")
184
- return null;
185
- throw error;
184
+ for (const candidate of vendoredNpmPluginRoots(requestedProjectRoot, expectedName, expectedVersion)) {
185
+ requestedInstallRoot = candidate;
186
+ try {
187
+ installInfo = await lstat(requestedInstallRoot);
188
+ break;
189
+ } catch (error) {
190
+ if (error.code !== "ENOENT")
191
+ throw error;
192
+ }
186
193
  }
194
+ if (!installInfo)
195
+ return null;
187
196
  if (!installInfo.isDirectory() || installInfo.isSymbolicLink()) {
188
197
  throw new Error("Vendor install root is not a normal directory.");
189
198
  }
@@ -197,4 +206,4 @@ async function resolveVendoredPluginInstallRoot(projectDir, expectedName, expect
197
206
  return installRoot;
198
207
  }
199
208
 
200
- export { PLUGINS_DIR, VENDORED_NPM_DIR, isValidNpmPackageName, parseNpmPluginSpec, isExactNpmVersion, vendoredNpmPluginRoot, vendoredNpmPluginPackageDir, assertWindowsSafeRelativePath, windowsPathKey, packageResolutionTargets, safePackageTarget, resolvePackageEntry, resolveVendoredPluginInstallRoot };
209
+ export { EXTENSIONS_DIR, LEGACY_PLUGINS_DIR, VENDORED_NPM_DIR, isValidNpmPackageName, parseNpmPluginSpec, isExactNpmVersion, vendoredNpmPluginRoot, vendoredNpmPluginRoots, vendoredNpmPluginPackageDir, toPosixPath, assertWindowsSafeRelativePath, windowsPathKey, packageResolutionTargets, safePackageTarget, resolvePackageEntry, resolveVendoredPluginInstallRoot };
@@ -1,7 +1,8 @@
1
1
  import {
2
- PLUGINS_DIR,
2
+ EXTENSIONS_DIR,
3
+ LEGACY_PLUGINS_DIR,
3
4
  VENDORED_NPM_DIR
4
- } from "./index-r7jcsk6h.js";
5
+ } from "./index-5x42amh8.js";
5
6
  import {
6
7
  capabilitiesFor,
7
8
  detectProjectSource,
@@ -562,10 +563,10 @@ async function countUnversionedChanges(projectDir) {
562
563
  return null;
563
564
  const dir = gitScopeFor(source);
564
565
  const bookPrefix = source.subPath ? `${source.subPath.replace(/\/+$/, "")}/` : "";
565
- const appWritten = `${bookPrefix}${PLUGINS_DIR}/${VENDORED_NPM_DIR}/`;
566
+ const appWritten = [EXTENSIONS_DIR, LEGACY_PLUGINS_DIR].map((d) => `${bookPrefix}${d}/${VENDORED_NPM_DIR}/`);
566
567
  return withRepoLock(dir, async () => {
567
568
  const { adds, removes } = await listWorkdirChanges(dir);
568
- const mine = new Set([...adds, ...removes].filter((f) => f.startsWith(bookPrefix) && !f.startsWith(appWritten)));
569
+ const mine = new Set([...adds, ...removes].filter((f) => f.startsWith(bookPrefix) && !appWritten.some((p) => f.startsWith(p))));
569
570
  return { changedFiles: mine.size, stale: gitFs.existsSync(snapshotStagingMarkerPath(dir)) };
570
571
  });
571
572
  }
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  DEBOUNCE,
3
- EXTENSIONS_DIR,
4
3
  FriendlyHttpError,
5
4
  MANIFEST_FILENAMES,
6
5
  PRESETS,
@@ -13,7 +12,6 @@ import {
13
12
  ensureProjectGitignore,
14
13
  ensureSeq,
15
14
  extensionConfigFor,
16
- isPathSpecifier,
17
15
  listProjectExtensions,
18
16
  loadManifest,
19
17
  loadManifestDoc,
@@ -29,7 +27,7 @@ import {
29
27
  uniqueExtensionId,
30
28
  withFetchTimeout,
31
29
  writeManifestDoc
32
- } from "./index-6cmn2wpd.js";
30
+ } from "./index-x3ema2w3.js";
33
31
  import {
34
32
  EXTENSION_MANIFEST_FILENAME,
35
33
  GUTTERPRESS_CSS,
@@ -51,6 +49,7 @@ import {
51
49
  extensionEntry,
52
50
  extensionStyleList,
53
51
  info,
52
+ isPathSpecifier,
54
53
  loadPlugin,
55
54
  loadPlugins,
56
55
  loadPluginsWithCss,
@@ -64,21 +63,21 @@ import {
64
63
  setLogLevel,
65
64
  slugify,
66
65
  warn
67
- } from "./index-4v7e621e.js";
66
+ } from "./index-2bjcghze.js";
68
67
  import {
69
68
  blackTextToGray
70
69
  } from "./index-ht1bcqdd.js";
71
70
  import {
72
71
  MARGIN_BOX_IGNORED_PROPERTIES,
73
72
  extract,
73
+ getAssetPath,
74
74
  lastPageMode,
75
75
  parseSize,
76
76
  resolvePage
77
- } from "./index-apc8w9w8.js";
77
+ } from "./index-0skhjx3e.js";
78
78
  import {
79
- BuildError,
80
- getAssetPath
81
- } from "./index-vay3ff1f.js";
79
+ BuildError
80
+ } from "./index-46ycxe6r.js";
82
81
  import {
83
82
  countLinkAnnotations,
84
83
  getImageResolutions,
@@ -116,12 +115,13 @@ import {
116
115
  snapshotStagingMarkerPath,
117
116
  snapshotWorkingTreeUnlocked,
118
117
  withRepoLock
119
- } from "./index-93pqkvty.js";
118
+ } from "./index-h6yk5tjx.js";
120
119
  import {
120
+ EXTENSIONS_DIR,
121
121
  resolveVendoredPluginInstallRoot,
122
122
  toPosixPath,
123
123
  vendoredNpmPluginPackageDir
124
- } from "./index-r7jcsk6h.js";
124
+ } from "./index-5x42amh8.js";
125
125
  import {
126
126
  detectProjectSource,
127
127
  repoSubPath,
@@ -279,11 +279,12 @@ async function assembleBookHtml(opts) {
279
279
  const title = opts.title ?? "Document";
280
280
  const projectCss = opts.projectCss ?? "";
281
281
  const pluginCss = opts.pluginCss ?? "";
282
+ const language = opts.language ?? "en";
282
283
  const files = opts.files;
283
284
  if (files.length === 0) {
284
285
  throw new Error("No markdown files to render");
285
286
  }
286
- const md = createMarkdownRenderer(opts.plugins);
287
+ const md = createMarkdownRenderer(opts.plugins, { language });
287
288
  let bodyContent = "";
288
289
  const imageRefs = new Set;
289
290
  for (const file of files) {
@@ -295,7 +296,7 @@ async function assembleBookHtml(opts) {
295
296
  const errorMsg = err instanceof Error ? err.message : String(err);
296
297
  throw new Error(`Failed to read file ${file}: ${errorMsg}`);
297
298
  }
298
- const env = {};
299
+ const env = { docId: slugify(chapterId) };
299
300
  if (opts.annotateSourceChapters)
300
301
  env.sourceChapter = chapterId;
301
302
  const rendered = md.render(content, env);
@@ -341,7 +342,7 @@ ${projectCss.trim()}` : null
341
342
  const lastPage = opts.print && lastPageMode(opts.print);
342
343
  const printAttrs = (opts.print?.signature && opts.print.signature > 1 ? ` data-gp-signature="${opts.print.signature}"` : "") + (lastPage ? ` data-gp-last-page="${lastPage}"` : "");
343
344
  return `<!DOCTYPE html>
344
- <html lang="en"${printAttrs}>
345
+ <html lang="${language}"${printAttrs}>
345
346
  <head>
346
347
  <meta charset="UTF-8">
347
348
  <meta name="viewport" content="width=device-width, initial-scale=1.0">${opts.printArea ? `
@@ -362,13 +363,7 @@ import { existsSync } from "node:fs";
362
363
  import { readdir } from "node:fs/promises";
363
364
  import path from "node:path";
364
365
  var STYLES_SUBDIR = "styles";
365
- var FALLBACK_PRIORITY = [
366
- "styles/book.css",
367
- "css/print.css",
368
- "css/index.css",
369
- "css/style.css",
370
- "css/main.css"
371
- ];
366
+ var DEFAULT_STYLESHEET = "styles/book.css";
372
367
  function relDisplay(projectDir, absPath) {
373
368
  return path.relative(projectDir, absPath).split(path.sep).join("/");
374
369
  }
@@ -406,10 +401,8 @@ async function resolveActiveStyles(projectDir, manifestStyles) {
406
401
  const manifest = configured.filter((s) => typeof s === "string" && s.trim().length > 0);
407
402
  if (manifest.length > 0)
408
403
  return manifest;
409
- for (const rel of FALLBACK_PRIORITY) {
410
- if (existsSync(path.join(projectDir, rel)))
411
- return [rel];
412
- }
404
+ if (existsSync(path.join(projectDir, DEFAULT_STYLESHEET)))
405
+ return [DEFAULT_STYLESHEET];
413
406
  const discovered = await discoverCssFiles(projectDir);
414
407
  if (discovered.length > 0) {
415
408
  const first = discovered.map((abs) => relDisplay(projectDir, abs)).sort((a, b) => a.localeCompare(b))[0];
@@ -801,6 +794,7 @@ ${css.trim()}
801
794
  projectCss: inlined.css,
802
795
  preloadImages: cssAssetCopies.map((c) => c.to),
803
796
  title: opts.title,
797
+ language: opts.language,
804
798
  print: opts.print,
805
799
  printArea: opts.printArea,
806
800
  plugins: opts.plugins,
@@ -814,6 +808,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
814
808
  await mkdir(outDir, { recursive: true });
815
809
  const html = await renderChapters(inputDir, {
816
810
  title: opts.title,
811
+ language: opts.language,
817
812
  print: opts.print,
818
813
  printArea: opts.printArea,
819
814
  styles: opts.styles,
@@ -4023,7 +4018,7 @@ async function collectKnownClasses(ctx, plugins) {
4023
4018
  var check26 = {
4024
4019
  id: "source.markdown.layout-markers",
4025
4020
  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",
4021
+ 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
4022
  category: "source",
4028
4023
  phase: "pre-build",
4029
4024
  async run(ctx) {
@@ -4161,14 +4156,14 @@ registerCheck(check27);
4161
4156
  import { readFile as readFile17 } from "node:fs/promises";
4162
4157
 
4163
4158
  // 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");
4159
+ 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
4160
  var NAMED = {
4166
4161
  8212: ["Em dash", "typographer", "---"],
4167
4162
  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", "'"],
4163
+ 8220: ["Left curly quote", "typographer", '"'],
4164
+ 8221: ["Right curly quote", "typographer", '"'],
4165
+ 8216: ["Left curly single quote", "typographer", "'"],
4166
+ 8217: ["Right curly single quote (apostrophe)", "typographer", "'"],
4172
4167
  8230: ["Ellipsis character", "typographer", "..."],
4173
4168
  700: ["Modifier-letter apostrophe", "lookalike", "'"],
4174
4169
  180: ["Acute accent", "lookalike", "'"],
@@ -5017,7 +5012,7 @@ function reportMissingTools(result) {
5017
5012
  const affected = checkIds.filter((id) => result.skippedChecks.includes(id));
5018
5013
  if (affected.length === 0)
5019
5014
  continue;
5020
- log.warn(`Tool "${tool}" not found — skipping: ${affected.join(", ")}`);
5015
+ log.warn(`Tool "${tool}" not found; skipping: ${affected.join(", ")}`);
5021
5016
  }
5022
5017
  }
5023
5018
 
@@ -5519,6 +5514,7 @@ async function renderBook(ctx) {
5519
5514
  const cssAssets = [];
5520
5515
  const htmlFile = await renderChaptersToFile(renderDir, workDir, {
5521
5516
  title: config.title,
5517
+ language: config.language,
5522
5518
  print: config.print,
5523
5519
  printArea: { ...config.page, preset: config.preset, spec: config.spec },
5524
5520
  styles: config.styles,
@@ -5673,7 +5669,7 @@ class PdfOutput {
5673
5669
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5674
5670
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5675
5671
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5676
- const { buildNativePdf } = await import("./engine-0ndvy8vj.js");
5672
+ const { buildNativePdf } = await import("./engine-tn13gk0b.js");
5677
5673
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5678
5674
  title: config.title,
5679
5675
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -5946,6 +5942,7 @@ async function renderPreviewBook(inputPath, config, opts) {
5946
5942
  const { plugins, pluginStyles } = await loadPluginsWithCss(config.extensions, inputPath, (ref, err) => warn(`Skipping plugin "${ref}" in preview — ${err.message}`));
5947
5943
  return renderChapters(inputPath, {
5948
5944
  title: config.title ?? "Document",
5945
+ language: config.language,
5949
5946
  print: config.print,
5950
5947
  printArea: config.printArea,
5951
5948
  styles: config.styles,
@@ -6023,7 +6020,7 @@ function isGeneratedProjectPath(relative) {
6023
6020
  const segment = segments[i];
6024
6021
  if (segment === "dist" || segment === "node_modules")
6025
6022
  return true;
6026
- if (segment === "plugins" && segments[i + 1] === "npm")
6023
+ if ((segment === "extensions" || segment === "plugins") && segments[i + 1] === "npm")
6027
6024
  return true;
6028
6025
  }
6029
6026
  return false;
@@ -7379,10 +7376,10 @@ function starterOverridesCss() {
7379
7376
  * Your book's own styles.
7380
7377
  *
7381
7378
  * 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:
7383
- * gutterpress ext add clean-book <book> --look (a built-in look)
7384
- * gutterpress ext add <package> <book> (a look from npm)
7385
- * 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)
7386
7383
  */
7387
7384
  `;
7388
7385
  }
@@ -7403,9 +7400,8 @@ function escapeYamlScalar(value) {
7403
7400
  }
7404
7401
  var DEFAULT_AUTHOR = "Anonymous";
7405
7402
  function requirePreset(preset, customPage) {
7406
- if (!preset || !PRESET_IDS.includes(preset)) {
7407
- const got = preset ? ` (got "${preset}")` : "";
7408
- 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(", ")}.`);
7409
7405
  }
7410
7406
  if (preset === "custom") {
7411
7407
  const missing = [];
@@ -7463,7 +7459,7 @@ async function scaffoldProject(options) {
7463
7459
  }
7464
7460
  const template = options.template ?? "book";
7465
7461
  const customTemplateDir = options.templateDir;
7466
- const preset = customTemplateDir && options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
7462
+ const preset = options.preset === undefined ? undefined : requirePreset(options.preset, options.customPage);
7467
7463
  const targets = preset ? resolveScaffoldTargets(options.targets, preset) : undefined;
7468
7464
  try {
7469
7465
  await mkdir6(projectDir, { recursive: true });
@@ -7501,10 +7497,12 @@ async function scaffoldProject(options) {
7501
7497
  let openFile = path14.join(projectDir, "chapter-01.md");
7502
7498
  try {
7503
7499
  await fillTemplateFile(manifestPath, substitutions);
7504
- if (preset) {
7500
+ if (preset || options.customPage) {
7505
7501
  const { doc, file } = await loadManifestDoc(projectDir);
7506
- doc.set("preset", preset);
7507
- doc.set("targets", doc.createNode(targets ?? []));
7502
+ if (preset) {
7503
+ doc.set("preset", preset);
7504
+ doc.set("targets", doc.createNode(targets ?? []));
7505
+ }
7508
7506
  if (options.customPage)
7509
7507
  writePageBounds(doc, options.customPage);
7510
7508
  await writeManifestDoc(file, doc);
@@ -7521,7 +7519,7 @@ async function scaffoldProject(options) {
7521
7519
  let versionHistoryError;
7522
7520
  if (requested === "local-git") {
7523
7521
  try {
7524
- const { providerFor } = await import("./source-provider-qbfknhyy.js");
7522
+ const { providerFor } = await import("./source-provider-98k5deda.js");
7525
7523
  const provider = providerFor({ type: "local-folder", path: projectDir });
7526
7524
  await provider.initVersionHistory({
7527
7525
  projectDir,
@@ -7613,7 +7611,7 @@ styles:
7613
7611
  let versionHistoryError;
7614
7612
  if (requested === "local-git") {
7615
7613
  try {
7616
- const { providerFor } = await import("./source-provider-qbfknhyy.js");
7614
+ const { providerFor } = await import("./source-provider-98k5deda.js");
7617
7615
  const provider = providerFor({ type: "local-folder", path: dir });
7618
7616
  await provider.initVersionHistory({
7619
7617
  projectDir: dir,
@@ -7661,7 +7659,7 @@ async function fillTemplateFile(filePath, substitutions) {
7661
7659
  import { access as access2, mkdir as mkdir7, readFile as readFile26, writeFile as writeFile9 } from "node:fs/promises";
7662
7660
  import { constants as FS2 } from "node:fs";
7663
7661
  import path15 from "node:path";
7664
- var EXTENSION_KINDS = ["plugin", "theme"];
7662
+ var EXTENSION_KINDS = ["plugin", "look"];
7665
7663
  var RESERVED_PREFIX = "gp-";
7666
7664
  var TEMPLATE_FILES = {
7667
7665
  plugin: [
@@ -7674,7 +7672,7 @@ var TEMPLATE_FILES = {
7674
7672
  ["test/expected.html", "test/expected.html"],
7675
7673
  ["test/plugin.test.js.tpl", "test/plugin.test.js"]
7676
7674
  ],
7677
- theme: [
7675
+ look: [
7678
7676
  ["package.json", "package.json"],
7679
7677
  ["README.md", "README.md"],
7680
7678
  ["components.yaml", "components.yaml"],
@@ -7689,11 +7687,11 @@ var TEMPLATE_FILES = {
7689
7687
  };
7690
7688
  var OPEN_FILE = {
7691
7689
  plugin: "plugin.js",
7692
- theme: "styles/tokens.css"
7690
+ look: "styles/tokens.css"
7693
7691
  };
7694
7692
  var DEFAULT_DESCRIPTION = {
7695
7693
  plugin: "A Gutterpress plugin.",
7696
- theme: "A Gutterpress theme."
7694
+ look: "A Gutterpress look."
7697
7695
  };
7698
7696
  var DEFAULT_AUTHOR2 = "Anonymous";
7699
7697
 
@@ -7725,7 +7723,7 @@ function escapeJsonScalar(value) {
7725
7723
  }
7726
7724
  async function scaffoldExtension(options) {
7727
7725
  const kind = options.kind;
7728
- if (kind !== "plugin" && kind !== "theme") {
7726
+ if (kind !== "plugin" && kind !== "look") {
7729
7727
  throw new ExtensionScaffoldError("invalid-name", `Unknown extension kind "${String(kind)}". Choose one of: ${EXTENSION_KINDS.join(", ")}.`);
7730
7728
  }
7731
7729
  const name = (options.name ?? "").trim();
@@ -7809,7 +7807,8 @@ import { isMap as isMap2, isScalar, isSeq, parseDocument } from "yaml";
7809
7807
  var BUILT_IN_TEMPLATE_IDS = [
7810
7808
  "book",
7811
7809
  "zine",
7812
- "technical"
7810
+ "technical",
7811
+ "paper"
7813
7812
  ];
7814
7813
  async function readTemplateChoices(manifestPath) {
7815
7814
  try {
@@ -7840,6 +7839,10 @@ var BUILT_IN_META = {
7840
7839
  technical: {
7841
7840
  label: "Technical document",
7842
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."
7843
7846
  }
7844
7847
  };
7845
7848
  async function listBuiltInTemplates() {
@@ -12629,7 +12632,7 @@ async function resolvePublishProvider(id, book) {
12629
12632
  const ext = await bookExtensions(book);
12630
12633
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
12631
12634
  if (npmEntry) {
12632
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-7vp3vh77.js");
12635
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-s3x1618g.js");
12633
12636
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
12634
12637
  }
12635
12638
  const publishers = await discover(ext);
@@ -12667,7 +12670,7 @@ function infoFor(p) {
12667
12670
  }
12668
12671
  function pluginProvider(p) {
12669
12672
  let loading;
12670
- const load = () => loading ??= import("./plugins-g4dgw7mn.js").then((m) => m.loadPublishFunction(p));
12673
+ const load = () => loading ??= import("./plugins-2cek4mz1.js").then((m) => m.loadPublishFunction(p));
12671
12674
  return {
12672
12675
  info: infoFor(p),
12673
12676
  async authenticate() {