@ontrails/warden 1.0.0-beta.11 → 1.0.0-beta.13

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 (183) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +59 -31
  3. package/README.md +17 -17
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +14 -10
  7. package/dist/cli.js.map +1 -1
  8. package/dist/drift.d.ts +6 -6
  9. package/dist/drift.d.ts.map +1 -1
  10. package/dist/drift.js +8 -8
  11. package/dist/drift.js.map +1 -1
  12. package/dist/formatters.js +2 -2
  13. package/dist/formatters.js.map +1 -1
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +5 -5
  17. package/dist/index.js.map +1 -1
  18. package/dist/rules/ast.d.ts +15 -11
  19. package/dist/rules/ast.d.ts.map +1 -1
  20. package/dist/rules/ast.js +34 -30
  21. package/dist/rules/ast.js.map +1 -1
  22. package/dist/rules/context-no-trailhead-types.d.ts +12 -0
  23. package/dist/rules/context-no-trailhead-types.d.ts.map +1 -0
  24. package/dist/rules/context-no-trailhead-types.js +96 -0
  25. package/dist/rules/context-no-trailhead-types.js.map +1 -0
  26. package/dist/rules/cross-declarations.d.ts +13 -0
  27. package/dist/rules/cross-declarations.d.ts.map +1 -0
  28. package/dist/rules/cross-declarations.js +264 -0
  29. package/dist/rules/cross-declarations.js.map +1 -0
  30. package/dist/rules/follow-declarations.d.ts +1 -1
  31. package/dist/rules/follow-declarations.js +5 -5
  32. package/dist/rules/follow-declarations.js.map +1 -1
  33. package/dist/rules/implementation-returns-result.d.ts +2 -2
  34. package/dist/rules/implementation-returns-result.js +6 -6
  35. package/dist/rules/implementation-returns-result.js.map +1 -1
  36. package/dist/rules/index.d.ts +4 -4
  37. package/dist/rules/index.d.ts.map +1 -1
  38. package/dist/rules/index.js +12 -12
  39. package/dist/rules/index.js.map +1 -1
  40. package/dist/rules/no-direct-impl-in-route.d.ts +4 -4
  41. package/dist/rules/no-direct-impl-in-route.js +14 -14
  42. package/dist/rules/no-direct-impl-in-route.js.map +1 -1
  43. package/dist/rules/no-direct-implementation-call.d.ts +3 -3
  44. package/dist/rules/no-direct-implementation-call.js +7 -7
  45. package/dist/rules/no-direct-implementation-call.js.map +1 -1
  46. package/dist/rules/no-sync-result-assumption.d.ts +1 -1
  47. package/dist/rules/no-sync-result-assumption.js +5 -5
  48. package/dist/rules/no-sync-result-assumption.js.map +1 -1
  49. package/dist/rules/no-throw-in-detour-target.js +2 -2
  50. package/dist/rules/no-throw-in-detour-target.js.map +1 -1
  51. package/dist/rules/no-throw-in-implementation.d.ts +1 -1
  52. package/dist/rules/no-throw-in-implementation.js +3 -3
  53. package/dist/rules/no-throw-in-implementation.js.map +1 -1
  54. package/dist/rules/provision-declarations.d.ts +14 -0
  55. package/dist/rules/provision-declarations.d.ts.map +1 -0
  56. package/dist/rules/provision-declarations.js +344 -0
  57. package/dist/rules/provision-declarations.js.map +1 -0
  58. package/dist/rules/provision-exists.d.ts +6 -0
  59. package/dist/rules/provision-exists.d.ts.map +1 -0
  60. package/dist/rules/provision-exists.js +89 -0
  61. package/dist/rules/provision-exists.js.map +1 -0
  62. package/dist/rules/service-declarations.d.ts +7 -5
  63. package/dist/rules/service-declarations.d.ts.map +1 -1
  64. package/dist/rules/service-declarations.js +106 -103
  65. package/dist/rules/service-declarations.js.map +1 -1
  66. package/dist/rules/service-exists.d.ts +3 -1
  67. package/dist/rules/service-exists.d.ts.map +1 -1
  68. package/dist/rules/service-exists.js +35 -33
  69. package/dist/rules/service-exists.js.map +1 -1
  70. package/dist/rules/specs.d.ts +1 -1
  71. package/dist/rules/specs.d.ts.map +1 -1
  72. package/dist/rules/specs.js +1 -1
  73. package/dist/rules/specs.js.map +1 -1
  74. package/dist/rules/types.d.ts +2 -2
  75. package/dist/rules/types.d.ts.map +1 -1
  76. package/dist/trails/context-no-surface-types.trail.js +1 -1
  77. package/dist/trails/context-no-trailhead-types.trail.d.ts +13 -0
  78. package/dist/trails/context-no-trailhead-types.trail.d.ts.map +1 -0
  79. package/dist/trails/context-no-trailhead-types.trail.js +21 -0
  80. package/dist/trails/context-no-trailhead-types.trail.js.map +1 -0
  81. package/dist/trails/cross-declarations.trail.d.ts +13 -0
  82. package/dist/trails/cross-declarations.trail.d.ts.map +1 -0
  83. package/dist/trails/cross-declarations.trail.js +22 -0
  84. package/dist/trails/cross-declarations.trail.js.map +1 -0
  85. package/dist/trails/follow-declarations.trail.js +1 -1
  86. package/dist/trails/implementation-returns-result.trail.js +1 -1
  87. package/dist/trails/index.d.ts +4 -4
  88. package/dist/trails/index.d.ts.map +1 -1
  89. package/dist/trails/index.js +4 -4
  90. package/dist/trails/index.js.map +1 -1
  91. package/dist/trails/no-direct-impl-in-route.trail.js +4 -4
  92. package/dist/trails/no-direct-impl-in-route.trail.js.map +1 -1
  93. package/dist/trails/no-direct-implementation-call.trail.js +2 -2
  94. package/dist/trails/no-direct-implementation-call.trail.js.map +1 -1
  95. package/dist/trails/no-sync-result-assumption.trail.js +2 -2
  96. package/dist/trails/no-sync-result-assumption.trail.js.map +1 -1
  97. package/dist/trails/no-throw-in-detour-target.trail.d.ts +1 -1
  98. package/dist/trails/no-throw-in-detour-target.trail.js +1 -1
  99. package/dist/trails/no-throw-in-implementation.trail.js +1 -1
  100. package/dist/trails/prefer-schema-inference.trail.js +1 -1
  101. package/dist/trails/provision-declarations.trail.d.ts +13 -0
  102. package/dist/trails/provision-declarations.trail.d.ts.map +1 -0
  103. package/dist/trails/provision-declarations.trail.js +25 -0
  104. package/dist/trails/provision-declarations.trail.js.map +1 -0
  105. package/dist/trails/provision-exists.trail.d.ts +15 -0
  106. package/dist/trails/provision-exists.trail.d.ts.map +1 -0
  107. package/dist/trails/provision-exists.trail.js +27 -0
  108. package/dist/trails/provision-exists.trail.js.map +1 -0
  109. package/dist/trails/run.d.ts +2 -2
  110. package/dist/trails/run.d.ts.map +1 -1
  111. package/dist/trails/run.js +6 -6
  112. package/dist/trails/run.js.map +1 -1
  113. package/dist/trails/schema.d.ts +1 -1
  114. package/dist/trails/schema.js +2 -2
  115. package/dist/trails/schema.js.map +1 -1
  116. package/dist/trails/service-declarations.trail.d.ts +13 -0
  117. package/dist/trails/service-declarations.trail.d.ts.map +1 -1
  118. package/dist/trails/service-declarations.trail.js +9 -7
  119. package/dist/trails/service-declarations.trail.js.map +1 -1
  120. package/dist/trails/service-exists.trail.d.ts +17 -0
  121. package/dist/trails/service-exists.trail.d.ts.map +1 -1
  122. package/dist/trails/service-exists.trail.js +10 -8
  123. package/dist/trails/service-exists.trail.js.map +1 -1
  124. package/dist/trails/valid-describe-refs.trail.d.ts +1 -1
  125. package/dist/trails/valid-detour-refs.trail.d.ts +1 -1
  126. package/dist/trails/valid-detour-refs.trail.js +2 -2
  127. package/dist/trails/wrap-rule.js +14 -14
  128. package/dist/trails/wrap-rule.js.map +1 -1
  129. package/package.json +4 -4
  130. package/src/__tests__/cli.test.ts +8 -8
  131. package/src/__tests__/{follow-declarations.test.ts → cross-declarations.test.ts} +78 -78
  132. package/src/__tests__/drift.test.ts +5 -5
  133. package/src/__tests__/formatters.test.ts +2 -2
  134. package/src/__tests__/implementation-returns-result.test.ts +11 -11
  135. package/src/__tests__/no-direct-implementation-call.test.ts +10 -10
  136. package/src/__tests__/no-sync-result-assumption.test.ts +6 -6
  137. package/src/__tests__/no-throw-in-detour-target.test.ts +6 -6
  138. package/src/__tests__/prefer-schema-inference.test.ts +4 -4
  139. package/src/__tests__/provision-declarations.test.ts +318 -0
  140. package/src/__tests__/provision-exists.test.ts +122 -0
  141. package/src/__tests__/rules.test.ts +38 -38
  142. package/src/__tests__/valid-describe-refs.test.ts +4 -4
  143. package/src/__tests__/wrap-rule.test.ts +4 -4
  144. package/src/cli.ts +17 -13
  145. package/src/drift.ts +12 -12
  146. package/src/formatters.ts +2 -2
  147. package/src/index.ts +8 -8
  148. package/src/rules/ast.ts +36 -31
  149. package/src/rules/{context-no-surface-types.ts → context-no-trailhead-types.ts} +8 -8
  150. package/src/rules/{follow-declarations.ts → cross-declarations.ts} +63 -56
  151. package/src/rules/implementation-returns-result.ts +6 -6
  152. package/src/rules/index.ts +12 -12
  153. package/src/rules/no-direct-impl-in-route.ts +17 -17
  154. package/src/rules/no-direct-implementation-call.ts +7 -7
  155. package/src/rules/no-sync-result-assumption.ts +5 -5
  156. package/src/rules/no-throw-in-detour-target.ts +2 -2
  157. package/src/rules/no-throw-in-implementation.ts +3 -3
  158. package/src/rules/{service-declarations.ts → provision-declarations.ts} +145 -129
  159. package/src/rules/{service-exists.ts → provision-exists.ts} +51 -46
  160. package/src/rules/specs.ts +4 -4
  161. package/src/rules/types.ts +2 -2
  162. package/src/trails/{context-no-surface-types.trail.ts → context-no-trailhead-types.trail.ts} +5 -5
  163. package/src/trails/cross-declarations.trail.ts +22 -0
  164. package/src/trails/implementation-returns-result.trail.ts +1 -1
  165. package/src/trails/index.ts +4 -4
  166. package/src/trails/no-direct-impl-in-route.trail.ts +4 -4
  167. package/src/trails/no-direct-implementation-call.trail.ts +2 -2
  168. package/src/trails/no-sync-result-assumption.trail.ts +2 -2
  169. package/src/trails/no-throw-in-detour-target.trail.ts +1 -1
  170. package/src/trails/no-throw-in-implementation.trail.ts +1 -1
  171. package/src/trails/prefer-schema-inference.trail.ts +1 -1
  172. package/src/trails/provision-declarations.trail.ts +25 -0
  173. package/src/trails/provision-exists.trail.ts +27 -0
  174. package/src/trails/run.ts +7 -7
  175. package/src/trails/schema.ts +2 -2
  176. package/src/trails/valid-detour-refs.trail.ts +2 -2
  177. package/src/trails/wrap-rule.ts +17 -17
  178. package/tsconfig.tsbuildinfo +1 -1
  179. package/src/__tests__/service-declarations.test.ts +0 -318
  180. package/src/__tests__/service-exists.test.ts +0 -122
  181. package/src/trails/follow-declarations.trail.ts +0 -22
  182. package/src/trails/service-declarations.trail.ts +0 -25
  183. package/src/trails/service-exists.trail.ts +0 -27
