define-config pages
- define-config
- Layers and merge rules
Layers and merge rules
Share a team layer of defaults, let each project remove or replace parts of it, and catch keys set twice by mistake.
A team layer holds what every project shares: the checks that run, where build messages go. Each project then changes what it needs. These examples call merge directly, so you can try the rules without files. The loader applies the same rules to the files it reads, and tags each entry with its layer.
The rules in one table
| Value in a later entry | What happens |
|---|---|
| A scalar, such as a string or a number | It replaces the earlier value |
| An array | It replaces the earlier array whole; arrays are never merged item by item |
| A plain object | It merges into the earlier object, key by key |
false | It removes the key |
{ $replace: true, … } | It replaces the whole subtree at that key |
$layer on an entry labels the layer it belongs to. The loader sets it for you from each layer's name.
Remove one key and replace a subtree
The team runs lint and an end-to-end suite, and posts to one channel with a mention. This project drops the end-to-end suite and posts to its own channel with no mention:
import { merge } from '@open-tomato/define-config';
const result = merge(
[
{
$layer: 'team',
checks: { lint: { run: 'bun run lint' }, e2e: { run: 'bun run e2e' } },
notify: { channel: 'team-builds', mention: '@here' },
},
{
$layer: 'project',
checks: { e2e: false },
notify: { $replace: true, channel: 'shop-builds' },
},
],
{ required: ['checks.lint'] },
);
console.log(result.value);
console.log(result.diagnostics);
{
checks: {
lint: {
run: "bun run lint",
},
},
notify: {
channel: "shop-builds",
},
}
[]
Without $replace: true, notify would have merged, and the team's mention would have stayed.
Keep a key the team relies on
required lists dot-joined key paths that must survive the merge. A project that removes one gets an error that names the entry:
import { merge } from '@open-tomato/define-config';
const result = merge(
[
{ $layer: 'team', checks: { lint: { run: 'bun run lint' } } },
{ $layer: 'project', checks: { lint: false } },
],
{ required: ['checks.lint'] },
);
console.log(result.diagnostics);
[
{
level: "error",
code: "required-dropped",
path: [ "checks", "lint" ],
message: "checks.lint is required but was dropped: entry 1 removed checks.lint with false",
entry: 1,
}
]
Catch a key set twice in one layer
A later layer overriding an earlier one is the point of layers, so that is silent. Two entries of the same layer setting the same key is usually a mistake, and it gets a duplicate-key warning:
import { merge } from '@open-tomato/define-config';
const entries = [
{ $layer: 'project', port: 3000 },
{ $layer: 'project', port: 8080 },
];
console.log(merge(entries).diagnostics);
console.log(merge(entries, { duplicates: 'error' }).diagnostics[0]?.level);
[
{
level: "warn",
code: "duplicate-key",
path: [ "port" ],
message: "port is set more than once within one layer: layer \"project\" (entries 0, 1)",
entry: 1,
}
]
error
duplicates: 'error' turns the warning into an error, and 'allow' turns the check off. The loader takes the same option.
Find where a value came from
Every merge result carries provenance. Getting started reads it with provenanceOf. The loader also tells you which file each entry came from: each source lists the range of entry indexes it contributed, so a record's entry points at one file.