@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,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Firebase project selection/creation (config-landing flow):
|
|
3
|
+
* when cloud.config.projectId is missing in an interactive run, pick from the
|
|
4
|
+
* projects the authed user can see, or create one — project id + display
|
|
5
|
+
* name prompted (brand id/name as defaults), with the 30-project quota
|
|
6
|
+
* shown. The chosen id lands in omega.json5 (comment-preserving writeback).
|
|
7
|
+
*
|
|
8
|
+
* The organization is tri-state (#33): cloud.organizationId set → the
|
|
9
|
+
* project is created inside it (that's what gives the compute service
|
|
10
|
+
* account its default roles); `false` → standalone, no questions; missing →
|
|
11
|
+
* pick from the orgs the authed user can see or opt out — either answer
|
|
12
|
+
* lands in omega.json5, so it's a one-time question. Downloaders without an
|
|
13
|
+
* org onboard cleanly.
|
|
14
|
+
*/
|
|
15
|
+
const chalk = require('chalk').default;
|
|
16
|
+
const { input } = require('../../../vendor/devkit/prompt.js');
|
|
17
|
+
const { resolveConfigValue } = require('../../../lib/config-flow.js');
|
|
18
|
+
|
|
19
|
+
const PROJECT_QUOTA = 30;
|
|
20
|
+
const PROJECT_ID_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve (or interactively land) the organization for a NEW project.
|
|
24
|
+
* Chained inside the create flow the user already said Yes to, so there is
|
|
25
|
+
* no Yes/Skip gate — just the selection (orgs + a standalone opt-out).
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} context - Service context
|
|
28
|
+
* @param {Object} api - FirebaseAPI client
|
|
29
|
+
* @returns {Promise<string|null>} - organization id, or null for standalone
|
|
30
|
+
*/
|
|
31
|
+
async function resolveOrganization(context, api) {
|
|
32
|
+
return resolveConfigValue(context, {
|
|
33
|
+
path: 'cloud.organizationId',
|
|
34
|
+
label: 'Google Cloud organization',
|
|
35
|
+
gate: false,
|
|
36
|
+
message: 'Create the project inside a Google Cloud organization?',
|
|
37
|
+
choices: () => api.listOrganizations(),
|
|
38
|
+
getName: (org) => `${org.displayName} (${org.name.replace('organizations/', '')})`,
|
|
39
|
+
getValue: (org) => org.name.replace('organizations/', ''),
|
|
40
|
+
optOut: { label: 'No organization — create it standalone' },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve (or interactively land) the Firebase project id.
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} context - Service context
|
|
48
|
+
* @param {Object} api - FirebaseAPI client
|
|
49
|
+
* @returns {Promise<string|null>} - projectId, or null when skipped
|
|
50
|
+
*/
|
|
51
|
+
async function resolveFirebaseProject(context, api) {
|
|
52
|
+
return resolveConfigValue(context, {
|
|
53
|
+
path: 'cloud.config.projectId',
|
|
54
|
+
label: 'Firebase project',
|
|
55
|
+
choices: () => api.listProjects(),
|
|
56
|
+
getName: (project) => `${project.displayName} (${project.projectId})`,
|
|
57
|
+
getValue: (project) => project.projectId,
|
|
58
|
+
createNew: {
|
|
59
|
+
label: 'Firebase project',
|
|
60
|
+
handler: async () => {
|
|
61
|
+
const { brand } = context.brandConfig;
|
|
62
|
+
|
|
63
|
+
const projects = await api.listProjects();
|
|
64
|
+
const remaining = PROJECT_QUOTA - projects.length;
|
|
65
|
+
const color = remaining <= 5 ? chalk.yellow : chalk.dim;
|
|
66
|
+
const icon = remaining <= 5 ? '⚠' : '→';
|
|
67
|
+
console.log(` ${color(`${icon} Firebase project quota: ${projects.length}/${PROJECT_QUOTA} (${remaining} remaining)`)}`);
|
|
68
|
+
|
|
69
|
+
const projectId = await input({
|
|
70
|
+
message: 'Enter project ID (lowercase, numbers, hyphens only):',
|
|
71
|
+
default: brand.id,
|
|
72
|
+
validate: (val) => {
|
|
73
|
+
if (!val?.trim()) {
|
|
74
|
+
return 'Project ID is required';
|
|
75
|
+
}
|
|
76
|
+
if (!PROJECT_ID_PATTERN.test(val)) {
|
|
77
|
+
return 'Project ID must be 6-30 chars, start with a letter, only lowercase letters, numbers, hyphens';
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const displayName = await input({
|
|
84
|
+
message: 'Enter display name:',
|
|
85
|
+
default: brand.name,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const organizationId = await resolveOrganization(context, api);
|
|
89
|
+
console.log(` Creating Firebase project ${chalk.cyan(projectId)}...`);
|
|
90
|
+
if (organizationId) {
|
|
91
|
+
console.log(` ${chalk.dim('→')} Creating inside organization ${chalk.cyan(organizationId)}`);
|
|
92
|
+
}
|
|
93
|
+
await api.createProject(projectId, displayName, organizationId);
|
|
94
|
+
console.log(` ${chalk.green('✓')} Created`);
|
|
95
|
+
|
|
96
|
+
return projectId;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { resolveFirebaseProject };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push the brand's directory entry to the parent project's Firestore at
|
|
3
|
+
* brands/{brand.id}: identity plus the blocks the brand declares.
|
|
4
|
+
*
|
|
5
|
+
* Diff FIRST — an unchanged config performs zero writes, which is what makes
|
|
6
|
+
* this safe to run on every manage walk.
|
|
7
|
+
*
|
|
8
|
+
* MERGE-write, not replace (the server service's registry is a replace, and
|
|
9
|
+
* the difference is deliberate): the framework owns only its own sections of
|
|
10
|
+
* the entry, so the write masks exactly those. Whatever the hub keeps on the
|
|
11
|
+
* same document — order counts, moderation state, anything ITW's marketplace
|
|
12
|
+
* adds — survives every push, and a section the brand drops from config is
|
|
13
|
+
* still removed, because the mask names every owned section whether or not it
|
|
14
|
+
* has a value this run.
|
|
15
|
+
*/
|
|
16
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
17
|
+
const chalk = require('chalk').default;
|
|
18
|
+
|
|
19
|
+
const { OWNED_SECTIONS, buildEntry, ownedSlice } = require('../lib/blocks.js');
|
|
20
|
+
|
|
21
|
+
module.exports = async function ensureEntry(context) {
|
|
22
|
+
const { brandConfig, db, options } = context;
|
|
23
|
+
const dryRun = options?.dryRun || false;
|
|
24
|
+
|
|
25
|
+
const docPath = `brands/${brandConfig.brand.id}`;
|
|
26
|
+
const desired = buildEntry(brandConfig);
|
|
27
|
+
const blocks = Object.keys(desired).filter((key) => key !== 'brand' && key !== 'github');
|
|
28
|
+
|
|
29
|
+
const current = await db.getDoc(docPath);
|
|
30
|
+
|
|
31
|
+
if (isDeepStrictEqual(ownedSlice(current), desired)) {
|
|
32
|
+
console.log(` ${chalk.green('✓')} Directory entry ${chalk.dim(docPath)} in sync${blocks.length > 0 ? chalk.dim(` (${blocks.join(', ')})`) : ''}`);
|
|
33
|
+
return { output: { entry: { synced: true, blocks } } };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const action = current ? 'update' : 'create';
|
|
37
|
+
|
|
38
|
+
if (dryRun) {
|
|
39
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would ${action} directory entry ${chalk.cyan(docPath)}`);
|
|
40
|
+
return { output: { entry: { planned: action, blocks } } };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await db.patchDoc(docPath, desired, OWNED_SECTIONS);
|
|
44
|
+
|
|
45
|
+
console.log(` ${chalk.green('✓')} Directory entry ${chalk.dim(docPath)} ${action}d${blocks.length > 0 ? chalk.dim(` (${blocks.join(', ')})`) : ''}`);
|
|
46
|
+
|
|
47
|
+
return { output: { entry: { [`${action}d`]: true, blocks } } };
|
|
48
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory service — the brand PUSHES its own entry into the PARENT
|
|
3
|
+
* project's Firestore `brands` collection (`brands/{brand.id}`) during its
|
|
4
|
+
* manage walk, so whatever the parent runs on top of that collection reads a
|
|
5
|
+
* current directory instead of one that goes stale the day a brand migrates.
|
|
6
|
+
*
|
|
7
|
+
* GENERIC and opt-in (#246). The framework owes the fresh ENTRY and nothing
|
|
8
|
+
* else: the marketplace/hub that consumes it — ITW's guest-post sponsorship
|
|
9
|
+
* marketplace is the first — is brand code and never enters the framework.
|
|
10
|
+
* The payload is brand identity plus the opt-in BLOCKS the brand declares
|
|
11
|
+
* (lib/blocks.js); `sponsorships` is the first block. Field mapping from
|
|
12
|
+
* legacy omega-manager: docs/manager/directory.md.
|
|
13
|
+
*
|
|
14
|
+
* Gates, in order — every one of them is a clean skip:
|
|
15
|
+
* 1. `directory.enabled: true`. Absent config never pushes: the parent
|
|
16
|
+
* declares the collection world-readable in its own rules (the operator
|
|
17
|
+
* step in docs/manager/directory.md), so participation is explicit or
|
|
18
|
+
* nothing.
|
|
19
|
+
* 2. `parent` names the relationship. No parent (or `parent: false`) means
|
|
20
|
+
* there is no directory to push INTO.
|
|
21
|
+
* 3. Not a demo-* (emulator-only) brand — the cloud service's gate, for the
|
|
22
|
+
* same reason: an offline fixture must never reach a live project.
|
|
23
|
+
*
|
|
24
|
+
* Auth: DIRECTORY_SERVICE_ACCOUNT in the brand .env — a path to the PARENT
|
|
25
|
+
* project's Firebase service-account JSON (absolute, or relative to the brand
|
|
26
|
+
* root), the server service's mechanism exactly. The service account names the
|
|
27
|
+
* target project; no config key ever holds a credential.
|
|
28
|
+
*/
|
|
29
|
+
const { isDemoProject } = require('../../vendor/config/index.js');
|
|
30
|
+
|
|
31
|
+
const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
32
|
+
const { FirestoreREST, loadServiceAccount } = require('../../lib/firestore-rest.js');
|
|
33
|
+
|
|
34
|
+
module.exports.run = createServiceRunner({
|
|
35
|
+
serviceDir: __dirname,
|
|
36
|
+
setup: async (context) => {
|
|
37
|
+
const { brandConfig } = context;
|
|
38
|
+
|
|
39
|
+
if (brandConfig.directory?.enabled !== true) {
|
|
40
|
+
return { skip: true, reason: 'directory.enabled is not true (the brand does not participate in a directory)' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!brandConfig.parent) {
|
|
44
|
+
return { skip: true, reason: 'no parent configured — there is no parent directory to push into' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const projectId = brandConfig.cloud?.config?.projectId;
|
|
48
|
+
if (isDemoProject(projectId)) {
|
|
49
|
+
return { skip: true, reason: `${projectId} is a demo-* (emulator-only) project — no live directory to push into` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Tests inject a fake client via context.directoryDb
|
|
53
|
+
let db = context.directoryDb;
|
|
54
|
+
if (!db) {
|
|
55
|
+
const envPath = process.env.DIRECTORY_SERVICE_ACCOUNT;
|
|
56
|
+
if (!envPath) {
|
|
57
|
+
return { skip: true, reason: 'no DIRECTORY_SERVICE_ACCOUNT configured (path to the parent project\'s service-account JSON, set it in the brand .env)' };
|
|
58
|
+
}
|
|
59
|
+
db = new FirestoreREST(loadServiceAccount(envPath, context.brandRoot));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { db };
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directory payload — brand IDENTITY plus the opt-in BLOCKS the brand
|
|
3
|
+
* declares — and the list of sections the framework OWNS in the entry.
|
|
4
|
+
*
|
|
5
|
+
* Identity is not optional: a directory entry with no name addresses nothing.
|
|
6
|
+
* The repo slugs come from @omega.js/config's one derivation (#290) — the same
|
|
7
|
+
* `{ owner, name, repo }` the backend hands consumers as
|
|
8
|
+
* `config.resolved.github` — never re-derived here.
|
|
9
|
+
*
|
|
10
|
+
* BLOCKS is the extension point (#246). One entry per publishable block,
|
|
11
|
+
* naming the brand-config section it reads; a block crosses only when the
|
|
12
|
+
* brand DECLARES that section, which is what "opt-in blocks" means. Adding the
|
|
13
|
+
* next block is one entry here plus its schema line in @omega.js/config.
|
|
14
|
+
*
|
|
15
|
+
* Sections are pushed VERBATIM. Nothing is redacted on the way out, because
|
|
16
|
+
* nothing secret can be in them: these are schema-known public config keys and
|
|
17
|
+
* @omega.js/config hard-fails secret-shaped keys before any merge happens.
|
|
18
|
+
*/
|
|
19
|
+
const { brandRepo } = require('../../../vendor/config/index.js');
|
|
20
|
+
|
|
21
|
+
// Block name → the brand-config section it publishes.
|
|
22
|
+
const BLOCKS = {
|
|
23
|
+
// Guest-post / link-insertion terms, priced per placement. Legacy
|
|
24
|
+
// omega-manager carried the same shape at `.brands/<id>/config.json`
|
|
25
|
+
// `sponsorships` — the field mapping is in docs/manager/directory.md.
|
|
26
|
+
sponsorships: (config) => config.sponsorships,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Every top-level field of the entry the framework owns, block or not. The
|
|
30
|
+
// write masks ALL of them, so a section the brand drops from config is
|
|
31
|
+
// removed from the entry too — and every OTHER field of the document (whatever
|
|
32
|
+
// the hub itself owns: order counts, moderation state) survives untouched.
|
|
33
|
+
const OWNED_SECTIONS = ['brand', 'github', ...Object.keys(BLOCKS)];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether a config section is declared richly enough to publish. An absent
|
|
37
|
+
* section and an empty one say the same thing: the brand is not offering it.
|
|
38
|
+
*
|
|
39
|
+
* @param {*} section - The raw config value.
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
function isDeclared(section) {
|
|
43
|
+
return Boolean(section)
|
|
44
|
+
&& typeof section === 'object'
|
|
45
|
+
&& !Array.isArray(section)
|
|
46
|
+
&& Object.keys(section).length > 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build the directory entry for a brand.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} config - The resolved brand config.
|
|
53
|
+
* @returns {object} The entry document — only the sections that resolve.
|
|
54
|
+
*/
|
|
55
|
+
function buildEntry(config) {
|
|
56
|
+
const entry = {
|
|
57
|
+
brand: {
|
|
58
|
+
id: config.brand.id,
|
|
59
|
+
name: config.brand.name,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (config.brand.url) {
|
|
64
|
+
entry.brand.url = config.brand.url;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// '' unless BOTH halves resolve (#290) — half an address addresses nothing,
|
|
68
|
+
// and an entry is better without the key than with a broken one.
|
|
69
|
+
const github = brandRepo(config);
|
|
70
|
+
if (github.repo) {
|
|
71
|
+
entry.github = github;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const [name, read] of Object.entries(BLOCKS)) {
|
|
75
|
+
const section = read(config);
|
|
76
|
+
if (isDeclared(section)) {
|
|
77
|
+
entry[name] = section;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return entry;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The framework-owned slice of the entry as it stands in the directory today —
|
|
86
|
+
* what `buildEntry` output is diffed against. Everything else in the document
|
|
87
|
+
* belongs to the hub and is not this service's business.
|
|
88
|
+
*
|
|
89
|
+
* @param {object|null} current - The document read from the directory.
|
|
90
|
+
* @returns {object} Only the owned sections that are present.
|
|
91
|
+
*/
|
|
92
|
+
function ownedSlice(current) {
|
|
93
|
+
const slice = {};
|
|
94
|
+
|
|
95
|
+
for (const key of OWNED_SECTIONS) {
|
|
96
|
+
if (current && current[key] !== undefined) {
|
|
97
|
+
slice[key] = current[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return slice;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { BLOCKS, OWNED_SECTIONS, buildEntry, ownedSlice };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disperse service — the remnant of omega-manager's disperse after the
|
|
3
|
+
* config hierarchy dissolved file dispersal: targets read config/omega.json5
|
|
4
|
+
* directly, so there is no .brands/ mirror and no per-repo config writes
|
|
5
|
+
* (the old repos/products/pricing-md/cdn operations). What can't ride the
|
|
6
|
+
* hierarchy is exactly what this service still moves:
|
|
7
|
+
*
|
|
8
|
+
* certs — signing artifacts (binary, never mergeable) copied from the
|
|
9
|
+
* certificates service's .omega/certificates/apple/ tree into
|
|
10
|
+
* each desktop/mobile target's certs dir. The copy itself is
|
|
11
|
+
* devkit's (`@omega.js/devkit/certs`), so a desktop build reaches
|
|
12
|
+
* the same artifacts through the same rules (#678).
|
|
13
|
+
*
|
|
14
|
+
* The .env composition is GONE (#678): the brand-root .env is the one file
|
|
15
|
+
* humans and the manager edit, and every verb composes its target's runtime
|
|
16
|
+
* env from the cascade by schema — no machine writes a target .env. A CUSTOM
|
|
17
|
+
* target has no @omega.js/config to walk the cascade for it, so it inherits
|
|
18
|
+
* instead: manage.js loads the env chain into process.env before it spawns
|
|
19
|
+
* anything, and a custom target started by `omega dev`/`omega deploy` gets the
|
|
20
|
+
* brand keys that way. A standalone run inside the target dir does not.
|
|
21
|
+
*
|
|
22
|
+
* De-ITW'd from omega-manager: the company-wide .output/_shared/ cert tree
|
|
23
|
+
* and .brands/{id}/.env override layer are gone — artifacts are brand-local
|
|
24
|
+
* (.omega/certificates/apple/) and manage.js already layers shell env >
|
|
25
|
+
* brand .env > company .env before any service runs.
|
|
26
|
+
*
|
|
27
|
+
* Runs on the DELIVERY lane (config.js BOOT_SERVICES): every `omega dev`
|
|
28
|
+
* boot, every brand-root `omega deploy`, and the full manage walk — after
|
|
29
|
+
* certificates (the artifacts must exist) and before update (builds read the
|
|
30
|
+
* cert files).
|
|
31
|
+
*/
|
|
32
|
+
const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
33
|
+
|
|
34
|
+
module.exports.run = createServiceRunner({
|
|
35
|
+
serviceDir: __dirname,
|
|
36
|
+
setup: (context) => {
|
|
37
|
+
const mappedTargets = (context.targets || []).filter((entry) => entry.target);
|
|
38
|
+
|
|
39
|
+
if (mappedTargets.length === 0) {
|
|
40
|
+
return { skip: true, reason: 'no target-mapped dirs' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { mappedTargets };
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy signing artifacts into desktop/mobile targets.
|
|
3
|
+
*
|
|
4
|
+
* The copy itself is devkit's (`@omega.js/devkit/certs`, #678) — the ONE
|
|
5
|
+
* delivery step every verb rides, so a desktop build reaches the same
|
|
6
|
+
* artifacts through the same rules. What lives here is the manage-lane
|
|
7
|
+
* FRAMING: which targets get a pass, the certificates config gate, and the
|
|
8
|
+
* per-rule reporting.
|
|
9
|
+
*
|
|
10
|
+
* Sources are the certificates service's outputs under the signing tree —
|
|
11
|
+
* {companyRoot||brandRoot}/.omega/certificates/apple/ (company-managed
|
|
12
|
+
* brands share the company workspace's material); destinations are each
|
|
13
|
+
* target's certs dir. Optional rules skip silently when the source is
|
|
14
|
+
* missing; required rules warn (the desktop/mobile build would be unsigned).
|
|
15
|
+
* A brand with no Apple artifacts at all (certificates disabled or never run)
|
|
16
|
+
* is a quiet note, not a warning.
|
|
17
|
+
*
|
|
18
|
+
* A miss is warn-only: the dests are documented human drop points, so a dest
|
|
19
|
+
* with no source in the tree is left exactly where the operator put it.
|
|
20
|
+
*/
|
|
21
|
+
const { join } = require('node:path');
|
|
22
|
+
const jetpack = require('fs-jetpack');
|
|
23
|
+
const chalk = require('chalk').default;
|
|
24
|
+
|
|
25
|
+
const { deliverCerts, resolveCertRules, certsSourceDir, CERT_FILE_MAP } = require('../../../vendor/devkit/certs.js');
|
|
26
|
+
|
|
27
|
+
module.exports = async (context) => {
|
|
28
|
+
const { brandRoot, companyRoot, brandConfig, mappedTargets, options = {} } = context;
|
|
29
|
+
|
|
30
|
+
const certConfig = brandConfig.certificates;
|
|
31
|
+
if (certConfig === false || certConfig?.enabled === false) {
|
|
32
|
+
console.log(` ${chalk.dim('⊘ certificates.enabled = false — no signing artifacts to disperse')}`);
|
|
33
|
+
return { output: { certs: { reason: 'certificates.enabled = false' } } };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const certTargets = mappedTargets.filter((entry) => CERT_FILE_MAP[entry.target]);
|
|
37
|
+
if (certTargets.length === 0) {
|
|
38
|
+
console.log(` ${chalk.dim('⊘ no desktop or mobile targets')}`);
|
|
39
|
+
return { output: { certs: { reason: 'no desktop or mobile targets' } } };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The certificates service's signing tree — company-shared when the brand
|
|
43
|
+
// is company-managed, brand-local otherwise (the same resolution rule)
|
|
44
|
+
const sourceRoot = companyRoot || brandRoot;
|
|
45
|
+
const appleDir = certsSourceDir(sourceRoot);
|
|
46
|
+
if (!jetpack.exists(appleDir)) {
|
|
47
|
+
console.log(` ${chalk.dim('⊘ no signing artifacts yet (.omega/certificates/apple is empty — the certificates service produces them)')}`);
|
|
48
|
+
return { output: { certs: { reason: 'no signing artifacts yet' } } };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const brand = brandConfig.brand;
|
|
52
|
+
let copied = 0;
|
|
53
|
+
let current = 0;
|
|
54
|
+
let skipped = 0;
|
|
55
|
+
let warned = 0;
|
|
56
|
+
let planned = 0;
|
|
57
|
+
|
|
58
|
+
for (const entry of certTargets) {
|
|
59
|
+
console.log(` ${chalk.cyan(entry.dir)}:`);
|
|
60
|
+
|
|
61
|
+
const result = deliverCerts({
|
|
62
|
+
sourceRoot,
|
|
63
|
+
targetDir: entry.path,
|
|
64
|
+
target: entry.target,
|
|
65
|
+
brand,
|
|
66
|
+
dryRun: options.dryRun,
|
|
67
|
+
});
|
|
68
|
+
const delivered = new Set(result.copied);
|
|
69
|
+
const unchanged = new Set(result.current);
|
|
70
|
+
|
|
71
|
+
// The rules in their declared order — the delivery's own arrays carry the
|
|
72
|
+
// outcome, the rules carry what a miss MEANS (optional vs required)
|
|
73
|
+
for (const rule of resolveCertRules({ target: entry.target, brand })) {
|
|
74
|
+
const destRel = rule.destRel || rule.dest;
|
|
75
|
+
|
|
76
|
+
if (unchanged.has(destRel)) {
|
|
77
|
+
console.log(` ${chalk.dim(`✓ ${destRel} (current)`)}`);
|
|
78
|
+
current++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (delivered.has(destRel)) {
|
|
83
|
+
if (options.dryRun) {
|
|
84
|
+
console.log(` ${chalk.cyan('[DRY RUN]')} Would copy ${chalk.cyan(destRel)}`);
|
|
85
|
+
planned++;
|
|
86
|
+
} else {
|
|
87
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan(destRel)}`);
|
|
88
|
+
copied++;
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// A miss: an unset env placeholder (the rule has no real path) or a
|
|
94
|
+
// source that isn't there
|
|
95
|
+
const detail = rule.destRel === null
|
|
96
|
+
? `env placeholder unset in ${rule.source}`
|
|
97
|
+
: `source missing: ${join(appleDir, rule.sourceRel)}`;
|
|
98
|
+
|
|
99
|
+
if (rule.optional) {
|
|
100
|
+
console.log(` ${chalk.dim(`⊘ ${destRel} (${rule.destRel === null ? 'env placeholder unset' : 'source missing'}, optional)`)}`);
|
|
101
|
+
skipped++;
|
|
102
|
+
} else {
|
|
103
|
+
console.log(` ${chalk.yellow('⚠')} ${chalk.cyan(destRel)} ${chalk.dim(`(${detail})`)}`);
|
|
104
|
+
warned++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const summary = { copied, current, skipped, warned, ...(options.dryRun ? { planned } : {}) };
|
|
110
|
+
console.log(` Summary: ${chalk.bold(copied)} copied, ${current} current, ${chalk.dim(skipped)} skipped, ${warned > 0 ? chalk.yellow(warned) : warned} warnings`);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
status: warned > 0 ? 'warned' : 'success',
|
|
114
|
+
...(warned > 0 ? { reason: `${warned} cert file(s) not dispersed` } : {}),
|
|
115
|
+
output: { certs: summary },
|
|
116
|
+
};
|
|
117
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the registrar's nameservers point to Cloudflare.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Fetch the Cloudflare zone to get its assigned nameservers
|
|
6
|
+
* 2. namecheap: read current nameservers via API, set them when mismatched
|
|
7
|
+
* 3. manual registrars: an active zone proves they're already set (success);
|
|
8
|
+
* a pending zone prints the values to set and returns warned
|
|
9
|
+
*
|
|
10
|
+
* omega-manager split SLD/TLD by popping the last dot-label, which breaks
|
|
11
|
+
* multi-part TLDs (mybrand.co.uk → SLD "mybrand.co", TLD "uk"); this port
|
|
12
|
+
* uses the public suffix list instead.
|
|
13
|
+
*/
|
|
14
|
+
const chalk = require('chalk').default;
|
|
15
|
+
const psl = require('psl');
|
|
16
|
+
const { pollWithSpinner } = require('../../../vendor/devkit/flows.js');
|
|
17
|
+
const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
|
|
18
|
+
const { isWhitelistError } = require('../lib/namecheap-api.js');
|
|
19
|
+
const { walkWhitelist } = require('../lib/whitelist-walkthrough.js');
|
|
20
|
+
|
|
21
|
+
// A freshly created zone gets its nameservers within seconds; a zone still
|
|
22
|
+
// bare after this many reads is not going to fill in while we watch (#662).
|
|
23
|
+
const ZONE_NS_READS = 6;
|
|
24
|
+
const ZONE_NS_INTERVAL_MS = 5000;
|
|
25
|
+
|
|
26
|
+
/** The unreadable-nameservers warn — the registrar side steps aside for a run. */
|
|
27
|
+
const warnedRead = (message) => ({
|
|
28
|
+
status: 'warned',
|
|
29
|
+
reason: 'could not read the nameservers at Namecheap',
|
|
30
|
+
output: { nameservers: { error: message } },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The read failed for something the whitelist walk cannot fix — name it, point
|
|
35
|
+
* at the likeliest cause, and step aside. The walk's own recheck failure lands
|
|
36
|
+
* here too, so the hint stays reachable on that branch (#698).
|
|
37
|
+
*/
|
|
38
|
+
function warnUnreadable(message) {
|
|
39
|
+
console.log(` ${chalk.yellow('⚠')} Could not read nameservers at Namecheap${chalk.dim(`: ${message}`)}`);
|
|
40
|
+
console.log(` ${chalk.dim('→')} Domain not in your Namecheap account yet (not purchased?). Rerun after purchase.`);
|
|
41
|
+
return warnedRead(message);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = async function ensureNameservers(context) {
|
|
45
|
+
const { cloudflareApi, namecheapApi, domain, provider, options = {} } = context;
|
|
46
|
+
|
|
47
|
+
// Nameservers live at the REGISTRABLE domain — for subdomain projects
|
|
48
|
+
// (playground.omegajs.dev) that's the parent (omegajs.dev): the zone the
|
|
49
|
+
// cloudflare service manages, and the domain the registrar actually holds.
|
|
50
|
+
const zoneName = psl.get(domain) || domain;
|
|
51
|
+
if (zoneName !== domain) {
|
|
52
|
+
console.log(` ${chalk.dim(`Subdomain project — managing nameservers for parent ${zoneName}`)}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// === READ: Cloudflare's assigned nameservers ===
|
|
56
|
+
let zone = await cloudflareApi.getZoneByName(zoneName);
|
|
57
|
+
|
|
58
|
+
if (!zone) {
|
|
59
|
+
console.log(` ${chalk.yellow('⚠')} No Cloudflare zone found for ${chalk.cyan(zoneName)} — rerun once the cloudflare service creates it`);
|
|
60
|
+
return { status: 'warned', reason: 'no Cloudflare zone found for the zone', output: { nameservers: { note: 'no Cloudflare zone found' } } };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A zone created earlier in THIS walk (the edge service) may still be
|
|
64
|
+
// initializing — wait for its nameservers instead of leaving the registrar
|
|
65
|
+
// to a second run (#662). Bounded: the poll gives up on its own.
|
|
66
|
+
if ((zone.name_servers || []).length === 0 && canPrompt(options)) {
|
|
67
|
+
zone = await waitForAssignedNameservers(cloudflareApi, zoneName, zone);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const required = [...(zone.name_servers || [])].sort();
|
|
71
|
+
|
|
72
|
+
if (required.length === 0) {
|
|
73
|
+
console.log(` ${chalk.yellow('⚠')} Zone exists but has no assigned nameservers yet (still initializing) — rerun`);
|
|
74
|
+
return { status: 'warned', reason: 'the zone has no assigned nameservers yet', output: { nameservers: { note: 'zone has no assigned nameservers yet' } } };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(` ${chalk.dim('→')} Cloudflare nameservers: ${required.map((ns) => chalk.cyan(ns)).join(', ')}`);
|
|
78
|
+
|
|
79
|
+
if (provider === 'namecheap') {
|
|
80
|
+
return await ensureNamecheap(zoneName, required, namecheapApi, options);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// === Manual registrars — no read API, but the zone status is proof enough:
|
|
84
|
+
// an active zone means the nameservers already point to Cloudflare
|
|
85
|
+
if (zone.status === 'active') {
|
|
86
|
+
console.log(` ${chalk.green('✓')} Zone is active — nameservers already pointing to Cloudflare`);
|
|
87
|
+
return { output: { nameservers: { alreadySet: true } } };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(` ${chalk.yellow('⚠')} ${provider} requires manual nameserver configuration`);
|
|
91
|
+
console.log(` Set these nameservers at your registrar:`);
|
|
92
|
+
for (const ns of required) {
|
|
93
|
+
console.log(` ${chalk.cyan(ns)}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { status: 'warned', reason: `${provider} requires manual nameserver configuration`, output: { nameservers: { manual: true, provider, required } } };
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Re-read the zone until Cloudflare has assigned its nameservers, bounded to
|
|
101
|
+
* ZONE_NS_READS attempts (ENTER checks now, `s` skips).
|
|
102
|
+
*
|
|
103
|
+
* @returns {Promise<object>} - The zone: the assigned one, or the bare zone
|
|
104
|
+
* handed in when the wait ran out or was skipped.
|
|
105
|
+
*/
|
|
106
|
+
async function waitForAssignedNameservers(api, zoneName, zone) {
|
|
107
|
+
let reads = 0;
|
|
108
|
+
let assigned = null;
|
|
109
|
+
|
|
110
|
+
const result = await pollWithSpinner({
|
|
111
|
+
check: async () => {
|
|
112
|
+
reads += 1;
|
|
113
|
+
const fresh = await api.getZoneByName(zoneName);
|
|
114
|
+
if ((fresh?.name_servers || []).length > 0) {
|
|
115
|
+
assigned = fresh;
|
|
116
|
+
return { done: true };
|
|
117
|
+
}
|
|
118
|
+
return reads >= ZONE_NS_READS ? { done: true, error: 'still initializing' } : { done: false };
|
|
119
|
+
},
|
|
120
|
+
intervalMs: ZONE_NS_INTERVAL_MS,
|
|
121
|
+
message: `Waiting for Cloudflare to assign nameservers for ${zoneName}`,
|
|
122
|
+
indent: ' ',
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return result.success && assigned ? assigned : zone;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function ensureNamecheap(domain, required, api, options) {
|
|
129
|
+
const { sld, tld } = psl.parse(domain);
|
|
130
|
+
|
|
131
|
+
// Read current nameservers. Fails for domains not in the Namecheap account
|
|
132
|
+
// (e.g. not purchased yet) — warn instead of failing the brand.
|
|
133
|
+
console.log(` ${chalk.dim('→')} Checking current nameservers at Namecheap...`);
|
|
134
|
+
let current;
|
|
135
|
+
try {
|
|
136
|
+
current = await api.getDns(sld, tld);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
// The IP-whitelist rejection is the ONE read failure this walk can fix in
|
|
139
|
+
// place (#698): open the API access page, then recheck the refused read.
|
|
140
|
+
if (!isWhitelistError(error)) {
|
|
141
|
+
return warnUnreadable(error.message);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const recovered = await walkWhitelist(error, () => api.getDns(sld, tld), options);
|
|
145
|
+
if (recovered.error) {
|
|
146
|
+
// The recheck cleared the whitelist and failed for its own reason — that
|
|
147
|
+
// error is the one to report; the pre-walk rejection is stale (#698).
|
|
148
|
+
return warnUnreadable(recovered.error);
|
|
149
|
+
}
|
|
150
|
+
if (!recovered.success) {
|
|
151
|
+
return warnedRead(error.message);
|
|
152
|
+
}
|
|
153
|
+
current = recovered.result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const currentNs = [...current.nameservers].sort();
|
|
157
|
+
const alreadySet = required.length === currentNs.length
|
|
158
|
+
&& required.every((ns, i) => ns === currentNs[i]);
|
|
159
|
+
|
|
160
|
+
if (alreadySet) {
|
|
161
|
+
console.log(` ${chalk.green('✓')} Nameservers already pointing to Cloudflare`);
|
|
162
|
+
return { output: { nameservers: { alreadySet: true } } };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log(` ${chalk.dim('→')} Current: ${currentNs.join(', ') || '(default/none)'}`);
|
|
166
|
+
|
|
167
|
+
// === WRITE ===
|
|
168
|
+
if (options.dryRun) {
|
|
169
|
+
return dryRunPlan('set Cloudflare nameservers at Namecheap', { output: { nameservers: { planned: 'update', current: currentNs, required } } });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(` ${chalk.dim('→')} Setting Cloudflare nameservers at Namecheap...`);
|
|
173
|
+
|
|
174
|
+
await api.setCustomNameservers(sld, tld, required);
|
|
175
|
+
|
|
176
|
+
console.log(` ${chalk.green('✓')} Nameservers updated at Namecheap → Cloudflare`);
|
|
177
|
+
|
|
178
|
+
return { output: { nameservers: { updated: true, previous: currentNs } } };
|
|
179
|
+
}
|