@@ -10,7 +10,7 @@ trail("entity.show", {
10
10
  fields: {
11
11
  firstName: { label: "First Name" },
12
12
  },
13
- run: (input) => Result.ok(input),
13
+ blaze: (input) => Result.ok(input),
14
14
  })`;
15
15
 
16
16
  const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
@@ -31,7 +31,7 @@ trail("entity.paint", {
31
31
  options: [{ value: "red" }, { value: "green" }],
32
32
  },
33
33
  },
34
- run: (input) => Result.ok(input),
34
+ blaze: (input) => Result.ok(input),
35
35
  })`;
36
36
 
37
37
  const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
@@ -56,7 +56,7 @@ trail("entity.paint", {
56
56
  },
57
57
  displayName: { label: "Public name" },
58
58
  },
59
- run: (input) => Result.ok(input),
59
+ blaze: (input) => Result.ok(input),
60
60
  })`;
61
61
 
62
62
  const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
@@ -74,7 +74,7 @@ trail("entity.show", {
74
74
  message: "Who should we greet?",
75
75
  },
76
76
  },
77
- run: (input) => Result.ok(input),
77
+ blaze: (input) => Result.ok(input),
78
78
  })`;
79
79
 
80
80
  const diagnostics = preferSchemaInference.check(code, 'src/entity.ts');
