omkit 0.0.3 → 0.0.6
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/dist/actions/command.d.ts +6 -0
- package/dist/actions/command.js +3 -2
- package/dist/actions/tail-log.js +7 -1
- package/dist/core/action.d.ts +16 -6
- package/dist/core/action.js +15 -8
- package/dist/core/folder-cache.d.ts +23 -20
- package/dist/core/folder-cache.js +54 -35
- package/dist/core/output.d.ts +9 -0
- package/dist/core/output.js +18 -1
- package/dist/core/spin.d.ts +6 -0
- package/dist/core/spin.js +8 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -2
- package/package.json +1 -1
|
@@ -8,6 +8,12 @@ export interface CommandOptions {
|
|
|
8
8
|
cwd?: string;
|
|
9
9
|
/** Label the child's output is logged under. Defaults to the action's name. */
|
|
10
10
|
label?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Environment for the child. Pass the full set you want (e.g.
|
|
13
|
+
* `{ ...process.env, NODE_ENV: "development" }`) — it's handed to the child
|
|
14
|
+
* as-is, not merged. Omit to inherit the parent's `process.env`.
|
|
15
|
+
*/
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
19
|
* Runs a fixed command as an action **instance** — built in a single call, the
|
package/dist/actions/command.js
CHANGED
|
@@ -22,7 +22,7 @@ export function command(name, cmdOrFile, argsOrOptions, maybeOptions) {
|
|
|
22
22
|
// against process.cwd() — exactly the "absolute from where node runs" rule.
|
|
23
23
|
const cwd = path.resolve(options.cwd ?? ".");
|
|
24
24
|
// proc sources the LogsCollector from async context and streams output into it.
|
|
25
|
-
return proc(options.label ?? name, { cwd })(verbatim(cmdOrFile));
|
|
25
|
+
return proc(options.label ?? name, { cwd, ...(options.env ? { env: options.env } : {}) })(verbatim(cmdOrFile));
|
|
26
26
|
})(); // construct the instance here, so callers get one in a single call
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -36,7 +36,8 @@ const spawnDirect = (logs, signal, name, file, args, options) => {
|
|
|
36
36
|
const cwd = path.resolve(options.cwd ?? ".");
|
|
37
37
|
const source = options.label ?? name;
|
|
38
38
|
// windowsHide keeps a stray console window from flashing for the child.
|
|
39
|
-
|
|
39
|
+
// env omitted -> spawn inherits the parent's process.env (Node's default).
|
|
40
|
+
const child = spawn(file, args, { cwd, windowsHide: true, env: options.env });
|
|
40
41
|
// Mirror proc's launch line: a filterable milestone on the timeline, with the
|
|
41
42
|
// structured args snapshotted and linked (see process.ts for the rationale).
|
|
42
43
|
const snap = captureSnapshot(`proc-${name}-start`, { cmd: file, args, cwd });
|
package/dist/actions/tail-log.js
CHANGED
|
@@ -25,7 +25,13 @@ export const tailLog = action("Tail Log").run(async ({ logs, signal }, target, o
|
|
|
25
25
|
let pending = ""; // partial trailing line, buffered until its newline
|
|
26
26
|
// Append every complete line in `text`, keeping any remainder for next time.
|
|
27
27
|
const feed = (text) => {
|
|
28
|
-
|
|
28
|
+
// Strip NUL bytes first. Log rotators (log4js/streamroller) can pre-allocate
|
|
29
|
+
// or leave a rotated file padded with \0, and a NUL run contains no newline —
|
|
30
|
+
// so without this the whole padded region buffers and flushes as one huge
|
|
31
|
+
// bogus line (the tell-tale "line of blanks/spaces"). NUL is never valid in
|
|
32
|
+
// a text log, so dropping it is safe; a NUL-only stretch collapses to "" and
|
|
33
|
+
// is skipped by the empty-line guard below.
|
|
34
|
+
pending += text.replace(/\0/g, "");
|
|
29
35
|
let newline;
|
|
30
36
|
while ((newline = pending.indexOf("\n")) !== -1) {
|
|
31
37
|
const line = pending.slice(0, newline).replace(/\r$/, "");
|
package/dist/core/action.d.ts
CHANGED
|
@@ -39,6 +39,12 @@ export interface ActionContext<Events extends object, Handle = void> extends Sys
|
|
|
39
39
|
* one (e.g. CDP) follows the same pattern over `logs`/`signal`.
|
|
40
40
|
*/
|
|
41
41
|
readonly proc: Proc;
|
|
42
|
+
/**
|
|
43
|
+
* Absolute path to this run's output folder (`logs/<name>/<date>/<time>/`) —
|
|
44
|
+
* created and shared across the run. Write action artifacts here so they land
|
|
45
|
+
* beside `run.log`; the same folder is on the `spin` context.
|
|
46
|
+
*/
|
|
47
|
+
readonly artifactsFolder: string;
|
|
42
48
|
}
|
|
43
49
|
/** A constructed-but-not-yet-run action: what calling an `Action` produces. */
|
|
44
50
|
export interface ActionInstance<Result = unknown, Events extends object = NoEvents, Handle = void> {
|
|
@@ -63,18 +69,22 @@ export interface ActionInstance<Result = unknown, Events extends object = NoEven
|
|
|
63
69
|
*/
|
|
64
70
|
once<K extends keyof InstanceEvents<Events, Result>>(key: K): Promise<InstanceEvents<Events, Result>[K]>;
|
|
65
71
|
/**
|
|
66
|
-
* Gate this action on
|
|
67
|
-
*
|
|
68
|
-
*
|
|
72
|
+
* Gate this action on its **inputs** — the files/folders it *reads* (its
|
|
73
|
+
* sources), **not** what it produces. `paths` (each a file or a folder) are
|
|
74
|
+
* fingerprinted by mtime+size; the returned instance skips running when those
|
|
75
|
+
* inputs are unchanged since the last successful run, and otherwise runs this
|
|
76
|
+
* action and records the fingerprint. Pass inputs, not build outputs: caching
|
|
77
|
+
* on an output would skip the very run that produces it.
|
|
78
|
+
*
|
|
69
79
|
* Every path must be **absolute** (throws otherwise). A cache hit resolves
|
|
70
80
|
* immediately as success (result `undefined`) and logs a `cached, skipping`
|
|
71
81
|
* line; a failure of the inner action records nothing, so it re-runs next time.
|
|
72
82
|
*
|
|
73
83
|
* Because `nod`/`run` launch an instance the moment they receive it, chain
|
|
74
|
-
* `.withCache` *before* handing the instance in:
|
|
75
|
-
* `nod(command("build", "
|
|
84
|
+
* `.withCache` *before* handing the instance in — passing the source dir:
|
|
85
|
+
* `nod(command("build", "npm run build").withCache(srcAbs))`.
|
|
76
86
|
*/
|
|
77
|
-
withCache(...
|
|
87
|
+
withCache(...paths: string[]): AnyActionInstance;
|
|
78
88
|
}
|
|
79
89
|
/**
|
|
80
90
|
* Any constructed instance, whatever its result, events, or handle. The launch
|
package/dist/core/action.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { events } from "./events.js";
|
|
2
2
|
import { createProc } from "./process.js";
|
|
3
|
-
import { fingerprint, read, write,
|
|
3
|
+
import { fingerprint, read, write, resolvePaths } from "./folder-cache.js";
|
|
4
|
+
import { artifactsFolder } from "./output.js";
|
|
4
5
|
/** A promise paired with its settlers, for the set-once handle. */
|
|
5
6
|
const deferred = () => {
|
|
6
7
|
let resolve;
|
|
@@ -82,7 +83,13 @@ const build = (name, exec) => {
|
|
|
82
83
|
// `Promise.resolve().then` normalizes a sync result and routes a sync
|
|
83
84
|
// throw to the `error` path, so exec may be sync or async.
|
|
84
85
|
Promise.resolve()
|
|
85
|
-
.then(() => exec({
|
|
86
|
+
.then(() => exec({
|
|
87
|
+
...system,
|
|
88
|
+
emit,
|
|
89
|
+
attach,
|
|
90
|
+
proc: createProc(system.logs, system.signal),
|
|
91
|
+
artifactsFolder: artifactsFolder(),
|
|
92
|
+
}, ...args))
|
|
86
93
|
.then((result) => {
|
|
87
94
|
systemEmit("done", result);
|
|
88
95
|
// Settle `ref` if the action finished without attaching, so awaiters
|
|
@@ -105,23 +112,23 @@ const build = (name, exec) => {
|
|
|
105
112
|
return awaitEvent(emitter, name, key);
|
|
106
113
|
},
|
|
107
114
|
// Wrap this instance in a fresh one (reusing the engine for its own
|
|
108
|
-
// emitter/ref/on/once) that fingerprints `
|
|
115
|
+
// emitter/ref/on/once) that fingerprints `paths` and either skips or
|
|
109
116
|
// runs+records. The wrapper keeps this action's `name`, so log lines and
|
|
110
117
|
// the `launch <name>` line are unchanged. It forwards its injected
|
|
111
118
|
// logs/signal straight into the inner action's `start`.
|
|
112
|
-
withCache(...
|
|
119
|
+
withCache(...paths) {
|
|
113
120
|
// Validate + canonicalize eagerly here (at the call site), not lazily
|
|
114
121
|
// inside the run — a relative path fails fast where it's written, and
|
|
115
122
|
// the resolved paths give a stable cache key regardless of spelling.
|
|
116
|
-
const
|
|
123
|
+
const targets = resolvePaths(paths);
|
|
117
124
|
return action(name).run(async (ctx) => {
|
|
118
|
-
const fp = await fingerprint(
|
|
119
|
-
if ((await read(
|
|
125
|
+
const fp = await fingerprint(targets);
|
|
126
|
+
if ((await read(targets)) === fp) {
|
|
120
127
|
ctx.logs.append({ source: name, level: "info", message: "cached, skipping" });
|
|
121
128
|
return undefined;
|
|
122
129
|
}
|
|
123
130
|
const result = await instance.start({ logs: ctx.logs, signal: ctx.signal });
|
|
124
|
-
await write(
|
|
131
|
+
await write(targets, fp); // only on success — a throw above skips this
|
|
125
132
|
return result;
|
|
126
133
|
})();
|
|
127
134
|
},
|
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* method on action instances.
|
|
2
|
+
* An input-fingerprint cache: hash the mtime+size of one or more **input**
|
|
3
|
+
* targets (the files/folders an action reads — *not* what it produces), so an
|
|
4
|
+
* expensive action can be skipped when its inputs are unchanged since its last
|
|
5
|
+
* successful run. Backs the `.withCache(...)` method on action instances.
|
|
6
6
|
*
|
|
7
7
|
* Entries live under `node_modules/.cache/omkit/` (conventional, gitignored) —
|
|
8
|
-
* one JSON file per set of
|
|
8
|
+
* one JSON file per set of inputs, named by a hash of their absolute paths.
|
|
9
9
|
*/
|
|
10
10
|
/** Where cache entries are written — `node_modules/.cache/omkit/` under the cwd. */
|
|
11
11
|
export declare const cacheDir: string;
|
|
12
12
|
/**
|
|
13
|
-
* Require every path to be **absolute** (a relative
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Require every path to be **absolute** (a relative path is a developer error —
|
|
14
|
+
* throws, naming it) and return them canonicalized with `resolve`, so different
|
|
15
|
+
* spellings of the same target (`.`/`..`/trailing slash) collapse to one stable
|
|
16
|
+
* fingerprint prefix and cache key. `resolve` on an already-absolute path only
|
|
17
|
+
* normalizes — it never falls back to the cwd. Each target may be a file or a
|
|
18
|
+
* folder (see {@link fingerprint}).
|
|
18
19
|
*/
|
|
19
|
-
export declare const
|
|
20
|
+
export declare const resolvePaths: (paths: string[]) => string[];
|
|
20
21
|
/**
|
|
21
|
-
* Fingerprints one or more
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Fingerprints one or more **input** targets by mtime+size. Each target may be
|
|
23
|
+
* a **file** (fingerprinted directly) or a **folder** (walked recursively, its
|
|
24
|
+
* files fingerprinted). Every path must be absolute (throws otherwise). A
|
|
25
|
+
* missing target contributes nothing — so an input that doesn't exist yet is
|
|
26
|
+
* naturally a miss, not an error. The entry list is sorted for determinism
|
|
27
|
+
* before hashing.
|
|
25
28
|
*/
|
|
26
|
-
export declare const fingerprint: (
|
|
29
|
+
export declare const fingerprint: (paths: string[]) => Promise<string>;
|
|
27
30
|
/**
|
|
28
|
-
* Reads the fingerprint stored for these
|
|
31
|
+
* Reads the fingerprint stored for these targets, or `undefined` when there's
|
|
29
32
|
* no cache (absent, unreadable, or malformed — all treated as a miss).
|
|
30
33
|
*/
|
|
31
|
-
export declare const read: (
|
|
34
|
+
export declare const read: (paths: string[]) => Promise<string | undefined>;
|
|
32
35
|
/**
|
|
33
|
-
* Records the fingerprint for these
|
|
36
|
+
* Records the fingerprint for these targets after a successful run. Writes
|
|
34
37
|
* atomically (temp file + rename) so a concurrent read never sees a half-file.
|
|
35
38
|
*/
|
|
36
|
-
export declare const write: (
|
|
39
|
+
export declare const write: (paths: string[], fp: string) => Promise<void>;
|
|
@@ -2,52 +2,71 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* method on action instances.
|
|
5
|
+
* An input-fingerprint cache: hash the mtime+size of one or more **input**
|
|
6
|
+
* targets (the files/folders an action reads — *not* what it produces), so an
|
|
7
|
+
* expensive action can be skipped when its inputs are unchanged since its last
|
|
8
|
+
* successful run. Backs the `.withCache(...)` method on action instances.
|
|
9
9
|
*
|
|
10
10
|
* Entries live under `node_modules/.cache/omkit/` (conventional, gitignored) —
|
|
11
|
-
* one JSON file per set of
|
|
11
|
+
* one JSON file per set of inputs, named by a hash of their absolute paths.
|
|
12
12
|
*/
|
|
13
13
|
/** Where cache entries are written — `node_modules/.cache/omkit/` under the cwd. */
|
|
14
14
|
export const cacheDir = join(process.cwd(), "node_modules", ".cache", "omkit");
|
|
15
15
|
/**
|
|
16
|
-
* Require every path to be **absolute** (a relative
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* Require every path to be **absolute** (a relative path is a developer error —
|
|
17
|
+
* throws, naming it) and return them canonicalized with `resolve`, so different
|
|
18
|
+
* spellings of the same target (`.`/`..`/trailing slash) collapse to one stable
|
|
19
|
+
* fingerprint prefix and cache key. `resolve` on an already-absolute path only
|
|
20
|
+
* normalizes — it never falls back to the cwd. Each target may be a file or a
|
|
21
|
+
* folder (see {@link fingerprint}).
|
|
21
22
|
*/
|
|
22
|
-
export const
|
|
23
|
-
if (!isAbsolute(
|
|
24
|
-
throw new Error(`withCache:
|
|
23
|
+
export const resolvePaths = (paths) => paths.map((p) => {
|
|
24
|
+
if (!isAbsolute(p)) {
|
|
25
|
+
throw new Error(`withCache: path must be absolute, got "${p}"`);
|
|
25
26
|
}
|
|
26
|
-
return resolve(
|
|
27
|
+
return resolve(p);
|
|
27
28
|
});
|
|
28
29
|
/**
|
|
29
|
-
* Fingerprints one or more
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* Fingerprints one or more **input** targets by mtime+size. Each target may be
|
|
31
|
+
* a **file** (fingerprinted directly) or a **folder** (walked recursively, its
|
|
32
|
+
* files fingerprinted). Every path must be absolute (throws otherwise). A
|
|
33
|
+
* missing target contributes nothing — so an input that doesn't exist yet is
|
|
34
|
+
* naturally a miss, not an error. The entry list is sorted for determinism
|
|
35
|
+
* before hashing.
|
|
33
36
|
*/
|
|
34
|
-
export const fingerprint = async (
|
|
35
|
-
const
|
|
37
|
+
export const fingerprint = async (paths) => {
|
|
38
|
+
const targets = resolvePaths(paths);
|
|
36
39
|
const entries = [];
|
|
37
|
-
|
|
40
|
+
const record = (target, rel, mtimeMs, size) => {
|
|
41
|
+
entries.push(`${target}\0${rel}\0${mtimeMs}\0${size}`);
|
|
42
|
+
};
|
|
43
|
+
for (const target of targets) {
|
|
44
|
+
let root;
|
|
45
|
+
try {
|
|
46
|
+
root = await stat(target);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
continue; // missing/unreadable — contributes no entries
|
|
50
|
+
}
|
|
51
|
+
// A file target fingerprints itself (empty rel — there's no walk).
|
|
52
|
+
if (root.isFile()) {
|
|
53
|
+
record(target, "", root.mtimeMs, root.size);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// A folder target: walk it and fingerprint every file inside.
|
|
38
57
|
let rels;
|
|
39
58
|
try {
|
|
40
|
-
rels = await readdir(
|
|
59
|
+
rels = await readdir(target, { recursive: true });
|
|
41
60
|
}
|
|
42
61
|
catch {
|
|
43
|
-
continue;
|
|
62
|
+
continue;
|
|
44
63
|
}
|
|
45
64
|
for (const rel of rels) {
|
|
46
|
-
const abs = join(
|
|
65
|
+
const abs = join(target, rel);
|
|
47
66
|
try {
|
|
48
67
|
const stats = await stat(abs);
|
|
49
68
|
if (stats.isFile())
|
|
50
|
-
|
|
69
|
+
record(target, rel, stats.mtimeMs, stats.size);
|
|
51
70
|
}
|
|
52
71
|
catch {
|
|
53
72
|
// entry vanished between readdir and stat — skip
|
|
@@ -57,18 +76,18 @@ export const fingerprint = async (folders) => {
|
|
|
57
76
|
entries.sort();
|
|
58
77
|
return createHash("sha1").update(entries.join("\n")).digest("hex");
|
|
59
78
|
};
|
|
60
|
-
/** The on-disk filename for a set of
|
|
61
|
-
const entryPath = (
|
|
62
|
-
const key = createHash("sha1").update(
|
|
79
|
+
/** The on-disk filename for a set of targets — a hash of their absolute paths. */
|
|
80
|
+
const entryPath = (paths) => {
|
|
81
|
+
const key = createHash("sha1").update(resolvePaths(paths).sort().join("\0")).digest("hex");
|
|
63
82
|
return join(cacheDir, `${key}.json`);
|
|
64
83
|
};
|
|
65
84
|
/**
|
|
66
|
-
* Reads the fingerprint stored for these
|
|
85
|
+
* Reads the fingerprint stored for these targets, or `undefined` when there's
|
|
67
86
|
* no cache (absent, unreadable, or malformed — all treated as a miss).
|
|
68
87
|
*/
|
|
69
|
-
export const read = async (
|
|
88
|
+
export const read = async (paths) => {
|
|
70
89
|
try {
|
|
71
|
-
const raw = await readFile(entryPath(
|
|
90
|
+
const raw = await readFile(entryPath(paths), "utf8");
|
|
72
91
|
const entry = JSON.parse(raw);
|
|
73
92
|
return typeof entry.fingerprint === "string" ? entry.fingerprint : undefined;
|
|
74
93
|
}
|
|
@@ -77,13 +96,13 @@ export const read = async (folders) => {
|
|
|
77
96
|
}
|
|
78
97
|
};
|
|
79
98
|
/**
|
|
80
|
-
* Records the fingerprint for these
|
|
99
|
+
* Records the fingerprint for these targets after a successful run. Writes
|
|
81
100
|
* atomically (temp file + rename) so a concurrent read never sees a half-file.
|
|
82
101
|
*/
|
|
83
|
-
export const write = async (
|
|
102
|
+
export const write = async (paths, fp) => {
|
|
84
103
|
await mkdir(cacheDir, { recursive: true });
|
|
85
|
-
const target = entryPath(
|
|
86
|
-
const entry = { folders, fingerprint: fp, ts: Date.now() };
|
|
104
|
+
const target = entryPath(paths);
|
|
105
|
+
const entry = { folders: paths, fingerprint: fp, ts: Date.now() };
|
|
87
106
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
88
107
|
await writeFile(tmp, JSON.stringify(entry, null, 2));
|
|
89
108
|
await rename(tmp, target);
|
package/dist/core/output.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { LogsCollector } from "./log-collector/LogsCollector.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Absolute path to this run's output folder — `logs/<name>/<date>/<time>/`, where
|
|
4
|
+
* `run.log`, `run.jsonl`, and snapshots live. Exposed so actions can drop their
|
|
5
|
+
* own **artifacts** into the same per-run folder (one folder per run; one run per
|
|
6
|
+
* process). The folder is created on first access — synchronously, so a caller
|
|
7
|
+
* can write into it in the same tick — and the path is absolute so it's safe to
|
|
8
|
+
* hand to a child process with a different cwd.
|
|
9
|
+
*/
|
|
10
|
+
export declare const artifactsFolder: () => string;
|
|
2
11
|
/**
|
|
3
12
|
* Drains the collected log to two files in the run's output folder (the common
|
|
4
13
|
* `logs/<name>/<date>/<time>/` convention, local time):
|
package/dist/core/output.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createWriteStream } from "node:fs";
|
|
1
|
+
import { createWriteStream, mkdirSync } from "node:fs";
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { log } from "./log-collector/global.js";
|
|
@@ -24,6 +24,23 @@ const runDir = () => {
|
|
|
24
24
|
};
|
|
25
25
|
let made;
|
|
26
26
|
const ensureDir = () => (made ??= mkdir(runDir(), { recursive: true }).then(() => { }));
|
|
27
|
+
let ensuredSync = false;
|
|
28
|
+
/**
|
|
29
|
+
* Absolute path to this run's output folder — `logs/<name>/<date>/<time>/`, where
|
|
30
|
+
* `run.log`, `run.jsonl`, and snapshots live. Exposed so actions can drop their
|
|
31
|
+
* own **artifacts** into the same per-run folder (one folder per run; one run per
|
|
32
|
+
* process). The folder is created on first access — synchronously, so a caller
|
|
33
|
+
* can write into it in the same tick — and the path is absolute so it's safe to
|
|
34
|
+
* hand to a child process with a different cwd.
|
|
35
|
+
*/
|
|
36
|
+
export const artifactsFolder = () => {
|
|
37
|
+
const abs = path.resolve(runDir());
|
|
38
|
+
if (!ensuredSync) {
|
|
39
|
+
mkdirSync(abs, { recursive: true });
|
|
40
|
+
ensuredSync = true;
|
|
41
|
+
}
|
|
42
|
+
return abs;
|
|
43
|
+
};
|
|
27
44
|
/** A compact header block for the top of run.log — legend + run identity. */
|
|
28
45
|
const logHeader = () => {
|
|
29
46
|
const now = new Date();
|
package/dist/core/spin.d.ts
CHANGED
|
@@ -32,6 +32,12 @@ export interface SpinContext {
|
|
|
32
32
|
readonly cancel: Run["cancel"];
|
|
33
33
|
/** The run's abort signal — honor it to stop early. */
|
|
34
34
|
readonly signal: AbortSignal;
|
|
35
|
+
/**
|
|
36
|
+
* Absolute path to this run's output folder (`logs/<name>/<date>/<time>/`),
|
|
37
|
+
* created up front. Hand it to actions (or use `ctx.artifactsFolder` inside
|
|
38
|
+
* one) to write artifacts beside `run.log`.
|
|
39
|
+
*/
|
|
40
|
+
readonly artifactsFolder: string;
|
|
35
41
|
}
|
|
36
42
|
/** The body of a {@link spin}: any (async) function — it isn't named or wrapped by you. */
|
|
37
43
|
export type SpinBody = (ctx: SpinContext) => void | Promise<void>;
|
package/dist/core/spin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { action } from "./action.js";
|
|
2
2
|
import { run } from "./run.js";
|
|
3
|
+
import { artifactsFolder } from "./output.js";
|
|
3
4
|
export function spin(a, b) {
|
|
4
5
|
const [options, body] = typeof a === "function" ? [{}, a] : [a, b];
|
|
5
6
|
const { drain: autoDrain = true, ...runOptions } = options;
|
|
@@ -20,7 +21,13 @@ export function spin(a, b) {
|
|
|
20
21
|
host.run(instance);
|
|
21
22
|
return instance;
|
|
22
23
|
};
|
|
23
|
-
await body({
|
|
24
|
+
await body({
|
|
25
|
+
nod,
|
|
26
|
+
drain: startDrain,
|
|
27
|
+
cancel: () => host.cancel(),
|
|
28
|
+
signal,
|
|
29
|
+
artifactsFolder: artifactsFolder(),
|
|
30
|
+
});
|
|
24
31
|
});
|
|
25
32
|
host = run(runOptions, hosted());
|
|
26
33
|
if (autoDrain)
|
package/dist/index.d.ts
CHANGED
|
@@ -23,4 +23,5 @@ export type { Logger, LogEntry, LogInput, SubscribeOptions, } from "./core/log-c
|
|
|
23
23
|
export { log } from "./core/log-collector/global.ts";
|
|
24
24
|
export { snapshot, captureSnapshot } from "./core/output.ts";
|
|
25
25
|
export type { SnapshotRef } from "./core/output.ts";
|
|
26
|
+
export { artifactsFolder } from "./core/output.ts";
|
|
26
27
|
export { fingerprint, cacheDir } from "./core/folder-cache.ts";
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,12 @@ export { LogsCollector } from "./core/log-collector/LogsCollector.js";
|
|
|
23
23
|
export { log } from "./core/log-collector/global.js";
|
|
24
24
|
// ── Snapshots ──────────────────────────────────────────────────────────────
|
|
25
25
|
export { snapshot, captureSnapshot } from "./core/output.js";
|
|
26
|
+
// ── Artifacts ────────────────────────────────────────────────────────────────
|
|
27
|
+
// This run's output folder (absolute) — also on the `spin`/action `ctx`. Import
|
|
28
|
+
// it directly for artifact-writing helpers that aren't inside an action.
|
|
29
|
+
export { artifactsFolder } from "./core/output.js";
|
|
26
30
|
// ── Folder cache ─────────────────────────────────────────────────────────────
|
|
27
|
-
// Backs the `.withCache(...
|
|
28
|
-
//
|
|
31
|
+
// Backs the `.withCache(...inputs)` method on instances (fingerprints an
|
|
32
|
+
// action's input files/folders, not its outputs); exported for scripts that
|
|
33
|
+
// want to inspect or manually invalidate the cache.
|
|
29
34
|
export { fingerprint, cacheDir } from "./core/folder-cache.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omkit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A tiny runtime for developer workflows: typed actions that pass runtime capabilities and emit typed events, under one run and one shared log. The operational/runtime layer to tskb's knowledge layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|