@telorun/analyzer 0.72.0 → 0.74.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/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +76 -20
- package/dist/artifact-axes.d.ts +14 -0
- package/dist/artifact-axes.d.ts.map +1 -0
- package/dist/artifact-axes.js +12 -0
- package/dist/artifact-layer-index.d.ts +2 -2
- package/dist/artifact-layer-index.d.ts.map +1 -1
- package/dist/artifact-layer-index.js +9 -4
- package/dist/artifact-selector.d.ts +47 -21
- package/dist/artifact-selector.d.ts.map +1 -1
- package/dist/artifact-selector.js +85 -16
- package/dist/builtins.d.ts.map +1 -1
- package/dist/builtins.js +144 -113
- package/dist/call-graph.d.ts +12 -1
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +22 -0
- package/dist/catch-scope.js +1 -1
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +32 -11
- package/dist/cel-scope.d.ts.map +1 -1
- package/dist/cel-scope.js +1 -0
- package/dist/eval-paths.d.ts +36 -5
- package/dist/eval-paths.d.ts.map +1 -1
- package/dist/eval-paths.js +47 -6
- package/dist/extends-resolution.d.ts +12 -0
- package/dist/extends-resolution.d.ts.map +1 -1
- package/dist/extends-resolution.js +23 -0
- package/dist/forwarded-declaration.d.ts +26 -0
- package/dist/forwarded-declaration.d.ts.map +1 -0
- package/dist/forwarded-declaration.js +31 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/kernel-globals.d.ts.map +1 -1
- package/dist/kernel-globals.js +21 -5
- package/dist/module-alias-scope.d.ts +11 -0
- package/dist/module-alias-scope.d.ts.map +1 -1
- package/dist/module-alias-scope.js +10 -0
- package/dist/module-file-claims.d.ts +17 -1
- package/dist/module-file-claims.d.ts.map +1 -1
- package/dist/module-file-claims.js +38 -3
- package/dist/module-named-files.d.ts +72 -0
- package/dist/module-named-files.d.ts.map +1 -0
- package/dist/module-named-files.js +115 -0
- package/dist/native-entries.d.ts +83 -0
- package/dist/native-entries.d.ts.map +1 -0
- package/dist/native-entries.js +169 -0
- package/dist/nearest-name.d.ts +22 -0
- package/dist/nearest-name.d.ts.map +1 -0
- package/dist/nearest-name.js +43 -0
- package/dist/normalize-inline-resources.d.ts +41 -13
- package/dist/normalize-inline-resources.d.ts.map +1 -1
- package/dist/normalize-inline-resources.js +236 -54
- package/dist/reference-field-map.d.ts +59 -1
- package/dist/reference-field-map.d.ts.map +1 -1
- package/dist/reference-field-map.js +200 -34
- package/dist/release/destinations.d.ts +59 -0
- package/dist/release/destinations.d.ts.map +1 -0
- package/dist/release/destinations.js +75 -0
- package/dist/release/index.d.ts +6 -2
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +3 -1
- package/dist/release/ledger.d.ts +20 -11
- package/dist/release/ledger.d.ts.map +1 -1
- package/dist/release/ledger.js +43 -14
- package/dist/release/payload-digest.d.ts +2 -1
- package/dist/release/payload-digest.d.ts.map +1 -1
- package/dist/release/payload-digest.js +2 -1
- package/dist/release/release-plan.d.ts +3 -2
- package/dist/release/release-plan.d.ts.map +1 -1
- package/dist/release/release-plan.js +12 -19
- package/dist/release/workspace-config.d.ts +114 -34
- package/dist/release/workspace-config.d.ts.map +1 -1
- package/dist/release/workspace-config.js +322 -38
- package/dist/release/workspace-schema.d.ts +41 -0
- package/dist/release/workspace-schema.d.ts.map +1 -0
- package/dist/release/workspace-schema.js +77 -0
- package/dist/resolve-ref-sentinels.d.ts.map +1 -1
- package/dist/resolve-ref-sentinels.js +8 -5
- package/dist/resolve-throws-union.d.ts +25 -4
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +62 -32
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +2 -1
- package/dist/rewrite-synthetic-origins.d.ts +6 -1
- package/dist/rewrite-synthetic-origins.d.ts.map +1 -1
- package/dist/rewrite-synthetic-origins.js +107 -13
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +18 -5
- package/dist/schema-error-report.d.ts +7 -0
- package/dist/schema-error-report.d.ts.map +1 -1
- package/dist/schema-error-report.js +9 -4
- package/dist/schema-walk.d.ts +47 -13
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +150 -30
- package/dist/scope-declarations.d.ts +59 -0
- package/dist/scope-declarations.d.ts.map +1 -0
- package/dist/scope-declarations.js +45 -0
- package/dist/source-entries.d.ts +119 -0
- package/dist/source-entries.d.ts.map +1 -0
- package/dist/source-entries.js +349 -0
- package/dist/step-bodies.d.ts +24 -0
- package/dist/step-bodies.d.ts.map +1 -0
- package/dist/step-bodies.js +28 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts +10 -0
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +25 -0
- package/dist/validate-cel-context.d.ts +23 -8
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +51 -18
- package/dist/validate-exports.d.ts +28 -0
- package/dist/validate-exports.d.ts.map +1 -0
- package/dist/validate-exports.js +146 -0
- package/dist/validate-extends.d.ts.map +1 -1
- package/dist/validate-extends.js +37 -1
- package/dist/validate-module-artifact.d.ts +4 -2
- package/dist/validate-module-artifact.d.ts.map +1 -1
- package/dist/validate-module-artifact.js +43 -4
- package/dist/validate-native-entries.d.ts +15 -0
- package/dist/validate-native-entries.d.ts.map +1 -0
- package/dist/validate-native-entries.js +103 -0
- package/dist/validate-observed-state.d.ts +7 -1
- package/dist/validate-observed-state.d.ts.map +1 -1
- package/dist/validate-observed-state.js +24 -6
- package/dist/validate-provider-coherence.d.ts +4 -8
- package/dist/validate-provider-coherence.d.ts.map +1 -1
- package/dist/validate-provider-coherence.js +12 -123
- package/dist/validate-reference-forms.d.ts.map +1 -1
- package/dist/validate-reference-forms.js +2 -2
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +152 -65
- package/dist/validate-scope-reach.d.ts +31 -0
- package/dist/validate-scope-reach.d.ts.map +1 -0
- package/dist/validate-scope-reach.js +180 -0
- package/dist/validate-source-entries.d.ts +16 -0
- package/dist/validate-source-entries.d.ts.map +1 -0
- package/dist/validate-source-entries.js +90 -0
- package/dist/validate-template-body.d.ts +36 -0
- package/dist/validate-template-body.d.ts.map +1 -0
- package/dist/validate-template-body.js +246 -0
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +21 -39
- package/dist/value-type-keyword.d.ts +22 -5
- package/dist/value-type-keyword.d.ts.map +1 -1
- package/dist/value-type-keyword.js +16 -7
- package/package.json +4 -3
- package/src/analyzer.ts +97 -20
- package/src/artifact-axes.ts +24 -0
- package/src/artifact-layer-index.ts +9 -6
- package/src/artifact-selector.ts +110 -35
- package/src/builtins.ts +160 -113
- package/src/call-graph.ts +30 -0
- package/src/catch-scope.ts +1 -1
- package/src/cel-scope-query.ts +28 -9
- package/src/cel-scope.ts +1 -0
- package/src/eval-paths.ts +53 -6
- package/src/extends-resolution.ts +24 -0
- package/src/forwarded-declaration.ts +32 -0
- package/src/index.ts +37 -1
- package/src/kernel-globals.ts +28 -5
- package/src/migrations/CLAUDE.md +35 -0
- package/src/module-alias-scope.ts +17 -0
- package/src/module-file-claims.ts +63 -4
- package/src/module-named-files.ts +162 -0
- package/src/native-entries.ts +227 -0
- package/src/nearest-name.ts +47 -0
- package/src/normalize-inline-resources.ts +326 -70
- package/src/reference-field-map.ts +257 -52
- package/src/release/CLAUDE.md +32 -0
- package/src/release/destinations.ts +105 -0
- package/src/release/index.ts +33 -2
- package/src/release/ledger.ts +51 -21
- package/src/release/payload-digest.ts +2 -1
- package/src/release/release-plan.ts +16 -26
- package/src/release/workspace-config.ts +483 -47
- package/src/release/workspace-schema.ts +99 -0
- package/src/resolve-ref-sentinels.ts +7 -6
- package/src/resolve-throws-union.ts +87 -32
- package/src/resolve-zone-requirements.ts +2 -1
- package/src/rewrite-synthetic-origins.ts +107 -16
- package/src/schema-compat.ts +18 -4
- package/src/schema-error-report.ts +16 -4
- package/src/schema-walk.ts +162 -34
- package/src/scope-declarations.ts +98 -0
- package/src/source-entries.ts +502 -0
- package/src/step-bodies.ts +55 -0
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +25 -0
- package/src/validate-cel-context.ts +60 -18
- package/src/validate-exports.ts +185 -0
- package/src/validate-extends.ts +43 -0
- package/src/validate-module-artifact.ts +46 -3
- package/src/validate-native-entries.ts +147 -0
- package/src/validate-observed-state.ts +36 -7
- package/src/validate-provider-coherence.ts +17 -127
- package/src/validate-reference-forms.ts +2 -3
- package/src/validate-references.ts +151 -66
- package/src/validate-scope-reach.ts +213 -0
- package/src/validate-source-entries.ts +120 -0
- package/src/validate-template-body.ts +274 -0
- package/src/validate-throws-coverage.ts +24 -31
- package/src/value-type-keyword.ts +35 -9
- package/dist/validate-template-dispatch.d.ts +0 -27
- package/dist/validate-template-dispatch.d.ts.map +0 -1
- package/dist/validate-template-dispatch.js +0 -95
- package/src/validate-template-dispatch.ts +0 -99
|
@@ -1,5 +1,23 @@
|
|
|
1
|
+
import { resolveLocalRef } from "./manifest-navigation.js";
|
|
1
2
|
import { readRefSlot } from "./ref-slot.js";
|
|
3
|
+
import { readStepSlot } from "./step-slot.js";
|
|
2
4
|
export { readRefSlot, isRefSlot, hasDeclaredUse } from "./ref-slot.js";
|
|
5
|
+
/** The slot an entry records, as `readRefSlot` read it. */
|
|
6
|
+
export function refSlotOfEntry(entry) {
|
|
7
|
+
const slot = {
|
|
8
|
+
kinds: entry.refs,
|
|
9
|
+
uses: entry.uses,
|
|
10
|
+
inline: entry.inline === true,
|
|
11
|
+
valueBranches: entry.valueBranches ?? [],
|
|
12
|
+
};
|
|
13
|
+
if (entry.useCases)
|
|
14
|
+
slot.useCases = entry.useCases;
|
|
15
|
+
if (entry.inputs !== undefined)
|
|
16
|
+
slot.inputs = entry.inputs;
|
|
17
|
+
if (entry.throwsThrough)
|
|
18
|
+
slot.throwsThrough = true;
|
|
19
|
+
return slot;
|
|
20
|
+
}
|
|
3
21
|
export function isRefEntry(entry) {
|
|
4
22
|
return "refs" in entry;
|
|
5
23
|
}
|
|
@@ -121,9 +139,10 @@ export function resolveFieldValues(obj, path) {
|
|
|
121
139
|
*/
|
|
122
140
|
export function buildReferenceFieldMap(schema) {
|
|
123
141
|
const map = new Map();
|
|
142
|
+
const sink = injectionSink(map);
|
|
124
143
|
if (schema.properties) {
|
|
125
144
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
126
|
-
traverseNode(propSchema, key,
|
|
145
|
+
traverseNode(propSchema, key, sink, schema);
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
148
|
return map;
|
|
@@ -140,32 +159,175 @@ export function collectRefs(node) {
|
|
|
140
159
|
* hid behind the schema-from indirection. */
|
|
141
160
|
export function buildFieldMapAtPath(schema, pathPrefix) {
|
|
142
161
|
const map = new Map();
|
|
143
|
-
traverseNode(schema, pathPrefix, map, schema);
|
|
162
|
+
traverseNode(schema, pathPrefix, injectionSink(map), schema);
|
|
144
163
|
return map;
|
|
145
164
|
}
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
const drivenSlotMaps = new WeakMap();
|
|
166
|
+
/** The resource envelope: present on every resource document, and never
|
|
167
|
+
* configuration a kind's root `additionalProperties` describes. */
|
|
168
|
+
const RESOURCE_ENVELOPE_KEYS = ["kind", "metadata"];
|
|
169
|
+
/**
|
|
170
|
+
* Every slot through which a resource of this schema DRIVES another — the
|
|
171
|
+
* reference field map's own traversal, plus what a dispatch analysis needs and
|
|
172
|
+
* the injection surface must not have:
|
|
173
|
+
*
|
|
174
|
+
* - local `$ref` is followed, resolved against the root. The injection map
|
|
175
|
+
* stops there so a step's `invoke` never becomes an injection site; this map
|
|
176
|
+
* stops AT a step body instead, which removes that reason. A reference back
|
|
177
|
+
* to a node already on the descent is recorded as a back-edge rather than
|
|
178
|
+
* unrolled, so a recursive shape stays finite here and is followed as deep as
|
|
179
|
+
* the data goes by `forEachDrivenSlot`;
|
|
180
|
+
* - a step body is recorded and not descended — the step traversal owns
|
|
181
|
+
* everything below it;
|
|
182
|
+
* - the root's own variant branches and `additionalProperties` are walked, not
|
|
183
|
+
* only its `properties`;
|
|
184
|
+
* - a path keeps EVERY slot any branch declares there, where the injection map
|
|
185
|
+
* keeps the last one written (see {@link DrivenPath.refs});
|
|
186
|
+
* - each `{}` path records the keys its `additionalProperties` does not cover.
|
|
187
|
+
*
|
|
188
|
+
* Shared by every throws question (a kind's `inherit` union, a scope list's
|
|
189
|
+
* denominator, catch-scope enclosure, and whether `inherit` is legal at all),
|
|
190
|
+
* so none can reach a slot another cannot. Memoized per schema object.
|
|
191
|
+
*/
|
|
192
|
+
export function buildDrivenSlotMap(schema) {
|
|
193
|
+
const cached = drivenSlotMaps.get(schema);
|
|
194
|
+
if (cached)
|
|
195
|
+
return cached;
|
|
196
|
+
const slots = { paths: new Map(), declaredKeys: new Map(), drives: false };
|
|
197
|
+
traverseNode(schema, "", drivenSink(slots), schema);
|
|
198
|
+
drivenSlotMaps.set(schema, slots);
|
|
199
|
+
return slots;
|
|
200
|
+
}
|
|
201
|
+
const joinPath = (path, key) => (path ? `${path}.${key}` : key);
|
|
202
|
+
/** The injection map: one entry per path, the last one written. Step bodies,
|
|
203
|
+
* back-edges and map-value keys are the driven map's alone. */
|
|
204
|
+
function injectionSink(map) {
|
|
205
|
+
return {
|
|
206
|
+
driven: false,
|
|
207
|
+
ref: (path, entry) => {
|
|
208
|
+
map.set(path, entry);
|
|
209
|
+
},
|
|
210
|
+
stop: (path, entry) => {
|
|
211
|
+
map.set(path, entry);
|
|
212
|
+
},
|
|
213
|
+
step: () => { },
|
|
214
|
+
recurse: () => { },
|
|
215
|
+
mapValue: () => { },
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function drivenSink(slots) {
|
|
219
|
+
const recorded = new Map();
|
|
220
|
+
const at = (path) => {
|
|
221
|
+
let entry = slots.paths.get(path);
|
|
222
|
+
if (!entry) {
|
|
223
|
+
entry = { refs: [], steps: [], recurse: [] };
|
|
224
|
+
slots.paths.set(path, entry);
|
|
225
|
+
}
|
|
226
|
+
return entry;
|
|
227
|
+
};
|
|
228
|
+
// One schema node reached twice at one path (two branches `$ref`-ing one
|
|
229
|
+
// definition) is one slot.
|
|
230
|
+
const firstTime = (path, node) => {
|
|
231
|
+
let nodes = recorded.get(path);
|
|
232
|
+
if (!nodes)
|
|
233
|
+
recorded.set(path, (nodes = new Set()));
|
|
234
|
+
if (nodes.has(node))
|
|
235
|
+
return false;
|
|
236
|
+
nodes.add(node);
|
|
237
|
+
return true;
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
driven: true,
|
|
241
|
+
ref: (path, entry, node) => {
|
|
242
|
+
if (!firstTime(path, node))
|
|
243
|
+
return;
|
|
244
|
+
at(path).refs.push(entry);
|
|
245
|
+
slots.drives = true;
|
|
246
|
+
},
|
|
247
|
+
stop: () => { },
|
|
248
|
+
step: (path, step, node) => {
|
|
249
|
+
if (!firstTime(path, node))
|
|
250
|
+
return;
|
|
251
|
+
at(path).steps.push(step);
|
|
252
|
+
slots.drives = true;
|
|
253
|
+
},
|
|
254
|
+
recurse: (path, to) => {
|
|
255
|
+
const entry = at(path);
|
|
256
|
+
if (!entry.recurse.includes(to))
|
|
257
|
+
entry.recurse.push(to);
|
|
258
|
+
},
|
|
259
|
+
mapValue: (mapPath, declared) => {
|
|
260
|
+
const previous = slots.declaredKeys.get(mapPath);
|
|
261
|
+
slots.declaredKeys.set(mapPath, new Set(previous ? declared.filter((key) => previous.has(key)) : declared));
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/** Follow a local `$ref` in driven mode. `onStack` maps each node on the
|
|
266
|
+
* current descent to the path it was entered at: re-entering one records a
|
|
267
|
+
* back-edge to that path, and a cycle that made no structural progress (same
|
|
268
|
+
* path) records nothing, since its body is already recorded there. */
|
|
269
|
+
function followLocalRef(node, path, sink, root, onStack) {
|
|
270
|
+
if (!root)
|
|
271
|
+
return;
|
|
272
|
+
const target = resolveLocalRef(node, root);
|
|
273
|
+
if (!target || target === node)
|
|
274
|
+
return;
|
|
275
|
+
const entered = onStack.get(target);
|
|
276
|
+
if (entered !== undefined) {
|
|
277
|
+
if (entered !== path)
|
|
278
|
+
sink.recurse(path, entered);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
onStack.set(target, path);
|
|
282
|
+
try {
|
|
283
|
+
traverseNode(target, path, sink, root, onStack);
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
onStack.delete(target);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** `owner.additionalProperties`, the schema of every value in an open-keyed
|
|
290
|
+
* object. JSON Schema applies it only to keys `owner` does not declare, which
|
|
291
|
+
* the driven map records so a walk skips them. */
|
|
292
|
+
function traverseMapValue(owner, path, sink, root, onStack) {
|
|
293
|
+
const valueSchema = owner.additionalProperties;
|
|
294
|
+
if (!valueSchema || typeof valueSchema !== "object" || Array.isArray(valueSchema))
|
|
295
|
+
return;
|
|
296
|
+
const mapPath = joinPath(path, "{}");
|
|
297
|
+
const declared = owner.properties && typeof owner.properties === "object" ? Object.keys(owner.properties) : [];
|
|
298
|
+
sink.mapValue(mapPath, path === "" ? [...declared, ...RESOURCE_ENVELOPE_KEYS] : declared);
|
|
299
|
+
traverseNode(valueSchema, mapPath, sink, root, onStack);
|
|
300
|
+
}
|
|
301
|
+
function traverseNode(node, path, sink, root, onStack = new Map()) {
|
|
302
|
+
const driven = sink.driven;
|
|
303
|
+
// Local `$ref` is intentionally NOT followed here. This map is the kernel's
|
|
148
304
|
// Phase-5 injection surface: descending into shared `$defs` (notably
|
|
149
305
|
// `Run.Sequence`'s `step` definition) would make every step's `invoke` an
|
|
150
306
|
// injection site, and step slots resolve at dispatch — injecting there is
|
|
151
307
|
// unwanted regardless of tracing (the original dispatcher-bypass blocker
|
|
152
308
|
// has since shipped via the `REF_IDENTITY` stamp). Static analysis is NOT
|
|
153
309
|
// limited by this stop: the call graph (`call-graph.ts`) reads step slots
|
|
154
|
-
// from the item schema itself and scans the value tree,
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
if (typeof node?.$ref === "string")
|
|
310
|
+
// from the item schema itself and scans the value tree, and the driven-slot
|
|
311
|
+
// mode (`buildDrivenSlotMap`) follows the reference.
|
|
312
|
+
if (typeof node?.$ref === "string" && !driven)
|
|
158
313
|
return;
|
|
159
314
|
// Scope slot — record and stop; do not recurse into scope contents
|
|
160
315
|
if ("x-telo-scope" in node) {
|
|
161
|
-
|
|
316
|
+
sink.stop(path, { scope: node["x-telo-scope"] });
|
|
162
317
|
return;
|
|
163
318
|
}
|
|
164
319
|
// Schema-from slot — record and stop; no further traversal needed
|
|
165
320
|
if ("x-telo-schema-from" in node) {
|
|
166
|
-
|
|
321
|
+
sink.stop(path, { schemaFrom: node["x-telo-schema-from"] });
|
|
167
322
|
return;
|
|
168
323
|
}
|
|
324
|
+
if (driven) {
|
|
325
|
+
const step = readStepSlot(node);
|
|
326
|
+
if (step) {
|
|
327
|
+
sink.step(path, step, node);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
169
331
|
// Reference slot (direct, via a `kind:` list, or via anyOf)
|
|
170
332
|
const slot = readRefSlot(node);
|
|
171
333
|
if (slot && slot.kinds.length > 0) {
|
|
@@ -184,7 +346,9 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
|
|
|
184
346
|
entry.context = node["x-telo-context"];
|
|
185
347
|
if (slot.inline)
|
|
186
348
|
entry.inline = true;
|
|
187
|
-
|
|
349
|
+
if (slot.throwsThrough)
|
|
350
|
+
entry.throwsThrough = true;
|
|
351
|
+
sink.ref(path, entry, node);
|
|
188
352
|
// A node can mix item-level ref branches (a bare string / `{kind, name}`)
|
|
189
353
|
// with object branches that carry their OWN nested refs — e.g. Application
|
|
190
354
|
// `targets`: a bare ref vs inline `{ invoke }` vs gated `{ ref }`. Descend
|
|
@@ -198,21 +362,24 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
|
|
|
198
362
|
for (const variant of variants) {
|
|
199
363
|
if (!variant || typeof variant !== "object")
|
|
200
364
|
continue;
|
|
201
|
-
traverseVariant(variant, path,
|
|
365
|
+
traverseVariant(variant, path, sink, root, onStack);
|
|
202
366
|
}
|
|
203
367
|
}
|
|
204
368
|
return;
|
|
205
369
|
}
|
|
206
|
-
|
|
370
|
+
// Reached only in driven mode — the injection map stopped at the top.
|
|
371
|
+
if (typeof node?.$ref === "string") {
|
|
372
|
+
followLocalRef(node, path, sink, root, onStack);
|
|
207
373
|
return;
|
|
374
|
+
}
|
|
208
375
|
// Array — recurse into items
|
|
209
376
|
if (node.type === "array" && node.items) {
|
|
210
|
-
traverseNode(node.items, path + "[]",
|
|
377
|
+
traverseNode(node.items, path + "[]", sink, root, onStack);
|
|
211
378
|
}
|
|
212
379
|
// Object — recurse into properties
|
|
213
380
|
if (node.properties) {
|
|
214
381
|
for (const [key, propSchema] of Object.entries(node.properties)) {
|
|
215
|
-
traverseNode(propSchema,
|
|
382
|
+
traverseNode(propSchema, joinPath(path, key), sink, root, onStack);
|
|
216
383
|
}
|
|
217
384
|
}
|
|
218
385
|
// Variant branches — descend into every alternative's properties / items.
|
|
@@ -221,10 +388,11 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
|
|
|
221
388
|
// slots inside the branch. Walking each branch surfaces those slots into
|
|
222
389
|
// the field map so downstream passes (ref validation, sentinel
|
|
223
390
|
// resolution, dependency graph) cover them without a runtime fallback.
|
|
224
|
-
// The same field path may be added by multiple branches
|
|
225
|
-
// assignment
|
|
226
|
-
// share the same ref/context configuration
|
|
227
|
-
//
|
|
391
|
+
// The same field path may be added by multiple branches. The injection map
|
|
392
|
+
// keeps the later assignment, which is fine for injection — branches with
|
|
393
|
+
// the same field path share the same ref/context configuration. The driven
|
|
394
|
+
// map keeps every one, because which branch applies decides what a
|
|
395
|
+
// dispatch can throw.
|
|
228
396
|
for (const variantKey of ["oneOf", "anyOf", "allOf"]) {
|
|
229
397
|
const variants = node[variantKey];
|
|
230
398
|
if (!Array.isArray(variants))
|
|
@@ -232,34 +400,32 @@ function traverseNode(node, path, map, root, visitedRefs = new Set()) {
|
|
|
232
400
|
for (const variant of variants) {
|
|
233
401
|
if (!variant || typeof variant !== "object")
|
|
234
402
|
continue;
|
|
235
|
-
traverseVariant(variant, path,
|
|
403
|
+
traverseVariant(variant, path, sink, root, onStack);
|
|
236
404
|
}
|
|
237
405
|
}
|
|
238
406
|
// Map — `additionalProperties: { ... }` describes every value in an
|
|
239
407
|
// open-keyed object. Encoder refs nested inside `content[mime]` map
|
|
240
408
|
// entries reach Phase 5 through this branch.
|
|
241
|
-
|
|
242
|
-
typeof node.additionalProperties === "object" &&
|
|
243
|
-
!Array.isArray(node.additionalProperties)) {
|
|
244
|
-
traverseNode(node.additionalProperties, `${path}.{}`, map, root, visitedRefs);
|
|
245
|
-
}
|
|
409
|
+
traverseMapValue(node, path, sink, root, onStack);
|
|
246
410
|
}
|
|
247
411
|
/** Walk a single variant of a `oneOf` / `anyOf` / `allOf` branch. Only
|
|
248
412
|
* the properties / items / map slots are followed — collectRefs at the
|
|
249
413
|
* variant root is handled by the parent's `collectRefs(node)` already
|
|
250
|
-
* (anyOf of x-telo-ref branches is the canonical multi-ref shape).
|
|
251
|
-
|
|
414
|
+
* (anyOf of x-telo-ref branches is the canonical multi-ref shape). In
|
|
415
|
+
* driven mode a `$ref` branch is followed as a node in its own right, since
|
|
416
|
+
* `readRefSlot` does not look through one. */
|
|
417
|
+
function traverseVariant(variant, path, sink, root, onStack = new Map()) {
|
|
418
|
+
if (sink.driven && typeof variant.$ref === "string") {
|
|
419
|
+
followLocalRef(variant, path, sink, root, onStack);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
252
422
|
if (variant.properties) {
|
|
253
423
|
for (const [key, propSchema] of Object.entries(variant.properties)) {
|
|
254
|
-
traverseNode(propSchema,
|
|
424
|
+
traverseNode(propSchema, joinPath(path, key), sink, root, onStack);
|
|
255
425
|
}
|
|
256
426
|
}
|
|
257
427
|
if (variant.type === "array" && variant.items) {
|
|
258
|
-
traverseNode(variant.items, path + "[]",
|
|
259
|
-
}
|
|
260
|
-
if (variant.additionalProperties &&
|
|
261
|
-
typeof variant.additionalProperties === "object" &&
|
|
262
|
-
!Array.isArray(variant.additionalProperties)) {
|
|
263
|
-
traverseNode(variant.additionalProperties, `${path}.{}`, map, root, visitedRefs);
|
|
428
|
+
traverseNode(variant.items, path + "[]", sink, root, onStack);
|
|
264
429
|
}
|
|
430
|
+
traverseMapValue(variant, path, sink, root, onStack);
|
|
265
431
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each module publishes, and the two ways a set of destinations can be
|
|
3
|
+
* inconsistent.
|
|
4
|
+
*
|
|
5
|
+
* A destination is the module's resolved registry base plus its own directory
|
|
6
|
+
* name. That rule held silently for as long as every module sat at one depth
|
|
7
|
+
* under one base with a unique directory name; per-subtree bases remove that
|
|
8
|
+
* coincidence, so both failures it was hiding become reachable and are checked
|
|
9
|
+
* here — **at plan time, before any payload is built**, because the payload
|
|
10
|
+
* builder's own refusal speaks about a manifest published to two places rather
|
|
11
|
+
* than about the workspace file that said so.
|
|
12
|
+
*
|
|
13
|
+
* Pure data in, diagnostics out: the destinations are already computed for
|
|
14
|
+
* `telo release order`, so one derivation feeds that payload and both checks.
|
|
15
|
+
*/
|
|
16
|
+
import type { ModuleKey } from "./fragment.js";
|
|
17
|
+
import type { ReleaseDiagnostic } from "./release-plan.js";
|
|
18
|
+
export interface ModuleDestination {
|
|
19
|
+
readonly key: ModuleKey;
|
|
20
|
+
/** `<registry>/<the module's own directory name>`. */
|
|
21
|
+
readonly destination: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A relative import between two workspace modules, with both answers about
|
|
25
|
+
* where the target publishes.
|
|
26
|
+
*
|
|
27
|
+
* `derived` is what the importer's own destination yields when the import path
|
|
28
|
+
* is applied to it — the transport's rule, and what ref canonicalization writes
|
|
29
|
+
* into the published manifest. `assigned` is what discovery independently gives
|
|
30
|
+
* that module.
|
|
31
|
+
*/
|
|
32
|
+
export interface ImportDestination {
|
|
33
|
+
readonly from: ModuleKey;
|
|
34
|
+
readonly to: ModuleKey;
|
|
35
|
+
readonly derived: string;
|
|
36
|
+
readonly assigned: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Two modules must not resolve to one ref.
|
|
40
|
+
*
|
|
41
|
+
* Nothing else catches it: the payload builder is keyed by manifest, so it sees
|
|
42
|
+
* one module claimed by two destinations and never two modules claiming one,
|
|
43
|
+
* while the ledger keys by module — both entries would record digests for a
|
|
44
|
+
* single published artifact and reconciliation could never settle.
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkDestinationCollisions(destinations: readonly ModuleDestination[]): ReleaseDiagnostic[];
|
|
47
|
+
/**
|
|
48
|
+
* A relative import is valid only where the importer's derived ref for its
|
|
49
|
+
* target equals the destination that module is independently assigned.
|
|
50
|
+
*
|
|
51
|
+
* Equal registries are necessary and not sufficient — two modules under one base
|
|
52
|
+
* at different directory depths derive differently too. Making the builder take
|
|
53
|
+
* the assigned destination instead is not the fix: it would put a ref in the
|
|
54
|
+
* artifact that the transport's resolution rule does not produce, so the
|
|
55
|
+
* manifest would say one thing and every consumer resolving relatively would
|
|
56
|
+
* compute another.
|
|
57
|
+
*/
|
|
58
|
+
export declare function checkImportDestinations(edges: readonly ImportDestination[]): ReleaseDiagnostic[];
|
|
59
|
+
//# sourceMappingURL=destinations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"destinations.d.ts","sourceRoot":"","sources":["../../src/release/destinations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,SAAS,iBAAiB,EAAE,GACzC,iBAAiB,EAAE,CAsBrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,iBAAiB,EAAE,GAClC,iBAAiB,EAAE,CAgBrB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each module publishes, and the two ways a set of destinations can be
|
|
3
|
+
* inconsistent.
|
|
4
|
+
*
|
|
5
|
+
* A destination is the module's resolved registry base plus its own directory
|
|
6
|
+
* name. That rule held silently for as long as every module sat at one depth
|
|
7
|
+
* under one base with a unique directory name; per-subtree bases remove that
|
|
8
|
+
* coincidence, so both failures it was hiding become reachable and are checked
|
|
9
|
+
* here — **at plan time, before any payload is built**, because the payload
|
|
10
|
+
* builder's own refusal speaks about a manifest published to two places rather
|
|
11
|
+
* than about the workspace file that said so.
|
|
12
|
+
*
|
|
13
|
+
* Pure data in, diagnostics out: the destinations are already computed for
|
|
14
|
+
* `telo release order`, so one derivation feeds that payload and both checks.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Two modules must not resolve to one ref.
|
|
18
|
+
*
|
|
19
|
+
* Nothing else catches it: the payload builder is keyed by manifest, so it sees
|
|
20
|
+
* one module claimed by two destinations and never two modules claiming one,
|
|
21
|
+
* while the ledger keys by module — both entries would record digests for a
|
|
22
|
+
* single published artifact and reconciliation could never settle.
|
|
23
|
+
*/
|
|
24
|
+
export function checkDestinationCollisions(destinations) {
|
|
25
|
+
const byDestination = new Map();
|
|
26
|
+
for (const { key, destination } of destinations) {
|
|
27
|
+
const sharing = byDestination.get(destination);
|
|
28
|
+
if (sharing)
|
|
29
|
+
sharing.push(key);
|
|
30
|
+
else
|
|
31
|
+
byDestination.set(destination, [key]);
|
|
32
|
+
}
|
|
33
|
+
const diagnostics = [];
|
|
34
|
+
for (const [destination, keys] of byDestination) {
|
|
35
|
+
if (keys.length < 2)
|
|
36
|
+
continue;
|
|
37
|
+
diagnostics.push({
|
|
38
|
+
severity: "error",
|
|
39
|
+
code: "DESTINATION_COLLISION",
|
|
40
|
+
message: `${keys.sort().join(" and ")} both publish to '${destination}'. A module's ref is its ` +
|
|
41
|
+
`registry base plus its own directory name, so they would overwrite one artifact and ` +
|
|
42
|
+
`their ledger entries could never reconcile. Rename one directory, or give one of the ` +
|
|
43
|
+
`subtrees its own 'registry:' in telo-workspace.yaml.`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return diagnostics;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A relative import is valid only where the importer's derived ref for its
|
|
50
|
+
* target equals the destination that module is independently assigned.
|
|
51
|
+
*
|
|
52
|
+
* Equal registries are necessary and not sufficient — two modules under one base
|
|
53
|
+
* at different directory depths derive differently too. Making the builder take
|
|
54
|
+
* the assigned destination instead is not the fix: it would put a ref in the
|
|
55
|
+
* artifact that the transport's resolution rule does not produce, so the
|
|
56
|
+
* manifest would say one thing and every consumer resolving relatively would
|
|
57
|
+
* compute another.
|
|
58
|
+
*/
|
|
59
|
+
export function checkImportDestinations(edges) {
|
|
60
|
+
const diagnostics = [];
|
|
61
|
+
for (const edge of edges) {
|
|
62
|
+
if (edge.derived === edge.assigned)
|
|
63
|
+
continue;
|
|
64
|
+
diagnostics.push({
|
|
65
|
+
severity: "error",
|
|
66
|
+
code: "IMPORT_DESTINATION_CONFLICT",
|
|
67
|
+
message: `${edge.from} imports ${edge.to} by relative path, which canonicalizes to ` +
|
|
68
|
+
`'${edge.derived}' — but ${edge.to} publishes to '${edge.assigned}'. Publishing rewrites ` +
|
|
69
|
+
`the import to the ref its own path yields, so the artifact would name a module nobody ` +
|
|
70
|
+
`pushes. Give the two the same registry base and the same directory depth, or make it a ` +
|
|
71
|
+
`pinned remote import, which is what a dependency across a publish boundary is.`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return diagnostics;
|
|
75
|
+
}
|
package/dist/release/index.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export type { LayerChange, LayerDigests } from "./payload-digest.js";
|
|
|
21
21
|
export { orderByImports, planRelease } from "./release-plan.js";
|
|
22
22
|
export type { ArtifactKind, BumpReason, ChangelogEntry, ModuleEvidence, PlannedModule, ReleaseDiagnostic, ReleaseEvidence, ReleasePlan, } from "./release-plan.js";
|
|
23
23
|
export { VersionStampError, readManifestVersion, stampCrateVersion, stampManifestVersion, stampSelfNpmPins, stampPackageVersion, } from "./version-stamp.js";
|
|
24
|
-
export {
|
|
25
|
-
export type {
|
|
24
|
+
export { checkDestinationCollisions, checkImportDestinations } from "./destinations.js";
|
|
25
|
+
export type { ImportDestination, ModuleDestination } from "./destinations.js";
|
|
26
|
+
export { DEFAULT_ENV_FILES, DEFAULT_RELEASE_IGNORE, WORKSPACE_FILENAME, WorkspaceConfigError, diagnosticsFor, hasError, matchesPatterns, readWorkspaceConfig, requireReleaseSettings, settingsForModule, } from "./workspace-config.js";
|
|
27
|
+
export type { EnvSettings, ModuleEntry, ModuleSettings, PatternMatch, ReleaseSettings, WorkspaceConfig, WorkspaceDiagnostic, WorkspaceDiagnosticCode, WorkspaceRead, } from "./workspace-config.js";
|
|
28
|
+
export { MODULE_ENTRY_KEYS, WORKSPACE_BLOCKS, WORKSPACE_SCHEMA, } from "./workspace-schema.js";
|
|
29
|
+
export type { WorkspaceBlockSchema, WorkspaceKeySchema } from "./workspace-schema.js";
|
|
26
30
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,QAAQ,GACT,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpG,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnG,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,gBAAgB,EAChB,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,QAAQ,GACT,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACpG,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnG,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,gBAAgB,EAChB,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACxF,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACd,YAAY,EACZ,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/release/index.js
CHANGED
|
@@ -15,4 +15,6 @@ export { EMPTY_LEDGER, LEDGER_PATH, LedgerError, parseLedger, serializeLedger }
|
|
|
15
15
|
export { LOCALLY_DERIVED_LAYERS, MANIFEST_LAYER, diffLayerDigests, layerDigestKey, } from "./payload-digest.js";
|
|
16
16
|
export { orderByImports, planRelease } from "./release-plan.js";
|
|
17
17
|
export { VersionStampError, readManifestVersion, stampCrateVersion, stampManifestVersion, stampSelfNpmPins, stampPackageVersion, } from "./version-stamp.js";
|
|
18
|
-
export {
|
|
18
|
+
export { checkDestinationCollisions, checkImportDestinations } from "./destinations.js";
|
|
19
|
+
export { DEFAULT_ENV_FILES, DEFAULT_RELEASE_IGNORE, WORKSPACE_FILENAME, WorkspaceConfigError, diagnosticsFor, hasError, matchesPatterns, readWorkspaceConfig, requireReleaseSettings, settingsForModule, } from "./workspace-config.js";
|
|
20
|
+
export { MODULE_ENTRY_KEYS, WORKSPACE_BLOCKS, WORKSPACE_SCHEMA, } from "./workspace-schema.js";
|
package/dist/release/ledger.d.ts
CHANGED
|
@@ -14,10 +14,22 @@
|
|
|
14
14
|
* drift: it means nothing is published, which is the correct reading for a
|
|
15
15
|
* module that has never shipped.
|
|
16
16
|
*
|
|
17
|
-
* **
|
|
18
|
-
* source writes the destination into the manifest layer — so
|
|
19
|
-
*
|
|
20
|
-
* another
|
|
17
|
+
* **Every entry records its own registry base**, because canonicalizing a
|
|
18
|
+
* relative `imports:` source writes the destination into the manifest layer — so
|
|
19
|
+
* each digest is a digest *against that base*, and comparing it to one taken
|
|
20
|
+
* against another would be comparing two different artifacts.
|
|
21
|
+
*
|
|
22
|
+
* Per entry rather than one top-level base with per-entry deltas: a workspace
|
|
23
|
+
* whose `release.modules` entries each author a destination has no meaningful
|
|
24
|
+
* top-level base, an absent one already means *nothing has been published yet*,
|
|
25
|
+
* and the agreement check returns early on that — so the multi-destination
|
|
26
|
+
* workspace would be the one whose bases are never compared. The file is
|
|
27
|
+
* generated and never hand-maintained, so the redundancy costs nothing and it
|
|
28
|
+
* removes the "differs from what?" question and the absent-versus-unknown
|
|
29
|
+
* conflation together. A **top-level `registry:` is a legacy form the reader
|
|
30
|
+
* accepts and never writes**, applied to every entry: the credential-free PR
|
|
31
|
+
* gate reads whatever ledger is committed on the branch, and that stays in the
|
|
32
|
+
* old shape until a release regenerates it.
|
|
21
33
|
*/
|
|
22
34
|
import { type ModuleKey } from "./fragment.js";
|
|
23
35
|
import type { LayerDigests } from "./payload-digest.js";
|
|
@@ -26,16 +38,13 @@ export interface LedgerEntry {
|
|
|
26
38
|
/** The version these digests were taken at — the tag the artifact published
|
|
27
39
|
* under. */
|
|
28
40
|
readonly version: string;
|
|
41
|
+
/** The publish destination base these digests were taken against
|
|
42
|
+
* (`oci://ghcr.io/telorun`). Absent only in a ledger written before the base
|
|
43
|
+
* was recorded at all. */
|
|
44
|
+
readonly registry?: string;
|
|
29
45
|
readonly layers: LayerDigests;
|
|
30
46
|
}
|
|
31
47
|
export interface Ledger {
|
|
32
|
-
/**
|
|
33
|
-
* The publish destination base the digests were taken against
|
|
34
|
-
* (`oci://ghcr.io/telorun`). Absent in a workspace that has published nothing
|
|
35
|
-
* yet, which is why it is optional rather than required — but a `check` that
|
|
36
|
-
* has entries and no base cannot reproduce them, and says so.
|
|
37
|
-
*/
|
|
38
|
-
readonly registry?: string;
|
|
39
48
|
readonly modules: ReadonlyMap<ModuleKey, LedgerEntry>;
|
|
40
49
|
}
|
|
41
50
|
export declare class LedgerError extends Error {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/release/ledger.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/release/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,eAAO,MAAM,WAAW,yBAAyB,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B;iBACa;IACb,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;+BAE2B;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;CACvD;AAED,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAEzC,eAAO,MAAM,YAAY,EAAE,MAA+B,CAAC;AAE3D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAmF/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBtD"}
|
package/dist/release/ledger.js
CHANGED
|
@@ -14,10 +14,22 @@
|
|
|
14
14
|
* drift: it means nothing is published, which is the correct reading for a
|
|
15
15
|
* module that has never shipped.
|
|
16
16
|
*
|
|
17
|
-
* **
|
|
18
|
-
* source writes the destination into the manifest layer — so
|
|
19
|
-
*
|
|
20
|
-
* another
|
|
17
|
+
* **Every entry records its own registry base**, because canonicalizing a
|
|
18
|
+
* relative `imports:` source writes the destination into the manifest layer — so
|
|
19
|
+
* each digest is a digest *against that base*, and comparing it to one taken
|
|
20
|
+
* against another would be comparing two different artifacts.
|
|
21
|
+
*
|
|
22
|
+
* Per entry rather than one top-level base with per-entry deltas: a workspace
|
|
23
|
+
* whose `release.modules` entries each author a destination has no meaningful
|
|
24
|
+
* top-level base, an absent one already means *nothing has been published yet*,
|
|
25
|
+
* and the agreement check returns early on that — so the multi-destination
|
|
26
|
+
* workspace would be the one whose bases are never compared. The file is
|
|
27
|
+
* generated and never hand-maintained, so the redundancy costs nothing and it
|
|
28
|
+
* removes the "differs from what?" question and the absent-versus-unknown
|
|
29
|
+
* conflation together. A **top-level `registry:` is a legacy form the reader
|
|
30
|
+
* accepts and never writes**, applied to every entry: the credential-free PR
|
|
31
|
+
* gate reads whatever ledger is committed on the branch, and that stays in the
|
|
32
|
+
* old shape until a release regenerates it.
|
|
21
33
|
*/
|
|
22
34
|
import { Document, parseDocument } from "yaml";
|
|
23
35
|
import { isReleaseVersion } from "./bump-level.js";
|
|
@@ -45,13 +57,14 @@ export function parseLedger(text, where) {
|
|
|
45
57
|
throw new LedgerError(`${where}: unknown field '${key}'. The ledger carries 'registry:' and 'modules:'.`);
|
|
46
58
|
}
|
|
47
59
|
}
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
// The legacy top-level base: read and applied to every entry, never written.
|
|
61
|
+
const inherited = record.registry;
|
|
62
|
+
if (inherited !== undefined && typeof inherited !== "string") {
|
|
50
63
|
throw new LedgerError(`${where}: 'registry' must be the publish destination base, as a string.`);
|
|
51
64
|
}
|
|
52
65
|
const rawModules = record.modules;
|
|
53
66
|
if (rawModules === undefined || rawModules === null)
|
|
54
|
-
return {
|
|
67
|
+
return { modules: new Map() };
|
|
55
68
|
if (typeof rawModules !== "object" || Array.isArray(rawModules)) {
|
|
56
69
|
throw new LedgerError(`${where}: 'modules' must be a mapping of module path to entry.`);
|
|
57
70
|
}
|
|
@@ -61,6 +74,16 @@ export function parseLedger(text, where) {
|
|
|
61
74
|
throw new LedgerError(`${where}: entry '${key}' must be a mapping.`);
|
|
62
75
|
}
|
|
63
76
|
const entry = raw;
|
|
77
|
+
for (const field of Object.keys(entry)) {
|
|
78
|
+
if (field !== "version" && field !== "registry" && field !== "layers") {
|
|
79
|
+
throw new LedgerError(`${where}: entry '${key}' has unknown field '${field}'. An entry carries 'version:', ` +
|
|
80
|
+
`'registry:' and 'layers:'.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (entry.registry !== undefined && typeof entry.registry !== "string") {
|
|
84
|
+
throw new LedgerError(`${where}: entry '${key}' has a non-string 'registry'. It is the publish destination base ` +
|
|
85
|
+
`the digests beside it were taken against.`);
|
|
86
|
+
}
|
|
64
87
|
if (!isReleaseVersion(entry.version)) {
|
|
65
88
|
throw new LedgerError(`${where}: entry '${key}' has no major.minor.patch 'version'. The ledger records the ` +
|
|
66
89
|
`version each digest was taken at; without it the digest says nothing.`);
|
|
@@ -78,9 +101,14 @@ export function parseLedger(text, where) {
|
|
|
78
101
|
layers[layer] = digest;
|
|
79
102
|
}
|
|
80
103
|
}
|
|
81
|
-
|
|
104
|
+
const registry = entry.registry ?? inherited;
|
|
105
|
+
modules.set(normalizeModuleKey(key), {
|
|
106
|
+
version: entry.version,
|
|
107
|
+
...(registry ? { registry } : {}),
|
|
108
|
+
layers,
|
|
109
|
+
});
|
|
82
110
|
}
|
|
83
|
-
return {
|
|
111
|
+
return { modules };
|
|
84
112
|
}
|
|
85
113
|
/**
|
|
86
114
|
* Render the ledger.
|
|
@@ -96,12 +124,13 @@ export function serializeLedger(ledger) {
|
|
|
96
124
|
const layers = {};
|
|
97
125
|
for (const layer of Object.keys(entry.layers).sort())
|
|
98
126
|
layers[layer] = entry.layers[layer];
|
|
99
|
-
modules[key] = {
|
|
127
|
+
modules[key] = {
|
|
128
|
+
version: entry.version,
|
|
129
|
+
...(entry.registry ? { registry: entry.registry } : {}),
|
|
130
|
+
layers,
|
|
131
|
+
};
|
|
100
132
|
}
|
|
101
|
-
const doc = new Document({
|
|
102
|
-
...(ledger.registry ? { registry: ledger.registry } : {}),
|
|
103
|
-
modules,
|
|
104
|
-
});
|
|
133
|
+
const doc = new Document({ modules });
|
|
105
134
|
return ("# Generated by `telo release apply` — what each module looks like as published.\n" +
|
|
106
135
|
"# A cache of the registry's answer, so a PR gate needs no credentials.\n" +
|
|
107
136
|
"# `telo release verify` reconciles it against the registry.\n" +
|