@open-press/cli 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/cli.js +406 -79
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,7 +26,8 @@ npx @open-press/cli init <target> [flags]
|
|
|
26
26
|
|
|
27
27
|
| Flag | Description |
|
|
28
28
|
| -------------------- | --------------------------------------------------------------------------- |
|
|
29
|
-
| `--title <s>` | Document title
|
|
29
|
+
| `--title <s>` | Document title |
|
|
30
|
+
| `--type <pages|slides>` | Scaffold a folder-convention Press under `press/<target-name>/` |
|
|
30
31
|
| `--no-git` | Skip `git init` + initial commit (use when scaffolding inside an existing repo) |
|
|
31
32
|
| `--no-install` | Skip `npm install` (offline, or you'll run pnpm/bun yourself) |
|
|
32
33
|
| `--skills` | Install OpenPress agent skills after scaffolding |
|
|
@@ -46,8 +47,10 @@ npx -y skills@latest add quan0715/openpress-social-card-skill
|
|
|
46
47
|
A self-contained workspace with:
|
|
47
48
|
|
|
48
49
|
- `package.json` with `@open-press/core`, `@open-press/cli`, and `open-press ...` scripts
|
|
49
|
-
- `press
|
|
50
|
-
- `press
|
|
50
|
+
- `press/<target-name>/press.tsx` for folder-convention Press entries
|
|
51
|
+
- `press/<target-name>/theme/`, `press/<target-name>/media/`, `press/<target-name>/components/` — artifact-owned authoring files
|
|
52
|
+
- optional `press/shared/` for assets, media, components, or theme used by multiple Press folders
|
|
53
|
+
- `press/<target-name>/ui/` and `press/<target-name>/layouts/` for slide starters created with `--type slides`
|
|
51
54
|
- `press/design.md` — working design notes for agents and maintainers
|
|
52
55
|
|
|
53
56
|
It does **not** create `engine/`, `src/openpress/`, `index.html`, or `vite.config.ts` in your project. Those are package-owned runtime internals.
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import process2 from "process";
|
|
|
9
9
|
// src/init.ts
|
|
10
10
|
import { spawn } from "child_process";
|
|
11
11
|
import { existsSync } from "fs";
|
|
12
|
-
import { mkdir, readFile
|
|
12
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
13
13
|
import path from "path";
|
|
14
14
|
import process from "process";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
@@ -29,24 +29,6 @@ async function pathIsEmpty(target, options = {}) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// src/metadata.ts
|
|
33
|
-
import { readFile, writeFile } from "fs/promises";
|
|
34
|
-
async function patchPressTitle(entryPath, title) {
|
|
35
|
-
const source = await readFile(entryPath, "utf8");
|
|
36
|
-
const escaped = escapeStringForJs(title);
|
|
37
|
-
const existing = /(<Press\b[^>]*\btitle\s*=\s*)("[^"]*"|'[^']*'|\{`[^`]*`\})/;
|
|
38
|
-
let next;
|
|
39
|
-
if (existing.test(source)) {
|
|
40
|
-
next = source.replace(existing, `$1"${escaped}"`);
|
|
41
|
-
} else {
|
|
42
|
-
next = source.replace(/<Press\b/, `<Press title="${escaped}"`);
|
|
43
|
-
}
|
|
44
|
-
await writeFile(entryPath, next);
|
|
45
|
-
}
|
|
46
|
-
function escapeStringForJs(value) {
|
|
47
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
32
|
// src/init.ts
|
|
51
33
|
var FRAMEWORK_SKILLS_SOURCE = "quan0715/open-press";
|
|
52
34
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -58,14 +40,12 @@ async function init(options) {
|
|
|
58
40
|
const title = options.title ?? "OpenPress Document";
|
|
59
41
|
const version = await readCliVersion();
|
|
60
42
|
log(`Creating open-press workspace at ${target}`);
|
|
43
|
+
if (!options.type) {
|
|
44
|
+
throw new Error("open-press init requires --type pages or --type slides.");
|
|
45
|
+
}
|
|
61
46
|
await writeWorkspacePackageJson(target, workspaceName, version);
|
|
62
47
|
await writeWorkspaceGitignore(target);
|
|
63
|
-
await
|
|
64
|
-
if (options.title) {
|
|
65
|
-
const pressEntry = path.join(target, "press", "index.tsx");
|
|
66
|
-
log("Writing title into <Press> in press/index.tsx");
|
|
67
|
-
await patchPressTitle(pressEntry, options.title);
|
|
68
|
-
}
|
|
48
|
+
await writeTypedStarterPress(target, { pressName: workspaceName, title, type: options.type });
|
|
69
49
|
if (options.skills) {
|
|
70
50
|
log(`Installing framework skills via \`open-press skills add\`\u2026`);
|
|
71
51
|
try {
|
|
@@ -110,7 +90,7 @@ async function ensureTarget(target) {
|
|
|
110
90
|
await mkdir(target, { recursive: true });
|
|
111
91
|
}
|
|
112
92
|
async function readCliVersion() {
|
|
113
|
-
const pkg = JSON.parse(await
|
|
93
|
+
const pkg = JSON.parse(await readFile(CLI_PACKAGE_JSON, "utf8"));
|
|
114
94
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : "latest";
|
|
115
95
|
}
|
|
116
96
|
async function writeWorkspacePackageJson(target, workspaceName, version) {
|
|
@@ -154,7 +134,7 @@ async function writeWorkspacePackageJson(target, workspaceName, version) {
|
|
|
154
134
|
}
|
|
155
135
|
}
|
|
156
136
|
};
|
|
157
|
-
await
|
|
137
|
+
await writeFile(path.join(target, "package.json"), `${JSON.stringify(pkg, null, 2)}
|
|
158
138
|
`, "utf8");
|
|
159
139
|
}
|
|
160
140
|
async function writeWorkspaceGitignore(target) {
|
|
@@ -173,21 +153,45 @@ async function writeWorkspaceGitignore(target) {
|
|
|
173
153
|
"output/",
|
|
174
154
|
""
|
|
175
155
|
].join("\n");
|
|
176
|
-
await
|
|
156
|
+
await writeFile(path.join(target, ".gitignore"), content, "utf8");
|
|
177
157
|
}
|
|
178
|
-
async function
|
|
158
|
+
async function writeTypedStarterPress(target, options) {
|
|
159
|
+
const folder = folderNameFromPressName(options.pressName);
|
|
160
|
+
const title = options.title;
|
|
179
161
|
const pressRoot = path.join(target, "press");
|
|
180
|
-
|
|
181
|
-
|
|
162
|
+
const sharedRoot = path.join(pressRoot, "shared");
|
|
163
|
+
const typedRoot = path.join(pressRoot, folder);
|
|
164
|
+
await writeWorkspaceThemeSkeleton(sharedRoot);
|
|
165
|
+
await mkdir(path.join(sharedRoot, "media"), { recursive: true });
|
|
166
|
+
await mkdir(path.join(typedRoot, "components"), { recursive: true });
|
|
167
|
+
await mkdir(path.join(typedRoot, "theme"), { recursive: true });
|
|
168
|
+
await mkdir(path.join(typedRoot, "media"), { recursive: true });
|
|
169
|
+
await writeFile(path.join(pressRoot, "design.md"), `# ${title}
|
|
170
|
+
|
|
171
|
+
Starter OpenPress ${options.type} workspace.
|
|
172
|
+
`, "utf8");
|
|
173
|
+
await writeFile(path.join(sharedRoot, "media", "README.md"), "# Shared Media\n\nPlace workspace-wide media here.\n", "utf8");
|
|
174
|
+
await writeFile(path.join(typedRoot, "media", "README.md"), "# Media\n\nPlace Press-specific media here.\n", "utf8");
|
|
175
|
+
if (options.type === "pages") {
|
|
176
|
+
await writePagesPress(typedRoot, { folder, title });
|
|
177
|
+
} else {
|
|
178
|
+
await writeSlidesPress(typedRoot, { folder, title });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function writeWorkspaceThemeSkeleton(pressRoot) {
|
|
182
182
|
await mkdir(path.join(pressRoot, "theme", "base"), { recursive: true });
|
|
183
183
|
await mkdir(path.join(pressRoot, "theme", "page-surfaces"), { recursive: true });
|
|
184
184
|
await mkdir(path.join(pressRoot, "theme", "shell"), { recursive: true });
|
|
185
|
-
await
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
185
|
+
await writeFile(
|
|
186
|
+
path.join(pressRoot, "theme", "tokens.css"),
|
|
187
|
+
`:root {
|
|
188
|
+
--openpress-font-body: system-ui, sans-serif;
|
|
189
|
+
--openpress-font-serif: Georgia, serif;
|
|
190
|
+
}
|
|
191
|
+
`,
|
|
192
|
+
"utf8"
|
|
193
|
+
);
|
|
194
|
+
await writeFile(
|
|
191
195
|
path.join(pressRoot, "theme", "base", "page-contract.css"),
|
|
192
196
|
`* {
|
|
193
197
|
box-sizing: border-box;
|
|
@@ -212,7 +216,7 @@ body {
|
|
|
212
216
|
`,
|
|
213
217
|
"utf8"
|
|
214
218
|
);
|
|
215
|
-
await
|
|
219
|
+
await writeFile(
|
|
216
220
|
path.join(pressRoot, "theme", "base", "typography.css"),
|
|
217
221
|
`h1,
|
|
218
222
|
h2,
|
|
@@ -221,25 +225,13 @@ p {
|
|
|
221
225
|
margin: 0;
|
|
222
226
|
}
|
|
223
227
|
|
|
224
|
-
h1 {
|
|
225
|
-
font-family: var(--openpress-font-serif, Georgia, serif);
|
|
226
|
-
font-size: clamp(42px, 8cqw, 72px);
|
|
227
|
-
line-height: 1;
|
|
228
|
-
font-weight: 500;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
p {
|
|
232
|
-
font-size: clamp(16px, 2cqw, 22px);
|
|
233
|
-
line-height: 1.5;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
228
|
code {
|
|
237
229
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
238
230
|
}
|
|
239
231
|
`,
|
|
240
232
|
"utf8"
|
|
241
233
|
);
|
|
242
|
-
await
|
|
234
|
+
await writeFile(
|
|
243
235
|
path.join(pressRoot, "theme", "base", "print.css"),
|
|
244
236
|
`@media print {
|
|
245
237
|
html,
|
|
@@ -250,41 +242,363 @@ code {
|
|
|
250
242
|
`,
|
|
251
243
|
"utf8"
|
|
252
244
|
);
|
|
253
|
-
await
|
|
254
|
-
await
|
|
255
|
-
await
|
|
256
|
-
await
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
245
|
+
await writeFile(path.join(pressRoot, "theme", "page-surfaces", "cover.css"), "/* Starter cover surface. */\n", "utf8");
|
|
246
|
+
await writeFile(path.join(pressRoot, "theme", "page-surfaces", "back-cover.css"), "/* Starter back-cover surface. */\n", "utf8");
|
|
247
|
+
await writeFile(path.join(pressRoot, "theme", "page-surfaces", "toc.css"), "/* Starter TOC surface. */\n", "utf8");
|
|
248
|
+
await writeFile(path.join(pressRoot, "theme", "shell", "reader-controls.css"), "/* Starter reader controls surface. */\n", "utf8");
|
|
249
|
+
}
|
|
250
|
+
async function writePagesPress(typedRoot, { folder, title }) {
|
|
251
|
+
await mkdir(path.join(typedRoot, "chapters", "01-intro", "content"), { recursive: true });
|
|
252
|
+
await writeFile(
|
|
253
|
+
path.join(typedRoot, "press.tsx"),
|
|
254
|
+
`import { Press } from "@open-press/core";
|
|
255
|
+
import { mdxSource } from "@open-press/core/mdx";
|
|
256
|
+
import { Sections, Toc } from "@open-press/core/manuscript";
|
|
257
|
+
|
|
258
|
+
export default function ${componentNameFromFolder(folder)}Press() {
|
|
259
|
+
return (
|
|
260
|
+
<Press
|
|
261
|
+
slug="${escapeJsxAttribute(folder)}"
|
|
262
|
+
title="${escapeJsxAttribute(title)}"
|
|
263
|
+
type="pages"
|
|
264
|
+
page="a4"
|
|
265
|
+
componentsDir="./components"
|
|
266
|
+
mediaDir="./media"
|
|
267
|
+
sources={[mdxSource({ id: "${escapeJsxAttribute(folder)}", preset: "section-folders", root: "${escapeJsxAttribute(folder)}/chapters" })]}
|
|
268
|
+
>
|
|
269
|
+
<Toc source="${escapeJsxAttribute(folder)}" maxLevel={2} />
|
|
270
|
+
<Sections source="${escapeJsxAttribute(folder)}" />
|
|
271
|
+
</Press>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
`,
|
|
275
|
+
"utf8"
|
|
276
|
+
);
|
|
277
|
+
await writeFile(
|
|
278
|
+
path.join(typedRoot, "chapters", "01-intro", "content", "01-intro.mdx"),
|
|
279
|
+
`# ${escapeText(title)}
|
|
280
|
+
|
|
281
|
+
Start writing this OpenPress document here.
|
|
282
|
+
`,
|
|
283
|
+
"utf8"
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
async function writeSlidesPress(typedRoot, { folder, title }) {
|
|
287
|
+
await mkdir(path.join(typedRoot, "ui"), { recursive: true });
|
|
288
|
+
await mkdir(path.join(typedRoot, "layouts"), { recursive: true });
|
|
289
|
+
await writeFile(
|
|
290
|
+
path.join(typedRoot, "press.tsx"),
|
|
291
|
+
`import { Press, Text } from "@open-press/core";
|
|
292
|
+
import { TitleSlide } from "./layouts/title-slide";
|
|
293
|
+
import { TitledContentSlide } from "./layouts/titled-content-slide";
|
|
294
|
+
import { Timeline } from "./ui/timeline";
|
|
295
|
+
import "./theme/slides.css";
|
|
296
|
+
|
|
297
|
+
export default function ${componentNameFromFolder(folder)}Press() {
|
|
298
|
+
return (
|
|
299
|
+
<Press
|
|
300
|
+
slug="${escapeJsxAttribute(folder)}"
|
|
301
|
+
title="${escapeJsxAttribute(title)}"
|
|
302
|
+
type="slides"
|
|
303
|
+
page="slide-16-9"
|
|
304
|
+
componentsDir="./components"
|
|
305
|
+
mediaDir="./media"
|
|
306
|
+
>
|
|
307
|
+
<TitleSlide id="cover">
|
|
308
|
+
<TitleSlide.Title objectId="title">${escapeText(title)}</TitleSlide.Title>
|
|
309
|
+
<TitleSlide.Description objectId="description">
|
|
310
|
+
A concise deck authored as editable OpenPress source.
|
|
311
|
+
</TitleSlide.Description>
|
|
312
|
+
</TitleSlide>
|
|
313
|
+
|
|
314
|
+
<TitledContentSlide id="problem-context">
|
|
315
|
+
<TitledContentSlide.Eyebrow objectId="eyebrow">Context</TitledContentSlide.Eyebrow>
|
|
316
|
+
<TitledContentSlide.Title objectId="title">Problem Context</TitledContentSlide.Title>
|
|
317
|
+
<TitledContentSlide.Content>
|
|
318
|
+
<Text as="p" objectId="summary">Write visible slide content directly in JSX.</Text>
|
|
319
|
+
</TitledContentSlide.Content>
|
|
320
|
+
</TitledContentSlide>
|
|
321
|
+
|
|
322
|
+
<TitledContentSlide id="workflow">
|
|
323
|
+
<TitledContentSlide.Eyebrow objectId="eyebrow">Process</TitledContentSlide.Eyebrow>
|
|
324
|
+
<TitledContentSlide.Title objectId="title">Workflow</TitledContentSlide.Title>
|
|
325
|
+
<TitledContentSlide.Content>
|
|
326
|
+
<Timeline>
|
|
327
|
+
<Timeline.Item id="draft" marker="01" title="Draft">Create the first structure.</Timeline.Item>
|
|
328
|
+
<Timeline.Item id="review" marker="02" title="Review">Tighten content and layout.</Timeline.Item>
|
|
329
|
+
<Timeline.Item id="export" marker="03" title="Export">Render PDF or images.</Timeline.Item>
|
|
330
|
+
</Timeline>
|
|
331
|
+
</TitledContentSlide.Content>
|
|
332
|
+
</TitledContentSlide>
|
|
333
|
+
</Press>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
`,
|
|
337
|
+
"utf8"
|
|
338
|
+
);
|
|
339
|
+
await writeFile(
|
|
340
|
+
path.join(typedRoot, "components", "DeckSlide.tsx"),
|
|
341
|
+
`import { PageFolio, Slide } from "@open-press/core";
|
|
342
|
+
import type { ReactNode } from "react";
|
|
343
|
+
|
|
344
|
+
export function DeckSlide({
|
|
345
|
+
id,
|
|
346
|
+
variant = "default",
|
|
347
|
+
children,
|
|
348
|
+
}: {
|
|
349
|
+
id: string;
|
|
350
|
+
variant?: "default" | "cover";
|
|
351
|
+
children: ReactNode;
|
|
352
|
+
}) {
|
|
353
|
+
return (
|
|
354
|
+
<Slide id={id} className={\`op-slide op-slide--\${variant}\`}>
|
|
355
|
+
<div className="op-slide__surface">
|
|
356
|
+
<main className="op-slide__content">{children}</main>
|
|
357
|
+
<footer className="op-slide__footer">
|
|
358
|
+
<PageFolio variant="slash" currentFormat="2-digit" totalFormat="2-digit" />
|
|
359
|
+
</footer>
|
|
360
|
+
</div>
|
|
361
|
+
</Slide>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
`,
|
|
365
|
+
"utf8"
|
|
366
|
+
);
|
|
367
|
+
await writeFile(
|
|
368
|
+
path.join(typedRoot, "layouts", "title-slide.tsx"),
|
|
369
|
+
`import { Text, type TextProps } from "@open-press/core";
|
|
370
|
+
import type { ReactNode } from "react";
|
|
371
|
+
import { DeckSlide } from "../components/DeckSlide";
|
|
372
|
+
|
|
373
|
+
function TitleSlideRoot({ id, children }: { id: string; children: ReactNode }) {
|
|
374
|
+
return (
|
|
375
|
+
<DeckSlide id={id} variant="cover">
|
|
376
|
+
<section className="op-title-slide">{children}</section>
|
|
377
|
+
</DeckSlide>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function TitleSlideTitle(props: Omit<TextProps, "as" | "label">) {
|
|
382
|
+
return <Text as="h1" label="Title" className="op-slide-title" {...props} />;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function TitleSlideDescription(props: Omit<TextProps, "as" | "label">) {
|
|
386
|
+
return <Text as="p" label="Description" className="op-slide-description" {...props} />;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export const TitleSlide = Object.assign(TitleSlideRoot, {
|
|
390
|
+
Title: TitleSlideTitle,
|
|
391
|
+
Description: TitleSlideDescription,
|
|
392
|
+
});
|
|
393
|
+
`,
|
|
394
|
+
"utf8"
|
|
395
|
+
);
|
|
396
|
+
await writeFile(
|
|
397
|
+
path.join(typedRoot, "layouts", "titled-content-slide.tsx"),
|
|
398
|
+
`import { Text, type TextProps } from "@open-press/core";
|
|
399
|
+
import type { ReactNode } from "react";
|
|
400
|
+
import { DeckSlide } from "../components/DeckSlide";
|
|
401
|
+
|
|
402
|
+
function TitledContentSlideRoot({ id, children }: { id: string; children: ReactNode }) {
|
|
403
|
+
return (
|
|
404
|
+
<DeckSlide id={id}>
|
|
405
|
+
<section className="op-titled-content-slide">{children}</section>
|
|
406
|
+
</DeckSlide>
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function TitledContentSlideEyebrow(props: Omit<TextProps, "as" | "label">) {
|
|
411
|
+
return <Text as="p" label="Eyebrow" className="op-slide-eyebrow" {...props} />;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function TitledContentSlideTitle(props: Omit<TextProps, "as" | "label">) {
|
|
415
|
+
return <Text as="h2" label="Title" className="op-slide-heading" {...props} />;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function TitledContentSlideDescription(props: Omit<TextProps, "as" | "label">) {
|
|
419
|
+
return <Text as="p" label="Description" className="op-slide-description" {...props} />;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function TitledContentSlideContent({ children }: { children: ReactNode }) {
|
|
423
|
+
return <div className="op-slide-body">{children}</div>;
|
|
262
424
|
}
|
|
425
|
+
|
|
426
|
+
export const TitledContentSlide = Object.assign(TitledContentSlideRoot, {
|
|
427
|
+
Eyebrow: TitledContentSlideEyebrow,
|
|
428
|
+
Title: TitledContentSlideTitle,
|
|
429
|
+
Description: TitledContentSlideDescription,
|
|
430
|
+
Desc: TitledContentSlideDescription,
|
|
431
|
+
Content: TitledContentSlideContent,
|
|
432
|
+
});
|
|
263
433
|
`,
|
|
264
434
|
"utf8"
|
|
265
435
|
);
|
|
266
|
-
await
|
|
267
|
-
path.join(
|
|
268
|
-
`import {
|
|
436
|
+
await writeFile(
|
|
437
|
+
path.join(typedRoot, "ui", "timeline.tsx"),
|
|
438
|
+
`import { Text } from "@open-press/core";
|
|
439
|
+
import type { ReactNode } from "react";
|
|
440
|
+
|
|
441
|
+
function TimelineRoot({ children }: { children: ReactNode }) {
|
|
442
|
+
return <ol className="op-timeline">{children}</ol>;
|
|
443
|
+
}
|
|
269
444
|
|
|
270
|
-
|
|
445
|
+
function TimelineItem({
|
|
446
|
+
id,
|
|
447
|
+
marker,
|
|
448
|
+
title,
|
|
449
|
+
children,
|
|
450
|
+
}: {
|
|
451
|
+
id: string;
|
|
452
|
+
marker: string;
|
|
453
|
+
title: string;
|
|
454
|
+
children: ReactNode;
|
|
455
|
+
}) {
|
|
271
456
|
return (
|
|
272
|
-
<
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
</main>
|
|
280
|
-
</Frame>
|
|
281
|
-
</Press>
|
|
282
|
-
</Workspace>
|
|
457
|
+
<li className="op-timeline__item">
|
|
458
|
+
<Text as="span" objectId={\`\${id}-marker\`} label="Timeline marker" className="op-timeline__marker">{marker}</Text>
|
|
459
|
+
<div>
|
|
460
|
+
<Text as="h3" objectId={\`\${id}-title\`} label="Timeline title" className="op-timeline__title">{title}</Text>
|
|
461
|
+
<Text as="p" objectId={\`\${id}-body\`} label="Timeline body" className="op-timeline__body">{children}</Text>
|
|
462
|
+
</div>
|
|
463
|
+
</li>
|
|
283
464
|
);
|
|
284
465
|
}
|
|
466
|
+
|
|
467
|
+
export const Timeline = Object.assign(TimelineRoot, {
|
|
468
|
+
Item: TimelineItem,
|
|
469
|
+
});
|
|
470
|
+
`,
|
|
471
|
+
"utf8"
|
|
472
|
+
);
|
|
473
|
+
await writeFile(
|
|
474
|
+
path.join(typedRoot, "ui", "text.tsx"),
|
|
475
|
+
`export { Text } from "@open-press/core";
|
|
476
|
+
export type { TextProps } from "@open-press/core";
|
|
285
477
|
`,
|
|
286
478
|
"utf8"
|
|
287
479
|
);
|
|
480
|
+
await writeFile(path.join(typedRoot, "ui", "card.tsx"), `import type { ReactNode } from "react";
|
|
481
|
+
|
|
482
|
+
export function Card({ children }: { children: ReactNode }) {
|
|
483
|
+
return <article className="op-card">{children}</article>;
|
|
484
|
+
}
|
|
485
|
+
`, "utf8");
|
|
486
|
+
await writeFile(path.join(typedRoot, "theme", "tokens.css"), `:root {
|
|
487
|
+
--op-slide-bg: #f8fafc;
|
|
488
|
+
--op-slide-fg: #17202a;
|
|
489
|
+
--op-slide-muted: #607084;
|
|
490
|
+
--op-slide-accent: #2563eb;
|
|
491
|
+
}
|
|
492
|
+
`, "utf8");
|
|
493
|
+
await writeFile(
|
|
494
|
+
path.join(typedRoot, "theme", "slides.css"),
|
|
495
|
+
`@import "./tokens.css";
|
|
496
|
+
|
|
497
|
+
.op-slide {
|
|
498
|
+
background: var(--op-slide-bg);
|
|
499
|
+
color: var(--op-slide-fg);
|
|
500
|
+
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.op-slide__surface {
|
|
504
|
+
width: 100%;
|
|
505
|
+
height: 100%;
|
|
506
|
+
padding: 92px 112px;
|
|
507
|
+
display: grid;
|
|
508
|
+
grid-template-rows: 1fr auto;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.op-slide__content {
|
|
512
|
+
min-width: 0;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.op-slide__footer {
|
|
516
|
+
color: var(--op-slide-muted);
|
|
517
|
+
display: flex;
|
|
518
|
+
justify-content: flex-end;
|
|
519
|
+
font-size: 22px;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.op-title-slide,
|
|
523
|
+
.op-titled-content-slide {
|
|
524
|
+
display: grid;
|
|
525
|
+
gap: 28px;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.op-title-slide {
|
|
529
|
+
align-content: center;
|
|
530
|
+
max-width: 1180px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.op-slide-title {
|
|
534
|
+
font-size: 112px;
|
|
535
|
+
line-height: 1;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.op-slide-heading {
|
|
539
|
+
font-size: 72px;
|
|
540
|
+
line-height: 1.08;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.op-slide-eyebrow {
|
|
544
|
+
color: var(--op-slide-accent);
|
|
545
|
+
font-size: 24px;
|
|
546
|
+
font-weight: 700;
|
|
547
|
+
text-transform: uppercase;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.op-slide-description,
|
|
551
|
+
.op-slide-body {
|
|
552
|
+
color: var(--op-slide-muted);
|
|
553
|
+
font-size: 34px;
|
|
554
|
+
line-height: 1.35;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.op-timeline {
|
|
558
|
+
display: grid;
|
|
559
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
560
|
+
gap: 24px;
|
|
561
|
+
padding: 0;
|
|
562
|
+
margin: 24px 0 0;
|
|
563
|
+
list-style: none;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.op-timeline__item {
|
|
567
|
+
background: #ffffff;
|
|
568
|
+
border: 1px solid #dbe3ef;
|
|
569
|
+
display: grid;
|
|
570
|
+
gap: 18px;
|
|
571
|
+
padding: 28px;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.op-timeline__marker {
|
|
575
|
+
color: var(--op-slide-accent);
|
|
576
|
+
font-size: 24px;
|
|
577
|
+
font-weight: 800;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.op-timeline__title {
|
|
581
|
+
font-size: 34px;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.op-timeline__body {
|
|
585
|
+
color: var(--op-slide-muted);
|
|
586
|
+
font-size: 26px;
|
|
587
|
+
line-height: 1.35;
|
|
588
|
+
}
|
|
589
|
+
`,
|
|
590
|
+
"utf8"
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
function folderNameFromPressName(value) {
|
|
594
|
+
const base = path.basename(value);
|
|
595
|
+
const folder = base.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
596
|
+
return folder || "press";
|
|
597
|
+
}
|
|
598
|
+
function componentNameFromFolder(folder) {
|
|
599
|
+
const words = folder.split(/[-_]+/).filter(Boolean);
|
|
600
|
+
const name = words.map((word) => word.slice(0, 1).toUpperCase() + word.slice(1)).join("");
|
|
601
|
+
return name || "OpenPress";
|
|
288
602
|
}
|
|
289
603
|
function escapeJsxAttribute(value) {
|
|
290
604
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
@@ -332,7 +646,7 @@ function printNextSteps(target, options) {
|
|
|
332
646
|
"",
|
|
333
647
|
"Then open the local URL printed by Vite (typically http://127.0.0.1:5173/workspace).",
|
|
334
648
|
"",
|
|
335
|
-
"Use
|
|
649
|
+
"Use openpress-create-pages for a page-based artifact or openpress-create-slide for a deck.",
|
|
336
650
|
""
|
|
337
651
|
);
|
|
338
652
|
process.stdout.write(lines.join("\n"));
|
|
@@ -348,7 +662,8 @@ Usage:
|
|
|
348
662
|
open-press skills <add|update> [--source <owner/repo>]
|
|
349
663
|
|
|
350
664
|
Init flags:
|
|
351
|
-
--title <s> Document title (written into <Press title="...">
|
|
665
|
+
--title <s> Document title (written into <Press title="...">)
|
|
666
|
+
--type <pages|slides> Required. Scaffold a Press under press/<target-name>/
|
|
352
667
|
--no-git Skip git init
|
|
353
668
|
--no-install Skip npm install
|
|
354
669
|
--skills Install OpenPress agent skills after scaffolding
|
|
@@ -371,8 +686,8 @@ Runtime commands:
|
|
|
371
686
|
skills Install or update OpenPress agent skills
|
|
372
687
|
|
|
373
688
|
Examples:
|
|
374
|
-
npx @open-press/cli init my-doc
|
|
375
|
-
npx @open-press/cli init my-brief --title "Q2 Brief"
|
|
689
|
+
npx @open-press/cli init my-doc --type pages
|
|
690
|
+
npx @open-press/cli init my-brief --type slides --title "Q2 Brief"
|
|
376
691
|
npx open-press dev .
|
|
377
692
|
npx open-press image .
|
|
378
693
|
`;
|
|
@@ -411,6 +726,7 @@ function parseInitArgs(args) {
|
|
|
411
726
|
const options = {
|
|
412
727
|
target: "",
|
|
413
728
|
title: void 0,
|
|
729
|
+
type: void 0,
|
|
414
730
|
git: true,
|
|
415
731
|
install: true,
|
|
416
732
|
skills: false
|
|
@@ -421,6 +737,17 @@ function parseInitArgs(args) {
|
|
|
421
737
|
case "--title":
|
|
422
738
|
options.title = args[++i];
|
|
423
739
|
break;
|
|
740
|
+
case "--type": {
|
|
741
|
+
const type = args[++i];
|
|
742
|
+
if (type !== "pages" && type !== "slides") {
|
|
743
|
+
process2.stderr.write(`Invalid --type: ${type}. Expected "pages" or "slides".
|
|
744
|
+
|
|
745
|
+
`);
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
options.type = type;
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
424
751
|
case "--no-git":
|
|
425
752
|
options.git = false;
|
|
426
753
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-press/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Command line interface for OpenPress workspaces.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"node": ">=20"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@open-press/core": "1.
|
|
45
|
+
"@open-press/core": "1.3.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^25.8.0",
|