gutterpress 0.10.8 → 0.10.9

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 (59) hide show
  1. package/README.md +93 -90
  2. package/dist/{README-aaqapam1.md → README-6c7fda1s.md} +9 -7
  3. package/dist/{README-mdq34cmb.md → README-jntqyj1z.md} +19 -10
  4. package/dist/api/index.d.ts +8 -8
  5. package/dist/api/index.js +46 -46
  6. package/dist/{audit-m3ce9pvq.js → audit-fq7s1gsz.js} +4 -4
  7. package/dist/{build-qnecb3a1.js → build-dyat3fqd.js} +5 -8
  8. package/dist/checks/source/dangling-links.d.ts +18 -0
  9. package/dist/checks/source/index.d.ts +1 -0
  10. package/dist/checks/source/local-ref-parser.d.ts +13 -0
  11. package/dist/{cli-1hx14cgk.js → cli-8pg731p5.js} +2941 -2927
  12. package/dist/{cli-4vtgt2ng.js → cli-bx950tb4.js} +7 -7
  13. package/dist/{cli-pfpb1t3h.js → cli-bzxqtvrk.js} +1 -2
  14. package/dist/{cli-vcv5ewqj.js → cli-rh05byck.js} +2 -9
  15. package/dist/{cli-vxf151ea.js → cli-ypfvnhag.js} +1 -1
  16. package/dist/cli.js +24 -53
  17. package/dist/{doctor-qvqye1wd.js → doctor-yr85n080.js} +2 -2
  18. package/dist/{engine-daad33qn.js → engine-19mshh07.js} +2 -2
  19. package/dist/{engine-db8632w6.js → engine-d46wgg99.js} +3 -3
  20. package/dist/ext-jrg8c48s.js +268 -0
  21. package/dist/{index-rpt4vtby.js → index-dhgzkhs7.js} +1 -1
  22. package/dist/{index-dfw8hn8q.js → index-mey5vraz.js} +5852 -5899
  23. package/dist/{index-9pbaznww.js → index-zwcv1njy.js} +7 -7
  24. package/dist/index.js +47 -47
  25. package/dist/lib/build-runner.d.ts +0 -7
  26. package/dist/lib/build-staging.d.ts +36 -0
  27. package/dist/lib/cli-args.d.ts +0 -7
  28. package/dist/lib/{theme-import.d.ts → extension-import.d.ts} +20 -11
  29. package/dist/lib/extension-manager.d.ts +170 -0
  30. package/dist/lib/extension-manifest.d.ts +25 -17
  31. package/dist/lib/extension-specifier.d.ts +31 -0
  32. package/dist/lib/manifest-doc.d.ts +3 -9
  33. package/dist/lib/markdown/markers.d.ts +2 -2
  34. package/dist/lib/markdown/plugins.d.ts +4 -4
  35. package/dist/lib/missing-asset-placeholder.d.ts +11 -0
  36. package/dist/lib/presets.d.ts +1 -1
  37. package/dist/lib/publish/providers/gdrive.d.ts +1 -1
  38. package/dist/lib/snippets.d.ts +11 -15
  39. package/dist/lib/style-resolver.d.ts +1 -1
  40. package/dist/{lint-3p1s8agk.js → lint-enasqbqx.js} +4 -4
  41. package/dist/{manifest.schema-1q35yxs9.json → manifest.schema-hsn8rsgr.json} +18 -53
  42. package/dist/{new-xffarp40.js → new-r9c99wsn.js} +11 -12
  43. package/dist/{plugin.test.js-mv0ghv5n.tpl → plugin.test.js-npnfhyca.tpl} +0 -1
  44. package/dist/{preflight-z6xpty9w.js → preflight-shk7vqbs.js} +4 -4
  45. package/dist/preview/file-watcher.d.ts +4 -4
  46. package/dist/preview/lifecycle.d.ts +1 -1
  47. package/dist/{preview-xa1n7why.js → preview-52918mxk.js} +6 -10
  48. package/dist/{publish-zgmr99kd.js → publish-8s60vhx7.js} +4 -4
  49. package/dist/schema/manifest.types.d.ts +28 -44
  50. package/dist/{theme-cd6efzjw.json → theme-etj4gxck.json} +4 -1
  51. package/dist/{theme-tqfypc1w.json → theme-s2rg7xe7.json} +4 -1
  52. package/dist/{theme-xt0hjb1b.json → theme-vktedy8q.json} +4 -1
  53. package/dist/types.d.ts +0 -7
  54. package/dist/{validate-43hqgzz5.js → validate-awccx32r.js} +4 -4
  55. package/package.json +1 -1
  56. package/dist/lib/plugin-manager.d.ts +0 -106
  57. package/dist/lib/theme-manager.d.ts +0 -242
  58. package/dist/plugin-b05ck7zd.js +0 -126
  59. package/dist/theme-m9dtxkhj.js +0 -323
