@deckspec/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +114 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/approve.d.ts +2 -0
  6. package/dist/commands/approve.d.ts.map +1 -0
  7. package/dist/commands/approve.js +56 -0
  8. package/dist/commands/approve.js.map +1 -0
  9. package/dist/commands/dev.d.ts +2 -0
  10. package/dist/commands/dev.d.ts.map +1 -0
  11. package/dist/commands/dev.js +331 -0
  12. package/dist/commands/dev.js.map +1 -0
  13. package/dist/commands/init.d.ts +5 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +257 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/lock.d.ts +2 -0
  18. package/dist/commands/lock.d.ts.map +1 -0
  19. package/dist/commands/lock.js +155 -0
  20. package/dist/commands/lock.js.map +1 -0
  21. package/dist/commands/patterns.d.ts +6 -0
  22. package/dist/commands/patterns.d.ts.map +1 -0
  23. package/dist/commands/patterns.js +97 -0
  24. package/dist/commands/patterns.js.map +1 -0
  25. package/dist/commands/render.d.ts +6 -0
  26. package/dist/commands/render.d.ts.map +1 -0
  27. package/dist/commands/render.js +41 -0
  28. package/dist/commands/render.js.map +1 -0
  29. package/dist/commands/validate.d.ts +6 -0
  30. package/dist/commands/validate.d.ts.map +1 -0
  31. package/dist/commands/validate.js +37 -0
  32. package/dist/commands/validate.js.map +1 -0
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +5 -0
  36. package/dist/index.js.map +1 -0
  37. package/package.json +8 -4
  38. package/src/cli.ts +7 -0
  39. package/src/commands/init.ts +284 -0
  40. package/tsconfig.json +0 -14
@@ -0,0 +1,37 @@
1
+ import { resolve, dirname } from "node:path";
2
+ import { loadDeckFile, validateDeck } from "@deckspec/dsl";
3
+ import { extractThemeName, resolveThemePatternsDir, compileTsxCached } from "@deckspec/renderer";
4
+ /**
5
+ * Validates a deck YAML file.
6
+ * Prints per-slide results to stdout and exits with code 1 if any errors.
7
+ */
8
+ export async function validateCommand(filePath) {
9
+ const raw = await loadDeckFile(filePath);
10
+ const basePath = dirname(resolve(filePath));
11
+ const themeName = extractThemeName(raw);
12
+ const patternsDir = resolveThemePatternsDir(themeName);
13
+ const result = await validateDeck(raw, { basePath, patternsDir, compileTsx: compileTsxCached });
14
+ if (result.deckError) {
15
+ console.error("\u2717 Deck structure is invalid:");
16
+ for (const issue of result.deckError.issues) {
17
+ console.error(` ${issue.path.join(".")}: ${issue.message}`);
18
+ }
19
+ process.exit(1);
20
+ }
21
+ for (const slideResult of result.results) {
22
+ if (slideResult.valid) {
23
+ console.log(`\u2713 slides[${slideResult.index}]: valid`);
24
+ }
25
+ else {
26
+ for (const issue of slideResult.errors.issues) {
27
+ const detail = issue.message;
28
+ console.error(`\u2717 slides[${slideResult.index}]: ${detail}`);
29
+ }
30
+ }
31
+ }
32
+ if (!result.valid) {
33
+ process.exit(1);
34
+ }
35
+ console.log(`\nAll ${result.results.length} slide(s) passed validation.`);
36
+ }
37
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEjG;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAEhG,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACnD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;YAC5C,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACzC,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,iBAAiB,WAAW,CAAC,KAAK,UAAU,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,MAAO,CAAC,MAAM,EAAE,CAAC;gBAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC;gBAC7B,OAAO,CAAC,KAAK,CAAC,iBAAiB,WAAW,CAAC,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,OAAO,CAAC,MAAM,8BAA8B,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { validateCommand } from "./commands/validate.js";
2
+ export { renderCommand } from "./commands/render.js";
3
+ export { lockCommand } from "./commands/lock.js";
4
+ export { patternsCommand } from "./commands/patterns.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { validateCommand } from "./commands/validate.js";
2
+ export { renderCommand } from "./commands/render.js";
3
+ export { lockCommand } from "./commands/lock.js";
4
+ export { patternsCommand } from "./commands/patterns.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "name": "@deckspec/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
+ "files": [
5
+ "dist",
6
+ "src"
7
+ ],
4
8
  "type": "module",
