@telorun/analyzer 0.56.1 → 0.58.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 +5 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +232 -94
- package/dist/cel-environment.d.ts.map +1 -1
- package/dist/cel-environment.js +6 -3
- package/dist/definition-registry.d.ts +5 -0
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +9 -0
- package/dist/index.d.ts +14 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/invocation-contract.d.ts +21 -13
- package/dist/invocation-contract.d.ts.map +1 -1
- package/dist/invocation-contract.js +43 -46
- package/dist/loaded-types.d.ts +13 -0
- package/dist/loaded-types.d.ts.map +1 -1
- package/dist/manifest-loader.d.ts +8 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +33 -6
- package/dist/manifest-visitor.d.ts +4 -0
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +3 -3
- package/dist/migrations/driver.d.ts +69 -0
- package/dist/migrations/driver.d.ts.map +1 -0
- package/dist/migrations/driver.js +200 -0
- package/dist/migrations/entries/index.d.ts +3 -0
- package/dist/migrations/entries/index.d.ts.map +1 -0
- package/dist/migrations/entries/index.js +9 -0
- package/dist/migrations/entries/normalize-value-types.json +48 -0
- package/dist/migrations/entries/ref-slot-scalar-type.json +20 -0
- package/dist/migrations/entry-data.d.ts +25 -0
- package/dist/migrations/entry-data.d.ts.map +1 -0
- package/dist/migrations/entry-data.js +171 -0
- package/dist/migrations/index.d.ts +22 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +19 -0
- package/dist/migrations/match.d.ts +162 -0
- package/dist/migrations/match.d.ts.map +1 -0
- package/dist/migrations/match.js +295 -0
- package/dist/migrations/patch.d.ts +68 -0
- package/dist/migrations/patch.d.ts.map +1 -0
- package/dist/migrations/patch.js +236 -0
- package/dist/migrations/provenance.d.ts +25 -0
- package/dist/migrations/provenance.d.ts.map +1 -0
- package/dist/migrations/provenance.js +118 -0
- package/dist/migrations/registry.d.ts +35 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +35 -0
- package/dist/migrations/report.d.ts +28 -0
- package/dist/migrations/report.d.ts.map +1 -0
- package/dist/migrations/report.js +144 -0
- package/dist/migrations/types.d.ts +115 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +24 -0
- package/dist/migrations/yaml-edit.d.ts +21 -0
- package/dist/migrations/yaml-edit.d.ts.map +1 -0
- package/dist/migrations/yaml-edit.js +245 -0
- package/dist/module-file-claims.d.ts +65 -0
- package/dist/module-file-claims.d.ts.map +1 -0
- package/dist/module-file-claims.js +106 -0
- package/dist/parse-loaded-file.d.ts +8 -0
- package/dist/parse-loaded-file.d.ts.map +1 -1
- package/dist/parse-loaded-file.js +22 -13
- package/dist/resolve-schema-type-refs.d.ts +29 -12
- package/dist/resolve-schema-type-refs.d.ts.map +1 -1
- package/dist/resolve-schema-type-refs.js +89 -16
- package/dist/schema-compat.d.ts +35 -9
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +181 -54
- package/dist/schema-region.d.ts +33 -0
- package/dist/schema-region.d.ts.map +1 -0
- package/dist/schema-region.js +45 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/validate-cel-context.d.ts.map +1 -1
- package/dist/validate-cel-context.js +29 -3
- package/dist/validate-include-placement.d.ts +26 -0
- package/dist/validate-include-placement.d.ts.map +1 -0
- package/dist/validate-include-placement.js +67 -0
- package/dist/validate-step-inputs.d.ts +8 -1
- package/dist/validate-step-inputs.d.ts.map +1 -1
- package/dist/validate-step-inputs.js +47 -2
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +15 -12
- package/dist/validate-value-type-slots.d.ts +43 -0
- package/dist/validate-value-type-slots.d.ts.map +1 -0
- package/dist/validate-value-type-slots.js +142 -0
- package/dist/value-type-keyword.d.ts +60 -0
- package/dist/value-type-keyword.d.ts.map +1 -0
- package/dist/value-type-keyword.js +119 -0
- package/dist/yaml-source-edit.d.ts +56 -0
- package/dist/yaml-source-edit.d.ts.map +1 -0
- package/dist/yaml-source-edit.js +93 -0
- package/package.json +4 -3
- package/src/analyzer.ts +276 -129
- package/src/cel-environment.ts +6 -3
- package/src/definition-registry.ts +10 -0
- package/src/index.ts +31 -3
- package/src/invocation-contract.ts +43 -48
- package/src/loaded-types.ts +13 -0
- package/src/manifest-loader.ts +37 -6
- package/src/manifest-visitor.ts +11 -3
- package/src/migrations/driver.ts +262 -0
- package/src/migrations/entries/index.ts +10 -0
- package/src/migrations/entries/normalize-value-types.json +48 -0
- package/src/migrations/entries/ref-slot-scalar-type.json +20 -0
- package/src/migrations/entry-data.ts +207 -0
- package/src/migrations/index.ts +22 -0
- package/src/migrations/match.ts +382 -0
- package/src/migrations/patch.ts +265 -0
- package/src/migrations/provenance.ts +167 -0
- package/src/migrations/registry.ts +40 -0
- package/src/migrations/report.ts +169 -0
- package/src/migrations/types.ts +144 -0
- package/src/migrations/yaml-edit.ts +260 -0
- package/src/module-file-claims.ts +168 -0
- package/src/parse-loaded-file.ts +31 -12
- package/src/resolve-schema-type-refs.ts +93 -16
- package/src/schema-compat.ts +218 -62
- package/src/schema-region.ts +46 -0
- package/src/types.ts +54 -0
- package/src/validate-cel-context.ts +36 -3
- package/src/validate-include-placement.ts +70 -0
- package/src/validate-step-inputs.ts +56 -1
- package/src/validate-throws-coverage.ts +16 -11
- package/src/validate-value-type-slots.ts +182 -0
- package/src/value-type-keyword.ts +135 -0
- package/src/yaml-source-edit.ts +106 -0
- package/dist/binary-slot.d.ts +0 -39
- package/dist/binary-slot.d.ts.map +0 -1
- package/dist/binary-slot.js +0 -58
- package/src/binary-slot.ts +0 -71
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** Manifest migrations — the entry vocabulary.
|
|
2
|
+
*
|
|
3
|
+
* A migration is a MATCHER plus a PATCH. The two halves are at different
|
|
4
|
+
* maturities and that is deliberate (`plans/manifest-migrations.md`):
|
|
5
|
+
*
|
|
6
|
+
* - The **patch** names what it targets. Every operation has a known YAML edit
|
|
7
|
+
* form, which is what makes a migration applicable to a *file* at all and
|
|
8
|
+
* what lets the driver derive whether a quick fix exists — read straight off
|
|
9
|
+
* the verb, never declared by the author.
|
|
10
|
+
* - The **matcher** selects every occurrence of a legacy spelling, which is
|
|
11
|
+
* the half a plain patch format has none of (see `match.ts`).
|
|
12
|
+
*
|
|
13
|
+
* BOTH halves are data, and an entry contains no code at all. That is what
|
|
14
|
+
* lets one entry file be read by every kernel: a predicate expressed in one
|
|
15
|
+
* language would mean one artifact is read two ways, invisibly, since a
|
|
16
|
+
* migration that succeeds is silent. A migration that does not fit is a signal
|
|
17
|
+
* to extend the vocabulary, never to hand-write a rewrite. */
|
|
18
|
+
import type { DiagnosticSeverity } from "../types.js";
|
|
19
|
+
import type { MigrationMatch } from "./match.js";
|
|
20
|
+
/** A location inside one manifest document. Segments are mapping keys
|
|
21
|
+
* (strings) and sequence indices (numbers) — the same shape `Document.getIn`
|
|
22
|
+
* takes, so the tree applier and the YAML applier address a node identically. */
|
|
23
|
+
export type MigrationPath = ReadonlyArray<string | number>;
|
|
24
|
+
/** Rename the matched mapping entry's KEY, within its own mapping. Deliberately
|
|
25
|
+
* not JSON Patch's `move`, which relocates a value anywhere in a document
|
|
26
|
+
* across parents and replaces an occupied destination. This refuses an
|
|
27
|
+
* occupied destination instead — silently discarding a value the author wrote
|
|
28
|
+
* is exactly what the leave-it-alone invariant exists to prevent. */
|
|
29
|
+
export interface RenameKeyOperation {
|
|
30
|
+
readonly op: "rename-key";
|
|
31
|
+
readonly to: string;
|
|
32
|
+
}
|
|
33
|
+
/** Replace the value at the matched location.
|
|
34
|
+
*
|
|
35
|
+
* Exactly one of `value` / `qualify` is supplied. `qualify` prefixes the
|
|
36
|
+
* existing string — the shape a spelling rewrite that alias-qualifies a bare
|
|
37
|
+
* name needs, which a literal `value` cannot express because a patch is static
|
|
38
|
+
* data and cannot read the match. */
|
|
39
|
+
export interface SetValueOperation {
|
|
40
|
+
readonly op: "set-value";
|
|
41
|
+
readonly value?: unknown;
|
|
42
|
+
readonly qualify?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Put the matched scalar behind a templating tag (`!cel`, `!ref`, …). `tag` is
|
|
45
|
+
* the engine name without its `!`. */
|
|
46
|
+
export interface SetTagOperation {
|
|
47
|
+
readonly op: "set-tag";
|
|
48
|
+
readonly tag: string;
|
|
49
|
+
}
|
|
50
|
+
/** Insert an item into the matched sequence. `at` defaults to the end. */
|
|
51
|
+
export interface InsertItemOperation {
|
|
52
|
+
readonly op: "insert-item";
|
|
53
|
+
readonly value: unknown;
|
|
54
|
+
readonly at?: number;
|
|
55
|
+
}
|
|
56
|
+
/** Remove the matched mapping entry or sequence item. */
|
|
57
|
+
export interface RemoveEntryOperation {
|
|
58
|
+
readonly op: "remove-entry";
|
|
59
|
+
}
|
|
60
|
+
/** The closed operation vocabulary. Named for what each TARGETS, so the target
|
|
61
|
+
* is never inferred from which parameter happens to be present and the
|
|
62
|
+
* quick-fix question reads off the name. */
|
|
63
|
+
export type MigrationOperation = RenameKeyOperation | SetValueOperation | SetTagOperation | InsertItemOperation | RemoveEntryOperation;
|
|
64
|
+
export declare const MIGRATION_OPS: readonly ["rename-key", "set-value", "set-tag", "insert-item", "remove-entry"];
|
|
65
|
+
/** One legacy spelling and the edit that replaces it. */
|
|
66
|
+
export interface MigrationRule {
|
|
67
|
+
/** Which nodes this rule rewrites, resolved against the frozen pre-migration
|
|
68
|
+
* tree. Declarative — see `match.ts`. */
|
|
69
|
+
readonly match: MigrationMatch;
|
|
70
|
+
/** Applied in order at each match. A patch that cannot apply in full leaves
|
|
71
|
+
* the node untouched. */
|
|
72
|
+
readonly patch: readonly MigrationOperation[];
|
|
73
|
+
}
|
|
74
|
+
/** One deprecation story. May carry several rules — the value-type unification
|
|
75
|
+
* changed three spellings but tells the author one thing, which is why the
|
|
76
|
+
* rationale is entry-level and a mechanical description is not. */
|
|
77
|
+
export interface MigrationEntry {
|
|
78
|
+
/** Stable identifier. Names which migration fired; docs list them. */
|
|
79
|
+
readonly id: string;
|
|
80
|
+
/** Diagnostic code reported for every rewrite this entry makes. */
|
|
81
|
+
readonly code: string;
|
|
82
|
+
readonly severity: DiagnosticSeverity;
|
|
83
|
+
/** A sentence or two of rationale — the one part the driver cannot generate,
|
|
84
|
+
* and the part that makes a deprecation actionable rather than mysterious.
|
|
85
|
+
* Never a clause of the generated sentence. */
|
|
86
|
+
readonly reason: string;
|
|
87
|
+
readonly rules: readonly MigrationRule[];
|
|
88
|
+
}
|
|
89
|
+
/** One applied rewrite. Path provenance is part of the driver's contract:
|
|
90
|
+
* diagnostics are remapped through `legacyPath` before position resolution,
|
|
91
|
+
* and `telo migrate` reads its edit target from the same record — the location
|
|
92
|
+
* in the author's file, not the post-rewrite path. */
|
|
93
|
+
export interface MigrationRewrite {
|
|
94
|
+
readonly entryId: string;
|
|
95
|
+
readonly code: string;
|
|
96
|
+
readonly severity: DiagnosticSeverity;
|
|
97
|
+
readonly documentIndex: number;
|
|
98
|
+
/** Dotted path (`a.b[0].c`) as the AUTHOR wrote it — the key into a
|
|
99
|
+
* position index built from the raw file. */
|
|
100
|
+
readonly legacyPath: string;
|
|
101
|
+
/** Dotted path after the rewrite. Equal to `legacyPath` unless a
|
|
102
|
+
* `rename-key` moved it. */
|
|
103
|
+
readonly migratedPath: string;
|
|
104
|
+
/** Human-readable description of what changed, generated by the driver. */
|
|
105
|
+
readonly summary: string;
|
|
106
|
+
}
|
|
107
|
+
/** Why a matched patch was refused. A migration that cannot rewrite leaves the
|
|
108
|
+
* node untouched for the ordinary validator to reject — never guessing, never
|
|
109
|
+
* dropping. */
|
|
110
|
+
export type MigrationRefusal = "path-not-found" | "destination-occupied" | "not-a-mapping-entry" | "not-a-sequence" | "not-a-scalar" | "malformed-value"
|
|
111
|
+
/** The value is already what the patch would write. A rule should match only
|
|
112
|
+
* the legacy spelling, so this means its matcher was too wide — refusing
|
|
113
|
+
* keeps that from surfacing as a deprecation the author cannot act on. */
|
|
114
|
+
| "nothing-to-rewrite";
|
|
115
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/migrations/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;+DAgB+D;AAE/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;kFAEkF;AAClF,MAAM,MAAM,aAAa,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;AAE3D;;;;sEAIsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED;;;;;sCAKsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;uCACuC;AACvC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC;CAC7B;AAED;;6CAE6C;AAC7C,MAAM,MAAM,kBAAkB,GAC1B,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,mBAAmB,GACnB,oBAAoB,CAAC;AAEzB,eAAO,MAAM,aAAa,gFAMhB,CAAC;AAEX,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B;8CAC0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B;8BAC0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;CAC/C;AAED;;oEAEoE;AACpE,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC;;oDAEgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;CAC1C;AAED;;;uDAGuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;kDAC8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;iCAC6B;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;gBAEgB;AAChB,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,sBAAsB,GACtB,qBAAqB,GACrB,gBAAgB,GAChB,cAAc,GACd,iBAAiB;AACnB;;2EAE2E;GACzE,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Manifest migrations — the entry vocabulary.
|
|
2
|
+
*
|
|
3
|
+
* A migration is a MATCHER plus a PATCH. The two halves are at different
|
|
4
|
+
* maturities and that is deliberate (`plans/manifest-migrations.md`):
|
|
5
|
+
*
|
|
6
|
+
* - The **patch** names what it targets. Every operation has a known YAML edit
|
|
7
|
+
* form, which is what makes a migration applicable to a *file* at all and
|
|
8
|
+
* what lets the driver derive whether a quick fix exists — read straight off
|
|
9
|
+
* the verb, never declared by the author.
|
|
10
|
+
* - The **matcher** selects every occurrence of a legacy spelling, which is
|
|
11
|
+
* the half a plain patch format has none of (see `match.ts`).
|
|
12
|
+
*
|
|
13
|
+
* BOTH halves are data, and an entry contains no code at all. That is what
|
|
14
|
+
* lets one entry file be read by every kernel: a predicate expressed in one
|
|
15
|
+
* language would mean one artifact is read two ways, invisibly, since a
|
|
16
|
+
* migration that succeeds is silent. A migration that does not fit is a signal
|
|
17
|
+
* to extend the vocabulary, never to hand-write a rewrite. */
|
|
18
|
+
export const MIGRATION_OPS = [
|
|
19
|
+
"rename-key",
|
|
20
|
+
"set-value",
|
|
21
|
+
"set-tag",
|
|
22
|
+
"insert-item",
|
|
23
|
+
"remove-entry",
|
|
24
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Rendering a migration plan back into the author's YAML.
|
|
2
|
+
*
|
|
3
|
+
* Edits are BYTE SPLICES computed from the parsed document's node ranges, not
|
|
4
|
+
* `Document.toString()`. That is `cli/nodejs/src/commands/upgrade.ts`'s
|
|
5
|
+
* precedent and it is load-bearing here for the same reason: re-serializing
|
|
6
|
+
* re-folds block scalars, drops quote-style hints and reflows long strings, so
|
|
7
|
+
* a one-key repair would arrive as a whole-file diff. Everything outside the
|
|
8
|
+
* spliced ranges is byte-identical to the input.
|
|
9
|
+
*
|
|
10
|
+
* The document is parsed with the same `defaultCustomTags()` every other
|
|
11
|
+
* parse site uses, so a tagged scalar is a node here exactly as it is in the
|
|
12
|
+
* loader's tree. */
|
|
13
|
+
import { type Document } from "yaml";
|
|
14
|
+
import { type TextEdit } from "../yaml-source-edit.js";
|
|
15
|
+
import type { MigrationEffect } from "./patch.js";
|
|
16
|
+
export { applyTextEdits, type TextEdit } from "../yaml-source-edit.js";
|
|
17
|
+
/** One YAML edit per effect, or `undefined` when the effect cannot be written
|
|
18
|
+
* into this file — the same all-or-nothing rule the tree side has, so a
|
|
19
|
+
* partially applied patch never reaches disk. */
|
|
20
|
+
export declare function planTextEdits(doc: Document, text: string, effects: readonly MigrationEffect[]): TextEdit[] | undefined;
|
|
21
|
+
//# sourceMappingURL=yaml-edit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml-edit.d.ts","sourceRoot":"","sources":["../../src/migrations/yaml-edit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;qBAWqB;AAErB,OAAO,EAAkC,KAAK,QAAQ,EAAa,MAAM,MAAM,CAAC;AAChF,OAAO,EAAwB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AA2CvE;;kDAEkD;AAClD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS,eAAe,EAAE,GAClC,QAAQ,EAAE,GAAG,SAAS,CAkFxB"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/** Rendering a migration plan back into the author's YAML.
|
|
2
|
+
*
|
|
3
|
+
* Edits are BYTE SPLICES computed from the parsed document's node ranges, not
|
|
4
|
+
* `Document.toString()`. That is `cli/nodejs/src/commands/upgrade.ts`'s
|
|
5
|
+
* precedent and it is load-bearing here for the same reason: re-serializing
|
|
6
|
+
* re-folds block scalars, drops quote-style hints and reflows long strings, so
|
|
7
|
+
* a one-key repair would arrive as a whole-file diff. Everything outside the
|
|
8
|
+
* spliced ranges is byte-identical to the input.
|
|
9
|
+
*
|
|
10
|
+
* The document is parsed with the same `defaultCustomTags()` every other
|
|
11
|
+
* parse site uses, so a tagged scalar is a node here exactly as it is in the
|
|
12
|
+
* loader's tree. */
|
|
13
|
+
import { isMap, isPair, isScalar, isSeq } from "yaml";
|
|
14
|
+
import { renderFixReplacement } from "../yaml-source-edit.js";
|
|
15
|
+
export { applyTextEdits } from "../yaml-source-edit.js";
|
|
16
|
+
function nodeAt(doc, path) {
|
|
17
|
+
if (path.length === 0)
|
|
18
|
+
return doc.contents;
|
|
19
|
+
return doc.getIn(path, true);
|
|
20
|
+
}
|
|
21
|
+
function rangeOf(node) {
|
|
22
|
+
const range = node?.range;
|
|
23
|
+
if (!Array.isArray(range) || typeof range[0] !== "number" || typeof range[1] !== "number") {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return [range[0], range[1]];
|
|
27
|
+
}
|
|
28
|
+
/** The Pair whose key is `key` inside the map at `parent`. */
|
|
29
|
+
function pairAt(doc, parent, key) {
|
|
30
|
+
const map = nodeAt(doc, parent);
|
|
31
|
+
if (!isMap(map))
|
|
32
|
+
return undefined;
|
|
33
|
+
return map.items.find((item) => isPair(item) && isScalar(item.key) && String(item.key.value) === key);
|
|
34
|
+
}
|
|
35
|
+
/** Re-quote `value` in the style the author used at `original`.
|
|
36
|
+
*
|
|
37
|
+
* A non-string is rendered as its YAML spelling and then handed to the shared
|
|
38
|
+
* rule, so a migration and a quick fix quote the same value identically — the
|
|
39
|
+
* rule itself is `renderFixReplacement` (`../yaml-source-edit.js`), which both
|
|
40
|
+
* this and `@telorun/ide-support` read rather than restate. */
|
|
41
|
+
function renderScalar(original, value) {
|
|
42
|
+
if (value === null)
|
|
43
|
+
return "null";
|
|
44
|
+
if (typeof value === "boolean" || typeof value === "number")
|
|
45
|
+
return String(value);
|
|
46
|
+
if (typeof value !== "string")
|
|
47
|
+
return undefined;
|
|
48
|
+
return renderFixReplacement(original, value);
|
|
49
|
+
}
|
|
50
|
+
/** Column of `offset` within its line, and the offset of that line's start. */
|
|
51
|
+
function lineGeometry(text, offset) {
|
|
52
|
+
const lineStart = text.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
|
|
53
|
+
return { lineStart, indent: offset - lineStart };
|
|
54
|
+
}
|
|
55
|
+
/** One YAML edit per effect, or `undefined` when the effect cannot be written
|
|
56
|
+
* into this file — the same all-or-nothing rule the tree side has, so a
|
|
57
|
+
* partially applied patch never reaches disk. */
|
|
58
|
+
export function planTextEdits(doc, text, effects) {
|
|
59
|
+
if (effects.length === 0)
|
|
60
|
+
return [];
|
|
61
|
+
// **The document is never mutated.** A patch's later effects address the node
|
|
62
|
+
// by its POST-rename path, which the file does not have — so every effect is
|
|
63
|
+
// resolved at the one location the patch matched, read off the first effect.
|
|
64
|
+
// The tree applier and this one therefore agree without either replaying the
|
|
65
|
+
// other's state.
|
|
66
|
+
const first = effects[0];
|
|
67
|
+
const docPath = first.kind === "rename-key" ? [...first.parent, first.from] : first.path;
|
|
68
|
+
// A removal supersedes every other edit at the location: `planPatch` refuses
|
|
69
|
+
// anything after a `remove-entry`, and the line it deletes contains them.
|
|
70
|
+
const removal = effects.find((e) => e.kind === "remove-entry");
|
|
71
|
+
if (removal)
|
|
72
|
+
return removeEntryEdit(doc, text, docPath);
|
|
73
|
+
const edits = [];
|
|
74
|
+
let keyEdit;
|
|
75
|
+
// Value edits compose into ONE splice: a `set-tag` after a `set-value` must
|
|
76
|
+
// emit the tag in front of the NEW scalar, not re-read the old one.
|
|
77
|
+
let valueSpan;
|
|
78
|
+
let scalarText;
|
|
79
|
+
let tagText;
|
|
80
|
+
for (const effect of effects) {
|
|
81
|
+
switch (effect.kind) {
|
|
82
|
+
case "rename-key": {
|
|
83
|
+
const pair = pairAt(doc, docPath.slice(0, -1), String(docPath[docPath.length - 1]));
|
|
84
|
+
if (!isPair(pair) || !isScalar(pair.key))
|
|
85
|
+
return undefined;
|
|
86
|
+
const range = rangeOf(pair.key);
|
|
87
|
+
if (!range)
|
|
88
|
+
return undefined;
|
|
89
|
+
const rendered = renderScalar(text.slice(range[0], range[1]), effect.to);
|
|
90
|
+
if (rendered === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
keyEdit = { start: range[0], end: range[1], newText: rendered };
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "set-value": {
|
|
96
|
+
const range = rangeOf(nodeAt(doc, docPath));
|
|
97
|
+
if (!range)
|
|
98
|
+
return undefined;
|
|
99
|
+
const rendered = renderScalar(text.slice(range[0], range[1]), effect.value);
|
|
100
|
+
if (rendered === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
valueSpan ??= range;
|
|
103
|
+
scalarText = rendered;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case "set-tag": {
|
|
107
|
+
const node = nodeAt(doc, docPath);
|
|
108
|
+
if (!isScalar(node))
|
|
109
|
+
return undefined;
|
|
110
|
+
const range = rangeOf(node);
|
|
111
|
+
if (!range)
|
|
112
|
+
return undefined;
|
|
113
|
+
// The tag sits outside the scalar's own span, so an existing one is
|
|
114
|
+
// replaced by rewriting from where it starts; a plain scalar just gains
|
|
115
|
+
// a prefix.
|
|
116
|
+
const existing = typeof node.tag === "string" ? node.tag : undefined;
|
|
117
|
+
const tagStart = existing ? text.lastIndexOf(existing, range[0]) : -1;
|
|
118
|
+
valueSpan = [tagStart >= 0 ? tagStart : range[0], range[1]];
|
|
119
|
+
scalarText ??= text.slice(range[0], range[1]);
|
|
120
|
+
tagText = `!${effect.tag}`;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "insert-item": {
|
|
124
|
+
const edit = insertItemEdit(doc, text, docPath, effect.index, effect.value);
|
|
125
|
+
if (!edit)
|
|
126
|
+
return undefined;
|
|
127
|
+
edits.push(edit);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "remove-entry":
|
|
131
|
+
// Handled above.
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (keyEdit)
|
|
136
|
+
edits.push(keyEdit);
|
|
137
|
+
if (valueSpan && scalarText !== undefined) {
|
|
138
|
+
edits.push({
|
|
139
|
+
start: valueSpan[0],
|
|
140
|
+
end: valueSpan[1],
|
|
141
|
+
newText: tagText ? `${tagText} ${scalarText}` : scalarText,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return edits;
|
|
145
|
+
}
|
|
146
|
+
function insertItemEdit(doc, text, path, index, value) {
|
|
147
|
+
const seq = nodeAt(doc, path);
|
|
148
|
+
// An empty sequence is written `[]` in flow style, which has no item line to
|
|
149
|
+
// extend — the one insert shape this cannot render.
|
|
150
|
+
if (!isSeq(seq) || seq.items.length === 0)
|
|
151
|
+
return undefined;
|
|
152
|
+
const rendered = renderScalar("", value);
|
|
153
|
+
if (rendered === undefined)
|
|
154
|
+
return undefined;
|
|
155
|
+
const anchorIndex = Math.min(index, seq.items.length - 1);
|
|
156
|
+
const anchor = rangeOf(seq.items[anchorIndex]);
|
|
157
|
+
if (!anchor)
|
|
158
|
+
return undefined;
|
|
159
|
+
const { lineStart, indent } = lineGeometry(text, anchor[0]);
|
|
160
|
+
// A block sequence's item starts two columns after its `- `; a flow sequence
|
|
161
|
+
// has no line of its own.
|
|
162
|
+
if (text.slice(lineStart, anchor[0]).trimStart() !== "- ")
|
|
163
|
+
return undefined;
|
|
164
|
+
const line = `${" ".repeat(Math.max(0, indent - 2))}- ${rendered}\n`;
|
|
165
|
+
if (index >= seq.items.length) {
|
|
166
|
+
const last = rangeOf(seq.items[seq.items.length - 1]);
|
|
167
|
+
if (!last)
|
|
168
|
+
return undefined;
|
|
169
|
+
const lineEnd = text.indexOf("\n", last[1]);
|
|
170
|
+
const at = lineEnd < 0 ? text.length : lineEnd + 1;
|
|
171
|
+
return { start: at, end: at, newText: line };
|
|
172
|
+
}
|
|
173
|
+
return { start: lineStart, end: lineStart, newText: line };
|
|
174
|
+
}
|
|
175
|
+
function removeEntryEdit(doc, text, path) {
|
|
176
|
+
const located = locateEntry(doc, path);
|
|
177
|
+
if (!located)
|
|
178
|
+
return undefined;
|
|
179
|
+
const { lineStart } = lineGeometry(text, located.start);
|
|
180
|
+
const prefix = text.slice(lineStart, located.start);
|
|
181
|
+
const opensSequenceItem = /^-\s+$/.test(prefix.trimStart());
|
|
182
|
+
// The entry OWNS its line — indentation for a mapping entry, indentation plus
|
|
183
|
+
// `- ` for a sequence item — so removing the line is exact.
|
|
184
|
+
if (located.item ? opensSequenceItem : prefix.trim() === "") {
|
|
185
|
+
const lineEnd = text.indexOf("\n", located.end);
|
|
186
|
+
return [{ start: lineStart, end: lineEnd < 0 ? text.length : lineEnd + 1, newText: "" }];
|
|
187
|
+
}
|
|
188
|
+
// A mapping entry that OPENS a sequence item shares its line with the `- `,
|
|
189
|
+
// and is the overwhelmingly common shape of the one thing a migration removes
|
|
190
|
+
// today: a legacy ref slot is almost always an `anyOf` branch, written
|
|
191
|
+
// `- type: string` with the annotation beneath it. Deleting the line would
|
|
192
|
+
// take the dash with it and fold the item into its predecessor — so the entry
|
|
193
|
+
// is spliced out up to the FOLLOWING sibling's key instead, which slides onto
|
|
194
|
+
// the dash at the column it already occupies. Refusing here would have made
|
|
195
|
+
// the diagnostic's own advice ("run `telo migrate`") dead for the case it is
|
|
196
|
+
// most often given in.
|
|
197
|
+
if (!located.item && opensSequenceItem) {
|
|
198
|
+
const next = nextSiblingKeyStart(doc, path);
|
|
199
|
+
// Nothing to promote onto the dash: the entry is the item's only one, and
|
|
200
|
+
// removing it would leave `- ` with no value.
|
|
201
|
+
if (next === undefined)
|
|
202
|
+
return undefined;
|
|
203
|
+
// Only whitespace may be swallowed. A comment or anything else between the
|
|
204
|
+
// two entries would be destroyed by the splice, so that stays a hand edit.
|
|
205
|
+
const between = text.slice(located.end, next);
|
|
206
|
+
if (!/^\s*$/.test(between) || !between.includes("\n"))
|
|
207
|
+
return undefined;
|
|
208
|
+
return [{ start: located.start, end: next, newText: "" }];
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
/** Offset of the key of the entry FOLLOWING `path` in its own mapping, or
|
|
213
|
+
* `undefined` when `path` is the last entry (or anything is not a plain
|
|
214
|
+
* key-scalar pair). */
|
|
215
|
+
function nextSiblingKeyStart(doc, path) {
|
|
216
|
+
const map = nodeAt(doc, path.slice(0, -1));
|
|
217
|
+
if (!isMap(map))
|
|
218
|
+
return undefined;
|
|
219
|
+
const key = String(path[path.length - 1]);
|
|
220
|
+
const at = map.items.findIndex((item) => isPair(item) && isScalar(item.key) && String(item.key.value) === key);
|
|
221
|
+
if (at < 0)
|
|
222
|
+
return undefined;
|
|
223
|
+
const next = map.items[at + 1];
|
|
224
|
+
if (!isPair(next) || !isScalar(next.key))
|
|
225
|
+
return undefined;
|
|
226
|
+
return rangeOf(next.key)?.[0];
|
|
227
|
+
}
|
|
228
|
+
/** Span of a whole mapping entry (key through value) or sequence item. */
|
|
229
|
+
function locateEntry(doc, path) {
|
|
230
|
+
const last = path[path.length - 1];
|
|
231
|
+
if (typeof last === "string") {
|
|
232
|
+
const pair = pairAt(doc, path.slice(0, -1), last);
|
|
233
|
+
if (!isPair(pair) || !isScalar(pair.key))
|
|
234
|
+
return undefined;
|
|
235
|
+
const keyRange = rangeOf(pair.key);
|
|
236
|
+
const valueRange = rangeOf(pair.value) ?? keyRange;
|
|
237
|
+
if (!keyRange || !valueRange)
|
|
238
|
+
return undefined;
|
|
239
|
+
return { start: keyRange[0], end: valueRange[1], item: false };
|
|
240
|
+
}
|
|
241
|
+
const range = rangeOf(nodeAt(doc, path));
|
|
242
|
+
if (!range)
|
|
243
|
+
return undefined;
|
|
244
|
+
return { start: range[0], end: range[1], item: true };
|
|
245
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type TemplatingEngineRegistry } from "@telorun/templating";
|
|
2
|
+
import { type ArtifactSelector } from "./artifact-selector.js";
|
|
3
|
+
/**
|
|
4
|
+
* One module-relative file a manifest names, and the artifact layer it belongs
|
|
5
|
+
* to.
|
|
6
|
+
*
|
|
7
|
+
* The single answer to "why is this file in the payload", replacing two
|
|
8
|
+
* derivations that happened to agree: publish used to re-parse the manifest with
|
|
9
|
+
* PURL knowledge hardcoded into the CLI, and any second vocabulary — a tag that
|
|
10
|
+
* embeds a file, say — would have had to be added there by hand. Here the
|
|
11
|
+
* knowledge sits with whoever owns the syntax: a controller candidate is read by
|
|
12
|
+
* this module, and a tagged value is read by the engine that owns its tag, via
|
|
13
|
+
* `TemplatingEngine.fileClaims`. Publish maps role to layer and recognises
|
|
14
|
+
* neither.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately NOT hung off `analyze()`. That pass runs over a flattened,
|
|
17
|
+
* import-inclusive manifest set, so its claims would mix in imported libraries'
|
|
18
|
+
* files — whose paths are relative to *their* module and must never join this
|
|
19
|
+
* artifact — and it would make packaging, today derivable offline from manifest
|
|
20
|
+
* text, a product of resolving the whole import graph. This is per-module by
|
|
21
|
+
* construction and needs nothing but the text.
|
|
22
|
+
*
|
|
23
|
+
* Browser-safe, like the rest of the analyzer: parsing and string work only, no
|
|
24
|
+
* filesystem. Whether a claimed file EXISTS is a separate question, asked by the
|
|
25
|
+
* Node-side caller that has a directory to look in.
|
|
26
|
+
*/
|
|
27
|
+
interface ClaimBase {
|
|
28
|
+
/** Module-root-relative POSIX path — relative to the directory holding
|
|
29
|
+
* `telo.yaml`, never to the file the claim was written in. Publish inlines
|
|
30
|
+
* every `include:` partial into the published `telo.yaml`, so a
|
|
31
|
+
* per-file-relative path would change meaning in the artifact. */
|
|
32
|
+
readonly path: string;
|
|
33
|
+
/** Where the claim came from, for diagnostics: the PURL, or `!<tag>` and the
|
|
34
|
+
* path of the value that carried it. */
|
|
35
|
+
readonly origin: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A **discriminated union**, not one shape with optional fields: a controller
|
|
39
|
+
* layer is one per selector and carries sibling patterns, and an assets layer is
|
|
40
|
+
* neither. Optional fields on a single shape put the consumer one `!` away from
|
|
41
|
+
* a crash inside `selectorKey` with no useful message, and let a producer emit a
|
|
42
|
+
* controller claim with no selector that nothing would reject.
|
|
43
|
+
*/
|
|
44
|
+
export type ModuleFileClaim = (ClaimBase & {
|
|
45
|
+
readonly role: "controller";
|
|
46
|
+
readonly selector: ArtifactSelector;
|
|
47
|
+
/** Extra payload patterns that belong in the same layer as this claim —
|
|
48
|
+
* `.gitignore`-style globs over the selected files, matched by the
|
|
49
|
+
* caller, which is the side that knows what was selected. */
|
|
50
|
+
readonly siblings: readonly string[];
|
|
51
|
+
}) | (ClaimBase & {
|
|
52
|
+
readonly role: "assets";
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Every module-relative file the manifest names, from every syntax that can name
|
|
56
|
+
* one.
|
|
57
|
+
*
|
|
58
|
+
* `manifestText` is one module's `telo.yaml`. Publish passes the text it is
|
|
59
|
+
* about to ship — i.e. after `include:` partials have been inlined — but the
|
|
60
|
+
* answer does not depend on that: claims are root-relative, so collecting them
|
|
61
|
+
* before or after inlining gives the same set.
|
|
62
|
+
*/
|
|
63
|
+
export declare function collectModuleFileClaims(manifestText: string, registry?: TemplatingEngineRegistry): ModuleFileClaim[];
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=module-file-claims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-file-claims.d.ts","sourceRoot":"","sources":["../src/module-file-claims.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEpG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,UAAU,SAAS;IACjB;;;uEAGmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;6CACyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACvB,CAAC,SAAS,GAAG;IACX,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC;;kEAE8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC,GACF,CAAC,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAiF9C;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,QAAQ,GAAE,wBAA4C,GACrD,eAAe,EAAE,CAanB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { defaultCustomTags, defaultRegistry, walkCelExpressions, } from "@telorun/templating";
|
|
2
|
+
import { PackageURL } from "packageurl-js";
|
|
3
|
+
import { parseAllDocuments } from "yaml";
|
|
4
|
+
import { selectorFromQualifiers, selectorKey } from "./artifact-selector.js";
|
|
5
|
+
/** `pkg:telo/local/<format>?path=…` — the bundled-controller delivery mode.
|
|
6
|
+
* Anything else (`pkg:npm`, `pkg:cargo`) fetches from its own ecosystem and
|
|
7
|
+
* contributes no layer. */
|
|
8
|
+
const BUNDLED_TYPE = "telo";
|
|
9
|
+
const BUNDLED_NAMESPACE = "local";
|
|
10
|
+
/** Qualifier naming extra files that belong in a controller's layer — what an
|
|
11
|
+
* entry point loads but the manifest cannot otherwise see (a `.wasm` beside its
|
|
12
|
+
* glue, a native library opened at runtime). */
|
|
13
|
+
const SIBLINGS_QUALIFIER = "siblings";
|
|
14
|
+
/** Normalize a `path=` / sibling value to the manifest-relative POSIX form the
|
|
15
|
+
* file selector returns, so membership is a string comparison. */
|
|
16
|
+
function normalizeRelative(value) {
|
|
17
|
+
return value.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
18
|
+
}
|
|
19
|
+
/** Bundled-controller claims from one document's `controllers:` list. */
|
|
20
|
+
function controllerClaims(json) {
|
|
21
|
+
const candidates = json?.controllers;
|
|
22
|
+
if (!Array.isArray(candidates))
|
|
23
|
+
return [];
|
|
24
|
+
const claims = [];
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
if (typeof candidate !== "string")
|
|
27
|
+
continue;
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = PackageURL.fromString(candidate);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Not a parseable PURL — claim collection is not the place to reject it;
|
|
34
|
+
// the analyzer's own validation and the controller loader both report it
|
|
35
|
+
// with better context.
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (parsed.type !== BUNDLED_TYPE || parsed.namespace !== BUNDLED_NAMESPACE)
|
|
39
|
+
continue;
|
|
40
|
+
const entry = parsed.qualifiers?.path;
|
|
41
|
+
if (typeof entry !== "string" || entry === "")
|
|
42
|
+
continue;
|
|
43
|
+
claims.push({
|
|
44
|
+
role: "controller",
|
|
45
|
+
path: normalizeRelative(entry),
|
|
46
|
+
selector: selectorFromQualifiers(parsed.name, parsed.qualifiers, `controller "${candidate}"`),
|
|
47
|
+
siblings: String(parsed.qualifiers?.[SIBLINGS_QUALIFIER] ?? "")
|
|
48
|
+
.split(",")
|
|
49
|
+
.map((p) => p.trim())
|
|
50
|
+
.filter((p) => p !== ""),
|
|
51
|
+
origin: candidate,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return claims;
|
|
55
|
+
}
|
|
56
|
+
/** Claims contributed by tagged values, asked of the engine that owns each tag.
|
|
57
|
+
* The walk reaches every tagged scalar in the document, so an engine that
|
|
58
|
+
* embeds files is discovered wherever its tag was written.
|
|
59
|
+
*
|
|
60
|
+
* The layer role is assigned HERE, not by the engine: an engine reports what it
|
|
61
|
+
* embeds, and which layer that belongs in is this module's vocabulary. A file a
|
|
62
|
+
* tag embeds is read only when the resource holding it is created, so `assets`
|
|
63
|
+
* — the lazily-fetched layer — is what it is. */
|
|
64
|
+
function taggedClaims(json, registry) {
|
|
65
|
+
const claims = [];
|
|
66
|
+
walkCelExpressions(json, "", (source, path, engineName) => {
|
|
67
|
+
const engine = registry.get(engineName);
|
|
68
|
+
for (const claim of engine?.fileClaims?.(source) ?? []) {
|
|
69
|
+
claims.push({ role: "assets", path: claim.path, origin: `!${engineName} at '${path}'` });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return claims;
|
|
73
|
+
}
|
|
74
|
+
/** Identity of a claim for de-duplication: the same file claimed twice by two
|
|
75
|
+
* resources is one file in one layer. Role and selector are part of it because
|
|
76
|
+
* a file two controller candidates both claim is genuinely copied into each of
|
|
77
|
+
* their layers — dropping one would leave a platform's layer short a file it
|
|
78
|
+
* declared it needs. */
|
|
79
|
+
function claimKey(claim) {
|
|
80
|
+
const selector = claim.role === "controller" ? selectorKey(claim.selector) : "";
|
|
81
|
+
return `${claim.role}\0${selector}\0${claim.path}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Every module-relative file the manifest names, from every syntax that can name
|
|
85
|
+
* one.
|
|
86
|
+
*
|
|
87
|
+
* `manifestText` is one module's `telo.yaml`. Publish passes the text it is
|
|
88
|
+
* about to ship — i.e. after `include:` partials have been inlined — but the
|
|
89
|
+
* answer does not depend on that: claims are root-relative, so collecting them
|
|
90
|
+
* before or after inlining gives the same set.
|
|
91
|
+
*/
|
|
92
|
+
export function collectModuleFileClaims(manifestText, registry = defaultRegistry()) {
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
const claims = [];
|
|
95
|
+
for (const doc of parseAllDocuments(manifestText, { customTags: defaultCustomTags() })) {
|
|
96
|
+
const json = doc.toJSON();
|
|
97
|
+
for (const claim of [...controllerClaims(json), ...taggedClaims(json, registry)]) {
|
|
98
|
+
const key = claimKey(claim);
|
|
99
|
+
if (seen.has(key))
|
|
100
|
+
continue;
|
|
101
|
+
seen.add(key);
|
|
102
|
+
claims.push(claim);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return claims;
|
|
106
|
+
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type { Environment } from "@marcbachmann/cel-js";
|
|
2
2
|
import type { LoadedFile } from "./loaded-types.js";
|
|
3
|
+
import type { MigrationEntry } from "./migrations/types.js";
|
|
3
4
|
export interface ParseOptions {
|
|
4
5
|
/** When true, runs `precompileDoc` per document and stamps compiled CEL
|
|
5
6
|
* on the manifests — same flag `LoadOptions.compile` carries today. */
|
|
6
7
|
compile?: boolean;
|
|
7
8
|
/** CEL environment for precompile. Defaults to `buildCelEnvironment()`. */
|
|
8
9
|
celEnv?: Environment;
|
|
10
|
+
/** When true, the migration phase runs over the parsed documents — legacy
|
|
11
|
+
* spellings rewritten to the current ones before anything else reads the
|
|
12
|
+
* tree. Off by default so a round-trip consumer (the editor) keeps the
|
|
13
|
+
* author's vocabulary; see `LoadOptions.migrate`. */
|
|
14
|
+
migrate?: boolean;
|
|
15
|
+
/** Migration set. Defaults to the analyzer's own `CORE_MIGRATIONS`. */
|
|
16
|
+
migrations?: readonly MigrationEntry[];
|
|
9
17
|
}
|
|
10
18
|
/** Pure: text in, structured load result out. No I/O, no caches. */
|
|
11
19
|
export declare function parseLoadedFile(source: string, requestedUrl: string, text: string, options?: ParseOptions): LoadedFile;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-loaded-file.d.ts","sourceRoot":"","sources":["../src/parse-loaded-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAKxD,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"parse-loaded-file.d.ts","sourceRoot":"","sources":["../src/parse-loaded-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAKxD,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAK5D,MAAM,WAAW,YAAY;IAC3B;4EACwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;0DAGsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,UAAU,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACxC;AA4BD,oEAAoE;AACpE,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,YAAY,GACrB,UAAU,CA2DZ"}
|