@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.
Files changed (268) hide show
  1. package/README.md +2 -2
  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 +176 -42
  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 +146 -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 +635 -5
  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 +125 -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 +249 -103
  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 +425 -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 +505 -75
  197. package/dist/vendor/devkit/git-auth.js +73 -0
  198. package/dist/vendor/devkit/git-remote.js +172 -0
  199. package/dist/vendor/devkit/github-repo.js +290 -0
  200. package/dist/vendor/devkit/local.js +98 -3
  201. package/dist/vendor/devkit/lockfile.js +127 -0
  202. package/dist/vendor/devkit/merge-line-files.js +23 -16
  203. package/dist/vendor/devkit/omega-bin.js +18 -3
  204. package/dist/vendor/devkit/pack-local.js +388 -0
  205. package/dist/vendor/devkit/preludes/index.js +120 -0
  206. package/dist/vendor/devkit/preludes/origin-heal.js +142 -0
  207. package/dist/vendor/devkit/service-account.js +43 -0
  208. package/dist/vendor/devkit/ship-plan.js +112 -0
  209. package/dist/vendor/devkit/signing-env.js +180 -0
  210. package/dist/vendor/devkit/signing-tree.js +92 -0
  211. package/dist/vendor/devkit/target-seams.js +142 -0
  212. package/dist/vendor/devkit/target-secrets.js +231 -57
  213. package/dist/vendor/devkit/test/esm-only-fixture.js +48 -0
  214. package/dist/vendor/devkit/test/fixtures/esm-only-package/browser.js +19 -0
  215. package/dist/vendor/devkit/test/fixtures/esm-only-package/index.js +20 -0
  216. package/dist/vendor/devkit/test/fixtures/esm-only-package/package.json +13 -0
  217. package/dist/vendor/monitoring/env.js +20 -10
  218. package/dist/vendor/monitoring/main.js +1 -1
  219. package/dist/vendor/monitoring/preload.js +1 -1
  220. package/dist/vendor/monitoring/renderer.js +1 -1
  221. package/docs/analytics.md +1 -1
  222. package/docs/auto-updater.md +5 -5
  223. package/docs/boot-sequence.md +1 -1
  224. package/docs/build-system.md +15 -7
  225. package/docs/client-bridge.md +9 -7
  226. package/docs/config-schema.md +4 -4
  227. package/docs/css.md +8 -2
  228. package/docs/deep-link.md +12 -4
  229. package/docs/environment-detection.md +32 -24
  230. package/docs/hooks.md +3 -1
  231. package/docs/icons.md +7 -7
  232. package/docs/index.md +61 -23
  233. package/docs/installer-options.md +24 -21
  234. package/docs/logging.md +5 -5
  235. package/docs/releasing.md +29 -19
  236. package/docs/runner.md +51 -14
  237. package/docs/shared/brands.md +12 -6
  238. package/docs/shared/breaking-changes.md +375 -21
  239. package/docs/shared/config.md +764 -199
  240. package/docs/shared/deploys.md +217 -91
  241. package/docs/shared/icons.md +18 -0
  242. package/docs/shared/local-dev.md +28 -8
  243. package/docs/shared/logging.md +9 -6
  244. package/docs/shared/monitoring.md +27 -13
  245. package/docs/shared/publishing.md +3 -3
  246. package/docs/shared/rulings.md +2 -2
  247. package/docs/shared/testing.md +1 -1
  248. package/docs/shared/theming.md +26 -1
  249. package/docs/shared/translation.md +49 -7
  250. package/docs/shared/updates.md +1 -1
  251. package/docs/signing.md +60 -34
  252. package/docs/test-framework.md +10 -5
  253. package/docs/themes.md +15 -1
  254. package/package.json +18 -12
  255. package/bin/omega-desktop +0 -2
  256. package/dist/commands/push-secrets.js +0 -141
  257. package/dist/test/suites/build/deliver-certs.test.js +0 -95
  258. package/dist/test/suites/build/derive-signing-env.test.js +0 -122
  259. package/dist/test/suites/build/push-secrets.test.js +0 -226
  260. package/dist/test/suites/build/resolve-signing-cert.test.js +0 -342
  261. package/dist/utils/deliver-certs.js +0 -69
  262. package/dist/utils/derive-signing-env.js +0 -56
  263. package/dist/utils/resolve-signing-cert.js +0 -175
  264. package/dist/vendor/config/desktop-artifacts.js +0 -110
  265. package/dist/vendor/config/instances.js +0 -208
  266. /package/dist/defaults/config/icons/{macos → mac}/dmg.png +0 -0
  267. /package/dist/defaults/config/icons/{macos → mac}/icon.png +0 -0
  268. /package/dist/defaults/config/icons/{macos → mac}/tray.png +0 -0
@@ -14,18 +14,20 @@
14
14
  // the defaults — and the orgs it serves. How a value that
15
15
  // is already there gets changed.
16
16
  // register-org <org> Register the runner against one specific GH org.
