@telorun/analyzer 0.14.0 → 0.16.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.
Files changed (41) hide show
  1. package/dist/analyzer.d.ts.map +1 -1
  2. package/dist/analyzer.js +39 -8
  3. package/dist/builtins.d.ts.map +1 -1
  4. package/dist/builtins.js +64 -1
  5. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  6. package/dist/flatten-for-analyzer.js +8 -4
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/inline-imports.d.ts +34 -0
  11. package/dist/inline-imports.d.ts.map +1 -0
  12. package/dist/inline-imports.js +106 -0
  13. package/dist/manifest-loader.d.ts +10 -1
  14. package/dist/manifest-loader.d.ts.map +1 -1
  15. package/dist/manifest-loader.js +45 -22
  16. package/dist/resolve-ref-sentinels.d.ts.map +1 -1
  17. package/dist/resolve-ref-sentinels.js +3 -11
  18. package/dist/types.d.ts +9 -0
  19. package/dist/types.d.ts.map +1 -1
  20. package/dist/validate-references.d.ts.map +1 -1
  21. package/dist/validate-references.js +25 -22
  22. package/package.json +5 -4
  23. package/src/alias-resolver.ts +9 -0
  24. package/src/analyzer.ts +64 -5
  25. package/src/builtins.ts +64 -0
  26. package/src/flatten-for-analyzer.ts +20 -0
  27. package/src/index.ts +2 -0
  28. package/src/inline-imports.ts +130 -0
  29. package/src/manifest-loader.ts +52 -21
  30. package/src/resolve-ref-sentinels.ts +89 -44
  31. package/src/types.ts +9 -0
  32. package/src/validate-references.ts +103 -8
  33. package/dist/adapters/http-adapter.d.ts +0 -10
  34. package/dist/adapters/http-adapter.d.ts.map +0 -1
  35. package/dist/adapters/http-adapter.js +0 -18
  36. package/dist/adapters/node-adapter.d.ts +0 -17
  37. package/dist/adapters/node-adapter.d.ts.map +0 -1
  38. package/dist/adapters/node-adapter.js +0 -71
  39. package/dist/adapters/registry-adapter.d.ts +0 -15
  40. package/dist/adapters/registry-adapter.d.ts.map +0 -1
  41. package/dist/adapters/registry-adapter.js +0 -53
@@ -5,43 +5,100 @@ import type { DefinitionRegistry } from "./definition-registry.js";
5
5
  import { isRefEntry } from "./reference-field-map.js";
6
6
  import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
7
7
 
8
+ /** Resolved ref shape written in place of a `!ref` sentinel. `alias` is set only for
9
+ * cross-module references (resolved into an imported library's exported instance). */
10
+ type ResolvedRef = { kind: string; name: string; alias?: string };
11
+
8
12
  /**
9
13
  * Walks every `x-telo-ref` slot in every non-system resource and rewrites
10
- * `!ref <name>` sentinels in-place to `{kind: <resolved-kind>, name}`.
14
+ * `!ref <name>` sentinels in-place to `{kind, name}` (local) or
15
+ * `{kind, name, alias}` (cross-module).
11
16
  *
12
- * The downstream pipeline (inline normalization, dependency graph, kernel
13
- * controllers) expects every ref-slot value to be either a `{kind, name}`
14
- * object, an inline-definition object, or a legacy bare string — resolving
15
- * sentinels here keeps that contract intact so each consumer doesn't need
16
- * its own sentinel branch.
17
+ * Reference grammar the tag's source string is split on the FIRST dot:
18
+ * - `!ref writeLine` → local resource `writeLine`
19
+ * - `!ref Self.writeLine` → local resource `writeLine` (explicit self-qualifier)
20
+ * - `!ref Console.writeLine` → instance `writeLine` exported by the import aliased
21
+ * `Console`, resolved against the forwarded foreign set
17
22
  *
18
- * The walker assigns `kind` by name lookup (resource names are unique
19
- * within a manifest scope). When the name doesn't resolve in the local
20
- * `byName` map, the sentinel is left in place so `validateReferences`
21
- * can emit the `UNRESOLVED_REFERENCE` diagnostic with full context.
23
+ * Aliases are PascalCase identifiers without dots and resource names carry no dots
24
+ * (enforced as a hard diagnostic), so the first-dot split is unambiguous. When the
25
+ * name doesn't resolve, the sentinel is left in place so `validateReferences` emits the
26
+ * `UNRESOLVED_REFERENCE` diagnostic with full context.
22
27
  *
23
- * Mutation strategy: the field-path walker descends the resource tree
24
- * directly and replaces the sentinel on its parent container. Re-parsing
25
- * a string-encoded concrete path (the earlier shape) coupled the writer
26
- * to the path-encoding rules of `resolveFieldEntries` — any new path
27
- * marker would silently break this writer. Descending directly avoids
28
- * that coupling.
28
+ * Forwarded foreign resources (an imported library's exported instances, carrying a
29
+ * `metadata.module` that isn't a root module) are resolution TARGETS only — they are not
30
+ * re-walked as sources here, since their own ref slots belong to their own module scope.
29
31
  */
