@telorun/kernel 0.54.0 → 0.56.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-manifest.d.ts +2 -3
- package/dist/bundle/module-manifest.d.ts.map +1 -1
- package/dist/bundle/module-manifest.js +2 -3
- package/dist/bundle/module-manifest.js.map +1 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +5 -3
- package/dist/kernel.js.map +1 -1
- package/dist/manifest-schemas.d.ts +12 -0
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +6 -1
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/manifest-sources/local-manifest-cache-source.d.ts.map +1 -1
- package/dist/manifest-sources/local-manifest-cache-source.js +10 -6
- package/dist/manifest-sources/local-manifest-cache-source.js.map +1 -1
- package/dist/transports/oci/oci-transport.d.ts +8 -4
- package/dist/transports/oci/oci-transport.d.ts.map +1 -1
- package/dist/transports/oci/oci-transport.js +13 -4
- package/dist/transports/oci/oci-transport.js.map +1 -1
- package/dist/transports/registry-transport.d.ts +6 -4
- package/dist/transports/registry-transport.d.ts.map +1 -1
- package/dist/transports/registry-transport.js +81 -18
- package/dist/transports/registry-transport.js.map +1 -1
- package/dist/transports/transport-registry.d.ts +4 -4
- package/dist/transports/transport-registry.d.ts.map +1 -1
- package/dist/transports/transport-registry.js +4 -4
- package/dist/transports/transport-registry.js.map +1 -1
- package/dist/transports/transport.d.ts +27 -19
- package/dist/transports/transport.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/bundle/module-manifest.ts +2 -4
- package/src/kernel.ts +5 -3
- package/src/manifest-schemas.ts +6 -1
- package/src/manifest-sources/local-manifest-cache-source.ts +15 -6
- package/src/transports/oci/oci-transport.ts +14 -5
- package/src/transports/registry-transport.ts +80 -19
- package/src/transports/transport-registry.ts +5 -5
- package/src/transports/transport.ts +27 -20
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
parseModuleRef,
|
|
8
8
|
sha256Base64Url,
|
|
9
9
|
splitIntegrity,
|
|
10
|
+
type ManifestCacheCoords,
|
|
10
11
|
type ManifestSource,
|
|
11
12
|
} from "@telorun/analyzer";
|
|
12
13
|
import { fetchOrThrow } from "@telorun/sdk";
|
|
@@ -21,12 +22,23 @@ import type {
|
|
|
21
22
|
PublishBundle,
|
|
22
23
|
PublishOptions,
|
|
23
24
|
PublishResult,
|
|
24
|
-
SiblingIdentity,
|
|
25
25
|
Transport,
|
|
26
26
|
} from "./transport.js";
|
|
27
27
|
|
|
28
28
|
const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
|
|
29
|
-
|
|
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
|
+
}
|
|
30
42
|
const QUERY_HASH_LENGTH = 12;
|
|
31
43
|
|
|
32
44
|
/** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
|
|
@@ -95,19 +107,28 @@ export class RegistryTransport implements Transport {
|
|
|
95
107
|
return base.startsWith("http://") || base.startsWith("https://") || isRegistryRef(ref);
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
|
|
110
|
+
cacheCoords(ref: string): ManifestCacheCoords | null {
|
|
99
111
|
const url = splitIntegrity(ref).base;
|
|
100
112
|
const trimmedRegistry = this.registryUrl.replace(/\/+$/, "");
|
|
113
|
+
const registryHost = this.registryHost();
|
|
101
114
|
|
|
102
|
-
// 1. Registry ref form:
|
|
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.
|
|
103
118
|
if (isRegistryRef(url)) {
|
|
119
|
+
if (!registryHost) return null;
|
|
104
120
|
let parsed: ReturnType<typeof parseModuleRef>;
|
|
105
121
|
try {
|
|
106
122
|
parsed = parseModuleRef(url);
|
|
107
123
|
} catch {
|
|
108
124
|
return null;
|
|
109
125
|
}
|
|
110
|
-
return
|
|
126
|
+
return {
|
|
127
|
+
transport: "registry",
|
|
128
|
+
host: registryHost,
|
|
129
|
+
path: parsed.modulePath,
|
|
130
|
+
version: parsed.version,
|
|
131
|
+
};
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
// 2. HTTP(S) URL — a direct registry URL or arbitrary external.
|
|
@@ -124,24 +145,49 @@ export class RegistryTransport implements Transport {
|
|
|
124
145
|
// registry layout so a ref and a direct URL land on the same file.
|
|
125
146
|
const normalizedUrl = `${parsed.protocol}//${parsed.host}${pathname}`;
|
|
126
147
|
if (
|
|
148
|
+
registryHost &&
|
|
127
149
|
!parsed.search &&
|
|
128
150
|
!parsed.hash &&
|
|
129
|
-
|
|
151
|
+
normalizedUrl.startsWith(`${trimmedRegistry}/`)
|
|
130
152
|
) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
}
|
|
134
167
|
}
|
|
135
168
|
|
|
136
|
-
// 2b. Arbitrary HTTP(S) →
|
|
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.
|
|
137
172
|
const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
138
|
-
const
|
|
139
|
-
|
|
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 };
|
|
140
177
|
}
|
|
141
178
|
|
|
142
179
|
return null;
|
|
143
180
|
}
|
|
144
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
|
+
|
|
145
191
|
async listVersions(ref: string): Promise<string[] | null> {
|
|
146
192
|
// Only bare registry refs are version-enumerable — a direct `https://` URL
|
|
147
193
|
// has no version-list endpoint.
|
|
@@ -279,16 +325,31 @@ export class RegistryTransport implements Transport {
|
|
|
279
325
|
}
|
|
280
326
|
|
|
281
327
|
canonicalizeSiblingRef(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
328
|
+
destination: string,
|
|
329
|
+
relativeSource: string,
|
|
330
|
+
version: string,
|
|
285
331
|
): string {
|
|
286
|
-
//
|
|
287
|
-
|
|
332
|
+
// The sibling sits beside the destination module: resolve the relative path
|
|
333
|
+
// against the destination's own path, then pin the sibling's version. A bare
|
|
334
|
+
// registry ref (`std/foo`) is a path, not a URL, so it borrows a throwaway
|
|
335
|
+
// origin for the join and drops it again.
|
|
336
|
+
const base = splitIntegrity(destination).base.replace(/@[^/@]*$/, "");
|
|
337
|
+
// A registry *base* (`https://registry.telo.run`) is not a module location,
|
|
338
|
+
// so there is nothing for `../lib` to resolve beside — joining anyway would
|
|
339
|
+
// silently produce a ref one path segment short.
|
|
340
|
+
if (isRegistryBase(base)) {
|
|
288
341
|
throw new Error(
|
|
289
|
-
|
|
342
|
+
`cannot canonicalize the relative import '${relativeSource}': publish destination ` +
|
|
343
|
+
`'${destination}' is a registry base, not this module's own location. Pass the ` +
|
|
344
|
+
`module's full destination (e.g. '${base.replace(/\/+$/, "")}/<namespace>/<name>').`,
|
|
290
345
|
);
|
|
291
346
|
}
|
|
292
|
-
|
|
347
|
+
const isUrl = base.startsWith("http://") || base.startsWith("https://");
|
|
348
|
+
const origin = isUrl ? base : `${JOIN_ORIGIN}/${base.replace(/^\/+/, "")}`;
|
|
349
|
+
const resolved = new URL(relativeSource, `${origin.replace(/\/+$/, "")}/`);
|
|
350
|
+
const joined = isUrl
|
|
351
|
+
? `${resolved.protocol}//${resolved.host}${resolved.pathname}`
|
|
352
|
+
: resolved.pathname.replace(/^\/+/, "");
|
|
353
|
+
return `${joined.replace(/\/+$/, "")}@${version}`;
|
|
293
354
|
}
|
|
294
355
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ManifestSource } from "@telorun/analyzer";
|
|
1
|
+
import type { ManifestCacheCoords, ManifestSource } from "@telorun/analyzer";
|
|
2
2
|
|
|
3
3
|
import { OciTransport } from "./oci/oci-transport.js";
|
|
4
4
|
import { RegistryTransport } from "./registry-transport.js";
|
|
@@ -29,10 +29,10 @@ export class TransportRegistry {
|
|
|
29
29
|
return this.transports.map((t) => t.source);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* transport owns it or the ref is not cacheable. */
|
|
34
|
-
|
|
35
|
-
return this.forRef(ref)?.
|
|
32
|
+
/** Manifest-cache coordinates for `ref`, from its owning transport; `null`
|
|
33
|
+
* when no transport owns it or the ref is not cacheable. */
|
|
34
|
+
cacheCoords(ref: string): ManifestCacheCoords | null {
|
|
35
|
+
return this.forRef(ref)?.cacheCoords(ref) ?? null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Published versions for `ref` via its owning transport; `null` when the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ManifestSource } from "@telorun/analyzer";
|
|
1
|
+
import type { ManifestCacheCoords, ManifestSource } from "@telorun/analyzer";
|
|
2
2
|
|
|
3
3
|
import type { PayloadFile } from "../bundle/files-integrity.js";
|
|
4
4
|
|
|
@@ -28,15 +28,6 @@ export interface PublishResult {
|
|
|
28
28
|
url: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/** Identity of a sibling library, read from its own manifest, that a relative
|
|
32
|
-
* import canonicalizes to. `version` is always required; `namespace`/`name` are
|
|
33
|
-
* used only by transports whose location is metadata-derived (HTTP registry). */
|
|
34
|
-
export interface SiblingIdentity {
|
|
35
|
-
namespace?: string;
|
|
36
|
-
name?: string;
|
|
37
|
-
version: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
31
|
export interface PublishOptions {
|
|
41
32
|
/** Bearer token for registries that require auth. */
|
|
42
33
|
token?: string;
|
|
@@ -61,7 +52,7 @@ export interface PublishOptions {
|
|
|
61
52
|
* `ManifestSource` is the browser-safe resolution primitive (also implemented
|
|
62
53
|
* by the cache / local / memory sources, which have no versions and nothing to
|
|
63
54
|
* publish), so it stays in `analyzer`, while the Node-only management methods
|
|
64
|
-
* (`
|
|
55
|
+
* (`cacheCoords` and, in later phases, `listVersions` / `fetchArtifact` /
|
|
65
56
|
* `publish`) live on the Transport here in `kernel`. */
|
|
66
57
|
export interface Transport {
|
|
67
58
|
/** True when this transport owns the given ref (or publish destination). */
|
|
@@ -72,10 +63,21 @@ export interface Transport {
|
|
|
72
63
|
* live in `analyzer`; a Node-only transport has no browser-safe source. */
|
|
73
64
|
readonly source: ManifestSource;
|
|
74
65
|
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
66
|
+
/** Where a ref's `telo.yaml` is cached, as the transport-neutral
|
|
67
|
+
* `{ transport, host, path, version, file }` coordinates the analyzer's
|
|
68
|
+
* `manifestCacheKey` renders into a path. One grammar serves the local
|
|
69
|
+
* install cache, the hub's static manifest bucket, and the editor's read
|
|
70
|
+
* path, so the three cannot drift on the *shape* of a cache key.
|
|
71
|
+
*
|
|
72
|
+
* They do differ on coordinates, deliberately, for a `url` ref: the hub reads
|
|
73
|
+
* the version out of the fetched manifest and keys by it, while this cache
|
|
74
|
+
* maps a ref to a path before any fetch and so has no version to supply —
|
|
75
|
+
* it names the file instead, since it also stores each `include:` partial.
|
|
76
|
+
* Same grammar, different coordinates; not a drift to reconcile.
|
|
77
|
+
*
|
|
78
|
+
* Returns `null` when the ref is not cacheable here (unsupported scheme,
|
|
79
|
+
* malformed ref, or path-traversal in the ref). */
|
|
80
|
+
cacheCoords(ref: string): ManifestCacheCoords | null;
|
|
79
81
|
|
|
80
82
|
/** The versions published for the module `ref` names, newest-first order not
|
|
81
83
|
* guaranteed (the caller sorts). Returns `null` when the module is not
|
|
@@ -147,13 +149,18 @@ export interface Transport {
|
|
|
147
149
|
|
|
148
150
|
/** Canonicalize a relative sibling import (`../lib`) declared in a module
|
|
149
151
|
* being published to `destination` into the absolute ref it will resolve to
|
|
150
|
-
* once published.
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
152
|
+
* once published.
|
|
153
|
+
*
|
|
154
|
+
* The sibling lands beside the destination: the destination's last segment is
|
|
155
|
+
* the module's own directory, so the relative path resolves against it
|
|
156
|
+
* exactly as it does on the publisher's disk — publishing `…/telorun/foo`
|
|
157
|
+
* with an import of `../bar` yields `…/telorun/bar`. Only the version comes
|
|
158
|
+
* from the sibling's own manifest; nothing is read from its metadata to
|
|
159
|
+
* decide where it lives. Each transport owns the join for its ref grammar, so
|
|
160
|
+
* `telo publish` delegates instead of branching on transport shape. */
|
|
154
161
|
canonicalizeSiblingRef(
|
|
155
162
|
destination: string,
|
|
156
163
|
relativeSource: string,
|
|
157
|
-
|
|
164
|
+
version: string,
|
|
158
165
|
): string;
|
|
159
166
|
}
|