5
9
  "main": "./dist/index.js",
6
10
  "types": "./dist/index.d.ts",
@@ -13,9 +17,9 @@
13
17
  "react": "^19.0.0",
14
18
  "react-dom": "^19.0.0",
15
19
  "zod": "^3.23.0",
16
- "@deckspec/dsl": "0.1.0",
17
- "@deckspec/schema": "0.1.0",
18
- "@deckspec/renderer": "0.1.0"
20
+ "@deckspec/dsl": "0.1.1",
21
+ "@deckspec/schema": "0.1.1",
22
+ "@deckspec/renderer": "0.1.1"
19
23
  },
20
24
  "license": "Apache-2.0",
21
25
  "repository": {
package/src/cli.ts CHANGED
@@ -6,11 +6,13 @@ import { approveCommand } from "./commands/approve.js";
6
6
  import { lockCommand } from "./commands/lock.js";
7
7
  import { patternsCommand } from "./commands/patterns.js";
8
8
  import { devCommand } from "./commands/dev.js";
9
+ import { initCommand } from "./commands/init.js";
9
10
 
10
11
  const USAGE = `
11
12
  Usage: deckspec <command> [options]
12
13
 
13
14
  Commands:
15
+ init [dir] [--theme <name>] Scaffold a new DeckSpec project
14
16
  validate <file> Validate a deck YAML file
15
17
  render <file> -o <output> Render a deck YAML file to HTML
16
18
  approve <file> [options] Approve/reject slides or archive/activate decks
@@ -108,6 +110,11 @@ async function main(): Promise<void> {
108
110
  break;
109
111
  }
110
112
 
113
+ case "init": {
114
+ await initCommand(args.slice(1));
115
+ break;
116
+ }
117
+
111
118
  default: {
112
119
  console.error(`Error: Unknown command "${command}".\n`);
113
120
  console.log(USAGE);
@@ -0,0 +1,284 @@
1
+ import { resolve, join, dirname, basename, relative } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import {
4
+ mkdir,
5
+ readdir,
6
+ copyFile,
7
+ writeFile,
8
+ stat,
9
+ } from "node:fs/promises";
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ /** Directories to skip when copying a theme. */
15
+ const SKIP_DIRS = new Set(["dist", "node_modules", ".turbo"]);
16
+
17
+ /**
18
+ * Recursively copy a directory, skipping entries in SKIP_DIRS.
19
+ */
20
+ async function copyDirRecursive(src: string, dest: string): Promise<void> {
21
+ await mkdir(dest, { recursive: true });
22
+ const entries = await readdir(src, { withFileTypes: true });
23
+
24
+ for (const entry of entries) {
25
+ const srcPath = join(src, entry.name);
26
+ const destPath = join(dest, entry.name);
27
+
28
+ if (entry.isDirectory()) {
29
+ if (SKIP_DIRS.has(entry.name)) continue;
30
+ await copyDirRecursive(srcPath, destPath);
31
+ } else {
32
+ await copyFile(srcPath, destPath);
33
+ }
34
+ }
35
+ }
36
+
37
+ /**
38
+ * List pattern directory names under a theme's patterns/ folder.
39
+ * Excludes _lib and index.ts; returns only directories.
40
+ */
41
+ async function listPatternNames(patternsDir: string): Promise<string[]> {
42
+ let entries;
43
+ try {
44
+ entries = await readdir(patternsDir, { withFileTypes: true });
45
+ } catch {
46
+ return [];
47
+ }
48
+ return entries
49
+ .filter((e) => e.isDirectory() && !e.name.startsWith("_"))
50
+ .map((e) => e.name)
51
+ .sort();
52
+ }
53
+
54
+ /**
55
+ * Resolve the monorepo root from the CLI package location.
56
+ * Walks up from packages/cli/src/commands/ (or dist/) to find themes/.
57
+ */
58
+ function resolveMonorepoRoot(): string {
59
+ // In dev: packages/cli/src/commands/init.ts → up 4
60
+ // In dist: packages/cli/dist/commands/init.js → up 4
61
+ let dir = __dirname;
62
+ for (let i = 0; i < 6; i++) {
63
+ const candidate = join(dir, "themes");
64
+ // Synchronous existence check via try/catch is avoided;
65
+ // we just go up 4 levels from commands/ which should land at monorepo root.
66
+ dir = dirname(dir);
67
+ }
68
+ // __dirname is packages/cli/dist/commands or packages/cli/src/commands
69
+ // Go up 4 levels: commands → dist/src → cli → packages → root
70
+ return resolve(__dirname, "..", "..", "..", "..");
71
+ }
72
+
73
+ function generateSampleDeck(theme: string): string {
74
+ return `meta:
75
+ title: "My Presentation"
76
+ theme: ${theme}
77
+ state: active
78
+ slides:
79
+ - file: title-center
80
+ vars:
81
+ title: "Welcome"
82
+ subtitle: "A presentation built with DeckSpec"
83
+
84
+ - file: feature-metrics
85
+ vars:
86
+ headline: "Key Numbers"
87
+ metrics:
88
+ - label: "Slides"
89
+ value: "3"
90
+ - label: "Patterns"
91
+ value: "17+"
92
+ - label: "Build Steps"
93
+ value: "0"
94
+
95
+ - file: three-pillars
96
+ vars:
97
+ label: "How It Works"
98
+ heading: "Three Simple Steps"
99
+ pillars:
100
+ - title: "Write"
101
+ value: "YAML"
102
+ description: "Define your content in a simple, structured format."
103
+ - title: "Validate"
104
+ value: "Zod"
105
+ description: "Schemas catch mistakes before you ever see the output."
106
+ - title: "Render"
107
+ value: "HTML"
108
+ description: "Patterns produce standalone HTML — no dependencies."
109
+ `;
110
+ }
111
+
112
+ function generatePackageJson(): string {
113
+ return JSON.stringify(
114
+ {
115
+ private: true,
116
+ type: "module",
117
+ dependencies: {
118
+ "@deckspec/cli": "^0.1.0",
119
+ },
120
+ },
121
+ null,
122
+ 2,
123
+ ) + "\n";
124
+ }
125
+
126
+ function generateGitignore(): string {
127
+ return `node_modules/
128
+ dist/
129
+ output/
130
+ tmp/
131
+ *.tsbuildinfo
132
+ `;
133
+ }
134
+
135
+ function generateClaudeMd(theme: string, patterns: string[]): string {
136
+ const patternList = patterns.map((p) => `- \`${p}\``).join("\n");
137
+
138
+ return `# DeckSpec Project
139
+
140
+ Programmable presentations. YAML で書く。Zod で守る。React で描く。
141
+
142
+ ## YAML DSL Spec
143
+
144
+ A deck is defined by a single \`deck.yaml\` file:
145
+
146
+ \`\`\`yaml
147
+ meta:
148
+ title: "Presentation Title"
149
+ theme: ${theme}
150
+ state: active # active | archived
151
+ slides:
152
+ - file: pattern-name # pattern from themes/${theme}/patterns/
153
+ vars:
154
+ key: "value" # content injected into the pattern
155
+ \`\`\`
156
+
157
+ ### Slide Fields
158
+
159
+ | Field | Required | Description |
160
+ |-------|----------|-------------|
161
+ | \`file\` | yes | Pattern name (resolved from theme) or relative path |
162
+ | \`vars\` | yes | Content variables — validated by the pattern's Zod schema |
163
+ | \`state\` | no | \`generated\` (default) / \`approved\` / \`locked\` |
164
+
165
+ ## Available Patterns (theme: ${theme})
166
+
167
+ ${patternList}
168
+
169
+ Each pattern lives in \`themes/${theme}/patterns/<name>/index.tsx\` and exports:
170
+ - \`export const schema\` — Zod schema defining accepted \`vars\`
171
+ - \`export default Component\` — React component for SSR
172
+
173
+ Check each pattern's \`examples.yaml\` (if present) for usage examples.
174
+
175
+ ## Commands
176
+
177
+ \`\`\`bash
178
+ npx deckspec validate decks/sample/deck.yaml # Validate YAML against Zod schemas
179
+ npx deckspec render decks/sample/deck.yaml -o out # Render to standalone HTML
180
+ npx deckspec dev # Live preview at http://localhost:3002
181
+ npx deckspec patterns # List all patterns with schemas
182
+ \`\`\`
183
+
184
+ ## Creating a New Slide
185
+
186
+ 1. Pick a pattern from the list above
187
+ 2. Add a slide entry to \`deck.yaml\` with \`file:\` and \`vars:\`
188
+ 3. Run \`npx deckspec validate\` to check your YAML
189
+ 4. Run \`npx deckspec dev\` to preview
190
+
191
+ ## Creating a Deck-Local Pattern
192
+
193
+ If no existing pattern fits, create a custom one:
194
+
195
+ 1. Create \`decks/<deck>/patterns/<name>/index.tsx\`
196
+ 2. Export \`schema\` (Zod) and \`default\` (React component)
197
+ 3. Reference it in \`deck.yaml\` with \`file: <name>\`
198
+ 4. Deck-local patterns are compiled on-the-fly with esbuild — no build step needed
199
+
200
+ ## Theme Design Reference
201
+
202
+ See \`themes/${theme}/design.md\` for the theme's design principles, color palette, and typography rules.
203
+ `;
204
+ }
205
+
206
+ /**
207
+ * deckspec init [dir] --theme <name>
208
+ */
209
+ export async function initCommand(args: string[]): Promise<void> {
210
+ // Parse arguments
211
+ let targetDir = ".";
212
+ let themeName = "noir-display";
213
+
214
+ const themeIdx = args.indexOf("--theme");
215
+ if (themeIdx !== -1) {
216
+ const val = args[themeIdx + 1];
217
+ if (!val) {
218
+ console.error("Error: --theme requires a value.");
219
+ process.exit(1);
220
+ }
221
+ themeName = val;
222
+ }
223
+
224
+ // First positional arg (not a flag) is the target directory
225
+ for (const arg of args) {
226
+ if (arg === "--theme") break;
227
+ if (!arg.startsWith("-")) {
228
+ targetDir = arg;
229
+ break;
230
+ }
231
+ }
232
+
233
+ const root = resolve(targetDir);
234
+ const monorepoRoot = resolveMonorepoRoot();
235
+ const themeSrc = join(monorepoRoot, "themes", themeName);
236
+
237
+ // Verify theme exists
238
+ try {
239
+ const s = await stat(themeSrc);
240
+ if (!s.isDirectory()) throw new Error();
241
+ } catch {
242
+ console.error(`Error: Theme "${themeName}" not found at ${themeSrc}`);
243
+ process.exit(1);
244
+ }
245
+
246
+ console.log(`Initializing DeckSpec project in ${root}`);
247
+ console.log(` Theme: ${themeName}`);
248
+
249
+ // 1. Copy theme
250
+ const themeDest = join(root, "themes", themeName);
251
+ console.log(` Copying theme to themes/${themeName}/`);
252
+ await copyDirRecursive(themeSrc, themeDest);
253
+
254
+ // 2. Create sample deck
255
+ const deckDir = join(root, "decks", "sample");
256
+ await mkdir(deckDir, { recursive: true });
257
+ const deckPath = join(deckDir, "deck.yaml");
258
+ await writeFile(deckPath, generateSampleDeck(themeName));
259
+ console.log(" Created decks/sample/deck.yaml");
260
+
261
+ // 3. Create package.json
262
+ const pkgPath = join(root, "package.json");
263
+ await writeFile(pkgPath, generatePackageJson());
264
+ console.log(" Created package.json");
265
+
266
+ // 4. Create CLAUDE.md
267
+ const patterns = await listPatternNames(join(themeDest, "patterns"));
268
+ const claudePath = join(root, "CLAUDE.md");
269
+ await writeFile(claudePath, generateClaudeMd(themeName, patterns));
270
+ console.log(" Created CLAUDE.md");
271
+
272
+ // 5. Create .gitignore
273
+ const gitignorePath = join(root, ".gitignore");
274
+ await writeFile(gitignorePath, generateGitignore());
275
+ console.log(" Created .gitignore");
276
+
277
+ console.log("");
278
+ console.log("Done! Next steps:");
279
+ console.log("");
280
+ console.log(` cd ${relative(process.cwd(), root) || "."}`);
281
+ console.log(" npm install");
282
+ console.log(" npx deckspec dev");
283
+ console.log("");
284
+ }
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src",
6
- "jsx": "react-jsx"
7
- },
8
- "include": ["src/"],
9
- "references": [
10
- { "path": "../schema" },
11
- { "path": "../dsl" },
12
- { "path": "../renderer" }
13
- ]
14
- }