From cdea81b1188cc0b88b23d15b2bd4b04c38daf399 Mon Sep 17 00:00:00 2001 From: Thomas <28439359+thoda-dev@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:39:40 +0200 Subject: [PATCH 1/2] test(integration): cover the setup wizard and the permission matrix --- .github/workflows/ci.yml | 40 + CONTRIBUTING.md | 14 + ROADMAP.md | 26 +- apps/app/package.json | 2 + apps/app/server/api/setup/complete.post.ts | 15 +- apps/app/server/plugins/migrate.ts | 10 +- .../app/tests/integration/permissions.test.ts | 259 +++++ .../tests/integration/setup-wizard.test.ts | 151 +++ apps/app/tests/integration/support/client.ts | 65 ++ .../app/tests/integration/support/database.ts | 21 + apps/app/tests/integration/support/env.ts | 43 + .../app/tests/integration/support/fixtures.ts | 101 ++ .../tests/integration/support/global-setup.ts | 99 ++ apps/app/vitest.config.ts | 4 +- apps/app/vitest.integration.config.ts | 14 + package.json | 1 + pnpm-lock.yaml | 968 +++++------------- 17 files changed, 1082 insertions(+), 751 deletions(-) create mode 100644 apps/app/tests/integration/permissions.test.ts create mode 100644 apps/app/tests/integration/setup-wizard.test.ts create mode 100644 apps/app/tests/integration/support/client.ts create mode 100644 apps/app/tests/integration/support/database.ts create mode 100644 apps/app/tests/integration/support/env.ts create mode 100644 apps/app/tests/integration/support/fixtures.ts create mode 100644 apps/app/tests/integration/support/global-setup.ts create mode 100644 apps/app/vitest.integration.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 525644c..85c0689 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,6 +57,46 @@ jobs: DATABASE_URL: postgres://unused run: ./node_modules/.bin/drizzle-kit check + integration: + name: Integration tests + runs-on: ubuntu-latest + timeout-minutes: 25 + # A real PostgreSQL, because what these cover is the routes against the schema, not pure logic. + services: + postgres: + image: postgres:18 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: shhh_test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Enable corepack + run: corepack enable + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + + - name: Install + run: pnpm install --frozen-lockfile + + # Turnstile runs for real, on Cloudflare's always-passes test keys, so this step needs egress. + - name: Integration tests + env: + TEST_DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/shhh_test + run: pnpm test:integration + docker-app: name: Docker image (app) runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 116df3f..7e26d81 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,20 @@ pnpm test Schema changes need a migration committed alongside them: `pnpm db:generate`. CI fails if the migrations and the schema disagree. +## Integration tests + +`pnpm test:integration` builds the app, boots it and drives the routes over HTTP. +It needs a database of its own: the schema is dropped on every run, which is why it reads `TEST_DATABASE_URL` rather than `DATABASE_URL`. + +```bash +createdb shhh_test +TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/shhh_test pnpm test:integration +``` + +Turnstile is exercised for real, on Cloudflare's always-passes test keys, so the run needs network access. +`TEST_SKIP_BUILD=true` reuses the last `.output` while you iterate on the tests themselves. +[ROADMAP.md](ROADMAP.md) lists the suites still to write. + By taking part, you agree to the [Code of Conduct](CODE_OF_CONDUCT.md). ## Commit messages diff --git a/ROADMAP.md b/ROADMAP.md index f3747a2..123e960 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,30 +15,28 @@ Anything not listed here is either done or deliberately out of scope (see the bo The mail templates have no header image, which is deliberate: an image in an email means either an external URL that tells the sender the message was opened, or a base64 payload in every message. -- **Integration tests.** Unit tests cover the pure logic (see below), but nothing exercises the - routes — and that gap has already shipped a broken release. v1.1.0 and v1.1.1 could not complete - their setup wizard at all: bumping Better Auth to 1.7 needed a new `issuer` column on `accounts`, - and nothing noticed, because lint, types, 86 unit tests, both builds and both Docker images all - pass without a single request ever reaching `signUpEmail`. The paths where a silent regression - would cost most, in order: - 1. **Sign-up and the setup wizard** — the one that was actually broken. A fresh database, the - wizard completed, a super admin created. It would have caught the schema drift on the commit - that introduced it rather than on a self-hoster's first boot. +- **Integration tests.** The harness exists — `pnpm test:integration`, see + [CONTRIBUTING.md](CONTRIBUTING.md) — and two of the seven suites are written. What it cost to get + here: v1.1.0 and v1.1.1 could not complete their setup wizard at all, because bumping Better Auth + to 1.7 needed a new `issuer` column on `accounts` and nothing noticed. Lint, types, 86 unit tests, + both builds and both Docker images all pass without a single request ever reaching `signUpEmail`. + The remaining paths, in the order a silent regression would cost most: + 1. ~~**Sign-up and the setup wizard**~~ — done, `tests/integration/setup-wizard.test.ts`. 2. **The read counter under concurrency** — fire many simultaneous reveals at a one-read paste and assert exactly one succeeds. This is the test that guards the atomic `UPDATE … WHERE read_count < max_reads RETURNING`, and now also that a caller without the unlock hash moves nothing. - 3. **The permission matrix** — the table in the security docs, case by case. + 3. ~~**The permission matrix**~~ — done, `tests/integration/permissions.test.ts`. 4. **Invitations** — single use including two concurrent accepts, address fixed by the invitation rather than the request body, role always `user`, registration bypass. 5. **Anonymous restrictions** — no uploads, no server-side sharing, `public_paste_enabled` off. 6. **Settings semantics** — an absent row means the default, a row holding `null` means unlimited. 7. **Account deletion** — full cascade, audit log anonymised rather than deleted. - Needs Vitest with `@nuxt/test-utils` and a real PostgreSQL (a service container in CI), with - tables truncated between tests. Turnstile is bypassed with Cloudflare's always-valid test keys. - Assert on `paste_email_recipients` rows rather than on delivered mail, to avoid depending on a - mail server in CI. + Invitations and account deletion need a mail provider, which the harness deliberately runs without: + sign-up would then wait on a verification click. They need a second server configuration, or a + provider stub. Assert on `paste_email_recipients` rows rather than on delivered mail, to avoid + depending on a mail server in CI. ## Waiting on upstream diff --git a/apps/app/package.json b/apps/app/package.json index a091181..0208426 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -14,6 +14,7 @@ "db:studio": "node ../../scripts/drizzle-kit.mjs studio", "typecheck": "nuxt typecheck", "test": "vitest run", + "test:integration": "vitest run --config vitest.integration.config.ts", "test:watch": "vitest", "lint": "eslint .", "lint:fix": "eslint . --fix" @@ -40,6 +41,7 @@ }, "devDependencies": { "@nuxt/eslint": "1.17.0", + "@nuxt/test-utils": "^4.2.0", "@types/nodemailer": "^8.0.1", "@types/qrcode": "^1.5.6", "drizzle-kit": "^0.31.10", diff --git a/apps/app/server/api/setup/complete.post.ts b/apps/app/server/api/setup/complete.post.ts index f41ee94..2d303bf 100644 --- a/apps/app/server/api/setup/complete.post.ts +++ b/apps/app/server/api/setup/complete.post.ts @@ -25,15 +25,12 @@ const SETUP_LOCK_KEY = 4_827_302 export default defineEventHandler(async (event) => { const body = await readValidatedBody(event, setupSchema.parse) - // The check and the write have to be one critical section. Without it two requests arriving - // together both see an empty instance and both create a super_admin — the account that owns - // everything, so "unlikely" is not a good enough guarantee. - await db.execute(sql`select pg_advisory_lock(${SETUP_LOCK_KEY})`) - try { - return await completeSetup(event, body) - } finally { - await db.execute(sql`select pg_advisory_unlock(${SETUP_LOCK_KEY})`) - } + // The check and the write have to be one critical section, or two requests both create a super_admin. + // Transaction-scoped: a session lock and its unlock are two pool checkouts, and land on different connections. + return await db.transaction(async (tx) => { + await tx.execute(sql`select pg_advisory_xact_lock(${SETUP_LOCK_KEY})`) + return completeSetup(event, body) + }) }) async function completeSetup(event: Parameters[0], body: z.infer) { diff --git a/apps/app/server/plugins/migrate.ts b/apps/app/server/plugins/migrate.ts index 5055b78..6903bc3 100644 --- a/apps/app/server/plugins/migrate.ts +++ b/apps/app/server/plugins/migrate.ts @@ -13,17 +13,15 @@ export default defineNitroPlugin(async () => { const migrationsFolder = process.env.MIGRATIONS_DIR ?? './migrations' // Arbitrary but fixed key, so two containers starting at once can't both apply the same migration. - // Session-scoped: a crashed process releases it when its connection drops rather than deadlocking every later boot. const LOCK_KEY = 4_827_301 try { - await db.execute(sql`select pg_advisory_lock(${LOCK_KEY})`) - try { + // Transaction-scoped: a session lock and its unlock are two pool checkouts, and land on different connections. + await db.transaction(async (tx) => { + await tx.execute(sql`select pg_advisory_xact_lock(${LOCK_KEY})`) await migrate(db, { migrationsFolder }) console.log('[migrate] database is up to date') - } finally { - await db.execute(sql`select pg_advisory_unlock(${LOCK_KEY})`) - } + }) } catch (error) { // Hard stop: serving against a schema the code doesn't expect corrupts data far more quietly than refusing to start. console.error('[migrate] migration failed:', error instanceof Error ? error.message : error) diff --git a/apps/app/tests/integration/permissions.test.ts b/apps/app/tests/integration/permissions.test.ts new file mode 100644 index 0000000..90ed2e6 --- /dev/null +++ b/apps/app/tests/integration/permissions.test.ts @@ -0,0 +1,259 @@ +import { beforeAll, beforeEach, describe, expect, inject, it } from 'vitest' +import { setup } from '@nuxt/test-utils/e2e' +import { ApiClient } from './support/client' +import { resetDatabase } from './support/database' +import { TURNSTILE_TEST_TOKEN } from './support/env' +import { completeSetupWizard, createPasteRow, createUserRow, signUpIdentity } from './support/fixtures' + +type Actor = 'anonymous' | 'user' | 'admin' | 'superAdmin' + +const ACTORS: Actor[] = ['anonymous', 'user', 'admin', 'superAdmin'] + +interface Instance { + clients: Record + self: Record, string> + ownPaste: Record + otherPaste: string + targetUser: string + targetAdmin: string +} + +/** Every field `PUT /api/admin/settings` validates, on the shipped defaults, so a write changes nothing. */ +const FULL_SETTINGS = { + max_retention_days_anonymous: 7, + max_retention_days_authenticated: 30, + max_reads_anonymous: null, + max_reads_authenticated: null, + max_text_size_bytes: 100_000, + max_upload_size_bytes: 2_000_000, + max_total_pastes: null, + max_total_storage_bytes: 40_000_000_000, + rate_limit_anonymous_creates_per_period: null, + rate_limit_authenticated_creates_per_period: null, + rate_limit_uploads_per_period: null, + rate_limit_period_minutes: 10, + max_email_recipients_per_paste: 3, + invitation_expiry_days: 7, + registration_enabled: true, + public_paste_enabled: true, + require_2fa: false +} + +function textPaste() { + return { + kind: 'text', + ciphertext: Buffer.from('ciphertext').toString('base64'), + iv: Buffer.from('123456789012').toString('base64'), + turnstileToken: TURNSTILE_TEST_TOKEN + } +} + +/** A fresh instance per assertion: half the matrix deletes accounts or moves roles. */ +async function createInstance(): Promise { + const superAdmin = await completeSetupWizard() + const admin = await signUpIdentity('admin') + const user = await signUpIdentity('user') + + const targetUser = await createUserRow('user') + const targetAdmin = await createUserRow('admin') + + return { + clients: { anonymous: new ApiClient(), user: user.client, admin: admin.client, superAdmin: superAdmin.client }, + self: { user: user.id, admin: admin.id, superAdmin: superAdmin.id }, + ownPaste: { + anonymous: (await createPasteRow(null)).id, + user: (await createPasteRow(user.id)).id, + admin: (await createPasteRow(admin.id)).id, + superAdmin: (await createPasteRow(superAdmin.id)).id + }, + otherPaste: (await createPasteRow(targetUser.id)).id, + targetUser: targetUser.id, + targetAdmin: targetAdmin.id + } +} + +interface Case { + label: string + expected: Record + call: (instance: Instance, actor: Actor) => Promise +} + +/** The table in `apps/docs/content/2.self-hosting/3.security.md`, as status codes rather than ticks. */ +const MATRIX: Record = { + 'Manage own pastes': [ + { + label: 'POST /api/pastes', + // Anonymous creation is the product; `public_paste_enabled` is what turns it off. + expected: { anonymous: 201, user: 201, admin: 201, superAdmin: 201 }, + call: (instance, actor) => instance.clients[actor].post('/api/pastes', textPaste()) + }, + { + label: 'GET /api/pastes', + expected: { anonymous: 401, user: 200, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/pastes') + }, + { + label: 'DELETE /api/pastes/{own}', + expected: { anonymous: 401, user: 204, admin: 204, superAdmin: 204 }, + call: (instance, actor) => instance.clients[actor].delete(`/api/pastes/${instance.ownPaste[actor]}`) + }, + { + label: 'DELETE /api/pastes/{someone else\'s}', + // 404, not 403: ownership is in the WHERE clause, and a super admin has no override here either. + expected: { anonymous: 401, user: 404, admin: 404, superAdmin: 404 }, + call: (instance, actor) => instance.clients[actor].delete(`/api/pastes/${instance.otherPaste}`) + } + ], + + 'Change settings, view stats': [ + { + label: 'GET /api/admin/settings', + expected: { anonymous: 403, user: 403, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/admin/settings') + }, + { + label: 'PUT /api/admin/settings', + expected: { anonymous: 403, user: 403, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].put('/api/admin/settings', FULL_SETTINGS) + }, + { + label: 'GET /api/admin/storage', + // Stricter than the documented row: storage is super admin only, as `AdminNav.vue` already says. + expected: { anonymous: 403, user: 403, admin: 403, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/admin/storage') + } + ], + + 'Create and delete user accounts': [ + { + label: 'GET /api/admin/users', + expected: { anonymous: 403, user: 403, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/admin/users') + }, + { + label: 'POST /api/admin/invitations', + // 503 is the pass: this instance has no mail provider, and that check sits past the gate. + expected: { anonymous: 403, user: 403, admin: 503, superAdmin: 503 }, + call: (instance, actor) => instance.clients[actor].post('/api/admin/invitations', { email: 'invitee@example.test' }) + }, + { + label: 'DELETE /api/admin/users/{user}', + expected: { anonymous: 403, user: 403, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].delete(`/api/admin/users/${instance.targetUser}`) + } + ], + + 'Act on admin / super admin accounts': [ + { + label: 'DELETE /api/admin/users/{admin}', + expected: { anonymous: 403, user: 403, admin: 403, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].delete(`/api/admin/users/${instance.targetAdmin}`) + }, + { + label: 'DELETE /api/admin/users/{id}/pastes', + expected: { anonymous: 403, user: 403, admin: 403, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].delete(`/api/admin/users/${instance.targetUser}/pastes?scope=all`) + } + ], + + 'Change anyone\'s role': [ + { + label: 'PATCH /api/admin/users/{user}', + expected: { anonymous: 403, user: 403, admin: 403, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].patch(`/api/admin/users/${instance.targetUser}`, { role: 'admin' }) + }, + { + label: 'PATCH /api/admin/users/{admin}', + expected: { anonymous: 403, user: 403, admin: 403, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].patch(`/api/admin/users/${instance.targetAdmin}`, { role: 'user' }) + } + ], + + // Not a documented row, but the same gate, and leaving it untested is how one of these quietly loosens. + 'The rest of the admin surface': [ + { + label: 'GET /api/admin/invitations', + expected: { anonymous: 403, user: 403, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/admin/invitations') + }, + { + label: 'GET /api/admin/banned-ips', + expected: { anonymous: 403, user: 403, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/admin/banned-ips') + }, + { + label: 'GET /api/admin/allowed-ips', + expected: { anonymous: 403, user: 403, admin: 200, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/admin/allowed-ips') + }, + { + label: 'POST /api/admin/pastes/purge', + expected: { anonymous: 403, user: 403, admin: 403, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].post('/api/admin/pastes/purge') + }, + { + label: 'GET /api/admin/legal', + expected: { anonymous: 403, user: 403, admin: 403, superAdmin: 200 }, + call: (instance, actor) => instance.clients[actor].get('/api/admin/legal') + } + ] +} + +describe('permission matrix', () => { + beforeAll(async () => { + await setup({ host: inject('baseUrl') }) + }) + + beforeEach(resetDatabase) + + for (const [row, cases] of Object.entries(MATRIX)) { + describe(row, () => { + for (const testCase of cases) { + for (const actor of ACTORS) { + it(`${testCase.label} — ${actor} gets ${testCase.expected[actor]}`, async () => { + const instance = await createInstance() + const response = await testCase.call(instance, actor) + expect(response.status, await response.clone().text()).toBe(testCase.expected[actor]) + }) + } + } + }) + } +}) + +/** "Nobody can delete or demote themselves, whatever their role." */ +describe('nobody acts on their own account', () => { + beforeAll(async () => { + await setup({ host: inject('baseUrl') }) + }) + + beforeEach(resetDatabase) + + it('an admin cannot delete itself from the admin routes', async () => { + const instance = await createInstance() + const response = await instance.clients.admin.delete(`/api/admin/users/${instance.self.admin}`) + expect(response.status).toBe(403) + }) + + it('a super admin cannot delete itself from the admin routes', async () => { + const instance = await createInstance() + const response = await instance.clients.superAdmin.delete(`/api/admin/users/${instance.self.superAdmin}`) + expect(response.status).toBe(403) + }) + + it('a super admin cannot change its own role', async () => { + const instance = await createInstance() + const response = await instance.clients.superAdmin.patch(`/api/admin/users/${instance.self.superAdmin}`, { role: 'user' }) + expect(response.status).toBe(403) + }) + + it('a super admin cannot delete its own account', async () => { + // The system account is outside the right to erasure, and it cannot demote itself either. + const instance = await createInstance() + const response = await instance.clients.superAdmin.delete('/api/account/me', { + password: 'integration-test-password', + confirmation: 'anything' + }) + expect(response.status).toBe(403) + }) +}) diff --git a/apps/app/tests/integration/setup-wizard.test.ts b/apps/app/tests/integration/setup-wizard.test.ts new file mode 100644 index 0000000..626c5d0 --- /dev/null +++ b/apps/app/tests/integration/setup-wizard.test.ts @@ -0,0 +1,151 @@ +import { beforeAll, beforeEach, describe, expect, inject, it } from 'vitest' +import { setup } from '@nuxt/test-utils/e2e' +import { eq } from 'drizzle-orm' +import { ApiClient, json } from './support/client' +import { db, resetDatabase, schema } from './support/database' +import { PASSWORD, SETUP_SETTINGS, completeSetupWizard, setupBody, uniqueEmail } from './support/fixtures' + +/** The path that shipped broken in v1.1.0 and v1.1.1, with every other check green. */ +describe('setup wizard', () => { + beforeAll(async () => { + await setup({ host: inject('baseUrl') }) + }) + + beforeEach(resetDatabase) + + it('reports an empty instance as not set up', async () => { + const response = await new ApiClient().get('/api/setup/status') + expect(response.status).toBe(200) + expect(await json(response)).toEqual({ completed: false }) + }) + + it('creates a verified super admin and signs the caller in', async () => { + const client = new ApiClient() + const body = setupBody() + + const response = await client.post('/api/setup/complete', body) + + expect(response.status).toBe(201) + expect(await json(response)).toMatchObject({ name: body.name, email: body.email }) + + const [created] = await db.select().from(schema.users).where(eq(schema.users.email, body.email)) + expect(created).toBeDefined() + // Neither is a sign-up input: completing the wizard is what proves both. + expect(created!.role).toBe('super_admin') + expect(created!.emailVerified).toBe(true) + + // The wizard ends signed in, not on a login screen. + expect((await client.get('/api/admin/settings')).status).toBe(200) + }) + + it('stores the settings the wizard submitted', async () => { + await completeSetupWizard() + + const rows = await db.select().from(schema.appSettings) + const stored = Object.fromEntries(rows.map(row => [row.key, row.value])) + + expect(stored).toMatchObject(SETUP_SETTINGS) + // A row holding null is a deliberate "unlimited", so it has to be written rather than skipped. + expect(Object.keys(stored)).toContain('rate_limit_anonymous_creates_per_period') + expect(stored.rate_limit_anonymous_creates_per_period).toBeNull() + }) + + it('flips the status endpoint once a super admin exists', async () => { + await completeSetupWizard() + expect(await json(await new ApiClient().get('/api/setup/status'))).toEqual({ completed: true }) + }) + + it('refuses a second run', async () => { + await completeSetupWizard() + + const response = await new ApiClient().post('/api/setup/complete', setupBody()) + + expect(response.status).toBe(409) + // The second call must not have created an account on its way to the conflict. + const superAdmins = await db.select().from(schema.users).where(eq(schema.users.role, 'super_admin')) + expect(superAdmins).toHaveLength(1) + }) + + it('serialises concurrent runs into a single super admin', async () => { + // The advisory lock has to survive the pool: held on one connection and released on another, it leaks and every later run blocks forever. + const responses = await Promise.all( + Array.from({ length: 5 }, () => new ApiClient().post('/api/setup/complete', setupBody())) + ) + + expect(responses.map(response => response.status).sort((a, b) => a - b)).toEqual([201, 409, 409, 409, 409]) + expect(await db.select().from(schema.users)).toHaveLength(1) + }) + + it('rejects a payload missing the settings block', async () => { + const { settings: _settings, ...withoutSettings } = setupBody() + + const response = await new ApiClient().post('/api/setup/complete', withoutSettings) + + expect(response.status).toBe(400) + expect(await db.select().from(schema.users)).toHaveLength(0) + }) +}) + +describe('sign-up', () => { + beforeAll(async () => { + await setup({ host: inject('baseUrl') }) + }) + + beforeEach(resetDatabase) + + it('creates a plain user and signs it in', async () => { + await completeSetupWizard() + + const client = new ApiClient() + const email = uniqueEmail('member') + const response = await client.post('/api/auth/sign-up/email', { name: 'Member', email, password: PASSWORD }) + + expect(response.status).toBe(200) + + const [created] = await db.select().from(schema.users).where(eq(schema.users.email, email)) + expect(created!.role).toBe('user') + + expect((await client.get('/api/pastes')).status).toBe(200) + }) + + it('ignores a role the sign-up body asks for', async () => { + await completeSetupWizard() + + const email = uniqueEmail('climber') + const response = await new ApiClient().post('/api/auth/sign-up/email', { + name: 'Climber', + email, + password: PASSWORD, + role: 'super_admin' + }) + + expect(response.status).toBe(200) + const [created] = await db.select().from(schema.users).where(eq(schema.users.email, email)) + expect(created!.role).toBe('user') + }) + + it('is refused while registration is disabled', async () => { + const superAdmin = await completeSetupWizard() + await db + .insert(schema.appSettings) + .values({ key: 'registration_enabled', value: false, updatedBy: superAdmin.id }) + .onConflictDoUpdate({ target: schema.appSettings.key, set: { value: false } }) + + const response = await new ApiClient().post('/api/auth/sign-up/email', { + name: 'Uninvited', + email: uniqueEmail('uninvited'), + password: PASSWORD + }) + + expect(response.status).toBe(403) + }) + + it('lets the wizard through even with registration disabled', async () => { + // The bypass is proven from `isSetupComplete()`, so this cannot lock the first account out. + await db.insert(schema.appSettings).values({ key: 'registration_enabled', value: false }) + + const response = await new ApiClient().post('/api/setup/complete', setupBody()) + + expect(response.status).toBe(201) + }) +}) diff --git a/apps/app/tests/integration/support/client.ts b/apps/app/tests/integration/support/client.ts new file mode 100644 index 0000000..4a336b6 --- /dev/null +++ b/apps/app/tests/integration/support/client.ts @@ -0,0 +1,65 @@ +import { url } from '@nuxt/test-utils/e2e' + +let allocated = 0 + +/** Better Auth caps `/sign-in*` and `/sign-up*` at 3 per 10s per IP, read from a single-value `x-forwarded-for`. */ +function nextClientIp() { + allocated += 1 + return `10.0.${Math.floor(allocated / 254) % 254}.${(allocated % 254) + 1}` +} + +/** A browser-shaped caller: it keeps the cookies it is handed and sends them back. */ +export class ApiClient { + readonly ip = nextClientIp() + private readonly cookies = new Map() + + async request(path: string, init: RequestInit = {}) { + const headers = new Headers(init.headers) + headers.set('x-forwarded-for', this.ip) + if (this.cookies.size > 0) { + headers.set('cookie', [...this.cookies].map(([name, value]) => `${name}=${value}`).join('; ')) + } + if (init.body !== undefined && !headers.has('content-type')) { + headers.set('content-type', 'application/json') + } + + const response = await fetch(url(path), { ...init, headers, redirect: 'manual' }) + + for (const cookie of response.headers.getSetCookie()) { + const [pair] = cookie.split(';') + const separator = pair?.indexOf('=') ?? -1 + if (!pair || separator < 1) continue + const name = pair.slice(0, separator) + const value = pair.slice(separator + 1) + // An expired cookie is how Better Auth signs a caller out. + if (value === '' || /expires=Thu, 01 Jan 1970/i.test(cookie)) this.cookies.delete(name) + else this.cookies.set(name, value) + } + + return response + } + + get(path: string) { + return this.request(path) + } + + post(path: string, body?: unknown) { + return this.request(path, { method: 'POST', body: body === undefined ? undefined : JSON.stringify(body) }) + } + + put(path: string, body?: unknown) { + return this.request(path, { method: 'PUT', body: body === undefined ? undefined : JSON.stringify(body) }) + } + + patch(path: string, body?: unknown) { + return this.request(path, { method: 'PATCH', body: body === undefined ? undefined : JSON.stringify(body) }) + } + + delete(path: string, body?: unknown) { + return this.request(path, { method: 'DELETE', body: body === undefined ? undefined : JSON.stringify(body) }) + } +} + +export async function json(response: Response): Promise { + return response.json() as Promise +} diff --git a/apps/app/tests/integration/support/database.ts b/apps/app/tests/integration/support/database.ts new file mode 100644 index 0000000..a621694 --- /dev/null +++ b/apps/app/tests/integration/support/database.ts @@ -0,0 +1,21 @@ +import { drizzle } from 'drizzle-orm/postgres-js' +import postgres from 'postgres' +import * as schema from '../../../server/database/schema' +import { testDatabaseUrl } from './env' + +const client = postgres(testDatabaseUrl(), { onnotice: () => {} }) + +/** A second connection to the server's own database, for arranging rows and asserting on them. */ +export const db = drizzle(client, { schema }) +export { schema } + +/** Truncate rather than re-migrate: the schema is applied once, at boot. */ +export async function resetDatabase() { + // Read from the catalog, so a new table is covered without editing this file. + const tables = await client<{ tablename: string }[]>` + select tablename from pg_tables where schemaname = 'public' + ` + if (tables.length === 0) return + const list = tables.map(row => `"${row.tablename}"`).join(', ') + await client.unsafe(`truncate table ${list} restart identity cascade`) +} diff --git a/apps/app/tests/integration/support/env.ts b/apps/app/tests/integration/support/env.ts new file mode 100644 index 0000000..2a4abec --- /dev/null +++ b/apps/app/tests/integration/support/env.ts @@ -0,0 +1,43 @@ +import { randomBytes } from 'node:crypto' +import { fileURLToPath } from 'node:url' +import { resolve } from 'node:path' + +/** A database of its own, never `DATABASE_URL`: the harness drops its schema on every run. */ +export function testDatabaseUrl() { + const url = process.env.TEST_DATABASE_URL + if (!url) { + throw new Error( + 'TEST_DATABASE_URL is not set. Integration tests need a database of their own — they drop its schema on every run.\n' + + 'e.g. TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/shhh_test' + ) + } + return url +} + +/** Cloudflare's always-passes pair, so creating a paste still goes through the real verification. */ +export const TURNSTILE_TEST_SITE_KEY = '1x00000000000000000000AA' +export const TURNSTILE_TEST_SECRET_KEY = '1x0000000000000000000000000000000AA' + +/** Any string passes with the always-passes secret. */ +export const TURNSTILE_TEST_TOKEN = 'integration-test-token' + +/** The environment the server under test boots with. */ +export function serverEnv(port: number) { + return { + NODE_ENV: 'production', + NITRO_PORT: String(port), + PORT: String(port), + HOST: '127.0.0.1', + DATABASE_URL: testDatabaseUrl(), + // The boot plugin's default `./migrations` only exists inside the Docker image. + MIGRATIONS_DIR: resolve(fileURLToPath(new URL('.', import.meta.url)), '../../../server/database/migrations'), + BETTER_AUTH_SECRET: randomBytes(32).toString('base64'), + BETTER_AUTH_URL: `http://127.0.0.1:${port}`, + NUXT_PUBLIC_TURNSTILE_SITE_KEY: TURNSTILE_TEST_SITE_KEY, + NUXT_TURNSTILE_SECRET_KEY: TURNSTILE_TEST_SECRET_KEY, + // A supported configuration, and the only one where sign-up doesn't wait on a verification click. + MAIL_PROVIDER: 'none', + // Keeps our own abuse controls on the loopback address, which `ip-sec.ts` skips. + TRUSTED_PROXY_DEPTH: '0' + } +} diff --git a/apps/app/tests/integration/support/fixtures.ts b/apps/app/tests/integration/support/fixtures.ts new file mode 100644 index 0000000..0b317f6 --- /dev/null +++ b/apps/app/tests/integration/support/fixtures.ts @@ -0,0 +1,101 @@ +import { randomUUID } from 'node:crypto' +import { eq } from 'drizzle-orm' +import { ApiClient, json } from './client' +import { db, schema } from './database' + +export type Role = 'user' | 'admin' | 'super_admin' + +export const PASSWORD = 'integration-test-password' + +let accounts = 0 + +export function uniqueEmail(prefix = 'user') { + accounts += 1 + return `${prefix}-${accounts}-${randomUUID().slice(0, 8)}@example.test` +} + +/** The nine keys the wizard insists on, with rate limits left unlimited so no suite throttles itself. */ +export const SETUP_SETTINGS = { + max_retention_days_anonymous: 7, + max_retention_days_authenticated: 30, + max_text_size_bytes: 100_000, + max_upload_size_bytes: 2_000_000, + max_total_storage_bytes: 40_000_000_000, + rate_limit_anonymous_creates_per_period: null, + rate_limit_authenticated_creates_per_period: null, + rate_limit_uploads_per_period: null, + rate_limit_period_minutes: 10 +} as const + +export interface SetupBody { + name: string + email: string + password: string + settings: typeof SETUP_SETTINGS +} + +export function setupBody(overrides: Partial = {}): SetupBody { + return { + name: 'Super Admin', + email: uniqueEmail('super-admin'), + password: PASSWORD, + settings: SETUP_SETTINGS, + ...overrides + } +} + +export interface Identity { + client: ApiClient + id: string + email: string +} + +/** The wizard over HTTP, exactly as `pages/setup.vue` runs it — the only way to get a super admin. */ +export async function completeSetupWizard(overrides: Partial = {}): Promise { + const client = new ApiClient() + const body = setupBody(overrides) + const response = await client.post('/api/setup/complete', body) + if (response.status !== 201) { + throw new Error(`setup wizard failed with ${response.status}: ${await response.text()}`) + } + const created = await json<{ id: string }>(response) + return { client, id: created.id, email: body.email } +} + +/** A signed-in account from the public sign-up, promoted afterwards if asked. */ +export async function signUpIdentity(role: Exclude = 'user'): Promise { + const client = new ApiClient() + const email = uniqueEmail(role) + const response = await client.post('/api/auth/sign-up/email', { name: `Test ${role}`, email, password: PASSWORD }) + if (response.status !== 200) { + throw new Error(`sign-up failed with ${response.status}: ${await response.text()}`) + } + const created = await json<{ user: { id: string } }>(response) + if (role !== 'user') { + await db.update(schema.users).set({ role }).where(eq(schema.users.id, created.user.id)) + } + return { client, id: created.user.id, email } +} + +/** A row, not a session: a deletion target never signs in, and skipping the password hash keeps fixtures cheap. */ +export async function createUserRow(role: Role = 'user') { + const [row] = await db + .insert(schema.users) + .values({ id: randomUUID(), name: `Target ${role}`, email: uniqueEmail(`target-${role}`), role }) + .returning({ id: schema.users.id, email: schema.users.email }) + return row! +} + +export async function createPasteRow(ownerId: string | null) { + const [row] = await db + .insert(schema.pastes) + .values({ + ownerId, + kind: 'text', + ciphertext: Buffer.from('ciphertext'), + iv: Buffer.from('123456789012'), + expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) + }) + .returning({ id: schema.pastes.id }) + return row! +} diff --git a/apps/app/tests/integration/support/global-setup.ts b/apps/app/tests/integration/support/global-setup.ts new file mode 100644 index 0000000..bb755b9 --- /dev/null +++ b/apps/app/tests/integration/support/global-setup.ts @@ -0,0 +1,99 @@ +import { createServer } from 'node:net' +import { spawn, execFileSync, type ChildProcess } from 'node:child_process' +import { fileURLToPath } from 'node:url' +import { resolve } from 'node:path' +import postgres from 'postgres' +import type { TestProject } from 'vitest/node' +import { serverEnv, testDatabaseUrl } from './env' + +declare module 'vitest' { + interface ProvidedContext { + baseUrl: string + } +} + +const appDir = resolve(fileURLToPath(new URL('.', import.meta.url)), '../../..') + +function freePort() { + return new Promise((done, fail) => { + const probe = createServer() + probe.on('error', fail) + probe.listen(0, '127.0.0.1', () => { + const address = probe.address() + if (typeof address === 'string' || address === null) return fail(new Error('could not resolve a free port')) + probe.close(() => done(address.port)) + }) + }) +} + +/** A clean instance, not merely empty tables: the migrations are part of what is covered. */ +async function resetSchema() { + const client = postgres(testDatabaseUrl(), { max: 1, onnotice: () => {} }) + try { + await client.unsafe('drop schema if exists public cascade; create schema public; drop schema if exists drizzle cascade') + } finally { + await client.end() + } +} + +/** `TEST_SKIP_BUILD=true` reuses the last `.output`, for a quick local loop. */ +function build() { + if (process.env.TEST_SKIP_BUILD === 'true') return + execFileSync(process.execPath, ['node_modules/nuxt/bin/nuxt.mjs', 'build'], { + cwd: appDir, + stdio: 'inherit', + env: { + ...process.env, + NODE_OPTIONS: [process.env.NODE_OPTIONS, '--max-old-space-size=8192'].filter(Boolean).join(' ') + } + }) +} + +async function waitForServer(baseUrl: string, server: ChildProcess, log: () => string) { + const deadline = Date.now() + 60_000 + while (Date.now() < deadline) { + if (server.exitCode !== null) throw new Error(`the server exited with code ${server.exitCode} before answering:\n${log()}`) + try { + // 503 means the database is unreachable, worth failing on here rather than in every test. + const response = await fetch(`${baseUrl}/api/health`) + if (response.status === 200) return + throw new Error(`/api/health answered ${response.status}`) + } catch { + await new Promise(done => setTimeout(done, 250)) + } + } + throw new Error(`the server never answered on ${baseUrl}:\n${log()}`) +} + +/** One build and one server for the whole run; `setup()` on its own rebuilds per file. */ +export default async function globalSetup(project: TestProject) { + build() + await resetSchema() + + const port = await freePort() + const baseUrl = `http://127.0.0.1:${port}` + + // Migrations run in the server's own boot plugin, against the schema just dropped. + const server = spawn(process.execPath, ['.output/server/index.mjs'], { + cwd: appDir, + env: { ...process.env, ...serverEnv(port) }, + stdio: ['ignore', 'pipe', 'pipe'] + }) + + const output: string[] = [] + server.stdout?.on('data', chunk => output.push(String(chunk))) + server.stderr?.on('data', chunk => output.push(String(chunk))) + + try { + await waitForServer(baseUrl, server, () => output.join('')) + } catch (error) { + server.kill('SIGKILL') + throw error + } + + project.provide('baseUrl', baseUrl) + + return async () => { + server.kill('SIGTERM') + } +} diff --git a/apps/app/vitest.config.ts b/apps/app/vitest.config.ts index f345eba..f060946 100644 --- a/apps/app/vitest.config.ts +++ b/apps/app/vitest.config.ts @@ -1,11 +1,13 @@ import { defineConfig } from 'vitest/config' // Plain Vitest, not `@nuxt/test-utils`: these suites cover pure modules with no Nuxt or Nitro runtime to boot, so they run on every push without a database or a build step. -// Integration tests will need their own setup with a real Postgres — see ROADMAP.md. +// The routes are covered against a real Postgres in vitest.integration.config.ts. export default defineConfig({ test: { environment: 'node', include: ['tests/**/*.test.ts'], + // Integration tests have a config of their own: they need a build and a database. + exclude: ['tests/integration/**'], // Argon2id is intentionally slow, so key derivation tests need more than the 5s default. testTimeout: 30_000 } diff --git a/apps/app/vitest.integration.config.ts b/apps/app/vitest.integration.config.ts new file mode 100644 index 0000000..ca23bcd --- /dev/null +++ b/apps/app/vitest.integration.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config' + +// Separate from vitest.config.ts so `pnpm test` stays a database-free check: these build the app and boot it. +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/integration/**/*.test.ts'], + globalSetup: ['tests/integration/support/global-setup.ts'], + // One server and one database for the whole run, truncated between tests. + fileParallelism: false, + testTimeout: 60_000, + hookTimeout: 180_000 + } +}) diff --git a/package.json b/package.json index 52df8de..ac906d4 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "build:docs": "pnpm --filter docs build", "typecheck": "pnpm --filter app typecheck", "test": "pnpm --filter app test", + "test:integration": "pnpm --filter app test:integration", "lint": "pnpm --filter app lint", "lint:fix": "pnpm --filter app lint:fix", "release": "node scripts/release.mjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af0ce74..ad1b8ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: '@nuxt/eslint': specifier: 1.17.0 version: 1.17.0(@typescript-eslint/utils@8.68.0(eslint@10.9.1(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(@vue/compiler-sfc@3.5.42)(esbuild@0.28.2)(eslint@10.9.1(jiti@2.7.0)(supports-color@10.2.2))(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(srvx@0.12.7)(supports-color@10.2.2)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + '@nuxt/test-utils': + specifier: ^4.2.0 + version: 4.2.0(esbuild@0.28.2)(magicast@0.5.4)(rolldown@1.2.6)(rollup@4.63.1)(typescript@6.0.3)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.0)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) '@types/nodemailer': specifier: ^8.0.1 version: 8.0.1 @@ -1596,9 +1599,6 @@ packages: '@jridgewell/source-map@0.3.11': resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/sourcemap-codec@1.6.0': resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} @@ -1717,6 +1717,11 @@ packages: '@nuxt/devalue@2.0.2': resolution: {integrity: sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==} + '@nuxt/devtools-kit@2.7.0': + resolution: {integrity: sha512-MIJdah6CF6YOW2GhfKnb8Sivu6HpcQheqdjOlZqShBr+1DyjtKQbAKSCAyKPaoIzZP4QOo2SmTFV6aN8jBeEIQ==} + peerDependencies: + vite: '>=6.0' + '@nuxt/devtools-kit@3.4.2': resolution: {integrity: sha512-TPPbmH9xrExagYxfCe6JgtbHmgchfgDdA656Yws18rn8I/oCvB+gcw3kTWBBp2sC9ipZPXsgbsW/yZnVnGNBiA==} peerDependencies: @@ -1845,6 +1850,45 @@ packages: peerDependencies: '@nuxt/kit': '>=3.0.0' + '@nuxt/test-utils@4.2.0': + resolution: {integrity: sha512-QwmLxlOFoqHrHnOSSl3TJObJ+htx1UgoasP98zbcQbWjkXBef//pMMn7pKzEBHbrW4WfRCioDQw1L2nI3BqkfA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@cucumber/cucumber': '>=11.0.0' + '@jest/globals': '>=30.0.0' + '@playwright/test': ^1.43.1 + '@testing-library/vue': ^8.0.1 + '@vitest/ui': '*' + '@vue/test-utils': ^2.4.2 + h3-next: '>=2.0.1-rc.22' + happy-dom: '>=20.0.11' + jsdom: '>=27.4.0' + playwright-core: ^1.43.1 + vitest: ^4.0.2 + peerDependenciesMeta: + '@cucumber/cucumber': + optional: true + '@jest/globals': + optional: true + '@playwright/test': + optional: true + '@testing-library/vue': + optional: true + '@vitest/ui': + optional: true + '@vue/test-utils': + optional: true + h3-next: + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright-core: + optional: true + vitest: + optional: true + '@nuxt/ui@4.11.0': resolution: {integrity: sha512-gDs67/fWk3kipcEV4Pc5h1VnZD1glfWfINmJU7s3qpkxxRTkfkPxnUszXG664whNWcoqucS1WgZ/rNjZa3pTMw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2495,9 +2539,6 @@ packages: '@oxc-project/types@0.143.0': resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} - '@oxc-project/types@0.146.0': - resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==} - '@oxc-project/types@0.147.0': resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} @@ -2741,85 +2782,42 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - '@rolldown/binding-android-arm-eabi@1.2.5': - resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - '@rolldown/binding-android-arm-eabi@1.2.6': resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.5': - resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - '@rolldown/binding-android-arm64@1.2.6': resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.5': - resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - '@rolldown/binding-darwin-arm64@1.2.6': resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.5': - resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - '@rolldown/binding-darwin-x64@1.2.6': resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.5': - resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - '@rolldown/binding-freebsd-x64@1.2.6': resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.5': - resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.2.6': resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.5': - resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@rolldown/binding-linux-arm64-gnu@1.2.6': resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2827,13 +2825,6 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.5': - resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - '@rolldown/binding-linux-arm64-musl@1.2.6': resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2841,13 +2832,6 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.5': - resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@rolldown/binding-linux-ppc64-gnu@1.2.6': resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2855,13 +2839,6 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.5': - resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.6': resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2869,13 +2846,6 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.5': - resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.6': resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2883,13 +2853,6 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.5': - resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - '@rolldown/binding-linux-x64-musl@1.2.6': resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2897,36 +2860,18 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.5': - resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.2.6': resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.5': - resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.2.6': resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.5': - resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.6': resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3017,277 +2962,139 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.62.4': - resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.63.1': resolution: {integrity: sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.62.4': - resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.63.1': resolution: {integrity: sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.62.4': - resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.63.1': resolution: {integrity: sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.62.4': - resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.63.1': resolution: {integrity: sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.62.4': - resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} - cpu: [arm64] - os: [freebsd] - '@rollup/rollup-freebsd-arm64@4.63.1': resolution: {integrity: sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.62.4': - resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} - cpu: [x64] - os: [freebsd] - '@rollup/rollup-freebsd-x64@4.63.1': resolution: {integrity: sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.62.4': - resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} - cpu: [arm] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-gnueabihf@4.63.1': resolution: {integrity: sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.62.4': - resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} - cpu: [arm] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm-musleabihf@4.63.1': resolution: {integrity: sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.62.4': - resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm64-gnu@4.63.1': resolution: {integrity: sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.62.4': - resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} - cpu: [arm64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-musl@4.63.1': resolution: {integrity: sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.62.4': - resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} - cpu: [loong64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-loong64-gnu@4.63.1': resolution: {integrity: sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.62.4': - resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} - cpu: [loong64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-loong64-musl@4.63.1': resolution: {integrity: sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.62.4': - resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.63.1': resolution: {integrity: sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.62.4': - resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} - cpu: [ppc64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-ppc64-musl@4.63.1': resolution: {integrity: sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.62.4': - resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.63.1': resolution: {integrity: sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.62.4': - resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} - cpu: [riscv64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-riscv64-musl@4.63.1': resolution: {integrity: sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.62.4': - resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-s390x-gnu@4.63.1': resolution: {integrity: sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.62.4': - resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} - cpu: [x64] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.63.1': resolution: {integrity: sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.62.4': - resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} - cpu: [x64] - os: [linux] - libc: [musl] - '@rollup/rollup-linux-x64-musl@4.63.1': resolution: {integrity: sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.62.4': - resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} - cpu: [x64] - os: [openbsd] - '@rollup/rollup-openbsd-x64@4.63.1': resolution: {integrity: sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.62.4': - resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} - cpu: [arm64] - os: [openharmony] - '@rollup/rollup-openharmony-arm64@4.63.1': resolution: {integrity: sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.62.4': - resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.63.1': resolution: {integrity: sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.62.4': - resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.63.1': resolution: {integrity: sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.62.4': - resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-gnu@4.63.1': resolution: {integrity: sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.62.4': - resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.63.1': resolution: {integrity: sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==} cpu: [x64] @@ -4184,15 +3991,9 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@vue/compiler-core@3.5.41': - resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==} - '@vue/compiler-core@3.5.42': resolution: {integrity: sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==} - '@vue/compiler-dom@3.5.41': - resolution: {integrity: sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==} - '@vue/compiler-dom@3.5.42': resolution: {integrity: sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==} @@ -4237,9 +4038,6 @@ packages: '@vue/server-renderer@3.5.42': resolution: {integrity: sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==} - '@vue/shared@3.5.41': - resolution: {integrity: sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==} - '@vue/shared@3.5.42': resolution: {integrity: sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==} @@ -4677,9 +4475,6 @@ packages: caniuse-api@4.0.0: resolution: {integrity: sha512-B0hQ1OLyJuHTQSOWXvwibWqM6DCoqJdvBA6X1S/53bd4XU7LJ1yurIPlrsouol3mw1jh9pGI4ivubSpmJeIqCA==} - caniuse-lite@1.0.30001809: - resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} - caniuse-lite@1.0.30001810: resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} @@ -4895,14 +4690,6 @@ packages: crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} - crossws@0.4.11: - resolution: {integrity: sha512-qCHS/+7vxBgoGM5WxkfJDXLzEjSkbNjoFrExW7nEHr0BRuqEoA2vvPubTqJCe6+LlQKKlUZ7GLL7yEcNrkxHFQ==} - peerDependencies: - srvx: '>=0.11.5' - peerDependenciesMeta: - srvx: - optional: true - crossws@0.4.12: resolution: {integrity: sha512-aypfsr6t0uNvkqaZc6zvBfXzC6pLI0/sIulpkV6RwCVtZqG5ebBzv4weImKK0VNCj91Wl9F5j7p5WU4MNrybng==} peerDependencies: @@ -5601,6 +5388,10 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fake-indexeddb@6.2.5: + resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==} + engines: {node: '>=18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5984,10 +5775,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} - engines: {node: '>= 4'} - ignore@7.0.7: resolution: {integrity: sha512-dML0wP6oak21rsNYCJpJB6O1BJIEwNpGrTw0URPfAk4hm0e3pRfCtzkfB6olBcXcVlU2rouCyz7lCyRB0OMVCA==} engines: {node: '>= 4'} @@ -6192,10 +5979,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} - hasBin: true - js-yaml@4.3.2: resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true @@ -6497,9 +6280,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magic-string@1.2.2: - resolution: {integrity: sha512-veT/+7iXrXzT39XnEN4lOxtNl72dMgJ8Lp+5Bd6YcMSWpb0n0MjBM8Uuooi6jgJr8dhUW2swQgBmoZVMni5SVg==} - magic-string@1.2.3: resolution: {integrity: sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==} @@ -6835,6 +6615,9 @@ packages: node-mock-http@1.0.4: resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + node-mock-http@1.0.5: + resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} + node-releases@2.0.53: resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} engines: {node: '>=18'} @@ -7020,10 +6803,6 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} - open@11.0.1: - resolution: {integrity: sha512-NzwMUB6C1D0+Kd+9iMS/H4k+Ck3cTX6Ckyfr/gAGlmvSE1LUQZnEZvWBi4PYmMwH/S5SMeTXnE+9uAz8uF+pWw==} - engines: {node: '>=20'} - open@11.0.2: resolution: {integrity: sha512-RWqF+pBSkqecEvCKOn8QYhaNdRMJDZRIrlS/7rTDdLHaPcfXGCZ/h8zb413NfvdeAV0MR7T1yJcA34/q+CSm1Q==} engines: {node: '>=20'} @@ -7187,10 +6966,6 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - picomatch@4.0.7: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} @@ -7402,10 +7177,6 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} - powershell-utils@0.2.0: - resolution: {integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==} - engines: {node: '>=20'} - powershell-utils@0.2.1: resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==} engines: {node: '>=20'} @@ -7674,11 +7445,6 @@ packages: rolldown: optional: true - rolldown@1.2.5: - resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rolldown@1.2.6: resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -7697,11 +7463,6 @@ packages: rollup: optional: true - rollup@4.62.4: - resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rollup@4.63.1: resolution: {integrity: sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -8071,11 +7832,6 @@ packages: teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} - terser@5.50.0: - resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==} - engines: {node: '>=10'} - hasBin: true - terser@5.51.2: resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} engines: {node: '>=10'} @@ -8629,6 +8385,9 @@ packages: yaml: optional: true + vitest-environment-nuxt@2.0.0: + resolution: {integrity: sha512-zEGFRiCAaRR3fHnqISHKMNTRvCzkQEI1XyFeqNgR2IBD0oYkfZ1rUHwi7C+h3Cns3KPykfB0av1B3MtLEbChDw==} + vitest@4.1.11: resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -8975,7 +8734,7 @@ snapshots: '@apidevtools/json-schema-ref-parser@14.2.1(@types/json-schema@7.0.15)': dependencies: '@types/json-schema': 7.0.15 - js-yaml: 4.3.1 + js-yaml: 4.3.2 '@babel/code-frame@7.29.7': dependencies: @@ -10110,7 +9869,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': @@ -10125,8 +9884,6 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/sourcemap-codec@1.6.0': {} '@jridgewell/trace-mapping@0.3.31': @@ -10426,6 +10183,14 @@ snapshots: '@nuxt/devalue@2.0.2': {} + '@nuxt/devtools-kit@2.7.0(magicast@0.5.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': + dependencies: + '@nuxt/kit': 3.21.11(magicast@0.5.4) + execa: 8.0.1 + vite: 8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) + transitivePeerDependencies: + - magicast + '@nuxt/devtools-kit@3.4.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.140.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': dependencies: '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.140.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) @@ -11230,7 +10995,7 @@ snapshots: '@nuxt/schema@4.5.2': dependencies: - '@vue/shared': 3.5.41 + '@vue/shared': 3.5.42 defu: 6.1.7 nostics: 1.2.0 pathe: 2.0.3 @@ -11314,40 +11079,85 @@ snapshots: rc9: 3.0.1 std-env: 4.2.0 - '@nuxt/ui@4.11.0(b9845a5c287143a05d8d71c66ca9aeee)': + '@nuxt/test-utils@4.2.0(esbuild@0.28.2)(magicast@0.5.4)(rolldown@1.2.6)(rollup@4.63.1)(typescript@6.0.3)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.0)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)))': dependencies: - '@floating-ui/dom': 1.8.0 - '@iconify/vue': 5.0.1(vue@3.5.42(typescript@6.0.3)) - '@nuxt/fonts': 0.14.0(db0@0.3.4(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(kysely@0.29.5)(postgres@3.4.9)))(esbuild@0.28.2)(ioredis@5.11.1(supports-color@10.2.2))(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) - '@nuxt/icon': 2.5.1(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vue@3.5.42(typescript@6.0.3)) - '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) - '@nuxt/schema': 4.5.2 - '@nuxtjs/color-mode': 4.0.1(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) - '@standard-schema/spec': 1.1.0 - '@tailwindcss/postcss': 4.3.3 - '@tailwindcss/vite': 4.3.3(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) - '@tanstack/vue-table': 8.21.3(vue@3.5.42(typescript@6.0.3)) - '@tanstack/vue-virtual': 3.13.36(vue@3.5.42(typescript@6.0.3)) - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/extension-bubble-menu': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-code': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-collaboration': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31) - '@tiptap/extension-drag-handle': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/extension-collaboration@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31))(@tiptap/extension-node-range@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31)) - '@tiptap/extension-drag-handle-vue-3': 3.27.1(@tiptap/extension-drag-handle@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/extension-collaboration@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31))(@tiptap/extension-node-range@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31)))(@tiptap/pm@3.27.1)(@tiptap/vue-3@3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3)) - '@tiptap/extension-floating-menu': 3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-horizontal-rule': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-image': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-mention': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/suggestion@3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-node-range': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-placeholder': 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/markdown': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 - '@tiptap/starter-kit': 3.27.1 - '@tiptap/suggestion': 3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/vue-3': 3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.42(typescript@6.0.3)) - '@unhead/vue': 3.4.0(@oxc-project/types@0.147.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vue@3.5.42(typescript@6.0.3)) - '@vueuse/core': 14.4.0(vue@3.5.42(typescript@6.0.3)) - '@vueuse/integrations': 14.4.0(change-case@5.4.4)(fuse.js@7.5.0)(qrcode@1.5.4)(vue@3.5.42(typescript@6.0.3)) + '@clack/prompts': 1.7.0 + '@nuxt/devtools-kit': 2.7.0(magicast@0.5.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + '@nuxt/kit': 3.21.11(magicast@0.5.4) + c12: 3.3.4(magicast@0.5.4) + consola: 3.4.2 + defu: 6.1.7 + destr: 2.0.5 + estree-walker: 3.0.3 + exsolve: 1.1.1 + fake-indexeddb: 6.2.5 + get-port-please: 3.2.0 + h3: 1.15.11 + local-pkg: 1.2.1 + magic-string: 1.2.3 + node-fetch-native: 1.6.7 + node-mock-http: 1.0.5 + nypm: 0.6.9 + ofetch: 1.5.1 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + radix3: 1.1.2 + scule: 1.3.0 + std-env: 4.2.0 + tinyexec: 1.3.0 + ufo: 1.6.4 + unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + vitest-environment-nuxt: 2.0.0(esbuild@0.28.2)(magicast@0.5.4)(rolldown@1.2.6)(rollup@4.63.1)(typescript@6.0.3)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.0)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) + vue: 3.5.42(typescript@6.0.3) + optionalDependencies: + vitest: 4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.0)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - magicast + - rolldown + - rollup + - typescript + - unloader + - vite + - webpack + + '@nuxt/ui@4.11.0(b9845a5c287143a05d8d71c66ca9aeee)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@iconify/vue': 5.0.1(vue@3.5.42(typescript@6.0.3)) + '@nuxt/fonts': 0.14.0(db0@0.3.4(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(kysely@0.29.5)(postgres@3.4.9)))(esbuild@0.28.2)(ioredis@5.11.1(supports-color@10.2.2))(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + '@nuxt/icon': 2.5.1(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vue@3.5.42(typescript@6.0.3)) + '@nuxt/kit': 4.5.2(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) + '@nuxt/schema': 4.5.2 + '@nuxtjs/color-mode': 4.0.1(magic-string@1.2.3)(magicast@0.5.4)(oxc-parser@0.141.0)(rolldown@1.2.6)(unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) + '@standard-schema/spec': 1.1.0 + '@tailwindcss/postcss': 4.3.3 + '@tailwindcss/vite': 4.3.3(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + '@tanstack/vue-table': 8.21.3(vue@3.5.42(typescript@6.0.3)) + '@tanstack/vue-virtual': 3.13.36(vue@3.5.42(typescript@6.0.3)) + '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/extension-bubble-menu': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-code': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + '@tiptap/extension-collaboration': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31) + '@tiptap/extension-drag-handle': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/extension-collaboration@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31))(@tiptap/extension-node-range@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31)) + '@tiptap/extension-drag-handle-vue-3': 3.27.1(@tiptap/extension-drag-handle@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/extension-collaboration@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31))(yjs@13.6.31))(@tiptap/extension-node-range@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/y-tiptap@3.0.5(prosemirror-model@1.25.9)(prosemirror-state@1.4.4)(prosemirror-view@1.42.0)(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31)))(@tiptap/pm@3.27.1)(@tiptap/vue-3@3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.42(typescript@6.0.3)))(vue@3.5.42(typescript@6.0.3)) + '@tiptap/extension-floating-menu': 3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-horizontal-rule': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-image': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + '@tiptap/extension-mention': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@tiptap/suggestion@3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + '@tiptap/extension-node-range': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-placeholder': 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + '@tiptap/markdown': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/pm': 3.27.1 + '@tiptap/starter-kit': 3.27.1 + '@tiptap/suggestion': 3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/vue-3': 3.27.1(@floating-ui/dom@1.8.0)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(vue@3.5.42(typescript@6.0.3)) + '@unhead/vue': 3.4.0(@oxc-project/types@0.147.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vue@3.5.42(typescript@6.0.3)) + '@vueuse/core': 14.4.0(vue@3.5.42(typescript@6.0.3)) + '@vueuse/integrations': 14.4.0(change-case@5.4.4)(fuse.js@7.5.0)(qrcode@1.5.4)(vue@3.5.42(typescript@6.0.3)) '@vueuse/shared': 14.4.0(vue@3.5.42(typescript@6.0.3)) colortranslator: 5.0.0 consola: 3.4.2 @@ -11957,7 +11767,7 @@ snapshots: '@shikijs/transformers': 4.4.3 '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - '@vue/compiler-core': 3.5.41 + '@vue/compiler-core': 3.5.42 consola: 3.4.2 debug: 4.4.3(supports-color@10.2.2) defu: 6.1.7 @@ -12012,7 +11822,7 @@ snapshots: '@shikijs/transformers': 4.4.3 '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - '@vue/compiler-core': 3.5.41 + '@vue/compiler-core': 3.5.42 consola: 3.4.2 debug: 4.4.3(supports-color@10.2.2) defu: 6.1.7 @@ -12378,8 +12188,6 @@ snapshots: '@oxc-project/types@0.143.0': {} - '@oxc-project/types@0.146.0': {} - '@oxc-project/types@0.147.0': {} '@oxc-transform/binding-android-arm-eabi@0.141.0': @@ -12479,7 +12287,7 @@ snapshots: '@parcel/watcher-wasm@2.6.0': dependencies: is-glob: 4.0.3 - picomatch: 4.0.5 + picomatch: 4.0.7 '@parcel/watcher-win32-arm64@2.5.6': optional: true @@ -12495,7 +12303,7 @@ snapshots: detect-libc: 2.1.2 is-glob: 4.0.3 node-addon-api: 7.1.1 - picomatch: 4.0.5 + picomatch: 4.0.7 optionalDependencies: '@parcel/watcher-android-arm64': 2.5.6 '@parcel/watcher-darwin-arm64': 2.5.6 @@ -12534,152 +12342,107 @@ snapshots: quansync: 1.0.0 optional: true - '@rolldown/binding-android-arm-eabi@1.2.5': - optional: true - '@rolldown/binding-android-arm-eabi@1.2.6': optional: true - '@rolldown/binding-android-arm64@1.2.5': - optional: true - '@rolldown/binding-android-arm64@1.2.6': optional: true - '@rolldown/binding-darwin-arm64@1.2.5': - optional: true - '@rolldown/binding-darwin-arm64@1.2.6': optional: true - '@rolldown/binding-darwin-x64@1.2.5': - optional: true - '@rolldown/binding-darwin-x64@1.2.6': optional: true - '@rolldown/binding-freebsd-x64@1.2.5': - optional: true - '@rolldown/binding-freebsd-x64@1.2.6': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.5': - optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.6': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.5': - optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.6': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.5': - optional: true - '@rolldown/binding-linux-arm64-musl@1.2.6': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.5': - optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.6': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.5': - optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.6': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.5': - optional: true - '@rolldown/binding-linux-x64-gnu@1.2.6': optional: true - '@rolldown/binding-linux-x64-musl@1.2.5': - optional: true - '@rolldown/binding-linux-x64-musl@1.2.6': optional: true - '@rolldown/binding-openharmony-arm64@1.2.5': - optional: true - '@rolldown/binding-openharmony-arm64@1.2.6': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.5': - optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.6': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.5': - optional: true - '@rolldown/binding-win32-x64-msvc@1.2.6': optional: true '@rolldown/pluginutils@1.0.1': {} - '@rollup/plugin-alias@6.0.0(rollup@4.62.4)': + '@rollup/plugin-alias@6.0.0(rollup@4.63.1)': optionalDependencies: - rollup: 4.62.4 + rollup: 4.63.1 - '@rollup/plugin-commonjs@29.0.3(rollup@4.62.4)': + '@rollup/plugin-commonjs@29.0.3(rollup@4.63.1)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.63.1) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.5.0(picomatch@4.0.5) + fdir: 6.5.0(picomatch@4.0.7) is-reference: 1.2.1 magic-string: 0.30.21 - picomatch: 4.0.5 + picomatch: 4.0.7 optionalDependencies: - rollup: 4.62.4 + rollup: 4.63.1 - '@rollup/plugin-inject@5.0.5(rollup@4.62.4)': + '@rollup/plugin-inject@5.0.5(rollup@4.63.1)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.63.1) estree-walker: 2.0.2 magic-string: 0.30.21 optionalDependencies: - rollup: 4.62.4 + rollup: 4.63.1 - '@rollup/plugin-json@6.1.0(rollup@4.62.4)': + '@rollup/plugin-json@6.1.0(rollup@4.63.1)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.63.1) optionalDependencies: - rollup: 4.62.4 + rollup: 4.63.1 - '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.4)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.63.1)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.63.1) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.12 optionalDependencies: - rollup: 4.62.4 + rollup: 4.63.1 - '@rollup/plugin-replace@6.0.3(rollup@4.62.4)': + '@rollup/plugin-replace@6.0.3(rollup@4.63.1)': dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.63.1) magic-string: 0.30.21 optionalDependencies: - rollup: 4.62.4 + rollup: 4.63.1 - '@rollup/plugin-terser@1.0.0(rollup@4.62.4)': + '@rollup/plugin-terser@1.0.0(rollup@4.63.1)': dependencies: serialize-javascript: 7.0.7 smob: 1.6.2 - terser: 5.50.0 + terser: 5.51.2 optionalDependencies: - rollup: 4.62.4 + rollup: 4.63.1 '@rollup/plugin-yaml@4.1.2(rollup@4.63.1)': dependencies: @@ -12689,14 +12452,6 @@ snapshots: optionalDependencies: rollup: 4.63.1 - '@rollup/pluginutils@5.4.0(rollup@4.62.4)': - dependencies: - '@types/estree': 1.0.9 - estree-walker: 2.0.2 - picomatch: 4.0.7 - optionalDependencies: - rollup: 4.62.4 - '@rollup/pluginutils@5.4.0(rollup@4.63.1)': dependencies: '@types/estree': 1.0.9 @@ -12705,153 +12460,78 @@ snapshots: optionalDependencies: rollup: 4.63.1 - '@rollup/rollup-android-arm-eabi@4.62.4': - optional: true - '@rollup/rollup-android-arm-eabi@4.63.1': optional: true - '@rollup/rollup-android-arm64@4.62.4': - optional: true - '@rollup/rollup-android-arm64@4.63.1': optional: true - '@rollup/rollup-darwin-arm64@4.62.4': - optional: true - '@rollup/rollup-darwin-arm64@4.63.1': optional: true - '@rollup/rollup-darwin-x64@4.62.4': - optional: true - '@rollup/rollup-darwin-x64@4.63.1': optional: true - '@rollup/rollup-freebsd-arm64@4.62.4': - optional: true - '@rollup/rollup-freebsd-arm64@4.63.1': optional: true - '@rollup/rollup-freebsd-x64@4.62.4': - optional: true - '@rollup/rollup-freebsd-x64@4.63.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.62.4': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.63.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.62.4': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.63.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.62.4': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.63.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.62.4': - optional: true - '@rollup/rollup-linux-arm64-musl@4.63.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.62.4': - optional: true - '@rollup/rollup-linux-loong64-gnu@4.63.1': optional: true - '@rollup/rollup-linux-loong64-musl@4.62.4': - optional: true - '@rollup/rollup-linux-loong64-musl@4.63.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.62.4': - optional: true - '@rollup/rollup-linux-ppc64-gnu@4.63.1': optional: true - '@rollup/rollup-linux-ppc64-musl@4.62.4': - optional: true - '@rollup/rollup-linux-ppc64-musl@4.63.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.62.4': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.63.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.62.4': - optional: true - '@rollup/rollup-linux-riscv64-musl@4.63.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.62.4': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.63.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.62.4': - optional: true - '@rollup/rollup-linux-x64-gnu@4.63.1': optional: true - '@rollup/rollup-linux-x64-musl@4.62.4': - optional: true - '@rollup/rollup-linux-x64-musl@4.63.1': optional: true - '@rollup/rollup-openbsd-x64@4.62.4': - optional: true - '@rollup/rollup-openbsd-x64@4.63.1': optional: true - '@rollup/rollup-openharmony-arm64@4.62.4': - optional: true - '@rollup/rollup-openharmony-arm64@4.63.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.62.4': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.63.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.62.4': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.63.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.62.4': - optional: true - '@rollup/rollup-win32-x64-gnu@4.63.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.62.4': - optional: true - '@rollup/rollup-win32-x64-msvc@4.63.1': optional: true @@ -13460,7 +13140,7 @@ snapshots: '@unhead/bundler@3.4.0(@oxc-project/types@0.147.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(unhead@3.4.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': dependencies: - magic-string: 1.2.2 + magic-string: 1.2.3 oxc-walker: 1.1.1(@oxc-project/types@0.147.0)(oxc-parser@0.141.0)(rolldown@1.2.6) unhead: 3.4.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) @@ -13480,7 +13160,7 @@ snapshots: '@unhead/bundler@3.4.0(@oxc-project/types@0.147.0)(esbuild@0.28.2)(lightningcss@1.33.0)(oxc-parser@0.143.0)(rolldown@1.2.6)(rollup@4.63.1)(unhead@3.4.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))': dependencies: - magic-string: 1.2.2 + magic-string: 1.2.3 oxc-walker: 1.1.1(@oxc-project/types@0.147.0)(oxc-parser@0.143.0)(rolldown@1.2.6) unhead: 3.4.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) @@ -13625,10 +13305,10 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vercel/nft@1.10.2(rollup@4.62.4)(supports-color@10.2.2)': + '@vercel/nft@1.10.2(rollup@4.63.1)(supports-color@10.2.2)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(supports-color@10.2.2) - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + '@rollup/pluginutils': 5.4.0(rollup@4.63.1) acorn: 8.18.0 acorn-import-attributes: 1.9.5(acorn@8.18.0) async-sema: 3.1.1 @@ -13637,7 +13317,7 @@ snapshots: glob: 13.0.6 graceful-fs: 4.2.11 node-gyp-build: 4.8.4 - picomatch: 4.0.5 + picomatch: 4.0.7 resolve-from: 5.0.0 transitivePeerDependencies: - encoding @@ -13766,14 +13446,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vue/compiler-core@3.5.41': - dependencies: - '@babel/parser': 7.29.8 - '@vue/shared': 3.5.41 - entities: 7.0.1 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - '@vue/compiler-core@3.5.42': dependencies: '@babel/parser': 7.29.8 @@ -13782,11 +13454,6 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.41': - dependencies: - '@vue/compiler-core': 3.5.41 - '@vue/shared': 3.5.41 - '@vue/compiler-dom@3.5.42': dependencies: '@vue/compiler-core': 3.5.42 @@ -13833,12 +13500,12 @@ snapshots: '@vue/language-core@3.3.10': dependencies: '@volar/language-core': 2.4.28 - '@vue/compiler-dom': 3.5.41 - '@vue/shared': 3.5.41 + '@vue/compiler-dom': 3.5.42 + '@vue/shared': 3.5.42 alien-signals: 3.2.1 muggle-string: 0.4.1 path-browserify: 1.0.1 - picomatch: 4.0.5 + picomatch: 4.0.7 '@vue/language-core@3.3.11': dependencies: @@ -13872,8 +13539,6 @@ snapshots: '@vue/runtime-dom': 3.5.42 '@vue/shared': 3.5.42 - '@vue/shared@3.5.41': {} - '@vue/shared@3.5.42': {} '@vueuse/core@10.11.1(vue@3.5.42(typescript@6.0.3))': @@ -14188,7 +13853,7 @@ snapshots: browserslist@4.28.8: dependencies: baseline-browser-mapping: 2.11.15 - caniuse-lite: 1.0.30001809 + caniuse-lite: 1.0.30001810 electron-to-chromium: 1.5.409 node-releases: 2.0.53 update-browserslist-db: 1.3.1(browserslist@4.28.8) @@ -14258,8 +13923,6 @@ snapshots: browserslist: 4.28.8 caniuse-lite: 1.0.30001810 - caniuse-lite@1.0.30001809: {} - caniuse-lite@1.0.30001810: {} ccount@2.0.1: {} @@ -14439,10 +14102,6 @@ snapshots: dependencies: uncrypto: 0.1.3 - crossws@0.4.11(srvx@0.12.7): - optionalDependencies: - srvx: 0.12.7 - crossws@0.4.12(srvx@0.12.7): optionalDependencies: srvx: 0.12.7 @@ -15348,6 +15007,8 @@ snapshots: extend@3.0.2: {} + fake-indexeddb@6.2.5: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -15384,9 +15045,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 file-entry-cache@8.0.0: dependencies: @@ -15645,7 +15306,7 @@ snapshots: dependencies: '@sindresorhus/merge-streams': 4.0.0 fast-glob: 3.3.3 - ignore: 7.0.6 + ignore: 7.0.7 is-path-inside: 4.0.0 slash: 5.1.0 unicorn-magic: 0.4.0 @@ -15882,8 +15543,6 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.6: {} - ignore@7.0.7: {} image-meta@0.2.2: {} @@ -16069,10 +15728,6 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.3.1: - dependencies: - argparse: 2.0.1 - js-yaml@4.3.2: dependencies: argparse: 2.0.1 @@ -16264,7 +15919,7 @@ snapshots: '@parcel/watcher-wasm': 2.6.0 citty: 0.2.2 consola: 3.4.2 - crossws: 0.4.11(srvx@0.12.7) + crossws: 0.4.12(srvx@0.12.7) defu: 6.1.7 get-port-please: 3.2.0 h3: 1.15.11 @@ -16332,13 +15987,9 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 - magic-string@1.2.2: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@1.2.3: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 magicast@0.5.4: dependencies: @@ -16790,14 +16441,14 @@ snapshots: nitropack@2.13.4(@parcel/watcher@2.5.6)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(kysely@0.29.5)(postgres@3.4.9))(oxc-parser@0.141.0)(rolldown@1.2.6)(srvx@0.12.7)(supports-color@10.2.2)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@rollup/plugin-alias': 6.0.0(rollup@4.62.4) - '@rollup/plugin-commonjs': 29.0.3(rollup@4.62.4) - '@rollup/plugin-inject': 5.0.5(rollup@4.62.4) - '@rollup/plugin-json': 6.1.0(rollup@4.62.4) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.4) - '@rollup/plugin-replace': 6.0.3(rollup@4.62.4) - '@rollup/plugin-terser': 1.0.0(rollup@4.62.4) - '@vercel/nft': 1.10.2(rollup@4.62.4)(supports-color@10.2.2) + '@rollup/plugin-alias': 6.0.0(rollup@4.63.1) + '@rollup/plugin-commonjs': 29.0.3(rollup@4.63.1) + '@rollup/plugin-inject': 5.0.5(rollup@4.63.1) + '@rollup/plugin-json': 6.1.0(rollup@4.63.1) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.63.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.63.1) + '@rollup/plugin-terser': 1.0.0(rollup@4.63.1) + '@vercel/nft': 1.10.2(rollup@4.63.1)(supports-color@10.2.2) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.4) chokidar: 5.0.0 @@ -16839,8 +16490,8 @@ snapshots: pkg-types: 2.3.1 pretty-bytes: 7.1.0 radix3: 1.1.2 - rollup: 4.62.4 - rollup-plugin-visualizer: 7.1.1(rolldown@1.2.6)(rollup@4.62.4) + rollup: 4.63.1 + rollup-plugin-visualizer: 7.1.1(rolldown@1.2.6)(rollup@4.63.1) scule: 1.3.0 semver: 7.8.5 serve-placeholder: 2.0.2 @@ -16852,7 +16503,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.2)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.62.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + unimport: 6.4.0(esbuild@0.28.2)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) unplugin-utils: 0.3.2 unstorage: 1.17.5(db0@0.3.4(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(kysely@0.29.5)(postgres@3.4.9)))(ioredis@5.11.1(supports-color@10.2.2)) untyped: 2.0.0 @@ -16902,14 +16553,14 @@ snapshots: nitropack@2.13.4(@parcel/watcher@2.5.6)(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(kysely@0.29.5)(postgres@3.4.9))(oxc-parser@0.143.0)(rolldown@1.2.6)(srvx@0.12.7)(supports-color@10.2.2)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@rollup/plugin-alias': 6.0.0(rollup@4.62.4) - '@rollup/plugin-commonjs': 29.0.3(rollup@4.62.4) - '@rollup/plugin-inject': 5.0.5(rollup@4.62.4) - '@rollup/plugin-json': 6.1.0(rollup@4.62.4) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.4) - '@rollup/plugin-replace': 6.0.3(rollup@4.62.4) - '@rollup/plugin-terser': 1.0.0(rollup@4.62.4) - '@vercel/nft': 1.10.2(rollup@4.62.4)(supports-color@10.2.2) + '@rollup/plugin-alias': 6.0.0(rollup@4.63.1) + '@rollup/plugin-commonjs': 29.0.3(rollup@4.63.1) + '@rollup/plugin-inject': 5.0.5(rollup@4.63.1) + '@rollup/plugin-json': 6.1.0(rollup@4.63.1) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.63.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.63.1) + '@rollup/plugin-terser': 1.0.0(rollup@4.63.1) + '@vercel/nft': 1.10.2(rollup@4.63.1)(supports-color@10.2.2) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.4) chokidar: 5.0.0 @@ -16951,8 +16602,8 @@ snapshots: pkg-types: 2.3.1 pretty-bytes: 7.1.0 radix3: 1.1.2 - rollup: 4.62.4 - rollup-plugin-visualizer: 7.1.1(rolldown@1.2.6)(rollup@4.62.4) + rollup: 4.63.1 + rollup-plugin-visualizer: 7.1.1(rolldown@1.2.6)(rollup@4.63.1) scule: 1.3.0 semver: 7.8.5 serve-placeholder: 2.0.2 @@ -16964,7 +16615,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.2)(oxc-parser@0.143.0)(rolldown@1.2.6)(rollup@4.62.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) + unimport: 6.4.0(esbuild@0.28.2)(oxc-parser@0.143.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) unplugin-utils: 0.3.2 unstorage: 1.17.5(db0@0.3.4(better-sqlite3@12.11.1)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(kysely@0.29.5)(postgres@3.4.9)))(ioredis@5.11.1(supports-color@10.2.2)) untyped: 2.0.0 @@ -17037,6 +16688,8 @@ snapshots: node-mock-http@1.0.4: {} + node-mock-http@1.0.5: {} + node-releases@2.0.53: {} nodemailer@9.0.6: {} @@ -17633,15 +17286,6 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 - open@11.0.1: - dependencies: - default-browser: 5.5.1 - define-lazy-prop: 3.0.0 - is-in-ssh: 1.0.0 - is-inside-container: 1.0.0 - powershell-utils: 0.2.0 - wsl-utils: 1.0.0 - open@11.0.2: dependencies: default-browser: 5.5.1 @@ -17916,8 +17560,6 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.5: {} - picomatch@4.0.7: {} pify@4.0.1: {} @@ -18110,8 +17752,6 @@ snapshots: powershell-utils@0.1.0: {} - powershell-utils@0.2.0: {} - powershell-utils@0.2.1: {} prebuild-install@7.1.3: @@ -18496,27 +18136,6 @@ snapshots: optionalDependencies: rolldown: 1.2.6 - rolldown@1.2.5: - dependencies: - '@oxc-project/types': 0.146.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.5 - '@rolldown/binding-android-arm64': 1.2.5 - '@rolldown/binding-darwin-arm64': 1.2.5 - '@rolldown/binding-darwin-x64': 1.2.5 - '@rolldown/binding-freebsd-x64': 1.2.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.5 - '@rolldown/binding-linux-arm64-gnu': 1.2.5 - '@rolldown/binding-linux-arm64-musl': 1.2.5 - '@rolldown/binding-linux-ppc64-gnu': 1.2.5 - '@rolldown/binding-linux-s390x-gnu': 1.2.5 - '@rolldown/binding-linux-x64-gnu': 1.2.5 - '@rolldown/binding-linux-x64-musl': 1.2.5 - '@rolldown/binding-openharmony-arm64': 1.2.5 - '@rolldown/binding-win32-arm64-msvc': 1.2.5 - '@rolldown/binding-win32-x64-msvc': 1.2.5 - rolldown@1.2.6: dependencies: '@oxc-project/types': 0.147.0 @@ -18538,58 +18157,15 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.6 '@rolldown/binding-win32-x64-msvc': 1.2.6 - rollup-plugin-visualizer@7.1.1(rolldown@1.2.6)(rollup@4.62.4): - dependencies: - open: 11.0.1 - picomatch: 4.0.5 - source-map: 0.8.0 - yargs: 18.1.0 - optionalDependencies: - rolldown: 1.2.6 - rollup: 4.62.4 - rollup-plugin-visualizer@7.1.1(rolldown@1.2.6)(rollup@4.63.1): dependencies: - open: 11.0.1 - picomatch: 4.0.5 + open: 11.0.2 + picomatch: 4.0.7 source-map: 0.8.0 yargs: 18.1.0 optionalDependencies: rolldown: 1.2.6 rollup: 4.63.1 - optional: true - - rollup@4.62.4: - dependencies: - '@types/estree': 1.0.9 - optionalDependencies: - '@napi-rs/lzma-linux-x64-gnu': 1.5.1 - '@rollup/rollup-android-arm-eabi': 4.62.4 - '@rollup/rollup-android-arm64': 4.62.4 - '@rollup/rollup-darwin-arm64': 4.62.4 - '@rollup/rollup-darwin-x64': 4.62.4 - '@rollup/rollup-freebsd-arm64': 4.62.4 - '@rollup/rollup-freebsd-x64': 4.62.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 - '@rollup/rollup-linux-arm-musleabihf': 4.62.4 - '@rollup/rollup-linux-arm64-gnu': 4.62.4 - '@rollup/rollup-linux-arm64-musl': 4.62.4 - '@rollup/rollup-linux-loong64-gnu': 4.62.4 - '@rollup/rollup-linux-loong64-musl': 4.62.4 - '@rollup/rollup-linux-ppc64-gnu': 4.62.4 - '@rollup/rollup-linux-ppc64-musl': 4.62.4 - '@rollup/rollup-linux-riscv64-gnu': 4.62.4 - '@rollup/rollup-linux-riscv64-musl': 4.62.4 - '@rollup/rollup-linux-s390x-gnu': 4.62.4 - '@rollup/rollup-linux-x64-gnu': 4.62.4 - '@rollup/rollup-linux-x64-musl': 4.62.4 - '@rollup/rollup-openbsd-x64': 4.62.4 - '@rollup/rollup-openharmony-arm64': 4.62.4 - '@rollup/rollup-win32-arm64-msvc': 4.62.4 - '@rollup/rollup-win32-ia32-msvc': 4.62.4 - '@rollup/rollup-win32-x64-gnu': 4.62.4 - '@rollup/rollup-win32-x64-msvc': 4.62.4 - fsevents: 2.3.3 rollup@4.63.1: dependencies: @@ -19082,20 +18658,12 @@ snapshots: - bare-abort-controller - react-native-b4a - terser@5.50.0: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.18.0 - commander: 2.20.3 - source-map-support: 0.5.21 - terser@5.51.2: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.18.0 commander: 2.20.3 source-map-support: 0.5.21 - optional: true text-decoder@1.2.7: dependencies: @@ -19121,8 +18689,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 tinyrainbow@3.1.1: {} @@ -19314,35 +18882,6 @@ snapshots: ohash: 2.0.12 undici: 8.10.0 - unimport@6.4.0(esbuild@0.28.2)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.62.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): - dependencies: - acorn: 8.18.0 - escape-string-regexp: 5.0.0 - estree-walker: 3.0.3 - local-pkg: 1.2.1 - magic-string: 1.2.3 - mlly: 1.8.2 - pathe: 2.0.3 - picomatch: 4.0.7 - pkg-types: 2.3.1 - scule: 1.3.0 - strip-literal: 4.0.0 - tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.62.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) - unplugin-utils: 0.3.2 - optionalDependencies: - oxc-parser: 0.141.0 - rolldown: 1.2.6 - transitivePeerDependencies: - - '@farmfe/core' - - '@rspack/core' - - bun-types-no-globals - - esbuild - - rollup - - unloader - - vite - - webpack - unimport@6.4.0(esbuild@0.28.2)(oxc-parser@0.141.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): dependencies: acorn: 8.18.0 @@ -19372,35 +18911,6 @@ snapshots: - vite - webpack - unimport@6.4.0(esbuild@0.28.2)(oxc-parser@0.143.0)(rolldown@1.2.6)(rollup@4.62.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): - dependencies: - acorn: 8.18.0 - escape-string-regexp: 5.0.0 - estree-walker: 3.0.3 - local-pkg: 1.2.1 - magic-string: 1.2.3 - mlly: 1.8.2 - pathe: 2.0.3 - picomatch: 4.0.7 - pkg-types: 2.3.1 - scule: 1.3.0 - strip-literal: 4.0.0 - tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.62.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)) - unplugin-utils: 0.3.2 - optionalDependencies: - oxc-parser: 0.143.0 - rolldown: 1.2.6 - transitivePeerDependencies: - - '@farmfe/core' - - '@rspack/core' - - bun-types-no-globals - - esbuild - - rollup - - unloader - - vite - - webpack - unimport@6.4.0(esbuild@0.28.2)(oxc-parser@0.143.0)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): dependencies: acorn: 8.18.0 @@ -19569,24 +19079,13 @@ snapshots: dependencies: '@jridgewell/remapping': 2.3.5 acorn: 8.18.0 - picomatch: 4.0.5 + picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 - unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.62.4)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): - dependencies: - '@jridgewell/remapping': 2.3.5 - picomatch: 4.0.5 - webpack-virtual-modules: 0.6.2 - optionalDependencies: - esbuild: 0.28.2 - rolldown: 1.2.6 - rollup: 4.62.4 - vite: 8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0) - unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.6)(rollup@4.63.1)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 - picomatch: 4.0.5 + picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 optionalDependencies: esbuild: 0.28.2 @@ -19817,9 +19316,9 @@ snapshots: vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 - picomatch: 4.0.5 + picomatch: 4.0.7 postcss: 8.5.26 - rolldown: 1.2.5 + rolldown: 1.2.6 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.4.0 @@ -19830,6 +19329,33 @@ snapshots: tsx: 4.23.13 yaml: 2.9.0 + vitest-environment-nuxt@2.0.0(esbuild@0.28.2)(magicast@0.5.4)(rolldown@1.2.6)(rollup@4.63.1)(typescript@6.0.3)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.0)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))): + dependencies: + '@nuxt/test-utils': 4.2.0(esbuild@0.28.2)(magicast@0.5.4)(rolldown@1.2.6)(rollup@4.63.1)(typescript@6.0.3)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.0)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))) + transitivePeerDependencies: + - '@cucumber/cucumber' + - '@farmfe/core' + - '@jest/globals' + - '@playwright/test' + - '@rspack/core' + - '@testing-library/vue' + - '@vitest/ui' + - '@vue/test-utils' + - bun-types-no-globals + - esbuild + - h3-next + - happy-dom + - jsdom + - magicast + - playwright-core + - rolldown + - rollup + - typescript + - unloader + - vite + - vitest + - webpack + vitest@4.1.11(@opentelemetry/api@1.9.1)(@types/node@26.4.0)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.11 From 112c32dc4ae733ab82efdd260f7d9229fa5bbc62 Mon Sep 17 00:00:00 2001 From: Thomas <28439359+thoda-dev@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:27:41 +0200 Subject: [PATCH 2/2] test(integration): wait on the server at teardown and close the pool --- apps/app/server/plugins/migrate.ts | 1 + apps/app/tests/integration/support/database.ts | 4 ++++ apps/app/tests/integration/support/global-setup.ts | 6 ++++++ apps/app/tests/integration/support/setup-file.ts | 5 +++++ apps/app/vitest.integration.config.ts | 1 + 5 files changed, 17 insertions(+) create mode 100644 apps/app/tests/integration/support/setup-file.ts diff --git a/apps/app/server/plugins/migrate.ts b/apps/app/server/plugins/migrate.ts index 6903bc3..f8d74f7 100644 --- a/apps/app/server/plugins/migrate.ts +++ b/apps/app/server/plugins/migrate.ts @@ -19,6 +19,7 @@ export default defineNitroPlugin(async () => { // Transaction-scoped: a session lock and its unlock are two pool checkouts, and land on different connections. await db.transaction(async (tx) => { await tx.execute(sql`select pg_advisory_xact_lock(${LOCK_KEY})`) + // `db`, not `tx`: the migrator opens a transaction of its own on whatever session it is handed. await migrate(db, { migrationsFolder }) console.log('[migrate] database is up to date') }) diff --git a/apps/app/tests/integration/support/database.ts b/apps/app/tests/integration/support/database.ts index a621694..32ee305 100644 --- a/apps/app/tests/integration/support/database.ts +++ b/apps/app/tests/integration/support/database.ts @@ -19,3 +19,7 @@ export async function resetDatabase() { const list = tables.map(row => `"${row.tablename}"`).join(', ') await client.unsafe(`truncate table ${list} restart identity cascade`) } + +export function closeDatabase() { + return client.end() +} diff --git a/apps/app/tests/integration/support/global-setup.ts b/apps/app/tests/integration/support/global-setup.ts index bb755b9..b25b934 100644 --- a/apps/app/tests/integration/support/global-setup.ts +++ b/apps/app/tests/integration/support/global-setup.ts @@ -94,6 +94,12 @@ export default async function globalSetup(project: TestProject) { project.provide('baseUrl', baseUrl) return async () => { + if (server.exitCode !== null) return + const exited = new Promise(done => server.once('exit', () => done())) server.kill('SIGTERM') + // Waited on, and escalated: an orphaned server holds the port and the database for the next run. + const forced = setTimeout(() => server.kill('SIGKILL'), 5_000) + await exited + clearTimeout(forced) } } diff --git a/apps/app/tests/integration/support/setup-file.ts b/apps/app/tests/integration/support/setup-file.ts new file mode 100644 index 0000000..0019e55 --- /dev/null +++ b/apps/app/tests/integration/support/setup-file.ts @@ -0,0 +1,5 @@ +import { afterAll } from 'vitest' +import { closeDatabase } from './database' + +// Runs in every worker: the global teardown lives in another process and cannot reach this pool. +afterAll(closeDatabase) diff --git a/apps/app/vitest.integration.config.ts b/apps/app/vitest.integration.config.ts index ca23bcd..4f335a2 100644 --- a/apps/app/vitest.integration.config.ts +++ b/apps/app/vitest.integration.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ environment: 'node', include: ['tests/integration/**/*.test.ts'], globalSetup: ['tests/integration/support/global-setup.ts'], + setupFiles: ['tests/integration/support/setup-file.ts'], // One server and one database for the whole run, truncated between tests. fileParallelism: false, testTimeout: 60_000,