@hajimism/dek 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/LICENSE +21 -0
- package/README.ja.md +75 -0
- package/README.md +75 -0
- package/package.json +65 -0
- package/src/cli/address.ts +53 -0
- package/src/cli/build.ts +85 -0
- package/src/cli/check.ts +97 -0
- package/src/cli/commands.ts +851 -0
- package/src/cli/contract.ts +444 -0
- package/src/cli/cues.ts +18 -0
- package/src/cli/files.ts +309 -0
- package/src/cli/flags.ts +295 -0
- package/src/cli/format.ts +156 -0
- package/src/cli/goto.ts +96 -0
- package/src/cli/init.ts +136 -0
- package/src/cli/keep-alive.ts +72 -0
- package/src/cli/lint.ts +84 -0
- package/src/cli/ls.ts +119 -0
- package/src/cli/main.ts +231 -0
- package/src/cli/marks.ts +16 -0
- package/src/cli/mv.ts +37 -0
- package/src/cli/new.ts +64 -0
- package/src/cli/pdf.ts +16 -0
- package/src/cli/qr.ts +466 -0
- package/src/cli/ref.ts +165 -0
- package/src/cli/rehearse.ts +42 -0
- package/src/cli/result.ts +161 -0
- package/src/cli/scope.ts +308 -0
- package/src/cli/serve.ts +125 -0
- package/src/cli/shot.ts +134 -0
- package/src/cli/show.ts +103 -0
- package/src/cli/sync.ts +37 -0
- package/src/cli/text.ts +479 -0
- package/src/cli/theme.ts +56 -0
- package/src/cli/tty.ts +81 -0
- package/src/cli/usage.ts +198 -0
- package/src/cli/video.ts +17 -0
- package/src/cli/voice.ts +113 -0
- package/src/cli.ts +4 -0
- package/src/core/assets.ts +239 -0
- package/src/core/build.ts +77 -0
- package/src/core/bundled-theme.ts +15 -0
- package/src/core/capture-go.ts +72 -0
- package/src/core/chrome.ts +128 -0
- package/src/core/config-keys.ts +82 -0
- package/src/core/config.ts +95 -0
- package/src/core/css-scan.ts +158 -0
- package/src/core/css-transform.ts +140 -0
- package/src/core/css-value.ts +127 -0
- package/src/core/css.ts +545 -0
- package/src/core/cue.ts +187 -0
- package/src/core/deck-paths.ts +34 -0
- package/src/core/diagnostic.ts +114 -0
- package/src/core/document.ts +218 -0
- package/src/core/error.ts +31 -0
- package/src/core/escape.ts +16 -0
- package/src/core/fill.ts +100 -0
- package/src/core/finish-beat.ts +30 -0
- package/src/core/github.ts +234 -0
- package/src/core/html-scan.ts +253 -0
- package/src/core/html.ts +234 -0
- package/src/core/in-page-go.ts +128 -0
- package/src/core/lines.ts +43 -0
- package/src/core/lint/asset-refs.ts +109 -0
- package/src/core/lint/context.ts +116 -0
- package/src/core/lint/files.ts +117 -0
- package/src/core/lint/keys.ts +48 -0
- package/src/core/lint/morph.ts +91 -0
- package/src/core/lint/script.ts +102 -0
- package/src/core/lint/shared-styles.ts +165 -0
- package/src/core/lint/slide-html.ts +466 -0
- package/src/core/lint/slide-script.ts +179 -0
- package/src/core/lint/theme-css.ts +295 -0
- package/src/core/lint/timing.ts +66 -0
- package/src/core/lint/tokens.ts +520 -0
- package/src/core/lint/voice.ts +76 -0
- package/src/core/lint.ts +137 -0
- package/src/core/live-protocol.ts +130 -0
- package/src/core/marks.ts +198 -0
- package/src/core/mv.ts +293 -0
- package/src/core/ogp.ts +99 -0
- package/src/core/optional.ts +24 -0
- package/src/core/overflow.ts +220 -0
- package/src/core/page-pool.ts +53 -0
- package/src/core/page.ts +66 -0
- package/src/core/parse.ts +307 -0
- package/src/core/path.ts +58 -0
- package/src/core/pdf.ts +71 -0
- package/src/core/playwright-module.d.ts +43 -0
- package/src/core/playwright-motion.ts +42 -0
- package/src/core/playwright-sheet.ts +32 -0
- package/src/core/playwright-visual.ts +391 -0
- package/src/core/playwright-worker.ts +5 -0
- package/src/core/playwright.ts +431 -0
- package/src/core/position.ts +146 -0
- package/src/core/presenter-state.ts +43 -0
- package/src/core/presenter.ts +131 -0
- package/src/core/prose.ts +16 -0
- package/src/core/pseudo-text.ts +67 -0
- package/src/core/rail-width.ts +4 -0
- package/src/core/ref-name.ts +110 -0
- package/src/core/ref.ts +238 -0
- package/src/core/resolve.ts +228 -0
- package/src/core/rumdl.ts +99 -0
- package/src/core/safe-fs.ts +196 -0
- package/src/core/sarif.ts +135 -0
- package/src/core/schema.ts +89 -0
- package/src/core/script-lines.ts +53 -0
- package/src/core/sheet.ts +223 -0
- package/src/core/shot/morph.ts +80 -0
- package/src/core/shot/motion.ts +187 -0
- package/src/core/shot/still.ts +216 -0
- package/src/core/shot-cache.ts +140 -0
- package/src/core/size.ts +11 -0
- package/src/core/skeleton-record.ts +51 -0
- package/src/core/skeleton.ts +89 -0
- package/src/core/slide-draw.ts +165 -0
- package/src/core/slide-eval-worker.ts +58 -0
- package/src/core/slide-measure.ts +272 -0
- package/src/core/slide-script-eval.ts +258 -0
- package/src/core/slide-script.ts +252 -0
- package/src/core/spawn.ts +128 -0
- package/src/core/step.ts +94 -0
- package/src/core/still-page.ts +70 -0
- package/src/core/suggest.ts +33 -0
- package/src/core/sync.ts +316 -0
- package/src/core/text-contrast.ts +466 -0
- package/src/core/theme-excerpt.ts +187 -0
- package/src/core/theme-facts.ts +51 -0
- package/src/core/timeline.ts +216 -0
- package/src/core/timing.ts +88 -0
- package/src/core/toml-keys.ts +349 -0
- package/src/core/url-attributes.ts +103 -0
- package/src/core/video-document.ts +15 -0
- package/src/core/visual-cache.ts +100 -0
- package/src/core/visual.ts +558 -0
- package/src/core/voice.ts +321 -0
- package/src/core/zod.ts +79 -0
- package/src/runtime/browser.ts +203 -0
- package/src/runtime/clone.ts +52 -0
- package/src/runtime/deck-control.ts +43 -0
- package/src/runtime/fit.ts +32 -0
- package/src/runtime/go.ts +56 -0
- package/src/runtime/input.ts +128 -0
- package/src/runtime/laser-view.ts +110 -0
- package/src/runtime/laser.ts +145 -0
- package/src/runtime/live-host.ts +33 -0
- package/src/runtime/live-reload.ts +29 -0
- package/src/runtime/live.ts +88 -0
- package/src/runtime/marks-view.ts +74 -0
- package/src/runtime/motion.ts +113 -0
- package/src/runtime/navigator.ts +189 -0
- package/src/runtime/player.ts +28 -0
- package/src/runtime/presenter-view.ts +289 -0
- package/src/runtime/presenter.ts +52 -0
- package/src/runtime/rail-view.ts +229 -0
- package/src/runtime/rail.ts +52 -0
- package/src/runtime/rehearse.ts +264 -0
- package/src/runtime/routes.ts +142 -0
- package/src/runtime/settle.ts +45 -0
- package/src/runtime/slide.d.ts +28 -0
- package/src/runtime/socket.ts +143 -0
- package/src/runtime/stage.ts +259 -0
- package/src/runtime/step.ts +139 -0
- package/src/runtime/window.ts +38 -0
- package/src/server/auth.ts +210 -0
- package/src/server/deck-snapshot.ts +146 -0
- package/src/server/dev.ts +251 -0
- package/src/server/hub.ts +104 -0
- package/src/server/lan.ts +51 -0
- package/src/server/lock.ts +97 -0
- package/src/server/pages.ts +71 -0
- package/src/server/pairing.ts +40 -0
- package/src/server/project-watch.ts +142 -0
- package/src/server/rooms.ts +133 -0
- package/src/server/routes.ts +286 -0
- package/src/server/serial.ts +25 -0
- package/src/server/watch.ts +308 -0
- package/src/theme/default.css +175 -0
- package/src/video/bake.ts +152 -0
- package/src/video/mux.ts +103 -0
- package/src/video/recorder.ts +115 -0
- package/src/video/sidecar.ts +60 -0
- package/src/video/worker.ts +51 -0
- package/src/voice/engine.ts +177 -0
- package/src/voice/synth.ts +127 -0
- package/src/voice/wav.ts +115 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hajime Nakagawa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# dek
|
|
2
|
+
|
|
3
|
+
**発表のビルドシステム。** 喋ることを書けば、あとは dek が組み立て、測り、届ける。
|
|
4
|
+
|
|
5
|
+
[ドキュメント](https://hajimism.github.io/dek/ja/) · [English README](./README.md)
|
|
6
|
+
|
|
7
|
+
dek は、ビルドシステムがコードを扱うように発表を扱う CLI です。ソースは Markdown で書くトークの台本で、`##` の見出しひとつが 1 枚の HTML スライドになります。同じソースから、尺を見積もり、どのスライドも lint して描画を測り、1 ファイルの HTML、PDF、読み上げ付きの動画をビルドします。プロジェクトが複数のデッキを持つので、テーマも規約も前回のトークから次のトークへ引き継がれます。AI エージェントと一緒に作ることを、最初から前提に設計しています。
|
|
8
|
+
|
|
9
|
+
## なぜ dek か
|
|
10
|
+
|
|
11
|
+
**スライドは喋るためにあります。** 主流のツールはどれも空の箱から始まり、埋めれば埋めるほど資料は立派になり、本番で喋り切るのは難しくなる。dek は反対側から始めます。何を、どの順で、どれだけの尺で喋るか。そのうえで、画面に何が出ているべきか。台本が親で、スライドはそこから派生します。
|
|
12
|
+
|
|
13
|
+
**トークは一度きりではありません。** dek のプロジェクトは複数のデッキを入れる場所です。規約はプロジェクトのもの、見た目は前回のデッキの続きから、台本とスライドはデッキごと。
|
|
14
|
+
|
|
15
|
+
**エージェントには小さなファイルが要ります。** 1 スライドは約 40 行の HTML ファイルひとつ。必要なら CSS とスクリプトを横に置けます。編集対象は小さく、差分は読め、壊れてもその 1 枚で済みます。
|
|
16
|
+
|
|
17
|
+
**エージェントは自分の描画結果を見られません。** dek はスライドを描画し、はみ出し、コントラスト、欠けた画像を機械可読な診断として返します。エージェントが自分の出力を確かめ、自分で直せます。
|
|
18
|
+
|
|
19
|
+
三原則と Slidev との比較は [dek の設計思想](https://hajimism.github.io/dek/ja/guide/why.html) にあります。
|
|
20
|
+
|
|
21
|
+
## クイックスタート
|
|
22
|
+
|
|
23
|
+
[Bun](https://bun.sh) 1.4 以上。
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bunx @hajimism/dek init my-talks --deck 2026-04-vite
|
|
27
|
+
cd my-talks
|
|
28
|
+
bun add -d @hajimism/dek
|
|
29
|
+
cd decks/2026-04-vite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`init` は終わりに同じ手順を表示します。プロジェクトを作るのは最初の一度だけです。dek をプロジェクトの開発依存に入れたあとは `bunx dekc` で動きます。残るのは 3 手で、ライブ発表ならこれがすべてです。
|
|
33
|
+
|
|
34
|
+
> [!WARNING]
|
|
35
|
+
> パッケージは `@hajimism/dek` で、入るコマンドは `dek` ではなく `dekc` です。npm の `dek` は無関係の別パッケージで、`bunx dek` はそれをダウンロードして実行します。プロジェクトの中では `bunx dekc` がそこに入れた dek を動かします。プロジェクトの外では `bunx @hajimism/dek` を使ってください。
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
$EDITOR script.md # 1. 喋ることを書く。時間をかけるのはここ
|
|
39
|
+
bunx dekc # 2. 開発サーバ。骨格スライドが生え、保存のたびに描画と lint
|
|
40
|
+
bunx dekc build # 3. dist/2026-04-vite.html — トーク全体がこの 1 ファイル
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
台本を書いて `dekc` を叩けば、HTML を一行も書かずに同梱テーマで発表できます。
|
|
44
|
+
|
|
45
|
+
続きは [はじめる](https://hajimism.github.io/dek/ja/guide/getting-started.html)へ。通しで 1 本作るなら [チュートリアル](https://hajimism.github.io/dek/ja/guide/tutorial.html)。コマンドの一覧は [CLI リファレンス](https://hajimism.github.io/dek/ja/reference/cli.html)にあります。
|
|
46
|
+
|
|
47
|
+
## サンプル
|
|
48
|
+
|
|
49
|
+
`sample/` は Playwright、rumdl、声まで入れたフル構成のプロジェクトで、3 つのデッキが入っています。どれも dek 自身が題材で、配布資料としても読める約 12 分の解説 `why-dek`、ターミナルで発表を作ってみせる 3 分のライトニングトーク `lightning`、スライドを AI エージェントに任せる 6 分のトーク `with-agents` です。手順は [sample/README.ja.md](sample/README.ja.md)。
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd sample
|
|
53
|
+
bun install
|
|
54
|
+
bun run setup # Playwright 用の Chromium
|
|
55
|
+
bun run dev # または bun run lint:visual / bun run build
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 開発
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bun install
|
|
62
|
+
bunx playwright install chromium # 最初に一度。実ブラウザを動かすテストに使う
|
|
63
|
+
bun test --watch
|
|
64
|
+
bun run typecheck
|
|
65
|
+
bun run check # Biome の format + lint
|
|
66
|
+
bun run docs:dev # VitePress
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
CI では `biome ci`、`typecheck`、全テスト(ブラウザを使うものは Chromium で)、`docs:build` を走らせます。
|
|
70
|
+
|
|
71
|
+
ドキュメントサイトは GitHub Actions 経由で GitHub Pages に出します。初回だけ、リポジトリの設定で **Pages → Source** を **GitHub Actions** にしてください。
|
|
72
|
+
|
|
73
|
+
## ライセンス
|
|
74
|
+
|
|
75
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# dek
|
|
2
|
+
|
|
3
|
+
**A build system for talks.** Write what you will say; dek builds, measures, and ships the rest.
|
|
4
|
+
|
|
5
|
+
[Documentation](https://hajimism.github.io/dek/) · [日本語 README](./README.ja.md)
|
|
6
|
+
|
|
7
|
+
dek treats a talk the way a build system treats code. The source is your talk script, in Markdown; every `##` heading becomes one HTML slide, and from the same source dek estimates the timing, lints and measures every slide, and builds a single HTML file, a PDF, and a narrated video. A project holds many decks, so the theme and conventions carry forward from one talk to the next. It is designed from the start to be worked on together with AI agents.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
**Slides exist so that you can talk.** Every mainstream tool starts with an empty box and asks you to fill it, and the deck grows more polished as the talk grows harder to deliver. dek starts from the other end: what you will say, in what order, for how long, and only then what should be on screen. The script is the parent. Slides are derived from it.
|
|
12
|
+
|
|
13
|
+
**A talk is not a one-off.** A dek project holds many decks. Conventions belong to the project, the look carries forward from your last deck, and every deck owns its script and slides.
|
|
14
|
+
|
|
15
|
+
**Agents need small files.** One slide is one HTML file of about forty lines, with an optional stylesheet and script beside it. The edit target is small, the diff is readable, and a broken edit breaks one slide.
|
|
16
|
+
|
|
17
|
+
**Agents cannot see what they render.** dek renders each slide and reports overflow, contrast, and missing images as machine-readable diagnostics, so an agent can check and fix its own output.
|
|
18
|
+
|
|
19
|
+
The three principles and the comparison with Slidev are in [Why dek](https://hajimism.github.io/dek/guide/why.html).
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
[Bun](https://bun.sh) 1.4 or later.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bunx @hajimism/dek init my-talks --deck 2026-04-vite
|
|
27
|
+
cd my-talks
|
|
28
|
+
bun add -d @hajimism/dek
|
|
29
|
+
cd decks/2026-04-vite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`init` prints these same steps as it finishes. You create the project once. Once dek is a dev dependency of the project, `bunx dekc` runs it. Three commands remain, and for a live talk that is all there is.
|
|
33
|
+
|
|
34
|
+
> [!WARNING]
|
|
35
|
+
> The package is `@hajimism/dek`; the command it installs is `dekc`, not `dek`. `dek` on npm is an unrelated package, and `bunx dek` downloads and runs it. Inside a project, `bunx dekc` runs the dek installed there; outside one, use `bunx @hajimism/dek`.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
$EDITOR script.md # 1. write what you will say — spend your time here
|
|
39
|
+
bunx dekc # 2. dev server: skeleton slides, live reload, lint on save
|
|
40
|
+
bunx dekc build # 3. dist/2026-04-vite.html — the whole talk in one file
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Write the script, run `dekc`, and you can present with the bundled theme without writing a line of HTML.
|
|
44
|
+
|
|
45
|
+
Continue with [Getting Started](https://hajimism.github.io/dek/guide/getting-started.html), or build a real deck end to end in the [Tutorial](https://hajimism.github.io/dek/guide/tutorial.html). Every command is listed in the [CLI reference](https://hajimism.github.io/dek/reference/cli.html).
|
|
46
|
+
|
|
47
|
+
## Sample
|
|
48
|
+
|
|
49
|
+
`sample/` is a full project with Playwright, rumdl, and voice, holding three decks about dek itself: `why-dek`, a twelve-minute explainer built to read as a handout; `lightning`, a three-minute lightning talk that builds a talk in a terminal; and `with-agents`, a six-minute talk about handing slides to an AI agent. See [sample/README.md](sample/README.md).
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd sample
|
|
53
|
+
bun install
|
|
54
|
+
bun run setup # Chromium for Playwright
|
|
55
|
+
bun run dev # or bun run lint:visual / bun run build
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bun install
|
|
62
|
+
bunx playwright install chromium # once, for the tests that drive a real browser
|
|
63
|
+
bun test --watch
|
|
64
|
+
bun run typecheck
|
|
65
|
+
bun run check # Biome format + lint
|
|
66
|
+
bun run docs:dev # VitePress
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
CI runs `biome ci`, `typecheck`, the full test suite (the browser tests in Chromium), and `docs:build`.
|
|
70
|
+
|
|
71
|
+
The documentation site deploys to GitHub Pages through Actions. Once, in the repository settings, set **Pages → Source** to **GitHub Actions**.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hajimism/dek",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A build system for talks.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dekc": "src/cli.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"README.md",
|
|
14
|
+
"README.ja.md"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/hajimism/dek.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/hajimism/dek/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://hajimism.github.io/dek/",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"bun": ">=1.4.0"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"slides",
|
|
32
|
+
"presentation",
|
|
33
|
+
"talk",
|
|
34
|
+
"build",
|
|
35
|
+
"cli",
|
|
36
|
+
"html"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "bun test --parallel=4",
|
|
40
|
+
"test:watch": "bun test --watch",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"knip": "knip",
|
|
43
|
+
"schema": "bun scripts/cli-schema.ts",
|
|
44
|
+
"format": "biome format --write .",
|
|
45
|
+
"lint": "biome lint .",
|
|
46
|
+
"check": "biome check --write .",
|
|
47
|
+
"docs:dev": "vitepress dev docs",
|
|
48
|
+
"docs:samples": "cd sample && bun ../src/cli.ts build --root-dist && mkdir -p ../docs/public/samples && cp dist/*.html dist/*.png ../docs/public/samples/",
|
|
49
|
+
"docs:build": "bun run docs:samples && vitepress build docs",
|
|
50
|
+
"docs:preview": "vitepress preview docs"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"zod": "^4.6.5"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@biomejs/biome": "2.5.14",
|
|
57
|
+
"@happy-dom/global-registrator": "20.14.5",
|
|
58
|
+
"@types/bun": "^1.4.2",
|
|
59
|
+
"happy-dom": "20.14.5",
|
|
60
|
+
"knip": "6.38.0",
|
|
61
|
+
"playwright": "^1.63.0",
|
|
62
|
+
"typescript": "^7.0.2",
|
|
63
|
+
"vitepress": "^1.6.4"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { isInside } from "../core/path.ts";
|
|
4
|
+
import { COMMANDS } from "./commands.ts";
|
|
5
|
+
|
|
6
|
+
/** A deck a hint is about: the name a command takes, and the directory that makes it implicit. */
|
|
7
|
+
export type HintDeck = { name: string; dir: string };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A hint's commands as they run from `cwd`. Hints are written as if run inside the deck they are
|
|
11
|
+
* about, where a command finds its deck by itself. Run anywhere else, a command that takes a deck
|
|
12
|
+
* needs its name: as the first word outside every deck, and as `--deck` inside another deck,
|
|
13
|
+
* where a first word names something in that deck instead. A command that already names a deck,
|
|
14
|
+
* or takes none, is left as written.
|
|
15
|
+
*/
|
|
16
|
+
export function addressHint(hint: string, deck: HintDeck | undefined, cwd: string): string {
|
|
17
|
+
if (deck === undefined || isInside(cwd, deck.dir)) {
|
|
18
|
+
return hint;
|
|
19
|
+
}
|
|
20
|
+
const naming = deckAround(cwd) === undefined ? deck.name : `--deck ${deck.name}`;
|
|
21
|
+
return hint.replace(COMMAND_RE, (whole, command: string, rest: string) => {
|
|
22
|
+
if (!DECK_COMMANDS.has(command) || namesDeck(rest, deck.name)) {
|
|
23
|
+
return whole;
|
|
24
|
+
}
|
|
25
|
+
return `\`dekc ${command} ${naming}${rest}\``;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A backquoted command: `dekc`, a command word, and what follows it up to the closing quote. */
|
|
30
|
+
const COMMAND_RE = /`dekc ([a-z]+)((?: [^`]*)?)`/g;
|
|
31
|
+
|
|
32
|
+
const DECK_COMMANDS = new Set(
|
|
33
|
+
Object.entries(COMMANDS).flatMap(([name, spec]) => ("scope" in spec && spec.scope ? [name] : [])),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
function namesDeck(rest: string, name: string): boolean {
|
|
37
|
+
const [first] = rest.trim().split(/\s+/);
|
|
38
|
+
return first === name || first?.includes("/") === true || /(?:^| )--deck(?: |$)/.test(rest);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The deck `path` lies in: the directory under a project's `decks/`, found from the path alone.
|
|
43
|
+
* A source path dek reports is absolute, so no project needs to be resolved to address it.
|
|
44
|
+
*/
|
|
45
|
+
export function deckAround(path: string): HintDeck | undefined {
|
|
46
|
+
for (let dir = path; dirname(dir) !== dir; dir = dirname(dir)) {
|
|
47
|
+
const parent = dirname(dir);
|
|
48
|
+
if (basename(parent) === "decks" && existsSync(join(dirname(parent), "dek.toml"))) {
|
|
49
|
+
return { name: basename(dir), dir };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
package/src/cli/build.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { buildDeck } from "../core/build.ts";
|
|
2
|
+
import type { Diagnostic } from "../core/diagnostic.ts";
|
|
3
|
+
import { DekError } from "../core/error.ts";
|
|
4
|
+
import { lintDeck, lintProject } from "../core/lint.ts";
|
|
5
|
+
import { parsePublicUrl } from "../core/ogp.ts";
|
|
6
|
+
import { PLAYWRIGHT_INSTALL, type PlaywrightRunner } from "../core/playwright.ts";
|
|
7
|
+
import { playerScript } from "../runtime/player.ts";
|
|
8
|
+
import { type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
|
|
9
|
+
import type { DecksTarget } from "./scope.ts";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A build never fails on lint: at the venue, a deck that shows is better than
|
|
13
|
+
* none. What dek's own rules say comes back with it, so nobody ships it unaware;
|
|
14
|
+
* rumdl's Markdown style is `dekc lint`'s alone, since it never stops a deck showing.
|
|
15
|
+
*/
|
|
16
|
+
export type BuildCliResult = {
|
|
17
|
+
/** One file per deck built, a single deck included, so the shape never depends on the scope. */
|
|
18
|
+
outs: string[];
|
|
19
|
+
/** dist/<deck>.png for each deck that got a link preview image. */
|
|
20
|
+
images: string[];
|
|
21
|
+
diagnostics: Diagnostic[];
|
|
22
|
+
/** Why some deck has no preview image, said once per reason. */
|
|
23
|
+
skipped?: SkippedCheck[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const PREVIEW_SKIPPED = {
|
|
27
|
+
"no-url": {
|
|
28
|
+
check: "preview",
|
|
29
|
+
reason: "url is not set",
|
|
30
|
+
hint: "set url in dek.toml, or pass --url, to the URL dist/ is served from",
|
|
31
|
+
},
|
|
32
|
+
"no-playwright": {
|
|
33
|
+
check: "preview",
|
|
34
|
+
reason: "Playwright is not installed",
|
|
35
|
+
hint: PLAYWRIGHT_INSTALL,
|
|
36
|
+
},
|
|
37
|
+
} as const satisfies Record<string, SkippedCheck>;
|
|
38
|
+
|
|
39
|
+
export async function buildCommand(
|
|
40
|
+
{ project, decks }: DecksTarget,
|
|
41
|
+
options: { rootDist?: boolean; url?: string; public?: boolean; runner?: PlaywrightRunner } = {},
|
|
42
|
+
): Promise<BuildCliResult> {
|
|
43
|
+
const url = options.url === undefined ? undefined : parsePublicUrl(options.url);
|
|
44
|
+
if (options.url !== undefined && url === undefined) {
|
|
45
|
+
throw new DekError(`invalid --url "${options.url}"`, {
|
|
46
|
+
hint: "pass the absolute http(s) URL dist/ is served from, like --url https://example.com/talks/",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const script = await playerScript();
|
|
50
|
+
const results = await Promise.all(
|
|
51
|
+
decks.map((entry) =>
|
|
52
|
+
buildDeck(
|
|
53
|
+
{ project, deck: entry },
|
|
54
|
+
{
|
|
55
|
+
playerScript: script,
|
|
56
|
+
rootDist: options.rootDist,
|
|
57
|
+
...(url ? { url } : {}),
|
|
58
|
+
...(options.public ? { public: true } : {}),
|
|
59
|
+
...(options.runner ? { runner: options.runner } : {}),
|
|
60
|
+
},
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
const diagnostics = [
|
|
65
|
+
...lintProject(project),
|
|
66
|
+
...decks.flatMap((entry) => lintDeck({ project, deck: entry })),
|
|
67
|
+
];
|
|
68
|
+
const skipped = new Set(
|
|
69
|
+
results.flatMap((result) =>
|
|
70
|
+
"imageSkipped" in result ? [PREVIEW_SKIPPED[result.imageSkipped]] : [],
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
return {
|
|
74
|
+
outs: results.map((result) => result.outPath),
|
|
75
|
+
images: results.flatMap((result) => ("image" in result ? [result.image] : [])),
|
|
76
|
+
diagnostics,
|
|
77
|
+
...skippedChecks([
|
|
78
|
+
...skipped,
|
|
79
|
+
visualSkipped("dekc lint --visual", {
|
|
80
|
+
reason: "overflow and contrast are measured only by `dekc lint --visual`",
|
|
81
|
+
...(options.runner ? { runner: options.runner } : {}),
|
|
82
|
+
}),
|
|
83
|
+
]),
|
|
84
|
+
};
|
|
85
|
+
}
|
package/src/cli/check.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Diagnostic } from "../core/diagnostic.ts";
|
|
2
|
+
import { DekError } from "../core/error.ts";
|
|
3
|
+
import type { Fill } from "../core/fill.ts";
|
|
4
|
+
import { lintDeck } from "../core/lint.ts";
|
|
5
|
+
import { type PlaywrightRunner, playwrightMissingError } from "../core/playwright.ts";
|
|
6
|
+
import { runVisualDeck } from "../core/visual.ts";
|
|
7
|
+
import { hasVoice, loadCachedTimeline, VOICE_SETUP_HINT } from "../core/voice.ts";
|
|
8
|
+
import { type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
|
|
9
|
+
import { type DeckTarget, requireSection } from "./scope.ts";
|
|
10
|
+
|
|
11
|
+
type VoiceCheckBeat = {
|
|
12
|
+
beatIndex: number;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
empty: boolean;
|
|
15
|
+
sentences: Array<{ text: string; kana: string; start: number; end: number }>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type CheckCliResult = {
|
|
19
|
+
slug: string;
|
|
20
|
+
diagnostics: Diagnostic[];
|
|
21
|
+
/** Checks that did not run, each with why and how to run it, so an empty list is not a pass. */
|
|
22
|
+
skipped?: SkippedCheck[];
|
|
23
|
+
shot?: string;
|
|
24
|
+
/** How much of the frame the slide fills at `step`, its last beat; absent when not measured. */
|
|
25
|
+
fill?: Fill & { step: string };
|
|
26
|
+
voice?: { beats: VoiceCheckBeat[] };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export async function checkCommand(
|
|
30
|
+
{ project, deck }: DeckTarget,
|
|
31
|
+
options: { slug: string; shot?: boolean; voice?: boolean; runner?: PlaywrightRunner },
|
|
32
|
+
): Promise<CheckCliResult> {
|
|
33
|
+
const { slug } = options;
|
|
34
|
+
requireSection(deck, slug);
|
|
35
|
+
|
|
36
|
+
const diagnostics = lintDeck({ project, deck }, { slug });
|
|
37
|
+
const visual = await runVisualDeck(
|
|
38
|
+
{ project, deck },
|
|
39
|
+
{
|
|
40
|
+
slug,
|
|
41
|
+
screenshot: options.shot === true,
|
|
42
|
+
...(options.runner ? { runner: options.runner } : {}),
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
if (visual) {
|
|
46
|
+
diagnostics.push(...visual.diagnostics);
|
|
47
|
+
} else if (options.shot) {
|
|
48
|
+
throw playwrightMissingError();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const shot = visual?.screenshotPath;
|
|
52
|
+
const { slug: _slide, ...fill } = visual?.fills.find((found) => found.slug === slug) ?? {};
|
|
53
|
+
|
|
54
|
+
const skipped: SkippedCheck[] = visual
|
|
55
|
+
? []
|
|
56
|
+
: [visualSkipped(`dekc check ${slug}`, { reason: "Playwright is not installed" })];
|
|
57
|
+
|
|
58
|
+
// A live-only deck is done without voice, so asking for it skips the check rather than failing,
|
|
59
|
+
// the way a missing Playwright skips visual.
|
|
60
|
+
let voice: CheckCliResult["voice"];
|
|
61
|
+
if (options.voice && !hasVoice(deck.dir)) {
|
|
62
|
+
skipped.push({
|
|
63
|
+
check: "voice",
|
|
64
|
+
reason: "the deck has no voice/voice.toml",
|
|
65
|
+
hint: VOICE_SETUP_HINT,
|
|
66
|
+
});
|
|
67
|
+
} else if (options.voice) {
|
|
68
|
+
const { synthDeck } = await import("../voice/synth.ts");
|
|
69
|
+
await synthDeck({ project, deck });
|
|
70
|
+
const timeline = loadCachedTimeline(deck.dir);
|
|
71
|
+
if (!timeline) {
|
|
72
|
+
throw new DekError("Timeline not found", {
|
|
73
|
+
hint: "run `dekc voice`",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const slideIndex = deck.deck.sections.findIndex((entry) => entry.slug === slug);
|
|
77
|
+
voice = {
|
|
78
|
+
beats: timeline.beats
|
|
79
|
+
.filter((beat) => beat.position.slideIndex === slideIndex)
|
|
80
|
+
.map((beat) => ({
|
|
81
|
+
beatIndex: beat.position.beatIndex,
|
|
82
|
+
durationMs: beat.end - beat.start,
|
|
83
|
+
empty: beat.sentences.length === 0,
|
|
84
|
+
sentences: beat.sentences,
|
|
85
|
+
})),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
slug,
|
|
91
|
+
diagnostics,
|
|
92
|
+
...skippedChecks(skipped),
|
|
93
|
+
...(shot ? { shot } : {}),
|
|
94
|
+
...("step" in fill ? { fill } : {}),
|
|
95
|
+
...(voice ? { voice } : {}),
|
|
96
|
+
};
|
|
97
|
+
}
|