@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
@@ -7,13 +7,34 @@ const fs = require('fs');
7
7
  const os = require('os');
8
8
  const defineCases = require('../../../vendor/devkit/test/define-cases.js');
9
9
 
10
- function stageConsumer(jsonText) {
10
+ function stageConsumer(jsonText, overlays) {
11
11
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-getconfig-'));
12
12
  fs.mkdirSync(path.join(tmp, 'config'), { recursive: true });
13
13
  fs.writeFileSync(path.join(tmp, 'config', 'omega.json5'), jsonText);
14
+ for (const [environment, contents] of Object.entries(overlays || {})) {
15
+ fs.writeFileSync(path.join(tmp, 'config', `omega.${environment}.json5`), contents);
16
+ }
14
17
  return tmp;
15
18
  }
16
19
 
20
+ // Run `fn` with the build-mode flag and this machine's ambient environment
21
+ // stated the way a real run states them; the originals come back after.
22
+ function withEnv(vars, fn) {
23
+ const saved = Object.keys(vars).map((key) => [key, process.env[key]]);
24
+ try {
25
+ for (const [key, value] of Object.entries(vars)) {
26
+ if (value === undefined) delete process.env[key];
27
+ else process.env[key] = value;
28
+ }
29
+ return fn();
30
+ } finally {
31
+ for (const [key, value] of saved) {
32
+ if (value === undefined) delete process.env[key];
33
+ else process.env[key] = value;
34
+ }
35
+ }
36
+ }
37
+
17
38
  function loadConfigInDir(dir) {
18
39
  const oldCwd = process.cwd();
19
40
  // Bust the build module cache since it's a singleton.
@@ -37,14 +58,14 @@ module.exports = defineCases({
37
58
  {
38
59
  name: 'derives appId from the BRAND: reverse-domain of brand.url, else app.<brand.id> (friction #18)',
39
60
  run: (ctx) => {
40
- const withUrl = stageConsumer(`{ brand: { id: 'somiibo', name: 'Somiibo', url: 'https://www.somiibo.com' }, targets: { desktop: {} } }`);
61
+ const withUrl = stageConsumer(`{ brand: { id: 'somiibo', name: 'Somiibo', url: 'https://www.somiibo.com' }, targets: { desktop: { type: 'desktop' } } }`);
41
62
  try {
42
63
  ctx.expect(loadConfigInDir(withUrl).app.appId).toBe('com.somiibo');
43
64
  } finally {
44
65
  fs.rmSync(withUrl, { recursive: true, force: true });
45
66
  }
46
67
 
47
- const noUrl = stageConsumer(`{ brand: { id: 'somiibo', name: 'Somiibo' }, targets: { desktop: {} } }`);
68
+ const noUrl = stageConsumer(`{ brand: { id: 'somiibo', name: 'Somiibo' }, targets: { desktop: { type: 'desktop' } } }`);
48
69
  try {
49
70
  ctx.expect(loadConfigInDir(noUrl).app.appId).toBe('app.somiibo');
50
71
  } finally {
@@ -52,10 +73,58 @@ module.exports = defineCases({
52
73
  }
53
74
  },
54
75
  },
76
+ {
77
+ // #909: the certificates service registers
78
+ // `<certificates.providers.apple.bundleIdPrefix>.<brand.id>` (dashes as
79
+ // dots), so a brand whose config carries a prefix (its own, or the
80
+ // company layer's) signs under that same id. The URL host is what a
81
+ // brand with no prefix still derives from, and a GitHub repo URL is
82
+ // exactly why that fallback cannot be the rule.
83
+ name: 'derives appId from certificates.providers.apple.bundleIdPrefix when the config carries one (#909)',
84
+ run: (ctx) => {
85
+ const withPrefix = stageConsumer(`{
86
+ brand: { id: 'my-app', name: 'My App', url: 'https://github.com/acme/my-app' },
87
+ certificates: { providers: { apple: { bundleIdPrefix: 'com.itwcreativeworks' } } },
88
+ targets: { desktop: { type: 'desktop' } },
89
+ }`);
90
+ try {
91
+ ctx.expect(loadConfigInDir(withPrefix).app.appId).toBe('com.itwcreativeworks.my.app');
92
+ } finally {
93
+ fs.rmSync(withPrefix, { recursive: true, force: true });
94
+ }
95
+
96
+ const noPrefix = stageConsumer(`{
97
+ brand: { id: 'my-app', name: 'My App', url: 'https://github.com/acme/my-app' },
98
+ targets: { desktop: { type: 'desktop' } },
99
+ }`);
100
+ try {
101
+ ctx.expect(loadConfigInDir(noPrefix).app.appId).toBe('com.github');
102
+ } finally {
103
+ fs.rmSync(noPrefix, { recursive: true, force: true });
104
+ }
105
+ },
106
+ },
107
+ {
108
+ // ONE derivation, @omega.js/config's (#909). A hyphenated or uppercase
109
+ // host is exactly where reversing the string by hand and the helper part
110
+ // ways, and a bundle id carrying a hyphen is not a bundle id.
111
+ name: 'the url fallback is @omega.js/config\'s own derivation, hyphens and case included (#909)',
112
+ run: (ctx) => {
113
+ const { deriveBundleIdPrefix } = require('../../../vendor/config/index.js');
114
+ const url = 'https://My-App.Example.com';
115
+ const dir = stageConsumer(`{ brand: { id: 'my-app', name: 'My App', url: '${url}' }, targets: { desktop: { type: 'desktop' } } }`);
116
+
117
+ try {
118
+ ctx.expect(loadConfigInDir(dir).app.appId).toBe(deriveBundleIdPrefix(url));
119
+ } finally {
120
+ fs.rmSync(dir, { recursive: true, force: true });
121
+ }
122
+ },
123
+ },
55
124
  {
56
125
  name: 'derives productName from brand.name when not set',
57
126
  run: (ctx) => {
58
- const tmp = stageConsumer(`{ brand: { id: 'somiibo', name: 'Somiibo' }, targets: { desktop: {} } }`);
127
+ const tmp = stageConsumer(`{ brand: { id: 'somiibo', name: 'Somiibo' }, targets: { desktop: { type: 'desktop' } } }`);
59
128
  try {
60
129
  const cfg = loadConfigInDir(tmp);
61
130
  ctx.expect(cfg.app.productName).toBe('Somiibo');
@@ -70,6 +139,7 @@ module.exports = defineCases({
70
139
  const tmp = stageConsumer(`{
71
140
  brand: { id: 'somiibo', name: 'Somiibo' },
72
141
  targets: { desktop: {
142
+ type: 'desktop',
73
143
  app: { appId: 'com.custom.id', productName: 'Custom Name' },
74
144
  } },
75
145
  }`);
@@ -88,14 +158,15 @@ module.exports = defineCases({
88
158
  const tmp = stageConsumer(`{
89
159
  brand: { id: 'somiibo', name: 'Somiibo' },
90
160
  targets: { desktop: {
161
+ type: 'desktop',
91
162
  startup: { mode: 'hidden' },
92
- platforms: { win: { signing: { strategy: 'cloud' } } },
163
+ platforms: { windows: { signing: { strategy: 'cloud' } } },
93
164
  } },
94
165
  }`);
95
166
  try {
96
167
  const cfg = loadConfigInDir(tmp);
97
168
  ctx.expect(cfg.startup.mode).toBe('hidden');
98
- ctx.expect(cfg.platforms.win.signing.strategy).toBe('cloud');
169
+ ctx.expect(cfg.platforms.windows.signing.strategy).toBe('cloud');
99
170
  } finally {
100
171
  fs.rmSync(tmp, { recursive: true, force: true });
101
172
  }
@@ -108,6 +179,7 @@ module.exports = defineCases({
108
179
  brand: { id: 'somiibo', name: 'Somiibo' },
109
180
  monitoring: { providers: { sentry: { dsn: 'https://shared.example.com' } } },
110
181
  targets: { desktop: {
182
+ type: 'desktop',
111
183
  monitoring: { providers: { sentry: { dsn: 'https://desktop-only.example.com' } } },
112
184
  } },
113
185
  }`);
@@ -119,6 +191,44 @@ module.exports = defineCases({
119
191
  }
120
192
  },
121
193
  },
194
+ {
195
+ // #856: the environment overlay a BUILD composes is the one the lane
196
+ // names, not the one the machine answers. OMEGA_BUILD_MODE is the lane's
197
+ // own word for a production bake, and a dev boot (no flag) is development
198
+ // ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)). The
199
+ // resolved config carries the word it composed under, as `environment`,
200
+ // the build fact every OMEGA surface spells the same way (#896).
201
+ name: 'a PRODUCTION build composes the production overlay, a dev boot the ambient one (#856)',
202
+ run: (ctx) => {
203
+ const tmp = stageConsumer(
204
+ `{ brand: { id: 'somiibo', name: 'Somiibo' }, targets: { desktop: { type: 'desktop' } } }`,
205
+ {
206
+ production: `{ targets: { desktop: { app: { productName: 'Somiibo Production' } } } }`,
207
+ development: `{ targets: { desktop: { app: { productName: 'Somiibo Dev' } } } }`,
208
+ },
209
+ );
210
+ try {
211
+ // A fresh lane each time: OMEGA_ENVIRONMENT is the ONE input (#817)
212
+ // and src/build.js sets it at load from the build-mode flag, so each
213
+ // sub-case starts from a process that has not been told anything.
214
+ const freshLane = { OMEGA_ENVIRONMENT: undefined, OMEGA_TEST_MODE: undefined, ENVIRONMENT: undefined, FUNCTIONS_EMULATOR: undefined, TERM_PROGRAM: undefined };
215
+
216
+ const built = withEnv({ ...freshLane, OMEGA_BUILD_MODE: 'true' }, () => loadConfigInDir(tmp));
217
+ ctx.expect(built.app.productName).toBe('Somiibo Production');
218
+ ctx.expect(built.environment).toBe('production');
219
+
220
+ // A dev boot names nothing, and resolves development rather than the
221
+ // machine-sniffed answer its old copy gave (#817): the same run used
222
+ // to compose the PRODUCTION overlay in a terminal that did not look
223
+ // like one.
224
+ const dev = withEnv({ ...freshLane, OMEGA_BUILD_MODE: undefined }, () => loadConfigInDir(tmp));
225
+ ctx.expect(dev.app.productName).toBe('Somiibo Dev');
226
+ ctx.expect(dev.environment).toBe('development');
227
+ } finally {
228
+ fs.rmSync(tmp, { recursive: true, force: true });
229
+ }
230
+ },
231
+ },
122
232
  {
123
233
  name: 'returns seeded {brand:{}, app:{}} when config file missing',
124
234
  run: (ctx) => {
@@ -126,8 +236,10 @@ module.exports = defineCases({
126
236
  // deref config.brand.X / config.app.X without optional-chaining at every site.
127
237
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-getconfig-empty-'));
128
238
  try {
129
- const cfg = loadConfigInDir(tmp);
130
- ctx.expect(cfg).toEqual({ brand: {}, app: {} });
239
+ // A fresh lane, so the seeded shape is stated against a known input.
240
+ const cfg = withEnv({ OMEGA_ENVIRONMENT: undefined, OMEGA_BUILD_MODE: undefined }, () => loadConfigInDir(tmp));
241
+ // `environment` rides on every resolved config as the build fact (#817/#896).
242
+ ctx.expect(cfg).toEqual({ environment: 'development', brand: {}, app: {} });
131
243
  } finally {
132
244
  fs.rmSync(tmp, { recursive: true, force: true });
133
245
  }
@@ -1,6 +1,9 @@
1
- // Build-layer tests for src/utils/github.js — the octokit factory + repo ensure.
2
- // Repo DISCOVERY is not this module's job any more (#799): the app repo and the
3
- // releases repo come from @omega.js/config, never a package.json or a git remote.
1
+ // Build-layer tests for src/utils/github.js: the octokit factory, and nothing
2
+ // else. Repo DISCOVERY is not this module's job (#799): the source repo and the
3
+ // releases repo come from @omega.js/config, never a package.json or a git
4
+ // remote. Repo CREATION is not either (#883): `@omega.js/devkit/github-repo`'s
5
+ // ensureRepo is the one home, so a bug in it is fixed once for the manage walk,
6
+ // the web deploy and the release precheck alike.
4
7
 
5
8
  const path = require('path');
6
9
  const defineCases = require('../../../vendor/devkit/test/define-cases.js');
@@ -8,17 +11,20 @@ const defineCases = require('../../../vendor/devkit/test/define-cases.js');
8
11
  module.exports = defineCases({
9
12
  type: 'suite',
10
13
  layer: 'build',
11
- description: 'utils/github — octokit factory + repo ensure',
14
+ description: 'utils/github: the octokit factory, and only that',
12
15
  tests: [
13
16
  {
14
- name: 'github utils exports the octokit surface and NO repo discovery (#799)',
17
+ name: 'github utils exports the octokit surface, no discovery (#799) and no ensure (#883)',
15
18
  run: (ctx) => {
16
19
  const mod = require(path.join(__dirname, '..', '..', '..', 'utils', 'github.js'));
17
20
  ctx.expect(typeof mod.getOctokit).toBe('function');
18
- ctx.expect(typeof mod.ensureRepo).toBe('function');
19
21
  // The git-remote fallback is gone: in a brand monorepo it answered the
20
22
  // ENCLOSING repo, so every release verb targeted the wrong owner.
21
23
  ctx.expect(mod.discoverRepo).toBeUndefined();
24
+ // And the second ensureRepo is gone with it: one implementation, in
25
+ // devkit, so a repo this framework creates matches every other.
26
+ ctx.expect(mod.ensureRepo).toBeUndefined();
27
+ ctx.expect(typeof require('../../../vendor/devkit/github-repo.js').ensureRepo).toBe('function');
22
28
  },
23
29
  },
24
30
  {
@@ -22,13 +22,15 @@ module.exports = defineCases({
22
22
  {
23
23
  name: 'the stamp rides the build blob, never the client config',
24
24
  run: (ctx) => {
25
- const { composeBuildJson } = require(path.join(FRAMEWORK_ROOT, 'src', 'gulp', 'tasks', 'bundle.js'));
25
+ const { composeBuildJson, buildFacts } = require(path.join(FRAMEWORK_ROOT, 'src', 'gulp', 'tasks', 'bundle.js'));
26
26
 
27
+ const pkg = { version: '3.1.4' };
28
+ const mode = { environment: 'production', build: true, publish: false };
27
29
  const buildJson = composeBuildJson({
28
30
  config: { brand: { id: 'paperloom' } },
29
- dev: null,
30
- pkg: { version: '3.1.4' },
31
- mode: { environment: 'production' },
31
+ facts: buildFacts({ pkg, mode, dev: null }),
32
+ pkg,
33
+ mode,
32
34
  license: { status: 'licensed', payments: 'live', attribution: 'removed' },
33
35
  });
34
36
 
@@ -3,6 +3,51 @@
3
3
  const Manager = require('../../../build.js');
4
4
  const defineCases = require('../../../vendor/devkit/test/define-cases.js');
5
5
 
6
+ // The one input and the two lane flags that decide it, saved and restored
7
+ // around every case that touches them (#817).
8
+ const LANE_KEYS = ['OMEGA_ENVIRONMENT', 'OMEGA_BUILD_MODE', 'OMEGA_TEST_MODE', 'NODE_ENV'];
9
+
10
+ /**
11
+ * Run `fn` with exactly `vars` set on the lane keys (null clears them all).
12
+ * @param {object|null} vars - the lane env for this case.
13
+ * @param {function} fn - the body.
14
+ * @returns {*} whatever `fn` returns.
15
+ */
16
+ function withEnvironment(vars, fn) {
17
+ const saved = {};
18
+ for (const key of LANE_KEYS) saved[key] = process.env[key];
19
+ try {
20
+ for (const key of LANE_KEYS) delete process.env[key];
21
+ Object.assign(process.env, vars || {});
22
+ return fn();
23
+ } finally {
24
+ for (const key of LANE_KEYS) {
25
+ if (saved[key] === undefined) delete process.env[key];
26
+ else process.env[key] = saved[key];
27
+ }
28
+ }
29
+ }
30
+
31
+ /**
32
+ * What src/build.js resolves at LOAD for a given lane. build.js sets the input
33
+ * once, at require time, so this loads a fresh copy of it per lane.
34
+ * @param {object} vars - the lane env.
35
+ * @returns {string} the resolved environment.
36
+ */
37
+ function laneEnvironment(vars) {
38
+ const buildPath = require.resolve('../../../build.js');
39
+ return withEnvironment(vars, () => {
40
+ const saved = require.cache[buildPath];
41
+ delete require.cache[buildPath];
42
+ try {
43
+ return require(buildPath).getEnvironment();
44
+ } finally {
45
+ if (saved) require.cache[buildPath] = saved;
46
+ else delete require.cache[buildPath];
47
+ }
48
+ });
49
+ }
50
+
6
51
  module.exports = defineCases({
7
52
  type: 'group', // independent tests; run all even on failure
8
53
  layer: 'build',
@@ -23,96 +68,71 @@ module.exports = defineCases({
23
68
  ctx.expect(mode).toHaveProperty('publish');
24
69
  ctx.expect(mode).toHaveProperty('server');
25
70
  ctx.expect(mode).toHaveProperty('environment');
26
- ctx.expect(['development', 'production']).toContain(mode.environment);
71
+ ctx.expect(['development', 'testing', 'production']).toContain(mode.environment);
27
72
  },
28
73
  },
29
74
  {
30
- name: 'getEnvironment is testing when OMEGA_TEST_MODE=true (takes precedence)',
75
+ // The environment reads ONE input, `OMEGA_ENVIRONMENT`
76
+ // ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)), and the
77
+ // lane that boots or builds this app sets it (src/build.js does, at load).
78
+ // Nothing sniffs NODE_ENV, app.isPackaged or OMEGA_TEST_MODE any more.
79
+ name: 'getEnvironment answers the one input, for each of the three names',
31
80
  run: (ctx) => {
32
- const prevTest = process.env.OMEGA_TEST_MODE;
33
- const prevBuild = process.env.OMEGA_BUILD_MODE;
34
- process.env.OMEGA_TEST_MODE = 'true';
35
- process.env.OMEGA_BUILD_MODE = 'true'; // even with build mode set, testing wins
36
- try {
37
- ctx.expect(Manager.getEnvironment()).toBe('testing');
38
- } finally {
39
- if (prevTest === undefined) delete process.env.OMEGA_TEST_MODE; else process.env.OMEGA_TEST_MODE = prevTest;
40
- if (prevBuild === undefined) delete process.env.OMEGA_BUILD_MODE; else process.env.OMEGA_BUILD_MODE = prevBuild;
41
- }
81
+ withEnvironment(null, () => {
82
+ for (const name of ['development', 'testing', 'production']) {
83
+ process.env.OMEGA_ENVIRONMENT = name;
84
+ ctx.expect(Manager.getEnvironment()).toBe(name);
85
+ }
86
+ });
42
87
  },
43
88
  },
44
89
  {
45
- name: 'getEnvironment is production when OMEGA_BUILD_MODE=true (and not testing)',
90
+ // The bug this replaced: with no signal desktop's own copy answered
91
+ // 'production', so a plain `npm start` bundled itself as a production
92
+ // artifact, while @omega.js/extension's copy of the same function
93
+ // answered 'development' from the same inputs (#817).
94
+ name: 'the build lane resolves production, a dev boot resolves development',
46
95
  run: (ctx) => {
47
- const prevTest = process.env.OMEGA_TEST_MODE;
48
- const prevBuild = process.env.OMEGA_BUILD_MODE;
49
- delete process.env.OMEGA_TEST_MODE;
50
- process.env.OMEGA_BUILD_MODE = 'true';
51
- try {
52
- ctx.expect(Manager.getEnvironment()).toBe('production');
53
- } finally {
54
- if (prevTest !== undefined) process.env.OMEGA_TEST_MODE = prevTest;
55
- if (prevBuild === undefined) delete process.env.OMEGA_BUILD_MODE; else process.env.OMEGA_BUILD_MODE = prevBuild;
56
- }
96
+ const buildLane = laneEnvironment({ OMEGA_BUILD_MODE: 'true' });
97
+ ctx.expect(buildLane).toBe('production');
98
+
99
+ const devBoot = laneEnvironment({});
100
+ ctx.expect(devBoot).toBe('development');
101
+
102
+ // A lane that already named one keeps it, EXCEPT a build lane: a
103
+ // production build spawned from a test run still bakes production.
104
+ ctx.expect(laneEnvironment({ OMEGA_ENVIRONMENT: 'testing' })).toBe('testing');
105
+ ctx.expect(laneEnvironment({ OMEGA_ENVIRONMENT: 'testing', OMEGA_BUILD_MODE: 'true' })).toBe('production');
57
106
  },
58
107
  },
59
108
  {
60
- // @omega.js/desktop defaults to 'production' when no signal is present (no app.isPackaged in plain Node,
61
- // no OMEGA_TEST_MODE/OMEGA_BUILD_MODE, no NODE_ENV=development). @omega.js/desktop's deployed RUNTIME can
62
- // legitimately reach here without a dev signal (a shipped binary), so production is the
63
- // safe default. NODE_ENV=development is the explicit dev override (tested separately).
64
- name: 'getEnvironment defaults to production when no dev/test signal is present',
109
+ name: 'no input at all is a loud error naming OMEGA_ENVIRONMENT, never a default',
65
110
  run: (ctx) => {
66
- const prevTest = process.env.OMEGA_TEST_MODE;
67
- const prevBuild = process.env.OMEGA_BUILD_MODE;
68
- const prevNode = process.env.NODE_ENV;
69
- delete process.env.OMEGA_TEST_MODE;
70
- delete process.env.OMEGA_BUILD_MODE;
71
- delete process.env.NODE_ENV;
72
- try {
73
- ctx.expect(Manager.getEnvironment()).toBe('production');
74
- // NODE_ENV=development is the explicit local-dev override.
75
- process.env.NODE_ENV = 'development';
76
- ctx.expect(Manager.getEnvironment()).toBe('development');
77
- } finally {
78
- if (prevTest !== undefined) process.env.OMEGA_TEST_MODE = prevTest;
79
- if (prevBuild !== undefined) process.env.OMEGA_BUILD_MODE = prevBuild;
80
- if (prevNode === undefined) delete process.env.NODE_ENV; else process.env.NODE_ENV = prevNode;
81
- }
111
+ withEnvironment(null, () => {
112
+ delete process.env.OMEGA_ENVIRONMENT;
113
+ ctx.expect(() => Manager.getEnvironment()).toThrow(/OMEGA_ENVIRONMENT/);
114
+ process.env.OMEGA_ENVIRONMENT = 'staging';
115
+ ctx.expect(() => Manager.getEnvironment()).toThrow(/OMEGA_ENVIRONMENT/);
116
+ });
82
117
  },
83
118
  },
84
119
  {
85
- // The core invariant of the SSOT refactor: is*() DERIVE from getEnvironment(), so they
86
- // can NEVER disagree with it, and exactly one is always true. (In plain Node `app` is
87
- // unavailable, so getEnvironment() resolves via the env-var / config fallback.)
120
+ // The core invariant: is*() DERIVE from getEnvironment(), so they can
121
+ // NEVER disagree with it, and exactly one is always true.
88
122
  name: 'invariant: is*() exactly matches getEnvironment() + mutually exclusive (every scenario)',
89
123
  run: (ctx) => {
90
- const prevTest = process.env.OMEGA_TEST_MODE;
91
- const prevBuild = process.env.OMEGA_BUILD_MODE;
92
- const prevNode = process.env.NODE_ENV;
93
- const scenarios = [
94
- { env: { OMEGA_TEST_MODE: 'true', OMEGA_BUILD_MODE: 'true' }, expect: 'testing' },
95
- { env: { OMEGA_BUILD_MODE: 'true' }, expect: 'production' },
96
- { env: { NODE_ENV: 'development' }, expect: 'development' },
97
- { env: {}, expect: 'production' }, // @omega.js/desktop defaults prod (shipped artifact)
98
- ];
99
- try {
100
- for (const s of scenarios) {
101
- delete process.env.OMEGA_TEST_MODE; delete process.env.OMEGA_BUILD_MODE; delete process.env.NODE_ENV;
102
- for (const k of Object.keys(s.env)) process.env[k] = s.env[k];
124
+ withEnvironment(null, () => {
125
+ for (const name of ['development', 'testing', 'production']) {
126
+ process.env.OMEGA_ENVIRONMENT = name;
103
127
  const e = Manager.getEnvironment();
104
- ctx.expect(e).toBe(s.expect);
128
+ ctx.expect(e).toBe(name);
105
129
  ctx.expect(Manager.isDevelopment()).toBe(e === 'development');
106
130
  ctx.expect(Manager.isTesting()).toBe(e === 'testing');
107
131
  ctx.expect(Manager.isProduction()).toBe(e === 'production');
108
132
  const trueCount = [Manager.isDevelopment(), Manager.isTesting(), Manager.isProduction()].filter(Boolean).length;
109
133
  ctx.expect(trueCount).toBe(1);
110
134
  }
111
- } finally {
112
- if (prevTest === undefined) delete process.env.OMEGA_TEST_MODE; else process.env.OMEGA_TEST_MODE = prevTest;
113
- if (prevBuild === undefined) delete process.env.OMEGA_BUILD_MODE; else process.env.OMEGA_BUILD_MODE = prevBuild;
114
- if (prevNode === undefined) delete process.env.NODE_ENV; else process.env.NODE_ENV = prevNode;
115
- }
135
+ });
116
136
  },
117
137
  },
118
138
  {
@@ -162,13 +182,13 @@ module.exports = defineCases({
162
182
  },
163
183
  },
164
184
  {
165
- name: 'getWindowsSignStrategy reads config platforms.win.signing.strategy',
185
+ name: 'getWindowsSignStrategy reads config platforms.windows.signing.strategy',
166
186
  run: (ctx) => {
167
187
  const fs = require('fs'); const os = require('os'); const path = require('path');
168
188
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-strategy-'));
169
189
  fs.mkdirSync(path.join(tmp, 'config'), { recursive: true });
170
190
  fs.writeFileSync(path.join(tmp, 'config', 'omega.json5'),
171
- `{ targets: { desktop: { platforms: { win: { signing: { strategy: 'cloud' } } } } } }`);
191
+ `{ targets: { desktop: { type: 'desktop', platforms: { windows: { signing: { strategy: 'cloud' } } } } } }`);
172
192
  const orig = process.cwd();
173
193
  try {
174
194
  process.chdir(tmp);
@@ -0,0 +1,116 @@
1
+ // Build-layer test: a library the runtime source requires by BARE specifier is
2
+ // DECLARED by this package ([#872](https://github.com/Omega-JS-Stack/omega/issues/872)).
3
+ //
4
+ // The class of bug: on a laptop the monorepo root hoists every workspace's deps
5
+ // into one node_modules, so `require('sharp')` resolves from packages/desktop even
6
+ // though the desktop manifest never named it. The packed tarball a runner installs
7
+ // carries only what the manifest declares, and the first mac CI publish died on
8
+ // `Cannot find module 'sharp'` inside resolve-icons.js. Nothing about that failure
9
+ // was visible locally, so the manifest is checked HERE instead.
10
+ //
11
+ // Scope: src/lib and src/gulp, the two trees that run unbundled from the installed
12
+ // package (main-process libs and the gulp build lane). Internal `@omega.js/*`
13
+ // packages are excluded: the prepare lane vendors them into dist/vendor and rewires
14
+ // the requires, so they are never resolved by name from an install.
15
+
16
+ const path = require('path');
17
+ const fs = require('fs');
18
+ const { builtinModules } = require('module');
19
+ const defineCases = require('../../../vendor/devkit/test/define-cases.js');
20
+
21
+ const PACKAGE_ROOT = path.join(__dirname, '..', '..', '..', '..');
22
+ const SOURCE_DIRS = ['lib', 'gulp'];
23
+
24
+ // The two specifiers that are ALLOWED to be undeclared, each one required inside a
25
+ // try/catch whose catch is the documented behaviour, not a crash:
26
+ // firebase @omega.js/client's own dependency; the client bridge falls back
27
+ // to no-op mode when it is not there (lib/client-bridge.js)
28
+ // app-builder-bin electron-builder's own dependency; blockmap generation is
29
+ // best-effort and warns-and-skips (lib/sign-helpers/update-info.js)
30
+ const GUARDED = new Set(['firebase', 'app-builder-bin']);
31
+
32
+ // Every bare specifier `require`d under a dir, as package names.
33
+ function requiredPackages(dir) {
34
+ const names = new Map();
35
+
36
+ const walk = (current) => {
37
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
38
+ const file = path.join(current, entry.name);
39
+
40
+ if (entry.isDirectory()) {
41
+ walk(file);
42
+ continue;
43
+ }
44
+ if (!entry.name.endsWith('.js')) {
45
+ continue;
46
+ }
47
+
48
+ const source = fs.readFileSync(file, 'utf8');
49
+ for (const match of source.matchAll(/require\(\s*['"]([^'"]+)['"]\s*\)/g)) {
50
+ const specifier = match[1];
51
+ if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('node:')) {
52
+ continue;
53
+ }
54
+
55
+ const name = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0];
56
+ if (builtinModules.includes(name)) {
57
+ continue;
58
+ }
59
+
60
+ if (!names.has(name)) {
61
+ names.set(name, path.relative(PACKAGE_ROOT, file));
62
+ }
63
+ }
64
+ }
65
+ };
66
+
67
+ walk(dir);
68
+
69
+ return names;
70
+ }
71
+
72
+ module.exports = defineCases({
73
+ type: 'suite',
74
+ layer: 'build',
75
+ description: 'manifest deps: what the runtime source requires, the package declares (#872)',
76
+ tests: [
77
+ {
78
+ name: 'every bare require under src/lib and src/gulp is declared by the manifest',
79
+ run: (ctx) => {
80
+ const pkg = require(path.join(PACKAGE_ROOT, 'package.json'));
81
+ // dependencies ship inside the tarball; peers and optionals are installed
82
+ // beside it (ensureTarget installs the peers on every verb).
83
+ const declared = new Set([
84
+ ...Object.keys(pkg.dependencies || {}),
85
+ ...Object.keys(pkg.peerDependencies || {}),
86
+ ...Object.keys(pkg.optionalDependencies || {}),
87
+ ]);
88
+
89
+ const undeclared = [];
90
+ for (const dir of SOURCE_DIRS) {
91
+ for (const [name, file] of requiredPackages(path.join(PACKAGE_ROOT, 'src', dir))) {
92
+ // The package requiring ITSELF is node's self-reference through the
93
+ // exports map, and the internal packages are vendored at prepare time.
94
+ if (name === pkg.name || name.startsWith('@omega.js/') || GUARDED.has(name)) {
95
+ continue;
96
+ }
97
+ if (!declared.has(name)) {
98
+ undeclared.push(`${name} (${file})`);
99
+ }
100
+ }
101
+ }
102
+
103
+ ctx.expect(undeclared).toEqual([]);
104
+ },
105
+ },
106
+ {
107
+ name: 'sharp, the icon pipeline it was missing, is a dependency and not a hoisted accident',
108
+ run: (ctx) => {
109
+ const pkg = require(path.join(PACKAGE_ROOT, 'package.json'));
110
+
111
+ ctx.expect(Boolean(pkg.dependencies.sharp)).toBe(true);
112
+ ctx.expect(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'lib', 'sign-helpers', 'resolve-icons.js'), 'utf8')).toContain("require('sharp')");
113
+ },
114
+ },
115
+ ],
116
+ });
@@ -117,7 +117,11 @@ ${CUSTOM_MARKER}
117
117
  },
118
118
  },
119
119
  {
120
- name: 'gitignore: migrates user-added Default line to Custom when not in new defaults (no quoting for gitignore)',
120
+ // The framework block is COMPLETELY managed ([#926](https://github.com/Omega-JS-Stack/omega/issues/926)):
121
+ // a retired framework line and a line a user typed into the framework block
122
+ // cannot be told apart, and the block header says it is overwritten on
123
+ // every setup, so a line the new block does not carry leaves the file.
124
+ name: 'gitignore: a Default line the new defaults no longer carry DROPS instead of migrating (#926)',
121
125
  run: (ctx) => {
122
126
  const existing = `${DEFAULT_MARKER}
123
127
  node_modules/
@@ -132,10 +136,29 @@ dist/
132
136
  ${CUSTOM_MARKER}
133
137
  `;
134
138
  const merged = mergeLineBasedFiles(existing, incoming, '.gitignore');
135
- ctx.expect(merged).toContain('my-fork-only.txt');
139
+ ctx.expect(merged).not.toContain('my-fork-only.txt');
136
140
  ctx.expect(merged).toContain('dist/');
137
- // gitignore is NOT quoted.
138
- ctx.expect(merged).not.toContain('"my-fork-only.txt"');
141
+ },
142
+ },
143
+ {
144
+ // The value a retired key holds is the user's DATA, so it still migrates
145
+ // (the case above); an EMPTY retired key holds nothing and drops (#926).
146
+ name: 'env: an EMPTY retired key drops instead of migrating to Custom (#926)',
147
+ run: (ctx) => {
148
+ const existing = `${DEFAULT_MARKER}
149
+ GH_TOKEN="mine"
150
+ MY_EMPTY_KEY=
151
+
152
+ ${CUSTOM_MARKER}
153
+ `;
154
+ const incoming = `${DEFAULT_MARKER}
155
+ GH_TOKEN=""
156
+
157
+ ${CUSTOM_MARKER}
158
+ `;
159
+ const merged = mergeLineBasedFiles(existing, incoming, '.env');
160
+ ctx.expect(merged).not.toContain('MY_EMPTY_KEY');
161
+ ctx.expect(merged).toContain('GH_TOKEN="mine"');
139
162
  },
140
163
  },
141
164
  {