gutterpress 0.10.9 → 0.10.10-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +66 -5
  2. package/dist/{README-jntqyj1z.md → README-3zk4tgnj.md} +4 -4
  3. package/dist/{README-6c7fda1s.md → README-7x5akcns.md} +5 -4
  4. package/dist/api/index.d.ts +7 -4
  5. package/dist/api/index.js +22 -12
  6. package/dist/{audit-fq7s1gsz.js → audit-wcmbgh39.js} +5 -5
  7. package/dist/{build-dyat3fqd.js → build-c1w6kqpv.js} +6 -6
  8. package/dist/{cli-ypfvnhag.js → cli-2rswy18t.js} +1 -1
  9. package/dist/{cli-w692g6dx.js → cli-4mtf9ajw.js} +127 -1
  10. package/dist/{cli-bzxqtvrk.js → cli-75qkn0k5.js} +1 -1
  11. package/dist/{cli-bx950tb4.js → cli-k8f9eb12.js} +22 -26
  12. package/dist/{cli-8pg731p5.js → cli-n0vgey6x.js} +4914 -4707
  13. package/dist/{cli-rh05byck.js → cli-scqp638v.js} +1 -1
  14. package/dist/cli.js +16 -16
  15. package/dist/{components-0mmswvvm.yaml → components-dhgv2sm7.yaml} +2 -2
  16. package/dist/{doctor-yr85n080.js → doctor-drv36btj.js} +2 -2
  17. package/dist/{engine-d46wgg99.js → engine-pt2acjb5.js} +2 -2
  18. package/dist/{engine-19mshh07.js → engine-qtmvaqt7.js} +1 -1
  19. package/dist/{ext-jrg8c48s.js → ext-xqx26xvy.js} +52 -8
  20. package/dist/{index-mey5vraz.js → index-1he93sjd.js} +918 -796
  21. package/dist/{index-zwcv1njy.js → index-5dgxeevv.js} +22 -26
  22. package/dist/{index-dhgzkhs7.js → index-a0e2253t.js} +1 -1
  23. package/dist/{index-dtrftayr.js → index-y01v2fe0.js} +127 -1
  24. package/dist/index.js +102 -15
  25. package/dist/lib/build-error.d.ts +6 -5
  26. package/dist/lib/build-preflight.d.ts +11 -1
  27. package/dist/lib/build-runner.d.ts +49 -16
  28. package/dist/lib/extension-import.d.ts +4 -4
  29. package/dist/lib/extension-manager.d.ts +3 -3
  30. package/dist/lib/extension-manifest.d.ts +91 -87
  31. package/dist/lib/extension-scaffold.d.ts +1 -1
  32. package/dist/lib/extension-search.d.ts +46 -0
  33. package/dist/lib/host-policy.d.ts +10 -12
  34. package/dist/lib/lint-runner.d.ts +16 -9
  35. package/dist/lib/npm-registry.d.ts +22 -0
  36. package/dist/lib/source-provider.d.ts +71 -0
  37. package/dist/lib/validation-exec.d.ts +11 -0
  38. package/dist/{lint-enasqbqx.js → lint-ejzmx14j.js} +5 -5
  39. package/dist/{new-r9c99wsn.js → new-g3q3h4s5.js} +6 -6
  40. package/dist/package-97gm31sg.json +10 -0
  41. package/dist/{package-rm9d2tht.json → package-9rv69pxm.json} +4 -1
  42. package/dist/package-aenwjew3.json +28 -0
  43. package/dist/package-gag6ykwn.json +10 -0
  44. package/dist/package-nrhkwf2m.json +10 -0
  45. package/dist/{plugin.test.js-npnfhyca.tpl → plugin.test.js-k2r87k04.tpl} +9 -8
  46. package/dist/{preflight-shk7vqbs.js → preflight-w5z2vfhx.js} +5 -5
  47. package/dist/{preview-52918mxk.js → preview-9s22dq77.js} +6 -6
  48. package/dist/{publish-8s60vhx7.js → publish-mt22ewpk.js} +5 -5
  49. package/dist/{source-provider-2j5x2hyw.js → source-provider-51g6mt6y.js} +7 -1
  50. package/dist/{source-provider-kn41jmbs.js → source-provider-jkppq9fc.js} +7 -1
  51. package/dist/{tokens-e4xk3x4v.css → tokens-2sracx1w.css} +1 -1
  52. package/dist/{validate-awccx32r.js → validate-kkn8rmy5.js} +5 -5
  53. package/package.json +1 -1
  54. package/dist/gutterpress-m98e2w2d.json +0 -8
  55. package/dist/gutterpress-x2ycdsb8.json +0 -17
  56. package/dist/theme-etj4gxck.json +0 -9
  57. package/dist/theme-s2rg7xe7.json +0 -9
  58. package/dist/theme-vktedy8q.json +0 -9
