@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/trails",
3
- "version": "1.0.0-beta.12",
3
+ "version": "1.0.0-beta.14",
4
4
  "bin": {
5
5
  "trails": "./bin/trails.ts"
6
6
  },
@@ -14,15 +14,16 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@clack/prompts": "^1.1.0",
17
- "@ontrails/cli": "^1.0.0-beta.12",
18
- "@ontrails/core": "^1.0.0-beta.12",
19
- "@ontrails/logging": "^1.0.0-beta.12",
20
- "@ontrails/schema": "^1.0.0-beta.12",
21
- "@ontrails/warden": "^1.0.0-beta.12",
17
+ "@ontrails/cli": "^1.0.0-beta.13",
18
+ "@ontrails/core": "^1.0.0-beta.13",
19
+ "@ontrails/logging": "^1.0.0-beta.13",
20
+ "@ontrails/schema": "^1.0.0-beta.13",
21
+ "@ontrails/tracker": "^1.0.0-beta.13",
22
+ "@ontrails/warden": "^1.0.0-beta.13",
22
23
  "commander": "^14.0.3",
23
24
  "zod": "^4.3.5"
24
25
  },
25
26
  "devDependencies": {
26
- "@ontrails/testing": "^1.0.0-beta.12"
27
+ "@ontrails/testing": "^1.0.0-beta.13"
27
28
  }
28
29
  }
@@ -11,19 +11,19 @@ import { basename, dirname, join } from 'node:path';
11
11
 
12
12
  import { Result } from '@ontrails/core';
13
13
 
14
- import { addSurface } from '../trails/add-surface.js';
14
+ import { addTrailhead } from '../trails/add-trailhead.js';
15
15
  import { addVerify } from '../trails/add-verify.js';
16
16
  import { createRoute } from '../trails/create.js';
17
17
  import { createScaffold } from '../trails/create-scaffold.js';
18
18
  import { isInsideProject } from '../trails/project.js';
19
19
 
20
20
  type Starter = 'empty' | 'entity' | 'hello';
21
- type Surface = 'cli' | 'mcp';
21
+ type Trailhead = 'cli' | 'mcp';
22
22
 
23
23
  const makeTempProject = (): string =>
24
24
  join(
25
25
  tmpdir(),
26
- `trails-blaze-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
26
+ `trails-create-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
27
27
  );
28
28
 
29
29
  const readJson = (dir: string, relativePath: string): Record<string, unknown> =>
@@ -68,43 +68,43 @@ const expectErr = <T>(result: Result<T, Error>): Error => {
68
68
  return result.error;
69
69
  };
70
70
 
