@open-press/cli 1.0.0 → 1.1.1

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 (175) hide show
  1. package/README.md +11 -12
  2. package/dist/cli.js +298 -79
  3. package/package.json +9 -7
  4. package/template/core/AGENTS.md +0 -130
  5. package/template/core/CHANGELOG.md +0 -218
  6. package/template/core/README.md +0 -43
  7. package/template/core/engine/cli.mjs +0 -96
  8. package/template/core/engine/commands/_shared.mjs +0 -199
  9. package/template/core/engine/commands/deploy.mjs +0 -31
  10. package/template/core/engine/commands/dev.mjs +0 -49
  11. package/template/core/engine/commands/doctor.mjs +0 -229
  12. package/template/core/engine/commands/export.mjs +0 -8
  13. package/template/core/engine/commands/image.mjs +0 -29
  14. package/template/core/engine/commands/inspect.mjs +0 -35
  15. package/template/core/engine/commands/pdf.mjs +0 -26
  16. package/template/core/engine/commands/preview.mjs +0 -26
  17. package/template/core/engine/commands/render.mjs +0 -17
  18. package/template/core/engine/commands/replace.mjs +0 -41
  19. package/template/core/engine/commands/search.mjs +0 -33
  20. package/template/core/engine/commands/skills-sync.mjs +0 -71
  21. package/template/core/engine/commands/typecheck.mjs +0 -67
  22. package/template/core/engine/commands/upgrade.mjs +0 -159
  23. package/template/core/engine/commands/validate.mjs +0 -17
  24. package/template/core/engine/document-export.mjs +0 -15
  25. package/template/core/engine/output/chrome-pdf.d.mts +0 -34
  26. package/template/core/engine/output/chrome-pdf.mjs +0 -450
  27. package/template/core/engine/output/deploy-sync.mjs +0 -15
  28. package/template/core/engine/output/fonts.mjs +0 -62
  29. package/template/core/engine/output/katex-assets.mjs +0 -45
  30. package/template/core/engine/output/page-block.mjs +0 -30
  31. package/template/core/engine/output/pdf-media.mjs +0 -45
  32. package/template/core/engine/output/public-assets.mjs +0 -19
  33. package/template/core/engine/output/static-server.mjs +0 -571
  34. package/template/core/engine/react/caption-numbering.mjs +0 -73
  35. package/template/core/engine/react/comment-endpoint.d.mts +0 -11
  36. package/template/core/engine/react/comment-endpoint.mjs +0 -102
  37. package/template/core/engine/react/comment-marker.mjs +0 -374
  38. package/template/core/engine/react/document-entry.mjs +0 -331
  39. package/template/core/engine/react/document-export.mjs +0 -512
  40. package/template/core/engine/react/http-json.mjs +0 -24
  41. package/template/core/engine/react/mdx-compile.mjs +0 -629
  42. package/template/core/engine/react/measurement-css.mjs +0 -157
  43. package/template/core/engine/react/object-entities.mjs +0 -204
  44. package/template/core/engine/react/pagination/allocator.mjs +0 -167
  45. package/template/core/engine/react/pagination/regions.mjs +0 -81
  46. package/template/core/engine/react/pagination-constants.mjs +0 -3
  47. package/template/core/engine/react/pagination.mjs +0 -9
  48. package/template/core/engine/react/pipeline/allocate.mjs +0 -217
  49. package/template/core/engine/react/pipeline/final-render.mjs +0 -94
  50. package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
  51. package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
  52. package/template/core/engine/react/press-tree-inspection.mjs +0 -172
  53. package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
  54. package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
  55. package/template/core/engine/react/section-css.mjs +0 -56
  56. package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
  57. package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
  58. package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
  59. package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
  60. package/template/core/engine/react/style-discovery.mjs +0 -160
  61. package/template/core/engine/runtime/config.d.mts +0 -48
  62. package/template/core/engine/runtime/config.mjs +0 -172
  63. package/template/core/engine/runtime/file-utils.mjs +0 -114
  64. package/template/core/engine/runtime/file-walk.mjs +0 -22
  65. package/template/core/engine/runtime/inspection.mjs +0 -328
  66. package/template/core/engine/runtime/issue-report.mjs +0 -44
  67. package/template/core/engine/runtime/page-geometry.mjs +0 -131
  68. package/template/core/engine/runtime/path-utils.mjs +0 -20
  69. package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
  70. package/template/core/engine/runtime/source-text-tools.mjs +0 -832
  71. package/template/core/engine/runtime/source-workspace.mjs +0 -168
  72. package/template/core/engine/runtime/validation.mjs +0 -183
  73. package/template/core/index.html +0 -13
  74. package/template/core/openpress.config.mjs +0 -8
  75. package/template/core/package.json +0 -89
  76. package/template/core/src/main.tsx +0 -16
  77. package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
  78. package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
  79. package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
  80. package/template/core/src/openpress/app/index.ts +0 -2
  81. package/template/core/src/openpress/core/Frame.tsx +0 -91
  82. package/template/core/src/openpress/core/FrameContext.tsx +0 -26
  83. package/template/core/src/openpress/core/MdxArea.tsx +0 -34
  84. package/template/core/src/openpress/core/Press.tsx +0 -55
  85. package/template/core/src/openpress/core/Workspace.tsx +0 -36
  86. package/template/core/src/openpress/core/cn.ts +0 -4
  87. package/template/core/src/openpress/core/index.tsx +0 -47
  88. package/template/core/src/openpress/core/primitives.tsx +0 -91
  89. package/template/core/src/openpress/core/types.ts +0 -236
  90. package/template/core/src/openpress/core/useSource.ts +0 -28
  91. package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
  92. package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
  93. package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
  94. package/template/core/src/openpress/document-model/index.ts +0 -7
  95. package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
  96. package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
  97. package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
  98. package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
  99. package/template/core/src/openpress/manuscript/index.tsx +0 -238
  100. package/template/core/src/openpress/mdx/index.ts +0 -96
  101. package/template/core/src/openpress/numbering/index.ts +0 -294
  102. package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
  103. package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
  104. package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
  105. package/template/core/src/openpress/reader/index.ts +0 -11
  106. package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
  107. package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
  108. package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
  109. package/template/core/src/openpress/reader/readerScroll.ts +0 -92
  110. package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
  111. package/template/core/src/openpress/reader/readerTypes.ts +0 -4
  112. package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
  113. package/template/core/src/openpress/reader/usePanelState.ts +0 -56
  114. package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
  115. package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
  116. package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
  117. package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
  118. package/template/core/src/openpress/shared/Panel.tsx +0 -77
  119. package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
  120. package/template/core/src/openpress/shared/index.ts +0 -4
  121. package/template/core/src/openpress/shared/numberUtils.ts +0 -3
  122. package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
  123. package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
  124. package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
  125. package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
  126. package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
  127. package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
  128. package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
  129. package/template/core/src/openpress/workbench/actions/index.ts +0 -6
  130. package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
  131. package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
  132. package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
  133. package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
  134. package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
  135. package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
  136. package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
  137. package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
  138. package/template/core/src/openpress/workbench/document/index.ts +0 -10
  139. package/template/core/src/openpress/workbench/index.ts +0 -2
  140. package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
  141. package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
  142. package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
  143. package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
  144. package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
  145. package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
  146. package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
  147. package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
  148. package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
  149. package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
  150. package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
  151. package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
  152. package/template/core/src/openpress/workbench/panels/index.ts +0 -3
  153. package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
  154. package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
  155. package/template/core/src/openpress/workbench/project/index.ts +0 -2
  156. package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
  157. package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
  158. package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
  159. package/template/core/src/openpress/workbench/shell/index.ts +0 -1
  160. package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
  161. package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
  162. package/template/core/src/styles/openpress/app-shell.css +0 -251
  163. package/template/core/src/styles/openpress/media-workspace.css +0 -230
  164. package/template/core/src/styles/openpress/print-route.css +0 -184
  165. package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
  166. package/template/core/src/styles/openpress/public-viewer.css +0 -688
  167. package/template/core/src/styles/openpress/reader-runtime.css +0 -989
  168. package/template/core/src/styles/openpress/responsive.css +0 -245
  169. package/template/core/src/styles/openpress/workbench-panels.css +0 -707
  170. package/template/core/src/styles/openpress/workbench.css +0 -1255
  171. package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
  172. package/template/core/src/styles/openpress.css +0 -15
  173. package/template/core/src/vite-env.d.ts +0 -9
  174. package/template/core/tsconfig.json +0 -40
  175. package/template/core/vite.config.ts +0 -584
