Skip to main content
praisonai-ts publishes both CJS and ESM outputs from a single TypeScript source, and a build shim called esm-shim.js post-processes the emitted ESM files so anything that still relies on require(...), __dirname, __filename, or module.exports keeps working at runtime under Node ESM.

How The Build Runs

The dual build is driven by two tsc passes wired through npm scripts in src/praisonai-ts/package.json.
The esm-shim.js step runs after tsc emits the ESM build into dist/esm/. It walks every .js file, fixes relative import specifiers, repoints internal require(...) calls at the CJS twin, and prepends the createRequire banner only where genuinely needed. Source files are never touched, and the CJS build in dist/ is unaffected.

What The Shim Does

The shim lives at src/praisonai-ts/scripts/esm-shim.js and exports four helpers. When a file needs the banner, applyBanner prepends CJS_BANNER, which imports createRequire from module, fileURLToPath from url, and dirname from path, then re-creates require, __filename, and __dirname at module scope (plus a module stub so require.main === module evaluates to false).
A shebang line (#!...) is preserved: the banner is inserted after it so the file stays executable.

Why Detection Is Non-Trivial

Detection has to avoid two opposite failure modes.
A plain string like "Zod schemas require zod-to-json-schema" used to trigger the banner and pull in unused Node built-in imports (module, url, path), making the bundled ESM strictly worse than the TypeScript source. The real-world case was dist/esm/llm/providers/openai.js, where an error message alone caused 24 of 288 files to gain a banner they never needed.
An earlier fix over-blanked template literals: a genuine require(...) or __dirname inside a ${...} interpolation was blanked along with the surrounding literal text, so the file emitted without the banner and threw ReferenceError: require is not defined at runtime under Node ESM. This is what PR #4436 fixed.

How stripStringsAndComments Works

needsCjsBanner first calls stripStringsAndComments, which blanks literal text and comments while keeping ${...} interpolations intact, then runs regex detectors on the cleaned code. Blanking preserves offsets and newlines β€” each token is replaced with same-length whitespace β€” so error messages and source-map-friendly positions still line up. Inside a ${...} interpolation the shim honors nested braces, nested strings and templates, regex literals (with } inside a character class), and line/block comments, so a stray } cannot close the interpolation early. The regex detectors then match real CJS usage:

What Does And Does Not Get Bannered

Use this table as the quick reference for expected behavior.

Regression Tests

The detector is guarded by src/praisonai-ts/tests/unit/packaging/esm-shim-banner.test.ts, split into two directions plus an end-to-end emit check. Run them from the TypeScript SDK folder:

When To Update This Page

Whenever a contributor changes needsCjsBanner, stripStringsAndComments, or the CJS_BANNER string, they should:
1

Add a matching test

Add a case to src/praisonai-ts/tests/unit/packaging/esm-shim-banner.test.ts in the matching describe block.
2

Update the behavior table

Update the β€œWhat Does And Does Not Get Bannered” table on this page if the observable behavior changes.

Scripts & Automation

Includes a TypeScript SDK scripts section

Development Setup

Setting up the dev environment