gutterpress 0.10.7 → 0.10.8

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 +10 -3
  2. package/dist/README-aaqapam1.md +186 -0
  3. package/dist/README-mdq34cmb.md +155 -0
  4. package/dist/api/index.d.ts +6 -2
  5. package/dist/api/index.js +30 -2
  6. package/dist/{audit-19e2431j.js → audit-m3ce9pvq.js} +4 -4
  7. package/dist/base-ypqh9n7f.css +123 -0
  8. package/dist/book-ted3tzrp.css +58 -0
  9. package/dist/{build-gqqew2vr.js → build-qnecb3a1.js} +4 -4
  10. package/dist/callout-hv56m16h.md +7 -0
  11. package/dist/{cli-kzf64c2g.js → cli-1hx14cgk.js} +946 -401
  12. package/dist/{cli-s8q42x3r.js → cli-4vtgt2ng.js} +78 -2
  13. package/dist/{cli-h8ejbtyr.js → cli-vcv5ewqj.js} +1 -1
  14. package/dist/{cli-pvk2s9d7.js → cli-vxf151ea.js} +1 -1
  15. package/dist/cli.js +16 -16
  16. package/dist/components-0mmswvvm.yaml +51 -0
  17. package/dist/components-aam0fe2z.css +94 -0
  18. package/dist/{doctor-3ccz35yz.js → doctor-qvqye1wd.js} +2 -2
  19. package/dist/{engine-pb6h089e.js → engine-daad33qn.js} +1 -1
  20. package/dist/{engine-88mynx9a.js → engine-db8632w6.js} +2 -2
  21. package/dist/expected-ycmacem8.html +9 -0
  22. package/dist/fixture-2qnyqgb5.md +10 -0
  23. package/dist/gutterpress-m98e2w2d.json +8 -0
  24. package/dist/gutterpress-x2ycdsb8.json +17 -0
  25. package/dist/{index-837htyjy.js → index-9pbaznww.js} +78 -2
  26. package/dist/{index-r4ny4fyr.js → index-dfw8hn8q.js} +1147 -523
  27. package/dist/{index-a0xpm188.js → index-rpt4vtby.js} +1 -1
  28. package/dist/index.js +31 -3
  29. package/dist/lib/build-runner.d.ts +52 -0
  30. package/dist/lib/extension-manifest.d.ts +164 -0
  31. package/dist/lib/extension-scaffold.d.ts +65 -0
  32. package/dist/lib/lint-runner.d.ts +12 -0
  33. package/dist/lib/markdown/assemble.d.ts +12 -0
  34. package/dist/lib/markdown/markers.d.ts +20 -0
  35. package/dist/lib/markdown/plugins.d.ts +1 -1
  36. package/dist/lib/markdown/renderer.d.ts +99 -0
  37. package/dist/lib/snippets.d.ts +121 -5
  38. package/dist/lib/theme-import.d.ts +8 -4
  39. package/dist/lib/theme-manager.d.ts +52 -39
  40. package/dist/lib/validation-exec.d.ts +12 -0
  41. package/dist/{lint-mpz7vfc8.js → lint-3p1s8agk.js} +4 -4
  42. package/dist/{manifest.schema-kwnrwv8b.json → manifest.schema-1q35yxs9.json} +2 -2
  43. package/dist/{new-nbrpsw7t.js → new-xffarp40.js} +111 -6
  44. package/dist/package-rm9d2tht.json +29 -0
  45. package/dist/page-rules-nsj3aw42.css +128 -0
  46. package/dist/page-templates-7md4ev7d.css +60 -0
  47. package/dist/{plugin-gg5mk26h.js → plugin-b05ck7zd.js} +4 -4
  48. package/dist/plugin-m2w7g47v.css +114 -0
  49. package/dist/plugin.js-aszz0zgh.tpl +172 -0
  50. package/dist/plugin.test.js-mv0ghv5n.tpl +232 -0
  51. package/dist/{preflight-jnmd4mg2.js → preflight-z6xpty9w.js} +4 -4
  52. package/dist/{preview-hsr9d12d.js → preview-xa1n7why.js} +4 -4
  53. package/dist/{publish-pcrrn6fd.js → publish-zgmr99kd.js} +4 -4
  54. package/dist/render.js +275 -8
  55. package/dist/term-box-4y0w7y32.md +7 -0
  56. package/dist/{theme-6fa0yjzq.js → theme-m9dtxkhj.js} +4 -4
  57. package/dist/tokens-e4xk3x4v.css +86 -0
  58. package/dist/{validate-7n25fx0g.js → validate-43hqgzz5.js} +4 -4
  59. package/package.json +1 -1
