@miragon/create-slidev-deck 0.0.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 ADDED
@@ -0,0 +1,94 @@
1
+ # @miragon/create-slidev-deck
2
+
3
+ Scaffold a lean [Miragon Slidev deck](https://github.com/Miragon/slidev-deck-template): a fresh
4
+ repo with **only** the files a deck needs, and `@miragon/slidev-toolkit` pulled from npm instead of
5
+ vendored.
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ npm create @miragon/slidev-deck@latest my-talk
11
+ # equivalently:
12
+ npx @miragon/create-slidev-deck@latest my-talk
13
+ ```
14
+
15
+ Then:
16
+
17
+ ```bash
18
+ cd my-talk
19
+ npm install
20
+ npm run dev
21
+ ```
22
+
23
+ ## Options
24
+
25
+ | Flag | Default | Effect |
26
+ |---|---|---|
27
+ | `--ref <tag\|sha\|branch>` | this version's release tag | which template snapshot to fetch the skeleton from |
28
+ | `--toolkit-version <x>` | pinned default | pin `@miragon/slidev-toolkit` in the generated `package.json` |
29
+ | `-v`, `--version` | — | print the `create-slidev-deck` version |
30
+ | `-h`, `--help` | — | show usage |
31
+
32
+ Launched via `pnpm create` / `yarn create` / `bun create`, the printed next steps use that package manager.
33
+
34
+ ## What it emits
35
+
36
+ Running `npm create @miragon/slidev-deck@latest my-talk` produces:
37
+
38
+ ```
39
+ my-talk/
40
+ ├── deck/ # your content — this is what you edit
41
+ │ ├── slides.md # entry: cover + one src: import per chapter + closing
42
+ │ ├── vite.config.ts # chapter-resources plugin + shaders pre-bundle
43
+ │ ├── public/og-image.png
44
+ │ ├── chapter/
45
+ │ │ ├── 01-intro/01-intro.md
46
+ │ │ ├── 02-slidev/… # each chapter: NN-name.md + its own resources/
47
+ │ │ ├── 03-theme/…
48
+ │ │ ├── 04-diagrams/… # .bpmn, .dmn, .excalidraw.svg demos
49
+ │ │ └── 05-authoring/…
50
+ │ └── README.md
51
+ ├── verify/ # brand guardrails — `npm run verify`
52
+ │ ├── rules/ # sanctioned-layout, no-raw-html, excalidraw checks…
53
+ │ ├── slides.spec.ts
54
+ │ └── …
55
+ ├── .claude/skills/ # authoring guidance for Claude Code
56
+ │ ├── slides/
57
+ │ └── excalidraw/
58
+ ├── .github/workflows/ # only Build Deck + Pin Check
59
+ │ ├── build-and-deploy.yml
60
+ │ └── pin-check.yml
61
+ ├── CLAUDE.md # design-system rules, auto-loaded by Claude Code
62
+ ├── .npmrc # save-exact=true
63
+ ├── .gitignore
64
+ ├── README.md # ← generated overlay (deck-focused)
65
+ └── package.json # ← generated overlay (standalone, toolkit pinned, no workspace)
66
+ ```
67
+
68
+ Everything except the two `← generated overlay` files is the shared **skeleton**, fetched from the
69
+ template repo (a single source of design truth). It never emits the template-only infrastructure:
70
+ `packages/`, the release-please / pr-title workflows and config, `LICENSE`, or `netlify.toml`.
71
+
72
+ ## How it stays a single source of truth
73
+
74
+ The skeleton is **fetched**, not duplicated here — pinned to the git tag that matches this package's
75
+ version, so a given `create-slidev-deck` version always produces an identical deck. Template content
76
+ changes reach new decks when a new `create-slidev-deck` version is released (a deliberate snapshot),
77
+ keeping the two release lines independent.
78
+
79
+ **Nothing pins a dependency version by hand in this package.** The generated `package.json` is
80
+ *derived* from the fetched skeleton's own manifests at scaffold time:
81
+
82
+ - Slidev runtime deps (`@slidev/cli`, the addons, `vue`) come from the reference `deck/package.json`.
83
+ - The verify tooling (`@playwright/test`, `playwright-chromium`) and the verify scripts come from the
84
+ root `package.json`.
85
+ - Only `@miragon/slidev-toolkit` comes from this package's own pinned devDependency (the reference
86
+ deck resolves the toolkit via a workspace symlink, so it has no version to read). `--toolkit-version`
87
+ overrides it.
88
+
89
+ All of those manifests are kept current by the monorepo's Dependabot, so a freshly-scaffolded deck
90
+ always gets the versions the reference deck currently uses — with no version list to maintain in this
91
+ package.
92
+
93
+ For local development, set `CREATE_DECK_SKELETON=/path/to/template-checkout` to copy the skeleton
94
+ from a local checkout instead of fetching a tag.
package/bin/index.mjs ADDED
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ // Scaffold a lean Miragon Slidev deck.
3
+ //
4
+ // Emits ONLY what a deck needs: the shared skeleton (deck/, .claude/, CLAUDE.md,
5
+ // verify/, .npmrc, .gitignore, the two CI workflows) plus a generated overlay (a
6
+ // standalone package.json + a deck-focused README). The toolkit is NOT vendored:
7
+ // it is added as an exact-pinned npm dependency and consumed via the theme.
8
+ //
9
+ // The generated package.json is DERIVED from the fetched skeleton's own manifests
10
+ // (deck/package.json for the Slidev runtime deps, the root package.json for the
11
+ // verify tooling), so the deck's versions are whatever the reference deck currently
12
+ // pins — kept current by the monorepo's Dependabot, nothing to hand-maintain here.
13
+ // Only the toolkit version comes from this package (the reference deck resolves it
14
+ // via a workspace symlink, so it has no version to read).
15
+ //
16
+ // Reproducibility: the skeleton is fetched from this template repo pinned to the
17
+ // tag that matches THIS package's version, so a given create-slidev-deck version
18
+ // always emits an identical deck. `--ref` and `--toolkit-version` override.
19
+
20
+ import { existsSync, readFileSync, statSync } from 'node:fs'
21
+ import { cp, mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
22
+ import { tmpdir } from 'node:os'
23
+ import { basename, dirname, join, resolve } from 'node:path'
24
+ import { fileURLToPath } from 'node:url'
25
+ import { parseArgs } from 'node:util'
26
+ import { downloadTemplate } from 'giget'
27
+
28
+ const HERE = dirname(fileURLToPath(import.meta.url))
29
+ const REPO = 'Miragon/slidev-deck-template'
30
+ const SELF = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
31
+
32
+ // The toolkit version written into the generated package.json: this package's own
33
+ // pinned @miragon/slidev-toolkit devDependency, so Dependabot keeps the default
34
+ // current and a given create-slidev-deck version emits a byte-identical deck.
35
+ const TOOLKIT_VERSION = SELF.devDependencies['@miragon/slidev-toolkit']
36
+
37
+ // Paths copied verbatim from the fetched skeleton into the new deck. Anything not
38
+ // listed (packages/, release-please*, pr-title.yml, LICENSE, netlify.toml, docs/)
39
+ // is intentionally left out — that is the whole point of the scaffold.
40
+ const SKELETON = [
41
+ 'deck',
42
+ '.claude',
43
+ 'verify',
44
+ 'CLAUDE.md',
45
+ '.npmrc',
46
+ '.gitignore',
47
+ '.github/workflows/build-and-deploy.yml',
48
+ '.github/workflows/pin-check.yml',
49
+ ]
50
+
51
+ // Skeleton files READ (not copied) to derive the generated package.json.
52
+ const MANIFESTS = ['package.json', 'deck/package.json']
53
+
54
+ // Skeleton files that must not survive into a standalone deck. The deck's own
55
+ // workspace sub-manifest is replaced by the generated root package.json below.
56
+ const PRUNE = ['deck/package.json']
57
+
58
+ // The verify tooling the generated deck needs, pulled (by name) from the root
59
+ // package.json's devDependencies so the versions track the reference repo.
60
+ const VERIFY_DEV_DEPS = ['@playwright/test', 'playwright-chromium']
61
+
62
+ /** Parse argv with node:util — it validates unknown options and missing values for us. */
63
+ function parseCliArgs(argv) {
64
+ const { values, positionals } = parseArgs({
65
+ args: argv,
66
+ allowPositionals: true,
67
+ options: {
68
+ ref: { type: 'string' },
69
+ 'toolkit-version': { type: 'string' },
70
+ version: { type: 'boolean', short: 'v' },
71
+ help: { type: 'boolean', short: 'h' },
72
+ },
73
+ })
74
+ if (positionals.length > 1) throw new Error(`Unexpected argument: ${positionals[1]}`)
75
+ return {
76
+ target: positionals[0],
77
+ ref: values.ref,
78
+ toolkitVersion: values['toolkit-version'],
79
+ version: values.version,
80
+ help: values.help,
81
+ }
82
+ }
83
+
84
+ const USAGE = `Usage: npm create @miragon/slidev-deck@latest <dir> [options]
85
+
86
+ <dir> target directory for the new deck (must be empty)
87
+
88
+ Options:
89
+ --ref <tag|sha|branch> skeleton source ref (default: this version's release tag)
90
+ --toolkit-version <x> pin @miragon/slidev-toolkit to <x> (default: ${TOOLKIT_VERSION})
91
+ -v, --version print the create-slidev-deck version
92
+ -h, --help show this help
93
+
94
+ Env:
95
+ CREATE_DECK_SKELETON=<dir> copy the skeleton from a local checkout instead of
96
+ fetching (development / offline).`
97
+
98
+ /** Detect the package manager the initializer was launched with (npm create / pnpm create / …). */
99
+ function packageManager() {
100
+ const name = (process.env.npm_config_user_agent ?? '').split('/')[0]
101
+ return ['pnpm', 'yarn', 'bun'].includes(name) ? name : 'npm'
102
+ }
103
+
104
+ /** Fetch (or locally copy) the template repo into a scratch dir; return its path. */
105
+ async function fetchSkeleton(ref) {
106
+ const local = process.env.CREATE_DECK_SKELETON
107
+ const scratch = await mkdtemp(join(tmpdir(), 'create-slidev-deck-'))
108
+ if (!local) {
109
+ await downloadTemplate(`github:${REPO}#${ref}`, { dir: scratch, force: true })
110
+ return scratch
111
+ }
112
+ // Local mode brings the copy whitelist plus the manifests we read to build the
113
+ // package.json (giget's full download already contains them).
114
+ const src = resolve(local)
115
+ for (const rel of [...SKELETON, ...MANIFESTS]) {
116
+ const from = join(src, rel)
117
+ if (existsSync(from)) {
118
+ await mkdir(dirname(join(scratch, rel)), { recursive: true })
119
+ await cp(from, join(scratch, rel), { recursive: true })
120
+ }
121
+ }
122
+ return scratch
123
+ }
124
+
125
+ /** Turn a target directory into a valid, lowercase npm package name. */
126
+ function deckNameFrom(dir) {
127
+ return basename(dir).toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') || 'my-deck'
128
+ }
129
+
130
+ /** The verify tooling versions, read (by name) from the root package.json's devDependencies. */
131
+ function verifyDevDeps(rootPkg) {
132
+ const deps = {}
133
+ for (const name of VERIFY_DEV_DEPS) {
134
+ const version = rootPkg.devDependencies?.[name]
135
+ if (!version) throw new Error(`Root package.json is missing devDependency ${name}`)
136
+ deps[name] = version
137
+ }
138
+ return deps
139
+ }
140
+
141
+ /** Build the standalone deck package.json from the fetched skeleton's manifests. */
142
+ function buildPackageJson(scratch, deckName, toolkitVersion) {
143
+ const readManifest = (rel) => JSON.parse(readFileSync(join(scratch, rel), 'utf8'))
144
+ const deckPkg = readManifest('deck/package.json')
145
+ const rootPkg = readManifest('package.json')
146
+ const pkg = {
147
+ name: deckName,
148
+ type: 'module',
149
+ private: true,
150
+ scripts: {
151
+ dev: 'slidev deck/slides.md --open',
152
+ build: 'slidev build deck/slides.md --out ../dist',
153
+ export: 'slidev export deck/slides.md',
154
+ verify: rootPkg.scripts.verify,
155
+ 'verify:source': rootPkg.scripts['verify:source'],
156
+ },
157
+ dependencies: { '@miragon/slidev-toolkit': toolkitVersion, ...deckPkg.dependencies },
158
+ devDependencies: verifyDevDeps(rootPkg),
159
+ }
160
+ return JSON.stringify(pkg, null, 2) + '\n'
161
+ }
162
+
163
+ /** Exit unless `target` is absent or an empty directory. Returns whether it already exists. */
164
+ async function ensureEmptyTarget(target) {
165
+ if (!existsSync(target)) return false
166
+ const refuse = (why) => {
167
+ console.error(`Refusing to scaffold: ${target} ${why}.`)
168
+ process.exit(1)
169
+ }
170
+ if (!statSync(target).isDirectory()) refuse('exists and is not a directory')
171
+ if ((await readdir(target)).length > 0) refuse('exists and is not empty')
172
+ return true
173
+ }
174
+
175
+ /** Copy each whitelisted skeleton path into the target. */
176
+ async function copySkeleton(scratch, target, ref) {
177
+ for (const rel of SKELETON) {
178
+ const from = join(scratch, rel)
179
+ if (!existsSync(from)) throw new Error(`Skeleton is missing ${rel} (ref ${ref}).`)
180
+ const to = join(target, rel)
181
+ await mkdir(dirname(to), { recursive: true })
182
+ await cp(from, to, { recursive: true })
183
+ }
184
+ }
185
+
186
+ /** Undo a half-written target so a retry is not blocked by the empty-dir guard. */
187
+ async function rollback(target, preexisting) {
188
+ if (!preexisting) return rm(target, { recursive: true, force: true })
189
+ for (const entry of await readdir(target).catch(() => [])) {
190
+ await rm(join(target, entry), { recursive: true, force: true })
191
+ }
192
+ }
193
+
194
+ /** Assemble the deck in `target`: skeleton, prune, then the generated overlay. */
195
+ async function layDownDeck({ scratch, target, deckName, toolkitVersion, ref, preexisting }) {
196
+ try {
197
+ await mkdir(target, { recursive: true })
198
+ await copySkeleton(scratch, target, ref)
199
+ for (const rel of PRUNE) await rm(join(target, rel), { force: true })
200
+
201
+ const packageJson = buildPackageJson(scratch, deckName, toolkitVersion)
202
+ await writeFile(join(target, 'package.json'), packageJson)
203
+ await cp(join(HERE, '..', 'templates', 'README.md'), join(target, 'README.md'))
204
+ } catch (err) {
205
+ await rollback(target, preexisting)
206
+ throw err
207
+ }
208
+ }
209
+
210
+ function printNextSteps(dir) {
211
+ const pm = packageManager()
212
+ console.log(`
213
+ Done. Your deck is ready in ${dir}
214
+
215
+ Next steps:
216
+ cd ${dir}
217
+ ${pm} install
218
+ ${pm} run dev
219
+
220
+ Build with '${pm} run build', check brand guardrails with '${pm} run verify'.`)
221
+ }
222
+
223
+ async function main() {
224
+ const opts = parseCliArgs(process.argv.slice(2))
225
+ if (opts.version) {
226
+ console.log(SELF.version)
227
+ return
228
+ }
229
+ if (opts.help || !opts.target) {
230
+ console.log(USAGE)
231
+ process.exit(opts.help ? 0 : 1)
232
+ }
233
+
234
+ const target = resolve(opts.target)
235
+ const preexisting = await ensureEmptyTarget(target)
236
+ const ref = opts.ref ?? `create-slidev-deck-v${SELF.version}`
237
+ const toolkitVersion = opts.toolkitVersion ?? TOOLKIT_VERSION
238
+ const deckName = deckNameFrom(target)
239
+
240
+ const source = process.env.CREATE_DECK_SKELETON ? 'local checkout' : `${REPO}#${ref}`
241
+ console.log(`Scaffolding ${deckName} from ${source} ...`)
242
+
243
+ const scratch = await fetchSkeleton(ref)
244
+ try {
245
+ await layDownDeck({ scratch, target, deckName, toolkitVersion, ref, preexisting })
246
+ } finally {
247
+ await rm(scratch, { recursive: true, force: true })
248
+ }
249
+
250
+ printNextSteps(opts.target)
251
+ }
252
+
253
+ main().catch((err) => {
254
+ console.error(err instanceof Error ? err.message : err)
255
+ process.exit(1)
256
+ })
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@miragon/create-slidev-deck",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "description": "Scaffold a lean Miragon Slidev deck: only the files a deck needs, with @miragon/slidev-toolkit pulled from npm. Run via `npm create @miragon/slidev-deck`.",
6
+ "keywords": [
7
+ "slidev",
8
+ "miragon",
9
+ "create",
10
+ "scaffold",
11
+ "template"
12
+ ],
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Miragon/slidev-deck-template.git",
17
+ "directory": "packages/create-deck"
18
+ },
19
+ "homepage": "https://github.com/Miragon/slidev-deck-template/tree/main/packages/create-deck#readme",
20
+ "bugs": "https://github.com/Miragon/slidev-deck-template/issues",
21
+ "bin": {
22
+ "create-slidev-deck": "bin/index.mjs"
23
+ },
24
+ "scripts": {
25
+ "test": "node --test"
26
+ },
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "dependencies": {
31
+ "giget": "3.3.0"
32
+ },
33
+ "devDependencies": {
34
+ "@miragon/slidev-toolkit": "1.2.1"
35
+ },
36
+ "files": [
37
+ "bin",
38
+ "templates"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ }
44
+ }
@@ -0,0 +1,76 @@
1
+ # __DECK_NAME__
2
+
3
+ A [Slidev](https://sli.dev) presentation in the Miragon corporate design, scaffolded from
4
+ [`@miragon/slidev-deck-template`](https://github.com/Miragon/slidev-deck-template). The design
5
+ system ships as the [`@miragon/slidev-toolkit`](https://www.npmjs.com/package/@miragon/slidev-toolkit)
6
+ npm package — you fill in the content, the theme is fixed.
7
+
8
+ ## Quick start
9
+
10
+ Needs **Node 20+** and a modern browser (WebGL2 for the animated background, with a CSS-gradient fallback).
11
+
12
+ ```bash
13
+ npm install
14
+ npm run dev # opens the deck on http://localhost:3030 with live reload
15
+ ```
16
+
17
+ Edit files under `deck/` and save — the preview updates instantly. Every demo slide carries a
18
+ `REQUIRED / OPTIONAL / LIMIT / HOW TO USE` comment block: your in-place authoring guide.
19
+
20
+ ## Layout
21
+
22
+ | Path | What it is |
23
+ |---|---|
24
+ | `deck/` | **Your content** — `slides.md` is the entry (cover + one `src:` import per chapter + closing); each chapter is a folder `deck/chapter/NN-name/` with its own `resources/`. |
25
+ | `verify/` | Brand guardrails (`npm run verify`) — every slide declares a sanctioned layout, cards stay white, no em-dashes, headings black, diagrams light/transparent. |
26
+ | `CLAUDE.md` + `.claude/skills/` | Authoring guidance for Claude Code, so a session knows the design system on the first prompt. |
27
+ | `.github/workflows/` | **Build Deck** and **Pin Check** run on every push and PR. |
28
+
29
+ The deck consumes the toolkit by name (`theme: '@miragon/slidev-toolkit'`); you never touch the theme.
30
+
31
+ ## Commands
32
+
33
+ | Command | Result |
34
+ |---|---|
35
+ | `npm run dev` | Live preview on `:3030`; `p` for presenter mode, `o` for overview |
36
+ | `npm run build` | Static `dist/` you can host anywhere |
37
+ | `npm run export` | `slidev-exported.pdf` locally (needs Chromium) |
38
+ | `npm run verify` | Screenshot + checklist per slide against the design rules |
39
+
40
+ ## Next steps
41
+
42
+ 1. Replace the demo content under `deck/`; keep the comment-block guardrails.
43
+ 2. Point the `seoMeta` block in `deck/slides.md` at your own domain, or delete it.
44
+ 3. Commit the generated `package-lock.json` after the first `npm install` so CI (`npm ci`) is reproducible.
45
+ 4. Or open the repo with Claude Code and let it draft the first pass from your outline.
46
+
47
+ Authoring conventions live in the `slides` skill (`.claude/skills/slides/`). Starter prompts:
48
+
49
+ > Outline a 30-minute talk on [topic] using `cover`, three `section` chapters with two `content` slides each, then `closing`.
50
+
51
+ > Build a `goodbad` slide asking "Which error message helps the user more?" Make Model A the avoid side.
52
+
53
+ ## Staying up to date
54
+
55
+ Your dependencies (`@miragon/slidev-toolkit`, `@slidev/cli`, the addons) are exact-pinned so installs stay
56
+ reproducible. For a deck you keep around, enable [Dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates)
57
+ so it opens PRs when new versions ship — you get the latest toolkit and Slidev without hunting for updates,
58
+ and **Build Deck** + **Pin Check** gate each PR. Add `.github/dependabot.yml`:
59
+
60
+ ```yaml
61
+ version: 2
62
+ updates:
63
+ - package-ecosystem: npm
64
+ directory: /
65
+ schedule:
66
+ interval: weekly
67
+ groups:
68
+ npm:
69
+ patterns: ["*"]
70
+ - package-ecosystem: github-actions
71
+ directory: /
72
+ schedule:
73
+ interval: monthly
74
+ ```
75
+
76
+ For a one-off talk you can skip this — the pinned versions keep working as-is.