@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,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Runner — shared library for running service operations. Ported from
|
|
3
|
+
* omega-manager's src/lib/service-runner.js with the SAME strict contract so
|
|
4
|
+
* services port over without semantic drift.
|
|
5
|
+
*
|
|
6
|
+
* All services use this same pattern:
|
|
7
|
+
* 1. Operations are declared in src/config.js OPERATIONS[serviceName]
|
|
8
|
+
* 2. Handlers live in ensure/, read/, transform/, write/ directories
|
|
9
|
+
* 3. This runner loads and executes handlers in operation order
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // In src/services/my-service/index.js
|
|
13
|
+
* const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
14
|
+
* module.exports.run = createServiceRunner({ serviceDir: __dirname });
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // With custom setup/hooks
|
|
18
|
+
* module.exports.run = createServiceRunner({
|
|
19
|
+
* serviceDir: __dirname,
|
|
20
|
+
* setup: (context) => {
|
|
21
|
+
* if (!context.brandConfig.myService) {
|
|
22
|
+
* return { skip: true, reason: 'no config' };
|
|
23
|
+
* }
|
|
24
|
+
* return { api: new MyAPI() };
|
|
25
|
+
* },
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const { join } = require('node:path');
|
|
31
|
+
const chalk = require('chalk').default;
|
|
32
|
+
|
|
33
|
+
const { needsInteractiveSkip } = require('./run-gates.js');
|
|
34
|
+
const { CONSENT_REQUIRED } = require('./google-auth.js');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Class a caught error (#228): a consent gate is a pending HUMAN step, not a
|
|
38
|
+
* failure — it becomes the warned step-aside the run summary's ⚑ section
|
|
39
|
+
* names, so the walk (and an `omega dev` boot on top of it) continues.
|
|
40
|
+
* Everything else stays loud.
|
|
41
|
+
*
|
|
42
|
+
* @param {Error} error - The caught error.
|
|
43
|
+
* @param {string} operationName - The operation's output key.
|
|
44
|
+
* @returns {object|null} The needsInteractiveSkip return, or null when the
|
|
45
|
+
* error is a real failure.
|
|
46
|
+
*/
|
|
47
|
+
function pendingGate(error, operationName) {
|
|
48
|
+
if (error?.code !== CONSENT_REQUIRED) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(` ${chalk.yellow('⚑')} Needs an interactive run${chalk.dim(`: ${error.message}`)}`);
|
|
53
|
+
return needsInteractiveSkip(operationName, error.message);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load an operation handler from a service directory.
|
|
58
|
+
*
|
|
59
|
+
* Missing handler file → null (the runner logs and continues). A handler file
|
|
60
|
+
* that EXISTS but fails to require throws loudly — omega-manager's message
|
|
61
|
+
* sniffing silently swallowed broken handlers; existence-check-first doesn't.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} serviceDir - Absolute path to service directory
|
|
64
|
+
* @param {string} operationName - Name of the operation (e.g., 'targets')
|
|
65
|
+
* @param {string} type - Handler type ('ensure', 'read', 'transform', 'write')
|
|
66
|
+
* @returns {Function|null} - Handler function or null if not found
|
|
67
|
+
*/
|
|
68
|
+
function getOperationHandler(serviceDir, operationName, type) {
|
|
69
|
+
const modulePath = join(serviceDir, type, `${operationName}.js`);
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(modulePath)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const module = require(modulePath);
|
|
76
|
+
return module.default || module || null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Split a handler return into the within-run carry vs transient output.
|
|
81
|
+
*
|
|
82
|
+
* STRICT contract — handler returns MUST be one of:
|
|
83
|
+
* - undefined / null → no-op
|
|
84
|
+
* - { state: {...} } → carry only (this run's serviceData, never a file)
|
|
85
|
+
* - { output: {...} } → transient only (lands in .omega/runs/{ts}.json)
|
|
86
|
+
* - { state, output } → both
|
|
87
|
+
* - { status, error?, reason?, state?, output? } → status/error/reason are metadata, allowed alongside state/output
|
|
88
|
+
*
|
|
89
|
+
* Any other top-level key throws. This prevents accidental data leakage into
|
|
90
|
+
* the carry via an "everything dumps to serviceData" pattern.
|
|
91
|
+
*
|
|
92
|
+
* `state` is the LATER-OPERATIONS channel and nothing more (#434 retired the
|
|
93
|
+
* .omega/state.json cache): a value a following operation in the same service needs
|
|
94
|
+
* (the zone id, the bundle id, the Sentry project map). A fact that must
|
|
95
|
+
* OUTLIVE the run belongs in config/omega.json5 via lib/config-write.js — or
|
|
96
|
+
* in the brand .env via lib/env-secret.js when it is secret-shaped. Anything
|
|
97
|
+
* else (counts, success/fail flags, "what happened this run") goes to `output`.
|
|
98
|
+
*/
|
|
99
|
+
const ALLOWED_KEYS = new Set(['state', 'output', 'status', 'error', 'reason']);
|
|
100
|
+
|
|
101
|
+
function splitReturn(result, operationName = 'unknown') {
|
|
102
|
+
if (result === undefined || result === null) {
|
|
103
|
+
return { state: null, output: null };
|
|
104
|
+
}
|
|
105
|
+
if (typeof result !== 'object' || Array.isArray(result)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Handler "${operationName}" returned ${Array.isArray(result) ? 'an array' : typeof result}. `
|
|
108
|
+
+ `Returns must be undefined or an object with shape { state?, output?, status?, error?, reason? }.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const keys = Object.keys(result);
|
|
113
|
+
const invalidKeys = keys.filter((k) => !ALLOWED_KEYS.has(k));
|
|
114
|
+
if (invalidKeys.length > 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Handler "${operationName}" returned invalid top-level key(s): ${invalidKeys.join(', ')}. `
|
|
117
|
+
+ `Allowed keys: state, output, status, error, reason. `
|
|
118
|
+
+ `Wrap durable IDs in { state: {...} } and transient flags/counts in { output: {...} }.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
state: 'state' in result ? result.state : null,
|
|
124
|
+
output: 'output' in result ? result.output : null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Run ensure operation
|
|
130
|
+
*/
|
|
131
|
+
async function runEnsure(serviceDir, operation, context, accumulators) {
|
|
132
|
+
const handler = getOperationHandler(serviceDir, operation.name, 'ensure');
|
|
133
|
+
|
|
134
|
+
if (!handler) {
|
|
135
|
+
console.log(` ${chalk.yellow('⚠')} No ensure handler for ${chalk.bold(operation.name)}`);
|
|
136
|
+
return { status: 'continue' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// serviceData passed to handler = merged state + output (so handlers can read previous handler state)
|
|
141
|
+
const result = await handler({ ...context, serviceData: { ...accumulators.state, ...accumulators.output } });
|
|
142
|
+
|
|
143
|
+
const { state, output } = splitReturn(result, operation.name);
|
|
144
|
+
if (state) Object.assign(accumulators.state, state);
|
|
145
|
+
if (output) Object.assign(accumulators.output, output);
|
|
146
|
+
|
|
147
|
+
// Status check on the raw handler return
|
|
148
|
+
if (result?.status === 'error') return { status: 'error', error: result.error };
|
|
149
|
+
if (result?.status === 'warned') return { status: 'warned', reason: result.reason };
|
|
150
|
+
|
|
151
|
+
return { status: 'continue' };
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const pending = pendingGate(error, operation.name);
|
|
154
|
+
if (pending) {
|
|
155
|
+
Object.assign(accumulators.output, pending.output);
|
|
156
|
+
return { status: 'warned', reason: pending.reason };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.error(` ${chalk.red('❌')} Ensure failed${chalk.dim(`: ${error.message}`)}`);
|
|
160
|
+
return { status: 'error', error: error.message };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Run read operation
|
|
166
|
+
*/
|
|
167
|
+
async function runRead(serviceDir, operation, context, serviceData) {
|
|
168
|
+
const handler = getOperationHandler(serviceDir, operation.name, 'read');
|
|
169
|
+
|
|
170
|
+
if (!handler) {
|
|
171
|
+
return { data: null, status: 'continue' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const result = await handler({ ...context, serviceData });
|
|
176
|
+
|
|
177
|
+
if (result?.count !== undefined) {
|
|
178
|
+
console.log(` ${chalk.green('✓')} Read ${chalk.dim(`(${result.count} items)`)}`);
|
|
179
|
+
} else {
|
|
180
|
+
console.log(` ${chalk.green('✓')} Read`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { data: result, status: 'continue' };
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const pending = pendingGate(error, operation.name);
|
|
186
|
+
if (pending) {
|
|
187
|
+
// No accumulators here — the caller merges the marker
|
|
188
|
+
return { data: null, status: 'warned', output: pending.output, reason: pending.reason };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.error(` ${chalk.red('❌')} Read failed${chalk.dim(`: ${error.message}`)}`);
|
|
192
|
+
return { data: null, status: 'error' };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Run transform operation
|
|
198
|
+
*/
|
|
199
|
+
async function runTransform(serviceDir, operation, context, serviceData, readData) {
|
|
200
|
+
const handler = getOperationHandler(serviceDir, operation.name, 'transform');
|
|
201
|
+
|
|
202
|
+
if (!handler) {
|
|
203
|
+
return { data: readData, status: 'continue' };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const result = await handler({ ...context, serviceData, readData });
|
|
208
|
+
|
|
209
|
+
if (!result) {
|
|
210
|
+
console.log(` ${chalk.dim('⊘ No changes needed')}`);
|
|
211
|
+
return { data: null, status: 'skip' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(` ${chalk.green('✓')} Transform`);
|
|
215
|
+
return { data: result, status: 'continue' };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const pending = pendingGate(error, operation.name);
|
|
218
|
+
if (pending) {
|
|
219
|
+
return { data: null, status: 'warned', output: pending.output, reason: pending.reason };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.error(` ${chalk.red('❌')} Transform failed${chalk.dim(`: ${error.message}`)}`);
|
|
223
|
+
return { data: null, status: 'error' };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Run write operation
|
|
229
|
+
*/
|
|
230
|
+
async function runWrite(serviceDir, operation, context, accumulators, data) {
|
|
231
|
+
const handler = getOperationHandler(serviceDir, operation.name, 'write');
|
|
232
|
+
|
|
233
|
+
if (!handler) {
|
|
234
|
+
console.log(` ${chalk.yellow('⚠')} No write handler for ${chalk.bold(operation.name)}`);
|
|
235
|
+
return { status: 'continue' };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const result = await handler({
|
|
240
|
+
...context,
|
|
241
|
+
serviceData: { ...accumulators.state, ...accumulators.output },
|
|
242
|
+
data,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
console.log(` ${chalk.green('✓')} Write`);
|
|
246
|
+
|
|
247
|
+
const { state, output } = splitReturn(result, operation.name);
|
|
248
|
+
if (state) Object.assign(accumulators.state, state);
|
|
249
|
+
if (output) Object.assign(accumulators.output, output);
|
|
250
|
+
|
|
251
|
+
if (result?.status === 'error') return { status: 'error', error: result.error };
|
|
252
|
+
if (result?.status === 'warned') return { status: 'warned', reason: result.reason };
|
|
253
|
+
|
|
254
|
+
return { status: 'continue' };
|
|
255
|
+
} catch (error) {
|
|
256
|
+
const pending = pendingGate(error, operation.name);
|
|
257
|
+
if (pending) {
|
|
258
|
+
Object.assign(accumulators.output, pending.output);
|
|
259
|
+
return { status: 'warned', reason: pending.reason };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.error(` ${chalk.red('❌')} Write failed${chalk.dim(`: ${error.message}`)}`);
|
|
263
|
+
return { status: 'error', error: error.message };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Create a service runner function
|
|
269
|
+
*
|
|
270
|
+
* @param {Object} options - Configuration options
|
|
271
|
+
* @param {string} options.serviceDir - Absolute path to the service's directory
|
|
272
|
+
* (handlers load from its ensure/read/transform/write subdirs). Services pass
|
|
273
|
+
* __dirname; context.serviceDir is the fallback for tests/embedding.
|
|
274
|
+
* @param {Function} options.setup - Optional setup function called before operations
|
|
275
|
+
* - Receives: (context) => { skip?, reason?, ...additionalContext }
|
|
276
|
+
* - Return { skip: true, reason: 'message' } to skip the service
|
|
277
|
+
* - Return additional properties to merge into context (e.g., { api: new API() })
|
|
278
|
+
* @param {boolean} options.logOperations - Whether to log each operation (default: true)
|
|
279
|
+
* @param {boolean} options.stopOnError - Whether to stop on first error (default: true)
|
|
280
|
+
* @returns {Function} - Service runner function
|
|
281
|
+
*/
|
|
282
|
+
function createServiceRunner(options = {}) {
|
|
283
|
+
const {
|
|
284
|
+
serviceDir: ownServiceDir = null,
|
|
285
|
+
setup = null,
|
|
286
|
+
logOperations = true,
|
|
287
|
+
stopOnError = true,
|
|
288
|
+
} = options;
|
|
289
|
+
|
|
290
|
+
return async function run(context) {
|
|
291
|
+
// Run setup if provided
|
|
292
|
+
let setupResult = {};
|
|
293
|
+
if (setup) {
|
|
294
|
+
setupResult = await setup(context);
|
|
295
|
+
|
|
296
|
+
if (setupResult?.skip) {
|
|
297
|
+
console.log(` ${chalk.dim(`⊘ Skipped (${setupResult.reason || 'setup returned skip'})`)}`);
|
|
298
|
+
return {
|
|
299
|
+
status: 'skipped',
|
|
300
|
+
reason: setupResult.reason,
|
|
301
|
+
// Machine-readable missing-secret list (cp114) — the run summary
|
|
302
|
+
// aggregates these into the 🔑 section
|
|
303
|
+
...(setupResult.missingEnv ? { missingEnv: setupResult.missingEnv } : {}),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Use operations from setup result if provided, otherwise from context
|
|
309
|
+
const operations = setupResult.operations || context.operations || [];
|
|
310
|
+
|
|
311
|
+
// Merge setup result into context
|
|
312
|
+
const enrichedContext = { ...context, ...setupResult };
|
|
313
|
+
|
|
314
|
+
const serviceDir = ownServiceDir || enrichedContext.serviceDir;
|
|
315
|
+
if (!serviceDir) {
|
|
316
|
+
throw new Error('serviceDir must be provided via createServiceRunner options or context');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Accumulators: the within-run carry (→ each later operation's
|
|
320
|
+
// serviceData) vs transient output (→ .omega/runs/{ts}.json). Setup's
|
|
321
|
+
// serviceData seeds the carry — that's how a service hands its
|
|
322
|
+
// operations something it resolved once (the Stripe account id).
|
|
323
|
+
const accumulators = {
|
|
324
|
+
state: {
|
|
325
|
+
...context.serviceData,
|
|
326
|
+
...(setupResult.serviceData || {}),
|
|
327
|
+
},
|
|
328
|
+
output: {},
|
|
329
|
+
};
|
|
330
|
+
let overallStatus = 'success';
|
|
331
|
+
let firstError = null;
|
|
332
|
+
|
|
333
|
+
// What warned/failed, by NAME (#643): the run summary's breakdown reads
|
|
334
|
+
// these instead of collapsing every operation into "some operations had
|
|
335
|
+
// issues". `reason` is the operation's own one-liner (never secret
|
|
336
|
+
// values); null when it gave none, and the summary prints the name alone.
|
|
337
|
+
const warned = [];
|
|
338
|
+
const failed = [];
|
|
339
|
+
const recordWarned = (operation, reason) => warned.push({ operation, reason: reason || null });
|
|
340
|
+
const recordFailed = (operation, reason) => failed.push({ operation, reason: reason || null });
|
|
341
|
+
|
|
342
|
+
// Process operations in order
|
|
343
|
+
for (const operation of operations) {
|
|
344
|
+
if (logOperations) {
|
|
345
|
+
console.log(` ${chalk.dim('→')} ${operation.name}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Handle ensure operations
|
|
349
|
+
if (operation.ensure) {
|
|
350
|
+
const result = await runEnsure(serviceDir, operation, enrichedContext, accumulators);
|
|
351
|
+
|
|
352
|
+
if (result.status === 'error') {
|
|
353
|
+
overallStatus = 'error';
|
|
354
|
+
firstError = firstError || result.error || `${operation.name} failed`;
|
|
355
|
+
recordFailed(operation.name, result.error);
|
|
356
|
+
if (stopOnError) break;
|
|
357
|
+
} else if (result.status === 'warned') {
|
|
358
|
+
recordWarned(operation.name, result.reason);
|
|
359
|
+
if (overallStatus !== 'error') overallStatus = 'warned';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Handle read/write operations
|
|
366
|
+
if (operation.read || operation.write) {
|
|
367
|
+
let readData = null;
|
|
368
|
+
const mergedView = { ...accumulators.state, ...accumulators.output };
|
|
369
|
+
|
|
370
|
+
// Read phase
|
|
371
|
+
if (operation.read) {
|
|
372
|
+
const readResult = await runRead(serviceDir, operation, enrichedContext, mergedView);
|
|
373
|
+
readData = readResult.data;
|
|
374
|
+
|
|
375
|
+
if (readResult.status === 'error') {
|
|
376
|
+
overallStatus = 'error';
|
|
377
|
+
firstError = firstError || `${operation.name} read failed`;
|
|
378
|
+
recordFailed(operation.name, 'read failed');
|
|
379
|
+
if (stopOnError) break;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// A pending gate (#228): carry its marker and move on — there is
|
|
383
|
+
// nothing to transform or write on top of an operation that never
|
|
384
|
+
// read
|
|
385
|
+
if (readResult.status === 'warned') {
|
|
386
|
+
Object.assign(accumulators.output, readResult.output);
|
|
387
|
+
recordWarned(operation.name, readResult.reason);
|
|
388
|
+
if (overallStatus !== 'error') overallStatus = 'warned';
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Call onRead hook if provided (e.g., to save read data to disk)
|
|
393
|
+
if (readData && enrichedContext.onRead) {
|
|
394
|
+
await enrichedContext.onRead(operation.name, readData);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Write phase (with optional transform)
|
|
399
|
+
if (operation.write && readData) {
|
|
400
|
+
// Transform phase
|
|
401
|
+
const transformResult = await runTransform(
|
|
402
|
+
serviceDir, operation, enrichedContext, mergedView, readData,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
if (transformResult.status === 'error') {
|
|
406
|
+
overallStatus = 'error';
|
|
407
|
+
firstError = firstError || `${operation.name} transform failed`;
|
|
408
|
+
recordFailed(operation.name, 'transform failed');
|
|
409
|
+
if (stopOnError) break;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (transformResult.status === 'warned') {
|
|
413
|
+
Object.assign(accumulators.output, transformResult.output);
|
|
414
|
+
recordWarned(operation.name, transformResult.reason);
|
|
415
|
+
if (overallStatus !== 'error') overallStatus = 'warned';
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (transformResult.status === 'skip') {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const writeResult = await runWrite(
|
|
424
|
+
serviceDir, operation, enrichedContext, accumulators, transformResult.data,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
if (writeResult.status === 'error') {
|
|
428
|
+
overallStatus = 'error';
|
|
429
|
+
firstError = firstError || writeResult.error || `${operation.name} write failed`;
|
|
430
|
+
recordFailed(operation.name, writeResult.error || 'write failed');
|
|
431
|
+
if (stopOnError) break;
|
|
432
|
+
} else if (writeResult.status === 'warned') {
|
|
433
|
+
recordWarned(operation.name, writeResult.reason);
|
|
434
|
+
if (overallStatus !== 'error') overallStatus = 'warned';
|
|
435
|
+
}
|
|
436
|
+
} else if (operation.write && !operation.read) {
|
|
437
|
+
// Write-only operation (no read phase)
|
|
438
|
+
const writeResult = await runWrite(
|
|
439
|
+
serviceDir, operation, enrichedContext, accumulators, null,
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
if (writeResult.status === 'error') {
|
|
443
|
+
overallStatus = 'error';
|
|
444
|
+
firstError = firstError || writeResult.error || `${operation.name} write failed`;
|
|
445
|
+
recordFailed(operation.name, writeResult.error || 'write failed');
|
|
446
|
+
if (stopOnError) break;
|
|
447
|
+
} else if (writeResult.status === 'warned') {
|
|
448
|
+
recordWarned(operation.name, writeResult.reason);
|
|
449
|
+
if (overallStatus !== 'error') overallStatus = 'warned';
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
status: overallStatus,
|
|
457
|
+
error: firstError,
|
|
458
|
+
state: Object.keys(accumulators.state).length > 0 ? accumulators.state : null,
|
|
459
|
+
output: Object.keys(accumulators.output).length > 0 ? accumulators.output : null,
|
|
460
|
+
...(warned.length > 0 ? { warned } : {}),
|
|
461
|
+
...(failed.length > 0 ? { failed } : {}),
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
module.exports = { createServiceRunner, getOperationHandler, splitReturn };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Freshness check for derived files — a derived file needs regeneration
|
|
3
|
+
* when it's missing or older than its source. This is what makes local
|
|
4
|
+
* file-producing operations idempotent diff-syncs (assets: logo variants
|
|
5
|
+
* from their SVG sources; certificates: .p12 exports from their .cer):
|
|
6
|
+
* omega-manager regenerated blindly; the port regenerates only what's
|
|
7
|
+
* stale, so a converged brand is a zero-work no-op on every run.
|
|
8
|
+
*/
|
|
9
|
+
const { statSync } = require('node:fs');
|
|
10
|
+
const jetpack = require('fs-jetpack');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} sourcePath - The source file (must exist)
|
|
14
|
+
* @param {string} derivedPath - The derived file
|
|
15
|
+
* @returns {boolean} true when the derived file must be (re)generated
|
|
16
|
+
*/
|
|
17
|
+
function isStale(sourcePath, derivedPath) {
|
|
18
|
+
if (!jetpack.exists(derivedPath)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return statSync(derivedPath).mtimeMs < statSync(sourcePath).mtimeMs;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { isStale };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHICH targets a brand-root fan-out runs over, in WHICH order, with WHICH
|
|
3
|
+
* flags — the two pure helpers every fan-out shares.
|
|
4
|
+
*
|
|
5
|
+
* They started in commands/deploy.js because deploy was the first fan-out;
|
|
6
|
+
* `omega update` and the build/clean fan-out (lib/verb-fanout.js) then imported
|
|
7
|
+
* them from there, which put a lib file under a command. They live HERE, one
|
|
8
|
+
* home, and deploy.js re-exports them for its own callers.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Deploy order — backend's API goes live before the surfaces that call it, and
|
|
12
|
+
// every other fan-out reuses it as the dependency order. A custom target (#603)
|
|
13
|
+
// has no rank, so it lands after every framework one.
|
|
14
|
+
const DEPLOY_ORDER = ['backend', 'web', 'extension', 'desktop', 'mobile'];
|
|
15
|
+
|
|
16
|
+
// The flag that spells the target picker, on every brand-root verb (#780).
|
|
17
|
+
// ONE name, one constant: commands/test.js reads this very export, so
|
|
18
|
+
// `omega test --target=` and `omega deploy --target=` can never drift.
|
|
19
|
+
const PICKER_FLAG = 'target';
|
|
20
|
+
|
|
21
|
+
// The pickers `--target=` replaced (#780). Removed, never aliased (no legacy
|
|
22
|
+
// accommodations); `--only` was the collision that forced the rename, since
|
|
23
|
+
// `firebase deploy --only hosting` picks a SERVICE, not a brand target.
|
|
24
|
+
const RETIRED_PICKERS = ['only', 'except'];
|
|
25
|
+
|
|
26
|
+
// Brand-level flags consumed by the fan-out — everything else forwards to the
|
|
27
|
+
// targets. `_`/`$0` are yargs bookkeeping; continue-on-error is the
|
|
28
|
+
// manage-parity bail switch; --target is the target picker. A caller adds
|
|
29
|
+
// the flags IT consumes (verb-fanout's --dry-run) per call.
|
|
30
|
+
const CONSUMED_KEYS = new Set(['_', '$0', PICKER_FLAG, 'continue-on-error', 'continueOnError']);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Refuse a retired picker (#780), loudly: silently ignoring `--only` would run
|
|
34
|
+
* the whole brand when a subset was asked for, and aliasing it would keep the
|
|
35
|
+
* firebase collision alive. A `refusal` error prints its message alone (the
|
|
36
|
+
* cli-router keeps stacks for bugs), so the one sentence IS the output.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} [options] - the parsed CLI options
|
|
39
|
+
* @throws {Error} when --only or --except was passed
|
|
40
|
+
*/
|
|
41
|
+
function assertPickerFlags(options = {}) {
|
|
42
|
+
const used = RETIRED_PICKERS.filter((flag) => options[flag] !== undefined);
|
|
43
|
+
if (used.length === 0) return;
|
|
44
|
+
|
|
45
|
+
const error = new Error(`${used.map((flag) => `--${flag}`).join(' and ')} ${used.length > 1 ? 'are' : 'is'} retired: pick targets with --${PICKER_FLAG}=<target|targetDir>[,…] instead (firebase's own --only hosting runs from targets/backend).`);
|
|
46
|
+
error.refusal = true;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Refuse a picker token that names nothing (#780). A typo can never quietly
|
|
52
|
+
* narrow the set: on a publishing verb that would under-deploy, and on any
|
|
53
|
+
* verb a green exit would stand for work that never ran. The whole run stops,
|
|
54
|
+
* naming the token and what the brand actually has.
|
|
55
|
+
*
|
|
56
|
+
* The MATCHING is the caller's (a fan-out matches key-or-dir, `omega dev`
|
|
57
|
+
* matches the targets that have a dev leg); the refusal is one implementation.
|
|
58
|
+
*
|
|
59
|
+
* @param {string[]} unknown - the tokens that matched nothing
|
|
60
|
+
* @param {string[]} known - the names a token could have spelled
|
|
61
|
+
* @throws {Error} when any token matched nothing
|
|
62
|
+
*/
|
|
63
|
+
function assertKnownTargets(unknown, known) {
|
|
64
|
+
if (unknown.length === 0) return;
|
|
65
|
+
|
|
66
|
+
const error = new Error(`Unknown --${PICKER_FLAG} ${unknown.length > 1 ? 'tokens' : 'token'} ${unknown.map((token) => `"${token}"`).join(', ')}: this brand's targets are ${known.join(', ')}. Nothing ran.`);
|
|
67
|
+
error.refusal = true;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A comma list of target tokens, trimmed and emptied of blanks.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} [value] - the raw flag value
|
|
75
|
+
* @returns {string[]}
|
|
76
|
+
*/
|
|
77
|
+
function parseTargetTokens(value) {
|
|
78
|
+
return String(value || '').split(',').map((part) => part.trim()).filter(Boolean);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Does this target answer to this token? A target is named by its KEY ('web')
|
|
83
|
+
* or by its dir name ('website') — the ONE matcher every brand-root picker
|
|
84
|
+
* uses, so `--target=` cannot mean one thing on `deploy` and another on `test`.
|
|
85
|
+
*
|
|
86
|
+
* @param {{ target: string|null, name: string }} entry - a discovered target
|
|
87
|
+
* @param {string} token - the picker token
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
function targetMatches(entry, token) {
|
|
91
|
+
return entry.target === token || entry.name === token;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Pure target selection — which targets run for a given picker, in order.
|
|
96
|
+
* Picker tokens match a target's key ('web') or its dir name ('website'); no
|
|
97
|
+
* picker means every target.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} input
|
|
100
|
+
* @param {Array<{ name: string, target: string|null }>} input.targets - the target-mapped target dirs
|
|
101
|
+
* @param {string} [input.target] - the --target= comma list: the exact set to run
|
|
102
|
+
* @returns {{ selected: Array }}
|
|
103
|
+
* @throws {Error} when a token matches no target
|
|
104
|
+
*/
|
|
105
|
+
function selectTargets({ targets, target }) {
|
|
106
|
+
const tokens = parseTargetTokens(target);
|
|
107
|
+
assertKnownTargets(
|
|
108
|
+
tokens.filter((token) => !targets.some((entry) => targetMatches(entry, token))),
|
|
109
|
+
targets.map((entry) => entry.target || entry.name),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const selected = targets
|
|
113
|
+
.filter((entry) => tokens.length === 0 || tokens.some((token) => targetMatches(entry, token)))
|
|
114
|
+
.sort((a, b) => {
|
|
115
|
+
const rank = (entry) => {
|
|
116
|
+
const index = DEPLOY_ORDER.indexOf(entry.target);
|
|
117
|
+
return index === -1 ? DEPLOY_ORDER.length : index;
|
|
118
|
+
};
|
|
119
|
+
return rank(a) - rank(b);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return { selected };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Rebuild forwardable CLI flags from the yargs-parsed options: brand-level
|
|
127
|
+
* keys are consumed, camelCase twins of kebab-case flags are skipped (yargs
|
|
128
|
+
* mints both), booleans re-spell as --flag/--no-flag, values as --flag=value.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} options - the yargs-parsed options
|
|
131
|
+
* @param {string[]} [alsoConsumed] - extra keys THIS fan-out consumes (both spellings)
|
|
132
|
+
* @returns {string[]}
|
|
133
|
+
*/
|
|
134
|
+
function buildForwardedFlags(options, alsoConsumed = []) {
|
|
135
|
+
const flags = [];
|
|
136
|
+
const consumed = alsoConsumed.length ? new Set([...CONSUMED_KEYS, ...alsoConsumed]) : CONSUMED_KEYS;
|
|
137
|
+
|
|
138
|
+
for (const [key, value] of Object.entries(options)) {
|
|
139
|
+
if (consumed.has(key)) continue;
|
|
140
|
+
if (value === undefined || value === null) continue;
|
|
141
|
+
// Skip yargs' camelCase duplicate when the kebab-case original exists
|
|
142
|
+
if (/[A-Z]/.test(key) && key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`) in options) continue;
|
|
143
|
+
|
|
144
|
+
const values = Array.isArray(value) ? value : [value];
|
|
145
|
+
for (const entry of values) {
|
|
146
|
+
if (entry === true) flags.push(`--${key}`);
|
|
147
|
+
else if (entry === false) flags.push(`--no-${key}`);
|
|
148
|
+
else flags.push(`--${key}=${entry}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return flags;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { DEPLOY_ORDER, PICKER_FLAG, CONSUMED_KEYS, assertPickerFlags, assertKnownTargets, selectTargets, buildForwardedFlags, parseTargetTokens, targetMatches };
|