@telorun/kernel 0.84.0 → 0.86.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/bundle/module-artifact.d.ts +1 -1
- package/dist/bundle/module-artifact.d.ts.map +1 -1
- package/dist/bundle/module-artifact.js +3 -3
- package/dist/bundle/module-artifact.js.map +1 -1
- package/dist/controller-loaders/bundle-loader.d.ts +5 -5
- package/dist/controller-loaders/bundle-loader.js +7 -7
- package/dist/controller-loaders/bundle-loader.js.map +1 -1
- package/dist/evaluation-context.d.ts +158 -25
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +234 -44
- package/dist/evaluation-context.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +60 -5
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +256 -9
- package/dist/kernel.js.map +1 -1
- package/dist/manifest-sources/local-manifest-cache-source.d.ts +6 -6
- package/dist/manifest-sources/local-manifest-cache-source.d.ts.map +1 -1
- package/dist/manifest-sources/local-manifest-cache-source.js +11 -12
- package/dist/manifest-sources/local-manifest-cache-source.js.map +1 -1
- package/dist/module-context.d.ts +31 -1
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +41 -1
- package/dist/module-context.js.map +1 -1
- package/dist/reconcile.d.ts +42 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +58 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/resource-edges.d.ts +58 -0
- package/dist/resource-edges.d.ts.map +1 -0
- package/dist/resource-edges.js +110 -0
- package/dist/resource-edges.js.map +1 -0
- package/dist/runtime-seam.d.ts.map +1 -1
- package/dist/runtime-seam.js +1 -2
- package/dist/runtime-seam.js.map +1 -1
- package/dist/transports/http-transport.d.ts +37 -0
- package/dist/transports/http-transport.d.ts.map +1 -0
- package/dist/transports/http-transport.js +168 -0
- package/dist/transports/http-transport.js.map +1 -0
- package/dist/transports/transport-registry.d.ts +13 -14
- package/dist/transports/transport-registry.d.ts.map +1 -1
- package/dist/transports/transport-registry.js +17 -24
- package/dist/transports/transport-registry.js.map +1 -1
- package/package.json +3 -3
- package/src/bundle/module-artifact.ts +2 -3
- package/src/controller-loaders/bundle-loader.ts +7 -7
- package/src/evaluation-context.ts +257 -53
- package/src/index.ts +1 -1
- package/src/kernel.ts +314 -12
- package/src/manifest-sources/local-manifest-cache-source.ts +9 -16
- package/src/module-context.ts +41 -1
- package/src/reconcile.ts +83 -0
- package/src/resource-edges.ts +114 -0
- package/src/runtime-seam.ts +1 -2
- package/src/transports/http-transport.ts +209 -0
- package/src/transports/transport-registry.ts +17 -24
- package/dist/transports/registry-transport.d.ts +0 -41
- package/dist/transports/registry-transport.d.ts.map +0 -1
- package/dist/transports/registry-transport.js +0 -282
- package/dist/transports/registry-transport.js.map +0 -1
- package/src/transports/registry-transport.ts +0 -339
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_MANIFEST_FILENAME, HttpSource, RegistrySource, isRegistryRef, parseModuleRef, parseVersionedRef, sha256Base64Url, withRefVersion, splitIntegrity, } from "@telorun/analyzer";
|
|
2
|
-
import { fetchOrThrow } from "@telorun/sdk";
|
|
3
|
-
import { createHash } from "crypto";
|
|
4
|
-
import { assertPublicEgress } from "./egress-guard.js";
|
|
5
|
-
const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
|
|
6
|
-
/** Throwaway origin that lets a bare registry ref use URL path resolution. */
|
|
7
|
-
const JOIN_ORIGIN = "https://ref.invalid";
|
|
8
|
-
/** True for an HTTP(S) destination that names a registry root rather than a
|
|
9
|
-
* module within it — no path segments to resolve a sibling beside. */
|
|
10
|
-
function isRegistryBase(base) {
|
|
11
|
-
if (!base.startsWith("http://") && !base.startsWith("https://"))
|
|
12
|
-
return false;
|
|
13
|
-
try {
|
|
14
|
-
return new URL(base).pathname.replace(/\/+/g, "/").replace(/^\/|\/$/g, "") === "";
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const QUERY_HASH_LENGTH = 12;
|
|
21
|
-
/** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
|
|
22
|
-
* already point at a YAML file, append `/telo.yaml`, so a raw import URL and
|
|
23
|
-
* the canonical source it resolves to map to the same cache path. */
|
|
24
|
-
function normalizePathname(rawUrl, parsed) {
|
|
25
|
-
let pathname = parsed.pathname;
|
|
26
|
-
if (!rawUrl.includes(".yaml")) {
|
|
27
|
-
pathname = pathname.endsWith("/")
|
|
28
|
-
? `${pathname}${DEFAULT_MANIFEST_FILENAME}`
|
|
29
|
-
: `${pathname}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
30
|
-
}
|
|
31
|
-
return pathname;
|
|
32
|
-
}
|
|
33
|
-
/** Short hash of `search + hash` so two URLs that differ only in query /
|
|
34
|
-
* fragment do not collide at the same cache path. */
|
|
35
|
-
function disambiguatePath(pathname, search, hash) {
|
|
36
|
-
if (!search && !hash)
|
|
37
|
-
return pathname;
|
|
38
|
-
const digest = createHash("sha256")
|
|
39
|
-
.update(search + hash)
|
|
40
|
-
.digest("hex")
|
|
41
|
-
.slice(0, QUERY_HASH_LENGTH);
|
|
42
|
-
const dotIdx = pathname.lastIndexOf(".");
|
|
43
|
-
const slashIdx = pathname.lastIndexOf("/");
|
|
44
|
-
const ext = dotIdx > slashIdx ? pathname.slice(dotIdx) : "";
|
|
45
|
-
const base = pathname.slice(0, pathname.length - ext.length);
|
|
46
|
-
return `${base}.${digest}${ext}`;
|
|
47
|
-
}
|
|
48
|
-
/** The default HTTP transport: bare `namespace/name@version` registry refs and
|
|
49
|
-
* direct `https://…` URLs, resolving against `registry.telo.run` (or a
|
|
50
|
-
* configured registry). Its resolution `source` composes the browser-safe
|
|
51
|
-
* `RegistrySource` / `HttpSource` from `analyzer`; the Node-only management
|
|
52
|
-
* methods live here. This is the fallback transport for any ref that carries
|
|
53
|
-
* no owning scheme, so `oci://` (or a future `s3://`) never falls through to
|
|
54
|
-
* it — those refs are claimed by their own transport's `supports()`. */
|
|
55
|
-
export class RegistryTransport {
|
|
56
|
-
registryUrl;
|
|
57
|
-
registrySource;
|
|
58
|
-
httpSource;
|
|
59
|
-
source;
|
|
60
|
-
constructor(registryUrl = DEFAULT_REGISTRY_URL) {
|
|
61
|
-
this.registryUrl = registryUrl;
|
|
62
|
-
this.registrySource = new RegistrySource(registryUrl);
|
|
63
|
-
this.httpSource = new HttpSource();
|
|
64
|
-
const pick = (ref) => this.httpSource.supports(ref) ? this.httpSource : this.registrySource;
|
|
65
|
-
this.source = {
|
|
66
|
-
supports: (url) => this.supports(url),
|
|
67
|
-
read: async (url) => {
|
|
68
|
-
// The browser-safe sources do the fetch; the Node-side egress policy
|
|
69
|
-
// is enforced here, on the host the read will actually hit.
|
|
70
|
-
await assertPublicEgress(this.httpSource.supports(url) ? url : this.registryUrl);
|
|
71
|
-
return pick(url).read(url);
|
|
72
|
-
},
|
|
73
|
-
resolveRelative: (base, relative) => pick(base).resolveRelative(base, relative),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
supports(ref) {
|
|
77
|
-
const { base } = splitIntegrity(ref);
|
|
78
|
-
return base.startsWith("http://") || base.startsWith("https://") || isRegistryRef(ref);
|
|
79
|
-
}
|
|
80
|
-
cacheCoords(ref) {
|
|
81
|
-
const url = splitIntegrity(ref).base;
|
|
82
|
-
const trimmedRegistry = this.registryUrl.replace(/\/+$/, "");
|
|
83
|
-
const registryHost = this.registryHost();
|
|
84
|
-
// 1. Registry ref form: <path>@<version>. Keyed under the registry's host —
|
|
85
|
-
// a ref says nothing about which registry serves it, so without the host
|
|
86
|
-
// two registries' copies of the same path/version share one cache entry.
|
|
87
|
-
if (isRegistryRef(url)) {
|
|
88
|
-
if (!registryHost)
|
|
89
|
-
return null;
|
|
90
|
-
let parsed;
|
|
91
|
-
try {
|
|
92
|
-
parsed = parseModuleRef(url);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
transport: "registry",
|
|
99
|
-
host: registryHost,
|
|
100
|
-
path: parsed.modulePath,
|
|
101
|
-
version: parsed.version,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
// 2. HTTP(S) URL — a direct registry URL or arbitrary external.
|
|
105
|
-
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
106
|
-
let parsed;
|
|
107
|
-
try {
|
|
108
|
-
parsed = new URL(url);
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
const pathname = normalizePathname(url, parsed);
|
|
114
|
-
// 2a. On the configured registry, no query/fragment: fold into the
|
|
115
|
-
// registry layout so a ref and a direct URL land on the same file.
|
|
116
|
-
const normalizedUrl = `${parsed.protocol}//${parsed.host}${pathname}`;
|
|
117
|
-
if (registryHost &&
|
|
118
|
-
!parsed.search &&
|
|
119
|
-
!parsed.hash &&
|
|
120
|
-
normalizedUrl.startsWith(`${trimmedRegistry}/`)) {
|
|
121
|
-
// The registry serves `<path…>/<version>/<file>`, where `<file>` is the
|
|
122
|
-
// module manifest or one of its `include:` partials.
|
|
123
|
-
const segments = normalizedUrl.slice(trimmedRegistry.length + 1).split("/");
|
|
124
|
-
const file = segments.pop();
|
|
125
|
-
const version = segments.pop();
|
|
126
|
-
if (file && version && segments.length > 0) {
|
|
127
|
-
return {
|
|
128
|
-
transport: "registry",
|
|
129
|
-
host: registryHost,
|
|
130
|
-
path: segments.join("/"),
|
|
131
|
-
version,
|
|
132
|
-
file,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// 2b. Arbitrary HTTP(S) → `url` subtree, query-hash suffix on collision.
|
|
137
|
-
// No version segment: a URL addresses exactly one file, and the
|
|
138
|
-
// version it declares lives inside bytes the cache maps paths without.
|
|
139
|
-
const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
140
|
-
const segments = disambiguatePath(cleanPath, parsed.search, parsed.hash).split("/");
|
|
141
|
-
const file = segments.pop();
|
|
142
|
-
if (!file)
|
|
143
|
-
return null;
|
|
144
|
-
return { transport: "url", host: parsed.host, path: segments.join("/"), file };
|
|
145
|
-
}
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
/** Host of the configured registry, or `null` when the URL is unparseable. */
|
|
149
|
-
registryHost() {
|
|
150
|
-
try {
|
|
151
|
-
return new URL(this.registryUrl).host || null;
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
async listVersions(ref) {
|
|
158
|
-
// Only bare registry refs are version-enumerable — a direct `https://` URL
|
|
159
|
-
// has no version-list endpoint.
|
|
160
|
-
if (!isRegistryRef(ref))
|
|
161
|
-
return null;
|
|
162
|
-
const { modulePath } = parseModuleRef(ref);
|
|
163
|
-
const url = `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}`;
|
|
164
|
-
await assertPublicEgress(url);
|
|
165
|
-
const res = await fetchOrThrow(url, { headers: { accept: "application/json" } }, { operation: "Registry version list", setting: "--registry / TELO_REGISTRY" });
|
|
166
|
-
if (res.status === 404)
|
|
167
|
-
return null;
|
|
168
|
-
if (!res.ok) {
|
|
169
|
-
throw new Error(`Registry returned ${res.status} ${res.statusText} for ${modulePath}`);
|
|
170
|
-
}
|
|
171
|
-
const body = (await res.json());
|
|
172
|
-
return Array.isArray(body.versions) ? body.versions : [];
|
|
173
|
-
}
|
|
174
|
-
refVersion(ref) {
|
|
175
|
-
// Only bare `namespace/name@version` refs carry an upgradeable version — a
|
|
176
|
-
// direct `https://` URL has no version segment to bump.
|
|
177
|
-
return isRegistryRef(ref) ? (parseVersionedRef(ref)?.version ?? null) : null;
|
|
178
|
-
}
|
|
179
|
-
withVersion(ref, version) {
|
|
180
|
-
return withRefVersion(ref, version);
|
|
181
|
-
}
|
|
182
|
-
/** Mirrors the sources' fetch-URL derivation: a direct URL points at (or
|
|
183
|
-
* contains) the YAML file; a bare registry ref folds into the registry
|
|
184
|
-
* layout. `null` when this transport does not own the ref's shape. */
|
|
185
|
-
manifestUrl(ref) {
|
|
186
|
-
const { base } = splitIntegrity(ref);
|
|
187
|
-
if (base.startsWith("http://") || base.startsWith("https://")) {
|
|
188
|
-
return base.includes(".yaml") ? base : `${base}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
189
|
-
}
|
|
190
|
-
if (isRegistryRef(ref)) {
|
|
191
|
-
const { modulePath, version } = parseModuleRef(ref);
|
|
192
|
-
return `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}/${version}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
async digest(ref) {
|
|
197
|
-
// The digest is Telo's canonical hash over the `telo.yaml` bytes — the same
|
|
198
|
-
// value `manifestHash` returns, but absent-is-null rather than a throw.
|
|
199
|
-
const fetchUrl = this.manifestUrl(ref);
|
|
200
|
-
if (!fetchUrl)
|
|
201
|
-
return null;
|
|
202
|
-
await assertPublicEgress(fetchUrl);
|
|
203
|
-
const res = await fetchOrThrow(fetchUrl, undefined, {
|
|
204
|
-
operation: "Registry manifest read",
|
|
205
|
-
setting: "--registry / TELO_REGISTRY",
|
|
206
|
-
});
|
|
207
|
-
if (res.status === 404)
|
|
208
|
-
return null;
|
|
209
|
-
if (!res.ok) {
|
|
210
|
-
throw new Error(`Registry returned ${res.status} ${res.statusText} for ${fetchUrl}`);
|
|
211
|
-
}
|
|
212
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
213
|
-
return `sha256-${await sha256Base64Url(bytes)}`;
|
|
214
|
-
}
|
|
215
|
-
/** Hashes the **raw response bytes**, which is exactly what `verifiedFetch`
|
|
216
|
-
* checks an inline `#sha256-…` pin against on the read path. */
|
|
217
|
-
async manifestHash(ref) {
|
|
218
|
-
const fetchUrl = this.manifestUrl(ref);
|
|
219
|
-
if (!fetchUrl) {
|
|
220
|
-
throw new Error(`cannot hash non-remote import '${ref}'`);
|
|
221
|
-
}
|
|
222
|
-
await assertPublicEgress(fetchUrl);
|
|
223
|
-
const res = await fetchOrThrow(fetchUrl, undefined, {
|
|
224
|
-
operation: "Registry manifest hash",
|
|
225
|
-
setting: "--registry / TELO_REGISTRY",
|
|
226
|
-
});
|
|
227
|
-
if (!res.ok) {
|
|
228
|
-
throw new Error(`fetch ${fetchUrl}: ${res.status} ${res.statusText}`);
|
|
229
|
-
}
|
|
230
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
231
|
-
return `sha256-${await sha256Base64Url(bytes)}`;
|
|
232
|
-
}
|
|
233
|
-
/** Layered artifacts are an OCI concept, and the registry origin is read-only
|
|
234
|
-
* — nothing has ever published a payload here, so there is no layer to pull.
|
|
235
|
-
* A module reached over the registry is manifest-only, and its controllers
|
|
236
|
-
* come from npm. */
|
|
237
|
-
async fetchLayer(ref, blobDigest) {
|
|
238
|
-
throw new Error(`Cannot fetch layer ${blobDigest} of ${ref}: the Telo registry serves manifests only. ` +
|
|
239
|
-
`A module with a bundled payload is published as an OCI artifact (oci://host/repo).`);
|
|
240
|
-
}
|
|
241
|
-
async layerIndex(layers) {
|
|
242
|
-
// Same boundary `fetchLayer` and `publish` draw: the HTTP registry serves
|
|
243
|
-
// manifests only, so it frames no layer and can name no blob. An empty set
|
|
244
|
-
// is not a payload, so it answers rather than throws — a manifest-only
|
|
245
|
-
// module builds its payload through this transport during analysis.
|
|
246
|
-
if (layers.every((layer) => layer.files.length === 0))
|
|
247
|
-
return [];
|
|
248
|
-
throw new Error("The Telo registry serves manifests only, so it cannot index payload layers. " +
|
|
249
|
-
"A module with a bundled payload is published as an OCI artifact (oci://host/repo).");
|
|
250
|
-
}
|
|
251
|
-
async publish(destination, bundle, opts = {}) {
|
|
252
|
-
// Publishing to the HTTP Telo registry has been removed — the registry
|
|
253
|
-
// origin stays read-only, and new versions publish to OCI. `telo publish`
|
|
254
|
-
// rejects a non-OCI destination up front, so this is only a guard for a
|
|
255
|
-
// direct programmatic call.
|
|
256
|
-
throw new Error("Publishing to the HTTP Telo registry has been removed. Publish to an OCI " +
|
|
257
|
-
"registry (oci://host/repo) instead.");
|
|
258
|
-
}
|
|
259
|
-
canonicalizeSiblingRef(destination, relativeSource, version) {
|
|
260
|
-
// The sibling sits beside the destination module: resolve the relative path
|
|
261
|
-
// against the destination's own path, then pin the sibling's version. A bare
|
|
262
|
-
// registry ref (`std/foo`) is a path, not a URL, so it borrows a throwaway
|
|
263
|
-
// origin for the join and drops it again.
|
|
264
|
-
const base = splitIntegrity(destination).base.replace(/@[^/@]*$/, "");
|
|
265
|
-
// A registry *base* (`https://registry.telo.run`) is not a module location,
|
|
266
|
-
// so there is nothing for `../lib` to resolve beside — joining anyway would
|
|
267
|
-
// silently produce a ref one path segment short.
|
|
268
|
-
if (isRegistryBase(base)) {
|
|
269
|
-
throw new Error(`cannot canonicalize the relative import '${relativeSource}': publish destination ` +
|
|
270
|
-
`'${destination}' is a registry base, not this module's own location. Pass the ` +
|
|
271
|
-
`module's full destination (e.g. '${base.replace(/\/+$/, "")}/<namespace>/<name>').`);
|
|
272
|
-
}
|
|
273
|
-
const isUrl = base.startsWith("http://") || base.startsWith("https://");
|
|
274
|
-
const origin = isUrl ? base : `${JOIN_ORIGIN}/${base.replace(/^\/+/, "")}`;
|
|
275
|
-
const resolved = new URL(relativeSource, `${origin.replace(/\/+$/, "")}/`);
|
|
276
|
-
const joined = isUrl
|
|
277
|
-
? `${resolved.protocol}//${resolved.host}${resolved.pathname}`
|
|
278
|
-
: resolved.pathname.replace(/^\/+/, "");
|
|
279
|
-
return `${joined.replace(/\/+$/, "")}@${version}`;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
//# sourceMappingURL=registry-transport.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"registry-transport.js","sourceRoot":"","sources":["../../src/transports/registry-transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_MANIFEST_FILENAME,
|
|
3
|
-
HttpSource,
|
|
4
|
-
RegistrySource,
|
|
5
|
-
isRegistryRef,
|
|
6
|
-
parseModuleRef,
|
|
7
|
-
parseVersionedRef,
|
|
8
|
-
sha256Base64Url,
|
|
9
|
-
withRefVersion,
|
|
10
|
-
splitIntegrity,
|
|
11
|
-
type ArtifactLayer,
|
|
12
|
-
type ManifestCacheCoords,
|
|
13
|
-
type ManifestSource,
|
|
14
|
-
} from "@telorun/analyzer";
|
|
15
|
-
import { fetchOrThrow } from "@telorun/sdk";
|
|
16
|
-
import { createHash } from "crypto";
|
|
17
|
-
|
|
18
|
-
import type { PayloadFile } from "../bundle/files-integrity.js";
|
|
19
|
-
import { assertPublicEgress } from "./egress-guard.js";
|
|
20
|
-
import type {
|
|
21
|
-
PayloadLayer,
|
|
22
|
-
PublishBundle,
|
|
23
|
-
PublishOptions,
|
|
24
|
-
PublishResult,
|
|
25
|
-
Transport,
|
|
26
|
-
} from "./transport.js";
|
|
27
|
-
|
|
28
|
-
const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
|
|
29
|
-
/** Throwaway origin that lets a bare registry ref use URL path resolution. */
|
|
30
|
-
const JOIN_ORIGIN = "https://ref.invalid";
|
|
31
|
-
|
|
32
|
-
/** True for an HTTP(S) destination that names a registry root rather than a
|
|
33
|
-
* module within it — no path segments to resolve a sibling beside. */
|
|
34
|
-
function isRegistryBase(base: string): boolean {
|
|
35
|
-
if (!base.startsWith("http://") && !base.startsWith("https://")) return false;
|
|
36
|
-
try {
|
|
37
|
-
return new URL(base).pathname.replace(/\/+/g, "/").replace(/^\/|\/$/g, "") === "";
|
|
38
|
-
} catch {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const QUERY_HASH_LENGTH = 12;
|
|
43
|
-
|
|
44
|
-
/** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
|
|
45
|
-
* already point at a YAML file, append `/telo.yaml`, so a raw import URL and
|
|
46
|
-
* the canonical source it resolves to map to the same cache path. */
|
|
47
|
-
function normalizePathname(rawUrl: string, parsed: URL): string {
|
|
48
|
-
let pathname = parsed.pathname;
|
|
49
|
-
if (!rawUrl.includes(".yaml")) {
|
|
50
|
-
pathname = pathname.endsWith("/")
|
|
51
|
-
? `${pathname}${DEFAULT_MANIFEST_FILENAME}`
|
|
52
|
-
: `${pathname}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
53
|
-
}
|
|
54
|
-
return pathname;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Short hash of `search + hash` so two URLs that differ only in query /
|
|
58
|
-
* fragment do not collide at the same cache path. */
|
|
59
|
-
function disambiguatePath(pathname: string, search: string, hash: string): string {
|
|
60
|
-
if (!search && !hash) return pathname;
|
|
61
|
-
const digest = createHash("sha256")
|
|
62
|
-
.update(search + hash)
|
|
63
|
-
.digest("hex")
|
|
64
|
-
.slice(0, QUERY_HASH_LENGTH);
|
|
65
|
-
const dotIdx = pathname.lastIndexOf(".");
|
|
66
|
-
const slashIdx = pathname.lastIndexOf("/");
|
|
67
|
-
const ext = dotIdx > slashIdx ? pathname.slice(dotIdx) : "";
|
|
68
|
-
const base = pathname.slice(0, pathname.length - ext.length);
|
|
69
|
-
return `${base}.${digest}${ext}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface VersionsResponse {
|
|
73
|
-
versions?: string[];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** The default HTTP transport: bare `namespace/name@version` registry refs and
|
|
77
|
-
* direct `https://…` URLs, resolving against `registry.telo.run` (or a
|
|
78
|
-
* configured registry). Its resolution `source` composes the browser-safe
|
|
79
|
-
* `RegistrySource` / `HttpSource` from `analyzer`; the Node-only management
|
|
80
|
-
* methods live here. This is the fallback transport for any ref that carries
|
|
81
|
-
* no owning scheme, so `oci://` (or a future `s3://`) never falls through to
|
|
82
|
-
* it — those refs are claimed by their own transport's `supports()`. */
|
|
83
|
-
export class RegistryTransport implements Transport {
|
|
84
|
-
private readonly registrySource: RegistrySource;
|
|
85
|
-
private readonly httpSource: HttpSource;
|
|
86
|
-
readonly source: ManifestSource;
|
|
87
|
-
|
|
88
|
-
constructor(private readonly registryUrl: string = DEFAULT_REGISTRY_URL) {
|
|
89
|
-
this.registrySource = new RegistrySource(registryUrl);
|
|
90
|
-
this.httpSource = new HttpSource();
|
|
91
|
-
const pick = (ref: string): ManifestSource =>
|
|
92
|
-
this.httpSource.supports(ref) ? this.httpSource : this.registrySource;
|
|
93
|
-
this.source = {
|
|
94
|
-
supports: (url) => this.supports(url),
|
|
95
|
-
read: async (url) => {
|
|
96
|
-
// The browser-safe sources do the fetch; the Node-side egress policy
|
|
97
|
-
// is enforced here, on the host the read will actually hit.
|
|
98
|
-
await assertPublicEgress(this.httpSource.supports(url) ? url : this.registryUrl);
|
|
99
|
-
return pick(url).read(url);
|
|
100
|
-
},
|
|
101
|
-
resolveRelative: (base, relative) => pick(base).resolveRelative(base, relative),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
supports(ref: string): boolean {
|
|
106
|
-
const { base } = splitIntegrity(ref);
|
|
107
|
-
return base.startsWith("http://") || base.startsWith("https://") || isRegistryRef(ref);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
cacheCoords(ref: string): ManifestCacheCoords | null {
|
|
111
|
-
const url = splitIntegrity(ref).base;
|
|
112
|
-
const trimmedRegistry = this.registryUrl.replace(/\/+$/, "");
|
|
113
|
-
const registryHost = this.registryHost();
|
|
114
|
-
|
|
115
|
-
// 1. Registry ref form: <path>@<version>. Keyed under the registry's host —
|
|
116
|
-
// a ref says nothing about which registry serves it, so without the host
|
|
117
|
-
// two registries' copies of the same path/version share one cache entry.
|
|
118
|
-
if (isRegistryRef(url)) {
|
|
119
|
-
if (!registryHost) return null;
|
|
120
|
-
let parsed: ReturnType<typeof parseModuleRef>;
|
|
121
|
-
try {
|
|
122
|
-
parsed = parseModuleRef(url);
|
|
123
|
-
} catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
transport: "registry",
|
|
128
|
-
host: registryHost,
|
|
129
|
-
path: parsed.modulePath,
|
|
130
|
-
version: parsed.version,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 2. HTTP(S) URL — a direct registry URL or arbitrary external.
|
|
135
|
-
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
136
|
-
let parsed: URL;
|
|
137
|
-
try {
|
|
138
|
-
parsed = new URL(url);
|
|
139
|
-
} catch {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
const pathname = normalizePathname(url, parsed);
|
|
143
|
-
|
|
144
|
-
// 2a. On the configured registry, no query/fragment: fold into the
|
|
145
|
-
// registry layout so a ref and a direct URL land on the same file.
|
|
146
|
-
const normalizedUrl = `${parsed.protocol}//${parsed.host}${pathname}`;
|
|
147
|
-
if (
|
|
148
|
-
registryHost &&
|
|
149
|
-
!parsed.search &&
|
|
150
|
-
!parsed.hash &&
|
|
151
|
-
normalizedUrl.startsWith(`${trimmedRegistry}/`)
|
|
152
|
-
) {
|
|
153
|
-
// The registry serves `<path…>/<version>/<file>`, where `<file>` is the
|
|
154
|
-
// module manifest or one of its `include:` partials.
|
|
155
|
-
const segments = normalizedUrl.slice(trimmedRegistry.length + 1).split("/");
|
|
156
|
-
const file = segments.pop();
|
|
157
|
-
const version = segments.pop();
|
|
158
|
-
if (file && version && segments.length > 0) {
|
|
159
|
-
return {
|
|
160
|
-
transport: "registry",
|
|
161
|
-
host: registryHost,
|
|
162
|
-
path: segments.join("/"),
|
|
163
|
-
version,
|
|
164
|
-
file,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 2b. Arbitrary HTTP(S) → `url` subtree, query-hash suffix on collision.
|
|
170
|
-
// No version segment: a URL addresses exactly one file, and the
|
|
171
|
-
// version it declares lives inside bytes the cache maps paths without.
|
|
172
|
-
const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
173
|
-
const segments = disambiguatePath(cleanPath, parsed.search, parsed.hash).split("/");
|
|
174
|
-
const file = segments.pop();
|
|
175
|
-
if (!file) return null;
|
|
176
|
-
return { transport: "url", host: parsed.host, path: segments.join("/"), file };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Host of the configured registry, or `null` when the URL is unparseable. */
|
|
183
|
-
private registryHost(): string | null {
|
|
184
|
-
try {
|
|
185
|
-
return new URL(this.registryUrl).host || null;
|
|
186
|
-
} catch {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async listVersions(ref: string): Promise<string[] | null> {
|
|
192
|
-
// Only bare registry refs are version-enumerable — a direct `https://` URL
|
|
193
|
-
// has no version-list endpoint.
|
|
194
|
-
if (!isRegistryRef(ref)) return null;
|
|
195
|
-
const { modulePath } = parseModuleRef(ref);
|
|
196
|
-
const url = `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}`;
|
|
197
|
-
await assertPublicEgress(url);
|
|
198
|
-
const res = await fetchOrThrow(
|
|
199
|
-
url,
|
|
200
|
-
{ headers: { accept: "application/json" } },
|
|
201
|
-
{ operation: "Registry version list", setting: "--registry / TELO_REGISTRY" },
|
|
202
|
-
);
|
|
203
|
-
if (res.status === 404) return null;
|
|
204
|
-
if (!res.ok) {
|
|
205
|
-
throw new Error(`Registry returned ${res.status} ${res.statusText} for ${modulePath}`);
|
|
206
|
-
}
|
|
207
|
-
const body = (await res.json()) as VersionsResponse;
|
|
208
|
-
return Array.isArray(body.versions) ? body.versions : [];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
refVersion(ref: string): string | null {
|
|
212
|
-
// Only bare `namespace/name@version` refs carry an upgradeable version — a
|
|
213
|
-
// direct `https://` URL has no version segment to bump.
|
|
214
|
-
return isRegistryRef(ref) ? (parseVersionedRef(ref)?.version ?? null) : null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
withVersion(ref: string, version: string): string {
|
|
218
|
-
return withRefVersion(ref, version);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** Mirrors the sources' fetch-URL derivation: a direct URL points at (or
|
|
222
|
-
* contains) the YAML file; a bare registry ref folds into the registry
|
|
223
|
-
* layout. `null` when this transport does not own the ref's shape. */
|
|
224
|
-
private manifestUrl(ref: string): string | null {
|
|
225
|
-
const { base } = splitIntegrity(ref);
|
|
226
|
-
if (base.startsWith("http://") || base.startsWith("https://")) {
|
|
227
|
-
return base.includes(".yaml") ? base : `${base}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
228
|
-
}
|
|
229
|
-
if (isRegistryRef(ref)) {
|
|
230
|
-
const { modulePath, version } = parseModuleRef(ref);
|
|
231
|
-
return `${this.registryUrl.replace(/\/+$/, "")}/${modulePath}/${version}/${DEFAULT_MANIFEST_FILENAME}`;
|
|
232
|
-
}
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async digest(ref: string): Promise<string | null> {
|
|
237
|
-
// The digest is Telo's canonical hash over the `telo.yaml` bytes — the same
|
|
238
|
-
// value `manifestHash` returns, but absent-is-null rather than a throw.
|
|
239
|
-
const fetchUrl = this.manifestUrl(ref);
|
|
240
|
-
if (!fetchUrl) return null;
|
|
241
|
-
await assertPublicEgress(fetchUrl);
|
|
242
|
-
const res = await fetchOrThrow(fetchUrl, undefined, {
|
|
243
|
-
operation: "Registry manifest read",
|
|
244
|
-
setting: "--registry / TELO_REGISTRY",
|
|
245
|
-
});
|
|
246
|
-
if (res.status === 404) return null;
|
|
247
|
-
if (!res.ok) {
|
|
248
|
-
throw new Error(`Registry returned ${res.status} ${res.statusText} for ${fetchUrl}`);
|
|
249
|
-
}
|
|
250
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
251
|
-
return `sha256-${await sha256Base64Url(bytes)}`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/** Hashes the **raw response bytes**, which is exactly what `verifiedFetch`
|
|
255
|
-
* checks an inline `#sha256-…` pin against on the read path. */
|
|
256
|
-
async manifestHash(ref: string): Promise<string> {
|
|
257
|
-
const fetchUrl = this.manifestUrl(ref);
|
|
258
|
-
if (!fetchUrl) {
|
|
259
|
-
throw new Error(`cannot hash non-remote import '${ref}'`);
|
|
260
|
-
}
|
|
261
|
-
await assertPublicEgress(fetchUrl);
|
|
262
|
-
const res = await fetchOrThrow(fetchUrl, undefined, {
|
|
263
|
-
operation: "Registry manifest hash",
|
|
264
|
-
setting: "--registry / TELO_REGISTRY",
|
|
265
|
-
});
|
|
266
|
-
if (!res.ok) {
|
|
267
|
-
throw new Error(`fetch ${fetchUrl}: ${res.status} ${res.statusText}`);
|
|
268
|
-
}
|
|
269
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
270
|
-
return `sha256-${await sha256Base64Url(bytes)}`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** Layered artifacts are an OCI concept, and the registry origin is read-only
|
|
274
|
-
* — nothing has ever published a payload here, so there is no layer to pull.
|
|
275
|
-
* A module reached over the registry is manifest-only, and its controllers
|
|
276
|
-
* come from npm. */
|
|
277
|
-
async fetchLayer(ref: string, blobDigest: string): Promise<PayloadFile[]> {
|
|
278
|
-
throw new Error(
|
|
279
|
-
`Cannot fetch layer ${blobDigest} of ${ref}: the Telo registry serves manifests only. ` +
|
|
280
|
-
`A module with a bundled payload is published as an OCI artifact (oci://host/repo).`,
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async layerIndex(layers: readonly PayloadLayer[]): Promise<ArtifactLayer[]> {
|
|
285
|
-
// Same boundary `fetchLayer` and `publish` draw: the HTTP registry serves
|
|
286
|
-
// manifests only, so it frames no layer and can name no blob. An empty set
|
|
287
|
-
// is not a payload, so it answers rather than throws — a manifest-only
|
|
288
|
-
// module builds its payload through this transport during analysis.
|
|
289
|
-
if (layers.every((layer) => layer.files.length === 0)) return [];
|
|
290
|
-
throw new Error(
|
|
291
|
-
"The Telo registry serves manifests only, so it cannot index payload layers. " +
|
|
292
|
-
"A module with a bundled payload is published as an OCI artifact (oci://host/repo).",
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async publish(
|
|
297
|
-
destination: string,
|
|
298
|
-
bundle: PublishBundle,
|
|
299
|
-
opts: PublishOptions = {},
|
|
300
|
-
): Promise<PublishResult> {
|
|
301
|
-
// Publishing to the HTTP Telo registry has been removed — the registry
|
|
302
|
-
// origin stays read-only, and new versions publish to OCI. `telo publish`
|
|
303
|
-
// rejects a non-OCI destination up front, so this is only a guard for a
|
|
304
|
-
// direct programmatic call.
|
|
305
|
-
throw new Error(
|
|
306
|
-
"Publishing to the HTTP Telo registry has been removed. Publish to an OCI " +
|
|
307
|
-
"registry (oci://host/repo) instead.",
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
canonicalizeSiblingRef(
|
|
312
|
-
destination: string,
|
|
313
|
-
relativeSource: string,
|
|
314
|
-
version: string,
|
|
315
|
-
): string {
|
|
316
|
-
// The sibling sits beside the destination module: resolve the relative path
|
|
317
|
-
// against the destination's own path, then pin the sibling's version. A bare
|
|
318
|
-
// registry ref (`std/foo`) is a path, not a URL, so it borrows a throwaway
|
|
319
|
-
// origin for the join and drops it again.
|
|
320
|
-
const base = splitIntegrity(destination).base.replace(/@[^/@]*$/, "");
|
|
321
|
-
// A registry *base* (`https://registry.telo.run`) is not a module location,
|
|
322
|
-
// so there is nothing for `../lib` to resolve beside — joining anyway would
|
|
323
|
-
// silently produce a ref one path segment short.
|
|
324
|
-
if (isRegistryBase(base)) {
|
|
325
|
-
throw new Error(
|
|
326
|
-
`cannot canonicalize the relative import '${relativeSource}': publish destination ` +
|
|
327
|
-
`'${destination}' is a registry base, not this module's own location. Pass the ` +
|
|
328
|
-
`module's full destination (e.g. '${base.replace(/\/+$/, "")}/<namespace>/<name>').`,
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
const isUrl = base.startsWith("http://") || base.startsWith("https://");
|
|
332
|
-
const origin = isUrl ? base : `${JOIN_ORIGIN}/${base.replace(/^\/+/, "")}`;
|
|
333
|
-
const resolved = new URL(relativeSource, `${origin.replace(/\/+$/, "")}/`);
|
|
334
|
-
const joined = isUrl
|
|
335
|
-
? `${resolved.protocol}//${resolved.host}${resolved.pathname}`
|
|
336
|
-
: resolved.pathname.replace(/^\/+/, "");
|
|
337
|
-
return `${joined.replace(/\/+$/, "")}@${version}`;
|
|
338
|
-
}
|
|
339
|
-
}
|