@honeydeck/honeydeck 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.
Files changed (144) hide show
  1. package/AGENTS.md +25 -0
  2. package/DEVELOPMENT.md +522 -0
  3. package/LICENSE +21 -0
  4. package/Readme.md +49 -0
  5. package/SPEC.md +88 -0
  6. package/docs/components.md +63 -0
  7. package/docs/configuration.md +91 -0
  8. package/docs/getting-started.md +116 -0
  9. package/docs/kit-authoring.md +207 -0
  10. package/docs/kits.md +387 -0
  11. package/docs/local-development.md +95 -0
  12. package/docs/mermaid.md +198 -0
  13. package/docs/mobile.md +108 -0
  14. package/docs/navigation.md +93 -0
  15. package/docs/next-steps.md +377 -0
  16. package/docs/pdf-export.md +91 -0
  17. package/docs/presenter-mode.md +104 -0
  18. package/docs/slides.md +130 -0
  19. package/docs/slidev-migration.md +42 -0
  20. package/docs/steps-and-reveals.md +171 -0
  21. package/package.json +134 -0
  22. package/skills/SPEC.md +21 -0
  23. package/skills/honeydeck/SKILL.md +65 -0
  24. package/skills/presentation-writing/SKILL.md +75 -0
  25. package/skills/slidev-migration/SKILL.md +153 -0
  26. package/src/SPEC.md +89 -0
  27. package/src/assets.d.ts +30 -0
  28. package/src/cli/SPEC.md +230 -0
  29. package/src/cli/args.ts +3 -0
  30. package/src/cli/banner.ts +9 -0
  31. package/src/cli/bin.js +5 -0
  32. package/src/cli/build.ts +229 -0
  33. package/src/cli/deck-path.ts +32 -0
  34. package/src/cli/dev.ts +263 -0
  35. package/src/cli/index.ts +126 -0
  36. package/src/cli/init.ts +369 -0
  37. package/src/cli/pdf.ts +923 -0
  38. package/src/cli/skill.ts +75 -0
  39. package/src/cli/templates/SPEC.md +70 -0
  40. package/src/cli/templates/deck-mdx.ts +15 -0
  41. package/src/cli/templates/package-json.ts +36 -0
  42. package/src/cli/templates/sparkle-button.ts +15 -0
  43. package/src/cli/templates/starter/components/SparkleButton.tsx +84 -0
  44. package/src/cli/templates/starter/deck.mdx +153 -0
  45. package/src/cli/templates/starter/styles.css +14 -0
  46. package/src/cli/templates/styles-css.ts +14 -0
  47. package/src/defaults.ts +1 -0
  48. package/src/layouts/ColorModeImage.tsx +55 -0
  49. package/src/layouts/SPEC.md +393 -0
  50. package/src/layouts/SlideFrame.tsx +48 -0
  51. package/src/layouts/bee/Blank.tsx +12 -0
  52. package/src/layouts/bee/Cover.tsx +70 -0
  53. package/src/layouts/bee/Default.tsx +42 -0
  54. package/src/layouts/bee/Image/Image.tsx +151 -0
  55. package/src/layouts/bee/Image/placeholder-dark.webp +0 -0
  56. package/src/layouts/bee/Image/placeholder-vertical-dark.webp +0 -0
  57. package/src/layouts/bee/Image/placeholder-vertical.webp +0 -0
  58. package/src/layouts/bee/Image/placeholder.webp +0 -0
  59. package/src/layouts/bee/ImageLeft.tsx +27 -0
  60. package/src/layouts/bee/ImageRight.tsx +27 -0
  61. package/src/layouts/bee/ImageSide.tsx +107 -0
  62. package/src/layouts/bee/Section.tsx +40 -0
  63. package/src/layouts/bee/TwoCol.tsx +108 -0
  64. package/src/layouts/bee/index.ts +40 -0
  65. package/src/layouts/clean/Blank.tsx +12 -0
  66. package/src/layouts/clean/Cover.tsx +58 -0
  67. package/src/layouts/clean/Default.tsx +33 -0
  68. package/src/layouts/clean/Image/Image.tsx +103 -0
  69. package/src/layouts/clean/ImageLeft.tsx +27 -0
  70. package/src/layouts/clean/ImageRight.tsx +27 -0
  71. package/src/layouts/clean/ImageSide.tsx +113 -0
  72. package/src/layouts/clean/Section.tsx +35 -0
  73. package/src/layouts/clean/TwoCol.tsx +63 -0
  74. package/src/layouts/clean/index.ts +40 -0
  75. package/src/layouts/index.ts +60 -0
  76. package/src/layouts/placeholders.ts +9 -0
  77. package/src/layouts/utils.ts +13 -0
  78. package/src/remark/SPEC.md +49 -0
  79. package/src/remark/h1-extract.ts +124 -0
  80. package/src/remark/index.ts +4 -0
  81. package/src/remark/shiki-code-blocks.ts +325 -0
  82. package/src/remark/step-numbering.ts +412 -0
  83. package/src/runtime/Deck.tsx +533 -0
  84. package/src/runtime/SPEC.md +256 -0
  85. package/src/runtime/SlideCanvas.tsx +95 -0
  86. package/src/runtime/TimelineContext.tsx +122 -0
  87. package/src/runtime/app-shell/index.html +31 -0
  88. package/src/runtime/app-shell/main.tsx +42 -0
  89. package/src/runtime/aspectRatio.ts +34 -0
  90. package/src/runtime/colorMode.ts +23 -0
  91. package/src/runtime/components/BrowserFrame.tsx +233 -0
  92. package/src/runtime/components/Button.tsx +57 -0
  93. package/src/runtime/components/CodeBlock.tsx +210 -0
  94. package/src/runtime/components/ColorModeCycleButton.tsx +59 -0
  95. package/src/runtime/components/ErrorBoundary.tsx +125 -0
  96. package/src/runtime/components/Keyboard.tsx +87 -0
  97. package/src/runtime/components/ListStyle.tsx +203 -0
  98. package/src/runtime/components/NavBar.tsx +223 -0
  99. package/src/runtime/components/NavBarButton.tsx +47 -0
  100. package/src/runtime/components/NavBarDivider.tsx +3 -0
  101. package/src/runtime/components/Notes.tsx +171 -0
  102. package/src/runtime/components/Reveal.tsx +82 -0
  103. package/src/runtime/components/RevealGroup.tsx +193 -0
  104. package/src/runtime/components/SPEC.md +263 -0
  105. package/src/runtime/components/SlideNumberBadge.tsx +11 -0
  106. package/src/runtime/components/TimelineSteps.tsx +115 -0
  107. package/src/runtime/components/index.ts +55 -0
  108. package/src/runtime/index.ts +42 -0
  109. package/src/runtime/inputOwnership.ts +68 -0
  110. package/src/runtime/keyboardTarget.ts +7 -0
  111. package/src/runtime/lastSlideRoute.ts +56 -0
  112. package/src/runtime/navigation.ts +211 -0
  113. package/src/runtime/router.ts +157 -0
  114. package/src/runtime/slideData.ts +137 -0
  115. package/src/runtime/sync.ts +267 -0
  116. package/src/runtime/types.ts +182 -0
  117. package/src/runtime/useKeyboardNav.ts +138 -0
  118. package/src/runtime/useSwipeNav.ts +257 -0
  119. package/src/runtime/views/DocsView.tsx +74 -0
  120. package/src/runtime/views/OverviewView.tsx +386 -0
  121. package/src/runtime/views/PresenterNotesPanel.tsx +76 -0
  122. package/src/runtime/views/PresenterView.tsx +340 -0
  123. package/src/runtime/views/SPEC.md +152 -0
  124. package/src/runtime/views/docs/ComponentsTab.tsx +178 -0
  125. package/src/runtime/views/docs/DocsHeader.tsx +101 -0
  126. package/src/runtime/views/docs/Intro.tsx +20 -0
  127. package/src/runtime/views/docs/LayoutsTab.tsx +324 -0
  128. package/src/runtime/views/docs/ThemeTab.tsx +110 -0
  129. package/src/runtime/views/index.ts +7 -0
  130. package/src/runtime/views/overviewGrid.ts +106 -0
  131. package/src/runtime/views/presenterPreview.ts +27 -0
  132. package/src/runtime/virtual-modules.d.ts +98 -0
  133. package/src/theme/SPEC.md +179 -0
  134. package/src/theme/base.css +623 -0
  135. package/src/theme/bee.css +35 -0
  136. package/src/theme/clean.css +38 -0
  137. package/src/vite-plugin/SPEC.md +114 -0
  138. package/src/vite-plugin/component-doc-crawler.ts +350 -0
  139. package/src/vite-plugin/deck-loader.ts +148 -0
  140. package/src/vite-plugin/index.ts +373 -0
  141. package/src/vite-plugin/layout-demo-crawler.ts +802 -0
  142. package/src/vite-plugin/splitter.ts +353 -0
  143. package/src/vite-plugin/token-manifest.ts +163 -0
  144. package/src/vite-plugin/virtual-modules.ts +587 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * `honeydeck build` β€” build the presentation as a deployable static SPA.
