@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,788 @@
1
+ /**
2
+ * Test: a stack stop takes the whole family with it
3
+ * ([#304](https://github.com/Omega-JS-Stack/omega/issues/304)).
4
+ *
5
+ * firebase-tools starts each java emulator in its OWN process group, so the
6
+ * group signal shutdown() sends reaches `sh`/firebase and nothing else. When
7
+ * the graceful stop does not finish inside the grace window the escalation
8
+ * SIGKILLs the parent, the jars reparent to PID 1, and they hold the classic
9
+ * ports until somebody hand-kills them — four parentless firestore jars after
10
+ * one afternoon of normal restarts. The pid record written while the tree was
11
+ * still attached is the only surviving link, so the stop path signals it BY PID.
12
+ *
13
+ * Run: npx omega test backend:cli/emulator-shutdown
14
+ *
15
+ * The processes below are REAL and detached (their own process group, exactly
16
+ * like the jars) — nothing here is a stand-in for a process. Only their command
17
+ * lines are fixtures, copied from `ps -o command=` against a booted sandbox
18
+ * emulator, because a real firestore jar takes seconds to boot and proves
19
+ * nothing extra about the signal.
20
+ *
21
+ * The last two cases run the COMMAND, not one of its methods: the stop path can
22
+ * be correct and still never be reached, which is how the reaper call and the
23
+ * exit path's join could both be deleted with every other case here green. They
24
+ * boot against a `firebase` on PATH that only decides the two things the boot
25
+ * path reads — whether the ready marker arrives, and when the child exits — so
26
+ * the spawn, the process group, the pid record and the ports stay real.
27
+ */
28
+ const os = require('os');
29
+ const net = require('net');
30
+ const path = require('path');
31
+ const { spawn } = require('child_process');
32
+ const jetpack = require('fs-jetpack');
33
+ const powertools = require('node-powertools');
34
+
35
+ const EmulatorCommand = require('../../dist/cli/commands/emulator.js');
36
+ const WatchCommand = require('../../dist/cli/commands/watch.js');
37
+ const defineCases = require('../../dist/vendor/devkit/test/define-cases.js');
38
+
39
+ const { isStoppableEmulatorProcess, listListeningPids, PORT_LOOKUP_TIMEOUT_MS } = EmulatorCommand;
40
+
41
+ const OURS = 'demo-sandbox-brand';
42
+
43
+ // What the recorded pids actually are, from a booted sandbox emulator.
44
+ const FIRESTORE_JAR = 'java -jar cloud-firestore-emulator-v1.21.0.jar --host 127.0.0.1 --port 8080 --project_id demo-sandbox-brand';
45
+ const PUBSUB_JAR = 'java -jar cloud-pubsub-emulator-0.8.34-all.jar --host=127.0.0.1 --port=8085';
46
+ const NEIGHBOUR_JAR = FIRESTORE_JAR.replace(OURS, 'demo-other-brand');
47
+ // The reload watcher, from the same ps: a nodemon the CLI process spawns, not
48
+ // firebase-tools — so the record's walk down from the emulator child never
49
+ // reaches it ([#629](https://github.com/Omega-JS-Stack/omega/issues/629)).
50
+ const NODEMON_WATCHER = 'nodemon --on-change-only --delay 1 --watch /omega/packages/backend/src --ext js,json --exec node-e-writes-.temp/emulator.log.reset';
51
+
52
+ // Stand-ins for `firebase emulators:start`, on PATH as `firebase`. A real
53
+ // firebase boot needs a real project and half a minute; what the boot path owes
54
+ // its caller is decided by two things only — whether the ready marker ever
55
+ // arrives, and when the child exits.
56
+ const FIREBASE_READY_THEN_EXITS = '#!/bin/sh\necho "All emulators ready!"\nsleep 0.5\n';
57
+ const FIREBASE_DIES_BEFORE_READY = '#!/bin/sh\necho "Error: could not start emulators" >&2\nexit 1\n';
58
+
59
+ /**
60
+ * A real, detached, long-lived process whose command line reads like `marker`.
61
+ * Detached is the point: it sits in its own process group, so the group signal
62
+ * the stop path sends its firebase child can never reach it.
63
+ */
64
+ function spawnDetached(marker) {
65
+ const child = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000)', ...marker.split(' ')], {
66
+ detached: true,
67
+ stdio: 'ignore',
68
+ });
69
+
70
+ child.unref();
71
+
72
+ return child.pid;
73
+ }
74
+
75
+ /**
76
+ * A port nothing is listening on. The suites run against a LIVE emulator, so
77
+ * the classic numbers are held by the stack running this very test — the stop
78
+ * path's port check would read that as a leak.
79
+ */
80
+ async function freePort() {
81
+ const server = net.createServer();
82
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
83
+ const port = server.address().port;
84
+ await new Promise((resolve) => server.close(resolve));
85
+
86
+ return port;
87
+ }
88
+
89
+ /**
90
+ * Did this child process exit inside the window? A signalled child is a ZOMBIE
91
+ * until its parent reaps it, and a zombie still answers `kill(pid, 0)` — the
92
+ * exit event is the honest read.
93
+ */
94
+ function exitedWithin(child, timeoutMs) {
95
+ if (child.exitCode !== null || child.signalCode !== null) {
96
+ return Promise.resolve(true);
97
+ }
98
+
99
+ return new Promise((resolve) => {
100
+ const timer = setTimeout(() => resolve(false), timeoutMs);
101
+ child.once('exit', () => {
102
+ clearTimeout(timer);
103
+ resolve(true);
104
+ });
105
+ });
106
+ }
107
+
108
+ function isAlive(pid) {
109
+ try {
110
+ process.kill(pid, 0);
111
+ return true;
112
+ } catch (e) {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ function stopAll(pids) {
118
+ for (const pid of pids) {
119
+ try { process.kill(pid, 'SIGKILL'); } catch (e) { /* already gone */ }
120
+ }
121
+ }
122
+
123
+ /**
124
+ * An emulator command pointed at a throwaway project dir, with a pid record
125
+ * already written — the state a stop path finds after boot recorded the stack.
126
+ */
127
+ function commandWithRecord(pids) {
128
+ const projectDir = path.join(os.tmpdir(), `omega-shutdown-${Date.now()}-${Math.random().toString(36).slice(2)}`);
129
+ const command = new EmulatorCommand({ firebaseProjectPath: projectDir, argv: {}, options: {} });
130
+ const lines = [];
131
+
132
+ command.log = (message) => lines.push(String(message));
133
+ command.logWarning = (message) => lines.push(String(message));
134
+
135
+ jetpack.write(command.getTempPath('emulator-pids.json'), {
136
+ pids: pids,
137
+ projectId: OURS,
138
+ rootPid: pids[0],
139
+ startedAt: new Date().toISOString(),
140
+ });
141
+
142
+ return { command, lines, cleanup: () => jetpack.remove(projectDir) };
143
+ }
144
+
145
+ /**
146
+ * An emulator command that can actually BOOT: a throwaway project whose
147
+ * firebase.json names ports nothing else uses, and a `firebase` on PATH that
148
+ * behaves like `script` says.
149
+ *
150
+ * Three seams are neutralized because they reach outside the throwaway project
151
+ * rather than into it: attachVerbLog re-tees THIS process' stdout (the runner's
152
+ * own output), and ensureStaged/startStageWatch build and watch a src/ tree
153
+ * that does not exist here. Everything the stop path touches — the spawn, the
154
+ * process group, the pid record, the ports — is real.
155
+ */
156
+ async function bootableCommand(script) {
157
+ const projectDir = path.join(os.tmpdir(), `omega-boot-${Date.now()}-${Math.random().toString(36).slice(2)}`);
158
+ // execute() runs IN-PROCESS and the boot path publishes its resolved map
159
+ // into process.env (portsToEnv + emulator hosts). This runner is SHARED with
160
+ // every later suite — a leaked OMEGA_HOSTING_PORT pointed the payment
161
+ // journeys' in-process webhook at the throwaway project's dead port. Snapshot
162
+ // the whole env and restore it in cleanup.
163
+ const savedEnv = { ...process.env };
164
+ const savedPath = process.env.PATH;
165
+
166
+ // Ports the kernel just handed out: free, so the allocator resolves them
167
+ // verbatim, and nowhere near the classic map the live emulator running this
168
+ // very suite is sitting on.
169
+ const names = ['auth', 'functions', 'firestore', 'database', 'hosting', 'storage', 'pubsub', 'ui'];
170
+ const emulators = {};
171
+ const taken = new Set();
172
+ for (const name of names) {
173
+ let port = await freePort();
174
+ while (taken.has(port)) port = await freePort();
175
+ taken.add(port);
176
+ emulators[name] = { port: port };
177
+ }
178
+
179
+ jetpack.write(path.join(projectDir, 'firebase.json'), JSON.stringify({ emulators }, null, 2));
180
+ jetpack.dir(path.join(projectDir, 'dist'));
181
+ jetpack.file(path.join(projectDir, 'bin', 'firebase'), { content: script, mode: '755' });
182
+ process.env.PATH = `${path.join(projectDir, 'bin')}${path.delimiter}${savedPath}`;
183
+
184
+ const command = new EmulatorCommand({
185
+ firebaseProjectPath: projectDir,
186
+ argv: { https: false, seed: false },
187
+ options: {},
188
+ });
189
+ const lines = [];
190
+
191
+ command.log = (message) => lines.push(String(message));
192
+ command.logWarning = (message) => lines.push(String(message));
193
+ command.logError = (message) => lines.push(String(message));
194
+ command.attachVerbLog = () => '';
195
+ command.ensureStaged = () => {};
196
+ command.startStageWatch = () => ({ close: () => {} });
197
+
198
+ return {
199
+ command,
200
+ lines,
201
+ ports: emulators,
202
+ cleanup: () => {
203
+ for (const key of Object.keys(process.env)) {
204
+ if (!(key in savedEnv)) delete process.env[key];
205
+ }
206
+ Object.assign(process.env, savedEnv);
207
+ jetpack.remove(projectDir);
208
+ },
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Run `fn` with an `lsof` on PATH that never answers — the smbfs stall of
214
+ * [#332](https://github.com/Omega-JS-Stack/omega/issues/332), made
215
+ * deterministic. Nothing about the sweep is stubbed: it shells out for real,
216
+ * to a real process, and the only fixture is that the process never replies.
217
+ *
218
+ * `exec` in the shim is the point: the hung process must BE the one the lookup
219
+ * spawned, so the bound's kill lands on the thing holding its stdout — a shell
220
+ * left waiting on a `sleep` grandchild would hang past any timeout.
221
+ */
222
+ async function withHungLsof(fn) {
223
+ const binDir = path.join(os.tmpdir(), `omega-lsof-${Date.now()}-${Math.random().toString(36).slice(2)}`);
224
+ const savedPath = process.env.PATH;
225
+
226
+ jetpack.file(path.join(binDir, 'lsof'), { content: '#!/bin/sh\nexec sleep 600\n', mode: '755' });
227
+ process.env.PATH = `${binDir}${path.delimiter}${savedPath}`;
228
+
229
+ try {
230
+ return await fn();
231
+ } finally {
232
+ process.env.PATH = savedPath;
233
+ jetpack.remove(binDir);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Run `fn` with the background watcher swapped for a REAL detached process.
239
+ *
240
+ * The watcher itself is a nodemon over the framework's own src/ — pointing one
241
+ * at this repo mid-test would start a second live reload lane, and on a
242
+ * registry install it does not start at all. What the run owes it is a signal,
243
+ * and a real process proves the signal for real.
244
+ */
245
+ async function withRealWatcherStandIn(fn) {
246
+ const original = WatchCommand.prototype.startBackground;
247
+ const watchers = [];
248
+
249
+ WatchCommand.prototype.startBackground = function () {
250
+ const child = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000)'], {
251
+ detached: true,
252
+ stdio: 'ignore',
253
+ });
254
+ child.unref();
255
+ watchers.push(child);
256
+
257
+ return child;
258
+ };
259
+
260
+ try {
261
+ return await fn(watchers);
262
+ } finally {
263
+ WatchCommand.prototype.startBackground = original;
264
+ stopAll(watchers.map((child) => child.pid));
265
+ }
266
+ }
267
+
268
+ /**
269
+ * The stub `firebase`, on PATH: a process group shaped like firebase-tools' own
270
+ * — a shell with a child of its own — that reports ready and then idles until
271
+ * something signals it. `$$` is the group leader the run spawned; `$!` is a
272
+ * member only the GROUP signal reaches. Neither ever binds a port, and no real
273
+ * emulator is involved.
274
+ * @param {string} groupPath - Where the stub records its two pids.
275
+ * @returns {string} The script body.
276
+ */
277
+ function firebaseGroupThenIdles(groupPath) {
278
+ return [
279
+ '#!/bin/sh',
280
+ "node -e 'setTimeout(() => {}, 30000)' &",
281
+ `printf '%s %s\\n' "$$" "$!" > "${groupPath}"`,
282
+ 'echo "All emulators ready!"',
283
+ 'wait',
284
+ '',
285
+ ].join('\n');
286
+ }
287
+
288
+ /**
289
+ * The run under test, as a REAL child process. A signal is only a signal when
290
+ * it arrives at a process: in-process cases can call the stop path but can
291
+ * never prove that SIGHUP reaches it at all.
292
+ *
293
+ * Three seams are neutralized for the same reasons bootableCommand() names, and
294
+ * one more: the recorded-pid reap is proven by the cases above, and here it
295
+ * would send the run looking at ports outside the throwaway project — so the
296
+ * group signal stands on its own.
297
+ */
298
+ const SIGNAL_DRIVER = `
299
+ const fs = require('fs');
300
+ const { spawn } = require('child_process');
301
+ const [, , projectDir, commandPath, watchPath, statePath] = process.argv;
302
+
303
+ const EmulatorCommand = require(commandPath);
304
+ const WatchCommand = require(watchPath);
305
+
306
+ WatchCommand.prototype.startBackground = function () {
307
+ const child = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000)'], { detached: true, stdio: 'ignore' });
308
+ child.unref();
309
+ fs.writeFileSync(statePath, String(child.pid));
310
+
311
+ return child;
312
+ };
313
+
314
+ const command = new EmulatorCommand({
315
+ firebaseProjectPath: projectDir,
316
+ argv: { https: false, seed: false },
317
+ options: {},
318
+ });
319
+
320
+ command.attachVerbLog = () => '';
321
+ command.ensureStaged = () => {};
322
+ command.startStageWatch = () => ({ close: () => {} });
323
+ command.terminateRecordedEmulatorProcesses = async () => {};
324
+
325
+ command.execute();
326
+ `;
327
+
328
+ /**
329
+ * Poll until `check` passes or the window closes.
330
+ * @param {Function} check - Returns truthy when the wait is over.
331
+ * @param {number} timeoutMs - How long to keep looking.
332
+ * @returns {Promise<boolean>} Whether it passed inside the window.
333
+ */
334
+ async function waitUntil(check, timeoutMs) {
335
+ const deadline = Date.now() + timeoutMs;
336
+
337
+ while (Date.now() < deadline) {
338
+ if (check()) {
339
+ return true;
340
+ }
341
+
342
+ await powertools.wait(50);
343
+ }
344
+
345
+ return check();
346
+ }
347
+
348
+ /**
349
+ * Boot the emulator command in its own process and wait for it to report ready.
350
+ * Returns the handle plus every pid the run is now responsible for.
351
+ */
352
+ async function bootedRun() {
353
+ const dir = path.join(os.tmpdir(), `omega-signal-${Date.now()}-${Math.random().toString(36).slice(2)}`);
354
+ const groupPath = path.join(dir, 'group.txt');
355
+ const statePath = path.join(dir, 'watcher.txt');
356
+
357
+ // Kernel-assigned ports, exactly as bootableCommand does: free, so nothing
358
+ // bumps, and nowhere near the classic map.
359
+ const emulators = {};
360
+ const taken = new Set();
361
+ for (const name of ['auth', 'functions', 'firestore', 'database', 'hosting', 'storage', 'pubsub', 'ui']) {
362
+ let port = await freePort();
363
+ while (taken.has(port)) port = await freePort();
364
+ taken.add(port);
365
+ emulators[name] = { port: port };
366
+ }
367
+
368
+ jetpack.write(path.join(dir, 'firebase.json'), JSON.stringify({ emulators }, null, 2));
369
+ jetpack.dir(path.join(dir, 'dist'));
370
+ jetpack.write(path.join(dir, 'driver.js'), SIGNAL_DRIVER);
371
+ jetpack.file(path.join(dir, 'bin', 'firebase'), { content: firebaseGroupThenIdles(groupPath), mode: '755' });
372
+
373
+ // Detached: the run gets its OWN process group, so the signal below lands on
374
+ // that ONE process and nothing else — the strictly harder case, and the one
375
+ // `omega dev`'s stop and a closed terminal both produce.
376
+ const child = spawn(process.execPath, [
377
+ path.join(dir, 'driver.js'),
378
+ dir,
379
+ require.resolve('../../dist/cli/commands/emulator.js'),
380
+ require.resolve('../../dist/cli/commands/watch.js'),
381
+ statePath,
382
+ ], {
383
+ env: { ...process.env, PATH: `${path.join(dir, 'bin')}${path.delimiter}${process.env.PATH}` },
384
+ stdio: ['ignore', 'pipe', 'pipe'],
385
+ detached: true,
386
+ });
387
+
388
+ let output = '';
389
+ child.stdout.on('data', (data) => { output += data.toString(); });
390
+ child.stderr.on('data', (data) => { output += data.toString(); });
391
+
392
+ const ready = await waitUntil(() => /Emulator ready/.test(output) && jetpack.exists(groupPath) && jetpack.exists(statePath), 60000);
393
+ const [leader, member] = String(jetpack.read(groupPath) || '').trim().split(/\s+/).map(Number);
394
+ const watcher = Number(jetpack.read(statePath));
395
+
396
+ return {
397
+ child: child,
398
+ ready: ready,
399
+ output: () => output,
400
+ stubPids: [leader, member].filter(Number.isInteger),
401
+ watcher: watcher,
402
+ cleanup: () => {
403
+ try { process.kill(-child.pid, 'SIGKILL'); } catch (e) { /* already gone */ }
404
+ stopAll([leader, member, watcher, child.pid].filter(Number.isInteger));
405
+ jetpack.remove(dir);
406
+ },
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Boot the run, signal it with `signal`, and prove nothing it started is left.
412
+ * Every process signalled here is one this case spawned.
413
+ * @param {object} assert - The case assertions.
414
+ * @param {string} signal - The stop signal under test.
415
+ */
416
+ async function stopsEverything(assert, signal) {
417
+ const run = await bootedRun();
418
+
419
+ try {
420
+ assert.equal(run.ready, true, `the run must come up before it can be stopped: ${run.output()}`);
421
+ assert.equal(run.stubPids.length, 2, 'the stub emulator reported its group leader and a member');
422
+ assert.equal(Number.isInteger(run.watcher), true, 'the run started a background watcher');
423
+
424
+ process.kill(run.child.pid, signal);
425
+
426
+ assert.equal(await exitedWithin(run.child, 30000), true, `the run must take its stop path on ${signal}: ${run.output()}`);
427
+ assert.equal(
428
+ await waitUntil(() => run.stubPids.every((pid) => !isAlive(pid)), 15000),
429
+ true,
430
+ `the emulator process group must be gone after ${signal} (still alive: ${run.stubPids.filter((pid) => isAlive(pid)).join(', ')})`,
431
+ );
432
+ assert.equal(await waitUntil(() => !isAlive(run.watcher), 15000), true, `no watcher may outlive the run on ${signal}`);
433
+ } finally {
434
+ run.cleanup();
435
+ }
436
+ }
437
+
438
+ // The stop path also sweeps the ports it was handed, and on a defaults run the
439
+ // SHARED hub/storage numbers with them. A unit test's map is synthetic, so the
440
+ // shared pair is never this record's business — every direct call below says so.
441
+ const NO_SHARED_SWEEP = { sweepShared: false };
442
+
443
+ module.exports = defineCases({
444
+ description: 'emulator stop path: the recorded stack goes down with it',
445
+ type: 'group',
446
+ timeout: 30000,
447
+
448
+ tests: [
449
+ {
450
+ name: 'the-stop-path-terminates-the-jars-the-group-signal-missed',
451
+ async run({ assert }) {
452
+ const firestore = spawnDetached(FIRESTORE_JAR);
453
+ const pubsub = spawnDetached(PUBSUB_JAR);
454
+ const { command, cleanup } = commandWithRecord([firestore, pubsub]);
455
+
456
+ try {
457
+ await command.terminateRecordedEmulatorProcesses({ firestore: await freePort() }, NO_SHARED_SWEEP);
458
+
459
+ assert.equal(isAlive(firestore), false, 'the recorded firestore jar should be stopped');
460
+ assert.equal(isAlive(pubsub), false, 'the recorded pubsub jar should be stopped');
461
+ } finally {
462
+ stopAll([firestore, pubsub]);
463
+ cleanup();
464
+ }
465
+ },
466
+ },
467
+
468
+ {
469
+ name: 'a-recycled-pid-is-never-a-kill-order',
470
+ async run({ assert }) {
471
+ // The record names pids, and the OS hands those numbers out again. A
472
+ // recorded number now running something that is not emulator machinery
473
+ // is a stranger, and the stop path has no port to narrow by.
474
+ const stranger = spawnDetached('some-other-tool --serve');
475
+ const { command, cleanup } = commandWithRecord([stranger]);
476
+
477
+ try {
478
+ await command.terminateRecordedEmulatorProcesses({ firestore: await freePort() }, NO_SHARED_SWEEP);
479
+
480
+ assert.equal(isAlive(stranger), true, 'a recycled pid running something else must be left alone');
481
+ } finally {
482
+ stopAll([stranger]);
483
+ cleanup();
484
+ }
485
+ },
486
+ },
487
+
488
+ {
489
+ name: 'a-recycled-pid-running-another-brands-jar-is-left-alone',
490
+ async run({ assert }) {
491
+ // The harder recycling: the number came back as emulator machinery, so
492
+ // "is this a jar" passes and the record's pid membership used to end
493
+ // the question there. The jar names ITS project on its own command
494
+ // line, and it is not ours.
495
+ const neighbour = spawnDetached(NEIGHBOUR_JAR);
496
+ const { command, cleanup } = commandWithRecord([neighbour]);
497
+
498
+ try {
499
+ await command.terminateRecordedEmulatorProcesses({ firestore: await freePort() }, NO_SHARED_SWEEP);
500
+
501
+ assert.equal(isAlive(neighbour), true, "another brand's live jar must survive our recorded pid");
502
+ } finally {
503
+ stopAll([neighbour]);
504
+ cleanup();
505
+ }
506
+ },
507
+ },
508
+
509
+ {
510
+ name: 'a-port-still-held-by-somebody-else-is-reported-not-killed',
511
+ async run({ assert }) {
512
+ // The port check is evidence for the human, never a kill order:
513
+ // signaling whatever answers on a port is what took down another
514
+ // project's live stack ([#274](https://github.com/Omega-JS-Stack/omega/issues/274)).
515
+ const server = net.createServer();
516
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
517
+ const port = server.address().port;
518
+
519
+ const { command, lines, cleanup } = commandWithRecord([]);
520
+
521
+ try {
522
+ await command.terminateRecordedEmulatorProcesses({ hosting: port }, NO_SHARED_SWEEP);
523
+
524
+ assert.equal(server.listening, true, 'the foreign listener keeps its port');
525
+ assert.match(lines.join('\n'), new RegExp(`still in use after shutdown: ${port}`));
526
+ } finally {
527
+ server.close();
528
+ cleanup();
529
+ }
530
+ },
531
+ },
532
+
533
+ {
534
+ name: 'a-port-lookup-that-hangs-is-bounded-and-names-nobody',
535
+ timeout: 60000,
536
+ async run({ assert }) {
537
+ // lsof is the one step with no Node primitive, and on a machine with a
538
+ // network mount it can stall for MINUTES before it answers
539
+ // ([#332](https://github.com/Omega-JS-Stack/omega/issues/332)). The
540
+ // call was unbounded, and being synchronous it blocks the event loop —
541
+ // so nothing downstream, not even a test's own timeout, could cut it
542
+ // short ([#459](https://github.com/Omega-JS-Stack/omega/issues/459)).
543
+ const port = await freePort();
544
+ const startedAt = Date.now();
545
+ const pids = await withHungLsof(() => listListeningPids(port));
546
+ const elapsed = Date.now() - startedAt;
547
+
548
+ assert.deepEqual(pids, [], 'a lookup that never answered names nobody — no ownership, so no kill order');
549
+ assert.equal(elapsed >= 1000, true, `the hung stub really was reached (a missing lsof returns at once), took ${elapsed}ms`);
550
+ assert.equal(elapsed < PORT_LOOKUP_TIMEOUT_MS * 2, true, `the lookup must return inside its bound, took ${elapsed}ms`);
551
+ },
552
+ },
553
+
554
+ {
555
+ name: 'a-stop-path-whose-lookup-hangs-still-finishes-and-kills-nothing',
556
+ timeout: 60000,
557
+ async run({ assert }) {
558
+ // The gate failure this bound exists for: a held port sends the sweep
559
+ // to lsof, lsof stalls on the smbfs mount, and the stop path sat there
560
+ // until the lane's whole 113s budget was gone. The verdict was never
561
+ // wrong — there was simply no bound on reaching it. A sweep that could
562
+ // not prove ownership takes the branch it already had: report, not kill.
563
+ const server = net.createServer();
564
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
565
+ const port = server.address().port;
566
+
567
+ const { command, lines, cleanup } = commandWithRecord([]);
568
+ const startedAt = Date.now();
569
+
570
+ try {
571
+ await withHungLsof(() => command.terminateRecordedEmulatorProcesses({ hosting: port }, NO_SHARED_SWEEP));
572
+ const elapsed = Date.now() - startedAt;
573
+
574
+ assert.equal(server.listening, true, 'a holder the sweep could not identify keeps its port');
575
+ assert.match(lines.join('\n'), new RegExp(`still in use after shutdown: ${port}`));
576
+ assert.equal(elapsed < 30000, true, `the stop path must stay bounded end to end, took ${elapsed}ms`);
577
+ } finally {
578
+ server.close();
579
+ cleanup();
580
+ }
581
+ },
582
+ },
583
+
584
+ {
585
+ name: 'the-orphan-sweep-runs-before-the-port-verdict',
586
+ async run({ assert }) {
587
+ // A jar the record never named (spawned after the last snapshot, then
588
+ // reparented to PID 1) is the SWEEP's case, not the record's. Polling
589
+ // the ports first spent the whole release window waiting on a port the
590
+ // next step was about to free, then called it somebody else's.
591
+ const server = net.createServer();
592
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
593
+ const port = server.address().port;
594
+
595
+ const { command, lines, cleanup } = commandWithRecord([]);
596
+ let swept = false;
597
+
598
+ // The sweep's own kill decision is proven above and in the #274/#293
599
+ // suites; what is under test here is WHEN it runs, so it stands in as
600
+ // the thing it would do to a squatter it owns.
601
+ command.terminateOrphanedEmulatorProcesses = () => {
602
+ swept = true;
603
+ server.close();
604
+ };
605
+
606
+ const startedAt = Date.now();
607
+
608
+ try {
609
+ await command.terminateRecordedEmulatorProcesses({ hosting: port }, NO_SHARED_SWEEP);
610
+
611
+ assert.equal(swept, true, 'the stop path runs the owned-orphan sweep itself');
612
+ assert.equal(lines.join('\n').includes('still in use'), false, `a port the sweep freed is not somebody else's: ${lines.join(' | ')}`);
613
+ assert.equal(Date.now() - startedAt < 2000, true, 'no full release-window stall for a port the sweep already freed');
614
+ } finally {
615
+ server.close();
616
+ cleanup();
617
+ }
618
+ },
619
+ },
620
+
621
+ {
622
+ name: 'the-run-that-shut-down-cleanly-reports-nothing',
623
+ async run({ assert }) {
624
+ // Everything already gone (the group signal did reach them) and every
625
+ // port free: the stop path is silent, no phantom warnings.
626
+ const gone = spawnDetached(FIRESTORE_JAR);
627
+ stopAll([gone]);
628
+ await powertools.wait(200);
629
+
630
+ const { command, lines, cleanup } = commandWithRecord([gone]);
631
+
632
+ try {
633
+ await command.terminateRecordedEmulatorProcesses({ firestore: await freePort() }, NO_SHARED_SWEEP);
634
+
635
+ assert.equal(lines.length, 0, `a clean stop should say nothing, said: ${lines.join(' | ')}`);
636
+ } finally {
637
+ cleanup();
638
+ }
639
+ },
640
+ },
641
+
642
+ // ─── the ownership bar the stop path clears ───
643
+
644
+ {
645
+ name: 'a-recorded-pid-still-running-emulator-machinery-is-stoppable',
646
+ async run({ assert }) {
647
+ assert.equal(isStoppableEmulatorProcess({ pid: 37088, command: FIRESTORE_JAR }, { pids: [37088], projectId: OURS }), true);
648
+ assert.equal(isStoppableEmulatorProcess({ pid: 37088, command: 'node /Users/ian/some-other-tool/server.js' }, { pids: [37088], projectId: OURS }), false);
649
+ assert.equal(isStoppableEmulatorProcess({ pid: 51001, command: NEIGHBOUR_JAR }, { pids: [], projectId: OURS }), false);
650
+ },
651
+ },
652
+
653
+ {
654
+ name: 'a-command-line-that-names-another-project-outranks-the-record',
655
+ async run({ assert }) {
656
+ // Pid membership short-circuited the project check, so a recorded
657
+ // number recycled onto the neighbouring brand's jar read as ours.
658
+ assert.equal(isStoppableEmulatorProcess({ pid: 37088, command: NEIGHBOUR_JAR }, { pids: [37088], projectId: OURS }), false);
659
+
660
+ // A jar that names NO project (pubsub never does) is still decided by
661
+ // the record — that is the whole reason the record exists.
662
+ assert.equal(isStoppableEmulatorProcess({ pid: 37088, command: PUBSUB_JAR }, { pids: [37088], projectId: OURS }), true);
663
+
664
+ // And with no project id of our own there is nothing to disagree with.
665
+ assert.equal(isStoppableEmulatorProcess({ pid: 37088, command: NEIGHBOUR_JAR }, { pids: [37088], projectId: null }), true);
666
+ },
667
+ },
668
+
669
+ // ─── the wiring: the run's exit path joins the stop path ───
670
+
671
+ {
672
+ name: 'the-exit-path-joins-the-shutdown-and-its-reaper',
673
+ timeout: 60000,
674
+ async run({ assert }) {
675
+ // Deleting the reaper call from shutdown(), or the `await shutdown()`
676
+ // the exit path joins it with, left every other test in this file
677
+ // green: they call the reaper directly. This one boots the real command
678
+ // and watches the RUN carry it.
679
+ await withRealWatcherStandIn(async (watchers) => {
680
+ const { command, ports, cleanup } = await bootableCommand(FIREBASE_READY_THEN_EXITS);
681
+ const reaped = [];
682
+
683
+ // Resolves a beat late on purpose: a shutdown that fires and forgets
684
+ // returns before this records anything.
685
+ command.terminateRecordedEmulatorProcesses = async (emulatorPorts, options) => {
686
+ await powertools.wait(200);
687
+ reaped.push({ emulatorPorts, options });
688
+ };
689
+
690
+ try {
691
+ await command.execute();
692
+
693
+ assert.equal(reaped.length, 1, 'the run must join the shutdown it started, reaper included');
694
+ assert.equal(reaped[0].emulatorPorts.hosting, ports.hosting.port, 'the reaper gets this run\'s RESOLVED port map');
695
+ assert.equal(reaped[0].options.sweepShared, true, 'nothing bumped, so the shared hub/storage ports are this run\'s to sweep');
696
+ assert.equal(await exitedWithin(watchers[0], 3000), true, 'the background watcher goes down with the run');
697
+ } finally {
698
+ cleanup();
699
+ }
700
+ });
701
+ },
702
+ },
703
+
704
+ {
705
+ name: 'a-boot-that-never-came-up-still-tears-down-what-it-started',
706
+ timeout: 60000,
707
+ async run({ assert }) {
708
+ // The boot-failure catch exited straight out: the watcher it had just
709
+ // started stayed alive (a nodemon nothing else signals) and whatever
710
+ // the spawn got as far as was never stopped.
711
+ await withRealWatcherStandIn(async (watchers) => {
712
+ const { command, cleanup } = await bootableCommand(FIREBASE_DIES_BEFORE_READY);
713
+ const reaped = [];
714
+ command.terminateRecordedEmulatorProcesses = async () => { reaped.push(true); };
715
+
716
+ // The one stub the framework's rules name outright: a real exit here
717
+ // takes the test runner with it mid-assertion.
718
+ const realExit = process.exit;
719
+ const exits = [];
720
+ process.exit = (code) => { exits.push(code); };
721
+
722
+ try {
723
+ await command.execute();
724
+
725
+ assert.deepEqual(exits, [1], 'a failed boot still exits non-zero');
726
+ assert.equal(reaped.length, 1, 'a failed boot runs the same stop path a normal one does');
727
+ assert.equal(await exitedWithin(watchers[0], 3000), true, 'the watcher does not outlive the failed boot');
728
+ } finally {
729
+ process.exit = realExit;
730
+ cleanup();
731
+ }
732
+ });
733
+ },
734
+ },
735
+
736
+ // ─── the stop path is reached on EVERY way this run is asked to stop ───
737
+
738
+ {
739
+ name: 'the-record-names-the-background-watcher-the-descent-cannot-reach',
740
+ async run({ assert }) {
741
+ // The record walks DOWN from the emulator child, which covers
742
+ // firebase-tools' own tree — the jars and every functions runtime
743
+ // worker it forks. The reload watcher hangs off the CLI process
744
+ // instead, so the walk never sees it: unnamed, a run that dies without
745
+ // teardown leaves a nodemon no later stop path can even name.
746
+ const root = spawnDetached(FIRESTORE_JAR);
747
+ const watcher = spawnDetached(NODEMON_WATCHER);
748
+ const { command, cleanup } = commandWithRecord([]);
749
+
750
+ try {
751
+ command.backgroundWatcherPid = watcher;
752
+ const pids = command.writeEmulatorPidRecord(root, OURS);
753
+
754
+ assert.equal(pids.includes(root), true, 'the emulator child is the record\'s root');
755
+ assert.equal(pids.includes(watcher), true, 'the background watcher is recorded with it');
756
+ assert.equal(command.readEmulatorOwnership().pids.includes(watcher), true, 'and it survives the round trip through the file');
757
+ assert.equal(isStoppableEmulatorProcess({ pid: watcher, command: NODEMON_WATCHER }, command.readEmulatorOwnership()), true, 'so the stop path may signal it');
758
+ } finally {
759
+ stopAll([root, watcher]);
760
+ cleanup();
761
+ }
762
+ },
763
+ },
764
+
765
+ {
766
+ name: 'a-programmatic-stop-takes-the-whole-stack-down',
767
+ timeout: 120000,
768
+ async run({ assert }) {
769
+ // `omega dev` stops its backend child with SIGTERM. The stop path was
770
+ // wired to SIGINT alone, so the default action killed THIS process and
771
+ // left the detached group — and the watcher — running.
772
+ await stopsEverything(assert, 'SIGTERM');
773
+ },
774
+ },
775
+
776
+ {
777
+ name: 'a-closed-terminal-takes-the-whole-stack-down',
778
+ timeout: 120000,
779
+ async run({ assert }) {
780
+ // A closed terminal sends SIGHUP, and it orphaned the same tree: four
781
+ // generations of parentless java emulators on one machine, one of them
782
+ // still holding 8080 four days later
783
+ // ([#629](https://github.com/Omega-JS-Stack/omega/issues/629)).
784
+ await stopsEverything(assert, 'SIGHUP');
785
+ },
786
+ },
787
+ ],
788
+ });