gutterpress 0.11.17-alpha.2 → 0.11.17-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/api/index.d.ts +4 -0
  2. package/dist/api/index.js +10 -2
  3. package/dist/{build-1p999cmg.js → build-394c1ee1.js} +4 -4
  4. package/dist/checks/pdf/index.d.ts +1 -0
  5. package/dist/checks/pdf/signature.d.ts +3 -0
  6. package/dist/{cli-70xgehq2.js → cli-3p1e95zc.js} +1 -1
  7. package/dist/{cli-6z5zthzt.js → cli-9nba5ve6.js} +562 -211
  8. package/dist/{cli-ppc0vz4b.js → cli-d57bsfpb.js} +1 -1
  9. package/dist/{cli-gymfg04q.js → cli-jyy9pqf3.js} +15 -10
  10. package/dist/cli.js +12 -12
  11. package/dist/{doctor-reqpnn3h.js → doctor-c459qv5j.js} +2 -2
  12. package/dist/engine/compiler/build.d.ts +3 -2
  13. package/dist/engine/compiler/postprocess.d.ts +6 -4
  14. package/dist/engine/shared/synthesis.d.ts +29 -5
  15. package/dist/{engine-5jzhjjvv.js → engine-3xqgm82d.js} +2 -2
  16. package/dist/{engine-078ggczp.js → engine-zdb41ay1.js} +1 -1
  17. package/dist/{ext-dsbfejt3.js → ext-ha7gg8h9.js} +4 -4
  18. package/dist/{gutterpress-viewer-zkv2stfy.js → gutterpress-viewer-1n793gwd.js} +5 -3
  19. package/dist/{index-c275p061.js → index-bscfmg5r.js} +1714 -1054
  20. package/dist/{index-5dcj1vts.js → index-hrx0gfkj.js} +23 -684
  21. package/dist/index-q1xqxrh9.js +949 -0
  22. package/dist/index.js +11 -3
  23. package/dist/lib/black-text-gray.d.ts +38 -8
  24. package/dist/lib/build-fingerprint.d.ts +2 -0
  25. package/dist/lib/engine.d.ts +2 -1
  26. package/dist/lib/lint-rules.d.ts +9 -0
  27. package/dist/lib/markdown/assemble.d.ts +3 -5
  28. package/dist/lib/markdown/index.d.ts +3 -8
  29. package/dist/lib/markdown/renderer.d.ts +9 -1
  30. package/dist/lib/page-size.d.ts +66 -0
  31. package/dist/lib/pdf-inspect.d.ts +6 -0
  32. package/dist/lib/printsafe.d.ts +8 -1
  33. package/dist/lib/project-scaffold.d.ts +3 -9
  34. package/dist/lib/remote-auth/github-repos.d.ts +7 -0
  35. package/dist/lib/remote-auth/online-backup.d.ts +51 -0
  36. package/dist/lib/snippets.d.ts +3 -0
  37. package/dist/{manifest.schema-40sfq49z.json → manifest.schema-gh0jjve9.json} +14 -2
  38. package/dist/{new-m5y2wepr.js → new-kky3z7kv.js} +4 -4
  39. package/dist/{plugin.js-m4a7m3t4.tpl → plugin.js-cswq7wvd.tpl} +4 -1
  40. package/dist/{plugin.test.js-6n8ct5e3.tpl → plugin.test.js-xgxrd8hf.tpl} +4 -1
  41. package/dist/{preflight-bwpqwwny.js → preflight-t8v6szq6.js} +4 -4
  42. package/dist/preview/file-watcher.d.ts +2 -4
  43. package/dist/{preview-62y7qhkp.js → preview-cxab4y5x.js} +4 -4
  44. package/dist/{publish-k2zyt3x6.js → publish-s2mtenb5.js} +4 -4
  45. package/dist/render.js +30 -5
  46. package/dist/schema/manifest.types.d.ts +17 -0
  47. package/dist/{validate-fgwwxtbn.js → validate-xaajdw4x.js} +4 -4
  48. package/package.json +1 -1
  49. package/dist/index-eyaj6nkc.js +0 -267