3
+ *
4
+ * ### Architecture
5
+ *
6
+ * Vite root is set to `src/runtime/app-shell/` so that `index.html` lives
7
+ * at the root and Vite outputs it as `dist/index.html`.
8
+ *
9
+ * Why APP_SHELL_DIR as root?
10
+ * Vite's default HTML entry resolution uses `<root>/index.html`. By setting
11
+ * root to the directory that *owns* our shell HTML we get clean output paths
12
+ * without having to configure `rollupOptions.input` to an unusual path.
13
+ *
14
+ * The `__HONEYDECK_MAIN_ENTRY__` placeholder in index.html is replaced with
15
+ * `./main.tsx` by the `buildHtmlPlugin`. Vite resolves this relative to the
16
+ * HTML file's location (APP_SHELL_DIR), finding `app-shell/main.tsx`. βœ“
17
+ *
18
+ * User project files (deck entry, CSS, components) are outside APP_SHELL_DIR
19
+ * but are accessed via absolute paths by `honeydeckPlugin`'s virtual module
20
+ * resolver and resolveId hooks. Rollup (used by Vite during build) has no
21
+ * file-system restrictions β€” all absolute paths are accessible.
22
+ *
23
+ * `outDir` points to `<userRoot>/dist` (outside the Vite root). Vite 5+
24
+ * allows this when `emptyOutDir: true` is set explicitly.
25
+ *
26
+ * ### Output structure
27
+ * ```
28
+ * dist/
29
+ * index.html
30
+ * assets/
31
+ * honeydeck-[hash].js
32
+ * honeydeck-[hash].css
33
+ * public/ ← copied from project's public/ (if present)
34
+ * ```
35
+ */
36
+
37
+ import { existsSync } from "node:fs";
38
+ import { dirname, resolve } from "node:path";
39
+ import { fileURLToPath } from "node:url";
40
+ import type { Plugin } from "vite";
41
+ import { build } from "vite";
42
+ import { loadDeck } from "#vite-plugin/deck-loader.ts";
43
+ import { honeydeckPlugin } from "#vite-plugin/index.ts";
44
+ import { hasHelpFlag } from "./args.ts";
45
+ import { formatCommandBanner } from "./banner.ts";
46
+ import {
47
+ type DeckPathOptions,
48
+ rejectRootOption,
49
+ resolveDeckPath,
50
+ validateDeckPath,
51
+ } from "./deck-path.ts";
52
+
53
+ const __filename = fileURLToPath(import.meta.url);
54
+ const __dirname = dirname(__filename);
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Paths
58
+ // ---------------------------------------------------------------------------
59
+
60
+ /** Absolute path to src/runtime/app-shell/ β€” used as Vite root for build. */
61
+ export const APP_SHELL_DIR = resolve(__dirname, "../runtime/app-shell");
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // HTML fix plugin β€” shared with pdf.ts via the export below
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /**
68
+ * Replaces `__HONEYDECK_MAIN_ENTRY__` in index.html with `./main.tsx`.
69
+ *
70
+ * During build (root = APP_SHELL_DIR), `./main.tsx` is resolved relative to
71
+ * the HTML file's directory, which is APP_SHELL_DIR β€” exactly where main.tsx
72
+ * lives. This allows the same index.html template to be used for both the
73
+ * dev server (which injects an `/@fs/` absolute path) and the production
74
+ * build (which uses a normal relative module path).
75
+ */
76
+ export function buildHtmlPlugin(): Plugin {
77
+ return {
78
+ name: "honeydeck:build-html",
79
+ // `order: 'pre'` ensures this hook runs before Vite's built-in
80
+ // `vite:build-html` plugin tries to resolve the script src as a module.
81
+ transformIndexHtml: {
82
+ order: "pre",
83
+ handler(html: string): string {
84
+ return html.replace("__HONEYDECK_MAIN_ENTRY__", "./main.tsx");
85
+ },
86
+ },
87
+ };
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Shared build function (also consumed by pdf.ts)
92
+ // ---------------------------------------------------------------------------
93
+
94
+ export type BuildConfig = {
95
+ /** User project root β€” where the deck entry, CSS, and components live. */
96
+ userRoot: string;
97
+ /** Entry MDX file path relative to userRoot. */
98
+ entry: string;
99
+ /** Absolute path to the output directory. */
100
+ outDir: string;
101
+ /**
102
+ * Vite log level.
103
+ * @default 'warn' Use 'error' for quiet PDF sub-builds.
104
+ */
105
+ logLevel?: "silent" | "error" | "warn" | "info";
106
+ };
107
+
108
+ /**
109
+ * Programmatic Vite build for Honeydeck presentations.
110
+ * Called by both `runBuild` (normal build) and `runPdf` (PDF sub-build).
111
+ */
112
+ export async function buildPresentation(config: BuildConfig): Promise<void> {
113
+ const { userRoot, entry, outDir, logLevel = "warn" } = config;
114
+
115
+ // Only pass publicDir when the project actually has a public/ folder,
116
+ // otherwise Vite logs a harmless-but-noisy "public dir does not exist" warning.
117
+ const publicDir = resolve(userRoot, "public");
118
+
119
+ await build({
120
+ // root = APP_SHELL_DIR: index.html is here, outputs to outDir/index.html.
121
+ root: APP_SHELL_DIR,
122
+ publicDir: existsSync(publicDir) ? publicDir : false,
123
+ logLevel,
124
+
125
+ build: {
126
+ outDir,
127
+ // Explicit true so Vite honours it even though outDir is outside the root.
128
+ emptyOutDir: true,
129
+ },
130
+
131
+ plugins: [
132
+ // Replace HTML placeholder before Vite bundles the entry script.
133
+ buildHtmlPlugin(),
134
+ // Virtual slides + MDX compilation + Tailwind β€” user files via userRoot.
135
+ ...honeydeckPlugin({ root: userRoot, entry }),
136
+ ],
137
+ });
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Arg parsing
142
+ // ---------------------------------------------------------------------------
143
+
144
+ export type BuildOptions = DeckPathOptions;
145
+
146
+ export function printBuildHelp(): void {
147
+ console.log(`
148
+ ✨ honeydeck build β€” build for production
149
+
150
+ Usage:
151
+ honeydeck build [options]
152
+
153
+ Options:
154
+ --deck <file.mdx> Deck entry file (default: ./deck.mdx)
155
+ -h, --help Show this help page
156
+
157
+ Examples:
158
+ honeydeck build
159
+ honeydeck build --deck talk.mdx
160
+ `);
161
+ }
162
+
163
+ function readOptionValue(
164
+ args: string[],
165
+ index: number,
166
+ option: string,
167
+ ): string {
168
+ const value = args[index + 1];
169
+ if (!value || value.startsWith("-")) {
170
+ console.error(`❌ Missing value for ${option}`);
171
+ process.exit(1);
172
+ }
173
+ return value;
174
+ }
175
+
176
+ export function parseBuildArgs(args: string[]): BuildOptions {
177
+ let deckPath: string | null = null;
178
+
179
+ for (let i = 0; i < args.length; i++) {
180
+ const arg = args[i];
181
+ if (arg === "--deck") {
182
+ const value = readOptionValue(args, i, arg);
183
+ validateDeckPath(value, arg);
184
+ deckPath = value;
185
+ i++;
186
+ } else if (arg === "--root") {
187
+ rejectRootOption();
188
+ }
189
+ }
190
+
191
+ return resolveDeckPath(deckPath ?? undefined);
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Public API
196
+ // ---------------------------------------------------------------------------
197
+
198
+ export async function runBuild(args: string[]): Promise<void> {
199
+ if (hasHelpFlag(args)) {
200
+ printBuildHelp();
201
+ return;
202
+ }
203
+
204
+ const { root, entry, deck } = parseBuildArgs(args);
205
+
206
+ // Count slides for the summary line β€” non-fatal if the deck is unreadable
207
+ // (the Vite build step will surface the real error with full context).
208
+ let slideCount = 0;
209
+ try {
210
+ const { slides } = loadDeck(deck);
211
+ slideCount = slides.length;
212
+ } catch {
213
+ // Non-fatal β€” Vite build will surface the actual error.
214
+ }
215
+
216
+ console.log(`\n${formatCommandBanner()}\n`);
217
+ console.log(" πŸ“¦ Building presentation…\n");
218
+
219
+ const outDir = resolve(root, "dist");
220
+
221
+ await buildPresentation({ userRoot: root, entry, outDir });
222
+
223
+ console.log("\n βœ… Build complete!");
224
+ console.log(" πŸ“ Output: dist/");
225
+ if (slideCount > 0) {
226
+ console.log(` πŸ“„ ${slideCount} slide${slideCount !== 1 ? "s" : ""}`);
227
+ }
228
+ console.log();
229
+ }
@@ -0,0 +1,32 @@
1
+ import { basename, dirname, extname, resolve } from "node:path";
2
+ import { DEFAULT_DECK_ENTRY } from "../defaults.ts";
3
+
4
+ export type DeckPathOptions = {
5
+ deck: string;
6
+ root: string;
7
+ entry: string;
8
+ };
9
+
10
+ export function resolveDeckPath(
11
+ deckPath = DEFAULT_DECK_ENTRY,
12
+ ): DeckPathOptions {
13
+ const deck = resolve(process.cwd(), deckPath);
14
+
15
+ return {
16
+ deck,
17
+ root: dirname(deck),
18
+ entry: basename(deck),
19
+ };
20
+ }
21
+
22
+ export function validateDeckPath(deckPath: string, option: string): void {
23
+ if (extname(deckPath) !== ".mdx") {
24
+ console.error(`❌ ${option} must point to an .mdx file: ${deckPath}`);
25
+ process.exit(1);
26
+ }
27
+ }
28
+
29
+ export function rejectRootOption(): never {
30
+ console.error("❌ --root is no longer supported. Use --deck <file.mdx>.");
31
+ process.exit(1);
32
+ }
package/src/cli/dev.ts ADDED
@@ -0,0 +1,263 @@
1
+ /**
2
+ * `honeydeck dev` β€” start the Vite development server.
3
+ *
4
+ * Architecture summary:
5
+ *
6
+ * Vite root = <deck dir>
7
+ * β†’ deck-local files (CSS, images, components) resolve naturally
8
+ *
9
+ * `honeydeck:app-shell` plugin
10
+ * β†’ intercepts GET / and serves src/runtime/app-shell/index.html
11
+ * β†’ replaces the MAIN_ENTRY placeholder with `/@fs/<abs-path>/main.tsx`
12
+ * β†’ Vite's /@fs/ handler serves files outside the configured root
13
+ *
14
+ * `honeydeckPlugin()` (virtual modules + MDX + Tailwind)
15
+ * β†’ splits the selected deck entry, serves virtual:honeydeck/* modules, compiles MDX
16
+ *
17
+ * Why this approach?
18
+ * Vite root controls where CSS/assets/components resolve. Setting it to
19
+ * the deck dir lets users `@import './styles.css'` etc. normally.
20
+ * The app shell (main.tsx, Deck.tsx) lives inside the honeydeck package and
21
+ * is accessed via /@fs/ β€” Vite's escape hatch for files outside the root.
22
+ */
23
+
24
+ import { readFileSync } from "node:fs";
25
+ import { dirname, resolve } from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+ import { createServer, type Plugin } from "vite";
28
+ import { honeydeckPlugin } from "#vite-plugin/index.ts";
29
+ import { hasHelpFlag } from "./args.ts";
30
+ import { formatCommandBanner } from "./banner.ts";
31
+ import {
32
+ type DeckPathOptions,
33
+ rejectRootOption,
34
+ resolveDeckPath,
35
+ validateDeckPath,
36
+ } from "./deck-path.ts";
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Paths (all resolved at module load time, before any async work)
40
+ // ---------------------------------------------------------------------------
41
+
42
+ const __filename = fileURLToPath(import.meta.url);
43
+ const __dirname = dirname(__filename);
44
+
45
+ /** Absolute path to src/runtime/app-shell/ */
46
+ const APP_SHELL_DIR = resolve(__dirname, "../runtime/app-shell");
47
+
48
+ /** Absolute path to src/runtime/app-shell/main.tsx */
49
+ const MAIN_TSX_PATH = resolve(APP_SHELL_DIR, "main.tsx");
50
+
51
+ /** Absolute path to src/runtime/app-shell/index.html (the template) */
52
+ const INDEX_HTML_PATH = resolve(APP_SHELL_DIR, "index.html");
53
+
54
+ /**
55
+ * Absolute path to the honeydeck package root (the directory that contains src/).
56
+ * Passed to `server.fs.allow` so that Vite's /@fs/ handler can serve
57
+ * files under src/ even when the Vite root is set to the deck dir.
58
+ */
59
+ const PACKAGE_ROOT = resolve(__dirname, "../..");
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Arg parsing
63
+ // ---------------------------------------------------------------------------
64
+
65
+ export type DevOptions = {
66
+ port: number;
67
+ open: boolean;
68
+ } & DeckPathOptions;
69
+
70
+ export function createDevServerConfig(port: number, open: boolean) {
71
+ return {
72
+ port,
73
+ open,
74
+ strictPort: false,
75
+ // Bind to all interfaces so phones/tablets on the same local network can
76
+ // open the printed Network URL.
77
+ host: "0.0.0.0",
78
+ } as const;
79
+ }
80
+
81
+ export function printDevHelp(): void {
82
+ console.log(`
83
+ ✨ honeydeck dev β€” start the development server
84
+
85
+ Usage:
86
+ honeydeck dev [options]
87
+
88
+ Options:
89
+ --deck <file.mdx> Deck entry file (default: ./deck.mdx)
90
+ -p, --port <n> Dev server port (default: 4200)
91
+ -o, --open Open browser on start
92
+ -h, --help Show this help page
93
+
94
+ Examples:
95
+ honeydeck dev
96
+ honeydeck dev --open
97
+ honeydeck dev --deck talk.mdx --port 8080
98
+ `);
99
+ }
100
+
101
+ function readOptionValue(
102
+ args: string[],
103
+ index: number,
104
+ option: string,
105
+ ): string {
106
+ const value = args[index + 1];
107
+ if (!value || value.startsWith("-")) {
108
+ console.error(`❌ Missing value for ${option}`);
109
+ process.exit(1);
110
+ }
111
+ return value;
112
+ }
113
+
114
+ export function parseDevArgs(args: string[]): DevOptions {
115
+ let port = 4200;
116
+ let open = false;
117
+ let deckPath: string | null = null;
118
+
119
+ for (let i = 0; i < args.length; i++) {
120
+ const arg = args[i];
121
+ if (arg === "--port" || arg === "-p") {
122
+ const value = readOptionValue(args, i, arg);
123
+ port = parseInt(value, 10);
124
+ i++;
125
+ if (Number.isNaN(port)) {
126
+ console.error(`❌ Invalid --port value: ${value}`);
127
+ process.exit(1);
128
+ }
129
+ } else if (arg === "--open" || arg === "-o") {
130
+ open = true;
131
+ } else if (arg === "--deck") {
132
+ const value = readOptionValue(args, i, arg);
133
+ validateDeckPath(value, arg);
134
+ deckPath = value;
135
+ i++;
136
+ } else if (arg === "--root") {
137
+ rejectRootOption();
138
+ }
139
+ }
140
+
141
+ return { port, open, ...resolveDeckPath(deckPath ?? undefined) };
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // App shell Vite plugin
146
+ // ---------------------------------------------------------------------------
147
+
148
+ /**
149
+ * `honeydeck:app-shell` β€” serves the Honeydeck index.html for the root route.
150
+ *
151
+ * Handles two concerns:
152
+ * 1. `config` hook: broadens `server.fs.allow` so /@fs/ can reach the
153
+ * app shell files which live outside the user's project root.
154
+ * 2. `configureServer` hook: registers a connect middleware that intercepts
155
+ * GET / (and /index.html), reads the HTML template, injects the correct
156
+ * /@fs/ script src, and lets Vite's `transformIndexHtml` pipeline apply
157
+ * any further HTML transforms (e.g. inject the HMR client).
158
+ */
159
+ function appShellPlugin(): Plugin {
160
+ return {
161
+ name: "honeydeck:app-shell",
162
+
163
+ config() {
164
+ return {
165
+ server: {
166
+ fs: {
167
+ // Allow Vite's /@fs/ handler to serve any file under the honeydeck
168
+ // package root (so src/runtime/app-shell/main.tsx is accessible).
169
+ allow: [PACKAGE_ROOT],
170
+ },
171
+ },
172
+ };
173
+ },
174
+
175
+ configureServer(server) {
176
+ server.middlewares.use(async (req, res, next) => {
177
+ const url = req.url ?? "/";
178
+
179
+ // Only handle the root HTML request; let everything else pass through.
180
+ if (url !== "/" && url !== "/index.html") {
181
+ next();
182
+ return;
183
+ }
184
+
185
+ try {
186
+ // Read the template and replace the placeholder script src.
187
+ // The /@fs/ prefix tells Vite to serve the file by absolute path,
188
+ // bypassing the root restriction for this single module.
189
+ let html = readFileSync(INDEX_HTML_PATH, "utf-8");
190
+ html = html.replace(
191
+ "__HONEYDECK_MAIN_ENTRY__",
192
+ `/@fs${MAIN_TSX_PATH}`,
193
+ );
194
+
195
+ // Let Vite's HTML transform pipeline run (injects HMR client, etc.)
196
+ const transformed = await server.transformIndexHtml(url, html);
197
+
198
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
199
+ res.end(transformed);
200
+ } catch (err) {
201
+ next(err);
202
+ }
203
+ });
204
+ },
205
+ };
206
+ }
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Public API
210
+ // ---------------------------------------------------------------------------
211
+
212
+ export async function runDev(args: string[]): Promise<void> {
213
+ if (hasHelpFlag(args)) {
214
+ printDevHelp();
215
+ return;
216
+ }
217
+
218
+ const { port, open, root, entry, deck } = parseDevArgs(args);
219
+
220
+ console.log("\n ✨ Honeydeck dev starting...\n");
221
+ console.log(` Root β†’ ${root}`);
222
+ console.log(` Deck β†’ ${deck}`);
223
+ console.log(` Port β†’ ${port}`);
224
+
225
+ const server = await createServer({
226
+ // Set root to the deck dir so local CSS/assets resolve naturally.
227
+ root,
228
+
229
+ // `custom` appType: we serve our own index.html; Vite doesn't look for
230
+ // one in the root dir.
231
+ appType: "custom",
232
+
233
+ server: createDevServerConfig(port, open),
234
+
235
+ plugins: [
236
+ // 1. App shell: serve our index.html + configure fs.allow
237
+ appShellPlugin(),
238
+ // 2. Virtual modules + MDX compilation + Tailwind
239
+ ...honeydeckPlugin({ root, entry }),
240
+ ],
241
+ });
242
+
243
+ await server.listen();
244
+
245
+ const resolvedPort = server.config.server.port ?? port;
246
+ // Try to get the network address
247
+ const networkAddresses = server.resolvedUrls?.network ?? [];
248
+ const networkUrl =
249
+ networkAddresses[0] ?? `http://192.168.x.x:${resolvedPort}/`;
250
+
251
+ console.log(`\n${formatCommandBanner()}\n`);
252
+ console.log(` πŸš€ Local: http://localhost:${resolvedPort}/`);
253
+ console.log(` 🌐 Network: ${networkUrl}`);
254
+ console.log(` 🎨 Theme: http://localhost:${resolvedPort}/#/theme`);
255
+ console.log(`\n πŸ‘€ Watching for changes...\n`);
256
+
257
+ // Keep the process alive (Vite does this, but be explicit).
258
+ process.on("SIGINT", async () => {
259
+ console.log("\n πŸ‘‹ Shutting down honeydeck dev server…\n");
260
+ await server.close();
261
+ process.exit(0);
262
+ });
263
+ }
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * honeydeck CLI β€” entry point.
4
+ *
5
+ * Dispatches to sub-commands based on process.argv.
6
+ * Uses dynamic `import()` for each sub-command so that only the required
7
+ * code is loaded (avoids pulling in Vite at startup just to print --help).
8
+ *
9
+ * Usage:
10
+ * honeydeck β€” show help
11
+ * honeydeck help <command> β€” show sub-command help
12
+ * honeydeck init [--name <n>] [--skip-skill|--install-skill]
13
+ * β€” scaffold a new project
14
+ * honeydeck skill β€” install Honeydeck agent skills
15
+ * honeydeck dev [--deck <file.mdx>] [--port <n>] [--open]
16
+ * honeydeck build [--deck <file.mdx>]
17
+ * honeydeck pdf [--deck <file.mdx>] [-o <file>] [--steps all|final] [--mode light|dark]
18
+ */
19
+
20
+ import { formatTopLevelBanner } from "./banner.ts";
21
+
22
+ const [, , command, ...args] = process.argv;
23
+
24
+ async function main(): Promise<void> {
25
+ if (!command || isHelpFlag(command)) {
26
+ printHelp();
27
+ process.exit(0);
28
+ }
29
+
30
+ if (command === "help") {
31
+ const target = args[0];
32
+ if (!target || isHelpFlag(target)) {
33
+ printHelp();
34
+ process.exit(0);
35
+ }
36
+
37
+ const handled = await runCommand(target, ["--help"]);
38
+ if (!handled) {
39
+ console.error(` ❌ Unknown command "${target}".`);
40
+ printHelp();
41
+ process.exit(1);
42
+ }
43
+ return;
44
+ }
45
+
46
+ const handled = await runCommand(command, args);
47
+ if (!handled) {
48
+ printHelp();
49
+ process.exit(1);
50
+ }
51
+ }
52
+
53
+ async function runCommand(command: string, args: string[]): Promise<boolean> {
54
+ switch (command) {
55
+ case "dev": {
56
+ const { runDev } = await import("./dev.ts");
57
+ await runDev(args);
58
+ return true;
59
+ }
60
+
61
+ case "build": {
62
+ const { runBuild } = await import("./build.ts");
63
+ await runBuild(args);
64
+ return true;
65
+ }
66
+
67
+ case "pdf": {
68
+ const { runPdf } = await import("./pdf.ts");
69
+ await runPdf(args);
70
+ return true;
71
+ }
72
+
73
+ case "init": {
74
+ const { runInit } = await import("./init.ts");
75
+ await runInit(args);
76
+ return true;
77
+ }
78
+
79
+ case "skill": {
80
+ const { runSkill } = await import("./skill.ts");
81
+ await runSkill(args);
82
+ return true;
83
+ }
84
+
85
+ default:
86
+ return false;
87
+ }
88
+ }
89
+
90
+ function isHelpFlag(value: string): boolean {
91
+ return value === "--help" || value === "-h";
92
+ }
93
+
94
+ function printHelp(): void {
95
+ console.log(`
96
+ ${formatTopLevelBanner()}
97
+
98
+ Usage:
99
+ honeydeck <command> [options]
100
+ honeydeck help <command>
101
+
102
+ Commands:
103
+ init Scaffold a new presentation project
104
+ skill Install Honeydeck agent skills
105
+ dev Start the development server
106
+ build Build for production (static SPA)
107
+ pdf Export slides to a PDF file
108
+ help Show top-level or command-specific help
109
+
110
+ Options:
111
+ -h, --help Show help
112
+
113
+ Examples:
114
+ honeydeck init --name my-talk
115
+ honeydeck help dev
116
+ honeydeck dev --open
117
+ honeydeck pdf --steps all --mode dark -o slides.pdf
118
+
119
+ 🎞 Happy presenting!
120
+ `);
121
+ }
122
+
123
+ main().catch((err: unknown) => {
124
+ console.error("❌ honeydeck error:", err);
125
+ process.exit(1);
126
+ });