diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38c5af1..e529fb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,10 @@ name: ci on: + push: + branches: [main] pull_request: + branches: [main] env: HUSKY: 0 @@ -15,28 +18,31 @@ jobs: steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - fetch-depth: 0 persist-credentials: false + fetch-depth: 0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: ".nvmrc" - - - name: Install commitlint - run: npm install -D @commitlint/cli @commitlint/config-conventional - - name: Print versions - run: | - git --version - node --version - npm --version - npx commitlint --version - - - name: Validate current commit (last commit) with commitlint + - run: npm ci + - run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose + if: github.event_name == 'pull_request' + - run: npx commitlint --from ${{ github.event.before }} --to ${{ github.event.after }} --verbose if: github.event_name == 'push' - run: npx commitlint --last --verbose - - name: Validate PR commits with commitlint - if: github.event_name == 'pull_request' - run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose + smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: ".nvmrc" + - run: npm ci + - run: | + npm run prettier:check + npm run lint + npm run fallow check-workflows: permissions: @@ -53,39 +59,51 @@ jobs: - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 shellcheck: - needs: - - commitlint runs-on: ubuntu-latest steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - name: Run shellcheck - uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 - with: - severity: warning + - run: shellcheck scripts/*.sh - smoke: - needs: - - commitlint + test: runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: authuser + POSTGRES_PASSWORD: authpass + POSTGRES_DB: authdb + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - fetch-depth: 0 persist-credentials: false - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: ".nvmrc" - run: npm ci - - run: | - npm run prettier:check - npm run lint - npm run fallow + - run: npm run db:generate + - run: npm run db:migrate + - run: npm test - test: + env: + DATABASE_URL: postgres://authuser:authpass@localhost:5432/authdb + GITHUB_CLIENT_ID: dummy-id-for-testing + GITHUB_CLIENT_SECRET: dummy-secret-for-testing + + e2e: needs: - - commitlint + - test runs-on: ubuntu-latest services: @@ -111,9 +129,18 @@ jobs: with: node-version-file: ".nvmrc" - run: npm ci + - run: npx playwright install chromium-headless-shell - run: npm run db:generate - run: npm run db:migrate - - run: npm test -- --disable-coverage + - run: node scripts/migrate.js + - run: | + node src/index.js > server.log 2>&1 & + for i in {1..30}; do + curl -sf 'http://127.0.0.1:3001/health?type=startup' && break + sleep 1 + done + cat server.log + npx playwright test env: DATABASE_URL: postgres://authuser:authpass@localhost:5432/authdb diff --git a/.gitignore b/.gitignore index 130b7a1..48121cb 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ lerna-debug.log* # test coverage and tap coverage/ .tap/ +test-results/ # worktrees .worktrees/ diff --git a/.taprc b/.taprc index fc35f45..557fc92 100644 --- a/.taprc +++ b/.taprc @@ -4,3 +4,4 @@ jobs: 1 timeout: 30 coverage-report: - "text" +allow-incomplete-coverage: true diff --git a/e2e/oauth-flow.spec.js b/e2e/oauth-flow.spec.js new file mode 100644 index 0000000..bccbcd0 --- /dev/null +++ b/e2e/oauth-flow.spec.js @@ -0,0 +1,68 @@ +import { test, expect } from "@playwright/test"; +import { AUTH_DEFAULT_PORT, PLANNER_DEFAULT_PORT } from "../src/config.js"; + +const authBase = `http://localhost:${AUTH_DEFAULT_PORT}`; + +test("OAuth 2.1 magic link flow", async ({ page, request }) => { + const email = `e2e-${Date.now()}@example.com`; + + // Clear any stale magic links + await request.delete(`${authBase}/api/test/magic-links`); + + // Start OAuth flow: unauthenticated user hits authorize endpoint with PKCE + const codeChallenge = "MJk6-W6P2z_PgOvWcEvbyqeIyc-GthZov8-QX37r0Vo"; + const authorizeParams = new URLSearchParams({ + client_id: "planner", + redirect_uri: `http://localhost:${PLANNER_DEFAULT_PORT}/auth/codebar/callback`, + response_type: "code", + state: "e2e-state-123", + scope: "openid profile", + code_challenge: codeChallenge, + code_challenge_method: "S256", + }); + await page.goto( + `${authBase}/api/auth/oauth2/authorize?${authorizeParams.toString()}`, + ); + + // OAuth provider redirects unauthenticated users to the login page + await expect(page).toHaveURL(/\/login/); + + // Click through to the magic link form + await page.getByRole("button", { name: /Send Magic Link/i }).click(); + await expect(page).toHaveURL(/\/login\/magic-link/); + + // Submit email + await page.fill('input[name="email"]', email); + await page.click('button[type="submit"]'); + + // Success page is shown + await expect(page).toHaveURL(/\/login\/magic-link\?success=/); + await expect(page.locator("body")).toContainText("Magic link sent"); + + // Fetch the magic link from the auth dev endpoint + const linksRes = await request.get(`${authBase}/api/test/magic-links`); + const links = await linksRes.json(); + const link = links.find((l) => l.email === email); + expect(link).toBeDefined(); + expect(link.url).toMatch(/magic-link\/verify/); + + // Call the magic link verify endpoint via API to establish a session. + // APIRequestContext automatically stores and sends cookies across requests. + const verifyRes = await request.get(link.url, { maxRedirects: 0 }); + expect(verifyRes.status()).toBe(302); + const verifyLocation = verifyRes.headers()["location"]; + expect(verifyLocation).toContain("/api/auth/oauth2/authorize"); + + // Call the authorize endpoint with the session cookie (don't follow redirects) + const authRes = await request.get( + `${authBase}/api/auth/oauth2/authorize?${authorizeParams.toString()}`, + { maxRedirects: 0 }, + ); + + // Should get a 302 redirect to the planner callback with the code + expect(authRes.status()).toBe(302); + const location = authRes.headers()["location"]; + expect(location).toBeTruthy(); + expect(location).toContain("code="); + expect(location).toContain("state=e2e-state-123"); +}); diff --git a/package-lock.json b/package-lock.json index e629c98..c3ad318 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,8 +6,9 @@ "": { "name": "auth-auth", "dependencies": { + "@better-auth/oauth-provider": "^1.6.20", "@hono/node-server": "^2.0.4", - "better-auth": "^1.6.16", + "better-auth": "^1.6.20", "date-fns": "^4.4.0", "hono": "^4.12.25", "hono-pino": "^0.10.3", @@ -18,6 +19,7 @@ "@commitlint/cli": "^21.0.2", "@commitlint/config-conventional": "^21.0.2", "@eslint/js": "^10.0.1", + "@playwright/test": "^1.61.0", "eslint": "^10.4.1", "fallow": "^2.92.1", "globals": "^17.6.0", @@ -45,6 +47,19 @@ "node": ">=14.13.1" } }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -88,9 +103,9 @@ } }, "node_modules/@better-auth/core": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.16.tgz", - "integrity": "sha512-a0+ZNaaYYxOdFXFXmOE36TgtYN8QDzSYDozaAH0zsiWB0oyljsENyCxHJSekysISftb0rFpVXNdw525aEAOa6w==", + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.20.tgz", + "integrity": "sha512-y73I1xNXuNYiHBFduWGRcJ2ro2rNuVDEYkgVMJtIaRXtbosdXHs9gfyQrHecgeHMHKx1SYSBT/CExak0vVMTng==", "license": "MIT", "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", @@ -98,8 +113,8 @@ "zod": "^4.3.6" }, "peerDependencies": { - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.2.2", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.6", @@ -116,14 +131,30 @@ } } }, + "node_modules/@better-auth/drizzle-adapter": { + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.20.tgz", + "integrity": "sha512-hJHfCdAiZrC7EmZAt3NAiGgcNo9Y5Qz3PLL+a9rODXaAJGCMvzUJniqef9wHuJBwU0SWW+2f4wXe8xQmaC/IKQ==", + "license": "MIT", + "peerDependencies": { + "@better-auth/core": "^1.6.20", + "@better-auth/utils": "0.4.2", + "drizzle-orm": "^0.45.2" + }, + "peerDependenciesMeta": { + "drizzle-orm": { + "optional": true + } + } + }, "node_modules/@better-auth/kysely-adapter": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.16.tgz", - "integrity": "sha512-ys/feL1p6By3/rQlMZ8QTgf9K2tZAIp1p+fGqT2krIoG5r+UsH3gMkUdbHlYxLt790Bo+Njkiqt59P0BMNsi+g==", + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.20.tgz", + "integrity": "sha512-Uvpmgbx5y8JqXroVanNzDdKzOl3HojoTz+/X6MR6zOUr25IzlYz660mjnu0rxKiIF55kD3CroqFsDzjNUw7ERw==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.16", - "@better-auth/utils": "0.4.1", + "@better-auth/core": "^1.6.20", + "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "peerDependenciesMeta": { @@ -133,23 +164,23 @@ } }, "node_modules/@better-auth/memory-adapter": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.16.tgz", - "integrity": "sha512-8mDqe+2PMF9hUxjGNP1NOcqU1AqjUgmE8YC1HTtxa+LjnO7zsAPSxGSyo1L+7buFNLtiNyGFxccHpwOkO4/Msw==", + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.20.tgz", + "integrity": "sha512-J5Ni0LlFijbzXlwu2rFHaD8zEFocmajyzWkRnHsq8LhV/Dk4iWQwwnqzLrPoDQEj8roECAUF03hrIeMzqWRqJQ==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.16", - "@better-auth/utils": "0.4.1" + "@better-auth/core": "^1.6.20", + "@better-auth/utils": "0.4.2" } }, "node_modules/@better-auth/mongo-adapter": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.16.tgz", - "integrity": "sha512-JbUg/v3m9WUX94ivVdUOF8t/w2mWNBWvqYMqyWybfHQEPR8cvcqsqpfYvwg9HLBrYwhKXBS3KcJ1Rtk6gZ19Yw==", + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.20.tgz", + "integrity": "sha512-ClDBJf6h4g85WJswxwQwxLaiyRU67Gmz/uaIf19tY1gqlLJDykSGjmqRNSBMG5rWABNzcNqbO4KG31rYUldbIw==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.16", - "@better-auth/utils": "0.4.1", + "@better-auth/core": "^1.6.20", + "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { @@ -158,14 +189,31 @@ } } }, + "node_modules/@better-auth/oauth-provider": { + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/oauth-provider/-/oauth-provider-1.6.20.tgz", + "integrity": "sha512-+YzrmMN9N5pv8s/nOaF6dUS3s0yH4PykZWLdaEeT7ZMWB+zEAa4E8TYKBH2CcWmlXmoYR4uAHtZYI+QiEu/guw==", + "license": "MIT", + "dependencies": { + "jose": "^6.1.3", + "zod": "^4.3.6" + }, + "peerDependencies": { + "@better-auth/core": "^1.6.20", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", + "better-auth": "^1.6.20", + "better-call": "1.3.6" + } + }, "node_modules/@better-auth/prisma-adapter": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.16.tgz", - "integrity": "sha512-2bIlA7wjBx+4N2QcM32xL/YojRuJpDvskXqT/dGYKToDIEl/7yr12cLYlqeaFLL0O0s5qNZ8jbDtlCz20eogeQ==", + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.20.tgz", + "integrity": "sha512-WhYdhSGuVSfu1peCSf2snmmVzfWjRaEvbSrsNCusiwGE9l94HlES4mjSPM48fed24hL7yg4j1dYK/yjEt87FpQ==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.16", - "@better-auth/utils": "0.4.1", + "@better-auth/core": "^1.6.20", + "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, @@ -179,29 +227,30 @@ } }, "node_modules/@better-auth/telemetry": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.16.tgz", - "integrity": "sha512-A782UQvlqZBddw0j2Q6tdroHulIpMlqQh/pbw2up30drLi66jz1ttgShRmryfOLAqN4DHqteuRrSsqDDrsp/pA==", + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.20.tgz", + "integrity": "sha512-3BhbY3naQDERvdJvJ7fGszVY6rpsVfc6c9uyBVZlC1coVEF/rkM0rIcjtMVI1GUH7vWy1wjR6qF5vQnMun3XNQ==", "license": "MIT", "peerDependencies": { - "@better-auth/core": "^1.6.16", - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.2.2" + "@better-auth/core": "^1.6.20", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1" } }, "node_modules/@better-auth/utils": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.1.tgz", - "integrity": "sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.2.tgz", + "integrity": "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==", "license": "MIT", "dependencies": { "@noble/hashes": "^2.0.1" } }, "node_modules/@better-fetch/fetch": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.2.2.tgz", - "integrity": "sha512-xlgQcYROGFgKg5FY7ZLppFmG7rR5Hkmz7tgDuQeR79i5KhKRjr2QC9xsBG2qEGPJJjf9bxzg/NMW2hEUWs5OnA==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.1.tgz", + "integrity": "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==", + "license": "MIT" }, "node_modules/@commitlint/cli": { "version": "21.0.2", @@ -225,49 +274,6 @@ "node": ">=22.12.0" } }, - "node_modules/@commitlint/cli/node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@commitlint/cli/node_modules/yargs": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^9.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "string-width": "^7.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^22.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/@commitlint/cli/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, "node_modules/@commitlint/config-conventional": { "version": "21.0.2", "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.0.2.tgz", @@ -296,30 +302,6 @@ "node": ">=22.12.0" } }, - "node_modules/@commitlint/config-validator/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/@commitlint/ensure": { "version": "21.0.1", "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.0.1.tgz", @@ -561,9 +543,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -689,9 +671,9 @@ } }, "node_modules/@fallow-cli/darwin-arm64": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/darwin-arm64/-/darwin-arm64-2.92.1.tgz", - "integrity": "sha512-kPl7WWX/QKL/V2lcK60HlITaXuEYTOUC4LGiJxGltXZcnG9JWegmOw8o8VmlyiDMlikKMCwAwjUi4RPejwkwYA==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/darwin-arm64/-/darwin-arm64-2.101.0.tgz", + "integrity": "sha512-LUdT8IGU0v/c9/N1hq6HrrCGUtwKFNw2NrZBx/TS1nsqt/EfrDAhRBYm4nE1y7O/p47WlCcHHcEq7O0F4Tb5tA==", "cpu": [ "arm64" ], @@ -703,9 +685,9 @@ ] }, "node_modules/@fallow-cli/darwin-x64": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/darwin-x64/-/darwin-x64-2.92.1.tgz", - "integrity": "sha512-o+6otOHDrb8qasWWQQ235I6Pudmqg7Vu3rFQyCZMHKXuUxd3mKUyM6RFU/y4HM8yigotofv/rJjf9QXWNa13nA==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/darwin-x64/-/darwin-x64-2.101.0.tgz", + "integrity": "sha512-W8uM2xbnVioP6GpUGbSV1aGZUC8Ru3D4a1JSkSOZoPvS4K7CWWZte0KaGe35FYOkR+g4DLvfkjrjLcMEpi+3OQ==", "cpu": [ "x64" ], @@ -717,9 +699,9 @@ ] }, "node_modules/@fallow-cli/linux-arm64-gnu": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/linux-arm64-gnu/-/linux-arm64-gnu-2.92.1.tgz", - "integrity": "sha512-CI/km5TSLTgzghhS/2dEtfLm/cDVlHhanpUG+zexxjpPq33hfXZQhn0Klpa+SyTduKfZADSAfyy/FspOH4q3uQ==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-arm64-gnu/-/linux-arm64-gnu-2.101.0.tgz", + "integrity": "sha512-KUsm8EAjuLVi5TUb5K/ADyLXLe5gGQnOpU/4u8nnzxJmaiAWamibnkwFlKdHx88NMVIpOG/VQ1zVyS8HRv2zNQ==", "cpu": [ "arm64" ], @@ -731,9 +713,9 @@ ] }, "node_modules/@fallow-cli/linux-arm64-musl": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/linux-arm64-musl/-/linux-arm64-musl-2.92.1.tgz", - "integrity": "sha512-kTNpiBfmaUSCveFVFvTHb6+dbDsb9vtylvjzpAG8t8xCCJV9dGdHWZyDWf5LQQDjXlkIhvaNgCf8kX5j1IuY5g==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-arm64-musl/-/linux-arm64-musl-2.101.0.tgz", + "integrity": "sha512-AJKZl4YP2p/9xpPo31fAzLO95HrBAbiQD+nYzQ6AlibZV2J41teswD/dVBXW+togR9qME0pMhx0UdESRoQ2hAw==", "cpu": [ "arm64" ], @@ -745,9 +727,9 @@ ] }, "node_modules/@fallow-cli/linux-x64-gnu": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/linux-x64-gnu/-/linux-x64-gnu-2.92.1.tgz", - "integrity": "sha512-hocWfN8IcRRpAy27jXuXQYOVSCu8WMSEvpH8AvhF24/naOf2xwchzK9NRGDXui+MeARKB5kRWBbwv8JEcuMXLQ==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-x64-gnu/-/linux-x64-gnu-2.101.0.tgz", + "integrity": "sha512-Q3VPCUGtEkrDRujrrmNI64L7F7EuE845eRMYNI23DuFOyif3VaprLRAAjcDShZnbaoaOkc5ToJBYuo6V30ntkQ==", "cpu": [ "x64" ], @@ -759,9 +741,9 @@ ] }, "node_modules/@fallow-cli/linux-x64-musl": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/linux-x64-musl/-/linux-x64-musl-2.92.1.tgz", - "integrity": "sha512-b/xcP2D0TAfBp3ZcSN7wHRdWBGd4KREyxeHpIYf8SRSRl8ktf084d50rnV2p/I9zB2EszbJMgNnLLqMQIENXjw==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/linux-x64-musl/-/linux-x64-musl-2.101.0.tgz", + "integrity": "sha512-VQIBVwm4afBUCkpAOH5I2QoikFONkWOmC/u8pePElKv8Oy/GYv8K0Lz2stNU3s/+u17LYUKVLcmiIgoV3F4hyA==", "cpu": [ "x64" ], @@ -773,9 +755,9 @@ ] }, "node_modules/@fallow-cli/win32-arm64-msvc": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/win32-arm64-msvc/-/win32-arm64-msvc-2.92.1.tgz", - "integrity": "sha512-1hvMQTKSp3fTm365ZY1lRveN71T96VYU3bKQWkE6T6wKRHbyhPP4KJNX0wKyDTZ7RPEeBqfNfczOn9sV1dyCXw==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/win32-arm64-msvc/-/win32-arm64-msvc-2.101.0.tgz", + "integrity": "sha512-JVdh1GCxYXB5Pglx4Z/XY28lMJXNpMsas40q01sIm9TK/wTDN28+sF754c17IyV4wwH4iews9hCc2VnKj9Mg3Q==", "cpu": [ "arm64" ], @@ -787,9 +769,9 @@ ] }, "node_modules/@fallow-cli/win32-x64-msvc": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/@fallow-cli/win32-x64-msvc/-/win32-x64-msvc-2.92.1.tgz", - "integrity": "sha512-lMso9EJyZdxvb132WDO/UYudDu6BE/iVWcXtdtQvsIVRQ/42dMXrEe7qQH4ikx037behritDuBDtg+LK+KKHmg==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/@fallow-cli/win32-x64-msvc/-/win32-x64-msvc-2.101.0.tgz", + "integrity": "sha512-AH4PTlTGeaYsiGIBJhuKIYt10ErkgTXBiIKkVCggHLxjmV4E/f9/AYC2YTwRXO8DtZngCIfH1BAzwrL+SmESQQ==", "cpu": [ "x64" ], @@ -811,9 +793,9 @@ } }, "node_modules/@hono/node-server": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.4.tgz", - "integrity": "sha512-Ut3y0dMMPWy6bZ2kVfx25EOVbZlm15dhF4mOsezMlhpNHy+4MkU1qN9Y6lnruYi4wPmFzimGX2X7LF/FwHli4A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.5.tgz", + "integrity": "sha512-yQFvDmyDo3y6rEOJZDUYPJ49DIKTPpIk4kGvm40xx4Ejne0Pu9a1+exxPN+C1UppWK/WGZX9F++/Xs231tE86g==", "license": "MIT", "engines": { "node": ">=20" @@ -823,41 +805,41 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -975,16 +957,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@isaacs/which/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", @@ -1024,9 +996,9 @@ } }, "node_modules/@noble/ciphers": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", - "integrity": "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", + "integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -1097,26 +1069,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/git/node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/git/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, "node_modules/@npmcli/git/node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", @@ -1202,16 +1154,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, "node_modules/@npmcli/promise-spawn/node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", @@ -1281,6 +1223,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.0.tgz", + "integrity": "sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@sigstore/bundle": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", @@ -1586,6 +1544,16 @@ "node": "20 || >=22" } }, + "node_modules/@tapjs/core/node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/@tapjs/error-serdes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@tapjs/error-serdes/-/error-serdes-4.3.2.tgz", @@ -1901,6 +1869,20 @@ "@tapjs/core": "4.5.8" } }, + "node_modules/@tapjs/test/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@tapjs/typescript": { "version": "3.5.10", "resolved": "https://registry.npmjs.org/@tapjs/typescript/-/typescript-3.5.10.tgz", @@ -1990,9 +1972,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -2011,20 +1993,20 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", - "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~8.3.0" } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-A3/9yZTt2V5NlDURcVJ4mN2YjfeQTXCRyLuENKrNdGhO+y59mC/2UDr7UvpB3Li+83TRAuhDN8SBoM+7gkHdzQ==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-2epYxi5NDRPOvAG8TPEFd43qDVGHhD4IbIHF0uhsy72OSUbVY2rjEAv1DkW1Q2p/YbszXd7ZL/Ulch8VuDDS1g==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2034,19 +2016,19 @@ "node": ">=16.20.0" }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260604.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260604.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20260604.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260604.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20260604.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260604.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20260604.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260621.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260621.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20260621.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260621.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20260621.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260621.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20260621.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-zs616um9UuaODLsNlCu5Aw95rFcTV4u3hVt090r6k0lVvTxfaJOv8HKA6BpIotcEYlZlMQowrMSYCCdedo7iyA==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-E81mNlY2JqZlDqflCvRpt6WhU6ha1SOkRC85o+EmKZJCjBxaaJpBGdwGjIisv2jiEMcHy2OW4ZHcE9GTM1BL2A==", "cpu": [ "arm64" ], @@ -2061,9 +2043,9 @@ } }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-pOdNAf2pwc9JBjo8gUsvLs5uqg7d0AhYXfE8/3zvPKBlIZG+mTcvEWW1hPawlWxXzf/vxnP4dgYwUHAMDghhKA==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-VS4VF8L0So56tC1jaJ57pMQOGd0Hc1wU0RIMkJi41/dKvcmc/WTRa7hiWJwzZLxXhZoVkdjesT36D9GahoAulg==", "cpu": [ "x64" ], @@ -2078,9 +2060,9 @@ } }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-IKaZL3i5HKmKqwb2IZEXW1j68fVg1HsvAaXkbrkOIG/J5Eyksu5tEnTzucrIY1oPdzgHT+y2HpDIYp2sGeHFvw==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-2Xp+pu7Xu9EMKxYFiZuyIzd6KUkMn7SvElzgBRwbrhfsk54Jieg2sxyL3aAw9xfzRsEN4m1VrU3Z/iq2gL5OUw==", "cpu": [ "arm" ], @@ -2095,9 +2077,9 @@ } }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-GjIrt6YHP3bbOWBCCE08SlBSDf84Lnjn3Td822/lOX9nm6ODlA/HI7rtGh7KzS/fxehep2Vy4dXU4Il12X1s5A==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-Ek1uLWN6VzqwwTQNu1vcnRW4F3ipqvediLEwJP6uR5P6vrXYE7XvcGLEJcXiw31SR40NhxZW/RhLcJrHQcehrA==", "cpu": [ "arm64" ], @@ -2112,9 +2094,9 @@ } }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-twQ7XDjsmaHIevN1MjeRYIVLUPL0fBm8A0jg1FhGYPckhTxEBiHIpJf9E//eFidAdrEHjeTSBP1jJw3GNAensg==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-VUhea6+Drw+PHRpsjsY7lNOZtXAlRXLHrpYynJU3jfnSflYgoqkrvlSzWVhcvhkjxGOZsqlIV+5ryy8ANZ6peg==", "cpu": [ "x64" ], @@ -2129,9 +2111,9 @@ } }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-QBRxaVT3SFiNfOhwYb/56ddpHWPMFdfiJ4zkFJIjaAXeZ/ssWNHM9lH7yR++GrE9VTsUZ4eVSZfOmQbzRERhgw==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-JyxxNWWs2X7WzK90zN8hgGKqP4zo2/y7Z2tlLLaHhSFtEh1AC+tsQun3NBlW9JvF1vHhGI/hPpnoZOxLO0CEWw==", "cpu": [ "arm64" ], @@ -2146,9 +2128,9 @@ } }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20260604.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260604.1.tgz", - "integrity": "sha512-hR7YHoRpm88Q86gK6/IMayWUA+ROdHGzOPKK8EBp1xD/Cg2Bh5AXbut8HcyUDx/bcGQvnuht/XsW47bT3to9mg==", + "version": "7.0.0-dev.20260621.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260621.1.tgz", + "integrity": "sha512-2ijIynqyxK1LkWUpLlmsBKuH7XHk6K4TAwdnfZ7x7BQHF9Z3okIXtKMzOik8fajB3oooVrEvbfOe2Mf+uZ7maQ==", "cpu": [ "x64" ], @@ -2173,9 +2155,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -2219,16 +2201,16 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -2331,27 +2313,30 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/better-auth": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.16.tgz", - "integrity": "sha512-YlBITnH3LIBRD+JpR1XRIToJAVVpoQvZzRc4sm5W0/bnPZKLbsmtXbVWJF3ypo9TVnF6geczJKprG/CsWT07Wg==", - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.6.16", - "@better-auth/drizzle-adapter": "1.6.16", - "@better-auth/kysely-adapter": "1.6.16", - "@better-auth/memory-adapter": "1.6.16", - "@better-auth/mongo-adapter": "1.6.16", - "@better-auth/prisma-adapter": "1.6.16", - "@better-auth/telemetry": "1.6.16", - "@better-auth/utils": "0.4.1", - "@better-fetch/fetch": "1.2.2", + "version": "1.6.20", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.20.tgz", + "integrity": "sha512-fSpGHGRKiGRiYVd3QTQtuVZ8oxpiSe/7ip0Rpvt/Sy8zQbEbVKUPMOhE0gLXg+FjqTUsIo7582hxUYxtEcqUpA==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.6.20", + "@better-auth/drizzle-adapter": "1.6.20", + "@better-auth/kysely-adapter": "1.6.20", + "@better-auth/memory-adapter": "1.6.20", + "@better-auth/mongo-adapter": "1.6.20", + "@better-auth/prisma-adapter": "1.6.20", + "@better-auth/telemetry": "1.6.20", + "@better-auth/utils": "0.4.2", + "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.6", @@ -2442,22 +2427,6 @@ } } }, - "node_modules/better-auth/node_modules/@better-auth/drizzle-adapter": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.16.tgz", - "integrity": "sha512-AZjswadpR7zlQduj3fRSsu1R5ldQRR9AeFqoxXRI4colrQhevOVY+tJr8RTJv9Nh18e9FMYDXUju2GX+QWHDzg==", - "license": "MIT", - "peerDependencies": { - "@better-auth/core": "^1.6.16", - "@better-auth/utils": "0.4.1", - "drizzle-orm": "^0.45.2" - }, - "peerDependenciesMeta": { - "drizzle-orm": { - "optional": true - } - } - }, "node_modules/better-call": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.6.tgz", @@ -2479,9 +2448,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -2491,16 +2460,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/brace-expansion/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/c8": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", @@ -2535,6 +2494,47 @@ } } }, + "node_modules/c8/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/c8/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/c8/node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2552,8 +2552,83 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache": { - "version": "20.0.4", + "node_modules/c8/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/c8/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/c8/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache": { + "version": "20.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", "dev": true, @@ -2613,6 +2688,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -2627,154 +2712,91 @@ } }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate/node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=20" } }, "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=18" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -2989,16 +3011,16 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3026,9 +3048,9 @@ "license": "MIT" }, "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, @@ -3066,9 +3088,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", - "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.48.1.tgz", + "integrity": "sha512-wfnXlwd5I75eXRtdD2vuEs50xHHESECDsGD7yiQnfFVNoa5522NwXEbmgo98LfiukSQHs+mBM7/YG3qKJB9/mQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -3100,11 +3122,14 @@ } }, "node_modules/eslint": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", - "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -3187,19 +3212,30 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=10.13.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", @@ -3289,9 +3325,9 @@ "license": "Apache-2.0" }, "node_modules/fallow": { - "version": "2.92.1", - "resolved": "https://registry.npmjs.org/fallow/-/fallow-2.92.1.tgz", - "integrity": "sha512-WAKPfDwUZ4gdNJBUh1SJZi7gdkqEG9nCvL/+rlcapsXxykwEyfG3xqqYD8bXDsEPzKHMc+/YIv8Wierz7d5vAA==", + "version": "2.101.0", + "resolved": "https://registry.npmjs.org/fallow/-/fallow-2.101.0.tgz", + "integrity": "sha512-7N1tBOD2EuomFhPNyTJl4PgcWNlrvz7hy8kfvbNBlFbt62byF8mDzAZ5x+GrjDVmetLjNerMeOojpwEy3GRJ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -3306,14 +3342,14 @@ "node": ">=16" }, "optionalDependencies": { - "@fallow-cli/darwin-arm64": "2.92.1", - "@fallow-cli/darwin-x64": "2.92.1", - "@fallow-cli/linux-arm64-gnu": "2.92.1", - "@fallow-cli/linux-arm64-musl": "2.92.1", - "@fallow-cli/linux-x64-gnu": "2.92.1", - "@fallow-cli/linux-x64-musl": "2.92.1", - "@fallow-cli/win32-arm64-msvc": "2.92.1", - "@fallow-cli/win32-x64-msvc": "2.92.1" + "@fallow-cli/darwin-arm64": "2.101.0", + "@fallow-cli/darwin-x64": "2.101.0", + "@fallow-cli/linux-arm64-gnu": "2.101.0", + "@fallow-cli/linux-arm64-musl": "2.101.0", + "@fallow-cli/linux-x64-gnu": "2.101.0", + "@fallow-cli/linux-x64-musl": "2.101.0", + "@fallow-cli/win32-arm64-msvc": "2.101.0", + "@fallow-cli/win32-x64-msvc": "2.101.0" } }, "node_modules/fast-deep-equal": { @@ -3473,6 +3509,21 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-loop": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-4.0.0.tgz", @@ -3538,6 +3589,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/global-directory": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", @@ -3554,16 +3618,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-directory/node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/globals": { "version": "17.6.0", "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", @@ -3595,9 +3649,9 @@ } }, "node_modules/hono": { - "version": "4.12.25", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", - "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", + "version": "4.12.26", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.26.tgz", + "integrity": "sha512-uyZtpnYxM9CmQ7QsQknM4zN8EftNqhON1qYeIKM0Se67CCEe2c44xyGURwB0axX2fBDu1dqHrHAc1hmNT8ITkw==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -3768,75 +3822,241 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/ink": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", + "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.1.3", + "ansi-escapes": "^7.0.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^4.0.0", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.22.0", + "indent-string": "^5.0.0", + "is-in-ci": "^1.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.29.0", + "scheduler": "^0.23.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^7.1.0", + "stack-utils": "^2.0.6", + "string-width": "^7.2.0", + "type-fest": "^4.27.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "react": ">=18.0.0", + "react-devtools-core": "^4.19.1" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/ink/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ink/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ink/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/ink/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ink": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", - "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", + "node_modules/ink/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "@alcalzone/ansi-tokenize": "^0.1.3", - "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", - "auto-bind": "^5.0.1", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "cli-cursor": "^4.0.0", - "cli-truncate": "^4.0.0", - "code-excerpt": "^4.0.0", - "es-toolkit": "^1.22.0", - "indent-string": "^5.0.0", - "is-in-ci": "^1.0.0", - "patch-console": "^2.0.0", - "react-reconciler": "^0.29.0", - "scheduler": "^0.23.0", - "signal-exit": "^3.0.7", - "slice-ansi": "^7.1.0", - "stack-utils": "^2.0.6", - "string-width": "^7.2.0", - "type-fest": "^4.27.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0", - "ws": "^8.18.0", - "yoga-layout": "~3.2.1" + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { "node": ">=18" }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "react": ">=18.0.0", - "react-devtools-core": "^4.19.1" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-devtools-core": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/ink/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", @@ -3872,13 +4092,16 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3947,11 +4170,14 @@ } }, "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -4072,9 +4298,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, @@ -4186,91 +4412,6 @@ "node": ">=22.13.0" } }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/listr2/node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", - "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "string-width": "^8.2.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4307,30 +4448,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^5.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/log-update/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { "node": ">=18" @@ -4339,21 +4490,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/loose-envify": { @@ -4463,13 +4615,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -4667,9 +4819,9 @@ } }, "node_modules/node-gyp": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", - "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", "dev": true, "license": "MIT", "dependencies": { @@ -4691,16 +4843,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, "node_modules/node-gyp/node_modules/which": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", @@ -4852,16 +4994,16 @@ } }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4948,9 +5090,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/pacote": { - "version": "21.5.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.0.tgz", - "integrity": "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ==", + "version": "21.5.1", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.5.1.tgz", + "integrity": "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg==", "dev": true, "license": "ISC", "dependencies": { @@ -5059,14 +5201,14 @@ } }, "node_modules/pg": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", - "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", + "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.13.0", + "pg-connection-string": "^2.14.0", "pg-pool": "^3.14.0", - "pg-protocol": "^1.14.0", + "pg-protocol": "^1.15.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -5093,9 +5235,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", - "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", "license": "MIT" }, "node_modules/pg-int8": { @@ -5117,9 +5259,9 @@ } }, "node_modules/pg-protocol": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", - "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz", + "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==", "license": "MIT" }, "node_modules/pg-types": { @@ -5198,20 +5340,52 @@ "split2": "^4.0.0" } }, - "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "license": "MIT" - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, "engines": { - "node": ">= 6" + "node": ">=18" } }, "node_modules/polite-json": { @@ -5521,29 +5695,22 @@ } }, "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", @@ -5605,10 +5772,10 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "devOptional": true, + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5694,49 +5861,23 @@ "url": "https://opencollective.com/sinon" } }, - "node_modules/sinon/node_modules/diff": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", - "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -5779,9 +5920,9 @@ } }, "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0" @@ -5884,18 +6025,17 @@ } }, "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5927,13 +6067,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6118,16 +6251,6 @@ "node": ">=18" } }, - "node_modules/tar/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/tcompare": { "version": "9.3.2", "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-9.3.2.tgz", @@ -6142,6 +6265,16 @@ "node": "20 || >=22" } }, + "node_modules/tcompare/node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/test-exclude": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", @@ -6175,6 +6308,13 @@ "node": ">=12" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", @@ -6324,17 +6464,23 @@ } }, "node_modules/thread-stream": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", - "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", "license": "MIT", "dependencies": { - "real-require": "^0.2.0" + "real-require": "^1.0.0" }, "engines": { "node": ">=20" } }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", @@ -6400,6 +6546,20 @@ "node": "20 || >=22" } }, + "node_modules/tshy/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/tuf-js": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", @@ -6452,11 +6612,12 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6466,9 +6627,9 @@ } }, "node_modules/undici": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", - "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", "dev": true, "license": "MIT", "engines": { @@ -6476,9 +6637,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT", "peer": true @@ -6494,9 +6655,9 @@ } }, "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz", + "integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==", "dev": true, "funding": [ "https://github.com/sponsors/broofa", @@ -6576,6 +6737,13 @@ "node": ">= 8" } }, + "node_modules/which/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/widest-line": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", @@ -6592,6 +6760,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -6603,18 +6796,18 @@ } }, "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", + "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -6665,13 +6858,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6792,22 +6978,21 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { @@ -6820,59 +7005,39 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "license": "ISC", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { diff --git a/package.json b/package.json index 28e16c6..df2675c 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,13 @@ "test:pg:start": "scripts/container.sh start", "test:pg:stop": "scripts/container.sh stop", "test:pg:status": "scripts/container.sh status", - "test:coverage": "tap --coverage-report=html" + "test:coverage": "tap --coverage-report=html", + "e2e": "playwright test" }, "dependencies": { + "@better-auth/oauth-provider": "^1.6.20", "@hono/node-server": "^2.0.4", - "better-auth": "^1.6.16", + "better-auth": "^1.6.20", "date-fns": "^4.4.0", "hono": "^4.12.25", "hono-pino": "^0.10.3", @@ -41,6 +43,7 @@ "@commitlint/cli": "^21.0.2", "@commitlint/config-conventional": "^21.0.2", "@eslint/js": "^10.0.1", + "@playwright/test": "^1.61.0", "eslint": "^10.4.1", "fallow": "^2.92.1", "globals": "^17.6.0", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..e9107e0 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,17 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./e2e", + fullyParallel: false, + workers: 1, + use: { + baseURL: "http://127.0.0.1:3001", + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"], headless: true }, + }, + ], +}); diff --git a/scripts/migrate.js b/scripts/migrate.js new file mode 100644 index 0000000..a9dcf3d --- /dev/null +++ b/scripts/migrate.js @@ -0,0 +1,23 @@ +/** + * Run Better Auth database migrations. + * + * Usage: node scripts/migrate.js + * + * This is idempotent — safe to run multiple times. + * Intended for Heroku release phase or one-off commands, + * not the web process boot path. + */ +import { getMigrations } from "better-auth/db/migration"; +import { auth, db } from "../src/auth.js"; +import { seedPlannerClient } from "../src/app/db/seed-client.js"; +import appConfig from "../src/config.js"; + +const migrations = await getMigrations(auth.options); +await migrations.runMigrations(); + +// Seed the planner OAuth client after migrations +await seedPlannerClient(db, appConfig.planner_url + "/auth/codebar/callback"); + +console.log("Migrations complete. Planner client seeded."); + +process.exit(0); diff --git a/src/app/app.js b/src/app/app.js index 37a796c..bc82a94 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -3,6 +3,7 @@ import { pinoLogger } from "hono-pino"; import { serveStatic } from "@hono/node-server/serve-static"; import { db } from "../auth.js"; +import { devMagicLinks } from "../dev/magic-links.js"; import authHandler from "./routes/auth.js"; import healthHandler from "./routes/health.js"; import homeHandler from "./routes/home.js"; @@ -63,10 +64,17 @@ export function createApp(auth, injectedDb) { return next(); }); - // Better-auth API routes — ** matches any depth (e.g. /api/auth/sign-in/social) + // Better-auth API and OAuth routes app.on(["POST", "GET"], "/api/auth/**", (c) => { return auth.handler(c.req.raw); }); + // OAuth provider metadata endpoints + app.on(["GET", "HEAD"], "/.well-known/oauth-authorization-server", (c) => { + return auth.handler(c.req.raw); + }); + app.on(["GET", "HEAD"], "/.well-known/openid-configuration", (c) => { + return auth.handler(c.req.raw); + }); // Health check routes (no session required) app.route("/", healthHandler); @@ -99,6 +107,15 @@ export function createApp(auth, injectedDb) { app.route("/demo", demoHandler); + // Dev-only endpoint for Playwright tests to retrieve captured magic links + if (process.env.NODE_ENV !== "production") { + app.get("/api/test/magic-links", (c) => c.json(devMagicLinks)); + app.delete("/api/test/magic-links", (c) => { + devMagicLinks.length = 0; + return c.json({ cleared: true }); + }); + } + // serve assets, etc. app.use("/static/*", serveStatic({ root: "./" })); diff --git a/src/app/components/login.js b/src/app/components/login.js index 4f73cef..c840514 100644 --- a/src/app/components/login.js +++ b/src/app/components/login.js @@ -1,21 +1,17 @@ import { html } from "hono/html"; -// GitHub login -export const GitHubButton = ({ redirectUrl } = {}) => html` +export const GitHubButton = ({ callbackURL } = {}) => html`