@@ -4,7 +4,7 @@ import {
4
4
  launchChromium,
5
5
  marginBoxAlign,
6
6
  marginBoxRectPt
7
- } from "./index-837htyjy.js";
7
+ } from "./index-9pbaznww.js";
8
8
 
9
9
  // src/engine/compiler/build.ts
10
10
  import { readFile } from "node:fs/promises";
package/dist/index.js CHANGED
@@ -8,18 +8,22 @@ import {
8
8
  AUTO_SYNC_PUSH_INTERVAL_MINUTES,
9
9
  BUILT_IN_TEMPLATE_IDS,
10
10
  BUILT_IN_THEME_IDS,
11
+ EXTENSION_KINDS,
12
+ EXTENSION_MANIFEST_FILENAME,
11
13
  FileTokenStore,
12
14
  GDRIVE_HOST,
13
15
  GITHUB_HOST,
14
16
  GOOGLE_NOT_CONFIGURED_MESSAGE,
15
17
  GitHubAuthProvider,
16
18
  GoogleAuthProvider,
19
+ LEGACY_THEME_MANIFEST_FILENAME,
17
20
  MANIFEST_FILENAMES,
18
21
  PLUGINS_DIR,
19
22
  PRESETS,
20
23
  PRESET_IDS,
21
24
  PublishSelectionsStore,
22
25
  RECOMMENDED_PLUGINS,
26
+ RESERVED_PREFIX,
23
27
  SNIPPETS_DIR,
24
28
  SYNC_SNAPSHOT_MESSAGE,
25
29
  TARGETS,
@@ -29,6 +33,7 @@ import {
29
33
  addNpmPlugin,
30
34
  adoptFolder,
31
35
  applyTheme,
36
+ assertExtensionContained,
32
37
  autoSnapshotDelayMs,
33
38
  autoSyncDelayMs,
34
39
  checkCss,
@@ -45,6 +50,8 @@ import {
45
50
  escapeYamlScalar,
46
51
  executeAndReport,
47
52
  executeValidation,
53
+ extensionEngineStyleList,
54
+ extensionStyleList,
48
55
  extractUrlCredential,
49
56
  extractVariables,
50
57
  forgeKindForHost,
@@ -68,6 +75,7 @@ import {
68
75
  listCustomTemplates,
69
76
  listGitHubBranches,
70
77
  listGitHubRepositories,
78
+ listMergedSnippets,
71
79
  listProjectPlugins,
72
80
  listProjectStyles,
73
81
  listProjectThemes,
@@ -80,11 +88,14 @@ import {
80
88
  log,
81
89
  openPath,
82
90
  parseRemoteOrigin,
91
+ pathEscapesFolder,
83
92
  pkceChallengeFromVerifier,
84
93
  publishConnectionStatus,
85
94
  publishCredentialKey,
86
95
  publishProviderFor,
87
96
  publishTargetFor,
97
+ readExtensionMeta,
98
+ readExtensionSnippet,
88
99
  readManifestFields,
89
100
  readPublishSettings,
90
101
  readSnippet,
@@ -96,6 +107,8 @@ import {
96
107
  resolveBuiltInTheme,
97
108
  resolveCheckSelectors,
98
109
  resolveConfig,
110
+ resolveExtension,
111
+ resolveExtensionPrefix,
99
112
  resolveGitHubClientId,
100
113
  resolveGoogleClientId,
101
114
  resolveGoogleClientSecret,
@@ -113,6 +126,7 @@ import {
113
126
  sanitizeCloneFolderName,
114
127
  saveProjectAsTemplate,
115
128
  saveSnippet,
129
+ scaffoldExtension,
116
130
  scaffoldProject,
117
131
  setActiveStyles,
118
132
  setManifestFields,
@@ -125,15 +139,15 @@ import {
125
139
  syncProject,
126
140
  testRemoteAccess,
127
141
  validateProjectPlugins
128
- } from "./index-r4ny4fyr.js";
142
+ } from "./index-dfw8hn8q.js";
129
143
  import {
130
144
  BUILD_DIAGNOSTIC_CODES
131
- } from "./index-a0xpm188.js";
145
+ } from "./index-rpt4vtby.js";
132
146
  import {
133
147
  BuildError,
134
148
  DEFAULT_PRINT_OPTS,
135
149
  readyProbeExpr
136
- } from "./index-837htyjy.js";
150
+ } from "./index-9pbaznww.js";
137
151
  import {
138
152
  AUTO_SNAPSHOT_MESSAGE,
139
153
  HISTORY_PAGE_LIMIT,
@@ -164,6 +178,7 @@ export {
164
178
  setManifestFields,
165
179
  setActiveStyles,
166
180
  scaffoldProject,
181
+ scaffoldExtension,
167
182
  saveSnippet,
168
183
  saveProjectAsTemplate,
169
184
  sanitizeCloneFolderName,
@@ -183,6 +198,8 @@ export {
183
198
  resolveGoogleClientSecret,
184
199
  resolveGoogleClientId,
185
200
  resolveGitHubClientId,
201
+ resolveExtensionPrefix,
202
+ resolveExtension,
186
203
  resolveConfig,
187
204
  resolveCheckSelectors,
188
205
  resolveBuiltInTheme,
@@ -197,12 +214,15 @@ export {
197
214
  readSnippet,
198
215
  readPublishSettings,
199
216
  readManifestFields,
217
+ readExtensionSnippet,
218
+ readExtensionMeta,
200
219
  publishTargetFor,
201
220
  publishProviderFor,
202
221
  publishCredentialKey,
203
222
  publishConnectionStatus,
204
223
  providerFor,
205
224
  pkceChallengeFromVerifier,
225
+ pathEscapesFolder,
206
226
  parseRemoteOrigin,
207
227
  openPath,
208
228
  log,
@@ -215,6 +235,7 @@ export {
215
235
  listProjectThemes,
216
236
  listProjectStyles,
217
237
  listProjectPlugins,
238
+ listMergedSnippets,
218
239
  listGitHubRepositories,
219
240
  listGitHubBranches,
220
241
  listCustomTemplates,
@@ -240,6 +261,8 @@ export {
240
261
  findEnclosingRepoDir,
241
262
  extractVariables,
242
263
  extractUrlCredential,
264
+ extensionStyleList,
265
+ extensionEngineStyleList,
243
266
  executeValidation,
244
267
  executeAndReport,
245
268
  escapeYamlScalar,
@@ -258,6 +281,7 @@ export {
258
281
  capabilitiesFor,
259
282
  autoSyncDelayMs,
260
283
  autoSnapshotDelayMs,
284
+ assertExtensionContained,
261
285
  applyTheme,
262
286
  adoptFolder,
263
287
  addNpmPlugin,
@@ -268,12 +292,14 @@ export {
268
292
  SYNC_SNAPSHOT_MESSAGE,
269
293
  SNIPPETS_DIR,
270
294
  RESTORE_BACKUP_MESSAGE,
295
+ RESERVED_PREFIX,
271
296
  RECOMMENDED_PLUGINS,
272
297
  PublishSelectionsStore,
273
298
  PRESET_IDS,
274
299
  PRESETS,
275
300
  PLUGINS_DIR,
276
301
  MANIFEST_FILENAMES,
302
+ LEGACY_THEME_MANIFEST_FILENAME,
277
303
  HISTORY_PAGE_LIMIT,
278
304
  GoogleAuthProvider,
279
305
  GitHubAuthProvider,
@@ -281,6 +307,8 @@ export {
281
307
  GITHUB_HOST,
282
308
  GDRIVE_HOST,
283
309
  FileTokenStore,
310
+ EXTENSION_MANIFEST_FILENAME,
311
+ EXTENSION_KINDS,
284
312
  DEFAULT_PRINT_OPTS,
285
313
  BuildError,
286
314
  BUILT_IN_THEME_IDS,
@@ -1,4 +1,5 @@
1
1
  import { resolveConfig } from "./manifest";
2
+ import { type LoadedPluginsWithCss } from "./markdown/plugins";
2
3
  import { RENDER_TIMEOUT_MS } from "./browser-pool";
3
4
  import { BuildError } from "./build-error";
4
5
  import type { BuildDiagnostic } from "../engine/compiler/build.ts";
@@ -165,6 +166,29 @@ export interface BuildContext {
165
166
  * warning from the final render path.
166
167
  */
167
168
  prevalidatedLayoutWarningKeys: Set<string>;
169
+ /**
170
+ * The build's ONE plugin load (#262). `null` until {@link loadBuildPlugins}
171
+ * runs; every stage that needs plugins (the lint gate and preValidate gate
172
+ * in {@link runQualityGates}, and {@link renderBook}) calls that function
173
+ * and gets the SAME resolved `{ plugins, pluginCss, pluginStylePaths }`
174
+ * back, whichever of them runs first.
175
+ *
176
+ * Before this, `runQualityGates`'s lint gate and `renderBook` each called
177
+ * `loadPluginsWithCss` independently for the identical manifest. For an
178
+ * npm-vendored plugin, EVERY load re-runs `verifyVendoredPlugin` ->
179
+ * `computeVendorTreeDigest` (plugin-vendor.ts) — a recursive walk of the
180
+ * vendored tree plus a full read-and-SHA-256 of every file, with no cache
181
+ * anywhere in that module BY DESIGN (the digest exists to detect tampering
182
+ * with the vendored tree; a cache keyed on anything less than the file
183
+ * contents themselves — e.g. the receipt's mtime — would not notice an
184
+ * edited vendored file). A `gutterpress build` with the lint gate on
185
+ * therefore paid that cost twice for no reason: same manifest, same
186
+ * `renderDir` anchor (see that field's doc comment — the two call sites'
187
+ * base dirs were the historical risk here, and they are now provably the
188
+ * same value), same plugin list. Memoizing the ONE load on the context
189
+ * removes the duplicate work without caching anything security-relevant.
190
+ */
191
+ plugins: LoadedPluginsWithCss | null;
168
192
  }
169
193
  /**
170
194
  * Stage 1 — load the manifest, merge CLI overrides into the resolved config,
@@ -173,6 +197,34 @@ export interface BuildContext {
173
197
  * flags-ignored notice. Everything downstream reads from the returned context.
174
198
  */
175
199
  export declare function resolveBuildContext(opts: BuildRunnerOptions): Promise<BuildContext>;
200
+ /**
201
+ * Load every plugin the manifest configures, exactly once for the whole
202
+ * build (#262). Memoized on `ctx.plugins`: the lint gate, the preValidate
203
+ * gate (both in {@link runQualityGates}), and {@link renderBook} each call
204
+ * this instead of `loadPluginsWithCss` directly, and only the first caller
205
+ * does real work — the rest get the cached result back, however the gates
206
+ * are configured and whichever stage happens to run first (a test calling
207
+ * {@link renderBook} directly, without going through `runQualityGates`, gets
208
+ * a fresh load here exactly as it would have before this existed).
209
+ *
210
+ * Fail-fast (no `onError`), matching `renderBook`'s pre-existing behavior:
211
+ * a build/export must never silently omit author-configured formatting (see
212
+ * `loadPlugins`'s doc comment in markdown/plugins.ts on the two failure
213
+ * modes). One consequence: a plugin that fails to load now aborts the build
214
+ * as soon as quality gates start, instead of (as before) the lint gate's own
215
+ * degrade-and-report call warning-and-skipping that same plugin only for
216
+ * `renderBook` to hard-fail on it moments later — the build failed either
217
+ * way, this just stops wasting the lint pass first.
218
+ *
219
+ * Resolves plugin `path:` entries against `ctx.renderDir`, which is by
220
+ * construction identical to `ctx.manifestDir` for the life of one
221
+ * `BuildContext` (see {@link BuildContext.renderDir}'s doc comment and
222
+ * build-runner.output-dir.test.ts's anchor tests) — so hoisting this load
223
+ * ahead of the gates cannot resolve a plugin against a different root than
224
+ * `renderBook` used to, including under an explicit `--manifest` outside
225
+ * `--input`.
226
+ */
227
+ export declare function loadBuildPlugins(ctx: BuildContext): Promise<LoadedPluginsWithCss>;
176
228
  /**
177
229
  * Stage 3 — load configured plugins, render the markdown chapters to
178
230
  * `outDir/book.html`, and copy user asset directories. Returns the path to the
@@ -0,0 +1,164 @@
1
+ /** The unified package's metadata filename — checked before the legacy
2
+ * theme-only filename by {@link readExtensionMeta}. */
3
+ export declare const EXTENSION_MANIFEST_FILENAME = "gutterpress.json";
4
+ /** The pre-#241 theme-only metadata filename. Still fully supported: a folder
5
+ * with only this file (no `gutterpress.json`) is a theme, i.e. "an extension
6
+ * with only styles" — see {@link readExtensionMeta}. */
7
+ export declare const LEGACY_THEME_MANIFEST_FILENAME = "theme.json";
8
+ /**
9
+ * Parsed extension metadata — a superset of the pre-#241 theme metadata
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."
13
+ * Symmetrically, a bare `.js` plugin file (no metadata file at all) never
14
+ * constructs one of these — "plugin ≡ extension with only markdown" needs no
15
+ * metadata file until it wants more than a function (see `plugins.ts`'s
16
+ * `loadExtensionFromDir`, which only reaches this type for a `path` that
17
+ * names a folder).
18
+ */
19
+ export interface ExtensionMetadata {
20
+ name?: string;
21
+ author?: string;
22
+ description?: string;
23
+ /** Optional preview image path (relative to the extension folder). */
24
+ preview?: string | null;
25
+ /**
26
+ * Ordered stylesheets, relative to the extension folder. Absent/empty means
27
+ * "no styles declared" here — {@link extensionStyleList} does NOT default
28
+ * to `["theme.css"]`; that default is theme-manager.ts's OWN, layered on
29
+ * top for its theme-shaped callers (a plain markdown-only extension folder
30
+ * has no reason to require a `theme.css` it never declared).
31
+ */
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
+ /** Which declared sheet (a path from `styles`) carries the author-facing
39
+ * `:root` token surface for the Design panel's guided editor. Purely
40
+ * advisory — nothing in this module enforces or existence-checks it,
41
+ * matching the pre-#241 theme behavior it generalizes. */
42
+ tokensFile?: string;
43
+ /**
44
+ * Markdown-it entry, relative to the extension folder — a path to a JS
45
+ * module exporting a plugin function exactly like a bare-file plugin
46
+ * (`export default function (md, options) { ... }`, optionally `metadata`/
47
+ * `css`/`styles`, #238). Absent means "no markdown behavior" — the
48
+ * degenerate case that makes a styles-only folder indistinguishable from a
49
+ * theme. CLAUDE.md §5 is unaffected: this field is DATA a loader resolves
50
+ * to a file, then loads through the exact same plain-markdown-it-plugin
51
+ * contract every other plugin module does — no new plugin API is
52
+ * introduced.
53
+ */
54
+ markdown?: string;
55
+ /**
56
+ * Component catalog file, relative to the extension folder (the CSS
57
+ * architecture review's `components.yaml`, #242). Parsed for existence/
58
+ * containment only by this module — the catalog SCHEMA and the registry
59
+ * that reads it are #242's scope, not this one's.
60
+ */
61
+ components?: string;
62
+ /**
63
+ * Snippets folder, relative to the extension folder, merged into the
64
+ * project's snippet picker under the extension's name (#240). Parsed for
65
+ * existence/containment only by this module — the merge itself is #240's
66
+ * scope, not this one's.
67
+ */
68
+ snippets?: string;
69
+ }
70
+ /**
71
+ * Read an extension folder's metadata: `gutterpress.json` when present,
72
+ * otherwise the legacy `theme.json` — ONE reader for both filenames, which is
73
+ * what makes "the theme verbs keep working on the extension format" true
74
+ * everywhere a real on-disk extension/theme folder is read (project themes,
75
+ * apply, import, revert). `gutterpress.json` wins outright when present (no
76
+ * silent merge with a sibling `theme.json` — a package declares itself
77
+ * through exactly one file), matching {@link readJsonMetaFile}'s tolerant
78
+ * contract when that one file is missing or broken.
79
+ */
80
+ export declare function readExtensionMeta(dir: string): Promise<ExtensionMetadata>;
81
+ /**
82
+ * An extension's declared stylesheets, relative to its folder, in cascade
83
+ * order. UNLIKE `theme-manager.ts`'s `themeStyleList` (which layers a
84
+ * `["theme.css"]` default on top of this for its theme-shaped callers), an
85
+ * absent/empty `styles` here means exactly "none declared" — a markdown-only
86
+ * extension folder must not be forced to carry a `theme.css` it never wanted.
87
+ */
88
+ 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[];
91
+ /**
92
+ * True when a declared relative path escapes its own folder (absolute, or a
93
+ * `..` segment) — the traversal shape every containment check in this
94
+ * package rejects.
95
+ *
96
+ * Exported (#242) for `snippets.ts`'s installed-extension snippet merge,
97
+ * which needs this SAME single-field check but cannot use
98
+ * {@link assertExtensionContained}: that guard throws on ANY escaping field
99
+ * (styles/markdown/components/tokensFile too), which is correct for a
100
+ * write-boundary check at install/apply time but wrong for a tolerant
101
+ * listing — an extension with a broken, unrelated `styles` entry must not
102
+ * make its perfectly fine `snippets` folder disappear from the picker (the
103
+ * same "one hand-edited file must not take down listing everyone else's"
104
+ * doctrine {@link readExtensionMeta}'s tolerant JSON parse already follows).
105
+ */
106
+ export declare function pathEscapesFolder(rel: string): boolean;
107
+ /**
108
+ * Every path an extension declares must live INSIDE its own folder — an
109
+ * extension is self-contained by contract (apply/install copies the whole
110
+ * folder), and an imported/vendored package is untrusted input: a `../` or
111
+ * absolute entry would make apply/load read a file from anywhere on disk.
112
+ *
113
+ * Generalizes the pre-#241 theme-only `assertThemeSheetsContained` (still
114
+ * exported under that name from `theme-manager.ts`, now a re-export of this
115
+ * function) to the three new fields: a `gutterpress.json`-formatted theme
116
+ * folder can declare `markdown`/`components`/`snippets` too, so the SAME
117
+ * write-boundary guard must cover them, not just `styles`/`engineStyles`.
118
+ * `tokensFile` is included even though it is advisory/unenforced elsewhere —
119
+ * defense in depth against a future consumer reading it unchecked.
120
+ *
121
+ * A WRITE-BOUNDARY guard, not a read-path check (mirrors the theme-only
122
+ * predecessor): callers invoke this before copying anything or wiring a
123
+ * manifest, never from a plain listing/read path, so one hand-edited
124
+ * metadata file cannot take down listing every extension.
125
+ */
126
+ export declare function assertExtensionContained(meta: ExtensionMetadata): void;
127
+ /**
128
+ * An extension's declared paths, resolved to absolute, existence-checked
129
+ * filesystem paths — every list/single-path field goes through the SAME
130
+ * {@link resolveDeclaredStyles} a plugin's `styles` export and a theme's
131
+ * `styles`/`engineStyles.native` already resolve through, so a broken
132
+ * declaration (a missing file) throws HERE, at load/apply time, instead of
133
+ * failing silently deep in the render pipeline (or never, for `components`/
134
+ * `snippets`, which nothing yet reads — #240/#242).
135
+ *
136
+ * `tokensFile` is the one exception, kept as declared (relative, not
137
+ * existence-checked) — it stays purely advisory, matching
138
+ * `ThemeInfo.tokensFile`'s pre-#241 contract.
139
+ */
140
+ export interface ResolvedExtension {
141
+ /** Absolute path to the markdown-it entry module, when declared. */
142
+ markdown?: string;
143
+ /** Absolute paths, in cascade order, when any are declared. */
144
+ styles?: string[];
145
+ /** Absolute paths, in cascade order, when any are declared. */
146
+ engineStyles?: string[];
147
+ /** Declared-relative path of the `:root` token surface — advisory. */
148
+ tokensFile?: string;
149
+ /** Absolute path to the component catalog file, when declared (#242). */
150
+ components?: string;
151
+ /** Absolute path to the snippets folder, when declared (#240). */
152
+ snippets?: string;
153
+ }
154
+ /**
155
+ * Resolve every path {@link ExtensionMetadata} declares, relative to `dir`
156
+ * (the extension's own folder). `subject` names the declaring thing for a
157
+ * resolution error, e.g. `Plugin "my-extension"` — passed straight through to
158
+ * `resolveDeclaredStyles`, so a broken `markdown`/`components`/`snippets`
159
+ * entry throws the same "declares stylesheet ... but no file exists" shape a
160
+ * broken `styles` entry always has (the wording says "stylesheet" for every
161
+ * field — a small, accepted cost of resolving all of them through one
162
+ * function rather than inventing per-field messages).
163
+ */
164
+ export declare function resolveExtension(dir: string, meta: ExtensionMetadata, subject: string): ResolvedExtension;
@@ -0,0 +1,65 @@
1
+ /** Which starter package to create. */
2
+ export type ExtensionKind = "plugin" | "theme";
3
+ /** The kinds `--kind` accepts, for CLI help text and pickers. */
4
+ export declare const EXTENSION_KINDS: readonly ["plugin", "theme"];
5
+ /**
6
+ * The class/property prefix a scaffolded extension is FORBIDDEN to take.
7
+ * `gp-` is Gutterpress core's own vocabulary (CLAUDE.md §6); an extension
8
+ * using it does not conflict with core, it silently overrides it.
9
+ */
10
+ export declare const RESERVED_PREFIX = "gp-";
11
+ /** Inputs the CLI / desktop collect. Only `name`, `kind` and `parentDir` are
12
+ * required; everything else is derived. */
13
+ export interface ScaffoldExtensionOptions {
14
+ /** Human-friendly package name, e.g. "Field Notes". Required. */
15
+ name: string;
16
+ /** Which starter to create. Required. */
17
+ kind: ExtensionKind;
18
+ /** Absolute path to the PARENT directory the folder is created in. */
19
+ parentDir: string;
20
+ /** Folder name under `parentDir`. Defaults to a slug of `name`. */
21
+ folderName?: string;
22
+ /**
23
+ * Class / custom-property / marker prefix this package claims. Defaults to
24
+ * the slug plus a hyphen (`field-notes-`). A trailing hyphen is added when
25
+ * missing — the templates concatenate it directly onto a name, so
26
+ * `.${PREFIX}callout` has to produce `.field-notes-callout`.
27
+ */
28
+ prefix?: string;
29
+ /** Author display name. Defaults to "Anonymous". */
30
+ author?: string;
31
+ /** One-line description for the metadata and the README. */
32
+ description?: string;
33
+ }
34
+ export interface ScaffoldExtensionResult {
35
+ /** Absolute path of the created folder. */
36
+ extensionDir: string;
37
+ /** Absolute path of its `gutterpress.json`. */
38
+ manifestPath: string;
39
+ kind: ExtensionKind;
40
+ /** Folder name / package name that was used. */
41
+ slug: string;
42
+ /** The prefix baked into the scaffolded files. */
43
+ prefix: string;
44
+ /** Absolute path of the file worth opening first. */
45
+ openFile: string;
46
+ /** Every file written, relative to `extensionDir`, in write order. */
47
+ files: string[];
48
+ }
49
+ /**
50
+ * Normalize an author-supplied prefix, or derive one from the slug.
51
+ *
52
+ * The prefix is the single most load-bearing convention an extension has
53
+ * (#245's "which conventions are load-bearing and why"), so it is validated
54
+ * here rather than left to fail as a puzzling CSS bug later: it must read
55
+ * like a CSS identifier, and it must not be core's.
56
+ */
57
+ export declare function resolveExtensionPrefix(slug: string, requested?: string): string;
58
+ /**
59
+ * Create a plugin or theme starter package.
60
+ *
61
+ * Resolves with a {@link ScaffoldExtensionResult}; throws a
62
+ * {@link CreateProjectError} (the SAME error type `scaffoldProject` throws, so
63
+ * one `catch` handles both) on any precondition failure.
64
+ */
65
+ export declare function scaffoldExtension(options: ScaffoldExtensionOptions): Promise<ScaffoldExtensionResult>;
@@ -1,6 +1,18 @@
1
1
  export interface LintRunnerOptions {
2
2
  files?: string;
3
3
  manifest?: string;
4
+ /**
5
+ * Pre-loaded, absolute plugin `styles` file paths (#262) — supplied by the
6
+ * build pipeline (`build-runner.ts`'s `runQualityGates`, via
7
+ * `loadBuildPlugins`) so this call does not load plugins a second time in
8
+ * the same build. `undefined` (the default — every standalone
9
+ * `gutterpress lint` invocation) makes this function load plugins itself,
10
+ * exactly as before, with the same degrade-and-report (warn-and-skip)
11
+ * behavior for a plugin that fails to load. An explicit `[]` from a caller
12
+ * that already knows there is nothing to add is honored as-is, not treated
13
+ * as "unset".
14
+ */
15
+ pluginStylePaths?: string[];
4
16
  }
5
17
  export interface LintRunnerResult {
6
18
  ok: boolean;
@@ -12,6 +12,18 @@ export type ReadText = (relPath: string) => Promise<string>;
12
12
  * emitted by `gp-pin-scope.js`'s `gp_pin_scope_check`, same as
13
13
  * `pin_outside_page`, see #226).
14
14
  *
15
+ * #240 (declarative container components) adds three more, all emitted by
16
+ * `markers.js`'s `layout_transform`/`scanForUnknownDeclaredMarkers` for a
17
+ * plugin-DECLARED marker: `deprecated_marker` (a `{ deprecated: "…" }`
18
+ * marker, or its `@end-` form, was used), `declared_marker_close_without_open`
19
+ * (an `@end-<name>` with nothing of that kind open — the declared-marker
20
+ * twin of `continue_without_section`), and `declared_marker_eof_close` (an
21
+ * open declared container reached EOF without `autoCloseAt: ["eof"]` — the
22
+ * declared-marker twin of `spread_eof_close`). `unknown_marker` above is
23
+ * reused, not duplicated: a typo close to a DECLARED name warns through the
24
+ * same type, via a second, unconditional check modeled on `unknown_gp_class`
25
+ * rather than the core-only `scanForMistypedMarkers`.
26
+ *
15
27
  * `section_without_page` and `implicit_page` were REMOVED 2026-08-12: a
16
28
  * @section with no open @page is valid authoring (audited, 17/17 false
17
29
  * positives across two real books), and the `implicitPage` option that
@@ -1,3 +1,23 @@
1
+ /**
2
+ * Merge every loaded plugin's `markers` export into ONE flat, resolved
3
+ * registry — `Map<name, ResolvedDecl>` — validating collisions at LOAD TIME
4
+ * (#240 / P2): a declared name that shadows a core reserved name, or that
5
+ * two different plugins both declare, throws immediately, naming both
6
+ * sides, instead of the pre-#240 silent-skip footgun (core claims its
7
+ * `@`-names during block parsing, before any plugin runs, so a colliding
8
+ * plugin marker used to never run and never warn — see the header comment
9
+ * on `KNOWN_KINDS`).
10
+ *
11
+ * @param {{ pluginName: string, markers: Record<string, unknown> }[]} sources
12
+ * One entry per LOADED plugin that declared a non-empty `markers` export,
13
+ * in plugin load order. A plugin with no `markers` export is simply
14
+ * omitted by the caller — this function never sees it.
15
+ * @returns {Map<string, object>} empty when `sources` is empty.
16
+ */
17
+ export function buildDeclaredMarkerRegistry(sources: {
18
+ pluginName: string;
19
+ markers: Record<string, unknown>;
20
+ }[]): Map<string, object>;
1
21
  export default function plugin(md: any, pluginOptions?: {}): void;
2
22
  /**
3
23
  * The minimal CSS the DOM this module emits requires. Author utility
@@ -1,6 +1,6 @@
1
1
  import type { ResolvedPluginConfig } from "../../schema/manifest.types";
2
2
  import type { LoadedPlugin } from "./renderer";
3
- export type { GutterpressPlugin, GutterpressPluginMetadata, GutterpressPluginExport, LoadedPlugin, } from "./renderer";
3
+ export type { GutterpressMarkerDeclaration, GutterpressMarkerLabel, GutterpressMarkerTable, GutterpressPlugin, GutterpressPluginMetadata, GutterpressPluginExport, LoadedPlugin, } from "./renderer";
4
4
  export { applyPlugins, collectPluginCss, collectPluginStylePaths } from "./renderer";
5
5
  /** Test-only mutation seam between snapshot completion and snapshot verification. */
6
6
  export declare function __setVendorSnapshotHookForTests(hook?: (sourceRoot: string, snapshotRoot: string) => void | Promise<void>): void;