30
32
  export function resolveRefSentinels(
31
33
  resources: ResourceManifest[],
32
34
  registry: DefinitionRegistry,
33
35
  aliases?: AliasResolver,
34
36
  aliasesByModule?: Map<string, AliasResolver>,
37
+ // Extra foreign resources used only as cross-module resolution TARGETS (not mutated, not
38
+ // walked as sources). The kernel passes the analyzer-flattened set here so the runtime
39
+ // pass — which loads the entry module only — can still resolve `!ref Alias.name` against
40
+ // imported libraries' exported instances.
41
+ crossModuleTargets: ResourceManifest[] = [],
35
42
  ): void {
43
+ const moduleOf = (r: ResourceManifest): string | undefined =>
44
+ (r.metadata as { module?: string } | undefined)?.module;
45
+ // Forwarded exports are flagged by flattenForAnalyzer (`metadata.forwardedExport`); they're
46
+ // cross-module resolution targets only — never walked as local ref sources here.
47
+ const isForeign = (r: ResourceManifest): boolean =>
48
+ (r.metadata as { forwardedExport?: boolean } | undefined)?.forwardedExport === true;
49
+
50
+ // Local resources resolve a bare / `Self.`-qualified name; forwarded foreign exports
51
+ // resolve an `Alias.`-qualified name keyed by (module, name).
36
52
  const byName = new Map<string, ResourceManifest>();
53
+ const byModuleName = new Map<string, ResourceManifest>();
37
54
  for (const r of resources) {
38
- if (r.metadata?.name && !SYSTEM_KINDS.has(r.kind)) {
39
- byName.set(r.metadata.name as string, r);
55
+ if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind)) continue;
56
+ const name = r.metadata.name as string;
57
+ if (isForeign(r)) {
58
+ byModuleName.set(`${moduleOf(r)}\0${name}`, r);
59
+ } else {
60
+ byName.set(name, r);
40
61
  }
41
62
  }