71
- const runFollow = async (
71
+ const runCross = async (
72
72
  id: string,
73
73
  input: unknown
74
74
  ): Promise<Result<unknown, Error>> => {
75
75
  switch (id) {
76
76
  case 'create.scaffold': {
77
- return await createScaffold.run(input as never, {} as never);
77
+ return await createScaffold.blaze(input as never, {} as never);
78
78
  }
79
- case 'add.surface': {
80
- return await addSurface.run(input as never, {} as never);
79
+ case 'add.trailhead': {
80
+ return await addTrailhead.blaze(input as never, {} as never);
81
81
  }
82
82
  case 'add.verify': {
83
- return await addVerify.run(input as never, {} as never);
83
+ return await addVerify.blaze(input as never, {} as never);
84
84
  }
85
85
  default: {
86
- return Result.err(new Error(`Unknown follow target: ${id}`));
86
+ return Result.err(new Error(`Unknown cross target: ${id}`));
87
87
  }
88
88
  }
89
89
  };
90
90
 
91
- const runBlaze = (
91
+ const runCreate = (
92
92
  projectDir: string,
93
93
  overrides?: Partial<{
94
94
  starter: Starter;
95
- surfaces: readonly Surface[];
95
+ trailheads: readonly Trailhead[];
96
96
  verify: boolean;
97
97
  }>
98
98
  ) =>
99
- createRoute.run(
99
+ createRoute.blaze(
100
100
  {
101
101
  dir: dirname(projectDir),
102
102
  name: basename(projectDir),
103
103
  starter: overrides?.starter ?? 'hello',
104
- surfaces: [...(overrides?.surfaces ?? ['cli'])],
104
+ trailheads: [...(overrides?.trailheads ?? ['cli'])],
105
105
  verify: overrides?.verify ?? true,
106
106
  },
107
- { follow: runFollow } as never
107
+ { cross: runCross } as never
108
108
  );
109
109
 
110
110
  const setupMinimalProject = (dir: string): void => {
@@ -177,7 +177,7 @@ const assertEntityStarter = (dir: string): void => {
177
177
  'src/trails/entity.ts',
178
178
  'src/trails/search.ts',
179
179
  'src/trails/onboard.ts',
180
- 'src/events/entity-events.ts',
180
+ 'src/signals/entity-signals.ts',
181
181
  'src/store.ts',
182
182
  ],
183
183
  true
@@ -187,7 +187,7 @@ const assertEntityStarter = (dir: string): void => {
187
187
  "import * as entity from './trails/entity.js'",
188
188
  "import * as search from './trails/search.js'",
189
189
  "import * as onboard from './trails/onboard.js'",
190
- "import * as entityEvents from './events/entity-events.js'",
190
+ "import * as entitySignals from './signals/entity-signals.js'",
191
191
  ]);
192
192
  expectContainsAll(readText(dir, 'src/trails/entity.ts'), [
193
193
  "import { Result, trail } from '@ontrails/core'",
@@ -204,12 +204,12 @@ const assertEntityStarter = (dir: string): void => {
204
204
  ]);
205
205
  };
206
206
 
207
- const assertMcpSurface = (dir: string): void => {
207
+ const assertMcpTrailhead = (dir: string): void => {
208
208
  expectPaths(dir, ['src/mcp.ts'], true);
209
209
  expectPaths(dir, ['src/cli.ts'], false);
210
210
  expectContainsAll(readText(dir, 'src/mcp.ts'), [
211
- "import { blaze } from '@ontrails/mcp'",
212
- 'await blaze(app)',
211
+ "import { trailhead } from '@ontrails/mcp'",
212
+ 'await trailhead(app)',
213
213
  ]);
214
214
 
215
215
  const deps = readJson(dir, 'package.json')['dependencies'] as Record<
@@ -244,11 +244,11 @@ const withTempProject = async (
244
244
  }
245
245
  };
246
246
 
247
- describe('trails blaze', () => {
247
+ describe('trails create', () => {
248
248
  describe('create mode', () => {
249
249
  test('generates project structure with defaults', async () => {
250
250
  await withTempProject(async (dir) => {
251
- expectOk(await runBlaze(dir));
251
+ expectOk(await runCreate(dir));
252
252
  assertDefaultProjectFiles(dir);
253
253
  assertCliPackage(dir);
254
254
  assertHelloApp(dir);
@@ -257,46 +257,46 @@ describe('trails blaze', () => {
257
257
 
258
258
  test('generates with entity starter', async () => {
259
259
  await withTempProject(async (dir) => {
260
- expectOk(await runBlaze(dir, { starter: 'entity' }));
260
+ expectOk(await runCreate(dir, { starter: 'entity' }));
261
261
  assertEntityStarter(dir);
262
262
  });
263
263
  });
264
264
 
265
- test('generates with MCP surface', async () => {
265
+ test('generates with MCP trailhead', async () => {
266
266
  await withTempProject(async (dir) => {
267
- expectOk(await runBlaze(dir, { surfaces: ['mcp'] }));
268
- assertMcpSurface(dir);
267
+ expectOk(await runCreate(dir, { trailheads: ['mcp'] }));
268
+ assertMcpTrailhead(dir);
269
269
  });
270
270
  });
271
271
 
272
272
  test('skips verification when verify is false', async () => {
273
273
  await withTempProject(async (dir) => {
274
- expectOk(await runBlaze(dir, { verify: false }));
274
+ expectOk(await runCreate(dir, { verify: false }));
275
275
  assertVerifySkipped(dir);
276
276
  });
277
277
  });
278
278
 
279
279
  test('generates with empty starter', async () => {
280
280
  await withTempProject(async (dir) => {
281
- expectOk(await runBlaze(dir, { starter: 'empty' }));
281
+ expectOk(await runCreate(dir, { starter: 'empty' }));
282
282
  assertEmptyStarter(dir);
283
283
  });
284
284
  });
285
285
  });
286
286
 
287
- describe('add-surface mode', () => {
287
+ describe('add-trailhead mode', () => {
288
288
  test('adds MCP to existing project', async () => {
289
289
  await withTempProject(async (dir) => {
290
290
  setupMinimalProject(dir);
291
291
  const result = expectOk(
292
- await addSurface.run({ dir, surface: 'mcp' }, {} as never)
292
+ await addTrailhead.blaze({ dir, trailhead: 'mcp' }, {} as never)
293
293
  );
294
294
 
295
295
  expect(result.created).toBe('src/mcp.ts');
296
296
  expect(result.dependency).toBe('@ontrails/mcp');
297
297
  expectPaths(dir, ['src/mcp.ts'], true);
298
298
  expectContainsAll(readText(dir, 'src/mcp.ts'), [
299
- "import { blaze } from '@ontrails/mcp'",
299
+ "import { trailhead } from '@ontrails/mcp'",
300
300
  ]);
301
301
  const deps = readJson(dir, 'package.json')['dependencies'] as Record<
302
302
  string,
@@ -306,16 +306,18 @@ describe('trails blaze', () => {
306
306
  });
307
307
  });
308
308
 
309
- test('detects already-blazed surface', async () => {
309
+ test('detects existing trailhead entrypoint', async () => {
310
310
  await withTempProject(async (dir) => {
311
311
  mkdirSync(join(dir, 'src'), { recursive: true });
312
312
  mkdirSync(join(dir, '.trails'), { recursive: true });
313
313
  writeFileSync(join(dir, 'src', 'mcp.ts'), 'existing content');
314
314
 
315
315
  const error = expectErr(
316
- await addSurface.run({ dir, surface: 'mcp' }, {} as never)
316
+ await addTrailhead.blaze({ dir, trailhead: 'mcp' }, {} as never)
317
+ );
318
+ expect(error.message).toBe(
319
+ 'MCP trailhead already exists. Nothing to do.'
317
320
  );
318
- expect(error.message).toBe('MCP is already blazed. Nothing to do.');
319
321
  });
320
322
  });
321
323
  });
@@ -0,0 +1,144 @@
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';
10
+
11
+ import type { Result } from '@ontrails/core';
12
+ import { ValidationError } from '@ontrails/core';
13
+
14
+ import { draftPromoteTrail } from '../trails/draft-promote.js';
15
+
16
+ const repoTempDir = (): string =>
17
+ join(
18
+ resolve(import.meta.dir, '../..'),
19
+ '.tmp-tests',
20
+ `draft-promote-${Date.now()}-${Math.random().toString(36).slice(2)}`
21
+ );
22
+
23
+ const expectOk = <T>(result: Result<T, Error>): T => {
24
+ if (result.isErr()) {
25
+ throw result.error;
26
+ }
27
+ return result.value;
28
+ };
29
+
30
+ const expectErr = <E extends Error>(result: Result<unknown, E>): E => {
31
+ if (result.isOk()) {
32
+ throw new Error('expected result to be an error');
33
+ }
34
+ return result.error;
35
+ };
36
+
37
+ const writeDraftPromoteFixture = (dir: string): void => {
38
+ mkdirSync(join(dir, 'src'), { recursive: true });
39
+
40
+ writeFileSync(
41
+ join(dir, 'src', 'app.ts'),
42
+ `import { topo } from '@ontrails/core';
43
+ import { draftPrepare } from './_draft.prepare.js';
44
+ import { exportTrail } from './export.js';
45
+
46
+ export const app = topo('draft-test', { draftPrepare, exportTrail });
47
+ `
48
+ );
49
+
50
+ writeFileSync(
51
+ join(dir, 'src', '_draft.prepare.ts'),
52
+ `import { Result, trail } from '@ontrails/core';
53
+ import { z } from 'zod';
54
+
55
+ export const draftPrepare = trail('_draft.entity.prepare', {
56
+ blaze: async () => Result.ok({ ready: true }),
57
+ input: z.object({}),
58
+ output: z.object({ ready: z.boolean() }),
59
+ });
60
+ `
61
+ );
62
+
63
+ writeFileSync(
64
+ join(dir, 'src', 'export.ts'),
65
+ `import { Result, trail } from '@ontrails/core';
66
+ import { z } from 'zod';
67
+
68
+ export const exportTrail = trail('entity.export', {
69
+ blaze: async () => Result.ok({ exported: true }),
70
+ crosses: ['_draft.entity.prepare'],
71
+ input: z.object({}),
72
+ output: z.object({ exported: z.boolean() }),
73
+ });
74
+ `
75
+ );
76
+ };
77
+
78
+ const expectDraftPromoteResults = (dir: string): void => {
79
+ expect(existsSync(join(dir, 'src', '_draft.prepare.ts'))).toBe(false);
80
+ expect(existsSync(join(dir, 'src', 'prepare.ts'))).toBe(true);
81
+ expect(readFileSync(join(dir, 'src', 'prepare.ts'), 'utf8')).toContain(
82
+ "trail('entity.prepare'"
83
+ );
84
+ expect(readFileSync(join(dir, 'src', 'export.ts'), 'utf8')).toContain(
85
+ "crosses: ['entity.prepare']"
86
+ );
87
+ expect(readFileSync(join(dir, 'src', 'app.ts'), 'utf8')).toContain(
88
+ "from './prepare.js'"
89
+ );
90
+ };
91
+
92
+ describe('draft.promote', () => {
93
+ test('promotes draft ids, renames files, and updates imports', async () => {
94
+ const dir = repoTempDir();
95
+
96
+ try {
97
+ writeDraftPromoteFixture(dir);
98
+
99
+ const result = expectOk(
100
+ await draftPromoteTrail.blaze(
101
+ {
102
+ fromId: '_draft.entity.prepare',
103
+ renameFiles: true,
104
+ rootDir: dir,
105
+ toId: 'entity.prepare',
106
+ },
107
+ { cwd: dir } as never
108
+ )
109
+ );
110
+
111
+ expect(result.promotedEstablished).toBe(true);
112
+ expect(result.remainingDraftIds).toEqual([]);
113
+ expect(result.updatedFiles).toEqual(
114
+ expect.arrayContaining(['src/app.ts', 'src/export.ts'])
115
+ );
116
+ expect(result.renamedFiles).toEqual([
117
+ {
118
+ from: 'src/_draft.prepare.ts',
119
+ to: 'src/prepare.ts',
120
+ },
121
+ ]);
122
+ expectDraftPromoteResults(dir);
123
+ } finally {
124
+ rmSync(dir, { force: true, recursive: true });
125
+ }
126
+ });
127
+
128
+ test('returns ValidationError when rootDir does not exist', async () => {
129
+ const error = expectErr(
130
+ await draftPromoteTrail.blaze(
131
+ {
132
+ fromId: '_draft.entity.prepare',
133
+ renameFiles: true,
134
+ rootDir: join(repoTempDir(), 'missing'),
135
+ toId: 'entity.prepare',
136
+ },
137
+ { cwd: process.cwd() } as never
138
+ )
139
+ );
140
+
141
+ expect(error).toBeInstanceOf(ValidationError);
142
+ expect(error.message).toContain('rootDir does not exist');
143
+ });
144
+ });
@@ -9,6 +9,10 @@ import { z } from 'zod';
9
9
  // ---------------------------------------------------------------------------
10
10
 
11
11
  const helloTrail = trail('hello', {
12
+ blaze: (input) => {
13
+ const name = input.name ?? 'world';
14
+ return Result.ok({ message: `Hello, ${name}!` });
15
+ },
12
16
  description: 'Say hello',
13
17
  detours: {
14
18
  NotFoundError: ['search'],
@@ -28,10 +32,6 @@ const helloTrail = trail('hello', {
28
32
  input: z.object({ name: z.string().optional() }),
29
33
  intent: 'read',
30
34
  output: z.object({ message: z.string() }),
31
- run: (input) => {
32
- const name = input.name ?? 'world';
33
- return Result.ok({ message: `Hello, ${name}!` });
34
- },
35
35
  });
36
36
 
37
37
  const app = topo('test-app', { hello: helloTrail });
@@ -1,8 +1,37 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
2
4
  import { resolve } from 'node:path';
3
5
 
4
6
  import { loadApp } from '../trails/load-app.js';
5
7
 
8
+ const writeLoadAppFixture = (cwd: string, name: string): void => {
9
+ writeFileSync(
10
+ resolve(cwd, 'src/app.ts'),
11
+ `export const app = {
12
+ name: '${name}',
13
+ trails: new Map(),
14
+ signals: new Map(),
15
+ provisions: new Map()
16
+ };`
17
+ );
18
+ };
19
+
20
+ const assertLoadAppCaching = async (cwd: string): Promise<void> => {
21
+ writeLoadAppFixture(cwd, 'first');
22
+
23
+ const first = await loadApp('./src/app.ts', cwd);
24
+
25
+ writeLoadAppFixture(cwd, 'second');
26
+
27
+ const cached = await loadApp('./src/app.ts', cwd);
28
+ const fresh = await loadApp('./src/app.ts', cwd, { fresh: true });
29
+
30
+ expect(first.name).toBe('first');
31
+ expect(cached.name).toBe('first');
32
+ expect(fresh.name).toBe('second');
33
+ };
34
+
6
35
  describe('loadApp', () => {
7
36
  test('resolves relative module paths from cwd', async () => {
8
37
  // import.meta.dir is src/__tests__/, go up two to get apps/trails/
@@ -12,4 +41,18 @@ describe('loadApp', () => {
12
41
  expect(app.name).toBe('trails');
13
42
  expect(app.get('survey')).toBeDefined();
14
43
  });
44
+
45
+ test('can bypass module caching with fresh loading', async () => {
46
+ const cwd = resolve(
47
+ tmpdir(),
48
+ `trails-load-app-${Date.now()}-${Math.random().toString(36).slice(2)}`
49
+ );
50
+
51
+ try {
52
+ mkdirSync(resolve(cwd, 'src'), { recursive: true });
53
+ await assertLoadAppCaching(cwd);
54
+ } finally {
55
+ rmSync(cwd, { force: true, recursive: true });
56
+ }
57
+ });
15
58
  });