@ontrails/trails 1.0.0-beta.12 → 1.0.0-beta.14

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/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +40 -12
  3. package/__tests__/examples.test.ts +14 -0
  4. package/dist/src/app.d.ts.map +1 -1
  5. package/dist/src/app.js +13 -2
  6. package/dist/src/app.js.map +1 -1
  7. package/dist/src/clack.d.ts +1 -1
  8. package/dist/src/clack.js +1 -1
  9. package/dist/src/cli.js +2 -2
  10. package/dist/src/cli.js.map +1 -1
  11. package/dist/src/trails/add-trail.js +13 -13
  12. package/dist/src/trails/add-trail.js.map +1 -1
  13. package/dist/src/trails/add-trailhead.d.ts +13 -0
  14. package/dist/src/trails/add-trailhead.d.ts.map +1 -0
  15. package/dist/src/trails/add-trailhead.js +88 -0
  16. package/dist/src/trails/add-trailhead.js.map +1 -0
  17. package/dist/src/trails/add-verify.js +10 -10
  18. package/dist/src/trails/add-verify.js.map +1 -1
  19. package/dist/src/trails/create-scaffold.js +26 -26
  20. package/dist/src/trails/create-scaffold.js.map +1 -1
  21. package/dist/src/trails/create.d.ts +6 -6
  22. package/dist/src/trails/create.d.ts.map +1 -1
  23. package/dist/src/trails/create.js +29 -29
  24. package/dist/src/trails/create.js.map +1 -1
  25. package/dist/src/trails/dev-clean.d.ts +9 -0
  26. package/dist/src/trails/dev-clean.d.ts.map +1 -0
  27. package/dist/src/trails/dev-clean.js +65 -0
  28. package/dist/src/trails/dev-clean.js.map +1 -0
  29. package/dist/src/trails/dev-reset.d.ts +6 -0
  30. package/dist/src/trails/dev-reset.d.ts.map +1 -0
  31. package/dist/src/trails/dev-reset.js +38 -0
  32. package/dist/src/trails/dev-reset.js.map +1 -0
  33. package/dist/src/trails/dev-stats.d.ts +7 -0
  34. package/dist/src/trails/dev-stats.d.ts.map +1 -0
  35. package/dist/src/trails/dev-stats.js +61 -0
  36. package/dist/src/trails/dev-stats.js.map +1 -0
  37. package/dist/src/trails/dev-support.d.ts +64 -0
  38. package/dist/src/trails/dev-support.d.ts.map +1 -0
  39. package/dist/src/trails/dev-support.js +178 -0
  40. package/dist/src/trails/dev-support.js.map +1 -0
  41. package/dist/src/trails/draft-promote.d.ts +18 -0
  42. package/dist/src/trails/draft-promote.d.ts.map +1 -0
  43. package/dist/src/trails/draft-promote.js +386 -0
  44. package/dist/src/trails/draft-promote.js.map +1 -0
  45. package/dist/src/trails/guide.d.ts +13 -3
  46. package/dist/src/trails/guide.d.ts.map +1 -1
  47. package/dist/src/trails/guide.js +21 -37
  48. package/dist/src/trails/guide.js.map +1 -1
  49. package/dist/src/trails/load-app.d.ts +3 -1
  50. package/dist/src/trails/load-app.d.ts.map +1 -1
  51. package/dist/src/trails/load-app.js +53 -10
  52. package/dist/src/trails/load-app.js.map +1 -1
  53. package/dist/src/trails/project.d.ts.map +1 -1
  54. package/dist/src/trails/project.js +14 -3
  55. package/dist/src/trails/project.js.map +1 -1
  56. package/dist/src/trails/survey.d.ts +4 -58
  57. package/dist/src/trails/survey.d.ts.map +1 -1
  58. package/dist/src/trails/survey.js +52 -173
  59. package/dist/src/trails/survey.js.map +1 -1
  60. package/dist/src/trails/topo-constants.d.ts +3 -0
  61. package/dist/src/trails/topo-constants.d.ts.map +1 -0
  62. package/dist/src/trails/topo-constants.js +3 -0
  63. package/dist/src/trails/topo-constants.js.map +1 -0
  64. package/dist/src/trails/topo-export.d.ts +18 -0
  65. package/dist/src/trails/topo-export.d.ts.map +1 -0
  66. package/dist/src/trails/topo-export.js +34 -0
  67. package/dist/src/trails/topo-export.js.map +1 -0
  68. package/dist/src/trails/topo-history.d.ts +24 -0
  69. package/dist/src/trails/topo-history.d.ts.map +1 -0
  70. package/dist/src/trails/topo-history.js +33 -0
  71. package/dist/src/trails/topo-history.js.map +1 -0
  72. package/dist/src/trails/topo-pin.d.ts +21 -0
  73. package/dist/src/trails/topo-pin.d.ts.map +1 -0
  74. package/dist/src/trails/topo-pin.js +35 -0
  75. package/dist/src/trails/topo-pin.js.map +1 -0
  76. package/dist/src/trails/topo-read-support.d.ts +54 -0
  77. package/dist/src/trails/topo-read-support.d.ts.map +1 -0
  78. package/dist/src/trails/topo-read-support.js +178 -0
  79. package/dist/src/trails/topo-read-support.js.map +1 -0
  80. package/dist/src/trails/topo-reports.d.ts +50 -0
  81. package/dist/src/trails/topo-reports.d.ts.map +1 -0
  82. package/dist/src/trails/topo-reports.js +122 -0
  83. package/dist/src/trails/topo-reports.js.map +1 -0
  84. package/dist/src/trails/topo-show.d.ts +23 -0
  85. package/dist/src/trails/topo-show.d.ts.map +1 -0
  86. package/dist/src/trails/topo-show.js +53 -0
  87. package/dist/src/trails/topo-show.js.map +1 -0
  88. package/dist/src/trails/topo-store-support.d.ts +13 -0
  89. package/dist/src/trails/topo-store-support.d.ts.map +1 -0
  90. package/dist/src/trails/topo-store-support.js +55 -0
  91. package/dist/src/trails/topo-store-support.js.map +1 -0
  92. package/dist/src/trails/topo-support.d.ts +87 -0
  93. package/dist/src/trails/topo-support.d.ts.map +1 -0
  94. package/dist/src/trails/topo-support.js +165 -0
  95. package/dist/src/trails/topo-support.js.map +1 -0
  96. package/dist/src/trails/topo-unpin.d.ts +15 -0
  97. package/dist/src/trails/topo-unpin.d.ts.map +1 -0
  98. package/dist/src/trails/topo-unpin.js +39 -0
  99. package/dist/src/trails/topo-unpin.js.map +1 -0
  100. package/dist/src/trails/topo-verify.d.ts +5 -0
  101. package/dist/src/trails/topo-verify.d.ts.map +1 -0
  102. package/dist/src/trails/topo-verify.js +28 -0
  103. package/dist/src/trails/topo-verify.js.map +1 -0
  104. package/dist/src/trails/topo.d.ts +5 -0
  105. package/dist/src/trails/topo.d.ts.map +1 -0
  106. package/dist/src/trails/topo.js +67 -0
  107. package/dist/src/trails/topo.js.map +1 -0
  108. package/dist/src/trails/warden.d.ts +1 -1
  109. package/dist/src/trails/warden.d.ts.map +1 -1
  110. package/dist/src/trails/warden.js +28 -27
  111. package/dist/src/trails/warden.js.map +1 -1
  112. package/dist/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +8 -7
  114. package/src/__tests__/create.test.ts +35 -33
  115. package/src/__tests__/draft-promote.test.ts +144 -0
  116. package/src/__tests__/guide.test.ts +4 -4
  117. package/src/__tests__/load-app.test.ts +43 -0
  118. package/src/__tests__/survey.test.ts +140 -55
  119. package/src/__tests__/topo-dev.test.ts +424 -0
  120. package/src/__tests__/warden.test.ts +2 -2
  121. package/src/app.ts +24 -2
  122. package/src/clack.ts +1 -1
  123. package/src/cli.ts +2 -2
  124. package/src/trails/add-trail.ts +13 -13
  125. package/src/trails/{add-surface.ts → add-trailhead.ts} +39 -37
  126. package/src/trails/add-verify.ts +10 -10
  127. package/src/trails/create-scaffold.ts +28 -28
  128. package/src/trails/create.ts +42 -42
  129. package/src/trails/dev-clean.ts +73 -0
  130. package/src/trails/dev-reset.ts +44 -0
  131. package/src/trails/dev-stats.ts +64 -0
  132. package/src/trails/dev-support.ts +326 -0
  133. package/src/trails/draft-promote.ts +704 -0
  134. package/src/trails/guide.ts +29 -44
  135. package/src/trails/load-app.ts +76 -13
  136. package/src/trails/project.ts +17 -3
  137. package/src/trails/survey.ts +80 -279
  138. package/src/trails/topo-constants.ts +2 -0
  139. package/src/trails/topo-export.ts +39 -0
  140. package/src/trails/topo-history.ts +40 -0
  141. package/src/trails/topo-pin.ts +42 -0
  142. package/src/trails/topo-read-support.ts +332 -0
  143. package/src/trails/topo-reports.ts +221 -0
  144. package/src/trails/topo-show.ts +58 -0
  145. package/src/trails/topo-store-support.ts +96 -0
  146. package/src/trails/topo-support.ts +274 -0
  147. package/src/trails/topo-unpin.ts +51 -0
  148. package/src/trails/topo-verify.ts +29 -0
  149. package/src/trails/topo.ts +73 -0
  150. package/src/trails/warden.ts +33 -32