Using GitHub

- +
+ + +
`; -// Magic link button component -export const MagicLinkButton = ({ redirectUrl } = {}) => html` +export const MagicLinkButton = ({ callbackURL } = {}) => html`

Using magic link

- +
`; diff --git a/src/app/db/seed-client.js b/src/app/db/seed-client.js new file mode 100644 index 0000000..fff5fab --- /dev/null +++ b/src/app/db/seed-client.js @@ -0,0 +1,24 @@ +/** + * Seed the planner OAuth client directly into the database. + * + * Bypasses the admin-only createOAuthClient API by inserting the + * first-party client row via raw SQL. + */ + +// ponytail: raw SQL because Better Auth doesn't expose a public API to +// create OAuth clients without admin auth. If the schema changes, update here. +export async function seedPlannerClient(db, redirectUri, schemaName) { + const table = schemaName ? `"${schemaName}"."oauthClient"` : `"oauthClient"`; + const sql = `INSERT INTO ${table} ( + id, "clientId", "clientSecret", name, "redirectUris", + "grantTypes", "responseTypes", "tokenEndpointAuthMethod", + "public", "skipConsent", "requirePKCE", "createdAt", "updatedAt" + ) VALUES ( + 'planner-seed', 'planner', NULL, 'Codebar Planner', + to_jsonb(ARRAY[$1]), to_jsonb(ARRAY['authorization_code']), + to_jsonb(ARRAY['code']), 'none', true, true, true, + NOW(), NOW() + ) + ON CONFLICT ("clientId") DO NOTHING`; + await db.query(sql, [redirectUri]); +} diff --git a/src/app/routes/auth.js b/src/app/routes/auth.js index 52649fe..d7486df 100644 --- a/src/app/routes/auth.js +++ b/src/app/routes/auth.js @@ -3,100 +3,134 @@ import { html } from "hono/html"; import { Layout, Navigation } from "../components/layout.js"; import { Message, FormSection } from "../components/common.js"; import { GitHubButton, MagicLinkButton } from "../components/login.js"; -import { getLink } from "../utils/links.js"; -import { validateRedirectUrl } from "../utils/redirect.js"; import { getAuthFromContext } from "../utils/auth.js"; import { logout } from "../handlers/logout.js"; +import appConfig from "../../config.js"; -export default new Hono() - .get("/login", (c) => { - const error = c.req.query("error"); - const success = c.req.query("success"); - const redirectUrl = validateRedirectUrl(c.req.query("redirect_url"), ""); +function getCallbackURL(c) { + const url = new URL(c.req.url); + const search = new URLSearchParams(url.search); + const base = appConfig.base_url; + if (search.has("response_type")) { + const params = new URLSearchParams(); + for (const key of [ + "response_type", + "client_id", + "redirect_uri", + "state", + "scope", + "code_challenge", + "code_challenge_method", + ]) { + if (search.has(key)) params.set(key, search.get(key)); + } + return `${base}/api/auth/oauth2/authorize?${params.toString()}`; + } + const params = new URLSearchParams({ + client_id: "planner", + redirect_uri: appConfig.allowed_redirects[0], + response_type: "code", + scope: "openid profile", + }); + return `${base}/api/auth/oauth2/authorize?${params.toString()}`; +} - return c.html( - Layout({ - title: "Sign In", - children: html` -

