@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,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The derived output NAMES every assets operation owns — the one home of the
|
|
3
|
+
* naming conventions, read by the writers that produce the files, by the
|
|
4
|
+
* `--reset-assets` cache clear (lib/reset.js), and by the reconcile that
|
|
5
|
+
* deletes what the current sources no longer derive (lib/reconcile.js).
|
|
6
|
+
*
|
|
7
|
+
* It has to be ONE list: a name a writer produces but the reconcile does not
|
|
8
|
+
* derive would be deleted on every walk and regenerated on the next one, so
|
|
9
|
+
* the two views of "what belongs under .omega/assets/" can never be allowed
|
|
10
|
+
* to drift apart.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is relative to `.omega/assets/`; the shapes come from
|
|
13
|
+
* assets-config.js, so a new rule/template/platform is covered without a
|
|
14
|
+
* second registry to update.
|
|
15
|
+
*/
|
|
16
|
+
const { join } = require('node:path');
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
PROCESSING_RULES, SOCIAL_ICON_CONFIG, TEMPLATE_CONFIG, ICON_PLATFORMS, FAVICON_CONFIG,
|
|
20
|
+
} = require('./assets-config.js');
|
|
21
|
+
|
|
22
|
+
/** The two variants every logo source produces: the SVG as-is, and all-black. */
|
|
23
|
+
const LOGO_VARIANTS = ['color', 'black'];
|
|
24
|
+
|
|
25
|
+
/** The webmanifest — derived from config, not from the brandmark, so it is content-diffed. */
|
|
26
|
+
const WEBMANIFEST_NAME = 'site.webmanifest';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One logo variant's files, relative to the rule's output dir.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} rule - A PROCESSING_RULES entry.
|
|
32
|
+
* @param {string} variant - A LOGO_VARIANTS entry.
|
|
33
|
+
* @returns {string[]} The variant SVG plus a PNG per ladder size.
|
|
34
|
+
*/
|
|
35
|
+
function logoVariantFiles(rule, variant) {
|
|
36
|
+
return [`${variant}-x.svg`, ...rule.sizes.map((size) => `${variant}-${size}.png`)];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The social profile icons, relative to SOCIAL_ICON_CONFIG.outputDir.
|
|
41
|
+
*
|
|
42
|
+
* @returns {string[]} The wrapped SVG plus a PNG per size.
|
|
43
|
+
*/
|
|
44
|
+
function socialIconFiles() {
|
|
45
|
+
return ['color-x.svg', ...SOCIAL_ICON_CONFIG.sizes.map((size) => `color-${size}.png`)];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The favicon IMAGES, relative to FAVICON_CONFIG.outputDir — the PNG ladder
|
|
50
|
+
* plus the multi-size .ico. The webmanifest is not here: it derives from
|
|
51
|
+
* config rather than from the brandmark, so it is diffed differently.
|
|
52
|
+
*
|
|
53
|
+
* @returns {string[]} File names.
|
|
54
|
+
*/
|
|
55
|
+
function faviconImageFiles() {
|
|
56
|
+
return [...FAVICON_CONFIG.files.map((file) => file.name), 'favicon.ico'];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One PSD template's exports.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} config - A TEMPLATE_CONFIG entry.
|
|
63
|
+
* @returns {string[]} Paths relative to `.omega/assets/`.
|
|
64
|
+
*/
|
|
65
|
+
function templateExportFiles(config) {
|
|
66
|
+
return (config.exports || [{ suffix: '' }])
|
|
67
|
+
.map((exp) => join(config.outputDir, `${config.outputName}${exp.suffix || ''}.png`));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The platform app icons.
|
|
72
|
+
*
|
|
73
|
+
* @returns {Array<{ platform: string, format: string, file: string }>} `file`
|
|
74
|
+
* is relative to `.omega/assets/`.
|
|
75
|
+
*/
|
|
76
|
+
function appIconFiles() {
|
|
77
|
+
return Object.entries(ICON_PLATFORMS).map(([platform, { format }]) => ({
|
|
78
|
+
platform,
|
|
79
|
+
format,
|
|
80
|
+
file: join('app', platform, `icon.${format}`),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Every directory under `.omega/assets/` an assets operation writes into —
|
|
86
|
+
* the reconcile's output BOUNDARY. Nested entries are dropped (a recursive
|
|
87
|
+
* walk of `social` already covers `social/brandmark`), so each file is
|
|
88
|
+
* visited once.
|
|
89
|
+
*
|
|
90
|
+
* @returns {string[]} Paths relative to `.omega/assets/`.
|
|
91
|
+
*/
|
|
92
|
+
function assetsOutputDirs() {
|
|
93
|
+
const dirs = [...new Set([
|
|
94
|
+
...Object.values(PROCESSING_RULES).map((rule) => rule.outputDir),
|
|
95
|
+
SOCIAL_ICON_CONFIG.outputDir,
|
|
96
|
+
...Object.values(TEMPLATE_CONFIG).map((config) => config.outputDir),
|
|
97
|
+
...Object.keys(ICON_PLATFORMS).map((platform) => join('app', platform)),
|
|
98
|
+
FAVICON_CONFIG.outputDir,
|
|
99
|
+
])];
|
|
100
|
+
|
|
101
|
+
return dirs.filter((dir) => !dirs.some((other) => other !== dir && dir.startsWith(`${other}/`)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
LOGO_VARIANTS,
|
|
106
|
+
WEBMANIFEST_NAME,
|
|
107
|
+
logoVariantFiles,
|
|
108
|
+
socialIconFiles,
|
|
109
|
+
faviconImageFiles,
|
|
110
|
+
templateExportFiles,
|
|
111
|
+
appIconFiles,
|
|
112
|
+
assetsOutputDirs,
|
|
113
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Font loader for wordmark/combomark generation — resolves `brand.font`
|
|
3
|
+
* (a font filename without extension, e.g. `Inter-Bold`) to a parsed
|
|
4
|
+
* opentype.js Font.
|
|
5
|
+
*
|
|
6
|
+
* Search order: the brand repo's own `assets/fonts/` first, then the
|
|
7
|
+
* system font directories. omega-manager instead PACKAGED the company's
|
|
8
|
+
* commercial fonts (CromaSans/NowAlt — licensed, so they can't ship in a
|
|
9
|
+
* published package) and defaulted every brand to CromaSans-ExtraBold via
|
|
10
|
+
* a hardcoded alias table; the port has no packaged fonts and no default —
|
|
11
|
+
* the brand owns its font choice.
|
|
12
|
+
*/
|
|
13
|
+
const { join } = require('node:path');
|
|
14
|
+
const { homedir } = require('node:os');
|
|
15
|
+
const jetpack = require('fs-jetpack');
|
|
16
|
+
const opentype = require('opentype.js');
|
|
17
|
+
|
|
18
|
+
const FONT_EXTENSIONS = ['.otf', '.ttf', '.woff'];
|
|
19
|
+
|
|
20
|
+
const SYSTEM_FONT_DIRS = [
|
|
21
|
+
join(homedir(), 'Library', 'Fonts'),
|
|
22
|
+
'/Library/Fonts',
|
|
23
|
+
'/System/Library/Fonts',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Find a font file by name (without extension).
|
|
28
|
+
*
|
|
29
|
+
* @param {string} name - Font filename, e.g. 'Inter-Bold'
|
|
30
|
+
* @param {string} brandRoot - Brand repo root (its assets/fonts/ is searched first)
|
|
31
|
+
* @returns {string|null} Full path to the font file, or null
|
|
32
|
+
*/
|
|
33
|
+
function findFontFile(name, brandRoot) {
|
|
34
|
+
const dirs = [join(brandRoot, 'assets', 'fonts'), ...SYSTEM_FONT_DIRS];
|
|
35
|
+
|
|
36
|
+
for (const dir of dirs) {
|
|
37
|
+
for (const ext of FONT_EXTENSIONS) {
|
|
38
|
+
const filePath = join(dir, `${name}${ext}`);
|
|
39
|
+
if (jetpack.exists(filePath)) {
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load and parse a font.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} name - Font filename without extension
|
|
52
|
+
* @param {string} brandRoot - Brand repo root
|
|
53
|
+
* @returns {Object|null} opentype.js Font, or null when no file was found
|
|
54
|
+
*/
|
|
55
|
+
function loadFont(name, brandRoot) {
|
|
56
|
+
const fontPath = findFontFile(name, brandRoot);
|
|
57
|
+
if (!fontPath) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const buffer = jetpack.read(fontPath, 'buffer');
|
|
62
|
+
// opentype.parse expects an ArrayBuffer
|
|
63
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
64
|
+
return opentype.parse(arrayBuffer);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { loadFont, findFontFile };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assets reconcile (#636) — every file under `.omega/assets/` derives from the
|
|
3
|
+
* brand's sources and config, so the walk that writes them also DELETES the
|
|
4
|
+
* ones those sources no longer derive. Dropping `assets/logo/wordmark.svg` is
|
|
5
|
+
* a real removal: the wordmark's variants and ladders go with it, not on the
|
|
6
|
+
* next hand-deletion of the cache.
|
|
7
|
+
*
|
|
8
|
+
* Deleting is safe HERE and only here: `.omega/assets/` is the machine-owned,
|
|
9
|
+
* gitignored cache the reset already clears (lib/reset.js), and every file in
|
|
10
|
+
* it is regenerable from the brand's committed sources. The brand's own
|
|
11
|
+
* collateral (`assets/logo/*.svg`, `assets/templates/*.psd`) is a SOURCE and
|
|
12
|
+
* is never touched.
|
|
13
|
+
*
|
|
14
|
+
* Two locks keep it honest: the derived set comes from the SAME names the
|
|
15
|
+
* writers generate (lib/derived.js), and the diff runs only inside the
|
|
16
|
+
* operations' own output dirs — a file the assets service never writes into
|
|
17
|
+
* cannot be reached from here.
|
|
18
|
+
*/
|
|
19
|
+
const { join } = require('node:path');
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const jetpack = require('fs-jetpack');
|
|
22
|
+
|
|
23
|
+
const { PROCESSING_RULES, SOCIAL_ICON_CONFIG, TEMPLATE_CONFIG, FAVICON_CONFIG } = require('./assets-config.js');
|
|
24
|
+
const {
|
|
25
|
+
LOGO_VARIANTS, WEBMANIFEST_NAME, logoVariantFiles, socialIconFiles,
|
|
26
|
+
faviconImageFiles, templateExportFiles, appIconFiles, assetsOutputDirs,
|
|
27
|
+
} = require('./derived.js');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The FULL set of files the brand's current sources derive, exactly as the
|
|
31
|
+
* write operations name them. A logo source or a PSD that isn't in the brand
|
|
32
|
+
* derives nothing — which is what makes a removed source remove its outputs.
|
|
33
|
+
*
|
|
34
|
+
* The brandmark is guaranteed by the service's setup, so the operations that
|
|
35
|
+
* read it alone (social icons, favicons, app icons) always derive their set.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} brandRoot - Absolute brand-monorepo root.
|
|
38
|
+
* @returns {Set<string>} Paths relative to `.omega/assets/`.
|
|
39
|
+
*/
|
|
40
|
+
function derivedAssetFiles(brandRoot) {
|
|
41
|
+
const files = [];
|
|
42
|
+
|
|
43
|
+
for (const rule of Object.values(PROCESSING_RULES)) {
|
|
44
|
+
if (!jetpack.exists(join(brandRoot, rule.source))) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
for (const variant of LOGO_VARIANTS) {
|
|
48
|
+
files.push(...logoVariantFiles(rule, variant).map((name) => join(rule.outputDir, name)));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
files.push(...socialIconFiles().map((name) => join(SOCIAL_ICON_CONFIG.outputDir, name)));
|
|
53
|
+
|
|
54
|
+
for (const [name, config] of Object.entries(TEMPLATE_CONFIG)) {
|
|
55
|
+
// The brand's PSD is the source — a template it does not carry (and the
|
|
56
|
+
// company root could not seed) exports nothing
|
|
57
|
+
if (!jetpack.exists(join(brandRoot, 'assets', 'templates', `${name}.psd`))) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
files.push(...templateExportFiles(config));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
files.push(...appIconFiles().map((entry) => entry.file));
|
|
64
|
+
files.push(...faviconImageFiles().map((name) => join(FAVICON_CONFIG.outputDir, name)));
|
|
65
|
+
files.push(join(FAVICON_CONFIG.outputDir, WEBMANIFEST_NAME));
|
|
66
|
+
|
|
67
|
+
return new Set(files);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Every file under a directory, relative to it.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} dir - Absolute directory.
|
|
74
|
+
* @returns {string[]} Relative paths.
|
|
75
|
+
*/
|
|
76
|
+
function filesUnder(dir) {
|
|
77
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => (entry.isDirectory()
|
|
78
|
+
? filesUnder(join(dir, entry.name)).map((nested) => join(entry.name, nested))
|
|
79
|
+
: [entry.name]));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Delete the files inside the assets operations' output dirs that the brand's
|
|
84
|
+
* current sources no longer derive. Runs AFTER the write operations, so a file
|
|
85
|
+
* this same walk just generated (or a PSD it just seeded) is already derived.
|
|
86
|
+
*
|
|
87
|
+
* @param {object} args
|
|
88
|
+
* @param {string} args.brandRoot - Absolute brand-monorepo root.
|
|
89
|
+
* @param {string} args.outDir - The brand's `.omega/assets/`.
|
|
90
|
+
* @param {boolean} [args.dryRun] - Report what would go, delete nothing.
|
|
91
|
+
* @returns {{ removed: string[] }} The relative paths that were strays (and,
|
|
92
|
+
* outside a dry run, are now gone).
|
|
93
|
+
*/
|
|
94
|
+
function reconcileAssets({ brandRoot, outDir, dryRun = false }) {
|
|
95
|
+
const derived = derivedAssetFiles(brandRoot);
|
|
96
|
+
const removed = [];
|
|
97
|
+
|
|
98
|
+
for (const dir of assetsOutputDirs()) {
|
|
99
|
+
const base = join(outDir, dir);
|
|
100
|
+
|
|
101
|
+
if (jetpack.exists(base) !== 'dir') {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const relative of filesUnder(base)) {
|
|
106
|
+
const path = join(dir, relative);
|
|
107
|
+
|
|
108
|
+
if (derived.has(path)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!dryRun) {
|
|
113
|
+
jetpack.remove(join(base, relative));
|
|
114
|
+
}
|
|
115
|
+
removed.push(path);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { removed: removed.sort() };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { derivedAssetFiles, reconcileAssets };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assets cache invalidation: the force refresh behind `omega manage
|
|
3
|
+
* --reset-assets` (#214). Every assets operation is mtime-diffed, so a
|
|
4
|
+
* converged brand regenerates nothing; when a derived file is wrong for a
|
|
5
|
+
* reason no timestamp records (a hand-mangled export, a sharp/ag-psd
|
|
6
|
+
* upgrade that renders differently), the only lever was hand-deleting
|
|
7
|
+
* `.omega/assets/`. omega-manager had `--reset-templates` /
|
|
8
|
+
* `--reset-logos` for exactly this.
|
|
9
|
+
*
|
|
10
|
+
* Invalidation IS deletion here: `isStale()` calls a missing derived file
|
|
11
|
+
* stale, so removing a kind's outputs is precisely what makes the same walk
|
|
12
|
+
* regenerate them, with no second freshness mechanism to keep in sync.
|
|
13
|
+
*
|
|
14
|
+
* Only `.omega/assets/` is cache. The brand's committed collateral
|
|
15
|
+
* (`assets/logo/*.svg`, `assets/templates/*.psd`) is a SOURCE and is never
|
|
16
|
+
* resettable: a reset that ate the PSD an operator edited by hand would be
|
|
17
|
+
* unrecoverable.
|
|
18
|
+
*
|
|
19
|
+
* The two kinds together are the whole cache: `templates` is the PSD export
|
|
20
|
+
* PNGs, `logos` is everything the logo sources derive (variants, app icon
|
|
21
|
+
* containers, social icons, favicons).
|
|
22
|
+
*/
|
|
23
|
+
const { join } = require('node:path');
|
|
24
|
+
const jetpack = require('fs-jetpack');
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
PROCESSING_RULES, SOCIAL_ICON_CONFIG, TEMPLATE_CONFIG, FAVICON_CONFIG,
|
|
28
|
+
} = require('./assets-config.js');
|
|
29
|
+
const { templateExportFiles, appIconFiles } = require('./derived.js');
|
|
30
|
+
|
|
31
|
+
const RESET_KINDS = ['logos', 'templates'];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The derived paths a kind owns, relative to the out dir. Both lists come
|
|
35
|
+
* from the shared name lib (derived.js) so a new rule/template/platform is
|
|
36
|
+
* covered without a second registry to update.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} kind - A RESET_KINDS entry.
|
|
39
|
+
* @returns {string[]} Relative paths (dirs for whole-output operations,
|
|
40
|
+
* files where one dir holds more than one kind's output).
|
|
41
|
+
*/
|
|
42
|
+
function kindPaths(kind) {
|
|
43
|
+
if (kind === 'templates') {
|
|
44
|
+
// Files, not dirs: `app/macos` also holds the icons operation's .icns
|
|
45
|
+
return Object.values(TEMPLATE_CONFIG).flatMap(templateExportFiles);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return [
|
|
49
|
+
...Object.values(PROCESSING_RULES).map((rule) => rule.outputDir),
|
|
50
|
+
SOCIAL_ICON_CONFIG.outputDir,
|
|
51
|
+
FAVICON_CONFIG.outputDir,
|
|
52
|
+
...appIconFiles().map((entry) => entry.file),
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The kinds a run asked to reset. Mirrors the `--migration` idiom: the bare
|
|
58
|
+
* flag means all of them, a value names one (or a comma list). An unknown
|
|
59
|
+
* kind throws, because a typo that quietly reset everything (or nothing) is
|
|
60
|
+
* worse than a stopped run.
|
|
61
|
+
*
|
|
62
|
+
* @param {boolean|string|undefined} flag - options.resetAssets.
|
|
63
|
+
* @returns {string[]} Kinds in RESET_KINDS order, deduped.
|
|
64
|
+
*/
|
|
65
|
+
function resolveResetKinds(flag) {
|
|
66
|
+
if (!flag) {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (flag === true) {
|
|
71
|
+
return [...RESET_KINDS];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const asked = String(flag).split(',').map((kind) => kind.trim()).filter(Boolean);
|
|
75
|
+
const unknown = asked.filter((kind) => !RESET_KINDS.includes(kind));
|
|
76
|
+
|
|
77
|
+
if (unknown.length > 0) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Unknown --reset-assets kind(s): ${unknown.join(', ')}. `
|
|
80
|
+
+ `Available: ${RESET_KINDS.join(', ')} (bare --reset-assets resets both).`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return RESET_KINDS.filter((kind) => asked.includes(kind));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Clear the derived outputs of the given kinds.
|
|
89
|
+
*
|
|
90
|
+
* @param {object} args
|
|
91
|
+
* @param {string} args.outDir - The brand's `.omega/assets/`.
|
|
92
|
+
* @param {string[]} args.kinds - Kinds from resolveResetKinds().
|
|
93
|
+
* @param {boolean} [args.dryRun] - Report what would go, delete nothing.
|
|
94
|
+
* @returns {{ kinds: string[], removed: string[] }} The relative paths that
|
|
95
|
+
* existed (and, outside a dry run, are now gone).
|
|
96
|
+
*/
|
|
97
|
+
function resetAssetsCache({ outDir, kinds, dryRun = false }) {
|
|
98
|
+
const removed = [];
|
|
99
|
+
|
|
100
|
+
for (const kind of kinds) {
|
|
101
|
+
for (const relative of kindPaths(kind)) {
|
|
102
|
+
const target = join(outDir, relative);
|
|
103
|
+
|
|
104
|
+
if (!jetpack.exists(target)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!dryRun) {
|
|
109
|
+
jetpack.remove(target);
|
|
110
|
+
}
|
|
111
|
+
removed.push(relative);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { kinds, removed };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { RESET_KINDS, resolveResetKinds, resetAssetsCache };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG logo generator — wordmark and combomark SVGs via opentype.js
|
|
3
|
+
* text-to-path conversion. All text is converted to path outlines so the
|
|
4
|
+
* SVGs are self-contained (no font dependency at render time).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate a wordmark SVG (text-only logo).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} brandName - The brand name text
|
|
11
|
+
* @param {Object} font - opentype.js Font object
|
|
12
|
+
* @returns {string} SVG markup
|
|
13
|
+
*/
|
|
14
|
+
function generateWordmark(brandName, font) {
|
|
15
|
+
const fontSize = 72;
|
|
16
|
+
const fill = '#000000';
|
|
17
|
+
|
|
18
|
+
const path = font.getPath(brandName, 0, 0, fontSize);
|
|
19
|
+
const bbox = path.getBoundingBox();
|
|
20
|
+
|
|
21
|
+
const width = bbox.x2 - bbox.x1;
|
|
22
|
+
const height = bbox.y2 - bbox.y1;
|
|
23
|
+
|
|
24
|
+
// Translate path so it starts at origin
|
|
25
|
+
const translateX = -bbox.x1;
|
|
26
|
+
const translateY = -bbox.y1;
|
|
27
|
+
|
|
28
|
+
const pathData = path.toPathData(4);
|
|
29
|
+
|
|
30
|
+
return buildSvg(width, height, [
|
|
31
|
+
`<path d="${pathData}" fill="${fill}" transform="translate(${round(translateX)}, ${round(translateY)})"/>`,
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a combomark SVG (brandmark left, text right).
|
|
37
|
+
*
|
|
38
|
+
* @param {string} brandName - The brand name text
|
|
39
|
+
* @param {string} brandmarkSvg - Raw SVG content of the brandmark
|
|
40
|
+
* @param {Object} font - opentype.js Font object
|
|
41
|
+
* @returns {string} SVG markup
|
|
42
|
+
*/
|
|
43
|
+
function generateCombomark(brandName, brandmarkSvg, font) {
|
|
44
|
+
const fontSize = 72;
|
|
45
|
+
const fill = '#000000';
|
|
46
|
+
const gapRatio = 0.3; // Gap between brandmark and text as fraction of brandmark height
|
|
47
|
+
|
|
48
|
+
const textPath = font.getPath(brandName, 0, 0, fontSize);
|
|
49
|
+
const textBbox = textPath.getBoundingBox();
|
|
50
|
+
const textWidth = textBbox.x2 - textBbox.x1;
|
|
51
|
+
const textHeight = textBbox.y2 - textBbox.y1;
|
|
52
|
+
|
|
53
|
+
const viewBox = parseBrandmarkViewBox(brandmarkSvg);
|
|
54
|
+
const brandmarkAspect = viewBox.width / viewBox.height;
|
|
55
|
+
|
|
56
|
+
// Scale brandmark to match text height
|
|
57
|
+
const brandmarkHeight = textHeight;
|
|
58
|
+
const brandmarkWidth = brandmarkHeight * brandmarkAspect;
|
|
59
|
+
|
|
60
|
+
// Layout: [brandmark] [gap] [text]
|
|
61
|
+
const gapWidth = brandmarkHeight * gapRatio;
|
|
62
|
+
const totalWidth = brandmarkWidth + gapWidth + textWidth;
|
|
63
|
+
const totalHeight = Math.max(brandmarkHeight, textHeight);
|
|
64
|
+
|
|
65
|
+
const brandmarkX = 0;
|
|
66
|
+
const brandmarkY = (totalHeight - brandmarkHeight) / 2;
|
|
67
|
+
|
|
68
|
+
const textOffsetX = brandmarkWidth + gapWidth - textBbox.x1;
|
|
69
|
+
const textOffsetY = (totalHeight - textHeight) / 2 - textBbox.y1;
|
|
70
|
+
|
|
71
|
+
const textPathData = textPath.toPathData(4);
|
|
72
|
+
|
|
73
|
+
const brandmarkInner = extractSvgInner(brandmarkSvg);
|
|
74
|
+
const viewBoxStr = `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`;
|
|
75
|
+
|
|
76
|
+
return buildSvg(totalWidth, totalHeight, [
|
|
77
|
+
// Nested <svg> preserves the brandmark's viewBox so the renderer handles
|
|
78
|
+
// content scaling and centering automatically (no manual transform needed)
|
|
79
|
+
`<svg x="${round(brandmarkX)}" y="${round(brandmarkY)}" width="${round(brandmarkWidth)}" height="${round(brandmarkHeight)}" viewBox="${viewBoxStr}">`,
|
|
80
|
+
...brandmarkInner.map((line) => ` ${line}`),
|
|
81
|
+
`</svg>`,
|
|
82
|
+
`<path d="${textPathData}" fill="${fill}" transform="translate(${round(textOffsetX)}, ${round(textOffsetY)})"/>`,
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build a clean, minimal SVG wrapper.
|
|
88
|
+
*/
|
|
89
|
+
function buildSvg(width, height, innerContent) {
|
|
90
|
+
const w = round(width);
|
|
91
|
+
const h = round(height);
|
|
92
|
+
|
|
93
|
+
return [
|
|
94
|
+
`<?xml version="1.0" encoding="UTF-8"?>`,
|
|
95
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="100%" height="100%">`,
|
|
96
|
+
...innerContent.map((line) => ` ${line}`),
|
|
97
|
+
`</svg>`,
|
|
98
|
+
``,
|
|
99
|
+
].join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Parse brandmark SVG viewBox dimensions.
|
|
104
|
+
*/
|
|
105
|
+
function parseBrandmarkViewBox(svgContent) {
|
|
106
|
+
const match = svgContent.match(/viewBox="([^"]+)"/);
|
|
107
|
+
if (!match) {
|
|
108
|
+
return { x: 0, y: 0, width: 100, height: 100 };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const [x, y, width, height] = match[1].split(/\s+/).map(Number);
|
|
112
|
+
return { x, y, width, height };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Extract inner content from an SVG (everything between the opening <svg>
|
|
117
|
+
* and closing </svg>). Preserves <g> groups, transforms, and all element
|
|
118
|
+
* types; strips comments and vendor-specific attributes.
|
|
119
|
+
*/
|
|
120
|
+
function extractSvgInner(svgContent) {
|
|
121
|
+
const openMatch = svgContent.match(/<svg\s[^>]*>/);
|
|
122
|
+
if (!openMatch) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const innerStart = openMatch.index + openMatch[0].length;
|
|
127
|
+
const innerEnd = svgContent.lastIndexOf('</svg>');
|
|
128
|
+
if (innerEnd <= innerStart) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let inner = svgContent.slice(innerStart, innerEnd);
|
|
133
|
+
|
|
134
|
+
inner = inner.replace(/<!--[\s\S]*?-->/g, '');
|
|
135
|
+
inner = inner.replace(/<defs\s*\/>/g, '');
|
|
136
|
+
inner = inner.replace(/\s+vectornator:\w+="[^"]*"/g, '');
|
|
137
|
+
inner = inner.replace(/\s+xmlns:\w+="[^"]*"/g, '');
|
|
138
|
+
inner = inner.replace(/\s+xml:space="[^"]*"/g, '');
|
|
139
|
+
inner = inner.replace(/\s+opacity="1"/g, '');
|
|
140
|
+
inner = inner.replace(/\s+stroke="none"/g, '');
|
|
141
|
+
|
|
142
|
+
return inner.split('\n')
|
|
143
|
+
.map((line) => line.trim())
|
|
144
|
+
.filter((line) => line.length > 0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Round to 2 decimal places (for positions/dimensions).
|
|
149
|
+
*/
|
|
150
|
+
function round(n) {
|
|
151
|
+
return Math.round(n * 100) / 100;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = { generateWordmark, generateCombomark };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert an SVG to a pure black version — the source for the `black-*`
|
|
3
|
+
* logo variants (and macOS template images, which must be pure black with
|
|
4
|
+
* alpha so the system can invert them in dark mode).
|
|
5
|
+
*
|
|
6
|
+
* Replaces all fills and strokes with #000000:
|
|
7
|
+
* - Removes <defs> blocks (gradients, filters, patterns)
|
|
8
|
+
* - Converts fill="..." / stroke="..." attributes (fill="none" preserved)
|
|
9
|
+
* - Converts fill:... / stroke:... in style attributes (none preserved)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} svgContent - Raw SVG string
|
|
14
|
+
* @returns {string} SVG with all colors replaced by black
|
|
15
|
+
*/
|
|
16
|
+
function convertSvgToBlack(svgContent) {
|
|
17
|
+
let svg = svgContent;
|
|
18
|
+
|
|
19
|
+
svg = svg.replace(/<defs>[\s\S]*?<\/defs>/gi, '');
|
|
20
|
+
svg = svg.replace(/<defs\s*\/>/gi, '');
|
|
21
|
+
|
|
22
|
+
svg = svg.replace(/\bfill="(?!none)[^"]*"/g, 'fill="#000000"');
|
|
23
|
+
svg = svg.replace(/\bstroke="(?!none)[^"]*"/g, 'stroke="#000000"');
|
|
24
|
+
|
|
25
|
+
// fill:url(#...), fill:#hex, fill:rgb(...), fill:colorname inside style="..."
|
|
26
|
+
svg = svg.replace(/\bfill\s*:\s*(?!none)[^;}"]+/g, 'fill:#000000');
|
|
27
|
+
svg = svg.replace(/\bstroke\s*:\s*(?!none)[^;}"]+/g, 'stroke:#000000');
|
|
28
|
+
|
|
29
|
+
return svg;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { convertSvgToBlack };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the web favicon set from the brandmark — the PNG ladder
|
|
3
|
+
* (favicon/apple-touch/android-chrome), a multi-size favicon.ico, and
|
|
4
|
+
* site.webmanifest — into `.omega/assets/favicon/`. Images are
|
|
5
|
+
* mtime-diffed against the brandmark; the webmanifest derives from
|
|
6
|
+
* config, so it's content-diffed instead.
|
|
7
|
+
*/
|
|
8
|
+
const { join } = require('node:path');
|
|
9
|
+
const chalk = require('chalk').default;
|
|
10
|
+
const jetpack = require('fs-jetpack');
|
|
11
|
+
const sharp = require('sharp');
|
|
12
|
+
const png2icons = require('png2icons');
|
|
13
|
+
const { FAVICON_CONFIG } = require('../lib/assets-config.js');
|
|
14
|
+
const { faviconImageFiles, WEBMANIFEST_NAME } = require('../lib/derived.js');
|
|
15
|
+
const { isStale } = require('../../../lib/stale.js');
|
|
16
|
+
|
|
17
|
+
module.exports = async function writeFavicons(context) {
|
|
18
|
+
const { brandConfig, brandmarkPath, outDir, options } = context;
|
|
19
|
+
const dryRun = options?.dryRun || false;
|
|
20
|
+
|
|
21
|
+
const outputDir = join(outDir, FAVICON_CONFIG.outputDir);
|
|
22
|
+
|
|
23
|
+
const staleImages = faviconImageFiles().filter((name) => isStale(brandmarkPath, join(outputDir, name)));
|
|
24
|
+
|
|
25
|
+
const brandName = brandConfig.brand.name;
|
|
26
|
+
const desiredManifest = JSON.stringify({
|
|
27
|
+
name: brandName,
|
|
28
|
+
short_name: brandName,
|
|
29
|
+
icons: [
|
|
30
|
+
{ src: '/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' },
|
|
31
|
+
{ src: '/android-chrome-512x512.png', sizes: '512x512', type: 'image/png' },
|
|
32
|
+
],
|
|
33
|
+
theme_color: '#ffffff',
|
|
34
|
+
background_color: '#ffffff',
|
|
35
|
+
display: 'standalone',
|
|
36
|
+
}, null, 2);
|
|
37
|
+
|
|
38
|
+
const manifestPath = join(outputDir, WEBMANIFEST_NAME);
|
|
39
|
+
const manifestStale = jetpack.read(manifestPath) !== desiredManifest;
|
|
40
|
+
|
|
41
|
+
if (staleImages.length === 0 && !manifestStale) {
|
|
42
|
+
console.log(` ${chalk.green('✓')} Favicons in sync ${chalk.dim(`(${FAVICON_CONFIG.files.length + 2} files fresh)`)}`);
|
|
43
|
+
return { output: { favicons: { synced: true } } };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (dryRun) {
|
|
47
|
+
const count = staleImages.length + (manifestStale ? 1 : 0);
|
|
48
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would generate ${chalk.cyan(count)} favicon files`);
|
|
49
|
+
return { output: { favicons: { planned: count } } };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
jetpack.dir(outputDir);
|
|
53
|
+
let generated = 0;
|
|
54
|
+
|
|
55
|
+
for (const file of FAVICON_CONFIG.files) {
|
|
56
|
+
if (!staleImages.includes(file.name)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
await sharp(brandmarkPath)
|
|
60
|
+
.resize(file.size, file.size, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
61
|
+
.png()
|
|
62
|
+
.toFile(join(outputDir, file.name));
|
|
63
|
+
generated++;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (staleImages.includes('favicon.ico')) {
|
|
67
|
+
const sourcePng = await sharp(brandmarkPath)
|
|
68
|
+
.resize(512, 512, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
69
|
+
.png()
|
|
70
|
+
.toBuffer();
|
|
71
|
+
jetpack.write(join(outputDir, 'favicon.ico'), png2icons.createICO(sourcePng, png2icons.BICUBIC, 0, true));
|
|
72
|
+
generated++;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (manifestStale) {
|
|
76
|
+
jetpack.write(manifestPath, desiredManifest);
|
|
77
|
+
generated++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(` ${chalk.green('✓')} Generated ${chalk.bold(generated)} favicon files`);
|
|
81
|
+
return { output: { favicons: { generated } } };
|
|
82
|
+
};
|