From 414e845f128011369b451a3ffd15d8d314351cdb Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 15 Oct 2026 14:08:25 +0100 Subject: [PATCH] fix(better-auth): keep package subpaths in sync with build entries (#11822) --- .changeset/plugin-subpath-exports.md | 7 +++ packages/better-auth/package.json | 22 ++++++++ .../better-auth/src/package-exports.test.ts | 56 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .changeset/plugin-subpath-exports.md create mode 100644 packages/better-auth/src/package-exports.test.ts diff --git a/.changeset/plugin-subpath-exports.md b/.changeset/plugin-subpath-exports.md new file mode 100644 index 0000000000..0b58b9344b --- /dev/null +++ b/.changeset/plugin-subpath-exports.md @@ -0,0 +1,7 @@ +--- +"better-auth": patch +--- + +You can now import the Captcha and OpenAPI plugins from `better-auth/plugins/captcha` and `better-auth/plugins/open-api`, as you can for other plugins. Previously these imports failed with a "not exported" error. Imports from `better-auth/plugins` are unchanged. + +If your TypeScript config uses `"moduleResolution": "node"` (or `node10`), `better-auth/plugins/multi-session` and `better-auth/test` now resolve their types. Previously TypeScript reported that it could not find their declarations. Projects using `bundler`, `node16`, or `nodenext` resolution are unaffected. diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json index f9df5dd7ea..35252b2e4b 100644 --- a/packages/better-auth/package.json +++ b/packages/better-auth/package.json @@ -231,6 +231,11 @@ "types": "./dist/plugins/bearer/index.d.mts", "default": "./dist/plugins/bearer/index.mjs" }, + "./plugins/captcha": { + "dev-source": "./src/plugins/captcha/index.ts", + "types": "./dist/plugins/captcha/index.d.mts", + "default": "./dist/plugins/captcha/index.mjs" + }, "./plugins/custom-session": { "dev-source": "./src/plugins/custom-session/index.ts", "types": "./dist/plugins/custom-session/index.d.mts", @@ -291,6 +296,11 @@ "types": "./dist/plugins/one-tap/index.d.mts", "default": "./dist/plugins/one-tap/index.mjs" }, + "./plugins/open-api": { + "dev-source": "./src/plugins/open-api/index.ts", + "types": "./dist/plugins/open-api/index.d.mts", + "default": "./dist/plugins/open-api/index.mjs" + }, "./plugins/phone-number": { "dev-source": "./src/plugins/phone-number/index.ts", "types": "./dist/plugins/phone-number/index.d.mts", @@ -370,6 +380,9 @@ "api": [ "./dist/api/index.d.mts" ], + "test": [ + "./dist/test-utils/index.d.mts" + ], "db": [ "./dist/db/index.d.mts" ], @@ -430,6 +443,9 @@ "plugins/bearer": [ "./dist/plugins/bearer/index.d.mts" ], + "plugins/captcha": [ + "./dist/plugins/captcha/index.d.mts" + ], "plugins/custom-session": [ "./dist/plugins/custom-session/index.d.mts" ], @@ -451,12 +467,18 @@ "plugins/one-tap": [ "./dist/plugins/one-tap/index.d.mts" ], + "plugins/open-api": [ + "./dist/plugins/open-api/index.d.mts" + ], "plugins/jwt": [ "./dist/plugins/jwt/index.d.mts" ], "plugins/magic-link": [ "./dist/plugins/magic-link/index.d.mts" ], + "plugins/multi-session": [ + "./dist/plugins/multi-session/index.d.mts" + ], "plugins/organization": [ "./dist/plugins/organization/index.d.mts" ], diff --git a/packages/better-auth/src/package-exports.test.ts b/packages/better-auth/src/package-exports.test.ts new file mode 100644 index 0000000000..497c9afd7c --- /dev/null +++ b/packages/better-auth/src/package-exports.test.ts @@ -0,0 +1,56 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const pkgDir = path.resolve(import.meta.dirname, ".."); +const pkg: { + exports: Record; + typesVersions: { "*": Record }; +} = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf8")); +const entries = [ + ...readFileSync(path.join(pkgDir, "tsdown.config.ts"), "utf8").matchAll( + /"(\.\/src\/[^"]+\.ts)"/g, + ), +].map((match) => match[1]!); + +/** + * Build entries that intentionally have no subpath export. + */ +const internalEntries = new Set([ + // Lazy-loaded by the built-in Kysely adapter; users import `@better-auth/kysely-adapter`. + "./src/adapters/kysely-adapter/index.ts", +]); + +const toTypes = (entry: string) => + entry.replace("./src/", "./dist/").replace(/\.ts$/, ".d.mts"); + +/** + * @see https://github.com/better-auth/better-auth/issues/10172 + */ +describe("package.json subpaths", () => { + it("reads the build entries from tsdown.config.ts", () => { + expect(entries).toContain("./src/index.ts"); + }); + + it.each(entries.filter((entry) => !internalEntries.has(entry)))( + "exposes %s through exports and typesVersions", + (entry) => { + const subpath = Object.keys(pkg.exports).find( + (key) => pkg.exports[key]!["dev-source"] === entry, + ); + expect(subpath, `no exports entry for ${entry}`).toBeDefined(); + expect(pkg.exports[subpath!]!.types).toBe(toTypes(entry)); + expect( + pkg.typesVersions["*"][subpath === "." ? "*" : subpath!.slice(2)], + ).toEqual([toTypes(entry)]); + }, + ); + + it("builds every exported subpath", () => { + for (const [subpath, target] of Object.entries(pkg.exports)) { + expect(entries, `${subpath} is not a build entry`).toContain( + target["dev-source"], + ); + } + }); +});