@omega.js/desktop 0.50.0 → 0.52.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/README.md +2 -2
- package/dist/assets/css/tokens/_index.scss +1 -1
- package/dist/assets/themes/base/_includes/frontend/sections/account-section-header.html +4 -1
- package/dist/assets/themes/base/_includes/frontend/sections/footer.html +13 -6
- package/dist/assets/themes/base/_includes/frontend/sections/nav.html +10 -8
- package/dist/assets/themes/base/_includes/global/sections/account.html +3 -1
- package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +12 -8
- package/dist/assets/themes/base/_includes/global/sections/app-topbar.html +10 -6
- package/dist/assets/themes/base/_includes/global/sections/page-header.html +8 -4
- package/dist/assets/themes/base/_layouts/backend/pages/dashboard/index.html +24 -24
- package/dist/assets/themes/base/_layouts/frontend/pages/about.html +10 -10
- package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +35 -35
- package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +4 -4
- package/dist/assets/themes/base/_layouts/frontend/pages/auth/signin.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/auth/signup.html +4 -4
- package/dist/assets/themes/base/_layouts/frontend/pages/contact.html +3 -3
- package/dist/assets/themes/base/_layouts/frontend/pages/download.html +34 -33
- package/dist/assets/themes/base/_layouts/frontend/pages/extension/index.html +11 -11
- package/dist/assets/themes/base/_layouts/frontend/pages/legal/document.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/status.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/team/index.html +9 -7
- package/dist/assets/themes/base/_layouts/frontend/pages/team/member.html +5 -3
- package/dist/assets/themes/base/_sections/about/letter/section.html +1 -1
- package/dist/assets/themes/base/_sections/about/letter/section.json5 +4 -4
- package/dist/assets/themes/base/_sections/marketing/bento/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/bento/section.json5 +15 -15
- package/dist/assets/themes/base/_sections/marketing/cta/section.json5 +1 -1
- package/dist/assets/themes/base/_sections/marketing/hero/section.html +6 -6
- package/dist/assets/themes/base/_sections/marketing/hero/section.json5 +10 -10
- package/dist/assets/themes/base/_sections/marketing/product-demo/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/product-demo/section.json5 +2 -2
- package/dist/assets/themes/base/_sections/marketing/stats/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/stats/section.json5 +5 -5
- package/dist/assets/themes/base/_sections/marketing/trusted-by/section.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/trusted-by/section.json5 +2 -2
- package/dist/assets/themes/neobrutalism/_layouts/frontend/pages/index.html +10 -10
- package/dist/assets/themes/newsflash/_layouts/frontend/pages/index.html +10 -10
- package/dist/assets/themes/newsflash/_sections/marketing/desks/section.html +2 -2
- package/dist/assets/themes/newsflash/_sections/marketing/desks/section.json5 +4 -4
- package/dist/build.js +69 -28
- package/dist/cli-run.js +20 -13
- package/dist/cli.js +12 -7
- package/dist/commands/build.js +0 -2
- package/dist/commands/deploy.js +116 -44
- package/dist/commands/finalize-release.js +5 -4
- package/dist/commands/launch.js +12 -7
- package/dist/commands/lib/deploy-precheck.js +66 -34
- package/dist/commands/lib/ensure-target.js +71 -11
- package/dist/commands/package.js +0 -1
- package/dist/commands/publish.js +11 -4
- package/dist/commands/release.js +99 -252
- package/dist/commands/runner.js +176 -42
- package/dist/commands/sign-windows.js +46 -15
- package/dist/commands/test.js +4 -3
- package/dist/commands/validate-certs.js +291 -115
- package/dist/config/page-template.html +4 -0
- package/dist/defaults/.github/workflows/build.yml +146 -64
- package/dist/defaults/AGENTS.md +3 -2
- package/dist/defaults/_.gitignore +4 -2
- package/dist/defaults/config/certs/README.md +25 -45
- package/dist/defaults/config/omega.json5 +66 -32
- package/dist/defaults/hooks/deploy/pre.js +10 -0
- package/dist/defaults/src/assets/scss/main.scss +12 -2
- package/dist/defaults/src/integrations/tray/index.js +1 -1
- package/dist/gulp/main.js +13 -25
- package/dist/gulp/tasks/audit.js +18 -6
- package/dist/gulp/tasks/build-config.js +181 -60
- package/dist/gulp/tasks/bundle.js +108 -36
- package/dist/gulp/tasks/release.js +86 -4
- package/dist/gulp/tasks/sass.js +11 -0
- package/dist/hooks/lib/notarize-tools.js +137 -0
- package/dist/hooks/notarize-artifacts.js +57 -0
- package/dist/hooks/notarize.js +59 -8
- package/dist/lib/auth-persistence.js +25 -0
- package/dist/lib/client-bridge.js +11 -8
- package/dist/lib/deep-link.js +15 -8
- package/dist/lib/protocol.js +7 -1
- package/dist/lib/restart-manager/install.js +6 -3
- package/dist/lib/sign-helpers/auto-unlock.js +65 -27
- package/dist/lib/sign-helpers/console-lock.js +34 -0
- package/dist/lib/sign-helpers/exec-with-limit.js +68 -0
- package/dist/lib/sign-helpers/resolve-icons.js +6 -4
- package/dist/lib/tray.js +7 -6
- package/dist/main.js +16 -0
- package/dist/preload.js +8 -0
- package/dist/renderer.js +16 -4
- package/dist/runner/job-started.js +104 -0
- package/dist/test/fixtures/consumer-app/config/omega.json5 +7 -0
- package/dist/test/fixtures/consumer-app/package.json +1 -1
- package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +7 -1
- package/dist/test/harness/main-entry.js +2 -1
- package/dist/test/harness/renderer-preload.js +12 -3
- package/dist/test/runners/boot.js +101 -11
- package/dist/test/runners/electron.js +1 -1
- package/dist/test/suites/boot/consumer-app-boots.test.js +54 -0
- package/dist/test/suites/build/audit.test.js +45 -6
- package/dist/test/suites/build/auth-persistence-resolve.test.js +119 -0
- package/dist/test/suites/build/auto-unlock.test.js +105 -0
- package/dist/test/suites/build/boot-runner-timeout.test.js +253 -0
- package/dist/test/suites/build/brand-scss.test.js +106 -0
- package/dist/test/suites/build/build-config.test.js +146 -36
- package/dist/test/suites/build/build-json-bake.test.js +245 -0
- package/dist/test/suites/build/build-verbs.test.js +48 -15
- package/dist/test/suites/build/build-workflow-jobs.test.js +190 -0
- package/dist/test/suites/build/cli.test.js +32 -10
- package/dist/test/suites/build/config-schema.test.js +4 -4
- package/dist/test/suites/build/console-lock.test.js +51 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +71 -2
- package/dist/test/suites/build/deploy-direct.test.js +241 -0
- package/dist/test/suites/build/deploy-dispatch.test.js +287 -0
- package/dist/test/suites/build/deploy-hook.test.js +169 -0
- package/dist/test/suites/build/ensure-target.test.js +14 -2
- package/dist/test/suites/build/env-delivery.test.js +19 -10
- package/dist/test/suites/build/env-watch.test.js +18 -7
- package/dist/test/suites/build/esm-only-dependency.test.js +127 -0
- package/dist/test/suites/build/exec-with-limit.test.js +53 -0
- package/dist/test/suites/build/finalize-release.test.js +2 -2
- package/dist/test/suites/build/get-config.test.js +120 -8
- package/dist/test/suites/build/github-utils.test.js +12 -6
- package/dist/test/suites/build/license-stamp.test.js +6 -4
- package/dist/test/suites/build/manager.test.js +88 -68
- package/dist/test/suites/build/manifest-deps.test.js +116 -0
- package/dist/test/suites/build/merge-line-files.test.js +27 -4
- package/dist/test/suites/build/notarize-artifacts.test.js +135 -0
- package/dist/test/suites/build/notarize-tools.test.js +38 -0
- package/dist/test/suites/build/notarize.test.js +207 -0
- package/dist/test/suites/build/release-pipeline.test.js +38 -60
- package/dist/test/suites/build/release-skipped-upload.test.js +107 -0
- package/dist/test/suites/build/resolve-icons.test.js +35 -35
- package/dist/test/suites/build/runner-job-guard.test.js +182 -0
- package/dist/test/suites/build/runner.test.js +635 -5
- package/dist/test/suites/build/sentry.test.js +8 -3
- package/dist/test/suites/build/setup-scripts.test.js +3 -0
- package/dist/test/suites/build/sign-windows.test.js +92 -8
- package/dist/test/suites/build/test-stealth.test.js +17 -11
- package/dist/test/suites/build/url-helpers.test.js +37 -17
- package/dist/test/suites/build/validate-certs.test.js +428 -55
- package/dist/test/suites/build/validate-config.test.js +3 -3
- package/dist/test/suites/main/auth-flow.test.js +12 -0
- package/dist/test/suites/main/auth-persistence.test.js +14 -17
- package/dist/test/suites/main/auto-updater.test.js +2 -2
- package/dist/test/suites/main/boot-sequence.test.js +1 -1
- package/dist/test/suites/main/client-bridge.integration.test.js +5 -82
- package/dist/test/suites/main/client-bridge.test.js +19 -6
- package/dist/test/suites/main/deep-link.test.js +54 -0
- package/dist/test/suites/main/startup-paths-and-ua.test.js +1 -1
- package/dist/test/suites/main/url-helpers.test.js +81 -72
- package/dist/test/suites/renderer/cross-context-helpers.test.js +19 -16
- package/dist/utils/build-pipeline.js +10 -10
- package/dist/utils/github.js +12 -51
- package/dist/utils/load-env.js +66 -0
- package/dist/utils/mode-helpers.js +43 -111
- package/dist/utils/platform.js +37 -0
- package/dist/utils/runner-env.js +2 -1
- package/dist/utils/runner-job-guard.js +149 -0
- package/dist/utils/ship-keys.js +52 -0
- package/dist/utils/test-stealth.js +5 -3
- package/dist/utils/url-helpers.js +33 -17
- package/dist/vendor/config/bundle-id.js +53 -0
- package/dist/vendor/config/client-config.js +141 -0
- package/dist/vendor/config/company.js +334 -15
- package/dist/vendor/config/dev-facts.js +48 -0
- package/dist/vendor/config/env-delivery.js +231 -9
- package/dist/vendor/config/env-retired.js +137 -0
- package/dist/vendor/config/env-rules.js +22 -3
- package/dist/vendor/config/env-schema.js +234 -117
- package/dist/vendor/config/env.js +55 -26
- package/dist/vendor/config/environment.js +189 -0
- package/dist/vendor/config/hooks.js +13 -11
- package/dist/vendor/config/index.js +125 -44
- package/dist/vendor/config/load.js +366 -115
- package/dist/vendor/config/merge.js +2 -2
- package/dist/vendor/config/order.js +3 -3
- package/dist/vendor/config/platforms.js +276 -0
- package/dist/vendor/config/repo.js +249 -103
- package/dist/vendor/config/retired-keys.js +232 -27
- package/dist/vendor/config/schema.js +525 -99
- package/dist/vendor/config/site-global.js +63 -53
- package/dist/vendor/config/targets.js +187 -0
- package/dist/vendor/config/validate.js +119 -59
- package/dist/vendor/devkit/argv.js +118 -0
- package/dist/vendor/devkit/attach-log-file.js +21 -13
- package/dist/vendor/devkit/brand-tokens.js +278 -0
- package/dist/vendor/devkit/brand-version.js +425 -0
- package/dist/vendor/devkit/build-json.js +91 -0
- package/dist/vendor/devkit/bundle.js +48 -0
- package/dist/vendor/devkit/certificate-expiry.js +108 -0
- package/dist/vendor/devkit/certs.js +16 -194
- package/dist/vendor/devkit/ci-workflows.js +124 -8
- package/dist/vendor/devkit/cli-router.js +3 -3
- package/dist/vendor/devkit/defaults-engine.js +69 -7
- package/dist/vendor/devkit/deploy-follow.js +297 -0
- package/dist/vendor/devkit/deploy-precheck.js +23 -8
- package/dist/vendor/devkit/deploy-record.js +11 -27
- package/dist/vendor/devkit/deploy-snapshot.js +661 -0
- package/dist/vendor/devkit/deploy.js +505 -75
- package/dist/vendor/devkit/git-auth.js +73 -0
- package/dist/vendor/devkit/git-remote.js +172 -0
- package/dist/vendor/devkit/github-repo.js +290 -0
- package/dist/vendor/devkit/local.js +98 -3
- package/dist/vendor/devkit/lockfile.js +127 -0
- package/dist/vendor/devkit/merge-line-files.js +23 -16
- package/dist/vendor/devkit/omega-bin.js +18 -3
- package/dist/vendor/devkit/pack-local.js +388 -0
- package/dist/vendor/devkit/preludes/index.js +120 -0
- package/dist/vendor/devkit/preludes/origin-heal.js +142 -0
- package/dist/vendor/devkit/service-account.js +43 -0
- package/dist/vendor/devkit/ship-plan.js +112 -0
- package/dist/vendor/devkit/signing-env.js +180 -0
- package/dist/vendor/devkit/signing-tree.js +92 -0
- package/dist/vendor/devkit/target-seams.js +142 -0
- package/dist/vendor/devkit/target-secrets.js +231 -57
- package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
- package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
- package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
- package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
- package/dist/vendor/monitoring/env.js +20 -10
- package/dist/vendor/monitoring/main.js +1 -1
- package/dist/vendor/monitoring/preload.js +1 -1
- package/dist/vendor/monitoring/renderer.js +1 -1
- package/docs/analytics.md +1 -1
- package/docs/auto-updater.md +5 -5
- package/docs/boot-sequence.md +1 -1
- package/docs/build-system.md +15 -7
- package/docs/client-bridge.md +9 -7
- package/docs/config-schema.md +4 -4
- package/docs/css.md +8 -2
- package/docs/deep-link.md +12 -4
- package/docs/environment-detection.md +32 -24
- package/docs/hooks.md +3 -1
- package/docs/icons.md +7 -7
- package/docs/index.md +61 -23
- package/docs/installer-options.md +24 -21
- package/docs/logging.md +5 -5
- package/docs/releasing.md +29 -19
- package/docs/runner.md +51 -14
- package/docs/shared/brands.md +12 -6
- package/docs/shared/breaking-changes.md +375 -21
- package/docs/shared/config.md +764 -199
- package/docs/shared/deploys.md +217 -91
- package/docs/shared/icons.md +18 -0
- package/docs/shared/local-dev.md +28 -8
- package/docs/shared/logging.md +9 -6
- package/docs/shared/monitoring.md +27 -13
- package/docs/shared/publishing.md +3 -3
- package/docs/shared/rulings.md +2 -2
- package/docs/shared/testing.md +1 -1
- package/docs/shared/theming.md +26 -1
- package/docs/shared/translation.md +49 -7
- package/docs/shared/updates.md +1 -1
- package/docs/signing.md +60 -34
- package/docs/test-framework.md +10 -5
- package/docs/themes.md +15 -1
- package/package.json +18 -12
- package/bin/omega-desktop +0 -2
- package/dist/commands/push-secrets.js +0 -141
- package/dist/test/suites/build/deliver-certs.test.js +0 -95
- package/dist/test/suites/build/derive-signing-env.test.js +0 -122
- package/dist/test/suites/build/push-secrets.test.js +0 -226
- package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
- package/dist/utils/deliver-certs.js +0 -69
- package/dist/utils/derive-signing-env.js +0 -56
- package/dist/utils/resolve-signing-cert.js +0 -175
- package/dist/vendor/config/desktop-artifacts.js +0 -110
- package/dist/vendor/config/instances.js +0 -208
- /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
- /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
- /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev-facts.js, the ONE missing-dev-fact error
|
|
3
|
+
* ([#834](https://github.com/Omega-JS-Stack/omega/issues/834)).
|
|
4
|
+
*
|
|
5
|
+
* The classic port numbers are DEFINED once, in `CLASSIC_PORTS` beside this
|
|
6
|
+
* file, and the allocator resolves a live map from them at boot (N7). Browser
|
|
7
|
+
* code can read neither env nor files, so every surface BAKES that resolved map
|
|
8
|
+
* into its build json under one key, `OMEGA_BUILD_JSON.config.dev`
|
|
9
|
+
* ([#300](https://github.com/Omega-JS-Stack/omega/issues/300),
|
|
10
|
+
* [#262](https://github.com/Omega-JS-Stack/omega/issues/262)).
|
|
11
|
+
*
|
|
12
|
+
* What this file exists to stop is the OTHER half of that arrangement: the same
|
|
13
|
+
* numbers hand-typed a second time as a browser-side fallback for "a build made
|
|
14
|
+
* with no stack up". @omega.js/client carried four of them plus a classic dev
|
|
15
|
+
* origin, @omega.js/desktop's url-helpers and client-bridge carried three more,
|
|
16
|
+
* and @omega.js/extension's url-helpers and background worker two. Every one of
|
|
17
|
+
* them was a guess about a port nothing identity-checks, so a neighbouring
|
|
18
|
+
* project's emulator holding 9099 read as an auth mystery for hours rather than
|
|
19
|
+
* as a port problem, and a wrong API base read as a silent connection refusal.
|
|
20
|
+
*
|
|
21
|
+
* So there is no fallback anywhere any more. A real build always carries the
|
|
22
|
+
* map; a read that finds none throws THIS error, which names the fact it wanted
|
|
23
|
+
* and the build step that writes it.
|
|
24
|
+
*
|
|
25
|
+
* This module requires NOTHING: it is bundled into browser artifacts (the
|
|
26
|
+
* desktop renderer, every extension bundle) and vendored into @omega.js/client.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// The ONE key every surface bakes the resolved map under, spelled once.
|
|
30
|
+
const DEV_FACT_CHANNEL = 'OMEGA_BUILD_JSON.config.dev';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The error a surface throws when a dev fact it needs was never baked.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} what - the fact, named the way the caller wanted it
|
|
36
|
+
* ("dev port for `auth`", "dev website origin").
|
|
37
|
+
* @param {string} writer - the build step that writes the map on THIS surface,
|
|
38
|
+
* so the message says where to go and not just what is absent.
|
|
39
|
+
* @returns {Error} the error to throw.
|
|
40
|
+
*/
|
|
41
|
+
function devFactMissing(what, writer) {
|
|
42
|
+
return new Error(`No resolved ${what} in ${DEV_FACT_CHANNEL}. It is written by ${writer}. A build made without it is not a build, so nothing here assumes a classic port.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
DEV_FACT_CHANNEL,
|
|
47
|
+
devFactMissing,
|
|
48
|
+
};
|
|
@@ -21,14 +21,31 @@
|
|
|
21
21
|
* ([#454](https://github.com/Omega-JS-Stack/omega/issues/454)), so they
|
|
22
22
|
* drop out of every rendered and published set.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* The schema NAMES most of what travels, and two kinds of key it cannot name
|
|
25
|
+
* ride the same pipeline through the target's COMPOSED production values
|
|
26
|
+
* (`values`, from composeTargetEnv), which every list here takes as an option:
|
|
26
27
|
*
|
|
27
|
-
*
|
|
28
|
+
* - a PATTERN family member (`match`, no `name`): the schema knows
|
|
29
|
+
* `CONNECTIONS_<PROVIDER>_CLIENT_ID` as a shape, so only a composed set can
|
|
30
|
+
* say which providers this brand actually configured
|
|
31
|
+
* ([#876](https://github.com/Omega-JS-Stack/omega/issues/876)).
|
|
32
|
+
* - a CUSTOM key the schema does not know at all: a consumer's own line in
|
|
33
|
+
* the brand `.env`, which used to reach no runner and fail the consumer's
|
|
34
|
+
* own workflow step silently
|
|
35
|
+
* ([#835](https://github.com/Omega-JS-Stack/omega/issues/835)). It travels
|
|
36
|
+
* in the target's FILE mode: written into the `.env` the runner builds on
|
|
37
|
+
* the backend, the runner env alone everywhere else.
|
|
38
|
+
*
|
|
39
|
+
* Both halves come out of ONE primitive (`deliveredKeys`), so what a workflow
|
|
40
|
+
* injects, what push-secrets publishes, and what the backend's `.env` writer
|
|
41
|
+
* names can never disagree inside a run. No `values` = the schema half alone,
|
|
42
|
+
* which is what a lane with no brand to compose from gets.
|
|
43
|
+
*
|
|
44
|
+
* Every function takes an optional `schema` so a caller (the tests) can
|
|
28
45
|
* exercise the rules against a fixture; the default is the real one.
|
|
29
46
|
*/
|
|
30
47
|
|
|
31
|
-
const { ENV_SCHEMA } = require('./env-schema.js');
|
|
48
|
+
const { ENV_SCHEMA, envSchemaEntry } = require('./env-schema.js');
|
|
32
49
|
|
|
33
50
|
// Keys a generated workflow's own `env:` block already declares. The generated
|
|
34
51
|
// block never restates one (a repeated YAML mapping key is invalid) — they are
|
|
@@ -39,6 +56,39 @@ const WORKFLOW_OWNED_KEYS = ['GH_TOKEN', 'CLOUDFLARE_TOKEN', 'NODE_VERSION', 'NO
|
|
|
39
56
|
// generated region is always a valid, self-explaining line of YAML.
|
|
40
57
|
const EMPTY_BLOCK = '# (no CI-delivered keys for this target — the next omega verb regenerates this block)';
|
|
41
58
|
|
|
59
|
+
// What a target's generated workflow carries in the runner env, per target.
|
|
60
|
+
//
|
|
61
|
+
// The default is the CI half: `ci` plus `bake` (a bake is injected before it is
|
|
62
|
+
// baked), because those artifacts carry their values inside themselves and
|
|
63
|
+
// never read an env file.
|
|
64
|
+
//
|
|
65
|
+
// The BACKEND is the one target whose deployed artifact ships a composed `.env`
|
|
66
|
+
// and whose deploy now runs on a runner
|
|
67
|
+
// ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)): the runner has
|
|
68
|
+
// no brand checkout to compose that file from, so its workflow WRITES the .env
|
|
69
|
+
// from the runner env, which puts every `env` delivery on the runner too.
|
|
70
|
+
const WORKFLOW_MODES = {
|
|
71
|
+
backend: ['env', 'ci', 'bake'],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const DEFAULT_WORKFLOW_MODES = ['ci', 'bake'];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The mode a key the schema does not declare travels in, per target: the
|
|
78
|
+
* target's own FILE mode where it has one (backend, whose workflow writes the
|
|
79
|
+
* `.env` its upload ships with), the runner env everywhere else (#835).
|
|
80
|
+
*
|
|
81
|
+
* A custom key declares no delivery, so it takes the channel its target
|
|
82
|
+
* already uses for the keys it composes. A packaged app carries no `.env`, so
|
|
83
|
+
* there is nothing on web, desktop or the extension for a file mode to mean.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} target - Target name.
|
|
86
|
+
* @returns {string} `'env'` or `'ci'`.
|
|
87
|
+
*/
|
|
88
|
+
function customDeliveryMode(target) {
|
|
89
|
+
return (WORKFLOW_MODES[target] || DEFAULT_WORKFLOW_MODES).includes('env') ? 'env' : 'ci';
|
|
90
|
+
}
|
|
91
|
+
|
|
42
92
|
/**
|
|
43
93
|
* The delivered names a target receives in the given modes, sorted and
|
|
44
94
|
* deduplicated.
|
|
@@ -74,6 +124,79 @@ function deliveredNames(target, modes, schema) {
|
|
|
74
124
|
return [...names].sort();
|
|
75
125
|
}
|
|
76
126
|
|
|
127
|
+
// GitHub's own rule for a secret name: letters, digits and underscores, never
|
|
128
|
+
// leading with a digit. A composed key that breaks it is refused by name.
|
|
129
|
+
const SECRET_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The ONE delivered key set (#835, #876): the schema's named deliveries for
|
|
133
|
+
* this target in these modes, plus what only the target's COMPOSED production
|
|
134
|
+
* values can name.
|
|
135
|
+
*
|
|
136
|
+
* Every list in this file derives from here, so the workflow block, the
|
|
137
|
+
* published secrets and the backend's `.env` key list are three renderings of
|
|
138
|
+
* one set rather than three sets that have to agree.
|
|
139
|
+
*
|
|
140
|
+
* A composed key is added when it is a `match` family member this target
|
|
141
|
+
* delivers in these modes, or when the schema does not know it at all and the
|
|
142
|
+
* target's custom mode is one of them. It is NOT added when the schema names
|
|
143
|
+
* it: the walk above already ruled on every fixed name, so a `machineLocal`
|
|
144
|
+
* path and a key whose only delivery here is the local `.env` stay home
|
|
145
|
+
* exactly as they did before a composed set existed. A WORKFLOW_OWNED name is
|
|
146
|
+
* never taken from a composed set either: those belong to the template, and
|
|
147
|
+
* neither is a `GITHUB_`-prefixed name, which GitHub refuses as a secret. A
|
|
148
|
+
* composed name GitHub could not hold as a secret at all throws, naming the key
|
|
149
|
+
* and the rule.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} target - Target name ('web', 'backend', …).
|
|
152
|
+
* @param {string[]} modes - The delivery modes to collect.
|
|
153
|
+
* @param {object} [options]
|
|
154
|
+
* @param {object[]} [options.schema] - Env schema entries (default: the real schema).
|
|
155
|
+
* @param {Object<string, string>} [options.values] - The target's composed
|
|
156
|
+
* production values (names are read, values never are).
|
|
157
|
+
* @returns {string[]} Delivered env var names, sorted.
|
|
158
|
+
*/
|
|
159
|
+
function deliveredKeys(target, modes, { schema = ENV_SCHEMA, values = null } = {}) {
|
|
160
|
+
const names = new Set(deliveredNames(target, modes, schema));
|
|
161
|
+
if (!values) return [...names].sort();
|
|
162
|
+
|
|
163
|
+
const customMode = customDeliveryMode(target);
|
|
164
|
+
|
|
165
|
+
for (const key of Object.keys(values)) {
|
|
166
|
+
if (names.has(key)) continue;
|
|
167
|
+
|
|
168
|
+
const entry = envSchemaEntry(key, schema);
|
|
169
|
+
|
|
170
|
+
if (entry) {
|
|
171
|
+
// A fixed name is the walk above's business, whatever it decided.
|
|
172
|
+
if (entry.name) continue;
|
|
173
|
+
if (entry.machineLocal) continue;
|
|
174
|
+
if (!modes.includes(entry.delivery && entry.delivery[target])) continue;
|
|
175
|
+
names.add(key);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!modes.includes(customMode)) continue;
|
|
180
|
+
if (WORKFLOW_OWNED_KEYS.includes(key)) continue;
|
|
181
|
+
// GitHub REFUSES an Actions secret whose name starts with GITHUB_ (the
|
|
182
|
+
// prefix is the runner's own), so publishing one would fail the deploy
|
|
183
|
+
// precheck outright. A brand .env carrying GITHUB_TOKEN is a plausible
|
|
184
|
+
// typo for GH_TOKEN, and the workflow reads its own `secrets.GITHUB_TOKEN`
|
|
185
|
+
// either way.
|
|
186
|
+
if (key.startsWith('GITHUB_')) continue;
|
|
187
|
+
// dotenv reads `[\w.-]+` as a key, so a brand `.env` can carry a name
|
|
188
|
+
// GitHub's secret API refuses outright (`ACME.WEBHOOK`). Delivering it
|
|
189
|
+
// would render a workflow line nothing can resolve and fail the publish
|
|
190
|
+
// three layers from the typo, so it fails HERE, by name.
|
|
191
|
+
if (!SECRET_NAME.test(key)) {
|
|
192
|
+
throw new Error(`${key}: a delivered env key must be a GitHub secret name (${SECRET_NAME.source.replace(/^\^|\$$/g, '')}), so rename it in the target's .env`);
|
|
193
|
+
}
|
|
194
|
+
names.add(key);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return [...names].sort();
|
|
198
|
+
}
|
|
199
|
+
|
|
77
200
|
/**
|
|
78
201
|
* The keys a target's generated workflow needs in the runner env: every `ci`
|
|
79
202
|
* delivery, plus every `bake` (a bake is injected before it is baked).
|
|
@@ -83,8 +206,60 @@ function deliveredNames(target, modes, schema) {
|
|
|
83
206
|
* @param {object[]} [options.schema] - Env schema entries (default: the real schema).
|
|
84
207
|
* @returns {string[]} Delivered env var names, sorted.
|
|
85
208
|
*/
|
|
86
|
-
function workflowSecretKeys(target, { schema = ENV_SCHEMA } = {}) {
|
|
87
|
-
return
|
|
209
|
+
function workflowSecretKeys(target, { schema = ENV_SCHEMA, values = null } = {}) {
|
|
210
|
+
return deliveredKeys(target, WORKFLOW_MODES[target] || DEFAULT_WORKFLOW_MODES, { schema, values });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The keys a target's RUNTIME reads from the `.env` its artifact ships with:
|
|
215
|
+
* every `env` delivery, and ONLY those. Backend is the only target with one
|
|
216
|
+
* today, and the one caller is its generated workflow, which writes that file
|
|
217
|
+
* on the runner from the secrets the block above injects (#872).
|
|
218
|
+
*
|
|
219
|
+
* A `ci` key is deliberately not here: it reaches the deploy PROCESS through
|
|
220
|
+
* the runner env (the deploy credential, the license key the verdict is checked
|
|
221
|
+
* with) and the shipped artifact must never carry it. The mode list below is
|
|
222
|
+
* the whole of that exclusion, and `artifactEnvValues` applies the same rule to
|
|
223
|
+
* the other writer of a backend `.env`, the stage's composer.
|
|
224
|
+
*
|
|
225
|
+
* @param {string} target - Target name.
|
|
226
|
+
* @param {object} [options]
|
|
227
|
+
* @param {object[]} [options.schema] - Env schema entries.
|
|
228
|
+
* @returns {string[]} Delivered env var names, sorted.
|
|
229
|
+
*/
|
|
230
|
+
function envFileKeys(target, { schema = ENV_SCHEMA, values = null } = {}) {
|
|
231
|
+
return deliveredKeys(target, ['env'], { schema, values });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Composed env values, narrowed to what a target's shipped artifact may carry:
|
|
236
|
+
* everything the composer resolved, minus every key the schema delivers to this
|
|
237
|
+
* target as `ci` ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)).
|
|
238
|
+
*
|
|
239
|
+
* The two sets differ on purpose. `composeTargetEnv` (env.js) resolves a value
|
|
240
|
+
* for EVERY key a target claims, because the secrets publisher needs the `ci`
|
|
241
|
+
* values too: a repo secret the workflow injects has to be valued from the same
|
|
242
|
+
* cascade as everything else. The `.env` written INTO an artifact is the
|
|
243
|
+
* narrower half, and this is the filter that says so, from the same declaration
|
|
244
|
+
* `envFileKeys` reads. A pattern family and a target-layer key have no `ci`
|
|
245
|
+
* delivery to match, so they pass through untouched.
|
|
246
|
+
*
|
|
247
|
+
* @param {string} target - Target name.
|
|
248
|
+
* @param {Object<string, string>} values - The composed values.
|
|
249
|
+
* @param {object} [options]
|
|
250
|
+
* @param {object[]} [options.schema] - Env schema entries.
|
|
251
|
+
* @returns {Object<string, string>} A new map, runner-only keys removed.
|
|
252
|
+
*/
|
|
253
|
+
function artifactEnvValues(target, values, { schema = ENV_SCHEMA } = {}) {
|
|
254
|
+
const runnerOnly = new Set(deliveredKeys(target, ['ci'], { schema }));
|
|
255
|
+
const shipped = {};
|
|
256
|
+
|
|
257
|
+
for (const [key, value] of Object.entries(values)) {
|
|
258
|
+
if (runnerOnly.has(key)) continue;
|
|
259
|
+
shipped[key] = value;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return shipped;
|
|
88
263
|
}
|
|
89
264
|
|
|
90
265
|
/**
|
|
@@ -97,7 +272,27 @@ function workflowSecretKeys(target, { schema = ENV_SCHEMA } = {}) {
|
|
|
97
272
|
* @returns {string[]} Delivered env var names, sorted.
|
|
98
273
|
*/
|
|
99
274
|
function bakeKeys(target, { schema = ENV_SCHEMA } = {}) {
|
|
100
|
-
return
|
|
275
|
+
return deliveredKeys(target, ['bake'], { schema });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The SOURCE names of a target's baked keys: the brand-level name a human sets
|
|
280
|
+
* (`GOOGLE_ANALYTICS_SECRET_DESKTOP`), not the `deliverAs` name the build reads
|
|
281
|
+
* it under. The bake GUARD names keys at the level a human can fix them, and it
|
|
282
|
+
* must not judge a target on keys it never bakes
|
|
283
|
+
* ([#891](https://github.com/Omega-JS-Stack/omega/issues/891): the desktop's
|
|
284
|
+
* other rules are CI-delivered signing credentials, which the deploy lane owns).
|
|
285
|
+
*
|
|
286
|
+
* @param {string} target - Target name.
|
|
287
|
+
* @param {object} [options]
|
|
288
|
+
* @param {object[]} [options.schema] - Env schema entries.
|
|
289
|
+
* @returns {string[]} Brand-level env var names, sorted.
|
|
290
|
+
*/
|
|
291
|
+
function bakeSourceKeys(target, { schema = ENV_SCHEMA } = {}) {
|
|
292
|
+
return schema
|
|
293
|
+
.filter((entry) => entry.name && !entry.machineLocal && entry.delivery && entry.delivery[target] === 'bake')
|
|
294
|
+
.map((entry) => entry.name)
|
|
295
|
+
.sort();
|
|
101
296
|
}
|
|
102
297
|
|
|
103
298
|
/**
|
|
@@ -128,18 +323,45 @@ function publishSecretKeys(target, options) {
|
|
|
128
323
|
* @param {string} [options.indent] - The token's indent (default two spaces).
|
|
129
324
|
* @returns {string} The block body (no leading indent).
|
|
130
325
|
*/
|
|
131
|
-
function renderSecretsBlock(target, { schema = ENV_SCHEMA, indent = ' ' } = {}) {
|
|
132
|
-
const lines = workflowSecretKeys(target, { schema })
|
|
326
|
+
function renderSecretsBlock(target, { schema = ENV_SCHEMA, indent = ' ', values = null } = {}) {
|
|
327
|
+
const lines = workflowSecretKeys(target, { schema, values })
|
|
133
328
|
.filter((key) => !WORKFLOW_OWNED_KEYS.includes(key))
|
|
134
329
|
.map((key) => `${key}: \${{ secrets.${key} }}`);
|
|
135
330
|
|
|
136
331
|
return lines.length ? lines.join(`\n${indent}`) : EMPTY_BLOCK;
|
|
137
332
|
}
|
|
138
333
|
|
|
334
|
+
/**
|
|
335
|
+
* The KEY NAMES a workflow writes its target's `.env` from, as a JSON array
|
|
336
|
+
* ready to drop into the template's writer step (#872).
|
|
337
|
+
*
|
|
338
|
+
* NAMES only, never `KEY="$KEY"` lines: the writer is a node one-liner that
|
|
339
|
+
* reads each name out of the runner env and serializes the file through
|
|
340
|
+
* `serializeEnv` (env.js), the documented SSOT for every `.env` writeback. The
|
|
341
|
+
* step this replaced pasted values into a shell heredoc, where a value carrying
|
|
342
|
+
* a NEWLINE split its own line and everything after it parsed as a key of its
|
|
343
|
+
* own (verified: a `K="$K"` heredoc with a multi-line value yields the injected
|
|
344
|
+
* key), and a quote or a backslash left the file malformed. No shell ever sees
|
|
345
|
+
* a value now.
|
|
346
|
+
*
|
|
347
|
+
* @param {string} target - Target name.
|
|
348
|
+
* @param {object} [options]
|
|
349
|
+
* @param {object[]} [options.schema] - Env schema entries.
|
|
350
|
+
* @returns {string} A JSON array of names, sorted (`[]` when the target ships no .env).
|
|
351
|
+
*/
|
|
352
|
+
function renderEnvFileKeys(target, { schema = ENV_SCHEMA, values = null } = {}) {
|
|
353
|
+
return JSON.stringify(envFileKeys(target, { schema, values }));
|
|
354
|
+
}
|
|
355
|
+
|
|
139
356
|
module.exports = {
|
|
140
357
|
WORKFLOW_OWNED_KEYS,
|
|
358
|
+
deliveredKeys,
|
|
141
359
|
workflowSecretKeys,
|
|
360
|
+
envFileKeys,
|
|
361
|
+
artifactEnvValues,
|
|
142
362
|
bakeKeys,
|
|
363
|
+
bakeSourceKeys,
|
|
143
364
|
publishSecretKeys,
|
|
144
365
|
renderSecretsBlock,
|
|
366
|
+
renderEnvFileKeys,
|
|
145
367
|
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retired ENV keys ([#893](https://github.com/Omega-JS-Stack/omega/issues/893))
|
|
3
|
+
* - the `.env` half of retired-keys.js.
|
|
4
|
+
*
|
|
5
|
+
* Six values were declared in both homes at once: the env schema carried them
|
|
6
|
+
* as non-secret keys while the config schema already declared (or now
|
|
7
|
+
* declares) the same fact. Two homes for one value drift, and the rule that
|
|
8
|
+
* settles which one wins is in docs/shared/config.md ("Config or env?"):
|
|
9
|
+
* config holds what is PUBLIC by design (an id a store URL carries, a key a
|
|
10
|
+
* browser bundle ships, a value printed in a binary), `.env` holds secrets,
|
|
11
|
+
* the login coordinates that only travel with a secret, and machine paths.
|
|
12
|
+
*
|
|
13
|
+
* There is no dual-read anywhere in OMEGA, so a `.env` line left behind is a
|
|
14
|
+
* value nothing reads: the id a brand carefully pasted would be ignored and
|
|
15
|
+
* the publish would address the wrong listing, or none. Every layer read
|
|
16
|
+
* therefore FAILS on one, naming the config path the value moved to
|
|
17
|
+
* (env.js's parseEnvFile, the one place a `.env` layer is parsed).
|
|
18
|
+
*
|
|
19
|
+
* The config-side register gets nothing from this move: these were never
|
|
20
|
+
* config keys, so nothing there was renamed.
|
|
21
|
+
*
|
|
22
|
+
* The register also carries the env-side RENAMES
|
|
23
|
+
* ([#845](https://github.com/Omega-JS-Stack/omega/issues/845)): a key that is
|
|
24
|
+
* still a secret and still lives in `.env`, under a new name. Same silence,
|
|
25
|
+
* same refusal; the fix it names is renaming the line, not deleting it.
|
|
26
|
+
*
|
|
27
|
+
* And it carries the keys that are retired OUTRIGHT
|
|
28
|
+
* ([#819](https://github.com/Omega-JS-Stack/omega/issues/819)): no config path,
|
|
29
|
+
* no new env name, because what replaced the key is a MECHANISM rather than a
|
|
30
|
+
* value. Such a row declares `replacement: null` and its refusal reads as a
|
|
31
|
+
* deletion with nowhere to move the value to.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// env var name -> { replacement, why, home } (docs/shared/config.md carries the
|
|
35
|
+
// rows). `home` is where the replacement LIVES: 'config' by default (the #893
|
|
36
|
+
// move), or 'env' for a key that stayed a secret and only changed NAME, which
|
|
37
|
+
// is a rename of the line rather than a deletion. `replacement: null` is a key
|
|
38
|
+
// with no successor of any kind (#819): the line is simply deleted, and the
|
|
39
|
+
// row's `why` names the mechanism that made it unnecessary.
|
|
40
|
+
const RETIRED_ENV_KEYS = {
|
|
41
|
+
CHROME_EXTENSION_ID: {
|
|
42
|
+
replacement: 'targets.<name>.listings.chrome.id',
|
|
43
|
+
why: 'a store item id is public by design (it is in the listing URL), so it lives beside that listing in config',
|
|
44
|
+
},
|
|
45
|
+
FIREFOX_EXTENSION_ID: {
|
|
46
|
+
replacement: 'targets.<name>.listings.firefox.id',
|
|
47
|
+
why: "the AMO add-on id IS the manifest's gecko id, and config is its ONE home: the local scaffold pins the derived id there and the package task writes it into the manifest",
|
|
48
|
+
},
|
|
49
|
+
EDGE_PRODUCT_ID: {
|
|
50
|
+
replacement: 'targets.<name>.listings.edge.id',
|
|
51
|
+
why: 'a store product id is public by design (it is in the listing URL), so it lives beside that listing in config',
|
|
52
|
+
},
|
|
53
|
+
RECAPTCHA_SITE_KEY: {
|
|
54
|
+
replacement: 'captcha.providers.recaptcha.siteKey',
|
|
55
|
+
why: 'the site key is rendered into every page that carries a form, so it is public by definition; RECAPTCHA_SECRET_KEY stays in .env',
|
|
56
|
+
},
|
|
57
|
+
PAYPAL_CLIENT_ID: {
|
|
58
|
+
replacement: 'payment.providers.paypal.clientId',
|
|
59
|
+
why: 'the public half of the PayPal pair, already declared in config; the backend read it through a boot bridge that copied config into env',
|
|
60
|
+
},
|
|
61
|
+
CHARGEBEE_SITE: {
|
|
62
|
+
replacement: 'payment.providers.chargebee.site',
|
|
63
|
+
why: 'the site name is in every Chargebee URL, already declared in config; the backend read it through a boot bridge that copied config into env',
|
|
64
|
+
},
|
|
65
|
+
// The connections pair ([#845](https://github.com/Omega-JS-Stack/omega/issues/845)):
|
|
66
|
+
// these are SECRETS and stayed in `.env`, so the row is a rename, not a move.
|
|
67
|
+
// Exact names only, one per provider: the register is a name lookup, so the
|
|
68
|
+
// `OAUTH2_*` family is spelled out a row at a time as a brand needs it, and
|
|
69
|
+
// Google is the pair every brand carries today.
|
|
70
|
+
OAUTH2_GOOGLE_CLIENT_ID: {
|
|
71
|
+
home: 'env',
|
|
72
|
+
replacement: 'CONNECTIONS_GOOGLE_CLIENT_ID',
|
|
73
|
+
why: '#788 renamed the oauth2 feature to connections, the env family included; the backend reads only CONNECTIONS_<PROVIDER>_CLIENT_ID now',
|
|
74
|
+
},
|
|
75
|
+
OAUTH2_GOOGLE_CLIENT_SECRET: {
|
|
76
|
+
home: 'env',
|
|
77
|
+
replacement: 'CONNECTIONS_GOOGLE_CLIENT_SECRET',
|
|
78
|
+
why: '#788 renamed the oauth2 feature to connections, the env family included; the backend reads only CONNECTIONS_<PROVIDER>_CLIENT_SECRET now',
|
|
79
|
+
},
|
|
80
|
+
// The test-lane pair ([#819](https://github.com/Omega-JS-Stack/omega/issues/819),
|
|
81
|
+
// Ian 2026-09-13): retired outright, so neither row names a replacement. A
|
|
82
|
+
// suite no longer holds a credential of its own, which is why there is
|
|
83
|
+
// nothing to move the value to.
|
|
84
|
+
OMEGA_TEST_FIREBASE_ADMIN_KEY: {
|
|
85
|
+
replacement: null,
|
|
86
|
+
why: 'retired: desktop, extension and web test their own sign-in through a seeded persona from the backend emulator (#904), so no suite mints a custom token from a service account any more',
|
|
87
|
+
},
|
|
88
|
+
OMEGA_TEST_USER_UID: {
|
|
89
|
+
replacement: null,
|
|
90
|
+
why: 'retired: desktop, extension and web test their own sign-in through a seeded persona from the backend emulator (#904), and the roster names the persona, so no uid is configured anywhere',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The retired keys a parsed `.env` layer carries, in the layer's own order.
|
|
96
|
+
*
|
|
97
|
+
* Presence is the whole test: an empty value (`KEY=`) documents a key nothing
|
|
98
|
+
* reads, so it is owed the same deletion as a valued one.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object<string, string>} values - Parsed `.env` values.
|
|
101
|
+
* @returns {Array<{ key: string, replacement: string|null, why: string }>}
|
|
102
|
+
*/
|
|
103
|
+
function findRetiredEnvKeys(values) {
|
|
104
|
+
return Object.keys(values || {})
|
|
105
|
+
.filter((key) => RETIRED_ENV_KEYS[key])
|
|
106
|
+
.map((key) => ({ key, ...RETIRED_ENV_KEYS[key] }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Refuse a `.env` layer that still carries a retired key, spelling the move
|
|
111
|
+
* out. A row with no replacement says so plainly: the line is deleted and
|
|
112
|
+
* nothing takes the value anywhere (#819).
|
|
113
|
+
*
|
|
114
|
+
* @param {Object<string, string>} values - Parsed `.env` values.
|
|
115
|
+
* @param {string} envPath - The file the values came from (named in the error).
|
|
116
|
+
* @throws {Error} Naming every retired key in the layer, its new home, and why.
|
|
117
|
+
*/
|
|
118
|
+
function assertNoRetiredEnvKeys(values, envPath) {
|
|
119
|
+
const found = findRetiredEnvKeys(values);
|
|
120
|
+
if (found.length === 0) return;
|
|
121
|
+
|
|
122
|
+
const moves = found.map(({ key, replacement, why, home }) => {
|
|
123
|
+
if (!replacement) return ` ${key} is retired outright: delete the .env line, nothing replaces the value (${why})`;
|
|
124
|
+
|
|
125
|
+
return home === 'env'
|
|
126
|
+
? ` ${key} renamed to ${replacement}; rename the .env line (${why})`
|
|
127
|
+
: ` ${key} moved to ${replacement} in config/omega.json5; delete the .env line (${why})`;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
throw new Error(
|
|
131
|
+
`${envPath} carries ${found.length} retired env key(s). Nothing reads them, so the value is silently lost:\n`
|
|
132
|
+
+ `${moves.join('\n')}\n`
|
|
133
|
+
+ 'The rule, and the whole register: docs/shared/config.md ("Config or env?").',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { RETIRED_ENV_KEYS, findRetiredEnvKeys, assertNoRetiredEnvKeys };
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
* PRESENCE ONLY, never a value shape (Ian 2026-08-26): a rule fires when the
|
|
27
27
|
* key is empty, never because a value "looks wrong". `requiredWhen` is
|
|
28
28
|
* one-directional — a truthy config path makes the key mandatory; an empty key
|
|
29
|
-
* never says anything about the config.
|
|
29
|
+
* never says anything about the config. A rule may PIN the path to one value
|
|
30
|
+
* (`platforms.windows.signing.strategy=self-hosted`,
|
|
31
|
+
* [#891](https://github.com/Omega-JS-Stack/omega/issues/891)), because an
|
|
32
|
+
* enum's value is what decides which credentials exist: the EV token belongs to
|
|
33
|
+
* the self-hosted strategy, never to the cloud one.
|
|
30
34
|
*
|
|
31
35
|
* The checker NEVER throws and never logs. It returns the violations and the
|
|
32
36
|
* caller decides the severity (build mode and production boot fail loudly,
|
|
@@ -53,6 +57,21 @@ function hasValue(env, entry) {
|
|
|
53
57
|
return Boolean(entry.deliverAs && env[entry.deliverAs]);
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Whether a `requiredWhen` expression holds for a config: a truthy path, or an
|
|
62
|
+
* exact match when the expression pins one (`<path>=<value>`).
|
|
63
|
+
*
|
|
64
|
+
* @param {object} config - The resolved omega.json5 config.
|
|
65
|
+
* @param {string} expression - The schema entry's `requiredWhen`.
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
function requiredWhenHolds(config, expression) {
|
|
69
|
+
const [path, expected] = String(expression).split('=');
|
|
70
|
+
const value = getPath(config, path);
|
|
71
|
+
|
|
72
|
+
return expected === undefined ? Boolean(value) : String(value) === expected;
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
/**
|
|
57
76
|
* Check a resolved config + env pair against the schema's presence rules.
|
|
58
77
|
*
|
|
@@ -82,7 +101,7 @@ function checkEnvRules(config, env, { target, schema = ENV_SCHEMA } = {}) {
|
|
|
82
101
|
|
|
83
102
|
if (entry.required) {
|
|
84
103
|
violations.push({ key: entry.name, rule: 'required', path: null });
|
|
85
|
-
} else if (
|
|
104
|
+
} else if (requiredWhenHolds(config, entry.requiredWhen)) {
|
|
86
105
|
violations.push({ key: entry.name, rule: 'requiredWhen', path: entry.requiredWhen });
|
|
87
106
|
}
|
|
88
107
|
}
|
|
@@ -90,4 +109,4 @@ function checkEnvRules(config, env, { target, schema = ENV_SCHEMA } = {}) {
|
|
|
90
109
|
return violations;
|
|
91
110
|
}
|
|
92
111
|
|
|
93
|
-
module.exports = { checkEnvRules };
|
|
112
|
+
module.exports = { checkEnvRules, requiredWhenHolds };
|