17
- // start Bring EVERY registered org's runner online, detached,
18
- // in Startup-shortcut order. An org already alive is
19
- // named and skipped; the terminal is never taken over.
17
+ // start The one command: installs a bare box, refreshes a stale
18
+ // actions/runner download in place, registers any admin
19
+ // org the box does not serve yet, then brings EVERY
20
+ // registered org online, detached, in Startup-shortcut
21
+ // order. An org already alive is named and skipped; the
22
+ // terminal is never taken over.
20
23
  // restart stop, wait for every listener to go, then start.
21
24
  // stop Kill every Runner.Listener.exe under RUNNER_HOME.
22
25
  // status Registered orgs, Startup shortcuts, live listeners.
23
26
  // uninstall Remove everything — legacy services and tasks, and the
24
27
  // electron-manager era's em-runner install, included.
25
- // self-update Force an immediate self-update of @omega.js/desktop (npm i -g @omega.js/desktop@latest).
26
28
  // monitor Tail the JSONL signing event log, pretty-printed.
27
29
  //
28
- // Every subcommand except `self-update` and `monitor` refuses on non-Windows
30
+ // Every subcommand except `monitor` refuses on non-Windows
29
31
  // (set OMEGA_RUNNER_FORCE=1 to override — testing only).
30
32
 
31
33
  const path = require('path');
@@ -35,7 +37,6 @@ const jetpack = require('fs-jetpack');
35
37
 
36
38
  const Manager = new (require('../build.js'));
37
39
  const logger = Manager.logger('runner');
38
- const { safeInstall } = require('../utils/safe-install');
39
40
 
40
41
  const RUNNER_LABELS = ['self-hosted', 'windows', 'ev-token'];
41
42
 
