Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 93 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ name: Better Auth UI Release
on:
release:
types: [published]
workflow_dispatch:
inputs:
release_tag:
description: Existing v* tag to publish
required: true
type: string
prerelease:
description: Publish with the npm next dist-tag
required: true
default: true
type: boolean

permissions:
contents: write
contents: read
id-token: write

jobs:
Expand All @@ -15,19 +26,22 @@ jobs:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.release.tag_name || inputs.release_tag }}
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
with:
version: 10.26.2

- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22.18.0'
registry-url: 'https://registry.npmjs.org'

- name: Update npm
run: npm install -g npm@latest
- name: Install npm with trusted publishing support
run: npm install -g npm@11.17.0

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -38,15 +52,18 @@ jobs:
- name: Resolve release metadata
id: release-metadata
env:
GITHUB_PRERELEASE: ${{ github.event.release.prerelease }}
GITHUB_PRERELEASE: ${{ github.event.release.prerelease || inputs.prerelease }}
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
run: |
set -euo pipefail

PKG_VERSION=$(node -p "require('./package.json').version")
if [ -n "${{ github.event.release.tag_name }}" ]; then
RAW_TAG="${{ github.event.release.tag_name }}"
else
RAW_TAG="${GITHUB_REF#refs/tags/}"
if [[ ! "$RELEASE_TAG" =~ ^v ]]; then
echo "Release tag must start with v; found $RELEASE_TAG"
exit 1
fi
TAG_VERSION="${RAW_TAG#v}"

TAG_VERSION="${RELEASE_TAG#v}"
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
echo "Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)"
exit 1
Expand All @@ -62,4 +79,70 @@ jobs:
echo "Publishing @btst/better-auth-ui@$PKG_VERSION with dist-tag '$NPM_DIST_TAG'"

- name: Publish to NPM
run: npm publish --access public --tag "${{ steps.release-metadata.outputs.npm_dist_tag }}"
env:
NPM_DIST_TAG: ${{ steps.release-metadata.outputs.npm_dist_tag }}
PKG_VERSION: ${{ steps.release-metadata.outputs.package_version }}
run: |
set -euo pipefail

if npm view "@btst/better-auth-ui@$PKG_VERSION" version >/dev/null 2>&1; then
echo "@btst/better-auth-ui@$PKG_VERSION is already published; skipping."
exit 0
fi
npm publish --access public --provenance --tag "$NPM_DIST_TAG"

- name: Verify published package
env:
NPM_DIST_TAG: ${{ steps.release-metadata.outputs.npm_dist_tag }}
PKG_VERSION: ${{ steps.release-metadata.outputs.package_version }}
run: |
set -euo pipefail

PUBLISHED_VERSION=""
for attempt in {1..12}; do
PUBLISHED_VERSION=$(npm view "@btst/better-auth-ui@$NPM_DIST_TAG" version 2>/dev/null || true)
if [ "$PUBLISHED_VERSION" = "$PKG_VERSION" ]; then
break
fi
echo "Waiting for npm to resolve $NPM_DIST_TAG to $PKG_VERSION (attempt $attempt/12)"
sleep 10
done

if [ "$PUBLISHED_VERSION" != "$PKG_VERSION" ]; then
echo "@btst/better-auth-ui@$NPM_DIST_TAG resolves to ${PUBLISHED_VERSION:-nothing}, expected $PKG_VERSION"
exit 1
fi

INTEGRITY=$(npm view "@btst/better-auth-ui@$PKG_VERSION" dist.integrity)
if [ -z "$INTEGRITY" ]; then
echo "Published package integrity metadata is missing"
exit 1
fi

LATEST_SUMMARY=""
if [ "$NPM_DIST_TAG" = "next" ]; then
LATEST_VERSION=$(npm view "@btst/better-auth-ui@latest" version 2>/dev/null || true)
if [ "$LATEST_VERSION" = "$PKG_VERSION" ] || [[ "$LATEST_VERSION" == *-* ]]; then
echo "Prerelease publication must not move latest to a prerelease; found $LATEST_VERSION"
exit 1
fi
LATEST_SUMMARY="@btst/better-auth-ui@latest remains at ${LATEST_VERSION:-no published version}"
fi

{
echo "### npm release"
echo ""
echo "@btst/better-auth-ui@$PKG_VERSION published with dist-tag $NPM_DIST_TAG."
echo "Integrity: \`$INTEGRITY\`"
if [ -n "$LATEST_SUMMARY" ]; then
echo "$LATEST_SUMMARY"
fi
} >> "$GITHUB_STEP_SUMMARY"

- name: Upload npm logs on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: npm-debug-logs
path: /home/runner/.npm/_logs/
retention-days: 7
191 changes: 191 additions & 0 deletions docs/content/docs/integrations/btst-v3.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
title: BTST v3
description: Configure Better Auth UI as the client and server auth provider for BTST v3
---

`@btst/better-auth-ui` supplies the Better Auth client plugins, page routes, and
the first-party auth-provider adapters used by BTST v3. Router, API,
notification, localization, and auth services are configured once at the top
level instead of repeated in each plugin override.

## Install the release candidate

```bash
pnpm add @btst/stack@next @btst/better-auth-ui@next @btst/yar@^1.3.0
```

## Register the client plugins

