@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.
- package/CHANGELOG.md +41 -0
- package/README.md +20 -28
- package/package.json +10 -2
- package/src/app-config.ts +23 -12
- package/src/compose.ts +16 -22
- package/src/{config-provision.ts → config-resource.ts} +5 -5
- package/src/define-config.ts +18 -18
- package/src/{generate → derive}/env.ts +3 -3
- package/src/{generate → derive}/example.ts +1 -1
- package/src/{generate → derive}/helpers.ts +1 -1
- package/src/derive/index.ts +3 -0
- package/src/{generate → derive}/json-schema.ts +1 -1
- package/src/{describe.ts → derive-fields.ts} +1 -1
- package/src/{explain.ts → derive-provenance.ts} +6 -6
- package/src/extensions.ts +1 -1
- package/src/index.ts +12 -19
- package/src/registry.ts +2 -2
- package/src/resolve.ts +17 -15
- package/src/trails/config-check.ts +3 -3
- package/src/trails/config-describe.ts +5 -5
- package/src/trails/config-explain.ts +13 -13
- package/src/trails/config-init.ts +10 -10
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/app-config.d.ts +0 -65
- package/dist/app-config.d.ts.map +0 -1
- package/dist/app-config.js +0 -172
- package/dist/app-config.js.map +0 -1
- package/dist/collect.d.ts +0 -11
- package/dist/collect.d.ts.map +0 -1
- package/dist/collect.js +0 -81
- package/dist/collect.js.map +0 -1
- package/dist/compose.d.ts +0 -30
- package/dist/compose.d.ts.map +0 -1
- package/dist/compose.js +0 -21
- package/dist/compose.js.map +0 -1
- package/dist/config-gate.d.ts +0 -11
- package/dist/config-gate.d.ts.map +0 -1
- package/dist/config-gate.js +0 -6
- package/dist/config-gate.js.map +0 -1
- package/dist/config-layer.d.ts +0 -11
- package/dist/config-layer.d.ts.map +0 -1
- package/dist/config-layer.js +0 -6
- package/dist/config-layer.js.map +0 -1
- package/dist/config-provision.d.ts +0 -3
- package/dist/config-provision.d.ts.map +0 -1
- package/dist/config-provision.js +0 -26
- package/dist/config-provision.js.map +0 -1
- package/dist/config-service.d.ts +0 -3
- package/dist/config-service.d.ts.map +0 -1
- package/dist/config-service.js +0 -26
- package/dist/config-service.js.map +0 -1
- package/dist/define-config.d.ts +0 -61
- package/dist/define-config.d.ts.map +0 -1
- package/dist/define-config.js +0 -90
- package/dist/define-config.js.map +0 -1
- package/dist/describe.d.ts +0 -25
- package/dist/describe.d.ts.map +0 -1
- package/dist/describe.js +0 -147
- package/dist/describe.js.map +0 -1
- package/dist/doctor.d.ts +0 -27
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js +0 -131
- package/dist/doctor.js.map +0 -1
- package/dist/explain.d.ts +0 -30
- package/dist/explain.d.ts.map +0 -1
- package/dist/explain.js +0 -101
- package/dist/explain.js.map +0 -1
- package/dist/extensions.d.ts +0 -38
- package/dist/extensions.d.ts.map +0 -1
- package/dist/extensions.js +0 -35
- package/dist/extensions.js.map +0 -1
- package/dist/generate/env.d.ts +0 -15
- package/dist/generate/env.d.ts.map +0 -1
- package/dist/generate/env.js +0 -65
- package/dist/generate/env.js.map +0 -1
- package/dist/generate/example.d.ts +0 -16
- package/dist/generate/example.d.ts.map +0 -1
- package/dist/generate/example.js +0 -138
- package/dist/generate/example.js.map +0 -1
- package/dist/generate/helpers.d.ts +0 -35
- package/dist/generate/helpers.d.ts.map +0 -1
- package/dist/generate/helpers.js +0 -116
- package/dist/generate/helpers.js.map +0 -1
- package/dist/generate/index.d.ts +0 -4
- package/dist/generate/index.d.ts.map +0 -1
- package/dist/generate/index.js +0 -4
- package/dist/generate/index.js.map +0 -1
- package/dist/generate/json-schema.d.ts +0 -18
- package/dist/generate/json-schema.d.ts.map +0 -1
- package/dist/generate/json-schema.js +0 -97
- package/dist/generate/json-schema.js.map +0 -1
- package/dist/index.d.ts +0 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -21
- package/dist/index.js.map +0 -1
- package/dist/merge.d.ts +0 -16
- package/dist/merge.d.ts.map +0 -1
- package/dist/merge.js +0 -34
- package/dist/merge.js.map +0 -1
- package/dist/ref.d.ts +0 -29
- package/dist/ref.d.ts.map +0 -1
- package/dist/ref.js +0 -30
- package/dist/ref.js.map +0 -1
- package/dist/registry.d.ts +0 -24
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -12
- package/dist/registry.js.map +0 -1
- package/dist/resolve.d.ts +0 -21
- package/dist/resolve.d.ts.map +0 -1
- package/dist/resolve.js +0 -177
- package/dist/resolve.js.map +0 -1
- package/dist/secret-heuristics.d.ts +0 -10
- package/dist/secret-heuristics.d.ts.map +0 -1
- package/dist/secret-heuristics.js +0 -11
- package/dist/secret-heuristics.js.map +0 -1
- package/dist/trails/config-check.d.ts +0 -11
- package/dist/trails/config-check.d.ts.map +0 -1
- package/dist/trails/config-check.js +0 -53
- package/dist/trails/config-check.js.map +0 -1
- package/dist/trails/config-describe.d.ts +0 -12
- package/dist/trails/config-describe.d.ts.map +0 -1
- package/dist/trails/config-describe.js +0 -41
- package/dist/trails/config-describe.js.map +0 -1
- package/dist/trails/config-explain.d.ts +0 -8
- package/dist/trails/config-explain.d.ts.map +0 -1
- package/dist/trails/config-explain.js +0 -74
- package/dist/trails/config-explain.js.map +0 -1
- package/dist/trails/config-init.d.ts +0 -9
- package/dist/trails/config-init.d.ts.map +0 -1
- package/dist/trails/config-init.js +0 -78
- package/dist/trails/config-init.js.map +0 -1
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -49
- package/dist/workspace.js.map +0 -1
- package/dist/zod-utils.d.ts +0 -14
- package/dist/zod-utils.d.ts.map +0 -1
- package/dist/zod-utils.js +0 -41
- package/dist/zod-utils.js.map +0 -1
- package/src/__tests__/app-config.test.ts +0 -329
- package/src/__tests__/compose.test.ts +0 -59
- package/src/__tests__/config-check.test.ts +0 -171
- package/src/__tests__/config-describe.test.ts +0 -154
- package/src/__tests__/config-explain.test.ts +0 -167
- package/src/__tests__/config-gate.test.ts +0 -53
- package/src/__tests__/config-init.test.ts +0 -210
- package/src/__tests__/config-provision.test.ts +0 -87
- package/src/__tests__/define-config.test.ts +0 -263
- package/src/__tests__/describe.test.ts +0 -158
- package/src/__tests__/doctor.test.ts +0 -172
- package/src/__tests__/explain.test.ts +0 -139
- package/src/__tests__/extensions.test.ts +0 -134
- package/src/__tests__/generate.test.ts +0 -269
- package/src/__tests__/ref.test.ts +0 -35
- package/src/__tests__/resolve.test.ts +0 -246
- package/src/__tests__/workspace.test.ts +0 -64
- package/src/config-gate.ts +0 -15
- package/src/generate/index.ts +0 -3
- package/src/workspace.ts +0 -56
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @ontrails/config
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.16
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 10eae9a: Migrate the Trails workspace to the documented `.trails/` layout: committed `.lock` files at the workspace root, ignored `cache/` for rebuildable derived data, ignored `state/` for mutable runtime state, and `.trails/config.local.{ts,js}` for local overrides. The default SQLite path is now `.trails/state/trails.db`. Workspace bootstrap creates only `cache/` and `state/` — the legacy `dev/` and `generated/` subdirectories are no longer created. Dev reset cleans both the new `.trails/state/` paths and legacy `.trails/trails.db*` and `.trails/dev/tracing.db*` paths for one cycle. Scaffold and workspace gitignores reflect the new layout.
|
|
8
|
+
|
|
9
|
+
Workspace bootstrap is now owned by a single canonical source in `@ontrails/core`. The package exposes `ensureTrailsWorkspace()`, `WORKSPACE_GITIGNORE_CONTENT`, and `WORKSPACE_GITIGNORE_LINES`. `@ontrails/config` no longer exports its own `ensureWorkspace` (consumers should import from `@ontrails/core`). `trails create` now writes `.trails/.gitignore` during scaffolding so a fresh-scaffolded project's initial commit includes the workspace gitignore (resolves TRL-703).
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 49c2e7d: Refresh published package README taxonomy to use adapter language instead of retired connector vocabulary.
|
|
14
|
+
- Updated dependencies [73622ae]
|
|
15
|
+
- Updated dependencies [6300f70]
|
|
16
|
+
- Updated dependencies [d172013]
|
|
17
|
+
- Updated dependencies [c3fc5c3]
|
|
18
|
+
- Updated dependencies [20d7a5c]
|
|
19
|
+
- Updated dependencies [be5fb46]
|
|
20
|
+
- Updated dependencies [e898cc4]
|
|
21
|
+
- Updated dependencies [3395234]
|
|
22
|
+
- Updated dependencies [bcdc484]
|
|
23
|
+
- Updated dependencies [331e3a9]
|
|
24
|
+
- Updated dependencies [4399fdb]
|
|
25
|
+
- Updated dependencies [4b8d13b]
|
|
26
|
+
- Updated dependencies [112b9f2]
|
|
27
|
+
- Updated dependencies [893025e]
|
|
28
|
+
- Updated dependencies [eec5e9d]
|
|
29
|
+
- Updated dependencies [ebd4434]
|
|
30
|
+
- Updated dependencies [863d473]
|
|
31
|
+
- Updated dependencies [344f2f7]
|
|
32
|
+
- Updated dependencies [26f9ffd]
|
|
33
|
+
- Updated dependencies [10eae9a]
|
|
34
|
+
- Updated dependencies [22c6c06]
|
|
35
|
+
- @ontrails/core@1.0.0-beta.16
|
|
36
|
+
|
|
37
|
+
## 1.0.0-beta.15
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Updated dependencies [4ad6b25]
|
|
42
|
+
- @ontrails/core@1.0.0-beta.15
|
|
43
|
+
|
|
3
44
|
## 1.0.0-beta.14
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Schema-derived configuration for Trails.
|
|
4
4
|
|
|
5
|
-
The root package owns the
|
|
5
|
+
The root package owns the runtime-agnostic config declaration and resolution engine. Schemas define the contract, and `configResource` exposes resolved values to trail execution.
|
|
6
6
|
|
|
7
7
|
## The core pattern
|
|
8
8
|
|
|
@@ -24,7 +24,7 @@ export const config = defineConfig({
|
|
|
24
24
|
base: {
|
|
25
25
|
host: 'example.com',
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
profiles: {
|
|
28
28
|
production: {
|
|
29
29
|
debug: false,
|
|
30
30
|
host: 'prod.example.com',
|
|
@@ -45,7 +45,7 @@ import { registerConfigState } from '@ontrails/config';
|
|
|
45
45
|
|
|
46
46
|
const result = await config.resolve({
|
|
47
47
|
cwd: process.cwd(),
|
|
48
|
-
|
|
48
|
+
profile: process.env.TRAILS_ENV,
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
if (!result.isOk()) {
|
|
@@ -56,7 +56,7 @@ registerConfigState({
|
|
|
56
56
|
resolved: result.unwrap(),
|
|
57
57
|
schema: config.schema,
|
|
58
58
|
base: config.base,
|
|
59
|
-
|
|
59
|
+
profile: process.env.TRAILS_ENV,
|
|
60
60
|
env: process.env,
|
|
61
61
|
});
|
|
62
62
|
```
|
|
@@ -64,12 +64,12 @@ registerConfigState({
|
|
|
64
64
|
### 3. Access resolved config in trails
|
|
65
65
|
|
|
66
66
|
```typescript
|
|
67
|
-
import {
|
|
67
|
+
import { configResource } from '@ontrails/config';
|
|
68
68
|
|
|
69
69
|
export const getStatus = trail('status.get', {
|
|
70
|
-
|
|
70
|
+
resources: [configResource],
|
|
71
71
|
blaze: (_input, ctx) => {
|
|
72
|
-
const state =
|
|
72
|
+
const state = configResource.from(ctx);
|
|
73
73
|
return Result.ok({
|
|
74
74
|
port: state.resolved.port,
|
|
75
75
|
debug: state.resolved.debug,
|
|
@@ -83,11 +83,14 @@ export const getStatus = trail('status.get', {
|
|
|
83
83
|
Config resolves through a deterministic priority order:
|
|
84
84
|
|
|
85
85
|
```text
|
|
86
|
-
defaults (schema) → base →
|
|
86
|
+
defaults (schema) → base → profile → local → env
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
Each layer overrides the previous. Environment variables always win.
|
|
90
90
|
|
|
91
|
+
`appConfig()` discovers `*.config.toml`, `*.config.json`, `*.config.jsonc`, and
|
|
92
|
+
`*.config.yaml` by default, plus dotfile equivalents when `dotfile: true`.
|
|
93
|
+
|
|
91
94
|
## Extensions
|
|
92
95
|
|
|
93
96
|
### `env()`
|
|
@@ -133,33 +136,22 @@ const schema = z.object({
|
|
|
133
136
|
});
|
|
134
137
|
```
|
|
135
138
|
|
|
136
|
-
## The
|
|
139
|
+
## The resource
|
|
137
140
|
|
|
138
|
-
The config
|
|
141
|
+
The config resource manages resolved config lifecycle:
|
|
139
142
|
|
|
140
143
|
```typescript
|
|
141
|
-
import {
|
|
144
|
+
import { configResource } from '@ontrails/config';
|
|
142
145
|
|
|
143
146
|
export const myTrail = trail('my.trail', {
|
|
144
|
-
|
|
147
|
+
resources: [configResource],
|
|
145
148
|
blaze: (_input, ctx) => {
|
|
146
|
-
const state =
|
|
149
|
+
const state = configResource.from(ctx);
|
|
147
150
|
return Result.ok(state.resolved);
|
|
148
151
|
},
|
|
149
152
|
});
|
|
150
153
|
```
|
|
151
154
|
|
|
152
|
-
## The gate
|
|
153
|
-
|
|
154
|
-
The config gate reserves a slot in the execution context for per-trail config validation:
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
import { configGate } from '@ontrails/config';
|
|
158
|
-
|
|
159
|
-
export const app = topo('my-app', configModule);
|
|
160
|
-
// Register configGate with your trailhead
|
|
161
|
-
```
|
|
162
|
-
|
|
163
155
|
## Trail definitions
|
|
164
156
|
|
|
165
157
|
### `config.check`
|
|
@@ -172,7 +164,7 @@ Describe all fields in the schema — paths, types, defaults, env bindings, secr
|
|
|
172
164
|
|
|
173
165
|
### `config.explain`
|
|
174
166
|
|
|
175
|
-
Show which source won for each config field — defaults, base,
|
|
167
|
+
Show which source won for each config field — defaults, base, profile, local, or env.
|
|
176
168
|
|
|
177
169
|
### `config.init`
|
|
178
170
|
|
|
@@ -180,13 +172,13 @@ Generate example config files in TOML, JSON, JSONC, or YAML. Optionally writes `
|
|
|
180
172
|
|
|
181
173
|
## Testing
|
|
182
174
|
|
|
183
|
-
Trails that depend on `
|
|
175
|
+
Trails that depend on `configResource` auto-resolve with a mock when registered in the topo:
|
|
184
176
|
|
|
185
177
|
```typescript
|
|
186
178
|
import { testAll } from '@ontrails/testing';
|
|
187
179
|
|
|
188
|
-
const results = testAll(
|
|
189
|
-
//
|
|
180
|
+
const results = testAll(graph);
|
|
181
|
+
// configResource.mock() is called automatically
|
|
190
182
|
```
|
|
191
183
|
|
|
192
184
|
For explicit test setup:
|
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/config",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.16",
|
|
4
|
+
"files": [
|
|
5
|
+
"src/**/*.ts",
|
|
6
|
+
"!src/**/__tests__/**",
|
|
7
|
+
"!src/**/*.test.ts",
|
|
8
|
+
"!src/**/*.test-d.ts",
|
|
9
|
+
"README.md",
|
|
10
|
+
"CHANGELOG.md"
|
|
11
|
+
],
|
|
4
12
|
"type": "module",
|
|
5
13
|
"exports": {
|
|
6
14
|
".": "./src/index.ts",
|
|
@@ -14,7 +22,7 @@
|
|
|
14
22
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
15
23
|
},
|
|
16
24
|
"peerDependencies": {
|
|
17
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
25
|
+
"@ontrails/core": "^1.0.0-beta.15",
|
|
18
26
|
"zod": "^4.3.5"
|
|
19
27
|
}
|
|
20
28
|
}
|
package/src/app-config.ts
CHANGED
|
@@ -9,10 +9,13 @@ import type { z } from 'zod';
|
|
|
9
9
|
|
|
10
10
|
import type { CheckResult } from './doctor.js';
|
|
11
11
|
import { checkConfig } from './doctor.js';
|
|
12
|
-
import type { FieldDescription } from './
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
|
|
12
|
+
import type { FieldDescription } from './derive-fields.js';
|
|
13
|
+
import { deriveConfigFields } from './derive-fields.js';
|
|
14
|
+
import type {
|
|
15
|
+
DeriveConfigProvenanceOptions,
|
|
16
|
+
ProvenanceEntry,
|
|
17
|
+
} from './derive-provenance.js';
|
|
18
|
+
import { deriveConfigProvenance } from './derive-provenance.js';
|
|
16
19
|
import type { ConfigRef } from './ref.js';
|
|
17
20
|
import { configRef } from './ref.js';
|
|
18
21
|
|
|
@@ -39,8 +42,8 @@ export interface ResolveOptions {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/** Options for the `explain()` method on AppConfig, excluding schema. */
|
|
42
|
-
export type
|
|
43
|
-
|
|
45
|
+
export type AppConfigDeriveProvenanceOptions = Omit<
|
|
46
|
+
DeriveConfigProvenanceOptions<z.ZodType>,
|
|
44
47
|
'schema'
|
|
45
48
|
>;
|
|
46
49
|
|
|
@@ -62,7 +65,9 @@ export interface AppConfig<T extends z.ZodType> {
|
|
|
62
65
|
): CheckResult;
|
|
63
66
|
|
|
64
67
|
/** Show which source won for each config field. */
|
|
65
|
-
explain(
|
|
68
|
+
explain(
|
|
69
|
+
options: AppConfigDeriveProvenanceOptions
|
|
70
|
+
): readonly ProvenanceEntry[];
|
|
66
71
|
|
|
67
72
|
/** Create a lazy reference to a config field for use as a trail input default. */
|
|
68
73
|
ref(fieldPath: string): ConfigRef;
|
|
@@ -72,7 +77,12 @@ export interface AppConfig<T extends z.ZodType> {
|
|
|
72
77
|
// Default values
|
|
73
78
|
// ---------------------------------------------------------------------------
|
|
74
79
|
|
|
75
|
-
const DEFAULT_FORMATS: readonly ConfigFormat[] = [
|
|
80
|
+
const DEFAULT_FORMATS: readonly ConfigFormat[] = [
|
|
81
|
+
'toml',
|
|
82
|
+
'json',
|
|
83
|
+
'jsonc',
|
|
84
|
+
'yaml',
|
|
85
|
+
];
|
|
76
86
|
|
|
77
87
|
// ---------------------------------------------------------------------------
|
|
78
88
|
// Internal helpers (defined before consumers — no-use-before-define)
|
|
@@ -225,7 +235,7 @@ const discoverConfigFile = async (
|
|
|
225
235
|
};
|
|
226
236
|
|
|
227
237
|
/** Resolve a config file — either from an explicit path or via discovery. */
|
|
228
|
-
const
|
|
238
|
+
const resolveAppConfigFile = async <T extends z.ZodType>(
|
|
229
239
|
name: string,
|
|
230
240
|
schema: T,
|
|
231
241
|
formats: readonly ConfigFormat[],
|
|
@@ -292,16 +302,17 @@ export const appConfig = <T extends z.ZodType>(
|
|
|
292
302
|
return {
|
|
293
303
|
check: (values, checkOpts) => checkConfig(schema, values, checkOpts),
|
|
294
304
|
describe: () =>
|
|
295
|
-
|
|
305
|
+
deriveConfigFields(
|
|
296
306
|
schema as unknown as z.ZodObject<Record<string, z.ZodType>>
|
|
297
307
|
),
|
|
298
308
|
dotfile,
|
|
299
|
-
explain: (explainOpts) =>
|
|
309
|
+
explain: (explainOpts) =>
|
|
310
|
+
deriveConfigProvenance({ ...explainOpts, schema }),
|
|
300
311
|
formats,
|
|
301
312
|
name,
|
|
302
313
|
ref: (fieldPath) => configRef(fieldPath),
|
|
303
314
|
resolve: (resolveOptions?: ResolveOptions) =>
|
|
304
|
-
|
|
315
|
+
resolveAppConfigFile(name, schema, formats, dotfile, resolveOptions),
|
|
305
316
|
schema,
|
|
306
317
|
};
|
|
307
318
|
};
|
package/src/compose.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Config composition utilities for
|
|
2
|
+
* Config composition utilities for resources.
|
|
3
3
|
*
|
|
4
|
-
* Collects config schemas from
|
|
4
|
+
* Collects config schemas from resource declarations so they can be
|
|
5
5
|
* composed into a unified config structure via `defineConfig`.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -11,17 +11,14 @@ import type { z } from 'zod';
|
|
|
11
11
|
// Types
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
|
-
/** A
|
|
15
|
-
export interface
|
|
16
|
-
readonly
|
|
14
|
+
/** A resource config schema entry extracted from a resource declaration. */
|
|
15
|
+
export interface ResourceConfigEntry {
|
|
16
|
+
readonly resourceId: string;
|
|
17
17
|
readonly schema: z.ZodType;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/** Minimal shape needed to extract config from a provision-like object. */
|
|
24
|
-
interface ProvisionWithOptionalConfig {
|
|
20
|
+
/** Minimal shape needed to extract config from a resource-like object. */
|
|
21
|
+
interface ResourceWithOptionalConfig {
|
|
25
22
|
readonly id: string;
|
|
26
23
|
readonly config?: z.ZodType | undefined;
|
|
27
24
|
}
|
|
@@ -31,22 +28,19 @@ interface ProvisionWithOptionalConfig {
|
|
|
31
28
|
// ---------------------------------------------------------------------------
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
|
-
* Collect config schemas from
|
|
31
|
+
* Collect config schemas from resources that declare them.
|
|
35
32
|
*
|
|
36
|
-
* Returns entries keyed by
|
|
37
|
-
*
|
|
33
|
+
* Returns entries keyed by resource ID for composition into `defineConfig`.
|
|
34
|
+
* Resources without a `config` schema are excluded.
|
|
38
35
|
*/
|
|
39
|
-
export const
|
|
40
|
-
|
|
41
|
-
):
|
|
42
|
-
|
|
36
|
+
export const collectResourceConfigs = (
|
|
37
|
+
resources: readonly ResourceWithOptionalConfig[]
|
|
38
|
+
): ResourceConfigEntry[] =>
|
|
39
|
+
resources
|
|
43
40
|
.filter(
|
|
44
41
|
(
|
|
45
42
|
svc
|
|
46
|
-
): svc is
|
|
43
|
+
): svc is ResourceWithOptionalConfig & { readonly config: z.ZodType } =>
|
|
47
44
|
svc.config !== undefined
|
|
48
45
|
)
|
|
49
|
-
.map((svc) => ({
|
|
50
|
-
|
|
51
|
-
/** Backward-compatible alias while the migration is in flight. */
|
|
52
|
-
export const collectServiceConfigs = collectProvisionConfigs;
|
|
46
|
+
.map((svc) => ({ resourceId: svc.id, schema: svc.config }));
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Config
|
|
2
|
+
* Config resource — manages resolved config lifecycle.
|
|
3
3
|
*
|
|
4
4
|
* The config is resolved during bootstrap (two-phase init per ADR-010)
|
|
5
|
-
* and registered via `registerConfigState`. This
|
|
6
|
-
* global registry so trails can access it through `
|
|
5
|
+
* and registered via `registerConfigState`. This resource reads from the
|
|
6
|
+
* global registry so trails can access it through `configResource.from(ctx)`.
|
|
7
7
|
*/
|
|
8
|
-
import { InternalError, Result,
|
|
8
|
+
import { InternalError, Result, resource } from '@ontrails/core';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
|
|
11
11
|
import type { ConfigState } from './registry.js';
|
|
12
12
|
import { getConfigState } from './registry.js';
|
|
13
13
|
|
|
14
|
-
export const
|
|
14
|
+
export const configResource = resource<ConfigState>('config', {
|
|
15
15
|
create: () => {
|
|
16
16
|
const state = getConfigState();
|
|
17
17
|
if (state === undefined) {
|
package/src/define-config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Trails-specific config wrapper — `appConfig('trails', ...)` with
|
|
3
|
-
* framework conventions for
|
|
3
|
+
* framework conventions for profile selection and local overrides.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
@@ -9,7 +9,7 @@ import { join } from 'node:path';
|
|
|
9
9
|
import type { z } from 'zod';
|
|
10
10
|
|
|
11
11
|
import { appConfig } from './app-config.js';
|
|
12
|
-
import {
|
|
12
|
+
import { deriveConfig } from './resolve.js';
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Types
|
|
@@ -19,14 +19,14 @@ import { resolveConfig } from './resolve.js';
|
|
|
19
19
|
export interface DefineConfigOptions<T extends z.ZodType> {
|
|
20
20
|
readonly schema: T;
|
|
21
21
|
readonly base?: Partial<z.infer<T>>;
|
|
22
|
-
readonly
|
|
22
|
+
readonly profiles?: Record<string, Partial<z.infer<T>>>;
|
|
23
23
|
/** When true, fall back to `NODE_ENV` when `TRAILS_ENV` is unset. */
|
|
24
24
|
readonly envFromNodeEnv?: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** Options passed to `resolve()` on a defined config. */
|
|
28
28
|
interface DefineConfigResolveOptions {
|
|
29
|
-
readonly
|
|
29
|
+
readonly profile?: string;
|
|
30
30
|
readonly env?: Record<string, string | undefined>;
|
|
31
31
|
/** Working directory for local overrides discovery. Defaults to `process.cwd()`. */
|
|
32
32
|
readonly cwd?: string;
|
|
@@ -36,10 +36,13 @@ interface DefineConfigResolveOptions {
|
|
|
36
36
|
// Local overrides discovery
|
|
37
37
|
// ---------------------------------------------------------------------------
|
|
38
38
|
|
|
39
|
-
const LOCAL_OVERRIDE_CANDIDATES = [
|
|
39
|
+
const LOCAL_OVERRIDE_CANDIDATES = [
|
|
40
|
+
'config.local.ts',
|
|
41
|
+
'config.local.js',
|
|
42
|
+
] as const;
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
|
-
* Discover and synchronously import a `.trails/config
|
|
45
|
+
* Discover and synchronously import a `.trails/config.local.{ts,js}` file.
|
|
43
46
|
*
|
|
44
47
|
* Skipped when `TRAILS_ENV=test` for hermetic test environments.
|
|
45
48
|
*/
|
|
@@ -52,7 +55,7 @@ const discoverLocalOverrides = async (
|
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
for (const filename of LOCAL_OVERRIDE_CANDIDATES) {
|
|
55
|
-
const candidate = join(cwd, '.trails',
|
|
58
|
+
const candidate = join(cwd, '.trails', filename);
|
|
56
59
|
if (existsSync(candidate)) {
|
|
57
60
|
const mod: Record<string, unknown> = await import(candidate);
|
|
58
61
|
return (mod['default'] ?? mod) as Record<string, unknown>;
|
|
@@ -70,7 +73,7 @@ const discoverLocalOverrides = async (
|
|
|
70
73
|
* Define Trails app config.
|
|
71
74
|
*
|
|
72
75
|
* This is `appConfig('trails', ...)` with the framework's own conventions:
|
|
73
|
-
* `TRAILS_ENV` selects the
|
|
76
|
+
* `TRAILS_ENV` selects the profile. When `envFromNodeEnv` is true,
|
|
74
77
|
* `NODE_ENV` is used as a fallback when `TRAILS_ENV` is unset.
|
|
75
78
|
*
|
|
76
79
|
* @example
|
|
@@ -81,7 +84,7 @@ const discoverLocalOverrides = async (
|
|
|
81
84
|
* debug: z.boolean().default(false),
|
|
82
85
|
* }),
|
|
83
86
|
* base: { port: 8080 },
|
|
84
|
-
*
|
|
87
|
+
* profiles: {
|
|
85
88
|
* production: { debug: false },
|
|
86
89
|
* test: { debug: true, port: 0 },
|
|
87
90
|
* },
|
|
@@ -93,15 +96,12 @@ const discoverLocalOverrides = async (
|
|
|
93
96
|
export const defineConfig = <T extends z.ZodType>(
|
|
94
97
|
options: DefineConfigOptions<T>
|
|
95
98
|
) => {
|
|
96
|
-
const config = appConfig('trails', {
|
|
97
|
-
formats: ['toml', 'json'],
|
|
98
|
-
schema: options.schema,
|
|
99
|
-
});
|
|
99
|
+
const config = appConfig('trails', { schema: options.schema });
|
|
100
100
|
|
|
101
101
|
return {
|
|
102
102
|
...config,
|
|
103
103
|
base: options.base,
|
|
104
|
-
|
|
104
|
+
profiles: options.profiles,
|
|
105
105
|
resolve: async (resolveOpts?: DefineConfigResolveOptions) => {
|
|
106
106
|
const envRecord = {
|
|
107
107
|
...(resolveOpts?.env ?? process.env),
|
|
@@ -118,14 +118,14 @@ export const defineConfig = <T extends z.ZodType>(
|
|
|
118
118
|
const cwd = resolveOpts?.cwd ?? process.cwd();
|
|
119
119
|
const localOverrides = await discoverLocalOverrides(cwd, envRecord);
|
|
120
120
|
|
|
121
|
-
return
|
|
121
|
+
return deriveConfig({
|
|
122
122
|
base: options.base as Record<string, unknown> | undefined,
|
|
123
123
|
env: envRecord,
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
localOverrides,
|
|
125
|
+
profile: resolveOpts?.profile ?? envRecord['TRAILS_ENV'],
|
|
126
|
+
profiles: options.profiles as
|
|
126
127
|
| Record<string, Record<string, unknown>>
|
|
127
128
|
| undefined,
|
|
128
|
-
localOverrides,
|
|
129
129
|
schema: options.schema,
|
|
130
130
|
});
|
|
131
131
|
},
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
formatValue,
|
|
16
16
|
getDefault,
|
|
17
17
|
getDescription,
|
|
18
|
-
|
|
18
|
+
deriveFieldByPath,
|
|
19
19
|
unwrap,
|
|
20
20
|
zodTypeName,
|
|
21
21
|
zodTypeToJsonSchema,
|
|
@@ -76,7 +76,7 @@ const collectEnvEntries = (
|
|
|
76
76
|
if (!fieldMeta.env) {
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
|
-
const fieldSchema =
|
|
79
|
+
const fieldSchema = deriveFieldByPath(schema, path);
|
|
80
80
|
if (!fieldSchema) {
|
|
81
81
|
continue;
|
|
82
82
|
}
|
|
@@ -96,7 +96,7 @@ const collectEnvEntries = (
|
|
|
96
96
|
* Lists each env var with its type, default, and whether it is a secret.
|
|
97
97
|
* Returns an empty string when no env bindings are present.
|
|
98
98
|
*/
|
|
99
|
-
export const
|
|
99
|
+
export const deriveConfigEnvExample = (
|
|
100
100
|
schema: z.ZodObject<Record<string, z.ZodType>>
|
|
101
101
|
): string => {
|
|
102
102
|
const entries = collectEnvEntries(schema, collectConfigMeta(schema));
|
|
@@ -216,7 +216,7 @@ const formatters: Record<
|
|
|
216
216
|
*
|
|
217
217
|
* Includes descriptions as comments, defaults shown, deprecated fields annotated.
|
|
218
218
|
*/
|
|
219
|
-
export const
|
|
219
|
+
export const deriveConfigExample = (
|
|
220
220
|
schema: z.ZodObject<Record<string, z.ZodType>>,
|
|
221
221
|
format: ExampleFormat
|
|
222
222
|
): string => formatters[format](schema);
|
|
@@ -86,7 +86,7 @@ export const getObjectShape = (
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
/** Resolve a dot-separated path to the field schema within an object. */
|
|
89
|
-
export const
|
|
89
|
+
export const deriveFieldByPath = (
|
|
90
90
|
schema: z.ZodObject<Record<string, z.ZodType>>,
|
|
91
91
|
path: string
|
|
92
92
|
): z.ZodType | undefined => {
|
|
@@ -112,7 +112,7 @@ const buildSchemaProperties = (
|
|
|
112
112
|
* Includes descriptions, defaults, deprecated annotations, and constraints.
|
|
113
113
|
* Produces JSON Schema Draft 2020-12.
|
|
114
114
|
*/
|
|
115
|
-
export const
|
|
115
|
+
export const deriveConfigJsonSchema = (
|
|
116
116
|
schema: z.ZodObject<Record<string, z.ZodType>>,
|
|
117
117
|
options?: { readonly description?: string; readonly title?: string }
|
|
118
118
|
): Record<string, unknown> => {
|
|
@@ -228,7 +228,7 @@ const walkShapeLevel = (
|
|
|
228
228
|
*
|
|
229
229
|
* Returns a structured catalog suitable for CLI rendering or agent inspection.
|
|
230
230
|
*/
|
|
231
|
-
export const
|
|
231
|
+
export const deriveConfigFields = (
|
|
232
232
|
schema: z.ZodObject<Record<string, z.ZodType>>
|
|
233
233
|
): readonly FieldDescription[] => {
|
|
234
234
|
const configMeta = collectConfigMeta(schema);
|
|
@@ -18,15 +18,15 @@ import { getAtPath, isZodObject, unwrapToBase, zodDef } from './zod-utils.js';
|
|
|
18
18
|
export interface ProvenanceEntry {
|
|
19
19
|
readonly path: string;
|
|
20
20
|
readonly value: unknown;
|
|
21
|
-
readonly source: 'default' | 'base' | '
|
|
21
|
+
readonly source: 'default' | 'base' | 'profile' | 'local' | 'env';
|
|
22
22
|
readonly redacted: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Options for explaining config provenance. */
|
|
26
|
-
export interface
|
|
26
|
+
export interface DeriveConfigProvenanceOptions<T extends z.ZodType> {
|
|
27
27
|
readonly schema: T;
|
|
28
28
|
readonly base?: Record<string, unknown>;
|
|
29
|
-
readonly
|
|
29
|
+
readonly profile?: Record<string, unknown>;
|
|
30
30
|
readonly local?: Record<string, unknown>;
|
|
31
31
|
readonly env?: Record<string, string | undefined>;
|
|
32
32
|
readonly resolved: Record<string, unknown>;
|
|
@@ -135,8 +135,8 @@ const collectLeafPaths = (schema: z.ZodType, prefix: string): string[] => {
|
|
|
135
135
|
* Used for debugging — answers "where did this value come from?"
|
|
136
136
|
*
|
|
137
137
|
*/
|
|
138
|
-
export const
|
|
139
|
-
options:
|
|
138
|
+
export const deriveConfigProvenance = <T extends z.ZodType>(
|
|
139
|
+
options: DeriveConfigProvenanceOptions<T>
|
|
140
140
|
): readonly ProvenanceEntry[] => {
|
|
141
141
|
const objSchema = options.schema as unknown as z.ZodObject<
|
|
142
142
|
Record<string, z.ZodType>
|
|
@@ -146,7 +146,7 @@ export const explainConfig = <T extends z.ZodType>(
|
|
|
146
146
|
|
|
147
147
|
const sources: readonly SourceEntry[] = [
|
|
148
148
|
['local', options.local],
|
|
149
|
-
['
|
|
149
|
+
['profile', options.profile],
|
|
150
150
|
['base', options.base],
|
|
151
151
|
];
|
|
152
152
|
|
package/src/extensions.ts
CHANGED
|
@@ -34,7 +34,7 @@ export const secret = <T extends z.ZodType>(schema: T): T =>
|
|
|
34
34
|
* standard key. We set `deprecated: true` so Zod-native tooling (schema
|
|
35
35
|
* serializers, OpenAPI generators) recognises the field as deprecated, and store
|
|
36
36
|
* the human-readable message under `deprecationMessage` for our own
|
|
37
|
-
* `collectConfigMeta` / survey / explain
|
|
37
|
+
* `collectConfigMeta` / survey / explain surfaces.
|
|
38
38
|
*
|
|
39
39
|
* Must be called BEFORE `.default()`, `.optional()`, or other transforms
|
|
40
40
|
* so that the metadata lives on the inner type where `collectConfigMeta`
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
export {
|
|
2
2
|
appConfig,
|
|
3
3
|
type AppConfig,
|
|
4
|
-
type
|
|
4
|
+
type AppConfigDeriveProvenanceOptions,
|
|
5
5
|
type AppConfigOptions,
|
|
6
6
|
type ConfigFormat,
|
|
7
7
|
type ResolveOptions,
|
|
8
8
|
} from './app-config.js';
|
|
9
9
|
export { collectConfigMeta } from './collect.js';
|
|
10
|
-
export {
|
|
11
|
-
collectProvisionConfigs,
|
|
12
|
-
collectServiceConfigs,
|
|
13
|
-
type ProvisionConfigEntry,
|
|
14
|
-
type ServiceConfigEntry,
|
|
15
|
-
} from './compose.js';
|
|
10
|
+
export { collectResourceConfigs, type ResourceConfigEntry } from './compose.js';
|
|
16
11
|
export { defineConfig, type DefineConfigOptions } from './define-config.js';
|
|
17
|
-
export {
|
|
12
|
+
export { deriveConfigFields, type FieldDescription } from './derive-fields.js';
|
|
18
13
|
export {
|
|
19
14
|
checkConfig,
|
|
20
15
|
type CheckResult,
|
|
@@ -22,17 +17,16 @@ export {
|
|
|
22
17
|
} from './doctor.js';
|
|
23
18
|
export { env, secret, deprecated, type ConfigFieldMeta } from './extensions.js';
|
|
24
19
|
export {
|
|
25
|
-
|
|
26
|
-
type
|
|
20
|
+
deriveConfigProvenance,
|
|
21
|
+
type DeriveConfigProvenanceOptions,
|
|
27
22
|
type ProvenanceEntry,
|
|
28
|
-
} from './
|
|
23
|
+
} from './derive-provenance.js';
|
|
29
24
|
export {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} from './
|
|
34
|
-
export {
|
|
35
|
-
export { configProvision } from './config-provision.js';
|
|
25
|
+
deriveConfigEnvExample,
|
|
26
|
+
deriveConfigExample,
|
|
27
|
+
deriveConfigJsonSchema,
|
|
28
|
+
} from './derive/index.js';
|
|
29
|
+
export { configResource } from './config-resource.js';
|
|
36
30
|
export {
|
|
37
31
|
clearConfigState,
|
|
38
32
|
type ConfigState,
|
|
@@ -41,9 +35,8 @@ export {
|
|
|
41
35
|
} from './registry.js';
|
|
42
36
|
export { deepMerge } from './merge.js';
|
|
43
37
|
export { configRef, isConfigRef, type ConfigRef } from './ref.js';
|
|
44
|
-
export {
|
|
38
|
+
export { deriveConfig, type DeriveConfigOptions } from './resolve.js';
|
|
45
39
|
export { configCheck } from './trails/config-check.js';
|
|
46
40
|
export { configDescribe } from './trails/config-describe.js';
|
|
47
41
|
export { configExplain } from './trails/config-explain.js';
|
|
48
42
|
export { configInit } from './trails/config-init.js';
|
|
49
|
-
export { ensureWorkspace } from './workspace.js';
|