UNPKG

kdp-book-generator

Version:

Generate KDP-compliant PDFs and EPUBs from Markdown for Amazon book publishing

389 lines • 18.1 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const fs_1 = require("fs"); const path_1 = require("path"); const markdown_parser_1 = require("../parser/markdown-parser"); const pdf_generator_1 = require("../generator/pdf-generator"); const epub_generator_1 = require("../generator/epub-generator"); const multi_file_assembler_1 = require("../assembler/multi-file-assembler"); const book_config_1 = require("../config/book-config"); const program = new commander_1.Command(); program .name('kdp-generator') .description('Generate KDP-compliant PDFs from Markdown for book publishing') .version('1.0.0'); program .command('generate') .description('Generate PDF from markdown file') .argument('<input>', 'Input markdown file') .option('-o, --output <output>', 'Output file (PDF for print, EPUB for Kindle)', 'book.pdf') .option('-f, --format <format>', 'Book format (6x9, 5.5x8.5, 5x8, 8.5x11, 7x10, 7.5x9.25, kindle)', '6x9') .option('-c, --config <config>', 'Configuration file (JSON)') .option('-t, --title <title>', 'Book title') .option('-a, --author <author>', 'Book author') .option('--no-toc', 'Disable table of contents') .option('--bleed', 'Enable bleed (0.125" on all sides)') .option('--pages <pages>', 'Estimated page count for margin calculation', '200') .option('--cover <cover>', 'Path to cover image file (for EPUB/Kindle format)') .option('--debug', 'Enable debug output') .action(async (input, options) => { try { console.log('šŸ“š KDP Book Generator'); console.log('='.repeat(20)); // Check if input file exists if (!(0, fs_1.existsSync)(input)) { console.error(`āŒ Input file not found: ${input}`); process.exit(1); } // Read markdown file console.log(`šŸ“– Reading markdown file: ${input}`); const markdownContent = (0, fs_1.readFileSync)(input, 'utf-8'); // Parse markdown console.log('šŸ” Parsing markdown content...'); const parser = new markdown_parser_1.MarkdownParser(); const parsedBook = parser.parse(markdownContent); // Load configuration let config; // Check if Kindle format is requested if (options.format === 'kindle') { config = book_config_1.BookConfigManager.getKindleConfig(); } else { config = book_config_1.BookConfigManager.getDefaultConfig(); } if (options.config && (0, fs_1.existsSync)(options.config)) { console.log(`āš™ļø Loading configuration: ${options.config}`); // TODO: Implement config file loading // config = BookConfigManager.loadFromFile(options.config); } // Override config with CLI options if (options.format) { const format = book_config_1.BookConfigManager.getFormatByName(options.format); if (format) { config.format = format; } else { console.error(`āŒ Unknown format: ${options.format}`); console.log('Available formats:', book_config_1.BookConfigManager.getAvailableFormats().join(', ')); process.exit(1); } } if (options.title) { config.title = options.title; } if (options.author) { config.author = options.author; } if (options.noToc) { config.tableOfContents = false; } if (options.bleed) { config.bleed = true; } // Calculate margins based on page count (skip for Kindle) if (options.format !== 'kindle') { const pageCount = parseInt(options.pages) || 200; const formatKey = book_config_1.BookConfigManager.getAvailableFormats().find((key) => book_config_1.BookConfigManager.getFormatByName(key)?.name === config.format.name); if (formatKey && formatKey !== 'kindle') { config.margins = book_config_1.BookConfigManager.calculateKDPMargins(pageCount, formatKey); } } // Validate configuration console.log('āœ… Validating configuration...'); const validation = book_config_1.BookConfigManager.validateConfig(config); if (!validation.valid) { console.error('āŒ Configuration validation failed:'); validation.errors.forEach((error) => console.error(` - ${error}`)); process.exit(1); } // Generate output based on format const outputPath = (0, path_1.resolve)(options.output); if (options.format === 'kindle') { // Generate EPUB for Kindle console.log('šŸ“± Generating EPUB for Kindle...'); const epubGenerator = new epub_generator_1.EPUBGenerator(); // Change extension to .epub if it's .pdf const epubPath = outputPath.replace(/\.pdf$/i, '.epub'); await epubGenerator.generateEPUB(parsedBook, config, { outputPath: epubPath, language: 'en', publisher: 'KDP Book Generator', coverImage: options.cover, }); console.log(`āœ… EPUB generated successfully: ${epubPath}`); console.log('šŸ’” Tip: You can upload this EPUB directly to Amazon KDP'); } else { // Generate PDF for print formats console.log('šŸŽØ Generating PDF...'); const pdfGenerator = new pdf_generator_1.PDFGenerator(); await pdfGenerator.generatePDF(parsedBook, config, { outputPath, debug: options.debug, }); console.log(`āœ… PDF generated successfully: ${outputPath}`); } // Print summary console.log('\nšŸ“Š Summary:'); console.log(` Title: ${config.title}`); console.log(` Author: ${config.author}`); console.log(` Format: ${config.format.name}`); console.log(` Margins: ${config.margins.top}"T ${config.margins.bottom}"B ${config.margins.inside}"I ${config.margins.outside}"O`); console.log(` Table of Contents: ${config.tableOfContents ? 'Yes' : 'No'}`); console.log(` Bleed: ${config.bleed ? 'Yes' : 'No'}`); console.log(` Chapters: ${parsedBook.chapters.length}`); } catch (error) { console.error('āŒ Error generating PDF:', error); process.exit(1); } }); program .command('formats') .description('List available book formats') .action(() => { console.log('šŸ“ Available KDP Book Formats:'); console.log('='.repeat(30)); const formatNames = book_config_1.BookConfigManager.getAvailableFormats(); formatNames.forEach((key) => { const format = book_config_1.BookConfigManager.getFormatByName(key); if (format) { console.log(` ${key.padEnd(10)} - ${format.name}`); } }); }); program .command('config') .description('Generate default configuration file') .option('-o, --output <output>', 'Output configuration file', 'book-config.json') .action(async (options) => { const config = book_config_1.BookConfigManager.getDefaultConfig(); const configJson = JSON.stringify(config, null, 2); const fs = await Promise.resolve().then(() => __importStar(require('fs'))); fs.writeFileSync(options.output, configJson); console.log(`āœ… Default configuration saved to: ${options.output}`); }); program .command('validate') .description('Validate a configuration file') .argument('<config>', 'Configuration file to validate') .action((configPath) => { try { if (!(0, fs_1.existsSync)(configPath)) { console.error(`āŒ Configuration file not found: ${configPath}`); process.exit(1); } const configContent = (0, fs_1.readFileSync)(configPath, 'utf-8'); const config = JSON.parse(configContent); const validation = book_config_1.BookConfigManager.validateConfig(config); if (validation.valid) { console.log('āœ… Configuration is valid'); } else { console.error('āŒ Configuration validation failed:'); validation.errors.forEach((error) => console.error(` - ${error}`)); process.exit(1); } } catch (error) { console.error('āŒ Error validating configuration:', error); process.exit(1); } }); program .command('assemble') .description('Assemble multiple markdown files into a single book') .argument('<config>', 'Assembly configuration file (YAML)') .option('-o, --output <output>', 'Output file (PDF for print, EPUB for Kindle)') .option('-f, --format <format>', 'Book format (6x9, 5.5x8.5, 5x8, 8.5x11, 7x10, 7.5x9.25, kindle)', '6x9') .option('-t, --title <title>', 'Override book title') .option('-a, --author <author>', 'Override book author') .option('--base-dir <dir>', 'Base directory for markdown files (defaults to config file directory)') .option('--validate', 'Validate all files before assembly') .option('--no-toc', 'Disable table of contents') .option('--cover <cover>', 'Path to cover image file (for EPUB/Kindle format)') .option('--debug', 'Enable debug output') .action(async (configPath, options) => { try { console.log('šŸ“š KDP Multi-File Book Assembly'); console.log('='.repeat(35)); // Check if config file exists if (!(0, fs_1.existsSync)(configPath)) { console.error(`āŒ Configuration file not found: ${configPath}`); process.exit(1); } // Validate assembly configuration console.log('šŸ” Validating assembly configuration...'); const validation = await multi_file_assembler_1.MultiFileAssembler.validateConfig(configPath); if (!validation.valid) { console.error('āŒ Assembly configuration validation failed:'); validation.errors.forEach((error) => console.error(` - ${error}`)); process.exit(1); } // Create assembler and assemble files const assembler = new multi_file_assembler_1.MultiFileAssembler(configPath, options.baseDir); const parsedBook = await assembler.assemble({ configPath, baseDir: options.baseDir, validateFiles: options.validate, debug: options.debug, }); // Get assembly config for cover image const assemblyConfig = assembler.getConfig(); // Load book configuration let config; // Check if Kindle format is requested if (options.format === 'kindle') { config = book_config_1.BookConfigManager.getKindleConfig(); } else { config = book_config_1.BookConfigManager.getDefaultConfig(); } // Apply typography overrides from assembly config config.typography = assembler.getTypographyConfig(); // Override config with CLI options and assembly metadata if (options.title || parsedBook.frontMatter.title) { config.title = options.title || parsedBook.frontMatter.title || 'Untitled Book'; } if (options.author || parsedBook.frontMatter.author) { config.author = options.author || parsedBook.frontMatter.author || 'Unknown Author'; } if (options.format) { const format = book_config_1.BookConfigManager.getFormatByName(options.format); if (format) { config.format = format; } else { console.error(`āŒ Unknown format: ${options.format}`); console.log('Available formats:', book_config_1.BookConfigManager.getAvailableFormats().join(', ')); process.exit(1); } } if (options.noToc) { config.tableOfContents = false; } // Calculate margins for non-Kindle formats if (options.format !== 'kindle') { const pageCount = parsedBook.frontMatter.total_estimated_pages || 200; const formatKey = book_config_1.BookConfigManager.getAvailableFormats().find((key) => book_config_1.BookConfigManager.getFormatByName(key)?.name === config.format.name); if (formatKey && formatKey !== 'kindle') { config.margins = book_config_1.BookConfigManager.calculateKDPMargins(pageCount, formatKey); } } // Validate final configuration console.log('āœ… Validating final book configuration...'); const configValidation = book_config_1.BookConfigManager.validateConfig(config); if (!configValidation.valid) { console.error('āŒ Book configuration validation failed:'); configValidation.errors.forEach((error) => console.error(` - ${error}`)); process.exit(1); } // Generate output const sanitizeFilename = (title) => { return title .replace(/[<>:"/\\|?*]/g, '') // Remove invalid filename characters .replace(/\s+/g, '-') // Replace spaces with dashes .replace(/-+/g, '-') // Replace multiple dashes with single dash .replace(/^-|-$/g, ''); // Remove leading/trailing dashes }; // Determine output path - if not specified, use config directory let outputPath; if (options.output) { outputPath = (0, path_1.resolve)(options.output); } else { const configDir = (0, path_1.dirname)((0, path_1.resolve)(configPath)); const defaultFilename = `${sanitizeFilename(config.title)}.pdf`; outputPath = (0, path_1.resolve)(configDir, defaultFilename); } // Show output location if (!options.output) { console.log(`šŸ“ Output directory: ${(0, path_1.dirname)(outputPath)}`); } if (options.format === 'kindle') { // Generate EPUB for Kindle console.log('šŸ“± Generating EPUB for Kindle...'); const epubGenerator = new epub_generator_1.EPUBGenerator(); // Change extension to .epub if it's .pdf const epubPath = outputPath.replace(/\.pdf$/i, '.epub'); // Use cover from command line or assembly config let coverImage = options.cover || assemblyConfig.book_info.cover_image; // Resolve cover image path if relative if (coverImage && !(0, path_1.isAbsolute)(coverImage)) { const baseDir = options.baseDir || (0, path_1.dirname)(configPath); coverImage = (0, path_1.resolve)(baseDir, coverImage); } await epubGenerator.generateEPUB(parsedBook, config, { outputPath: epubPath, language: parsedBook.frontMatter.language || 'en', publisher: 'KDP Book Generator', coverImage: coverImage, }); console.log(`āœ… EPUB generated successfully: ${epubPath}`); console.log('šŸ’” Tip: You can upload this EPUB directly to Amazon KDP'); } else { // Generate PDF for print formats console.log('šŸŽØ Generating PDF...'); const pdfGenerator = new pdf_generator_1.PDFGenerator(); await pdfGenerator.generatePDF(parsedBook, config, { outputPath, debug: options.debug, }); console.log(`āœ… PDF generated successfully: ${outputPath}`); } // Print summary console.log('\nšŸ“Š Assembly Summary:'); console.log(` Title: ${config.title}`); console.log(` Author: ${config.author}`); console.log(` Format: ${config.format.name}`); console.log(` Language: ${parsedBook.frontMatter.language || 'en'}`); console.log(` Level: ${parsedBook.frontMatter.level || 'N/A'}`); console.log(` Total Files Assembled: ${parsedBook.frontMatter.total_files || 'N/A'}`); console.log(` Total Sections: ${parsedBook.frontMatter.total_sections || 'N/A'}`); console.log(` Total Chapters: ${parsedBook.frontMatter.total_chapters || parsedBook.chapters.length}`); console.log(` Table of Contents: ${config.tableOfContents ? 'Yes' : 'No'}`); console.log(` Generation Date: ${parsedBook.frontMatter.generation_date || 'N/A'}`); } catch (error) { console.error('āŒ Error during assembly:', error); process.exit(1); } }); program.parse(); //# sourceMappingURL=index.js.map