@yourrealm/sdk 0.0.0-alpha.1378.ce03c8dc
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-EXCEPTION +26 -0
- package/README.md +34 -0
- package/dist/chunk-3R6KEFOI.js +22 -0
- package/dist/chunk-67BDAMYX.js +376 -0
- package/dist/chunk-AITGS27R.js +139 -0
- package/dist/chunk-CMAEHQ4F.js +6 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-O3UIK34B.js +1430 -0
- package/dist/chunk-VHTUSZUX.js +176 -0
- package/dist/cli.js +9006 -0
- package/dist/docker.js +54 -0
- package/dist/index.js +1053 -0
- package/dist/ingress.js +436 -0
- package/dist/jsx-runtime.js +2 -0
- package/dist/preact.js +22 -0
- package/dist/service-url.js +11 -0
- package/dist/services.js +213 -0
- package/dist/test.js +5069 -0
- package/dist/ui.js +254 -0
- package/dist/worker-protocol.js +89 -0
- package/package.json +112 -0
- package/src/app-icon.ts +74 -0
- package/src/apply.ts +52 -0
- package/src/bundle-ui.ts +211 -0
- package/src/bundle.ts +546 -0
- package/src/cli/build-dev-landing.ts +84 -0
- package/src/cli/build-install-panel.ts +90 -0
- package/src/cli/build-runtime.ts +133 -0
- package/src/cli/build.ts +63 -0
- package/src/cli/dev.ts +107 -0
- package/src/cli/down.ts +60 -0
- package/src/cli/main.ts +56 -0
- package/src/cli/test.ts +61 -0
- package/src/cli/validate.ts +37 -0
- package/src/compile.ts +100 -0
- package/src/create-app.ts +982 -0
- package/src/deep-merge.ts +33 -0
- package/src/dev/fixture.ts +48 -0
- package/src/dev/install-handler.ts +383 -0
- package/src/dev/server.ts +968 -0
- package/src/dev/stack.ts +108 -0
- package/src/dev/state.ts +159 -0
- package/src/docker.ts +96 -0
- package/src/errors.ts +61 -0
- package/src/factory.ts +481 -0
- package/src/garage.ts +56 -0
- package/src/generator/hash.ts +6 -0
- package/src/generator/resolve-refs.ts +207 -0
- package/src/grants-builder.ts +100 -0
- package/src/grants.ts +260 -0
- package/src/home-api.ts +172 -0
- package/src/home-cookie.ts +10 -0
- package/src/home-registry.ts +120 -0
- package/src/home.ts +187 -0
- package/src/host-registry.ts +143 -0
- package/src/http-header.ts +20 -0
- package/src/identifiers.ts +49 -0
- package/src/index.ts +66 -0
- package/src/info.ts +64 -0
- package/src/ini.ts +50 -0
- package/src/json.ts +14 -0
- package/src/jsx-runtime/index.ts +5 -0
- package/src/keys.ts +29 -0
- package/src/mysql.ts +56 -0
- package/src/pack-build.ts +108 -0
- package/src/pack.ts +115 -0
- package/src/peer-registry.ts +121 -0
- package/src/peer-world.ts +117 -0
- package/src/permission-meta.ts +116 -0
- package/src/permissions.ts +108 -0
- package/src/pkg-root.ts +34 -0
- package/src/postgres.ts +79 -0
- package/src/preact/index.ts +27 -0
- package/src/redis.ts +43 -0
- package/src/ref.ts +271 -0
- package/src/remote-registry.ts +33 -0
- package/src/required-host-kinds.ts +110 -0
- package/src/resolve-context.ts +111 -0
- package/src/resolve.ts +471 -0
- package/src/runtime/docker/compose/compose-schema.ts +118 -0
- package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
- package/src/runtime/docker/compose/default-hardening.ts +21 -0
- package/src/runtime/docker/compose/effects.ts +107 -0
- package/src/runtime/docker/compose/index.ts +17 -0
- package/src/runtime/docker/compose/labels.ts +39 -0
- package/src/runtime/docker/compose/types.ts +78 -0
- package/src/runtime/docker/home-proxy-server/index.ts +177 -0
- package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
- package/src/runtime/docker/ingress/build-config.ts +819 -0
- package/src/runtime/docker/ingress/index.ts +16 -0
- package/src/runtime/docker/ingress/types.ts +182 -0
- package/src/runtime/docker/log-frames.ts +121 -0
- package/src/runtime/docker/logs-server/index.ts +366 -0
- package/src/runtime/docker/spawn-server/context-key.ts +82 -0
- package/src/runtime/docker/spawn-server/index.ts +492 -0
- package/src/runtime/docker/spawn-server/protocol.ts +70 -0
- package/src/runtime/docker/spawn-server/spec.ts +122 -0
- package/src/runtime/docker/stats-server/disks.ts +161 -0
- package/src/runtime/docker/stats-server/index.ts +322 -0
- package/src/runtime/home-client/index.ts +71 -0
- package/src/runtime/log-line.ts +24 -0
- package/src/runtime/logs-client/index.ts +175 -0
- package/src/runtime/spawn-client/index.ts +93 -0
- package/src/runtime/spawn-client/kaniko.ts +79 -0
- package/src/runtime/stats-client/index.ts +65 -0
- package/src/runtime/stats-sample.ts +95 -0
- package/src/safe-json.ts +37 -0
- package/src/sandbox.ts +1209 -0
- package/src/schema/build-apps.ts +38 -0
- package/src/schema/home.ts +217 -0
- package/src/schema/host.ts +792 -0
- package/src/schema/inputs.ts +26 -0
- package/src/schema/nodes.ts +75 -0
- package/src/schema/peer.ts +400 -0
- package/src/schema/public-domain.ts +116 -0
- package/src/schema/remote.ts +74 -0
- package/src/security.ts +594 -0
- package/src/serialize.ts +21 -0
- package/src/service-definition.ts +284 -0
- package/src/service-url.ts +64 -0
- package/src/service.ts +39 -0
- package/src/services/index.ts +5 -0
- package/src/shared.ts +65 -0
- package/src/speculative-silo.ts +96 -0
- package/src/stable-secret.ts +14 -0
- package/src/surfaces.ts +264 -0
- package/src/tailwind-bin.ts +56 -0
- package/src/test/boot.ts +192 -0
- package/src/test/compile.ts +501 -0
- package/src/test/describe.ts +117 -0
- package/src/test/index.ts +11 -0
- package/src/test/ingress-fixture.ts +37 -0
- package/src/test/types.ts +318 -0
- package/src/trusted-headers.ts +79 -0
- package/src/types.ts +126 -0
- package/src/ui/components/aside.tsx +96 -0
- package/src/ui/components/badge.tsx +42 -0
- package/src/ui/components/button.tsx +59 -0
- package/src/ui/components/card.tsx +106 -0
- package/src/ui/components/checkbox.tsx +37 -0
- package/src/ui/components/code-block.tsx +29 -0
- package/src/ui/components/combobox.tsx +119 -0
- package/src/ui/components/config-field.tsx +284 -0
- package/src/ui/components/dialog.tsx +137 -0
- package/src/ui/components/domain-editor.tsx +317 -0
- package/src/ui/components/domain-list.ts +205 -0
- package/src/ui/components/dropdown-menu.tsx +73 -0
- package/src/ui/components/empty-state.tsx +51 -0
- package/src/ui/components/highlight.ts +59 -0
- package/src/ui/components/icons.tsx +101 -0
- package/src/ui/components/image-posture.tsx +85 -0
- package/src/ui/components/input.tsx +28 -0
- package/src/ui/components/label.tsx +16 -0
- package/src/ui/components/line-chart.tsx +113 -0
- package/src/ui/components/page.tsx +39 -0
- package/src/ui/components/permission-row.tsx +262 -0
- package/src/ui/components/permission-section.tsx +51 -0
- package/src/ui/components/pie-chart.tsx +245 -0
- package/src/ui/components/schema-block.tsx +60 -0
- package/src/ui/components/separator.tsx +42 -0
- package/src/ui/components/sheet.tsx +130 -0
- package/src/ui/components/sidebar.tsx +650 -0
- package/src/ui/components/silo-picker.tsx +510 -0
- package/src/ui/components/skeleton.tsx +13 -0
- package/src/ui/components/sonner.tsx +33 -0
- package/src/ui/components/switch.tsx +58 -0
- package/src/ui/components/table-view.ts +219 -0
- package/src/ui/components/table.tsx +506 -0
- package/src/ui/components/tabs.tsx +59 -0
- package/src/ui/components/timeago.tsx +84 -0
- package/src/ui/components/tooltip.tsx +56 -0
- package/src/ui/components.json +21 -0
- package/src/ui/dev/dev-shell.tsx +145 -0
- package/src/ui/home/home-grid.tsx +177 -0
- package/src/ui/hooks/use-mobile.ts +19 -0
- package/src/ui/index.ts +189 -0
- package/src/ui/install/adapter.ts +193 -0
- package/src/ui/install/deploy-targets-picker.tsx +185 -0
- package/src/ui/install/install-app-panel.tsx +912 -0
- package/src/ui/install/install-error.tsx +62 -0
- package/src/ui/install/perms-inactive.ts +50 -0
- package/src/ui/install/required-fields.ts +37 -0
- package/src/ui/install/tabs-form.tsx +557 -0
- package/src/ui/tokens.css +279 -0
- package/src/ui/utils.ts +78 -0
- package/src/ui-error.ts +139 -0
- package/src/ui-runtime/dev-landing-entry.tsx +258 -0
- package/src/ui-runtime/index.ts +83 -0
- package/src/ui-runtime/install-entry.tsx +119 -0
- package/src/ui-runtime/mount.ts +367 -0
- package/src/ui-runtime/runtime.css +56 -0
- package/src/ui-surface.ts +451 -0
- package/src/valkey.ts +43 -0
- package/src/worker/extract-tar.ts +125 -0
- package/src/worker/identity.ts +183 -0
- package/src/worker/index.ts +16 -0
- package/src/worker/public-ip.ts +92 -0
- package/src/worker/queue.ts +54 -0
- package/src/worker/reconcile.ts +127 -0
- package/src/worker/start-worker.ts +227 -0
- package/src/worker/ui-proxy.ts +335 -0
- package/src/worker/uplink.ts +523 -0
- package/src/worker/validate.ts +34 -0
- package/src/worker/verify-relay.ts +102 -0
- package/src/worker-protocol/app-dirs.ts +94 -0
- package/src/worker-protocol/home-proxy-paths.ts +76 -0
- package/src/worker-protocol/index.ts +55 -0
- package/src/worker-protocol/ingress.ts +166 -0
- package/src/worker-protocol/logs-socket-path.ts +50 -0
- package/src/worker-protocol/networks.ts +39 -0
- package/src/worker-protocol/node-version.ts +70 -0
- package/src/worker-protocol/silo-paths.ts +114 -0
- package/src/worker-protocol/spawn-paths.ts +165 -0
- package/src/worker-protocol/stats-socket-path.ts +40 -0
- package/src/worker-protocol/update-files.ts +77 -0
- package/src/worker-protocol/uplink/constants.ts +86 -0
- package/src/worker-protocol/uplink/errors.ts +61 -0
- package/src/worker-protocol/uplink/frame.ts +114 -0
- package/src/worker-protocol/uplink/identity.ts +40 -0
- package/src/worker-protocol/uplink/index.ts +63 -0
- package/src/worker-protocol/uplink/methods.ts +462 -0
- package/src/worker-protocol/uplink/mux.ts +646 -0
- package/src/worker-protocol/uplink/protocol.ts +11 -0
- package/src/worker-protocol/uplink/rpc.ts +309 -0
- package/src/worker-protocol/uplink/session.ts +540 -0
- package/src/worker-protocol/uplink/test-support.ts +188 -0
- package/src/worker-protocol/worker-mount.ts +27 -0
- package/src/worker-protocol/worker-state.ts +190 -0
- package/src/xml.ts +49 -0
- package/src/yaml.ts +15 -0
package/src/dev/stack.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Shared compose-stack lifecycle: write a compose file, run
|
|
2
|
+
// `docker compose up -d --wait`, and tear down deterministically. Both
|
|
3
|
+
// `realm-sdk test --e2e` (test/boot.ts) and `realm-sdk dev` (dev/server.ts) go
|
|
4
|
+
// through it. The activeStacks registry and beforeunload hook are module-level
|
|
5
|
+
// so a process driving both code paths drains every stack on exit.
|
|
6
|
+
|
|
7
|
+
import { join } from "@std/path";
|
|
8
|
+
import { stringify as yamlStringify } from "yaml";
|
|
9
|
+
import type { ComposeConfig } from "../runtime/docker/compose/types.ts";
|
|
10
|
+
|
|
11
|
+
interface StackInputs {
|
|
12
|
+
/** Pre-built docker-compose object (services, networks, etc.). */
|
|
13
|
+
compose: ComposeConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Compose project name. Docker-compose-validates to a-z 0-9; pick
|
|
16
|
+
* something unique enough to avoid collisions across concurrent runs.
|
|
17
|
+
*/
|
|
18
|
+
project: string;
|
|
19
|
+
/** Working directory for the compose file. Caller owns its lifetime. */
|
|
20
|
+
tmpDir: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface StackHandle {
|
|
24
|
+
project: string;
|
|
25
|
+
composeFile: string;
|
|
26
|
+
/**
|
|
27
|
+
* Take down the stack and remove its volumes. Idempotent — calling
|
|
28
|
+
* twice (e.g. once from a test's finally + again from the exit hook)
|
|
29
|
+
* is safe.
|
|
30
|
+
*/
|
|
31
|
+
cleanup: () => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ActiveStack {
|
|
35
|
+
project: string;
|
|
36
|
+
composeFile: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const activeStacks = new Set<ActiveStack>();
|
|
40
|
+
let exitHookRegistered = false;
|
|
41
|
+
|
|
42
|
+
function registerExitHook(): void {
|
|
43
|
+
if (exitHookRegistered) return;
|
|
44
|
+
exitHookRegistered = true;
|
|
45
|
+
// Deno emits `beforeunload` on a clean exit; we attach there so any
|
|
46
|
+
// stack a caller forgot still goes down before the process leaves.
|
|
47
|
+
// Ctrl-C / uncaught throws don't fire this — `realm-sdk down` is the
|
|
48
|
+
// recovery path for those.
|
|
49
|
+
globalThis.addEventListener("beforeunload", () => {
|
|
50
|
+
for (const stack of activeStacks) {
|
|
51
|
+
// Sync best-effort: we can't await inside beforeunload, but
|
|
52
|
+
// `Deno.Command.outputSync` blocks the event loop until done.
|
|
53
|
+
try {
|
|
54
|
+
new Deno.Command("docker", {
|
|
55
|
+
args: ["compose", "-p", stack.project, "-f", stack.composeFile, "down", "-v"],
|
|
56
|
+
stdout: "null",
|
|
57
|
+
stderr: "null",
|
|
58
|
+
}).outputSync();
|
|
59
|
+
} catch {
|
|
60
|
+
// Ignore — best effort.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
activeStacks.clear();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function downStack(project: string, composeFile: string): Promise<void> {
|
|
68
|
+
const cmd = new Deno.Command("docker", {
|
|
69
|
+
args: ["compose", "-p", project, "-f", composeFile, "down", "-v"],
|
|
70
|
+
stdout: "null",
|
|
71
|
+
stderr: "null",
|
|
72
|
+
});
|
|
73
|
+
await cmd.output().catch(() => {});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function bringUpStack(inputs: StackInputs): Promise<StackHandle> {
|
|
77
|
+
const { compose, project, tmpDir } = inputs;
|
|
78
|
+
const composeFile = join(tmpDir, "docker-compose.yml");
|
|
79
|
+
await Deno.writeTextFile(composeFile, yamlStringify(compose as unknown));
|
|
80
|
+
|
|
81
|
+
registerExitHook();
|
|
82
|
+
|
|
83
|
+
const upCmd = new Deno.Command("docker", {
|
|
84
|
+
args: ["compose", "-p", project, "-f", composeFile, "up", "-d", "--wait"],
|
|
85
|
+
stdout: "piped",
|
|
86
|
+
stderr: "piped",
|
|
87
|
+
});
|
|
88
|
+
const upOut = await upCmd.output();
|
|
89
|
+
if (upOut.code !== 0) {
|
|
90
|
+
const stderr = new TextDecoder().decode(upOut.stderr);
|
|
91
|
+
const stdout = new TextDecoder().decode(upOut.stdout);
|
|
92
|
+
// Best-effort cleanup before throwing — leaving the stack up after
|
|
93
|
+
// a failure makes the next run fight over names.
|
|
94
|
+
await downStack(project, composeFile);
|
|
95
|
+
throw new Error(`docker compose up failed (exit ${upOut.code})\n${stdout}\n${stderr}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const stack: ActiveStack = { project, composeFile };
|
|
99
|
+
activeStacks.add(stack);
|
|
100
|
+
|
|
101
|
+
const cleanup = async () => {
|
|
102
|
+
if (!activeStacks.has(stack)) return;
|
|
103
|
+
activeStacks.delete(stack);
|
|
104
|
+
await downStack(project, composeFile);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return { project, composeFile, cleanup };
|
|
108
|
+
}
|
package/src/dev/state.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Shared compile + bring-up plumbing for `realm-sdk dev`. The HTTP server
|
|
2
|
+
// (dev/server.ts) and the in-process install handler (dev/install-handler.ts)
|
|
3
|
+
// both go through it, so they can't drift on how a realm.ts becomes a bundled,
|
|
4
|
+
// compiled, compose-running stack: `compileAndBundle` returns a `DevState` and
|
|
5
|
+
// `bringUp` runs `docker compose up -d --wait` against the resolved state.
|
|
6
|
+
|
|
7
|
+
import type { Grants } from "../home.ts";
|
|
8
|
+
import { bundle } from "../bundle.ts";
|
|
9
|
+
import { bundleUiSurfaces, type UiSurfaceBundle } from "../bundle-ui.ts";
|
|
10
|
+
import { mergeSurfaces } from "../surfaces.ts";
|
|
11
|
+
import { compile } from "../test/compile.ts";
|
|
12
|
+
import { convertToDockerCompose } from "../runtime/docker/compose/index.ts";
|
|
13
|
+
import { extractAppName } from "../sandbox.ts";
|
|
14
|
+
import { type CompileIssue, formatIssue } from "../errors.ts";
|
|
15
|
+
import type { HomeApp } from "../resolve.ts";
|
|
16
|
+
import type { Violation } from "../security.ts";
|
|
17
|
+
import type { IngressInput, WorkerApp, WorkerState } from "../worker-protocol/index.ts";
|
|
18
|
+
import { fixtureIngressInput } from "../test/ingress-fixture.ts";
|
|
19
|
+
import { bringUpStack } from "./stack.ts";
|
|
20
|
+
import { loadDevFixture, stableDevSeed } from "./fixture.ts";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compile failure thrown by `compileAndBundle`. Carries the original
|
|
24
|
+
* issue list (for the human-readable stderr summary on dev boot / file
|
|
25
|
+
* watcher reloads) and the raw violations (so the install panel can
|
|
26
|
+
* render the structured per-permission list instead of a flat blob).
|
|
27
|
+
*/
|
|
28
|
+
export class DevCompileError extends Error {
|
|
29
|
+
constructor(
|
|
30
|
+
message: string,
|
|
31
|
+
public readonly issues: readonly CompileIssue[],
|
|
32
|
+
public readonly violations: readonly Violation[],
|
|
33
|
+
) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "DevCompileError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DevState {
|
|
40
|
+
appId: string;
|
|
41
|
+
appName: string;
|
|
42
|
+
source: string;
|
|
43
|
+
resolvedState: HomeApp;
|
|
44
|
+
bundles: Map<string, UiSurfaceBundle>;
|
|
45
|
+
/** Map of `${kind}:${key}` → hash. */
|
|
46
|
+
surfaceHashes: Map<string, string>;
|
|
47
|
+
/**
|
|
48
|
+
* This box's ingress, off the dev fixture's `management` block. Carried on
|
|
49
|
+
* the state because `bringUp` needs it for the wire and the fixture is read
|
|
50
|
+
* here - it is the one piece of the wire dev cannot reconstruct downstream.
|
|
51
|
+
*/
|
|
52
|
+
ingress: IngressInput;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface CompileAndBundleOpts {
|
|
56
|
+
cwd: string;
|
|
57
|
+
realmFile: string;
|
|
58
|
+
/**
|
|
59
|
+
* In-flight install POST from the panel. When set, `source` is the packed
|
|
60
|
+
* artifact the panel pre-filled (and the operator may have edited) — used
|
|
61
|
+
* verbatim, not re-read from disk — and `config` / `grants` replace the
|
|
62
|
+
* on-disk fixture's values for this one compile. `handleInstall` persists
|
|
63
|
+
* them to `realm-sdk.dev.ts` immediately after, so subsequent reloads pick
|
|
64
|
+
* them up from disk normally. `peers` + `seed` always come from the disk
|
|
65
|
+
* fixture so peer-world resolution and password derivation match the
|
|
66
|
+
* file-watcher path.
|
|
67
|
+
*/
|
|
68
|
+
override?: {
|
|
69
|
+
source: string;
|
|
70
|
+
config: Record<string, unknown>;
|
|
71
|
+
grants: Grants;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function compileAndBundle(opts: CompileAndBundleOpts): Promise<DevState> {
|
|
76
|
+
// The install panel POSTs the packed artifact it pre-filled (what actually
|
|
77
|
+
// ships), so Preview and Install read the exact same bytes. The file-watcher
|
|
78
|
+
// path has no override and merges the app's live files off disk instead. Both
|
|
79
|
+
// feed bundle(), which unpacks + merges a packed source and passes a plain
|
|
80
|
+
// one straight through.
|
|
81
|
+
const source = opts.override ? opts.override.source : (await mergeSurfaces(opts.cwd)).source;
|
|
82
|
+
const bundledCode = await bundle(source);
|
|
83
|
+
|
|
84
|
+
const { fixture: rawFixture, source: fixtureSource } = await loadDevFixture(opts.cwd);
|
|
85
|
+
const fixture = {
|
|
86
|
+
...rawFixture,
|
|
87
|
+
seed: rawFixture.seed ?? stableDevSeed(opts.cwd),
|
|
88
|
+
...(opts.override
|
|
89
|
+
? {
|
|
90
|
+
config: opts.override.config as never,
|
|
91
|
+
grants: opts.override.grants as never,
|
|
92
|
+
}
|
|
93
|
+
: {}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const appName = await extractAppName(bundledCode);
|
|
97
|
+
const appId = fixture.appId ?? appName;
|
|
98
|
+
|
|
99
|
+
const result = await compile({ bundledCode, appName, fixture });
|
|
100
|
+
if (result.error) {
|
|
101
|
+
const lines = [
|
|
102
|
+
`compile failed${fixtureSource ? ` (using ${fixtureSource})` : " (no realm-sdk.dev.ts found — using empty fixture)"}:`,
|
|
103
|
+
...result.issues.map((i) => ` - ${formatIssue(i)}`),
|
|
104
|
+
];
|
|
105
|
+
throw new DevCompileError(lines.join("\n"), result.issues, result.violations);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const surfaces = await bundleUiSurfaces(source, bundledCode);
|
|
109
|
+
const bundles = new Map<string, UiSurfaceBundle>();
|
|
110
|
+
const surfaceHashes = new Map<string, string>();
|
|
111
|
+
for (const b of surfaces) {
|
|
112
|
+
bundles.set(b.hash, b);
|
|
113
|
+
surfaceHashes.set(`${b.surface.kind}:${b.surface.key}`, b.hash);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
appId,
|
|
118
|
+
appName,
|
|
119
|
+
source,
|
|
120
|
+
resolvedState: result.app,
|
|
121
|
+
bundles,
|
|
122
|
+
surfaceHashes,
|
|
123
|
+
ingress: fixtureIngressInput(fixture.management),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function bringUp(state: DevState, tmpDir: string) {
|
|
128
|
+
const workerApp: WorkerApp = {
|
|
129
|
+
appId: state.appId,
|
|
130
|
+
// Dev renders in-process and never serializes to the wire, and the
|
|
131
|
+
// renderer reads only the fields it knows, so the resolve model's extra
|
|
132
|
+
// Home-only fields are simply ignored — cast straight to the wire shape.
|
|
133
|
+
services: state.resolvedState.services as unknown as WorkerApp["services"],
|
|
134
|
+
...(state.resolvedState.egress ? { egress: true } : {}),
|
|
135
|
+
externalNetworks: [],
|
|
136
|
+
dependsOn: {},
|
|
137
|
+
inlineFiles: {},
|
|
138
|
+
allowedAbsoluteHostPaths: [],
|
|
139
|
+
};
|
|
140
|
+
const wireState: WorkerState = {
|
|
141
|
+
apps: [workerApp],
|
|
142
|
+
artifactHashes: [],
|
|
143
|
+
workerNetworks: [],
|
|
144
|
+
ingress: state.ingress,
|
|
145
|
+
};
|
|
146
|
+
// Single-app harness: the renderer returns one project per app, and
|
|
147
|
+
// dev only ever has one.
|
|
148
|
+
const compose = convertToDockerCompose(wireState)[0]!.compose;
|
|
149
|
+
// Mark every service so `realm-sdk down` (com.realm.test=true) won't
|
|
150
|
+
// sweep them, and so an operator can grep their docker ps. Also inject
|
|
151
|
+
// REALM_SDK_DEV=1 so apps can opt into dev-only behavior (synthetic
|
|
152
|
+
// fixtures, skipping connections that need a real worker, etc.).
|
|
153
|
+
for (const svc of Object.values(compose.services)) {
|
|
154
|
+
svc.labels = { ...svc.labels, "com.realm.dev": "true" };
|
|
155
|
+
svc.environment = { ...svc.environment, REALM_SDK_DEV: "1" };
|
|
156
|
+
}
|
|
157
|
+
const project = `realm-dev-${state.appId}`;
|
|
158
|
+
return bringUpStack({ compose, project, tmpDir });
|
|
159
|
+
}
|
package/src/docker.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Docker registry v2 gate helpers — for use inside `verifyAnonymousRequest`
|
|
2
|
+
// when an app fronts an OCI registry (e.g. Forge's Zot). A `docker pull`
|
|
3
|
+
// answers a 401-with-Basic-challenge with `Authorization: Basic
|
|
4
|
+
// base64(username:password)`; `checkDockerBasic` compares that password to the
|
|
5
|
+
// app's `stableSecret(...)`, and `dockerChallenge` is the 401 that triggers it.
|
|
6
|
+
//
|
|
7
|
+
// These run in the QuickJS gate sandbox, which shims only
|
|
8
|
+
// Request/Response/Headers/URL — there is no `atob` and no Web Crypto — so the
|
|
9
|
+
// code is pure JS: base64 is decoded by hand and the compare is a plain loop.
|
|
10
|
+
// Keep it that way (no `Deno`/`atob`/`TextEncoder`) or the gate bundle breaks
|
|
11
|
+
// at load under QuickJS.
|
|
12
|
+
|
|
13
|
+
/** Standard-base64 alphabet (RFC 4648). */
|
|
14
|
+
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Decode standard base64 to a Latin-1 string, or `null` on an invalid
|
|
18
|
+
* character. Pure JS (no `atob`) so it runs in the gate sandbox. Exact for
|
|
19
|
+
* well-formed base64; looser than `atob` on a malformed *length* — a dangling
|
|
20
|
+
* final partial group is dropped rather than rejected. That only ever drops a
|
|
21
|
+
* trailing group (never alters earlier bytes), so it's safe for this
|
|
22
|
+
* fail-closed credential check: a bad credential still can't match the secret.
|
|
23
|
+
* Registry credentials are ASCII (`username:hexsecret`), so the byte-as-charcode
|
|
24
|
+
* string round-trips them exactly.
|
|
25
|
+
*/
|
|
26
|
+
function decodeBase64(input: string): string | null {
|
|
27
|
+
let s = input;
|
|
28
|
+
// Drop CR/LF and trailing `=` padding; the bit accumulator handles the rest.
|
|
29
|
+
s = s.replace(/[\r\n]/g, "");
|
|
30
|
+
while (s.length > 0 && s[s.length - 1] === "=") s = s.slice(0, -1);
|
|
31
|
+
|
|
32
|
+
let out = "";
|
|
33
|
+
let acc = 0;
|
|
34
|
+
let bits = 0;
|
|
35
|
+
for (let i = 0; i < s.length; i++) {
|
|
36
|
+
const idx = B64.indexOf(s[i]!);
|
|
37
|
+
if (idx < 0) return null;
|
|
38
|
+
acc = (acc << 6) | idx;
|
|
39
|
+
bits += 6;
|
|
40
|
+
if (bits >= 8) {
|
|
41
|
+
bits -= 8;
|
|
42
|
+
out += String.fromCharCode((acc >> bits) & 0xff);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Length-then-XOR compare; no early return on the first differing byte. */
|
|
49
|
+
function constantTimeEqual(a: string, b: string): boolean {
|
|
50
|
+
if (a.length !== b.length) return false;
|
|
51
|
+
let diff = 0;
|
|
52
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
53
|
+
return diff === 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `true` when the request carries an `Authorization: Basic` header whose
|
|
58
|
+
* password half equals `expectedSecret`. The username half is ignored — the
|
|
59
|
+
* single shared `stableSecret(...)` is the whole credential, so any username
|
|
60
|
+
* with the right password passes. Anything malformed (no header, wrong scheme,
|
|
61
|
+
* bad base64, no colon) is a plain `false`, so the caller falls through to
|
|
62
|
+
* {@link dockerChallenge}.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* verifyAnonymousRequest: (request, stableSecret) =>
|
|
66
|
+
* checkDockerBasic(request, stableSecret("registry")) || dockerChallenge();
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function checkDockerBasic(request: Request, expectedSecret: string): boolean {
|
|
70
|
+
const header = request.headers.get("authorization");
|
|
71
|
+
if (!header) return false;
|
|
72
|
+
const space = header.indexOf(" ");
|
|
73
|
+
if (space < 0) return false;
|
|
74
|
+
if (header.slice(0, space).toLowerCase() !== "basic") return false;
|
|
75
|
+
|
|
76
|
+
const decoded = decodeBase64(header.slice(space + 1).trim());
|
|
77
|
+
if (decoded == null) return false;
|
|
78
|
+
const colon = decoded.indexOf(":");
|
|
79
|
+
if (colon < 0) return false;
|
|
80
|
+
return constantTimeEqual(decoded.slice(colon + 1), expectedSecret);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The `401` that makes a `docker pull` retry with Basic credentials: an empty
|
|
85
|
+
* body plus `WWW-Authenticate: Basic`. Return it from `verifyAnonymousRequest`
|
|
86
|
+
* for an unauthenticated (or wrong-credential) registry request.
|
|
87
|
+
*/
|
|
88
|
+
export function dockerChallenge(realm = "Realm registry"): Response {
|
|
89
|
+
return new Response(null, {
|
|
90
|
+
status: 401,
|
|
91
|
+
headers: {
|
|
92
|
+
"WWW-Authenticate": `Basic realm="${realm}"`,
|
|
93
|
+
"Docker-Distribution-Api-Version": "registry/2.0",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error contract for the realm.ts → state compile pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Modeled on Zod: one class with a list of issues, where each issue is a
|
|
5
|
+
* discriminated union keyed on `kind`. Single-issue throw sites pass one
|
|
6
|
+
* issue; accumulating boundaries (Phase 2 `compile()`, multi-violation
|
|
7
|
+
* grant checks) pass a list. The list is always the public surface — a
|
|
8
|
+
* one-issue error is just a one-element list.
|
|
9
|
+
*
|
|
10
|
+
* No `publicMessage` on the throw site. The HTTP boundary decides what's
|
|
11
|
+
* safe to render per-kind in one place; throw sites just describe what
|
|
12
|
+
* happened.
|
|
13
|
+
*/
|
|
14
|
+
export type CompileIssue =
|
|
15
|
+
| { kind: "eval"; message: string; line?: number; column?: number }
|
|
16
|
+
| { kind: "validation"; path: string[]; message: string }
|
|
17
|
+
| { kind: "permission"; missing: string };
|
|
18
|
+
|
|
19
|
+
export class RealmCompileError extends Error {
|
|
20
|
+
readonly issues: readonly CompileIssue[];
|
|
21
|
+
|
|
22
|
+
constructor(input: CompileIssue | CompileIssue[]) {
|
|
23
|
+
const list = Array.isArray(input) ? input : [input];
|
|
24
|
+
super(formatHeader(list));
|
|
25
|
+
this.issues = Object.freeze([...list]) as readonly CompileIssue[];
|
|
26
|
+
this.name = "RealmCompileError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A `host.silo` require-map the Silo doesn't meet yet. Its own subclass so
|
|
32
|
+
* the eval pass structure can tell it apart from a permanent failure: a
|
|
33
|
+
* provider that evaluates later may satisfy it, so this is the one drop the
|
|
34
|
+
* fixpoint loop retries.
|
|
35
|
+
*
|
|
36
|
+
* Lives here rather than in Home because the thrower is `host.silo`'s
|
|
37
|
+
* resolver, which lives in the SDK.
|
|
38
|
+
*/
|
|
39
|
+
export class SiloRequiresError extends RealmCompileError {}
|
|
40
|
+
|
|
41
|
+
function formatHeader(issues: readonly CompileIssue[]): string {
|
|
42
|
+
if (issues.length === 1) return formatIssue(issues[0]!);
|
|
43
|
+
return `${issues.length} compile errors`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Render a single issue as a human-readable string. Used for the
|
|
48
|
+
* Error.message header (stack traces, logs) - NOT for HTTP responses,
|
|
49
|
+
* which redact eval issues to a generic string. The HTTP boundary
|
|
50
|
+
* lives in Home's `parseRealmError`.
|
|
51
|
+
*/
|
|
52
|
+
export function formatIssue(issue: CompileIssue): string {
|
|
53
|
+
switch (issue.kind) {
|
|
54
|
+
case "eval":
|
|
55
|
+
return issue.message;
|
|
56
|
+
case "validation":
|
|
57
|
+
return `${issue.path.join(".")}: ${issue.message}`;
|
|
58
|
+
case "permission":
|
|
59
|
+
return `missing grant: ${issue.missing}`;
|
|
60
|
+
}
|
|
61
|
+
}
|