Files
tea-sdlc/scripts/overview-capture.js
T

77 lines
3.7 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Capture an overview using an available non-managed backend.
* Managed browser preview remains the preferred platform-level path; this CLI handles
* Firefox and deterministic SVG rasterization when those binaries are installed.
*/
import { execFileSync, spawnSync } from 'node:child_process';
import { existsSync, mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { ScriptError, main, parseFlags } from './lib.js';
main(async () => {
const flags = parseFlags(process.argv.slice(2), {
required: ['output'],
optional: ['url', 'svg', 'backend', 'width', 'height'],
});
const output = resolve(flags.output);
mkdirSync(dirname(output), { recursive: true });
const backend = chooseBackend(flags.backend, flags.url, flags.svg);
if (backend === 'firefox') captureFirefox(flags.url, output, flags.width, flags.height);
else captureSvg(flags.svg, output, flags.width, flags.height);
return { backend, output };
});
function chooseBackend(requested, url, svg) {
if (requested && requested !== 'auto' && requested !== 'firefox' && requested !== 'svg') {
throw new ScriptError('BACKEND_UNKNOWN', `不支援的 capture backend:${requested}`);
}
if (requested === 'firefox') {
requireBinary('firefox');
if (!url) throw new ScriptError('URL_REQUIRED', 'Firefox backend 需要 --url');
return 'firefox';
}
if (requested === 'svg') {
requireSvgInput(svg);
requireAnyBinary(['rsvg-convert', 'resvg']);
return 'svg';
}
if (url && hasBinary('firefox')) return 'firefox';
if (svg && (hasBinary('rsvg-convert') || hasBinary('resvg'))) return 'svg';
throw new ScriptError('NO_CAPTURE_BACKEND', '沒有可用的 Firefox 或 SVG rasterizer;請使用平台 preview,或安裝 firefox、rsvg-convert、resvg');
}
function captureFirefox(url, output, width, height) {
const args = ['--headless'];
if (width) args.push('--window-size', `${width}${height ? `,${height}` : ''}`);
args.push('--screenshot', output, url);
const result = spawnSync('firefox', args, { encoding: 'utf8' });
if (result.status !== 0 || !existsSync(output)) throw new ScriptError('CAPTURE_FAILED', `Firefox 截圖失敗:${result.stderr || result.stdout || '沒有輸出檔案'}`);
}
function captureSvg(svg, output, width, height) {
requireSvgInput(svg);
if (hasBinary('rsvg-convert')) {
const args = [`--output=${output}`];
if (width) args.push(`--width=${width}`);
if (height) args.push(`--height=${height}`);
args.push(resolve(svg));
const result = spawnSync('rsvg-convert', args, { encoding: 'utf8' });
if (result.status !== 0) throw new ScriptError('CAPTURE_FAILED', `rsvg-convert 失敗:${result.stderr || result.stdout}`);
} else {
const args = [];
if (width) args.push('-w', String(width));
args.push(resolve(svg), output);
const result = spawnSync('resvg', args, { encoding: 'utf8' });
if (result.status !== 0) throw new ScriptError('CAPTURE_FAILED', `resvg 失敗:${result.stderr || result.stdout}`);
}
if (!existsSync(output)) throw new ScriptError('CAPTURE_FAILED', 'rasterizer 沒有產生輸出檔案');
}
function requireSvgInput(svg) {
if (!svg || !existsSync(resolve(svg))) throw new ScriptError('SVG_REQUIRED', 'SVG backend 需要存在的 --svg');
}
function requireBinary(binary) { if (!hasBinary(binary)) throw new ScriptError('BINARY_NOT_FOUND', `找不到 ${binary}`); }
function requireAnyBinary(binaries) { if (!binaries.some(hasBinary)) throw new ScriptError('BINARY_NOT_FOUND', `找不到 ${binaries.join(' 或 ')}`); }
function hasBinary(binary) { try { execFileSync('sh', ['-c', `command -v ${binary}`], { stdio: 'ignore' }); return true; } catch { return false; } }