@ontrails/config 1.0.0-beta.14 → 1.0.0-beta.16

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 (163) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +20 -28
  3. package/package.json +10 -2
  4. package/src/app-config.ts +23 -12
  5. package/src/compose.ts +16 -22
  6. package/src/{config-provision.ts → config-resource.ts} +5 -5
  7. package/src/define-config.ts +18 -18
  8. package/src/{generate → derive}/env.ts +3 -3
  9. package/src/{generate → derive}/example.ts +1 -1
  10. package/src/{generate → derive}/helpers.ts +1 -1
  11. package/src/derive/index.ts +3 -0
  12. package/src/{generate → derive}/json-schema.ts +1 -1
  13. package/src/{describe.ts → derive-fields.ts} +1 -1
  14. package/src/{explain.ts → derive-provenance.ts} +6 -6
  15. package/src/extensions.ts +1 -1
  16. package/src/index.ts +12 -19
  17. package/src/registry.ts +2 -2
  18. package/src/resolve.ts +17 -15
  19. package/src/trails/config-check.ts +3 -3
  20. package/src/trails/config-describe.ts +5 -5
  21. package/src/trails/config-explain.ts +13 -13
  22. package/src/trails/config-init.ts +10 -10
  23. package/.turbo/turbo-build.log +0 -1
  24. package/.turbo/turbo-lint.log +0 -3
  25. package/.turbo/turbo-typecheck.log +0 -1
  26. package/dist/app-config.d.ts +0 -65
  27. package/dist/app-config.d.ts.map +0 -1
  28. package/dist/app-config.js +0 -172
  29. package/dist/app-config.js.map +0 -1
  30. package/dist/collect.d.ts +0 -11
  31. package/dist/collect.d.ts.map +0 -1
  32. package/dist/collect.js +0 -81
  33. package/dist/collect.js.map +0 -1
  34. package/dist/compose.d.ts +0 -30
  35. package/dist/compose.d.ts.map +0 -1
  36. package/dist/compose.js +0 -21
  37. package/dist/compose.js.map +0 -1
  38. package/dist/config-gate.d.ts +0 -11
  39. package/dist/config-gate.d.ts.map +0 -1
  40. package/dist/config-gate.js +0 -6
  41. package/dist/config-gate.js.map +0 -1
  42. package/dist/config-layer.d.ts +0 -11
  43. package/dist/config-layer.d.ts.map +0 -1
  44. package/dist/config-layer.js +0 -6
  45. package/dist/config-layer.js.map +0 -1
  46. package/dist/config-provision.d.ts +0 -3
  47. package/dist/config-provision.d.ts.map +0 -1
  48. package/dist/config-provision.js +0 -26
  49. package/dist/config-provision.js.map +0 -1
  50. package/dist/config-service.d.ts +0 -3
  51. package/dist/config-service.d.ts.map +0 -1
  52. package/dist/config-service.js +0 -26
  53. package/dist/config-service.js.map +0 -1
  54. package/dist/define-config.d.ts +0 -61
  55. package/dist/define-config.d.ts.map +0 -1
  56. package/dist/define-config.js +0 -90
  57. package/dist/define-config.js.map +0 -1
  58. package/dist/describe.d.ts +0 -25
  59. package/dist/describe.d.ts.map +0 -1
  60. package/dist/describe.js +0 -147
  61. package/dist/describe.js.map +0 -1
  62. package/dist/doctor.d.ts +0 -27
  63. package/dist/doctor.d.ts.map +0 -1
  64. package/dist/doctor.js +0 -131
  65. package/dist/doctor.js.map +0 -1
  66. package/dist/explain.d.ts +0 -30
  67. package/dist/explain.d.ts.map +0 -1
  68. package/dist/explain.js +0 -101
  69. package/dist/explain.js.map +0 -1
  70. package/dist/extensions.d.ts +0 -38
  71. package/dist/extensions.d.ts.map +0 -1
  72. package/dist/extensions.js +0 -35
  73. package/dist/extensions.js.map +0 -1
  74. package/dist/generate/env.d.ts +0 -15
  75. package/dist/generate/env.d.ts.map +0 -1
  76. package/dist/generate/env.js +0 -65
  77. package/dist/generate/env.js.map +0 -1
  78. package/dist/generate/example.d.ts +0 -16
  79. package/dist/generate/example.d.ts.map +0 -1
  80. package/dist/generate/example.js +0 -138
  81. package/dist/generate/example.js.map +0 -1
  82. package/dist/generate/helpers.d.ts +0 -35
  83. package/dist/generate/helpers.d.ts.map +0 -1
  84. package/dist/generate/helpers.js +0 -116
  85. package/dist/generate/helpers.js.map +0 -1
  86. package/dist/generate/index.d.ts +0 -4
  87. package/dist/generate/index.d.ts.map +0 -1
  88. package/dist/generate/index.js +0 -4
  89. package/dist/generate/index.js.map +0 -1
  90. package/dist/generate/json-schema.d.ts +0 -18
  91. package/dist/generate/json-schema.d.ts.map +0 -1
  92. package/dist/generate/json-schema.js +0 -97
  93. package/dist/generate/json-schema.js.map +0 -1
  94. package/dist/index.d.ts +0 -21
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/index.js +0 -21
  97. package/dist/index.js.map +0 -1
  98. package/dist/merge.d.ts +0 -16
  99. package/dist/merge.d.ts.map +0 -1
  100. package/dist/merge.js +0 -34
  101. package/dist/merge.js.map +0 -1
  102. package/dist/ref.d.ts +0 -29
  103. package/dist/ref.d.ts.map +0 -1
  104. package/dist/ref.js +0 -30
  105. package/dist/ref.js.map +0 -1
  106. package/dist/registry.d.ts +0 -24
  107. package/dist/registry.d.ts.map +0 -1
  108. package/dist/registry.js +0 -12
  109. package/dist/registry.js.map +0 -1
  110. package/dist/resolve.d.ts +0 -21
  111. package/dist/resolve.d.ts.map +0 -1
  112. package/dist/resolve.js +0 -177
  113. package/dist/resolve.js.map +0 -1
  114. package/dist/secret-heuristics.d.ts +0 -10
  115. package/dist/secret-heuristics.d.ts.map +0 -1
  116. package/dist/secret-heuristics.js +0 -11
  117. package/dist/secret-heuristics.js.map +0 -1
  118. package/dist/trails/config-check.d.ts +0 -11
  119. package/dist/trails/config-check.d.ts.map +0 -1
  120. package/dist/trails/config-check.js +0 -53
  121. package/dist/trails/config-check.js.map +0 -1
  122. package/dist/trails/config-describe.d.ts +0 -12
  123. package/dist/trails/config-describe.d.ts.map +0 -1
  124. package/dist/trails/config-describe.js +0 -41
  125. package/dist/trails/config-describe.js.map +0 -1
  126. package/dist/trails/config-explain.d.ts +0 -8
  127. package/dist/trails/config-explain.d.ts.map +0 -1
  128. package/dist/trails/config-explain.js +0 -74
  129. package/dist/trails/config-explain.js.map +0 -1
  130. package/dist/trails/config-init.d.ts +0 -9
  131. package/dist/trails/config-init.d.ts.map +0 -1
  132. package/dist/trails/config-init.js +0 -78
  133. package/dist/trails/config-init.js.map +0 -1
  134. package/dist/workspace.d.ts +0 -9
  135. package/dist/workspace.d.ts.map +0 -1
  136. package/dist/workspace.js +0 -49
  137. package/dist/workspace.js.map +0 -1
  138. package/dist/zod-utils.d.ts +0 -14
  139. package/dist/zod-utils.d.ts.map +0 -1
  140. package/dist/zod-utils.js +0 -41
  141. package/dist/zod-utils.js.map +0 -1
  142. package/src/__tests__/app-config.test.ts +0 -329
  143. package/src/__tests__/compose.test.ts +0 -59
  144. package/src/__tests__/config-check.test.ts +0 -171
  145. package/src/__tests__/config-describe.test.ts +0 -154
  146. package/src/__tests__/config-explain.test.ts +0 -167
  147. package/src/__tests__/config-gate.test.ts +0 -53
  148. package/src/__tests__/config-init.test.ts +0 -210
  149. package/src/__tests__/config-provision.test.ts +0 -87
  150. package/src/__tests__/define-config.test.ts +0 -263
  151. package/src/__tests__/describe.test.ts +0 -158
  152. package/src/__tests__/doctor.test.ts +0 -172
  153. package/src/__tests__/explain.test.ts +0 -139
  154. package/src/__tests__/extensions.test.ts +0 -134
  155. package/src/__tests__/generate.test.ts +0 -269
  156. package/src/__tests__/ref.test.ts +0 -35
  157. package/src/__tests__/resolve.test.ts +0 -246
  158. package/src/__tests__/workspace.test.ts +0 -64
  159. package/src/config-gate.ts +0 -15
  160. package/src/generate/index.ts +0 -3
  161. package/src/workspace.ts +0 -56
  162. package/tsconfig.json +0 -9
  163. package/tsconfig.tsbuildinfo +0 -1
