@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,1155 @@
1
+ /**
2
+ * compile-rules — the compiled Firestore rules model
3
+ * ([#255](https://github.com/Omega-JS-Stack/omega/issues/255)) and its
4
+ * merge-by-match contract
5
+ * ([#353](https://github.com/Omega-JS-Stack/omega/issues/353)).
6
+ *
7
+ * Firestore ORs `allow` across sibling match blocks, so a brand's own
8
+ * `match /users/{uid}` can only WIDEN the framework's — never tighten it. The
9
+ * model:
10
+ *
11
+ * brand firestore.rules ← SOURCE, the brand's, pure rules
12
+ * + framework templates/firestore.framework.rules ← ships inside this package
13
+ * = dist/firestore.rules ← GENERATED artifact, deployed
14
+ *
15
+ * Both halves are spliced into ONE `match /databases/{database}/documents`
16
+ * scope, so functions resolve across the seam in both directions.
17
+ *
18
+ * MERGE-BY-MATCH is how a brand tightens: a brand match block whose path
19
+ * matches a framework block's (wildcard names normalized) is MERGED into it —
20
+ * for an op BOTH declare, the brand's condition is parenthesized and ANDed onto
21
+ * the framework's; an op only the brand declares appends verbatim; nested
22
+ * matches splice in. A brand writes ordinary rules; nothing hooks into
23
+ * anything. The v2 hook functions (`protectedFields()`, `canWriteUser()`) are
24
+ * RETIRED — see `ensureBrandRulesSource`, which migrates them out.
25
+ *
26
+ * Splitting the work:
27
+ * - `compileFirestoreRules()` writes dist/ ONLY (the build step, run on every
28
+ * stage).
29
+ * - `ensureBrandRulesSource()` writes the brand's authored file (the setup
30
+ * step): seeds it, and migrates a legacy marker-block or hook-era file once.
31
+ */
32
+ const path = require('path');
33
+ const jetpack = require('fs-jetpack');
34
+ const chalk = require('chalk').default;
35
+
36
+ // The rules SCHEMA version — the stamp in the compiled artifact's header and in
37
+ // `database.rules.json`'s marker block. Bumps ONLY when the generated rule
38
+ // semantics change, never with the package version (tying it to the package
39
+ // version made every release rewrite every consumer's rules files). v2.0.0 was
40
+ // the compiled model (#255) + the notifications malformed-doc guard (#288);
41
+ // v3.0.0 is merge-by-match + the settled helper API (#353).
42
+ const RULES_VERSION = '3.0.0';
43
+
44
+ // The brand's authored source, at the target root — the file the brand edits.
45
+ const BRAND_RULES_FILE = 'firestore.rules';
46
+
47
+ // The compiled artifact, inside the staged output tree (src/dist pillar:
48
+ // dist/ is the generated tree every runtime surface reads — emulator, serve,
49
+ // test, deploy). Relative to the target root, which is how firebase.json spells
50
+ // it. Re-created by every stage, so it can never go stale or missing.
51
+ const COMPILED_RULES_FILE = 'dist/firestore.rules';
52
+
53
+ // The deliberate, run-alone step that moves a brand onto the compiled model —
54
+ // never a side effect of another verb
55
+ // ([#522](https://github.com/Omega-JS-Stack/omega/issues/522)).
56
+ const RULES_MIGRATION_COMMAND = 'npx omega migrate:rules';
57
+
58
+ // The one-time conversion of a PRE-FAMILY marker file onto the family grammar
59
+ // ([#40](https://github.com/Omega-JS-Stack/omega/issues/40)). Every evergreen
60
+ // verb speaks ONLY the family, so a tree carried over from BEM converges at no
61
+ // verb until this one has run — the verbs DETECT the old shapes and point here,
62
+ // never convert.
63
+ const MARKER_MIGRATION_COMMAND = 'npx omega migrate:markers';
64
+
65
+ // The two halves that ship inside this package.
66
+ const TEMPLATES_DIR = path.resolve(__dirname, '..', '..', '..', 'templates');
67
+ const FRAMEWORK_RULES_TEMPLATE = path.join(TEMPLATES_DIR, 'firestore.framework.rules');
68
+ const BRAND_RULES_SEED = path.join(TEMPLATES_DIR, BRAND_RULES_FILE);
69
+
70
+ // The v2 hooks and the DEFAULT body each shipped with. A brand file carrying
71
+ // the default body had never edited it: the migration deletes it outright. A
72
+ // body that differs is the brand's own work and is KEPT (as a plain function)
73
+ // and reported.
74
+ const RETIRED_HOOKS = {
75
+ protectedFields: 'return [];',
76
+ canWriteUser: 'return true;',
77
+ };
78
+
79
+ // The helpers v3 RENAMED, old → new. A brand source still CALLING one names a
80
+ // function no half defines any more, so the migration renames the call. Both
81
+ // eras are here: the v2 (0.36.0) names, and the ones v3 itself carried before
82
+ // the API settled on `is*` predicates + `get*` values — v3 never shipped, so a
83
+ // brand can only have the first kind, but naming both keeps ONE map honest.
84
+ const RENAMED_HELPERS = {
85
+ emailVerified: 'isEmailVerified',
86
+ isWritingProtectedUserField: 'isWritingFrameworkField',
87
+ belongsTo: 'isUser',
88
+ authUid: 'getAuthUid',
89
+ authEmail: 'getAuthEmail',
90
+ existingData: 'getExistingData',
91
+ incomingData: 'getIncomingData',
92
+ };
93
+
94
+ // Every name v3 retired: the two hooks plus the renamed helpers. A brand source
95
+ // naming one is pre-v3 and gets migrated, and the rename sweep greps this list.
96
+ const RETIRED_NAMES = [...Object.keys(RETIRED_HOOKS), ...Object.keys(RENAMED_HELPERS)];
97
+
98
+ // Firestore's `allow` aliases in primitive terms. Merge-by-match pairs ops by
99
+ // NAME, so a brand `allow create` beside a framework `allow write` does not
100
+ // tighten it — Firestore ORs the two. That case is reported, never silent.
101
+ const OP_ALIASES = { read: ['get', 'list'], write: ['create', 'update', 'delete'] };
102
+
103
+ // The seed's example-rules block — what a migration replaces with the brand's
104
+ // own extracted rules. Kept verbatim in templates/firestore.rules.
105
+ const SEED_RULES_PLACEHOLDER = [
106
+ ' // match /posts/{id} {',
107
+ ' // allow read: if true;',
108
+ ' // allow write: if isAdmin();',
109
+ ' // }',
110
+ ].join('\n');
111
+
112
+ function defaultWarn(message) {
113
+ console.warn(chalk.yellow(message));
114
+ }
115
+
116
+ /**
117
+ * The index just past the comment or string literal starting at `index`, or -1
118
+ * when code starts there. The ONE place the parser knows what is not code, so a
119
+ * `// }` never closes a block and a `'match /x'` never opens one.
120
+ * @param {string} source
121
+ * @param {number} index
122
+ * @returns {number}
123
+ */
124
+ function skipNonCode(source, index) {
125
+ const char = source[index];
126
+ const next = source[index + 1];
127
+
128
+ if (char === '/' && next === '/') {
129
+ const end = source.indexOf('\n', index);
130
+ return end === -1 ? source.length : end;
131
+ }
132
+ if (char === '/' && next === '*') {
133
+ const end = source.indexOf('*/', index + 2);
134
+ return end === -1 ? source.length : end + 2;
135
+ }
136
+ if (char === '\'' || char === '"') {
137
+ const end = source.indexOf(char, index + 1);
138
+ return end === -1 ? source.length : end + 1;
139
+ }
140
+
141
+ return -1;
142
+ }
143
+
144
+ /**
145
+ * Index of the `}` closing the `{` at `open`, skipping comments and strings.
146
+ * @param {string} source
147
+ * @param {number} open - Index of the opening brace.
148
+ * @param {string} label - What to name the file in an error.
149
+ * @returns {number}
150
+ */
151
+ function findClosingBrace(source, open, label) {
152
+ let depth = 0;
153
+
154
+ for (let i = open; i < source.length; i++) {
155
+ const skip = skipNonCode(source, i);
156
+ if (skip !== -1) {
157
+ i = skip - 1;
158
+ continue;
159
+ }
160
+
161
+ const char = source[i];
162
+ if (char === '{') {
163
+ depth++;
164
+ } else if (char === '}') {
165
+ depth--;
166
+ if (depth === 0) {
167
+ return i;
168
+ }
169
+ }
170
+ }
171
+
172
+ throw new Error(`${label}: unbalanced braces — could not find the block closing the one at offset ${open}`);
173
+ }
174
+
175
+ /**
176
+ * The body of the `match /databases/{database}/documents { … }` block.
177
+ *
178
+ * Brace-matched rather than regexed: a rules file nests match blocks and
179
+ * functions arbitrarily deep.
180
+ * @param {string} source - A whole rules file.
181
+ * @param {string} label - What to name the file in an error.
182
+ * @returns {string} The block's inner text (indentation preserved).
183
+ */
184
+ function extractDocumentsBody(source, label) {
185
+ const opener = /match\s+\/databases\/\{[A-Za-z0-9_]+\}\/documents\s*\{/.exec(source);
186
+ if (!opener) {
187
+ throw new Error(`${label}: no \`match /databases/{database}/documents\` block found — a Firestore rules file must carry one`);
188
+ }
189
+
190
+ const open = opener.index + opener[0].length - 1;
191
+ const close = findClosingBrace(source, open, label);
192
+
193
+ return source.slice(open + 1, close);
194
+ }
195
+
196
+ /**
197
+ * Parse a rules block's body into its top-level nodes: `match` blocks,
198
+ * `function` definitions, `allow` statements, and the plain text between them
199
+ * (comments, blank lines).
200
+ *
201
+ * Every node keeps its RAW text and they tile the input end to end, so
202
+ * concatenating them reproduces the body byte for byte — the merge rewrites the
203
+ * two or three nodes it touches and re-emits everything else untouched.
204
+ * @param {string} body - A block's inner text.
205
+ * @param {string} label - What to name the file in an error.
206
+ * @returns {object[]}
207
+ */
208
+ function parseNodes(body, label) {
209
+ const nodes = [];
210
+ let plainStart = 0;
211
+ let index = 0;
212
+
213
+ const pushPlain = (end) => {
214
+ if (end > plainStart) {
215
+ nodes.push({ type: 'text', raw: body.slice(plainStart, end) });
216
+ }
217
+ };
218
+
219
+ while (index < body.length) {
220
+ const skip = skipNonCode(body, index);
221
+ if (skip !== -1) {
222
+ index = skip;
223
+ continue;
224
+ }
225
+
226
+ // Keywords only count at a token boundary: `isMatching(` is not a block.
227
+ if (/[A-Za-z0-9_]/.test(body[index - 1] || '')) {
228
+ index++;
229
+ continue;
230
+ }
231
+
232
+ const rest = body.slice(index);
233
+
234
+ // A match PATH carries braces of its own (`/users/{uid}`), so it is spelled
235
+ // out segment by segment rather than "everything up to the first {".
236
+ const block = /^match\s+((?:\/(?:[A-Za-z0-9_.$~%-]+|\{[A-Za-z0-9_]+(?:=\*\*)?\}))+)\s*\{/.exec(rest)
237
+ || /^function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*\{/.exec(rest);
238
+ if (block) {
239
+ const isMatch = block[0].startsWith('match');
240
+ const open = index + block[0].length - 1;
241
+ const close = findClosingBrace(body, open, label);
242
+
243
+ pushPlain(index);
244
+ nodes.push({
245
+ type: isMatch ? 'match' : 'function',
246
+ path: isMatch ? block[1] : null,
247
+ name: isMatch ? null : block[1],
248
+ inner: body.slice(open + 1, close),
249
+ indent: lineIndent(body, index),
250
+ raw: body.slice(index, close + 1),
251
+ });
252
+ index = close + 1;
253
+ plainStart = index;
254
+ continue;
255
+ }
256
+
257
+ const allow = /^allow\s+([a-z]+(?:\s*,\s*[a-z]+)*)\s*:\s*if\s/.exec(rest);
258
+ if (allow) {
259
+ const conditionStart = index + allow[0].length;
260
+ const end = findStatementEnd(body, conditionStart, label);
261
+
262
+ pushPlain(index);
263
+ nodes.push({
264
+ type: 'allow',
265
+ ops: allow[1].split(',').map((op) => op.trim()),
266
+ condition: body.slice(conditionStart, end).trim(),
267
+ indent: lineIndent(body, index),
268
+ raw: body.slice(index, end + 1),
269
+ });
270
+ index = end + 1;
271
+ plainStart = index;
272
+ continue;
273
+ }
274
+
275
+ index++;
276
+ }
277
+
278
+ pushPlain(body.length);
279
+
280
+ return nodes;
281
+ }
282
+
283
+ /**
284
+ * Index of the `;` ending the statement that starts at `index`.
285
+ * @param {string} source
286
+ * @param {number} index
287
+ * @param {string} label - What to name the file in an error.
288
+ * @returns {number}
289
+ */
290
+ function findStatementEnd(source, index, label) {
291
+ for (let i = index; i < source.length; i++) {
292
+ const skip = skipNonCode(source, i);
293
+ if (skip !== -1) {
294
+ i = skip - 1;
295
+ continue;
296
+ }
297
+ if (source[i] === ';') {
298
+ return i;
299
+ }
300
+ }
301
+
302
+ throw new Error(`${label}: an \`allow\` statement at offset ${index} is never terminated with \`;\``);
303
+ }
304
+
305
+ /**
306
+ * The indentation of the line `index` sits on, or '' when code precedes it.
307
+ * @param {string} source
308
+ * @param {number} index
309
+ * @returns {string}
310
+ */
311
+ function lineIndent(source, index) {
312
+ const start = source.lastIndexOf('\n', index - 1) + 1;
313
+ const before = source.slice(start, index);
314
+
315
+ return /^\s*$/.test(before) ? before : '';
316
+ }
317
+
318
+ /**
319
+ * A match path with its wildcard NAMES normalized away, so `/users/{uid}` and
320
+ * `/users/{userId}` are the same block — and `/{doc=**}` (recursive) stays
321
+ * distinct from `/{doc}` (one segment).
322
+ * @param {string} matchPath
323
+ * @returns {string}
324
+ */
325
+ function canonicalPath(matchPath) {
326
+ return matchPath.trim().replace(/\{[A-Za-z0-9_]+(=\*\*)?\}/g, (whole, recursive) => (recursive ? '{**}' : '{*}'));
327
+ }
328
+
329
+ /**
330
+ * The wildcard variable names of a match path, in order.
331
+ * @param {string} matchPath
332
+ * @returns {string[]}
333
+ */
334
+ function wildcardNames(matchPath) {
335
+ return [...matchPath.matchAll(/\{([A-Za-z0-9_]+)(?:=\*\*)?\}/g)].map((match) => match[1]);
336
+ }
337
+
338
+ /**
339
+ * Rename identifiers in rules text, skipping comments and string literals —
340
+ * how a brand's `{userId}` becomes the framework's `{uid}` inside the merged
341
+ * conditions.
342
+ * @param {string} text
343
+ * @param {object} renames - `{ from: to }`.
344
+ * @returns {string}
345
+ */
346
+ function renameIdentifiers(text, renames) {
347
+ const names = Object.keys(renames);
348
+ if (!names.length) {
349
+ return text;
350
+ }
351
+
352
+ const identifier = /[A-Za-z0-9_]/;
353
+ let output = '';
354
+ let index = 0;
355
+
356
+ while (index < text.length) {
357
+ const skip = skipNonCode(text, index);
358
+ if (skip !== -1) {
359
+ output += text.slice(index, skip);
360
+ index = skip;
361
+ continue;
362
+ }
363
+
364
+ const rest = text.slice(index);
365
+ const word = /^[A-Za-z_][A-Za-z0-9_]*/.exec(rest);
366
+ if (word && !identifier.test(text[index - 1] || '')) {
367
+ output += Object.prototype.hasOwnProperty.call(renames, word[0]) ? renames[word[0]] : word[0];
368
+ index += word[0].length;
369
+ continue;
370
+ }
371
+
372
+ output += text[index];
373
+ index++;
374
+ }
375
+
376
+ return output;
377
+ }
378
+
379
+ /**
380
+ * Shift every line of `text` from one indentation to another, so a brand's
381
+ * block reads correctly once it lands inside the framework's.
382
+ * @param {string} text
383
+ * @param {string} from
384
+ * @param {string} to
385
+ * @returns {string}
386
+ */
387
+ function reindent(text, from, to) {
388
+ if (from === to) {
389
+ return text;
390
+ }
391
+
392
+ return text
393
+ .split('\n')
394
+ .map((line) => {
395
+ if (!line.trim()) {
396
+ return '';
397
+ }
398
+ return line.startsWith(from) ? to + line.slice(from.length) : to + line.trim();
399
+ })
400
+ .join('\n');
401
+ }
402
+
403
+ /**
404
+ * Merge ONE brand match block into the framework block it matches.
405
+ *
406
+ * Shared op → the brand's condition is parenthesized and ANDed onto the
407
+ * framework's (the tighten lane; a framework statement listing several ops
408
+ * splits only as far as it must). Brand-only op → appended verbatim (that op
409
+ * keeps today's sibling-OR widening). Anything else in the brand block (nested
410
+ * matches, its own functions) splices in as written.
411
+ *
412
+ * The result comes back in PARTS rather than re-found in the merged text: the
413
+ * marker comment quotes the brand's path, braces and all, so nothing
414
+ * downstream can locate the block's own brace by scanning for one.
415
+ * @param {object} frameworkNode - A parsed framework `match` node, carrying the
416
+ * `markers` and `label` any earlier merge into it left behind.
417
+ * @param {object} brandNode - The parsed brand `match` node merging into it.
418
+ * @param {string[]} warnings - Collector for ops that widen instead of tighten.
419
+ * @returns {{ raw: string, inner: string, markers: string[] }}
420
+ */
421
+ function mergeMatchBlock(frameworkNode, brandNode, warnings) {
422
+ const label = `${BRAND_RULES_FILE} match ${brandNode.path}`;
423
+ const renames = {};
424
+ const brandWildcards = wildcardNames(brandNode.path);
425
+
426
+ wildcardNames(frameworkNode.path).forEach((name, position) => {
427
+ if (brandWildcards[position] && brandWildcards[position] !== name) {
428
+ renames[brandWildcards[position]] = name;
429
+ }
430
+ });
431
+
432
+ const brandChildren = parseNodes(renameIdentifiers(brandNode.inner, renames), label);
433
+ const frameworkChildren = parseNodes(frameworkNode.inner, frameworkNode.label);
434
+ const frameworkOps = new Set(frameworkChildren.filter((node) => node.type === 'allow').flatMap((node) => node.ops));
435
+
436
+ // What the brand asks of each op it names.
437
+ const brandConditions = new Map();
438
+ for (const node of brandChildren) {
439
+ if (node.type === 'allow') {
440
+ for (const op of node.ops) {
441
+ brandConditions.set(op, node.condition);
442
+ }
443
+ }
444
+ }
445
+
446
+ const childIndent = frameworkChildren.find((node) => node.type === 'allow')?.indent || `${frameworkNode.indent} `;
447
+ let inner = '';
448
+
449
+ for (const node of frameworkChildren) {
450
+ if (node.type !== 'allow' || !node.ops.some((op) => brandConditions.has(op))) {
451
+ inner += node.raw;
452
+ continue;
453
+ }
454
+
455
+ // One statement per distinct brand condition, ops in their original order,
456
+ // so `allow create, update` splits only when the brand tightens just one.
457
+ const groups = [];
458
+ for (const op of node.ops) {
459
+ const condition = brandConditions.get(op) || null;
460
+ const group = groups.find((entry) => entry.condition === condition);
461
+ if (group) {
462
+ group.ops.push(op);
463
+ } else {
464
+ groups.push({ condition, ops: [op] });
465
+ }
466
+ }
467
+
468
+ inner += groups
469
+ .map(({ condition, ops }) => `allow ${ops.join(', ')}: if ${node.condition}${condition ? ` && (${condition})` : ''};`)
470
+ .join(`\n${node.indent}`);
471
+ }
472
+
473
+ // Everything the framework block has no opinion about, appended as written.
474
+ const appended = [];
475
+ for (const node of brandChildren) {
476
+ if (node.type === 'allow') {
477
+ const ops = node.ops.filter((op) => !frameworkOps.has(op));
478
+ if (!ops.length) {
479
+ continue;
480
+ }
481
+ for (const op of ops) {
482
+ // EVERY framework op the brand op covers, in the framework's own
483
+ // order: `write` beside `allow create, update` overlaps both, and a
484
+ // remedy naming only the first would tighten half the rule.
485
+ const overlapping = [...frameworkOps].filter((frameworkOp) => primitiveOps(frameworkOp).some((primitive) => primitiveOps(op).includes(primitive)));
486
+ if (overlapping.length) {
487
+ const named = overlapping.join(', ');
488
+ warnings.push(`${BRAND_RULES_FILE}: in \`match ${brandNode.path}\`, your \`allow ${op}\` overlaps the framework's \`allow ${named}\` without being the same op — merging pairs ops by NAME, so Firestore ORs these two: your condition GRANTS ${op} on top of the framework rule instead of tightening it. Deliberate? Fine. Meant to tighten? Write \`allow ${named}\`.`);
489
+ }
490
+ }
491
+ appended.push(`${childIndent}allow ${ops.join(', ')}: if ${node.condition};`);
492
+ continue;
493
+ }
494
+ if (node.type === 'match' || node.type === 'function') {
495
+ // A block of its own keeps the air it had in the brand file.
496
+ appended.push(`\n${childIndent}${reindent(node.raw, node.indent, childIndent).trimStart()}`);
497
+ }
498
+ }
499
+
500
+ if (appended.length) {
501
+ inner = `${inner.replace(/\s+$/, '')}\n\n${childIndent}// ⤷ from your ${BRAND_RULES_FILE}\n${appended.join('\n')}\n${frameworkNode.indent}`;
502
+ }
503
+
504
+ // The text node ahead of this one already carries the block's indentation, so
505
+ // the FIRST marker line opens flush and every line after it is indented. The
506
+ // markers accumulate: a second brand block on this path adds its own, and the
507
+ // artifact keeps a line per merge.
508
+ const markers = [...frameworkNode.markers, `// ⤷ merged with your ${BRAND_RULES_FILE} \`match ${brandNode.path}\``];
509
+
510
+ return {
511
+ raw: `${markers.join(`\n${frameworkNode.indent}`)}\n${frameworkNode.indent}match ${frameworkNode.path} {${inner}}`,
512
+ inner,
513
+ markers,
514
+ };
515
+ }
516
+
517
+ /**
518
+ * An `allow` op in primitive terms (`write` → create/update/delete).
519
+ * @param {string} op
520
+ * @returns {string[]}
521
+ */
522
+ function primitiveOps(op) {
523
+ return OP_ALIASES[op] || [op];
524
+ }
525
+
526
+ /**
527
+ * The compiled artifact's header — the ONE place a reader learns where the
528
+ * two sources live and which one to edit (Ian's clarity requirement, #255).
529
+ * @returns {string}
530
+ */
531
+ function compiledHeader() {
532
+ return [
533
+ '// ============================================================================',
534
+ '// GENERATED FILE — DO NOT EDIT.',
535
+ '//',
536
+ `// Compiled by @omega.js/backend (rules schema v${RULES_VERSION}) from TWO sources:`,
537
+ '//',
538
+ `// 1. YOUR rules ./${BRAND_RULES_FILE}`,
539
+ '// ^ EDIT THAT FILE — everything a brand owns lives there.',
540
+ '// 2. Framework rules ./node_modules/@omega.js/backend/templates/firestore.framework.rules',
541
+ '// ^ ships inside @omega.js/backend; upgrade the package to change it.',
542
+ '//',
543
+ '// A match block of yours whose path matches a framework block is MERGED into',
544
+ '// it (your condition ANDs onto the framework\'s) — look for the `⤷ merged`',
545
+ '// markers below to see what your file did to a framework rule.',
546
+ '//',
547
+ '// Rebuilt by every `omega build` (and so by setup, emulator, serve, test and',
548
+ '// deploy). firebase.json points BOTH the emulator and `firebase deploy` at',
549
+ '// this file, and every edit made here is overwritten on the next build.',
550
+ '// ============================================================================',
551
+ ].join('\n');
552
+ }
553
+
554
+ /**
555
+ * Compile the framework half + a brand half into one deployable ruleset.
556
+ *
557
+ * Pure string work — no disk writes, so the tests can compile any brand source
558
+ * they like.
559
+ *
560
+ * An UNMIGRATED (legacy marker-block) source throws: its managed block still
561
+ * carries the framework's own functions, so splicing it in would emit every one
562
+ * of them twice — a ruleset Firestore refuses to load. There is no valid
563
+ * artifact to produce from that input, so nothing pretends otherwise.
564
+ * @param {object} options
565
+ * @param {string} options.brandSource - The brand's whole firestore.rules.
566
+ * @returns {{ compiled: string, merged: string[], warnings: string[] }}
567
+ */
568
+ function compileRules(options) {
569
+ if (isPreFamilyMarkerFile(options.brandSource)) {
570
+ throw new Error(`${BRAND_RULES_FILE} still carries a pre-family marker ('{{ backend-manager }}' or a '///---...---///' block), which is not rules syntax — compiling it would splice that marker into the deployed ruleset as if it were one of your rules. Run \`${MARKER_MIGRATION_COMMAND}\` to convert it (your custom rules are kept).`);
571
+ }
572
+
573
+ if (isLegacyMarkerFile(options.brandSource)) {
574
+ throw new Error(`${BRAND_RULES_FILE} still carries the legacy OMEGA Rules marker block, which redefines the framework's own functions — compiling it would emit a duplicate-function ruleset that cannot load. Run \`npx omega migrate:rules\` to migrate it (your custom rules are kept).`);
575
+ }
576
+
577
+ const framework = jetpack.read(FRAMEWORK_RULES_TEMPLATE);
578
+ const brandNodes = parseNodes(extractDocumentsBody(options.brandSource, BRAND_RULES_FILE), BRAND_RULES_FILE);
579
+ const frameworkNodes = parseNodes(extractDocumentsBody(framework, 'firestore.framework.rules'), 'firestore.framework.rules');
580
+
581
+ const merged = [];
582
+ const warnings = [];
583
+ const frameworkBlocks = new Map();
584
+ for (const node of frameworkNodes) {
585
+ if (node.type === 'match') {
586
+ // What a merge into this block accumulates: its marker lines, and the
587
+ // name an error in its text should carry once it is no longer purely the
588
+ // framework's.
589
+ node.markers = [];
590
+ node.label = 'firestore.framework.rules';
591
+ frameworkBlocks.set(canonicalPath(node.path), node);
592
+ }
593
+ }
594
+
595
+ let brandRendered = '';
596
+ for (const node of brandNodes) {
597
+ const target = node.type === 'match' ? frameworkBlocks.get(canonicalPath(node.path)) : null;
598
+ if (!target) {
599
+ brandRendered += node.raw;
600
+ continue;
601
+ }
602
+
603
+ // Merged in place: a second brand block on the same path tightens what the
604
+ // first one already produced, so the merged parts go back on the node as
605
+ // they were built — the text is no longer safe to re-split on a brace.
606
+ const rewritten = mergeMatchBlock(target, node, warnings);
607
+ target.raw = rewritten.raw;
608
+ target.inner = rewritten.inner;
609
+ target.markers = rewritten.markers;
610
+ target.label = `the merged \`match ${target.path}\` block (firestore.framework.rules + ${BRAND_RULES_FILE})`;
611
+ merged.push(target.path);
612
+ }
613
+
614
+ const compiled = [
615
+ compiledHeader(),
616
+ 'rules_version = \'2\';',
617
+ 'service cloud.firestore {',
618
+ ' match /databases/{database}/documents {',
619
+ ' // ─── Brand rules (from firestore.rules) ───────────────────────────────',
620
+ collapseBlankRuns(trimBlankEdges(brandRendered)),
621
+ '',
622
+ ' // ─── Framework rules (@omega.js/backend) ──────────────────────────────',
623
+ collapseBlankRuns(trimBlankEdges(frameworkNodes.map((node) => node.raw).join(''))),
624
+ ' }',
625
+ '}',
626
+ '',
627
+ ].join('\n');
628
+
629
+ return { compiled, merged, warnings };
630
+ }
631
+
632
+ /**
633
+ * Compile and write `dist/firestore.rules` for a consumer app.
634
+ *
635
+ * Writes the STAGED tree only — never the brand's authored file (that is
636
+ * `ensureBrandRulesSource`'s job). A brand with no source file yet (first
637
+ * setup, before the seed lands) compiles against the seed so the emulator
638
+ * still boots.
639
+ *
640
+ * An UNMIGRATED source is reported and SKIPPED rather than thrown on: the
641
+ * stage runs before setup's checks do, so a throw here would kill the very run
642
+ * that migrates the file. Skipping leaves no artifact for firebase.json to
643
+ * name, which fails loudly at the next surface that wants one — and the build
644
+ * still never writes the brand's authored file (#255).
645
+ * @param {object} options
646
+ * @param {string} options.projectDir - The target root.
647
+ * @param {function} [options.onWarn] - Loud reporter (defaults to a yellow console line).
648
+ * @returns {{ compiledPath: string, merged: string[], seededFallback: boolean, refused: boolean }}
649
+ */
650
+ function compileFirestoreRules(options) {
651
+ const projectDir = options.projectDir;
652
+ const onWarn = options.onWarn || defaultWarn;
653
+ const sourcePath = path.join(projectDir, BRAND_RULES_FILE);
654
+ const compiledPath = path.join(projectDir, COMPILED_RULES_FILE);
655
+
656
+ const seededFallback = !jetpack.exists(sourcePath);
657
+ if (seededFallback) {
658
+ onWarn(`No ${BRAND_RULES_FILE} at the target root — compiling the framework half against the shipped defaults. Any verb seeds your own.`);
659
+ }
660
+
661
+ const brandSource = seededFallback ? jetpack.read(BRAND_RULES_SEED) : jetpack.read(sourcePath);
662
+
663
+ // Which command actually moves this tree forward: the target checks
664
+ // (`omega test`) migrate a source whose brand already deploys the compiled
665
+ // artifact, but a brand still pointing firebase.json at its own file has
666
+ // deferred, and only the run-alone verb touches it
667
+ // ([#522](https://github.com/Omega-JS-Stack/omega/issues/522)).
668
+ const deferred = deferredRulesTarget({ projectDir });
669
+ const nextStep = deferred ? RULES_MIGRATION_COMMAND : 'npx omega test';
670
+
671
+ if (isPreFamilyMarkerFile(brandSource)) {
672
+ onWarn(`${BRAND_RULES_FILE} still carries a pre-family marker ('{{ backend-manager }}' or a '///---...---///' block), which is not rules syntax — refusing to write a ${COMPILED_RULES_FILE} that carries that marker as if it were one of your rules. Run \`${MARKER_MIGRATION_COMMAND}\` to convert it (your custom rules are kept).`);
673
+
674
+ return { compiledPath, merged: [], seededFallback, refused: true };
675
+ }
676
+
677
+ if (isLegacyMarkerFile(brandSource)) {
678
+ onWarn(`${BRAND_RULES_FILE} still carries the legacy OMEGA Rules marker block, which redefines the framework's own functions — refusing to write a duplicate-function ${COMPILED_RULES_FILE} that cannot load. Run \`${nextStep}\` to migrate it (your custom rules are kept).`);
679
+
680
+ return { compiledPath, merged: [], seededFallback, refused: true };
681
+ }
682
+
683
+ const { compiled, merged, warnings } = compileRules({ brandSource });
684
+
685
+ for (const warning of warnings) {
686
+ onWarn(warning);
687
+ }
688
+
689
+ jetpack.write(compiledPath, compiled);
690
+
691
+ // The artifact is worthless if nothing reads it. A pre-#255 firebase.json
692
+ // still names the brand SOURCE, which would boot the emulator (and deploy)
693
+ // on the brand half alone — every framework rule silently gone. The build
694
+ // does not rewrite authored config (that is setup's job), but it refuses to
695
+ // be quiet about it.
696
+ const firebaseJSON = jetpack.read(path.join(projectDir, 'firebase.json'), 'json');
697
+ const target = firebaseJSON?.firestore?.rules;
698
+ if (firebaseJSON && target !== COMPILED_RULES_FILE) {
699
+ onWarn(`firebase.json points firestore.rules at "${target}" — it must be "${COMPILED_RULES_FILE}" (the compiled artifact) or the framework rules never load. Run \`${nextStep}\`.`);
700
+ }
701
+
702
+ return { compiledPath, merged, seededFallback, refused: false };
703
+ }
704
+
705
+ /**
706
+ * Seed / migrate the brand's AUTHORED firestore.rules.
707
+ *
708
+ * All of it idempotent, and all of it setup's (never the build's — the build
709
+ * writes dist/ only):
710
+ * - no file → write the seed
711
+ * - pre-family → REFUSED, untouched (only `omega migrate:markers` speaks it)
712
+ * - legacy markers → extract the non-managed region into the new source ONCE
713
+ * - v2 hook era → delete a hook still carrying its default body, KEEP a
714
+ * customized one as an ordinary brand function, and
715
+ * refresh a header that still documents the hook model
716
+ * @param {object} options
717
+ * @param {string} options.projectDir - The target root.
718
+ * @returns {{ created: boolean, migrated: boolean, refused: boolean, strippedHooks: string[], keptHooks: string[] }}
719
+ */
720
+ function ensureBrandRulesSource(options) {
721
+ const projectDir = options.projectDir;
722
+ const sourcePath = path.join(projectDir, BRAND_RULES_FILE);
723
+ const seed = jetpack.read(BRAND_RULES_SEED);
724
+ const result = { created: false, migrated: false, refused: false, strippedHooks: [], keptHooks: [] };
725
+
726
+ let contents = jetpack.exists(sourcePath) ? jetpack.read(sourcePath) : '';
727
+
728
+ if (!contents.trim()) {
729
+ jetpack.write(sourcePath, seed);
730
+
731
+ return { ...result, created: true };
732
+ }
733
+
734
+ // A PRE-FAMILY source is not a legacy-marker one and not a v2 one: nothing
735
+ // below speaks `///---backend-manager---///` or `{{ backend-manager }}`, so
736
+ // the hook-era pass would rename its helpers, report a MIGRATION, and leave
737
+ // every pre-family marker sitting in the file. Refuse instead — the same
738
+ // shape the build's refusal takes, and the caller names the run-alone verb
739
+ // that does speak it ([#40](https://github.com/Omega-JS-Stack/omega/issues/40)).
740
+ if (isPreFamilyMarkerFile(contents)) {
741
+ return { ...result, refused: true };
742
+ }
743
+
744
+ if (isLegacyMarkerFile(contents)) {
745
+ contents = migrateLegacyMarkerFile(contents);
746
+ result.migrated = true;
747
+ }
748
+
749
+ if (hasRetiredHookEra(contents)) {
750
+ const retired = retireHooks(contents, seed);
751
+
752
+ result.migrated = result.migrated || retired.contents !== contents;
753
+ result.strippedHooks = retired.stripped;
754
+ result.keptHooks = retired.kept;
755
+ contents = retired.contents;
756
+ }
757
+
758
+ if (result.migrated) {
759
+ jetpack.write(sourcePath, contents);
760
+ }
761
+
762
+ return result;
763
+ }
764
+
765
+ /**
766
+ * A pre-#255 rules file: the framework's marker block regenerated in place.
767
+ * @param {string} contents
768
+ * @returns {boolean}
769
+ */
770
+ function isLegacyMarkerFile(contents) {
771
+ // Fresh regex per call — the shared one is /g and carries lastIndex.
772
+ return /\/\/ ========== OMEGA Rules \(v.*?\) ==========/.test(contents);
773
+ }
774
+
775
+ /**
776
+ * BEM's hand-written insertion placeholder, whitespace-tolerant exactly as BEM
777
+ * matched it. Fresh regex per call — these are /g for the replace paths.
778
+ * @returns {RegExp}
779
+ */
780
+ function preFamilyPlaceholderRegex() {
781
+ return /{{\s*?backend-manager\s*?}}/g;
782
+ }
783
+
784
+ /**
785
+ * The pre-family managed BLOCK, open marker through end marker: BEM's own
786
+ * `///---backend-manager---///` and the cp72-74 `///---omega---///` interim
787
+ * flavor, which differ only in that word.
788
+ * @returns {RegExp}
789
+ */
790
+ function preFamilyBlockRegex() {
791
+ return /\/\/\/---(?:backend-manager|omega)---\/\/\/[\s\S]*?\/\/\/---------end---------\/\/\//g;
792
+ }
793
+
794
+ /**
795
+ * A PRE-FAMILY marker file — one the family grammar cannot see at all, so no
796
+ * evergreen verb can converge it ([#40](https://github.com/Omega-JS-Stack/omega/issues/40)).
797
+ *
798
+ * Both rules files ask here: firestore.rules and database.rules.json carried the
799
+ * same two shapes (the placeholder, and the bracket block).
800
+ * @param {string} contents
801
+ * @returns {boolean}
802
+ */
803
+ function isPreFamilyMarkerFile(contents) {
804
+ return preFamilyPlaceholderRegex().test(contents) || preFamilyBlockRegex().test(contents);
805
+ }
806
+
807
+ /**
808
+ * A v2 (hook-era) rules file: it still carries something the migration would
809
+ * CHANGE — a strippable hook, a call to a helper v3 renamed, or a header that
810
+ * still teaches a retired name.
811
+ *
812
+ * Only what the migration changes counts, so the question settles: a KEPT
813
+ * (customized or still-called) hook is the brand's own function afterwards, and
814
+ * asking about it forever would re-report and re-warn every setter-up. Narrow
815
+ * on purpose too — a brand's own comment naming an old symbol elsewhere is the
816
+ * brand's business.
817
+ * @param {string} contents
818
+ * @returns {boolean}
819
+ */
820
+ function hasRetiredHookEra(contents) {
821
+ if (Object.keys(RETIRED_HOOKS).some((hook) => findRetiredHook(contents, hook)?.strippable)) {
822
+ return true;
823
+ }
824
+
825
+ if (Object.keys(RENAMED_HELPERS).some((name) => referencesName(contents, name))) {
826
+ return true;
827
+ }
828
+
829
+ const header = leadingCommentBlock(contents);
830
+
831
+ return !!header && RETIRED_NAMES.some((name) => new RegExp(`\\b${name}\\b`).test(contents.slice(0, header.end)));
832
+ }
833
+
834
+ /**
835
+ * Does this brand source need setup to migrate it? The ONE place that question
836
+ * is answered — setup's check and its fix both ask here.
837
+ * @param {string} contents - The brand's whole firestore.rules.
838
+ * @returns {boolean}
839
+ */
840
+ function needsRulesMigration(contents) {
841
+ return !contents.trim() || isPreFamilyMarkerFile(contents) || isLegacyMarkerFile(contents) || hasRetiredHookEra(contents);
842
+ }
843
+
844
+ /**
845
+ * Convert a legacy marker-block file into the new source: drop the managed
846
+ * block, keep everything the brand wrote, and land it in the seed's shape.
847
+ * Runs ONCE — the result carries no markers.
848
+ * @param {string} contents - The legacy file.
849
+ * @returns {string} The new brand source.
850
+ */
851
+ function migrateLegacyMarkerFile(contents) {
852
+ return spliceIntoSeed(contents.replace(/\/\/ ========== OMEGA Rules \(v.*?\) ==========.*?\/\/ ========== End OMEGA Rules ==========/s, ''));
853
+ }
854
+
855
+ /**
856
+ * Convert a PRE-FAMILY firestore.rules — a `///---backend-manager---///` /
857
+ * `///---omega---///` managed block, or the bare `{{ backend-manager }}`
858
+ * placeholder — into the new source, the same three steps
859
+ * `migrateLegacyMarkerFile()` takes: only the markers stripped differ. Runs
860
+ * ONCE ([#40](https://github.com/Omega-JS-Stack/omega/issues/40)).
861
+ * @param {string} contents - The pre-family file.
862
+ * @returns {string} The new brand source.
863
+ */
864
+ function migratePreFamilyMarkerFile(contents) {
865
+ return spliceIntoSeed(contents
866
+ .replace(preFamilyBlockRegex(), '')
867
+ .replace(preFamilyPlaceholderRegex(), ''));
868
+ }
869
+
870
+ /**
871
+ * The tail every rules-marker migration shares: take what the brand wrote out of
872
+ * a now-markerless file and land it in the seed's example-rules slot.
873
+ * @param {string} stripped - The file with its managed block/placeholder gone.
874
+ * @returns {string} The new brand source.
875
+ */
876
+ function spliceIntoSeed(stripped) {
877
+ const seed = jetpack.read(BRAND_RULES_SEED);
878
+ const custom = trimBlankEdges(extractDocumentsBody(stripped, BRAND_RULES_FILE));
879
+
880
+ if (!seed.includes(SEED_RULES_PLACEHOLDER)) {
881
+ // The placeholder is a literal block of templates/firestore.rules, in this
882
+ // same package — drift here is a framework bug, never a consumer's.
883
+ throw new Error(`templates/${BRAND_RULES_FILE}: the example-rules placeholder the migration replaces is gone`);
884
+ }
885
+
886
+ // Function replacement, never a replacement STRING: brand-authored rules are
887
+ // arbitrary text, and `$$` / `$&` / `` $` `` / `$'` in a replacement string
888
+ // are directives that would rewrite the brand's own bytes on the way in.
889
+ return seed.replace(SEED_RULES_PLACEHOLDER, () => (custom.trim() ? custom : SEED_RULES_PLACEHOLDER));
890
+ }
891
+
892
+ /**
893
+ * Retire the v2 hooks out of a brand source: a hook still carrying its shipped
894
+ * default body is deleted outright, a customized one is KEPT as an ordinary
895
+ * brand function (nothing calls it now, which is why the caller reports it),
896
+ * and a header that still teaches the hook model is replaced with the seed's.
897
+ * @param {string} contents - The brand's whole firestore.rules.
898
+ * @param {string} seed - templates/firestore.rules.
899
+ * @returns {{ contents: string, stripped: string[], kept: string[] }}
900
+ */
901
+ function retireHooks(contents, seed) {
902
+ const stripped = [];
903
+ const kept = [];
904
+ let output = contents;
905
+
906
+ for (const hook of Object.keys(RETIRED_HOOKS)) {
907
+ const found = findRetiredHook(output, hook);
908
+ if (!found) {
909
+ continue;
910
+ }
911
+
912
+ const { block, strippable } = found;
913
+ if (strippable) {
914
+ output = `${output.slice(0, block.start)}${output.slice(block.end)}`;
915
+ stripped.push(hook);
916
+ } else {
917
+ // The brand's own code: keep the function, drop the doc comment that
918
+ // describes a contract nothing honors any more.
919
+ output = [
920
+ output.slice(0, block.start),
921
+ `${block.indent}// Kept from the retired \`${hook}()\` framework hook (rules v2): nothing\n`,
922
+ `${block.indent}// calls it now — call it from your own match block, or delete it.\n`,
923
+ output.slice(block.definitionStart, block.end),
924
+ output.slice(block.end),
925
+ ].join('');
926
+ kept.push(hook);
927
+ }
928
+ }
929
+
930
+ // Calls to the helpers v3 renamed — including from inside a hook body kept
931
+ // above. A rules file that calls a function no half defines does not load.
932
+ output = renameIdentifiers(output, RENAMED_HELPERS);
933
+
934
+ // The hook-era section header, left dangling once its functions are gone.
935
+ output = output.replace(/[^\S\n]*\/\/ ─+ Framework hooks ─+\n(?:[^\S\n]*\/\/[^\n]*\n)*/g, '');
936
+
937
+
938
+ // The file's opening header is OURS (every era's seed shipped one). One that
939
+ // still teaches names v3 retired is replaced wholesale with the current seed's.
940
+ const header = leadingCommentBlock(output);
941
+ const seedHeader = leadingCommentBlock(seed);
942
+ if (header && seedHeader && RETIRED_NAMES.some((name) => new RegExp(`\\b${name}\\b`).test(output.slice(0, header.end)))) {
943
+ output = `${seed.slice(0, seedHeader.end)}${output.slice(header.end)}`;
944
+ }
945
+
946
+ // Collapse what the removals left: a run of blank lines, and the blank line
947
+ // a hook at the end of a block leaves in front of its closing brace.
948
+ return { contents: collapseBlankRuns(output).replace(/\n[^\S\n]*\n([^\S\n]*\})/g, '\n$1'), stripped, kept };
949
+ }
950
+
951
+ /**
952
+ * A retired hook as this file defines it, and whether the migration would DELETE
953
+ * it outright. Strippable means the shipped default body AND nothing calling it:
954
+ * a body the brand edited is its own work, and deleting a hook something still
955
+ * calls would emit a ruleset Firestore cannot load. The ONE place that verdict
956
+ * is reached — the migration acts on it, and `hasRetiredHookEra` asks it so the
957
+ * check settles once every strippable hook is gone.
958
+ * @param {string} source - The brand's whole firestore.rules.
959
+ * @param {string} hook - A key of RETIRED_HOOKS.
960
+ * @returns {?{ block: object, strippable: boolean }}
961
+ */
962
+ function findRetiredHook(source, hook) {
963
+ const block = findFunctionBlock(source, hook);
964
+ if (!block) {
965
+ return null;
966
+ }
967
+
968
+ const called = referencesName(`${source.slice(0, block.start)}${source.slice(block.end)}`, hook);
969
+
970
+ return { block, strippable: !called && block.body.replace(/\s+/g, ' ').trim() === RETIRED_HOOKS[hook] };
971
+ }
972
+
973
+ /**
974
+ * A function definition plus the contiguous `//` comment lines above it.
975
+ * @param {string} source
976
+ * @param {string} name
977
+ * @returns {?{ start: number, definitionStart: number, end: number, body: string, indent: string }}
978
+ */
979
+ function findFunctionBlock(source, name) {
980
+ const match = new RegExp(`function\\s+${name}\\s*\\([^)]*\\)\\s*\\{`).exec(source);
981
+ if (!match) {
982
+ return null;
983
+ }
984
+
985
+ const open = match.index + match[0].length - 1;
986
+ const close = findClosingBrace(source, open, BRAND_RULES_FILE);
987
+ const definitionStart = source.lastIndexOf('\n', match.index) + 1;
988
+
989
+ // Walk back over the comment lines introducing it, so a delete takes the
990
+ // hook's docs with it.
991
+ let start = definitionStart;
992
+ while (start > 0) {
993
+ const previousStart = source.lastIndexOf('\n', start - 2) + 1;
994
+ if (!source.slice(previousStart, start - 1).trim().startsWith('//')) {
995
+ break;
996
+ }
997
+ start = previousStart;
998
+ }
999
+
1000
+ return {
1001
+ start,
1002
+ definitionStart,
1003
+ end: source[close + 1] === '\n' ? close + 2 : close + 1,
1004
+ body: source.slice(open + 1, close),
1005
+ indent: source.slice(definitionStart, match.index),
1006
+ };
1007
+ }
1008
+
1009
+ /**
1010
+ * Is `name` named by the CODE (never by a comment or a string) in this text?
1011
+ * @param {string} source
1012
+ * @param {string} name
1013
+ * @returns {boolean}
1014
+ */
1015
+ function referencesName(source, name) {
1016
+ let index = 0;
1017
+
1018
+ while (index < source.length) {
1019
+ const skip = skipNonCode(source, index);
1020
+ if (skip !== -1) {
1021
+ index = skip;
1022
+ continue;
1023
+ }
1024
+
1025
+ const word = /^[A-Za-z_][A-Za-z0-9_]*/.exec(source.slice(index));
1026
+ if (word) {
1027
+ if (word[0] === name) {
1028
+ return true;
1029
+ }
1030
+ index += word[0].length;
1031
+ continue;
1032
+ }
1033
+
1034
+ index++;
1035
+ }
1036
+
1037
+ return false;
1038
+ }
1039
+
1040
+ /**
1041
+ * The file's opening `//` comment block, if it has one.
1042
+ * @param {string} source
1043
+ * @returns {?{ end: number }}
1044
+ */
1045
+ function leadingCommentBlock(source) {
1046
+ const lines = source.split('\n');
1047
+ let end = 0;
1048
+
1049
+ for (const line of lines) {
1050
+ if (!line.trim().startsWith('//')) {
1051
+ break;
1052
+ }
1053
+ end += line.length + 1;
1054
+ }
1055
+
1056
+ return end ? { end } : null;
1057
+ }
1058
+
1059
+ /**
1060
+ * Drop leading/trailing blank lines, keeping indentation on the rest.
1061
+ * @param {string} text
1062
+ * @returns {string}
1063
+ */
1064
+ function trimBlankEdges(text) {
1065
+ return text.replace(/^(\s*\n)+/, '').replace(/(\n\s*)+$/, '');
1066
+ }
1067
+
1068
+ /**
1069
+ * Collapse a run of blank lines (what removing a block leaves behind) to one.
1070
+ * @param {string} text
1071
+ * @returns {string}
1072
+ */
1073
+ function collapseBlankRuns(text) {
1074
+ return text.replace(/\n[^\S\n]*\n(?:[^\S\n]*\n)+/g, '\n\n');
1075
+ }
1076
+
1077
+ /**
1078
+ * Has this brand DEFERRED the compiled-rules migration?
1079
+ *
1080
+ * A brand whose firebase.json still names its own rules file deploys the brand
1081
+ * half alone — the legacy posture, deliberately kept. Migrating it flips what
1082
+ * the live project enforces (the framework half joins, and `allow write`
1083
+ * becomes `allow create, update`), so it is a one-time step a human runs alone,
1084
+ * never something another verb heals on the way to a deploy
1085
+ * ([#522](https://github.com/Omega-JS-Stack/omega/issues/522)).
1086
+ *
1087
+ * A target naming nothing, or naming a file that is not there, has deferred
1088
+ * nothing: that is a fresh brand, and setup seeds it as it always has.
1089
+ * @param {object} options
1090
+ * @param {string} options.projectDir - The target root.
1091
+ * @param {object} [options.firebaseJSON] - The parsed firebase.json (read from disk when omitted).
1092
+ * @returns {string|null} The deferred target firebase.json names, or null.
1093
+ */
1094
+ function deferredRulesTarget(options) {
1095
+ const projectDir = options.projectDir;
1096
+ const firebaseJSON = options.firebaseJSON || jetpack.read(path.join(projectDir, 'firebase.json'), 'json');
1097
+ const target = firebaseJSON?.firestore?.rules;
1098
+
1099
+ if (!target || target === COMPILED_RULES_FILE) {
1100
+ return null;
1101
+ }
1102
+
1103
+ return jetpack.exists(path.join(projectDir, target)) === 'file' ? target : null;
1104
+ }
1105
+
1106
+ /**
1107
+ * The deferral, as setup reports it: what was NOT done, what migrating would
1108
+ * change on the live project, and the one command that does it.
1109
+ * @param {string} target - The rules file firebase.json still names.
1110
+ * @returns {string[]} Pre-formatted lines.
1111
+ */
1112
+ function rulesMigrationDeferralNotice(target) {
1113
+ return [
1114
+ `firebase.json still points firestore.rules at "${target}" — the compiled-rules migration is DEFERRED and nothing was changed.`,
1115
+ `Migrating rewrites your ${BRAND_RULES_FILE} and deploys ${COMPILED_RULES_FILE} (your rules + the framework half), which CHANGES LIVE POSTURE — a legacy \`allow write\` becomes \`allow create, update\`, so a client deleting its own doc flips allowed → denied.`,
1116
+ `Run it deliberately, on its own, once you have read the diff: ${RULES_MIGRATION_COMMAND}`,
1117
+ ];
1118
+ }
1119
+
1120
+ /**
1121
+ * The pre-family deferral, as setup reports it: what was NOT done, and the one
1122
+ * verb that does it. The twin of `rulesMigrationDeferralNotice()` above, shared
1123
+ * by both rules checks so they defer in identical words
1124
+ * ([#40](https://github.com/Omega-JS-Stack/omega/issues/40)).
1125
+ * @param {string} fileName - The file carrying the pre-family marker.
1126
+ * @returns {string[]} Pre-formatted lines.
1127
+ */
1128
+ function markerMigrationDeferralNotice(fileName) {
1129
+ return [
1130
+ `${fileName} still carries a pre-family marker ('{{ backend-manager }}' or a '///---...---///' block) — nothing here speaks it, so the file was left untouched.`,
1131
+ `Convert it once, on its own, then run this again: ${MARKER_MIGRATION_COMMAND}`,
1132
+ ];
1133
+ }
1134
+
1135
+ module.exports = {
1136
+ RULES_VERSION,
1137
+ BRAND_RULES_FILE,
1138
+ BRAND_RULES_SEED,
1139
+ COMPILED_RULES_FILE,
1140
+ FRAMEWORK_RULES_TEMPLATE,
1141
+ MARKER_MIGRATION_COMMAND,
1142
+ RETIRED_NAMES,
1143
+ RULES_MIGRATION_COMMAND,
1144
+ compileRules,
1145
+ compileFirestoreRules,
1146
+ deferredRulesTarget,
1147
+ ensureBrandRulesSource,
1148
+ extractDocumentsBody,
1149
+ isPreFamilyMarkerFile,
1150
+ markerMigrationDeferralNotice,
1151
+ migratePreFamilyMarkerFile,
1152
+ preFamilyPlaceholderRegex,
1153
+ needsRulesMigration,
1154
+ rulesMigrationDeferralNotice,
1155
+ };