Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ oclif.manifest.json
dist
coverage
test/config/src/__gen__/
playground
/playground
test/codegen/generators/*/output
.claude
37 changes: 37 additions & 0 deletions docs/migrations/v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ sidebar_position: 99
* [Breaking Changes 0.61.0](#breaking-changes-0610)
+ [Channels Multi-File Output](#channels-multi-file-output)
* [Breaking Changes 0.64.2](#breaking-changes-0642)
* [Breaking Changes 0.71.0](#breaking-changes-0710)
+ [Library API Type Changes](#library-api-type-changes)

<!-- tocstop -->

Expand Down Expand Up @@ -123,10 +125,45 @@ outputPath/

Upgraded node to minimum v22.

## Breaking Changes 0.71.0

### Library API Type Changes

The `GenerationResult` and `GeneratorResult` types have changed to support browser-based generation (playground). This only affects users consuming the library programmatically - CLI users are not affected.

**Before (v0.70.x and earlier):**
```typescript
import { runGenerators } from '@the-codegen-project/cli';

const result = await runGenerators(context);

// Accessing results
console.log(result.totalFiles); // number
console.log(result.allFiles); // string[] (absolute paths)
console.log(result.generators[0].filesWritten); // string[] (absolute paths)
```

**After (v0.71.0+):**
```typescript
import { runGenerators } from '@the-codegen-project/cli';

const result = await runGenerators(context);

// Accessing results - now includes file content
console.log(result.files.length); // number (replaces totalFiles)
console.log(result.files); // GeneratedFile[]
console.log(result.generators[0].files); // GeneratedFile[]

// GeneratedFile shape:
interface GeneratedFile {
path: string; // Relative path (e.g., 'src/payloads/User.ts')
content: string; // Full file content
}
```

**Migration steps:**
1. Replace `result.totalFiles` with `result.files.length`
2. Replace `result.allFiles` with `result.files.map(f => f.path)`
3. Replace `generator.filesWritten` with `generator.files.map(f => f.path)`
4. Optionally leverage the new `content` property for in-memory processing

254 changes: 254 additions & 0 deletions esbuild.browser.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/**
* ESBuild configuration for browser bundle.
* Creates a browser-compatible bundle of the codegen library.
*/
import * as esbuild from 'esbuild';
import { polyfillNode } from 'esbuild-plugin-polyfill-node';
import path from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';

const require = createRequire(import.meta.url);

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const isWatch = process.argv.includes('--watch');
const isMinify = process.argv.includes('--minify');

// Custom plugin to alias fs to our browser shim
const fsShimPlugin = {
name: 'fs-shim',
setup(build) {
const shimPath = path.resolve(__dirname, 'src/browser/shims/fs.ts');

// Intercept fs imports
build.onResolve({ filter: /^(node:)?fs$/ }, () => {
return { path: shimPath };
});

// Also handle fs/promises
build.onResolve({ filter: /^(node:)?fs\/promises$/ }, () => {
return { path: shimPath };
});
},
};

// Custom plugin to redirect lodash to lodash-es for proper ESM support
// The @stoplight/spectral libraries use lodash but expect ES module named exports
const lodashEsPlugin = {
name: 'lodash-es',
setup(build) {
const lodashEsPath = path.dirname(require.resolve('lodash-es/package.json'));

// Redirect all lodash imports to lodash-es
build.onResolve({ filter: /^lodash$/ }, () => {
return { path: path.join(lodashEsPath, 'lodash.js') };
});

// Also handle lodash/xxx imports -> lodash-es/xxx
build.onResolve({ filter: /^lodash\/(.+)$/ }, (args) => {
const subpath = args.path.replace('lodash/', '');
return { path: path.join(lodashEsPath, `${subpath}.js`) };
});
},
};

// Custom plugin to shim @apidevtools/json-schema-ref-parser for browser
// The library uses Node.js file system APIs that don't work in browsers
const jsonSchemaRefParserShimPlugin = {
name: 'json-schema-ref-parser-shim',
setup(build) {
const shimPath = path.resolve(__dirname, 'src/browser/shims/json-schema-ref-parser.ts');

// Intercept @apidevtools/json-schema-ref-parser imports (main and sub-paths)
build.onResolve({ filter: /^@apidevtools\/json-schema-ref-parser(\/.*)?$/ }, () => {
return { path: shimPath };
});
},
};

// Custom plugin to shim @apidevtools/swagger-parser for browser
// We don't use swagger-parser directly - we use @readme/openapi-parser
const swaggerParserShimPlugin = {
name: 'swagger-parser-shim',
setup(build) {
const shimPath = path.resolve(__dirname, 'src/browser/shims/swagger-parser.ts');

// Intercept @apidevtools/swagger-parser imports
build.onResolve({ filter: /^@apidevtools\/swagger-parser(\/.*)?$/ }, () => {
return { path: shimPath };
});
},
};

