@mostra/sdk 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +37 -2
- package/dist/bundles.json +31 -0
- package/dist/index.d.ts +374 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +484 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-CUbnK5Za.js +1010 -0
- package/dist/runtime-CUbnK5Za.js.map +1 -0
- package/dist/setup-main.d.ts +1 -0
- package/dist/setup-main.js +49 -0
- package/dist/setup-main.js.map +1 -0
- package/package.json +63 -3
- package/src/bundle/cache.ts +375 -0
- package/src/bundle/download.ts +255 -0
- package/src/bundle/extract.ts +60 -0
- package/src/bundle/setup.ts +278 -0
- package/src/bundle/tree.ts +176 -0
- package/src/events.ts +276 -0
- package/src/index.ts +67 -0
- package/src/mostra.ts +607 -0
- package/src/platform.ts +81 -0
- package/src/runtime.ts +179 -0
- package/src/setup-main.ts +49 -0
- package/src/version.ts +38 -0
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which runtime a host runs (RT-01, D85). In order:
|
|
3
|
+
*
|
|
4
|
+
* 1. `bundle`, a directory the caller names (the SDK's option), then `MOSTRA_BUNDLE`;
|
|
5
|
+
* 2. `MOSTRA_RUNTIME_BIN`, a `mostra-runtime` entry run with this process's Node (tests and
|
|
6
|
+
* development);
|
|
7
|
+
* 3. the bundle this package itself sits in (the CLI inside a bundle);
|
|
8
|
+
* 4. the bundle this package's release pins (`bundles.json`), from the cache or downloaded;
|
|
9
|
+
* 5. a development checkout's built runtime beside this package.
|
|
10
|
+
*
|
|
11
|
+
* Anything a bundle supplies is checked first: its setup marker must match it, or setup runs.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, resolve, sep } from "node:path";
|
|
15
|
+
import { BundlePins, createDiagnostic, RpcError } from "@mostra/protocol";
|
|
16
|
+
import { type InstallProgress, installPinned } from "./bundle/cache.ts";
|
|
17
|
+
import {
|
|
18
|
+
type Bundle,
|
|
19
|
+
type Environment,
|
|
20
|
+
readBundle,
|
|
21
|
+
readSetup,
|
|
22
|
+
type SetupStep,
|
|
23
|
+
setupBundle,
|
|
24
|
+
} from "./bundle/setup.ts";
|
|
25
|
+
import { bundlePlatform } from "./platform.ts";
|
|
26
|
+
import { packageRoot } from "./version.ts";
|
|
27
|
+
|
|
28
|
+
/** A runtime to spawn: the Node.js to run it with and its entry points. */
|
|
29
|
+
export interface Runtime {
|
|
30
|
+
/** How it was found. */
|
|
31
|
+
readonly source: "bundle" | "override" | "development";
|
|
32
|
+
/** The Node.js executable: the bundle's own, or this process's for the others. */
|
|
33
|
+
readonly node: string;
|
|
34
|
+
/** `mostra-runtime`'s entry (`host`, `daemon`, `service`). */
|
|
35
|
+
readonly runtimeEntry: string;
|
|
36
|
+
/** The `mostra` CLI's entry, when this runtime has one. */
|
|
37
|
+
readonly cliEntry?: string;
|
|
38
|
+
/** The bundle, when the runtime is one. */
|
|
39
|
+
readonly bundle?: Bundle;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Options of {@link resolveRuntime}. */
|
|
43
|
+
export interface RuntimeOptions {
|
|
44
|
+
/** An extracted bundle to use, ahead of everything else. */
|
|
45
|
+
readonly bundle?: string | undefined;
|
|
46
|
+
/** The environment: `MOSTRA_BUNDLE`, `MOSTRA_RUNTIME_BIN`, the cache's variables. */
|
|
47
|
+
readonly env?: Environment | undefined;
|
|
48
|
+
/** Gives up a download or setup. */
|
|
49
|
+
readonly signal?: AbortSignal | undefined;
|
|
50
|
+
/** Progress of a download or setup, for a person waiting (the CLI prints it on stderr). */
|
|
51
|
+
readonly onProgress?: ((progress: InstallProgress) => void) | undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Where the pins are read from: `<package>/dist/bundles.json`, which the release writes before
|
|
56
|
+
* publishing. `MOSTRA_TEST_BUNDLE_PINS` names another file only for a package that runs from a
|
|
57
|
+
* development checkout (its real path outside any `node_modules`), where the tests use it: an
|
|
58
|
+
* installed copy (npm's `node_modules`, or a bundle's `lib/node_modules`) ignores it, so a
|
|
59
|
+
* variable leaked into a user's environment can't swap the pins npm's provenance covers (Opus
|
|
60
|
+
* review finding 8).
|
|
61
|
+
*/
|
|
62
|
+
export function pinsFile(env: Environment, root: string): string {
|
|
63
|
+
const override = env["MOSTRA_TEST_BUNDLE_PINS"];
|
|
64
|
+
const installed = root.split(sep).includes("node_modules");
|
|
65
|
+
return override && !installed ? override : join(root, "dist", "bundles.json");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The pins this build carries: `dist/bundles.json` (a test override applies only in a development checkout). */
|
|
69
|
+
export function readPins(env: Environment): BundlePins | undefined {
|
|
70
|
+
const file = pinsFile(env, packageRoot());
|
|
71
|
+
if (!existsSync(file)) return undefined;
|
|
72
|
+
let data: unknown;
|
|
73
|
+
try {
|
|
74
|
+
data = JSON.parse(readFileSync(file, "utf8"));
|
|
75
|
+
} catch {
|
|
76
|
+
data = undefined; // not JSON (or unreadable): the same refusal as a file of the wrong shape
|
|
77
|
+
}
|
|
78
|
+
const parsed = BundlePins.safeParse(data);
|
|
79
|
+
if (!parsed.success) {
|
|
80
|
+
throw new RpcError(
|
|
81
|
+
createDiagnostic("MOSTRA_ENV_BUNDLE_INVALID", {
|
|
82
|
+
message: `${file} isn't a release's bundle pins.`,
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return parsed.data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The bundle this package was installed into, if any: `<root>/lib/node_modules/@mostra/sdk`. */
|
|
90
|
+
export function enclosingBundle(): string | undefined {
|
|
91
|
+
const root = resolve(packageRoot(), "..", "..", "..", "..");
|
|
92
|
+
return existsSync(join(root, "bundle.json")) &&
|
|
93
|
+
existsSync(join(root, "lib", "node_modules", "@mostra", "sdk"))
|
|
94
|
+
? root
|
|
95
|
+
: undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The runtime described by a bundle, once it is set up. */
|
|
99
|
+
async function fromBundle(root: string, options: RuntimeOptions): Promise<Runtime> {
|
|
100
|
+
let bundle = await readBundle(resolve(root));
|
|
101
|
+
if (!(await readSetup(bundle))) {
|
|
102
|
+
await setupBundle(bundle.root, {
|
|
103
|
+
env: options.env,
|
|
104
|
+
signal: options.signal,
|
|
105
|
+
onStep: (step: SetupStep) => options.onProgress?.({ step }),
|
|
106
|
+
});
|
|
107
|
+
bundle = await readBundle(bundle.root);
|
|
108
|
+
}
|
|
109
|
+
return runtimeOf(bundle);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A bundle's entry points as a runtime. */
|
|
113
|
+
export function runtimeOf(bundle: Bundle): Runtime {
|
|
114
|
+
const { root, info } = bundle;
|
|
115
|
+
return {
|
|
116
|
+
source: "bundle",
|
|
117
|
+
node: join(root, info.node.path),
|
|
118
|
+
runtimeEntry: join(root, info.entries.runtime),
|
|
119
|
+
cliEntry: join(root, info.entries.cli),
|
|
120
|
+
bundle,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A development checkout's runtime: `packages/runtime/dist/bin.js` beside this package. */
|
|
125
|
+
function development(): Runtime | undefined {
|
|
126
|
+
const root = packageRoot();
|
|
127
|
+
const entry = resolve(root, "..", "runtime", "dist", "bin.js");
|
|
128
|
+
if (!existsSync(entry)) return undefined;
|
|
129
|
+
const cli = resolve(root, "..", "cli", "dist", "bin.js");
|
|
130
|
+
return {
|
|
131
|
+
source: "development",
|
|
132
|
+
node: process.execPath,
|
|
133
|
+
runtimeEntry: entry,
|
|
134
|
+
...(existsSync(cli) ? { cliEntry: cli } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Finds the runtime to spawn, downloading and setting up the pinned bundle when nothing nearer
|
|
140
|
+
* exists. Throws `MOSTRA_PLATFORM_UNSUPPORTED`, `MOSTRA_ENV_RUNTIME_MISSING` (a
|
|
141
|
+
* `MOSTRA_RUNTIME_BIN` that doesn't exist), `MOSTRA_ENV_BUNDLE_UNAVAILABLE`,
|
|
142
|
+
* `MOSTRA_ENV_BUNDLE_INVALID` or `MOSTRA_ENV_BUNDLE_DOWNLOAD_FAILED`.
|
|
143
|
+
*/
|
|
144
|
+
export async function resolveRuntime(options: RuntimeOptions = {}): Promise<Runtime> {
|
|
145
|
+
const env = options.env ?? process.env;
|
|
146
|
+
const named = options.bundle ?? (env["MOSTRA_BUNDLE"] || undefined);
|
|
147
|
+
if (named !== undefined) return fromBundle(named, { ...options, env });
|
|
148
|
+
const override = env["MOSTRA_RUNTIME_BIN"];
|
|
149
|
+
if (override) {
|
|
150
|
+
if (!existsSync(override)) {
|
|
151
|
+
throw new RpcError(
|
|
152
|
+
createDiagnostic("MOSTRA_ENV_RUNTIME_MISSING", {
|
|
153
|
+
message: `MOSTRA_RUNTIME_BIN names ${override}, which doesn't exist.`,
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return { source: "override", node: process.execPath, runtimeEntry: override };
|
|
158
|
+
}
|
|
159
|
+
const enclosing = enclosingBundle();
|
|
160
|
+
if (enclosing !== undefined) return fromBundle(enclosing, { ...options, env });
|
|
161
|
+
const pins = readPins(env);
|
|
162
|
+
if (pins !== undefined) {
|
|
163
|
+
const bundle = await installPinned(pins, {
|
|
164
|
+
env,
|
|
165
|
+
platform: bundlePlatform(),
|
|
166
|
+
signal: options.signal,
|
|
167
|
+
onProgress: options.onProgress,
|
|
168
|
+
});
|
|
169
|
+
return runtimeOf(bundle);
|
|
170
|
+
}
|
|
171
|
+
const checkout = development();
|
|
172
|
+
if (checkout !== undefined) return checkout;
|
|
173
|
+
throw new RpcError(
|
|
174
|
+
createDiagnostic("MOSTRA_ENV_BUNDLE_UNAVAILABLE", {
|
|
175
|
+
message:
|
|
176
|
+
"This build of @mostra/sdk pins no runtime bundle and has no runtime beside it; set MOSTRA_BUNDLE to an extracted bundle.",
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `mostra-setup [--root <bundle>]`: makes an extracted runtime bundle ready (D142). It checks every
|
|
4
|
+
* file against `files.json`, fetches the pinned verify browser and writes `.mostra-setup.json`.
|
|
5
|
+
* The bundle's `bin/mostra-setup` runs it for the bundle it sits in; the Rust crate (WP-12) and
|
|
6
|
+
* anyone who extracts an archive by hand run it once. It prints one JSON line on stdout,
|
|
7
|
+
* `{ "status": "ok", "bundle": { … } }` or `{ "status": "failed", "diagnostic": { … } }` (exit 3),
|
|
8
|
+
* and its progress on stderr.
|
|
9
|
+
*/
|
|
10
|
+
import { parseArgs } from "node:util";
|
|
11
|
+
import { createDiagnostic, RpcError } from "@mostra/protocol";
|
|
12
|
+
import { setupBundle } from "./bundle/setup.ts";
|
|
13
|
+
import { enclosingBundle } from "./runtime.ts";
|
|
14
|
+
|
|
15
|
+
const STEPS = { files: "Checking the bundle's files", browser: "Fetching the verify browser" };
|
|
16
|
+
|
|
17
|
+
async function main(): Promise<number> {
|
|
18
|
+
const { values } = parseArgs({ options: { root: { type: "string" } }, strict: true });
|
|
19
|
+
const root = values.root ?? enclosingBundle();
|
|
20
|
+
if (root === undefined) {
|
|
21
|
+
throw new RpcError(
|
|
22
|
+
createDiagnostic("MOSTRA_ENV_BUNDLE_UNAVAILABLE", {
|
|
23
|
+
message: "mostra-setup isn't inside a runtime bundle; pass --root <bundle directory>.",
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const marker = await setupBundle(root, {
|
|
28
|
+
onStep: (step) => process.stderr.write(`mostra-setup: ${STEPS[step]}…\n`),
|
|
29
|
+
});
|
|
30
|
+
process.stdout.write(`${JSON.stringify({ status: "ok", bundle: { root, ...marker } })}\n`);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().then(
|
|
35
|
+
(code) => {
|
|
36
|
+
process.exitCode = code;
|
|
37
|
+
},
|
|
38
|
+
(error: unknown) => {
|
|
39
|
+
const diagnostic =
|
|
40
|
+
error instanceof RpcError
|
|
41
|
+
? error.diagnostic
|
|
42
|
+
: createDiagnostic("MOSTRA_HOST_INTERNAL_ERROR", {
|
|
43
|
+
message: `mostra-setup failed: ${(error as Error).message}`,
|
|
44
|
+
});
|
|
45
|
+
process.stdout.write(`${JSON.stringify({ status: "failed", diagnostic })}\n`);
|
|
46
|
+
process.stderr.write(`mostra-setup: ${diagnostic.message}\n`);
|
|
47
|
+
process.exitCode = 3;
|
|
48
|
+
},
|
|
49
|
+
);
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The SDK's own package: its root directory and its release. */
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
let found: { readonly root: string; readonly version: string } | undefined;
|
|
7
|
+
|
|
8
|
+
function own(): { readonly root: string; readonly version: string } {
|
|
9
|
+
if (found) return found;
|
|
10
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
for (;;) {
|
|
12
|
+
try {
|
|
13
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
|
|
14
|
+
name?: string;
|
|
15
|
+
version?: string;
|
|
16
|
+
};
|
|
17
|
+
if (manifest.name === "@mostra/sdk" && manifest.version) {
|
|
18
|
+
found = { root: dir, version: manifest.version };
|
|
19
|
+
return found;
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// Keep looking upwards.
|
|
23
|
+
}
|
|
24
|
+
const parent = dirname(dir);
|
|
25
|
+
if (parent === dir) throw new Error("Couldn't find @mostra/sdk's package.json.");
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** This SDK's release: the client version an SDK host reports by default. */
|
|
31
|
+
export function sdkVersion(): string {
|
|
32
|
+
return own().version;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The SDK package's directory; `dist/bundles.json` lives under it in a release. */
|
|
36
|
+
export function packageRoot(): string {
|
|
37
|
+
return own().root;
|
|
38
|
+
}
|