@@ -8,8 +8,8 @@ export declare const LEGACY_THEME_MANIFEST_FILENAME = "theme.json";
8
8
  /**
9
9
  * Parsed extension metadata — a superset of the pre-#241 theme metadata
10
10
  * shape. Every field is optional, and a folder declaring only the theme-era
11
- * fields (`name`/`author`/`description`/`preview`/`styles`/`engineStyles`/
12
- * `tokensFile`) IS a valid extension: "theme ≡ extension with only styles."
11
+ * fields (`name`/`author`/`description`/`preview`/`styles`/`tokensFile`) IS
12
+ * a valid extension: "theme ≡ extension with only styles."
13
13
  * Symmetrically, a bare `.js` plugin file (no metadata file at all) never
14
14
  * constructs one of these — "plugin ≡ extension with only markdown" needs no
15
15
  * metadata file until it wants more than a function (see `plugins.ts`'s
@@ -25,16 +25,11 @@ export interface ExtensionMetadata {
25
25
  /**
26
26
  * Ordered stylesheets, relative to the extension folder. Absent/empty means
27
27
  * "no styles declared" here — {@link extensionStyleList} does NOT default
28
- * to `["theme.css"]`; that default is theme-manager.ts's OWN, layered on
28
+ * to `["theme.css"]`; that default is {@link extensionStyleListWithDefault}'s, layered on
29
29
  * top for its theme-shaped callers (a plain markdown-only extension folder
30
30
  * has no reason to require a `theme.css` it never declared).
31
31
  */
32
32
  styles?: string[];
33
- /** Engine-conditional sheets, relative to the extension folder, appended
34
- * after `styles` (mirrors the manifest's own `engineStyles.native`). */
35
- engineStyles?: {
36
- native?: string[];
37
- };
38
33
  /** Which declared sheet (a path from `styles`) carries the author-facing
39
34
  * `:root` token surface for the Design panel's guided editor. Purely
40
35
  * advisory — nothing in this module enforces or existence-checks it,
@@ -80,14 +75,26 @@ export interface ExtensionMetadata {
80
75
  export declare function readExtensionMeta(dir: string): Promise<ExtensionMetadata>;
81
76
  /**
82
77
  * An extension's declared stylesheets, relative to its folder, in cascade
83
- * order. UNLIKE `theme-manager.ts`'s `themeStyleList` (which layers a
78
+ * order. UNLIKE {@link extensionStyleListWithDefault} (which layers a
84
79
  * `["theme.css"]` default on top of this for its theme-shaped callers), an
85
80
  * absent/empty `styles` here means exactly "none declared" — a markdown-only
86
81
  * extension folder must not be forced to carry a `theme.css` it never wanted.
87
82
  */
88
83
  export declare function extensionStyleList(meta: ExtensionMetadata): string[];
89
- /** An extension's declared engine-conditional sheets, relative to its folder. */
90
- export declare function extensionEngineStyleList(meta: ExtensionMetadata): string[];
84
+ /**
85
+ * `styles`, with the theme-era default (#265): a folder that declares none
86
+ * but holds a `theme.css` IS a one-sheet look — every theme published before
87
+ * `styles` existed. A folder with neither still declares nothing.
88
+ */
89
+ export declare function extensionStyleListWithDefault(meta: ExtensionMetadata, dir: string): string[];
90
+ /** What an extension declares, by field — the desktop's one-list filter. */
91
+ export interface ExtensionCarries {
92
+ markdown: boolean;
93
+ styles: boolean;
94
+ snippets: boolean;
95
+ components: boolean;
96
+ }
97
+ export declare function extensionCarries(meta: ExtensionMetadata, dir: string): ExtensionCarries;
91
98
  /**
92
99
  * True when a declared relative path escapes its own folder (absolute, or a
93
100
  * `..` segment) — the traversal shape every containment check in this
@@ -111,24 +118,27 @@ export declare function pathEscapesFolder(rel: string): boolean;
111
118
  * absolute entry would make apply/load read a file from anywhere on disk.
112
119
  *
113
120
  * Generalizes the pre-#241 theme-only `assertThemeSheetsContained` (still
114
- * exported under that name from `theme-manager.ts`, now a re-export of this
121
+ * once exported under that name from the theme manager, now this
115
122
  * function) to the three new fields: a `gutterpress.json`-formatted theme
116
123
  * folder can declare `markdown`/`components`/`snippets` too, so the SAME
117
- * write-boundary guard must cover them, not just `styles`/`engineStyles`.
124
+ * write-boundary guard must cover them, not just `styles`.
118
125
  * `tokensFile` is included even though it is advisory/unenforced elsewhere —
119
126
  * defense in depth against a future consumer reading it unchecked.
120
127
  *
121
128
  * A WRITE-BOUNDARY guard, not a read-path check (mirrors the theme-only
122
129
  * predecessor): callers invoke this before copying anything or wiring a
123
130
  * manifest, never from a plain listing/read path, so one hand-edited
124
- * metadata file cannot take down listing every extension.
131
+ * metadata file cannot take down listing every extension. The removed
132
+ * `engineStyles` field (#266) is rejected here for the same reason: the
133
+ * error names the replacement, and it fires only where something would be
134
+ * copied or wired, never while listing.
125
135
  */
