diff --git a/job-results/crypto.test.ts b/job-results/crypto.test.ts new file mode 100644 index 0000000..27d115d --- /dev/null +++ b/job-results/crypto.test.ts @@ -0,0 +1,144 @@ +import { describe, it, expect } from 'vitest' +import { readPublicKey, readPrivateKey } from '../testing' +import { fingerprintKeyData, pemToArrayBuffer, generateKeyPair } from '../util' +import { ResultsWriter } from './writer' +import { ResultsReader } from './reader' +import { unwrapAesKey, wrapAesKey, decryptFile } from './crypto' + +const toArrayBuffer = (str: string): ArrayBuffer => { + const buf = Buffer.from(str, 'utf-8') + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) +} + +describe('wrapAesKey / unwrapAesKey', () => { + it('round-trips a raw AES key through an RSA keypair', async () => { + const publicKey = pemToArrayBuffer(readPublicKey()) + const privateKey = pemToArrayBuffer(readPrivateKey()) + + const aesKey = await crypto.subtle.generateKey({ name: 'AES-CBC', length: 256 }, true, ['encrypt']) + const rawAesKey = await crypto.subtle.exportKey('raw', aesKey) + + const crypt = await wrapAesKey(rawAesKey, publicKey) + const { rawAesKey: unwrapped } = await unwrapAesKey(crypt, privateKey) + + expect(new Uint8Array(unwrapped)).toEqual(new Uint8Array(rawAesKey)) + }) +}) + +describe('decryptFile', () => { + it('decrypts a standalone body + metadata and returns the raw AES key', async () => { + const publicKey = pemToArrayBuffer(readPublicKey()) + const privateKey = pemToArrayBuffer(readPrivateKey()) + + // Encrypt a body with AES-CBC, then RSA-wrap the AES key. + const aesKey = await crypto.subtle.generateKey({ name: 'AES-CBC', length: 256 }, true, ['encrypt']) + const rawAesKey = await crypto.subtle.exportKey('raw', aesKey) + const iv = crypto.getRandomValues(new Uint8Array(16)) + const plaintext = toArrayBuffer('secret,data') + const body = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, aesKey, plaintext) + const crypt = await wrapAesKey(rawAesKey, publicKey) + + const { contents, rawAesKey: recovered } = await decryptFile({ + body, + iv: Buffer.from(iv).toString('base64'), + crypt, + privateKey, + }) + + expect(new TextDecoder().decode(contents)).toBe('secret,data') + expect(new Uint8Array(recovered)).toEqual(new Uint8Array(rawAesKey)) + }) +}) + +describe('ResultsReader override keys (researcher re-wrap)', () => { + it('lets a researcher decrypt with a re-wrapped key absent from the manifest', async () => { + // Data owner encrypts results for their own key only. + const doPublic = pemToArrayBuffer(readPublicKey()) + const doFingerprint = await fingerprintKeyData(doPublic) + const doPrivate = pemToArrayBuffer(readPrivateKey()) + + const writer = new ResultsWriter([{ publicKey: doPublic, fingerprint: doFingerprint }]) + await writer.addFile('result.csv', toArrayBuffer('secret,data')) + const zip = await writer.generate() + + // Reviewer reads with their manifest key and recovers each file's raw AES key. + const reviewer = new ResultsReader(zip, doPrivate, doFingerprint) + const [entry] = await reviewer.extractFilesWithKeys() + expect(new TextDecoder().decode(entry.contents)).toBe('secret,data') + expect(entry.rawAesKey.byteLength).toBe(32) + + // Researcher: brand-new keypair, NOT an original manifest recipient. + const researcher = await generateKeyPair() + const researcherFp = await fingerprintKeyData(researcher.exportedPublicKey) + const crypt = await wrapAesKey(entry.rawAesKey, researcher.exportedPublicKey) + + // Same ciphertext + IV; only the wrapped key differs, supplied as an override. + const reader = new ResultsReader(zip, researcher.exportedPrivateKey, researcherFp, { + 'result.csv': crypt, + }) + const [out] = await reader.extractFiles() + expect(new TextDecoder().decode(out.contents)).toBe('secret,data') + }) + + it('throws cleanly when an override is wrapped for a different keypair', async () => { + const doPublic = pemToArrayBuffer(readPublicKey()) + const doFingerprint = await fingerprintKeyData(doPublic) + + const writer = new ResultsWriter([{ publicKey: doPublic, fingerprint: doFingerprint }]) + await writer.addFile('result.csv', toArrayBuffer('secret,data')) + const zip = await writer.generate() + + const reviewer = new ResultsReader(zip, pemToArrayBuffer(readPrivateKey()), doFingerprint) + const [entry] = await reviewer.extractFilesWithKeys() + + // Wrap the raw key for researcher A, but hand the reader researcher B's private key. + const researcherA = await generateKeyPair() + const researcherB = await generateKeyPair() + const crypt = await wrapAesKey(entry.rawAesKey, researcherA.exportedPublicKey) + + const reader = new ResultsReader(zip, researcherB.exportedPrivateKey, researcherB.fingerprint, { + 'result.csv': crypt, + }) + // RSA-OAEP unwrap fails on the mismatched key — deterministic throw, never garbage plaintext. + await expect(reader.extractFiles()).rejects.toThrow() + }) + + it('throws when a manifest file has neither a fingerprint match nor an override', async () => { + const doPublic = pemToArrayBuffer(readPublicKey()) + const doFingerprint = await fingerprintKeyData(doPublic) + + const writer = new ResultsWriter([{ publicKey: doPublic, fingerprint: doFingerprint }]) + await writer.addFile('a.csv', toArrayBuffer('alpha')) + await writer.addFile('b.csv', toArrayBuffer('beta')) + const zip = await writer.generate() + + const researcher = await generateKeyPair() + // Override supplied for a.csv only; b.csv has no key for this researcher. + const decrypted = await new ResultsReader( + zip, + pemToArrayBuffer(readPrivateKey()), + doFingerprint, + ).extractFilesWithKeys() + const aEntry = decrypted.find((e) => e.path === 'a.csv')! + const crypt = await wrapAesKey(aEntry.rawAesKey, researcher.exportedPublicKey) + + const reader = new ResultsReader(zip, researcher.exportedPrivateKey, researcher.fingerprint, { + 'a.csv': crypt, + }) + await expect(reader.extractFiles()).rejects.toThrow(/key signature/) + }) + + it('still reads with the manifest fingerprint when no override is supplied', async () => { + const publicKey = pemToArrayBuffer(readPublicKey()) + const fingerprint = await fingerprintKeyData(publicKey) + const privateKey = pemToArrayBuffer(readPrivateKey()) + + const writer = new ResultsWriter([{ publicKey, fingerprint }]) + await writer.addFile('a.txt', toArrayBuffer('alpha')) + const zip = await writer.generate() + + const reader = new ResultsReader(zip, privateKey, fingerprint) + const [out] = await reader.extractFiles() + expect(new TextDecoder().decode(out.contents)).toBe('alpha') + }) +}) diff --git a/job-results/crypto.ts b/job-results/crypto.ts new file mode 100644 index 0000000..c45ef57 --- /dev/null +++ b/job-results/crypto.ts @@ -0,0 +1,65 @@ +import { privateKeyFromBuffer } from '../util' +import logger from '../lib/logger' + +/** + * Unwrap a file's RSA-encrypted AES key. Returns the AES `CryptoKey` plus its raw + * bytes — re-wrap needs the bytes to grant other recipients access (see {@link wrapAesKey}). + */ +export async function unwrapAesKey( + crypt: string, + privateKey: ArrayBuffer, +): Promise<{ aesKey: CryptoKey; rawAesKey: ArrayBuffer }> { + const encryptedKey = Buffer.from(crypt, 'base64') + + const rawAesKey = await crypto.subtle.decrypt( + { name: 'RSA-OAEP' }, + await privateKeyFromBuffer(privateKey), + encryptedKey, + ) + + const aesKey = await crypto.subtle.importKey('raw', rawAesKey, { name: 'AES-CBC' }, false, ['decrypt']) + + return { aesKey, rawAesKey } +} + +/** Re-wrap a raw AES key for a recipient's RSA public key. Body and IV are untouched. */ +export async function wrapAesKey(rawAesKey: ArrayBuffer, publicKey: ArrayBuffer): Promise { + const key = await crypto.subtle.importKey('spki', publicKey, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, [ + 'encrypt', + ]) + + const encryptedKey = await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, key, rawAesKey) + + return Buffer.from(encryptedKey).toString('base64') +} + +// AES-CBC kept for backward-compat with existing production results (see writer.addFile). +// CBC is unauthenticated: a wrong-but-valid key usually trips PKCS#7 padding and throws, but not +// guaranteed (~1/256 yields garbage, no error), and tampered ciphertext is not detected. +export async function decryptFileBody(body: ArrayBuffer, iv: BufferSource, aesKey: CryptoKey): Promise { + return crypto.subtle.decrypt({ name: 'AES-CBC', iv }, aesKey, body) +} + +/** + * Decrypt a standalone file body + metadata (vs {@link ResultsReader}'s zip iteration). + * Returns the raw AES key too, so the caller can re-wrap without decrypting again. + */ +export async function decryptFile({ + body, + iv, + crypt, + privateKey, +}: { + body: ArrayBuffer + iv: string + crypt: string + privateKey: ArrayBuffer +}): Promise<{ contents: ArrayBuffer; rawAesKey: ArrayBuffer }> { + logger.info(`Decrypting file`) + + const { aesKey, rawAesKey } = await unwrapAesKey(crypt, privateKey) + const contents = await decryptFileBody(body, Buffer.from(iv, 'base64'), aesKey) + + logger.info(`Finished decrypting file`) + return { contents, rawAesKey } +} diff --git a/job-results/index.ts b/job-results/index.ts index 4315afd..0e4122c 100644 --- a/job-results/index.ts +++ b/job-results/index.ts @@ -1,3 +1,4 @@ export * from './reader' export * from './writer' +export * from './crypto' export type { FileInfo } from './types' diff --git a/job-results/reader.ts b/job-results/reader.ts index 7e721f4..a221c33 100644 --- a/job-results/reader.ts +++ b/job-results/reader.ts @@ -1,9 +1,11 @@ import { BlobReader, BlobWriter, TextWriter, ZipReader } from '@zip.js/zip.js' import type { FileEntry as ZipFileEntry } from '@zip.js/zip.js' import type { ResultsFile, ResultsManifest, FileEntry, FileInfo } from './types' -import { privateKeyFromBuffer } from '../util' +import { decryptFileBody, unwrapAesKey } from './crypto' import logger from '../lib/logger' +export type DecryptedEntry = FileEntry & { rawAesKey: ArrayBuffer } + export class ResultsReader { manifest: ResultsManifest = { files: {}, @@ -12,25 +14,44 @@ export class ResultsReader { private zipReader: ZipReader private fingerprint: string private privateKey: ArrayBuffer + private readonly additionalKeys: Record private decoded = false - constructor(zipBlob: Blob, privateKey: ArrayBuffer, fingerprint: string) { + /** + * @param additionalKeys inner file path -> AES key wrapped for *this* `fingerprint`. Lets a + * recipient absent from the original manifest (e.g. a researcher granted access later) + * decrypt the same ciphertext: on decode these are spliced into the manifest under + * `fingerprint`, so reads stay a single fingerprint lookup with no bypass. + */ + constructor( + zipBlob: Blob, + privateKey: ArrayBuffer, + fingerprint: string, + additionalKeys: Record = {}, + ) { this.zipReader = new ZipReader(new BlobReader(zipBlob)) this.fingerprint = fingerprint this.privateKey = privateKey + this.additionalKeys = additionalKeys + } + + async extractFiles(): Promise { + const entries = await this.extractFilesWithKeys() + return entries.map(({ path, contents }) => ({ path, contents })) } - async extractFiles() { + /** Like {@link extractFiles}, but also returns each file's raw AES key for re-wrapping. */ + async extractFilesWithKeys(): Promise { logger.info(`Extracting files`) await this.decode() - const generator = this.entries() - const entries: FileEntry[] = [] - for await (const entry of generator) { + const entries: DecryptedEntry[] = [] + for await (const entry of this.entries()) { entries.push({ path: entry.path, contents: entry.contents, + rawAesKey: entry.rawAesKey, }) } logger.info(`Finished extracting files`) @@ -43,17 +64,26 @@ export class ResultsReader { logger.info(`Decoding entries`) const entries = await this.zipReader.getEntries() + let manifestFound = false for (const entry of entries) { - if (!entry.directory && entry.filename == 'manifest.json') { + if (!entry.directory && entry.filename === 'manifest.json') { const manifestText = await entry.getData(new TextWriter()) this.manifest = JSON.parse(manifestText) as ResultsManifest + manifestFound = true } } - if (!this.manifest) { + if (!manifestFound) { throw new Error('Manifest not found in zip archive.') } + // Splice any caller-supplied keys into the manifest under our fingerprint, so a recipient + // not baked into the zip (e.g. a researcher) reads through the same path as everyone else. + for (const [path, crypt] of Object.entries(this.additionalKeys)) { + const file = this.manifest.files[path] + if (file) file.keys[this.fingerprint] = { crypt } + } + this.decoded = true logger.info(`Finished decoding entries`) } @@ -77,72 +107,40 @@ export class ResultsReader { throw new Error(`File not found in zip archive: ${filePath}`) } - const contents = await this.readFile(file, entry) + const { contents } = await this.readFile(file, entry) return { path: filePath, contents } } - async *entries(): AsyncGenerator { + async *entries(): AsyncGenerator { const entries = await this.zipReader.getEntries() for (const entry of entries) { const file = this.manifest.files[entry.filename] if (!entry.directory && file) { - const contents = await this.readFile(file, entry) - yield { ...file, contents } + const { contents, rawAesKey } = await this.readFile(file, entry) + yield { ...file, contents, rawAesKey } } } } - private async readFile(fileEntry: ResultsFile, entry: ZipFileEntry): Promise { + private async readFile( + fileEntry: ResultsFile, + entry: ZipFileEntry, + ): Promise<{ contents: ArrayBuffer; rawAesKey: ArrayBuffer }> { logger.info(`Reading file ${entry.filename}`) const encryptedData = await entry.getData(new BlobWriter()) - const encryptionKey = fileEntry.keys[this.fingerprint] - if (!encryptionKey) throw new Error(`file was not encrypted with key signature ${this.fingerprint}`) + const crypt = fileEntry.keys[this.fingerprint]?.crypt + if (!crypt) throw new Error(`file was not encrypted with key signature ${this.fingerprint}`) - const aesKey = await this.decryptKeyWithPrivateKey(encryptionKey.crypt) - - const iv = Buffer.from(fileEntry.iv, 'base64') + const { aesKey, rawAesKey } = await unwrapAesKey(crypt, this.privateKey) logger.info(`Finished reading file ${entry.filename}`) - return this.decryptData(encryptedData, aesKey, iv) - } - - private async decryptKeyWithPrivateKey(encryptedKeyBase64: string): Promise { - logger.info(`Decrypting key`) - - const encryptedKey = Buffer.from(encryptedKeyBase64, 'base64') - - const rawKey = await crypto.subtle.decrypt( - { - name: 'RSA-OAEP', - }, - await privateKeyFromBuffer(this.privateKey), - encryptedKey, - ) - - const key = await crypto.subtle.importKey('raw', rawKey, { name: 'AES-CBC' }, false, ['decrypt']) - - logger.info(`Finished decrypting key`) - - return key - } - - private async decryptData(encryptedData: Blob, aesKey: CryptoKey, iv: BufferSource): Promise { - logger.info(`Decrypting data`) - - const arrayBuffer = await encryptedData.arrayBuffer() - const results = crypto.subtle.decrypt( - { - name: 'AES-CBC', - iv, - }, + const contents = await decryptFileBody( + await encryptedData.arrayBuffer(), + Buffer.from(fileEntry.iv, 'base64'), aesKey, - arrayBuffer, ) - - logger.info(`Finished decrypting data`) - - return results + return { contents, rawAesKey } } } diff --git a/job-results/writer.ts b/job-results/writer.ts index 8abd83c..37578d1 100644 --- a/job-results/writer.ts +++ b/job-results/writer.ts @@ -1,6 +1,7 @@ import { ZipWriter, BlobWriter, TextReader, BlobReader } from '@zip.js/zip.js' import type { ResultsManifest, PublicKey, FileKeyMap } from './types' +import { wrapAesKey } from './crypto' import logger from '../lib/logger' export class ResultsWriter { @@ -15,7 +16,10 @@ export class ResultsWriter { async addFile(fileName: string, content: ArrayBuffer) { logger.info(`Adding file ${fileName} to manifest`) - // Generate AES key + // AES-CBC (not GCM) for backward-compat: existing production results are CBC-encrypted, + // and the cipher isn't stamped in the manifest, so switching would orphan that data. + // NOTE: CBC is unauthenticated — ciphertext/IV integrity is NOT verified on decrypt + // (SonarQube S5542). Callers must not treat decrypted bodies as tamper-proof. const aesKey = await crypto.subtle.generateKey({ name: 'AES-CBC', length: 256 }, true, ['encrypt']) // Generate random IV @@ -30,7 +34,7 @@ export class ResultsWriter { const keys: FileKeyMap = {} for (const key of this.publicKeys) { keys[key.fingerprint] = { - crypt: await this.encryptAesKeyWithPublicKey(key, rawAesKey), + crypt: await wrapAesKey(rawAesKey, key.publicKey), } } @@ -54,32 +58,4 @@ export class ResultsWriter { logger.info(`Finished adding manifest.json to zip`) return this.zipBlobWriter.getData() } - - private async encryptAesKeyWithPublicKey(key: PublicKey, aesKey: ArrayBuffer): Promise { - logger.info(`Encrypting AES key`) - - // Decode the public key - const publicKey = await crypto.subtle.importKey( - 'spki', - key.publicKey, - { - name: 'RSA-OAEP', - hash: 'SHA-256', - }, - false, - ['encrypt'], - ) - - // Encrypt the AES key - const encryptedKey = await crypto.subtle.encrypt( - { - name: 'RSA-OAEP', - }, - publicKey, - aesKey, - ) - - logger.info(`Finished encrypting AES key`) - return Buffer.from(encryptedKey).toString('base64') - } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cc91c0..694acc5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + esbuild: '>=0.28.1' + vite: 8.0.16 + ws: '>=8.21.0' + importers: .: @@ -50,7 +55,7 @@ importers: version: 8.59.3(eslint@10.3.0)(typescript@6.0.3) vitest: specifier: ^4.1.5 - version: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)) + version: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(vite@8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0)) packages: @@ -88,158 +93,158 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -331,100 +336,100 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.129.0': - resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} - '@rolldown/binding-android-arm64@1.0.0': - resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0': - resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0': - resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0': - resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0': - resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0': - resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0': - resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0': - resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0': - resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0': - resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0': - resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0': - resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0': - resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0': - resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0': - resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -549,7 +554,7 @@ packages: resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: 8.0.16 peerDependenciesMeta: msw: optional: true @@ -681,8 +686,8 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true @@ -1098,8 +1103,8 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -1141,8 +1146,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.0.0: - resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -1210,6 +1215,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -1265,14 +1274,14 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vite@8.0.12: - resolution: {integrity: sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==} + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 '@vitejs/devtools': ^0.1.18 - esbuild: ^0.27.0 || ^0.28.0 + esbuild: '>=0.28.1' jiti: '>=1.21.0' less: ^4.0.0 sass: ^1.70.0 @@ -1324,7 +1333,7 @@ packages: '@vitest/ui': 4.1.6 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + vite: 8.0.16 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -1367,8 +1376,8 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1429,82 +1438,82 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.7': + '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/android-arm64@0.27.7': + '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.27.7': + '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-x64@0.27.7': + '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.7': + '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.7': + '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.7': + '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.7': + '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.7': + '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/linux-arm@0.27.7': + '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/linux-ia32@0.27.7': + '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/linux-loong64@0.27.7': + '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/linux-mips64el@0.27.7': + '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/linux-ppc64@0.27.7': + '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/linux-riscv64@0.27.7': + '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/linux-s390x@0.27.7': + '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/linux-x64@0.27.7': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/netbsd-arm64@0.27.7': + '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/netbsd-x64@0.27.7': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/openbsd-arm64@0.27.7': + '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/openbsd-x64@0.27.7': + '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/openharmony-arm64@0.27.7': + '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/sunos-x64@0.27.7': + '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/win32-arm64@0.27.7': + '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/win32-ia32@0.27.7': + '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/win32-x64@0.27.7': + '@esbuild/win32-x64@0.28.1': optional: true '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)': @@ -1585,55 +1594,55 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@oxc-project/types@0.129.0': {} + '@oxc-project/types@0.133.0': {} - '@rolldown/binding-android-arm64@1.0.0': + '@rolldown/binding-android-arm64@1.0.3': optional: true - '@rolldown/binding-darwin-arm64@1.0.0': + '@rolldown/binding-darwin-arm64@1.0.3': optional: true - '@rolldown/binding-darwin-x64@1.0.0': + '@rolldown/binding-darwin-x64@1.0.3': optional: true - '@rolldown/binding-freebsd-x64@1.0.0': + '@rolldown/binding-freebsd-x64@1.0.3': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0': + '@rolldown/binding-linux-arm64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0': + '@rolldown/binding-linux-arm64-musl@1.0.3': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0': + '@rolldown/binding-linux-ppc64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0': + '@rolldown/binding-linux-s390x-gnu@1.0.3': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0': + '@rolldown/binding-linux-x64-gnu@1.0.3': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0': + '@rolldown/binding-linux-x64-musl@1.0.3': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0': + '@rolldown/binding-openharmony-arm64@1.0.3': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0': + '@rolldown/binding-wasm32-wasi@1.0.3': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0': + '@rolldown/binding-win32-arm64-msvc@1.0.3': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0': + '@rolldown/binding-win32-x64-msvc@1.0.3': optional: true '@rolldown/pluginutils@1.0.0': {} @@ -1781,7 +1790,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)) + vitest: 4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(vite@8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0)) '@vitest/expect@4.1.6': dependencies: @@ -1792,13 +1801,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.6(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0))': + '@vitest/mocker@4.1.6(vite@8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0))': dependencies: '@vitest/spy': 4.1.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0) + vite: 8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0) '@vitest/pretty-format@4.1.6': dependencies: @@ -1914,34 +1923,34 @@ snapshots: es-module-lexer@2.1.0: {} - esbuild@0.27.7: + esbuild@0.28.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 escape-string-regexp@4.0.0: {} @@ -2094,7 +2103,7 @@ snapshots: '@types/ws': 8.18.1 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.20.1 + ws: 8.21.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -2337,7 +2346,7 @@ snapshots: picomatch@4.0.4: {} - postcss@8.5.14: + postcss@8.5.15: dependencies: nanoid: 3.3.12 picocolors: 1.1.1 @@ -2375,26 +2384,26 @@ snapshots: reusify@1.1.0: {} - rolldown@1.0.0: + rolldown@1.0.3: dependencies: - '@oxc-project/types': 0.129.0 + '@oxc-project/types': 0.133.0 '@rolldown/pluginutils': 1.0.0 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0 - '@rolldown/binding-darwin-arm64': 1.0.0 - '@rolldown/binding-darwin-x64': 1.0.0 - '@rolldown/binding-freebsd-x64': 1.0.0 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 - '@rolldown/binding-linux-arm64-gnu': 1.0.0 - '@rolldown/binding-linux-arm64-musl': 1.0.0 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0 - '@rolldown/binding-linux-s390x-gnu': 1.0.0 - '@rolldown/binding-linux-x64-gnu': 1.0.0 - '@rolldown/binding-linux-x64-musl': 1.0.0 - '@rolldown/binding-openharmony-arm64': 1.0.0 - '@rolldown/binding-wasm32-wasi': 1.0.0 - '@rolldown/binding-win32-arm64-msvc': 1.0.0 - '@rolldown/binding-win32-x64-msvc': 1.0.0 + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 run-parallel@1.2.0: dependencies: @@ -2447,6 +2456,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinyrainbow@3.1.0: {} to-regex-range@5.0.1: @@ -2464,7 +2478,7 @@ snapshots: tsx@4.21.0: dependencies: - esbuild: 0.27.7 + esbuild: 0.28.1 get-tsconfig: 4.14.0 optionalDependencies: fsevents: 2.3.3 @@ -2499,23 +2513,23 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0): + vite@8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.14 - rolldown: 1.0.0 - tinyglobby: 0.2.16 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.12.4 - esbuild: 0.27.7 + esbuild: 0.28.1 fsevents: 2.3.3 tsx: 4.21.0 - vitest@4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)): + vitest@4.1.6(@types/node@24.12.4)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(vite@8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0)): dependencies: '@vitest/expect': 4.1.6 - '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)) + '@vitest/mocker': 4.1.6(vite@8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0)) '@vitest/pretty-format': 4.1.6 '@vitest/runner': 4.1.6 '@vitest/snapshot': 4.1.6 @@ -2532,7 +2546,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0) + vite: 8.0.16(@types/node@24.12.4)(esbuild@0.28.1)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.12.4 @@ -2554,7 +2568,7 @@ snapshots: word-wrap@1.2.5: {} - ws@8.20.1: {} + ws@8.21.0: {} yallist@4.0.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ebb8150..29ad276 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,7 @@ allowBuilds: esbuild: true minimumReleaseAge: 2880 +overrides: + esbuild: '>=0.28.1' + vite: '8.0.16' + ws: '>=8.21.0'