Goal: Sync our fork of better-auth/better-auth with the latest upstream release, vendor updated files into @btst/* packages, bump versions, and publish.
- Our fork:
git@github.com:olliethedev/better-auth.git(origin) - Upstream:
https://github.com/better-auth/better-auth.git— tracked asupstreamremote (already configured) - Our packages: all live under
packages/btst/— upstream never touches this directory, so there are no btst-specific merge conflicts
| Path | Description |
|---|---|
packages/btst/ |
All 8 @btst/* packages — our entire product |
scripts/sync-upstream.ts |
File-copy script that vendors upstream files into @btst |
README.md |
Fork-specific README |
.github/workflows/better-db-release.yml |
Our npm publish workflow |
.github/workflows/btst-ci.yml |
Our CI workflow |
packages/btst/DEVELOPMENT.md |
Our contributor guide |
packages/btst/README.md |
Shared @btst package README |
The sync script vendors files from upstream standalone adapter packages into @btst. All adapters are vendored (not thin re-exports) to ensure @btst always gets the full-featured standalone adapter implementations with all upstream fixes:
| Source (in this monorepo) | Destination | Notes |
|---|---|---|
packages/drizzle-adapter/src/ |
packages/btst/adapter-drizzle/src/ |
Full drizzle adapter implementation |
packages/prisma-adapter/src/ |
packages/btst/adapter-prisma/src/ |
Full prisma adapter implementation |
packages/memory-adapter/src/ |
packages/btst/adapter-memory/src/ |
Full memory adapter implementation |
packages/mongo-adapter/src/ |
packages/btst/adapter-mongodb/src/ |
Full mongodb adapter implementation |
packages/kysely-adapter/src/ |
packages/btst/adapter-kysely/src/ |
Kysely adapter with import patches (@better-auth/core/utils → local utility) |
packages/cli/src/generators/ |
packages/btst/cli/src/generators/ |
Drizzle, Prisma, Kysely schema generators |
packages/cli/src/utils/ |
packages/btst/cli/src/utils/ |
Package detection utility and helpers |
We only sync when a new stable upstream tag has been released. Do not sync against upstream/main directly — only sync against a specific release tag.
git fetch upstream --tags
# List the latest upstream tags (most recent first)
git tag --list --sort=-version:refname | head -20Compare against the last tag we synced. For example, if the last sync was v1.5.0, look for any v1.5.x or v1.6.x tag that is newer.
Important: Skip any pre-release / beta tags (e.g. v1.6.0-beta.1). We only sync stable releases.
If there is no newer stable tag than the one we last synced, stop here — there is nothing to sync this week.
Record the new tag you will sync to (e.g. TAG=v1.5.4) — you will use it in subsequent steps.
Always work on a branch, not directly on main:
TAG=v1.5.4 # replace with the actual new tag
git checkout main
git pull origin main
git checkout -b sync-upstream-${TAG}
# e.g. sync-upstream-v1.5.4# Commits between our HEAD and the upstream tag
git log --oneline HEAD..${TAG} | head -30
# Files changed between our HEAD and the upstream tag
git diff --name-only HEAD..${TAG} | grep -v "^packages/btst/" | head -40
# Check the adapter and CLI paths specifically (these drive step 4)
git diff --name-only HEAD..${TAG} -- packages/kysely-adapter/ packages/cli/src/generators/ packages/cli/src/utils/git merge ${TAG} --no-editThis will produce many conflicts — that is normal. The vast majority are in docs, demos, and upstream packages that we simply accept as-is.
Use the following strategy for each conflict type:
a) Files upstream deleted (UD conflicts) — remove them:
git status --short | grep "^UD" | awk '{print $2}' | xargs git rm -fb) Binary or asset conflicts (AU where only we added) — keep ours:
git checkout --ours <file> && git add <file>c) README.md — keep ours (fork-specific content):
git checkout --ours README.md && git add README.mdd) Everything else outside packages/btst/ — accept upstream:
# List all remaining content conflicts
git diff --name-only --diff-filter=U | grep -v "^README.md$" > /tmp/conflicts.txt
# Accept upstream for all of them
xargs git checkout --theirs < /tmp/conflicts.txt
xargs git add < /tmp/conflicts.txte) Verify no conflicts remain:
git diff --name-only --diff-filter=U
# Should output nothingf) Commit the merge:
git commit -m "chore: merge upstream better-auth ${TAG}"Before running the sync script, verify the source paths it references still exist in the newly merged code:
pnpm tsx scripts/sync-upstream.ts 2>&1 | head -20If you see ❌ Source directory not found or ❌ Source file not found, the upstream structure changed. Common changes to watch for:
- New files added to any adapter — check each vendored adapter's source directory for new
.tsfiles not listed in itsfilesarray. For example, ifquery-builders.tsappears inpackages/drizzle-adapter/src/and the adapter now imports it, add it to the correspondingCOPY_CONFIGSentry. After a sync, a build error likeCannot find module './query-builders'is a sign a new file was missed.# Check each adapter for new files vs what sync-upstream.ts lists: ls packages/drizzle-adapter/src/ packages/prisma-adapter/src/ \ packages/memory-adapter/src/ packages/mongo-adapter/src/ \ packages/kysely-adapter/src/ packages/cli/src/generators/ \ packages/cli/src/utils/ - Adapter directory location changed — if an adapter moves (e.g. kysely moved from
packages/better-auth/src/adapters/kysely-adapter/topackages/kysely-adapter/src/in v1.5.4), update thefrompath inCOPY_CONFIGS. - Import path changes — if upstream changes an internal import (e.g.
@better-auth/core/utils→@better-auth/core/utils/string), update thetransformImportsregex in the relevant config block.
See the COPY_CONFIGS array in scripts/sync-upstream.ts for the exact paths and transforms being applied.
pnpm tsx scripts/sync-upstream.tsExpected output:
🔄 Syncing upstream files to @btst packages...
✅ All source files found
📦 packages/kysely-adapter/src → adapter-kysely/src/
kysely-adapter.ts ✨ Patches applied
...
📦 packages/cli/src/generators → generators/
...
✅ Sync complete! Copied N files.
Review the generated files briefly — the script adds a ⚠️ AUTO-GENERATED header to each file so they're easy to identify.
All 8 packages under packages/btst/ should be bumped together:
- Minor bump (e.g.
2.1.0→2.2.0) for new routes, new@btstfeatures, or meaningful additions — whether alongside an upstream sync or as a fork-only change - Patch bump (e.g.
2.1.0→2.1.1) for a pure upstream sync with no new@btstadditions, or for a fork-only fix/small tweak with no new functionality
Note: @btst versioning is independent of upstream's version scheme. An upstream minor release does not automatically mandate a minor @btst bump — what matters is whether our packages gain new functionality.
Edit each packages/btst/*/package.json and update "version". Also update any peerDependencies referencing "better-auth" to reflect the new minimum version (e.g. ">=1.4.0" → ">=1.5.0") when syncing a minor upstream release.
Packages to update:
packages/btst/db/package.jsonpackages/btst/cli/package.jsonpackages/btst/plugins/package.jsonpackages/btst/adapter-drizzle/package.jsonpackages/btst/adapter-kysely/package.jsonpackages/btst/adapter-memory/package.jsonpackages/btst/adapter-mongodb/package.jsonpackages/btst/adapter-prisma/package.json
The @btst packages depend on better-auth via workspace:*, so build order matters:
pnpm build --filter better-auth
pnpm build --filter "@btst/*"All builds must succeed with no errors before proceeding.
This step mirrors exactly what ci.yml and btst-ci.yml run on the PR. Run every check locally and fix all failures before pushing. Do not skip any step — all of these ran in CI and failures blocked the last sync.
# Build everything first
pnpm build
# Biome lint (formatting + code style)
pnpm lint
# Knip — unused exports / dead code across the whole monorepo
pnpm lint:dependencies
# Per-package knip checks (turbo)
pnpm lint:packages
# Spell check (cspell)
pnpm lint:spell
# Docs markdown format check
pnpm format:check
# TypeScript type checking per-package
pnpm lint:types
# Full monorepo typecheck
pnpm typecheck
# Dist declaration typecheck
pnpm typecheck:distCommon failures and fixes:
-
pnpm lintfails — Biome formatting/lint errors in@btstvendored files. The sync script adds an auto-generated header that can break linting if the source file had trailing newlines or other issues. Runpnpm lint:fixon the failing files or manually fix the reported errors. -
pnpm lint:packages/pnpm lint:dependenciesfails (knip) — Knip reports unused exports or files. After a sync, adapters may export symbols thatknip.jsoncdidn't know about. Fix by:- Adding the entry to the
ignoreorignoreDependenciesarray inknip.jsonc, or - Removing the unused export from the
@btstadapter'sindex.tsif it was mistakenly added.
Also check
packages/btst/adapter-kysely/package.jsonandpackages/btst/db/package.json— if they declareexportsthat reference non-existent files, knip will complain. - Adding the entry to the
-
pnpm lint:spellfails (cspell) — New words introduced by upstream (library names, technical terms, author names). Add them to the correct file:.cspell/tech-terms.txt— technical terms, library names, acronyms.cspell/names.txt— author names or proper nouns.cspell/custom-words.txt— domain-specific words
-
pnpm lint:types/pnpm typecheckfails — Type errors in@btstpackages after a sync. Common causes:- Missing type dependency: add it to the adapter's
package.jsondevDependenciesand runpnpm install - Wrong
tsconfig.jsonreference: eachpackages/btst/*/tsconfig.jsonshould extend../../tsconfig.base.json, not a path that no longer exists - New upstream export types that reference packages not in
@btstdeps
- Missing type dependency: add it to the adapter's
# Build @btst packages
pnpm turbo build --filter="./packages/btst/*"
# Typecheck @btst packages
pnpm --filter "@btst/*" exec tsc --noEmit
# Run @btst tests (SQLite-only tests pass locally; Postgres/MySQL tests need CI)
pnpm turbo test --continue --filter="./packages/btst/*"Expected result on a developer machine (no local databases):
@btst/dbtests — all pass@btst/adapter-memorytests — all pass@btst/clischema-conversion.test.ts— all pass@btst/cligenerate-all-orms.test.ts— all pass (uses SQLite)@btst/clie2e-cli.test.ts— fails (needs live Postgres + MySQL — CI only)
The e2e failures are expected locally. CI provides the databases.
If generate-all-orms.test.ts has snapshot failures, update them — this means the generators produce slightly different output in the new version (normal for a minor upstream bump):
cd packages/btst/cli && pnpm vitest run -uCommit the updated snapshots as part of the sync PR.
git add -A
git commit -m "chore: sync upstream ${TAG} + bump @btst to vY.Y.Y"
git push -u origin HEAD
# then open a PR → mainThe GitHub Action better-db-release.yml publishes all @btst/* packages automatically when a GitHub Release is published with a btst-v* tag:
# After PR is merged to main:
git checkout main && git pull
git tag btst-vY.Y.Y
git push origin btst-vY.Y.Y
# Then create a GitHub Release from that tag in the GitHub UITag convention: btst-v2.1.0 → published as latest
-
packages/btst/never conflicts with upstream — upstream has no knowledge of this directory. Git will never touch these files during a merge. Any changes in this directory between syncs are purely our own. -
pnpm-lock.yamlalways conflicts — it's a large generated file that both sides modify independently. Always accept upstream's version (git checkout --theirs pnpm-lock.yaml) then runpnpm installto update it with our workspace entries. -
UDconflicts needgit rm, not--theirs— when upstream deletes a file that we modified,git checkout --theirsfails because "their" version doesn't exist. You must usegit rm -f <file>instead. -
scripts/sync-upstream.tsmay need updating between major/minor releases — upstream occasionally restructures packages. Always validate the script runs cleanly (step 5) before trusting its output. The script itself will fail early and tell you exactly which source path is missing. -
The
transformImportsregexes insync-upstream.tstrack internal upstream import paths — if upstream reorganises its own internals (e.g.@better-auth/core/utilsbecame@better-auth/core/utils/stringin v1.5.4), the regex must be updated to match the new path, or the vendored files will have broken imports that fail at build time. -
d1-sqlite-dialect.tswas added in v1.5.4 — if the kysely adapter gains new files in future releases, checkpackages/kysely-adapter/src/after the merge and add any new files to thefilesarray insync-upstream.ts. -
Docs conflicts are noise — the
docs/directory changes every release (new MDX pages, sidebar changes, component restructures). Always accept upstream's version; we don't host the docs. -
Upstream sometimes deletes GitHub Actions workflows — in v1.5.4,
adapter-tests.yml,auto-cherry-pick-to-main.yml, andcherry-pick-to-main.ymlwere removed. Accept those deletions. Our fork-only workflows (better-db-release.yml,btst-ci.yml) are not in conflict because upstream doesn't have them. -
pnpm-workspace.yamlcatalog entries may disappear — upstream removes catalog entries when they switch build tools. In v1.5.4,unbuildandvitest(default catalog) were removed because upstream switched totsdownand moved vitest to a named sub-catalog. Our@btstpackages still need them. Ifpnpm installfails withERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC, add the missing entry back to thecatalog:block inpnpm-workspace.yamland re-run. -
New utility files in synced directories need to be added to
sync-upstream.ts— in v1.5.4,helper.tswas added topackages/cli/src/utils/and immediately imported byget-package-info.ts. Because it wasn't in thefileslist, the build failed. After a sync, if the build complains about a missing module in a synced path, check whether upstream added a new file that needs to be added to the relevantCOPY_CONFIGSentry. -
Renamed exports in synced files cascade to our custom code —
generateMigrationswas renamed togenerateKyselySchemain the upstream CLI generators in v1.5.4. Our customgenerators/index.tsand CLI commands referenced the old name and needed updating. After a sync, always check the build output for"X is not exported by Y"errors. -
Check the upstream changelog before syncing — scan
https://github.com/better-auth/better-auth/releasesfor anything database or adapter related. New adapter exports, new CLI flags, or changed field types may require updates to@btstpackage wrappers beyond what the sync script handles automatically. -
Versioning note —
@btst/*uses its own version scheme independent ofbetter-auth. Minor bumps are driven by new@btstfunctionality (new routes, features, additions), not by upstream's release cadence. Patch bumps cover pure upstream syncs and fork-only fixes with no new functionality. This is documented inpackages/btst/DEVELOPMENT.md. -
@btsttsconfig files must extend the base — eachpackages/btst/*/tsconfig.jsonmust have"extends": "../../tsconfig.base.json"(two levels up, relative to the package dir). Ifpnpm --filter "@btst/*" exec tsc --noEmitreportsCannot find tsconfig base, check thatpackages/btst/tsconfig.base.jsonexists and all package tsconfigs point to it correctly. -
All adapters are now vendored, not re-exports — as of the v1.6.x sync,
sync-upstream.tsvendors all five adapters (drizzle, prisma, memory, mongodb, kysely) from their standalone upstream packages. If you see a@btstadapterindex.tsthat re-exports frombetter-auth/adapters/...instead of from a local vendored file, update it to import from the vendored file. The upstream standalone packages receive fixes that may not make it intobetter-auth's built-in adapters. -
New adapter source files break the build silently — when upstream adds a new file to an adapter (e.g.
query-builders.tsin drizzle/memory/mongodb adapters in v1.6.x), the vendored copy won't have it and the build will fail withCannot find module. After each sync, checkpackages/*-adapter/src/for files not yet listed insync-upstream.ts COPY_CONFIGSand add them. Then re-run the sync script. -
ALL workflow files use upstream's private runner — after merging upstream, any workflow that has
runs-on: starsling-ubuntu-24.04(upstream's self-hosted runner) will queue forever in our fork. Replace it in every workflow file at once:sed -i 's/runs-on: starsling-ubuntu-24\.04/runs-on: ubuntu-latest/g' \ .github/workflows/*.yml
This covers
ci.yml,e2e.yml, and any new workflows upstream added in the release (e.g.auto-changeset.yml,promote.yml,verify-changesets.yml, etc.). -
preview.ymlshould be deleted — upstream's docs preview workflow (preview.yml) is only meaningful inside the upstream org (it previously had anif: github.repository == 'better-auth/better-auth'guard). After a merge where upstream removes that guard, the workflow will try to run in our fork and fail. Delete it since we don't host the docs:rm -f .github/workflows/preview.yml git rm -f .github/workflows/preview.yml 2>/dev/null || true