@@ -0,0 +1,318 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { provisionDeclarations } from '../rules/provision-declarations.js';
4
+
5
+ const TEST_FILE = 'test.ts';
6
+
7
+ describe('provision-declarations', () => {
8
+ describe('clean cases', () => {
9
+ test('declared provisions match provision.from(ctx) usage', () => {
10
+ const code = `
11
+ import { Result, provision, trail } from '@ontrails/core';
12
+
13
+ const db = provision('db.main', {
14
+ create: () => Result.ok({ source: 'factory' }),
15
+ });
16
+
17
+ trail('entity.show', {
18
+ provisions: [db],
19
+ blaze: async (_input, ctx) => {
20
+ return Result.ok({ source: db.from(ctx).source });
21
+ },
22
+ });
23
+ `;
24
+
25
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
26
+
27
+ expect(diagnostics.length).toBe(0);
28
+ });
29
+
30
+ test('local helper named provision() is not treated as ctx lookup', () => {
31
+ const code = `
32
+ import { Result, provision, trail } from '@ontrails/core';
33
+
34
+ const db = provision('db.main', {
35
+ create: () => Result.ok({ source: 'factory' }),
36
+ });
37
+
38
+ trail('entity.show', {
39
+ provisions: [db],
40
+ blaze: async (_input, ctx) => {
41
+ const provision = (id: string) => id;
42
+ return Result.ok({
43
+ resolved: provision('db.main'),
44
+ source: db.from(ctx).source,
45
+ });
46
+ },
47
+ });
48
+ `;
49
+
50
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
51
+
52
+ expect(diagnostics.length).toBe(0);
53
+ });
54
+
55
+ test('declared provisions match ctx.provision() usage', () => {
56
+ const code = `
57
+ import { Result, provision, trail } from '@ontrails/core';
58
+
59
+ const db = provision('db.main', {
60
+ create: () => Result.ok({ source: 'factory' }),
61
+ });
62
+
63
+ trail('entity.show', {
64
+ provisions: [db],
65
+ blaze: async (_input, ctx) => {
66
+ const resolved = ctx.provision('db.main');
67
+ return Result.ok(resolved);
68
+ },
69
+ });
70
+ `;
71
+
72
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
73
+
74
+ expect(diagnostics.length).toBe(0);
75
+ });
76
+
77
+ test('recognizes destructured provision() calls', () => {
78
+ const code = `
79
+ import { Result, provision, trail } from '@ontrails/core';
80
+
81
+ const db = provision('db.main', {
82
+ create: () => Result.ok({ source: 'factory' }),
83
+ });
84
+
85
+ trail('entity.show', {
86
+ provisions: [db],
87
+ blaze: async (_input, ctx) => {
88
+ const { provision } = ctx;
89
+ return Result.ok(provision('db.main'));
90
+ },
91
+ });
92
+ `;
93
+
94
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
95
+
96
+ expect(diagnostics.length).toBe(0);
97
+ });
98
+
99
+ test('recognizes ctx.provision(db) lookups by declared provision object', () => {
100
+ const code = `
101
+ import { Result, provision, trail } from '@ontrails/core';
102
+
103
+ const db = provision('db.main', {
104
+ create: () => Result.ok({ source: 'factory' }),
105
+ });
106
+
107
+ trail('entity.show', {
108
+ provisions: [db],
109
+ blaze: async (_input, ctx) => {
110
+ return Result.ok(ctx.provision(db));
111
+ },
112
+ });
113
+ `;
114
+
115
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
116
+
117
+ expect(diagnostics.length).toBe(0);
118
+ });
119
+ });
120
+
121
+ describe('error cases', () => {
122
+ test('provision.from(ctx) without a declaration produces an error', () => {
123
+ const code = `
124
+ import { Result, provision, trail } from '@ontrails/core';
125
+
126
+ const db = provision('db.main', {
127
+ create: () => Result.ok({ source: 'factory' }),
128
+ });
129
+
130
+ trail('entity.show', {
131
+ blaze: async (_input, ctx) => {
132
+ return Result.ok({ source: db.from(ctx).source });
133
+ },
134
+ });
135
+ `;
136
+
137
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
138
+
139
+ expect(diagnostics.length).toBe(1);
140
+ expect(diagnostics[0]?.severity).toBe('error');
141
+ expect(diagnostics[0]?.rule).toBe('provision-declarations');
142
+ expect(diagnostics[0]?.message).toContain('db.from(ctx)');
143
+ expect(diagnostics[0]?.message).toContain('not declared in provisions');
144
+ });
145
+
146
+ test('ctx.provision() without a declaration produces an error', () => {
147
+ const code = `
148
+ trail('entity.show', {
149
+ blaze: async (_input, ctx) => {
150
+ return Result.ok(ctx.provision('db.main'));
151
+ },
152
+ });
153
+ `;
154
+
155
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
156
+
157
+ expect(diagnostics.length).toBe(1);
158
+ expect(diagnostics[0]?.severity).toBe('error');
159
+ expect(diagnostics[0]?.message).toContain("ctx.provision('db.main')");
160
+ });
161
+
162
+ test('unresolved imported provision declarations do not suppress lookup diagnostics', () => {
163
+ const code = `
164
+ import { Result, trail } from '@ontrails/core';
165
+ import { db } from './provisions';
166
+
167
+ // const db = provision('db.main', {
168
+ // create: () => Result.ok({ source: 'factory' }),
169
+ // });
170
+
171
+ trail('entity.show', {
172
+ provisions: [db],
173
+ blaze: async (_input, ctx) => {
174
+ return Result.ok(ctx.provision('db.main'));
175
+ },
176
+ });
177
+ `;
178
+
179
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
180
+
181
+ expect(diagnostics.length).toBe(1);
182
+ expect(diagnostics[0]?.severity).toBe('error');
183
+ expect(diagnostics[0]?.message).toContain("ctx.provision('db.main')");
184
+ });
185
+
186
+ test('ctx.provision(db) without a declaration produces an error', () => {
187
+ const code = `
188
+ import { Result, provision, trail } from '@ontrails/core';
189
+
190
+ const db = provision('db.main', {
191
+ create: () => Result.ok({ source: 'factory' }),
192
+ });
193
+
194
+ trail('entity.show', {
195
+ blaze: async (_input, ctx) => {
196
+ return Result.ok(ctx.provision(db));
197
+ },
198
+ });
199
+ `;
200
+
201
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
202
+
203
+ expect(diagnostics.length).toBe(1);
204
+ expect(diagnostics[0]?.severity).toBe('error');
205
+ expect(diagnostics[0]?.message).toContain('ctx.provision(db)');
206
+ });
207
+ });
208
+
209
+ describe('warn cases', () => {
210
+ test('declared but unused provision produces a warning', () => {
211
+ const code = `
212
+ import { Result, provision, trail } from '@ontrails/core';
213
+
214
+ const db = provision('db.main', {
215
+ create: () => Result.ok({ source: 'factory' }),
216
+ });
217
+
218
+ trail('entity.show', {
219
+ provisions: [db],
220
+ blaze: async () => {
221
+ return Result.ok({ ok: true });
222
+ },
223
+ });
224
+ `;
225
+
226
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
227
+
228
+ expect(diagnostics.length).toBe(1);
229
+ expect(diagnostics[0]?.severity).toBe('warn');
230
+ expect(diagnostics[0]?.rule).toBe('provision-declarations');
231
+ expect(diagnostics[0]?.message).toContain("'db' declared in provisions");
232
+ expect(diagnostics[0]?.message).toContain('never used');
233
+ });
234
+ });
235
+
236
+ describe('single-object overload', () => {
237
+ test('recognizes trail({ id, provisions, blaze }) form', () => {
238
+ const code = `
239
+ import { Result, provision, trail } from '@ontrails/core';
240
+
241
+ const db = provision('db.main', {
242
+ create: () => Result.ok({ source: 'factory' }),
243
+ });
244
+
245
+ trail({
246
+ id: 'entity.show',
247
+ provisions: [db],
248
+ blaze: async (_input, ctx) => {
249
+ return Result.ok({ source: db.from(ctx).source });
250
+ },
251
+ });
252
+ `;
253
+
254
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
255
+
256
+ expect(diagnostics.length).toBe(0);
257
+ });
258
+ });
259
+
260
+ describe('context parameter naming', () => {
261
+ test('recognizes database.from(context) when second param is named context', () => {
262
+ const code = `
263
+ import { Result, provision, trail } from '@ontrails/core';
264
+
265
+ const database = provision('db.main', {
266
+ create: () => Result.ok({ source: 'factory' }),
267
+ });
268
+
269
+ trail('entity.show', {
270
+ provisions: [database],
271
+ blaze: async (_input, context) => {
272
+ return Result.ok(database.from(context));
273
+ },
274
+ });
275
+ `;
276
+
277
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
278
+
279
+ expect(diagnostics.length).toBe(0);
280
+ });
281
+ });
282
+
283
+ describe('nested run false positives', () => {
284
+ test('meta.run does not trigger false positives', () => {
285
+ const code = `
286
+ import { Result, provision, trail } from '@ontrails/core';
287
+
288
+ const db = provision('db.main', {
289
+ create: () => Result.ok({ source: 'factory' }),
290
+ });
291
+
292
+ trail('entity.show', {
293
+ provisions: [db],
294
+ meta: { blaze: async () => ctx.provision('phantom') },
295
+ blaze: async (_input, ctx) => {
296
+ return Result.ok(db.from(ctx));
297
+ },
298
+ });
299
+ `;
300
+
301
+ const diagnostics = provisionDeclarations.check(code, TEST_FILE);
302
+
303
+ expect(diagnostics.length).toBe(0);
304
+ });
305
+ });
306
+
307
+ test('skips test files', () => {
308
+ const code = `
309
+ trail('entity.show', {
310
+ blaze: async (_input, ctx) => {
311
+ return Result.ok(ctx.provision('db.main'));
312
+ },
313
+ });
314
+ `;
315
+
316
+ expect(provisionDeclarations.check(code, 'entity.test.ts')).toEqual([]);
317
+ });
318
+ });
@@ -0,0 +1,122 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { provisionExists } from '../rules/provision-exists.js';
4
+
5
+ const TEST_FILE = 'entity.ts';
6
+
7
+ describe('provision-exists', () => {
8
+ test('passes when a locally declared provision exists', () => {
9
+ const code = `
10
+ import { Result, provision, trail } from '@ontrails/core';
11
+ import type { Provision } from '@ontrails/core';
12
+
13
+ const db: Provision<{ source: string }> = provision('db.main', {
14
+ create: () => Result.ok({ source: 'factory' }),
15
+ });
16
+
17
+ trail('entity.show', {
18
+ provisions: [db],
19
+ blaze: async (_input, ctx) => Result.ok(db.from(ctx)),
20
+ });
21
+ `;
22
+
23
+ expect(provisionExists.check(code, TEST_FILE)).toEqual([]);
24
+ });
25
+
26
+ test('ignores commented-out provision declarations when resolving local ids', () => {
27
+ const code = `
28
+ import { Result, trail } from '@ontrails/core';
29
+
30
+ trail('entity.show', {
31
+ provisions: ['db.main'],
32
+ blaze: async (_input, ctx) => Result.ok(ctx.provision('db.main')),
33
+ });
34
+
35
+ // const db = provision('db.main', {
36
+ // create: () => Result.ok({ source: 'factory' }),
37
+ // });
38
+ `;
39
+
40
+ const diagnostics = provisionExists.check(code, TEST_FILE);
41
+
42
+ expect(diagnostics).toHaveLength(1);
43
+ expect(diagnostics[0]?.rule).toBe('provision-exists');
44
+ expect(diagnostics[0]?.message).toContain('db.main');
45
+ });
46
+
47
+ test('flags a declared provision missing from project context', () => {
48
+ const code = `
49
+ import { Result, provision, trail } from '@ontrails/core';
50
+
51
+ const db = provision('db.main', {
52
+ create: () => Result.ok({ source: 'factory' }),
53
+ });
54
+
55
+ trail('entity.show', {
56
+ provisions: [db],
57
+ blaze: async (_input, ctx) => Result.ok(db.from(ctx)),
58
+ });
59
+ `;
60
+
61
+ const diagnostics = provisionExists.checkWithContext(code, TEST_FILE, {
62
+ knownProvisionIds: new Set(['db.other']),
63
+ knownTrailIds: new Set(['entity.show']),
64
+ });
65
+
66
+ expect(diagnostics).toHaveLength(1);
67
+ expect(diagnostics[0]?.rule).toBe('provision-exists');
68
+ expect(diagnostics[0]?.message).toContain('db.main');
69
+ });
70
+
71
+ test('passes when project context includes the declared provision', () => {
72
+ const code = `
73
+ import { Result, provision, trail } from '@ontrails/core';
74
+
75
+ const db = provision('db.main', {
76
+ create: () => Result.ok({ source: 'factory' }),
77
+ });
78
+
79
+ trail('entity.show', {
80
+ provisions: [db],
81
+ blaze: async (_input, ctx) => Result.ok(db.from(ctx)),
82
+ });
83
+ `;
84
+
85
+ const diagnostics = provisionExists.checkWithContext(code, TEST_FILE, {
86
+ knownProvisionIds: new Set(['db.main']),
87
+ knownTrailIds: new Set(['entity.show']),
88
+ });
89
+
90
+ expect(diagnostics).toEqual([]);
91
+ });
92
+
93
+ test('skips unresolved imported provisions instead of guessing', () => {
94
+ const code = `
95
+ import { trail } from '@ontrails/core';
96
+ import { db } from './provisions';
97
+
98
+ trail('entity.show', {
99
+ provisions: [db],
100
+ blaze: async (_input, ctx) => Result.ok(db.from(ctx)),
101
+ });
102
+ `;
103
+
104
+ expect(
105
+ provisionExists.checkWithContext(code, TEST_FILE, {
106
+ knownProvisionIds: new Set(['db.main']),
107
+ knownTrailIds: new Set(['entity.show']),
108
+ })
109
+ ).toEqual([]);
110
+ });
111
+
112
+ test('skips test files', () => {
113
+ const code = `
114
+ trail('entity.show', {
115
+ provisions: ['db.main'],
116
+ blaze: async (_input, ctx) => Result.ok(ctx.provision('db.main')),
117
+ });
118
+ `;
119
+
120
+ expect(provisionExists.check(code, 'entity.test.ts')).toEqual([]);
121
+ });
122
+ });
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
 
