@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
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Brand-monorepo hierarchy: when projectDir is a target inside a brand
|
|
13
13
|
* monorepo ({brand}/targets/{target}), the brand root's config/omega.json5 is the
|
|
14
|
-
* brand layer under the target's file, and a brand
|
|
15
|
-
*
|
|
16
|
-
* Resolution for a target:
|
|
14
|
+
* brand layer under the target's file, and a brand naming a company
|
|
15
|
+
* (`company: { id }`) inherits that company's own config/omega.json5 underneath
|
|
16
|
+
* that (#677). Resolution for a target:
|
|
17
17
|
*
|
|
18
|
-
* schema defaults ← framework defaults ← company ← brand shared ← brand targets[
|
|
18
|
+
* schema defaults ← framework defaults ← company ← company.<environment> ← brand shared ← brand targets[name] ← brand.<environment> ← local shared ← local targets[name] ← local.<environment>
|
|
19
19
|
*
|
|
20
20
|
* The bottom layer derives from the schema's own `default:` entries
|
|
21
21
|
* ([#478](https://github.com/Omega-JS-Stack/omega/issues/478)) — the one home
|
|
@@ -23,21 +23,36 @@
|
|
|
23
23
|
* framework genuinely differs.
|
|
24
24
|
*
|
|
25
25
|
* The company file layers exactly like the brand file (shared, then its
|
|
26
|
-
* targets[
|
|
27
|
-
* nothing inside a brand.
|
|
26
|
+
* targets[name] entry) minus its `brands` key, which is company plumbing that
|
|
27
|
+
* means nothing inside a brand.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* Every layer is TWO files, not one
|
|
30
|
+
* ([#856](https://github.com/Omega-JS-Stack/omega/issues/856)): its omega.json5
|
|
31
|
+
* and the `omega.<environment>.json5` overlay beside it, which wins over its
|
|
32
|
+
* OWN base and still loses to the layer above (each file keeping the
|
|
33
|
+
* shared-then-targets[name] split). The environment is envEnvironment()'s
|
|
34
|
+
* answer, exactly the vocabulary the `.env.<environment>` overlays are suffixed
|
|
35
|
+
* with (#586), so ONE word names the config overlay, the env overlay and the
|
|
36
|
+
* runtime's own environment. Only the RUNNING environment's overlay composes,
|
|
37
|
+
* and a missing overlay is nothing. A file named for anything else
|
|
38
|
+
* (`omega.staging.json5`) is not a layer at all, so nothing reads it and there
|
|
39
|
+
* is nothing to warn about. An overlay holds only the OVERRIDES: the validator
|
|
40
|
+
* judges the merged result, never an overlay on its own.
|
|
41
|
+
*
|
|
42
|
+
* Targets are keyed by NAME (#886): the target dir IS the name
|
|
43
|
+
* (targets/community → the `community` entry), and that entry is the target
|
|
44
|
+
* layer of the chain. A standalone project has no such dir, so its name is the
|
|
45
|
+
* single target of that framework's type its own file declares (a deployed
|
|
46
|
+
* backend staged from `api: { type: 'backend' }` is still the `api` target).
|
|
47
|
+
* The caller passes the framework TYPE it runs, and a name declared with a
|
|
48
|
+
* different type fails loud (see targets.js).
|
|
34
49
|
*
|
|
35
50
|
* "shared" = the file minus its `targets` key. A target entry may override
|
|
36
51
|
* ANY shared key — same agnostic deep merge at every step (see merge.js), so
|
|
37
52
|
* a per-surface Sentry DSN or analytics id is just
|
|
38
|
-
* targets.<
|
|
53
|
+
* targets.<name>.monitoring.providers.sentry.dsn.
|
|
39
54
|
* Target-section keys land at the TOP LEVEL of the resolved config
|
|
40
|
-
* (targets.desktop.platforms resolves to config.platforms); the merged
|
|
55
|
+
* (targets.desktop.platforms resolves to config.platforms, for a target NAMED desktop); the merged
|
|
41
56
|
* `targets` map itself is kept on the result purely so enabled-target
|
|
42
57
|
* enumeration survives resolution — settings are never read from it.
|
|
43
58
|
*
|
|
@@ -52,12 +67,12 @@ const path = require('node:path');
|
|
|
52
67
|
const JSON5 = require('json5');
|
|
53
68
|
|
|
54
69
|
const { deepMerge, isPlainObject } = require('./merge.js');
|
|
55
|
-
const {
|
|
70
|
+
const { resolveCompany, recordBrand } = require('./company.js');
|
|
56
71
|
const { findSecretKeys } = require('./secrets.js');
|
|
57
72
|
const { validateConfig } = require('./validate.js');
|
|
58
73
|
const { TARGETS } = require('./schema.js');
|
|
59
74
|
const { schemaDefaults } = require('./defaults.js');
|
|
60
|
-
const {
|
|
75
|
+
const { targetNameFromDir, targetUrl } = require('./targets.js');
|
|
61
76
|
|
|
62
77
|
const FILE_NAME = 'omega.json5';
|
|
63
78
|
const CONFIG_LOCATIONS = [
|
|
@@ -82,6 +97,48 @@ function resolveConfigPath(projectDir) {
|
|
|
82
97
|
return hit || null;
|
|
83
98
|
}
|
|
84
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The environment an overlay chain composes for. A lane that KNOWS the
|
|
102
|
+
* environment its artifact is for (a production build, a deploy stage) names it
|
|
103
|
+
* ([#856](https://github.com/Omega-JS-Stack/omega/issues/856)), exactly the way
|
|
104
|
+
* composeTargetEnv() already takes one for the `.env.<environment>` overlay
|
|
105
|
+
* beside it (#586); anything else gets `fallback`. One vocabulary, so a name
|
|
106
|
+
* outside it is a caller defect and fails loud rather than resolving to no
|
|
107
|
+
* overlay at all.
|
|
108
|
+
* @param {string} [environment] - The environment the caller named, or undefined.
|
|
109
|
+
* @param {string|null} fallback - What an unnamed environment resolves to.
|
|
110
|
+
* @returns {string|null} The environment whose overlay composes, or null for none.
|
|
111
|
+
*/
|
|
112
|
+
function resolveOverlayEnvironment(environment, fallback) {
|
|
113
|
+
// lazy require: env.js depends on this module (findBrandRoot)
|
|
114
|
+
const { ENV_ENVIRONMENTS } = require('./env.js');
|
|
115
|
+
|
|
116
|
+
if (environment === undefined || environment === null) return fallback;
|
|
117
|
+
|
|
118
|
+
if (!ENV_ENVIRONMENTS.includes(environment)) {
|
|
119
|
+
throw new Error(`Unknown environment "${environment}": must be one of [${ENV_ENVIRONMENTS.join(', ')}]`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return environment;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* ONE layer's environment overlay (#856): the `omega.<environment>.json5`
|
|
127
|
+
* beside its own base file, the config mirror of the `.env` + `.env.<environment>`
|
|
128
|
+
* pair (#586). Only ONE environment is ever asked for, so an overlay named for
|
|
129
|
+
* any other word is not a layer and is never read (nothing to warn about: it is
|
|
130
|
+
* simply not part of the chain).
|
|
131
|
+
* @param {string|null} basePath - The layer's omega.json5 path, or null when the layer has none.
|
|
132
|
+
* @param {string|null} environment - The environment whose overlay composes (one of ENV_ENVIRONMENTS), or null for none.
|
|
133
|
+
* @returns {string|null} Absolute overlay path, or null when there is no overlay.
|
|
134
|
+
*/
|
|
135
|
+
function resolveOverlayPath(basePath, environment) {
|
|
136
|
+
if (!basePath || !environment) return null;
|
|
137
|
+
|
|
138
|
+
const overlay = path.join(path.dirname(basePath), `${path.basename(FILE_NAME, '.json5')}.${environment}.json5`);
|
|
139
|
+
return fs.existsSync(overlay) ? overlay : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
85
142
|
/**
|
|
86
143
|
* Cheap probe: does this project have an omega.json5 at all? Frameworks use it
|
|
87
144
|
* to fail soft in non-consumer dirs (seeded-empty config); tooling uses it as
|
|
@@ -156,32 +213,22 @@ function findBrandConfigPath(projectDir) {
|
|
|
156
213
|
}
|
|
157
214
|
|
|
158
215
|
/**
|
|
159
|
-
* The
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
216
|
+
* The BRAND ROOT every company question is asked from: a target resolves it
|
|
217
|
+
* through its brand, a brand root (or standalone project) answers for itself.
|
|
218
|
+
*
|
|
219
|
+
* Same normalization as findBrandRoot: the company is named by the brand's own
|
|
220
|
+
* config, never by anything inside the runtime cwd or the staged build output.
|
|
221
|
+
* Reading only `functions/` left a STANDALONE project resolving from dist/ (the
|
|
222
|
+
* view `omega test` loads) looking one level too low, so its company layer
|
|
223
|
+
* vanished ([#257](https://github.com/Omega-JS-Stack/omega/issues/257)).
|
|
166
224
|
* @param {string} projectDir - Target dir, brand root, or a TARGET_SUBDIR of one (functions/, dist/).
|
|
167
|
-
* @returns {string
|
|
225
|
+
* @returns {string} Absolute brand (or standalone-project) root.
|
|
168
226
|
*/
|
|
169
|
-
function
|
|
227
|
+
function companyHostRoot(projectDir) {
|
|
170
228
|
let dir = path.resolve(projectDir);
|
|
171
|
-
// Same normalization as findBrandRoot: a marker is stamped at the target/brand
|
|
172
|
-
// root, never inside the runtime cwd or the staged build output. Reading
|
|
173
|
-
// only `functions/` left a STANDALONE project resolving from dist/ (the view
|
|
174
|
-
// `omega test` loads) looking for the marker inside dist/, so its company
|
|
175
|
-
// layer vanished ([#257](https://github.com/Omega-JS-Stack/omega/issues/257)).
|
|
176
229
|
if (TARGET_SUBDIRS.includes(path.basename(dir))) dir = path.dirname(dir);
|
|
177
230
|
|
|
178
|
-
|
|
179
|
-
const companyRoot = readCompanyRoot(markerRoot);
|
|
180
|
-
|
|
181
|
-
// A root stamped at ITSELF would merge its own file in twice.
|
|
182
|
-
if (!companyRoot || path.resolve(companyRoot) === markerRoot) return null;
|
|
183
|
-
|
|
184
|
-
return resolveConfigPath(companyRoot);
|
|
231
|
+
return findBrandRoot(dir) || dir;
|
|
185
232
|
}
|
|
186
233
|
|
|
187
234
|
/**
|
|
@@ -255,6 +302,28 @@ function stripTargets(config) {
|
|
|
255
302
|
return shared;
|
|
256
303
|
}
|
|
257
304
|
|
|
305
|
+
// The `company` keys the LOADER fills (#677): authored, they would be
|
|
306
|
+
// overwritten at every load, so an author hears about it at the file.
|
|
307
|
+
const RESOLVED_COMPANY_KEYS = ['name', 'url', 'images'];
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The company keys nobody types, refused where a human writes them: the BRAND
|
|
311
|
+
* file and the COMPANY file (#677). The local layer is deliberately exempt,
|
|
312
|
+
* because a target's `config/omega.json5` may be a staged compose output, which
|
|
313
|
+
* carries the RESOLVED facts on purpose (the deployed runtime has no registry
|
|
314
|
+
* and no company tree to re-resolve them from).
|
|
315
|
+
* @param {string|null} file - The layer's path, for the message.
|
|
316
|
+
* @param {object|null} data - The parsed layer.
|
|
317
|
+
*/
|
|
318
|
+
function assertNoTypedCompany(file, data) {
|
|
319
|
+
if (!data || !isPlainObject(data.company)) return;
|
|
320
|
+
|
|
321
|
+
const typed = RESOLVED_COMPANY_KEYS.filter((key) => data.company[key] !== undefined);
|
|
322
|
+
if (typed.length) {
|
|
323
|
+
throw new Error(`${typed.map((key) => `company.${key}`).join(', ')} in ${file} is resolved from the company, delete it (#677): the brand types company: { id: '<parent brand.id>' } (or 'self') and the loader fills the rest`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
258
327
|
/**
|
|
259
328
|
* Raw-file hard fails, applied BEFORE any merge: secrets anywhere in the
|
|
260
329
|
* file (requested target or not) and the legacy targets ARRAY (it would
|
|
@@ -273,6 +342,83 @@ function assertUsableRawFile(file, data) {
|
|
|
273
342
|
}
|
|
274
343
|
}
|
|
275
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Each layer's environment overlay (#856), read beside its own base file, plus
|
|
347
|
+
* the raw-file gate every one of the six files passes. ONE copy for the two
|
|
348
|
+
* readers of the chain (`loadConfig` and `composeTargetConfig`), which resolved
|
|
349
|
+
* the same three paths, read the same three files and ran the same ten asserts
|
|
350
|
+
* in the same order.
|
|
351
|
+
*
|
|
352
|
+
* An overlay is judged by the SAME hard fails as the base beside it: it is the
|
|
353
|
+
* same authored file, so a secret (or a legacy targets array) in one is the
|
|
354
|
+
* same defect, with the same message naming the overlay. That is why the bases
|
|
355
|
+
* come in here too: the gate is one block, not two halves that can drift.
|
|
356
|
+
*
|
|
357
|
+
* @param {object} options - Options.
|
|
358
|
+
* @param {string|null} options.companyPath - The company layer's base file.
|
|
359
|
+
* @param {object|null} options.company - The parsed company layer.
|
|
360
|
+
* @param {string|null} options.brandPath - The brand layer's base file.
|
|
361
|
+
* @param {object|null} options.brand - The parsed brand layer.
|
|
362
|
+
* @param {string|null} options.localPath - The local layer's base file.
|
|
363
|
+
* @param {object|null} options.local - The parsed local layer.
|
|
364
|
+
* @param {string|null} options.environment - The environment the overlays are
|
|
365
|
+
* FOR; null reads no overlay at all.
|
|
366
|
+
* @returns {{ companyOverlay: object|null, brandOverlay: object|null, localOverlay: object|null }}
|
|
367
|
+
* Each layer's overlay, or null where that layer has none.
|
|
368
|
+
*/
|
|
369
|
+
function resolveLayerOverlays({ companyPath, company, brandPath, brand, localPath, local, environment }) {
|
|
370
|
+
const companyOverlayPath = resolveOverlayPath(companyPath, environment);
|
|
371
|
+
const brandOverlayPath = resolveOverlayPath(brandPath, environment);
|
|
372
|
+
const localOverlayPath = resolveOverlayPath(localPath, environment);
|
|
373
|
+
|
|
374
|
+
const companyOverlay = companyOverlayPath ? readConfigFile(companyOverlayPath) : null;
|
|
375
|
+
const brandOverlay = brandOverlayPath ? readConfigFile(brandOverlayPath) : null;
|
|
376
|
+
const localOverlay = localOverlayPath ? readConfigFile(localOverlayPath) : null;
|
|
377
|
+
|
|
378
|
+
assertUsableRawFile(companyPath, company);
|
|
379
|
+
assertUsableRawFile(brandPath, brand);
|
|
380
|
+
assertUsableRawFile(localPath, local);
|
|
381
|
+
assertUsableRawFile(companyOverlayPath, companyOverlay);
|
|
382
|
+
assertUsableRawFile(brandOverlayPath, brandOverlay);
|
|
383
|
+
assertUsableRawFile(localOverlayPath, localOverlay);
|
|
384
|
+
assertNoTypedCompany(companyPath, company);
|
|
385
|
+
assertNoTypedCompany(brandPath, brand);
|
|
386
|
+
assertNoTypedCompany(companyOverlayPath, companyOverlay);
|
|
387
|
+
assertNoTypedCompany(brandOverlayPath, brandOverlay);
|
|
388
|
+
|
|
389
|
+
return { companyOverlay, brandOverlay, localOverlay };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* WHICH target a STANDALONE project is (no brand root above it, so no dir to
|
|
394
|
+
* read the name off): the single declared target of the framework's type, else
|
|
395
|
+
* the type word. A deployed backend staged from `api: { type: 'backend' }` is
|
|
396
|
+
* the `api` target, so its entry is the target layer, `enabled` is true and its
|
|
397
|
+
* url derives, exactly as it does inside the brand it was staged from. Two
|
|
398
|
+
* targets of one type have no single answer, so the type word stands.
|
|
399
|
+
* @param {object} targets - The merged targets map.
|
|
400
|
+
* @param {string} target - The framework type this project runs.
|
|
401
|
+
* @returns {string} The target name.
|
|
402
|
+
*/
|
|
403
|
+
function standaloneTargetName(targets, target) {
|
|
404
|
+
const matches = Object.keys(targets || {})
|
|
405
|
+
.filter((name) => isPlainObject(targets[name]) && targets[name].type === target);
|
|
406
|
+
|
|
407
|
+
return matches.length === 1 ? matches[0] : target;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* ONE layer's contribution to a target's config: its `targets.<name>` entry,
|
|
412
|
+
* or null when that layer says nothing about this target. Every merge chain
|
|
413
|
+
* (loadConfig and composeTargetConfig alike) folds the layers through this.
|
|
414
|
+
* @param {object|null} layer - A raw config layer (company, brand, or local).
|
|
415
|
+
* @param {string|null} name - The resolved target name.
|
|
416
|
+
* @returns {object|null} The target layer, or null.
|
|
417
|
+
*/
|
|
418
|
+
function targetLayerOf(layer, name) {
|
|
419
|
+
return layer && layer.targets && isPlainObject(layer.targets[name]) ? layer.targets[name] : null;
|
|
420
|
+
}
|
|
421
|
+
|
|
276
422
|
/**
|
|
277
423
|
* Enabled targets of a config (raw or resolved): the keys of its `targets`
|
|
278
424
|
* object — key presence IS the enablement signal.
|
|
@@ -284,22 +430,46 @@ function getEnabledTargets(config) {
|
|
|
284
430
|
return targets && typeof targets === 'object' ? Object.keys(targets) : [];
|
|
285
431
|
}
|
|
286
432
|
|
|
433
|
+
/**
|
|
434
|
+
* The RESOLVED `company` section: the same keys the consumer typed (`{ id }`,
|
|
435
|
+
* and `{ webhooks }` when the brand opts out), filled with the company's public
|
|
436
|
+
* facts (#677). A brand with no company resolves to its OWN name and url under
|
|
437
|
+
* a null id, so no reader anywhere needs a fallback.
|
|
438
|
+
* @param {string} hostRoot - The brand root the company is named from.
|
|
439
|
+
* @param {object} config - The merged config (its `company` and `brand` blocks).
|
|
440
|
+
* @returns {{ id: string|null, name: string|null, url: string|null, images: object, webhooks: boolean }}
|
|
441
|
+
*/
|
|
442
|
+
function companyFacts(hostRoot, config) {
|
|
443
|
+
const { id, name, url, images, webhooks } = resolveCompany(hostRoot, config);
|
|
444
|
+
|
|
445
|
+
return { id, name, url, images, webhooks };
|
|
446
|
+
}
|
|
447
|
+
|
|
287
448
|
/**
|
|
288
449
|
* Load + resolve a project's omega.json5.
|
|
289
450
|
* @param {string} projectDir - The project root (target root in a brand monorepo).
|
|
290
|
-
* @param {string} [target] -
|
|
291
|
-
* given, the target sections overlay the shared
|
|
292
|
-
* brand + local files merge whole (targets map
|
|
293
|
-
* omega-manager's disperse want.
|
|
451
|
+
* @param {string} [target] - The framework TYPE this project runs ('web',
|
|
452
|
+
* 'backend', ...). When given, the target sections overlay the shared
|
|
453
|
+
* namespace. When omitted, the brand + local files merge whole (targets map
|
|
454
|
+
* included): the shape tools like omega-manager's disperse want.
|
|
294
455
|
* @param {object} [options]
|
|
295
456
|
* @param {object} [options.defaults] - Framework defaults, layered directly on
|
|
296
457
|
* top of the schema defaults (#478) — only what this framework does differently.
|
|
297
|
-
* @
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* the
|
|
458
|
+
* @param {string} [options.environment] - The environment this load is FOR (one
|
|
459
|
+
* of ENV_ENVIRONMENTS): which `omega.<environment>.json5` overlay composes
|
|
460
|
+
* ([#856](https://github.com/Omega-JS-Stack/omega/issues/856)). Every lane
|
|
461
|
+
* that produces a PRODUCTION artifact names it, the way stageFunctions()
|
|
462
|
+
* already names one for the `.env` overlay beside it (#586), because the
|
|
463
|
+
* ambient answer is the composing machine's and a build from a terminal
|
|
464
|
+
* resolves `development`. Omitted (a dev boot, a deployed runtime) = the
|
|
465
|
+
* running environment, envEnvironment().
|
|
466
|
+
* @returns {{ config: object, errors: string[], warnings: string[], enabled: boolean|null, name: string|null, files: { local: string, brand: string|null, company: string|null } }}
|
|
467
|
+
* `enabled` = whether the resolved NAME is listed under `targets` (null when
|
|
468
|
+
* no target was requested); schema `errors` are returned, not thrown; only
|
|
469
|
+
* secrets and unusable files throw. `warnings` are advisory findings (e.g. >1
|
|
470
|
+
* backend target); `name` is the target-dir-resolved target name (for a
|
|
471
|
+
* standalone project, the single declared target of that type, else the type
|
|
472
|
+
* word; null when no target was requested).
|
|
303
473
|
*/
|
|
304
474
|
function loadConfig(projectDir, target, options) {
|
|
305
475
|
options = options || {};
|
|
@@ -319,7 +489,6 @@ function loadConfig(projectDir, target, options) {
|
|
|
319
489
|
localPath = resolveConfigPath(path.dirname(path.resolve(projectDir)));
|
|
320
490
|
}
|
|
321
491
|
const brandPath = findBrandConfigPath(projectDir);
|
|
322
|
-
const companyPath = findCompanyConfigPath(projectDir);
|
|
323
492
|
|
|
324
493
|
// The local-layer file is OPTIONAL inside a brand monorepo (Ian 2026-07-13:
|
|
325
494
|
// the brand file's targets section IS the per-target home) — a target with
|
|
@@ -331,80 +500,130 @@ function loadConfig(projectDir, target, options) {
|
|
|
331
500
|
|
|
332
501
|
const local = localPath ? readConfigFile(localPath) : {};
|
|
333
502
|
const brand = brandPath ? readConfigFile(brandPath) : null;
|
|
334
|
-
const company = companyPath ? readConfigFile(companyPath) : null;
|
|
335
|
-
|
|
336
|
-
assertUsableRawFile(companyPath, company);
|
|
337
|
-
assertUsableRawFile(brandPath, brand);
|
|
338
|
-
assertUsableRawFile(localPath, local);
|
|
339
503
|
|
|
340
|
-
|
|
504
|
+
// The company layer comes from the brand's typed `company: { id }` through the
|
|
505
|
+
// ONE resolver (#677), which also answers for a runner that received a
|
|
506
|
+
// generated layer instead of a machine to resolve on.
|
|
507
|
+
const company = resolveCompany(companyHostRoot(projectDir), brand || local);
|
|
508
|
+
const companyPath = company.configFile;
|
|
509
|
+
|
|
510
|
+
// Each layer's environment overlay (#856), found beside its own base file.
|
|
511
|
+
// The environment is the one the CALLER named when it named one (a lane that
|
|
512
|
+
// knows what its artifact is FOR), else this machine's ambient answer, which
|
|
513
|
+
// is what a dev boot and a deployed runtime both want.
|
|
514
|
+
// lazy require: env.js depends on this module (findBrandRoot)
|
|
515
|
+
const { envEnvironment } = require('./env.js');
|
|
516
|
+
const environment = resolveOverlayEnvironment(options.environment, envEnvironment());
|
|
517
|
+
const { companyOverlay, brandOverlay, localOverlay } = resolveLayerOverlays({
|
|
518
|
+
companyPath, company: company.config, brandPath, brand, localPath, local, environment,
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
const inherited = stripCompanyPlumbing(company.config);
|
|
341
522
|
|
|
342
523
|
// ─── Resolve ─────────────────────────────────────────────────────────────
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
524
|
+
// The chain, weakest first: every layer's base file followed by its own
|
|
525
|
+
// environment overlay (#856), which beats that base and still loses to the
|
|
526
|
+
// layer above. Each entry contributes shared, then targets[name], below.
|
|
527
|
+
const layers = [
|
|
528
|
+
inherited,
|
|
529
|
+
stripCompanyPlumbing(companyOverlay),
|
|
530
|
+
brand,
|
|
531
|
+
brandOverlay,
|
|
532
|
+
local,
|
|
533
|
+
localOverlay,
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
const hasTargets = layers.some((layer) => !!(layer && layer.targets));
|
|
537
|
+
const targets = deepMerge(...layers.map((layer) => (layer ? layer.targets : null)));
|
|
538
|
+
|
|
539
|
+
// WHICH target this is comes from its dir name (#886): targets/community is
|
|
540
|
+
// the `community` entry. Only brand-monorepo targets resolve through the
|
|
541
|
+
// walk: a standalone project's dir name is arbitrary, so it is named by what
|
|
542
|
+
// its own file declares for the framework it runs.
|
|
543
|
+
const name = target ? (targetNameFromDir(projectDir) || standaloneTargetName(targets, target)) : null;
|
|
544
|
+
|
|
545
|
+
// A dir whose entry runs a DIFFERENT framework has no honest resolution: the
|
|
546
|
+
// caller would silently merge somebody else's target layer.
|
|
547
|
+
const declared = name ? targets[name] : null;
|
|
548
|
+
if (target && declared && declared.type && declared.type !== target) {
|
|
549
|
+
throw new Error(`targets.${name} is type ${declared.type}; this project runs the ${target} framework`);
|
|
353
550
|
}
|
|
354
|
-
const instance = target && brandPath ? instanceIdFromDirName(path.basename(targetRoot), target) : MAIN_INSTANCE;
|
|
355
551
|
|
|
356
552
|
const config = target
|
|
357
553
|
? deepMerge(
|
|
358
554
|
schemaDefaults(target),
|
|
359
555
|
options.defaults,
|
|
360
|
-
stripTargets(
|
|
361
|
-
inherited && inherited.targets ? resolveInstanceEntry(inherited.targets[target], instance) : null,
|
|
362
|
-
stripTargets(brand),
|
|
363
|
-
brand && brand.targets ? resolveInstanceEntry(brand.targets[target], instance) : null,
|
|
364
|
-
stripTargets(local),
|
|
365
|
-
local.targets ? resolveInstanceEntry(local.targets[target], instance) : null,
|
|
556
|
+
...layers.flatMap((layer) => [stripTargets(layer), targetLayerOf(layer, name)]),
|
|
366
557
|
)
|
|
367
|
-
: deepMerge(schemaDefaults(), options.defaults,
|
|
558
|
+
: deepMerge(schemaDefaults(), options.defaults, ...layers);
|
|
368
559
|
|
|
369
560
|
// Keep the merged targets map on the resolved config (presence = enabled)
|
|
370
561
|
if (target && hasTargets) {
|
|
371
562
|
config.targets = targets;
|
|
372
563
|
}
|
|
373
564
|
|
|
374
|
-
// The
|
|
375
|
-
// merged to the top level above, and a bare `{
|
|
376
|
-
// https://
|
|
377
|
-
// place, so every reader of the resolved config (site-global's
|
|
378
|
-
// web deploy's host + CNAME) sees
|
|
379
|
-
//
|
|
565
|
+
// The target's own public URL (#588): an entry's explicit `url` already
|
|
566
|
+
// merged to the top level above, and a bare `community: { type: 'web' }`
|
|
567
|
+
// derives https://community.<brand host> through the ONE resolver, landing in
|
|
568
|
+
// that same place, so every reader of the resolved config (site-global's
|
|
569
|
+
// site.url, the web deploy's host + CNAME) sees THIS target's url and not the
|
|
570
|
+
// main site's. A target named for its type derives nothing: brand.url IS its
|
|
571
|
+
// url.
|
|
380
572
|
//
|
|
381
|
-
// A brand.url OVERRIDE that reached this
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
if (target &&
|
|
387
|
-
|
|
573
|
+
// A brand.url OVERRIDE that reached this target (the entry's own `brand.url`,
|
|
574
|
+
// the target dir's local layer, a dev layer pointing at localhost) IS the
|
|
575
|
+
// target url already, never a base to stack the name on: deriving there gave
|
|
576
|
+
// shop.shop.acme.test and https://admin.localhost:4000. The test is whether
|
|
577
|
+
// the resolved brand.url still equals the BRAND layer's.
|
|
578
|
+
if (target && name !== target && !config.url) {
|
|
579
|
+
// A layer's environment overlay IS that layer's statement of brand.url
|
|
580
|
+
// (#856), so a development brand.url is a brand-layer value like any
|
|
581
|
+
// other: the targets under it keep deriving their own names off it.
|
|
582
|
+
const brandLayerUrl = (brandOverlay && brandOverlay.brand && brandOverlay.brand.url)
|
|
583
|
+
|| (brand && brand.brand && brand.brand.url)
|
|
584
|
+
|| (companyOverlay && companyOverlay.brand && companyOverlay.brand.url)
|
|
388
585
|
|| (inherited && inherited.brand && inherited.brand.url)
|
|
586
|
+
// A standalone project has no brand layer above it, so its OWN shared
|
|
587
|
+
// brand.url IS the brand's: only a target entry below it overrides.
|
|
588
|
+
|| (!brandPath && localOverlay && localOverlay.brand ? localOverlay.brand.url : null)
|
|
589
|
+
|| (!brandPath && local.brand ? local.brand.url : null)
|
|
389
590
|
|| null;
|
|
390
591
|
const resolvedUrl = (config.brand && config.brand.url) || null;
|
|
391
592
|
|
|
392
|
-
const
|
|
593
|
+
const resolvedTargetUrl = resolvedUrl && resolvedUrl !== brandLayerUrl
|
|
393
594
|
? resolvedUrl
|
|
394
|
-
:
|
|
595
|
+
: targetUrl(config, name);
|
|
395
596
|
|
|
396
|
-
if (
|
|
397
|
-
config.url =
|
|
597
|
+
if (resolvedTargetUrl) {
|
|
598
|
+
config.url = resolvedTargetUrl;
|
|
398
599
|
}
|
|
399
600
|
}
|
|
400
601
|
|
|
401
602
|
const enabled = target
|
|
402
|
-
? hasTargets && Object.prototype.hasOwnProperty.call(targets,
|
|
603
|
+
? hasTargets && Object.prototype.hasOwnProperty.call(targets, name)
|
|
403
604
|
: null;
|
|
404
605
|
|
|
405
606
|
const { errors, warnings } = validateConfig(config, { target });
|
|
406
607
|
|
|
407
|
-
|
|
608
|
+
// The `company` section is RESOLVED, never typed past its `id` (#677): the
|
|
609
|
+
// same key the consumer wrote comes back filled, so every reader of a parent
|
|
610
|
+
// fact (the footer's credit, an email wordmark, the in-house ads api) reads
|
|
611
|
+
// one shape whether the brand has a company, IS one, or stands alone. Filled
|
|
612
|
+
// AFTER validation, so a typed name/url/images is still the author's key when
|
|
613
|
+
// the validator judges it.
|
|
614
|
+
config.company = companyFacts(companyHostRoot(projectDir), config);
|
|
615
|
+
|
|
616
|
+
// Every brand run writes its own line in the machine registry, which is what
|
|
617
|
+
// makes a sibling brand's `company: { id }` resolvable here without anyone
|
|
618
|
+
// maintaining a map (#677). Never fails the load.
|
|
619
|
+
recordBrand({
|
|
620
|
+
id: config.brand && config.brand.id,
|
|
621
|
+
root: companyHostRoot(projectDir),
|
|
622
|
+
name: config.brand && config.brand.name,
|
|
623
|
+
url: config.brand && config.brand.url,
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
return { config, errors, warnings, enabled, name, files: { local: localPath, brand: brandPath, company: companyPath } };
|
|
408
627
|
}
|
|
409
628
|
|
|
410
629
|
/**
|
|
@@ -415,30 +634,49 @@ function loadConfig(projectDir, target, options) {
|
|
|
415
634
|
* interleave (company shared ← company targets[target] ← brand shared ← brand
|
|
416
635
|
* targets[target] ← local shared ← local targets[target]) is frozen into the
|
|
417
636
|
* shared namespace: the deployed
|
|
418
|
-
* runtime's own `deepMerge(defaults, shared, targets[
|
|
419
|
-
* EXACTLY the local resolution. `targets` keeps presence-
|
|
420
|
-
* (presence = enabled
|
|
421
|
-
*
|
|
422
|
-
* let a brand-target value beat a
|
|
637
|
+
* runtime's own `deepMerge(defaults, shared, targets[name])` then yields
|
|
638
|
+
* EXACTLY the local resolution. `targets` keeps presence-and-type keys
|
|
639
|
+
* (presence = enabled, and the `type` every entry must declare; every other
|
|
640
|
+
* value is already folded in, so nothing re-applies above the frozen
|
|
641
|
+
* interleave: a raw merged targets map would let a brand-target value beat a
|
|
642
|
+
* local-shared one, flipping the chain).
|
|
423
643
|
* Framework defaults are NOT baked in: the deployed runtime applies its
|
|
424
644
|
* own, so defaults evolve with the shipped package, not the deploy moment.
|
|
425
645
|
*
|
|
646
|
+
* Environment overlays (#856) compose only for an environment the CALLER named:
|
|
647
|
+
* a compose is a BUILD-time op over the authored layers, and the environment an
|
|
648
|
+
* artifact is FOR is the LANE's answer, never the composing machine's (`omega
|
|
649
|
+
* deploy` from a terminal resolves `development`, which would bake a
|
|
650
|
+
* development override into a production upload). So the ambient answer is
|
|
651
|
+
* never read here, and a compose told nothing freezes the base layers alone.
|
|
652
|
+
* The knob is the one stageFunctions() already takes for the `.env` overlay
|
|
653
|
+
* beside it, spelled the same: `options.environment`.
|
|
654
|
+
*
|
|
426
655
|
* The local-layer file is OPTIONAL inside a brand monorepo (same rule as
|
|
427
656
|
* loadConfig since cp121c): a target with no omega.json5 of its own composes
|
|
428
657
|
* from the brand file alone. Standalone projects still require their file.
|
|
429
658
|
*
|
|
430
659
|
* @param {string} projectDir - Target root or one of its TARGET_SUBDIRS (functions/, dist/).
|
|
431
660
|
* @param {string} target - Canonical target the upload serves ('backend').
|
|
661
|
+
* @param {object} [options]
|
|
662
|
+
* @param {string} [options.environment] - The environment the upload is FOR (one
|
|
663
|
+
* of ENV_ENVIRONMENTS): which `omega.<environment>.json5` overlay is frozen
|
|
664
|
+
* into the composed file. Omitted = no overlay at all.
|
|
432
665
|
* @returns {{ config: object, files: { local: string|null, brand: string|null, company: string|null } }}
|
|
433
666
|
* `files.brand` null = no brand layer above the target (already self-contained);
|
|
434
667
|
* `files.local` null = the target rides the brand file alone; `files.company`
|
|
435
|
-
* null = the brand
|
|
668
|
+
* null = the brand names no company with a tree on this machine.
|
|
436
669
|
*/
|
|
437
|
-
function composeTargetConfig(projectDir, target) {
|
|
670
|
+
function composeTargetConfig(projectDir, target, options) {
|
|
671
|
+
options = options || {};
|
|
672
|
+
|
|
438
673
|
if (!TARGETS.includes(target)) {
|
|
439
674
|
throw new Error(`Unknown target "${target}" — must be one of [${TARGETS.join(', ')}]`);
|
|
440
675
|
}
|
|
441
676
|
|
|
677
|
+
// No fallback: an unnamed environment composes NO overlay (see above).
|
|
678
|
+
const environment = resolveOverlayEnvironment(options.environment, null);
|
|
679
|
+
|
|
442
680
|
// Compose is a BUILD-time op over the AUTHORED layers: a TARGET_SUBDIR
|
|
443
681
|
// (functions/, dist/) normalizes up to its target root, so a previously-staged
|
|
444
682
|
// config/omega.json5 (compose OUTPUT) can never read back in as a local
|
|
@@ -450,40 +688,53 @@ function composeTargetConfig(projectDir, target) {
|
|
|
450
688
|
|
|
451
689
|
const localPath = resolveConfigPath(targetRoot);
|
|
452
690
|
const brandPath = findBrandConfigPath(targetRoot);
|
|
453
|
-
const companyPath = findCompanyConfigPath(targetRoot);
|
|
454
691
|
if (!localPath && !brandPath) {
|
|
455
692
|
throw new Error(`No ${FILE_NAME} found under ${projectDir} (looked in ${CONFIG_LOCATIONS.join(', ')})`);
|
|
456
693
|
}
|
|
457
694
|
|
|
458
695
|
const local = localPath ? readConfigFile(localPath) : {};
|
|
459
696
|
const brand = brandPath ? readConfigFile(brandPath) : null;
|
|
460
|
-
const company = companyPath ? readConfigFile(companyPath) : null;
|
|
461
697
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
assertUsableRawFile(localPath, local);
|
|
698
|
+
const company = resolveCompany(companyHostRoot(targetRoot), brand || local);
|
|
699
|
+
const companyPath = company.configFile;
|
|
465
700
|
|
|
466
|
-
|
|
701
|
+
// Each layer's overlay for THIS environment (#856), beside its own base file,
|
|
702
|
+
// read and gated by the same one helper loadConfig uses.
|
|
703
|
+
const { companyOverlay, brandOverlay, localOverlay } = resolveLayerOverlays({
|
|
704
|
+
companyPath, company: company.config, brandPath, brand, localPath, local, environment,
|
|
705
|
+
});
|
|
467
706
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
707
|
+
const inherited = stripCompanyPlumbing(company.config);
|
|
708
|
+
const inheritedOverlay = stripCompanyPlumbing(companyOverlay);
|
|
709
|
+
|
|
710
|
+
// The chain, weakest first: every layer's base file followed by its own
|
|
711
|
+
// environment overlay (#856), exactly the order loadConfig folds them in.
|
|
712
|
+
const layers = [inherited, inheritedOverlay, brand, brandOverlay, local, localOverlay];
|
|
713
|
+
|
|
714
|
+
// Same collapse as loadConfig: the target dir NAMES the entry that is this
|
|
715
|
+
// compose's target layer (outside a brand, the file's single target of this
|
|
716
|
+
// framework's type).
|
|
717
|
+
|
|
718
|
+
const mergedTargets = deepMerge(...layers.map((layer) => (layer ? layer.targets : null)));
|
|
719
|
+
const name = targetNameFromDir(targetRoot) || standaloneTargetName(mergedTargets, target);
|
|
471
720
|
|
|
472
721
|
const config = deepMerge(
|
|
473
|
-
stripTargets(
|
|
474
|
-
inherited && inherited.targets ? resolveInstanceEntry(inherited.targets[target], instance) : null,
|
|
475
|
-
stripTargets(brand),
|
|
476
|
-
brand && brand.targets ? resolveInstanceEntry(brand.targets[target], instance) : null,
|
|
477
|
-
stripTargets(local),
|
|
478
|
-
local.targets ? resolveInstanceEntry(local.targets[target], instance) : null,
|
|
722
|
+
...layers.flatMap((layer) => [stripTargets(layer), targetLayerOf(layer, name)]),
|
|
479
723
|
);
|
|
480
724
|
|
|
481
|
-
const hasTargets =
|
|
725
|
+
const hasTargets = layers.some((layer) => !!(layer && layer.targets));
|
|
482
726
|
if (hasTargets) {
|
|
483
|
-
|
|
484
|
-
|
|
727
|
+
config.targets = Object.fromEntries(Object.keys(mergedTargets).map((entryName) => {
|
|
728
|
+
const entry = mergedTargets[entryName];
|
|
729
|
+
return [entryName, isPlainObject(entry) && entry.type ? { type: entry.type } : {}];
|
|
730
|
+
}));
|
|
485
731
|
}
|
|
486
732
|
|
|
733
|
+
// The resolved company rides the upload like every other layer above it: the
|
|
734
|
+
// deployed runtime has no registry and no company tree, so the facts have to
|
|
735
|
+
// be frozen in here (#677).
|
|
736
|
+
config.company = companyFacts(companyHostRoot(targetRoot), config);
|
|
737
|
+
|
|
487
738
|
return { config, files: { local: localPath, brand: brandPath, company: companyPath } };
|
|
488
739
|
}
|
|
489
740
|
|