@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,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate the platform app-icon files — macOS `.icns` and Windows
|
|
3
|
+
* `.ico` — into `.omega/assets/app/{platform}/`. The source is the
|
|
4
|
+
* composited `icon.png` in that directory when present (produced by the
|
|
5
|
+
* templates operation, which runs just before), else the brandmark SVG.
|
|
6
|
+
* mtime-diffed against the chosen source.
|
|
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 { appIconFiles } = require('../lib/derived.js');
|
|
14
|
+
const { isStale } = require('../../../lib/stale.js');
|
|
15
|
+
|
|
16
|
+
module.exports = async function writeIcons(context) {
|
|
17
|
+
const { brandmarkPath, outDir, options } = context;
|
|
18
|
+
const dryRun = options?.dryRun || false;
|
|
19
|
+
|
|
20
|
+
let generated = 0;
|
|
21
|
+
let fresh = 0;
|
|
22
|
+
const planned = [];
|
|
23
|
+
|
|
24
|
+
for (const { platform, format, file } of appIconFiles()) {
|
|
25
|
+
const compositedPath = join(outDir, 'app', platform, 'icon.png');
|
|
26
|
+
const sourcePath = jetpack.exists(compositedPath) ? compositedPath : brandmarkPath;
|
|
27
|
+
const iconPath = join(outDir, file);
|
|
28
|
+
|
|
29
|
+
if (!isStale(sourcePath, iconPath)) {
|
|
30
|
+
fresh++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (dryRun) {
|
|
35
|
+
planned.push(`${platform}/icon.${format}`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const sourcePng = await sharp(sourcePath)
|
|
40
|
+
.resize(1024, 1024, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
41
|
+
.png()
|
|
42
|
+
.toBuffer();
|
|
43
|
+
|
|
44
|
+
const buffer = format === 'icns'
|
|
45
|
+
? png2icons.createICNS(sourcePng, png2icons.BICUBIC, 0)
|
|
46
|
+
: png2icons.createICO(sourcePng, png2icons.BICUBIC, 0, true);
|
|
47
|
+
|
|
48
|
+
jetpack.write(iconPath, buffer);
|
|
49
|
+
console.log(` ${chalk.green('✓')} Generated ${chalk.cyan(`${platform}/icon.${format}`)}`);
|
|
50
|
+
generated++;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (dryRun && planned.length > 0) {
|
|
54
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would generate ${planned.map((name) => chalk.cyan(name)).join(' + ')}`);
|
|
55
|
+
return { output: { icons: { planned: planned.length } } };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (generated === 0) {
|
|
59
|
+
console.log(` ${chalk.green('✓')} App icons in sync ${chalk.dim(`(${fresh} fresh)`)}`);
|
|
60
|
+
return { output: { icons: { synced: true } } };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { output: { icons: { generated } } };
|
|
64
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process logo sources into their derived variants — for each of
|
|
3
|
+
* brandmark/wordmark/combomark that exists: the color SVG as-is + an
|
|
4
|
+
* all-black SVG, and PNGs at every ladder size for both. mtime-diffed
|
|
5
|
+
* against the source, so an unchanged logo regenerates nothing.
|
|
6
|
+
*/
|
|
7
|
+
const { join } = require('node:path');
|
|
8
|
+
const chalk = require('chalk').default;
|
|
9
|
+
const jetpack = require('fs-jetpack');
|
|
10
|
+
const sharp = require('sharp');
|
|
11
|
+
const { PROCESSING_RULES } = require('../lib/assets-config.js');
|
|
12
|
+
const { logoVariantFiles } = require('../lib/derived.js');
|
|
13
|
+
const { convertSvgToBlack } = require('../lib/svg-to-black.js');
|
|
14
|
+
const { isStale } = require('../../../lib/stale.js');
|
|
15
|
+
|
|
16
|
+
module.exports = async function writeProcess(context) {
|
|
17
|
+
const { brandRoot, outDir, options } = context;
|
|
18
|
+
const dryRun = options?.dryRun || false;
|
|
19
|
+
|
|
20
|
+
let generated = 0;
|
|
21
|
+
let fresh = 0;
|
|
22
|
+
const planned = [];
|
|
23
|
+
|
|
24
|
+
for (const [key, rule] of Object.entries(PROCESSING_RULES)) {
|
|
25
|
+
const sourcePath = join(brandRoot, rule.source);
|
|
26
|
+
|
|
27
|
+
if (!jetpack.exists(sourcePath)) {
|
|
28
|
+
// Only the brandmark is guaranteed by setup — wordmark/combomark are optional
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const outputDir = join(outDir, rule.outputDir);
|
|
33
|
+
const resize = rule.square
|
|
34
|
+
? (size) => ({ width: size, height: size, fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
35
|
+
: (size) => ({ width: size, fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } });
|
|
36
|
+
|
|
37
|
+
// Lazy so a fully fresh source never reads or converts anything
|
|
38
|
+
let svgContent = null;
|
|
39
|
+
let ruleGenerated = 0;
|
|
40
|
+
const variants = [
|
|
41
|
+
{ name: 'color', svg: () => svgContent },
|
|
42
|
+
{ name: 'black', svg: () => convertSvgToBlack(svgContent) },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
for (const variant of variants) {
|
|
46
|
+
const targets = logoVariantFiles(rule, variant.name)
|
|
47
|
+
.filter((name) => isStale(sourcePath, join(outputDir, name)));
|
|
48
|
+
|
|
49
|
+
if (targets.length === 0) {
|
|
50
|
+
fresh += 1 + rule.sizes.length;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fresh += 1 + rule.sizes.length - targets.length;
|
|
55
|
+
|
|
56
|
+
if (dryRun) {
|
|
57
|
+
planned.push(...targets.map((name) => `${rule.outputDir}/${name}`));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
svgContent = svgContent ?? jetpack.read(sourcePath);
|
|
62
|
+
const variantSvg = variant.svg();
|
|
63
|
+
const svgBuffer = Buffer.from(variantSvg);
|
|
64
|
+
|
|
65
|
+
for (const name of targets) {
|
|
66
|
+
const outputPath = join(outputDir, name);
|
|
67
|
+
if (name.endsWith('.svg')) {
|
|
68
|
+
jetpack.write(outputPath, variantSvg);
|
|
69
|
+
} else {
|
|
70
|
+
const size = parseInt(name.match(/-(\d+)\.png$/)[1], 10);
|
|
71
|
+
jetpack.dir(outputDir);
|
|
72
|
+
await sharp(svgBuffer).resize(resize(size)).png().toFile(outputPath);
|
|
73
|
+
}
|
|
74
|
+
generated++;
|
|
75
|
+
ruleGenerated++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!dryRun && ruleGenerated > 0) {
|
|
80
|
+
console.log(` ${chalk.green('✓')} Processed ${chalk.cyan(key)} ${chalk.dim(`(${ruleGenerated} files)`)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (dryRun && planned.length > 0) {
|
|
85
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would generate ${chalk.cyan(planned.length)} logo files`);
|
|
86
|
+
return { output: { process: { planned: planned.length } } };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (generated === 0) {
|
|
90
|
+
console.log(` ${chalk.green('✓')} Logo variants in sync ${chalk.dim(`(${fresh} files fresh)`)}`);
|
|
91
|
+
return { output: { process: { synced: true, fresh } } };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(` ${chalk.green('✓')} Summary: ${chalk.bold(generated)} logo files generated`);
|
|
95
|
+
return { output: { process: { generated, fresh } } };
|
|
96
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete the derived files the brand's current sources no longer name (#636).
|
|
3
|
+
* Last operation in the service on purpose: everything this walk generated is
|
|
4
|
+
* already on disk, so what remains inside the operations' output dirs and is
|
|
5
|
+
* not in the derived set is a leftover of an older shape — a dropped logo
|
|
6
|
+
* source's ladders, a removed PSD's exports, a hand-dropped stray.
|
|
7
|
+
*/
|
|
8
|
+
const chalk = require('chalk').default;
|
|
9
|
+
|
|
10
|
+
const { reconcileAssets } = require('../lib/reconcile.js');
|
|
11
|
+
|
|
12
|
+
module.exports = async function writeReconcile(context) {
|
|
13
|
+
const { brandRoot, outDir, options } = context;
|
|
14
|
+
const dryRun = options?.dryRun || false;
|
|
15
|
+
|
|
16
|
+
const { removed } = reconcileAssets({ brandRoot, outDir, dryRun });
|
|
17
|
+
|
|
18
|
+
if (removed.length === 0) {
|
|
19
|
+
console.log(` ${chalk.green('✓')} No leftovers ${chalk.dim('(.omega/assets/ matches the brand\'s sources)')}`);
|
|
20
|
+
return { output: { reconcile: { removed: 0 } } };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (dryRun) {
|
|
24
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would remove ${chalk.cyan(removed.length)} file(s) the sources no longer derive: ${removed.join(', ')}`);
|
|
25
|
+
return { output: { reconcile: { planned: removed.length } } };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(` ${chalk.yellow('↺')} Removed ${chalk.bold(removed.length)} file(s) the sources no longer derive ${chalk.dim(`(${removed.join(', ')})`)}`);
|
|
29
|
+
return { output: { reconcile: { removed: removed.length } } };
|
|
30
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate social profile icons — the square brandmark centered on a
|
|
3
|
+
* white background with padding, for profile pictures (YouTube, Twitter,
|
|
4
|
+
* …) — as a wrapped SVG + PNGs into `.omega/assets/social/brandmark/`.
|
|
5
|
+
* mtime-diffed against the brandmark.
|
|
6
|
+
*/
|
|
7
|
+
const { join } = require('node:path');
|
|
8
|
+
const chalk = require('chalk').default;
|
|
9
|
+
const jetpack = require('fs-jetpack');
|
|
10
|
+
const sharp = require('sharp');
|
|
11
|
+
const { SOCIAL_ICON_CONFIG } = require('../lib/assets-config.js');
|
|
12
|
+
const { socialIconFiles } = require('../lib/derived.js');
|
|
13
|
+
const { isStale } = require('../../../lib/stale.js');
|
|
14
|
+
|
|
15
|
+
module.exports = async function writeSocialIcons(context) {
|
|
16
|
+
const { brandmarkPath, outDir, options } = context;
|
|
17
|
+
const dryRun = options?.dryRun || false;
|
|
18
|
+
|
|
19
|
+
const { outputDir: subDir, sizes, padding, background } = SOCIAL_ICON_CONFIG;
|
|
20
|
+
const outputDir = join(outDir, subDir);
|
|
21
|
+
|
|
22
|
+
const targets = socialIconFiles().filter((name) => isStale(brandmarkPath, join(outputDir, name)));
|
|
23
|
+
|
|
24
|
+
if (targets.length === 0) {
|
|
25
|
+
console.log(` ${chalk.green('✓')} Social icons in sync ${chalk.dim(`(${1 + sizes.length} files fresh)`)}`);
|
|
26
|
+
return { output: { socialIcons: { synced: true } } };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (dryRun) {
|
|
30
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would generate ${chalk.cyan(targets.length)} social icon files`);
|
|
31
|
+
return { output: { socialIcons: { planned: targets.length } } };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const svgContent = jetpack.read(brandmarkPath);
|
|
35
|
+
let generated = 0;
|
|
36
|
+
|
|
37
|
+
for (const name of targets) {
|
|
38
|
+
const outputPath = join(outputDir, name);
|
|
39
|
+
|
|
40
|
+
if (name.endsWith('.svg')) {
|
|
41
|
+
jetpack.write(outputPath, wrapSvgWithBackground(svgContent, padding, background));
|
|
42
|
+
generated++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const size = parseInt(name.match(/-(\d+)\.png$/)[1], 10);
|
|
47
|
+
const logoSize = Math.round(size * (1 - padding * 2));
|
|
48
|
+
const offset = Math.round(size * padding);
|
|
49
|
+
|
|
50
|
+
const logoPng = await sharp(Buffer.from(svgContent))
|
|
51
|
+
.resize(logoSize, logoSize, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
52
|
+
.png()
|
|
53
|
+
.toBuffer();
|
|
54
|
+
|
|
55
|
+
jetpack.dir(outputDir);
|
|
56
|
+
await sharp({ create: { width: size, height: size, channels: 4, background } })
|
|
57
|
+
.composite([{ input: logoPng, left: offset, top: offset }])
|
|
58
|
+
.png()
|
|
59
|
+
.toFile(outputPath);
|
|
60
|
+
generated++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(` ${chalk.green('✓')} Generated ${chalk.bold(generated)} social icon files`);
|
|
64
|
+
return { output: { socialIcons: { generated } } };
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Wrap an SVG's content in a padded 1024×1024 canvas with a background
|
|
69
|
+
* rect, preserving aspect ratio.
|
|
70
|
+
*/
|
|
71
|
+
function wrapSvgWithBackground(svgContent, paddingRatio, background) {
|
|
72
|
+
const viewBoxMatch = svgContent.match(/viewBox="([^"]+)"/);
|
|
73
|
+
const widthMatch = svgContent.match(/<svg[^>]*\swidth="([^"]+)"/);
|
|
74
|
+
const heightMatch = svgContent.match(/<svg[^>]*\sheight="([^"]+)"/);
|
|
75
|
+
|
|
76
|
+
let srcWidth, srcHeight;
|
|
77
|
+
if (viewBoxMatch) {
|
|
78
|
+
const parts = viewBoxMatch[1].split(/\s+/);
|
|
79
|
+
srcWidth = parseFloat(parts[2]);
|
|
80
|
+
srcHeight = parseFloat(parts[3]);
|
|
81
|
+
} else {
|
|
82
|
+
srcWidth = parseFloat(widthMatch?.[1]) || 100;
|
|
83
|
+
srcHeight = parseFloat(heightMatch?.[1]) || 100;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const canvasSize = 1024;
|
|
87
|
+
const pad = canvasSize * paddingRatio;
|
|
88
|
+
const availableSize = canvasSize - pad * 2;
|
|
89
|
+
|
|
90
|
+
const scale = Math.min(availableSize / srcWidth, availableSize / srcHeight);
|
|
91
|
+
const scaledWidth = srcWidth * scale;
|
|
92
|
+
const scaledHeight = srcHeight * scale;
|
|
93
|
+
const offsetX = pad + (availableSize - scaledWidth) / 2;
|
|
94
|
+
const offsetY = pad + (availableSize - scaledHeight) / 2;
|
|
95
|
+
|
|
96
|
+
const innerContent = svgContent
|
|
97
|
+
.replace(/<svg[^>]*>/, '')
|
|
98
|
+
.replace(/<\/svg>\s*$/, '');
|
|
99
|
+
|
|
100
|
+
const bgHex = `#${[background.r, background.g, background.b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;
|
|
101
|
+
|
|
102
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${canvasSize} ${canvasSize}" width="${canvasSize}" height="${canvasSize}">
|
|
103
|
+
<rect width="${canvasSize}" height="${canvasSize}" fill="${bgHex}"/>
|
|
104
|
+
<g transform="translate(${offsetX}, ${offsetY}) scale(${scale})">
|
|
105
|
+
${innerContent.trim()}
|
|
106
|
+
</g>
|
|
107
|
+
</svg>`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PSD templates — seed the brand's editable Photoshop sources and keep
|
|
3
|
+
* their derived PNGs current.
|
|
4
|
+
*
|
|
5
|
+
* Per TEMPLATE_CONFIG entry:
|
|
6
|
+
* 1. `assets/templates/{name}.psd` missing in the brand repo → seed it
|
|
7
|
+
* from the company root's `assets/templates/` (company-managed
|
|
8
|
+
* brands only — the marker locates the company; omega-manager baked
|
|
9
|
+
* ITW's binaries into its own src/defaults/ instead)
|
|
10
|
+
* 2. When the PSD or the brandmark is newer than the exports: replace
|
|
11
|
+
* the logo layers (brandmark rendered via sharp, black variant for
|
|
12
|
+
* tray templates) and text layers (brandConfig values, font shrunk
|
|
13
|
+
* to fit), write the PSD back, composite all visible layers, and
|
|
14
|
+
* export the PNGs into `.omega/assets/{outputDir}/`
|
|
15
|
+
*
|
|
16
|
+
* Manual Photoshop edits persist — editing the PSD bumps its mtime, so
|
|
17
|
+
* the next run re-replaces the logo/text layers and re-exports. Note
|
|
18
|
+
* that PSDs store each layer's raster: replaced TEXT content lands in
|
|
19
|
+
* the layer metadata (Photoshop re-renders it on open), so a text change
|
|
20
|
+
* appears in the exports after the PSD is opened + saved once. The logo
|
|
21
|
+
* layers are re-rastered here, so they're always current. Converged
|
|
22
|
+
* brands are zero-work (mtime-diffed like every assets operation).
|
|
23
|
+
*/
|
|
24
|
+
const { join } = require('node:path');
|
|
25
|
+
const chalk = require('chalk').default;
|
|
26
|
+
const jetpack = require('fs-jetpack');
|
|
27
|
+
const sharp = require('sharp');
|
|
28
|
+
const { readPsd, writePsdBuffer, initializeCanvas } = require('ag-psd');
|
|
29
|
+
const canvasModule = require('canvas');
|
|
30
|
+
const { TEMPLATE_CONFIG } = require('../lib/assets-config.js');
|
|
31
|
+
const { templateExportFiles } = require('../lib/derived.js');
|
|
32
|
+
const { convertSvgToBlack } = require('../lib/svg-to-black.js');
|
|
33
|
+
const { readCompanyMarker } = require('../../../lib/company.js');
|
|
34
|
+
const { isStale } = require('../../../lib/stale.js');
|
|
35
|
+
|
|
36
|
+
const { createCanvas, loadImage } = canvasModule;
|
|
37
|
+
initializeCanvas(createCanvas);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Find a layer by slash-delimited path (e.g. 'Main/Logo/Logo') — walks
|
|
41
|
+
* folders by name, falling back to the first child on the last segment.
|
|
42
|
+
*/
|
|
43
|
+
function findLayerByPath(psd, layerPath) {
|
|
44
|
+
const segments = layerPath.split('/');
|
|
45
|
+
let current = psd;
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < segments.length; i++) {
|
|
48
|
+
if (!current.children) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const segment = segments[i];
|
|
53
|
+
const isLast = i === segments.length - 1;
|
|
54
|
+
|
|
55
|
+
const found = current.children.find((l) => l.name === segment);
|
|
56
|
+
if (found) {
|
|
57
|
+
current = found;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Last segment: fall back to first child of current folder
|
|
62
|
+
if (isLast && current.children.length > 0) {
|
|
63
|
+
current = current.children[0];
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return current;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get a nested value from an object using dot notation (e.g. 'brand.name').
|
|
75
|
+
*/
|
|
76
|
+
function getNestedValue(obj, path) {
|
|
77
|
+
return path.split('.').reduce((curr, key) => curr?.[key], obj);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Recursively composite all visible layers onto a canvas context — first
|
|
82
|
+
* layer = bottom, last = top (Photoshop draw order).
|
|
83
|
+
*/
|
|
84
|
+
function compositeLayers(ctx, layers) {
|
|
85
|
+
if (!layers) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const layer of layers) {
|
|
90
|
+
if (layer.hidden) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (layer.children) {
|
|
95
|
+
compositeLayers(ctx, layer.children);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!layer.canvas) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ctx.drawImage(layer.canvas, layer.left || 0, layer.top || 0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Scale a text layer's font down until the text fits maxWidthPx (canvas
|
|
109
|
+
* measurement). Updates the default style and all styleRuns
|
|
110
|
+
* proportionally. Returns { from, to } when scaled, null otherwise.
|
|
111
|
+
*/
|
|
112
|
+
function fitTextFontSize(textData, maxWidthPx) {
|
|
113
|
+
const originalSize = textData.style?.fontSize
|
|
114
|
+
|| textData.styleRuns?.[0]?.style?.fontSize;
|
|
115
|
+
|
|
116
|
+
if (!originalSize) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const fontName = textData.style?.font?.name
|
|
121
|
+
|| textData.styleRuns?.[0]?.style?.font?.name
|
|
122
|
+
|| 'Arial';
|
|
123
|
+
|
|
124
|
+
const measureCanvas = createCanvas(1, 1);
|
|
125
|
+
const ctx = measureCanvas.getContext('2d');
|
|
126
|
+
|
|
127
|
+
let fontSize = originalSize;
|
|
128
|
+
ctx.font = `${fontSize}px "${fontName}"`;
|
|
129
|
+
const width = ctx.measureText(textData.text).width;
|
|
130
|
+
|
|
131
|
+
if (width <= maxWidthPx) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Scale down proportionally, then step until it fits (rounding can
|
|
136
|
+
// leave it slightly over)
|
|
137
|
+
fontSize = Math.floor(fontSize * (maxWidthPx / width));
|
|
138
|
+
ctx.font = `${fontSize}px "${fontName}"`;
|
|
139
|
+
while (ctx.measureText(textData.text).width > maxWidthPx && fontSize > 4) {
|
|
140
|
+
fontSize--;
|
|
141
|
+
ctx.font = `${fontSize}px "${fontName}"`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (textData.style) {
|
|
145
|
+
textData.style.fontSize = fontSize;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (textData.styleRuns) {
|
|
149
|
+
const scale = fontSize / originalSize;
|
|
150
|
+
for (const run of textData.styleRuns) {
|
|
151
|
+
if (run.style.fontSize) {
|
|
152
|
+
run.style.fontSize = Math.floor(run.style.fontSize * scale);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { from: originalSize, to: fontSize };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Replace the configured logo/text layers in a parsed PSD.
|
|
162
|
+
*
|
|
163
|
+
* @returns {boolean} whether any layer was modified
|
|
164
|
+
*/
|
|
165
|
+
async function replaceLayers(psd, config, { brandConfig, brandmarkPath }) {
|
|
166
|
+
const [canvasWidth, canvasHeight] = config.dimensions;
|
|
167
|
+
let modified = false;
|
|
168
|
+
|
|
169
|
+
for (const layerConfig of config.layers || []) {
|
|
170
|
+
const layer = findLayerByPath(psd, layerConfig.path);
|
|
171
|
+
if (!layer) {
|
|
172
|
+
console.log(` ${chalk.yellow('⚠')} Layer ${chalk.cyan(layerConfig.path)} not found in ${config.outputName} template`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (layerConfig.type === 'logo') {
|
|
177
|
+
const logoSize = Math.floor(layerConfig.size * canvasWidth);
|
|
178
|
+
|
|
179
|
+
// Tray templates need the pure-black brandmark (macOS inverts it)
|
|
180
|
+
let logoSource;
|
|
181
|
+
if (layerConfig.useBlackLogo) {
|
|
182
|
+
const sourceSvg = jetpack.read(brandmarkPath, 'utf8') || '';
|
|
183
|
+
logoSource = Buffer.from(convertSvgToBlack(sourceSvg));
|
|
184
|
+
} else {
|
|
185
|
+
logoSource = brandmarkPath;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const logoPng = await sharp(logoSource)
|
|
189
|
+
.resize(logoSize, logoSize, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
190
|
+
.png()
|
|
191
|
+
.toBuffer();
|
|
192
|
+
|
|
193
|
+
const logoImage = await loadImage(logoPng);
|
|
194
|
+
const logoCanvas = createCanvas(logoSize, logoSize);
|
|
195
|
+
const ctx = logoCanvas.getContext('2d');
|
|
196
|
+
ctx.drawImage(logoImage, 0, 0, logoSize, logoSize);
|
|
197
|
+
layer.canvas = logoCanvas;
|
|
198
|
+
|
|
199
|
+
// Center within bounds (or the full canvas without bounds)
|
|
200
|
+
let left, top;
|
|
201
|
+
if (layerConfig.bounds) {
|
|
202
|
+
const [bLeftPct, bTopPct, bRightPct, bBottomPct] = layerConfig.bounds;
|
|
203
|
+
const bLeft = Math.floor(bLeftPct * canvasWidth);
|
|
204
|
+
const bTop = Math.floor(bTopPct * canvasHeight);
|
|
205
|
+
const bRight = Math.floor(bRightPct * canvasWidth);
|
|
206
|
+
const bBottom = Math.floor(bBottomPct * canvasHeight);
|
|
207
|
+
left = bLeft + Math.floor((bRight - bLeft - logoSize) / 2);
|
|
208
|
+
top = bTop + Math.floor((bBottom - bTop - logoSize) / 2);
|
|
209
|
+
} else {
|
|
210
|
+
left = Math.floor((canvasWidth - logoSize) / 2);
|
|
211
|
+
top = Math.floor((canvasHeight - logoSize) / 2);
|
|
212
|
+
}
|
|
213
|
+
layer.left = left;
|
|
214
|
+
layer.top = top;
|
|
215
|
+
layer.right = left + logoSize;
|
|
216
|
+
layer.bottom = top + logoSize;
|
|
217
|
+
|
|
218
|
+
modified = true;
|
|
219
|
+
} else if (layerConfig.type === 'text') {
|
|
220
|
+
const value = getNestedValue(brandConfig, layerConfig.value);
|
|
221
|
+
if (!value) {
|
|
222
|
+
console.log(` ${chalk.dim(`⊘ No ${layerConfig.value} configured — text layer ${layerConfig.path} left as-is`)}`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (layer.text) {
|
|
227
|
+
layer.text.text = value;
|
|
228
|
+
|
|
229
|
+
if (layerConfig.maxWidth) {
|
|
230
|
+
fitTextFontSize(layer.text, Math.floor(layerConfig.maxWidth * canvasWidth));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
modified = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return modified;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = async function writeTemplates(context) {
|
|
242
|
+
const { brandRoot, brandConfig, brandmarkPath, outDir, options } = context;
|
|
243
|
+
const dryRun = options?.dryRun || false;
|
|
244
|
+
|
|
245
|
+
const brandTemplatesDir = join(brandRoot, 'assets', 'templates');
|
|
246
|
+
const marker = readCompanyMarker(brandRoot);
|
|
247
|
+
const companyTemplatesDir = marker && !marker.stale
|
|
248
|
+
? join(marker.companyRoot, 'assets', 'templates')
|
|
249
|
+
: null;
|
|
250
|
+
|
|
251
|
+
let seeded = 0;
|
|
252
|
+
let processed = 0;
|
|
253
|
+
let fresh = 0;
|
|
254
|
+
const missing = [];
|
|
255
|
+
const planned = [];
|
|
256
|
+
|
|
257
|
+
for (const [name, config] of Object.entries(TEMPLATE_CONFIG)) {
|
|
258
|
+
const psdPath = join(brandTemplatesDir, `${name}.psd`);
|
|
259
|
+
const companyPsdPath = companyTemplatesDir ? join(companyTemplatesDir, `${name}.psd`) : null;
|
|
260
|
+
|
|
261
|
+
// Resolve the source: the brand's own PSD, else seed from the company
|
|
262
|
+
let needsSeed = false;
|
|
263
|
+
if (!jetpack.exists(psdPath)) {
|
|
264
|
+
if (companyPsdPath && jetpack.exists(companyPsdPath)) {
|
|
265
|
+
needsSeed = true;
|
|
266
|
+
} else {
|
|
267
|
+
missing.push(name);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Staleness: every export must be newer than the PSD AND the brandmark
|
|
273
|
+
const exportSpecs = config.exports || [{ suffix: '' }];
|
|
274
|
+
const outPaths = templateExportFiles(config).map((relative) => join(outDir, relative));
|
|
275
|
+
const stalePsd = needsSeed ? companyPsdPath : psdPath;
|
|
276
|
+
const stale = needsSeed || outPaths.some((p) => isStale(stalePsd, p) || isStale(brandmarkPath, p));
|
|
277
|
+
|
|
278
|
+
if (!stale) {
|
|
279
|
+
fresh++;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (dryRun) {
|
|
284
|
+
planned.push(needsSeed ? `${name} (seed + process)` : name);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (needsSeed) {
|
|
289
|
+
jetpack.copy(companyPsdPath, psdPath);
|
|
290
|
+
seeded++;
|
|
291
|
+
console.log(` ${chalk.green('✓')} Seeded ${chalk.cyan(`assets/templates/${name}.psd`)} from the company templates`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const psd = readPsd(jetpack.read(psdPath, 'buffer'));
|
|
296
|
+
const [canvasWidth, canvasHeight] = config.dimensions;
|
|
297
|
+
|
|
298
|
+
const modified = await replaceLayers(psd, config, { brandConfig, brandmarkPath });
|
|
299
|
+
|
|
300
|
+
if (modified) {
|
|
301
|
+
jetpack.write(psdPath, writePsdBuffer(psd));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Composite all visible layers and export the PNGs
|
|
305
|
+
const compositeCanvas = createCanvas(canvasWidth, canvasHeight);
|
|
306
|
+
compositeLayers(compositeCanvas.getContext('2d'), psd.children);
|
|
307
|
+
const fullPngBuffer = compositeCanvas.toBuffer('image/png');
|
|
308
|
+
|
|
309
|
+
for (const [i, exp] of exportSpecs.entries()) {
|
|
310
|
+
const resized = exp.width && exp.height
|
|
311
|
+
? await sharp(fullPngBuffer).resize(exp.width, exp.height).png().toBuffer()
|
|
312
|
+
: fullPngBuffer;
|
|
313
|
+
jetpack.write(outPaths[i], resized);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
processed++;
|
|
317
|
+
console.log(` ${chalk.green('✓')} Exported ${chalk.cyan(`${config.outputDir}/${config.outputName}.png`)}${modified ? '' : chalk.dim(' (no replaceable layers)')}`);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.log(` ${chalk.red('✗')} Failed to process ${chalk.cyan(`${name}.psd`)}${chalk.dim(`: ${error.message}`)}`);
|
|
320
|
+
return { status: 'error', error: `template ${name}: ${error.message}` };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (dryRun && planned.length > 0) {
|
|
325
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} Would process: ${planned.join(', ')}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (missing.length === Object.keys(TEMPLATE_CONFIG).length) {
|
|
329
|
+
console.log(` ${chalk.dim(`⊘ No PSD templates yet — add assets/templates/{name}.psd to the brand${companyTemplatesDir ? ' or the company root' : ''} (see TEMPLATE_CONFIG)`)}`);
|
|
330
|
+
} else if (processed === 0 && seeded === 0 && planned.length === 0) {
|
|
331
|
+
console.log(` ${chalk.green('✓')} Templates ${chalk.dim(`(${fresh} fresh${missing.length ? `, ${missing.length} absent` : ''})`)} in sync`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
output: {
|
|
336
|
+
templates: {
|
|
337
|
+
seeded,
|
|
338
|
+
processed,
|
|
339
|
+
fresh,
|
|
340
|
+
missing,
|
|
341
|
+
...(dryRun ? { planned } : {}),
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
};
|