From ce965696d1f8695e96e6b88490fc8ea2f8ab78ae Mon Sep 17 00:00:00 2001 From: Christoph Nakazawa Date: Sat, 17 Oct 2026 06:35:12 +0000 Subject: [PATCH 1/7] perf(core): inline OpenTelemetry attribute constants (#11623) Co-authored-by: Taesu --- .../local-instrumentation-attributes.md | 5 +++ packages/core/package.json | 2 +- .../src/instrumentation/attributes.test.ts | 36 +++++++++++++++++++ .../core/src/instrumentation/attributes.ts | 26 +++++++------- pnpm-lock.yaml | 6 ++-- 5 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 .changeset/local-instrumentation-attributes.md create mode 100644 packages/core/src/instrumentation/attributes.test.ts diff --git a/.changeset/local-instrumentation-attributes.md b/.changeset/local-instrumentation-attributes.md new file mode 100644 index 0000000000..3764545ddc --- /dev/null +++ b/.changeset/local-instrumentation-attributes.md @@ -0,0 +1,5 @@ +--- +"@better-auth/core": patch +--- + +Reduce installation size by removing the semantic conventions production dependency while preserving all instrumentation attribute keys. diff --git a/packages/core/package.json b/packages/core/package.json index 901ce2a6b3..4f8f65b3b6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -164,7 +164,6 @@ } }, "dependencies": { - "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", "zod": "catalog:" }, @@ -175,6 +174,7 @@ "@opentelemetry/api": "^1.9.1", "@opentelemetry/sdk-trace-base": "^2.10.0", "@opentelemetry/sdk-trace-node": "^2.10.0", + "@opentelemetry/semantic-conventions": "^1.41.1", "better-call": "catalog:", "jose": "^6.2.3", "kysely": "catalog:", diff --git a/packages/core/src/instrumentation/attributes.test.ts b/packages/core/src/instrumentation/attributes.test.ts new file mode 100644 index 0000000000..2d9754c6bf --- /dev/null +++ b/packages/core/src/instrumentation/attributes.test.ts @@ -0,0 +1,36 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import * as semanticConventions from "@opentelemetry/semantic-conventions"; +import { describe, expect, it } from "vitest"; +import * as attributes from "./attributes"; + +const SEMANTIC_CONVENTIONS = "@opentelemetry/semantic-conventions"; + +/** + * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/db/ + * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/http/ + */ +describe("instrumentation attributes", () => { + it.for([ + "ATTR_DB_COLLECTION_NAME", + "ATTR_DB_OPERATION_NAME", + "ATTR_HTTP_RESPONSE_STATUS_CODE", + "ATTR_HTTP_ROUTE", + ] as const)("%s matches the OpenTelemetry semantic convention", (name) => { + expect(attributes[name]).toBe(semanticConventions[name]); + }); + + it("keeps the semantic conventions package out of production source", () => { + const srcDir = fileURLToPath(new URL("..", import.meta.url)); + const importers = readdirSync(srcDir, { recursive: true, encoding: "utf8" }) + .filter((file) => file.endsWith(".ts") && !file.endsWith(".test.ts")) + .filter((file) => + readFileSync(join(srcDir, file), "utf8").includes( + `"${SEMANTIC_CONVENTIONS}"`, + ), + ); + + expect(importers).toEqual([]); + }); +}); diff --git a/packages/core/src/instrumentation/attributes.ts b/packages/core/src/instrumentation/attributes.ts index 10bc1fdd89..de4d179622 100644 --- a/packages/core/src/instrumentation/attributes.ts +++ b/packages/core/src/instrumentation/attributes.ts @@ -1,16 +1,18 @@ -import { - ATTR_DB_COLLECTION_NAME, - ATTR_DB_OPERATION_NAME, - ATTR_HTTP_RESPONSE_STATUS_CODE, - ATTR_HTTP_ROUTE, -} from "@opentelemetry/semantic-conventions"; +// Keep these stable OpenTelemetry keys local to avoid installing the full +// semantic conventions registry just for four string constants. -export { - ATTR_DB_COLLECTION_NAME, - ATTR_DB_OPERATION_NAME, - ATTR_HTTP_RESPONSE_STATUS_CODE, - ATTR_HTTP_ROUTE, -}; +/** Database collection accessed by the operation. */ +export const ATTR_DB_COLLECTION_NAME = "db.collection.name" as const; + +/** Database operation performed by the span. */ +export const ATTR_DB_OPERATION_NAME = "db.operation.name" as const; + +/** HTTP response status code. */ +export const ATTR_HTTP_RESPONSE_STATUS_CODE = + "http.response.status_code" as const; + +/** Matched HTTP route template. */ +export const ATTR_HTTP_ROUTE = "http.route" as const; /** Operation identifier (e.g. getSession, signUpWithEmailAndPassword). Uses endpoint operationId when set, otherwise the endpoint key. */ export const ATTR_OPERATION_ID = "better_auth.operation_id" as const; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bede79117e..e382e02acb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1335,9 +1335,6 @@ importers: packages/core: dependencies: - '@opentelemetry/semantic-conventions': - specifier: ^1.41.1 - version: 1.41.1 '@standard-schema/spec': specifier: ^1.1.0 version: 1.1.0 @@ -1363,6 +1360,9 @@ importers: '@opentelemetry/sdk-trace-node': specifier: ^2.10.0 version: 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': + specifier: ^1.41.1 + version: 1.41.1 better-call: specifier: 'catalog:' version: 1.4.2(zod@4.5.4) From abee9506c3580690c079e1979494b188b9bea38e Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Sat, 17 Oct 2026 07:16:18 +0000 Subject: [PATCH 2/7] fix(e2e): prevent hangs during process tree cleanup (#11885) --- .../nextjs-demo/e2e/scim-demo.spec.ts | 6 +- e2e/integration/solid-vinxi/e2e/utils.ts | 3 +- .../sveltekit/e2e/sveltekit.spec.ts | 3 +- e2e/integration/test-utils/package.json | 5 +- e2e/integration/test-utils/src/playwright.ts | 52 ++++++++++++-- e2e/integration/test-utils/tsconfig.json | 7 ++ e2e/integration/vanilla-node/e2e/utils.ts | 8 ++- e2e/smoke/test/cloudflare.spec.ts | 24 ++++--- pnpm-lock.yaml | 72 +------------------ tsconfig.json | 3 + 10 files changed, 85 insertions(+), 98 deletions(-) create mode 100644 e2e/integration/test-utils/tsconfig.json diff --git a/e2e/integration/nextjs-demo/e2e/scim-demo.spec.ts b/e2e/integration/nextjs-demo/e2e/scim-demo.spec.ts index f97eff750b..659d1c673f 100644 --- a/e2e/integration/nextjs-demo/e2e/scim-demo.spec.ts +++ b/e2e/integration/nextjs-demo/e2e/scim-demo.spec.ts @@ -43,9 +43,9 @@ async function getAvailablePort(): Promise { async function stopProcess( child: ChildProcessWithoutNullStreams | undefined, ): Promise { - if (!child?.pid || child.exitCode !== null) return; + if (!child) return; const { terminate } = await import("@better-auth-test/test-utils/playwright"); - await terminate(child.pid); + await terminate(child); } async function startNextDemoRuntime(): Promise { @@ -108,7 +108,7 @@ async function startNextDemoRuntime(): Promise { ["dev", "--hostname", "127.0.0.1", "--port", String(port)], { cwd: demoDirectory, - detached: true, + detached: process.platform !== "win32", env: environment, stdio: "pipe", }, diff --git a/e2e/integration/solid-vinxi/e2e/utils.ts b/e2e/integration/solid-vinxi/e2e/utils.ts index bf605a3a11..a83c56ebfa 100644 --- a/e2e/integration/solid-vinxi/e2e/utils.ts +++ b/e2e/integration/solid-vinxi/e2e/utils.ts @@ -25,6 +25,7 @@ export function setup() { ref, start: async () => { clientChild = spawn("pnpm", ["run", "dev"], { + detached: process.platform !== "win32", cwd: root, stdio: "pipe", env: { @@ -58,7 +59,7 @@ export function setup() { ]); }, clean: async () => { - await terminate(clientChild.pid!); + await terminate(clientChild); }, }; } diff --git a/e2e/integration/sveltekit/e2e/sveltekit.spec.ts b/e2e/integration/sveltekit/e2e/sveltekit.spec.ts index c6cb336620..4eecc0ba47 100644 --- a/e2e/integration/sveltekit/e2e/sveltekit.spec.ts +++ b/e2e/integration/sveltekit/e2e/sveltekit.spec.ts @@ -11,6 +11,7 @@ let baseURL: string; test.beforeAll(async () => { server = spawn("pnpm", ["run", "dev"], { + detached: process.platform !== "win32", cwd: root, stdio: "pipe", env: { ...process.env, NO_COLOR: "1" }, @@ -28,7 +29,7 @@ test.beforeAll(async () => { }); test.afterAll(async () => { - if (server?.pid) await terminate(server.pid); + if (server) await terminate(server); }); /** diff --git a/e2e/integration/test-utils/package.json b/e2e/integration/test-utils/package.json index 94118399b8..ca9b8b9132 100644 --- a/e2e/integration/test-utils/package.json +++ b/e2e/integration/test-utils/package.json @@ -4,8 +4,5 @@ "exports": { "./playwright": "./src/playwright.ts" }, - "private": true, - "devDependencies": { - "terminate": "^2.8.0" - } + "private": true } diff --git a/e2e/integration/test-utils/src/playwright.ts b/e2e/integration/test-utils/src/playwright.ts index 0e2e61a128..2528b1b54b 100644 --- a/e2e/integration/test-utils/src/playwright.ts +++ b/e2e/integration/test-utils/src/playwright.ts @@ -1,8 +1,46 @@ -import { createRequire } from "node:module"; +import type { ChildProcess } from "node:child_process"; +import { execFile } from "node:child_process"; +import { once } from "node:events"; +import { promisify } from "node:util"; -export const terminate = createRequire(import.meta.url)( - // use terminate instead of cp.kill, - // because cp.kill will not kill the child process of the child process - // to avoid the zombie process - "terminate/promise", -) as (pid: number) => Promise; +// Spawn with detached: true on Unix so the child owns its process group. +export async function terminate(child: ChildProcess): Promise { + const pid = child.pid; + if (pid === undefined) return; + + const exited = child.exitCode !== null || child.signalCode !== null; + if (process.platform === "win32" && exited) { + return; + } + + const closed = exited + ? Promise.resolve() + : once(child, "close", { signal: AbortSignal.timeout(5000) }); + const killing = Promise.resolve().then(async () => { + if (process.platform === "win32") { + try { + await promisify(execFile)( + "taskkill", + ["/pid", String(pid), "/T", "/F"], + { + timeout: 5000, + windowsHide: true, + }, + ); + } catch (error) { + if (child.exitCode === null && child.signalCode === null) throw error; + } + } else { + try { + process.kill(-pid, "SIGKILL"); + } catch (error) { + if ( + !(error instanceof Error && "code" in error && error.code === "ESRCH") + ) { + throw error; + } + } + } + }); + await Promise.all([closed, killing]); +} diff --git a/e2e/integration/test-utils/tsconfig.json b/e2e/integration/test-utils/tsconfig.json new file mode 100644 index 0000000000..858dc5c45a --- /dev/null +++ b/e2e/integration/test-utils/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/e2e/integration/vanilla-node/e2e/utils.ts b/e2e/integration/vanilla-node/e2e/utils.ts index c1218f60a2..2c8831098e 100644 --- a/e2e/integration/vanilla-node/e2e/utils.ts +++ b/e2e/integration/vanilla-node/e2e/utils.ts @@ -62,6 +62,7 @@ export function setup(overrides?: Partial) { start: async () => { server = await createAuthServer(undefined, overrides); clientChild = spawn("pnpm", ["run", "start:client"], { + detached: process.platform !== "win32", cwd: root, stdio: "pipe", }); @@ -101,8 +102,11 @@ export function setup(overrides?: Partial) { ]); }, clean: async () => { - await terminate(clientChild.pid!); - server.close(); + try { + await terminate(clientChild); + } finally { + server.close(); + } }, }; } diff --git a/e2e/smoke/test/cloudflare.spec.ts b/e2e/smoke/test/cloudflare.spec.ts index d7ca77ad86..bc9fc7643f 100644 --- a/e2e/smoke/test/cloudflare.spec.ts +++ b/e2e/smoke/test/cloudflare.spec.ts @@ -29,20 +29,26 @@ describe("(cloudflare) simple server", () => { */ it("builds and runs the Worker", async (t) => { const cp = spawn("pnpm", ["run", "e2e:smoke"], { + detached: process.platform !== "win32", cwd: join(fixturesDir, "cloudflare"), stdio: "pipe", }); - const stopProcessTree = async () => { - if ( - cp.pid === undefined || - cp.exitCode !== null || - cp.signalCode !== null - ) { - return; - } - await terminate(cp.pid); + const stopProcessTree = () => terminate(cp); + const stopOnSignal = (signal: NodeJS.Signals) => { + const exitWithSignal = () => { + process.off("SIGINT", stopOnSignal); + process.off("SIGTERM", stopOnSignal); + process.kill(process.pid, signal); + }; + void stopProcessTree().then(exitWithSignal, exitWithSignal); }; + process.on("SIGINT", stopOnSignal); + process.on("SIGTERM", stopOnSignal); + cp.once("close", () => { + process.off("SIGINT", stopOnSignal); + process.off("SIGTERM", stopOnSignal); + }); t.after(stopProcessTree); const unexpectedWarnings = new Set(["node:sqlite", "node:async_hooks"]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e382e02acb..53d75f2c35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -811,11 +811,7 @@ importers: specifier: 8.2.0 version: 8.2.0(@types/node@25.9.4)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) - e2e/integration/test-utils: - devDependencies: - terminate: - specifier: ^2.8.0 - version: 2.8.0 + e2e/integration/test-utils: {} e2e/integration/vanilla-node: dependencies: @@ -9902,9 +9898,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-stream@3.3.4: - resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -10288,9 +10281,6 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} - from@0.1.7: - resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - fromentries@1.3.2: resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} @@ -11699,9 +11689,6 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - map-stream@0.1.0: - resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - mariadb@3.4.5: resolution: {integrity: sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==} engines: {node: '>= 14'} @@ -12807,9 +12794,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pause-stream@0.0.11: - resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -13299,11 +13283,6 @@ packages: resolution: {integrity: sha512-lxMEoIL/BQlk2KunFzxwUPwMvjFH7x7cmvzSLsSHpyMXl9FFfLUlfKrYwFc4wx/ZaIxxuXC4n8rjQ1CX/tkXVQ==} engines: {node: '>=20'} - ps-tree@1.2.0: - resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} - engines: {node: '>= 0.10'} - hasBin: true - publint@0.3.21: resolution: {integrity: sha512-OqejcnMV6E9zel2oCrUOJEiiFkGiAAni0A6ibfQNh1k9Gu5z4F+Yso8lllam7AzmV6Do0vp7u3UpZNRBwuXaHQ==} engines: {node: '>=18'} @@ -14210,9 +14189,6 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - split@0.3.3: - resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -14277,9 +14253,6 @@ packages: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} engines: {node: '>= 0.10.0'} - stream-combiner@0.0.4: - resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -14540,10 +14513,6 @@ packages: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} - terminate@2.8.0: - resolution: {integrity: sha512-bcbjJEg0wY5nuJXvGxxHfmoEPkyHLCctUKO6suwtxy7jVSgGcgPeGwpbLDLELFhIaxCGRr3dPvyNg1yuz2V0eg==} - engines: {node: '>=12'} - terracotta@1.1.0: resolution: {integrity: sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww==} engines: {node: '>=10'} @@ -14585,9 +14554,6 @@ packages: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} @@ -25980,16 +25946,6 @@ snapshots: etag@1.8.1: {} - event-stream@3.3.4: - dependencies: - duplexer: 0.1.2 - from: 0.1.7 - map-stream: 0.1.0 - pause-stream: 0.0.11 - split: 0.3.3 - stream-combiner: 0.0.4 - through: 2.3.8 - event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -26511,8 +26467,6 @@ snapshots: fresh@2.0.0: {} - from@0.1.7: {} - fromentries@1.3.2: {} fs-constants@1.0.0: {} @@ -27911,8 +27865,6 @@ snapshots: dependencies: tmpl: 1.0.5 - map-stream@0.1.0: {} - mariadb@3.4.5: dependencies: '@types/geojson': 7946.0.16 @@ -30235,10 +30187,6 @@ snapshots: pathe@2.0.3: {} - pause-stream@0.0.11: - dependencies: - through: 2.3.8 - perfect-debounce@1.0.0: {} perfect-debounce@2.1.0: {} @@ -30702,10 +30650,6 @@ snapshots: ps-list@9.0.0: {} - ps-tree@1.2.0: - dependencies: - event-stream: 3.3.4 - publint@0.3.21: dependencies: '@publint/pack': 0.1.4 @@ -32160,10 +32104,6 @@ snapshots: split2@4.2.0: {} - split@0.3.3: - dependencies: - through: 2.3.8 - sprintf-js@1.0.3: {} sprintf-js@1.1.3: {} @@ -32205,10 +32145,6 @@ snapshots: stream-buffers@2.2.0: {} - stream-combiner@0.0.4: - dependencies: - duplexer: 0.1.2 - streamx@2.23.0: dependencies: events-universal: 1.0.1 @@ -32526,10 +32462,6 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terminate@2.8.0: - dependencies: - ps-tree: 1.2.0 - terracotta@1.1.0(solid-js@1.9.11): dependencies: solid-js: 1.9.11 @@ -32572,8 +32504,6 @@ snapshots: throttleit@2.1.0: {} - through@2.3.8: {} - tiny-inflate@1.0.3: {} tiny-invariant@1.3.3: {} diff --git a/tsconfig.json b/tsconfig.json index a9ad1f99a6..d246b4e24c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -84,6 +84,9 @@ { "path": "./e2e/adapter/tsconfig.json" }, + { + "path": "./e2e/integration/test-utils/tsconfig.json" + }, { "path": "./test/tsconfig.json" } From c875131deca3cf0767796ce134deff37bea0514f Mon Sep 17 00:00:00 2001 From: "starslingdev[bot]" <248995740+starslingdev[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 07:28:51 +0000 Subject: [PATCH 3/7] ci: move StarSling jobs to Ubuntu 26.04 (#11733) Co-authored-by: starslingdev[bot] <248995740+starslingdev[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- .github/workflows/claude.yml | 2 +- .github/workflows/e2e.yml | 9 ++++++--- .github/workflows/preview.yml | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 220eb9d3a3..abc776efe5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ env: jobs: lint: - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} permissions: contents: read concurrency: @@ -91,7 +91,7 @@ jobs: run: pnpm lint:types typecheck: - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} permissions: contents: read concurrency: @@ -126,7 +126,7 @@ jobs: run: pnpm typecheck:dist test: - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} permissions: contents: read timeout-minutes: 30 diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 26c3c6347a..a7fe951d4c 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -21,7 +21,7 @@ jobs: (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), github.event.review.author_association)) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), github.event.issue.author_association)) ) - runs-on: ${{ github.repository == 'better-auth/better-auth' && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ github.repository == 'better-auth/better-auth' && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} permissions: contents: write pull-requests: write diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 46ca823367..a4ed3c77f8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -20,7 +20,7 @@ jobs: smoke: name: Smoke test (${{ matrix.node-version }}) timeout-minutes: 30 - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} strategy: fail-fast: false matrix: @@ -81,11 +81,14 @@ jobs: integration: name: Integration test timeout-minutes: 60 - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} permissions: contents: read env: COMPOSE_SERVICES: postgres + # Playwright 1.58 has no Ubuntu 26.04 browser build (support starts in 1.61), so use the + # Ubuntu 24.04 build. Remove this once Playwright is 1.61 or later. + PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: ubuntu24.04-x64 steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: @@ -153,7 +156,7 @@ jobs: run: docker compose down --volumes --remove-orphans adapter-integration: name: ${{ matrix.packages }} Integration Test - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} timeout-minutes: 30 permissions: contents: read diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index f6cb84962b..19e9e78614 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -14,7 +14,7 @@ concurrency: jobs: build: - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-24.04' || 'ubuntu-24.04' }} + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'better-auth/better-auth' || github.event_name != 'pull_request' && github.repository == 'better-auth/better-auth') && 'starsling-ubuntu-26.04' || 'ubuntu-24.04' }} permissions: contents: read From 5ba9ce51cded84e8fa37d917461aa03a50258a2d Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Sat, 17 Oct 2026 07:40:55 +0000 Subject: [PATCH 4/7] docs: redesign docs OG image (#11888) --- docs/app/api/og-release/route.tsx | 171 --------------- docs/app/api/og/route.tsx | 322 ++++++++++++++--------------- docs/app/blog/layout.tsx | 4 +- docs/app/docs/[[...slug]]/page.tsx | 10 +- docs/lib/rss.ts | 2 +- 5 files changed, 161 insertions(+), 348 deletions(-) delete mode 100644 docs/app/api/og-release/route.tsx diff --git a/docs/app/api/og-release/route.tsx b/docs/app/api/og-release/route.tsx deleted file mode 100644 index 4d76eb8070..0000000000 --- a/docs/app/api/og-release/route.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { ImageResponse } from "@vercel/og"; -import * as z from "zod"; -export const runtime = "edge"; - -const ogSchema = z.object({ - heading: z.string(), - description: z.string().optional(), - date: z.string().optional(), -}); - -export async function GET(req: Request) { - try { - const geist = await fetch( - new URL("../../../assets/Geist.ttf", import.meta.url), - ).then((res) => res.arrayBuffer()); - - const url = new URL(req.url); - const urlParamsValues = Object.fromEntries(url.searchParams); - const validParams = ogSchema.parse(urlParamsValues); - - const { heading, description, date } = validParams; - const trueHeading = - heading.length > 140 ? `${heading.substring(0, 140)}...` : heading; - - return new ImageResponse( -
-
-
- -
-
The resulting Hono route must match your Better Auth `basePath`, which - defaults to `/api/auth`. If your app already uses `new - Hono().basePath("/api")`, mount Better Auth at `/auth/*`: + defaults to `/api/auth`. If your app already uses + `new Hono().basePath("/api")`, mount Better Auth at `/auth/*`: ```ts const app = new Hono().basePath("/api"); From d0ca6d538db47547b0a842cfa44ca92a8ea9bba0 Mon Sep 17 00:00:00 2001 From: Panagiotis Plytas Date: Sat, 17 Oct 2026 12:44:22 +0300 Subject: [PATCH 7/7] docs: remove beta wording from good-webhooks community plugin entry (#11877) --- docs/lib/community-plugins-data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/lib/community-plugins-data.ts b/docs/lib/community-plugins-data.ts index 193b47ae6e..4b540659d7 100644 --- a/docs/lib/community-plugins-data.ts +++ b/docs/lib/community-plugins-data.ts @@ -434,7 +434,7 @@ export const communityPlugins: CommunityPlugin[] = [ name: "good-webhooks", url: "https://github.com/pplytas/good-webhooks", description: - "Authenticated webhook endpoint management with typed client APIs, scoped permissions, signing-secret rotation, and optional self-hosted delivery. Currently in beta with a frozen public API.", + "Authenticated webhook endpoint management with typed client APIs, scoped permissions, signing-secret rotation, and optional self-hosted delivery.", author: { name: "Panagiotis Plytas", github: "pplytas",