@omega.js/backend 0.1.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 (993) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +1053 -0
  3. package/bin/omega +2 -0
  4. package/bin/omega-backend +2 -0
  5. package/bin/omg +2 -0
  6. package/cli.js +3 -0
  7. package/dist/cli/command-table.js +268 -0
  8. package/dist/cli/commands/auth.js +259 -0
  9. package/dist/cli/commands/base-command.js +451 -0
  10. package/dist/cli/commands/build.js +27 -0
  11. package/dist/cli/commands/clean.js +13 -0
  12. package/dist/cli/commands/clear.js +11 -0
  13. package/dist/cli/commands/cwd.js +9 -0
  14. package/dist/cli/commands/deploy.js +205 -0
  15. package/dist/cli/commands/emulator-orphans.js +440 -0
  16. package/dist/cli/commands/emulator.js +1829 -0
  17. package/dist/cli/commands/firebase-init.js +101 -0
  18. package/dist/cli/commands/firestore.js +261 -0
  19. package/dist/cli/commands/indexes.js +51 -0
  20. package/dist/cli/commands/install.js +151 -0
  21. package/dist/cli/commands/logs.js +358 -0
  22. package/dist/cli/commands/mcp.js +41 -0
  23. package/dist/cli/commands/migrate-markers.js +176 -0
  24. package/dist/cli/commands/migrate-rules.js +90 -0
  25. package/dist/cli/commands/migrate.js +59 -0
  26. package/dist/cli/commands/serve.js +208 -0
  27. package/dist/cli/commands/setup-tests/base-test.js +174 -0
  28. package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
  29. package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
  30. package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
  31. package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
  32. package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
  33. package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
  34. package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
  35. package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
  36. package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
  37. package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
  38. package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
  39. package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
  40. package/dist/cli/commands/setup-tests/functions-package.js +44 -0
  41. package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
  42. package/dist/cli/commands/setup-tests/gitignore.js +122 -0
  43. package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
  44. package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
  45. package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
  46. package/dist/cli/commands/setup-tests/helpers.js +72 -0
  47. package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
  48. package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
  49. package/dist/cli/commands/setup-tests/index.js +111 -0
  50. package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
  51. package/dist/cli/commands/setup-tests/java-installed.js +27 -0
  52. package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
  53. package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
  54. package/dist/cli/commands/setup-tests/node-version.js +60 -0
  55. package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
  56. package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
  57. package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
  58. package/dist/cli/commands/setup-tests/omega-config.js +84 -0
  59. package/dist/cli/commands/setup-tests/project-directories.js +34 -0
  60. package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
  61. package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
  62. package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
  63. package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
  64. package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
  65. package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
  66. package/dist/cli/commands/setup-tests/service-account.js +86 -0
  67. package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
  68. package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
  69. package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
  70. package/dist/cli/commands/stripe.js +14 -0
  71. package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
  72. package/dist/cli/commands/test.js +976 -0
  73. package/dist/cli/commands/update.js +27 -0
  74. package/dist/cli/commands/version.js +10 -0
  75. package/dist/cli/commands/watch.js +196 -0
  76. package/dist/cli/flags.js +13 -0
  77. package/dist/cli/index.js +173 -0
  78. package/dist/cli/run.js +33 -0
  79. package/dist/cli/utils/attach-log-file.js +4 -0
  80. package/dist/cli/utils/compile-rules.js +1155 -0
  81. package/dist/cli/utils/ensure-target.js +314 -0
  82. package/dist/cli/utils/project-type.js +140 -0
  83. package/dist/cli/utils/public-files.js +49 -0
  84. package/dist/cli/utils/safe-install.js +4 -0
  85. package/dist/cli/utils/spawn-shell.js +94 -0
  86. package/dist/cli/utils/stage-functions.js +338 -0
  87. package/dist/cli/utils/stage-local-packages.js +210 -0
  88. package/dist/cli/utils/target-checks.js +161 -0
  89. package/dist/cli/utils/target.js +24 -0
  90. package/dist/cli/utils/ui.js +292 -0
  91. package/dist/defaults/AGENTS.md +125 -0
  92. package/dist/defaults/CHANGELOG.md +15 -0
  93. package/dist/defaults/CLAUDE.md +1 -0
  94. package/dist/defaults/_.gitignore +65 -0
  95. package/dist/defaults/docs/README.md +17 -0
  96. package/dist/defaults/test/README.md +65 -0
  97. package/dist/defaults/test/_init.js +14 -0
  98. package/dist/defaults/test/helpers/connect-trap.js +72 -0
  99. package/dist/defaults/test/unit/registration.test.js +143 -0
  100. package/dist/defaults/test/unit/rules-posture.test.js +101 -0
  101. package/dist/defaults/test/unit/socket-free.test.js +43 -0
  102. package/dist/manager/events/auth/before-create.js +89 -0
  103. package/dist/manager/events/auth/before-signin.js +76 -0
  104. package/dist/manager/events/auth/on-create.js +112 -0
  105. package/dist/manager/events/auth/on-delete.js +141 -0
  106. package/dist/manager/events/auth/utils.js +93 -0
  107. package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
  108. package/dist/manager/events/cron/daily/data-requests.js +57 -0
  109. package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
  110. package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
  111. package/dist/manager/events/cron/daily/reset-usage.js +188 -0
  112. package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
  113. package/dist/manager/events/cron/daily.js +3 -0
  114. package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
  115. package/dist/manager/events/cron/frequent/email-queue.js +71 -0
  116. package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
  117. package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
  118. package/dist/manager/events/cron/frequent.js +3 -0
  119. package/dist/manager/events/cron/runner.js +64 -0
  120. package/dist/manager/events/firestore/notifications/on-write.js +123 -0
  121. package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
  122. package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
  123. package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
  124. package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
  125. package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
  126. package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
  127. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
  128. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
  129. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
  130. package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
  131. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
  132. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
  133. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
  134. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
  135. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
  136. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
  137. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
  138. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
  139. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
  140. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
  141. package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
  142. package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
  143. package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
  144. package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
  145. package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
  146. package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
  147. package/dist/manager/functions/_legacy/admin/query.js +198 -0
  148. package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
  149. package/dist/manager/functions/_legacy/template.js +33 -0
  150. package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
  151. package/dist/manager/functions/_legacy/test/webhook.js +37 -0
  152. package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
  153. package/dist/manager/helpers/analytics.js +498 -0
  154. package/dist/manager/helpers/api-manager.js +313 -0
  155. package/dist/manager/helpers/backend-router.js +37 -0
  156. package/dist/manager/helpers/context/authenticate.js +215 -0
  157. package/dist/manager/helpers/context/client-info.js +91 -0
  158. package/dist/manager/helpers/context/index.js +228 -0
  159. package/dist/manager/helpers/context/logging.js +150 -0
  160. package/dist/manager/helpers/context/parse.js +160 -0
  161. package/dist/manager/helpers/context/respond.js +247 -0
  162. package/dist/manager/helpers/event-middleware.js +109 -0
  163. package/dist/manager/helpers/metadata.js +32 -0
  164. package/dist/manager/helpers/middleware.js +541 -0
  165. package/dist/manager/helpers/redact-secret.js +25 -0
  166. package/dist/manager/helpers/resolved-config.js +34 -0
  167. package/dist/manager/helpers/roles.js +69 -0
  168. package/dist/manager/helpers/safe-compare.js +31 -0
  169. package/dist/manager/helpers/schema-engine.js +314 -0
  170. package/dist/manager/helpers/schema-zod.js +203 -0
  171. package/dist/manager/helpers/settings.js +215 -0
  172. package/dist/manager/helpers/usage.js +541 -0
  173. package/dist/manager/helpers/user.js +57 -0
  174. package/dist/manager/helpers/utilities.js +565 -0
  175. package/dist/manager/index.js +1404 -0
  176. package/dist/manager/libraries/abandoned-cart-config.js +12 -0
  177. package/dist/manager/libraries/ai/index.js +321 -0
  178. package/dist/manager/libraries/ai/prompt.js +85 -0
  179. package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
  180. package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
  181. package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
  182. package/dist/manager/libraries/ai/providers/openai.js +1272 -0
  183. package/dist/manager/libraries/ai/providers/test.js +257 -0
  184. package/dist/manager/libraries/ai/tokens.js +54 -0
  185. package/dist/manager/libraries/analytics/conversions.js +478 -0
  186. package/dist/manager/libraries/analytics/match-data.js +573 -0
  187. package/dist/manager/libraries/analytics/signup.js +120 -0
  188. package/dist/manager/libraries/auth-user.js +46 -0
  189. package/dist/manager/libraries/content/feed-parser.js +198 -0
  190. package/dist/manager/libraries/content/ghostii.js +191 -0
  191. package/dist/manager/libraries/content/source-resolver.js +629 -0
  192. package/dist/manager/libraries/email/constants.js +532 -0
  193. package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
  194. package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
  195. package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
  196. package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
  197. package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
  198. package/dist/manager/libraries/email/data/typo-domains.js +83 -0
  199. package/dist/manager/libraries/email/disposable-domains.js +92 -0
  200. package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
  201. package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
  202. package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
  203. package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
  204. package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
  205. package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
  206. package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
  207. package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
  208. package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
  209. package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
  210. package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
  211. package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
  212. package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
  213. package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
  214. package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
  215. package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
  216. package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
  217. package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
  218. package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
  219. package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
  220. package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
  221. package/dist/manager/libraries/email/generators/newsletter.js +899 -0
  222. package/dist/manager/libraries/email/index.js +146 -0
  223. package/dist/manager/libraries/email/marketing/index.js +754 -0
  224. package/dist/manager/libraries/email/prepare.js +365 -0
  225. package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
  226. package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
  227. package/dist/manager/libraries/email/transactional/index.js +510 -0
  228. package/dist/manager/libraries/email/utm.js +86 -0
  229. package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
  230. package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
  231. package/dist/manager/libraries/email/validation.js +255 -0
  232. package/dist/manager/libraries/env.js +198 -0
  233. package/dist/manager/libraries/infer-contact.js +101 -0
  234. package/dist/manager/libraries/load-provider.js +65 -0
  235. package/dist/manager/libraries/notification.js +256 -0
  236. package/dist/manager/libraries/openai.js +13 -0
  237. package/dist/manager/libraries/payment/discount-codes.js +204 -0
  238. package/dist/manager/libraries/payment/fetch-failure.js +56 -0
  239. package/dist/manager/libraries/payment/license.js +44 -0
  240. package/dist/manager/libraries/payment/order-id.js +57 -0
  241. package/dist/manager/libraries/payment/provider-errors.js +71 -0
  242. package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
  243. package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
  244. package/dist/manager/libraries/payment/providers/paypal.js +987 -0
  245. package/dist/manager/libraries/payment/providers/stripe.js +755 -0
  246. package/dist/manager/libraries/payment/providers/test.js +250 -0
  247. package/dist/manager/libraries/payment/refund-linkage.js +78 -0
  248. package/dist/manager/libraries/payment/refund-policy.js +113 -0
  249. package/dist/manager/libraries/payment/winback.js +55 -0
  250. package/dist/manager/libraries/prompts/infer-contact.md +78 -0
  251. package/dist/manager/libraries/rate-limits.js +17 -0
  252. package/dist/manager/libraries/recaptcha.js +58 -0
  253. package/dist/manager/libraries/user-doc.js +234 -0
  254. package/dist/manager/routes/admin/backup/post.js +106 -0
  255. package/dist/manager/routes/admin/cron/post.js +37 -0
  256. package/dist/manager/routes/admin/database/get.js +35 -0
  257. package/dist/manager/routes/admin/database/post.js +34 -0
  258. package/dist/manager/routes/admin/email/post.js +74 -0
  259. package/dist/manager/routes/admin/firestore/get.js +35 -0
  260. package/dist/manager/routes/admin/firestore/post.js +56 -0
  261. package/dist/manager/routes/admin/firestore/query/post.js +128 -0
  262. package/dist/manager/routes/admin/hook/post.js +104 -0
  263. package/dist/manager/routes/admin/infer-contact/post.js +35 -0
  264. package/dist/manager/routes/admin/notification/post.js +37 -0
  265. package/dist/manager/routes/admin/payment/post.js +56 -0
  266. package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
  267. package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
  268. package/dist/manager/routes/admin/post/post.js +487 -0
  269. package/dist/manager/routes/admin/post/put.js +146 -0
  270. package/dist/manager/routes/admin/post/templates/post.html +14 -0
  271. package/dist/manager/routes/admin/repo/content/post.js +106 -0
  272. package/dist/manager/routes/admin/stats/get.js +222 -0
  273. package/dist/manager/routes/admin/users/disable/post.js +50 -0
  274. package/dist/manager/routes/admin/users/list/get.js +110 -0
  275. package/dist/manager/routes/admin/users/sync/post.js +119 -0
  276. package/dist/manager/routes/brand/get.js +26 -0
  277. package/dist/manager/routes/content/post/get.js +110 -0
  278. package/dist/manager/routes/general/email/post.js +89 -0
  279. package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
  280. package/dist/manager/routes/general/uuid/post.js +31 -0
  281. package/dist/manager/routes/handler/post/post.js +142 -0
  282. package/dist/manager/routes/health/get.js +43 -0
  283. package/dist/manager/routes/index.js +11 -0
  284. package/dist/manager/routes/marketing/campaign/delete.js +45 -0
  285. package/dist/manager/routes/marketing/campaign/get.js +69 -0
  286. package/dist/manager/routes/marketing/campaign/post.js +124 -0
  287. package/dist/manager/routes/marketing/campaign/put.js +86 -0
  288. package/dist/manager/routes/marketing/campaign/utils.js +59 -0
  289. package/dist/manager/routes/marketing/contact/delete.js +100 -0
  290. package/dist/manager/routes/marketing/contact/post.js +149 -0
  291. package/dist/manager/routes/marketing/contact/put.js +37 -0
  292. package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
  293. package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
  294. package/dist/manager/routes/marketing/webhook/post.js +123 -0
  295. package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
  296. package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
  297. package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
  298. package/dist/manager/routes/payments/cancel/post.js +149 -0
  299. package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
  300. package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
  301. package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
  302. package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
  303. package/dist/manager/routes/payments/discount/get.js +25 -0
  304. package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
  305. package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
  306. package/dist/manager/routes/payments/intent/post.js +341 -0
  307. package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
  308. package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
  309. package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
  310. package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
  311. package/dist/manager/routes/payments/intent/providers/test.js +341 -0
  312. package/dist/manager/routes/payments/plan/post.js +147 -0
  313. package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
  314. package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
  315. package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
  316. package/dist/manager/routes/payments/plan/providers/test.js +113 -0
  317. package/dist/manager/routes/payments/portal/post.js +113 -0
  318. package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
  319. package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
  320. package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
  321. package/dist/manager/routes/payments/portal/providers/test.js +18 -0
  322. package/dist/manager/routes/payments/refund/post.js +207 -0
  323. package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
  324. package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
  325. package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
  326. package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
  327. package/dist/manager/routes/payments/refund/providers/test.js +204 -0
  328. package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
  329. package/dist/manager/routes/payments/uncancel/post.js +106 -0
  330. package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
  331. package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
  332. package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
  333. package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
  334. package/dist/manager/routes/payments/webhook/post.js +157 -0
  335. package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
  336. package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
  337. package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
  338. package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
  339. package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
  340. package/dist/manager/routes/payments/winback/post.js +276 -0
  341. package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
  342. package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
  343. package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
  344. package/dist/manager/routes/payments/winback/providers/test.js +42 -0
  345. package/dist/manager/routes/restart/index.js +37 -0
  346. package/dist/manager/routes/special/electron-client/post.js +59 -0
  347. package/dist/manager/routes/test/authenticate/get.js +11 -0
  348. package/dist/manager/routes/test/health/get.js +8 -0
  349. package/dist/manager/routes/test/index.js +16 -0
  350. package/dist/manager/routes/test/lab/post.js +17 -0
  351. package/dist/manager/routes/test/redirect/get.js +27 -0
  352. package/dist/manager/routes/test/reset-account/post.js +79 -0
  353. package/dist/manager/routes/test/roster/get.js +40 -0
  354. package/dist/manager/routes/test/schema/post.js +20 -0
  355. package/dist/manager/routes/test/usage/post.js +44 -0
  356. package/dist/manager/routes/test/webhook/post.js +21 -0
  357. package/dist/manager/routes/user/api-keys/post.js +51 -0
  358. package/dist/manager/routes/user/connections/_context.js +194 -0
  359. package/dist/manager/routes/user/connections/_grant.js +247 -0
  360. package/dist/manager/routes/user/connections/_lease.js +184 -0
  361. package/dist/manager/routes/user/connections/_providers.js +191 -0
  362. package/dist/manager/routes/user/connections/_state.js +117 -0
  363. package/dist/manager/routes/user/connections/delete.js +55 -0
  364. package/dist/manager/routes/user/connections/get.js +167 -0
  365. package/dist/manager/routes/user/connections/post.js +370 -0
  366. package/dist/manager/routes/user/connections/providers/discord.js +41 -0
  367. package/dist/manager/routes/user/connections/providers/google.js +55 -0
  368. package/dist/manager/routes/user/connections/providers/kick.js +68 -0
  369. package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
  370. package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
  371. package/dist/manager/routes/user/data-request/delete.js +84 -0
  372. package/dist/manager/routes/user/data-request/get.js +229 -0
  373. package/dist/manager/routes/user/data-request/post.js +136 -0
  374. package/dist/manager/routes/user/delete.js +149 -0
  375. package/dist/manager/routes/user/feedback/post.js +81 -0
  376. package/dist/manager/routes/user/get.js +14 -0
  377. package/dist/manager/routes/user/orders/get.js +95 -0
  378. package/dist/manager/routes/user/sessions/delete.js +95 -0
  379. package/dist/manager/routes/user/sessions/get.js +40 -0
  380. package/dist/manager/routes/user/settings/validate/post.js +86 -0
  381. package/dist/manager/routes/user/signup/post.js +662 -0
  382. package/dist/manager/routes/user/subscription/get.js +77 -0
  383. package/dist/manager/routes/user/token/post.js +30 -0
  384. package/dist/manager/routes/verts/delete.js +43 -0
  385. package/dist/manager/routes/verts/get.js +49 -0
  386. package/dist/manager/routes/verts/post.js +61 -0
  387. package/dist/manager/routes/verts/put.js +75 -0
  388. package/dist/manager/routes/verts/redirect/get.js +36 -0
  389. package/dist/manager/routes/verts/serve/get.js +67 -0
  390. package/dist/manager/routes/verts/utils.js +436 -0
  391. package/dist/manager/schemas/admin/backup/post.js +8 -0
  392. package/dist/manager/schemas/admin/cron/post.js +8 -0
  393. package/dist/manager/schemas/admin/database/get.js +5 -0
  394. package/dist/manager/schemas/admin/database/post.js +6 -0
  395. package/dist/manager/schemas/admin/email/post.js +31 -0
  396. package/dist/manager/schemas/admin/firestore/get.js +5 -0
  397. package/dist/manager/schemas/admin/firestore/post.js +8 -0
  398. package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
  399. package/dist/manager/schemas/admin/hook/post.js +8 -0
  400. package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
  401. package/dist/manager/schemas/admin/notification/post.js +23 -0
  402. package/dist/manager/schemas/admin/payment/post.js +8 -0
  403. package/dist/manager/schemas/admin/post/post.js +23 -0
  404. package/dist/manager/schemas/admin/post/put.js +13 -0
  405. package/dist/manager/schemas/admin/repo/content/post.js +10 -0
  406. package/dist/manager/schemas/admin/stats/get.js +5 -0
  407. package/dist/manager/schemas/admin/users/disable/post.js +9 -0
  408. package/dist/manager/schemas/admin/users/list/get.js +10 -0
  409. package/dist/manager/schemas/admin/users/sync/post.js +7 -0
  410. package/dist/manager/schemas/brand/get.js +3 -0
  411. package/dist/manager/schemas/content/post/get.js +8 -0
  412. package/dist/manager/schemas/general/email/post.js +10 -0
  413. package/dist/manager/schemas/general/uuid/post.js +9 -0
  414. package/dist/manager/schemas/handler/post/post.js +13 -0
  415. package/dist/manager/schemas/health/get.js +3 -0
  416. package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
  417. package/dist/manager/schemas/marketing/campaign/get.js +13 -0
  418. package/dist/manager/schemas/marketing/campaign/post.js +43 -0
  419. package/dist/manager/schemas/marketing/campaign/put.js +37 -0
  420. package/dist/manager/schemas/marketing/contact/delete.js +8 -0
  421. package/dist/manager/schemas/marketing/contact/post.js +14 -0
  422. package/dist/manager/schemas/marketing/contact/put.js +8 -0
  423. package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
  424. package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
  425. package/dist/manager/schemas/marketing/webhook/post.js +7 -0
  426. package/dist/manager/schemas/payments/cancel/post.js +15 -0
  427. package/dist/manager/schemas/payments/discount/get.js +8 -0
  428. package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
  429. package/dist/manager/schemas/payments/intent/post.js +28 -0
  430. package/dist/manager/schemas/payments/plan/post.js +11 -0
  431. package/dist/manager/schemas/payments/portal/post.js +12 -0
  432. package/dist/manager/schemas/payments/refund/post.js +14 -0
  433. package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
  434. package/dist/manager/schemas/payments/uncancel/post.js +9 -0
  435. package/dist/manager/schemas/payments/webhook/post.js +6 -0
  436. package/dist/manager/schemas/payments/winback/post.js +12 -0
  437. package/dist/manager/schemas/restart/index.js +5 -0
  438. package/dist/manager/schemas/special/electron-client/post.js +11 -0
  439. package/dist/manager/schemas/test/authenticate/get.js +3 -0
  440. package/dist/manager/schemas/test/health/get.js +3 -0
  441. package/dist/manager/schemas/test/index.js +5 -0
  442. package/dist/manager/schemas/test/lab/post.js +3 -0
  443. package/dist/manager/schemas/test/redirect/get.js +7 -0
  444. package/dist/manager/schemas/test/reset-account/post.js +3 -0
  445. package/dist/manager/schemas/test/roster/get.js +3 -0
  446. package/dist/manager/schemas/test/schema/post.js +70 -0
  447. package/dist/manager/schemas/test/usage/post.js +8 -0
  448. package/dist/manager/schemas/test/webhook/post.js +7 -0
  449. package/dist/manager/schemas/user/api-keys/post.js +6 -0
  450. package/dist/manager/schemas/user/connections/delete.js +8 -0
  451. package/dist/manager/schemas/user/connections/get.js +21 -0
  452. package/dist/manager/schemas/user/connections/post.js +12 -0
  453. package/dist/manager/schemas/user/data-request/delete.js +3 -0
  454. package/dist/manager/schemas/user/data-request/get.js +5 -0
  455. package/dist/manager/schemas/user/data-request/post.js +6 -0
  456. package/dist/manager/schemas/user/delete.js +6 -0
  457. package/dist/manager/schemas/user/feedback/post.js +8 -0
  458. package/dist/manager/schemas/user/get.js +3 -0
  459. package/dist/manager/schemas/user/orders/get.js +6 -0
  460. package/dist/manager/schemas/user/sessions/delete.js +6 -0
  461. package/dist/manager/schemas/user/sessions/get.js +6 -0
  462. package/dist/manager/schemas/user/settings/validate/post.js +8 -0
  463. package/dist/manager/schemas/user/signup/post.js +27 -0
  464. package/dist/manager/schemas/user/subscription/get.js +5 -0
  465. package/dist/manager/schemas/user/token/post.js +5 -0
  466. package/dist/manager/schemas/verts/delete.js +8 -0
  467. package/dist/manager/schemas/verts/get.js +9 -0
  468. package/dist/manager/schemas/verts/post.js +23 -0
  469. package/dist/manager/schemas/verts/put.js +20 -0
  470. package/dist/manager/schemas/verts/redirect/get.js +10 -0
  471. package/dist/manager/schemas/verts/serve/get.js +14 -0
  472. package/dist/manager/server-manager.js +69 -0
  473. package/dist/mcp/client.js +98 -0
  474. package/dist/mcp/handler.js +521 -0
  475. package/dist/mcp/index.js +157 -0
  476. package/dist/mcp/tools.js +555 -0
  477. package/dist/mcp/utils.js +108 -0
  478. package/dist/omega-bin.js +6 -0
  479. package/dist/require.js +3 -0
  480. package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
  481. package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
  482. package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
  483. package/dist/test/fixtures/firebase-project/firebase.json +49 -0
  484. package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
  485. package/dist/test/fixtures/firebase-project/package.json +14 -0
  486. package/dist/test/fixtures/firebase-project/src/index.js +11 -0
  487. package/dist/test/fixtures/firebase-project/storage.rules +8 -0
  488. package/dist/test/parse-audit.js +25 -0
  489. package/dist/test/run-tests.js +90 -0
  490. package/dist/test/runner.js +982 -0
  491. package/dist/test/seed.js +242 -0
  492. package/dist/test/test-accounts.js +2660 -0
  493. package/dist/test/utils/assertions.js +189 -0
  494. package/dist/test/utils/email-capture.js +252 -0
  495. package/dist/test/utils/extended-mode-warning.js +11 -0
  496. package/dist/test/utils/firestore-rules-client.js +277 -0
  497. package/dist/test/utils/http-client.js +228 -0
  498. package/dist/test/utils/test-mode-file.js +192 -0
  499. package/dist/utils/merge-line-files.js +6 -0
  500. package/dist/utils/scaffold-defaults.js +71 -0
  501. package/dist/utils/test-lanes.js +62 -0
  502. package/dist/vendor/account/engine.js +182 -0
  503. package/dist/vendor/account/features.js +220 -0
  504. package/dist/vendor/account/index.js +53 -0
  505. package/dist/vendor/account/schema.js +272 -0
  506. package/dist/vendor/account/subscription.js +38 -0
  507. package/dist/vendor/analytics/adapters/ga4.js +26 -0
  508. package/dist/vendor/analytics/adapters/meta.js +26 -0
  509. package/dist/vendor/analytics/adapters/resolve.js +130 -0
  510. package/dist/vendor/analytics/adapters/tiktok.js +27 -0
  511. package/dist/vendor/analytics/catalog.js +908 -0
  512. package/dist/vendor/analytics/identity.js +136 -0
  513. package/dist/vendor/config/company.js +31 -0
  514. package/dist/vendor/config/defaults.js +173 -0
  515. package/dist/vendor/config/demo.js +18 -0
  516. package/dist/vendor/config/desktop-artifacts.js +110 -0
  517. package/dist/vendor/config/edit.js +769 -0
  518. package/dist/vendor/config/env-delivery.js +145 -0
  519. package/dist/vendor/config/env-rules.js +93 -0
  520. package/dist/vendor/config/env-schema.js +1078 -0
  521. package/dist/vendor/config/env.js +445 -0
  522. package/dist/vendor/config/hooks.js +97 -0
  523. package/dist/vendor/config/index.js +237 -0
  524. package/dist/vendor/config/instances.js +208 -0
  525. package/dist/vendor/config/load.js +490 -0
  526. package/dist/vendor/config/merge.js +68 -0
  527. package/dist/vendor/config/order.js +139 -0
  528. package/dist/vendor/config/ports.js +374 -0
  529. package/dist/vendor/config/providers.js +32 -0
  530. package/dist/vendor/config/repo.js +142 -0
  531. package/dist/vendor/config/retired-keys.js +430 -0
  532. package/dist/vendor/config/schema.js +1610 -0
  533. package/dist/vendor/config/secrets.js +50 -0
  534. package/dist/vendor/config/seed.js +34 -0
  535. package/dist/vendor/config/site-global.js +205 -0
  536. package/dist/vendor/config/validate.js +554 -0
  537. package/dist/vendor/config/winback.js +61 -0
  538. package/dist/vendor/devkit/attach-log-file.js +262 -0
  539. package/dist/vendor/devkit/bare-requires.js +153 -0
  540. package/dist/vendor/devkit/command-path.js +46 -0
  541. package/dist/vendor/devkit/defaults-engine.js +372 -0
  542. package/dist/vendor/devkit/deploy-record.js +180 -0
  543. package/dist/vendor/devkit/deploy.js +286 -0
  544. package/dist/vendor/devkit/license.js +155 -0
  545. package/dist/vendor/devkit/local-https.js +360 -0
  546. package/dist/vendor/devkit/local.js +1905 -0
  547. package/dist/vendor/devkit/logger.js +128 -0
  548. package/dist/vendor/devkit/merge-line-files.js +296 -0
  549. package/dist/vendor/devkit/npm-registry.js +52 -0
  550. package/dist/vendor/devkit/omega-bin.js +345 -0
  551. package/dist/vendor/devkit/parse-audit.js +74 -0
  552. package/dist/vendor/devkit/safe-install.js +18 -0
  553. package/dist/vendor/devkit/scaffold-guard.js +96 -0
  554. package/dist/vendor/devkit/stop-signals.js +28 -0
  555. package/dist/vendor/devkit/test/assert.js +120 -0
  556. package/dist/vendor/devkit/test/define-cases.js +104 -0
  557. package/dist/vendor/devkit/test/runner-core.js +554 -0
  558. package/dist/vendor/devkit/test/scope.js +162 -0
  559. package/dist/vendor/devkit/update.js +569 -0
  560. package/dist/vendor/monitoring/core.js +180 -0
  561. package/dist/vendor/monitoring/env.js +49 -0
  562. package/dist/vendor/monitoring/logger.js +39 -0
  563. package/dist/vendor/monitoring/node.js +72 -0
  564. package/docs/admin-post-route.md +57 -0
  565. package/docs/ai-library.md +171 -0
  566. package/docs/architecture.md +62 -0
  567. package/docs/audit.md +70 -0
  568. package/docs/auth-hooks.md +74 -0
  569. package/docs/build-system.md +65 -0
  570. package/docs/cdp-debugging.md +29 -0
  571. package/docs/cli-firestore-auth.md +85 -0
  572. package/docs/cli-logs.md +67 -0
  573. package/docs/cli-output.md +146 -0
  574. package/docs/code-patterns.md +77 -0
  575. package/docs/common-mistakes.md +12 -0
  576. package/docs/common-operations.md +60 -0
  577. package/docs/connections.md +212 -0
  578. package/docs/consent.md +362 -0
  579. package/docs/directory-structure.md +150 -0
  580. package/docs/email-system.md +459 -0
  581. package/docs/environment-detection.md +93 -0
  582. package/docs/file-naming.md +10 -0
  583. package/docs/firestore.md +134 -0
  584. package/docs/ghostii.md +240 -0
  585. package/docs/index.md +299 -0
  586. package/docs/key-files.md +37 -0
  587. package/docs/logging.md +60 -0
  588. package/docs/marketing-campaigns.md +407 -0
  589. package/docs/marketing-fields.md +25 -0
  590. package/docs/mcp.md +222 -0
  591. package/docs/migration.md +129 -0
  592. package/docs/payment-system.md +856 -0
  593. package/docs/paypal-sandbox-qa.md +105 -0
  594. package/docs/response-headers.md +9 -0
  595. package/docs/routes.md +211 -0
  596. package/docs/sanitization.md +71 -0
  597. package/docs/schemas.md +163 -0
  598. package/docs/shared/agent-docs.md +89 -0
  599. package/docs/shared/analytics.md +612 -0
  600. package/docs/shared/brands.md +51 -0
  601. package/docs/shared/breaking-changes.md +497 -0
  602. package/docs/shared/config.md +1387 -0
  603. package/docs/shared/deploys.md +215 -0
  604. package/docs/shared/icons.md +201 -0
  605. package/docs/shared/local-dev.md +147 -0
  606. package/docs/shared/logging.md +202 -0
  607. package/docs/shared/monitoring.md +153 -0
  608. package/docs/shared/publishing.md +183 -0
  609. package/docs/shared/rulings.md +34 -0
  610. package/docs/shared/testing.md +147 -0
  611. package/docs/shared/theming.md +604 -0
  612. package/docs/shared/translation.md +291 -0
  613. package/docs/shared/updates.md +61 -0
  614. package/docs/stripe-webhook-forwarding.md +20 -0
  615. package/docs/test-boot-layer.md +67 -0
  616. package/docs/test-framework.md +583 -0
  617. package/docs/usage-rate-limiting.md +121 -0
  618. package/docs/verts.md +29 -0
  619. package/package.json +143 -0
  620. package/templates/config/omega.json5 +322 -0
  621. package/templates/database.rules.json +82 -0
  622. package/templates/firebase.json +67 -0
  623. package/templates/firestore.framework.rules +191 -0
  624. package/templates/firestore.indexes.json +4 -0
  625. package/templates/firestore.rules +59 -0
  626. package/templates/index.js +12 -0
  627. package/templates/public/404.html +26 -0
  628. package/templates/public/index.html +24 -0
  629. package/templates/remoteconfig.template.json +1 -0
  630. package/templates/storage-lifecycle-config-1-day.json +9 -0
  631. package/templates/storage-lifecycle-config-30-days.json +9 -0
  632. package/templates/storage.rules +11 -0
  633. package/test/_init/accounts-validation.js +58 -0
  634. package/test/ai/tools-live.test.js +171 -0
  635. package/test/analytics/conversion-delivery.test.js +1013 -0
  636. package/test/analytics/match-normalization.test.js +238 -0
  637. package/test/analytics/signup-conversion.test.js +135 -0
  638. package/test/boot/cli-dispatch.test.js +142 -0
  639. package/test/boot/defaults-scaffold.test.js +248 -0
  640. package/test/boot/deploy-staging.test.js +446 -0
  641. package/test/boot/disposable-domains.test.js +154 -0
  642. package/test/boot/emulator-boots.test.js +39 -0
  643. package/test/boot/emulator-port-preflight.test.js +126 -0
  644. package/test/boot/emulator-ready-timeout.test.js +46 -0
  645. package/test/boot/project-id-refresh.test.js +73 -0
  646. package/test/boot/suite-portability.test.js +278 -0
  647. package/test/boot/update-command.test.js +63 -0
  648. package/test/cli/cross-platform.test.js +168 -0
  649. package/test/cli/custom-project-type.test.js +426 -0
  650. package/test/cli/deploy-license-env.test.js +100 -0
  651. package/test/cli/emulator-adoption.test.js +254 -0
  652. package/test/cli/emulator-orphans.test.js +658 -0
  653. package/test/cli/emulator-ownership.test.js +177 -0
  654. package/test/cli/emulator-port-retry.test.js +395 -0
  655. package/test/cli/emulator-shutdown.test.js +788 -0
  656. package/test/cli/emulator-stale-reap.test.js +574 -0
  657. package/test/cli/ensure-target.test.js +325 -0
  658. package/test/cli/flags.test.js +64 -0
  659. package/test/cli/https-trust.test.js +38 -0
  660. package/test/cli/install-update-dispatch.test.js +47 -0
  661. package/test/cli/lane-environments.test.js +159 -0
  662. package/test/cli/marketing-campaigns-seeded.test.js +194 -0
  663. package/test/cli/migrate-bare-requires.test.js +128 -0
  664. package/test/cli/migrate-markers.test.js +530 -0
  665. package/test/cli/required-indexes.test.js +64 -0
  666. package/test/cli/rules-compile.test.js +622 -0
  667. package/test/cli/rules-migration-deferral.test.js +259 -0
  668. package/test/cli/rules-version.test.js +104 -0
  669. package/test/cli/setup-load-files.test.js +66 -0
  670. package/test/cli/setup-offline-mode.test.js +276 -0
  671. package/test/cli/setup-retired.test.js +64 -0
  672. package/test/cli/setup-shared-project-indexes.test.js +262 -0
  673. package/test/cli/setup-tests-requires.test.js +45 -0
  674. package/test/cli/stage-env-compose.test.js +316 -0
  675. package/test/cli/stripe-live-lane.test.js +303 -0
  676. package/test/cli/target.test.js +182 -0
  677. package/test/cli/templates.test.js +66 -0
  678. package/test/cli/test-runner-env.test.js +115 -0
  679. package/test/cli/test-stack-shutdown.test.js +275 -0
  680. package/test/cli/test-target-no-match.test.js +128 -0
  681. package/test/cli/verb-logs.test.js +118 -0
  682. package/test/cli/version-dispatch.test.js +39 -0
  683. package/test/content/blog-generate.test.js +164 -0
  684. package/test/email/campaign-config-fault.test.js +376 -0
  685. package/test/email/campaign-cron-pipeline.test.js +536 -0
  686. package/test/email/campaign-send.test.js +47 -0
  687. package/test/email/consent-lifecycle.test.js +258 -0
  688. package/test/email/content-html-policy.test.js +270 -0
  689. package/test/email/feedback-and-plain-send.test.js +54 -0
  690. package/test/email/fixtures/clean.json +30 -0
  691. package/test/email/fixtures/editorial.json +30 -0
  692. package/test/email/fixtures/field-report.json +53 -0
  693. package/test/email/identity.test.js +321 -0
  694. package/test/email/marketing/consent-gate.test.js +265 -0
  695. package/test/email/marketing/custom-fields-catalog.test.js +259 -0
  696. package/test/email/marketing/prune-per-provider.test.js +762 -0
  697. package/test/email/marketing/remove-log-privacy.test.js +225 -0
  698. package/test/email/marketing-lifecycle.test.js +139 -0
  699. package/test/email/newsletter-generate.test.js +853 -0
  700. package/test/email/newsletter-svg-tokens.test.js +51 -0
  701. package/test/email/newsletter-templates.test.js +492 -0
  702. package/test/email/order-one-time-cta.test.js +97 -0
  703. package/test/email/render-content.test.js +248 -0
  704. package/test/email/safe-url.test.js +0 -0
  705. package/test/email/sanitize-images.test.js +66 -0
  706. package/test/email/send-log-privacy.test.js +107 -0
  707. package/test/email/templates.test.js +288 -0
  708. package/test/email/testing-capture.test.js +260 -0
  709. package/test/email/transactional-send.test.js +36 -0
  710. package/test/email/transactional.test.js +562 -0
  711. package/test/email/unsubscribe-groups.test.js +155 -0
  712. package/test/email/unsubscribe-key.test.js +91 -0
  713. package/test/email/validation-cases.test.js +152 -0
  714. package/test/email/validation.test.js +746 -0
  715. package/test/events/auth-delete-conversion.test.js +138 -0
  716. package/test/events/auth-delete-race.test.js +211 -0
  717. package/test/events/auth-on-create-log.test.js +164 -0
  718. package/test/events/auth-signup-conversion.test.js +141 -0
  719. package/test/events/auth-signup-limit.test.js +142 -0
  720. package/test/events/auth-trigger-log-privacy.test.js +256 -0
  721. package/test/events/cron-job-doc-shape.test.js +101 -0
  722. package/test/events/cron-reset-usage.test.js +116 -0
  723. package/test/events/notification-conversion.test.js +182 -0
  724. package/test/events/payments/_webhook-harness.js +307 -0
  725. package/test/events/payments/abandoned-cart-activity.test.js +135 -0
  726. package/test/events/payments/analytics-payment-events.test.js +945 -0
  727. package/test/events/payments/dispute-email-status.test.js +55 -0
  728. package/test/events/payments/journey-payments-abandoned.test.js +126 -0
  729. package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
  730. package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
  731. package/test/events/payments/journey-payments-cancel.test.js +184 -0
  732. package/test/events/payments/journey-payments-decline.test.js +225 -0
  733. package/test/events/payments/journey-payments-discount.test.js +91 -0
  734. package/test/events/payments/journey-payments-dispute.test.js +190 -0
  735. package/test/events/payments/journey-payments-failure.test.js +148 -0
  736. package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
  737. package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
  738. package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
  739. package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
  740. package/test/events/payments/journey-payments-one-time.test.js +175 -0
  741. package/test/events/payments/journey-payments-plan-change.test.js +146 -0
  742. package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
  743. package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
  744. package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
  745. package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
  746. package/test/events/payments/journey-payments-suspend.test.js +183 -0
  747. package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
  748. package/test/events/payments/journey-payments-trial.test.js +173 -0
  749. package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
  750. package/test/events/payments/journey-payments-uncancel.test.js +157 -0
  751. package/test/events/payments/journey-payments-upgrade.test.js +137 -0
  752. package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
  753. package/test/events/payments/journey-payments-winback.test.js +195 -0
  754. package/test/events/payments/paypal-expiry-cron.test.js +166 -0
  755. package/test/events/payments/purchase-failed-handler.test.js +80 -0
  756. package/test/events/payments/purchase-refunded-handler.test.js +108 -0
  757. package/test/events/payments/test-processor-doc-shape.test.js +162 -0
  758. package/test/events/payments/transition-order-emails.test.js +185 -0
  759. package/test/events/payments/transition-promo-lines.test.js +192 -0
  760. package/test/events/payments/transitions-detect.test.js +767 -0
  761. package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
  762. package/test/events/payments/trial-lapse-sweep.test.js +359 -0
  763. package/test/events/payments/webhook-atomic-writes.test.js +69 -0
  764. package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
  765. package/test/events/payments/webhook-discount-clear.test.js +189 -0
  766. package/test/events/payments/webhook-failure-intent.test.js +93 -0
  767. package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
  768. package/test/events/payments/webhook-ordering.test.js +326 -0
  769. package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
  770. package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
  771. package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
  772. package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
  773. package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
  774. package/test/events/payments/webhook-refund-without-order.test.js +170 -0
  775. package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
  776. package/test/events/payments/webhook-refused-intent.test.js +142 -0
  777. package/test/events/payments/webhook-retry-sweep.test.js +252 -0
  778. package/test/events/payments/webhook-transition-claim.test.js +389 -0
  779. package/test/events/payments/webhook-uid-trust.test.js +203 -0
  780. package/test/events/payments/webhook-user-without-auth.test.js +161 -0
  781. package/test/fixtures/chargebee/invoice-one-time.json +27 -0
  782. package/test/fixtures/chargebee/subscription-active.json +44 -0
  783. package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
  784. package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
  785. package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
  786. package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
  787. package/test/fixtures/chargebee/subscription-paused.json +42 -0
  788. package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
  789. package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
  790. package/test/fixtures/coinbase/charge-confirmed.json +50 -0
  791. package/test/fixtures/coinbase/charge-failed.json +34 -0
  792. package/test/fixtures/coinbase/charge-pending.json +34 -0
  793. package/test/fixtures/migrate/ported-route.js.txt +11 -0
  794. package/test/fixtures/paypal/capture-completed.json +51 -0
  795. package/test/fixtures/paypal/capture-refunded.json +43 -0
  796. package/test/fixtures/paypal/order-approved.json +62 -0
  797. package/test/fixtures/paypal/order-completed.json +110 -0
  798. package/test/fixtures/paypal/sale-refunded.json +38 -0
  799. package/test/fixtures/paypal/subscription-active.json +76 -0
  800. package/test/fixtures/paypal/subscription-cancelled.json +50 -0
  801. package/test/fixtures/paypal/subscription-suspended.json +65 -0
  802. package/test/fixtures/stripe/checkout-session-completed.json +130 -0
  803. package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
  804. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
  805. package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
  806. package/test/fixtures/stripe/subscription-active.json +161 -0
  807. package/test/fixtures/stripe/subscription-canceled.json +161 -0
  808. package/test/fixtures/stripe/subscription-trialing.json +161 -0
  809. package/test/helpers/_shared-config.js +15 -0
  810. package/test/helpers/ai-request-payload.test.js +619 -0
  811. package/test/helpers/ai-schema-resolve.test.js +125 -0
  812. package/test/helpers/ai-test-provider.test.js +227 -0
  813. package/test/helpers/ai-token-accounting.test.js +150 -0
  814. package/test/helpers/ai-tools-format.test.js +384 -0
  815. package/test/helpers/analytics-no-id-notice.test.js +146 -0
  816. package/test/helpers/analytics-user-data.test.js +194 -0
  817. package/test/helpers/api-manager.test.js +311 -0
  818. package/test/helpers/backend-router.test.js +136 -0
  819. package/test/helpers/content/blog-auto-publisher.test.js +482 -0
  820. package/test/helpers/content/feed-parser.test.js +529 -0
  821. package/test/helpers/content/ghostii-blocks.test.js +135 -0
  822. package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
  823. package/test/helpers/content/ghostii-write-article.test.js +244 -0
  824. package/test/helpers/dev-only-routes.test.js +225 -0
  825. package/test/helpers/env-reader.test.js +362 -0
  826. package/test/helpers/environment.test.js +263 -0
  827. package/test/helpers/event-middleware.test.js +407 -0
  828. package/test/helpers/infer-contact.test.js +157 -0
  829. package/test/helpers/lane-url.test.js +90 -0
  830. package/test/helpers/merge-line-files.test.js +272 -0
  831. package/test/helpers/metadata.test.js +122 -0
  832. package/test/helpers/middleware-request-log.test.js +134 -0
  833. package/test/helpers/middleware-user-log.test.js +124 -0
  834. package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
  835. package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
  836. package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
  837. package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
  838. package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
  839. package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
  840. package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
  841. package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
  842. package/test/helpers/payment/discount-codes.test.js +141 -0
  843. package/test/helpers/payment/extract-resource.test.js +113 -0
  844. package/test/helpers/payment/fetch-failure.test.js +215 -0
  845. package/test/helpers/payment/license-gate.test.js +108 -0
  846. package/test/helpers/payment/order-id.test.js +99 -0
  847. package/test/helpers/payment/paypal/create-intent.test.js +382 -0
  848. package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
  849. package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
  850. package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
  851. package/test/helpers/payment/paypal/refund-details.test.js +202 -0
  852. package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
  853. package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
  854. package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
  855. package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
  856. package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
  857. package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
  858. package/test/helpers/payment/stripe/refund-details.test.js +147 -0
  859. package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
  860. package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
  861. package/test/helpers/persona-domain.test.js +87 -0
  862. package/test/helpers/recaptcha.test.js +166 -0
  863. package/test/helpers/resolved-config.test.js +106 -0
  864. package/test/helpers/response-log-redaction.test.js +262 -0
  865. package/test/helpers/roles.test.js +155 -0
  866. package/test/helpers/route-context-debug-gate.test.js +118 -0
  867. package/test/helpers/route-context-logging.test.js +214 -0
  868. package/test/helpers/route-context.test.js +129 -0
  869. package/test/helpers/safe-compare.test.js +114 -0
  870. package/test/helpers/sanitize.test.js +223 -0
  871. package/test/helpers/schema-engine.test.js +537 -0
  872. package/test/helpers/schema-zod.test.js +554 -0
  873. package/test/helpers/seed-accounts-load.test.js +163 -0
  874. package/test/helpers/seed-google-personas.test.js +114 -0
  875. package/test/helpers/seeded-personas.test.js +823 -0
  876. package/test/helpers/settings.test.js +292 -0
  877. package/test/helpers/setup-engines-pin.test.js +68 -0
  878. package/test/helpers/setup-manifest-sync.test.js +116 -0
  879. package/test/helpers/slugify.test.js +395 -0
  880. package/test/helpers/storage.test.js +195 -0
  881. package/test/helpers/test-banner-latch.test.js +139 -0
  882. package/test/helpers/usage-consume.test.js +460 -0
  883. package/test/helpers/usage-log-privacy.test.js +121 -0
  884. package/test/helpers/user-doc-heal.test.js +540 -0
  885. package/test/helpers/user.test.js +771 -0
  886. package/test/helpers/webhook-forward.test.js +419 -0
  887. package/test/helpers/wipe-auth-project.test.js +143 -0
  888. package/test/mcp/discovery.test.js +52 -0
  889. package/test/mcp/oauth.test.js +160 -0
  890. package/test/mcp/protocol.test.js +278 -0
  891. package/test/mcp/roles.test.js +203 -0
  892. package/test/mcp/utils.test.js +245 -0
  893. package/test/notification/identity.test.js +155 -0
  894. package/test/routes/admin/create-post.test.js +362 -0
  895. package/test/routes/admin/database.test.js +133 -0
  896. package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
  897. package/test/routes/admin/email-content-html.test.js +58 -0
  898. package/test/routes/admin/email-request-log.test.js +86 -0
  899. package/test/routes/admin/email.test.js +116 -0
  900. package/test/routes/admin/firestore-query.test.js +206 -0
  901. package/test/routes/admin/firestore.test.js +129 -0
  902. package/test/routes/admin/infer-contact.test.js +220 -0
  903. package/test/routes/admin/notification.test.js +199 -0
  904. package/test/routes/admin/post-convert-image.test.js +159 -0
  905. package/test/routes/admin/post-deploy-flag.test.js +81 -0
  906. package/test/routes/admin/post-download-error.test.js +90 -0
  907. package/test/routes/admin/post-resize-image.test.js +185 -0
  908. package/test/routes/admin/post.test.js +369 -0
  909. package/test/routes/admin/repo-content.test.js +223 -0
  910. package/test/routes/admin/stats.test.js +114 -0
  911. package/test/routes/admin/users-disable.test.js +63 -0
  912. package/test/routes/admin/users-list.test.js +71 -0
  913. package/test/routes/content/post.test.js +60 -0
  914. package/test/routes/general/uuid.test.js +133 -0
  915. package/test/routes/health.test.js +110 -0
  916. package/test/routes/marketing/campaign.test.js +184 -0
  917. package/test/routes/marketing/contact.test.js +416 -0
  918. package/test/routes/marketing/email-preferences.test.js +293 -0
  919. package/test/routes/marketing/push-send.test.js +34 -0
  920. package/test/routes/marketing/webhook-forward.test.js +63 -0
  921. package/test/routes/marketing/webhook.test.js +641 -0
  922. package/test/routes/payments/_route-harness.js +145 -0
  923. package/test/routes/payments/cancel-provider-errors.test.js +227 -0
  924. package/test/routes/payments/cancel-skip-guards.test.js +126 -0
  925. package/test/routes/payments/cancel-trialing.test.js +428 -0
  926. package/test/routes/payments/cancel.test.js +165 -0
  927. package/test/routes/payments/dedup-race.test.js +186 -0
  928. package/test/routes/payments/discount.test.js +82 -0
  929. package/test/routes/payments/dispute-alert.test.js +324 -0
  930. package/test/routes/payments/intent-discount-amounts.test.js +550 -0
  931. package/test/routes/payments/intent-discount-coupons.test.js +293 -0
  932. package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
  933. package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
  934. package/test/routes/payments/intent-zero-total.test.js +260 -0
  935. package/test/routes/payments/intent.test.js +413 -0
  936. package/test/routes/payments/plan.test.js +367 -0
  937. package/test/routes/payments/portal-return-url.test.js +107 -0
  938. package/test/routes/payments/portal.test.js +94 -0
  939. package/test/routes/payments/refund-one-time.test.js +254 -0
  940. package/test/routes/payments/refund-paypal-proration.test.js +190 -0
  941. package/test/routes/payments/refund.test.js +181 -0
  942. package/test/routes/payments/trial-eligibility.test.js +73 -0
  943. package/test/routes/payments/uncancel.test.js +270 -0
  944. package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
  945. package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
  946. package/test/routes/payments/webhook-test-provider.test.js +156 -0
  947. package/test/routes/payments/webhook.test.js +115 -0
  948. package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
  949. package/test/routes/payments/winback.test.js +597 -0
  950. package/test/routes/test/authenticate.test.js +79 -0
  951. package/test/routes/test/redirect.test.js +66 -0
  952. package/test/routes/test/reset-account.test.js +126 -0
  953. package/test/routes/test/roster.test.js +101 -0
  954. package/test/routes/test/schema.test.js +556 -0
  955. package/test/routes/test/usage.test.js +374 -0
  956. package/test/routes/user/api-keys.test.js +158 -0
  957. package/test/routes/user/connections-grant.test.js +759 -0
  958. package/test/routes/user/connections-identity.test.js +260 -0
  959. package/test/routes/user/connections-log-privacy.test.js +206 -0
  960. package/test/routes/user/connections-refresh-lease.test.js +696 -0
  961. package/test/routes/user/connections-return.test.js +261 -0
  962. package/test/routes/user/connections-uid.test.js +516 -0
  963. package/test/routes/user/delete.test.js +138 -0
  964. package/test/routes/user/feedback.test.js +106 -0
  965. package/test/routes/user/orders.test.js +233 -0
  966. package/test/routes/user/sessions.test.js +231 -0
  967. package/test/routes/user/settings-validate.test.js +84 -0
  968. package/test/routes/user/signup-emails.test.js +113 -0
  969. package/test/routes/user/signup-location.test.js +84 -0
  970. package/test/routes/user/signup-log-privacy.test.js +112 -0
  971. package/test/routes/user/signup.test.js +579 -0
  972. package/test/routes/user/subscription.test.js +101 -0
  973. package/test/routes/user/token.test.js +111 -0
  974. package/test/routes/user/user.test.js +158 -0
  975. package/test/routes/verts/cache.test.js +78 -0
  976. package/test/routes/verts/click-destination.test.js +76 -0
  977. package/test/routes/verts/crud.test.js +194 -0
  978. package/test/routes/verts/redirect.test.js +147 -0
  979. package/test/routes/verts/selection.test.js +237 -0
  980. package/test/routes/verts/serve.test.js +185 -0
  981. package/test/routes/verts/unit-document.test.js +117 -0
  982. package/test/rules/_environment.js +66 -0
  983. package/test/rules/brand-merge.test.js +195 -0
  984. package/test/rules/field-helpers.test.js +309 -0
  985. package/test/rules/notifications.test.js +577 -0
  986. package/test/rules/payments-carts.test.js +406 -0
  987. package/test/rules/sessions.test.js +170 -0
  988. package/test/rules/user.test.js +517 -0
  989. package/test/rules/verts.test.js +101 -0
  990. package/test/security/fetch-log-secrets.test.js +124 -0
  991. package/test/security/primitives.test.js +105 -0
  992. package/test/security/repo-pinning.test.js +77 -0
  993. package/test/stripe-live/subscription-lifecycle.test.js +141 -0
