summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/thin-news-collect.md48
-rw-r--r--packages/astro/package.json2
-rw-r--r--packages/astro/src/@types/astro.ts7
-rw-r--r--packages/astro/src/core/app/index.ts5
-rw-r--r--packages/astro/src/core/cookies/cookies.ts202
-rw-r--r--packages/astro/src/core/cookies/index.ts9
-rw-r--r--packages/astro/src/core/cookies/response.ts26
-rw-r--r--packages/astro/src/core/endpoint/index.ts18
-rw-r--r--packages/astro/src/core/render/core.ts11
-rw-r--r--packages/astro/src/core/render/result.ts13
-rw-r--r--packages/astro/src/runtime/server/endpoint.ts13
-rw-r--r--packages/astro/src/vite-plugin-astro-server/index.ts6
-rw-r--r--packages/astro/test/astro-cookies.test.js119
-rw-r--r--packages/astro/test/fixtures/astro-cookies/package.json8
-rw-r--r--packages/astro/test/fixtures/astro-cookies/src/pages/early-return.astro14
-rw-r--r--packages/astro/test/fixtures/astro-cookies/src/pages/get-json.astro17
-rw-r--r--packages/astro/test/fixtures/astro-cookies/src/pages/set-prefs.js15
-rw-r--r--packages/astro/test/fixtures/astro-cookies/src/pages/set-value.astro15
-rw-r--r--packages/astro/test/units/cookies/delete.test.js60
-rw-r--r--packages/astro/test/units/cookies/get.test.js136
-rw-r--r--packages/astro/test/units/cookies/has.test.js32
-rw-r--r--packages/astro/test/units/cookies/set.test.js82
-rw-r--r--packages/integrations/cloudflare/src/server.advanced.ts10
-rw-r--r--packages/integrations/cloudflare/src/server.directory.ts10
-rw-r--r--packages/integrations/deno/src/server.ts17
-rw-r--r--packages/integrations/netlify/src/netlify-edge-functions.ts8
-rw-r--r--packages/integrations/netlify/src/netlify-functions.ts10
-rw-r--r--packages/integrations/node/src/server.ts12
-rw-r--r--packages/integrations/vercel/src/edge/entrypoint.ts8
-rw-r--r--packages/integrations/vercel/src/serverless/entrypoint.ts2
-rw-r--r--packages/integrations/vercel/src/serverless/request-transform.ts10
-rw-r--r--pnpm-lock.yaml27
32 files changed, 943 insertions, 29 deletions
diff --git a/.changeset/thin-news-collect.md b/.changeset/thin-news-collect.md
new file mode 100644
index 000000000..7779013b8
--- /dev/null
+++ b/.changeset/thin-news-collect.md
@@ -0,0 +1,48 @@
+---
+'astro': minor
+'@astrojs/cloudflare': minor
+'@astrojs/deno': minor
+'@astrojs/netlify': minor
+'@astrojs/node': minor
+'@astrojs/vercel': minor
+---
+
+Adds the Astro.cookies API
+
+`Astro.cookies` is a new API for manipulating cookies in Astro components and API routes.
+
+In Astro components, the new `Astro.cookies` object is a map-like object that allows you to get, set, delete, and check for a cookie's existence (`has`):
+
+```astro
+---
+type Prefs = {
+ darkMode: boolean;
+}
+
+Astro.cookies.set<Prefs>('prefs', { darkMode: true }, {
+ expires: '1 month'
+});
+
+const prefs = Astro.cookies.get<Prefs>('prefs').json();
+---
+<body data-theme={prefs.darkMode ? 'dark' : 'light'}>
+```
+
+Once you've set a cookie with Astro.cookies it will automatically be included in the outgoing response.
+
+This API is also available with the same functionality in API routes:
+
+```js
+export function post({ cookies }) {
+ cookies.set('loggedIn', false);
+
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: '/login'
+ }
+ });
+}
+```
+
+See [the RFC](https://github.com/withastro/rfcs/blob/main/proposals/0025-cookie-management.md) to learn more.
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 0792f118a..0a39c75e1 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -114,6 +114,7 @@
"boxen": "^6.2.1",
"ci-info": "^3.3.1",
"common-ancestor-path": "^1.0.1",
+ "cookie": "^0.5.0",
"debug": "^4.3.4",
"diff": "^5.1.0",
"eol": "^0.9.1",
@@ -161,6 +162,7 @@
"@types/chai": "^4.3.1",
"@types/common-ancestor-path": "^1.0.0",
"@types/connect": "^3.4.35",
+ "@types/cookie": "^0.5.1",
"@types/debug": "^4.1.7",
"@types/diff": "^5.0.2",
"@types/estree": "^0.0.51",
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index c423a1abf..7c19dcca6 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -14,6 +14,7 @@ import type * as vite from 'vite';
import type { z } from 'zod';
import type { SerializedSSRManifest } from '../core/app/types';
import type { PageBuildData } from '../core/build/types';
+import type { AstroCookies } from '../core/cookies';
import type { AstroConfigSchema } from '../core/config';
import type { ViteConfigWithSSR } from '../core/create-vite';
import type { AstroComponentFactory, Metadata } from '../runtime/server';
@@ -116,6 +117,10 @@ export interface AstroGlobal extends AstroGlobalPartial {
*
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#url)
*/
+ /**
+ * Utility for getting and setting cookies values.
+ */
+ cookies: AstroCookies,
url: URL;
/** Parameters passed to a dynamic page generated using [getStaticPaths](https://docs.astro.build/en/reference/api-reference/#getstaticpaths)
*
@@ -1083,6 +1088,7 @@ export interface AstroAdapter {
type Body = string;
export interface APIContext {
+ cookies: AstroCookies;
params: Params;
request: Request;
}
@@ -1219,6 +1225,7 @@ export interface SSRResult {
styles: Set<SSRElement>;
scripts: Set<SSRElement>;
links: Set<SSRElement>;
+ cookies: AstroCookies | undefined;
createAstro(
Astro: AstroGlobalPartial,
props: Record<string, any>,
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index 413dba20c..ce3422738 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -21,6 +21,7 @@ import {
} from '../render/ssr-element.js';
import { matchRoute } from '../routing/match.js';
export { deserializeManifest } from './common.js';
+import { getSetCookiesFromResponse } from '../cookies/index.js';
export const pagesVirtualModuleId = '@astrojs-pages-virtual-entry';
export const resolvedPagesVirtualModuleId = '\0' + pagesVirtualModuleId;
@@ -116,6 +117,10 @@ export class App {
}
}
+ setCookieHeaders(response: Response) {
+ return getSetCookiesFromResponse(response);
+ }
+
async #renderPage(
request: Request,
routeData: RouteData,
diff --git a/packages/astro/src/core/cookies/cookies.ts b/packages/astro/src/core/cookies/cookies.ts
new file mode 100644
index 000000000..7f530ce85
--- /dev/null
+++ b/packages/astro/src/core/cookies/cookies.ts
@@ -0,0 +1,202 @@
+import type { CookieSerializeOptions } from 'cookie';
+import { parse, serialize } from 'cookie';
+
+interface AstroCookieSetOptions {
+ domain?: string;
+ expires?: Date;
+ httpOnly?: boolean;
+ maxAge?: number;
+ path?: string;
+ sameSite?: boolean | 'lax' | 'none' | 'strict';
+ secure?: boolean;
+}
+
+interface AstroCookieDeleteOptions {
+ path?: string;
+}
+
+interface AstroCookieInterface {
+ value: string | undefined;
+ json(): Record<string, any>;
+ number(): number;
+ boolean(): boolean;
+}
+
+interface AstroCookiesInterface {
+ get(key: string): AstroCookieInterface;
+ has(key: string): boolean;
+ set(key: string, value: string | Record<string, any>, options?: AstroCookieSetOptions): void;
+ delete(key: string, options?: AstroCookieDeleteOptions): void;
+}
+
+const DELETED_EXPIRATION = new Date(0);
+const DELETED_VALUE = 'deleted';
+
+class AstroCookie implements AstroCookieInterface {
+ constructor(public value: string | undefined) {}
+ json() {
+ if(this.value === undefined) {
+ throw new Error(`Cannot convert undefined to an object.`);
+ }
+ return JSON.parse(this.value);
+ }
+ number() {
+ return Number(this.value);
+ }
+ boolean() {
+ if(this.value === 'false') return false;
+ if(this.value === '0') return false;
+ return Boolean(this.value);
+ }
+}
+
+class AstroCookies implements AstroCookiesInterface {
+ #request: Request;
+ #requestValues: Record<string, string> | null;
+ #outgoing: Map<string, [string, string, boolean]> | null;
+ constructor(request: Request) {
+ this.#request = request;
+ this.#requestValues = null;
+ this.#outgoing = null;
+ }
+
+ /**
+ * Astro.cookies.delete(key) is used to delete a cookie. Using this method will result
+ * in a Set-Cookie header added to the response.
+ * @param key The cookie to delete
+ * @param options Options related to this deletion, such as the path of the cookie.
+ */
+ delete(key: string, options?: AstroCookieDeleteOptions): void {
+ const serializeOptions: CookieSerializeOptions = {
+ expires: DELETED_EXPIRATION
+ };
+
+ if(options?.path) {
+ serializeOptions.path = options.path;
+ }
+
+ // Set-Cookie: token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT
+ this.#ensureOutgoingMap().set(key, [
+ DELETED_VALUE,
+ serialize(key, DELETED_VALUE, serializeOptions),
+ false
+ ]);
+ }
+
+ /**
+ * Astro.cookies.get(key) is used to get a cookie value. The cookie value is read from the
+ * request. If you have set a cookie via Astro.cookies.set(key, value), the value will be taken
+ * from that set call, overriding any values already part of the request.
+ * @param key The cookie to get.
+ * @returns An object containing the cookie value as well as convenience methods for converting its value.
+ */
+ get(key: string): AstroCookie {
+ // Check for outgoing Set-Cookie values first
+ if(this.#outgoing !== null && this.#outgoing.has(key)) {
+ let [serializedValue,, isSetValue] = this.#outgoing.get(key)!;
+ if(isSetValue) {
+ return new AstroCookie(serializedValue);
+ } else {
+ return new AstroCookie(undefined);
+ }
+ }
+
+ const values = this.#ensureParsed();
+ const value = values[key];
+ return new AstroCookie(value);
+ }
+
+ /**
+ * Astro.cookies.has(key) returns a boolean indicating whether this cookie is either
+ * part of the initial request or set via Astro.cookies.set(key)
+ * @param key The cookie to check for.
+ * @returns
+ */
+ has(key: string): boolean {
+ if(this.#outgoing !== null && this.#outgoing.has(key)) {
+ let [,,isSetValue] = this.#outgoing.get(key)!;
+ return isSetValue;
+ }
+ const values = this.#ensureParsed();
+ return !!values[key];
+ }
+
+ /**
+ * Astro.cookies.set(key, value) is used to set a cookie's value. If provided
+ * an object it will be stringified via JSON.stringify(value). Additionally you
+ * can provide options customizing how this cookie will be set, such as setting httpOnly
+ * in order to prevent the cookie from being read in client-side JavaScript.
+ * @param key The name of the cookie to set.
+ * @param value A value, either a string or other primitive or an object.
+ * @param options Options for the cookie, such as the path and security settings.
+ */
+ set(key: string, value: string | Record<string, any>, options?: AstroCookieSetOptions): void {
+ let serializedValue: string;
+ if(typeof value === 'string') {
+ serializedValue = value;
+ } else {
+ // Support stringifying JSON objects for convenience. First check that this is
+ // a plain object and if it is, stringify. If not, allow support for toString() overrides.
+ let toStringValue = value.toString();
+ if(toStringValue === Object.prototype.toString.call(value)) {
+ serializedValue = JSON.stringify(value);
+ } else {
+ serializedValue = toStringValue;
+ }
+ }
+
+ const serializeOptions: CookieSerializeOptions = {};
+ if(options) {
+ Object.assign(serializeOptions, options);
+ }
+
+ this.#ensureOutgoingMap().set(key, [
+ serializedValue,
+ serialize(key, serializedValue, serializeOptions),
+ true
+ ]);
+ }
+
+ /**
+ * Astro.cookies.header() returns an iterator for the cookies that have previously
+ * been set by either Astro.cookies.set() or Astro.cookies.delete().
+ * This method is primarily used by adapters to set the header on outgoing responses.
+ * @returns
+ */
+ *headers(): Generator<string, void, unknown> {
+ if(this.#outgoing == null) return;
+ for(const [,value] of this.#outgoing) {
+ yield value[1];
+ }
+ }
+
+ #ensureParsed(): Record<string, string> {
+ if(!this.#requestValues) {
+ this.#parse();
+ }
+ if(!this.#requestValues) {
+ this.#requestValues = {};
+ }
+ return this.#requestValues;
+ }
+
+ #ensureOutgoingMap(): Map<string, [string, string, boolean]> {
+ if(!this.#outgoing) {
+ this.#outgoing = new Map();
+ }
+ return this.#outgoing;
+ }
+
+ #parse() {
+ const raw = this.#request.headers.get('cookie');
+ if(!raw) {
+ return;
+ }
+
+ this.#requestValues = parse(raw);
+ }
+}
+
+export {
+ AstroCookies
+};
diff --git a/packages/astro/src/core/cookies/index.ts b/packages/astro/src/core/cookies/index.ts
new file mode 100644
index 000000000..18dc3ebca
--- /dev/null
+++ b/packages/astro/src/core/cookies/index.ts
@@ -0,0 +1,9 @@
+
+export {
+ AstroCookies
+} from './cookies.js';
+
+export {
+ attachToResponse,
+ getSetCookiesFromResponse
+} from './response.js';
diff --git a/packages/astro/src/core/cookies/response.ts b/packages/astro/src/core/cookies/response.ts
new file mode 100644
index 000000000..0e52ac8cb
--- /dev/null
+++ b/packages/astro/src/core/cookies/response.ts
@@ -0,0 +1,26 @@
+import type { AstroCookies } from './cookies';
+
+const astroCookiesSymbol = Symbol.for('astro.cookies');
+
+export function attachToResponse(response: Response, cookies: AstroCookies) {
+ Reflect.set(response, astroCookiesSymbol, cookies);
+}
+
+function getFromResponse(response: Response): AstroCookies | undefined {
+ let cookies = Reflect.get(response, astroCookiesSymbol);
+ if(cookies != null) {
+ return cookies as AstroCookies;
+ } else {
+ return undefined;
+ }
+}
+
+export function * getSetCookiesFromResponse(response: Response): Generator<string, void, unknown> {
+ const cookies = getFromResponse(response);
+ if(!cookies) {
+ return;
+ }
+ for(const headerValue of cookies.headers()) {
+ yield headerValue;
+ }
+}
diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts
index 7fee0c428..75e451e6f 100644
--- a/packages/astro/src/core/endpoint/index.ts
+++ b/packages/astro/src/core/endpoint/index.ts
@@ -1,6 +1,8 @@
-import type { EndpointHandler } from '../../@types/astro';
-import { renderEndpoint } from '../../runtime/server/index.js';
+import type { APIContext, EndpointHandler, Params } from '../../@types/astro';
import type { RenderOptions } from '../render/core';
+
+import { AstroCookies, attachToResponse } from '../cookies/index.js';
+import { renderEndpoint } from '../../runtime/server/index.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
export type EndpointOptions = Pick<
@@ -28,6 +30,14 @@ type EndpointCallResult =
response: Response;
};
+function createAPIContext(request: Request, params: Params): APIContext {
+ return {
+ cookies: new AstroCookies(request),
+ request,
+ params
+ };
+}
+
export async function call(
mod: EndpointHandler,
opts: EndpointOptions
@@ -41,9 +51,11 @@ export async function call(
}
const [params] = paramsAndPropsResp;
- const response = await renderEndpoint(mod, opts.request, params, opts.ssr);
+ const context = createAPIContext(opts.request, params);
+ const response = await renderEndpoint(mod, context, opts.ssr);
if (response instanceof Response) {
+ attachToResponse(response, context.cookies);
return {
type: 'response',
response,
diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts
index c9efe02de..7e5fe1f96 100644
--- a/packages/astro/src/core/render/core.ts
+++ b/packages/astro/src/core/render/core.ts
@@ -10,6 +10,7 @@ import type {
} from '../../@types/astro';
import type { LogOptions } from '../logger/core.js';
+import { attachToResponse } from '../cookies/index.js';
import { Fragment, renderPage } from '../../runtime/server/index.js';
import { getParams } from '../routing/params.js';
import { createResult } from './result.js';
@@ -164,5 +165,13 @@ export async function render(opts: RenderOptions): Promise<Response> {
});
}
- return await renderPage(result, Component, pageProps, null, streaming);
+ const response = await renderPage(result, Component, pageProps, null, streaming);
+
+ // If there is an Astro.cookies instance, attach it to the response so that
+ // adapters can grab the Set-Cookie headers.
+ if(result.cookies) {
+ attachToResponse(response, result.cookies);
+ }
+
+ return response;
}
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index d4704ca1f..c0e650a8f 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -11,6 +11,7 @@ import type {
SSRResult,
} from '../../@types/astro';
import { renderSlot } from '../../runtime/server/index.js';
+import { AstroCookies } from '../cookies/index.js';
import { LogOptions, warn } from '../logger/core.js';
import { isScriptRequest } from './script.js';
import { isCSSRequest } from './util.js';
@@ -139,6 +140,9 @@ export function createResult(args: CreateResultArgs): SSRResult {
writable: false,
});
+ // Astro.cookies is defined lazily to avoid the cost on pages that do not use it.
+ let cookies: AstroCookies | undefined = undefined;
+
// Create the result object that will be passed into the render function.
// This object starts here as an empty shell (not yet the result) but then
// calling the render() function will populate the object with scripts, styles, etc.
@@ -146,6 +150,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
styles: args.styles ?? new Set<SSRElement>(),
scripts: args.scripts ?? new Set<SSRElement>(),
links: args.links ?? new Set<SSRElement>(),
+ cookies,
/** This function returns the `Astro` faux-global */
createAstro(
astroGlobal: AstroGlobalPartial,
@@ -171,6 +176,14 @@ export function createResult(args: CreateResultArgs): SSRResult {
return Reflect.get(request, clientAddressSymbol);
},
+ get cookies() {
+ if(cookies) {
+ return cookies;
+ }
+ cookies = new AstroCookies(request);
+ result.cookies = cookies;
+ return cookies;
+ },
params,
props,
request,
diff --git a/packages/astro/src/runtime/server/endpoint.ts b/packages/astro/src/runtime/server/endpoint.ts
index 31a0069db..a93d02adb 100644
--- a/packages/astro/src/runtime/server/endpoint.ts
+++ b/packages/astro/src/runtime/server/endpoint.ts
@@ -18,12 +18,8 @@ function getHandlerFromModule(mod: EndpointHandler, method: string) {
}
/** Renders an endpoint request to completion, returning the body. */
-export async function renderEndpoint(
- mod: EndpointHandler,
- request: Request,
- params: Params,
- ssr?: boolean
-) {
+export async function renderEndpoint(mod: EndpointHandler, context: APIContext, ssr: boolean) {
+ const { request, params } = context;
const chosenMethod = request.method?.toLowerCase();
const handler = getHandlerFromModule(mod, chosenMethod);
if (!ssr && ssr === false && chosenMethod && chosenMethod !== 'get') {
@@ -56,11 +52,6 @@ export function get({ params, request }) {
Update your code to remove this warning.`);
}
- const context = {
- request,
- params,
- };
-
const proxy = new Proxy(context, {
get(target, prop) {
if (prop in target) {
diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts
index 72f2ce9ba..e976280c8 100644
--- a/packages/astro/src/vite-plugin-astro-server/index.ts
+++ b/packages/astro/src/vite-plugin-astro-server/index.ts
@@ -6,6 +6,7 @@ import type { SSROptions } from '../core/render/dev/index';
import { Readable } from 'stream';
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
+import { getSetCookiesFromResponse } from '../core/cookies/index.js';
import {
collectErrorMetadata,
ErrorWithMetadata,
@@ -62,6 +63,11 @@ async function writeWebResponse(res: http.ServerResponse, webResponse: Response)
_headers = Object.fromEntries(headers.entries());
}
+ // Attach any set-cookie headers added via Astro.cookies.set()
+ const setCookieHeaders = Array.from(getSetCookiesFromResponse(webResponse));
+ if(setCookieHeaders.length) {
+ res.setHeader('Set-Cookie', setCookieHeaders);
+ }
res.writeHead(status, _headers);
if (body) {
if (Symbol.for('astro.responseBody') in webResponse) {
diff --git a/packages/astro/test/astro-cookies.test.js b/packages/astro/test/astro-cookies.test.js
new file mode 100644
index 000000000..ece374d82
--- /dev/null
+++ b/packages/astro/test/astro-cookies.test.js
@@ -0,0 +1,119 @@
+import { expect } from 'chai';
+import * as cheerio from 'cheerio';
+import { loadFixture } from './test-utils.js';
+import testAdapter from './test-adapter.js';
+
+describe('Astro.cookies', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/astro-cookies/',
+ output: 'server',
+ adapter: testAdapter(),
+ });
+ });
+
+ describe('Development', () => {
+ /** @type {import('./test-utils').DevServer} */
+ let devServer;
+
+ before(async () => {
+ devServer = await fixture.startDevServer();
+ });
+
+ after(async () => {
+ await devServer.stop();
+ });
+
+ it('is able to get cookies from the request', async () => {
+ const response = await fixture.fetch('/get-json', {
+ headers: {
+ cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
+ }
+ });
+ expect(response.status).to.equal(200);
+ const html = await response.text();
+
+ const $ = cheerio.load(html);
+ expect($('dd').text()).to.equal('light');
+ });
+
+ it('can set the cookie value', async () => {
+ const response = await fixture.fetch('/set-value', {
+ method: 'POST'
+ });
+ expect(response.status).to.equal(200);
+ expect(response.headers.has('set-cookie')).to.equal(true);
+ });
+ });
+
+ describe('Production', () => {
+ let app;
+ before(async () => {
+ await fixture.build();
+ app = await fixture.loadTestAdapterApp();
+ });
+
+ async function fetchResponse(path, requestInit) {
+ const request = new Request('http://example.com' + path, requestInit);
+ const response = await app.render(request);
+ return response;
+ }
+
+ it('is able to get cookies from the request', async () => {
+ const response = await fetchResponse('/get-json', {
+ headers: {
+ cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
+ }
+ });
+ expect(response.status).to.equal(200);
+ const html = await response.text();
+
+ const $ = cheerio.load(html);
+ expect($('dd').text()).to.equal('light');
+ });
+
+ it('can set the cookie value', async () => {
+ const response = await fetchResponse('/set-value', {
+ method: 'POST'
+ });
+ expect(response.status).to.equal(200);
+ let headers = Array.from(app.setCookieHeaders(response));
+ expect(headers).to.have.a.lengthOf(1);
+ expect(headers[0]).to.match(/Expires/);
+ });
+
+ it('Early returning a Response still includes set headers', async () => {
+ const response = await fetchResponse('/early-return', {
+ headers: {
+ cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
+ }
+ });
+ expect(response.status).to.equal(302);
+ let headers = Array.from(app.setCookieHeaders(response));
+ expect(headers).to.have.a.lengthOf(1);
+ let raw = headers[0].slice(6);
+ let data = JSON.parse(decodeURIComponent(raw));
+ expect(data).to.be.an('object');
+ expect(data.mode).to.equal('dark');
+ });
+
+ it('API route can get and set cookies', async () => {
+ const response = await fetchResponse('/set-prefs', {
+ method: 'POST',
+ headers: {
+ cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`
+ }
+ });
+ expect(response.status).to.equal(302);
+ let headers = Array.from(app.setCookieHeaders(response));
+ expect(headers).to.have.a.lengthOf(1);
+ let raw = headers[0].slice(6);
+ let data = JSON.parse(decodeURIComponent(raw));
+ expect(data).to.be.an('object');
+ expect(data.mode).to.equal('dark');
+ });
+ })
+});
diff --git a/packages/astro/test/fixtures/astro-cookies/package.json b/packages/astro/test/fixtures/astro-cookies/package.json
new file mode 100644
index 000000000..42009b4af
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-cookies/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/astro-cookies",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/astro-cookies/src/pages/early-return.astro b/packages/astro/test/fixtures/astro-cookies/src/pages/early-return.astro
new file mode 100644
index 000000000..2796b3989
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-cookies/src/pages/early-return.astro
@@ -0,0 +1,14 @@
+---
+const mode = Astro.cookies.get('prefs').json().mode;
+
+Astro.cookies.set('prefs', {
+ mode: mode === 'light' ? 'dark' : 'light'
+});
+
+return new Response(null, {
+ status: 302,
+ headers: {
+ 'Location': '/prefs'
+ }
+})
+---
diff --git a/packages/astro/test/fixtures/astro-cookies/src/pages/get-json.astro b/packages/astro/test/fixtures/astro-cookies/src/pages/get-json.astro
new file mode 100644
index 000000000..034881d22
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-cookies/src/pages/get-json.astro
@@ -0,0 +1,17 @@
+---
+const cookie = Astro.cookies.get('prefs');
+const prefs = cookie.json();
+---
+<html>
+ <head>
+ <title>Testing</title>
+ </head>
+ <body>
+ <h1>Testing</h1>
+ <h2>Preferences</h2>
+ <dl>
+ <dt>Dark/light mode</dt>
+ <dd>{ prefs.mode }</dd>
+ </dl>
+ </body>
+</html>
diff --git a/packages/astro/test/fixtures/astro-cookies/src/pages/set-prefs.js b/packages/astro/test/fixtures/astro-cookies/src/pages/set-prefs.js
new file mode 100644
index 000000000..ccbdceff6
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-cookies/src/pages/set-prefs.js
@@ -0,0 +1,15 @@
+
+export function post({ cookies }) {
+ const mode = cookies.get('prefs').json().mode;
+
+ cookies.set('prefs', {
+ mode: mode === 'light' ? 'dark' : 'light'
+ });
+
+ return new Response(null, {
+ status: 302,
+ headers: {
+ 'Location': '/prefs'
+ }
+ });
+}
diff --git a/packages/astro/test/fixtures/astro-cookies/src/pages/set-value.astro b/packages/astro/test/fixtures/astro-cookies/src/pages/set-value.astro
new file mode 100644
index 000000000..cd286da9e
--- /dev/null
+++ b/packages/astro/test/fixtures/astro-cookies/src/pages/set-value.astro
@@ -0,0 +1,15 @@
+---
+if(Astro.request.method === 'POST') {
+ Astro.cookies.set('admin', 'true', {
+ expires: new Date()
+ });
+}
+---
+<html>
+ <head>
+ <title>Testing</title>
+ </head>
+ <body>
+ <h1>Testing</h1>
+ </body>
+</html>
diff --git a/packages/astro/test/units/cookies/delete.test.js b/packages/astro/test/units/cookies/delete.test.js
new file mode 100644
index 000000000..e049995d4
--- /dev/null
+++ b/packages/astro/test/units/cookies/delete.test.js
@@ -0,0 +1,60 @@
+import { expect } from 'chai';
+import { AstroCookies } from '../../../dist/core/cookies/index.js';
+import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
+
+applyPolyfill();
+
+describe('astro/src/core/cookies', () => {
+ describe('Astro.cookies.delete', () => {
+ it('creates a Set-Cookie header to delete it', () => {
+ let req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ let cookies = new AstroCookies(req);
+ expect(cookies.get('foo').value).to.equal('bar');
+
+ cookies.delete('foo');
+ let headers = Array.from(cookies.headers());
+ expect(headers).to.have.a.lengthOf(1);
+ });
+
+ it('calling cookies.get() after returns undefined', () => {
+ let req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ let cookies = new AstroCookies(req);
+ expect(cookies.get('foo').value).to.equal('bar');
+
+ cookies.delete('foo');
+ expect(cookies.get('foo').value).to.equal(undefined);
+ });
+
+ it('calling cookies.has() after returns false', () => {
+ let req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ let cookies = new AstroCookies(req);
+ expect(cookies.has('foo')).to.equal(true);
+
+ cookies.delete('foo');
+ expect(cookies.has('foo')).to.equal(false);
+ });
+
+ it('can provide a path', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.delete('foo', {
+ path: '/subpath/'
+ });
+ let headers = Array.from(cookies.headers());
+ expect(headers).to.have.a.lengthOf(1);
+ expect(headers[0]).to.match(/Path=\/subpath\//);
+ });
+ });
+});
diff --git a/packages/astro/test/units/cookies/get.test.js b/packages/astro/test/units/cookies/get.test.js
new file mode 100644
index 000000000..837c2075e
--- /dev/null
+++ b/packages/astro/test/units/cookies/get.test.js
@@ -0,0 +1,136 @@
+import { expect } from 'chai';
+import { AstroCookies } from '../../../dist/core/cookies/index.js';
+import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
+
+applyPolyfill();
+
+describe('astro/src/core/cookies', () => {
+ describe('Astro.cookies.get', () => {
+ it('gets the cookie value', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ const cookies = new AstroCookies(req);
+ expect(cookies.get('foo').value).to.equal('bar');
+ });
+
+ describe('.json()', () => {
+ it('returns a JavaScript object', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=%7B%22key%22%3A%22value%22%7D'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const json = cookies.get('foo').json();
+ expect(json).to.be.an('object');
+ expect(json.key).to.equal('value');
+ });
+
+ it('throws if the value is undefined', () => {
+ const req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ let cookie = cookies.get('foo');
+ expect(() => cookie.json()).to.throw('Cannot convert undefined to an object.');
+ });
+ });
+
+ describe('.number()', () => {
+ it('Coerces into a number', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=22'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const value = cookies.get('foo').number();
+ expect(value).to.be.an('number');
+ expect(value).to.equal(22);
+ });
+
+ it('Coerces non-number into NaN', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const value = cookies.get('foo').number();
+ expect(value).to.be.an('number');
+ expect(Number.isNaN(value)).to.equal(true);
+ });
+ });
+
+ describe('.boolean()', () => {
+ it('Coerces true into `true`', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=true'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const value = cookies.get('foo').boolean();
+ expect(value).to.be.an('boolean');
+ expect(value).to.equal(true);
+ });
+
+ it('Coerces false into `false`', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=false'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const value = cookies.get('foo').boolean();
+ expect(value).to.be.an('boolean');
+ expect(value).to.equal(false);
+ });
+
+ it('Coerces 1 into `true`', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=1'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const value = cookies.get('foo').boolean();
+ expect(value).to.be.an('boolean');
+ expect(value).to.equal(true);
+ });
+
+ it('Coerces 0 into `false`', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=0'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const value = cookies.get('foo').boolean();
+ expect(value).to.be.an('boolean');
+ expect(value).to.equal(false);
+ });
+
+ it('Coerces truthy strings into `true`', () => {
+ const req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ let cookies = new AstroCookies(req);
+
+ const value = cookies.get('foo').boolean();
+ expect(value).to.be.an('boolean');
+ expect(value).to.equal(true);
+ });
+ });
+ });
+});
diff --git a/packages/astro/test/units/cookies/has.test.js b/packages/astro/test/units/cookies/has.test.js
new file mode 100644
index 000000000..d9a7eb66f
--- /dev/null
+++ b/packages/astro/test/units/cookies/has.test.js
@@ -0,0 +1,32 @@
+import { expect } from 'chai';
+import { AstroCookies } from '../../../dist/core/cookies/index.js';
+import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
+
+applyPolyfill();
+
+describe('astro/src/core/cookies', () => {
+ describe('Astro.cookies.has', () => {
+ it('returns true if the request has the cookie', () => {
+ let req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ let cookies = new AstroCookies(req);
+ expect(cookies.has('foo')).to.equal(true);
+ });
+
+ it('returns false if the request does not have the cookie', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ expect(cookies.has('foo')).to.equal(false);
+ });
+
+ it('returns true if the cookie has been set', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.set('foo', 'bar');
+ expect(cookies.has('foo')).to.equal(true);
+ });
+ });
+});
diff --git a/packages/astro/test/units/cookies/set.test.js b/packages/astro/test/units/cookies/set.test.js
new file mode 100644
index 000000000..acf436766
--- /dev/null
+++ b/packages/astro/test/units/cookies/set.test.js
@@ -0,0 +1,82 @@
+import { expect } from 'chai';
+import { AstroCookies } from '../../../dist/core/cookies/index.js';
+import { apply as applyPolyfill } from '../../../dist/core/polyfill.js';
+
+applyPolyfill();
+
+describe('astro/src/core/cookies', () => {
+ describe('Astro.cookies.set', () => {
+ it('Sets a cookie value that can be serialized', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.set('foo', 'bar');
+ let headers = Array.from(cookies.headers());
+ expect(headers).to.have.a.lengthOf(1);
+ expect(headers[0]).to.equal('foo=bar');
+ });
+
+ it('Can set cookie options', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.set('foo', 'bar', {
+ httpOnly: true,
+ path: '/subpath/'
+ });
+ let headers = Array.from(cookies.headers());
+ expect(headers).to.have.a.lengthOf(1);
+ expect(headers[0]).to.equal('foo=bar; Path=/subpath/; HttpOnly');
+ });
+
+ it('Can pass a JavaScript object that will be serialized', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.set('options', { one: 'two', three: 4 });
+ let headers = Array.from(cookies.headers());
+ expect(headers).to.have.a.lengthOf(1);
+ expect(JSON.parse(decodeURIComponent(headers[0].slice(8))).one).to.equal('two');
+ });
+
+ it('Can pass a number', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.set('one', 2);
+ let headers = Array.from(cookies.headers());
+ expect(headers).to.have.a.lengthOf(1);
+ expect(headers[0]).to.equal('one=2');
+ });
+
+ it('Can get the value after setting', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.set('foo', 'bar');
+ let r = cookies.get('foo');
+ expect(r.value).to.equal('bar');
+ });
+
+ it('Can get the JavaScript object after setting', () => {
+ let req = new Request('http://example.com/');
+ let cookies = new AstroCookies(req);
+ cookies.set('options', { one: 'two', three: 4 });
+ let cook = cookies.get('options');
+ let value = cook.json();
+ expect(value).to.be.an('object');
+ expect(value.one).to.equal('two');
+ expect(value.three).to.be.a('number');
+ expect(value.three).to.equal(4);
+ });
+
+ it('Overrides a value in the request', () => {
+ let req = new Request('http://example.com/', {
+ headers: {
+ 'cookie': 'foo=bar'
+ }
+ });
+ let cookies = new AstroCookies(req);
+ expect(cookies.get('foo').value).to.equal('bar');
+
+ // Set a new value
+ cookies.set('foo', 'baz');
+ expect(cookies.get('foo').value).to.equal('baz');
+ });
+ });
+});
diff --git a/packages/integrations/cloudflare/src/server.advanced.ts b/packages/integrations/cloudflare/src/server.advanced.ts
index 7b88c7b1e..62adb44ec 100644
--- a/packages/integrations/cloudflare/src/server.advanced.ts
+++ b/packages/integrations/cloudflare/src/server.advanced.ts
@@ -26,7 +26,15 @@ export function createExports(manifest: SSRManifest) {
Symbol.for('astro.clientAddress'),
request.headers.get('cf-connecting-ip')
);
- return app.render(request, routeData);
+ let response = await app.render(request, routeData);
+
+ if(app.setCookieHeaders) {
+ for(const setCookieHeader of app.setCookieHeaders(response)) {
+ response.headers.append('Set-Cookie', setCookieHeader);
+ }
+ }
+
+ return response;
}
return new Response(null, {
diff --git a/packages/integrations/cloudflare/src/server.directory.ts b/packages/integrations/cloudflare/src/server.directory.ts
index 58e83be34..7a484378c 100644
--- a/packages/integrations/cloudflare/src/server.directory.ts
+++ b/packages/integrations/cloudflare/src/server.directory.ts
@@ -28,7 +28,15 @@ export function createExports(manifest: SSRManifest) {
Symbol.for('astro.clientAddress'),
request.headers.get('cf-connecting-ip')
);
- return app.render(request, routeData);
+ let response = await app.render(request, routeData);
+
+ if(app.setCookieHeaders) {
+ for(const setCookieHeader of app.setCookieHeaders(response)) {
+ response.headers.append('Set-Cookie', setCookieHeader);
+ }
+ }
+
+ return response;
}
return new Response(null, {
diff --git a/packages/integrations/deno/src/server.ts b/packages/integrations/deno/src/server.ts
index d8eb3320d..d8c6aede9 100644
--- a/packages/integrations/deno/src/server.ts
+++ b/packages/integrations/deno/src/server.ts
@@ -26,7 +26,13 @@ export function start(manifest: SSRManifest, options: Options) {
if (app.match(request)) {
let ip = connInfo?.remoteAddr?.hostname;
Reflect.set(request, Symbol.for('astro.clientAddress'), ip);
- return await app.render(request);
+ const response = await app.render(request);
+ if(app.setCookieHeaders) {
+ for(const setCookieHeader of app.setCookieHeaders(response)) {
+ response.headers.append('Set-Cookie', setCookieHeader);
+ }
+ }
+ return response;
}
// If the request path wasn't found in astro,
@@ -38,7 +44,14 @@ export function start(manifest: SSRManifest, options: Options) {
// If the static file can't be found
if (fileResp.status == 404) {
// Render the astro custom 404 page
- return await app.render(request);
+ const response = await app.render(request);
+
+ if(app.setCookieHeaders) {
+ for(const setCookieHeader of app.setCookieHeaders(response)) {
+ response.headers.append('Set-Cookie', setCookieHeader);
+ }
+ }
+ return response;
// If the static file is found
} else {
diff --git a/packages/integrations/netlify/src/netlify-edge-functions.ts b/packages/integrations/netlify/src/netlify-edge-functions.ts
index a2c883585..c788b5f67 100644
--- a/packages/integrations/netlify/src/netlify-edge-functions.ts
+++ b/packages/integrations/netlify/src/netlify-edge-functions.ts
@@ -17,7 +17,13 @@ export function createExports(manifest: SSRManifest) {
if (app.match(request)) {
const ip = request.headers.get('x-nf-client-connection-ip');
Reflect.set(request, clientAddressSymbol, ip);
- return app.render(request);
+ const response = await app.render(request);
+ if(app.setCookieHeaders) {
+ for(const setCookieHeader of app.setCookieHeaders(response)) {
+ response.headers.append('Set-Cookie', setCookieHeader);
+ }
+ }
+ return response;
}
return new Response(null, {
diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts
index 94c9b6eee..7945b4687 100644
--- a/packages/integrations/netlify/src/netlify-functions.ts
+++ b/packages/integrations/netlify/src/netlify-functions.ts
@@ -120,6 +120,16 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
}
}
+ // Apply cookies set via Astro.cookies.set/delete
+ if(app.setCookieHeaders) {
+ const setCookieHeaders = Array.from(app.setCookieHeaders(response));
+ fnResponse.multiValueHeaders = fnResponse.multiValueHeaders || {};
+ if(!fnResponse.multiValueHeaders['set-cookie']) {
+ fnResponse.multiValueHeaders['set-cookie'] = [];
+ }
+ fnResponse.multiValueHeaders['set-cookie'].push(...setCookieHeaders);
+ }
+
return fnResponse;
};
diff --git a/packages/integrations/node/src/server.ts b/packages/integrations/node/src/server.ts
index 12fcf0448..794580ee9 100644
--- a/packages/integrations/node/src/server.ts
+++ b/packages/integrations/node/src/server.ts
@@ -18,7 +18,7 @@ export function createExports(manifest: SSRManifest) {
if (route) {
try {
const response = await app.render(req);
- await writeWebResponse(res, response);
+ await writeWebResponse(app, res, response);
} catch (err: unknown) {
if (next) {
next(err);
@@ -39,8 +39,16 @@ export function createExports(manifest: SSRManifest) {
};
}
-async function writeWebResponse(res: ServerResponse, webResponse: Response) {
+async function writeWebResponse(app: NodeApp, res: ServerResponse, webResponse: Response) {
const { status, headers, body } = webResponse;
+
+ if(app.setCookieHeaders) {
+ const setCookieHeaders: Array<string> = Array.from(app.setCookieHeaders(webResponse));
+ if(setCookieHeaders.length) {
+ res.setHeader('Set-Cookie', setCookieHeaders);
+ }
+ }
+
res.writeHead(status, Object.fromEntries(headers.entries()));
if (body) {
for await (const chunk of body as unknown as Readable) {
diff --git a/packages/integrations/vercel/src/edge/entrypoint.ts b/packages/integrations/vercel/src/edge/entrypoint.ts
index 8063c271a..b37421744 100644
--- a/packages/integrations/vercel/src/edge/entrypoint.ts
+++ b/packages/integrations/vercel/src/edge/entrypoint.ts
@@ -15,7 +15,13 @@ export function createExports(manifest: SSRManifest) {
const handler = async (request: Request): Promise<Response> => {
if (app.match(request)) {
Reflect.set(request, clientAddressSymbol, request.headers.get('x-forwarded-for'));
- return await app.render(request);
+ const response = await app.render(request);
+ if(app.setCookieHeaders) {
+ for(const setCookieHeader of app.setCookieHeaders(response)) {
+ response.headers.append('Set-Cookie', setCookieHeader);
+ }
+ }
+ return response;
}
return new Response(null, {
diff --git a/packages/integrations/vercel/src/serverless/entrypoint.ts b/packages/integrations/vercel/src/serverless/entrypoint.ts
index 6b94f201c..e41d0a438 100644
--- a/packages/integrations/vercel/src/serverless/entrypoint.ts
+++ b/packages/integrations/vercel/src/serverless/entrypoint.ts
@@ -28,7 +28,7 @@ export const createExports = (manifest: SSRManifest) => {
return res.end('Not found');
}
- await setResponse(res, await app.render(request, routeData));
+ await setResponse(app, res, await app.render(request, routeData));
};
return { default: handler };
diff --git a/packages/integrations/vercel/src/serverless/request-transform.ts b/packages/integrations/vercel/src/serverless/request-transform.ts
index 6f3a063bd..97337751f 100644
--- a/packages/integrations/vercel/src/serverless/request-transform.ts
+++ b/packages/integrations/vercel/src/serverless/request-transform.ts
@@ -1,4 +1,5 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
+import type { App } from 'astro/app';
import { Readable } from 'node:stream';
const clientAddressSymbol = Symbol.for('astro.clientAddress');
@@ -77,7 +78,7 @@ export async function getRequest(base: string, req: IncomingMessage): Promise<Re
return request;
}
-export async function setResponse(res: ServerResponse, response: Response): Promise<void> {
+export async function setResponse(app: App, res: ServerResponse, response: Response): Promise<void> {
const headers = Object.fromEntries(response.headers);
if (response.headers.has('set-cookie')) {
@@ -85,6 +86,13 @@ export async function setResponse(res: ServerResponse, response: Response): Prom
headers['set-cookie'] = response.headers.raw()['set-cookie'];
}
+ if(app.setCookieHeaders) {
+ const setCookieHeaders: Array<string> = Array.from(app.setCookieHeaders(response));
+ if(setCookieHeaders.length) {
+ res.setHeader('Set-Cookie', setCookieHeaders);
+ }
+ }
+
res.writeHead(response.status, headers);
if (response.body instanceof Readable) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 98ba35f22..aaeec592c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -369,6 +369,7 @@ importers:
'@types/chai': ^4.3.1
'@types/common-ancestor-path': ^1.0.0
'@types/connect': ^3.4.35
+ '@types/cookie': ^0.5.1
'@types/debug': ^4.1.7
'@types/diff': ^5.0.2
'@types/estree': ^0.0.51
@@ -392,6 +393,7 @@ importers:
cheerio: ^1.0.0-rc.11
ci-info: ^3.3.1
common-ancestor-path: ^1.0.1
+ cookie: ^0.5.0
debug: ^4.3.4
diff: ^5.1.0
eol: ^0.9.1
@@ -460,6 +462,7 @@ importers:
boxen: 6.2.1
ci-info: 3.4.0
common-ancestor-path: 1.0.1
+ cookie: 0.5.0
debug: 4.3.4
diff: 5.1.0
eol: 0.9.1
@@ -506,6 +509,7 @@ importers:
'@types/chai': 4.3.3
'@types/common-ancestor-path': 1.0.0
'@types/connect': 3.4.35
+ '@types/cookie': 0.5.1
'@types/debug': 4.1.7
'@types/diff': 5.0.2
'@types/estree': 0.0.51
@@ -1200,6 +1204,12 @@ importers:
dependencies:
astro: link:../../..
+ packages/astro/test/fixtures/astro-cookies:
+ specifiers:
+ astro: workspace:*
+ dependencies:
+ astro: link:../../..
+
packages/astro/test/fixtures/astro-css-bundling:
specifiers:
astro: workspace:*
@@ -4997,7 +5007,7 @@ packages:
babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.19.1
babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.19.1
babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.19.1
- core-js-compat: 3.25.2
+ core-js-compat: 3.25.3
semver: 6.3.0
transitivePeerDependencies:
- supports-color
@@ -9286,6 +9296,10 @@ packages:
'@types/node': 18.7.23
dev: true
+ /@types/cookie/0.5.1:
+ resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
+ dev: true
+
/@types/debug/4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies:
@@ -10311,7 +10325,7 @@ packages:
dependencies:
'@babel/core': 7.19.1
'@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.19.1
- core-js-compat: 3.25.2
+ core-js-compat: 3.25.3
transitivePeerDependencies:
- supports-color
dev: false
@@ -10823,8 +10837,13 @@ packages:
engines: {node: '>= 0.6'}
dev: true
- /core-js-compat/3.25.2:
- resolution: {integrity: sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==}
+ /cookie/0.5.0:
+ resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /core-js-compat/3.25.3:
+ resolution: {integrity: sha512-xVtYpJQ5grszDHEUU9O7XbjjcZ0ccX3LgQsyqSvTnjX97ZqEgn9F5srmrwwwMtbKzDllyFPL+O+2OFMl1lU4TQ==}
dependencies:
browserslist: 4.21.4
dev: false