diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index db92239..50ff79d 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -4,3 +4,4 @@ github: [KindSpells, castarco] open_collective: kindspells-labs +polar: kindspells diff --git a/.github/workflows/npm_publish.yml b/.github/workflows/npm_publish.yml index 7c00219..fdbf4c9 100644 --- a/.github/workflows/npm_publish.yml +++ b/.github/workflows/npm_publish.yml @@ -25,7 +25,7 @@ jobs: - name: Install PNPM # v3.0.0 uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d with: - version: '9.10.0' + version: '9.11.0' - name: Use Node.js ${{ matrix.node-version }} # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e999df8..0d29c7e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: - name: Install PNPM # v3.0.0 uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d with: - version: '9.10.0' + version: '9.11.0' - name: Use Node.js ${{ matrix.node-version }} # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 with: diff --git a/.moon/toolchain.yml b/.moon/toolchain.yml index 256a399..a2c064d 100644 --- a/.moon/toolchain.yml +++ b/.moon/toolchain.yml @@ -5,4 +5,4 @@ node: packageManager: 'pnpm' pnpm: - version: 9.10.0 + version: 9.11.0 diff --git a/.node-version b/.node-version index 9d67327..728f7de 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.8.0 +22.9.0 diff --git a/@kindspells/astro-shield/package.json b/@kindspells/astro-shield/package.json index 7ea42f1..5ea4c4c 100644 --- a/@kindspells/astro-shield/package.json +++ b/@kindspells/astro-shield/package.json @@ -1,6 +1,6 @@ { "name": "@kindspells/astro-shield", - "version": "1.5.3", + "version": "1.6.0", "description": "Astro integration to enhance your website's security with SubResource Integrity hashes, Content-Security-Policy headers, and other techniques.", "private": false, "type": "module", @@ -63,14 +63,14 @@ "astro": "^4.0.0" }, "devDependencies": { - "@types/node": "^22.5.5", - "astro": "^4.15.8", + "@types/node": "^22.7.4", + "astro": "^4.15.9", "get-tsconfig": "^4.8.1", - "rollup": "^4.22.0", + "rollup": "^4.22.5", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "^6.1.1", "typescript": "^5.6.2", - "vite": "^5.4.6", + "vite": "^5.4.8", "vitest": "^2.1.1" }, "repository": { @@ -89,7 +89,7 @@ "url": "https://ko-fi.com/coderspirit" } ], - "packageManager": "pnpm@9.10.0", + "packageManager": "pnpm@9.11.0", "engines": { "node": ">= 18.0.0" }, diff --git a/@kindspells/astro-shield/src/core.mts b/@kindspells/astro-shield/src/core.mts index e9c7433..80f9616 100644 --- a/@kindspells/astro-shield/src/core.mts +++ b/@kindspells/astro-shield/src/core.mts @@ -16,6 +16,7 @@ import { doesFileExist, scanDirectory } from './fs.mts' import { patchHeaders } from './headers.mts' import type { HashesCollection, + IntegrationState, Logger, MiddlewareHashes, PerPageHashes, @@ -25,6 +26,9 @@ import type { } from './types.mts' import { patchNetlifyHeadersConfig } from './netlify.mts' import { exhaustiveGuard } from './utils.mts' +import { patchVercelHeadersConfig } from './vercel.mts' + +type AstroHooks = AstroIntegration['hooks'] export type HashesModule = { [k in keyof HashesCollection]: HashesCollection[k] extends Set @@ -741,7 +745,7 @@ const newHashesCollection = (): HashesCollection => ({ // TODO: TEST CASE! export const processStaticFiles = async ( logger: Logger, - { distDir, sri, securityHeaders }: StrictShieldOptions, + { state, distDir, sri, securityHeaders }: StrictShieldOptions, ): Promise => { const h = newHashesCollection() @@ -761,23 +765,23 @@ export const processStaticFiles = async ( const provider = securityHeaders.enableOnStaticPages.provider switch (provider) { case 'netlify': { - if ( - (securityHeaders.enableOnStaticPages.mode ?? '_headers') !== - '_headers' - ) { - throw new Error( - 'Netlify provider only supports "_headers" mode for now', - ) - } await patchNetlifyHeadersConfig( resolve(distDir, '_headers'), securityHeaders, - h, + h.perPageSriHashes, + ) + break + } + case 'vercel': { + await patchVercelHeadersConfig( + logger, + distDir, + state.config, + securityHeaders, + h.perPageSriHashes, ) break } - case 'vercel': - throw new Error('Vercel provider is still not supported') default: exhaustiveGuard(provider, 'provider') } @@ -992,29 +996,61 @@ export const getViteMiddlewarePlugin = ( } } +const getAstroBuildDone = ( + state: IntegrationState, + sri: Required, + securityHeaders: SecurityHeadersOptions | undefined, +): NonNullable => + (async ({ dir, logger }) => { + if (sri.enableStatic) { + await processStaticFiles(logger, { + state, + distDir: fileURLToPath(dir), + sri, + securityHeaders, + }) + } + }) satisfies NonNullable + /** * @param {Required} sri * @param {SecurityHeadersOptions | undefined} securityHeaders * @returns */ export const getAstroConfigSetup = ( + state: IntegrationState, sri: Required, securityHeaders: SecurityHeadersOptions | undefined, ): Required['astro:config:setup'] => { // biome-ignore lint/suspicious/useAwait: We have to conform to the Astro API return async ({ logger, addMiddleware, config, updateConfig }) => { - const publicDir = fileURLToPath(config.publicDir) - const plugin = getViteMiddlewarePlugin( - logger, - sri, - securityHeaders, - publicDir, - ) - updateConfig({ vite: { plugins: [plugin] } }) + state.config = config + + if (sri.enableMiddleware) { + const publicDir = fileURLToPath(config.publicDir) + const plugin = getViteMiddlewarePlugin( + logger, + sri, + securityHeaders, + publicDir, + ) + updateConfig({ vite: { plugins: [plugin] } }) + + addMiddleware({ + order: 'post', + entrypoint: 'virtual:@kindspells/astro-shield/middleware', + }) + } - addMiddleware({ - order: 'post', - entrypoint: 'virtual:@kindspells/astro-shield/middleware', + updateConfig({ + integrations: [ + { + name: '@kindspells/astro-shield-post-config-setup', + hooks: { + 'astro:build:done': getAstroBuildDone(state, sri, securityHeaders), + }, + }, + ], }) } } diff --git a/@kindspells/astro-shield/src/e2e/fixtures/dynamic/package.json b/@kindspells/astro-shield/src/e2e/fixtures/dynamic/package.json index 0fa0541..a2dece6 100644 --- a/@kindspells/astro-shield/src/e2e/fixtures/dynamic/package.json +++ b/@kindspells/astro-shield/src/e2e/fixtures/dynamic/package.json @@ -10,8 +10,8 @@ }, "license": "MIT", "dependencies": { - "@astrojs/node": "^8.3.3", - "astro": "^4.15.8" + "@astrojs/node": "^8.3.4", + "astro": "^4.15.9" }, "devDependencies": { "@kindspells/astro-shield": "workspace:*" diff --git a/@kindspells/astro-shield/src/e2e/fixtures/hybrid/package.json b/@kindspells/astro-shield/src/e2e/fixtures/hybrid/package.json index 420c31b..eaf9ac3 100644 --- a/@kindspells/astro-shield/src/e2e/fixtures/hybrid/package.json +++ b/@kindspells/astro-shield/src/e2e/fixtures/hybrid/package.json @@ -8,8 +8,8 @@ }, "license": "MIT", "dependencies": { - "@astrojs/node": "^8.3.3", - "astro": "^4.15.8" + "@astrojs/node": "^8.3.4", + "astro": "^4.15.9" }, "devDependencies": { "@kindspells/astro-shield": "workspace:*" diff --git a/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/package.json b/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/package.json index 8bc615c..986609a 100644 --- a/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/package.json +++ b/@kindspells/astro-shield/src/e2e/fixtures/hybrid2/package.json @@ -8,8 +8,8 @@ }, "license": "MIT", "dependencies": { - "@astrojs/node": "^8.3.3", - "astro": "^4.15.8" + "@astrojs/node": "^8.3.4", + "astro": "^4.15.9" }, "devDependencies": { "@kindspells/astro-shield": "workspace:*" diff --git a/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/package.json b/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/package.json index 5a836cd..6a4106e 100644 --- a/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/package.json +++ b/@kindspells/astro-shield/src/e2e/fixtures/hybrid3/package.json @@ -8,8 +8,8 @@ }, "license": "MIT", "dependencies": { - "@astrojs/node": "^8.3.3", - "astro": "^4.15.8" + "@astrojs/node": "^8.3.4", + "astro": "^4.15.9" }, "devDependencies": { "@kindspells/astro-shield": "workspace:*" diff --git a/@kindspells/astro-shield/src/e2e/fixtures/static/package.json b/@kindspells/astro-shield/src/e2e/fixtures/static/package.json index 396bf24..394f6f6 100644 --- a/@kindspells/astro-shield/src/e2e/fixtures/static/package.json +++ b/@kindspells/astro-shield/src/e2e/fixtures/static/package.json @@ -8,7 +8,7 @@ }, "license": "MIT", "dependencies": { - "astro": "^4.15.8" + "astro": "^4.15.9" }, "devDependencies": { "@kindspells/astro-shield": "workspace:*" diff --git a/@kindspells/astro-shield/src/main.mts b/@kindspells/astro-shield/src/main.mts index de8a095..c61b107 100644 --- a/@kindspells/astro-shield/src/main.mts +++ b/@kindspells/astro-shield/src/main.mts @@ -4,29 +4,10 @@ * SPDX-License-Identifier: MIT */ -import { fileURLToPath } from 'node:url' - import type { AstroIntegration } from 'astro' -import { getAstroConfigSetup, processStaticFiles } from '#as/core' -import type { - SecurityHeadersOptions, - ShieldOptions, - SRIOptions, -} from './types.mts' - -type AstroHooks = AstroIntegration['hooks'] - -const getAstroBuildDone = ( - sri: Required, - securityHeaders: SecurityHeadersOptions | undefined, -): NonNullable => - (async ({ dir, logger }) => - await processStaticFiles(logger, { - distDir: fileURLToPath(dir), - sri, - securityHeaders, - })) satisfies NonNullable +import { getAstroConfigSetup } from '#as/core' +import type { IntegrationState, ShieldOptions, SRIOptions } from './types.mts' const logWarn = (msg: string): void => console.warn(`\nWARNING (@kindspells/astro-shield):\n\t${msg}\n`) @@ -54,19 +35,12 @@ export const shield = ({ logWarn('`sri.hashesModule` is ignored when `sri.enableStatic` is `false`') } + const state: IntegrationState = { config: {} } + return { name: '@kindspells/astro-shield', hooks: { - ...(_sri.enableStatic === true - ? { - 'astro:build:done': getAstroBuildDone(_sri, securityHeaders), - } - : undefined), - ...(_sri.enableMiddleware === true - ? { - 'astro:config:setup': getAstroConfigSetup(_sri, securityHeaders), - } - : undefined), + 'astro:config:setup': getAstroConfigSetup(state, _sri, securityHeaders), }, } satisfies AstroIntegration } diff --git a/@kindspells/astro-shield/src/netlify.mts b/@kindspells/astro-shield/src/netlify.mts index 1c52337..74c3a26 100644 --- a/@kindspells/astro-shield/src/netlify.mts +++ b/@kindspells/astro-shield/src/netlify.mts @@ -2,15 +2,15 @@ import { readFile, writeFile } from 'node:fs/promises' import type { CSPDirectives, - HashesCollection, PerPageHashes, + PerPageHashesCollection, SecurityHeadersOptions, } from './types.mts' import { serialiseCspDirectives, setSrcDirective } from './headers.mts' import { doesFileExist } from './fs.mts' type HeaderEntry = { - headerName: string + key: string value: string } @@ -81,7 +81,7 @@ const pushHeader = ( } currentPath?.entries.push({ - headerName: match.groups.name, + key: match.groups.name, value: match.groups.value, }) } @@ -188,7 +188,7 @@ export const serializeNetlifyHeadersConfig = ( .map(e => 'comment' in e ? `${indent}${e.comment}` - : `${indent}${e.headerName}: ${e.value}`, + : `${indent}${e.key}: ${e.value}`, ) .join('\n')}\n` } @@ -219,9 +219,9 @@ export const comparePathEntries = ( // We leave comments in place return 'comment' in a || 'comment' in b ? 0 - : a.headerName < b.headerName + : a.key < b.key ? -1 - : a.headerName > b.headerName + : a.key > b.key ? 1 : a.value < b.value // headers can have many values ? -1 @@ -238,16 +238,16 @@ export const comparePathEntriesSimplified = ( // We leave comments in place return 'comment' in a || 'comment' in b ? 0 - : a.headerName < b.headerName + : a.key < b.key ? -1 - : a.headerName > b.headerName + : a.key > b.key ? 1 : 0 } export const buildNetlifyHeadersConfig = ( securityHeadersOptions: SecurityHeadersOptions, - resourceHashes: Pick, + perPageSriHashes: PerPageHashesCollection, ): NetlifyHeadersRawConfig => { const config: NetlifyHeadersRawConfig = { indentWith: '\t', @@ -255,7 +255,7 @@ export const buildNetlifyHeadersConfig = ( } const pagesToIterate: [string, PerPageHashes][] = [] - for (const [page, hashes] of resourceHashes.perPageSriHashes) { + for (const [page, hashes] of perPageSriHashes.entries()) { if (page === 'index.html' || page.endsWith('/index.html')) { pagesToIterate.push([page.slice(0, -10), hashes]) } @@ -286,7 +286,7 @@ export const buildNetlifyHeadersConfig = ( } pathEntries.push({ - headerName: 'content-security-policy', + key: 'content-security-policy', value: serialiseCspDirectives(directives), }) } @@ -446,7 +446,7 @@ export const mergeNetlifyHeadersConfig = ( export const patchNetlifyHeadersConfig = async ( configPath: string, securityHeadersOptions: SecurityHeadersOptions, - resourceHashes: Pick, + perPageSriHashes: PerPageHashesCollection, ): Promise => { const baseConfig = (await doesFileExist(configPath)) ? await readNetlifyHeadersFile(configPath) @@ -454,7 +454,7 @@ export const patchNetlifyHeadersConfig = async ( const patchConfig = buildNetlifyHeadersConfig( securityHeadersOptions, - resourceHashes, + perPageSriHashes, ) const mergedConfig = mergeNetlifyHeadersConfig(baseConfig, patchConfig) diff --git a/@kindspells/astro-shield/src/tests/fixtures/vercel_config.json b/@kindspells/astro-shield/src/tests/fixtures/vercel_config.json new file mode 100644 index 0000000..3d39aaa --- /dev/null +++ b/@kindspells/astro-shield/src/tests/fixtures/vercel_config.json @@ -0,0 +1,29 @@ +{ + "version": 3, + "routes": [ + { + "src": "/es", + "headers": { + "Location": "/es/" + }, + "status": 308 + }, + { + "src": "/new", + "headers": { + "Location": "/new/" + }, + "status": 308 + }, + { + "src": "^/_astro/(.*)$", + "headers": { + "cache-control": "public, max-age=31536000, immutable" + }, + "continue": true + }, + { + "handle": "filesystem" + } + ] +} diff --git a/@kindspells/astro-shield/src/tests/main.test.mts b/@kindspells/astro-shield/src/tests/main.test.mts index d5d9a47..141d41a 100644 --- a/@kindspells/astro-shield/src/tests/main.test.mts +++ b/@kindspells/astro-shield/src/tests/main.test.mts @@ -17,7 +17,7 @@ describe('sriCSP', () => { const checkIntegration = ( integration: AstroIntegration, - keys: (keyof AstroIntegration['hooks'])[] = ['astro:build:done' as const], + keys: (keyof AstroIntegration['hooks'])[] = ['astro:config:setup'] as const, ) => { expect(Object.keys(integration).sort()).toEqual(['hooks', 'name']) expect(integration.name).toBe('@kindspells/astro-shield') @@ -40,14 +40,16 @@ describe('sriCSP', () => { checkIntegration(integration) }) - it('returns an "empty" integration when we disable all features', () => { + it('returns an integration even when we disable all features', () => { const integration = shield({ sri: { enableStatic: false } }) - checkIntegration(integration, []) + + // NOTE: it is too much work to verify that those hooks will do nothing + checkIntegration(integration, ['astro:config:setup']) }) it('returns hooks for static & dynamic content when we enable middleware', () => { const integration = shield({ sri: { enableMiddleware: true } }) - checkIntegration(integration, ['astro:build:done', 'astro:config:setup']) + checkIntegration(integration, ['astro:config:setup']) }) it('returns hooks only for dynamic content when we enable middleware and disable static sri', () => { diff --git a/@kindspells/astro-shield/src/tests/netlify.test.mts b/@kindspells/astro-shield/src/tests/netlify.test.mts index 13d48ed..c627ffb 100644 --- a/@kindspells/astro-shield/src/tests/netlify.test.mts +++ b/@kindspells/astro-shield/src/tests/netlify.test.mts @@ -20,15 +20,15 @@ const testEntries = [ path: '/index.html', entries: [ { comment: '# Nested Comment' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, - { headerName: 'X-XSS-Protection', value: '1; mode=block' }, + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-XSS-Protection', value: '1; mode=block' }, ], }, { path: '/es/index.html', entries: [ - { headerName: 'X-Frame-Options', value: 'DENY' }, - { headerName: 'X-XSS-Protection', value: '1; mode=block' }, + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-XSS-Protection', value: '1; mode=block' }, ], }, ] satisfies NetlifyHeadersRawConfig['entries'] @@ -36,42 +36,36 @@ const testEntries = [ describe('comparePathEntries', () => { it.each([ [{ comment: 'Comment A' }, { comment: 'Comment B' }], - [ - { comment: 'Comment A' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, - ], - [ - { headerName: 'X-Frame-Options', value: 'DENY' }, - { comment: 'Comment A' }, - ], + [{ comment: 'Comment A' }, { key: 'X-Frame-Options', value: 'DENY' }], + [{ key: 'X-Frame-Options', value: 'DENY' }, { comment: 'Comment A' }], ] as const)('returns 0 if there is any comment', (entryA, entryB) => { expect(comparePathEntries(entryA, entryB)).toBe(0) }) it.each([ [ - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, 0, ], [ - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'X-Frame-Options', value: 'DENY' }, -1, ], [ - { headerName: 'X-Frame-Options', value: 'DENY' }, - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, 1, ], [ - { headerName: 'X-Frame-Options', value: 'ALLOW' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-Frame-Options', value: 'ALLOW' }, + { key: 'X-Frame-Options', value: 'DENY' }, -1, ], [ - { headerName: 'X-Frame-Options', value: 'DENY' }, - { headerName: 'X-Frame-Options', value: 'ALLOW' }, + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-Frame-Options', value: 'ALLOW' }, 1, ], ] as const)( @@ -85,42 +79,36 @@ describe('comparePathEntries', () => { describe('comparePathEntriesSimplified', () => { it.each([ [{ comment: 'Comment A' }, { comment: 'Comment B' }], - [ - { comment: 'Comment A' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, - ], - [ - { headerName: 'X-Frame-Options', value: 'DENY' }, - { comment: 'Comment A' }, - ], + [{ comment: 'Comment A' }, { key: 'X-Frame-Options', value: 'DENY' }], + [{ key: 'X-Frame-Options', value: 'DENY' }, { comment: 'Comment A' }], ] as const)('returns 0 if there is any comment', (entryA, entryB) => { expect(comparePathEntriesSimplified(entryA, entryB)).toBe(0) }) it.each([ [ - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, 0, ], [ - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'X-Frame-Options', value: 'DENY' }, -1, ], [ - { headerName: 'X-Frame-Options', value: 'DENY' }, - { headerName: 'Authorization', value: 'Bearer 0123456789abcdef' }, + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'Authorization', value: 'Bearer 0123456789abcdef' }, 1, ], [ - { headerName: 'X-Frame-Options', value: 'ALLOW' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-Frame-Options', value: 'ALLOW' }, + { key: 'X-Frame-Options', value: 'DENY' }, 0, ], [ - { headerName: 'X-Frame-Options', value: 'DENY' }, - { headerName: 'X-Frame-Options', value: 'ALLOW' }, + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-Frame-Options', value: 'ALLOW' }, 0, ], ] as const)( @@ -236,11 +224,7 @@ describe('buildNetlifyHeadersConfig', () => { it('creates an "empty" config when there is no info to construct headers', () => { const config = buildNetlifyHeadersConfig( {}, - { - perPageSriHashes: new Map([ - ['index.html', { scripts: new Set(), styles: new Set() }], - ]), - }, + new Map([['index.html', { scripts: new Set(), styles: new Set() }]]), ) expect(config.entries.length).toBe(0) @@ -249,44 +233,42 @@ describe('buildNetlifyHeadersConfig', () => { it('creates a basic csp config with resource hashes', () => { const config = buildNetlifyHeadersConfig( { contentSecurityPolicy: {} }, - { - perPageSriHashes: new Map([ - [ - 'onlyscripts.html', - { - scripts: new Set([ - 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', - 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', - ]), - styles: new Set(), - }, - ], - [ - 'onlystyles.html', - { - scripts: new Set(), - styles: new Set([ - 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', - 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', - ]), - }, - ], - [ - 'scriptsandstyles.html', - { - scripts: new Set([ - 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', - 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', - ]), - styles: new Set([ - 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', - 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', - ]), - }, - ], - ['nothing.html', { scripts: new Set(), styles: new Set() }], - ]), - }, + new Map([ + [ + 'onlyscripts.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set(), + }, + ], + [ + 'onlystyles.html', + { + scripts: new Set(), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + [ + 'scriptsandstyles.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + ['nothing.html', { scripts: new Set(), styles: new Set() }], + ]), ) // It also orders the entries to ensure that we have a canonical order @@ -297,7 +279,7 @@ describe('buildNetlifyHeadersConfig', () => { path: '/nothing.html', entries: [ { - headerName: 'content-security-policy', + key: 'content-security-policy', value: "script-src 'none'; style-src 'none'", }, ], @@ -306,7 +288,7 @@ describe('buildNetlifyHeadersConfig', () => { path: '/onlyscripts.html', entries: [ { - headerName: 'content-security-policy', + key: 'content-security-policy', value: "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'none'", }, @@ -316,7 +298,7 @@ describe('buildNetlifyHeadersConfig', () => { path: '/onlystyles.html', entries: [ { - headerName: 'content-security-policy', + key: 'content-security-policy', value: "script-src 'none'; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", }, @@ -326,7 +308,7 @@ describe('buildNetlifyHeadersConfig', () => { path: '/scriptsandstyles.html', entries: [ { - headerName: 'content-security-policy', + key: 'content-security-policy', value: "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", }, @@ -339,42 +321,40 @@ describe('buildNetlifyHeadersConfig', () => { it('creates a "double entry" for "index.html" files', () => { const config = buildNetlifyHeadersConfig( { contentSecurityPolicy: {} }, - { - perPageSriHashes: new Map([ - [ - 'index.html', - { - scripts: new Set([ - 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', - ]), - styles: new Set(), - }, - ], - [ - 'es/index.html', - { - scripts: new Set([ - 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', - ]), - styles: new Set(), - }, - ], - [ - 'fakeindex.html', - { - scripts: new Set([ - 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', - ]), - styles: new Set(), - }, - ], - ]), - }, + new Map([ + [ + 'index.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + ]), + styles: new Set(), + }, + ], + [ + 'es/index.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + ]), + styles: new Set(), + }, + ], + [ + 'fakeindex.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + ]), + styles: new Set(), + }, + ], + ]), ) const testEntries = [ { - headerName: 'content-security-policy', + key: 'content-security-policy', value: "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c='; style-src 'none'", }, @@ -415,11 +395,11 @@ describe('mergeNetlifyHeadersConfig', () => { entries: [ { path: '/a.html', - entries: [{ headerName: 'X-Frame-Options', value: 'DENY' }], + entries: [{ key: 'X-Frame-Options', value: 'DENY' }], }, { path: '/c.html', - entries: [{ headerName: 'Cache-Control', value: 'no-cache' }], + entries: [{ key: 'Cache-Control', value: 'no-cache' }], }, { path: '/cc.html', @@ -428,8 +408,8 @@ describe('mergeNetlifyHeadersConfig', () => { { path: '/d.html', // shared path entries: [ - { headerName: 'Cache-Control', value: 'no-cache' }, - { headerName: 'X-Frame-Options', value: 'DENY' }, + { key: 'Cache-Control', value: 'no-cache' }, + { key: 'X-Frame-Options', value: 'DENY' }, ], }, ], @@ -441,7 +421,7 @@ describe('mergeNetlifyHeadersConfig', () => { path: '/b.html', entries: [ { - headerName: 'content-security-policy', + key: 'content-security-policy', value: "script-src 'none'", }, ], @@ -449,8 +429,8 @@ describe('mergeNetlifyHeadersConfig', () => { { path: '/d.html', // shared path entries: [ - { headerName: 'X-Frame-Options', value: 'ALLOW' }, - { headerName: 'X-XSS-Protection', value: '1; mode=block' }, + { key: 'X-Frame-Options', value: 'ALLOW' }, + { key: 'X-XSS-Protection', value: '1; mode=block' }, ], }, ], @@ -463,28 +443,28 @@ describe('mergeNetlifyHeadersConfig', () => { entries: [ { path: '/a.html', - entries: [{ headerName: 'X-Frame-Options', value: 'DENY' }], + entries: [{ key: 'X-Frame-Options', value: 'DENY' }], }, { path: '/b.html', entries: [ { - headerName: 'content-security-policy', + key: 'content-security-policy', value: "script-src 'none'", }, ], }, { path: '/c.html', - entries: [{ headerName: 'Cache-Control', value: 'no-cache' }], + entries: [{ key: 'Cache-Control', value: 'no-cache' }], }, // cc.html is discarded for not having entries { path: '/d.html', entries: [ - { headerName: 'Cache-Control', value: 'no-cache' }, // from c1 - { headerName: 'X-Frame-Options', value: 'ALLOW' }, // overriden - { headerName: 'X-XSS-Protection', value: '1; mode=block' }, // from c2 + { key: 'Cache-Control', value: 'no-cache' }, // from c1 + { key: 'X-Frame-Options', value: 'ALLOW' }, // overriden + { key: 'X-XSS-Protection', value: '1; mode=block' }, // from c2 ], }, ], diff --git a/@kindspells/astro-shield/src/tests/vercel.test.mts b/@kindspells/astro-shield/src/tests/vercel.test.mts new file mode 100644 index 0000000..bca66eb --- /dev/null +++ b/@kindspells/astro-shield/src/tests/vercel.test.mts @@ -0,0 +1,390 @@ +import { resolve } from 'node:path' + +import { describe, expect, it } from 'vitest' + +import type { VercelConfig } from '../vercel.mts' +import { + buildVercelConfig, + mergeVercelConfig, + parseVercelConfig, + readVercelConfigFile, + serializeVercelConfig, +} from '../vercel.mts' +import type { Logger } from '../types.mts' + +describe('parseVercelConfig', () => { + it('parses a valid minimal Vercel config', () => { + const config_a = '{"version":3}' + expect(parseVercelConfig(console, config_a)).toEqual({ version: 3 }) + + const config_b = '{"version":3, "routes": []}' + expect(parseVercelConfig(console, config_b)).toEqual({ + version: 3, + routes: [], + }) + }) + + it('throws an error when version field is missing', () => { + const config = '{}' + expect(() => parseVercelConfig(console, config)).toThrowError( + 'Invalid Vercel config: missing "version" field', + ) + }) + + // TODO: This test should be removed once we improve versions handling + it('logs a warning message when version field is not 3', () => { + const config = '{"version":2}' + + let warnCalls = 0 + let lastWarnMsg = '' + const logger: Logger = { + info: () => {}, + warn: (msg: string) => { + warnCalls += 1 + lastWarnMsg = msg + }, + error: () => {}, + } + + parseVercelConfig(logger, config) + + expect(warnCalls).toBe(1) + expect(lastWarnMsg).toBe( + 'Expected Vercel config version 3, but got version 2', + ) + }) +}) + +describe('readVercelConfigFile', () => { + const testsDir = new URL('.', import.meta.url).pathname + + it('reads a valid Vercel config file', async () => { + const config = await readVercelConfigFile( + console, + resolve(testsDir, 'fixtures', 'vercel_config.json'), + ) + + expect(config).toEqual({ + version: 3, + routes: [ + { + src: '/es', + headers: { + Location: '/es/', + }, + status: 308, + }, + { + src: '/new', + headers: { + Location: '/new/', + }, + status: 308, + }, + { + src: '^/_astro/(.*)$', + headers: { + 'cache-control': 'public, max-age=31536000, immutable', + }, + continue: true, + }, + { + handle: 'filesystem', + }, + ], + }) + }) +}) + +describe('buildVercelConfig', () => { + it('creates an "empty" config when there is no info to construct headers', () => { + const config = buildVercelConfig( + {}, + {}, + new Map([['index.html', { scripts: new Set(), styles: new Set() }]]), + ) + + expect(config).toEqual({ + version: 3, + routes: [], + }) + }) + + it('creates a basic csp config with resource hashes', () => { + const config = buildVercelConfig( + {}, + { contentSecurityPolicy: {} }, + new Map([ + [ + 'onlyscripts.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set(), + }, + ], + [ + 'onlystyles.html', + { + scripts: new Set(), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + [ + 'scriptsandstyles.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + ['nothing.html', { scripts: new Set(), styles: new Set() }], + ]), + ) + + // It also orders the entries to ensure that we have a canonical order + expect(config).toEqual({ + version: 3, + routes: [ + { + src: '/nothing.html', + headers: { + 'content-security-policy': "script-src 'none'; style-src 'none'", + }, + }, + { + src: '/onlyscripts.html', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'none'", + }, + }, + { + src: '/onlystyles.html', + headers: { + 'content-security-policy': + "script-src 'none'; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + { + src: '/scriptsandstyles.html', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + ], + } satisfies VercelConfig) + }) + + it('respects the "trailingSlash=always" Astro option', () => { + const config = buildVercelConfig( + { trailingSlash: 'always' }, + { contentSecurityPolicy: {} }, + new Map([ + [ + 'nested/index.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + [ + 'notindex.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + ]), + ) + + expect(config).toEqual({ + version: 3, + routes: [ + { + src: '/nested/', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + { + src: '/nested/index.html', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + { + src: '/notindex.html', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + ], + }) + }) + + it('respects the "trailingSlash=never" Astro option', () => { + const config = buildVercelConfig( + { trailingSlash: 'never' }, + { contentSecurityPolicy: {} }, + new Map([ + [ + 'nested/index.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + [ + 'notindex.html', + { + scripts: new Set([ + 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=', + 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA=', + ]), + styles: new Set([ + 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=', + 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE=', + ]), + }, + ], + ]), + ) + + expect(config).toEqual({ + version: 3, + routes: [ + { + src: '/nested', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + { + src: '/nested/index.html', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + { + src: '/notindex.html', + headers: { + 'content-security-policy': + "script-src 'self' 'sha256-071spvYLMvnwaR0H7M2dfK0enB0cGtydTbgJkdoWq7c=' 'sha256-KWrCkmqpW9eWGwZRBZ9KqXsoHtAbAH/zPJvmUhsMKpA='; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + ], + }) + }) +}) + +describe('mergeVercelConfig', () => { + it('merges two Vercel configs', () => { + const base: VercelConfig = { + version: 3, + routes: [ + { + src: '/nothing.html', + headers: { + 'content-security-policy': "script-src 'none'; style-src 'none'", + }, + }, + ], + } + const patch: VercelConfig = { + version: 3, + routes: [ + { + src: '/onlystyles.html', + headers: { + 'content-security-policy': + "script-src 'none'; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + ], + } + + const merged = mergeVercelConfig(base, patch) + + expect(merged).toEqual({ + version: 3, + routes: [ + { + src: '/nothing.html', + headers: { + 'content-security-policy': "script-src 'none'; style-src 'none'", + }, + }, + { + src: '/onlystyles.html', + headers: { + 'content-security-policy': + "script-src 'none'; style-src 'self' 'sha256-VC84dQdO3Mo7nZIRaNTJgrqPQ0foHI8gdp/DS+e9/lk=' 'sha256-iwd3GNfA+kImEozakD3ZZQSZ8VVb3MFBOhJH6dEMnDE='", + }, + }, + ], + }) + }) +}) + +describe('serializeVercelConfig', () => { + it('serializes a Vercel config', () => { + const serialized = serializeVercelConfig({ + version: 3, + routes: [ + { + src: '/nothing.html', + headers: { + 'content-security-policy': "script-src 'none'; style-src 'none'", + }, + }, + ], + }) + + expect(serialized).toBe(`{ + "version": 3, + "routes": [ + { + "src": "/nothing.html", + "headers": { + "content-security-policy": "script-src 'none'; style-src 'none'" + } + } + ] +}`) + }) +}) diff --git a/@kindspells/astro-shield/src/types.mts b/@kindspells/astro-shield/src/types.mts index 522fbd6..fd03409 100644 --- a/@kindspells/astro-shield/src/types.mts +++ b/@kindspells/astro-shield/src/types.mts @@ -1,3 +1,5 @@ +import type { AstroConfig } from 'astro' + // Options // ----------------------------------------------------------------------------- // We don't include 'script-src' and 'style-src' because they are handled by the @@ -113,26 +115,8 @@ export type SRIOptions = { stylesAllowListUrls?: string[] } -type NetlifyConfig = { - provider: 'netlify' -} & ( - | { - mode: '_headers' - } - | { - mode: 'netlify.toml' - configFile: string // TODO: auto-discovery? - } - | { - mode: 'both' - configFile: string // TODO: auto-discovery? - } -) - -type VercelConfig = { - provider: 'vercel' - configFile: string // TODO: auto-discovery? -} +type NetlifyConfig = { provider: 'netlify' } +type VercelConfig = { provider: 'vercel' } export type SecurityHeadersOptions = { enableOnStaticPages?: NetlifyConfig | VercelConfig | undefined @@ -164,6 +148,7 @@ export type ShieldOptions = { } export type StrictShieldOptions = ShieldOptions & { + state: IntegrationState distDir: string sri: SRIOptions & { enableStatic: boolean; enableMiddleware: boolean } } @@ -190,3 +175,5 @@ export type HashesCollection = { perPageSriHashes: PerPageHashesCollection perResourceSriHashes: MiddlewareHashes } + +export type IntegrationState = { config: Partial } diff --git a/@kindspells/astro-shield/src/vercel.mts b/@kindspells/astro-shield/src/vercel.mts new file mode 100644 index 0000000..58dace7 --- /dev/null +++ b/@kindspells/astro-shield/src/vercel.mts @@ -0,0 +1,159 @@ +import { resolve } from 'node:path' +import type { + CSPDirectives, + Logger, + PerPageHashes, + PerPageHashesCollection, + SecurityHeadersOptions, +} from './types.mts' +import { doesFileExist } from './fs.mts' +import { readFile, writeFile, readdir } from 'node:fs/promises' +import type { AstroConfig } from 'astro' +import { serialiseCspDirectives, setSrcDirective } from './headers.mts' + +type VercelRoute = { + src: string + headers?: Record + status?: number + [key: string]: unknown +} + +type VercelConfigV3 = { + version: number + routes?: VercelRoute[] +} + +export type VercelConfig = VercelConfigV3 + +const vercelAdapterDistRegexp = /\.vercel\/output\/static\/?$/ + +export const parseVercelConfig = ( + logger: Logger, + config: string, +): VercelConfig => { + const parsed = JSON.parse(config) + + // TODO: Improve validation and error handling + if (!('version' in parsed)) { + throw new Error('Invalid Vercel config: missing "version" field') + } + if (parsed.version !== 3) { + logger.warn( + `Expected Vercel config version 3, but got version ${parsed.version}`, + ) + } + + return parsed as VercelConfig +} + +export const readVercelConfigFile = async ( + logger: Logger, + path: string, +): Promise => { + return parseVercelConfig(logger, await readFile(path, 'utf8')) +} + +export const buildVercelConfig = ( + astroConfig: Partial, + securityHeadersOptions: SecurityHeadersOptions, + perPageSriHashes: PerPageHashesCollection, +): VercelConfig => { + const indexSlashOffset = + astroConfig.trailingSlash === 'never' + ? -11 + : astroConfig.trailingSlash === 'always' + ? -10 + : undefined + + const pagesToIterate: [string, PerPageHashes][] = [] + for (const [page, hashes] of perPageSriHashes.entries()) { + if ( + indexSlashOffset !== undefined && + (page === 'index.html' || page.endsWith('/index.html')) + ) { + pagesToIterate.push([page.slice(0, indexSlashOffset), hashes]) + } + pagesToIterate.push([page, hashes]) + } + pagesToIterate.sort() + + const routes: VercelRoute[] = [] + for (const [page, hashes] of pagesToIterate) { + const headers: Record = {} + + if (securityHeadersOptions.contentSecurityPolicy !== undefined) { + const directives: CSPDirectives = + securityHeadersOptions.contentSecurityPolicy.cspDirectives ?? {} + + if (hashes.scripts.size > 0) { + setSrcDirective(directives, 'script-src', hashes.scripts) + } else { + directives['script-src'] = "'none'" + } + if (hashes.styles.size > 0) { + setSrcDirective(directives, 'style-src', hashes.styles) + } else { + directives['style-src'] = "'none'" + } + + if (Object.keys(directives).length === 0) { + continue + } + + headers['content-security-policy'] = serialiseCspDirectives(directives) + } + + if (Object.keys(headers).length > 0) { + routes.push({ src: `/${page}`, headers }) + } + } + + return { version: 3, routes } +} + +export const mergeVercelConfig = ( + base: VercelConfig, + patch: VercelConfig, +): VercelConfig => { + return { ...base, routes: [...(base.routes ?? []), ...(patch.routes ?? [])] } +} + +export const serializeVercelConfig = (config: VercelConfig): string => { + return JSON.stringify(config, null, '\t') +} + +export const patchVercelHeadersConfig = async ( + logger: Logger, + distDir: string, + astroConfig: Partial, + securityHeadersOptions: SecurityHeadersOptions, + perPageSriHashes: PerPageHashesCollection, +): Promise => { + if (!vercelAdapterDistRegexp.test(distDir)) { + logger.warn( + '"@astrojs/vercel/static" adapter not detected, but "securityHeaders.enableOnStaticPages.provider" is set to "vercel". See https://docs.astro.build/en/guides/integrations-guide/vercel/#choosing-a-target to learn how to set up the adapter.', + ) + return + } + const configPath = resolve(distDir, '..', 'config.json') + if (!(await doesFileExist(configPath))) { + logger.error( + `Vercel adapter detected, but "config.json" not found in "${configPath}".`, + ) + logger.error(JSON.stringify(await readdir(resolve(distDir)))) + logger.error(JSON.stringify(await readdir(resolve(distDir, '..')))) + return + } + + const baseConfig = await readVercelConfigFile(logger, configPath) + + const patchConfig = buildVercelConfig( + astroConfig, + securityHeadersOptions, + perPageSriHashes, + ) + + const mergedConfig = mergeVercelConfig(baseConfig, patchConfig) + + await writeFile(configPath, serializeVercelConfig(mergedConfig)) +} diff --git a/@kindspells/astro-shield/vitest.config.unit.mts b/@kindspells/astro-shield/vitest.config.unit.mts index 334ae09..90f2009 100644 --- a/@kindspells/astro-shield/vitest.config.unit.mts +++ b/@kindspells/astro-shield/vitest.config.unit.mts @@ -20,10 +20,10 @@ export default defineConfig({ 'coverage-unit/**/*', ], thresholds: { - statements: 78.0, + statements: 76.0, branches: 80.0, - functions: 88.0, - lines: 78.0, + functions: 86.0, + lines: 76.0, }, reportsDirectory: 'coverage-unit', }, diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index f1c8fcf..3c7e954 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -16,12 +16,11 @@ const locales = { } export default defineConfig({ + site: 'https://astro-shield.kindspells.dev', output: 'static', adapter: aws(), - site: 'https://astro-shield.kindspells.dev', - image: { - service: passthroughImageService(), - }, + trailingSlash: 'always', + image: { service: passthroughImageService() }, integrations: [ shield({}), starlight({ @@ -135,6 +134,10 @@ export default defineConfig({ ], }, ], + lastUpdated: true, + editLink: { + baseUrl: 'https://github.com/kindspells/astro-shield/edit/main/docs/', + }, }), ], }) diff --git a/docs/package.json b/docs/package.json index 9039d8c..72f66c9 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,14 +13,14 @@ "dependencies": { "astro-sst": "^2.43.5", "sharp": "0.33.5", - "sst": "^3.1.25" + "sst": "^3.1.49" }, "devDependencies": { "@astrojs/check": "^0.9.3", "@astrojs/starlight": "^0.28.2", "@astrojs/ts-plugin": "^1.10.2", "@kindspells/astro-shield": "workspace:^", - "astro": "^4.15.8", + "astro": "^4.15.9", "typescript": "^5.6.2" } } diff --git a/package.json b/package.json index cc685a5..e45e17b 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,15 @@ ], "devDependencies": { "@biomejs/biome": "^1.9.2", - "@moonrepo/cli": "^1.28.2", + "@moonrepo/cli": "^1.28.3", "@vitest/coverage-v8": "^2.1.1", "publint": "^0.2.11", "vitest": "^2.1.1" }, "engines": { - "node": ">= 18.0.0" + "node": ">= 22.9.0" }, - "packageManager": "pnpm@9.10.0", + "packageManager": "pnpm@9.11.0", "scripts": { "format": "biome format --write .", "install-githooks": "if [ -d .git ]; then git config core.hooksPath .hooks; fi" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbe05d1..01d49ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,56 +12,56 @@ importers: specifier: ^1.9.2 version: 1.9.2 '@moonrepo/cli': - specifier: ^1.28.2 - version: 1.28.2 + specifier: ^1.28.3 + version: 1.28.3 '@vitest/coverage-v8': specifier: ^2.1.1 - version: 2.1.1(vitest@2.1.1(@types/node@22.5.5)) + version: 2.1.1(vitest@2.1.1(@types/node@22.7.4)) publint: specifier: ^0.2.11 version: 0.2.11 vitest: specifier: ^2.1.1 - version: 2.1.1(@types/node@22.5.5) + version: 2.1.1(@types/node@22.7.4) '@kindspells/astro-shield': devDependencies: '@types/node': - specifier: ^22.5.5 - version: 22.5.5 + specifier: ^22.7.4 + version: 22.7.4 astro: - specifier: ^4.15.8 - version: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + specifier: ^4.15.9 + version: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) get-tsconfig: specifier: ^4.8.1 version: 4.8.1 rollup: - specifier: ^4.22.0 - version: 4.22.0 + specifier: ^4.22.5 + version: 4.22.5 rollup-plugin-dts: specifier: ^6.1.1 - version: 6.1.1(rollup@4.22.0)(typescript@5.6.2) + version: 6.1.1(rollup@4.22.5)(typescript@5.6.2) rollup-plugin-esbuild: specifier: ^6.1.1 - version: 6.1.1(esbuild@0.21.5)(rollup@4.22.0) + version: 6.1.1(esbuild@0.21.5)(rollup@4.22.5) typescript: specifier: ^5.6.2 version: 5.6.2 vite: - specifier: ^5.4.6 - version: 5.4.6(@types/node@22.5.5) + specifier: ^5.4.8 + version: 5.4.8(@types/node@22.7.4) vitest: specifier: ^2.1.1 - version: 2.1.1(@types/node@22.5.5) + version: 2.1.1(@types/node@22.7.4) '@kindspells/astro-shield/src/e2e/fixtures/dynamic': dependencies: '@astrojs/node': - specifier: ^8.3.3 - version: 8.3.3(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)) + specifier: ^8.3.4 + version: 8.3.4(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)) astro: - specifier: ^4.15.8 - version: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + specifier: ^4.15.9 + version: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) devDependencies: '@kindspells/astro-shield': specifier: workspace:* @@ -70,11 +70,11 @@ importers: '@kindspells/astro-shield/src/e2e/fixtures/hybrid': dependencies: '@astrojs/node': - specifier: ^8.3.3 - version: 8.3.3(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)) + specifier: ^8.3.4 + version: 8.3.4(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)) astro: - specifier: ^4.15.8 - version: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + specifier: ^4.15.9 + version: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) devDependencies: '@kindspells/astro-shield': specifier: workspace:* @@ -83,11 +83,11 @@ importers: '@kindspells/astro-shield/src/e2e/fixtures/hybrid2': dependencies: '@astrojs/node': - specifier: ^8.3.3 - version: 8.3.3(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)) + specifier: ^8.3.4 + version: 8.3.4(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)) astro: - specifier: ^4.15.8 - version: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + specifier: ^4.15.9 + version: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) devDependencies: '@kindspells/astro-shield': specifier: workspace:* @@ -96,11 +96,11 @@ importers: '@kindspells/astro-shield/src/e2e/fixtures/hybrid3': dependencies: '@astrojs/node': - specifier: ^8.3.3 - version: 8.3.3(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)) + specifier: ^8.3.4 + version: 8.3.4(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)) astro: - specifier: ^4.15.8 - version: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + specifier: ^4.15.9 + version: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) devDependencies: '@kindspells/astro-shield': specifier: workspace:* @@ -109,8 +109,8 @@ importers: '@kindspells/astro-shield/src/e2e/fixtures/static': dependencies: astro: - specifier: ^4.15.8 - version: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + specifier: ^4.15.9 + version: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) devDependencies: '@kindspells/astro-shield': specifier: workspace:* @@ -125,15 +125,15 @@ importers: specifier: 0.33.5 version: 0.33.5 sst: - specifier: ^3.1.25 - version: 3.1.25(hono@4.0.1) + specifier: ^3.1.49 + version: 3.1.49(hono@4.0.1) devDependencies: '@astrojs/check': specifier: ^0.9.3 version: 0.9.3(typescript@5.6.2) '@astrojs/starlight': specifier: ^0.28.2 - version: 0.28.2(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)) + version: 0.28.2(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)) '@astrojs/ts-plugin': specifier: ^1.10.2 version: 1.10.2 @@ -141,8 +141,8 @@ importers: specifier: workspace:^ version: link:../@kindspells/astro-shield astro: - specifier: ^4.15.8 - version: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + specifier: ^4.15.9 + version: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) typescript: specifier: ^5.6.2 version: 5.6.2 @@ -186,8 +186,8 @@ packages: peerDependencies: astro: ^4.8.0 - '@astrojs/node@8.3.3': - resolution: {integrity: sha512-idrKhnnPSi0ABV+PCQsRQqVNwpOvVDF/+fkwcIiE8sr9J8EMvW9g/oyAt8T4X2OBJ8FUzYPL8klfCdG7r0eB5g==} + '@astrojs/node@8.3.4': + resolution: {integrity: sha512-xzQs39goN7xh9np9rypGmbgZj3AmmjNxEMj9ZWz5aBERlqqFF3n8A/w/uaJeZ/bkHS60l1BXVS0tgsQt9MFqBA==} peerDependencies: astro: ^4.2.0 @@ -684,42 +684,42 @@ packages: '@mdx-js/mdx@3.0.1': resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==} - '@moonrepo/cli@1.28.2': - resolution: {integrity: sha512-ugdCEcvq02tx5YNDWlm+6mURaTcEdJgdIWx21kY1mtED0alEISvfnpIRhPCRb9TeRDY8xzh9+ZPNM3dv6kXclg==} + '@moonrepo/cli@1.28.3': + resolution: {integrity: sha512-/zBs8RPOsGddZ5KmzQsoE9DPWN67+nVcF6nPFipNNe7ufC876SoVR7n4kkyMOzcgfoF+c/NEv+5fFPweKsmAsA==} hasBin: true - '@moonrepo/core-linux-arm64-gnu@1.28.2': - resolution: {integrity: sha512-ko8IHMT3Oqc2yqNNG2mM6il5VnW/02dm5owhhu8f73ywf4q+N67yNh3fyVBwZQClJ/sEkU+6rU2fMYAWjJo0jQ==} + '@moonrepo/core-linux-arm64-gnu@1.28.3': + resolution: {integrity: sha512-nf+V/0yuMftZHPoQObzpLQJSh4g3tdF3ssgNSxoEBaY0JZaYiEvdGx0M9VYCg9WlJEFquH3NErhapqEi5pV+Sg==} cpu: [arm64] os: [linux] - '@moonrepo/core-linux-arm64-musl@1.28.2': - resolution: {integrity: sha512-0J4mPBDKaS4a+agqB3IBYHmmCA6/iHaE+ZcBrDvpj/X1WQL9Lu9NgnAJAjsMNhIsfRnzWE1A6RfeHYzTzprfEQ==} + '@moonrepo/core-linux-arm64-musl@1.28.3': + resolution: {integrity: sha512-mzUfVbjxL+/GESTO/4WwS7zpXwmiOKFmRJjpBjBv2kxplbj46jQUXyvGXm2VjB40+0EYJjq+9g3rhKeE5mmzTQ==} cpu: [arm64] os: [linux] - '@moonrepo/core-linux-x64-gnu@1.28.2': - resolution: {integrity: sha512-wswKf3qqM3dpU98c1kAdlVgy9ixP5y6iJorcR6tygzHtZhcVuD+0/I6XYYMQNTokO2wc3zKHCBpAiqg1MNz3Dg==} + '@moonrepo/core-linux-x64-gnu@1.28.3': + resolution: {integrity: sha512-UbHtZK+NS9T76uNEvw66RjaCPUwkkekIWBn7ma88W8LXC7onoKfPsQtMHO7XD50XlRuGonzZONY7Wq4wSJprcA==} cpu: [x64] os: [linux] - '@moonrepo/core-linux-x64-musl@1.28.2': - resolution: {integrity: sha512-/E+j/NYqq1NHLy0V2i31cuOTx9zvgs+FO9NclFQxkr8XxY6YC48nYf+L23oNuxg4eTQH+XOMP6QJIrh9qCL3Kg==} + '@moonrepo/core-linux-x64-musl@1.28.3': + resolution: {integrity: sha512-vbg9Wih32mYShTinPDU7A5Yg/5XWojFjMc8IQ8KPVNdCPGEuk2f6s6yWMc5iOX9s0LCkZ88kZpGMhWi2EaPWaw==} cpu: [x64] os: [linux] - '@moonrepo/core-macos-arm64@1.28.2': - resolution: {integrity: sha512-6nRyk0hsSeuwA0cCKxmeccRXcin1RJEuXdy10A6qTOByIt3CuSUtudbTTBnc/ZEvTdhFYUMacCAWrg5BYaqM7Q==} + '@moonrepo/core-macos-arm64@1.28.3': + resolution: {integrity: sha512-d8FOQyOW77AolThZqFYFVabWTAr+4GsVY0nybZR3zIKd47++hCPCGnus2XA5uuf08chsYK3wi5w2TkHSKtN7gw==} cpu: [arm64] os: [darwin] - '@moonrepo/core-macos-x64@1.28.2': - resolution: {integrity: sha512-CsebsrCUULXYW4lnWdWA/EYUP1n17SK0kLCeMaBK43Nq6dLMJvX/Lij0vTgPenaq5ImgefMshqQKrZqg3CJRRQ==} + '@moonrepo/core-macos-x64@1.28.3': + resolution: {integrity: sha512-0iEfr2JvE4JVEG6CoA9rWD5zUI5xQev9gnOdTmGeXvrrzOASM3bnp8C9jqDu++o4K3jMRWpgNFTIyz5vWIAXKw==} cpu: [x64] os: [darwin] - '@moonrepo/core-windows-x64-msvc@1.28.2': - resolution: {integrity: sha512-/e++sLoQoANaa63j66qVWtIG8Q8bByxLQP2mW73Lv+T7y9Bn2Ht2kk5uCf/lESMnGl6UtAFv1XTvu2ydbb6fFw==} + '@moonrepo/core-windows-x64-msvc@1.28.3': + resolution: {integrity: sha512-ix+82sXXq4dH9CSM2xYLlQ1wqFHX1l+rsw86YjoSy+K48vbpvTvKqkQP7trs0EQMlrW25sNJRiuEebdtm9/CLg==} cpu: [x64] os: [win32] @@ -735,8 +735,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oslojs/encoding@0.4.1': - resolution: {integrity: sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q==} + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} '@pagefind/darwin-arm64@1.1.1': resolution: {integrity: sha512-tZ9tysUmQpFs2EqWG2+E1gc+opDAhSyZSsgKmFzhnWfkK02YHZhvL5XJXEZDqYy3s1FAKhwjTg8XDxneuBlDZQ==} @@ -770,8 +770,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rollup/pluginutils@5.1.0': - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + '@rollup/pluginutils@5.1.2': + resolution: {integrity: sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -779,97 +779,97 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.22.0': - resolution: {integrity: sha512-/IZQvg6ZR0tAkEi4tdXOraQoWeJy9gbQ/cx4I7k9dJaCk9qrXEcdouxRVz5kZXt5C2bQ9pILoAA+KB4C/d3pfw==} + '@rollup/rollup-android-arm-eabi@4.22.5': + resolution: {integrity: sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.22.0': - resolution: {integrity: sha512-ETHi4bxrYnvOtXeM7d4V4kZWixib2jddFacJjsOjwbgYSRsyXYtZHC4ht134OsslPIcnkqT+TKV4eU8rNBKyyQ==} + '@rollup/rollup-android-arm64@4.22.5': + resolution: {integrity: sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.22.0': - resolution: {integrity: sha512-ZWgARzhSKE+gVUX7QWaECoRQsPwaD8ZR0Oxb3aUpzdErTvlEadfQpORPXkKSdKbFci9v8MJfkTtoEHnnW9Ulng==} + '@rollup/rollup-darwin-arm64@4.22.5': + resolution: {integrity: sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.22.0': - resolution: {integrity: sha512-h0ZAtOfHyio8Az6cwIGS+nHUfRMWBDO5jXB8PQCARVF6Na/G6XS2SFxDl8Oem+S5ZsHQgtsI7RT4JQnI1qrlaw==} + '@rollup/rollup-darwin-x64@4.22.5': + resolution: {integrity: sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.22.0': - resolution: {integrity: sha512-9pxQJSPwFsVi0ttOmqLY4JJ9pg9t1gKhK0JDbV1yUEETSx55fdyCjt39eBQ54OQCzAF0nVGO6LfEH1KnCPvelA==} + '@rollup/rollup-linux-arm-gnueabihf@4.22.5': + resolution: {integrity: sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.22.0': - resolution: {integrity: sha512-YJ5Ku5BmNJZb58A4qSEo3JlIG4d3G2lWyBi13ABlXzO41SsdnUKi3HQHe83VpwBVG4jHFTW65jOQb8qyoR+qzg==} + '@rollup/rollup-linux-arm-musleabihf@4.22.5': + resolution: {integrity: sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.22.0': - resolution: {integrity: sha512-U4G4u7f+QCqHlVg1Nlx+qapZy+QoG+NV6ux+upo/T7arNGwKvKP2kmGM4W5QTbdewWFgudQxi3kDNST9GT1/mg==} + '@rollup/rollup-linux-arm64-gnu@4.22.5': + resolution: {integrity: sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.22.0': - resolution: {integrity: sha512-aQpNlKmx3amwkA3a5J6nlXSahE1ijl0L9KuIjVOUhfOh7uw2S4piR3mtpxpRtbnK809SBtyPsM9q15CPTsY7HQ==} + '@rollup/rollup-linux-arm64-musl@4.22.5': + resolution: {integrity: sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.22.0': - resolution: {integrity: sha512-9fx6Zj/7vve/Fp4iexUFRKb5+RjLCff6YTRQl4CoDhdMfDoobWmhAxQWV3NfShMzQk1Q/iCnageFyGfqnsmeqQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.22.5': + resolution: {integrity: sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.22.0': - resolution: {integrity: sha512-VWQiCcN7zBgZYLjndIEh5tamtnKg5TGxyZPWcN9zBtXBwfcGSZ5cHSdQZfQH/GB4uRxk0D3VYbOEe/chJhPGLQ==} + '@rollup/rollup-linux-riscv64-gnu@4.22.5': + resolution: {integrity: sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.22.0': - resolution: {integrity: sha512-EHmPnPWvyYqncObwqrosb/CpH3GOjE76vWVs0g4hWsDRUVhg61hBmlVg5TPXqF+g+PvIbqkC7i3h8wbn4Gp2Fg==} + '@rollup/rollup-linux-s390x-gnu@4.22.5': + resolution: {integrity: sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.22.0': - resolution: {integrity: sha512-tsSWy3YQzmpjDKnQ1Vcpy3p9Z+kMFbSIesCdMNgLizDWFhrLZIoN21JSq01g+MZMDFF+Y1+4zxgrlqPjid5ohg==} + '@rollup/rollup-linux-x64-gnu@4.22.5': + resolution: {integrity: sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.22.0': - resolution: {integrity: sha512-anr1Y11uPOQrpuU8XOikY5lH4Qu94oS6j0xrulHk3NkLDq19MlX8Ng/pVipjxBJ9a2l3+F39REZYyWQFkZ4/fw==} + '@rollup/rollup-linux-x64-musl@4.22.5': + resolution: {integrity: sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.22.0': - resolution: {integrity: sha512-7LB+Bh+Ut7cfmO0m244/asvtIGQr5pG5Rvjz/l1Rnz1kDzM02pSX9jPaS0p+90H5I1x4d1FkCew+B7MOnoatNw==} + '@rollup/rollup-win32-arm64-msvc@4.22.5': + resolution: {integrity: sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.22.0': - resolution: {integrity: sha512-+3qZ4rer7t/QsC5JwMpcvCVPRcJt1cJrYS/TMJZzXIJbxWFQEVhrIc26IhB+5Z9fT9umfVc+Es2mOZgl+7jdJQ==} + '@rollup/rollup-win32-ia32-msvc@4.22.5': + resolution: {integrity: sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.22.0': - resolution: {integrity: sha512-YdicNOSJONVx/vuPkgPTyRoAPx3GbknBZRCOUkK84FJ/YTfs/F0vl/YsMscrB6Y177d+yDRcj+JWMPMCgshwrA==} + '@rollup/rollup-win32-x64-msvc@4.22.5': + resolution: {integrity: sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==} cpu: [x64] os: [win32] - '@shikijs/core@1.18.0': - resolution: {integrity: sha512-VK4BNVCd2leY62Nm2JjyxtRLkyrZT/tv104O81eyaCjHq4Adceq2uJVFJJAIof6lT1mBwZrEo2qT/T+grv3MQQ==} + '@shikijs/core@1.21.0': + resolution: {integrity: sha512-zAPMJdiGuqXpZQ+pWNezQAk5xhzRXBNiECFPcJLtUdsFM3f//G95Z15EHTnHchYycU8kIIysqGgxp8OVSj1SPQ==} - '@shikijs/engine-javascript@1.18.0': - resolution: {integrity: sha512-qoP/aO/ATNwYAUw1YMdaip/YVEstMZEgrwhePm83Ll9OeQPuxDZd48szZR8oSQNQBT8m8UlWxZv8EA3lFuyI5A==} + '@shikijs/engine-javascript@1.21.0': + resolution: {integrity: sha512-jxQHNtVP17edFW4/0vICqAVLDAxmyV31MQJL4U/Kg+heQALeKYVOWo0sMmEZ18FqBt+9UCdyqGKYE7bLRtk9mg==} - '@shikijs/engine-oniguruma@1.18.0': - resolution: {integrity: sha512-B9u0ZKI/cud+TcmF8Chyh+R4V5qQVvyDOqXC2l2a4x73PBSBc6sZ0JRAX3eqyJswqir6ktwApUUGBYePdKnMJg==} + '@shikijs/engine-oniguruma@1.21.0': + resolution: {integrity: sha512-AIZ76XocENCrtYzVU7S4GY/HL+tgHGbVU+qhiDyNw1qgCA5OSi4B4+HY4BtAoJSMGuD/L5hfTzoRVbzEm2WTvg==} - '@shikijs/types@1.18.0': - resolution: {integrity: sha512-O9N36UEaGGrxv1yUrN2nye7gDLG5Uq0/c1LyfmxsvzNPqlHzWo9DI0A4+fhW2y3bGKuQu/fwS7EPdKJJCowcVA==} + '@shikijs/types@1.21.0': + resolution: {integrity: sha512-tzndANDhi5DUndBtpojEq/42+dpUF2wS7wdCDQaFtIXm3Rd1QkrcVgSSRLOvEwexekihOXfbYJINW37g96tJRw==} '@shikijs/vscode-textmate@9.2.2': resolution: {integrity: sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==} @@ -898,9 +898,6 @@ packages: '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -922,8 +919,8 @@ packages: '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - '@types/node@22.5.5': - resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} + '@types/node@22.7.4': + resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} @@ -1051,8 +1048,8 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.3.1: - resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} array-iterate@2.0.1: @@ -1074,8 +1071,8 @@ packages: astro-sst@2.43.5: resolution: {integrity: sha512-CWSJO5Kdn7B6CI+qvd82T+ldqaI8y5HeQm4U9dGSSZe+v2KtArac94s2PaE27nxMTLy1JNG101cmyZj+1LhdjA==} - astro@4.15.8: - resolution: {integrity: sha512-pdXjtRF6O1xChiPAUF32R7oVRTW7AK1/Oy/JqPNhLfbelO0l6C7cLdSEuSLektwOEnMhOVXqccetjBs7HPaoxA==} + astro@4.15.9: + resolution: {integrity: sha512-51oXq9qrZ5OPWYmEXt1kGrvWmVeWsx28SgBTzi2XW6iwcnW/wC5ONm6ol6qBGSCF93tQvZplXvuzpaw1injECA==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true @@ -1119,8 +1116,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.23.3: - resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1132,8 +1129,8 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - caniuse-lite@1.0.30001662: - resolution: {integrity: sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==} + caniuse-lite@1.0.30001664: + resolution: {integrity: sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1286,8 +1283,8 @@ packages: resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} engines: {node: '>=18'} - devalue@5.0.0: - resolution: {integrity: sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==} + devalue@5.1.1: + resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -1313,11 +1310,11 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.25: - resolution: {integrity: sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==} + electron-to-chromium@1.5.30: + resolution: {integrity: sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA==} - emmet@2.4.8: - resolution: {integrity: sha512-wFe/dxsx7oi/M2UJ/3yBu4Fm24Irho6lqut4C1YFaZebCvCCMygoDGC7W6I+8+K8PAjfa/Ojn52UHi8WCdDiRA==} + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -1409,8 +1406,8 @@ packages: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} - fast-uri@3.0.1: - resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} + fast-uri@3.0.2: + resolution: {integrity: sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==} fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -1518,14 +1515,14 @@ packages: hast-util-has-property@3.0.0: resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} - hast-util-is-body-ok-link@3.0.0: - resolution: {integrity: sha512-VFHY5bo2nY8HiV6nir2ynmEB1XkxzuUffhEGeVx7orbu/B1KaGyeGgMZldvMVx5xWrDlLLG/kQ6YkJAMkBEx0w==} + hast-util-is-body-ok-link@3.0.1: + resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} hast-util-is-element@3.0.0: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} - hast-util-minify-whitespace@1.0.0: - resolution: {integrity: sha512-gD1m4YJSIk62ij32TlhFNqsC3dOQvpA4QAhyZOZFAT4u8LfEfB6N+F0V9oXQGBWXoqrs0h9wQRKa8RCeo8j61g==} + hast-util-minify-whitespace@1.0.1: + resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} @@ -1551,8 +1548,8 @@ packages: hast-util-to-parse5@8.0.0: resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} - hast-util-to-string@3.0.0: - resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} @@ -1579,8 +1576,8 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - html-whitespace-sensitive-tag-names@3.0.0: - resolution: {integrity: sha512-KlClZ3/Qy5UgvpvVvDomGhnQhNWH5INE8GwvSIQ9CWt1K0zbbXrl7eN5bWaafOZgtmO3jMPwUqmrmEwinhPq1w==} + html-whitespace-sensitive-tag-names@3.0.1: + resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==} http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -2101,8 +2098,8 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} pagefind@1.1.1: resolution: {integrity: sha512-U2YR0dQN5B2fbIXrLtt/UXNS0yWSSYfePaad1KcBPTi0p+zRtsVjwmoPaMQgTks5DnHNbmDxyJUL5TGaLljK3A==} @@ -2219,17 +2216,17 @@ packages: rehype-format@5.0.1: resolution: {integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==} - rehype-parse@9.0.0: - resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==} + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} - rehype-stringify@10.0.0: - resolution: {integrity: sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==} + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} - rehype@13.0.1: - resolution: {integrity: sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg==} + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} remark-directive@3.0.0: resolution: {integrity: sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==} @@ -2304,8 +2301,8 @@ packages: esbuild: '>=0.18.0' rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - rollup@4.22.0: - resolution: {integrity: sha512-W21MUIFPZ4+O2Je/EU+GP3iz7PH4pVPUXSbEZdatQnxo29+3rsUjgrJmzuAZU24z7yRAnFN6ukxeAhZh/c7hzg==} + rollup@4.22.5: + resolution: {integrity: sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2332,8 +2329,8 @@ packages: engines: {node: '>=10'} hasBin: true - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} server-destroy@1.0.1: @@ -2357,8 +2354,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.18.0: - resolution: {integrity: sha512-8jo7tOXr96h9PBQmOHVrltnETn1honZZY76YA79MHheGQg55jBvbm9dtU+MI5pjC5NJCFuA6rvVTLVeSW5cE4A==} + shiki@1.21.0: + resolution: {integrity: sha512-apCH5BoWTrmHDPGgg3RF8+HAAbEL/CdbYr8rMw7eIrdhCkZHdVGat5mMNlRtd1erNG01VPMIKHNQ0Pj2HMAiog==} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2392,33 +2389,33 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - sst-darwin-arm64@3.1.25: - resolution: {integrity: sha512-AVVBlV1AjwMyHx2hVXRVNlzgURveEjrx3u0RAw6Qn5nqb8kHEYrRejU8iEB2QkrXwJzb9UReCpmiicY+vcv+nA==} + sst-darwin-arm64@3.1.49: + resolution: {integrity: sha512-R9NKAzFXnQwyoc9OAV6sAEWQ5WhwOTt6u69q1+DS4sVdkUe0kK4LHkd1k5LAZy9rb5cGiKUFshKXGMQzC2kBJQ==} cpu: [arm64] os: [darwin] - sst-darwin-x64@3.1.25: - resolution: {integrity: sha512-s0K3D6r7tr1a4oM0z35FO7g1bkRsWAxYZA4rLU3/P3qvm2xMTJ850OMphh3KLFpLlLx+TNd9R7inwXjZifjlxQ==} + sst-darwin-x64@3.1.49: + resolution: {integrity: sha512-bFhBCVeiYrDxmG7WhsUeoG6R+Jae/O9gNP992/0lmfYySIoAN5gHCJMog2LwekDv0NLGwJhY77td6fSuEVdzXw==} cpu: [x64] os: [darwin] - sst-linux-arm64@3.1.25: - resolution: {integrity: sha512-FvVdx0ynqvXeObM10ofHfbDVq3xCzHGfGmca03euw+kljYwNoLetFB32xeuYl4RmreTM8hr/wk7J/toPH+McNA==} + sst-linux-arm64@3.1.49: + resolution: {integrity: sha512-AeNarbV05u5IXrtuuhPl/5jusKTdYn3jpdhH7zkZw+VQrNc4lxQGJ2t90jMqtf/oygVxXM5hKzHaQq8Umhh0AQ==} cpu: [arm64] os: [linux] - sst-linux-x64@3.1.25: - resolution: {integrity: sha512-GzDrChDvoPy8JXkhlwUXSVmjqc3eU1QRtL6R1E7Ef1+DiJvIQMhVidSZ6ZLrDamJr2USHMzkanhJSGF6DiKQmg==} + sst-linux-x64@3.1.49: + resolution: {integrity: sha512-TPFboRfU0dnq5gSJ8fMMdM/nzlc4m0wY/FxEyoMhV+mR3zbjSfJtMgAQJpUjXIyhH4fsxZzC7wZWMa2nA5GTbw==} cpu: [x64] os: [linux] - sst-linux-x86@3.1.25: - resolution: {integrity: sha512-+5fdexQ2FzW42muvzR/jcgGXqri9Yxh+j+qLgIITzEWGnaB/nTOmFc4TTqQtrCaW4vcvtlUnXe85UIB/tzkDow==} + sst-linux-x86@3.1.49: + resolution: {integrity: sha512-CZ3CmLxUK91umWIw9dtZpegAms0EibqspB8nncrbzeUTHHx2o3RpXGR7/giQSYQdXdwNC75wxOUEk6/4E2Alzw==} cpu: [x86] os: [linux] - sst@3.1.25: - resolution: {integrity: sha512-hMm0OV8gTmsgotxTMj+J5AuvgK6XDno/toq7JcSFnvWeufSBtl/3HY6SdE99iu13r9Un99s/t6uXcZDd8zZySA==} + sst@3.1.49: + resolution: {integrity: sha512-iwxgceCGzqzBThh6SgvigT6FoFH/Y+WVIGH/WbaVvB09+JvFZbuMbP2JU/PjxTBcupEN2KC9/cv7OJo4LDeTnw==} hasBin: true peerDependencies: hono: 4.x @@ -2599,8 +2596,8 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - update-browserslist-db@1.1.0: - resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2622,8 +2619,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite@5.4.6: - resolution: {integrity: sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==} + vite@5.4.8: + resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2945,12 +2942,12 @@ snapshots: import-meta-resolve: 4.1.0 mdast-util-definitions: 6.0.0 rehype-raw: 7.0.0 - rehype-stringify: 10.0.0 + rehype-stringify: 10.0.1 remark-gfm: 4.0.0 remark-parse: 11.0.0 remark-rehype: 11.1.1 remark-smartypants: 3.0.2 - shiki: 1.18.0 + shiki: 1.21.0 unified: 11.0.5 unist-util-remove-position: 5.0.0 unist-util-visit: 5.0.0 @@ -2959,12 +2956,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@3.1.7(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2))': + '@astrojs/mdx@3.1.7(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2))': dependencies: '@astrojs/markdown-remark': 5.2.0 '@mdx-js/mdx': 3.0.1 acorn: 8.12.1 - astro: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + astro: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 gray-matter: 4.0.3 @@ -2979,10 +2976,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/node@8.3.3(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2))': + '@astrojs/node@8.3.4(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2))': dependencies: - astro: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) - send: 0.18.0 + astro: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) + send: 0.19.0 server-destroy: 1.0.1 transitivePeerDependencies: - supports-color @@ -2997,26 +2994,26 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.23.8 - '@astrojs/starlight@0.28.2(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2))': + '@astrojs/starlight@0.28.2(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2))': dependencies: - '@astrojs/mdx': 3.1.7(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)) + '@astrojs/mdx': 3.1.7(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)) '@astrojs/sitemap': 3.1.6 '@pagefind/default-ui': 1.1.1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - astro: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) - astro-expressive-code: 0.35.6(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)) + astro: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) + astro-expressive-code: 0.35.6(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.2 - hast-util-to-string: 3.0.0 + hast-util-to-string: 3.0.1 hastscript: 9.0.0 i18next: 23.15.1 mdast-util-directive: 3.0.0 mdast-util-to-markdown: 2.1.0 mdast-util-to-string: 4.0.0 pagefind: 1.1.1 - rehype: 13.0.1 + rehype: 13.0.2 rehype-format: 5.0.1 remark-directive: 3.0.0 unified: 11.0.5 @@ -3097,7 +3094,7 @@ snapshots: dependencies: '@babel/compat-data': 7.25.4 '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.3 + browserslist: 4.24.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -3348,7 +3345,7 @@ snapshots: '@expressive-code/plugin-shiki@0.35.6': dependencies: '@expressive-code/core': 0.35.6 - shiki: 1.18.0 + shiki: 1.21.0 '@expressive-code/plugin-text-markers@0.35.6': dependencies: @@ -3487,37 +3484,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@moonrepo/cli@1.28.2': + '@moonrepo/cli@1.28.3': dependencies: detect-libc: 2.0.3 optionalDependencies: - '@moonrepo/core-linux-arm64-gnu': 1.28.2 - '@moonrepo/core-linux-arm64-musl': 1.28.2 - '@moonrepo/core-linux-x64-gnu': 1.28.2 - '@moonrepo/core-linux-x64-musl': 1.28.2 - '@moonrepo/core-macos-arm64': 1.28.2 - '@moonrepo/core-macos-x64': 1.28.2 - '@moonrepo/core-windows-x64-msvc': 1.28.2 - - '@moonrepo/core-linux-arm64-gnu@1.28.2': + '@moonrepo/core-linux-arm64-gnu': 1.28.3 + '@moonrepo/core-linux-arm64-musl': 1.28.3 + '@moonrepo/core-linux-x64-gnu': 1.28.3 + '@moonrepo/core-linux-x64-musl': 1.28.3 + '@moonrepo/core-macos-arm64': 1.28.3 + '@moonrepo/core-macos-x64': 1.28.3 + '@moonrepo/core-windows-x64-msvc': 1.28.3 + + '@moonrepo/core-linux-arm64-gnu@1.28.3': optional: true - '@moonrepo/core-linux-arm64-musl@1.28.2': + '@moonrepo/core-linux-arm64-musl@1.28.3': optional: true - '@moonrepo/core-linux-x64-gnu@1.28.2': + '@moonrepo/core-linux-x64-gnu@1.28.3': optional: true - '@moonrepo/core-linux-x64-musl@1.28.2': + '@moonrepo/core-linux-x64-musl@1.28.3': optional: true - '@moonrepo/core-macos-arm64@1.28.2': + '@moonrepo/core-macos-arm64@1.28.3': optional: true - '@moonrepo/core-macos-x64@1.28.2': + '@moonrepo/core-macos-x64@1.28.3': optional: true - '@moonrepo/core-windows-x64-msvc@1.28.2': + '@moonrepo/core-windows-x64-msvc@1.28.3': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3532,7 +3529,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@oslojs/encoding@0.4.1': {} + '@oslojs/encoding@1.1.0': {} '@pagefind/darwin-arm64@1.1.1': optional: true @@ -3554,83 +3551,83 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/pluginutils@5.1.0(rollup@4.22.0)': + '@rollup/pluginutils@5.1.2(rollup@4.22.5)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.22.0 + rollup: 4.22.5 - '@rollup/rollup-android-arm-eabi@4.22.0': + '@rollup/rollup-android-arm-eabi@4.22.5': optional: true - '@rollup/rollup-android-arm64@4.22.0': + '@rollup/rollup-android-arm64@4.22.5': optional: true - '@rollup/rollup-darwin-arm64@4.22.0': + '@rollup/rollup-darwin-arm64@4.22.5': optional: true - '@rollup/rollup-darwin-x64@4.22.0': + '@rollup/rollup-darwin-x64@4.22.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.22.0': + '@rollup/rollup-linux-arm-gnueabihf@4.22.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.22.0': + '@rollup/rollup-linux-arm-musleabihf@4.22.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.22.0': + '@rollup/rollup-linux-arm64-gnu@4.22.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.22.0': + '@rollup/rollup-linux-arm64-musl@4.22.5': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.22.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.22.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.22.0': + '@rollup/rollup-linux-riscv64-gnu@4.22.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.22.0': + '@rollup/rollup-linux-s390x-gnu@4.22.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.22.0': + '@rollup/rollup-linux-x64-gnu@4.22.5': optional: true - '@rollup/rollup-linux-x64-musl@4.22.0': + '@rollup/rollup-linux-x64-musl@4.22.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.22.0': + '@rollup/rollup-win32-arm64-msvc@4.22.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.22.0': + '@rollup/rollup-win32-ia32-msvc@4.22.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.22.0': + '@rollup/rollup-win32-x64-msvc@4.22.5': optional: true - '@shikijs/core@1.18.0': + '@shikijs/core@1.21.0': dependencies: - '@shikijs/engine-javascript': 1.18.0 - '@shikijs/engine-oniguruma': 1.18.0 - '@shikijs/types': 1.18.0 + '@shikijs/engine-javascript': 1.21.0 + '@shikijs/engine-oniguruma': 1.21.0 + '@shikijs/types': 1.21.0 '@shikijs/vscode-textmate': 9.2.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 - '@shikijs/engine-javascript@1.18.0': + '@shikijs/engine-javascript@1.21.0': dependencies: - '@shikijs/types': 1.18.0 + '@shikijs/types': 1.21.0 '@shikijs/vscode-textmate': 9.2.2 oniguruma-to-js: 0.4.3 - '@shikijs/engine-oniguruma@1.18.0': + '@shikijs/engine-oniguruma@1.21.0': dependencies: - '@shikijs/types': 1.18.0 + '@shikijs/types': 1.21.0 '@shikijs/vscode-textmate': 9.2.2 - '@shikijs/types@1.18.0': + '@shikijs/types@1.21.0': dependencies: '@shikijs/vscode-textmate': 9.2.2 '@types/hast': 3.0.4 @@ -3672,8 +3669,6 @@ snapshots: dependencies: '@types/estree': 1.0.6 - '@types/estree@1.0.5': {} - '@types/estree@1.0.6': {} '@types/hast@3.0.4': @@ -3694,13 +3689,13 @@ snapshots: '@types/node@17.0.45': {} - '@types/node@22.5.5': + '@types/node@22.7.4': dependencies: undici-types: 6.19.8 '@types/sax@1.2.7': dependencies: - '@types/node': 22.5.5 + '@types/node': 17.0.45 '@types/unist@2.0.11': {} @@ -3708,7 +3703,7 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitest/coverage-v8@2.1.1(vitest@2.1.1(@types/node@22.5.5))': + '@vitest/coverage-v8@2.1.1(vitest@2.1.1(@types/node@22.7.4))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -3722,7 +3717,7 @@ snapshots: std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.1(@types/node@22.5.5) + vitest: 2.1.1(@types/node@22.7.4) transitivePeerDependencies: - supports-color @@ -3733,13 +3728,13 @@ snapshots: chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5))': + '@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.8(@types/node@22.7.4))': dependencies: '@vitest/spy': 2.1.1 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: - vite: 5.4.6(@types/node@22.5.5) + vite: 5.4.8(@types/node@22.7.4) '@vitest/pretty-format@2.1.1': dependencies: @@ -3808,7 +3803,7 @@ snapshots: '@vscode/emmet-helper@2.9.3': dependencies: - emmet: 2.4.8 + emmet: 2.4.11 jsonc-parser: 2.3.1 vscode-languageserver-textdocument: 1.0.12 vscode-languageserver-types: 3.17.5 @@ -3825,7 +3820,7 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.1 + fast-uri: 3.0.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -3860,7 +3855,7 @@ snapshots: argparse@2.0.1: {} - aria-query@5.3.1: {} + aria-query@5.3.2: {} array-iterate@2.0.1: {} @@ -3868,9 +3863,9 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.35.6(astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2)): + astro-expressive-code@0.35.6(astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2)): dependencies: - astro: 4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2) + astro: 4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2) rehype-expressive-code: 0.35.6 astro-sst@2.43.5: @@ -3878,7 +3873,7 @@ snapshots: '@astrojs/webapi': 3.0.0 set-cookie-parser: 2.7.0 - astro@4.15.8(@types/node@22.5.5)(rollup@4.22.0)(typescript@5.6.2): + astro@4.15.9(@types/node@22.7.4)(rollup@4.22.5)(typescript@5.6.2): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.1 @@ -3887,12 +3882,12 @@ snapshots: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.2) '@babel/types': 7.25.6 - '@oslojs/encoding': 0.4.1 - '@rollup/pluginutils': 5.1.0(rollup@4.22.0) + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.1.2(rollup@4.22.5) '@types/babel__core': 7.20.5 '@types/cookie': 0.6.0 acorn: 8.12.1 - aria-query: 5.3.1 + aria-query: 5.3.2 axobject-query: 4.1.0 boxen: 7.1.1 ci-info: 4.0.0 @@ -3902,7 +3897,7 @@ snapshots: cssesc: 3.0.0 debug: 4.3.7 deterministic-object-hash: 2.0.2 - devalue: 5.0.0 + devalue: 5.1.1 diff: 5.2.0 dlv: 1.1.3 dset: 3.1.4 @@ -3928,17 +3923,17 @@ snapshots: p-queue: 8.0.1 preferred-pm: 4.0.0 prompts: 2.4.2 - rehype: 13.0.1 + rehype: 13.0.2 semver: 7.6.3 - shiki: 1.18.0 + shiki: 1.21.0 string-width: 7.2.0 strip-ansi: 7.1.0 tinyexec: 0.3.0 tsconfck: 3.1.3(typescript@5.6.2) unist-util-visit: 5.0.0 vfile: 6.0.3 - vite: 5.4.6(@types/node@22.5.5) - vitefu: 1.0.2(vite@5.4.6(@types/node@22.5.5)) + vite: 5.4.8(@types/node@22.7.4) + vitefu: 1.0.2(vite@5.4.8(@types/node@22.7.4)) which-pm: 3.0.0 xxhash-wasm: 1.0.2 yargs-parser: 21.1.1 @@ -4001,18 +3996,18 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.23.3: + browserslist@4.24.0: dependencies: - caniuse-lite: 1.0.30001662 - electron-to-chromium: 1.5.25 + caniuse-lite: 1.0.30001664 + electron-to-chromium: 1.5.30 node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.23.3) + update-browserslist-db: 1.1.1(browserslist@4.24.0) cac@6.7.14: {} camelcase@7.0.1: {} - caniuse-lite@1.0.30001662: {} + caniuse-lite@1.0.30001664: {} ccount@2.0.1: {} @@ -4140,7 +4135,7 @@ snapshots: dependencies: base-64: 1.0.0 - devalue@5.0.0: {} + devalue@5.1.1: {} devlop@1.1.0: dependencies: @@ -4158,9 +4153,9 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.25: {} + electron-to-chromium@1.5.30: {} - emmet@2.4.8: + emmet@2.4.11: dependencies: '@emmetio/abbreviation': 2.3.3 '@emmetio/css-abbreviation': 2.1.8 @@ -4270,7 +4265,7 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-uri@3.0.1: {} + fast-uri@3.0.2: {} fastq@1.17.1: dependencies: @@ -4330,7 +4325,7 @@ snapshots: jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 - package-json-from-dist: 1.0.0 + package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@8.1.0: @@ -4365,10 +4360,10 @@ snapshots: dependencies: '@types/hast': 3.0.4 hast-util-embedded: 3.0.0 - hast-util-minify-whitespace: 1.0.0 + hast-util-minify-whitespace: 1.0.1 hast-util-phrasing: 3.0.1 hast-util-whitespace: 3.0.0 - html-whitespace-sensitive-tag-names: 3.0.0 + html-whitespace-sensitive-tag-names: 3.0.1 unist-util-visit-parents: 6.0.1 hast-util-from-html@2.0.3: @@ -4395,7 +4390,7 @@ snapshots: dependencies: '@types/hast': 3.0.4 - hast-util-is-body-ok-link@3.0.0: + hast-util-is-body-ok-link@3.0.1: dependencies: '@types/hast': 3.0.4 @@ -4403,7 +4398,7 @@ snapshots: dependencies: '@types/hast': 3.0.4 - hast-util-minify-whitespace@1.0.0: + hast-util-minify-whitespace@1.0.1: dependencies: '@types/hast': 3.0.4 hast-util-embedded: 3.0.0 @@ -4420,7 +4415,7 @@ snapshots: '@types/hast': 3.0.4 hast-util-embedded: 3.0.0 hast-util-has-property: 3.0.0 - hast-util-is-body-ok-link: 3.0.0 + hast-util-is-body-ok-link: 3.0.1 hast-util-is-element: 3.0.0 hast-util-raw@9.0.4: @@ -4449,7 +4444,7 @@ snapshots: devlop: 1.1.0 direction: 2.0.1 hast-util-has-property: 3.0.0 - hast-util-to-string: 3.0.0 + hast-util-to-string: 3.0.1 hast-util-whitespace: 3.0.0 not: 0.1.0 nth-check: 2.1.1 @@ -4523,7 +4518,7 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 - hast-util-to-string@3.0.0: + hast-util-to-string@3.0.1: dependencies: '@types/hast': 3.0.4 @@ -4563,7 +4558,7 @@ snapshots: html-void-elements@3.0.0: {} - html-whitespace-sensitive-tag-names@3.0.0: {} + html-whitespace-sensitive-tag-names@3.0.1: {} http-cache-semantics@4.1.1: {} @@ -5338,7 +5333,7 @@ snapshots: p-try@2.2.0: {} - package-json-from-dist@1.0.0: {} + package-json-from-dist@1.0.1: {} pagefind@1.1.1: optionalDependencies: @@ -5464,7 +5459,7 @@ snapshots: '@types/hast': 3.0.4 hast-util-format: 1.1.0 - rehype-parse@9.0.0: + rehype-parse@9.0.1: dependencies: '@types/hast': 3.0.4 hast-util-from-html: 2.0.3 @@ -5476,17 +5471,17 @@ snapshots: hast-util-raw: 9.0.4 vfile: 6.0.3 - rehype-stringify@10.0.0: + rehype-stringify@10.0.1: dependencies: '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 unified: 11.0.5 - rehype@13.0.1: + rehype@13.0.2: dependencies: '@types/hast': 3.0.4 - rehype-parse: 9.0.0 - rehype-stringify: 10.0.0 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 unified: 11.0.5 remark-directive@3.0.0: @@ -5588,45 +5583,45 @@ snapshots: reusify@1.0.4: {} - rollup-plugin-dts@6.1.1(rollup@4.22.0)(typescript@5.6.2): + rollup-plugin-dts@6.1.1(rollup@4.22.5)(typescript@5.6.2): dependencies: magic-string: 0.30.11 - rollup: 4.22.0 + rollup: 4.22.5 typescript: 5.6.2 optionalDependencies: '@babel/code-frame': 7.24.7 - rollup-plugin-esbuild@6.1.1(esbuild@0.21.5)(rollup@4.22.0): + rollup-plugin-esbuild@6.1.1(esbuild@0.21.5)(rollup@4.22.5): dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.22.0) + '@rollup/pluginutils': 5.1.2(rollup@4.22.5) debug: 4.3.7 es-module-lexer: 1.5.4 esbuild: 0.21.5 get-tsconfig: 4.8.1 - rollup: 4.22.0 + rollup: 4.22.5 transitivePeerDependencies: - supports-color - rollup@4.22.0: + rollup@4.22.5: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.22.0 - '@rollup/rollup-android-arm64': 4.22.0 - '@rollup/rollup-darwin-arm64': 4.22.0 - '@rollup/rollup-darwin-x64': 4.22.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.22.0 - '@rollup/rollup-linux-arm-musleabihf': 4.22.0 - '@rollup/rollup-linux-arm64-gnu': 4.22.0 - '@rollup/rollup-linux-arm64-musl': 4.22.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.22.0 - '@rollup/rollup-linux-riscv64-gnu': 4.22.0 - '@rollup/rollup-linux-s390x-gnu': 4.22.0 - '@rollup/rollup-linux-x64-gnu': 4.22.0 - '@rollup/rollup-linux-x64-musl': 4.22.0 - '@rollup/rollup-win32-arm64-msvc': 4.22.0 - '@rollup/rollup-win32-ia32-msvc': 4.22.0 - '@rollup/rollup-win32-x64-msvc': 4.22.0 + '@rollup/rollup-android-arm-eabi': 4.22.5 + '@rollup/rollup-android-arm64': 4.22.5 + '@rollup/rollup-darwin-arm64': 4.22.5 + '@rollup/rollup-darwin-x64': 4.22.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.22.5 + '@rollup/rollup-linux-arm-musleabihf': 4.22.5 + '@rollup/rollup-linux-arm64-gnu': 4.22.5 + '@rollup/rollup-linux-arm64-musl': 4.22.5 + '@rollup/rollup-linux-powerpc64le-gnu': 4.22.5 + '@rollup/rollup-linux-riscv64-gnu': 4.22.5 + '@rollup/rollup-linux-s390x-gnu': 4.22.5 + '@rollup/rollup-linux-x64-gnu': 4.22.5 + '@rollup/rollup-linux-x64-musl': 4.22.5 + '@rollup/rollup-win32-arm64-msvc': 4.22.5 + '@rollup/rollup-win32-ia32-msvc': 4.22.5 + '@rollup/rollup-win32-x64-msvc': 4.22.5 fsevents: 2.3.3 run-parallel@1.2.0: @@ -5648,7 +5643,7 @@ snapshots: semver@7.6.3: {} - send@0.18.0: + send@0.19.0: dependencies: debug: 2.6.9 depd: 2.0.0 @@ -5704,12 +5699,12 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.18.0: + shiki@1.21.0: dependencies: - '@shikijs/core': 1.18.0 - '@shikijs/engine-javascript': 1.18.0 - '@shikijs/engine-oniguruma': 1.18.0 - '@shikijs/types': 1.18.0 + '@shikijs/core': 1.21.0 + '@shikijs/engine-javascript': 1.21.0 + '@shikijs/engine-oniguruma': 1.21.0 + '@shikijs/types': 1.21.0 '@shikijs/vscode-textmate': 9.2.2 '@types/hast': 3.0.4 @@ -5738,33 +5733,33 @@ snapshots: sprintf-js@1.0.3: {} - sst-darwin-arm64@3.1.25: + sst-darwin-arm64@3.1.49: optional: true - sst-darwin-x64@3.1.25: + sst-darwin-x64@3.1.49: optional: true - sst-linux-arm64@3.1.25: + sst-linux-arm64@3.1.49: optional: true - sst-linux-x64@3.1.25: + sst-linux-x64@3.1.49: optional: true - sst-linux-x86@3.1.25: + sst-linux-x86@3.1.49: optional: true - sst@3.1.25(hono@4.0.1): + sst@3.1.49(hono@4.0.1): dependencies: aws4fetch: 1.0.20 jose: 5.2.3 openid-client: 5.6.4 optionalDependencies: hono: 4.0.1 - sst-darwin-arm64: 3.1.25 - sst-darwin-x64: 3.1.25 - sst-linux-arm64: 3.1.25 - sst-linux-x64: 3.1.25 - sst-linux-x86: 3.1.25 + sst-darwin-arm64: 3.1.49 + sst-darwin-x64: 3.1.49 + sst-linux-arm64: 3.1.49 + sst-linux-x64: 3.1.49 + sst-linux-x86: 3.1.49 stackback@0.0.2: {} @@ -5934,9 +5929,9 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - update-browserslist-db@1.1.0(browserslist@4.23.3): + update-browserslist-db@1.1.1(browserslist@4.24.0): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.0 escalade: 3.2.0 picocolors: 1.1.0 @@ -5957,12 +5952,12 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.1(@types/node@22.5.5): + vite-node@2.1.1(@types/node@22.7.4): dependencies: cac: 6.7.14 debug: 4.3.7 pathe: 1.1.2 - vite: 5.4.6(@types/node@22.5.5) + vite: 5.4.8(@types/node@22.7.4) transitivePeerDependencies: - '@types/node' - less @@ -5974,23 +5969,23 @@ snapshots: - supports-color - terser - vite@5.4.6(@types/node@22.5.5): + vite@5.4.8(@types/node@22.7.4): dependencies: esbuild: 0.21.5 postcss: 8.4.47 - rollup: 4.22.0 + rollup: 4.22.5 optionalDependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.4 fsevents: 2.3.3 - vitefu@1.0.2(vite@5.4.6(@types/node@22.5.5)): + vitefu@1.0.2(vite@5.4.8(@types/node@22.7.4)): optionalDependencies: - vite: 5.4.6(@types/node@22.5.5) + vite: 5.4.8(@types/node@22.7.4) - vitest@2.1.1(@types/node@22.5.5): + vitest@2.1.1(@types/node@22.7.4): dependencies: '@vitest/expect': 2.1.1 - '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.6(@types/node@22.5.5)) + '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.8(@types/node@22.7.4)) '@vitest/pretty-format': 2.1.1 '@vitest/runner': 2.1.1 '@vitest/snapshot': 2.1.1 @@ -6005,11 +6000,11 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.6(@types/node@22.5.5) - vite-node: 2.1.1(@types/node@22.5.5) + vite: 5.4.8(@types/node@22.7.4) + vite-node: 2.1.1(@types/node@22.7.4) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.5.5 + '@types/node': 22.7.4 transitivePeerDependencies: - less - lightningcss