fix(brand): close Task 3 spec gaps surfaced by code review
Three issues caught by the code review of scripts/gen-icons.mjs: (1) Missing source SVG validation. Spec requires the script "refuses to run if any source SVG is missing." Added an existsSync guard at the top of main() that throws a targeted message rather than letting sharp fail with a raw ENOENT. (2) Summary table missing byte sizes. Spec says the summary prints "each output, its size in pixels, its file size in bytes." trace() now captures statSync().size; the printout has a size column with human-readable formatting and a total at the end. (3) writeMaskablePng's intermediate buffer used .png() without the compressionLevel/palette options that every other render uses. Zero functional impact (the buffer is only piped into composite, never written to disk) but inconsistent and misleading. Aligned with the rest of the script. README: replaced 'git add -A' with explicit directory paths matching the project's convention — the generator writes to a fixed set of three directories and shouldn't accidentally stage unrelated working-tree changes during a regen. Plan updated to match the corrected code so Task 3's source-of-truth stays consistent.
This commit is contained in:
@@ -15,10 +15,12 @@ the same PR.
|
||||
```bash
|
||||
pnpm gen-icons
|
||||
git status # review which files changed
|
||||
git add -A
|
||||
git add packages/desktop/build/ packages/desktop/resources/ packages/web/public/icons/
|
||||
git commit -m "chore: regenerate brand icons"
|
||||
```
|
||||
|
||||
(Stage explicit paths rather than `git add -A` — the generator only writes to those three directories, and an unrelated working-tree change shouldn't accidentally land in a "regenerate icons" commit.)
|
||||
|
||||
## Determinism
|
||||
|
||||
Output is byte-stable for a given lockfile. The same SVGs in produce the
|
||||
|
||||
+28
-7
@@ -22,7 +22,7 @@
|
||||
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join, relative } from 'node:path';
|
||||
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync } from 'node:fs';
|
||||
import sharp from 'sharp';
|
||||
import pngToIco from 'png-to-ico';
|
||||
import png2icons from 'png2icons';
|
||||
@@ -102,7 +102,7 @@ async function writeMaskablePng(path, markSvg, canvas, scale, bgHex) {
|
||||
fit: 'contain',
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
||||
})
|
||||
.png()
|
||||
.png({ compressionLevel: 9, palette: false })
|
||||
.toBuffer();
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
const composed = await sharp({
|
||||
@@ -122,13 +122,28 @@ async function writeMaskablePng(path, markSvg, canvas, scale, bgHex) {
|
||||
// ---- main ----
|
||||
|
||||
async function main() {
|
||||
// Spec: refuse to run if any source SVG is missing — fail loudly, not on
|
||||
// a downstream sharp error with a cryptic ENOENT.
|
||||
for (const [, path] of Object.entries(SRC)) {
|
||||
if (!existsSync(path)) {
|
||||
throw new Error(
|
||||
`Missing source SVG: ${relative(ROOT, path)} — copy from Artworks-Backspace/SVG/`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const appIcon = loadSvg(SRC.appIcon);
|
||||
const mark = loadSvg(SRC.mark);
|
||||
const markMonoDark = loadSvg(SRC.markMonoDark);
|
||||
|
||||
const written = [];
|
||||
const trace = (label, path, info) =>
|
||||
written.push({ label, info, path: relative(ROOT, path) });
|
||||
written.push({
|
||||
label,
|
||||
info,
|
||||
bytes: statSync(path).size,
|
||||
path: relative(ROOT, path),
|
||||
});
|
||||
|
||||
// --- Desktop: application icon ---
|
||||
const linuxSizes = [16, 32, 48, 64, 128, 256, 512, 1024];
|
||||
@@ -189,13 +204,19 @@ async function main() {
|
||||
trace('in-app-logo', join(WEB_ICONS, 'logo.png'), '256 (transparent)');
|
||||
|
||||
// --- Summary ---
|
||||
const fmtBytes = (n) => {
|
||||
if (n < 1024) return `${n} B`;
|
||||
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
||||
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
||||
};
|
||||
console.log('\nGenerated icons:');
|
||||
console.log(' ' + 'kind'.padEnd(14) + 'info'.padEnd(30) + 'path');
|
||||
console.log(' ' + '----'.padEnd(14) + '----'.padEnd(30) + '----');
|
||||
console.log(' ' + 'kind'.padEnd(14) + 'info'.padEnd(30) + 'size'.padStart(10) + ' path');
|
||||
console.log(' ' + '----'.padEnd(14) + '----'.padEnd(30) + '----'.padStart(10) + ' ----');
|
||||
for (const r of written) {
|
||||
console.log(' ' + r.label.padEnd(14) + r.info.padEnd(30) + r.path);
|
||||
console.log(' ' + r.label.padEnd(14) + r.info.padEnd(30) + fmtBytes(r.bytes).padStart(10) + ' ' + r.path);
|
||||
}
|
||||
console.log(`\n${written.length} files written.`);
|
||||
const totalBytes = written.reduce((sum, r) => sum + r.bytes, 0);
|
||||
console.log(`\n${written.length} files written, ${fmtBytes(totalBytes)} total.`);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
||||
Reference in New Issue
Block a user