@ontrails/config 1.0.0-beta.15 → 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 (139) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/package.json +10 -2
  4. package/src/define-config.ts +6 -3
  5. package/src/extensions.ts +1 -1
  6. package/src/index.ts +0 -1
  7. package/src/registry.ts +1 -1
  8. package/src/resolve.ts +4 -2
  9. package/.turbo/turbo-build.log +0 -1
  10. package/.turbo/turbo-lint.log +0 -3
  11. package/.turbo/turbo-typecheck.log +0 -1
  12. package/dist/app-config.d.ts +0 -65
  13. package/dist/app-config.d.ts.map +0 -1
  14. package/dist/app-config.js +0 -177
  15. package/dist/app-config.js.map +0 -1
  16. package/dist/collect.d.ts +0 -11
  17. package/dist/collect.d.ts.map +0 -1
  18. package/dist/collect.js +0 -81
  19. package/dist/collect.js.map +0 -1
  20. package/dist/compose.d.ts +0 -26
  21. package/dist/compose.d.ts.map +0 -1
  22. package/dist/compose.js +0 -19
  23. package/dist/compose.js.map +0 -1
  24. package/dist/config-layer.d.ts +0 -11
  25. package/dist/config-layer.d.ts.map +0 -1
  26. package/dist/config-layer.js +0 -6
  27. package/dist/config-layer.js.map +0 -1
  28. package/dist/config-resource.d.ts +0 -3
  29. package/dist/config-resource.d.ts.map +0 -1
  30. package/dist/config-resource.js +0 -26
  31. package/dist/config-resource.js.map +0 -1
  32. package/dist/define-config.d.ts +0 -61
  33. package/dist/define-config.d.ts.map +0 -1
  34. package/dist/define-config.js +0 -87
  35. package/dist/define-config.js.map +0 -1
  36. package/dist/derive/env.d.ts +0 -15
  37. package/dist/derive/env.d.ts.map +0 -1
  38. package/dist/derive/env.js +0 -65
  39. package/dist/derive/env.js.map +0 -1
  40. package/dist/derive/example.d.ts +0 -16
  41. package/dist/derive/example.d.ts.map +0 -1
  42. package/dist/derive/example.js +0 -138
  43. package/dist/derive/example.js.map +0 -1
  44. package/dist/derive/helpers.d.ts +0 -35
  45. package/dist/derive/helpers.d.ts.map +0 -1
  46. package/dist/derive/helpers.js +0 -116
  47. package/dist/derive/helpers.js.map +0 -1
  48. package/dist/derive/index.d.ts +0 -4
  49. package/dist/derive/index.d.ts.map +0 -1
  50. package/dist/derive/index.js +0 -4
  51. package/dist/derive/index.js.map +0 -1
  52. package/dist/derive/json-schema.d.ts +0 -18
  53. package/dist/derive/json-schema.d.ts.map +0 -1
  54. package/dist/derive/json-schema.js +0 -97
  55. package/dist/derive/json-schema.js.map +0 -1
  56. package/dist/derive-fields.d.ts +0 -25
  57. package/dist/derive-fields.d.ts.map +0 -1
  58. package/dist/derive-fields.js +0 -147
  59. package/dist/derive-fields.js.map +0 -1
  60. package/dist/derive-provenance.d.ts +0 -30
  61. package/dist/derive-provenance.d.ts.map +0 -1
  62. package/dist/derive-provenance.js +0 -101
  63. package/dist/derive-provenance.js.map +0 -1
  64. package/dist/doctor.d.ts +0 -27
  65. package/dist/doctor.d.ts.map +0 -1
  66. package/dist/doctor.js +0 -131
  67. package/dist/doctor.js.map +0 -1
  68. package/dist/extensions.d.ts +0 -38
  69. package/dist/extensions.d.ts.map +0 -1
  70. package/dist/extensions.js +0 -35
  71. package/dist/extensions.js.map +0 -1
  72. package/dist/index.d.ts +0 -20
  73. package/dist/index.d.ts.map +0 -1
  74. package/dist/index.js +0 -20
  75. package/dist/index.js.map +0 -1
  76. package/dist/merge.d.ts +0 -16
  77. package/dist/merge.d.ts.map +0 -1
  78. package/dist/merge.js +0 -34
  79. package/dist/merge.js.map +0 -1
  80. package/dist/ref.d.ts +0 -29
  81. package/dist/ref.d.ts.map +0 -1
  82. package/dist/ref.js +0 -30
  83. package/dist/ref.js.map +0 -1
  84. package/dist/registry.d.ts +0 -24
  85. package/dist/registry.d.ts.map +0 -1
  86. package/dist/registry.js +0 -12
  87. package/dist/registry.js.map +0 -1
  88. package/dist/resolve.d.ts +0 -21
  89. package/dist/resolve.d.ts.map +0 -1
  90. package/dist/resolve.js +0 -177
  91. package/dist/resolve.js.map +0 -1
  92. package/dist/secret-heuristics.d.ts +0 -10
  93. package/dist/secret-heuristics.d.ts.map +0 -1
  94. package/dist/secret-heuristics.js +0 -11
  95. package/dist/secret-heuristics.js.map +0 -1
  96. package/dist/trails/config-check.d.ts +0 -11
  97. package/dist/trails/config-check.d.ts.map +0 -1
  98. package/dist/trails/config-check.js +0 -53
  99. package/dist/trails/config-check.js.map +0 -1
  100. package/dist/trails/config-describe.d.ts +0 -12
  101. package/dist/trails/config-describe.d.ts.map +0 -1
  102. package/dist/trails/config-describe.js +0 -41
  103. package/dist/trails/config-describe.js.map +0 -1
  104. package/dist/trails/config-explain.d.ts +0 -8
  105. package/dist/trails/config-explain.d.ts.map +0 -1
  106. package/dist/trails/config-explain.js +0 -74
  107. package/dist/trails/config-explain.js.map +0 -1
  108. package/dist/trails/config-init.d.ts +0 -9
  109. package/dist/trails/config-init.d.ts.map +0 -1
  110. package/dist/trails/config-init.js +0 -78
  111. package/dist/trails/config-init.js.map +0 -1
  112. package/dist/workspace.d.ts +0 -9
  113. package/dist/workspace.d.ts.map +0 -1
  114. package/dist/workspace.js +0 -49
  115. package/dist/workspace.js.map +0 -1
  116. package/dist/zod-utils.d.ts +0 -14
  117. package/dist/zod-utils.d.ts.map +0 -1
  118. package/dist/zod-utils.js +0 -41
  119. package/dist/zod-utils.js.map +0 -1
  120. package/src/__tests__/app-config.test.ts +0 -342
  121. package/src/__tests__/compose.test.ts +0 -59
  122. package/src/__tests__/config-check.test.ts +0 -171
  123. package/src/__tests__/config-describe.test.ts +0 -154
  124. package/src/__tests__/config-explain.test.ts +0 -167
  125. package/src/__tests__/config-init.test.ts +0 -210
  126. package/src/__tests__/config-resource.test.ts +0 -87
  127. package/src/__tests__/define-config.test.ts +0 -269
  128. package/src/__tests__/derive-fields.test.ts +0 -158
  129. package/src/__tests__/derive-provenance.test.ts +0 -139
  130. package/src/__tests__/derive.test.ts +0 -269
  131. package/src/__tests__/doctor.test.ts +0 -172
  132. package/src/__tests__/extensions.test.ts +0 -134
  133. package/src/__tests__/ref.test.ts +0 -35
  134. package/src/__tests__/resolve.test.ts +0 -246
  135. package/src/__tests__/workspace.test.ts +0 -64
  136. package/src/workspace.ts +0 -56
  137. package/tsconfig.json +0 -9
  138. package/tsconfig.tests.json +0 -10
  139. package/tsconfig.tsbuildinfo +0 -1
