diff --git a/.changeset/purple-spies-give.md b/.changeset/purple-spies-give.md new file mode 100644 index 000000000000..d611a220b884 --- /dev/null +++ b/.changeset/purple-spies-give.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Add support for Content-Type and Content-Disposition headers in SSG diff --git a/.gitignore b/.gitignore index 8626a30a0ec1..292cc0a5f605 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ package-lock.json .turbo/ .eslintcache .pnpm-store +.envrc +devbox.json +devbox.lock # do not commit .env files or any files that end with `.env` *.env diff --git a/.vscode/settings.json b/.vscode/settings.json index 97aeabec0f36..11cb6d1e951a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "biome.enabled": true, "[json]": { "editor.defaultFormatter": "biomejs.biome" }, diff --git a/packages/astro/package.json b/packages/astro/package.json index 0ed30c0555b5..598bc12f1170 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -132,6 +132,8 @@ "ci-info": "^4.1.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", + "content-disposition": "^0.5.4", + "content-type": "^1.0.5", "cookie": "^0.7.2", "cssesc": "^3.0.0", "debug": "^4.4.0", @@ -186,6 +188,8 @@ "@playwright/test": "^1.49.1", "@types/aria-query": "^5.0.4", "@types/common-ancestor-path": "^1.0.2", + "@types/content-disposition": "^0.5.8", + "@types/content-type": "^1.1.8", "@types/cssesc": "^3.0.2", "@types/debug": "^4.1.12", "@types/diff": "^5.2.3", diff --git a/packages/astro/src/core/build/common.ts b/packages/astro/src/core/build/common.ts index 4ee826f8b221..8584a3759abf 100644 --- a/packages/astro/src/core/build/common.ts +++ b/packages/astro/src/core/build/common.ts @@ -4,6 +4,7 @@ import { appendForwardSlash } from '../../core/path.js'; import type { AstroSettings } from '../../types/astro.js'; import type { AstroConfig } from '../../types/public/config.js'; import type { RouteData } from '../../types/public/internal.js'; +import type { FileDescriptor } from './util.js'; const STATUS_CODE_PAGES = new Set(['/404', '/500']); const FALLBACK_OUT_DIR_NAME = './.astro/'; @@ -20,6 +21,7 @@ export function getOutFolder( astroSettings: AstroSettings, pathname: string, routeData: RouteData, + fileDescriptor?: FileDescriptor, ): URL { const outRoot = getOutRoot(astroSettings); const routeType = routeData.type; @@ -31,6 +33,15 @@ export function getOutFolder( case 'fallback': case 'page': case 'redirect': + if (fileDescriptor?.isHtml === false) { + if (pathname === '' || routeData.isIndex) { + throw new Error(`Root must be html`); + } + const dirname = npath.dirname(pathname); + const result = new URL('.' + appendForwardSlash(dirname), outRoot); + + return result; + } switch (astroSettings.config.build.format) { case 'directory': { if (STATUS_CODE_PAGES.has(pathname)) { @@ -62,6 +73,7 @@ export function getOutFile( outFolder: URL, pathname: string, routeData: RouteData, + fileDescriptor: FileDescriptor | null = null, ): URL { const routeType = routeData.type; switch (routeType) { @@ -70,6 +82,17 @@ export function getOutFile( case 'page': case 'fallback': case 'redirect': + if (fileDescriptor?.isHtml === false) { + let baseName = fileDescriptor.filename ?? npath.basename(pathname); + // If there is no base name this is the root route. + // If this is an index route, the name should be `index.html`. + if (!baseName || routeData.isIndex) { + throw new Error(`Root must be html`); + } + const result = new URL(`./${baseName}`, outFolder); + + return result; + } switch (astroConfig.build.format) { case 'directory': { if (STATUS_CODE_PAGES.has(pathname)) { diff --git a/packages/astro/src/core/build/contentHeaders.ts b/packages/astro/src/core/build/contentHeaders.ts new file mode 100644 index 000000000000..7c4e40cd57be --- /dev/null +++ b/packages/astro/src/core/build/contentHeaders.ts @@ -0,0 +1,56 @@ +import contentTypeLib from 'content-type'; +import contentDispositionLib from 'content-disposition'; + +export class ContentHeaders { + #contentType: contentTypeLib.ParsedMediaType | null; + #contentDisposition: contentDispositionLib.ContentDisposition | null; + + constructor(headers: Headers) { + this.#contentType = ContentHeaders.#parseContentType(headers); + this.#contentDisposition = ContentHeaders.#parseContentDisposition(headers); + } + + get charset(): string | undefined { + return this.#contentType?.parameters.charset; + } + + get mediaType(): string | undefined { + return this.#contentType?.type; + } + + get filename(): string | undefined { + return this.#contentDisposition?.parameters.filename; + } + + static #parseContentType(headers: Headers): contentTypeLib.ParsedMediaType | null { + const header = headers.get('Content-Type'); + if (header == null) { + return null; + } + + try { + return contentTypeLib.parse(header); + } catch (err) { + console.error(`Had trouble parsing Content-Type header = "${header}"`, err); + } + + return null; + } + + static #parseContentDisposition( + headers: Headers, + ): contentDispositionLib.ContentDisposition | null { + const header = headers.get('Content-Disposition'); + if (header == null) { + return null; + } + + try { + return contentDispositionLib.parse(header); + } catch (err) { + console.error(`Had trouble parsing Content-Disposition header = "${header}"`, err); + } + + return null; + } +} diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 2d29ebcc61a2..01e27950e69a 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -48,7 +48,7 @@ import type { StaticBuildOptions, StylesheetAsset, } from './types.js'; -import { getTimeStat, shouldAppendForwardSlash } from './util.js'; +import { getFileDescriptorFromResponse, getTimeStat, shouldAppendForwardSlash } from './util.js'; export async function generatePages(options: StaticBuildOptions, internals: BuildInternals) { const generatePagesTimer = performance.now(); @@ -561,8 +561,10 @@ async function generatePath( // We encode the path because some paths will received encoded characters, e.g. /[page] VS /%5Bpage%5D. // Node.js decodes the paths, so to avoid a clash between paths, do encode paths again, so we create the correct files and folders requested by the user. const encodedPath = encodeURI(pathname); - const outFolder = getOutFolder(pipeline.settings, encodedPath, route); - const outFile = getOutFile(config, outFolder, encodedPath, route); + const fileDescriptor = getFileDescriptorFromResponse(response); + const outFolder = getOutFolder(pipeline.settings, encodedPath, route, fileDescriptor); + const outFile = getOutFile(config, outFolder, encodedPath, route, fileDescriptor); + if (route.distURL) { route.distURL.push(outFile); } else { @@ -570,7 +572,7 @@ async function generatePath( } await fs.promises.mkdir(outFolder, { recursive: true }); - await fs.promises.writeFile(outFile, body); + await fs.promises.writeFile(outFile, body, fileDescriptor.encoding); return true; } diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 14e0a08b455c..30809d68aa74 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -201,8 +201,14 @@ function buildManifest( if (!route.prerender) continue; if (!route.pathname) continue; - const outFolder = getOutFolder(opts.settings, route.pathname, route); - const outFile = getOutFile(opts.settings.config, outFolder, route.pathname, route); + const outFile = route.distURL + ? route.distURL + : getOutFile( + opts.settings.config, + getOutFolder(opts.settings, route.pathname, route), + route.pathname, + route, + ); const file = outFile.toString().replace(opts.settings.config.build.client.toString(), ''); routes.push({ file, diff --git a/packages/astro/src/core/build/util.ts b/packages/astro/src/core/build/util.ts index b6b313254379..2c187187f6c0 100644 --- a/packages/astro/src/core/build/util.ts +++ b/packages/astro/src/core/build/util.ts @@ -1,6 +1,7 @@ import type { Rollup } from 'vite'; import type { AstroConfig } from '../../types/public/config.js'; import type { ViteBuildReturn } from './types.js'; +import { ContentHeaders } from './contentHeaders.js'; export function getTimeStat(timeStart: number, timeEnd: number) { const buildTime = timeEnd - timeStart; @@ -67,3 +68,34 @@ export function viteBuildReturnToRollupOutputs( } return result; } + +export type FileDescriptor = { + filename: string | undefined; + encoding: BufferEncoding | undefined; + isHtml: boolean; +}; + +export function getFileDescriptorFromResponse(response: Response): FileDescriptor { + const headers = new ContentHeaders(response.headers); + return { + encoding: isBufferEncoding(headers.charset) ? headers.charset : undefined, + isHtml: headers.mediaType === 'text/html', + filename: headers.filename, + }; +} + +function isBufferEncoding(val: string | null | undefined): val is BufferEncoding { + return ( + val === 'ascii' || + val === 'utf8' || + val === 'utf-8' || + val === 'utf16le' || + val === 'ucs2' || + val === 'ucs-2' || + val === 'base64' || + val === 'base64url' || + val === 'latin1' || + val === 'binary' || + val === 'hex' + ); +} diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 635e57798c66..cb15bf87f016 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -608,6 +608,7 @@ export interface ViteUserConfig extends OriginalViteUserConfig { * } * ``` * + * This option is ignored when an astro template resolves to a non-html file, e.g. if middleware is used to override the filename of the template by providing a Content-Disposition header. * * * #### Effect on Astro.url diff --git a/packages/astro/test/fixtures/middleware-non-html-ssg/astro.config.mjs b/packages/astro/test/fixtures/middleware-non-html-ssg/astro.config.mjs new file mode 100644 index 000000000000..250672aaa68f --- /dev/null +++ b/packages/astro/test/fixtures/middleware-non-html-ssg/astro.config.mjs @@ -0,0 +1,5 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: "static", +}); diff --git a/packages/astro/test/fixtures/middleware-non-html-ssg/package.json b/packages/astro/test/fixtures/middleware-non-html-ssg/package.json new file mode 100644 index 000000000000..04e9c606ca81 --- /dev/null +++ b/packages/astro/test/fixtures/middleware-non-html-ssg/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/middleware-non-html-ssg", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/middleware-non-html-ssg/src/middleware.js b/packages/astro/test/fixtures/middleware-non-html-ssg/src/middleware.js new file mode 100644 index 000000000000..2c4f7b894c86 --- /dev/null +++ b/packages/astro/test/fixtures/middleware-non-html-ssg/src/middleware.js @@ -0,0 +1,27 @@ +import { defineMiddleware, sequence } from 'astro:middleware'; +import { promises as fs } from 'node:fs'; + +const first = defineMiddleware(async (context, next) => { + if (context.request.url.includes('/placeholder.png')) { + const imgURL = new URL('../../non-html-pages/src/images/placeholder.png', import.meta.url); + const buffer = await fs.readFile(imgURL); + return new Response(buffer.buffer, { + headers: { + "Content-Type": "image/png", + "Content-Disposition": `inline; filename="placeholder.png"`, + }, + }); + } else if (context.request.url.includes('/rename-me.json')) { + const content = JSON.stringify({name: "alan"}) + return new Response(content, { + headers: { + "Content-Type": "application/json", + "Content-Disposition": `inline; filename="data.json"`, + }, + }); + } + + return next(); +}); + +export const onRequest = sequence(first); diff --git a/packages/astro/test/fixtures/middleware-non-html-ssg/src/pages/placeholder.png.astro b/packages/astro/test/fixtures/middleware-non-html-ssg/src/pages/placeholder.png.astro new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/astro/test/fixtures/middleware-non-html-ssg/src/pages/rename-me.json.astro b/packages/astro/test/fixtures/middleware-non-html-ssg/src/pages/rename-me.json.astro new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/astro/test/fixtures/middleware-non-html-ssr/astro.config.mjs b/packages/astro/test/fixtures/middleware-non-html-ssr/astro.config.mjs new file mode 100644 index 000000000000..fb8776c50e94 --- /dev/null +++ b/packages/astro/test/fixtures/middleware-non-html-ssr/astro.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'astro/config'; +import node from "@astrojs/node"; + +export default defineConfig({ + output: "server", + adapter: node({ + mode: "standalone" + }) +}); diff --git a/packages/astro/test/fixtures/middleware-non-html-ssr/package.json b/packages/astro/test/fixtures/middleware-non-html-ssr/package.json new file mode 100644 index 000000000000..eb366d27724b --- /dev/null +++ b/packages/astro/test/fixtures/middleware-non-html-ssr/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/middleware-non-html-ssr", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/node": "^8.3.4" + } +} diff --git a/packages/astro/test/fixtures/middleware-non-html-ssr/src/middleware.js b/packages/astro/test/fixtures/middleware-non-html-ssr/src/middleware.js new file mode 100644 index 000000000000..d9f0c47990b5 --- /dev/null +++ b/packages/astro/test/fixtures/middleware-non-html-ssr/src/middleware.js @@ -0,0 +1,28 @@ +import { defineMiddleware, sequence } from 'astro:middleware'; +import { promises as fs } from 'node:fs'; + +const first = defineMiddleware(async (context, next) => { + if (context.request.url.includes('/placeholder.png')) { + const buffer = await fs.readFile('./test/fixtures/non-html-pages/src/images/placeholder.png'); + return new Response(buffer.buffer, { + headers: { + "Content-Type": "image/png", + "Content-Disposition": `inline; filename="placeholder.png"`, + }, + }); + } else if (context.request.url.includes('/rename-me.json')) { + const content = JSON.stringify({name: "alan"}) + return new Response(content, { + headers: { + "Content-Type": "application/json", + "Content-Disposition": `inline; filename="data.json"`, + }, + }); + } + + return next(); +}); + +export const onRequest = sequence( + first +); diff --git a/packages/astro/test/fixtures/middleware-non-html-ssr/src/pages/placeholder.png.astro b/packages/astro/test/fixtures/middleware-non-html-ssr/src/pages/placeholder.png.astro new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/astro/test/fixtures/middleware-non-html-ssr/src/pages/rename-me.json.astro b/packages/astro/test/fixtures/middleware-non-html-ssr/src/pages/rename-me.json.astro new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/astro/test/non-html-pages.test.js b/packages/astro/test/non-html-pages.test.js index f84c12f7b09d..9d68047aee36 100644 --- a/packages/astro/test/non-html-pages.test.js +++ b/packages/astro/test/non-html-pages.test.js @@ -3,6 +3,7 @@ import { before, describe, it } from 'node:test'; import { loadFixture } from './test-utils.js'; describe('Non-HTML Pages', () => { + /** @type {import('./test-utils').Fixture} */ let fixture; before(async () => { diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index c33e43ca36b0..8ca7347dc37d 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -22,8 +22,12 @@ process.env.ASTRO_TELEMETRY_DISABLED = true; * @typedef {import('../src/core/dev/dev').DevServer} DevServer * @typedef {import('../src/types/public/config.js').AstroInlineConfig & { root?: string | URL }} AstroInlineConfig * @typedef {import('../src/types/public/config.js').AstroConfig} AstroConfig + * @typedef {import('../src/types/public/config.js').RouteData} RouteData + * @typedef {import('../src/types/public/config.js').RouteOptions} RouteOptions * @typedef {import('../src/core/preview/index').PreviewServer} PreviewServer * @typedef {import('../src/core/app/index').App} App + * @typedef {import('../src/core/app/types').SerializedSSRManifest} SerializedSSRManifest + * @typedef {import('../src/types/public/integrations').IntegrationResolvedRoute} IntegrationResolvedRoute * @typedef {import('../src/cli/check/index').AstroChecker} AstroChecker * @typedef {import('../src/cli/check/index').CheckPayload} CheckPayload * @typedef {import('http').IncomingMessage} NodeRequest diff --git a/packages/astro/test/units/middleware-non-html.test.js b/packages/astro/test/units/middleware-non-html.test.js new file mode 100644 index 000000000000..59fa6cdb8c81 --- /dev/null +++ b/packages/astro/test/units/middleware-non-html.test.js @@ -0,0 +1,79 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { loadFixture } from '../test-utils.js'; + +describe('Middleware returning non-html, SSR', () => { + /** @type {import('../test-utils').Fixture} */ + let fixture; + /** @type {import('../test-utils').DevServer} */ + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/middleware-non-html-ssr/', + }); + // do an ssr build + await fixture.build(); + // start the dev server + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('should return placeholder.png with a custom content type', async () => { + const response = await fixture.fetch('/placeholder.png'); + assert.equal(response.headers.get('Content-Type'), 'image/png'); + assert.equal(response.headers.get('Content-Disposition'), 'inline; filename="placeholder.png"'); + + const imageBase64 = await response + .arrayBuffer() + .then((arrayBuffer) => Buffer.from(arrayBuffer).toString('base64')); + + // Make sure the whole buffer (in base64) matches this snapshot + assert.equal( + imageBase64, + 'iVBORw0KGgoAAAANSUhEUgAAAGQAAACWCAMAAAAfZt10AAAABlBMVEXd3d3+/v7B/CFgAAAA3UlEQVR42u3ZMQ7DIBQFQeb+l06bNgUbG/5eYApLFjzWNE3TNE3TNE035av9AhAQEBBQGAQEFAaFQWFQGBQGhUGCKAwKgwQpDJ6JECgCRYIEikH8YAyCRyEGyRCDvBWRIPNNBpm/8G6kUM45EhXKlQfuFSHFpbFH+jt2j/S7xwqUYvBaCRIozZy6X2km7v1K8uwQIIWBwkBAQEBg3Tyj3z4LnzRBKgwKg8KgMEgQhaEwSBCFQWBEiMIgQQqDBCkMEqQw+APixYgcsa0TERs7D/F6xGmIAxCD/Iw4AvEB92Ec3ZAPdlMAAAAASUVORK5CYII=', + ); + }); + + it('should return rename-me.json renamed to data.json', async () => { + const response = await fixture.fetch('/rename-me.json'); + assert.equal(response.status, 200); + assert.equal(response.headers.get('Content-Type'), 'application/json'); + assert.equal(response.headers.get('Content-Disposition'), 'inline; filename="data.json"'); + + const data = await response.json(); + assert.equal(data.name, 'alan'); + }); +}); + +describe('Middleware returning non-html, SSG', () => { + /** @type {import('../test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/middleware-non-html-ssg/', + }); + // do an ssg build + await fixture.build(); + }); + + it('should have built placeholder.png with a custom content type', async () => { + const imageText = await fixture.readFile('/placeholder.png', 'base64'); + + // Make sure the whole buffer (in base64) matches this snapshot + assert.equal( + imageText, + 'iVBORw0KGgoAAAANSUhEUgAAAGQAAACWCAMAAAAfZt10AAAABlBMVEXd3d3+/v7B/CFgAAAA3UlEQVR42u3ZMQ7DIBQFQeb+l06bNgUbG/5eYApLFjzWNE3TNE3TNE035av9AhAQEBBQGAQEFAaFQWFQGBQGhUGCKAwKgwQpDJ6JECgCRYIEikH8YAyCRyEGyRCDvBWRIPNNBpm/8G6kUM45EhXKlQfuFSHFpbFH+jt2j/S7xwqUYvBaCRIozZy6X2km7v1K8uwQIIWBwkBAQEBg3Tyj3z4LnzRBKgwKg8KgMEgQhaEwSBCFQWBEiMIgQQqDBCkMEqQw+APixYgcsa0TERs7D/F6xGmIAxCD/Iw4AvEB92Ec3ZAPdlMAAAAASUVORK5CYII=', + ); + }); + + it('should have built data.json with a custom name (rename-me.json)', async () => { + const file = await fixture.readFile('/data.json'); + const data = JSON.parse(file); + assert.equal(data.name, 'alan'); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c43a2a7c8b5..85ba1ed19104 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -508,6 +508,12 @@ importers: common-ancestor-path: specifier: ^1.0.1 version: 1.0.1 + content-disposition: + specifier: ^0.5.4 + version: 0.5.4 + content-type: + specifier: ^1.0.5 + version: 1.0.5 cookie: specifier: ^0.7.2 version: 0.7.2 @@ -660,6 +666,12 @@ importers: '@types/common-ancestor-path': specifier: ^1.0.2 version: 1.0.2 + '@types/content-disposition': + specifier: ^0.5.8 + version: 0.5.8 + '@types/content-type': + specifier: ^1.1.8 + version: 1.1.8 '@types/cssesc': specifier: ^3.0.2 version: 3.0.2 @@ -3357,6 +3369,21 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/middleware-non-html-ssg: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + + packages/astro/test/fixtures/middleware-non-html-ssr: + dependencies: + '@astrojs/node': + specifier: ^8.3.4 + version: 8.3.4(astro@packages+astro) + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/middleware-ssg: dependencies: astro: @@ -5668,6 +5695,11 @@ packages: prettier-plugin-astro: optional: true + '@astrojs/node@8.3.4': + resolution: {integrity: sha512-xzQs39goN7xh9np9rypGmbgZj3AmmjNxEMj9ZWz5aBERlqqFF3n8A/w/uaJeZ/bkHS60l1BXVS0tgsQt9MFqBA==} + peerDependencies: + astro: ^4.2.0 + '@astrojs/node@9.0.0': resolution: {integrity: sha512-3h/5kFZvpuo+chYAjj75YhtRUxfquxEJrpZRRC7TdiMGp2WhLp2us4VXm2mjezJp/zHKotW2L3qgp0P2ujQ0xw==} peerDependencies: @@ -7037,6 +7069,12 @@ packages: '@types/common-ancestor-path@1.0.2': resolution: {integrity: sha512-8llyULydTb7nM9yfiW78n6id3cet+qnATPV3R44yIywxgBaa8QXFSM9QTMf4OH64QOB45BlgZ3/oL4mmFLztQw==} + '@types/content-disposition@0.5.8': + resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==} + + '@types/content-type@1.1.8': + resolution: {integrity: sha512-1tBhmVUeso3+ahfyaKluXe38p+94lovUZdoVfQ3OnJo9uJC42JT7CBoN3k9HYhAae+GwiBYmHu+N9FZhOG+2Pg==} + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} @@ -7742,6 +7780,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -7838,6 +7880,14 @@ packages: dataloader@1.4.0: resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -9228,6 +9278,9 @@ packages: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -10120,6 +10173,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.1: + resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} + engines: {node: '>= 0.8.0'} + send@1.1.0: resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} engines: {node: '>= 18'} @@ -11258,6 +11315,14 @@ snapshots: transitivePeerDependencies: - typescript + '@astrojs/node@8.3.4(astro@packages+astro)': + dependencies: + astro: link:packages/astro + send: 0.19.1 + server-destroy: 1.0.1 + transitivePeerDependencies: + - supports-color + '@astrojs/node@9.0.0(astro@packages+astro)': dependencies: astro: link:packages/astro @@ -12669,6 +12734,10 @@ snapshots: '@types/common-ancestor-path@1.0.2': {} + '@types/content-disposition@0.5.8': {} + + '@types/content-type@1.1.8': {} + '@types/cookie@0.6.0': {} '@types/cssesc@3.0.2': {} @@ -13519,6 +13588,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-type@1.0.5: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} @@ -13606,6 +13677,10 @@ snapshots: dataloader@1.4.0: {} + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@4.4.0: dependencies: ms: 2.1.3 @@ -15319,6 +15394,8 @@ snapshots: mrmime@2.0.0: {} + ms@2.0.0: {} + ms@2.1.3: {} muggle-string@0.4.1: {} @@ -16331,6 +16408,24 @@ snapshots: semver@7.6.3: {} + send@0.19.1: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + send@1.1.0: dependencies: debug: 4.4.0