// Custom plugin to properly handle the @asyncapi/parser/browser UMD bundle
// The browser bundle needs special handling because it's UMD and exports differently
const asyncapiParserBrowserPlugin = {
name: 'asyncapi-parser-browser',
setup(build) {
const browserBundlePath = require.resolve('@asyncapi/parser/browser/index.js');

// Handle the browser bundle import in our shim
build.onResolve({ filter: /^@asyncapi\/parser\/browser$/ }, () => {
return { path: browserBundlePath };
});

// Transform the UMD bundle to properly export Parser for ESM
build.onLoad({ filter: /parser[\\/]browser[\\/]index\.js$/ }, async (args) => {
const fs = await import('fs');
const contents = fs.readFileSync(args.path, 'utf8');

// The UMD bundle pattern: sets module.exports in CommonJS, global in browser
// We need to create a proper ESM wrapper that captures the exports
return {
contents: `
// Create fake module/exports for the UMD bundle to write to
var module = { exports: {} };
var exports = module.exports;

// The original UMD bundle
${contents}

// Extract the AsyncAPIParser object that was set on module.exports
var AsyncAPIParserExports = module.exports;

// Re-export Parser for ESM consumption
export var Parser = AsyncAPIParserExports.Parser;
export default AsyncAPIParserExports;
`,
loader: 'js',
};
});
},
};

// Custom plugin to add SlowBuffer to the buffer module (for avsc compatibility)
// The polyfill uses @jspm/core which wraps buffer but doesn't export SlowBuffer
const slowBufferPlugin = {
name: 'slowbuffer-patch',
setup(build) {
// Patch the @jspm/core buffer wrapper to export SlowBuffer
build.onLoad({ filter: /@jspm[\\/]core[\\/]nodelibs[\\/]browser[\\/]buffer\.js$/ }, async (args) => {
const fs = await import('fs');
let contents = fs.readFileSync(args.path, 'utf8');

// Add SlowBuffer variable declaration after Buffer
// Original: var Buffer = exports.Buffer;
// Target: var Buffer = exports.Buffer;
// var SlowBuffer = exports.SlowBuffer;
contents = contents.replace(
'var Buffer = exports.Buffer;',
'var Buffer = exports.Buffer;\nvar SlowBuffer = exports.SlowBuffer;'
);

// Add SlowBuffer to the named exports
// Original: export { Buffer, INSPECT_MAX_BYTES, exports as default, kMaxLength };
// Target: export { Buffer, SlowBuffer, INSPECT_MAX_BYTES, exports as default, kMaxLength };
contents = contents.replace(
'export { Buffer,',
'export { Buffer, SlowBuffer,'
);

return { contents, loader: 'js' };
});
},
};

const buildOptions = {
entryPoints: ['src/browser/index.ts'],
bundle: true,
format: 'esm',
outfile: 'dist/browser/codegen.browser.mjs',
platform: 'browser',
target: 'es2020',
sourcemap: true,
minify: isMinify,
plugins: [
// Our custom shims must come first
fsShimPlugin,
// Shim json-schema-ref-parser for browser (no Node.js file system)
jsonSchemaRefParserShimPlugin,
// Shim swagger-parser (we use @readme/openapi-parser instead)
swaggerParserShimPlugin,
// Handle @asyncapi/parser/browser UMD bundle properly
asyncapiParserBrowserPlugin,
// Redirect lodash to lodash-es for proper ESM support
lodashEsPlugin,
// Add SlowBuffer to buffer module for avsc compatibility
slowBufferPlugin,
// Polyfill other Node.js built-in modules
polyfillNode({
globals: {
process: true,
Buffer: true,
global: true,
},
polyfills: {
assert: true,
buffer: true,
crypto: true,
events: true,
// fs is handled by our custom shim
fs: false,
http: true,
https: true,
os: true,
path: true,
process: true,
stream: true,
string_decoder: true,
url: true,
util: true,
zlib: true,
},
}),
],
external: [
// Only mark things that truly can't be polyfilled
'inspector',
'node:inspector',
// JSON file that has import issues
'ajv/lib/refs/json-schema-draft-04.json',
],
// Define browser-friendly replacements
define: {
'process.env.NODE_ENV': '"production"',
'global': 'globalThis',
},
// Configure main fields resolution - prefer browser field
mainFields: ['browser', 'module', 'main'],
// Resolve extensions
resolveExtensions: ['.ts', '.js', '.mjs', '.cjs', '.json'],
// Handle the banner for ESM
// webapi-parser expects Ajv to be a global variable
banner: {
js: `// Browser bundle for The Codegen Project
// Generated by esbuild with Node.js polyfills

// Global Ajv placeholder for webapi-parser (will be set by require_ajv4)
var Ajv;
`
},
logLevel: 'info',
};

async function build() {
try {
if (isWatch) {
const context = await esbuild.context(buildOptions);
await context.watch();
console.log('Watching for changes...');
} else {
const result = await esbuild.build(buildOptions);
console.log('Browser bundle built successfully');
if (result.metafile) {
console.log(await esbuild.analyzeMetafile(result.metafile));
}
}
} catch (error) {
console.error('Build failed:', error);
process.exit(1);
}
}

build();
Loading
Loading