@open-press/cli 0.5.0 → 0.7.0
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/dist/cli.js +114 -23
- package/package.json +1 -1
- package/template/core/CHANGELOG.md +97 -1
- package/template/core/README.md +9 -5
- package/template/core/engine/cli.mjs +2 -5
- package/template/core/engine/commands/_shared.mjs +4 -4
- package/template/core/engine/commands/deploy.mjs +1 -1
- package/template/core/engine/commands/inspect.mjs +3 -3
- package/template/core/engine/commands/replace.mjs +1 -1
- package/template/core/engine/commands/search.mjs +1 -1
- package/template/core/engine/commands/validate.mjs +2 -2
- package/template/core/engine/document-export.mjs +1 -1
- package/template/core/engine/{chrome-pdf.mjs → output/chrome-pdf.mjs} +1 -2
- package/template/core/engine/{deploy-sync.mjs → output/deploy-sync.mjs} +2 -2
- package/template/core/engine/{fonts.mjs → output/fonts.mjs} +1 -1
- package/template/core/engine/{public-assets.mjs → output/public-assets.mjs} +2 -2
- package/template/core/engine/{static-server.mjs → output/static-server.mjs} +2 -2
- package/template/core/engine/react/caption-numbering.mjs +73 -0
- package/template/core/engine/react/comment-marker.mjs +54 -10
- package/template/core/engine/react/document-entry.mjs +124 -64
- package/template/core/engine/react/document-export.mjs +252 -311
- package/template/core/engine/react/mdx-compile.mjs +123 -3
- package/template/core/engine/react/measurement-css.mjs +3 -3
- package/template/core/engine/react/pagination/allocator.mjs +122 -0
- package/template/core/engine/react/pagination/regions.mjs +81 -0
- package/template/core/engine/react/pagination.mjs +9 -121
- package/template/core/engine/react/pipeline/allocate.mjs +248 -0
- package/template/core/engine/react/pipeline/final-render.mjs +94 -0
- package/template/core/engine/react/pipeline/frame-measurement.mjs +271 -0
- package/template/core/engine/react/pipeline/press-tree.mjs +135 -0
- package/template/core/engine/react/project-asset-endpoint.mjs +2 -2
- package/template/core/engine/react/{chapter-css.mjs → section-css.mjs} +12 -9
- package/template/core/engine/react/sources/heading-numbering.mjs +132 -0
- package/template/core/engine/react/sources/mdx-resolver.mjs +441 -0
- package/template/core/engine/react/{workspace-discovery.mjs → style-discovery.mjs} +29 -40
- package/template/core/engine/{config.mjs → runtime/config.mjs} +15 -0
- package/template/core/engine/{file-utils.mjs → runtime/file-utils.mjs} +1 -1
- package/template/core/engine/{inspection.mjs → runtime/inspection.mjs} +3 -4
- package/template/core/engine/{source-text-tools.mjs → runtime/source-text-tools.mjs} +24 -7
- package/template/core/engine/runtime/source-workspace.mjs +186 -0
- package/template/core/engine/{validation.mjs → runtime/validation.mjs} +19 -17
- package/template/core/package.json +5 -2
- package/template/core/src/openpress/anchorMap.ts +27 -0
- package/template/core/src/openpress/core/Frame.tsx +80 -0
- package/template/core/src/openpress/core/FrameContext.tsx +19 -0
- package/template/core/src/openpress/core/MdxArea.tsx +35 -0
- package/template/core/src/openpress/core/Press.tsx +34 -0
- package/template/core/src/openpress/core/index.tsx +34 -15
- package/template/core/src/openpress/core/primitives.tsx +23 -0
- package/template/core/src/openpress/core/types.ts +131 -19
- package/template/core/src/openpress/core/useSource.ts +28 -0
- package/template/core/src/openpress/manuscript/index.tsx +196 -0
- package/template/core/src/openpress/mdx/index.ts +88 -0
- package/template/core/src/openpress/numbering/index.ts +294 -0
- package/template/core/src/openpress/publicPage.tsx +4 -186
- package/template/core/src/openpress/reactDocumentMetadata.ts +2 -16
- package/template/core/src/openpress/types.ts +0 -16
- package/template/core/src/openpress/workbench.tsx +2 -36
- package/template/core/src/styles/openpress/responsive.css +0 -14
- package/template/core/tsconfig.json +4 -1
- package/template/core/vite.config.ts +10 -3
- package/template/packs/academic-paper/document/chapters/01-introduction/content/01-introduction.mdx +21 -0
- package/template/packs/academic-paper/document/chapters/02-methods/content/01-methods.mdx +30 -0
- package/template/packs/academic-paper/document/chapters/03-results-and-discussion/content/01-results.mdx +29 -0
- package/template/packs/academic-paper/document/chapters/04-acknowledgment/content/01-acknowledgment.mdx +12 -0
- package/template/packs/academic-paper/document/chapters/05-references/content/01-references.mdx +27 -0
- package/template/packs/academic-paper/document/components/ChapterOpenerVisual/index.tsx +76 -0
- package/template/packs/academic-paper/document/components/Page.tsx +37 -0
- package/template/packs/academic-paper/document/components/TokenSwatchGrid/index.tsx +46 -0
- package/template/packs/academic-paper/document/components/TokenSwatchGrid/style.css +63 -0
- package/template/packs/academic-paper/document/components/TypeSpecimen/index.tsx +38 -0
- package/template/packs/academic-paper/document/components/TypeSpecimen/style.css +111 -0
- package/template/packs/academic-paper/document/design.md +279 -0
- package/template/packs/academic-paper/document/index.tsx +131 -0
- package/template/packs/academic-paper/document/media/README.md +13 -0
- package/template/packs/academic-paper/document/openpress.config.mjs +26 -0
- package/template/packs/academic-paper/document/theme/README.md +11 -0
- package/template/packs/academic-paper/document/theme/base/page-contract.css +505 -0
- package/template/packs/academic-paper/document/theme/base/print.css +93 -0
- package/template/packs/academic-paper/document/theme/base/typography.css +336 -0
- package/template/packs/academic-paper/document/theme/fonts.css +3 -0
- package/template/packs/academic-paper/document/theme/page-surfaces/back-cover.css +43 -0
- package/template/packs/academic-paper/document/theme/page-surfaces/chapter-opener.css +205 -0
- package/template/packs/academic-paper/document/theme/page-surfaces/cover.css +267 -0
- package/template/packs/academic-paper/document/theme/page-surfaces/toc.css +149 -0
- package/template/packs/academic-paper/document/theme/patterns/_chart-frame.css +49 -0
- package/template/packs/academic-paper/document/theme/patterns/figure-grid.css +68 -0
- package/template/packs/academic-paper/document/theme/patterns/table-utilities.css +66 -0
- package/template/packs/academic-paper/document/theme/shell/reader-controls.css +761 -0
- package/template/packs/academic-paper/document/theme/tokens.css +80 -0
- package/template/packs/academic-paper/openpress.config.mjs +5 -0
- package/template/packs/claude-document/document/components/Page.tsx +24 -14
- package/template/packs/claude-document/document/design.md +2 -2
- package/template/packs/claude-document/document/index.tsx +67 -62
- package/template/packs/claude-document/document/theme/page-surfaces/toc.css +19 -7
- package/template/packs/editorial-monograph/document/components/Page.tsx +24 -14
- package/template/packs/editorial-monograph/document/design.md +2 -2
- package/template/packs/editorial-monograph/document/index.tsx +71 -47
- package/template/packs/editorial-monograph/document/theme/page-surfaces/toc.css +19 -9
- package/template/core/engine/commands/migrate-to-react.mjs +0 -27
- package/template/core/engine/page-renderer.mjs +0 -217
- package/template/core/engine/react/migrate-to-react.mjs +0 -355
- package/template/core/engine/source-workspace.mjs +0 -76
- package/template/core/src/openpress/core/basePages.tsx +0 -87
- package/template/core/src/openpress/pagination.ts +0 -845
- package/template/packs/claude-document/document/chapters/01-document-shape/chapter.tsx +0 -30
- package/template/packs/claude-document/document/chapters/02-review-loop/chapter.tsx +0 -30
- /package/template/core/engine/{chrome-pdf.d.mts → output/chrome-pdf.d.mts} +0 -0
- /package/template/core/engine/{katex-assets.mjs → output/katex-assets.mjs} +0 -0
- /package/template/core/engine/{page-block.mjs → output/page-block.mjs} +0 -0
- /package/template/core/engine/{pdf-media.mjs → output/pdf-media.mjs} +0 -0
- /package/template/core/engine/{config.d.mts → runtime/config.d.mts} +0 -0
- /package/template/core/engine/{issue-report.mjs → runtime/issue-report.mjs} +0 -0
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,41 @@ import path from "path";
|
|
|
19
19
|
import { Readable } from "stream";
|
|
20
20
|
import { pipeline } from "stream/promises";
|
|
21
21
|
import { x as extract } from "tar";
|
|
22
|
+
async function degit({ owner, repo, ref = "main", dest, subdir }) {
|
|
23
|
+
const url = `https://codeload.github.com/${owner}/${repo}/tar.gz/refs/heads/${ref}`;
|
|
24
|
+
const tmpDir = await mkdir(path.join(tmpdir(), `open-press-degit-${Date.now()}`), { recursive: true });
|
|
25
|
+
const tarballPath = path.join(tmpDir, "repo.tar.gz");
|
|
26
|
+
try {
|
|
27
|
+
await fetchTo(url, tarballPath);
|
|
28
|
+
await mkdir(dest, { recursive: true });
|
|
29
|
+
const subdirSegments = subdir ? subdir.split("/").filter(Boolean).length : 0;
|
|
30
|
+
const totalStrip = 1 + subdirSegments;
|
|
31
|
+
const filterPrefix = subdir ? subdir.replace(/\/$/, "") + "/" : null;
|
|
32
|
+
await extract({
|
|
33
|
+
file: tarballPath,
|
|
34
|
+
cwd: dest,
|
|
35
|
+
strip: totalStrip,
|
|
36
|
+
filter: (filePath) => {
|
|
37
|
+
const segments = filePath.split("/");
|
|
38
|
+
const inside = segments.slice(1).join("/");
|
|
39
|
+
if (filterPrefix) {
|
|
40
|
+
return inside.startsWith(filterPrefix);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
} finally {
|
|
46
|
+
await rm(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function fetchTo(url, destFile) {
|
|
51
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
52
|
+
if (!res.ok || !res.body) {
|
|
53
|
+
throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(destFile));
|
|
56
|
+
}
|
|
22
57
|
async function pathIsEmpty(target) {
|
|
23
58
|
try {
|
|
24
59
|
const s = await stat(target);
|
|
@@ -80,14 +115,14 @@ async function patchPackageJsonName(packagePath, newName) {
|
|
|
80
115
|
}
|
|
81
116
|
|
|
82
117
|
// src/init.ts
|
|
83
|
-
var
|
|
84
|
-
var
|
|
118
|
+
var BUNDLED_PACKS = ["editorial-monograph", "claude-document", "academic-paper"];
|
|
119
|
+
var FRAMEWORK_SKILLS_SOURCE = "quan0715/open-press";
|
|
85
120
|
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
86
121
|
var TEMPLATE_ROOT = path2.resolve(__dirname, "..", "template");
|
|
87
122
|
var TEMPLATE_CORE = path2.join(TEMPLATE_ROOT, "core");
|
|
88
123
|
var TEMPLATE_PACKS = path2.join(TEMPLATE_ROOT, "packs");
|
|
89
124
|
async function init(options) {
|
|
90
|
-
|
|
125
|
+
const packSpec = options.pack ? parsePackSpec(options.pack) : null;
|
|
91
126
|
ensureTemplateBundled();
|
|
92
127
|
const target = path2.resolve(process.cwd(), options.target);
|
|
93
128
|
await ensureTarget(target, options.force);
|
|
@@ -95,17 +130,40 @@ async function init(options) {
|
|
|
95
130
|
log("Copying framework (engine + runtime + config)\u2026");
|
|
96
131
|
await cp(TEMPLATE_CORE, target, { recursive: true });
|
|
97
132
|
const docDest = path2.join(target, "document");
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const packStarter = path2.join(TEMPLATE_PACKS,
|
|
133
|
+
await rm2(docDest, { recursive: true, force: true });
|
|
134
|
+
await mkdir2(docDest, { recursive: true });
|
|
135
|
+
if (packSpec?.kind === "bundled") {
|
|
136
|
+
log(`Applying bundled style pack: ${packSpec.name}`);
|
|
137
|
+
const packStarter = path2.join(TEMPLATE_PACKS, packSpec.name, "document");
|
|
103
138
|
if (!existsSync(packStarter)) {
|
|
104
|
-
throw new Error(`
|
|
139
|
+
throw new Error(`Bundled style pack starter not found: ${packStarter}`);
|
|
105
140
|
}
|
|
106
141
|
await cp(packStarter, docDest, { recursive: true });
|
|
107
|
-
} else {
|
|
108
|
-
|
|
142
|
+
} else if (packSpec?.kind === "github") {
|
|
143
|
+
log(`Fetching style pack from github:${packSpec.owner}/${packSpec.repo}${packSpec.ref ? `#${packSpec.ref}` : ""}\u2026`);
|
|
144
|
+
try {
|
|
145
|
+
await degit({
|
|
146
|
+
owner: packSpec.owner,
|
|
147
|
+
repo: packSpec.repo,
|
|
148
|
+
ref: packSpec.ref,
|
|
149
|
+
dest: docDest,
|
|
150
|
+
subdir: "starter/document"
|
|
151
|
+
});
|
|
152
|
+
} catch (err) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Failed to fetch pack from github:${packSpec.owner}/${packSpec.repo}: ${err instanceof Error ? err.message : String(err)}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (await pathIsEmpty(docDest)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`github:${packSpec.owner}/${packSpec.repo} doesn't contain starter/document/ at the repo root.
|
|
160
|
+
Third-party pack repos should follow this layout:
|
|
161
|
+
<repo>/
|
|
162
|
+
\u251C\u2500\u2500 starter/
|
|
163
|
+
\u2502 \u2514\u2500\u2500 document/ \u2190 cli copies this into your workspace's document/
|
|
164
|
+
\u2514\u2500\u2500 skills/<pack>/SKILL.md \u2190 npx skills add picks this up`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
109
167
|
}
|
|
110
168
|
const pkgPath = path2.join(target, "package.json");
|
|
111
169
|
if (existsSync(pkgPath)) {
|
|
@@ -121,13 +179,23 @@ async function init(options) {
|
|
|
121
179
|
author: options.author
|
|
122
180
|
});
|
|
123
181
|
}
|
|
124
|
-
log(`Installing
|
|
182
|
+
log(`Installing framework skills via \`npx skills add ${FRAMEWORK_SKILLS_SOURCE}\`\u2026`);
|
|
125
183
|
try {
|
|
126
|
-
await runInTarget(target, "npx", ["-y", "skills@latest", "add",
|
|
184
|
+
await runInTarget(target, "npx", ["-y", "skills@latest", "add", FRAMEWORK_SKILLS_SOURCE]);
|
|
127
185
|
} catch (err) {
|
|
128
|
-
log(`(skills install failed;
|
|
186
|
+
log(`(framework skills install failed; retry: npx skills add ${FRAMEWORK_SKILLS_SOURCE})`);
|
|
129
187
|
log(` reason: ${err instanceof Error ? err.message : String(err)}`);
|
|
130
188
|
}
|
|
189
|
+
if (packSpec?.kind === "github") {
|
|
190
|
+
const packSource = `${packSpec.owner}/${packSpec.repo}`;
|
|
191
|
+
log(`Installing pack skills via \`npx skills add ${packSource}\`\u2026`);
|
|
192
|
+
try {
|
|
193
|
+
await runInTarget(target, "npx", ["-y", "skills@latest", "add", packSource]);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
log(`(pack skills install failed; retry: npx skills add ${packSource})`);
|
|
196
|
+
log(` reason: ${err instanceof Error ? err.message : String(err)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
131
199
|
if (options.install) {
|
|
132
200
|
log("Installing dependencies (npm install)\u2026");
|
|
133
201
|
await runInTarget(target, "npm", ["install"]);
|
|
@@ -148,6 +216,26 @@ async function init(options) {
|
|
|
148
216
|
}
|
|
149
217
|
printNextSteps(target, options);
|
|
150
218
|
}
|
|
219
|
+
function parsePackSpec(spec) {
|
|
220
|
+
if (spec.startsWith("github:")) {
|
|
221
|
+
const rest = spec.slice("github:".length);
|
|
222
|
+
const [pathPart, ref] = rest.split("#");
|
|
223
|
+
const segments = pathPart.split("/").filter(Boolean);
|
|
224
|
+
if (segments.length !== 2) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Invalid --pack spec: "${spec}". Use github:owner/repo or github:owner/repo#ref.`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
const [owner, repo] = segments;
|
|
230
|
+
return { kind: "github", owner, repo, ref: ref?.trim() || void 0 };
|
|
231
|
+
}
|
|
232
|
+
if (!BUNDLED_PACKS.includes(spec)) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Unknown style pack: "${spec}". Bundled packs: ${BUNDLED_PACKS.join(", ")}. For third-party packs use github:owner/repo (e.g. github:quan0715/openpress-pack-nycu-thesis).`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return { kind: "bundled", name: spec };
|
|
238
|
+
}
|
|
151
239
|
function ensureTemplateBundled() {
|
|
152
240
|
if (!existsSync(TEMPLATE_CORE) || !existsSync(TEMPLATE_PACKS)) {
|
|
153
241
|
throw new Error(
|
|
@@ -155,12 +243,6 @@ function ensureTemplateBundled() {
|
|
|
155
243
|
);
|
|
156
244
|
}
|
|
157
245
|
}
|
|
158
|
-
function validatePack(pack) {
|
|
159
|
-
if (pack === void 0) return;
|
|
160
|
-
if (!KNOWN_PACKS.includes(pack)) {
|
|
161
|
-
throw new Error(`Unknown style pack: ${pack}. Known packs: ${KNOWN_PACKS.join(", ")}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
246
|
async function ensureTarget(target, force) {
|
|
165
247
|
if (existsSync(target)) {
|
|
166
248
|
if (force) return;
|
|
@@ -228,19 +310,28 @@ Usage:
|
|
|
228
310
|
npx @open-press/cli init <target> [flags]
|
|
229
311
|
|
|
230
312
|
Flags:
|
|
231
|
-
--pack <
|
|
313
|
+
--pack <spec> Style pack source. Either:
|
|
314
|
+
\u2022 a bundled name \u2014 editorial-monograph | claude-document | academic-paper
|
|
315
|
+
\u2022 github:owner/repo (third-party pack)
|
|
316
|
+
\u2022 github:owner/repo#branch-or-tag
|
|
232
317
|
--title <s> Document title (written to openpress.config.mjs)
|
|
233
318
|
--subtitle <s> Document subtitle
|
|
234
319
|
--organization <s> Organization name
|
|
235
|
-
--author <s> Author name
|
|
320
|
+
--author <s> Author name
|
|
236
321
|
--no-git Skip git init
|
|
237
322
|
--no-install Skip npm install
|
|
238
323
|
--force Allow non-empty target
|
|
239
324
|
--help Show this help
|
|
240
325
|
|
|
241
326
|
Examples:
|
|
327
|
+
# Bundled
|
|
242
328
|
npx @open-press/cli init my-doc --pack editorial-monograph
|
|
243
|
-
npx @open-press/cli init my-
|
|
329
|
+
npx @open-press/cli init my-brief --pack claude-document --title "Q2 Brief" --author Quan
|
|
330
|
+
npx @open-press/cli init my-paper --pack academic-paper --title "Paper Title" --author "First Author"
|
|
331
|
+
|
|
332
|
+
# Third-party (any GitHub repo with starter/document/ at the root)
|
|
333
|
+
npx @open-press/cli init my-thesis --pack github:quan0715/openpress-pack-nycu-thesis
|
|
334
|
+
npx @open-press/cli init my-paper --pack github:foo/their-pack#v1.2
|
|
244
335
|
`;
|
|
245
336
|
async function main(argv) {
|
|
246
337
|
if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
|
package/package.json
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# @open-press/core
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 718d2d1: **Press Tree render architecture** — full refresh of the React export pipeline (clean break, no v0.5 compatibility).
|
|
8
|
+
|
|
9
|
+
The render kernel no longer knows about `cover`, `toc`, `chapter`, or `back-cover` as built-in concepts. Workspaces describe their document as a React tree using three primitives:
|
|
10
|
+
|
|
11
|
+
- `Press` — root composition boundary.
|
|
12
|
+
- `Frame` — a single fixed-layout surface (replaces `BasePage` and friends).
|
|
13
|
+
- `MdxArea` — a measurable slot consuming a content chain, with `overflow="extend|truncate|error"` control.
|
|
14
|
+
|
|
15
|
+
Sources are now declarative descriptors:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
export const sources = {
|
|
19
|
+
story: mdxSource({ preset: "section-folders", root: "chapters" }),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default function MyPress() {
|
|
23
|
+
return (
|
|
24
|
+
<Press>
|
|
25
|
+
<Cover />
|
|
26
|
+
<Toc source="story" />
|
|
27
|
+
<Sections source="story" page={Page} />
|
|
28
|
+
<BackCover />
|
|
29
|
+
</Press>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Three `mdxSource()` presets: `section-folders` (existing convention), `section-files` (flat file-per-section), `file-list` (explicit ordering).
|
|
35
|
+
|
|
36
|
+
Manuscript helpers (`Toc`, `Sections`, `Chapters` alias) ship in `@open-press/core/manuscript`. `mdxSource()` lives in `@open-press/core/mdx`. Subpath exports keep the public surface tight without committing to separate npm packages.
|
|
37
|
+
|
|
38
|
+
`Toc` is implemented as a manuscript helper, not a core kernel special case. Registered sources generate a synthetic `toc:<sourceId>` chain; `TocArea` consumes it with the same allocation path as `MdxArea`.
|
|
39
|
+
|
|
40
|
+
Reader-side pagination is removed. The export pipeline writes final frame HTML into `document.json`; the reader displays that HTML and no longer mutates headings/captions, injects footers, or reflows pages at runtime. Page shell belongs to workspace components.
|
|
41
|
+
|
|
42
|
+
MDX source resolution now derives manuscript TOC entries from actual `##` / `###` headings, not folder slugs. Heading numbering is written during export as `data-chapter="01"` / `data-section="1.1"` attributes so themes can render numbering with CSS without reader-side mutation.
|
|
43
|
+
|
|
44
|
+
**Removed (no compatibility layer):**
|
|
45
|
+
|
|
46
|
+
- `BasePage`, `BaseCoverPage`, `BaseTocPage`, `BaseContentPage`, `BaseBackCoverPage`.
|
|
47
|
+
- Legacy named exports (`cover`, `toc`, `backCover`) from `document/index.tsx`.
|
|
48
|
+
- The `migrate-to-react` CLI command.
|
|
49
|
+
- Implicit chapter discovery as the only source mechanism.
|
|
50
|
+
- Legacy `chapter.tsx` meta/opener auto-discovery. Section openers are explicit workspace components in the Press tree.
|
|
51
|
+
|
|
52
|
+
The top-level purity gate remains: `config` must be data, `sources` must be pure `mdxSource()` descriptors, and filesystem/network/process side effects are rejected before module execution. Default-exported function bodies can contain normal React component logic, including hooks and `.map()`.
|
|
53
|
+
|
|
54
|
+
All `<Frame>` instances require a stable `frameKey`; source roots and file-list entries must stay inside `document/`.
|
|
55
|
+
|
|
56
|
+
**Workspace data attributes:**
|
|
57
|
+
|
|
58
|
+
- `data-frame-role` (new, opaque role like `"manuscript.content"`).
|
|
59
|
+
- `data-page-kind` (derived from role's last segment — reader CSS keeps using this).
|
|
60
|
+
- `data-section-id` replaces `data-chapter-slug` for section-scoped CSS.
|
|
61
|
+
|
|
62
|
+
**Migration:** Workspaces written for v0.5.x must rewrite `document/index.tsx` to default-export a Press component. Pre-1.0 minor bump is acceptable per repo policy; no production deployments exist to break.
|
|
63
|
+
|
|
64
|
+
See `docs/superpowers/specs/2026-05-23-press-tree-render-architecture-design.md` for full design rationale.
|
|
65
|
+
|
|
66
|
+
## 0.6.0
|
|
67
|
+
|
|
68
|
+
### Minor Changes
|
|
69
|
+
|
|
70
|
+
- f8fdecd: Third bundled pack: `academic-paper`.
|
|
71
|
+
|
|
72
|
+
A single-column A4 academic / research paper starter — serif title block, abstract band, index terms, numbered sections (I, II, III), italic sub-sections (A, B, C), `[N]` numeric references, sample chapters derived from the IEEE conference template structure (Introduction, Methods, Results & Discussion, Acknowledgment, References).
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx @open-press/cli init my-paper --pack academic-paper
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Suitable for: draft / preprint / iteration. Not suitable for camera-ready IEEE / ACM submission — those still need LaTeX with the publisher's class file.
|
|
79
|
+
|
|
80
|
+
Two-column body and other paged-document features (footnotes, cross-references with page numbers, running headers) are intentionally **out of scope for this release**. They'll be designed as a self-maintained engine evolution + multi-mode architecture in a separate spec round, rather than depending on a third-party pagination polyfill.
|
|
81
|
+
|
|
82
|
+
- c490653: `@open-press/cli init` accepts third-party style packs via `--pack github:owner/repo`.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# bundled (unchanged)
|
|
86
|
+
npx @open-press/cli init my-doc --pack editorial-monograph
|
|
87
|
+
|
|
88
|
+
# third-party (new)
|
|
89
|
+
npx @open-press/cli init my-thesis --pack github:quan0715/openpress-pack-nycu-thesis
|
|
90
|
+
npx @open-press/cli init my-paper --pack github:foo/their-pack#v1.2
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The cli fetches `starter/document/` from the named repo (default branch, or `#ref` for a specific branch/tag) and copies it into the new workspace. If the pack repo also publishes SKILL files at `skills/<name>/`, they're installed via `npx skills add <owner>/<repo>` after the framework skills, so the agent picks them up automatically.
|
|
94
|
+
|
|
95
|
+
Repo layout convention for third-party packs is documented in `docs/style-pack-authoring.md`. Empty-result extraction (the named repo exists but has no `starter/document/` at root) fails with a clear error pointing at the expected layout.
|
|
96
|
+
|
|
97
|
+
The two bundled packs (`editorial-monograph`, `claude-document`) keep their current short-name behaviour; only the cli's validator widened to accept the `github:` prefix.
|
|
98
|
+
|
|
3
99
|
## 0.5.0
|
|
4
100
|
|
|
5
101
|
### Minor Changes
|
|
@@ -51,4 +147,4 @@
|
|
|
51
147
|
|
|
52
148
|
**@open-press/cli** (new): scaffolder for open-press workspaces. Run `npx @open-press/cli init <target> --pack <pack>` to create a fixed-layout document workspace from a bundled template. Supports `editorial-monograph` and `claude-document` style packs, metadata flags, and AI-agent skill installation under `.claude/skills/` and `.agents/skills/`.
|
|
53
149
|
|
|
54
|
-
**@open-press/core** (new): framework runtime, CLI engine, render pipeline, and
|
|
150
|
+
**@open-press/core** (new): framework runtime, CLI engine, render pipeline, and document primitives. Consumed transitively by workspaces scaffolded via `@open-press/cli`. Exposes the `open-press` bin (dev / build / preview / validate / pdf / deploy / export).
|
package/template/core/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @open-press/core
|
|
2
2
|
|
|
3
|
-
Framework runtime, CLI engine, and
|
|
3
|
+
Framework runtime, CLI engine, and Press Tree primitives for [open-press](https://github.com/quan0715/open-press) — an AI-first fixed-layout document workspace.
|
|
4
4
|
|
|
5
5
|
Most users do **not** install this package directly. Instead, scaffold a workspace with the CLI:
|
|
6
6
|
|
|
@@ -20,15 +20,19 @@ npm install @open-press/core
|
|
|
20
20
|
|
|
21
21
|
```tsx
|
|
22
22
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
BaseBackCoverPage,
|
|
23
|
+
Press,
|
|
24
|
+
Frame,
|
|
25
|
+
MdxArea,
|
|
27
26
|
BaseFigure,
|
|
28
27
|
BaseCallout,
|
|
29
28
|
} from "@open-press/core";
|
|
29
|
+
|
|
30
|
+
import { mdxSource } from "@open-press/core/mdx";
|
|
31
|
+
import { Sections, Toc } from "@open-press/core/manuscript";
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
`document/index.tsx` default-exports a `<Press>` tree. `Frame` marks fixed-layout pages, `MdxArea` receives measured MDX blocks, and `mdxSource()` declares which MDX files participate in the render pipeline.
|
|
35
|
+
|
|
32
36
|
The CLI bin (`open-press`) supports dev / build / preview / validate / pdf / deploy / export commands. It requires a workspace with `openpress.config.mjs` and the surrounding framework files (which the scaffolder installs).
|
|
33
37
|
|
|
34
38
|
## License
|
|
@@ -6,7 +6,6 @@ import * as doctorCmd from "./commands/doctor.mjs";
|
|
|
6
6
|
import * as exportCmd from "./commands/export.mjs";
|
|
7
7
|
import * as initCmd from "./commands/init.mjs";
|
|
8
8
|
import * as inspectCmd from "./commands/inspect.mjs";
|
|
9
|
-
import * as migrateToReactCmd from "./commands/migrate-to-react.mjs";
|
|
10
9
|
import * as pdfCmd from "./commands/pdf.mjs";
|
|
11
10
|
import * as previewCmd from "./commands/preview.mjs";
|
|
12
11
|
import * as replaceCmd from "./commands/replace.mjs";
|
|
@@ -16,13 +15,12 @@ import * as typecheckCmd from "./commands/typecheck.mjs";
|
|
|
16
15
|
import * as upgradeCmd from "./commands/upgrade.mjs";
|
|
17
16
|
import * as validateCmd from "./commands/validate.mjs";
|
|
18
17
|
import { parseOptions } from "./commands/_shared.mjs";
|
|
19
|
-
import { loadConfig } from "./config.mjs";
|
|
18
|
+
import { loadConfig } from "./runtime/config.mjs";
|
|
20
19
|
import { listStylePackSkills } from "./init.mjs";
|
|
21
|
-
import { discoverWorkspace } from "./validation.mjs";
|
|
20
|
+
import { discoverWorkspace } from "./runtime/validation.mjs";
|
|
22
21
|
|
|
23
22
|
const COMMANDS = {
|
|
24
23
|
init: initCmd,
|
|
25
|
-
"migrate-to-react": migrateToReactCmd,
|
|
26
24
|
validate: validateCmd,
|
|
27
25
|
inspect: inspectCmd,
|
|
28
26
|
search: searchCmd,
|
|
@@ -79,7 +77,6 @@ async function printHelp() {
|
|
|
79
77
|
|
|
80
78
|
Commands:
|
|
81
79
|
init <target> [--skill <name>] [--force]
|
|
82
|
-
migrate-to-react [path] [--dry-run] [--force] [--json]
|
|
83
80
|
validate
|
|
84
81
|
inspect [--json] [--no-build] [--dry-run]
|
|
85
82
|
search [path] <query> [--json] [--scope content|all]
|
|
@@ -2,14 +2,14 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { printUrlToPdf, stopChildProcess, waitForPrintReady } from "../chrome-pdf.mjs";
|
|
6
|
-
import { loadConfig, publicPdfHref } from "../config.mjs";
|
|
5
|
+
import { printUrlToPdf, stopChildProcess, waitForPrintReady } from "../output/chrome-pdf.mjs";
|
|
6
|
+
import { loadConfig, publicPdfHref } from "../runtime/config.mjs";
|
|
7
7
|
import { exportDocument } from "../document-export.mjs";
|
|
8
|
-
import { optimizePdfMediaForStaticRoot } from "../pdf-media.mjs";
|
|
8
|
+
import { optimizePdfMediaForStaticRoot } from "../output/pdf-media.mjs";
|
|
9
9
|
|
|
10
10
|
export const ENGINE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
11
|
export const CLI_ENTRY = path.join(ENGINE_DIR, "cli.mjs");
|
|
12
|
-
export const STATIC_SERVER = path.join(ENGINE_DIR, "static-server.mjs");
|
|
12
|
+
export const STATIC_SERVER = path.join(ENGINE_DIR, "output", "static-server.mjs");
|
|
13
13
|
|
|
14
14
|
export function parseOptions(argv) {
|
|
15
15
|
const options = {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { deploySync } from "../deploy-sync.mjs";
|
|
2
|
+
import { deploySync } from "../output/deploy-sync.mjs";
|
|
3
3
|
import { CLI_ENTRY, buildReactPdf, formatNodeScriptCommand, runCommand, writePdfStageDeployConfig } from "./_shared.mjs";
|
|
4
4
|
|
|
5
5
|
export async function run({ root, config, options, recurse }) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { inspectWorkspace } from "../inspection.mjs";
|
|
2
|
-
import { exitCodeForIssueReport } from "../issue-report.mjs";
|
|
1
|
+
import { inspectWorkspace } from "../runtime/inspection.mjs";
|
|
2
|
+
import { exitCodeForIssueReport } from "../runtime/issue-report.mjs";
|
|
3
3
|
|
|
4
4
|
export async function run({ root, config, options, recurse }) {
|
|
5
5
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -10,7 +10,7 @@ export async function run({ root, config, options, recurse }) {
|
|
|
10
10
|
if (!options.noBuild) {
|
|
11
11
|
console.log("Command: node engine/cli.mjs render . --renderer react");
|
|
12
12
|
}
|
|
13
|
-
console.log(`Command: node engine/static-server.mjs ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
13
|
+
console.log(`Command: node engine/output/static-server.mjs ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
14
14
|
console.log(`Chrome inspection URL: ${url}`);
|
|
15
15
|
return 0;
|
|
16
16
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { validateWorkspace } from "../validation.mjs";
|
|
2
|
-
import { exitCodeForIssueReport } from "../issue-report.mjs";
|
|
1
|
+
import { validateWorkspace } from "../runtime/validation.mjs";
|
|
2
|
+
import { exitCodeForIssueReport } from "../runtime/issue-report.mjs";
|
|
3
3
|
|
|
4
4
|
export async function run({ root, options }) {
|
|
5
5
|
const report = await validateWorkspace(root);
|
|
@@ -10,6 +10,6 @@ export async function exportDocument(root = ROOT) {
|
|
|
10
10
|
if (reactResult) return reactResult;
|
|
11
11
|
|
|
12
12
|
throw new Error(
|
|
13
|
-
"React/MDX document entry not found. Expected document/index.tsx
|
|
13
|
+
"React/MDX document entry not found. Expected document/index.tsx with a Press default export before exporting.",
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -203,8 +203,7 @@ export async function waitForPrintReady(client) {
|
|
|
203
203
|
awaitPromise: true,
|
|
204
204
|
expression: `Promise.resolve().then(async () => {
|
|
205
205
|
const root = document.querySelector('[data-openpress-print-document="true"]');
|
|
206
|
-
|
|
207
|
-
if (!ready) return 0;
|
|
206
|
+
if (!root || root.querySelectorAll('.openpress-html-page').length === 0) return 0;
|
|
208
207
|
|
|
209
208
|
await document.fonts?.ready;
|
|
210
209
|
await Promise.all(Array.from(document.images).map(async (img) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "
|
|
4
|
-
import { copyDirectory } from "
|
|
3
|
+
import { loadConfig } from "../runtime/config.mjs";
|
|
4
|
+
import { copyDirectory } from "../runtime/file-utils.mjs";
|
|
5
5
|
|
|
6
6
|
export async function deploySync(root, sourceDir, deployDir) {
|
|
7
7
|
const config = await loadConfig(root);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { copyDirectory } from "
|
|
3
|
+
import { copyDirectory } from "../runtime/file-utils.mjs";
|
|
4
4
|
|
|
5
5
|
export async function copyThemeFonts(root, publicOutputDir, config) {
|
|
6
6
|
const themeDir = config?.paths?.themeDir ?? path.join(path.resolve(root), "theme");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "
|
|
4
|
-
import { copyDirectory, writeComponentsCss, writeContentCss } from "
|
|
3
|
+
import { loadConfig } from "../runtime/config.mjs";
|
|
4
|
+
import { copyDirectory, writeComponentsCss, writeContentCss } from "../runtime/file-utils.mjs";
|
|
5
5
|
import { copyThemeFonts } from "./fonts.mjs";
|
|
6
6
|
import { copyKatexFonts } from "./katex-assets.mjs";
|
|
7
7
|
|
|
@@ -2,8 +2,8 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { loadConfig, publicPdfHref } from "
|
|
6
|
-
import { handleProjectAssetRequest } from "
|
|
5
|
+
import { loadConfig, publicPdfHref } from "../runtime/config.mjs";
|
|
6
|
+
import { handleProjectAssetRequest } from "../react/project-asset-endpoint.mjs";
|
|
7
7
|
|
|
8
8
|
const [rootArg = "dist", ...rest] = process.argv.slice(2);
|
|
9
9
|
const host = valueAfter(rest, "--host") ?? "127.0.0.1";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const DEFAULT_CAPTION_NUMBERING = {
|
|
2
|
+
figure: "Figure",
|
|
3
|
+
table: "Table",
|
|
4
|
+
separator: " ",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function normalizeCaptionNumbering(value = {}) {
|
|
8
|
+
const input = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9
|
+
return {
|
|
10
|
+
figure: stringOption(input.figure, DEFAULT_CAPTION_NUMBERING.figure),
|
|
11
|
+
table: stringOption(input.table, DEFAULT_CAPTION_NUMBERING.table),
|
|
12
|
+
separator: typeof input.separator === "string" ? input.separator : DEFAULT_CAPTION_NUMBERING.separator,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createCaptionNumberingState() {
|
|
17
|
+
return {
|
|
18
|
+
figure: 0,
|
|
19
|
+
table: 0,
|
|
20
|
+
seenTables: new Set(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function numberCaptionsInHtml(html, numbering, state = createCaptionNumberingState()) {
|
|
25
|
+
if (!html) return html;
|
|
26
|
+
const options = normalizeCaptionNumbering(numbering);
|
|
27
|
+
let out = String(html);
|
|
28
|
+
out = numberTableCaptions(out, options, state);
|
|
29
|
+
out = numberFigureCaptions(out, options, state);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function numberTableCaptions(html, options, state) {
|
|
34
|
+
return html.replace(/<table\b([^>]*)>([\s\S]*?<caption\b([^>]*)>)([\s\S]*?)(<\/caption>[\s\S]*?<\/table>)/g, (match, tableAttrs, beforeCaptionText, captionAttrs, captionText, afterCaptionText) => {
|
|
35
|
+
if (captionText.includes("data-openpress-caption-label=")) return match;
|
|
36
|
+
const tableId = attrValue(tableAttrs, "data-openpress-table-id");
|
|
37
|
+
if (tableId && state.seenTables.has(tableId)) return match;
|
|
38
|
+
if (tableId) state.seenTables.add(tableId);
|
|
39
|
+
state.table += 1;
|
|
40
|
+
const label = captionLabel(options.table, state.table, options.separator);
|
|
41
|
+
return `<table${tableAttrs}>${beforeCaptionText}${captionLabelSpan("table", state.table, label)} ${captionText}${afterCaptionText}`;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function numberFigureCaptions(html, options, state) {
|
|
46
|
+
return html.replace(/<figure\b([^>]*)>([\s\S]*?<figcaption\b([^>]*)>)([\s\S]*?)(<\/figcaption>[\s\S]*?<\/figure>)/g, (match, figureAttrs, beforeCaptionText, captionAttrs, captionText, afterCaptionText) => {
|
|
47
|
+
if (captionText.includes("data-openpress-caption-label=")) return match;
|
|
48
|
+
state.figure += 1;
|
|
49
|
+
const label = captionLabel(options.figure, state.figure, options.separator);
|
|
50
|
+
return `<figure${figureAttrs}>${beforeCaptionText}${captionLabelSpan("figure", state.figure, label)} ${captionText}${afterCaptionText}`;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function captionLabel(noun, number, separator) {
|
|
55
|
+
return `${noun}${separator}${number}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function captionLabelSpan(kind, number, label) {
|
|
59
|
+
return `<span class="openpress-caption-label" data-openpress-caption-label="${kind}" data-openpress-caption-number="${number}">${escapeHtml(label)}</span>`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function attrValue(attrs, name) {
|
|
63
|
+
const pattern = new RegExp(`${name}=(["'])(.*?)\\1`);
|
|
64
|
+
return attrs.match(pattern)?.[2] ?? "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function stringOption(value, fallback) {
|
|
68
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function escapeHtml(value) {
|
|
72
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
73
|
+
}
|