@open-press/core 1.2.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/engine/commands/typecheck.mjs +1 -1
- package/engine/document-export.mjs +1 -1
- package/engine/output/page-block.mjs +11 -2
- package/engine/output/public-assets.mjs +41 -6
- package/engine/output/static-server.mjs +68 -15
- package/engine/react/caption-numbering.mjs +2 -2
- package/engine/react/comment-marker.mjs +1 -2
- package/engine/react/document-entry.mjs +64 -11
- package/engine/react/document-export.d.mts +6 -0
- package/engine/react/document-export.mjs +158 -28
- package/engine/react/mdx-compile.mjs +4 -4
- package/engine/react/measurement-css.mjs +3 -3
- package/engine/react/page-folio.mjs +37 -0
- package/engine/react/pagination/allocator.mjs +4 -4
- package/engine/react/pipeline/frame-measurement.mjs +34 -16
- package/engine/react/press-tree-inspection.mjs +43 -13
- package/engine/react/project-asset-endpoint.mjs +45 -11
- package/engine/react/sources/heading-numbering.mjs +2 -2
- package/engine/react/sources/mdx-resolver.mjs +3 -3
- package/engine/react/style-discovery.mjs +60 -11
- package/engine/react/text-source-transform.mjs +18 -4
- package/engine/runtime/config.mjs +22 -22
- package/engine/runtime/file-utils.mjs +57 -13
- package/engine/runtime/inspection.mjs +40 -15
- package/engine/runtime/page-geometry.mjs +6 -6
- package/engine/runtime/source-text-tools.mjs +28 -4
- package/engine/runtime/source-workspace.mjs +6 -9
- package/engine/runtime/validation.mjs +42 -24
- package/package.json +1 -1
- package/src/openpress/app/OpenPressApp.tsx +20 -18
- package/src/openpress/app/OpenPressRuntime.tsx +3 -3
- package/src/openpress/app/WorkspaceGalleryPage.tsx +65 -39
- package/src/openpress/core/PageFolio.tsx +115 -0
- package/src/openpress/core/Press.tsx +5 -10
- package/src/openpress/core/Slide.tsx +11 -0
- package/src/openpress/core/index.tsx +4 -0
- package/src/openpress/core/types.ts +21 -13
- package/src/openpress/core/useSource.ts +1 -1
- package/src/openpress/document-model/workspaceManifestModel.ts +4 -9
- package/src/openpress/reader/SlidePresentationPage.tsx +7 -3
- package/src/openpress/workbench/shell/WorkbenchToolbarActions.tsx +46 -43
- package/src/styles/openpress/workbench-toolbar.css +33 -0
- package/src/styles/openpress/workspace-gallery.css +130 -47
- package/vite.config.ts +82 -16
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Package-owned runtime, render engine, and Press Tree primitives for [open-press]
|
|
|
5
5
|
Most users do **not** install this package directly. Instead, scaffold a workspace with the CLI:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx @open-press/cli init my-doc
|
|
8
|
+
npx @open-press/cli init my-doc --type pages
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
The scaffolded workspace depends on this package; it does not vendor a copy of the runtime. Starter files are supplied by skills or by project-specific `press/` source files.
|
|
@@ -31,7 +31,7 @@ import { mdxSource } from "@open-press/core/mdx";
|
|
|
31
31
|
import { Sections, Toc } from "@open-press/core/manuscript";
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
`press
|
|
34
|
+
Each `press/<slug>/press.tsx` default-exports a component that renders one `<Press>`. `Frame` marks fixed-layout pages, `MdxArea` receives measured MDX blocks, and `mdxSource()` declares which MDX files participate in the render pipeline.
|
|
35
35
|
|
|
36
36
|
For the maintenance contract around Press Tree, page geometry presets, and the
|
|
37
37
|
allocation pipeline, see [`docs/press-tree.md`](https://github.com/quan0715/open-press/blob/main/docs/press-tree.md).
|
|
@@ -8,7 +8,7 @@ import { loadConfig } from "../runtime/config.mjs";
|
|
|
8
8
|
// Run typecheck via the locally installed typescript. The previous
|
|
9
9
|
// implementation used `npx tsc`; npm 11 + Node 24 (our CI / release
|
|
10
10
|
// pin) changed npx's bin lookup so it no longer walks pnpm's nested
|
|
11
|
-
// `.bin/` symlink farm and
|
|
11
|
+
// `.bin/` symlink farm and may fetch the wrong
|
|
12
12
|
// `tsc@2.0.4` shim, which crashes.
|
|
13
13
|
//
|
|
14
14
|
// Resolution order:
|
|
@@ -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 press
|
|
13
|
+
"React/MDX document entry not found. Expected one or more press/*/press.tsx files before exporting.",
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -11,12 +11,21 @@ function rewriteAssetPaths(pageHtml, config) {
|
|
|
11
11
|
const mediaDir = config.mediaDir.replace(/^\/+|\/+$/g, "");
|
|
12
12
|
return pageHtml
|
|
13
13
|
.replaceAll(`src="${mediaDir}/`, 'src="/openpress/media/')
|
|
14
|
-
.replaceAll(`src='${mediaDir}/`, "src='/openpress/media/")
|
|
14
|
+
.replaceAll(`src='${mediaDir}/`, "src='/openpress/media/")
|
|
15
|
+
.replaceAll('src="media/', 'src="/openpress/media/')
|
|
16
|
+
.replaceAll("src='media/", "src='/openpress/media/")
|
|
17
|
+
.replaceAll(`src="./${mediaDir}/`, 'src="/openpress/media/')
|
|
18
|
+
.replaceAll(`src='./${mediaDir}/`, "src='/openpress/media/")
|
|
19
|
+
.replaceAll('src="./media/', 'src="/openpress/media/')
|
|
20
|
+
.replaceAll("src='./media/", "src='/openpress/media/");
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
export function pageToBlock(index, pageHtml, source, config, { idPrefix = "openpress-page", anchorPrefix = "page", titleFallback = "Page" } = {}) {
|
|
18
24
|
const paddedIndex = String(index + 1).padStart(2, "0");
|
|
19
|
-
const title =
|
|
25
|
+
const title =
|
|
26
|
+
pageHtml.match(/data-page-title="([^"]*)"/)?.[1] ??
|
|
27
|
+
pageHtml.match(/data-openpress-frame-key="([^"]*)"/)?.[1] ??
|
|
28
|
+
`${titleFallback} ${index + 1}`;
|
|
20
29
|
const anchor = pageHtml.match(/\bid="([^"]+)"/)?.[1] ?? `${anchorPrefix}-${paddedIndex}`;
|
|
21
30
|
return {
|
|
22
31
|
id: `${idPrefix}-${paddedIndex}`,
|
|
@@ -1,19 +1,54 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { loadConfig } from "../runtime/config.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { writeComponentsCss, writeContentCss } from "../runtime/file-utils.mjs";
|
|
5
5
|
import { copyThemeFonts } from "./fonts.mjs";
|
|
6
6
|
import { copyKatexFonts } from "./katex-assets.mjs";
|
|
7
7
|
|
|
8
|
-
export async function syncPublicAssets(root, publicOutputDir, config) {
|
|
8
|
+
export async function syncPublicAssets(root, publicOutputDir, config, options = {}) {
|
|
9
9
|
config ??= await loadConfig(root);
|
|
10
10
|
await fs.rm(path.join(publicOutputDir, "report.css"), { force: true });
|
|
11
11
|
for (const name of ["tokens.css"]) {
|
|
12
|
-
await
|
|
12
|
+
await copyOptionalFile(path.join(config.paths.themeDir, name), path.join(publicOutputDir, name));
|
|
13
13
|
}
|
|
14
|
-
await writeContentCss(root, publicOutputDir, config);
|
|
14
|
+
await writeContentCss(root, publicOutputDir, config, { themeRoots: options.themeRoots });
|
|
15
15
|
await copyThemeFonts(root, publicOutputDir, config);
|
|
16
16
|
await copyKatexFonts(publicOutputDir);
|
|
17
|
-
await writeComponentsCss(root, publicOutputDir, config);
|
|
18
|
-
await
|
|
17
|
+
await writeComponentsCss(root, publicOutputDir, config, { componentRoots: options.componentRoots });
|
|
18
|
+
await copyMediaRoots(options.mediaRoots ?? [config.paths.mediaDir], path.join(publicOutputDir, "media"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function copyOptionalFile(src, dst) {
|
|
22
|
+
try {
|
|
23
|
+
await fs.copyFile(src, dst);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error?.code === "ENOENT") return;
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function copyMediaRoots(mediaRoots, dst) {
|
|
31
|
+
await fs.rm(dst, { recursive: true, force: true });
|
|
32
|
+
await fs.mkdir(dst, { recursive: true });
|
|
33
|
+
for (const mediaRoot of uniquePaths(mediaRoots)) {
|
|
34
|
+
try {
|
|
35
|
+
await fs.cp(mediaRoot, dst, { recursive: true, force: true });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error?.code === "ENOENT") continue;
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function uniquePaths(paths) {
|
|
44
|
+
const out = [];
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
for (const candidate of paths ?? []) {
|
|
47
|
+
if (!candidate) continue;
|
|
48
|
+
const normalized = path.resolve(candidate);
|
|
49
|
+
if (seen.has(normalized)) continue;
|
|
50
|
+
seen.add(normalized);
|
|
51
|
+
out.push(normalized);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
19
54
|
}
|
|
@@ -84,7 +84,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
84
84
|
res.writeHead(200, { "Content-Type": mimeTypes[path.extname(filePath)] ?? "application/octet-stream" });
|
|
85
85
|
res.end(body);
|
|
86
86
|
} catch (err) {
|
|
87
|
-
// SPA
|
|
87
|
+
// SPA HTML response: when a path doesn't map to a real file AND it
|
|
88
88
|
// looks like a client-side route (no extension, not under a
|
|
89
89
|
// reserved namespace), serve index.html so the reader's URL-based
|
|
90
90
|
// routing can take over. This lets /cheatsheet / /proposal etc.
|
|
@@ -181,9 +181,9 @@ function valueAfter(args, flag) {
|
|
|
181
181
|
|
|
182
182
|
async function inferWorkspaceRoot(staticRoot) {
|
|
183
183
|
for (const candidate of [staticRoot, path.dirname(staticRoot), path.dirname(path.dirname(staticRoot))]) {
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
if (await
|
|
184
|
+
// Workspace markers: folder-convention Press entries or package.json
|
|
185
|
+
// with an "openpress" field. Either is sufficient.
|
|
186
|
+
if (await hasFolderPressEntries(candidate)) return candidate;
|
|
187
187
|
if (await hasOpenpressPackageField(candidate)) return candidate;
|
|
188
188
|
}
|
|
189
189
|
if (path.basename(path.dirname(staticRoot)) === ".deploy") {
|
|
@@ -202,6 +202,20 @@ async function hasOpenpressPackageField(dir) {
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
async function hasFolderPressEntries(dir) {
|
|
206
|
+
let entries;
|
|
207
|
+
try {
|
|
208
|
+
entries = await fs.readdir(path.join(dir, "press"), { withFileTypes: true });
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
for (const entry of entries) {
|
|
213
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "shared") continue;
|
|
214
|
+
if (await fileExists(path.join(dir, "press", entry.name, "press.tsx"))) return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
205
219
|
async function handleLocalPdfExportRequest(req, res) {
|
|
206
220
|
if (req.method !== "POST") {
|
|
207
221
|
writeJson(res, 405, { ok: false, message: "Local PDF export endpoint requires POST." });
|
|
@@ -377,14 +391,12 @@ async function handleMediaFileRequest(req, res, url) {
|
|
|
377
391
|
writeJson(res, 404, { ok: false, message: "Media file not found." });
|
|
378
392
|
return;
|
|
379
393
|
}
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (!resolvedTarget.startsWith(`${mediaRoot}${path.sep}`) && resolvedTarget !== mediaRoot) {
|
|
384
|
-
writeJson(res, 403, { ok: false, message: "Forbidden." });
|
|
394
|
+
const mediaPath = await findMediaFile(fileName);
|
|
395
|
+
if (!mediaPath) {
|
|
396
|
+
writeJson(res, 404, { ok: false, message: "Media file not found." });
|
|
385
397
|
return;
|
|
386
398
|
}
|
|
387
|
-
const body = await fs.readFile(
|
|
399
|
+
const body = await fs.readFile(mediaPath);
|
|
388
400
|
res.writeHead(200, {
|
|
389
401
|
"Content-Type": mediaMimeType(fileName),
|
|
390
402
|
"Cache-Control": "no-store",
|
|
@@ -517,11 +529,7 @@ async function isDeploymentDirty(deployedAt) {
|
|
|
517
529
|
|
|
518
530
|
function getDeploymentSourcePaths() {
|
|
519
531
|
return [
|
|
520
|
-
config.paths.
|
|
521
|
-
config.paths.mediaDir,
|
|
522
|
-
config.paths.themeDir,
|
|
523
|
-
config.paths.designDoc,
|
|
524
|
-
config.paths.componentsDir,
|
|
532
|
+
config.paths.documentRoot,
|
|
525
533
|
path.join(FRAMEWORK_ROOT, "src"),
|
|
526
534
|
path.join(FRAMEWORK_ROOT, "index.html"),
|
|
527
535
|
path.join(FRAMEWORK_ROOT, "vite.config.ts"),
|
|
@@ -597,6 +605,51 @@ async function uniqueMediaFileName(mediaDir, fileName) {
|
|
|
597
605
|
return candidate;
|
|
598
606
|
}
|
|
599
607
|
|
|
608
|
+
async function findMediaFile(fileName) {
|
|
609
|
+
for (const mediaRoot of await collectMediaRoots()) {
|
|
610
|
+
const resolvedRoot = path.resolve(mediaRoot);
|
|
611
|
+
const candidate = path.resolve(mediaRoot, fileName);
|
|
612
|
+
if (!isInsideRoot(candidate, resolvedRoot)) continue;
|
|
613
|
+
if (await fileExists(candidate)) return candidate;
|
|
614
|
+
}
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function collectMediaRoots() {
|
|
619
|
+
const roots = [
|
|
620
|
+
config.paths.mediaDir,
|
|
621
|
+
path.join(config.paths.publicDir, "media"),
|
|
622
|
+
path.join(root, "openpress", "media"),
|
|
623
|
+
];
|
|
624
|
+
try {
|
|
625
|
+
const entries = await fs.readdir(config.paths.documentRoot, { withFileTypes: true });
|
|
626
|
+
for (const entry of entries) {
|
|
627
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "shared") continue;
|
|
628
|
+
roots.push(path.join(config.paths.documentRoot, entry.name, "media"));
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
631
|
+
// Missing press/ is handled by render/validate.
|
|
632
|
+
}
|
|
633
|
+
return uniquePaths(roots);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function uniquePaths(paths) {
|
|
637
|
+
const out = [];
|
|
638
|
+
const seen = new Set();
|
|
639
|
+
for (const candidate of paths) {
|
|
640
|
+
const normalized = path.resolve(candidate);
|
|
641
|
+
if (seen.has(normalized)) continue;
|
|
642
|
+
seen.add(normalized);
|
|
643
|
+
out.push(normalized);
|
|
644
|
+
}
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function isInsideRoot(candidate, rootDir) {
|
|
649
|
+
const relative = path.relative(rootDir, candidate);
|
|
650
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
651
|
+
}
|
|
652
|
+
|
|
600
653
|
function readRequestBuffer(req, maxBytes) {
|
|
601
654
|
return new Promise((resolve, reject) => {
|
|
602
655
|
const chunks = [];
|
|
@@ -64,8 +64,8 @@ function attrValue(attrs, name) {
|
|
|
64
64
|
return attrs.match(pattern)?.[2] ?? "";
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function stringOption(value,
|
|
68
|
-
return typeof value === "string" && value.trim() ? value.trim() :
|
|
67
|
+
function stringOption(value, defaultValue) {
|
|
68
|
+
return typeof value === "string" && value.trim() ? value.trim() : defaultValue;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function escapeHtml(value) {
|
|
@@ -6,8 +6,7 @@ import { collectSourceTextFiles } from "../runtime/source-text-tools.mjs";
|
|
|
6
6
|
|
|
7
7
|
// Any `.mdx` or `.tsx` file under `press/` is a legal comment target.
|
|
8
8
|
// The Press Tree allows arbitrary source layouts — `section-folders`,
|
|
9
|
-
// `section-files`, `file-list`, custom `root` paths, etc.
|
|
10
|
-
// longer hardcode `press/chapters/<slug>/content/*.mdx`. The boundary
|
|
9
|
+
// `section-files`, `file-list`, custom `root` paths, etc. The boundary
|
|
11
10
|
// is "inside the workspace's authored `press/` directory" and "looks
|
|
12
11
|
// like an editable React/MDX source" by extension.
|
|
13
12
|
const EDITABLE_COMMENT_SOURCE_PATTERNS = [
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// Layer 1 — Document entry loader.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// subpaths `/mdx` and `/manuscript`).
|
|
3
|
+
// Discovers `press/*/press.tsx`, generates an internal Workspace entry,
|
|
4
|
+
// and sets up the vite SSR server with `@open-press/core` aliases
|
|
5
|
+
// (including the subpaths `/mdx` and `/manuscript`).
|
|
7
6
|
|
|
8
7
|
import fs from "node:fs/promises";
|
|
9
8
|
import { createRequire } from "node:module";
|
|
@@ -26,11 +25,61 @@ const REACT_PACKAGE_ROOT = path.join(FRAMEWORK_ROOT, "node_modules", "react");
|
|
|
26
25
|
const require = createRequire(import.meta.url);
|
|
27
26
|
const REACT_EXPORT_NAMES = Object.keys(require("react")).filter((name) => /^[A-Za-z_$][\w$]*$/.test(name));
|
|
28
27
|
|
|
29
|
-
// 1.0 contract: the document entry lives at press/index.tsx.
|
|
30
28
|
async function resolveEntryPath(workspaceRoot) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
return createDiscoveredPressEntry(workspaceRoot);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function createDiscoveredPressEntry(workspaceRoot) {
|
|
33
|
+
const pressRoot = path.join(workspaceRoot, "press");
|
|
34
|
+
let entries = [];
|
|
35
|
+
try {
|
|
36
|
+
const children = await fs.readdir(pressRoot, { withFileTypes: true });
|
|
37
|
+
for (const child of children) {
|
|
38
|
+
if (!child.isDirectory()) continue;
|
|
39
|
+
if (child.name === "shared" || child.name.startsWith(".")) continue;
|
|
40
|
+
const entryPath = path.join(pressRoot, child.name, "press.tsx");
|
|
41
|
+
if (await fileExists(entryPath)) entries.push({ folder: child.name, entryPath });
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
entries = entries.sort((a, b) => a.folder.localeCompare(b.folder));
|
|
48
|
+
if (entries.length === 0) return null;
|
|
49
|
+
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
const source = await fs.readFile(entry.entryPath, "utf8");
|
|
52
|
+
assertNoObviousTopLevelSideEffects(source, entry.entryPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const generatedDir = path.join(workspaceRoot, ".openpress", "react");
|
|
56
|
+
await fs.mkdir(generatedDir, { recursive: true });
|
|
57
|
+
const generatedEntry = path.join(generatedDir, "discovered-press-entry.tsx");
|
|
58
|
+
const imports = entries
|
|
59
|
+
.map((entry, index) => `import Press${index} from "${relativeImportPath(generatedDir, entry.entryPath)}";`)
|
|
60
|
+
.join("\n");
|
|
61
|
+
const children = entries.map((_, index) => ` <Press${index} />`).join("\n");
|
|
62
|
+
const source = `import { Workspace } from "@open-press/core";
|
|
63
|
+
${imports}
|
|
64
|
+
|
|
65
|
+
export const __openpressPressFolders = ${JSON.stringify(entries.map((entry) => entry.folder))};
|
|
66
|
+
|
|
67
|
+
export default function DiscoveredOpenPressWorkspace() {
|
|
68
|
+
return (
|
|
69
|
+
<Workspace>
|
|
70
|
+
${children}
|
|
71
|
+
</Workspace>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
await fs.writeFile(generatedEntry, source, "utf8");
|
|
76
|
+
return generatedEntry;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function relativeImportPath(fromDir, toFile) {
|
|
80
|
+
let relative = path.relative(fromDir, toFile).replaceAll(path.sep, "/");
|
|
81
|
+
if (!relative.startsWith(".")) relative = `./${relative}`;
|
|
82
|
+
return relative;
|
|
34
83
|
}
|
|
35
84
|
|
|
36
85
|
export async function loadReactDocumentEntry(root = ".", { server: externalServer } = {}) {
|
|
@@ -82,6 +131,9 @@ export async function loadReactDocumentEntry(root = ".", { server: externalServe
|
|
|
82
131
|
workspaceProps: inspection.workspaceProps,
|
|
83
132
|
pressCount: inspection.presses.length,
|
|
84
133
|
wrappedInWorkspace: inspection.wrappedInWorkspace,
|
|
134
|
+
pressFolders: Array.isArray(mod.__openpressPressFolders)
|
|
135
|
+
? mod.__openpressPressFolders.filter((item) => typeof item === "string")
|
|
136
|
+
: [],
|
|
85
137
|
};
|
|
86
138
|
} finally {
|
|
87
139
|
if (!externalServer) await ownServer.close();
|
|
@@ -112,7 +164,7 @@ export async function createReactSsrServer(workspaceRoot = ".") {
|
|
|
112
164
|
{ find: "@open-press/core/manuscript", replacement: MANUSCRIPT_ENTRY },
|
|
113
165
|
{ find: "@open-press/core/numbering", replacement: NUMBERING_ENTRY },
|
|
114
166
|
{ find: "@open-press/core", replacement: CORE_ENTRY },
|
|
115
|
-
{ find: "@/components", replacement: path.join(resolvedWorkspaceRoot, "press", "components") },
|
|
167
|
+
{ find: "@/components", replacement: path.join(resolvedWorkspaceRoot, "press", "shared", "components") },
|
|
116
168
|
],
|
|
117
169
|
},
|
|
118
170
|
optimizeDeps: {
|
|
@@ -159,10 +211,11 @@ function assertNoObviousTopLevelSideEffects(source, entryPath) {
|
|
|
159
211
|
}
|
|
160
212
|
|
|
161
213
|
function assertPureImport(statement, entryPath) {
|
|
214
|
+
const moduleName = stringLiteralText(statement.moduleSpecifier);
|
|
162
215
|
if (!statement.importClause) {
|
|
216
|
+
if (typeof moduleName === "string" && moduleName.endsWith(".css")) return;
|
|
163
217
|
throw new Error(`OpenPress document entry has an unsupported side-effect import in ${entryPath}`);
|
|
164
218
|
}
|
|
165
|
-
const moduleName = stringLiteralText(statement.moduleSpecifier);
|
|
166
219
|
if (!statement.importClause.isTypeOnly && isFileSystemModule(moduleName)) {
|
|
167
220
|
throw new Error(`OpenPress document entry imports filesystem APIs at top level in ${entryPath}`);
|
|
168
221
|
}
|
|
@@ -179,7 +232,7 @@ function assertTopLevelVariableStatement(statement, entryPath) {
|
|
|
179
232
|
throw new Error(`OpenPress document entry only allows identifier const declarations at top level in ${entryPath}`);
|
|
180
233
|
}
|
|
181
234
|
const name = declaration.name.text;
|
|
182
|
-
if (exported && name !== "config" && name !== "sources") {
|
|
235
|
+
if (exported && name !== "config" && name !== "sources" && name !== "__openpressPressFolders") {
|
|
183
236
|
throw new Error(`OpenPress document entry only allows exported const config and sources in ${entryPath}`);
|
|
184
237
|
}
|
|
185
238
|
if (!declaration.initializer) {
|