@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,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight — the REQUIRES registry check run before any service. Collects
|
|
3
|
+
* the enabled services' declared REQUIRED inputs (env var names + Google OAuth
|
|
4
|
+
* scopes, src/config.js REQUIRES; an entry marked `gates: false` is optional
|
|
5
|
+
* and belongs to the mid-run ask, not here), checks what IS knowable up front —
|
|
6
|
+
* process.env presence (names only, values never printed) and the token
|
|
7
|
+
* store's granted-scopes record — and prints ONE consolidated fix
|
|
8
|
+
* walkthrough (cp236's 403-diagnostics tone: what's missing, which service,
|
|
9
|
+
* why, the exact fix, then the rerun verb) instead of N mid-run skips.
|
|
10
|
+
*
|
|
11
|
+
* Verdicts per failing service (Ian's ruling — absorb, never crash):
|
|
12
|
+
* run - interactive and the run itself collects the fix (the cp114
|
|
13
|
+
* setup-contract paste flow / the Google consent flow) — the
|
|
14
|
+
* service proceeds and asks.
|
|
15
|
+
* skip - the fix needs the operator (non-interactive, or an env var no
|
|
16
|
+
* flow collects) — the service skips with the walkthrough printed
|
|
17
|
+
* and the cycle continues; the skip carries the machine-readable
|
|
18
|
+
* missingEnv list the run summary's 🔑 section aggregates.
|
|
19
|
+
* error - --strict: every preflight failure fails hard instead.
|
|
20
|
+
*
|
|
21
|
+
* The scope leg's boundary: the token store records what the last consent
|
|
22
|
+
* GRANTED — that is what's knowable before a call. The live grant is only
|
|
23
|
+
* proven at call time; the google-auth 403 diagnostics are the backstop.
|
|
24
|
+
*
|
|
25
|
+
* The file's OTHER gate is assertFamilyVersions (#794), the lockstep check:
|
|
26
|
+
* same place in the run (before any service, from `runManage` and `omega dev`
|
|
27
|
+
* alike) and the opposite verdict — a mixed @omega.js family is REFUSED, not
|
|
28
|
+
* absorbed, because every service below it would reconcile on top of two
|
|
29
|
+
* copies of the runtime.
|
|
30
|
+
*/
|
|
31
|
+
const fs = require('node:fs');
|
|
32
|
+
const path = require('node:path');
|
|
33
|
+
const chalk = require('chalk').default;
|
|
34
|
+
|
|
35
|
+
const { REQUIRES, TARGET_FRAMEWORKS } = require('../config.js');
|
|
36
|
+
const { canPrompt } = require('./run-gates.js');
|
|
37
|
+
const { googleTokenStorePath } = require('./google-auth.js');
|
|
38
|
+
|
|
39
|
+
// The family's ONE version (#794) — the manager's own, read at run time. A
|
|
40
|
+
// published brand installs the manager and its frameworks from the same
|
|
41
|
+
// release, so the manager's number IS what every target must carry.
|
|
42
|
+
const FAMILY_VERSION = require('../../package.json').version;
|
|
43
|
+
|
|
44
|
+
// The client rides inside every framework (web/backend/desktop/extension all
|
|
45
|
+
// depend on it), so a drifted client is the second way one brand ends up
|
|
46
|
+
// running two copies of the runtime — checked per target beside the framework.
|
|
47
|
+
const CLIENT_PACKAGE = '@omega.js/client';
|
|
48
|
+
|
|
49
|
+
/** Shorten a Google scope URL to its trailing name for display. */
|
|
50
|
+
function shortScope(scope) {
|
|
51
|
+
return scope.replace('https://www.googleapis.com/auth/', '');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read the ONE Google token store's granted-scopes record. Stores from
|
|
56
|
+
* before scope tracking (no `scopes` array) count as granting nothing —
|
|
57
|
+
* the same rule google-auth's getAccessToken applies.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} brandRoot - Brand monorepo root
|
|
60
|
+
* @returns {{ exists: boolean, scopes: string[], accountEmail: string|null, storePath: string }}
|
|
61
|
+
*/
|
|
62
|
+
function readTokenStore(brandRoot) {
|
|
63
|
+
const storePath = googleTokenStorePath(brandRoot);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const stored = JSON.parse(fs.readFileSync(storePath, 'utf8'));
|
|
67
|
+
return {
|
|
68
|
+
exists: true,
|
|
69
|
+
scopes: Array.isArray(stored.scopes) ? stored.scopes : [],
|
|
70
|
+
accountEmail: stored.account_email || null,
|
|
71
|
+
storePath,
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return { exists: false, scopes: [], accountEmail: null, storePath };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check one service's declaration against the environment + token store.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} serviceName - Service being checked
|
|
82
|
+
* @param {object} declaration - REQUIRES entry ({ why, when?, env, scopes })
|
|
83
|
+
* @param {object} brandConfig - Merged brand config (gates the when clauses)
|
|
84
|
+
* @param {object} tokenStore - readTokenStore() result
|
|
85
|
+
* @returns {object|null} Finding ({ service, why, missingEnv, missingScopes,
|
|
86
|
+
* noConsent }) or null when the service's requirements are met (or don't
|
|
87
|
+
* apply to this brand)
|
|
88
|
+
*/
|
|
89
|
+
function checkService(serviceName, declaration, brandConfig, tokenStore) {
|
|
90
|
+
if (declaration.when && !declaration.when(brandConfig)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// `gates: false` entries are OPTIONAL inputs (#608): the service runs
|
|
95
|
+
// without them, so their absence is not a preflight finding — the operation
|
|
96
|
+
// that needs one asks for it in place, through the shared setup contract.
|
|
97
|
+
const envEntries = (declaration.env || [])
|
|
98
|
+
.filter((entry) => entry.gates !== false)
|
|
99
|
+
.filter((entry) => !entry.when || entry.when(brandConfig));
|
|
100
|
+
const missingEnv = envEntries.filter((entry) => !process.env[entry.name]);
|
|
101
|
+
|
|
102
|
+
const requiredScopes = declaration.scopes || [];
|
|
103
|
+
let missingScopes = [];
|
|
104
|
+
let noConsent = false;
|
|
105
|
+
|
|
106
|
+
if (requiredScopes.length > 0) {
|
|
107
|
+
if (!tokenStore.exists) {
|
|
108
|
+
noConsent = true;
|
|
109
|
+
} else {
|
|
110
|
+
missingScopes = requiredScopes.filter((scope) => !tokenStore.scopes.includes(scope));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (missingEnv.length === 0 && missingScopes.length === 0 && !noConsent) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
service: serviceName,
|
|
120
|
+
why: declaration.why || null,
|
|
121
|
+
missingEnv,
|
|
122
|
+
missingScopes,
|
|
123
|
+
noConsent,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build the machine-readable skip/error reason for a finding — always names
|
|
129
|
+
* the exact vars/scopes (never values) so run records and the 🔑 aggregate
|
|
130
|
+
* say precisely what to add where.
|
|
131
|
+
*/
|
|
132
|
+
function buildReason(finding) {
|
|
133
|
+
const parts = [];
|
|
134
|
+
|
|
135
|
+
if (finding.missingEnv.length > 0) {
|
|
136
|
+
parts.push(`missing ${finding.missingEnv.map((entry) => entry.name).join(', ')} — add to the brand .env, or rerun interactively to paste`);
|
|
137
|
+
}
|
|
138
|
+
parts.push(...consentParts(finding));
|
|
139
|
+
|
|
140
|
+
return `preflight: ${parts.join('; ')}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The consent/scope half of a finding's wording — ONE home for both users. */
|
|
144
|
+
function consentParts(finding) {
|
|
145
|
+
const parts = [];
|
|
146
|
+
|
|
147
|
+
if (finding.noConsent) {
|
|
148
|
+
parts.push('Google consent not granted yet — rerun interactively once to consent');
|
|
149
|
+
}
|
|
150
|
+
if (finding.missingScopes.length > 0) {
|
|
151
|
+
parts.push(`Google token missing scopes ${finding.missingScopes.map(shortScope).join(', ')} — rerun interactively for the one re-consent`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return parts;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The gate's human-pending summary (#228) — what the run summary's ⚑ section
|
|
159
|
+
* prints for this skip, so the item is NAMED with a rerun hint instead of
|
|
160
|
+
* counting as an anonymous "skipped". Consent/scope gaps only: a missing
|
|
161
|
+
* secret already rides the 🔑 section, and double-listing is noise.
|
|
162
|
+
*
|
|
163
|
+
* @param {object} finding - A checkService finding.
|
|
164
|
+
* @returns {string|null} The pending line, or null for env-only gaps.
|
|
165
|
+
*/
|
|
166
|
+
function buildPending(finding) {
|
|
167
|
+
const parts = consentParts(finding);
|
|
168
|
+
return parts.length > 0 ? parts.join('; ') : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Print the consolidated walkthrough (cp236 tone) — one block per failing
|
|
173
|
+
* service: what's missing, why the service needs it, the exact fix, then
|
|
174
|
+
* the rerun verb. Env VALUES never appear — names, labels, and mint URLs only.
|
|
175
|
+
*/
|
|
176
|
+
function printWalkthrough(findings, gates, tokenStore, strict) {
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log(` ${chalk.yellow('⚑ Preflight')} — ${findings.length} service${findings.length === 1 ? ' is' : 's are'} missing requirements${strict ? chalk.red(' (--strict: failing hard)') : ''}:`);
|
|
179
|
+
|
|
180
|
+
for (const finding of findings) {
|
|
181
|
+
const gate = gates[finding.service];
|
|
182
|
+
const missingBits = [
|
|
183
|
+
...finding.missingEnv.map((entry) => entry.name),
|
|
184
|
+
...(finding.noConsent ? ['Google consent'] : []),
|
|
185
|
+
...finding.missingScopes.map((scope) => `scope:${shortScope(scope)}`),
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
console.log('');
|
|
189
|
+
console.log(` ${chalk.yellow('✗')} ${chalk.bold(finding.service)} — missing ${chalk.bold(missingBits.join(', '))}`);
|
|
190
|
+
if (finding.why) {
|
|
191
|
+
console.log(` ${chalk.dim('needed:')} ${finding.why}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const entry of finding.missingEnv) {
|
|
195
|
+
console.log(` ${chalk.dim('fix:')} add ${chalk.bold(`${entry.name}=<value>`)} to the brand .env${entry.label ? chalk.dim(` — ${entry.label}`) : ''}`);
|
|
196
|
+
if (entry.url) {
|
|
197
|
+
console.log(` ${chalk.dim(`→ mint it at ${entry.url}`)}`);
|
|
198
|
+
}
|
|
199
|
+
if (entry.hint) {
|
|
200
|
+
console.log(` ${chalk.dim(entry.hint)}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (finding.noConsent) {
|
|
205
|
+
console.log(` ${chalk.dim('fix:')} run interactively once — the Google consent flow opens, and every later run works from the cached grant`);
|
|
206
|
+
} else if (finding.missingScopes.length > 0) {
|
|
207
|
+
console.log(` ${chalk.dim('acting identity:')} ${tokenStore.accountEmail || 'unknown'} ${chalk.dim(`(token store: ${tokenStore.storePath})`)}`);
|
|
208
|
+
console.log(` ${chalk.dim('fix:')} rerun interactively — one re-consent upgrades the cached token with the new scopes`);
|
|
209
|
+
console.log(` ${chalk.dim('→ or delete the token store to consent as a different account')}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (gate.action === 'run') {
|
|
213
|
+
console.log(` ${chalk.dim('→ this run is interactive — it will ask, then continue')}`);
|
|
214
|
+
} else {
|
|
215
|
+
console.log(` ${chalk.dim('then:')} npm run manage -- --service=${finding.service} ${chalk.dim('(from the brand root)')}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Run the preflight over the services about to run.
|
|
222
|
+
*
|
|
223
|
+
* @param {object} params
|
|
224
|
+
* @param {string[]} params.services - Services this manage run will walk
|
|
225
|
+
* @param {object} params.brandConfig - Merged brand config
|
|
226
|
+
* @param {string} params.brandRoot - Brand monorepo root
|
|
227
|
+
* @param {object} params.options - Run options ({ strict?, dryRun?, … })
|
|
228
|
+
* @param {object} [deps] - Test seams: { requires } replaces the REQUIRES
|
|
229
|
+
* registry, { canPrompt } the interactivity gate
|
|
230
|
+
* @returns {{ findings: object[], gates: Object<string, { action: 'run'|'skip'|'error', reason: string, missingEnv: string[], needsInteractive?: string }> }}
|
|
231
|
+
* gates only contains entries for services with findings — everything
|
|
232
|
+
* else runs untouched
|
|
233
|
+
*/
|
|
234
|
+
function runPreflight({ services, brandConfig, brandRoot, options = {} }, deps = {}) {
|
|
235
|
+
const requires = deps.requires || REQUIRES;
|
|
236
|
+
const interactive = (deps.canPrompt || canPrompt)(options);
|
|
237
|
+
const strict = Boolean(options.strict);
|
|
238
|
+
const tokenStore = readTokenStore(brandRoot);
|
|
239
|
+
|
|
240
|
+
const findings = [];
|
|
241
|
+
for (const serviceName of services) {
|
|
242
|
+
const declaration = requires[serviceName];
|
|
243
|
+
if (!declaration) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const finding = checkService(serviceName, declaration, brandConfig, tokenStore);
|
|
248
|
+
if (finding) {
|
|
249
|
+
findings.push(finding);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const gates = {};
|
|
254
|
+
for (const finding of findings) {
|
|
255
|
+
// Scope gaps always self-heal on a TTY (the consent flow); env gaps only
|
|
256
|
+
// when every missing entry rides the setup-contract paste flow
|
|
257
|
+
const selfHealing = finding.missingEnv.every((entry) => entry.prompted === true);
|
|
258
|
+
const action = strict ? 'error' : (interactive && selfHealing ? 'run' : 'skip');
|
|
259
|
+
|
|
260
|
+
const pending = buildPending(finding);
|
|
261
|
+
|
|
262
|
+
gates[finding.service] = {
|
|
263
|
+
action,
|
|
264
|
+
reason: buildReason(finding),
|
|
265
|
+
missingEnv: finding.missingEnv.map((entry) => entry.name),
|
|
266
|
+
...(pending ? { needsInteractive: pending } : {}),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (findings.length > 0) {
|
|
271
|
+
printWalkthrough(findings, gates, tokenStore, strict);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { findings, gates };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ─── The lockstep boot check (#794) ─────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The nearest installed copy of a package, climbing from a target dir toward
|
|
281
|
+
* the brand root — npm hoists a workspace tree's deps to the root, so the
|
|
282
|
+
* target's own node_modules is the first place to look and never the only
|
|
283
|
+
* one (the same "nearest, climbing" resolution `omega update` reports from).
|
|
284
|
+
*
|
|
285
|
+
* @param {string} fromDir - Where to start looking (a target dir).
|
|
286
|
+
* @param {string} brandRoot - Where to stop climbing.
|
|
287
|
+
* @param {string} packageName - Full package name (`@omega.js/web`).
|
|
288
|
+
* @returns {string|null} The installed package dir, or null when nothing is installed.
|
|
289
|
+
*/
|
|
290
|
+
function findInstalled(fromDir, brandRoot, packageName) {
|
|
291
|
+
let current = path.resolve(fromDir);
|
|
292
|
+
const stop = path.resolve(brandRoot);
|
|
293
|
+
|
|
294
|
+
for (;;) {
|
|
295
|
+
const candidate = path.join(current, 'node_modules', packageName);
|
|
296
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate;
|
|
297
|
+
|
|
298
|
+
const parent = path.dirname(current);
|
|
299
|
+
if (current === stop || parent === current) return null;
|
|
300
|
+
current = parent;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Is this install a LINK out of node_modules — the local era's `file:` spec,
|
|
306
|
+
* or an npm workspace link? Its version is the monorepo's by construction, so
|
|
307
|
+
* comparing it to a published number says nothing.
|
|
308
|
+
*/
|
|
309
|
+
function isLinkedInstall(installedDir) {
|
|
310
|
+
try {
|
|
311
|
+
return fs.lstatSync(installedDir).isSymbolicLink();
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The installed version, or the PROBLEM that stopped us reading it. A manifest
|
|
319
|
+
* that exists but cannot be parsed (or carries no version) is never a pass:
|
|
320
|
+
* the whole point of the gate is knowing what is installed, and "unknown"
|
|
321
|
+
* answers that question with silence.
|
|
322
|
+
*
|
|
323
|
+
* @param {string} installedDir - An installed package dir.
|
|
324
|
+
* @returns {{ version: string|null, problem: string|null }}
|
|
325
|
+
*/
|
|
326
|
+
function readInstalledVersion(installedDir) {
|
|
327
|
+
const manifest = path.join(installedDir, 'package.json');
|
|
328
|
+
let raw;
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
raw = fs.readFileSync(manifest, 'utf8');
|
|
332
|
+
} catch {
|
|
333
|
+
return { version: null, problem: 'cannot be read' };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let parsed;
|
|
337
|
+
try {
|
|
338
|
+
parsed = JSON.parse(raw);
|
|
339
|
+
} catch {
|
|
340
|
+
return { version: null, problem: 'is not valid JSON' };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (typeof parsed.version !== 'string' || parsed.version === '') {
|
|
344
|
+
return { version: null, problem: 'declares no version' };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { version: parsed.version, problem: null };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** An Error the CLI prints as its message alone — a refusal is not a bug (#706). */
|
|
351
|
+
function refusal(message) {
|
|
352
|
+
const error = new Error(message);
|
|
353
|
+
error.refusal = true;
|
|
354
|
+
return error;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** The framework spec a target's own manifest declares, or null. */
|
|
358
|
+
function declaredSpec(targetPath, packageName) {
|
|
359
|
+
try {
|
|
360
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(targetPath, 'package.json'), 'utf8'));
|
|
361
|
+
return pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName] || null;
|
|
362
|
+
} catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The lockstep gate (#794) — the @omega.js family ships ONE version, so every
|
|
369
|
+
* target's installed framework (and the `@omega.js/client` under it) must be
|
|
370
|
+
* the manager's own version. Run before any service, by `runManage` and by
|
|
371
|
+
* `omega dev` alike: a brand running a backend from one release beside a
|
|
372
|
+
* client from another validates its config with two validators and serves two
|
|
373
|
+
* copies of the runtime, and nothing downstream can tell.
|
|
374
|
+
*
|
|
375
|
+
* What is NOT a mismatch: a target linked with a `file:` spec (the local era —
|
|
376
|
+
* its version is the monorepo's by construction), and a target with nothing
|
|
377
|
+
* installed yet (the install itself is the fix, so it skips with a line).
|
|
378
|
+
*
|
|
379
|
+
* @param {object} params
|
|
380
|
+
* @param {string} params.brandRoot - Brand monorepo root.
|
|
381
|
+
* @param {Array<{ name, dir, path, target }>} params.targets - Discovered target entries.
|
|
382
|
+
* @param {string} [params.version] - The family version (defaults to the manager's own).
|
|
383
|
+
* @returns {{ checked: object[], mismatched: object[], exempt: object[], skipped: object[], unreadable: object[] }}
|
|
384
|
+
* @throws {Error} ONE refusal (`error.refusal`, so the CLI prints the message
|
|
385
|
+
* alone) naming every mismatched target and the fix — or, first, every
|
|
386
|
+
* installed manifest that cannot be read.
|
|
387
|
+
*/
|
|
388
|
+
function assertFamilyVersions({ brandRoot, targets = [], version = FAMILY_VERSION }) {
|
|
389
|
+
const checked = [];
|
|
390
|
+
const mismatched = [];
|
|
391
|
+
const exempt = [];
|
|
392
|
+
const skipped = [];
|
|
393
|
+
const unreadable = [];
|
|
394
|
+
|
|
395
|
+
for (const entry of targets) {
|
|
396
|
+
const framework = TARGET_FRAMEWORKS[entry.target];
|
|
397
|
+
// A custom target (#603) maps to no framework — there is nothing to compare
|
|
398
|
+
if (!framework) continue;
|
|
399
|
+
|
|
400
|
+
const spec = declaredSpec(entry.path, framework);
|
|
401
|
+
if (spec && /^(file|link):/.test(spec)) {
|
|
402
|
+
exempt.push({ dir: entry.name, framework, reason: spec });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const installedDir = findInstalled(entry.path, brandRoot, framework);
|
|
407
|
+
if (!installedDir) {
|
|
408
|
+
skipped.push({ dir: entry.name, framework });
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (isLinkedInstall(installedDir)) {
|
|
412
|
+
exempt.push({ dir: entry.name, framework, reason: 'linked install' });
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// The client is checked wherever npm placed it: nested under the
|
|
417
|
+
// framework when the ranges stopped overlapping (the second-copy case
|
|
418
|
+
// this check exists for), otherwise the hoisted copy the climb finds.
|
|
419
|
+
const nestedClient = path.join(installedDir, 'node_modules', CLIENT_PACKAGE);
|
|
420
|
+
const clientDir = fs.existsSync(path.join(nestedClient, 'package.json'))
|
|
421
|
+
? nestedClient
|
|
422
|
+
: findInstalled(entry.path, brandRoot, CLIENT_PACKAGE);
|
|
423
|
+
|
|
424
|
+
const reads = [{ name: framework, dir: installedDir, ...readInstalledVersion(installedDir) }];
|
|
425
|
+
if (clientDir && !isLinkedInstall(clientDir)) {
|
|
426
|
+
reads.push({ name: CLIENT_PACKAGE, dir: clientDir, ...readInstalledVersion(clientDir) });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const broken = reads.filter((read) => read.problem);
|
|
430
|
+
if (broken.length > 0) {
|
|
431
|
+
unreadable.push(...broken.map((read) => ({ dir: entry.name, name: read.name, manifest: path.join(read.dir, 'package.json'), problem: read.problem })));
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const packages = reads.map((read) => ({ name: read.name, version: read.version }));
|
|
436
|
+
checked.push({ dir: entry.name, packages });
|
|
437
|
+
|
|
438
|
+
const drifted = packages.filter((pkg) => pkg.version !== version);
|
|
439
|
+
if (drifted.length > 0) {
|
|
440
|
+
mismatched.push({ dir: entry.name, packages: drifted });
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
for (const entry of skipped) {
|
|
445
|
+
console.log(` ${chalk.dim(`⊘ ${entry.dir}: ${entry.framework} is not installed yet — skipped (npm install, or \`omega update --apply\`, is the fix)`)}`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// An install we cannot READ is checked first: it answers "which version is
|
|
449
|
+
// this" with silence, and a gate that shrugs at silence is not a gate.
|
|
450
|
+
if (unreadable.length > 0) {
|
|
451
|
+
const lines = unreadable.map((entry) => ` ${entry.dir}: ${entry.manifest} ${entry.problem}`);
|
|
452
|
+
throw refusal(
|
|
453
|
+
`an installed @omega.js package cannot be read (#794):\n${lines.join('\n')}\n`
|
|
454
|
+
+ ' fix: reinstall the brand — `npm install` at the brand root',
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (mismatched.length > 0) {
|
|
459
|
+
const lines = mismatched.map((entry) => ` ${entry.dir}: ${entry.packages.map((pkg) => `${pkg.name} ${pkg.version}`).join(', ')}`);
|
|
460
|
+
throw refusal(
|
|
461
|
+
`the @omega.js family ships ONE version — this brand is mixed (#794):\n${lines.join('\n')}\n`
|
|
462
|
+
+ ` this manager is ${version}\n`
|
|
463
|
+
+ ' fix: run `omega update --apply` at the brand root — it moves every target together',
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return { checked, mismatched, exempt, skipped, unreadable };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
module.exports = { runPreflight, checkService, readTokenStore, assertFamilyVersions };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create-on-missing for the ITW product services — forms (Slapform contact
|
|
3
|
+
* form), chat (Chatsy agent), email (Replyify agent). When a brand configures no
|
|
4
|
+
* asset id, the OPERATOR service account mints a brand-OWNED asset instead
|
|
5
|
+
* of the brand borrowing another brand's (2b, Ian 2026-07-13: "this shit
|
|
6
|
+
* was supposed to create new accounts for the brand"):
|
|
7
|
+
*
|
|
8
|
+
* 1. Product USER — the brand's own account on the product, minted via
|
|
9
|
+
* the product project's Identity Toolkit (email = brand contact
|
|
10
|
+
* email; password through the account service's owner channels:
|
|
11
|
+
* env pin → owner hook → derived seed). The users/{uid} doc writes
|
|
12
|
+
* the CANONICAL @omega.js/account shape (the BEM golden master the
|
|
13
|
+
* products run) with real generated credentials — including
|
|
14
|
+
* api.privateKey, the user API key the products accept.
|
|
15
|
+
* 2. The ASSET doc — shape-templated from an existing doc (the config's
|
|
16
|
+
* template id — typically the company's own richest example), with
|
|
17
|
+
* `owner` pointed at the new user. The service's normal ensures then
|
|
18
|
+
* converge every managed field (name, settings, knowledge, plan) in
|
|
19
|
+
* the SAME run, so the mint stays minimal and the convergence logic
|
|
20
|
+
* stays in one place.
|
|
21
|
+
*
|
|
22
|
+
* Auth tiers (Ian's call): operator SA = full create/manage (this lib) ·
|
|
23
|
+
* user API key = recognized, manage via the product APIs lands when those
|
|
24
|
+
* routes are verified · no credentials = the interactive dashboard
|
|
25
|
+
* paste-back every service already ships.
|
|
26
|
+
*
|
|
27
|
+
* Idempotent by construction: the minted id writes back into omega.json5
|
|
28
|
+
* (comment-preserving), so a rerun takes the normal converge path; an
|
|
29
|
+
* existing product user is reused by email lookup.
|
|
30
|
+
*/
|
|
31
|
+
const { randomBytes, randomUUID } = require('node:crypto');
|
|
32
|
+
const chalk = require('chalk').default;
|
|
33
|
+
|
|
34
|
+
const { resolveAccount } = require('../vendor/account/index.js');
|
|
35
|
+
|
|
36
|
+
const BASE62 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Random base62 string (rejection-free modulo is fine here — ids/keys,
|
|
40
|
+
* not crypto material with uniformity proofs).
|
|
41
|
+
* @param {number} size
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function randomBase62(size) {
|
|
45
|
+
const bytes = randomBytes(size);
|
|
46
|
+
let out = '';
|
|
47
|
+
for (let i = 0; i < size; i++) {
|
|
48
|
+
out += BASE62[bytes[i] % BASE62.length];
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** New asset doc id — matches the products' visible 14-char convention. */
|
|
54
|
+
function generateAssetId() {
|
|
55
|
+
return randomBase62(14);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Ensure the brand has its own user on the product: reuse by email, else
|
|
60
|
+
* create the auth user + the canonical users/{uid} doc.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} spec
|
|
63
|
+
* @param {object} spec.db - FirestoreREST on the product project (operator SA)
|
|
64
|
+
* @param {object} spec.authAdmin - auth-admin client on the same SA
|
|
65
|
+
* @param {string} spec.email - the brand's product-account email
|
|
66
|
+
* @param {function} spec.resolvePassword - createPasswordResolver instance
|
|
67
|
+
* @param {function} spec.log - line logger
|
|
68
|
+
* @returns {Promise<{ uid: string, created: boolean }>}
|
|
69
|
+
*/
|
|
70
|
+
async function ensureProductUser(spec) {
|
|
71
|
+
const { db, authAdmin, email, resolvePassword, log } = spec;
|
|
72
|
+
|
|
73
|
+
const existing = await authAdmin.getUserByEmail(email);
|
|
74
|
+
if (existing) {
|
|
75
|
+
log(`${chalk.green('✓')} Product user exists: ${chalk.cyan(email)} ${chalk.dim(`(${existing.uid})`)}`);
|
|
76
|
+
return { uid: existing.uid, created: false };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { password, source } = await resolvePassword(email);
|
|
80
|
+
if (!password) {
|
|
81
|
+
throw new Error(`no password available for ${email} (channel: ${source})`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const created = await authAdmin.createUser({ email, password });
|
|
85
|
+
|
|
86
|
+
// The canonical account shape (the BEM golden master) with REAL generated
|
|
87
|
+
// credentials — a direct write, since the product's own auth-onCreate
|
|
88
|
+
// trigger only fires for its backend, not for admin REST creates.
|
|
89
|
+
const userDoc = resolveAccount({ auth: { uid: created.uid, email } }, {
|
|
90
|
+
generators: {
|
|
91
|
+
uuid: () => randomUUID(),
|
|
92
|
+
randomId: () => randomBase62(8),
|
|
93
|
+
apiKey: () => randomBase62(43),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
await db.setDoc(`users/${created.uid}`, userDoc);
|
|
97
|
+
|
|
98
|
+
log(`${chalk.green('✓')} Product user created: ${chalk.cyan(email)} ${chalk.dim(`(${created.uid}, password via ${source})`)}`);
|
|
99
|
+
return { uid: created.uid, created: true };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Mint a brand-owned product asset from a template doc.
|
|
104
|
+
*
|
|
105
|
+
* @param {object} spec
|
|
106
|
+
* @param {object} spec.db - FirestoreREST on the product project (operator SA)
|
|
107
|
+
* @param {object} spec.authAdmin - auth-admin client on the same SA
|
|
108
|
+
* @param {string} spec.collection - 'forms' | 'chats' | 'agents' (the product's collection)
|
|
109
|
+
* @param {string} spec.templateId - existing doc to shape-template from
|
|
110
|
+
* @param {object} spec.overrides - fields the mint sets outright (beyond owner)
|
|
111
|
+
* @param {string} spec.email - brand product-account email
|
|
112
|
+
* @param {function} spec.resolvePassword - createPasswordResolver instance
|
|
113
|
+
* @param {boolean} spec.dryRun
|
|
114
|
+
* @param {function} spec.log - line logger (indented by the caller)
|
|
115
|
+
* @returns {Promise<{ id: string, ownerUid: string }|null>} null on dry-run
|
|
116
|
+
*/
|
|
117
|
+
async function createProductAsset(spec) {
|
|
118
|
+
const { db, authAdmin, collection, templateId, overrides = {}, email, resolvePassword, dryRun, log } = spec;
|
|
119
|
+
|
|
120
|
+
const template = await db.getDoc(`${collection}/${templateId}`);
|
|
121
|
+
if (!template) {
|
|
122
|
+
throw new Error(`template doc ${collection}/${templateId} not found — check the template id in omega.json5`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (dryRun) {
|
|
126
|
+
log(`${chalk.yellow('[DRY RUN]')} Would create ${chalk.cyan(collection)} doc from template ${chalk.dim(templateId)} + product user ${chalk.cyan(email)}`);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { uid } = await ensureProductUser({ db, authAdmin, email, resolvePassword, log });
|
|
131
|
+
|
|
132
|
+
const id = generateAssetId();
|
|
133
|
+
// BEM docs mirror their doc id in an `id` field — a copied donor id would
|
|
134
|
+
// self-reference the WRONG doc. Donor usage counters copy as-is (their
|
|
135
|
+
// shape is product-specific; the product resets them on its own cycle).
|
|
136
|
+
const doc = { ...template, ...overrides, owner: uid, ...(template.id !== undefined ? { id } : {}) };
|
|
137
|
+
await db.setDoc(`${collection}/${id}`, doc);
|
|
138
|
+
|
|
139
|
+
log(`${chalk.green('✓')} Created ${chalk.cyan(`${collection}/${id}`)} from template ${chalk.dim(templateId)} ${chalk.dim(`(fields: ${Object.keys(doc).sort().join(', ')})`)}`);
|
|
140
|
+
|
|
141
|
+
return { id, ownerUid: uid };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { createProductAsset, ensureProductUser, generateAssetId, randomBase62 };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runCommand — spawn a child in a directory, streaming its output, and
|
|
3
|
+
* resolve a { success, error? } result instead of rejecting. Shared by the
|
|
4
|
+
* update service (npm install / npm run build) and the brand-root test
|
|
5
|
+
* fan-out (per-target `omega test`).
|
|
6
|
+
*
|
|
7
|
+
* Every command runs under the TARGET'S OWN Node (friction #15): the target
|
|
8
|
+
* root's .nvmrc picks the nvm install whose bin is prepended to PATH — one
|
|
9
|
+
* brand can span Node majors (web 24, backend 22) without a single nvm
|
|
10
|
+
* switch. A pinned-but-missing major fails fast with
|
|
11
|
+
* the `nvm install` to run. If a command rewrites the .nvmrc and THEN
|
|
12
|
+
* fails (EM setup bumps the pin when Electron's bundled Node moved), it is
|
|
13
|
+
* re-resolved and retried once — one run self-heals instead of two.
|
|
14
|
+
*/
|
|
15
|
+
const { spawn } = require('node:child_process');
|
|
16
|
+
const chalk = require('chalk').default;
|
|
17
|
+
const { resolveTargetNode, nodeEnvFor } = require('./node-version.js');
|
|
18
|
+
|
|
19
|
+
function spawnOnce(command, args, cwd, env) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const child = spawn(command, args, { cwd, shell: false, stdio: 'inherit', env });
|
|
22
|
+
|
|
23
|
+
child.on('close', (code) => {
|
|
24
|
+
resolve(code === 0
|
|
25
|
+
? { success: true, code }
|
|
26
|
+
: { success: false, code, error: `exit code ${code}` });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
child.on('error', (error) => {
|
|
30
|
+
resolve({ success: false, error: error.message });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run a command in a directory under that directory's own Node, streaming
|
|
37
|
+
* output. Resolves { success, error?, code? }.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} command - Executable (no shell)
|
|
40
|
+
* @param {string[]} args - Arguments
|
|
41
|
+
* @param {string} cwd - Working directory (its .nvmrc picks the Node)
|
|
42
|
+
* @param {object} [extraEnv] - Env this run carries on top of the caller's own
|
|
43
|
+
* (the test fan-out's OMEGA_TEST_FANOUT signal, #814)
|
|
44
|
+
* @returns {Promise<{ success: boolean, error?: string, code?: number }>}
|
|
45
|
+
*/
|
|
46
|
+
async function runCommand(command, args, cwd, extraEnv) {
|
|
47
|
+
let resolved = resolveTargetNode(cwd);
|
|
48
|
+
if (resolved?.error) {
|
|
49
|
+
console.log(` ${chalk.red('✗')} ${resolved.error}`);
|
|
50
|
+
return { success: false, error: resolved.error };
|
|
51
|
+
}
|
|
52
|
+
if (resolved?.binDir) {
|
|
53
|
+
console.log(` ${chalk.dim(`↪ Node ${resolved.version} (.nvmrc ${resolved.spec})`)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const specBefore = resolved?.spec || null;
|
|
57
|
+
const result = await spawnOnce(command, args, cwd, { ...process.env, ...nodeEnvFor(resolved), ...extraEnv });
|
|
58
|
+
if (result.success) {
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Self-heal: the command may have rewritten the .nvmrc mid-run (EM setup
|
|
63
|
+
// writes the pin BEFORE its own Node check). Re-resolve; a changed pin
|
|
64
|
+
// gets one retry under the newly pinned Node.
|
|
65
|
+
resolved = resolveTargetNode(cwd);
|
|
66
|
+
if (!resolved || resolved.error || (resolved.spec || null) === specBefore) {
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(` ${chalk.dim(`↪ .nvmrc changed to ${resolved.spec} — retrying under Node ${resolved.version || `v${resolved.major}`}`)}`);
|
|
71
|
+
return spawnOnce(command, args, cwd, { ...process.env, ...nodeEnvFor(resolved), ...extraEnv });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { runCommand };
|