@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.
- package/AGENTS.md +25 -0
- package/DEVELOPMENT.md +522 -0
- package/LICENSE +21 -0
- package/Readme.md +49 -0
- package/SPEC.md +88 -0
- package/docs/components.md +63 -0
- package/docs/configuration.md +91 -0
- package/docs/getting-started.md +116 -0
- package/docs/kit-authoring.md +207 -0
- package/docs/kits.md +387 -0
- package/docs/local-development.md +95 -0
- package/docs/mermaid.md +198 -0
- package/docs/mobile.md +108 -0
- package/docs/navigation.md +93 -0
- package/docs/next-steps.md +377 -0
- package/docs/pdf-export.md +91 -0
- package/docs/presenter-mode.md +104 -0
- package/docs/slides.md +130 -0
- package/docs/slidev-migration.md +42 -0
- package/docs/steps-and-reveals.md +171 -0
- package/package.json +134 -0
- package/skills/SPEC.md +21 -0
- package/skills/honeydeck/SKILL.md +65 -0
- package/skills/presentation-writing/SKILL.md +75 -0
- package/skills/slidev-migration/SKILL.md +153 -0
- package/src/SPEC.md +89 -0
- package/src/assets.d.ts +30 -0
- package/src/cli/SPEC.md +230 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/banner.ts +9 -0
- package/src/cli/bin.js +5 -0
- package/src/cli/build.ts +229 -0
- package/src/cli/deck-path.ts +32 -0
- package/src/cli/dev.ts +263 -0
- package/src/cli/index.ts +126 -0
- package/src/cli/init.ts +369 -0
- package/src/cli/pdf.ts +923 -0
- package/src/cli/skill.ts +75 -0
- package/src/cli/templates/SPEC.md +70 -0
- package/src/cli/templates/deck-mdx.ts +15 -0
- package/src/cli/templates/package-json.ts +36 -0
- package/src/cli/templates/sparkle-button.ts +15 -0
- package/src/cli/templates/starter/components/SparkleButton.tsx +84 -0
- package/src/cli/templates/starter/deck.mdx +153 -0
- package/src/cli/templates/starter/styles.css +14 -0
- package/src/cli/templates/styles-css.ts +14 -0
- package/src/defaults.ts +1 -0
- package/src/layouts/ColorModeImage.tsx +55 -0
- package/src/layouts/SPEC.md +393 -0
- package/src/layouts/SlideFrame.tsx +48 -0
- package/src/layouts/bee/Blank.tsx +12 -0
- package/src/layouts/bee/Cover.tsx +70 -0
- package/src/layouts/bee/Default.tsx +42 -0
- package/src/layouts/bee/Image/Image.tsx +151 -0
- package/src/layouts/bee/Image/placeholder-dark.webp +0 -0
- package/src/layouts/bee/Image/placeholder-vertical-dark.webp +0 -0
- package/src/layouts/bee/Image/placeholder-vertical.webp +0 -0
- package/src/layouts/bee/Image/placeholder.webp +0 -0
- package/src/layouts/bee/ImageLeft.tsx +27 -0
- package/src/layouts/bee/ImageRight.tsx +27 -0
- package/src/layouts/bee/ImageSide.tsx +107 -0
- package/src/layouts/bee/Section.tsx +40 -0
- package/src/layouts/bee/TwoCol.tsx +108 -0
- package/src/layouts/bee/index.ts +40 -0
- package/src/layouts/clean/Blank.tsx +12 -0
- package/src/layouts/clean/Cover.tsx +58 -0
- package/src/layouts/clean/Default.tsx +33 -0
- package/src/layouts/clean/Image/Image.tsx +103 -0
- package/src/layouts/clean/ImageLeft.tsx +27 -0
- package/src/layouts/clean/ImageRight.tsx +27 -0
- package/src/layouts/clean/ImageSide.tsx +113 -0
- package/src/layouts/clean/Section.tsx +35 -0
- package/src/layouts/clean/TwoCol.tsx +63 -0
- package/src/layouts/clean/index.ts +40 -0
- package/src/layouts/index.ts +60 -0
- package/src/layouts/placeholders.ts +9 -0
- package/src/layouts/utils.ts +13 -0
- package/src/remark/SPEC.md +49 -0
- package/src/remark/h1-extract.ts +124 -0
- package/src/remark/index.ts +4 -0
- package/src/remark/shiki-code-blocks.ts +325 -0
- package/src/remark/step-numbering.ts +412 -0
- package/src/runtime/Deck.tsx +533 -0
- package/src/runtime/SPEC.md +256 -0
- package/src/runtime/SlideCanvas.tsx +95 -0
- package/src/runtime/TimelineContext.tsx +122 -0
- package/src/runtime/app-shell/index.html +31 -0
- package/src/runtime/app-shell/main.tsx +42 -0
- package/src/runtime/aspectRatio.ts +34 -0
- package/src/runtime/colorMode.ts +23 -0
- package/src/runtime/components/BrowserFrame.tsx +233 -0
- package/src/runtime/components/Button.tsx +57 -0
- package/src/runtime/components/CodeBlock.tsx +210 -0
- package/src/runtime/components/ColorModeCycleButton.tsx +59 -0
- package/src/runtime/components/ErrorBoundary.tsx +125 -0
- package/src/runtime/components/Keyboard.tsx +87 -0
- package/src/runtime/components/ListStyle.tsx +203 -0
- package/src/runtime/components/NavBar.tsx +223 -0
- package/src/runtime/components/NavBarButton.tsx +47 -0
- package/src/runtime/components/NavBarDivider.tsx +3 -0
- package/src/runtime/components/Notes.tsx +171 -0
- package/src/runtime/components/Reveal.tsx +82 -0
- package/src/runtime/components/RevealGroup.tsx +193 -0
- package/src/runtime/components/SPEC.md +263 -0
- package/src/runtime/components/SlideNumberBadge.tsx +11 -0
- package/src/runtime/components/TimelineSteps.tsx +115 -0
- package/src/runtime/components/index.ts +55 -0
- package/src/runtime/index.ts +42 -0
- package/src/runtime/inputOwnership.ts +68 -0
- package/src/runtime/keyboardTarget.ts +7 -0
- package/src/runtime/lastSlideRoute.ts +56 -0
- package/src/runtime/navigation.ts +211 -0
- package/src/runtime/router.ts +157 -0
- package/src/runtime/slideData.ts +137 -0
- package/src/runtime/sync.ts +267 -0
- package/src/runtime/types.ts +182 -0
- package/src/runtime/useKeyboardNav.ts +138 -0
- package/src/runtime/useSwipeNav.ts +257 -0
- package/src/runtime/views/DocsView.tsx +74 -0
- package/src/runtime/views/OverviewView.tsx +386 -0
- package/src/runtime/views/PresenterNotesPanel.tsx +76 -0
- package/src/runtime/views/PresenterView.tsx +340 -0
- package/src/runtime/views/SPEC.md +152 -0
- package/src/runtime/views/docs/ComponentsTab.tsx +178 -0
- package/src/runtime/views/docs/DocsHeader.tsx +101 -0
- package/src/runtime/views/docs/Intro.tsx +20 -0
- package/src/runtime/views/docs/LayoutsTab.tsx +324 -0
- package/src/runtime/views/docs/ThemeTab.tsx +110 -0
- package/src/runtime/views/index.ts +7 -0
- package/src/runtime/views/overviewGrid.ts +106 -0
- package/src/runtime/views/presenterPreview.ts +27 -0
- package/src/runtime/virtual-modules.d.ts +98 -0
- package/src/theme/SPEC.md +179 -0
- package/src/theme/base.css +623 -0
- package/src/theme/bee.css +35 -0
- package/src/theme/clean.css +38 -0
- package/src/vite-plugin/SPEC.md +114 -0
- package/src/vite-plugin/component-doc-crawler.ts +350 -0
- package/src/vite-plugin/deck-loader.ts +148 -0
- package/src/vite-plugin/index.ts +373 -0
- package/src/vite-plugin/layout-demo-crawler.ts +802 -0
- package/src/vite-plugin/splitter.ts +353 -0
- package/src/vite-plugin/token-manifest.ts +163 -0
- package/src/vite-plugin/virtual-modules.ts +587 -0
package/src/cli/build.ts
ADDED
|
@@ -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
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -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
|
+
});
|