From 8d47d7cae6dbe9295248a7beff3f3dfdff2244fc Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 27 Jul 2026 11:11:55 +0200 Subject: [PATCH 1/7] fix: avoid dual package hazard by building single CJS version that's wrapped for ESM --- packages/livekit-rtc/package.json | 5 ++--- packages/livekit-rtc/scripts/postbuild.mjs | 24 ++++++++++++++++++++++ packages/livekit-rtc/tsup.config.ts | 6 ++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 packages/livekit-rtc/scripts/postbuild.mjs diff --git a/packages/livekit-rtc/package.json b/packages/livekit-rtc/package.json index b4ce7c47..8a93a833 100644 --- a/packages/livekit-rtc/package.json +++ b/packages/livekit-rtc/package.json @@ -4,8 +4,7 @@ "license": "Apache-2.0", "author": "LiveKit", "version": "0.13.31", - "main": "dist/index.js", - "require": "dist/index.cjs", + "main": "dist/index.cjs", "types": "dist/index.d.ts", "exports": { ".": { @@ -51,7 +50,7 @@ }, "scripts": { "prebuild": "node -p \"'export const SDK_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/version.ts", - "build": "pnpm prebuild && tsup --onSuccess \"tsc --declaration --emitDeclarationOnly\"", + "build": "pnpm prebuild && tsup && node scripts/postbuild.mjs", "lint": "eslint -f unix \"src/**/*.ts\" --ignore-pattern \"src/proto/*\"", "test": "vitest run src", "test:e2e": "node scripts/run-e2e.mjs" diff --git a/packages/livekit-rtc/scripts/postbuild.mjs b/packages/livekit-rtc/scripts/postbuild.mjs new file mode 100644 index 00000000..798d27f9 --- /dev/null +++ b/packages/livekit-rtc/scripts/postbuild.mjs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +// Runs after `tsup` (which emits the single CJS build + `.d.cts` declarations). +// Must NOT be wired into tsup's `onSuccess`: that hook fires before tsup's own +// DTS build, which then clobbers the declaration output — so any `.d.ts` emitted +// there gets wiped. Running here, after tsup has fully exited, is safe. +// +// 1. Emit the ESM-facing `.d.ts` declarations via tsc (tsup only produces the +// `.d.cts` files for the `require` condition). +// 2. Write the ESM entry point as a thin wrapper that re-exports the single CJS +// build. esbuild emits a cjs-module-lexer-friendly `module.exports` marker, +// so Node's ESM loader statically resolves every named export from +// `index.cjs` and `export *` needs no per-export maintenance. +// +// Keeping a single physical implementation (index.cjs) means `import` and +// `require` share one module instance, avoiding the dual-package hazard. +import { execSync } from 'node:child_process'; +import { writeFileSync } from 'node:fs'; + +execSync('tsc --declaration --emitDeclarationOnly', { stdio: 'inherit' }); + +writeFileSync(new URL('../dist/index.js', import.meta.url), "export * from './index.cjs';\n"); diff --git a/packages/livekit-rtc/tsup.config.ts b/packages/livekit-rtc/tsup.config.ts index 753158ca..5e367589 100644 --- a/packages/livekit-rtc/tsup.config.ts +++ b/packages/livekit-rtc/tsup.config.ts @@ -4,5 +4,11 @@ import defaults from '../../tsup.config'; export default defineConfig({ ...defaults, + // Emit a single CJS implementation only. A thin ESM wrapper + // (dist/index.js, written by scripts/write-esm-wrapper.mjs) re-exports from + // it so that `import` and `require` resolve to the same module instance. + // This avoids the dual-package hazard (duplicate class constructors) that + // bites consumers who depend on @livekit/rtc-node as a shared peer dependency. + format: ['cjs'], external: [/\.\/.*\.cjs/, /\.\/.*.node/], }); From a85a3b79f1e99ba29c62ad7e013e988413130cce Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 27 Jul 2026 11:13:08 +0200 Subject: [PATCH 2/7] Update postbuild.mjs --- packages/livekit-rtc/scripts/postbuild.mjs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/livekit-rtc/scripts/postbuild.mjs b/packages/livekit-rtc/scripts/postbuild.mjs index 798d27f9..6b2dfcdd 100644 --- a/packages/livekit-rtc/scripts/postbuild.mjs +++ b/packages/livekit-rtc/scripts/postbuild.mjs @@ -1,11 +1,8 @@ -// SPDX-FileCopyrightText: 2024 LiveKit, Inc. +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. // // SPDX-License-Identifier: Apache-2.0 // Runs after `tsup` (which emits the single CJS build + `.d.cts` declarations). -// Must NOT be wired into tsup's `onSuccess`: that hook fires before tsup's own -// DTS build, which then clobbers the declaration output — so any `.d.ts` emitted -// there gets wiped. Running here, after tsup has fully exited, is safe. // // 1. Emit the ESM-facing `.d.ts` declarations via tsc (tsup only produces the // `.d.cts` files for the `require` condition). From caca2daf7671636f15ae0aeae74f345d63edae4c Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 27 Jul 2026 11:13:31 +0200 Subject: [PATCH 3/7] Update CJS build to include ESM wrapper --- .changeset/fiery-swans-happen.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fiery-swans-happen.md diff --git a/.changeset/fiery-swans-happen.md b/.changeset/fiery-swans-happen.md new file mode 100644 index 00000000..73fa018e --- /dev/null +++ b/.changeset/fiery-swans-happen.md @@ -0,0 +1,5 @@ +--- +"@livekit/rtc-node": patch +--- + +fix: avoid dual package hazard by building single CJS version with an ESM wrapper From dd09c3a6ffe833914deef738782b7798123f3a91 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 27 Jul 2026 11:15:52 +0200 Subject: [PATCH 4/7] Update tsup.config.ts --- packages/livekit-rtc/tsup.config.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/livekit-rtc/tsup.config.ts b/packages/livekit-rtc/tsup.config.ts index 5e367589..0b7cd481 100644 --- a/packages/livekit-rtc/tsup.config.ts +++ b/packages/livekit-rtc/tsup.config.ts @@ -4,11 +4,8 @@ import defaults from '../../tsup.config'; export default defineConfig({ ...defaults, - // Emit a single CJS implementation only. A thin ESM wrapper - // (dist/index.js, written by scripts/write-esm-wrapper.mjs) re-exports from - // it so that `import` and `require` resolve to the same module instance. - // This avoids the dual-package hazard (duplicate class constructors) that - // bites consumers who depend on @livekit/rtc-node as a shared peer dependency. + // Emit a single CJS implementation only with a thin ESM wrapper + // to avoid dual-package hazard format: ['cjs'], external: [/\.\/.*\.cjs/, /\.\/.*.node/], }); From 50fff9a26addae970cd2858db01367ec82061b1e Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 27 Jul 2026 12:22:32 +0200 Subject: [PATCH 5/7] assert package artefact equality --- packages/livekit-rtc/scripts/assertPackageEquality.mjs | 9 +++++++++ packages/livekit-rtc/scripts/postbuild.mjs | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 packages/livekit-rtc/scripts/assertPackageEquality.mjs diff --git a/packages/livekit-rtc/scripts/assertPackageEquality.mjs b/packages/livekit-rtc/scripts/assertPackageEquality.mjs new file mode 100644 index 00000000..c86da60f --- /dev/null +++ b/packages/livekit-rtc/scripts/assertPackageEquality.mjs @@ -0,0 +1,9 @@ +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import * as esm from '../dist/index.js'; + +const require = createRequire(import.meta.url); +const cjs = require('../dist/index.cjs'); + +assert.equal(esm.Room, cjs.Room); +assert.equal(esm.AudioFrame, cjs.AudioFrame); diff --git a/packages/livekit-rtc/scripts/postbuild.mjs b/packages/livekit-rtc/scripts/postbuild.mjs index 798d27f9..6eeed986 100644 --- a/packages/livekit-rtc/scripts/postbuild.mjs +++ b/packages/livekit-rtc/scripts/postbuild.mjs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2024 LiveKit, Inc. // // SPDX-License-Identifier: Apache-2.0 - // Runs after `tsup` (which emits the single CJS build + `.d.cts` declarations). // Must NOT be wired into tsup's `onSuccess`: that hook fires before tsup's own // DTS build, which then clobbers the declaration output — so any `.d.ts` emitted @@ -22,3 +21,5 @@ import { writeFileSync } from 'node:fs'; execSync('tsc --declaration --emitDeclarationOnly', { stdio: 'inherit' }); writeFileSync(new URL('../dist/index.js', import.meta.url), "export * from './index.cjs';\n"); + +await import('./assertPackageEquality.mjs'); From 6d268e497cc31890d270dc0f8bc6798869203c2b Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 27 Jul 2026 12:36:13 +0200 Subject: [PATCH 6/7] reuse --- packages/livekit-rtc/scripts/assertPackageEquality.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/livekit-rtc/scripts/assertPackageEquality.mjs b/packages/livekit-rtc/scripts/assertPackageEquality.mjs index c86da60f..dc655809 100644 --- a/packages/livekit-rtc/scripts/assertPackageEquality.mjs +++ b/packages/livekit-rtc/scripts/assertPackageEquality.mjs @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 import assert from 'node:assert/strict'; import { createRequire } from 'node:module'; import * as esm from '../dist/index.js'; From 12a2fc43d46cd8a5f98a0593b7507870b6aa2053 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Tue, 28 Jul 2026 08:40:09 +0200 Subject: [PATCH 7/7] use cts for default types --- packages/livekit-rtc/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/livekit-rtc/package.json b/packages/livekit-rtc/package.json index 8a7135cd..bff67d35 100644 --- a/packages/livekit-rtc/package.json +++ b/packages/livekit-rtc/package.json @@ -5,7 +5,7 @@ "author": "LiveKit", "version": "0.13.32", "main": "dist/index.cjs", - "types": "dist/index.d.ts", + "types": "dist/index.d.cts", "exports": { ".": { "import": {