Compare commits

...

4 Commits

Author SHA1 Message Date
6d88adcd1e v2.8.1
Some checks failed
Default (tags) / security (push) Successful in 40s
Default (tags) / test (push) Failing after 34s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-12 02:03:38 +00:00
4349571112 fix(readme): document maxLineLength option for base64ts output and add example and tip 2026-01-12 02:03:38 +00:00
b3080023ab v2.8.0
Some checks failed
Default (tags) / security (push) Successful in 34s
Default (tags) / test (push) Failing after 1m10s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-12 01:41:08 +00:00
8b6ae043a2 feat(tsbundle): add configurable maxLineLength for base64ts output and improve build/error handling in child builds 2026-01-12 01:41:08 +00:00
9 changed files with 121 additions and 55 deletions

View File

@@ -1,5 +1,22 @@
# Changelog # Changelog
## 2026-01-12 - 2.8.1 - fix(readme)
document maxLineLength option for base64ts output and add example and tip
- Add documented `maxLineLength` configuration option (number, default 0 = unlimited) for `base64ts` output.
- Include example config showing `maxLineLength: 200`.
- Add a tip recommending setting `maxLineLength` to split long base64 strings when using AI tools with line-length limits.
## 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) ## 2026-01-12 - 2.7.4 - fix(deps)
bump @push.rocks/smartcli dependency to ^4.0.20 bump @push.rocks/smartcli dependency to ^4.0.20

View File

@@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tsbundle", "name": "@git.zone/tsbundle",
"version": "2.7.4", "version": "2.8.1",
"private": false, "private": false,
"description": "a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects", "description": "a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",

View File

@@ -91,6 +91,7 @@ tsbundle uses `npmextra.json` for configuration. Here's an example:
| `bundler` | `"esbuild"` \| `"rolldown"` \| `"rspack"` | `"esbuild"` | Which bundler to use | | `bundler` | `"esbuild"` \| `"rolldown"` \| `"rspack"` | `"esbuild"` | Which bundler to use |
| `production` | `boolean` | `false` | Enable minification | | `production` | `boolean` | `false` | Enable minification |
| `includeFiles` | `string[]` | `[]` | Glob patterns for additional files (HTML, assets) | | `includeFiles` | `string[]` | `[]` | Glob patterns for additional files (HTML, assets) |
| `maxLineLength` | `number` | `0` (unlimited) | For `base64ts` mode: max chars per line in output |
### Output Modes ### Output Modes
@@ -108,6 +109,8 @@ export const files: { path: string; contentBase64: string }[] = [
]; ];
``` ```
**Tip:** If you're working with AI tools that have line length limitations, set `maxLineLength` (e.g., `200`) to split long base64 strings across multiple lines.
## Available Bundlers 🔧 ## Available Bundlers 🔧
tsbundle supports three modern bundlers: tsbundle supports three modern bundlers:
@@ -241,7 +244,8 @@ Config:
"outputMode": "base64ts", "outputMode": "base64ts",
"bundler": "esbuild", "bundler": "esbuild",
"production": true, "production": true,
"includeFiles": ["./html/index.html"] "includeFiles": ["./html/index.html"],
"maxLineLength": 200
} }
] ]
} }

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tsbundle', name: '@git.zone/tsbundle',
version: '2.7.4', version: '2.8.1',
description: 'a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects' description: 'a multi-bundler tool supporting esbuild, rolldown, and rspack for painless bundling of web projects'
} }

View File

@@ -24,6 +24,7 @@ export interface IBundleConfig {
bundler?: TBundler; bundler?: TBundler;
production?: boolean; production?: boolean;
includeFiles?: string[]; includeFiles?: string[];
maxLineLength?: number; // For base64ts output: max chars per line. 0 or undefined = unlimited (default)
} }
export interface ITsbundleConfig { export interface ITsbundleConfig {

View File

@@ -61,15 +61,21 @@ export class CustomBundleHandler {
// Build the bundle to temp location // Build the bundle to temp location
const tsbundle = new TsBundle(); const tsbundle = new TsBundle();
await tsbundle.build( try {
this.cwd, await tsbundle.build(
bundleConfig.from, this.cwd,
tempBundlePath, bundleConfig.from,
{ tempBundlePath,
bundler, {
production: bundleConfig.production || false, 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') { if (outputMode === 'base64ts') {
await this.handleBase64TsOutput(bundleConfig, tempBundlePath); await this.handleBase64TsOutput(bundleConfig, tempBundlePath);
@@ -105,7 +111,7 @@ export class CustomBundleHandler {
} }
// Write the TypeScript output // Write the TypeScript output
await base64Output.writeToFile(bundleConfig.to); await base64Output.writeToFile(bundleConfig.to, bundleConfig.maxLineLength);
} }
/** /**

View File

@@ -79,41 +79,65 @@ export class TsBundleProcess {
} }
const run = async () => { const run = async () => {
console.log('running spawned compilation process'); try {
const transportOptions: interfaces.IEnvTransportOptions = JSON.parse( console.log('running spawned compilation process');
process.env.transportOptions, 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,
); );
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);
} }
}; };

View File

@@ -77,18 +77,26 @@ export class Base64TsOutput {
/** /**
* Generate TypeScript file content * Generate TypeScript file content
* @param maxLineLength - Max chars per line for base64 strings. 0 or undefined = unlimited (default)
*/ */
public generateTypeScript(): string { public generateTypeScript(maxLineLength?: number): string {
const MAX_LINE_LENGTH = 200; // 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 => { const formatBase64 = (base64: string): string => {
if (base64.length <= MAX_LINE_LENGTH) { if (base64.length <= maxLineLength) {
return `"${base64}"`; return `"${base64}"`;
} }
const chunks: string[] = []; const chunks: string[] = [];
for (let i = 0; i < base64.length; i += MAX_LINE_LENGTH) { for (let i = 0; i < base64.length; i += maxLineLength) {
chunks.push(base64.slice(i, i + MAX_LINE_LENGTH)); chunks.push(base64.slice(i, i + maxLineLength));
} }
return `"" +\n "${chunks.join('" +\n "')}"`; return `"" +\n "${chunks.join('" +\n "')}"`;
@@ -110,12 +118,14 @@ ${filesFormatted}
/** /**
* Write the TypeScript file to disk * 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<void> { public async writeToFile(outputPath: string, maxLineLength?: number): Promise<void> {
const absolutePath = plugins.smartpath.transform.toAbsolute(outputPath, this.cwd) as string; const absolutePath = plugins.smartpath.transform.toAbsolute(outputPath, this.cwd) as string;
const outputDir = plugins.path.dirname(absolutePath); const outputDir = plugins.path.dirname(absolutePath);
await plugins.fs.directory(outputDir).create(); await plugins.fs.directory(outputDir).create();
const content = this.generateTypeScript(); const content = this.generateTypeScript(maxLineLength);
await plugins.fs.file(absolutePath).encoding('utf8').write(content); await plugins.fs.file(absolutePath).encoding('utf8').write(content);
console.log(`Generated base64ts output: ${outputPath}`); console.log(`Generated base64ts output: ${outputPath}`);
} }

View File

@@ -45,7 +45,11 @@ export class TsBundle {
); );
const childProcess = await threadsimple.start(); const childProcess = await threadsimple.start();
childProcess.on('exit', (status) => { 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; await done.promise;
} }