@telorun/analyzer 0.26.0 → 0.28.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/README.md +1 -140
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +52 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/loaded-types.d.ts +15 -2
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-loader.d.ts +6 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +21 -17
- package/dist/reconcile-module-versions.d.ts +25 -0
- package/dist/reconcile-module-versions.d.ts.map +1 -0
- package/dist/reconcile-module-versions.js +215 -0
- package/dist/sources/default-sources.d.ts +8 -0
- package/dist/sources/default-sources.d.ts.map +1 -0
- package/dist/sources/default-sources.js +10 -0
- package/dist/types.d.ts +0 -8
- package/dist/types.d.ts.map +1 -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/index.ts +3 -0
- package/src/loaded-types.ts +15 -2
- package/src/manifest-loader.ts +22 -19
- package/src/reconcile-module-versions.ts +253 -0
- package/src/sources/default-sources.ts +12 -0
- package/src/types.ts +0 -8
- package/src/validate-provider-coherence.ts +85 -0
|
@@ -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,
|