@omega.js/manager 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/.claude-plugin/marketplace.json +14 -0
- package/LICENSE +98 -0
- package/README.md +165 -0
- package/bin/omega +2 -0
- package/bin/omg +2 -0
- package/claude-plugin/.claude-plugin/plugin.json +11 -0
- package/claude-plugin/.mcp.json +9 -0
- package/claude-plugin/README.md +139 -0
- package/claude-plugin/hooks/gate/mark.sh +58 -0
- package/claude-plugin/hooks/gate/run.sh +97 -0
- package/claude-plugin/hooks/guard/run.sh +249 -0
- package/claude-plugin/hooks/hooks.json +63 -0
- package/claude-plugin/hooks/inject/run.sh +127 -0
- package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
- package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
- package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
- package/claude-plugin/hooks/quality/run.sh +132 -0
- package/claude-plugin/hooks/shape/run.sh +45 -0
- package/claude-plugin/mcp-router-launch.js +29 -0
- package/claude-plugin/skills/README.md +48 -0
- package/claude-plugin/skills/accessibility/SKILL.md +31 -0
- package/claude-plugin/skills/analytics/SKILL.md +30 -0
- package/claude-plugin/skills/backend/SKILL.md +25 -0
- package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
- package/claude-plugin/skills/browser/SKILL.md +64 -0
- package/claude-plugin/skills/client/SKILL.md +24 -0
- package/claude-plugin/skills/desktop/SKILL.md +25 -0
- package/claude-plugin/skills/extension/SKILL.md +25 -0
- package/claude-plugin/skills/main/SKILL.md +54 -0
- package/claude-plugin/skills/manager/SKILL.md +25 -0
- package/claude-plugin/skills/seo/SKILL.md +36 -0
- package/claude-plugin/skills/theme/SKILL.md +33 -0
- package/claude-plugin/skills/web/SKILL.md +29 -0
- package/dist/cli-run.js +34 -0
- package/dist/cli.js +29 -0
- package/dist/commands/build.js +17 -0
- package/dist/commands/clean.js +16 -0
- package/dist/commands/company.js +46 -0
- package/dist/commands/deploy.js +143 -0
- package/dist/commands/dev.js +437 -0
- package/dist/commands/devlog.js +25 -0
- package/dist/commands/help.js +48 -0
- package/dist/commands/manage.js +53 -0
- package/dist/commands/migrate.js +76 -0
- package/dist/commands/onboard.js +31 -0
- package/dist/commands/pipeline.js +319 -0
- package/dist/commands/test.js +380 -0
- package/dist/commands/update.js +128 -0
- package/dist/commands/version.js +8 -0
- package/dist/company-init.js +167 -0
- package/dist/company.js +283 -0
- package/dist/config.js +1359 -0
- package/dist/devlog/index.js +226 -0
- package/dist/devlog/lib/collect-commits.js +136 -0
- package/dist/devlog/lib/generate-post.js +201 -0
- package/dist/devlog/lib/ghostii.js +122 -0
- package/dist/devlog/lib/project-map.js +98 -0
- package/dist/devlog/lib/publish-website.js +88 -0
- package/dist/index.js +45 -0
- package/dist/lib/agents-md.js +201 -0
- package/dist/lib/analytics-secret.js +22 -0
- package/dist/lib/argv.js +21 -0
- package/dist/lib/auth-admin.js +120 -0
- package/dist/lib/automation-client.js +210 -0
- package/dist/lib/backend-marketing.js +49 -0
- package/dist/lib/brand.js +240 -0
- package/dist/lib/bundle-id.js +53 -0
- package/dist/lib/claude-settings.js +125 -0
- package/dist/lib/company-scaffold.js +213 -0
- package/dist/lib/company.js +227 -0
- package/dist/lib/config-flow.js +350 -0
- package/dist/lib/config-write.js +41 -0
- package/dist/lib/custom-target.js +75 -0
- package/dist/lib/domain-utils.js +19 -0
- package/dist/lib/duration.js +39 -0
- package/dist/lib/env-order.js +236 -0
- package/dist/lib/env-secret.js +78 -0
- package/dist/lib/firestore-rest.js +326 -0
- package/dist/lib/framework-bin.js +156 -0
- package/dist/lib/gitignore.js +63 -0
- package/dist/lib/google-auth.js +495 -0
- package/dist/lib/google-token.js +66 -0
- package/dist/lib/jwt.js +31 -0
- package/dist/lib/legacy-oauth.js +59 -0
- package/dist/lib/node-version.js +127 -0
- package/dist/lib/owner-plan.js +78 -0
- package/dist/lib/package-scripts.js +152 -0
- package/dist/lib/preflight.js +470 -0
- package/dist/lib/product-create.js +144 -0
- package/dist/lib/run-command.js +74 -0
- package/dist/lib/run-gates.js +70 -0
- package/dist/lib/run-output.js +42 -0
- package/dist/lib/run-summary.js +384 -0
- package/dist/lib/scaffold.js +427 -0
- package/dist/lib/service-input.js +207 -0
- package/dist/lib/service-runner.js +466 -0
- package/dist/lib/stale.js +24 -0
- package/dist/lib/target-selection.js +155 -0
- package/dist/lib/verb-fanout.js +128 -0
- package/dist/lib/verify-live.js +188 -0
- package/dist/manage.js +311 -0
- package/dist/omega-bin.js +7 -0
- package/dist/onboard.js +532 -0
- package/dist/services/account/ensure/users.js +259 -0
- package/dist/services/account/index.js +97 -0
- package/dist/services/account/lib/backend-client.js +107 -0
- package/dist/services/account/lib/password.js +36 -0
- package/dist/services/account/lib/resolve-password.js +100 -0
- package/dist/services/advertising/ensure/sites.js +101 -0
- package/dist/services/advertising/index.js +101 -0
- package/dist/services/advertising/lib/adsense-api.js +41 -0
- package/dist/services/ai/ensure/keys.js +21 -0
- package/dist/services/ai/index.js +36 -0
- package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
- package/dist/services/analytics/ensure/google-streams.js +256 -0
- package/dist/services/analytics/ensure/meta-pixel.js +22 -0
- package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
- package/dist/services/analytics/index.js +134 -0
- package/dist/services/analytics/lib/analytics-api.js +156 -0
- package/dist/services/analytics/lib/meta-api.js +83 -0
- package/dist/services/analytics/lib/pixel-account.js +78 -0
- package/dist/services/analytics/lib/pixel-provision.js +152 -0
- package/dist/services/analytics/lib/pixel-specs.js +57 -0
- package/dist/services/analytics/lib/pixel-token.js +88 -0
- package/dist/services/analytics/lib/property-flow.js +77 -0
- package/dist/services/analytics/lib/tiktok-api.js +157 -0
- package/dist/services/analytics/lib/tiktok-auth.js +153 -0
- package/dist/services/assets/ensure/logo-gen.js +68 -0
- package/dist/services/assets/index.js +109 -0
- package/dist/services/assets/lib/assets-config.js +168 -0
- package/dist/services/assets/lib/brandmark-api.js +160 -0
- package/dist/services/assets/lib/derived.js +113 -0
- package/dist/services/assets/lib/font-loader.js +67 -0
- package/dist/services/assets/lib/reconcile.js +122 -0
- package/dist/services/assets/lib/reset.js +118 -0
- package/dist/services/assets/lib/svg-logo-generator.js +154 -0
- package/dist/services/assets/lib/svg-to-black.js +32 -0
- package/dist/services/assets/write/favicons.js +82 -0
- package/dist/services/assets/write/icons.js +64 -0
- package/dist/services/assets/write/process.js +96 -0
- package/dist/services/assets/write/reconcile.js +30 -0
- package/dist/services/assets/write/social-icons.js +108 -0
- package/dist/services/assets/write/templates.js +345 -0
- package/dist/services/bookmark/ensure/sync.js +255 -0
- package/dist/services/bookmark/index.js +17 -0
- package/dist/services/campaigns/ensure/contact-person.js +40 -0
- package/dist/services/campaigns/ensure/custom-fields.js +84 -0
- package/dist/services/campaigns/ensure/domain-auth.js +92 -0
- package/dist/services/campaigns/ensure/event-webhook.js +89 -0
- package/dist/services/campaigns/ensure/link-branding.js +187 -0
- package/dist/services/campaigns/ensure/list.js +50 -0
- package/dist/services/campaigns/ensure/segments.js +109 -0
- package/dist/services/campaigns/ensure/sender-identity.js +153 -0
- package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
- package/dist/services/campaigns/index.js +79 -0
- package/dist/services/campaigns/lib/dns-sync.js +81 -0
- package/dist/services/campaigns/lib/segment-query.js +138 -0
- package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
- package/dist/services/captcha/ensure/site-key.js +83 -0
- package/dist/services/captcha/index.js +78 -0
- package/dist/services/captcha/lib/console-url.js +45 -0
- package/dist/services/captcha/lib/recaptcha-api.js +36 -0
- package/dist/services/certificates/ensure/api-key.js +22 -0
- package/dist/services/certificates/ensure/bundle-ids.js +115 -0
- package/dist/services/certificates/ensure/certificates.js +187 -0
- package/dist/services/certificates/ensure/profiles.js +143 -0
- package/dist/services/certificates/index.js +225 -0
- package/dist/services/certificates/lib/apple-api.js +183 -0
- package/dist/services/certificates/lib/certificate-manager.js +252 -0
- package/dist/services/certificates/lib/identifier-manager.js +107 -0
- package/dist/services/certificates/lib/keychain.js +135 -0
- package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
- package/dist/services/certificates/lib/profile-manager.js +115 -0
- package/dist/services/chat/data/baseline-knowledge.md +96 -0
- package/dist/services/chat/ensure/chat.js +106 -0
- package/dist/services/chat/ensure/user.js +45 -0
- package/dist/services/chat/index.js +139 -0
- package/dist/services/chat/lib/baseline-knowledge.js +137 -0
- package/dist/services/cloud/ensure/authentication.js +267 -0
- package/dist/services/cloud/ensure/billing.js +120 -0
- package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
- package/dist/services/cloud/ensure/database.js +60 -0
- package/dist/services/cloud/ensure/firestore.js +79 -0
- package/dist/services/cloud/ensure/functions.js +49 -0
- package/dist/services/cloud/ensure/hosting.js +398 -0
- package/dist/services/cloud/ensure/oauth-consent.js +248 -0
- package/dist/services/cloud/ensure/project-settings.js +72 -0
- package/dist/services/cloud/ensure/sdk-config.js +95 -0
- package/dist/services/cloud/ensure/service-account.js +152 -0
- package/dist/services/cloud/ensure/services.js +103 -0
- package/dist/services/cloud/ensure/storage.js +47 -0
- package/dist/services/cloud/index.js +130 -0
- package/dist/services/cloud/lib/access-heal.js +130 -0
- package/dist/services/cloud/lib/firebase-api.js +662 -0
- package/dist/services/cloud/lib/project-flow.js +102 -0
- package/dist/services/directory/ensure/entry.js +48 -0
- package/dist/services/directory/index.js +64 -0
- package/dist/services/directory/lib/blocks.js +104 -0
- package/dist/services/disperse/index.js +45 -0
- package/dist/services/disperse/write/certs.js +117 -0
- package/dist/services/domain/ensure/nameservers.js +179 -0
- package/dist/services/domain/index.js +69 -0
- package/dist/services/domain/lib/namecheap-api.js +167 -0
- package/dist/services/domain/lib/registrars.js +46 -0
- package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
- package/dist/services/edge/ensure/cache-rules.js +105 -0
- package/dist/services/edge/ensure/dns-records.js +304 -0
- package/dist/services/edge/ensure/email-routing.js +292 -0
- package/dist/services/edge/ensure/rules-configuration.js +98 -0
- package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
- package/dist/services/edge/ensure/rules-redirect.js +107 -0
- package/dist/services/edge/ensure/rules-response-headers.js +90 -0
- package/dist/services/edge/ensure/rules-security.js +142 -0
- package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
- package/dist/services/edge/ensure/workers.js +219 -0
- package/dist/services/edge/ensure/zone-settings.js +108 -0
- package/dist/services/edge/ensure/zone.js +183 -0
- package/dist/services/edge/index.js +73 -0
- package/dist/services/edge/lib/cloudflare-api.js +84 -0
- package/dist/services/edge/lib/dns-records-helpers.js +518 -0
- package/dist/services/edge/lib/read-cache.js +16 -0
- package/dist/services/edge/lib/ruleset-helper.js +108 -0
- package/dist/services/edge/workers/omega-api-proxy.js +43 -0
- package/dist/services/email/data/baseline-filter.md +4 -0
- package/dist/services/email/data/baseline-knowledge.md +86 -0
- package/dist/services/email/ensure/agent.js +107 -0
- package/dist/services/email/ensure/user.js +45 -0
- package/dist/services/email/index.js +138 -0
- package/dist/services/email/lib/baseline.js +82 -0
- package/dist/services/email/lib/knowledge-file.js +53 -0
- package/dist/services/forms/ensure/form.js +46 -0
- package/dist/services/forms/ensure/user.js +45 -0
- package/dist/services/forms/index.js +138 -0
- package/dist/services/migrations/ensure/notifications.js +189 -0
- package/dist/services/migrations/ensure/orders.js +34 -0
- package/dist/services/migrations/ensure/payment-provider.js +132 -0
- package/dist/services/migrations/ensure/payments-intents.js +34 -0
- package/dist/services/migrations/ensure/state-retirement.js +266 -0
- package/dist/services/migrations/ensure/targets-rename.js +124 -0
- package/dist/services/migrations/ensure/users.js +1176 -0
- package/dist/services/migrations/index.js +101 -0
- package/dist/services/migrations/lib/attribution-touch.js +64 -0
- package/dist/services/migrations/lib/ensure-metadata.js +144 -0
- package/dist/services/migrations/lib/migration-runner.js +473 -0
- package/dist/services/migrations/lib/sanitize-strings.js +112 -0
- package/dist/services/migrations/lib/schema-validator.js +154 -0
- package/dist/services/monitoring/ensure/dsn.js +54 -0
- package/dist/services/monitoring/ensure/projects.js +135 -0
- package/dist/services/monitoring/index.js +54 -0
- package/dist/services/monitoring/lib/sentry-api.js +94 -0
- package/dist/services/newsletter/ensure/custom-fields.js +90 -0
- package/dist/services/newsletter/ensure/publication.js +94 -0
- package/dist/services/newsletter/ensure/segments.js +140 -0
- package/dist/services/newsletter/ensure/webhook.js +108 -0
- package/dist/services/newsletter/index.js +64 -0
- package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
- package/dist/services/newsletter/lib/segment-automation.js +577 -0
- package/dist/services/payment/ensure/chargebee-account.js +30 -0
- package/dist/services/payment/ensure/chargebee-products.js +359 -0
- package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
- package/dist/services/payment/ensure/paypal-account.js +36 -0
- package/dist/services/payment/ensure/paypal-products.js +374 -0
- package/dist/services/payment/ensure/paypal-webhook.js +128 -0
- package/dist/services/payment/ensure/stripe-account.js +93 -0
- package/dist/services/payment/ensure/stripe-disputes.js +56 -0
- package/dist/services/payment/ensure/stripe-products.js +245 -0
- package/dist/services/payment/ensure/stripe-radar.js +74 -0
- package/dist/services/payment/ensure/stripe-webhook.js +130 -0
- package/dist/services/payment/index.js +163 -0
- package/dist/services/payment/lib/chargebee-api.js +338 -0
- package/dist/services/payment/lib/payment-utils.js +116 -0
- package/dist/services/payment/lib/paypal-api.js +354 -0
- package/dist/services/payment/lib/provider-setup.js +134 -0
- package/dist/services/payment/lib/stripe-api.js +152 -0
- package/dist/services/repo/ensure/org.js +70 -0
- package/dist/services/repo/ensure/pages.js +72 -0
- package/dist/services/repo/ensure/repo.js +90 -0
- package/dist/services/repo/index.js +52 -0
- package/dist/services/repo/lib/github-api.js +196 -0
- package/dist/services/search/ensure/ga-link.js +57 -0
- package/dist/services/search/ensure/property.js +137 -0
- package/dist/services/search/ensure/sitemaps.js +70 -0
- package/dist/services/search/index.js +61 -0
- package/dist/services/search/lib/search-console-api.js +92 -0
- package/dist/services/seo/ensure/github-repos.js +271 -0
- package/dist/services/seo/index.js +55 -0
- package/dist/services/seo/lib/gh-api.js +161 -0
- package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
- package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
- package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
- package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
- package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
- package/dist/services/seo/templates/index.js +57 -0
- package/dist/services/server/ensure/brands.js +63 -0
- package/dist/services/server/index.js +42 -0
- package/dist/services/testing/ensure/target-checks.js +97 -0
- package/dist/services/testing/index.js +11 -0
- package/dist/services/testing/lib/checks.js +467 -0
- package/dist/services/update/index.js +20 -0
- package/dist/services/update/lib/cache.js +59 -0
- package/dist/services/update/lib/fingerprint.js +157 -0
- package/dist/services/update/write/targets.js +206 -0
- package/dist/services/workspace/ensure/agents.js +42 -0
- package/dist/services/workspace/ensure/claude-settings.js +34 -0
- package/dist/services/workspace/ensure/config.js +54 -0
- package/dist/services/workspace/ensure/defaults.js +60 -0
- package/dist/services/workspace/ensure/env-keys.js +55 -0
- package/dist/services/workspace/ensure/env-order.js +66 -0
- package/dist/services/workspace/ensure/env-rules.js +97 -0
- package/dist/services/workspace/ensure/gitignore.js +18 -0
- package/dist/services/workspace/ensure/scripts.js +126 -0
- package/dist/services/workspace/ensure/structure.js +97 -0
- package/dist/services/workspace/ensure/translation-sdk.js +148 -0
- package/dist/services/workspace/ensure/workflows.js +54 -0
- package/dist/services/workspace/index.js +8 -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/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/certs.js +199 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/flows.js +317 -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/omega-bin.js +345 -0
- package/dist/vendor/devkit/prompt.js +187 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/docs/AGENTS.md +200 -0
- package/docs/account.md +45 -0
- package/docs/advertising.md +46 -0
- package/docs/ai.md +34 -0
- package/docs/analytics.md +69 -0
- package/docs/assets.md +57 -0
- package/docs/bookmark.md +26 -0
- package/docs/brand.md +151 -0
- package/docs/campaigns.md +96 -0
- package/docs/captcha.md +46 -0
- package/docs/certificates.md +68 -0
- package/docs/chat.md +47 -0
- package/docs/cloud.md +110 -0
- package/docs/company.md +64 -0
- package/docs/directory.md +140 -0
- package/docs/disperse.md +46 -0
- package/docs/domain.md +56 -0
- package/docs/edge.md +234 -0
- package/docs/email.md +42 -0
- package/docs/forms.md +48 -0
- package/docs/index.md +109 -0
- package/docs/migration.md +203 -0
- package/docs/migrations.md +56 -0
- package/docs/monitoring.md +41 -0
- package/docs/newsletter.md +48 -0
- package/docs/payment.md +83 -0
- package/docs/repo.md +48 -0
- package/docs/search.md +44 -0
- package/docs/seo.md +34 -0
- package/docs/server.md +37 -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/testing.md +34 -0
- package/docs/update.md +36 -0
- package/docs/workspace.md +64 -0
- package/package.json +88 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure Firebase Authentication is configured: Identity Platform enabled,
|
|
3
|
+
* email/password sign-in, email-enumeration privacy, anonymous-user
|
|
4
|
+
* auto-delete, the password policy, authorized domains — all diffed via the
|
|
5
|
+
* Identity Toolkit API — plus Google sign-in.
|
|
6
|
+
*
|
|
7
|
+
* Google sign-in's OAuth client CANNOT be created programmatically (no Google
|
|
8
|
+
* API exists); omega-manager opened a browser and polled. Not-enabled prints
|
|
9
|
+
* the console instructions and warns — reruns converge once it's enabled
|
|
10
|
+
* (the API verifies). The OAuth client's redirect URIs have no API either:
|
|
11
|
+
* instructions + an interactive confirm. A confirmation nothing can re-check
|
|
12
|
+
* is the one kind of reconcile flag config keeps (#434): it lands at
|
|
13
|
+
* `cloud.oauthRedirectsConfigured`. Non-interactive runs warn.
|
|
14
|
+
*
|
|
15
|
+
* OAuth client credentials land in the brand's gitignored
|
|
16
|
+
* .omega/secrets/google-oauth.json (never in omega.json5).
|
|
17
|
+
*/
|
|
18
|
+
const { join } = require('node:path');
|
|
19
|
+
const chalk = require('chalk').default;
|
|
20
|
+
const jetpack = require('fs-jetpack');
|
|
21
|
+
const { confirm, pressEnterToOpen } = require('../../../vendor/devkit/prompt.js');
|
|
22
|
+
const { writeBrandConfig } = require('../../../lib/config-write.js');
|
|
23
|
+
const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
|
|
24
|
+
|
|
25
|
+
const OAUTH_REDIRECTS_PATH = 'cloud.oauthRedirectsConfigured';
|
|
26
|
+
|
|
27
|
+
module.exports = async function ensureAuthentication(context) {
|
|
28
|
+
const { firebaseApi: api, brandConfig, brandRoot, projectId, domain, options = {} } = context;
|
|
29
|
+
|
|
30
|
+
// What warned, by name (#643) — the run summary prints these as the
|
|
31
|
+
// operation's reason
|
|
32
|
+
const warnings = [];
|
|
33
|
+
let needsInteractive = null; // #32: set when a step steps aside for lack of a TTY
|
|
34
|
+
|
|
35
|
+
// === Identity Platform (upgrade only when the config GET says it's absent) ===
|
|
36
|
+
let config = await api.getIdentityConfig(projectId);
|
|
37
|
+
|
|
38
|
+
if (!config) {
|
|
39
|
+
if (options.dryRun) {
|
|
40
|
+
return dryRunPlan('enable Identity Platform', { output: { authentication: { planned: 'initialize' } } });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await api.initializeIdentityPlatform(projectId);
|
|
45
|
+
console.log(` ${chalk.green('✓')} Identity Platform enabled`);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (!/409|ALREADY_EXISTS|already/.test(error.message || '')) {
|
|
48
|
+
console.log(` ${chalk.yellow('⚠')} Could not enable Identity Platform${chalk.dim(`: ${error.message}`)}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
config = await api.getIdentityConfig(projectId);
|
|
53
|
+
if (!config) {
|
|
54
|
+
console.log(` ${chalk.yellow('⚠')} Could not get Identity Platform config`);
|
|
55
|
+
console.log(` ${chalk.dim('→')} Enable Identity Platform in the Firebase Console, then rerun`);
|
|
56
|
+
return { status: 'warned', reason: 'no Identity Platform config — enable it in the Firebase Console', output: { authentication: { error: 'no identity config' } } };
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` ${chalk.green('✓')} Identity Platform enabled`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const enabled = [];
|
|
63
|
+
|
|
64
|
+
// Helper: diff-gated PATCH of one identity-config slice
|
|
65
|
+
const patchConfig = async (label, needsUpdate, payload) => {
|
|
66
|
+
if (!needsUpdate) {
|
|
67
|
+
console.log(` ${chalk.green('✓')} ${label} already configured`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (options.dryRun) {
|
|
71
|
+
return dryRunPlan(`configure ${label}`);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await api.updateIdentityConfig(projectId, payload);
|
|
75
|
+
console.log(` ${chalk.green('✓')} ${label} configured`);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.log(` ${chalk.yellow('⚠')} Could not configure ${label}${chalk.dim(`: ${error.message}`)}`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// === Email/password sign-in ===
|
|
82
|
+
const signInConfig = config.signIn || {};
|
|
83
|
+
await patchConfig('Email/password sign-in', !signInConfig.email?.enabled, {
|
|
84
|
+
signIn: {
|
|
85
|
+
...signInConfig,
|
|
86
|
+
email: { enabled: true, passwordRequired: true },
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
enabled.push('email');
|
|
90
|
+
|
|
91
|
+
// === Email privacy (prevents email-enumeration attacks) ===
|
|
92
|
+
await patchConfig('Email privacy', !config.emailPrivacyConfig?.enableImprovedEmailPrivacy, {
|
|
93
|
+
emailPrivacyConfig: { enableImprovedEmailPrivacy: true },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// === Auto-delete anonymous users (30 days) ===
|
|
97
|
+
await patchConfig('Anonymous-user auto-delete', !config.autodeleteAnonymousUsers, {
|
|
98
|
+
autodeleteAnonymousUsers: true,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// === Password policy (8–128 chars, enforced) ===
|
|
102
|
+
const currentPolicy = config.passwordPolicyConfig || {};
|
|
103
|
+
const currentConstraints = currentPolicy.passwordPolicyVersions?.[0]?.customStrengthOptions || {};
|
|
104
|
+
const policyDrifted = currentPolicy.passwordPolicyEnforcementState !== 'ENFORCE'
|
|
105
|
+
|| currentPolicy.forceUpgradeOnSignin !== false
|
|
106
|
+
|| currentConstraints.minPasswordLength !== 8
|
|
107
|
+
|| currentConstraints.maxPasswordLength !== 128;
|
|
108
|
+
await patchConfig('Password policy', policyDrifted, {
|
|
109
|
+
passwordPolicyConfig: {
|
|
110
|
+
passwordPolicyEnforcementState: 'ENFORCE',
|
|
111
|
+
forceUpgradeOnSignin: false,
|
|
112
|
+
passwordPolicyVersions: [{
|
|
113
|
+
customStrengthOptions: {
|
|
114
|
+
minPasswordLength: 8,
|
|
115
|
+
maxPasswordLength: 128,
|
|
116
|
+
},
|
|
117
|
+
}],
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// === Google sign-in ===
|
|
122
|
+
const googleConfig = await api.getIdpConfig(projectId, 'google.com');
|
|
123
|
+
const firebaseAuthUrl = `https://console.firebase.google.com/project/${projectId}/authentication/providers`;
|
|
124
|
+
let googleClientId = null;
|
|
125
|
+
|
|
126
|
+
// OAuth client IDs start with the project number — a mismatch means the
|
|
127
|
+
// wrong project's credentials got attached
|
|
128
|
+
let projectNumber = null;
|
|
129
|
+
try {
|
|
130
|
+
projectNumber = await api.getProjectNumber(projectId);
|
|
131
|
+
} catch {
|
|
132
|
+
// Validation is best-effort without it
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (googleConfig?.enabled && googleConfig.clientId && googleConfig.clientSecret) {
|
|
136
|
+
const clientProjectNumber = googleConfig.clientId.split('-')[0];
|
|
137
|
+
|
|
138
|
+
if (projectNumber && clientProjectNumber !== projectNumber) {
|
|
139
|
+
console.log(` ${chalk.yellow('⚠')} Google sign-in has the WRONG OAuth client (project ${clientProjectNumber}, expected ${projectNumber})`);
|
|
140
|
+
console.log(` ${chalk.dim('→')} Delete Google sign-in in the Firebase Console and re-enable it: ${chalk.cyan(firebaseAuthUrl)}`);
|
|
141
|
+
warnings.push('Google sign-in has the wrong OAuth client');
|
|
142
|
+
} else {
|
|
143
|
+
console.log(` ${chalk.green('✓')} Google sign-in enabled`);
|
|
144
|
+
enabled.push('google');
|
|
145
|
+
googleClientId = googleConfig.clientId;
|
|
146
|
+
|
|
147
|
+
// Secrets belong in the gitignored secrets dir, never state/omega.json5.
|
|
148
|
+
// Read-compare first (#590's idiom, #623): the console's credentials
|
|
149
|
+
// barely ever move, and a manage run that rewrites an identical secret
|
|
150
|
+
// file on every pass churns the mtime every watcher and backup sees.
|
|
151
|
+
const secretsPath = join(brandRoot, '.omega', 'secrets', 'google-oauth.json');
|
|
152
|
+
const secrets = {
|
|
153
|
+
clientId: googleConfig.clientId,
|
|
154
|
+
clientSecret: googleConfig.clientSecret,
|
|
155
|
+
};
|
|
156
|
+
// A truncated write to this machine-owned file is an expected external
|
|
157
|
+
// condition: treat unparseable as absent so the next run self-heals.
|
|
158
|
+
let stored;
|
|
159
|
+
try {
|
|
160
|
+
stored = jetpack.read(secretsPath, 'json');
|
|
161
|
+
} catch {
|
|
162
|
+
stored = undefined;
|
|
163
|
+
}
|
|
164
|
+
const drifted = stored?.clientId !== secrets.clientId || stored?.clientSecret !== secrets.clientSecret;
|
|
165
|
+
|
|
166
|
+
if (drifted && !options.dryRun) {
|
|
167
|
+
jetpack.write(secretsPath, secrets);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else if (googleConfig?.enabled) {
|
|
171
|
+
console.log(` ${chalk.yellow('⚠')} Google sign-in enabled but OAuth credentials missing (corrupted config)`);
|
|
172
|
+
console.log(` ${chalk.dim('→')} Delete Google sign-in and re-enable it: ${chalk.cyan(firebaseAuthUrl)}`);
|
|
173
|
+
warnings.push('Google sign-in is missing its OAuth credentials');
|
|
174
|
+
} else {
|
|
175
|
+
// No API to create the OAuth client — manual enable in the console
|
|
176
|
+
console.log(` ${chalk.yellow('⚠')} Google sign-in not enabled — requires one-time manual setup`);
|
|
177
|
+
console.log(` ${chalk.dim('→')} Enable "Google", pick a support email, Save — then rerun`);
|
|
178
|
+
if (canPrompt(options)) {
|
|
179
|
+
await pressEnterToOpen(firebaseAuthUrl, 'the sign-in providers page');
|
|
180
|
+
} else {
|
|
181
|
+
console.log(` ${chalk.dim('→')} ${chalk.cyan(firebaseAuthUrl)}`);
|
|
182
|
+
}
|
|
183
|
+
warnings.push('Google sign-in not enabled — one-time manual setup');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// === OAuth client redirect URIs (no API — instructions + confirm until done) ===
|
|
187
|
+
let oauthRedirectsConfigured = brandConfig.cloud?.oauthRedirectsConfigured === true;
|
|
188
|
+
if (googleClientId && !oauthRedirectsConfigured) {
|
|
189
|
+
const gcpCredentialsUrl = `https://console.cloud.google.com/apis/credentials/oauthclient/${googleClientId}?project=${projectId}`;
|
|
190
|
+
const authorizedOrigins = [
|
|
191
|
+
'https://localhost',
|
|
192
|
+
'https://localhost:5000',
|
|
193
|
+
`https://${projectId}.firebaseapp.com`,
|
|
194
|
+
`https://${domain}`,
|
|
195
|
+
];
|
|
196
|
+
const redirectUris = [
|
|
197
|
+
`https://${projectId}.firebaseapp.com/__/auth/handler`,
|
|
198
|
+
'https://localhost:5000/__/auth/handler',
|
|
199
|
+
`https://${domain}/__/auth/handler`,
|
|
200
|
+
];
|
|
201
|
+
console.log(` ${chalk.yellow('⚠')} OAuth client redirect URIs need one-time manual configuration`);
|
|
202
|
+
console.log(` ${chalk.dim('→')} Authorized origins:`);
|
|
203
|
+
for (const origin of authorizedOrigins) {
|
|
204
|
+
console.log(` ${chalk.cyan(origin)}`);
|
|
205
|
+
}
|
|
206
|
+
console.log(` ${chalk.dim('→')} Redirect URIs:`);
|
|
207
|
+
for (const uri of redirectUris) {
|
|
208
|
+
console.log(` ${chalk.cyan(uri)}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (canPrompt(options)) {
|
|
212
|
+
await pressEnterToOpen(gcpCredentialsUrl, 'the OAuth client settings');
|
|
213
|
+
const done = await confirm({ message: 'Origins + redirect URIs configured in the OAuth client?', default: false });
|
|
214
|
+
if (done) {
|
|
215
|
+
oauthRedirectsConfigured = true;
|
|
216
|
+
writeBrandConfig(context, { [OAUTH_REDIRECTS_PATH]: true });
|
|
217
|
+
console.log(` ${chalk.green('✓')} OAuth client redirect URIs confirmed`);
|
|
218
|
+
} else {
|
|
219
|
+
warnings.push('OAuth client redirect URIs not confirmed');
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
console.log(` ${chalk.dim('→')} ${chalk.cyan(gcpCredentialsUrl)}`);
|
|
223
|
+
console.log(` ${chalk.dim('→')} (rerun in an interactive terminal to confirm)`);
|
|
224
|
+
needsInteractive = 'confirm the OAuth client origins + redirect URIs (the run opens the console for you)';
|
|
225
|
+
warnings.push('OAuth client redirect URIs not confirmed');
|
|
226
|
+
}
|
|
227
|
+
} else if (googleClientId) {
|
|
228
|
+
console.log(` ${chalk.green('✓')} OAuth client redirect URIs configured`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// === Authorized domains ===
|
|
232
|
+
const authorizedDomains = config.authorizedDomains || [];
|
|
233
|
+
const domainsToAdd = [
|
|
234
|
+
domain,
|
|
235
|
+
`${projectId}.firebaseapp.com`,
|
|
236
|
+
`${projectId}.web.app`,
|
|
237
|
+
'localhost',
|
|
238
|
+
].filter((d) => !authorizedDomains.includes(d));
|
|
239
|
+
|
|
240
|
+
if (domainsToAdd.length === 0) {
|
|
241
|
+
console.log(` ${chalk.green('✓')} Authorized domains configured`);
|
|
242
|
+
} else if (options.dryRun) {
|
|
243
|
+
dryRunPlan(`add ${domainsToAdd.length} authorized domain(s)`);
|
|
244
|
+
} else {
|
|
245
|
+
try {
|
|
246
|
+
await api.updateIdentityConfig(projectId, {
|
|
247
|
+
authorizedDomains: [...authorizedDomains, ...domainsToAdd],
|
|
248
|
+
});
|
|
249
|
+
console.log(` ${chalk.green('✓')} Added ${chalk.bold(domainsToAdd.length)} authorized domain(s)`);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.log(` ${chalk.yellow('⚠')} Could not update authorized domains${chalk.dim(`: ${error.message}`)}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
status: warnings.length > 0 ? 'warned' : 'success',
|
|
257
|
+
...(warnings.length > 0 ? { reason: warnings.join('; ') } : {}),
|
|
258
|
+
output: {
|
|
259
|
+
authentication: {
|
|
260
|
+
enabled,
|
|
261
|
+
authorizedDomains: [domain, `${projectId}.firebaseapp.com`, `${projectId}.web.app`],
|
|
262
|
+
oauthRedirectsConfigured,
|
|
263
|
+
...(needsInteractive ? { needsInteractive } : {}),
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the project is on the Blaze plan.
|
|
3
|
+
*
|
|
4
|
+
* omega-manager silently linked a hardcoded company billing account; the
|
|
5
|
+
* account is tri-state config now (#33) — `cloud.billingAccount` set →
|
|
6
|
+
* link it, `false` → the user chose Spark (silent, no nagging), missing →
|
|
7
|
+
* ask in an interactive run (pick from the accounts the authed user can
|
|
8
|
+
* see, or create one in the console; the answer lands in omega.json5) and
|
|
9
|
+
* warn with guidance otherwise.
|
|
10
|
+
*/
|
|
11
|
+
const chalk = require('chalk').default;
|
|
12
|
+
const { resolveConfigValue } = require('../../../lib/config-flow.js');
|
|
13
|
+
const { dryRunPlan, needsInteractiveSkip } = require('../../../lib/run-gates.js');
|
|
14
|
+
|
|
15
|
+
/** Interactive pick/create/opt-out flow — the answer lands in omega.json5. */
|
|
16
|
+
function resolveBillingAccount(context, api) {
|
|
17
|
+
return resolveConfigValue(context, {
|
|
18
|
+
path: 'cloud.billingAccount',
|
|
19
|
+
label: 'Billing account',
|
|
20
|
+
instructions: [
|
|
21
|
+
'Blaze (pay-as-you-go) unlocks functions deploys and outbound network.',
|
|
22
|
+
'The account you pick links to this brand\'s projects from now on.',
|
|
23
|
+
],
|
|
24
|
+
choices: () => api.listBillingAccounts(),
|
|
25
|
+
getName: (account) => `${account.displayName} (${account.name.replace('billingAccounts/', '')})${account.open === false ? ' [closed]' : ''}`,
|
|
26
|
+
getValue: (account) => account.name,
|
|
27
|
+
createNew: {
|
|
28
|
+
label: 'billing account',
|
|
29
|
+
url: 'https://console.cloud.google.com/billing/create',
|
|
30
|
+
refreshChoices: true,
|
|
31
|
+
},
|
|
32
|
+
optOut: { label: 'Stay on the Spark plan (free) — don\'t ask again' },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = async function ensureBilling(context) {
|
|
37
|
+
const { firebaseApi: api, brandConfig, projectId, options = {} } = context;
|
|
38
|
+
|
|
39
|
+
// === READ ===
|
|
40
|
+
const billingInfo = await api.getProjectBillingInfo(projectId);
|
|
41
|
+
|
|
42
|
+
if (billingInfo?.billingEnabled) {
|
|
43
|
+
console.log(` ${chalk.green('✓')} Blaze plan enabled`);
|
|
44
|
+
return {
|
|
45
|
+
state: {
|
|
46
|
+
billing: {
|
|
47
|
+
enabled: true,
|
|
48
|
+
billingAccountName: billingInfo.billingAccountName,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A failed READ is not a Spark verdict (#56): getProjectBillingInfo() turns
|
|
55
|
+
// every failure (auth timeout, permission denied, disabled API) into null,
|
|
56
|
+
// and a live run announced "on the Spark plan" right after its auth attempt
|
|
57
|
+
// timed out. "Could not check" and "checked: Spark" are separate outputs.
|
|
58
|
+
if (!billingInfo) {
|
|
59
|
+
console.log(` ${chalk.yellow('⚠')} Could not check the billing plan ${chalk.dim('(the billing API read failed: unauthenticated, no permission, or the API is off)')}`);
|
|
60
|
+
console.log(` ${chalk.dim('→')} Rerun once Google auth succeeds: ${chalk.cyan(`https://console.firebase.google.com/project/${projectId}/usage/details`)}`);
|
|
61
|
+
return {
|
|
62
|
+
status: 'warned',
|
|
63
|
+
reason: 'could not check the billing plan',
|
|
64
|
+
output: { billing: { error: 'billing plan unknown: could not check (the billing API read failed)' } },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Tri-state (#33): false = the user chose Spark — a clean state, not a warning
|
|
69
|
+
if (brandConfig.cloud?.billingAccount === false) {
|
|
70
|
+
console.log(` ${chalk.dim('⊘ Billing opted out (cloud.billingAccount: false) — staying on the Spark plan')}`);
|
|
71
|
+
return { output: { billing: { note: 'billing opted out — Spark plan' } } };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(` ${chalk.yellow('⚠')} Project is on the Spark plan (free tier)`);
|
|
75
|
+
|
|
76
|
+
const billingAccountName = brandConfig.cloud?.billingAccount
|
|
77
|
+
|| await resolveBillingAccount(context, api);
|
|
78
|
+
|
|
79
|
+
// The flow may have just landed the opt-out
|
|
80
|
+
if (brandConfig.cloud?.billingAccount === false) {
|
|
81
|
+
return { output: { billing: { note: 'billing opted out — Spark plan' } } };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!billingAccountName) {
|
|
85
|
+
console.log(` ${chalk.dim('→')} Set cloud.billingAccount ("billingAccounts/XXXXXX-XXXXXX-XXXXXX") in company/brand config to auto-upgrade`);
|
|
86
|
+
console.log(` ${chalk.dim('→')} Or upgrade manually: ${chalk.cyan(`https://console.firebase.google.com/project/${projectId}/usage/details`)}`);
|
|
87
|
+
return needsInteractiveSkip(
|
|
88
|
+
'billing',
|
|
89
|
+
'pick or create a billing account (the answer lands in omega.json5)',
|
|
90
|
+
'no cloud.billingAccount configured',
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// === WRITE ===
|
|
95
|
+
if (options.dryRun) {
|
|
96
|
+
return dryRunPlan(`link ${billingAccountName} (Blaze plan)`, { output: { billing: { planned: 'link' } } });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(` ${chalk.yellow('⏳')} Upgrading to Blaze plan...`);
|
|
100
|
+
try {
|
|
101
|
+
await api.linkBillingAccount(projectId, billingAccountName);
|
|
102
|
+
console.log(` ${chalk.green('✓')} Upgraded to Blaze plan`);
|
|
103
|
+
return {
|
|
104
|
+
state: {
|
|
105
|
+
billing: {
|
|
106
|
+
enabled: true,
|
|
107
|
+
billingAccountName,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.log(` ${chalk.yellow('⚠')} Could not upgrade${chalk.dim(`: ${error.message}`)}`);
|
|
113
|
+
return {
|
|
114
|
+
status: 'warned',
|
|
115
|
+
reason: 'could not upgrade the billing plan',
|
|
116
|
+
state: { billing: { enabled: false } },
|
|
117
|
+
output: { billing: { error: error.message } },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure Firebase Cloud Messaging is ready: the FCM API is enabled and a
|
|
3
|
+
* VAPID key pair (web push) is on hand.
|
|
4
|
+
*
|
|
5
|
+
* There is no public API for Web Push certificates — missing keys print the
|
|
6
|
+
* console URL and, in an interactive terminal, prompt to paste both back
|
|
7
|
+
* (length-validated; omega-manager's auto-open of the console is dropped —
|
|
8
|
+
* the URL is printed and clickable). Non-interactive runs warn and move on.
|
|
9
|
+
*
|
|
10
|
+
* The halves go to their own homes (#434): the PUBLIC key to
|
|
11
|
+
* `cloud.messaging.vapidKey` in omega.json5 (it ships to every browser — the
|
|
12
|
+
* client reads it from there), the PRIVATE key to VAPID_PRIVATE_KEY in the
|
|
13
|
+
* gitignored brand .env.
|
|
14
|
+
*/
|
|
15
|
+
const chalk = require('chalk').default;
|
|
16
|
+
const { input, pressEnterToOpen } = require('../../../vendor/devkit/prompt.js');
|
|
17
|
+
const { writeBrandConfig } = require('../../../lib/config-write.js');
|
|
18
|
+
const { writeEnvValue } = require('../../../lib/env-secret.js');
|
|
19
|
+
const { canPrompt, dryRunPlan, needsInteractiveSkip } = require('../../../lib/run-gates.js');
|
|
20
|
+
|
|
21
|
+
const VAPID_PUBLIC_PATH = 'cloud.messaging.vapidKey';
|
|
22
|
+
const VAPID_PRIVATE_ENV = 'VAPID_PRIVATE_KEY';
|
|
23
|
+
|
|
24
|
+
module.exports = async function ensureCloudMessaging(context) {
|
|
25
|
+
const { firebaseApi: api, brandConfig, brandRoot, projectId, options = {} } = context;
|
|
26
|
+
|
|
27
|
+
// === FCM API ===
|
|
28
|
+
const fcmEnabled = await api.isServiceEnabled(projectId, 'fcm.googleapis.com');
|
|
29
|
+
|
|
30
|
+
if (fcmEnabled) {
|
|
31
|
+
console.log(` ${chalk.green('✓')} FCM API enabled`);
|
|
32
|
+
} else if (options.dryRun) {
|
|
33
|
+
dryRunPlan('enable the FCM API');
|
|
34
|
+
} else {
|
|
35
|
+
console.log(' Enabling FCM API...');
|
|
36
|
+
try {
|
|
37
|
+
await api.enableService(projectId, 'fcm.googleapis.com');
|
|
38
|
+
console.log(` ${chalk.green('✓')} FCM API enabled`);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.log(` ${chalk.yellow('⚠')} Failed to enable FCM API${chalk.dim(`: ${error.message}`)}`);
|
|
41
|
+
return { status: 'warned', reason: 'failed to enable the FCM API', output: { cloudMessaging: { error: error.message } } };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// === VAPID key pair (config + .env — no API to create or read them) ===
|
|
46
|
+
const existingPublicKey = brandConfig.cloud?.messaging?.vapidKey;
|
|
47
|
+
|
|
48
|
+
if (existingPublicKey && process.env[VAPID_PRIVATE_ENV]) {
|
|
49
|
+
console.log(` ${chalk.green('✓')} VAPID key pair exists ${chalk.dim(`(${existingPublicKey.substring(0, 20)}...)`)}`);
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const consoleUrl = `https://console.firebase.google.com/project/${projectId}/settings/cloudmessaging`;
|
|
54
|
+
console.log(` ${chalk.yellow('⚠')} No VAPID key pair — web push won't work without one`);
|
|
55
|
+
console.log(` ${chalk.dim('→')} Under "Web Push certificates": generate (or reveal via ⋮) the key pair`);
|
|
56
|
+
|
|
57
|
+
if (!canPrompt(options)) {
|
|
58
|
+
console.log(` ${chalk.dim('→')} ${chalk.cyan(consoleUrl)}`);
|
|
59
|
+
console.log(` ${chalk.dim('→')} Rerun in an interactive terminal to paste both keys; the public half lands in omega.json5 (${chalk.cyan(VAPID_PUBLIC_PATH)}), the private half in the brand .env (${chalk.cyan(VAPID_PRIVATE_ENV)})`);
|
|
60
|
+
return needsInteractiveSkip(
|
|
61
|
+
'cloudMessaging',
|
|
62
|
+
'paste the VAPID key pair from the Cloud Messaging settings',
|
|
63
|
+
'no VAPID key pair (needs an interactive run)',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await pressEnterToOpen(consoleUrl, 'the Cloud Messaging settings');
|
|
68
|
+
|
|
69
|
+
const vapidPublicKey = await input({
|
|
70
|
+
message: ' VAPID public key:',
|
|
71
|
+
validate: (value) => {
|
|
72
|
+
if (!value?.trim()) {
|
|
73
|
+
return 'Required — copy the Key pair value from the Firebase Console';
|
|
74
|
+
}
|
|
75
|
+
if (value.trim().length !== 87) {
|
|
76
|
+
return `VAPID public key must be exactly 87 characters (got ${value.trim().length})`;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const vapidPrivateKey = await input({
|
|
83
|
+
message: ' VAPID private key:',
|
|
84
|
+
validate: (value) => {
|
|
85
|
+
if (!value?.trim()) {
|
|
86
|
+
return 'Required — click the ⋮ menu next to the key pair to reveal it';
|
|
87
|
+
}
|
|
88
|
+
if (value.trim() === vapidPublicKey.trim()) {
|
|
89
|
+
return 'That\'s the public key again — click ⋮ to reveal the private key (shorter, 43 chars)';
|
|
90
|
+
}
|
|
91
|
+
if (value.trim().length !== 43) {
|
|
92
|
+
return `VAPID private key must be exactly 43 characters (got ${value.trim().length})`;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
writeBrandConfig(context, { [VAPID_PUBLIC_PATH]: vapidPublicKey.trim() });
|
|
99
|
+
writeEnvValue(brandRoot, VAPID_PRIVATE_ENV, vapidPrivateKey.trim());
|
|
100
|
+
process.env[VAPID_PRIVATE_ENV] = vapidPrivateKey.trim();
|
|
101
|
+
|
|
102
|
+
console.log(` ${chalk.green('✓')} VAPID key pair saved ${chalk.dim(`(${VAPID_PUBLIC_PATH} + .env ${VAPID_PRIVATE_ENV})`)}`);
|
|
103
|
+
|
|
104
|
+
return {};
|
|
105
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the Realtime Database default instance exists
|
|
3
|
+
* ({projectId}-default-rtdb in us-central1).
|
|
4
|
+
*/
|
|
5
|
+
const chalk = require('chalk').default;
|
|
6
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
7
|
+
|
|
8
|
+
module.exports = async function ensureDatabase(context) {
|
|
9
|
+
const { firebaseApi: api, projectId, options = {} } = context;
|
|
10
|
+
|
|
11
|
+
// === READ ===
|
|
12
|
+
const existing = await api.listRealtimeDatabases(projectId);
|
|
13
|
+
|
|
14
|
+
if (existing.length > 0) {
|
|
15
|
+
const db = existing[0];
|
|
16
|
+
console.log(` ${chalk.green('✓')} Realtime Database exists`);
|
|
17
|
+
return {
|
|
18
|
+
state: {
|
|
19
|
+
database: {
|
|
20
|
+
databaseURL: db.databaseUrl,
|
|
21
|
+
name: db.name,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// === WRITE ===
|
|
28
|
+
if (options.dryRun) {
|
|
29
|
+
return dryRunPlan('create the Realtime Database', { output: { database: { planned: 'create' } } });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(' Creating Realtime Database...');
|
|
33
|
+
try {
|
|
34
|
+
const result = await api.createRealtimeDatabase(projectId, 'us-central1');
|
|
35
|
+
console.log(` ${chalk.green('✓')} Created Realtime Database`);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
state: {
|
|
39
|
+
database: {
|
|
40
|
+
databaseURL: result.databaseUrl || `https://${projectId}-default-rtdb.firebaseio.com`,
|
|
41
|
+
name: result.name,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.message?.includes('already exists')) {
|
|
47
|
+
console.log(` ${chalk.green('✓')} Realtime Database exists`);
|
|
48
|
+
return {
|
|
49
|
+
state: {
|
|
50
|
+
database: {
|
|
51
|
+
databaseURL: `https://${projectId}-default-rtdb.firebaseio.com`,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(` ${chalk.yellow('⚠')} Could not create database${chalk.dim(`: ${error.message}`)}`);
|
|
58
|
+
return { status: 'warned', reason: 'could not create the Realtime Database', output: { database: { error: error.message } } };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the Firestore database exists (nam5 US multi-region) with
|
|
3
|
+
* Point-in-Time Recovery enabled (7-day disaster recovery).
|
|
4
|
+
*/
|
|
5
|
+
const chalk = require('chalk').default;
|
|
6
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
7
|
+
|
|
8
|
+
module.exports = async function ensureFirestore(context) {
|
|
9
|
+
const { firebaseApi: api, projectId, options = {} } = context;
|
|
10
|
+
|
|
11
|
+
// === READ ===
|
|
12
|
+
const existing = await api.getFirestoreDatabase(projectId);
|
|
13
|
+
|
|
14
|
+
if (existing) {
|
|
15
|
+
console.log(` ${chalk.green('✓')} Firestore database exists`);
|
|
16
|
+
await ensurePITR(api, projectId, existing, options);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
state: {
|
|
20
|
+
firestore: {
|
|
21
|
+
databaseId: '(default)',
|
|
22
|
+
locationId: existing.locationId,
|
|
23
|
+
pitrEnabled: true,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// === WRITE ===
|
|
30
|
+
if (options.dryRun) {
|
|
31
|
+
return dryRunPlan('create Firestore database (nam5) + enable PITR', { output: { firestore: { planned: 'create' } } });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(' Creating Firestore database...');
|
|
35
|
+
try {
|
|
36
|
+
const result = await api.createFirestoreDatabase(projectId, 'nam5');
|
|
37
|
+
console.log(` ${chalk.green('✓')} Created Firestore database`);
|
|
38
|
+
|
|
39
|
+
await ensurePITR(api, projectId, result, options);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
state: {
|
|
43
|
+
firestore: {
|
|
44
|
+
databaseId: '(default)',
|
|
45
|
+
locationId: result?.locationId || 'nam5',
|
|
46
|
+
pitrEnabled: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.message?.includes('already exists')) {
|
|
52
|
+
console.log(` ${chalk.green('✓')} Firestore database exists`);
|
|
53
|
+
await ensurePITR(api, projectId, null, options);
|
|
54
|
+
return { state: { firestore: { databaseId: '(default)', pitrEnabled: true } } };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log(` ${chalk.yellow('⚠')} Could not create Firestore${chalk.dim(`: ${error.message}`)}`);
|
|
58
|
+
return { status: 'warned', reason: 'could not create the Firestore database', output: { firestore: { error: error.message } } };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
async function ensurePITR(api, projectId, database, options) {
|
|
63
|
+
if (database?.pointInTimeRecoveryEnablement === 'POINT_IN_TIME_RECOVERY_ENABLED') {
|
|
64
|
+
console.log(` ${chalk.green('✓')} PITR enabled (7-day recovery)`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (options.dryRun) {
|
|
69
|
+
return dryRunPlan('enable PITR');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(' Enabling PITR (Point-in-Time Recovery)...');
|
|
73
|
+
try {
|
|
74
|
+
await api.enableFirestorePITR(projectId);
|
|
75
|
+
console.log(` ${chalk.green('✓')} PITR enabled (7-day recovery)`);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.log(` ${chalk.yellow('⚠')} Could not enable PITR${chalk.dim(`: ${error.message}`)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Cloud Functions prerequisites are ready (the services operation
|
|
3
|
+
* enables them; this is a read-only readiness check).
|
|
4
|
+
*/
|
|
5
|
+
const chalk = require('chalk').default;
|
|
6
|
+
|
|
7
|
+
const REQUIRED_SERVICES = [
|
|
8
|
+
'cloudfunctions.googleapis.com',
|
|
9
|
+
'cloudbuild.googleapis.com',
|
|
10
|
+
'run.googleapis.com',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
module.exports = async function ensureFunctions(context) {
|
|
14
|
+
const { firebaseApi: api, projectId } = context;
|
|
15
|
+
|
|
16
|
+
let allEnabled = true;
|
|
17
|
+
const status = {};
|
|
18
|
+
|
|
19
|
+
for (const service of REQUIRED_SERVICES) {
|
|
20
|
+
try {
|
|
21
|
+
const enabled = await api.isServiceEnabled(projectId, service);
|
|
22
|
+
status[service.replace('.googleapis.com', '')] = enabled;
|
|
23
|
+
|
|
24
|
+
if (!enabled) {
|
|
25
|
+
allEnabled = false;
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
allEnabled = false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (allEnabled) {
|
|
33
|
+
console.log(` ${chalk.green('✓')} Cloud Functions ready`);
|
|
34
|
+
} else {
|
|
35
|
+
console.log(` ${chalk.yellow('⚠')} Some Cloud Functions APIs not enabled`);
|
|
36
|
+
console.log(` ${chalk.dim('→')} The services operation enables them — rerun`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
status: allEnabled ? 'success' : 'warned',
|
|
41
|
+
...(allEnabled ? {} : { reason: 'some Cloud Functions APIs are not enabled' }),
|
|
42
|
+
state: {
|
|
43
|
+
functions: {
|
|
44
|
+
ready: allEnabled,
|
|
45
|
+
services: status,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|