@@ -1,18 +1,27 @@
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, service, topo, trail } from '@ontrails/core';
11
+ import { Result, provision, topo, trail } from '@ontrails/core';
4
12
  import {
5
- generateSurfaceMap,
6
- hashSurfaceMap,
7
- diffSurfaceMaps,
13
+ generateTrailheadMap,
14
+ hashTrailheadMap,
15
+ diffTrailheadMaps,
8
16
  } from '@ontrails/schema';
9
- import type { SurfaceMap } from '@ontrails/schema';
17
+ import type { TrailheadMap } from '@ontrails/schema';
10
18
  import { z } from 'zod';
11
19
 
12
20
  import {
13
21
  generateBriefReport,
14
22
  generateSurveyList,
15
23
  generateTrailDetail,
24
+ surveyTrail,
16
25
  } from '../trails/survey.js';
17
26
  import type {
18
27
  BriefReport,
@@ -25,6 +34,10 @@ import type {
25
34
  // ---------------------------------------------------------------------------
26
35
 
27
36
  const helloTrail = trail('hello', {
37
+ blaze: (input) => {
38
+ const name = input.name ?? 'world';
39
+ return Result.ok({ message: `Hello, ${name}!` });
40
+ },
28
41
  description: 'Say hello',
29
42
  detours: {
30
43
  NotFoundError: ['search'],
@@ -39,80 +52,120 @@ const helloTrail = trail('hello', {
39
52
  input: z.object({ name: z.string().optional() }),
40
53
  intent: 'read',
41
54
  output: z.object({ message: z.string() }),
42
- run: (input) => {
43
- const name = input.name ?? 'world';
44
- return Result.ok({ message: `Hello, ${name}!` });
45
- },
46
- services: [
47
- service('db.main', {
55
+ provisions: [
56
+ provision('db.main', {
48
57
  create: () => Result.ok({ source: 'factory' }),
49
58
  }),
50
59
  ],
51
60
  });
52
61
 
53
62
  const byeTrail = trail('bye', {
63
+ blaze: (input) => Result.ok({ message: `Goodbye, ${input.name}!` }),
54
64
  description: 'Say goodbye',
55
65
  input: z.object({ name: z.string() }),
56
66
  output: z.object({ message: z.string() }),
57
- run: (input) => Result.ok({ message: `Goodbye, ${input.name}!` }),
58
67
  });
59
68
 
60
- const [dbService] = helloTrail.services;
61
- if (!dbService) {
69
+ const [dbProvision] = helloTrail.provisions;
70
+ if (!dbProvision) {
62
71
  throw new Error('Expected helloTrail to declare db.main');
63
72
  }
64
73
 
65
74
  const app = topo('test-app', {
66
75
  bye: byeTrail,
67
- dbService,
76
+ dbProvision,
68
77
  hello: helloTrail,
69
78
  });
70
79
 
80
+ const expectOk = <T>(result: Result<T, Error>): T => {
81
+ if (result.isErr()) {
82
+ throw result.error;
83
+ }
84
+ return result.value;
85
+ };
86
+
87
+ const writeSurveyAppFixture = (dir: string): void => {
88
+ mkdirSync(join(dir, 'src'), { recursive: true });
89
+ writeFileSync(
90
+ join(dir, 'src', 'app.ts'),
91
+ `import { Result, provision, topo, trail } from '@ontrails/core';
92
+ import { z } from 'zod';
93
+
94
+ const hello = trail('hello', {
95
+ blaze: async (input) => Result.ok({ message: \`Hello, \${input.name ?? 'world'}!\` }),
96
+ input: z.object({ name: z.string().optional() }),
97
+ intent: 'read',
98
+ output: z.object({ message: z.string() }),
99
+ provisions: [
100
+ provision('db.main', {
101
+ create: () => Result.ok({ source: 'factory' }),
102
+ }),
103
+ ],
104
+ });
105
+
106
+ const [dbMain] = hello.provisions;
107
+ if (!dbMain) {
108
+ throw new Error('expected hello to declare db.main');
109
+ }
110
+
111
+ export const app = topo('survey-fixture', { dbMain, hello });
112
+ `
113
+ );
114
+ };
115
+
116
+ const repoTempDir = (): string =>
117
+ join(
118
+ resolve(import.meta.dir, '../..'),
119
+ '.tmp-tests',
120
+ `trails-survey-${Date.now()}-${Math.random().toString(36).slice(2)}`
121
+ );
122
+
71
123
  // ---------------------------------------------------------------------------
72
124
  // Tests
73
125
  // ---------------------------------------------------------------------------
74
126
 
75
127
  describe('trails survey', () => {
76
- test('generateSurfaceMap includes all trails', () => {
77
- const surfaceMap = generateSurfaceMap(app);
78
- expect(surfaceMap.entries.length).toBe(3);
79
- const ids = surfaceMap.entries.map((e) => e.id);
128
+ test('generateTrailheadMap includes all trails', () => {
129
+ const trailheadMap = generateTrailheadMap(app);
130
+ expect(trailheadMap.entries.length).toBe(3);
131
+ const ids = trailheadMap.entries.map((e) => e.id);
80
132
  expect(ids).toContain('hello');
81
133
  expect(ids).toContain('bye');
82
134
  expect(ids).toContain('db.main');
83
135
  });
84
136
 
85
- test('surface map entries have expected fields', () => {
86
- const surfaceMap = generateSurfaceMap(app);
87
- const hello = surfaceMap.entries.find((e) => e.id === 'hello');
137
+ test('trailhead map entries have expected fields', () => {
138
+ const trailheadMap = generateTrailheadMap(app);
139
+ const hello = trailheadMap.entries.find((e) => e.id === 'hello');
88
140
  expect(hello).toBeDefined();
141
+ expect(hello?.cli?.path).toEqual(['hello']);
89
142
  expect(hello?.kind).toBe('trail');
90
143
  expect(hello?.intent).toBe('read');
91
144
  expect(hello?.exampleCount).toBe(1);
92
- expect(hello?.services).toEqual(['db.main']);
145
+ expect(hello?.provisions).toEqual(['db.main']);
93
146
  });
94
147
 
95
148
  test('JSON output is valid JSON', () => {
96
- const surfaceMap = generateSurfaceMap(app);
97
- const json = JSON.stringify(surfaceMap, null, 2);
98
- const parsed = JSON.parse(json) as SurfaceMap;
149
+ const trailheadMap = generateTrailheadMap(app);
150
+ const json = JSON.stringify(trailheadMap, null, 2);
151
+ const parsed = JSON.parse(json) as TrailheadMap;
99
152
  expect(parsed.version).toBe('1.0');
100
153
  expect(parsed.entries.length).toBe(3);
101
154
  });
102
155
 
103
- test('hashSurfaceMap produces stable hash', () => {
104
- const surfaceMap = generateSurfaceMap(app);
105
- const hash1 = hashSurfaceMap(surfaceMap);
106
- const hash2 = hashSurfaceMap(surfaceMap);
156
+ test('hashTrailheadMap produces stable hash', () => {
157
+ const trailheadMap = generateTrailheadMap(app);
158
+ const hash1 = hashTrailheadMap(trailheadMap);
159
+ const hash2 = hashTrailheadMap(trailheadMap);
107
160
  expect(hash1).toBe(hash2);
108
161
  // SHA-256 hex
109
162
  expect(hash1.length).toBe(64);
110
163
  });
111
164
 
112
- test('diffSurfaceMaps detects added trails', () => {
113
- const prev = generateSurfaceMap(topo('test', { hello: helloTrail }));
114
- const curr = generateSurfaceMap(app);
115
- const diff = diffSurfaceMaps(prev, curr);
165
+ test('diffTrailheadMaps detects added trails', () => {
166
+ const prev = generateTrailheadMap(topo('test', { hello: helloTrail }));
167
+ const curr = generateTrailheadMap(app);
168
+ const diff = diffTrailheadMaps(prev, curr);
116
169
 
117
170
  expect(diff.info.length).toBeGreaterThan(0);
118
171
  const addedBye = diff.info.find((e) => e.id === 'bye');
@@ -120,10 +173,10 @@ describe('trails survey', () => {
120
173
  expect(addedBye?.change).toBe('added');
121
174
  });
122
175
 
123
- test('diffSurfaceMaps detects removed trails', () => {
124
- const prev = generateSurfaceMap(app);
125
- const curr = generateSurfaceMap(topo('test', { hello: helloTrail }));
126
- const diff = diffSurfaceMaps(prev, curr);
176
+ test('diffTrailheadMaps detects removed trails', () => {
177
+ const prev = generateTrailheadMap(app);
178
+ const curr = generateTrailheadMap(topo('test', { hello: helloTrail }));
179
+ const diff = diffTrailheadMaps(prev, curr);
127
180
 
128
181
  expect(diff.hasBreaking).toBe(true);
129
182
  const removedBye = diff.breaking.find((e) => e.id === 'bye');
@@ -131,9 +184,9 @@ describe('trails survey', () => {
131
184
  expect(removedBye?.change).toBe('removed');
132
185
  });
133
186
 
134
- test('diffSurfaceMaps returns empty for identical maps', () => {
135
- const map = generateSurfaceMap(app);
136
- const diff = diffSurfaceMaps(map, map);
187
+ test('diffTrailheadMaps returns empty for identical maps', () => {
188
+ const trailheadMap = generateTrailheadMap(app);
189
+ const diff = diffTrailheadMaps(trailheadMap, trailheadMap);
137
190
  expect(diff.entries.length).toBe(0);
138
191
  expect(diff.hasBreaking).toBe(false);
139
192
  });
@@ -153,8 +206,8 @@ describe('trails survey --brief', () => {
153
206
  test('report includes correct trail count', () => {
154
207
  const report = generateBriefReport(app);
155
208
  expect(report.trails).toBe(2);
156
- expect(report.events).toBe(0);
157
- expect(report.services).toBe(1);
209
+ expect(report.signals).toBe(0);
210
+ expect(report.provisions).toBe(1);
158
211
  });
159
212
 
160
213
  test('detects features in use', () => {
@@ -162,8 +215,8 @@ describe('trails survey --brief', () => {
162
215
  expect(report.features.outputSchemas).toBe(true);
163
216
  expect(report.features.examples).toBe(true);
164
217
  expect(report.features.detours).toBe(true);
165
- expect(report.features.events).toBe(false);
166
- expect(report.features.services).toBe(true);
218
+ expect(report.features.signals).toBe(false);
219
+ expect(report.features.provisions).toBe(true);
167
220
  });
168
221
 
169
222
  test('JSON output is valid', () => {
@@ -172,7 +225,7 @@ describe('trails survey --brief', () => {
172
225
  const parsed = JSON.parse(json) as BriefReport;
173
226
  expect(parsed.name).toBe('test-app');
174
227
  expect(parsed.trails).toBe(2);
175
- expect(parsed.services).toBe(1);
228
+ expect(parsed.provisions).toBe(1);
176
229
  });
177
230
 
178
231
  test('empty app reports zero features', () => {
@@ -182,35 +235,67 @@ describe('trails survey --brief', () => {
182
235
  expect(report.features.outputSchemas).toBe(false);
183
236
  expect(report.features.examples).toBe(false);
184
237
  expect(report.features.detours).toBe(false);
185
- expect(report.features.services).toBe(false);
238
+ expect(report.features.provisions).toBe(false);
186
239
  });
187
240
  });
188
241
 
189
242
  describe('trails survey detail', () => {
190
- test('trail detail includes declared services, follow, and intent', () => {
243
+ test('trail detail includes declared provisions, crossings, and intent', () => {
191
244
  const detail = generateTrailDetail(helloTrail);
192
245
  const parsed = structuredClone(detail) as TrailDetailReport;
193
246
 
194
- expect(parsed.follow).toEqual([]);
247
+ expect(parsed.crosses).toEqual([]);
195
248
  expect(parsed.intent).toBe('read');
196
- expect(parsed.services).toEqual(['db.main']);
249
+ expect(parsed.provisions).toEqual(['db.main']);
197
250
  });
198
251
  });
199
252
 
200
- describe('trails survey services section', () => {
201
- test('list output includes service lifetime and health status', () => {
253
+ describe('trails survey provisions section', () => {
254
+ test('list output includes provision lifetime and health status', () => {
202
255
  const report = generateSurveyList(app);
203
256
  const parsed = structuredClone(report) as SurveyListReport;
204
- const db = parsed.services.find((entry) => entry.id === 'db.main');
257
+ const db = parsed.provisions.find((entry) => entry.id === 'db.main');
205
258
 
206
- expect(parsed.serviceCount).toBe(1);
259
+ expect(parsed.provisionCount).toBe(1);
207
260
  expect(db).toEqual({
208
261
  description: null,
209
262
  health: 'none',
210
263
  id: 'db.main',
211
- kind: 'service',
264
+ kind: 'provision',
212
265
  lifetime: 'singleton',
213
266
  usedBy: ['hello'],
214
267
  });
215
268
  });
216
269
  });
270
+
271
+ describe('trails survey generate', () => {
272
+ test('delegates to topo export and writes a structured lock', async () => {
273
+ const dir = repoTempDir();
274
+
275
+ try {
276
+ writeSurveyAppFixture(dir);
277
+
278
+ const generated = expectOk(
279
+ await surveyTrail.blaze({ generate: true, module: './src/app.ts' }, {
280
+ cwd: dir,
281
+ } as never)
282
+ ) as {
283
+ readonly hash: string;
284
+ readonly lockPath: string;
285
+ readonly mapPath: string;
286
+ };
287
+
288
+ expect(generated.hash).toHaveLength(64);
289
+ expect(existsSync(join(dir, '.trails', '_trailhead.json'))).toBe(true);
290
+ expect(existsSync(join(dir, '.trails', 'trails.lock'))).toBe(true);
291
+ expect(
292
+ JSON.parse(readFileSync(join(dir, '.trails', 'trails.lock'), 'utf8'))
293
+ ).toMatchObject({
294
+ hash: generated.hash,
295
+ version: 1,
296
+ });
297
+ } finally {
298
+ rmSync(dir, { force: true, recursive: true });
299
+ }
300
+ });
301
+ });