@telorun/analyzer 0.12.0 → 0.12.1
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/README.md +2 -2
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +67 -1
- package/dist/definition-registry.d.ts +12 -1
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +20 -1
- package/dist/reference-field-map.d.ts +1 -0
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +1 -1
- package/dist/schema-compat.d.ts +4 -0
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +6 -2
- package/dist/validate-nested-inline.d.ts +30 -0
- package/dist/validate-nested-inline.d.ts.map +1 -0
- package/dist/validate-nested-inline.js +129 -0
- package/package.json +1 -1
- package/src/analyzer.ts +72 -1
- package/src/definition-registry.ts +20 -1
- package/src/reference-field-map.ts +1 -1
- package/src/schema-compat.ts +6 -2
- package/src/validate-nested-inline.ts +158 -0
- package/dist/adapters/http-adapter.d.ts +0 -10
- package/dist/adapters/http-adapter.d.ts.map +0 -1
- package/dist/adapters/http-adapter.js +0 -18
- package/dist/adapters/node-adapter.d.ts +0 -17
- package/dist/adapters/node-adapter.d.ts.map +0 -1
- package/dist/adapters/node-adapter.js +0 -71
- package/dist/adapters/registry-adapter.d.ts +0 -15
- package/dist/adapters/registry-adapter.d.ts.map +0 -1
- package/dist/adapters/registry-adapter.js +0 -53
package/README.md
CHANGED
|
@@ -61,12 +61,12 @@ targets:
|
|
|
61
61
|
kind: Telo.Import
|
|
62
62
|
metadata:
|
|
63
63
|
name: Http
|
|
64
|
-
source: std/http-server@0.
|
|
64
|
+
source: std/http-server@0.5.0
|
|
65
65
|
---
|
|
66
66
|
kind: Telo.Import
|
|
67
67
|
metadata:
|
|
68
68
|
name: Sql
|
|
69
|
-
source: std/sql@0.
|
|
69
|
+
source: std/sql@0.3.0
|
|
70
70
|
---
|
|
71
71
|
# SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
|
|
72
72
|
kind: Sql.Connection
|
package/dist/analyzer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAgB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAgB9B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAif/F,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;gBAEzB,OAAO,GAAE,qBAA0B;IAI/C;;;;;;;;;;;;;;OAcG;IACH,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAoiBvB,aAAa,CACX,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,kBAAkB,EAAE;IAMvB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,EAAE;IAexF,OAAO,CACL,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,gBAAgB,GACzB;QAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAsB5F"}
|
package/dist/analyzer.js
CHANGED
|
@@ -14,6 +14,7 @@ import { celTypeSatisfiesJsonSchema, substituteCelFields, validateAgainstSchema,
|
|
|
14
14
|
import { DiagnosticSeverity } from "./types.js";
|
|
15
15
|
import { getManifestItem, pathMatchesScope, resolveContextAnnotations, resolveTypeFieldToSchema, } from "./validate-cel-context.js";
|
|
16
16
|
import { validateExtends } from "./validate-extends.js";
|
|
17
|
+
import { validateNestedInlineResources } from "./validate-nested-inline.js";
|
|
17
18
|
import { validateProviderCoherence } from "./validate-provider-coherence.js";
|
|
18
19
|
import { validateReferences } from "./validate-references.js";
|
|
19
20
|
import { validateThrowsCoverage } from "./validate-throws-coverage.js";
|
|
@@ -563,6 +564,34 @@ export class StaticAnalyzer {
|
|
|
563
564
|
byName.set(m.metadata.name, m);
|
|
564
565
|
}
|
|
565
566
|
}
|
|
567
|
+
// Fail loud on definition schemas AJV cannot compile. `validateAgainstSchema`
|
|
568
|
+
// and `validateWithRefs` swallow compile failures (returning no issues),
|
|
569
|
+
// which would silently skip schema validation for every resource of that
|
|
570
|
+
// kind — surface the broken schema once, anchored on the definition itself.
|
|
571
|
+
for (const m of allManifests) {
|
|
572
|
+
if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract")
|
|
573
|
+
continue;
|
|
574
|
+
const schema = m.schema;
|
|
575
|
+
if (!schema || typeof schema !== "object")
|
|
576
|
+
continue;
|
|
577
|
+
const name = m.metadata?.name;
|
|
578
|
+
if (!name)
|
|
579
|
+
continue;
|
|
580
|
+
const compileError = defs.schemaCompileError(schema);
|
|
581
|
+
if (compileError) {
|
|
582
|
+
diagnostics.push({
|
|
583
|
+
severity: DiagnosticSeverity.Error,
|
|
584
|
+
code: "SCHEMA_COMPILE_ERROR",
|
|
585
|
+
source: SOURCE,
|
|
586
|
+
message: `${m.kind}/${name}: definition schema failed to compile: ${compileError}`,
|
|
587
|
+
data: {
|
|
588
|
+
resource: { kind: m.kind, name },
|
|
589
|
+
filePath: m.metadata?.source,
|
|
590
|
+
path: "schema",
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
566
595
|
// Library env: rejection — `env:` on a Library `variables` / `secrets`
|
|
567
596
|
// entry is forbidden. The Library entry schema is otherwise open so that
|
|
568
597
|
// any JSON Schema property schema is valid; this targeted check produces
|
|
@@ -664,6 +693,33 @@ export class StaticAnalyzer {
|
|
|
664
693
|
});
|
|
665
694
|
}
|
|
666
695
|
}
|
|
696
|
+
// Validate inline resources nested inside this resource's body (e.g. a
|
|
697
|
+
// Run.Sequence step's `invoke: { kind, ...config }`). These sit at
|
|
698
|
+
// x-telo-ref slots reached only through local `$ref`s, which the
|
|
699
|
+
// reference field map intentionally does not follow, so they escape both
|
|
700
|
+
// inline-extraction and the per-resource schema check above.
|
|
701
|
+
if (definition.schema) {
|
|
702
|
+
// Resolve inline kinds in the parent resource's scope: direct kind
|
|
703
|
+
// first, then the parent module's own aliases (for resources declared
|
|
704
|
+
// inside an imported module), then the root aliases. Mirrors how the
|
|
705
|
+
// analyzer resolves kinds elsewhere so module-scoped aliases don't
|
|
706
|
+
// produce false UNDEFINED_KIND diagnostics.
|
|
707
|
+
const ownModule = m.metadata?.module;
|
|
708
|
+
const scopeResolver = ownModule && !rootModules.has(ownModule) ? aliasesByModule.get(ownModule) : undefined;
|
|
709
|
+
diagnostics.push(...validateNestedInlineResources(m, definition.schema, (kind) => {
|
|
710
|
+
const direct = defs.resolve(kind);
|
|
711
|
+
if (direct)
|
|
712
|
+
return direct;
|
|
713
|
+
const viaScope = scopeResolver?.resolveKind(kind);
|
|
714
|
+
if (viaScope) {
|
|
715
|
+
const scoped = defs.resolve(viaScope);
|
|
716
|
+
if (scoped)
|
|
717
|
+
return scoped;
|
|
718
|
+
}
|
|
719
|
+
const viaRoot = aliases.resolveKind(kind);
|
|
720
|
+
return viaRoot ? defs.resolve(viaRoot) : undefined;
|
|
721
|
+
}));
|
|
722
|
+
}
|
|
667
723
|
// (Invocation context compatibility check is handled via x-telo-context in the CEL pass below)
|
|
668
724
|
}
|
|
669
725
|
// Template-body structural validations: check that template entry-points produce
|
|
@@ -789,8 +845,18 @@ export class StaticAnalyzer {
|
|
|
789
845
|
effectiveContext = mergeKernelGlobalsIntoContext(resolvedContext, kernelGlobals);
|
|
790
846
|
}
|
|
791
847
|
const engine = defaultRegistry().get(engineName);
|
|
792
|
-
if (!engine)
|
|
848
|
+
if (!engine) {
|
|
849
|
+
// No registered engine owns this tag — the expression would go
|
|
850
|
+
// entirely unanalyzed. Surface it rather than skipping silently.
|
|
851
|
+
diagnostics.push({
|
|
852
|
+
severity: DiagnosticSeverity.Error,
|
|
853
|
+
code: "UNKNOWN_ENGINE",
|
|
854
|
+
source: SOURCE,
|
|
855
|
+
message: `${m.kind}/${resource.name}: no templating engine registered for '!${engineName}' at '${path}'.`,
|
|
856
|
+
data: { resource, filePath, path },
|
|
857
|
+
});
|
|
793
858
|
return;
|
|
859
|
+
}
|
|
794
860
|
const findings = engine.analyze(expr, { celEnv: this.celEnv, contextSchema: effectiveContext });
|
|
795
861
|
for (const f of findings) {
|
|
796
862
|
if (f.code === "CEL_SYNTAX_ERROR") {
|
|
@@ -31,8 +31,19 @@ export declare class DefinitionRegistry {
|
|
|
31
31
|
* Returns undefined when the module identity is not yet registered. */
|
|
32
32
|
computeId(moduleName: string, typeName: string): string | undefined;
|
|
33
33
|
/** Validates data against a schema using this registry's AJV instance, which has all
|
|
34
|
-
* registered definition schemas loaded — enabling cross-module $ref resolution.
|
|
34
|
+
* registered definition schemas loaded — enabling cross-module $ref resolution.
|
|
35
|
+
* A compile failure returns `[]` here; it is surfaced loudly (once, on the
|
|
36
|
+
* owning definition) by `schemaCompileError` via the analyzer's
|
|
37
|
+
* definition-schema compile check, so resources are never silently skipped. */
|
|
35
38
|
validateWithRefs(data: unknown, schema: Record<string, any>): string[];
|
|
39
|
+
/** Returns the AJV compile error for `schema`, or `undefined` when it compiles.
|
|
40
|
+
* Compiles on this registry's instance, which has every loaded module schema
|
|
41
|
+
* plus the manifest root registered, so local `#/$defs`, `telo://manifest`,
|
|
42
|
+
* and cross-module `$ref`s all resolve. Used to fail loud on a definition
|
|
43
|
+
* schema that AJV cannot compile — otherwise `validateAgainstSchema` /
|
|
44
|
+
* `validateWithRefs` would swallow the failure and silently skip every
|
|
45
|
+
* resource of that kind. */
|
|
46
|
+
schemaCompileError(schema: Record<string, any>): string | undefined;
|
|
36
47
|
private tryRegisterSchema;
|
|
37
48
|
/** Resolves an x-telo-ref string to a canonical registry kind key.
|
|
38
49
|
* Splits on "#", looks up the left side in the identity table, and returns
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;6DACyD;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAiC9C,OAAO,CAAC,aAAa;IASrB;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+B1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;
|
|
1
|
+
{"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAGlC,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;6DACyD;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAiC9C,OAAO,CAAC,aAAa;IASrB;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA+B1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;;;;oFAIgF;IAChF,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE;IAWtE;;;;;;iCAM6B;IAC7B,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,SAAS;IASnE,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;4FAQwF;IACxF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIrD,+FAA+F;IAC/F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIxD,gGAAgG;IAChG,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS;IAOhC;;;;;;;;qFAQiF;IACjF,2BAA2B,CACzB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,aAAa,EACtB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,GAC1C,iBAAiB,GAAG,SAAS;IAuBhC,OAAO,CAAC,uBAAuB;IA+B/B;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
|
|
@@ -105,7 +105,10 @@ export class DefinitionRegistry {
|
|
|
105
105
|
return `${identity}/${typeName}`;
|
|
106
106
|
}
|
|
107
107
|
/** Validates data against a schema using this registry's AJV instance, which has all
|
|
108
|
-
* registered definition schemas loaded — enabling cross-module $ref resolution.
|
|
108
|
+
* registered definition schemas loaded — enabling cross-module $ref resolution.
|
|
109
|
+
* A compile failure returns `[]` here; it is surfaced loudly (once, on the
|
|
110
|
+
* owning definition) by `schemaCompileError` via the analyzer's
|
|
111
|
+
* definition-schema compile check, so resources are never silently skipped. */
|
|
109
112
|
validateWithRefs(data, schema) {
|
|
110
113
|
let validate;
|
|
111
114
|
try {
|
|
@@ -118,6 +121,22 @@ export class DefinitionRegistry {
|
|
|
118
121
|
return [];
|
|
119
122
|
return (validate.errors ?? []).map(formatSingleError);
|
|
120
123
|
}
|
|
124
|
+
/** Returns the AJV compile error for `schema`, or `undefined` when it compiles.
|
|
125
|
+
* Compiles on this registry's instance, which has every loaded module schema
|
|
126
|
+
* plus the manifest root registered, so local `#/$defs`, `telo://manifest`,
|
|
127
|
+
* and cross-module `$ref`s all resolve. Used to fail loud on a definition
|
|
128
|
+
* schema that AJV cannot compile — otherwise `validateAgainstSchema` /
|
|
129
|
+
* `validateWithRefs` would swallow the failure and silently skip every
|
|
130
|
+
* resource of that kind. */
|
|
131
|
+
schemaCompileError(schema) {
|
|
132
|
+
try {
|
|
133
|
+
this.ajv.compile(schema);
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
return err instanceof Error ? err.message : String(err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
121
140
|
tryRegisterSchema(moduleName, typeName, schema) {
|
|
122
141
|
const id = this.computeId(moduleName, typeName);
|
|
123
142
|
if (!id || this.registeredSchemaIds.has(id))
|
|
@@ -78,6 +78,7 @@ export declare function resolveFieldValues(obj: unknown, path: string): unknown[
|
|
|
78
78
|
* - A node with `properties` → recurse into each property
|
|
79
79
|
*/
|
|
80
80
|
export declare function buildReferenceFieldMap(schema: Record<string, any>): ReferenceFieldMap;
|
|
81
|
+
export declare function collectRefs(node: Record<string, any>): string[];
|
|
81
82
|
/** Traverses an arbitrary JSON Schema starting at the given path prefix. Used to
|
|
82
83
|
* expand x-telo-schema-from sub-schemas into nested ref/scope entries so Phase 2
|
|
83
84
|
* inline normalization and Phase 5 injection see slots that the local field map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;;;;;;;;2EAS2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAED;;;;sEAIsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;wDAUwD;AACxD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAoCpF;AAED;+DAC+D;AAC/D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAExE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF;
|
|
1
|
+
{"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;;;;;;;;2EAS2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAItE;AAED;;;;sEAIsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;wDAUwD;AACxD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAoCpF;AAED;+DAC+D;AAC/D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAExE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CAa/D;AAED;;;8CAG8C;AAC9C,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,EAAE,MAAM,GACjB,iBAAiB,CAInB"}
|
|
@@ -103,7 +103,7 @@ export function buildReferenceFieldMap(schema) {
|
|
|
103
103
|
}
|
|
104
104
|
return map;
|
|
105
105
|
}
|
|
106
|
-
function collectRefs(node) {
|
|
106
|
+
export function collectRefs(node) {
|
|
107
107
|
const refs = [];
|
|
108
108
|
if (typeof node["x-telo-ref"] === "string") {
|
|
109
109
|
refs.push(node["x-telo-ref"]);
|
package/dist/schema-compat.d.ts
CHANGED
|
@@ -41,6 +41,10 @@ export declare function jsonSchemaToCelType(schema: Record<string, any> | undefi
|
|
|
41
41
|
export declare function celTypeSatisfiesJsonSchema(celType: string, schema: Record<string, any>): boolean;
|
|
42
42
|
/** Return a literal placeholder value of the correct schema type for AJV. */
|
|
43
43
|
export declare function celPlaceholderForSchema(schema: Record<string, any>): unknown;
|
|
44
|
+
/** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
|
|
45
|
+
export declare function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any>;
|
|
46
|
+
/** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
|
|
47
|
+
export declare function collectProperties(schema: Record<string, any>): Record<string, any>;
|
|
44
48
|
/** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
|
|
45
49
|
* schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
|
|
46
50
|
export declare function substituteCelFields(data: unknown, schema: Record<string, any>, rootSchema?: Record<string, any>): unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;;;;;;;mCAOmC;AACnC,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAOpD;AAKD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAelD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAmB/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAuBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAqBhG;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAiB5E;AAID,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAOtG;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAUlF;AAED;iGACiG;AACjG,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,OAAO,CAqCT"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -109,6 +109,10 @@ export function validateAgainstSchema(data, schema) {
|
|
|
109
109
|
compiledSchemaValidators.set(schema, validate);
|
|
110
110
|
}
|
|
111
111
|
catch {
|
|
112
|
+
// A schema that won't compile is reported loudly (once, on the owning
|
|
113
|
+
// definition) by the analyzer's definition-schema compile check
|
|
114
|
+
// (`DefinitionRegistry.schemaCompileError`), so returning `[]` here does
|
|
115
|
+
// not silently accept resources of that kind.
|
|
112
116
|
return [];
|
|
113
117
|
}
|
|
114
118
|
}
|
|
@@ -247,7 +251,7 @@ export function celPlaceholderForSchema(schema) {
|
|
|
247
251
|
}
|
|
248
252
|
const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
|
|
249
253
|
/** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
|
|
250
|
-
function resolveRef(schema, root) {
|
|
254
|
+
export function resolveRef(schema, root) {
|
|
251
255
|
if (schema.$ref && typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
|
|
252
256
|
const defName = schema.$ref.slice("#/$defs/".length);
|
|
253
257
|
const resolved = root.$defs?.[defName];
|
|
@@ -257,7 +261,7 @@ function resolveRef(schema, root) {
|
|
|
257
261
|
return schema;
|
|
258
262
|
}
|
|
259
263
|
/** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
|
|
260
|
-
function collectProperties(schema) {
|
|
264
|
+
export function collectProperties(schema) {
|
|
261
265
|
const props = { ...(schema.properties ?? {}) };
|
|
262
266
|
for (const sub of schema.oneOf ?? schema.anyOf ?? []) {
|
|
263
267
|
if (sub && typeof sub === "object" && sub.properties) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import { type AnalysisDiagnostic } from "./types.js";
|
|
3
|
+
/** Minimal view of a definition needed to validate an inline resource's config. */
|
|
4
|
+
export interface InlineDefinitionLookup {
|
|
5
|
+
(kind: string): {
|
|
6
|
+
schema?: Record<string, any>;
|
|
7
|
+
} | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Validates inline resources nested inside a resource body against their kind's
|
|
11
|
+
* config schema. The per-resource walk in `analyze()` validates a resource's
|
|
12
|
+
* own top-level config; inline resources at `x-telo-ref` slots reachable only
|
|
13
|
+
* through a local `$ref` (notably `Run.Sequence`'s `steps[].invoke`, hidden
|
|
14
|
+
* behind `#/$defs/step`) never reach the reference field map, so they would
|
|
15
|
+
* otherwise escape schema validation — e.g. `invoke: { kind: Console.ReadLine,
|
|
16
|
+
* prompt: "…" }`, where `prompt` belongs in the step's `inputs`, not the config.
|
|
17
|
+
*
|
|
18
|
+
* Walks the manifest data together with its definition schema, resolving local
|
|
19
|
+
* `$ref`s (so step trees of arbitrary depth are covered). At each `x-telo-ref`
|
|
20
|
+
* slot holding an inline resource, the inline's config is validated against its
|
|
21
|
+
* own kind's schema, then recursed into so inline resources nested inside
|
|
22
|
+
* inline resources are covered.
|
|
23
|
+
*
|
|
24
|
+
* Non-mutating: reads `manifest` and emits diagnostics anchored to its identity
|
|
25
|
+
* and a concrete dotted path matching the position-index key format;
|
|
26
|
+
* `rewriteSyntheticOrigins` reroutes those on inline-extracted (synthetic)
|
|
27
|
+
* manifests back to the root doc.
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateNestedInlineResources(manifest: ResourceManifest, rootSchema: Record<string, any>, lookupDefinition: InlineDefinitionLookup): AnalysisDiagnostic[];
|
|
30
|
+
//# sourceMappingURL=validate-nested-inline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-nested-inline.d.ts","sourceRoot":"","sources":["../src/validate-nested-inline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQrD,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,GAAG,SAAS,CAAC;CAC9D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC/B,gBAAgB,EAAE,sBAAsB,GACvC,kBAAkB,EAAE,CAoHtB"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { collectRefs, isInlineResource } from "./reference-field-map.js";
|
|
2
|
+
import { collectProperties, resolveRef, substituteCelFields, validateAgainstSchema, } from "./schema-compat.js";
|
|
3
|
+
import { DiagnosticSeverity } from "./types.js";
|
|
4
|
+
const SOURCE = "telo-analyzer";
|
|
5
|
+
/**
|
|
6
|
+
* Validates inline resources nested inside a resource body against their kind's
|
|
7
|
+
* config schema. The per-resource walk in `analyze()` validates a resource's
|
|
8
|
+
* own top-level config; inline resources at `x-telo-ref` slots reachable only
|
|
9
|
+
* through a local `$ref` (notably `Run.Sequence`'s `steps[].invoke`, hidden
|
|
10
|
+
* behind `#/$defs/step`) never reach the reference field map, so they would
|
|
11
|
+
* otherwise escape schema validation — e.g. `invoke: { kind: Console.ReadLine,
|
|
12
|
+
* prompt: "…" }`, where `prompt` belongs in the step's `inputs`, not the config.
|
|
13
|
+
*
|
|
14
|
+
* Walks the manifest data together with its definition schema, resolving local
|
|
15
|
+
* `$ref`s (so step trees of arbitrary depth are covered). At each `x-telo-ref`
|
|
16
|
+
* slot holding an inline resource, the inline's config is validated against its
|
|
17
|
+
* own kind's schema, then recursed into so inline resources nested inside
|
|
18
|
+
* inline resources are covered.
|
|
19
|
+
*
|
|
20
|
+
* Non-mutating: reads `manifest` and emits diagnostics anchored to its identity
|
|
21
|
+
* and a concrete dotted path matching the position-index key format;
|
|
22
|
+
* `rewriteSyntheticOrigins` reroutes those on inline-extracted (synthetic)
|
|
23
|
+
* manifests back to the root doc.
|
|
24
|
+
*/
|
|
25
|
+
export function validateNestedInlineResources(manifest, rootSchema, lookupDefinition) {
|
|
26
|
+
const diagnostics = [];
|
|
27
|
+
const resource = { kind: manifest.kind, name: manifest.metadata?.name };
|
|
28
|
+
const filePath = manifest.metadata?.source;
|
|
29
|
+
function validateInline(inline, path) {
|
|
30
|
+
const kind = inline.kind;
|
|
31
|
+
const def = lookupDefinition(kind);
|
|
32
|
+
// Unknown kind: these `$ref`-hidden slots are invisible to the field-map
|
|
33
|
+
// driven reference checks too, so nothing else would flag it — report here.
|
|
34
|
+
if (!def) {
|
|
35
|
+
diagnostics.push({
|
|
36
|
+
severity: DiagnosticSeverity.Error,
|
|
37
|
+
code: "UNDEFINED_KIND",
|
|
38
|
+
source: SOURCE,
|
|
39
|
+
message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': No Telo.Definition found for kind '${kind}'.`,
|
|
40
|
+
data: { resource, filePath, path: `${path}.kind` },
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Kind exists but declares no config schema (e.g. a pure Telo.Type): no
|
|
45
|
+
// config to validate and no schema-declared slots to nest resources in.
|
|
46
|
+
if (!def.schema)
|
|
47
|
+
return;
|
|
48
|
+
const schema = def.schema;
|
|
49
|
+
// `kind` / `metadata` are implicit on every resource; inject them so a
|
|
50
|
+
// strict `additionalProperties: false` config schema doesn't reject them.
|
|
51
|
+
const effectiveSchema = schema.additionalProperties === false
|
|
52
|
+
? {
|
|
53
|
+
...schema,
|
|
54
|
+
properties: {
|
|
55
|
+
kind: { type: "string" },
|
|
56
|
+
metadata: { type: "object" },
|
|
57
|
+
...schema.properties,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
: schema;
|
|
61
|
+
// Inline resources omit `metadata` — it is synthesized when the kernel
|
|
62
|
+
// registers them (and by `normalizeInlineResources` for extracted slots,
|
|
63
|
+
// which assigns a derived `metadata.name`). Config schemas conventionally
|
|
64
|
+
// declare `required: ["metadata", …]` with `metadata.name` required, so add
|
|
65
|
+
// a placeholder before validating to mirror the post-registration shape.
|
|
66
|
+
const existingMeta = inline.metadata && typeof inline.metadata === "object"
|
|
67
|
+
? inline.metadata
|
|
68
|
+
: {};
|
|
69
|
+
const data = { ...inline, metadata: { name: "__inline__", ...existingMeta } };
|
|
70
|
+
const substituted = substituteCelFields(data, effectiveSchema, effectiveSchema);
|
|
71
|
+
for (const issue of validateAgainstSchema(substituted, effectiveSchema)) {
|
|
72
|
+
diagnostics.push({
|
|
73
|
+
severity: DiagnosticSeverity.Error,
|
|
74
|
+
code: "SCHEMA_VIOLATION",
|
|
75
|
+
source: SOURCE,
|
|
76
|
+
message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': ${issue.message}`,
|
|
77
|
+
data: { resource, filePath, path: issue.path ? `${path}.${issue.path}` : path },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Recurse into the inline body against its own schema so deeper inline
|
|
81
|
+
// resources (e.g. an inline Run.Sequence's own steps) are validated too.
|
|
82
|
+
walk(inline, schema, schema, path);
|
|
83
|
+
}
|
|
84
|
+
function walk(data, schema, schemaRoot, path) {
|
|
85
|
+
if (!schema || typeof schema !== "object")
|
|
86
|
+
return;
|
|
87
|
+
const resolved = resolveRef(schema, schemaRoot);
|
|
88
|
+
// Reference slot: the value is either a named reference (`{kind, name}`,
|
|
89
|
+
// validated as its own manifest) or an inline resource to validate here.
|
|
90
|
+
if (collectRefs(resolved).length > 0) {
|
|
91
|
+
if (data &&
|
|
92
|
+
typeof data === "object" &&
|
|
93
|
+
!Array.isArray(data) &&
|
|
94
|
+
isInlineResource(data)) {
|
|
95
|
+
validateInline(data, path);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(data)) {
|
|
100
|
+
const itemSchema = resolved.items;
|
|
101
|
+
if (!itemSchema)
|
|
102
|
+
return;
|
|
103
|
+
for (let i = 0; i < data.length; i++) {
|
|
104
|
+
walk(data[i], itemSchema, schemaRoot, `${path}[${i}]`);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (data && typeof data === "object") {
|
|
109
|
+
const props = collectProperties(resolved);
|
|
110
|
+
const additional = resolved.additionalProperties &&
|
|
111
|
+
typeof resolved.additionalProperties === "object" &&
|
|
112
|
+
!Array.isArray(resolved.additionalProperties)
|
|
113
|
+
? resolved.additionalProperties
|
|
114
|
+
: undefined;
|
|
115
|
+
// Descend only where the schema declares structure. Freeform fields
|
|
116
|
+
// (`additionalProperties: true`, e.g. step `inputs`) carry caller data
|
|
117
|
+
// that may coincidentally look like `{kind: …}`; not descending there
|
|
118
|
+
// keeps the inline-resource detection anchored to real ref slots.
|
|
119
|
+
for (const [key, value] of Object.entries(data)) {
|
|
120
|
+
const propSchema = props[key] ?? additional;
|
|
121
|
+
if (!propSchema)
|
|
122
|
+
continue;
|
|
123
|
+
walk(value, propSchema, schemaRoot, path ? `${path}.${key}` : key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
walk(manifest, rootSchema, rootSchema, "");
|
|
128
|
+
return diagnostics;
|
|
129
|
+
}
|
package/package.json
CHANGED
package/src/analyzer.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
resolveTypeFieldToSchema,
|
|
32
32
|
} from "./validate-cel-context.js";
|
|
33
33
|
import { validateExtends } from "./validate-extends.js";
|
|
34
|
+
import { validateNestedInlineResources } from "./validate-nested-inline.js";
|
|
34
35
|
import { validateProviderCoherence } from "./validate-provider-coherence.js";
|
|
35
36
|
import { validateReferences } from "./validate-references.js";
|
|
36
37
|
import { validateThrowsCoverage } from "./validate-throws-coverage.js";
|
|
@@ -698,6 +699,32 @@ export class StaticAnalyzer {
|
|
|
698
699
|
}
|
|
699
700
|
}
|
|
700
701
|
|
|
702
|
+
// Fail loud on definition schemas AJV cannot compile. `validateAgainstSchema`
|
|
703
|
+
// and `validateWithRefs` swallow compile failures (returning no issues),
|
|
704
|
+
// which would silently skip schema validation for every resource of that
|
|
705
|
+
// kind — surface the broken schema once, anchored on the definition itself.
|
|
706
|
+
for (const m of allManifests) {
|
|
707
|
+
if (m.kind !== "Telo.Definition" && m.kind !== "Telo.Abstract") continue;
|
|
708
|
+
const schema = (m as Record<string, any>).schema;
|
|
709
|
+
if (!schema || typeof schema !== "object") continue;
|
|
710
|
+
const name = m.metadata?.name as string | undefined;
|
|
711
|
+
if (!name) continue;
|
|
712
|
+
const compileError = defs.schemaCompileError(schema as Record<string, any>);
|
|
713
|
+
if (compileError) {
|
|
714
|
+
diagnostics.push({
|
|
715
|
+
severity: DiagnosticSeverity.Error,
|
|
716
|
+
code: "SCHEMA_COMPILE_ERROR",
|
|
717
|
+
source: SOURCE,
|
|
718
|
+
message: `${m.kind}/${name}: definition schema failed to compile: ${compileError}`,
|
|
719
|
+
data: {
|
|
720
|
+
resource: { kind: m.kind, name },
|
|
721
|
+
filePath: (m.metadata as { source?: string } | undefined)?.source,
|
|
722
|
+
path: "schema",
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
701
728
|
// Library env: rejection — `env:` on a Library `variables` / `secrets`
|
|
702
729
|
// entry is forbidden. The Library entry schema is otherwise open so that
|
|
703
730
|
// any JSON Schema property schema is valid; this targeted check produces
|
|
@@ -805,6 +832,39 @@ export class StaticAnalyzer {
|
|
|
805
832
|
}
|
|
806
833
|
}
|
|
807
834
|
|
|
835
|
+
// Validate inline resources nested inside this resource's body (e.g. a
|
|
836
|
+
// Run.Sequence step's `invoke: { kind, ...config }`). These sit at
|
|
837
|
+
// x-telo-ref slots reached only through local `$ref`s, which the
|
|
838
|
+
// reference field map intentionally does not follow, so they escape both
|
|
839
|
+
// inline-extraction and the per-resource schema check above.
|
|
840
|
+
if (definition.schema) {
|
|
841
|
+
// Resolve inline kinds in the parent resource's scope: direct kind
|
|
842
|
+
// first, then the parent module's own aliases (for resources declared
|
|
843
|
+
// inside an imported module), then the root aliases. Mirrors how the
|
|
844
|
+
// analyzer resolves kinds elsewhere so module-scoped aliases don't
|
|
845
|
+
// produce false UNDEFINED_KIND diagnostics.
|
|
846
|
+
const ownModule = (m.metadata as { module?: string } | undefined)?.module;
|
|
847
|
+
const scopeResolver =
|
|
848
|
+
ownModule && !rootModules.has(ownModule) ? aliasesByModule.get(ownModule) : undefined;
|
|
849
|
+
diagnostics.push(
|
|
850
|
+
...validateNestedInlineResources(
|
|
851
|
+
m,
|
|
852
|
+
definition.schema as Record<string, any>,
|
|
853
|
+
(kind: string) => {
|
|
854
|
+
const direct = defs.resolve(kind);
|
|
855
|
+
if (direct) return direct;
|
|
856
|
+
const viaScope = scopeResolver?.resolveKind(kind);
|
|
857
|
+
if (viaScope) {
|
|
858
|
+
const scoped = defs.resolve(viaScope);
|
|
859
|
+
if (scoped) return scoped;
|
|
860
|
+
}
|
|
861
|
+
const viaRoot = aliases.resolveKind(kind);
|
|
862
|
+
return viaRoot ? defs.resolve(viaRoot) : undefined;
|
|
863
|
+
},
|
|
864
|
+
),
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
|
|
808
868
|
// (Invocation context compatibility check is handled via x-telo-context in the CEL pass below)
|
|
809
869
|
}
|
|
810
870
|
|
|
@@ -973,7 +1033,18 @@ export class StaticAnalyzer {
|
|
|
973
1033
|
}
|
|
974
1034
|
|
|
975
1035
|
const engine = defaultRegistry().get(engineName);
|
|
976
|
-
if (!engine)
|
|
1036
|
+
if (!engine) {
|
|
1037
|
+
// No registered engine owns this tag — the expression would go
|
|
1038
|
+
// entirely unanalyzed. Surface it rather than skipping silently.
|
|
1039
|
+
diagnostics.push({
|
|
1040
|
+
severity: DiagnosticSeverity.Error,
|
|
1041
|
+
code: "UNKNOWN_ENGINE",
|
|
1042
|
+
source: SOURCE,
|
|
1043
|
+
message: `${m.kind}/${resource.name}: no templating engine registered for '!${engineName}' at '${path}'.`,
|
|
1044
|
+
data: { resource, filePath, path },
|
|
1045
|
+
});
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
977
1048
|
const findings = engine.analyze(expr, { celEnv: this.celEnv, contextSchema: effectiveContext });
|
|
978
1049
|
for (const f of findings) {
|
|
979
1050
|
if (f.code === "CEL_SYNTAX_ERROR") {
|
|
@@ -119,7 +119,10 @@ export class DefinitionRegistry {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/** Validates data against a schema using this registry's AJV instance, which has all
|
|
122
|
-
* registered definition schemas loaded — enabling cross-module $ref resolution.
|
|
122
|
+
* registered definition schemas loaded — enabling cross-module $ref resolution.
|
|
123
|
+
* A compile failure returns `[]` here; it is surfaced loudly (once, on the
|
|
124
|
+
* owning definition) by `schemaCompileError` via the analyzer's
|
|
125
|
+
* definition-schema compile check, so resources are never silently skipped. */
|
|
123
126
|
validateWithRefs(data: unknown, schema: Record<string, any>): string[] {
|
|
124
127
|
let validate: ReturnType<typeof this.ajv.compile>;
|
|
125
128
|
try {
|
|
@@ -131,6 +134,22 @@ export class DefinitionRegistry {
|
|
|
131
134
|
return (validate.errors ?? []).map(formatSingleError);
|
|
132
135
|
}
|
|
133
136
|
|
|
137
|
+
/** Returns the AJV compile error for `schema`, or `undefined` when it compiles.
|
|
138
|
+
* Compiles on this registry's instance, which has every loaded module schema
|
|
139
|
+
* plus the manifest root registered, so local `#/$defs`, `telo://manifest`,
|
|
140
|
+
* and cross-module `$ref`s all resolve. Used to fail loud on a definition
|
|
141
|
+
* schema that AJV cannot compile — otherwise `validateAgainstSchema` /
|
|
142
|
+
* `validateWithRefs` would swallow the failure and silently skip every
|
|
143
|
+
* resource of that kind. */
|
|
144
|
+
schemaCompileError(schema: Record<string, any>): string | undefined {
|
|
145
|
+
try {
|
|
146
|
+
this.ajv.compile(schema);
|
|
147
|
+
return undefined;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return err instanceof Error ? err.message : String(err);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
134
153
|
private tryRegisterSchema(
|
|
135
154
|
moduleName: string,
|
|
136
155
|
typeName: string,
|
|
@@ -148,7 +148,7 @@ export function buildReferenceFieldMap(schema: Record<string, any>): ReferenceFi
|
|
|
148
148
|
return map;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
function collectRefs(node: Record<string, any>): string[] {
|
|
151
|
+
export function collectRefs(node: Record<string, any>): string[] {
|
|
152
152
|
const refs: string[] = [];
|
|
153
153
|
if (typeof node["x-telo-ref"] === "string") {
|
|
154
154
|
refs.push(node["x-telo-ref"]);
|
package/src/schema-compat.ts
CHANGED
|
@@ -146,6 +146,10 @@ export function validateAgainstSchema(data: unknown, schema: Record<string, any>
|
|
|
146
146
|
validate = ajv.compile(schema);
|
|
147
147
|
compiledSchemaValidators.set(schema, validate);
|
|
148
148
|
} catch {
|
|
149
|
+
// A schema that won't compile is reported loudly (once, on the owning
|
|
150
|
+
// definition) by the analyzer's definition-schema compile check
|
|
151
|
+
// (`DefinitionRegistry.schemaCompileError`), so returning `[]` here does
|
|
152
|
+
// not silently accept resources of that kind.
|
|
149
153
|
return [];
|
|
150
154
|
}
|
|
151
155
|
}
|
|
@@ -273,7 +277,7 @@ export function celPlaceholderForSchema(schema: Record<string, any>): unknown {
|
|
|
273
277
|
const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
|
|
274
278
|
|
|
275
279
|
/** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
|
|
276
|
-
function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any> {
|
|
280
|
+
export function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any> {
|
|
277
281
|
if (schema.$ref && typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
|
|
278
282
|
const defName = schema.$ref.slice("#/$defs/".length);
|
|
279
283
|
const resolved = root.$defs?.[defName];
|
|
@@ -283,7 +287,7 @@ function resolveRef(schema: Record<string, any>, root: Record<string, any>): Rec
|
|
|
283
287
|
}
|
|
284
288
|
|
|
285
289
|
/** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
|
|
286
|
-
function collectProperties(schema: Record<string, any>): Record<string, any> {
|
|
290
|
+
export function collectProperties(schema: Record<string, any>): Record<string, any> {
|
|
287
291
|
const props: Record<string, any> = { ...(schema.properties ?? {}) };
|
|
288
292
|
for (const sub of schema.oneOf ?? schema.anyOf ?? []) {
|
|
289
293
|
if (sub && typeof sub === "object" && sub.properties) {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import { collectRefs, isInlineResource } from "./reference-field-map.js";
|
|
3
|
+
import {
|
|
4
|
+
collectProperties,
|
|
5
|
+
resolveRef,
|
|
6
|
+
substituteCelFields,
|
|
7
|
+
validateAgainstSchema,
|
|
8
|
+
} from "./schema-compat.js";
|
|
9
|
+
import { DiagnosticSeverity, type AnalysisDiagnostic } from "./types.js";
|
|
10
|
+
|
|
11
|
+
const SOURCE = "telo-analyzer";
|
|
12
|
+
|
|
13
|
+
/** Minimal view of a definition needed to validate an inline resource's config. */
|
|
14
|
+
export interface InlineDefinitionLookup {
|
|
15
|
+
(kind: string): { schema?: Record<string, any> } | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates inline resources nested inside a resource body against their kind's
|
|
20
|
+
* config schema. The per-resource walk in `analyze()` validates a resource's
|
|
21
|
+
* own top-level config; inline resources at `x-telo-ref` slots reachable only
|
|
22
|
+
* through a local `$ref` (notably `Run.Sequence`'s `steps[].invoke`, hidden
|
|
23
|
+
* behind `#/$defs/step`) never reach the reference field map, so they would
|
|
24
|
+
* otherwise escape schema validation — e.g. `invoke: { kind: Console.ReadLine,
|
|
25
|
+
* prompt: "…" }`, where `prompt` belongs in the step's `inputs`, not the config.
|
|
26
|
+
*
|
|
27
|
+
* Walks the manifest data together with its definition schema, resolving local
|
|
28
|
+
* `$ref`s (so step trees of arbitrary depth are covered). At each `x-telo-ref`
|
|
29
|
+
* slot holding an inline resource, the inline's config is validated against its
|
|
30
|
+
* own kind's schema, then recursed into so inline resources nested inside
|
|
31
|
+
* inline resources are covered.
|
|
32
|
+
*
|
|
33
|
+
* Non-mutating: reads `manifest` and emits diagnostics anchored to its identity
|
|
34
|
+
* and a concrete dotted path matching the position-index key format;
|
|
35
|
+
* `rewriteSyntheticOrigins` reroutes those on inline-extracted (synthetic)
|
|
36
|
+
* manifests back to the root doc.
|
|
37
|
+
*/
|
|
38
|
+
export function validateNestedInlineResources(
|
|
39
|
+
manifest: ResourceManifest,
|
|
40
|
+
rootSchema: Record<string, any>,
|
|
41
|
+
lookupDefinition: InlineDefinitionLookup,
|
|
42
|
+
): AnalysisDiagnostic[] {
|
|
43
|
+
const diagnostics: AnalysisDiagnostic[] = [];
|
|
44
|
+
const resource = { kind: manifest.kind, name: manifest.metadata?.name as string };
|
|
45
|
+
const filePath = (manifest.metadata as { source?: string } | undefined)?.source;
|
|
46
|
+
|
|
47
|
+
function validateInline(inline: Record<string, any>, path: string): void {
|
|
48
|
+
const kind = inline.kind as string;
|
|
49
|
+
const def = lookupDefinition(kind);
|
|
50
|
+
// Unknown kind: these `$ref`-hidden slots are invisible to the field-map
|
|
51
|
+
// driven reference checks too, so nothing else would flag it — report here.
|
|
52
|
+
if (!def) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
severity: DiagnosticSeverity.Error,
|
|
55
|
+
code: "UNDEFINED_KIND",
|
|
56
|
+
source: SOURCE,
|
|
57
|
+
message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': No Telo.Definition found for kind '${kind}'.`,
|
|
58
|
+
data: { resource, filePath, path: `${path}.kind` },
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Kind exists but declares no config schema (e.g. a pure Telo.Type): no
|
|
63
|
+
// config to validate and no schema-declared slots to nest resources in.
|
|
64
|
+
if (!def.schema) return;
|
|
65
|
+
const schema = def.schema;
|
|
66
|
+
// `kind` / `metadata` are implicit on every resource; inject them so a
|
|
67
|
+
// strict `additionalProperties: false` config schema doesn't reject them.
|
|
68
|
+
const effectiveSchema =
|
|
69
|
+
schema.additionalProperties === false
|
|
70
|
+
? {
|
|
71
|
+
...schema,
|
|
72
|
+
properties: {
|
|
73
|
+
kind: { type: "string" },
|
|
74
|
+
metadata: { type: "object" },
|
|
75
|
+
...schema.properties,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
: schema;
|
|
79
|
+
// Inline resources omit `metadata` — it is synthesized when the kernel
|
|
80
|
+
// registers them (and by `normalizeInlineResources` for extracted slots,
|
|
81
|
+
// which assigns a derived `metadata.name`). Config schemas conventionally
|
|
82
|
+
// declare `required: ["metadata", …]` with `metadata.name` required, so add
|
|
83
|
+
// a placeholder before validating to mirror the post-registration shape.
|
|
84
|
+
const existingMeta =
|
|
85
|
+
inline.metadata && typeof inline.metadata === "object"
|
|
86
|
+
? (inline.metadata as Record<string, unknown>)
|
|
87
|
+
: {};
|
|
88
|
+
const data = { ...inline, metadata: { name: "__inline__", ...existingMeta } };
|
|
89
|
+
const substituted = substituteCelFields(data, effectiveSchema, effectiveSchema);
|
|
90
|
+
for (const issue of validateAgainstSchema(substituted, effectiveSchema)) {
|
|
91
|
+
diagnostics.push({
|
|
92
|
+
severity: DiagnosticSeverity.Error,
|
|
93
|
+
code: "SCHEMA_VIOLATION",
|
|
94
|
+
source: SOURCE,
|
|
95
|
+
message: `${resource.kind}/${resource.name}: inline ${kind} at '${path}': ${issue.message}`,
|
|
96
|
+
data: { resource, filePath, path: issue.path ? `${path}.${issue.path}` : path },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Recurse into the inline body against its own schema so deeper inline
|
|
100
|
+
// resources (e.g. an inline Run.Sequence's own steps) are validated too.
|
|
101
|
+
walk(inline, schema, schema, path);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function walk(
|
|
105
|
+
data: unknown,
|
|
106
|
+
schema: Record<string, any> | undefined,
|
|
107
|
+
schemaRoot: Record<string, any>,
|
|
108
|
+
path: string,
|
|
109
|
+
): void {
|
|
110
|
+
if (!schema || typeof schema !== "object") return;
|
|
111
|
+
const resolved = resolveRef(schema, schemaRoot);
|
|
112
|
+
|
|
113
|
+
// Reference slot: the value is either a named reference (`{kind, name}`,
|
|
114
|
+
// validated as its own manifest) or an inline resource to validate here.
|
|
115
|
+
if (collectRefs(resolved).length > 0) {
|
|
116
|
+
if (
|
|
117
|
+
data &&
|
|
118
|
+
typeof data === "object" &&
|
|
119
|
+
!Array.isArray(data) &&
|
|
120
|
+
isInlineResource(data as Record<string, unknown>)
|
|
121
|
+
) {
|
|
122
|
+
validateInline(data as Record<string, any>, path);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (Array.isArray(data)) {
|
|
128
|
+
const itemSchema = resolved.items as Record<string, any> | undefined;
|
|
129
|
+
if (!itemSchema) return;
|
|
130
|
+
for (let i = 0; i < data.length; i++) {
|
|
131
|
+
walk(data[i], itemSchema, schemaRoot, `${path}[${i}]`);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (data && typeof data === "object") {
|
|
137
|
+
const props = collectProperties(resolved);
|
|
138
|
+
const additional =
|
|
139
|
+
resolved.additionalProperties &&
|
|
140
|
+
typeof resolved.additionalProperties === "object" &&
|
|
141
|
+
!Array.isArray(resolved.additionalProperties)
|
|
142
|
+
? (resolved.additionalProperties as Record<string, any>)
|
|
143
|
+
: undefined;
|
|
144
|
+
// Descend only where the schema declares structure. Freeform fields
|
|
145
|
+
// (`additionalProperties: true`, e.g. step `inputs`) carry caller data
|
|
146
|
+
// that may coincidentally look like `{kind: …}`; not descending there
|
|
147
|
+
// keeps the inline-resource detection anchored to real ref slots.
|
|
148
|
+
for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
|
|
149
|
+
const propSchema = props[key] ?? additional;
|
|
150
|
+
if (!propSchema) continue;
|
|
151
|
+
walk(value, propSchema as Record<string, any>, schemaRoot, path ? `${path}.${key}` : key);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
walk(manifest, rootSchema, rootSchema, "");
|
|
157
|
+
return diagnostics;
|
|
158
|
+
}
|
|
@@ -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
|
-
}
|