126
136
  export declare function assertExtensionContained(meta: ExtensionMetadata): void;
127
137
  /**
128
138
  * An extension's declared paths, resolved to absolute, existence-checked
129
139
  * filesystem paths — every list/single-path field goes through the SAME
130
140
  * {@link resolveDeclaredStyles} a plugin's `styles` export and a theme's
131
- * `styles`/`engineStyles.native` already resolve through, so a broken
141
+ * `styles` already resolve through, so a broken
132
142
  * declaration (a missing file) throws HERE, at load/apply time, instead of
133
143
  * failing silently deep in the render pipeline (or never, for `components`/
134
144
  * `snippets`, which nothing yet reads — #240/#242).
@@ -142,8 +152,6 @@ export interface ResolvedExtension {
142
152
  markdown?: string;
143
153
  /** Absolute paths, in cascade order, when any are declared. */
144
154
  styles?: string[];
145
- /** Absolute paths, in cascade order, when any are declared. */
146
- engineStyles?: string[];
147
155
  /** Declared-relative path of the `:root` token surface — advisory. */
148
156
  tokensFile?: string;
149
157
  /** Absolute path to the component catalog file, when declared (#242). */
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The markdown features compiled into Gutterpress (`BUILTIN_OPTIONAL_PLUGINS`,
3
+ * `markdown/renderer.ts` — a test pins the two lists together). Writing one
4
+ * of these names installs nothing: the loader resolves it from the bundle.
5
+ */
6
+ export declare const BUNDLED_EXTENSIONS: readonly ["markdown-it-mark", "markdown-it-sub", "markdown-it-sup", "markdown-it-abbr", "gutterpress-gfm-alerts"];
7
+ export type BundledExtensionName = (typeof BUNDLED_EXTENSIONS)[number];
8
+ export declare function isBundledExtension(name: string): name is BundledExtensionName;
9
+ /** `./x`, `../x`, `/x`, or a Windows drive path (`C:\x`). Nothing else is a path. */
10
+ export declare function isPathSpecifier(use: string): boolean;
11
+ export type ParsedExtensionSpecifier = {
12
+ kind: "path";
13
+ path: string;
14
+ } | {
15
+ kind: "bundled";
16
+ name: string;
17
+ } | {
18
+ kind: "npm";
19
+ name: string;
20
+ version?: string;
21
+ };
22
+ /**
23
+ * Parse one specifier. Throws a plain `Error` whose message says what to
24
+ * write instead — callers wrap it with the entry's position.
25
+ */
26
+ export declare function parseExtensionSpecifier(use: string): ParsedExtensionSpecifier;
27
+ /**
28
+ * The specifier an `ext add` writes back for an npm install: the exact
29
+ * version is part of the specifier, so the manifest reads `name@1.2.3`.
30
+ */
31
+ export declare function pinnedNpmSpecifier(name: string, version: string): string;
@@ -9,14 +9,8 @@ export declare function loadManifestDoc(projectDir: string): Promise<{
9
9
  }>;
10
10
  /** Atomically write the doc back, creating the project dir if needed. */
11
11
  export declare function writeManifestDoc(file: string, doc: Document.Parsed): Promise<void>;
12
- /**
13
- * The named sequence node, creating (and attaching) an empty one if missing.
14
- * `key` is a single top-level key (the original, still-exact behavior) OR a
15
- * path for a nested key (e.g. `["engineStyles", "native"]`, #239) — the
16
- * `getIn`/`setIn` branch auto-vivifies any missing intermediate map, exactly
17
- * like a hand-written `engineStyles: { native: [...] }` would parse.
18
- */
19
- export declare function ensureSeq(doc: Document.Parsed, key: string | readonly string[]): YAMLSeq;
12
+ /** The named top-level sequence node, creating (and attaching) an empty one if missing. */
13
+ export declare function ensureSeq(doc: Document.Parsed, key: string): YAMLSeq;
20
14
  /**
21
15
  * Unwrap a yaml seq item (or `getIn`-style Pair) to its string value: `null`
22
16
  * when the item isn't a string. Handles both a bare Scalar/Pair-shaped node
@@ -25,7 +19,7 @@ export declare function ensureSeq(doc: Document.Parsed, key: string | readonly s
25
19
  * array entry would be).
26
20
  *
27
21
  * ARCH finding #25: this was two near-duplicate helpers — `unwrapScalar`
28
- * (manifest-config.ts) and `styleHrefOf` (theme-manager.ts) — with the same
22
+ * (manifest-config.ts) and `styleHrefOf` (the theme manager, since retired) — with the same
29
23
  * shape-sniffing logic. One implementation here, consumed by both.
30
24
  */
31
25
  export declare function scalarString(item: unknown): string | null;
@@ -27,8 +27,8 @@ export default function plugin(md: any, pluginOptions?: {}): void;
27
27
  * opposite and was stale — 2026-09-01 CSS architecture review, C4):
