@executablemd/runtime 0.5.2 → 0.7.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/esm/apis.js +173 -38
- package/esm/mod.js +6 -4
- package/esm/test/stubs.js +4 -1
- package/package.json +2 -2
- package/types/apis.d.ts +55 -10
- package/types/mod.d.ts +7 -5
- package/types/test/stubs.d.ts +4 -1
package/esm/apis.js
CHANGED
|
@@ -29,13 +29,18 @@
|
|
|
29
29
|
*
|
|
30
30
|
* - **Process** — subprocess lifecycle has its own cancellation semantics
|
|
31
31
|
* (killing processes on scope teardown). Middleware targets exec only.
|
|
32
|
-
* - **Fs** —
|
|
33
|
-
* used together for component resolution
|
|
32
|
+
* - **Fs** — reading, writing, and inspecting files form a cohesive file-IO
|
|
33
|
+
* surface used together for component resolution, replay guards, and the
|
|
34
|
+
* `<File>` component. Middleware installed here sees a document's own file
|
|
35
|
+
* access on the same terms as the engine's.
|
|
34
36
|
* - **Fetch** — HTTP has distinct timeout/body/abort semantics. Merging
|
|
35
37
|
* with Fs or Process would blur cancellation boundaries.
|
|
36
|
-
* - **Env** —
|
|
37
|
-
*
|
|
38
|
-
*
|
|
38
|
+
* - **Env** — the host itself: metadata (env vars, platform) plus the two
|
|
39
|
+
* capabilities only the entrypoint can supply, `command` (how to re-invoke
|
|
40
|
+
* this xmd) and `compile` (how this host loads a generated module). Tests
|
|
41
|
+
* use `.around()` to mock platform/env for deterministic replay; an
|
|
42
|
+
* entrypoint installs its `command` and `compile` with `{ at: "min" }` so
|
|
43
|
+
* ordinary middleware can wrap them.
|
|
39
44
|
*
|
|
40
45
|
* ## Middleware
|
|
41
46
|
*
|
|
@@ -57,13 +62,28 @@
|
|
|
57
62
|
* `useStubFs(files)`, `useEchoExec()`, `useFailingExec(code, stderr)`.
|
|
58
63
|
*/
|
|
59
64
|
import { createApi } from "@effectionx/context-api";
|
|
60
|
-
import {
|
|
65
|
+
import { join } from "node:path";
|
|
61
66
|
import process from "node:process";
|
|
67
|
+
import { realpath as fsRealpath, rename as fsRename } from "node:fs/promises";
|
|
62
68
|
import { fetch as effectionFetch } from "@effectionx/fetch";
|
|
63
|
-
import { readTextFile as fsReadTextFile, stat as fsStat,
|
|
69
|
+
import { ensureDir as fsEnsureDir, FsApi, globToRegExp, readTextFile as fsReadTextFile, rm as fsRm, stat as fsStat, writeTextFile as fsWriteTextFile, } from "@effectionx/fs";
|
|
64
70
|
import { exec as processExec } from "@effectionx/process";
|
|
65
|
-
import {
|
|
71
|
+
import { race, sleep, until } from "effection";
|
|
66
72
|
import { timeout as contextualTimeout } from "./config.js";
|
|
73
|
+
/**
|
|
74
|
+
* The `errno` string a failed filesystem call carries, when it carries one.
|
|
75
|
+
*
|
|
76
|
+
* Read rather than asserted: `catch` gives back `unknown`, and what arrives
|
|
77
|
+
* there is only conventionally an `ErrnoException`. Narrowing says what is
|
|
78
|
+
* actually known about the value instead of claiming a shape it may not have.
|
|
79
|
+
*/
|
|
80
|
+
function errorCode(error) {
|
|
81
|
+
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const { code } = error;
|
|
85
|
+
return typeof code === "string" ? code : undefined;
|
|
86
|
+
}
|
|
67
87
|
function* withTimeout(label, timeout, operation) {
|
|
68
88
|
if (timeout === undefined) {
|
|
69
89
|
return yield* operation;
|
|
@@ -79,6 +99,97 @@ function* withTimeout(label, timeout, operation) {
|
|
|
79
99
|
})(),
|
|
80
100
|
]));
|
|
81
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* A glob pattern as a matcher for relative POSIX paths.
|
|
104
|
+
*
|
|
105
|
+
* A pattern that cannot be compiled throws here — an unterminated character
|
|
106
|
+
* class is a `SyntaxError` from `RegExp` — so it surfaces from `glob` itself
|
|
107
|
+
* rather than silently matching nothing.
|
|
108
|
+
*/
|
|
109
|
+
function toRegExp(pattern) {
|
|
110
|
+
return globToRegExp(pattern, { extended: true, globstar: true });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A matcher for directories whose entire subtree an exclusion covers, when the
|
|
114
|
+
* pattern is one that can prove it.
|
|
115
|
+
*
|
|
116
|
+
* Skipping a subtree is only sound if *every* path beneath it is excluded, and
|
|
117
|
+
* matching the directory tells us nothing of the sort: `foo` does not match
|
|
118
|
+
* `foo/deep/keep.md`, and `foo/*` matches `foo/direct.md` but stops at the next
|
|
119
|
+
* separator. Testing the directory — or the directory with a trailing separator
|
|
120
|
+
* — as a proxy for "all descendants" prunes more than the pattern selects.
|
|
121
|
+
*
|
|
122
|
+
* A trailing `/**` is the form that does prove it. It compiles to
|
|
123
|
+
* `(?:[^/]*(?:/|$))*`, which matches any sequence of segments, so once the part
|
|
124
|
+
* before it matches a directory the pattern matches every path under that
|
|
125
|
+
* directory at any depth. `**` alone covers the whole tree the same way.
|
|
126
|
+
*
|
|
127
|
+
* Anything else returns `undefined` and the subtree is walked, with its files
|
|
128
|
+
* filtered one at a time. That is the conservative direction: descending a
|
|
129
|
+
* subtree whose files are all excluded costs reads, while skipping one that
|
|
130
|
+
* holds a match loses the match.
|
|
131
|
+
*/
|
|
132
|
+
const SUBTREE = "/**";
|
|
133
|
+
function isRegExp(value) {
|
|
134
|
+
return value !== undefined;
|
|
135
|
+
}
|
|
136
|
+
function pruneMatcher(pattern) {
|
|
137
|
+
if (pattern === "**") {
|
|
138
|
+
return toRegExp("**");
|
|
139
|
+
}
|
|
140
|
+
if (!pattern.endsWith(SUBTREE)) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
return toRegExp(pattern.slice(0, -SUBTREE.length));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Collect matches under `directory`, whose path relative to the glob root is
|
|
147
|
+
* `prefix`.
|
|
148
|
+
*
|
|
149
|
+
* A plain recursive generator rather than `@effectionx/fs`'s `walk()`, whose
|
|
150
|
+
* producer runs in a spawned task: a `readdir` that fails there tears down the
|
|
151
|
+
* surrounding scope instead of throwing at the call site, so no caller can
|
|
152
|
+
* report it. Recursing here makes a failure `glob`'s own, and makes every
|
|
153
|
+
* directory read a cancellation point.
|
|
154
|
+
*
|
|
155
|
+
* Paths are assembled from entry names with `/`, so what patterns match is the
|
|
156
|
+
* relative POSIX path on every platform.
|
|
157
|
+
*
|
|
158
|
+
* Exclusion is decided per **candidate**: a file or symlink whose own path an
|
|
159
|
+
* exclude pattern matches is not reported, which is what makes exclusions win.
|
|
160
|
+
* A directory is not a candidate — it is never reported — so its own path is
|
|
161
|
+
* not tested against exclusions at all. The only question a directory raises is
|
|
162
|
+
* whether walking it can still produce something, and that is `pruneMatcher`'s.
|
|
163
|
+
*/
|
|
164
|
+
function* descend(directory, prefix, walk) {
|
|
165
|
+
for (const entry of yield* FsApi.operations.readdirDirents(directory)) {
|
|
166
|
+
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
167
|
+
// Before the exclusion test, because a directory is not a candidate: an
|
|
168
|
+
// exclusion matching its path says nothing about the files beneath it.
|
|
169
|
+
// A symlink to a directory takes the branches below instead — `isDirectory`
|
|
170
|
+
// is false for one — so traversal never follows it.
|
|
171
|
+
if (entry.isDirectory()) {
|
|
172
|
+
if (!walk.prune.some((re) => re.test(path))) {
|
|
173
|
+
yield* descend(join(directory, entry.name), path, walk);
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (walk.exclude.some((re) => re.test(path))) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// A symlink is reported by its own path and never followed, so traversal
|
|
181
|
+
// stays under the root and cannot cycle.
|
|
182
|
+
if (entry.isSymbolicLink()) {
|
|
183
|
+
if (walk.include.some((re) => re.test(path))) {
|
|
184
|
+
walk.matched.push({ path, isFile: false });
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (entry.isFile() && walk.include.some((re) => re.test(path))) {
|
|
189
|
+
walk.matched.push({ path, isFile: true });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
82
193
|
export const API = {
|
|
83
194
|
/**
|
|
84
195
|
* Subprocess execution.
|
|
@@ -125,7 +236,7 @@ export const API = {
|
|
|
125
236
|
};
|
|
126
237
|
}
|
|
127
238
|
catch (err) {
|
|
128
|
-
if (err
|
|
239
|
+
if (errorCode(err) === "ENOENT") {
|
|
129
240
|
return { exists: false, isFile: false, isDirectory: false };
|
|
130
241
|
}
|
|
131
242
|
throw err;
|
|
@@ -133,27 +244,38 @@ export const API = {
|
|
|
133
244
|
},
|
|
134
245
|
*glob(options) {
|
|
135
246
|
const { patterns, root, exclude = [] } = options;
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const stream = walk(root, {
|
|
143
|
-
includeFiles: true,
|
|
144
|
-
includeDirs: false,
|
|
145
|
-
skip: excludeRegexes.length > 0 ? excludeRegexes : undefined,
|
|
247
|
+
const matched = [];
|
|
248
|
+
yield* descend(root, "", {
|
|
249
|
+
include: patterns.map(toRegExp),
|
|
250
|
+
exclude: exclude.map(toRegExp),
|
|
251
|
+
prune: exclude.map(pruneMatcher).filter(isRegExp),
|
|
252
|
+
matched,
|
|
146
253
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
254
|
+
return matched;
|
|
255
|
+
},
|
|
256
|
+
*writeTextFile(path, content) {
|
|
257
|
+
yield* fsWriteTextFile(path, content);
|
|
258
|
+
},
|
|
259
|
+
*ensureDir(path) {
|
|
260
|
+
yield* fsEnsureDir(path);
|
|
261
|
+
},
|
|
262
|
+
*rename(from, to) {
|
|
263
|
+
yield* until(fsRename(from, to));
|
|
264
|
+
},
|
|
265
|
+
*remove(path, options) {
|
|
266
|
+
yield* fsRm(path, options);
|
|
267
|
+
},
|
|
268
|
+
*realpath(path) {
|
|
269
|
+
try {
|
|
270
|
+
return yield* until(fsRealpath(path));
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
const code = errorCode(err);
|
|
274
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
275
|
+
return undefined;
|
|
153
276
|
}
|
|
154
|
-
|
|
277
|
+
throw err;
|
|
155
278
|
}
|
|
156
|
-
return results;
|
|
157
279
|
},
|
|
158
280
|
}),
|
|
159
281
|
/**
|
|
@@ -201,18 +323,25 @@ export const API = {
|
|
|
201
323
|
arch: process.arch,
|
|
202
324
|
};
|
|
203
325
|
},
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
326
|
+
/**
|
|
327
|
+
* The default cannot be derived. `process.execPath` names the executable
|
|
328
|
+
* but not how it was launched — `deno run --allow-all <entry>` and
|
|
329
|
+
* `node <entry>` are not recoverable from "deno" or "node", and a
|
|
330
|
+
* compiled binary takes no entry script at all. Only the entrypoint that
|
|
331
|
+
* started this process knows.
|
|
332
|
+
*/
|
|
333
|
+
// deno-lint-ignore require-yield
|
|
334
|
+
*command(_args) {
|
|
335
|
+
throw new Error("xmd command not installed — a runtime-named entrypoint must install it via API.Env.around()");
|
|
336
|
+
},
|
|
337
|
+
/**
|
|
338
|
+
* Compiling an eval block means loading a module the way this host
|
|
339
|
+
* loads modules, so the implementation belongs with the entrypoint that
|
|
340
|
+
* knows the host — beside `command`, installed in the same call.
|
|
341
|
+
*/
|
|
213
342
|
// deno-lint-ignore require-yield
|
|
214
343
|
*compile(_source, _options) {
|
|
215
|
-
throw new Error("compiler not installed — install platform-specific middleware via API.
|
|
344
|
+
throw new Error("compiler not installed — install platform-specific middleware via API.Env.around()");
|
|
216
345
|
},
|
|
217
346
|
}),
|
|
218
347
|
};
|
|
@@ -220,8 +349,14 @@ export const exec = API.Process.operations.exec;
|
|
|
220
349
|
export const readTextFile = API.Fs.operations.readTextFile;
|
|
221
350
|
export const stat = API.Fs.operations.stat;
|
|
222
351
|
export const glob = API.Fs.operations.glob;
|
|
352
|
+
export const writeTextFile = API.Fs.operations.writeTextFile;
|
|
353
|
+
export const ensureDir = API.Fs.operations.ensureDir;
|
|
354
|
+
export const rename = API.Fs.operations.rename;
|
|
355
|
+
export const remove = API.Fs.operations.remove;
|
|
356
|
+
export const realpath = API.Fs.operations.realpath;
|
|
223
357
|
export const fetch = API.Fetch.operations.fetch;
|
|
224
358
|
export const env = API.Env.operations.env;
|
|
225
359
|
export const cwd = API.Env.operations.cwd;
|
|
226
360
|
export const platform = API.Env.operations.platform;
|
|
227
|
-
export const
|
|
361
|
+
export const command = API.Env.operations.command;
|
|
362
|
+
export const compile = API.Env.operations.compile;
|
package/esm/mod.js
CHANGED
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Six domain APIs:
|
|
9
9
|
* - `API.Process` — subprocess execution (`exec`)
|
|
10
|
-
* - `API.Fs` — filesystem (`readTextFile`, `stat`, `glob
|
|
10
|
+
* - `API.Fs` — filesystem (`readTextFile`, `writeTextFile`, `stat`, `glob`,
|
|
11
|
+
* `realpath`, `ensureDir`, `rename`, `remove`)
|
|
11
12
|
* - `API.Fetch` — HTTP requests (`fetch`)
|
|
12
|
-
* - `API.Env` —
|
|
13
|
-
*
|
|
13
|
+
* - `API.Env` — the host: variables, platform info, the command that invokes
|
|
14
|
+
* this xmd, and eval-block compilation
|
|
15
|
+
* (`cwd`, `env`, `platform`, `command`, `compile`)
|
|
14
16
|
* - `Config` — shared execution config (`timeout`)
|
|
15
17
|
*
|
|
16
18
|
* See `apis.ts` for architecture rationale.
|
|
17
19
|
* See `@executablemd/runtime/test` for composable test stubs.
|
|
18
20
|
*/
|
|
19
21
|
export { API } from "./apis.js";
|
|
20
|
-
export { exec, readTextFile, stat, glob, fetch, cwd, env, platform, compile } from "./apis.js";
|
|
22
|
+
export { exec, readTextFile, writeTextFile, stat, glob, realpath, ensureDir, rename, remove, fetch, cwd, env, platform, command, compile, } from "./apis.js";
|
|
21
23
|
export { findFreePort } from "./find-free-port.js";
|
|
22
24
|
export { Config, timeout } from "./config.js";
|
package/esm/test/stubs.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* yield* useStubFs({ "doc.md": "# Hello\n" });
|
|
15
15
|
* yield* useEchoExec();
|
|
16
16
|
*
|
|
17
|
-
* const execution = yield* execute({
|
|
17
|
+
* const execution = yield* execute({ path: "doc.md", stream });
|
|
18
18
|
* const output = yield* execution;
|
|
19
19
|
* });
|
|
20
20
|
* ```
|
|
@@ -26,6 +26,9 @@ import { API } from "../apis.js";
|
|
|
26
26
|
* - `readTextFile` returns content from the `files` map; throws ENOENT for missing keys.
|
|
27
27
|
* - `stat` returns `{ exists: true, isFile: true }` for keys in the map.
|
|
28
28
|
* - `glob` throws (not stubbed). Install `API.Fs.around()` directly if needed.
|
|
29
|
+
* - the writing half — `writeTextFile`, `ensureDir`, `rename`, `remove`, and
|
|
30
|
+
* `realpath` — is not stubbed and reaches the real filesystem. A test that
|
|
31
|
+
* exercises a document writing files wants a real temporary directory.
|
|
29
32
|
*
|
|
30
33
|
* The `files` object is captured **by reference** — mutating it between
|
|
31
34
|
* operations changes what `readTextFile`/`stat` see. This is useful for
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@executablemd/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Runtime host APIs for executable.md documents.",
|
|
5
5
|
"homepage": "https://executable.md",
|
|
6
6
|
"repository": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@effectionx/fs": "0.3.0",
|
|
35
35
|
"@effectionx/node": "0.2.4",
|
|
36
36
|
"@effectionx/process": "0.8.1",
|
|
37
|
-
"effection": "4.1.0-alpha.
|
|
37
|
+
"effection": "4.1.0-alpha.10"
|
|
38
38
|
},
|
|
39
39
|
"_generatedBy": "dnt@dev"
|
|
40
40
|
}
|
package/types/apis.d.ts
CHANGED
|
@@ -29,13 +29,18 @@
|
|
|
29
29
|
*
|
|
30
30
|
* - **Process** — subprocess lifecycle has its own cancellation semantics
|
|
31
31
|
* (killing processes on scope teardown). Middleware targets exec only.
|
|
32
|
-
* - **Fs** —
|
|
33
|
-
* used together for component resolution
|
|
32
|
+
* - **Fs** — reading, writing, and inspecting files form a cohesive file-IO
|
|
33
|
+
* surface used together for component resolution, replay guards, and the
|
|
34
|
+
* `<File>` component. Middleware installed here sees a document's own file
|
|
35
|
+
* access on the same terms as the engine's.
|
|
34
36
|
* - **Fetch** — HTTP has distinct timeout/body/abort semantics. Merging
|
|
35
37
|
* with Fs or Process would blur cancellation boundaries.
|
|
36
|
-
* - **Env** —
|
|
37
|
-
*
|
|
38
|
-
*
|
|
38
|
+
* - **Env** — the host itself: metadata (env vars, platform) plus the two
|
|
39
|
+
* capabilities only the entrypoint can supply, `command` (how to re-invoke
|
|
40
|
+
* this xmd) and `compile` (how this host loads a generated module). Tests
|
|
41
|
+
* use `.around()` to mock platform/env for deterministic replay; an
|
|
42
|
+
* entrypoint installs its `command` and `compile` with `{ at: "min" }` so
|
|
43
|
+
* ordinary middleware can wrap them.
|
|
39
44
|
*
|
|
40
45
|
* ## Middleware
|
|
41
46
|
*
|
|
@@ -105,6 +110,22 @@ interface ProcessHandler {
|
|
|
105
110
|
interface FsHandler {
|
|
106
111
|
readTextFile(path: string): Operation<string>;
|
|
107
112
|
stat(path: string): Operation<StatResult>;
|
|
113
|
+
/**
|
|
114
|
+
* Files and symbolic links beneath `root` whose path relative to it matches
|
|
115
|
+
* `patterns` and matches none of `exclude`. Paths come back relative and
|
|
116
|
+
* POSIX-separated, which is what both pattern lists are matched against, so a
|
|
117
|
+
* caller's patterns mean the same thing on every platform.
|
|
118
|
+
*
|
|
119
|
+
* Exclusion is per candidate: an entry is dropped when its own relative path
|
|
120
|
+
* matches. Directories are not candidates and are not reported, so an
|
|
121
|
+
* exclusion matching a directory does not remove what is beneath it — only a
|
|
122
|
+
* pattern ending in `/**`, which provably covers every descendant, lets the
|
|
123
|
+
* subtree be skipped rather than walked and filtered.
|
|
124
|
+
*
|
|
125
|
+
* Symbolic links are reported but never followed: a link's own path can
|
|
126
|
+
* match, and a link to a directory is not descended into. Traversal
|
|
127
|
+
* therefore stays inside `root` and cannot cycle.
|
|
128
|
+
*/
|
|
108
129
|
glob(options: {
|
|
109
130
|
patterns: string[];
|
|
110
131
|
root: string;
|
|
@@ -113,6 +134,20 @@ interface FsHandler {
|
|
|
113
134
|
path: string;
|
|
114
135
|
isFile: boolean;
|
|
115
136
|
}>>;
|
|
137
|
+
writeTextFile(path: string, content: string): Operation<void>;
|
|
138
|
+
ensureDir(path: string): Operation<void>;
|
|
139
|
+
rename(from: string, to: string): Operation<void>;
|
|
140
|
+
remove(path: string, options?: {
|
|
141
|
+
recursive?: boolean;
|
|
142
|
+
force?: boolean;
|
|
143
|
+
}): Operation<void>;
|
|
144
|
+
/**
|
|
145
|
+
* The canonical path, with every symlink resolved, or `undefined` when the
|
|
146
|
+
* path does not exist. Like `stat`, "it isn't there" is an answer rather
|
|
147
|
+
* than a failure — a caller resolving a path it is about to create asks
|
|
148
|
+
* about ancestors that may legitimately be missing.
|
|
149
|
+
*/
|
|
150
|
+
realpath(path: string): Operation<string | undefined>;
|
|
116
151
|
}
|
|
117
152
|
interface FetchHandler {
|
|
118
153
|
fetch(input: string, init?: {
|
|
@@ -122,6 +157,12 @@ interface FetchHandler {
|
|
|
122
157
|
timeout?: number;
|
|
123
158
|
}): Operation<RuntimeFetchResponse>;
|
|
124
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* A compiled eval block accepts the document binding environment and returns
|
|
162
|
+
* an Operation. Current compilers implement it with generated `function*`
|
|
163
|
+
* modules, but callers do not depend on that representation.
|
|
164
|
+
*/
|
|
165
|
+
export type EvalBlock = (env: Record<string, unknown>) => Operation<unknown>;
|
|
125
166
|
interface EnvHandler {
|
|
126
167
|
cwd(): Operation<string>;
|
|
127
168
|
env(name: string): Operation<string | undefined>;
|
|
@@ -129,26 +170,30 @@ interface EnvHandler {
|
|
|
129
170
|
os: string;
|
|
130
171
|
arch: string;
|
|
131
172
|
}>;
|
|
132
|
-
|
|
133
|
-
interface CompilerHandler {
|
|
173
|
+
command(args?: string[]): Operation<string[]>;
|
|
134
174
|
compile(source: string, options?: {
|
|
135
175
|
imports: string[];
|
|
136
|
-
}): Operation<
|
|
176
|
+
}): Operation<EvalBlock>;
|
|
137
177
|
}
|
|
138
178
|
export declare const API: {
|
|
139
179
|
Process: Api<ProcessHandler>;
|
|
140
180
|
Fs: Api<FsHandler>;
|
|
141
181
|
Fetch: Api<FetchHandler>;
|
|
142
182
|
Env: Api<EnvHandler>;
|
|
143
|
-
Compiler: Api<CompilerHandler>;
|
|
144
183
|
};
|
|
145
184
|
export declare const exec: typeof API.Process.operations.exec;
|
|
146
185
|
export declare const readTextFile: typeof API.Fs.operations.readTextFile;
|
|
147
186
|
export declare const stat: typeof API.Fs.operations.stat;
|
|
148
187
|
export declare const glob: typeof API.Fs.operations.glob;
|
|
188
|
+
export declare const writeTextFile: typeof API.Fs.operations.writeTextFile;
|
|
189
|
+
export declare const ensureDir: typeof API.Fs.operations.ensureDir;
|
|
190
|
+
export declare const rename: typeof API.Fs.operations.rename;
|
|
191
|
+
export declare const remove: typeof API.Fs.operations.remove;
|
|
192
|
+
export declare const realpath: typeof API.Fs.operations.realpath;
|
|
149
193
|
export declare const fetch: typeof API.Fetch.operations.fetch;
|
|
150
194
|
export declare const env: typeof API.Env.operations.env;
|
|
151
195
|
export declare const cwd: typeof API.Env.operations.cwd;
|
|
152
196
|
export declare const platform: typeof API.Env.operations.platform;
|
|
153
|
-
export declare const
|
|
197
|
+
export declare const command: typeof API.Env.operations.command;
|
|
198
|
+
export declare const compile: typeof API.Env.operations.compile;
|
|
154
199
|
export {};
|
package/types/mod.d.ts
CHANGED
|
@@ -7,18 +7,20 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Six domain APIs:
|
|
9
9
|
* - `API.Process` — subprocess execution (`exec`)
|
|
10
|
-
* - `API.Fs` — filesystem (`readTextFile`, `stat`, `glob
|
|
10
|
+
* - `API.Fs` — filesystem (`readTextFile`, `writeTextFile`, `stat`, `glob`,
|
|
11
|
+
* `realpath`, `ensureDir`, `rename`, `remove`)
|
|
11
12
|
* - `API.Fetch` — HTTP requests (`fetch`)
|
|
12
|
-
* - `API.Env` —
|
|
13
|
-
*
|
|
13
|
+
* - `API.Env` — the host: variables, platform info, the command that invokes
|
|
14
|
+
* this xmd, and eval-block compilation
|
|
15
|
+
* (`cwd`, `env`, `platform`, `command`, `compile`)
|
|
14
16
|
* - `Config` — shared execution config (`timeout`)
|
|
15
17
|
*
|
|
16
18
|
* See `apis.ts` for architecture rationale.
|
|
17
19
|
* See `@executablemd/runtime/test` for composable test stubs.
|
|
18
20
|
*/
|
|
19
21
|
export { API } from "./apis.js";
|
|
20
|
-
export { exec, readTextFile, stat, glob, fetch, cwd, env, platform, compile } from "./apis.js";
|
|
21
|
-
export type { ResponseHeaders, RuntimeFetchResponse, StatResult } from "./apis.js";
|
|
22
|
+
export { exec, readTextFile, writeTextFile, stat, glob, realpath, ensureDir, rename, remove, fetch, cwd, env, platform, command, compile, } from "./apis.js";
|
|
23
|
+
export type { EvalBlock, ResponseHeaders, RuntimeFetchResponse, StatResult } from "./apis.js";
|
|
22
24
|
export { findFreePort } from "./find-free-port.js";
|
|
23
25
|
export { Config, timeout } from "./config.js";
|
|
24
26
|
export type { ConfigApi } from "./config.js";
|
package/types/test/stubs.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* yield* useStubFs({ "doc.md": "# Hello\n" });
|
|
15
15
|
* yield* useEchoExec();
|
|
16
16
|
*
|
|
17
|
-
* const execution = yield* execute({
|
|
17
|
+
* const execution = yield* execute({ path: "doc.md", stream });
|
|
18
18
|
* const output = yield* execution;
|
|
19
19
|
* });
|
|
20
20
|
* ```
|
|
@@ -26,6 +26,9 @@ import type { Operation } from "effection";
|
|
|
26
26
|
* - `readTextFile` returns content from the `files` map; throws ENOENT for missing keys.
|
|
27
27
|
* - `stat` returns `{ exists: true, isFile: true }` for keys in the map.
|
|
28
28
|
* - `glob` throws (not stubbed). Install `API.Fs.around()` directly if needed.
|
|
29
|
+
* - the writing half — `writeTextFile`, `ensureDir`, `rename`, `remove`, and
|
|
30
|
+
* `realpath` — is not stubbed and reaches the real filesystem. A test that
|
|
31
|
+
* exercises a document writing files wants a real temporary directory.
|
|
29
32
|
*
|
|
30
33
|
* The `files` object is captured **by reference** — mutating it between
|
|
31
34
|
* operations changes what `readTextFile`/`stat` see. This is useful for
|