kdp-book-generator
Version:
Generate KDP-compliant PDFs and EPUBs from Markdown for Amazon book publishing
389 lines ⢠18.1 kB
JavaScript
;
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