28
28
  * consumers inject this FIRST, wrapped in `@layer gp.marker` (assemble.ts
29
29
  * declares `@layer gp.marker, gp.vocab;` before both core blocks). Author
30
- * CSS — plugin CSS, every project stylesheet, anything loaded via
31
- * `engineStyles.native` — stays UNLAYERED, and per the CSS Cascade Layers
30
+ * CSS — plugin CSS, every project stylesheet — stays UNLAYERED, and per
31
+ * the CSS Cascade Layers
32
32
  * spec unlayered CSS always wins over layered CSS regardless of selector
33
33
  * specificity. That is what makes "author wins" true now, not injection
34
34
  * order. The `:where()` wrapping on the break/orphan/sizing rules below is
@@ -1,4 +1,4 @@
1
- import type { ResolvedPluginConfig } from "../../schema/manifest.types";
1
+ import type { ResolvedExtensionConfig } from "../../schema/manifest.types";
2
2
  import type { LoadedPlugin } from "./renderer";
3
3
  export type { GutterpressMarkerDeclaration, GutterpressMarkerLabel, GutterpressMarkerTable, GutterpressPlugin, GutterpressPluginMetadata, GutterpressPluginExport, LoadedPlugin, } from "./renderer";
4
4
  export { applyPlugins, collectPluginCss, collectPluginStylePaths } from "./renderer";
