@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,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health-check implementations for the testing service — omega-manager's
|
|
3
|
+
* target-checks helpers rebuilt for brand monorepos. Local checks read the
|
|
4
|
+
* repo; live checks fetch the deployed site/API and shell out to npm/gh.
|
|
5
|
+
*
|
|
6
|
+
* Every check records into a shared recorder whose results shape matches
|
|
7
|
+
* RunSummary's drill-down: passed = [name], warned = [{ name, warning }],
|
|
8
|
+
* failed = [{ name, error }]. Dim notes and dry-run "would" lines are
|
|
9
|
+
* display-only — they never enter the results.
|
|
10
|
+
*
|
|
11
|
+
* Injection seams (all via context.options — they ride runManage options so
|
|
12
|
+
* the full-loop tests stay network-free): options.fetch, options.exec,
|
|
13
|
+
* options.retryDelayMs.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const { execSync } = require('node:child_process');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const chalk = require('chalk').default;
|
|
20
|
+
const jetpack = require('fs-jetpack');
|
|
21
|
+
|
|
22
|
+
const { TARGET_FRAMEWORKS } = require('../../../config.js');
|
|
23
|
+
const { brandRepo, brandRepoName, instanceIdFromDirName, resolveInstanceUrl } = require('../../../vendor/config/index.js');
|
|
24
|
+
const { recordDeploy, readDeployRecord } = require('../../../vendor/devkit/deploy-record.js');
|
|
25
|
+
|
|
26
|
+
const MAX_RETRIES = 3;
|
|
27
|
+
const MAX_FILES_SHOWN = 10;
|
|
28
|
+
|
|
29
|
+
// Mirrors @omega.js/backend: getApiUrl() serves from the `api.` subdomain and
|
|
30
|
+
// mounts its routes under /omega (packages/backend route prefix)
|
|
31
|
+
const API_SUBDOMAIN = 'api';
|
|
32
|
+
const API_HEALTH_PATH = '/omega/health';
|
|
33
|
+
|
|
34
|
+
const FETCH_HEADERS = {
|
|
35
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
36
|
+
'Pragma': 'no-cache',
|
|
37
|
+
'User-Agent': 'OmegaManager/1.0 HealthCheck',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create the shared check recorder. Prints each check as it lands and
|
|
42
|
+
* accumulates the results arrays the handler returns.
|
|
43
|
+
*
|
|
44
|
+
* @returns {Object} - { pass, warn, fail, note, would, results, counts }
|
|
45
|
+
*/
|
|
46
|
+
function createRecorder() {
|
|
47
|
+
const passed = [];
|
|
48
|
+
const warned = [];
|
|
49
|
+
const failed = [];
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
pass(name, detail) {
|
|
53
|
+
passed.push(name);
|
|
54
|
+
console.log(` ${chalk.green('✓')} ${name}${detail ? chalk.dim(`: ${detail}`) : ''}`);
|
|
55
|
+
},
|
|
56
|
+
warn(name, warning) {
|
|
57
|
+
warned.push({ name, warning });
|
|
58
|
+
console.log(` ${chalk.yellow('⚠')} ${name}${warning ? `: ${chalk.yellow(warning)}` : ''}`);
|
|
59
|
+
},
|
|
60
|
+
fail(name, error) {
|
|
61
|
+
failed.push({ name, error });
|
|
62
|
+
console.log(` ${chalk.red('✗')} ${name}${error ? `: ${chalk.red(error)}` : ''}`);
|
|
63
|
+
},
|
|
64
|
+
// Display-only informational line (check not applicable / not verifiable)
|
|
65
|
+
note(name, detail) {
|
|
66
|
+
console.log(` ${chalk.dim(`– ${name}${detail ? `: ${detail}` : ''}`)}`);
|
|
67
|
+
},
|
|
68
|
+
// Display-only dry-run line for a skipped remote action
|
|
69
|
+
would(action) {
|
|
70
|
+
console.log(` ${chalk.yellow('⊘')} would ${action} ${chalk.dim('[DRY RUN]')}`);
|
|
71
|
+
},
|
|
72
|
+
results() {
|
|
73
|
+
return { passed, warned, failed };
|
|
74
|
+
},
|
|
75
|
+
counts() {
|
|
76
|
+
return { passed: passed.length, warned: warned.length, failed: failed.length };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Default exec — thin execSync wrapper so tests can inject a recording fake
|
|
83
|
+
* with the same (command, options) → stdout surface.
|
|
84
|
+
*/
|
|
85
|
+
function defaultExec(command, options = {}) {
|
|
86
|
+
return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], ...options });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Fetch with retry — retries network errors AND non-2xx/3xx statuses with a
|
|
91
|
+
* linear backoff (delayMs, 2×delayMs, …), returning the last response either
|
|
92
|
+
* way. Success (2xx/3xx) returns immediately.
|
|
93
|
+
*
|
|
94
|
+
* @param {Object} ctx - Check context ({ fetchImpl, retryDelayMs })
|
|
95
|
+
* @param {string} url - URL to fetch
|
|
96
|
+
* @returns {Object} - { response, duration } or { error } after all retries
|
|
97
|
+
*/
|
|
98
|
+
async function fetchWithRetry(ctx, url) {
|
|
99
|
+
let last = { error: 'unreachable' };
|
|
100
|
+
|
|
101
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
102
|
+
try {
|
|
103
|
+
const start = Date.now();
|
|
104
|
+
const response = await ctx.fetchImpl(url, { headers: FETCH_HEADERS, redirect: 'follow' });
|
|
105
|
+
last = { response, duration: Date.now() - start };
|
|
106
|
+
|
|
107
|
+
if (response.status >= 200 && response.status < 400) {
|
|
108
|
+
return last;
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// Node's fetch wraps network errors ("fetch failed") — the cause has
|
|
112
|
+
// the useful message (getaddrinfo ENOTFOUND, ECONNREFUSED, …)
|
|
113
|
+
last = { error: error.cause?.message || error.message };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (attempt < MAX_RETRIES) {
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, ctx.retryDelayMs * attempt));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return last;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* npm latest version of a package, cached per run. null when the registry
|
|
126
|
+
* lookup fails (unpublished package, offline) — callers dim out, never warn.
|
|
127
|
+
*/
|
|
128
|
+
function getLatestVersion(ctx, packageName) {
|
|
129
|
+
if (!(packageName in ctx.versionCache)) {
|
|
130
|
+
try {
|
|
131
|
+
ctx.versionCache[packageName] = ctx.exec(`npm view ${packageName} version`).trim();
|
|
132
|
+
} catch {
|
|
133
|
+
ctx.versionCache[packageName] = null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return ctx.versionCache[packageName];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Compare two x.y.z versions numerically. -1 / 0 / 1.
|
|
141
|
+
*/
|
|
142
|
+
function compareVersions(a, b) {
|
|
143
|
+
const pa = a.split('.').map(Number);
|
|
144
|
+
const pb = b.split('.').map(Number);
|
|
145
|
+
for (let i = 0; i < 3; i++) {
|
|
146
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
147
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolve the INSTALLED version of a package via the node_modules climb from
|
|
154
|
+
* a directory — handles file:/workspace refs where the declared range says
|
|
155
|
+
* nothing. null when not installed.
|
|
156
|
+
*/
|
|
157
|
+
function installedVersion(fromDir, packageName) {
|
|
158
|
+
let dir = path.resolve(fromDir);
|
|
159
|
+
|
|
160
|
+
while (true) {
|
|
161
|
+
const pkg = jetpack.read(path.join(dir, 'node_modules', packageName, 'package.json'), 'json');
|
|
162
|
+
if (pkg?.version) {
|
|
163
|
+
return pkg.version;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const parent = path.dirname(dir);
|
|
167
|
+
if (parent === dir) return null;
|
|
168
|
+
dir = parent;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Local checks ────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Per-target file checks: package.json parses; web targets produced a build;
|
|
176
|
+
* backend targets have their Firebase scaffolding.
|
|
177
|
+
*/
|
|
178
|
+
function checkTargetFiles(recorder, entry) {
|
|
179
|
+
const pkg = jetpack.read(path.join(entry.path, 'package.json'), 'json');
|
|
180
|
+
if (pkg) {
|
|
181
|
+
recorder.pass(`${entry.name}: package.json`);
|
|
182
|
+
} else {
|
|
183
|
+
recorder.fail(`${entry.name}: package.json`, 'missing or unparseable');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (entry.target === 'web') {
|
|
187
|
+
if (fs.existsSync(path.join(entry.path, 'dist', 'index.html'))) {
|
|
188
|
+
recorder.pass(`${entry.name}: build output`);
|
|
189
|
+
} else {
|
|
190
|
+
recorder.fail(`${entry.name}: build output`, 'dist/index.html missing — run the update service');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (entry.target === 'backend') {
|
|
195
|
+
// A custom-server backend (#584) deploys no Cloud Functions, so it has no
|
|
196
|
+
// firebase.json to carry — the ONE Firebase-only check here, noted rather
|
|
197
|
+
// than failed. The staged build below is checked in both modes.
|
|
198
|
+
if (entry.projectType === 'custom') {
|
|
199
|
+
recorder.note(`${entry.name}: firebase.json`, "skipped — projectType: 'custom' deploys no Cloud Functions");
|
|
200
|
+
} else if (fs.existsSync(path.join(entry.path, 'firebase.json'))) {
|
|
201
|
+
recorder.pass(`${entry.name}: firebase.json`);
|
|
202
|
+
} else {
|
|
203
|
+
recorder.fail(`${entry.name}: firebase.json`, 'missing');
|
|
204
|
+
}
|
|
205
|
+
// Staged output (src/dist pillar) — the backend's build-output twin of
|
|
206
|
+
// web's dist/index.html check
|
|
207
|
+
if (fs.existsSync(path.join(entry.path, 'dist', 'package.json'))) {
|
|
208
|
+
recorder.pass(`${entry.name}: staged dist/`);
|
|
209
|
+
} else {
|
|
210
|
+
recorder.fail(`${entry.name}: staged dist/`, 'dist/package.json missing — run the update service (omega build)');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Framework version check: the target's installed framework package vs the npm
|
|
217
|
+
* latest. Silent when the target doesn't declare its framework;
|
|
218
|
+
* dims out when the registry lookup fails (e.g. package not published yet).
|
|
219
|
+
*/
|
|
220
|
+
function checkFrameworkVersion(recorder, entry, ctx) {
|
|
221
|
+
const framework = TARGET_FRAMEWORKS[entry.target];
|
|
222
|
+
if (!framework) return;
|
|
223
|
+
|
|
224
|
+
// Deps live on the ONE manifest per target (src/dist pillar)
|
|
225
|
+
const pkgDir = entry.path;
|
|
226
|
+
const pkg = jetpack.read(path.join(pkgDir, 'package.json'), 'json');
|
|
227
|
+
const declared = pkg?.dependencies?.[framework] || pkg?.devDependencies?.[framework];
|
|
228
|
+
if (!declared) return;
|
|
229
|
+
|
|
230
|
+
const installed = installedVersion(pkgDir, framework) || declared.replace(/^[\^~>=<]+/, '');
|
|
231
|
+
const name = `${entry.name}: ${framework}`;
|
|
232
|
+
|
|
233
|
+
if (!/^\d/.test(installed)) {
|
|
234
|
+
recorder.note(name, `${installed} (not installed — run the update service)`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (ctx.dryRun) {
|
|
239
|
+
recorder.would(`npm view ${framework} version`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const latest = getLatestVersion(ctx, framework);
|
|
244
|
+
|
|
245
|
+
if (!latest) {
|
|
246
|
+
recorder.note(name, `${installed} (could not check latest)`);
|
|
247
|
+
} else if (compareVersions(installed, latest) >= 0) {
|
|
248
|
+
recorder.pass(name, `${installed} (latest: ${latest})`);
|
|
249
|
+
} else {
|
|
250
|
+
recorder.warn(name, `outdated (${installed} → ${latest})`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Parse `git status --porcelain` output into { status, file } entries.
|
|
256
|
+
* Lines must NOT be trimmed before slicing — the two status columns can
|
|
257
|
+
* legitimately start with a space (` M path`), so a leading trim shifts
|
|
258
|
+
* the path offset and eats the first character.
|
|
259
|
+
* @param {string} stdout - raw porcelain output
|
|
260
|
+
* @returns {Array<{ status: string, file: string }>}
|
|
261
|
+
*/
|
|
262
|
+
function parseWorkingTree(stdout) {
|
|
263
|
+
return stdout.split('\n').filter((line) => line.trim()).map((line) => ({
|
|
264
|
+
status: line.substring(0, 2).trim(),
|
|
265
|
+
file: line.substring(3),
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Working tree check — uncommitted files under the brand root (scoped with
|
|
271
|
+
* `-- .` so a brand nested in a larger workspace only sees its own files).
|
|
272
|
+
* Silent when the brand isn't in a git repository.
|
|
273
|
+
*/
|
|
274
|
+
function checkWorkingTree(recorder, ctx) {
|
|
275
|
+
let stdout;
|
|
276
|
+
try {
|
|
277
|
+
stdout = ctx.exec('git status --porcelain -- .', { cwd: ctx.brandRoot });
|
|
278
|
+
} catch {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const files = parseWorkingTree(stdout);
|
|
283
|
+
|
|
284
|
+
if (files.length === 0) {
|
|
285
|
+
recorder.pass('working tree', 'clean');
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
recorder.warn('working tree', `${files.length} uncommitted file${files.length === 1 ? '' : 's'}`);
|
|
290
|
+
for (const { status, file } of files.slice(0, MAX_FILES_SHOWN)) {
|
|
291
|
+
console.log(` ${chalk.dim(status)} ${chalk.dim(file)}`);
|
|
292
|
+
}
|
|
293
|
+
if (files.length > MAX_FILES_SHOWN) {
|
|
294
|
+
console.log(` ${chalk.dim(`... and ${files.length - MAX_FILES_SHOWN} more`)}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Live checks ─────────────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Homepage check — the deployed site answers on ITS instance's URL: the
|
|
302
|
+
* instance entry's url (multi-instance targets), else the id AS a subdomain of
|
|
303
|
+
* the brand host (#588), falling back to the brand URL for the single-instance
|
|
304
|
+
* world. Deploy records key per instance too, so a never-deployed admin
|
|
305
|
+
* instance nudges without failing a live main.
|
|
306
|
+
*/
|
|
307
|
+
async function checkHomepage(recorder, entry, ctx) {
|
|
308
|
+
const instance = instanceIdFromDirName(entry.name, entry.target);
|
|
309
|
+
const url = resolveInstanceUrl(ctx.brandConfig.targets?.[entry.target], instance, ctx.brandConfig);
|
|
310
|
+
const name = `${entry.name}: homepage`;
|
|
311
|
+
|
|
312
|
+
if (!url) {
|
|
313
|
+
recorder.warn(name, 'no brand.url configured — cannot check');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (ctx.dryRun) {
|
|
318
|
+
recorder.would(`fetch ${url}`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// No deploy record → a live miss means "not deployed yet", not an outage
|
|
323
|
+
// (never-deployed vs deployed-but-down — Ian 2026-07-17). A live HIT on a
|
|
324
|
+
// record-less brand adopts: the record is per-machine, so fresh clones of
|
|
325
|
+
// deployed brands self-heal here.
|
|
326
|
+
const deployed = readDeployRecord({ dir: ctx.brandRoot, target: entry.target, instance });
|
|
327
|
+
const { response, duration, error } = await fetchWithRetry(ctx, url);
|
|
328
|
+
const live = !error && response.status >= 200 && response.status < 400;
|
|
329
|
+
|
|
330
|
+
if (live) {
|
|
331
|
+
recorder.pass(name, `${url} → ${response.status} (${duration}ms)`);
|
|
332
|
+
if (!deployed) {
|
|
333
|
+
recordDeploy({ dir: ctx.brandRoot, target: entry.target, instance, detail: { adopted: true } });
|
|
334
|
+
}
|
|
335
|
+
} else if (!deployed) {
|
|
336
|
+
recorder.warn(name, `not deployed yet — run \`omega deploy\` when ready (${url})`);
|
|
337
|
+
} else {
|
|
338
|
+
recorder.fail(name, `${url} → ${error || response.status}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* API health check — the deployed backend answers on
|
|
344
|
+
* https://api.{host}{API_HEALTH_PATH}, plus a deployed-version comparison
|
|
345
|
+
* from the health payload. Skipped for shared Firebase projects (the owning
|
|
346
|
+
* brand deploys the backend).
|
|
347
|
+
*/
|
|
348
|
+
async function checkApiHealth(recorder, entry, ctx) {
|
|
349
|
+
const name = `${entry.name}: API health`;
|
|
350
|
+
|
|
351
|
+
if (ctx.brandConfig.cloud?.shared === true) {
|
|
352
|
+
recorder.note(name, 'shared Firebase project (owning brand deploys the backend)');
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const baseUrl = ctx.brandConfig.brand?.url;
|
|
357
|
+
if (!baseUrl) {
|
|
358
|
+
recorder.warn(name, 'no brand.url configured — cannot check');
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const host = baseUrl.replace(/^https?:\/\//, '');
|
|
363
|
+
const apiUrl = `https://${API_SUBDOMAIN}.${host}${API_HEALTH_PATH}`;
|
|
364
|
+
|
|
365
|
+
if (ctx.dryRun) {
|
|
366
|
+
recorder.would(`fetch ${apiUrl}`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Same never-deployed vs deployed-but-down split as checkHomepage
|
|
371
|
+
const deployRecord = readDeployRecord({ dir: ctx.brandRoot, target: entry.target });
|
|
372
|
+
const { response, duration, error } = await fetchWithRetry(ctx, apiUrl);
|
|
373
|
+
const live = !error && response.status >= 200 && response.status < 400;
|
|
374
|
+
|
|
375
|
+
if (!live) {
|
|
376
|
+
if (!deployRecord) {
|
|
377
|
+
recorder.warn(name, `not deployed yet — run \`omega deploy\` when ready (${apiUrl})`);
|
|
378
|
+
} else {
|
|
379
|
+
recorder.fail(name, `${apiUrl} → ${error || response.status}`);
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
recorder.pass(name, `${apiUrl} → ${response.status} (${duration}ms)`);
|
|
385
|
+
if (!deployRecord) {
|
|
386
|
+
recordDeploy({ dir: ctx.brandRoot, target: entry.target, detail: { adopted: true } });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Deployed-version comparison from the health payload
|
|
390
|
+
let data = null;
|
|
391
|
+
try {
|
|
392
|
+
data = await response.json();
|
|
393
|
+
} catch {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const framework = TARGET_FRAMEWORKS[entry.target];
|
|
398
|
+
const deployed = data?.backendVersion;
|
|
399
|
+
if (!deployed) return;
|
|
400
|
+
|
|
401
|
+
const deployedName = `${entry.name}: deployed backend`;
|
|
402
|
+
const latest = getLatestVersion(ctx, framework);
|
|
403
|
+
|
|
404
|
+
if (!latest) {
|
|
405
|
+
recorder.note(deployedName, `${deployed} (could not check latest)`);
|
|
406
|
+
} else if (deployed === latest) {
|
|
407
|
+
recorder.pass(deployedName, `${deployed} (up to date)`);
|
|
408
|
+
} else {
|
|
409
|
+
recorder.warn(deployedName, `outdated (${deployed} → ${latest})`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* GitHub Actions check — latest workflow run of the brand repo. Silent when
|
|
415
|
+
* no repo.providers.github.org is configured (the github service already reports that skip)
|
|
416
|
+
* or when the gh CLI is unavailable.
|
|
417
|
+
*/
|
|
418
|
+
function checkGitHubActions(recorder, ctx) {
|
|
419
|
+
const github = ctx.brandConfig.repo?.providers?.github || {};
|
|
420
|
+
if (!github.org) return;
|
|
421
|
+
|
|
422
|
+
// BOTH halves from the one derivation (an `owner/name` slug owns itself); the
|
|
423
|
+
// service's own brand id stands in for a config that names no brand.
|
|
424
|
+
const repo = brandRepo(ctx.brandConfig).repo || `${github.org}/${brandRepoName({ brand: { id: ctx.brandId } })}`;
|
|
425
|
+
|
|
426
|
+
if (ctx.dryRun) {
|
|
427
|
+
recorder.would(`gh run list --repo ${repo}`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
let runs;
|
|
432
|
+
try {
|
|
433
|
+
runs = JSON.parse(ctx.exec(`gh run list --repo ${repo} --limit 1 --json status,conclusion,name`));
|
|
434
|
+
} catch {
|
|
435
|
+
recorder.note('GitHub Actions', 'could not check (gh unavailable or no access)');
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!runs.length) {
|
|
440
|
+
recorder.note('GitHub Actions', 'no runs found');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const run = runs[0];
|
|
445
|
+
if (run.status === 'completed' && run.conclusion === 'success') {
|
|
446
|
+
recorder.pass('GitHub Actions', `${run.name} → success`);
|
|
447
|
+
} else if (run.status === 'in_progress') {
|
|
448
|
+
recorder.warn('GitHub Actions', `${run.name} → in progress`);
|
|
449
|
+
} else {
|
|
450
|
+
recorder.fail('GitHub Actions', `${run.name} → ${run.conclusion || run.status}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
module.exports = {
|
|
455
|
+
createRecorder,
|
|
456
|
+
defaultExec,
|
|
457
|
+
fetchWithRetry,
|
|
458
|
+
compareVersions,
|
|
459
|
+
installedVersion,
|
|
460
|
+
parseWorkingTree,
|
|
461
|
+
checkTargetFiles,
|
|
462
|
+
checkFrameworkVersion,
|
|
463
|
+
checkWorkingTree,
|
|
464
|
+
checkHomepage,
|
|
465
|
+
checkApiHealth,
|
|
466
|
+
checkGitHubActions,
|
|
467
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update service — installs dependencies and builds every target in the brand
|
|
3
|
+
* monorepo. The monorepo cousin of omega-manager's update service (which
|
|
4
|
+
* walks separate per-target repos); phases beyond install/build (bump,
|
|
5
|
+
* deploy, sync) port over with their flags as the cutover advances.
|
|
6
|
+
*/
|
|
7
|
+
const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
8
|
+
|
|
9
|
+
module.exports.run = createServiceRunner({
|
|
10
|
+
serviceDir: __dirname,
|
|
11
|
+
setup: (context) => {
|
|
12
|
+
const targets = (context.targets || []).filter((entry) => entry.target);
|
|
13
|
+
|
|
14
|
+
if (targets.length === 0) {
|
|
15
|
+
return { skip: true, reason: 'no target-mapped dirs' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {};
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The update service's incremental cache (#445) — `.omega/cache/update.json`
|
|
3
|
+
* at the brand root: per target, the two fingerprints its last successful build
|
|
4
|
+
* ran against. Derived data, so it lives with the brand's other caches under
|
|
5
|
+
* `.omega/cache/` (the edge service's read cache is its neighbour) — never in
|
|
6
|
+
* config, which holds provisioned facts.
|
|
7
|
+
*
|
|
8
|
+
* Missing, unparseable, or written in an older shape → an empty cache, which
|
|
9
|
+
* reads as "every target is fresh": the run does the full update and rewrites the
|
|
10
|
+
* file. A cache is an optimization, never a reason to fail the walk, so a
|
|
11
|
+
* failed write warns and the walk continues.
|
|
12
|
+
*/
|
|
13
|
+
const { join } = require('node:path');
|
|
14
|
+
const chalk = require('chalk').default;
|
|
15
|
+
const jetpack = require('fs-jetpack');
|
|
16
|
+
|
|
17
|
+
const CACHE_VERSION = 2; // v2: the per-target map key renamed apps -> targets (#455)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} brandRoot - The brand monorepo root.
|
|
21
|
+
* @returns {string} Absolute path to the cache file.
|
|
22
|
+
*/
|
|
23
|
+
function cacheFile(brandRoot) {
|
|
24
|
+
return join(brandRoot, '.omega', 'cache', 'update.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} brandRoot - The brand monorepo root.
|
|
29
|
+
* @returns {{ version: number, targets: Object }} The cache, or an empty one.
|
|
30
|
+
*/
|
|
31
|
+
function readCache(brandRoot) {
|
|
32
|
+
let data = null;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
data = jetpack.read(cacheFile(brandRoot), 'json');
|
|
36
|
+
} catch {
|
|
37
|
+
data = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (data?.version !== CACHE_VERSION || typeof data.targets !== 'object' || data.targets === null) {
|
|
41
|
+
return { version: CACHE_VERSION, targets: {} };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} brandRoot - The brand monorepo root.
|
|
49
|
+
* @param {Object} cache - The cache to persist.
|
|
50
|
+
*/
|
|
51
|
+
function writeCache(brandRoot, cache) {
|
|
52
|
+
try {
|
|
53
|
+
jetpack.write(cacheFile(brandRoot), cache);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.log(` ${chalk.yellow('⚠')} update cache not written${chalk.dim(`: ${error.message}`)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { readCache, writeCache, cacheFile, CACHE_VERSION };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update fingerprints (#445) — the two input sets that decide whether a target
|
|
3
|
+
* still needs its build, each hashed to one string:
|
|
4
|
+
*
|
|
5
|
+
* files — a sweep of the target's own tree (relative path + size + mtime
|
|
6
|
+
* per file): the assets service's mtime-diff precedent scaled
|
|
7
|
+
* from a file to a directory. Installed deps, build output,
|
|
8
|
+
* logs and the machinery dirs are excluded, so a build never
|
|
9
|
+
* re-dirties its own inputs. The merge-chain files ABOVE the
|
|
10
|
+
* target (brand and company omega.json5 + .env) fold in too —
|
|
11
|
+
* the build reads them, and a brand-config edit that left every
|
|
12
|
+
* target "converged" would serve stale output silently.
|
|
13
|
+
* frameworks — every declared `@omega.js/*` dependency's installed identity:
|
|
14
|
+
* the version string for an npm install, and for a LOCAL LINK
|
|
15
|
+
* (which points into the monorepo, where the version stands
|
|
16
|
+
* still between builds) the link target's version plus a sweep
|
|
17
|
+
* of its build output.
|
|
18
|
+
*
|
|
19
|
+
* Both are stat walks — no file contents are ever read.
|
|
20
|
+
*/
|
|
21
|
+
const { createHash } = require('node:crypto');
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const jetpack = require('fs-jetpack');
|
|
25
|
+
|
|
26
|
+
// Never part of a target's input set: installed deps (node_modules), build
|
|
27
|
+
// output (dist), run logs (logs), and the machinery `omega build` / `omega
|
|
28
|
+
// dev` / firebase write beside the sources (.temp, .omega, .cache, .firebase).
|
|
29
|
+
const EXCLUDED_DIRS = new Set(['node_modules', 'dist', 'logs', '.temp', '.omega', '.cache', '.firebase', '.git']);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hash a directory tree by path + size + mtime. Entries are walked in name
|
|
33
|
+
* order so the digest is machine-independent; anything that is neither a
|
|
34
|
+
* plain file nor a directory (symlinks, sockets) is skipped, as are debug
|
|
35
|
+
* logs and .DS_Store — churn that says nothing about the sources.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} dir - Directory to sweep.
|
|
38
|
+
* @returns {string|null} Hex digest, or null when the directory doesn't exist.
|
|
39
|
+
*/
|
|
40
|
+
function sweep(dir) {
|
|
41
|
+
if (!jetpack.exists(dir)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const hash = createHash('sha1');
|
|
46
|
+
|
|
47
|
+
const walk = (current, prefix) => {
|
|
48
|
+
const entries = fs.readdirSync(current, { withFileTypes: true })
|
|
49
|
+
.sort((a, b) => (a.name < b.name ? -1 : 1));
|
|
50
|
+
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
if (!EXCLUDED_DIRS.has(entry.name)) {
|
|
54
|
+
walk(path.join(current, entry.name), `${prefix}${entry.name}/`);
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!entry.isFile() || entry.name.endsWith('.log') || entry.name === '.DS_Store') {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const stat = fs.statSync(path.join(current, entry.name));
|
|
64
|
+
hash.update(`${prefix}${entry.name}:${stat.size}:${stat.mtimeMs}\n`);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
walk(dir, '');
|
|
69
|
+
return hash.digest('hex');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Where does `name` resolve from `fromDir` via the node_modules climb? A
|
|
74
|
+
* manual walk (not require.resolve) so exports-restricted packages and
|
|
75
|
+
* bin-only packages don't false-negative.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} fromDir - Directory to climb from.
|
|
78
|
+
* @param {string} name - Package name.
|
|
79
|
+
* @returns {string|null} The package directory, or null when it doesn't resolve.
|
|
80
|
+
*/
|
|
81
|
+
function resolvePackageDir(fromDir, name) {
|
|
82
|
+
let dir = path.resolve(fromDir);
|
|
83
|
+
|
|
84
|
+
while (true) {
|
|
85
|
+
const candidate = path.join(dir, 'node_modules', name);
|
|
86
|
+
if (fs.existsSync(candidate)) {
|
|
87
|
+
return candidate;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parent = path.dirname(dir);
|
|
91
|
+
if (parent === dir) return null;
|
|
92
|
+
dir = parent;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Hash the installed identity of every `@omega.js/*` package the target declares.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} targetPath - The target root.
|
|
100
|
+
* @returns {string} Hex digest.
|
|
101
|
+
*/
|
|
102
|
+
function frameworkFingerprint(targetPath) {
|
|
103
|
+
const pkg = jetpack.read(path.join(targetPath, 'package.json'), 'json');
|
|
104
|
+
const declared = Object.keys({ ...pkg?.dependencies, ...pkg?.devDependencies })
|
|
105
|
+
.filter((name) => name.startsWith('@omega.js/'))
|
|
106
|
+
.sort();
|
|
107
|
+
|
|
108
|
+
const parts = declared.map((name) => {
|
|
109
|
+
const dir = resolvePackageDir(targetPath, name);
|
|
110
|
+
if (!dir) {
|
|
111
|
+
return `${name}@missing`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const version = jetpack.read(path.join(dir, 'package.json'), 'json')?.version || 'unknown';
|
|
115
|
+
if (!fs.lstatSync(dir).isSymbolicLink()) {
|
|
116
|
+
return `${name}@${version}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A local link resolves into the monorepo: the version never moves there,
|
|
120
|
+
// so the link target's build output is what actually changes under the target.
|
|
121
|
+
const target = fs.realpathSync(dir);
|
|
122
|
+
return `${name}@${version}#${sweep(path.join(target, 'dist')) || 'no-dist'}`;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return createHash('sha1').update(parts.join('\n')).digest('hex');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The pair of fingerprints cached per target.
|
|
130
|
+
*
|
|
131
|
+
* Chain entries are labelled (not path-keyed) so the digest survives a brand
|
|
132
|
+
* directory move the way the sweep's relative paths do; an absent file hashes
|
|
133
|
+
* as absent, so growing a .env later dirties the fingerprint like an edit.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} targetPath - The target root.
|
|
136
|
+
* @param {Array<{ label: string, file: string }>} [chainFiles] - Merge-chain
|
|
137
|
+
* files outside the target tree that its build reads (brand/company config
|
|
138
|
+
* and .env).
|
|
139
|
+
* @returns {{ files: string, frameworks: string }}
|
|
140
|
+
*/
|
|
141
|
+
function fingerprintTarget(targetPath, chainFiles = []) {
|
|
142
|
+
const chain = chainFiles.map(({ label, file }) => {
|
|
143
|
+
const stat = fs.statSync(file, { throwIfNoEntry: false });
|
|
144
|
+
return stat ? `${label}:${stat.size}:${stat.mtimeMs}` : `${label}:absent`;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const files = createHash('sha1')
|
|
148
|
+
.update([sweep(targetPath) || 'missing', ...chain].join('\n'))
|
|
149
|
+
.digest('hex');
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
files,
|
|
153
|
+
frameworks: frameworkFingerprint(targetPath),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { fingerprintTarget, resolvePackageDir, sweep, EXCLUDED_DIRS };
|