@telorun/analyzer 0.20.0 → 0.21.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
CHANGED
|
@@ -55,8 +55,8 @@ metadata:
|
|
|
55
55
|
A complete feedback collection REST API — no code, pure YAML.
|
|
56
56
|
Persists entries to SQLite and serves them over HTTP.
|
|
57
57
|
imports:
|
|
58
|
-
Http: std/http-server@0.
|
|
59
|
-
Sql: std/sql@0.
|
|
58
|
+
Http: std/http-server@0.9.0
|
|
59
|
+
Sql: std/sql@0.8.0
|
|
60
60
|
targets:
|
|
61
61
|
- Migrations
|
|
62
62
|
- Server
|
|
@@ -175,6 +175,8 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
|
|
|
175
175
|
}
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
|
+
if (typeof node?.$ref === "string")
|
|
179
|
+
return;
|
|
178
180
|
// Array — recurse into items
|
|
179
181
|
if (node.type === "array" && node.items) {
|
|
180
182
|
traverseNode(node.items, path + "[]", map, root, visitedRefs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAQnE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO,GAC1C,IAAI,
|
|
1
|
+
{"version":3,"file":"resolve-ref-sentinels.d.ts","sourceRoot":"","sources":["../src/resolve-ref-sentinels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAQnE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAK5C,kBAAkB,GAAE,gBAAgB,EAAO,GAC1C,IAAI,CAmFN"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isRefSentinel } from "@telorun/templating";
|
|
2
|
-
import { isRefEntry } from "./reference-field-map.js";
|
|
2
|
+
import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
3
3
|
import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
4
4
|
/**
|
|
5
5
|
* Walks every `x-telo-ref` slot in every non-system resource and rewrites
|
|
@@ -81,22 +81,80 @@ crossModuleTargets = []) {
|
|
|
81
81
|
}
|
|
82
82
|
return undefined;
|
|
83
83
|
};
|
|
84
|
-
|
|
84
|
+
const processResource = (r) => {
|
|
85
85
|
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
|
|
86
|
-
|
|
87
|
-
if (isForeign(r))
|
|
88
|
-
continue;
|
|
86
|
+
return;
|
|
89
87
|
const fieldMap = aliases && aliasesByModule
|
|
90
88
|
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
91
89
|
: registry.getFieldMapForKind(r.kind, aliases);
|
|
92
90
|
if (!fieldMap)
|
|
93
|
-
|
|
91
|
+
return;
|
|
94
92
|
for (const [fieldPath, entry] of fieldMap) {
|
|
95
|
-
|
|
93
|
+
const parts = fieldPath.split(".");
|
|
94
|
+
if (isRefEntry(entry)) {
|
|
95
|
+
descend(r, parts, resolveTarget);
|
|
96
|
+
}
|
|
97
|
+
else if (isScopeEntry(entry)) {
|
|
98
|
+
// x-telo-scope resources (e.g. a Run.Sequence `with` server) carry their own ref
|
|
99
|
+
// slots. The top-level walk skips scope contents, so recurse so a scoped resource's
|
|
100
|
+
// `!ref` (e.g. an Http.Server mount) is canonicalized to {kind, name} like any other.
|
|
101
|
+
forEachScopeResource(r, parts, processResource);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
for (const r of resources) {
|
|
106
|
+
if (isForeign(r))
|
|
107
|
+
continue;
|
|
108
|
+
processResource(r);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Navigates `obj` along the scope field path (dot notation, `[]` = array items) and
|
|
113
|
+
* invokes `cb` on every resource-like object found — any value carrying a `kind` string.
|
|
114
|
+
*
|
|
115
|
+
* Two-phase design:
|
|
116
|
+
*
|
|
117
|
+
* Phase 1 — path-walk: steps through each `parts` segment. `[]`-suffixed parts spread
|
|
118
|
+
* the array into individual elements so `current` always ends up holding scalars or
|
|
119
|
+
* plain objects, never intermediate arrays. Non-`[]` parts push the value as-is.
|
|
120
|
+
*
|
|
121
|
+
* Phase 2 — terminal visit: after the walk, `current` contains the values at the end
|
|
122
|
+
* of the path. These are always scalars or plain objects because of the `[]` spreading
|
|
123
|
+
* above, EXCEPT when a scope field is typed as an array in the schema but the path
|
|
124
|
+
* was authored WITHOUT a `[]` suffix. The `visit` function handles that case by
|
|
125
|
+
* recursing one level into arrays so `cb` is always called on resource objects, not
|
|
126
|
+
* on their container.
|
|
127
|
+
*/
|
|
128
|
+
function forEachScopeResource(obj, parts, cb) {
|
|
129
|
+
let current = [obj];
|
|
130
|
+
for (const part of parts) {
|
|
131
|
+
const isArr = part.endsWith("[]");
|
|
132
|
+
const key = isArr ? part.slice(0, -2) : part;
|
|
133
|
+
const next = [];
|
|
134
|
+
for (const node of current) {
|
|
135
|
+
if (!node || typeof node !== "object")
|
|
136
|
+
continue;
|
|
137
|
+
const val = node[key];
|
|
138
|
+
if (val == null)
|
|
96
139
|
continue;
|
|
97
|
-
|
|
140
|
+
if (isArr && Array.isArray(val))
|
|
141
|
+
next.push(...val);
|
|
142
|
+
else if (!isArr)
|
|
143
|
+
next.push(val);
|
|
98
144
|
}
|
|
145
|
+
current = next;
|
|
99
146
|
}
|
|
147
|
+
const visit = (node) => {
|
|
148
|
+
if (Array.isArray(node)) {
|
|
149
|
+
for (const elem of node)
|
|
150
|
+
visit(elem);
|
|
151
|
+
}
|
|
152
|
+
else if (node && typeof node === "object" && typeof node.kind === "string") {
|
|
153
|
+
cb(node);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
for (const node of current)
|
|
157
|
+
visit(node);
|
|
100
158
|
}
|
|
101
159
|
/** Walks `obj` along `fieldPath` parts (dot notation with `[]` for arrays and `{}` for
|
|
102
160
|
* additionalProperties-typed maps) and replaces any `!ref` sentinel at the terminal slot
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Telo Analyzer - Static manifest validator for Telo manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
"src/**"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@marcbachmann/cel-js": "^7.
|
|
40
|
+
"@marcbachmann/cel-js": "^7.6.1",
|
|
41
41
|
"ajv": "^8.17.1",
|
|
42
42
|
"ajv-formats": "^3.0.1",
|
|
43
43
|
"jsonpath-plus": "^10.3.0",
|
|
44
44
|
"yaml": "^2.8.3",
|
|
45
|
-
"@telorun/templating": "0.
|
|
45
|
+
"@telorun/templating": "0.7.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
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.23.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@telorun/sdk": "*"
|
|
@@ -2,7 +2,7 @@ import type { ResourceManifest } from "@telorun/sdk";
|
|
|
2
2
|
import { isRefSentinel } from "@telorun/templating";
|
|
3
3
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
4
4
|
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
5
|
-
import { isRefEntry } from "./reference-field-map.js";
|
|
5
|
+
import { isRefEntry, isScopeEntry } from "./reference-field-map.js";
|
|
6
6
|
import { REF_RESOLUTION_SKIP_KINDS as SYSTEM_KINDS } from "./system-kinds.js";
|
|
7
7
|
|
|
8
8
|
/** Resolved ref shape written in place of a `!ref` sentinel. `alias` is set only for
|
|
@@ -96,21 +96,78 @@ export function resolveRefSentinels(
|
|
|
96
96
|
return undefined;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
|
|
101
|
-
if (isForeign(r)) continue;
|
|
99
|
+
const processResource = (r: ResourceManifest): void => {
|
|
100
|
+
if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind)) return;
|
|
102
101
|
|
|
103
102
|
const fieldMap =
|
|
104
103
|
aliases && aliasesByModule
|
|
105
104
|
? registry.expandedFieldMapForResource(r, aliases, aliasesByModule)
|
|
106
105
|
: registry.getFieldMapForKind(r.kind, aliases);
|
|
107
|
-
if (!fieldMap)
|
|
106
|
+
if (!fieldMap) return;
|
|
108
107
|
|
|
109
108
|
for (const [fieldPath, entry] of fieldMap) {
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
const parts = fieldPath.split(".");
|
|
110
|
+
if (isRefEntry(entry)) {
|
|
111
|
+
descend(r as Record<string, unknown>, parts, resolveTarget);
|
|
112
|
+
} else if (isScopeEntry(entry)) {
|
|
113
|
+
// x-telo-scope resources (e.g. a Run.Sequence `with` server) carry their own ref
|
|
114
|
+
// slots. The top-level walk skips scope contents, so recurse so a scoped resource's
|
|
115
|
+
// `!ref` (e.g. an Http.Server mount) is canonicalized to {kind, name} like any other.
|
|
116
|
+
forEachScopeResource(r as Record<string, unknown>, parts, processResource);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
for (const r of resources) {
|
|
122
|
+
if (isForeign(r)) continue;
|
|
123
|
+
processResource(r);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Navigates `obj` along the scope field path (dot notation, `[]` = array items) and
|
|
129
|
+
* invokes `cb` on every resource-like object found — any value carrying a `kind` string.
|
|
130
|
+
*
|
|
131
|
+
* Two-phase design:
|
|
132
|
+
*
|
|
133
|
+
* Phase 1 — path-walk: steps through each `parts` segment. `[]`-suffixed parts spread
|
|
134
|
+
* the array into individual elements so `current` always ends up holding scalars or
|
|
135
|
+
* plain objects, never intermediate arrays. Non-`[]` parts push the value as-is.
|
|
136
|
+
*
|
|
137
|
+
* Phase 2 — terminal visit: after the walk, `current` contains the values at the end
|
|
138
|
+
* of the path. These are always scalars or plain objects because of the `[]` spreading
|
|
139
|
+
* above, EXCEPT when a scope field is typed as an array in the schema but the path
|
|
140
|
+
* was authored WITHOUT a `[]` suffix. The `visit` function handles that case by
|
|
141
|
+
* recursing one level into arrays so `cb` is always called on resource objects, not
|
|
142
|
+
* on their container.
|
|
143
|
+
*/
|
|
144
|
+
function forEachScopeResource(
|
|
145
|
+
obj: Record<string, unknown>,
|
|
146
|
+
parts: string[],
|
|
147
|
+
cb: (resource: ResourceManifest) => void,
|
|
148
|
+
): void {
|
|
149
|
+
let current: unknown[] = [obj];
|
|
150
|
+
for (const part of parts) {
|
|
151
|
+
const isArr = part.endsWith("[]");
|
|
152
|
+
const key = isArr ? part.slice(0, -2) : part;
|
|
153
|
+
const next: unknown[] = [];
|
|
154
|
+
for (const node of current) {
|
|
155
|
+
if (!node || typeof node !== "object") continue;
|
|
156
|
+
const val = (node as Record<string, unknown>)[key];
|
|
157
|
+
if (val == null) continue;
|
|
158
|
+
if (isArr && Array.isArray(val)) next.push(...val);
|
|
159
|
+
else if (!isArr) next.push(val);
|
|
112
160
|
}
|
|
161
|
+
current = next;
|
|
113
162
|
}
|
|
163
|
+
const visit = (node: unknown): void => {
|
|
164
|
+
if (Array.isArray(node)) {
|
|
165
|
+
for (const elem of node) visit(elem);
|
|
166
|
+
} else if (node && typeof node === "object" && typeof (node as { kind?: unknown }).kind === "string") {
|
|
167
|
+
cb(node as ResourceManifest);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
for (const node of current) visit(node);
|
|
114
171
|
}
|
|
115
172
|
|
|
116
173
|
/** Walks `obj` along `fieldPath` parts (dot notation with `[]` for arrays and `{}` for
|