@@ -0,0 +1,976 @@
1
+ const BaseCommand = require('./base-command');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const util = require('util');
6
+ const { spawnShell } = require('../utils/spawn-shell');
7
+ const chalk = require('chalk').default;
8
+ const jetpack = require('fs-jetpack');
9
+ const powertools = require('node-powertools');
10
+ const { loadEmulatorPorts } = require('./setup-tests/emulator-config');
11
+ const { javaInstallHint } = require('./setup-tests/helpers');
12
+ const { readPortsFile, portsToEnv } = require('../../vendor/config/index.js');
13
+ const { writeTestMode, captureSyncedEnv, SYNCED_ENV_KEYS } = require('../../test/utils/test-mode-file');
14
+ const EmulatorCommand = require('./emulator');
15
+ const { refuseWhenCustom } = require('../utils/project-type');
16
+ const { runTargetChecks } = require('../utils/target-checks');
17
+ const { STOP_SIGNALS } = require('../../vendor/devkit/stop-signals.js');
18
+
19
+ // The Firebase emulator hub — fixed, not part of the N7-allocated map.
20
+ const HUB_PORT = 4400;
21
+
22
+ // Where a running hub publishes WHO it is: firebase-tools writes
23
+ // `${os.tmpdir()}/hub-<projectId>.json` ({version, origins, pid}) for the life
24
+ // of the stack. The file name IS the identity — the hub's own
25
+ // `GET /emulators` answers a map of emulator → listen info and has never
26
+ // carried a project id, so the read that asked it for one always came back
27
+ // empty ([#258](https://github.com/Omega-JS-Stack/omega/issues/258)).
28
+ const HUB_LOCATOR_PREFIX = 'hub-';
29
+ const HUB_LOCATOR_SUFFIX = '.json';
30
+
31
+ /**
32
+ * Does a process with this pid exist right now?
33
+ *
34
+ * Signal 0 checks for existence without delivering anything. EPERM means it
35
+ * exists and belongs to another user — still alive, which is the question.
36
+ * @param {number} pid
37
+ * @returns {boolean}
38
+ */
39
+ function isProcessAlive(pid) {
40
+ if (!Number.isInteger(pid) || pid <= 1) {
41
+ return false;
42
+ }
43
+
44
+ try {
45
+ process.kill(pid, 0);
46
+ return true;
47
+ } catch (error) {
48
+ return error.code === 'EPERM';
49
+ }
50
+ }
51
+
52
+ /**
53
+ * The project id of the emulator whose HUB is listening on `hubPort`, read
54
+ * from the locator files firebase-tools publishes.
55
+ *
56
+ * A locator only counts when it claims THIS port and its pid is still alive —
57
+ * firebase-tools does not always remove the file, so a crashed run's leftover
58
+ * would otherwise name a hub that is long gone. Two live claims on one port
59
+ * mean one of them is a stale file whose pid got recycled, and nothing says
60
+ * which: ambiguous reads as unproven, the same safe fall-through as no file at
61
+ * all ([#258](https://github.com/Omega-JS-Stack/omega/issues/258)).
62
+ * @param {number} hubPort - The hub port to identify.
63
+ * @param {string} [tmpDir] - Where locators live (defaults to os.tmpdir()).
64
+ * @returns {string|null} The project id, or null when unproven.
65
+ */
66
+ function readHubLocatorProjectId(hubPort, tmpDir) {
67
+ const dir = tmpDir || os.tmpdir();
68
+ const claimants = new Set();
69
+
70
+ for (const name of jetpack.list(dir) || []) {
71
+ if (!name.startsWith(HUB_LOCATOR_PREFIX) || !name.endsWith(HUB_LOCATOR_SUFFIX)) {
72
+ continue;
73
+ }
74
+
75
+ const projectId = name.slice(HUB_LOCATOR_PREFIX.length, -HUB_LOCATOR_SUFFIX.length);
76
+ if (!projectId) {
77
+ continue;
78
+ }
79
+
80
+ let locator;
81
+ try {
82
+ locator = jetpack.read(path.join(dir, name), 'json');
83
+ } catch (error) {
84
+ continue; // Not a locator, or half-written
85
+ }
86
+
87
+ const onThisPort = (locator?.origins || []).some((origin) => {
88
+ try {
89
+ return Number(new URL(origin).port) === Number(hubPort);
90
+ } catch (error) {
91
+ return false;
92
+ }
93
+ });
94
+
95
+ if (onThisPort && isProcessAlive(Number(locator.pid))) {
96
+ claimants.add(projectId);
97
+ }
98
+ }
99
+
100
+ return claimants.size === 1 ? [...claimants][0] : null;
101
+ }
102
+
103
+ /**
104
+ * Adopt the emulator already listening on our functions port, or allocate
105
+ * fresh ports and boot our own?
106
+ *
107
+ * A bare port probe answered "something is listening", which the run read as
108
+ * "our emulator is up" — so another brand's stack on the classic ports got
109
+ * adopted and the run died on a health fetch against the wrong project
110
+ * ([#258](https://github.com/Omega-JS-Stack/omega/issues/258)). Adoption now
111
+ * needs PROOF of identity: unproven falls through to the N7 bump path, which
112
+ * relocates around the incumbent exactly as a second brand's boot does.
113
+ * @param {object} evidence
114
+ * @param {boolean} evidence.portInUse - Something is listening on the functions port.
115
+ * @param {boolean} [evidence.ownsPortsFile] - This project's own live ports file publishes that port.
116
+ * @param {string|null} evidence.runningProjectId - Project id the listener reports (null when unreadable).
117
+ * @param {string|null} evidence.expectedProjectId - Project id this run belongs to.
118
+ * @returns {{adopt: boolean, reason: string, runningProjectId: string|null}}
119
+ */
120
+ function decideEmulatorAdoption({ portInUse, ownsPortsFile, runningProjectId, expectedProjectId }) {
121
+ const decision = { adopt: false, reason: 'no-emulator', runningProjectId: runningProjectId || null };
122
+
123
+ if (!portInUse) {
124
+ return decision;
125
+ }
126
+
127
+ // This project's .temp/ports.json, written by OUR emulator and read only
128
+ // while its writer is alive. The hub is NOT allocated by N7, so a bumped run
129
+ // shares it with whoever booted first — this is the proof that still holds.
130
+ if (ownsPortsFile) {
131
+ return { ...decision, adopt: true, reason: 'ports-file' };
132
+ }
133
+
134
+ if (!runningProjectId || !expectedProjectId) {
135
+ return { ...decision, reason: 'unidentified' };
136
+ }
137
+
138
+ if (runningProjectId !== expectedProjectId) {
139
+ return { ...decision, reason: 'project-mismatch' };
140
+ }
141
+
142
+ return { ...decision, adopt: true, reason: 'project-match' };
143
+ }
144
+
145
+ class TestCommand extends BaseCommand {
146
+ async execute() {
147
+ const self = this.main;
148
+ const argv = self.argv;
149
+
150
+ // The emulator test lane needs Cloud Functions; custom mode has none (#584)
151
+ if (refuseWhenCustom(self.firebaseProjectPath, 'test')) return;
152
+
153
+ // Tee THIS process to <targetRoot>/logs/test.log (#197) — the setup lines, the
154
+ // port summary and the emulator boot that the runner-child's dist/test.log
155
+ // never sees.
156
+ this.attachVerbLog('test');
157
+
158
+ // `--extended` CLI shorthand for the shared, unprefixed TEST_EXTENDED_MODE
159
+ // env var (cross-framework parity with BXM/UJM/EM). Either the flag OR the
160
+ // env var opts into REAL external services (default skipped). Set this
161
+ // BEFORE the captureSyncedEnv/writeTestMode pre-flight below so the flag
162
+ // flows into the emulator's test-mode.json too — making
163
+ // `npx omega test --extended` equivalent to `TEST_EXTENDED_MODE=true npx omega test`.
164
+ if (argv.extended) {
165
+ process.env.TEST_EXTENDED_MODE = 'true';
166
+ }
167
+
168
+ // Framework self-test: when `npx omega test` is run from the @omega.js/backend repo
169
+ // (no firebase.json in cwd), boot the bundled fixture project under
170
+ // src/test/fixtures/firebase-project. Mirrors BXM/UJM *_TEST_BOOT_PROJECT.
171
+ const isSelfTest = this.setupSelfTest();
172
+
173
+ // The AUDIT half of the retired `omega setup` (#675): the target checks
174
+ // run before anything boots, healing what they can and halting on what
175
+ // they cannot. Skipped on the framework self-test — the bundled fixture is
176
+ // not a consumer target to heal, and the checks would rewrite it.
177
+ if (!isSelfTest) {
178
+ this.ui.section('Target checks');
179
+ await runTargetChecks(self);
180
+ }
181
+
182
+ // functions/ is staged output (src/dist pillar): stage fresh so the run
183
+ // reads current src + composed config. The auto-start emulator path stages
184
+ // again inside startEmulators — idempotent and cheap; THIS call covers the
185
+ // existing-emulator path, where nothing else would refresh the tree.
186
+ // A test lane composes its .env for TESTING (#586): the run's own
187
+ // OMEGA_TEST_MODE is set on the emulator child, not on this process, so the
188
+ // environment is named here rather than sniffed.
189
+ this.ensureStaged({ environment: 'testing' });
190
+
191
+ // Get test paths from CLI args (e.g., "bem test admin/" or "bem test general/generate-uuid")
192
+ const testPaths = (argv._ || []).slice(1); // Remove 'test' from args
193
+
194
+ // On self-test with no explicit target, run only the boot smoke suite —
195
+ // the fast inner loop. The FULL suite also passes against the fixture
196
+ // (`npm test -- backend:`): it carries corpus-parity payment config and
197
+ // boot-syncs the canonical rules for exactly that.
198
+ if (isSelfTest && testPaths.length === 0) {
199
+ testPaths.push('backend:boot');
200
+ }
201
+
202
+ // Determine the project directory
203
+ const projectDir = self.firebaseProjectPath;
204
+ const functionsDir = path.join(projectDir, 'dist');
205
+
206
+ // Pre-flight: write the allowlisted env subset to a shared state file
207
+ // (`<projectDir>/.temp/test-mode.json`). The running emulator watches this
208
+ // file and mutates its own `process.env` to match, eliminating the need
209
+ // to coordinate env vars across both terminals. The test command is the
210
+ // authoritative writer — whatever you pass here becomes the live mode
211
+ // within ~50ms.
212
+ //
213
+ // Allowlist lives in src/test/utils/test-mode-file.js (SYNCED_ENV_KEYS).
214
+ // Today: just TEST_EXTENDED_MODE. Add more keys there to make them
215
+ // live-syncable.
216
+ {
217
+ const envSubset = captureSyncedEnv(process.env);
218
+ writeTestMode(projectDir, envSubset);
219
+ const extended = !!process.env.TEST_EXTENDED_MODE;
220
+ this.log(chalk.gray(` Test mode: ${extended ? 'extended (real external APIs)' : 'normal (external APIs skipped)'}`));
221
+ }
222
+
223
+ // N7: a RUNNING emulator's published port map wins — it may have bumped
224
+ // off the firebase.json values (second-brand boot). No live map →
225
+ // firebase.json/classic (and the auto-start path re-resolves after boot).
226
+ let emulatorPorts = loadEmulatorPorts(projectDir);
227
+ const publishedPorts = readPortsFile(projectDir);
228
+ if (publishedPorts) {
229
+ emulatorPorts = { ...emulatorPorts, ...publishedPorts };
230
+ }
231
+
232
+ // Load project configuration
233
+ const projectConfig = this.loadProjectConfig(functionsDir, argv);
234
+ if (!projectConfig) {
235
+ // Config-validation abort must fail the process — a bare return exits 0
236
+ // and reads as a green run to any script chaining on the exit code.
237
+ process.exitCode = 1;
238
+ return;
239
+ }
240
+
241
+ // Opt-in lanes. OFF by default and gated on conditions the default suite
242
+ // must never depend on (credentials, the network, a CLI). A gate that
243
+ // refuses prints ONE line and the run exits clean — a skipped lane is a
244
+ // normal outcome, not a failure
245
+ // ([#212](https://github.com/Omega-JS-Stack/omega/issues/212)).
246
+ if (argv.lane) {
247
+ const gate = this.openLane(argv.lane);
248
+
249
+ if (!gate) {
250
+ return;
251
+ }
252
+
253
+ this.lane = gate;
254
+ testPaths.push(`backend:${gate.laneModule.LANE}`);
255
+ }
256
+
257
+ // Build unified test config object
258
+ // Use hosting URL for all API requests (rewrites to omega_api function)
259
+ const testConfig = {
260
+ ...projectConfig,
261
+ apiUrl: `http://127.0.0.1:${emulatorPorts.hosting}`,
262
+ projectDir,
263
+ testPaths,
264
+ emulatorPorts,
265
+ isFrameworkSelfTest: isSelfTest, // gates the boot/ smoke layer (excluded for consumers)
266
+ };
267
+
268
+ // Adopt the running emulator only when it PROVES it is ours — a listener on
269
+ // the functions port is not evidence of ownership
270
+ // ([#258](https://github.com/Omega-JS-Stack/omega/issues/258)).
271
+ const portInUse = await this.isEmulatorRunning(emulatorPorts);
272
+ const adoption = decideEmulatorAdoption({
273
+ portInUse: portInUse,
274
+ ownsPortsFile: !!publishedPorts && publishedPorts.functions === emulatorPorts.functions,
275
+ runningProjectId: portInUse ? this.readRunningProjectId(emulatorPorts) : null,
276
+ expectedProjectId: projectConfig.cloud?.config?.projectId || null,
277
+ });
278
+
279
+ if (adoption.adopt) {
280
+ this.log(chalk.cyan('Running tests against EXISTING emulator'));
281
+
282
+ // The forwarder needs the RESOLVED port, so it starts here rather than at
283
+ // the gate — and stops in the finally, so a failed run never leaves a
284
+ // `stripe listen` behind holding the terminal.
285
+ const laneSession = await this.startLane(testConfig, emulatorPorts);
286
+
287
+ try {
288
+ await this.runTestsDirectly(this.buildTestCommand({ ...testConfig, laneEnv: laneSession.env }), functionsDir, emulatorPorts);
289
+ } finally {
290
+ laneSession.stop();
291
+ }
292
+ } else {
293
+ if (adoption.reason === 'project-mismatch') {
294
+ this.log(chalk.yellow(` Port ${emulatorPorts.functions} belongs to project "${adoption.runningProjectId}", not "${projectConfig.cloud?.config?.projectId}" — booting this project's own emulator on free ports.`));
295
+ } else if (adoption.reason === 'unidentified') {
296
+ this.log(chalk.yellow(` Port ${emulatorPorts.functions} is in use by a listener that does not identify itself — booting this project's own emulator on free ports.`));
297
+ }
298
+
299
+ this.log(chalk.cyan('Starting emulator and running tests...'));
300
+ // The command is built INSIDE runEmulatorTests, after boot — allocation
301
+ // may bump ports, and a pre-built command would bake the stale ones.
302
+ await this.runEmulatorTests(testConfig, functionsDir);
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Open an opt-in lane, or say why it stays shut
308
+ *
309
+ * The ONE decision point for every lane: the gate answers from facts already
310
+ * on this machine (a credential, a CLI, the environment), so a refusal costs
311
+ * nothing and reads as a skip. Nothing is started here — the forwarding needs
312
+ * a port the emulator has not chosen yet.
313
+ *
314
+ * @param {string} name - The `--lane=` value
315
+ * @returns {object|null} The opened lane, or null when it stays shut
316
+ */
317
+ openLane(name) {
318
+ let laneModule;
319
+
320
+ try {
321
+ laneModule = require(path.join(__dirname, 'test-lanes', `${name}.js`));
322
+ } catch (error) {
323
+ this.logError(`Unknown test lane: ${name}`);
324
+ process.exitCode = 1;
325
+ return null;
326
+ }
327
+
328
+ const decision = laneModule.resolveGate({ env: laneModule.env, hasStripeCli: !!laneModule.findStripeCli() });
329
+
330
+ if (!decision.ok) {
331
+ this.log(laneModule.skipLine(decision.reason));
332
+ return null;
333
+ }
334
+
335
+ this.log(chalk.cyan(` Lane --lane=${name}: ENABLED (real external services)`));
336
+
337
+ return { laneModule, decision };
338
+ }
339
+
340
+ /**
341
+ * Bring an opened lane up against the RESOLVED emulator ports
342
+ *
343
+ * Creates the Stripe fixtures the brand's catalogue needs (idempotently — a
344
+ * rerun reuses them) and starts the forwarder, handing back the env the runner
345
+ * child needs and the stop the caller owes it. No lane opened = an inert
346
+ * session, so both run paths call this unconditionally.
347
+ *
348
+ * @param {object} testConfig - The resolved test config
349
+ * @param {object} emulatorPorts - The port map the emulator actually took
350
+ * @returns {Promise<{ env: object, stop: function }>}
351
+ */
352
+ async startLane(testConfig, emulatorPorts) {
353
+ const inert = { env: {}, stop: () => {} };
354
+
355
+ if (!this.lane) {
356
+ return inert;
357
+ }
358
+
359
+ const { laneModule, decision } = this.lane;
360
+ const stripePath = laneModule.findStripeCli();
361
+
362
+ const { fixtures, unsupported } = laneModule.planFixtures(
363
+ testConfig.payment?.products,
364
+ testConfig.payment?.currency,
365
+ );
366
+
367
+ for (const item of unsupported) {
368
+ this.logWarning(` Lane ${laneModule.LANE}: ${item.productId} has no Stripe fixture — ${item.reason}`);
369
+ }
370
+
371
+ const stripe = require('stripe')(decision.key);
372
+
373
+ await laneModule.ensureFixtures({
374
+ stripe: stripe,
375
+ fixtures: fixtures,
376
+ log: (line) => this.log(chalk.gray(` [${laneModule.LANE}] ${line}`)),
377
+ });
378
+
379
+ const { stop } = await laneModule.startForwarding({
380
+ apiKey: decision.key,
381
+ forwardUrl: laneModule.forwardUrl({ hostingPort: emulatorPorts.hosting, webhookKey: testConfig.webhookKey }),
382
+ log: (line) => this.log(chalk.gray(` [Stripe] ${line}`)),
383
+ });
384
+
385
+ this.log(chalk.gray(` [${laneModule.LANE}] forwarding real test-mode webhooks to localhost:${emulatorPorts.hosting}\n`));
386
+
387
+ return {
388
+ env: {
389
+ [laneModule.LANE_ENV]: laneModule.LANE,
390
+ STRIPE_CLI_PATH: stripePath,
391
+ },
392
+ stop: stop,
393
+ };
394
+ }
395
+
396
+ /**
397
+ * Load project configuration from config/omega.json5 and .env
398
+ */
399
+ loadProjectConfig(functionsDir, argv) {
400
+ const { hasOmegaConfig, loadConfig, loadEnv, resolvedBrandHost } = require('../../vendor/config/index.js');
401
+
402
+ // Load the .env cascade first so env vars are available
403
+ loadEnv(functionsDir);
404
+
405
+ // Load config/omega.json5 (resolved — the loader walks up to the brand
406
+ // layer from the functions dir in a brand monorepo)
407
+ if (!hasOmegaConfig(functionsDir)) {
408
+ this.logError('Error: Missing config/omega.json5');
409
+ return null;
410
+ }
411
+
412
+ let config;
413
+ try {
414
+ config = loadConfig(functionsDir, 'backend').config;
415
+ } catch (error) {
416
+ this.logError(`Error: Could not load config/omega.json5: ${error.message}`);
417
+ return null;
418
+ }
419
+
420
+ // Derive computed values (not in config file)
421
+ const adminKey = argv.key || process.env.OMEGA_ADMIN_KEY;
422
+ const webhookKey = argv.webhookKey || process.env.OMEGA_WEBHOOK_KEY;
423
+ // The persona domain is the brand's HOST — one derivation, shared with the
424
+ // seeder and the dev palette
425
+ // ([#708](https://github.com/Omega-JS-Stack/omega/issues/708)).
426
+ const domain = resolvedBrandHost(config);
427
+
428
+ // Validate required configuration
429
+ if (!config.cloud?.config?.projectId) {
430
+ this.logError('Error: Missing cloud.config.projectId in config/omega.json5');
431
+ return null;
432
+ }
433
+
434
+ if (!adminKey) {
435
+ this.logError('Error: Missing admin key');
436
+ this.log(chalk.gray(' Set OMEGA_ADMIN_KEY in your .env file or pass --key flag'));
437
+ return null;
438
+ }
439
+
440
+ if (!webhookKey) {
441
+ this.logError('Error: Missing webhook key');
442
+ this.log(chalk.gray(' Set OMEGA_WEBHOOK_KEY in your .env file or pass --webhook-key flag'));
443
+ return null;
444
+ }
445
+
446
+ if (!config.brand?.id) {
447
+ this.logError('Error: Missing brand.id in config/omega.json5');
448
+ return null;
449
+ }
450
+
451
+ if (!domain) {
452
+ this.logError('Error: Missing brand.url in config/omega.json5 (personas are seeded on the brand host)');
453
+ return null;
454
+ }
455
+
456
+ // Pass entire config + computed values not in config file
457
+ return {
458
+ ...config,
459
+ adminKey,
460
+ webhookKey,
461
+ domain,
462
+ };
463
+ }
464
+
465
+ /**
466
+ * Build the test command with environment variables
467
+ */
468
+ /**
469
+ * Framework self-test detection + fixture wiring.
470
+ *
471
+ * When `npx omega test` runs from a directory that is NOT a Firebase project
472
+ * (no firebase.json) AND is the @omega.js/backend repo (or OMEGA_TEST_BOOT_PROJECT
473
+ * is set), point the run at the bundled fixture project and link the local
474
+ * framework + firebase deps into it so the emulator's function workers resolve
475
+ * them. This is @omega.js/backend's equivalent of BXM's OMEGA_TEST_BOOT_PROJECT / UJM's
476
+ * UJ_TEST_BOOT_PROJECT. Returns true if self-test wiring was applied.
477
+ */
478
+ setupSelfTest() {
479
+ const self = this.main;
480
+
481
+ // Normal consumer run — cwd is already a Firebase project. Nothing to do.
482
+ if (jetpack.exists(path.join(self.firebaseProjectPath, 'firebase.json'))) {
483
+ return false;
484
+ }
485
+
486
+ // Self-test if OMEGA_TEST_BOOT_PROJECT is set, or cwd is the @omega.js/backend repo.
487
+ let isSelfTest = !!process.env.OMEGA_TEST_BOOT_PROJECT;
488
+ if (!isSelfTest) {
489
+ try {
490
+ isSelfTest = require(path.join(process.cwd(), 'package.json')).name === '@omega.js/backend';
491
+ } catch (_) { /* no package.json — not a self-test */ }
492
+ }
493
+ if (!isSelfTest) {
494
+ return false;
495
+ }
496
+
497
+ const fixture = process.env.OMEGA_TEST_BOOT_PROJECT
498
+ ? path.resolve(process.env.OMEGA_TEST_BOOT_PROJECT)
499
+ : path.resolve(__dirname, '..', '..', 'test', 'fixtures', 'firebase-project');
500
+
501
+ process.env.OMEGA_TEST_BOOT_PROJECT = fixture;
502
+ self.firebaseProjectPath = fixture;
503
+
504
+ // The test HTTP client authenticates with the fixture's admin keys (the
505
+ // server reads the same keys from config/omega.json5). Inject them from
506
+ // the fixture config so loadProjectConfig finds them — no committed .env
507
+ // needed (single source = the fixture config). The namespace rides the
508
+ // same mechanism: the uuid route's v5 default reads OMEGA_NAMESPACE in
509
+ // the emulated functions, which inherit this process env.
510
+ try {
511
+ const cfg = require('../../vendor/config/index.js').loadConfig(fixture, 'backend').config;
512
+ process.env.OMEGA_ADMIN_KEY = process.env.OMEGA_ADMIN_KEY || cfg.omega?.key;
513
+ process.env.OMEGA_WEBHOOK_KEY = process.env.OMEGA_WEBHOOK_KEY || cfg.omega?.webhookKey;
514
+ process.env.OMEGA_NAMESPACE = process.env.OMEGA_NAMESPACE || cfg.omega?.namespace;
515
+ } catch (_) { /* fixture config unreadable — let the normal key check report it */ }
516
+
517
+ // Anonymous HMAC unsubscribe tests sign links with this shared secret; the
518
+ // emulated functions inherit it from this process env (same mechanism as the
519
+ // webhook key above). Test-only value — production sets its own env var.
520
+ process.env.UNSUBSCRIBE_HMAC_KEY = process.env.UNSUBSCRIBE_HMAC_KEY || '_test-unsubscribe-hmac-key';
521
+
522
+ this.ensureFixtureServiceAccount(fixture);
523
+ this.ensureFixtureEnv(fixture);
524
+ this.ensureFixtureRules(fixture);
525
+ this.linkFixtureDeps(fixture);
526
+ this.log(chalk.cyan(` Self-test: booting bundled fixture project (${fixture})`));
527
+ return true;
528
+ }
529
+
530
+ /**
531
+ * Write a throwaway service-account.json into the fixture so firebase-admin's
532
+ * `cert()` can parse it. @omega.js/backend's manager uses the cert path when
533
+ * GOOGLE_APPLICATION_CREDENTIALS is unset (as in the functions emulator). The
534
+ * key is a freshly-generated RSA key — emulator-only, never authenticates
535
+ * against Google (the project is a `demo-` project), so it is generated at
536
+ * runtime and gitignored, never committed. Written to the TARGET ROOT (the
537
+ * authored home under the src/dist pillar) — the stage step carries it into
538
+ * functions/.
539
+ */
540
+ ensureFixtureServiceAccount(fixture) {
541
+ const crypto = require('crypto');
542
+ const saPath = path.join(fixture, 'service-account.json');
543
+ const projectId = 'demo-omega-backend';
544
+ const { privateKey } = crypto.generateKeyPairSync('rsa', {
545
+ modulusLength: 2048,
546
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
547
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
548
+ });
549
+ const serviceAccount = {
550
+ type: 'service_account',
551
+ project_id: projectId,
552
+ private_key_id: '0'.repeat(40),
553
+ private_key: privateKey,
554
+ client_email: `fixture@${projectId}.iam.gserviceaccount.com`,
555
+ client_id: '0'.repeat(21),
556
+ auth_uri: 'https://accounts.google.com/o/oauth2/auth',
557
+ token_uri: 'https://oauth2.googleapis.com/token',
558
+ auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
559
+ client_x509_cert_url: `https://www.googleapis.com/robot/v1/metadata/x509/fixture%40${projectId}.iam.gserviceaccount.com`,
560
+ };
561
+ try {
562
+ fs.writeFileSync(saPath, `${JSON.stringify(serviceAccount, null, 2)}\n`);
563
+ } catch (e) {
564
+ this.logWarning(`Could not write fixture service-account.json: ${e.message}`);
565
+ }
566
+ }
567
+
568
+ /**
569
+ * Seed the fixture's `.env` with every key the env schema marks REQUIRED
570
+ * ([#581](https://github.com/Omega-JS-Stack/omega/issues/581)). The fixture
571
+ * carries a consumer `config/omega.json5`, so it IS a brand's backend to the
572
+ * boot guard — and firebase-tools analyzes function definitions in its own
573
+ * child process, which never saw the values injected into this one above, so
574
+ * `Manager.init()` threw before a single function loaded.
575
+ *
576
+ * Values come from the SAME resolution the injection above does (the fixture
577
+ * config's `omega.*` keys, the shared unsubscribe secret), so the server and
578
+ * the test client authenticate with one value; a key the schema marks
579
+ * required LATER gets an obvious dummy, so the fixture can never fall behind
580
+ * the schema again. Existing lines are never touched — a fixture edit
581
+ * survives the run that reads it. Written to the TARGET ROOT (the authored
582
+ * home under the src/dist pillar); the stage step carries it into functions/.
583
+ * Emulator-only values, gitignored like the service account, never committed.
584
+ */
585
+ ensureFixtureEnv(fixture) {
586
+ const { requiredEnvKeys } = require('../../vendor/config/index.js');
587
+ const envPath = path.join(fixture, '.env');
588
+ const existing = jetpack.exists(envPath) ? jetpack.read(envPath) : '';
589
+ const present = util.parseEnv(existing);
590
+
591
+ const lines = requiredEnvKeys('backend')
592
+ .filter((name) => !present[name])
593
+ .map((name) => `${name}="${process.env[name] || `fixture-${name.toLowerCase().replace(/_/g, '-')}`}"`);
594
+
595
+ if (lines.length === 0) {
596
+ return;
597
+ }
598
+
599
+ const header = existing ? '' : '# @omega.js/backend self-test fixture — emulator-only values, seeded per run.\n';
600
+
601
+ try {
602
+ jetpack.write(envPath, `${header}${existing}${existing && !existing.endsWith('\n') ? '\n' : ''}${lines.join('\n')}\n`);
603
+ } catch (e) {
604
+ this.logWarning(`Could not write fixture .env: ${e.message}`);
605
+ }
606
+ }
607
+
608
+ /**
609
+ * Seed the fixture's BRAND rules source — the same seed the scaffold ships
610
+ * to a consumer. The framework half is no longer
611
+ * copied anywhere: the stage below compiles it in
612
+ * ([#255](https://github.com/Omega-JS-Stack/omega/issues/255)), so the rules
613
+ * suite exercises the real compiled artifact, exactly what a consumer
614
+ * deploys. Runtime-derived and gitignored like the service account, and
615
+ * never clobbered — a fixture edit survives the run that reads it.
616
+ */
617
+ ensureFixtureRules(fixture) {
618
+ const { ensureBrandRulesSource } = require('../utils/compile-rules');
619
+ ensureBrandRulesSource({ projectDir: fixture });
620
+ }
621
+
622
+ /**
623
+ * Symlink the local framework + firebase deps into the fixture's TARGET-ROOT
624
+ * node_modules so the emulator's function workers can resolve them — Node
625
+ * resolution walks up from the staged functions/ tree (which carries no
626
+ * node_modules of its own under the src/dist pillar). Mirrors what
627
+ * `npx omega install dev` does in a real consumer, but for the fixture and
628
+ * without an npm install (firebase-admin/firebase-functions come from
629
+ * @omega.js/backend's own node_modules; @omega.js/backend points at the repo root).
630
+ */
631
+ linkFixtureDeps(fixture) {
632
+ const fnNodeModules = path.join(fixture, 'node_modules');
633
+ jetpack.dir(fnNodeModules);
634
+
635
+ const frameworkRoot = path.resolve(__dirname, '..', '..', '..'); // src/cli/commands -> repo root
636
+ const links = {
637
+ '@omega.js/backend': frameworkRoot,
638
+ 'firebase-admin': path.join(frameworkRoot, 'node_modules', 'firebase-admin'),
639
+ 'firebase-functions': path.join(frameworkRoot, 'node_modules', 'firebase-functions'),
640
+ };
641
+
642
+ for (const [name, target] of Object.entries(links)) {
643
+ const linkPath = path.join(fnNodeModules, name);
644
+ try { fs.rmSync(linkPath, { recursive: true, force: true }); } catch (_) { /* nothing to remove */ }
645
+ try {
646
+ // The link's PARENT, not just node_modules — a scoped name like
647
+ // @omega.js/backend needs its node_modules/@omega.js dir to exist first
648
+ fs.mkdirSync(path.dirname(linkPath), { recursive: true });
649
+ fs.symlinkSync(target, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
650
+ } catch (e) {
651
+ this.logWarning(`Could not link ${name} into fixture: ${e.message}`);
652
+ }
653
+ }
654
+ }
655
+
656
+ buildTestCommand(testConfig) {
657
+ const testScriptPath = path.join(__dirname, '..', '..', 'test', 'run-tests.js');
658
+
659
+ // The RESOLVED port map, minus the TLS front. The runner loads route
660
+ // handlers IN-PROCESS, so its Manager's URL getters answer from
661
+ // OMEGA_*_PORT the same way a function worker's do — unset, they fell back
662
+ // to the classic 5002, which under `omega emulator`'s HTTPS default is the
663
+ // mkcert proxy, not hosting: the test provider's auto-webhook followed the
664
+ // proxy's 307 to https and died on a cert this process has no CA for
665
+ // ([#291](https://github.com/Omega-JS-Stack/omega/issues/291)). `https` is
666
+ // deliberately dropped — the runner is a plain-http client of the stack
667
+ // (its own apiUrl already is), so it gets hosting's internal port.
668
+ const { https: _tlsFront, ...plainHttpPorts } = testConfig.emulatorPorts;
669
+
670
+ // Pass entire config as base64-encoded JSON to avoid shell escaping issues
671
+ const testEnv = {
672
+ ...portsToEnv(plainHttpPorts),
673
+ OMEGA_TEST_CONFIG: Buffer.from(JSON.stringify(testConfig)).toString('base64'),
674
+ FIRESTORE_EMULATOR_HOST: `127.0.0.1:${testConfig.emulatorPorts.firestore}`,
675
+ FIREBASE_AUTH_EMULATOR_HOST: `127.0.0.1:${testConfig.emulatorPorts.auth}`,
676
+ // The project the emulator booted with — resolved from the same config
677
+ // the emulator command boots on (loadProjectConfig hard-fails without it).
678
+ // The runner's admin app and the wipe's auth bulk-clear both read it, and
679
+ // the wipe's URL names a project the emulator answers 200 for whether it
680
+ // knows it or not: without this the wipe cleared a project nothing in the
681
+ // run ever touched and reported success
682
+ // ([#292](https://github.com/Omega-JS-Stack/omega/issues/292)).
683
+ GCLOUD_PROJECT: testConfig.cloud.config.projectId,
684
+ // GCLOUD_PROJECT alone makes the child's init-time environment read as
685
+ // production (nothing else marks it yet: runner.js stamps its own
686
+ // OMEGA_TEST_MODE only later, inside run()), which wakes the
687
+ // firebase-functions logger compat shim and turns every test line into
688
+ // JSON. Say what this process IS before Manager.init() looks.
689
+ OMEGA_TEST_MODE: 'true',
690
+ // An opt-in lane's own environment: the lane name the runner's discovery
691
+ // gates on, plus whatever the lane brought up (the forwarder's signing
692
+ // secret). Empty on every default run.
693
+ ...(testConfig.laneEnv || {}),
694
+ };
695
+
696
+ const envString = Object.entries(testEnv)
697
+ .map(([key, value]) => `${key}='${value}'`)
698
+ .join(' ');
699
+
700
+ return `${envString} node "${testScriptPath}"`;
701
+ }
702
+
703
+ /**
704
+ * Check if emulator is already running
705
+ * @param {object} emulatorPorts - The port map this run expects.
706
+ * @returns {Promise<boolean>}
707
+ */
708
+ isEmulatorRunning(emulatorPorts) {
709
+ // Check if functions emulator port is in use
710
+ // If it is, assume emulator is running
711
+ return this.isPortInUse(emulatorPorts.functions);
712
+ }
713
+
714
+ /**
715
+ * The project id the emulator listening on our ports belongs to, read from
716
+ * the hub locator firebase-tools publishes for a running stack. Anything
717
+ * unproven (no locator, a dead pid, two live claims) comes back null, which
718
+ * the decision above treats as "unidentified", never as "ours".
719
+ * @param {object} emulatorPorts - The resolved port map.
720
+ * @returns {string|null} The running emulator's project id.
721
+ */
722
+ readRunningProjectId(emulatorPorts) {
723
+ return readHubLocatorProjectId(emulatorPorts?.hub || HUB_PORT);
724
+ }
725
+
726
+ /**
727
+ * Signal the running emulator process to roll emulator.log.
728
+ *
729
+ * Mechanism: write a sentinel file at emulator.log.reset. The emulator command
730
+ * (src/cli/commands/emulator.js) polls for it and, on detection, closes its
731
+ * current write stream and reopens with flags: 'w' (truncating cleanly from its
732
+ * own perspective — avoids the sparse-file problem caused by external truncation).
733
+ *
734
+ * Waits up to 2s for the sentinel to be consumed. If it's still there after 2s
735
+ * the emulator isn't watching (probably running an older @omega.js/backend or started outside
736
+ * `npx omega emulator`); we delete the sentinel and proceed — tests still run, the
737
+ * log just won't be reset for this run.
738
+ */
739
+ async requestEmulatorLogReset(projectDir) {
740
+ const sentinelPath = this.getTempPath('emulator.log.reset');
741
+
742
+ try {
743
+ fs.writeFileSync(sentinelPath, '');
744
+ } catch (e) {
745
+ return; // Can't write — skip, not fatal
746
+ }
747
+
748
+ // Poll for the emulator to consume the sentinel (it deletes the file when done)
749
+ const maxWaitMs = 2000;
750
+ const pollIntervalMs = 100;
751
+ const start = Date.now();
752
+
753
+ while (Date.now() - start < maxWaitMs) {
754
+ if (!fs.existsSync(sentinelPath)) {
755
+ return; // Emulator picked it up and rolled the log
756
+ }
757
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
758
+ }
759
+
760
+ // Timed out — emulator didn't see the sentinel. Clean up so we don't leave it behind.
761
+ try { fs.unlinkSync(sentinelPath); } catch (e) { /* ok */ }
762
+ }
763
+
764
+ /**
765
+ * Run tests directly (emulator already running)
766
+ */
767
+ async runTestsDirectly(testCommand, functionsDir, emulatorPorts) {
768
+ const projectDir = this.main.firebaseProjectPath;
769
+
770
+ // Ask the running emulator process to roll emulator.log so this test run gets a
771
+ // clean slate. We touch a sentinel file the emulator polls for (every ~500ms) and
772
+ // wait briefly for it to be consumed. If the emulator isn't watching (older @omega.js/backend
773
+ // version, or not started via `npx omega emulator`), we time out silently — the log
774
+ // just won't be fresh, tests still run normally.
775
+ await this.requestEmulatorLogReset(projectDir);
776
+
777
+ this.log(chalk.gray(` Hosting: http://127.0.0.1:${emulatorPorts.hosting}`));
778
+ this.log(chalk.gray(` Firestore: 127.0.0.1:${emulatorPorts.firestore}`));
779
+ this.log(chalk.gray(` Auth: 127.0.0.1:${emulatorPorts.auth}`));
780
+ this.log(chalk.gray(` UI: http://127.0.0.1:${emulatorPorts.ui}`));
781
+
782
+ // Set up log file in the project's functions/ directory (alongside firebase-tools logs)
783
+ const logPath = this.getLogsPath('test.log');
784
+ const logStream = fs.createWriteStream(logPath, { flags: 'w' });
785
+ const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
786
+
787
+ this.log(chalk.gray(` Logs saving to: ${logPath}\n`));
788
+
789
+ // The runner's OWN exit code, kept: a no-match answers with a code of its
790
+ // own inside a brand-root fan-out (#814), and collapsing every non-zero to
791
+ // 1 here would hide it. powertools registers its close listener before it
792
+ // hands the child over, so this one has run by the time the catch does.
793
+ let childExit = 0;
794
+
795
+ try {
796
+ await powertools.execute(testCommand, {
797
+ log: false,
798
+ cwd: functionsDir,
799
+ config: {
800
+ stdio: ['inherit', 'pipe', 'pipe'],
801
+ env: { ...process.env, FORCE_COLOR: '1' },
802
+ },
803
+ }, (child) => {
804
+ // Tee stdout to both console and log file (strip ANSI codes for clean log)
805
+ child.stdout.on('data', (data) => {
806
+ process.stdout.write(data);
807
+ logStream.write(stripAnsi(data.toString()));
808
+ });
809
+
810
+ // Tee stderr to both console and log file (strip ANSI codes for clean log)
811
+ child.stderr.on('data', (data) => {
812
+ process.stderr.write(data);
813
+ logStream.write(stripAnsi(data.toString()));
814
+ });
815
+
816
+ // Clean up log stream when child exits
817
+ child.on('close', (code) => {
818
+ childExit = code ?? 1;
819
+ logStream.end();
820
+ });
821
+ });
822
+ } catch (error) {
823
+ process.exit(childExit || 1);
824
+ }
825
+ }
826
+
827
+ /**
828
+ * Run tests with Firebase emulator (starts emulator, runs tests, shuts down).
829
+ *
830
+ * Two real child processes are used so emulator output and test-runner output
831
+ * land in separate log files:
832
+ * - `emulator.log` — `firebase emulators:start` stdout/stderr (managed by EmulatorCommand)
833
+ * - `test.log` — the test-runner subprocess stdout/stderr (managed here)
834
+ */
835
+ async runEmulatorTests(testConfig, functionsDir) {
836
+ try {
837
+ await powertools.execute('java -version', { log: false });
838
+ } catch (e) {
839
+ this.logError(`Java is required to run tests (Firebase emulators depend on it).`);
840
+ this.logError(`Install with: ${javaInstallHint()}`);
841
+ process.exit(1);
842
+ }
843
+
844
+ this.log(chalk.gray(' Starting Firebase emulator...\n'));
845
+
846
+ const emulatorCmd = new EmulatorCommand(this.main);
847
+ let started;
848
+
849
+ try {
850
+ started = await emulatorCmd.startEmulators({ environment: 'testing' });
851
+ } catch (error) {
852
+ this.logError(`Emulator error: ${error.message || error}`);
853
+ process.exit(1);
854
+ }
855
+
856
+ const { shutdown, exitPromise, emulatorPorts } = started;
857
+
858
+ // The forwarder needs the RESOLVED port too, so it starts after the boot
859
+ const laneSession = await this.startLane(testConfig, emulatorPorts);
860
+
861
+ // Build the test command from the RESOLVED ports — allocation may have
862
+ // bumped them off the firebase.json values testConfig was seeded with.
863
+ const testCommand = this.buildTestCommand({
864
+ ...testConfig,
865
+ apiUrl: `http://127.0.0.1:${emulatorPorts.hosting}`,
866
+ emulatorPorts,
867
+ laneEnv: laneSession.env,
868
+ });
869
+
870
+ // ONE handler, three signals: this run OWNS the stack it just booted, so
871
+ // every way it is asked to stop takes the same teardown. Wired to SIGINT
872
+ // alone, a supervisor's SIGTERM or a closed terminal's SIGHUP took Node's
873
+ // default action and orphaned the whole firebase tree onto PID 1 — the
874
+ // [#629](https://github.com/Omega-JS-Stack/omega/issues/629) defect on this
875
+ // sibling path ([#722](https://github.com/Omega-JS-Stack/omega/issues/722)).
876
+ //
877
+ // The signal is DIRECTED at this pid, so it reaches the CLI and nothing
878
+ // else: the test runner spawned below is this run's child, not its
879
+ // co-signee, and a teardown that only stops the emulator leaves the suites
880
+ // running orphaned to PID 1. It is therefore signalled by hand, and the
881
+ // handler is reentrancy-guarded — a second signal arriving mid-teardown
882
+ // (SIGINT then SIGTERM is exactly how a supervisor escalates) would
883
+ // otherwise start the whole thing again underneath the first.
884
+ let testChild = null;
885
+ let stopping = false;
886
+
887
+ const onStopSignal = async () => {
888
+ if (stopping) {
889
+ return;
890
+ }
891
+
892
+ stopping = true;
893
+
894
+ this.log(chalk.gray('\n Shutting down emulator...'));
895
+
896
+ if (testChild && testChild.exitCode === null && testChild.signalCode === null) {
897
+ try { testChild.kill('SIGTERM'); } catch (e) { /* already gone */ }
898
+ }
899
+
900
+ await shutdown();
901
+ process.exit(130);
902
+ };
903
+ STOP_SIGNALS.forEach((signal) => process.on(signal, onStopSignal));
904
+
905
+ // Print the same connection summary the existing-emulator path shows
906
+ this.log('');
907
+ this.log(chalk.gray(` Hosting: http://127.0.0.1:${emulatorPorts.hosting}`));
908
+ this.log(chalk.gray(` Firestore: 127.0.0.1:${emulatorPorts.firestore}`));
909
+ this.log(chalk.gray(` Auth: 127.0.0.1:${emulatorPorts.auth}`));
910
+ this.log(chalk.gray(` UI: http://127.0.0.1:${emulatorPorts.ui}`));
911
+
912
+ // Spawn the test runner as its own child so its stdout/stderr can be teed to test.log
913
+ const logPath = this.getLogsPath('test.log');
914
+ const logStream = fs.createWriteStream(logPath, { flags: 'w' });
915
+ const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
916
+
917
+ this.log(chalk.gray(` Logs saving to: ${logPath}\n`));
918
+
919
+ let testExitCode = 0;
920
+
921
+ try {
922
+ testChild = spawnShell(testCommand, {
923
+ cwd: functionsDir,
924
+ env: { ...process.env, FORCE_COLOR: '1' },
925
+ stdio: ['inherit', 'pipe', 'pipe'],
926
+ });
927
+
928
+ testChild.stdout.on('data', (data) => {
929
+ process.stdout.write(data);
930
+ if (!logStream.destroyed) logStream.write(stripAnsi(data.toString()));
931
+ });
932
+
933
+ testChild.stderr.on('data', (data) => {
934
+ process.stderr.write(data);
935
+ if (!logStream.destroyed) logStream.write(stripAnsi(data.toString()));
936
+ });
937
+
938
+ testExitCode = await new Promise((resolve) => {
939
+ testChild.on('close', (code) => {
940
+ if (!logStream.destroyed) logStream.end();
941
+ resolve(code ?? 1);
942
+ });
943
+ });
944
+ } catch (error) {
945
+ this.logError(`Test runner error: ${error.message || error}`);
946
+ testExitCode = 1;
947
+ } finally {
948
+ laneSession.stop();
949
+
950
+ // A stop signal owns the teardown AND the exit from the moment it
951
+ // arrives: this path is only reached at all because the handler killed
952
+ // the child this run was waiting on. Tearing down again here would
953
+ // signal the stack out from under it — and dropping the handlers would
954
+ // hand the next signal back to Node's default action mid-teardown. So
955
+ // the process is left to the handler's own exit.
956
+ if (stopping) {
957
+ await new Promise(() => {});
958
+ }
959
+
960
+ STOP_SIGNALS.forEach((signal) => process.removeListener(signal, onStopSignal));
961
+ await shutdown();
962
+ await exitPromise;
963
+ }
964
+
965
+ process.exit(testExitCode);
966
+ }
967
+ }
968
+
969
+ // Static, alongside Middleware's precedent — the adopt-or-bump decision is
970
+ // pure, so tests exercise it directly instead of through a booted emulator, and
971
+ // the identity read takes a tmp dir so real locator files can stand in for a
972
+ // running hub.
973
+ TestCommand.decideEmulatorAdoption = decideEmulatorAdoption;
974
+ TestCommand.readHubLocatorProjectId = readHubLocatorProjectId;
975
+
976
+ module.exports = TestCommand;