@telorun/analyzer 0.59.0 → 0.61.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.
Files changed (95) hide show
  1. package/dist/analyzer.d.ts +9 -2
  2. package/dist/analyzer.d.ts.map +1 -1
  3. package/dist/analyzer.js +38 -10
  4. package/dist/artifact-layer-index.d.ts +13 -5
  5. package/dist/artifact-layer-index.d.ts.map +1 -1
  6. package/dist/artifact-layer-index.js +37 -13
  7. package/dist/artifact-selector.d.ts +10 -3
  8. package/dist/artifact-selector.d.ts.map +1 -1
  9. package/dist/artifact-selector.js +10 -1
  10. package/dist/builtins.d.ts.map +1 -1
  11. package/dist/builtins.js +44 -43
  12. package/dist/cel-environment.d.ts.map +1 -1
  13. package/dist/cel-environment.js +35 -0
  14. package/dist/flatten-for-analyzer.d.ts +4 -0
  15. package/dist/flatten-for-analyzer.d.ts.map +1 -1
  16. package/dist/flatten-for-analyzer.js +8 -0
  17. package/dist/index.d.ts +7 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +9 -2
  20. package/dist/kernel-globals.d.ts +1 -1
  21. package/dist/kernel-globals.d.ts.map +1 -1
  22. package/dist/kernel-globals.js +15 -1
  23. package/dist/manifest-schemas.d.ts +407 -0
  24. package/dist/manifest-schemas.d.ts.map +1 -0
  25. package/dist/manifest-schemas.js +396 -0
  26. package/dist/module-file-claims.d.ts +13 -0
  27. package/dist/module-file-claims.d.ts.map +1 -1
  28. package/dist/module-file-claims.js +26 -2
  29. package/dist/module-library.d.ts +97 -0
  30. package/dist/module-library.d.ts.map +1 -0
  31. package/dist/module-library.js +162 -0
  32. package/dist/module-metadata-scope.d.ts +44 -0
  33. package/dist/module-metadata-scope.d.ts.map +1 -0
  34. package/dist/module-metadata-scope.js +82 -0
  35. package/dist/parse-loaded-file.d.ts.map +1 -1
  36. package/dist/parse-loaded-file.js +16 -0
  37. package/dist/release/bump-level.d.ts +46 -0
  38. package/dist/release/bump-level.d.ts.map +1 -0
  39. package/dist/release/bump-level.js +80 -0
  40. package/dist/release/changelog.d.ts +36 -0
  41. package/dist/release/changelog.d.ts.map +1 -0
  42. package/dist/release/changelog.js +53 -0
  43. package/dist/release/fragment.d.ts +48 -0
  44. package/dist/release/fragment.d.ts.map +1 -0
  45. package/dist/release/fragment.js +84 -0
  46. package/dist/release/index.d.ts +26 -0
  47. package/dist/release/index.d.ts.map +1 -0
  48. package/dist/release/index.js +18 -0
  49. package/dist/release/ledger.d.ts +53 -0
  50. package/dist/release/ledger.d.ts.map +1 -0
  51. package/dist/release/ledger.js +109 -0
  52. package/dist/release/payload-digest.d.ts +63 -0
  53. package/dist/release/payload-digest.d.ts.map +1 -0
  54. package/dist/release/payload-digest.js +65 -0
  55. package/dist/release/release-plan.d.ts +128 -0
  56. package/dist/release/release-plan.d.ts.map +1 -0
  57. package/dist/release/release-plan.js +277 -0
  58. package/dist/release/version-stamp.d.ts +50 -0
  59. package/dist/release/version-stamp.d.ts.map +1 -0
  60. package/dist/release/version-stamp.js +119 -0
  61. package/dist/release/workspace-config.d.ts +41 -0
  62. package/dist/release/workspace-config.d.ts.map +1 -0
  63. package/dist/release/workspace-config.js +60 -0
  64. package/dist/schema-compat.js +2 -1
  65. package/dist/validate-module-artifact.d.ts.map +1 -1
  66. package/dist/validate-module-artifact.js +53 -1
  67. package/dist/validate-step-inputs.d.ts +1 -1
  68. package/dist/validate-step-inputs.d.ts.map +1 -1
  69. package/dist/validate-step-inputs.js +99 -8
  70. package/package.json +3 -3
  71. package/src/analyzer.ts +41 -10
  72. package/src/artifact-layer-index.ts +47 -14
  73. package/src/artifact-selector.ts +15 -4
  74. package/src/builtins.ts +45 -43
  75. package/src/cel-environment.ts +37 -0
  76. package/src/flatten-for-analyzer.ts +12 -0
  77. package/src/index.ts +19 -2
  78. package/src/kernel-globals.ts +23 -1
  79. package/src/manifest-schemas.ts +408 -0
  80. package/src/module-file-claims.ts +42 -2
  81. package/src/module-library.ts +208 -0
  82. package/src/module-metadata-scope.ts +88 -0
  83. package/src/parse-loaded-file.ts +16 -0
  84. package/src/release/bump-level.ts +95 -0
  85. package/src/release/changelog.ts +62 -0
  86. package/src/release/fragment.ts +130 -0
  87. package/src/release/index.ts +66 -0
  88. package/src/release/ledger.ts +142 -0
  89. package/src/release/payload-digest.ts +83 -0
  90. package/src/release/release-plan.ts +392 -0
  91. package/src/release/version-stamp.ts +142 -0
  92. package/src/release/workspace-config.ts +81 -0
  93. package/src/schema-compat.ts +1 -1
  94. package/src/validate-module-artifact.ts +56 -0
  95. package/src/validate-step-inputs.ts +109 -9
