@ontrails/config 0.2.0
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 +366 -0
- package/README.md +262 -0
- package/package.json +37 -0
- package/src/app-config.ts +318 -0
- package/src/collect.ts +117 -0
- package/src/compose.ts +47 -0
- package/src/config-resource.ts +32 -0
- package/src/define-config.ts +126 -0
- package/src/derive/env.ts +108 -0
- package/src/derive/example.ts +222 -0
- package/src/derive/helpers.ts +158 -0
- package/src/derive/index.ts +3 -0
- package/src/derive/json-schema.ts +137 -0
- package/src/derive-fields.ts +252 -0
- package/src/derive-provenance.ts +240 -0
- package/src/doctor.ts +238 -0
- package/src/extensions.ts +51 -0
- package/src/index.ts +74 -0
- package/src/merge.ts +43 -0
- package/src/path-boundary.ts +40 -0
- package/src/ref.ts +38 -0
- package/src/registry.ts +33 -0
- package/src/resolve.ts +196 -0
- package/src/secret-heuristics.ts +13 -0
- package/src/trails/config-check.ts +95 -0
- package/src/trails/config-describe.ts +44 -0
- package/src/trails/config-init.ts +96 -0
- package/src/trails-config-file.ts +136 -0
- package/src/trails-conventions.ts +240 -0
- package/src/workspace-config-collection.ts +371 -0
- package/src/workspace-config-source.ts +532 -0
- package/src/workspace-config.ts +552 -0
- package/src/zod-utils.ts +152 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config introspection — describe all fields in a schema without values.
|
|
3
|
+
*
|
|
4
|
+
* Returns a structured catalog suitable for CLI rendering or agent inspection.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { globalRegistry } from 'zod';
|
|
8
|
+
import type { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
import { collectConfigMeta } from './collect.js';
|
|
11
|
+
import { isZodObject, unwrapToBase, zodDef } from './zod-utils.js';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Types
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/** Description of a single config field. */
|
|
18
|
+
export interface FieldDescription {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly type: string;
|
|
21
|
+
readonly description?: string;
|
|
22
|
+
readonly default?: unknown;
|
|
23
|
+
readonly required: boolean;
|
|
24
|
+
readonly env?: string;
|
|
25
|
+
readonly secret?: boolean;
|
|
26
|
+
readonly deprecated?: string;
|
|
27
|
+
readonly constraints?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Accumulated state while unwrapping Zod wrappers. */
|
|
31
|
+
interface UnwrapState {
|
|
32
|
+
hasDefault: boolean;
|
|
33
|
+
defaultValue: unknown;
|
|
34
|
+
isOptional: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Unwrap result carrying both base schema and accumulated metadata. */
|
|
38
|
+
interface UnwrapResult {
|
|
39
|
+
readonly base: z.ZodType;
|
|
40
|
+
readonly hasDefault: boolean;
|
|
41
|
+
readonly defaultValue: unknown;
|
|
42
|
+
readonly isOptional: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Helpers (defined before consumers — satisfies no-use-before-define)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/** Read the description from the Zod global registry. */
|
|
50
|
+
const getDescription = (schema: z.ZodType): string | undefined => {
|
|
51
|
+
const meta = globalRegistry.get(schema);
|
|
52
|
+
return meta?.description as string | undefined;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Handle a single unwrap step; returns updated inner type and state, or null to stop. */
|
|
56
|
+
const unwrapStep = (
|
|
57
|
+
def: Record<string, unknown>,
|
|
58
|
+
state: UnwrapState
|
|
59
|
+
): { inner: z.ZodType; state: UnwrapState } | null => {
|
|
60
|
+
const typeName = def['type'] as string;
|
|
61
|
+
|
|
62
|
+
if (typeName === 'default') {
|
|
63
|
+
return {
|
|
64
|
+
inner: def['innerType'] as z.ZodType,
|
|
65
|
+
state: { ...state, defaultValue: def['defaultValue'], hasDefault: true },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeName === 'optional') {
|
|
70
|
+
return {
|
|
71
|
+
inner: def['innerType'] as z.ZodType,
|
|
72
|
+
state: { ...state, isOptional: true },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeName === 'nullable') {
|
|
77
|
+
return { inner: def['innerType'] as z.ZodType, state };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Unwrap through default/optional/nullable wrappers to find the base type. */
|
|
84
|
+
const unwrapSchema = (schema: z.ZodType): UnwrapResult => {
|
|
85
|
+
let current = schema;
|
|
86
|
+
let state: UnwrapState = {
|
|
87
|
+
defaultValue: undefined,
|
|
88
|
+
hasDefault: false,
|
|
89
|
+
isOptional: false,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
for (let depth = 0; depth < 10; depth += 1) {
|
|
93
|
+
const result = unwrapStep(zodDef(current), state);
|
|
94
|
+
if (!result) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
current = result.inner;
|
|
98
|
+
({ state } = result);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { base: current, ...state };
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** Resolve the user-facing type name from a base Zod schema. */
|
|
105
|
+
const resolveTypeName = (schema: z.ZodType): string => {
|
|
106
|
+
const typeName = zodDef(schema)['type'] as string;
|
|
107
|
+
const typeMap: Record<string, string> = {
|
|
108
|
+
boolean: 'boolean',
|
|
109
|
+
enum: 'enum',
|
|
110
|
+
number: 'number',
|
|
111
|
+
string: 'string',
|
|
112
|
+
};
|
|
113
|
+
return typeMap[typeName] ?? typeName;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Extract enum values from an enum def. */
|
|
117
|
+
const extractEnumConstraints = (
|
|
118
|
+
def: Record<string, unknown>
|
|
119
|
+
): Record<string, unknown> | undefined => {
|
|
120
|
+
const entries = def['entries'] as Record<string, string> | undefined;
|
|
121
|
+
if (!entries) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
return { values: Object.values(entries) };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** Extract min/max from a number schema's properties. */
|
|
128
|
+
const extractNumberConstraints = (
|
|
129
|
+
schema: z.ZodType
|
|
130
|
+
): Record<string, unknown> | undefined => {
|
|
131
|
+
const result: Record<string, unknown> = {};
|
|
132
|
+
const numSchema = schema as unknown as {
|
|
133
|
+
minValue?: number;
|
|
134
|
+
maxValue?: number;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (numSchema.minValue !== undefined && numSchema.minValue !== null) {
|
|
138
|
+
result['min'] = numSchema.minValue;
|
|
139
|
+
}
|
|
140
|
+
if (numSchema.maxValue !== undefined && numSchema.maxValue !== null) {
|
|
141
|
+
result['max'] = numSchema.maxValue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** Extract constraints from a base schema (min, max, enum values). */
|
|
148
|
+
const extractConstraints = (
|
|
149
|
+
schema: z.ZodType
|
|
150
|
+
): Record<string, unknown> | undefined => {
|
|
151
|
+
const def = zodDef(schema);
|
|
152
|
+
const typeName = def['type'] as string;
|
|
153
|
+
|
|
154
|
+
if (typeName === 'enum') {
|
|
155
|
+
return extractEnumConstraints(def);
|
|
156
|
+
}
|
|
157
|
+
if (typeName === 'number') {
|
|
158
|
+
return extractNumberConstraints(schema);
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Schema walking
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
/** Entry for iterative schema walk. */
|
|
168
|
+
interface WalkEntry {
|
|
169
|
+
readonly schema: z.ZodType;
|
|
170
|
+
readonly prefix: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Build a single FieldDescription from a leaf schema and its metadata. */
|
|
174
|
+
const buildFieldDescription = (
|
|
175
|
+
path: string,
|
|
176
|
+
fieldSchema: z.ZodType,
|
|
177
|
+
configMeta: Map<
|
|
178
|
+
string,
|
|
179
|
+
{ env?: string; secret?: boolean; deprecated?: string }
|
|
180
|
+
>
|
|
181
|
+
): FieldDescription => {
|
|
182
|
+
const { base, defaultValue, hasDefault, isOptional } =
|
|
183
|
+
unwrapSchema(fieldSchema);
|
|
184
|
+
const meta = configMeta.get(path);
|
|
185
|
+
const description = getDescription(base) ?? getDescription(fieldSchema);
|
|
186
|
+
const constraints = extractConstraints(base);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
...(constraints ? { constraints } : {}),
|
|
190
|
+
...(hasDefault ? { default: defaultValue } : {}),
|
|
191
|
+
...(meta?.deprecated ? { deprecated: meta.deprecated } : {}),
|
|
192
|
+
...(description ? { description } : {}),
|
|
193
|
+
...(meta?.env ? { env: meta.env } : {}),
|
|
194
|
+
path,
|
|
195
|
+
required: !hasDefault && !isOptional,
|
|
196
|
+
...(meta?.secret ? { secret: meta.secret } : {}),
|
|
197
|
+
type: resolveTypeName(base),
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/** Walk one level of an object shape, collecting leaves and queuing nested objects. */
|
|
202
|
+
const walkShapeLevel = (
|
|
203
|
+
shape: Record<string, z.ZodType>,
|
|
204
|
+
prefix: string,
|
|
205
|
+
configMeta: Map<
|
|
206
|
+
string,
|
|
207
|
+
{ env?: string; secret?: boolean; deprecated?: string }
|
|
208
|
+
>,
|
|
209
|
+
results: FieldDescription[],
|
|
210
|
+
queue: WalkEntry[]
|
|
211
|
+
): void => {
|
|
212
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
213
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
214
|
+
if (isZodObject(fieldSchema)) {
|
|
215
|
+
queue.push({ prefix: path, schema: unwrapToBase(fieldSchema) });
|
|
216
|
+
} else {
|
|
217
|
+
results.push(buildFieldDescription(path, fieldSchema, configMeta));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Public API
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Describe all fields in a schema without needing a config file.
|
|
228
|
+
*
|
|
229
|
+
* Returns a structured catalog suitable for CLI rendering or agent inspection.
|
|
230
|
+
*/
|
|
231
|
+
export const deriveConfigFields = (
|
|
232
|
+
schema: z.ZodObject<Record<string, z.ZodType>>
|
|
233
|
+
): readonly FieldDescription[] => {
|
|
234
|
+
const configMeta = collectConfigMeta(schema);
|
|
235
|
+
const queue: WalkEntry[] = [];
|
|
236
|
+
const results: FieldDescription[] = [];
|
|
237
|
+
|
|
238
|
+
walkShapeLevel(
|
|
239
|
+
schema.shape as Record<string, z.ZodType>,
|
|
240
|
+
'',
|
|
241
|
+
configMeta,
|
|
242
|
+
results,
|
|
243
|
+
queue
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
for (let entry = queue.pop(); entry; entry = queue.pop()) {
|
|
247
|
+
const nested = zodDef(entry.schema)['shape'] as Record<string, z.ZodType>;
|
|
248
|
+
walkShapeLevel(nested, entry.prefix, configMeta, results, queue);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return results;
|
|
252
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config provenance — show which source won for each config field.
|
|
3
|
+
*
|
|
4
|
+
* Used for debugging: answers "where did this value come from?"
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
import { collectConfigMeta } from './collect.js';
|
|
10
|
+
import { isLikelySecret } from './secret-heuristics.js';
|
|
11
|
+
import {
|
|
12
|
+
getAtPath,
|
|
13
|
+
getSchemaAtPath,
|
|
14
|
+
isZodContainer,
|
|
15
|
+
isZodObject,
|
|
16
|
+
unwrapToBase,
|
|
17
|
+
zodDef,
|
|
18
|
+
} from './zod-utils.js';
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Types
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/** Provenance entry describing the source of a resolved config value. */
|
|
25
|
+
export interface ProvenanceEntry {
|
|
26
|
+
readonly path: string;
|
|
27
|
+
readonly value: unknown;
|
|
28
|
+
readonly source: 'default' | 'base' | 'profile' | 'local' | 'env';
|
|
29
|
+
readonly redacted: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Options for explaining config provenance. */
|
|
33
|
+
export interface DeriveConfigProvenanceOptions<T extends z.ZodType> {
|
|
34
|
+
readonly schema: T;
|
|
35
|
+
readonly base?: Record<string, unknown>;
|
|
36
|
+
readonly profile?: Record<string, unknown>;
|
|
37
|
+
readonly local?: Record<string, unknown>;
|
|
38
|
+
readonly env?: Record<string, string | undefined>;
|
|
39
|
+
readonly resolved: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Helpers (defined before consumers)
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/** Build a map of path → env var name from config metadata. */
|
|
47
|
+
const buildEnvMap = (
|
|
48
|
+
schema: z.ZodObject<Record<string, z.ZodType>>
|
|
49
|
+
): Map<string, string> => {
|
|
50
|
+
const meta = collectConfigMeta(schema);
|
|
51
|
+
const result = new Map<string, string>();
|
|
52
|
+
for (const [path, fieldMeta] of meta) {
|
|
53
|
+
if (fieldMeta.env) {
|
|
54
|
+
result.set(path, fieldMeta.env);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Build a set of paths marked as secret from config metadata. */
|
|
61
|
+
const buildSecretSet = (
|
|
62
|
+
schema: z.ZodObject<Record<string, z.ZodType>>
|
|
63
|
+
): Set<string> => {
|
|
64
|
+
const meta = collectConfigMeta(schema);
|
|
65
|
+
const result = new Set<string>();
|
|
66
|
+
for (const [path, fieldMeta] of meta) {
|
|
67
|
+
if (fieldMeta.secret) {
|
|
68
|
+
result.add(path);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Build a set of env-backed container paths that env overlay skips. */
|
|
75
|
+
const buildSkippedEnvContainerSet = (
|
|
76
|
+
schema: z.ZodObject<Record<string, z.ZodType>>,
|
|
77
|
+
envMap: Map<string, string>
|
|
78
|
+
): Set<string> => {
|
|
79
|
+
const result = new Set<string>();
|
|
80
|
+
for (const path of envMap.keys()) {
|
|
81
|
+
const fieldSchema = getSchemaAtPath(schema, path);
|
|
82
|
+
if (fieldSchema && isZodContainer(fieldSchema)) {
|
|
83
|
+
result.add(path);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Source entries in reverse precedence order for winner detection. */
|
|
90
|
+
type SourceEntry = readonly [
|
|
91
|
+
name: ProvenanceEntry['source'],
|
|
92
|
+
values: Record<string, unknown> | undefined,
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
/** Compare JSON-shaped config values for provenance winner detection. */
|
|
96
|
+
const areConfigValuesEqual = (left: unknown, right: unknown): boolean => {
|
|
97
|
+
if (Object.is(left, right)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
101
|
+
if (!(Array.isArray(left) && Array.isArray(right))) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return (
|
|
105
|
+
left.length === right.length &&
|
|
106
|
+
left.every((value, index) => areConfigValuesEqual(value, right[index]))
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
typeof left !== 'object' ||
|
|
111
|
+
left === null ||
|
|
112
|
+
typeof right !== 'object' ||
|
|
113
|
+
right === null
|
|
114
|
+
) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const leftRecord = left as Record<string, unknown>;
|
|
118
|
+
const rightRecord = right as Record<string, unknown>;
|
|
119
|
+
const leftKeys = Object.keys(leftRecord);
|
|
120
|
+
const rightKeys = Object.keys(rightRecord);
|
|
121
|
+
return (
|
|
122
|
+
leftKeys.length === rightKeys.length &&
|
|
123
|
+
leftKeys.every(
|
|
124
|
+
(key) =>
|
|
125
|
+
Object.hasOwn(rightRecord, key) &&
|
|
126
|
+
areConfigValuesEqual(leftRecord[key], rightRecord[key])
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** Determine which source provided the winning value for a given path. */
|
|
132
|
+
const determineSource = (
|
|
133
|
+
path: string,
|
|
134
|
+
resolved: Record<string, unknown>,
|
|
135
|
+
sources: readonly SourceEntry[],
|
|
136
|
+
envMap: Map<string, string>,
|
|
137
|
+
skippedEnvContainers: Set<string>,
|
|
138
|
+
envVars: Record<string, string | undefined> | undefined
|
|
139
|
+
): ProvenanceEntry['source'] => {
|
|
140
|
+
if (envVars && envMap.has(path) && !skippedEnvContainers.has(path)) {
|
|
141
|
+
const envVar = envMap.get(path);
|
|
142
|
+
if (envVar && envVars[envVar] !== undefined) {
|
|
143
|
+
return 'env';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const resolvedValue = getAtPath(resolved, path);
|
|
148
|
+
for (const [name, values] of sources) {
|
|
149
|
+
if (
|
|
150
|
+
values &&
|
|
151
|
+
areConfigValuesEqual(getAtPath(values, path), resolvedValue)
|
|
152
|
+
) {
|
|
153
|
+
return name;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return 'default';
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Schema walking
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
/** Entry for iterative schema walk. */
|
|
165
|
+
interface WalkEntry {
|
|
166
|
+
readonly schema: z.ZodType;
|
|
167
|
+
readonly prefix: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Collect all leaf field paths from an object schema. */
|
|
171
|
+
const collectLeafPaths = (schema: z.ZodType, prefix: string): string[] => {
|
|
172
|
+
const paths: string[] = [];
|
|
173
|
+
const queue: WalkEntry[] = [{ prefix, schema }];
|
|
174
|
+
|
|
175
|
+
for (let entry = queue.pop(); entry; entry = queue.pop()) {
|
|
176
|
+
const shape = zodDef(entry.schema)['shape'] as Record<string, z.ZodType>;
|
|
177
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
178
|
+
const path = entry.prefix ? `${entry.prefix}.${key}` : key;
|
|
179
|
+
if (isZodObject(fieldSchema)) {
|
|
180
|
+
queue.push({ prefix: path, schema: unwrapToBase(fieldSchema) });
|
|
181
|
+
} else {
|
|
182
|
+
paths.push(path);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return paths;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Public API
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Show which source won for each config field.
|
|
196
|
+
*
|
|
197
|
+
* Used for debugging — answers "where did this value come from?"
|
|
198
|
+
*
|
|
199
|
+
*/
|
|
200
|
+
export const deriveConfigProvenance = <T extends z.ZodType>(
|
|
201
|
+
options: DeriveConfigProvenanceOptions<T>
|
|
202
|
+
): readonly ProvenanceEntry[] => {
|
|
203
|
+
const objSchema = options.schema as unknown as z.ZodObject<
|
|
204
|
+
Record<string, z.ZodType>
|
|
205
|
+
>;
|
|
206
|
+
const envMap = buildEnvMap(objSchema);
|
|
207
|
+
const secretSet = buildSecretSet(objSchema);
|
|
208
|
+
const skippedEnvContainers = buildSkippedEnvContainerSet(objSchema, envMap);
|
|
209
|
+
|
|
210
|
+
const sources: readonly SourceEntry[] = [
|
|
211
|
+
['local', options.local],
|
|
212
|
+
['profile', options.profile],
|
|
213
|
+
['base', options.base],
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const paths = collectLeafPaths(objSchema, '');
|
|
217
|
+
|
|
218
|
+
return paths.map((path) => {
|
|
219
|
+
const source = determineSource(
|
|
220
|
+
path,
|
|
221
|
+
options.resolved,
|
|
222
|
+
sources,
|
|
223
|
+
envMap,
|
|
224
|
+
skippedEnvContainers,
|
|
225
|
+
options.env
|
|
226
|
+
);
|
|
227
|
+
const envVarName = envMap.get(path);
|
|
228
|
+
const isSecret =
|
|
229
|
+
secretSet.has(path) ||
|
|
230
|
+
(envVarName !== undefined && isLikelySecret(envVarName));
|
|
231
|
+
const rawValue = getAtPath(options.resolved, path);
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
path,
|
|
235
|
+
redacted: isSecret,
|
|
236
|
+
source,
|
|
237
|
+
value: isSecret ? '[REDACTED]' : rawValue,
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
};
|