From 8b6ae043a2017988f0c85a3abb85a95cb3ea7a82 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 12 Jan 2026 01:41:08 +0000 Subject: [PATCH] feat(tsbundle): add configurable maxLineLength for base64ts output and improve build/error handling in child builds --- changelog.md | 10 ++++ ts/00_commitinfo_data.ts | 2 +- ts/interfaces/index.ts | 1 + ts/mod_custom/index.ts | 26 ++++++---- ts/mod_esbuild/index.child.ts | 92 ++++++++++++++++++++++------------- ts/mod_output/index.ts | 24 ++++++--- ts/tsbundle.class.tsbundle.ts | 6 ++- 7 files changed, 108 insertions(+), 53 deletions(-) diff --git a/changelog.md b/changelog.md index c98c30e..246bedd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Changelog +## 2026-01-12 - 2.8.0 - feat(tsbundle) +add configurable maxLineLength for base64ts output and improve build/error handling in child builds + +- Add optional maxLineLength?: number to IBundleConfig to control max characters per line for base64ts output (0 or undefined = unlimited). +- Support splitting base64 strings when maxLineLength is specified; generateTypeScript(maxLineLength?) and writeToFile(outputPath, maxLineLength?) updated to accept and apply this setting. +- Pass bundleConfig.maxLineLength through in mod_custom so base64ts output respects bundle configuration. +- Wrap TsBundle.build in mod_custom with try/catch to log failures and skip output handling when build fails. +- tsbundle.class now rejects the bundle promise when the child process exits with a non-zero status. +- mod_esbuild child process now awaits builds, exits with appropriate success/failure codes, and formats esbuild errors for clearer console output. + ## 2026-01-12 - 2.7.4 - fix(deps) bump @push.rocks/smartcli dependency to ^4.0.20 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 82f591f..6269877 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tsbundle', - version: '2.7.4', + version: '2.8.0', description: 'a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects' } diff --git a/ts/interfaces/index.ts b/ts/interfaces/index.ts index 5858162..db3921d 100644 --- a/ts/interfaces/index.ts +++ b/ts/interfaces/index.ts @@ -24,6 +24,7 @@ export interface IBundleConfig { bundler?: TBundler; production?: boolean; includeFiles?: string[]; + maxLineLength?: number; // For base64ts output: max chars per line. 0 or undefined = unlimited (default) } export interface ITsbundleConfig { diff --git a/ts/mod_custom/index.ts b/ts/mod_custom/index.ts index 9e4ae66..b9be741 100644 --- a/ts/mod_custom/index.ts +++ b/ts/mod_custom/index.ts @@ -61,15 +61,21 @@ export class CustomBundleHandler { // Build the bundle to temp location const tsbundle = new TsBundle(); - await tsbundle.build( - this.cwd, - bundleConfig.from, - tempBundlePath, - { - bundler, - production: bundleConfig.production || false, - } - ); + 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); @@ -105,7 +111,7 @@ export class CustomBundleHandler { } // Write the TypeScript output - await base64Output.writeToFile(bundleConfig.to); + await base64Output.writeToFile(bundleConfig.to, bundleConfig.maxLineLength); } /** diff --git a/ts/mod_esbuild/index.child.ts b/ts/mod_esbuild/index.child.ts index ed9e686..29aa1f1 100644 --- a/ts/mod_esbuild/index.child.ts +++ b/ts/mod_esbuild/index.child.ts @@ -79,41 +79,65 @@ export class TsBundleProcess { } const run = async () => { - console.log('running spawned compilation process'); - const transportOptions: interfaces.IEnvTransportOptions = JSON.parse( - process.env.transportOptions, - ); - console.log('=======> ESBUILD'); - console.log(transportOptions); - process.chdir(transportOptions.cwd); - console.log(`switched to ${process.cwd()}`); - const tsbundleProcessInstance = new TsBundleProcess(); - if (transportOptions.mode === 'test') { - console.log('building for test:'); - tsbundleProcessInstance.buildTest( - plugins.smartpath.transform.makeAbsolute( - transportOptions.from, - process.cwd(), - ), - plugins.smartpath.transform.makeAbsolute( - transportOptions.to, - process.cwd(), - ), - transportOptions.argv, - ); - } else { - console.log('building for production:'); - tsbundleProcessInstance.buildProduction( - plugins.smartpath.transform.makeAbsolute( - transportOptions.from, - process.cwd(), - ), - plugins.smartpath.transform.makeAbsolute( - transportOptions.to, - process.cwd(), - ), - transportOptions.argv, + try { + console.log('running spawned compilation process'); + const transportOptions: interfaces.IEnvTransportOptions = JSON.parse( + process.env.transportOptions, ); + console.log('=======> ESBUILD'); + console.log(transportOptions); + process.chdir(transportOptions.cwd); + console.log(`switched to ${process.cwd()}`); + const tsbundleProcessInstance = new TsBundleProcess(); + if (transportOptions.mode === 'test') { + console.log('building for test:'); + await tsbundleProcessInstance.buildTest( + plugins.smartpath.transform.makeAbsolute( + transportOptions.from, + process.cwd(), + ), + plugins.smartpath.transform.makeAbsolute( + transportOptions.to, + process.cwd(), + ), + transportOptions.argv, + ); + } else { + console.log('building for production:'); + await tsbundleProcessInstance.buildProduction( + plugins.smartpath.transform.makeAbsolute( + transportOptions.from, + process.cwd(), + ), + plugins.smartpath.transform.makeAbsolute( + transportOptions.to, + process.cwd(), + ), + transportOptions.argv, + ); + } + process.exit(0); + } catch (error: any) { + console.error('\n\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); + console.error('\x1b[31m❌ BUILD FAILED\x1b[0m'); + console.error('\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n'); + + if (error.errors && Array.isArray(error.errors)) { + // esbuild errors - format them nicely + console.error(`Found ${error.errors.length} error(s):\n`); + for (const err of error.errors) { + const file = err.location?.file || 'unknown'; + const line = err.location?.line || '?'; + const column = err.location?.column || '?'; + console.error(` \x1b[36m${file}\x1b[0m:\x1b[33m${line}\x1b[0m:\x1b[33m${column}\x1b[0m`); + console.error(` ${err.text}\n`); + } + } else { + console.error(error.message || error); + } + + console.error('\x1b[31m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m\n'); + process.exit(1); } }; diff --git a/ts/mod_output/index.ts b/ts/mod_output/index.ts index 8095c12..abf70ee 100644 --- a/ts/mod_output/index.ts +++ b/ts/mod_output/index.ts @@ -77,18 +77,26 @@ export class Base64TsOutput { /** * Generate TypeScript file content + * @param maxLineLength - Max chars per line for base64 strings. 0 or undefined = unlimited (default) */ - public generateTypeScript(): string { - const MAX_LINE_LENGTH = 200; + public generateTypeScript(maxLineLength?: number): string { + // Default behavior: no line splitting (unlimited) + if (!maxLineLength || maxLineLength <= 0) { + const filesJson = JSON.stringify(this.files, null, 2); + return `// Auto-generated by tsbundle - do not edit +export const files: { path: string; contentBase64: string }[] = ${filesJson}; +`; + } + // Split base64 strings into chunks when maxLineLength is specified const formatBase64 = (base64: string): string => { - if (base64.length <= MAX_LINE_LENGTH) { + if (base64.length <= maxLineLength) { return `"${base64}"`; } const chunks: string[] = []; - for (let i = 0; i < base64.length; i += MAX_LINE_LENGTH) { - chunks.push(base64.slice(i, i + MAX_LINE_LENGTH)); + for (let i = 0; i < base64.length; i += maxLineLength) { + chunks.push(base64.slice(i, i + maxLineLength)); } return `"" +\n "${chunks.join('" +\n "')}"`; @@ -110,12 +118,14 @@ ${filesFormatted} /** * Write the TypeScript file to disk + * @param outputPath - Output file path + * @param maxLineLength - Max chars per line for base64 strings. 0 or undefined = unlimited (default) */ - public async writeToFile(outputPath: string): Promise { + public async writeToFile(outputPath: string, maxLineLength?: number): Promise { const absolutePath = plugins.smartpath.transform.toAbsolute(outputPath, this.cwd) as string; const outputDir = plugins.path.dirname(absolutePath); await plugins.fs.directory(outputDir).create(); - const content = this.generateTypeScript(); + const content = this.generateTypeScript(maxLineLength); await plugins.fs.file(absolutePath).encoding('utf8').write(content); console.log(`Generated base64ts output: ${outputPath}`); } diff --git a/ts/tsbundle.class.tsbundle.ts b/ts/tsbundle.class.tsbundle.ts index 0da5fde..5fe2b91 100644 --- a/ts/tsbundle.class.tsbundle.ts +++ b/ts/tsbundle.class.tsbundle.ts @@ -45,7 +45,11 @@ export class TsBundle { ); const childProcess = await threadsimple.start(); childProcess.on('exit', (status) => { - done.resolve(); + if (status !== 0) { + done.reject(new Error(`Bundle build failed with exit code ${status}`)); + } else { + done.resolve(); + } }); await done.promise; }