package/dist/index.js CHANGED
@@ -71,6 +71,7 @@ import {
71
71
  isGitInternalPath,
72
72
  isPathSpecifier,
73
73
  isSshRemoteUrl,
74
+ isValidRepositoryName,
74
75
  knownForgeTokenUrl,
75
76
  listBuiltInStyleSets,
76
77
  listBuiltInTemplates,
@@ -102,6 +103,7 @@ import {
102
103
  readExtensionCss,
103
104
  readExtensionMeta,
104
105
  readManifestFields,
106
+ readPageSetup,
105
107
  readPublishSettings,
106
108
  refreshRemoteCopies,
107
109
  removeExtension,
@@ -139,7 +141,9 @@ import {
139
141
  setActiveStyles,
140
142
  setExtensionEnabled,
141
143
  setManifestFields,
144
+ setPageSetup,
142
145
  setPublishProviderConfig,
146
+ setUpOnlineBackup,
143
147
  slugifyProjectName,
144
148
  splitOutPath,
145
149
  startPreviewServer,
@@ -148,13 +152,13 @@ import {
148
152
  testRemoteAccess,
149
153
  updateExtensions,
150
154
  validateProjectExtensions
151
- } from "./index-c275p061.js";
155
+ } from "./index-bscfmg5r.js";
152
156
  import {
153
157
  BUILD_DIAGNOSTIC_CODES
154
- } from "./index-5dcj1vts.js";
158
+ } from "./index-hrx0gfkj.js";
155
159
  import {
156
160
  BuildError
157
- } from "./index-eyaj6nkc.js";
161
+ } from "./index-q1xqxrh9.js";
158
162
  import {
159
163
  DEFAULT_PRINT_OPTS,
160
164
  assertMilestone,
@@ -194,7 +198,9 @@ export {
194
198
  startPreviewServer,
195
199
  splitOutPath,
196
200
  slugifyProjectName,
201
+ setUpOnlineBackup,
197
202
  setPublishProviderConfig,
203
+ setPageSetup,
198
204
  setManifestFields,
199
205
  setExtensionEnabled,
200
206
  setActiveStyles,
@@ -237,6 +243,7 @@ export {
237
243
  refreshRemoteCopies,
238
244
  readyProbeExpr,
239
245
  readPublishSettings,
246
+ readPageSetup,
240
247
  readManifestFields,
241
248
  readExtensionMeta,
242
249
  readExtensionCss,
@@ -270,6 +277,7 @@ export {
270
277
  listBuiltInTemplates,
271
278
  listBuiltInStyleSets,
272
279
  knownForgeTokenUrl,
280
+ isValidRepositoryName,
273
281
  isSshRemoteUrl,
274
282
  isPathSpecifier,
275
283
  isNoChangesError,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * K-only black text for PDF/X (#331).
2
+ * K-only black text for PDF/X (#331, #336).
3
3
  *
4
4
  * PRINT-SPEC GAP: print vendors (DriveThruRPG) require body text to print on
5
5
  * the black plate alone (100% K). Chromium writes every text fill as
@@ -21,18 +21,48 @@
21
21
  * scoped to `BT`…`ET`: on entering a text object whose inherited fill/stroke
22
22
  * is near-black RGB we emit `0 g`/`0 G`, and at `ET` we put the original RGB
23
23
  * back. A near-black `rg`/`RG` set inside the block is rewritten the same way.
24
- * Page content streams and Form XObjects are both processed; a Form's
25
- * inherited (pre-`Do`) colour is unknowable from its own stream, so only
26
- * colours set inside the form are considered.
24
+ * Page content streams and Form XObjects are both processed.
27
25
  *
28
- * Known limit: text-decoration rectangles (underline/strike) are `re f` after
29
- * `ET`, i.e. non-text painting, and stay rich black.
26
+ * Size threshold (#336): DriveThruRPG recommends rich black for text LARGER
27
+ * than 24pt, so only text at or below {@link LARGE_TEXT_PT} is rewritten. The
28
+ * effective size is `Tf` size × the text matrix's scale (`Tm`) × the CTM's
29
+ * scale (`cm`, restored by `q`/`Q`) — each reduced to √|det| of its 2×2, so
30
+ * rotation and flips do not matter and anisotropic scaling is averaged.
31
+ * `Tz`/`Ts` are ignored. A Form XObject's stream cannot know the CTM it is
32
+ * drawn under, so the page pass records the CTM at each `Do` and the form is
33
+ * sized from that (the smallest, if drawn more than once — K-only large text
34
+ * is harmless, rich small text is not). A form reached any other way (nested
35
+ * in another form, unreferenced) has unknown scale and is treated as small.
36
+ * A `Tf` inside a text object re-evaluates, so one `BT` can switch between
37
+ * K-only and rich as the size crosses the threshold.
38
+ *
39
+ * Underline / strike-through (#336): Chromium (tagged PDF, which the engine
40
+ * always requests) wraps each text run and its decoration in one marked-content
41
+ * span (`/NonStruct <</MCID n>> BDC … EMC`) and paints the decoration as a thin
42
+ * `x y w h re f` outside `BT…ET`. A near-black such rect, thinner than a fifth
43
+ * of the span's small text, is wrapped `q 0 g … Q` so it prints K-only like the
44
+ * text it decorates. A rect outside any span, or in a span with no small text
45
+ * (rules, borders, large headings), is left alone — nothing is guessed.
46
+ */
47
+ /**
48
+ * DriveThruRPG: black text LARGER than this (in points) may be rich black;
49
+ * at or below it, it must be K-only.
30
50
  */
51
+ export declare const LARGE_TEXT_PT = 24;
52
+ /** What a stream's caller knows that the stream itself cannot. */
53
+ export interface ContentContext {
54
+ /** Scale in effect where the stream starts: 1 for a page, the invoking CTM for a form. 0 = unknown (treated as small). */
55
+ baseScale?: number;
56
+ /** Called for each `Do` with the XObject's resource name and the CTM scale there. */
57
+ onDo?: (name: string, scale: number) => void;
58
+ }
31
59
  /**
32
60
  * Rewrite near-black RGB fill/stroke to DeviceGray black inside BT…ET of one
33
- * content stream. Pure string → string; returns the input when nothing changed.
61
+ * content stream, for text at or below {@link LARGE_TEXT_PT}, plus the thin
62
+ * decoration rects in that text's marked-content span. Pure string → string;
63
+ * returns the input when nothing changed.
34
64
  */
35
- export declare function rewriteBlackText(src: string): string;
65
+ export declare function rewriteBlackText(src: string, ctx?: ContentContext): string;
36
66
  /**
37
67
  * Apply {@link rewriteBlackText} to every page content stream and Form XObject
38
68
  * of a PDF. Returns the input bytes untouched when nothing needed rewriting.
@@ -3,6 +3,8 @@ type PdfxFingerprintConfig = {
3
3
  resolvedFlavor: "x1a" | "x3";
4
4
  iccPath: string | null;
5
5
  stripAnnotations: boolean | null;
6
+ /** How near-black text was separated (#331); null when no PDF/X pass ran. Provenance only. */
7
+ blackText?: "icc" | "k-only" | null;
6
8
  };
7
9
  export type BuildFingerprintInput = {
8
10
  command: "build";
@@ -1,12 +1,13 @@
1
1
  import { type BuildDiagnostic } from "../engine/compiler/build.ts";
2
2
  import type { Browser as EngineBrowser } from "../engine/shared/cdp.ts";
3
+ import type { LastPageMode } from "../engine/shared/synthesis.ts";
3
4
  export type { EngineBrowser };
4
5
  /** The subset of `BuildOptions` that has a manifest/CLI source today (B.12). */
5
6
  export interface NativePdfOptions {
6
7
  title?: string;
7
8
  author?: string;
8
9
  signature?: number;
9
- reserveLastPage?: boolean;
10
+ lastPage?: LastPageMode;
10
11
  /**
11
12
  * Downgrade the engine's over-wide-content hard error to a warning +
12
13
  * diagnostic. The engine's message tells the author to "pass allowShrink to
@@ -0,0 +1,9 @@
1
+ export type LintLevel = "off" | "warn" | "error";
2
+ /** Read one rule's configured level; unrecognised values are ignored. */
3
+ export declare function ruleLevel(rules: Readonly<Record<string, unknown>> | undefined, id: string): LintLevel | undefined;
4
+ /**
5
+ * The rule ids named by a `gutterpress-disable-next-line <id>[, <id>...]
6
+ * [-- reason]` comment body (the text between the comment delimiters), or
7
+ * null when the comment is not such a directive.
8
+ */
9
+ export declare function parseDisableDirective(commentText: string): string[] | null;
@@ -1,4 +1,5 @@
1
1
  import { type LoadedPlugin } from "./renderer";
2
+ import { type PrintPadding } from "../../engine/shared/synthesis.ts";
2
3
  /** Reader injected by the host: resolve a project-root-relative file → its text. */
3
4
  export type ReadText = (relPath: string) => Promise<string>;
4
5
  /**
@@ -118,14 +119,11 @@ export interface AssembleBookHtmlOptions {
118
119
  title?: string;
119
120
  /**
120
121
  * The manifest's `print` padding, written on `<html>` as
121
- * `data-gp-signature` / `data-gp-reserve-last-page` so the viewer shows the
122
+ * `data-gp-signature` / `data-gp-last-page` so the viewer shows the
122
123
  * blank pages the PDF build appends (`signaturePadding` in
123
124
  * engine/shared/synthesis.ts) and the two page counts agree.
124
125
  */
125
- print?: {
126
- signature?: number;
127
- reserveLastPage?: boolean;
128
- };
126
+ print?: PrintPadding;
129
127
  plugins?: LoadedPlugin[];
130
128
  pluginCss?: string;
131
129
  /** Add a layout-neutral source-file id to source-mapped preview blocks. */
@@ -1,6 +1,7 @@
1
1
  import { type LayoutWarning } from "./assemble";
2
2
  import { type AssetCopy } from "../asset-inline";
3
3
  import type { LoadedPlugin, PluginStyleGroup } from "./renderer";
4
+ import type { PrintPadding } from "../../engine/shared/synthesis.ts";
4
5
  export type { LayoutWarning } from "./assemble";
5
6
  /**
6
7
  * THE canonical "which markdown files make up this book, and in what order?"
@@ -31,10 +32,7 @@ export declare function resolveActiveMarkdownFiles(inputDir: string, configuredF
31
32
  export declare function renderChapters(inputDir: string, opts?: {
32
33
  title?: string;
33
34
  /** See {@link AssembleBookHtmlOptions.print}. */
34
- print?: {
35
- signature?: number;
36
- reserveLastPage?: boolean;
37
- };
35
+ print?: PrintPadding;
38
36
  styles?: string[];
39
37
  files?: string[] | null;
40
38
  plugins?: LoadedPlugin[];
@@ -73,10 +71,7 @@ export declare function renderChapters(inputDir: string, opts?: {
73
71
  export declare function renderChaptersToFile(inputDir: string, outDir: string, opts?: {
74
72
  title?: string;
75
73
  /** See {@link AssembleBookHtmlOptions.print}. */
76
- print?: {
77
- signature?: number;
78
- reserveLastPage?: boolean;
79
- };
74
+ print?: PrintPadding;
80
75
  styles?: string[];
81
76
  files?: string[] | null;
82
77
  plugins?: LoadedPlugin[];
@@ -62,7 +62,7 @@ export interface GutterpressMarkerLabel {
62
62
  * One entry in a plugin's declared `markers` table (#240 — "declarative
63
63
  * container components in core"). Exactly one of three shapes:
64
64
  *
65
- * - a CONTAINER: `section`/`tag`/`class`/`variants`/`label`/`autoCloseAt`, any/all
65
+ * - a CONTAINER: `section`/`tag`/`class`/`variants`/`label`/`autoCloseAt`/`selfClosing`, any/all
66
66
  * optional (a bare `{}` is a valid, if pointless, `<div>` wrapper);
67
67
  * - an ALIAS: `alias` (another declared name) + optional `preset`;
68
68
  * - a DEPRECATION: `deprecated` (a human-readable retirement message) —
@@ -132,6 +132,14 @@ export interface GutterpressMarkerDeclaration {
132
132
  * warns (the default — most likely a forgotten `@end-<name>`).
133
133
  */
134
134
  autoCloseAt?: Array<"eof">;
135
+ /**
136
+ * The marker is one element with no body and no `@end-<name>`: `@tape
137
+ * label="..."` emits the element (and its label) and closes it at once, so
138
+ * nothing that follows is nested inside and `@end-<name>` warns. Cannot be
139
+ * combined with `section` or `autoCloseAt`. A `validate` on a self-closing
140
+ * marker always sees empty `blocks` and `text`. Aliases inherit it.
141
+ */
142
+ selfClosing?: boolean;
135
143
  /** This marker is sugar for another declared marker (must name a non-alias, non-deprecated entry). */
136
144
  alias?: string;
137
145
  /** With `alias`: defaults applied when the invocation line supplies none. */
@@ -0,0 +1,66 @@
1
+ import type { Document } from "yaml";
2
+ import { type PresetId } from "./presets.ts";
3
+ /** Page bounds written to the manifest's `page:` block (points; 72pt = 1in). */
4
+ export interface CustomPageOptions {
5
+ /** Trim width in points (72pt = 1in). */
6
+ width: number;
7
+ /** Trim height in points (72pt = 1in). */
8
+ height: number;
9
+ /** Allowed deviation in points when validating a built PDF. Default 0.5. */
10
+ tolerance?: number;
11
+ }
12
+ /** A page size in points. */
13
+ export interface PageSizePoints {
14
+ width: number;
15
+ height: number;
16
+ }
17
+ /** What an author picks: a preset, plus the trim when the preset is `custom`. */
18
+ export interface PageSizeChoice {
19
+ preset: PresetId;
20
+ /** Required for `custom`; ignored for `dtrpg` / `book` (the preset owns it). */
21
+ page?: PageSizePoints;
22
+ }
23
+ /** A book's page size as currently recorded, read back for the settings UI. */
24
+ export interface PageSetup {
25
+ /** The manifest's preset (`dtrpg` when undeclared — the product default); null when unrecognised. */
26
+ preset: PresetId | null;
27
+ /** The validation bounds the manifest resolves to, or null (custom with no `page:`). */
28
+ bounds: (PageSizePoints & {
29
+ tolerance: number;
30
+ }) | null;
31
+ /** What the book's stylesheets print at, or null when no plain `@page` sets a size. */
32
+ css: (PageSizePoints & {
33
+ file: string;
34
+ }) | null;
35
+ }
36
+ export declare function isPositivePoints(value: unknown): value is number;
37
+ /**
38
+ * Write the manifest's `page:` bounds. `tolerance` defaults to whatever the
39
+ * block already carries, so re-sizing a book never silently resets a
40
+ * hand-tuned tolerance.
41
+ */
42
+ export declare function writePageBounds(doc: Document, page: CustomPageOptions): void;
43
+ /** The size (points) a choice means: `custom` brings its own, the others are the preset's. */
44
+ export declare function pageSizeForChoice(choice: PageSizeChoice): PageSizePoints;
45
+ /** The `size` value written for a page: `8.5in 11in`, `595pt 842pt`. */
46
+ export declare function formatPageSize(page: PageSizePoints): string;
47
+ /** The size the LAST plain `@page { size }` in the stylesheet sets (cascade: last wins), or null. */
48
+ export declare function readPageSizeFromCss(css: string): PageSizePoints | null;
49
+ /**
50
+ * Point a stylesheet's plain `@page` rule at `page`. Rewrites the existing
51
+ * `size` declaration's value in place — comments, whitespace, other rules and
52
+ * `!important` untouched — or, when no plain `@page` sets a size, appends a
53
+ * minimal rule. An already-matching size leaves the text exactly as it was.
54
+ * Throws when the CSS does not parse (it must never be half-rewritten).
55
+ */
56
+ export declare function setPageSizeInCss(css: string, page: PageSizePoints): string;
57
+ /** Read a book's page size back from its manifest and stylesheets. */
58
+ export declare function readPageSetup(projectDir: string): Promise<PageSetup>;
59
+ /**
60
+ * Change a book's page size: the manifest's `preset:` + `page:` bounds and the
61
+ * stylesheet's `@page` size, together. Mirrors what the wizard records —
62
+ * `dtrpg` / `book` carry their own bounds (any `page:` block is removed so it
63
+ * cannot shadow them), `custom` records the typed trim. Nothing is written
64
+ * unless the whole choice validates and the stylesheet parses.
65
+ */
66
+ export declare function setPageSetup(projectDir: string, choice: PageSizeChoice): Promise<PageSetup>;
@@ -134,6 +134,12 @@ export interface OpPass {
134
134
  fonts: FontRef[];
135
135
  }
136
136
  export declare function getOpPass(doc: PDFDocumentProxy): Promise<OpPass>;
137
+ /**
138
+ * Does page `pageNum` (1-based) paint anything at all? False for a page with
139
+ * no content and for one whose operators only set state or clip. A path op
140
+ * carries its painting operator as its first argument (`constructPath`).
141
+ */
142
+ export declare function pageHasMarks(doc: PDFDocumentProxy, pageNum: number): Promise<boolean>;
137
143
  /**
138
144
  * Per-image effective DPI = pixel dimension ÷ placed size in inches. Decodes
139
145
  * each image (best-effort). Used only by the opt-in image-resolution check.
@@ -9,8 +9,15 @@ export interface PrintSafeWarning {
9
9
  line: number;
10
10
  column: number;
11
11
  }
12
+ export interface CheckCssOptions {
13
+ /** The manifest's `lint.rules`: `off` drops a rule, `warn`/`error` set its severity. */
14
+ rules?: Readonly<Record<string, unknown>>;
15
+ }
12
16
  /**
13
17
  * Run print-safety checks against a CSS string. Returns one warning per finding
14
18
  * (errors for remote URLs / syntax errors; warnings for risky print effects).
19
+ * A `/* gutterpress-disable-next-line <rule-id> *\/` comment silences that
20
+ * rule on the line directly below it; `options.rules` applies the project's
21
+ * per-rule levels (see lint-rules.ts).
15
22
  */
16
- export declare function checkCss(css: string, from?: string): PrintSafeWarning[];
23
+ export declare function checkCss(css: string, from?: string, options?: CheckCssOptions): PrintSafeWarning[];
@@ -1,4 +1,5 @@
1
1
  import { type PresetId } from "./presets.ts";
2
+ import { type CustomPageOptions } from "./page-size.ts";
2
3
  /**
3
4
  * Which embedded starter template to scaffold from. Each id maps to a directory
4
5
  * under `assets/templates/<id>/` (a `manifest.yaml` + a `chapter-01.md`), baked
@@ -71,15 +72,8 @@ export interface CreateProjectOptions {
71
72
  /** Version-history mode for the new project. Defaults to `"local-git"`. */
72
73
  versionHistory?: ProjectVersionHistoryMode;
73
74
  }
74
- /** Page bounds for {@link CreateProjectOptions.customPage}. */
75
- export interface CustomPageOptions {
76
- /** Trim width in points (72pt = 1in). */
77
- width: number;
78
- /** Trim height in points (72pt = 1in). */
79
- height: number;
80
- /** Allowed deviation in points when validating a built PDF. Default 0.5. */
81
- tolerance?: number;
82
- }
75
+ /** Page bounds for {@link CreateProjectOptions.customPage} (defined in page-size.ts). */
76
+ export type { CustomPageOptions };
83
77
  /** The result of a successful scaffold. */
84
78
  export interface CreateProjectResult {
85
79
  /** Absolute path of the created project directory. */
@@ -19,6 +19,13 @@ export interface RemoteRepository {
19
19
  private: boolean;
20
20
  defaultBranch: string;
21
21
  htmlUrl: string;
22
+ /**
23
+ * Could be an empty repository the author can back a book up to: GitHub
24
+ * reports size 0, and the repository is writable and not archived. A hint
25
+ * for pickers only — a tiny non-empty repository also reports size 0, so
26
+ * the real emptiness check happens when a book is connected (online-backup.ts).
27
+ */
28
+ maybeEmpty?: boolean;
22
29
  }
23
30
  /** One branch of a remote repository. */
24
31
  export interface RemoteBranch {
@@ -0,0 +1,51 @@
1
+ import { type RemoteRepository } from "./github-repos.ts";
2
+ import type { SyncOutcome, SyncProjectOptions } from "./sync-types.ts";
3
+ import type { HostCredential, TokenStore } from "./token-store.ts";
4
+ /** What to connect the book to. */
5
+ export type BackupTarget = {
6
+ kind: "create";
7
+ /** Repository name (letters, numbers, `-`, `_`, `.`). */
8
+ name: string;
9
+ /** Defaults to private — a book is not published by backing it up. */
10
+ private?: boolean;
11
+ } | {
12
+ kind: "existing";
13
+ owner: string;
14
+ name: string;
15
+ };
16
+ export interface SetUpOnlineBackupOptions {
17
+ projectDir: string;
18
+ target: BackupTarget;
19
+ /** Explicit credential; wins over the token store. */
20
+ credential?: HostCredential;
21
+ /** Source of the saved GitHub connection when no credential is passed. */
22
+ tokenStore?: TokenStore;
23
+ authorName?: string;
24
+ authorEmail?: string;
25
+ /** Injectable fetch for tests. Defaults to global fetch. */
26
+ fetchImpl?: typeof fetch;
27
+ /** Injectable git transport for tests (passed to `syncProject`). */
28
+ httpClient?: SyncProjectOptions["httpClient"];
29
+ /** Operation log (passed to `syncProject`). */
30
+ logFile?: SyncProjectOptions["logFile"];
31
+ }
32
+ export type BackupSetupFailureReason = "not-connected" | "auth" | "offline" | "no-history" | "already-has-remote" | "invalid-name" | "name-taken" | "not-found" | "no-permission" | "not-empty" | "push-failed";
33
+ export type BackupSetupResult = {
34
+ status: "connected";
35
+ repository: RemoteRepository;
36
+ sync: SyncOutcome;
37
+ } | {
38
+ status: "failed";
39
+ reason: BackupSetupFailureReason;
40
+ /** Plain-language, safe to show verbatim. */
41
+ message: string;
42
+ /** The repository that exists online after a late failure (created or chosen). */
43
+ repository?: RemoteRepository;
44
+ };
45
+ /** True for a name GitHub accepts for a repository. */
46
+ export declare function isValidRepositoryName(name: string): boolean;
47
+ /**
48
+ * Connect a locally-versioned book to a GitHub repository and push its
49
+ * history. Never throws for expected outcomes; see the header.
50
+ */
51
+ export declare function setUpOnlineBackup(options: SetUpOnlineBackupOptions): Promise<BackupSetupResult>;
@@ -87,6 +87,9 @@ export interface MarkerComponent {
87
87
  * as the renderer resolves them — an alias lists its target's. A variant
88
88
  * only adds a class, so one snippet serves them all. */
89
89
  variants: string[];
90
+ /** True for a `selfClosing` marker: one element, no body, no `@end-<name>` —
91
+ * the editor inserts the bare marker without a closing pair. */
92
+ selfClosing?: true;
90
93
  /** The winning example snippet's body (book, then extension, then core). */
91
94
  snippet?: string;
92
95
  }
@@ -169,7 +169,13 @@
169
169
  },
170
170
  "reserveLastPage": {
171
171
  "type": "boolean",
172
- "description": "Guarantee the final page is a blank page added by the build (no background, no page number). When the content already fills a signature exactly, a full signature of blank pages is added. DriveThruRPG reserves the last page for printer information. Preset defaults: dtrpg true, book false."
172
+ "description": "Guarantee the final page is a blank page added by the build (no background, no page number). When the content already fills a signature exactly, a full signature of blank pages is added. DriveThruRPG reserves the last page for printer information. Preset defaults: dtrpg true, book false. Same as `lastPage: blank`; `lastPage` wins when both are set."
173
+ },
174
+ "lastPage": {
175
+ "type": "string",
176
+ "description": "How the interior's last page is reserved for a printer that uses it (DriveThruRPG puts its printer information there). `blank` ends the interior on a blank page the build adds. `short-by-one` pads to one page short of a multiple of `signature` (k x signature - 1), and the printer supplies the last page. Wins over `reserveLastPage`. Not set by any preset.",
177
+ "enum": ["blank", "short-by-one"],
178
+ "examples": ["blank", "short-by-one"]
173
179
  }
174
180
  }
175
181
  },
@@ -217,7 +223,7 @@
217
223
  },
218
224
  "lint": {
219
225
  "type": "object",
220
- "description": "Markdown linting run before the build.",
226
+ "description": "Lint settings: whether the pre-build lint runs, the markdownlint config, and per-rule level overrides.",
221
227
  "properties": {
222
228
  "enabled": {
223
229
  "type": "boolean",
@@ -228,6 +234,12 @@
228
234
  "type": ["string", "null"],
229
235
  "description": "Path to a markdownlint config file, relative to the manifest directory. null uses the built-in defaults.",
230
236
  "default": null
237
+ },
238
+ "rules": {
239
+ "type": "object",
240
+ "description": "Per-rule level overrides, keyed by rule id: a printsafe rule (printsafe/no-risky-print-effects, printsafe/page-containment, printsafe/no-remote-urls) or a check id such as source.accessibility.heading-order. \"off\" silences the rule; \"warn\" / \"error\" set its severity. An id that matches no rule is ignored. To silence one site instead, put a `gutterpress-disable-next-line <rule-id> -- reason` comment (CSS `/* */` or markdown `<!-- -->`) on the line above it.",
241
+ "additionalProperties": { "enum": ["off", "warn", "error"] },
242
+ "examples": [{ "printsafe/no-risky-print-effects": "off" }]
231
243
  }
232
244
  }
233
245
  },
@@ -7,16 +7,16 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-6z5zthzt.js";
10
+ } from "./cli-9nba5ve6.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-70xgehq2.js";
13
+ } from "./cli-3p1e95zc.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-ppc0vz4b.js";
19
- import"./cli-gymfg04q.js";
18
+ } from "./cli-d57bsfpb.js";
19
+ import"./cli-jyy9pqf3.js";
20
20
  import {
21
21
  EXIT_CODES
22
22
  } from "./cli-46ycxe6r.js";
@@ -62,12 +62,15 @@ const PREFIX = "{{PREFIX}}";
62
62
  * @end-term-box
63
63
  *
64
64
  * Fields, all optional:
65
- * section true makes the marker a core @section (no tag/label/autoCloseAt)
65
+ * section true makes the marker a core @section (no tag/label/autoCloseAt/selfClosing)
66
66
  * tag wrapper element (default "div")
67
67
  * class base class on the wrapper
68
68
  * variants extra classes keyed by the marker's bare word
69
69
  * label a label element built from one of the marker's attributes
70
70
  * autoCloseAt ["eof"] closes an unclosed container at end of file
71
+ * selfClosing true for a marker with no body and no @end-<name>: it emits its
72
+ * element (and label) and closes it at once. Not with autoCloseAt;
73
+ * a `validate` on it sees empty `blocks` and `text`.
71
74
  * snippet example content the editor inserts for this component, as a
72
75
  * path inside this folder (default: snippets/<name>.md — so
73
76
  * `snippets/term-box.md` is term-box's example already)
@@ -210,6 +210,9 @@ describe("conventions", () => {
210
210
  for (const at of decl.autoCloseAt ?? []) {
211
211
  expect(at).toBe("eof");
212
212
  }
213
+ expect(typeof decl.selfClosing === "undefined" || typeof decl.selfClosing === "boolean").toBe(true);
214
+ // A self-closing marker has no frame for autoCloseAt to close.
215
+ if (decl.selfClosing) expect(decl.autoCloseAt).toBeUndefined();
213
216
  expect(name).toBeTruthy();
214
217
  }
215
218
  });
@@ -218,7 +221,7 @@ describe("conventions", () => {
218
221
  // The editor inserts this file when an author picks the component, so it
219
222
  // is the example of the structure the component expects.
220
223
  for (const [name, decl] of Object.entries(markers)) {
221
- if (decl.deprecated !== undefined || decl.alias !== undefined) continue;
224
+ if (decl.deprecated !== undefined || decl.alias !== undefined || decl.selfClosing) continue;
222
225
  const rel = decl.snippet ?? `${pkg.gutterpress?.snippets ?? "snippets"}/${name}.md`;
223
226
  if (!existsSync(path.join(root, rel))) continue;
224
227
  const snippet = read(rel);
@@ -2,16 +2,16 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-6z5zthzt.js";
5
+ } from "./cli-9nba5ve6.js";
6
6
  import {
7
7
  log
8
- } from "./cli-70xgehq2.js";
8
+ } from "./cli-3p1e95zc.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-ppc0vz4b.js";
14
- import"./cli-gymfg04q.js";
13
+ } from "./cli-d57bsfpb.js";
14
+ import"./cli-jyy9pqf3.js";
15
15
  import {
16
16
  BuildError,
17
17
  EXIT_CODES
@@ -7,6 +7,7 @@
7
7
  import { type FSWatcher } from 'chokidar';
8
8
  import type { ServerState } from './server-context';
9
9
  import type { ResolvedExtensionConfig } from '../schema/manifest.types';
10
+ import type { PrintPadding } from '../engine/shared/synthesis.ts';
10
11
  /**
11
12
  * Rewrite rendered book HTML for the live preview: inject the Gutterpress
12
13
  * engine's viewer bundle (`/engine/gutterpress-viewer.js`, embedded the same
@@ -43,10 +44,7 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
43
44
  files?: string[] | null;
44
45
  };
45
46
  extensions?: ResolvedExtensionConfig[];
46
- print?: {
47
- signature?: number;
48
- reserveLastPage?: boolean;
49
- };
47
+ print?: PrintPadding;
50
48
  }, cssAssets: Map<string, string>, imageRefs?: Set<string>): Promise<void>;
51
49
  /**
52
50
  * Chokidar 5's `ignored` matcher (regex or function) is tested against the
@@ -7,10 +7,10 @@ import {
7
7
  runBuild,
8
8
  splitOutPath,
9
9
  startPreviewServer
10
- } from "./cli-6z5zthzt.js";
10
+ } from "./cli-9nba5ve6.js";
11
11
  import {
12
12
  log
13
- } from "./cli-70xgehq2.js";
13
+ } from "./cli-3p1e95zc.js";
14
14
  import {
15
15
  UsageError,
16
16
  parseFormat,
@@ -18,8 +18,8 @@ import {
18
18
  rejectExtraPositionals,
19
19
  rejectUnknownFlags,
20
20
  resolvePort
21
- } from "./cli-ppc0vz4b.js";
22
- import"./cli-gymfg04q.js";
21
+ } from "./cli-d57bsfpb.js";
22
+ import"./cli-jyy9pqf3.js";
23
23
  import {
24
24
  BuildError
25
25
  } from "./cli-46ycxe6r.js";
@@ -11,17 +11,17 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-6z5zthzt.js";
14
+ } from "./cli-9nba5ve6.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-70xgehq2.js";
18
+ } from "./cli-3p1e95zc.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-ppc0vz4b.js";
24
- import"./cli-gymfg04q.js";
23
+ } from "./cli-d57bsfpb.js";
24
+ import"./cli-jyy9pqf3.js";
25
25
  import {
26
26
  EXIT_CODES
27
27
  } from "./cli-46ycxe6r.js";