gutterpress 0.11.23-alpha.1 → 0.11.23-alpha.3

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 (82) hide show
  1. package/README.md +27 -29
  2. package/dist/{README-8ef336pp.md → README-8db7btxb.md} +13 -19
  3. package/dist/api/index.d.ts +5 -5
  4. package/dist/api/index.js +16 -22
  5. package/dist/{build-09cd1n7s.js → build-m78jcpf7.js} +9 -10
  6. package/dist/{cli-5hkmp2wy.js → cli-1h17ktsg.js} +125 -187
  7. package/dist/{cli-nr6j6300.js → cli-5jzyw06s.js} +1 -1
  8. package/dist/{cli-habkc5ng.js → cli-a35w7wzg.js} +5 -4
  9. package/dist/{cli-dd115fa8.js → cli-h4x81x97.js} +152 -124
  10. package/dist/{cli-ccp7sxz3.js → cli-n2tsxqpn.js} +1 -1
  11. package/dist/{cli-b0vyjvmt.js → cli-s6djx4ma.js} +1 -1
  12. package/dist/{cli-h17e3mtz.js → cli-t8be9yeb.js} +380 -16
  13. package/dist/{cli-dtckwda1.js → cli-xsrffqzt.js} +192 -10
  14. package/dist/{index-r7jcsk6h.js → cli-y28j019d.js} +19 -10
  15. package/dist/cli.js +12 -12
  16. package/dist/{doctor-nddspx4n.js → doctor-j71jqce3.js} +3 -3
  17. package/dist/engine/shared/gcpm-extract.d.ts +8 -0
  18. package/dist/engine/shared/print-area.d.ts +16 -4
  19. package/dist/{engine-yjzknt0g.js → engine-1r6q80tq.js} +3 -3
  20. package/dist/{engine-892swzzc.js → engine-m4y10shc.js} +1 -2
  21. package/dist/{ext-zjpen4yx.js → ext-fq2hg9va.js} +18 -33
  22. package/dist/{extension-restore-jvs60x9b.js → extension-restore-9dh7c4tp.js} +4 -4
  23. package/dist/{extension-restore-w8xt4kbq.js → extension-restore-tmxtzkm0.js} +5 -6
  24. package/dist/{gutterpress-viewer-701vf4qp.js → gutterpress-viewer-mafsby5k.js} +9 -2
  25. package/dist/{index-2jpk25en.js → index-0wee4408.js} +380 -16
  26. package/dist/index-46ycxe6r.js +18 -0
  27. package/dist/{cli-h3phdstf.js → index-5x42amh8.js} +19 -10
  28. package/dist/{index-m4hhzg3c.js → index-7pck1q9c.js} +174 -142
  29. package/dist/{index-93pqkvty.js → index-h6yk5tjx.js} +5 -4
  30. package/dist/{index-apc8w9w8.js → index-nedd07bk.js} +187 -2
  31. package/dist/{index-0w2azsk1.js → index-nvmf22rt.js} +2 -4
  32. package/dist/{index-zrhyb967.js → index-prqvz3xm.js} +510 -587
  33. package/dist/index.js +17 -23
  34. package/dist/lib/extension-manager.d.ts +24 -50
  35. package/dist/lib/extension-manifest.d.ts +1 -13
  36. package/dist/lib/extension-scaffold.d.ts +3 -3
  37. package/dist/lib/extension-specifier.d.ts +23 -6
  38. package/dist/lib/manifest.d.ts +15 -1
  39. package/dist/lib/markdown/assemble.d.ts +4 -3
  40. package/dist/lib/markdown/math.d.ts +1 -1
  41. package/dist/lib/npm-plugin-installer.d.ts +1 -1
  42. package/dist/lib/page-size.d.ts +34 -27
  43. package/dist/lib/plugin-vendor.d.ts +11 -3
  44. package/dist/lib/printsafe.d.ts +1 -0
  45. package/dist/lib/project-gitignore.d.ts +2 -2
  46. package/dist/lib/project-scaffold.d.ts +5 -7
  47. package/dist/lib/project-templates.d.ts +9 -3
  48. package/dist/lib/snippets.d.ts +2 -1
  49. package/dist/lib/style-resolver.d.ts +1 -2
  50. package/dist/manifest-8afhrk30.yaml +19 -0
  51. package/dist/manifest-d4f9q79e.yaml +23 -0
  52. package/dist/manifest-gf13sjp8.yaml +27 -0
  53. package/dist/manifest-my7mdv8j.yaml +23 -0
  54. package/dist/{manifest.schema-9aeaered.json → manifest.schema-650pe0d1.json} +17 -5
  55. package/dist/{new-xhq8wczn.js → new-s4zcaepg.js} +22 -21
  56. package/dist/{package-aenwjew3.json → package-awwkt7sa.json} +0 -1
  57. package/dist/{page-rules-nsj3aw42.css → page-rules-fj09xct5.css} +12 -40
  58. package/dist/{plugin.test.js-xgxrd8hf.tpl → plugin.test.js-gxzqqfdh.tpl} +0 -1
  59. package/dist/{plugins-9en8m9y0.js → plugins-4vaf27n9.js} +4 -4
  60. package/dist/{plugins-gf0gkjmv.js → plugins-80emnf31.js} +2 -2
  61. package/dist/{preflight-negnxg7b.js → preflight-c7w1xdn3.js} +9 -10
  62. package/dist/{preview-cep2s9cg.js → preview-mzytcwyk.js} +9 -10
  63. package/dist/{publish-xery6qc6.js → publish-fez14ad4.js} +9 -10
  64. package/dist/render.js +24 -561
  65. package/dist/schema/manifest.types.d.ts +22 -0
  66. package/dist/{source-provider-qbfknhyy.js → source-provider-98k5deda.js} +2 -2
  67. package/dist/{source-provider-2r74a7wp.js → source-provider-knmcam50.js} +2 -2
  68. package/dist/{tokens-7984461t.css → tokens-ye64p899.css} +5 -12
  69. package/dist/{validate-3d0sgf4g.js → validate-p2chr6hr.js} +9 -10
  70. package/package.json +1 -1
  71. package/dist/cli-fe43evmf.js +0 -212
  72. package/dist/index-dsvk7q6s.js +0 -229
  73. package/dist/manifest-8f10dr4f.yaml +0 -17
  74. package/dist/manifest-n1gh3qw5.yaml +0 -9
  75. package/dist/package-97gm31sg.json +0 -10
  76. package/dist/package-gag6ykwn.json +0 -10
  77. package/dist/package-nrhkwf2m.json +0 -10
  78. package/dist/package-zttkgydq.json +0 -10
  79. package/dist/theme-9c58td2d.css +0 -76
  80. package/dist/theme-h5recz6c.css +0 -61
  81. package/dist/theme-j2bagrfx.css +0 -59
  82. package/dist/theme-nn6d53zy.css +0 -71
