@ontrails/trails 1.0.0-beta.13 → 1.0.0-beta.15

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 (165) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/__tests__/examples.test.ts +39 -0
  4. package/dist/src/app.d.ts.map +1 -1
  5. package/dist/src/app.js +12 -1
  6. package/dist/src/app.js.map +1 -1
  7. package/dist/src/cli.js +4 -3
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/trails/add-surface.d.ts +3 -3
  10. package/dist/src/trails/add-surface.d.ts.map +1 -1
  11. package/dist/src/trails/add-surface.js +46 -24
  12. package/dist/src/trails/add-surface.js.map +1 -1
  13. package/dist/src/trails/add-trail.d.ts +3 -1
  14. package/dist/src/trails/add-trail.d.ts.map +1 -1
  15. package/dist/src/trails/add-trail.js +49 -22
  16. package/dist/src/trails/add-trail.js.map +1 -1
  17. package/dist/src/trails/add-trailhead.d.ts +13 -0
  18. package/dist/src/trails/add-trailhead.d.ts.map +1 -0
  19. package/dist/src/trails/add-trailhead.js +88 -0
  20. package/dist/src/trails/add-trailhead.js.map +1 -0
  21. package/dist/src/trails/add-verify.d.ts +1 -1
  22. package/dist/src/trails/add-verify.d.ts.map +1 -1
  23. package/dist/src/trails/add-verify.js +17 -16
  24. package/dist/src/trails/add-verify.js.map +1 -1
  25. package/dist/src/trails/create-scaffold.d.ts +1 -1
  26. package/dist/src/trails/create-scaffold.d.ts.map +1 -1
  27. package/dist/src/trails/create-scaffold.js +34 -27
  28. package/dist/src/trails/create-scaffold.js.map +1 -1
  29. package/dist/src/trails/create.d.ts +9 -13
  30. package/dist/src/trails/create.d.ts.map +1 -1
  31. package/dist/src/trails/create.js +40 -35
  32. package/dist/src/trails/create.js.map +1 -1
  33. package/dist/src/trails/dev-clean.d.ts +9 -0
  34. package/dist/src/trails/dev-clean.d.ts.map +1 -0
  35. package/dist/src/trails/dev-clean.js +66 -0
  36. package/dist/src/trails/dev-clean.js.map +1 -0
  37. package/dist/src/trails/dev-reset.d.ts +6 -0
  38. package/dist/src/trails/dev-reset.d.ts.map +1 -0
  39. package/dist/src/trails/dev-reset.js +39 -0
  40. package/dist/src/trails/dev-reset.js.map +1 -0
  41. package/dist/src/trails/dev-stats.d.ts +7 -0
  42. package/dist/src/trails/dev-stats.d.ts.map +1 -0
  43. package/dist/src/trails/dev-stats.js +61 -0
  44. package/dist/src/trails/dev-stats.js.map +1 -0
  45. package/dist/src/trails/dev-support.d.ts +64 -0
  46. package/dist/src/trails/dev-support.d.ts.map +1 -0
  47. package/dist/src/trails/dev-support.js +181 -0
  48. package/dist/src/trails/dev-support.js.map +1 -0
  49. package/dist/src/trails/draft-promote.d.ts +18 -0
  50. package/dist/src/trails/draft-promote.d.ts.map +1 -0
  51. package/dist/src/trails/draft-promote.js +400 -0
  52. package/dist/src/trails/draft-promote.js.map +1 -0
  53. package/dist/src/trails/guide.d.ts +14 -4
  54. package/dist/src/trails/guide.d.ts.map +1 -1
  55. package/dist/src/trails/guide.js +22 -41
  56. package/dist/src/trails/guide.js.map +1 -1
  57. package/dist/src/trails/load-app.d.ts +9 -1
  58. package/dist/src/trails/load-app.d.ts.map +1 -1
  59. package/dist/src/trails/load-app.js +404 -13
  60. package/dist/src/trails/load-app.js.map +1 -1
  61. package/dist/src/trails/project.d.ts.map +1 -1
  62. package/dist/src/trails/project.js +14 -3
  63. package/dist/src/trails/project.js.map +1 -1
  64. package/dist/src/trails/survey.d.ts +6 -60
  65. package/dist/src/trails/survey.d.ts.map +1 -1
  66. package/dist/src/trails/survey.js +83 -182
  67. package/dist/src/trails/survey.js.map +1 -1
  68. package/dist/src/trails/topo-constants.d.ts +3 -0
  69. package/dist/src/trails/topo-constants.d.ts.map +1 -0
  70. package/dist/src/trails/topo-constants.js +3 -0
  71. package/dist/src/trails/topo-constants.js.map +1 -0
  72. package/dist/src/trails/topo-export.d.ts +19 -0
  73. package/dist/src/trails/topo-export.d.ts.map +1 -0
  74. package/dist/src/trails/topo-export.js +31 -0
  75. package/dist/src/trails/topo-export.js.map +1 -0
  76. package/dist/src/trails/topo-history.d.ts +20 -0
  77. package/dist/src/trails/topo-history.d.ts.map +1 -0
  78. package/dist/src/trails/topo-history.js +32 -0
  79. package/dist/src/trails/topo-history.js.map +1 -0
  80. package/dist/src/trails/topo-pin.d.ts +17 -0
  81. package/dist/src/trails/topo-pin.d.ts.map +1 -0
  82. package/dist/src/trails/topo-pin.js +31 -0
  83. package/dist/src/trails/topo-pin.js.map +1 -0
  84. package/dist/src/trails/topo-read-support.d.ts +58 -0
  85. package/dist/src/trails/topo-read-support.d.ts.map +1 -0
  86. package/dist/src/trails/topo-read-support.js +167 -0
  87. package/dist/src/trails/topo-read-support.js.map +1 -0
  88. package/dist/src/trails/topo-reports.d.ts +54 -0
  89. package/dist/src/trails/topo-reports.d.ts.map +1 -0
  90. package/dist/src/trails/topo-reports.js +128 -0
  91. package/dist/src/trails/topo-reports.js.map +1 -0
  92. package/dist/src/trails/topo-show.d.ts +23 -0
  93. package/dist/src/trails/topo-show.d.ts.map +1 -0
  94. package/dist/src/trails/topo-show.js +49 -0
  95. package/dist/src/trails/topo-show.js.map +1 -0
  96. package/dist/src/trails/topo-store-support.d.ts +13 -0
  97. package/dist/src/trails/topo-store-support.d.ts.map +1 -0
  98. package/dist/src/trails/topo-store-support.js +55 -0
  99. package/dist/src/trails/topo-store-support.js.map +1 -0
  100. package/dist/src/trails/topo-support.d.ts +76 -0
  101. package/dist/src/trails/topo-support.d.ts.map +1 -0
  102. package/dist/src/trails/topo-support.js +132 -0
  103. package/dist/src/trails/topo-support.js.map +1 -0
  104. package/dist/src/trails/topo-unpin.d.ts +20 -0
  105. package/dist/src/trails/topo-unpin.d.ts.map +1 -0
  106. package/dist/src/trails/topo-unpin.js +44 -0
  107. package/dist/src/trails/topo-unpin.js.map +1 -0
  108. package/dist/src/trails/topo-verify.d.ts +5 -0
  109. package/dist/src/trails/topo-verify.d.ts.map +1 -0
  110. package/dist/src/trails/topo-verify.js +24 -0
  111. package/dist/src/trails/topo-verify.js.map +1 -0
  112. package/dist/src/trails/topo.d.ts +5 -0
  113. package/dist/src/trails/topo.d.ts.map +1 -0
  114. package/dist/src/trails/topo.js +63 -0
  115. package/dist/src/trails/topo.js.map +1 -0
  116. package/dist/src/trails/warden.d.ts +3 -2
  117. package/dist/src/trails/warden.d.ts.map +1 -1
  118. package/dist/src/trails/warden.js +37 -27
  119. package/dist/src/trails/warden.js.map +1 -1
  120. package/dist/src/versions.d.ts +12 -0
  121. package/dist/src/versions.d.ts.map +1 -0
  122. package/dist/src/versions.js +23 -0
  123. package/dist/src/versions.js.map +1 -0
  124. package/dist/tsconfig.tsbuildinfo +1 -1
  125. package/package.json +8 -7
  126. package/src/__tests__/add-trail.test.ts +97 -0
  127. package/src/__tests__/create.test.ts +91 -27
  128. package/src/__tests__/draft-promote.test.ts +144 -0
  129. package/src/__tests__/guide.test.ts +10 -5
  130. package/src/__tests__/load-app.test.ts +406 -2
  131. package/src/__tests__/survey.test.ts +221 -60
  132. package/src/__tests__/topo-dev.test.ts +426 -0
  133. package/src/app.ts +24 -2
  134. package/src/clack.ts +1 -1
  135. package/src/cli.ts +4 -3
  136. package/src/trails/add-surface.ts +150 -0
  137. package/src/trails/add-trail.ts +46 -10
  138. package/src/trails/add-verify.ts +11 -6
  139. package/src/trails/create-scaffold.ts +16 -3
  140. package/src/trails/create.ts +76 -71
  141. package/src/trails/dev-clean.ts +77 -0
  142. package/src/trails/dev-reset.ts +45 -0
  143. package/src/trails/dev-stats.ts +67 -0
  144. package/src/trails/dev-support.ts +328 -0
  145. package/src/trails/draft-promote.ts +739 -0
  146. package/src/trails/guide.ts +23 -41
  147. package/src/trails/load-app.ts +556 -14
  148. package/src/trails/project.ts +17 -3
  149. package/src/trails/survey.ts +110 -285
  150. package/src/trails/topo-constants.ts +2 -0
  151. package/src/trails/topo-export.ts +35 -0
  152. package/src/trails/topo-history.ts +38 -0
  153. package/src/trails/topo-pin.ts +38 -0
  154. package/src/trails/topo-read-support.ts +329 -0
  155. package/src/trails/topo-reports.ts +228 -0
  156. package/src/trails/topo-show.ts +54 -0
  157. package/src/trails/topo-store-support.ts +104 -0
  158. package/src/trails/topo-support.ts +230 -0
  159. package/src/trails/topo-unpin.ts +56 -0
  160. package/src/trails/topo-verify.ts +25 -0
  161. package/src/trails/topo.ts +69 -0
  162. package/src/trails/warden.ts +13 -3
  163. package/src/versions.ts +43 -0
  164. package/tsconfig.tests.json +10 -0
  165. package/src/trails/add-trailhead.ts +0 -121
