@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
@@ -23,19 +23,20 @@ module.exports = defineCases({
23
23
  },
24
24
  },
25
25
  {
26
- name: 'isTesting() returns true (we are running under OMEGA_TEST_MODE=true)',
26
+ name: 'isTesting() returns true (this lane named OMEGA_ENVIRONMENT=testing)',
27
27
  run: (ctx) => {
28
28
  ctx.expect(window.__emTestManager.isTesting()).toBe(true);
29
29
  },
30
30
  },
31
31
  {
32
- name: 'isDevelopment() works (renderer has no app.isPackaged so it falls through)',
32
+ name: 'isDevelopment() answers the one input, with no renderer-specific sniff',
33
33
  run: (ctx) => {
34
- // In renderer process, `electron.app` is undefined, so the helper falls back
35
- // to NODE_ENV / config.em.environment. The test harness seeds config with
36
- // environment='production' so this should report false.
34
+ // Nothing here reads app.isPackaged or NODE_ENV any more (#817): the
35
+ // preload has a process, so the lane's OMEGA_ENVIRONMENT answers, and a
36
+ // real renderer bundle answers from the baked config.environment.
37
37
  const v = window.__emTestManager.isDevelopment();
38
38
  ctx.expect(typeof v).toBe('boolean');
39
+ ctx.expect(v).toBe(false);
39
40
  },
40
41
  },
