@telorun/analyzer 0.26.0 → 0.27.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/builtins.d.ts.map +1 -1
- package/dist/builtins.js +52 -1
- package/dist/validate-provider-coherence.d.ts +8 -0
- package/dist/validate-provider-coherence.d.ts.map +1 -1
- package/dist/validate-provider-coherence.js +81 -0
- package/package.json +2 -2
- package/src/builtins.ts +52 -1
- package/src/validate-provider-coherence.ts +85 -0
package/dist/builtins.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../src/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,eAAO,MAAM,eAAe,EAAE,kBAAkB,EA2hB/C,CAAC"}
|
package/dist/builtins.js
CHANGED
|
@@ -59,12 +59,28 @@ export const KERNEL_BUILTINS = [
|
|
|
59
59
|
items: {
|
|
60
60
|
type: "object",
|
|
61
61
|
additionalProperties: true,
|
|
62
|
+
// Resource bodies are `self`-only for config: per-call `inputs` is
|
|
63
|
+
// NOT in scope here. Each entry is a persistent child created once at
|
|
64
|
+
// init() and reused, so its config cannot depend on call-time data —
|
|
65
|
+
// that flows through the top-level `inputs:` sibling into the dispatch
|
|
66
|
+
// target's invoke().
|
|
67
|
+
//
|
|
68
|
+
// The exception is CEL the child's OWN controller evaluates later
|
|
69
|
+
// against a runtime context it owns (e.g. an Http.Api evaluating route
|
|
70
|
+
// CEL per request). Those `request` / `result` / `steps` / `error`
|
|
71
|
+
// variables are deferred — the template controller preserves them
|
|
72
|
+
// untouched (see resource-template-controller.ts) — so they are
|
|
73
|
+
// exposed here permissively. Their deep shape is the child kind's
|
|
74
|
+
// concern, not the template's, so they type as open values.
|
|
62
75
|
"x-telo-context": {
|
|
63
76
|
type: "object",
|
|
64
77
|
additionalProperties: false,
|
|
65
78
|
properties: {
|
|
66
79
|
self: { "x-telo-context-from-root": "schema" },
|
|
67
|
-
|
|
80
|
+
request: {},
|
|
81
|
+
result: {},
|
|
82
|
+
steps: {},
|
|
83
|
+
error: {},
|
|
68
84
|
},
|
|
69
85
|
},
|
|
70
86
|
},
|
|
@@ -127,6 +143,41 @@ export const KERNEL_BUILTINS = [
|
|
|
127
143
|
},
|
|
128
144
|
},
|
|
129
145
|
},
|
|
146
|
+
// Mount dispatch: names the `resources:` entry (a Telo.Mount, e.g. an
|
|
147
|
+
// Http.Api) whose `register()` this definition delegates to. Same
|
|
148
|
+
// string / { kind, name } grammar as `invoke:`. The named child stays
|
|
149
|
+
// persistent so the produced mount's routes can `!ref` its siblings.
|
|
150
|
+
mount: {
|
|
151
|
+
oneOf: [
|
|
152
|
+
{
|
|
153
|
+
type: "string",
|
|
154
|
+
"x-telo-context": {
|
|
155
|
+
type: "object",
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
properties: {
|
|
158
|
+
self: { "x-telo-context-from-root": "schema" },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: "object",
|
|
164
|
+
additionalProperties: true,
|
|
165
|
+
properties: {
|
|
166
|
+
kind: { type: "string" },
|
|
167
|
+
name: {
|
|
168
|
+
type: "string",
|
|
169
|
+
"x-telo-context": {
|
|
170
|
+
type: "object",
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
properties: {
|
|
173
|
+
self: { "x-telo-context-from-root": "schema" },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
130
181
|
inputs: {
|
|
131
182
|
type: "object",
|
|
132
183
|
additionalProperties: true,
|
|
@@ -18,6 +18,14 @@ import { type AnalysisDiagnostic } from "./types.js";
|
|
|
18
18
|
* kind is registered but not a `Telo.Invocable`.
|
|
19
19
|
* - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
|
|
20
20
|
* declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
|
|
21
|
+
* - MOUNT_ON_NON_MOUNT: `mount:` declared on a definition whose `capability` is
|
|
22
|
+
* not `Telo.Mount`.
|
|
23
|
+
* - MOUNT_DISPATCHER_CONFLICT: `mount:` co-exists with another dispatch
|
|
24
|
+
* entry-point (`invoke:` / `run:` / `provide:`).
|
|
25
|
+
* - MOUNT_TARGET_UNKNOWN: `mount.name` does not resolve to an entry in
|
|
26
|
+
* `resources:`.
|
|
27
|
+
* - MOUNT_TARGET_NOT_MOUNTABLE: `mount.name` resolves to a resource whose kind
|
|
28
|
+
* is registered but not a `Telo.Mount`.
|
|
21
29
|
*/
|
|
22
30
|
export declare function validateProviderCoherence(manifests: ResourceManifest[], registry: DefinitionRegistry, aliases: AliasResolver): AnalysisDiagnostic[];
|
|
23
31
|
//# sourceMappingURL=validate-provider-coherence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-provider-coherence.d.ts","sourceRoot":"","sources":["../src/validate-provider-coherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE
|
|
1
|
+
{"version":3,"file":"validate-provider-coherence.d.ts","sourceRoot":"","sources":["../src/validate-provider-coherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,EAAE,aAAa,GACrB,kBAAkB,EAAE,CAsNtB"}
|
|
@@ -16,6 +16,14 @@ const SOURCE = "telo-analyzer";
|
|
|
16
16
|
* kind is registered but not a `Telo.Invocable`.
|
|
17
17
|
* - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
|
|
18
18
|
* declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
|
|
19
|
+
* - MOUNT_ON_NON_MOUNT: `mount:` declared on a definition whose `capability` is
|
|
20
|
+
* not `Telo.Mount`.
|
|
21
|
+
* - MOUNT_DISPATCHER_CONFLICT: `mount:` co-exists with another dispatch
|
|
22
|
+
* entry-point (`invoke:` / `run:` / `provide:`).
|
|
23
|
+
* - MOUNT_TARGET_UNKNOWN: `mount.name` does not resolve to an entry in
|
|
24
|
+
* `resources:`.
|
|
25
|
+
* - MOUNT_TARGET_NOT_MOUNTABLE: `mount.name` resolves to a resource whose kind
|
|
26
|
+
* is registered but not a `Telo.Mount`.
|
|
19
27
|
*/
|
|
20
28
|
export function validateProviderCoherence(manifests, registry, aliases) {
|
|
21
29
|
const diagnostics = [];
|
|
@@ -45,11 +53,13 @@ export function validateProviderCoherence(manifests, registry, aliases) {
|
|
|
45
53
|
const provide = md.provide;
|
|
46
54
|
const invoke = md.invoke;
|
|
47
55
|
const run = md.run;
|
|
56
|
+
const mount = md.mount;
|
|
48
57
|
const controllers = md.controllers;
|
|
49
58
|
const resources = md.resources;
|
|
50
59
|
const hasProvide = provide !== undefined && provide !== null;
|
|
51
60
|
const hasInvoke = invoke !== undefined && invoke !== null;
|
|
52
61
|
const hasRun = run !== undefined && run !== null;
|
|
62
|
+
const hasMount = mount !== undefined && mount !== null;
|
|
53
63
|
const hasControllers = Array.isArray(controllers) && controllers.length > 0;
|
|
54
64
|
if (hasProvide && capability !== "Telo.Provider") {
|
|
55
65
|
diagnostics.push({
|
|
@@ -133,6 +143,77 @@ export function validateProviderCoherence(manifests, registry, aliases) {
|
|
|
133
143
|
}
|
|
134
144
|
}
|
|
135
145
|
}
|
|
146
|
+
if (hasMount && capability !== "Telo.Mount") {
|
|
147
|
+
diagnostics.push({
|
|
148
|
+
severity: DiagnosticSeverity.Error,
|
|
149
|
+
code: "MOUNT_ON_NON_MOUNT",
|
|
150
|
+
source: SOURCE,
|
|
151
|
+
message: `${label}: 'mount:' is only valid on definitions with 'capability: Telo.Mount' ` +
|
|
152
|
+
`(found '${capability ?? "<unset>"}'). Use 'invoke:' / 'run:' / 'provide:' for other capabilities.`,
|
|
153
|
+
data: { resource, filePath, path: "mount" },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (hasMount && (hasInvoke || hasRun || hasProvide)) {
|
|
157
|
+
const conflict = hasInvoke ? "invoke" : hasRun ? "run" : "provide";
|
|
158
|
+
diagnostics.push({
|
|
159
|
+
severity: DiagnosticSeverity.Error,
|
|
160
|
+
code: "MOUNT_DISPATCHER_CONFLICT",
|
|
161
|
+
source: SOURCE,
|
|
162
|
+
message: `${label}: 'mount:' cannot co-exist with '${conflict}:'. ` +
|
|
163
|
+
`A definition declares exactly one dispatch entry-point.`,
|
|
164
|
+
data: { resource, filePath, path: "mount" },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (hasMount) {
|
|
168
|
+
// Resolve the target's name from either form: the bare string (the
|
|
169
|
+
// primary, documented form — `mount: api`) or the object's `name`. A CEL
|
|
170
|
+
// target (`${{ … }}`) can only be checked at runtime, so skip those.
|
|
171
|
+
let mountedName;
|
|
172
|
+
if (typeof mount === "string") {
|
|
173
|
+
if (!mount.includes("${{"))
|
|
174
|
+
mountedName = mount;
|
|
175
|
+
}
|
|
176
|
+
else if (typeof mount === "object" && !Array.isArray(mount)) {
|
|
177
|
+
const mountObj = mount;
|
|
178
|
+
if (typeof mountObj.name === "string" && !mountObj.name.includes("${{")) {
|
|
179
|
+
mountedName = mountObj.name;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const mountPath = typeof mount === "string" ? "mount" : "mount.name";
|
|
183
|
+
if (mountedName && Array.isArray(resources)) {
|
|
184
|
+
const match = resources.find((r) => {
|
|
185
|
+
const meta = r?.metadata;
|
|
186
|
+
return typeof meta?.name === "string" && meta.name === mountedName;
|
|
187
|
+
});
|
|
188
|
+
if (!match) {
|
|
189
|
+
diagnostics.push({
|
|
190
|
+
severity: DiagnosticSeverity.Error,
|
|
191
|
+
code: "MOUNT_TARGET_UNKNOWN",
|
|
192
|
+
source: SOURCE,
|
|
193
|
+
message: `${label}: '${mountPath}: ${mountedName}' does not match any entry's ` +
|
|
194
|
+
`metadata.name in 'resources:'.`,
|
|
195
|
+
data: { resource, filePath, path: mountPath },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else if (typeof match.kind === "string") {
|
|
199
|
+
const resolvedKind = aliases.resolveKind(match.kind) ?? match.kind;
|
|
200
|
+
const targetDef = registry.resolve(resolvedKind) ?? registry.resolve(match.kind);
|
|
201
|
+
if (targetDef && targetDef.kind === "Telo.Definition") {
|
|
202
|
+
const targetCap = targetDef.capability;
|
|
203
|
+
if (typeof targetCap === "string" && targetCap !== "Telo.Mount") {
|
|
204
|
+
diagnostics.push({
|
|
205
|
+
severity: DiagnosticSeverity.Error,
|
|
206
|
+
code: "MOUNT_TARGET_NOT_MOUNTABLE",
|
|
207
|
+
source: SOURCE,
|
|
208
|
+
message: `${label}: '${mountPath}: ${mountedName}' resolves to a ${match.kind} ` +
|
|
209
|
+
`(capability '${targetCap}'); 'mount:' requires a Telo.Mount target.`,
|
|
210
|
+
data: { resource, filePath, path: mountPath },
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
136
217
|
if (capability === "Telo.Provider" && !hasControllers && !hasProvide) {
|
|
137
218
|
diagnostics.push({
|
|
138
219
|
severity: DiagnosticSeverity.Error,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@types/node": "^20.0.0",
|
|
49
49
|
"typescript": "^5.0.0",
|
|
50
50
|
"vitest": "^2.1.8",
|
|
51
|
-
"@telorun/sdk": "0.
|
|
51
|
+
"@telorun/sdk": "0.36.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@telorun/sdk": "*"
|
package/src/builtins.ts
CHANGED
|
@@ -61,12 +61,28 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
61
61
|
items: {
|
|
62
62
|
type: "object",
|
|
63
63
|
additionalProperties: true,
|
|
64
|
+
// Resource bodies are `self`-only for config: per-call `inputs` is
|
|
65
|
+
// NOT in scope here. Each entry is a persistent child created once at
|
|
66
|
+
// init() and reused, so its config cannot depend on call-time data —
|
|
67
|
+
// that flows through the top-level `inputs:` sibling into the dispatch
|
|
68
|
+
// target's invoke().
|
|
69
|
+
//
|
|
70
|
+
// The exception is CEL the child's OWN controller evaluates later
|
|
71
|
+
// against a runtime context it owns (e.g. an Http.Api evaluating route
|
|
72
|
+
// CEL per request). Those `request` / `result` / `steps` / `error`
|
|
73
|
+
// variables are deferred — the template controller preserves them
|
|
74
|
+
// untouched (see resource-template-controller.ts) — so they are
|
|
75
|
+
// exposed here permissively. Their deep shape is the child kind's
|
|
76
|
+
// concern, not the template's, so they type as open values.
|
|
64
77
|
"x-telo-context": {
|
|
65
78
|
type: "object",
|
|
66
79
|
additionalProperties: false,
|
|
67
80
|
properties: {
|
|
68
81
|
self: { "x-telo-context-from-root": "schema" },
|
|
69
|
-
|
|
82
|
+
request: {},
|
|
83
|
+
result: {},
|
|
84
|
+
steps: {},
|
|
85
|
+
error: {},
|
|
70
86
|
},
|
|
71
87
|
},
|
|
72
88
|
},
|
|
@@ -129,6 +145,41 @@ export const KERNEL_BUILTINS: ResourceDefinition[] = [
|
|
|
129
145
|
},
|
|
130
146
|
},
|
|
131
147
|
},
|
|
148
|
+
// Mount dispatch: names the `resources:` entry (a Telo.Mount, e.g. an
|
|
149
|
+
// Http.Api) whose `register()` this definition delegates to. Same
|
|
150
|
+
// string / { kind, name } grammar as `invoke:`. The named child stays
|
|
151
|
+
// persistent so the produced mount's routes can `!ref` its siblings.
|
|
152
|
+
mount: {
|
|
153
|
+
oneOf: [
|
|
154
|
+
{
|
|
155
|
+
type: "string",
|
|
156
|
+
"x-telo-context": {
|
|
157
|
+
type: "object",
|
|
158
|
+
additionalProperties: false,
|
|
159
|
+
properties: {
|
|
160
|
+
self: { "x-telo-context-from-root": "schema" },
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: "object",
|
|
166
|
+
additionalProperties: true,
|
|
167
|
+
properties: {
|
|
168
|
+
kind: { type: "string" },
|
|
169
|
+
name: {
|
|
170
|
+
type: "string",
|
|
171
|
+
"x-telo-context": {
|
|
172
|
+
type: "object",
|
|
173
|
+
additionalProperties: false,
|
|
174
|
+
properties: {
|
|
175
|
+
self: { "x-telo-context-from-root": "schema" },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
132
183
|
inputs: {
|
|
133
184
|
type: "object",
|
|
134
185
|
additionalProperties: true,
|
|
@@ -21,6 +21,14 @@ const SOURCE = "telo-analyzer";
|
|
|
21
21
|
* kind is registered but not a `Telo.Invocable`.
|
|
22
22
|
* - PROVIDER_MISSING_IMPLEMENTATION: definition with `capability: Telo.Provider`
|
|
23
23
|
* declares neither `controllers:` (TS-backed) nor `provide:` (template-backed).
|
|
24
|
+
* - MOUNT_ON_NON_MOUNT: `mount:` declared on a definition whose `capability` is
|
|
25
|
+
* not `Telo.Mount`.
|
|
26
|
+
* - MOUNT_DISPATCHER_CONFLICT: `mount:` co-exists with another dispatch
|
|
27
|
+
* entry-point (`invoke:` / `run:` / `provide:`).
|
|
28
|
+
* - MOUNT_TARGET_UNKNOWN: `mount.name` does not resolve to an entry in
|
|
29
|
+
* `resources:`.
|
|
30
|
+
* - MOUNT_TARGET_NOT_MOUNTABLE: `mount.name` resolves to a resource whose kind
|
|
31
|
+
* is registered but not a `Telo.Mount`.
|
|
24
32
|
*/
|
|
25
33
|
export function validateProviderCoherence(
|
|
26
34
|
manifests: ResourceManifest[],
|
|
@@ -52,12 +60,14 @@ export function validateProviderCoherence(
|
|
|
52
60
|
const provide = md.provide;
|
|
53
61
|
const invoke = md.invoke;
|
|
54
62
|
const run = md.run;
|
|
63
|
+
const mount = md.mount;
|
|
55
64
|
const controllers = md.controllers;
|
|
56
65
|
const resources = md.resources;
|
|
57
66
|
|
|
58
67
|
const hasProvide = provide !== undefined && provide !== null;
|
|
59
68
|
const hasInvoke = invoke !== undefined && invoke !== null;
|
|
60
69
|
const hasRun = run !== undefined && run !== null;
|
|
70
|
+
const hasMount = mount !== undefined && mount !== null;
|
|
61
71
|
const hasControllers = Array.isArray(controllers) && controllers.length > 0;
|
|
62
72
|
|
|
63
73
|
if (hasProvide && capability !== "Telo.Provider") {
|
|
@@ -149,6 +159,81 @@ export function validateProviderCoherence(
|
|
|
149
159
|
}
|
|
150
160
|
}
|
|
151
161
|
|
|
162
|
+
if (hasMount && capability !== "Telo.Mount") {
|
|
163
|
+
diagnostics.push({
|
|
164
|
+
severity: DiagnosticSeverity.Error,
|
|
165
|
+
code: "MOUNT_ON_NON_MOUNT",
|
|
166
|
+
source: SOURCE,
|
|
167
|
+
message:
|
|
168
|
+
`${label}: 'mount:' is only valid on definitions with 'capability: Telo.Mount' ` +
|
|
169
|
+
`(found '${capability ?? "<unset>"}'). Use 'invoke:' / 'run:' / 'provide:' for other capabilities.`,
|
|
170
|
+
data: { resource, filePath, path: "mount" },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (hasMount && (hasInvoke || hasRun || hasProvide)) {
|
|
175
|
+
const conflict = hasInvoke ? "invoke" : hasRun ? "run" : "provide";
|
|
176
|
+
diagnostics.push({
|
|
177
|
+
severity: DiagnosticSeverity.Error,
|
|
178
|
+
code: "MOUNT_DISPATCHER_CONFLICT",
|
|
179
|
+
source: SOURCE,
|
|
180
|
+
message:
|
|
181
|
+
`${label}: 'mount:' cannot co-exist with '${conflict}:'. ` +
|
|
182
|
+
`A definition declares exactly one dispatch entry-point.`,
|
|
183
|
+
data: { resource, filePath, path: "mount" },
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (hasMount) {
|
|
188
|
+
// Resolve the target's name from either form: the bare string (the
|
|
189
|
+
// primary, documented form — `mount: api`) or the object's `name`. A CEL
|
|
190
|
+
// target (`${{ … }}`) can only be checked at runtime, so skip those.
|
|
191
|
+
let mountedName: string | undefined;
|
|
192
|
+
if (typeof mount === "string") {
|
|
193
|
+
if (!mount.includes("${{")) mountedName = mount;
|
|
194
|
+
} else if (typeof mount === "object" && !Array.isArray(mount)) {
|
|
195
|
+
const mountObj = mount as { name?: unknown };
|
|
196
|
+
if (typeof mountObj.name === "string" && !mountObj.name.includes("${{")) {
|
|
197
|
+
mountedName = mountObj.name;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const mountPath = typeof mount === "string" ? "mount" : "mount.name";
|
|
201
|
+
if (mountedName && Array.isArray(resources)) {
|
|
202
|
+
const match = resources.find((r) => {
|
|
203
|
+
const meta = (r as { metadata?: { name?: unknown } })?.metadata;
|
|
204
|
+
return typeof meta?.name === "string" && meta.name === mountedName;
|
|
205
|
+
}) as { kind?: unknown } | undefined;
|
|
206
|
+
if (!match) {
|
|
207
|
+
diagnostics.push({
|
|
208
|
+
severity: DiagnosticSeverity.Error,
|
|
209
|
+
code: "MOUNT_TARGET_UNKNOWN",
|
|
210
|
+
source: SOURCE,
|
|
211
|
+
message:
|
|
212
|
+
`${label}: '${mountPath}: ${mountedName}' does not match any entry's ` +
|
|
213
|
+
`metadata.name in 'resources:'.`,
|
|
214
|
+
data: { resource, filePath, path: mountPath },
|
|
215
|
+
});
|
|
216
|
+
} else if (typeof match.kind === "string") {
|
|
217
|
+
const resolvedKind = aliases.resolveKind(match.kind) ?? match.kind;
|
|
218
|
+
const targetDef = registry.resolve(resolvedKind) ?? registry.resolve(match.kind);
|
|
219
|
+
if (targetDef && targetDef.kind === "Telo.Definition") {
|
|
220
|
+
const targetCap = (targetDef as { capability?: unknown }).capability;
|
|
221
|
+
if (typeof targetCap === "string" && targetCap !== "Telo.Mount") {
|
|
222
|
+
diagnostics.push({
|
|
223
|
+
severity: DiagnosticSeverity.Error,
|
|
224
|
+
code: "MOUNT_TARGET_NOT_MOUNTABLE",
|
|
225
|
+
source: SOURCE,
|
|
226
|
+
message:
|
|
227
|
+
`${label}: '${mountPath}: ${mountedName}' resolves to a ${match.kind} ` +
|
|
228
|
+
`(capability '${targetCap}'); 'mount:' requires a Telo.Mount target.`,
|
|
229
|
+
data: { resource, filePath, path: mountPath },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
152
237
|
if (capability === "Telo.Provider" && !hasControllers && !hasProvide) {
|
|
153
238
|
diagnostics.push({
|
|
154
239
|
severity: DiagnosticSeverity.Error,
|