@omega.js/desktop 0.53.0 → 0.54.1
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/README.md +38 -38
- package/dist/cli-run.js +4 -1
- package/dist/cli.js +2 -2
- package/dist/commands/cdp/client.js +1 -1
- package/dist/commands/cdp.js +1 -1
- package/dist/commands/clean.js +2 -3
- package/dist/commands/dev.js +25 -0
- package/dist/commands/lib/ensure-target.js +12 -17
- package/dist/commands/lib/migrate.js +17 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/release.js +1 -1
- package/dist/commands/test.js +4 -4
- package/dist/commands/update.js +5 -4
- package/dist/defaults/.github/workflows/build.yml +18 -18
- package/dist/defaults/_.gitignore +0 -2
- package/dist/defaults/_mas/README.md +3 -3
- package/dist/defaults/config/certs/README.md +1 -1
- package/dist/defaults/config/omega.json5 +36 -36
- package/dist/defaults/docs/README.md +3 -3
- package/dist/defaults/gulpfile.js +1 -1
- package/dist/defaults/hooks/build/post.js +1 -1
- package/dist/defaults/hooks/build/pre.js +1 -1
- package/dist/defaults/hooks/notarize/post.js +2 -2
- package/dist/defaults/hooks/release/post.js +1 -1
- package/dist/defaults/hooks/release/pre.js +1 -1
- package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
- package/dist/defaults/src/integrations/context-menu/index.js +11 -11
- package/dist/defaults/src/integrations/menu/index.js +5 -5
- package/dist/defaults/src/integrations/tray/index.js +9 -9
- package/dist/defaults/src/main.js +2 -2
- package/dist/defaults/src/preload.js +1 -1
- package/dist/defaults/test/README.md +3 -3
- package/dist/defaults/test/_init.js +1 -1
- package/dist/gulp/tasks/audit.js +5 -8
- package/dist/lib/restart-manager/index.js +1 -1
- package/dist/lib/restart-manager/install.js +1 -1
- package/dist/lib/restart-manager/protocol.js +1 -1
- package/dist/main.js +4 -3
- package/dist/preload.js +1 -1
- package/dist/test/suites/build/audit.test.js +20 -7
- package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
- package/dist/test/suites/build/cli.test.js +28 -0
- package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +19 -5
- package/dist/test/suites/build/deploy-direct.test.js +7 -5
- package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
- package/dist/test/suites/build/deploy-hook.test.js +4 -2
- package/dist/test/suites/build/dev-verb.test.js +67 -0
- package/dist/test/suites/build/ensure-target.test.js +11 -3
- package/dist/test/suites/build/merge-line-files.test.js +6 -6
- package/dist/test/suites/build/migrate.test.js +29 -0
- package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
- package/dist/test/suites/build/runner-env-write.test.js +73 -0
- package/dist/test/suites/build/runner.test.js +9 -8
- package/dist/test/suites/build/setup-scripts.test.js +27 -0
- package/dist/test/suites/build/validate-config.test.js +13 -2
- package/dist/test/suites/build/verb-logs.test.js +20 -0
- package/dist/test/suites/renderer/window-desktop-surface.test.js +1 -1
- package/dist/utils/build-pipeline.js +4 -4
- package/dist/utils/runner-env.js +13 -28
- package/dist/vendor/config/company.js +46 -14
- package/dist/vendor/config/defaults.js +30 -7
- package/dist/vendor/config/edit.js +25 -3
- package/dist/vendor/config/env-delivery.js +1 -1
- package/dist/vendor/config/env-schema.js +3 -6
- package/dist/vendor/config/env.js +34 -22
- package/dist/vendor/config/index.js +13 -17
- package/dist/vendor/config/load.js +15 -7
- package/dist/vendor/config/repo.js +10 -27
- package/dist/vendor/config/schema-client.js +64 -0
- package/dist/vendor/config/schema-cloud.js +38 -0
- package/dist/vendor/config/schema-manager.js +118 -0
- package/dist/vendor/config/schema-overrides.js +68 -0
- package/dist/vendor/config/schema.js +99 -152
- package/dist/vendor/config/validate.js +97 -77
- package/dist/vendor/devkit/agents-md.js +233 -0
- package/dist/vendor/devkit/attach-log-file.js +15 -1
- package/dist/vendor/devkit/ci-workflows.js +30 -30
- package/dist/vendor/devkit/cli-router.js +13 -7
- package/dist/vendor/devkit/defaults-engine.js +9 -43
- package/dist/vendor/devkit/deploy-snapshot.js +44 -9
- package/dist/vendor/devkit/env-lines.js +183 -0
- package/dist/vendor/devkit/local.js +62 -10
- package/dist/vendor/devkit/lockfile.js +32 -13
- package/dist/vendor/devkit/logger.js +7 -2
- package/dist/vendor/devkit/merge-line-files.js +219 -176
- package/dist/vendor/devkit/omega-bin.js +208 -111
- package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
- package/dist/vendor/devkit/preludes/index.js +1 -0
- package/dist/vendor/devkit/target-picker.js +45 -0
- package/dist/vendor/devkit/test/dashed-files.js +37 -0
- package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
- package/dist/vendor/devkit/update.js +15 -15
- package/dist/vendor/devkit/verb-scripts.js +40 -0
- package/dist/vendor/devkit/verbs.js +170 -0
- package/package.json +18 -24
- package/dist/commands/install.js +0 -37
- package/dist/defaults/AGENTS.md +0 -119
- package/dist/defaults/CLAUDE.md +0 -1
- package/dist/vendor/config/env-retired.js +0 -137
- package/dist/vendor/config/retired-keys.js +0 -635
- package/docs/analytics.md +0 -140
- package/docs/app-state.md +0 -92
- package/docs/audit.md +0 -69
- package/docs/auth.md +0 -284
- package/docs/auto-updater.md +0 -243
- package/docs/boot-sequence.md +0 -44
- package/docs/build-system.md +0 -169
- package/docs/cdp-debugging.md +0 -169
- package/docs/common-mistakes.md +0 -21
- package/docs/config-schema.md +0 -120
- package/docs/context-menu.md +0 -112
- package/docs/context.md +0 -81
- package/docs/css.md +0 -84
- package/docs/deep-link.md +0 -186
- package/docs/environment-detection.md +0 -112
- package/docs/fontawesome.md +0 -109
- package/docs/hooks.md +0 -89
- package/docs/icons.md +0 -79
- package/docs/index.md +0 -328
- package/docs/installer-options.md +0 -165
- package/docs/ipc.md +0 -61
- package/docs/lib-modules.md +0 -53
- package/docs/logging.md +0 -227
- package/docs/menu.md +0 -160
- package/docs/releasing.md +0 -239
- package/docs/remote-config.md +0 -118
- package/docs/remote-scripts.md +0 -144
- package/docs/restart-manager.md +0 -144
- package/docs/runner.md +0 -290
- package/docs/sentry.md +0 -97
- package/docs/shared/agent-docs.md +0 -89
- package/docs/shared/analytics.md +0 -612
- package/docs/shared/brands.md +0 -57
- package/docs/shared/breaking-changes.md +0 -917
- package/docs/shared/config.md +0 -1948
- package/docs/shared/deploys.md +0 -341
- package/docs/shared/icons.md +0 -219
- package/docs/shared/local-dev.md +0 -167
- package/docs/shared/logging.md +0 -205
- package/docs/shared/monitoring.md +0 -167
- package/docs/shared/publishing.md +0 -187
- package/docs/shared/rulings.md +0 -34
- package/docs/shared/testing.md +0 -147
- package/docs/shared/theming.md +0 -629
- package/docs/shared/translation.md +0 -342
- package/docs/shared/updates.md +0 -61
- package/docs/signing.md +0 -293
- package/docs/startup.md +0 -142
- package/docs/storage.md +0 -59
- package/docs/templating.md +0 -101
- package/docs/test-boot-layer.md +0 -157
- package/docs/test-framework.md +0 -362
- package/docs/themes.md +0 -149
- package/docs/tooltips.md +0 -99
- package/docs/tray.md +0 -164
- package/docs/usage.md +0 -58
- package/docs/verts.md +0 -62
- package/docs/windows.md +0 -149
|
@@ -1,635 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Retired-key detection (#142) — a key that was RENAMED outright in the
|
|
3
|
-
* migration to omega.json5 is an error, never a silent no-op. There is no
|
|
4
|
-
* dual-read anywhere in OMEGA, so a config still carrying `web_manager`
|
|
5
|
-
* validated clean and quietly lost its auth policy, cookie consent and
|
|
6
|
-
* chatsy settings: nothing reads that name.
|
|
7
|
-
*
|
|
8
|
-
* Like secrets.js this is a key-NAME test, walked at every depth (shared
|
|
9
|
-
* level, inside a target entry, inside an array item): the rename moved
|
|
10
|
-
* the key, not its home. Only unambiguous renames belong here: a name that
|
|
11
|
-
* still exists as a legitimate key somewhere in the schema (`google`/`meta`
|
|
12
|
-
* under `analytics.providers`) would false-positive and is left to the mapping
|
|
13
|
-
* tables. `sentry` is the one that LOOKS like an omission and is not: its new
|
|
14
|
-
* home is itself a `sentry` key (`monitoring.providers.sentry` — #485 moved
|
|
15
|
-
* the web converter there too), so a name test would fire on the very shape
|
|
16
|
-
* every mapping row points at.
|
|
17
|
-
*
|
|
18
|
-
* RETIRED_PATHS is the second half (#23): the de-branding rekey moved whole
|
|
19
|
-
* top-level keys into role-shaped homes where the provider keeps its own
|
|
20
|
-
* name (`slapform` → `forms.providers.slapform`), and folded two keys into
|
|
21
|
-
* `cloud`. A name test can't express those — `slapform` is legitimate again
|
|
22
|
-
* one level down, and `firebase` lives on as `client.firebase` — so these
|
|
23
|
-
* match one EXACT path from the root, with the target NAME read as its TYPE
|
|
24
|
-
* (#886) so a `targets.web.*` row fires on every web target a brand runs.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
const { TARGETS, CUSTOM_TARGET_TYPE } = require('./schema.js');
|
|
28
|
-
|
|
29
|
-
// Every legal target `type`, so a per-type row set covers all of them (#883).
|
|
30
|
-
const TARGET_TYPES = [...TARGETS, CUSTOM_TARGET_TYPE];
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The `translation.exclude` → `translation.include` move (#858): every route
|
|
34
|
-
* the brand skipped becomes a negation, in the order it was written, on top
|
|
35
|
-
* of `**`. The FRAMEWORK default (`['**', '!blog/**']`) is deliberately not
|
|
36
|
-
* what a carrying brand lands on: it was translating its blog, and a
|
|
37
|
-
* conversion that changed which pages reach a provider would be a behavior
|
|
38
|
-
* change dressed as a rename. Routes are normalized the way the translation
|
|
39
|
-
* pass normalizes them (no leading or trailing slash), so `/changelog/` lands
|
|
40
|
-
* as `!changelog`.
|
|
41
|
-
* @param {*} value - the authored `exclude` list
|
|
42
|
-
* @returns {string[]} the `include` list that means the same thing
|
|
43
|
-
*/
|
|
44
|
-
function excludeToInclude(value) {
|
|
45
|
-
const routes = Array.isArray(value) ? value : [];
|
|
46
|
-
|
|
47
|
-
return ['**', ...routes.map((entry) => `!${String(entry).replace(/^\/+|\/+$/g, '')}`)];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// key name → { replacement, why } (docs/shared/config.md carries the rows)
|
|
51
|
-
const RETIRED_KEYS = {
|
|
52
|
-
web_manager: {
|
|
53
|
-
replacement: 'client',
|
|
54
|
-
why: 'it configures @omega.js/client (#1); WebManager is not an OMEGA concept',
|
|
55
|
-
},
|
|
56
|
-
firebaseConfig: {
|
|
57
|
-
replacement: 'cloud',
|
|
58
|
-
why: "the provider-discriminated role key: cloud: { provider: 'firebase', config: {…} }",
|
|
59
|
-
},
|
|
60
|
-
cookieConsent: {
|
|
61
|
-
replacement: 'client.consent',
|
|
62
|
-
why: 'the banner became a real consent gate (#383) — the block names the DECISION, not the cookie, and its palette/theme/type keys are gone (tokens paint it, the visitor\'s region picks opt-in vs opt-out)',
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
// The web TARGET is the subdomain (#588). `brand.subdomains` was read by
|
|
66
|
-
// ONE thing (the cloud hosting op, which ensured an api.{sub}.{domain} per
|
|
67
|
-
// entry) and declared by nothing: no schema rule, no default, never
|
|
68
|
-
// materialized. Ian's 2026-09-01 call gave the fact a real home. A NAME test
|
|
69
|
-
// by the rule above: `subdomains` exists nowhere else in the schema (the
|
|
70
|
-
// legacy searchConsole.subdomains was dropped outright), so the walk catches
|
|
71
|
-
// it at every depth, target entries included.
|
|
72
|
-
subdomains: {
|
|
73
|
-
replacement: 'targets.web',
|
|
74
|
-
why: "each subdomain is its own web TARGET (#588/#886): [\"admin\", \"cdn\"] becomes sibling keys beside the main site, `admin: { type: 'web' }, cdn: { type: 'web' }`, where the NAME is the subdomain (https://admin.<brand host>), an entry's own `url` overrides it for a custom host, and the targets share ONE api.<domain>",
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
// The feature is `connections` now (#788, Ian 2026-09-03): a user connection
|
|
78
|
-
// will not always be an OAuth grant — an API key or a bot token is a
|
|
79
|
-
// connection too — so the section, the route, the user-record field, the env
|
|
80
|
-
// prefix and the brand provider folder all carry the product word, and each
|
|
81
|
-
// record names its own kind with `type: 'oauth2'`. A NAME test by the rule
|
|
82
|
-
// above: `oauth2` exists nowhere else in the schema.
|
|
83
|
-
oauth2: {
|
|
84
|
-
replacement: 'connections',
|
|
85
|
-
why: "the product concept is a CONNECTION (#788) — the per-provider block is unchanged, the credentials are the CONNECTIONS_<PROVIDER>_CLIENT_ID/_SECRET env pair now, and a brand's own provider lives at targets/backend/src/connections/<name>.js",
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
// The company block names the parent (#677), so `parent` is retired
|
|
89
|
-
// OUTRIGHT: the topology moved to `company: { id }` first, and the last
|
|
90
|
-
// meaning it carried, the webhook opt-out, is `company: { webhooks: false }`
|
|
91
|
-
// now. A NAME test by the rule above: `parent` exists nowhere else in the
|
|
92
|
-
// schema.
|
|
93
|
-
parent: {
|
|
94
|
-
replacement: 'company.webhooks',
|
|
95
|
-
why: "the company block names the parent (#677): the topology is `company: { id: '<parent brand.id>' }` (or 'self'), and the only other thing `parent` ever said, `false` for \"the provider ACCOUNT is shared and its one account-level webhook is owned elsewhere\", is `company: { webhooks: false }`",
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// exact dotted path → { replacement, why } (docs/shared/config.md carries the rows)
|
|
100
|
-
const RETIRED_PATHS = {
|
|
101
|
-
slapform: {
|
|
102
|
-
replacement: 'forms.providers.slapform',
|
|
103
|
-
why: 'config keys name the ROLE, not the vendor (#23)',
|
|
104
|
-
},
|
|
105
|
-
chatsy: {
|
|
106
|
-
replacement: 'inbound.chat.providers.chatsy',
|
|
107
|
-
why: 'config keys name the ROLE, not the vendor (#23) — one home for the manager fields and the widget settings',
|
|
108
|
-
},
|
|
109
|
-
replyify: {
|
|
110
|
-
replacement: 'inbound.email.providers.replyify',
|
|
111
|
-
why: 'config keys name the ROLE, not the vendor (#23)',
|
|
112
|
-
},
|
|
113
|
-
cloudflare: {
|
|
114
|
-
replacement: 'edge.providers.cloudflare',
|
|
115
|
-
why: 'config keys name the ROLE, not the vendor (#23)',
|
|
116
|
-
},
|
|
117
|
-
recaptcha: {
|
|
118
|
-
replacement: 'captcha.providers.recaptcha',
|
|
119
|
-
why: 'config keys name the ROLE, not the vendor (#23)',
|
|
120
|
-
},
|
|
121
|
-
searchConsole: {
|
|
122
|
-
replacement: 'search.providers.searchConsole',
|
|
123
|
-
why: 'config keys name the ROLE, not the vendor (#23) — `seo` already means the parasite-SEO content feature',
|
|
124
|
-
},
|
|
125
|
-
gcp: {
|
|
126
|
-
replacement: 'cloud',
|
|
127
|
-
why: 'one cloud home (#23): gcp.organizationId/billingAccount are now cloud.organizationId/cloud.billingAccount',
|
|
128
|
-
},
|
|
129
|
-
firebase: {
|
|
130
|
-
replacement: 'cloud',
|
|
131
|
-
why: 'one cloud home (#23): the provisioning fields are now cloud.shared/supportEmail/apiSubdomain, and projectId lives only at cloud.config.projectId',
|
|
132
|
-
},
|
|
133
|
-
'advertising.providers.google-adsense': {
|
|
134
|
-
replacement: 'advertising.providers.adsense',
|
|
135
|
-
why: 'provider ids drop the vendor prefix and every key is camelCase (#23) — the slots are displaySlot/inArticleSlot/inFeedSlot/multiplexSlot',
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
// ─── adsense has ONE switch (#527/#628) ───────────────────────────────
|
|
139
|
-
// The second gate is what let one config say "stop managing" while the site
|
|
140
|
-
// kept serving ads off the same id, so #527 deleted the service's
|
|
141
|
-
// `enabled === false` skip and never shipped the `units` half of the same
|
|
142
|
-
// proposal. Neither was registered here, so a brand still carrying
|
|
143
|
-
// `enabled: false` validated CLEAN — and the account it was meant to leave
|
|
144
|
-
// alone started being managed on the next walk, the gate reading exactly
|
|
145
|
-
// like it still worked.
|
|
146
|
-
'advertising.providers.adsense.enabled': {
|
|
147
|
-
replacement: 'advertising.providers.adsense',
|
|
148
|
-
why: 'adsense has ONE switch (#527): `client` presence manages the account, renders the units and writes the ads.txt record together — set `advertising.providers.adsense: false` to opt the provider out, and there is no second gate to disable it with',
|
|
149
|
-
},
|
|
150
|
-
'advertising.providers.adsense.units': {
|
|
151
|
-
replacement: 'advertising.providers.adsense',
|
|
152
|
-
why: 'adsense has ONE switch (#527): the render gate the key proposed was refused — `client` presence is the whole answer, so a managed-but-ad-free brand omits the block and manages the account by hand',
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
// ─── one provider shape everywhere (#425) ──────────────────────────────
|
|
156
|
-
// Every role names its vendors under `providers`, so the flat picks
|
|
157
|
-
// (`domain.provider`), the fourth word (`payment.processors`) and the bare
|
|
158
|
-
// vendor key (`certificates.apple`) are gone. Each CONVERTED LEAF is its
|
|
159
|
-
// own row: a config still carrying the old key would validate clean and
|
|
160
|
-
// silently lose the setting, which is exactly what this guard exists for.
|
|
161
|
-
'payment.processors': {
|
|
162
|
-
replacement: 'payment.providers',
|
|
163
|
-
why: 'one provider shape everywhere (#425) — the block is `providers` in every role, payment included (and the singular word followed in #428: `provider` on a subscription/webhook document, on the payments routes, and in the code)',
|
|
164
|
-
},
|
|
165
|
-
'certificates.apple': {
|
|
166
|
-
replacement: 'certificates.providers.apple',
|
|
167
|
-
why: 'one provider shape everywhere (#425) — no bare vendor keys; Windows signing will sit beside it as certificates.providers.<vendor>',
|
|
168
|
-
},
|
|
169
|
-
'domain.provider': {
|
|
170
|
-
replacement: 'domain.providers.<registrar>',
|
|
171
|
-
why: 'one provider shape everywhere (#425) — the registrar is a KEY under `domain.providers` (namecheap/squarespace); no entry = none chosen and the domain service skips, exactly as a null provider did',
|
|
172
|
-
},
|
|
173
|
-
'domain.email.provider': {
|
|
174
|
-
replacement: 'domain.email.providers.<provider>',
|
|
175
|
-
why: 'one provider shape everywhere (#425) — the mailbox provider is a KEY under `domain.email.providers` (cloudflare/squarespace/privateemail); `domain.email.forwarding` stays role-level',
|
|
176
|
-
},
|
|
177
|
-
'translation.provider': {
|
|
178
|
-
replacement: 'translation.providers.<name>',
|
|
179
|
-
why: 'one provider shape everywhere (#425) — presence picks the engine ({ claude: {} } / { chatgpt: {} }); `translation.model` stays role-level',
|
|
180
|
-
},
|
|
181
|
-
'devlog.provider': {
|
|
182
|
-
replacement: 'devlog.providers.ghostii',
|
|
183
|
-
why: 'one provider shape everywhere (#425) — the writer is a KEY under `devlog.providers`, and its settings moved inside it; `devlog.enabled` stays role-level',
|
|
184
|
-
},
|
|
185
|
-
'devlog.lookbackDays': {
|
|
186
|
-
replacement: 'devlog.providers.ghostii.lookbackDays',
|
|
187
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
188
|
-
},
|
|
189
|
-
'devlog.orgs': {
|
|
190
|
-
replacement: 'devlog.providers.ghostii.orgs',
|
|
191
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
192
|
-
},
|
|
193
|
-
'devlog.excludeRepos': {
|
|
194
|
-
replacement: 'devlog.providers.ghostii.excludeRepos',
|
|
195
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
196
|
-
},
|
|
197
|
-
'devlog.excludeCommits': {
|
|
198
|
-
replacement: 'devlog.providers.ghostii.excludeCommits',
|
|
199
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
200
|
-
},
|
|
201
|
-
'devlog.excludeTopics': {
|
|
202
|
-
replacement: 'devlog.providers.ghostii.excludeTopics',
|
|
203
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
204
|
-
},
|
|
205
|
-
'devlog.includePrivate': {
|
|
206
|
-
replacement: 'devlog.providers.ghostii.includePrivate',
|
|
207
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
208
|
-
},
|
|
209
|
-
'devlog.postPath': {
|
|
210
|
-
replacement: 'devlog.providers.ghostii.postPath',
|
|
211
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
212
|
-
},
|
|
213
|
-
'devlog.destinations': {
|
|
214
|
-
replacement: 'devlog.providers.ghostii.destinations',
|
|
215
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
216
|
-
},
|
|
217
|
-
'devlog.overrides': {
|
|
218
|
-
replacement: 'devlog.providers.ghostii.overrides',
|
|
219
|
-
why: 'provider-hung devlog settings live under the provider that reads them (#425)',
|
|
220
|
-
},
|
|
221
|
-
'monitoring.provider': {
|
|
222
|
-
replacement: 'monitoring.providers.sentry',
|
|
223
|
-
why: "one provider shape everywhere (#425) — the monitor is a KEY under `monitoring.providers` (only sentry today); `monitoring.enabled` stays role-level",
|
|
224
|
-
},
|
|
225
|
-
'monitoring.org': {
|
|
226
|
-
replacement: 'monitoring.providers.sentry.org',
|
|
227
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425)',
|
|
228
|
-
},
|
|
229
|
-
'monitoring.dsn': {
|
|
230
|
-
replacement: 'monitoring.providers.sentry.dsn',
|
|
231
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425): per-surface DSNs are targets.<name>.monitoring.providers.sentry.dsn',
|
|
232
|
-
},
|
|
233
|
-
'monitoring.environment': {
|
|
234
|
-
replacement: 'monitoring.providers.sentry.environment',
|
|
235
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425)',
|
|
236
|
-
},
|
|
237
|
-
'monitoring.sampleRate': {
|
|
238
|
-
replacement: 'monitoring.providers.sentry.sampleRate',
|
|
239
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425)',
|
|
240
|
-
},
|
|
241
|
-
'monitoring.tracesSampleRate': {
|
|
242
|
-
replacement: 'monitoring.providers.sentry.tracesSampleRate',
|
|
243
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425)',
|
|
244
|
-
},
|
|
245
|
-
'monitoring.scrubEmail': {
|
|
246
|
-
replacement: 'monitoring.providers.sentry.scrubEmail',
|
|
247
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425)',
|
|
248
|
-
},
|
|
249
|
-
'monitoring.attachScreenshot': {
|
|
250
|
-
replacement: 'monitoring.providers.sentry.attachScreenshot',
|
|
251
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425)',
|
|
252
|
-
},
|
|
253
|
-
'monitoring.bundlePatterns': {
|
|
254
|
-
replacement: 'monitoring.providers.sentry.bundlePatterns',
|
|
255
|
-
why: 'provider-hung Sentry settings live under the provider that reads them (#425)',
|
|
256
|
-
},
|
|
257
|
-
'marketing.campaigns.provider': {
|
|
258
|
-
replacement: 'marketing.campaigns.providers.sendgrid',
|
|
259
|
-
why: "one provider shape everywhere (#425) — the email-marketing vendor is a KEY under `marketing.campaigns.providers`; `marketing.campaigns.enabled` stays role-level",
|
|
260
|
-
},
|
|
261
|
-
'marketing.campaigns.listId': {
|
|
262
|
-
replacement: 'marketing.campaigns.providers.sendgrid.listId',
|
|
263
|
-
why: 'the list id is a SendGrid fact and lives under the provider that reads it (#425)',
|
|
264
|
-
},
|
|
265
|
-
'marketing.newsletter.provider': {
|
|
266
|
-
replacement: 'marketing.newsletter.providers.beehiiv',
|
|
267
|
-
why: "one provider shape everywhere (#425) — the newsletter vendor is a KEY under `marketing.newsletter.providers`; `marketing.newsletter.enabled` and `marketing.newsletter.content` stay role-level (content is pipeline config, not Beehiiv config)",
|
|
268
|
-
},
|
|
269
|
-
'marketing.newsletter.publicationId': {
|
|
270
|
-
replacement: 'marketing.newsletter.providers.beehiiv.publicationId',
|
|
271
|
-
why: 'the publication id is a Beehiiv fact and lives under the provider that reads it (#425)',
|
|
272
|
-
},
|
|
273
|
-
'blog.provider': {
|
|
274
|
-
replacement: 'blog.providers.ghostii',
|
|
275
|
-
why: "one provider shape everywhere (#425) — the blog writer is a KEY under `blog.providers`; `blog.enabled` and `blog.content` stay role-level (content is pipeline config)",
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
// ─── a page's TITLE and DESCRIPTION live in page frontmatter (#607/#564) ──
|
|
279
|
-
// The config `meta` section shipped for one wave as the site's default page
|
|
280
|
-
// meta, beside the bare `meta:` a page and a layout already wrote — two homes
|
|
281
|
-
// for one fact, free to disagree. Ian's 2026-08-26 ruling deleted the copy
|
|
282
|
-
// that could not win: the head walk is page `meta:` → layout `meta:` →
|
|
283
|
-
// brand.name / brand.description, so a title or description left in
|
|
284
|
-
// omega.json5 reaches NOTHING and every page silently falls back to the brand.
|
|
285
|
-
//
|
|
286
|
-
// `index` is the exception, and #564 (Ian 2026-09-09, the same-name ruling in
|
|
287
|
-
// docs/shared/rulings.md) is why the rows are per KEY now rather than on the
|
|
288
|
-
// whole block: a site-wide default and the page override of it share ONE name
|
|
289
|
-
// at every level, so the site default is `targets.web.meta.index` and the page
|
|
290
|
-
// writes `meta.index`. That key is LIVE, and it overlays to the resolved root
|
|
291
|
-
// on a web load like every target key, and a row on the whole `meta` object would
|
|
292
|
-
// fire on a brand that is simply using it.
|
|
293
|
-
//
|
|
294
|
-
// Matched at AUTHORED paths, never by key NAME: `analytics.providers.meta` is
|
|
295
|
-
// a legitimate key one level down.
|
|
296
|
-
'meta.title': {
|
|
297
|
-
replacement: 'brand.name (and the page\'s own frontmatter `meta.title`)',
|
|
298
|
-
why: 'a title never exists in two places (#607): page frontmatter `meta:` is the only per-page meta, and the site-wide default is the brand block the head falls back to',
|
|
299
|
-
},
|
|
300
|
-
'meta.description': {
|
|
301
|
-
replacement: 'brand.description (and the page\'s own frontmatter `meta.description`)',
|
|
302
|
-
why: 'a description never exists in two places (#607): page frontmatter `meta:` is the only per-page meta, and the site-wide default is the brand block the head falls back to',
|
|
303
|
-
},
|
|
304
|
-
'targets.web.meta.title': {
|
|
305
|
-
replacement: 'brand.name (and the page\'s own frontmatter `meta.title`)',
|
|
306
|
-
why: 'a title never exists in two places (#607): @omega.js/web reads no site-wide title; a per-page title belongs in that page\'s own frontmatter',
|
|
307
|
-
},
|
|
308
|
-
'targets.web.meta.description': {
|
|
309
|
-
replacement: 'brand.description (and the page\'s own frontmatter `meta.description`)',
|
|
310
|
-
why: 'a description never exists in two places (#607): @omega.js/web reads no site-wide description; a per-page description belongs in that page\'s own frontmatter',
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
// ─── one index flag, one name at both levels (#564) ────────────────────
|
|
314
|
-
// #725 gave the site-wide noindex its own name, `seo.index`, while a page
|
|
315
|
-
// said `meta.index`, one decision spelled two ways, which is the defect
|
|
316
|
-
// Ian's 2026-09-09 same-name ruling names. The site default is now the same
|
|
317
|
-
// key the page writes, under the target that reads it.
|
|
318
|
-
'seo.index': {
|
|
319
|
-
replacement: 'targets.web.meta.index',
|
|
320
|
-
why: 'a global value and its specific override share ONE name (#564, Ian 2026-09-09): the site-wide default is `targets.web.meta.index` and a page overrides it with `meta.index` in its own frontmatter; `seo` keeps `enabled` and `github.content`',
|
|
321
|
-
},
|
|
322
|
-
|
|
323
|
-
// ─── one home for the download/extension links (#610) ──────────────────
|
|
324
|
-
// The hand-written page maps survived beside the derivation #85/#124 added,
|
|
325
|
-
// so explicit config could silently override the release the desktop target
|
|
326
|
-
// actually ships. Matched at their AUTHORED path: `targets.web` is where the
|
|
327
|
-
// converter wrote them and where every carrying brand still has them, and
|
|
328
|
-
// the merged targets map rides every resolved config, so one row fires once
|
|
329
|
-
// for every target load (a root `download` on a web load is the same key
|
|
330
|
-
// overlaid, not a second mistake).
|
|
331
|
-
'targets.web.download': {
|
|
332
|
-
replacement: 'targets.desktop.releases',
|
|
333
|
-
why: 'two homes for one fact (#610) — the /download page and its shortlinks derive from the desktop target\'s releases block, curated onto site.targets.desktop.releasesUrl; a hand-written map could point at a release that does not exist',
|
|
334
|
-
},
|
|
335
|
-
'targets.web.extension': {
|
|
336
|
-
replacement: 'targets.extension.listings',
|
|
337
|
-
why: 'two homes for one fact (#610) — the /extension page and its shortlinks read the extension target\'s store listings, curated onto site.targets.extension.listings',
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
// ─── the four schema-less web sections (#850) ─────────────────────────
|
|
341
|
-
// Everything the build processes has a schema home (Ian 2026-09-09). These
|
|
342
|
-
// four were the exception: presentation blocks the converter
|
|
343
|
-
// wrote under `targets.web`, which @omega.js/web carried in a PRIVATE list
|
|
344
|
-
// (`WEB_ONLY_SECTIONS`) purely to let its own `config:` guard pass them.
|
|
345
|
-
// The list is gone, so each one is a registered path instead of a key that
|
|
346
|
-
// validates clean and reaches nothing. Matched at their AUTHORED path, the
|
|
347
|
-
// one place a carrying brand has them (a root spelling on a web load is the
|
|
348
|
-
// same key overlaid, exactly as with the #610 pair above).
|
|
349
|
-
'targets.web.favicon': {
|
|
350
|
-
replacement: 'nothing for the path; brand.images.favicon for the source image',
|
|
351
|
-
why: 'the favicon set is MINTED from the brand images (#850): the manager assets service mints it and the web build bridges it to /assets/images/favicon, so a path override pointed the whole site at an unminted folder; theme-color comes from `brand.color` now, the one place a brand states its hex',
|
|
352
|
-
},
|
|
353
|
-
'targets.web.manifest': {
|
|
354
|
-
replacement: 'nothing: the minted set ships site.webmanifest',
|
|
355
|
-
why: 'no reader anywhere in @omega.js/web (#850): the web app manifest that ships is the minted set\'s own site.webmanifest, so every key under this block was a value nothing consulted',
|
|
356
|
-
},
|
|
357
|
-
'targets.web.icons': {
|
|
358
|
-
replacement: 'the `icon` on the link itself, as Font Awesome classes',
|
|
359
|
-
why: 'one icon mechanism (#619): a footer link carries its own `fa-*` classes (`icon: \'fa-brands fa-github\'`), so the name-to-markup map is gone; the legacy block only ever held a `style`, which the map lookup could never resolve',
|
|
360
|
-
},
|
|
361
|
-
'targets.web.currency': {
|
|
362
|
-
replacement: 'payment.currency',
|
|
363
|
-
why: 'two homes for one fact (#850): the price currency belongs to the payment section every other surface reads it from, and the pricing JSON-LD reads it there',
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
// ─── redirects are not web config (#466) ──────────────────────────────
|
|
367
|
-
// The block shipped for one wave (0.45.0) and was withdrawn: static hosting
|
|
368
|
-
// has no server, so the map could only be answered CLIENT-side off the built
|
|
369
|
-
// 404 page — a search engine saw a 404 that redirects, never a move. A
|
|
370
|
-
// TEMPLATED redirect genuinely needs edge computing and is a Cloudflare
|
|
371
|
-
// redirect rule; an enumerable one is a redirect PAGE. Matched at its
|
|
372
|
-
// AUTHORED path, the one place a carrying brand has it.
|
|
373
|
-
'targets.web.redirects': {
|
|
374
|
-
replacement: 'edge.providers.cloudflare.rules.redirect',
|
|
375
|
-
why: 'redirects are not web config (#466) — a TEMPLATED redirect (/c/:id → /code?id=:id) is a Cloudflare redirect rule the edge service reconciles, and a redirect whose URLs can be enumerated is a PAGE on the `modules/utilities/redirect` layout with `redirect.url` in its frontmatter',
|
|
376
|
-
},
|
|
377
|
-
|
|
378
|
-
// ─── the bundler has no consumer override (#737) ──────────────────────
|
|
379
|
-
// Desktop moved off webpack and the externals knob went with it: esbuild's
|
|
380
|
-
// externals set is the framework's own native-module list plus whatever the
|
|
381
|
-
// consumer's package.json declares from it, resolved at build time. Nothing
|
|
382
|
-
// reads the key any more — and it sits inside the `targets` namespace the
|
|
383
|
-
// undeclared-key warning EXEMPTS, so without a row here a brand still
|
|
384
|
-
// carrying it validates completely clean and silently loses the setting.
|
|
385
|
-
// Matched at its AUTHORED path, the one place a carrying brand has it.
|
|
386
|
-
'targets.desktop.em.webpack.externals': {
|
|
387
|
-
replacement: 'nothing — the externals set is framework-owned',
|
|
388
|
-
why: "the desktop bundler is esbuild (#737) and there is no consumer-facing override key: the externals set is the framework's native-module list (`nativeExternals` in @omega.js/desktop's src/gulp/tasks/bundle.js) plus what the consumer's own package.json declares from it, so a genuinely native module the list misses is raised upstream and every brand gets the fix",
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
// ─── one features catalog, one values map (#647) ───────────────────────
|
|
392
|
-
// A metered feature used to be spelled twice on every product — a number in
|
|
393
|
-
// `limits` and a display row in the `features` ARRAY — and its PACING was a
|
|
394
|
-
// product-wide `rateLimit` that no single feature could opt out of. The
|
|
395
|
-
// catalog defines each feature once (name, icon, definition, and the `usage`
|
|
396
|
-
// block that meters it) and a product names only its value, so a limit and
|
|
397
|
-
// the row that renders it can no longer disagree. Matched at their AUTHORED
|
|
398
|
-
// paths: `limits` is a legitimate word elsewhere, so this is not a name test.
|
|
399
|
-
'payment.products.limits': {
|
|
400
|
-
replacement: 'payment.products[].features',
|
|
401
|
-
why: 'a product names one VALUE per feature (#647) — `limits: { saves: 100 }` becomes `features: { saves: 100 }`, and the feature itself (name, icon, definition, pacing, mirrors) is defined once in the top-level `features` catalog',
|
|
402
|
-
},
|
|
403
|
-
'payment.products.rateLimit': {
|
|
404
|
-
replacement: 'features.<id>.usage.pace',
|
|
405
|
-
why: 'pacing is per FEATURE now (#647) — day pacing is the default on every counted feature, and `usage: { pace: false }` on the catalog entry is the opt-out the product-wide `rateLimit: "monthly"` used to be',
|
|
406
|
-
},
|
|
407
|
-
|
|
408
|
-
// ─── one releases repo, no mirror (#620/#799) ──────────────────────────
|
|
409
|
-
// The `download-server` mirror existed to give marketing a fixed filename,
|
|
410
|
-
// which the versionless artifact names (#620) made free: the site links the
|
|
411
|
-
// releases repo directly and reads nothing from the mirror. #799 deleted the
|
|
412
|
-
// lane (gulp/mirror-downloads, the finalize-release mirror step, the repo
|
|
413
|
-
// provisioning), so a brand still carrying the block gets a second repo
|
|
414
|
-
// provisioned and nothing published to it. Matched at their AUTHORED paths,
|
|
415
|
-
// one row per key: `downloads` is a legitimate word elsewhere (the curated
|
|
416
|
-
// site.targets.desktop.downloads map is the derived direct-download links),
|
|
417
|
-
// so this is not a name test.
|
|
418
|
-
'targets.desktop.downloads.enabled': {
|
|
419
|
-
replacement: 'targets.desktop.releases',
|
|
420
|
-
why: 'one public releases repo per brand (#620/#799): the fixed-name mirror is gone, and the versionless assets on the releases repo ARE the permanent download links',
|
|
421
|
-
},
|
|
422
|
-
'targets.desktop.downloads.owner': {
|
|
423
|
-
replacement: 'targets.desktop.releases.owner',
|
|
424
|
-
why: 'one public releases repo per brand (#620/#799): there is no second repo to own, and the releases owner defaults to the brand repo owner',
|
|
425
|
-
},
|
|
426
|
-
'targets.desktop.downloads.repo': {
|
|
427
|
-
replacement: 'targets.desktop.releases.repo',
|
|
428
|
-
why: 'one public releases repo per brand (#620/#799): the release artifacts and the marketing downloads are the same assets in `<brand.id>-releases`',
|
|
429
|
-
},
|
|
430
|
-
'targets.desktop.downloads.tag': {
|
|
431
|
-
replacement: 'nothing: the versionless assets live on the `v<x.y.z>` release',
|
|
432
|
-
why: 'one public releases repo per brand (#620/#799): a stable mirror tag is what `/releases/latest/download/<asset>` replaced, so no tag is configured anywhere',
|
|
433
|
-
},
|
|
434
|
-
|
|
435
|
-
// ─── one repo block, and no repo name anywhere (#883) ──────────────────
|
|
436
|
-
// A brand's repo hosting used to be spelled in four places: the
|
|
437
|
-
// `repo.providers.github` block, a separate top-level `github` identity, a
|
|
438
|
-
// `targets.<name>.github.repo` override, and the desktop releases
|
|
439
|
-
// owner/repo. One block says it now (`repo: { provider, org }`), every repo
|
|
440
|
-
// name derives from `<brand.id>-<role>`, and visibility is the brand root
|
|
441
|
-
// package.json's `private` field. Matched at their AUTHORED paths, one row
|
|
442
|
-
// per LEAF: each of these validated clean under the new shape and silently
|
|
443
|
-
// addressed a repo nothing publishes to.
|
|
444
|
-
'repo.providers.github.org': {
|
|
445
|
-
replacement: 'repo.org',
|
|
446
|
-
why: 'ONE repo block (#883): `repo: { provider: \'github\', org }` is the whole declaration, and the org owns every repo the brand derives',
|
|
447
|
-
},
|
|
448
|
-
'repo.providers.github.repo': {
|
|
449
|
-
replacement: 'nothing: the source repo IS `<brand.id>-omega`',
|
|
450
|
-
why: 'no repo NAME is configurable anywhere (#883): a repo name that must differ is a brand id that must differ, so rename the GitHub repo to `<brand.id>-omega` instead',
|
|
451
|
-
},
|
|
452
|
-
'repo.providers.github.private': {
|
|
453
|
-
replacement: 'the brand root package.json `private` field',
|
|
454
|
-
why: 'visibility has ONE statement (#883): `private: true` (or absent) is a private brand, `false` a public one, and the manage walk reconciles the repo to it in both directions',
|
|
455
|
-
},
|
|
456
|
-
'repo.providers.github.shared': {
|
|
457
|
-
replacement: 'nothing: an org may host many brands, and no brand rewrites an org profile',
|
|
458
|
-
why: 'the org-profile reconcile is gone (#883), so there is no shared-org exception left to declare',
|
|
459
|
-
},
|
|
460
|
-
'repo.providers.github.enabled': {
|
|
461
|
-
replacement: 'the presence of the `repo` block',
|
|
462
|
-
why: 'presence is the switch (#883), exactly as a target key\'s presence enables that target: a brand that hosts its source somewhere the manager does not touch omits the block',
|
|
463
|
-
},
|
|
464
|
-
'github.user': {
|
|
465
|
-
replacement: 'nothing: the org is `repo.org`',
|
|
466
|
-
why: 'the separate GitHub identity block is gone (#883): nothing read `user`, and every repo address derives from `repo.org` plus `brand.id`',
|
|
467
|
-
},
|
|
468
|
-
'github.website': {
|
|
469
|
-
replacement: 'nothing: a web target\'s site repo is `<brand.id>-<target name>`',
|
|
470
|
-
why: 'the website repo derives now (#883): each GitHub-hosted web target publishes its built site to its own `<brand.id>-<name>` repo, Pages serving it at the target\'s url',
|
|
471
|
-
},
|
|
472
|
-
// ─── the company is ONE key, outside `brand` (#677) ────────────────────
|
|
473
|
-
// `brand.company` was a typed DISPLAY NAME (Ian 2026-09-12: "brand.company
|
|
474
|
-
// hardcoded was a workaround"): the parent's name is the parent's to state,
|
|
475
|
-
// so it resolves now. Registered at its authored path, because `company` is
|
|
476
|
-
// a legitimate key one level up and at the top level.
|
|
477
|
-
'brand.company': {
|
|
478
|
-
replacement: 'company.name (resolved from `company: { id }`)',
|
|
479
|
-
why: "the company is ONE top-level key now (#677): type `company: { id: '<parent brand.id>' }` (or 'self') and the loader fills company.name/url/images from the parent's own config, so no brand restates its parent's facts",
|
|
480
|
-
},
|
|
481
|
-
'brand.images.companyWordmark': {
|
|
482
|
-
replacement: 'company.images.wordmark (resolved from `company: { id }`)',
|
|
483
|
-
why: "the parent's wordmark is the parent's own `brand.images.wordmark` (#677), resolved through `company: { id }` for every sub-brand instead of pasted into each one",
|
|
484
|
-
},
|
|
485
|
-
|
|
486
|
-
// ─── the ONE platform vocabulary (#867) ───────────────────────────────
|
|
487
|
-
// The client's words everywhere (`mac`, `windows`, `linux`), and what a
|
|
488
|
-
// target ships is DECLARED per format. Both of these validated clean while
|
|
489
|
-
// nothing read them: a brand still saying `win` got @omega.js/desktop's
|
|
490
|
-
// default Windows settings and its own were ignored, and a brand still
|
|
491
|
-
// saying `snap.enabled: true` published no snap at all. Registered at both
|
|
492
|
-
// shapes a brand can write them in: inside the target entry (the brand file)
|
|
493
|
-
// and at the top level (a target's own config/omega.json5).
|
|
494
|
-
'platforms.win': {
|
|
495
|
-
replacement: 'platforms.windows (with its formats inside: platforms.windows.formats.nsis)',
|
|
496
|
-
why: "ONE platform vocabulary, the client's (#867): run `npx omega manage --migration=platform-names --execute` at the brand root to rewrite it (it also renames config/icons/macos/ to config/icons/mac/). Run the migration BEFORE `omega migrate`, which deletes a retired key rather than moving it",
|
|
497
|
-
},
|
|
498
|
-
'targets.desktop.platforms.win': {
|
|
499
|
-
replacement: 'targets.desktop.platforms.windows (with its formats inside: platforms.windows.formats.nsis)',
|
|
500
|
-
why: "ONE platform vocabulary, the client's (#867): run `npx omega manage --migration=platform-names --execute` at the brand root to rewrite it (it also renames config/icons/macos/ to config/icons/mac/). Run the migration BEFORE `omega migrate`, which deletes a retired key rather than moving it",
|
|
501
|
-
},
|
|
502
|
-
'platforms.linux.snap': {
|
|
503
|
-
replacement: 'platforms.linux.formats.snap (presence IS the switch, so the `enabled` flag is gone)',
|
|
504
|
-
why: 'what a target ships is one declaration now (#867): every platform and format defaults ON and `platforms.linux.formats.snap: false` is the only way to drop the snap. Its settings (channels, confinement, grade, autoStart) move inside the format. `npx omega manage --migration=platform-names --execute` performs the move',
|
|
505
|
-
},
|
|
506
|
-
'targets.desktop.platforms.linux.snap': {
|
|
507
|
-
replacement: 'targets.desktop.platforms.linux.formats.snap (presence IS the switch, so the `enabled` flag is gone)',
|
|
508
|
-
why: 'what a target ships is one declaration now (#867): every platform and format defaults ON and `platforms.linux.formats.snap: false` is the only way to drop the snap. Its settings (channels, confinement, grade, autoStart) move inside the format. `npx omega manage --migration=platform-names --execute` performs the move',
|
|
509
|
-
},
|
|
510
|
-
|
|
511
|
-
// ─── the translation list says what to TRANSLATE (#858) ───────────────
|
|
512
|
-
// Ian 2026-09-13: an exclude list defaulted to "translate everything", so a
|
|
513
|
-
// brand that never thought about it paid a provider for its whole blog. The
|
|
514
|
-
// list is `translation.include` now: globs with `!` negation, defaulting to
|
|
515
|
-
// ['**', '!blog/**'] in the DEFAULTS layer, and the page half is the same
|
|
516
|
-
// key one level down (`translation.include: true`/`false` in frontmatter),
|
|
517
|
-
// which is Ian's 2026-09-09 same-name ruling. The row carries a `convert`,
|
|
518
|
-
// so `omega migrate` MOVES the setting instead of only deleting it: each
|
|
519
|
-
// excluded route becomes a negation on top of the default. Registered at its
|
|
520
|
-
// authored path AND at the `targets.web` overlay, the two places a brand can
|
|
521
|
-
// write it; `translation` is a legitimate key name at both.
|
|
522
|
-
'translation.exclude': {
|
|
523
|
-
replacement: 'translation.include',
|
|
524
|
-
why: "the route list says what to TRANSLATE now (#858, Ian 2026-09-13): globs with `!` negation, read in .gitignore order, defaulting to ['**', '!blog/**'], so `exclude: ['docs']` becomes `include: ['**', '!docs']`, and a page overrides it for itself with `translation.include: true`/`false` in its own frontmatter. `omega migrate` performs the move",
|
|
525
|
-
convert: excludeToInclude,
|
|
526
|
-
},
|
|
527
|
-
'targets.web.translation.exclude': {
|
|
528
|
-
replacement: 'targets.web.translation.include',
|
|
529
|
-
why: "the route list says what to TRANSLATE now (#858, Ian 2026-09-13): globs with `!` negation, read in .gitignore order, defaulting to ['**', '!blog/**'], so `exclude: ['docs']` becomes `include: ['**', '!docs']`, and a page overrides it for itself with `translation.include: true`/`false` in its own frontmatter. `omega migrate` performs the move",
|
|
530
|
-
convert: excludeToInclude,
|
|
531
|
-
},
|
|
532
|
-
|
|
533
|
-
'targets.desktop.releases.owner': {
|
|
534
|
-
replacement: 'nothing: the releases repo is `<brand.id>-releases` under `repo.org`',
|
|
535
|
-
why: 'one public releases repo per brand (#883), with no override: `releases: {}` stays the presence switch for the site\'s download links',
|
|
536
|
-
},
|
|
537
|
-
'targets.desktop.releases.repo': {
|
|
538
|
-
replacement: 'nothing: the releases repo is `<brand.id>-releases` under `repo.org`',
|
|
539
|
-
why: 'one public releases repo per brand (#883), with no override: `releases: {}` stays the presence switch for the site\'s download links',
|
|
540
|
-
},
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
// The per-target `github` override, one row per TYPE (#883). The type-row rule
|
|
544
|
-
// above (shapePath reads a target's NAME as its TYPE) makes each row fire on
|
|
545
|
-
// every target of that type a brand runs, and enumerating TARGET_TYPES is what
|
|
546
|
-
// keeps a type nobody thought of from slipping through: the backend's CMS
|
|
547
|
-
// content repo is the override that existed, but the key validated clean on any
|
|
548
|
-
// target and pointed the commits at a repo nothing else addressed.
|
|
549
|
-
for (const type of TARGET_TYPES) {
|
|
550
|
-
RETIRED_PATHS[`targets.${type}.github.repo`] = {
|
|
551
|
-
replacement: 'nothing: every repo the brand owns is `<brand.id>-<role>` under `repo.org`',
|
|
552
|
-
why: 'no repo NAME is configurable anywhere (#883): the CMS commits to the SOURCE repo `<brand.id>-omega`, so a content repo of its own is a brand of its own',
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// The walked path is not always the SHAPE a RETIRED_PATHS row names, two ways:
|
|
557
|
-
// a value inside an array carries its position (`redirects.0.from`), and a
|
|
558
|
-
// target carries its NAME while the rows name its TYPE (#886). Both are
|
|
559
|
-
// normalized for MATCHING only: the REPORTED path stays the real one, which is
|
|
560
|
-
// where the author finds the key.
|
|
561
|
-
//
|
|
562
|
-
// The name normalization is what makes a `targets.web.*` row fire on EVERY web
|
|
563
|
-
// target a brand runs (`targets.community.meta.title` when community is
|
|
564
|
-
// `type: 'web'`), which is the shape a brand with two sites has (#732, #886).
|
|
565
|
-
function shapePath(keyPath, targetTypes) {
|
|
566
|
-
const parts = keyPath.split('.').filter((segment) => !/^\d+$/.test(segment));
|
|
567
|
-
|
|
568
|
-
if (parts[0] === 'targets' && parts.length > 1 && targetTypes[parts[1]]) {
|
|
569
|
-
parts[1] = targetTypes[parts[1]];
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return parts.join('.');
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* Every declared target's name → type, read off the config being walked. A
|
|
577
|
-
* sub-tree with no `targets` map (or entries with no `type`) yields {}, so the
|
|
578
|
-
* rows then match by the key as written.
|
|
579
|
-
* @param {object} object - Parsed config (or any sub-tree of one).
|
|
580
|
-
* @returns {object} name → type.
|
|
581
|
-
*/
|
|
582
|
-
function targetTypes(object) {
|
|
583
|
-
const targets = object && object.targets;
|
|
584
|
-
if (!targets || typeof targets !== 'object' || Array.isArray(targets)) return {};
|
|
585
|
-
|
|
586
|
-
const types = {};
|
|
587
|
-
Object.keys(targets).forEach((name) => {
|
|
588
|
-
const entry = targets[name];
|
|
589
|
-
if (entry && typeof entry === 'object' && !Array.isArray(entry) && typeof entry.type === 'string') {
|
|
590
|
-
types[name] = entry.type;
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
return types;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function walk(node, path, found, types) {
|
|
598
|
-
if (Array.isArray(node)) {
|
|
599
|
-
node.forEach((item, index) => walk(item, path ? `${path}.${index}` : String(index), found, types));
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
if (typeof node !== 'object' || node === null) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
Object.keys(node).forEach((key) => {
|
|
608
|
-
const keyPath = path ? `${path}.${key}` : key;
|
|
609
|
-
|
|
610
|
-
if (RETIRED_KEYS[key]) {
|
|
611
|
-
found.push({ path: keyPath, key, ...RETIRED_KEYS[key] });
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
const shape = shapePath(keyPath, types);
|
|
615
|
-
|
|
616
|
-
if (RETIRED_PATHS[shape]) {
|
|
617
|
-
found.push({ path: keyPath, key, ...RETIRED_PATHS[shape] });
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
walk(node[key], keyPath, found, types);
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* Recursively find retired config keys.
|
|
626
|
-
* @param {object} object - Parsed config (or any sub-tree of one).
|
|
627
|
-
* @returns {Array<{ path: string, key: string, replacement: string, why: string }>}
|
|
628
|
-
*/
|
|
629
|
-
function findRetiredKeys(object) {
|
|
630
|
-
const found = [];
|
|
631
|
-
walk(object, '', found, targetTypes(object));
|
|
632
|
-
return found;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
module.exports = { findRetiredKeys, RETIRED_KEYS, RETIRED_PATHS };
|