pptgenerator-cli
v0.1.0
Published
Slide outline (Markdown / JSON) → HTML preview → PPTX, Manus-style. Outline-first authoring with Tailwind + Iconify CDNs and a PptxGenJS image strategy backed by headless chromium.
Maintainers
Readme
pptgenerator-cli
Slide outline (Markdown / JSON) → HTML preview → PPTX, Manus-style.
pptgenerator-cli is a Node.js + TypeScript pipeline that takes a slide outline
and produces:
- a self-contained HTML preview styled with Tailwind (via CDN) and Iconify icons — the same visual you ship to PowerPoint;
- a real .pptx file built from that HTML by screenshotting each slide and embedding it full-bleed (the image strategy). A future structured strategy will produce editable PPT shapes.
The whole flow is designed around a single in-memory contract — the
SlideDeck IR — so adding a new input format = new parser, and adding a new
output format = new renderer.
deck.md / deck.json ─▶ parsers/ ─▶ SlideDeck IR ─┬─▶ renderers/html/ ─▶ deck.preview.html
└─▶ renderers/pptx/ ─▶ deck.pptx
(image strategy)Install
pnpm install
pnpm playwright:install # downloads chromium for the image strategyCLI
# Render the HTML preview (open it directly in any browser)
pnpm render-html examples/deck.md -o out/deck.html
# Build a .pptx (default: image strategy)
pnpm build-pptx examples/deck.md -o out/deck.pptx
# Future: editable shapes (currently throws "not implemented yet")
pnpm build-pptx examples/deck.md -o out/deck.pptx --strategy structuredLibrary
import { loadDeck, renderHtml, buildPptx } from "pptgenerator-cli";
const deck = loadDeck("examples/deck.md"); // auto-detects md vs json
const html = renderHtml(deck); // string
await buildPptx(deck, { outputPath: "deck.pptx" }); // image strategy by defaultMarkdown convention
Slides are separated by a blank-line --- thematic break. Inside a slide a
blank-line ::: line splits a two-column layout.
---
title: My Deck
author: Yuki
theme: default
---
# Title slide
## Optional subtitle
---
## Section divider
---
## A content slide
- :icon[lucide:rocket] Bullets can carry an inline icon
- Plain bullet
- More text
---
## Two-column slide
- left bullet 1
- left bullet 2
:::
- right bullet 1
- right bullet 2Theme names accepted by frontmatter: default, dark, minimal.
JSON shape
examples/deck.json mirrors the IR exactly. The IR is validated with zod
(src/ir/schema.ts) and the TypeScript types live in
src/ir/types.ts.
Icons (Iconify CDN)
We never bundle SVGs. The HTML renderer emits
<img src="https://api.iconify.design/lucide/rocket.svg?width=48" />so any of Iconify's 200,000+ icons can be referenced via prefix:slug
(lucide:rocket, mdi:account, tabler:wand, …). The image strategy
pre-renders the HTML in headless chromium, so the icons end up baked into the
.pptx pixels — your audience never needs internet access.
Project layout
src/
cli.ts # `pptgenerator-cli` binary
index.ts # library exports
ir/{types,schema}.ts # SlideDeck IR + zod validator
parsers/{markdown,json,index}.ts
renderers/
html/{render,template,icons}.ts
pptx/{render,strategy,image-strategy,structured-strategy}.ts
examples/{deck.md,deck.json}
tests/ # vitest specsScripts
| script | purpose |
| ------------------------- | ------------------------------------------------------------------- |
| pnpm dev <args> | run the CLI from source via tsx |
| pnpm render-html | shorthand for dev render-html |
| pnpm build-pptx | shorthand for dev build-pptx |
| pnpm build | bundle to dist/ with tsup |
| pnpm typecheck | tsc --noEmit |
| pnpm test | vitest — Layers A, B, B+ unit, D (cheap, default-on) |
| pnpm test:integration | sets PPT_INTEGRATION=1 — adds Layer B+ PPTX media audit |
| pnpm test:visual | sets PPT_VISUAL=1 — Layer C visual regression (run inside Docker) |
| pnpm test:watch | vitest in watch mode |
| pnpm lint | ESLint over src/ and tests/ |
| pnpm format[:check] | Prettier write / check |
| pnpm playwright:install | download chromium (one-off, required for image strategy) |
Evaluation harness
Because the pipeline is HTML → PNG → PPTX, regressions can hide at multiple
layers. tests/ is organized as a layered harness so cheap checks run on
every push and expensive ones only when explicitly invoked.
| layer | what it guards | gate |
| ------ | ---------------------------------------------------------- | --------------------------------------------- |
| A | IR shape after parsing (tests/snapshots/parsers.snap) | always on |
| B | HTML structure / Tailwind class output | always on |
| B+ | PPTX media + structural audit (unzip + magic bytes) | unit always; chromium leg PPT_INTEGRATION=1 |
| C | Pixel-level visual regression with pixelmatch | PPT_VISUAL=1, Docker only |
| D | --evaluation-mode swaps CDNs for vendored offline assets | always on (static checks) |
--evaluation-mode
Both subcommands accept --evaluation-mode. When set, the renderer rewrites
Tailwind + Iconify CDN refs to file:// URLs under vendored/:
pnpm build-pptx examples/deck.md -o out/deck.pptx --evaluation-modeUse it for air-gapped builds, deterministic CI runs, or to make Layer C visual baselines reproducible across machines.
Updating Layer C baselines (Docker)
Visual baselines depend on the exact chromium build, system fonts, and the Linux text-rendering stack — so they are produced and compared inside the shipped Docker image only:
docker build -t pptgenerator-cli:dev .
docker run --rm -e PPT_VISUAL_UPDATE=1 \
-v "$PWD":/work -w /app pptgenerator-cli:dev \
/app/node_modules/.bin/vitest run tests/visualCommit the regenerated PNGs under tests/visual/__baseline__/.
Distribution
Three publishing tracks ship with v* tags:
- npm (
pptgenerator-cli) —npx pptgenerator-cli build-pptx ...(release.yml). - Docker (GHCR
ghcr.io/chibayuki347/pptgenerator-cli) — the canonical environment for Layer C; also the engine behind the GitHub Action (docker.yml). - GitHub Action (
ChibaYuki347/pptgenerator-cli@v*) — Docker-based; just declareinput/outputand the action handles the rest. Seeaction.yml.
License
MIT.
