@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,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* brand-version: the brand's ONE version
|
|
3
|
+
* ([#869](https://github.com/Omega-JS-Stack/omega/issues/869)).
|
|
4
|
+
*
|
|
5
|
+
* A brand root's `package.json` `version` is the master and every
|
|
6
|
+
* `targets/<name>/package.json` follows it. Targets keep a real `version`
|
|
7
|
+
* field because every tool reads it there (electron-builder, the extension
|
|
8
|
+
* manifest, npm), so nothing needs plumbing in dev or CI; what they never do
|
|
9
|
+
* is move on their own.
|
|
10
|
+
*
|
|
11
|
+
* Two halves, one rule:
|
|
12
|
+
* - `bumpBrandVersion` is the ONE writer, behind `omega bump` at the brand
|
|
13
|
+
* root. It never commits and never tags: the ship flow owns the
|
|
14
|
+
* `chore(release)` commit.
|
|
15
|
+
* - `assertBrandVersion` is the gate every framework's `omega deploy` runs
|
|
16
|
+
* before its secrets precheck, so a target that drifted is refused before
|
|
17
|
+
* anything is published or published to.
|
|
18
|
+
*
|
|
19
|
+
* Twin in shape of the manager's `assertFamilyVersions`
|
|
20
|
+
* ([#794](https://github.com/Omega-JS-Stack/omega/issues/794)), which refuses a
|
|
21
|
+
* mixed @omega.js FAMILY. Same verdict, different subject: that one is about
|
|
22
|
+
* the framework packages a brand installed, this one about the brand's own
|
|
23
|
+
* release number.
|
|
24
|
+
*
|
|
25
|
+
* Beside it, the LOCKFILE gate
|
|
26
|
+
* ([#938](https://github.com/Omega-JS-Stack/omega/issues/938)): the registry
|
|
27
|
+
* deploy lane ships the brand's own `package-lock.json`, and the runner's
|
|
28
|
+
* `npm ci` installs exactly what it says, so `assertBrandLockfile` refuses a
|
|
29
|
+
* lock that disagrees with the manifests' @omega.js specs before anything is
|
|
30
|
+
* pushed. `brandLockfileDrift` is the read both the gate and the lock
|
|
31
|
+
* regeneration (`lockfile.js`) share.
|
|
32
|
+
*/
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const jetpack = require('fs-jetpack');
|
|
35
|
+
|
|
36
|
+
const { resolveBrandRoot } = require('../config/index.js');
|
|
37
|
+
const Logger = require('./logger.js');
|
|
38
|
+
const { isRegistrySpec, resolveWanted } = require('./update.js');
|
|
39
|
+
|
|
40
|
+
const defaultLogger = new Logger('brand-version');
|
|
41
|
+
|
|
42
|
+
// What `omega bump` takes, in the order it prints them.
|
|
43
|
+
const KINDS = ['patch', 'minor', 'major'];
|
|
44
|
+
|
|
45
|
+
// A plain release number and nothing else: a prerelease tag is refused by name
|
|
46
|
+
// rather than guessed at (which digit does `patch` move on `1.0.0-beta.2`?).
|
|
47
|
+
const RELEASE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A refusal: crafted text a human is meant to read, flagged so every surface
|
|
51
|
+
* prints it the same way (the message alone, nothing added), the same shape
|
|
52
|
+
* scaffold-guard's refusals take.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} message - The lines to print verbatim.
|
|
55
|
+
* @returns {Error} The flagged error to throw.
|
|
56
|
+
*/
|
|
57
|
+
function refusal(message) {
|
|
58
|
+
const error = new Error(message);
|
|
59
|
+
error.refusal = true;
|
|
60
|
+
return error;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The brand's version, resolved from anywhere inside the brand.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} options
|
|
67
|
+
* @param {string} options.dir - Any directory inside the brand (root or target).
|
|
68
|
+
* @returns {{ brandRoot: string, version: string }|null} null when `dir` is not inside a brand.
|
|
69
|
+
* @throws {Error} A refusal when the brand root carries no `version`: the
|
|
70
|
+
* master number cannot be defaulted, or every target would follow a guess.
|
|
71
|
+
*/
|
|
72
|
+
function readBrandVersion({ dir }) {
|
|
73
|
+
const brandRoot = resolveBrandRoot(dir);
|
|
74
|
+
if (!brandRoot) return null;
|
|
75
|
+
|
|
76
|
+
const manifest = path.join(brandRoot, 'package.json');
|
|
77
|
+
const version = (jetpack.read(manifest, 'json') || {}).version;
|
|
78
|
+
|
|
79
|
+
if (!version) {
|
|
80
|
+
throw refusal(
|
|
81
|
+
`${manifest} carries no "version", and the brand root is the one version every target follows (#869).\n`
|
|
82
|
+
+ ` fix: add "version" to ${manifest}, then run \`omega bump\` at the brand root`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { brandRoot, version };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Every target that carries a package.json, by name. A directory under
|
|
91
|
+
* `targets/` with no manifest is not a target (the manager's `discoverTargets`
|
|
92
|
+
* rule), and the list is sorted so a bump's output reads the same every run.
|
|
93
|
+
*
|
|
94
|
+
* @param {object} options
|
|
95
|
+
* @param {string} options.brandRoot - Brand monorepo root.
|
|
96
|
+
* @returns {Array<{ name: string, dir: string, manifest: string }>}
|
|
97
|
+
*/
|
|
98
|
+
function listTargetPackages({ brandRoot }) {
|
|
99
|
+
const targetsDir = path.join(brandRoot, 'targets');
|
|
100
|
+
|
|
101
|
+
return (jetpack.list(targetsDir) || [])
|
|
102
|
+
.filter((name) => !name.startsWith('.'))
|
|
103
|
+
.sort()
|
|
104
|
+
.map((name) => ({
|
|
105
|
+
name,
|
|
106
|
+
dir: path.join(targetsDir, name),
|
|
107
|
+
manifest: path.join(targetsDir, name, 'package.json'),
|
|
108
|
+
}))
|
|
109
|
+
.filter((entry) => jetpack.exists(entry.manifest) === 'file');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The next release number for a kind.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} previous - The brand's current version.
|
|
116
|
+
* @param {'patch'|'minor'|'major'} kind - Which digit moves.
|
|
117
|
+
* @returns {string} The next version.
|
|
118
|
+
* @throws {Error} A refusal when `previous` is not a plain release number.
|
|
119
|
+
*/
|
|
120
|
+
function nextVersion(previous, kind) {
|
|
121
|
+
const parts = RELEASE.exec(String(previous));
|
|
122
|
+
|
|
123
|
+
if (!parts) {
|
|
124
|
+
throw refusal(
|
|
125
|
+
`the brand version "${previous}" is not a plain major.minor.patch, and \`omega bump\` moves whole releases with no prerelease handling.\n`
|
|
126
|
+
+ ' fix: set a plain version in the brand root package.json first',
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const [major, minor, patch] = parts.slice(1).map(Number);
|
|
131
|
+
|
|
132
|
+
if (kind === 'major') return `${major + 1}.0.0`;
|
|
133
|
+
if (kind === 'minor') return `${major}.${minor + 1}.0`;
|
|
134
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The same content with `version` set, gaining the key after `name` when the
|
|
139
|
+
* file never had one, so a target's first bump reads like every sibling's
|
|
140
|
+
* manifest instead of appending a stray key at the end.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} content - The parsed manifest (or lock entry).
|
|
143
|
+
* @param {string} next - The version to write.
|
|
144
|
+
* @returns {object} The content to write back.
|
|
145
|
+
*/
|
|
146
|
+
function withVersion(content, next) {
|
|
147
|
+
if (content.version !== undefined) return { ...content, version: next };
|
|
148
|
+
|
|
149
|
+
const updated = content.name === undefined ? { version: next } : {};
|
|
150
|
+
for (const [key, value] of Object.entries(content)) {
|
|
151
|
+
updated[key] = value;
|
|
152
|
+
if (key === 'name') updated.version = next;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return updated;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Write a JSON file the way npm writes them: 2-space indent, trailing newline.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} file - Where to write.
|
|
162
|
+
* @param {object} content - What to write.
|
|
163
|
+
* @returns {void}
|
|
164
|
+
*/
|
|
165
|
+
function writeJson(file, content) {
|
|
166
|
+
jetpack.write(file, `${JSON.stringify(content, null, 2)}\n`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Move the brand and every target onto the next version. The ONE writer.
|
|
171
|
+
*
|
|
172
|
+
* Writes the brand root manifest, every target manifest (a target with no
|
|
173
|
+
* `version` GAINS one: every target follows), and the brand lock's entry for
|
|
174
|
+
* each target plus its own root version where the lock carries one, since a
|
|
175
|
+
* lock left behind would have `npm ci` install the old number.
|
|
176
|
+
*
|
|
177
|
+
* @param {object} options
|
|
178
|
+
* @param {string} options.brandRoot - Brand monorepo root.
|
|
179
|
+
* @param {'patch'|'minor'|'major'} options.kind - Which digit moves.
|
|
180
|
+
* @param {object} [options.logger] - Where the per-file lines go.
|
|
181
|
+
* @returns {{ previous: string, next: string, files: string[] }} Paths relative to the brand root.
|
|
182
|
+
* @throws {Error} A refusal on an unknown kind, a missing root version, or a prerelease one.
|
|
183
|
+
*/
|
|
184
|
+
function bumpBrandVersion({ brandRoot, kind, logger = defaultLogger }) {
|
|
185
|
+
if (!KINDS.includes(kind)) {
|
|
186
|
+
throw refusal(`\`omega bump\` takes ${KINDS.join(', ')}, and "${kind}" is none of them.`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const previous = readBrandVersion({ dir: brandRoot }).version;
|
|
190
|
+
const next = nextVersion(previous, kind);
|
|
191
|
+
const files = [];
|
|
192
|
+
|
|
193
|
+
// One line per file, always `<old> -> <new>`; a file that had no version
|
|
194
|
+
// says so rather than printing an empty left-hand side.
|
|
195
|
+
const write = (file, old) => {
|
|
196
|
+
const relative = path.relative(brandRoot, file);
|
|
197
|
+
files.push(relative);
|
|
198
|
+
logger.log(`${relative} ${old || '(none)'} -> ${next}`);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const rootManifest = path.join(brandRoot, 'package.json');
|
|
202
|
+
writeJson(rootManifest, withVersion(jetpack.read(rootManifest, 'json'), next));
|
|
203
|
+
write(rootManifest, previous);
|
|
204
|
+
|
|
205
|
+
const targets = listTargetPackages({ brandRoot });
|
|
206
|
+
|
|
207
|
+
for (const target of targets) {
|
|
208
|
+
const content = jetpack.read(target.manifest, 'json');
|
|
209
|
+
writeJson(target.manifest, withVersion(content, next));
|
|
210
|
+
write(target.manifest, content.version);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const lockPath = path.join(brandRoot, 'package-lock.json');
|
|
214
|
+
const lock = jetpack.exists(lockPath) === 'file' ? jetpack.read(lockPath, 'json') : null;
|
|
215
|
+
|
|
216
|
+
if (lock && lock.packages) {
|
|
217
|
+
for (const target of targets) {
|
|
218
|
+
const key = `targets/${target.name}`;
|
|
219
|
+
if (lock.packages[key]) lock.packages[key] = withVersion(lock.packages[key], next);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// The lock's own root number exists only when npm wrote one, and npm
|
|
223
|
+
// writes it from the root manifest: where it is absent, the next install
|
|
224
|
+
// is what puts it there.
|
|
225
|
+
if (lock.version) lock.version = next;
|
|
226
|
+
if (lock.packages[''] && lock.packages[''].version) lock.packages[''].version = next;
|
|
227
|
+
|
|
228
|
+
writeJson(lockPath, lock);
|
|
229
|
+
write(lockPath, previous);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { previous, next, files };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The deploy gate: refuse a target whose version has drifted from the brand's.
|
|
237
|
+
*
|
|
238
|
+
* Read-only, so every lane runs it (`--direct` and `--dry-run` included), and
|
|
239
|
+
* it runs BEFORE the secrets precheck: a drifted target must not push its
|
|
240
|
+
* secrets and dispatch a build of the wrong number. A match says nothing.
|
|
241
|
+
*
|
|
242
|
+
* @param {object} options
|
|
243
|
+
* @param {string} options.dir - The target directory being deployed.
|
|
244
|
+
* @returns {{ brandRoot: string, name: string, version: string }|null} null when there is nothing to compare.
|
|
245
|
+
* @throws {Error} A refusal naming the target and `omega bump`.
|
|
246
|
+
*/
|
|
247
|
+
function assertBrandVersion({ dir }) {
|
|
248
|
+
const brandRoot = resolveBrandRoot(dir);
|
|
249
|
+
if (!brandRoot) return null;
|
|
250
|
+
|
|
251
|
+
// Only a target follows the brand. A brand root, or any other directory
|
|
252
|
+
// inside the brand, has nothing of its own to compare.
|
|
253
|
+
const segments = path.relative(brandRoot, path.resolve(dir)).split(path.sep);
|
|
254
|
+
if (segments[0] !== 'targets' || !segments[1]) return null;
|
|
255
|
+
|
|
256
|
+
const name = segments[1];
|
|
257
|
+
const manifest = path.join(brandRoot, 'targets', name, 'package.json');
|
|
258
|
+
if (jetpack.exists(manifest) !== 'file') return null;
|
|
259
|
+
|
|
260
|
+
const found = (jetpack.read(manifest, 'json') || {}).version;
|
|
261
|
+
const { version } = readBrandVersion({ dir: brandRoot });
|
|
262
|
+
|
|
263
|
+
if (found !== version) {
|
|
264
|
+
throw refusal(
|
|
265
|
+
`targets/${name} ${found ? `is ${found}` : 'has no version'} but the brand is ${version}; `
|
|
266
|
+
+ 'run `omega bump` at the brand root (#869).',
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { brandRoot, name, version };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// The family's scope: the only dependencies the lockfile gate reads.
|
|
274
|
+
const SCOPE = '@omega.js/';
|
|
275
|
+
|
|
276
|
+
// A registry entry's `resolved` is a URL; a link target, a packed tarball or a
|
|
277
|
+
// directory is a path, which a runner has nothing at.
|
|
278
|
+
const REGISTRY_RESOLVED = /^https?:\/\//;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The lock entry npm would use for `name` as declared by the manifest at
|
|
282
|
+
* `where`: its own nested copy first, then the hoisted one at the root, the
|
|
283
|
+
* order node resolves in.
|
|
284
|
+
*
|
|
285
|
+
* @param {object} packages - The lock's `packages` map.
|
|
286
|
+
* @param {string} where - The manifest's dir relative to the root ('' for the root).
|
|
287
|
+
* @param {string} name - The dependency name.
|
|
288
|
+
* @returns {string|null} The lock key, or null when neither slot exists.
|
|
289
|
+
*/
|
|
290
|
+
function lockKeyFor(packages, where, name) {
|
|
291
|
+
const keys = where ? [`${where}/node_modules/${name}`, `node_modules/${name}`] : [`node_modules/${name}`];
|
|
292
|
+
return keys.find((key) => packages[key]) || null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Why a lock entry cannot satisfy a registry spec on a runner, or null when it
|
|
297
|
+
* does.
|
|
298
|
+
*
|
|
299
|
+
* @param {object|undefined} entry - The lock entry.
|
|
300
|
+
* @param {string} spec - The manifest's registry spec.
|
|
301
|
+
* @returns {string|null} The reason, worded to follow `<name> <spec> `.
|
|
302
|
+
*/
|
|
303
|
+
function entryDrift(entry, spec) {
|
|
304
|
+
if (!entry) return 'is not in the lock';
|
|
305
|
+
if (entry.link) return `is locked as a link to ${entry.resolved}`;
|
|
306
|
+
if (entry.resolved && !REGISTRY_RESOLVED.test(entry.resolved)) return `is locked at the path ${entry.resolved}`;
|
|
307
|
+
if (resolveWanted(spec, [entry.version]) !== entry.version) return `is locked at ${entry.version || 'no version'}`;
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The lock's STALE path entries: a key that names a folder under the root (a
|
|
313
|
+
* workspace, or a link target) rather than a `node_modules/` slot, where that
|
|
314
|
+
* folder is not on disk. A renamed target leaves one behind (`targets/website`
|
|
315
|
+
* after the move to `targets/web`), and npm keeps honoring the dependencies it
|
|
316
|
+
* declares on every lock-only run.
|
|
317
|
+
*
|
|
318
|
+
* @param {object} options
|
|
319
|
+
* @param {string} options.root - The install root the lock's keys are relative to.
|
|
320
|
+
* @param {object} options.packages - The lock's `packages` map.
|
|
321
|
+
* @returns {string[]} The stale keys, nested ones included.
|
|
322
|
+
*/
|
|
323
|
+
function staleLockPaths({ root, packages }) {
|
|
324
|
+
return Object.keys(packages).filter((key) => key !== ''
|
|
325
|
+
&& !key.split('/').includes('node_modules')
|
|
326
|
+
&& !jetpack.exists(path.join(root, key)));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Every @omega.js registry spec in the brand's manifests (the root and each
|
|
331
|
+
* `targets/<name>/package.json`) that the brand's OWN lockfile does not
|
|
332
|
+
* satisfy. Read-only. `file:`/`link:`/git specs are the linked lane's, which
|
|
333
|
+
* regenerates its lock in the pack step, so only registry specs are read. A
|
|
334
|
+
* STALE lock entry (`staleLockPaths`) declaring an @omega.js spec that is NOT a
|
|
335
|
+
* registry one is drift too: npm would re-create the link it names on the next
|
|
336
|
+
* lock-only run.
|
|
337
|
+
*
|
|
338
|
+
* @param {object} options
|
|
339
|
+
* @param {string} options.root - The install root (a brand root, or a standalone target).
|
|
340
|
+
* @returns {{ lockPath: string, lock: object|null, drift: Array<{ manifest: string, name: string, spec: string, key: string|null, entry: object|undefined, reason: string }> }}
|
|
341
|
+
* `lock` is null when the root has none; `manifest` is the declaring
|
|
342
|
+
* manifest's dir relative to the root, `package.json` for the root itself.
|
|
343
|
+
*/
|
|
344
|
+
function brandLockfileDrift({ root }) {
|
|
345
|
+
const lockPath = path.join(root, 'package-lock.json');
|
|
346
|
+
const lock = jetpack.exists(lockPath) === 'file' ? jetpack.read(lockPath, 'json') : null;
|
|
347
|
+
const packages = (lock && lock.packages) || {};
|
|
348
|
+
|
|
349
|
+
const manifests = [
|
|
350
|
+
{ where: '', file: path.join(root, 'package.json') },
|
|
351
|
+
...listTargetPackages({ brandRoot: root }).map((target) => ({ where: `targets/${target.name}`, file: target.manifest })),
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
const drift = [];
|
|
355
|
+
|
|
356
|
+
for (const { where, file } of manifests) {
|
|
357
|
+
const pkg = jetpack.read(file, 'json') || {};
|
|
358
|
+
const declared = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
359
|
+
|
|
360
|
+
for (const [name, spec] of Object.entries(declared)) {
|
|
361
|
+
if (!name.startsWith(SCOPE) || !isRegistrySpec(spec)) continue;
|
|
362
|
+
|
|
363
|
+
const key = lockKeyFor(packages, where, name);
|
|
364
|
+
const entry = key ? packages[key] : undefined;
|
|
365
|
+
const reason = entryDrift(entry, spec);
|
|
366
|
+
|
|
367
|
+
if (reason) drift.push({ manifest: where || 'package.json', name, spec, key, entry, reason });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
for (const stale of staleLockPaths({ root, packages })) {
|
|
372
|
+
const declared = { ...(packages[stale].dependencies || {}), ...(packages[stale].devDependencies || {}) };
|
|
373
|
+
|
|
374
|
+
for (const [name, spec] of Object.entries(declared)) {
|
|
375
|
+
if (!name.startsWith(SCOPE) || isRegistrySpec(spec)) continue;
|
|
376
|
+
|
|
377
|
+
// No key: nothing here is the dependency's own entry, and the pruning
|
|
378
|
+
// of stale paths (lockfile.js) is what removes this one.
|
|
379
|
+
drift.push({ manifest: stale, name, spec, key: null, entry: undefined, reason: `is declared by a stale lock entry (${stale} is not on disk)` });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return { lockPath, lock, drift };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* The deploy gate on the registry lane: the brand's lockfile must exist and
|
|
388
|
+
* carry every @omega.js registry spec of its manifests as a registry entry at
|
|
389
|
+
* a version that satisfies it. Read-only, so a dry run runs it too. Agreement
|
|
390
|
+
* says nothing.
|
|
391
|
+
*
|
|
392
|
+
* @param {object} options
|
|
393
|
+
* @param {string} options.root - The brand root the lane snapshots.
|
|
394
|
+
* @returns {void}
|
|
395
|
+
* @throws {Error} A refusal naming each disagreeing entry and `omega i live`.
|
|
396
|
+
*/
|
|
397
|
+
function assertBrandLockfile({ root }) {
|
|
398
|
+
const { lockPath, lock, drift } = brandLockfileDrift({ root });
|
|
399
|
+
const fix = ' fix: run `omega i live` in the brand, which regenerates the brand\'s own lockfile from its manifests (#938)';
|
|
400
|
+
|
|
401
|
+
if (!lock) {
|
|
402
|
+
throw refusal(
|
|
403
|
+
`${root} has no package-lock.json, and the deploy runner installs with \`npm ci\`, which needs one.\n${fix}`,
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (drift.length) {
|
|
408
|
+
throw refusal(
|
|
409
|
+
`${lockPath} disagrees with the manifests, and the deploy runner's \`npm ci\` installs the lock:\n`
|
|
410
|
+
+ drift.map((item) => ` ${item.manifest}: ${item.name} ${item.spec} ${item.reason}\n`).join('')
|
|
411
|
+
+ fix,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
module.exports = {
|
|
417
|
+
readBrandVersion,
|
|
418
|
+
listTargetPackages,
|
|
419
|
+
bumpBrandVersion,
|
|
420
|
+
assertBrandVersion,
|
|
421
|
+
staleLockPaths,
|
|
422
|
+
brandLockfileDrift,
|
|
423
|
+
assertBrandLockfile,
|
|
424
|
+
KINDS,
|
|
425
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* build-json: the ONE composer and the ONE writer of `OMEGA_BUILD_JSON`
|
|
3
|
+
* ([#743](https://github.com/Omega-JS-Stack/omega/issues/743)).
|
|
4
|
+
*
|
|
5
|
+
* Every browser surface OMEGA builds (web pages and its service worker,
|
|
6
|
+
* desktop's renderer windows, every extension page and the extension's service
|
|
7
|
+
* worker) reads the same snapshot under the same name, so it is composed once
|
|
8
|
+
* here and delivered once: a single `build.js` at the artifact's web root that
|
|
9
|
+
* every shell loads with one script tag and every worker with one
|
|
10
|
+
* `importScripts` line. The banners and defines each framework grew for itself
|
|
11
|
+
* are gone with it (Ian 2026-09-12: "make it same shape and consumption
|
|
12
|
+
* everywhere as much as possible").
|
|
13
|
+
*
|
|
14
|
+
* Desktop's main and preload bundles are the one exception, and not to this
|
|
15
|
+
* file: they are NODE, they boot from the WHOLE resolved config inside the
|
|
16
|
+
* asar, and they keep carrying it in their own bundle.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const jetpack = require('fs-jetpack');
|
|
20
|
+
const { clientConfig } = require('../config/index.js');
|
|
21
|
+
|
|
22
|
+
// The wrapper's `mode`, one shape on every surface
|
|
23
|
+
// ([#894](https://github.com/Omega-JS-Stack/omega/issues/894)): the build's
|
|
24
|
+
// verdict (`environment`), whether it was a BUILD rather than a dev/watch run,
|
|
25
|
+
// and whether it publishes. A surface's own extra verdicts (desktop's
|
|
26
|
+
// `server`) stay inside that surface's Manager and never reach the artifact.
|
|
27
|
+
const MODE_KEYS = ['environment', 'build', 'publish'];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compose the snapshot a browser artifact carries.
|
|
31
|
+
* @param {object} input
|
|
32
|
+
* @param {object} input.config - the target's resolved omega.json5.
|
|
33
|
+
* @param {object} input.pkg - the project package (`{ name, version, … }`), the wrapper's `package`.
|
|
34
|
+
* @param {object} input.mode - `{ environment, build, publish }`.
|
|
35
|
+
* @param {object} input.license - the build's license stamp (#320).
|
|
36
|
+
* @param {object} input.facts - the build facts that ride on the config (`runtime`, `environment`, `version`, `buildTime`, `target`, `dev`).
|
|
37
|
+
* @returns {object} `{ config, package, mode, license, builtAt }`.
|
|
38
|
+
* @throws {Error} when `mode` is missing one of its three keys: a half-stated verdict is a build bug, never a default.
|
|
39
|
+
*/
|
|
40
|
+
function composeBuildJson({ config, pkg, mode, license, facts }) {
|
|
41
|
+
const missing = MODE_KEYS.filter((key) => !(mode && key in mode));
|
|
42
|
+
if (missing.length) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`[@omega.js/devkit] OMEGA_BUILD_JSON needs a mode carrying ${MODE_KEYS.join(', ')}, missing: ${missing.join(', ')}.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
config: clientConfig({ ...config, ...facts }),
|
|
50
|
+
package: pkg,
|
|
51
|
+
mode: { environment: mode.environment, build: mode.build, publish: mode.publish },
|
|
52
|
+
license,
|
|
53
|
+
builtAt: new Date().toISOString(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The text of the `build.js` file every surface writes.
|
|
59
|
+
*
|
|
60
|
+
* TWO statements, the same two everywhere. `self` is the one global name that
|
|
61
|
+
* answers in a window and in a worker alike, so there is no IIFE and no
|
|
62
|
+
* `globalThis` dance to read past. The `dev` map is its OWN statement because
|
|
63
|
+
* `omega dev` REWRITES that line per request
|
|
64
|
+
* ([#346](https://github.com/Omega-JS-Stack/omega/issues/346)), so the file on
|
|
65
|
+
* disk stays the build's own snapshot while a served page gets the map of the
|
|
66
|
+
* stack running right now. A build with no local stack writes `null`, the
|
|
67
|
+
* value @omega.js/client already reads as "assume the classics".
|
|
68
|
+
* @param {object} buildJson - composeBuildJson()'s answer.
|
|
69
|
+
* @returns {string} the file text.
|
|
70
|
+
*/
|
|
71
|
+
function buildJsSource(buildJson) {
|
|
72
|
+
const { dev = null, ...config } = buildJson.config || {};
|
|
73
|
+
|
|
74
|
+
return `self.OMEGA_BUILD_JSON = ${JSON.stringify({ ...buildJson, config })};\n`
|
|
75
|
+
+ `self.OMEGA_BUILD_JSON.config.dev = ${JSON.stringify(dev)};\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Write `<webRoot>/build.js`.
|
|
80
|
+
* @param {string} webRoot - the artifact's web root (what `/` resolves to for its pages).
|
|
81
|
+
* @param {object} buildJson - composeBuildJson()'s answer.
|
|
82
|
+
* @returns {string} the path written.
|
|
83
|
+
*/
|
|
84
|
+
function writeBuildJs(webRoot, buildJson) {
|
|
85
|
+
const file = jetpack.path(webRoot, 'build.js');
|
|
86
|
+
jetpack.write(file, buildJsSource(buildJson));
|
|
87
|
+
|
|
88
|
+
return file;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { composeBuildJson, buildJsSource, writeBuildJs, MODE_KEYS };
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* - the `@dev-only` strip (#18), registered for PRODUCTION builds only
|
|
14
14
|
* - minify / sourcemap by mode, and `bundle` + `metafile` on
|
|
15
15
|
* - one timing line per build, watch rebuilds included
|
|
16
|
+
* - `import.meta.url`, kept answering in CommonJS output (#906), so an
|
|
17
|
+
* ESM-only dependency runs bundled
|
|
16
18
|
*
|
|
17
19
|
* esbuild is REQUIRED LAZILY, from the CALLING FRAMEWORK's installation rather
|
|
18
20
|
* than devkit's. Devkit is vendored into each framework's dist at prepare time
|
|
@@ -52,6 +54,24 @@ const RESERVED_OPTIONS = ['bundle', 'metafile', 'minify', 'sourcemap', 'plugins'
|
|
|
52
54
|
// when it tries to shut down.
|
|
53
55
|
const MODES = ['build', 'watch'];
|
|
54
56
|
|
|
57
|
+
// The `import.meta.url` a CommonJS bundle keeps
|
|
58
|
+
// ([#906](https://github.com/Omega-JS-Stack/omega/issues/906)). esbuild rewrites
|
|
59
|
+
// `import.meta` to `{}` in `format: 'cjs'` output (its documented limitation:
|
|
60
|
+
// `import.meta` only exists in an ES module), so an ESM-only dependency that
|
|
61
|
+
// opens a require of its own with `createRequire(import.meta.url)`, which
|
|
62
|
+
// yargs-parser and a growing share of the registry do, is handed `undefined`
|
|
63
|
+
// and throws ERR_INVALID_ARG_VALUE the moment the app boots. Node's own answer
|
|
64
|
+
// for a CommonJS file is `pathToFileURL(__filename)`, so that is what the
|
|
65
|
+
// reference becomes.
|
|
66
|
+
//
|
|
67
|
+
// It is a define onto an IDENTIFIER plus a banner declaring it, rather than one
|
|
68
|
+
// define carrying the expression, because esbuild refuses an expression as a
|
|
69
|
+
// define value ("must be an entity name or JS literal"). The banner rides every
|
|
70
|
+
// emitted file, so each answers with its OWN path.
|
|
71
|
+
const IMPORT_META_URL = 'import.meta.url';
|
|
72
|
+
const IMPORT_META_URL_NAME = '__omegaImportMetaUrl';
|
|
73
|
+
const IMPORT_META_URL_SHIM = `var ${IMPORT_META_URL_NAME} = require('url').pathToFileURL(__filename).href;`;
|
|
74
|
+
|
|
55
75
|
/**
|
|
56
76
|
* esbuild as the CALLING FRAMEWORK resolves it (see the header).
|
|
57
77
|
* @param {string} frameworkRoot - the framework package's root (the dir holding its package.json)
|
|
@@ -130,6 +150,32 @@ function timingPlugin(destination, entryCount, watching) {
|
|
|
130
150
|
};
|
|
131
151
|
}
|
|
132
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Keep `import.meta.url` answering in a CommonJS bundle (see IMPORT_META_URL).
|
|
155
|
+
* Composed from the OUTPUT SHAPE rather than wired per caller: every cjs+node
|
|
156
|
+
* build has the hole, and no other output format has it (a browser bundle is
|
|
157
|
+
* iife or esm, and esm keeps `import.meta` natively).
|
|
158
|
+
* @param {object} config - the composed esbuild options, edited in place
|
|
159
|
+
* @throws {Error} when the caller defines the same key, the RESERVED_OPTIONS rule:
|
|
160
|
+
* two answers for one reference is a programmer error, not a merge
|
|
161
|
+
*/
|
|
162
|
+
function keepImportMetaUrl(config) {
|
|
163
|
+
if (config.format !== 'cjs' || config.platform !== 'node') return;
|
|
164
|
+
|
|
165
|
+
if (config.define && IMPORT_META_URL in config.define) {
|
|
166
|
+
throw new Error(`[devkit bundle] \`${IMPORT_META_URL}\` is composed by bundle() for CommonJS output and cannot be defined by a caller: a cjs+node build always gets \`pathToFileURL(__filename)\`, so a second answer would silently win or lose depending on merge order`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
config.define = { ...config.define, [IMPORT_META_URL]: IMPORT_META_URL_NAME };
|
|
170
|
+
// The shim is declared FIRST and the caller's banner rides below it,
|
|
171
|
+
// unchanged: anything the banner itself does could read the reference, and a
|
|
172
|
+
// declaration has to come before its readers.
|
|
173
|
+
config.banner = {
|
|
174
|
+
...config.banner,
|
|
175
|
+
js: [IMPORT_META_URL_SHIM, config.banner && config.banner.js].filter(Boolean).join('\n'),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
133
179
|
/**
|
|
134
180
|
* One esbuild message as a line: what, and where.
|
|
135
181
|
* @param {object} message - an esbuild warning or error
|
|
@@ -248,6 +294,8 @@ async function bundle(options) {
|
|
|
248
294
|
if (value != null) config[key] = value;
|
|
249
295
|
}
|
|
250
296
|
|
|
297
|
+
keepImportMetaUrl(config);
|
|
298
|
+
|
|
251
299
|
if (mode !== 'watch') {
|
|
252
300
|
return esbuild.build(config);
|
|
253
301
|
}
|