Skip to content

feat(typescript): add opt-in esmOnly flag for ESM-only SDK output - #17503

Open
thesandlord wants to merge 1 commit into
mainfrom
devin/1787321606-ts-sdk-esm-only
Open

feat(typescript): add opt-in esmOnly flag for ESM-only SDK output#17503
thesandlord wants to merge 1 commit into
mainfrom
devin/1787321606-ts-sdk-esm-only

Conversation

@thesandlord

@thesandlord thesandlord commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Description

Linear ticket: Refs (none — customer request, Auth0 Pylon #22973)

Adds an opt-in esmOnly custom config flag to the TypeScript SDK generator. When enabled, the generated package ships only the existing ESM build — decoupling it from the combined CJS package — to avoid the Dual Package Hazard. Nothing changes for the default (CJS + ESM) or outputEsm: true generations; esmOnly reuses the existing ESM pipeline (tsconfig.esm.json, rename-to-esm-files .mjs/.d.mts output) and simply drops the CJS half.

With esmOnly: true the generated package.json becomes:

{
    "type": "module",
    "main": "./dist/esm/index.mjs",
    "module": "./dist/esm/index.mjs",
    "types": "./dist/esm/index.d.mts",
    "exports": {
        ".": { "types": "./dist/esm/index.d.mts", "default": "./dist/esm/index.mjs" },
        "./package.json": "./package.json"
    },
    "scripts": { "build": "pnpm build:esm" }
}

tsconfig.cjs.json and build:cjs are not generated; root tsconfig.json extends tsconfig.esm.json. The bundled CJS rename script is emitted as scripts/rename-to-esm-files.cjs (instead of .js) because "type": "module" would otherwise make Node execute the CommonJS script as ESM.

Unsupported combinations fail fast with a clear error rather than silently ignoring the flag:

  • esmOnly + useLegacyExports
  • esmOnly + bundle

Changes Made

  • Config plumbing: esmOnly added to TypescriptCustomConfigSchema, SdkCustomConfig, threaded through SdkGeneratorCliSdkGeneratorSimpleTypescriptProject / AsIsManager
  • SimpleTypescriptProject: ESM-only package.json (type, main/types, exports map without require/CJS conditions, incl. subpackage exports), skip tsconfig.cjs.json, root tsconfig extends ESM config, build runs only build:esm
  • AsIsManager: emit rename script as .cjs when esmOnly is set
  • SdkGeneratorCli: throw on esmOnly + useLegacyExports and esmOnly + bundle
  • Seed: new simple-api/esm-only output variant (seed/ts-sdk/seed.yml) with generated fixture
  • Changelog: generators/typescript/sdk/changes/unreleased/add-esm-only-flag.yml
  • Updated README.md generator (if applicable) — N/A

Testing

  • Unit tests added/updated — new SimpleTypescriptProject.test.ts covering default (dual CJS+ESM), outputEsm: true (still dual), and esmOnly: true; all 33 tests in the package pass
  • Manual testing completed — generated the esm-only seed fixture, ran pnpm install && pnpm build (compiles, 126 files renamed to .mjs/.d.mts), verified Node import resolves root and /user subpath exports, and publint passes. Verified default and outputEsm fixtures are byte-identical to before (no snapshot changes with the flag off). Seed's Docker validator image could not be pulled in this environment (network-restricted), so full seed validation should run in CI.

Written by Devin


Open in Devin Review

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@github-actions

Copy link
Copy Markdown
Contributor

SDK Generation Benchmark Results

Comparing PR branch against median of 5 nightly run(s) on main (latest: 2026-08-21T04:13:10Z).

Full benchmark table (click to expand)
Generator Spec main (generator) main (E2E) PR (generator) Delta
ts-sdk square 152s (n=5) 181s (n=5) 184s +32s (+21.1%)

main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via fern generate). main (E2E): full customer-observable time including build/test scripts (nightly baseline, informational). Delta is computed against generator-only baseline.
⚠️ = generation exited with a non-zero exit code (timing may not reflect a successful run).
Baseline from nightly runs on main (latest: 2026-08-21T04:13:10Z). Trigger benchmark-baseline to refresh.
Last updated: 2026-08-21 16:31 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant