@telorun/runner-core 0.5.2
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 +17 -0
- package/README.md +38 -0
- package/dist/backend.d.ts +71 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +2 -0
- package/dist/backend.js.map +1 -0
- package/dist/base-image-catalog.d.ts +110 -0
- package/dist/base-image-catalog.d.ts.map +1 -0
- package/dist/base-image-catalog.js +245 -0
- package/dist/base-image-catalog.js.map +1 -0
- package/dist/capabilities-schema.d.ts +33 -0
- package/dist/capabilities-schema.d.ts.map +1 -0
- package/dist/capabilities-schema.js +44 -0
- package/dist/capabilities-schema.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +93 -0
- package/dist/config.js.map +1 -0
- package/dist/contract.d.ts +170 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +24 -0
- package/dist/contract.js.map +1 -0
- package/dist/debug/relay.d.ts +25 -0
- package/dist/debug/relay.d.ts.map +1 -0
- package/dist/debug/relay.js +89 -0
- package/dist/debug/relay.js.map +1 -0
- package/dist/dependency-key.d.ts +38 -0
- package/dist/dependency-key.d.ts.map +1 -0
- package/dist/dependency-key.js +68 -0
- package/dist/dependency-key.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/capabilities.d.ts +12 -0
- package/dist/routes/capabilities.d.ts.map +1 -0
- package/dist/routes/capabilities.js +15 -0
- package/dist/routes/capabilities.js.map +1 -0
- package/dist/routes/health.d.ts +5 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +8 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/io.d.ts +8 -0
- package/dist/routes/io.d.ts.map +1 -0
- package/dist/routes/io.js +239 -0
- package/dist/routes/io.js.map +1 -0
- package/dist/routes/probe.d.ts +7 -0
- package/dist/routes/probe.d.ts.map +1 -0
- package/dist/routes/probe.js +22 -0
- package/dist/routes/probe.js.map +1 -0
- package/dist/routes/sessions.d.ts +22 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +223 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/server.d.ts +34 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/dist/session/bundle-path.d.ts +12 -0
- package/dist/session/bundle-path.d.ts.map +1 -0
- package/dist/session/bundle-path.js +27 -0
- package/dist/session/bundle-path.js.map +1 -0
- package/dist/session/byte-ring-buffer.d.ts +29 -0
- package/dist/session/byte-ring-buffer.d.ts.map +1 -0
- package/dist/session/byte-ring-buffer.js +54 -0
- package/dist/session/byte-ring-buffer.js.map +1 -0
- package/dist/session/registry.d.ts +62 -0
- package/dist/session/registry.d.ts.map +1 -0
- package/dist/session/registry.js +156 -0
- package/dist/session/registry.js.map +1 -0
- package/dist/session/ring-buffer.d.ts +37 -0
- package/dist/session/ring-buffer.d.ts.map +1 -0
- package/dist/session/ring-buffer.js +64 -0
- package/dist/session/ring-buffer.js.map +1 -0
- package/dist/session/session-id.d.ts +3 -0
- package/dist/session/session-id.d.ts.map +1 -0
- package/dist/session/session-id.js +19 -0
- package/dist/session/session-id.js.map +1 -0
- package/dist/sse/channel.d.ts +11 -0
- package/dist/sse/channel.d.ts.map +1 -0
- package/dist/sse/channel.js +129 -0
- package/dist/sse/channel.js.map +1 -0
- package/package.json +48 -0
- package/src/backend.ts +88 -0
- package/src/base-image-catalog.test.ts +209 -0
- package/src/base-image-catalog.ts +320 -0
- package/src/capabilities-schema.test.ts +54 -0
- package/src/capabilities-schema.ts +71 -0
- package/src/config.ts +122 -0
- package/src/contract.ts +170 -0
- package/src/debug/relay.ts +104 -0
- package/src/dependency-key.test.ts +64 -0
- package/src/dependency-key.ts +105 -0
- package/src/index.ts +33 -0
- package/src/routes/capabilities.ts +20 -0
- package/src/routes/health.ts +9 -0
- package/src/routes/io.ts +265 -0
- package/src/routes/probe.ts +35 -0
- package/src/routes/sessions.ts +270 -0
- package/src/server.ts +108 -0
- package/src/session/bundle-path.ts +27 -0
- package/src/session/byte-ring-buffer.ts +62 -0
- package/src/session/registry.test.ts +34 -0
- package/src/session/registry.ts +185 -0
- package/src/session/ring-buffer.test.ts +54 -0
- package/src/session/ring-buffer.ts +75 -0
- package/src/session/session-id.test.ts +17 -0
- package/src/session/session-id.ts +20 -0
- package/src/sse/channel.ts +154 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SUSTAINABLE USE LICENSE (Fair-code)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CodeNet Sp. z o.o.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute the Software for any purpose—including commercial purposes—subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
1. ANTI-COMPETITION RESTRICTION: The Software may not be provided to third parties as a managed service, commercial SaaS (Software-as-a-Service), PaaS (Platform-as-a-Service), BaaS (Backend-as-a-Service), or similar offering where the primary value provided to the user is the functionality of the Software itself, without a separate commercial license from the copyright holder.
|
|
8
|
+
|
|
9
|
+
2. PERMITTED COMMERCIAL USE: You are free to use the Software to build, host, and monetize your own commercial applications, products, and services, provided such use does not violate Clause 1.
|
|
10
|
+
|
|
11
|
+
3. ATTRIBUTION: This copyright notice and license must be included in all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
4. CONTRIBUTIONS: Contributions to the Software are welcome and encouraged. By contributing, you agree that your contributions may be incorporated into the Software and distributed under this license.
|
|
14
|
+
|
|
15
|
+
5. DISCLAIMER: The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.
|
|
16
|
+
|
|
17
|
+
For commercial licensing, managed hosting exemptions, or enterprise inquiries, please contact <contact@codenet.pl>.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @telorun/runner-core
|
|
2
|
+
|
|
3
|
+
Backend-neutral core shared by the Telo runners ([`docker-runner`](../../apps/docker-runner)
|
|
4
|
+
and [`k8s-runner`](../../apps/k8s-runner)). It owns everything about a run
|
|
5
|
+
*except* how the workload is actually spawned:
|
|
6
|
+
|
|
7
|
+
- the `/v1` HTTP+SSE **session contract** and Fastify routes (`/v1/health`,
|
|
8
|
+
`/v1/probe`, `/v1/sessions`, `/v1/sessions/:id/events` SSE,
|
|
9
|
+
`/v1/sessions/:id/io` WebSocket PTY);
|
|
10
|
+
- the in-memory **session registry**, event/byte **ring buffers** (SSE + PTY
|
|
11
|
+
replay with gap detection), and bundle-path traversal guards;
|
|
12
|
+
- graceful-shutdown helpers and base config parsing.
|
|
13
|
+
|
|
14
|
+
## The seam: `RunnerBackend`
|
|
15
|
+
|
|
16
|
+
Concrete runners implement a small abstract interface — a byte-stream out
|
|
17
|
+
(`onOutput`), a stdin writer, a resize signal, and a wait/exit (`done`) — *not*
|
|
18
|
+
any docker- or k8s-specific stream shape:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
interface RunnerBackend {
|
|
22
|
+
probe(config): Promise<AvailabilityReport>;
|
|
23
|
+
start(spec): Promise<BackendSession>; // writeStdin / resize / done / stop
|
|
24
|
+
reapOrphans?(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Bundle delivery is the backend's responsibility (docker writes a shared-volume
|
|
29
|
+
workdir; k8s stages a tarball for an initContainer fetch), so `start` receives
|
|
30
|
+
the raw `bundle` rather than a pre-resolved path. `buildServer({ backend, config,
|
|
31
|
+
version })` wires a backend into the full `/v1` app.
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm --filter @telorun/runner-core build
|
|
37
|
+
pnpm --filter @telorun/runner-core test
|
|
38
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { DebugFrame } from "@telorun/debug-wire";
|
|
2
|
+
import type { AvailabilityReport, PortMapping, ProbeConfig, RunBundle, RunPhase, RunStatus, SessionConfig } from "./contract.js";
|
|
3
|
+
/**
|
|
4
|
+
* The seam between backend-neutral session machinery (routes, registry, SSE)
|
|
5
|
+
* and a concrete workload runtime (docker container, kubernetes pod).
|
|
6
|
+
*
|
|
7
|
+
* Deliberately abstract: a byte-stream out (`onOutput`), a stdin writer
|
|
8
|
+
* (`BackendSession.writeStdin`), a resize signal, and a wait/exit (`done`) —
|
|
9
|
+
* NOT docker's `ReadWriteStream` duplex. The k8s backend serves the same shape
|
|
10
|
+
* over the Pod `attach` subresource; the docker backend adapts its hijacked
|
|
11
|
+
* attach duplex onto it. Bundle delivery is the backend's responsibility too
|
|
12
|
+
* (docker writes a shared-volume workdir; k8s stages the bundle for an
|
|
13
|
+
* initContainer fetch), so the spec carries the raw `bundle` rather than a
|
|
14
|
+
* pre-resolved on-disk path.
|
|
15
|
+
*/
|
|
16
|
+
export interface RunnerBackend {
|
|
17
|
+
/** Report readiness without starting a session (daemon/cluster reachable,
|
|
18
|
+
* image pullable, scaffolding present). Backs `POST /v1/probe`. */
|
|
19
|
+
probe(config: ProbeConfig): Promise<AvailabilityReport>;
|
|
20
|
+
/** Spawn the workload and wire it to the supplied callbacks. Resolves once
|
|
21
|
+
* the workload has started (after a `running` status is emitted); rejects
|
|
22
|
+
* with `SessionStartError` for any pre-start failure. */
|
|
23
|
+
start(spec: BackendStartSpec): Promise<BackendSession>;
|
|
24
|
+
/** Reap workloads orphaned by a prior runner process, matched by the
|
|
25
|
+
* backend's own labelling. Run once at boot — the session registry is
|
|
26
|
+
* in-memory, so a restart otherwise leaks running workloads. */
|
|
27
|
+
reapOrphans?(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface BackendStartSpec {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
bundle: RunBundle;
|
|
32
|
+
/** Bundle-relative entry path, already traversal-normalized by core. */
|
|
33
|
+
entryRelativePath: string;
|
|
34
|
+
env: Record<string, string>;
|
|
35
|
+
ports: PortMapping[];
|
|
36
|
+
config: SessionConfig;
|
|
37
|
+
/** When true, launch the workload with `--inspect` and relay its kernel debug
|
|
38
|
+
* stream via `onDebug`. The inspect endpoint stays reachable only by the
|
|
39
|
+
* runner — never published outward. */
|
|
40
|
+
inspect: boolean;
|
|
41
|
+
/** Emit a lifecycle status. The backend drives `starting` → `running` →
|
|
42
|
+
* terminal (`exited`/`failed`/`stopped`). */
|
|
43
|
+
onStatus(status: RunStatus): void;
|
|
44
|
+
/** Emit a progress message for a coming-up phase (build / provision / boot).
|
|
45
|
+
* Additive to status — surfaces what's happening while the session is still
|
|
46
|
+
* `starting`, driving the editor's spinner + step feed. */
|
|
47
|
+
onProgress(phase: RunPhase, message: string, done?: boolean): void;
|
|
48
|
+
/** Raw merged stdout/stderr (PTY) bytes from the workload. */
|
|
49
|
+
onOutput(chunk: Buffer): void;
|
|
50
|
+
/** A frame relayed from the workload's kernel debug stream. Only called when
|
|
51
|
+
* `inspect` is true and the backend has connected to the inspect endpoint. */
|
|
52
|
+
onDebug(frame: DebugFrame): void;
|
|
53
|
+
/** True once a user stop / shutdown has been requested — lets the backend
|
|
54
|
+
* classify a kill as `stopped` rather than `failed`. */
|
|
55
|
+
isUserStopped(): boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface BackendSession {
|
|
58
|
+
/** Write bytes to the workload's stdin (PTY). A no-op once the workload has
|
|
59
|
+
* terminated, so callers need not track liveness. */
|
|
60
|
+
writeStdin(bytes: Uint8Array): void;
|
|
61
|
+
/** Resize the workload's PTY. */
|
|
62
|
+
resize(cols: number, rows: number): void;
|
|
63
|
+
/** Resolves after the workload terminates and its terminal status has been
|
|
64
|
+
* emitted via `onStatus`. Never rejects — terminal failures surface as a
|
|
65
|
+
* `failed` status. */
|
|
66
|
+
readonly done: Promise<void>;
|
|
67
|
+
/** Force-stop the workload. Idempotent and safe to call after natural exit
|
|
68
|
+
* (a backend should treat an already-gone workload as a no-op). */
|
|
69
|
+
stop(): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,SAAS,EACT,QAAQ,EACR,SAAS,EACT,aAAa,EACd,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B;wEACoE;IACpE,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAExD;;8DAE0D;IAC1D,KAAK,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAEvD;;qEAEiE;IACjE,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,wEAAwE;IACxE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,aAAa,CAAC;IACtB;;4CAEwC;IACxC,OAAO,EAAE,OAAO,CAAC;IAEjB;kDAC8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAAC;IAClC;;gEAE4D;IAC5D,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnE,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;mFAC+E;IAC/E,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC;6DACyD;IACzD,aAAa,IAAI,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B;0DACsD;IACtD,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAEpC,iCAAiC;IACjC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzC;;2BAEuB;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;wEACoE;IACpE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB"}
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A base-image catalog backed by the Docker Hub repository-tags API. A runner
|
|
3
|
+
* advertises the resolved tag list as an `enum` on its session `image` field so
|
|
4
|
+
* the editor renders a base-image picker, and validates a chosen image against
|
|
5
|
+
* the same list server-side — the runner is the source of truth, so a non-editor
|
|
6
|
+
* client can't widen the allowed set.
|
|
7
|
+
*
|
|
8
|
+
* The list is fetched at startup and on a refresh interval, cached in memory,
|
|
9
|
+
* and degrades gracefully: when Docker Hub is unreachable the catalog still
|
|
10
|
+
* serves the configured default (always present), so the runner boots and a
|
|
11
|
+
* session can still start on the default image.
|
|
12
|
+
*
|
|
13
|
+
* Free of runner specifics — it takes only a repository, a default ref, and a
|
|
14
|
+
* tag filter — but the tag source is specifically Docker Hub's registry API
|
|
15
|
+
* (`hub.docker.com/v2`); a base on another registry (GHCR, a private registry)
|
|
16
|
+
* can't be enumerated or digest-resolved here. The Docker registry the kubelet
|
|
17
|
+
* pulls from is a separate concern; this resolves the *menu* of base images, not
|
|
18
|
+
* pull auth.
|
|
19
|
+
*/
|
|
20
|
+
/** A composable predicate set narrowing a repository's tag list to the offered
|
|
21
|
+
* menu. Named flags cover the common cases; `include`/`exclude` are the regex
|
|
22
|
+
* escape hatch for everything else. */
|
|
23
|
+
export interface TagFilter {
|
|
24
|
+
/** Keep only `MAJOR.MINOR.PATCH[-variant]` tags — drops moving tags (`latest`,
|
|
25
|
+
* `0`, `0.30`) and their variants (`latest-slim`, `0-slim`). */
|
|
26
|
+
pinnedOnly?: boolean;
|
|
27
|
+
/** Drop commit-hash tags (`sha-<hex>` or a bare hex string). */
|
|
28
|
+
excludeSha?: boolean;
|
|
29
|
+
/** Drop semver prereleases (`0.30.1-rc.1`, `…-alpha`). Variant suffixes like
|
|
30
|
+
* `-slim` / `-rust-1.95.0` are NOT prereleases and are kept. */
|
|
31
|
+
excludePrerelease?: boolean;
|
|
32
|
+
/** A tag must match at least one of these to be kept. */
|
|
33
|
+
include?: RegExp[];
|
|
34
|
+
/** A tag matching any of these is dropped. */
|
|
35
|
+
exclude?: RegExp[];
|
|
36
|
+
}
|
|
37
|
+
export interface BaseImageCatalogOptions {
|
|
38
|
+
/** `namespace/repository`, e.g. `telorun/node`. */
|
|
39
|
+
repository: string;
|
|
40
|
+
/** Always-present default ref — the pre-selected option and the
|
|
41
|
+
* Docker-Hub-unreachable fallback, e.g. `telorun/node:latest-slim`. */
|
|
42
|
+
defaultRef: string;
|
|
43
|
+
filter?: TagFilter;
|
|
44
|
+
/** Cap the advertised list to this many tags (newest first). Default 20. */
|
|
45
|
+
limit?: number;
|
|
46
|
+
/** Background refresh cadence in ms. Default 1h. */
|
|
47
|
+
refreshIntervalMs?: number;
|
|
48
|
+
/** Upper bound on tags pulled from Docker Hub before filtering. Default 200. */
|
|
49
|
+
maxTagsScanned?: number;
|
|
50
|
+
/** Per-request timeout for a Docker Hub call, in ms. Default 10s. */
|
|
51
|
+
requestTimeoutMs?: number;
|
|
52
|
+
/** Injectable fetch for tests; defaults to the global `fetch`. */
|
|
53
|
+
fetchImpl?: typeof fetch;
|
|
54
|
+
}
|
|
55
|
+
/** Apply a {@link TagFilter} to a tag list, preserving input order. Pure — the
|
|
56
|
+
* unit-testable core of the catalog. */
|
|
57
|
+
export declare function filterTags(tags: string[], filter?: TagFilter): string[];
|
|
58
|
+
/**
|
|
59
|
+
* Parse a Docker image reference into the Docker Hub coordinates needed to look
|
|
60
|
+
* up its digest, or `null` when the ref points at a non-Docker-Hub registry.
|
|
61
|
+
* Handles the `library/` shorthand, optional `docker.io` host, and an already
|
|
62
|
+
* `@sha256:`-pinned digest (carried through verbatim).
|
|
63
|
+
*/
|
|
64
|
+
export declare function parseDockerHubRef(ref: string): {
|
|
65
|
+
namespace: string;
|
|
66
|
+
repo: string;
|
|
67
|
+
tag: string;
|
|
68
|
+
digest?: string;
|
|
69
|
+
} | null;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a Docker Hub image ref's CURRENT manifest digest. A moving tag
|
|
72
|
+
* (`latest-slim`) maps to a new digest once a new version is published, so a
|
|
73
|
+
* build that folds this into its cache key detects that the base moved and
|
|
74
|
+
* rebuilds. An already `@sha256:`-pinned ref returns its digest without a
|
|
75
|
+
* network call. Returns `undefined` for a non-Docker-Hub ref (not an error —
|
|
76
|
+
* resolution simply doesn't apply) or when a Hub request fails. A genuine
|
|
77
|
+
* request failure is reported to `onError` rather than swallowed, so an operator
|
|
78
|
+
* whose Hub calls persistently fail (auth / network / rate-limit) gets a signal
|
|
79
|
+
* instead of an unexplained "always reuses the cached image".
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveTagDigest(ref: string, opts?: {
|
|
82
|
+
fetchImpl?: typeof fetch;
|
|
83
|
+
requestTimeoutMs?: number;
|
|
84
|
+
onError?: (err: unknown) => void;
|
|
85
|
+
}): Promise<string | undefined>;
|
|
86
|
+
export declare class BaseImageCatalog {
|
|
87
|
+
private readonly repository;
|
|
88
|
+
private readonly defaultRef;
|
|
89
|
+
private readonly filter;
|
|
90
|
+
private readonly limit;
|
|
91
|
+
private readonly refreshIntervalMs;
|
|
92
|
+
private readonly maxTagsScanned;
|
|
93
|
+
private readonly requestTimeoutMs;
|
|
94
|
+
private readonly fetchImpl;
|
|
95
|
+
/** Filtered tags (bare, newest first); empty until the first refresh. */
|
|
96
|
+
private tags;
|
|
97
|
+
private timer?;
|
|
98
|
+
constructor(opts: BaseImageCatalogOptions);
|
|
99
|
+
/** The advertised image refs: the default first, then the filtered tags. */
|
|
100
|
+
current(): string[];
|
|
101
|
+
/** Whether `ref` is offered by this catalog (server-side allowlist check). */
|
|
102
|
+
isAllowed(ref: string): boolean;
|
|
103
|
+
/** Refetch and re-filter the tag list. Throws on a fetch/HTTP failure. */
|
|
104
|
+
refresh(): Promise<void>;
|
|
105
|
+
/** Begin periodic background refresh. Refresh failures are surfaced to
|
|
106
|
+
* `onError` rather than swallowed; the last good list keeps serving. */
|
|
107
|
+
start(onError?: (err: unknown) => void): void;
|
|
108
|
+
stop(): void;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=base-image-catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-image-catalog.d.ts","sourceRoot":"","sources":["../src/base-image-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;wCAEwC;AACxC,MAAM,WAAW,SAAS;IACxB;qEACiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;qEACiE;IACjE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB;4EACwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AA+BD;yCACyC;AACzC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,GAAE,SAAc,GAAG,MAAM,EAAE,CAW3E;AAoED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,GACV;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAoC1E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IACJ,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7B,GACL,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwB7B;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,yEAAyE;IACzE,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,KAAK,CAAC,CAAiC;gBAEnC,IAAI,EAAE,uBAAuB;IAWzC,4EAA4E;IAC5E,OAAO,IAAI,MAAM,EAAE;IAKnB,8EAA8E;IAC9E,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI/B,0EAA0E;IACpE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAU9B;6EACyE;IACzE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAQ7C,IAAI,IAAI,IAAI;CAMb"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A base-image catalog backed by the Docker Hub repository-tags API. A runner
|
|
3
|
+
* advertises the resolved tag list as an `enum` on its session `image` field so
|
|
4
|
+
* the editor renders a base-image picker, and validates a chosen image against
|
|
5
|
+
* the same list server-side — the runner is the source of truth, so a non-editor
|
|
6
|
+
* client can't widen the allowed set.
|
|
7
|
+
*
|
|
8
|
+
* The list is fetched at startup and on a refresh interval, cached in memory,
|
|
9
|
+
* and degrades gracefully: when Docker Hub is unreachable the catalog still
|
|
10
|
+
* serves the configured default (always present), so the runner boots and a
|
|
11
|
+
* session can still start on the default image.
|
|
12
|
+
*
|
|
13
|
+
* Free of runner specifics — it takes only a repository, a default ref, and a
|
|
14
|
+
* tag filter — but the tag source is specifically Docker Hub's registry API
|
|
15
|
+
* (`hub.docker.com/v2`); a base on another registry (GHCR, a private registry)
|
|
16
|
+
* can't be enumerated or digest-resolved here. The Docker registry the kubelet
|
|
17
|
+
* pulls from is a separate concern; this resolves the *menu* of base images, not
|
|
18
|
+
* pull auth.
|
|
19
|
+
*/
|
|
20
|
+
const SHA_TAG = /^(?:sha-)?[0-9a-f]{7,}$/i;
|
|
21
|
+
const PINNED_TAG = /^\d+\.\d+\.\d+(?:[-.].*)?$/;
|
|
22
|
+
const PRERELEASE_CORE = /^\d+\.\d+\.\d+-(.+)$/;
|
|
23
|
+
/** First `-` identifier on a pinned tag that marks it a prerelease rather than a
|
|
24
|
+
* build variant (`-slim`, `-rust-*`). */
|
|
25
|
+
const PRERELEASE_TOKENS = new Set([
|
|
26
|
+
"rc",
|
|
27
|
+
"alpha",
|
|
28
|
+
"beta",
|
|
29
|
+
"pre",
|
|
30
|
+
"preview",
|
|
31
|
+
"next",
|
|
32
|
+
"canary",
|
|
33
|
+
"dev",
|
|
34
|
+
"nightly",
|
|
35
|
+
"snapshot",
|
|
36
|
+
]);
|
|
37
|
+
function isShaTag(tag) {
|
|
38
|
+
return SHA_TAG.test(tag);
|
|
39
|
+
}
|
|
40
|
+
function isPrerelease(tag) {
|
|
41
|
+
const m = PRERELEASE_CORE.exec(tag);
|
|
42
|
+
if (!m)
|
|
43
|
+
return false;
|
|
44
|
+
const firstId = m[1].split(/[-.]/, 1)[0].toLowerCase();
|
|
45
|
+
return PRERELEASE_TOKENS.has(firstId);
|
|
46
|
+
}
|
|
47
|
+
/** Apply a {@link TagFilter} to a tag list, preserving input order. Pure — the
|
|
48
|
+
* unit-testable core of the catalog. */
|
|
49
|
+
export function filterTags(tags, filter = {}) {
|
|
50
|
+
return tags.filter((tag) => {
|
|
51
|
+
if (filter.excludeSha && isShaTag(tag))
|
|
52
|
+
return false;
|
|
53
|
+
if (filter.pinnedOnly && !PINNED_TAG.test(tag))
|
|
54
|
+
return false;
|
|
55
|
+
if (filter.excludePrerelease && isPrerelease(tag))
|
|
56
|
+
return false;
|
|
57
|
+
if (filter.include && filter.include.length > 0 && !filter.include.some((re) => re.test(tag))) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (filter.exclude && filter.exclude.some((re) => re.test(tag)))
|
|
61
|
+
return false;
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function splitRepository(repository) {
|
|
66
|
+
const slash = repository.indexOf("/");
|
|
67
|
+
// A bare name (no slash) is a Docker official image under `library/`.
|
|
68
|
+
if (slash < 0)
|
|
69
|
+
return ["library", repository];
|
|
70
|
+
return [repository.slice(0, slash), repository.slice(slash + 1)];
|
|
71
|
+
}
|
|
72
|
+
function unique(refs) {
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const ref of refs) {
|
|
76
|
+
if (!seen.has(ref)) {
|
|
77
|
+
seen.add(ref);
|
|
78
|
+
out.push(ref);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fetch a repository's tags from Docker Hub, newest first, bounded by
|
|
85
|
+
* `maxTagsScanned`. Paginates the `/v2/repositories/{ns}/{repo}/tags` endpoint
|
|
86
|
+
* and sorts by `last_updated` descending so the cap keeps the most recent tags
|
|
87
|
+
* regardless of the API's page ordering. Throws on a network/HTTP error — the
|
|
88
|
+
* caller decides whether a failure degrades to default-only.
|
|
89
|
+
*/
|
|
90
|
+
async function fetchRepoTags(repository, maxTagsScanned, requestTimeoutMs, fetchImpl) {
|
|
91
|
+
const [namespace, repo] = splitRepository(repository);
|
|
92
|
+
const collected = [];
|
|
93
|
+
let url = `https://hub.docker.com/v2/repositories/${encodeURIComponent(namespace)}/` +
|
|
94
|
+
`${encodeURIComponent(repo)}/tags?page_size=100`;
|
|
95
|
+
while (url && collected.length < maxTagsScanned) {
|
|
96
|
+
const res = await fetchImpl(url, {
|
|
97
|
+
headers: { Accept: "application/json" },
|
|
98
|
+
signal: AbortSignal.timeout(requestTimeoutMs),
|
|
99
|
+
});
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
throw new Error(`Docker Hub tags request for '${repository}' failed: ${res.status} ${res.statusText}`);
|
|
102
|
+
}
|
|
103
|
+
const body = (await res.json());
|
|
104
|
+
for (const r of body.results ?? []) {
|
|
105
|
+
if (typeof r.name === "string") {
|
|
106
|
+
collected.push({ name: r.name, lastUpdated: r.last_updated ?? "" });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
url = body.next ?? null;
|
|
110
|
+
}
|
|
111
|
+
collected.sort((a, b) => a.lastUpdated < b.lastUpdated ? 1 : a.lastUpdated > b.lastUpdated ? -1 : 0);
|
|
112
|
+
return collected.map((c) => c.name);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Parse a Docker image reference into the Docker Hub coordinates needed to look
|
|
116
|
+
* up its digest, or `null` when the ref points at a non-Docker-Hub registry.
|
|
117
|
+
* Handles the `library/` shorthand, optional `docker.io` host, and an already
|
|
118
|
+
* `@sha256:`-pinned digest (carried through verbatim).
|
|
119
|
+
*/
|
|
120
|
+
export function parseDockerHubRef(ref) {
|
|
121
|
+
let rest = ref;
|
|
122
|
+
let digest;
|
|
123
|
+
const at = ref.indexOf("@");
|
|
124
|
+
if (at >= 0) {
|
|
125
|
+
digest = ref.slice(at + 1);
|
|
126
|
+
rest = ref.slice(0, at);
|
|
127
|
+
}
|
|
128
|
+
// A first path segment with a `.`/`:` (or `localhost`) is a registry host.
|
|
129
|
+
const firstSlash = rest.indexOf("/");
|
|
130
|
+
const firstPart = firstSlash >= 0 ? rest.slice(0, firstSlash) : "";
|
|
131
|
+
if (firstPart.includes(".") || firstPart.includes(":") || firstPart === "localhost") {
|
|
132
|
+
const hubHosts = new Set(["docker.io", "index.docker.io", "registry-1.docker.io"]);
|
|
133
|
+
if (!hubHosts.has(firstPart))
|
|
134
|
+
return null;
|
|
135
|
+
rest = rest.slice(firstSlash + 1);
|
|
136
|
+
}
|
|
137
|
+
// `rest` is now `name[:tag]`; the tag is the segment after a colon that isn't
|
|
138
|
+
// inside the path (i.e. after the last `/`).
|
|
139
|
+
const lastColon = rest.lastIndexOf(":");
|
|
140
|
+
const lastSlash = rest.lastIndexOf("/");
|
|
141
|
+
let name = rest;
|
|
142
|
+
let tag = "latest";
|
|
143
|
+
if (lastColon > lastSlash) {
|
|
144
|
+
name = rest.slice(0, lastColon);
|
|
145
|
+
tag = rest.slice(lastColon + 1);
|
|
146
|
+
}
|
|
147
|
+
const slash = name.indexOf("/");
|
|
148
|
+
return {
|
|
149
|
+
namespace: slash >= 0 ? name.slice(0, slash) : "library",
|
|
150
|
+
repo: slash >= 0 ? name.slice(slash + 1) : name,
|
|
151
|
+
tag,
|
|
152
|
+
digest,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Resolve a Docker Hub image ref's CURRENT manifest digest. A moving tag
|
|
157
|
+
* (`latest-slim`) maps to a new digest once a new version is published, so a
|
|
158
|
+
* build that folds this into its cache key detects that the base moved and
|
|
159
|
+
* rebuilds. An already `@sha256:`-pinned ref returns its digest without a
|
|
160
|
+
* network call. Returns `undefined` for a non-Docker-Hub ref (not an error —
|
|
161
|
+
* resolution simply doesn't apply) or when a Hub request fails. A genuine
|
|
162
|
+
* request failure is reported to `onError` rather than swallowed, so an operator
|
|
163
|
+
* whose Hub calls persistently fail (auth / network / rate-limit) gets a signal
|
|
164
|
+
* instead of an unexplained "always reuses the cached image".
|
|
165
|
+
*/
|
|
166
|
+
export async function resolveTagDigest(ref, opts = {}) {
|
|
167
|
+
const parsed = parseDockerHubRef(ref);
|
|
168
|
+
if (!parsed)
|
|
169
|
+
return undefined;
|
|
170
|
+
if (parsed.digest)
|
|
171
|
+
return parsed.digest;
|
|
172
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
173
|
+
const url = `https://hub.docker.com/v2/repositories/${encodeURIComponent(parsed.namespace)}/` +
|
|
174
|
+
`${encodeURIComponent(parsed.repo)}/tags/${encodeURIComponent(parsed.tag)}`;
|
|
175
|
+
try {
|
|
176
|
+
const res = await fetchImpl(url, {
|
|
177
|
+
headers: { Accept: "application/json" },
|
|
178
|
+
signal: AbortSignal.timeout(opts.requestTimeoutMs ?? 10_000),
|
|
179
|
+
});
|
|
180
|
+
if (!res.ok) {
|
|
181
|
+
opts.onError?.(new Error(`Docker Hub tag request for '${ref}' failed: ${res.status} ${res.statusText}`));
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const body = (await res.json());
|
|
185
|
+
return body.digest ?? body.images?.find((i) => i.digest)?.digest ?? undefined;
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
opts.onError?.(err);
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export class BaseImageCatalog {
|
|
193
|
+
repository;
|
|
194
|
+
defaultRef;
|
|
195
|
+
filter;
|
|
196
|
+
limit;
|
|
197
|
+
refreshIntervalMs;
|
|
198
|
+
maxTagsScanned;
|
|
199
|
+
requestTimeoutMs;
|
|
200
|
+
fetchImpl;
|
|
201
|
+
/** Filtered tags (bare, newest first); empty until the first refresh. */
|
|
202
|
+
tags = [];
|
|
203
|
+
timer;
|
|
204
|
+
constructor(opts) {
|
|
205
|
+
this.repository = opts.repository;
|
|
206
|
+
this.defaultRef = opts.defaultRef;
|
|
207
|
+
this.filter = opts.filter ?? {};
|
|
208
|
+
this.limit = opts.limit ?? 20;
|
|
209
|
+
this.refreshIntervalMs = opts.refreshIntervalMs ?? 60 * 60 * 1000;
|
|
210
|
+
this.maxTagsScanned = opts.maxTagsScanned ?? 200;
|
|
211
|
+
this.requestTimeoutMs = opts.requestTimeoutMs ?? 10_000;
|
|
212
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
213
|
+
}
|
|
214
|
+
/** The advertised image refs: the default first, then the filtered tags. */
|
|
215
|
+
current() {
|
|
216
|
+
const refs = this.tags.map((tag) => `${this.repository}:${tag}`);
|
|
217
|
+
return unique([this.defaultRef, ...refs]);
|
|
218
|
+
}
|
|
219
|
+
/** Whether `ref` is offered by this catalog (server-side allowlist check). */
|
|
220
|
+
isAllowed(ref) {
|
|
221
|
+
return this.current().includes(ref);
|
|
222
|
+
}
|
|
223
|
+
/** Refetch and re-filter the tag list. Throws on a fetch/HTTP failure. */
|
|
224
|
+
async refresh() {
|
|
225
|
+
const tags = await fetchRepoTags(this.repository, this.maxTagsScanned, this.requestTimeoutMs, this.fetchImpl);
|
|
226
|
+
this.tags = filterTags(tags, this.filter).slice(0, this.limit);
|
|
227
|
+
}
|
|
228
|
+
/** Begin periodic background refresh. Refresh failures are surfaced to
|
|
229
|
+
* `onError` rather than swallowed; the last good list keeps serving. */
|
|
230
|
+
start(onError) {
|
|
231
|
+
if (this.timer)
|
|
232
|
+
return;
|
|
233
|
+
this.timer = setInterval(() => {
|
|
234
|
+
this.refresh().catch((err) => onError?.(err));
|
|
235
|
+
}, this.refreshIntervalMs);
|
|
236
|
+
this.timer.unref?.();
|
|
237
|
+
}
|
|
238
|
+
stop() {
|
|
239
|
+
if (this.timer) {
|
|
240
|
+
clearInterval(this.timer);
|
|
241
|
+
this.timer = undefined;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=base-image-catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-image-catalog.js","sourceRoot":"","sources":["../src/base-image-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAuCH,MAAM,OAAO,GAAG,0BAA0B,CAAC;AAC3C,MAAM,UAAU,GAAG,4BAA4B,CAAC;AAChD,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAC/C;0CAC0C;AAC1C,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,IAAI;IACJ,OAAO;IACP,MAAM;IACN,KAAK;IACL,SAAS;IACT,MAAM;IACN,QAAQ;IACR,KAAK;IACL,SAAS;IACT,UAAU;CACX,CAAC,CAAC;AAEH,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACvD,OAAO,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC;AAED;yCACyC;AACzC,MAAM,UAAU,UAAU,CAAC,IAAc,EAAE,SAAoB,EAAE;IAC/D,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QACzB,IAAI,MAAM,CAAC,UAAU,IAAI,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,MAAM,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7D,IAAI,MAAM,CAAC,iBAAiB,IAAI,YAAY,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAChE,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC9F,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9E,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,UAAkB;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtC,sEAAsE;IACtE,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAC9C,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,MAAM,CAAC,IAAc;IAC5B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,aAAa,CAC1B,UAAkB,EAClB,cAAsB,EACtB,gBAAwB,EACxB,SAAuB;IAEvB,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,SAAS,GAAiD,EAAE,CAAC;IACnE,IAAI,GAAG,GACL,0CAA0C,kBAAkB,CAAC,SAAS,CAAC,GAAG;QAC1E,GAAG,kBAAkB,CAAC,IAAI,CAAC,qBAAqB,CAAC;IAEnD,OAAO,GAAG,IAAI,SAAS,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YAC/B,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,gCAAgC,UAAU,aAAa,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CACtF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAG7B,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACnC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC/B,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;IAC1B,CAAC;IAED,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACtB,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3E,CAAC;IACF,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAW;IAEX,IAAI,IAAI,GAAG,GAAG,CAAC;IACf,IAAI,MAA0B,CAAC;IAC/B,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3B,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,2EAA2E;IAC3E,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,KAAK,WAAW,EAAE,CAAC;QACpF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,iBAAiB,EAAE,sBAAsB,CAAC,CAAC,CAAC;QACnF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACpC,CAAC;IAED,8EAA8E;IAC9E,6CAA6C;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAChC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO;QACL,SAAS,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;QACxD,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QAC/C,GAAG;QACH,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,OAII,EAAE;IAEN,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IAExC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC1C,MAAM,GAAG,GACP,0CAA0C,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG;QACjF,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9E,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YAC/B,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,IAAI,MAAM,CAAC;SAC7D,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,KAAK,CAAC,+BAA+B,GAAG,aAAa,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YACzG,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA6D,CAAC;QAC5F,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,SAAS,CAAC;IAChF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QACpB,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,OAAO,gBAAgB;IACV,UAAU,CAAS;IACnB,UAAU,CAAS;IACnB,MAAM,CAAY;IAClB,KAAK,CAAS;IACd,iBAAiB,CAAS;IAC1B,cAAc,CAAS;IACvB,gBAAgB,CAAS;IACzB,SAAS,CAAe;IACzC,yEAAyE;IACjE,IAAI,GAAa,EAAE,CAAC;IACpB,KAAK,CAAkC;IAE/C,YAAY,IAA6B;QACvC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAClE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,GAAG,CAAC;QACjD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,MAAM,CAAC;QACxD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC3C,CAAC;IAED,4EAA4E;IAC5E,OAAO;QACL,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC,CAAC;QACjE,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,8EAA8E;IAC9E,SAAS,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC;IAED,0EAA0E;IAC1E,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,GAAG,MAAM,aAAa,CAC9B,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,cAAc,EACnB,IAAI,CAAC,gBAAgB,EACrB,IAAI,CAAC,SAAS,CACf,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IAED;6EACyE;IACzE,KAAK,CAAC,OAAgC;QACpC,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAChD,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACvB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACzB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { JsonSchema, PullPolicy } from "./contract.js";
|
|
2
|
+
export interface SessionConfigSchemaOptions {
|
|
3
|
+
/** Default container image; becomes the field's advertised `default`. */
|
|
4
|
+
imageDefault: string;
|
|
5
|
+
/** When set (and non-empty), constrains `image` to this allowlist — advertised
|
|
6
|
+
* as a JSON Schema `enum` so the editor renders a base-image picker. The
|
|
7
|
+
* picker is editable within the list, so an `imageEnum` overrides `enforced`
|
|
8
|
+
* for the `image` field (a one-entry enum is effectively locked anyway). */
|
|
9
|
+
imageEnum?: string[];
|
|
10
|
+
/** Default pull policy (defaults to `missing`). */
|
|
11
|
+
pullPolicyDefault?: PullPolicy;
|
|
12
|
+
/** Override the `pullPolicy` field description. `pullPolicy` semantics differ
|
|
13
|
+
* per backend (docker: container pull policy; k8s: base-image build
|
|
14
|
+
* freshness), so each runner can supply wording that matches its behaviour. */
|
|
15
|
+
pullPolicyDescription?: string;
|
|
16
|
+
/** When true, `image` is server-enforced — advertised `readOnly` (locked to
|
|
17
|
+
* `imageDefault`) so the editor renders it disabled but still sends it. No
|
|
18
|
+
* effect once `imageEnum` is set (the picker is the constraint). `pullPolicy`
|
|
19
|
+
* is always client-editable. */
|
|
20
|
+
enforced?: boolean;
|
|
21
|
+
/** Include the optional `registryUrl` field (a runner that forwards a module
|
|
22
|
+
* registry URL to its workloads). */
|
|
23
|
+
registryUrl?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds the JSON Schema a runner advertises on `/v1/capabilities` for the
|
|
27
|
+
* editable `SessionConfig` surface. `baseUrl` is deliberately absent — the
|
|
28
|
+
* client owns it. Server-enforced runners pass `enforced: true` to lock `image`
|
|
29
|
+
* as `readOnly` (the value still travels on the wire); `pullPolicy` is always
|
|
30
|
+
* client-editable.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sessionConfigSchema(opts: SessionConfigSchemaOptions): JsonSchema;
|
|
33
|
+
//# sourceMappingURL=capabilities-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities-schema.d.ts","sourceRoot":"","sources":["../src/capabilities-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE5D,MAAM,WAAW,0BAA0B;IACzC,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB;;;iFAG6E;IAC7E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAC/B;;oFAEgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;qCAGiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;0CACsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,0BAA0B,GAAG,UAAU,CAqChF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the JSON Schema a runner advertises on `/v1/capabilities` for the
|
|
3
|
+
* editable `SessionConfig` surface. `baseUrl` is deliberately absent — the
|
|
4
|
+
* client owns it. Server-enforced runners pass `enforced: true` to lock `image`
|
|
5
|
+
* as `readOnly` (the value still travels on the wire); `pullPolicy` is always
|
|
6
|
+
* client-editable.
|
|
7
|
+
*/
|
|
8
|
+
export function sessionConfigSchema(opts) {
|
|
9
|
+
const readOnly = opts.enforced === true;
|
|
10
|
+
const hasEnum = Array.isArray(opts.imageEnum) && opts.imageEnum.length > 0;
|
|
11
|
+
const properties = {
|
|
12
|
+
image: {
|
|
13
|
+
type: "string",
|
|
14
|
+
minLength: 1,
|
|
15
|
+
default: opts.imageDefault,
|
|
16
|
+
title: "Image",
|
|
17
|
+
description: "Container image the runner spawns for each run.",
|
|
18
|
+
// An allowlist renders as an editable picker; otherwise fall back to the
|
|
19
|
+
// enforced (readOnly) single value.
|
|
20
|
+
...(hasEnum ? { enum: opts.imageEnum } : readOnly ? { readOnly: true } : {}),
|
|
21
|
+
},
|
|
22
|
+
pullPolicy: {
|
|
23
|
+
type: "string",
|
|
24
|
+
enum: ["missing", "always", "never"],
|
|
25
|
+
default: opts.pullPolicyDefault ?? "missing",
|
|
26
|
+
title: "Pull policy",
|
|
27
|
+
description: opts.pullPolicyDescription ??
|
|
28
|
+
"`missing` pulls on first use; `always` forces a pull every run; `never` fails if the image isn't present.",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
if (opts.registryUrl) {
|
|
32
|
+
properties.registryUrl = {
|
|
33
|
+
type: "string",
|
|
34
|
+
title: "Registry URL",
|
|
35
|
+
description: "Optional base URL for the telo module registry, forwarded to the runner as TELO_REGISTRY_URL. Leave blank for the default registry.",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
type: "object",
|
|
40
|
+
required: ["image", "pullPolicy"],
|
|
41
|
+
properties,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=capabilities-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities-schema.js","sourceRoot":"","sources":["../src/capabilities-schema.ts"],"names":[],"mappings":"AA0BA;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAgC;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3E,MAAM,UAAU,GAA+B;QAC7C,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,CAAC;YACZ,OAAO,EAAE,IAAI,CAAC,YAAY;YAC1B,KAAK,EAAE,OAAO;YACd,WAAW,EAAE,iDAAiD;YAC9D,yEAAyE;YACzE,oCAAoC;YACpC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7E;QACD,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC;YACpC,OAAO,EAAE,IAAI,CAAC,iBAAiB,IAAI,SAAS;YAC5C,KAAK,EAAE,aAAa;YACpB,WAAW,EACT,IAAI,CAAC,qBAAqB;gBAC1B,2GAA2G;SAC9G;KACF,CAAC;IACF,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,UAAU,CAAC,WAAW,GAAG;YACvB,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,cAAc;YACrB,WAAW,EACT,qIAAqI;SACxI,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC;QACjC,UAAU;KACX,CAAC;AACJ,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RunnerTerms } from "./contract.js";
|
|
2
|
+
/**
|
|
3
|
+
* Backend-neutral runner configuration. Concrete runners (docker, k8s) extend
|
|
4
|
+
* `RunnerCoreConfig` with their own fields and reuse these parse helpers so
|
|
5
|
+
* config validation and error reporting stay identical across backends.
|
|
6
|
+
*/
|
|
7
|
+
export interface RunnerCoreConfig {
|
|
8
|
+
port: number;
|
|
9
|
+
logLevel: string;
|
|
10
|
+
maxSessions: number;
|
|
11
|
+
exitTtlMs: number;
|
|
12
|
+
replayBufferBytes: number;
|
|
13
|
+
corsOrigins: string[] | "*";
|
|
14
|
+
}
|
|
15
|
+
export declare class RunnerConfigError extends Error {
|
|
16
|
+
}
|
|
17
|
+
export declare function parsePositiveInt(raw: string | undefined, fallback: number, field: string): number;
|
|
18
|
+
export declare function parseBool(raw: string | undefined, fallback: boolean, field: string): boolean;
|
|
19
|
+
export declare function parsePort(raw: string | undefined, fallback: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* Build the runner's usage terms from the environment. Terms are opt-in: returns
|
|
22
|
+
* `undefined` (no gate) unless a non-empty body is provided.
|
|
23
|
+
*
|
|
24
|
+
* The body comes from `RUNNER_TERMS_FILE` (a path read at startup — the natural
|
|
25
|
+
* fit for a k8s ConfigMap mount or a bind-mounted file) or, for short notes,
|
|
26
|
+
* inline `RUNNER_TERMS_BODY`. `RUNNER_TERMS_VERSION` is optional and defaults to
|
|
27
|
+
* a short hash of the body, so editing the agreement automatically re-prompts
|
|
28
|
+
* every client; set it explicitly only to control "material change vs. typo".
|
|
29
|
+
* A configured-but-unreadable `RUNNER_TERMS_FILE` is a hard error rather than a
|
|
30
|
+
* silent "no terms", so a misconfiguration can't quietly drop the gate.
|
|
31
|
+
*/
|
|
32
|
+
export declare function loadTermsFromEnv(env: NodeJS.ProcessEnv): RunnerTerms | undefined;
|
|
33
|
+
export declare function parseCorsOrigins(raw: string | undefined): string[] | "*";
|
|
34
|
+
export declare function loadCoreConfig(env: NodeJS.ProcessEnv, defaults?: {
|
|
35
|
+
port?: number;
|
|
36
|
+
}): RunnerCoreConfig;
|
|
37
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;CAC7B;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,MAAM,CAOR;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAM5F;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,WAAW,GAAG,SAAS,CAQhF;AAsBD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,GAAG,CAMxE;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,QAAQ,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,gBAAgB,CAiBlB"}
|