@omega.js/desktop 0.50.0 → 0.51.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.
Files changed (267) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/css/tokens/_index.scss +1 -1
  3. package/dist/assets/themes/base/_includes/frontend/sections/account-section-header.html +4 -1
  4. package/dist/assets/themes/base/_includes/frontend/sections/footer.html +13 -6
  5. package/dist/assets/themes/base/_includes/frontend/sections/nav.html +10 -8
  6. package/dist/assets/themes/base/_includes/global/sections/account.html +3 -1
  7. package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +12 -8
  8. package/dist/assets/themes/base/_includes/global/sections/app-topbar.html +10 -6
  9. package/dist/assets/themes/base/_includes/global/sections/page-header.html +8 -4
  10. package/dist/assets/themes/base/_layouts/backend/pages/dashboard/index.html +24 -24
  11. package/dist/assets/themes/base/_layouts/frontend/pages/about.html +10 -10
  12. package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +35 -35
  13. package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +4 -4
  14. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signin.html +1 -1
  15. package/dist/assets/themes/base/_layouts/frontend/pages/auth/signup.html +4 -4
  16. package/dist/assets/themes/base/_layouts/frontend/pages/contact.html +3 -3
  17. package/dist/assets/themes/base/_layouts/frontend/pages/download.html +34 -33
  18. package/dist/assets/themes/base/_layouts/frontend/pages/extension/index.html +11 -11
  19. package/dist/assets/themes/base/_layouts/frontend/pages/legal/document.html +1 -1
  20. package/dist/assets/themes/base/_layouts/frontend/pages/status.html +1 -1
  21. package/dist/assets/themes/base/_layouts/frontend/pages/team/index.html +9 -7
  22. package/dist/assets/themes/base/_layouts/frontend/pages/team/member.html +5 -3
  23. package/dist/assets/themes/base/_sections/about/letter/section.html +1 -1
  24. package/dist/assets/themes/base/_sections/about/letter/section.json5 +4 -4
  25. package/dist/assets/themes/base/_sections/marketing/bento/section.html +1 -1
  26. package/dist/assets/themes/base/_sections/marketing/bento/section.json5 +15 -15
  27. package/dist/assets/themes/base/_sections/marketing/cta/section.json5 +1 -1
  28. package/dist/assets/themes/base/_sections/marketing/hero/section.html +6 -6
  29. package/dist/assets/themes/base/_sections/marketing/hero/section.json5 +10 -10
  30. package/dist/assets/themes/base/_sections/marketing/product-demo/section.html +1 -1
  31. package/dist/assets/themes/base/_sections/marketing/product-demo/section.json5 +2 -2
  32. package/dist/assets/themes/base/_sections/marketing/stats/section.html +1 -1
  33. package/dist/assets/themes/base/_sections/marketing/stats/section.json5 +5 -5
  34. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.html +1 -1
  35. package/dist/assets/themes/base/_sections/marketing/trusted-by/section.json5 +2 -2
  36. package/dist/assets/themes/neobrutalism/_layouts/frontend/pages/index.html +10 -10
  37. package/dist/assets/themes/newsflash/_layouts/frontend/pages/index.html +10 -10
  38. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.html +2 -2
  39. package/dist/assets/themes/newsflash/_sections/marketing/desks/section.json5 +4 -4
  40. package/dist/build.js +69 -28
  41. package/dist/cli-run.js +20 -13
  42. package/dist/cli.js +12 -7
  43. package/dist/commands/build.js +0 -2
  44. package/dist/commands/deploy.js +116 -44
  45. package/dist/commands/finalize-release.js +5 -4
  46. package/dist/commands/launch.js +12 -7
  47. package/dist/commands/lib/deploy-precheck.js +66 -34
  48. package/dist/commands/lib/ensure-target.js +71 -11
  49. package/dist/commands/package.js +0 -1
  50. package/dist/commands/publish.js +11 -4
  51. package/dist/commands/release.js +99 -252
  52. package/dist/commands/runner.js +42 -5
  53. package/dist/commands/sign-windows.js +46 -15
  54. package/dist/commands/test.js +4 -3
  55. package/dist/commands/validate-certs.js +291 -115
  56. package/dist/config/page-template.html +4 -0
  57. package/dist/defaults/.github/workflows/build.yml +144 -64
  58. package/dist/defaults/AGENTS.md +3 -2
  59. package/dist/defaults/_.gitignore +4 -2
  60. package/dist/defaults/config/certs/README.md +25 -45
  61. package/dist/defaults/config/omega.json5 +66 -32
  62. package/dist/defaults/hooks/deploy/pre.js +10 -0
  63. package/dist/defaults/src/assets/scss/main.scss +12 -2
  64. package/dist/defaults/src/integrations/tray/index.js +1 -1
  65. package/dist/gulp/main.js +13 -25
  66. package/dist/gulp/tasks/audit.js +18 -6
  67. package/dist/gulp/tasks/build-config.js +181 -60
  68. package/dist/gulp/tasks/bundle.js +108 -36
  69. package/dist/gulp/tasks/release.js +86 -4
  70. package/dist/gulp/tasks/sass.js +11 -0
  71. package/dist/hooks/lib/notarize-tools.js +137 -0
  72. package/dist/hooks/notarize-artifacts.js +57 -0
  73. package/dist/hooks/notarize.js +59 -8
  74. package/dist/lib/auth-persistence.js +25 -0
  75. package/dist/lib/client-bridge.js +11 -8
  76. package/dist/lib/deep-link.js +15 -8
  77. package/dist/lib/protocol.js +7 -1
  78. package/dist/lib/restart-manager/install.js +6 -3
  79. package/dist/lib/sign-helpers/auto-unlock.js +65 -27
  80. package/dist/lib/sign-helpers/console-lock.js +34 -0
  81. package/dist/lib/sign-helpers/exec-with-limit.js +68 -0
  82. package/dist/lib/sign-helpers/resolve-icons.js +6 -4
  83. package/dist/lib/tray.js +7 -6
  84. package/dist/main.js +16 -0
  85. package/dist/preload.js +8 -0
  86. package/dist/renderer.js +16 -4
  87. package/dist/runner/job-started.js +104 -0
  88. package/dist/test/fixtures/consumer-app/config/omega.json5 +7 -0
  89. package/dist/test/fixtures/consumer-app/package.json +1 -1
  90. package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +7 -1
  91. package/dist/test/harness/main-entry.js +2 -1
  92. package/dist/test/harness/renderer-preload.js +12 -3
  93. package/dist/test/runners/boot.js +101 -11
  94. package/dist/test/runners/electron.js +1 -1
  95. package/dist/test/suites/boot/consumer-app-boots.test.js +54 -0
  96. package/dist/test/suites/build/audit.test.js +45 -6
  97. package/dist/test/suites/build/auth-persistence-resolve.test.js +119 -0
  98. package/dist/test/suites/build/auto-unlock.test.js +105 -0
  99. package/dist/test/suites/build/boot-runner-timeout.test.js +253 -0
  100. package/dist/test/suites/build/brand-scss.test.js +106 -0
  101. package/dist/test/suites/build/build-config.test.js +146 -36
  102. package/dist/test/suites/build/build-json-bake.test.js +245 -0
  103. package/dist/test/suites/build/build-verbs.test.js +48 -15
  104. package/dist/test/suites/build/build-workflow-jobs.test.js +190 -0
  105. package/dist/test/suites/build/cli.test.js +32 -10
  106. package/dist/test/suites/build/config-schema.test.js +4 -4
  107. package/dist/test/suites/build/console-lock.test.js +51 -0
  108. package/dist/test/suites/build/defaults-scaffold.test.js +71 -2
  109. package/dist/test/suites/build/deploy-direct.test.js +241 -0
  110. package/dist/test/suites/build/deploy-dispatch.test.js +287 -0
  111. package/dist/test/suites/build/deploy-hook.test.js +169 -0
  112. package/dist/test/suites/build/ensure-target.test.js +14 -2
  113. package/dist/test/suites/build/env-delivery.test.js +19 -10
  114. package/dist/test/suites/build/env-watch.test.js +18 -7
  115. package/dist/test/suites/build/esm-only-dependency.test.js +127 -0
  116. package/dist/test/suites/build/exec-with-limit.test.js +53 -0
  117. package/dist/test/suites/build/finalize-release.test.js +2 -2
  118. package/dist/test/suites/build/get-config.test.js +120 -8
  119. package/dist/test/suites/build/github-utils.test.js +12 -6
  120. package/dist/test/suites/build/license-stamp.test.js +6 -4
  121. package/dist/test/suites/build/manager.test.js +88 -68
  122. package/dist/test/suites/build/manifest-deps.test.js +116 -0
  123. package/dist/test/suites/build/merge-line-files.test.js +27 -4
  124. package/dist/test/suites/build/notarize-artifacts.test.js +135 -0
  125. package/dist/test/suites/build/notarize-tools.test.js +38 -0
  126. package/dist/test/suites/build/notarize.test.js +207 -0
  127. package/dist/test/suites/build/release-pipeline.test.js +38 -60
  128. package/dist/test/suites/build/release-skipped-upload.test.js +107 -0
  129. package/dist/test/suites/build/resolve-icons.test.js +35 -35
  130. package/dist/test/suites/build/runner-job-guard.test.js +182 -0
  131. package/dist/test/suites/build/runner.test.js +25 -2
  132. package/dist/test/suites/build/sentry.test.js +8 -3
  133. package/dist/test/suites/build/setup-scripts.test.js +3 -0
  134. package/dist/test/suites/build/sign-windows.test.js +92 -8
  135. package/dist/test/suites/build/test-stealth.test.js +17 -11
  136. package/dist/test/suites/build/url-helpers.test.js +37 -17
  137. package/dist/test/suites/build/validate-certs.test.js +428 -55
  138. package/dist/test/suites/build/validate-config.test.js +3 -3
  139. package/dist/test/suites/main/auth-flow.test.js +12 -0
  140. package/dist/test/suites/main/auth-persistence.test.js +14 -17
  141. package/dist/test/suites/main/auto-updater.test.js +2 -2
  142. package/dist/test/suites/main/boot-sequence.test.js +1 -1
  143. package/dist/test/suites/main/client-bridge.integration.test.js +5 -82
  144. package/dist/test/suites/main/client-bridge.test.js +19 -6
  145. package/dist/test/suites/main/deep-link.test.js +54 -0
  146. package/dist/test/suites/main/startup-paths-and-ua.test.js +1 -1
  147. package/dist/test/suites/main/url-helpers.test.js +81 -72
  148. package/dist/test/suites/renderer/cross-context-helpers.test.js +19 -16
  149. package/dist/utils/build-pipeline.js +10 -10
  150. package/dist/utils/github.js +12 -51
  151. package/dist/utils/load-env.js +66 -0
  152. package/dist/utils/mode-helpers.js +43 -111
  153. package/dist/utils/platform.js +37 -0
  154. package/dist/utils/runner-env.js +2 -1
  155. package/dist/utils/runner-job-guard.js +149 -0
  156. package/dist/utils/ship-keys.js +52 -0
  157. package/dist/utils/test-stealth.js +5 -3
  158. package/dist/utils/url-helpers.js +33 -17
  159. package/dist/vendor/config/bundle-id.js +53 -0
  160. package/dist/vendor/config/client-config.js +141 -0
  161. package/dist/vendor/config/company.js +334 -15
  162. package/dist/vendor/config/dev-facts.js +48 -0
  163. package/dist/vendor/config/env-delivery.js +231 -9
  164. package/dist/vendor/config/env-retired.js +137 -0
  165. package/dist/vendor/config/env-rules.js +22 -3
  166. package/dist/vendor/config/env-schema.js +234 -117
  167. package/dist/vendor/config/env.js +55 -26
  168. package/dist/vendor/config/environment.js +189 -0
  169. package/dist/vendor/config/hooks.js +13 -11
  170. package/dist/vendor/config/index.js +121 -44
  171. package/dist/vendor/config/load.js +366 -115
  172. package/dist/vendor/config/merge.js +2 -2
  173. package/dist/vendor/config/order.js +3 -3
  174. package/dist/vendor/config/platforms.js +276 -0
  175. package/dist/vendor/config/repo.js +226 -104
  176. package/dist/vendor/config/retired-keys.js +232 -27
  177. package/dist/vendor/config/schema.js +525 -99
  178. package/dist/vendor/config/site-global.js +63 -53
  179. package/dist/vendor/config/targets.js +187 -0
  180. package/dist/vendor/config/validate.js +119 -59
  181. package/dist/vendor/devkit/argv.js +118 -0
  182. package/dist/vendor/devkit/attach-log-file.js +21 -13
  183. package/dist/vendor/devkit/brand-tokens.js +278 -0
  184. package/dist/vendor/devkit/brand-version.js +264 -0
  185. package/dist/vendor/devkit/build-json.js +91 -0
  186. package/dist/vendor/devkit/bundle.js +48 -0
  187. package/dist/vendor/devkit/certificate-expiry.js +108 -0
  188. package/dist/vendor/devkit/certs.js +16 -194
  189. package/dist/vendor/devkit/ci-workflows.js +124 -8
  190. package/dist/vendor/devkit/cli-router.js +3 -3
  191. package/dist/vendor/devkit/defaults-engine.js +69 -7
  192. package/dist/vendor/devkit/deploy-follow.js +297 -0
  193. package/dist/vendor/devkit/deploy-precheck.js +23 -8
  194. package/dist/vendor/devkit/deploy-record.js +11 -27
  195. package/dist/vendor/devkit/deploy-snapshot.js +661 -0
  196. package/dist/vendor/devkit/deploy.js +445 -75
  197. package/dist/vendor/devkit/git-auth.js +73 -0
  198. package/dist/vendor/devkit/git-remote.js +95 -0
  199. package/dist/vendor/devkit/github-repo.js +290 -0
  200. package/dist/vendor/devkit/local.js +47 -0
  201. package/dist/vendor/devkit/merge-line-files.js +23 -16
  202. package/dist/vendor/devkit/omega-bin.js +18 -3
  203. package/dist/vendor/devkit/pack-local.js +391 -0
  204. package/dist/vendor/devkit/preludes/index.js +120 -0
  205. package/dist/vendor/devkit/preludes/origin-heal.js +156 -0
  206. package/dist/vendor/devkit/service-account.js +43 -0
  207. package/dist/vendor/devkit/ship-plan.js +112 -0
  208. package/dist/vendor/devkit/signing-env.js +180 -0
  209. package/dist/vendor/devkit/signing-tree.js +92 -0
  210. package/dist/vendor/devkit/target-seams.js +142 -0
  211. package/dist/vendor/devkit/target-secrets.js +235 -50
  212. package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
  213. package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
  214. package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
  215. package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
  216. package/dist/vendor/monitoring/env.js +20 -10
  217. package/dist/vendor/monitoring/main.js +1 -1
  218. package/dist/vendor/monitoring/preload.js +1 -1
  219. package/dist/vendor/monitoring/renderer.js +1 -1
  220. package/docs/analytics.md +1 -1
  221. package/docs/auto-updater.md +5 -5
  222. package/docs/boot-sequence.md +1 -1
  223. package/docs/build-system.md +15 -7
  224. package/docs/client-bridge.md +9 -7
  225. package/docs/config-schema.md +4 -4
  226. package/docs/css.md +8 -2
  227. package/docs/deep-link.md +12 -4
  228. package/docs/environment-detection.md +32 -24
  229. package/docs/hooks.md +3 -1
  230. package/docs/icons.md +7 -7
  231. package/docs/index.md +61 -23
  232. package/docs/installer-options.md +24 -21
  233. package/docs/logging.md +5 -5
  234. package/docs/releasing.md +25 -17
  235. package/docs/runner.md +40 -5
  236. package/docs/shared/brands.md +12 -6
  237. package/docs/shared/breaking-changes.md +375 -21
  238. package/docs/shared/config.md +757 -199
  239. package/docs/shared/deploys.md +194 -91
  240. package/docs/shared/icons.md +18 -0
  241. package/docs/shared/local-dev.md +24 -6
  242. package/docs/shared/logging.md +9 -6
  243. package/docs/shared/monitoring.md +27 -13
  244. package/docs/shared/publishing.md +3 -3
  245. package/docs/shared/rulings.md +2 -2
  246. package/docs/shared/testing.md +1 -1
  247. package/docs/shared/theming.md +26 -1
  248. package/docs/shared/translation.md +49 -7
  249. package/docs/shared/updates.md +1 -1
  250. package/docs/signing.md +59 -33
  251. package/docs/test-framework.md +10 -5
  252. package/docs/themes.md +15 -1
  253. package/package.json +18 -12
  254. package/bin/omega-desktop +0 -2
  255. package/dist/commands/push-secrets.js +0 -141
  256. package/dist/test/suites/build/deliver-certs.test.js +0 -95
  257. package/dist/test/suites/build/derive-signing-env.test.js +0 -122
  258. package/dist/test/suites/build/push-secrets.test.js +0 -226
  259. package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
  260. package/dist/utils/deliver-certs.js +0 -69
  261. package/dist/utils/derive-signing-env.js +0 -56
  262. package/dist/utils/resolve-signing-cert.js +0 -175
  263. package/dist/vendor/config/desktop-artifacts.js +0 -110
  264. package/dist/vendor/config/instances.js +0 -208
  265. /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
  266. /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
  267. /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