@@ -1,22 +1,26 @@
1
1
  import {
2
+ BUNDLED_EXTENSIONS,
2
3
  EXTENSION_MANIFEST_FILENAME,
3
4
  TWO_VERSIONS_OPEN,
5
+ bundledExtension,
4
6
  extensionCarries,
5
7
  extensionStyleList,
8
+ isPathSpecifier,
6
9
  loadPlugin,
7
10
  log,
11
+ parseExtensionSpecifier,
12
+ pinnedNpmSpecifier,
8
13
  prettify,
9
14
  readExtensionMeta,
10
15
  resolveExtension,
11
16
  slugify
12
- } from "./index-2jpk25en.js";
17
+ } from "./index-0wee4408.js";
13
18
  import {
14
19
  BuildError,
15
- EXIT_CODES,
16
- getAssetsDir
17
- } from "./index-dsvk7q6s.js";
20
+ EXIT_CODES
21
+ } from "./index-46ycxe6r.js";
18
22
  import {
19
- PLUGINS_DIR,
23
+ EXTENSIONS_DIR,
20
24
  VENDORED_NPM_DIR,
21
25
  assertWindowsSafeRelativePath,
22
26
  isExactNpmVersion,
@@ -25,8 +29,9 @@ import {
25
29
  resolveVendoredPluginInstallRoot,
26
30
  vendoredNpmPluginPackageDir,
27
31
  vendoredNpmPluginRoot,
32
+ vendoredNpmPluginRoots,
28
33
  windowsPathKey
29
- } from "./index-r7jcsk6h.js";
34
+ } from "./index-5x42amh8.js";
30
35
  import {
31
36
  __require
32
37
  } from "./index-37x76zdn.js";