@@ -40,7 +40,7 @@ export declare function classifyThemeCssFindings(findings: PrintSafeWarning[]):
40
40
  };
41
41
  /**
42
42
  * From a theme folder's relative file paths, return the ones that are neither
43
- * `theme.css`/`theme.json`/`gutterpress.json`, a recognized bundled asset
43
+ * `theme.css`/`package.json`, a recognized bundled asset
44
44
  * (font/image/css), nor one the metadata itself declared (#241 —
45
45
  * `declaredExtras`: the extension's `markdown`/`components`/`snippets`
46
46
  * entries, when present, passed as relative paths; a `snippets` folder
@@ -59,8 +59,8 @@ export declare function unexpectedThemeFiles(relPaths: string[], declaredExtras?
59
59
  export declare function importExtensionFromZip(projectDir: string, archive: Uint8Array): Promise<ExtensionImportResult>;
60
60
  /**
61
61
  * Import a theme from a bare `.css` file by wrapping it into a one-file theme
62
- * folder (`theme.css` + a synthesized `theme.json` naming it). REJECTS a CSS
63
- * that fails to parse.
62
+ * folder (`theme.css` + a synthesized `package.json` naming it and declaring
63
+ * it). REJECTS a CSS that fails to parse.
64
64
  */
65
65
  export declare function importExtensionFromCssText(projectDir: string, css: string, name: string): Promise<ExtensionImportResult>;
66
66
  /**
@@ -73,7 +73,7 @@ export declare function importExtensionFromFile(projectDir: string, filePath: st
73
73
  * Import a look from a URL using the global `fetch` (bundle-safe — no node
74
74
  * http client). Two shapes: a `.css` URL is the look's `theme.css`, with
75
75
  * metadata synthesised from the URL; a base URL (no `.css`) fetches
76
- * `<base>/theme.json` (optional) and `<base>/theme.css` (required). Bundled
76
+ * `<base>/package.json` (optional) and `<base>/theme.css` (required). Bundled
77
77
  * fonts are not followed — authors wanting bundled assets use a folder or a
78
78
  * `.zip`. The fetched files go through the same validation as a `.zip`.
79
79
  */
