takt-marp 0.1.0 → 0.2.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/package.json
CHANGED
|
@@ -6,11 +6,16 @@ import path from "node:path";
|
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { initializeProject } from "./takt-marp-project-init.mjs";
|
|
8
8
|
import { packageScriptPath } from "./takt-marp-runtime-context.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
APPROVAL_COMMANDS,
|
|
11
|
+
formatError,
|
|
12
|
+
SlideWorkflowError,
|
|
13
|
+
} from "./takt-marp-slide-workflow.mjs";
|
|
10
14
|
|
|
11
15
|
const WORKFLOW_COMMANDS = ["plan", "compose", "polish", "deliver"];
|
|
12
|
-
const VALID_COMMANDS = ["init", ...WORKFLOW_COMMANDS, "smoke"];
|
|
16
|
+
const VALID_COMMANDS = ["init", ...WORKFLOW_COMMANDS, "approve", "smoke"];
|
|
13
17
|
const RUNNER_SCRIPT = "scripts/takt-marp-run-slide-workflow.mjs";
|
|
18
|
+
const APPROVE_SCRIPT = "scripts/takt-marp-approve-slide-workflow-state.mjs";
|
|
14
19
|
const SMOKE_SCRIPT = "scripts/takt-marp-validate-slide-workflow-smoke.mjs";
|
|
15
20
|
const REQUIRED_PROJECT_DIRS = [".takt/workflows", ".takt/facets"];
|
|
16
21
|
|
|
@@ -24,6 +29,8 @@ function usage() {
|
|
|
24
29
|
" compose <slides/deck> [options] Run the compose workflow for a deck in the current project",
|
|
25
30
|
" polish <slides/deck> [options] Run the polish workflow for a deck in the current project",
|
|
26
31
|
" deliver <slides/deck> [options] Run the deliver workflow for a deck in the current project",
|
|
32
|
+
" approve <slides/deck> <command> --by <name> [--force]",
|
|
33
|
+
" Approve a workflow state (command: plan or compose)",
|
|
27
34
|
" smoke [--provider <name>] Run smoke validation in a temporary project (default provider: mock)",
|
|
28
35
|
"",
|
|
29
36
|
"Workflow options (passed through to the workflow runner unchanged):",
|
|
@@ -171,6 +178,62 @@ async function runSmoke(args) {
|
|
|
171
178
|
return exitCode;
|
|
172
179
|
}
|
|
173
180
|
|
|
181
|
+
function approveUsage() {
|
|
182
|
+
return [
|
|
183
|
+
"Usage: takt-marp approve <slides/deck> <command> --by <name> [--force]",
|
|
184
|
+
"",
|
|
185
|
+
`Commands: ${APPROVAL_COMMANDS.join(", ")}`,
|
|
186
|
+
"",
|
|
187
|
+
"Options:",
|
|
188
|
+
" --by <name> Approver identifier (required)",
|
|
189
|
+
" --force Overwrite an existing approval",
|
|
190
|
+
" --help, -h Show this message",
|
|
191
|
+
].join("\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function runApprove(args) {
|
|
195
|
+
let parsed;
|
|
196
|
+
try {
|
|
197
|
+
parsed = parseArgs({
|
|
198
|
+
args,
|
|
199
|
+
allowPositionals: true,
|
|
200
|
+
options: {
|
|
201
|
+
by: { type: "string" },
|
|
202
|
+
force: { type: "boolean", default: false },
|
|
203
|
+
help: { type: "boolean", short: "h", default: false },
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
throw new SlideWorkflowError(
|
|
208
|
+
`Invalid approve arguments: ${error.message}. Run 'takt-marp approve --help' for usage.`,
|
|
209
|
+
"INVALID_ARGS",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
if (parsed.values.help) {
|
|
213
|
+
console.log(approveUsage());
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
assertProjectInitialized();
|
|
217
|
+
if (parsed.positionals.length !== 2) {
|
|
218
|
+
throw new SlideWorkflowError(
|
|
219
|
+
`Expected <slides/deck> and <command>. Run 'takt-marp approve --help' for usage.`,
|
|
220
|
+
"INVALID_ARGS",
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const [target, command] = parsed.positionals;
|
|
224
|
+
if (!parsed.values.by) {
|
|
225
|
+
throw new SlideWorkflowError(
|
|
226
|
+
`Missing required --by <name>. Run 'takt-marp approve --help' for usage.`,
|
|
227
|
+
"INVALID_ARGS",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const approveArgs = [target, command, "--by", parsed.values.by];
|
|
231
|
+
if (parsed.values.force) {
|
|
232
|
+
approveArgs.push("--force");
|
|
233
|
+
}
|
|
234
|
+
return runPackageScript(APPROVE_SCRIPT, approveArgs);
|
|
235
|
+
}
|
|
236
|
+
|
|
174
237
|
export async function runCli(argv) {
|
|
175
238
|
const [command, ...rest] = argv;
|
|
176
239
|
if (command === undefined || command === "--help" || command === "-h" || command === "help") {
|
|
@@ -188,6 +251,9 @@ export async function runCli(argv) {
|
|
|
188
251
|
if (command === "init") {
|
|
189
252
|
return await runInit(rest);
|
|
190
253
|
}
|
|
254
|
+
if (command === "approve") {
|
|
255
|
+
return await runApprove(rest);
|
|
256
|
+
}
|
|
191
257
|
if (command === "smoke") {
|
|
192
258
|
return await runSmoke(rest);
|
|
193
259
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
supervisionPath,
|
|
21
21
|
writeApproval,
|
|
22
22
|
} from "./lib/takt-marp-slide-workflow.mjs";
|
|
23
|
+
import { runCli } from "./lib/takt-marp-cli.mjs";
|
|
23
24
|
|
|
24
25
|
const checks = [];
|
|
25
26
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -306,6 +307,47 @@ async function main() {
|
|
|
306
307
|
assert(verifyResult.stdout.includes("skipped for non-successful verify report"), `verify verifier did not report skip: ${verifyResult.stdout}`);
|
|
307
308
|
});
|
|
308
309
|
|
|
310
|
+
await check("approve command shows help without initialized project", async () => {
|
|
311
|
+
const root = await fixtureRoot();
|
|
312
|
+
const originalCwd = process.cwd();
|
|
313
|
+
let output;
|
|
314
|
+
try {
|
|
315
|
+
process.chdir(root);
|
|
316
|
+
output = await captureStdout(() => runCli(["approve", "--help"]));
|
|
317
|
+
} finally {
|
|
318
|
+
process.chdir(originalCwd);
|
|
319
|
+
}
|
|
320
|
+
assert(output.includes("Usage: takt-marp approve"), `approve help missing usage: ${output}`);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await check("approve command requires initialized project", async () => {
|
|
324
|
+
const root = await fixtureRoot();
|
|
325
|
+
const originalCwd = process.cwd();
|
|
326
|
+
try {
|
|
327
|
+
process.chdir(root);
|
|
328
|
+
const code = await runCli(["approve", "slides/demo", "plan", "--by", "foundation-test"]);
|
|
329
|
+
assert(code !== 0, "approve unexpectedly succeeded in uninitialized project");
|
|
330
|
+
} finally {
|
|
331
|
+
process.chdir(originalCwd);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await check("approve command writes approval file", async () => {
|
|
336
|
+
const root = await initializedFixtureRoot();
|
|
337
|
+
const targetInfo = await makeDeck(root, "demo");
|
|
338
|
+
await writeSupervision(targetInfo, "plan", "planned", "passed", "run-plan-1");
|
|
339
|
+
const originalCwd = process.cwd();
|
|
340
|
+
try {
|
|
341
|
+
process.chdir(root);
|
|
342
|
+
const code = await runCli(["approve", targetInfo.target, "plan", "--by", "foundation-test"]);
|
|
343
|
+
assert(code === 0, `approve command failed: check stderr`);
|
|
344
|
+
} finally {
|
|
345
|
+
process.chdir(originalCwd);
|
|
346
|
+
}
|
|
347
|
+
const approval = await readFile(path.join(targetInfo.reviewPath, "plan-approval.md"), "utf8");
|
|
348
|
+
assert(approval.includes("approved_by: foundation-test"), `approval file missing approver: ${approval}`);
|
|
349
|
+
});
|
|
350
|
+
|
|
309
351
|
await check("package scripts expose canonical entrypoints only", async () => {
|
|
310
352
|
const pkg = JSON.parse(await readFile(path.join(process.cwd(), "package.json"), "utf8"));
|
|
311
353
|
const scripts = pkg.scripts ?? {};
|
|
@@ -348,6 +390,32 @@ async function fixtureRoot() {
|
|
|
348
390
|
return root;
|
|
349
391
|
}
|
|
350
392
|
|
|
393
|
+
async function initializedFixtureRoot() {
|
|
394
|
+
const root = await fixtureRoot();
|
|
395
|
+
await mkdir(path.join(root, ".takt", "facets"), { recursive: true });
|
|
396
|
+
return root;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function captureStdout(fn) {
|
|
400
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
401
|
+
const chunks = [];
|
|
402
|
+
process.stdout.write = (chunk, encoding, callback) => {
|
|
403
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString());
|
|
404
|
+
if (typeof encoding === "function") {
|
|
405
|
+
encoding();
|
|
406
|
+
} else if (typeof callback === "function") {
|
|
407
|
+
callback();
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
};
|
|
411
|
+
try {
|
|
412
|
+
await fn();
|
|
413
|
+
} finally {
|
|
414
|
+
process.stdout.write = originalWrite;
|
|
415
|
+
}
|
|
416
|
+
return chunks.join("");
|
|
417
|
+
}
|
|
418
|
+
|
|
351
419
|
async function makeDeck(root, deckName) {
|
|
352
420
|
const deckPath = path.join(root, "slides", deckName);
|
|
353
421
|
await mkdir(path.join(deckPath, "review"), { recursive: true });
|