@gjermundgaraba/clankerbox-sdk 0.16.0 → 0.18.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/dist/{client-DgDb-TR7.d.mts → client-8af586hb.d.mts} +292 -243
- package/dist/{client-CLemHDCl.mjs → client-CbMWwHdT.mjs} +226 -165
- package/dist/index.d.mts +22 -14
- package/dist/index.mjs +53 -45
- package/dist/node.d.mts +6 -2
- package/dist/node.mjs +3 -3
- package/package.json +5 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as ConflictKind, B as Refused, C as Declared, D as Capacity, E as readTimeout, F as NotFound, G as hostErrors, H as Unavailable, I as NotMade, K as retryable, L as NotOffered, M as Internal, N as Invalid, O as ClankerboxError, P as Missing, R as NotReady, S as stateLabel, T as actionsByName, U as Unsizable, V as ScriptFailed, W as WrongState, _ as Setup, b as Usage, c as ActionRecord, d as CreateRequest, f as Host, g as Runtime, h as Reading, i as Interface, j as HostError, k as Conflict, l as ActionStatus, m as MachineSpec, n as CreateOptions$1, o as client_d_exports, p as Machine, s as ActionName, u as Checkpoint, v as SshEndpoint, w as Http, x as isReady, y as SshLogin, z as PinMismatch } from "./client-8af586hb.mjs";
|
|
2
2
|
import { Effect, FileSystem, Path, PlatformError, Schema, Scope } from "effect";
|
|
3
3
|
//#region src/ids.d.ts
|
|
4
4
|
export declare const HostId: Schema.String;
|
|
@@ -46,11 +46,14 @@ interface Built {
|
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Builds the spec's prebuild on the host a create of it is placed on, with a machine of its own
|
|
49
|
-
* that it deletes however it ends. A prebuild that is there and done is the answer
|
|
50
|
-
* failed
|
|
51
|
-
*
|
|
49
|
+
* that it deletes however it ends. A prebuild that is there and done is the answer, one that
|
|
50
|
+
* failed is deleted and built again, and one still running is `Conflict{busy}`, as its capture
|
|
51
|
+
* holds it. Of two builds of one spec at once, the second's capture is `Conflict`, and neither
|
|
52
|
+
* touches the other's machine.
|
|
52
53
|
*/
|
|
53
|
-
declare const build: (client: Interface, spec: MachineSpec
|
|
54
|
+
declare const build: (client: Interface, spec: MachineSpec & {
|
|
55
|
+
readonly setup: Setup;
|
|
56
|
+
}, options?: CreateOptions$1) => Effect.Effect<Built, ClankerboxError>;
|
|
54
57
|
//#endregion
|
|
55
58
|
//#region src/release.d.ts
|
|
56
59
|
/** Also the version of the clankerbox binaries it ships with. */
|
|
@@ -120,10 +123,11 @@ export interface SshTarget {
|
|
|
120
123
|
readonly login: SshLogin;
|
|
121
124
|
readonly hostKey: string;
|
|
122
125
|
}
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Only a running machine has an endpoint, and only a prepared one a host key; a machine without
|
|
128
|
+
* either is `WrongState`, naming what it lacks and why.
|
|
129
|
+
*/
|
|
130
|
+
export declare const sshTarget: (machine: Machine) => Effect.Effect<SshTarget, WrongState>;
|
|
127
131
|
/**
|
|
128
132
|
* ssh's arguments, pinned to the target's host key through a known-hosts file that lasts as long
|
|
129
133
|
* as the scope, which must outlast the ssh. Ports change on fork, restore, and every boat start,
|
|
@@ -131,10 +135,7 @@ export declare const sshTarget: (machine: Machine) => SshTarget | {
|
|
|
131
135
|
*/
|
|
132
136
|
export declare const pinnedSsh: (target: SshTarget, user: string, extra: ReadonlyArray<string>) => Effect.Effect<Array<string>, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | Scope.Scope>;
|
|
133
137
|
//#endregion
|
|
134
|
-
//#region src/
|
|
135
|
-
export declare const mibPerGib = 1024;
|
|
136
|
-
/** Exactly, as a size flag takes it: `32G`, `1.5G`, `512M`, `1500M`. */
|
|
137
|
-
export declare const formatMib: (mib: number) => string;
|
|
138
|
+
//#region src/describe.d.ts
|
|
138
139
|
/** On one line: `cpu: missing; ram: not a known field`. `names` renames top-level fields. */
|
|
139
140
|
export declare const describeIssues: (error: Schema.SchemaError, names?: ReadonlyMap<PropertyKey, string>) => string;
|
|
140
141
|
/**
|
|
@@ -143,4 +144,11 @@ export declare const describeIssues: (error: Schema.SchemaError, names?: Readonl
|
|
|
143
144
|
*/
|
|
144
145
|
export declare const describeFileError: ({ reason }: PlatformError.PlatformError) => string;
|
|
145
146
|
//#endregion
|
|
146
|
-
|
|
147
|
+
//#region src/sizes.d.ts
|
|
148
|
+
export declare const mibPerGib = 1024;
|
|
149
|
+
/** Exactly, as a size flag takes it: `32G`, `1.5G`, `512M`, `1500M`. */
|
|
150
|
+
export declare const formatMib: (mib: number) => string;
|
|
151
|
+
/** As a size flag takes it: `60G`. */
|
|
152
|
+
export declare const formatGib: (gib: number) => string;
|
|
153
|
+
//#endregion
|
|
154
|
+
export { ActionName, ActionRecord, ActionStatus, Capacity, Checkpoint, ClankerboxError, client_d_exports as Client, Conflict, ConflictKind, CreateRequest, Declared, Host, HostError, Http, Internal, Invalid, Machine, MachineSpec, Missing, NotFound, NotMade, NotOffered, NotReady, PinMismatch, prebuild_d_exports as Prebuild, Reading, Refused, Runtime, ScriptFailed, Setup, SshEndpoint, SshLogin, Unavailable, Unsizable, Usage, WrongState, actionsByName, hostErrors, isReady, readTimeout, retryable, stateLabel };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as parseId, B as NotFound, C as Usage, D as Id, E as HostId, F as ConflictKind, G as Refused, H as NotOffered, I as HostError, J as Unsizable, K as ScriptFailed, L as Internal, M as Capacity, N as ClankerboxError, O as Name, P as Conflict, R as Invalid, S as SshLogin, T as stateLabel, U as NotReady, V as NotMade, W as PinMismatch, X as hostErrors, Y as WrongState, Z as retryable, _ as Machine, a as release, b as Setup, c as Http, d as ActionName, f as ActionRecord, g as Host, h as CreateRequest, i as describeIssues, j as parseName, k as formatId, l as actionsByName, m as Checkpoint, o as version, p as ActionStatus, q as Unavailable, r as describeFileError, s as versionHeader, t as client_exports, u as readTimeout, v as MachineSpec, w as isReady, x as SshEndpoint, y as Runtime, z as Missing } from "./client-CbMWwHdT.mjs";
|
|
3
3
|
import { Effect, FileSystem, Option, Path, Predicate, Record, Schema } from "effect";
|
|
4
|
-
import { Base64 } from "effect/encoding";
|
|
4
|
+
import { Base64, Hex } from "effect/encoding";
|
|
5
5
|
//#region src/prebuild.ts
|
|
6
6
|
var prebuild_exports = /* @__PURE__ */ __exportAll({
|
|
7
7
|
build: () => build,
|
|
@@ -10,12 +10,7 @@ var prebuild_exports = /* @__PURE__ */ __exportAll({
|
|
|
10
10
|
});
|
|
11
11
|
const prefix = "prebuilt-";
|
|
12
12
|
const digestDigits = 12;
|
|
13
|
-
/**
|
|
14
|
-
* 64 bits, so no two builds draw one name. With `prebuilding-` it is 28 characters, inside the 29
|
|
15
|
-
* a 32-character host ID and its `_` leave of an ID's 62.
|
|
16
|
-
*/
|
|
17
13
|
const builderBytes = 8;
|
|
18
|
-
const hex = (bytes) => Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
19
14
|
/**
|
|
20
15
|
* A digest of the spec's label, base, sizes, setup script as sent, and the host's runtime
|
|
21
16
|
* version: a `ram` checkpoint restores only under the release that saved it, so after a runtime
|
|
@@ -30,8 +25,8 @@ const nameOf = (spec, host) => Effect.promise(() => globalThis.crypto.subtle.dig
|
|
|
30
25
|
spec.diskGib,
|
|
31
26
|
spec.setup?.script ?? null,
|
|
32
27
|
host.runtimeVersion
|
|
33
|
-
])))).pipe(Effect.map((digest) => `${prefix}${
|
|
34
|
-
const lookup = (client, id) => client.
|
|
28
|
+
])))).pipe(Effect.map((digest) => `${prefix}${Hex.encode(new Uint8Array(digest)).slice(0, digestDigits)}`));
|
|
29
|
+
const lookup = (client, id) => client.getCheckpoint({ id }).pipe(Effect.map(Option.some), Effect.catchTag("NotFound", () => Effect.succeedNone));
|
|
35
30
|
/**
|
|
36
31
|
* `client.create`, restoring the spec's prebuild when the host it is placed on has one whose
|
|
37
32
|
* capture is done, unless `fresh`. A failed restore is the reply.
|
|
@@ -41,39 +36,53 @@ const create = (client, name, spec, options) => Effect.gen(function* () {
|
|
|
41
36
|
const host = yield* client.place(spec.base, options);
|
|
42
37
|
const id = yield* formatId(host.id, yield* nameOf(spec, host));
|
|
43
38
|
const found = yield* lookup(client, id);
|
|
44
|
-
if (Option.isSome(found) && found.value
|
|
45
|
-
machine: yield* client.
|
|
39
|
+
if (Option.isSome(found) && isReady(found.value)) return {
|
|
40
|
+
machine: yield* client.restoreMachine({
|
|
41
|
+
checkpoint: id,
|
|
42
|
+
name
|
|
43
|
+
}),
|
|
46
44
|
prebuild: found.value
|
|
47
45
|
};
|
|
48
46
|
return { machine: yield* client.create(name, spec, { host: host.id }) };
|
|
49
47
|
});
|
|
50
48
|
/**
|
|
51
49
|
* Builds the spec's prebuild on the host a create of it is placed on, with a machine of its own
|
|
52
|
-
* that it deletes however it ends. A prebuild that is there and done is the answer
|
|
53
|
-
* failed
|
|
54
|
-
*
|
|
50
|
+
* that it deletes however it ends. A prebuild that is there and done is the answer, one that
|
|
51
|
+
* failed is deleted and built again, and one still running is `Conflict{busy}`, as its capture
|
|
52
|
+
* holds it. Of two builds of one spec at once, the second's capture is `Conflict`, and neither
|
|
53
|
+
* touches the other's machine.
|
|
55
54
|
*/
|
|
56
55
|
const build = (client, spec, options) => Effect.gen(function* () {
|
|
57
|
-
if (spec.setup === void 0) return yield* new Invalid({ message: "a prebuild needs a setup" });
|
|
58
56
|
const host = yield* client.place(spec.base, options);
|
|
59
57
|
const name = yield* nameOf(spec, host);
|
|
60
58
|
const id = yield* formatId(host.id, name);
|
|
61
59
|
const found = yield* lookup(client, id);
|
|
62
60
|
if (Option.isSome(found)) {
|
|
63
61
|
const { action } = found.value;
|
|
64
|
-
|
|
62
|
+
if (isReady(found.value)) return {
|
|
65
63
|
checkpoint: found.value,
|
|
66
64
|
built: false
|
|
67
|
-
}
|
|
65
|
+
};
|
|
66
|
+
if (action.status === "running") return yield* new Conflict({
|
|
67
|
+
message: `prebuild ${id} is busy: its ${action.name} is running`,
|
|
68
|
+
kind: "busy",
|
|
69
|
+
host: host.id,
|
|
70
|
+
action: "captureCheckpoint",
|
|
71
|
+
id
|
|
72
|
+
});
|
|
73
|
+
yield* client.deleteCheckpoint({ id });
|
|
68
74
|
}
|
|
69
|
-
const builder = `prebuilding-${
|
|
75
|
+
const builder = `prebuilding-${Hex.encode(globalThis.crypto.getRandomValues(new Uint8Array(builderBytes)))}`;
|
|
70
76
|
const builderId = yield* formatId(host.id, builder);
|
|
71
|
-
const removal =
|
|
77
|
+
const removal = client.deleteMachine({ id: builderId });
|
|
72
78
|
return {
|
|
73
79
|
checkpoint: yield* Effect.gen(function* () {
|
|
74
80
|
yield* client.create(builder, spec, { host: host.id });
|
|
75
|
-
if (host.captureFrom === "stopped") yield* client.
|
|
76
|
-
return yield* client.
|
|
81
|
+
if (host.captureFrom === "stopped") yield* client.stopMachine({ id: builderId });
|
|
82
|
+
return yield* client.captureCheckpoint({
|
|
83
|
+
machine: builderId,
|
|
84
|
+
name
|
|
85
|
+
});
|
|
77
86
|
}).pipe(Effect.onError(() => Effect.ignore(removal))),
|
|
78
87
|
built: true,
|
|
79
88
|
leftover: yield* removal.pipe(Effect.as(void 0), Effect.catch(() => Effect.succeed(builderId)))
|
|
@@ -81,9 +90,7 @@ const build = (client, spec, options) => Effect.gen(function* () {
|
|
|
81
90
|
});
|
|
82
91
|
//#endregion
|
|
83
92
|
//#region src/setup.ts
|
|
84
|
-
/** Nothing here logs a script or a packed recipe: a recipe can carry secrets. */
|
|
85
93
|
const lineLength = 76;
|
|
86
|
-
/** base64 has no `_`, so no line of a file's payload can end its here-document early. */
|
|
87
94
|
const terminator = "CLANKERBOX_RECIPE";
|
|
88
95
|
const wrap = (base64) => {
|
|
89
96
|
const lines = [];
|
|
@@ -91,14 +98,8 @@ const wrap = (base64) => {
|
|
|
91
98
|
return lines.join("\n");
|
|
92
99
|
};
|
|
93
100
|
const quote = (text) => `'${text.replaceAll("'", `'\\''`)}'`;
|
|
94
|
-
/** Under `./`, so a name that starts with `-` is never read as an option. */
|
|
95
101
|
const argument = (entry) => quote(`./${entry}`);
|
|
96
|
-
/** The one bit of a mode a recipe carries, as git does, so a checkout's umask changes nothing. */
|
|
97
102
|
const executable = 73;
|
|
98
|
-
/**
|
|
99
|
-
* Under `/var/tmp`, which nothing mounts `noexec`. Setup runs as a child rather than through
|
|
100
|
-
* `exec`, so the cleanup trap still runs after it.
|
|
101
|
-
*/
|
|
102
103
|
const selfExtracting = (steps) => [
|
|
103
104
|
"#!/bin/sh",
|
|
104
105
|
"set -eu",
|
|
@@ -242,30 +243,30 @@ const specRequest = (what, profile, overrides, names) => Effect.gen(function* ()
|
|
|
242
243
|
});
|
|
243
244
|
//#endregion
|
|
244
245
|
//#region src/ssh.ts
|
|
245
|
-
/**
|
|
246
|
+
/**
|
|
247
|
+
* Only a running machine has an endpoint, and only a prepared one a host key; a machine without
|
|
248
|
+
* either is `WrongState`, naming what it lacks and why.
|
|
249
|
+
*/
|
|
246
250
|
const sshTarget = (machine) => {
|
|
247
|
-
const { id, ssh: login, hostKey } = machine;
|
|
248
|
-
if (login
|
|
249
|
-
return hostKey === void 0 ? { lacks: "SSH host key on record" } : {
|
|
251
|
+
const { id, ssh: login, hostKey, action } = machine;
|
|
252
|
+
if (login !== void 0 && hostKey !== void 0) return Effect.succeed({
|
|
250
253
|
id,
|
|
251
254
|
login,
|
|
252
255
|
hostKey
|
|
253
|
-
};
|
|
256
|
+
});
|
|
257
|
+
const lacks = login === void 0 ? "SSH endpoint" : "SSH host key on record";
|
|
258
|
+
const state = stateLabel(machine);
|
|
259
|
+
const message = `${id} has no ${lacks} (${state}, ${action.name} ${action.status}): see clankerbox get ${id}`;
|
|
260
|
+
return Effect.fail(new WrongState({
|
|
261
|
+
message,
|
|
262
|
+
machine: id,
|
|
263
|
+
state
|
|
264
|
+
}));
|
|
254
265
|
};
|
|
255
|
-
/**
|
|
256
|
-
* ssh looks the key up by `HostKeyAlias`, so the line doesn't depend on the endpoint, which
|
|
257
|
-
* changes.
|
|
258
|
-
*/
|
|
259
266
|
const knownHostsLine = (id, hostKey) => {
|
|
260
267
|
const [type = "", key = ""] = hostKey.trim().split(/\s+/u);
|
|
261
268
|
return `${id} ${type} ${key}\n`;
|
|
262
269
|
};
|
|
263
|
-
/**
|
|
264
|
-
* OpenSSH keeps the first user it is given, so the leading `-l` decides it. The destination is
|
|
265
|
-
* the machine's ID, so a `Host <pattern>` block in the user's ssh config can match it, while the
|
|
266
|
-
* endpoint and the pinned key come from options no config overrides. The caller's arguments go
|
|
267
|
-
* after the destination, where OpenSSH still reads options (`-L …`) before a remote command.
|
|
268
|
-
*/
|
|
269
270
|
const sshArguments = (target, user, knownHosts, extra) => [
|
|
270
271
|
"-l",
|
|
271
272
|
user,
|
|
@@ -298,4 +299,11 @@ const pinnedSsh = (target, user, extra) => Effect.gen(function* () {
|
|
|
298
299
|
return sshArguments(target, user, knownHosts, extra);
|
|
299
300
|
});
|
|
300
301
|
//#endregion
|
|
301
|
-
|
|
302
|
+
//#region src/sizes.ts
|
|
303
|
+
const mibPerGib = 1024;
|
|
304
|
+
/** Exactly, as a size flag takes it: `32G`, `1.5G`, `512M`, `1500M`. */
|
|
305
|
+
const formatMib = (mib) => mib >= 1024 && mib % 512 === 0 ? `${mib / mibPerGib}G` : `${mib}M`;
|
|
306
|
+
/** As a size flag takes it: `60G`. */
|
|
307
|
+
const formatGib = (gib) => `${gib}G`;
|
|
308
|
+
//#endregion
|
|
309
|
+
export { ActionName, ActionRecord, ActionStatus, Capacity, Checkpoint, ClankerboxError, client_exports as Client, Conflict, ConflictKind, CreateRequest, Host, HostError, HostId, Http, Id, Internal, Invalid, Machine, MachineSpec, Missing, Name, NotFound, NotMade, NotOffered, NotReady, PinMismatch, prebuild_exports as Prebuild, Profile, Refused, Runtime, ScriptFailed, Setup, SshEndpoint, SshLogin, Unavailable, Unsizable, Usage, WrongState, actionsByName, describeFileError, describeIssues, formatGib, formatId, formatMib, hostErrors, isReady, loadProfile, mibPerGib, packRecipe, parseId, parseName, pinnedSsh, readSetup, readTimeout, release, retryable, specRequest, sshTarget, stateLabel, version, versionHeader };
|
package/dist/node.d.mts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { N as Invalid, a as Options, r as HostEntry, t as Client } from "./client-8af586hb.mjs";
|
|
2
2
|
import { Layer } from "effect";
|
|
3
3
|
//#region src/node.d.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The client over Node's `http`, which sets no timeout of its own: a mutation can run for hours.
|
|
6
|
+
* It is in `./node`, apart from `.`, so an app that brings its own `HttpClient` never loads
|
|
7
|
+
* `@effect/platform-node`.
|
|
8
|
+
*/
|
|
5
9
|
export declare const layer: (hosts: ReadonlyArray<HostEntry>, options?: Options) => Layer.Layer<Client, Invalid>;
|
|
6
10
|
//#endregion
|
package/dist/node.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { n as layer$1 } from "./client-
|
|
1
|
+
import { n as layer$1 } from "./client-CbMWwHdT.mjs";
|
|
2
2
|
import { Layer } from "effect";
|
|
3
3
|
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
|
|
4
4
|
//#region src/node.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* The client over Node's `http`, which sets no timeout of its own: a mutation can run for hours.
|
|
7
|
+
* It is in `./node`, apart from `.`, so an app that brings its own `HttpClient` never loads
|
|
7
8
|
* `@effect/platform-node`.
|
|
8
9
|
*/
|
|
9
|
-
/** Over Node's `http`, which sets no timeout of its own: a mutation can run for hours. */
|
|
10
10
|
const layer = (hosts, options) => layer$1(hosts, options).pipe(Layer.provide(NodeHttpClient.layerNodeHttp));
|
|
11
11
|
//#endregion
|
|
12
12
|
export { layer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjermundgaraba/clankerbox-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "clankerbox's API contract and client library, for Effect",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@gjermundgaraba/effect-actions": "0.
|
|
31
|
+
"@gjermundgaraba/effect-actions": "0.17.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@effect/platform-node": "4.0.4",
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
"@types/node": "26.6.4",
|
|
37
37
|
"effect": "4.0.4",
|
|
38
38
|
"typescript": "7.0.2",
|
|
39
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
40
|
-
"vite-plus": "1.
|
|
39
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
|
|
40
|
+
"vite-plus": "1.2.0",
|
|
41
|
+
"vitest": "5.0.4"
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"@effect/platform-node": "~4.0.4",
|