@telorun/kernel 0.55.0 → 0.57.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.js +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-ref.d.ts +1 -1
- package/dist/transports/oci/oci-ref.d.ts.map +1 -1
- package/dist/transports/oci/oci-ref.js +1 -1
- package/dist/transports/oci/oci-ref.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 +20 -14
- 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 +84 -26
- 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 +1 -1
- package/src/manifest-sources/local-manifest-cache-source.ts +15 -6
- package/src/transports/oci/oci-ref.ts +8 -1
- package/src/transports/oci/oci-transport.ts +26 -14
- package/src/transports/registry-transport.ts +84 -25
- package/src/transports/transport-registry.ts +5 -5
- package/src/transports/transport.ts +27 -20
|
@@ -5,8 +5,11 @@ import {
|
|
|
5
5
|
RegistrySource,
|
|
6
6
|
isRegistryRef,
|
|
7
7
|
parseModuleRef,
|
|
8
|
+
parseVersionedRef,
|
|
8
9
|
sha256Base64Url,
|
|
10
|
+
withRefVersion,
|
|
9
11
|
splitIntegrity,
|
|
12
|
+
type ManifestCacheCoords,
|
|
10
13
|
type ManifestSource,
|
|
11
14
|
} from "@telorun/analyzer";
|
|
12
15
|
import { fetchOrThrow } from "@telorun/sdk";
|
|
@@ -21,12 +24,23 @@ import type {
|
|
|
21
24
|
PublishBundle,
|
|
22
25
|
PublishOptions,
|
|
23
26
|
PublishResult,
|
|
24
|
-
SiblingIdentity,
|
|
25
27
|
Transport,
|
|
26
28
|
} from "./transport.js";
|
|
27
29
|
|
|
28
30
|
const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
|
|
29
|
-
|
|
31
|
+
/** Throwaway origin that lets a bare registry ref use URL path resolution. */
|
|
32
|
+
const JOIN_ORIGIN = "https://ref.invalid";
|
|
33
|
+
|
|
34
|
+
/** True for an HTTP(S) destination that names a registry root rather than a
|
|
35
|
+
* module within it — no path segments to resolve a sibling beside. */
|
|
36
|
+
function isRegistryBase(base: string): boolean {
|
|
37
|
+
if (!base.startsWith("http://") && !base.startsWith("https://")) return false;
|
|
38
|
+
try {
|
|
39
|
+
return new URL(base).pathname.replace(/\/+/g, "/").replace(/^\/|\/$/g, "") === "";
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
30
44
|
const QUERY_HASH_LENGTH = 12;
|
|
31
45
|
|
|
32
46
|
/** Mirror `HttpSource.read`'s `fetchUrl` derivation: when the URL does not
|
|
@@ -95,19 +109,28 @@ export class RegistryTransport implements Transport {
|
|
|
95
109
|
return base.startsWith("http://") || base.startsWith("https://") || isRegistryRef(ref);
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
|
|
112
|
+
cacheCoords(ref: string): ManifestCacheCoords | null {
|
|
99
113
|
const url = splitIntegrity(ref).base;
|
|
100
114
|
const trimmedRegistry = this.registryUrl.replace(/\/+$/, "");
|
|
115
|
+
const registryHost = this.registryHost();
|
|
101
116
|
|
|
102
|
-
// 1. Registry ref form:
|
|
117
|
+
// 1. Registry ref form: <path>@<version>. Keyed under the registry's host —
|
|
118
|
+
// a ref says nothing about which registry serves it, so without the host
|
|
119
|
+
// two registries' copies of the same path/version share one cache entry.
|
|
103
120
|
if (isRegistryRef(url)) {
|
|
121
|
+
if (!registryHost) return null;
|
|
104
122
|
let parsed: ReturnType<typeof parseModuleRef>;
|
|
105
123
|
try {
|
|
106
124
|
parsed = parseModuleRef(url);
|
|
107
125
|
} catch {
|
|
108
126
|
return null;
|
|
109
127
|
}
|
|
110
|
-
return
|
|
128
|
+
return {
|
|
129
|
+
transport: "registry",
|
|
130
|
+
host: registryHost,
|
|
131
|
+
path: parsed.modulePath,
|
|
132
|
+
version: parsed.version,
|
|
133
|
+
};
|
|
111
134
|
}
|
|
112
135
|
|
|
113
136
|
// 2. HTTP(S) URL — a direct registry URL or arbitrary external.
|
|
@@ -124,24 +147,49 @@ export class RegistryTransport implements Transport {
|
|
|
124
147
|
// registry layout so a ref and a direct URL land on the same file.
|
|
125
148
|
const normalizedUrl = `${parsed.protocol}//${parsed.host}${pathname}`;
|
|
126
149
|
if (
|
|
150
|
+
registryHost &&
|
|
127
151
|
!parsed.search &&
|
|
128
152
|
!parsed.hash &&
|
|
129
|
-
|
|
153
|
+
normalizedUrl.startsWith(`${trimmedRegistry}/`)
|
|
130
154
|
) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
155
|
+
// The registry serves `<path…>/<version>/<file>`, where `<file>` is the
|
|
156
|
+
// module manifest or one of its `include:` partials.
|
|
157
|
+
const segments = normalizedUrl.slice(trimmedRegistry.length + 1).split("/");
|
|
158
|
+
const file = segments.pop();
|
|
159
|
+
const version = segments.pop();
|
|
160
|
+
if (file && version && segments.length > 0) {
|
|
161
|
+
return {
|
|
162
|
+
transport: "registry",
|
|
163
|
+
host: registryHost,
|
|
164
|
+
path: segments.join("/"),
|
|
165
|
+
version,
|
|
166
|
+
file,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
134
169
|
}
|
|
135
170
|
|
|
136
|
-
// 2b. Arbitrary HTTP(S) →
|
|
171
|
+
// 2b. Arbitrary HTTP(S) → `url` subtree, query-hash suffix on collision.
|
|
172
|
+
// No version segment: a URL addresses exactly one file, and the
|
|
173
|
+
// version it declares lives inside bytes the cache maps paths without.
|
|
137
174
|
const cleanPath = pathname.startsWith("/") ? pathname.slice(1) : pathname;
|
|
138
|
-
const
|
|
139
|
-
|
|
175
|
+
const segments = disambiguatePath(cleanPath, parsed.search, parsed.hash).split("/");
|
|
176
|
+
const file = segments.pop();
|
|
177
|
+
if (!file) return null;
|
|
178
|
+
return { transport: "url", host: parsed.host, path: segments.join("/"), file };
|
|
140
179
|
}
|
|
141
180
|
|
|
142
181
|
return null;
|
|
143
182
|
}
|
|
144
183
|
|
|
184
|
+
/** Host of the configured registry, or `null` when the URL is unparseable. */
|
|
185
|
+
private registryHost(): string | null {
|
|
186
|
+
try {
|
|
187
|
+
return new URL(this.registryUrl).host || null;
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
145
193
|
async listVersions(ref: string): Promise<string[] | null> {
|
|
146
194
|
// Only bare registry refs are version-enumerable — a direct `https://` URL
|
|
147
195
|
// has no version-list endpoint.
|
|
@@ -165,15 +213,11 @@ export class RegistryTransport implements Transport {
|
|
|
165
213
|
refVersion(ref: string): string | null {
|
|
166
214
|
// Only bare `namespace/name@version` refs carry an upgradeable version — a
|
|
167
215
|
// direct `https://` URL has no version segment to bump.
|
|
168
|
-
|
|
169
|
-
const { base } = splitIntegrity(ref);
|
|
170
|
-
const at = base.lastIndexOf("@");
|
|
171
|
-
return at > 0 ? base.slice(at + 1) : null;
|
|
216
|
+
return isRegistryRef(ref) ? (parseVersionedRef(ref)?.version ?? null) : null;
|
|
172
217
|
}
|
|
173
218
|
|
|
174
219
|
withVersion(ref: string, version: string): string {
|
|
175
|
-
|
|
176
|
-
return `${modulePath}@${version}`;
|
|
220
|
+
return withRefVersion(ref, version);
|
|
177
221
|
}
|
|
178
222
|
|
|
179
223
|
/** Mirrors the sources' fetch-URL derivation: a direct URL points at (or
|
|
@@ -279,16 +323,31 @@ export class RegistryTransport implements Transport {
|
|
|
279
323
|
}
|
|
280
324
|
|
|
281
325
|
canonicalizeSiblingRef(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
326
|
+
destination: string,
|
|
327
|
+
relativeSource: string,
|
|
328
|
+
version: string,
|
|
285
329
|
): string {
|
|
286
|
-
//
|
|
287
|
-
|
|
330
|
+
// The sibling sits beside the destination module: resolve the relative path
|
|
331
|
+
// against the destination's own path, then pin the sibling's version. A bare
|
|
332
|
+
// registry ref (`std/foo`) is a path, not a URL, so it borrows a throwaway
|
|
333
|
+
// origin for the join and drops it again.
|
|
334
|
+
const base = splitIntegrity(destination).base.replace(/@[^/@]*$/, "");
|
|
335
|
+
// A registry *base* (`https://registry.telo.run`) is not a module location,
|
|
336
|
+
// so there is nothing for `../lib` to resolve beside — joining anyway would
|
|
337
|
+
// silently produce a ref one path segment short.
|
|
338
|
+
if (isRegistryBase(base)) {
|
|
288
339
|
throw new Error(
|
|
289
|
-
|
|
340
|
+
`cannot canonicalize the relative import '${relativeSource}': publish destination ` +
|
|
341
|
+
`'${destination}' is a registry base, not this module's own location. Pass the ` +
|
|
342
|
+
`module's full destination (e.g. '${base.replace(/\/+$/, "")}/<namespace>/<name>').`,
|
|
290
343
|
);
|
|
291
344
|
}
|
|
292
|
-
|
|
345
|
+
const isUrl = base.startsWith("http://") || base.startsWith("https://");
|
|
346
|
+
const origin = isUrl ? base : `${JOIN_ORIGIN}/${base.replace(/^\/+/, "")}`;
|
|
347
|
+
const resolved = new URL(relativeSource, `${origin.replace(/\/+$/, "")}/`);
|
|
348
|
+
const joined = isUrl
|
|
349
|
+
? `${resolved.protocol}//${resolved.host}${resolved.pathname}`
|
|
350
|
+
: resolved.pathname.replace(/^\/+/, "");
|
|
351
|
+
return `${joined.replace(/\/+$/, "")}@${version}`;
|
|
293
352
|
}
|
|
294
353
|
}
|
|
@@ -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
|
}
|