@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.
- package/LICENSE +98 -0
- package/README.md +1053 -0
- package/bin/omega +2 -0
- package/bin/omega-backend +2 -0
- package/bin/omg +2 -0
- package/cli.js +3 -0
- package/dist/cli/command-table.js +268 -0
- package/dist/cli/commands/auth.js +259 -0
- package/dist/cli/commands/base-command.js +451 -0
- package/dist/cli/commands/build.js +27 -0
- package/dist/cli/commands/clean.js +13 -0
- package/dist/cli/commands/clear.js +11 -0
- package/dist/cli/commands/cwd.js +9 -0
- package/dist/cli/commands/deploy.js +205 -0
- package/dist/cli/commands/emulator-orphans.js +440 -0
- package/dist/cli/commands/emulator.js +1829 -0
- package/dist/cli/commands/firebase-init.js +101 -0
- package/dist/cli/commands/firestore.js +261 -0
- package/dist/cli/commands/indexes.js +51 -0
- package/dist/cli/commands/install.js +151 -0
- package/dist/cli/commands/logs.js +358 -0
- package/dist/cli/commands/mcp.js +41 -0
- package/dist/cli/commands/migrate-markers.js +176 -0
- package/dist/cli/commands/migrate-rules.js +90 -0
- package/dist/cli/commands/migrate.js +59 -0
- package/dist/cli/commands/serve.js +208 -0
- package/dist/cli/commands/setup-tests/base-test.js +174 -0
- package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
- package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
- package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
- package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
- package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
- package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
- package/dist/cli/commands/setup-tests/functions-package.js +44 -0
- package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
- package/dist/cli/commands/setup-tests/gitignore.js +122 -0
- package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
- package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
- package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
- package/dist/cli/commands/setup-tests/helpers.js +72 -0
- package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
- package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
- package/dist/cli/commands/setup-tests/index.js +111 -0
- package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
- package/dist/cli/commands/setup-tests/java-installed.js +27 -0
- package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
- package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
- package/dist/cli/commands/setup-tests/node-version.js +60 -0
- package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
- package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
- package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
- package/dist/cli/commands/setup-tests/omega-config.js +84 -0
- package/dist/cli/commands/setup-tests/project-directories.js +34 -0
- package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
- package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
- package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
- package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
- package/dist/cli/commands/setup-tests/service-account.js +86 -0
- package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
- package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
- package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
- package/dist/cli/commands/stripe.js +14 -0
- package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
- package/dist/cli/commands/test.js +976 -0
- package/dist/cli/commands/update.js +27 -0
- package/dist/cli/commands/version.js +10 -0
- package/dist/cli/commands/watch.js +196 -0
- package/dist/cli/flags.js +13 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/run.js +33 -0
- package/dist/cli/utils/attach-log-file.js +4 -0
- package/dist/cli/utils/compile-rules.js +1155 -0
- package/dist/cli/utils/ensure-target.js +314 -0
- package/dist/cli/utils/project-type.js +140 -0
- package/dist/cli/utils/public-files.js +49 -0
- package/dist/cli/utils/safe-install.js +4 -0
- package/dist/cli/utils/spawn-shell.js +94 -0
- package/dist/cli/utils/stage-functions.js +338 -0
- package/dist/cli/utils/stage-local-packages.js +210 -0
- package/dist/cli/utils/target-checks.js +161 -0
- package/dist/cli/utils/target.js +24 -0
- package/dist/cli/utils/ui.js +292 -0
- package/dist/defaults/AGENTS.md +125 -0
- package/dist/defaults/CHANGELOG.md +15 -0
- package/dist/defaults/CLAUDE.md +1 -0
- package/dist/defaults/_.gitignore +65 -0
- package/dist/defaults/docs/README.md +17 -0
- package/dist/defaults/test/README.md +65 -0
- package/dist/defaults/test/_init.js +14 -0
- package/dist/defaults/test/helpers/connect-trap.js +72 -0
- package/dist/defaults/test/unit/registration.test.js +143 -0
- package/dist/defaults/test/unit/rules-posture.test.js +101 -0
- package/dist/defaults/test/unit/socket-free.test.js +43 -0
- package/dist/manager/events/auth/before-create.js +89 -0
- package/dist/manager/events/auth/before-signin.js +76 -0
- package/dist/manager/events/auth/on-create.js +112 -0
- package/dist/manager/events/auth/on-delete.js +141 -0
- package/dist/manager/events/auth/utils.js +93 -0
- package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
- package/dist/manager/events/cron/daily/data-requests.js +57 -0
- package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
- package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
- package/dist/manager/events/cron/daily/reset-usage.js +188 -0
- package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
- package/dist/manager/events/cron/daily.js +3 -0
- package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
- package/dist/manager/events/cron/frequent/email-queue.js +71 -0
- package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
- package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
- package/dist/manager/events/cron/frequent.js +3 -0
- package/dist/manager/events/cron/runner.js +64 -0
- package/dist/manager/events/firestore/notifications/on-write.js +123 -0
- package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
- package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
- package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
- package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
- package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
- package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
- package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
- package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
- package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
- package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
- package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
- package/dist/manager/functions/_legacy/admin/query.js +198 -0
- package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
- package/dist/manager/functions/_legacy/template.js +33 -0
- package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
- package/dist/manager/functions/_legacy/test/webhook.js +37 -0
- package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
- package/dist/manager/helpers/analytics.js +498 -0
- package/dist/manager/helpers/api-manager.js +313 -0
- package/dist/manager/helpers/backend-router.js +37 -0
- package/dist/manager/helpers/context/authenticate.js +215 -0
- package/dist/manager/helpers/context/client-info.js +91 -0
- package/dist/manager/helpers/context/index.js +228 -0
- package/dist/manager/helpers/context/logging.js +150 -0
- package/dist/manager/helpers/context/parse.js +160 -0
- package/dist/manager/helpers/context/respond.js +247 -0
- package/dist/manager/helpers/event-middleware.js +109 -0
- package/dist/manager/helpers/metadata.js +32 -0
- package/dist/manager/helpers/middleware.js +541 -0
- package/dist/manager/helpers/redact-secret.js +25 -0
- package/dist/manager/helpers/resolved-config.js +34 -0
- package/dist/manager/helpers/roles.js +69 -0
- package/dist/manager/helpers/safe-compare.js +31 -0
- package/dist/manager/helpers/schema-engine.js +314 -0
- package/dist/manager/helpers/schema-zod.js +203 -0
- package/dist/manager/helpers/settings.js +215 -0
- package/dist/manager/helpers/usage.js +541 -0
- package/dist/manager/helpers/user.js +57 -0
- package/dist/manager/helpers/utilities.js +565 -0
- package/dist/manager/index.js +1404 -0
- package/dist/manager/libraries/abandoned-cart-config.js +12 -0
- package/dist/manager/libraries/ai/index.js +321 -0
- package/dist/manager/libraries/ai/prompt.js +85 -0
- package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
- package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
- package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
- package/dist/manager/libraries/ai/providers/openai.js +1272 -0
- package/dist/manager/libraries/ai/providers/test.js +257 -0
- package/dist/manager/libraries/ai/tokens.js +54 -0
- package/dist/manager/libraries/analytics/conversions.js +478 -0
- package/dist/manager/libraries/analytics/match-data.js +573 -0
- package/dist/manager/libraries/analytics/signup.js +120 -0
- package/dist/manager/libraries/auth-user.js +46 -0
- package/dist/manager/libraries/content/feed-parser.js +198 -0
- package/dist/manager/libraries/content/ghostii.js +191 -0
- package/dist/manager/libraries/content/source-resolver.js +629 -0
- package/dist/manager/libraries/email/constants.js +532 -0
- package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
- package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
- package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
- package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
- package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
- package/dist/manager/libraries/email/data/typo-domains.js +83 -0
- package/dist/manager/libraries/email/disposable-domains.js +92 -0
- package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
- package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
- package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
- package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
- package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
- package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
- package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
- package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
- package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
- package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
- package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
- package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
- package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
- package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
- package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
- package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
- package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
- package/dist/manager/libraries/email/generators/newsletter.js +899 -0
- package/dist/manager/libraries/email/index.js +146 -0
- package/dist/manager/libraries/email/marketing/index.js +754 -0
- package/dist/manager/libraries/email/prepare.js +365 -0
- package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
- package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
- package/dist/manager/libraries/email/transactional/index.js +510 -0
- package/dist/manager/libraries/email/utm.js +86 -0
- package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
- package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
- package/dist/manager/libraries/email/validation.js +255 -0
- package/dist/manager/libraries/env.js +198 -0
- package/dist/manager/libraries/infer-contact.js +101 -0
- package/dist/manager/libraries/load-provider.js +65 -0
- package/dist/manager/libraries/notification.js +256 -0
- package/dist/manager/libraries/openai.js +13 -0
- package/dist/manager/libraries/payment/discount-codes.js +204 -0
- package/dist/manager/libraries/payment/fetch-failure.js +56 -0
- package/dist/manager/libraries/payment/license.js +44 -0
- package/dist/manager/libraries/payment/order-id.js +57 -0
- package/dist/manager/libraries/payment/provider-errors.js +71 -0
- package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
- package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
- package/dist/manager/libraries/payment/providers/paypal.js +987 -0
- package/dist/manager/libraries/payment/providers/stripe.js +755 -0
- package/dist/manager/libraries/payment/providers/test.js +250 -0
- package/dist/manager/libraries/payment/refund-linkage.js +78 -0
- package/dist/manager/libraries/payment/refund-policy.js +113 -0
- package/dist/manager/libraries/payment/winback.js +55 -0
- package/dist/manager/libraries/prompts/infer-contact.md +78 -0
- package/dist/manager/libraries/rate-limits.js +17 -0
- package/dist/manager/libraries/recaptcha.js +58 -0
- package/dist/manager/libraries/user-doc.js +234 -0
- package/dist/manager/routes/admin/backup/post.js +106 -0
- package/dist/manager/routes/admin/cron/post.js +37 -0
- package/dist/manager/routes/admin/database/get.js +35 -0
- package/dist/manager/routes/admin/database/post.js +34 -0
- package/dist/manager/routes/admin/email/post.js +74 -0
- package/dist/manager/routes/admin/firestore/get.js +35 -0
- package/dist/manager/routes/admin/firestore/post.js +56 -0
- package/dist/manager/routes/admin/firestore/query/post.js +128 -0
- package/dist/manager/routes/admin/hook/post.js +104 -0
- package/dist/manager/routes/admin/infer-contact/post.js +35 -0
- package/dist/manager/routes/admin/notification/post.js +37 -0
- package/dist/manager/routes/admin/payment/post.js +56 -0
- package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
- package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
- package/dist/manager/routes/admin/post/post.js +487 -0
- package/dist/manager/routes/admin/post/put.js +146 -0
- package/dist/manager/routes/admin/post/templates/post.html +14 -0
- package/dist/manager/routes/admin/repo/content/post.js +106 -0
- package/dist/manager/routes/admin/stats/get.js +222 -0
- package/dist/manager/routes/admin/users/disable/post.js +50 -0
- package/dist/manager/routes/admin/users/list/get.js +110 -0
- package/dist/manager/routes/admin/users/sync/post.js +119 -0
- package/dist/manager/routes/brand/get.js +26 -0
- package/dist/manager/routes/content/post/get.js +110 -0
- package/dist/manager/routes/general/email/post.js +89 -0
- package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
- package/dist/manager/routes/general/uuid/post.js +31 -0
- package/dist/manager/routes/handler/post/post.js +142 -0
- package/dist/manager/routes/health/get.js +43 -0
- package/dist/manager/routes/index.js +11 -0
- package/dist/manager/routes/marketing/campaign/delete.js +45 -0
- package/dist/manager/routes/marketing/campaign/get.js +69 -0
- package/dist/manager/routes/marketing/campaign/post.js +124 -0
- package/dist/manager/routes/marketing/campaign/put.js +86 -0
- package/dist/manager/routes/marketing/campaign/utils.js +59 -0
- package/dist/manager/routes/marketing/contact/delete.js +100 -0
- package/dist/manager/routes/marketing/contact/post.js +149 -0
- package/dist/manager/routes/marketing/contact/put.js +37 -0
- package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
- package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
- package/dist/manager/routes/marketing/webhook/post.js +123 -0
- package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
- package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
- package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
- package/dist/manager/routes/payments/cancel/post.js +149 -0
- package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
- package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
- package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
- package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
- package/dist/manager/routes/payments/discount/get.js +25 -0
- package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
- package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
- package/dist/manager/routes/payments/intent/post.js +341 -0
- package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
- package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
- package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
- package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
- package/dist/manager/routes/payments/intent/providers/test.js +341 -0
- package/dist/manager/routes/payments/plan/post.js +147 -0
- package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
- package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
- package/dist/manager/routes/payments/plan/providers/test.js +113 -0
- package/dist/manager/routes/payments/portal/post.js +113 -0
- package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
- package/dist/manager/routes/payments/portal/providers/test.js +18 -0
- package/dist/manager/routes/payments/refund/post.js +207 -0
- package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
- package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
- package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
- package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
- package/dist/manager/routes/payments/refund/providers/test.js +204 -0
- package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
- package/dist/manager/routes/payments/uncancel/post.js +106 -0
- package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
- package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
- package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
- package/dist/manager/routes/payments/webhook/post.js +157 -0
- package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
- package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
- package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
- package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
- package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
- package/dist/manager/routes/payments/winback/post.js +276 -0
- package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
- package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
- package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
- package/dist/manager/routes/payments/winback/providers/test.js +42 -0
- package/dist/manager/routes/restart/index.js +37 -0
- package/dist/manager/routes/special/electron-client/post.js +59 -0
- package/dist/manager/routes/test/authenticate/get.js +11 -0
- package/dist/manager/routes/test/health/get.js +8 -0
- package/dist/manager/routes/test/index.js +16 -0
- package/dist/manager/routes/test/lab/post.js +17 -0
- package/dist/manager/routes/test/redirect/get.js +27 -0
- package/dist/manager/routes/test/reset-account/post.js +79 -0
- package/dist/manager/routes/test/roster/get.js +40 -0
- package/dist/manager/routes/test/schema/post.js +20 -0
- package/dist/manager/routes/test/usage/post.js +44 -0
- package/dist/manager/routes/test/webhook/post.js +21 -0
- package/dist/manager/routes/user/api-keys/post.js +51 -0
- package/dist/manager/routes/user/connections/_context.js +194 -0
- package/dist/manager/routes/user/connections/_grant.js +247 -0
- package/dist/manager/routes/user/connections/_lease.js +184 -0
- package/dist/manager/routes/user/connections/_providers.js +191 -0
- package/dist/manager/routes/user/connections/_state.js +117 -0
- package/dist/manager/routes/user/connections/delete.js +55 -0
- package/dist/manager/routes/user/connections/get.js +167 -0
- package/dist/manager/routes/user/connections/post.js +370 -0
- package/dist/manager/routes/user/connections/providers/discord.js +41 -0
- package/dist/manager/routes/user/connections/providers/google.js +55 -0
- package/dist/manager/routes/user/connections/providers/kick.js +68 -0
- package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
- package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
- package/dist/manager/routes/user/data-request/delete.js +84 -0
- package/dist/manager/routes/user/data-request/get.js +229 -0
- package/dist/manager/routes/user/data-request/post.js +136 -0
- package/dist/manager/routes/user/delete.js +149 -0
- package/dist/manager/routes/user/feedback/post.js +81 -0
- package/dist/manager/routes/user/get.js +14 -0
- package/dist/manager/routes/user/orders/get.js +95 -0
- package/dist/manager/routes/user/sessions/delete.js +95 -0
- package/dist/manager/routes/user/sessions/get.js +40 -0
- package/dist/manager/routes/user/settings/validate/post.js +86 -0
- package/dist/manager/routes/user/signup/post.js +662 -0
- package/dist/manager/routes/user/subscription/get.js +77 -0
- package/dist/manager/routes/user/token/post.js +30 -0
- package/dist/manager/routes/verts/delete.js +43 -0
- package/dist/manager/routes/verts/get.js +49 -0
- package/dist/manager/routes/verts/post.js +61 -0
- package/dist/manager/routes/verts/put.js +75 -0
- package/dist/manager/routes/verts/redirect/get.js +36 -0
- package/dist/manager/routes/verts/serve/get.js +67 -0
- package/dist/manager/routes/verts/utils.js +436 -0
- package/dist/manager/schemas/admin/backup/post.js +8 -0
- package/dist/manager/schemas/admin/cron/post.js +8 -0
- package/dist/manager/schemas/admin/database/get.js +5 -0
- package/dist/manager/schemas/admin/database/post.js +6 -0
- package/dist/manager/schemas/admin/email/post.js +31 -0
- package/dist/manager/schemas/admin/firestore/get.js +5 -0
- package/dist/manager/schemas/admin/firestore/post.js +8 -0
- package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
- package/dist/manager/schemas/admin/hook/post.js +8 -0
- package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
- package/dist/manager/schemas/admin/notification/post.js +23 -0
- package/dist/manager/schemas/admin/payment/post.js +8 -0
- package/dist/manager/schemas/admin/post/post.js +23 -0
- package/dist/manager/schemas/admin/post/put.js +13 -0
- package/dist/manager/schemas/admin/repo/content/post.js +10 -0
- package/dist/manager/schemas/admin/stats/get.js +5 -0
- package/dist/manager/schemas/admin/users/disable/post.js +9 -0
- package/dist/manager/schemas/admin/users/list/get.js +10 -0
- package/dist/manager/schemas/admin/users/sync/post.js +7 -0
- package/dist/manager/schemas/brand/get.js +3 -0
- package/dist/manager/schemas/content/post/get.js +8 -0
- package/dist/manager/schemas/general/email/post.js +10 -0
- package/dist/manager/schemas/general/uuid/post.js +9 -0
- package/dist/manager/schemas/handler/post/post.js +13 -0
- package/dist/manager/schemas/health/get.js +3 -0
- package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
- package/dist/manager/schemas/marketing/campaign/get.js +13 -0
- package/dist/manager/schemas/marketing/campaign/post.js +43 -0
- package/dist/manager/schemas/marketing/campaign/put.js +37 -0
- package/dist/manager/schemas/marketing/contact/delete.js +8 -0
- package/dist/manager/schemas/marketing/contact/post.js +14 -0
- package/dist/manager/schemas/marketing/contact/put.js +8 -0
- package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
- package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
- package/dist/manager/schemas/marketing/webhook/post.js +7 -0
- package/dist/manager/schemas/payments/cancel/post.js +15 -0
- package/dist/manager/schemas/payments/discount/get.js +8 -0
- package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
- package/dist/manager/schemas/payments/intent/post.js +28 -0
- package/dist/manager/schemas/payments/plan/post.js +11 -0
- package/dist/manager/schemas/payments/portal/post.js +12 -0
- package/dist/manager/schemas/payments/refund/post.js +14 -0
- package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
- package/dist/manager/schemas/payments/uncancel/post.js +9 -0
- package/dist/manager/schemas/payments/webhook/post.js +6 -0
- package/dist/manager/schemas/payments/winback/post.js +12 -0
- package/dist/manager/schemas/restart/index.js +5 -0
- package/dist/manager/schemas/special/electron-client/post.js +11 -0
- package/dist/manager/schemas/test/authenticate/get.js +3 -0
- package/dist/manager/schemas/test/health/get.js +3 -0
- package/dist/manager/schemas/test/index.js +5 -0
- package/dist/manager/schemas/test/lab/post.js +3 -0
- package/dist/manager/schemas/test/redirect/get.js +7 -0
- package/dist/manager/schemas/test/reset-account/post.js +3 -0
- package/dist/manager/schemas/test/roster/get.js +3 -0
- package/dist/manager/schemas/test/schema/post.js +70 -0
- package/dist/manager/schemas/test/usage/post.js +8 -0
- package/dist/manager/schemas/test/webhook/post.js +7 -0
- package/dist/manager/schemas/user/api-keys/post.js +6 -0
- package/dist/manager/schemas/user/connections/delete.js +8 -0
- package/dist/manager/schemas/user/connections/get.js +21 -0
- package/dist/manager/schemas/user/connections/post.js +12 -0
- package/dist/manager/schemas/user/data-request/delete.js +3 -0
- package/dist/manager/schemas/user/data-request/get.js +5 -0
- package/dist/manager/schemas/user/data-request/post.js +6 -0
- package/dist/manager/schemas/user/delete.js +6 -0
- package/dist/manager/schemas/user/feedback/post.js +8 -0
- package/dist/manager/schemas/user/get.js +3 -0
- package/dist/manager/schemas/user/orders/get.js +6 -0
- package/dist/manager/schemas/user/sessions/delete.js +6 -0
- package/dist/manager/schemas/user/sessions/get.js +6 -0
- package/dist/manager/schemas/user/settings/validate/post.js +8 -0
- package/dist/manager/schemas/user/signup/post.js +27 -0
- package/dist/manager/schemas/user/subscription/get.js +5 -0
- package/dist/manager/schemas/user/token/post.js +5 -0
- package/dist/manager/schemas/verts/delete.js +8 -0
- package/dist/manager/schemas/verts/get.js +9 -0
- package/dist/manager/schemas/verts/post.js +23 -0
- package/dist/manager/schemas/verts/put.js +20 -0
- package/dist/manager/schemas/verts/redirect/get.js +10 -0
- package/dist/manager/schemas/verts/serve/get.js +14 -0
- package/dist/manager/server-manager.js +69 -0
- package/dist/mcp/client.js +98 -0
- package/dist/mcp/handler.js +521 -0
- package/dist/mcp/index.js +157 -0
- package/dist/mcp/tools.js +555 -0
- package/dist/mcp/utils.js +108 -0
- package/dist/omega-bin.js +6 -0
- package/dist/require.js +3 -0
- package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
- package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
- package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
- package/dist/test/fixtures/firebase-project/firebase.json +49 -0
- package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
- package/dist/test/fixtures/firebase-project/package.json +14 -0
- package/dist/test/fixtures/firebase-project/src/index.js +11 -0
- package/dist/test/fixtures/firebase-project/storage.rules +8 -0
- package/dist/test/parse-audit.js +25 -0
- package/dist/test/run-tests.js +90 -0
- package/dist/test/runner.js +982 -0
- package/dist/test/seed.js +242 -0
- package/dist/test/test-accounts.js +2660 -0
- package/dist/test/utils/assertions.js +189 -0
- package/dist/test/utils/email-capture.js +252 -0
- package/dist/test/utils/extended-mode-warning.js +11 -0
- package/dist/test/utils/firestore-rules-client.js +277 -0
- package/dist/test/utils/http-client.js +228 -0
- package/dist/test/utils/test-mode-file.js +192 -0
- package/dist/utils/merge-line-files.js +6 -0
- package/dist/utils/scaffold-defaults.js +71 -0
- package/dist/utils/test-lanes.js +62 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/analytics/adapters/ga4.js +26 -0
- package/dist/vendor/analytics/adapters/meta.js +26 -0
- package/dist/vendor/analytics/adapters/resolve.js +130 -0
- package/dist/vendor/analytics/adapters/tiktok.js +27 -0
- package/dist/vendor/analytics/catalog.js +908 -0
- package/dist/vendor/analytics/identity.js +136 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/bare-requires.js +153 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/defaults-engine.js +372 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/deploy.js +286 -0
- package/dist/vendor/devkit/license.js +155 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/merge-line-files.js +296 -0
- package/dist/vendor/devkit/npm-registry.js +52 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/parse-audit.js +74 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/scaffold-guard.js +96 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/assert.js +120 -0
- package/dist/vendor/devkit/test/define-cases.js +104 -0
- package/dist/vendor/devkit/test/runner-core.js +554 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/dist/vendor/monitoring/core.js +180 -0
- package/dist/vendor/monitoring/env.js +49 -0
- package/dist/vendor/monitoring/logger.js +39 -0
- package/dist/vendor/monitoring/node.js +72 -0
- package/docs/admin-post-route.md +57 -0
- package/docs/ai-library.md +171 -0
- package/docs/architecture.md +62 -0
- package/docs/audit.md +70 -0
- package/docs/auth-hooks.md +74 -0
- package/docs/build-system.md +65 -0
- package/docs/cdp-debugging.md +29 -0
- package/docs/cli-firestore-auth.md +85 -0
- package/docs/cli-logs.md +67 -0
- package/docs/cli-output.md +146 -0
- package/docs/code-patterns.md +77 -0
- package/docs/common-mistakes.md +12 -0
- package/docs/common-operations.md +60 -0
- package/docs/connections.md +212 -0
- package/docs/consent.md +362 -0
- package/docs/directory-structure.md +150 -0
- package/docs/email-system.md +459 -0
- package/docs/environment-detection.md +93 -0
- package/docs/file-naming.md +10 -0
- package/docs/firestore.md +134 -0
- package/docs/ghostii.md +240 -0
- package/docs/index.md +299 -0
- package/docs/key-files.md +37 -0
- package/docs/logging.md +60 -0
- package/docs/marketing-campaigns.md +407 -0
- package/docs/marketing-fields.md +25 -0
- package/docs/mcp.md +222 -0
- package/docs/migration.md +129 -0
- package/docs/payment-system.md +856 -0
- package/docs/paypal-sandbox-qa.md +105 -0
- package/docs/response-headers.md +9 -0
- package/docs/routes.md +211 -0
- package/docs/sanitization.md +71 -0
- package/docs/schemas.md +163 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/stripe-webhook-forwarding.md +20 -0
- package/docs/test-boot-layer.md +67 -0
- package/docs/test-framework.md +583 -0
- package/docs/usage-rate-limiting.md +121 -0
- package/docs/verts.md +29 -0
- package/package.json +143 -0
- package/templates/config/omega.json5 +322 -0
- package/templates/database.rules.json +82 -0
- package/templates/firebase.json +67 -0
- package/templates/firestore.framework.rules +191 -0
- package/templates/firestore.indexes.json +4 -0
- package/templates/firestore.rules +59 -0
- package/templates/index.js +12 -0
- package/templates/public/404.html +26 -0
- package/templates/public/index.html +24 -0
- package/templates/remoteconfig.template.json +1 -0
- package/templates/storage-lifecycle-config-1-day.json +9 -0
- package/templates/storage-lifecycle-config-30-days.json +9 -0
- package/templates/storage.rules +11 -0
- package/test/_init/accounts-validation.js +58 -0
- package/test/ai/tools-live.test.js +171 -0
- package/test/analytics/conversion-delivery.test.js +1013 -0
- package/test/analytics/match-normalization.test.js +238 -0
- package/test/analytics/signup-conversion.test.js +135 -0
- package/test/boot/cli-dispatch.test.js +142 -0
- package/test/boot/defaults-scaffold.test.js +248 -0
- package/test/boot/deploy-staging.test.js +446 -0
- package/test/boot/disposable-domains.test.js +154 -0
- package/test/boot/emulator-boots.test.js +39 -0
- package/test/boot/emulator-port-preflight.test.js +126 -0
- package/test/boot/emulator-ready-timeout.test.js +46 -0
- package/test/boot/project-id-refresh.test.js +73 -0
- package/test/boot/suite-portability.test.js +278 -0
- package/test/boot/update-command.test.js +63 -0
- package/test/cli/cross-platform.test.js +168 -0
- package/test/cli/custom-project-type.test.js +426 -0
- package/test/cli/deploy-license-env.test.js +100 -0
- package/test/cli/emulator-adoption.test.js +254 -0
- package/test/cli/emulator-orphans.test.js +658 -0
- package/test/cli/emulator-ownership.test.js +177 -0
- package/test/cli/emulator-port-retry.test.js +395 -0
- package/test/cli/emulator-shutdown.test.js +788 -0
- package/test/cli/emulator-stale-reap.test.js +574 -0
- package/test/cli/ensure-target.test.js +325 -0
- package/test/cli/flags.test.js +64 -0
- package/test/cli/https-trust.test.js +38 -0
- package/test/cli/install-update-dispatch.test.js +47 -0
- package/test/cli/lane-environments.test.js +159 -0
- package/test/cli/marketing-campaigns-seeded.test.js +194 -0
- package/test/cli/migrate-bare-requires.test.js +128 -0
- package/test/cli/migrate-markers.test.js +530 -0
- package/test/cli/required-indexes.test.js +64 -0
- package/test/cli/rules-compile.test.js +622 -0
- package/test/cli/rules-migration-deferral.test.js +259 -0
- package/test/cli/rules-version.test.js +104 -0
- package/test/cli/setup-load-files.test.js +66 -0
- package/test/cli/setup-offline-mode.test.js +276 -0
- package/test/cli/setup-retired.test.js +64 -0
- package/test/cli/setup-shared-project-indexes.test.js +262 -0
- package/test/cli/setup-tests-requires.test.js +45 -0
- package/test/cli/stage-env-compose.test.js +316 -0
- package/test/cli/stripe-live-lane.test.js +303 -0
- package/test/cli/target.test.js +182 -0
- package/test/cli/templates.test.js +66 -0
- package/test/cli/test-runner-env.test.js +115 -0
- package/test/cli/test-stack-shutdown.test.js +275 -0
- package/test/cli/test-target-no-match.test.js +128 -0
- package/test/cli/verb-logs.test.js +118 -0
- package/test/cli/version-dispatch.test.js +39 -0
- package/test/content/blog-generate.test.js +164 -0
- package/test/email/campaign-config-fault.test.js +376 -0
- package/test/email/campaign-cron-pipeline.test.js +536 -0
- package/test/email/campaign-send.test.js +47 -0
- package/test/email/consent-lifecycle.test.js +258 -0
- package/test/email/content-html-policy.test.js +270 -0
- package/test/email/feedback-and-plain-send.test.js +54 -0
- package/test/email/fixtures/clean.json +30 -0
- package/test/email/fixtures/editorial.json +30 -0
- package/test/email/fixtures/field-report.json +53 -0
- package/test/email/identity.test.js +321 -0
- package/test/email/marketing/consent-gate.test.js +265 -0
- package/test/email/marketing/custom-fields-catalog.test.js +259 -0
- package/test/email/marketing/prune-per-provider.test.js +762 -0
- package/test/email/marketing/remove-log-privacy.test.js +225 -0
- package/test/email/marketing-lifecycle.test.js +139 -0
- package/test/email/newsletter-generate.test.js +853 -0
- package/test/email/newsletter-svg-tokens.test.js +51 -0
- package/test/email/newsletter-templates.test.js +492 -0
- package/test/email/order-one-time-cta.test.js +97 -0
- package/test/email/render-content.test.js +248 -0
- package/test/email/safe-url.test.js +0 -0
- package/test/email/sanitize-images.test.js +66 -0
- package/test/email/send-log-privacy.test.js +107 -0
- package/test/email/templates.test.js +288 -0
- package/test/email/testing-capture.test.js +260 -0
- package/test/email/transactional-send.test.js +36 -0
- package/test/email/transactional.test.js +562 -0
- package/test/email/unsubscribe-groups.test.js +155 -0
- package/test/email/unsubscribe-key.test.js +91 -0
- package/test/email/validation-cases.test.js +152 -0
- package/test/email/validation.test.js +746 -0
- package/test/events/auth-delete-conversion.test.js +138 -0
- package/test/events/auth-delete-race.test.js +211 -0
- package/test/events/auth-on-create-log.test.js +164 -0
- package/test/events/auth-signup-conversion.test.js +141 -0
- package/test/events/auth-signup-limit.test.js +142 -0
- package/test/events/auth-trigger-log-privacy.test.js +256 -0
- package/test/events/cron-job-doc-shape.test.js +101 -0
- package/test/events/cron-reset-usage.test.js +116 -0
- package/test/events/notification-conversion.test.js +182 -0
- package/test/events/payments/_webhook-harness.js +307 -0
- package/test/events/payments/abandoned-cart-activity.test.js +135 -0
- package/test/events/payments/analytics-payment-events.test.js +945 -0
- package/test/events/payments/dispute-email-status.test.js +55 -0
- package/test/events/payments/journey-payments-abandoned.test.js +126 -0
- package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
- package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-cancel.test.js +184 -0
- package/test/events/payments/journey-payments-decline.test.js +225 -0
- package/test/events/payments/journey-payments-discount.test.js +91 -0
- package/test/events/payments/journey-payments-dispute.test.js +190 -0
- package/test/events/payments/journey-payments-failure.test.js +148 -0
- package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
- package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
- package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
- package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
- package/test/events/payments/journey-payments-one-time.test.js +175 -0
- package/test/events/payments/journey-payments-plan-change.test.js +146 -0
- package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
- package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
- package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
- package/test/events/payments/journey-payments-suspend.test.js +183 -0
- package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
- package/test/events/payments/journey-payments-trial.test.js +173 -0
- package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
- package/test/events/payments/journey-payments-uncancel.test.js +157 -0
- package/test/events/payments/journey-payments-upgrade.test.js +137 -0
- package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
- package/test/events/payments/journey-payments-winback.test.js +195 -0
- package/test/events/payments/paypal-expiry-cron.test.js +166 -0
- package/test/events/payments/purchase-failed-handler.test.js +80 -0
- package/test/events/payments/purchase-refunded-handler.test.js +108 -0
- package/test/events/payments/test-processor-doc-shape.test.js +162 -0
- package/test/events/payments/transition-order-emails.test.js +185 -0
- package/test/events/payments/transition-promo-lines.test.js +192 -0
- package/test/events/payments/transitions-detect.test.js +767 -0
- package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
- package/test/events/payments/trial-lapse-sweep.test.js +359 -0
- package/test/events/payments/webhook-atomic-writes.test.js +69 -0
- package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
- package/test/events/payments/webhook-discount-clear.test.js +189 -0
- package/test/events/payments/webhook-failure-intent.test.js +93 -0
- package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
- package/test/events/payments/webhook-ordering.test.js +326 -0
- package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
- package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
- package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
- package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
- package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
- package/test/events/payments/webhook-refund-without-order.test.js +170 -0
- package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
- package/test/events/payments/webhook-refused-intent.test.js +142 -0
- package/test/events/payments/webhook-retry-sweep.test.js +252 -0
- package/test/events/payments/webhook-transition-claim.test.js +389 -0
- package/test/events/payments/webhook-uid-trust.test.js +203 -0
- package/test/events/payments/webhook-user-without-auth.test.js +161 -0
- package/test/fixtures/chargebee/invoice-one-time.json +27 -0
- package/test/fixtures/chargebee/subscription-active.json +44 -0
- package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
- package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
- package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
- package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
- package/test/fixtures/chargebee/subscription-paused.json +42 -0
- package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
- package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
- package/test/fixtures/coinbase/charge-confirmed.json +50 -0
- package/test/fixtures/coinbase/charge-failed.json +34 -0
- package/test/fixtures/coinbase/charge-pending.json +34 -0
- package/test/fixtures/migrate/ported-route.js.txt +11 -0
- package/test/fixtures/paypal/capture-completed.json +51 -0
- package/test/fixtures/paypal/capture-refunded.json +43 -0
- package/test/fixtures/paypal/order-approved.json +62 -0
- package/test/fixtures/paypal/order-completed.json +110 -0
- package/test/fixtures/paypal/sale-refunded.json +38 -0
- package/test/fixtures/paypal/subscription-active.json +76 -0
- package/test/fixtures/paypal/subscription-cancelled.json +50 -0
- package/test/fixtures/paypal/subscription-suspended.json +65 -0
- package/test/fixtures/stripe/checkout-session-completed.json +130 -0
- package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
- package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
- package/test/fixtures/stripe/subscription-active.json +161 -0
- package/test/fixtures/stripe/subscription-canceled.json +161 -0
- package/test/fixtures/stripe/subscription-trialing.json +161 -0
- package/test/helpers/_shared-config.js +15 -0
- package/test/helpers/ai-request-payload.test.js +619 -0
- package/test/helpers/ai-schema-resolve.test.js +125 -0
- package/test/helpers/ai-test-provider.test.js +227 -0
- package/test/helpers/ai-token-accounting.test.js +150 -0
- package/test/helpers/ai-tools-format.test.js +384 -0
- package/test/helpers/analytics-no-id-notice.test.js +146 -0
- package/test/helpers/analytics-user-data.test.js +194 -0
- package/test/helpers/api-manager.test.js +311 -0
- package/test/helpers/backend-router.test.js +136 -0
- package/test/helpers/content/blog-auto-publisher.test.js +482 -0
- package/test/helpers/content/feed-parser.test.js +529 -0
- package/test/helpers/content/ghostii-blocks.test.js +135 -0
- package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
- package/test/helpers/content/ghostii-write-article.test.js +244 -0
- package/test/helpers/dev-only-routes.test.js +225 -0
- package/test/helpers/env-reader.test.js +362 -0
- package/test/helpers/environment.test.js +263 -0
- package/test/helpers/event-middleware.test.js +407 -0
- package/test/helpers/infer-contact.test.js +157 -0
- package/test/helpers/lane-url.test.js +90 -0
- package/test/helpers/merge-line-files.test.js +272 -0
- package/test/helpers/metadata.test.js +122 -0
- package/test/helpers/middleware-request-log.test.js +134 -0
- package/test/helpers/middleware-user-log.test.js +124 -0
- package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
- package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
- package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
- package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
- package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
- package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
- package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
- package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
- package/test/helpers/payment/discount-codes.test.js +141 -0
- package/test/helpers/payment/extract-resource.test.js +113 -0
- package/test/helpers/payment/fetch-failure.test.js +215 -0
- package/test/helpers/payment/license-gate.test.js +108 -0
- package/test/helpers/payment/order-id.test.js +99 -0
- package/test/helpers/payment/paypal/create-intent.test.js +382 -0
- package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
- package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
- package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
- package/test/helpers/payment/paypal/refund-details.test.js +202 -0
- package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
- package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
- package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
- package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
- package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
- package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
- package/test/helpers/payment/stripe/refund-details.test.js +147 -0
- package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
- package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
- package/test/helpers/persona-domain.test.js +87 -0
- package/test/helpers/recaptcha.test.js +166 -0
- package/test/helpers/resolved-config.test.js +106 -0
- package/test/helpers/response-log-redaction.test.js +262 -0
- package/test/helpers/roles.test.js +155 -0
- package/test/helpers/route-context-debug-gate.test.js +118 -0
- package/test/helpers/route-context-logging.test.js +214 -0
- package/test/helpers/route-context.test.js +129 -0
- package/test/helpers/safe-compare.test.js +114 -0
- package/test/helpers/sanitize.test.js +223 -0
- package/test/helpers/schema-engine.test.js +537 -0
- package/test/helpers/schema-zod.test.js +554 -0
- package/test/helpers/seed-accounts-load.test.js +163 -0
- package/test/helpers/seed-google-personas.test.js +114 -0
- package/test/helpers/seeded-personas.test.js +823 -0
- package/test/helpers/settings.test.js +292 -0
- package/test/helpers/setup-engines-pin.test.js +68 -0
- package/test/helpers/setup-manifest-sync.test.js +116 -0
- package/test/helpers/slugify.test.js +395 -0
- package/test/helpers/storage.test.js +195 -0
- package/test/helpers/test-banner-latch.test.js +139 -0
- package/test/helpers/usage-consume.test.js +460 -0
- package/test/helpers/usage-log-privacy.test.js +121 -0
- package/test/helpers/user-doc-heal.test.js +540 -0
- package/test/helpers/user.test.js +771 -0
- package/test/helpers/webhook-forward.test.js +419 -0
- package/test/helpers/wipe-auth-project.test.js +143 -0
- package/test/mcp/discovery.test.js +52 -0
- package/test/mcp/oauth.test.js +160 -0
- package/test/mcp/protocol.test.js +278 -0
- package/test/mcp/roles.test.js +203 -0
- package/test/mcp/utils.test.js +245 -0
- package/test/notification/identity.test.js +155 -0
- package/test/routes/admin/create-post.test.js +362 -0
- package/test/routes/admin/database.test.js +133 -0
- package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
- package/test/routes/admin/email-content-html.test.js +58 -0
- package/test/routes/admin/email-request-log.test.js +86 -0
- package/test/routes/admin/email.test.js +116 -0
- package/test/routes/admin/firestore-query.test.js +206 -0
- package/test/routes/admin/firestore.test.js +129 -0
- package/test/routes/admin/infer-contact.test.js +220 -0
- package/test/routes/admin/notification.test.js +199 -0
- package/test/routes/admin/post-convert-image.test.js +159 -0
- package/test/routes/admin/post-deploy-flag.test.js +81 -0
- package/test/routes/admin/post-download-error.test.js +90 -0
- package/test/routes/admin/post-resize-image.test.js +185 -0
- package/test/routes/admin/post.test.js +369 -0
- package/test/routes/admin/repo-content.test.js +223 -0
- package/test/routes/admin/stats.test.js +114 -0
- package/test/routes/admin/users-disable.test.js +63 -0
- package/test/routes/admin/users-list.test.js +71 -0
- package/test/routes/content/post.test.js +60 -0
- package/test/routes/general/uuid.test.js +133 -0
- package/test/routes/health.test.js +110 -0
- package/test/routes/marketing/campaign.test.js +184 -0
- package/test/routes/marketing/contact.test.js +416 -0
- package/test/routes/marketing/email-preferences.test.js +293 -0
- package/test/routes/marketing/push-send.test.js +34 -0
- package/test/routes/marketing/webhook-forward.test.js +63 -0
- package/test/routes/marketing/webhook.test.js +641 -0
- package/test/routes/payments/_route-harness.js +145 -0
- package/test/routes/payments/cancel-provider-errors.test.js +227 -0
- package/test/routes/payments/cancel-skip-guards.test.js +126 -0
- package/test/routes/payments/cancel-trialing.test.js +428 -0
- package/test/routes/payments/cancel.test.js +165 -0
- package/test/routes/payments/dedup-race.test.js +186 -0
- package/test/routes/payments/discount.test.js +82 -0
- package/test/routes/payments/dispute-alert.test.js +324 -0
- package/test/routes/payments/intent-discount-amounts.test.js +550 -0
- package/test/routes/payments/intent-discount-coupons.test.js +293 -0
- package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
- package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
- package/test/routes/payments/intent-zero-total.test.js +260 -0
- package/test/routes/payments/intent.test.js +413 -0
- package/test/routes/payments/plan.test.js +367 -0
- package/test/routes/payments/portal-return-url.test.js +107 -0
- package/test/routes/payments/portal.test.js +94 -0
- package/test/routes/payments/refund-one-time.test.js +254 -0
- package/test/routes/payments/refund-paypal-proration.test.js +190 -0
- package/test/routes/payments/refund.test.js +181 -0
- package/test/routes/payments/trial-eligibility.test.js +73 -0
- package/test/routes/payments/uncancel.test.js +270 -0
- package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
- package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
- package/test/routes/payments/webhook-test-provider.test.js +156 -0
- package/test/routes/payments/webhook.test.js +115 -0
- package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
- package/test/routes/payments/winback.test.js +597 -0
- package/test/routes/test/authenticate.test.js +79 -0
- package/test/routes/test/redirect.test.js +66 -0
- package/test/routes/test/reset-account.test.js +126 -0
- package/test/routes/test/roster.test.js +101 -0
- package/test/routes/test/schema.test.js +556 -0
- package/test/routes/test/usage.test.js +374 -0
- package/test/routes/user/api-keys.test.js +158 -0
- package/test/routes/user/connections-grant.test.js +759 -0
- package/test/routes/user/connections-identity.test.js +260 -0
- package/test/routes/user/connections-log-privacy.test.js +206 -0
- package/test/routes/user/connections-refresh-lease.test.js +696 -0
- package/test/routes/user/connections-return.test.js +261 -0
- package/test/routes/user/connections-uid.test.js +516 -0
- package/test/routes/user/delete.test.js +138 -0
- package/test/routes/user/feedback.test.js +106 -0
- package/test/routes/user/orders.test.js +233 -0
- package/test/routes/user/sessions.test.js +231 -0
- package/test/routes/user/settings-validate.test.js +84 -0
- package/test/routes/user/signup-emails.test.js +113 -0
- package/test/routes/user/signup-location.test.js +84 -0
- package/test/routes/user/signup-log-privacy.test.js +112 -0
- package/test/routes/user/signup.test.js +579 -0
- package/test/routes/user/subscription.test.js +101 -0
- package/test/routes/user/token.test.js +111 -0
- package/test/routes/user/user.test.js +158 -0
- package/test/routes/verts/cache.test.js +78 -0
- package/test/routes/verts/click-destination.test.js +76 -0
- package/test/routes/verts/crud.test.js +194 -0
- package/test/routes/verts/redirect.test.js +147 -0
- package/test/routes/verts/selection.test.js +237 -0
- package/test/routes/verts/serve.test.js +185 -0
- package/test/routes/verts/unit-document.test.js +117 -0
- package/test/rules/_environment.js +66 -0
- package/test/rules/brand-merge.test.js +195 -0
- package/test/rules/field-helpers.test.js +309 -0
- package/test/rules/notifications.test.js +577 -0
- package/test/rules/payments-carts.test.js +406 -0
- package/test/rules/sessions.test.js +170 -0
- package/test/rules/user.test.js +517 -0
- package/test/rules/verts.test.js +101 -0
- package/test/security/fetch-log-secrets.test.js +124 -0
- package/test/security/primitives.test.js +105 -0
- package/test/security/repo-pinning.test.js +77 -0
- 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
|
+
};
|