gutterpress 0.11.16-alpha.5 → 0.11.16
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.
- package/README.md +36 -4
- package/dist/api/index.d.ts +4 -1
- package/dist/api/index.js +7 -3
- package/dist/{build-ee2traqp.js → build-8epzrj8g.js} +7 -5
- package/dist/{cli-6s4pkvbx.js → cli-0prrt9e4.js} +1 -1
- package/dist/{cli-cg03zvez.js → cli-182j48xc.js} +2 -1
- package/dist/{cli-4d4xegsw.js → cli-ev175hx9.js} +9822 -9573
- package/dist/cli.js +12 -12
- package/dist/{doctor-zn08944p.js → doctor-24mbx7y1.js} +2 -2
- package/dist/{ext-bw7da6e1.js → ext-p9gx5aac.js} +3 -3
- package/dist/{index-nk2mt7gp.js → index-40achhs8.js} +7936 -7685
- package/dist/index.js +7 -3
- package/dist/lib/extension-manager.d.ts +13 -0
- package/dist/lib/extension-restore.d.ts +74 -0
- package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
- package/dist/lib/npm-plugin-installer.d.ts +8 -0
- package/dist/lib/project-gitignore.d.ts +10 -0
- package/dist/lib/project-scaffold.d.ts +2 -22
- package/dist/lib/validation-exec.d.ts +3 -2
- package/dist/{new-c9b5wwpe.js → new-wsjsrm7w.js} +3 -3
- package/dist/{preflight-mnpqsnsz.js → preflight-0fjjnk9e.js} +5 -4
- package/dist/{preview-kcqapj66.js → preview-azy06gw0.js} +7 -4
- package/dist/{publish-zf218d0c.js → publish-8db67m2h.js} +7 -5
- package/dist/render.js +33 -9
- package/dist/{validate-mh9r5qzx.js → validate-e7mw1xyd.js} +5 -4
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
describeExtension,
|
|
49
49
|
diagnoseProjectRemote,
|
|
50
50
|
disconnectPublishCredential,
|
|
51
|
-
|
|
51
|
+
ensureProjectGitignore,
|
|
52
52
|
escapeYamlScalar,
|
|
53
53
|
executeAndReport,
|
|
54
54
|
executeValidation,
|
|
@@ -119,6 +119,8 @@ import {
|
|
|
119
119
|
resolveGoogleClientSecret,
|
|
120
120
|
resolvePublishFormat,
|
|
121
121
|
resolvePublishRequest,
|
|
122
|
+
restoreFailureMessage,
|
|
123
|
+
restorePinnedExtensions,
|
|
122
124
|
revokeGoogleCredential,
|
|
123
125
|
ruleRemoteUrls,
|
|
124
126
|
ruleRiskyProps,
|
|
@@ -146,7 +148,7 @@ import {
|
|
|
146
148
|
testRemoteAccess,
|
|
147
149
|
updateExtensions,
|
|
148
150
|
validateProjectExtensions
|
|
149
|
-
} from "./index-
|
|
151
|
+
} from "./index-40achhs8.js";
|
|
150
152
|
import {
|
|
151
153
|
BUILD_DIAGNOSTIC_CODES
|
|
152
154
|
} from "./index-qep93v0z.js";
|
|
@@ -212,6 +214,8 @@ export {
|
|
|
212
214
|
ruleRemoteUrls,
|
|
213
215
|
revokeGoogleCredential,
|
|
214
216
|
restoreVersionWithBackup,
|
|
217
|
+
restorePinnedExtensions,
|
|
218
|
+
restoreFailureMessage,
|
|
215
219
|
resolvePublishRequest,
|
|
216
220
|
resolvePublishFormat,
|
|
217
221
|
resolveLogger,
|
|
@@ -290,7 +294,7 @@ export {
|
|
|
290
294
|
executeValidation,
|
|
291
295
|
executeAndReport,
|
|
292
296
|
escapeYamlScalar,
|
|
293
|
-
|
|
297
|
+
ensureProjectGitignore,
|
|
294
298
|
disconnectPublishCredential,
|
|
295
299
|
diagnoseProjectRemote,
|
|
296
300
|
detectProjectSource,
|
|
@@ -82,6 +82,8 @@ export interface BuiltInStyleSet {
|
|
|
82
82
|
name: string;
|
|
83
83
|
description: string;
|
|
84
84
|
}
|
|
85
|
+
/** One key per book folder, however the path is spelled (Windows paths are case-insensitive). */
|
|
86
|
+
export declare function projectKey(projectDir: string): string;
|
|
85
87
|
/**
|
|
86
88
|
* Two specifiers name the same extension when they are equal, or when both
|
|
87
89
|
* are npm specifiers for the same package (a re-install re-pins the version
|
|
@@ -146,6 +148,17 @@ export interface AddExtensionOptions extends NpmPluginInstallOptions {
|
|
|
146
148
|
* Returns the described entry.
|
|
147
149
|
*/
|
|
148
150
|
export declare function addExtension(projectDir: string, specifier: string, options?: AddExtensionOptions): Promise<ProjectExtensionEntry>;
|
|
151
|
+
/**
|
|
152
|
+
* Download one pinned npm extension again (`name@<exact version>`): the same
|
|
153
|
+
* verified transaction as {@link addExtension}, minus the manifest write — the
|
|
154
|
+
* pin is already there and is never changed. Returns the install's warnings.
|
|
155
|
+
*/
|
|
156
|
+
export declare function restoreNpmExtension(projectDir: string, name: string, version: string, exportName: string | undefined, options?: NpmPluginInstallOptions & {
|
|
157
|
+
/** Every version of `name` an enabled manifest entry pins — never pruned. */
|
|
158
|
+
keepVersions?: readonly string[];
|
|
159
|
+
}): Promise<string[]>;
|
|
160
|
+
/** True when `plugins/npm/<name>/<version>/` is absent or has no loadable entry — what the loader calls "not installed" or "incomplete". */
|
|
161
|
+
export declare function vendoredCopyNeedsInstall(projectDir: string, name: string, version: string): Promise<boolean>;
|
|
149
162
|
/** One pinned npm extension against npm's `latest` tag. */
|
|
150
163
|
export interface ExtensionUpdateCheck {
|
|
151
164
|
/** The specifier as written in the manifest (`name@current`). */
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { NpmPluginInstallOptions } from "./npm-plugin-installer.ts";
|
|
2
|
+
export interface RestorePinnedOptions extends NpmPluginInstallOptions {
|
|
3
|
+
/** An explicit `--manifest` file; the book folder is then that file's folder. */
|
|
4
|
+
manifestPath?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Progress for a UI: nothing is reported when every copy is present; else a
|
|
7
|
+
* `start` (the packages to download), one `package` event as each starts
|
|
8
|
+
* and ends, and an `end`. A caller that joins a run already in flight for
|
|
9
|
+
* the same book gets that run's result but no events.
|
|
10
|
+
*/
|
|
11
|
+
onProgress?: (event: RestoreProgress) => void;
|
|
12
|
+
}
|
|
13
|
+
/** One step of a restore, for a progress indicator. `spec` is `name@version`. */
|
|
14
|
+
export type RestoreProgress = {
|
|
15
|
+
type: "start";
|
|
16
|
+
specs: string[];
|
|
17
|
+
} | {
|
|
18
|
+
type: "package";
|
|
19
|
+
spec: string;
|
|
20
|
+
index: number;
|
|
21
|
+
total: number;
|
|
22
|
+
state: "downloading" | "done" | "failed";
|
|
23
|
+
message?: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "end";
|
|
26
|
+
installed: string[];
|
|
27
|
+
failed: RestoreFailure[];
|
|
28
|
+
};
|
|
29
|
+
export interface RestoreFailure {
|
|
30
|
+
/** `name@version`, as pinned. */
|
|
31
|
+
use: string;
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
export interface RestoreResult {
|
|
35
|
+
/** File name of the manifest the pins were read from (`manifest.yaml`), or null when the folder has none. */
|
|
36
|
+
manifestFile: string | null;
|
|
37
|
+
/** `name@version` of each package downloaded. */
|
|
38
|
+
installed: string[];
|
|
39
|
+
failed: RestoreFailure[];
|
|
40
|
+
warnings: string[];
|
|
41
|
+
/** npm could not be reached, so every package still missing was reported as failed without being tried. */
|
|
42
|
+
offline: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Download every pinned npm extension of the book whose copy is missing or
|
|
46
|
+
* incomplete. Never throws for a failed download — failures come back in
|
|
47
|
+
* `failed` so each caller can decide (the CLI fails fast, preview and the
|
|
48
|
+
* desktop degrade). An unreadable manifest restores nothing: whoever reads it
|
|
49
|
+
* next reports the real problem.
|
|
50
|
+
*/
|
|
51
|
+
export declare function restorePinnedExtensions(projectDir: string, options?: RestorePinnedOptions): Promise<RestoreResult>;
|
|
52
|
+
/** What went wrong, which extension, and how to retry — one failure as a sentence. */
|
|
53
|
+
export declare function restoreFailureMessage(failure: RestoreFailure, manifestFile: string | null): string;
|
|
54
|
+
/**
|
|
55
|
+
* One sentence for a restore that could not reach npm: which extensions are
|
|
56
|
+
* missing, and that they have to be installed while online — after which the
|
|
57
|
+
* book works offline.
|
|
58
|
+
*/
|
|
59
|
+
export declare function restoreOfflineMessage(failed: RestoreFailure[], manifestFile: string | null): string;
|
|
60
|
+
/**
|
|
61
|
+
* The restore step every command and host path that loads a book's plugins
|
|
62
|
+
* runs first: restore, print one line per downloaded package, and either fail
|
|
63
|
+
* fast (build, export, validate and the checks: a final artifact must never
|
|
64
|
+
* silently omit author-configured formatting) or, for the live preview, warn
|
|
65
|
+
* and carry on so one missing package cannot blank the whole preview.
|
|
66
|
+
*
|
|
67
|
+
* It is called from the shared "load the book's plugins" seams themselves
|
|
68
|
+
* (`loadBuildPlugins`, `executeValidation`), not from each command, so a new
|
|
69
|
+
* command that builds or checks cannot forget it. The loader stays offline.
|
|
70
|
+
*/
|
|
71
|
+
export declare function restoreForCommand(dir: string, options: {
|
|
72
|
+
manifestPath?: string;
|
|
73
|
+
failFast: boolean;
|
|
74
|
+
}): Promise<void>;
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
* carrying gp-left could start shifting; that is standards-tracking per
|
|
134
134
|
* CLAUDE.md ("Chrome wins once it ships"), not a bug in the author's book.
|
|
135
135
|
*/
|
|
136
|
-
export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 75%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, exposed as author vocabulary so\n \"put this in two columns\" does not require borrowing a styled container\n from the book's own component layer. That borrowing is what this exists\n to prevent: a book whose theme paints .section chrome by default gives\n every author who opens a section just to start a column run a panel they\n did not ask for, and the book then needs a reset rule to take it back.\n With a neutral primitive the author opts into columns and nothing else.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set, because the correct value depends on whether the run fragments\n across pages (auto packs each page's columns; the CSS initial balance is\n right for a run that fits on one page) and only the author knows which.\n --gp-column-gap is author-settable. */\n.gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }\n.gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }\n\n/* the per-shape decisions the paragraph above deliberately leaves to the\n author, named instead of left as raw CSS every book was reinventing\n (CLAUDE.md \u00A70: \"behavior broadly useful to non-technical authors belongs\n in core\").\n Permanent vocabulary, standard properties verbatim \u2014 same rationale as\n the column/grid runs above. One name each, no aliases:\n .gp-columns-all column-span: all a heading or block that\n spans every column in the\n run it sits inside.\n .gp-columns-flow column-fill: auto a run that FRAGMENTS across\n pages \u2014 every page's\n columns fill instead of\n only the last one\n balancing (the dead-column\n collapse the build's\n engine.multicol.dead-column\n warning names this fix\n for).\n .gp-columns-balanced column-fill: balance a run that fits on ONE\n page (the CSS initial\n value \u2014 ragged columns\n would be wrong here). */\n.gp-columns-all { column-span: all; }\n.gp-columns-flow { column-fill: auto; }\n.gp-columns-balanced { column-fill: balance; }\n\n/* grid runs \u2014 the SLOTTED counterpart to the column runs above. Grid places\n each child into the next cell, across then down (deterministic slots: card\n layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run\n down then across. Same neutral-primitive rationale as .gp-columns-*, and\n permanent vocabulary for the same reason: standard CSS Grid verbatim, no\n spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):\n grid rows fragment across sheets with EXACT print/viewer parity \u2014 2- and\n 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,\n break-inside:avoid, gap geometry \u2014 so a grid taller than the page is safe,\n no fit-one-page constraint. Two things to know, not fix:\n - on a min-height page root (MARKER_CSS), default align-content\n stretches rows apart to fill the page \u2014 identically in both engines.\n Authors wanting packed rows set align-content: start.\n - a @page-break / @column-break marker DIRECTLY inside a grid container\n becomes a grid item and corrupts placement (the one measured parity\n break); markers.js diagnoses it (break_inside_grid).\n --gp-grid-gap is author-settable. */\n.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column/grid vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n inset:0 and the explicit centers are load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n inset: 0;\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n";
|
|
136
|
+
export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 75%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, exposed as author vocabulary so\n \"put this in two columns\" does not require borrowing a styled container\n from the book's own component layer. That borrowing is what this exists\n to prevent: a book whose theme paints .section chrome by default gives\n every author who opens a section just to start a column run a panel they\n did not ask for, and the book then needs a reset rule to take it back.\n With a neutral primitive the author opts into columns and nothing else.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set, because the correct value depends on whether the run fragments\n across pages (auto packs each page's columns; the CSS initial balance is\n right for a run that fits on one page) and only the author knows which.\n --gp-column-gap is author-settable.\n .gp-columns is what the core @columns marker emits (markers.js); on its\n own it is two columns, and @columns 1 to 5 add the matching count, which\n wins because it comes later at the same specificity. */\n.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5 {\n column-gap: var(--gp-column-gap, 1.5em);\n}\n.gp-columns { columns: 2; }\n.gp-columns-1 { columns: 1; }\n.gp-columns-2 { columns: 2; }\n.gp-columns-3 { columns: 3; }\n.gp-columns-4 { columns: 4; }\n.gp-columns-5 { columns: 5; }\n\n/* the per-shape decisions the paragraph above deliberately leaves to the\n author, named instead of left as raw CSS every book was reinventing\n (CLAUDE.md \u00A70: \"behavior broadly useful to non-technical authors belongs\n in core\").\n Permanent vocabulary, standard properties verbatim \u2014 same rationale as\n the column/grid runs above. One name each, no aliases:\n .gp-columns-all column-span: all a heading or block that\n spans every column in the\n run it sits inside.\n .gp-columns-flow column-fill: auto a run that FRAGMENTS across\n pages \u2014 every page's\n columns fill instead of\n only the last one\n balancing (the dead-column\n collapse the build's\n engine.multicol.dead-column\n warning names this fix\n for).\n .gp-columns-balanced column-fill: balance a run that fits on ONE\n page (the CSS initial\n value \u2014 ragged columns\n would be wrong here). */\n.gp-columns-all { column-span: all; }\n.gp-columns-flow { column-fill: auto; }\n.gp-columns-balanced { column-fill: balance; }\n\n/* grid runs \u2014 the SLOTTED counterpart to the column runs above. Grid places\n each child into the next cell, across then down (deterministic slots: card\n layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run\n down then across. Same neutral-primitive rationale as .gp-columns-*, and\n permanent vocabulary for the same reason: standard CSS Grid verbatim, no\n spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):\n grid rows fragment across sheets with EXACT print/viewer parity \u2014 2- and\n 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,\n break-inside:avoid, gap geometry \u2014 so a grid taller than the page is safe,\n no fit-one-page constraint. Two things to know, not fix:\n - on a min-height page root (MARKER_CSS), default align-content\n stretches rows apart to fill the page \u2014 identically in both engines.\n Authors wanting packed rows set align-content: start.\n - a @page-break / @column-break marker DIRECTLY inside a grid container\n becomes a grid item and corrupts placement (the one measured parity\n break); markers.js diagnoses it (break_inside_grid).\n --gp-grid-gap is author-settable. */\n.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column/grid vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n inset:0 and the explicit centers are load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n inset: 0;\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n";
|
|
137
137
|
/**
|
|
138
138
|
* GP_CLASSES — every class an author may legitimately write with a `gp-`
|
|
139
139
|
* prefix: the `.gp-*` selectors in `GUTTERPRESS_CSS` above, the two
|
|
@@ -22,8 +22,16 @@ export interface InstalledNpmPlugin {
|
|
|
22
22
|
installRoot: string;
|
|
23
23
|
/** Previous same-version directory held until the manifest commits. */
|
|
24
24
|
backupRoot: string | null;
|
|
25
|
+
/** Folders THIS install created (`plugins/`, `plugins/npm/`, `plugins/npm/<name>/`), outermost first. */
|
|
26
|
+
createdDirs: string[];
|
|
25
27
|
warnings: string[];
|
|
26
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Remove folders a failed install created — innermost first, and only while
|
|
31
|
+
* they are still empty, so a folder that gained anything else (a local plugin
|
|
32
|
+
* in `plugins/`, another package's copy) is never touched.
|
|
33
|
+
*/
|
|
34
|
+
export declare function removeEmptyDirs(dirs: readonly string[]): Promise<void>;
|
|
27
35
|
/**
|
|
28
36
|
* Download and publish a fresh vendor tree for one npm plugin. Never reuses
|
|
29
37
|
* bytes: a same-version reinstall downloads again and replaces the tree (the
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Entries every book ignores, in the order they are appended. */
|
|
2
|
+
export declare const PROJECT_GITIGNORE_ENTRIES: readonly ["dist/", "plugins/npm/"];
|
|
3
|
+
/**
|
|
4
|
+
* Make sure `<projectDir>/.gitignore` ignores `dist/` and `plugins/npm/`,
|
|
5
|
+
* creating the file or appending what is missing. Returns the entries the
|
|
6
|
+
* author's own rules re-include (left alone) so a caller can warn.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ensureProjectGitignore(projectDir: string): Promise<{
|
|
9
|
+
negated: string[];
|
|
10
|
+
}>;
|
|
@@ -143,31 +143,11 @@ export interface AdoptFolderOptions {
|
|
|
143
143
|
* Adopt an EXISTING folder as a gutterpress project, in place (no new subfolder).
|
|
144
144
|
* Writes a `manifest.yaml` (using the folder's existing top-level `.md` files as
|
|
145
145
|
* `source.files`, or scaffolding a `chapter-01.md` when there are none), copies
|
|
146
|
-
* a starter `styles/book.css`, ensures `dist/`
|
|
147
|
-
* `
|
|
146
|
+
* a starter `styles/book.css`, ensures `dist/` and `plugins/npm/` are gitignored
|
|
147
|
+
* (see `ensureProjectGitignore`), and optionally initialises local version history.
|
|
148
148
|
*
|
|
149
149
|
* NON-DESTRUCTIVE (global never-overwrite rule): refuses if the folder is
|
|
150
150
|
* already a project, and never overwrites an existing `manifest.yaml`,
|
|
151
151
|
* `styles/book.css`, `.gitignore`, or any markdown file.
|
|
152
152
|
*/
|
|
153
153
|
export declare function adoptFolder(options: AdoptFolderOptions): Promise<CreateProjectResult>;
|
|
154
|
-
/**
|
|
155
|
-
* Ensure `<projectDir>/.gitignore` excludes the build output directory
|
|
156
|
-
* (`dist/` — lib/output-paths.ts's `DIST_DIRNAME`).
|
|
157
|
-
*
|
|
158
|
-
* Confirmed bug this closes: no scaffolded project ever got a `.gitignore`,
|
|
159
|
-
* and gutterpress's auto-snapshot feature is ON BY DEFAULT and auto-pushes.
|
|
160
|
-
* Every `gutterpress build` writes a fresh, incompressible PDF (plus any copied
|
|
161
|
-
* assets) into `dist/<title-slug>/`; without an ignore rule, the very next
|
|
162
|
-
* snapshot commits and pushes it. That grows `.git` by a full PDF on every
|
|
163
|
-
* build and, once a single artifact crosses GitHub's 100MB per-file limit,
|
|
164
|
-
* the push is rejected outright — for a non-technical author with no way to
|
|
165
|
-
* `git filter-repo` their way out.
|
|
166
|
-
*
|
|
167
|
-
* NEVER overwrites an existing `.gitignore` (global never-delete-user-data
|
|
168
|
-
* rule): if the file already excludes `dist/` in some common spelling
|
|
169
|
-
* (`dist`, `dist/`, `/dist/`, …) it is left untouched; otherwise the line is
|
|
170
|
-
* APPENDED, not prepended or reformatted, so any content the author already
|
|
171
|
-
* has stays exactly as they wrote it.
|
|
172
|
-
*/
|
|
173
|
-
export declare function ensureGitignoreHasDist(projectDir: string): Promise<void>;
|
|
@@ -20,8 +20,9 @@ export interface ValidationExecutionArgs {
|
|
|
20
20
|
* `runQualityGates`, via `loadBuildPlugins`) so this run does not load
|
|
21
21
|
* plugins itself when the build already has. `undefined` (the default —
|
|
22
22
|
* every standalone `validate`/`preflight` invocation, and the desktop
|
|
23
|
-
* Problems panel) makes {@link executeValidation}
|
|
24
|
-
*
|
|
23
|
+
* Problems panel) makes {@link executeValidation} restore the book's
|
|
24
|
+
* missing pinned extensions (failing the run if it cannot) and then load
|
|
25
|
+
* plugins itself, degrade-and-report. An explicit `[]` is honored as-is, not treated as
|
|
25
26
|
* "unset".
|
|
26
27
|
*/
|
|
27
28
|
pluginStylePaths?: string[];
|
|
@@ -7,15 +7,15 @@ import {
|
|
|
7
7
|
TARGET_IDS,
|
|
8
8
|
scaffoldExtension,
|
|
9
9
|
scaffoldProject
|
|
10
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-ev175hx9.js";
|
|
11
11
|
import {
|
|
12
12
|
resolveGhostscript
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-0prrt9e4.js";
|
|
14
14
|
import {
|
|
15
15
|
UsageError,
|
|
16
16
|
rejectExtraPositionals,
|
|
17
17
|
rejectUnknownFlags
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-182j48xc.js";
|
|
19
19
|
import"./cli-wqy8hnnh.js";
|
|
20
20
|
import {
|
|
21
21
|
EXIT_CODES
|
|
@@ -2,17 +2,18 @@ import {
|
|
|
2
2
|
executeValidation,
|
|
3
3
|
publishTargetFor,
|
|
4
4
|
reportMissingTools
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-ev175hx9.js";
|
|
6
6
|
import {
|
|
7
7
|
log
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-0prrt9e4.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError,
|
|
11
11
|
rejectExtraPositionals,
|
|
12
12
|
rejectUnknownFlags
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-182j48xc.js";
|
|
14
14
|
import"./cli-wqy8hnnh.js";
|
|
15
15
|
import {
|
|
16
|
+
BuildError,
|
|
16
17
|
EXIT_CODES
|
|
17
18
|
} from "./cli-46ycxe6r.js";
|
|
18
19
|
import"./cli-kkzka31p.js";
|
|
@@ -190,7 +191,7 @@ var preflight_default = defineCommand({
|
|
|
190
191
|
});
|
|
191
192
|
} catch (error) {
|
|
192
193
|
log.error(error instanceof Error ? error.message : String(error));
|
|
193
|
-
process.exit(error instanceof UsageError ? error.exitCode : EXIT_CODES.USAGE);
|
|
194
|
+
process.exit(error instanceof UsageError || error instanceof BuildError ? error.exitCode : EXIT_CODES.USAGE);
|
|
194
195
|
}
|
|
195
196
|
if (!execution)
|
|
196
197
|
return;
|
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
} from "./cli-75qkn0k5.js";
|
|
4
4
|
import {
|
|
5
5
|
openPath,
|
|
6
|
+
restoreForCommand,
|
|
6
7
|
runBuild,
|
|
7
8
|
splitOutPath,
|
|
8
9
|
startPreviewServer
|
|
9
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-ev175hx9.js";
|
|
10
11
|
import {
|
|
11
12
|
log
|
|
12
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-0prrt9e4.js";
|
|
13
14
|
import {
|
|
14
15
|
UsageError,
|
|
15
16
|
parseFormat,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
rejectExtraPositionals,
|
|
18
19
|
rejectUnknownFlags,
|
|
19
20
|
resolvePort
|
|
20
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-182j48xc.js";
|
|
21
22
|
import"./cli-wqy8hnnh.js";
|
|
22
23
|
import {
|
|
23
24
|
BuildError
|
|
@@ -52,6 +53,7 @@ var preview_default = defineCommand({
|
|
|
52
53
|
if (typeof args.manifest === "string") {
|
|
53
54
|
throw new UsageError("--manifest is only supported by preview --format pdf or pdfx; live HTML preview discovers the book manifest from its input directory.");
|
|
54
55
|
}
|
|
56
|
+
await restoreForCommand(inputPath ?? process.cwd(), { failFast: false });
|
|
55
57
|
await startPreviewServer({
|
|
56
58
|
input: inputPath,
|
|
57
59
|
port: resolvePort(args.port),
|
|
@@ -68,6 +70,7 @@ var preview_default = defineCommand({
|
|
|
68
70
|
}
|
|
69
71
|
const pdfxFlavor = parsePdfxFlavor(args["pdfx-flavor"], format);
|
|
70
72
|
const { outDir, pdfFileOverride } = splitOutPath(typeof args.out === "string" ? args.out : undefined, format);
|
|
73
|
+
const manifestPath = typeof args.manifest === "string" ? args.manifest : undefined;
|
|
71
74
|
const result = await runBuild({
|
|
72
75
|
inputDir: inputPath,
|
|
73
76
|
format,
|
|
@@ -75,7 +78,7 @@ var preview_default = defineCommand({
|
|
|
75
78
|
pdfFileOverride,
|
|
76
79
|
pdfxFlavor,
|
|
77
80
|
iccPath: typeof args.icc === "string" ? args.icc : undefined,
|
|
78
|
-
manifestPath
|
|
81
|
+
manifestPath,
|
|
79
82
|
stripAnnotations: typeof args["strip-annotations"] === "boolean" ? args["strip-annotations"] : undefined,
|
|
80
83
|
skipLint: !!args["skip-lint"],
|
|
81
84
|
skipPreValidate: !!args["skip-pre-validate"],
|
|
@@ -11,16 +11,16 @@ import {
|
|
|
11
11
|
publishProviderFor,
|
|
12
12
|
resolvePublishFormat,
|
|
13
13
|
runPublish
|
|
14
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-ev175hx9.js";
|
|
15
15
|
import {
|
|
16
16
|
FileTokenStore,
|
|
17
17
|
log
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-0prrt9e4.js";
|
|
19
19
|
import {
|
|
20
20
|
UsageError,
|
|
21
21
|
rejectExtraPositionals,
|
|
22
22
|
rejectUnknownFlags
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-182j48xc.js";
|
|
24
24
|
import"./cli-wqy8hnnh.js";
|
|
25
25
|
import {
|
|
26
26
|
EXIT_CODES
|
|
@@ -55,7 +55,8 @@ function stdinLooksPiped() {
|
|
|
55
55
|
}
|
|
56
56
|
function emitResult(result, json) {
|
|
57
57
|
if (json) {
|
|
58
|
-
|
|
58
|
+
process.stdout.write(JSON.stringify(result, null, 2) + `
|
|
59
|
+
`);
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
for (const issue of result.issues) {
|
|
@@ -173,7 +174,8 @@ var publish_default = defineCommand({
|
|
|
173
174
|
};
|
|
174
175
|
}));
|
|
175
176
|
if (json) {
|
|
176
|
-
|
|
177
|
+
process.stdout.write(JSON.stringify(rows, null, 2) + `
|
|
178
|
+
`);
|
|
177
179
|
} else {
|
|
178
180
|
for (const r of rows) {
|
|
179
181
|
const status = r.kind === "guided" ? "no key needed" : r.connected ? "connected" : "not connected";
|
package/dist/render.js
CHANGED
|
@@ -457,16 +457,23 @@ function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
|
|
|
457
457
|
}
|
|
458
458
|
return { name, baseKind: name, ...resolveContainerShape(name, pluginName, rawDecl) };
|
|
459
459
|
}
|
|
460
|
+
var CORE_MARKER_OWNER = "Gutterpress";
|
|
461
|
+
var CORE_DECLARED_MARKERS = {
|
|
462
|
+
columns: {
|
|
463
|
+
class: "gp-columns",
|
|
464
|
+
variants: { 1: "gp-columns-1", 2: "gp-columns-2", 3: "gp-columns-3", 4: "gp-columns-4", 5: "gp-columns-5" }
|
|
465
|
+
}
|
|
466
|
+
};
|
|
460
467
|
function buildDeclaredMarkerRegistry(sources) {
|
|
461
468
|
const rawRegistry = new Map;
|
|
462
469
|
const originOf = new Map;
|
|
463
|
-
for (const { pluginName, markers } of sources) {
|
|
470
|
+
for (const { pluginName, markers } of [{ pluginName: CORE_MARKER_OWNER, markers: CORE_DECLARED_MARKERS }, ...sources]) {
|
|
464
471
|
if (!markers)
|
|
465
472
|
continue;
|
|
466
473
|
for (const [name, rawDecl] of Object.entries(markers)) {
|
|
467
474
|
validateDeclaredMarkerName(name, pluginName);
|
|
468
|
-
if (KNOWN_KINDS.includes(name)) {
|
|
469
|
-
throw new Error(`Plugin "${pluginName}" declares marker "@${name}", which is a core Gutterpress marker name ` + `(${KNOWN_KINDS.map((k) => `@${k}`).join(", ")}). Core marker names cannot be shadowed — ` + `rename the plugin's marker.`);
|
|
475
|
+
if (KNOWN_KINDS.includes(name) || pluginName !== CORE_MARKER_OWNER && name in CORE_DECLARED_MARKERS) {
|
|
476
|
+
throw new Error(`Plugin "${pluginName}" declares marker "@${name}", which is a core Gutterpress marker name ` + `(${[...KNOWN_KINDS, ...Object.keys(CORE_DECLARED_MARKERS)].map((k) => `@${k}`).join(", ")}). ` + `Core marker names cannot be shadowed — ` + `rename the plugin's marker.`);
|
|
470
477
|
}
|
|
471
478
|
if (rawRegistry.has(name)) {
|
|
472
479
|
throw new Error(`Marker "@${name}" is declared by both plugin "${originOf.get(name)}" and plugin "${pluginName}". ` + `Declared marker names must be unique across every loaded plugin — rename one of them.`);
|
|
@@ -531,6 +538,9 @@ function scanForUnknownDeclaredMarkers(state, declaredMarkers) {
|
|
|
531
538
|
}
|
|
532
539
|
}
|
|
533
540
|
}
|
|
541
|
+
function variantClassFor(decl, word) {
|
|
542
|
+
return word && decl.variants && Object.hasOwn(decl.variants, word) ? decl.variants[word] : "";
|
|
543
|
+
}
|
|
534
544
|
function warnUnknownVariant(env, meta, decl) {
|
|
535
545
|
const word = meta.name;
|
|
536
546
|
if (!word)
|
|
@@ -546,7 +556,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
546
556
|
preferPagesInSpreads: false,
|
|
547
557
|
...pluginOptions
|
|
548
558
|
};
|
|
549
|
-
const declaredMarkers = options.declaredMarkers instanceof Map && options.declaredMarkers.size > 0 ? options.declaredMarkers :
|
|
559
|
+
const declaredMarkers = options.declaredMarkers instanceof Map && options.declaredMarkers.size > 0 ? options.declaredMarkers : buildDeclaredMarkerRegistry([]);
|
|
550
560
|
const declaredWords = declaredKindWords(declaredMarkers);
|
|
551
561
|
function markerBlock(state, startLine, endLine, silent) {
|
|
552
562
|
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
@@ -659,7 +669,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
659
669
|
attrs: meta.attrs || {},
|
|
660
670
|
labelled: false
|
|
661
671
|
};
|
|
662
|
-
const variantClass =
|
|
672
|
+
const variantClass = variantClassFor(decl, variant);
|
|
663
673
|
const baseClass = [decl.classBase, variantClass].filter(Boolean).join(" ");
|
|
664
674
|
addClasses(t, baseClass, meta.attrs && meta.attrs.class ? meta.attrs.class : "");
|
|
665
675
|
attachDataAttrs(t, decl.baseKind, variant, meta.attrs || {});
|
|
@@ -770,7 +780,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
770
780
|
const attrs = meta.attrs || {};
|
|
771
781
|
const variant = meta.name || decl.presetVariant || null;
|
|
772
782
|
warnUnknownVariant(state.env, meta, decl);
|
|
773
|
-
const variantClass =
|
|
783
|
+
const variantClass = variantClassFor(decl, variant);
|
|
774
784
|
const cls = [decl.classBase, variantClass, attrs.class].filter(Boolean).join(" ");
|
|
775
785
|
return {
|
|
776
786
|
kind: "section",
|
|
@@ -1105,9 +1115,19 @@ var GUTTERPRESS_CSS = `
|
|
|
1105
1115
|
NOT set, because the correct value depends on whether the run fragments
|
|
1106
1116
|
across pages (auto packs each page's columns; the CSS initial balance is
|
|
1107
1117
|
right for a run that fits on one page) and only the author knows which.
|
|
1108
|
-
--gp-column-gap is author-settable.
|
|
1109
|
-
.gp-columns
|
|
1110
|
-
|
|
1118
|
+
--gp-column-gap is author-settable.
|
|
1119
|
+
.gp-columns is what the core @columns marker emits (markers.js); on its
|
|
1120
|
+
own it is two columns, and @columns 1 to 5 add the matching count, which
|
|
1121
|
+
wins because it comes later at the same specificity. */
|
|
1122
|
+
.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5 {
|
|
1123
|
+
column-gap: var(--gp-column-gap, 1.5em);
|
|
1124
|
+
}
|
|
1125
|
+
.gp-columns { columns: 2; }
|
|
1126
|
+
.gp-columns-1 { columns: 1; }
|
|
1127
|
+
.gp-columns-2 { columns: 2; }
|
|
1128
|
+
.gp-columns-3 { columns: 3; }
|
|
1129
|
+
.gp-columns-4 { columns: 4; }
|
|
1130
|
+
.gp-columns-5 { columns: 5; }
|
|
1111
1131
|
|
|
1112
1132
|
/* the per-shape decisions the paragraph above deliberately leaves to the
|
|
1113
1133
|
author, named instead of left as raw CSS every book was reinventing
|
|
@@ -1308,8 +1328,12 @@ var GP_CLASSES = new Set([
|
|
|
1308
1328
|
"gp-large",
|
|
1309
1329
|
"gp-tight",
|
|
1310
1330
|
"gp-loose",
|
|
1331
|
+
"gp-columns",
|
|
1332
|
+
"gp-columns-1",
|
|
1311
1333
|
"gp-columns-2",
|
|
1312
1334
|
"gp-columns-3",
|
|
1335
|
+
"gp-columns-4",
|
|
1336
|
+
"gp-columns-5",
|
|
1313
1337
|
"gp-columns-all",
|
|
1314
1338
|
"gp-columns-flow",
|
|
1315
1339
|
"gp-columns-balanced",
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-ev175hx9.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-0prrt9e4.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-182j48xc.js";
|
|
12
12
|
import"./cli-wqy8hnnh.js";
|
|
13
13
|
import {
|
|
14
|
+
BuildError,
|
|
14
15
|
EXIT_CODES
|
|
15
16
|
} from "./cli-46ycxe6r.js";
|
|
16
17
|
import"./cli-kkzka31p.js";
|
|
@@ -96,7 +97,7 @@ var validate_default = defineCommand({
|
|
|
96
97
|
}, format);
|
|
97
98
|
} catch (error) {
|
|
98
99
|
log.error(error instanceof Error ? error.message : String(error));
|
|
99
|
-
process.exit(error instanceof UsageError ? error.exitCode : EXIT_CODES.USAGE);
|
|
100
|
+
process.exit(error instanceof UsageError || error instanceof BuildError ? error.exitCode : EXIT_CODES.USAGE);
|
|
100
101
|
}
|
|
101
102
|
if (!result.ok) {
|
|
102
103
|
process.exit(EXIT_CODES.FINDINGS);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gutterpress",
|
|
3
|
-
"version": "0.11.16
|
|
3
|
+
"version": "0.11.16",
|
|
4
4
|
"description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
5
5
|
"author": "itlackey",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"fflate": "0.8.3",
|
|
67
67
|
"glob": "^13.0.0",
|
|
68
68
|
"htmlhint": "^1.9.2",
|
|
69
|
+
"ignore": "^5.3.2",
|
|
69
70
|
"isomorphic-git": "^1.38.4",
|
|
70
71
|
"markdown-it": "^14.3.2",
|
|
71
72
|
"markdown-it-abbr": "^2.0.0",
|