@@ -57,6 +58,7 @@ function ghRunnerName(org) {
57
58
  // — is `<home>\.env`, read here before anything looks at process.env, so every
58
59
  // subcommand works from any directory (utils/runner-env.js).
59
60
  const { defaultRunnerHome, runnerEnvFile, runnerLogFile, loadRunnerEnv, ensureRunnerConfig, reconfigureRunnerEnv, runnerEnvReport, parseRunnerOrgs, sameRunnerOrgs, isTestRun, isScratchRunnerHome, assertTestSafeRunnerHome, runnerPrivateHome, ensureRunnerPrivateHome, ensureRunnerDirEnv } = require('../utils/runner-env.js');
61
+ const { ensureRunnerJobGuard, jobGuardListFiles } = require('../utils/runner-job-guard.js');
60
62
  const attachLogFile = require('../utils/attach-log-file.js');
61
63
  const RUNNER_HOME = process.env.OMEGA_RUNNER_HOME || defaultRunnerHome();
62
64
  loadRunnerEnv({ home: RUNNER_HOME });
@@ -102,8 +104,8 @@ function orgRunnerDir(org, home) {
102
104
  }
103
105
  const ACTIONS_RUNNER_VERSION = '2.319.1'; // pinned; bump intentionally
104
106
 
105
- // The subcommands, and the two of them that run on any platform: self-update is
106
- // an npm install, monitor only reads a log file. Every other one is the box's.
107
+ // The subcommands, and the one of them that runs on any platform: monitor only
108
+ // reads a log file. Every other one is the box's.
107
109
  const SUBCOMMANDS = {
108
110
  'install': install,
109
111
  'config': configureRunner,
@@ -113,17 +115,16 @@ const SUBCOMMANDS = {
113
115
  'stop': stopServices,
114
116
  'status': statusServices,
115
117
  'uninstall': uninstall,
116
- 'self-update': selfUpdate,
117
118
  'monitor': monitor,
118
119
  };
119
- const ANY_PLATFORM_SUBCOMMANDS = new Set(['self-update', 'monitor']);
120
+ const ANY_PLATFORM_SUBCOMMANDS = new Set(['monitor']);
120
121
 
121
122
  // The ones that CHANGE the machine: they register, deregister, download, delete,
122
- // kill listeners, rewrite the box's own configuration, or run a global npm
123
- // install. A test process may only point these at a scratch home
123
+ // kill listeners, or rewrite the box's own configuration. A test process may
124
+ // only point these at a scratch home
124
125
  // ([#337](https://github.com/Omega-JS-Stack/omega/issues/337) — the suite once
125
126
  // ran a real install against the box). `status` and `monitor` only read.
126
- const MUTATING_SUBCOMMANDS = new Set(['install', 'config', 'register-org', 'start', 'restart', 'stop', 'uninstall', 'self-update']);
127
+ const MUTATING_SUBCOMMANDS = new Set(['install', 'config', 'register-org', 'start', 'restart', 'stop', 'uninstall']);
127
128
 
128
129
  module.exports = async function (options) {
129
130
  options = options || {};
@@ -260,7 +261,7 @@ async function install(options) {
260
261
  // admin orgs on a tick ran as NT AUTHORITY\NETWORK SERVICE in Session 0, so
261
262
  // the runners it spawned could not see the user's cert store and the Startup
262
263
  // shortcuts it wrote landed in the wrong profile. Adding a new org is a
263
- // `mgr runner install` away; uninstall() still tears down any leftover
264
+ // `npx omega runner start` away; uninstall() still tears down any leftover
264
265
  // watcher service from an older install.
265
266
 
266
267
  // 5. Save install metadata.
@@ -303,7 +304,7 @@ async function install(options) {
303
304
  logger.log('');
304
305
  logger.log(`Bringing every registered runner (${succeeded.length}) online in the background. 'npx omega runner status' shows them, 'npx omega runner monitor' tails the log.`);
305
306
  logger.log('');
306
- await startServices({ ...options, _home: home });
307
+ await startServices({ ...options, _home: home, _reconcileDone: true });
307
308
  }
308
309
  }
309
310
 
@@ -415,7 +416,7 @@ async function registerOrg(options) {
415
416
  const runnerDir = orgRunnerDir(org, home);
416
417
  const templateDir = options._templateDir || path.join(home, '_template');
417
418
  if (!jetpack.exists(path.join(templateDir, 'config.cmd'))) {
418
- throw new Error(`actions/runner template not found at ${templateDir}. Run 'npx omega runner install' first.`);
419
+ throw new Error(`actions/runner template not found at ${templateDir}. Run 'npx omega runner start' first.`);
419
420
  }
420
421
  if (jetpack.exists(runnerDir)) {
421
422
  logger.log(` Removing stale actions-runner-${org.toLowerCase()}/ before re-clone…`);
@@ -480,9 +481,27 @@ async function registerOrg(options) {
480
481
  // runner's `<runner dir>\.env`, which the listener reads at startup and
481
482
  // applies to every job, so the detached spawn and the Startup shortcut both
482
483
  // get it from one write.
484
+ //
485
+ // The job guard travels the same way
486
+ // ([#875](https://github.com/Omega-JS-Stack/omega/issues/875)): the hook the
487
+ // listener runs before a job's first step, refusing anything that is not a
488
+ // dispatch from a listed repo by a listed actor. The actor list starts as the
489
+ // `GH_TOKEN` user, which is this box owner's account and the account a
490
+ // `workflow_dispatch` from the deploy token carries (Ian 2026-09-13); a
491
+ // lookup that does not answer leaves the file for the operator rather than
492
+ // failing a registration over it.
483
493
  const privateHome = ensureRunnerPrivateHome(home, { logger });
484
- ensureRunnerDirEnv(runnerDir, { HOME: privateHome.home });
494
+ let actor;
495
+ try {
496
+ const { data } = await octokit.rest.users.getAuthenticated();
497
+ actor = data.login;
498
+ } catch (e) {
499
+ logger.warn(` Could not read the GH_TOKEN user for the job guard's actor list: ${e.message}`);
500
+ }
501
+ const guard = ensureRunnerJobGuard(home, { orgs: [org], actor, logger });
502
+ ensureRunnerDirEnv(runnerDir, { HOME: privateHome.home, ACTIONS_RUNNER_HOOK_JOB_STARTED: guard.hook });
485
503
  logger.log(` HOME for ${org}: ${privateHome.home}`);
504
+ logger.log(` Job guard for ${org}: ${guard.hook}`);
486
505
 
487
506
  // Write the .cmd shortcut in the user's Startup folder so Explorer auto-runs
488
507
  // it at every interactive logon (Session 1). No Task Scheduler, no admin
@@ -509,7 +528,8 @@ async function registerOrg(options) {
509
528
  // What `start` settles before it touches a listener: the box's required keys —
510
529
  // asked in a terminal and refused without one, exactly like install — and then
511
530
  // the orgs the .env names against the orgs this install actually registered.
512
- // start registers nothing; it names the command that would.
531
+ // start only ever ADDS a named admin org (reconcileRunnerInstall); reshaping the
532
+ // box to the list is install's, so the mismatch names it.
513
533
  async function startPreflight(options) {
514
534
  const { home, env = process.env, logger: log = logger, prompt, interactive } = options || {};
515
535
  await ensureRunnerConfig({ home, env, logger: log, prompt, interactive });
@@ -518,16 +538,27 @@ async function startPreflight(options) {
518
538
  // an install that predates it, or a runner dir whose `.env` was replaced, has
519
539
  // no HOME line, and the job it picks up then dies inside `actions/checkout`.
520
540
  // Every registered dir gets it here, before a single listener comes up.
541
+ //
542
+ // And the job guard with it
543
+ // ([#875](https://github.com/Omega-JS-Stack/omega/issues/875)): a box that
544
+ // installed before the guard existed, or a runner dir whose `.env` lost the
545
+ // line, would otherwise come online signing for anything. The hook script is
546
+ // rewritten from this package, so a framework update reaches the box on the
547
+ // next start; the allow lists are the operator's and are only appended to,
548
+ // and this path asks GitHub nothing, so `start` still works offline.
521
549
  const privateHome = ensureRunnerPrivateHome(home, { logger: log });
522
- for (const { org, dir } of listOrgRunnerDirs(home)) {
523
- ensureRunnerDirEnv(dir, { HOME: privateHome.home });
550
+ const dirs = listOrgRunnerDirs(home);
551
+ const guard = ensureRunnerJobGuard(home, { orgs: dirs.map(({ org }) => org), logger: log });
552
+ for (const { org, dir } of dirs) {
553
+ ensureRunnerDirEnv(dir, { HOME: privateHome.home, ACTIONS_RUNNER_HOOK_JOB_STARTED: guard.hook });
524
554
  log.log(`HOME for ${org}: ${privateHome.home}`);
555
+ log.log(`Job guard for ${org}: ${guard.hook}`);
525
556
  }
526
557
 
527
558
  const named = parseRunnerOrgs(env.OMEGA_RUNNER_ORGS);
528
559
  const registered = readConfig(home).registeredOrgs || [];
529
560
  if (named.length > 0 && !sameRunnerOrgs(named, registered)) {
530
- log.warn(`OMEGA_RUNNER_ORGS names ${named.join(', ')} but this install registered ${registered.join(', ') || '(none)'}. Run \`npx omega runner install\` to apply.`);
561
+ log.warn(`OMEGA_RUNNER_ORGS names ${named.join(', ')} but this install registered ${registered.join(', ') || '(none)'}. \`start\` registers a named admin org it does not serve yet and keeps the rest; \`npx omega runner install\` reshapes the box to the list.`);
531
562
  }
532
563
  }
533
564
 
@@ -544,6 +575,92 @@ function runnerStartupTargets(home) {
544
575
  });
545
576
  }
546
577
 
578
+ // What `start` settles between the preflight and the spawns
579
+ // ([#937](https://github.com/Omega-JS-Stack/omega/issues/937)), so a box never
580
+ // needs a second verb to catch up:
581
+ // 1. a stale actions/runner download is refreshed IN PLACE: the template is
582
+ // re-downloaded and laid over each org dir whose listener is down. The
583
+ // registration (`.runner`, `.credentials`, `.env`) is never in the
584
+ // template, so it survives the copy. A live listener holds its binaries
585
+ // open, so that org is skipped and named with `restart`, and the new
586
+ // version is recorded only once every org dir took it. No teardown. A
587
+ // download or a copy that fails is one warn and a skipped refresh, never
588
+ // a failed start: the next start tries again.
589
+ // 2. an admin org the box does not serve yet (OMEGA_RUNNER_ORGS honoured) is
590
+ // registered, Startup shortcut and all. The only step that needs GitHub,
591
+ // so discovery that fails is one line and the box still comes online.
592
+ // GH_TOKEN is never missing here: it is a required key, and the preflight
593
+ // refuses a box without one. Orgs registered outside the filter stay;
594
+ // reshaping the box to the list is install's.
595
+ //
596
+ // `_download`, `_discoverOrgs` and `_registerOrg` are the tests' seams, the way
597
+ // `_spawn` is. Registration is not home-scoped (it acts on GitHub with the real
598
+ // token, whatever scratch home a case made), so a test run that has not
599
+ // injected both GitHub seams skips the org check instead of reaching it.
600
+ async function reconcileRunnerInstall(options) {
601
+ const home = options._home;
602
+ const env = options._env;
603
+ const log = options._logger;
604
+ const listen = options._listeners;
605
+ const download = options._download || downloadActionsRunner;
606
+ const templateDir = path.join(home, '_template');
607
+
608
+ const installed = readConfig(home).actionsRunnerVersion;
609
+ if (installed !== ACTIONS_RUNNER_VERSION) {
610
+ log.log(`actions/runner ${installed || '(unrecorded)'} is installed and ${ACTIONS_RUNNER_VERSION} is pinned: refreshing in place.`);
611
+ let downloaded = true;
612
+ try {
613
+ await download(templateDir, home);
614
+ } catch (e) {
615
+ log.warn(`✗ actions/runner ${ACTIONS_RUNNER_VERSION} download failed (${e.message}): refresh skipped until the next start.`);
616
+ downloaded = false;
617
+ }
618
+ if (downloaded) {
619
+ let skipped = 0;
620
+ for (const { org, dir } of listOrgRunnerDirs(home)) {
621
+ if (listen(dir).length > 0) {
622
+ log.warn(`· ${org} is running, so its actions/runner files are locked and were not refreshed. Run \`npx omega runner restart\` to refresh it.`);
623
+ skipped++;
624
+ continue;
625
+ }
626
+ try {
627
+ jetpack.copy(templateDir, dir, { overwrite: true });
628
+ log.log(`✓ ${org} refreshed to actions/runner ${ACTIONS_RUNNER_VERSION}`);
629
+ } catch (e) {
630
+ log.warn(`✗ ${org} was not refreshed (${e.message}): the next start tries again.`);
631
+ skipped++;
632
+ }
633
+ }
634
+ if (skipped === 0) saveConfig({ actionsRunnerVersion: ACTIONS_RUNNER_VERSION }, home);
635
+ }
636
+ }
637
+
638
+ if (isTestRun() && !(options._discoverOrgs && options._registerOrg)) {
639
+ log.log('Org check skipped: a test run never asks GitHub unless both _discoverOrgs and _registerOrg are injected.');
640
+ return;
641
+ }
642
+ const discover = options._discoverOrgs || discoverAdminOrgs;
643
+ const register = options._registerOrg || registerOrg;
644
+ let adminOrgs;
645
+ try {
646
+ adminOrgs = await discover();
647
+ } catch (e) {
648
+ log.warn(`Org check skipped: could not list your admin orgs (${e.message}).`);
649
+ return;
650
+ }
651
+ const { orgs } = selectRunnerOrgs(adminOrgs, env.OMEGA_RUNNER_ORGS);
652
+ const served = new Set((readConfig(home).registeredOrgs || []).map((org) => org.toLowerCase()));
653
+ for (const org of orgs.filter((o) => !served.has(o.toLowerCase()))) {
654
+ log.log(`Registering ${org}: an admin org this box does not serve yet.`);
655
+ try {
656
+ await register({ ...options, _: ['runner', 'register-org', org], _templateDir: templateDir });
657
+ } catch (e) {
658
+ log.warn(`✗ register ${org}: ${e.message}`);
659
+ process.exitCode = 1;
660
+ }
661
+ }
662
+ }
663
+
547
664
  // Every registered org, brought online detached, in Startup-shortcut order.
548
665
  //
549
666
  // A box registers one runner per org, and `start` used to bring up exactly ONE
@@ -559,7 +676,15 @@ function runnerStartupTargets(home) {
559
676
  // `_spawn` and `_listeners` are injected by the tests, the same way `_home` and
560
677
  // `_logger` are: neither cmd.exe nor the process scan exists off Windows.
561
678
  // `_preflightDone` is `restart`'s: it settles the box config BEFORE it kills
562
- // anything, so the walk must not run a second time here.
679
+ // anything, so the walk must not run a second time here. The reconcile is NOT
680
+ // gated on it: after restart's stop is exactly when nothing is locked.
681
+ // `_reconcileDone` is install's alone: it just downloaded and registered, so the
682
+ // reconcile is skipped there.
683
+ //
684
+ // A bare box (no template to clone from) is handed to install, `_install` in
685
+ // the tests, which ends by calling this function again with the template in
686
+ // place. A test run without `_install` refuses: the real install downloads,
687
+ // discovers and registers against GitHub, and no scratch home scopes that.
563
688
  async function startServices(options) {
564
689
  options = options || {};
565
690
  ensureWindows();
@@ -567,8 +692,23 @@ async function startServices(options) {
567
692
  const log = options._logger || logger;
568
693
  const spawnIt = options._spawn || spawnRunnerDetached;
569
694
  const listen = options._listeners || listRunnerListenerProcessesUnder;
695
+ if (!jetpack.exists(path.join(home, '_template', 'config.cmd'))) {
696
+ if (isTestRun() && !options._install) {
697
+ throw new Error(
698
+ `Refusing to install onto the bare runner home ${home}: this is a test run `
699
+ + '(OMEGA_TEST_RUNNER / OMEGA_TEST_MODE) and the call did not inject `_install`. The real install '
700
+ + 'downloads actions/runner and registers every admin org against GitHub with GH_TOKEN.',
701
+ );
702
+ }
703
+ log.log(`No runner install under ${home}: running the full install.`);
704
+ return (options._install || install)(options);
705
+ }
706
+ const env = options._env || process.env;
570
707
  if (!options._preflightDone) {
571
- await startPreflight({ home, env: options._env || process.env, logger: log, prompt: options._prompt, interactive: options._interactive });
708
+ await startPreflight({ home, env, logger: log, prompt: options._prompt, interactive: options._interactive });
709
+ }
710
+ if (!options._reconcileDone) {
711
+ await reconcileRunnerInstall({ ...options, _home: home, _env: env, _logger: log, _listeners: listen });
572
712
  }
573
713
  const targets = runnerStartupTargets(home);
574
714
  if (targets.length === 0) {
@@ -732,7 +872,7 @@ async function statusServices(options) {
732
872
  logger.log('');
733
873
  const report = orgRunnerReport({ home });
734
874
  if (report.length === 0) {
735
- logger.warn('No orgs registered. Run `npx omega runner install`.');
875
+ logger.warn('No orgs registered. Run `npx omega runner start`.');
736
876
  } else {
737
877
  logger.log(`Per-org runner state (${report.length}):`);
738
878
  for (const { state, lines } of report) {
@@ -966,7 +1106,13 @@ async function removeRunnerHomeWithRetry(keepDirs, home) {
966
1106
  // open by the tee while the removal runs.
967
1107
  const envFile = runnerEnvFile(root);
968
1108
  const logsDir = path.dirname(runnerLogFile(root));
969
- const keep = new Set([...(keepDirs || []), envFile, logsDir]);
1109
+ // The job guard's two allow lists are configuration too
1110
+ // ([#875](https://github.com/Omega-JS-Stack/omega/issues/875)): a list an
1111
+ // operator narrowed by hand outlives an uninstall exactly as the `.env` does.
1112
+ // The hook SCRIPT is install state and goes with the rest; the next install
1113
+ // writes it back from the package.
1114
+ const guardLists = jobGuardListFiles(root);
1115
+ const keep = new Set([...(keepDirs || []), envFile, logsDir, ...guardLists]);
970
1116
  const targets = (jetpack.list(root) || [])
971
1117
  .map((name) => path.join(root, name))
972
1118
  .filter((entry) => !keep.has(entry));
@@ -974,7 +1120,8 @@ async function removeRunnerHomeWithRetry(keepDirs, home) {
974
1120
  // Nothing kept survives on disk: the home itself goes too.
975
1121
  const kept = [...(keepDirs || [])].length > 0
976
1122
  || jetpack.exists(envFile) === 'file'
977
- || jetpack.exists(logsDir) === 'dir';
1123
+ || jetpack.exists(logsDir) === 'dir'
1124
+ || guardLists.some((file) => jetpack.exists(file) === 'file');
978
1125
  if (!kept) targets.push(root);
979
1126
 
980
1127
  for (const target of targets) {
@@ -1124,19 +1271,6 @@ function identifyHandleHolders(targetPath) {
1124
1271
  }
1125
1272
  }
1126
1273
 
1127
- // ─── self-update ────────────────────────────────────────────────────────────────
1128
- async function selfUpdate() {
1129
- const { execute } = require('node-powertools');
1130
- logger.log('Updating @omega.js/desktop to latest…');
1131
- try {
1132
- const out = await safeInstall('npm i -g @omega.js/desktop@latest');
1133
- logger.log(out);
1134
- logger.log('✓ @omega.js/desktop updated.');
1135
- } catch (e) {
1136
- logger.warn(`Self-update failed: ${e.message}`);
1137
- }
1138
- }
1139
-
1140
1274
  // ─── helpers ────────────────────────────────────────────────────────────────────
1141
1275
 
1142
1276
  const WATCHER_SERVICE_NAME = 'omega-runner-watcher';
@@ -1534,8 +1668,8 @@ function listLegacyRunnerStartupShortcuts(startupDir) {
1534
1668
  // wrapper that blocks for the lifetime of the listener with cwd = runnerDir;
1535
1669
  // a long-lived cmd.exe holding cwd inside RUNNER_HOME blocks every later
1536
1670
  // uninstall with EPERM. Bypassing run.cmd loses its self-update relaunch
1537
- // path, but `mgr runner install` refreshing the runner binary is the
1538
- // preferred update mechanism in @omega.js/desktop anyway.
1671
+ // path, but `npx omega runner start` refreshing the runner binary in place
1672
+ // is the update mechanism in @omega.js/desktop anyway.
1539
1673
  //
1540
1674
  // Detached + windowsHide + ignored stdio = no console window flashes during
1541
1675
  // install and the listener survives the install command's exit. UAC-
@@ -1696,7 +1830,7 @@ async function monitor(options) {
1696
1830
  // exactly which orgs the monitor will pick up signing events from.
1697
1831
  const report = orgRunnerReport({ home });
1698
1832
  if (report.length === 0) {
1699
- logger.log('(no orgs registered yet — run `npx omega runner install` first)');
1833
+ logger.log('(no orgs registered yet: run `npx omega runner start` first)');
1700
1834
  } else {
1701
1835
  logger.log(`Monitoring signing requests across ${report.length} org(s):`);
1702
1836
  for (const { state, lines } of report) {
@@ -1,6 +1,6 @@
1
1
  // Strategy-aware Windows code signer.
2
2
  //
3
- // Reads strategy from config platforms.win.signing.strategy:
3
+ // Reads strategy from config platforms.windows.signing.strategy:
4
4
  // self-hosted — sign with signtool against an EV USB token (typically on a self-hosted runner)
5
5
  // cloud — shell out to a cloud signing provider's CLI (Azure / SSL.com / DigiCert)
6
6
  // local — no-op (developer signs manually on their own Windows box)
@@ -33,6 +33,8 @@ runnerEnv.loadRunnerEnv();
33
33
  const attachLogFile = require('../utils/attach-log-file.js');
34
34
 
35
35
  const { startAutoUnlock } = require('../lib/sign-helpers/auto-unlock.js');
36
+ const { assertConsoleUnlocked } = require('../lib/sign-helpers/console-lock.js');
37
+ const { execWithLimit } = require('../lib/sign-helpers/exec-with-limit.js');
36
38
  const { writeUpdateInfo } = require('../lib/sign-helpers/update-info.js');
37
39
  const signEvents = require('../lib/sign-helpers/sign-events.js');
38
40
 
@@ -140,9 +142,9 @@ async function runSignCommand(options) {
140
142
  }
141
143
 
142
144
  if (strategy === 'cloud') {
143
- const provider = config.platforms?.win?.signing?.cloud?.provider;
145
+ const provider = config.platforms?.windows?.signing?.cloud?.provider;
144
146
  if (!provider) {
145
- throw new Error('strategy=cloud but no provider set (config platforms.win.signing.cloud.provider).');
147
+ throw new Error('strategy=cloud but no provider set (config platforms.windows.signing.cloud.provider).');
146
148
  }
147
149
  await signWithCloudProvider(provider, targets, inDir, outDir);
148
150
  return;
@@ -261,9 +263,17 @@ function buildVerifyCommand({ signtool, outPath }) {
261
263
  const SIGN_ATTEMPTS = 3;
262
264
  const SIGN_RETRY_DELAY_MS = 5000;
263
265
 
264
- // node-powertools' `execute` with `log: false` — what this file passes — rejects
265
- // with `new Error(stderr || 'Command failed with exit code N')`, and the sign
266
- // command ends in `2>&1` (it runs through a shell), so BOTH of signtool's
266
+ // A healthy sign takes ~18s (the timestamp round trip is most of it). One that
267
+ // is still running minutes later is waiting on something that will never come:
268
+ // a PIN typed at a locked console, a dialog nobody can see. Every attempt gets
269
+ // this hard limit, after which the signtool process tree is terminated and the
270
+ // attempt fails with a message that says so, instead of holding the job until
271
+ // GitHub's six-hour default cancels it ([#864](https://github.com/Omega-JS-Stack/omega/issues/864)).
272
+ const SIGN_TIME_LIMIT_MS = 3 * 60 * 1000;
273
+
274
+ // node-powertools' `execute` with `log: false` — what execWithLimit passes —
275
+ // rejects with `new Error(stderr || 'Command failed with exit code N')`, and the
276
+ // sign command ends in `2>&1` (it runs through a shell), so BOTH of signtool's
267
277
  // streams reach `e.message` and this is classifying on signtool's own words.
268
278
  //
269
279
  // Failures signtool NAMES as permanent. Retrying a wrong PIN walks the EV token
@@ -298,7 +308,7 @@ async function signWithRetry(cmd, options) {
298
308
  file,
299
309
  attempts = SIGN_ATTEMPTS,
300
310
  delayMs = SIGN_RETRY_DELAY_MS,
301
- exec = (c) => execute(c, { log: false }),
311
+ exec = (c) => execWithLimit(c, { limitMs: SIGN_TIME_LIMIT_MS, label: 'signtool' }),
302
312
  sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
303
313
  emit = signEvents.emit,
304
314
  startUnlock = () => ({ stop: () => {} }),
@@ -334,7 +344,17 @@ async function signWithRetry(cmd, options) {
334
344
  throw new Error(`signWithRetry exhausted ${attempts} attempts without a verdict`);
335
345
  }
336
346
 
337
- async function signWithSigntool(targets, inDir, outDir) {
347
+ // The signing loop. Both of its shell-outs and every line it prints sit behind
348
+ // one seam (`deps.exec` runs signtool, `deps.logger` prints), so the ORDER that
349
+ // makes the proof line true (sign, verify, THEN the line) is provable without a
350
+ // Windows box. Production passes neither: the real signtool and the real logger
351
+ // are the defaults, and the deliberate end-to-end sign is sign-windows-e2e.
352
+ async function signWithSigntool(targets, inDir, outDir, deps) {
353
+ deps = deps || {};
354
+ const exec = deps.exec;
355
+ const runVerify = exec || ((command) => execute(command, { log: false }));
356
+ const log = deps.logger || logger;
357
+
338
358
  const projectRoot = process.cwd();
339
359
  const { tokenRef, password, useThumbprint, signtool, timestampUrl } = resolveSigntoolEnv();
340
360
 
@@ -343,6 +363,11 @@ async function signWithSigntool(targets, inDir, outDir) {
343
363
  // update mechanism (not electron-updater) and don't need a yml.
344
364
  const signedExes = [];
345
365
 
366
+ // The PIN is typed into the Token Logon dialog on the interactive desktop.
367
+ // Behind a lock screen it never arrives and signtool waits forever, so a
368
+ // locked console is refused before the first call (console-lock.js).
369
+ if (useThumbprint && password) await assertConsoleUnlocked();
370
+
346
371
  for (const target of targets) {
347
372
  const rel = path.relative(inDir, target);
348
373
  const outPath = path.join(outDir, rel);
@@ -353,7 +378,7 @@ async function signWithSigntool(targets, inDir, outDir) {
353
378
 
354
379
  const cmd = buildSignCommand({ signtool, tokenRef, password, timestampUrl, outPath });
355
380
 
356
- logger.log(`Signing ${path.relative(projectRoot, outPath)}${useThumbprint ? ' (thumbprint mode)' : ''}...`);
381
+ log.log(`Signing ${path.relative(projectRoot, outPath)}${useThumbprint ? ' (thumbprint mode)' : ''}...`);
357
382
 
358
383
  const fileBytes = (() => { try { return fs.statSync(outPath).size; } catch (_) { return null; } })();
359
384
  signEvents.emit('sign-start', {
@@ -370,6 +395,7 @@ async function signWithSigntool(targets, inDir, outDir) {
370
395
  try {
371
396
  await signWithRetry(cmd, {
372
397
  file: path.basename(outPath),
398
+ exec,
373
399
  startUnlock: () => (useThumbprint ? startAutoUnlock({ password, logger }) : { stop: () => {} }),
374
400
  });
375
401
  } catch (e) {
@@ -385,7 +411,7 @@ async function signWithSigntool(targets, inDir, outDir) {
385
411
  // Verify is a local check with no third party in it — one shot.
386
412
  const verifyCmd = buildVerifyCommand({ signtool, outPath });
387
413
  try {
388
- await execute(verifyCmd, { log: false });
414
+ await runVerify(verifyCmd);
389
415
  } catch (e) {
390
416
  signEvents.emit('sign-fail', {
391
417
  file: path.basename(outPath),
@@ -399,7 +425,11 @@ async function signWithSigntool(targets, inDir, outDir) {
399
425
  file: path.basename(outPath),
400
426
  duration_ms: Date.now() - fileStart,
401
427
  });
402
- logger.log(logger.format.green(`✓ Signed: ${path.relative(projectRoot, outPath)}`));
428
+ // The proof line, in the mac lane's shape (hooks/notarize.js): the verify
429
+ // above aborts the job on a failure, so this line names the check that ran
430
+ // rather than claiming a signature nobody checked
431
+ // ([#918](https://github.com/Omega-JS-Stack/omega/issues/918)).
432
+ log.log(`Signed and verified: signtool verify /pa accepts ${path.basename(outPath)}.`);
403
433
 
404
434
  if (outPath.toLowerCase().endsWith('.exe')) {
405
435
  signedExes.push({ filePath: outPath, urlName: path.basename(outPath) });
@@ -414,9 +444,9 @@ async function signWithSigntool(targets, inDir, outDir) {
414
444
  const pkg = Manager.getPackage('project') || {};
415
445
  const version = pkg.version;
416
446
  if (!version) {
417
- logger.warn('Could not determine version from package.json — skipping latest.yml generation.');
447
+ log.warn('Could not determine version from package.json; skipping latest.yml generation.');
418
448
  } else {
419
- logger.log(`Generating Windows auto-updater feed (latest.yml) for ${signedExes.length} installer(s)...`);
449
+ log.log(`Generating Windows auto-updater feed (latest.yml) for ${signedExes.length} installer(s)...`);
420
450
  try {
421
451
  await writeUpdateInfo({
422
452
  signedExes,
@@ -428,8 +458,8 @@ async function signWithSigntool(targets, inDir, outDir) {
428
458
  // Don't fail the whole sign step if yml generation hits something unexpected —
429
459
  // the signed exe is still valid + uploadable. But this IS a real problem
430
460
  // because auto-updates won't work, so log loudly.
431
- logger.error(`latest.yml generation failed: ${e.message}`);
432
- logger.error(' Signed binary is OK but Windows auto-updater will not see this release.');
461
+ log.error(`latest.yml generation failed: ${e.message}`);
462
+ log.error(' Signed binary is OK but Windows auto-updater will not see this release.');
433
463
  }
434
464
  }
435
465
  }
@@ -554,6 +584,7 @@ module.exports.signWithRetry = signWithRetry;
554
584
  module.exports.isTransientSignFailure = isTransientSignFailure;
555
585
  module.exports.SIGN_ATTEMPTS = SIGN_ATTEMPTS;
556
586
  module.exports.SIGN_RETRY_DELAY_MS = SIGN_RETRY_DELAY_MS;
587
+ module.exports.SIGN_TIME_LIMIT_MS = SIGN_TIME_LIMIT_MS;
557
588
 
558
589
  // Exports for the Windows-gated end-to-end suite — the halves that shell out to
559
590
  // signtool for real. They only run where signtool and the EV token are
@@ -13,9 +13,10 @@ module.exports = async function (options) {
13
13
  // This process is a TEST RUN, and everything it spawns inherits that. The box's
14
14
  // mutating verbs read it and refuse to touch a real runner home
15
15
  // ([#337](https://github.com/Omega-JS-Stack/omega/issues/337): a suite once ran
16
- // a real `runner install` on the signing box). Deliberately NOT the canonical
17
- // OMEGA_TEST_MODE, which means "the app under test runs in testing mode" and
18
- // would change what the boot lane's production build compiles.
16
+ // a real `runner install` on the signing box). Deliberately a marker about THIS
17
+ // process, never a word about the app under test: the environment the thing
18
+ // being tested RUNS in is `OMEGA_ENVIRONMENT`, and the runners name it on their
19
+ // own children alone (#925).
19
20
  process.env.OMEGA_TEST_RUNNER = '1';
20
21
 
21
22
  // Tee all test output to <projectRoot>/logs/test.log (ANSI-stripped) — mirrors