@open-press/core 0.8.0 → 1.1.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/README.md +17 -5
- package/engine/cli.mjs +9 -9
- package/engine/commands/_shared.mjs +70 -18
- package/engine/commands/deploy.mjs +3 -3
- package/engine/commands/dev.mjs +13 -4
- package/engine/commands/image.mjs +29 -0
- package/engine/commands/inspect.mjs +3 -2
- package/engine/commands/pdf.mjs +2 -2
- package/engine/commands/preview.mjs +2 -2
- package/engine/commands/render.mjs +6 -4
- package/engine/commands/replace.mjs +1 -1
- package/engine/commands/search.mjs +1 -1
- package/engine/commands/skills-sync.mjs +71 -0
- package/engine/commands/typecheck.mjs +71 -1
- package/engine/commands/upgrade.mjs +3 -3
- package/engine/document-export.mjs +1 -1
- package/engine/output/chrome-pdf.mjs +92 -0
- package/engine/output/static-server.mjs +60 -17
- package/engine/react/comment-marker.mjs +13 -13
- package/engine/react/document-entry.mjs +35 -28
- package/engine/react/document-export.mjs +309 -170
- package/engine/react/mdx-compile.mjs +30 -0
- package/engine/react/measurement-css.mjs +21 -0
- package/engine/react/object-entities.mjs +85 -0
- package/engine/react/pagination/allocator.mjs +48 -3
- package/engine/react/pagination.mjs +1 -1
- package/engine/react/pipeline/allocate.mjs +31 -65
- package/engine/react/pipeline/frame-measurement.mjs +4 -0
- package/engine/react/press-tree-inspection.mjs +172 -0
- package/engine/react/sources/mdx-resolver.mjs +1 -1
- package/engine/react/style-discovery.mjs +22 -4
- package/engine/runtime/config.d.mts +8 -0
- package/engine/runtime/config.mjs +57 -60
- package/engine/runtime/file-utils.mjs +9 -1
- package/engine/runtime/page-geometry.mjs +131 -0
- package/engine/runtime/source-text-tools.mjs +1 -1
- package/engine/runtime/source-workspace.mjs +12 -3
- package/engine/runtime/validation.mjs +19 -10
- package/index.html +4 -0
- package/package.json +9 -12
- package/src/main.tsx +16 -0
- package/src/openpress/app/OpenPressApp.tsx +173 -17
- package/src/openpress/app/OpenPressRuntime.tsx +10 -2
- package/src/openpress/app/WorkspaceGalleryPage.tsx +219 -0
- package/src/openpress/core/Frame.tsx +20 -7
- package/src/openpress/core/FrameContext.tsx +2 -0
- package/src/openpress/core/Press.tsx +25 -4
- package/src/openpress/core/Workspace.tsx +36 -0
- package/src/openpress/core/index.tsx +10 -3
- package/src/openpress/core/primitives.tsx +48 -1
- package/src/openpress/core/types.ts +86 -41
- package/src/openpress/core/useSource.ts +1 -1
- package/src/openpress/document-model/documentTypes.ts +9 -0
- package/src/openpress/document-model/index.ts +1 -0
- package/src/openpress/document-model/objectEntityModel.ts +4 -0
- package/src/openpress/document-model/workspaceManifestModel.ts +57 -0
- package/src/openpress/mdx/index.ts +15 -7
- package/src/openpress/reader/PageThumbnailsPanel.tsx +168 -0
- package/src/openpress/reader/index.ts +1 -0
- package/src/openpress/workbench/Workbench.tsx +120 -21
- package/src/openpress/workbench/actions/ExportImageControl.tsx +96 -0
- package/src/openpress/workbench/actions/SearchControl.tsx +3 -3
- package/src/openpress/workbench/actions/index.ts +1 -0
- package/src/openpress/workbench/inspector/useInspectorComments.ts +7 -1
- package/src/openpress/workbench/panels/PendingCommentsPanel.tsx +5 -1
- package/src/openpress/workbench/project/ProjectEntryPanel.tsx +4 -2
- package/src/openpress/workbench/project/projectSourceModel.ts +2 -2
- package/src/openpress/workbench/workbenchFormatters.ts +2 -2
- package/src/styles/openpress/reader-runtime.css +9 -0
- package/src/styles/openpress/workbench-panels.css +113 -0
- package/src/styles/openpress/workspace-gallery.css +300 -0
- package/src/styles/openpress.css +1 -5
- package/src/vite-env.d.ts +8 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +6 -6
- package/engine/commands/init.mjs +0 -24
- package/engine/init.mjs +0 -90
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# @open-press/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Package-owned runtime, render 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
|
|
|
7
7
|
```bash
|
|
8
|
-
npx @open-press/cli init my-doc
|
|
8
|
+
npx @open-press/cli init my-doc
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The scaffolded workspace
|
|
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.
|
|
12
12
|
|
|
13
13
|
## Direct use
|
|
14
14
|
|
|
@@ -31,9 +31,21 @@ import { mdxSource } from "@open-press/core/mdx";
|
|
|
31
31
|
import { Sections, Toc } from "@open-press/core/manuscript";
|
|
32
32
|
```
|
|
33
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.
|
|
34
|
+
`press/index.tsx` or transitional `document/index.tsx` default-exports a `<Workspace>/<Press>` tree. `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
|
+
allocation pipeline, see [`docs/press-tree.md`](https://github.com/quan0715/open-press/blob/main/docs/press-tree.md).
|
|
38
|
+
|
|
39
|
+
The public CLI bin lives in `@open-press/cli` and delegates runtime commands to this package:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @open-press/core @open-press/cli
|
|
43
|
+
npx open-press dev .
|
|
44
|
+
npx open-press render .
|
|
45
|
+
npx open-press pdf .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`@open-press/core` owns the internal browser shell (`index.html`), Vite config, React app runtime, render pipeline, and static server. A workspace owns `press/`, `package.json`, media, components, and theme files.
|
|
37
49
|
|
|
38
50
|
## License
|
|
39
51
|
|
package/engine/cli.mjs
CHANGED
|
@@ -4,25 +4,25 @@ import * as deployCmd from "./commands/deploy.mjs";
|
|
|
4
4
|
import * as devCmd from "./commands/dev.mjs";
|
|
5
5
|
import * as doctorCmd from "./commands/doctor.mjs";
|
|
6
6
|
import * as exportCmd from "./commands/export.mjs";
|
|
7
|
-
import * as initCmd from "./commands/init.mjs";
|
|
8
7
|
import * as inspectCmd from "./commands/inspect.mjs";
|
|
8
|
+
import * as imageCmd from "./commands/image.mjs";
|
|
9
9
|
import * as pdfCmd from "./commands/pdf.mjs";
|
|
10
10
|
import * as previewCmd from "./commands/preview.mjs";
|
|
11
11
|
import * as replaceCmd from "./commands/replace.mjs";
|
|
12
12
|
import * as renderCmd from "./commands/render.mjs";
|
|
13
13
|
import * as searchCmd from "./commands/search.mjs";
|
|
14
|
+
import * as skillsSyncCmd from "./commands/skills-sync.mjs";
|
|
14
15
|
import * as typecheckCmd from "./commands/typecheck.mjs";
|
|
15
16
|
import * as upgradeCmd from "./commands/upgrade.mjs";
|
|
16
17
|
import * as validateCmd from "./commands/validate.mjs";
|
|
17
18
|
import { parseOptions } from "./commands/_shared.mjs";
|
|
18
19
|
import { loadConfig } from "./runtime/config.mjs";
|
|
19
|
-
import { listStylePackSkills } from "./init.mjs";
|
|
20
20
|
import { discoverWorkspace } from "./runtime/validation.mjs";
|
|
21
21
|
|
|
22
22
|
const COMMANDS = {
|
|
23
|
-
init: initCmd,
|
|
24
23
|
validate: validateCmd,
|
|
25
24
|
inspect: inspectCmd,
|
|
25
|
+
image: imageCmd,
|
|
26
26
|
search: searchCmd,
|
|
27
27
|
replace: replaceCmd,
|
|
28
28
|
export: exportCmd,
|
|
@@ -34,6 +34,8 @@ const COMMANDS = {
|
|
|
34
34
|
deploy: deployCmd,
|
|
35
35
|
doctor: doctorCmd,
|
|
36
36
|
upgrade: upgradeCmd,
|
|
37
|
+
migrate: upgradeCmd,
|
|
38
|
+
"skills:sync": skillsSyncCmd,
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
const args = process.argv.slice(2);
|
|
@@ -71,12 +73,9 @@ async function main(commandName, argv) {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
async function printHelp() {
|
|
74
|
-
|
|
75
|
-
const skillList = packs.length ? packs.join(" | ") : "(none installed)";
|
|
76
|
-
console.log(`Usage: node engine/cli.mjs <command> [path] [options]
|
|
76
|
+
console.log(`Usage: open-press <command> [path] [options]
|
|
77
77
|
|
|
78
78
|
Commands:
|
|
79
|
-
init <target> [--skill <name>] [--force]
|
|
80
79
|
validate
|
|
81
80
|
inspect [--json] [--no-build] [--dry-run]
|
|
82
81
|
search [path] <query> [--json] [--scope content|all]
|
|
@@ -86,11 +85,12 @@ Commands:
|
|
|
86
85
|
preview --renderer react [--host 127.0.0.1] [--port 5173] [--no-build] [--dry-run]
|
|
87
86
|
dev --renderer react [--host 127.0.0.1] [--port 5173] [--no-build] [--dry-run]
|
|
88
87
|
typecheck
|
|
88
|
+
image [--output <outputDir>] [--no-build] [--dry-run]
|
|
89
89
|
pdf [--output <outputDir>/<pdf.filename>] [--no-build] [--dry-run]
|
|
90
90
|
deploy --confirm [--dry-run]
|
|
91
91
|
doctor [--json] [--no-cache] # version + skill staleness check
|
|
92
92
|
upgrade [--dry-run] [--no-deps] [--no-skills] [--json] # apply updates; agent-driven
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
migrate [--dry-run] [--no-deps] [--no-skills] [--json] # alias for upgrade; reads migration notes
|
|
94
|
+
skills:sync [--source <owner/repo>] [--dry-run] # refresh installed agent skills
|
|
95
95
|
`);
|
|
96
96
|
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { printUrlToPdf, stopChildProcess, waitForPrintReady } from "../output/chrome-pdf.mjs";
|
|
6
|
+
import { captureUrlPagesToPng, printUrlToPdf, stopChildProcess, waitForPrintReady } from "../output/chrome-pdf.mjs";
|
|
6
7
|
import { loadConfig, publicPdfHref } from "../runtime/config.mjs";
|
|
7
8
|
import { exportDocument } from "../document-export.mjs";
|
|
8
9
|
import { optimizePdfMediaForStaticRoot } from "../output/pdf-media.mjs";
|
|
9
10
|
|
|
10
11
|
export const ENGINE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
export const FRAMEWORK_ROOT = path.resolve(ENGINE_DIR, "..");
|
|
11
13
|
export const CLI_ENTRY = path.join(ENGINE_DIR, "cli.mjs");
|
|
12
14
|
export const STATIC_SERVER = path.join(ENGINE_DIR, "output", "static-server.mjs");
|
|
15
|
+
export const VITE_CONFIG = path.join(FRAMEWORK_ROOT, "vite.config.ts");
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const VITE_PACKAGE_JSON = require.resolve("vite/package.json");
|
|
19
|
+
export const VITE_BIN = path.join(path.dirname(VITE_PACKAGE_JSON), "bin", "vite.js");
|
|
13
20
|
|
|
14
21
|
export function parseOptions(argv) {
|
|
15
22
|
const options = {};
|
|
@@ -23,6 +30,9 @@ export function parseOptions(argv) {
|
|
|
23
30
|
else if (value === "--force") options.force = true;
|
|
24
31
|
else if (value === "--confirm") options.confirm = true;
|
|
25
32
|
else if (value === "--json") options.json = true;
|
|
33
|
+
else if (value === "--no-cache") options.noCache = true;
|
|
34
|
+
else if (value === "--no-deps") options.noDeps = true;
|
|
35
|
+
else if (value === "--no-skills") options.noSkills = true;
|
|
26
36
|
else if (value === "--no-build") options.noBuild = true;
|
|
27
37
|
else if (value === "--apply") options.apply = true;
|
|
28
38
|
else if (value === "--include-code") options.includeCode = true;
|
|
@@ -38,28 +48,18 @@ export function parseOptions(argv) {
|
|
|
38
48
|
return options;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
export function parseInitOptions(argv) {
|
|
42
|
-
const options = { force: false };
|
|
43
|
-
const positional = [];
|
|
44
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
45
|
-
const value = argv[i];
|
|
46
|
-
if (value === "--skill") options.skill = argv[++i];
|
|
47
|
-
else if (value === "--force") options.force = true;
|
|
48
|
-
else if (value.startsWith("--")) throw new Error(`Unknown option: ${value}`);
|
|
49
|
-
else positional.push(value);
|
|
50
|
-
}
|
|
51
|
-
options.target = positional[0];
|
|
52
|
-
return options;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
51
|
export function formatDisplayPath(absolutePath) {
|
|
56
52
|
const relative = path.relative(process.cwd(), absolutePath);
|
|
57
53
|
if (!relative || relative.startsWith("..")) return absolutePath;
|
|
58
54
|
return relative;
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
export function runCommand(commandName, commandArgs, cwd) {
|
|
62
|
-
const result = spawnSync(commandName, commandArgs, {
|
|
57
|
+
export function runCommand(commandName, commandArgs, cwd, opts = {}) {
|
|
58
|
+
const result = spawnSync(commandName, commandArgs, {
|
|
59
|
+
cwd,
|
|
60
|
+
env: { ...process.env, ...(opts.env ?? {}) },
|
|
61
|
+
stdio: "inherit",
|
|
62
|
+
});
|
|
63
63
|
return result.status ?? 1;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -69,6 +69,24 @@ export function formatNodeScriptCommand(root, scriptPath) {
|
|
|
69
69
|
return `node ${displayPath}`;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export function formatOpenPressCommand(args = []) {
|
|
73
|
+
return `open-press ${args.join(" ")}`.trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function workspaceRuntimeEnv(root) {
|
|
77
|
+
return { OPENPRESS_WORKSPACE_ROOT: path.resolve(root) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function viteCommandArgs(args = []) {
|
|
81
|
+
return [VITE_BIN, ...args];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function formatViteCommand(root, args = []) {
|
|
85
|
+
const script = formatNodeScriptCommand(root, VITE_BIN);
|
|
86
|
+
const config = formatDisplayPath(VITE_CONFIG);
|
|
87
|
+
return `${script} ${args.join(" ")} --config ${config}`.replace(/\s+/g, " ").trim();
|
|
88
|
+
}
|
|
89
|
+
|
|
72
90
|
export async function buildReactStatic({ root, noBuild = false, recurse, silent = false }) {
|
|
73
91
|
if (noBuild) return 0;
|
|
74
92
|
if (!silent) {
|
|
@@ -76,9 +94,10 @@ export async function buildReactStatic({ root, noBuild = false, recurse, silent
|
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
await exportDocument(root);
|
|
79
|
-
const result = spawnSync("
|
|
97
|
+
const result = spawnSync("node", viteCommandArgs(["build", "--config", VITE_CONFIG]), {
|
|
80
98
|
cwd: root,
|
|
81
99
|
encoding: "utf8",
|
|
100
|
+
env: { ...process.env, ...workspaceRuntimeEnv(root) },
|
|
82
101
|
});
|
|
83
102
|
return result.status ?? 1;
|
|
84
103
|
}
|
|
@@ -118,6 +137,39 @@ export async function buildReactPdf({
|
|
|
118
137
|
return { pdfPath: outPath };
|
|
119
138
|
}
|
|
120
139
|
|
|
140
|
+
export async function buildReactImages({
|
|
141
|
+
root,
|
|
142
|
+
config,
|
|
143
|
+
outDir,
|
|
144
|
+
host = "127.0.0.1",
|
|
145
|
+
port = "5186",
|
|
146
|
+
noBuild = false,
|
|
147
|
+
recurse,
|
|
148
|
+
}) {
|
|
149
|
+
config ??= await loadConfig(root);
|
|
150
|
+
outDir ??= path.join(config.paths.outputDir, "images");
|
|
151
|
+
const renderCode = await buildReactStatic({ root, noBuild, recurse });
|
|
152
|
+
if (renderCode !== 0) throw new Error(`React render failed with exit code ${renderCode}`);
|
|
153
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
154
|
+
|
|
155
|
+
const server = await startStaticServer(root, config, host, port);
|
|
156
|
+
try {
|
|
157
|
+
const result = await captureUrlPagesToPng({
|
|
158
|
+
root,
|
|
159
|
+
url: `http://${host}:${port}/?print=1`,
|
|
160
|
+
outDir,
|
|
161
|
+
waitForReady: waitForPrintReady,
|
|
162
|
+
debuggingPortBase: 9700,
|
|
163
|
+
debuggingPortRange: 600,
|
|
164
|
+
profilePrefix: "chrome-image",
|
|
165
|
+
});
|
|
166
|
+
console.log(`${result.files.length} OpenPress pages exported to PNG`);
|
|
167
|
+
return { outDir, files: result.files, pageCount: result.pageCount };
|
|
168
|
+
} finally {
|
|
169
|
+
await stopChildProcess(server);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
121
173
|
export function startStaticServer(root, config, host, port) {
|
|
122
174
|
return new Promise((resolve, reject) => {
|
|
123
175
|
const child = spawn("node", [STATIC_SERVER, config.outputDir, "--host", host, "--port", port, "--workspace", "."], {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { deploySync } from "../output/deploy-sync.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { buildReactPdf, formatOpenPressCommand, runCommand, writePdfStageDeployConfig } from "./_shared.mjs";
|
|
4
4
|
|
|
5
5
|
export async function run({ root, config, options, recurse }) {
|
|
6
6
|
if (config.deploy.requiresConfirmation === true && !options.confirm) {
|
|
@@ -12,9 +12,9 @@ export async function run({ root, config, options, recurse }) {
|
|
|
12
12
|
const commitDirty = config.deploy.commitDirty;
|
|
13
13
|
if (options.dryRun) {
|
|
14
14
|
console.log("OpenPress deploy dry run");
|
|
15
|
-
console.log(`Command: ${
|
|
15
|
+
console.log(`Command: ${formatOpenPressCommand(["render", ".", "--renderer", "react"])}`);
|
|
16
16
|
console.log(`Step: deploy-sync (copy ${config.outputDir} → ${source})`);
|
|
17
|
-
console.log(`Command: ${
|
|
17
|
+
console.log(`Command: ${formatOpenPressCommand(["pdf", ".", "--output", `${source}/${config.pdf.filename}`])}`);
|
|
18
18
|
console.log(`Step: write ${source}/openpress/deploy.json with deployment metadata`);
|
|
19
19
|
console.log(`Command: npx wrangler pages deploy ${source}${projectName ? ` --project-name=${projectName}` : ""}${commitDirty ? " --commit-dirty=true" : ""}`);
|
|
20
20
|
return 0;
|
package/engine/commands/dev.mjs
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { exportDocument } from "../document-export.mjs";
|
|
2
2
|
import { diagnose } from "./doctor.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
VITE_CONFIG,
|
|
5
|
+
formatOpenPressCommand,
|
|
6
|
+
formatViteCommand,
|
|
7
|
+
runCommand,
|
|
8
|
+
viteCommandArgs,
|
|
9
|
+
workspaceRuntimeEnv,
|
|
10
|
+
} from "./_shared.mjs";
|
|
4
11
|
|
|
5
12
|
export async function run({ root, options }) {
|
|
6
13
|
const renderer = options.renderer ?? "react";
|
|
@@ -14,9 +21,9 @@ export async function run({ root, options }) {
|
|
|
14
21
|
if (options.dryRun) {
|
|
15
22
|
console.log(`OpenPress dev URL: ${url}`);
|
|
16
23
|
if (!options.noBuild) {
|
|
17
|
-
console.log(`Command: ${
|
|
24
|
+
console.log(`Command: ${formatOpenPressCommand(["export", "."])}`);
|
|
18
25
|
}
|
|
19
|
-
console.log(`Command:
|
|
26
|
+
console.log(`Command: ${formatViteCommand(root, ["--force", "--host", host, "--port", port])}`);
|
|
20
27
|
return 0;
|
|
21
28
|
}
|
|
22
29
|
if (!options.noBuild) {
|
|
@@ -27,7 +34,9 @@ export async function run({ root, options }) {
|
|
|
27
34
|
await printDoctorNoticeIfStale(root);
|
|
28
35
|
|
|
29
36
|
console.log(`OpenPress dev: ${url}`);
|
|
30
|
-
return runCommand("
|
|
37
|
+
return runCommand("node", viteCommandArgs(["--force", "--config", VITE_CONFIG, "--host", host, "--port", port]), root, {
|
|
38
|
+
env: workspaceRuntimeEnv(root),
|
|
39
|
+
});
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
async function printDoctorNoticeIfStale(root) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { STATIC_SERVER, buildReactImages, formatNodeScriptCommand, formatOpenPressCommand } from "./_shared.mjs";
|
|
3
|
+
|
|
4
|
+
export async function run({ root, config, options, recurse }) {
|
|
5
|
+
const outputDir = options.output ? path.resolve(root, options.output) : path.join(config.paths.outputDir, "images");
|
|
6
|
+
const host = options.host ?? "127.0.0.1";
|
|
7
|
+
const port = options.port ?? "5186";
|
|
8
|
+
|
|
9
|
+
if (options.dryRun) {
|
|
10
|
+
console.log(`Command: ${formatOpenPressCommand(["render", ".", "--renderer", "react"])}`);
|
|
11
|
+
console.log(`Command: ${formatNodeScriptCommand(root, STATIC_SERVER)} ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
12
|
+
console.log(`Chrome image export URL: http://${host}:${port}/?print=1`);
|
|
13
|
+
console.log(`Output: ${path.relative(root, path.join(outputDir, "page-001.png"))}`);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = await buildReactImages({
|
|
18
|
+
root,
|
|
19
|
+
config,
|
|
20
|
+
outDir: outputDir,
|
|
21
|
+
host,
|
|
22
|
+
port,
|
|
23
|
+
noBuild: options.noBuild,
|
|
24
|
+
recurse,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(`OpenPress images: ${path.relative(root, result.outDir)} (${result.files.length} pages)`);
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { inspectWorkspace } from "../runtime/inspection.mjs";
|
|
2
2
|
import { exitCodeForIssueReport } from "../runtime/issue-report.mjs";
|
|
3
|
+
import { STATIC_SERVER, formatNodeScriptCommand, formatOpenPressCommand } from "./_shared.mjs";
|
|
3
4
|
|
|
4
5
|
export async function run({ root, config, options, recurse }) {
|
|
5
6
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -8,9 +9,9 @@ export async function run({ root, config, options, recurse }) {
|
|
|
8
9
|
|
|
9
10
|
if (options.dryRun) {
|
|
10
11
|
if (!options.noBuild) {
|
|
11
|
-
console.log(
|
|
12
|
+
console.log(`Command: ${formatOpenPressCommand(["render", ".", "--renderer", "react"])}`);
|
|
12
13
|
}
|
|
13
|
-
console.log(`Command:
|
|
14
|
+
console.log(`Command: ${formatNodeScriptCommand(root, STATIC_SERVER)} ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
14
15
|
console.log(`Chrome inspection URL: ${url}`);
|
|
15
16
|
return 0;
|
|
16
17
|
}
|
package/engine/commands/pdf.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { STATIC_SERVER, buildReactPdf, formatNodeScriptCommand, formatOpenPressCommand } from "./_shared.mjs";
|
|
3
3
|
|
|
4
4
|
export async function run({ root, config, options, recurse }) {
|
|
5
5
|
const outputPath = options.output ? path.resolve(root, options.output) : undefined;
|
|
@@ -7,7 +7,7 @@ export async function run({ root, config, options, recurse }) {
|
|
|
7
7
|
const relOutput = path.relative(root, outputPath ?? config.paths.pdf);
|
|
8
8
|
const host = options.host ?? "127.0.0.1";
|
|
9
9
|
const port = options.port ?? "5185";
|
|
10
|
-
console.log(`Command: ${
|
|
10
|
+
console.log(`Command: ${formatOpenPressCommand(["render", ".", "--renderer", "react"])}`);
|
|
11
11
|
console.log(`Command: ${formatNodeScriptCommand(root, STATIC_SERVER)} ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
12
12
|
console.log(`Command: Chrome --print-to-pdf=${relOutput} http://${host}:${port}/?print=1`);
|
|
13
13
|
return 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { STATIC_SERVER, formatNodeScriptCommand, formatOpenPressCommand, runCommand } from "./_shared.mjs";
|
|
2
2
|
|
|
3
3
|
export async function run({ root, config, options, recurse }) {
|
|
4
4
|
const renderer = options.renderer ?? "react";
|
|
@@ -12,7 +12,7 @@ export async function run({ root, config, options, recurse }) {
|
|
|
12
12
|
if (options.dryRun) {
|
|
13
13
|
console.log(`OpenPress preview URL: ${url}`);
|
|
14
14
|
if (!options.noBuild) {
|
|
15
|
-
console.log(`Command: ${
|
|
15
|
+
console.log(`Command: ${formatOpenPressCommand(["render", ".", "--renderer", "react"])}`);
|
|
16
16
|
}
|
|
17
17
|
console.log(`Command: ${formatNodeScriptCommand(root, STATIC_SERVER)} ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
18
18
|
return 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { exportDocument } from "../document-export.mjs";
|
|
2
|
-
import { runCommand } from "./_shared.mjs";
|
|
2
|
+
import { VITE_CONFIG, formatOpenPressCommand, formatViteCommand, runCommand, viteCommandArgs, workspaceRuntimeEnv } from "./_shared.mjs";
|
|
3
3
|
|
|
4
4
|
export async function run({ root, options }) {
|
|
5
5
|
const renderer = options.renderer ?? "react";
|
|
@@ -8,10 +8,12 @@ export async function run({ root, options }) {
|
|
|
8
8
|
return 2;
|
|
9
9
|
}
|
|
10
10
|
if (options.dryRun) {
|
|
11
|
-
console.log(
|
|
12
|
-
console.log(
|
|
11
|
+
console.log(`Command: ${formatOpenPressCommand(["export", "."])}`);
|
|
12
|
+
console.log(`Command: ${formatViteCommand(root, ["build"])}`);
|
|
13
13
|
return 0;
|
|
14
14
|
}
|
|
15
15
|
await exportDocument(root);
|
|
16
|
-
return runCommand("
|
|
16
|
+
return runCommand("node", viteCommandArgs(["build", "--config", VITE_CONFIG]), root, {
|
|
17
|
+
env: workspaceRuntimeEnv(root),
|
|
18
|
+
});
|
|
17
19
|
}
|
|
@@ -3,7 +3,7 @@ import { replaceSourceText } from "../runtime/source-text-tools.mjs";
|
|
|
3
3
|
export async function run({ config, options }) {
|
|
4
4
|
const args = replaceArgsFromOptions(options);
|
|
5
5
|
if (!args) {
|
|
6
|
-
console.error("Usage:
|
|
6
|
+
console.error("Usage: open-press replace [path] <from> <to> [--json] [--apply] [--scope content|all] [--include-code] [--case-sensitive]");
|
|
7
7
|
return 2;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ import { searchSourceText } from "../runtime/source-text-tools.mjs";
|
|
|
3
3
|
export async function run({ config, options }) {
|
|
4
4
|
const query = searchQueryFromOptions(options);
|
|
5
5
|
if (!query) {
|
|
6
|
-
console.error("Usage:
|
|
6
|
+
console.error("Usage: open-press search [path] <query> [--json] [--scope content|all] [--case-sensitive]");
|
|
7
7
|
return 2;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { runCommand } from "./_shared.mjs";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_SOURCE = "quan0715/open-press";
|
|
7
|
+
|
|
8
|
+
// Refresh installed agent skills against the workspace's lock file.
|
|
9
|
+
// Behavior:
|
|
10
|
+
// - If skills-lock.json exists, run `npx skills upgrade` (refreshes all
|
|
11
|
+
// currently-installed sources to their latest published versions).
|
|
12
|
+
// - If skills-lock.json is missing, install the OpenPress framework
|
|
13
|
+
// skill bundle (and any user-supplied --source) as a first-time setup.
|
|
14
|
+
// - If a --source flag is passed, also add that source on top of any
|
|
15
|
+
// existing installations.
|
|
16
|
+
//
|
|
17
|
+
// Always exits 0 unless the underlying `skills` tool fails.
|
|
18
|
+
export async function run({ root, options }) {
|
|
19
|
+
const lockPath = path.join(root, "skills-lock.json");
|
|
20
|
+
const lockExists = existsSync(lockPath);
|
|
21
|
+
const extraSource = options?.source;
|
|
22
|
+
|
|
23
|
+
if (options?.dryRun) {
|
|
24
|
+
if (lockExists) {
|
|
25
|
+
console.log("Command: npx -y skills@latest upgrade");
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`Command: npx -y skills@latest add ${DEFAULT_SOURCE}`);
|
|
28
|
+
}
|
|
29
|
+
if (extraSource) {
|
|
30
|
+
console.log(`Command: npx -y skills@latest add ${extraSource}`);
|
|
31
|
+
}
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (lockExists) {
|
|
36
|
+
const sources = await readLockSources(lockPath);
|
|
37
|
+
if (sources.length === 0) {
|
|
38
|
+
console.log("skills-lock.json has no sources; installing framework default…");
|
|
39
|
+
const code = await runCommand("npx", ["-y", "skills@latest", "add", DEFAULT_SOURCE], root);
|
|
40
|
+
if (code !== 0) return code;
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`Refreshing ${sources.length} installed source(s)…`);
|
|
43
|
+
for (const src of sources) console.log(` ${src}`);
|
|
44
|
+
const code = await runCommand("npx", ["-y", "skills@latest", "upgrade"], root);
|
|
45
|
+
if (code !== 0) return code;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
console.log(`No skills-lock.json; installing framework default: ${DEFAULT_SOURCE}`);
|
|
49
|
+
const code = await runCommand("npx", ["-y", "skills@latest", "add", DEFAULT_SOURCE], root);
|
|
50
|
+
if (code !== 0) return code;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (extraSource) {
|
|
54
|
+
console.log(`Adding extra source: ${extraSource}`);
|
|
55
|
+
const code = await runCommand("npx", ["-y", "skills@latest", "add", extraSource], root);
|
|
56
|
+
if (code !== 0) return code;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log("✓ Skills synced");
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function readLockSources(lockPath) {
|
|
64
|
+
try {
|
|
65
|
+
const lock = JSON.parse(await readFile(lockPath, "utf8"));
|
|
66
|
+
const sources = Array.isArray(lock?.sources) ? lock.sources : [];
|
|
67
|
+
return sources.map((s) => s?.source).filter((s) => typeof s === "string");
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,5 +1,75 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
1
4
|
import { runCommand } from "./_shared.mjs";
|
|
2
5
|
|
|
6
|
+
// Run typecheck via the locally installed typescript. The previous
|
|
7
|
+
// implementation used `npx tsc`; npm 11 + Node 24 (our CI / release
|
|
8
|
+
// pin) changed npx's bin lookup so it no longer walks pnpm's nested
|
|
9
|
+
// `.bin/` symlink farm and falls back to fetching the legacy
|
|
10
|
+
// `tsc@2.0.4` shim, which crashes.
|
|
11
|
+
//
|
|
12
|
+
// Resolution order:
|
|
13
|
+
// 1. `node <resolved tsc>` via require.resolve(typescript/package.json)
|
|
14
|
+
// — works with npm-hoisted layouts and most pnpm installs.
|
|
15
|
+
// 2. Walk up node_modules/.bin/tsc — covers downstream npm/yarn.
|
|
16
|
+
// 3. Fall back to `pnpm exec tsc` — pnpm knows its own symlink farm
|
|
17
|
+
// even when bare require.resolve doesn't, which is what CI hits.
|
|
3
18
|
export async function run({ root }) {
|
|
4
|
-
|
|
19
|
+
const absoluteRoot = path.resolve(root);
|
|
20
|
+
|
|
21
|
+
const tscBin = resolveTscBin(absoluteRoot);
|
|
22
|
+
if (tscBin) {
|
|
23
|
+
return runCommand("node", [tscBin, "--noEmit", "-p", "tsconfig.json"], absoluteRoot);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (hasCommand("pnpm")) {
|
|
27
|
+
return runCommand("pnpm", ["exec", "tsc", "--noEmit", "-p", "tsconfig.json"], absoluteRoot);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.error("[openpress] typescript is not installed in this workspace.");
|
|
31
|
+
console.error("Add it with: npm install --save-dev typescript");
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveTscBin(absoluteRoot) {
|
|
36
|
+
try {
|
|
37
|
+
const require = createRequire(path.join(absoluteRoot, "package.json"));
|
|
38
|
+
const pkgPath = require.resolve("typescript/package.json");
|
|
39
|
+
return path.join(path.dirname(pkgPath), "bin", "tsc");
|
|
40
|
+
} catch {
|
|
41
|
+
// fall through to .bin probe
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const require = createRequire(import.meta.url);
|
|
46
|
+
const pkgPath = require.resolve("typescript/package.json");
|
|
47
|
+
return path.join(path.dirname(pkgPath), "bin", "tsc");
|
|
48
|
+
} catch {
|
|
49
|
+
// fall through to .bin probe
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let dir = absoluteRoot;
|
|
53
|
+
while (true) {
|
|
54
|
+
const candidate = path.join(dir, "node_modules", ".bin", "tsc");
|
|
55
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
56
|
+
const parent = path.dirname(dir);
|
|
57
|
+
if (parent === dir) return null;
|
|
58
|
+
dir = parent;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function hasCommand(name) {
|
|
63
|
+
const PATH = process.env.PATH ?? "";
|
|
64
|
+
const sep = process.platform === "win32" ? ";" : ":";
|
|
65
|
+
const exts = process.platform === "win32"
|
|
66
|
+
? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")
|
|
67
|
+
: [""];
|
|
68
|
+
for (const dir of PATH.split(sep)) {
|
|
69
|
+
if (!dir) continue;
|
|
70
|
+
for (const ext of exts) {
|
|
71
|
+
if (fs.existsSync(path.join(dir, name + ext))) return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
5
75
|
}
|
|
@@ -46,7 +46,7 @@ export async function run({ root, options }) {
|
|
|
46
46
|
if (!json) {
|
|
47
47
|
process.stdout.write("dry run — nothing changed. The agent should:\n");
|
|
48
48
|
process.stdout.write(" 1. read each docs/migrations/<version>.md for document-level changes\n");
|
|
49
|
-
process.stdout.write(" 2. apply edits to
|
|
49
|
+
process.stdout.write(" 2. apply edits to press/ where needed\n");
|
|
50
50
|
process.stdout.write(" 3. re-run: npx open-press upgrade (without --dry-run)\n");
|
|
51
51
|
} else {
|
|
52
52
|
process.stdout.write(JSON.stringify({ status: "dry-run", before }, null, 2) + "\n");
|
|
@@ -98,11 +98,11 @@ export async function run({ root, options }) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
process.stdout.write(
|
|
101
|
-
"\nAgent: open each file, identify document-level changes, grep
|
|
101
|
+
"\nAgent: open each file, identify document-level changes, grep press/ for affected patterns, propose edits before applying.\n",
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
process.stdout.write("\nVerify with:\n npm run
|
|
105
|
+
process.stdout.write("\nVerify with:\n npm run build\n\n");
|
|
106
106
|
return 0;
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -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
|
|
13
|
+
"React/MDX document entry not found. Expected press/index.tsx with a Press default export before exporting.",
|
|
14
14
|
);
|
|
15
15
|
}
|