diff --git a/README.md b/README.md index 995aaa9..99e5165 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Get a Steam Web API key at https://steamcommunity.com/dev/apikey (free). pnpm dev # run the proxy (tsx watch) pnpm build # compile all packages pnpm typecheck # typecheck all packages +pnpm test # run the unit tests (vitest) pnpm lint # lint all packages ``` diff --git a/docs/architecture.md b/docs/architecture.md index c37be90..0fb9cab 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -89,6 +89,7 @@ packages/extract ──► packages/tts (traverseMod) ──► packages/shared ## Tooling dependencies - `typescript` (strict), `tsx` (dev runner), `eslint`, `prettier`. +- `vitest` (unit tests), colocated as `*.test.ts` next to sources. - `pnpm` workspaces for package management. ## Deployment / runtime shape diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 93f000f..817d9a3 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -192,7 +192,8 @@ packages/extract → flatten objects / extract refs / download assets - TypeScript strict mode. - `tsx` for dev, `tsc` for build. -- Root scripts: `pnpm dev`, `pnpm build`, `pnpm lint`. +- `vitest` for unit tests, colocated as `*.test.ts` next to sources. +- Root scripts: `pnpm dev`, `pnpm build`, `pnpm test`, `pnpm lint`. ## Build order diff --git a/package.json b/package.json index 99f7db3..73fd934 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "dev": "pnpm --filter @tts/proxy dev", "build": "pnpm -r build", "lint": "pnpm -r lint", - "typecheck": "pnpm -r typecheck" + "typecheck": "pnpm -r typecheck", + "test": "vitest run" }, "packageManager": "pnpm@10.33.0", "devDependencies": { - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "vitest": "^4.1.10" } } \ No newline at end of file diff --git a/packages/extract/package.json b/packages/extract/package.json index 61fe790..468724c 100644 --- a/packages/extract/package.json +++ b/packages/extract/package.json @@ -14,6 +14,7 @@ "scripts": { "build": "tsc -p tsconfig.json", "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", "lint": "echo \"no lint configured\"" }, "dependencies": { diff --git a/packages/extract/src/download.test.ts b/packages/extract/src/download.test.ts new file mode 100644 index 0000000..584e7c6 --- /dev/null +++ b/packages/extract/src/download.test.ts @@ -0,0 +1,92 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import type { AssetRef } from '@tts/shared'; +import { downloadAll, downloadAsset, guessMimeType } from './download.js'; + +const ref = (url: string): AssetRef => ({ + kind: 'image', + url, + ownerGuid: 'g1', +}); + +function blobResponse(): Response { + return new Response(new Blob(['data']), { status: 200 }); +} + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('guessMimeType', () => { + it('maps common image extensions', () => { + expect(guessMimeType('https://example.com/a.png')).toBe('image/png'); + expect(guessMimeType('https://example.com/a.jpg')).toBe('image/jpeg'); + expect(guessMimeType('https://example.com/a.jpeg')).toBe('image/jpeg'); + expect(guessMimeType('https://example.com/a.gif')).toBe('image/gif'); + expect(guessMimeType('https://example.com/a.webp')).toBe('image/webp'); + expect(guessMimeType('https://example.com/a.pdf')).toBe('application/pdf'); + }); + + it('ignores query strings when reading the extension', () => { + expect(guessMimeType('https://example.com/a.png?v=2')).toBe('image/png'); + }); + + it('falls back to octet-stream for unknown extensions', () => { + expect(guessMimeType('https://example.com/a.xyz')).toBe( + 'application/octet-stream', + ); + expect(guessMimeType('https://example.com/noext')).toBe( + 'application/octet-stream', + ); + }); +}); + +describe('downloadAsset', () => { + it('returns a blob on success', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValue(blobResponse())); + const blob = await downloadAsset('https://example.com/a.png'); + expect(blob).toBeInstanceOf(Blob); + }); + + it('throws on a non-OK response', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue(new Response(null, { status: 404 })), + ); + await expect(downloadAsset('https://example.com/a.png')).rejects.toThrow( + 'Failed to download asset (404)', + ); + }); +}); + +describe('downloadAll', () => { + it('downloads every ref and reports progress', async () => { + vi.stubGlobal('fetch', vi.fn().mockImplementation(() => blobResponse())); + const refs = [ref('https://example.com/1.png'), ref('https://example.com/2.png')]; + const progress: number[] = []; + const results = await downloadAll(refs, { + concurrency: 2, + onProgress: (p) => progress.push(p.done), + }); + expect(results).toHaveLength(2); + expect(progress).toEqual([1, 2]); + }); + + it('respects the concurrency limit', async () => { + let inFlight = 0; + let maxInFlight = 0; + const fetchMock = vi.fn().mockImplementation(async () => { + inFlight += 1; + maxInFlight = Math.max(maxInFlight, inFlight); + await new Promise((r) => setTimeout(r, 5)); + inFlight -= 1; + return blobResponse(); + }); + vi.stubGlobal('fetch', fetchMock); + + const refs = Array.from({ length: 6 }, (_, i) => + ref(`https://example.com/${i}.png`), + ); + await downloadAll(refs, { concurrency: 2 }); + expect(maxInFlight).toBeLessThanOrEqual(2); + }); +}); \ No newline at end of file diff --git a/packages/extract/src/objects.test.ts b/packages/extract/src/objects.test.ts new file mode 100644 index 0000000..137d419 --- /dev/null +++ b/packages/extract/src/objects.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; +import type { TTSMod, TTSObject } from '@tts/shared'; +import { filterObjects, findObject, flattenObjects } from './objects.js'; + +function card(guid: string, name = 'Card'): TTSObject { + return { Name: name, GUID: guid, Description: '' }; +} + +function bag(guid: string, contained: TTSObject[]): TTSObject { + return { Name: 'Bag', GUID: guid, Description: '', ContainedObjects: contained }; +} + +const mod: TTSMod = { + GameMode: 'Tabletop', + Date: '2024-01-01', + ObjectStates: [ + card('a', 'Deck'), + bag('b', [card('c', 'Card'), card('d', 'Token')]), + card('e', 'Deck'), + ], +}; + +describe('flattenObjects', () => { + it('returns every object including those inside bags', () => { + const guids = flattenObjects(mod).map((o) => o.GUID); + expect(guids).toEqual(['a', 'c', 'd', 'e']); + }); + + it('sets Parent links before returning', () => { + const [a, c] = flattenObjects(mod); + expect(a!.Parent).toBeUndefined(); + expect(c!.Parent!.GUID).toBe('b'); + }); +}); + +describe('filterObjects', () => { + it('filters by predicate', () => { + const decks = filterObjects(mod, (o) => o.Name === 'Deck'); + expect(decks.map((o) => o.GUID)).toEqual(['a', 'e']); + }); +}); + +describe('findObject', () => { + it('finds an object by GUID', () => { + expect(findObject(mod, 'd')?.Name).toBe('Token'); + }); + + it('returns undefined for a missing GUID', () => { + expect(findObject(mod, 'zzz')).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/packages/extract/src/refs.test.ts b/packages/extract/src/refs.test.ts new file mode 100644 index 0000000..a3d2349 --- /dev/null +++ b/packages/extract/src/refs.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, it } from 'vitest'; +import type { TTSMod, TTSObject } from '@tts/shared'; +import { collectRefs, extractRefs } from './refs.js'; + +function card(guid: string): TTSObject { + return { Name: 'Card', GUID: guid, Description: '' }; +} + +describe('extractRefs', () => { + it('extracts a PDF reference', () => { + const o: TTSObject = { + ...card('g1'), + CustomPDF: { PDFUrl: 'https://example.com/rules.pdf' }, + }; + expect(extractRefs(o)).toEqual([ + { kind: 'pdf', url: 'https://example.com/rules.pdf', ownerGuid: 'g1' }, + ]); + }); + + it('extracts deck face and back references', () => { + const o: TTSObject = { + ...card('g2'), + CustomDeck: { + 1: { + FaceURL: 'https://example.com/face.png', + BackURL: 'https://example.com/back.png', + UniqueBack: true, + NumHeight: 1, + NumWidth: 1, + }, + }, + }; + expect(extractRefs(o)).toEqual([ + { kind: 'deckFace', url: 'https://example.com/face.png', ownerGuid: 'g2' }, + { kind: 'deckBack', url: 'https://example.com/back.png', ownerGuid: 'g2' }, + ]); + }); + + it('extracts image and secondary image references', () => { + const o: TTSObject = { + ...card('g3'), + CustomImage: { + ImageURL: 'https://example.com/a.png', + ImageSecondaryURL: 'https://example.com/b.png', + }, + }; + expect(extractRefs(o)).toEqual([ + { kind: 'image', url: 'https://example.com/a.png', ownerGuid: 'g3' }, + { + kind: 'imageSecondary', + url: 'https://example.com/b.png', + ownerGuid: 'g3', + }, + ]); + }); + + it('returns an empty array for an object with no refs', () => { + expect(extractRefs(card('g4'))).toEqual([]); + }); +}); + +describe('collectRefs', () => { + it('collects refs across the whole save', () => { + const mod: TTSMod = { + GameMode: 'Tabletop', + Date: '2024-01-01', + ObjectStates: [ + { ...card('g1'), CustomPDF: { PDFUrl: 'https://example.com/a.pdf' } }, + { + ...card('g2'), + CustomImage: { + ImageURL: 'https://example.com/a.png', + ImageSecondaryURL: 'https://example.com/b.png', + }, + }, + ], + }; + const refs = collectRefs(mod); + expect(refs).toHaveLength(3); + expect(refs.map((r) => r.kind).sort()).toEqual([ + 'image', + 'imageSecondary', + 'pdf', + ]); + }); + + it('dedupes refs by URL', () => { + const mod: TTSMod = { + GameMode: 'Tabletop', + Date: '2024-01-01', + ObjectStates: [ + { ...card('g1'), CustomPDF: { PDFUrl: 'https://example.com/a.pdf' } }, + { ...card('g2'), CustomPDF: { PDFUrl: 'https://example.com/a.pdf' } }, + ], + }; + const refs = collectRefs(mod); + expect(refs).toHaveLength(1); + expect(refs[0]!.ownerGuid).toBe('g1'); + }); +}); \ No newline at end of file diff --git a/packages/extract/src/traverse.test.ts b/packages/extract/src/traverse.test.ts new file mode 100644 index 0000000..2c0f778 --- /dev/null +++ b/packages/extract/src/traverse.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest'; +import type { TTSMod, TTSObject } from '@tts/shared'; +import { markParent, traverseMod } from './traverse.js'; + +function card(guid: string, name = 'Card'): TTSObject { + return { Name: name, GUID: guid, Description: '' }; +} + +function bag(guid: string, contained: TTSObject[]): TTSObject { + return { Name: 'Bag', GUID: guid, Description: '', ContainedObjects: contained }; +} + +const mod: TTSMod = { + GameMode: 'Tabletop', + Date: '2024-01-01', + ObjectStates: [ + card('a'), + bag('b', [card('c'), bag('d', [card('e')])]), + card('f'), + ], +}; + +describe('markParent', () => { + it('sets Parent on direct children', () => { + const root = bag('b', [card('c')]); + markParent(root); + expect(root.ContainedObjects![0]!.Parent).toBe(root); + }); + + it('sets Parent recursively', () => { + const root = bag('b', [bag('d', [card('e')])]); + markParent(root); + const d = root.ContainedObjects![0]!; + expect(d.Parent).toBe(root); + expect(d.ContainedObjects![0]!.Parent).toBe(d); + }); + + it('leaves leaf objects untouched', () => { + const leaf = card('a'); + markParent(leaf); + expect(leaf.Parent).toBeUndefined(); + }); +}); + +describe('traverseMod', () => { + it('yields every non-bag object in order', () => { + const guids = [...traverseMod(mod)].map((o) => o.GUID); + expect(guids).toEqual(['a', 'c', 'e', 'f']); + }); + + it('descends into nested bags', () => { + const guids = [...traverseMod(mod)].map((o) => o.GUID); + expect(guids).toContain('e'); + }); + + it('accepts a single object', () => { + const guids = [...traverseMod(bag('b', [card('c')]))].map((o) => o.GUID); + expect(guids).toEqual(['c']); + }); +}); \ No newline at end of file diff --git a/packages/shared/package.json b/packages/shared/package.json index 290c09a..ed8e636 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -14,6 +14,7 @@ "scripts": { "build": "tsc -p tsconfig.json", "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", "lint": "echo \"no lint configured\"" }, "dependencies": { diff --git a/packages/shared/src/schemas.test.ts b/packages/shared/src/schemas.test.ts new file mode 100644 index 0000000..98257f2 --- /dev/null +++ b/packages/shared/src/schemas.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from 'vitest'; +import { + assetRefSchema, + extractedObjectSchema, + itemIdSchema, + searchQuerySchema, + searchResultSchema, + workshopItemSchema, +} from './schemas.js'; + +describe('itemIdSchema', () => { + it('accepts a run of digits', () => { + expect(itemIdSchema.parse('123456')).toBe('123456'); + }); + + it('rejects empty strings', () => { + expect(itemIdSchema.safeParse('').success).toBe(false); + }); + + it('rejects non-numeric input', () => { + expect(itemIdSchema.safeParse('abc').success).toBe(false); + expect(itemIdSchema.safeParse('12a34').success).toBe(false); + }); +}); + +describe('searchQuerySchema', () => { + it('coerces page to a number and defaults it', () => { + expect(searchQuerySchema.parse({ q: 'cards' })).toEqual({ + q: 'cards', + page: 1, + }); + expect(searchQuerySchema.parse({ q: 'cards', page: '3' })).toEqual({ + q: 'cards', + page: 3, + }); + }); + + it('rejects a missing or blank query', () => { + expect(searchQuerySchema.safeParse({}).success).toBe(false); + expect(searchQuerySchema.safeParse({ q: ' ' }).success).toBe(false); + }); + + it('rejects a non-positive page', () => { + expect(searchQuerySchema.safeParse({ q: 'cards', page: 0 }).success).toBe( + false, + ); + }); +}); + +describe('assetRefSchema', () => { + it('accepts a valid ref', () => { + const ref = { + kind: 'pdf', + url: 'https://example.com/doc.pdf', + ownerGuid: 'abc', + }; + expect(assetRefSchema.parse(ref)).toEqual(ref); + }); + + it('rejects an invalid URL', () => { + expect( + assetRefSchema.safeParse({ + kind: 'pdf', + url: 'not-a-url', + ownerGuid: 'abc', + }).success, + ).toBe(false); + }); + + it('rejects an unknown kind', () => { + expect( + assetRefSchema.safeParse({ + kind: 'video', + url: 'https://example.com/a.mp4', + ownerGuid: 'abc', + }).success, + ).toBe(false); + }); +}); + +describe('extractedObjectSchema', () => { + it('accepts a minimal object', () => { + const obj = { + guid: 'g1', + name: 'Card', + type: 'Card', + childrenGuids: [], + refs: [], + }; + expect(extractedObjectSchema.parse(obj)).toEqual(obj); + }); + + it('accepts optional parentGuid', () => { + const obj = { + guid: 'g1', + name: 'Card', + type: 'Card', + parentGuid: 'g0', + childrenGuids: ['g2'], + refs: [], + }; + expect(extractedObjectSchema.parse(obj).parentGuid).toBe('g0'); + }); +}); + +describe('workshopItemSchema', () => { + it('accepts a full item', () => { + const item = { + id: '1', + title: 'Deck', + author: 'someone', + previewImageUrl: 'https://example.com/p.png', + tags: ['card'], + timeCreated: 123, + }; + expect(workshopItemSchema.parse(item)).toEqual(item); + }); + + it('accepts a minimal item', () => { + const item = { + id: '1', + title: 'Deck', + author: 'someone', + previewImageUrl: 'https://example.com/p.png', + }; + expect(workshopItemSchema.parse(item)).toEqual(item); + }); +}); + +describe('searchResultSchema', () => { + it('accepts an empty result set', () => { + const result = { items: [], page: 1, hasMore: false }; + expect(searchResultSchema.parse(result)).toEqual(result); + }); +}); \ No newline at end of file diff --git a/packages/tts/package.json b/packages/tts/package.json index cb4923c..29bed8d 100644 --- a/packages/tts/package.json +++ b/packages/tts/package.json @@ -14,6 +14,7 @@ "scripts": { "build": "tsc -p tsconfig.json", "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", "lint": "echo \"no lint configured\"" }, "dependencies": { diff --git a/packages/tts/src/index.test.ts b/packages/tts/src/index.test.ts new file mode 100644 index 0000000..2b5163e --- /dev/null +++ b/packages/tts/src/index.test.ts @@ -0,0 +1,56 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { getFileName } from './index.js'; +import { ItemNotFoundError, NoFileError, SteamApiError, TtsError } from './errors.js'; + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('getFileName', () => { + it('parses a quoted content-disposition filename', () => { + expect( + getFileName('https://example.com/save', 'attachment; filename="mod.json"'), + ).toBe('mod.json'); + }); + + it('parses an unquoted filename', () => { + expect( + getFileName('https://example.com/save', 'attachment; filename=mod.json'), + ).toBe('mod.json'); + }); + + it('falls back to the URL path when there is no disposition', () => { + expect(getFileName('https://example.com/files/mod.json', null)).toBe( + 'mod.json', + ); + }); + + it('falls back to a default when the URL has no path', () => { + expect(getFileName('https://example.com', null)).toBe('save.json'); + }); +}); + +describe('error classes', () => { + it('ItemNotFoundError carries a 404 status', () => { + const err = new ItemNotFoundError('123'); + expect(err).toBeInstanceOf(TtsError); + expect(err.status).toBe(404); + expect(err.message).toContain('123'); + }); + + it('NoFileError carries a 404 status', () => { + const err = new NoFileError('123'); + expect(err.status).toBe(404); + expect(err.message).toContain('123'); + }); + + it('SteamApiError defaults to a 502 status', () => { + const err = new SteamApiError('boom'); + expect(err.status).toBe(502); + }); + + it('TtsError defaults to a 500 status', () => { + const err = new TtsError('boom'); + expect(err.status).toBe(500); + }); +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a0e816..cd14a07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: typescript: specifier: ^5.7.2 version: 5.9.3 + vitest: + specifier: ^4.1.10 + version: 4.1.10(@types/node@22.20.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11)) apps/proxy: dependencies: @@ -237,18 +240,192 @@ packages: peerDependencies: hono: ^4 + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@oxc-project/types@0.143.0': + resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} + + '@rolldown/binding-android-arm64@1.2.3': + resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.3': + resolution: {integrity: sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.3': + resolution: {integrity: sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.3': + resolution: {integrity: sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': + resolution: {integrity: sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.3': + resolution: {integrity: sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.3': + resolution: {integrity: sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.3': + resolution: {integrity: sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.3': + resolution: {integrity: sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.3': + resolution: {integrity: sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.3': + resolution: {integrity: sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.3': + resolution: {integrity: sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.3': + resolution: {integrity: sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.3': + resolution: {integrity: sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/node@22.20.1': resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + bson@6.10.4: resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} engines: {node: '>=16.20.1'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -258,6 +435,139 @@ packages: resolution: {integrity: sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==} engines: {node: '>=16.9.0'} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + rolldown@1.2.3: + resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + tsx@4.23.11: resolution: {integrity: sha512-Ry2oTEUnhBdeEdWIztY8kf3/nBGnPnjMLVGL0YfdRXMORuPER5NlKmayqxtxRxwB1xBN+RivRaJfe7PM1rtiyw==} engines: {node: '>=18.0.0'} @@ -271,6 +581,95 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + vite@8.2.1: + resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -358,12 +757,122 @@ snapshots: dependencies: hono: 4.13.1 + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@oxc-project/types@0.143.0': {} + + '@rolldown/binding-android-arm64@1.2.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.3': + optional: true + + '@rolldown/binding-darwin-x64@1.2.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.3': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + '@types/node@22.20.1': dependencies: undici-types: 6.21.0 + '@vitest/expect@4.1.10': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.10(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11) + + '@vitest/pretty-format@4.1.10': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.10': + dependencies: + '@vitest/utils': 4.1.10 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.10': {} + + '@vitest/utils@4.1.10': + dependencies: + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + + assertion-error@2.0.1: {} + bson@6.10.4: {} + chai@6.2.2: {} + + convert-source-map@2.0.0: {} + + detect-libc@2.1.2: {} + + es-module-lexer@2.3.1: {} + esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -393,11 +902,129 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.4.0: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + fsevents@2.3.3: optional: true hono@4.13.1: {} + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + nanoid@3.3.18: {} + + obug@2.1.4: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rolldown@1.2.3: + dependencies: + '@oxc-project/types': 0.143.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.3 + '@rolldown/binding-darwin-arm64': 1.2.3 + '@rolldown/binding-darwin-x64': 1.2.3 + '@rolldown/binding-freebsd-x64': 1.2.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.3 + '@rolldown/binding-linux-arm64-gnu': 1.2.3 + '@rolldown/binding-linux-arm64-musl': 1.2.3 + '@rolldown/binding-linux-ppc64-gnu': 1.2.3 + '@rolldown/binding-linux-s390x-gnu': 1.2.3 + '@rolldown/binding-linux-x64-gnu': 1.2.3 + '@rolldown/binding-linux-x64-musl': 1.2.3 + '@rolldown/binding-openharmony-arm64': 1.2.3 + '@rolldown/binding-win32-arm64-msvc': 1.2.3 + '@rolldown/binding-win32-x64-msvc': 1.2.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.2.0: {} + + tinybench@2.9.0: {} + + tinyexec@1.3.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinyrainbow@3.1.1: {} + tsx@4.23.11: dependencies: esbuild: 0.28.1 @@ -408,4 +1035,49 @@ snapshots: undici-types@6.21.0: {} + vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.26 + rolldown: 1.2.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.20.1 + esbuild: 0.28.1 + fsevents: 2.3.3 + tsx: 4.23.11 + + vitest@4.1.10(@types/node@22.20.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.2.1(@types/node@22.20.1)(esbuild@0.28.1)(tsx@4.23.11) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.1 + transitivePeerDependencies: + - msw + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + zod@3.25.76: {} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..83d31ff --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['packages/*/src/**/*.test.ts', 'apps/*/src/**/*.test.ts'], + }, +}); \ No newline at end of file