@@ -68,7 +73,7 @@ async function withFetchTimeout(options, run) {
68
73
  }
69
74
 
70
75
  // src/lib/extension-manager.ts
71
- import { cp, mkdir as mkdir3, readdir, readFile as readFile5, rm as rm3, stat } from "node:fs/promises";
76
+ import { readdir, readFile as readFile5, rm as rm3, stat } from "node:fs/promises";
72
77
  import { existsSync as existsSync3 } from "node:fs";
73
78
  import path4 from "node:path";
74
79
  import { isSeq as isSeq2, isMap, isScalar, YAMLMap, Scalar } from "yaml";
@@ -86,42 +91,85 @@ import { existsSync, statSync } from "node:fs";
86
91
  import { resolve, dirname } from "node:path";
87
92
  import { parse as parseYaml, YAMLParseError } from "yaml";
88
93
 
89
- // src/lib/extension-specifier.ts
90
- var BUNDLED_EXTENSIONS = [
91
- "markdown-it-mark",
92
- "markdown-it-sub",
93
- "markdown-it-sup",
94
- "markdown-it-abbr",
95
- "gutterpress-gfm-alerts",
96
- "gutterpress-math"
97
- ];
98
- function isBundledExtension(name) {
99
- return BUNDLED_EXTENSIONS.includes(name);
100
- }
101
- function isPathSpecifier(use) {
102
- return use.startsWith("./") || use.startsWith("../") || use.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(use);
103
- }
104
- function parseExtensionSpecifier(use) {
105
- const spec = use.trim();
106
- if (!spec) {
107
- throw new Error("An extension specifier is required — an npm package name, or a ./ path.");
108
- }
109
- if (isPathSpecifier(spec))
110
- return { kind: "path", path: spec };
111
- if (/\.(m?js|cjs)$/i.test(spec) || !spec.startsWith("@") && spec.includes("/")) {
112
- throw new Error(`"${spec}" looks like a path; write it as "./${spec}". A specifier that does not ` + "start with ./, ../ or / is an npm package name.");
113
- }
114
- const parsed = parseNpmPluginSpec(spec);
115
- if (isBundledExtension(parsed.name)) {
116
- if (parsed.selector !== undefined) {
117
- throw new Error(`"${parsed.name}" is bundled with Gutterpress and always resolves to the bundled copy — ` + `drop "@${parsed.selector}".`);
94
+ // src/engine/shared/print-area.ts
95
+ function isPositivePoints(value) {
96
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
97
+ }
98
+ function isNonNegativePoints(value) {
99
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
100
+ }
101
+ var round3 = (n) => Math.round(n * 1000) / 1000;
102
+ function cssLength(pt) {
103
+ const inches = round3(pt / 72);
104
+ return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
105
+ }
106
+ function formatPageSize(page) {
107
+ return `${cssLength(page.width)} ${cssLength(page.height)}`;
108
+ }
109
+ function printAreaMeta(page) {
110
+ const area = JSON.stringify({
111
+ width: page.width,
112
+ height: page.height,
113
+ bleed: page.bleed ?? 0,
114
+ safe: page.safe ?? 0,
115
+ artSafe: page.artSafe,
116
+ gutter: page.gutter,
117
+ preset: page.preset,
118
+ spec: page.spec
119
+ });
120
+ return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
121
+ }
122
+ function pageBoxes(area, side, scale = 1) {
123
+ const w = area.width * scale;
124
+ const h = area.height * scale;
125
+ const b = Math.max(0, area.bleed) * scale;
126
+ const s = Math.max(0, area.safe) * scale;
127
+ const a = Math.max(0, area.artSafe) * scale;
128
+ const g = Math.max(0, area.gutter) * scale;
129
+ const [textLeft, textRight] = side === "verso" ? [s, g] : [g, s];
130
+ const [artLeft, artRight] = side === "verso" ? [a, g] : [g, a];
131
+ const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
132
+ return {
133
+ sheet: { x: 0, y: 0, w, h },
134
+ trim,
135
+ safe: {
136
+ x: trim.x + textLeft,
137
+ y: trim.y + s,
138
+ w: Math.max(0, trim.w - textLeft - textRight),
139
+ h: Math.max(0, trim.h - 2 * s)
140
+ },
141
+ safeArt: {
142
+ x: trim.x + artLeft,
143
+ y: trim.y + a,
144
+ w: Math.max(0, trim.w - artLeft - artRight),
145
+ h: Math.max(0, trim.h - 2 * a)
146
+ },
147
+ bleed: b
148
+ };
149
+ }
150
+ var SEVERITY = { inside: 0, unsafe: 1, bleed: 2, "off-page": 3 };
151
+ function judgeRect(r, boxes, rule, tol = 0) {
152
+ const { sheet, trim } = boxes;
153
+ const safe = rule === "art" ? boxes.safeArt : boxes.safe;
154
+ const sides = [
155
+ ["left", -r.x, -safe.x, -trim.x, -sheet.x],
156
+ ["right", r.x + r.w, safe.x + safe.w, trim.x + trim.w, sheet.x + sheet.w],
157
+ ["top", -r.y, -safe.y, -trim.y, -sheet.y],
158
+ ["bottom", r.y + r.h, safe.y + safe.h, trim.y + trim.h, sheet.y + sheet.h]
159
+ ];
160
+ let worst = { kind: "inside", gap: 0, need: 0, edge: null };
161
+ let beyond = 0;
162
+ for (const [edge, ink, limit, trimEdge, sheetEdge] of sides) {
163
+ const past = ink - limit;
164
+ if (past <= tol || rule === "art" && ink >= sheetEdge - tol)
165
+ continue;
166
+ const kind = ink > sheetEdge + tol ? "off-page" : ink > trimEdge + tol ? "bleed" : "unsafe";
167
+ if (SEVERITY[kind] > SEVERITY[worst.kind] || kind === worst.kind && past > beyond) {
168
+ worst = { kind, gap: trimEdge - ink, need: trimEdge - limit, edge };
169
+ beyond = past;
118
170
  }
119
- return { kind: "bundled", name: parsed.name };
120
171
  }
121
- return parsed.selector === undefined ? { kind: "npm", name: parsed.name } : { kind: "npm", name: parsed.name, version: parsed.selector };
122
- }
123
- function pinnedNpmSpecifier(name, version) {
124
- return `${name}@${version}`;
172
+ return worst;
125
173
  }
126
174
 
127
175
  // src/constants.ts
@@ -184,7 +232,8 @@ var DTRPG_PRESET = {
184
232
  bleed: 9,
185
233
  safe: 36,
186
234
  artSafe: 18,
187
- gutter: 36
235
+ gutter: 36,
236
+ margin: undefined
188
237
  },
189
238
  ink: {
190
239
  maxTac: 240,
@@ -270,7 +319,8 @@ var BOOK_PRESET = {
270
319
  bleed: undefined,
271
320
  safe: 18,
272
321
  artSafe: undefined,
273
- gutter: undefined
322
+ gutter: undefined,
323
+ margin: undefined
274
324
  },
275
325
  ink: {
276
326
  maxTac: 400,
@@ -642,9 +692,28 @@ function resolveCustomPage(c, m) {
642
692
  bleed: c.page?.bleed ?? m.page?.bleed,
643
693
  safe: c.page?.safe ?? m.page?.safe,
644
694
  artSafe: c.page?.artSafe ?? m.page?.artSafe,
645
- gutter: c.page?.gutter ?? m.page?.gutter
695
+ gutter: c.page?.gutter ?? m.page?.gutter,
696
+ margin: c.page?.margin ?? m.page?.margin
646
697
  };
647
698
  }
699
+ var MARGIN_EDGES = ["top", "bottom", "inside", "outside"];
700
+ function validatePageMargin(margin, size) {
701
+ const edges = isPlainObject(margin) && Object.keys(margin).every((k) => MARGIN_EDGES.includes(k)) && MARGIN_EDGES.every((e) => isNonNegativePoints(margin[e]));
702
+ if (!edges) {
703
+ throw new UsageError("Manifest field `page.margin` must set all four edges in points (72pt = 1in), for example\n" + ` page:
704
+ margin:
705
+ top: 54
706
+ bottom: 54
707
+ inside: 54
708
+ outside: 54
709
+ ` + `got ${JSON.stringify(margin)}.`);
710
+ }
711
+ const m = margin;
712
+ if (m.inside + m.outside >= size.width || m.top + m.bottom >= size.height) {
713
+ throw new UsageError(`Manifest field \`page.margin\` leaves no room for text on a ${size.width} x ${size.height}pt page ` + `(inside ${m.inside} + outside ${m.outside} across, top ${m.top} + bottom ${m.bottom} down).`);
714
+ }
715
+ return m;
716
+ }
648
717
  function resolveWithPreset(cliOverrides, manifest, preset) {
649
718
  const m = manifest;
650
719
  const c = cliOverrides;
@@ -697,6 +766,9 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
697
766
  throw new UsageError("Manifest field `extensions` must be a list of specifiers.");
698
767
  }
699
768
  const extensions = rawExtensions.map((entry, index) => ({ entry, index })).filter(({ entry }) => typeof entry === "string" || entry?.enabled !== false).map(({ entry, index }) => normalizeExtensionEntry(entry, index));
769
+ const page = preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m);
770
+ if (page.margin !== undefined && page.margin !== null)
771
+ validatePageMargin(page.margin, page);
700
772
  return {
701
773
  title: c.title ?? m.title ?? "Document",
702
774
  authors: c.authors ?? m.authors ?? [],
@@ -709,7 +781,7 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
709
781
  source: mergeShape(c.source, m.source, preset.source),
710
782
  pdfx: mergeShape(c.pdfx, m.pdfx, preset.pdfx),
711
783
  print: mergeShape(c.print, m.print, preset.print),
712
- page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
784
+ page,
713
785
  ink: mergeShape(c.ink, m.ink, preset.ink),
714
786
  lint: {
715
787
  ...mergeShape(c.lint, m.lint, preset.lint),
@@ -1324,13 +1396,13 @@ async function removeEmptyDirs(dirs) {
1324
1396
  }
1325
1397
  }
1326
1398
  async function prepareVendorParent(projectDir, name) {
1327
- const plugins = path2.join(projectDir, PLUGINS_DIR);
1328
- const npm = path2.join(plugins, VENDORED_NPM_DIR);
1399
+ const extensions = path2.join(projectDir, EXTENSIONS_DIR);
1400
+ const npm = path2.join(extensions, VENDORED_NPM_DIR);
1329
1401
  const packageParent = path2.dirname(vendoredNpmPluginRoot(projectDir, name, "0.0.0"));
1330
1402
  await mkdir2(projectDir, { recursive: true });
1331
1403
  const createdDirs = [];
1332
1404
  try {
1333
- for (const dir of [plugins, npm, packageParent]) {
1405
+ for (const dir of [extensions, npm, packageParent]) {
1334
1406
  if (await ensureDirectory(dir))
1335
1407
  createdDirs.push(dir);
1336
1408
  }
@@ -1449,7 +1521,7 @@ function ignoreStatus(gitignore, entry) {
1449
1521
  return "ignored";
1450
1522
  return dir.unignored || file.unignored ? "negated" : "open";
1451
1523
  }
1452
- var PROJECT_GITIGNORE_ENTRIES = ["dist/", "plugins/npm/"];
1524
+ var PROJECT_GITIGNORE_ENTRIES = ["dist/", "extensions/npm/"];
1453
1525
  async function ensureProjectGitignore(projectDir) {
1454
1526
  const gitignorePath = path3.join(projectDir, ".gitignore");
1455
1527
  let existing = null;
@@ -1477,40 +1549,6 @@ async function ensureProjectGitignore(projectDir) {
1477
1549
  }
1478
1550
 
1479
1551
  // src/lib/extension-manager.ts
1480
- var EXTENSIONS_DIR = "extensions";
1481
- var RECOMMENDED_EXTENSIONS = [
1482
- {
1483
- use: "markdown-it-mark",
1484
- label: "Highlight",
1485
- description: "Highlight text by wrapping it in `==double equals==`."
1486
- },
1487
- {
1488
- use: "markdown-it-sub",
1489
- label: "Subscript",
1490
- description: "Lower text below the line, like the 2 in `H~2~O`."
1491
- },
1492
- {
1493
- use: "markdown-it-sup",
1494
- label: "Superscript",
1495
- description: "Raise text above the line, like the th in `29^th^`."
1496
- },
1497
- {
1498
- use: "markdown-it-abbr",
1499
- label: "Abbreviations",
1500
- description: "Explain an abbreviation when readers hover over it. Define it once, like `*[GPS]: Global Positioning System`."
1501
- },
1502
- {
1503
- use: "gutterpress-gfm-alerts",
1504
- label: "Callouts",
1505
- description: "Turn a quote into a boxed note: start it with `> [!NOTE]` (or `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`)."
1506
- },
1507
- {
1508
- use: "gutterpress-math",
1509
- label: "Math",
1510
- description: "Write LaTeX math between dollar signs: `$E = mc^2$` in a line, or `$$` … `$$` on lines of their own."
1511
- }
1512
- ];
1513
- var BUILT_IN_STYLE_SET_IDS = ["clean-book", "zine", "technical-doc", "academic"];
1514
1552
  var mutationQueues = new Map;
1515
1553
  function projectKey(projectDir) {
1516
1554
  const resolved = path4.resolve(projectDir);
@@ -1602,7 +1640,6 @@ async function describeFolder(dir, base, fallbackLabel, labelFrom = prettify) {
1602
1640
  ...meta.description?.trim() ? { description: meta.description.trim() } : {},
1603
1641
  ...meta.author?.trim() ? { author: meta.author.trim() } : {},
1604
1642
  ...meta.preview !== undefined ? { preview: meta.preview } : {},
1605
- ...meta.tokensFile?.trim() ? { tokensFile: meta.tokensFile.trim() } : {},
1606
1643
  ...styles.length > 0 ? { styles } : {},
1607
1644
  carries: extensionCarries(meta),
1608
1645
  dir
@@ -1626,15 +1663,14 @@ async function describeEntry(projectDir, raw) {
1626
1663
  return withWarnings({ ...common, kind: "npm", name: raw.use, label: raw.use, carries: NO_CARRIES });
1627
1664
  }
1628
1665
  if (parsed.kind === "bundled") {
1629
- const bundledName = parsed.name;
1630
- const rec = RECOMMENDED_EXTENSIONS.find((r) => r.use === bundledName);
1666
+ const bundled = bundledExtension(parsed.name);
1631
1667
  return withWarnings({
1632
1668
  ...common,
1633
1669
  kind: "bundled",
1634
- name: bundledName,
1635
- label: rec?.label ?? bundledName,
1636
- ...rec ? { description: rec.description } : {},
1637
- carries: { ...NO_CARRIES, markdown: true }
1670
+ name: parsed.name,
1671
+ label: bundled.label,
1672
+ description: bundled.description,
1673
+ carries: { ...NO_CARRIES, ...bundled.css !== undefined ? { styles: true } : { markdown: true } }
1638
1674
  });
1639
1675
  }
1640
1676
  if (parsed.kind === "path") {
@@ -1779,9 +1815,10 @@ async function removeExtension(projectDir, use) {
1779
1815
  return;
1780
1816
  }
1781
1817
  if (parsed.kind === "npm" && parsed.version) {
1782
- const root = vendoredNpmPluginRoot(projectDir, parsed.name, parsed.version);
1783
- await rm3(root, { recursive: true, force: true });
1784
- await removeEmptyDirs([path4.dirname(root)]);
1818
+ for (const root of vendoredNpmPluginRoots(projectDir, parsed.name, parsed.version)) {
1819
+ await rm3(root, { recursive: true, force: true });
1820
+ await removeEmptyDirs([path4.dirname(root)]);
1821
+ }
1785
1822
  }
1786
1823
  });
1787
1824
  }
@@ -1899,8 +1936,8 @@ async function vendorNpmExtension(projectDir, input, exportName, options, commit
1899
1936
  });
1900
1937
  try {
1901
1938
  const { negated } = await ensureProjectGitignore(projectDir);
1902
- if (negated.includes("plugins/npm/")) {
1903
- warnings.push("Your .gitignore re-includes plugins/npm/, so downloaded extensions will be committed with " + "your book. They shouldn't be: the manifest pins each version and a build downloads a " + "missing copy again.");
1939
+ if (negated.includes("extensions/npm/")) {
1940
+ warnings.push("Your .gitignore re-includes extensions/npm/, so downloaded extensions will be committed with " + "your book. They shouldn't be: the manifest pins each version and a build downloads a " + "missing copy again.");
1904
1941
  }
1905
1942
  } catch (error) {
1906
1943
  warnings.push(`The extension was installed, but .gitignore could not be updated to keep it out of version history: ` + `${error instanceof Error ? error.message : String(error)}`);
@@ -1926,24 +1963,32 @@ async function pinnedVersionsOf(projectDir, name) {
1926
1963
  return versions;
1927
1964
  }
1928
1965
  async function pruneStaleVersions(projectDir, name, keep) {
1929
- const parent = path4.dirname(vendoredNpmPluginRoot(projectDir, name, "0.0.0"));
1966
+ const parents = vendoredNpmPluginRoots(projectDir, name, "0.0.0").map((root) => path4.dirname(root));
1930
1967
  try {
1931
- for (const entry of await readdir(parent, { withFileTypes: true })) {
1932
- if (!entry.isDirectory())
1933
- continue;
1934
- let version;
1968
+ for (const parent of parents) {
1969
+ let entries;
1935
1970
  try {
1936
- version = decodeURIComponent(entry.name);
1937
- } catch {
1938
- continue;
1971
+ entries = await readdir(parent, { withFileTypes: true });
1972
+ } catch (error) {
1973
+ if (error.code === "ENOENT")
1974
+ continue;
1975
+ throw error;
1976
+ }
1977
+ for (const entry of entries) {
1978
+ if (!entry.isDirectory())
1979
+ continue;
1980
+ let version;
1981
+ try {
1982
+ version = decodeURIComponent(entry.name);
1983
+ } catch {
1984
+ continue;
1985
+ }
1986
+ if (!isExactNpmVersion(version) || keep.includes(version))
1987
+ continue;
1988
+ await rm3(path4.join(parent, entry.name), { recursive: true, force: true });
1939
1989
  }
1940
- if (!isExactNpmVersion(version) || keep.includes(version))
1941
- continue;
1942
- await rm3(path4.join(parent, entry.name), { recursive: true, force: true });
1943
1990
  }
1944
1991
  } catch (error) {
1945
- if (error.code === "ENOENT")
1946
- return [];
1947
1992
  return [
1948
1993
  `The extension was installed, but a previous version's downloaded copy could not be removed: ` + `${error instanceof Error ? error.message : String(error)}`
1949
1994
  ];
@@ -2029,18 +2074,13 @@ async function updateExtensions(projectDir, options = {}) {
2029
2074
  }
2030
2075
  return updated;
2031
2076
  }
2032
- async function listBuiltInStyleSets() {
2033
- const assets = await getAssetsDir();
2034
- const out = [];
2035
- for (const id of BUILT_IN_STYLE_SET_IDS) {
2036
- const meta = await readExtensionMeta(path4.join(assets, "themes", id));
2037
- out.push({
2038
- id,
2039
- name: prettify(meta.name?.trim() || id),
2040
- description: meta.description?.trim() || ""
2041
- });
2042
- }
2043
- return out;
2077
+ function listBundledExtensions() {
2078
+ return BUNDLED_EXTENSIONS.map((b) => ({
2079
+ use: b.name,
2080
+ label: b.label,
2081
+ description: b.description,
2082
+ look: b.css !== undefined
2083
+ }));
2044
2084
  }
2045
2085
  async function uniqueExtensionId(projectDir, base) {
2046
2086
  const slug = slugify(base) || "extension";
@@ -2050,20 +2090,6 @@ async function uniqueExtensionId(projectDir, base) {
2050
2090
  }
2051
2091
  return candidate;
2052
2092
  }
2053
- async function addBuiltInStyleSet(projectDir, id) {
2054
- if (!BUILT_IN_STYLE_SET_IDS.includes(id)) {
2055
- throw new Error(`Unknown built-in look "${id}" (one of: ${BUILT_IN_STYLE_SET_IDS.join(", ")}).`);
2056
- }
2057
- return withMutationLock(projectDir, async () => {
2058
- const dest = path4.join(projectDir, EXTENSIONS_DIR, id);
2059
- if (!existsSync3(dest)) {
2060
- const src = path4.join(await getAssetsDir(), "themes", id);
2061
- await mkdir3(path4.dirname(dest), { recursive: true });
2062
- await cp(src, dest, { recursive: true });
2063
- }
2064
- return addExtensionUnlocked(projectDir, `./${EXTENSIONS_DIR}/${id}`, {});
2065
- });
2066
- }
2067
2093
  async function validateProjectExtensions(projectDir) {
2068
2094
  const out = [];
2069
2095
  for (const entry of await listProjectExtensions(projectDir)) {
@@ -2087,19 +2113,25 @@ async function validateProjectExtensions(projectDir) {
2087
2113
  return out;
2088
2114
  }
2089
2115
  async function readExtensionCss(projectDir, use) {
2116
+ const direct = bundledExtension(use)?.css;
2117
+ if (direct !== undefined)
2118
+ return direct;
2090
2119
  const entry = await describeExtension(projectDir, use);
2091
2120
  if (!entry)
2092
2121
  throw new Error(`Extension "${use}" is not in the manifest.`);
2122
+ return readEntryCss(entry);
2123
+ }
2124
+ async function readEntryCss(entry) {
2125
+ const direct = bundledExtension(entry.use)?.css;
2126
+ if (direct !== undefined)
2127
+ return direct;
2093
2128
  if (!entry.dir)
2094
- throw new Error(`Extension "${use}" has no stylesheets to preview.`);
2129
+ throw new Error(`Extension "${entry.use}" has no stylesheets to preview.`);
2095
2130
  const meta = await readExtensionMeta(entry.dir);
2096
- const sheets = resolveExtension(entry.dir, meta, `Extension "${use}"`).styles ?? [];
2131
+ const sheets = resolveExtension(entry.dir, meta, `Extension "${entry.use}"`).styles ?? [];
2097
2132
  if (sheets.length === 0)
2098
- throw new Error(`Extension "${use}" declares no stylesheets.`);
2099
- const parts = [];
2100
- for (const sheet of sheets)
2101
- parts.push(await readFile5(sheet, "utf8"));
2102
- return parts.join(`
2133
+ throw new Error(`Extension "${entry.use}" declares no stylesheets.`);
2134
+ return (await Promise.all(sheets.map((sheet) => readFile5(sheet, "utf8")))).join(`
2103
2135
 
2104
2136
  `);
2105
2137
  }
@@ -2221,4 +2253,4 @@ async function restoreForCommand(dir, options) {
2221
2253
  log.warn(problem);
2222
2254
  }
2223
2255
 
2224
- export { BUNDLED_EXTENSIONS, isBundledExtension, isPathSpecifier, parseExtensionSpecifier, DEBOUNCE, UsageError, resolvePort, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, resolveTargets, applyDefaultPdfStrictChecks, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, resolveConfigForTarget, FriendlyHttpError, withFetchTimeout, loadManifestDoc, writeManifestDoc, ensureSeq, scalarString, DEFAULT_NPM_REGISTRY, npmRegistryUrl, ensureProjectGitignore, EXTENSIONS_DIR, RECOMMENDED_EXTENSIONS, BUILT_IN_STYLE_SET_IDS, sameExtension, listProjectExtensions, describeExtension, extensionConfigFor, setExtensionEnabled, reorderExtensions, removeExtension, addExtension, listNpmVersions, checkExtensionUpdates, updateExtensions, listBuiltInStyleSets, uniqueExtensionId, addBuiltInStyleSet, validateProjectExtensions, readExtensionCss, restorePinnedExtensions, restoreFailureMessage, restoreOfflineMessage, restoreForCommand };
2256
+ export { isPositivePoints, isNonNegativePoints, round3, cssLength, formatPageSize, printAreaMeta, pageBoxes, judgeRect, DEBOUNCE, UsageError, resolvePort, PRESET_IDS, PRESETS, TARGETS, TARGET_IDS, publishTargetFor, resolveTargets, applyDefaultPdfStrictChecks, MANIFEST_FILENAMES, hasProjectManifest, loadManifest, loadManifestWithPath, resolveConfig, resolveConfigForTarget, MARGIN_EDGES, validatePageMargin, FriendlyHttpError, withFetchTimeout, loadManifestDoc, writeManifestDoc, scalarString, DEFAULT_NPM_REGISTRY, npmRegistryUrl, ensureProjectGitignore, sameExtension, listProjectExtensions, describeExtension, extensionConfigFor, setExtensionEnabled, reorderExtensions, removeExtension, addExtension, listNpmVersions, checkExtensionUpdates, updateExtensions, listBundledExtensions, uniqueExtensionId, validateProjectExtensions, readExtensionCss, readEntryCss, restorePinnedExtensions, restoreFailureMessage, restoreOfflineMessage, restoreForCommand };
@@ -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
  }