scripts/build-chickpea-product-mark.mjs
Rama main · réplica de lectura del repositorio original.
import { readFile, writeFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import sharp from 'sharp';
const ROOT = fileURLToPath(new URL('..', import.meta.url));
const OUTPUT = join(ROOT, 'src', 'brand', 'chickpea-mark.generated.ts');
const MARK_MASTER = join(ROOT, 'assets', 'chickpea-mark.png');
const WORDMARK_MASTER = join(ROOT, 'assets', 'chickpea-wordmark-mask.png');
const PNG_OPTIONS = { compressionLevel: 9, adaptiveFiltering: true, palette: false };
const DERIVATIVES = [
{ source: MARK_MASTER, file: 'chickpea-mark-128.png', width: 128, height: 128 },
{ source: MARK_MASTER, file: 'chickpea-favicon-32.png', width: 32, height: 32 },
{ source: WORDMARK_MASTER, file: 'chickpea-wordmark-512.png', width: 512, height: 126 },
// White-on-dark counterpart per BRAND.md (cocoa on light surfaces, white on
// dark); the README's <picture> lockup swaps to it under dark color schemes.
{ source: WORDMARK_MASTER, file: 'chickpea-wordmark-512-dark.png', width: 512, height: 126, tint: '#ffffff' },
];
const ASSETS = [
{ exportName: 'CHICKPEA_MARK_URL', file: 'chickpea-mark-128.png', width: 128, height: 128 },
{ exportName: 'CHICKPEA_FAVICON_URL', file: 'chickpea-favicon-32.png', width: 32, height: 32 },
{ exportName: 'CHICKPEA_WORDMARK_URL', file: 'chickpea-wordmark-512.png', width: 512, height: 126 },
];
async function validateMaster(file, width, height, label) {
const image = sharp(file);
const metadata = await image.metadata();
if (metadata.format !== 'png' || metadata.width !== width || metadata.height !== height) {
throw new Error(
`${label} must be a ${width}x${height} PNG, got ${metadata.width}x${metadata.height} ${metadata.format}`,
);
}
return image;
}
await validateMaster(MARK_MASTER, 512, 512, 'chickpea-mark.png');
const wordmark = await validateMaster(
WORDMARK_MASTER,
2106,
518,
'chickpea-wordmark-mask.png',
);
const wordmarkStats = await wordmark.ensureAlpha().stats();
const wordmarkAlpha = wordmarkStats.channels[3];
if (wordmarkAlpha.min !== 0 || wordmarkAlpha.max !== 255) {
throw new Error('chickpea-wordmark-mask.png must contain transparent and opaque pixels');
}
for (const derivative of DERIVATIVES) {
const output = join(ROOT, 'assets', derivative.file);
if (derivative.tint) {
const alpha = await sharp(derivative.source)
.resize(derivative.width, derivative.height, { fit: 'fill' })
.ensureAlpha()
.extractChannel(3)
.toBuffer();
await sharp({
create: {
width: derivative.width,
height: derivative.height,
channels: 3,
background: derivative.tint,
},
})
.joinChannel(alpha)
.png(PNG_OPTIONS)
.toFile(output);
continue;
}
await sharp(derivative.source)
.resize(derivative.width, derivative.height, { fit: 'fill' })
.png(PNG_OPTIONS)
.toFile(output);
}
const exports = [];
for (const asset of ASSETS) {
const bytes = await readFile(join(ROOT, 'assets', asset.file));
await validateMaster(join(ROOT, 'assets', asset.file), asset.width, asset.height, asset.file);
const version = createHash('sha256').update(bytes).digest('hex').slice(0, 12);
exports.push(
`export const ${asset.exportName} = ${JSON.stringify(`/${asset.file}?v=${version}`)};`,
);
}
const source =
'// Generated by scripts/build-chickpea-product-mark.mjs. Do not edit by hand.\n' +
`${exports.join('\n\n')}\n`;
await writeFile(OUTPUT, source);
console.log(`Wrote ${ASSETS.length} Chickpea product-mark assets to ${OUTPUT}`);