@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.
Files changed (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +75 -0
  3. package/README.md +75 -0
  4. package/package.json +65 -0
  5. package/src/cli/address.ts +53 -0
  6. package/src/cli/build.ts +85 -0
  7. package/src/cli/check.ts +97 -0
  8. package/src/cli/commands.ts +851 -0
  9. package/src/cli/contract.ts +444 -0
  10. package/src/cli/cues.ts +18 -0
  11. package/src/cli/files.ts +309 -0
  12. package/src/cli/flags.ts +295 -0
  13. package/src/cli/format.ts +156 -0
  14. package/src/cli/goto.ts +96 -0
  15. package/src/cli/init.ts +136 -0
  16. package/src/cli/keep-alive.ts +72 -0
  17. package/src/cli/lint.ts +84 -0
  18. package/src/cli/ls.ts +119 -0
  19. package/src/cli/main.ts +231 -0
  20. package/src/cli/marks.ts +16 -0
  21. package/src/cli/mv.ts +37 -0
  22. package/src/cli/new.ts +64 -0
  23. package/src/cli/pdf.ts +16 -0
  24. package/src/cli/qr.ts +466 -0
  25. package/src/cli/ref.ts +165 -0
  26. package/src/cli/rehearse.ts +42 -0
  27. package/src/cli/result.ts +161 -0
  28. package/src/cli/scope.ts +308 -0
  29. package/src/cli/serve.ts +125 -0
  30. package/src/cli/shot.ts +134 -0
  31. package/src/cli/show.ts +103 -0
  32. package/src/cli/sync.ts +37 -0
  33. package/src/cli/text.ts +479 -0
  34. package/src/cli/theme.ts +56 -0
  35. package/src/cli/tty.ts +81 -0
  36. package/src/cli/usage.ts +198 -0
  37. package/src/cli/video.ts +17 -0
  38. package/src/cli/voice.ts +113 -0
  39. package/src/cli.ts +4 -0
  40. package/src/core/assets.ts +239 -0
  41. package/src/core/build.ts +77 -0
  42. package/src/core/bundled-theme.ts +15 -0
  43. package/src/core/capture-go.ts +72 -0
  44. package/src/core/chrome.ts +128 -0
  45. package/src/core/config-keys.ts +82 -0
  46. package/src/core/config.ts +95 -0
  47. package/src/core/css-scan.ts +158 -0
  48. package/src/core/css-transform.ts +140 -0
  49. package/src/core/css-value.ts +127 -0
  50. package/src/core/css.ts +545 -0
  51. package/src/core/cue.ts +187 -0
  52. package/src/core/deck-paths.ts +34 -0
  53. package/src/core/diagnostic.ts +114 -0
  54. package/src/core/document.ts +218 -0
  55. package/src/core/error.ts +31 -0
  56. package/src/core/escape.ts +16 -0
  57. package/src/core/fill.ts +100 -0
  58. package/src/core/finish-beat.ts +30 -0
  59. package/src/core/github.ts +234 -0
  60. package/src/core/html-scan.ts +253 -0
  61. package/src/core/html.ts +234 -0
  62. package/src/core/in-page-go.ts +128 -0
  63. package/src/core/lines.ts +43 -0
  64. package/src/core/lint/asset-refs.ts +109 -0
  65. package/src/core/lint/context.ts +116 -0
  66. package/src/core/lint/files.ts +117 -0
  67. package/src/core/lint/keys.ts +48 -0
  68. package/src/core/lint/morph.ts +91 -0
  69. package/src/core/lint/script.ts +102 -0
  70. package/src/core/lint/shared-styles.ts +165 -0
  71. package/src/core/lint/slide-html.ts +466 -0
  72. package/src/core/lint/slide-script.ts +179 -0
  73. package/src/core/lint/theme-css.ts +295 -0
  74. package/src/core/lint/timing.ts +66 -0
  75. package/src/core/lint/tokens.ts +520 -0
  76. package/src/core/lint/voice.ts +76 -0
  77. package/src/core/lint.ts +137 -0
  78. package/src/core/live-protocol.ts +130 -0
  79. package/src/core/marks.ts +198 -0
  80. package/src/core/mv.ts +293 -0
  81. package/src/core/ogp.ts +99 -0
  82. package/src/core/optional.ts +24 -0
  83. package/src/core/overflow.ts +220 -0
  84. package/src/core/page-pool.ts +53 -0
  85. package/src/core/page.ts +66 -0
  86. package/src/core/parse.ts +307 -0
  87. package/src/core/path.ts +58 -0
  88. package/src/core/pdf.ts +71 -0
  89. package/src/core/playwright-module.d.ts +43 -0
  90. package/src/core/playwright-motion.ts +42 -0
  91. package/src/core/playwright-sheet.ts +32 -0
  92. package/src/core/playwright-visual.ts +391 -0
  93. package/src/core/playwright-worker.ts +5 -0
  94. package/src/core/playwright.ts +431 -0
  95. package/src/core/position.ts +146 -0
  96. package/src/core/presenter-state.ts +43 -0
  97. package/src/core/presenter.ts +131 -0
  98. package/src/core/prose.ts +16 -0
  99. package/src/core/pseudo-text.ts +67 -0
  100. package/src/core/rail-width.ts +4 -0
  101. package/src/core/ref-name.ts +110 -0
  102. package/src/core/ref.ts +238 -0
  103. package/src/core/resolve.ts +228 -0
  104. package/src/core/rumdl.ts +99 -0
  105. package/src/core/safe-fs.ts +196 -0
  106. package/src/core/sarif.ts +135 -0
  107. package/src/core/schema.ts +89 -0
  108. package/src/core/script-lines.ts +53 -0
  109. package/src/core/sheet.ts +223 -0
  110. package/src/core/shot/morph.ts +80 -0
  111. package/src/core/shot/motion.ts +187 -0
  112. package/src/core/shot/still.ts +216 -0
  113. package/src/core/shot-cache.ts +140 -0
  114. package/src/core/size.ts +11 -0
  115. package/src/core/skeleton-record.ts +51 -0
  116. package/src/core/skeleton.ts +89 -0
  117. package/src/core/slide-draw.ts +165 -0
  118. package/src/core/slide-eval-worker.ts +58 -0
  119. package/src/core/slide-measure.ts +272 -0
  120. package/src/core/slide-script-eval.ts +258 -0
  121. package/src/core/slide-script.ts +252 -0
  122. package/src/core/spawn.ts +128 -0
  123. package/src/core/step.ts +94 -0
  124. package/src/core/still-page.ts +70 -0
  125. package/src/core/suggest.ts +33 -0
  126. package/src/core/sync.ts +316 -0
  127. package/src/core/text-contrast.ts +466 -0
  128. package/src/core/theme-excerpt.ts +187 -0
  129. package/src/core/theme-facts.ts +51 -0
  130. package/src/core/timeline.ts +216 -0
  131. package/src/core/timing.ts +88 -0
  132. package/src/core/toml-keys.ts +349 -0
  133. package/src/core/url-attributes.ts +103 -0
  134. package/src/core/video-document.ts +15 -0
  135. package/src/core/visual-cache.ts +100 -0
  136. package/src/core/visual.ts +558 -0
  137. package/src/core/voice.ts +321 -0
  138. package/src/core/zod.ts +79 -0
  139. package/src/runtime/browser.ts +203 -0
  140. package/src/runtime/clone.ts +52 -0
  141. package/src/runtime/deck-control.ts +43 -0
  142. package/src/runtime/fit.ts +32 -0
  143. package/src/runtime/go.ts +56 -0
  144. package/src/runtime/input.ts +128 -0
  145. package/src/runtime/laser-view.ts +110 -0
  146. package/src/runtime/laser.ts +145 -0
  147. package/src/runtime/live-host.ts +33 -0
  148. package/src/runtime/live-reload.ts +29 -0
  149. package/src/runtime/live.ts +88 -0
  150. package/src/runtime/marks-view.ts +74 -0
  151. package/src/runtime/motion.ts +113 -0
  152. package/src/runtime/navigator.ts +189 -0
  153. package/src/runtime/player.ts +28 -0
  154. package/src/runtime/presenter-view.ts +289 -0
  155. package/src/runtime/presenter.ts +52 -0
  156. package/src/runtime/rail-view.ts +229 -0
  157. package/src/runtime/rail.ts +52 -0
  158. package/src/runtime/rehearse.ts +264 -0
  159. package/src/runtime/routes.ts +142 -0
  160. package/src/runtime/settle.ts +45 -0
  161. package/src/runtime/slide.d.ts +28 -0
  162. package/src/runtime/socket.ts +143 -0
  163. package/src/runtime/stage.ts +259 -0
  164. package/src/runtime/step.ts +139 -0
  165. package/src/runtime/window.ts +38 -0
  166. package/src/server/auth.ts +210 -0
  167. package/src/server/deck-snapshot.ts +146 -0
  168. package/src/server/dev.ts +251 -0
  169. package/src/server/hub.ts +104 -0
  170. package/src/server/lan.ts +51 -0
  171. package/src/server/lock.ts +97 -0
  172. package/src/server/pages.ts +71 -0
  173. package/src/server/pairing.ts +40 -0
  174. package/src/server/project-watch.ts +142 -0
  175. package/src/server/rooms.ts +133 -0
  176. package/src/server/routes.ts +286 -0
  177. package/src/server/serial.ts +25 -0
  178. package/src/server/watch.ts +308 -0
  179. package/src/theme/default.css +175 -0
  180. package/src/video/bake.ts +152 -0
  181. package/src/video/mux.ts +103 -0
  182. package/src/video/recorder.ts +115 -0
  183. package/src/video/sidecar.ts +60 -0
  184. package/src/video/worker.ts +51 -0
  185. package/src/voice/engine.ts +177 -0
  186. package/src/voice/synth.ts +127 -0
  187. package/src/voice/wav.ts +115 -0
@@ -0,0 +1,444 @@
1
+ import { z } from "zod";
2
+ import type { DiagnosticValue as DiagnosticValueType } from "../core/diagnostic.ts";
3
+ import type { ResultCommand } from "./commands.ts";
4
+ import { type CommandLine, parseCommandLine } from "./flags.ts";
5
+
6
+ /**
7
+ * What `--json` prints, stated once. The commands' own types are checked against it where
8
+ * `COMMANDS` is declared, the tests hold every line a test run prints to it, and the JSON Schema
9
+ * published beside the docs is generated from it, so none of the three can drift from the others.
10
+ * Every object is strict: a field the contract does not name is a change to the contract.
11
+ */
12
+
13
+ const DiagnosticValue: z.ZodType<DiagnosticValueType> = z
14
+ .lazy(() =>
15
+ z.union([
16
+ z.string(),
17
+ z.number(),
18
+ z.boolean(),
19
+ z.array(DiagnosticValue),
20
+ z.record(z.string(), DiagnosticValue),
21
+ ]),
22
+ )
23
+ .meta({ id: "DiagnosticValue" });
24
+
25
+ const Diagnostic = z
26
+ .strictObject({
27
+ id: z.string().describe("DEKnnn for dek's own rules; another tool's id for its findings."),
28
+ severity: z.enum(["error", "warning"]).describe("Only an error fails lint and check."),
29
+ message: z.string(),
30
+ path: z.string().optional(),
31
+ line: z.number().int().optional(),
32
+ column: z.number().int().optional().describe("1-based, in UTF-16 units as editors count."),
33
+ slug: z.string().optional().describe("The slide the finding is about."),
34
+ hint: z.string().optional().describe("The fix, phrased as what to do next."),
35
+ data: z
36
+ .record(z.string(), DiagnosticValue)
37
+ .optional()
38
+ .describe("The values the message names, as fields."),
39
+ })
40
+ .meta({ id: "Diagnostic" });
41
+
42
+ const SkippedCheck = z
43
+ .strictObject({
44
+ check: z.enum(["lint", "preview", "rumdl", "visual", "voice"]),
45
+ reason: z.string(),
46
+ hint: z.string().optional().describe("What to do so that the check runs."),
47
+ })
48
+ .meta({
49
+ id: "SkippedCheck",
50
+ description: "A check that did not run, so an empty list of findings is no pass for it.",
51
+ });
52
+
53
+ const Failure = z
54
+ .strictObject({
55
+ message: z.string(),
56
+ path: z.string().optional(),
57
+ line: z.number().int().optional(),
58
+ hint: z.string().optional().describe("The next step, whenever dek knows it."),
59
+ })
60
+ .meta({ id: "Error" });
61
+
62
+ const RefInfo = z
63
+ .strictObject({
64
+ name: z.string(),
65
+ rev: z.string(),
66
+ dir: z.string().describe("The snapshot directory."),
67
+ license: z.string().nullable().describe("The snapshot's license file."),
68
+ })
69
+ .meta({ id: "RefInfo", description: "Set when the deck read is a ref." });
70
+
71
+ const Position = z
72
+ .strictObject({ slideIndex: z.number().int(), beatIndex: z.number().int() })
73
+ .meta({ id: "Position" });
74
+
75
+ const paths = z.array(z.string());
76
+ const diagnostics = z.array(Diagnostic);
77
+ const skipped = z.array(SkippedCheck).optional().describe("Absent when every check ran.");
78
+
79
+ const nav = z.strictObject({
80
+ slug: z.string(),
81
+ slideIndex: z.number().int(),
82
+ beatIndex: z.number().int(),
83
+ viewers: z
84
+ .number()
85
+ .int()
86
+ .describe("The pages that show the deck; with none, where the next one opened lands."),
87
+ });
88
+
89
+ const shotFile = z.strictObject({
90
+ slug: z.string(),
91
+ step: z.string(),
92
+ path: z.string(),
93
+ to: z.string().optional().describe("A morph frame: the slide the transition goes to."),
94
+ at: z.number().optional().describe("A morph frame: where in the transition, 0..1."),
95
+ });
96
+
97
+ /** The fields each result command prints beside `ok`: one object, or one per form it takes. */
98
+ export const RESULT_FIELDS = {
99
+ init: z.strictObject({
100
+ root: z.string(),
101
+ created: paths,
102
+ updated: paths.describe("dek's own files, already there, brought up to date."),
103
+ kept: paths.describe("Files already there that differ from what init would write."),
104
+ next: z.array(z.string()).describe("The commands to run next."),
105
+ playwright: z.string().optional().describe("How to install Playwright, when it is missing."),
106
+ missingTokens: z.array(z.string()).optional(),
107
+ }),
108
+ new: z.strictObject({
109
+ name: z.string(),
110
+ dir: z.string(),
111
+ created: paths,
112
+ updated: paths,
113
+ next: z.array(z.string()),
114
+ }),
115
+ ls: z.union([
116
+ z.strictObject({
117
+ root: z.string(),
118
+ diagnostics: diagnostics.describe("The project's own findings, once."),
119
+ decks: z.array(
120
+ z.strictObject({
121
+ name: z.string(),
122
+ title: z.string(),
123
+ sections: z.number().int(),
124
+ slides: z.number().int(),
125
+ diagnostics,
126
+ }),
127
+ ),
128
+ failed: z.array(z.strictObject({ name: z.string() })),
129
+ }),
130
+ z.strictObject({
131
+ name: z.string(),
132
+ title: z.string(),
133
+ event: z.string().optional(),
134
+ date: z.string().optional(),
135
+ duration: z.string().optional(),
136
+ estimateSeconds: z.number(),
137
+ videoSeconds: z.number().optional(),
138
+ sections: z.array(
139
+ z.strictObject({
140
+ slug: z.string(),
141
+ title: z.string(),
142
+ beats: z.number().int(),
143
+ estimateSeconds: z.number(),
144
+ budgetSeconds: z.number().optional(),
145
+ videoSeconds: z.number().optional(),
146
+ }),
147
+ ),
148
+ diagnostics,
149
+ skipped,
150
+ ref: RefInfo.optional(),
151
+ }),
152
+ ]),
153
+ ref: z.union([
154
+ z.strictObject({
155
+ action: z.literal("add"),
156
+ name: z.string(),
157
+ rev: z.string(),
158
+ from: z.string().optional().describe("The rev it was pinned to before, when this moved it."),
159
+ changed: z.boolean(),
160
+ dir: z.string(),
161
+ license: z.string().nullable(),
162
+ warnings: z.array(z.string()),
163
+ }),
164
+ z.strictObject({
165
+ action: z.literal("list"),
166
+ refs: z.array(
167
+ z.strictObject({
168
+ name: z.string(),
169
+ rev: z.string(),
170
+ fetched: z.boolean(),
171
+ title: z.string().optional(),
172
+ slides: z.number().int().optional(),
173
+ }),
174
+ ),
175
+ }),
176
+ z.strictObject({ action: z.literal("rm"), name: z.string() }),
177
+ ]),
178
+ show: z.strictObject({
179
+ slug: z.string(),
180
+ title: z.string(),
181
+ script: z
182
+ .string()
183
+ .describe("The section as script.md has it, beat headings and their ids included."),
184
+ beats: z
185
+ .array(
186
+ z.strictObject({ id: z.string().optional(), title: z.string(), line: z.number().int() }),
187
+ )
188
+ .describe("The section's beats in order: what a data-step names, by id or by position."),
189
+ html: z.string().nullable(),
190
+ css: z.string().nullable(),
191
+ ts: z.string().nullable(),
192
+ theme: z.string().nullable().describe("The rules of the deck's theme.css this slide uses."),
193
+ assets: paths,
194
+ ref: RefInfo.optional(),
195
+ }),
196
+ theme: z.strictObject({
197
+ path: z.string(),
198
+ classes: z.array(z.string()),
199
+ tokens: z.array(z.strictObject({ name: z.string(), value: z.string() })),
200
+ layouts: z.array(z.strictObject({ name: z.string(), example: z.string().optional() })),
201
+ layout: z.strictObject({ name: z.string(), example: z.string() }).optional(),
202
+ ref: RefInfo.optional(),
203
+ }),
204
+ check: z.strictObject({
205
+ slug: z.string(),
206
+ diagnostics,
207
+ skipped,
208
+ shot: z.string().optional(),
209
+ fill: z
210
+ .strictObject({
211
+ step: z.string().describe("The beat measured: the last, as the slide's shot shows it."),
212
+ coverage: z.number().describe("The share of the frame the content covers."),
213
+ box: z
214
+ .strictObject({
215
+ left: z.number(),
216
+ top: z.number(),
217
+ right: z.number(),
218
+ bottom: z.number(),
219
+ })
220
+ .optional()
221
+ .describe(
222
+ "The smallest box holding it, as shares of the frame. Absent when there is none.",
223
+ ),
224
+ rows: z.array(z.number()).describe("The share of each tenth of the frame, top to bottom."),
225
+ columns: z.array(z.number()).describe("The same, left to right."),
226
+ })
227
+ .optional()
228
+ .describe(
229
+ "How much of the frame the slide fills: each text's lines, each picture's box, and each painted box that holds nothing, as a chart's bar; a card counts by what it holds; decoration under aria-hidden counts too, ::before and ::after text does not. Judge a slide by rows, columns, and box, not coverage alone. Shares from 0 to 1. Absent when the slide was not measured.",
230
+ ),
231
+ voice: z
232
+ .strictObject({
233
+ beats: z.array(
234
+ z.strictObject({
235
+ beatIndex: z.number().int(),
236
+ durationMs: z.number(),
237
+ empty: z.boolean(),
238
+ sentences: z.array(
239
+ z.strictObject({
240
+ text: z.string(),
241
+ kana: z.string(),
242
+ start: z.number(),
243
+ end: z.number(),
244
+ }),
245
+ ),
246
+ }),
247
+ ),
248
+ })
249
+ .optional(),
250
+ }),
251
+ shot: z.strictObject({
252
+ shots: z.array(shotFile),
253
+ sheets: paths.optional().describe("Contact sheets, from --sheet or --motion."),
254
+ motion: z
255
+ .array(
256
+ z.strictObject({
257
+ step: z.string(),
258
+ frames: z.array(
259
+ z.strictObject({ ms: z.number(), path: z.string(), end: z.literal(true).optional() }),
260
+ ),
261
+ }),
262
+ )
263
+ .optional(),
264
+ }),
265
+ mv: z.strictObject({
266
+ from: z.string(),
267
+ to: z.string().optional(),
268
+ before: z.string().optional(),
269
+ after: z.string().optional(),
270
+ }),
271
+ goto: nav,
272
+ current: nav,
273
+ marks: z.union([
274
+ z.strictObject({
275
+ action: z.literal("list"),
276
+ marks: z.array(
277
+ z.strictObject({
278
+ slug: z.string(),
279
+ beat: z
280
+ .string()
281
+ .nullable()
282
+ .describe("The beat's title; null for what the slide says before its first ###."),
283
+ beatIndex: z.number().int().nullable().describe("Null once the beat is gone."),
284
+ line: z.number().int().nullable().describe("The heading's line in script.md."),
285
+ path: z.string(),
286
+ status: z
287
+ .enum(["open", "edited", "gone"])
288
+ .describe("edited once the words differ from what was marked; gone with the beat."),
289
+ was: z.string().describe("The beat's words when it was marked."),
290
+ text: z.string().nullable().describe("The beat's words now."),
291
+ markedAt: z.string(),
292
+ }),
293
+ ),
294
+ }),
295
+ z.strictObject({ action: z.literal("clear"), cleared: z.number().int() }),
296
+ ]),
297
+ sync: z.strictObject({ created: paths, updated: paths, removed: paths, kept: paths }),
298
+ lint: z.strictObject({ diagnostics, skipped }),
299
+ cues: z.strictObject({
300
+ name: z.string(),
301
+ cues: z.array(
302
+ z.strictObject({
303
+ position: Position,
304
+ slug: z.string(),
305
+ line: z.number().int(),
306
+ paragraphs: z.array(z.string()),
307
+ }),
308
+ ),
309
+ diagnostics,
310
+ }),
311
+ voice: z.union([
312
+ z.strictObject({
313
+ action: z.literal("synth"),
314
+ synthesized: z.number().int(),
315
+ cached: z.number().int(),
316
+ timelinePath: z.string(),
317
+ audioPath: z.string(),
318
+ }),
319
+ z.strictObject({
320
+ action: z.literal("speakers"),
321
+ speakers: z.array(z.strictObject({ name: z.string(), styles: z.array(z.string()) })),
322
+ }),
323
+ z.strictObject({ action: z.literal("say"), text: z.string(), path: z.string() }),
324
+ z.strictObject({
325
+ action: z.literal("dict"),
326
+ path: z.string(),
327
+ key: z.string(),
328
+ kana: z.string(),
329
+ }),
330
+ z.strictObject({ action: z.literal("pin"), timelinePath: z.string(), audioPath: z.string() }),
331
+ ]),
332
+ build: z.strictObject({
333
+ outs: paths.describe("One file per deck built."),
334
+ images: paths.describe("dist/<deck>.png for each deck that got a link preview image."),
335
+ diagnostics,
336
+ skipped,
337
+ }),
338
+ video: z.strictObject({
339
+ out: z.string(),
340
+ vtt: z.string().optional(),
341
+ chapters: z.string().optional(),
342
+ credits: z.string().optional(),
343
+ }),
344
+ pdf: z.strictObject({ outs: paths }),
345
+ } satisfies Record<ResultCommand, FieldsSchema>;
346
+
347
+ type FieldsSchema = z.ZodObject | z.ZodUnion<readonly z.ZodObject[]>;
348
+
349
+ /** What a command prints beside `ok`, by command. */
350
+ export type ResultFields = { [K in ResultCommand]: z.input<(typeof RESULT_FIELDS)[K]> };
351
+
352
+ /** The commands that can do their job and still fail: on an error among their findings. */
353
+ export const FAILS_ON_FINDINGS = ["lint", "check"] as const satisfies readonly ResultCommand[];
354
+
355
+ /** What any command line prints when it cannot run. */
356
+ const Thrown = z.strictObject({ ok: z.literal(false), error: Failure });
357
+
358
+ const withOk = (fields: FieldsSchema, extra: z.ZodRawShape): z.ZodObject[] =>
359
+ (fields instanceof z.ZodUnion ? fields.options : [fields]).map((object) => object.extend(extra));
360
+
361
+ function resultEnvelope(command: ResultCommand): z.ZodType {
362
+ const fields: FieldsSchema = RESULT_FIELDS[command];
363
+ const fails = (FAILS_ON_FINDINGS as readonly string[]).includes(command);
364
+ return z.union([
365
+ ...withOk(fields, { ok: z.literal(true) }),
366
+ ...(fails ? withOk(fields, { ok: z.literal(false), error: Failure }) : []),
367
+ Thrown,
368
+ ]);
369
+ }
370
+
371
+ const HELP = {
372
+ help: z.union([z.strictObject({ ok: z.literal(true), help: z.string() }), Thrown]),
373
+ version: z.union([z.strictObject({ ok: z.literal(true), version: z.string() }), Thrown]),
374
+ };
375
+
376
+ /** Every line `--json` can print, by what printed it; `failure` is a line that could not run. */
377
+ function envelopes(): Record<string, z.ZodType> {
378
+ return {
379
+ ...Object.fromEntries(
380
+ (Object.keys(RESULT_FIELDS) as ResultCommand[]).map((command) => [
381
+ command,
382
+ resultEnvelope(command),
383
+ ]),
384
+ ),
385
+ ...HELP,
386
+ failure: Thrown,
387
+ };
388
+ }
389
+
390
+ /**
391
+ * The schema of what `dekc <argv>` prints with `--json`, read from the line as `main` reads it: a
392
+ * result command's envelope, help's or the version's, and for anything else, such as the dev
393
+ * server, which takes no `--json`, only the failure that says so.
394
+ */
395
+ export function printedBy(argv: string[]): z.ZodType {
396
+ let line: CommandLine;
397
+ try {
398
+ line = parseCommandLine(argv);
399
+ } catch {
400
+ return Thrown;
401
+ }
402
+ if (line.values.version === true) {
403
+ return HELP.version;
404
+ }
405
+ if (line.command === "help" || line.values.help === true) {
406
+ return HELP.help;
407
+ }
408
+ const command = line.command;
409
+ return command !== undefined && Object.hasOwn(RESULT_FIELDS, command)
410
+ ? resultEnvelope(command as ResultCommand)
411
+ : Thrown;
412
+ }
413
+
414
+ /** Where a printed value breaks `schema`, one line per issue; empty when it keeps it. */
415
+ export function contractIssues(schema: z.ZodType, value: unknown): string[] {
416
+ const result = schema.safeParse(value);
417
+ return result.success
418
+ ? []
419
+ : result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`);
420
+ }
421
+
422
+ /** Where the generated JSON Schema is kept, and the address the docs site serves it at. */
423
+ export const CLI_SCHEMA_PATH = "docs/public/cli.schema.json";
424
+ export const CLI_SCHEMA_URL = "https://hajimism.github.io/dek/cli.schema.json";
425
+
426
+ /**
427
+ * The contract as a JSON Schema 2020-12 document: each command's envelope under `$defs`, by name,
428
+ * next to the shapes they share.
429
+ */
430
+ export function cliJsonSchema(): object {
431
+ const all = envelopes();
432
+ const generated = z.toJSONSchema(z.strictObject(all)) as {
433
+ properties: Record<string, unknown>;
434
+ $defs?: Record<string, unknown>;
435
+ };
436
+ return {
437
+ $schema: "https://json-schema.org/draft/2020-12/schema",
438
+ $id: CLI_SCHEMA_URL,
439
+ title: "dekc --json",
440
+ description:
441
+ "What each dek command prints with --json, one JSON object per line. $defs holds one schema per command, and `failure` for a command line that could not run. While dek is 0.x the shape may change between releases.",
442
+ $defs: { ...generated.properties, ...generated.$defs },
443
+ };
444
+ }
@@ -0,0 +1,18 @@
1
+ import { cuesFromDeck } from "../core/cue.ts";
2
+ import type { Diagnostic } from "../core/diagnostic.ts";
3
+ import { silentCueDiagnostics } from "../core/lint.ts";
4
+ import type { DeckTarget } from "./scope.ts";
5
+
6
+ export type CuesResult = {
7
+ name: string;
8
+ cues: ReturnType<typeof cuesFromDeck>;
9
+ diagnostics: Diagnostic[];
10
+ };
11
+
12
+ export function cuesCommand({ deck }: DeckTarget): CuesResult {
13
+ return {
14
+ name: deck.name,
15
+ cues: cuesFromDeck(deck.deck),
16
+ diagnostics: silentCueDiagnostics(deck),
17
+ };
18
+ }