everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
133 lines (103 loc) • 4.57 kB
Markdown
---
name: extends-config
description: How bos.config.json extends chains work, deep merge semantics, resolved config lifecycle, env-specific extends, and canonical field ordering. Use when debugging extends inheritance, configuring per-environment parents, understanding what dev writes vs publish writes, or reasoning about config merging.
metadata:
sources: "src/merge.ts,src/config.ts,src/shared.ts,src/types.ts"
---
# extends & Config Merging
## extends Field
The `extends` field in `bos.config.json` specifies a parent config to inherit from. Supports two forms:
### String (all environments use same parent)
```json
{ "extends": "bos://dev.everything.near/everything.dev" }
```
### Object (per-environment parent)
```json
{
"extends": {
"development": "bos://dev.everything.near/everything.dev",
"production": "bos://dev.everything.near/everything.dev",
"staging": "bos://staging.everything.near/everything.dev"
}
}
```
Fallback chain: requested env → `production` → first defined value.
## Deep Merge Semantics
Uses `defu` (with `createDefu` for custom merge rules):
| Field type | Merge behavior |
|-----------|---------------|
| Scalars (account, domain, repository) | Child overrides parent; parent inherited when child omits |
| `shared.ui.*` dep entries | Deep merged — child overrides specific keys, parent deps preserved |
| `plugins` | Deep merged — child overrides per-key, parent plugins preserved unless removed |
| `secrets` arrays | Unioned (deduplicated) |
| `routes` arrays | Child replaces parent |
| `variables` | Deep merged per-key |
### Null Sentinel Removal
Set a plugin to `null` to explicitly remove an inherited plugin:
```json
{
"plugins": {
"template": null
}
}
```
## Resolved Config: `.bos/bos.resolved-config.json`
**Generated by**: `bos dev`, `bos build`, `syncAndGenerateSharedUi()`
**Gitignored**: Yes (inside `.bos/`)
When `bos dev` or `bos build` runs:
1. The full extends chain is resolved in memory
2. The merged config is written to `.bos/bos.resolved-config.json`
3. **`bos.config.json` is NOT modified** during dev
Structure:
```json
{
"_resolved": {
"env": "development",
"resolvedAt": "2026-05-11T...",
"extendsChain": ["bos://dev.everything.near/everything.dev"]
},
"account": "me.near",
"domain": "my.dev",
"shared": { ... },
"app": { ... },
"plugins": { ... }
}
```
### Build configs read resolved config first
All build configs (ui/rsbuild.config.ts, host/rsbuild.config.ts, api/rspack.config.js, plugins/*/rspack.config.js) try `.bos/bos.resolved-config.json` first, falling back to `bos.config.json`.
The `_resolved` metadata is stripped before use.
### When bos.config.json IS written
| Command | Writes bos.config.json? | Why |
|---------|------------------------|-----|
| `bos dev` | No | Uses resolved config |
| `bos build` | No | Uses resolved config |
| `bos publish --deploy` | Yes | Snapshot moment — pins production URLs + versions |
| `bos plugin publish` | Yes | Records production URL + integrity |
| `bos plugin add/remove` | Yes | Changes project's own plugin list |
| `bos sync` | Yes | Merges template updates into local config |
### Remote host mode (bos->catalog)
When host is remote, `syncAndGenerateSharedUi()` reads versions from `bos.config.json` and writes them into `package.json` catalog. No resolved config is written — the remote host reads `bos.config.json` directly.
## Canonical Field Ordering
`BOS_CONFIG_ORDER` enforces consistent key order:
1. `extends` — always first
2. `account`
3. `domain`
4. `testnet`
5. `staging`
6. `repository`
7. `app`
8. `plugins`
9. `shared`
Unknown keys go after known keys. `rebuildOrderedConfig()` is applied before every write.
## API
From `src/config.ts`:
- `writeResolvedConfig(configDir, config, env, extendsChain?)` — writes `.bos/bos.resolved-config.json`
- `loadResolvedConfig(configDir)` — reads resolved config, returns `BosConfig | null`
- `resolveBosConfigPath(configDir)` — returns resolved config path if exists, else `bos.config.json`
- `readBosConfigForBuild(configDir)` — reads resolved config stripping `_resolved`, falls back to `bos.config.json`
From `src/merge.ts`:
- `mergeBosConfigWithExtends(parent, child)` — deep merge for extends chain
- `mergeBosConfigWithTemplate(local, template)` — merge for sync (local wins)
- `resolveExtendsRef(extendsField, env)` — resolve string|object extends for a given env
- `rebuildOrderedConfig(config)` — enforce canonical ordering
- `BOS_CONFIG_ORDER` — ordered field names