@nimbus-sh/core 0.1.0 → 0.3.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/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/runtime/bash-runner.d.ts +63 -0
- package/dist/runtime/bash-runner.d.ts.map +1 -0
- package/dist/runtime/bash-runner.generated.d.ts +14 -0
- package/dist/runtime/bash-runner.generated.d.ts.map +1 -0
- package/dist/runtime/bash-runner.generated.js +13 -0
- package/dist/runtime/bash-runner.js +290 -0
- package/dist/runtime/cpython-runner.d.ts +86 -0
- package/dist/runtime/cpython-runner.d.ts.map +1 -0
- package/dist/runtime/cpython-runner.js +425 -0
- package/dist/runtime/facet-host.d.ts +159 -0
- package/dist/runtime/facet-host.d.ts.map +1 -0
- package/dist/runtime/facet-host.js +22 -0
- package/dist/runtime/installed-runtimes.d.ts +99 -0
- package/dist/runtime/installed-runtimes.d.ts.map +1 -0
- package/dist/runtime/installed-runtimes.js +162 -0
- package/dist/runtime/local-facet-host.d.ts +38 -0
- package/dist/runtime/local-facet-host.d.ts.map +1 -0
- package/dist/runtime/local-facet-host.js +171 -0
- package/dist/runtime/python-pip.d.ts +38 -0
- package/dist/runtime/python-pip.d.ts.map +1 -0
- package/dist/runtime/python-pip.js +1063 -0
- package/dist/runtime/runtime-manifest.d.ts +86 -0
- package/dist/runtime/runtime-manifest.d.ts.map +1 -0
- package/dist/runtime/runtime-manifest.js +72 -0
- package/dist/runtime/runtime-package.d.ts +63 -0
- package/dist/runtime/runtime-package.d.ts.map +1 -0
- package/dist/runtime/runtime-package.js +66 -0
- package/dist/runtime/runtime-registry.d.ts +162 -0
- package/dist/runtime/runtime-registry.d.ts.map +1 -0
- package/dist/runtime/runtime-registry.js +363 -0
- package/dist/runtime/vfs-snapshot.d.ts.map +1 -1
- package/dist/runtime/vfs-snapshot.js +15 -1
- package/dist/runtime/vfs-supervisor.d.ts +22 -0
- package/dist/runtime/vfs-supervisor.d.ts.map +1 -0
- package/dist/runtime/vfs-supervisor.js +65 -0
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +14 -0
- package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -0
- package/dist/runtime/virtual-socket-kernel.generated.js +13 -0
- package/dist/runtime/wasm-runner.d.ts +80 -0
- package/dist/runtime/wasm-runner.d.ts.map +1 -0
- package/dist/runtime/wasm-runner.js +686 -0
- package/dist/workspace/nimbus-workspace.d.ts +116 -20
- package/dist/workspace/nimbus-workspace.d.ts.map +1 -1
- package/dist/workspace/nimbus-workspace.js +238 -45
- package/package.json +4 -2
- package/src/index.ts +16 -0
- package/src/runtime/bash-runner.generated.ts +14 -0
- package/src/runtime/bash-runner.ts +347 -0
- package/src/runtime/cpython-runner.ts +504 -0
- package/src/runtime/facet-host.ts +170 -0
- package/src/runtime/installed-runtimes.ts +235 -0
- package/src/runtime/local-facet-host.ts +205 -0
- package/src/runtime/python-pip.ts +1211 -0
- package/src/runtime/runtime-manifest.ts +155 -0
- package/src/runtime/runtime-package.ts +114 -0
- package/src/runtime/runtime-registry.ts +511 -0
- package/src/runtime/vfs-snapshot.ts +15 -1
- package/src/runtime/vfs-supervisor.ts +67 -0
- package/src/runtime/virtual-socket-kernel.generated.ts +14 -0
- package/src/runtime/wasm-runner.ts +835 -0
- package/src/workspace/nimbus-workspace.ts +349 -54
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime-manifest.ts — what an installed language runtime IS.
|
|
3
|
+
*
|
|
4
|
+
* A manifest names the files a runtime is made of, the shell commands it
|
|
5
|
+
* provides, and the runner that answers them. `nimbus install <name>` writes
|
|
6
|
+
* one into `~/.nimbus/runtimes/<name>/<version>/manifest.json`; every runner
|
|
7
|
+
* reads its own out of the session filesystem from there.
|
|
8
|
+
*
|
|
9
|
+
* The data contract only. Where the bytes come FROM is the publisher's
|
|
10
|
+
* problem, and the Cloudflare deployment's answer to it — an R2 bucket, a
|
|
11
|
+
* per-colo cache, and a digest chain rooted at a build-time pin — lives in
|
|
12
|
+
* `@nimbus-sh/worker`'s `runtime/runtime-catalog.ts`. A runtime that has been
|
|
13
|
+
* installed is the same runtime whichever tier fetched it, so nothing below
|
|
14
|
+
* this point knows there were tiers.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { z } from 'zod/v4';
|
|
18
|
+
import { PYODIDE_PACKAGE_ABI } from './os-contracts.js';
|
|
19
|
+
|
|
20
|
+
export interface ManifestFile {
|
|
21
|
+
/** VFS path relative to ~/.nimbus/runtimes/<name>/<version>/. */
|
|
22
|
+
path: string;
|
|
23
|
+
/** Publisher-side key for the content blob. */
|
|
24
|
+
content: string;
|
|
25
|
+
/** Hex sha256 of the content blob bytes. */
|
|
26
|
+
sha256: string;
|
|
27
|
+
/** Byte size. */
|
|
28
|
+
size: number;
|
|
29
|
+
/** Optional file mode hint ("exec" → registered as a shell bin). */
|
|
30
|
+
mode?: 'exec';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ManifestEntrypoint {
|
|
34
|
+
/** Shell command name. */
|
|
35
|
+
binName: string;
|
|
36
|
+
/** Runner key (e.g. "clang-runner") — package manager dispatches to
|
|
37
|
+
* the right runner factory by this. */
|
|
38
|
+
runner: string;
|
|
39
|
+
/** Default args prepended to user args at invocation. */
|
|
40
|
+
args: string[];
|
|
41
|
+
/** Optional secondary classification (e.g. "linker" for wasm-ld). */
|
|
42
|
+
kind?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RuntimeArtifactMetadata {
|
|
46
|
+
path: string;
|
|
47
|
+
kind: string;
|
|
48
|
+
id: string;
|
|
49
|
+
source_sha256?: string;
|
|
50
|
+
sha256: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type RuntimePythonPackageAbi = typeof PYODIDE_PACKAGE_ABI;
|
|
54
|
+
|
|
55
|
+
export interface RuntimePythonExtensionModuleMetadata {
|
|
56
|
+
/** Path inside Python site-packages, as stored in the wheel. */
|
|
57
|
+
path: string;
|
|
58
|
+
/** Path inside the installed Nimbus runtime root. */
|
|
59
|
+
runtimePath: string;
|
|
60
|
+
sha256: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RuntimePythonPackageArtifactMetadata extends RuntimeArtifactMetadata {
|
|
64
|
+
kind: 'python-package';
|
|
65
|
+
language: 'python';
|
|
66
|
+
packageName: string;
|
|
67
|
+
version: string;
|
|
68
|
+
abi: RuntimePythonPackageAbi;
|
|
69
|
+
pyodideVersion: string;
|
|
70
|
+
pythonVersion: string;
|
|
71
|
+
wheelFileName: string;
|
|
72
|
+
wheelSha256: string;
|
|
73
|
+
loadMode: 'startup-module';
|
|
74
|
+
imports: string[];
|
|
75
|
+
dependencies: string[];
|
|
76
|
+
extensionModules: RuntimePythonExtensionModuleMetadata[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RuntimeManifest {
|
|
80
|
+
name: string;
|
|
81
|
+
version: string;
|
|
82
|
+
license: string;
|
|
83
|
+
/** Which WASI namespace the binaries import — `wasi_unstable` for
|
|
84
|
+
* binji clang. `null` for non-WASI runtimes (e.g. Pyodide). */
|
|
85
|
+
wasi_namespace: string | null;
|
|
86
|
+
files: ManifestFile[];
|
|
87
|
+
entrypoints: ManifestEntrypoint[];
|
|
88
|
+
runtime_artifacts?: RuntimeArtifactMetadata[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const HexSha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
92
|
+
|
|
93
|
+
const ManifestFileSchema: z.ZodType<ManifestFile> = z.object({
|
|
94
|
+
path: z.string().min(1),
|
|
95
|
+
content: z.string().min(1),
|
|
96
|
+
sha256: HexSha256Schema,
|
|
97
|
+
size: z.number().int().nonnegative(),
|
|
98
|
+
mode: z.literal('exec').optional(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const ManifestEntrypointSchema: z.ZodType<ManifestEntrypoint> = z.object({
|
|
102
|
+
binName: z.string().min(1),
|
|
103
|
+
runner: z.string().min(1),
|
|
104
|
+
args: z.array(z.string()),
|
|
105
|
+
kind: z.string().optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const RuntimeArtifactMetadataSchema: z.ZodType<RuntimeArtifactMetadata> = z.object({
|
|
109
|
+
path: z.string().min(1),
|
|
110
|
+
kind: z.string().min(1),
|
|
111
|
+
id: z.string().min(1),
|
|
112
|
+
source_sha256: HexSha256Schema.optional(),
|
|
113
|
+
sha256: HexSha256Schema,
|
|
114
|
+
}).passthrough();
|
|
115
|
+
|
|
116
|
+
export const RuntimePythonPackageArtifactMetadataSchema: z.ZodType<RuntimePythonPackageArtifactMetadata> =
|
|
117
|
+
RuntimeArtifactMetadataSchema.and(z.object({
|
|
118
|
+
kind: z.literal('python-package'),
|
|
119
|
+
language: z.literal('python'),
|
|
120
|
+
packageName: z.string().min(1),
|
|
121
|
+
version: z.string().min(1),
|
|
122
|
+
abi: z.literal(PYODIDE_PACKAGE_ABI),
|
|
123
|
+
pyodideVersion: z.string().min(1),
|
|
124
|
+
pythonVersion: z.string().min(1),
|
|
125
|
+
wheelFileName: z.string().min(1),
|
|
126
|
+
wheelSha256: HexSha256Schema,
|
|
127
|
+
loadMode: z.literal('startup-module'),
|
|
128
|
+
imports: z.array(z.string()),
|
|
129
|
+
dependencies: z.array(z.string()),
|
|
130
|
+
extensionModules: z.array(z.object({
|
|
131
|
+
path: z.string().min(1),
|
|
132
|
+
runtimePath: z.string().min(1),
|
|
133
|
+
sha256: HexSha256Schema,
|
|
134
|
+
})),
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
const RuntimeManifestSchema: z.ZodType<RuntimeManifest> = z.object({
|
|
138
|
+
name: z.string().min(1),
|
|
139
|
+
version: z.string().min(1),
|
|
140
|
+
license: z.string(),
|
|
141
|
+
wasi_namespace: z.string().nullable(),
|
|
142
|
+
files: z.array(ManifestFileSchema),
|
|
143
|
+
entrypoints: z.array(ManifestEntrypointSchema),
|
|
144
|
+
runtime_artifacts: z.array(RuntimeArtifactMetadataSchema).optional(),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export function parseRuntimeManifest(value: unknown): RuntimeManifest {
|
|
148
|
+
return RuntimeManifestSchema.parse(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function isRuntimePythonPackageArtifactMetadata(
|
|
152
|
+
artifact: RuntimeArtifactMetadata,
|
|
153
|
+
): artifact is RuntimePythonPackageArtifactMetadata {
|
|
154
|
+
return RuntimePythonPackageArtifactMetadataSchema.safeParse(artifact).success;
|
|
155
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime-package.ts — a runtime that arrived with the code, rather than over
|
|
3
|
+
* the network.
|
|
4
|
+
*
|
|
5
|
+
* `nimbus install <name>` reads a catalog out of R2 and writes the result into
|
|
6
|
+
* `~/.nimbus/runtimes/<name>/<version>/`. That is the Cloudflare deployment's
|
|
7
|
+
* answer to "where do the bytes come from", and it needs a bucket, a binding
|
|
8
|
+
* and a colo cache. An embedder who ran `npm i @nimbus-sh/runtime-bash` has
|
|
9
|
+
* already answered the same question: the bytes are on disk beside their
|
|
10
|
+
* manifest, fetched and integrity-checked by npm before any of this ran.
|
|
11
|
+
*
|
|
12
|
+
* So this is the second publisher, not the second package manager. It writes
|
|
13
|
+
* the SAME tree at the SAME path from the SAME manifest — `installRoot()`,
|
|
14
|
+
* `parseRuntimeManifest()`, one file per `manifest.files` entry — so
|
|
15
|
+
* `listInstalledManifests` and `rehydrateInstalledRuntimes` cannot tell which
|
|
16
|
+
* one ran, and a workspace behaves identically either way.
|
|
17
|
+
*
|
|
18
|
+
* Trust: R2 is verified against a digest chain rooted in a build-time pin
|
|
19
|
+
* because `caches.default` is shared across tenants and R2 keys are not
|
|
20
|
+
* content-addressed. A runtime package's root of trust is npm's own tarball
|
|
21
|
+
* integrity, which the install already checked; from there the manifest's
|
|
22
|
+
* per-file digests are re-verified here for exactly the reason the R2 path
|
|
23
|
+
* verifies them — these blobs are interpreters, so bytes that reach the
|
|
24
|
+
* filesystem are bytes that execute.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { sha256Hex } from '../_shared/crypto.js';
|
|
28
|
+
import type { CredentialedVfs } from '../vfs/sqlite-vfs.js';
|
|
29
|
+
import { installRoot } from './installed-runtimes.js';
|
|
30
|
+
import {
|
|
31
|
+
parseRuntimeManifest,
|
|
32
|
+
type ManifestFile,
|
|
33
|
+
type RuntimeManifest,
|
|
34
|
+
} from './runtime-manifest.js';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An installed npm package holding one runtime.
|
|
38
|
+
*
|
|
39
|
+
* The published packages (`@nimbus-sh/runtime-bash`,
|
|
40
|
+
* `@nimbus-sh/runtime-cpython`) are the implementations; each is a manifest,
|
|
41
|
+
* the content-addressed blobs it names, and the eight lines of `node:fs` that
|
|
42
|
+
* read them. The port is here rather than in those packages because the
|
|
43
|
+
* FILESYSTEM is what a runtime is, and this is the half that knows it.
|
|
44
|
+
*
|
|
45
|
+
* `readBlob` takes the whole manifest entry rather than a bare key, mirroring
|
|
46
|
+
* `fetchBlob` in the Cloudflare catalog: a key and the digest that vouches for
|
|
47
|
+
* it never travel as separate arguments, so there is no call in which they can
|
|
48
|
+
* disagree.
|
|
49
|
+
*/
|
|
50
|
+
export interface RuntimePackage {
|
|
51
|
+
readonly manifest: RuntimeManifest;
|
|
52
|
+
readBlob(file: ManifestFile): Uint8Array | Promise<Uint8Array>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SeededRuntime {
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly version: string;
|
|
58
|
+
/** VFS path of the install root, e.g. `home/user/.nimbus/runtimes/bash/5.2.37`. */
|
|
59
|
+
readonly root: string;
|
|
60
|
+
/** False when the runtime was already installed at `root` and nothing was written. */
|
|
61
|
+
readonly written: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Write a runtime package into the filesystem as an install.
|
|
66
|
+
*
|
|
67
|
+
* Idempotent on the same rule the package manager uses: a `manifest.json`
|
|
68
|
+
* already at the install root means the install completed, and the root
|
|
69
|
+
* carries the version, so a package upgrade lands beside its predecessor
|
|
70
|
+
* rather than half over it.
|
|
71
|
+
*/
|
|
72
|
+
export async function seedRuntimePackage(
|
|
73
|
+
vfs: CredentialedVfs,
|
|
74
|
+
homeDir: string,
|
|
75
|
+
runtimePackage: RuntimePackage,
|
|
76
|
+
): Promise<SeededRuntime> {
|
|
77
|
+
const manifest = parseRuntimeManifest(runtimePackage.manifest);
|
|
78
|
+
const root = installRoot(homeDir, manifest.name, manifest.version);
|
|
79
|
+
|
|
80
|
+
if (vfs.exists(`${root}/manifest.json`)) {
|
|
81
|
+
return { name: manifest.name, version: manifest.version, root, written: false };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
vfs.mkdir(root, { recursive: true });
|
|
85
|
+
for (const file of manifest.files) {
|
|
86
|
+
const target = `${root}/${file.path}`;
|
|
87
|
+
const parent = target.slice(0, target.lastIndexOf('/'));
|
|
88
|
+
if (!vfs.exists(parent)) vfs.mkdir(parent, { recursive: true });
|
|
89
|
+
vfs.writeFile(target, await verifiedBlob(manifest, runtimePackage, file));
|
|
90
|
+
}
|
|
91
|
+
// Last, where the R2 installer writes it first: that one has `--reinstall`
|
|
92
|
+
// to force a redo, and this one has no verb at all, so a manifest sitting
|
|
93
|
+
// beside a half-written tree would report a broken runtime as installed for
|
|
94
|
+
// the life of the filesystem.
|
|
95
|
+
vfs.writeFile(`${root}/manifest.json`, JSON.stringify(manifest, null, 2));
|
|
96
|
+
|
|
97
|
+
return { name: manifest.name, version: manifest.version, root, written: true };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function verifiedBlob(
|
|
101
|
+
manifest: RuntimeManifest,
|
|
102
|
+
runtimePackage: RuntimePackage,
|
|
103
|
+
file: ManifestFile,
|
|
104
|
+
): Promise<Uint8Array> {
|
|
105
|
+
const bytes = await runtimePackage.readBlob(file);
|
|
106
|
+
const actual = await sha256Hex(bytes);
|
|
107
|
+
if (actual !== file.sha256) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`${manifest.name}@${manifest.version}: sha256 mismatch for ${file.path} — manifest expects `
|
|
110
|
+
+ `${file.sha256}, ${file.content} holds ${actual}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return bytes;
|
|
114
|
+
}
|