```tsx title="lib/stack-client.tsx"
import { createStackClient } from "@btst/stack/client"
import {
accountClientPlugin,
authClientPlugin,
organizationClientPlugin,
} from "@btst/better-auth-ui/client"

export function getStackClient() {
return createStackClient({
basePath: "/p",
plugins: {
auth: authClientPlugin({
siteBasePath: "/p",
siteBaseURL: process.env.NEXT_PUBLIC_APP_URL!,
}),
account: accountClientPlugin({
siteBasePath: "/p",
siteBaseURL: process.env.NEXT_PUBLIC_APP_URL!,
}),
organization: organizationClientPlugin({
siteBasePath: "/p",
siteBaseURL: process.env.NEXT_PUBLIC_APP_URL!,
}),
},
})
}
```

## Configure the client provider

`createBetterAuthProvider` maps the Better Auth session to BTST identity. It
leaves Better Auth UI's native permission hook in place unless an explicit
authorization mapping is configured. Set `permissionProvider` only when the
matching Better Auth client plugin is installed; resource/action checks are
then sent to that plugin's `hasPermission` endpoint and anonymous checks fail
closed.

```tsx title="app/p/layout.tsx"
"use client"

import { StackProvider, type StackI18nProvider, type StackNotifyProvider } from "@btst/stack/context"
import { nextRouter } from "@btst/stack/next"
import { createBetterAuthProvider } from "@btst/better-auth-ui"
import {
type AccountPluginOverrides,
type AuthPluginOverrides,
type OrganizationPluginOverrides,
} from "@btst/better-auth-ui/client"
import { toast } from "sonner"
import { authClient } from "@/lib/auth-client"
import { translateForApp } from "@/lib/i18n"

type PluginOverrides = {
auth: AuthPluginOverrides
account: AccountPluginOverrides
organization: OrganizationPluginOverrides
}

const stackAuth = createBetterAuthProvider(authClient, {
loginPath: "/p/auth/sign-in",
permissionProvider: "organization",
})

const notify: StackNotifyProvider = {
success: toast.success,
error: toast.error,
info: toast.info,
warning: toast.warning,
}

const i18n: StackI18nProvider = {
translate: (key, defaultValue, params) =>
translateForApp(key, { defaultValue, ...params }),
}

export default function PagesLayout({ children }: { children: React.ReactNode }) {
const baseURL =
typeof window === "undefined"
? process.env.NEXT_PUBLIC_APP_URL!
: window.location.origin

return (
<StackProvider<PluginOverrides>
basePath="/p"
router={nextRouter()}
api={{ baseURL, basePath: "/api/data" }}
auth={stackAuth}
notify={notify}
i18n={i18n}
overrides={{
auth: {
authClient,
basePath: "/p/auth",
redirectTo: "/p/account/settings",
},
account: {
authClient,
basePath: "/p/account",
account: true,
},
organization: {
authClient,
basePath: "/p/organization",
organization: true,
},
}}
>
{children}
</StackProvider>
)
}
```

Better Auth UI localization keys are passed to the top-level translator as
`better-auth-ui.KEY`, with the package's English string as `defaultValue`.
Toasts use the top-level notification provider, and navigation/session refresh
use the top-level router.

## Configure the server provider

The server entry exports the corresponding adapter for `stack({ auth })`. It
passes the incoming headers to Better Auth and memoizes the session result for
the request. BTST also shares that identity with all lifecycle hooks handling
the request.

```ts title="lib/stack.ts"
import { stack } from "@btst/stack/api"
import { createBetterAuthProvider } from "@btst/better-auth-ui/server"
import { auth } from "@/lib/auth"
import { adapter } from "@/lib/btst-adapter"

export const { handler } = stack({
basePath: "/api/data",
adapter,
plugins: {},
auth: createBetterAuthProvider(auth, {
permissionProvider: "organization",
}),
})
```

If the Better Auth organization plugin is not configured, omit
`permissionProvider`. To use the admin plugin instead, set it to `"admin"` on
both the client and server adapters.

## Add the v3 entry factories

```ts title="app/api/data/[[...all]]/route.ts"
import { toNextRouteHandlers } from "@btst/stack/next"
import { handler } from "@/lib/stack"

export const { GET, POST, PUT, PATCH, DELETE } = toNextRouteHandlers(handler)
```

```tsx title="app/p/[[...all]]/page.tsx"
import { createNextPage } from "@btst/stack/next"
import { getOrCreateQueryClient } from "@/lib/query-client"
import { getStackClient } from "@/lib/stack-client"

export const dynamic = "force-dynamic"

const page = createNextPage({
getStackClient,
getQueryClient: getOrCreateQueryClient,
})

export default page.Page
export const generateMetadata = page.generateMetadata
```

Finally, import `@btst/better-auth-ui/css` from the application's global
stylesheet.
1 change: 1 addition & 0 deletions docs/content/docs/integrations/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"icon": "LayoutGrid",
"defaultOpen": true,
"pages": [
"btst-v3",
"next-js",
"tanstack-start",
"react"
Expand Down
1 change: 1 addition & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Re-export plugins and types

export * from "./lib/better-auth-provider"
export type {
AccountClientConfig,
AccountPageProps,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export * from "./hooks/use-auth-data"
export * from "./hooks/use-authenticate"
export * from "./hooks/use-current-organization"
export * from "./lib/auth-ui-provider"
export * from "./lib/better-auth-provider"
export * from "./lib/social-providers"
export { getViewByPath } from "./lib/utils"
export * from "./lib/view-paths"
Expand Down
Loading
Loading