From 2aa9dacfc4f87080192ac3190c81920e98b46dbc Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 15 Dec 2025 19:16:46 -0500 Subject: [PATCH] feat: add timestamped filenames and --name flag for print layouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Print layouts now auto-generate unique filenames with timestamps - Added --name/-n flag to customize zine name in filename - Example files default to 'undernet' as zine name - Added --help flag with usage examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/layout.mjs | 61 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/src/layout.mjs b/src/layout.mjs index 5760c1c..9cd78fa 100644 --- a/src/layout.mjs +++ b/src/layout.mjs @@ -39,22 +39,42 @@ const PANEL_HEIGHT = Math.floor(PAGE_HEIGHT / ROWS); // 1275 pixels (~10.8cm) const TOP_ROW_PAGES = [0, 7, 6, 5]; // 0-indexed: pages 1, 8, 7, 6 const BOTTOM_ROW_PAGES = [1, 2, 3, 4]; // 0-indexed: pages 2, 3, 4, 5 +/** + * Generate a timestamped filename for the print layout + * @param {string} [zineName] - Optional zine name to include in filename + * @returns {string} - Timestamped filename + */ +function generatePrintFilename(zineName = 'mycrozine') { + const timestamp = new Date().toISOString() + .replace(/T/, '_') + .replace(/:/g, '-') + .replace(/\..+/, ''); + const safeName = zineName.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(); + return `${safeName}_print_${timestamp}.png`; +} + /** * Create a print-ready zine layout from 8 page images * * @param {Object} options - Layout options * @param {string[]} options.pages - Array of 8 page image paths (in order: 1-8) - * @param {string} [options.outputPath] - Output file path (default: output/mycrozine_print.png) + * @param {string} [options.outputPath] - Output file path (auto-generated with timestamp if not provided) + * @param {string} [options.zineName] - Zine name for generated filename (default: 'mycrozine') * @param {string} [options.background] - Background color (default: '#ffffff') * @returns {Promise} - Path to generated print layout */ export async function createPrintLayout(options) { const { pages, - outputPath = path.join(__dirname, '..', 'output', 'mycrozine_print.png'), + zineName = 'mycrozine', background = '#ffffff' } = options; + // Use provided outputPath or generate timestamped filename + const outputPath = options.outputPath || path.join( + __dirname, '..', 'output', generatePrintFilename(zineName) + ); + if (!pages || pages.length !== 8) { throw new Error('Exactly 8 page images are required'); } @@ -128,18 +148,43 @@ export async function createPrintLayout(options) { /** * CLI entry point - * Usage: node layout.mjs ... [--output ] + * Usage: node layout.mjs ... [--output ] [--name ] */ async function main() { const args = process.argv.slice(2); + // Show help + if (args.includes('--help') || args.includes('-h')) { + console.log(` +MycroZine Layout Generator +Creates a print-ready layout with all 8 pages on a single 11"x8.5" sheet. + +Usage: + node layout.mjs page1.png page2.png ... page8.png [options] + +Options: + --output, -o Output file path (default: auto-generated with timestamp) + --name, -n Zine name for auto-generated filename (default: mycrozine) + --help, -h Show this help message + +Examples: + node layout.mjs p1.png p2.png p3.png p4.png p5.png p6.png p7.png p8.png + node layout.mjs p*.png --name "undernet" + node layout.mjs p*.png --output my_zine_print.png +`); + process.exit(0); + } + // Parse arguments let pages = []; let outputPath = null; + let zineName = 'mycrozine'; for (let i = 0; i < args.length; i++) { if (args[i] === '--output' || args[i] === '-o') { outputPath = args[++i]; + } else if (args[i] === '--name' || args[i] === '-n') { + zineName = args[++i]; } else if (!args[i].startsWith('-')) { pages.push(args[i]); } @@ -161,6 +206,11 @@ async function main() { console.log(' Or place 8 pages named undernet_zine_p1.png through p8.png in examples/undernet/'); process.exit(1); } + + // Use 'undernet' as default zine name when using example files + if (zineName === 'mycrozine') { + zineName = 'undernet'; + } } catch (e) { console.log('Usage: node layout.mjs page1.png page2.png ... page8.png [--output path]'); process.exit(1); @@ -172,13 +222,14 @@ async function main() { process.exit(1); } - const options = { pages }; + const options = { pages, zineName }; if (outputPath) { options.outputPath = outputPath; } try { - await createPrintLayout(options); + const result = await createPrintLayout(options); + console.log(`\nPrint file saved to: ${result}`); } catch (error) { console.error('Error creating layout:', error.message); process.exit(1);