diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e4a7277a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,176 @@ +# Checks every pull request and every push to main. +# +# web typechecks the web app, local API and reminder worker, runs the +# web tests and builds the web app +# database builds disposable databases from the migrations on PostgreSQL 16 +# and runs the database tests, the local API's HTTP tests and the +# reminder worker's tests on them +# secrets scans the files and every commit behind them for secrets +# +# Nothing here deploys, uses a secret or reaches Neon, Vercel or Resend. Each +# step is a command you can also run locally. +name: CI + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +# A new push to a pull request cancels its older run. Every push to main runs. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + web: + name: Web app + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + # Installs the pnpm version named by packageManager in package.json. + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22 + cache: pnpm + - name: Install + run: pnpm install --frozen-lockfile --filter @commune/web --filter @commune/local-api --filter @commune/reminder-worker + - name: Typecheck + run: pnpm --filter @commune/web --filter @commune/local-api --filter @commune/reminder-worker run typecheck + - name: Web tests + run: pnpm --filter @commune/web test + - name: Build + run: pnpm --filter @commune/web run build + + database: + name: Database, local API and worker + runs-on: ubuntu-24.04 + timeout-minutes: 20 + services: + postgres: + image: postgres:16.15 + env: + POSTGRES_USER: commune + POSTGRES_PASSWORD: commune + # Sort text as the local servers the tests are written against do + # (the image would otherwise use en_US.utf8). + POSTGRES_INITDB_ARGS: --locale=C.UTF-8 + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 5s + --health-timeout 5s + --health-retries 12 + env: + PGHOST: 127.0.0.1 + PGPORT: 5432 + PGUSER: commune + PGPASSWORD: commune + # Every database this job makes has a name starting with this. + SQL_TEST_PREFIX: commune_ci + # The local API's tests refuse its everyday port, 3002. + API_PORT: 3102 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22 + cache: pnpm + # The same install as the web job, so the two share one pnpm cache. + - name: Install + run: pnpm install --frozen-lockfile --filter @commune/web --filter @commune/local-api --filter @commune/reminder-worker + - name: PostgreSQL client + run: | + command -v psql >/dev/null || { sudo apt-get update -q && sudo apt-get install -y -q postgresql-client; } + psql --version + # Every test in infra/neon/tests, the lock order test included, each on + # a fresh database. The script's header explains the rest. + - name: Database tests + run: infra/neon/tests/run-local.sh + - name: Local API tests + run: | + db="${SQL_TEST_PREFIX}_api" + infra/neon/tests/run-local.sh --create "$db" local + log="$RUNNER_TEMP/local-api.log" + (cd apps/local-api && DATABASE_URL="postgres://127.0.0.1:$PGPORT/$db" exec node --import tsx src/server.ts) >"$log" 2>&1 & + api=$! + trap 'kill "$api" 2>/dev/null || true; wait "$api" 2>/dev/null || true; dropdb --if-exists --force "$db" || true' EXIT + for _ in $(seq 1 60); do + curl -sf "http://127.0.0.1:$API_PORT/api/health" >/dev/null && break + sleep 0.5 + done + if ! curl -sf "http://127.0.0.1:$API_PORT/api/health" >/dev/null; then + echo "The local API did not start:"; cat "$log"; exit 1 + fi + status=0 + for test in apps/local-api/tests/*.mjs; do + name=$(basename "$test" .mjs) + case "$name" in *fixture*) continue ;; esac + echo "::group::$name" + if API_BASE="http://127.0.0.1:$API_PORT" timeout 300 node "$test"; then result="PASS $name"; else result="FAIL $name"; status=1; fi + echo "::endgroup::" + echo "$result" + done + if [ "$status" -ne 0 ]; then echo "::group::Local API log"; cat "$log"; echo "::endgroup::"; fi + exit "$status" + - name: Reminder worker tests + working-directory: apps/reminder-worker + run: | + node --test tests/email.mjs tests/unsubscribe.mjs + status=0 + for test in integration invitations webhooks monthly-limits-concurrency; do + db="${SQL_TEST_PREFIX}_worker_${test//-/_}" + # The invitations test runs only on a database named for it. + if [ "$test" = invitations ]; then db="${SQL_TEST_PREFIX}_invite_email_test"; fi + ../../infra/neon/tests/run-local.sh --create "$db" stub + echo "::group::$test" + if DATABASE_URL="postgres://127.0.0.1:$PGPORT/$db" timeout 300 node "tests/$test.mjs"; then result="PASS $test"; else result="FAIL $test"; status=1; fi + echo "::endgroup::" + echo "$result" + dropdb --force "$db" + done + exit "$status" + + secrets: + name: Secret scan + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: '1.27.x' + cache: false + # The gitleaks command line tool, built from its published source, which + # Go checks against its checksum database. Unlike the gitleaks GitHub + # Action it needs no licence for an organisation's repository. + - name: Install gitleaks + run: go install github.com/zricethezav/gitleaks/v8@v8.30.1 + # A finding shows the file, line and rule, with the secret redacted. + - name: Scan the files + run: gitleaks dir . --redact --no-banner --verbose + # Every commit behind this checkout, so a secret added and removed again + # is caught however the change was pushed. These are gitleaks' own + # history options, limited to this checkout rather than every branch. + # .gitleaksignore lists the old commits that hold the Google API key from + # app review finding 1 and the earlier app's web push key, by fingerprint + # (commit, file, rule and line; no secret). Revoking both is still needed. + - name: Scan the history + run: gitleaks git --log-opts="--full-history --diff-filter=tuxdb HEAD" --redact --no-banner --verbose . diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000..a61b4e29 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,11 @@ +# Findings the secret scan (.github/workflows/ci.yml) leaves out, by +# fingerprint: commit:file:rule:line. None of them is in the files any more, +# but history keeps them, so each must be revoked where it was issued. +# +# The Google API key from app review finding 1, in three old commits. +23b3eeb5a19ccc38fb74b2755ce754496111e06b:.migration-backup/docs/COMMUNE-BUILD-BIBLE.md:gcp-api-key:619 +c5fae888cafd3074cfa4891184a3d61790b7eb2d:docs/SESSION-REFERENCE.md:gcp-api-key:619 +da953a1499af8b6c0d3bd8df2f6d2ea8ec4040e3:docs/COMMUNE-BUILD-BIBLE.md:gcp-api-key:619 +# The web push (VAPID) private key of the earlier Supabase app, committed to an +# example environment file in March 2026. The current app sends no web push. +fb1d3c8dd0cea11791c140db10159592bbddcb66:apps/web/.env.example:generic-api-key:7 diff --git a/apps/local-api/README.md b/apps/local-api/README.md index 1c36ffe1..5e2c696e 100644 --- a/apps/local-api/README.md +++ b/apps/local-api/README.md @@ -120,6 +120,10 @@ request ids, and pledge limits and notices. It also refuses port 3002. signed-in one (`X-Commune-Viewer`, sent by each tab) is refused with 401, while the signed-in account and requests without the header are still served. +`sign-in-lockout.mjs` takes about 40 seconds (1,000 failed sign-ins). +`invitation-email-opt-in.mjs` needs no API: +`node --test apps/local-api/tests/invitation-email-opt-in.mjs`. + Search integration requires migration 0014. It covers all eight result types, active membership, selected/all-commune scopes, removed-member denial, literal wildcards, result caps and private-note masking. Its loopback guard refuses diff --git a/apps/local-api/src/invitation-email.ts b/apps/local-api/src/invitation-email.ts new file mode 100644 index 00000000..c81b35b0 --- /dev/null +++ b/apps/local-api/src/invitation-email.ts @@ -0,0 +1,18 @@ +// The local API emails an invitation only when told to in so many words. The +// Resend settings alone are not enough: they may be exported in a shell for +// the worker, and an invitation made while testing would then reach a real +// inbox. LOCAL_API_SEND_EMAIL=yes is the opt-in, and the server logs which +// way it is set when it starts. +type Env = Record; + +export function invitationEmailSetting(env: Env): { send: boolean; notice: string } { + const configured = Boolean(env.RESEND_API_KEY && env.RESEND_FROM && env.APP_URL); + if (env.LOCAL_API_SEND_EMAIL !== "yes") { + return { send: false, notice: configured + ? "Invitation email is off: the Resend settings are ignored unless LOCAL_API_SEND_EMAIL=yes is set as well." + : "Invitation email is off. Invitation links can still be copied and shared." }; + } + return configured + ? { send: true, notice: "Invitation email is on (LOCAL_API_SEND_EMAIL=yes): invitations are emailed through Resend to the address invited." } + : { send: false, notice: "Invitation email is off: LOCAL_API_SEND_EMAIL=yes also needs RESEND_API_KEY, RESEND_FROM and APP_URL." }; +} diff --git a/apps/local-api/src/server.ts b/apps/local-api/src/server.ts index c5017832..afb86001 100644 --- a/apps/local-api/src/server.ts +++ b/apps/local-api/src/server.ts @@ -4,6 +4,7 @@ import type { PoolClient } from "pg"; import { changePassword, createAccount, getUser, login, logout, logoutOthers, setSessionCookie, type User } from "./auth.js"; import { pool, withActor } from "./db.js"; import { InvitationEmailError, invitationMailConfig, sendInvitationEmail } from "../../reminder-worker/src/invitations.js"; +import { invitationEmailSetting } from "./invitation-email.js"; const host = "127.0.0.1"; const port = Number(process.env.API_PORT ?? "3002"); @@ -180,7 +181,12 @@ async function actor(req: IncomingMessage): Promise { return user; } -// A bounded local throttle protects password verification from quick guessing. +// A local throttle protects password verification from quick guessing: ten +// failures for one email from one client lock that pair out until 15 minutes +// after the latest. Entries stay in order of their latest failure, so expired +// ones are always first and each failure drops them. A lockout still running +// is never dropped, however many other failures arrive; each one costs a +// password hash, which bounds how many can build up in 15 minutes. const failures = new Map(); function checkThrottle(req: IncomingMessage, address: string): string { const key = `${req.socket.remoteAddress ?? "?"}:${address}`; @@ -191,11 +197,16 @@ function checkThrottle(req: IncomingMessage, address: string): string { return key; } function failed(key: string): void { - if (failures.size > 1000) failures.clear(); + const now = Date.now(); + for (const [stale, item] of failures) { + if (item.until > now) break; + failures.delete(stale); + } const existing = failures.get(key); + failures.delete(key); failures.set(key, { - count: existing && existing.until > Date.now() ? existing.count + 1 : 1, - until: Date.now() + 15 * 60 * 1000, + count: existing && existing.until > now ? existing.count + 1 : 1, + until: now + 15 * 60 * 1000, }); } @@ -829,11 +840,17 @@ route("GET", /^\/api\/invites\/([0-9a-f]{64})$/, async (req, res, match) => { } : null }); }); +// Real email needs LOCAL_API_SEND_EMAIL=yes as well as the Resend settings. +// Without both, the sender is told email isn't set up here (503). +const invitationEmail = invitationEmailSetting(process.env); +console.log(invitationEmail.notice); + route("POST", /^\/api\/invitation-email$/, async (req, res) => { const user = await actor(req); const input = await body(req); try { - send(res, await sendInvitationEmail(pool, user.id, text(input.token, "token", 64), invitationMailConfig())); + send(res, await sendInvitationEmail(pool, user.id, text(input.token, "token", 64), + invitationEmail.send ? invitationMailConfig() : null)); } catch (error) { if (error instanceof InvitationEmailError) throw new ApiError(error.status, error.message); throw error; diff --git a/apps/local-api/tests/invitation-email-opt-in.mjs b/apps/local-api/tests/invitation-email-opt-in.mjs new file mode 100644 index 00000000..65551199 --- /dev/null +++ b/apps/local-api/tests/invitation-email-opt-in.mjs @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { invitationEmailSetting } from "../src/invitation-email.ts"; + +// Whether the local API may email an invitation. No API or database needed: +// node --test apps/local-api/tests/invitation-email-opt-in.mjs +const resend = { RESEND_API_KEY: "test-only", RESEND_FROM: "Commune ", APP_URL: "https://example.test" }; + +test("the Resend settings alone send nothing, and the log says how to opt in", () => { + const setting = invitationEmailSetting(resend); + assert.equal(setting.send, false); + assert.equal(setting.notice, "Invitation email is off: the Resend settings are ignored unless LOCAL_API_SEND_EMAIL=yes is set as well."); +}); + +test("sends only with the opt-in and every Resend setting", () => { + const setting = invitationEmailSetting({ ...resend, LOCAL_API_SEND_EMAIL: "yes" }); + assert.equal(setting.send, true); + assert.match(setting.notice, /^Invitation email is on/); + for (const missing of Object.keys(resend)) { + const partial = invitationEmailSetting({ ...resend, LOCAL_API_SEND_EMAIL: "yes", [missing]: "" }); + assert.equal(partial.send, false, `sent without ${missing}`); + assert.match(partial.notice, /also needs RESEND_API_KEY, RESEND_FROM and APP_URL/); + } +}); + +test("only the exact opt-in counts", () => { + for (const value of ["1", "true", "YES", " yes", "no", ""]) { + assert.equal(invitationEmailSetting({ ...resend, LOCAL_API_SEND_EMAIL: value }).send, false, `sent with ${JSON.stringify(value)}`); + } +}); + +test("off by default", () => { + assert.deepEqual(invitationEmailSetting({}), { send: false, notice: "Invitation email is off. Invitation links can still be copied and shared." }); +}); diff --git a/apps/local-api/tests/sign-in-lockout.mjs b/apps/local-api/tests/sign-in-lockout.mjs new file mode 100644 index 00000000..43cd8aa6 --- /dev/null +++ b/apps/local-api/tests/sign-in-lockout.mjs @@ -0,0 +1,49 @@ +import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; + +// Ten wrong passwords lock that email out of signing in for 15 minutes, and +// failures for other addresses never lift the lock early. Creates an account, +// so it refuses the normal application API. About 1,000 failed sign-ins, each +// a password hash, so it takes half a minute or so. +const base = process.env.API_BASE; +if (!base) throw new Error("Set API_BASE to an isolated local test API, not the user's database."); +const url = new URL(base); +if (url.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(url.hostname) || !url.port || + url.port === "3002" || url.pathname !== "/" || url.search || url.hash || url.username || url.password) { + throw new Error("Sign-in lockout tests require an isolated loopback API port (not 3002)."); +} +const suffix = randomUUID().slice(0, 8); +const password = "DisposableLockoutFixture-2026!"; +async function call(path, input) { + const response = await fetch(`${base}/api${path}`, { + method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input), + }); + return { status: response.status, ...(await response.json()) }; +} +const signIn = (email, attempt = password) => call("/auth/login", { email, password: attempt }); + +const avery = `lockout-avery-${suffix}@example.test`; +assert.equal((await call("/auth/signup", { name: "Avery Lockout", email: avery, password })).status, 201); +for (let attempt = 1; attempt <= 10; attempt += 1) { + assert.equal((await signIn(avery, `Wrong-guess-${attempt}`)).status, 401, `wrong password ${attempt}`); +} +// Locked: even the right password is refused without being checked. +const locked = await signIn(avery); +assert.equal(locked.status, 429, JSON.stringify(locked)); +assert.equal(locked.error, "Try signing in again later"); + +// The lockout list used to be emptied once it held more than 1,000 entries, +// so 1,001 failures for other addresses gave Avery's guesser ten more tries. +const others = Array.from({ length: 1001 }, (_, index) => `lockout-other-${index}-${suffix}@example.test`); +for (let start = 0; start < others.length; start += 8) { + const statuses = await Promise.all(others.slice(start, start + 8).map(async (email) => (await signIn(email)).status)); + assert.ok(statuses.every((status) => status === 401), `other addresses: ${statuses.join(", ")}`); +} +assert.equal((await signIn(avery)).status, 429, "Failures for other addresses lifted the lockout"); + +// Other people sign in as usual. +const blake = `lockout-blake-${suffix}@example.test`; +assert.equal((await call("/auth/signup", { name: "Blake Lockout", email: blake, password })).status, 201); +assert.equal((await signIn(blake, "Wrong-guess-1")).status, 401); +assert.equal((await signIn(blake)).status, 200); +console.log("Sign-in lockout: ten failures lock an address out, and failures for 1,001 other addresses leave it locked."); diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index d5b19518..2382a137 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -121,6 +121,10 @@ install was run. - Avoid `pnpm run` and `pnpm install` here for now. pnpm 11 checks the whole workspace first, the untracked apps are not in the lockfile, and the last attempt ran out of disk. +- CI: `.github/workflows/ci.yml` (web, database, secrets) runs on pull + requests and pushes to main. pnpm is pinned to 10.34.6 through + `packageManager`; pnpm 11 hands over to it here. vite is 7.3.7; reinstall + `.deps-web` to pick it up. - A proper `pnpm install` needs a decision about the untracked workspace apps and several GB free. **Free disk fell to about 1.6 GB during this session** (from 4.4 GB); this work accounts for under 200 MB of that. diff --git a/infra/local/README.md b/infra/local/README.md index a7ff2904..46b4e0bc 100644 --- a/infra/local/README.md +++ b/infra/local/README.md @@ -61,12 +61,14 @@ suppressed locally, so no email is claimed or sent. Contributions and settled shares are confirmations recorded by the relevant admin/payer, not payment transfers; Stripe is not yet connected. -Invitation emails now have a separate explicit send action. The local server -returns an honest unavailable message unless server-only `RESEND_API_KEY`, -`RESEND_FROM`, and `APP_URL` are configured. It still creates a real copyable -invite link. Never point `APP_URL` at production while testing local invites: -the email's HTTPS origin must serve this same database. Default local setup -sends no email. Delivery service tests use a mocked provider and isolated DB. +Invitation emails now have a separate explicit send action. The local API +emails invitations only when `LOCAL_API_SEND_EMAIL=yes` is set as well as +server-only `RESEND_API_KEY`, `RESEND_FROM` and `APP_URL`, and says at start-up +which way it is set. Otherwise it returns an honest unavailable message and +still creates a real invite link to copy and share. Never point `APP_URL` at +production while testing local invites: the email's HTTPS origin must serve +this same database. Default local setup sends no email. Delivery service tests +use a mocked provider and isolated DB. ## 2 October local workflow verification diff --git a/infra/neon/README.md b/infra/neon/README.md index 59dd1655..b65b5c92 100644 --- a/infra/neon/README.md +++ b/infra/neon/README.md @@ -385,6 +385,10 @@ rate limits) are recorded in [`docs/AUTH-SETTINGS.md`](../../docs/AUTH-SETTINGS. ## Local database verification +Run every database test locally with `infra/neon/tests/run-local.sh` (PG* +variables, loopback only; its header explains). CI runs it on every pull +request. + On a disposable local PostgreSQL database, run `tests/local_auth_stub.sql`, migrations 0001 through 0011, then `tests/reminders.sql`, `tests/reminder_monthly_limits.sql`, diff --git a/infra/neon/tests/run-local.sh b/infra/neon/tests/run-local.sh new file mode 100755 index 00000000..8c21e351 --- /dev/null +++ b/infra/neon/tests/run-local.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# Runs the database tests in this folder against a local PostgreSQL server: +# every *.sql test on its own fresh database, then every *.mjs test +# (task_lock_order.mjs) the same way. CI runs it, and so can you before +# pushing a migration: +# +# PGHOST=127.0.0.1 PGPORT=5432 PGUSER=commune PGPASSWORD=commune \ +# infra/neon/tests/run-local.sh [test-name ...] +# +# With names (grants, task_lock_order, ...) it runs only those tests. The +# *.mjs tests need the local API's dependencies installed (they use its pg). +# +# The connection comes from the usual PG* variables. PGHOST defaults to +# 127.0.0.1 and must be a loopback address: the auth stand-ins below create +# the authenticated and anonymous roles, which must never reach Neon. PGHOSTADDR, +# PGSERVICE and PGSERVICEFILE must be unset, as libpq would follow them instead. +# The role needs to create databases and roles (a local superuser, as in CI). +# +# Each test runs on a copy of one of two databases built from the migrations, +# one per local auth mode: +# local infra/local/bootstrap.sql, every migration, infra/local/after-core.sql +# (what the local API runs on) +# stub infra/neon/tests/local_auth_stub.sql and every migration +# A test's leading comment decides: one naming the stub runs on stub, one +# naming bootstrap.sql or after-core.sql runs on local, and any other runs on +# stub. One written for a "Neon branch" is skipped, as it needs hosted auth. +# +# It only ever drops databases it created in the same run. Their names start +# with SQL_TEST_PREFIX (default commune_sqltest) and a random run id that no +# database had when the run began, so an interrupted run cleans up after +# itself and never touches anything else. +# +# infra/neon/tests/run-local.sh --create +# builds one new database the same way and leaves it for the caller (the +# local API and reminder worker tests use this). It refuses a name in use. +set -euo pipefail +export LC_ALL=C + +here=$(cd "$(dirname "$0")" && pwd) +root=$(cd "$here/../../.." && pwd) +export PGHOST="${PGHOST:-127.0.0.1}" PGPORT="${PGPORT:-5432}" +case "$PGHOST" in + 127.0.0.1|localhost) ;; + *) echo "run-local.sh: PGHOST must be 127.0.0.1 or localhost, not $PGHOST" >&2; exit 2 ;; +esac +for setting in PGHOSTADDR PGSERVICE PGSERVICEFILE; do + if [ -n "${!setting:-}" ]; then + echo "run-local.sh: unset $setting; it would take the connection somewhere other than PGHOST" >&2; exit 2 + fi +done + +valid_name() { + [[ "$1" =~ ^[a-z_][a-z0-9_]*$ ]] && [ "${#1}" -le 63 ] +} +# Names of the databases carrying this run's id (validated, so safe to quote). +run_databases() { + psql -X -At -d postgres -c "SELECT datname FROM pg_database WHERE starts_with(datname, '${1}_') ORDER BY 1" +} + +work=$(mktemp -d "${TMPDIR:-/tmp}/run-local.XXXXXX") +run="" +cleanup() { + local db + if [ -n "$run" ]; then + for db in $(run_databases "$run" 2>/dev/null); do + dropdb --if-exists --force "$db" >/dev/null 2>&1 || echo "run-local.sh: could not drop $db" >&2 + done + fi + rm -rf "$work" +} +trap cleanup EXIT +trap 'exit 130' INT TERM + +# Applies every file for one auth mode to a new, empty database. +migrate() { + local db="$1" mode="$2" file + local files=() + if [ "$mode" = local ]; then files+=("$root/infra/local/bootstrap.sql"); else files+=("$here/local_auth_stub.sql"); fi + files+=("$root"/infra/neon/migrations/*.sql) + if [ "$mode" = local ]; then files+=("$root/infra/local/after-core.sql"); fi + for file in "${files[@]}"; do + if ! psql -X -q -v ON_ERROR_STOP=1 -d "$db" -f "$file" >"$work/build.log" 2>&1; then + echo "FAILED building the $mode database at ${file#"$root"/}:" >&2 + tail -n 20 "$work/build.log" | sed 's/^/ /' >&2 + return 1 + fi + done +} + +# local, stub or neon, from the test's leading comment lines. +mode_of() { + local header + header=$(sed -n -E -e '/^(--|\/\/)/!q' -e p "$1") + case "$header" in + *"Neon branch"*) echo neon ;; + *local_auth_stub*|*"auth stub"*|*"local stub"*) echo stub ;; + *bootstrap.sql*|*after-core*) echo local ;; + *) echo stub ;; + esac +} + +if [ "${1:-}" = "--create" ]; then + if [ "$#" -ne 3 ] || ! valid_name "$2" || { [ "$3" != local ] && [ "$3" != stub ]; }; then + echo "usage: run-local.sh --create " >&2; exit 2 + fi + createdb "$2" # fails if the name is taken + if ! migrate "$2" "$3"; then dropdb --force "$2"; exit 1; fi + echo "created $2 ($3)" + exit 0 +fi + +prefix="${SQL_TEST_PREFIX:-commune_sqltest}" +if ! valid_name "$prefix" || [ "${#prefix}" -gt 40 ]; then + echo "run-local.sh: SQL_TEST_PREFIX must be lower-case letters, digits or _, at most 40 characters" >&2; exit 2 +fi + +tests=() +if [ "$#" -gt 0 ]; then + for name in "$@"; do + if [ -f "$here/$name.sql" ] && [ "$name" != local_auth_stub ]; then tests+=("$here/$name.sql") + elif [ -f "$here/$name.mjs" ]; then tests+=("$here/$name.mjs") + else echo "run-local.sh: no test called $name in ${here#"$root"/}" >&2; exit 2; fi + done +else + for test in "$here"/*.sql "$here"/*.mjs; do + [ "$(basename "$test")" = local_auth_stub.sql ] || tests+=("$test") + done +fi + +candidate="${prefix}_$(od -An -N3 -tx1 /dev/urandom | tr -d ' \n')" +if [ -n "$(run_databases "$candidate")" ]; then + echo "run-local.sh: databases named ${candidate}_* already exist; run again for a new id" >&2; exit 2 +fi +run="$candidate" + +migrations=("$root"/infra/neon/migrations/*.sql) +echo "Building the local and stub databases from ${#migrations[@]} migrations" +# Local first: on a fresh server the local database then sees only the roles +# bootstrap.sql makes, as the local API does. +for mode in local stub; do + createdb "${run}_$mode" + migrate "${run}_$mode" "$mode" +done + +pass=0; fail=0; skip=0; n=0 +for test in "${tests[@]}"; do + name=$(basename "$test"); name="${name%.*}" + mode=$(mode_of "$test") + if [ "$mode" = neon ]; then + echo "SKIP $name (needs a Neon branch)"; skip=$((skip + 1)); continue + fi + n=$((n + 1)); db="${run}_$n" + createdb -T "${run}_$mode" "$db" + if [ "${test##*.}" = sql ]; then + # A hung lock wait fails the test instead of the whole run. + PGOPTIONS="${PGOPTIONS:-} -c statement_timeout=120s" \ + psql -X -q -v ON_ERROR_STOP=1 -d "$db" -f "$test" >"$work/test.log" 2>&1 && ok=1 || ok=0 + else + # pg takes the user and password from the PG* variables. + DATABASE_URL="postgres://127.0.0.1:$PGPORT/$db" node "$test" >"$work/test.log" 2>&1 && ok=1 || ok=0 + fi + if [ "$ok" = 1 ]; then + echo "PASS $name ($mode)"; pass=$((pass + 1)) + else + echo "FAIL $name ($mode)"; tail -n 20 "$work/test.log" | sed 's/^/ /'; fail=$((fail + 1)) + fi + dropdb --if-exists --force "$db" >/dev/null 2>&1 || true +done + +echo "Database tests: $pass passed, $fail failed, $skip skipped" +[ "$fail" -eq 0 ] diff --git a/package.json b/package.json index e1e903f3..859078b8 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "workspace", "version": "0.0.0", "license": "MIT", + "packageManager": "pnpm@10.34.6+sha512.7e1ae66a83a9143c118cc1944f37cc4f2b8acddeda4b001cfa78908f6e46e45e15292739ca6c9d026426cc757e9318cc98c5551d6a16c165097b0028ba87b4e6", "scripts": { "preinstall": "sh -c 'rm -f package-lock.json yarn.lock; case \"$npm_config_user_agent\" in pnpm/*) ;; *) echo \"Use pnpm instead\" >&2; exit 1 ;; esac'", "build": "pnpm run typecheck && pnpm -r --filter \"./apps/**\" --if-present run build", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca1c6c9e..fb96cdec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,8 +64,8 @@ catalogs: specifier: ^4.21.0 version: 4.21.0 vite: - specifier: ^7.3.2 - version: 7.3.2 + specifier: ^7.3.5 + version: 7.3.7 wouter: specifier: ^3.3.5 version: 3.9.0 @@ -530,7 +530,7 @@ importers: version: 5.3.0 '@neondatabase/neon-js': specifier: 0.7.0-beta - version: 0.7.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + version: 0.7.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) lucide-react: specifier: 'catalog:' version: 0.545.0(react@19.1.0) @@ -558,7 +558,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 5.1.4(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 5.1.4(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) jsdom: specifier: ^30.1.1 version: 30.1.1(@noble/hashes@2.4.0) @@ -567,10 +567,10 @@ importers: version: 5.9.3 vite: specifier: 'catalog:' - version: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) vitest: specifier: ^5.0.2 - version: 5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) artifacts/api-server: dependencies: @@ -724,7 +724,7 @@ importers: version: 0.5.19(tailwindcss@4.2.1) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.1(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.2.1(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@tanstack/react-query': specifier: 'catalog:' version: 5.90.21(react@19.1.0) @@ -739,7 +739,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 5.1.4(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 5.1.4(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@workspace/api-client-react': specifier: workspace:* version: link:../../lib/api-client-react @@ -808,7 +808,7 @@ importers: version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) vite: specifier: 'catalog:' - version: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) wouter: specifier: ^3.3.5 version: 3.9.0(react@19.1.0) @@ -910,7 +910,7 @@ importers: version: 0.0.6 '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.1(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.2.1(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@types/node': specifier: 'catalog:' version: 25.3.5 @@ -922,7 +922,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 5.1.4(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 5.1.4(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) chokidar: specifier: ^4.0.3 version: 4.0.3 @@ -994,7 +994,7 @@ importers: version: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) vite: specifier: 'catalog:' - version: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) zod: specifier: 'catalog:' version: 3.25.76 @@ -1012,7 +1012,7 @@ importers: version: 0.0.6 '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.1(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.2.1(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@types/node': specifier: 'catalog:' version: 25.3.5 @@ -1024,7 +1024,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 5.1.4(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 5.1.4(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) react: specifier: 'catalog:' version: 19.1.0 @@ -1039,7 +1039,7 @@ importers: version: 4.21.0 vite: specifier: 'catalog:' - version: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + version: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) wouter: specifier: 'catalog:' version: 3.9.0(react@19.1.0) @@ -11279,8 +11279,8 @@ packages: yaml: optional: true - vite@7.3.2: - resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} + vite@7.3.7: + resolution: {integrity: sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -12377,11 +12377,11 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@better-auth/api-key@1.7.5(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@2.0.2(zod@4.3.6))': + '@better-auth/api-key@1.7.5(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@2.0.2(zod@4.3.6))': dependencies: '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3) '@better-auth/utils': 0.4.2 - better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) better-call: 2.0.2(zod@4.3.6) zod: 4.6.5 @@ -12421,14 +12421,14 @@ snapshots: '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3) '@better-auth/utils': 0.4.2 - '@better-auth/passkey@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@1.3.7(zod@4.6.5))(nanostores@1.5.3)': + '@better-auth/passkey@1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@1.3.7(zod@4.6.5))(nanostores@1.5.3)': dependencies: '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@simplewebauthn/browser': 13.3.0 '@simplewebauthn/server': 13.3.3 - better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) better-call: 1.3.7(zod@4.6.5) nanostores: 1.5.3 zod: 4.3.6 @@ -12518,10 +12518,10 @@ snapshots: '@date-fns/tz@1.4.1': {} - '@daveyplate/better-auth-ui@3.4.0(e3c124b483afe493503f54b7cb398ba3)': + '@daveyplate/better-auth-ui@3.4.0(357ce37a9a6e0027d2c14043549f51de)': dependencies: - '@better-auth/api-key': 1.7.5(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@2.0.2(zod@4.3.6)) - '@better-auth/passkey': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@1.3.7(zod@4.6.5))(nanostores@1.5.3) + '@better-auth/api-key': 1.7.5(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@2.0.2(zod@4.3.6)) + '@better-auth/passkey': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@1.3.7(zod@4.6.5))(nanostores@1.5.3) '@better-fetch/fetch': 1.3.1 '@captchafox/react': 1.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@hcaptcha/react-hcaptcha': 2.2.0 @@ -12545,7 +12545,7 @@ snapshots: '@react-email/components': 1.0.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-query': 5.90.21(react@19.1.0) '@wojtekmaj/react-recaptcha-v3': 0.1.4(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) better-call: 2.0.2(zod@4.3.6) bowser: 2.14.1 class-variance-authority: 0.7.1 @@ -14060,15 +14060,15 @@ snapshots: '@neon/functions@0.11.0': {} - '@neondatabase/auth-ui@0.3.0-beta(f5b4828dc0f43eb0535364a260cbfc97)': + '@neondatabase/auth-ui@0.3.0-beta(3e0f700d598dfec41f6056c7399623ce)': dependencies: - '@better-auth/passkey': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@1.3.7(zod@4.6.5))(nanostores@1.5.3) + '@better-auth/passkey': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))))(better-call@1.3.7(zod@4.6.5))(nanostores@1.5.3) '@captchafox/react': 1.13.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@daveyplate/better-auth-ui': 3.4.0(e3c124b483afe493503f54b7cb398ba3) + '@daveyplate/better-auth-ui': 3.4.0(357ce37a9a6e0027d2c14043549f51de) '@hcaptcha/react-hcaptcha': 1.17.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@hookform/resolvers': 5.9.1(@standard-schema/spec@1.1.0)(ajv@8.18.0)(react-hook-form@7.71.2(react@19.1.0))(zod@4.3.6) '@marsidev/react-turnstile': 1.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@neondatabase/auth': 0.5.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + '@neondatabase/auth': 0.5.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) '@radix-ui/react-avatar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-context': 1.1.3(@types/react@19.2.14)(react@19.1.0) @@ -14083,7 +14083,7 @@ snapshots: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.1.0) '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.1.0) '@wojtekmaj/react-recaptcha-v3': 0.1.4(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) better-call: 1.3.7(zod@4.3.6) class-variance-authority: 0.7.1 clsx: 2.1.1 @@ -14158,12 +14158,12 @@ snapshots: - vue - yup - '@neondatabase/auth@0.5.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))': + '@neondatabase/auth@0.5.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: '@better-fetch/fetch': 1.3.1 - '@neondatabase/auth-ui': 0.3.0-beta(f5b4828dc0f43eb0535364a260cbfc97) + '@neondatabase/auth-ui': 0.3.0-beta(3e0f700d598dfec41f6056c7399623ce) '@supabase/auth-js': 2.79.0 - better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + better-auth: 1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) jose: 6.2.5 zod: 4.3.6 optionalDependencies: @@ -14223,9 +14223,9 @@ snapshots: - vue - yup - '@neondatabase/neon-js@0.7.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))': + '@neondatabase/neon-js@0.7.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)))': dependencies: - '@neondatabase/auth': 0.5.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) + '@neondatabase/auth': 0.5.0-beta(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@standard-schema/spec@1.1.0)(@tanstack/react-query@5.90.21(react@19.1.0))(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(ajv@8.18.0)(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(nanostores@1.5.3)(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))) '@neondatabase/postgrest-js': 0.2.0-beta meow: 14.0.0 pg: 8.20.0 @@ -16644,12 +16644,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.1 - '@tailwindcss/vite@4.2.1(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@tailwindcss/vite@4.2.1(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 tailwindcss: 4.2.1 - vite: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) '@tanstack/history@1.161.6': {} @@ -17201,7 +17201,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@5.1.4(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@5.1.4(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -17209,7 +17209,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-rc.3 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -17229,14 +17229,14 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@5.0.2(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@5.0.2(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.2 estree-walker: 3.0.3 magic-string: 1.4.2 optionalDependencies: - vite: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) '@vitest/pretty-format@3.2.4': dependencies: @@ -17729,7 +17729,7 @@ snapshots: baseline-browser-mapping@2.10.8: {} - better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))): + better-auth@1.6.23(drizzle-kit@0.31.9)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0))(pg@8.20.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))): dependencies: '@better-auth/core': 1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3) '@better-auth/drizzle-adapter': 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.6.5))(jose@6.2.5)(kysely@0.29.6)(nanostores@1.5.3))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@types/pg@8.18.0)(kysely@0.29.6)(pg@8.20.0)) @@ -17754,7 +17754,7 @@ snapshots: pg: 8.20.0 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - vitest: 5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + vitest: 5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -24245,14 +24245,14 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): + vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2): dependencies: esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 postcss: 8.5.8 rollup: 4.59.0 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.3.5 fsevents: 2.3.3 @@ -24303,10 +24303,10 @@ snapshots: - tsx - yaml - vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): + vitest@5.0.2(@types/node@25.3.5)(jsdom@30.1.1(@noble/hashes@2.4.0))(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.2(vite@7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 5.0.2(vite@7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -24317,7 +24317,7 @@ snapshots: tinybench: 6.2.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: 7.3.2(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.7(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 3.2.2 optionalDependencies: '@types/node': 25.3.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f30831da..017e0d70 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -63,7 +63,7 @@ catalog: tailwind-merge: ^3.3.1 tailwindcss: ^4.1.14 tsx: ^4.21.0 - vite: ^7.3.2 + vite: ^7.3.5 wouter: ^3.3.5 zod: ^3.25.76