@ontrails/trails 1.0.0-beta.2 → 1.0.0-beta.22

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 (150) hide show
  1. package/CHANGELOG.md +647 -0
  2. package/README.md +26 -0
  3. package/package.json +28 -7
  4. package/src/app.ts +86 -2
  5. package/src/clack.ts +22 -0
  6. package/src/cli.ts +330 -11
  7. package/src/completions.ts +240 -0
  8. package/src/lifecycle-source-io.ts +33 -0
  9. package/src/load-app-mirror.ts +202 -0
  10. package/src/local-state-io.ts +153 -0
  11. package/src/mcp-app.ts +30 -0
  12. package/src/mcp-options.ts +77 -0
  13. package/src/mcp.ts +8 -0
  14. package/src/project-writes.ts +377 -0
  15. package/src/release/bindings.ts +39 -0
  16. package/src/release/check.ts +818 -0
  17. package/src/release/config.ts +63 -0
  18. package/src/release/contract-facts.ts +425 -0
  19. package/src/release/index.ts +85 -0
  20. package/src/release/native-bun-publish.ts +651 -0
  21. package/src/release/native-bun-registry.ts +350 -0
  22. package/src/release/packed-artifacts-smoke.ts +236 -0
  23. package/src/release/smoke.ts +46 -0
  24. package/src/release/wayfinder-dogfood-smoke.ts +226 -0
  25. package/src/retired-topo-command.ts +36 -0
  26. package/src/run-adapter-check.ts +76 -0
  27. package/src/run-collision.ts +126 -0
  28. package/src/run-completions-install.ts +179 -0
  29. package/src/run-example.ts +149 -0
  30. package/src/run-examples.ts +148 -0
  31. package/src/run-quiet.ts +75 -0
  32. package/src/run-release-check.ts +74 -0
  33. package/src/run-trace.ts +273 -0
  34. package/src/run-warden.ts +39 -0
  35. package/src/run-watch.ts +432 -0
  36. package/src/scaffold-version-sync.ts +183 -0
  37. package/src/scaffold-versions.generated.ts +12 -0
  38. package/src/trails/adapter-check.ts +244 -0
  39. package/src/trails/add-surface.ts +94 -40
  40. package/src/trails/add-trail.ts +79 -41
  41. package/src/trails/add-verify.ts +95 -25
  42. package/src/trails/compile.ts +67 -0
  43. package/src/trails/completions-complete.ts +165 -0
  44. package/src/trails/completions.ts +47 -0
  45. package/src/trails/create-adapter.ts +1084 -0
  46. package/src/trails/create-scaffold.ts +399 -104
  47. package/src/trails/create-versions.ts +62 -0
  48. package/src/trails/create.ts +185 -71
  49. package/src/trails/deprecate.ts +59 -0
  50. package/src/trails/dev-clean.ts +82 -0
  51. package/src/trails/dev-reset.ts +50 -0
  52. package/src/trails/dev-stats.ts +72 -0
  53. package/src/trails/dev-support.ts +340 -0
  54. package/src/trails/doctor.ts +56 -0
  55. package/src/trails/draft-promote.ts +949 -0
  56. package/src/trails/guide.ts +74 -68
  57. package/src/trails/load-app.ts +1143 -15
  58. package/src/trails/project.ts +17 -3
  59. package/src/trails/release-check.ts +104 -0
  60. package/src/trails/release-smoke.ts +48 -0
  61. package/src/trails/revise.ts +53 -0
  62. package/src/trails/root-dir.ts +21 -0
  63. package/src/trails/run-example.ts +491 -0
  64. package/src/trails/run-examples.ts +145 -0
  65. package/src/trails/run.ts +410 -0
  66. package/src/trails/scaffold-json.ts +58 -0
  67. package/src/trails/survey.ts +881 -226
  68. package/src/trails/topo-activation.ts +385 -0
  69. package/src/trails/topo-constants.ts +2 -0
  70. package/src/trails/topo-history.ts +47 -0
  71. package/src/trails/topo-output-schemas.ts +248 -0
  72. package/src/trails/topo-pin.ts +52 -0
  73. package/src/trails/topo-read-support.ts +313 -0
  74. package/src/trails/topo-reports.ts +807 -0
  75. package/src/trails/topo-store-support.ts +174 -0
  76. package/src/trails/topo-support.ts +220 -0
  77. package/src/trails/topo-unpin.ts +61 -0
  78. package/src/trails/topo.ts +106 -0
  79. package/src/trails/validate.ts +38 -0
  80. package/src/trails/version-lifecycle-support.ts +945 -0
  81. package/src/trails/warden-guide.ts +129 -0
  82. package/src/trails/warden.ts +165 -58
  83. package/src/versions.ts +31 -0
  84. package/.turbo/turbo-build.log +0 -1
  85. package/.turbo/turbo-lint.log +0 -3
  86. package/.turbo/turbo-typecheck.log +0 -1
  87. package/__tests__/examples.test.ts +0 -6
  88. package/dist/bin/trails.d.ts +0 -3
  89. package/dist/bin/trails.d.ts.map +0 -1
  90. package/dist/bin/trails.js +0 -4
  91. package/dist/bin/trails.js.map +0 -1
  92. package/dist/src/app.d.ts +0 -2
  93. package/dist/src/app.d.ts.map +0 -1
  94. package/dist/src/app.js +0 -11
  95. package/dist/src/app.js.map +0 -1
  96. package/dist/src/clack.d.ts +0 -9
  97. package/dist/src/clack.d.ts.map +0 -1
  98. package/dist/src/clack.js +0 -62
  99. package/dist/src/clack.js.map +0 -1
  100. package/dist/src/cli.d.ts +0 -2
  101. package/dist/src/cli.d.ts.map +0 -1
  102. package/dist/src/cli.js +0 -13
  103. package/dist/src/cli.js.map +0 -1
  104. package/dist/src/trails/add-surface.d.ts +0 -13
  105. package/dist/src/trails/add-surface.d.ts.map +0 -1
  106. package/dist/src/trails/add-surface.js +0 -88
  107. package/dist/src/trails/add-surface.js.map +0 -1
  108. package/dist/src/trails/add-trail.d.ts +0 -11
  109. package/dist/src/trails/add-trail.d.ts.map +0 -1
  110. package/dist/src/trails/add-trail.js +0 -85
  111. package/dist/src/trails/add-trail.js.map +0 -1
  112. package/dist/src/trails/add-verify.d.ts +0 -10
  113. package/dist/src/trails/add-verify.d.ts.map +0 -1
  114. package/dist/src/trails/add-verify.js +0 -67
  115. package/dist/src/trails/add-verify.js.map +0 -1
  116. package/dist/src/trails/create-scaffold.d.ts +0 -15
  117. package/dist/src/trails/create-scaffold.d.ts.map +0 -1
  118. package/dist/src/trails/create-scaffold.js +0 -288
  119. package/dist/src/trails/create-scaffold.js.map +0 -1
  120. package/dist/src/trails/create.d.ts +0 -22
  121. package/dist/src/trails/create.d.ts.map +0 -1
  122. package/dist/src/trails/create.js +0 -121
  123. package/dist/src/trails/create.js.map +0 -1
  124. package/dist/src/trails/guide.d.ts +0 -11
  125. package/dist/src/trails/guide.d.ts.map +0 -1
  126. package/dist/src/trails/guide.js +0 -80
  127. package/dist/src/trails/guide.js.map +0 -1
  128. package/dist/src/trails/load-app.d.ts +0 -4
  129. package/dist/src/trails/load-app.d.ts.map +0 -1
  130. package/dist/src/trails/load-app.js +0 -24
  131. package/dist/src/trails/load-app.js.map +0 -1
  132. package/dist/src/trails/project.d.ts +0 -8
  133. package/dist/src/trails/project.d.ts.map +0 -1
  134. package/dist/src/trails/project.js +0 -43
  135. package/dist/src/trails/project.js.map +0 -1
  136. package/dist/src/trails/survey.d.ts +0 -33
  137. package/dist/src/trails/survey.d.ts.map +0 -1
  138. package/dist/src/trails/survey.js +0 -225
  139. package/dist/src/trails/survey.js.map +0 -1
  140. package/dist/src/trails/warden.d.ts +0 -19
  141. package/dist/src/trails/warden.d.ts.map +0 -1
  142. package/dist/src/trails/warden.js +0 -88
  143. package/dist/src/trails/warden.js.map +0 -1
  144. package/dist/tsconfig.tsbuildinfo +0 -1
  145. package/src/__tests__/create.test.ts +0 -349
  146. package/src/__tests__/guide.test.ts +0 -91
  147. package/src/__tests__/load-app.test.ts +0 -15
  148. package/src/__tests__/survey.test.ts +0 -161
  149. package/src/__tests__/warden.test.ts +0 -74
  150. package/tsconfig.json +0 -9
