UNPKG

kdp-book-generator

Version:

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

479 lines 19.7 kB
"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 }); exports.MultiFileAssembler = void 0; const fs_1 = require("fs"); const path_1 = require("path"); const yaml = __importStar(require("yaml")); const markdown_parser_1 = require("../parser/markdown-parser"); const book_config_1 = require("../config/book-config"); class MultiFileAssembler { constructor(configPath, baseDir) { this.translations = { Published: { en: 'Published', es: 'Publicado', fr: 'Publié', de: 'Veröffentlicht', it: 'Pubblicato', pt: 'Publicado', nl: 'Gepubliceerd', pl: 'Opublikowany', ru: 'Опубликовано', ja: '出版日', zh: '出版日期', ko: '출판일', }, }; this.config = this.loadConfig(configPath); this.baseDir = baseDir || (0, path_1.dirname)(configPath); this.parser = new markdown_parser_1.MarkdownParser(); } getTranslation(key, language) { const lang = language || this.config.book_info.language || 'en'; const langCode = lang.toLowerCase().substring(0, 2); return this.translations[key]?.[langCode] || this.translations[key]?.['en'] || key; } getConfig() { return this.config; } loadConfig(configPath) { if (!(0, fs_1.existsSync)(configPath)) { throw new Error(`Configuration file not found: ${configPath}`); } const configContent = (0, fs_1.readFileSync)(configPath, 'utf-8'); try { return yaml.parse(configContent); } catch (error) { throw new Error(`Error parsing configuration YAML: ${error}`); } } async assemble(options) { console.log('📚 Starting multi-file assembly...'); console.log(`📖 Book: ${this.config.book_info.title}`); console.log(`📁 Base directory: ${this.baseDir}`); if (options.validateFiles) { await this.validateFiles(); } const assembledContent = await this.assembleContent(); const frontMatter = this.generateFrontMatter(); // Parse the assembled content const parsedBook = this.parser.parse(assembledContent); // Override front matter with assembly config parsedBook.frontMatter = { ...parsedBook.frontMatter, ...frontMatter }; console.log('✅ Assembly completed successfully'); console.log(`📊 Total sections: ${this.config.assembly_order.length}`); console.log(`📊 Total files: ${this.getTotalFileCount()}`); console.log(`📊 Total chapters: ${parsedBook.chapters.length}`); return parsedBook; } async validateFiles() { console.log('🔍 Validating files...'); const allFiles = this.getAllFiles(); const missingFiles = []; const invalidFiles = []; for (const file of allFiles) { const filePath = (0, path_1.join)(this.baseDir, file.filename); if (!(0, fs_1.existsSync)(filePath)) { missingFiles.push(file.filename); continue; } // Validate file format if specified if (this.config.format_validation) { const content = (0, fs_1.readFileSync)(filePath, 'utf-8'); if (!this.validateFileFormat(content, file)) { invalidFiles.push(file.filename); } } } if (missingFiles.length > 0) { throw new Error(`Missing files: ${missingFiles.join(', ')}`); } if (invalidFiles.length > 0) { console.warn(`⚠️ Files with format issues: ${invalidFiles.join(', ')}`); } console.log(`✅ Validated ${allFiles.length} files`); } validateFileFormat(content, _file) { const formatValidation = this.config.format_validation; if (!formatValidation) return true; // Check header format if (formatValidation.header_format) { const lines = content.split('\n'); const firstLine = lines[0]; const secondLine = lines[1]; // Check for dual language header format if (!firstLine.startsWith('# **') || !firstLine.endsWith('**')) { return false; } if (secondLine && !secondLine.startsWith('*') && !secondLine.endsWith('*')) { return false; } } // Check required sections if (formatValidation.file_structure?.required_sections) { for (const section of formatValidation.file_structure.required_sections) { if (!content.includes(section)) { return false; } } } return true; } async assembleContent() { console.log('🔧 Assembling content...'); const parts = []; // Add book header parts.push(this.generateBookHeader()); // Add table of contents if enabled if (this.config.assembly_settings.include_table_of_contents) { parts.push(this.generateTableOfContents()); } // Process each section for (const section of this.config.assembly_order) { console.log(`📝 Processing section: ${section.title}`); // Add section header parts.push(this.generateSectionHeader(section)); // Process files in the section for (const file of section.files) { if (!file.required) continue; console.log(` 📄 Adding file: ${file.filename}`); const filePath = (0, path_1.join)(this.baseDir, file.filename); if (!(0, fs_1.existsSync)(filePath)) { console.warn(`⚠️ File not found: ${file.filename}`); continue; } const content = (0, fs_1.readFileSync)(filePath, 'utf-8'); // Add page break if specified if (file.page_break) { parts.push(this.config.assembly_settings.formatting.page_break_marker); } // Process and add file content const processedContent = this.processFileContent(content, file); parts.push(processedContent); } // Add section separator parts.push(this.config.assembly_settings.formatting.section_separator); } return parts.join('\n'); } generateBookHeader() { const bookInfo = this.config.book_info; const parts = []; // Main title parts.push(`# **${bookInfo.title}**`); if (bookInfo.subtitle) { parts.push(`## ${bookInfo.subtitle}`); } if (bookInfo.description) { parts.push(`**${bookInfo.description}**`); } if (bookInfo.spanish_title) { parts.push(`*${bookInfo.spanish_title}*`); } // Metadata if (this.config.assembly_settings.metadata.include_version_info && bookInfo.version) { parts.push(`**Version:** ${bookInfo.version}`); } if (this.config.assembly_settings.metadata.include_generation_date) { const publishedLabel = this.getTranslation('Published'); parts.push(`**${publishedLabel}:** ${new Date().toISOString().split('T')[0]}`); } parts.push('---'); return parts.join('\n\n'); } generateTableOfContents() { const tocConfig = this.config.table_of_contents; if (!tocConfig?.generate_automatically) return ''; const parts = []; parts.push('# Table of Contents'); parts.push(''); let chapterNumber = 1; for (const section of this.config.assembly_order) { // Add section header const sectionConfig = tocConfig.sections.find((s) => s.name.includes(section.title.replace(/^[📚📗📙📕🚀\s]/u, ''))); const emoji = sectionConfig?.emoji || ''; parts.push(`## ${emoji} ${section.title}`); // Add files in section for (const file of section.files) { if (!file.required) continue; const indent = ''; let numbering = ''; if (file.type === 'chapter' && tocConfig.include_chapter_numbers) { numbering = `${chapterNumber}. `; chapterNumber++; } else if (file.type === 'appendix') { numbering = `Appendix ${file.appendix_letter}: `; } const title = file.title; parts.push(`${indent}- ${numbering}${title}`); if (file.spanish_title) { parts.push(`${indent} *${file.spanish_title}*`); } } parts.push(''); } parts.push('---'); return parts.join('\n'); } generateSectionHeader(section) { const parts = []; parts.push(`# ${section.title}`); if (section.spanish_title) { parts.push(`*${section.spanish_title}*`); } return parts.join('\n\n'); } processFileContent(content, file) { let processed = content; // Remove file-specific headers if they duplicate the assembly title // This prevents duplicate titles when files are assembled // NOTE: Only remove titles for non-chapter files to preserve chapter headings if (file.type !== 'chapter' && file.type !== 'appendix') { const lines = processed.split('\n'); if (lines[0].startsWith('# **') && lines[0].includes(file.title)) { // Remove the title line and any following subtitle/description let linesToRemove = 1; if (lines[1] && lines[1].startsWith('*') && lines[1].endsWith('*')) { linesToRemove = 2; } processed = lines.slice(linesToRemove).join('\n'); } } // Add cross-references if enabled if (this.config.cross_references?.enable_internal_links) { processed = this.addCrossReferences(processed); } return processed; } addCrossReferences(content) { const crossRefs = this.config.cross_references; if (!crossRefs) return content; const patterns = crossRefs.reference_patterns; if (!patterns) return content; let processed = content; // Split content into lines to handle headings separately const lines = processed.split('\n'); const processedLines = lines.map((line) => { // Skip processing if this is a heading line if (line.match(/^#{1,6}\s+/)) { return line; } let processedLine = line; // Replace chapter references (only in non-heading lines) if (crossRefs.enable_chapter_references) { processedLine = processedLine.replace(/Chapter (\d+)/g, (match, num) => { const chapterFile = this.findChapterFile(parseInt(num)); if (chapterFile) { return patterns.chapter_reference .replace('{number}', num) .replace('{title}', chapterFile.title.replace(/^Chapter \d+: /, '')); } return match; }); } return processedLine; }); processed = processedLines.join('\n'); // Replace appendix references (continue with the line-by-line processing) if (crossRefs.enable_appendix_references) { const finalLines = processed.split('\n'); const finalProcessedLines = finalLines.map((line) => { // Skip processing if this is a heading line if (line.match(/^#{1,6}\s+/)) { return line; } return line.replace(/Appendix ([A-Z])/g, (match, letter) => { const appendixFile = this.findAppendixFile(letter); if (appendixFile) { return patterns.appendix_reference .replace('{letter}', letter) .replace('{title}', appendixFile.title.replace(/^Appendix [A-Z]: /, '')); } return match; }); }); processed = finalProcessedLines.join('\n'); } return processed; } findChapterFile(chapterNumber) { for (const section of this.config.assembly_order) { const file = section.files.find((f) => f.chapter_number === chapterNumber); if (file) return file; } return null; } findAppendixFile(letter) { for (const section of this.config.assembly_order) { const file = section.files.find((f) => f.appendix_letter === letter); if (file) return file; } return null; } generateFrontMatter() { const bookInfo = this.config.book_info; const metadata = this.config.metadata; return { title: bookInfo.title, author: bookInfo.author || metadata?.author || 'Unknown Author', description: bookInfo.description, language: bookInfo.language || 'en', version: bookInfo.version, level: bookInfo.level, spanish_title: bookInfo.spanish_title, total_chapters: bookInfo.total_chapters, total_sections: bookInfo.total_sections, total_files: bookInfo.total_files, generation_date: new Date().toISOString().split('T')[0], target_audience: metadata?.target_audience, complementary_resource: metadata?.complementary_resource, }; } getAllFiles() { const files = []; for (const section of this.config.assembly_order) { files.push(...section.files); } return files; } getTotalFileCount() { return this.getAllFiles().length; } getTypographyConfig() { const defaultTypography = book_config_1.BookConfigManager.getDefaultTypography(); const override = this.config.typography_override; if (!override) { console.log('📝 No typography overrides found, using defaults'); return defaultTypography; } // Apply overrides const typography = { ...defaultTypography }; if (override.body_font !== undefined) { typography.body.size = override.body_font; } if (override.body_line_height !== undefined) { typography.body.lineHeight = override.body_line_height; } if (override.h1_font !== undefined) { typography.heading1.size = override.h1_font; } if (override.h1_margin_top !== undefined) { typography.heading1.marginTop = override.h1_margin_top; } if (override.h1_margin_bottom !== undefined) { typography.heading1.marginBottom = override.h1_margin_bottom; } if (override.h2_font !== undefined) { typography.heading2.size = override.h2_font; } if (override.h2_margin_top !== undefined) { typography.heading2.marginTop = override.h2_margin_top; } if (override.h2_margin_bottom !== undefined) { typography.heading2.marginBottom = override.h2_margin_bottom; } if (override.h3_font !== undefined) { typography.heading3.size = override.h3_font; } if (override.h3_margin_top !== undefined) { typography.heading3.marginTop = override.h3_margin_top; } if (override.h3_margin_bottom !== undefined) { typography.heading3.marginBottom = override.h3_margin_bottom; } if (override.h4_font !== undefined) { typography.heading4.size = override.h4_font; } if (override.h5_font !== undefined) { typography.heading5.size = override.h5_font; } if (override.h6_font !== undefined) { typography.heading6.size = override.h6_font; } return typography; } // Static utility methods static async validateConfig(configPath) { const errors = []; try { if (!(0, fs_1.existsSync)(configPath)) { errors.push(`Configuration file not found: ${configPath}`); return { valid: false, errors }; } const configContent = (0, fs_1.readFileSync)(configPath, 'utf-8'); const config = yaml.parse(configContent); // Validate required fields if (!config.book_info) { errors.push('Missing required field: book_info'); } if (!config.assembly_order || config.assembly_order.length === 0) { errors.push('Missing or empty assembly_order'); } if (!config.assembly_settings) { errors.push('Missing required field: assembly_settings'); } // Validate file references const baseDir = (0, path_1.dirname)(configPath); for (const section of config.assembly_order || []) { for (const file of section.files || []) { const filePath = (0, path_1.join)(baseDir, file.filename); if (file.required && !(0, fs_1.existsSync)(filePath)) { errors.push(`Required file not found: ${file.filename}`); } } } } catch (error) { errors.push(`Error parsing configuration: ${error}`); } return { valid: errors.length === 0, errors, }; } } exports.MultiFileAssembler = MultiFileAssembler; //# sourceMappingURL=multi-file-assembler.js.map