package/dist/build.js CHANGED
@@ -1,17 +1,11 @@
1
1
  // Libraries
2
2
  const path = require('path');
3
3
  const jetpack = require('fs-jetpack');
4
+ const { setEnvironment, buildLaneEnvironment } = require('./vendor/config/environment.js');
4
5
  const fs = require('fs');
5
6
  const JSON5 = require('json5');
6
7
  const { force, execute } = require('node-powertools');
7
-
8
- // argv is parsed lazily — yargs is ESM-only and can't be `require()`'d in older Node runtimes.
9
- let _argv = null;
10
- function getArgv() {
11
- if (_argv) return _argv;
12
- _argv = require('yargs')(process.argv.slice(2)).parseSync();
13
- return _argv;
14
- }
8
+ const { parseArgv } = require('./vendor/devkit/argv.js');
15
9
 
16
10
  // Class
17
11
  function Manager() {
@@ -44,11 +38,11 @@ Manager.prototype.logger = function (name) {
44
38
  return this._logger;
45
39
  };
46
40
 
47
- // argv (yargs is loaded on first call so the runtime build.js doesn't pull in ESM-only deps)
41
+ // argv: the gulp lane's own parse. It is spawned with the TASK NAME alone
42
+ // (`npm run gulp -- build`), so `debug` is the one flag it declares.
48
43
  Manager.getArguments = function () {
49
- const options = getArgv() || {};
44
+ const options = parseArgv(process.argv.slice(2), { booleans: ['debug'] });
50
45
 
51
- options._ = options._ || [];
52
46
  options.debug = force(options.debug === undefined ? false : options.debug, 'boolean');
53
47
 
54
48
  return options;
@@ -92,12 +86,29 @@ Manager.actLikeProduction = function () {
92
86
  };
93
87
  Manager.prototype.actLikeProduction = Manager.actLikeProduction;
94
88
 
95
- // getEnvironment() is the SINGLE SOURCE OF TRUTH and lives in src/utils/mode-helpers.js
96
- // (alongside isDevelopment/isProduction/isTesting — the natural environment-helper family).
97
- // It's mixed into ALL FOUR @omega.js/desktop Manager entry points (main / renderer / preload / build) via
98
- // the mode-helpers attachTo() call at the bottom of each, so every context resolves the
99
- // environment identically. (@omega.js/desktop has four separate Manager constructors that share code only
100
- // through these mixins — unlike UJM/BXM, where one build.js Manager serves every context.)
89
+ // The environment is the ONE module's (@omega.js/config's environment.js,
90
+ // [#817](https://github.com/Omega-JS-Stack/omega/issues/817)), reachable from
91
+ // all four @omega.js/desktop Manager entry points (main / renderer / preload /
92
+ // build) through the mode-helpers attachTo() call at the bottom of each, so
93
+ // every context resolves it identically. It reads ONE input and never guesses.
94
+ //
95
+ // THIS FILE IS THE NODE LANE'S ONE SETTER, and it runs at load, before any
96
+ // gulp task, verb or getConfig() asks. Two rules, no sniffing:
97
+ // 1. OMEGA_BUILD_MODE is the lane saying it is producing a PRODUCTION
98
+ // artifact (`omega build` / `package` / `publish` / `release` all set it,
99
+ // and so does the boot runner's staged build). It WINS over an inherited
100
+ // variable, so a production build spawned from a test run still bakes
101
+ // production.
102
+ // 2. Otherwise a lane that already named one keeps it (the test runners spawn
103
+ // their children with `testing`), and a bare dev boot is `development`.
104
+ // Before #817 a dev boot with no signal answered `production` here, so
105
+ // `npm start` bundled itself as a production artifact, while the extension's
106
+ // copy of the same function answered `development` from the same inputs.
107
+ //
108
+ // The electron app this lane spawns inherits the variable; a PACKAGED app has
109
+ // no parent env, so main.js sets it from the baked config instead (the same
110
+ // word, written into the artifact by the bundle task).
111
+ setEnvironment(buildLaneEnvironment(Manager.isBuildMode()));
101
112
 
102
113
  Manager.getMode = function () {
103
114
  return {
@@ -114,28 +125,58 @@ Manager.prototype.getMode = Manager.getMode;
114
125
  // theme) at the top level, targets.desktop overlaid onto them (so app/platforms/startup/
115
126
  // releases/... land at the top level here), and in a brand monorepo the brand root's
116
127
  // config merges underneath the target's. Then @omega.js/desktop's derived defaults:
117
- // app.appId ← reverse-domain of brand.url (`https://foo.example.com` →
118
- // `com.example.foo`), else `app.${brand.id}` — the BRAND
119
- // owns the identity, never a hardcoded company (friction #18)
128
+ // app.appId ← certificates.providers.apple.bundleIdPrefix + brand.id,
129
+ // dashes as dots (`com.example` + `my-app` →
130
+ // `com.example.my.app`), the same id the
131
+ // certificates service registers (#909). No prefix
132
+ // declared → reverse-domain of brand.url
133
+ // (`https://foo.example.com` → `com.example.foo`), else
134
+ // `app.${brand.id}`: the BRAND owns the identity, never a
135
+ // hardcoded company (friction #18)
120
136
  // app.productName ← brand.name if not set
121
137
  // These keep the consumer's config minimal: setting `brand: { id: 'foo', name: 'Foo',
122
138
  // url: 'https://foo.com' }` is enough; appId/productName flow through automatically.
123
139
  Manager.getConfig = function () {
124
- const { hasOmegaConfig, loadConfig } = require('./vendor/config/index.js');
140
+ const { hasOmegaConfig, loadConfig, composeBundleId, deriveBundleIdPrefix } = require('./vendor/config/index.js');
141
+
142
+ // WHICH environment overlay composes
143
+ // ([#856](https://github.com/Omega-JS-Stack/omega/issues/856)): a build bakes
144
+ // a PRODUCTION artifact, so it names production rather than asking the
145
+ // machine, which answers `development` in a terminal. That decision is now
146
+ // made ONCE, at the top of this file, and lands in the one input (#817) that
147
+ // every other read in the process answers from, so the overlay that composes
148
+ // and the environment the artifact records can never be two different words.
149
+ // Same rule, same shape, in @omega.js/extension's getConfig.
150
+ const environment = Manager.getEnvironment();
125
151
 
126
152
  // No config at all (fresh dir, non-consumer cwd) → seeded empty shape below; the
127
153
  // schema validation in audit/boot reports what's actually missing.
128
154
  const cwd = process.cwd();
129
- const config = hasOmegaConfig(cwd) ? loadConfig(cwd, 'desktop').config : {};
155
+ const config = hasOmegaConfig(cwd) ? loadConfig(cwd, 'desktop', { environment }).config : {};
156
+
157
+ // The environment rides ON the resolved config as the build fact every OMEGA
158
+ // surface spells the same way ([#896](https://github.com/Omega-JS-Stack/omega/issues/896)),
159
+ // so a dev boot loading config off disk hands main the same key a packaged
160
+ // app reads out of OMEGA_BUILD_JSON.
161
+ config.environment = environment;
130
162
 
131
163
  // Apply derived defaults. Always seed `brand` + `app` so callers can deref
132
164
  // `config.brand.X` / `config.app.X` without optional-chaining at every callsite.
133
165
  config.brand = config.brand || {};
134
166
  config.app = config.app || {};
135
167
  if (!config.app.appId) {
136
- const host = String(config.brand.url || '').replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0];
137
- if (host) {
138
- config.app.appId = host.split('.').reverse().join('.');
168
+ // The bundle-id policy is @omega.js/config's ONE derivation (#909): a
169
+ // config carrying an Apple prefix (its own, or the company layer's) signs
170
+ // under the very id the certificates service registers. The URL host is
171
+ // the fallback for a brand that declares no prefix, and a brand whose url
172
+ // is a code-host repo is why it cannot be the rule.
173
+ const prefix = config.certificates?.providers?.apple?.bundleIdPrefix;
174
+ const derived = deriveBundleIdPrefix(config.brand.url || '');
175
+
176
+ if (prefix && config.brand.id) {
177
+ config.app.appId = composeBundleId(prefix, config.brand.id);
178
+ } else if (derived) {
179
+ config.app.appId = derived;
139
180
  } else if (config.brand.id) {
140
181
  config.app.appId = `app.${config.brand.id}`;
141
182
  }
@@ -178,11 +219,11 @@ Manager.getLiveReloadPort = function () {
178
219
  };
179
220
  Manager.prototype.getLiveReloadPort = Manager.getLiveReloadPort;
180
221
 
181
- // Windows signing strategy. Config-only — `platforms.win.signing.strategy`
182
- // (targets.desktop.platforms.win in the raw omega.json5). Default 'self-hosted'.
222
+ // Windows signing strategy. Config-only: `platforms.windows.signing.strategy`
223
+ // (targets.desktop.platforms.windows in the raw omega.json5). Default 'self-hosted'.
183
224
  Manager.getWindowsSignStrategy = function () {
184
225
  const config = Manager.getConfig();
185
- return config?.platforms?.win?.signing?.strategy || 'self-hosted';
226
+ return config?.platforms?.windows?.signing?.strategy || 'self-hosted';
186
227
  };
187
228
  Manager.prototype.getWindowsSignStrategy = Manager.getWindowsSignStrategy;
188
229
 
package/dist/cli-run.js CHANGED
@@ -1,27 +1,34 @@
1
1
  /**
2
- * run() — the omega-desktop bin body, shared by bin/omega-desktop (via
3
- * dist/omega-bin.js) and cross-framework dispatch ('@omega.js/desktop/cli').
2
+ * run(): the CLI body, shared by bin/omega (via dist/omega-bin.js, the
3
+ * dispatcher) and cross-framework dispatch ('@omega.js/desktop/cli').
4
4
  */
5
+ // The value-LESS flags, the only list a CLI owes the parse: every other flag
6
+ // takes the next token as its value, which is what carries CI's own
7
+ // `sign-windows --in release --out release/signed`. Mirrors the same fix in
8
+ // @omega.js/backend's CLI.
9
+ const BOOLEAN_FLAGS = [
10
+ 'extended', 'local', 'quick', 'q', 'direct', 'dry-run', 'secrets',
11
+ 'smoke', 'verify-only', 'find-window-id', 'publish', 'open', 'tail', 'f', 'strict',
12
+ 'apply', 'major', 'force-fresh',
13
+ ];
14
+
5
15
  async function run() {
6
16
  // Local-dist freshness guard: a stale locally-linked dist rebuilds and the
7
17
  // invocation re-execs once, so no command ever runs stale framework code
8
18
  require('./vendor/devkit/local.js').freshnessBoot({ packageName: '@omega.js/desktop' });
9
19
 
20
+ // Boot preludes: the cheap, non-interactive checks every verb passes through,
21
+ // filtered by the verb about to run (#890)
22
+ require('./vendor/devkit/preludes/index.js').runPreludes({ verb: process.argv[2], targetDir: process.cwd() });
23
+
10
24
  // Strip ELECTRON_RUN_AS_NODE — it leaks into our env from common parent processes
11
25
  // (e.g. VS Code's Claude Code extension) and makes every Electron binary we shell
12
26
  // to silently run as plain Node. Strip once at the CLI boundary.
13
27
  delete process.env.ELECTRON_RUN_AS_NODE;
14
28
 
15
- // Value-less flags must be declared boolean — otherwise yargs treats the next
16
- // positional as the flag's VALUE. Mirrors the same fix in @omega.js/backend's CLI.
17
- // yargs' built-in --version/--help are disabled so both route through the
18
- // alias table / the router's built-in help (the built-ins printed an empty
19
- // stub and version "0.0.0" — yargs can't resolve our package version here).
20
- const argv = require('yargs')(process.argv.slice(2))
21
- .boolean(['extended', 'local', 'quick', 'q'])
22
- .version(false)
23
- .help(false)
24
- .parseSync();
29
+ // The router owns --help/--version, so both route through the alias table
30
+ // and the router's built-in help.
31
+ const argv = require('./vendor/devkit/argv.js').parseArgv(process.argv.slice(2), { booleans: BOOLEAN_FLAGS });
25
32
  const cli = new (require('./cli.js'))(argv);
26
33
 
27
34
  try {
@@ -31,4 +38,4 @@ async function run() {
31
38
  }
32
39
  }
33
40
 
34
- module.exports = { run };
41
+ module.exports = { run, BOOLEAN_FLAGS };
package/dist/cli.js CHANGED
@@ -4,24 +4,30 @@ const { createCliRouter } = require('./vendor/devkit/cli-router.js');
4
4
  const { isBoxVerbArgv } = require('./vendor/devkit/omega-bin.js');
5
5
 
6
6
  // Resolve the .env cascade from the project root
7
- // (shell > local .env > brand .env > company .env). The target name delivers
8
- // the schema's `deliverAs` renames into process.env (#678) — the brand's
9
- // GOOGLE_ANALYTICS_SECRET_DESKTOP arrives as GOOGLE_ANALYTICS_SECRET.
7
+ // (shell > local .env > brand .env > company .env), then derive the signing
8
+ // paths the brand's signing tree implies. The target name delivers the schema's
9
+ // `deliverAs` renames into process.env (#678): the brand's
10
+ // GOOGLE_ANALYTICS_SECRET_DESKTOP arrives as GOOGLE_ANALYTICS_SECRET. ONE
11
+ // wrapper does both, here and in gulp ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)),
12
+ // so every verb after it reads the same CSC_LINK / APPLE_API_KEY answer.
10
13
  //
11
- // …for every verb but the signing box's. `runner` and `sign-windows` belong to
14
+ // ...for every verb but the signing box's. `runner` and `sign-windows` belong to
12
15
  // a MACHINE, not a project: they read the shell and `<runner home>\.env` and
13
16
  // nothing else, so a brand's GH_TOKEN can never register the box's runners or
14
17
  // hand its release token to a config walk ([#337](https://github.com/Omega-JS-Stack/omega/issues/337)).
15
18
  // Which invocations those are is the dispatcher's own answer, imported: one
16
19
  // reading of it, covering the bare verb and its `--` flag spelling alike.
17
20
  if (!isBoxVerbArgv(process.argv.slice(2))) {
18
- require('./vendor/config/index.js').loadEnv(process.cwd(), { target: 'desktop' });
21
+ require('./utils/load-env.js').loadDesktopEnv(process.cwd(), {
22
+ logger: new (require('./lib/logger.js'))('env'),
23
+ });
19
24
  }
20
25
 
21
26
  // Main class — dispatch (positional/flag alias resolution, command loading,
22
27
  // error surfacing) is the shared devkit router; this file owns only the alias
23
28
  // table and the commands directory. (ELECTRON_RUN_AS_NODE stripping stays in
24
- // the bin — it must happen before anything Electron-adjacent loads.)
29
+ // cli-run.js, the bin body, and it must happen before anything
30
+ // Electron-adjacent loads.)
25
31
  //
26
32
  // A bare `omega` prints help: setup is retired and every verb runs the local
27
33
  // scaffold itself ([#675](https://github.com/Omega-JS-Stack/omega/issues/675)).
@@ -41,7 +47,6 @@ module.exports = createCliRouter({
41
47
  logs: ['--logs', 'log'],
42
48
  'validate-certs': ['certs', '--validate-certs'],
43
49
  'sign-windows': ['--sign-windows'],
44
- 'push-secrets': ['secrets', '--push-secrets'],
45
50
  'finalize-release': ['finalize', '--finalize-release'],
46
51
  runner: ['--runner'],
47
52
  launch: ['open', '--launch'],
@@ -6,13 +6,11 @@ const { ensureTarget } = require('./lib/ensure-target.js');
6
6
 
7
7
  // The consumer's `build` script is `omega build` — this verb owns the
8
8
  // pipeline and the build-mode flag; it must never shell back to that script.
9
- // `certs` delivers the Apple artifacts first (#678).
10
9
  function plan() {
11
10
  return {
12
11
  env: { OMEGA_BUILD_MODE: 'true' },
13
12
  steps: [
14
13
  { type: 'clean' },
15
- { type: 'certs' },
16
14
  { type: 'gulp', task: 'build' },
17
15
  ],
18
16
  };
@@ -6,70 +6,142 @@
6
6
  * sending it; anything else delegates to release.
7
7
  *
8
8
  * Every run starts with the local scaffold the retired `omega setup` used to
9
- * own (ensureTarget, #675), delivers the Apple signing artifacts into the
10
- * target's certs dir (#678), and then runs setup's NETWORK half as a precheck:
11
- * framework freshness, cert validation, repo provisioning, secret publication.
12
- * `--no-secrets` skips that precheck.
9
+ * own (ensureTarget, #675). A DISPATCH then runs setup's NETWORK half as a
10
+ * precheck: framework freshness, cert validation, repo provisioning, secret
11
+ * publication. `--no-secrets` skips that precheck, and `--direct` never
12
+ * reaches it at all.
13
+ *
14
+ * `--direct` is the direct lane, asked for by a human
15
+ * ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)): `omega publish
16
+ * --local` for THIS machine's platform. It used to select itself whenever the
17
+ * tree was linked, which took the CI lane away from the brands that need it
18
+ * most; a linked brand now packs its frameworks into the snapshot instead.
13
19
  */
14
- const { execSync } = require('node:child_process');
20
+ const path = require('node:path');
15
21
  const Manager = new (require('../build.js'));
16
22
  const logger = Manager.logger('deploy');
17
- const { deployViaDispatch, findLocalSpecs } = require('../vendor/devkit/deploy.js');
23
+ const attachLogFile = require('../utils/attach-log-file.js');
24
+ const { assertBrandVersion } = require('../vendor/devkit/brand-version.js');
25
+ const { targetNameFromDir } = require('../vendor/config/index.js');
18
26
  const { ensureTarget } = require('./lib/ensure-target.js');
19
27
  const { deployPrecheck } = require('./lib/deploy-precheck.js');
20
- const { deliverTargetCerts } = require('../utils/deliver-certs.js');
21
- const { dispatchTarget } = require('./release.js');
28
+ const { desktopPlatform } = require('../utils/platform.js');
29
+ const runConsumerHook = require('../utils/run-consumer-hook.js');
22
30
 
23
31
  module.exports = async function (options) {
24
32
  options = options || {};
25
33
  const dryRun = options.dryRun || options['dry-run'];
26
34
  const projectDir = process.cwd();
27
35
 
28
- // The local half of the retired `omega setup` (#675), then the signing
29
- // artifacts this target needs before anything signs (#678). Both are
30
- // idempotent and quiet on a converged target.
36
+ // The whole verb goes to the target's own deploy log, from its first line
37
+ // ([#873](https://github.com/Omega-JS-Stack/omega/issues/873)): the scaffold,
38
+ // the precheck's refusals and the followed run all land in one file instead
39
+ // of scrollback. `omega release` attaches the same path, which is a no-op
40
+ // when this verb already did, and tees STACK, so a brand fan-out's log gets
41
+ // the same lines.
42
+ attachLogFile(path.join(projectDir, 'logs', 'deploy.log'));
43
+
44
+ // The local half of the retired `omega setup` (#675). Idempotent and quiet on
45
+ // a converged target. Nothing COPIES signing material any more: the tree is
46
+ // read in place and the env load derived the paths to it
47
+ // ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
31
48
  await ensureTarget({ projectDir, log: (line) => logger.log(line), warn: (line) => logger.warn(line) });
32
- deliverTargetCerts({ projectDir, logger });
33
49
 
34
- // The NETWORK half, as a precheck: a deploy is the verb that needs the
35
- // remote side right. A dry run sends nothing, so it prechecks nothing.
36
- if (!dryRun) {
37
- await deployPrecheck({ projectDir, options, logger });
50
+ // The brand's ONE version ([#869](https://github.com/Omega-JS-Stack/omega/issues/869)):
51
+ // a target whose version drifted from the brand root's is refused here, before
52
+ // the precheck, because a drifted target must not push its secrets and
53
+ // dispatch a build of the wrong number. A read, so every lane reaches it
54
+ // (`--direct` and `--dry-run` included).
55
+ assertBrandVersion({ dir: projectDir });
56
+
57
+ // The consumer's pre-deploy hook, on BOTH lanes and before anything is
58
+ // published or published to ([#899](https://github.com/Omega-JS-Stack/omega/issues/899)):
59
+ // the playground's prunes its release family down to the newest one.
60
+ // A dry run skips it, because a hook may act on the world and a dry run
61
+ // promises to send nothing.
62
+ if (dryRun) {
63
+ logger.log('DRY RUN, skipping hook "deploy/pre"');
64
+ } else {
65
+ // The one ctx shape every OMEGA hook takes, `{ manager, projectRoot, mode }`,
66
+ // and a deploy's mode is PRODUCTION: what it is about to publish is a release.
67
+ await runConsumerHook('deploy/pre', { manager: Manager, projectRoot: projectDir, mode: 'production' });
38
68
  }
39
69
 
40
- // Linked local packages (tree-wide file: specs — cp194) → the LOCAL
41
- // release flow automatically: build + sign + publish from this machine
42
- // with the linked frameworks bundled in. Mirrored rule (Ian 2026-07-20);
43
- // CI dispatch is only for registry-clean trees.
44
- if (findLocalSpecs({ dir: process.cwd() }).length > 0) {
45
- logger.log('Linked local packages detected — running the LOCAL release flow (build + sign + publish from this machine). CI dispatch resumes after `omega i live`.');
70
+ // The direct lane: build + sign + publish from THIS machine, for this
71
+ // machine's platform. A cross-platform build needs the runners CI has, so a
72
+ // `--platforms` naming anything else is refused rather than silently built
73
+ // for the host (#872).
74
+ //
75
+ // BEFORE the precheck, which belongs to the CI lane (web and backend already
76
+ // order it this way): a local deploy publishes nothing to the repo and needs
77
+ // no `gh` session, so pushing this target's signing certs into Actions
78
+ // secrets on the way past is exactly what it must not do (#872).
79
+ if (options.direct) {
80
+ const platform = directPlatform(options.platforms || options.platform);
81
+
46
82
  if (dryRun) {
47
- logger.log('DRY RUN — would run: npm run release:local');
83
+ logger.log(`DRY RUN, would run: omega publish --local (${platform}, this machine)`);
48
84
  return;
49
85
  }
50
- execSync('npm run release:local', { stdio: 'inherit' });
51
- require('../vendor/devkit/deploy-record.js').recordDeploy({ dir: process.cwd(), target: 'desktop', detail: { method: 'local' } });
52
- return logger.log('Deployed from the LOCAL release flow (linked frameworks included).');
53
- }
54
86
 
55
- if (dryRun) {
56
- const platforms = options.platforms || options.platform || 'all';
57
- // The SAME address `omega release` dispatches on (config, and the composed
58
- // workflow name inside a brand), so the printed plan is the real one.
59
- const target = dispatchTarget({ projectRoot: projectDir, config: Manager.getConfig() });
60
- const { plan } = await deployViaDispatch({
61
- workflow: target.workflow,
62
- owner: target.owner,
63
- repo: target.repo,
64
- inputs: { platforms: String(platforms) },
65
- dryRun: true,
66
- });
67
- logger.log('DRY RUN — would send:');
68
- logger.log(` ${plan.method} ${plan.url}`);
69
- logger.log(` body: ${JSON.stringify(plan.body)}`);
70
- logger.log(` then watch: ${plan.runsUrl}`);
71
- return;
87
+ logger.log(`Building + publishing LOCALLY for ${platform} (signing credentials must be available on this machine)...`);
88
+ await require('./publish.js')({ ...options, local: true });
89
+ require('../vendor/devkit/deploy-record.js').recordDeploy({ dir: process.cwd(), target: targetNameFromDir(process.cwd()) || 'desktop', detail: { method: 'direct' } });
90
+ return logger.log(`Deployed from the LOCAL publish (${platform}).`);
72
91
  }
73
92
 
93
+ // The NETWORK half, as a precheck: the CI lane needs the remote side right
94
+ // (its secrets, its repos). A DRY RUN runs it too
95
+ // ([#895](https://github.com/Omega-JS-Stack/omega/issues/895)): every step is
96
+ // a read or a plan under `dryRun`, so the preview is the real one, refusals
97
+ // included.
98
+ await deployPrecheck({ projectDir, options, logger, dryRun });
99
+
100
+ // The dispatch lane, dry or not, is `omega release`: ONE call site on this
101
+ // target, so the previewed plan is the very one a real run sends, with the
102
+ // ref and the snapshot sha threaded exactly the same way. The preview used to
103
+ // build a second `deployViaDispatch` call here, which named neither.
74
104
  return require('./release.js')(options);
75
105
  };
106
+
107
+ // What a `--platforms` value may say for each host, in OMEGA's own vocabulary
108
+ // (#867): the platform NAME comes from the one translation point, and these are
109
+ // the spellings the flag accepts for it. The workflow's `platforms` input speaks
110
+ // the same three words.
111
+ const HOST_PLATFORMS = {
112
+ mac: { accepts: ['mac'] },
113
+ windows: { accepts: ['windows'] },
114
+ linux: { accepts: ['linux'] },
115
+ };
116
+
117
+ /**
118
+ * The platform a `--direct` deploy builds: this machine's, and only this
119
+ * machine's. Electron cross-building (a signed Windows installer from a mac, a
120
+ * DMG from linux) is what the CI matrix exists for, so a `--platforms` naming
121
+ * anything but the host is an ERROR: building the host's platform instead
122
+ * would publish an artifact nobody asked for.
123
+ *
124
+ * @param {string} [platforms] - The `--platforms`/`--platform` value ('all', 'mac,linux', …).
125
+ * @param {string} [platform] - The host platform (defaults to process.platform).
126
+ * @returns {string} The host platform's workflow name ('mac' | 'windows' | 'linux').
127
+ * @throws {Error} When `platforms` names anything the host cannot build.
128
+ */
129
+ function directPlatform(platforms, platform) {
130
+ const name = desktopPlatform(platform || process.platform);
131
+ const host = name && HOST_PLATFORMS[name];
132
+
133
+ if (!host) {
134
+ throw new Error(`Unsupported host platform for \`omega deploy --direct\`: ${platform || process.platform}. Dispatch the CI build instead (\`omega deploy\`).`);
135
+ }
136
+
137
+ const asked = String(platforms || name).toLowerCase().split(',').map((value) => value.trim()).filter(Boolean);
138
+ const foreign = asked.filter((value) => !host.accepts.includes(value));
139
+
140
+ if (foreign.length > 0) {
141
+ throw new Error(`\`omega deploy --direct\` builds on THIS machine, so it can only build ${name}: ${foreign.join(', ')} ${foreign.length === 1 ? 'is' : 'are'} CI-only. Run \`omega deploy\` (no --direct) to build every platform on the runners.`);
142
+ }
143
+
144
+ return name;
145
+ }
146
+
147
+ module.exports.directPlatform = directPlatform;
@@ -33,7 +33,7 @@ const logger = Manager.logger('finalize-release');
33
33
 
34
34
  module.exports = async function finalizeRelease(options = {}) {
35
35
  const argv = options._ || [];
36
- // Parse flags from yargs-style options (yargs camelCases --signed-dir → signedDir).
36
+ // The parse mints both spellings of a kebab flag (--signed-dir, signedDir).
37
37
  const signedDir = options.signedDir || options['signed-dir'];
38
38
  const doPublish = options.publish === true;
39
39
 
@@ -59,10 +59,11 @@ module.exports = async function finalizeRelease(options = {}) {
59
59
  }
60
60
 
61
61
  // The releases repo (the auto-updater feed source), from config alone.
62
- const { owner, name, repo } = releasesRepo(config);
63
- if (!repo) {
64
- throw new Error('finalize-release: could not address the releases repo. Set repo.providers.github.org (or targets.desktop.releases.owner) and brand.id in config/omega.json5.');
62
+ const releases = releasesRepo(config);
63
+ if (!releases) {
64
+ throw new Error('finalize-release: could not address the releases repo. Set repo.org and brand.id in config/omega.json5 (the releases repo is <brand.id>-releases under that org).');
65
65
  }
66
+ const { owner, name } = releases;
66
67
 
67
68
  const releaseTag = `v${pkgVersion}`;
68
69
 
@@ -10,7 +10,7 @@
10
10
  // honors by running as plain Node: no `app` API, no BrowserWindow, no window. The
11
11
  // app exits cleanly with code 0 and no error. Total invisible silent failure.
12
12
  //
13
- // `bin/omega-desktop` and `src/gulp/main.js` already strip ELECTRON_RUN_AS_NODE
13
+ // `src/cli-run.js` and `src/gulp/main.js` already strip ELECTRON_RUN_AS_NODE
14
14
  // at the boundary so anything launched THROUGH mgr or gulp is fine. But the manual
15
15
  // smoke-test flow (`open -n release/mac-arm64/MyApp.app`) bypasses both. This
16
16
  // command is the manual-launch equivalent of those boundary strips.
@@ -21,7 +21,7 @@
21
21
  // npx omega launch /Applications/MyApp.app # an installed .app
22
22
  //
23
23
  // Forwarding argv to the app (for hidden-mode tests, custom flags, etc.):
24
- // npx omega launch --args="--omega-launched-at-login" # single flag (quoted to keep yargs from eating it)
24
+ // npx omega launch --args="--omega-launched-at-login" # single flag (quoted to keep the parse from eating it)
25
25
  // npx omega launch --args="--foo=bar --baz" # multiple, space-separated inside the quotes
26
26
  //
27
27
  // Aliases: `npx omega open`, `npx omega --launch`.
@@ -62,8 +62,8 @@ module.exports = async function (options) {
62
62
  const childEnv = { ...process.env };
63
63
  delete childEnv.ELECTRON_RUN_AS_NODE;
64
64
 
65
- // Forward extra argv to the app via `--args`. yargs splits the rest of argv into
66
- // options._ when no flag matches; everything after the app path goes through.
65
+ // Forward extra argv to the app via `--args`. The parse puts every unmatched
66
+ // token in options._; everything after the app path goes through.
67
67
  const extraArgs = options._.slice(2);
68
68
  // Also accept --args as an explicit array/string.
69
69
  if (typeof options.args === 'string') extraArgs.push(...options.args.split(/\s+/).filter(Boolean));
@@ -79,7 +79,7 @@ module.exports = async function (options) {
79
79
  // routes to this instance for activate events). Pass extra argv via --args.
80
80
  cmd = 'open';
81
81
  args = ['-n', appPath, ...(extraArgs.length ? ['--args', ...extraArgs] : [])];
82
- logHint = `open -n ${shellEscape(appPath)}${extraArgs.length ? ' --args ' + extraArgs.map(shellEscape).join(' ') : ''}`;
82
+ logHint = `open -n ${shellEscape(appPath)}${extraArgs.length ? ` --args ${extraArgs.map(shellEscape).join(' ')}` : ''}`;
83
83
  } else {
84
84
  // Direct binary launch — Windows .exe, Linux binary, or running the .app's MacOS
85
85
  // binary directly (rare, used to bypass LaunchServices for diagnosis).
@@ -88,7 +88,7 @@ module.exports = async function (options) {
88
88
  cmd = path.join(appPath, 'Contents', 'MacOS', path.basename(appPath, '.app'));
89
89
  }
90
90
  args = extraArgs;
91
- logHint = `${shellEscape(cmd)}${args.length ? ' ' + args.map(shellEscape).join(' ') : ''}`;
91
+ logHint = `${shellEscape(cmd)}${args.length ? ` ${args.map(shellEscape).join(' ')}` : ''}`;
92
92
  }
93
93
 
94
94
  logger.log(`launching with clean env (ELECTRON_RUN_AS_NODE stripped)...`);
@@ -111,11 +111,16 @@ module.exports = async function (options) {
111
111
  logger.log('launched.');
112
112
  };
113
113
 
114
+ // electron-builder's own output-dir words, keyed by OMEGA's (#867). This is the
115
+ // ONE place the release dir's spelling is translated: `release/win-<arch>` is
116
+ // electron-builder's name for the directory, not a platform OMEGA calls `win`.
117
+ const RELEASE_DIRS = { mac: 'mac', windows: 'win', linux: 'linux' };
118
+
114
119
  // Auto-discover a packaged app under release/<platform>-<arch>/ — matches the output
115
120
  // directory of `mgr package:quick` for the host platform/arch.
116
121
  function autoDiscoverApp() {
117
122
  const projectRoot = process.cwd();
118
- const platform = process.platform === 'darwin' ? 'mac' : process.platform === 'win32' ? 'win' : 'linux';
123
+ const platform = RELEASE_DIRS[require('../utils/platform.js').desktopPlatform()] || 'linux';
119
124
  const arch = process.arch;
120
125
  const releaseDir = path.join(projectRoot, 'release', `${platform}-${arch}`);
121
126