@@ -0,0 +1,63 @@
1
+ import { z } from 'zod';
2
+
3
+ export const releaseFactTypeValues = [
4
+ 'package-content',
5
+ 'public-trail-contract',
6
+ ] as const;
7
+
8
+ export const releaseIntentSourceValues = [
9
+ 'changeset',
10
+ 'no-release-override',
11
+ ] as const;
12
+
13
+ export const releaseRuleSeverityValues = ['error', 'warning'] as const;
14
+
15
+ export const releaseFactTypeSchema = z.enum(releaseFactTypeValues);
16
+ export const releaseIntentSourceSchema = z.enum(releaseIntentSourceValues);
17
+ export const releaseRuleSeveritySchema = z.enum(releaseRuleSeverityValues);
18
+
19
+ export const releaseRuleSchema = z.object({
20
+ description: z.string().optional(),
21
+ enabled: z.boolean().default(true),
22
+ facts: z.array(releaseFactTypeSchema).min(1),
23
+ id: z.string().min(1),
24
+ intent: z.array(releaseIntentSourceSchema).min(1).default(['changeset']),
25
+ severity: releaseRuleSeveritySchema.default('error'),
26
+ });
27
+
28
+ export type ReleaseFactType = z.output<typeof releaseFactTypeSchema>;
29
+ export type ReleaseIntentSource = z.output<typeof releaseIntentSourceSchema>;
30
+ export type ReleaseRule = z.output<typeof releaseRuleSchema>;
31
+ export type ReleaseRuleInput = z.input<typeof releaseRuleSchema>;
32
+
33
+ export const defaultReleaseRules = [
34
+ {
35
+ description:
36
+ 'Publishable package content changes require positive release intent.',
37
+ enabled: true,
38
+ facts: ['package-content'],
39
+ id: 'package-content-requires-intent',
40
+ intent: ['changeset', 'no-release-override'],
41
+ severity: 'error',
42
+ },
43
+ {
44
+ description:
45
+ 'Public trail contract changes require positive release intent.',
46
+ enabled: true,
47
+ facts: ['public-trail-contract'],
48
+ id: 'public-trail-contract-requires-intent',
49
+ intent: ['changeset', 'no-release-override'],
50
+ severity: 'error',
51
+ },
52
+ ] as const satisfies readonly ReleaseRuleInput[];
53
+
54
+ export const releaseConfigSchema = z
55
+ .object({
56
+ rules: z.array(releaseRuleSchema).default(() => [...defaultReleaseRules]),
57
+ })
58
+ .default({ rules: [...defaultReleaseRules] });
59
+
60
+ export type ReleaseConfig = z.output<typeof releaseConfigSchema>;
61
+ export type ReleaseConfigInput = z.input<typeof releaseConfigSchema>;
62
+
63
+ export const defaultReleaseConfig = releaseConfigSchema.parse({});
@@ -0,0 +1,425 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ import ts from 'typescript';
6
+
7
+ import type { WorkspaceInfo } from './check.js';
8
+
9
+ export type ContractReleaseFactAspect =
10
+ | 'input'
11
+ | 'output'
12
+ | 'surfaces'
13
+ | 'trail'
14
+ | 'visibility';
15
+
16
+ export interface ContractReleaseFact {
17
+ readonly aspect: ContractReleaseFactAspect;
18
+ readonly baseHash: string | null;
19
+ readonly changedFiles: readonly string[];
20
+ readonly currentHash: string | null;
21
+ readonly packageName?: string;
22
+ readonly path: string;
23
+ readonly trailId: string;
24
+ readonly workspacePath?: string;
25
+ }
26
+
27
+ export interface ContractReleaseFactInput {
28
+ readonly baseRef?: string;
29
+ readonly changedFiles: readonly string[];
30
+ readonly repoRoot: string;
31
+ readonly workspaces: readonly WorkspaceInfo[];
32
+ }
33
+
34
+ export interface ContractSourceSnapshot {
35
+ readonly baseSource: string | null;
36
+ readonly changedFiles?: readonly string[];
37
+ readonly currentSource: string | null;
38
+ readonly packageName?: string;
39
+ readonly path: string;
40
+ readonly workspacePath?: string;
41
+ }
42
+
43
+ interface TrailDefinition {
44
+ readonly aspects: Readonly<Record<ContractReleaseFactAspect, string | null>>;
45
+ readonly trailId: string;
46
+ readonly visibility: 'internal' | 'public';
47
+ }
48
+
49
+ const NON_SHIPPING_SOURCE_PATTERNS = [
50
+ /(?:^|\/)__tests__(?:\/|$)/u,
51
+ /(?:^|\/)__snapshots__(?:\/|$)/u,
52
+ /(?:^|\/)dist(?:\/|$)/u,
53
+ /(?:^|\/)\.turbo(?:\/|$)/u,
54
+ /(?:^|\/)node_modules(?:\/|$)/u,
55
+ /\.(?:test|spec|snap)\.[cm]?[jt]sx?$/u,
56
+ /\.test-d\.ts$/u,
57
+ /\.tsbuildinfo$/u,
58
+ ] as const;
59
+
60
+ const SOURCE_PATH_PATTERN = /\.[cm]?[jt]sx?$/u;
61
+
62
+ const normalizePath = (path: string): string =>
63
+ path.replaceAll('\\', '/').replace(/^\.\//u, '');
64
+
65
+ const isPublishableOnTrailsWorkspace = (workspace: WorkspaceInfo): boolean =>
66
+ !workspace.isPrivate && workspace.name.startsWith('@ontrails/');
67
+
68
+ const isUnderWorkspace = (filePath: string, workspacePath: string): boolean =>
69
+ filePath === workspacePath || filePath.startsWith(`${workspacePath}/`);
70
+
71
+ const getWorkspaceRelativePath = (
72
+ filePath: string,
73
+ workspacePath: string
74
+ ): string => filePath.slice(workspacePath.length + 1);
75
+
76
+ const isNonShippingSourcePath = (workspaceRelativePath: string): boolean =>
77
+ NON_SHIPPING_SOURCE_PATTERNS.some((pattern) =>
78
+ pattern.test(workspaceRelativePath)
79
+ );
80
+
81
+ const hashSource = (source: string | null): string | null =>
82
+ source === null
83
+ ? null
84
+ : createHash('sha256').update(source).digest('hex').slice(0, 16);
85
+
86
+ const readCurrentSource = (
87
+ repoRoot: string,
88
+ relativePath: string
89
+ ): string | null => {
90
+ const absolutePath = join(repoRoot, relativePath);
91
+ return existsSync(absolutePath) ? readFileSync(absolutePath, 'utf8') : null;
92
+ };
93
+
94
+ const readBaseSource = (
95
+ repoRoot: string,
96
+ baseRef: string | undefined,
97
+ relativePath: string
98
+ ): string | null => {
99
+ if (!baseRef) {
100
+ return null;
101
+ }
102
+
103
+ const result = Bun.spawnSync({
104
+ cmd: ['git', 'show', `${baseRef}:${relativePath}`],
105
+ cwd: repoRoot,
106
+ stderr: 'pipe',
107
+ stdout: 'pipe',
108
+ });
109
+
110
+ return result.exitCode === 0 ? result.stdout.toString() : null;
111
+ };
112
+
113
+ const workspaceForFile = (
114
+ filePath: string,
115
+ workspaces: readonly WorkspaceInfo[]
116
+ ): WorkspaceInfo | undefined =>
117
+ workspaces
118
+ .filter(isPublishableOnTrailsWorkspace)
119
+ .find((workspace) => isUnderWorkspace(filePath, workspace.relativePath));
120
+
121
+ export const createContractSourceSnapshots = (
122
+ input: ContractReleaseFactInput
123
+ ): readonly ContractSourceSnapshot[] => {
124
+ const snapshots: ContractSourceSnapshot[] = [];
125
+
126
+ for (const changedFile of input.changedFiles.map(normalizePath)) {
127
+ if (!SOURCE_PATH_PATTERN.test(changedFile)) {
128
+ continue;
129
+ }
130
+
131
+ const workspace = workspaceForFile(changedFile, input.workspaces);
132
+ if (!workspace) {
133
+ continue;
134
+ }
135
+
136
+ const workspaceRelativePath = getWorkspaceRelativePath(
137
+ changedFile,
138
+ workspace.relativePath
139
+ );
140
+ if (isNonShippingSourcePath(workspaceRelativePath)) {
141
+ continue;
142
+ }
143
+
144
+ const baseSource = readBaseSource(
145
+ input.repoRoot,
146
+ input.baseRef,
147
+ changedFile
148
+ );
149
+ const currentSource = readCurrentSource(input.repoRoot, changedFile);
150
+ if (baseSource === null && currentSource === null) {
151
+ continue;
152
+ }
153
+
154
+ snapshots.push({
155
+ baseSource,
156
+ changedFiles: [changedFile],
157
+ currentSource,
158
+ packageName: workspace.name,
159
+ path: changedFile,
160
+ workspacePath: workspace.relativePath,
161
+ });
162
+ }
163
+
164
+ return snapshots;
165
+ };
166
+
167
+ const isPropertyNamed = (
168
+ property: ts.ObjectLiteralElementLike,
169
+ name: string
170
+ ): property is ts.PropertyAssignment => {
171
+ if (!ts.isPropertyAssignment(property)) {
172
+ return false;
173
+ }
174
+
175
+ const propertyName = property.name;
176
+ return (
177
+ (ts.isIdentifier(propertyName) && propertyName.text === name) ||
178
+ (ts.isStringLiteral(propertyName) && propertyName.text === name)
179
+ );
180
+ };
181
+
182
+ const findProperty = (
183
+ object: ts.ObjectLiteralExpression,
184
+ name: string
185
+ ): ts.PropertyAssignment | undefined =>
186
+ object.properties.find((property) => isPropertyNamed(property, name));
187
+
188
+ const isTrailCall = (node: ts.CallExpression): boolean =>
189
+ ts.isIdentifier(node.expression) && node.expression.text === 'trail';
190
+
191
+ const literalText = (node: ts.Expression | undefined): string | undefined =>
192
+ node && ts.isStringLiteralLike(node) ? node.text : undefined;
193
+
194
+ const normalizeText = (value: string): string =>
195
+ value.replaceAll(/\s+/gu, ' ').trim();
196
+
197
+ const normalizeExpressionText = (
198
+ expression: ts.Expression | undefined,
199
+ sourceFile: ts.SourceFile,
200
+ constInitializers: ReadonlyMap<string, ts.Expression>
201
+ ): string | null => {
202
+ if (!expression) {
203
+ return null;
204
+ }
205
+
206
+ if (ts.isIdentifier(expression)) {
207
+ const initializer = constInitializers.get(expression.text);
208
+ if (initializer) {
209
+ return `${expression.text}=${normalizeText(
210
+ initializer.getText(sourceFile)
211
+ )}`;
212
+ }
213
+ }
214
+
215
+ return normalizeText(expression.getText(sourceFile));
216
+ };
217
+
218
+ const visibilityFor = (
219
+ object: ts.ObjectLiteralExpression
220
+ ): TrailDefinition['visibility'] => {
221
+ const visibility = findProperty(object, 'visibility');
222
+ const value = literalText(visibility?.initializer);
223
+ if (value === 'internal') {
224
+ return 'internal';
225
+ }
226
+ return 'public';
227
+ };
228
+
229
+ const collectConstInitializers = (
230
+ sourceFile: ts.SourceFile
231
+ ): ReadonlyMap<string, ts.Expression> => {
232
+ const initializers = new Map<string, ts.Expression>();
233
+
234
+ const visit = (node: ts.Node): void => {
235
+ if (
236
+ ts.isVariableDeclaration(node) &&
237
+ ts.isIdentifier(node.name) &&
238
+ node.initializer
239
+ ) {
240
+ initializers.set(node.name.text, node.initializer);
241
+ }
242
+
243
+ ts.forEachChild(node, visit);
244
+ };
245
+
246
+ visit(sourceFile);
247
+ return initializers;
248
+ };
249
+
250
+ const collectTrailDefinitions = (
251
+ source: string
252
+ ): readonly TrailDefinition[] => {
253
+ const sourceFile = ts.createSourceFile(
254
+ 'release-contract-facts.ts',
255
+ source,
256
+ ts.ScriptTarget.Latest,
257
+ true,
258
+ ts.ScriptKind.TS
259
+ );
260
+ const constInitializers = collectConstInitializers(sourceFile);
261
+ const definitions: TrailDefinition[] = [];
262
+
263
+ const visit = (node: ts.Node): void => {
264
+ if (ts.isCallExpression(node) && isTrailCall(node)) {
265
+ const [idArgument, spec] = node.arguments;
266
+ const trailId = literalText(idArgument);
267
+
268
+ if (trailId && spec && ts.isObjectLiteralExpression(spec)) {
269
+ const visibility = visibilityFor(spec);
270
+ definitions.push({
271
+ aspects: {
272
+ input: normalizeExpressionText(
273
+ findProperty(spec, 'input')?.initializer,
274
+ sourceFile,
275
+ constInitializers
276
+ ),
277
+ output: normalizeExpressionText(
278
+ findProperty(spec, 'output')?.initializer,
279
+ sourceFile,
280
+ constInitializers
281
+ ),
282
+ surfaces: normalizeExpressionText(
283
+ findProperty(spec, 'surfaces')?.initializer,
284
+ sourceFile,
285
+ constInitializers
286
+ ),
287
+ trail: normalizeText(trailId),
288
+ visibility,
289
+ },
290
+ trailId,
291
+ visibility,
292
+ });
293
+ }
294
+ }
295
+
296
+ ts.forEachChild(node, visit);
297
+ };
298
+
299
+ visit(sourceFile);
300
+ return definitions;
301
+ };
302
+
303
+ const definitionsById = (
304
+ definitions: readonly TrailDefinition[]
305
+ ): ReadonlyMap<string, TrailDefinition> =>
306
+ new Map(definitions.map((definition) => [definition.trailId, definition]));
307
+
308
+ const isPublicContract = (definition: TrailDefinition | undefined): boolean =>
309
+ definition !== undefined && definition.visibility !== 'internal';
310
+
311
+ const fact = (
312
+ snapshot: ContractSourceSnapshot,
313
+ trailId: string,
314
+ aspect: ContractReleaseFactAspect,
315
+ baseValue: string | null,
316
+ currentValue: string | null
317
+ ): ContractReleaseFact => ({
318
+ aspect,
319
+ baseHash: hashSource(baseValue),
320
+ changedFiles: snapshot.changedFiles ?? [snapshot.path],
321
+ currentHash: hashSource(currentValue),
322
+ ...(snapshot.packageName ? { packageName: snapshot.packageName } : {}),
323
+ path: snapshot.path,
324
+ trailId,
325
+ ...(snapshot.workspacePath ? { workspacePath: snapshot.workspacePath } : {}),
326
+ });
327
+
328
+ const compareDefinitions = (
329
+ snapshot: ContractSourceSnapshot,
330
+ baseDefinition: TrailDefinition | undefined,
331
+ currentDefinition: TrailDefinition | undefined
332
+ ): readonly ContractReleaseFact[] => {
333
+ if (
334
+ !isPublicContract(baseDefinition) &&
335
+ !isPublicContract(currentDefinition)
336
+ ) {
337
+ return [];
338
+ }
339
+
340
+ const trailId =
341
+ currentDefinition?.trailId ?? baseDefinition?.trailId ?? '(unknown)';
342
+
343
+ if (!baseDefinition || !currentDefinition) {
344
+ return [
345
+ fact(
346
+ snapshot,
347
+ trailId,
348
+ 'trail',
349
+ baseDefinition?.aspects.trail ?? null,
350
+ currentDefinition?.aspects.trail ?? null
351
+ ),
352
+ ];
353
+ }
354
+
355
+ const facts: ContractReleaseFact[] = [];
356
+
357
+ for (const aspect of ['input', 'output', 'surfaces'] as const) {
358
+ const baseValue = baseDefinition.aspects[aspect];
359
+ const currentValue = currentDefinition.aspects[aspect];
360
+ if (baseValue !== currentValue) {
361
+ facts.push(fact(snapshot, trailId, aspect, baseValue, currentValue));
362
+ }
363
+ }
364
+
365
+ if (baseDefinition.visibility !== currentDefinition.visibility) {
366
+ facts.push(
367
+ fact(
368
+ snapshot,
369
+ trailId,
370
+ 'visibility',
371
+ baseDefinition.visibility,
372
+ currentDefinition.visibility
373
+ )
374
+ );
375
+ }
376
+
377
+ return facts;
378
+ };
379
+
380
+ export const findPublicTrailContractChangeFactsFromSnapshots = (
381
+ snapshots: readonly ContractSourceSnapshot[]
382
+ ): readonly ContractReleaseFact[] => {
383
+ const facts: ContractReleaseFact[] = [];
384
+
385
+ for (const snapshot of snapshots) {
386
+ const baseDefinitions = definitionsById(
387
+ snapshot.baseSource === null
388
+ ? []
389
+ : collectTrailDefinitions(snapshot.baseSource)
390
+ );
391
+ const currentDefinitions = definitionsById(
392
+ snapshot.currentSource === null
393
+ ? []
394
+ : collectTrailDefinitions(snapshot.currentSource)
395
+ );
396
+ const trailIds = new Set([
397
+ ...baseDefinitions.keys(),
398
+ ...currentDefinitions.keys(),
399
+ ]);
400
+
401
+ for (const trailId of [...trailIds].toSorted()) {
402
+ facts.push(
403
+ ...compareDefinitions(
404
+ snapshot,
405
+ baseDefinitions.get(trailId),
406
+ currentDefinitions.get(trailId)
407
+ )
408
+ );
409
+ }
410
+ }
411
+
412
+ return facts.toSorted(
413
+ (left, right) =>
414
+ left.path.localeCompare(right.path) ||
415
+ left.trailId.localeCompare(right.trailId) ||
416
+ left.aspect.localeCompare(right.aspect)
417
+ );
418
+ };
419
+
420
+ export const findPublicTrailContractChangeFacts = (
421
+ input: ContractReleaseFactInput
422
+ ): readonly ContractReleaseFact[] =>
423
+ findPublicTrailContractChangeFactsFromSnapshots(
424
+ createContractSourceSnapshots(input)
425
+ );
@@ -0,0 +1,85 @@
1
+ export {
2
+ nativeBunReleaseBinding,
3
+ releaseBindingCapabilityValues,
4
+ releaseBindingKindValues,
5
+ releaseBindingPlacementValues,
6
+ type ReleaseBindingCapability,
7
+ type ReleaseBindingDescriptor,
8
+ type ReleaseBindingKind,
9
+ type ReleaseBindingPlacement,
10
+ } from './bindings.js';
11
+ export {
12
+ checkReleaseRules,
13
+ discoverWorkspaces,
14
+ formatReleaseCheckReport,
15
+ loadReleaseConfig,
16
+ runReleaseCheck,
17
+ runReleaseCheckCli,
18
+ type ReleaseCheckReport,
19
+ type ReleaseCheckInput,
20
+ type ReleaseCheckResult,
21
+ type ReleaseConfigLoadResult,
22
+ type RunReleaseCheckOptions,
23
+ type WorkspaceInfo,
24
+ } from './check.js';
25
+ export {
26
+ createContractSourceSnapshots,
27
+ findPublicTrailContractChangeFacts,
28
+ findPublicTrailContractChangeFactsFromSnapshots,
29
+ type ContractReleaseFact,
30
+ type ContractReleaseFactAspect,
31
+ type ContractReleaseFactInput,
32
+ type ContractSourceSnapshot,
33
+ } from './contract-facts.js';
34
+ export {
35
+ defaultReleaseConfig,
36
+ defaultReleaseRules,
37
+ releaseConfigSchema,
38
+ releaseFactTypeSchema,
39
+ releaseFactTypeValues,
40
+ releaseIntentSourceSchema,
41
+ releaseIntentSourceValues,
42
+ releaseRuleSchema,
43
+ releaseRuleSeveritySchema,
44
+ releaseRuleSeverityValues,
45
+ type ReleaseConfig,
46
+ type ReleaseConfigInput,
47
+ type ReleaseFactType,
48
+ type ReleaseIntentSource,
49
+ type ReleaseRule,
50
+ type ReleaseRuleInput,
51
+ } from './config.js';
52
+ export {
53
+ findPackedFirstPartyDependencyMismatches,
54
+ runNativeBunPublishCli,
55
+ type NativeBunPublishOptions,
56
+ type NativeBunPublishPackageJson,
57
+ type NativeBunPublishWorkspace,
58
+ } from './native-bun-publish.js';
59
+ export {
60
+ checkRegistryPosture,
61
+ discoverRegistryWorkspaces,
62
+ formatDistTagSummary,
63
+ registryPostureErrors,
64
+ runRegistryPreflight,
65
+ runRegistryPreflightCli,
66
+ type RegistryPreflightOptions,
67
+ type RegistryResult,
68
+ type RegistryView,
69
+ type RegistryWorkspace,
70
+ } from './native-bun-registry.js';
71
+ export {
72
+ releaseSmokeCheckValues,
73
+ runReleaseSmoke,
74
+ type ReleaseSmokeCheck,
75
+ type ReleaseSmokeCheckResult,
76
+ type ReleaseSmokeResult,
77
+ } from './smoke.js';
78
+ export {
79
+ runPackedArtifactsSmoke,
80
+ type PackedArtifactsSmokeResult,
81
+ } from './packed-artifacts-smoke.js';
82
+ export {
83
+ runWayfinderDogfoodSmoke,
84
+ type WayfinderDogfoodSmokeResult,
85
+ } from './wayfinder-dogfood-smoke.js';