@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,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure project identity: the GCP display name matches the brand and a web
|
|
3
|
+
* app named 'Web App' exists.
|
|
4
|
+
*
|
|
5
|
+
* omega-manager PATCHed the project name on every run; this diffs first —
|
|
6
|
+
* a converged project is a zero-mutation no-op.
|
|
7
|
+
*/
|
|
8
|
+
const chalk = require('chalk').default;
|
|
9
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
10
|
+
|
|
11
|
+
const WEB_APP_NAME = 'Web App';
|
|
12
|
+
|
|
13
|
+
module.exports = async function ensureProjectSettings(context) {
|
|
14
|
+
const { firebaseApi: api, brandConfig, projectId, options = {} } = context;
|
|
15
|
+
const brandName = brandConfig.brand?.name;
|
|
16
|
+
|
|
17
|
+
const updates = {};
|
|
18
|
+
|
|
19
|
+
// === Project display name ===
|
|
20
|
+
const gcpProject = await api.getGcpProject(projectId);
|
|
21
|
+
|
|
22
|
+
if (gcpProject?.displayName === brandName) {
|
|
23
|
+
console.log(` ${chalk.green('✓')} Project name: ${chalk.cyan(brandName)}`);
|
|
24
|
+
updates.projectName = brandName;
|
|
25
|
+
} else if (options.dryRun) {
|
|
26
|
+
dryRunPlan(`rename project ${gcpProject?.displayName || '(unknown)'} → ${brandName}`);
|
|
27
|
+
} else {
|
|
28
|
+
try {
|
|
29
|
+
await api.updateProjectName(projectId, brandName);
|
|
30
|
+
console.log(` ${chalk.green('✓')} Project name set to ${chalk.cyan(brandName)}`);
|
|
31
|
+
updates.projectName = brandName;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log(` ${chalk.yellow('⚠')} Could not update project name${chalk.dim(`: ${error.message}`)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// === Web app ===
|
|
38
|
+
try {
|
|
39
|
+
const apps = await api.listWebApps(projectId);
|
|
40
|
+
|
|
41
|
+
if (apps.length === 0) {
|
|
42
|
+
if (options.dryRun) {
|
|
43
|
+
dryRunPlan(`create web app "${WEB_APP_NAME}"`);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(' Creating web app...');
|
|
46
|
+
await api.createWebApp(projectId, WEB_APP_NAME);
|
|
47
|
+
console.log(` ${chalk.green('✓')} Created web app: ${chalk.cyan(WEB_APP_NAME)}`);
|
|
48
|
+
updates.webAppName = WEB_APP_NAME;
|
|
49
|
+
}
|
|
50
|
+
} else if (apps[0].displayName !== WEB_APP_NAME) {
|
|
51
|
+
if (options.dryRun) {
|
|
52
|
+
dryRunPlan(`rename web app ${apps[0].displayName} → ${WEB_APP_NAME}`);
|
|
53
|
+
} else {
|
|
54
|
+
try {
|
|
55
|
+
await api.updateWebAppDisplayName(projectId, apps[0].appId, WEB_APP_NAME);
|
|
56
|
+
console.log(` ${chalk.green('✓')} Renamed web app: ${chalk.cyan(apps[0].displayName)} → ${chalk.cyan(WEB_APP_NAME)}`);
|
|
57
|
+
updates.webAppName = WEB_APP_NAME;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.log(` ${chalk.yellow('⚠')} Could not rename web app${chalk.dim(`: ${error.message}`)}`);
|
|
60
|
+
updates.webAppName = apps[0].displayName;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
console.log(` ${chalk.green('✓')} Web app: ${chalk.cyan(WEB_APP_NAME)}`);
|
|
65
|
+
updates.webAppName = WEB_APP_NAME;
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.log(` ${chalk.yellow('⚠')} Could not configure web app${chalk.dim(`: ${error.message}`)}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { state: { settings: updates } };
|
|
72
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the web SDK config is fetched into state — and that the brand's
|
|
3
|
+
* omega.json5 `cloud.config` section matches it.
|
|
4
|
+
*
|
|
5
|
+
* authDomain is the BRAND host (Ian's ruling 2026-07-23): a first-party
|
|
6
|
+
* authDomain keeps signInWithRedirect working under browser storage
|
|
7
|
+
* partitioning, and the site self-hosts Firebase's /__/auth/* helper files
|
|
8
|
+
* at build time (`omega build` fetches them from
|
|
9
|
+
* {projectId}.firebaseapp.com), so the handler no longer 404s on site
|
|
10
|
+
* domains (the 2026-07-19 live find). The API base derives from brand.url,
|
|
11
|
+
* never authDomain.
|
|
12
|
+
* omega.json5 is the fetched config's ONE home: drift against it is written
|
|
13
|
+
* back into the file key-by-key (comment-preserving), so per-key comments
|
|
14
|
+
* survive. Dry-run prints the paste-able block instead and warns.
|
|
15
|
+
*/
|
|
16
|
+
const chalk = require('chalk').default;
|
|
17
|
+
const { writeBrandConfig } = require('../../../lib/config-write.js');
|
|
18
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
19
|
+
|
|
20
|
+
// The canonical cloud.config keys frameworks read from omega.json5
|
|
21
|
+
const SDK_KEYS = ['apiKey', 'authDomain', 'databaseURL', 'projectId', 'storageBucket', 'messagingSenderId', 'appId', 'measurementId'];
|
|
22
|
+
|
|
23
|
+
module.exports = async function ensureSdkConfig(context) {
|
|
24
|
+
const { firebaseApi: api, brandConfig, projectId, domain, options = {} } = context;
|
|
25
|
+
|
|
26
|
+
// === READ (web app must exist; project-settings creates it too) ===
|
|
27
|
+
const apps = await api.listWebApps(projectId);
|
|
28
|
+
let appId = apps[0]?.appId;
|
|
29
|
+
|
|
30
|
+
if (!appId) {
|
|
31
|
+
if (options.dryRun) {
|
|
32
|
+
return dryRunPlan('create a web app and fetch its SDK config', { output: { sdkConfig: { planned: 'create-web-app' } } });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(' Creating web app...');
|
|
36
|
+
appId = await api.createWebApp(projectId, 'Web App');
|
|
37
|
+
|
|
38
|
+
if (!appId) {
|
|
39
|
+
console.log(` ${chalk.yellow('⚠')} Could not get or create a web app`);
|
|
40
|
+
return { status: 'warned', reason: 'could not get or create a web app', output: { sdkConfig: { error: 'no web app' } } };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const raw = await api.getWebAppConfig(projectId, appId);
|
|
45
|
+
|
|
46
|
+
const sdkConfig = {
|
|
47
|
+
apiKey: raw.apiKey,
|
|
48
|
+
authDomain: domain || raw.authDomain, // brand host — the build self-hosts /__/auth/* (header comment)
|
|
49
|
+
databaseURL: raw.databaseURL || `https://${projectId}-default-rtdb.firebaseio.com`,
|
|
50
|
+
projectId: raw.projectId,
|
|
51
|
+
storageBucket: raw.storageBucket,
|
|
52
|
+
messagingSenderId: raw.messagingSenderId,
|
|
53
|
+
appId: raw.appId,
|
|
54
|
+
measurementId: raw.measurementId || '',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
console.log(` ${chalk.green('✓')} Got SDK config ${chalk.dim(`(apiKey: ${sdkConfig.apiKey.substring(0, 10)}...)`)}`);
|
|
58
|
+
|
|
59
|
+
// === Drift check: omega.json5's cloud.config is what the targets run on ===
|
|
60
|
+
const configured = brandConfig.cloud?.config || {};
|
|
61
|
+
const drifted = SDK_KEYS.filter((key) => (configured[key] || '') !== (sdkConfig[key] || ''));
|
|
62
|
+
|
|
63
|
+
if (drifted.length === 0) {
|
|
64
|
+
console.log(` ${chalk.green('✓')} omega.json5 cloud.config matches`);
|
|
65
|
+
return { state: { sdkConfig } };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(` ${chalk.yellow('↻')} omega.json5 cloud.config ${Object.keys(configured).length === 0 ? 'is missing' : `drifts (${drifted.join(', ')})`}`);
|
|
69
|
+
|
|
70
|
+
const edits = {};
|
|
71
|
+
if (brandConfig.cloud?.provider !== 'firebase') {
|
|
72
|
+
edits['cloud.provider'] = 'firebase';
|
|
73
|
+
}
|
|
74
|
+
for (const key of drifted) {
|
|
75
|
+
edits[`cloud.config.${key}`] = sdkConfig[key];
|
|
76
|
+
}
|
|
77
|
+
writeBrandConfig(context, edits);
|
|
78
|
+
|
|
79
|
+
if (options.dryRun) {
|
|
80
|
+
// The file wasn't touched — hand over the paste block
|
|
81
|
+
console.log(` ${chalk.dim('→')} Set this in config/omega.json5:`);
|
|
82
|
+
console.log(chalk.cyan(` cloud: { provider: 'firebase', config: ${JSON.stringify(sdkConfig, null, 2).replace(/\n/g, '\n ')} },`));
|
|
83
|
+
return {
|
|
84
|
+
status: 'warned',
|
|
85
|
+
reason: 'dry run — the SDK config in config/omega.json5 is out of date',
|
|
86
|
+
state: { sdkConfig },
|
|
87
|
+
output: { sdkConfig: { drifted } },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
state: { sdkConfig },
|
|
93
|
+
output: { sdkConfig: { updated: drifted } },
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the Firebase Admin SDK service account exists with the roles @omega.js/backend
|
|
3
|
+
* needs, and its key is downloaded.
|
|
4
|
+
*
|
|
5
|
+
* Google shows service account keys ONCE at creation — they can't be
|
|
6
|
+
* re-downloaded. The key's ONE home is the brand's gitignored
|
|
7
|
+
* .omega/secrets/service-account.json — the backend's stage step (`omega
|
|
8
|
+
* build`, src/dist pillar) reads it from there and carries it into the
|
|
9
|
+
* staged dist/ tree; no per-target copy exists anymore.
|
|
10
|
+
*
|
|
11
|
+
* The IAM role grant diffs the policy first (omega-manager PUT the policy on
|
|
12
|
+
* every run) — a converged account is a zero-mutation no-op.
|
|
13
|
+
*/
|
|
14
|
+
const { join } = require('node:path');
|
|
15
|
+
const chalk = require('chalk').default;
|
|
16
|
+
const jetpack = require('fs-jetpack');
|
|
17
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
18
|
+
|
|
19
|
+
const REQUIRED_ROLES = [
|
|
20
|
+
'roles/firebase.admin',
|
|
21
|
+
'roles/firebaseauth.admin',
|
|
22
|
+
'roles/datastore.owner',
|
|
23
|
+
'roles/serviceusage.serviceUsageConsumer',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Grant any missing REQUIRED_ROLES to the account. Reads the policy, diffs,
|
|
28
|
+
* and only writes when a binding actually changed.
|
|
29
|
+
*/
|
|
30
|
+
async function ensureRoles(api, projectId, serviceAccountEmail, options) {
|
|
31
|
+
const member = `serviceAccount:${serviceAccountEmail}`;
|
|
32
|
+
const policy = await api.getIamPolicy(projectId);
|
|
33
|
+
let changed = false;
|
|
34
|
+
|
|
35
|
+
for (const role of REQUIRED_ROLES) {
|
|
36
|
+
let binding = policy.bindings?.find((b) => b.role === role);
|
|
37
|
+
if (!binding) {
|
|
38
|
+
binding = { role, members: [] };
|
|
39
|
+
policy.bindings = policy.bindings || [];
|
|
40
|
+
policy.bindings.push(binding);
|
|
41
|
+
}
|
|
42
|
+
if (!binding.members.includes(member)) {
|
|
43
|
+
binding.members.push(member);
|
|
44
|
+
changed = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!changed) {
|
|
49
|
+
console.log(` ${chalk.green('✓')} IAM roles verified`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (options.dryRun) {
|
|
54
|
+
return dryRunPlan('grant missing IAM roles');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await api.setIamPolicy(projectId, policy);
|
|
58
|
+
console.log(` ${chalk.green('✓')} Granted IAM roles`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = async function ensureServiceAccount(context) {
|
|
62
|
+
const { firebaseApi: api, brandConfig, brandRoot, projectId, options = {} } = context;
|
|
63
|
+
const brandName = brandConfig.brand?.name || context.brandId;
|
|
64
|
+
|
|
65
|
+
// The key's ONE home — the backend stage step reads it from here
|
|
66
|
+
const sourceKeyPath = join(brandRoot, '.omega', 'secrets', 'service-account.json');
|
|
67
|
+
|
|
68
|
+
// === Key already downloaded — verify roles ===
|
|
69
|
+
if (jetpack.exists(sourceKeyPath)) {
|
|
70
|
+
const existingKey = jetpack.read(sourceKeyPath, 'json');
|
|
71
|
+
const serviceAccountEmail = existingKey?.client_email;
|
|
72
|
+
|
|
73
|
+
console.log(` ${chalk.green('✓')} Service account key exists`);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await ensureRoles(api, projectId, serviceAccountEmail, options);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.log(` ${chalk.yellow('⚠')} Could not verify roles${chalk.dim(`: ${error.message}`)}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { state: { serviceAccount: { email: serviceAccountEmail } } };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// === Find or create the firebase-adminsdk account ===
|
|
85
|
+
// Firebase Console creates these with random suffixes (firebase-adminsdk-abc12@…)
|
|
86
|
+
const existingAccounts = await api.listServiceAccounts(projectId);
|
|
87
|
+
const existing = existingAccounts.find((a) => a.email.startsWith('firebase-adminsdk'));
|
|
88
|
+
let serviceAccountEmail;
|
|
89
|
+
|
|
90
|
+
if (existing) {
|
|
91
|
+
serviceAccountEmail = existing.email;
|
|
92
|
+
console.log(` ${chalk.green('✓')} Service account exists`);
|
|
93
|
+
} else {
|
|
94
|
+
serviceAccountEmail = `firebase-adminsdk@${projectId}.iam.gserviceaccount.com`;
|
|
95
|
+
|
|
96
|
+
if (options.dryRun) {
|
|
97
|
+
return dryRunPlan('create service account + key', { output: { serviceAccount: { planned: 'create' } } });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log(' Creating service account...');
|
|
101
|
+
try {
|
|
102
|
+
await api.createServiceAccount(projectId, 'firebase-adminsdk', `Firebase Admin SDK Service Account for ${brandName}`);
|
|
103
|
+
console.log(` ${chalk.green('✓')} Created service account`);
|
|
104
|
+
|
|
105
|
+
// Eventual consistency — the account isn't immediately grantable
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (!error.message?.includes('already exists')) {
|
|
109
|
+
console.log(` ${chalk.yellow('⚠')} Could not create service account${chalk.dim(`: ${error.message}`)}`);
|
|
110
|
+
return { status: 'warned', reason: 'could not create the service account', output: { serviceAccount: { error: error.message } } };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await ensureRoles(api, projectId, serviceAccountEmail, options);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.log(` ${chalk.yellow('⚠')} Could not verify roles${chalk.dim(`: ${error.message}`)}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// === Create + download a new key (up to 10 per account) ===
|
|
122
|
+
if (options.dryRun) {
|
|
123
|
+
dryRunPlan('create + download a service account key');
|
|
124
|
+
return {
|
|
125
|
+
state: { serviceAccount: { email: serviceAccountEmail } },
|
|
126
|
+
output: { serviceAccount: { planned: 'create-key' } },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(' Creating service account key...');
|
|
131
|
+
try {
|
|
132
|
+
const keyData = await api.createServiceAccountKey(projectId, serviceAccountEmail);
|
|
133
|
+
|
|
134
|
+
jetpack.write(sourceKeyPath, keyData);
|
|
135
|
+
console.log(` ${chalk.green('✓')} Saved key to ${chalk.cyan('.omega/secrets/')}`);
|
|
136
|
+
|
|
137
|
+
return { state: { serviceAccount: { email: serviceAccountEmail } } };
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error.message?.includes('not allowed') || error.message?.includes('policy')) {
|
|
140
|
+
console.log(` ${chalk.yellow('⚠')} Key creation blocked by organization policy`);
|
|
141
|
+
console.log(` ${chalk.dim('→')} Download manually: ${chalk.cyan(`https://console.firebase.google.com/project/${projectId}/settings/serviceaccounts/adminsdk`)}`);
|
|
142
|
+
} else {
|
|
143
|
+
console.log(` ${chalk.yellow('⚠')} Could not create key${chalk.dim(`: ${error.message}`)}`);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
status: 'warned',
|
|
147
|
+
reason: 'could not create the service account key',
|
|
148
|
+
state: { serviceAccount: { email: serviceAccountEmail } },
|
|
149
|
+
output: { serviceAccount: { error: error.message } },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure required Google Cloud APIs are enabled + the compute service account
|
|
3
|
+
* carries the roles Cloud Functions deploys need.
|
|
4
|
+
*
|
|
5
|
+
* omega-manager batch-enabled the full list on every run; this reads the
|
|
6
|
+
* enabled set first and only enables what's missing, so a converged project
|
|
7
|
+
* is a zero-mutation no-op. The IAM grant was already diff-based — kept.
|
|
8
|
+
*/
|
|
9
|
+
const chalk = require('chalk').default;
|
|
10
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
11
|
+
|
|
12
|
+
const REQUIRED_SERVICES = [
|
|
13
|
+
'serviceusage.googleapis.com', // Must be first — Firebase CLI v15.20.0+ pre-flight checks require it
|
|
14
|
+
'firebase.googleapis.com',
|
|
15
|
+
'firestore.googleapis.com',
|
|
16
|
+
'firebasestorage.googleapis.com',
|
|
17
|
+
'firebasehosting.googleapis.com',
|
|
18
|
+
'firebasedatabase.googleapis.com',
|
|
19
|
+
'identitytoolkit.googleapis.com',
|
|
20
|
+
'cloudfunctions.googleapis.com',
|
|
21
|
+
'cloudbuild.googleapis.com',
|
|
22
|
+
'run.googleapis.com',
|
|
23
|
+
'artifactregistry.googleapis.com',
|
|
24
|
+
'iap.googleapis.com', // OAuth consent screen
|
|
25
|
+
'recaptchaenterprise.googleapis.com',
|
|
26
|
+
'fcm.googleapis.com', // Firebase Cloud Messaging V1 API
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// Roles the default compute service account needs for Cloud Functions deploys
|
|
30
|
+
const COMPUTE_ROLES = [
|
|
31
|
+
'roles/storage.objectAdmin',
|
|
32
|
+
'roles/cloudbuild.builds.builder',
|
|
33
|
+
'roles/artifactregistry.writer',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
module.exports = async function ensureServices(context) {
|
|
37
|
+
const { firebaseApi: api, projectId, options = {} } = context;
|
|
38
|
+
|
|
39
|
+
// === READ ===
|
|
40
|
+
const enabledServices = await api.listEnabledServices(projectId);
|
|
41
|
+
const missing = REQUIRED_SERVICES.filter((s) => !enabledServices.includes(s));
|
|
42
|
+
|
|
43
|
+
// === WRITE: enable missing APIs ===
|
|
44
|
+
if (missing.length === 0) {
|
|
45
|
+
console.log(` ${chalk.green('✓')} All ${chalk.bold(REQUIRED_SERVICES.length)} required APIs enabled`);
|
|
46
|
+
} else if (options.dryRun) {
|
|
47
|
+
dryRunPlan(`enable ${missing.length} API(s): ${missing.join(', ')}`);
|
|
48
|
+
} else {
|
|
49
|
+
console.log(` Enabling ${chalk.bold(missing.length)} missing API(s)...`);
|
|
50
|
+
try {
|
|
51
|
+
await api.enableServices(projectId, missing);
|
|
52
|
+
console.log(` ${chalk.green('✓')} Enabled ${chalk.bold(missing.length)} APIs`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Batch can fail on partially-restricted projects — fall back per-service
|
|
55
|
+
console.log(` ${chalk.yellow('⚠')} Batch enable failed, trying individually...`);
|
|
56
|
+
|
|
57
|
+
for (const service of missing) {
|
|
58
|
+
try {
|
|
59
|
+
await api.enableService(projectId, service);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.log(` ${chalk.yellow('⚠')} ${chalk.cyan(service)}${chalk.dim(`: ${err.message}`)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// === WRITE: compute service account roles (diff-based) ===
|
|
68
|
+
try {
|
|
69
|
+
const projectNumber = await api.getProjectNumber(projectId);
|
|
70
|
+
const member = `serviceAccount:${projectNumber}-compute@developer.gserviceaccount.com`;
|
|
71
|
+
|
|
72
|
+
const policy = await api.getIamPolicy(projectId);
|
|
73
|
+
let changed = false;
|
|
74
|
+
|
|
75
|
+
for (const role of COMPUTE_ROLES) {
|
|
76
|
+
const binding = policy.bindings?.find((b) => b.role === role);
|
|
77
|
+
if (binding) {
|
|
78
|
+
if (!binding.members?.includes(member)) {
|
|
79
|
+
binding.members.push(member);
|
|
80
|
+
changed = true;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
policy.bindings = policy.bindings || [];
|
|
84
|
+
policy.bindings.push({ role, members: [member] });
|
|
85
|
+
changed = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (changed) {
|
|
90
|
+
if (options.dryRun) {
|
|
91
|
+
dryRunPlan('grant compute service account deploy roles');
|
|
92
|
+
} else {
|
|
93
|
+
await api.setIamPolicy(projectId, policy);
|
|
94
|
+
console.log(` ${chalk.green('✓')} Granted compute service account deploy roles`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// Non-fatal — the account may already have the roles or the project structure differs
|
|
99
|
+
console.log(` ${chalk.yellow('⚠')} Could not verify compute roles${chalk.dim(`: ${error.message}`)}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { output: { services: { required: REQUIRED_SERVICES.length, enabled: missing.length } } };
|
|
103
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the Firebase Storage default bucket exists and is linked
|
|
3
|
+
* (new buckets: {projectId}.firebasestorage.app; pre-Oct-2024:
|
|
4
|
+
* {projectId}.appspot.com).
|
|
5
|
+
*/
|
|
6
|
+
const chalk = require('chalk').default;
|
|
7
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
8
|
+
|
|
9
|
+
module.exports = async function ensureStorage(context) {
|
|
10
|
+
const { firebaseApi: api, projectId, options = {} } = context;
|
|
11
|
+
|
|
12
|
+
// === READ ===
|
|
13
|
+
const existing = await api.getStorageBucket(projectId);
|
|
14
|
+
|
|
15
|
+
if (existing) {
|
|
16
|
+
console.log(` ${chalk.green('✓')} Storage bucket exists: ${chalk.cyan(existing.name)}`);
|
|
17
|
+
return { state: { storage: { bucket: existing.name } } };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// === WRITE ===
|
|
21
|
+
if (options.dryRun) {
|
|
22
|
+
return dryRunPlan('create the default storage bucket', { output: { storage: { planned: 'create' } } });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(' Creating storage bucket...');
|
|
26
|
+
try {
|
|
27
|
+
const result = await api.createDefaultStorageBucket(projectId);
|
|
28
|
+
|
|
29
|
+
if (result.alreadyLinked) {
|
|
30
|
+
console.log(` ${chalk.green('✓')} Storage bucket exists: ${chalk.cyan(result.bucket)}`);
|
|
31
|
+
} else {
|
|
32
|
+
console.log(` ${chalk.green('✓')} Storage bucket created: ${chalk.cyan(result.bucket)}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { state: { storage: { bucket: result.bucket } } };
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.log(` ${chalk.yellow('⚠')} Could not create storage bucket${chalk.dim(`: ${error.message}`)}`);
|
|
38
|
+
|
|
39
|
+
if (error.message?.includes('BILLING')) {
|
|
40
|
+
console.log(` ${chalk.dim('→')} Project must be on the Blaze plan to create storage buckets`);
|
|
41
|
+
} else {
|
|
42
|
+
console.log(` ${chalk.dim('→')} Create manually: ${chalk.cyan(`https://console.firebase.google.com/project/${projectId}/storage`)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { status: 'warned', reason: 'could not create the storage bucket', output: { storage: { error: error.message } } };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud service (Firebase provider) — reconciles the brand's Firebase/GCP project to
|
|
3
|
+
* `cloud: {}` in omega.json5: Blaze billing, required Google Cloud APIs,
|
|
4
|
+
* project identity, OAuth consent screen, Admin SDK service account + key,
|
|
5
|
+
* Hosting api.{domain} custom domains (DNS via Cloudflare), Firestore (+PITR),
|
|
6
|
+
* Realtime Database, Authentication (Identity Platform + sign-in methods),
|
|
7
|
+
* Storage, Cloud Functions readiness, Cloud Messaging, and the web SDK config.
|
|
8
|
+
*
|
|
9
|
+
* Auth: GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET in the brand .env (OAuth2;
|
|
10
|
+
* tokens cache to .omega/auth/google-tokens.json — the first run prints an
|
|
11
|
+
* auth URL). Missing credentials go through the shared setup contract (#608):
|
|
12
|
+
* an interactive run asks for them right here, a headless one steps aside
|
|
13
|
+
* loudly. No cloud.config.projectId → interactive runs offer the project
|
|
14
|
+
* selection/creation flow (lib/project-flow.js, lands the id in omega.json5);
|
|
15
|
+
* otherwise skip with guidance.
|
|
16
|
+
*
|
|
17
|
+
* `cloud.shared: true` (project shared by multiple brands) filters to the
|
|
18
|
+
* per-brand operations only (service-account, sdk-config) so one brand never
|
|
19
|
+
* rewrites a shared project's settings.
|
|
20
|
+
*/
|
|
21
|
+
const { googleTokenStorePath } = require('../../lib/google-auth.js');
|
|
22
|
+
const { ensureProjectAccess } = require('./lib/access-heal.js');
|
|
23
|
+
const { isDemoProject } = require('../../vendor/config/index.js');
|
|
24
|
+
const chalk = require('chalk').default;
|
|
25
|
+
const { serviceInputSpec } = require('../../config.js');
|
|
26
|
+
const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
27
|
+
const { requestServiceInput } = require('../../lib/service-input.js');
|
|
28
|
+
const { CloudflareAPI } = require('../edge/lib/cloudflare-api.js');
|
|
29
|
+
const { getApexDomain } = require('../../lib/domain-utils.js');
|
|
30
|
+
const { FirebaseAPI } = require('./lib/firebase-api.js');
|
|
31
|
+
const { resolveFirebaseProject } = require('./lib/project-flow.js');
|
|
32
|
+
|
|
33
|
+
// Operations that stay on for shared projects (per-brand, not project-level)
|
|
34
|
+
const SHARED_OPERATIONS = new Set(['service-account', 'sdk-config']);
|
|
35
|
+
|
|
36
|
+
module.exports.run = createServiceRunner({
|
|
37
|
+
serviceDir: __dirname,
|
|
38
|
+
setup: async (context) => {
|
|
39
|
+
const cloud = context.brandConfig.cloud || {};
|
|
40
|
+
|
|
41
|
+
if (cloud.enabled === false) {
|
|
42
|
+
return { skip: true, reason: 'cloud.enabled = false' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The shared setup contract (#608): an interactive run asks for the
|
|
46
|
+
// Google OAuth client right here — provide, skip this run, or disable the
|
|
47
|
+
// service for good — before anything decides there is nothing to do.
|
|
48
|
+
let gate = null;
|
|
49
|
+
if (!context.firebaseApi) {
|
|
50
|
+
gate = await requestServiceInput(context, serviceInputSpec('cloud'));
|
|
51
|
+
}
|
|
52
|
+
const haveCreds = Boolean(
|
|
53
|
+
context.firebaseApi
|
|
54
|
+
|| (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Tests inject fake clients via context.firebaseApi / context.cloudflareApi.
|
|
58
|
+
// Cloudflare is only needed by the hosting operation (custom-domain DNS);
|
|
59
|
+
// without a token the operation reports the required records instead.
|
|
60
|
+
const makeApi = () => context.firebaseApi || new FirebaseAPI({
|
|
61
|
+
tokenStorePath: googleTokenStorePath(context.brandRoot),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ONE home (#23): cloud.config.projectId, where the framework flow
|
|
65
|
+
// (setup/dev) already names the project. Missing → offer the interactive
|
|
66
|
+
// selection/creation flow (lands it in omega.json5); needs credentials
|
|
67
|
+
let projectId = cloud.config?.projectId;
|
|
68
|
+
if (!projectId && haveCreds) {
|
|
69
|
+
projectId = await resolveFirebaseProject(context, makeApi());
|
|
70
|
+
}
|
|
71
|
+
if (!projectId) {
|
|
72
|
+
return { skip: true, reason: 'no cloud.config.projectId configured (rerun interactively to select/create the project)' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// demo-* = emulator-only by Firebase's own convention: no real GCP
|
|
76
|
+
// project exists to reconcile (found live 2026-07-19 — the omega brand's
|
|
77
|
+
// offline demo-omega id sent the ensure at real Google APIs → 403,
|
|
78
|
+
// killing the whole manage boot).
|
|
79
|
+
if (isDemoProject(projectId)) {
|
|
80
|
+
return { skip: true, reason: `${projectId} is a demo-* (emulator-only) project — no real cloud to reconcile` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!haveCreds) {
|
|
84
|
+
return gate || { skip: true, reason: 'no GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET configured (set them in the brand .env)' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Hostname-only (cp268): this value feeds authDomain + api.{domain} — a
|
|
88
|
+
// brand.url carrying a path or port must never leak them into either.
|
|
89
|
+
const brandUrl = context.brandConfig.brand?.url;
|
|
90
|
+
const domain = brandUrl ? new URL(brandUrl).hostname : '';
|
|
91
|
+
if (!domain) {
|
|
92
|
+
return { skip: true, reason: 'no brand.url configured' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const firebaseApi = makeApi();
|
|
96
|
+
|
|
97
|
+
// The identity seam self-heals HERE, before any op runs (Ian
|
|
98
|
+
// 2026-07-19: wrapped in `npm start`, never a hand-run command): a
|
|
99
|
+
// manage identity with no role on the project gets granted owner via a
|
|
100
|
+
// local gcloud account that can, and the run proceeds normally.
|
|
101
|
+
// Idempotent — an accessible project is one probe and done.
|
|
102
|
+
const access = await ensureProjectAccess({
|
|
103
|
+
firebaseApi,
|
|
104
|
+
projectId,
|
|
105
|
+
...(context.gcloudExec ? { exec: context.gcloudExec, delayMs: 0 } : {}),
|
|
106
|
+
});
|
|
107
|
+
if (access.healed) {
|
|
108
|
+
console.log(` ${chalk.green('✓')} Access healed — ${chalk.cyan(access.grantor)} granted ${chalk.cyan(access.manageEmail)} ${chalk.cyan(access.roles.join(' + '))} on ${chalk.cyan(projectId)}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const shared = cloud.shared === true;
|
|
112
|
+
console.log(` Project: ${chalk.cyan(projectId)}${shared ? chalk.dim(' (shared)') : ''}`);
|
|
113
|
+
const cloudflareApi = context.cloudflareApi
|
|
114
|
+
|| (process.env.CLOUDFLARE_TOKEN ? new CloudflareAPI() : null);
|
|
115
|
+
|
|
116
|
+
const apexDomain = getApexDomain(domain);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
firebaseApi,
|
|
120
|
+
cloudflareApi,
|
|
121
|
+
projectId,
|
|
122
|
+
domain,
|
|
123
|
+
apexDomain,
|
|
124
|
+
isSubdomainProject: domain !== apexDomain,
|
|
125
|
+
operations: shared
|
|
126
|
+
? context.operations.filter((op) => SHARED_OPERATIONS.has(op.name))
|
|
127
|
+
: context.operations,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
});
|