@@ -0,0 +1,162 @@
1
+ /**
2
+ * A module's **exported code** — the `exports.code:` block on a `Telo.Library`
3
+ * doc, which names the entry point a *sibling module's* controller bundle
4
+ * resolves this module's bare specifier to.
5
+ *
6
+ * ```yaml
7
+ * exports:
8
+ * kinds:
9
+ * - Store
10
+ * code:
11
+ * - specifier: "@telorun/kv-store"
12
+ * format: js
13
+ * path: ./nodejs/kv-store.mjs
14
+ * source: ./nodejs/src/index.ts
15
+ * ```
16
+ *
17
+ * ## Why it sits under `exports:`
18
+ *
19
+ * A library already declares what crosses its boundary — the kinds importers may
20
+ * name, the resource instances they may `!ref`. This is the same statement about
21
+ * its *code*, and it gates the same way: a specifier nobody declares resolves to
22
+ * nothing. Putting it beside them keeps one block for "reachable from outside"
23
+ * rather than a second top-level key whose name (`library:` on a `Telo.Library`)
24
+ * meant a different thing from the kind one line above it.
25
+ *
26
+ * ## Why it is not a package URL
27
+ *
28
+ * `controllers:` names a PURL because it must be able to say `pkg:npm/…` or
29
+ * `pkg:cargo/…` — an ecosystem fetch. This entry never fetches: it names a file
30
+ * the module already ships, so `pkg:telo/local/` would be three constant segments
31
+ * before the first real datum. What is left after removing them is exactly these
32
+ * fields, and as data they are visually editable, where a query string is one
33
+ * opaque text box.
34
+ *
35
+ * The **model** is unchanged: `format` plus the optional platform axes build the
36
+ * same `ArtifactSelector` a controller candidate does, so layer matching, platform
37
+ * fallthrough and lazy materialization are inherited whole.
38
+ *
39
+ * ## Why the specifier is declared here
40
+ *
41
+ * A bundle imports the bare specifier `@telorun/sql`; the consumer's manifest
42
+ * declares the dependency as `Sql: ../sql`. Something has to connect the two, and
43
+ * it is the *library* that says so, once, rather than each of its consumers:
44
+ *
45
+ * - the specifier is a property of the library — its name in a host language's
46
+ * ecosystem — not of the relationship, so N consumers cannot disagree about it
47
+ * and adding a consumer restates nothing;
48
+ * - it sits beside the format, which keeps runtime **derived, never declared**:
49
+ * the entry says `format: js`, and a Rust entry carries `specifier:
50
+ * telorun-sql` with no runtime-keyed map anywhere.
51
+ *
52
+ * **One specifier, one entry point.** Subpaths are deliberately not
53
+ * representable: reproducing npm's `exports` map inside the artifact would pull a
54
+ * package manager's resolution semantics into Telo, which is what the "only
55
+ * workspace modules are de-inlined" rule refuses on `kysely`'s behalf.
56
+ *
57
+ * `Telo.Application` has no `exports:` block at all — an application is a root
58
+ * with no importer, so nothing could resolve a specifier to it.
59
+ *
60
+ * Browser-safe: string work only. Whether the named file EXISTS is a separate
61
+ * question, asked by the Node-side caller that has a directory.
62
+ */
63
+ import { ArtifactSelectorError, PLATFORM_AXES, selectorFromQualifiers, } from "./artifact-selector.js";
64
+ /** Every key an entry may carry: the two locators, plus the selector axes. */
65
+ const KNOWN_KEYS = new Set(["specifier", "path", "source", "format", ...PLATFORM_AXES]);
66
+ /** Normalize a `path` / `source` value to the manifest-relative POSIX form the
67
+ * file selector returns, so membership is a string comparison. */
68
+ function normalizeRelative(value) {
69
+ return value.replace(/^\.\//, "").replace(/\\/g, "/");
70
+ }
71
+ function requiredString(entry, key) {
72
+ const raw = entry[key];
73
+ if (typeof raw !== "string" || raw.trim() === "") {
74
+ return { detail: `'${key}' is required and must be a non-empty string.` };
75
+ }
76
+ return { value: raw.trim() };
77
+ }
78
+ /**
79
+ * Read the `exports.code:` block off an owner document's JSON projection.
80
+ *
81
+ * Everything malformed is a problem rather than a silent skip: an entry that
82
+ * cannot be read names no entry point, so a consumer's bundle falls back to
83
+ * *inlining* the library — the duplicated module scope this whole mechanism
84
+ * exists to remove — and it does so on someone else's machine.
85
+ */
86
+ export function readLibraryCandidates(ownerJson) {
87
+ const declared = ownerJson?.exports?.code;
88
+ const candidates = [];
89
+ const problems = [];
90
+ if (declared === undefined)
91
+ return { candidates, problems };
92
+ if (!Array.isArray(declared)) {
93
+ return {
94
+ candidates,
95
+ problems: [{ origin: "exports.code", detail: "expected a list of entries." }],
96
+ };
97
+ }
98
+ declared.forEach((raw, index) => {
99
+ const origin = `exports.code[${index}]`;
100
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
101
+ problems.push({ origin, detail: "expected an object." });
102
+ return;
103
+ }
104
+ const entry = raw;
105
+ const unknown = Object.keys(entry).filter((key) => !KNOWN_KEYS.has(key));
106
+ if (unknown.length > 0) {
107
+ // Reported, never ignored: an unrecognized platform axis would leave the
108
+ // entry platform-neutral and offer a single-platform file to every host.
109
+ problems.push({
110
+ origin,
111
+ detail: `unknown ${unknown.length === 1 ? "key" : "keys"} ${unknown.map((k) => `'${k}'`).join(", ")}. ` +
112
+ `Known: ${[...KNOWN_KEYS].join(", ")}.`,
113
+ });
114
+ return;
115
+ }
116
+ const specifier = requiredString(entry, "specifier");
117
+ if ("detail" in specifier) {
118
+ problems.push({ origin, detail: specifier.detail });
119
+ return;
120
+ }
121
+ const file = requiredString(entry, "path");
122
+ if ("detail" in file) {
123
+ problems.push({ origin: `${origin} ('${specifier.value}')`, detail: file.detail });
124
+ return;
125
+ }
126
+ // Explicit rather than inferred from the file extension: a `.mjs` can be
127
+ // wasm glue, and an inference rule is something every other runtime's reader
128
+ // would have to copy exactly.
129
+ const format = requiredString(entry, "format");
130
+ if ("detail" in format) {
131
+ problems.push({ origin: `${origin} ('${specifier.value}')`, detail: format.detail });
132
+ return;
133
+ }
134
+ let selector;
135
+ try {
136
+ selector = selectorFromQualifiers(format.value, entry, `${origin} ('${specifier.value}')`);
137
+ }
138
+ catch (err) {
139
+ problems.push({
140
+ origin: `${origin} ('${specifier.value}')`,
141
+ detail: err instanceof ArtifactSelectorError ? err.message : String(err),
142
+ });
143
+ return;
144
+ }
145
+ const source = entry.source;
146
+ if (source !== undefined && (typeof source !== "string" || source.trim() === "")) {
147
+ problems.push({
148
+ origin: `${origin} ('${specifier.value}')`,
149
+ detail: "'source' must be a non-empty string when present.",
150
+ });
151
+ return;
152
+ }
153
+ candidates.push({
154
+ specifier: specifier.value,
155
+ path: normalizeRelative(file.value),
156
+ ...(typeof source === "string" ? { localPath: normalizeRelative(source.trim()) } : {}),
157
+ selector,
158
+ origin: `${origin} ('${specifier.value}')`,
159
+ });
160
+ });
161
+ return { candidates, problems };
162
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What `module.<field>` may read: the metadata an AUTHOR wrote, never the
3
+ * loader's own stamps.
4
+ *
5
+ * A manifest reaching its own `metadata` is a small binding with one sharp edge.
6
+ * By the time analysis or the runtime sees a module doc, its `metadata` also
7
+ * carries fields nothing authored — `source` and `sourceLine` (the loader's
8
+ * provenance), `module`, `moduleGlobals`, `exportedKinds`, `reExportedKinds`,
9
+ * `forwardedExport` (derived indices the analyzer stamps). Exposing those would
10
+ * publish loader internals as a manifest surface, where they would be read,
11
+ * depended on, and then unchangeable.
12
+ *
13
+ * A DENYLIST rather than an allowlist, because the metadata vocabulary is
14
+ * deliberately open: a module may declare a field the standard library has never
15
+ * heard of, and an allowlist would silently hide it. The stamps, by contrast,
16
+ * are a closed set this repo controls — so the thing that can be enumerated is
17
+ * the thing enumerated.
18
+ */
19
+ /** Fields written by the loader or the analyzer, not by the module's author. */
20
+ export declare const DERIVED_METADATA_FIELDS: ReadonlySet<string>;
21
+ /** The author-written half of a module doc's `metadata`. */
22
+ export declare function authoredModuleMetadata(metadata: Record<string, unknown> | undefined | null): Record<string, unknown>;
23
+ /**
24
+ * The `module` namespace as a JSON Schema, or `undefined` when there is nothing
25
+ * to type it from.
26
+ *
27
+ * **One derivation, two consumers.** `cel-environment.ts` needs CEL type strings
28
+ * and `kernel-globals.ts` needs JSON Schema, and they used to reach the same
29
+ * conclusion through two hand-written ternary chains that had to agree forever
30
+ * about which values are open and which are closed. The schema is the richer of
31
+ * the two shapes, so it is what is derived; the CEL side converts with
32
+ * `jsonSchemaToCelType`, which every other namespace already goes through.
33
+ *
34
+ * Typed from the VALUES because a module doc's metadata is literals, not a
35
+ * schema map — the module a resource belongs to is fixed, so there is nothing to
36
+ * resolve.
37
+ *
38
+ * `undefined` means **open**, and the distinction matters in the rejecting
39
+ * direction: a set with no module doc must leave `module.*` unconstrained rather
40
+ * than close it over whatever metadata happened to be at hand, or a valid
41
+ * `module.version` becomes a hard error nobody can act on.
42
+ */
43
+ export declare function moduleMetadataSchema(metadata: Record<string, unknown> | undefined | null): Record<string, any> | undefined;
44
+ //# sourceMappingURL=module-metadata-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-metadata-scope.d.ts","sourceRoot":"","sources":["../src/module-metadata-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAQtD,CAAC;AAEH,4DAA4D;AAC5D,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GACnD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GACnD,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * What `module.<field>` may read: the metadata an AUTHOR wrote, never the
3
+ * loader's own stamps.
4
+ *
5
+ * A manifest reaching its own `metadata` is a small binding with one sharp edge.
6
+ * By the time analysis or the runtime sees a module doc, its `metadata` also
7
+ * carries fields nothing authored — `source` and `sourceLine` (the loader's
8
+ * provenance), `module`, `moduleGlobals`, `exportedKinds`, `reExportedKinds`,
9
+ * `forwardedExport` (derived indices the analyzer stamps). Exposing those would
10
+ * publish loader internals as a manifest surface, where they would be read,
11
+ * depended on, and then unchangeable.
12
+ *
13
+ * A DENYLIST rather than an allowlist, because the metadata vocabulary is
14
+ * deliberately open: a module may declare a field the standard library has never
15
+ * heard of, and an allowlist would silently hide it. The stamps, by contrast,
16
+ * are a closed set this repo controls — so the thing that can be enumerated is
17
+ * the thing enumerated.
18
+ */
19
+ /** Fields written by the loader or the analyzer, not by the module's author. */
20
+ export const DERIVED_METADATA_FIELDS = new Set([
21
+ "source",
22
+ "sourceLine",
23
+ "module",
24
+ "moduleGlobals",
25
+ "exportedKinds",
26
+ "reExportedKinds",
27
+ "forwardedExport",
28
+ ]);
29
+ /** The author-written half of a module doc's `metadata`. */
30
+ export function authoredModuleMetadata(metadata) {
31
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
32
+ return {};
33
+ const authored = {};
34
+ for (const [key, value] of Object.entries(metadata)) {
35
+ if (DERIVED_METADATA_FIELDS.has(key))
36
+ continue;
37
+ authored[key] = value;
38
+ }
39
+ return authored;
40
+ }
41
+ /**
42
+ * The `module` namespace as a JSON Schema, or `undefined` when there is nothing
43
+ * to type it from.
44
+ *
45
+ * **One derivation, two consumers.** `cel-environment.ts` needs CEL type strings
46
+ * and `kernel-globals.ts` needs JSON Schema, and they used to reach the same
47
+ * conclusion through two hand-written ternary chains that had to agree forever
48
+ * about which values are open and which are closed. The schema is the richer of
49
+ * the two shapes, so it is what is derived; the CEL side converts with
50
+ * `jsonSchemaToCelType`, which every other namespace already goes through.
51
+ *
52
+ * Typed from the VALUES because a module doc's metadata is literals, not a
53
+ * schema map — the module a resource belongs to is fixed, so there is nothing to
54
+ * resolve.
55
+ *
56
+ * `undefined` means **open**, and the distinction matters in the rejecting
57
+ * direction: a set with no module doc must leave `module.*` unconstrained rather
58
+ * than close it over whatever metadata happened to be at hand, or a valid
59
+ * `module.version` becomes a hard error nobody can act on.
60
+ */
61
+ export function moduleMetadataSchema(metadata) {
62
+ const authored = authoredModuleMetadata(metadata);
63
+ const keys = Object.keys(authored);
64
+ if (keys.length === 0)
65
+ return undefined;
66
+ const properties = {};
67
+ for (const key of keys) {
68
+ const value = authored[key];
69
+ properties[key] = Array.isArray(value)
70
+ ? { type: "array" }
71
+ : value !== null && typeof value === "object"
72
+ ? { type: "object", additionalProperties: true }
73
+ : {
74
+ type: typeof value === "number"
75
+ ? "number"
76
+ : typeof value === "boolean"
77
+ ? "boolean"
78
+ : "string",
79
+ };
80
+ }
81
+ return { type: "object", properties, additionalProperties: false };
82
+ }
@@ -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;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"}
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;AAM5D,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,CA0EZ"}
@@ -3,6 +3,7 @@ import { parseAllDocuments } from "yaml";
3
3
  import { buildCelEnvironment } from "./cel-environment.js";
4
4
  import { migrateManifests, NO_MIGRATIONS } from "./migrations/driver.js";
5
5
  import { buildDocumentPositions } from "./position-metadata.js";
6
+ import { expandManifestFragments } from "./manifest-schemas.js";
6
7
  import { precompileDoc } from "./precompile.js";
7
8
  import { documentToAst } from "./yaml-ast.js";
8
9
  /** Append an actionable hint to raw yaml-parser messages that are otherwise
@@ -57,6 +58,21 @@ export function parseLoadedFile(source, requestedUrl, text, options) {
57
58
  const migrations = options?.migrate
58
59
  ? migrateManifests({ source, manifests, entries: options.migrations })
59
60
  : NO_MIGRATIONS;
61
+ // Shared structural fragments (`telo://manifest#/$defs/InvokeStep`) are
62
+ // expanded for EVERY consumer, deliberately ungated.
63
+ //
64
+ // They are not authoring sugar the way `imports:` or a `!ref` tag is — they are
65
+ // the analyzer's own closed set, and nothing downstream is equipped to meet one
66
+ // unresolved: the editor's schema resolver handles document-local `#/` refs
67
+ // only and THROWS on anything else, so a gated expansion took every canvas that
68
+ // renders a `Run` step down. Round-tripping is unaffected because a save writes
69
+ // the YAML documents the editor holds separately (`saveModuleFromDocuments`),
70
+ // never a manifest object — so unlike `migrate`, no expansion here can reach an
71
+ // author's file.
72
+ for (const manifest of manifests) {
73
+ if (manifest)
74
+ expandManifestFragments(manifest);
75
+ }
60
76
  let env;
61
77
  if (options?.compile) {
62
78
  for (let i = 0; i < manifests.length; i++) {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The two vocabularies a release speaks, and the arithmetic between them.
3
+ *
4
+ * A **kind** is what an author writes in a fragment (`Added`, `Fixed`, …). It is
5
+ * changie's vocabulary, kept because it drives two things at once: the semantic
6
+ * level of the bump, and the heading the entry lands under in the changelog. A
7
+ * **level** is what the version arithmetic consumes.
8
+ *
9
+ * The mapping is the only place the two meet, and it is deliberately total: an
10
+ * unrecognized kind is refused rather than defaulted, because a typo that
11
+ * degraded to `patch` would silently under-release a breaking change.
12
+ */
13
+ /** Semantic level of a version move. */
14
+ export type BumpLevel = "major" | "minor" | "patch";
15
+ export declare function maxLevel(a: BumpLevel, b: BumpLevel): BumpLevel;
16
+ /**
17
+ * Fragment kinds, in changelog section order, each with the level it induces.
18
+ *
19
+ * `Changed` / `Removed` induce `major`, which the pre-1.0 guard then rejects —
20
+ * they are kept in the vocabulary rather than dropped so the rejection can name
21
+ * what was written and say why, instead of reporting an unknown kind.
22
+ */
23
+ export declare const FRAGMENT_KINDS: {
24
+ readonly Added: "minor";
25
+ readonly Changed: "major";
26
+ readonly Deprecated: "minor";
27
+ readonly Removed: "major";
28
+ readonly Fixed: "patch";
29
+ readonly Security: "patch";
30
+ };
31
+ export type FragmentKind = keyof typeof FRAGMENT_KINDS;
32
+ /** Declaration order, which is also the order sections appear in a changelog
33
+ * release block. */
34
+ export declare const FRAGMENT_KIND_ORDER: readonly FragmentKind[];
35
+ export declare function isFragmentKind(value: unknown): value is FragmentKind;
36
+ export declare function levelOfKind(kind: FragmentKind): BumpLevel;
37
+ export declare function isReleaseVersion(value: unknown): value is string;
38
+ /** Apply `level` to `version`. Throws on a version this system cannot represent,
39
+ * rather than returning something plausible — every caller has already read the
40
+ * value out of a manifest it is about to rewrite. */
41
+ export declare function applyBump(version: string, level: BumpLevel): string;
42
+ /** Numeric comparison, so `0.10.0` sorts after `0.9.0`. Returns a negative
43
+ * number when `a` precedes `b`. An unparseable version sorts last rather than
44
+ * throwing — this is used for display ordering, not for decisions. */
45
+ export declare function compareVersions(a: string, b: string): number;
46
+ //# sourceMappingURL=bump-level.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bump-level.d.ts","sourceRoot":"","sources":["../../src/release/bump-level.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wCAAwC;AACxC,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAOpD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAE9D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;CAOmB,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,cAAc,CAAC;AAEvD;qBACqB;AACrB,eAAO,MAAM,mBAAmB,EAAE,SAAS,YAAY,EAEpC,CAAC;AAEpB,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAEzD;AAOD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AAED;;sDAEsD;AACtD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAWnE;AAED;;uEAEuE;AACvE,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5D"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The two vocabularies a release speaks, and the arithmetic between them.
3
+ *
4
+ * A **kind** is what an author writes in a fragment (`Added`, `Fixed`, …). It is
5
+ * changie's vocabulary, kept because it drives two things at once: the semantic
6
+ * level of the bump, and the heading the entry lands under in the changelog. A
7
+ * **level** is what the version arithmetic consumes.
8
+ *
9
+ * The mapping is the only place the two meet, and it is deliberately total: an
10
+ * unrecognized kind is refused rather than defaulted, because a typo that
11
+ * degraded to `patch` would silently under-release a breaking change.
12
+ */
13
+ /** Ordering used whenever several levels reach one module — the maximum wins,
14
+ * because a module that inlines a breaking change is breaking for its own
15
+ * consumers. */
16
+ const LEVEL_RANK = { patch: 0, minor: 1, major: 2 };
17
+ export function maxLevel(a, b) {
18
+ return LEVEL_RANK[a] >= LEVEL_RANK[b] ? a : b;
19
+ }
20
+ /**
21
+ * Fragment kinds, in changelog section order, each with the level it induces.
22
+ *
23
+ * `Changed` / `Removed` induce `major`, which the pre-1.0 guard then rejects —
24
+ * they are kept in the vocabulary rather than dropped so the rejection can name
25
+ * what was written and say why, instead of reporting an unknown kind.
26
+ */
27
+ export const FRAGMENT_KINDS = {
28
+ Added: "minor",
29
+ Changed: "major",
30
+ Deprecated: "minor",
31
+ Removed: "major",
32
+ Fixed: "patch",
33
+ Security: "patch",
34
+ };
35
+ /** Declaration order, which is also the order sections appear in a changelog
36
+ * release block. */
37
+ export const FRAGMENT_KIND_ORDER = Object.keys(FRAGMENT_KINDS);
38
+ export function isFragmentKind(value) {
39
+ return typeof value === "string" && value in FRAGMENT_KINDS;
40
+ }
41
+ export function levelOfKind(kind) {
42
+ return FRAGMENT_KINDS[kind];
43
+ }
44
+ /** A `major.minor.patch` triple. Pre-release and build metadata are not accepted:
45
+ * a module version is the tag an artifact publishes under and the value a pin
46
+ * resolves, and neither has a meaning for a suffix today. */
47
+ const VERSION = /^(\d+)\.(\d+)\.(\d+)$/;
48
+ export function isReleaseVersion(value) {
49
+ return typeof value === "string" && VERSION.test(value);
50
+ }
51
+ /** Apply `level` to `version`. Throws on a version this system cannot represent,
52
+ * rather than returning something plausible — every caller has already read the
53
+ * value out of a manifest it is about to rewrite. */
54
+ export function applyBump(version, level) {
55
+ const match = VERSION.exec(version);
56
+ if (!match) {
57
+ throw new Error(`'${version}' is not a major.minor.patch version, so no ${level} bump can be derived from it.`);
58
+ }
59
+ const [major, minor, patch] = match.slice(1, 4).map(Number);
60
+ if (level === "major")
61
+ return `${major + 1}.0.0`;
62
+ if (level === "minor")
63
+ return `${major}.${minor + 1}.0`;
64
+ return `${major}.${minor}.${patch + 1}`;
65
+ }
66
+ /** Numeric comparison, so `0.10.0` sorts after `0.9.0`. Returns a negative
67
+ * number when `a` precedes `b`. An unparseable version sorts last rather than
68
+ * throwing — this is used for display ordering, not for decisions. */
69
+ export function compareVersions(a, b) {
70
+ const pa = VERSION.exec(a);
71
+ const pb = VERSION.exec(b);
72
+ if (!pa || !pb)
73
+ return pa ? -1 : pb ? 1 : a.localeCompare(b);
74
+ for (let i = 1; i <= 3; i++) {
75
+ const diff = Number(pa[i]) - Number(pb[i]);
76
+ if (diff !== 0)
77
+ return diff;
78
+ }
79
+ return 0;
80
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Rendering a module's `CHANGELOG.md` entry, and splicing it into the file.
3
+ *
4
+ * The output shape is the one changie was configured to emit — `## <version> -
5
+ * <date>`, `### <Kind>`, `* <body>` — so a module's history stays one document
6
+ * across the handover rather than changing format mid-file.
7
+ *
8
+ * Prepending rather than rewriting: a changelog is append-only history, and the
9
+ * only edit ever made to it is inserting the newest release above the previous
10
+ * one. Everything already written is untouched bytes.
11
+ */
12
+ import { type FragmentKind } from "./bump-level.js";
13
+ export declare const CHANGELOG_HEADER = "# Changelog";
14
+ export interface ChangelogRelease {
15
+ readonly version: string;
16
+ /** `YYYY-MM-DD`. Passed in rather than read from a clock, so a plan renders
17
+ * identically whenever it is rendered and a test needs no clock control. */
18
+ readonly date: string;
19
+ readonly entries: readonly {
20
+ readonly kind: FragmentKind;
21
+ readonly body: string;
22
+ }[];
23
+ }
24
+ /** One release block. Entries group under their kind, in the vocabulary's own
25
+ * order, so two releases never disagree about where `Fixed` sits. */
26
+ export declare function renderChangelogRelease(release: ChangelogRelease): string;
27
+ /**
28
+ * Insert a release block below the file's `# Changelog` header, creating the
29
+ * file's skeleton when it has none.
30
+ *
31
+ * A module with no changelog yet is the ordinary case for a new module, so this
32
+ * writes the header rather than failing — the alternative is a release that
33
+ * stops to ask for an empty file to be created by hand.
34
+ */
35
+ export declare function prependChangelogRelease(existing: string | undefined, block: string): string;
36
+ //# sourceMappingURL=changelog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/release/changelog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEzE,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;iFAC6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrF;AAED;sEACsE;AACtE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAgBxE;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAS3F"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Rendering a module's `CHANGELOG.md` entry, and splicing it into the file.
3
+ *
4
+ * The output shape is the one changie was configured to emit — `## <version> -
5
+ * <date>`, `### <Kind>`, `* <body>` — so a module's history stays one document
6
+ * across the handover rather than changing format mid-file.
7
+ *
8
+ * Prepending rather than rewriting: a changelog is append-only history, and the
9
+ * only edit ever made to it is inserting the newest release above the previous
10
+ * one. Everything already written is untouched bytes.
11
+ */
12
+ import { FRAGMENT_KIND_ORDER } from "./bump-level.js";
13
+ export const CHANGELOG_HEADER = "# Changelog";
14
+ /** One release block. Entries group under their kind, in the vocabulary's own
15
+ * order, so two releases never disagree about where `Fixed` sits. */
16
+ export function renderChangelogRelease(release) {
17
+ const byKind = new Map();
18
+ for (const entry of release.entries) {
19
+ const list = byKind.get(entry.kind);
20
+ if (list)
21
+ list.push(entry.body);
22
+ else
23
+ byKind.set(entry.kind, [entry.body]);
24
+ }
25
+ const lines = [`## ${release.version} - ${release.date}`];
26
+ for (const kind of FRAGMENT_KIND_ORDER) {
27
+ const bodies = byKind.get(kind);
28
+ if (!bodies)
29
+ continue;
30
+ lines.push(`### ${kind}`);
31
+ for (const body of bodies)
32
+ lines.push(`* ${body}`);
33
+ }
34
+ return lines.join("\n") + "\n";
35
+ }
36
+ /**
37
+ * Insert a release block below the file's `# Changelog` header, creating the
38
+ * file's skeleton when it has none.
39
+ *
40
+ * A module with no changelog yet is the ordinary case for a new module, so this
41
+ * writes the header rather than failing — the alternative is a release that
42
+ * stops to ask for an empty file to be created by hand.
43
+ */
44
+ export function prependChangelogRelease(existing, block) {
45
+ const text = existing ?? "";
46
+ const header = text.match(/^#\s+Changelog[^\n]*\n/);
47
+ if (!header) {
48
+ const rest = text.trim();
49
+ return `${CHANGELOG_HEADER}\n\n${block}${rest ? `\n${rest}\n` : ""}`;
50
+ }
51
+ const after = text.slice(header[0].length).replace(/^\n+/, "");
52
+ return `${header[0]}\n${block}${after ? `\n${after}` : ""}`;
53
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A release fragment — one pending change, written by an author.
3
+ *
4
+ * The shape is changesets' (one file, several modules, one body) carrying
5
+ * changie's kind vocabulary, which is what lets one cross-cutting change be one
6
+ * file while the kind still drives both the level and the changelog section a
7
+ * line lands under. Changie's own file is the other arrangement — one module per
8
+ * file — so a change touching five modules meant five files repeating one
9
+ * sentence, and its key was the module's bare directory NAME, which two
10
+ * directories in different subtrees can share.
11
+ *
12
+ * A fragment names modules by **workspace-relative path**, the same key the
13
+ * ledger and every diagnostic use.
14
+ *
15
+ * They are plain YAML validated here, not a `Telo.*` kind: build-time repo state
16
+ * with no controller and no capability, which the runtime never loads.
17
+ */
18
+ import { type FragmentKind } from "./bump-level.js";
19
+ /** A module's workspace-relative directory path — `modules/sql`, `apps/hub`. */
20
+ export type ModuleKey = string;
21
+ export interface ReleaseFragment {
22
+ /**
23
+ * Where this fragment was read from, workspace-relative. Carried so a
24
+ * diagnostic can name the file to fix and `apply` can delete the file it
25
+ * consumed.
26
+ */
27
+ readonly source: string;
28
+ /** The directly-changed modules this fragment declares, each with its kind. */
29
+ readonly modules: ReadonlyMap<ModuleKey, FragmentKind>;
30
+ /** The changelog line. */
31
+ readonly body: string;
32
+ }
33
+ export declare class FragmentError extends Error {
34
+ readonly source: string;
35
+ constructor(source: string, message: string);
36
+ }
37
+ export declare function parseFragment(text: string, source: string): ReleaseFragment;
38
+ /**
39
+ * Render a fragment, for `telo release add`.
40
+ *
41
+ * Serialized rather than templated so a long body folds the way the YAML writer
42
+ * folds it, and so a module path needing quotes gets them.
43
+ */
44
+ export declare function serializeFragment(modules: ReadonlyMap<ModuleKey, FragmentKind>, body: string): string;
45
+ /** Module keys are POSIX-separated and carry no leading `./` or trailing slash,
46
+ * so the same directory written three ways is one key. */
47
+ export declare function normalizeModuleKey(key: string): ModuleKey;
48
+ //# sourceMappingURL=fragment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fragment.d.ts","sourceRoot":"","sources":["../../src/release/fragment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpE,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACvD,0BAA0B;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,aAAc,SAAQ,KAAK;IAEpC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM;CAIlB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CA4D3E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,EAC7C,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED;2DAC2D;AAC3D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAEzD"}