import * as plugins from './plugins.js'; import * as paths from '../paths.js'; import * as interfaces from '../interfaces/index.js'; import { TsBundle } from '../tsbundle.class.tsbundle.js'; import { HtmlHandler } from '../mod_html/index.js'; import { Base64TsOutput } from '../mod_output/index.js'; const TEMP_DIR = '.nogit/tsbundle-temp'; export class CustomBundleHandler { private cwd: string; private config: interfaces.ITsbundleConfig; constructor(cwd: string = paths.cwd) { this.cwd = cwd; } /** * Load configuration from npmextra.json */ public async loadConfig(): Promise { const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd); this.config = npmextraInstance.dataFor('@git.zone/tsbundle', { bundles: [], }); if (!this.config.bundles || this.config.bundles.length === 0) { console.log('No bundle configuration found.'); console.log('Run `tsbundle init` to create one.'); return false; } console.log(`Found ${this.config.bundles.length} bundle configuration(s)`); return true; } /** * Process all configured bundles */ public async processAllBundles(): Promise { for (let i = 0; i < this.config.bundles.length; i++) { const bundleConfig = this.config.bundles[i]; console.log(`\nProcessing bundle ${i + 1}/${this.config.bundles.length}: ${bundleConfig.from} -> ${bundleConfig.to}`); await this.processSingleBundle(bundleConfig); } } /** * Process a single bundle configuration */ private async processSingleBundle(bundleConfig: interfaces.IBundleConfig): Promise { const outputMode = bundleConfig.outputMode || 'bundle'; const bundler = bundleConfig.bundler || 'esbuild'; // Determine temp output path const tempDir = plugins.path.join(this.cwd, TEMP_DIR); const tempBundlePath = plugins.path.join(tempDir, `bundle-${Date.now()}.js`); // Ensure temp directory exists await plugins.fs.directory(tempDir).create(); // Build the bundle to temp location const tsbundle = new TsBundle(); try { await tsbundle.build( this.cwd, bundleConfig.from, tempBundlePath, { bundler, production: bundleConfig.production || false, } ); } catch (error: any) { console.error(`\n\x1b[31m❌ Bundle failed: ${bundleConfig.from} -> ${bundleConfig.to}\x1b[0m`); // Don't re-print error details - they were already shown by the child process return; // Skip output handling since build failed } if (outputMode === 'base64ts') { await this.handleBase64TsOutput(bundleConfig, tempBundlePath); } else { await this.handleBundleOutput(bundleConfig, tempBundlePath); } // Clean up temp file const tempFileExists = await plugins.fs.file(tempBundlePath).exists(); if (tempFileExists) { await plugins.fs.file(tempBundlePath).delete(); } } /** * Handle base64ts output mode */ private async handleBase64TsOutput( bundleConfig: interfaces.IBundleConfig, tempBundlePath: string ): Promise { const base64Output = new Base64TsOutput(this.cwd); // Add the bundle itself const bundleContent = await plugins.fs.file(tempBundlePath).read(); base64Output.addFile('bundle.js', bundleContent); // Add included files if (bundleConfig.includeFiles && bundleConfig.includeFiles.length > 0) { for (const pattern of bundleConfig.includeFiles) { await base64Output.addFilesFromGlob(pattern); } } // Write the TypeScript output await base64Output.writeToFile(bundleConfig.to, bundleConfig.maxLineLength); } /** * Handle standard bundle output mode */ private async handleBundleOutput( bundleConfig: interfaces.IBundleConfig, tempBundlePath: string ): Promise { // Move bundle to final destination const toPath = plugins.smartpath.transform.toAbsolute(bundleConfig.to, this.cwd) as string; const toDir = plugins.path.dirname(toPath); await plugins.fs.directory(toDir).create(); const bundleContent = await plugins.fs.file(tempBundlePath).read(); await plugins.fs.file(toPath).write(bundleContent); console.log(`Bundle written to: ${bundleConfig.to}`); // Process included files (copy them) if (bundleConfig.includeFiles && bundleConfig.includeFiles.length > 0) { const htmlHandler = new HtmlHandler(); const outputDir = plugins.path.dirname(toPath); for (const pattern of bundleConfig.includeFiles) { await this.copyIncludedFiles(pattern, outputDir); } } } /** * Copy files matching a pattern to the output directory */ private async copyIncludedFiles(pattern: string, outputDir: string): Promise { const absolutePattern = plugins.smartpath.transform.toAbsolute(pattern, this.cwd) as string; const patternDir = plugins.path.dirname(absolutePattern); const patternBase = plugins.path.basename(absolutePattern); const isGlobPattern = patternBase.includes('*'); if (isGlobPattern) { const dirPath = patternDir.replace(/\/\*\*$/, ''); const dirExists = await plugins.fs.directory(dirPath).exists(); if (!dirExists) { console.log(`Directory does not exist: ${dirPath}`); return; } const isRecursive = pattern.includes('**'); let entries; if (isRecursive) { entries = await plugins.fs.directory(dirPath).recursive().list(); } else { entries = await plugins.fs.directory(dirPath).list(); } const filePattern = patternBase.replace('*', '.*'); const regex = new RegExp(filePattern); for (const entry of entries) { if (!entry.isDirectory && regex.test(entry.name)) { const fullPath = plugins.path.join(dirPath, entry.path); const relativePath = plugins.path.relative(this.cwd, fullPath); const destPath = plugins.path.join(outputDir, plugins.path.basename(entry.path)); await plugins.fs.directory(plugins.path.dirname(destPath)).create(); await plugins.fs.file(fullPath).copy(destPath); console.log(`Copied: ${relativePath} -> ${destPath}`); } } } else { const fileExists = await plugins.fs.file(absolutePattern).exists(); if (!fileExists) { console.log(`File does not exist: ${absolutePattern}`); return; } const fileName = plugins.path.basename(absolutePattern); const destPath = plugins.path.join(outputDir, fileName); await plugins.fs.file(absolutePattern).copy(destPath); console.log(`Copied: ${pattern} -> ${destPath}`); } } } /** * Run the custom bundle command */ export async function runCustomBundles(): Promise { const handler = new CustomBundleHandler(); const hasConfig = await handler.loadConfig(); if (!hasConfig) { return; } await handler.processAllBundles(); console.log('\nCustom bundle processing complete!'); }