@@ -1,512 +0,0 @@
1
- // Layer 6 orchestrator.
2
- //
3
- // Wires Layer 1 (entry load) -> source resolution -> Layer 2/3/4 iteration
4
- // -> Layer 5 final render -> document.json + asset sync.
5
-
6
- import fs from "node:fs/promises";
7
- import path from "node:path";
8
- import { pathToFileURL } from "node:url";
9
- import React from "react";
10
- import { documentRelativePath, pageToBlock } from "../output/page-block.mjs";
11
- import { syncPublicAssets } from "../output/public-assets.mjs";
12
- import { pageGeometryToTheme } from "../runtime/page-geometry.mjs";
13
- import { normalizePageGeometry } from "../runtime/page-geometry.mjs";
14
- import { createCaptionNumberingState, numberCaptionsInHtml } from "./caption-numbering.mjs";
15
- import { buildSectionScopedCss } from "./section-css.mjs";
16
- import { CORE_ENTRY, createReactSsrServer, loadReactDocumentEntry } from "./document-entry.mjs";
17
- import { buildReactMeasurementCss } from "./measurement-css.mjs";
18
- import { buildObjectEntities } from "./object-entities.mjs";
19
- import { allocateChains } from "./pipeline/allocate.mjs";
20
- import { measureFrames } from "./pipeline/frame-measurement.mjs";
21
- import { renderFinalPress } from "./pipeline/final-render.mjs";
22
- import { expandPressTree } from "./pipeline/press-tree.mjs";
23
- import { resolveAllSources } from "./sources/mdx-resolver.mjs";
24
- import { discoverSectionStyles } from "./style-discovery.mjs";
25
-
26
- const MAX_ITERATIONS = 20;
27
-
28
- export async function exportReactDocument(root = ".", { syncAssets = true } = {}) {
29
- const workspaceRoot = path.resolve(root);
30
- // Quick existence check without opening an SSR server.
31
- const fastCheck = await loadReactDocumentEntry(workspaceRoot);
32
- if (!fastCheck) return null;
33
-
34
- const server = await createReactSsrServer(workspaceRoot);
35
- try {
36
- // Reload the entry through THIS server so the module identity matches
37
- // what the rest of the pipeline (PressContext, hooks) sees.
38
- const entry = await loadReactDocumentEntry(workspaceRoot, { server });
39
- if (!entry) return null;
40
- if (!entry.Press) {
41
- throw new Error(
42
- `OpenPress document entry ${entry.entryPath} must default-export a Press component (function) to export. ` +
43
- `Legacy named exports (cover/toc/backCover) are not supported in v0.6 — see the Press Tree spec.`,
44
- );
45
- }
46
- // Resolve PressContext + Frame markers from the engine's loaded core module.
47
- // Use the absolute file path so the user's `import "@open-press/core"`
48
- // (resolved via vite alias) and our load hit the same module cache entry.
49
- const coreModule = await server.ssrLoadModule(CORE_ENTRY);
50
- const PressContext = coreModule.PressContext;
51
- if (!PressContext) {
52
- throw new Error("Engine could not resolve PressContext from @open-press/core.");
53
- }
54
-
55
- // Discover workspace for component scope and chapter-scoped style files.
56
- // Pass every Press's resolved section-folders root so per-Press chapter
57
- // folders (e.g. press/userstory/chapters/) are all picked up — the
58
- // workspace can host more than one chapter root.
59
- const sectionRoots = collectSectionRoots(entry.presses, entry.config.paths.documentRoot);
60
- const workspace = await discoverSectionStyles(workspaceRoot, entry.config, { sectionRoots });
61
- const coreAuthorComponents = {};
62
- for (const name of ["MediaFigure", "ImageFigure"]) {
63
- if (typeof coreModule[name] === "function") coreAuthorComponents[name] = coreModule[name];
64
- }
65
- const globalComponents = {
66
- ...coreAuthorComponents,
67
- ...(await loadComponentModules(server, workspace.globalComponents ?? [])),
68
- };
69
-
70
- // Build measurement CSS once at the workspace level — shared by every
71
- // Press inside the Workspace.
72
- const measurementCss = await buildReactMeasurementCss(workspaceRoot, entry.config, workspace);
73
-
74
- // Write chapter-scoped CSS once (workspace shared). Every per-press
75
- // readerDocument references the same file via "/openpress/chapter-scoped.css".
76
- const chapterCss = await buildSectionScopedCss(workspace);
77
- const sharedStyles = [];
78
- await fs.mkdir(entry.config.paths.publicDir, { recursive: true });
79
- if (chapterCss.trim()) {
80
- await fs.writeFile(path.join(entry.config.paths.publicDir, "chapter-scoped.css"), chapterCss, "utf8");
81
- sharedStyles.push({
82
- kind: "chapter-scoped-css",
83
- href: "/openpress/chapter-scoped.css",
84
- path: "chapter-scoped.css",
85
- });
86
- }
87
-
88
- // Iterate every Press declared inside <Workspace>. Single-doc
89
- // workspaces just have length-1 here; the code path is uniform.
90
- const pressResults = [];
91
- for (const press of entry.presses) {
92
- const result = await exportSinglePress({
93
- press,
94
- entry,
95
- workspaceRoot,
96
- server,
97
- coreModule,
98
- PressContext,
99
- workspace,
100
- globalComponents,
101
- measurementCss,
102
- sharedStyles,
103
- });
104
- pressResults.push(result);
105
- }
106
-
107
- // Build workspace.json — one entry per Press. The reader fetches
108
- // this first to decide between gallery (length > 1) and direct
109
- // load (length 1).
110
- const workspaceManifest = {
111
- version: 1,
112
- name: typeof entry.workspaceProps?.name === "string" && entry.workspaceProps.name.trim()
113
- ? entry.workspaceProps.name.trim()
114
- : null,
115
- presses: pressResults.map((r) => ({
116
- slug: r.slug,
117
- title: r.readerDocument.meta.title,
118
- page: r.readerDocument.theme ?? null,
119
- pageCount: r.pageCount,
120
- documentUrl: r.documentUrl,
121
- })),
122
- };
123
- const workspacePath = path.join(entry.config.paths.publicDir, "workspace.json");
124
- await fs.writeFile(workspacePath, JSON.stringify(workspaceManifest, null, 2), "utf8");
125
-
126
- if (syncAssets) {
127
- await syncPublicAssets(workspaceRoot, entry.config.paths.publicDir, entry.config);
128
- }
129
-
130
- const primary = pressResults[0];
131
- return {
132
- documentPath: primary?.documentPath,
133
- pageCount: primary?.pageCount ?? 0,
134
- document: primary?.readerDocument,
135
- presses: pressResults,
136
- };
137
- } finally {
138
- await server.close();
139
- }
140
- }
141
-
142
- // Render one Press from the Workspace into its own document.json.
143
- // Called once per <Press> child; single-doc workspaces just call this
144
- // once with the only Press. Returns the per-press summary the
145
- // workspace manifest is built from.
146
- async function exportSinglePress({
147
- press,
148
- entry,
149
- workspaceRoot,
150
- server,
151
- coreModule,
152
- PressContext,
153
- workspace,
154
- globalComponents,
155
- measurementCss,
156
- sharedStyles,
157
- }) {
158
- const slug = typeof press.metadata?.slug === "string" && press.metadata.slug.trim()
159
- ? press.metadata.slug.trim()
160
- : "";
161
-
162
- // Effective config for this press: workspace config with per-press
163
- // metadata overlaid. Press JSX page prop wins over the workspace page.
164
- const effectiveConfig = applyPressOverridesToConfig(entry.config, press.metadata);
165
- const documentRoot = effectiveConfig.paths.documentRoot;
166
-
167
- // Resolve sources for this press. The 1.0 contract reads them from
168
- // <Press sources={[...]}>; the v0.x legacy path uses the synthesized
169
- // record from `export const sources`.
170
- const sourcesRecord = press.sources ?? {};
171
- const { resolved: sources, renderData: renderRegistry } = await resolveAllSources({
172
- sources: sourcesRecord,
173
- documentRoot,
174
- globalComponents,
175
- });
176
-
177
- // Component the render pipeline drives. For Press elements captured
178
- // by inspection (1.0 contract), wrap the captured element in a thin
179
- // function component. For legacy projects without inspection data,
180
- // fall back to the user's whole default export.
181
- const PressComponent = press.element
182
- ? () => press.element
183
- : entry.Press;
184
-
185
- // Iterative allocation loop (identical to v0.x — paginates until the
186
- // hints stabilise).
187
- let hints = null;
188
- let allocation = null;
189
- let lastFrames = null;
190
- let warnings = [];
191
- for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
192
- const { html, frames } = expandPressTree({
193
- Press: PressComponent,
194
- PressContext,
195
- sources,
196
- hints,
197
- });
198
- lastFrames = frames;
199
- validateAllChainsKnown(frames, sources);
200
- const measurement = await measureFrames({
201
- pressHtml: html,
202
- sources,
203
- renderRegistry,
204
- css: measurementCss,
205
- baseHref: pathToFileURL(`${documentRoot}${path.sep}`).href,
206
- mediaDir: path.join(documentRoot, "media"),
207
- captionNumbering: effectiveConfig.captionNumbering,
208
- });
209
- const alloc = allocateChains({
210
- frames,
211
- mdxAreas: measurement.mdxAreas,
212
- blockHeights: measurement.blockHeights,
213
- sources,
214
- });
215
- if (process.env.OPENPRESS_DEBUG_ALLOC) {
216
- const sample = measurement.mdxAreas
217
- .slice(0, 5)
218
- .map((a) => `${a.frameKey}#${a.indexInFrame} cap=${a.capacity.toFixed(0)} raw=${(a.rawHeight ?? 0).toFixed(0)}`);
219
- const blocks = measurement.blockHeights
220
- .slice(0, 8)
221
- .map((b) => `${b.id} h=${b.height.toFixed(0)}`);
222
- process.stderr.write(`[allocator press=${slug || "(root)"} iter ${iteration}]\n`);
223
- process.stderr.write(` mdxAreas[0..4]: ${sample.join(" | ")}\n`);
224
- process.stderr.write(` blocks[0..7]: ${blocks.join(" | ")}\n`);
225
- process.stderr.write(` hints: ${JSON.stringify(alloc.hints.totalPagesPerChain)}\n`);
226
- if (alloc.warnings.length > 0) {
227
- process.stderr.write(` warnings: ${JSON.stringify(alloc.warnings)}\n`);
228
- }
229
- }
230
- if (hintsEqual(hints, alloc.hints)) {
231
- allocation = alloc.allocation;
232
- warnings = alloc.warnings;
233
- break;
234
- }
235
- hints = alloc.hints;
236
- }
237
- if (allocation == null) {
238
- throw new Error(
239
- `Allocation did not converge after ${MAX_ITERATIONS} iterations (press="${slug || "(root)"}"). ` +
240
- `This usually means a chain keeps growing without fitting; check MdxArea capacities and block heights.`,
241
- );
242
- }
243
-
244
- const toc = buildTocContext({ sources, frames: lastFrames ?? [], allocation });
245
-
246
- const final = await renderFinalPress({
247
- Press: PressComponent,
248
- PressContext,
249
- sources,
250
- hints,
251
- toc,
252
- allocation,
253
- renderRegistry,
254
- });
255
-
256
- // Build the reader's document.json. Same shape as v0.x; the only
257
- // change is metadata.title comes from the per-press Press JSX prop.
258
- const blockMap = {};
259
- const captionState = createCaptionNumberingState();
260
- const blocks = final.frames.map((frame, index) => {
261
- const source = {
262
- file: "index.tsx",
263
- path: slug ? `press/${slug}/index.tsx` : "press/index.tsx",
264
- kind: frame.role ?? "manuscript.content",
265
- slug: frame.frameKey,
266
- sectionIndex: index + 1,
267
- };
268
- const html = numberCaptionsInHtml(frame.html, effectiveConfig.captionNumbering, captionState);
269
- for (const id of collectFrameBlockIds(frame.blockIds, html)) {
270
- blockMap[id] = { id, pageIndex: index, pageNumber: index + 1, frameKey: frame.frameKey };
271
- }
272
- const block = pageToBlock(index, html, source, effectiveConfig, {
273
- idPrefix: "openpress-page",
274
- anchorPrefix: "page",
275
- titleFallback: "Page",
276
- });
277
- return {
278
- ...block,
279
- frameKey: frame.frameKey,
280
- role: frame.role ?? null,
281
- chrome: frame.chrome ?? true,
282
- blockIds: frame.blockIds,
283
- };
284
- });
285
-
286
- const sourceBlockIndex = buildSourceBlockIndex(sources);
287
- for (const id of Object.keys(blockMap)) {
288
- const sourceRecord = sourceBlockIndex.get(id);
289
- if (sourceRecord) {
290
- blockMap[id] = {
291
- ...blockMap[id],
292
- kind: sourceRecord.kind,
293
- name: sourceRecord.name,
294
- path: sourceRecord.path,
295
- source: sourceRecord.source,
296
- chainId: sourceRecord.chainId,
297
- sectionSlug: sourceRecord.sectionSlug,
298
- };
299
- }
300
- }
301
-
302
- const objectEntities = buildObjectEntities({
303
- frames: final.frames.map((frame, index) => ({ ...frame, pageIndex: index })),
304
- blocks,
305
- blockMap,
306
- });
307
-
308
- const readerDocument = {
309
- meta: {
310
- title: trimmedString(effectiveConfig.title) ?? "Untitled Document",
311
- subtitle: trimmedString(effectiveConfig.subtitle) ?? "",
312
- organization: trimmedString(effectiveConfig.organization) ?? "",
313
- workspaceLabel: trimmedString(effectiveConfig.workspaceLabel) ?? "",
314
- version: "openpress-press-tree-v1",
315
- },
316
- theme: pageGeometryToTheme(effectiveConfig.page),
317
- source: {
318
- type: "openpress-press-tree-mdx",
319
- contentDir: documentRelativePath(effectiveConfig, effectiveConfig.sourceDir),
320
- editable: true,
321
- editMode: "source-mdx",
322
- styles: sharedStyles,
323
- blockMap,
324
- objectEntities,
325
- frames: final.frames.map((frame, index) => ({
326
- frameKey: frame.frameKey,
327
- role: frame.role ?? null,
328
- pageIndex: index,
329
- mdxAreas: frame.mdxAreas.map((area) => ({
330
- chainId: area.chainId,
331
- indexInFrame: area.indexInFrame,
332
- blockIds: area.blockIds,
333
- })),
334
- })),
335
- chains: Object.keys(sources).flatMap((id) => Object.keys(sources[id].chains)),
336
- warnings,
337
- },
338
- blocks,
339
- };
340
-
341
- // Output path: empty slug → root /openpress/document.json (legacy
342
- // single-Press shape). Non-empty slug → /openpress/<slug>/document.json.
343
- const pressOutputDir = slug
344
- ? path.join(effectiveConfig.paths.publicDir, slug)
345
- : effectiveConfig.paths.publicDir;
346
- await fs.mkdir(pressOutputDir, { recursive: true });
347
- const documentPath = path.join(pressOutputDir, "document.json");
348
- await fs.writeFile(documentPath, JSON.stringify(readerDocument, null, 2), "utf8");
349
-
350
- return {
351
- slug,
352
- documentPath,
353
- documentUrl: slug ? `/openpress/${slug}/document.json` : "/openpress/document.json",
354
- readerDocument,
355
- pageCount: blocks.length,
356
- };
357
- }
358
-
359
- // Apply per-Press JSX prop overrides onto the workspace-level config.
360
- // Returns a new config object — the original is untouched so other
361
- // presses in the same workspace get a clean base.
362
- function applyPressOverridesToConfig(workspaceConfig, pressMetadata) {
363
- if (!pressMetadata) return workspaceConfig;
364
- const out = { ...workspaceConfig };
365
- if (pressMetadata.title) out.title = pressMetadata.title;
366
- if (pressMetadata.page !== undefined) {
367
- out.page = normalizePageGeometry(pressMetadata.page);
368
- }
369
- if (pressMetadata.captionNumbering !== undefined) {
370
- out.captionNumbering = { ...workspaceConfig.captionNumbering, ...pressMetadata.captionNumbering };
371
- }
372
- return out;
373
- }
374
-
375
- async function loadComponentModules(server, components) {
376
- const out = {};
377
- for (const component of components) {
378
- const mod = await server.ssrLoadModule(component.absolutePath);
379
- if (typeof mod.default !== "function") {
380
- throw new Error(
381
- `OpenPress component module ${component.documentPath} must default-export a React component.`,
382
- );
383
- }
384
- out[component.name] = mod.default;
385
- }
386
- return out;
387
- }
388
-
389
- function validateAllChainsKnown(frames, sources) {
390
- const known = new Set();
391
- for (const source of Object.values(sources)) {
392
- for (const chainId of Object.keys(source.chains)) known.add(chainId);
393
- }
394
- for (const frame of frames) {
395
- for (const area of frame.mdxAreas) {
396
- if (!known.has(area.chainId)) {
397
- const list = [...known].sort().slice(0, 10).join(", ");
398
- throw new Error(
399
- `Unknown chainId "${area.chainId}" referenced by frame "${frame.frameKey}". ` +
400
- `Known chains: ${list || "(none)"}${known.size > 10 ? ", ..." : ""}.`,
401
- );
402
- }
403
- }
404
- }
405
- }
406
-
407
- function hintsEqual(a, b) {
408
- if (a === b) return true;
409
- if (!a || !b) return false;
410
- const aMap = a.totalPagesPerChain ?? {};
411
- const bMap = b.totalPagesPerChain ?? {};
412
- const keys = new Set([...Object.keys(aMap), ...Object.keys(bMap)]);
413
- for (const key of keys) {
414
- if (aMap[key] !== bMap[key]) return false;
415
- }
416
- return true;
417
- }
418
-
419
- function buildSourceBlockIndex(sources) {
420
- const index = new Map();
421
- for (const source of Object.values(sources)) {
422
- for (const [chainId, blocks] of Object.entries(source.chains)) {
423
- for (const block of blocks) {
424
- index.set(block.id, { ...block, chainId });
425
- }
426
- }
427
- }
428
- return index;
429
- }
430
-
431
- function collectFrameBlockIds(allocatedIds, html) {
432
- const ids = new Set(allocatedIds ?? []);
433
- const pattern = /\sdata-openpress-block-id="([^"]+)"/g;
434
- let match;
435
- while ((match = pattern.exec(String(html ?? "")))) {
436
- if (match[1]) ids.add(match[1]);
437
- }
438
- return ids;
439
- }
440
-
441
- function buildTocContext({ sources, frames, allocation }) {
442
- const toc = {};
443
- for (const source of Object.values(sources)) {
444
- for (const [tocChainId, tocBlocks] of Object.entries(source.chains).filter(([chainId]) => chainId.startsWith(`toc:${source.id}`))) {
445
- if (tocBlocks.length === 0) continue;
446
- toc[tocChainId] = tocBlocks.map((block) => ({
447
- id: `${source.id}:${block.sectionSlug}`,
448
- blockId: block.id,
449
- sourceId: source.id,
450
- sectionSlug: block.sectionSlug,
451
- title: block.title,
452
- href: block.href,
453
- level: block.level,
454
- label: block.label,
455
- pageNumber: firstAllocatedPageNumberForBlock(frames, allocation, block.targetBlockId)
456
- ?? firstAllocatedPageNumber(frames, allocation, `${source.id}:${block.sectionSlug}`),
457
- }));
458
- }
459
- }
460
- return toc;
461
- }
462
-
463
- function firstAllocatedPageNumberForBlock(frames, allocation, blockId) {
464
- if (!blockId) return undefined;
465
- for (let index = 0; index < frames.length; index += 1) {
466
- const frameAllocation = allocation?.[frames[index].frameKey] ?? {};
467
- for (const areaArr of Object.values(frameAllocation)) {
468
- if (areaArr?.some((area) => Array.isArray(area) && area.includes(blockId))) return index + 1;
469
- }
470
- }
471
- return undefined;
472
- }
473
-
474
- function firstAllocatedPageNumber(frames, allocation, chainId) {
475
- for (let index = 0; index < frames.length; index += 1) {
476
- const frame = frames[index];
477
- const allocated = allocation?.[frame.frameKey]?.[chainId];
478
- if (allocated?.some((area) => Array.isArray(area) && area.length > 0)) return index + 1;
479
- }
480
- for (let index = 0; index < frames.length; index += 1) {
481
- if (frames[index].mdxAreas.some((area) => area.chainId === chainId)) return index + 1;
482
- }
483
- return undefined;
484
- }
485
-
486
- function trimmedString(value) {
487
- if (typeof value !== "string") return null;
488
- const trimmed = value.trim();
489
- return trimmed ? trimmed : null;
490
- }
491
-
492
- // Walk every Press's mdxSource descriptors and collect the absolute
493
- // path each section-folders root resolves to. discoverSectionStyles
494
- // iterates these to find section-scoped CSS across a multi-Press
495
- // workspace where chapters live under per-Press subfolders.
496
- function collectSectionRoots(presses, documentRoot) {
497
- const roots = new Set();
498
- for (const press of presses ?? []) {
499
- const sources = press?.sources;
500
- if (!sources || typeof sources !== "object") continue;
501
- for (const descriptor of Object.values(sources)) {
502
- if (descriptor?.type !== "mdx") continue;
503
- if (descriptor?.preset !== "section-folders") continue;
504
- const rel = typeof descriptor.root === "string" && descriptor.root.trim()
505
- ? descriptor.root.trim()
506
- : "chapters";
507
- roots.add(path.resolve(documentRoot, rel));
508
- }
509
- }
510
- return [...roots];
511
- }
512
-
@@ -1,24 +0,0 @@
1
- const DEFAULT_MAX_BODY_BYTES = 64 * 1024;
2
-
3
- export async function readJsonBody(req, {
4
- maxBytes = DEFAULT_MAX_BODY_BYTES,
5
- bodyLabel = "Request",
6
- } = {}) {
7
- let body = "";
8
- for await (const chunk of req) {
9
- body += String(chunk);
10
- if (Buffer.byteLength(body, "utf8") > maxBytes) {
11
- throw new Error(`${bodyLabel} body is too large.`);
12
- }
13
- }
14
- try {
15
- return JSON.parse(body || "{}");
16
- } catch {
17
- throw new Error(`${bodyLabel} body must be valid JSON.`);
18
- }
19
- }
20
-
21
- export function writeJson(res, status, body) {
22
- res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
23
- res.end(`${JSON.stringify(body, null, 2)}\n`);
24
- }