@@ -1,19 +1,30 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from 'node:fs';
9
+ import { join, resolve } from 'node:path';
2
10
 
3
- import { Result, provision, topo, trail } from '@ontrails/core';
11
+ import { ConflictError, Result, resource, topo, trail } from '@ontrails/core';
4
12
  import {
5
- generateTrailheadMap,
6
- hashTrailheadMap,
7
- diffTrailheadMaps,
13
+ deriveSurfaceMap,
14
+ deriveSurfaceMapHash,
15
+ deriveSurfaceMapDiff,
16
+ writeSurfaceMap,
8
17
  } from '@ontrails/schema';
9
- import type { TrailheadMap } from '@ontrails/schema';
18
+ import type { SurfaceMap } from '@ontrails/schema';
10
19
  import { z } from 'zod';
11
20
 
12
21
  import {
13
- generateBriefReport,
14
- generateSurveyList,
15
- generateTrailDetail,
22
+ deriveBriefReport,
23
+ deriveSurveyList,
24
+ deriveTrailDetail,
25
+ surveyTrail,
16
26
  } from '../trails/survey.js';
27
+ import { loadApp } from '../trails/load-app.js';
17
28
  import type {
18
29
  BriefReport,
19
30
  SurveyListReport,
@@ -30,9 +41,13 @@ const helloTrail = trail('hello', {
30
41
  return Result.ok({ message: `Hello, ${name}!` });
31
42
  },
32
43
  description: 'Say hello',
33
- detours: {
34
- NotFoundError: ['search'],
35
- },
44
+ detours: [
45
+ {
46
+ on: ConflictError,
47
+ /* oxlint-disable-next-line require-await -- test stub */
48
+ recover: async () => Result.ok({ message: 'recovered' }),
49
+ },
50
+ ],
36
51
  examples: [
37
52
  {
38
53
  expected: { message: 'Hello, world!' },
@@ -43,8 +58,8 @@ const helloTrail = trail('hello', {
43
58
  input: z.object({ name: z.string().optional() }),
44
59
  intent: 'read',
45
60
  output: z.object({ message: z.string() }),
46
- provisions: [
47
- provision('db.main', {
61
+ resources: [
62
+ resource('db.main', {
48
63
  create: () => Result.ok({ source: 'factory' }),
49
64
  }),
50
65
  ],
@@ -57,62 +72,124 @@ const byeTrail = trail('bye', {
57
72
  output: z.object({ message: z.string() }),
58
73
  });
59
74
 
60
- const [dbProvision] = helloTrail.provisions;
61
- if (!dbProvision) {
75
+ const [dbResource] = helloTrail.resources;
76
+ if (!dbResource) {
62
77
  throw new Error('Expected helloTrail to declare db.main');
63
78
  }
64
79
 
65
80
  const app = topo('test-app', {
66
81
  bye: byeTrail,
67
- dbProvision,
82
+ dbResource,
68
83
  hello: helloTrail,
69
84
  });
70
85
 
86
+ const expectOk = <T>(result: Result<T, Error>): T => {
87
+ if (result.isErr()) {
88
+ throw result.error;
89
+ }
90
+ return result.value;
91
+ };
92
+
93
+ const writeSurveyAppFixture = (
94
+ dir: string,
95
+ options?: { withBye?: boolean }
96
+ ) => {
97
+ mkdirSync(join(dir, 'src'), { recursive: true });
98
+ const byeSource = options?.withBye
99
+ ? `
100
+ const bye = trail('bye', {
101
+ blaze: async (input) => Result.ok({ message: \`Bye, \${input.name ?? 'world'}!\` }),
102
+ input: z.object({ name: z.string().optional() }),
103
+ intent: 'read',
104
+ output: z.object({ message: z.string() }),
105
+ });
106
+ `
107
+ : '';
108
+ const topoMembers = options?.withBye
109
+ ? '{ bye, dbMain, hello }'
110
+ : '{ dbMain, hello }';
111
+ writeFileSync(
112
+ join(dir, 'src', 'app.ts'),
113
+ `import { Result, resource, topo, trail } from '@ontrails/core';
114
+ import { z } from 'zod';
115
+
116
+ const hello = trail('hello', {
117
+ blaze: async (input) => Result.ok({ message: \`Hello, \${input.name ?? 'world'}!\` }),
118
+ input: z.object({ name: z.string().optional() }),
119
+ intent: 'read',
120
+ output: z.object({ message: z.string() }),
121
+ resources: [
122
+ resource('db.main', {
123
+ create: () => Result.ok({ source: 'factory' }),
124
+ }),
125
+ ],
126
+ });
127
+
128
+ const [dbMain] = hello.resources;
129
+ if (!dbMain) {
130
+ throw new Error('expected hello to declare db.main');
131
+ }
132
+
133
+ ${byeSource}
134
+
135
+ export const app = topo('survey-fixture', ${topoMembers});
136
+ `
137
+ );
138
+ };
139
+
140
+ const repoTempDir = (): string =>
141
+ join(
142
+ resolve(import.meta.dir, '../..'),
143
+ '.tmp-tests',
144
+ `trails-survey-${Date.now()}-${Math.random().toString(36).slice(2)}`
145
+ );
146
+
71
147
  // ---------------------------------------------------------------------------
72
148
  // Tests
73
149
  // ---------------------------------------------------------------------------
74
150
 
75
151
  describe('trails survey', () => {
76
- test('generateTrailheadMap includes all trails', () => {
77
- const trailheadMap = generateTrailheadMap(app);
78
- expect(trailheadMap.entries.length).toBe(3);
79
- const ids = trailheadMap.entries.map((e) => e.id);
152
+ test('deriveSurfaceMap includes all trails', () => {
153
+ const surfaceMap = deriveSurfaceMap(app);
154
+ expect(surfaceMap.entries.length).toBe(3);
155
+ const ids = surfaceMap.entries.map((e) => e.id);
80
156
  expect(ids).toContain('hello');
81
157
  expect(ids).toContain('bye');
82
158
  expect(ids).toContain('db.main');
83
159
  });
84
160
 
85
- test('trailhead map entries have expected fields', () => {
86
- const trailheadMap = generateTrailheadMap(app);
87
- const hello = trailheadMap.entries.find((e) => e.id === 'hello');
161
+ test('surface map entries have expected fields', () => {
162
+ const surfaceMap = deriveSurfaceMap(app);
163
+ const hello = surfaceMap.entries.find((e) => e.id === 'hello');
88
164
  expect(hello).toBeDefined();
165
+ expect(hello?.cli?.path).toEqual(['hello']);
89
166
  expect(hello?.kind).toBe('trail');
90
167
  expect(hello?.intent).toBe('read');
91
168
  expect(hello?.exampleCount).toBe(1);
92
- expect(hello?.provisions).toEqual(['db.main']);
169
+ expect(hello?.resources).toEqual(['db.main']);
93
170
  });
94
171
 
95
172
  test('JSON output is valid JSON', () => {
96
- const trailheadMap = generateTrailheadMap(app);
97
- const json = JSON.stringify(trailheadMap, null, 2);
98
- const parsed = JSON.parse(json) as TrailheadMap;
173
+ const surfaceMap = deriveSurfaceMap(app);
174
+ const json = JSON.stringify(surfaceMap, null, 2);
175
+ const parsed = JSON.parse(json) as SurfaceMap;
99
176
  expect(parsed.version).toBe('1.0');
100
177
  expect(parsed.entries.length).toBe(3);
101
178
  });
102
179
 
103
- test('hashTrailheadMap produces stable hash', () => {
104
- const trailheadMap = generateTrailheadMap(app);
105
- const hash1 = hashTrailheadMap(trailheadMap);
106
- const hash2 = hashTrailheadMap(trailheadMap);
180
+ test('deriveSurfaceMapHash produces stable hash', () => {
181
+ const surfaceMap = deriveSurfaceMap(app);
182
+ const hash1 = deriveSurfaceMapHash(surfaceMap);
183
+ const hash2 = deriveSurfaceMapHash(surfaceMap);
107
184
  expect(hash1).toBe(hash2);
108
185
  // SHA-256 hex
109
186
  expect(hash1.length).toBe(64);
110
187
  });
111
188
 
112
- test('diffTrailheadMaps detects added trails', () => {
113
- const prev = generateTrailheadMap(topo('test', { hello: helloTrail }));
114
- const curr = generateTrailheadMap(app);
115
- const diff = diffTrailheadMaps(prev, curr);
189
+ test('deriveSurfaceMapDiff detects added trails', () => {
190
+ const prev = deriveSurfaceMap(topo('test', { hello: helloTrail }));
191
+ const curr = deriveSurfaceMap(app);
192
+ const diff = deriveSurfaceMapDiff(prev, curr);
116
193
 
117
194
  expect(diff.info.length).toBeGreaterThan(0);
118
195
  const addedBye = diff.info.find((e) => e.id === 'bye');
@@ -120,10 +197,10 @@ describe('trails survey', () => {
120
197
  expect(addedBye?.change).toBe('added');
121
198
  });
122
199
 
123
- test('diffTrailheadMaps detects removed trails', () => {
124
- const prev = generateTrailheadMap(app);
125
- const curr = generateTrailheadMap(topo('test', { hello: helloTrail }));
126
- const diff = diffTrailheadMaps(prev, curr);
200
+ test('deriveSurfaceMapDiff detects removed trails', () => {
201
+ const prev = deriveSurfaceMap(app);
202
+ const curr = deriveSurfaceMap(topo('test', { hello: helloTrail }));
203
+ const diff = deriveSurfaceMapDiff(prev, curr);
127
204
 
128
205
  expect(diff.hasBreaking).toBe(true);
129
206
  const removedBye = diff.breaking.find((e) => e.id === 'bye');
@@ -131,9 +208,9 @@ describe('trails survey', () => {
131
208
  expect(removedBye?.change).toBe('removed');
132
209
  });
133
210
 
134
- test('diffTrailheadMaps returns empty for identical maps', () => {
135
- const trailheadMap = generateTrailheadMap(app);
136
- const diff = diffTrailheadMaps(trailheadMap, trailheadMap);
211
+ test('deriveSurfaceMapDiff returns empty for identical maps', () => {
212
+ const surfaceMap = deriveSurfaceMap(app);
213
+ const diff = deriveSurfaceMapDiff(surfaceMap, surfaceMap);
137
214
  expect(diff.entries.length).toBe(0);
138
215
  expect(diff.hasBreaking).toBe(false);
139
216
  });
@@ -145,72 +222,156 @@ describe('trails survey', () => {
145
222
 
146
223
  describe('trails survey --brief', () => {
147
224
  test('produces a valid capability report', () => {
148
- const report = generateBriefReport(app);
225
+ const report = deriveBriefReport(app);
149
226
  expect(report.name).toBe('test-app');
150
227
  expect(report.contractVersion).toBe('2026-03');
151
228
  });
152
229
 
153
230
  test('report includes correct trail count', () => {
154
- const report = generateBriefReport(app);
231
+ const report = deriveBriefReport(app);
155
232
  expect(report.trails).toBe(2);
156
233
  expect(report.signals).toBe(0);
157
- expect(report.provisions).toBe(1);
234
+ expect(report.resources).toBe(1);
158
235
  });
159
236
 
160
237
  test('detects features in use', () => {
161
- const report = generateBriefReport(app);
238
+ const report = deriveBriefReport(app);
162
239
  expect(report.features.outputSchemas).toBe(true);
163
240
  expect(report.features.examples).toBe(true);
164
241
  expect(report.features.detours).toBe(true);
165
242
  expect(report.features.signals).toBe(false);
166
- expect(report.features.provisions).toBe(true);
243
+ expect(report.features.resources).toBe(true);
167
244
  });
168
245
 
169
246
  test('JSON output is valid', () => {
170
- const report = generateBriefReport(app);
247
+ const report = deriveBriefReport(app);
171
248
  const json = JSON.stringify(report, null, 2);
172
249
  const parsed = JSON.parse(json) as BriefReport;
173
250
  expect(parsed.name).toBe('test-app');
174
251
  expect(parsed.trails).toBe(2);
175
- expect(parsed.provisions).toBe(1);
252
+ expect(parsed.resources).toBe(1);
176
253
  });
177
254
 
178
255
  test('empty app reports zero features', () => {
179
256
  const emptyApp = topo('empty', {});
180
- const report = generateBriefReport(emptyApp);
257
+ const report = deriveBriefReport(emptyApp);
181
258
  expect(report.trails).toBe(0);
182
259
  expect(report.features.outputSchemas).toBe(false);
183
260
  expect(report.features.examples).toBe(false);
184
261
  expect(report.features.detours).toBe(false);
185
- expect(report.features.provisions).toBe(false);
262
+ expect(report.features.resources).toBe(false);
186
263
  });
187
264
  });
188
265
 
189
266
  describe('trails survey detail', () => {
190
- test('trail detail includes declared provisions, crossings, and intent', () => {
191
- const detail = generateTrailDetail(helloTrail);
267
+ test('trail detail includes declared resources, crossings, and intent', () => {
268
+ const detail = deriveTrailDetail(helloTrail);
192
269
  const parsed = structuredClone(detail) as TrailDetailReport;
193
270
 
194
271
  expect(parsed.crosses).toEqual([]);
195
272
  expect(parsed.intent).toBe('read');
196
- expect(parsed.provisions).toEqual(['db.main']);
273
+ expect(parsed.resources).toEqual(['db.main']);
197
274
  });
198
275
  });
199
276
 
200
- describe('trails survey provisions section', () => {
201
- test('list output includes provision lifetime and health status', () => {
202
- const report = generateSurveyList(app);
277
+ describe('trails survey resources section', () => {
278
+ test('list output includes resource lifetime and health status', () => {
279
+ const report = deriveSurveyList(app);
203
280
  const parsed = structuredClone(report) as SurveyListReport;
204
- const db = parsed.provisions.find((entry) => entry.id === 'db.main');
281
+ const db = parsed.resources.find((entry) => entry.id === 'db.main');
205
282
 
206
- expect(parsed.provisionCount).toBe(1);
283
+ expect(parsed.resourceCount).toBe(1);
207
284
  expect(db).toEqual({
208
285
  description: null,
209
286
  health: 'none',
210
287
  id: 'db.main',
211
- kind: 'provision',
288
+ kind: 'resource',
212
289
  lifetime: 'singleton',
213
290
  usedBy: ['hello'],
214
291
  });
215
292
  });
216
293
  });
294
+
295
+ describe('trails survey generate', () => {
296
+ test('delegates to topo export and writes a structured lock', async () => {
297
+ const dir = repoTempDir();
298
+
299
+ try {
300
+ writeSurveyAppFixture(dir);
301
+
302
+ const generated = expectOk(
303
+ await surveyTrail.blaze({ generate: true, module: './src/app.ts' }, {
304
+ cwd: dir,
305
+ } as never)
306
+ ) as {
307
+ readonly hash: string;
308
+ readonly lockPath: string;
309
+ readonly mapPath: string;
310
+ };
311
+
312
+ expect(generated.hash).toHaveLength(64);
313
+ expect(existsSync(join(dir, '.trails', '_surface.json'))).toBe(true);
314
+ expect(existsSync(join(dir, '.trails', 'trails.lock'))).toBe(true);
315
+ expect(
316
+ JSON.parse(readFileSync(join(dir, '.trails', 'trails.lock'), 'utf8'))
317
+ ).toMatchObject({
318
+ hash: generated.hash,
319
+ version: 1,
320
+ });
321
+ } finally {
322
+ rmSync(dir, { force: true, recursive: true });
323
+ }
324
+ });
325
+ });
326
+
327
+ describe('trails survey diffSaved', () => {
328
+ test('returns an error when no saved surface map exists yet', async () => {
329
+ const dir = repoTempDir();
330
+
331
+ try {
332
+ writeSurveyAppFixture(dir);
333
+
334
+ const result = await surveyTrail.blaze(
335
+ { diffSaved: true, module: './src/app.ts' },
336
+ { cwd: dir } as never
337
+ );
338
+
339
+ expect(result.isErr()).toBe(true);
340
+ expect(result.error.message).toContain('Run `trails topo export` first');
341
+ } finally {
342
+ rmSync(dir, { force: true, recursive: true });
343
+ }
344
+ });
345
+
346
+ test('diffs against the saved local surface map', async () => {
347
+ const dir = repoTempDir();
348
+
349
+ try {
350
+ writeSurveyAppFixture(dir);
351
+ const baselineApp = await loadApp('./src/app.ts', dir);
352
+ await writeSurfaceMap(deriveSurfaceMap(baselineApp), {
353
+ dir: join(dir, '.trails'),
354
+ });
355
+
356
+ writeSurveyAppFixture(dir, { withBye: true });
357
+
358
+ const result = await surveyTrail.blaze(
359
+ { diffSaved: true, module: './src/app.ts' },
360
+ { cwd: dir } as never
361
+ );
362
+
363
+ expect(result.isOk()).toBe(true);
364
+ expect(result.value).toMatchObject({
365
+ hasBreaking: false,
366
+ info: [
367
+ expect.objectContaining({
368
+ change: 'added',
369
+ id: 'bye',
370
+ }),
371
+ ],
372
+ });
373
+ } finally {
374
+ rmSync(dir, { force: true, recursive: true });
375
+ }
376
+ });
377
+ });