@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/src/cli/main.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { DekError } from "../core/error.ts";
|
|
2
|
+
import { either } from "../core/prose.ts";
|
|
3
|
+
import { isRefName } from "../core/ref-name.ts";
|
|
4
|
+
import { resolveProject } from "../core/resolve.ts";
|
|
5
|
+
import { addressHint } from "./address.ts";
|
|
6
|
+
import {
|
|
7
|
+
type AnySpec,
|
|
8
|
+
type CliResult,
|
|
9
|
+
COMMANDS,
|
|
10
|
+
type CommandName,
|
|
11
|
+
formOf,
|
|
12
|
+
isTypedCommand,
|
|
13
|
+
subcommandAt,
|
|
14
|
+
subcommandsOf,
|
|
15
|
+
} from "./commands.ts";
|
|
16
|
+
import { shellQuote } from "./files.ts";
|
|
17
|
+
import { type CommandLine, parseCommandLine, wantsJson } from "./flags.ts";
|
|
18
|
+
import { writeFailure, writeSuccess } from "./result.ts";
|
|
19
|
+
import { DeckRequiredError, namesDeck, peelDeckArg, resolveTarget } from "./scope.ts";
|
|
20
|
+
import {
|
|
21
|
+
agentHelpText,
|
|
22
|
+
commandHelp,
|
|
23
|
+
helpRequest,
|
|
24
|
+
helpText,
|
|
25
|
+
unknownCommandError,
|
|
26
|
+
unknownSubcommandError,
|
|
27
|
+
usageError,
|
|
28
|
+
versionText,
|
|
29
|
+
} from "./usage.ts";
|
|
30
|
+
|
|
31
|
+
/** The decks a mistyped word may have meant; none outside a project. */
|
|
32
|
+
function knownDeckNames(cwd: string): string[] {
|
|
33
|
+
try {
|
|
34
|
+
return resolveProject(cwd).decks.map((deck) => deck.name);
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Help or the version, when argv asks for either; whether it did. */
|
|
41
|
+
function writeHelp(line: CommandLine): boolean {
|
|
42
|
+
const request = helpRequest(line);
|
|
43
|
+
if (!request) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const text =
|
|
47
|
+
request.kind === "version"
|
|
48
|
+
? versionText()
|
|
49
|
+
: request.topic
|
|
50
|
+
? commandHelp(request.topic)
|
|
51
|
+
: request.agent
|
|
52
|
+
? agentHelpText()
|
|
53
|
+
: helpText();
|
|
54
|
+
if (line.values.json === true) {
|
|
55
|
+
const key = request.kind === "version" ? "version" : "help";
|
|
56
|
+
process.stdout.write(`${JSON.stringify({ ok: true, [key]: text })}\n`);
|
|
57
|
+
} else {
|
|
58
|
+
process.stdout.write(`${text}\n`);
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A command line matched to its spec: the form it runs, the deck it names, its arguments. */
|
|
64
|
+
export type Call = {
|
|
65
|
+
name: CommandName;
|
|
66
|
+
subcommand?: string;
|
|
67
|
+
deck?: string;
|
|
68
|
+
args: Record<string, string | undefined>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Each word of a form's `args`, by name; a missing or extra word is a usage error. */
|
|
72
|
+
function bindArgs(
|
|
73
|
+
words: string[],
|
|
74
|
+
{ name, subcommand }: { name: CommandName; subcommand?: string },
|
|
75
|
+
): Record<string, string | undefined> {
|
|
76
|
+
const label = ["dekc", name === "serve" ? undefined : name, subcommand].filter(Boolean).join(" ");
|
|
77
|
+
const args: Record<string, string | undefined> = {};
|
|
78
|
+
let next = 0;
|
|
79
|
+
for (const arg of formOf(name, subcommand).args) {
|
|
80
|
+
const rest = arg.endsWith("...");
|
|
81
|
+
const key = arg.replace(/\?$|\.\.\.$/, "");
|
|
82
|
+
const value = (rest ? words.slice(next).join(" ") : words[next])?.trim() || undefined;
|
|
83
|
+
next = rest ? words.length : next + 1;
|
|
84
|
+
if (value === undefined && !arg.endsWith("?")) {
|
|
85
|
+
throw usageError(name, `missing <${key}> for ${label}`, { subcommand });
|
|
86
|
+
}
|
|
87
|
+
args[key] = value;
|
|
88
|
+
}
|
|
89
|
+
const extra = words[next];
|
|
90
|
+
if (extra !== undefined) {
|
|
91
|
+
// A command whose own form takes no words takes a subcommand there.
|
|
92
|
+
const subs = Object.keys(subcommandsOf(COMMANDS[name] as AnySpec));
|
|
93
|
+
if (subcommand === undefined && next === 0 && subs.length > 0) {
|
|
94
|
+
throw unknownSubcommandError(name, extra);
|
|
95
|
+
}
|
|
96
|
+
throw usageError(name, `unexpected argument "${extra}" for ${label}`, { subcommand });
|
|
97
|
+
}
|
|
98
|
+
return args;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The command a line runs, the deck it names, and its arguments. A first word that is no command
|
|
103
|
+
* is a deck for the bare `dekc [deck]`, or a mistake. A deck comes before a subcommand.
|
|
104
|
+
*/
|
|
105
|
+
export function bindCommandLine(cwd: string, line: CommandLine): Call {
|
|
106
|
+
const { command, values, positionals } = line;
|
|
107
|
+
if (command === undefined || !isTypedCommand(command)) {
|
|
108
|
+
if (command !== undefined && !namesDeck(cwd, command)) {
|
|
109
|
+
throw unknownCommandError(command, knownDeckNames(cwd));
|
|
110
|
+
}
|
|
111
|
+
return { name: "serve", args: bindArgs(positionals, { name: "serve" }) };
|
|
112
|
+
}
|
|
113
|
+
const spec = COMMANDS[command] as AnySpec;
|
|
114
|
+
const sub = subcommandAt(command, positionals);
|
|
115
|
+
let deck = values.deck;
|
|
116
|
+
let words: string[];
|
|
117
|
+
if (sub) {
|
|
118
|
+
const before = positionals.slice(1, sub.at);
|
|
119
|
+
words = positionals.slice(sub.at + sub.name.split(" ").length);
|
|
120
|
+
if (before[0] !== undefined) {
|
|
121
|
+
if (deck !== undefined) {
|
|
122
|
+
throw usageError(command, `unexpected argument "${before[0]}" for dekc ${command}`, {
|
|
123
|
+
subcommand: sub.name,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
deck = before[0];
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
words = positionals.slice(1);
|
|
130
|
+
if (spec.scope && deck === undefined) {
|
|
131
|
+
const args = formOf(command).args;
|
|
132
|
+
const peeled = peelDeckArg(cwd, words, {
|
|
133
|
+
max: args.some((arg) => arg.endsWith("...")) ? Number.POSITIVE_INFINITY : args.length,
|
|
134
|
+
subcommands: Object.keys(subcommandsOf(spec)).length > 0,
|
|
135
|
+
});
|
|
136
|
+
deck = peeled.deck;
|
|
137
|
+
words = peeled.rest;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
name: command,
|
|
142
|
+
...(sub ? { subcommand: sub.name } : {}),
|
|
143
|
+
...(deck !== undefined ? { deck } : {}),
|
|
144
|
+
args: bindArgs(words, { name: command, subcommand: sub?.name }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Resolve what the command's spec says it works on, then run it and print its result. */
|
|
149
|
+
async function run(cwd: string, line: CommandLine): Promise<void> {
|
|
150
|
+
const call = bindCommandLine(cwd, line);
|
|
151
|
+
const spec = COMMANDS[call.name] as AnySpec;
|
|
152
|
+
if (spec.kind === "session" && line.values.json === true) {
|
|
153
|
+
throw sessionJsonError(spec.bare ? "dekc" : `dekc ${call.name}`);
|
|
154
|
+
}
|
|
155
|
+
if (spec.refs && call.deck !== undefined && isRefName(call.deck)) {
|
|
156
|
+
const { restoreRef } = await import("./ref.ts");
|
|
157
|
+
await restoreRef(cwd, call.deck);
|
|
158
|
+
}
|
|
159
|
+
const scope = spec.scope;
|
|
160
|
+
const target = scope
|
|
161
|
+
? retypedWithDeck(line, cwd, () =>
|
|
162
|
+
resolveTarget(cwd, scope, {
|
|
163
|
+
deck: call.deck,
|
|
164
|
+
refs: spec.refs === true,
|
|
165
|
+
unreadable: spec.unreadable === true,
|
|
166
|
+
}),
|
|
167
|
+
)
|
|
168
|
+
: undefined;
|
|
169
|
+
const form = formOf(call.name, call.subcommand);
|
|
170
|
+
const ctx = { cwd, flags: line.values, args: call.args, target };
|
|
171
|
+
const data = await form.run?.(ctx);
|
|
172
|
+
if (spec.kind === "result") {
|
|
173
|
+
writeSuccess({ command: call.name, data } as CliResult, {
|
|
174
|
+
json: line.values.json === true,
|
|
175
|
+
format: line.values.format,
|
|
176
|
+
cwd,
|
|
177
|
+
...(target?.deck && {
|
|
178
|
+
deck: { name: target.ref?.name ?? target.deck.name, dir: target.deck.dir },
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Most decks a hint names a command for; past that, one and where the rest are listed. */
|
|
185
|
+
const NAMED_DECKS = 3;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* `resolve`, with a missing deck hinted as the line that was typed, the deck named where the
|
|
189
|
+
* command takes it: one command per deck of the project, so the hint runs as written.
|
|
190
|
+
*/
|
|
191
|
+
function retypedWithDeck<T>(line: CommandLine, cwd: string, resolve: () => T): T {
|
|
192
|
+
try {
|
|
193
|
+
return resolve();
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (!(error instanceof DeckRequiredError)) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
const typed = `\`dekc ${line.typed.map(shellQuote).join(" ")}\``;
|
|
199
|
+
const [first] = error.decks;
|
|
200
|
+
const hint = !first
|
|
201
|
+
? "run `dekc new <name>` to make a deck"
|
|
202
|
+
: error.decks.length > NAMED_DECKS
|
|
203
|
+
? `run ${addressHint(typed, first, cwd)}, or name another deck in place of ${first.name}; \`dekc ls\` lists them`
|
|
204
|
+
: `run ${either(error.decks.map((deck) => addressHint(typed, deck, cwd)))}`;
|
|
205
|
+
throw new DekError(error.message, { hint, cause: error });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** A session prints no result, so --json has nothing to shape, and a reader would wait forever. */
|
|
210
|
+
function sessionJsonError(name: string): DekError {
|
|
211
|
+
return new DekError(`${name} keeps running until stopped and prints no JSON`, {
|
|
212
|
+
hint: "start it without --json and leave it running; for a result, run `dekc lint --json` or `dekc check <slug> --json`",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* parse → help → bind the words to the spec → resolve its decks → run → print. Every failure
|
|
218
|
+
* prints once, here: as JSON when the line asks for it, even a line too broken to parse.
|
|
219
|
+
*/
|
|
220
|
+
export async function main(argv: string[], cwd: string): Promise<void> {
|
|
221
|
+
let json = wantsJson(argv);
|
|
222
|
+
try {
|
|
223
|
+
const line = parseCommandLine(argv);
|
|
224
|
+
json = line.values.json === true;
|
|
225
|
+
if (!writeHelp(line)) {
|
|
226
|
+
await run(cwd, line);
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
writeFailure(error, { json, cwd });
|
|
230
|
+
}
|
|
231
|
+
}
|
package/src/cli/marks.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { clearMarks, listMarks, type MarkRow } from "../core/marks.ts";
|
|
2
|
+
import type { DeckTarget } from "./scope.ts";
|
|
3
|
+
|
|
4
|
+
export type MarksCliResult =
|
|
5
|
+
| { action: "list"; marks: MarkRow[] }
|
|
6
|
+
| { action: "clear"; cleared: number };
|
|
7
|
+
|
|
8
|
+
/** `dekc marks`: the beats the speaker marked in the presenter view, where the script has them now. */
|
|
9
|
+
export async function marksCommand({ project, deck }: DeckTarget): Promise<MarksCliResult> {
|
|
10
|
+
return { action: "list", marks: listMarks(project.root, deck) };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** `dekc marks clear`: drop the deck's marks, once they are dealt with. */
|
|
14
|
+
export async function clearMarksCommand({ project, deck }: DeckTarget): Promise<MarksCliResult> {
|
|
15
|
+
return { action: "clear", cleared: clearMarks(project.root, deck) };
|
|
16
|
+
}
|
package/src/cli/mv.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { renameSection, reorderSection } from "../core/mv.ts";
|
|
2
|
+
import type { DeckTarget } from "./scope.ts";
|
|
3
|
+
import { usageError } from "./usage.ts";
|
|
4
|
+
|
|
5
|
+
export type MvResult = {
|
|
6
|
+
from: string;
|
|
7
|
+
to?: string;
|
|
8
|
+
before?: string;
|
|
9
|
+
after?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** A rename to `to`, or a move before or after another section: exactly one of them. */
|
|
13
|
+
export function mvCommand(
|
|
14
|
+
{ project, deck }: DeckTarget,
|
|
15
|
+
options: { slug: string; to?: string; before?: string; after?: string },
|
|
16
|
+
): MvResult {
|
|
17
|
+
const { slug, to, before, after } = options;
|
|
18
|
+
if (before && after) {
|
|
19
|
+
throw usageError("mv", "use only one of --before or --after", { match: "--before" });
|
|
20
|
+
}
|
|
21
|
+
const place = before ? { before } : after ? { after } : undefined;
|
|
22
|
+
if (place) {
|
|
23
|
+
if (to !== undefined) {
|
|
24
|
+
throw usageError("mv", `unexpected argument "${to}" for dekc mv --before|--after`, {
|
|
25
|
+
match: "--before",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
reorderSection({ project, deck }, slug, place);
|
|
29
|
+
return { from: slug, ...place };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (to === undefined) {
|
|
33
|
+
throw usageError("mv", "missing <new> for dekc mv", { match: "<new>" });
|
|
34
|
+
}
|
|
35
|
+
renameSection({ project, deck }, slug, to);
|
|
36
|
+
return { from: slug, to };
|
|
37
|
+
}
|
package/src/cli/new.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { loadConfig } from "../core/config.ts";
|
|
4
|
+
import { deckPaths } from "../core/deck-paths.ts";
|
|
5
|
+
import { DekError } from "../core/error.ts";
|
|
6
|
+
import { DECK_NAME_HINT, isDeckName } from "../core/path.ts";
|
|
7
|
+
import { syncDeck } from "../core/sync.ts";
|
|
8
|
+
import { applyPlan, deckPlan, nextSteps } from "./files.ts";
|
|
9
|
+
import { requireDeckFromCwd, requireProject } from "./scope.ts";
|
|
10
|
+
|
|
11
|
+
export type NewResult = {
|
|
12
|
+
name: string;
|
|
13
|
+
dir: string;
|
|
14
|
+
created: string[];
|
|
15
|
+
/** dek's own files at the project root that were there and are now brought up to date. */
|
|
16
|
+
updated: string[];
|
|
17
|
+
/** The commands to run next, from the directory new ran in. */
|
|
18
|
+
next: string[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function newCommand(options: { cwd: string; name: string; themeFrom?: string }): NewResult {
|
|
22
|
+
const { name } = options;
|
|
23
|
+
if (!isDeckName(name)) {
|
|
24
|
+
throw new DekError(`invalid deck name "${name}"`, {
|
|
25
|
+
hint: DECK_NAME_HINT,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const project = requireProject(options.cwd);
|
|
30
|
+
const dir = join(project.root, "decks", name);
|
|
31
|
+
if (existsSync(dir)) {
|
|
32
|
+
throw new DekError(`deck "${name}" already exists`, {
|
|
33
|
+
path: dir,
|
|
34
|
+
hint: "run `dekc ls`",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const themeSource = options.themeFrom
|
|
39
|
+
? deckPaths(requireDeckFromCwd(options.cwd, options.themeFrom).deck.dir).theme
|
|
40
|
+
: join(project.root, "theme.css");
|
|
41
|
+
if (!existsSync(themeSource)) {
|
|
42
|
+
throw new DekError("theme.css not found", {
|
|
43
|
+
path: themeSource,
|
|
44
|
+
hint: options.themeFrom
|
|
45
|
+
? "run `dekc ls` and pick a deck that has theme.css"
|
|
46
|
+
: "add theme.css at the project root",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const theme = readFileSync(themeSource, "utf8");
|
|
51
|
+
const voice = loadConfig(project.configPath).voice;
|
|
52
|
+
const { created } = applyPlan(deckPlan(project.root, name, theme, voice));
|
|
53
|
+
// As sync would. AGENTS.md and .dek/ follow the project theme and dek, so they change here
|
|
54
|
+
// only when either moved on since the last command that wrote them: say which.
|
|
55
|
+
const synced = syncDeck(dir);
|
|
56
|
+
created.push(...synced.created, ...synced.dekFiles.created);
|
|
57
|
+
return {
|
|
58
|
+
name,
|
|
59
|
+
dir,
|
|
60
|
+
created,
|
|
61
|
+
updated: synced.dekFiles.updated,
|
|
62
|
+
next: nextSteps(options.cwd, project.root, dir),
|
|
63
|
+
};
|
|
64
|
+
}
|
package/src/cli/pdf.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { pdfDeck } from "../core/pdf.ts";
|
|
2
|
+
import type { DecksTarget } from "./scope.ts";
|
|
3
|
+
|
|
4
|
+
/** One file per deck, a single deck included, so the shape never depends on the scope. */
|
|
5
|
+
export type PdfCliResult = { outs: string[] };
|
|
6
|
+
|
|
7
|
+
export async function pdfCommand(
|
|
8
|
+
{ project, decks }: DecksTarget,
|
|
9
|
+
options: { rootDist?: boolean } = {},
|
|
10
|
+
): Promise<PdfCliResult> {
|
|
11
|
+
const outs: string[] = [];
|
|
12
|
+
for (const entry of decks) {
|
|
13
|
+
outs.push((await pdfDeck({ project, deck: entry }, { rootDist: options.rootDist })).outPath);
|
|
14
|
+
}
|
|
15
|
+
return { outs };
|
|
16
|
+
}
|