@@ -29,10 +29,10 @@ export interface ProjectExtensionEntry {
29
29
  /** Preview image path relative to the extension folder, when declared. */
30
30
  preview?: string | null;
31
31
  /** The sheet carrying the `:root` token surface, relative to the folder,
32
- * when declared (`gutterpress.json`'s `tokensFile`). */
32
+ * when declared (package.json's `gutterpress.tokensFile`). */
33
33
  tokensFile?: string;
34
- /** Declared stylesheets relative to the folder, in cascade order (`theme.css`
35
- * by default for a metadata-less look). Absent when there is no folder. */
34
+ /** Declared stylesheets relative to the folder, in cascade order
35
+ * (`gutterpress.styles`). Absent when the folder declares none. */
36
36
  styles?: string[];
37
37
  /** What the extension declares — the desktop shows styles-carrying entries
38
38
  * in its Look view and markdown-carrying ones in Features; one list. */
@@ -1,92 +1,100 @@
1
- /** The unified package's metadata filenamechecked 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";
1
+ /** The one file an extension describes itself in npm's own manifest. */
2
+ export declare const EXTENSION_MANIFEST_FILENAME = "package.json";
8
3
  /**
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`/`tokensFile`) IS
12
- * 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).
4
+ * Parsed extension metadata — npm's standard fields plus the `gutterpress`
5
+ * block's, flattened into the one shape every consumer reads. Every field is
6
+ * optional: a folder declaring only `gutterpress.styles` IS a valid extension
7
+ * ("a look an extension with only styles"), and a package.json whose `main`
8
+ * is a plain markdown-it plugin with no `gutterpress` key at all is one too
9
+ * ("a plugin ≡ an extension with only markdown"). A bare `.js` plugin file
10
+ * (no package.json beside it) never constructs one of these see
11
+ * `plugins.ts`'s `loadExtensionFromDir`, which only reaches this type for a
12
+ * `path` that names a folder.
18
13
  */
19
14
  export interface ExtensionMetadata {
15
+ /** npm `name`. */
20
16
  name?: string;
17
+ /** npm `author`, as a string or the object form's `name`. */
21
18
  author?: string;
19
+ /** npm `description`. */
22
20
  description?: string;
23
- /** Optional preview image path (relative to the extension folder). */
21
+ /**
22
+ * npm `keywords` — informational. `gutterpress` marks a Gutterpress
23
+ * extension and `markdown-it-plugin` a markdown-it plugin, the two tags
24
+ * `gutterpress ext search` finds packages by; nothing here requires either.
25
+ */
26
+ keywords?: string[];
27
+ /**
28
+ * npm `main` — the markdown-it plugin module of a FOLDER extension, by
29
+ * npm's own convention. See {@link extensionEntry} for the entry rule
30
+ * (`gutterpress.markdown` wins when present). NOT used for an npm-installed
31
+ * package: the installer resolves that entry with full `exports` semantics
32
+ * (`npm-plugin-installer.ts`'s `resolvePackageEntry`), and it is never
33
+ * re-derived from `main` here.
34
+ */
35
+ main?: string;
36
+ /** `gutterpress.preview` — an optional preview image path, relative to the
37
+ * extension folder. */
24
38
  preview?: string | null;
25
39
  /**
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 {@link extensionStyleListWithDefault}'s, 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).
40
+ * `gutterpress.styles` — ordered stylesheets, relative to the extension
41
+ * folder. Absent/empty means exactly "no styles declared": a look declares
42
+ * its sheets, it does not get an implicit `theme.css`.
31
43
  */
32
44
  styles?: string[];
33
- /** Which declared sheet (a path from `styles`) carries the author-facing
34
- * `:root` token surface for the Design panel's guided editor. Purely
35
- * advisory — nothing in this module enforces or existence-checks it,
36
- * matching the pre-#241 theme behavior it generalizes. */
45
+ /** `gutterpress.tokensFile` — which declared sheet (a path from `styles`)
46
+ * carries the author-facing `:root` token surface for the Design panel's
47
+ * guided editor. Purely advisory — nothing in this module enforces or
48
+ * existence-checks it. */
37
49
  tokensFile?: string;
38
50
  /**
39
- * Markdown-it entry, relative to the extension folder — a path to a JS
40
- * module exporting a plugin function exactly like a bare-file plugin
41
- * (`export default function (md, options) { ... }`, optionally `metadata`/
42
- * `css`/`styles`, #238). Absent means "no markdown behavior" — the
43
- * degenerate case that makes a styles-only folder indistinguishable from a
44
- * theme. CLAUDE.md §5 is unaffected: this field is DATA a loader resolves
45
- * to a file, then loads through the exact same plain-markdown-it-plugin
46
- * contract every other plugin module does — no new plugin API is
47
- * introduced.
51
+ * `gutterpress.markdown` — an EXPLICIT markdown-it entry, relative to the
52
+ * extension folder, for the one case npm's `main` can't express: a package
53
+ * whose `main` is not the plugin. Absent is the normal case ({@link
54
+ * extensionEntry} falls back to `main`). CLAUDE.md §5 is unaffected: this
55
+ * field is DATA a loader resolves to a file, then loads through the exact
56
+ * same plain-markdown-it-plugin contract every other plugin module does
57
+ * no new plugin API is introduced.
48
58
  */
49
59
  markdown?: string;
50
60
  /**
51
- * Component catalog file, relative to the extension folder (the CSS
52
- * architecture review's `components.yaml`, #242). Parsed for existence/
53
- * containment only by this module — the catalog SCHEMA and the registry
54
- * that reads it are #242's scope, not this one's.
61
+ * `gutterpress.components` — component catalog file, relative to the
62
+ * extension folder (the CSS architecture review's `components.yaml`, #242).
63
+ * Parsed for existence/containment only by this module.
55
64
  */
56
65
  components?: string;
57
66
  /**
58
- * Snippets folder, relative to the extension folder, merged into the
59
- * project's snippet picker under the extension's name (#240). Parsed for
60
- * existence/containment only by this module — the merge itself is #240's
61
- * scope, not this one's.
67
+ * `gutterpress.snippets` — snippets folder, relative to the extension
68
+ * folder, merged into the project's snippet picker under the extension's
69
+ * name. Parsed for existence/containment only by this module; `snippets.ts`
70
+ * does the merge.
62
71
  */
63
72
  snippets?: string;
73
+ /**
74
+ * The removed `engineStyles` field (#266), carried through from the
75
+ * `gutterpress` block ONLY so {@link assertExtensionContained} can reject it
76
+ * by name at a write boundary. Never read as a declaration.
77
+ */
78
+ engineStyles?: unknown;
64
79
  }
65
80
  /**
66
- * Read an extension folder's metadata: `gutterpress.json` when present,
67
- * otherwise the legacy `theme.json` ONE reader for both filenames, which is
68
- * what makes "the theme verbs keep working on the extension format" true
69
- * everywhere a real on-disk extension/theme folder is read (project themes,
70
- * apply, import, revert). `gutterpress.json` wins outright when present (no
71
- * silent merge with a sibling `theme.json` — a package declares itself
72
- * through exactly one file), matching {@link readJsonMetaFile}'s tolerant
73
- * contract when that one file is missing or broken.
81
+ * Read an extension folder's `package.json`. Tolerant: a missing or
82
+ * unparseable file, or one that is not an object, reads as `{}` — one bad or
83
+ * absent manifest must never take down listing/reading every extension.
74
84
  */
75
85
  export declare function readExtensionMeta(dir: string): Promise<ExtensionMetadata>;
76
86
  /**
77
87
  * An extension's declared stylesheets, relative to its folder, in cascade
78
- * order. UNLIKE {@link extensionStyleListWithDefault} (which layers a
79
- * `["theme.css"]` default on top of this for its theme-shaped callers), an
80
- * absent/empty `styles` here means exactly "none declared" — a markdown-only
81
- * extension folder must not be forced to carry a `theme.css` it never wanted.
88
+ * order. Absent/empty `gutterpress.styles` means exactly "none declared".
82
89
  */
83
90
  export declare function extensionStyleList(meta: ExtensionMetadata): string[];
84
91
  /**
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.
92
+ * The markdown-it entry of a FOLDER extension: an explicit
93
+ * `gutterpress.markdown` when the package's `main` is not the plugin,
94
+ * otherwise npm's own `main`. `undefined` for a folder that carries no
95
+ * markdown behaviour at all (a look).
88
96
  */
89
- export declare function extensionStyleListWithDefault(meta: ExtensionMetadata, dir: string): string[];
97
+ export declare function extensionEntry(meta: ExtensionMetadata): string | undefined;
90
98
  /** What an extension declares, by field — the desktop's one-list filter. */
91
99
  export interface ExtensionCarries {
92
100
  markdown: boolean;
@@ -94,7 +102,7 @@ export interface ExtensionCarries {
94
102
  snippets: boolean;
95
103
  components: boolean;
96
104
  }
97
- export declare function extensionCarries(meta: ExtensionMetadata, dir: string): ExtensionCarries;
105
+ export declare function extensionCarries(meta: ExtensionMetadata): ExtensionCarries;
98
106
  /**
99
107
  * True when a declared relative path escapes its own folder (absolute, or a
100
108
  * `..` segment) — the traversal shape every containment check in this
@@ -116,39 +124,30 @@ export declare function pathEscapesFolder(rel: string): boolean;
116
124
  * extension is self-contained by contract (apply/install copies the whole
117
125
  * folder), and an imported/vendored package is untrusted input: a `../` or
118
126
  * absolute entry would make apply/load read a file from anywhere on disk.
119
- *
120
- * Generalizes the pre-#241 theme-only `assertThemeSheetsContained` (still
121
- * once exported under that name from the theme manager, now this
122
- * function) to the three new fields: a `gutterpress.json`-formatted theme
123
- * folder can declare `markdown`/`components`/`snippets` too, so the SAME
124
- * write-boundary guard must cover them, not just `styles`.
125
127
  * `tokensFile` is included even though it is advisory/unenforced elsewhere —
126
128
  * defense in depth against a future consumer reading it unchecked.
127
129
  *
128
- * A WRITE-BOUNDARY guard, not a read-path check (mirrors the theme-only
129
- * predecessor): callers invoke this before copying anything or wiring a
130
- * manifest, never from a plain listing/read path, so one hand-edited
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.
130
+ * A WRITE-BOUNDARY guard, not a read-path check: callers invoke this before
131
+ * copying anything or wiring a manifest, never from a plain listing/read
132
+ * path, so one hand-edited manifest cannot take down listing every extension.
133
+ * The removed `engineStyles` field (#266) is rejected here for the same
134
+ * reason: the error names the replacement, and it fires only where something
135
+ * would be copied or wired, never while listing.
135
136
  */
136
137
  export declare function assertExtensionContained(meta: ExtensionMetadata): void;
137
138
  /**
138
139
  * An extension's declared paths, resolved to absolute, existence-checked
139
140
  * filesystem paths — every list/single-path field goes through the SAME
140
- * {@link resolveDeclaredStyles} a plugin's `styles` export and a theme's
141
- * `styles` already resolve through, so a broken
142
- * declaration (a missing file) throws HERE, at load/apply time, instead of
143
- * failing silently deep in the render pipeline (or never, for `components`/
144
- * `snippets`, which nothing yet reads — #240/#242).
141
+ * {@link resolveDeclaredStyles} a plugin's `styles` export and a look's
142
+ * `styles` already resolve through, so a broken declaration (a missing file)
143
+ * throws HERE, at load/apply time, instead of failing silently deep in the
144
+ * render pipeline (or never, for `components`, which nothing yet reads).
145
145
  *
146
146
  * `tokensFile` is the one exception, kept as declared (relative, not
147
- * existence-checked) — it stays purely advisory, matching
148
- * `ThemeInfo.tokensFile`'s pre-#241 contract.
147
+ * existence-checked) — it stays purely advisory.
149
148
  */
150
149
  export interface ResolvedExtension {
151
- /** Absolute path to the markdown-it entry module, when declared. */
150
+ /** Absolute path to the markdown-it entry module, when the folder has one. */
152
151
  markdown?: string;
153
152
  /** Absolute paths, in cascade order, when any are declared. */
154
153
  styles?: string[];
@@ -156,17 +155,22 @@ export interface ResolvedExtension {
156
155
  tokensFile?: string;
157
156
  /** Absolute path to the component catalog file, when declared (#242). */
158
157
  components?: string;
159
- /** Absolute path to the snippets folder, when declared (#240). */
158
+ /** Absolute path to the snippets folder, when declared. */
160
159
  snippets?: string;
161
160
  }
162
161
  /**
163
162
  * Resolve every path {@link ExtensionMetadata} declares, relative to `dir`
164
163
  * (the extension's own folder). `subject` names the declaring thing for a
165
164
  * resolution error, e.g. `Plugin "my-extension"` — passed straight through to
166
- * `resolveDeclaredStyles`, so a broken `markdown`/`components`/`snippets`
167
- * entry throws the same "declares stylesheet ... but no file exists" shape a
168
- * broken `styles` entry always has (the wording says "stylesheet" for every
169
- * field — a small, accepted cost of resolving all of them through one
170
- * function rather than inventing per-field messages).
165
+ * `resolveDeclaredStyles`, so a broken entry throws the same "declares
166
+ * stylesheet ... but no file exists" shape a broken `styles` entry always has
167
+ * (the wording says "stylesheet" for every field — a small, accepted cost of
168
+ * resolving all of them through one function rather than inventing per-field
169
+ * messages).
170
+ *
171
+ * `markdown` resolves {@link extensionEntry} — the FOLDER rule. An
172
+ * npm-installed package's entry is whatever the installer resolved with full
173
+ * `exports` semantics, so `plugins.ts` calls this without `main` for that
174
+ * path; see its npm branch.
171
175
  */
172
176
  export declare function resolveExtension(dir: string, meta: ExtensionMetadata, subject: string): ResolvedExtension;
@@ -34,7 +34,7 @@ export interface ScaffoldExtensionOptions {
34
34
  export interface ScaffoldExtensionResult {
35
35
  /** Absolute path of the created folder. */
36
36
  extensionDir: string;
37
- /** Absolute path of its `gutterpress.json`. */
37
+ /** Absolute path of its `package.json`. */
38
38
  manifestPath: string;
39
39
  kind: ExtensionKind;
40
40
  /** Folder name / package name that was used. */
@@ -0,0 +1,46 @@
1
+ /** Reject a response larger than this before parsing. */
2
+ export declare const MAX_SEARCH_BYTES: number;
3
+ /** What a match was found BY — the two conventions, in precedence order. */
4
+ export declare const SEARCH_KEYWORDS: readonly ["gutterpress", "markdown-it-plugin"];
5
+ export type ExtensionSearchKind = "gutterpress" | "markdown-it";
6
+ /** One npm package a book could install. */
7
+ export interface NpmExtensionMatch {
8
+ name: string;
9
+ version: string;
10
+ description?: string;
11
+ /** `gutterpress` when the package tags itself as an extension; otherwise a
12
+ * plain markdown-it plugin found by the ecosystem keyword. */
13
+ kind: ExtensionSearchKind;
14
+ keywords: string[];
15
+ homepage?: string;
16
+ /** The package's page on the registry's website, when it published one. */
17
+ npmUrl?: string;
18
+ /** Last publish date, as the registry reported it. */
19
+ date?: string;
20
+ }
21
+ export interface NpmExtensionSearchResult {
22
+ matches: NpmExtensionMatch[];
23
+ /** What the registry said it had, summed over both keyword queries — the
24
+ * "showing N of M" denominator, not a count of `matches`. */
25
+ total: number;
26
+ }
27
+ export interface SearchNpmExtensionsOptions {
28
+ /** Dependency injection for tests; production uses global fetch. */
29
+ fetch?: typeof globalThis.fetch;
30
+ /** How many matches to return, and the `size` of each registry query. */
31
+ limit?: number;
32
+ signal?: AbortSignal;
33
+ }
34
+ /**
35
+ * Search npm for installable extensions.
36
+ *
37
+ * One request per keyword (`gutterpress`, then `markdown-it-plugin`), merged
38
+ * and de-duplicated by package name — a package carrying BOTH keywords is one
39
+ * row, reported as a Gutterpress extension. Ordering puts Gutterpress-tagged
40
+ * packages first (they are the ones built for this tool), then the registry's
41
+ * own relevance score.
42
+ *
43
+ * Throws a friendly, author-facing error on any failure (offline, timeout, bad
44
+ * HTTP status, oversize body, malformed JSON, or a shape that doesn't match).
45
+ */
46
+ export declare function searchNpmExtensions(query: string, options?: SearchNpmExtensionsOptions): Promise<NpmExtensionSearchResult>;
@@ -16,13 +16,9 @@
16
16
  export interface AutoSnapshotPolicy {
17
17
  /** Master switch — automatic snapshots default ON. */
18
18
  autoSnapshot: boolean;
19
- /** Minutes of quiet after the last edit before a snapshot fires. */
20
- autoSnapshotMinutes: number;
21
19
  }
22
- /** Cadence bounds: never below 5 minutes (commit-per-keystroke guard), never
23
- * above a day (a longer value means the user effectively wants it off). */
24
- export declare const AUTO_SNAPSHOT_MIN_MINUTES = 5;
25
- export declare const AUTO_SNAPSHOT_MAX_MINUTES: number;
20
+ /** Quiet period before a snapshot fires. Fixed (#274) — no longer a settings
21
+ * knob, so there is nothing left to clamp. */
26
22
  export declare const AUTO_SNAPSHOT_DEFAULT_MINUTES = 10;
27
23
  /** User-facing auto-sync policy (mirrors the desktop's settings group). */
28
24
  export interface AutoSyncPolicy {
@@ -32,10 +28,10 @@ export interface AutoSyncPolicy {
32
28
  autoSyncMinutes: number;
33
29
  }
34
30
  /**
35
- * Cadence bounds for the periodic safety sync. The floor (1 min) is lower than
36
- * the snapshot floor because a network round-trip is cheaper than a full tree
37
- * walk, and the transparent-sync plan targets ~2 min as the default cadence.
38
- * The ceiling matches the snapshot ceiling (one day = effectively paused).
31
+ * Cadence bounds for the periodic safety sync. The floor (1 min) is low
32
+ * because a network round-trip is cheaper than a full tree walk, and the
33
+ * transparent-sync plan targets ~2 min as the default cadence. The ceiling
34
+ * (one day) means "effectively paused" for an absurdly large configured value.
39
35
  */
40
36
  export declare const AUTO_SYNC_MIN_MINUTES = 1;
41
37
  export declare const AUTO_SYNC_MAX_MINUTES: number;
@@ -56,9 +52,11 @@ export declare const AUTO_SYNC_PUSH_INTERVAL_MINUTES = 15;
56
52
  * `null` when automatic snapshots are disabled. Pure — the testable core of
57
53
  * the trigger policy (the timer itself lives in the Electron main process).
58
54
  *
55
+ * The quiet period is fixed at `AUTO_SNAPSHOT_DEFAULT_MINUTES` (#274 — the
56
+ * minutes field this used to clamp was deleted from the settings schema, so
57
+ * there is nothing left to resolve here beyond the master switch).
59
58
  * Defensive about persisted settings: a missing policy means "defaults"
60
- * (enabled, 10 min); a non-finite/absurd minutes value falls back to the
61
- * default and is then clamped into [5, 1440].
59
+ * (enabled, 10 min).
62
60
  */
63
61
  export declare function autoSnapshotDelayMs(policy: Partial<AutoSnapshotPolicy> | undefined): number | null;
64
62
  /**
@@ -2,21 +2,28 @@ export interface LintRunnerOptions {
2
2
  files?: string;
3
3
  manifest?: string;
4
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".
5
+ * Pre-loaded, absolute plugin `styles` file paths (#262). `undefined` (the
6
+ * default every standalone `gutterpress lint` invocation, which is now
7
+ * this function's only production caller since #272 removed the build
8
+ * pipeline's own separate lint gate) makes this function load plugins
9
+ * itself, degrade-and-report (warn-and-skip) for a plugin that fails to
10
+ * load. An explicit `[]` or a pre-loaded list from a caller that already
11
+ * knows the resolved plugin styles for this manifest is honored as-is, not
12
+ * treated as "unset".
14
13
  */
15
14
  pluginStylePaths?: string[];
16
15
  }
17
16
  export interface LintRunnerResult {
18
17
  ok: boolean;
18
+ /** Count of `printsafe/no-risky-print-effects` findings across every linted file. */
19
19
  riskyCount: number;
20
+ /**
21
+ * Count of `printsafe/page-containment` findings across every linted file
22
+ * (#272). Kept separate from {@link riskyCount} rather than folded into it,
23
+ * so a consumer can tell the two finding kinds apart without re-parsing
24
+ * printed text.
25
+ */
26
+ containmentCount: number;
20
27
  filesLinted: number;
21
28
  }
22
29
  export declare function runLint(opts?: LintRunnerOptions): Promise<LintRunnerResult>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The npm registry Gutterpress talks to (#246) — ONE resolver for the two
3
+ * places that reach it: the installer (`npm-plugin-installer.ts`: metadata,
4
+ * tarballs, and the tarball-origin check) and extension search
5
+ * (`extension-search.ts`).
6
+ *
7
+ * `GUTTERPRESS_NPM_REGISTRY` points both at a private mirror, and is what the
8
+ * CLI's own tests serve a fake registry at. It is validated the same way every
9
+ * other author-supplied URL in this package is — http(s) only — because a
10
+ * `file:`/`data:` registry would turn "install a package" into "read an
11
+ * arbitrary path".
12
+ *
13
+ * Bundle-safe (CLAUDE.md §1/§3): pure string work over `process.env`, no deps.
14
+ */
15
+ /** The public registry, used when nothing overrides it. */
16
+ export declare const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
17
+ /**
18
+ * The configured registry's base URL, without a trailing slash (every caller
19
+ * builds `${registry}/<path>` and a doubled slash breaks path-sensitive
20
+ * mirrors). Throws on an override that is not an http(s) URL.
21
+ */
22
+ export declare function npmRegistryUrl(): string;
@@ -239,4 +239,75 @@ export declare const AUTO_SNAPSHOT_MESSAGE = "Automatic snapshot";
239
239
  * folder has no version history.
240
240
  */
241
241
  export declare function restoreVersionWithBackup(options: RestoreVersionOptions): Promise<RestoreVersionResult>;
242
+ /** The project's copies (git branches) and which one is currently open. */
243
+ export interface LocalBranches {
244
+ /** Name of the branch HEAD points to, or `null` on a detached/unborn HEAD. */
245
+ current: string | null;
246
+ /**
247
+ * Every copy the project can switch to, sorted: local branches
248
+ * (`refs/heads/*`) plus copies that so far exist only on a remote.
249
+ */
250
+ branches: string[];
251
+ /**
252
+ * The subset of {@link branches} with no local ref yet. Switching to one of
253
+ * these creates the local copy from the remote and tracks it; the UI uses
254
+ * this only to say so before it happens.
255
+ */
256
+ remoteOnly: string[];
257
+ }
258
+ /** Inputs for {@link switchBranch}. */
259
+ export interface SwitchBranchOptions {
260
+ projectDir: string;
261
+ /** Branch name to check out — local, or one that so far exists only on a remote. */
262
+ branch: string;
263
+ /** Identity recorded on the automatic pre-switch safety snapshot, if one is taken. */
264
+ authorName?: string;
265
+ authorEmail?: string;
266
+ }
267
+ /** Result of a successful {@link switchBranch}. */
268
+ export interface SwitchBranchResult {
269
+ /** The branch now checked out (equals `options.branch`). */
270
+ current: string;
271
+ /**
272
+ * Absolute paths of every file the checkout added, removed, or changed the
273
+ * content of (a diff of the old HEAD's tree against the new branch's tree,
274
+ * computed before the checkout runs). The host uses this to drop crash-
275
+ * recovery drafts for these paths — see recovery.ts's header: a draft taken
276
+ * on the copy just left behind must never be offered over a DIFFERENT copy's
277
+ * version of the same file.
278
+ */
279
+ changedFiles: string[];
280
+ }
281
+ /** Snapshot message for the safety snapshot {@link switchBranch} takes before checking out, when the working tree has changes. */
282
+ export declare const SWITCH_BRANCH_SNAPSHOT_MESSAGE = "Saved before switching copy";
283
+ /**
284
+ * List the project's copies (git branches) and which one is open, for a
285
+ * `local-git-folder` project. Returns `null` for any other source type —
286
+ * there is nothing to switch between, and the caller (the Saving settings row)
287
+ * hides the whole control on `null` rather than showing an error.
288
+ *
289
+ * Copies that exist only on a remote are included: they are as real to the
290
+ * author as the local ones, and {@link switchBranch} creates the local branch
291
+ * on the way in.
292
+ */
293
+ export declare function listLocalBranches(dir: string): Promise<LocalBranches | null>;
294
+ /**
295
+ * Switch the project's working tree to another local copy (git branch).
296
+ *
297
+ * Safety, in order (see the issue's "what the switch has to take care of"):
298
+ * 1. If the working tree has uncommitted changes, snapshot them first (so
299
+ * nothing the author typed is lost) and tolerate the expected
300
+ * "no changes" rejection when it doesn't.
301
+ * 2. Check out the target branch WITHOUT `force` — isomorphic-git then
302
+ * refuses (CheckoutConflictError) rather than silently overwriting a
303
+ * working-tree file that moved after the snapshot above, which is
304
+ * reported as a friendly error instead of a forced checkout.
305
+ * 3. Report every path the checkout touched so the host can drop stale
306
+ * crash-recovery drafts for them (see {@link SwitchBranchResult}).
307
+ *
308
+ * Pause/resume of the auto-snapshot/auto-sync host timers around the
309
+ * checkout, and refreshing the open editor/file-tree/preview afterwards, are
310
+ * host (desktop) responsibilities — this function only does the git work.
311
+ */
312
+ export declare function switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchResult>;
242
313
  export {};
@@ -26,6 +26,17 @@ export interface ValidationExecutionArgs {
26
26
  * "unset".
27
27
  */
28
28
  pluginStylePaths?: string[];
29
+ /**
30
+ * Disable the `source.stylelint` check (CSS print-safety) for this run
31
+ * only, without touching the manifest's `validate.source.stylelint`
32
+ * setting on disk (#272 — one CSS gate, not two). Set by the build
33
+ * pipeline's `runQualityGates` (build-runner.ts) from `--skip-lint` /
34
+ * `config.lint.enabled: false`, so that flag disables just the one check
35
+ * it always meant to gate rather than a whole separate lint pass. Every
36
+ * other caller (`gutterpress validate`/`preflight`/`audit`, the desktop
37
+ * Problems panel) leaves this unset and gets the manifest's own setting.
38
+ */
39
+ skipStylelint?: boolean;
29
40
  }
30
41
  export interface ValidationExecutionResult {
31
42
  config: ResolvedConfig;
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  MANIFEST_FILENAMES,
3
3
  runLint
4
- } from "./cli-8pg731p5.js";
4
+ } from "./cli-n0vgey6x.js";
5
5
  import {
6
6
  log
7
- } from "./cli-ypfvnhag.js";
7
+ } from "./cli-2rswy18t.js";
8
8
  import {
9
9
  UsageError,
10
10
  rejectExtraPositionals,
11
11
  rejectUnknownFlags
12
- } from "./cli-rh05byck.js";
13
- import"./cli-bx950tb4.js";
12
+ } from "./cli-scqp638v.js";
13
+ import"./cli-k8f9eb12.js";
14
14
  import"./cli-c41yr7he.js";
15
15
  import {
16
16
  EXIT_CODES
17
17
  } from "./cli-46ycxe6r.js";
18
- import"./cli-w692g6dx.js";
18
+ import"./cli-4mtf9ajw.js";
19
19
  import"./cli-v5mp7a6q.js";
20
20
  import"./cli-37x76zdn.js";
21
21
 
@@ -7,23 +7,23 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-8pg731p5.js";
10
+ } from "./cli-n0vgey6x.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-ypfvnhag.js";
13
+ } from "./cli-2rswy18t.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-rh05byck.js";
19
- import"./cli-bx950tb4.js";
18
+ } from "./cli-scqp638v.js";
19
+ import"./cli-k8f9eb12.js";
20
20
  import {
21
21
  isToolAvailable
22
22
  } from "./cli-c41yr7he.js";
23
23
  import {
24
24
  EXIT_CODES
25
25
  } from "./cli-46ycxe6r.js";
26
- import"./cli-w692g6dx.js";
26
+ import"./cli-4mtf9ajw.js";
27
27
  import"./cli-v5mp7a6q.js";
28
28
  import"./cli-37x76zdn.js";
29
29
 
@@ -163,7 +163,7 @@ async function runExtensionScaffold(kind, name, parentDir, args) {
163
163
  console.log(` gutterpress ext add ./${result.slug} <book>`);
164
164
  console.log("");
165
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");
166
+ console.log(" not plugin.js — which is what makes Gutterpress read package.json");
167
167
  console.log(" and pick up the stylesheet too.)");
168
168
  } else {
169
169
  console.log("Next: add it to a book —");
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "clean-book",
3
+ "description": "A calm, classic book look: serif body, generous margins, restrained accents.",
4
+ "author": "gutterpress",
5
+ "keywords": ["gutterpress", "gutterpress-look"],
6
+ "gutterpress": {
7
+ "styles": ["theme.css"],
8
+ "preview": null
9
+ }
10
+ }