63
+ for (const r of crossModuleTargets) {
64
+ if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !isForeign(r)) continue;
65
+ byModuleName.set(`${moduleOf(r)}\0${r.metadata.name as string}`, r);
66
+ }
67
+
68
+ const resolveTarget = (source: string): ResolvedRef | undefined => {
69
+ const dot = source.indexOf(".");
70
+ if (dot === -1) {
71
+ const t = byName.get(source);
72
+ return t ? { kind: t.kind as string, name: source } : undefined;
73
+ }
74
+ const alias = source.slice(0, dot);
75
+ const name = source.slice(dot + 1);
76
+ if (alias === "Self") {
77
+ const t = byName.get(name);
78
+ return t ? { kind: t.kind as string, name } : undefined;
79
+ }
80
+ const module = aliases?.moduleForAlias(alias);
81
+ if (module) {
82
+ const t = byModuleName.get(`${module}\0${name}`);
83
+ if (t) {
84
+ // The foreign instance's `kind` is authored in ITS module's scope (e.g.
85
+ // `Self.WriteLine`); canonicalize to a scope-independent `<module>.<Kind>` for the
86
+ // consumer's kind check. `Self.` maps to the owning module directly — the forwarded
87
+ // library's Library doc (hence its `Self` alias) isn't in the consumer's manifest
88
+ // set — while other alias prefixes resolve via that module's forwarded import scope.
89
+ const rawKind = t.kind as string;
90
+ const foreignKind = rawKind.startsWith("Self.")
91
+ ? `${module}.${rawKind.slice("Self.".length)}`
92
+ : aliasesByModule?.get(module)?.resolveKind(rawKind) ?? rawKind;
93
+ return { kind: foreignKind, name, alias };
94
+ }
95
+ }
96
+ return undefined;
97
+ };
42
98
 
43
99
  for (const r of resources) {
44
100
  if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind)) continue;
101
+ if (isForeign(r)) continue;
45
102
 
46
103
  const fieldMap =
47
104
  aliases && aliasesByModule
@@ -51,45 +108,33 @@ export function resolveRefSentinels(
51
108
 
52
109
  for (const [fieldPath, entry] of fieldMap) {
53
110
  if (!isRefEntry(entry)) continue;
54
- replaceSentinelsAtPath(r as Record<string, unknown>, fieldPath, byName);
111
+ descend(r as Record<string, unknown>, fieldPath.split("."), resolveTarget);
55
112
  }
56
113
  }
57
114
  }
58
115
 
59
- /** Walks `obj` along `fieldPath` (dot notation with `[]` for arrays and
60
- * `{}` for additionalProperties-typed maps) and replaces any `!ref`
61
- * sentinel value at the terminal slot with `{kind, name}` looked up
62
- * via `byName`. Mutates the parent container in place; no string-path
63
- * round-trip. */
64
- function replaceSentinelsAtPath(
65
- obj: Record<string, unknown>,
66
- fieldPath: string,
67
- byName: Map<string, ResourceManifest>,
68
- ): void {
69
- const parts = fieldPath.split(".");
70
- descend(obj, parts, byName);
71
- }
72
-
116
+ /** Walks `obj` along `fieldPath` parts (dot notation with `[]` for arrays and `{}` for
117
+ * additionalProperties-typed maps) and replaces any `!ref` sentinel at the terminal slot
118
+ * with its resolved `{kind, name, alias?}`. Mutates the parent container in place. */
73
119
  function descend(
74
120
  obj: unknown,
75
121
  parts: string[],
76
- byName: Map<string, ResourceManifest>,
122
+ resolve: (source: string) => ResolvedRef | undefined,
77
123
  ): void {
78
124
  if (obj == null || typeof obj !== "object" || parts.length === 0) return;
79
125
  const [head, ...rest] = parts;
80
126
 
81
- // Map iteration: descend into every value of the current object.
82
127
  if (head === "{}") {
83
128
  const container = obj as Record<string, unknown>;
84
129
  for (const key of Object.keys(container)) {
85
130
  const child = container[key];
86
131
  if (rest.length === 0) {
87
132
  if (isRefSentinel(child)) {
88
- const target = byName.get(child.source);
89
- if (target) container[key] = { kind: target.kind as string, name: child.source };
133
+ const target = resolve(child.source);
134
+ if (target) container[key] = target;
90
135
  }
91
136
  } else {
92
- descend(child, rest, byName);
137
+ descend(child, rest, resolve);
93
138
  }
94
139
  }
95
140
  return;
@@ -107,21 +152,21 @@ function descend(
107
152
  if (rest.length === 0) {
108
153
  const elem = val[i];
109
154
  if (isRefSentinel(elem)) {
110
- const target = byName.get(elem.source);
111
- if (target) val[i] = { kind: target.kind as string, name: elem.source };
155
+ const target = resolve(elem.source);
156
+ if (target) val[i] = target;
112
157
  }
113
158
  } else {
114
- descend(val[i], rest, byName);
159
+ descend(val[i], rest, resolve);
115
160
  }
116
161
  }
117
162
  } else {
118
163
  if (rest.length === 0) {
119
164
  if (isRefSentinel(val)) {
120
- const target = byName.get(val.source);
121
- if (target) container[key] = { kind: target.kind as string, name: val.source };
165
+ const target = resolve(val.source);
166
+ if (target) container[key] = target;
122
167
  }
123
168
  } else {
124
- descend(val, rest, byName);
169
+ descend(val, rest, resolve);
125
170
  }
126
171
  }
127
172
  }
