@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 GitHub Pages serves the brand's website from the brand repo:
|
|
3
|
+
* gh-pages source branch + custom domain from brand.url.
|
|
4
|
+
*
|
|
5
|
+
* A missing gh-pages branch is NOT an error — it appears with the first
|
|
6
|
+
* deploy (omega-manager treated it the same way). Old-world subdomain Pages
|
|
7
|
+
* repos ({brand}-{sub}-website) don't map to the monorepo (one Pages site
|
|
8
|
+
* per repo) — multi-site hosting is an open design question for the
|
|
9
|
+
* cloudflare port.
|
|
10
|
+
*/
|
|
11
|
+
const chalk = require('chalk').default;
|
|
12
|
+
const { brandRepoName, brandRepoOwner } = require('../../../vendor/config/index.js');
|
|
13
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
14
|
+
|
|
15
|
+
module.exports = async function ensurePages(context) {
|
|
16
|
+
const { brand, brandConfig, options = {}, githubApi: api } = context;
|
|
17
|
+
|
|
18
|
+
if (!brand.enabledTargets.includes('web')) {
|
|
19
|
+
console.log(` ${chalk.dim('⊘ No web target — Pages not needed')}`);
|
|
20
|
+
return { status: 'success', output: { pages: { skipped: 'no web target' } } };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const repoName = brandRepoName(brandConfig);
|
|
24
|
+
const repoOwner = brandRepoOwner(brandConfig);
|
|
25
|
+
const domain = (brandConfig.brand?.url || '').replace(/^https?:\/\//, '');
|
|
26
|
+
|
|
27
|
+
// The branch only exists once the site has deployed at least once
|
|
28
|
+
if (!api.branchExists(repoOwner, repoName, 'gh-pages')) {
|
|
29
|
+
console.log(` ${chalk.yellow('⚠')} No gh-pages branch yet — deploy the website first`);
|
|
30
|
+
return { status: 'success', output: { pages: { skipped: 'no gh-pages branch' } } };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Source: enabled, serving from gh-pages
|
|
35
|
+
let pages = api.getPages(repoOwner, repoName);
|
|
36
|
+
|
|
37
|
+
if (!pages) {
|
|
38
|
+
if (options.dryRun) {
|
|
39
|
+
return dryRunPlan('enable Pages on gh-pages', { status: 'success', output: { pages: { planned: ['enable', 'domain'] } } });
|
|
40
|
+
}
|
|
41
|
+
console.log(` Enabling Pages on gh-pages...`);
|
|
42
|
+
api.enablePages(repoOwner, repoName, { branch: 'gh-pages', path: '/' });
|
|
43
|
+
console.log(` ${chalk.green('✓')} Pages enabled`);
|
|
44
|
+
pages = api.getPages(repoOwner, repoName);
|
|
45
|
+
} else if (pages.source?.branch !== 'gh-pages') {
|
|
46
|
+
if (options.dryRun) {
|
|
47
|
+
dryRunPlan(`switch source branch ${pages.source?.branch} → gh-pages`);
|
|
48
|
+
} else {
|
|
49
|
+
console.log(` Updating source branch to gh-pages...`);
|
|
50
|
+
api.updatePages(repoOwner, repoName, { branch: 'gh-pages', path: '/' });
|
|
51
|
+
console.log(` ${chalk.green('✓')} Source branch updated`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Custom domain
|
|
56
|
+
if (pages?.cname !== domain) {
|
|
57
|
+
if (options.dryRun) {
|
|
58
|
+
return dryRunPlan(`set domain to ${domain}`, { status: 'success', output: { pages: { planned: ['domain'] } } });
|
|
59
|
+
}
|
|
60
|
+
console.log(` Setting domain to ${chalk.cyan(domain)}...`);
|
|
61
|
+
api.setPagesDomain(repoOwner, repoName, domain);
|
|
62
|
+
console.log(` ${chalk.green('✓')} Domain set to ${chalk.cyan(domain)}`);
|
|
63
|
+
return { status: 'success', state: { pages: { domain } }, output: { pages: { updated: ['domain'] } } };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(` ${chalk.green('✓')} Pages serving ${chalk.cyan(domain)} from gh-pages`);
|
|
67
|
+
return { status: 'success', state: { pages: { domain } } };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.log(` ${chalk.red('✗')} Pages configuration failed${chalk.dim(`: ${error.message}`)}`);
|
|
70
|
+
return { status: 'warned', reason: 'GitHub Pages configuration failed', output: { pages: { error: error.message } } };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the brand-monorepo repo exists on GitHub with the right settings.
|
|
3
|
+
*
|
|
4
|
+
* Missing → created EMPTY (the brand already exists locally — the user pushes
|
|
5
|
+
* to it; no template, no clone: that's onboarding's job). Existing → diff
|
|
6
|
+
* visibility + homepage, patch only the drift (omega-manager's exact repo
|
|
7
|
+
* reconciliation, minus per-target repos).
|
|
8
|
+
*
|
|
9
|
+
* The repo identity is pure derivation from config (repo.providers.github +
|
|
10
|
+
* brand.id), so nothing about it is persisted — every service that needs it
|
|
11
|
+
* derives it the same way.
|
|
12
|
+
*/
|
|
13
|
+
const chalk = require('chalk').default;
|
|
14
|
+
const { brandRepoName, brandRepoOwner } = require('../../../vendor/config/index.js');
|
|
15
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
16
|
+
|
|
17
|
+
module.exports = async function ensureRepo(context) {
|
|
18
|
+
const { brandConfig, options = {}, githubApi: api } = context;
|
|
19
|
+
|
|
20
|
+
const github = brandConfig.repo?.providers?.github;
|
|
21
|
+
const repoName = brandRepoName(brandConfig);
|
|
22
|
+
const repoOwner = brandRepoOwner(brandConfig);
|
|
23
|
+
const fullName = `${repoOwner}/${repoName}`;
|
|
24
|
+
const isPrivate = github.private !== false;
|
|
25
|
+
const homepage = brandConfig.brand?.url || '';
|
|
26
|
+
|
|
27
|
+
const repo = api.getRepo(repoOwner, repoName);
|
|
28
|
+
|
|
29
|
+
// ── Missing: create it ─────────────────────────────────────────────────────
|
|
30
|
+
if (!repo) {
|
|
31
|
+
if (options.dryRun) {
|
|
32
|
+
return dryRunPlan(`create ${fullName} (${isPrivate ? 'private' : 'public'})`, { status: 'success', output: { repo: { planned: 'create' } } });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(` Creating ${chalk.cyan(fullName)}...`);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const created = api.createRepo(repoOwner, repoName, {
|
|
39
|
+
isPrivate,
|
|
40
|
+
description: brandConfig.brand?.description || '',
|
|
41
|
+
homepage,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log(` ${chalk.green('✓')} Created: ${chalk.cyan(created.html_url)}`);
|
|
45
|
+
console.log(` ${chalk.dim(`Push your local repo: git remote add origin ${created.html_url}.git && git push -u origin main`)}`);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
status: 'success',
|
|
49
|
+
state: { repo: { fullName, htmlUrl: created.html_url, private: isPrivate } },
|
|
50
|
+
output: { repo: { created: true } },
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.log(` ${chalk.red('✗')} Failed to create repo${chalk.dim(`: ${error.message}`)}`);
|
|
54
|
+
return { status: 'error', error: error.message };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Exists: reconcile settings ─────────────────────────────────────────────
|
|
59
|
+
const updates = {};
|
|
60
|
+
|
|
61
|
+
if (repo.private !== isPrivate) {
|
|
62
|
+
updates.private = isPrivate;
|
|
63
|
+
console.log(` private: ${repo.private} ${chalk.dim('→')} ${chalk.cyan(isPrivate)}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (homepage && repo.homepage !== homepage) {
|
|
67
|
+
updates.homepage = homepage;
|
|
68
|
+
console.log(` homepage: "${repo.homepage || '(none)'}" ${chalk.dim('→')} "${chalk.cyan(homepage)}"`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const state = { repo: { fullName, htmlUrl: repo.html_url, private: isPrivate } };
|
|
72
|
+
|
|
73
|
+
if (Object.keys(updates).length === 0) {
|
|
74
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan(fullName)} already configured`);
|
|
75
|
+
return { status: 'success', state };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options.dryRun) {
|
|
79
|
+
return dryRunPlan('update repo settings', { status: 'success', state, output: { repo: { planned: Object.keys(updates) } } });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
api.updateRepo(repoOwner, repoName, updates);
|
|
84
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan(fullName)} updated`);
|
|
85
|
+
return { status: 'success', state, output: { repo: { updated: Object.keys(updates) } } };
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.log(` ${chalk.red('✗')} Failed to update repo${chalk.dim(`: ${error.message}`)}`);
|
|
88
|
+
return { status: 'error', error: error.message };
|
|
89
|
+
}
|
|
90
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo service — ensures the brand's GitHub presence matches config:
|
|
3
|
+
* org profile, the brand-monorepo repo, and GitHub Pages. The monorepo
|
|
4
|
+
* cousin of omega-manager's github service (which managed one repo per
|
|
5
|
+
* target); here a brand is ONE repo.
|
|
6
|
+
*
|
|
7
|
+
* Config (brand omega.json5 `repo.providers.github`):
|
|
8
|
+
* org — repo owner (GitHub org or user). No default; unset = service skips.
|
|
9
|
+
* shared — org shared with other brands → org-level reconciliation skipped.
|
|
10
|
+
* repo — optional "owner/name" slug or bare name; name defaults to the
|
|
11
|
+
* brand id, owner to repo.providers.github.org (@omega.js/config brandRepoName/
|
|
12
|
+
* brandRepoOwner — shared with deploy --direct).
|
|
13
|
+
* private — repo visibility (manager default: true).
|
|
14
|
+
* location — org profile location; only reconciled when set.
|
|
15
|
+
*/
|
|
16
|
+
const chalk = require('chalk').default;
|
|
17
|
+
const { brandRepoName, brandRepoOwner } = require('../../vendor/config/index.js');
|
|
18
|
+
const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
19
|
+
const { GitHubAPI } = require('./lib/github-api.js');
|
|
20
|
+
|
|
21
|
+
// Operations that touch org-level settings — skipped when the org is shared
|
|
22
|
+
// with other brands (one brand must not rewrite a shared org's profile)
|
|
23
|
+
const SHARED_SKIP_OPERATIONS = new Set(['org']);
|
|
24
|
+
|
|
25
|
+
module.exports.run = createServiceRunner({
|
|
26
|
+
serviceDir: __dirname,
|
|
27
|
+
setup: (context) => {
|
|
28
|
+
const github = context.brandConfig.repo?.providers?.github || {};
|
|
29
|
+
|
|
30
|
+
if (github.enabled === false) {
|
|
31
|
+
return { skip: true, reason: 'github.enabled = false' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!github.org) {
|
|
35
|
+
return { skip: true, reason: 'no repo.providers.github.org configured (set repo.providers.github.org in config/omega.json5)' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const repoName = brandRepoName(context.brandConfig);
|
|
39
|
+
const repoOwner = brandRepoOwner(context.brandConfig);
|
|
40
|
+
console.log(` Repo: ${chalk.cyan(`${repoOwner}/${repoName}`)}${github.shared === true ? chalk.dim(' (shared org)') : ''}`);
|
|
41
|
+
|
|
42
|
+
// Tests inject a fake client via context.githubApi; the real one verifies
|
|
43
|
+
// gh is installed + authenticated at construction
|
|
44
|
+
const result = { githubApi: context.githubApi || new GitHubAPI() };
|
|
45
|
+
|
|
46
|
+
if (github.shared === true) {
|
|
47
|
+
result.operations = context.operations.filter((op) => !SHARED_SKIP_OPERATIONS.has(op.name));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub API client — omega-manager's gh-CLI wrapper, trimmed to the
|
|
3
|
+
* brand-monorepo surface (one repo per brand: org profile, repo settings,
|
|
4
|
+
* Pages). Old-world methods (template creation, cloning, GitHub Desktop)
|
|
5
|
+
* ride the onboarding/company-mode ports.
|
|
6
|
+
*
|
|
7
|
+
* Uses the `gh` CLI for all operations. Requires `gh` installed and
|
|
8
|
+
* authenticated — `gh auth login`, or a GH_TOKEN/GITHUB_TOKEN in the brand
|
|
9
|
+
* .env (loaded before services run; gh honors both env vars).
|
|
10
|
+
*
|
|
11
|
+
* Every invocation is execFileSync with an argv array — no shell, so
|
|
12
|
+
* brand-config values (descriptions, homepages) can never inject commands.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { execFileSync } = require('node:child_process');
|
|
16
|
+
|
|
17
|
+
class GitHubAPI {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.verifyGhCli();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Verify gh CLI is installed and authenticated
|
|
24
|
+
*/
|
|
25
|
+
verifyGhCli() {
|
|
26
|
+
try {
|
|
27
|
+
execFileSync('gh', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error('GitHub CLI (gh) is not installed. Install from: https://cli.github.com/');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
execFileSync('gh', ['auth', 'status'], { encoding: 'utf8', stdio: 'pipe' });
|
|
34
|
+
} catch {
|
|
35
|
+
throw new Error('GitHub CLI is not authenticated. Run: gh auth login (or set GH_TOKEN in the brand .env)');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Run a gh CLI command (argv array, no shell) and return trimmed stdout
|
|
41
|
+
*/
|
|
42
|
+
runCommand(args, options = {}) {
|
|
43
|
+
try {
|
|
44
|
+
const result = execFileSync('gh', args, {
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
47
|
+
...options,
|
|
48
|
+
});
|
|
49
|
+
return result.trim();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const stderr = error.stderr?.toString() || error.message;
|
|
52
|
+
throw new Error(`gh command failed: ${stderr}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Run a gh CLI command and return parsed JSON
|
|
58
|
+
*/
|
|
59
|
+
runJsonCommand(args, options = {}) {
|
|
60
|
+
const result = this.runCommand(args, options);
|
|
61
|
+
if (!result) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return JSON.parse(result);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get authenticated user info
|
|
69
|
+
*/
|
|
70
|
+
getAuthenticatedUser() {
|
|
71
|
+
return this.runJsonCommand(['api', 'user']);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get organization details (null if the owner is not an org / not found)
|
|
76
|
+
*/
|
|
77
|
+
getOrg(orgName) {
|
|
78
|
+
try {
|
|
79
|
+
return this.runJsonCommand(['api', `orgs/${orgName}`]);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.message.includes('404')) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Update organization settings
|
|
90
|
+
*/
|
|
91
|
+
updateOrg(orgName, settings) {
|
|
92
|
+
const fields = Object.entries(settings)
|
|
93
|
+
.flatMap(([key, value]) => ['-f', `${key}=${String(value)}`]);
|
|
94
|
+
return this.runJsonCommand(['api', `orgs/${orgName}`, '-X', 'PATCH', ...fields]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get repository details (null if not found)
|
|
99
|
+
*/
|
|
100
|
+
getRepo(owner, repoName) {
|
|
101
|
+
try {
|
|
102
|
+
return this.runJsonCommand(['api', `repos/${owner}/${repoName}`]);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.message.includes('404')) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create an empty repository (the brand monorepo already exists locally —
|
|
113
|
+
* the user pushes to it; no template, no clone)
|
|
114
|
+
*/
|
|
115
|
+
createRepo(owner, name, { isPrivate = true, description = '', homepage = '' } = {}) {
|
|
116
|
+
const args = ['repo', 'create', `${owner}/${name}`, isPrivate ? '--private' : '--public'];
|
|
117
|
+
|
|
118
|
+
if (description) {
|
|
119
|
+
args.push('--description', description);
|
|
120
|
+
}
|
|
121
|
+
if (homepage) {
|
|
122
|
+
args.push('--homepage', homepage);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.runCommand(args);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
full_name: `${owner}/${name}`,
|
|
129
|
+
html_url: `https://github.com/${owner}/${name}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update repository settings (-F for typed values so booleans arrive as
|
|
135
|
+
* real JSON booleans, not the string "true")
|
|
136
|
+
*/
|
|
137
|
+
updateRepo(owner, repoName, settings) {
|
|
138
|
+
const fields = Object.entries(settings)
|
|
139
|
+
.flatMap(([key, value]) => {
|
|
140
|
+
if (typeof value === 'boolean') {
|
|
141
|
+
return ['-F', `${key}=${value}`];
|
|
142
|
+
}
|
|
143
|
+
return ['-f', `${key}=${String(value)}`];
|
|
144
|
+
});
|
|
145
|
+
return this.runJsonCommand(['api', `repos/${owner}/${repoName}`, '-X', 'PATCH', ...fields]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if a branch exists in the repository
|
|
150
|
+
*/
|
|
151
|
+
branchExists(owner, repo, branch) {
|
|
152
|
+
try {
|
|
153
|
+
this.runJsonCommand(['api', `repos/${owner}/${repo}/branches/${branch}`]);
|
|
154
|
+
return true;
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get GitHub Pages configuration (null if Pages is not enabled)
|
|
162
|
+
*/
|
|
163
|
+
getPages(owner, repo) {
|
|
164
|
+
try {
|
|
165
|
+
return this.runJsonCommand(['api', `repos/${owner}/${repo}/pages`]);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error.message.includes('404')) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Enable GitHub Pages on a repository
|
|
176
|
+
*/
|
|
177
|
+
enablePages(owner, repo, { branch = 'gh-pages', path = '/' } = {}) {
|
|
178
|
+
return this.runJsonCommand(['api', `repos/${owner}/${repo}/pages`, '-X', 'POST', '-f', `source[branch]=${branch}`, '-f', `source[path]=${path}`]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Update GitHub Pages source configuration
|
|
183
|
+
*/
|
|
184
|
+
updatePages(owner, repo, { branch = 'gh-pages', path = '/' } = {}) {
|
|
185
|
+
return this.runJsonCommand(['api', `repos/${owner}/${repo}/pages`, '-X', 'PUT', '-f', `source[branch]=${branch}`, '-f', `source[path]=${path}`]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Set the custom domain for GitHub Pages
|
|
190
|
+
*/
|
|
191
|
+
setPagesDomain(owner, repo, domain) {
|
|
192
|
+
return this.runJsonCommand(['api', `repos/${owner}/${repo}/pages`, '-X', 'PUT', '-f', `cname=${domain}`]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { GitHubAPI };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure Search Console is associated with the brand's GA4 property.
|
|
3
|
+
*
|
|
4
|
+
* There is NO API for this on either side (the GA Admin API supports
|
|
5
|
+
* Firebase/Ads/BigQuery links but not Search Console; the Search Console API
|
|
6
|
+
* can't read associations either) — so the check follows the firebase
|
|
7
|
+
* OAuth-redirect pattern: instructions + interactive confirm, warned until
|
|
8
|
+
* the link is confirmed. A confirmation nothing can re-check is the one kind
|
|
9
|
+
* of reconcile flag config keeps (#434): it lands at
|
|
10
|
+
* `search.providers.searchConsole.gaLinked`, so the next run believes it.
|
|
11
|
+
*/
|
|
12
|
+
const chalk = require('chalk').default;
|
|
13
|
+
const { confirm, pressEnterToOpen } = require('../../../vendor/devkit/prompt.js');
|
|
14
|
+
const { writeBrandConfig } = require('../../../lib/config-write.js');
|
|
15
|
+
const { canPrompt } = require('../../../lib/run-gates.js');
|
|
16
|
+
|
|
17
|
+
const CONFIG_PATH = 'search.providers.searchConsole.gaLinked';
|
|
18
|
+
|
|
19
|
+
module.exports = async function ensureGaLink(context) {
|
|
20
|
+
const { brandConfig, serviceData, options = {} } = context;
|
|
21
|
+
|
|
22
|
+
if (!serviceData.propertyUrl) {
|
|
23
|
+
console.log(chalk.dim(' ⊘ No Search Console property yet — nothing to associate'));
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const propertyId = brandConfig.analytics?.providers?.google?.propertyId;
|
|
28
|
+
if (!propertyId) {
|
|
29
|
+
console.log(chalk.dim(' ⊘ No analytics.providers.google.propertyId — nothing to associate'));
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (brandConfig.search?.providers?.searchConsole?.gaLinked === true) {
|
|
34
|
+
console.log(` ${chalk.green('✓')} GA association confirmed`);
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const associationsUrl = `https://search.google.com/search-console/settings/associations?resource_id=${encodeURIComponent(serviceData.propertyUrl)}`;
|
|
39
|
+
|
|
40
|
+
console.log(` ${chalk.yellow('⚠')} No API exists to check the Search Console ↔ GA association`);
|
|
41
|
+
console.log(` ${chalk.dim('→')} Associate with GA property ${chalk.cyan(propertyId)} on the associations page`);
|
|
42
|
+
|
|
43
|
+
if (canPrompt(options)) {
|
|
44
|
+
await pressEnterToOpen(associationsUrl, 'the Search Console associations page');
|
|
45
|
+
const done = await confirm({ message: `Search Console associated with GA property ${propertyId}?`, default: false });
|
|
46
|
+
if (done) {
|
|
47
|
+
writeBrandConfig(context, { [CONFIG_PATH]: true });
|
|
48
|
+
console.log(` ${chalk.green('✓')} GA association confirmed`);
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
console.log(` ${chalk.dim('→')} ${chalk.cyan(associationsUrl)}`);
|
|
53
|
+
console.log(` ${chalk.dim('→')} (rerun in an interactive terminal to confirm)`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { status: 'warned', reason: 'GA association not confirmed — link it on the associations page', output: { gaLink: { associationsUrl } } };
|
|
57
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the domain property (`sc-domain:{domain}`) exists in Search Console.
|
|
3
|
+
*
|
|
4
|
+
* An existing property is converged proof — nothing else to check. A missing
|
|
5
|
+
* one is created in one pass: fetch the DNS_TXT verification token (idempotent
|
|
6
|
+
* — Google returns the same token until it's used), write the TXT record via
|
|
7
|
+
* Cloudflare (stale google-site-verification records at the same name are
|
|
8
|
+
* replaced), then verify — interactive runs poll until DNS propagates;
|
|
9
|
+
* non-interactive/dry runs attempt verification ONCE, report warned, and the
|
|
10
|
+
* rerun converges.
|
|
11
|
+
*/
|
|
12
|
+
const chalk = require('chalk').default;
|
|
13
|
+
const { pollWithSpinner } = require('../../../vendor/devkit/flows.js');
|
|
14
|
+
const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
|
|
15
|
+
|
|
16
|
+
// Google answers this when the domain was verified by an earlier attempt
|
|
17
|
+
const isAlreadyVerified = (error) => error.message.includes('already verified')
|
|
18
|
+
|| error.message.includes('already been verified');
|
|
19
|
+
|
|
20
|
+
module.exports = async function ensureProperty(context) {
|
|
21
|
+
const { searchConsoleApi: api, cloudflareApi, domain, apexDomain, propertyUrl, options = {} } = context;
|
|
22
|
+
|
|
23
|
+
// === READ: does the domain property already exist? ===
|
|
24
|
+
const sites = await api.listSites();
|
|
25
|
+
const existing = sites.find((site) => site.siteUrl === propertyUrl);
|
|
26
|
+
|
|
27
|
+
if (existing) {
|
|
28
|
+
console.log(` ${chalk.green('✓')} Domain property exists (${chalk.cyan(existing.permissionLevel)})`);
|
|
29
|
+
return { state: { propertyUrl, permissionLevel: existing.permissionLevel } };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(` ${chalk.dim('→')} Domain property ${chalk.cyan(propertyUrl)} does not exist yet`);
|
|
33
|
+
|
|
34
|
+
if (options.dryRun) {
|
|
35
|
+
return dryRunPlan('verify the domain (DNS TXT) and add the property', { output: { property: { planned: 'verify-and-add' } } });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// === Verification token (same token returned until verification succeeds) ===
|
|
39
|
+
const tokenResponse = await api.getVerificationToken(domain, 'DNS_TXT', 'INET_DOMAIN');
|
|
40
|
+
if (!tokenResponse.token) {
|
|
41
|
+
throw new Error('No verification token received from Google Site Verification');
|
|
42
|
+
}
|
|
43
|
+
const token = tokenResponse.token;
|
|
44
|
+
|
|
45
|
+
// === TXT record via Cloudflare ===
|
|
46
|
+
// The record lives at the property's own name (apex '@', or the subdomain
|
|
47
|
+
// label for subdomain brands) in the APEX zone
|
|
48
|
+
const recordName = domain === apexDomain ? '@' : domain.replace(`.${apexDomain}`, '');
|
|
49
|
+
const recordFqdn = domain;
|
|
50
|
+
|
|
51
|
+
if (!cloudflareApi) {
|
|
52
|
+
console.log(` ${chalk.yellow('⚠')} No CLOUDFLARE_TOKEN — add this TXT record manually, then rerun:`);
|
|
53
|
+
console.log(` ${chalk.cyan(recordFqdn)} TXT ${chalk.cyan(`"${token}"`)}`);
|
|
54
|
+
return { status: 'warned', reason: 'no CLOUDFLARE_TOKEN — add the verification TXT record by hand', output: { property: { manualRecord: { name: recordFqdn, type: 'TXT', content: token } } } };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const zone = await cloudflareApi.getZoneByName(apexDomain);
|
|
58
|
+
if (!zone) {
|
|
59
|
+
console.log(` ${chalk.yellow('⚠')} No Cloudflare zone for ${chalk.cyan(apexDomain)} — rerun once the cloudflare service creates it`);
|
|
60
|
+
return { status: 'warned', reason: 'no Cloudflare zone yet', output: { property: { note: 'no Cloudflare zone yet' } } };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const records = await cloudflareApi.makeRequest(`/zones/${zone.id}/dns_records?type=TXT&per_page=100`, { method: 'GET' });
|
|
64
|
+
const atName = (records.result || []).filter((r) => r.type === 'TXT' && r.name === recordFqdn);
|
|
65
|
+
const tokenRecord = atName.find((r) => r.content === token || r.content === `"${token}"`);
|
|
66
|
+
|
|
67
|
+
if (tokenRecord) {
|
|
68
|
+
console.log(` ${chalk.dim('→')} Verification TXT record already in place`);
|
|
69
|
+
} else {
|
|
70
|
+
// Stale tokens from older verification attempts just add confusion —
|
|
71
|
+
// replace them (only google-site-verification records are touched)
|
|
72
|
+
for (const stale of atName.filter((r) => r.content.includes('google-site-verification'))) {
|
|
73
|
+
await cloudflareApi.makeRequest(`/zones/${zone.id}/dns_records/${stale.id}`, { method: 'DELETE' });
|
|
74
|
+
console.log(` ${chalk.dim('↻')} Deleted stale verification record`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await cloudflareApi.makeRequest(`/zones/${zone.id}/dns_records`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
type: 'TXT',
|
|
81
|
+
name: recordName,
|
|
82
|
+
content: `"${token}"`,
|
|
83
|
+
ttl: 60,
|
|
84
|
+
comment: 'Google domain verification',
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
console.log(` ${chalk.green('✓')} Verification TXT record added`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// === Verify — DNS may need a minute; interactive runs wait it out ===
|
|
91
|
+
let verified = false;
|
|
92
|
+
let lastError = null;
|
|
93
|
+
try {
|
|
94
|
+
await api.verifySite(domain, 'DNS_TXT', 'INET_DOMAIN');
|
|
95
|
+
verified = true;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
verified = isAlreadyVerified(error);
|
|
98
|
+
lastError = error.message;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!verified && canPrompt(options)) {
|
|
102
|
+
const result = await pollWithSpinner({
|
|
103
|
+
check: async () => {
|
|
104
|
+
try {
|
|
105
|
+
await api.verifySite(domain, 'DNS_TXT', 'INET_DOMAIN');
|
|
106
|
+
return { done: true };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Anything else = DNS still propagating; keep polling
|
|
109
|
+
return isAlreadyVerified(error) ? { done: true } : { done: false };
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
intervalMs: 5000,
|
|
113
|
+
message: 'Waiting for DNS propagation',
|
|
114
|
+
indent: ' ',
|
|
115
|
+
});
|
|
116
|
+
verified = result.success;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!verified) {
|
|
120
|
+
console.log(` ${chalk.yellow('⚠')} Verification pending${lastError ? chalk.dim(` (${lastError})`) : ''} — DNS is likely still propagating, rerun in a few minutes`);
|
|
121
|
+
return { status: 'warned', reason: 'domain verification pending — DNS is still propagating', output: { property: { pendingVerification: true } } };
|
|
122
|
+
}
|
|
123
|
+
console.log(` ${chalk.green('✓')} Domain verified`);
|
|
124
|
+
|
|
125
|
+
// === Add the property ===
|
|
126
|
+
try {
|
|
127
|
+
await api.addSite(propertyUrl);
|
|
128
|
+
console.log(` ${chalk.green('✓')} Domain property added to Search Console`);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (!error.message.includes('already exists')) {
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
console.log(` ${chalk.dim('→')} Dashboard: https://search.google.com/search-console?resource_id=${encodeURIComponent(propertyUrl)}`);
|
|
135
|
+
|
|
136
|
+
return { state: { propertyUrl, verificationMethod: 'DNS_TXT' } };
|
|
137
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the configured sitemaps are submitted for the domain property.
|
|
3
|
+
*
|
|
4
|
+
* Submits only what's MISSING — omega-manager resubmitted existing sitemaps
|
|
5
|
+
* on every run ("refresh"), which made a converged brand mutate forever;
|
|
6
|
+
* Google re-crawls submitted sitemaps on its own. Paths come from
|
|
7
|
+
* `search.providers.searchConsole.sitemapPaths` (default `/sitemap.xml`); brands without a
|
|
8
|
+
* web target have no sitemap to serve and are skipped.
|
|
9
|
+
*/
|
|
10
|
+
const chalk = require('chalk').default;
|
|
11
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
12
|
+
|
|
13
|
+
module.exports = async function ensureSitemaps(context) {
|
|
14
|
+
const { searchConsoleApi: api, brandConfig, brand, domain, serviceData, options = {} } = context;
|
|
15
|
+
|
|
16
|
+
if (brandConfig.search?.providers?.searchConsole?.submitSitemap === false) {
|
|
17
|
+
console.log(chalk.dim(' ⊘ Sitemap submission disabled (search.providers.searchConsole.submitSitemap = false)'));
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!brand.enabledTargets.includes('web')) {
|
|
22
|
+
console.log(chalk.dim(' ⊘ No web target — no sitemap to submit'));
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!serviceData.propertyUrl) {
|
|
27
|
+
console.log(chalk.dim(' ⊘ No Search Console property yet — rerun once the property is verified'));
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const siteUrl = serviceData.propertyUrl;
|
|
32
|
+
const paths = brandConfig.search?.providers?.searchConsole?.sitemapPaths || ['/sitemap.xml'];
|
|
33
|
+
const wanted = paths.map((path) => `https://${domain}${path}`);
|
|
34
|
+
|
|
35
|
+
// === READ ===
|
|
36
|
+
const existing = await api.listSitemaps(siteUrl);
|
|
37
|
+
const existingPaths = new Set(existing.map((s) => s.path));
|
|
38
|
+
|
|
39
|
+
const missing = wanted.filter((url) => !existingPaths.has(url));
|
|
40
|
+
|
|
41
|
+
if (missing.length === 0) {
|
|
42
|
+
console.log(` ${chalk.green('✓')} All ${wanted.length} sitemap(s) submitted`);
|
|
43
|
+
return { output: { sitemaps: { submitted: 0, existing: wanted.length } } };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (options.dryRun) {
|
|
47
|
+
return dryRunPlan(`submit ${missing.length} sitemap(s): ${missing.join(', ')}`, { output: { sitemaps: { planned: missing } } });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// === WRITE: submit only the missing ones ===
|
|
51
|
+
const submitted = [];
|
|
52
|
+
const errors = [];
|
|
53
|
+
for (const url of missing) {
|
|
54
|
+
try {
|
|
55
|
+
await api.submitSitemap(siteUrl, url);
|
|
56
|
+
submitted.push(url);
|
|
57
|
+
console.log(` ${chalk.green('✓')} Submitted: ${chalk.cyan(url)}`);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
errors.push({ url, error: error.message });
|
|
60
|
+
console.log(` ${chalk.yellow('⚠')} Failed: ${chalk.cyan(url)}${chalk.dim(` — ${error.message}`)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = { output: { sitemaps: { submitted: submitted.length, existing: wanted.length - missing.length, errors } } };
|
|
65
|
+
if (errors.length > 0) {
|
|
66
|
+
result.status = 'warned';
|
|
67
|
+
result.reason = `${errors.length} sitemap(s) failed to submit: ${errors.map((e) => e.url).join(', ')}`;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
};
|