Files
pipu-music/scripts/generate-icons.mjs
T
mrleo1nid 78c006184b
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 2m30s
ci / ts / ts (packages + web) (push) Successful in 3m29s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 2m10s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 56s
ci / desktop / rust (desktop proxy) (push) Successful in 3m15s
feat(branding): add unified Pipu Music app icons
2026-10-22 07:48:00 +03:00

52 lines
2.8 KiB
JavaScript

import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
// All platform assets derive from the editable web SVG. Requires workspace dependencies.
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const desktop = join(root, 'apps/desktop');
const require = createRequire(join(desktop, 'package.json'));
const cli = require.resolve('@tauri-apps/cli/tauri.js');
const scratch = join(root, 'artifacts/icon-build');
const source = join(root, 'apps/web/public/favicon.svg');
const svg = readFileSync(source, 'utf8');
const body = svg.replace(/<svg[^>]*>/, '').replace('</svg>', '');
mkdirSync(scratch, { recursive: true });
function render(input, output, sizes = []) {
mkdirSync(output, { recursive: true });
const result = spawnSync(process.execPath, [cli, 'icon', input, '--output', output,
...sizes.flatMap(size => ['--png', String(size)])], { cwd: desktop, stdio: 'inherit' });
if (result.error) throw result.error;
if (result.status !== 0) throw new Error(`Icon generation failed: ${input}`);
}
function variant(name, contents, size, destination) {
const input = join(scratch, `${name}.svg`);
const output = join(scratch, name);
writeFileSync(input, `<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">${contents}</svg>\n`);
render(input, output, [size]);
mkdirSync(dirname(destination), { recursive: true });
copyFileSync(join(output, `${size}x${size}.png`), destination);
}
const native = join(scratch, 'desktop');
render(source, native);
for (const name of ['32x32.png', '64x64.png', '128x128.png', '128x128@2x.png', 'icon.png', 'icon.ico', 'icon.icns']) {
copyFileSync(join(native, name), join(desktop, 'src-tauri/icons', name));
}
copyFileSync(join(native, '32x32.png'), join(root, 'apps/web/public/favicon-32.png'));
const background = '<rect width="512" height="512" fill="#05070D"/>';
variant('touch', background + body, 180, join(root, 'apps/web/public/apple-touch-icon.png'));
variant('mobile', background + body, 1024, join(root, 'apps/mobile/assets/icon.png'));
// The complete orb fits inside Android's central 66/108 safe-zone circle.
variant('adaptive', `<g transform="translate(92.16 92.16) scale(.64)">${body}</g>`, 1024,
join(root, 'apps/mobile/assets/adaptive-icon.png'));
const mono = body.replace(/<defs>[\s\S]*?<\/defs>/, '').replace(/<circle[^>]*\/>/, '')
.replaceAll('url(#note)', '#FFFFFF');
variant('monochrome', `<g transform="translate(51.2 51.2) scale(.8)">${mono}</g>`, 1024,
join(root, 'apps/mobile/assets/monochrome-icon.png'));
console.log('Updated desktop, Android, and browser icons.');