package/src/types.ts CHANGED
@@ -63,6 +63,15 @@ export interface LoadOptions {
63
63
  * so the kernel can evaluate them at runtime. Leave unset (false) for static analysis —
64
64
  * the analyzer works on raw strings and does not need compiled values. */
65
65
  compile?: boolean;
66
+ /** When true, each module document's inline `imports:` map is desugared into
67
+ * synthetic `Telo.Import` manifests appended to the file's `manifests` /
68
+ * `positions` (the AST `documents` array is left raw). On for every resolved
69
+ * consumer — the kernel's analysis and runtime loads, and the analyzer — so
70
+ * inline imports participate in discovery, alias resolution, and execution.
71
+ * Off for the editor's round-trip view, which reads the raw `imports:` map and
72
+ * pairs manifests to YAML nodes by index. Folded into the file cache key so a
73
+ * desugared and a raw load of the same file never collide. */
74
+ desugarImports?: boolean;
66
75
  }
67
76
 
68
77
  export interface LoaderInitOptions {
@@ -107,10 +107,46 @@ export function validateReferences(
107
107
  // source, different sourceLine) keep separate fingerprints and still
108
108
  // trip the diagnostic. `analyze()` enforces that every non-system
109
109
  // manifest carries both positional fields — no defensive guard needed.
110
+ // Forwarded foreign exports (an imported library's exported instances, carrying a
111
+ // metadata.module that isn't a root module) are resolution TARGETS only: excluded from
112
+ // duplicate detection and local name resolution, and never walked as ref sources.
113
+ const moduleOf = (r: ResourceManifest): string | undefined =>
114
+ (r.metadata as { module?: string } | undefined)?.module;
115
+ // Forwarded exports are flagged by flattenForAnalyzer (`metadata.forwardedExport`); they're
116
+ // cross-module resolution targets only — excluded from duplicate detection and local name
117
+ // resolution, and never walked as ref sources.
118
+ const isForeign = (r: ResourceManifest): boolean =>
119
+ (r.metadata as { forwardedExport?: boolean } | undefined)?.forwardedExport === true;
120
+ // Forwarded exported instances keyed `${module}\0${name}` — the lookup that resolves
121
+ // whether a cross-module `!ref Alias.name` names a real exported instance.
122
+ const byModuleName = new Map<string, ResourceManifest>();
123
+ /** Modules whose import subtree was actually loaded in this analysis. A resolved
124
+ * `Telo.Import` carries `resolvedModuleName` (stamped only once the edge — and thus the
125
+ * imported module — resolved); forwarded exports carry `metadata.module`. Either marks
126
+ * the module loaded independent of how many instances it exports, so a loaded library
127
+ * that exports nothing still reports invalid cross-module refs, while partial single-file
128
+ * analysis (neither present) is skipped to avoid false `UNRESOLVED_REFERENCE`. */
129
+ const loadedModules = new Set<string>();
130
+ for (const r of resources) {
131
+ if (r.kind === "Telo.Import") {
132
+ const m = (r.metadata as { resolvedModuleName?: unknown } | undefined)?.resolvedModuleName;
133
+ if (typeof m === "string") loadedModules.add(m);
134
+ continue;
135
+ }
136
+ if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || !isForeign(r)) continue;
137
+ const m = moduleOf(r);
138
+ if (!m) continue;
139
+ byModuleName.set(`${m}\0${r.metadata.name as string}`, r);
140
+ loadedModules.add(m);
141
+ }
142
+ const moduleLoaded = (module: string): boolean => loadedModules.has(module);
143
+ const localResources = resources.filter((r) => !isForeign(r));
144
+
110
145
  const byNameAll = new Map<string, ResourceManifest[]>();
111
146
  const seen = new Set<string>();
112
147
  for (const r of resources) {
113
- if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || r.kind === "Telo.Import") continue;
148
+ if (!r.metadata?.name || SYSTEM_KINDS.has(r.kind) || r.kind === "Telo.Import" || isForeign(r))
149
+ continue;
114
150
  const name = r.metadata.name as string;
115
151
  // `analyze()` guarantees both fields are present on non-system manifests.
116
152
  const meta = r.metadata as unknown as { source: string; sourceLine: number };
@@ -148,6 +184,33 @@ export function validateReferences(
148
184
  });
