feat: コミットレポートツール(コミネコ)初期コミット

Made-with: Cursor
This commit is contained in:
hiroki ito
2026-04-03 19:31:32 +09:00
commit c872debe4b
29 changed files with 4344 additions and 0 deletions
@@ -0,0 +1,143 @@
#!/usr/bin/env node
/**
* Batch Screenshot Capture Script
*
* 複数のHTMLファイルを一括でPNG化します。
*
* Usage: node capture-batch.js <file1.html:output1.png> <file2.html:output2.png> ...
*
* Example:
* node capture-batch.js \
* /tmp/summary.html:/tmp/summary.png \
* /tmp/timeline.html:/tmp/timeline.png \
* /tmp/commits.html:/tmp/commits.png
*/
const { chromium } = require('playwright');
const path = require('path');
// 設定
const CONFIG = {
scale: parseInt(process.env.SCREENSHOT_SCALE || '2', 10),
width: parseInt(process.env.SCREENSHOT_WIDTH || '450', 10),
wait: parseInt(process.env.SCREENSHOT_WAIT || '500', 10),
};
async function captureAll(files) {
console.log(`Capturing ${files.length} files...`);
console.log(`Scale: ${CONFIG.scale}x, Width: ${CONFIG.width}px`);
console.log('');
const browser = await chromium.launch({ headless: true });
try {
// 高解像度コンテキストを作成(ブラウザは1つを再利用)
const context = await browser.newContext({
deviceScaleFactor: CONFIG.scale,
viewport: { width: CONFIG.width, height: 800 },
});
const results = [];
for (const { input, output } of files) {
const page = await context.newPage();
try {
const fileUrl = input.startsWith('file://')
? input
: `file://${path.resolve(input)}`;
console.log(`[${results.length + 1}/${files.length}] ${path.basename(input)}`);
await page.goto(fileUrl, { waitUntil: 'networkidle' });
await page.waitForTimeout(CONFIG.wait);
// コンテンツの高さを取得
const contentHeight = await page.evaluate(() => {
const container = document.body.firstElementChild;
if (container) {
const rect = container.getBoundingClientRect();
const style = window.getComputedStyle(document.body);
const paddingTop = parseFloat(style.paddingTop) || 0;
const paddingBottom = parseFloat(style.paddingBottom) || 0;
return Math.ceil(rect.height + paddingTop + paddingBottom);
}
return document.body.scrollHeight;
});
// ビューポートをコンテンツサイズに合わせる
await page.setViewportSize({
width: CONFIG.width,
height: contentHeight
});
await page.waitForTimeout(100);
// スクリーンショット撮影
await page.screenshot({ path: output, type: 'png' });
results.push({ input, output, success: true, height: contentHeight });
console.log(` -> ${output} (${contentHeight}px)`);
} catch (error) {
results.push({ input, output, success: false, error: error.message });
console.error(` -> Error: ${error.message}`);
} finally {
await page.close();
}
}
await context.close();
return results;
} finally {
await browser.close();
}
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: node capture-batch.js <input.html:output.png> ...');
console.error('');
console.error('Example:');
console.error(' node capture-batch.js \\');
console.error(' /tmp/summary.html:/tmp/summary.png \\');
console.error(' /tmp/timeline.html:/tmp/timeline.png');
process.exit(1);
}
// 引数をパース
const files = args.map(arg => {
const [input, output] = arg.split(':');
if (!input || !output) {
console.error(`Invalid argument: ${arg}`);
console.error('Format should be: input.html:output.png');
process.exit(1);
}
return { input, output };
});
try {
const results = await captureAll(files);
console.log('');
console.log('Summary:');
const success = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
console.log(` Success: ${success}, Failed: ${failed}`);
// 結果をJSON出力(パイプ処理用)
if (process.env.OUTPUT_JSON === '1') {
console.log(JSON.stringify(results));
}
process.exit(failed > 0 ? 1 : 0);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
main();
@@ -0,0 +1,119 @@
#!/usr/bin/env node
/**
* Screenshot Capture Script
*
* HTMLファイルをPNG画像に変換します。
* - Retina対応 (2x)
* - コンテンツサイズに自動フィット(余白なし)
* - GitHub Actions対応(Headless)
*
* Usage: node capture.js <input.html> <output.png>
*/
const { chromium } = require('playwright');
const path = require('path');
// 設定
const CONFIG = {
scale: parseInt(process.env.SCREENSHOT_SCALE || '2', 10),
width: parseInt(process.env.SCREENSHOT_WIDTH || '450', 10),
wait: parseInt(process.env.SCREENSHOT_WAIT || '500', 10),
};
async function capture(inputPath, outputPath) {
// 入力パスをfile:// URLに変換
const fileUrl = inputPath.startsWith('file://')
? inputPath
: `file://${path.resolve(inputPath)}`;
console.log(`Capturing: ${inputPath}`);
console.log(`Output: ${outputPath}`);
console.log(`Scale: ${CONFIG.scale}x, Width: ${CONFIG.width}px`);
const browser = await chromium.launch({
headless: true,
});
try {
// 高解像度コンテキストを作成
const context = await browser.newContext({
deviceScaleFactor: CONFIG.scale,
viewport: { width: CONFIG.width, height: 800 }, // 初期高さ(後で調整)
});
const page = await context.newPage();
await page.goto(fileUrl, { waitUntil: 'networkidle' });
await page.waitForTimeout(CONFIG.wait);
// コンテンツの実際の高さを取得
const contentHeight = await page.evaluate(() => {
// body直下の最初のdivを探す(通常はメインコンテナ)
const container = document.body.firstElementChild;
if (container) {
const rect = container.getBoundingClientRect();
// パディングを考慮
const style = window.getComputedStyle(document.body);
const paddingTop = parseFloat(style.paddingTop) || 0;
const paddingBottom = parseFloat(style.paddingBottom) || 0;
return Math.ceil(rect.height + paddingTop + paddingBottom);
}
// フォールバック: body全体の高さ
return document.body.scrollHeight;
});
console.log(`Content height: ${contentHeight}px`);
// ビューポートをコンテンツサイズに合わせる
await page.setViewportSize({
width: CONFIG.width,
height: contentHeight
});
await page.waitForTimeout(100); // レイアウト安定化
// スクリーンショット撮影(fullPageではなくビューポートのみ)
await page.screenshot({
path: outputPath,
type: 'png',
// fullPage: false がデフォルト
});
console.log(`Done! Saved to ${outputPath}`);
await context.close();
} finally {
await browser.close();
}
}
// メイン処理
async function main() {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: node capture.js <input.html> <output.png>');
console.error('');
console.error('Environment variables:');
console.error(' SCREENSHOT_SCALE - Device pixel ratio (default: 2)');
console.error(' SCREENSHOT_WIDTH - Viewport width (default: 450)');
console.error(' SCREENSHOT_WAIT - Wait time in ms (default: 500)');
process.exit(1);
}
const [inputPath, outputPath] = args;
try {
await capture(inputPath, outputPath);
} catch (error) {
console.error('Error:', error.message);
if (error.message.includes('Executable doesn\'t exist')) {
console.error('');
console.error('Chromium is not installed. Run:');
console.error(' npx playwright install chromium');
}
process.exit(1);
}
}
main();