@executablemd/testing 0.3.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/LICENSE +21 -0
- package/esm/core/mod.js +55 -0
- package/esm/core/src/api.js +31 -0
- package/esm/core/src/collect.js +24 -0
- package/esm/core/src/component-api.js +47 -0
- package/esm/core/src/content-context.js +23 -0
- package/esm/core/src/deno-compiler.js +51 -0
- package/esm/core/src/errors.js +13 -0
- package/esm/core/src/eval-context.js +13 -0
- package/esm/core/src/eval-handler.js +79 -0
- package/esm/core/src/eval-interpolate.js +63 -0
- package/esm/core/src/eval-transform.js +378 -0
- package/esm/core/src/execute.js +378 -0
- package/esm/core/src/expand.js +988 -0
- package/esm/core/src/frontmatter.js +98 -0
- package/esm/core/src/heal.js +29 -0
- package/esm/core/src/interpolate.js +39 -0
- package/esm/core/src/modifiers/daemon.js +60 -0
- package/esm/core/src/modifiers/persist.js +35 -0
- package/esm/core/src/modifiers/timeout.js +55 -0
- package/esm/core/src/modifiers.js +113 -0
- package/esm/core/src/output/normalize.js +33 -0
- package/esm/core/src/output/terminal.js +20 -0
- package/esm/core/src/render.js +30 -0
- package/esm/core/src/replay-stream.js +58 -0
- package/esm/core/src/sample-api.js +45 -0
- package/esm/core/src/scanner.js +632 -0
- package/esm/core/src/temp-file-compiler.js +68 -0
- package/esm/core/src/types.js +7 -0
- package/esm/core/src/validate.js +82 -0
- package/esm/durable-streams/combinators.js +227 -0
- package/esm/durable-streams/context.js +13 -0
- package/esm/durable-streams/divergence.js +56 -0
- package/esm/durable-streams/each.js +193 -0
- package/esm/durable-streams/effect.js +273 -0
- package/esm/durable-streams/ephemeral.js +80 -0
- package/esm/durable-streams/errors.js +92 -0
- package/esm/durable-streams/http-stream.js +207 -0
- package/esm/durable-streams/mod.js +34 -0
- package/esm/durable-streams/operations.js +88 -0
- package/esm/durable-streams/replay-guard.js +87 -0
- package/esm/durable-streams/replay-index.js +132 -0
- package/esm/durable-streams/run.js +137 -0
- package/esm/durable-streams/serialize.js +65 -0
- package/esm/durable-streams/stream.js +56 -0
- package/esm/durable-streams/types.js +10 -0
- package/esm/package.json +3 -0
- package/esm/runtime/apis.js +237 -0
- package/esm/runtime/find-free-port.js +33 -0
- package/esm/runtime/mod.js +20 -0
- package/esm/testing/npm/src/_dnt.polyfills.js +1 -0
- package/esm/testing/npm/src/_dnt.shims.js +57 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +47 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/array_includes.js +65 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/assert.js +23 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +30 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/equal.js +194 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/equals.js +61 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/exists.js +27 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/fail.js +20 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/false.js +23 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/greater.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/instance_of.js +52 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/is_error.js +53 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/less.js +28 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/match.js +26 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/mod.js +45 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_equals.js +39 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +29 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_match.js +26 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +32 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/object_match.js +177 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/rejects.js +53 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +58 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/string_includes.js +26 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/throws.js +44 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +20 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/assert/1.0.19/unreachable.js +20 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/build_message.js +116 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/diff.js +277 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/diff_str.js +181 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/format.js +70 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/styles.js +207 -0
- package/esm/testing/npm/src/deps/jsr.io/@std/internal/1.0.14/types.js +3 -0
- package/esm/testing/npm/src/mod.js +27 -0
- package/esm/testing/npm/src/src/assertions.js +250 -0
- package/esm/testing/npm/src/src/handlers.js +245 -0
- package/esm/testing/npm/src/src/journal.js +152 -0
- package/esm/testing/npm/src/src/test-api.js +33 -0
- package/esm/testing/npm/src/src/use-testing.js +79 -0
- package/esm/testing/npm/src/src/vocabulary.js +111 -0
- package/package.json +35 -0
- package/types/_dnt.polyfills.d.ts +6 -0
- package/types/_dnt.shims.d.ts +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +29 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +28 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +16 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +25 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +24 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +36 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +14 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/false.d.ts +18 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +22 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +24 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/less.d.ts +18 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/match.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +43 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +26 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +19 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +22 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +28 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +41 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +23 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +17 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +44 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +14 -0
- package/types/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +14 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +79 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +139 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +98 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/format.d.ts +28 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +158 -0
- package/types/deps/jsr.io/@std/internal/1.0.14/types.d.ts +24 -0
- package/types/mod.d.ts +29 -0
- package/types/src/assertions.d.ts +55 -0
- package/types/src/handlers.d.ts +18 -0
- package/types/src/journal.d.ts +29 -0
- package/types/src/test-api.d.ts +54 -0
- package/types/src/use-testing.d.ts +25 -0
- package/types/src/vocabulary.d.ts +34 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Context APIs — platform I/O operations with pluggable middleware.
|
|
3
|
+
*
|
|
4
|
+
* Five domain-specific context APIs built on `@effectionx/context-api`.
|
|
5
|
+
* Each API provides default Node.js implementations. Use `.around()` to
|
|
6
|
+
* install middleware (mocking, instrumentation, sandboxing) scoped to the
|
|
7
|
+
* current Effection scope.
|
|
8
|
+
*
|
|
9
|
+
* ## Architecture
|
|
10
|
+
*
|
|
11
|
+
* Import operation functions for normal calls, and use `API` only when
|
|
12
|
+
* installing middleware with `.around()`:
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { readTextFile, stat, API } from "@executablemd/runtime";
|
|
16
|
+
*
|
|
17
|
+
* // normal calls
|
|
18
|
+
* const file = yield* readTextFile("doc.md");
|
|
19
|
+
*
|
|
20
|
+
* // middleware
|
|
21
|
+
* yield* API.Fs.around({
|
|
22
|
+
* *readTextFile([path], next) {
|
|
23
|
+
* return yield* next(path);
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ## Why four separate APIs?
|
|
29
|
+
*
|
|
30
|
+
* - **Process** — subprocess lifecycle has its own cancellation semantics
|
|
31
|
+
* (killing processes on scope teardown). Middleware targets exec only.
|
|
32
|
+
* - **Fs** — readTextFile, stat, and glob form a cohesive file-IO surface
|
|
33
|
+
* used together for component resolution and replay guards.
|
|
34
|
+
* - **Fetch** — HTTP has distinct timeout/body/abort semantics. Merging
|
|
35
|
+
* with Fs or Process would blur cancellation boundaries.
|
|
36
|
+
* - **Env** — synchronous host metadata (env vars, platform). Kept as a
|
|
37
|
+
* context-api despite being sync because tests use `.around()` to mock
|
|
38
|
+
* platform/env for deterministic replay testing.
|
|
39
|
+
*
|
|
40
|
+
* ## Middleware
|
|
41
|
+
*
|
|
42
|
+
* ```typescript
|
|
43
|
+
* yield* API.Fs.around({
|
|
44
|
+
* *readTextFile([path], next) {
|
|
45
|
+
* return "mocked content";
|
|
46
|
+
* },
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* Middleware is **scoped** — it only affects operations within the
|
|
51
|
+
* current Effection scope and its children. Install before calling
|
|
52
|
+
* `execute()` or `durableRun()`.
|
|
53
|
+
*
|
|
54
|
+
* ## Test stubs
|
|
55
|
+
*
|
|
56
|
+
* Common stubs are provided by `@executablemd/runtime/test`:
|
|
57
|
+
* `useStubFs(files)`, `useEchoExec()`, `useFailingExec(code, stderr)`.
|
|
58
|
+
*/
|
|
59
|
+
import { createApi } from "@effectionx/context-api";
|
|
60
|
+
import { relative, sep } from "node:path";
|
|
61
|
+
import process from "node:process";
|
|
62
|
+
import { fetch as effectionFetch } from "@effectionx/fetch";
|
|
63
|
+
import { readTextFile as fsReadTextFile, stat as fsStat, globToRegExp, walk } from "@effectionx/fs";
|
|
64
|
+
import { exec as processExec } from "@effectionx/process";
|
|
65
|
+
import { each, race, sleep } from "effection";
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Private helpers
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
function* withTimeout(label, timeout, operation) {
|
|
70
|
+
if (timeout === undefined) {
|
|
71
|
+
return yield* operation;
|
|
72
|
+
}
|
|
73
|
+
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
74
|
+
throw new Error(`${label}: timeout must be a non-negative finite number`);
|
|
75
|
+
}
|
|
76
|
+
return (yield* race([
|
|
77
|
+
operation,
|
|
78
|
+
(function* () {
|
|
79
|
+
yield* sleep(timeout);
|
|
80
|
+
throw new Error(`${label} timed out after ${timeout}ms`);
|
|
81
|
+
})(),
|
|
82
|
+
]));
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Context APIs
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
export const API = {
|
|
88
|
+
/**
|
|
89
|
+
* Subprocess execution.
|
|
90
|
+
*
|
|
91
|
+
* Default implementation uses `@effectionx/process`.
|
|
92
|
+
* Cancellation kills the process via Effection scope teardown.
|
|
93
|
+
*/
|
|
94
|
+
Process: createApi("runtime.process", {
|
|
95
|
+
*exec(options) {
|
|
96
|
+
const { command, cwd, env, timeout } = options;
|
|
97
|
+
const [cmd, ...args] = command;
|
|
98
|
+
if (!cmd) {
|
|
99
|
+
throw new Error("exec: command array must not be empty");
|
|
100
|
+
}
|
|
101
|
+
const result = yield* withTimeout(`exec(${cmd})`, timeout, processExec(cmd, {
|
|
102
|
+
arguments: args,
|
|
103
|
+
cwd,
|
|
104
|
+
env,
|
|
105
|
+
}).join());
|
|
106
|
+
return {
|
|
107
|
+
exitCode: result.code ?? 1,
|
|
108
|
+
stdout: result.stdout,
|
|
109
|
+
stderr: result.stderr,
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
}),
|
|
113
|
+
/**
|
|
114
|
+
* Filesystem operations.
|
|
115
|
+
*
|
|
116
|
+
* Default implementation uses `@effectionx/fs`.
|
|
117
|
+
*/
|
|
118
|
+
Fs: createApi("runtime.fs", {
|
|
119
|
+
*readTextFile(path) {
|
|
120
|
+
return yield* fsReadTextFile(path);
|
|
121
|
+
},
|
|
122
|
+
*stat(path) {
|
|
123
|
+
try {
|
|
124
|
+
const s = yield* fsStat(path);
|
|
125
|
+
return {
|
|
126
|
+
exists: true,
|
|
127
|
+
isFile: s.isFile(),
|
|
128
|
+
isDirectory: s.isDirectory(),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (err.code === "ENOENT") {
|
|
133
|
+
return { exists: false, isFile: false, isDirectory: false };
|
|
134
|
+
}
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
*glob(options) {
|
|
139
|
+
const { patterns, root, exclude = [] } = options;
|
|
140
|
+
const results = [];
|
|
141
|
+
// Convert include/exclude patterns to RegExp for matching
|
|
142
|
+
// against relative paths from root
|
|
143
|
+
const includeRegexes = patterns.map((p) => globToRegExp(p, { extended: true, globstar: true }));
|
|
144
|
+
const excludeRegexes = exclude.map((e) => globToRegExp(e, { extended: true, globstar: true }));
|
|
145
|
+
// Walk the directory tree and match relative paths
|
|
146
|
+
const stream = walk(root, {
|
|
147
|
+
includeFiles: true,
|
|
148
|
+
includeDirs: false,
|
|
149
|
+
skip: excludeRegexes.length > 0 ? excludeRegexes : undefined,
|
|
150
|
+
});
|
|
151
|
+
for (const entry of yield* each(stream)) {
|
|
152
|
+
// Normalize to POSIX separators for consistent matching across platforms
|
|
153
|
+
const relPath = relative(root, entry.path).split(sep).join("/");
|
|
154
|
+
const matches = includeRegexes.some((re) => re.test(relPath));
|
|
155
|
+
if (matches) {
|
|
156
|
+
results.push({ path: relPath, isFile: entry.isFile });
|
|
157
|
+
}
|
|
158
|
+
yield* each.next();
|
|
159
|
+
}
|
|
160
|
+
return results;
|
|
161
|
+
},
|
|
162
|
+
}),
|
|
163
|
+
/**
|
|
164
|
+
* HTTP requests.
|
|
165
|
+
*
|
|
166
|
+
* Default implementation uses `@effectionx/fetch`.
|
|
167
|
+
* Cancellation aborts the request via Effection scope teardown.
|
|
168
|
+
*/
|
|
169
|
+
Fetch: createApi("runtime.fetch", {
|
|
170
|
+
*fetch(input, init) {
|
|
171
|
+
const timeout = init?.timeout;
|
|
172
|
+
const response = yield* withTimeout(`fetch(${input})`, timeout, effectionFetch(input, {
|
|
173
|
+
method: init?.method,
|
|
174
|
+
headers: init?.headers,
|
|
175
|
+
body: init?.body,
|
|
176
|
+
}));
|
|
177
|
+
return {
|
|
178
|
+
status: response.status,
|
|
179
|
+
headers: response.headers,
|
|
180
|
+
*text() {
|
|
181
|
+
return yield* withTimeout(`fetch(${input}).text()`, timeout, response.text());
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
}),
|
|
186
|
+
/**
|
|
187
|
+
* Environment variables and platform information.
|
|
188
|
+
*
|
|
189
|
+
* These are synchronous lookups wrapped as Operations to satisfy
|
|
190
|
+
* context-api handler constraints.
|
|
191
|
+
*/
|
|
192
|
+
Env: createApi("runtime.env", {
|
|
193
|
+
// deno-lint-ignore require-yield
|
|
194
|
+
*cwd() {
|
|
195
|
+
return process.cwd();
|
|
196
|
+
},
|
|
197
|
+
// deno-lint-ignore require-yield
|
|
198
|
+
*env(name) {
|
|
199
|
+
return process.env[name];
|
|
200
|
+
},
|
|
201
|
+
// deno-lint-ignore require-yield
|
|
202
|
+
*platform() {
|
|
203
|
+
return {
|
|
204
|
+
os: process.platform,
|
|
205
|
+
arch: process.arch,
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
/**
|
|
210
|
+
* Block compilation.
|
|
211
|
+
*
|
|
212
|
+
* Default handler throws — platform-specific middleware must be
|
|
213
|
+
* installed via `yield* API.Compiler.around(...)` before use.
|
|
214
|
+
* See `core/src/deno-compiler.ts` for the Deno implementation.
|
|
215
|
+
*/
|
|
216
|
+
Compiler: createApi("runtime.compiler", {
|
|
217
|
+
// deno-lint-ignore require-yield
|
|
218
|
+
*compile(_source, _options) {
|
|
219
|
+
throw new Error("compiler not installed — install platform-specific middleware via API.Compiler.around()");
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
};
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Convenience operation exports
|
|
225
|
+
//
|
|
226
|
+
// Use these for normal operation calls. Use API.* only when installing
|
|
227
|
+
// middleware with .around().
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
export const exec = API.Process.operations.exec;
|
|
230
|
+
export const readTextFile = API.Fs.operations.readTextFile;
|
|
231
|
+
export const stat = API.Fs.operations.stat;
|
|
232
|
+
export const glob = API.Fs.operations.glob;
|
|
233
|
+
export const fetch = API.Fetch.operations.fetch;
|
|
234
|
+
export const env = API.Env.operations.env;
|
|
235
|
+
export const cwd = API.Env.operations.cwd;
|
|
236
|
+
export const platform = API.Env.operations.platform;
|
|
237
|
+
export const compile = API.Compiler.operations.compile;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* findFreePort — find an available TCP port using the OS.
|
|
3
|
+
*/
|
|
4
|
+
import { race } from "effection";
|
|
5
|
+
import { once } from "@effectionx/node";
|
|
6
|
+
import { createServer } from "node:net";
|
|
7
|
+
/**
|
|
8
|
+
* Find an available TCP port by binding to port 0 and reading the
|
|
9
|
+
* OS-assigned port number.
|
|
10
|
+
*/
|
|
11
|
+
export function* findFreePort() {
|
|
12
|
+
const server = createServer();
|
|
13
|
+
const listening = once(server, "listening");
|
|
14
|
+
const error = once(server, "error");
|
|
15
|
+
server.listen(0);
|
|
16
|
+
try {
|
|
17
|
+
const rethrowError = {
|
|
18
|
+
*[Symbol.iterator]() {
|
|
19
|
+
const [err] = yield* error;
|
|
20
|
+
throw err;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
yield* race([listening, rethrowError]);
|
|
24
|
+
const addr = server.address();
|
|
25
|
+
if (!addr || typeof addr !== "object") {
|
|
26
|
+
throw new Error("findFreePort: unexpected address format");
|
|
27
|
+
}
|
|
28
|
+
return addr.port;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
server.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Runtime Context APIs for executable markdown.
|
|
4
|
+
*
|
|
5
|
+
* `API` is available for middleware (`.around()`).
|
|
6
|
+
* For normal calls, import operations directly.
|
|
7
|
+
*
|
|
8
|
+
* Six domain APIs:
|
|
9
|
+
* - `API.Process` — subprocess execution (`exec`)
|
|
10
|
+
* - `API.Fs` — filesystem (`readTextFile`, `stat`, `glob`)
|
|
11
|
+
* - `API.Fetch` — HTTP requests (`fetch`)
|
|
12
|
+
* - `API.Env` — environment variables and platform info (`cwd`, `env`, `platform`)
|
|
13
|
+
* - `API.Compiler` — block compilation (`compile`)
|
|
14
|
+
*
|
|
15
|
+
* See `apis.ts` for architecture rationale.
|
|
16
|
+
* See `@executablemd/runtime/test` for composable test stubs.
|
|
17
|
+
*/
|
|
18
|
+
export { API } from "./apis.ts";
|
|
19
|
+
export { exec, readTextFile, stat, glob, fetch, cwd, env, platform, compile } from "./apis.ts";
|
|
20
|
+
export { findFreePort } from "./find-free-port.ts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const dntGlobals = {};
|
|
2
|
+
export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
3
|
+
function createMergeProxy(baseObj, extObj) {
|
|
4
|
+
return new Proxy(baseObj, {
|
|
5
|
+
get(_target, prop, _receiver) {
|
|
6
|
+
if (prop in extObj) {
|
|
7
|
+
return extObj[prop];
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
return baseObj[prop];
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
set(_target, prop, value) {
|
|
14
|
+
if (prop in extObj) {
|
|
15
|
+
delete extObj[prop];
|
|
16
|
+
}
|
|
17
|
+
baseObj[prop] = value;
|
|
18
|
+
return true;
|
|
19
|
+
},
|
|
20
|
+
deleteProperty(_target, prop) {
|
|
21
|
+
let success = false;
|
|
22
|
+
if (prop in extObj) {
|
|
23
|
+
delete extObj[prop];
|
|
24
|
+
success = true;
|
|
25
|
+
}
|
|
26
|
+
if (prop in baseObj) {
|
|
27
|
+
delete baseObj[prop];
|
|
28
|
+
success = true;
|
|
29
|
+
}
|
|
30
|
+
return success;
|
|
31
|
+
},
|
|
32
|
+
ownKeys(_target) {
|
|
33
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
34
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
35
|
+
const extKeysSet = new Set(extKeys);
|
|
36
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
37
|
+
},
|
|
38
|
+
defineProperty(_target, prop, desc) {
|
|
39
|
+
if (prop in extObj) {
|
|
40
|
+
delete extObj[prop];
|
|
41
|
+
}
|
|
42
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
46
|
+
if (prop in extObj) {
|
|
47
|
+
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
has(_target, prop) {
|
|
54
|
+
return prop in extObj || prop in baseObj;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright 2018-2026 the Deno authors. MIT license.
|
|
2
|
+
// This module is browser compatible.
|
|
3
|
+
import { AssertionError } from "./assertion_error.js";
|
|
4
|
+
/**
|
|
5
|
+
* Make an assertion that `actual` and `expected` are almost equal numbers
|
|
6
|
+
* through a given tolerance. It can be used to take into account IEEE-754
|
|
7
|
+
* double-precision floating-point representation limitations. If the values
|
|
8
|
+
* are not almost equal then throw.
|
|
9
|
+
*
|
|
10
|
+
* The default tolerance is one hundred thousandth of a percent of the
|
|
11
|
+
* expected value.
|
|
12
|
+
*
|
|
13
|
+
* @example Usage
|
|
14
|
+
* ```ts ignore
|
|
15
|
+
* import { assertAlmostEquals } from "@std/assert";
|
|
16
|
+
*
|
|
17
|
+
* assertAlmostEquals(0.01, 0.02); // Throws
|
|
18
|
+
* assertAlmostEquals(1e-8, 1e-9); // Throws
|
|
19
|
+
* assertAlmostEquals(1.000000001e-8, 1.000000002e-8); // Doesn't throw
|
|
20
|
+
* assertAlmostEquals(0.01, 0.02, 0.1); // Doesn't throw
|
|
21
|
+
* assertAlmostEquals(0.1 + 0.2, 0.3, 1e-16); // Doesn't throw
|
|
22
|
+
* assertAlmostEquals(0.1 + 0.2, 0.3, 1e-17); // Throws
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @param actual The actual value to compare.
|
|
26
|
+
* @param expected The expected value to compare.
|
|
27
|
+
* @param tolerance The tolerance to consider the values almost equal. The
|
|
28
|
+
* default is one hundred thousandth of a percent of the expected value.
|
|
29
|
+
* @param msg The optional message to include in the error.
|
|
30
|
+
* @throws {AssertionError} If the values are not almost equal.
|
|
31
|
+
*/
|
|
32
|
+
export function assertAlmostEquals(actual, expected, tolerance, msg) {
|
|
33
|
+
if (Object.is(actual, expected)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const delta = Math.abs(expected - actual);
|
|
37
|
+
if (tolerance === undefined) {
|
|
38
|
+
tolerance = isFinite(expected) ? Math.abs(expected * 1e-7) : 1e-7;
|
|
39
|
+
}
|
|
40
|
+
if (delta <= tolerance) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const msgSuffix = msg ? `: ${msg}` : ".";
|
|
44
|
+
const f = (n) => Number.isInteger(n) ? n : n.toExponential();
|
|
45
|
+
throw new AssertionError(`Expected actual: "${f(actual)}" to be close to "${f(expected)}": \
|
|
46
|
+
delta "${f(delta)}" is greater than "${f(tolerance)}"${msgSuffix}`);
|
|
47
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright 2018-2026 the Deno authors. MIT license.
|
|
2
|
+
// This module is browser compatible.
|
|
3
|
+
import { equal } from "./equal.js";
|
|
4
|
+
import { format } from "../../internal/1.0.14/format.js";
|
|
5
|
+
import { AssertionError } from "./assertion_error.js";
|
|
6
|
+
function isPrimitive(value) {
|
|
7
|
+
return value === null ||
|
|
8
|
+
typeof value !== "object" && typeof value !== "function";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Asserts that `actual` contains all values in `expected`, using deep equality
|
|
12
|
+
* for non-primitive values.
|
|
13
|
+
*
|
|
14
|
+
* @example Usage with primitives
|
|
15
|
+
* ```ts ignore
|
|
16
|
+
* import { assertArrayIncludes } from "@std/assert";
|
|
17
|
+
*
|
|
18
|
+
* assertArrayIncludes([1, 2, 3], [2, 3]); // Passes
|
|
19
|
+
* assertArrayIncludes([1, 2, 3], [4]); // Throws
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example Usage with objects (deep equality)
|
|
23
|
+
* ```ts ignore
|
|
24
|
+
* import { assertArrayIncludes } from "@std/assert";
|
|
25
|
+
*
|
|
26
|
+
* assertArrayIncludes([{ a: 1 }, { b: 2 }], [{ a: 1 }]); // Passes
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @typeParam T The element type of the arrays.
|
|
30
|
+
* @param actual The array-like object to search within.
|
|
31
|
+
* @param expected The values that must be present in `actual`.
|
|
32
|
+
* @param msg Optional message to display on failure.
|
|
33
|
+
* @throws {AssertionError} If any value in `expected` is not found in `actual`.
|
|
34
|
+
*/
|
|
35
|
+
export function assertArrayIncludes(actual, expected, msg) {
|
|
36
|
+
const missing = [];
|
|
37
|
+
const expectedLen = expected.length;
|
|
38
|
+
const actualLen = actual.length;
|
|
39
|
+
for (let i = 0; i < expectedLen; i++) {
|
|
40
|
+
const item = expected[i];
|
|
41
|
+
let found;
|
|
42
|
+
if (isPrimitive(item)) {
|
|
43
|
+
// Fast path
|
|
44
|
+
found = Array.prototype.includes.call(actual, item);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
found = false;
|
|
48
|
+
for (let j = 0; j < actualLen; j++) {
|
|
49
|
+
if (equal(item, actual[j])) {
|
|
50
|
+
found = true;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!found) {
|
|
56
|
+
missing.push(item);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (missing.length === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const msgSuffix = msg ? `: ${msg}` : ".";
|
|
63
|
+
msg = `Expected actual: "${format(actual)}" to include: "${format(expected)}"${msgSuffix}\nmissing: ${format(missing)}`;
|
|
64
|
+
throw new AssertionError(msg);
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright 2018-2026 the Deno authors. MIT license.
|
|
2
|
+
// This module is browser compatible.
|
|
3
|
+
import { AssertionError } from "./assertion_error.js";
|
|
4
|
+
/**
|
|
5
|
+
* Make an assertion, an error will be thrown if `expr` does not have a truthy value.
|
|
6
|
+
*
|
|
7
|
+
* @example Usage
|
|
8
|
+
* ```ts ignore
|
|
9
|
+
* import { assert } from "@std/assert";
|
|
10
|
+
*
|
|
11
|
+
* assert("hello".includes("ello")); // Doesn't throw
|
|
12
|
+
* assert("hello".includes("world")); // Throws
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @param expr The expression to test.
|
|
16
|
+
* @param msg The optional message to display if the assertion fails.
|
|
17
|
+
* @throws {AssertionError} If `expr` is falsy.
|
|
18
|
+
*/
|
|
19
|
+
export function assert(expr, msg = "") {
|
|
20
|
+
if (!expr) {
|
|
21
|
+
throw new AssertionError(msg);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Copyright 2018-2026 the Deno authors. MIT license.
|
|
2
|
+
// This module is browser compatible.
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown when an assertion fails.
|
|
5
|
+
*
|
|
6
|
+
* @example Usage
|
|
7
|
+
* ```ts ignore
|
|
8
|
+
* import { AssertionError } from "@std/assert";
|
|
9
|
+
*
|
|
10
|
+
* try {
|
|
11
|
+
* throw new AssertionError("foo", { cause: "bar" });
|
|
12
|
+
* } catch (error) {
|
|
13
|
+
* if (error instanceof AssertionError) {
|
|
14
|
+
* error.message === "foo"; // true
|
|
15
|
+
* error.cause === "bar"; // true
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class AssertionError extends Error {
|
|
21
|
+
/** Constructs a new instance.
|
|
22
|
+
*
|
|
23
|
+
* @param message The error message.
|
|
24
|
+
* @param options Additional options. This argument is still unstable. It may change in the future release.
|
|
25
|
+
*/
|
|
26
|
+
constructor(message, options) {
|
|
27
|
+
super(message, options);
|
|
28
|
+
this.name = "AssertionError";
|
|
29
|
+
}
|
|
30
|
+
}
|