149
185
  }
150
186
  }
187
+ // A resource name must contain no dot. The `!ref` resolver splits the tag's source on
188
+ // the first dot to separate an import alias from the resource name, so a dotted name
189
+ // would mis-resolve into a cross-module lookup. This is the load-bearing invariant of
190
+ // the reference grammar, so it is enforced here rather than left to the (unenforced)
191
+ // casing convention.
192
+ for (const [name, list] of byNameAll) {
193
+ if (!name.includes(".")) continue;
194
+ for (const r of list) {
195
+ const m = r.metadata as { source?: string; sourceLine?: number } | undefined;
196
+ const range =
197
+ typeof m?.sourceLine === "number"
198
+ ? {
199
+ start: { line: m.sourceLine, character: 0 },
200
+ end: { line: m.sourceLine, character: Number.MAX_SAFE_INTEGER },
201
+ }
202
+ : undefined;
203
+ diagnostics.push({
204
+ severity: DiagnosticSeverity.Error,
205
+ code: "INVALID_RESOURCE_NAME",
206
+ source: SOURCE,
207
+ message: `${r.kind}/${name}: resource name must not contain '.' — in a '!ref' the '.' separates an import alias from the resource name`,
208
+ ...(range ? { range } : {}),
209
+ data: { resource: { kind: r.kind, name }, filePath: m?.source, path: "metadata.name" },
210
+ });
211
+ }
212
+ }
213
+
151
214
  // Single-resource map for the resolution / scope lookups below — when a
152
215
  // collision exists, falling back to the first occurrence keeps the rest
153
216
  // of the pass behaving the same as before the duplicate diagnostic was