@@ -7,7 +7,7 @@ export declare function __setVendorSnapshotHookForTests(hook?: (sourceRoot: stri
7
7
  export declare function clearVendoredPluginResolver(baseDir: string, packageName: string, version: string): void;
8
8
  /** Test-only: reset the path-plugin cache between test cases. */
9
9
  export declare function __resetPathPluginCacheForTests(): void;
10
- export declare function loadPlugin(config: ResolvedPluginConfig, baseDir: string): Promise<LoadedPlugin>;
10
+ export declare function loadPlugin(config: ResolvedExtensionConfig, baseDir: string): Promise<LoadedPlugin>;
11
11
  /**
12
12
  * Load all plugins from the resolved configuration.
13
13
  *
@@ -30,7 +30,7 @@ export declare function loadPlugin(config: ResolvedPluginConfig, baseDir: string
30
30
  * unedited one is never re-imported, correct in both a one-shot CLI build and
31
31
  * the long-lived Electron host.
32
32
  */
33
- export declare function loadPlugins(configs: ResolvedPluginConfig[], baseDir: string, onError?: (pluginRef: string, error: Error) => void): Promise<LoadedPlugin[]>;
33
+ export declare function loadPlugins(configs: ResolvedExtensionConfig[], baseDir: string, onError?: (pluginRef: string, error: Error) => void): Promise<LoadedPlugin[]>;
34
34
  /** Result of {@link loadPluginsWithCss}: loaded plugins ready for `applyPlugins`
35
35
  * plus their concatenated CSS ready for injection into the rendered document. */
36
36
  export interface LoadedPluginsWithCss {
@@ -62,4 +62,4 @@ export interface LoadedPluginsWithCss {
62
62
  * both call sites' prior behavior of never plugin-loading when the manifest
63
63
  * declares no plugins.
64
64
  */
65
- export declare function loadPluginsWithCss(configs: ResolvedPluginConfig[] | undefined | null, baseDir: string, onError?: (pluginRef: string, error: Error) => void): Promise<LoadedPluginsWithCss>;
65
+ export declare function loadPluginsWithCss(configs: ResolvedExtensionConfig[] | undefined | null, baseDir: string, onError?: (pluginRef: string, error: Error) => void): Promise<LoadedPluginsWithCss>;
@@ -32,6 +32,8 @@
32
32
  * while hashing the original output path keeps repeat references deterministic.
33
33
  */
34
34
  export declare function placeholderOutputPath(missingOutputPath: string): string;
35
+ /** Decode the small entity set that can occur inside an HTML URL attribute. */
36
+ export declare function decodeHtmlAttribute(value: string): string;
35
37
  /**
36
38
  * Rewrite missing local image URLs in rendered HTML to their real PNG paths.
37
39
  *
@@ -43,6 +45,15 @@ export declare function placeholderOutputPath(missingOutputPath: string): string
43
45
  * at the same staged PNG before `inlineShapeUrls()` reads and embeds it.
44
46
  */
45
47
  export declare function rewriteMissingImageReferences(html: string, replacements: ReadonlyMap<string, string>): string;
48
+ /**
49
+ * Apply `rewriteTag` to every tag in the ACTIVE parts of rendered HTML.
50
+ * Comments and raw-text / literal-content elements (`<script>`, `<style>`,
51
+ * `<pre>`, `<code>`, `<textarea>`) are opaque to the tag pass — a prose
52
+ * example showing HTML must stay byte-for-byte authored. Each such region is
53
+ * handed whole to `rewriteProtected` with its element name (undefined for a
54
+ * comment), which keeps it unchanged unless the caller says otherwise.
55
+ */
56
+ export declare function rewriteActiveHtml(html: string, rewriteTag: (tag: string) => string, rewriteProtected?: (region: string, element: string | undefined) => string): string;
46
57
  /**
47
58
  * Encode a checkerboard PNG of `width`×`height` at `cell` pixels per square.
48
59
  * Truecolor (8-bit RGB, no alpha) with filter byte 0 per scanline — the
@@ -8,7 +8,7 @@ import type { ResolvedConfig } from "../schema/manifest.types";
8
8
  * Every preset value is overridable from the manifest, leaf by leaf
9
9
  * (resolveConfig's mergeShape; precedence cli > manifest > target > preset).
10
10
  */
11
- export interface VendorPreset extends Omit<ResolvedConfig, "title" | "authors" | "targets" | "page" | "engine"> {
11
+ export interface VendorPreset extends Omit<ResolvedConfig, "title" | "authors" | "targets" | "page"> {
12
12
  /**
13
13
  * Base page geometry in points, or `null` for `custom` — the one preset
14
14
  * with no built-in trim, which therefore REQUIRES the manifest to supply
@@ -13,7 +13,7 @@ interface UploadSource {
13
13
  * Package the HTML export directory into a single ZIP (D8: Drive is file
14
14
  * delivery, not web hosting — no N-file folder mirroring). Built via
15
15
  * `zipEntriesNonBlocking` (fflate is already a dependency, see
16
- * theme-import.ts for the sibling unzip-side usage) so building the archive
16
+ * extension-import.ts for the sibling unzip-side usage) so building the archive
17
17
  * doesn't block the event loop, then written to a temp file so the existing
18
18
  * file-based `resumableUpload` (google-drive.ts) can read it incrementally
19
19
  * like any other artifact. Note this is true only of the UPLOAD step — the
@@ -10,22 +10,19 @@ export declare const SNIPPETS_DIR = "snippets";
10
10
  * gate those actions, so "can this be deleted" never drifts out of sync with
11
11
  * "where did this come from" (one field, not two that could disagree).
12
12
  *
13
- * `{ kind: "plugin" | "theme", ref, name }` — a READ-ONLY snippet merged in
14
- * from an installed, currently-ACTIVE extension (see
15
- * {@link listInstalledExtensions} for exactly which extensions qualify).
16
- * `name` is the extension's display name the picker's group label, so an
17
- * author always sees WHICH extension a snippet came from, never just "not
18
- * mine". `ref` is the same stable identifier {@link listProjectPlugins} (a
19
- * plugin's manifest `path`) or {@link getActiveTheme} (a theme's project id)
20
- * already hand out; it is round-tripped back into {@link readExtensionSnippet}
21
- * so that function can re-derive the extension's folder itself from a small,
22
- * validated identifier instead of trusting a filesystem path a caller could
23
- * construct.
13
+ * `{ kind: "extension", ref, name }` — a READ-ONLY snippet merged in from an
14
+ * installed, ENABLED extension (see {@link listInstalledExtensions}). `name`
15
+ * is the extension's display name the picker's group label, so an author
16
+ * always sees WHICH extension a snippet came from, never just "not mine".
17
+ * `ref` is the extension's manifest specifier (`ProjectExtensionEntry.use`);
18
+ * it is round-tripped back into {@link readExtensionSnippet} so that function
19
+ * can re-derive the extension's folder itself from a small, validated
20
+ * identifier instead of trusting a filesystem path a caller could construct.
24
21
  */
25
22
  export type SnippetSource = {
26
23
  kind: "project";
27
24
  } | {
28
- kind: "plugin" | "theme";
25
+ kind: "extension";
29
26
  ref: string;
30
27
  name: string;
31
28
  };
@@ -144,12 +141,11 @@ export declare function listMergedSnippets(projectDir: string): Promise<SnippetE
144
141
  * extended to a second, per-extension root instead of a single project one.
145
142
  *
146
143
  * Throws when `source` no longer resolves to an installed, active extension
147
- * (it was disabled, uninstalled, or the theme was switched since the list
148
- * was fetched — the picker's existing `error` display already handles a
144
+ * (it was disabled or removed since the list was fetched the picker's existing `error` display already handles a
149
145
  * thrown read the same way a vanished project snippet would) or when
150
146
  * `fileName` escapes that extension's snippets folder.
151
147
  */
152
148
  export declare function readExtensionSnippet(projectDir: string, source: {
153
- kind: "plugin" | "theme";
149
+ kind: "extension";
154
150
  ref: string;
155
151
  }, fileName: string): Promise<string>;
@@ -2,7 +2,7 @@
2
2
  export interface ProjectStyle {
3
3
  /** Absolute path to the `.css` file. */
4
4
  path: string;
5
- /** Project-relative, "/"-separated display name (e.g. `themes/dark/theme.css`). */
5
+ /** Project-relative, "/"-separated display name (e.g. `styles/print.css`). */
6
6
  displayName: string;
7
7
  /** True when this stylesheet is in the manifest `styles:` list (the active set). */
8
8
  active: boolean;
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  MANIFEST_FILENAMES,
3
3
  runLint
4
- } from "./cli-1hx14cgk.js";
4
+ } from "./cli-8pg731p5.js";
5
5
  import {
6
6
  log
7
- } from "./cli-vxf151ea.js";
7
+ } from "./cli-ypfvnhag.js";
8
8
  import {
9
9
  UsageError,
10
10
  rejectExtraPositionals,
11
11
  rejectUnknownFlags
12
- } from "./cli-vcv5ewqj.js";
13
- import"./cli-4vtgt2ng.js";
12
+ } from "./cli-rh05byck.js";
13
+ import"./cli-bx950tb4.js";
14
14
  import"./cli-c41yr7he.js";
15
15
  import {
16
16
  EXIT_CODES
@@ -29,22 +29,6 @@
29
29
  "enum": ["dtrpg", "book", "custom"],
30
30
  "examples": ["dtrpg", "book", "custom"]
31
31
  },
32
- "engine": {
33
- "type": "string",
34
- "description": "Pagination engine. The Gutterpress engine (native Chromium pagination) is the only engine, and \"native\" is the only meaningful value. \"paged\" is accepted for backward-compatible manifest parsing only: it produces a one-line warning and the build proceeds natively regardless. `--engine` on the CLI is likewise an accepted-but-ignored no-op.",
35
- "enum": ["paged", "native"],
36
- "default": "native",
37
- "examples": ["native"]
38
- },
39
- "engineStyles": {
40
- "type": "object",
41
- "description": "Engine-conditional stylesheets, appended AFTER `styles`. `.native` is the only list — the native engine is the only engine, so there is nothing else to condition on. A manifest from the dual-engine era may still carry `.paged`; it keeps loading (with a one-line warning) but is no longer part of this schema.",
42
- "properties": {
43
- "native": { "type": "array", "items": { "type": "string" } }
44
- },
45
- "additionalProperties": false,
46
- "examples": [{ "native": ["css/native-furniture.css"] }]
47
- },
48
32
  "targets": {
49
33
  "type": "array",
50
34
  "description": "Where the book is published (ADR 0008): publish targets whose requirements `gutterpress validate` checks the built PDF against — each target is validated separately, so one book can target several destinations. Omitting it uses the preset's default (dtrpg -> [\"dtrpg\"]; book/custom -> []).",
@@ -65,42 +49,34 @@
65
49
  ["themes/parchment.css", "styles/override.css"]
66
50
  ]
67
51
  },
68
- "plugins": {
52
+ "extensions": {
69
53
  "type": "array",
70
- "description": "markdown-it plugins to load. Each entry is either a shorthand string or a configuration object. Entries are loaded highest-priority first.",
71
- "default": [],
54
+ "description": "Extensions, in load order: markdown-it plugins, looks (stylesheets), component libraries — anything from npm or a folder. Each entry is a specifier: a bundled feature name (markdown-it-mark, markdown-it-sub, markdown-it-sup, markdown-it-abbr, gutterpress-gfm-alerts), a path starting with ./ or ../ (relative to this file), or an npm package name, optionally pinned as name@version. A later entry loads later: its markdown runs after earlier entries' and its CSS wins ties. The project's own `styles` always load after every extension.",
72
55
  "items": {
73
56
  "oneOf": [
74
57
  {
75
58
  "type": "string",
76
59
  "minLength": 1,
77
- "description": "Shorthand. Treated as a local file path when it starts with ./, ../, / or a Windows drive letter, or when it contains a path separator and ends in .js/.mjs/.cjs. Otherwise treated as an npm package name.",
60
+ "description": "The specifier. Bundled names resolve to the copy compiled into Gutterpress; ./ and ../ are paths relative to this file; anything else is an npm package, name@version pins it.",
78
61
  "examples": [
79
- "./plugins/my-plugin.js",
80
- "plugins/my-plugin.js",
81
- "gutterpress-plugin-callouts"
62
+ "markdown-it-footnote",
63
+ "@dimm-city/components@2.1.0",
64
+ "./extensions/house-style",
65
+ "../shared/dc-design-guide",
66
+ "markdown-it-mark"
82
67
  ]
83
68
  },
84
69
  {
85
70
  "type": "object",
86
- "description": "Plugin configuration object. Supply either `path` (local file) or `name` (npm package).",
71
+ "description": "The object form, for an entry that needs more than its specifier. `use` carries the same specifier the bare form would.",
72
+ "required": ["use"],
73
+ "additionalProperties": false,
87
74
  "properties": {
88
- "path": {
75
+ "use": {
89
76
  "type": "string",
90
77
  "minLength": 1,
91
- "description": "Path to a local plugin module OR an extension folder (a gutterpress.json package, optionally carrying styles/engineStyles alongside its markdown entry), relative to the manifest directory.",
92
- "examples": ["plugins/dimm-city-plugin.js", "../shared/plugins/callouts.js", "./extensions/dc-components"]
93
- },
94
- "name": {
95
- "type": "string",
96
- "minLength": 1,
97
- "description": "npm package name of the plugin.",
98
- "examples": ["gutterpress-plugin-callouts"]
99
- },
100
- "version": {
101
- "type": "string",
102
- "description": "Exact npm version installed under plugins/npm by Gutterpress. Legacy non-exact values remain informational and use legacy node_modules resolution.",
103
- "examples": ["1.2.3", "2.1.0-beta.1"]
78
+ "description": "The specifier exactly what the bare string form would be.",
79
+ "examples": ["markdown-it-attrs", "./extensions/house-style"]
104
80
  },
105
81
  "export": {
106
82
  "type": "string",
@@ -108,15 +84,9 @@
108
84
  "description": "Named module export to use as the markdown-it plugin function when the package has no default export.",
109
85
  "examples": ["full"]
110
86
  },
111
- "priority": {
112
- "type": "integer",
113
- "description": "Advanced, rarely needed. Load order: higher loads first. If one of your plugins needs to see tokens another one of your plugins produces, give it the LOWER number of the two, so it loads (and runs) after.",
114
- "default": 100,
115
- "examples": [100, 200]
116
- },
117
87
  "options": {
118
88
  "type": "object",
119
- "description": "Plugin-specific options, passed straight through to the plugin.",
89
+ "description": "Options passed straight through to the markdown-it plugin.",
120
90
  "default": {},
121
91
  "additionalProperties": true
122
92
  },
@@ -130,11 +100,11 @@
130
100
  ]
131
101
  },
132
102
  "examples": [
133
- ["gutterpress-plugin-callouts", "./plugins/my-plugin.js"],
103
+ ["./extensions/house-style", "markdown-it-footnote"],
134
104
  [
135
105
  {
136
- "path": "plugins/dimm-city-plugin.js",
137
- "priority": 100
106
+ "use": "markdown-it-attrs",
107
+ "options": { "leftDelimiter": "[" }
138
108
  }
139
109
  ]
140
110
  ]
@@ -554,11 +524,6 @@
554
524
  }
555
525
  }
556
526
  }
557
- },
558
- "themePrevious": {
559
- "type": "string",
560
- "description": "Id of the theme active before the current one (the 'Revert to previous theme' target, #106). Managed automatically by Gutterpress; not authored by hand.",
561
- "examples": ["clean-book", "zine"]
562
527
  }
563
528
  },
564
529
  "examples": [
@@ -7,16 +7,16 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-1hx14cgk.js";
10
+ } from "./cli-8pg731p5.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-vxf151ea.js";
13
+ } from "./cli-ypfvnhag.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-vcv5ewqj.js";
19
- import"./cli-4vtgt2ng.js";
18
+ } from "./cli-rh05byck.js";
19
+ import"./cli-bx950tb4.js";
20
20
  import {
21
21
  isToolAvailable
22
22
  } from "./cli-c41yr7he.js";
@@ -159,16 +159,15 @@ async function runExtensionScaffold(kind, name, parentDir, args) {
159
159
  console.log("Next: check it still works —");
160
160
  console.log(` cd ${result.slug} && bun install && bun test`);
161
161
  console.log("");
162
- console.log(" then load it from a book's manifest.yaml:");
163
- console.log(" plugins:");
164
- console.log(` - path: plugins/${result.slug}`);
162
+ console.log(" then add it to a book:");
163
+ console.log(` gutterpress ext add ./${result.slug} <book>`);
165
164
  console.log("");
166
- console.log(" (point `path` at the FOLDER, not plugin.js that is what makes");
167
- console.log(" Gutterpress read gutterpress.json and pick up the stylesheet too.)");
165
+ console.log(" (that lists the FOLDER under `extensions:` in the book's manifest ");
166
+ console.log(" not plugin.js which is what makes Gutterpress read gutterpress.json");
167
+ console.log(" and pick up the stylesheet too.)");
168
168
  } else {
169
- console.log("Next: install it into a book —");
170
- console.log(` gutterpress theme import ${result.extensionDir} <book>`);
171
- console.log(` gutterpress theme apply ${result.slug} <book>`);
169
+ console.log("Next: add it to a book —");
170
+ console.log(` gutterpress ext add ${result.extensionDir} <book>`);
172
171
  console.log("");
173
172
  console.log(" Each stylesheet opens with the OWNS / MUST NOT CONTAIN header that");
174
173
  console.log(" says which rules belong in it. Start with styles/tokens.css.");
@@ -121,7 +121,6 @@ describe("loader contract", () => {
121
121
  const declared = [
122
122
  pkg.markdown,
123
123
  ...(pkg.styles ?? []),
124
- ...(pkg.engineStyles?.native ?? []),
125
124
  pkg.components,
126
125
  pkg.snippets,
127
126
  pkg.tokensFile,
@@ -2,16 +2,16 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-1hx14cgk.js";
5
+ } from "./cli-8pg731p5.js";
6
6
  import {
7
7
  log
8
- } from "./cli-vxf151ea.js";
8
+ } from "./cli-ypfvnhag.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-vcv5ewqj.js";
14
- import"./cli-4vtgt2ng.js";
13
+ } from "./cli-rh05byck.js";
14
+ import"./cli-bx950tb4.js";
15
15
  import"./cli-c41yr7he.js";
16
16
  import {
17
17
  EXIT_CODES
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { type FSWatcher } from 'chokidar';
8
8
  import type { ServerState } from './server-context';
9
- import type { ResolvedPluginConfig } from '../schema/manifest.types';
9
+ import type { ResolvedExtensionConfig } from '../schema/manifest.types';
10
10
  /**
11
11
  * Whether the incremental preview shell is active. The historical env name is
12
12
  * retained because users may already set it. A single Markdown edit paginates
@@ -50,7 +50,7 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
50
50
  source?: {
51
51
  files?: string[] | null;
52
52
  };
53
- plugins?: ResolvedPluginConfig[];
53
+ extensions?: ResolvedExtensionConfig[];
54
54
  }, cssAssets: Map<string, string>): Promise<void>;
55
55
  /**
56
56
  * Render one source file with the same CSS, plugins, source metadata, and
@@ -60,7 +60,7 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
60
60
  export declare function renderChapterPreviewHtml(inputPath: string, file: string, config: {
61
61
  title?: string;
62
62
  styles?: string[];
63
- plugins?: ResolvedPluginConfig[];
63
+ extensions?: ResolvedExtensionConfig[];
64
64
  }): Promise<string>;
65
65
  /** One changed project file, named for the preview broadcast decision. */
66
66
  export interface ChangedFile {
@@ -154,7 +154,7 @@ export declare function externalWatchRoots(targets: Iterable<string>): Promise<s
154
154
  */
155
155
  export declare function externalWatchTargets(projectDir: string, config: {
156
156
  styles?: string[];
157
- plugins?: ResolvedPluginConfig[];
157
+ extensions?: ResolvedExtensionConfig[];
158
158
  }): Promise<string[]>;
159
159
  /**
160
160
  * Create and configure a file watcher for the project's input directory plus
@@ -42,7 +42,7 @@ export declare function validateInputPath(inputPath: string): Promise<void>;
42
42
  * For empty input (no-input mode), skip manifest loading entirely and
43
43
  * return a default resolved config.
44
44
  */
45
- export declare function initializeConfiguration(inputPath: string, engine?: "paged" | "native"): Promise<ResolvedConfig>;
45
+ export declare function initializeConfiguration(inputPath: string): Promise<ResolvedConfig>;
46
46
  /**
47
47
  * Restart the preview server with a new input directory.
48
48
  *
@@ -1,25 +1,24 @@
1
1
  import {
2
2
  previewArgs
3
- } from "./cli-pfpb1t3h.js";
3
+ } from "./cli-bzxqtvrk.js";
4
4
  import {
5
5
  openPath,
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-1hx14cgk.js";
9
+ } from "./cli-8pg731p5.js";
10
10
  import {
11
11
  log
12
- } from "./cli-vxf151ea.js";
12
+ } from "./cli-ypfvnhag.js";
13
13
  import {
14
14
  UsageError,
15
- parseEngine,
16
15
  parseFormat,
17
16
  parsePdfxFlavor,
18
17
  rejectExtraPositionals,
19
18
  rejectUnknownFlags,
20
19
  resolvePort
21
- } from "./cli-vcv5ewqj.js";
22
- import"./cli-4vtgt2ng.js";
20
+ } from "./cli-rh05byck.js";
21
+ import"./cli-bx950tb4.js";
23
22
  import"./cli-c41yr7he.js";
24
23
  import {
25
24
  BuildError
@@ -48,7 +47,6 @@ var preview_default = defineCommand({
48
47
  }
49
48
  }
50
49
  const openFlag = args.open;
51
- const engine = parseEngine(args.engine);
52
50
  if (format === "html") {
53
51
  if (typeof args.manifest === "string") {
54
52
  throw new UsageError("--manifest is only supported by preview --format pdf or pdfx; live HTML preview discovers the project manifest from its input directory.");
@@ -60,8 +58,7 @@ var preview_default = defineCommand({
60
58
  noWatch: args.watch === false,
61
59
  verbose: !!args.verbose,
62
60
  openBrowser: openFlag,
63
- debug: !!args.debug,
64
- engine
61
+ debug: !!args.debug
65
62
  });
66
63
  return;
67
64
  }
@@ -83,7 +80,6 @@ var preview_default = defineCommand({
83
80
  skipPreValidate: !!args["skip-pre-validate"],
84
81
  skipPostValidate: !!args["skip-post-validate"],
85
82
  allowShrink: !!args["allow-shrink"],
86
- engine,
87
83
  rawArgs: args
88
84
  });
89
85
  if (openFlag && result.pdfPath) {