@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
@@ -1,50 +1,64 @@
1
- // release — trigger the GitHub Actions Build & Release workflow + stream logs locally.
1
+ // release: trigger the GitHub Actions Build & Release workflow, then follow the
2
+ // run it started.
2
3
  //
3
4
  // Replaces the old "do it from my laptop" release flow with "let CI do it, but make it
4
5
  // feel local." User runs `npm run release` (or `npx omega release`) and gets:
5
- // 1. A workflow_dispatch POST to GH Actions on the brand's repo (owner/repo from the
6
- // config, and the workflow file the scaffold actually wrote: see dispatchTarget).
7
- // 2. A few seconds of waiting while GH spins up the run.
8
- // 3. Live polling of every job's logs at 5s intervals, printing NEW lines as they
9
- // arrive (job-prefixed) so it looks like streaming.
10
- // 4. Everything teed to <root>/logs/ci.log with ANSI codes preserved on stdout
11
- // and stripped from the file (matches the dev/build log convention).
12
- // 5. Exit 0 on success, 1 on any job failure.
13
- //
14
- // Why poll instead of stream? GH Actions doesn't expose live stdout — logs are only
15
- // fetchable AFTER each STEP completes. So "streaming" is a polite fiction: every 5s
16
- // we re-fetch each job's logs and diff against what we already printed.
6
+ // 1. The ONE deploy lane every target takes (`@omega.js/devkit/deploy`'s
7
+ // deployViaDispatch, #872, #915): the composed workflow files reach the
8
+ // default branch when they differ there (GitHub registers a workflow from
9
+ // that branch), the brand folder is force-pushed to `omega-deploy` (packed
10
+ // when it is linked), and the workflow is dispatched on that ref (the
11
+ // brand's own repo and the composed workflow name: devkit's
12
+ // dispatchTarget, #847).
13
+ // 2. The ONE run follower every dispatched deploy uses
14
+ // (`@omega.js/devkit/deploy-follow`, [#873](https://github.com/Omega-JS-Stack/omega/issues/873)):
15
+ // it finds the run, polls it, prints each job's log with the job's name in
16
+ // front, and exits 1 on any conclusion but success. This file owned that
17
+ // follower alone until #873 gave the other three targets the same one.
18
+ // 3. Everything teed to <root>/logs/deploy.log, the one name every target's
19
+ // deploy writes (docs/shared/logging.md), ANSI stripped from the file.
17
20
 
18
21
  const path = require('path');
19
- const fs = require('fs');
20
- const jetpack = require('fs-jetpack');
21
22
 
22
- const { getOctokit } = require('../utils/github.js');
23
- const { dispatchRepo } = require('../vendor/devkit/deploy.js');
23
+ const attachLogFile = require('../utils/attach-log-file.js');
24
+ const { deployViaDispatch, dispatchTarget, laneLabel, resolveToken } = require('../vendor/devkit/deploy.js');
25
+ const { followRun } = require('../vendor/devkit/deploy-follow.js');
26
+ const { targetNameFromDir } = require('../vendor/config/index.js');
24
27
  const Manager = new (require('../build.js'));
25
28
 
26
29
  const logger = Manager.logger('release');
27
30
 