Sign In

- ${Navigation({ - back: { href: "/", text: "Back to Home" }, - })} - ${Message({ error, success })} - ${FormSection({ - children: html` - ${MagicLinkButton({ redirectUrl })} - ${GitHubButton({ redirectUrl })} - `, - })} - `, - }), - ); - }) - .get("/logout", logout) - .post("/logout", logout) - .get("/login/magic-link", (c) => { - const redirectUrl = validateRedirectUrl(c.req.query("redirect_url"), ""); +function showLogin(c) { + const error = c.req.query("error"); + const success = c.req.query("success"); + const callbackURL = getCallbackURL(c); - return c.html( - Layout({ - title: "Magic Link Login", - children: html` -

Magic Link Login

- ${Navigation({ - back: { - href: getLink("/login", redirectUrl), - text: "Back to Login", - }, - })} - ${Message({ - error: c.req.query("error"), - success: c.req.query("success"), - })} - ${FormSection({ - children: html` -
+ return c.html( + Layout({ + title: "Sign In", + children: html` +

Sign In

+ ${Navigation({ back: { href: "/", text: "Back to Home" } })} + ${Message({ error, success })} + ${FormSection({ + children: html` + ${MagicLinkButton({ callbackURL })} ${GitHubButton({ callbackURL })} + `, + })} + `, + }), + ); +} + +function showMagicLinkForm(c) { + const callbackURL = c.req.query("callbackURL") || getCallbackURL(c); + + return c.html( + Layout({ + title: "Magic Link Login", + children: html` +

Magic Link Login

+ ${Navigation({ back: { href: "/login", text: "Back to Login" } })} + ${Message({ + error: c.req.query("error"), + success: c.req.query("success"), + })} + ${FormSection({ + children: html` + + +
-
- -
- - - `, - })} - `, - }), - ); - }) - .post("/login/magic-link", async (c) => { - const auth = getAuthFromContext(c); - const body = await c.req.parseBody(); - const { email, redirect_url } = body; +
+ + + `, + })} + `, + }), + ); +} + +async function sendMagicLink(c) { + const auth = getAuthFromContext(c); + const body = await c.req.parseBody(); + const { email, callbackURL } = body; - const intermediateURL = `/login/magic-link?success=${encodeURIComponent("Magic link sent! Check your email.")}`; + await auth.api.signInMagicLink({ + body: { + email, + callbackURL, + errorCallbackURL: `${appConfig.base_url}/login?error=${encodeURIComponent("The magic link has expired or already been used")}`, + }, + headers: c.req.raw.headers, + }); + return c.redirect( + `/login/magic-link?success=${encodeURIComponent("Magic link sent! Check your email.")}`, + ); +} - const callbackURL = validateRedirectUrl(redirect_url, "/profile"); - const errorCallbackURL = validateRedirectUrl( - redirect_url, - "/login/magic-link", - ); +async function startGitHubOAuth(c) { + const auth = getAuthFromContext(c); + const body = await c.req.parseBody(); + const { callbackURL } = body; - await auth.api.signInMagicLink({ - body: { - email, - callbackURL, - errorCallbackURL, - }, - headers: c.req.raw.headers, - }); - return c.redirect(intermediateURL); + const data = await auth.api.signInSocial({ + body: { provider: "github", callbackURL }, }); + + if (data.url) { + return c.redirect(data.url); + } + + return c.redirect( + `/login?error=${encodeURIComponent("Failed to initiate GitHub sign-in")}`, + ); +} + +export default new Hono() + .get("/login", showLogin) + .get("/logout", logout) + .post("/logout", logout) + .get("/login/magic-link", showMagicLinkForm) + .post("/login/magic-link", sendMagicLink) + .post("/login/github", startGitHubOAuth); diff --git a/src/auth.js b/src/auth.js index f77a6f5..0e83ed7 100644 --- a/src/auth.js +++ b/src/auth.js @@ -1,7 +1,9 @@ import { Pool } from "pg"; import { betterAuth } from "better-auth"; -import { admin, magicLink } from "better-auth/plugins"; +import { admin, magicLink, jwt } from "better-auth/plugins"; +import { oauthProvider } from "@better-auth/oauth-provider"; import appConfig from "./config.js"; +import { devMagicLinks } from "./dev/magic-links.js"; // PostgreSQL connection pool for CI/production and local dev // SSL only for non-local connections (Heroku requires it; local/CI does not) @@ -42,6 +44,27 @@ export const auth = betterAuth({ updateAge: 60 * 60 * 24, // 1 day (update session every day) }, plugins: [ + jwt({ + jwks: { + keyPairConfig: { alg: "RS256" }, + }, + jwt: { + issuer: appConfig.base_url, + audience: "planner", + expirationTime: "15m", + definePayload: (session) => ({ + email: session.user.email, + name: session.user.name, + }), + }, + }), + oauthProvider({ + loginPage: "/login", + scopes: ["openid", "profile"], + accessTokenExpiresIn: 900, // 15 minutes + validAudiences: ["planner"], + allowDynamicClientRegistration: false, + }), magicLink({ sendMagicLink: async ({ email, url }) => { const apiKey = process.env.SENDGRID_API_KEY; @@ -50,6 +73,11 @@ export const auth = betterAuth({ if (!apiKey) { console.log(`Magic Link for ${email}: ${url}`); + devMagicLinks.push({ + email, + url, + createdAt: new Date().toISOString(), + }); return; } @@ -91,10 +119,10 @@ export const auth = betterAuth({ // domain: '.codebar.io', // }, // }, - // trustedOrigins: [ - // 'https://codebar.io', - // 'https://stats.codebar.io', - // 'https://calendar.codebar.io', - // 'https://jobs.codebar.io' - // ], + trustedOrigins: [ + appConfig.base_url, + ...(appConfig.base_url.includes("localhost") + ? ["http://127.0.0.1:3001"] + : []), + ], }); diff --git a/src/config.js b/src/config.js index b554f9b..3528468 100644 --- a/src/config.js +++ b/src/config.js @@ -1,9 +1,17 @@ +export const AUTH_DEFAULT_PORT = 3001; +export const PLANNER_DEFAULT_PORT = 3000; + const config = { - port: process.env.PORT || 3000, - base_url: process.env.CODEBAR_AUTH_URL || "http://localhost:3000", + port: process.env.PORT || AUTH_DEFAULT_PORT, + base_url: + process.env.CODEBAR_AUTH_URL || `http://localhost:${AUTH_DEFAULT_PORT}`, database_url: process.env.DATABASE_URL || "postgres://auth:auth@localhost:5433/test", - allowed_redirects: ["http://localhost:3000/demo"], + planner_url: + process.env.PLANNER_URL || `http://localhost:${PLANNER_DEFAULT_PORT}`, + allowed_redirects: [ + `http://localhost:${PLANNER_DEFAULT_PORT}/auth/codebar/callback`, + ], social: { github: { id: process.env.GITHUB_CLIENT_ID, diff --git a/src/dev/magic-links.js b/src/dev/magic-links.js new file mode 100644 index 0000000..8c77d4f --- /dev/null +++ b/src/dev/magic-links.js @@ -0,0 +1,2 @@ +// Dev-only store for captured magic links (used by Playwright tests) +export const devMagicLinks = []; diff --git a/src/index.js b/src/index.js index 4c6545f..4c94bc8 100644 --- a/src/index.js +++ b/src/index.js @@ -3,6 +3,16 @@ import { createApp } from "./app/app.js"; import appConfig from "./config.js"; import { auth, db } from "./auth.js"; +const githubId = appConfig.social.github.id; +const githubSecret = appConfig.social.github.secret; +if (!githubId || !githubSecret) { + console.error("GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET must be set."); + console.error( + "Copy .envrc-dist to .envrc, fill in your values, and run: source .envrc", + ); + process.exit(1); +} + const app = createApp(auth); const server = serve( diff --git a/test/features/oauth-provider.test.js b/test/features/oauth-provider.test.js new file mode 100644 index 0000000..99740da --- /dev/null +++ b/test/features/oauth-provider.test.js @@ -0,0 +1,267 @@ +import { test } from "tap"; +import { getTestInstance } from "../helpers/test-instance.js"; +import { createApp } from "../../src/app/app.js"; + +test("OAuth Provider plugin tests", async (t) => { + t.test("planner client exists after test setup", async (t) => { + const testInstance = await getTestInstance(t); + const db = testInstance.db; + + const result = await db.query( + 'SELECT * FROM "oauthClient" WHERE "clientId" = $1', + ["planner"], + ); + const client = result.rows[0]; + + t.ok(client, "planner client exists"); + t.equal(client.clientId, "planner", "clientId is planner"); + t.equal(client.public, true, "client is public"); + t.equal(client.requirePKCE, true, "PKCE is required"); + t.equal( + client.tokenEndpointAuthMethod, + "none", + "no client secret required", + ); + t.ok( + client.redirectUris.includes( + "http://localhost:3000/auth/codebar/callback", + ), + "has planner redirect URI", + ); + }); + + t.test( + "authorize endpoint redirects unauthenticated users to login", + async (t) => { + const testInstance = await getTestInstance(); + const app = createApp(testInstance.auth, testInstance.db); + + const params = new URLSearchParams({ + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + response_type: "code", + state: "test-state", + scope: "openid profile", + code_challenge: "test-challenge", + code_challenge_method: "S256", + }); + + const res = await app.request( + `/api/auth/oauth2/authorize?${params.toString()}`, + ); + + t.equal(res.status, 302, "redirects"); + const location = res.headers.get("location"); + t.match(location, /\/login/, "redirects to login page"); + // The OAuth provider signs the authorize params and appends them to the login URL + t.match(location, /response_type=code/, "preserves response_type"); + t.match(location, /client_id=planner/, "preserves client_id"); + }, + ); + + t.test("authorize endpoint issues code when authenticated", async (t) => { + const testInstance = await getTestInstance(); + const app = createApp(testInstance.auth, testInstance.db); + const { getAuthHeaders } = testInstance; + + const headers = await getAuthHeaders("oauthuser@example.com"); + + const params = new URLSearchParams({ + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + response_type: "code", + state: "auth-state", + scope: "openid profile", + code_challenge: "MJk6-W6P2z_PgOvWcEvbyqeIyc-GthZov8-QX37r0Vo", + code_challenge_method: "S256", + }); + + const res = await app.request( + `/api/auth/oauth2/authorize?${params.toString()}`, + { headers }, + ); + + t.equal(res.status, 302, "redirects to planner callback"); + const location = res.headers.get("location"); + t.ok(location, "has redirect location"); + t.match( + location, + /http:\/\/localhost:3000\/auth\/codebar\/callback/, + "redirects to planner callback", + ); + t.match(location, /code=/, "includes authorization code"); + t.match(location, /state=auth-state/, "preserves state"); + }); + + t.test( + "token endpoint exchanges code for access token with PKCE", + async (t) => { + const testInstance = await getTestInstance(); + const app = createApp(testInstance.auth, testInstance.db); + const { getAuthHeaders } = testInstance; + + // Step 1: Authorize to get a code + const headers = await getAuthHeaders("tokenuser@example.com"); + const codeVerifier = "test-verifier-123456789"; + const codeChallenge = "MJk6-W6P2z_PgOvWcEvbyqeIyc-GthZov8-QX37r0Vo"; + + const authParams = new URLSearchParams({ + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + response_type: "code", + state: "token-state", + scope: "openid profile", + code_challenge: codeChallenge, + code_challenge_method: "S256", + }); + + const authRes = await app.request( + `/api/auth/oauth2/authorize?${authParams.toString()}`, + { headers }, + ); + t.equal(authRes.status, 302, "authorize returns redirect"); + + const location = authRes.headers.get("location"); + const codeMatch = location.match(/code=([^&]+)/); + t.ok(codeMatch, "authorization response contains code"); + const code = decodeURIComponent(codeMatch[1]); + + // Step 2: Exchange code for tokens + const tokenRes = await app.request("/api/auth/oauth2/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + code_verifier: codeVerifier, + }).toString(), + }); + + t.equal(tokenRes.status, 200, "token endpoint returns 200"); + const body = await tokenRes.json(); + t.ok(body.access_token, "response contains access_token"); + t.equal(body.token_type, "Bearer", "token_type is Bearer"); + t.ok(body.expires_in, "response contains expires_in"); + }, + ); + + t.test("token endpoint rejects code without code_verifier", async (t) => { + const testInstance = await getTestInstance(); + const app = createApp(testInstance.auth, testInstance.db); + const { getAuthHeaders } = testInstance; + + // Step 1: Authorize to get a code + const headers = await getAuthHeaders("nopkce@example.com"); + const codeChallenge = "MJk6-W6P2z_PgOvWcEvbyqeIyc-GthZov8-QX37r0Vo"; + + const authParams = new URLSearchParams({ + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + response_type: "code", + state: "nopkce-state", + scope: "openid profile", + code_challenge: codeChallenge, + code_challenge_method: "S256", + }); + + const authRes = await app.request( + `/api/auth/oauth2/authorize?${authParams.toString()}`, + { headers }, + ); + const location = authRes.headers.get("location"); + const codeMatch = location.match(/code=([^&]+)/); + const code = decodeURIComponent(codeMatch[1]); + + // Step 2: Exchange without code_verifier + const tokenRes = await app.request("/api/auth/oauth2/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + // code_verifier intentionally omitted + }).toString(), + }); + + t.equal(tokenRes.status, 400, "token endpoint returns 400 without PKCE"); + const body = await tokenRes.json(); + t.ok(body.error, "response contains error"); + }); + + t.test("token endpoint rejects invalid code", async (t) => { + const testInstance = await getTestInstance(); + const app = createApp(testInstance.auth, testInstance.db); + + const tokenRes = await app.request("/api/auth/oauth2/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code: "invalid-code-123", + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + code_verifier: "test-verifier-123456789", + }).toString(), + }); + + t.ok( + tokenRes.status >= 400, + "token endpoint returns error status for invalid code", + ); + const body = await tokenRes.json(); + t.ok(body.error || body.code, "response contains error"); + }); + + t.test("token endpoint rejects mismatched redirect_uri", async (t) => { + const testInstance = await getTestInstance(); + const app = createApp(testInstance.auth, testInstance.db); + const { getAuthHeaders } = testInstance; + + // Step 1: Authorize to get a code + const headers = await getAuthHeaders("badredirect@example.com"); + const codeChallenge = "MJk6-W6P2z_PgOvWcEvbyqeIyc-GthZov8-QX37r0Vo"; + + const authParams = new URLSearchParams({ + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + response_type: "code", + state: "badredirect-state", + scope: "openid profile", + code_challenge: codeChallenge, + code_challenge_method: "S256", + }); + + const authRes = await app.request( + `/api/auth/oauth2/authorize?${authParams.toString()}`, + { headers }, + ); + const location = authRes.headers.get("location"); + const codeMatch = location.match(/code=([^&]+)/); + const code = decodeURIComponent(codeMatch[1]); + + // Step 2: Exchange with different redirect_uri + const tokenRes = await app.request("/api/auth/oauth2/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + client_id: "planner", + redirect_uri: "http://127.0.0.1:9999/callback", + code_verifier: "test-verifier-123456789", + }).toString(), + }); + + t.equal( + tokenRes.status, + 400, + "token endpoint returns 400 for mismatched redirect_uri", + ); + const body = await tokenRes.json(); + t.ok(body.error || body.code, "response contains error"); + }); +}); diff --git a/test/helpers/test-instance.js b/test/helpers/test-instance.js index eb632c3..d1059af 100644 --- a/test/helpers/test-instance.js +++ b/test/helpers/test-instance.js @@ -1,7 +1,10 @@ import pg from "pg"; import { betterAuth } from "better-auth"; -import { admin, magicLink } from "better-auth/plugins"; +import { admin, magicLink, jwt } from "better-auth/plugins"; +import { oauthProvider } from "@better-auth/oauth-provider"; import { getMigrations } from "better-auth/db/migration"; +import { seedPlannerClient } from "../../src/app/db/seed-client.js"; +import { AUTH_DEFAULT_PORT, PLANNER_DEFAULT_PORT } from "../../src/config.js"; /** * Parse DATABASE_URL into a pool config, falling back to defaults on failure @@ -57,7 +60,7 @@ export async function getTestInstance(t) { // Configure Better Auth with the pool as database const auth = betterAuth({ database: pool, - baseURL: "http://localhost:3000", + baseURL: `http://localhost:${AUTH_DEFAULT_PORT}`, logger: { disabled: true, }, @@ -75,6 +78,24 @@ export async function getTestInstance(t) { updateAge: 60 * 60 * 24, // 1 day }, plugins: [ + jwt({ + jwt: { + issuer: `http://localhost:${AUTH_DEFAULT_PORT}`, + audience: "planner", + expirationTime: "15m", + definePayload: (session) => ({ + email: session.user.email, + name: session.user.name, + }), + }, + }), + oauthProvider({ + loginPage: "/login", + scopes: ["openid", "profile"], + accessTokenExpiresIn: 900, + validAudiences: ["planner"], + allowDynamicClientRegistration: false, + }), magicLink({ sendMagicLink: async ({ email, token, url }) => { magicLinksStore.push({ email, token, url }); @@ -88,6 +109,13 @@ export async function getTestInstance(t) { const { runMigrations } = await getMigrations(auth.options); await runMigrations(); + // Seed the planner OAuth client for testing + await seedPlannerClient( + pool, + `http://localhost:${PLANNER_DEFAULT_PORT}/auth/codebar/callback`, + schemaName, + ); + // Create helpers const getAuthHeaders = createGetAuthHeaders(auth, magicLinksStore); const client = createTestClient(pool); @@ -102,6 +130,7 @@ export async function getTestInstance(t) { return { auth, + db: pool, pool, client, getAuthHeaders, @@ -205,6 +234,8 @@ function extractCookieFromError(error) { return null; } + // ponytail: Better Auth returns headers in two shapes (Headers object vs + // array) depending on the error path. Handle both until the API stabilises. let setCookie; if (error.headers && typeof error.headers.get === "function") { setCookie = error.headers.get("set-cookie"); diff --git a/test/integration/oauth-flow.test.js b/test/integration/oauth-flow.test.js new file mode 100644 index 0000000..a050d99 --- /dev/null +++ b/test/integration/oauth-flow.test.js @@ -0,0 +1,104 @@ +import { test } from "tap"; +import { getTestInstance } from "../helpers/test-instance.js"; +import { createApp } from "../../src/app/app.js"; + +test("end-to-end OAuth 2.1 flow", async (t) => { + const testInstance = await getTestInstance(); + const app = createApp(testInstance.auth, testInstance.db); + const { getAuthHeaders } = testInstance; + + // Step 1: Authenticate a user via magic link + const email = "oauth-integration@example.com"; + const { cookie: sessionCookie } = await getAuthHeaders(email); + t.ok(sessionCookie, "session token extracted"); + + // Step 2: Call authorize endpoint with PKCE + const codeVerifier = "test-verifier-123456789"; + const codeChallenge = "MJk6-W6P2z_PgOvWcEvbyqeIyc-GthZov8-QX37r0Vo"; + + const authorizeParams = new URLSearchParams({ + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + response_type: "code", + state: "integration-state", + scope: "openid profile", + code_challenge: codeChallenge, + code_challenge_method: "S256", + }); + + const authorizeRes = await app.request( + `/api/auth/oauth2/authorize?${authorizeParams.toString()}`, + { headers: { cookie: sessionCookie } }, + ); + + t.equal(authorizeRes.status, 302, "authorize returns redirect"); + + const location = authorizeRes.headers.get("location"); + t.ok(location, "has redirect location"); + t.match( + location, + /http:\/\/localhost:3000\/auth\/codebar\/callback/, + "redirects to planner callback", + ); + t.match(location, /code=/, "includes authorization code"); + t.match(location, /state=integration-state/, "preserves state"); + + const codeMatch = location.match(/code=([^&]+)/); + t.ok(codeMatch, "authorization code found in redirect"); + const code = decodeURIComponent(codeMatch[1]); + + // Step 3: Exchange code for tokens + const tokenRes = await app.request("/api/auth/oauth2/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + client_id: "planner", + redirect_uri: "http://localhost:3000/auth/codebar/callback", + code_verifier: codeVerifier, + }).toString(), + }); + + t.equal(tokenRes.status, 200, "token endpoint returns 200"); + + const tokens = await tokenRes.json(); + t.ok(tokens.access_token, "response contains access_token"); + t.equal(tokens.token_type, "Bearer", "token_type is Bearer"); + t.ok(tokens.expires_in, "response contains expires_in"); + t.ok(tokens.id_token, "response contains id_token"); + + // Step 4: Verify id_token via JWKS + const jwksRes = await app.request("/api/auth/jwks"); + t.equal(jwksRes.status, 200, "JWKS endpoint returns 200"); + + const jwks = await jwksRes.json(); + t.ok(jwks.keys, "JWKS contains keys array"); + t.ok(jwks.keys.length > 0, "JWKS has at least one key"); + + // Decode header to find kid + const idToken = tokens.id_token; + const headerJson = Buffer.from(idToken.split(".")[0], "base64url").toString(); + const header = JSON.parse(headerJson); + t.ok(header.kid, "id_token has kid in header"); + + const keyData = jwks.keys.find((k) => k.kid === header.kid); + t.ok(keyData, "JWKS contains matching key"); + + // Verify the JWT structure (signature verification would need jose or similar) + const payloadJson = Buffer.from( + idToken.split(".")[1], + "base64url", + ).toString(); + const payload = JSON.parse(payloadJson); + + t.ok(payload.sub, "payload has sub claim"); + t.ok(payload.iss, "payload has issuer"); + t.ok(payload.aud, "payload has audience"); + t.ok(payload.iat, "payload has issued-at"); + t.ok(payload.exp, "payload has expiration"); + + // Step 5: Verify the access token is usable (e.g., for userinfo if we had one) + // Note: introspection requires client authentication, which is skipped here + // since the core flow (authorize -> code -> token -> JWT) is fully validated. +});