41
42
  {
@@ -44,7 +45,7 @@ module.exports = defineCases({
44
45
  const dev = window.__emTestManager.isDevelopment();
45
46
  const test = window.__emTestManager.isTesting();
46
47
  const prod = window.__emTestManager.isProduction();
47
- // We run under OMEGA_TEST_MODE=true → testing wins; dev and prod are both false.
48
+ // This lane named testing; dev and prod are both false.
48
49
  ctx.expect(test).toBe(true);
49
50
  ctx.expect(dev).toBe(false);
50
51
  ctx.expect(prod).toBe(false);
@@ -62,16 +63,18 @@ module.exports = defineCases({
62
63
  },
63
64
  },
64
65
  {
65
- name: 'getEnvironment(): testing (OMEGA_TEST_MODE) takes precedence over config.em.environment',
66
+ name: 'getEnvironment(): the lane variable wins over the baked config.environment',
66
67
  run: (ctx) => {
67
- // The test runner sets OMEGA_TEST_MODE=true, which wins over any config override —
68
- // getEnvironment() always reports 'testing' here regardless of em.environment.
69
- window.__emTestManager.setConfig('em.environment', 'production');
68
+ // The test runner names OMEGA_ENVIRONMENT=testing, and this preload has a
69
+ // process to read it from, so it wins over whatever the artifact was
70
+ // baked as (#817). A real renderer bundle has no process and takes the
71
+ // baked word, which is the other half of the same one-input rule.
72
+ window.__emTestManager.setConfig('environment', 'production');
70
73
  ctx.expect(window.__emTestManager.getEnvironment()).toBe('testing');
71
- window.__emTestManager.setConfig('em.environment', 'development');
74
+ window.__emTestManager.setConfig('environment', 'development');
72
75
  ctx.expect(window.__emTestManager.getEnvironment()).toBe('testing');
73
76
  // Reset.
74
- window.__emTestManager.setConfig('em.environment', 'production');
77
+ window.__emTestManager.setConfig('environment', 'production');
75
78
  },
76
79
  },
77
80
  {
@@ -104,7 +107,7 @@ module.exports = defineCases({
104
107
  },
105
108
  },
106
109
  {
107
- name: 'getWebsiteUrl: dev → the classic dev origin https://localhost:4000',
110
+ name: 'getWebsiteUrl: dev → https://localhost:4000, from the baked classic map',
108
111
  run: (ctx) => {
109
112
  ctx.expect(window.__emTestManager.getWebsiteUrl('development')).toBe('https://localhost:4000');
110
113
  },
@@ -118,9 +121,9 @@ module.exports = defineCases({
118
121
  {
119
122
  name: 'getWebsiteUrl: no-arg resolves local under testing; explicit arg overrides',
120
123
  run: (ctx) => {
121
- // The renderer always runs under OMEGA_TEST_MODE (testing wins), so the no-arg form
122
- // correctly resolves LOCAL regardless of config — that's the safety guarantee.
123
- window.__emTestManager.setConfig('em.environment', 'production');
124
+ // This lane is testing, so the no-arg form resolves LOCAL regardless of
125
+ // what the artifact was baked as: that's the safety guarantee.
126
+ window.__emTestManager.setConfig('environment', 'production');
124
127
  ctx.expect(window.__emTestManager.getWebsiteUrl()).toBe('https://localhost:4000');
125
128
  // An explicit env arg bypasses the current environment and pins the mapping.
126
129
  ctx.expect(window.__emTestManager.getWebsiteUrl('production')).toBe('https://example.com');
@@ -18,18 +18,18 @@ function gulpCommand(task) {
18
18
  // loads the whole command surface.
19
19
  //
20
20
  // There is no `setup` step any more (#675): the local half runs inside the gulp
21
- // `defaults` task, which is the first step of every gulp build. What the plans
22
- // still need before signing is the Apple artifacts (#678) — `certs`.
21
+ // `defaults` task, which is the first step of every gulp build. There is no
22
+ // `certs` copy step either ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)):
23
+ // signing material is READ IN PLACE from the signing tree, and the env load
24
+ // derives the paths to it once (utils/load-env.js).
23
25
  const RUNNERS = {
24
26
  clean: (options) => require('../commands/clean.js')(options),
25
- certs: () => {
26
- const Manager = new (require('../build.js'));
27
- require('./deliver-certs.js').deliverTargetCerts({
28
- projectDir: Manager.getRootPath('project'),
29
- logger: Manager.logger('certs'),
30
- });
31
- },
32
- 'validate-certs': (options) => require('../commands/validate-certs.js')(options),
27
+ // The credentials the brand's DECLARED formats cannot ship without (#867),
28
+ // refused before a minute of build time. Publish declares this step; a bare
29
+ // build does not, because a build puts nothing in front of users.
30
+ 'ship-keys': () => require('./ship-keys.js').assertShipKeys(),
31
+ // `strict` rides the STEP: publish declares it (#891), a bare verb does not.
32
+ 'validate-certs': (options, step) => require('../commands/validate-certs.js')({ ...options, strict: step.strict === true || options.strict === true }),
33
33
  gulp: (options, step) => execute(gulpCommand(step.task), { log: true }),
34
34
  };
35
35
 
@@ -1,16 +1,19 @@
1
- // Shared GitHub helpers used by push-secrets and the release pipeline.
1
+ // The Octokit factory for the release pipeline (uploads, the draft flip).
2
2
  //
3
- // WHICH repo a verb addresses is not decided here: the app repo is
4
- // @omega.js/config's `brandRepo` and the releases repo its `releasesRepo`, from
5
- // config/omega.json5 alone (#799). The old package.json/git-remote discovery is
6
- // gone: a brand nested in another repo (a target inside a brand monorepo, the
7
- // playground inside the framework monorepo) resolved to the ENCLOSING repo.
3
+ // WHICH repo a verb addresses is not decided here: the source repo is
4
+ // @omega.js/config's `sourceRepo` and the releases repo its `releasesRepo`,
5
+ // from config/omega.json5 alone (#799). The old package.json/git-remote
6
+ // discovery is gone: a brand nested in another repo (a target inside a brand
7
+ // monorepo, the playground inside the framework monorepo) resolved to the
8
+ // ENCLOSING repo.
9
+ //
10
+ // CREATING a repo is not decided here either (#883): every repo a brand owns
11
+ // is created and reconciled by `@omega.js/devkit/github-repo`'s ensureRepo, the
12
+ // one home the manage walk and the web deploy use too. This file's own copy is
13
+ // gone with it.
8
14
  //
9
15
  // Octokit factory: returns an authenticated client when GH_TOKEN is set. Returns null otherwise so
10
16
  // callers can choose to no-op or bail with a friendly message.
11
- //
12
- // Repo ensure: idempotently create a repo under <owner> if it doesn't exist. Used by the
13
- // deploy precheck to auto-provision the brand's public releases repo.
14
17
 
15
18
  // silent: true — pass a no-op logger so transient 404s during polling (e.g. fetching
16
19
  // in-progress job logs) don't spam the console. Errors still surface via thrown rejections.
@@ -26,48 +29,6 @@ function getOctokit(opts = {}) {
26
29
  return new Octokit(config);
27
30
  }
28
31
 
29
- // Idempotent: returns true if the repo exists (created or already there), false on failure.
30
- // Honors the `owner` distinction between user vs. org — the API endpoints differ.
31
- async function ensureRepo(octokit, owner, repo, opts = {}) {
32
- const description = opts.description || '';
33
- const isPrivate = opts.private === true;
34
-
35
- try {
36
- await octokit.rest.repos.get({ owner, repo });
37
- return { created: false, exists: true };
38
- } catch (err) {
39
- if (err.status !== 404) throw err;
40
- }
41
-
42
- // Need to create. Check if owner is the authenticated user or an org.
43
- let isOrg = false;
44
- try {
45
- const { data: me } = await octokit.rest.users.getAuthenticated();
46
- isOrg = me.login.toLowerCase() !== owner.toLowerCase();
47
- } catch (e) {
48
- isOrg = true;
49
- }
50
-
51
- const params = {
52
- name: repo,
53
- description,
54
- private: isPrivate,
55
- has_issues: true,
56
- has_projects: false,
57
- has_wiki: false,
58
- auto_init: true,
59
- };
60
-
61
- if (isOrg) {
62
- await octokit.rest.repos.createInOrg({ org: owner, ...params });
63
- } else {
64
- await octokit.rest.repos.createForAuthenticatedUser(params);
65
- }
66
-
67
- return { created: true, exists: true };
68
- }
69
-
70
32
  module.exports = {
71
33
  getOctokit,
72
- ensureRepo,
73
34
  };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The desktop target's ONE env load
3
+ * ([#891](https://github.com/Omega-JS-Stack/omega/issues/891)).
4
+ *
5
+ * Two boots populate `process.env` for a desktop target: the CLI (`src/cli.js`)
6
+ * and gulp (`src/gulp/main.js`, which runs as its own process and is invoked
7
+ * directly by `npm start`). Both call HERE, so a signing path is derived once,
8
+ * by one rule, and every reader after it (the build, `validate-certs`, the
9
+ * deploy precheck's secrets publish) reads the answer instead of deriving its
10
+ * own. The derivation itself is `@omega.js/devkit/signing-env`: the signing
11
+ * tree, company tier first.
12
+ *
13
+ * The lines it prints name PATHS, never contents: `CSC_LINK derived from
14
+ * <path>` is exactly what a signing failure is diagnosed from. WHEN they print
15
+ * is the caller's (gulp holds them until its log tee is attached, so build.log
16
+ * carries them); the SHAPE of them is here, once.
17
+ */
18
+ const { loadEnv } = require('../vendor/config/index.js');
19
+ const { deriveSigningEnv } = require('../vendor/devkit/signing-env.js');
20
+
21
+ /**
22
+ * Load the `.env` cascade for a desktop target and derive its signing paths.
23
+ *
24
+ * @param {string} projectRoot - The target root.
25
+ * @param {object} [options]
26
+ * @param {object} [options.logger] - `{ log, warn }`; the result is reported at
27
+ * once when given, and handed back for the caller to report otherwise.
28
+ * @param {object} [options.env] - The env map to populate (default process.env).
29
+ * @returns {{ derived: Array<{ key: string, value: string }>, problems: Array<{ key: string, reason: string }> }}
30
+ */
31
+ function loadDesktopEnv(projectRoot, options = {}) {
32
+ const env = options.env || process.env;
33
+
34
+ // The cascade first (shell > target .env > brand .env > company .env). The
35
+ // target name delivers the schema's `deliverAs` renames (#678).
36
+ loadEnv(projectRoot, { target: 'desktop' });
37
+
38
+ const result = deriveSigningEnv({ env, targetDir: projectRoot });
39
+
40
+ if (options.logger) {
41
+ reportSigningEnv(result, options.logger);
42
+ }
43
+
44
+ return result;
45
+ }
46
+
47
+ /**
48
+ * Say what the derivation answered: one line per key, paths only.
49
+ *
50
+ * @param {{ derived: Array, problems: Array }} result - What loadDesktopEnv returned.
51
+ * @param {object} logger - `{ log, warn }`.
52
+ */
53
+ function reportSigningEnv({ derived, problems }, logger) {
54
+ for (const { key, value } of derived) {
55
+ logger.log(`${key} derived from ${value}`);
56
+ }
57
+
58
+ // A file that is THERE and cannot be used is never silent: the key stays
59
+ // unset (Keychain discovery stays the default), and the reason is said out
60
+ // loud so the next rung's error is not the first anyone hears of it.
61
+ for (const { key, reason } of problems) {
62
+ logger.warn(`${key} not derived: ${reason}`);
63
+ }
64
+ }
65
+
66
+ module.exports = { loadDesktopEnv, reportSigningEnv };
@@ -1,106 +1,43 @@
1
- // Runtime mode helpers (@omega.js/backend-pattern), shared across all three context Managers
2
- // (main / renderer / preload) and the build-time Manager.
1
+ // Runtime mode helpers, shared across all three context Managers (main /
2
+ // renderer / preload) and the build-time Manager.
3
3
  //
4
- // Three MUTUALLY EXCLUSIVE environments — exactly one is true:
5
- // isDevelopment() — app run from source / being developed (unpackaged, e.g. `electron .`,
6
- // gulp dev), and NOT testing. Authoritative signal: `app.isPackaged ===
7
- // false`. Falls back to NODE_ENV when `app` isn't available (preload,
8
- // build-time scripts, renderer).
9
- // isTesting() — app being tested (@omega.js/desktop's test framework running this process). Single
10
- // canonical env var: `OMEGA_TEST_MODE=true`. TAKES PRECEDENCE over dev:
11
- // a test run is unpackaged, but it's a TEST, not development.
12
- // isProduction() — app packaged & distributed to users (`app.isPackaged === true`),
13
- // and NOT testing. A real positive check — NOT `!isDevelopment()`.
4
+ // The environment half is NOT implemented here any more
5
+ // ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)). It is
6
+ // @omega.js/config's `environment.js`, the ONE module every OMEGA target
7
+ // answers from, and this file only re-exports it beside desktop's own
8
+ // getVersion(). The four copies this replaced each carried their own signal
9
+ // list and their own default, and they disagreed: desktop's answered
10
+ // `production` with no signal while the extension's answered `development`, so
11
+ // a desktop dev boot bundled itself as a production artifact.
14
12
  //
15
- // To gate "anything non-production" (skip OS side effects, isolate data) use
16
- // `!isProduction()` or `isDevelopment() || isTesting()` intentionally — never assume two
17
- // values.
13
+ // The contract, in full, is the shared module's:
14
+ // getEnvironment() reads ONE input, the `OMEGA_ENVIRONMENT` variable in Node
15
+ // and the baked `OMEGA_BUILD_JSON.config.environment` in a renderer (which
16
+ // has no process.env), and a context with neither throws by name. Nothing
17
+ // sniffs `app.isPackaged` or NODE_ENV any more: the lane that boots or builds
18
+ // this app names the environment, once, where it resolves its config
19
+ // (src/build.js's getConfig for the build lane, main.js's initialize for the
20
+ // packaged runtime).
18
21
  //
19
- // Use these whenever behavior should differ by *what kind of process* you're in —
20
- // shorter timeouts in tests, DevTools menu items only in dev, prompts suppressed in
21
- // tests. Don't use them for "should we hit dev or prod backends" — that's a config
22
- // concern; use `getEnvironment()` for that (in build.js).
22
+ // isDevelopment() / isProduction() / isTesting() DERIVE from it, so they can
23
+ // never disagree with it and exactly one is true. isProduction() is a real
24
+ // positive check, never `!isDevelopment()`. Gate "anything non-production"
25
+ // with `!isProduction()` or `isDevelopment() || isTesting()` intentionally.
23
26
  //
24
- // Renderer caveat: Electron's `app` API isn't available in renderer processes
25
- // (the renderer's `electron` import surface has `ipcRenderer`, `contextBridge`,
26
- // etc. — no `app`). Same in preload. So those contexts fall back to NODE_ENV /
27
- // config.em.environment. To get a renderer-truthful signal, the consumer can have
28
- // main inject the value into `OMEGA_BUILD_JSON.runtime` at build time (future
29
- // enhancement); for now, NODE_ENV being inherited from the parent process is the
30
- // pragmatic answer.
31
- //
32
- // In plain Node (gulp tasks, CLI commands), `require('electron')` returns the
33
- // binary path as a string — no `app` object — so the same `app && typeof
34
- // app.isPackaged === 'boolean'` check naturally falls through to NODE_ENV.
35
-
36
- // The three environment checks are MUTUALLY EXCLUSIVE — exactly one is true:
37
- // isDevelopment() — app run from source / being developed (unpackaged, NOT testing).
38
- // isTesting() — app being tested (OMEGA_TEST_MODE=true). Takes precedence over dev.
39
- // isProduction() — app packaged & distributed to users (real positive check, NOT testing).
40
- // Testing wins first: a test run is unpackaged, but it's a TEST, not development.
41
-
42
- // getEnvironment() — the SINGLE SOURCE OF TRUTH. Reads every raw signal and resolves to
43
- // exactly ONE of 'development' | 'testing' | 'production' (mutually exclusive; testing wins).
44
- // Precedence: testing → explicit config override → Electron app.isPackaged → build-time signal.
45
- function getEnvironment() {
46
- // 1. Testing wins — set by @omega.js/desktop's test runners (OMEGA_TEST_MODE=true), regardless of packaged state.
47
- if (process.env.OMEGA_TEST_MODE === 'true') return 'testing';
48
-
49
- // 2. An explicit config.em.environment override — the consumer's deliberate decision. It beats
50
- // the auto-detected app.isPackaged (e.g. a packaged app the consumer wants to treat as dev).
51
- const cfgEnv = this?.config?.em?.environment;
52
- if (cfgEnv === 'development' || cfgEnv === 'testing' || cfgEnv === 'production') return cfgEnv;
53
-
54
- // 3. In Electron main, `app.isPackaged` is the authoritative runtime signal (more accurate
55
- // than env vars). In renderer/preload, `electron` has no `app`; from plain Node (gulp/CLI),
56
- // `require('electron')` returns the binary path string — `.app` is undefined in both, so we
57
- // fall through to the build signals below.
58
- // In renderer bundles (target: 'web'), require is not available — skip entirely.
59
- if (typeof require !== 'undefined') {
60
- try {
61
- const { app } = require('electron');
62
- if (app && typeof app.isPackaged === 'boolean') return app.isPackaged ? 'production' : 'development';
63
- } catch (_) {}
64
- }
65
-
66
- // 4. Build-time / Node signals. OMEGA_BUILD_MODE=true is set during a production build
67
- // (npm run build / npm run release); NODE_ENV=development is the dev fallback.
68
- if (process.env.OMEGA_BUILD_MODE === 'true') return 'production';
69
- if (process.env.NODE_ENV === 'development') return 'development';
70
-
71
- // 5. Default: production. @omega.js/desktop's deployed RUNTIME can reach here without a dev signal — a
72
- // packaged app whose `app.isPackaged` somehow didn't resolve is still a shipped binary,
73
- // so production is the safe assumption for a distributed artifact. (Contrast UJM/BXM,
74
- // whose deployed artifacts always carry their signal, so they default to development —
75
- // a bare context there is just build tooling.)
76
- return 'production';
77
- }
78
-
79
- // The three checks DERIVE from getEnvironment() — they never read raw signals, so they can
80
- // never disagree with it. isDevelopment() is NOT true in testing; isProduction() is a real
81
- // positive check (never `!isDevelopment()`).
82
- function isDevelopment() {
83
- return getEnvironment.call(this) === 'development';
84
- }
85
-
86
- function isProduction() {
87
- return getEnvironment.call(this) === 'production';
88
- }
89
-
90
- function isTesting() {
91
- return getEnvironment.call(this) === 'testing';
92
- }
27
+ // The shared module requires nothing, so it rides the renderer bundle (a
28
+ // browser context with no Node built-ins) exactly as this file always has.
29
+ const environment = require('../vendor/config/environment.js');
93
30
 
94
- // `getVersion()` — returns the app's version string. Sources, in priority order:
95
- // 1. `electron.app.getVersion()` when running inside Electron (main process —
31
+ // `getVersion()` returns the app's version string. Sources, in priority order:
32
+ // 1. `electron.app.getVersion()` when running inside Electron (main process,
96
33
  // authoritative; reads from the packaged app's package.json baked into asar).
97
34
  // 2. `<cwd>/package.json#version` for build-time scripts / non-Electron contexts.
98
35
  // 3. null when neither resolves.
99
36
  //
100
- // Renderer caveat: same as isDevelopment — `electron.app` isn't available in renderer,
101
- // so the fallback to `process.cwd()/package.json` won't find anything useful in a
102
- // packaged app. Renderers that need the version should ask main via IPC, or read
103
- // `OMEGA_BUILD_JSON.package.version` (baked in by the bundle task's `define`).
37
+ // Renderer caveat: `electron.app` isn't available in renderer, so the fallback to
38
+ // `process.cwd()/package.json` won't find anything useful in a packaged app.
39
+ // Renderers that need the version should ask main via IPC, or read
40
+ // `OMEGA_BUILD_JSON.package.version` (baked in by the bundle task).
104
41
  function getVersion() {
105
42
  if (typeof require !== 'undefined') {
106
43
  try {
@@ -121,27 +58,22 @@ function getVersion() {
121
58
  }
122
59
 
123
60
  // Mix the helpers into a Manager constructor's prototype + the constructor itself
124
- // (so `Manager.isTesting()` works statically too, matching @omega.js/backend's pattern). getEnvironment()
125
- // is attached here too so all four entry points share one resolver — the URL helpers in
126
- // url-helpers.js depend on this.getEnvironment() existing.
61
+ // (so `Manager.isTesting()` works statically too). The environment four come from
62
+ // the shared module's own attachTo(), so every desktop entry point and every
63
+ // sibling framework hangs the identical functions; getVersion() is desktop's and
64
+ // is attached beside them. The URL helpers in url-helpers.js depend on
65
+ // this.getEnvironment() existing, so this attachTo() runs first.
127
66
  function attachTo(Manager) {
128
- Manager.prototype.isDevelopment = isDevelopment;
129
- Manager.prototype.isProduction = isProduction;
130
- Manager.prototype.isTesting = isTesting;
131
- Manager.prototype.getEnvironment = getEnvironment;
132
- Manager.prototype.getVersion = getVersion;
133
- Manager.isDevelopment = isDevelopment;
134
- Manager.isProduction = isProduction;
135
- Manager.isTesting = isTesting;
136
- Manager.getEnvironment = getEnvironment;
137
- Manager.getVersion = getVersion;
67
+ environment.attachTo(Manager);
68
+ Manager.prototype.getVersion = getVersion;
69
+ Manager.getVersion = getVersion;
138
70
  }
139
71
 
140
72
  module.exports = {
141
73
  attachTo,
142
- isDevelopment,
143
- isProduction,
144
- isTesting,
145
- getEnvironment,
74
+ isDevelopment: environment.isDevelopment,
75
+ isProduction: environment.isProduction,
76
+ isTesting: environment.isTesting,
77
+ getEnvironment: environment.getEnvironment,
146
78
  getVersion,
147
79
  };
@@ -0,0 +1,37 @@
1
+ // The ONE translation of Node's platform word into OMEGA's platform
2
+ // vocabulary ([#867](https://github.com/Omega-JS-Stack/omega/issues/867)).
3
+ //
4
+ // OMEGA says `mac`, `windows`, `linux` everywhere it speaks for itself: the
5
+ // config declaration (`platforms.windows.formats.nsis`), the icon dirs
6
+ // (`config/icons/mac/`), the workflow's `platforms` input, the site's
7
+ // `/download/<platform>/<format>` links, and a stored login record. Node says
8
+ // `darwin`/`win32`/`linux`, and electron-builder says `mac`/`win`/`linux`.
9
+ // Those are FOREIGN vocabularies: each is translated at exactly one point, and
10
+ // for Node's that point is here.
11
+ //
12
+ // A bare OS branch (`if (process.platform === 'darwin')`) needs no translation
13
+ // and stays as it is: it reads Node's word to decide Node's behavior and never
14
+ // produces a name anything else consumes. This function is for the places that
15
+ // turn the OS into a NAME: an icon directory, a release directory, the platform
16
+ // a deploy reports building.
17
+
18
+ // Node's process.platform → OMEGA's word. Electron runs on exactly these three.
19
+ const NODE_PLATFORMS = {
20
+ darwin: 'mac',
21
+ win32: 'windows',
22
+ linux: 'linux',
23
+ };
24
+
25
+ /**
26
+ * This machine's platform in OMEGA's vocabulary.
27
+ *
28
+ * @param {string} [nodePlatform] - A `process.platform` value (defaults to this
29
+ * process's own).
30
+ * @returns {string|null} 'mac' | 'windows' | 'linux', or null for an OS OMEGA
31
+ * has no word for (the caller decides whether that is fatal).
32
+ */
33
+ function desktopPlatform(nodePlatform = process.platform) {
34
+ return NODE_PLATFORMS[nodePlatform] || null;
35
+ }
36
+
37
+ module.exports = { desktopPlatform, NODE_PLATFORMS };
@@ -226,7 +226,8 @@ function runnerLogFile(home) {
226
226
 
227
227
  // Is this process a TEST run? `OMEGA_TEST_RUNNER` is set by `omega test` for the
228
228
  // whole run (and inherited by the electron/boot children); `OMEGA_TEST_MODE` is
229
- // the canonical signal those children already carried. Either one means: no
229
+ // the marker a sibling framework's runner or a consumer's own test script sets
230
+ // (desktop's runners name the environment instead, #925). Either one means: no
230
231
  // command here may touch the machine's real runner home.
231
232
  //
232
233
  // It reads process.env, ALWAYS, and takes no environment argument. An injected
@@ -0,0 +1,149 @@
1
+ // The box's half of "a self-hosted signer refuses any job that is not a trusted
2
+ // dispatch" ([#875](https://github.com/Omega-JS-Stack/omega/issues/875)): the
3
+ // three files `omega runner install` writes beside the runner, so the guard the
4
+ // listener runs before every job is CONFIGURATION on the box rather than a list
5
+ // baked into the shared desktop template.
6
+ //
7
+ // A runner is registered to ONE owner and serves only that owner's repos, so
8
+ // the guard is per box, configured by the box owner (Ian 2026-09-10). The files:
9
+ //
10
+ // <runner home>\job-started.js the hook itself, a copy of src/runner/job-started.js
11
+ // <runner home>\allowed-repos.txt one `owner/name` (or `owner/*`) per line
12
+ // <runner home>\allowed-actors.txt one GitHub login per line
13
+ //
14
+ // and the path of the first one is delivered to each registered runner through
15
+ // its own `<runner dir>\.env` as `ACTIONS_RUNNER_HOOK_JOB_STARTED`, the same
16
+ // file the private HOME travels in
17
+ // ([#807](https://github.com/Omega-JS-Stack/omega/issues/807)).
18
+ //
19
+ // Where the lists COME from at setup: the orgs this box registered against
20
+ // (`<org>/*`, which is exactly the scope a registration already has) and the
21
+ // `GH_TOKEN` user's own login. Narrowing `<org>/*` to individual repos, or
22
+ // adding a second account, is an edit of the file, which is why an org already
23
+ // answered for, by a wildcard or by any repo line, is never appended again.
24
+ const path = require('path');
25
+ const jetpack = require('fs-jetpack');
26
+
27
+ // The hook owns the names of its own list files and the way it READS them, so
28
+ // the writer imports both rather than restating either: what this appends is
29
+ // what the hook sees, by construction. Requiring the hook here runs nothing
30
+ // (its work sits behind `require.main === module`), and the hook file itself
31
+ // stays builtins-only for the runner home it is copied into.
32
+ const { readList, REPOS_FILE, ACTORS_FILE } = require('../runner/job-started.js');
33
+
34
+ const HOOK_FILE = 'job-started.js';
35
+
36
+ const REPOS_HEADER = [
37
+ '# The repositories this box will sign for (#875).',
38
+ '# One `owner/name` per line; `owner/*` is every repo of one owner.',
39
+ '# `npx omega runner install` appends a line for an org it registered and has',
40
+ '# no line for yet, so narrowing one to single repos by hand sticks.',
41
+ '',
42
+ ].join('\n');
43
+
44
+ const ACTORS_HEADER = [
45
+ '# The GitHub logins this box will sign for (#875): GITHUB_ACTOR must be one.',
46
+ '# One login per line. A `workflow_dispatch` carries the deploy token\'s user,',
47
+ '# which is the same account, so one line normally answers for every deploy.',
48
+ '',
49
+ ].join('\n');
50
+
51
+ /**
52
+ * The three files, by absolute path.
53
+ *
54
+ * @param {string} home - The runner home.
55
+ * @returns {{hook: string, repos: string, actors: string}} The paths.
56
+ */
57
+ function jobGuardPaths(home) {
58
+ return {
59
+ hook: path.join(home, HOOK_FILE),
60
+ repos: path.join(home, REPOS_FILE),
61
+ actors: path.join(home, ACTORS_FILE),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * The two allow lists, which are the box's CONFIGURATION: an uninstall keeps
67
+ * them the way it keeps the box's `.env`, so a re-install never asks again and
68
+ * a hand-narrowed list is never destroyed. The hook script is install state and
69
+ * goes with the rest.
70
+ *
71
+ * @param {string} home - The runner home.
72
+ * @returns {string[]} The two list paths.
73
+ */
74
+ function jobGuardListFiles(home) {
75
+ const { repos, actors } = jobGuardPaths(home);
76
+ return [repos, actors];
77
+ }
78
+
79
+ // Append the lines the file does not answer for yet, and nothing else: a file
80
+ // that already answers for every line is not rewritten at all, so running the
81
+ // setup twice writes the same bytes once.
82
+ function appendMissing(file, header, wanted, answered) {
83
+ const existing = readList(file);
84
+ const missing = wanted.filter((line) => !existing.some((entry) => answered(entry, line)));
85
+ if (missing.length === 0 && jetpack.exists(file) === 'file') return [];
86
+
87
+ const body = existing.length === 0 && jetpack.exists(file) !== 'file'
88
+ ? `${header}${missing.join('\n')}\n`
89
+ : `${(jetpack.read(file) || '').replace(/\n*$/, '\n')}${missing.join('\n')}\n`;
90
+ jetpack.write(file, body);
91
+ return missing;
92
+ }
93
+
94
+ /**
95
+ * Lay the guard down beside the runner, idempotently.
96
+ *
97
+ * The hook script is COPIED from this package on every run, so a box picks up a
98
+ * framework update the next time it installs or starts; the allow lists are the
99
+ * operator's and are only ever appended to.
100
+ *
101
+ * @param {string} home - The runner home.
102
+ * @param {object} [options]
103
+ * @param {string[]} [options.orgs] - The orgs this box is registered against.
104
+ * @param {string} [options.actor] - The GH_TOKEN user's login (absent = leave the actors file alone).
105
+ * @param {object} [options.logger] - Logger for the one line each write prints.
106
+ * @returns {{hook: string, repos: string, actors: string, added: {repos: string[], actors: string[]}}} The paths, and what was appended.
107
+ */
108
+ function ensureRunnerJobGuard(home, options) {
109
+ const { orgs = [], actor, logger } = options || {};
110
+ const paths = jobGuardPaths(home);
111
+
112
+ jetpack.copy(path.join(__dirname, '..', 'runner', HOOK_FILE), paths.hook, { overwrite: true });
113
+
114
+ // An org is answered for by a `<org>/*` line OR by any single repo of it: a
115
+ // person who narrowed `Omega-JS-Stack/*` to one repo does not get the
116
+ // wildcard back on the next install.
117
+ const addedRepos = appendMissing(
118
+ paths.repos,
119
+ REPOS_HEADER,
120
+ orgs.map((org) => `${org}/*`),
121
+ (entry, line) => entry.split('/')[0].toLowerCase() === line.split('/')[0].toLowerCase(),
122
+ );
123
+
124
+ // No actor (an offline heal, or a `GET /user` that did not answer) writes the
125
+ // file's header and nothing else: the operator sees the file the hook names,
126
+ // and an empty list refuses every job rather than passing one.
127
+ const addedActors = appendMissing(
128
+ paths.actors,
129
+ ACTORS_HEADER,
130
+ actor ? [actor] : [],
131
+ (entry, line) => entry.toLowerCase() === line.toLowerCase(),
132
+ );
133
+
134
+ if (logger) {
135
+ if (addedRepos.length > 0) logger.log(` Job guard: added ${addedRepos.join(', ')} to ${paths.repos}`);
136
+ if (addedActors.length > 0) logger.log(` Job guard: added ${addedActors.join(', ')} to ${paths.actors}`);
137
+ }
138
+
139
+ return { ...paths, added: { repos: addedRepos, actors: addedActors } };
140
+ }
141
+
142
+ module.exports = {
143
+ HOOK_FILE,
144
+ REPOS_FILE,
145
+ ACTORS_FILE,
146
+ jobGuardPaths,
147
+ jobGuardListFiles,
148
+ ensureRunnerJobGuard,
149
+ };