@hajimism/dek 0.3.3 → 0.4.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/CHANGELOG.md +63 -0
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/package.json +2 -1
- package/skills/dek/SKILL.md +10 -3
- package/skills/dek-design/SKILL.md +42 -0
- package/src/cli/address.ts +84 -14
- package/src/cli/build.ts +2 -2
- package/src/cli/check.ts +2 -1
- package/src/cli/commands.ts +11 -6
- package/src/cli/contract.ts +9 -1
- package/src/cli/files.ts +1 -5
- package/src/cli/flags.ts +1 -1
- package/src/cli/goto.ts +3 -1
- package/src/cli/init.ts +10 -5
- package/src/cli/lint.ts +4 -3
- package/src/cli/main.ts +7 -5
- package/src/cli/new.ts +14 -7
- package/src/cli/pptx.ts +9 -4
- package/src/cli/ref.ts +91 -9
- package/src/cli/result.ts +5 -2
- package/src/cli/scope.ts +17 -28
- package/src/cli/shot.ts +2 -1
- package/src/cli/text.ts +27 -2
- package/src/cli/theme.ts +16 -2
- package/src/cli/voice.ts +4 -3
- package/src/core/alignment.ts +320 -0
- package/src/core/annotation-rows.ts +16 -4
- package/src/core/annotations.ts +19 -1
- package/src/core/config.ts +61 -12
- package/src/core/diagnostic.ts +5 -1
- package/src/core/escape.ts +43 -0
- package/src/core/line-break.ts +275 -0
- package/src/core/lint/asset-refs.ts +6 -2
- package/src/core/lint/files.ts +30 -5
- package/src/core/lint/keys.ts +7 -3
- package/src/core/lint/slide-html.ts +3 -1
- package/src/core/lint/slide-script.ts +7 -5
- package/src/core/mv.ts +30 -10
- package/src/core/pack.ts +2 -2
- package/src/core/playwright-module.d.ts +1 -0
- package/src/core/playwright-visual.ts +180 -20
- package/src/core/playwright.ts +202 -25
- package/src/core/pptx-package.ts +461 -33
- package/src/core/pptx-paint.ts +1168 -0
- package/src/core/pptx-plan.ts +879 -0
- package/src/core/pptx-text.ts +204 -47
- package/src/core/pptx-theme.ts +222 -0
- package/src/core/pptx.ts +55 -8
- package/src/core/prose.ts +23 -0
- package/src/core/pseudo-text.ts +32 -10
- package/src/core/ref-name.ts +47 -8
- package/src/core/ref.ts +172 -16
- package/src/core/resolve.ts +20 -9
- package/src/core/shot/morph.ts +1 -1
- package/src/core/shot/motion.ts +2 -3
- package/src/core/shot/still.ts +2 -2
- package/src/core/shot-cache.ts +25 -17
- package/src/core/slide-measure.ts +51 -1
- package/src/core/slide-script-eval.ts +10 -8
- package/src/core/slide-script.ts +114 -20
- package/src/core/sync.ts +2 -2
- package/src/core/visual-cache.ts +2 -0
- package/src/core/visual.ts +154 -15
- package/src/core/voice.ts +3 -5
- package/src/core/zod.ts +9 -1
- package/src/server/dev.ts +15 -4
- package/src/server/lock.ts +48 -5
- package/src/server/routes.ts +7 -2
- package/src/theme/default.css +4 -0
- package/src/video/bake.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for `@hajimism/dek`. From 0.1.0 on, release-please writes this file from the Conventional Commits on `main`.
|
|
4
4
|
|
|
5
|
+
## [0.4.0](https://github.com/hajimism/dek/compare/v0.3.4...v0.4.0) (2026-10-27)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
* **theme:** `dekc theme --json` lists in `layouts` only layouts whose names are plain words: letters, marks, and digits of any script and `@%+=:,./_-`, not starting with `=`. A layout whose name holds anything else, such as a space, a quote, `$`, `;`, or a backtick, is no longer in `layouts`; `unprintedLayouts`, present only when there are some, says how many were left out. `dekc theme <layout>` still prints such a layout's example.
|
|
11
|
+
* **ref:** a dek.toml whose [refs] pins a deck name with any other character, starting with a dash, not in NFC, or longer than 64 characters or 200 bytes, such as `owner/repo/talk(1)` or `file/a+b`, no longer loads. Delete the line the error points at, and for a pack its packs/<deck>.tgz and refs/file/<deck>, then pin the deck again under a name a ref can carry.
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **annotations:** show a deck, slide, or beat that is no plain word as a placeholder in the notes ([3b04e89](https://github.com/hajimism/dek/commit/3b04e89dc22f9ea421919c920594f53a9c02d3be))
|
|
16
|
+
* **cli:** say once where to find a deck's name a retyped command leaves as <deck> ([d4a57d9](https://github.com/hajimism/dek/commit/d4a57d9f7836b2ba2433dc634a136bb7e97be509))
|
|
17
|
+
* **config:** show a key that is no plain word as <key> in an error ([22476a7](https://github.com/hajimism/dek/commit/22476a712ecf0ee4e3c552b1e0fe6f35ac10cacf))
|
|
18
|
+
* **dev:** read .dek/server.json only as the dev server writes it ([c402cb6](https://github.com/hajimism/dek/commit/c402cb6282783425611274fbc0337cd6652add7e))
|
|
19
|
+
* **dev:** read the lock a dev server on port 80 writes ([665e0e2](https://github.com/hajimism/dek/commit/665e0e237c3a639a45fa36dc14ed8bd2e7ad9168))
|
|
20
|
+
* keep reads, writes, and suggested commands inside what the project and dek wrote ([23a667d](https://github.com/hajimism/dek/commit/23a667d199ee5f0b34040c4c33f0c0bcfd47f819))
|
|
21
|
+
* **lint:** name a remote file in DEK020's hint only when it is a plain word ([fb77d02](https://github.com/hajimism/dek/commit/fb77d028db1bb38bea6b43695838d52776b192a7))
|
|
22
|
+
* **lint:** name a slide or asset file in a hint only when it is a plain word ([eeb240c](https://github.com/hajimism/dek/commit/eeb240c418b615067bd66ff45ac6b70361d3bd1b))
|
|
23
|
+
* **lint:** show an unknown key that is no plain word as <key> in DEK008 ([f950def](https://github.com/hajimism/dek/commit/f950def7f80f06da46bd8c913ae84c8c849d5ddf))
|
|
24
|
+
* never follow a link under refs/ when writing a snapshot ([0c843ea](https://github.com/hajimism/dek/commit/0c843ea4193270dd1cc0fde4a04768e210efd911))
|
|
25
|
+
* never write or remove a pack's copy through a link under packs/ ([95980a6](https://github.com/hajimism/dek/commit/95980a631e90ee97eda24784ded50b042111512d))
|
|
26
|
+
* quote every name in a command dek suggests ([d1e608d](https://github.com/hajimism/dek/commit/d1e608d5676e46b7947bd5f43a3315269fd27773))
|
|
27
|
+
* read a project's files only through links that stay inside it ([7ae3815](https://github.com/hajimism/dek/commit/7ae38152eadd6f58d67034169ade9dd749038002))
|
|
28
|
+
* **ref:** accept only letters, digits, dots, dashes, and underscores in a ref's deck name ([1b430e1](https://github.com/hajimism/dek/commit/1b430e1ea3d0f66af2642a5f257af1cefcae3ac0))
|
|
29
|
+
* **ref:** fold names as macOS does when telling whether two refs share a folder ([fdb32ee](https://github.com/hajimism/dek/commit/fdb32ee103facc5a661f20dabf79fe8f91ae48cd))
|
|
30
|
+
* **ref:** refuse a pin whose folders another ref already takes ([0d550d8](https://github.com/hajimism/dek/commit/0d550d897b0cdad86ac3de181bf7fd031061bd66))
|
|
31
|
+
* refuse a decks folder that links out of the project ([83f2ff0](https://github.com/hajimism/dek/commit/83f2ff00079893b8e2dbb2fc5d5b7588435aa4dd))
|
|
32
|
+
* refuse to serve or write inside a ref, as every other command does ([665b2de](https://github.com/hajimism/dek/commit/665b2de7a3e6aa3b67ce6140d3eb2a15fb398ee6))
|
|
33
|
+
* serve a deck's assets only from inside the project ([86743cf](https://github.com/hajimism/dek/commit/86743cf731d97f664e95b75f1441dd238f0eeb11))
|
|
34
|
+
* take a folder for a ref only where dek wrote one, and name it from there ([62cc8df](https://github.com/hajimism/dek/commit/62cc8df0a509a99a55d9db7698683bc3be5523d4))
|
|
35
|
+
* **theme:** leave layouts whose names are no plain word out of --json ([625f6d3](https://github.com/hajimism/dek/commit/625f6d3dab0dfe16dacb272e371b551214621a86))
|
|
36
|
+
* **theme:** print only layout names that are plain words ([62b15d1](https://github.com/hajimism/dek/commit/62b15d18afb91d6adbc2641376d9ca899cb6bf20))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Documentation
|
|
40
|
+
|
|
41
|
+
* say dek follows no link under packs/ either ([5a11b2c](https://github.com/hajimism/dek/commit/5a11b2ca9960a134e261f09f80408fc770aa44da))
|
|
42
|
+
|
|
43
|
+
## [0.3.4](https://github.com/hajimism/dek/compare/v0.3.3...v0.3.4) (2026-10-27)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Features
|
|
47
|
+
|
|
48
|
+
* **pptx:** lay out boxes, pictures, text, the theme, and backgrounds as PowerPoint's own ([#27](https://github.com/hajimism/dek/issues/27)) ([eb854e9](https://github.com/hajimism/dek/commit/eb854e9835508792d3e8cfb9dd4046e8750dab7f))
|
|
49
|
+
* **skills:** teach the review in the browser, and add an optional dek-design skill ([2c18b0e](https://github.com/hajimism/dek/commit/2c18b0e339160fb03ca6ce3915b89e37d82c9522))
|
|
50
|
+
* **skills:** teach the review in the browser, and add an optional dek-design skill ([1b04394](https://github.com/hajimism/dek/commit/1b04394023a239706a2fa29d168811e1a1be05d2))
|
|
51
|
+
* **visual:** report awkward line breaks and edges a few pixels off (DEK035, DEK036) ([#28](https://github.com/hajimism/dek/issues/28)) ([ce75fc1](https://github.com/hajimism/dek/commit/ce75fc14937d76ab457298a5aca9116a0fe3778e))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Bug Fixes
|
|
55
|
+
|
|
56
|
+
* refuse a slide script's imports before Bun reads it, so none runs while dek compiles ([2752356](https://github.com/hajimism/dek/commit/275235647869f1e0a040c080476141d3aaa1aaa6))
|
|
57
|
+
* refuse a slide script's imports before Bun reads it, so none runs while dek compiles ([198930c](https://github.com/hajimism/dek/commit/198930c1d41639cc9943f36a3a187ff6eab0f364))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Documentation
|
|
61
|
+
|
|
62
|
+
* **adr:** keep dependency code away from the tokens that publish ([103f17c](https://github.com/hajimism/dek/commit/103f17cebb37dd1750caa4cc242acedf55703809))
|
|
63
|
+
* **adr:** renumber the PPTX records after the line-break ones ([ca43352](https://github.com/hajimism/dek/commit/ca4335267b6eeed7131c4123a8a543a230d65875))
|
|
64
|
+
* **adr:** renumber the PPTX records after the line-break ones ([bef6e12](https://github.com/hajimism/dek/commit/bef6e12915ac301843297fb972cf36288bbf612c))
|
|
65
|
+
* **adr:** ship an optional design skill and raise the official skill's floor to 0.2.2 ([6f39dd1](https://github.com/hajimism/dek/commit/6f39dd1e8234297cb0fb72d93722a419d1921f23))
|
|
66
|
+
* name the optional dek-design skill, and the review the official skill teaches ([6908fa1](https://github.com/hajimism/dek/commit/6908fa195e16d79c75d5b77cda72f708798de34c))
|
|
67
|
+
|
|
5
68
|
## [0.3.3](https://github.com/hajimism/dek/compare/v0.3.2...v0.3.3) (2026-10-21)
|
|
6
69
|
|
|
7
70
|
|
package/README.ja.md
CHANGED
|
@@ -44,7 +44,7 @@ bunx dekc build # 3. dist/2026-04-vite.html — トーク全体がこの 1
|
|
|
44
44
|
|
|
45
45
|
台本を書いて `dekc` を叩けば、HTML を一行も書かずに同梱テーマで発表できます。
|
|
46
46
|
|
|
47
|
-
エージェントと作るなら、公式スキルを `npx skills add hajimism/dek` で、Claude Code なら `/plugin marketplace add hajimism/dek` と `/plugin install dek@dek`
|
|
47
|
+
エージェントと作るなら、公式スキルを `npx skills add hajimism/dek --skill dek` で、Claude Code なら `/plugin marketplace add hajimism/dek` と `/plugin install dek@dek` で入れてください。`dek-design` も、同じ方法で選んで入れられます。プロジェクトの外にいるエージェントは [llms.txt](https://hajimism.github.io/dek/llms.txt) から始められます。[AI エージェントと作る](https://hajimism.github.io/dek/ja/guide/ai.html)を参照してください。
|
|
48
48
|
|
|
49
49
|
続きは [はじめる](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)にあります。
|
|
50
50
|
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ bunx dekc build # 3. dist/2026-04-vite.html — the whole talk in one file
|
|
|
44
44
|
|
|
45
45
|
Write the script, run `dekc`, and you can present with the bundled theme without writing a line of HTML.
|
|
46
46
|
|
|
47
|
-
Working with an agent? Install the official skill with `npx skills add hajimism/dek`, or in Claude Code with `/plugin marketplace add hajimism/dek` and `/plugin install dek@dek`; an agent outside any project can start from [llms.txt](https://hajimism.github.io/dek/llms.txt). See [Working with AI Agents](https://hajimism.github.io/dek/guide/ai.html).
|
|
47
|
+
Working with an agent? Install the official skill with `npx skills add hajimism/dek --skill dek`, or in Claude Code with `/plugin marketplace add hajimism/dek` and `/plugin install dek@dek`; `dek-design` is an optional skill installed the same way; an agent outside any project can start from [llms.txt](https://hajimism.github.io/dek/llms.txt). See [Working with AI Agents](https://hajimism.github.io/dek/guide/ai.html).
|
|
48
48
|
|
|
49
49
|
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).
|
|
50
50
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hajimism/dek",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A build system for talks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hajime Nakagawa (https://github.com/hajimism)",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"happy-dom": "21.0.1",
|
|
68
68
|
"knip": "6.41.0",
|
|
69
69
|
"playwright": "^1.63.0",
|
|
70
|
+
"publint": "0.3.25",
|
|
70
71
|
"typescript": "^7.0.2",
|
|
71
72
|
"vitepress": "^1.6.4"
|
|
72
73
|
},
|
package/skills/dek/SKILL.md
CHANGED
|
@@ -16,7 +16,7 @@ dek builds a talk from its script. `script.md` holds what will be said, in order
|
|
|
16
16
|
|
|
17
17
|
## First, ask the dek you have
|
|
18
18
|
|
|
19
|
-
1. `bunx dekc --version` and `bunx dekc help --agent`: every command and flag of the dek installed in this project. This file names few on purpose; use only what that list shows.
|
|
19
|
+
1. `bunx dekc --version` and `bunx dekc help --agent`: every command and flag of the dek installed in this project. This file names few on purpose; use only what that list shows. It is written for dek 0.2.2 or newer; with an older one, follow only that list and `AGENTS.md`.
|
|
20
20
|
2. Read dek's block in `AGENTS.md`. dek rewrites it for the installed version: the conventions lint checks, how to check a slide, and what to do before reporting a deck as done. Follow it as written.
|
|
21
21
|
3. In the deck, `bunx dekc theme`: the classes, tokens, and layouts this deck's theme defines, and `bunx dekc theme <layout>` for a layout's markup. Use nothing else.
|
|
22
22
|
|
|
@@ -39,6 +39,13 @@ A hint that sends a fix to `theme.css` means every slide of the deck shares the
|
|
|
39
39
|
- You have looked at `bunx dekc shot --sheet` and judged the deck's balance.
|
|
40
40
|
- Say what you could not judge, such as the argument and how the timing feels when spoken, and hand it back to the author. Passing lint means nothing measurable is wrong, not that the talk is good.
|
|
41
41
|
|
|
42
|
-
## When the human
|
|
42
|
+
## When the human reviews the deck
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
The human sees what lint cannot. Let them point at it:
|
|
45
|
+
|
|
46
|
+
1. Start the dev server in the background, since it keeps running: `bunx dekc <deck> --port <free port>`, or your harness's preview tool if it has one.
|
|
47
|
+
2. Give them the URL and ask them to press `a`, click what they want changed, write a note, and tell you when they are done. They may not know dek: name the keys, not the commands.
|
|
48
|
+
3. `bunx dekc annotations <deck> --json`, and fix every note. Shoot the slide again with `bunx dekc shot <slug>` and compare it with the note's `shot`. Tell them in short what you changed.
|
|
49
|
+
4. Ask them to look again, until they say the deck is done. Stop the dev server you started.
|
|
50
|
+
|
|
51
|
+
Leave `dekc annotations clear` to the human. If they marked beats while rehearsing, `AGENTS.md` says how to read those too.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dek-design
|
|
3
|
+
description: Make dek slides that read at a glance - a heading that states a claim, one focal point, and no visual difference without a meaning. Use when writing or reviewing the look of slides in a dek deck, or when a deck lints clean but still looks off.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dek-design
|
|
7
|
+
|
|
8
|
+
The audience sees a slide, holds it in mind, then understands it. Make each step cost as little as it can. Lint measures what a rule can decide, such as overflow and contrast; this skill is for what it cannot. Use only the classes, tokens, and layouts `bunx dekc theme` lists.
|
|
9
|
+
|
|
10
|
+
## Before you start
|
|
11
|
+
|
|
12
|
+
- Projected or read: a talk is spoken, so the words live in `script.md` and a slide shows what the voice cannot. A deck sent to be read alone needs sentences that stand without the speaker.
|
|
13
|
+
- What the audience knows: do not use a term they do not know. If you must, tie it to something they do.
|
|
14
|
+
|
|
15
|
+
## One slide
|
|
16
|
+
|
|
17
|
+
- The `##` heading is the slide's claim, in one sentence: "Builds fail on cold caches", not "Problems". Read in order, the headings tell the whole talk.
|
|
18
|
+
- The body is the evidence for the heading. Show it as a figure, a chart, or a short table where you can.
|
|
19
|
+
- Keep about four groups on a slide at most. With more, nest them or split the slide.
|
|
20
|
+
|
|
21
|
+
## How it looks
|
|
22
|
+
|
|
23
|
+
- One thing stands out most on each slide, and it is the main point. Emphasis used everywhere is emphasis nowhere.
|
|
24
|
+
- Change color, type, or layout only when the meaning changes; the audience looks for a reason in every change, so never vary for variety. Mark sections with one layout used the same way each time.
|
|
25
|
+
- When things differ, make them differ clearly. A small difference is not seen as one.
|
|
26
|
+
- Line things up. Items side by side share their top, their bottom, and the line their text starts on; a row of cards is one height even when one holds less. Items stacked share their left edge. An edge a little off reads as a mistake, not a choice, and a shot shows it where markup does not.
|
|
27
|
+
- What sits close, aligns, or shares a color reads as one group. Do not set items of different rank as a row of equal cards.
|
|
28
|
+
- Position means something: time and steps run left to right, more or better goes up, the general sits above the specific. A person or anything with a direction faces into the slide or toward what it points at.
|
|
29
|
+
- The eye goes from the heading at top left, to the body in the middle, to the conclusion or next step at bottom right.
|
|
30
|
+
|
|
31
|
+
## Charts
|
|
32
|
+
|
|
33
|
+
- A line for a trend, bars to compare values. Show amounts by position or length, not by area, angle, or hue.
|
|
34
|
+
- Label lines and bars directly instead of using a legend. Put each note next to what it explains.
|
|
35
|
+
- No icon or picture that only decorates.
|
|
36
|
+
|
|
37
|
+
## Check
|
|
38
|
+
|
|
39
|
+
1. `bunx dekc ls <deck>` lists the headings in order. Read only them: does the talk hold?
|
|
40
|
+
2. `bunx dekc shot --sheet`, then each slide's own shot: where does the eye land first, and is that the main point? Read `fill` in `bunx dekc check <slug> --json` for where the slide is empty.
|
|
41
|
+
3. In each slide's shot, edges line up: in a row, tops, bottoms, and the first lines of text; in a column, left edges. Fix any that do not.
|
|
42
|
+
4. Every difference in position, color, size, and look has a reason you can say. Remove the ones that do not.
|
package/src/cli/address.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { shellName } from "../core/escape.ts";
|
|
3
4
|
import { isInside } from "../core/path.ts";
|
|
4
|
-
import { COMMANDS } from "./commands.ts";
|
|
5
|
+
import { type AnySpec, COMMANDS, subcommandsOf } from "./commands.ts";
|
|
5
6
|
|
|
6
7
|
/** A deck a hint is about: the name a command takes, and the directory that makes it implicit. */
|
|
7
8
|
export type HintDeck = { name: string; dir: string };
|
|
@@ -10,20 +11,72 @@ export type HintDeck = { name: string; dir: string };
|
|
|
10
11
|
* A hint's commands as they run from `cwd`. Hints are written as if run inside the deck they are
|
|
11
12
|
* about, where a command finds its deck by itself. Run anywhere else, a command that takes a deck
|
|
12
13
|
* 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
|
|
14
|
-
* or
|
|
14
|
+
* where a first word names something in that deck instead, or where the name would read as a
|
|
15
|
+
* subcommand or a flag. The name is one shell word, however it is spelled, or `<deck>` when no
|
|
16
|
+
* word holds it alike in every shell, with where to find it unless `note` is false, for a hint
|
|
17
|
+
* that joins several and says it once. A command that already names a deck, or takes none, is
|
|
18
|
+
* left as written.
|
|
15
19
|
*/
|
|
16
|
-
export function addressHint(
|
|
20
|
+
export function addressHint(
|
|
21
|
+
hint: string,
|
|
22
|
+
deck: HintDeck | undefined,
|
|
23
|
+
cwd: string,
|
|
24
|
+
{ note: noted = true }: { note?: boolean } = {},
|
|
25
|
+
): string {
|
|
17
26
|
if (deck === undefined || isInside(cwd, deck.dir)) {
|
|
18
27
|
return hint;
|
|
19
28
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
const word = shellName(deck.name) ?? DECK_PLACEHOLDER;
|
|
30
|
+
const inOther = deckAround(cwd) !== undefined;
|
|
31
|
+
// The note a placeholder adds names `dekc ls` for the project, not for the deck.
|
|
32
|
+
const note = hint.indexOf(PLACEHOLDER_NOTE);
|
|
33
|
+
let placed = false;
|
|
34
|
+
const addressed = hint.replace(
|
|
35
|
+
COMMAND_RE,
|
|
36
|
+
(whole, command: string, rest: string, offset: number) => {
|
|
37
|
+
const subcommand = SUBCOMMAND_WORDS.get(command)?.has(deck.name) === true;
|
|
38
|
+
// A first word that is a subcommand, or a flag, is never the deck's name.
|
|
39
|
+
const positional = !subcommand && !deck.name.startsWith("-");
|
|
40
|
+
if (
|
|
41
|
+
!DECK_COMMANDS.has(command) ||
|
|
42
|
+
namesDeck(rest, positional ? word : undefined) ||
|
|
43
|
+
(note >= 0 && offset >= note && offset < note + PLACEHOLDER_NOTE.length)
|
|
44
|
+
) {
|
|
45
|
+
return whole;
|
|
46
|
+
}
|
|
47
|
+
placed ||= word === DECK_PLACEHOLDER;
|
|
48
|
+
const flag = inOther || subcommand;
|
|
49
|
+
return `\`dekc ${command} ${flag ? deckFlag(deck.name) : deckWord(deck.name)}${rest}\``;
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
return placed && note < 0 && noted ? `${addressed}${PLACEHOLDER_NOTE}` : addressed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `hint` with where to find a deck's name, once, when one of its commands has `<deck>` for it. */
|
|
56
|
+
export function withPlaceholderNote(hint: string): string {
|
|
57
|
+
return hint.includes(DECK_PLACEHOLDER) && !hint.includes(PLACEHOLDER_NOTE)
|
|
58
|
+
? `${hint}${PLACEHOLDER_NOTE}`
|
|
59
|
+
: hint;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Where a hint with `<deck>` in it says to find the deck's name. */
|
|
63
|
+
const PLACEHOLDER_NOTE = "; for <deck>, name the deck as `dekc ls` lists it";
|
|
64
|
+
|
|
65
|
+
/** What a command shows for a deck no word holds alike in every shell. */
|
|
66
|
+
const DECK_PLACEHOLDER = "<deck>";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A deck's name as the first word of a command: one shell word, `<deck>` when there is none, or
|
|
70
|
+
* `--deck=` when the name starts with a dash, which a first word would read as a flag.
|
|
71
|
+
*/
|
|
72
|
+
export function deckWord(name: string): string {
|
|
73
|
+
return name.startsWith("-") ? deckFlag(name) : (shellName(name) ?? DECK_PLACEHOLDER);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A deck's name as `--deck`, joined with `=` when it starts with a dash, as a value must be. */
|
|
77
|
+
export function deckFlag(name: string): string {
|
|
78
|
+
const word = shellName(name) ?? DECK_PLACEHOLDER;
|
|
79
|
+
return name.startsWith("-") ? `--deck=${word}` : `--deck ${word}`;
|
|
27
80
|
}
|
|
28
81
|
|
|
29
82
|
/** A backquoted command: `dekc`, a command word, and what follows it up to the closing quote. */
|
|
@@ -33,9 +86,26 @@ const DECK_COMMANDS = new Set(
|
|
|
33
86
|
Object.entries(COMMANDS).flatMap(([name, spec]) => ("scope" in spec && spec.scope ? [name] : [])),
|
|
34
87
|
);
|
|
35
88
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
89
|
+
/** The first word of each command's subcommands, which a deck's name in its place would read as. */
|
|
90
|
+
const SUBCOMMAND_WORDS = new Map(
|
|
91
|
+
Object.entries(COMMANDS).map(([name, spec]) => [
|
|
92
|
+
name,
|
|
93
|
+
new Set(Object.keys(subcommandsOf(spec as AnySpec)).map((sub) => sub.split(" ")[0])),
|
|
94
|
+
]),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether a command's words already name a deck: as `word`, the deck's name as one shell word,
|
|
99
|
+
* when the deck can be named first, as a ref, or with --deck.
|
|
100
|
+
*/
|
|
101
|
+
function namesDeck(rest: string, word: string | undefined): boolean {
|
|
102
|
+
const words = rest.trim();
|
|
103
|
+
const [first] = words.split(/\s+/);
|
|
104
|
+
return (
|
|
105
|
+
(word !== undefined && (words === word || words.startsWith(`${word} `))) ||
|
|
106
|
+
first?.includes("/") === true ||
|
|
107
|
+
/(?:^| )--deck(?:[ =]|$)/.test(rest)
|
|
108
|
+
);
|
|
39
109
|
}
|
|
40
110
|
|
|
41
111
|
/**
|
package/src/cli/build.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { lintDeck, lintProject } from "../core/lint.ts";
|
|
|
5
5
|
import { parsePublicUrl } from "../core/ogp.ts";
|
|
6
6
|
import { PLAYWRIGHT_INSTALL, type PlaywrightRunner } from "../core/playwright.ts";
|
|
7
7
|
import { playerScript } from "../runtime/player.ts";
|
|
8
|
-
import { type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
|
|
8
|
+
import { RENDERED_RULES, type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
|
|
9
9
|
import type { DecksTarget } from "./scope.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -77,7 +77,7 @@ export async function buildCommand(
|
|
|
77
77
|
...skippedChecks([
|
|
78
78
|
...skipped,
|
|
79
79
|
visualSkipped("dekc lint --visual", {
|
|
80
|
-
reason:
|
|
80
|
+
reason: `${RENDERED_RULES} are measured only by \`dekc lint --visual\``,
|
|
81
81
|
...(options.runner ? { runner: options.runner } : {}),
|
|
82
82
|
}),
|
|
83
83
|
]),
|
package/src/cli/check.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Diagnostic } from "../core/diagnostic.ts";
|
|
2
2
|
import { DekError } from "../core/error.ts";
|
|
3
|
+
import { shellQuote } from "../core/escape.ts";
|
|
3
4
|
import type { Fill } from "../core/fill.ts";
|
|
4
5
|
import { lintDeck } from "../core/lint.ts";
|
|
5
6
|
import { type PlaywrightRunner, playwrightMissingError } from "../core/playwright.ts";
|
|
@@ -53,7 +54,7 @@ export async function checkCommand(
|
|
|
53
54
|
|
|
54
55
|
const skipped: SkippedCheck[] = visual
|
|
55
56
|
? []
|
|
56
|
-
: [visualSkipped(`dekc check ${slug}`, { reason: "Playwright is not installed" })];
|
|
57
|
+
: [visualSkipped(`dekc check ${shellQuote(slug)}`, { reason: "Playwright is not installed" })];
|
|
57
58
|
|
|
58
59
|
// A live-only deck is done without voice, so asking for it skips the check rather than failing,
|
|
59
60
|
// the way a missing Playwright skips visual.
|
package/src/cli/commands.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasErrors } from "../core/diagnostic.ts";
|
|
2
|
+
import { shellQuote } from "../core/escape.ts";
|
|
2
3
|
import { isRefName } from "../core/ref-name.ts";
|
|
3
4
|
import { mergeSarif, toSarif } from "../core/sarif.ts";
|
|
4
5
|
import type { CheckCliResult } from "./check.ts";
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
formatNew,
|
|
25
26
|
formatPack,
|
|
26
27
|
formatPdf,
|
|
28
|
+
formatPptx,
|
|
27
29
|
formatRef,
|
|
28
30
|
formatReport,
|
|
29
31
|
formatShot,
|
|
@@ -444,7 +446,7 @@ export const COMMANDS = {
|
|
|
444
446
|
text: formatCheck,
|
|
445
447
|
notes: (data, color) => formatSkipped(data.skipped, color) || undefined,
|
|
446
448
|
paths: withDisplayDiagnostics,
|
|
447
|
-
failure: (data) => diagnosticFailure("check", data, `dekc check ${data.slug}`),
|
|
449
|
+
failure: (data) => diagnosticFailure("check", data, `dekc check ${shellQuote(data.slug)}`),
|
|
448
450
|
},
|
|
449
451
|
}),
|
|
450
452
|
shot: result({
|
|
@@ -659,7 +661,7 @@ export const COMMANDS = {
|
|
|
659
661
|
group: "Output",
|
|
660
662
|
overview: [
|
|
661
663
|
["dekc lint [--fix]", "check script.md against slides; --fix syncs first"],
|
|
662
|
-
["dekc lint --visual", "add overflow and
|
|
664
|
+
["dekc lint --visual", "add overflow, contrast, line-break, and alignment rules"],
|
|
663
665
|
["dekc lint --format sarif", "print diagnostics as SARIF, for code scanning"],
|
|
664
666
|
],
|
|
665
667
|
agent: ["dekc lint [--fix] [--visual] [--format sarif]"],
|
|
@@ -827,19 +829,22 @@ export const COMMANDS = {
|
|
|
827
829
|
flags: ["root-dist", "public"],
|
|
828
830
|
usage: ["dekc pptx [deck] [--root-dist] [--public]"],
|
|
829
831
|
summary:
|
|
830
|
-
"Write dist/<deck>.pptx, one slide per slide at its last beat: a picture of
|
|
832
|
+
"Write dist/<deck>.pptx, one slide per slide at its last beat: its plain boxes, pictures, and\ntext as PowerPoint's own over a picture of the rest, in the deck's theme, and its script in\nthe notes. Needs Playwright.",
|
|
831
833
|
group: "Output",
|
|
832
834
|
overview: [
|
|
833
|
-
[
|
|
835
|
+
[
|
|
836
|
+
"dekc pptx [--root-dist] [--public]",
|
|
837
|
+
"write a PowerPoint file with editable shapes and text",
|
|
838
|
+
],
|
|
834
839
|
],
|
|
835
840
|
agent: [
|
|
836
|
-
"dekc pptx [--root-dist] [--public] text editable over a picture of
|
|
841
|
+
"dekc pptx [--root-dist] [--public] boxes, pictures, and text editable over a picture of the rest; script in notes",
|
|
837
842
|
],
|
|
838
843
|
run: async ({ target, flags }) => {
|
|
839
844
|
const { pptxCommand } = await import("./pptx.ts");
|
|
840
845
|
return pptxCommand(target, { rootDist: flags["root-dist"], public: flags.public });
|
|
841
846
|
},
|
|
842
|
-
output: { text:
|
|
847
|
+
output: { text: formatPptx, json: ({ outs }) => ({ outs }) },
|
|
843
848
|
}),
|
|
844
849
|
help: {
|
|
845
850
|
kind: "help",
|
package/src/cli/contract.ts
CHANGED
|
@@ -214,7 +214,15 @@ export const RESULT_FIELDS = {
|
|
|
214
214
|
"State classes the theme selects that the player sets at runtime (is-current, is-shown); never written in markup.",
|
|
215
215
|
),
|
|
216
216
|
tokens: z.array(z.strictObject({ name: z.string(), value: z.string() })),
|
|
217
|
-
layouts: z
|
|
217
|
+
layouts: z
|
|
218
|
+
.array(z.strictObject({ name: z.string(), example: z.string().optional() }))
|
|
219
|
+
.describe("The layouts whose names are plain words, which dek prints."),
|
|
220
|
+
unprintedLayouts: z
|
|
221
|
+
.number()
|
|
222
|
+
.optional()
|
|
223
|
+
.describe(
|
|
224
|
+
"How many layouts the theme has whose names are no plain word, left out of layouts; absent when none.",
|
|
225
|
+
),
|
|
218
226
|
layout: z.strictObject({ name: z.string(), example: z.string() }).optional(),
|
|
219
227
|
ref: RefInfo.optional(),
|
|
220
228
|
}),
|
package/src/cli/files.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { bundledTheme } from "../core/bundled-theme.ts";
|
|
|
4
4
|
import type { DekConfig } from "../core/config.ts";
|
|
5
5
|
import { deckPaths } from "../core/deck-paths.ts";
|
|
6
6
|
import { DekError } from "../core/error.ts";
|
|
7
|
+
import { shellQuote } from "../core/escape.ts";
|
|
7
8
|
import { walkUp } from "../core/optional.ts";
|
|
8
9
|
import { PLAYWRIGHT_INSTALL } from "../core/playwright.ts";
|
|
9
10
|
import { readSourceIfExists, writeInside } from "../core/safe-fs.ts";
|
|
@@ -316,8 +317,3 @@ function inNodeModules(root: string, ...path: string[]): boolean {
|
|
|
316
317
|
walkUp(root, (dir) => existsSync(join(dir, "node_modules", ...path)) || undefined) === true
|
|
317
318
|
);
|
|
318
319
|
}
|
|
319
|
-
|
|
320
|
-
/** `word` as one shell word: as it is when that is safe, else single-quoted. */
|
|
321
|
-
export function shellQuote(word: string): string {
|
|
322
|
-
return /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
323
|
-
}
|
package/src/cli/flags.ts
CHANGED
|
@@ -41,7 +41,7 @@ export const FLAGS = {
|
|
|
41
41
|
fix: { type: "boolean", text: "create missing skeleton slides before linting" },
|
|
42
42
|
visual: {
|
|
43
43
|
type: "boolean",
|
|
44
|
-
text: "add the rendered rules: overflow and
|
|
44
|
+
text: "add the rendered rules: overflow, contrast, line breaks, and alignment (needs Playwright)",
|
|
45
45
|
},
|
|
46
46
|
shot: { type: "boolean", text: "also write a screenshot and return its path" },
|
|
47
47
|
voice: { type: "boolean", text: "also return each sentence's reading and duration" },
|
package/src/cli/goto.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DekError } from "../core/error.ts";
|
|
2
2
|
import { deckRoutePath } from "../runtime/routes.ts";
|
|
3
3
|
import { isPidAlive, readDevServerLock } from "../server/lock.ts";
|
|
4
|
+
import { deckFlag, deckWord } from "./address.ts";
|
|
5
|
+
import { isTypedCommand } from "./commands.ts";
|
|
4
6
|
import { type DeckTarget, requireSection } from "./scope.ts";
|
|
5
7
|
|
|
6
8
|
export type NavResult = {
|
|
@@ -82,7 +84,7 @@ function refusal(
|
|
|
82
84
|
): DekError {
|
|
83
85
|
if (status === 404 && lock.deck !== undefined && lock.deck !== deckName) {
|
|
84
86
|
return new DekError(`the running dev server serves deck "${lock.deck}", not "${deckName}"`, {
|
|
85
|
-
hint: `pass
|
|
87
|
+
hint: `pass ${deckFlag(lock.deck)}, or stop the server at ${lock.url} and run \`dekc ${isTypedCommand(deckName) ? deckFlag(deckName) : deckWord(deckName)}\``,
|
|
86
88
|
});
|
|
87
89
|
}
|
|
88
90
|
if (status === 401) {
|
package/src/cli/init.ts
CHANGED
|
@@ -3,10 +3,12 @@ import { basename, join, resolve } from "node:path";
|
|
|
3
3
|
import { parseCss, publishedTokens } from "../core/css.ts";
|
|
4
4
|
import { deckPaths } from "../core/deck-paths.ts";
|
|
5
5
|
import { DekError } from "../core/error.ts";
|
|
6
|
+
import { shellQuote } from "../core/escape.ts";
|
|
6
7
|
import { REQUIRED_TOKENS } from "../core/lint/tokens.ts";
|
|
7
8
|
import { walkUp } from "../core/optional.ts";
|
|
8
9
|
import { DECK_NAME_HINT, isDeckName } from "../core/path.ts";
|
|
9
|
-
import {
|
|
10
|
+
import { refuseInsideRef } from "../core/ref.ts";
|
|
11
|
+
import { readSourceIfExists } from "../core/safe-fs.ts";
|
|
10
12
|
import {
|
|
11
13
|
defaultTsconfig,
|
|
12
14
|
dekFilePaths,
|
|
@@ -25,7 +27,6 @@ import {
|
|
|
25
27
|
nextSteps,
|
|
26
28
|
type PlannedPath,
|
|
27
29
|
playwrightStep,
|
|
28
|
-
shellQuote,
|
|
29
30
|
} from "./files.ts";
|
|
30
31
|
|
|
31
32
|
export type InitResult = {
|
|
@@ -84,7 +85,7 @@ export function initCommand(options: { cwd: string; dir?: string; deck?: string
|
|
|
84
85
|
/** The tokens every deck needs that the project's theme.css does not publish on `.slide`. */
|
|
85
86
|
function missingThemeTokens(root: string): string[] {
|
|
86
87
|
const published = new Set(
|
|
87
|
-
publishedTokens(parseCss(
|
|
88
|
+
publishedTokens(parseCss(readSourceIfExists(join(root, "theme.css"), root) ?? "")).map(
|
|
88
89
|
(token) => token.name,
|
|
89
90
|
),
|
|
90
91
|
);
|
|
@@ -100,8 +101,10 @@ function syncDeckFiles(deckDir: string, created: string[]): FileChanges {
|
|
|
100
101
|
/** Everything init writes but `.dek/` and AGENTS.md, in the order a reader meets it. */
|
|
101
102
|
function projectPlan(root: string, deck: string | undefined): PlannedPath[] {
|
|
102
103
|
const themePath = join(root, "theme.css");
|
|
103
|
-
// A first deck is drawn in the theme that will be there: the one kept, or dek's.
|
|
104
|
-
|
|
104
|
+
// A first deck is drawn in the theme that will be there: the one kept, or dek's. A folder that
|
|
105
|
+
// is not there yet holds no theme.
|
|
106
|
+
const theme =
|
|
107
|
+
(existsSync(root) ? readSourceIfExists(themePath, root) : undefined) ?? defaultTheme();
|
|
105
108
|
return [
|
|
106
109
|
{ path: join(root, "dek.toml"), contents: defaultToml() },
|
|
107
110
|
{ path: themePath, contents: defaultTheme() },
|
|
@@ -123,6 +126,8 @@ function checkTarget(root: string): void {
|
|
|
123
126
|
if (existsSync(root) && !statSync(root).isDirectory()) {
|
|
124
127
|
throw new DekError("not a directory", { path: root, hint: "pass a directory to init" });
|
|
125
128
|
}
|
|
129
|
+
// A ref is someone else's project, read where it is: init would write its files into it.
|
|
130
|
+
refuseInsideRef(root);
|
|
126
131
|
const outer = walkUp(root, (dir) => {
|
|
127
132
|
const config = join(dir, "dek.toml");
|
|
128
133
|
return existsSync(config) && statSync(config).isFile() ? dir : undefined;
|
package/src/cli/lint.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { relative } from "node:path";
|
|
3
3
|
import type { Diagnostic } from "../core/diagnostic.ts";
|
|
4
|
+
import { shellQuote } from "../core/escape.ts";
|
|
4
5
|
import { lintDeck, lintProject, unreadableScriptDiagnostics } from "../core/lint.ts";
|
|
5
6
|
import { playwrightMissingError } from "../core/playwright.ts";
|
|
6
7
|
import { resolveRumdlBin, runRumdl } from "../core/rumdl.ts";
|
|
7
8
|
import { mergeSarif, type SarifLog } from "../core/sarif.ts";
|
|
8
9
|
import { syncDeck } from "../core/sync.ts";
|
|
9
10
|
import { lintVisualDeck } from "../core/visual.ts";
|
|
10
|
-
import { type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
|
|
11
|
+
import { RENDERED_RULES, type SkippedCheck, skippedChecks, visualSkipped } from "./result.ts";
|
|
11
12
|
import type { DecksTarget } from "./scope.ts";
|
|
12
13
|
|
|
13
14
|
export type LintCliResult = {
|
|
@@ -60,7 +61,7 @@ export async function lintCommand(
|
|
|
60
61
|
? []
|
|
61
62
|
: [
|
|
62
63
|
visualSkipped("dekc lint --visual", {
|
|
63
|
-
reason:
|
|
64
|
+
reason: `${RENDERED_RULES} are measured only with --visual`,
|
|
64
65
|
}),
|
|
65
66
|
]),
|
|
66
67
|
]),
|
|
@@ -79,6 +80,6 @@ function rumdlSkipped(scriptPath: string, cwd: string): SkippedCheck {
|
|
|
79
80
|
return {
|
|
80
81
|
check: "rumdl",
|
|
81
82
|
reason: "rumdl ran but gave no result",
|
|
82
|
-
hint: `run \`${bin} check ${relative(cwd, scriptPath) || scriptPath}\` to see why`,
|
|
83
|
+
hint: `run \`${shellQuote(bin)} check ${shellQuote(relative(cwd, scriptPath) || scriptPath)}\` to see why`,
|
|
83
84
|
};
|
|
84
85
|
}
|
package/src/cli/main.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { DekError } from "../core/error.ts";
|
|
2
|
+
import { shellQuote } from "../core/escape.ts";
|
|
2
3
|
import { either } from "../core/prose.ts";
|
|
3
4
|
import { isRefName } from "../core/ref-name.ts";
|
|
4
5
|
import { resolveProject } from "../core/resolve.ts";
|
|
5
|
-
import { addressHint } from "./address.ts";
|
|
6
|
+
import { addressHint, deckWord, type HintDeck, withPlaceholderNote } from "./address.ts";
|
|
6
7
|
import {
|
|
7
8
|
type AnySpec,
|
|
8
9
|
type CliResult,
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
subcommandAt,
|
|
14
15
|
subcommandsOf,
|
|
15
16
|
} from "./commands.ts";
|
|
16
|
-
import { shellQuote } from "./files.ts";
|
|
17
17
|
import { type CommandLine, parseCommandLine, wantsJson } from "./flags.ts";
|
|
18
18
|
import { writeFailure, writeSuccess } from "./result.ts";
|
|
19
19
|
import { DeckRequiredError, namesDeck, peelDeckArg, resolveTarget } from "./scope.ts";
|
|
@@ -197,12 +197,14 @@ function retypedWithDeck<T>(line: CommandLine, cwd: string, resolve: () => T): T
|
|
|
197
197
|
}
|
|
198
198
|
const typed = `\`dekc ${line.typed.map(shellQuote).join(" ")}\``;
|
|
199
199
|
const [first] = error.decks;
|
|
200
|
+
// Each command is retyped for one deck; where to find a name left as <deck> is said once.
|
|
201
|
+
const retyped = (deck: HintDeck) => addressHint(typed, deck, cwd, { note: false });
|
|
200
202
|
const hint = !first
|
|
201
203
|
? "run `dekc new <name>` to make a deck"
|
|
202
204
|
: error.decks.length > NAMED_DECKS
|
|
203
|
-
? `run ${
|
|
204
|
-
: `run ${either(error.decks.map(
|
|
205
|
-
throw new DekError(error.message, { hint, cause: error });
|
|
205
|
+
? `run ${retyped(first)}, or name another deck in place of ${deckWord(first.name)}; \`dekc ls\` lists them`
|
|
206
|
+
: `run ${either(error.decks.map(retyped))}`;
|
|
207
|
+
throw new DekError(error.message, { hint: withPlaceholderNote(hint), cause: error });
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
210
|
|
package/src/cli/new.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { loadConfig } from "../core/config.ts";
|
|
4
4
|
import { deckPaths } from "../core/deck-paths.ts";
|
|
5
5
|
import { DekError } from "../core/error.ts";
|
|
6
6
|
import { DECK_NAME_HINT, isDeckName } from "../core/path.ts";
|
|
7
7
|
import { isRefName } from "../core/ref-name.ts";
|
|
8
|
+
import { readDeckFile } from "../core/resolve.ts";
|
|
9
|
+
import { readSourceIfExists } from "../core/safe-fs.ts";
|
|
8
10
|
import { syncDeck } from "../core/sync.ts";
|
|
9
11
|
import { applyPlan, deckPlan, nextSteps } from "./files.ts";
|
|
10
12
|
import { type RefInfo, requireDeckFromCwd, requireProject, resolveTarget } from "./scope.ts";
|
|
@@ -44,12 +46,17 @@ export function newCommand(options: { cwd: string; name: string; themeFrom?: str
|
|
|
44
46
|
? resolveTarget(options.cwd, "deck", { refs: true, deck: options.themeFrom })
|
|
45
47
|
: undefined;
|
|
46
48
|
const ref = fromRef?.ref;
|
|
47
|
-
const
|
|
48
|
-
?
|
|
49
|
+
const fromDeck = fromRef
|
|
50
|
+
? fromRef.deck
|
|
49
51
|
: options.themeFrom
|
|
50
|
-
?
|
|
51
|
-
:
|
|
52
|
-
|
|
52
|
+
? requireDeckFromCwd(options.cwd, options.themeFrom).deck
|
|
53
|
+
: undefined;
|
|
54
|
+
const themeSource = fromDeck ? deckPaths(fromDeck.dir).theme : join(project.root, "theme.css");
|
|
55
|
+
// What is copied is published with the new deck: a link out of its project is refused.
|
|
56
|
+
const source = fromDeck
|
|
57
|
+
? readDeckFile(fromDeck.dir, themeSource)
|
|
58
|
+
: readSourceIfExists(themeSource, project.root);
|
|
59
|
+
if (source === undefined) {
|
|
53
60
|
throw new DekError("theme.css not found", {
|
|
54
61
|
path: themeSource,
|
|
55
62
|
hint: ref
|
|
@@ -60,7 +67,7 @@ export function newCommand(options: { cwd: string; name: string; themeFrom?: str
|
|
|
60
67
|
});
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
const theme = `${ref ? provenance(ref) : ""}${
|
|
70
|
+
const theme = `${ref ? provenance(ref) : ""}${source}`;
|
|
64
71
|
const { created } = applyPlan(
|
|
65
72
|
deckPlan(project.root, name, theme, loadConfig(project.configPath)),
|
|
66
73
|
);
|