@tycoworks/bon 0.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/SKILL.md +218 -0
- package/bin/bon.js +2 -0
- package/package.json +20 -0
- package/src/cli.ts +98 -0
- package/src/config.ts +5 -0
- package/src/index.ts +17 -0
- package/src/manifest.ts +106 -0
- package/src/smoke.ts +34 -0
- package/tsconfig.json +19 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slides
|
|
3
|
+
description: >
|
|
4
|
+
Use this skill any time the user wants to create branded slides, presentations,
|
|
5
|
+
pitch decks, or sales collateral as a .pptx. Trigger whenever the user mentions "deck," "slides,"
|
|
6
|
+
"presentation," "pitch," or .pptx output. Also trigger when the user says "build me a deck,"
|
|
7
|
+
"make slides about X," or "turn this into a presentation."
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# slides
|
|
11
|
+
|
|
12
|
+
This skill builds on-brand decks from a deck file. The theme provides slide layouts that control design. Your job: pick the right layouts, fill them with content, and build. You never restyle the layout; the engine clones the real slides, so brand, layout, fonts, and chrome come for free.
|
|
13
|
+
|
|
14
|
+
For brand voice and naming guidelines, read `brand.md` if it exists alongside this skill.
|
|
15
|
+
|
|
16
|
+
## Quick Reference
|
|
17
|
+
|
|
18
|
+
| Task | Guide |
|
|
19
|
+
|------|-------|
|
|
20
|
+
| Discover layouts and assets | Read `manifest.json` |
|
|
21
|
+
| Write a deck (structure, slots, assets) | See [Creating Slides](#creating-slides) below |
|
|
22
|
+
| Fix build errors | See [QA](#qa-required) below |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Layout Discovery
|
|
27
|
+
|
|
28
|
+
Before writing anything, read `manifest.json`. It contains:
|
|
29
|
+
|
|
30
|
+
- **layouts** -- for each: `name`, `description`, `contentSlots` (with `type` and `limit`), `assetSlots` (with `accepts` and `required`), and documentation (`whenToUse`, `whenNotToUse`)
|
|
31
|
+
- **assets** -- brand logos, client logos, illustrations, and icons (`description`, `whenToUse`)
|
|
32
|
+
|
|
33
|
+
Study each layout's `whenToUse` and `limit`s before writing any slides. These are your primary guide for matching content to layouts.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Creating Slides
|
|
38
|
+
|
|
39
|
+
Write a deck file: a plain JSON file with an `output` name and an ordered list of `steps`. Each step names a `layout`, fills its `content` slots, and optionally swaps `assets`.
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"output": "my-deck.pptx",
|
|
44
|
+
"steps": [
|
|
45
|
+
{ "layout": "Title",
|
|
46
|
+
"content": { "title": "...", "name": "Presenter Name", "jobTitle": "Their Role" } },
|
|
47
|
+
{ "layout": "Quote dark",
|
|
48
|
+
"content": { "quote": "...", "attribution": ["Name", "Role, Company"] },
|
|
49
|
+
"assets": { "logo": "assets/clients/acme.png" } }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`content` keys must match the layout's `contentSlot` keys; `assets` keys must match its `assetSlot` keys. Asset paths are relative to the repo root.
|
|
55
|
+
|
|
56
|
+
### Asset slots (`assetSlots`)
|
|
57
|
+
Each asset slot declares:
|
|
58
|
+
- **`accepts`** — a **sizing constraint**, not a semantic label:
|
|
59
|
+
- `icon` — simple, scalable graphics that read at any size: glyphs and logos.
|
|
60
|
+
- `image` — detailed rasters that only read large: illustrations and schematics.
|
|
61
|
+
- **Match the kind exactly — the engine enforces it both ways.**
|
|
62
|
+
- An `image` asset in an `icon` slot is unreadable at card/grid size (detail turns to noise).
|
|
63
|
+
- An `icon` asset in an `image` slot blows up oversized filling a half-slide area and looks stupid.
|
|
64
|
+
- Small slots take icons; large/full-bleed `image` slots take dense `image` assets. The asset's `type` in `manifest.json` tells you which it is. The semantic role ("the client's logo here") lives in the slot's `key`/description, not the type.
|
|
65
|
+
- **`required: true`** — you MUST supply this asset; the slide has no usable default and the build fails without it (e.g. team-member photos, icon-grid icons, the quote logo). If you don't have a suitable image, ask the user for one.
|
|
66
|
+
- Slots without `required` are optional — omit them to keep the layout's built-in default (e.g. the full-bleed background, the two-column split image).
|
|
67
|
+
|
|
68
|
+
### Slot types (in `manifest.json`)
|
|
69
|
+
|
|
70
|
+
- **`text`** — a single line. Pass a string. (Use it for stat values too — a stat can be textual.)
|
|
71
|
+
- **`lines`** — a fixed set of styled lines (e.g. name + job title). Pass an array of strings, one per line.
|
|
72
|
+
- **`prose`** — a body block, **written as markdown**. Pass a markdown string, or an array of lines:
|
|
73
|
+
- `- ` (or `* `) starts a **bullet**; indent **2 spaces per level** to nest (` - ` = level 1).
|
|
74
|
+
- A line with no marker is a **paragraph** (a lead-in / prose line).
|
|
75
|
+
- Blank lines are ignored.
|
|
76
|
+
- Do NOT put headings in a `prose` body — the heading is the slide's `title` slot, and a subheading is the `subtitle` slot on subtitle layouts. Prose is paragraphs + bullets only.
|
|
77
|
+
- Some slots fill *part* of a shared box (they have a `paragraph` selector in the layout definition), e.g. a cover's `name` and `jobTitle` are two slots in one box. Nothing special to do: just fill each key. Check the layout's `contentSlots` for the exact keys.
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
"body": [
|
|
81
|
+
"Every option falls short:",
|
|
82
|
+
"- OLTP databases are siloed.",
|
|
83
|
+
"- Lakehouses lack freshness.",
|
|
84
|
+
" - and cost-efficiency."
|
|
85
|
+
]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Build:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Run the command from manifest.json's build.command
|
|
92
|
+
# e.g.: node src/run-spec.ts deck.json my-deck.pptx
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The deck is written to your current working directory (not inside the skill).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Layout Selection
|
|
100
|
+
|
|
101
|
+
**Don't create boring decks.** Repeating the same layout on every slide makes a forgettable presentation. Use variety and match content shape to layout purpose.
|
|
102
|
+
|
|
103
|
+
### Before Starting
|
|
104
|
+
|
|
105
|
+
1. **Read the manifest thoroughly.** Each layout has `whenToUse`, `whenNotToUse`, and `limit`s. Respect all three.
|
|
106
|
+
2. **Match content shape to layout purpose.** A comparison belongs in a two/three-column layout, quantified proof belongs in stat blocks, a customer voice belongs in a quote or testimonial layout. Don't force content into the wrong layout.
|
|
107
|
+
3. **Plan narrative arc first.** Decide the sequence of ideas before picking layouts. Then assign each idea to its best-fit layout from the manifest. Keep one variant (all dark or all light) across the deck.
|
|
108
|
+
|
|
109
|
+
### For Each Slide
|
|
110
|
+
|
|
111
|
+
**Every slide communicates one idea.** If you're writing more than 5 bullets or 3 paragraphs, split into two slides.
|
|
112
|
+
|
|
113
|
+
Check each layout's `limit` in the manifest for content density constraints. When content overflows, split across slides.
|
|
114
|
+
|
|
115
|
+
### Avoid (Common Mistakes)
|
|
116
|
+
|
|
117
|
+
- **Don't repeat the same layout** -- vary layouts for visual rhythm
|
|
118
|
+
- **Don't dump all content on one slide** -- two clear slides beat one crowded slide
|
|
119
|
+
- **Don't ignore layout limits** -- if a slot says max 4 stats, use 4 or fewer
|
|
120
|
+
- **Don't open with a body/content layout** -- use the Title layout for impact
|
|
121
|
+
- **Don't skip section dividers** -- for decks over 5 slides, use Section title layouts to group sections
|
|
122
|
+
- **Don't restyle the layout** -- the theme owns all design; you only fill slots
|
|
123
|
+
- **Don't invent layout or asset names** -- only use what exists in the manifest
|
|
124
|
+
- **Don't leave required slots empty** -- and don't leave a placeholder logo or dummy text in an asset slot you care about
|
|
125
|
+
- **Don't mix dark and light** -- keep one variant across the deck
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## QA (Required)
|
|
130
|
+
|
|
131
|
+
**Assume the first build will fail. Your job is to fix it.**
|
|
132
|
+
|
|
133
|
+
Your first draft almost never comes out clean. Approach QA as a debugging session, not a confirmation step. If you haven't run at least one build-fix cycle, you're not done.
|
|
134
|
+
|
|
135
|
+
### Build
|
|
136
|
+
|
|
137
|
+
Run the command from `manifest.json`'s `build.command`:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# e.g.: node src/run-spec.ts deck.json my-deck.pptx
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Read output carefully. Common errors and fixes:
|
|
144
|
+
|
|
145
|
+
| Error | Fix |
|
|
146
|
+
|-------|-----|
|
|
147
|
+
| `Unknown layout: 'xyz'` | Check layout names in `manifest.json` |
|
|
148
|
+
| A slot didn't fill | Use the `contentSlot` key names declared by the layout |
|
|
149
|
+
| An image didn't swap / placeholder remains | Use the `assetSlot` key, and an asset path that exists in `manifest.json` |
|
|
150
|
+
| `Cannot read ... JSON` / parse error | Fix the JSON syntax in the deck file |
|
|
151
|
+
| `Skipped setting relation target` | The asset image couldn't be placed; check the path and file |
|
|
152
|
+
|
|
153
|
+
### Verification Loop
|
|
154
|
+
|
|
155
|
+
1. Write the deck file → Build
|
|
156
|
+
2. **Read every error** -- fix all of them
|
|
157
|
+
3. Rebuild
|
|
158
|
+
4. **If content overflows**: reduce content or split into two slides
|
|
159
|
+
5. Repeat until the build exits cleanly
|
|
160
|
+
|
|
161
|
+
**Do not declare success until you've completed at least one build-fix cycle.**
|
|
162
|
+
|
|
163
|
+
### Visual Check
|
|
164
|
+
|
|
165
|
+
After a clean build, render the `.pptx` to PNGs and inspect them:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
soffice --headless --convert-to pdf --outdir . <deck>.pptx
|
|
169
|
+
pdftoppm -png -r 96 <deck>.pdf <name>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Read each slide image and check for:
|
|
173
|
+
|
|
174
|
+
- **Word wrapping** — text that breaks mid-word or overflows its container
|
|
175
|
+
- **Cramped text** — content too dense for the slide area
|
|
176
|
+
- **Leftover placeholders** — dummy text ("Lorem ipsum", "Firstname Lastname") or a placeholder logo that should have been swapped
|
|
177
|
+
- **Cut-off content** — text or images clipped at slide edges
|
|
178
|
+
|
|
179
|
+
If you spot issues, reduce content, switch layouts, or split into multiple slides. Rebuild and re-check.
|
|
180
|
+
|
|
181
|
+
### Content Review (Use Subagents)
|
|
182
|
+
|
|
183
|
+
**Use subagents for review** -- even for short decks. You've been staring at the content and will see what you expect, not what's there. Subagents have fresh eyes.
|
|
184
|
+
|
|
185
|
+
After a successful build, spawn a subagent:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
Review this deck. Assume there are issues -- find them.
|
|
189
|
+
|
|
190
|
+
Check for:
|
|
191
|
+
- Slides that are too dense (>7 bullets, >5 paragraphs, too many stats/rows)
|
|
192
|
+
- Same layout repeated multiple times with no variety
|
|
193
|
+
- Content that doesn't match layout purpose (check whenToUse in manifest.json)
|
|
194
|
+
- Narrative that doesn't flow logically
|
|
195
|
+
- Missing opening (Title) or closing (Thank you) slide
|
|
196
|
+
- Slides that are too sparse (a single bullet doesn't need its own slide)
|
|
197
|
+
- Leftover placeholder logos or dummy text in the rendered images
|
|
198
|
+
|
|
199
|
+
For each issue, suggest a specific fix.
|
|
200
|
+
|
|
201
|
+
Read: /path/to/deck.json and the rendered PNGs in the working directory
|
|
202
|
+
Also read: manifest.json (for layout documentation)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
If the subagent finds issues, fix them and rebuild.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Core Commands
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Build a deck (use command from manifest.json's build.command)
|
|
213
|
+
# e.g.: node src/run-spec.ts deck.json my-deck.pptx
|
|
214
|
+
|
|
215
|
+
# Render to images for visual QA
|
|
216
|
+
soffice --headless --convert-to pdf --outdir . <deck>.pptx
|
|
217
|
+
pdftoppm -png -r 96 <deck>.pdf <name>
|
|
218
|
+
```
|
package/bin/bon.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tycoworks/bon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bon": "bin/bon.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@tycoworks/bon-core": "^0.1.0",
|
|
14
|
+
"commander": "^15.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.0.0",
|
|
18
|
+
"typescript": "^5.8.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { Config, ThemeConfig } from "@tycoworks/bon-core";
|
|
5
|
+
import { generate } from "@tycoworks/bon-core";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { generateManifest } from "./manifest.ts";
|
|
8
|
+
import { smokeSteps } from "./smoke.ts";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CONFIG = "theme.config.ts";
|
|
11
|
+
const DEFAULT_SMOKE_OUTPUT = "smoke-all.pptx";
|
|
12
|
+
const SKILL_DIR = "skills/slides";
|
|
13
|
+
const MANIFEST_FILE = "manifest.json";
|
|
14
|
+
const SKILL_FILE = "SKILL.md";
|
|
15
|
+
const BUILD_COMMAND = "npx bon build";
|
|
16
|
+
|
|
17
|
+
const sdkDir = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const skillMdPath = resolve(sdkDir, "..", SKILL_FILE);
|
|
19
|
+
|
|
20
|
+
async function loadConfig(configPath: string): Promise<Config> {
|
|
21
|
+
const abs = resolve(process.cwd(), configPath);
|
|
22
|
+
let mod: Record<string, unknown>;
|
|
23
|
+
try {
|
|
24
|
+
mod = await import(abs);
|
|
25
|
+
} catch {
|
|
26
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
27
|
+
}
|
|
28
|
+
const raw = (mod.default ?? mod.config) as ThemeConfig | undefined;
|
|
29
|
+
if (!raw) {
|
|
30
|
+
throw new Error(`${configPath} must export a default or named "config" of type ThemeConfig`);
|
|
31
|
+
}
|
|
32
|
+
return { ...raw, rootDir: dirname(abs) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const program = new Command().name("bon").description("PPTX template engine CLI").version("0.1.0");
|
|
36
|
+
|
|
37
|
+
program
|
|
38
|
+
.command("build")
|
|
39
|
+
.description("Build a PPTX deck from a JSON spec")
|
|
40
|
+
.argument("<deck>", "path to deck JSON file")
|
|
41
|
+
.argument("[output]", "output PPTX filename")
|
|
42
|
+
.option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
|
|
43
|
+
.action(async (deckPath: string, output: string | undefined, opts: { config: string }) => {
|
|
44
|
+
const config = await loadConfig(opts.config);
|
|
45
|
+
let deck: Record<string, unknown>;
|
|
46
|
+
try {
|
|
47
|
+
const raw = readFileSync(resolve(process.cwd(), deckPath), "utf-8");
|
|
48
|
+
deck = JSON.parse(raw);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(`Failed to read deck file: ${deckPath} (${(err as Error).message})`);
|
|
51
|
+
}
|
|
52
|
+
if (output) deck.output = output;
|
|
53
|
+
await generate(deck as any, config);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
program
|
|
57
|
+
.command("manifest")
|
|
58
|
+
.description("Generate manifest.json from theme config")
|
|
59
|
+
.option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
|
|
60
|
+
.option(`-o, --out <file>`, "write to file instead of stdout")
|
|
61
|
+
.action(async (opts: { config: string; out?: string }) => {
|
|
62
|
+
const config = await loadConfig(opts.config);
|
|
63
|
+
const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
|
|
64
|
+
if (opts.out) {
|
|
65
|
+
writeFileSync(resolve(process.cwd(), opts.out), `${json}\n`);
|
|
66
|
+
console.log(`WROTE ${opts.out}`);
|
|
67
|
+
} else {
|
|
68
|
+
process.stdout.write(`${json}\n`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
program
|
|
73
|
+
.command("smoke")
|
|
74
|
+
.description("Generate one smoke-test slide per layout")
|
|
75
|
+
.option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
|
|
76
|
+
.option(`-o, --out <file>`, "output PPTX filename", DEFAULT_SMOKE_OUTPUT)
|
|
77
|
+
.action(async (opts: { config: string; out: string }) => {
|
|
78
|
+
const config = await loadConfig(opts.config);
|
|
79
|
+
const steps = smokeSteps(config);
|
|
80
|
+
await generate({ output: opts.out, steps }, config);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
program
|
|
84
|
+
.command("plugin")
|
|
85
|
+
.description("Generate plugin package (manifest.json + SKILL.md) for AI agents")
|
|
86
|
+
.option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
|
|
87
|
+
.action(async (opts: { config: string }) => {
|
|
88
|
+
const config = await loadConfig(opts.config);
|
|
89
|
+
const outDir = resolve(process.cwd(), SKILL_DIR);
|
|
90
|
+
mkdirSync(outDir, { recursive: true });
|
|
91
|
+
const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
|
|
92
|
+
writeFileSync(resolve(outDir, MANIFEST_FILE), `${json}\n`);
|
|
93
|
+
console.log(`WROTE ${SKILL_DIR}/${MANIFEST_FILE}`);
|
|
94
|
+
copyFileSync(skillMdPath, resolve(outDir, SKILL_FILE));
|
|
95
|
+
console.log(`WROTE ${SKILL_DIR}/${SKILL_FILE}`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await program.parseAsync(process.argv);
|
package/src/config.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
AssetCatalog,
|
|
3
|
+
AssetEntry,
|
|
4
|
+
AssetSlot,
|
|
5
|
+
Config,
|
|
6
|
+
ContentSlot,
|
|
7
|
+
Deck,
|
|
8
|
+
DeckStep,
|
|
9
|
+
Layout,
|
|
10
|
+
ParagraphSelector,
|
|
11
|
+
ThemeConfig,
|
|
12
|
+
} from "@tycoworks/bon-core";
|
|
13
|
+
export { AssetType, Fit, generate, SlotType } from "@tycoworks/bon-core";
|
|
14
|
+
export { defineConfig } from "./config.ts";
|
|
15
|
+
export type { ManifestOptions } from "./manifest.ts";
|
|
16
|
+
export { generateManifest } from "./manifest.ts";
|
|
17
|
+
export { smokeSteps } from "./smoke.ts";
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Config } from "@tycoworks/bon-core";
|
|
2
|
+
|
|
3
|
+
export type ManifestOptions = {
|
|
4
|
+
build?: { command: string };
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type ManifestContentSlot = {
|
|
8
|
+
key: string;
|
|
9
|
+
type: string;
|
|
10
|
+
limit?: { maxChars?: number; maxLines?: number; maxItems?: number };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type ManifestAssetSlot = {
|
|
14
|
+
key: string;
|
|
15
|
+
accepts: string;
|
|
16
|
+
required?: true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type ManifestLayout = {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
whenToUse: string;
|
|
23
|
+
whenNotToUse: string;
|
|
24
|
+
contentSlots: ManifestContentSlot[];
|
|
25
|
+
assetSlots: ManifestAssetSlot[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ManifestAssetEntry = {
|
|
29
|
+
path: string;
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
whenToUse?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type Manifest = {
|
|
36
|
+
version: 1;
|
|
37
|
+
layouts: ManifestLayout[];
|
|
38
|
+
assets: Record<string, Record<string, ManifestAssetEntry>>;
|
|
39
|
+
build: {
|
|
40
|
+
command: string;
|
|
41
|
+
deckFormat: "json";
|
|
42
|
+
deckSchema: {
|
|
43
|
+
output: "string";
|
|
44
|
+
steps: "array of { layout, content, assets }";
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function stripContentSlot(slot: { key: string; type: string; limit?: object }): ManifestContentSlot {
|
|
50
|
+
const result: ManifestContentSlot = { key: slot.key, type: slot.type };
|
|
51
|
+
if (slot.limit) {
|
|
52
|
+
result.limit = slot.limit;
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function stripAssetSlot(slot: { key: string; accepts: string; required?: boolean }): ManifestAssetSlot {
|
|
58
|
+
const result: ManifestAssetSlot = { key: slot.key, accepts: slot.accepts };
|
|
59
|
+
if (slot.required) {
|
|
60
|
+
result.required = true;
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function generateManifest(config: Config, options?: ManifestOptions): string {
|
|
66
|
+
const layouts: ManifestLayout[] = config.layouts.map((layout) => ({
|
|
67
|
+
name: layout.name,
|
|
68
|
+
description: layout.description,
|
|
69
|
+
whenToUse: layout.whenToUse,
|
|
70
|
+
whenNotToUse: layout.whenNotToUse,
|
|
71
|
+
contentSlots: layout.contentSlots.map(stripContentSlot),
|
|
72
|
+
assetSlots: (layout.assetSlots ?? []).map(stripAssetSlot),
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
const assets: Record<string, Record<string, ManifestAssetEntry>> = {};
|
|
76
|
+
for (const [category, entries] of Object.entries(config.assets)) {
|
|
77
|
+
assets[category] = {};
|
|
78
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
79
|
+
const manifestEntry: ManifestAssetEntry = {
|
|
80
|
+
path: entry.path,
|
|
81
|
+
type: entry.type,
|
|
82
|
+
description: entry.description,
|
|
83
|
+
};
|
|
84
|
+
if (entry.whenToUse) {
|
|
85
|
+
manifestEntry.whenToUse = entry.whenToUse;
|
|
86
|
+
}
|
|
87
|
+
assets[category][name] = manifestEntry;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const manifest: Manifest = {
|
|
92
|
+
version: 1,
|
|
93
|
+
layouts,
|
|
94
|
+
assets,
|
|
95
|
+
build: {
|
|
96
|
+
command: options?.build?.command ?? "npx bon build",
|
|
97
|
+
deckFormat: "json",
|
|
98
|
+
deckSchema: {
|
|
99
|
+
output: "string",
|
|
100
|
+
steps: "array of { layout, content, assets }",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return JSON.stringify(manifest, null, 2);
|
|
106
|
+
}
|
package/src/smoke.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Config, DeckStep } from "@tycoworks/bon-core";
|
|
2
|
+
import { SlotType } from "@tycoworks/bon-core";
|
|
3
|
+
|
|
4
|
+
function pickAsset(config: Config, slot: { key: string; accepts: string }): string | undefined {
|
|
5
|
+
for (const group of Object.values(config.assets)) {
|
|
6
|
+
for (const entry of Object.values(group)) {
|
|
7
|
+
if (entry.type === slot.accepts) return entry.path;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function smokeSteps(config: Config): DeckStep[] {
|
|
14
|
+
return config.layouts.map((layout) => {
|
|
15
|
+
const content: Record<string, string | string[]> = {};
|
|
16
|
+
for (const s of layout.contentSlots) {
|
|
17
|
+
if (s.type === SlotType.Prose) {
|
|
18
|
+
content[s.key] = ["Sample intro line for this block.", "- First point", "- Second point"];
|
|
19
|
+
} else if (s.type === SlotType.Lines) {
|
|
20
|
+
content[s.key] = ["Sample Name", "Sample Role, Company"];
|
|
21
|
+
} else if (s.key.includes("value")) {
|
|
22
|
+
content[s.key] = "42%";
|
|
23
|
+
} else {
|
|
24
|
+
content[s.key] = `Sample ${s.key.replace(/_/g, " ")}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const assets: Record<string, string> = {};
|
|
28
|
+
for (const a of layout.assetSlots ?? []) {
|
|
29
|
+
const path = pickAsset(config, a);
|
|
30
|
+
if (path) assets[a.key] = path;
|
|
31
|
+
}
|
|
32
|
+
return { layout: layout.name, content, assets };
|
|
33
|
+
});
|
|
34
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"lib": ["es2023"],
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"moduleResolution": "nodenext",
|
|
7
|
+
"allowImportingTsExtensions": true,
|
|
8
|
+
"verbatimModuleSyntax": true,
|
|
9
|
+
"erasableSyntaxOnly": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationDir": "dist",
|
|
12
|
+
"emitDeclarationOnly": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"composite": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*.ts"],
|
|
18
|
+
"references": [{ "path": "../core" }]
|
|
19
|
+
}
|