3
- import { contextNoSurfaceTypes } from '../rules/context-no-surface-types.js';
3
+ import { contextNoTrailheadTypes } from '../rules/context-no-trailhead-types.js';
4
4
  import { noDirectImplInRoute } from '../rules/no-direct-impl-in-route.js';
5
5
  import { noThrowInImplementation } from '../rules/no-throw-in-implementation.js';
6
6
  import { validDetourRefs } from '../rules/valid-detour-refs.js';
@@ -14,7 +14,7 @@ describe('no-throw-in-implementation', () => {
14
14
  test('flags throw inside implementation body', () => {
15
15
  const code = `
16
16
  trail("entity.show", {
17
- run: async (input, ctx) => {
17
+ blaze: async (input, ctx) => {
18
18
  throw new Error("boom");
19
19
  }
20
20
  })`;
@@ -27,7 +27,7 @@ trail("entity.show", {
27
27
  test('allows Result.err() in implementation', () => {
28
28
  const code = `
29
29
  trail("entity.show", {
30
- run: async (input, ctx) => {
30
+ blaze: async (input, ctx) => {
31
31
  return Result.err(new NotFoundError("not found"));
32
32
  }
33
33
  })`;
@@ -42,7 +42,7 @@ function helper() {
42
42
  }
43
43
 
44
44
  trail("entity.show", {
45
- run: async (input, ctx) => {
45
+ blaze: async (input, ctx) => {
46
46
  return Result.ok(data);
47
47
  }
48
48
  })`;
@@ -52,20 +52,20 @@ trail("entity.show", {
52
52
  });
53
53
 
54
54
  // ---------------------------------------------------------------------------
55
- // context-no-surface-types
55
+ // context-no-trailhead-types
56
56
  // ---------------------------------------------------------------------------
57
- describe('context-no-surface-types', () => {
57
+ describe('context-no-trailhead-types', () => {
58
58
  test('flags express import in trail file', () => {
59
59
  const code = `
60
60
  import { Request, Response } from "express";
61
61
  trail("entity.show", {
62
- run: async (input, ctx) => {
62
+ blaze: async (input, ctx) => {
63
63
  return Result.ok(data);
64
64
  }
65
65
  })`;
66
- const diagnostics = contextNoSurfaceTypes.check(code, TEST_FILE);
66
+ const diagnostics = contextNoTrailheadTypes.check(code, TEST_FILE);
67
67
  expect(diagnostics.length).toBe(1);
68
- expect(diagnostics[0]?.rule).toBe('context-no-surface-types');
68
+ expect(diagnostics[0]?.rule).toBe('context-no-trailhead-types');
69
69
  expect(diagnostics[0]?.message).toContain('express');
70
70
  });
71
71
 
@@ -73,11 +73,11 @@ trail("entity.show", {
73
73
  const code = `
74
74
  import type { McpSession } from "@modelcontextprotocol/sdk";
75
75
  trail("entity.show", {
76
- run: async (input, ctx) => {
76
+ blaze: async (input, ctx) => {
77
77
  return Result.ok(data);
78
78
  }
79
79
  })`;
80
- const diagnostics = contextNoSurfaceTypes.check(code, TEST_FILE);
80
+ const diagnostics = contextNoTrailheadTypes.check(code, TEST_FILE);
81
81
  expect(diagnostics.length).toBe(1);
82
82
  });
83
83
 
@@ -85,11 +85,11 @@ trail("entity.show", {
85
85
  const code = `
86
86
  import { trail, Result } from "@ontrails/core";
87
87
  trail("entity.show", {
88
- run: async (input, ctx) => {
88
+ blaze: async (input, ctx) => {
89
89
  return Result.ok(data);
90
90
  }
91
91
  })`;
92
- const diagnostics = contextNoSurfaceTypes.check(code, TEST_FILE);
92
+ const diagnostics = contextNoTrailheadTypes.check(code, TEST_FILE);
93
93
  expect(diagnostics.length).toBe(0);
94
94
  });
95
95
 
@@ -97,7 +97,7 @@ trail("entity.show", {
97
97
  const code = `
98
98
  import { Request, Response } from "express";
99
99
  export function handleRequest(req: Request, res: Response) {}`;
100
- const diagnostics = contextNoSurfaceTypes.check(code, TEST_FILE);
100
+ const diagnostics = contextNoTrailheadTypes.check(code, TEST_FILE);
101
101
  expect(diagnostics.length).toBe(0);
102
102
  });
103
103
  });
@@ -110,7 +110,7 @@ describe('valid-detour-refs', () => {
110
110
  const code = `
111
111
  trail("entity.show", {
112
112
  detours: [{ target: "entity.edit" }],
113
- run: async (input, ctx) => Result.ok(data)
113
+ blaze: async (input, ctx) => Result.ok(data)
114
114
  })`;
115
115
  const diagnostics = validDetourRefs.check(code, TEST_FILE);
116
116
  expect(diagnostics.length).toBe(1);
@@ -120,12 +120,12 @@ trail("entity.show", {
120
120
  test('passes when detour target exists', () => {
121
121
  const code = `
122
122
  trail("entity.edit", {
123
- run: async (input, ctx) => Result.ok(data)
123
+ blaze: async (input, ctx) => Result.ok(data)
124
124
  })
125
125
 
126
126
  trail("entity.show", {
127
127
  detours: [{ target: "entity.edit" }],
128
- run: async (input, ctx) => Result.ok(data)
128
+ blaze: async (input, ctx) => Result.ok(data)
129
129
  })`;
130
130
  const diagnostics = validDetourRefs.check(code, TEST_FILE);
131
131
  expect(diagnostics.length).toBe(0);
@@ -135,7 +135,7 @@ trail("entity.show", {
135
135
  const code = `
136
136
  trail("entity.show", {
137
137
  detours: [{ target: "entity.edit" }],
138
- run: async (input, ctx) => Result.ok(data)
138
+ blaze: async (input, ctx) => Result.ok(data)
139
139
  })`;
140
140
  const context = { knownTrailIds: new Set(['entity.show', 'entity.edit']) };
141
141
  const diagnostics = validDetourRefs.checkWithContext(
@@ -146,28 +146,28 @@ trail("entity.show", {
146
146
  expect(diagnostics.length).toBe(0);
147
147
  });
148
148
 
149
- test('flags detour target in trail with follow that does not exist', () => {
149
+ test('flags detour target in trail with crossings that does not exist', () => {
150
150
  const code = `
151
151
  trail("entity.onboard", {
152
152
  detours: [{ target: "entity.missing" }],
153
- follow: ["entity.create"],
154
- run: async (input, ctx) => Result.ok(data)
153
+ crosses: ["entity.create"],
154
+ blaze: async (input, ctx) => Result.ok(data)
155
155
  })`;
156
156
  const diagnostics = validDetourRefs.check(code, TEST_FILE);
157
157
  expect(diagnostics.length).toBe(1);
158
158
  expect(diagnostics[0]?.message).toContain('entity.missing');
159
159
  });
160
160
 
161
- test('passes when trail with follow detour target exists', () => {
161
+ test('passes when trail with crossings detour target exists', () => {
162
162
  const code = `
163
163
  trail("entity.fallback", {
164
- run: async (input, ctx) => Result.ok(data)
164
+ blaze: async (input, ctx) => Result.ok(data)
165
165
  })
166
166
 
167
167
  trail("entity.onboard", {
168
168
  detours: [{ target: "entity.fallback" }],
169
- follow: ["entity.create"],
170
- run: async (input, ctx) => Result.ok(data)
169
+ crosses: ["entity.create"],
170
+ blaze: async (input, ctx) => Result.ok(data)
171
171
  })`;
172
172
  const diagnostics = validDetourRefs.check(code, TEST_FILE);
173
173
  expect(diagnostics.length).toBe(0);
@@ -178,27 +178,27 @@ trail("entity.onboard", {
178
178
  // no-direct-impl-in-route
179
179
  // ---------------------------------------------------------------------------
180
180
  describe('no-direct-impl-in-route', () => {
181
- test('warns on direct .run() call in trail with follow', () => {
181
+ test('warns on direct .blaze() call in trail with crossings', () => {
182
182
  const code = `
183
183
  trail("entity.onboard", {
184
- follow: ["entity.create"],
185
- run: async (input, ctx) => {
186
- const result = await entityCreate.run(data);
184
+ crosses: ["entity.create"],
185
+ blaze: async (input, ctx) => {
186
+ const result = await entityCreate.blaze(data);
187
187
  return Result.ok(result);
188
188
  }
189
189
  })`;
190
190
  const diagnostics = noDirectImplInRoute.check(code, TEST_FILE);
191
191
  expect(diagnostics.length).toBe(1);
192
192
  expect(diagnostics[0]?.severity).toBe('warn');
193
- expect(diagnostics[0]?.message).toContain('ctx.follow');
193
+ expect(diagnostics[0]?.message).toContain('ctx.cross');
194
194
  });
195
195
 
196
- test('allows ctx.follow() calls', () => {
196
+ test('allows ctx.cross() calls', () => {
197
197
  const code = `
198
198
  trail("entity.onboard", {
199
- follow: ["entity.create"],
200
- run: async (input, ctx) => {
201
- const result = await ctx.follow("entity.create", data);
199
+ crosses: ["entity.create"],
200
+ blaze: async (input, ctx) => {
201
+ const result = await ctx.cross("entity.create", data);
202
202
  return Result.ok(result);
203
203
  }
204
204
  })`;
@@ -206,11 +206,11 @@ trail("entity.onboard", {
206
206
  expect(diagnostics.length).toBe(0);
207
207
  });
208
208
 
209
- test('ignores trails without follow', () => {
209
+ test('ignores trails without crossings', () => {
210
210
  const code = `
211
211
  trail("entity.show", {
212
- run: async (input, ctx) => {
213
- const result = await someTrail.run(data);
212
+ blaze: async (input, ctx) => {
213
+ const result = await someTrail.blaze(data);
214
214
  return Result.ok(result);
215
215
  }
216
216
  })`;
@@ -220,7 +220,7 @@ trail("entity.show", {
220
220
 
221
221
  test('ignores files without trail() calls', () => {
222
222
  const code = `
223
- const result = await someTrail.run(data);`;
223
+ const result = await someTrail.blaze(data);`;
224
224
  const diagnostics = noDirectImplInRoute.check(code, TEST_FILE);
225
225
  expect(diagnostics.length).toBe(0);
226
226
  });