@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,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "How is this run asked to stop?" The ONE list every supervisor registers.
|
|
3
|
+
*
|
|
4
|
+
* A supervisor is any process that owns a child, a watcher or a long-running
|
|
5
|
+
* loop it must unwind before it goes: the backend's emulator and test runner,
|
|
6
|
+
* the manager's `omega dev`, the web dev server, the log tails. Each of them
|
|
7
|
+
* used to hand-type its own subset, and the subsets disagreed. The manager
|
|
8
|
+
* registered SIGINT and SIGTERM only, so a CLOSED TERMINAL (SIGHUP) killed the
|
|
9
|
+
* orchestrator outright and its detached legs, the emulator tree among them,
|
|
10
|
+
* orphaned with their ports ([#629](https://github.com/Omega-JS-Stack/omega/issues/629)).
|
|
11
|
+
*
|
|
12
|
+
* A signal with no listener is not a shutdown: node kills THIS process only,
|
|
13
|
+
* and anything detached below it survives. So all three take the SAME teardown:
|
|
14
|
+
* Ctrl+C (SIGINT), a programmatic stop (a supervisor's own shutdown, `omega
|
|
15
|
+
* dev`'s stop, the journey lane, all SIGTERM), and a closed terminal (SIGHUP).
|
|
16
|
+
*
|
|
17
|
+
* Stdlib-only on purpose: a vendored module must not push a dependency onto
|
|
18
|
+
* its hosts.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Every way a run is asked to stop.
|
|
23
|
+
*
|
|
24
|
+
* @type {string[]}
|
|
25
|
+
*/
|
|
26
|
+
const STOP_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
27
|
+
|
|
28
|
+
module.exports = { STOP_SIGNALS };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-scope grammar (C5) — ONE parser for `omega test` targets across every
|
|
3
|
+
* framework, so scoping means the same thing in a web app, a backend, a
|
|
4
|
+
* desktop app, and an extension:
|
|
5
|
+
*
|
|
6
|
+
* npx omega test → PROJECT tests only (consumer context)
|
|
7
|
+
* npx omega test pages/ → project tests under pages/
|
|
8
|
+
* npx omega test project:pages/ → same, explicit (`brand:` = alias)
|
|
9
|
+
* npx omega test framework: → the framework's own suite
|
|
10
|
+
* npx omega test framework:routes/ → framework suite, scoped
|
|
11
|
+
* npx omega test full: → both sources
|
|
12
|
+
* npx omega test full:auth → both sources, path-scoped
|
|
13
|
+
*
|
|
14
|
+
* `omega:` and `mgr:` are universal framework aliases; each framework also
|
|
15
|
+
* answers to its own id (`backend:`, `desktop:`, `extension:`, `web:`) via
|
|
16
|
+
* `frameworkAliases`. Bare paths bind to the PROJECT source — reaching the
|
|
17
|
+
* framework suite is always an explicit choice (Ian 2026-07-11: bare runs
|
|
18
|
+
* from a brand must never drag the framework corpus in).
|
|
19
|
+
*
|
|
20
|
+
* Framework SELF context (running inside the framework package itself —
|
|
21
|
+
* `selfTest: true`) flips the no-target default to the framework suite, so
|
|
22
|
+
* each package's own `npm test` keeps meaning "run my suite".
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const UNIVERSAL_FRAMEWORK_ALIASES = ['framework', 'omega', 'mgr'];
|
|
26
|
+
const PROJECT_ALIASES = ['project', 'brand'];
|
|
27
|
+
const FULL_ALIASES = ['full'];
|
|
28
|
+
|
|
29
|
+
// Per-framework id prefixes, keyed by package name. The SSOT for "which
|
|
30
|
+
// prefixes does each framework answer to": framework runners pass their own
|
|
31
|
+
// entry as `frameworkAliases`, and the manager's brand-root `test` fan-out
|
|
32
|
+
// routes `web:`/`desktop:`/… targets to the target owning that framework.
|
|
33
|
+
// Legacy short ids (ujm/em/bxm) retired with cp262's legacy-name sweep.
|
|
34
|
+
const FRAMEWORK_IDS = {
|
|
35
|
+
'@omega.js/web': ['web'],
|
|
36
|
+
'@omega.js/backend': ['backend'],
|
|
37
|
+
'@omega.js/desktop': ['desktop'],
|
|
38
|
+
'@omega.js/extension': ['extension'],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Parse `omega test` positional targets into sources + per-source path filters.
|
|
43
|
+
*
|
|
44
|
+
* @param {string[]} rawTargets - Positional targets (may be empty)
|
|
45
|
+
* @param {Object} [options]
|
|
46
|
+
* @param {string[]} [options.frameworkAliases] - Extra framework prefixes for
|
|
47
|
+
* this framework (e.g. ['backend'], ['desktop'], ['extension'], ['web'])
|
|
48
|
+
* @param {boolean} [options.selfTest] - True when running inside the framework
|
|
49
|
+
* package itself (no consumer project)
|
|
50
|
+
* @returns {{
|
|
51
|
+
* sources: string[], // subset of ['framework', 'project']
|
|
52
|
+
* filters: { framework: string[], project: string[] }, // prefix-stripped path filters per source
|
|
53
|
+
* invalid: string[], // unrecognized `<word>:` prefixes (likely typos)
|
|
54
|
+
* }}
|
|
55
|
+
*/
|
|
56
|
+
function parseTestScope(rawTargets, options = {}) {
|
|
57
|
+
const frameworkAliases = new Set([
|
|
58
|
+
...UNIVERSAL_FRAMEWORK_ALIASES,
|
|
59
|
+
...(options.frameworkAliases || []),
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
const sources = new Set();
|
|
63
|
+
const filters = { framework: [], project: [] };
|
|
64
|
+
const invalid = [];
|
|
65
|
+
|
|
66
|
+
for (const raw of rawTargets || []) {
|
|
67
|
+
const target = String(raw).trim();
|
|
68
|
+
if (!target) continue;
|
|
69
|
+
|
|
70
|
+
const match = target.match(/^([a-z-]+):(.*)$/);
|
|
71
|
+
|
|
72
|
+
if (!match) {
|
|
73
|
+
// Bare path — binds to the PROJECT source.
|
|
74
|
+
sources.add('project');
|
|
75
|
+
filters.project.push(target);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const [, prefix, pathPart] = match;
|
|
80
|
+
|
|
81
|
+
if (frameworkAliases.has(prefix)) {
|
|
82
|
+
sources.add('framework');
|
|
83
|
+
if (pathPart) filters.framework.push(pathPart);
|
|
84
|
+
} else if (PROJECT_ALIASES.includes(prefix)) {
|
|
85
|
+
sources.add('project');
|
|
86
|
+
if (pathPart) filters.project.push(pathPart);
|
|
87
|
+
} else if (FULL_ALIASES.includes(prefix)) {
|
|
88
|
+
sources.add('framework');
|
|
89
|
+
sources.add('project');
|
|
90
|
+
if (pathPart) {
|
|
91
|
+
filters.framework.push(pathPart);
|
|
92
|
+
filters.project.push(pathPart);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// Unknown prefix — surface it rather than silently matching nothing.
|
|
96
|
+
invalid.push(target);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// No targets (or only invalid ones): consumer default = project only;
|
|
101
|
+
// framework self context = the framework's own suite.
|
|
102
|
+
if (sources.size === 0) {
|
|
103
|
+
sources.add(options.selfTest ? 'framework' : 'project');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
sources: ['framework', 'project'].filter((s) => sources.has(s)),
|
|
108
|
+
filters,
|
|
109
|
+
invalid,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Did the run ask for test files BY NAME? A path filter (`build/x`,
|
|
115
|
+
* `project:x`, `desktop:x`, `full:x`) names files; a bare run, or a bare
|
|
116
|
+
* source prefix, asks for a source. Only the first kind can be a typo, so
|
|
117
|
+
* only the first kind fails when it selects nothing
|
|
118
|
+
* ([#814](https://github.com/Omega-JS-Stack/omega/issues/814)).
|
|
119
|
+
*
|
|
120
|
+
* @param {{filters: {framework: string[], project: string[]}}} scope - A parseTestScope() result
|
|
121
|
+
* @returns {boolean} True when at least one path filter was given
|
|
122
|
+
*/
|
|
123
|
+
function isPathTargeted(scope) {
|
|
124
|
+
return scope.filters.framework.length > 0 || scope.filters.project.length > 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The ONE line every framework prints when a named target selects zero test
|
|
129
|
+
* files: the same sentence in a web app, a backend, a desktop app and an
|
|
130
|
+
* extension ([#814](https://github.com/Omega-JS-Stack/omega/issues/814)).
|
|
131
|
+
*
|
|
132
|
+
* @param {string} target - The target as the caller spelled it
|
|
133
|
+
* @returns {string} The message (the caller adds its own log tag)
|
|
134
|
+
*/
|
|
135
|
+
function noMatchMessage(target) {
|
|
136
|
+
return `No test file matches "${target}".`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// A brand-root run fans ONE target out over every target, where a target that
|
|
140
|
+
// does not carry that path is a legitimate no-op rather than a typo. The
|
|
141
|
+
// manager sets this signal on the runs it forwards and reads the exit code
|
|
142
|
+
// below to tell a miss from a failure
|
|
143
|
+
// ([#814](https://github.com/Omega-JS-Stack/omega/issues/814)). Its presence is
|
|
144
|
+
// the whole signal, the way OMEGA_CASE_RUNNER's is.
|
|
145
|
+
const FANOUT_ENV = 'OMEGA_TEST_FANOUT';
|
|
146
|
+
|
|
147
|
+
// The exit code a no-match answers with under that signal: distinct, so the
|
|
148
|
+
// fan-out counts misses without confusing them with failures. A standalone run
|
|
149
|
+
// stays 1, the code every human and every CI already reads as "this failed".
|
|
150
|
+
const NO_MATCH_EXIT_CODE = 3;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The exit code a no-match run ends on, given its environment.
|
|
154
|
+
*
|
|
155
|
+
* @param {object} [env] - The environment to read (defaults to process.env)
|
|
156
|
+
* @returns {number} NO_MATCH_EXIT_CODE inside a brand-root fan-out, else 1
|
|
157
|
+
*/
|
|
158
|
+
function noMatchExitCode(env = process.env) {
|
|
159
|
+
return env[FANOUT_ENV] ? NO_MATCH_EXIT_CODE : 1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { parseTestScope, isPathTargeted, noMatchMessage, noMatchExitCode, NO_MATCH_EXIT_CODE, FANOUT_ENV, UNIVERSAL_FRAMEWORK_ALIASES, PROJECT_ALIASES, FULL_ALIASES, FRAMEWORK_IDS };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The committed translation cache — per-string maps keyed by a hash of the
|
|
3
|
+
* SOURCE string, stored as reviewable JSON in the consumer's `translations/`
|
|
4
|
+
* directory (committed to git, replacing both the legacy gitignored `.cache/`
|
|
5
|
+
* and the cache-uj-translation GitHub-branch flow). A source edit changes the
|
|
6
|
+
* hash → cache miss → only that string re-translates; a human can hand-fix a
|
|
7
|
+
* translation VALUE and it sticks for as long as the source is unchanged.
|
|
8
|
+
*
|
|
9
|
+
* Layout: <rootDir>/<lang>/<namespace>.json → { "<hash12>": "translation" }
|
|
10
|
+
* (namespace = a page path for web, 'messages'/'description' for extension).
|
|
11
|
+
*/
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const crypto = require('node:crypto');
|
|
14
|
+
const jetpack = require('fs-jetpack');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Cache key for a source string.
|
|
18
|
+
* @param {string} source - the source-language string
|
|
19
|
+
* @returns {string} 12-hex-char sha256 prefix
|
|
20
|
+
*/
|
|
21
|
+
function hashKey(source) {
|
|
22
|
+
return crypto.createHash('sha256').update(source).digest('hex').slice(0, 12);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a namespace's cache file path.
|
|
27
|
+
* @param {string} rootDir - translations root (e.g. <consumer>/translations)
|
|
28
|
+
* @param {string} lang - language code
|
|
29
|
+
* @param {string} namespace - cache namespace (slashes allowed)
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function cachePath(rootDir, lang, namespace) {
|
|
33
|
+
return path.join(rootDir, lang, `${namespace}.json`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load a namespace's hash → translation map ({} when absent/corrupt).
|
|
38
|
+
* @param {string} rootDir - translations root
|
|
39
|
+
* @param {string} lang - language code
|
|
40
|
+
* @param {string} namespace - cache namespace
|
|
41
|
+
* @returns {object}
|
|
42
|
+
*/
|
|
43
|
+
function loadCache(rootDir, lang, namespace) {
|
|
44
|
+
const file = cachePath(rootDir, lang, namespace);
|
|
45
|
+
|
|
46
|
+
if (!jetpack.exists(file)) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(jetpack.read(file)) || {};
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Save a namespace's map, pruned to the CURRENT source strings so stale
|
|
59
|
+
* entries never accumulate. No-op delete when the map is empty.
|
|
60
|
+
* @param {string} rootDir - translations root
|
|
61
|
+
* @param {string} lang - language code
|
|
62
|
+
* @param {string} namespace - cache namespace
|
|
63
|
+
* @param {object} map - hash → translation
|
|
64
|
+
* @param {string[]} [currentSources] - prune to these sources' hashes
|
|
65
|
+
*/
|
|
66
|
+
function saveCache(rootDir, lang, namespace, map, currentSources) {
|
|
67
|
+
let pruned = map;
|
|
68
|
+
|
|
69
|
+
if (currentSources) {
|
|
70
|
+
const keep = new Set(currentSources.map(hashKey));
|
|
71
|
+
pruned = Object.fromEntries(Object.entries(map).filter(([key]) => keep.has(key)));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const file = cachePath(rootDir, lang, namespace);
|
|
75
|
+
|
|
76
|
+
if (!Object.keys(pruned).length) {
|
|
77
|
+
jetpack.remove(file);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
jetpack.write(file, `${JSON.stringify(pruned, null, 2)}\n`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { hashKey, cachePath, loadCache, saveCache };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The translation engine — one provider-agnostic protocol for translating a
|
|
3
|
+
* batch of strings: JSON array in → same-length JSON array out, with a control
|
|
4
|
+
* sentinel appended to every batch to prove alignment, automatic batching,
|
|
5
|
+
* validation retries, and a HALVING re-ask for a batch the model keeps
|
|
6
|
+
* collapsing. Frameworks feed it strings (page text nodes, extension messages)
|
|
7
|
+
* and get positionally-aligned translations back.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Alignment sentinel — appended to every batch, must return unchanged
|
|
11
|
+
const CONTROL = 'OMEGA-TRANSLATION-CONTROL';
|
|
12
|
+
|
|
13
|
+
const BATCH_SIZE = 25;
|
|
14
|
+
const MAX_RETRIES = 2;
|
|
15
|
+
|
|
16
|
+
// How many batches are in flight at once (#604). A batch is one model call and
|
|
17
|
+
// the model's latency dominates it, so a serial pass paid that latency once per
|
|
18
|
+
// 25 strings — a language took minutes it never needed to. Raise it only as far
|
|
19
|
+
// as the providers' rate limits allow.
|
|
20
|
+
const CONCURRENCY = 5;
|
|
21
|
+
|
|
22
|
+
const SYSTEM_PROMPT = `<role>
|
|
23
|
+
Professional translator. Return ONLY a valid JSON array — no commentary, no markdown fences, no wrapping.
|
|
24
|
+
</role>
|
|
25
|
+
|
|
26
|
+
<task>
|
|
27
|
+
Translate the input JSON array of strings into the target language.
|
|
28
|
+
The output array MUST have the EXACT same length as the input array.
|
|
29
|
+
</task>
|
|
30
|
+
|
|
31
|
+
<rules>
|
|
32
|
+
- Input: a JSON array of strings. Output: a JSON array of translated strings of the SAME length.
|
|
33
|
+
- DO NOT add, remove, merge, or reorder elements.
|
|
34
|
+
- Consider adjacent strings for context — they come from the same document.
|
|
35
|
+
- Preserve leading and trailing whitespace in each string.
|
|
36
|
+
- Keep HTML tags, attributes, URLs, and email addresses verbatim — never translate them.
|
|
37
|
+
- Preserve placeholder tokens exactly (e.g. $1, $2, %s, {name}, {{ value }}).
|
|
38
|
+
- Keep brand names, product names, and company names in their original language.
|
|
39
|
+
- Never translate the brand name "{brand}".
|
|
40
|
+
- The control string "${CONTROL}" must be returned unchanged at its exact position.
|
|
41
|
+
{extraRules}
|
|
42
|
+
</rules>
|
|
43
|
+
|
|
44
|
+
<example lang="es">
|
|
45
|
+
Input: ["Welcome to {brand}", "Get started today", "${CONTROL}"]
|
|
46
|
+
Output: ["Bienvenido a {brand}", "Comienza hoy", "${CONTROL}"]
|
|
47
|
+
</example>`;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse the provider's response into a JSON array (strips accidental fences).
|
|
51
|
+
* @param {string} text - raw provider response
|
|
52
|
+
* @returns {Array} parsed array
|
|
53
|
+
*/
|
|
54
|
+
function parseArrayResponse(text) {
|
|
55
|
+
const cleaned = (text || '').trim().replace(/^```json?\n?|\n?```$/g, '');
|
|
56
|
+
const parsed = JSON.parse(cleaned);
|
|
57
|
+
|
|
58
|
+
if (!Array.isArray(parsed)) {
|
|
59
|
+
throw new Error(`translation result is not an array (got ${typeof parsed})`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Re-apply the original string's leading/trailing whitespace to a translation
|
|
67
|
+
* (models trim despite instructions; surrounding markup relies on it).
|
|
68
|
+
* @param {string} original - source string
|
|
69
|
+
* @param {string} translated - translated string
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
function preserveWhitespace(original, translated) {
|
|
73
|
+
const leading = original.match(/^\s*/)[0];
|
|
74
|
+
const trailing = original.match(/\s*$/)[0];
|
|
75
|
+
|
|
76
|
+
return `${leading}${String(translated).trim()}${trailing}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* An alignment failure — the batch came back, but not one-for-one. Marked so
|
|
81
|
+
* the caller can tell it from a provider that never answered at all.
|
|
82
|
+
* @param {string} message
|
|
83
|
+
* @returns {Error}
|
|
84
|
+
*/
|
|
85
|
+
function alignmentError(message) {
|
|
86
|
+
const error = new Error(message);
|
|
87
|
+
error.alignment = true;
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Translate one batch (with the control sentinel and validation retries).
|
|
93
|
+
* @param {object} ctx - { send, system, language, languageName }
|
|
94
|
+
* @param {string[]} batch - strings to translate
|
|
95
|
+
* @param {number} attempt - current attempt (0-based)
|
|
96
|
+
* @returns {Promise<{ result: string[], usage: object }>}
|
|
97
|
+
*/
|
|
98
|
+
async function translateBatch(ctx, batch, attempt) {
|
|
99
|
+
const payload = [...batch, CONTROL];
|
|
100
|
+
const user = `Target language: ${ctx.language} (${ctx.languageName})\nArray length: ${payload.length}\n\n${JSON.stringify(payload)}`;
|
|
101
|
+
|
|
102
|
+
const { text, usage } = await ctx.send({ system: ctx.system, user });
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const parsed = parseArrayResponse(text);
|
|
106
|
+
|
|
107
|
+
if (parsed.length !== payload.length) {
|
|
108
|
+
throw alignmentError(`length mismatch: expected ${payload.length}, got ${parsed.length}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (parsed[parsed.length - 1] !== CONTROL) {
|
|
112
|
+
throw alignmentError('control sentinel missing or altered at the last position');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { result: parsed.slice(0, -1), usage };
|
|
116
|
+
} catch (e) {
|
|
117
|
+
if (attempt < MAX_RETRIES) {
|
|
118
|
+
return translateBatch(ctx, batch, attempt + 1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A batch a model MERGES is deterministic (#523): the playground's
|
|
122
|
+
// ship-the-docs page came back 25-for-26 on all six attempts, in two
|
|
123
|
+
// languages — re-asking the same array can only fail the same way. Halving
|
|
124
|
+
// it separates whatever pair collapsed, and each half is asked in full,
|
|
125
|
+
// so nothing is guessed at and the page stops shipping untranslated.
|
|
126
|
+
if (e.alignment && batch.length > 1) {
|
|
127
|
+
return splitBatch(ctx, batch);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
throw new Error(`translation failed after ${MAX_RETRIES + 1} attempts: ${e.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Re-ask a collapsing batch in halves, bottoming out at one string (a single
|
|
136
|
+
* string plus the sentinel is unambiguous — a provider that still drops it is
|
|
137
|
+
* broken, and the caller skips the page-language pair whole).
|
|
138
|
+
* @param {object} ctx - { send, system, language, languageName }
|
|
139
|
+
* @param {string[]} batch - the batch that would not come back aligned
|
|
140
|
+
* @returns {Promise<{ result: string[], usage: object }>}
|
|
141
|
+
*/
|
|
142
|
+
async function splitBatch(ctx, batch) {
|
|
143
|
+
const middle = Math.ceil(batch.length / 2);
|
|
144
|
+
const result = [];
|
|
145
|
+
const usage = { input: 0, output: 0 };
|
|
146
|
+
|
|
147
|
+
for (const half of [batch.slice(0, middle), batch.slice(middle)]) {
|
|
148
|
+
const outcome = await translateBatch(ctx, half, 0);
|
|
149
|
+
result.push(...outcome.result);
|
|
150
|
+
usage.input += outcome.usage?.input || 0;
|
|
151
|
+
usage.output += outcome.usage?.output || 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { result, usage };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Run a worker over every item with at most CONCURRENCY of them in flight,
|
|
159
|
+
* returning results in the ITEMS' order — never completion order.
|
|
160
|
+
* @param {Array} items - the work items
|
|
161
|
+
* @param {Function} worker - (item) → Promise
|
|
162
|
+
* @returns {Promise<Array>} results, positionally aligned with items
|
|
163
|
+
*/
|
|
164
|
+
async function mapLimited(items, worker) {
|
|
165
|
+
const results = new Array(items.length);
|
|
166
|
+
let next = 0;
|
|
167
|
+
|
|
168
|
+
const drain = async () => {
|
|
169
|
+
while (next < items.length) {
|
|
170
|
+
const index = next++;
|
|
171
|
+
results[index] = await worker(items[index]);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, items.length) }, drain));
|
|
176
|
+
|
|
177
|
+
return results;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Translate an array of strings, batching + validating along the way.
|
|
182
|
+
* @param {object} options
|
|
183
|
+
* @param {string[]} options.strings - source strings
|
|
184
|
+
* @param {string} options.language - target language code (e.g. 'es')
|
|
185
|
+
* @param {string} options.languageName - English name of the target language
|
|
186
|
+
* @param {Function} options.send - provider send ({ system, user }) → { text, usage }
|
|
187
|
+
* @param {string} [options.brand] - brand name to keep untranslated
|
|
188
|
+
* @param {string} [options.extraRules] - extra rule lines for the system prompt
|
|
189
|
+
* @returns {Promise<{ result: string[], usage: { input: number, output: number } }>}
|
|
190
|
+
* result is positionally aligned with options.strings, original whitespace preserved
|
|
191
|
+
*/
|
|
192
|
+
async function translateStrings(options) {
|
|
193
|
+
const { strings, language, languageName, send, brand, extraRules } = options;
|
|
194
|
+
|
|
195
|
+
if (!strings.length) {
|
|
196
|
+
return { result: [], usage: { input: 0, output: 0 } };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const system = SYSTEM_PROMPT
|
|
200
|
+
.split('{brand}').join(brand || 'the brand')
|
|
201
|
+
.replace('{extraRules}', extraRules ? `${extraRules.trim()}\n` : '');
|
|
202
|
+
|
|
203
|
+
const ctx = { send, system, language, languageName };
|
|
204
|
+
const translated = [];
|
|
205
|
+
const usage = { input: 0, output: 0 };
|
|
206
|
+
|
|
207
|
+
// Dedupe before batching (#529): a page sends its title and description once
|
|
208
|
+
// per meta tag, so the same string rode a batch three times over — three
|
|
209
|
+
// times the AI spend, and adjacent twins are exactly what a model merges.
|
|
210
|
+
// Each unique string is translated ONCE and fanned back below.
|
|
211
|
+
const unique = [];
|
|
212
|
+
const position = new Map();
|
|
213
|
+
for (const string of strings) {
|
|
214
|
+
if (!position.has(string)) {
|
|
215
|
+
position.set(string, unique.length);
|
|
216
|
+
unique.push(string);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const batches = [];
|
|
221
|
+
for (let i = 0; i < unique.length; i += BATCH_SIZE) {
|
|
222
|
+
batches.push(unique.slice(i, i + BATCH_SIZE));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Batches fly CONCURRENCY-wide (#604) and are stitched back in BATCH order:
|
|
226
|
+
// the sentinel, the alignment checks, and the #523 split backstop all stay
|
|
227
|
+
// per batch, so what a batch does on its own is exactly what it always did.
|
|
228
|
+
const outcomes = await mapLimited(batches, (batch) => translateBatch(ctx, batch, 0));
|
|
229
|
+
|
|
230
|
+
outcomes.forEach((outcome, i) => {
|
|
231
|
+
translated.push(...outcome.result.map((text, j) => preserveWhitespace(batches[i][j], text)));
|
|
232
|
+
usage.input += outcome.usage?.input || 0;
|
|
233
|
+
usage.output += outcome.usage?.output || 0;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Occurrences are keyed on the exact source string, whitespace included, so
|
|
237
|
+
// every one of them gets back the translation its own text asked for.
|
|
238
|
+
const result = strings.map((string) => translated[position.get(string)]);
|
|
239
|
+
|
|
240
|
+
return { result, usage };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = { translateStrings, preserveWhitespace, CONTROL, BATCH_SIZE, CONCURRENCY };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @omega.js/devkit/translate — the shared OMEGA translation system: language
|
|
3
|
+
* SSOT, provider abstraction (claude via local Claude Code / chatgpt via
|
|
4
|
+
* OpenAI), the batch translation engine, and the committed per-string cache.
|
|
5
|
+
* Config contract (shared `translation` section of omega.json5):
|
|
6
|
+
* { enabled, default, languages, providers: { claude|chatgpt: {} }, model, exclude }
|
|
7
|
+
*/
|
|
8
|
+
const { chosenProvider } = require('../../config/index.js');
|
|
9
|
+
const { LANGUAGE_NAMES, LANGUAGE_LOCALES, RTL_LANGUAGES, isRTL, languageName, ogLocale, assertKnownLanguages } = require('./languages.js');
|
|
10
|
+
const { PROVIDERS, DEFAULT_MODELS, resolveProvider } = require('./providers.js');
|
|
11
|
+
const { translateStrings, preserveWhitespace, CONTROL, BATCH_SIZE, CONCURRENCY } = require('./engine.js');
|
|
12
|
+
const { hashKey, cachePath, loadCache, saveCache } = require('./cache.js');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Read + validate the `translation` section of a resolved config.
|
|
16
|
+
* @param {object} config - resolved omega.json5 config
|
|
17
|
+
* @returns {{ enabled: boolean, default: string, languages: string[], provider: string, model: string|null, exclude: string[] }}
|
|
18
|
+
* enabled is false when the section is off or lists no languages
|
|
19
|
+
*/
|
|
20
|
+
function resolveTranslationSettings(config) {
|
|
21
|
+
const section = config?.translation || {};
|
|
22
|
+
const languages = section.languages || [];
|
|
23
|
+
|
|
24
|
+
assertKnownLanguages(languages);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
enabled: section.enabled !== false && languages.length > 0,
|
|
28
|
+
default: section.default || 'en',
|
|
29
|
+
languages,
|
|
30
|
+
// The engine is a KEY under translation.providers (#425); no block at all
|
|
31
|
+
// means claude, the no-API-key default.
|
|
32
|
+
provider: chosenProvider(section.providers) || 'claude',
|
|
33
|
+
model: section.model || null,
|
|
34
|
+
exclude: section.exclude || [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
// languages
|
|
40
|
+
LANGUAGE_NAMES, LANGUAGE_LOCALES, RTL_LANGUAGES, isRTL, languageName, ogLocale, assertKnownLanguages,
|
|
41
|
+
// providers
|
|
42
|
+
PROVIDERS, DEFAULT_MODELS, resolveProvider,
|
|
43
|
+
// engine
|
|
44
|
+
translateStrings, preserveWhitespace, CONTROL, BATCH_SIZE, CONCURRENCY,
|
|
45
|
+
// cache
|
|
46
|
+
hashKey, cachePath, loadCache, saveCache,
|
|
47
|
+
// config
|
|
48
|
+
resolveTranslationSettings,
|
|
49
|
+
};
|