@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,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync the brand's bookmarks to the companion Chrome extension.
|
|
3
|
+
*
|
|
4
|
+
* Starts the WebSocket server the extension auto-connects to (it retries
|
|
5
|
+
* every 5s), sends one OMEGA_BOOKMARK_SYNC message, and waits for the
|
|
6
|
+
* extension's ack. Interactive sessions only — a headless run has no
|
|
7
|
+
* Chrome to talk to, so it skips instead of burning the connect wait.
|
|
8
|
+
* Dry runs print the link groups that would sync without opening the
|
|
9
|
+
* server (and without the gcloud function-log lookup — no live reads).
|
|
10
|
+
*
|
|
11
|
+
* Link sources are config + state; groups with unmet inputs are simply
|
|
12
|
+
* absent (a brand without analytics IDs gets no Analytics folder).
|
|
13
|
+
*/
|
|
14
|
+
const { execFileSync } = require('node:child_process');
|
|
15
|
+
const chalk = require('chalk').default;
|
|
16
|
+
const { WebSocketServer } = require('ws');
|
|
17
|
+
const { brandRepo } = require('../../../vendor/config/index.js');
|
|
18
|
+
const { isInteractive } = require('../../../vendor/devkit/prompt.js');
|
|
19
|
+
const { resolveExtensionPort } = require('../../../lib/automation-client.js');
|
|
20
|
+
|
|
21
|
+
const CONNECT_TIMEOUT = 10000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Deployed Cloud Function names for the project (for per-function log
|
|
25
|
+
* links). Shells out to gcloud; any failure (no gcloud, no auth, no
|
|
26
|
+
* project) is just "no function links".
|
|
27
|
+
*/
|
|
28
|
+
function getDeployedFunctions(projectId) {
|
|
29
|
+
try {
|
|
30
|
+
const output = execFileSync(
|
|
31
|
+
'gcloud',
|
|
32
|
+
['functions', 'list', `--project=${projectId}`, '--format=value(name)'],
|
|
33
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
|
34
|
+
);
|
|
35
|
+
return output.trim().split('\n').filter(Boolean);
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build the bookmark link groups from brand config.
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} brandConfig - Merged brand config
|
|
45
|
+
* @param {Array} targets - Discovered target dirs ({ target } consulted)
|
|
46
|
+
* @param {Object} [extras] - { deployedFunctions } (injectable; defaults to none)
|
|
47
|
+
* @returns {Object} { Category: [{ title, url }] }
|
|
48
|
+
*/
|
|
49
|
+
function generateLinks(brandConfig, targets = [], extras = {}) {
|
|
50
|
+
const links = {};
|
|
51
|
+
|
|
52
|
+
const brandUrl = brandConfig.brand?.url;
|
|
53
|
+
const domain = brandUrl?.replace(/^https?:\/\//, '');
|
|
54
|
+
const firebaseProjectId = brandConfig.cloud?.config?.projectId;
|
|
55
|
+
const google = brandConfig.analytics?.providers?.google || {};
|
|
56
|
+
const github = brandConfig.repo?.providers?.github || {};
|
|
57
|
+
const deployedFunctions = extras.deployedFunctions || [];
|
|
58
|
+
|
|
59
|
+
// Cloud Console
|
|
60
|
+
if (firebaseProjectId) {
|
|
61
|
+
const functionLogUrl = (fnName) =>
|
|
62
|
+
`https://console.cloud.google.com/logs/query;query=--%20Date:%20%0A--%20Reason:%20%0Aresource.labels.function_name%3D%22${fnName}%22%0A--%20textPayload:%22%22;duration=P7D?project=${firebaseProjectId}`;
|
|
63
|
+
|
|
64
|
+
links.Cloud = [
|
|
65
|
+
{ title: 'Dashboard', url: `https://console.cloud.google.com/home/dashboard?project=${firebaseProjectId}` },
|
|
66
|
+
{ title: 'APIs & Services', url: `https://console.cloud.google.com/apis/dashboard?project=${firebaseProjectId}` },
|
|
67
|
+
{ title: 'IAM', url: `https://console.cloud.google.com/iam-admin/iam?project=${firebaseProjectId}` },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
if (deployedFunctions.length > 0) {
|
|
71
|
+
links.Cloud.push({ title: '───────────', url: `https://console.cloud.google.com/functions/list?project=${firebaseProjectId}` });
|
|
72
|
+
for (const fn of deployedFunctions) {
|
|
73
|
+
links.Cloud.push({ title: `${fn}()`, url: functionLogUrl(fn) });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Firebase Console
|
|
79
|
+
if (firebaseProjectId) {
|
|
80
|
+
links.Firebase = [
|
|
81
|
+
{ title: 'Overview', url: `https://console.firebase.google.com/project/${firebaseProjectId}/overview` },
|
|
82
|
+
{ title: 'Settings', url: `https://console.firebase.google.com/project/${firebaseProjectId}/settings/general` },
|
|
83
|
+
{ title: 'Authentication', url: `https://console.firebase.google.com/project/${firebaseProjectId}/authentication/users` },
|
|
84
|
+
{ title: 'App Check', url: `https://console.firebase.google.com/project/${firebaseProjectId}/appcheck` },
|
|
85
|
+
{ title: 'Firestore Database', url: `https://console.firebase.google.com/project/${firebaseProjectId}/firestore` },
|
|
86
|
+
{ title: 'Realtime Database', url: `https://console.firebase.google.com/project/${firebaseProjectId}/database` },
|
|
87
|
+
{ title: 'Storage', url: `https://console.firebase.google.com/project/${firebaseProjectId}/storage` },
|
|
88
|
+
{ title: 'Hosting', url: `https://console.firebase.google.com/project/${firebaseProjectId}/hosting` },
|
|
89
|
+
{ title: 'Functions', url: `https://console.firebase.google.com/project/${firebaseProjectId}/functions` },
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Analytics
|
|
94
|
+
if (google.accountId && google.propertyId) {
|
|
95
|
+
links.Analytics = [
|
|
96
|
+
{ title: 'Reports', url: `https://analytics.google.com/analytics/web/#/a${google.accountId}p${google.propertyId}/reports/intelligenthome` },
|
|
97
|
+
{ title: 'Realtime', url: `https://analytics.google.com/analytics/web/#/a${google.accountId}p${google.propertyId}/reports/dashboard?params=_u..nav%3Dmaui` },
|
|
98
|
+
{ title: 'Data Streams', url: `https://analytics.google.com/analytics/web/#/a${google.accountId}p${google.propertyId}/admin/streams` },
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Search Console
|
|
103
|
+
if (domain) {
|
|
104
|
+
links.Search = [
|
|
105
|
+
{ title: 'Performance', url: `https://search.google.com/search-console/performance/search-analytics?resource_id=sc-domain:${domain}` },
|
|
106
|
+
{ title: 'Indexing', url: `https://search.google.com/search-console/index?resource_id=sc-domain:${domain}` },
|
|
107
|
+
{ title: 'Sitemaps', url: `https://search.google.com/search-console/sitemaps?resource_id=sc-domain:${domain}` },
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Stripe Dashboard — per-brand keys, so the key's own account is the
|
|
112
|
+
// dashboard default (no account slug in the URL)
|
|
113
|
+
if (brandConfig.payment?.providers?.stripe) {
|
|
114
|
+
const s = 'https://dashboard.stripe.com';
|
|
115
|
+
links.Stripe = [
|
|
116
|
+
{ title: 'Dashboard', url: `${s}/dashboard` },
|
|
117
|
+
{ title: 'Products', url: `${s}/products?active=true` },
|
|
118
|
+
{ title: 'Customers', url: `${s}/customers` },
|
|
119
|
+
{ title: 'Subscriptions', url: `${s}/subscriptions?status=active` },
|
|
120
|
+
{ title: 'Radar', url: `${s}/radar` },
|
|
121
|
+
{ title: '───────────', url: `${s}/settings` },
|
|
122
|
+
{ title: 'Account', url: `${s}/settings/account` },
|
|
123
|
+
{ title: 'Profile', url: `${s}/settings/profile` },
|
|
124
|
+
{ title: 'Billing', url: `${s}/settings/billing/subscriptions` },
|
|
125
|
+
{ title: 'Emails', url: `${s}/settings/emails` },
|
|
126
|
+
{ title: 'Payouts', url: `${s}/settings/payouts` },
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// GitHub — one brand monorepo
|
|
131
|
+
if (github.org) {
|
|
132
|
+
// BOTH halves from the one derivation: an `owner/name` slug houses the repo
|
|
133
|
+
// under its own owner, which `repo.providers.github.org` does not name.
|
|
134
|
+
const { repo } = brandRepo(brandConfig);
|
|
135
|
+
links.GitHub = [
|
|
136
|
+
{ title: 'Repository', url: `https://github.com/${repo}` },
|
|
137
|
+
{ title: 'Actions', url: `https://github.com/${repo}/actions` },
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Live URLs
|
|
142
|
+
const liveLinks = [];
|
|
143
|
+
if (brandUrl) {
|
|
144
|
+
liveLinks.push({ title: 'Website', url: brandUrl });
|
|
145
|
+
}
|
|
146
|
+
if (domain && targets.some((entry) => entry.target === 'backend')) {
|
|
147
|
+
liveLinks.push({ title: 'API', url: `https://api.${domain}` });
|
|
148
|
+
}
|
|
149
|
+
if (liveLinks.length > 0) {
|
|
150
|
+
links.Live = liveLinks;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return links;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = async function ensureSync(context) {
|
|
157
|
+
const { brandConfig, targets, options = {} } = context;
|
|
158
|
+
|
|
159
|
+
const brandId = brandConfig.brand.id;
|
|
160
|
+
const brandName = brandConfig.brand.name || brandId;
|
|
161
|
+
const groupCount = (links) => Object.keys(links).length;
|
|
162
|
+
const linkCount = (links) => Object.values(links).flat().length;
|
|
163
|
+
|
|
164
|
+
if (options.dryRun) {
|
|
165
|
+
const links = generateLinks(brandConfig, targets);
|
|
166
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would sync ${chalk.cyan(linkCount(links))} bookmarks in ${chalk.cyan(groupCount(links))} groups: ${Object.keys(links).join(', ') || '(none)'}`);
|
|
167
|
+
return { output: { sync: { planned: Object.keys(links) } } };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!isInteractive()) {
|
|
171
|
+
console.log(chalk.dim(' ⊘ Interactive sessions only — the sync needs a running Chrome with the Omega extension connected'));
|
|
172
|
+
return { output: { sync: { reason: 'non-interactive' } } };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const firebaseProjectId = brandConfig.cloud?.config?.projectId;
|
|
176
|
+
const links = generateLinks(brandConfig, targets, {
|
|
177
|
+
deployedFunctions: firebaseProjectId ? getDeployedFunctions(firebaseProjectId) : [],
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const message = {
|
|
181
|
+
type: 'OMEGA_BOOKMARK_SYNC',
|
|
182
|
+
brand: { id: brandId, name: brandName, url: brandConfig.brand?.url },
|
|
183
|
+
links,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const port = resolveExtensionPort();
|
|
187
|
+
const connectTimeout = Number(process.env.OMEGA_EXTENSION_TIMEOUT) || CONNECT_TIMEOUT;
|
|
188
|
+
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
// Loopback only — the extension connects from this machine; binding all
|
|
191
|
+
// interfaces would expose the sync socket to the LAN for its lifetime
|
|
192
|
+
const wss = new WebSocketServer({ host: '127.0.0.1', port });
|
|
193
|
+
let resolved = false;
|
|
194
|
+
|
|
195
|
+
const cleanup = (result) => {
|
|
196
|
+
if (resolved) return;
|
|
197
|
+
resolved = true;
|
|
198
|
+
|
|
199
|
+
for (const client of wss.clients) {
|
|
200
|
+
client.terminate();
|
|
201
|
+
}
|
|
202
|
+
wss.close(() => resolve(result));
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const timeout = setTimeout(() => {
|
|
206
|
+
console.log(` ${chalk.yellow('⚠')} Extension not connected (timeout) — install/enable it from ${chalk.cyan('extension/')} (chrome://extensions → Load unpacked → extension/dist)`);
|
|
207
|
+
cleanup({ status: 'warned', reason: 'extension not connected', output: { sync: { reason: 'extension not connected' } } });
|
|
208
|
+
}, connectTimeout);
|
|
209
|
+
|
|
210
|
+
wss.on('error', (error) => {
|
|
211
|
+
clearTimeout(timeout);
|
|
212
|
+
const reason = error.code === 'EADDRINUSE'
|
|
213
|
+
? `port ${port} already in use (another manager run or the extension MCP server?)`
|
|
214
|
+
: error.message;
|
|
215
|
+
console.log(` ${chalk.yellow('⚠')} WebSocket server failed: ${reason}`);
|
|
216
|
+
cleanup({ status: 'warned', reason, output: { sync: { reason } } });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
wss.on('connection', (ws) => {
|
|
220
|
+
console.log(` ${chalk.dim('→')} Extension connected, sending ${chalk.cyan(brandName)} bookmarks (${linkCount(links)} links)...`);
|
|
221
|
+
|
|
222
|
+
ws.send(JSON.stringify(message));
|
|
223
|
+
|
|
224
|
+
ws.on('message', (data) => {
|
|
225
|
+
clearTimeout(timeout);
|
|
226
|
+
|
|
227
|
+
let response = null;
|
|
228
|
+
try {
|
|
229
|
+
response = JSON.parse(data.toString());
|
|
230
|
+
} catch {
|
|
231
|
+
// Non-JSON ack — treat as synced
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (response && response.success === false) {
|
|
235
|
+
console.log(` ${chalk.yellow('⚠')} Sync failed${chalk.dim(`: ${response.error}`)}`);
|
|
236
|
+
cleanup({ status: 'warned', reason: 'extension reported a sync failure', output: { sync: { reason: response.error || 'extension reported failure' } } });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log(` ${chalk.green('✓')} Bookmarks synced for ${chalk.cyan(brandName)}`);
|
|
241
|
+
cleanup({ output: { sync: { synced: true, links: linkCount(links) } } });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Extension closed without answering — the send went out; call it synced
|
|
245
|
+
ws.on('close', () => {
|
|
246
|
+
clearTimeout(timeout);
|
|
247
|
+
cleanup({ output: { sync: { synced: true, links: linkCount(links) } } });
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
console.log(` ${chalk.dim('→')} Waiting for the extension on ${chalk.cyan(`ws://localhost:${port}`)}...`);
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
module.exports.generateLinks = generateLinks;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bookmark service — pushes the brand's console/dashboard bookmarks to
|
|
3
|
+
* the companion Chrome extension (extension/) over the WebSocket
|
|
4
|
+
* protocol; the extension files them under Ω / {Brand} / {Category}.
|
|
5
|
+
*
|
|
6
|
+
* The links are derived from the brand's own config + state (new-world
|
|
7
|
+
* shapes: the single brand monorepo repo instead of omega-manager's
|
|
8
|
+
* per-target repos, unslugged Stripe dashboard URLs instead of the
|
|
9
|
+
* ITW platform-account deep links). The sync itself is interactive-only:
|
|
10
|
+
* it needs a running Chrome with the extension connected, so headless
|
|
11
|
+
* runs skip cleanly instead of sitting in the connect wait.
|
|
12
|
+
*/
|
|
13
|
+
const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
14
|
+
|
|
15
|
+
module.exports.run = createServiceRunner({
|
|
16
|
+
serviceDir: __dirname,
|
|
17
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the brand names the human its transactional email signs off as —
|
|
3
|
+
* `brand.contact.person.name` ([#694](https://github.com/Omega-JS-Stack/omega/issues/694)).
|
|
4
|
+
*
|
|
5
|
+
* @omega.js/backend's personal sends (welcome, discount nudge, checkup) resolve
|
|
6
|
+
* their signoff from it and throw a coded 400 without it. Found live: the first
|
|
7
|
+
* real signup on a launched brand returned 200 while all three sends failed in
|
|
8
|
+
* function logs nobody was watching, because nothing checked the value before
|
|
9
|
+
* the launch. SENDGRID_API_KEY is what turns those sends on, and this service
|
|
10
|
+
* only runs with it (or an injected client), so the SendGrid walk is the launch
|
|
11
|
+
* gate that catches the hole — and it FAILS, because a silent nudge is exactly
|
|
12
|
+
* what the runtime already does.
|
|
13
|
+
*
|
|
14
|
+
* Last in the operation order on purpose: the value is a precondition of
|
|
15
|
+
* @omega.js/backend's sends, not of anything provisioned here, so every SendGrid
|
|
16
|
+
* operation still converges and the run ends failed with the key named.
|
|
17
|
+
* The runtime error stays as it is — it names the key correctly already.
|
|
18
|
+
*/
|
|
19
|
+
const chalk = require('chalk').default;
|
|
20
|
+
|
|
21
|
+
const CONFIG_PATH = 'brand.contact.person.name';
|
|
22
|
+
|
|
23
|
+
module.exports = async function ensureContactPerson(context) {
|
|
24
|
+
const { brandConfig } = context;
|
|
25
|
+
const person = brandConfig.brand?.contact?.person || {};
|
|
26
|
+
|
|
27
|
+
if (person.name) {
|
|
28
|
+
console.log(` ${chalk.green('✓')} Email signoff: ${chalk.cyan(person.name)}`);
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(` ${chalk.red('✗')} No ${chalk.cyan(CONFIG_PATH)} — every personal send (welcome, discount nudge, checkup) fails at runtime`);
|
|
33
|
+
console.log(` ${chalk.dim('→')} Set ${chalk.cyan(CONFIG_PATH)} in config/omega.json5, then rerun`);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
status: 'error',
|
|
37
|
+
error: `Missing ${CONFIG_PATH} in config/omega.json5 — @omega.js/backend's welcome, discount-nudge and checkup emails cannot send without it`,
|
|
38
|
+
output: { contactPerson: { missing: CONFIG_PATH } },
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure @omega.js/backend's custom fields exist in SendGrid with the right types.
|
|
3
|
+
*
|
|
4
|
+
* The field list comes from @omega.js/backend's marketing SSOT — its OWN
|
|
5
|
+
* per-provider view of the catalog (#695), the same derivation the backend's
|
|
6
|
+
* contact sync writes through, so what is provisioned here and what is sent
|
|
7
|
+
* there are one list (SendGrid has first/last name built in, so the catalog
|
|
8
|
+
* skips them on both sides). A type mismatch can't be patched in SendGrid, so
|
|
9
|
+
* the field is deleted and recreated. Fields @omega.js/backend doesn't own are
|
|
10
|
+
* never touched.
|
|
11
|
+
*/
|
|
12
|
+
const chalk = require('chalk').default;
|
|
13
|
+
const { fieldsFor } = require('../../../lib/backend-marketing.js');
|
|
14
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
15
|
+
|
|
16
|
+
const SENDGRID_FIELDS = fieldsFor('sendgrid');
|
|
17
|
+
|
|
18
|
+
// @omega.js/backend type → SendGrid field_type
|
|
19
|
+
const TYPE_MAP = { text: 'Text', number: 'Number', date: 'Date' };
|
|
20
|
+
|
|
21
|
+
module.exports = async function ensureCustomFields(context) {
|
|
22
|
+
const { sendgridApi: api, options = {} } = context;
|
|
23
|
+
|
|
24
|
+
const existing = await api.getCustomFields();
|
|
25
|
+
const existingByName = Object.fromEntries(existing.map((f) => [f.name, f]));
|
|
26
|
+
|
|
27
|
+
const missing = [];
|
|
28
|
+
const mismatched = [];
|
|
29
|
+
|
|
30
|
+
for (const field of SENDGRID_FIELDS) {
|
|
31
|
+
const sgField = existingByName[field.name];
|
|
32
|
+
|
|
33
|
+
if (!sgField) {
|
|
34
|
+
missing.push(field);
|
|
35
|
+
} else if (sgField.field_type !== TYPE_MAP[field.type]) {
|
|
36
|
+
mismatched.push({ ...field, existingId: sgField.id, existingType: sgField.field_type });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (missing.length === 0 && mismatched.length === 0) {
|
|
41
|
+
console.log(` ${chalk.green('✓')} All ${SENDGRID_FIELDS.length} custom fields exist`);
|
|
42
|
+
return { output: { customFields: { total: SENDGRID_FIELDS.length, created: 0, recreated: 0 } } };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (options.dryRun) {
|
|
46
|
+
const planned = {
|
|
47
|
+
create: missing.map((f) => f.name),
|
|
48
|
+
recreate: mismatched.map((f) => `${f.name} (${f.existingType} → ${TYPE_MAP[f.type]})`),
|
|
49
|
+
};
|
|
50
|
+
return dryRunPlan(`create ${planned.create.length}, recreate ${planned.recreate.length} field(s)`, { output: { customFields: { planned } } });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Type mismatches: delete, then recreate alongside the missing ones
|
|
54
|
+
for (const field of mismatched) {
|
|
55
|
+
await api.deleteCustomField(field.existingId);
|
|
56
|
+
console.log(` ${chalk.yellow('↻')} Deleted ${chalk.cyan(field.name)} ${chalk.dim(`(was ${field.existingType}, need ${TYPE_MAP[field.type]})`)}`);
|
|
57
|
+
missing.push(field);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const field of missing) {
|
|
61
|
+
const created = await api.createCustomField(field.name, TYPE_MAP[field.type]);
|
|
62
|
+
console.log(` ${chalk.green('✓')} Created ${chalk.cyan(field.name)} ${chalk.dim(`(${created.id})`)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Verify everything actually persisted
|
|
66
|
+
const verify = await api.getCustomFields();
|
|
67
|
+
const verifyByName = new Set(verify.map((f) => f.name));
|
|
68
|
+
const stillMissing = SENDGRID_FIELDS.filter((f) => !verifyByName.has(f.name));
|
|
69
|
+
|
|
70
|
+
if (stillMissing.length > 0) {
|
|
71
|
+
console.log(` ${chalk.yellow('⚠')} ${stillMissing.length} field(s) failed to persist: ${chalk.cyan(stillMissing.map((f) => f.name).join(', '))}`);
|
|
72
|
+
return { status: 'warned', reason: `${stillMissing.length} field(s) failed to persist: ${stillMissing.map((f) => f.name).join(', ')}`, output: { customFields: { total: SENDGRID_FIELDS.length, failed: stillMissing.map((f) => f.name) } } };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
output: {
|
|
77
|
+
customFields: {
|
|
78
|
+
total: SENDGRID_FIELDS.length,
|
|
79
|
+
created: missing.length - mismatched.length,
|
|
80
|
+
recreated: mismatched.length,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure SendGrid domain authentication for the brand's domain.
|
|
3
|
+
*
|
|
4
|
+
* A valid authentication is converged proof. A missing one is created in one
|
|
5
|
+
* pass: authenticate in SendGrid (automatic_security → 3 CNAMEs), diff-sync
|
|
6
|
+
* the records into the Cloudflare apex zone (exact-match = untouched, wrong
|
|
7
|
+
* content = patched, missing = created), then validate — interactive runs
|
|
8
|
+
* poll until DNS propagates and SendGrid confirms; non-interactive/dry runs
|
|
9
|
+
* validate ONCE, report warned, and the rerun converges. No Cloudflare token
|
|
10
|
+
* → the records to add manually, and validation is still attempted so a
|
|
11
|
+
* manual fix converges on rerun.
|
|
12
|
+
*/
|
|
13
|
+
const chalk = require('chalk').default;
|
|
14
|
+
const { pollWithSpinner } = require('../../../vendor/devkit/flows.js');
|
|
15
|
+
const { canPrompt, dryRunPlan } = require('../../../lib/run-gates.js');
|
|
16
|
+
const { syncDnsRecords, manualRecordList } = require('../lib/dns-sync.js');
|
|
17
|
+
|
|
18
|
+
const SUBDOMAIN = 'emailauth';
|
|
19
|
+
|
|
20
|
+
// The record set automatic_security returns
|
|
21
|
+
const DNS_KEYS = ['mail_cname', 'dkim1', 'dkim2'];
|
|
22
|
+
|
|
23
|
+
module.exports = async function ensureDomainAuth(context) {
|
|
24
|
+
const { sendgridApi: api, cloudflareApi, domain, apexDomain, options = {} } = context;
|
|
25
|
+
|
|
26
|
+
// === READ: is the domain already authenticated and valid? ===
|
|
27
|
+
const domains = await api.getAuthenticatedDomains();
|
|
28
|
+
let domainAuth = domains.find((d) => d.domain === domain);
|
|
29
|
+
|
|
30
|
+
if (domainAuth?.valid) {
|
|
31
|
+
console.log(` ${chalk.green('✓')} Domain ${chalk.cyan(domain)} authenticated`);
|
|
32
|
+
return { output: { domainAuth: { id: domainAuth.id, valid: true } } };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (options.dryRun) {
|
|
36
|
+
const planned = domainAuth ? ['dns-records', 'validate'] : ['authenticate-domain', 'dns-records', 'validate'];
|
|
37
|
+
return dryRunPlan(`${planned.join(' → ')}`, { output: { domainAuth: { planned } } });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// === Create the authentication when missing ===
|
|
41
|
+
if (!domainAuth) {
|
|
42
|
+
domainAuth = await api.authenticateDomain(domain, SUBDOMAIN);
|
|
43
|
+
console.log(` ${chalk.green('✓')} Created domain authentication for ${chalk.cyan(domain)}`);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(` ${chalk.yellow('↻')} Domain ${chalk.cyan(domain)} exists but is not validated — reconciling DNS`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const records = DNS_KEYS
|
|
49
|
+
.map((key) => ({ key, record: domainAuth.dns?.[key] }))
|
|
50
|
+
.filter(({ record }) => record);
|
|
51
|
+
|
|
52
|
+
if (records.length === 0) {
|
|
53
|
+
throw new Error('SendGrid returned no DNS records for the domain authentication');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// === DNS via Cloudflare (or manual guidance) ===
|
|
57
|
+
const synced = await syncDnsRecords(cloudflareApi, apexDomain, records);
|
|
58
|
+
|
|
59
|
+
// === Validate — DNS may need a minute; interactive runs wait it out ===
|
|
60
|
+
const isValid = (validation) => Boolean(validation?.validation_results
|
|
61
|
+
&& Object.values(validation.validation_results).every((r) => r.valid));
|
|
62
|
+
|
|
63
|
+
let valid = isValid(await api.validateDomain(domainAuth.id));
|
|
64
|
+
|
|
65
|
+
if (!valid && canPrompt(options)) {
|
|
66
|
+
const result = await pollWithSpinner({
|
|
67
|
+
check: async () => {
|
|
68
|
+
try {
|
|
69
|
+
return isValid(await api.validateDomain(domainAuth.id)) ? { done: true } : { done: false };
|
|
70
|
+
} catch {
|
|
71
|
+
return { done: false };
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
intervalMs: 10000,
|
|
75
|
+
message: 'Validating domain authentication (waiting for DNS propagation)',
|
|
76
|
+
indent: ' ',
|
|
77
|
+
});
|
|
78
|
+
valid = result.success;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (valid) {
|
|
82
|
+
console.log(` ${chalk.green('✓')} Domain ${chalk.cyan(domain)} validated`);
|
|
83
|
+
return { output: { domainAuth: { id: domainAuth.id, valid: true } } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(` ${chalk.yellow('⚠')} Validation pending — DNS is likely still propagating, rerun in a few minutes`);
|
|
87
|
+
return {
|
|
88
|
+
status: 'warned',
|
|
89
|
+
reason: 'domain validation pending — DNS is still propagating',
|
|
90
|
+
output: { domainAuth: { id: domainAuth.id, valid: false, ...(synced ? {} : { manualRecords: manualRecordList(records) }) } },
|
|
91
|
+
};
|
|
92
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure the account-global SendGrid Event Webhook points at the parent
|
|
3
|
+
* @omega.js/backend's forwarder with the consent-pipeline events enabled.
|
|
4
|
+
*
|
|
5
|
+
* SendGrid supports ONE Event Webhook per account, so it always targets the
|
|
6
|
+
* parent brand (`parent` in omega.json5 — 'self' when this brand IS the
|
|
7
|
+
* parent); the parent fans events out to each brand's own
|
|
8
|
+
* /marketing/webhook endpoint. Sibling brands sharing the account all
|
|
9
|
+
* converge on the same URL + toggle set. Only the consent toggles are
|
|
10
|
+
* managed — tracking toggles (open/click/delivered/…) are not ours to touch.
|
|
11
|
+
* Drift is patched with the minimum diff.
|
|
12
|
+
*/
|
|
13
|
+
const chalk = require('chalk').default;
|
|
14
|
+
const { dryRunPlan } = require('../../../lib/run-gates.js');
|
|
15
|
+
|
|
16
|
+
// The consent-pipeline events @omega.js/backend must receive
|
|
17
|
+
const DESIRED_TOGGLES = {
|
|
18
|
+
bounce: true,
|
|
19
|
+
dropped: true,
|
|
20
|
+
spam_report: true,
|
|
21
|
+
unsubscribe: true,
|
|
22
|
+
group_unsubscribe: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = async function ensureEventWebhook(context) {
|
|
26
|
+
const { sendgridApi: api, brandConfig, domain, options = {} } = context;
|
|
27
|
+
|
|
28
|
+
const parent = brandConfig.parent;
|
|
29
|
+
if (parent === false) {
|
|
30
|
+
// Tri-state: explicit false = deliberate opt-out, no nudge
|
|
31
|
+
console.log(chalk.dim(' ⊘ parent = false — Event Webhook opted out'));
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
if (!parent) {
|
|
35
|
+
console.log(chalk.dim(' ⊘ No parent configured — nothing to point the Event Webhook at'));
|
|
36
|
+
console.log(chalk.dim(" → Set parent in omega.json5 ('self' when this brand runs the central backend)"));
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!process.env.OMEGA_WEBHOOK_KEY) {
|
|
41
|
+
// Unreachable by design (#635): the key is OMEGA's own (`generated:` in
|
|
42
|
+
// the env schema) and the campaigns service's setup mints it through the
|
|
43
|
+
// shared contract before any operation runs. Absent here = broken wiring.
|
|
44
|
+
throw new Error('OMEGA_WEBHOOK_KEY absent after the campaigns setup ran — its REQUIRES entry or its requestServiceInput call is missing');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parentHost = parent === 'self'
|
|
48
|
+
? domain
|
|
49
|
+
: parent.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
50
|
+
const desiredUrl = `https://api.${parentHost}/omega/marketing/webhook/forward?provider=sendgrid&key=${process.env.OMEGA_WEBHOOK_KEY}`;
|
|
51
|
+
|
|
52
|
+
const current = await api.getEventWebhookSettings();
|
|
53
|
+
|
|
54
|
+
const patch = {};
|
|
55
|
+
if (current.url !== desiredUrl) {
|
|
56
|
+
patch.url = desiredUrl;
|
|
57
|
+
}
|
|
58
|
+
if (current.enabled !== true) {
|
|
59
|
+
patch.enabled = true;
|
|
60
|
+
}
|
|
61
|
+
for (const [toggle, value] of Object.entries(DESIRED_TOGGLES)) {
|
|
62
|
+
if (current[toggle] !== value) {
|
|
63
|
+
patch[toggle] = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (Object.keys(patch).length === 0) {
|
|
68
|
+
console.log(` ${chalk.green('✓')} Event Webhook up to date`);
|
|
69
|
+
console.log(` ${chalk.dim('→')} ${chalk.dim(`https://api.${parentHost}/omega/marketing/webhook/forward`)}`);
|
|
70
|
+
return { output: { eventWebhook: { url: 'converged' } } };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (options.dryRun) {
|
|
74
|
+
return dryRunPlan(`patch: ${Object.keys(patch).join(', ')}`, { output: { eventWebhook: { planned: Object.keys(patch) } } });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (patch.url) {
|
|
78
|
+
console.log(` ${chalk.yellow('↻')} Pointing Event Webhook at ${chalk.cyan(`api.${parentHost}`)}'s forwarder`);
|
|
79
|
+
}
|
|
80
|
+
const enablingToggles = Object.keys(patch).filter((key) => key !== 'url' && key !== 'enabled');
|
|
81
|
+
if (enablingToggles.length > 0) {
|
|
82
|
+
console.log(` ${chalk.yellow('↻')} Enabling events: ${chalk.cyan(enablingToggles.join(', '))}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await api.updateEventWebhookSettings(patch);
|
|
86
|
+
console.log(` ${chalk.green('✓')} Event Webhook updated`);
|
|
87
|
+
|
|
88
|
+
return { output: { eventWebhook: { updated: Object.keys(patch) } } };
|
|
89
|
+
};
|