package/dist/zod-utils.js DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * Shared Zod introspection helpers used by config resolution, doctor,
3
- * describe, explain, and collect modules.
4
- */
5
- /** Extract the Zod def record from any ZodType. */
6
- export const zodDef = (schema) => schema.def;
7
- /** Wrapper types that should be peeled before checking the base type. */
8
- const WRAPPER_TYPES = new Set(['optional', 'default', 'nullable']);
9
- /** Unwrap through optional/default/nullable wrappers to find the base schema. */
10
- export const unwrapToBase = (schema) => {
11
- let current = schema;
12
- for (let depth = 0; depth < 10; depth += 1) {
13
- const def = zodDef(current);
14
- if (!WRAPPER_TYPES.has(def['type'])) {
15
- return current;
16
- }
17
- const inner = def['innerType'];
18
- if (!inner) {
19
- return current;
20
- }
21
- current = inner;
22
- }
23
- return current;
24
- };
25
- /** Check if a schema is (or wraps) a ZodObject by inspecting its def. */
26
- export const isZodObject = (schema) => {
27
- const def = zodDef(unwrapToBase(schema));
28
- return def['type'] === 'object' && 'shape' in def;
29
- };
30
- /** Read a value at a dot-separated path from a plain object. */
31
- export const getAtPath = (obj, path) => {
32
- let current = obj;
33
- for (const part of path.split('.')) {
34
- if (typeof current !== 'object' || current === null) {
35
- return undefined;
36
- }
37
- current = current[part];
38
- }
39
- return current;
40
- };
41
- //# sourceMappingURL=zod-utils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"zod-utils.js","sourceRoot":"","sources":["../src/zod-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,mDAAmD;AACnD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,MAAiB,EAA2B,EAAE,CACnE,MAAM,CAAC,GAAyC,CAAC;AAEnD,yEAAyE;AACzE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;AAEnE,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAiB,EAAa,EAAE;IAC3D,IAAI,OAAO,GAAG,MAAM,CAAC;IACrB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAW,CAAC,EAAE,CAAC;YAC9C,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,CAA0B,CAAC;QACxD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,yEAAyE;AACzE,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,MAAiB,EACiC,EAAE;IACpD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACzC,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,IAAI,GAAG,CAAC;AACpD,CAAC,CAAC;AAEF,gEAAgE;AAChE,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,GAA4B,EAC5B,IAAY,EACH,EAAE;IACX,IAAI,OAAO,GAAY,GAAG,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACpD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,GAAI,OAAmC,CAAC,IAAI,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
@@ -1,329 +0,0 @@
1
- import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
- import { mkdir, mkdtemp, rm } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import { tmpdir } from 'node:os';
5
- import { z } from 'zod';
6
-
7
- import type { AppConfig } from '../app-config.js';
8
- import { appConfig } from '../app-config.js';
9
- import { describeConfig } from '../describe.js';
10
- import { checkConfig } from '../doctor.js';
11
- import { configRef } from '../ref.js';
12
-
13
- const testSchema = z.object({
14
- output: z.string().describe('Output directory').default('./output'),
15
- verbose: z.boolean().default(false),
16
- });
17
-
18
- const createJsonConfig = () =>
19
- appConfig('myapp', {
20
- formats: ['json'],
21
- schema: testSchema,
22
- });
23
-
24
- const writeJsonConfig = (filePath: string, output: string) =>
25
- Bun.write(filePath, JSON.stringify({ output }));
26
-
27
- const resolveOutput = async (
28
- config: AppConfig<typeof testSchema>,
29
- filePath: string
30
- ): Promise<string> => {
31
- const result = await config.resolve({ path: filePath });
32
- expect(result.isOk()).toBe(true);
33
- return result.unwrap().output;
34
- };
35
-
36
- describe('appConfig()', () => {
37
- describe('creation', () => {
38
- test('returns an object with the provided name', () => {
39
- const config = appConfig('myapp', { schema: testSchema });
40
- expect(config.name).toBe('myapp');
41
- });
42
-
43
- test('returns the provided schema', () => {
44
- const config = appConfig('myapp', { schema: testSchema });
45
- expect(config.schema).toBe(testSchema);
46
- });
47
-
48
- test('defaults formats to toml, json, yaml when not specified', () => {
49
- const config = appConfig('myapp', { schema: testSchema });
50
- expect(config.formats).toEqual(['toml', 'json', 'yaml']);
51
- });
52
-
53
- test('uses provided formats', () => {
54
- const config = appConfig('myapp', {
55
- formats: ['json', 'jsonc'],
56
- schema: testSchema,
57
- });
58
- expect(config.formats).toEqual(['json', 'jsonc']);
59
- });
60
-
61
- test('defaults dotfile to false when not specified', () => {
62
- const config = appConfig('myapp', { schema: testSchema });
63
- expect(config.dotfile).toBe(false);
64
- });
65
-
66
- test('uses provided dotfile value', () => {
67
- const config = appConfig('myapp', {
68
- dotfile: true,
69
- schema: testSchema,
70
- });
71
- expect(config.dotfile).toBe(true);
72
- });
73
- });
74
-
75
- describe('resolve() with explicit path', () => {
76
- let tempDir: string;
77
-
78
- beforeEach(async () => {
79
- tempDir = await mkdtemp(join(tmpdir(), 'trails-config-'));
80
- });
81
-
82
- afterEach(async () => {
83
- await rm(tempDir, { force: true, recursive: true });
84
- });
85
-
86
- test('reads and validates a JSON config file', async () => {
87
- const filePath = join(tempDir, 'myapp.config.json');
88
- await Bun.write(
89
- filePath,
90
- JSON.stringify({ output: './dist', verbose: true })
91
- );
92
-
93
- const config = createJsonConfig();
94
- const result = await config.resolve({ path: filePath });
95
-
96
- expect(result.isOk()).toBe(true);
97
- expect(result.unwrap()).toEqual({ output: './dist', verbose: true });
98
- });
99
-
100
- test('reads and validates a TOML config file', async () => {
101
- const filePath = join(tempDir, 'myapp.config.toml');
102
- await Bun.write(filePath, 'output = "./build"\nverbose = true\n');
103
-
104
- const config = appConfig('myapp', {
105
- formats: ['toml'],
106
- schema: testSchema,
107
- });
108
- const result = await config.resolve({ path: filePath });
109
-
110
- expect(result.isOk()).toBe(true);
111
- expect(result.unwrap()).toEqual({ output: './build', verbose: true });
112
- });
113
-
114
- test('applies schema defaults for missing fields', async () => {
115
- const filePath = join(tempDir, 'myapp.config.json');
116
- await Bun.write(filePath, JSON.stringify({}));
117
-
118
- const config = createJsonConfig();
119
- const result = await config.resolve({ path: filePath });
120
-
121
- expect(result.isOk()).toBe(true);
122
- expect(result.unwrap()).toEqual({ output: './output', verbose: false });
123
- });
124
-
125
- test('re-reads config files after they change on disk', async () => {
126
- const filePath = join(tempDir, 'myapp.config.json');
127
- await writeJsonConfig(filePath, './first');
128
-
129
- const config = createJsonConfig();
130
-
131
- expect(await resolveOutput(config, filePath)).toBe('./first');
132
-
133
- await Bun.sleep(10);
134
- await writeJsonConfig(filePath, './second');
135
-
136
- expect(await resolveOutput(config, filePath)).toBe('./second');
137
- });
138
-
139
- test('returns Result.err when file does not exist', async () => {
140
- const filePath = join(tempDir, 'nonexistent.json');
141
-
142
- const config = createJsonConfig();
143
- const result = await config.resolve({ path: filePath });
144
-
145
- expect(result.isErr()).toBe(true);
146
- });
147
-
148
- test('returns Result.err when file has invalid content', async () => {
149
- const filePath = join(tempDir, 'myapp.config.json');
150
- await Bun.write(
151
- filePath,
152
- JSON.stringify({ output: 42, verbose: 'not-a-bool' })
153
- );
154
-
155
- const config = createJsonConfig();
156
- const result = await config.resolve({ path: filePath });
157
-
158
- expect(result.isErr()).toBe(true);
159
- });
160
- });
161
-
162
- describe('resolve() with discovery', () => {
163
- let tempDir: string;
164
-
165
- beforeEach(async () => {
166
- tempDir = await mkdtemp(join(tmpdir(), 'trails-config-'));
167
- });
168
-
169
- afterEach(async () => {
170
- await rm(tempDir, { force: true, recursive: true });
171
- });
172
-
173
- test('discovers config file in cwd', async () => {
174
- const filePath = join(tempDir, 'myapp.config.json');
175
- await writeJsonConfig(filePath, './found');
176
-
177
- const config = createJsonConfig();
178
- const result = await config.resolve({ cwd: tempDir });
179
-
180
- expect(result.isOk()).toBe(true);
181
- expect(result.unwrap().output).toBe('./found');
182
- });
183
-
184
- test('discovers config file by walking up directories', async () => {
185
- const nested = join(tempDir, 'a', 'b', 'c');
186
- await mkdir(nested, { recursive: true });
187
-
188
- const filePath = join(tempDir, 'myapp.config.json');
189
- await Bun.write(filePath, JSON.stringify({ output: './parent' }));
190
-
191
- const config = appConfig('myapp', {
192
- formats: ['json'],
193
- schema: testSchema,
194
- });
195
- const result = await config.resolve({ cwd: nested });
196
-
197
- expect(result.isOk()).toBe(true);
198
- expect(result.unwrap().output).toBe('./parent');
199
- });
200
-
201
- test('tries formats in order', async () => {
202
- await Bun.write(
203
- join(tempDir, 'myapp.config.toml'),
204
- 'output = "./from-toml"\n'
205
- );
206
- await Bun.write(
207
- join(tempDir, 'myapp.config.json'),
208
- JSON.stringify({ output: './from-json' })
209
- );
210
-
211
- const config = appConfig('myapp', {
212
- formats: ['toml', 'json'],
213
- schema: testSchema,
214
- });
215
- const result = await config.resolve({ cwd: tempDir });
216
-
217
- expect(result.isOk()).toBe(true);
218
- expect(result.unwrap().output).toBe('./from-toml');
219
- });
220
-
221
- test('returns Result.err when no config file found', async () => {
222
- const config = appConfig('myapp', { schema: testSchema });
223
- const result = await config.resolve({ cwd: tempDir });
224
-
225
- expect(result.isErr()).toBe(true);
226
- });
227
- });
228
-
229
- describe('file naming conventions', () => {
230
- let tempDir: string;
231
-
232
- beforeEach(async () => {
233
- tempDir = await mkdtemp(join(tmpdir(), 'trails-config-'));
234
- });
235
-
236
- afterEach(async () => {
237
- await rm(tempDir, { force: true, recursive: true });
238
- });
239
-
240
- test('dotfile: true searches for .myapprc.* files', async () => {
241
- await Bun.write(
242
- join(tempDir, '.myapprc.json'),
243
- JSON.stringify({ output: './dotfile' })
244
- );
245
-
246
- const config = appConfig('myapp', {
247
- dotfile: true,
248
- formats: ['json'],
249
- schema: testSchema,
250
- });
251
- const result = await config.resolve({ cwd: tempDir });
252
-
253
- expect(result.isOk()).toBe(true);
254
- expect(result.unwrap().output).toBe('./dotfile');
255
- });
256
-
257
- test('dotfile: false searches for myapp.config.* files', async () => {
258
- await Bun.write(
259
- join(tempDir, 'myapp.config.json'),
260
- JSON.stringify({ output: './standard' })
261
- );
262
-
263
- const config = appConfig('myapp', {
264
- dotfile: false,
265
- formats: ['json'],
266
- schema: testSchema,
267
- });
268
- const result = await config.resolve({ cwd: tempDir });
269
-
270
- expect(result.isOk()).toBe(true);
271
- expect(result.unwrap().output).toBe('./standard');
272
- });
273
-
274
- test('dotfile: true does NOT find myapp.config.* files', async () => {
275
- await Bun.write(
276
- join(tempDir, 'myapp.config.json'),
277
- JSON.stringify({ output: './nope' })
278
- );
279
-
280
- const config = appConfig('myapp', {
281
- dotfile: true,
282
- formats: ['json'],
283
- schema: testSchema,
284
- });
285
- const result = await config.resolve({ cwd: tempDir });
286
-
287
- expect(result.isErr()).toBe(true);
288
- });
289
- });
290
-
291
- describe('method delegations', () => {
292
- test('describe() returns same result as describeConfig(schema)', () => {
293
- const config = appConfig('myapp', { schema: testSchema });
294
- const methodResult = config.describe();
295
- const standaloneResult = describeConfig(testSchema);
296
-
297
- expect(methodResult).toEqual(standaloneResult);
298
- });
299
-
300
- test('check() returns same result as checkConfig(schema, values)', () => {
301
- const config = appConfig('myapp', { schema: testSchema });
302
- const values = { output: './dist', verbose: true };
303
-
304
- const methodResult = config.check(values);
305
- const standaloneResult = checkConfig(testSchema, values);
306
-
307
- expect(methodResult).toEqual(standaloneResult);
308
- });
309
-
310
- test('ref() returns same result as configRef(path)', () => {
311
- const config = appConfig('myapp', { schema: testSchema });
312
- const methodResult = config.ref('output');
313
- const standaloneResult = configRef('output');
314
-
315
- expect(methodResult).toEqual(standaloneResult);
316
- });
317
-
318
- test('explain() delegates to explainConfig with bound schema', () => {
319
- const config = appConfig('myapp', { schema: testSchema });
320
- const resolved = { output: './dist', verbose: true };
321
-
322
- const result = config.explain({ resolved });
323
-
324
- expect(result.length).toBeGreaterThan(0);
325
- expect(result[0]?.path).toBeDefined();
326
- expect(result[0]?.source).toBeDefined();
327
- });
328
- });
329
- });
@@ -1,59 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { collectProvisionConfigs } from '../compose.js';
6
-
7
- // ---------------------------------------------------------------------------
8
- // Tests
9
- // ---------------------------------------------------------------------------
10
-
11
- describe('collectProvisionConfigs', () => {
12
- test('extracts config schemas from provisions that declare them', () => {
13
- const dbSchema = z.object({ url: z.string().url() });
14
- const cacheSchema = z.object({ ttl: z.number() });
15
-
16
- const provisions = [
17
- { config: dbSchema, id: 'db.main' },
18
- { config: cacheSchema, id: 'cache.main' },
19
- ];
20
-
21
- const entries = collectProvisionConfigs(provisions);
22
-
23
- expect(entries).toHaveLength(2);
24
- expect(entries[0]).toEqual({ provisionId: 'db.main', schema: dbSchema });
25
- expect(entries[1]).toEqual({
26
- provisionId: 'cache.main',
27
- schema: cacheSchema,
28
- });
29
- });
30
-
31
- test('excludes provisions without config', () => {
32
- const schema = z.object({ url: z.string() });
33
-
34
- const provisions = [
35
- { config: schema, id: 'db.main' },
36
- { id: 'counter.main' },
37
- { config: undefined, id: 'logger.main' },
38
- ];
39
-
40
- const entries = collectProvisionConfigs(provisions);
41
-
42
- expect(entries).toHaveLength(1);
43
- expect(entries[0]?.provisionId).toBe('db.main');
44
- });
45
-
46
- test('returns empty array when no provisions have config', () => {
47
- const provisions = [{ id: 'counter.main' }, { id: 'logger.main' }];
48
-
49
- const entries = collectProvisionConfigs(provisions);
50
-
51
- expect(entries).toEqual([]);
52
- });
53
-
54
- test('returns empty array for empty input', () => {
55
- const entries = collectProvisionConfigs([]);
56
-
57
- expect(entries).toEqual([]);
58
- });
59
- });
@@ -1,171 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { createProvisionLookup } from '@ontrails/core';
3
- import type { TrailContext } from '@ontrails/core';
4
- import { z } from 'zod';
5
-
6
- import { configCheck } from '../trails/config-check.js';
7
- import type { ConfigState } from '../registry.js';
8
-
9
- /**
10
- * Build a TrailContext with configProvision resolved in extensions.
11
- */
12
- const buildCtx = (state: ConfigState): TrailContext => {
13
- const extensions = { config: state };
14
- const ctx: TrailContext = {
15
- abortSignal: AbortSignal.timeout(5000),
16
- cwd: '/tmp',
17
- env: {},
18
- extensions,
19
- provision: undefined as unknown as TrailContext['provision'],
20
- requestId: 'test',
21
- workspaceRoot: '/tmp',
22
- };
23
- const withLookup = {
24
- ...ctx,
25
- provision: createProvisionLookup(() => withLookup),
26
- };
27
- return withLookup;
28
- };
29
-
30
- describe('config.check trail', () => {
31
- describe('identity', () => {
32
- test('has id "config.check"', () => {
33
- expect(configCheck.id).toBe('config.check');
34
- });
35
-
36
- test('has kind "trail"', () => {
37
- expect(configCheck.kind).toBe('trail');
38
- });
39
-
40
- test('has intent "read"', () => {
41
- expect(configCheck.intent).toBe('read');
42
- });
43
-
44
- test('has infrastructure meta', () => {
45
- expect(configCheck.meta).toEqual({ category: 'infrastructure' });
46
- });
47
-
48
- test('has output schema', () => {
49
- expect(configCheck.output).toBeDefined();
50
- });
51
-
52
- test('declares configProvision dependency', () => {
53
- expect(configCheck.provisions).toBeDefined();
54
- expect(configCheck.provisions?.length).toBe(1);
55
- });
56
- });
57
-
58
- describe('examples', () => {
59
- test('has at least one example', () => {
60
- expect(configCheck.examples?.length).toBeGreaterThanOrEqual(1);
61
- });
62
- });
63
-
64
- describe('wired behavior', () => {
65
- test('reports valid when all required fields present', async () => {
66
- const schema = z.object({
67
- host: z.string().default('localhost'),
68
- port: z.number().default(3000),
69
- });
70
- const state: ConfigState = {
71
- resolved: { host: 'localhost', port: 3000 },
72
- schema,
73
- };
74
- const ctx = buildCtx(state);
75
- const result = await configCheck.blaze({ values: {} }, ctx);
76
-
77
- expect(result.isOk()).toBe(true);
78
- const value = result.unwrap();
79
- expect(value.valid).toBe(true);
80
- expect(value.diagnostics.length).toBeGreaterThan(0);
81
- });
82
-
83
- test('reports missing for required fields without values', async () => {
84
- const schema = z.object({
85
- host: z.string(),
86
- port: z.number(),
87
- });
88
- const state: ConfigState = {
89
- resolved: {},
90
- schema,
91
- };
92
- const ctx = buildCtx(state);
93
- const result = await configCheck.blaze({ values: {} }, ctx);
94
-
95
- expect(result.isOk()).toBe(true);
96
- const value = result.unwrap();
97
- expect(value.valid).toBe(false);
98
- const missing = value.diagnostics.filter((d) => d.status === 'missing');
99
- expect(missing.length).toBe(2);
100
- });
101
-
102
- test('uses input values when provided to override resolved', async () => {
103
- const schema = z.object({
104
- port: z.number(),
105
- });
106
- const state: ConfigState = {
107
- resolved: {},
108
- schema,
109
- };
110
- const ctx = buildCtx(state);
111
- const result = await configCheck.blaze({ values: { port: 8080 } }, ctx);
112
-
113
- expect(result.isOk()).toBe(true);
114
- const value = result.unwrap();
115
- expect(value.valid).toBe(true);
116
- expect(value.diagnostics.length).toBe(1);
117
- expect(value.diagnostics[0]?.status).toBe('valid');
118
- });
119
-
120
- test('deep merges nested input overrides with resolved values', async () => {
121
- const schema = z.object({
122
- db: z.object({
123
- host: z.string(),
124
- port: z.number(),
125
- }),
126
- });
127
- const state: ConfigState = {
128
- resolved: { db: { host: 'localhost', port: 5432 } },
129
- schema,
130
- };
131
- const ctx = buildCtx(state);
132
- const result = await configCheck.blaze(
133
- { values: { db: { port: 6543 } } },
134
- ctx
135
- );
136
-
137
- expect(result.isOk()).toBe(true);
138
- expect(result.unwrap().valid).toBe(true);
139
- expect(result.unwrap().diagnostics).toEqual([
140
- expect.objectContaining({
141
- path: 'db.host',
142
- status: 'valid',
143
- value: 'localhost',
144
- }),
145
- expect.objectContaining({
146
- path: 'db.port',
147
- status: 'valid',
148
- value: 6543,
149
- }),
150
- ]);
151
- });
152
-
153
- test('reports default status for fields using defaults', async () => {
154
- const schema = z.object({
155
- port: z.number().default(3000),
156
- });
157
- const state: ConfigState = {
158
- resolved: {},
159
- schema,
160
- };
161
- const ctx = buildCtx(state);
162
- const result = await configCheck.blaze({ values: {} }, ctx);
163
-
164
- expect(result.isOk()).toBe(true);
165
- const defaults = result
166
- .unwrap()
167
- .diagnostics.filter((d) => d.status === 'default');
168
- expect(defaults.length).toBe(1);
169
- });
170
- });
171
- });