@@ -1,154 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { createResourceLookup } from '@ontrails/core';
3
- import type { TrailContext } from '@ontrails/core';
4
- import { z } from 'zod';
5
-
6
- import { configDescribe } from '../trails/config-describe.js';
7
- import { env, secret } from '../extensions.js';
8
- import type { ConfigState } from '../registry.js';
9
-
10
- /**
11
- * Build a TrailContext with configResource resolved in extensions.
12
- */
13
- const buildCtx = (state: ConfigState): TrailContext => {
14
- const extensions = { config: state };
15
- const ctx: TrailContext = {
16
- abortSignal: AbortSignal.timeout(5000),
17
- cwd: '/tmp',
18
- env: {},
19
- extensions,
20
- requestId: 'test',
21
- resource: undefined as unknown as TrailContext['resource'],
22
- workspaceRoot: '/tmp',
23
- };
24
- const withLookup = {
25
- ...ctx,
26
- resource: createResourceLookup(() => withLookup),
27
- };
28
- return withLookup;
29
- };
30
-
31
- describe('config.describe trail', () => {
32
- describe('identity', () => {
33
- test('has id "config.describe"', () => {
34
- expect(configDescribe.id).toBe('config.describe');
35
- });
36
-
37
- test('has kind "trail"', () => {
38
- expect(configDescribe.kind).toBe('trail');
39
- });
40
-
41
- test('has intent "read"', () => {
42
- expect(configDescribe.intent).toBe('read');
43
- });
44
-
45
- test('has infrastructure meta', () => {
46
- expect(configDescribe.meta).toEqual({ category: 'infrastructure' });
47
- });
48
-
49
- test('has output schema', () => {
50
- expect(configDescribe.output).toBeDefined();
51
- });
52
-
53
- test('declares configResource dependency', () => {
54
- expect(configDescribe.resources).toBeDefined();
55
- expect(configDescribe.resources?.length).toBe(1);
56
- });
57
- });
58
-
59
- describe('examples', () => {
60
- test('has at least one example', () => {
61
- expect(configDescribe.examples?.length).toBeGreaterThanOrEqual(1);
62
- });
63
- });
64
-
65
- describe('wired behavior', () => {
66
- test('returns one field description per schema field', async () => {
67
- const schema = z.object({
68
- host: z.string().default('localhost'),
69
- port: z.number().default(3000),
70
- });
71
- const state: ConfigState = {
72
- resolved: { host: 'localhost', port: 3000 },
73
- schema,
74
- };
75
- const ctx = buildCtx(state);
76
- const result = await configDescribe.blaze({}, ctx);
77
-
78
- expect(result.isOk()).toBe(true);
79
- expect(result.unwrap().fields.length).toBe(2);
80
- });
81
-
82
- test('includes path and type for each field', async () => {
83
- const schema = z.object({
84
- host: z.string().default('localhost'),
85
- port: z.number().default(3000),
86
- });
87
- const state: ConfigState = {
88
- resolved: { host: 'localhost', port: 3000 },
89
- schema,
90
- };
91
- const ctx = buildCtx(state);
92
- const result = await configDescribe.blaze({}, ctx);
93
- const { fields } = result.unwrap();
94
-
95
- expect(fields[0]?.path).toBe('host');
96
- expect(fields[0]?.type).toBe('string');
97
- expect(fields[1]?.path).toBe('port');
98
- expect(fields[1]?.type).toBe('number');
99
- });
100
-
101
- test('includes env annotation when present', async () => {
102
- const schema = z.object({
103
- port: env(z.number(), 'PORT').default(3000),
104
- });
105
- const state: ConfigState = {
106
- resolved: { port: 3000 },
107
- schema,
108
- };
109
- const ctx = buildCtx(state);
110
- const result = await configDescribe.blaze({}, ctx);
111
-
112
- expect(result.isOk()).toBe(true);
113
- const [field] = result.unwrap().fields;
114
- expect(field?.env).toBe('PORT');
115
- });
116
-
117
- test('includes secret annotation when present', async () => {
118
- const schema = z.object({
119
- token: secret(z.string()).default('tok'),
120
- });
121
- const state: ConfigState = {
122
- resolved: { token: 'tok' },
123
- schema,
124
- };
125
- const ctx = buildCtx(state);
126
- const result = await configDescribe.blaze({}, ctx);
127
-
128
- expect(result.isOk()).toBe(true);
129
- const [field] = result.unwrap().fields;
130
- expect(field?.secret).toBe(true);
131
- });
132
-
133
- test('reports required status correctly', async () => {
134
- const schema = z.object({
135
- optional: z.string().optional(),
136
- required: z.string(),
137
- withDefault: z.string().default('val'),
138
- });
139
- const state: ConfigState = {
140
- resolved: { required: 'x', withDefault: 'val' },
141
- schema,
142
- };
143
- const ctx = buildCtx(state);
144
- const result = await configDescribe.blaze({}, ctx);
145
-
146
- expect(result.isOk()).toBe(true);
147
- const { fields } = result.unwrap();
148
- const findField = (path: string) => fields.find((f) => f.path === path);
149
- expect(findField('required')?.required).toBe(true);
150
- expect(findField('optional')?.required).toBe(false);
151
- expect(findField('withDefault')?.required).toBe(false);
152
- });
153
- });
154
- });
@@ -1,167 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { createResourceLookup } from '@ontrails/core';
3
- import type { TrailContext } from '@ontrails/core';
4
- import { z } from 'zod';
5
-
6
- import { configExplain } from '../trails/config-explain.js';
7
- import { env } from '../extensions.js';
8
- import type { ConfigState } from '../registry.js';
9
-
10
- /**
11
- * Build a TrailContext with configResource resolved in extensions.
12
- */
13
- const buildCtx = (state: ConfigState): TrailContext => {
14
- const extensions = { config: state };
15
- const ctx: TrailContext = {
16
- abortSignal: AbortSignal.timeout(5000),
17
- cwd: '/tmp',
18
- env: {},
19
- extensions,
20
- requestId: 'test',
21
- resource: undefined as unknown as TrailContext['resource'],
22
- workspaceRoot: '/tmp',
23
- };
24
- const withLookup = {
25
- ...ctx,
26
- resource: createResourceLookup(() => withLookup),
27
- };
28
- return withLookup;
29
- };
30
-
31
- describe('config.explain trail', () => {
32
- describe('identity', () => {
33
- test('has id "config.explain"', () => {
34
- expect(configExplain.id).toBe('config.explain');
35
- });
36
-
37
- test('has kind "trail"', () => {
38
- expect(configExplain.kind).toBe('trail');
39
- });
40
-
41
- test('has intent "read"', () => {
42
- expect(configExplain.intent).toBe('read');
43
- });
44
-
45
- test('has infrastructure meta', () => {
46
- expect(configExplain.meta).toEqual({ category: 'infrastructure' });
47
- });
48
-
49
- test('has output schema', () => {
50
- expect(configExplain.output).toBeDefined();
51
- });
52
-
53
- test('declares configResource dependency', () => {
54
- expect(configExplain.resources).toBeDefined();
55
- expect(configExplain.resources?.length).toBe(1);
56
- });
57
- });
58
-
59
- describe('examples', () => {
60
- test('has at least one example', () => {
61
- expect(configExplain.examples?.length).toBeGreaterThanOrEqual(1);
62
- });
63
- });
64
-
65
- describe('wired behavior', () => {
66
- test('returns provenance entries for all fields', async () => {
67
- const schema = z.object({
68
- host: z.string().default('localhost'),
69
- port: z.number().default(3000),
70
- });
71
- const state: ConfigState = {
72
- resolved: { host: 'localhost', port: 3000 },
73
- schema,
74
- };
75
- const ctx = buildCtx(state);
76
- const result = await configExplain.blaze({ path: '' }, ctx);
77
-
78
- expect(result.isOk()).toBe(true);
79
- const value = result.unwrap();
80
- expect(value.entries.length).toBe(2);
81
- expect(value.entries[0]?.path).toBe('host');
82
- expect(value.entries[0]?.source).toBe('default');
83
- expect(value.entries[1]?.path).toBe('port');
84
- });
85
-
86
- test('filters entries by path prefix', async () => {
87
- const schema = z.object({
88
- db: z.object({
89
- host: z.string().default('localhost'),
90
- port: z.number().default(5432),
91
- }),
92
- name: z.string().default('app'),
93
- });
94
- const state: ConfigState = {
95
- resolved: { db: { host: 'localhost', port: 5432 }, name: 'app' },
96
- schema,
97
- };
98
- const ctx = buildCtx(state);
99
- const result = await configExplain.blaze({ path: 'db' }, ctx);
100
-
101
- expect(result.isOk()).toBe(true);
102
- const value = result.unwrap();
103
- expect(value.entries.length).toBe(2);
104
- expect(value.entries[0]?.path).toBe('db.host');
105
- });
106
-
107
- test('does not match sibling roots that only share a prefix', async () => {
108
- const schema = z.object({
109
- db: z.object({
110
- host: z.string().default('localhost'),
111
- }),
112
- dbReplica: z.object({
113
- host: z.string().default('replica.local'),
114
- }),
115
- });
116
- const state: ConfigState = {
117
- resolved: {
118
- db: { host: 'localhost' },
119
- dbReplica: { host: 'replica.local' },
120
- },
121
- schema,
122
- };
123
- const ctx = buildCtx(state);
124
- const result = await configExplain.blaze({ path: 'db' }, ctx);
125
-
126
- expect(result.isOk()).toBe(true);
127
- expect(result.unwrap().entries).toEqual([
128
- expect.objectContaining({ path: 'db.host' }),
129
- ]);
130
- });
131
-
132
- test('shows base layer as source when base provides value', async () => {
133
- const schema = z.object({
134
- port: z.number().default(3000),
135
- });
136
- const state: ConfigState = {
137
- base: { port: 8080 },
138
- resolved: { port: 8080 },
139
- schema,
140
- };
141
- const ctx = buildCtx(state);
142
- const result = await configExplain.blaze({ path: '' }, ctx);
143
-
144
- expect(result.isOk()).toBe(true);
145
- const [entry] = result.unwrap().entries;
146
- expect(entry?.source).toBe('base');
147
- expect(entry?.value).toBe(8080);
148
- });
149
-
150
- test('shows env as source when env provides value', async () => {
151
- const schema = z.object({
152
- token: env(z.string(), 'TOKEN').default('fallback'),
153
- });
154
- const state: ConfigState = {
155
- env: { TOKEN: 'real-secret' },
156
- resolved: { token: 'real-secret' },
157
- schema,
158
- };
159
- const ctx = buildCtx(state);
160
- const result = await configExplain.blaze({ path: '' }, ctx);
161
-
162
- expect(result.isOk()).toBe(true);
163
- const [entry] = result.unwrap().entries;
164
- expect(entry?.source).toBe('env');
165
- });
166
- });
167
- });
@@ -1,210 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2
- import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { createResourceLookup } from '@ontrails/core';
6
- import type { TrailContext } from '@ontrails/core';
7
- import { z } from 'zod';
8
-
9
- import { env } from '../extensions.js';
10
- import type { ConfigState } from '../registry.js';
11
- import { configInit } from '../trails/config-init.js';
12
-
13
- /**
14
- * Build a TrailContext with configResource resolved in extensions.
15
- */
16
- const buildCtx = (state: ConfigState): TrailContext => {
17
- const extensions = { config: state };
18
- const ctx: TrailContext = {
19
- abortSignal: AbortSignal.timeout(5000),
20
- cwd: '/tmp',
21
- env: {},
22
- extensions,
23
- requestId: 'test',
24
- resource: undefined as unknown as TrailContext['resource'],
25
- workspaceRoot: '/tmp',
26
- };
27
- const withLookup = {
28
- ...ctx,
29
- resource: createResourceLookup(() => withLookup),
30
- };
31
- return withLookup;
32
- };
33
-
34
- const testSchema = z.object({
35
- host: z.string().default('localhost'),
36
- port: z.number().default(3000),
37
- });
38
-
39
- const testState: ConfigState = {
40
- resolved: { host: 'localhost', port: 3000 },
41
- schema: testSchema,
42
- };
43
-
44
- describe('config.init trail', () => {
45
- describe('identity', () => {
46
- test('has id "config.init"', () => {
47
- expect(configInit.id).toBe('config.init');
48
- });
49
-
50
- test('has kind "trail"', () => {
51
- expect(configInit.kind).toBe('trail');
52
- });
53
-
54
- test('has intent "write"', () => {
55
- expect(configInit.intent).toBe('write');
56
- });
57
-
58
- test('has infrastructure meta', () => {
59
- expect(configInit.meta).toEqual({ category: 'infrastructure' });
60
- });
61
-
62
- test('has output schema', () => {
63
- expect(configInit.output).toBeDefined();
64
- });
65
-
66
- test('declares configResource dependency', () => {
67
- expect(configInit.resources).toBeDefined();
68
- expect(configInit.resources?.length).toBe(1);
69
- });
70
- });
71
-
72
- describe('examples', () => {
73
- test('has at least one example', () => {
74
- expect(configInit.examples?.length).toBeGreaterThanOrEqual(1);
75
- });
76
- });
77
-
78
- describe('wired behavior', () => {
79
- test('generates TOML output by default', async () => {
80
- const ctx = buildCtx(testState);
81
- const result = await configInit.blaze({ format: 'toml' }, ctx);
82
-
83
- expect(result.isOk()).toBe(true);
84
- const value = result.unwrap();
85
- expect(value.format).toBe('toml');
86
- expect(value.content).toContain('host');
87
- expect(value.content).toContain('port');
88
- expect(value.content.length).toBeGreaterThan(0);
89
- });
90
-
91
- test('generates JSON output when requested', async () => {
92
- const ctx = buildCtx(testState);
93
- const result = await configInit.blaze({ format: 'json' }, ctx);
94
-
95
- expect(result.isOk()).toBe(true);
96
- const value = result.unwrap();
97
- expect(value.format).toBe('json');
98
- expect(value.content).toContain('"host"');
99
- expect(value.content).toContain('"port"');
100
- });
101
-
102
- test('generates YAML output when requested', async () => {
103
- const ctx = buildCtx(testState);
104
- const result = await configInit.blaze({ format: 'yaml' }, ctx);
105
-
106
- expect(result.isOk()).toBe(true);
107
- const value = result.unwrap();
108
- expect(value.format).toBe('yaml');
109
- expect(value.content).toContain('host:');
110
- });
111
-
112
- test('generates JSONC output when requested', async () => {
113
- const ctx = buildCtx(testState);
114
- const result = await configInit.blaze({ format: 'jsonc' }, ctx);
115
-
116
- expect(result.isOk()).toBe(true);
117
- const value = result.unwrap();
118
- expect(value.format).toBe('jsonc');
119
- expect(value.content).toContain('"host"');
120
- });
121
-
122
- test('output content is non-empty for schema with fields', async () => {
123
- const ctx = buildCtx(testState);
124
- const result = await configInit.blaze({ format: 'toml' }, ctx);
125
-
126
- expect(result.isOk()).toBe(true);
127
- expect(result.unwrap().content.trim().length).toBeGreaterThan(0);
128
- });
129
-
130
- test('returns content without writtenFiles when dir is not provided', async () => {
131
- const ctx = buildCtx(testState);
132
- const result = await configInit.blaze({ format: 'toml' }, ctx);
133
-
134
- expect(result.isOk()).toBe(true);
135
- expect(result.unwrap().writtenFiles).toBeUndefined();
136
- });
137
- });
138
-
139
- describe('artifact generation', () => {
140
- let tempDir: string;
141
-
142
- const envSchema = z.object({
143
- host: env(z.string(), 'APP_HOST').default('localhost'),
144
- port: z.number().default(3000),
145
- });
146
-
147
- const envState: ConfigState = {
148
- resolved: { host: 'localhost', port: 3000 },
149
- schema: envSchema,
150
- };
151
-
152
- beforeEach(async () => {
153
- tempDir = await mkdtemp(join(tmpdir(), 'trails-config-init-'));
154
- });
155
-
156
- afterEach(async () => {
157
- await rm(tempDir, { force: true, recursive: true });
158
- });
159
-
160
- test('writes .schema.json when dir is provided', async () => {
161
- const ctx = buildCtx(envState);
162
- const result = await configInit.blaze(
163
- { dir: tempDir, format: 'toml' },
164
- ctx
165
- );
166
-
167
- expect(result.isOk()).toBe(true);
168
- const value = result.unwrap();
169
- expect(value.writtenFiles).toBeDefined();
170
- expect(value.writtenFiles).toContainEqual(join(tempDir, '.schema.json'));
171
-
172
- const schemaContent = await readFile(
173
- join(tempDir, '.schema.json'),
174
- 'utf8'
175
- );
176
- const parsed = JSON.parse(schemaContent);
177
- expect(parsed['$schema']).toBe(
178
- 'https://json-schema.org/draft/2020-12/schema'
179
- );
180
- });
181
-
182
- test('writes .env.example when schema has env bindings', async () => {
183
- const ctx = buildCtx(envState);
184
- const result = await configInit.blaze(
185
- { dir: tempDir, format: 'toml' },
186
- ctx
187
- );
188
-
189
- expect(result.isOk()).toBe(true);
190
- const value = result.unwrap();
191
- expect(value.writtenFiles).toContainEqual(join(tempDir, '.env.example'));
192
-
193
- const envContent = await readFile(join(tempDir, '.env.example'), 'utf8');
194
- expect(envContent).toContain('APP_HOST');
195
- });
196
-
197
- test('still returns content alongside written files', async () => {
198
- const ctx = buildCtx(envState);
199
- const result = await configInit.blaze(
200
- { dir: tempDir, format: 'json' },
201
- ctx
202
- );
203
-
204
- expect(result.isOk()).toBe(true);
205
- const value = result.unwrap();
206
- expect(value.content.length).toBeGreaterThan(0);
207
- expect(value.format).toBe('json');
208
- });
209
- });
210
- });
@@ -1,87 +0,0 @@
1
- import { afterEach, describe, expect, test } from 'bun:test';
2
- import { z } from 'zod';
3
-
4
- import { configResource } from '../config-resource.js';
5
- import type { ConfigState } from '../registry.js';
6
- import { clearConfigState, registerConfigState } from '../registry.js';
7
-
8
- /** Stub ResourceContext for create calls. */
9
- const stubSvcCtx = {
10
- config: undefined,
11
- cwd: '/tmp',
12
- env: {},
13
- workspaceRoot: '/tmp',
14
- };
15
-
16
- describe('configResource', () => {
17
- afterEach(() => {
18
- clearConfigState();
19
- });
20
-
21
- describe('identity', () => {
22
- test('has id "config"', () => {
23
- expect(configResource.id).toBe('config');
24
- });
25
-
26
- test('has kind "resource"', () => {
27
- expect(configResource.kind).toBe('resource');
28
- });
29
-
30
- test('has infrastructure meta', () => {
31
- expect(configResource.meta).toEqual({ category: 'infrastructure' });
32
- });
33
-
34
- test('has description', () => {
35
- expect(configResource.description).toBeDefined();
36
- });
37
- });
38
-
39
- describe('mock', () => {
40
- test('returns a ConfigState with empty schema and resolved', () => {
41
- const value = configResource.mock?.() as ConfigState;
42
- expect(value).toBeDefined();
43
- expect(value.resolved).toEqual({});
44
- expect(value.schema).toBeDefined();
45
- });
46
- });
47
-
48
- describe('create', () => {
49
- test('returns Result.ok with registered ConfigState', async () => {
50
- const schema = z.object({ port: z.number().default(3000) });
51
- const state: ConfigState = { resolved: { port: 3000 }, schema };
52
- registerConfigState(state);
53
-
54
- const result = await configResource.create(stubSvcCtx);
55
-
56
- expect(result.isOk()).toBe(true);
57
- const value = result.unwrap() as ConfigState;
58
- expect(value.resolved).toEqual({ port: 3000 });
59
- expect(value.schema).toBe(schema);
60
- });
61
-
62
- test('includes optional layer data when present', async () => {
63
- const schema = z.object({ port: z.number() });
64
- const state: ConfigState = {
65
- base: { port: 8080 },
66
- local: { port: 3000 },
67
- resolved: { port: 3000 },
68
- schema,
69
- };
70
- registerConfigState(state);
71
-
72
- const result = await configResource.create(stubSvcCtx);
73
-
74
- expect(result.isOk()).toBe(true);
75
- const value = result.unwrap() as ConfigState;
76
- expect(value.base).toEqual({ port: 8080 });
77
- expect(value.local).toEqual({ port: 3000 });
78
- });
79
-
80
- test('returns Result.err when no state is registered', async () => {
81
- const result = await configResource.create(stubSvcCtx);
82
-
83
- expect(result.isErr()).toBe(true);
84
- expect(result.error.message).toContain('Config state not registered');
85
- });
86
- });
87
- });