@@ -161,7 +224,7 @@ export function validateReferences(
161
224
  // enclosure (`inScope`) and the scope manifests visible to it — so this
162
225
  // handler only validates, it does not re-walk.
163
226
  visitManifest(
164
- resources,
227
+ localResources,
165
228
  registry,
166
229
  {
167
230
  onRef: (e) => {
@@ -178,14 +241,37 @@ export function validateReferences(
178
241
  // string/inline ambiguity at the source.
179
242
  if (isRefSentinel(val)) {
180
243
  const refName = val.source;
244
+ const dot = refName.indexOf(".");
245
+ const aliasPrefix = dot > 0 ? refName.slice(0, dot) : undefined;
246
+
247
+ // Cross-module sentinel left unresolved by Phase 2.5 — it qualifies an import
248
+ // alias. If that module's exports are loaded in this analysis, the miss is real
249
+ // (name not in exports.resources, or a typo); if not (partial single-file
250
+ // analysis), skip rather than emit a false UNRESOLVED_REFERENCE.
251
+ if (aliasPrefix && aliasPrefix !== "Self" && aliases.hasAlias(aliasPrefix)) {
252
+ const module = aliases.moduleForAlias(aliasPrefix);
253
+ if (module && !moduleLoaded(module)) return;
254
+ diagnostics.push({
255
+ severity: DiagnosticSeverity.Error,
256
+ code: "UNRESOLVED_REFERENCE",
257
+ source: SOURCE,
258
+ message: `${resourceLabel}: reference at '${concretePath}' → '${refName}' is not exported by module '${module ?? aliasPrefix}' (add it to exports.resources)`,
259
+ data: { resource: resourceData, filePath, path: concretePath },
260
+ });
261
+ return;
262
+ }
263
+
264
+ // Local reference (bare name or explicit `Self.`-qualified).
265
+ const localName = aliasPrefix === "Self" ? refName.slice(dot + 1) : refName;
181
266
  const target =
182
- byName.get(refName) ?? visibleScopeManifests.find((m) => m.metadata?.name === refName);
267
+ byName.get(localName) ??
268
+ visibleScopeManifests.find((m) => m.metadata?.name === localName);
183
269
  if (!target) {
184
270
  diagnostics.push({
185
271
  severity: DiagnosticSeverity.Error,
186
272
  code: "UNRESOLVED_REFERENCE",
187
273
  source: SOURCE,
188
- message: `${resourceLabel}: reference at '${concretePath}' → resource '${refName}' not found`,
274
+ message: `${resourceLabel}: reference at '${concretePath}' → resource '${localName}' not found`,
189
275
  data: { resource: resourceData, filePath, path: concretePath },
190
276
  });
191
277
  return;
@@ -280,9 +366,18 @@ export function validateReferences(
280
366
  }
281
367
 
282
368
  // 3. Resolution check — resource with this name must exist.
283
- const exists =
284
- byName.has(refVal.name) ||
285
- visibleScopeManifests.some((m) => m.metadata?.name === refVal.name);
369
+ let exists: boolean;
370
+ if (typeof refVal.alias === "string" && refVal.alias !== "Self") {
371
+ // Cross-module ref resolved by Phase 2.5. Validate against the forwarded
372
+ // exports when loaded; in partial context (module not loaded) assume resolvable.
373
+ const module = aliases.moduleForAlias(refVal.alias);
374
+ exists =
375
+ !module || !moduleLoaded(module) || byModuleName.has(`${module}\0${refVal.name}`);
376
+ } else {
377
+ exists =
378
+ byName.has(refVal.name) ||
379
+ visibleScopeManifests.some((m) => m.metadata?.name === refVal.name);
380
+ }
286
381
  if (!exists) {
287
382
  diagnostics.push({
288
383
  severity: DiagnosticSeverity.Error,
@@ -303,7 +398,7 @@ export function validateReferences(
303
398
  // validate the field value against the resulting sub-schema. Driven off the base map
304
399
  // (un-expanded) so each schema-from slot is seen as its own site.
305
400
  visitManifest(
306
- resources,
401
+ localResources,
307
402
  registry,
308
403
  {
309
404
  onSchemaFrom: (e) => {
@@ -1,10 +0,0 @@
1
- import { type ManifestAdapter } from "../types.js";
2
- export declare class HttpAdapter implements ManifestAdapter {
3
- supports(url: string): boolean;
4
- read(url: string): Promise<{
5
- text: string;
6
- source: string;
7
- }>;
8
- resolveRelative(base: string, relative: string): string;
9
- }
10
- //# sourceMappingURL=http-adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/http-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9E,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAWlE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;CAIxD"}
@@ -1,18 +0,0 @@
1
- import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
2
- export class HttpAdapter {
3
- supports(url) {
4
- return url.startsWith("http://") || url.startsWith("https://");
5
- }
6
- async read(url) {
7
- const fetchUrl = url.includes(".yaml") ? url : `${url}/${DEFAULT_MANIFEST_FILENAME}`;
8
- const response = await fetch(fetchUrl);
9
- if (!response.ok) {
10
- throw new Error(`Failed to fetch manifest from ${fetchUrl}: ${response.status} ${response.statusText}`);
11
- }
12
- return { text: await response.text(), source: fetchUrl };
13
- }
14
- resolveRelative(base, relative) {
15
- const baseDir = base.endsWith("/") ? base : base.slice(0, base.lastIndexOf("/") + 1);
16
- return new URL(relative, baseDir).href;
17
- }
18
- }
@@ -1,17 +0,0 @@
1
- import { type ManifestAdapter } from "../types.js";
2
- /** Node.js fs-based ManifestAdapter for local files. Not browser-compatible. */
3
- export declare class NodeAdapter implements ManifestAdapter {
4
- private readonly cwd;
5
- constructor(cwd?: string);
6
- supports(url: string): boolean;
7
- read(url: string): Promise<{
8
- text: string;
9
- source: string;
10
- }>;
11
- resolveRelative(base: string, relative: string): string;
12
- expandGlob(base: string, patterns: string[]): Promise<string[]>;
13
- resolveOwnerOf(fileUrl: string): Promise<string | null>;
14
- }
15
- /** @deprecated Use `new NodeAdapter(cwd)` instead */
16
- export declare function createNodeAdapter(cwd?: string): ManifestAdapter;
17
- //# sourceMappingURL=node-adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"node-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/node-adapter.ts"],"names":[],"mappings":"AAIA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAM9E,gFAAgF;AAChF,qBAAa,WAAY,YAAW,eAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,GAAE,MAAsB;IAExD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAUxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IASlE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAKjD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAc/D,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAoB9D;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAsB,GAAG,eAAe,CAE9E"}
@@ -1,71 +0,0 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
3
- import { fileURLToPath } from "url";
4
- import { minimatch } from "minimatch";
5
- import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
6
- function toFilePath(url) {
7
- return url.startsWith("file://") ? fileURLToPath(url) : url;
8
- }
9
- /** Node.js fs-based ManifestAdapter for local files. Not browser-compatible. */
10
- export class NodeAdapter {
11
- cwd;
12
- constructor(cwd = process.cwd()) {
13
- this.cwd = cwd;
14
- }
15
- supports(url) {
16
- return (url.startsWith("file://") ||
17
- url.startsWith("/") ||
18
- url.startsWith("./") ||
19
- url.startsWith("../") ||
20
- (!url.includes("://") && !url.includes("@")));
21
- }
22
- async read(url) {
23
- const filePath = toFilePath(url);
24
- const stat = await fs.stat(filePath).catch(() => null);
25
- const resolvedPath = stat?.isDirectory() ? path.join(filePath, DEFAULT_MANIFEST_FILENAME) : filePath;
26
- const text = await fs.readFile(resolvedPath, "utf8");
27
- return { text, source: resolvedPath };
28
- }
29
- resolveRelative(base, relative) {
30
- const baseDir = path.dirname(path.resolve(this.cwd, toFilePath(base)));
31
- return path.resolve(baseDir, relative);
32
- }
33
- async expandGlob(base, patterns) {
34
- const baseDir = path.dirname(path.resolve(this.cwd, toFilePath(base)));
35
- const entries = await fs.readdir(baseDir, { recursive: true, encoding: "utf8" });
36
- const normalizedPatterns = patterns.map((p) => p.replace(/\\/g, "/").replace(/^\.\//, ""));
37
- const matched = [];
38
- for (const entry of entries) {
39
- const normalized = entry.replace(/\\/g, "/");
40
- if (normalizedPatterns.some((p) => minimatch(normalized, p))) {
41
- matched.push(path.resolve(baseDir, entry));
42
- }
43
- }
44
- return matched.sort();
45
- }
46
- async resolveOwnerOf(fileUrl) {
47
- const resolved = path.resolve(this.cwd, toFilePath(fileUrl));
48
- let dir = path.dirname(resolved);
49
- while (true) {
50
- const candidate = path.join(dir, DEFAULT_MANIFEST_FILENAME);
51
- if (candidate !== resolved) {
52
- try {
53
- await fs.access(candidate);
54
- return candidate;
55
- }
56
- catch {
57
- // telo.yaml not found at this level
58
- }
59
- }
60
- const parent = path.dirname(dir);
61
- if (parent === dir)
62
- break;
63
- dir = parent;
64
- }
65
- return null;
66
- }
67
- }
68
- /** @deprecated Use `new NodeAdapter(cwd)` instead */
69
- export function createNodeAdapter(cwd = process.cwd()) {
70
- return new NodeAdapter(cwd);
71
- }
@@ -1,15 +0,0 @@
1
- import { type ManifestAdapter } from "../types.js";
2
- export declare class RegistryAdapter implements ManifestAdapter {
3
- private registryUrl;
4
- constructor(registryUrl?: string);
5
- supports(url: string): boolean;
6
- read(moduleRef: string): Promise<{
7
- text: string;
8
- source: string;
9
- }>;
10
- resolveRelative(base: string, relative: string): string;
11
- private toRegistryModuleBase;
12
- private toRegistryUrl;
13
- private parseModuleRef;
14
- }
15
- //# sourceMappingURL=registry-adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/registry-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAI9E,qBAAa,eAAgB,YAAW,eAAe;IACzC,OAAO,CAAC,WAAW;gBAAX,WAAW,SAAuB;IAEtD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAWxB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAWxE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAMvD,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,cAAc;CAmBvB"}
@@ -1,53 +0,0 @@
1
- import { DEFAULT_MANIFEST_FILENAME } from "../types.js";
2
- const DEFAULT_REGISTRY_URL = "https://registry.telo.run";
3
- export class RegistryAdapter {
4
- registryUrl;
5
- constructor(registryUrl = DEFAULT_REGISTRY_URL) {
6
- this.registryUrl = registryUrl;
7
- }
8
- supports(url) {
9
- return (!url.startsWith("http://") &&
10
- !url.startsWith("https://") &&
11
- !url.startsWith("/") &&
12
- !url.startsWith(".") &&
13
- url.includes("@") &&
14
- url.includes("/"));
15
- }
16
- async read(moduleRef) {
17
- const fetchUrl = this.toRegistryUrl(moduleRef);
18
- const response = await fetch(fetchUrl);
19
- if (!response.ok) {
20
- throw new Error(`Failed to fetch manifest ${moduleRef}: ${response.status} ${response.statusText}`);
21
- }
22
- return { text: await response.text(), source: fetchUrl };
23
- }
24
- resolveRelative(base, relative) {
25
- const baseUrl = this.supports(base) ? this.toRegistryModuleBase(base) : base;
26
- const baseWithSlash = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
27
- return new URL(relative, baseWithSlash).href;
28
- }
29
- toRegistryModuleBase(moduleRef) {
30
- const parsed = this.parseModuleRef(moduleRef);
31
- const normalizedBase = this.registryUrl.replace(/\/+$/, "");
32
- return `${normalizedBase}/${parsed.modulePath}/${parsed.version}`;
33
- }
34
- toRegistryUrl(moduleRef) {
35
- return `${this.toRegistryModuleBase(moduleRef)}/${DEFAULT_MANIFEST_FILENAME}`;
36
- }
37
- parseModuleRef(moduleRef) {
38
- const atIdx = moduleRef.lastIndexOf("@");
39
- if (atIdx <= 0 || atIdx === moduleRef.length - 1) {
40
- throw new Error(`Invalid module reference '${moduleRef}', expected namespace/name@version`);
41
- }
42
- const modulePath = moduleRef.slice(0, atIdx);
43
- if (!modulePath.includes("/")) {
44
- throw new Error(`Invalid module reference '${moduleRef}', expected namespace/name@version`);
45
- }
46
- const rawVersion = moduleRef.slice(atIdx + 1);
47
- const version = rawVersion.startsWith("v") ? rawVersion.substring(1) : rawVersion;
48
- if (!version) {
49
- throw new Error(`Invalid module reference '${moduleRef}', expected namespace/name@version`);
50
- }
51
- return { modulePath, version };
52
- }
53
- }