@executablemd/cli 0.3.1 → 0.4.1

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/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "@executablemd/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "exports": "./src/cli.ts",
5
5
  "imports": {
6
6
  "@types/node": "npm:@types/node@^24.5.2",
@@ -10,10 +10,13 @@ export default {
10
10
  "@effectionx/test-adapter": "npm:@effectionx/test-adapter@0.7.4",
11
11
  "effection": "npm:effection@4.1.0-alpha.7",
12
12
  "effection/experimental": "npm:effection@4.1.0-alpha.7/experimental",
13
- "@executablemd/durable-streams": "npm:@executablemd/durable-streams@^0.3.1",
14
- "@executablemd/runtime": "npm:@executablemd/runtime@^0.3.1",
13
+ "@executablemd/durable-streams": "npm:@executablemd/durable-streams@^0.4.1",
14
+ "@executablemd/runtime": "npm:@executablemd/runtime@^0.4.1",
15
+ "@executablemd/testing": "npm:@executablemd/testing@^0.4.1",
16
+ "@std/assert": "jsr:@std/assert@^1.0.17",
17
+ "ajv": "npm:ajv@^8.17.1",
15
18
  "@executablemd/runtime/test": "file:///home/runner/work/executable.md/executable.md/runtime/test/mod.ts",
16
- "@executablemd/code-review-agent": "npm:@executablemd/code-review-agent@^0.3.1",
19
+ "@executablemd/code-review-agent": "npm:@executablemd/code-review-agent@^0.4.1",
17
20
  "@effectionx/context-api": "npm:@effectionx/context-api@0.6.0",
18
21
  "@effectionx/converge": "npm:@effectionx/converge@0.1.4",
19
22
  "@effectionx/fetch": "npm:@effectionx/fetch@0.2.0",
@@ -32,10 +35,11 @@ export default {
32
35
  "oxlint-tsgolint": "npm:oxlint-tsgolint@^0.17",
33
36
  "unist-util-select": "npm:unist-util-select@^5",
34
37
  "mdast-util-to-string": "npm:mdast-util-to-string@^4",
35
- "@executablemd/core": "npm:@executablemd/core@^0.3.1",
36
- "@executablemd/core/": "npm:@executablemd/core@^0.3.1/",
37
- "@executablemd/durable-streams/": "npm:@executablemd/durable-streams@^0.3.1/",
38
- "@executablemd/runtime/": "npm:@executablemd/runtime@^0.3.1/",
39
- "@executablemd/code-review-agent/": "npm:@executablemd/code-review-agent@^0.3.1/"
38
+ "@executablemd/core": "npm:@executablemd/core@^0.4.1",
39
+ "@executablemd/core/": "npm:@executablemd/core@^0.4.1/",
40
+ "@executablemd/durable-streams/": "npm:@executablemd/durable-streams@^0.4.1/",
41
+ "@executablemd/runtime/": "npm:@executablemd/runtime@^0.4.1/",
42
+ "@executablemd/testing/": "npm:@executablemd/testing@^0.4.1/",
43
+ "@executablemd/code-review-agent/": "npm:@executablemd/code-review-agent@^0.4.1/"
40
44
  }
41
45
  };
package/esm/src/cli.js CHANGED
@@ -19,16 +19,11 @@ import { inspect } from "node:util";
19
19
  import process from "node:process";
20
20
  import { program, object, field, cli, commands } from "configliere";
21
21
  import { z } from "zod";
22
- import { runDocument, useNormalizedOutput, useTerminalOutput } from "@executablemd/core";
22
+ import { execute, useNormalizedOutput, useTerminalOutput } from "@executablemd/core";
23
+ import { installTestingVocabulary, TestFailureError, useTesting } from "@executablemd/testing";
23
24
  import { FileStream } from "./file-stream.js";
24
25
  import denoJson from "../deno.js";
25
- // ---------------------------------------------------------------------------
26
- // Workaround: field.default exists at runtime but is missing from the .d.ts
27
- // ---------------------------------------------------------------------------
28
26
  const defaults = (value) => (mods) => ({ ...mods, default: value });
29
- // ---------------------------------------------------------------------------
30
- // Program schema
31
- // ---------------------------------------------------------------------------
32
27
  const runConfig = object({
33
28
  docPath: {
34
29
  description: "markdown document to execute",
@@ -53,14 +48,35 @@ const runConfig = object({
53
48
  ...field(z.boolean(), defaults(false)),
54
49
  },
55
50
  });
51
+ const testConfig = object({
52
+ docPath: {
53
+ description: "markdown document to test",
54
+ ...field(z.string(), cli.argument()),
55
+ },
56
+ componentDir: {
57
+ description: "component search directory",
58
+ ...field(z.array(z.string()), defaults(["components", "."]), field.array()),
59
+ },
60
+ verbose: {
61
+ description: "log journal entries to stderr",
62
+ aliases: ["-V"],
63
+ ...field(z.boolean(), defaults(false)),
64
+ },
65
+ journal: {
66
+ description: "write a diagnostic JSONL trace (path must not exist)",
67
+ aliases: ["-j"],
68
+ ...field(z.string().optional()),
69
+ },
70
+ raw: {
71
+ description: "output raw markdown without normalization or terminal formatting",
72
+ ...field(z.boolean(), defaults(false)),
73
+ },
74
+ });
56
75
  const xmd = program({
57
76
  name: "xmd",
58
77
  version: denoJson.version,
59
- config: commands({ run: runConfig }, { default: "run" }),
78
+ config: commands({ run: runConfig, test: testConfig }, { default: "run" }),
60
79
  });
61
- // ---------------------------------------------------------------------------
62
- // Journal entry formatting
63
- // ---------------------------------------------------------------------------
64
80
  const pretty = (value) => inspect(value, {
65
81
  colors: true,
66
82
  compact: true,
@@ -70,8 +86,9 @@ const pretty = (value) => inspect(value, {
70
86
  });
71
87
  function formatYieldResult(event) {
72
88
  const { result, description } = event;
73
- if (result.status !== "ok" || result.value === undefined)
89
+ if (result.status !== "ok" || result.value === undefined) {
74
90
  return "";
91
+ }
75
92
  const v = result.value;
76
93
  switch (description.type) {
77
94
  case "import_component":
@@ -97,9 +114,6 @@ function summarizeEvent(event) {
97
114
  const detail = status === "err" && "error" in event.result ? ` (${event.result.error.message})` : "";
98
115
  return `[close] ${event.coroutineId} → ${status}${detail}`;
99
116
  }
100
- // ---------------------------------------------------------------------------
101
- // Document runner
102
- // ---------------------------------------------------------------------------
103
117
  function* createJournalFile(filePath) {
104
118
  let handle;
105
119
  try {
@@ -115,7 +129,7 @@ function* createJournalFile(filePath) {
115
129
  }
116
130
  yield* until(handle.close());
117
131
  }
118
- function* run(config) {
132
+ function* run(config, mode) {
119
133
  const { docPath, componentDir, verbose, journal, raw } = config;
120
134
  // Every CLI invocation starts from an empty stream. --journal writes
121
135
  // current-run diagnostics only; existing traces are never loaded.
@@ -147,22 +161,23 @@ function* run(config) {
147
161
  }
148
162
  })
149
163
  : spawn(function* () { });
150
- // ---------------------------------------------------------------------------
151
- // Output middleware (spec §9).
152
- //
153
- // Middleware is installed on the DocumentOutput Api via Api.around() before
154
- // runDocument is called. runDocument owns the channel internally —
155
- // the CLI just installs transformations and consumes the returned stream.
156
- // ---------------------------------------------------------------------------
157
164
  if (!raw) {
158
165
  yield* useNormalizedOutput();
159
166
  }
160
167
  if (process.stdout.isTTY && !raw) {
161
168
  yield* useTerminalOutput();
162
169
  }
163
- // Run the document returns a DocumentExecution.
164
- // yield* execution waits for completion. execution.output streams chunks.
165
- const execution = yield* runDocument({
170
+ // Compose testing around the single core execution entrypoint: both
171
+ // commands register the vocabulary (assertions work in regular documents,
172
+ // explicit <Testing> boundaries affect the outcome), while `xmd test`
173
+ // additionally activates root testing through a useTesting() session.
174
+ if (mode.testing) {
175
+ yield* useTesting({ verbose });
176
+ }
177
+ else {
178
+ yield* installTestingVocabulary({ verbose });
179
+ }
180
+ const execution = yield* execute({
166
181
  docPath,
167
182
  stream,
168
183
  componentDirs: componentDir,
@@ -184,10 +199,19 @@ function* run(config) {
184
199
  signal.close();
185
200
  yield* writer;
186
201
  }
202
+ // Inspect the completion Result AFTER the report finished streaming:
203
+ // test failures, assertion aborts, and any document abort exit nonzero.
204
+ const result = yield* execution;
205
+ if (!result.ok) {
206
+ if (result.error instanceof TestFailureError) {
207
+ console.error(`\ntests failed: ${result.error.message}`);
208
+ }
209
+ else {
210
+ console.error(result.error.message);
211
+ }
212
+ yield* exit(1);
213
+ }
187
214
  }
188
- // ---------------------------------------------------------------------------
189
- // Entry point
190
- // ---------------------------------------------------------------------------
191
215
  await main(function* (args) {
192
216
  const parser = xmd.createParser({ args });
193
217
  switch (parser.type) {
@@ -208,7 +232,10 @@ await main(function* (args) {
208
232
  }
209
233
  switch (parsed.value.name) {
210
234
  case "run":
211
- yield* run(parsed.value.config);
235
+ yield* run(parsed.value.config, { testing: false });
236
+ break;
237
+ case "test":
238
+ yield* run(parsed.value.config, { testing: true });
212
239
  break;
213
240
  }
214
241
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@executablemd/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "The xmd command-line interface for executable.md.",
5
5
  "homepage": "https://executable.md",
6
6
  "repository": {
@@ -17,8 +17,9 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@effectionx/stream-helpers": "0.8.3",
20
- "@executablemd/core": "^0.3.1",
21
- "@executablemd/durable-streams": "^0.3.1",
20
+ "@executablemd/core": "^0.4.1",
21
+ "@executablemd/durable-streams": "^0.4.1",
22
+ "@executablemd/testing": "^0.4.1",
22
23
  "configliere": "^0.2.3",
23
24
  "effection": "4.1.0-alpha.7",
24
25
  "zod": "^4.3.6"
package/types/deno.d.ts CHANGED
@@ -12,6 +12,9 @@ declare namespace _default {
12
12
  "effection/experimental": string;
13
13
  "@executablemd/durable-streams": string;
14
14
  "@executablemd/runtime": string;
15
+ "@executablemd/testing": string;
16
+ "@std/assert": string;
17
+ ajv: string;
15
18
  "@executablemd/runtime/test": string;
16
19
  "@executablemd/code-review-agent": string;
17
20
  "@effectionx/context-api": string;
@@ -36,6 +39,7 @@ declare namespace _default {
36
39
  "@executablemd/core/": string;
37
40
  "@executablemd/durable-streams/": string;
38
41
  "@executablemd/runtime/": string;
42
+ "@executablemd/testing/": string;
39
43
  "@executablemd/code-review-agent/": string;
40
44
  };
41
45
  }