28
- const POLL_INTERVAL_MS = 5000;
29
-
30
31
  module.exports = async function release(options = {}) {
31
32
  const projectRoot = process.cwd();
32
-
33
- if (!process.env.GH_TOKEN) {
34
- throw new Error('GH_TOKEN not set in env. Set it in .env (or shell) so we can dispatch the workflow.');
35
- }
36
-
37
- // silent: true — suppress octokit's default console output for transient 404s
38
- // (in-progress job logs return 404 until the step completes).
39
- const octokit = getOctokit({ silent: true });
40
- if (!octokit) {
41
- throw new Error('Failed to create octokit (missing GH_TOKEN?).');
33
+ // The DRY RUN arrives here too ([#895](https://github.com/Omega-JS-Stack/omega/issues/895)):
34
+ // this verb is desktop's one dispatch call site, so the plan a preview prints
35
+ // is built by the very call a real run sends, threading the ref and the
36
+ // snapshot sha the same way.
37
+ const dryRun = !!(options.dryRun || options['dry-run']);
38
+
39
+ // The whole verb in one file, from its first line (#873), the token refusal
40
+ // below included: a caller that hands the writers back afterwards then pops
41
+ // THIS layer, never the one under it. A no-op when `omega deploy` delegated
42
+ // here and already attached this exact path.
43
+ const logPath = path.join(projectRoot, 'logs', 'deploy.log');
44
+ attachLogFile(logPath);
45
+
46
+ // The ONE token chain devkit resolves for every target, `GH_TOKEN` →
47
+ // `GITHUB_TOKEN` → `gh auth token`, and the very one the follower below is
48
+ // handed: testing the bare variable refused a machine signed in with `gh`
49
+ // and holding no variable at all, which is most of them. A dry run needs
50
+ // none: every step under it is a read or a plan.
51
+ if (!dryRun && !resolveToken()) {
52
+ throw new Error('No GitHub token: set GH_TOKEN in .env (or GITHUB_TOKEN in the shell), or sign in with `gh auth login`, so we can dispatch the workflow.');
42
53
  }
43
54
 
44
- const { owner, repo, workflow: WORKFLOW_FILE } = dispatchTarget({ projectRoot, config: Manager.getConfig() });
45
-
46
- // Discover ref (current branch or override via --ref).
47
- const ref = options.ref || (await currentBranch(projectRoot)) || 'main';
55
+ // The CI dispatch address, from the config and the scaffold: the brand's own
56
+ // repo and the workflow file the target's ensure-target pass actually wrote
57
+ // (composed as `desktop-build.yml` at the brand root inside a monorepo, plain
58
+ // `build.yml` standalone). ONE helper for all four targets
59
+ // ([#847](https://github.com/Omega-JS-Stack/omega/issues/847)), where this
60
+ // file used to keep desktop's own copy of it.
61
+ const { owner, repo, workflow: WORKFLOW_FILE } = dispatchTarget({ projectRoot, config: Manager.getConfig(), workflow: 'build.yml' });
48
62
 
49
63
  // Optional --platforms / --platform flag forwarded as a workflow input. Accepts a
50
64
  // single value ('windows') or comma-separated list ('mac,linux'). Special value
@@ -53,234 +67,67 @@ module.exports = async function release(options = {}) {
53
67
  // workflows (without a `platforms` input declared) keep working unchanged.
54
68
  const platforms = options.platforms || options.platform || null;
55
69
 
56
- const dispatchArgs = { owner, repo, workflow_id: WORKFLOW_FILE, ref };
57
- if (platforms) dispatchArgs.inputs = { platforms: String(platforms) };
58
-
59
70
  const platformsLabel = platforms ? ` (platforms=${platforms})` : '';
60
- logger.log(`Triggering ${owner}/${repo} workflow ${WORKFLOW_FILE} on ref=${ref}${platformsLabel}...`);
61
-
62
- // 1. Mark a "before" timestamp so we can identify the new run we just dispatched.
63
- const before = new Date();
64
-
65
- // 2. Dispatch.
66
- await octokit.rest.actions.createWorkflowDispatch(dispatchArgs);
67
-
68
- // 3. Wait for the new run to appear (GH Actions takes a few seconds to register it).
69
- const run = await waitForNewRun({ octokit, owner, repo, after: before, workflowFile: WORKFLOW_FILE });
70
- if (!run) {
71
- throw new Error('Workflow dispatch succeeded but no new run appeared after 60s. Check GitHub Actions UI.');
72
- }
73
-
74
- logger.log(`Run started: ${run.html_url}`);
75
- require('../vendor/devkit/deploy-record.js').recordDeploy({ dir: projectRoot, target: 'desktop', detail: { method: 'release-dispatch' } });
76
-
77
- // 4. Set up the CI log tee.
78
- const logsDir = path.join(projectRoot, 'logs');
79
- jetpack.dir(logsDir);
80
- const ciLogPath = path.join(logsDir, 'ci.log');
81
- // Truncate so each release run starts fresh.
82
- fs.writeFileSync(ciLogPath, `# release run ${run.html_url}\n# started ${new Date().toISOString()}\n\n`);
83
- const logFile = fs.createWriteStream(ciLogPath, { flags: 'a' });
84
-
85
- // Spinner state. We tick every 250ms even between polls so the terminal feels alive.
86
- // The spinner line uses \r to overwrite itself; before printing real content we clear
87
- // the line, write content, then re-render the spinner. File output is unaffected.
88
- const startedAt = Date.now();
89
- const isTty = process.stdout.isTTY === true;
90
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
91
- let spinnerFrame = 0;
92
- let pollCount = 0;
93
- let latestState = null;
94
- let latestJobs = [];
95
-
96
- function clearSpinner() {
97
- if (!isTty) return;
98
- process.stdout.write('\r\x1b[2K');
99
- }
100
-
101
- function renderSpinner() {
102
- if (!isTty || !latestState) return;
103
- const frame = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length];
104
- const elapsed = formatElapsed(Date.now() - startedAt);
105
- const status = `${latestState.status}${latestState.conclusion ? ` (${latestState.conclusion})` : ''}`;
106
- const jobBits = latestJobs.map((j) => `${jobSymbol(j)} ${j.name}`).join(' ');
107
- const line = `${frame} ${status} · ${elapsed} · ${pollCount} polls · ${jobBits}`;
108
- // Truncate so we never wrap (which breaks the \r overwrite).
109
- const cols = process.stdout.columns || 120;
110
- const safe = line.length > cols - 1 ? line.slice(0, cols - 2) + '…' : line;
111
- process.stdout.write(`\r${safe}`);
112
- }
113
-
114
- // Print line(s) to stdout AND tee to file. Clears spinner first; spinner re-renders next tick.
115
- function print(line) {
116
- clearSpinner();
117
- process.stdout.write(line);
118
- logFile.write(stripAnsi(line));
71
+ if (!dryRun) {
72
+ logger.log(`Triggering ${owner}/${repo} workflow ${WORKFLOW_FILE}${platformsLabel}...`);
119
73
  }
120
74
 
121
- // Tick the spinner ~4 times/sec so it animates between polls.
122
- const spinnerTimer = isTty ? setInterval(() => {
123
- spinnerFrame += 1;
124
- renderSpinner();
125
- }, 250) : null;
75
+ // 1. Mark a "before" timestamp so the follower identifies the run we just
76
+ // dispatched rather than the one before it.
77
+ const since = new Date();
126
78
 
127
- // 5. Poll until completion. Track byte offsets per job so we only print new lines.
128
- const printedByJob = new Map(); // jobId -> chars printed so far
129
- let lastStatusLine = '';
130
-
131
- try {
132
- while (true) {
133
- pollCount += 1;
134
- const { data: state } = await octokit.rest.actions.getWorkflowRun({
135
- owner, repo, run_id: run.id,
136
- });
137
-
138
- // Pull all jobs. Each job has steps; each step has a step-level status.
139
- const { data: jobsResp } = await octokit.rest.actions.listJobsForWorkflowRun({
140
- owner, repo, run_id: run.id, per_page: 100,
141
- });
142
- const jobs = jobsResp.jobs || [];
143
- latestState = state;
144
- latestJobs = jobs;
145
-
146
- // For each job, fetch its logs (only available once steps complete; for in-progress
147
- // jobs, GH returns 404 or partial — we tolerate both).
148
- for (const job of jobs) {
149
- // Skip queued jobs entirely (no logs yet).
150
- if (job.status === 'queued') continue;
151
-
152
- let logsText = '';
153
- try {
154
- const { data } = await octokit.request('GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs', {
155
- owner, repo, job_id: job.id,
156
- });
157
- // Octokit returns the raw text content here.
158
- logsText = typeof data === 'string' ? data : Buffer.from(data || '').toString('utf8');
159
- } catch (e) {
160
- // 404 = logs not ready yet. Other errors we just skip until next tick.
161
- continue;
162
- }
163
-
164
- const printed = printedByJob.get(job.id) || 0;
165
- if (logsText.length > printed) {
166
- const fresh = logsText.slice(printed);
167
- // Prefix each line with the job name so interleaved output is readable.
168
- const prefix = `[${job.name}] `;
169
- for (const rawLine of fresh.split('\n')) {
170
- if (!rawLine) continue;
171
- print(`${prefix}${rawLine}\n`);
172
- }
173
- printedByJob.set(job.id, logsText.length);
174
- }
175
- }
176
-
177
- // Print a one-line status banner if it changed (no spam — only on transition).
178
- const banner = formatStatusBanner(state, jobs);
179
- if (banner !== lastStatusLine) {
180
- print(`\n${banner}\n`);
181
- lastStatusLine = banner;
182
- }
183
-
184
- if (state.status === 'completed') {
185
- clearSpinner();
186
- logFile.end();
187
- const success = state.conclusion === 'success';
188
- const symbol = success ? '✓' : '✗';
189
- logger.log(`${symbol} Run ${state.conclusion} — ${state.html_url}`);
190
- logger.log(`Logs: ${path.relative(projectRoot, ciLogPath)}`);
191
- if (!success) {
192
- process.exitCode = 1;
193
- throw new Error(`Release run failed (conclusion=${state.conclusion}). See ${ciLogPath} or ${state.html_url}.`);
194
- }
195
- return;
196
- }
197
-
198
- await sleep(POLL_INTERVAL_MS);
199
- }
200
- } finally {
201
- if (spinnerTimer) clearInterval(spinnerTimer);
202
- clearSpinner();
203
- }
204
- };
205
-
206
- // The CI dispatch address, from the config and the scaffold: the brand's APP
207
- // repo (`@omega.js/devkit/deploy`'s dispatchRepo, the one call web and the
208
- // extension deploy verbs make too) and the workflow file the target's
209
- // ensure-target pass actually wrote (composed as `desktop-build.yml` at the
210
- // brand root inside a monorepo, plain `build.yml` standalone:
211
- // `@omega.js/devkit/ci-workflows` owns that name for all four frameworks).
212
- // Never the git remote: a brand nested in another repo would dispatch on the
213
- // enclosing one, which for the playground is the framework monorepo itself.
214
- // Exported for tests.
215
- function dispatchTarget({ projectRoot, config }) {
216
- const { composedWorkflowName } = require('../vendor/devkit/ci-workflows.js');
217
- const { resolveSeedMode } = require('../vendor/config/index.js');
218
- const { owner, repo } = dispatchRepo(config);
219
-
220
- return {
79
+ // 2. Deliver the brand and dispatch, through the ONE lane (#872, #915): the
80
+ // composed workflows to the repo's default branch when they differ, the brand
81
+ // folder to `omega-deploy`, which is the ref this dispatches. An explicit
82
+ // `--ref` still wins.
83
+ const { plan, lane, sha } = await deployViaDispatch({
84
+ workflow: WORKFLOW_FILE,
221
85
  owner,
222
86
  repo,
223
- workflow: composedWorkflowName({
224
- targetDir: projectRoot,
225
- brandRoot: resolveSeedMode(projectRoot).brandRoot,
226
- workflow: 'build.yml',
227
- }),
228
- };
229
- }
230
-
231
- function formatElapsed(ms) {
232
- const totalSec = Math.floor(ms / 1000);
233
- const m = Math.floor(totalSec / 60);
234
- const s = totalSec % 60;
235
- return m > 0 ? `${m}m ${String(s).padStart(2, '0')}s` : `${s}s`;
236
- }
237
-
238
- function jobSymbol(j) {
239
- if (j.status === 'completed') return j.conclusion === 'success' ? '✓' : '✗';
240
- if (j.status === 'in_progress') return '…';
241
- if (j.status === 'queued') return '·';
242
- return '?';
243
- }
244
-
245
- async function waitForNewRun({ octokit, owner, repo, after, workflowFile }) {
246
- const deadline = Date.now() + 60_000;
247
- while (Date.now() < deadline) {
248
- const { data } = await octokit.rest.actions.listWorkflowRuns({
249
- owner, repo, workflow_id: workflowFile, per_page: 5, event: 'workflow_dispatch',
250
- });
251
- const runs = data.workflow_runs || [];
252
- // The newest run created strictly after our dispatch timestamp.
253
- const fresh = runs.find((r) => new Date(r.created_at).getTime() >= after.getTime() - 1000);
254
- if (fresh) return fresh;
255
- await sleep(2000);
87
+ dir: projectRoot,
88
+ ref: options.ref,
89
+ inputs: platforms ? { platforms: String(platforms) } : undefined,
90
+ // The brand root's one snapshot for the whole fan-out, when a brand-root
91
+ // deploy spawned this verb (#901): the push is done, so this run
92
+ // dispatches against that sha instead of pushing over it. Nobody types it.
93
+ snapshot: options.snapshot,
94
+ dryRun,
95
+ logger,
96
+ });
97
+
98
+ // The plan IS the dry run: what would be sent, and nothing sent.
99
+ if (dryRun) {
100
+ logger.log(`DRY RUN (${laneLabel(lane)}), would send:`);
101
+ logger.log(` ${plan.method} ${plan.url}`);
102
+ logger.log(` body: ${JSON.stringify(plan.body)}`);
103
+ logger.log(` then watch: ${plan.runsUrl}`);
104
+ return;
256
105
  }
257
- return null;
258
- }
259
106
 
260
- function formatStatusBanner(run, jobs) {
261
- const parts = jobs.map((j) => `${jobSymbol(j)} ${j.name}`);
262
- return `── ${run.status}${run.conclusion ? ` (${run.conclusion})` : ''} ── ${parts.join(' | ')}`;
263
- }
107
+ logger.log(`Dispatched ${WORKFLOW_FILE} (${laneLabel(lane, sha)}): CI builds, signs, and publishes the release artifacts.`);
108
+ // The ONE runs-url formula is the plan's (`buildDispatch`), the same line
109
+ // backend, web and the extension print: rebuilt by hand here, it was a second
110
+ // copy of an address the dispatch already answered.
111
+ logger.log(`Watch: ${plan.runsUrl}`);
264
112
 
265
- async function currentBranch(projectRoot) {
266
- try {
267
- const { execute } = require('node-powertools');
268
- const out = await execute('git rev-parse --abbrev-ref HEAD', { cwd: projectRoot, log: false });
269
- return String(out || '').trim();
270
- } catch (e) {
271
- return null;
272
- }
273
- }
113
+ require('../vendor/devkit/deploy-record.js').recordDeploy({ dir: projectRoot, target: targetNameFromDir(projectRoot) || 'desktop', detail: { method: 'release-dispatch' } });
274
114
 
275
- function sleep(ms) {
276
- return new Promise((resolve) => setTimeout(resolve, ms));
277
- }
115
+ // 3. Follow the run to its verdict: the jobs' logs stream in here, and a red
116
+ // run throws, so this verb exits 1 on anything but success (#873). The run
117
+ // also has to be building the tree this deploy pushed (#902): on the push
118
+ // lane there is no sha and the check is off, because that lane's run head is
119
+ // the developer's own commit.
120
+ const { conclusion } = await followRun({
121
+ owner,
122
+ repo,
123
+ workflow: WORKFLOW_FILE,
124
+ since,
125
+ headSha: sha,
126
+ token: resolveToken(),
127
+ logger,
128
+ });
278
129
 
279
- function stripAnsi(s) {
280
- // Minimal ANSI stripper — handles CSI sequences (colors, cursor moves).
281
- return String(s).replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
282
- }
130
+ logger.log(`Logs: ${path.relative(projectRoot, logPath)}`);
283
131
 
284
- // Exported for tests + `omega deploy`, which prints the exact dispatch this
285
- // sends before delegating here.
286
- module.exports.dispatchTarget = dispatchTarget;
132
+ return conclusion;
133
+ };