From 23804cda3892abe376c2657d0438ae2d80006cc9 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 17 Jul 2023 15:53:10 +0100 Subject: feat(@astrojs/netlify): edge middleware support (#7632) Co-authored-by: Bjorn Lu Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com> --- .../netlify/test/functions/edge-middleware.test.js | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/integrations/netlify/test/functions/edge-middleware.test.js (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js new file mode 100644 index 000000000..219fd1ced --- /dev/null +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -0,0 +1,43 @@ +import netlifyAdapter from '../../dist/index.js'; +import { testIntegration, loadFixture } from './test-utils.js'; +import { expect } from 'chai'; + +describe('Middleware', () => { + it('with edge handle file, should successfully build the middleware', async () => { + /** @type {import('./test-utils').Fixture} */ + const fixture = await loadFixture({ + root: new URL('./fixtures/middleware-with-handler-file/', import.meta.url).toString(), + output: 'server', + adapter: netlifyAdapter({ + dist: new URL('./fixtures/middleware-with-handler-file/dist/', import.meta.url), + }), + site: `http://example.com`, + integrations: [testIntegration()], + build: { + excludeMiddleware: true, + }, + }); + await fixture.build(); + const contents = await fixture.readFile('../.netlify/edge-functions/edgeMiddleware.js'); + expect(contents.includes('"Hello world"')).to.be.true; + }); + + it('without edge handle file, should successfully build the middleware', async () => { + /** @type {import('./test-utils').Fixture} */ + const fixture = await loadFixture({ + root: new URL('./fixtures/middleware-without-handler-file/', import.meta.url).toString(), + output: 'server', + adapter: netlifyAdapter({ + dist: new URL('./fixtures/middleware-without-handler-file/dist/', import.meta.url), + }), + site: `http://example.com`, + integrations: [testIntegration()], + build: { + excludeMiddleware: true, + }, + }); + await fixture.build(); + const contents = await fixture.readFile('../.netlify/edge-functions/edgeMiddleware.js'); + expect(contents.includes('"Hello world"')).to.be.false; + }); +}); -- cgit v1.2.3 From d7fe86ff639fb8a159de55080fa398f005509016 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 28 Jul 2023 10:11:13 +0100 Subject: feat: adapter features, deprecate astro configs (#7839) --- packages/integrations/netlify/src/integration-functions.ts | 12 ++++++++++-- packages/integrations/netlify/src/netlify-functions.ts | 2 ++ .../netlify/test/functions/edge-middleware.test.js | 1 + .../netlify/test/functions/split-support.test.js | 4 +--- 4 files changed, 14 insertions(+), 5 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index a950167e5..3b10e096c 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -8,12 +8,16 @@ import { createRedirects } from './shared.js'; export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware'; export const ASTRO_LOCALS_HEADER = 'x-astro-locals'; -export function getAdapter(args: Args = {}): AstroAdapter { +export function getAdapter({ functionPerRoute, edgeMiddleware, ...args }: Args): AstroAdapter { return { name: '@astrojs/netlify/functions', serverEntrypoint: '@astrojs/netlify/netlify-functions.js', exports: ['handler'], args, + adapterFeatures: { + functionPerRoute, + edgeMiddleware, + }, }; } @@ -21,12 +25,16 @@ interface NetlifyFunctionsOptions { dist?: URL; builders?: boolean; binaryMediaTypes?: string[]; + edgeMiddleware?: boolean; + functionPerRoute?: boolean; } function netlifyFunctions({ dist, builders, binaryMediaTypes, + functionPerRoute = false, + edgeMiddleware = false, }: NetlifyFunctionsOptions = {}): AstroIntegration { let _config: AstroConfig; let _entryPoints: Map; @@ -53,7 +61,7 @@ function netlifyFunctions({ _entryPoints = entryPoints; }, 'astro:config:done': ({ config, setAdapter }) => { - setAdapter(getAdapter({ binaryMediaTypes, builders })); + setAdapter(getAdapter({ binaryMediaTypes, builders, functionPerRoute, edgeMiddleware })); _config = config; ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ''); diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts index 91c4f1ef7..3da0718b0 100644 --- a/packages/integrations/netlify/src/netlify-functions.ts +++ b/packages/integrations/netlify/src/netlify-functions.ts @@ -9,6 +9,8 @@ applyPolyfills(); export interface Args { builders?: boolean; binaryMediaTypes?: string[]; + edgeMiddleware: boolean; + functionPerRoute: boolean; } function parseContentType(header?: string) { diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 219fd1ced..a83720a4d 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -10,6 +10,7 @@ describe('Middleware', () => { output: 'server', adapter: netlifyAdapter({ dist: new URL('./fixtures/middleware-with-handler-file/dist/', import.meta.url), + edgeMiddleware: true, }), site: `http://example.com`, integrations: [testIntegration()], diff --git a/packages/integrations/netlify/test/functions/split-support.test.js b/packages/integrations/netlify/test/functions/split-support.test.js index 217b3c0d3..fde8b5eb1 100644 --- a/packages/integrations/netlify/test/functions/split-support.test.js +++ b/packages/integrations/netlify/test/functions/split-support.test.js @@ -13,6 +13,7 @@ describe('Split support', () => { output: 'server', adapter: netlifyAdapter({ dist: new URL('./fixtures/split-support/dist/', import.meta.url), + functionPerRoute: true, }), site: `http://example.com`, integrations: [ @@ -22,9 +23,6 @@ describe('Split support', () => { }, }), ], - build: { - split: true, - }, }); await fixture.build(); }); -- cgit v1.2.3 From 1e0610b088a518ea257287da9d1bc0ccd15cb522 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 25 Sep 2023 15:52:27 -0400 Subject: chore(netlify): fixes after migration Co-authored-by: HiDeoo <494699+HiDeoo@users.noreply.github.com> Co-authored-by: Matthew Phillips Co-authored-by: Reuben Tier <64310361+TheOtterlord@users.noreply.github.com> --- packages/integrations/netlify/README.md | 46 +++++----- packages/integrations/netlify/package.json | 15 +-- packages/integrations/netlify/src/env.d.ts | 1 + .../netlify/src/integration-functions.ts | 2 + .../netlify/test/functions/404.test.js | 22 ++--- .../netlify/test/functions/base64-response.test.js | 20 +--- .../netlify/test/functions/builders.test.js | 21 +---- .../netlify/test/functions/cookies.test.js | 20 +--- .../netlify/test/functions/dynamic-route.test.js | 25 ++--- .../netlify/test/functions/edge-middleware.test.js | 42 ++------- .../test/functions/fixtures/404/astro.config.mjs | 8 ++ .../test/functions/fixtures/404/package.json | 6 ++ .../test/functions/fixtures/404/src/env.d.ts | 1 + .../fixtures/base64-response/astro.config.mjs | 10 ++ .../fixtures/base64-response/package.json | 6 ++ .../fixtures/base64-response/src/env.d.ts | 1 + .../functions/fixtures/builders/astro.config.mjs | 10 ++ .../test/functions/fixtures/builders/package.json | 6 ++ .../test/functions/fixtures/builders/src/env.d.ts | 1 + .../functions/fixtures/cookies/astro.config.mjs | 10 ++ .../test/functions/fixtures/cookies/package.json | 6 ++ .../test/functions/fixtures/cookies/src/env.d.ts | 1 + .../fixtures/dynamic-route/astro.config.mjs | 10 ++ .../functions/fixtures/dynamic-route/package.json | 6 ++ .../functions/fixtures/dynamic-route/src/env.d.ts | 1 + .../middleware-with-handler-file/astro.config.mjs | 10 ++ .../middleware-with-handler-file/package.json | 6 ++ .../middleware-with-handler-file/src/env.d.ts | 1 + .../astro.config.mjs | 10 ++ .../middleware-without-handler-file/package.json | 6 ++ .../middleware-without-handler-file/src/env.d.ts | 1 + .../functions/fixtures/prerender/astro.config.mjs | 11 +++ .../test/functions/fixtures/prerender/package.json | 6 ++ .../test/functions/fixtures/prerender/src/env.d.ts | 1 + .../functions/fixtures/redirects/astro.config.mjs | 11 +++ .../test/functions/fixtures/redirects/package.json | 6 ++ .../test/functions/fixtures/redirects/src/env.d.ts | 1 + .../fixtures/split-support/astro.config.mjs | 11 +++ .../functions/fixtures/split-support/package.json | 6 ++ .../netlify/test/functions/prerender.test.js | 41 ++------- .../netlify/test/functions/redirects.test.js | 27 ++---- .../netlify/test/functions/split-support.test.js | 102 +++++++++------------ .../netlify/test/functions/test-utils.js | 4 +- .../test/hosted/hosted-astro-project/package.json | 2 +- .../test/static/fixtures/redirects/src/env.d.ts | 1 + .../integrations/netlify/test/static/test-utils.js | 4 +- packages/integrations/netlify/test/test-utils.js | 12 +++ packages/integrations/netlify/tsconfig.json | 11 ++- 48 files changed, 332 insertions(+), 256 deletions(-) create mode 100644 packages/integrations/netlify/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/404/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/builders/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/cookies/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/cookies/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/redirects/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/redirects/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/split-support/package.json create mode 100644 packages/integrations/netlify/test/static/fixtures/redirects/src/env.d.ts create mode 100644 packages/integrations/netlify/test/test-utils.js (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md index a313486dc..f448af1b3 100644 --- a/packages/integrations/netlify/README.md +++ b/packages/integrations/netlify/README.md @@ -46,15 +46,15 @@ If you prefer to install the adapter manually instead, complete the following tw 1. Add two new lines to your `astro.config.mjs` project configuration file. - ```js ins={3, 6-7} - // astro.config.mjs - import { defineConfig } from 'astro/config'; - import netlify from '@astrojs/netlify/functions'; - - export default defineConfig({ - output: 'server', - adapter: netlify(), - }); + ```diff lang="js" + // astro.config.mjs + import { defineConfig } from 'astro/config'; + + import netlify from '@astrojs/netlify/functions'; + + export default defineConfig({ + + output: 'server', + + adapter: netlify(), + }); ``` ### Run middleware in Edge Functions @@ -63,17 +63,17 @@ When deploying to Netlify Functions, you can choose to use an Edge Function to r To enable this, set the `edgeMiddleware` config option to `true`: -```js ins={9} -// astro.config.mjs -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; +```diff lang="js" + // astro.config.mjs + import { defineConfig } from 'astro/config'; + import netlify from '@astrojs/netlify/functions'; -export default defineConfig({ - output: 'server', - adapter: netlify({ - edgeMiddleware: true, - }), -}); + export default defineConfig({ + output: 'server', + adapter: netlify({ ++ edgeMiddleware: true, + }), + }); ``` #### Pass edge context to your site @@ -128,6 +128,7 @@ export default defineConfig({ For static sites you usually don't need an adapter. However, if you use `redirects` configuration in your Astro config, the Netlify adapter can be used to translate this to the proper `_redirects` format. ```js +// astro.config.mjs import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify/static'; @@ -155,6 +156,7 @@ The following example sets a revalidation time of 45, causing Netlify to store t ```astro --- +// src/pages/index.astro import Layout from '../components/Layout.astro'; if (import.meta.env.PROD) { @@ -206,7 +208,8 @@ export default defineConfig({ And then point to the dist in your `netlify.toml`: -```toml title="netlify.toml" +```toml +# netlify.toml [functions] directory = "dist/functions" ``` @@ -238,9 +241,8 @@ Netlify Functions requires binary data in the `body` to be base64 encoded. The ` We check for common mime types for audio, image, and video files. To include specific mime types that should be treated as binary data, include the `binaryMediaTypes` option with a list of binary mime types. -```js {12} +```js // src/pages/image.jpg.ts - import fs from 'node:fs'; export function GET() { diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 6081ed074..325f7d6c6 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -29,32 +29,33 @@ "dist" ], "scripts": { - "build": "astro-scripts build \"src/**/*.ts\" && tsc", - "build:ci": "astro-scripts build \"src/**/*.ts\"", - "dev": "astro-scripts dev \"src/**/*.ts\"", + "build": "tsc", "test-fn": "mocha --exit --timeout 20000 --file \"./test/setup.js\" test/functions/", "test-edge": "deno test --allow-run --allow-read --allow-net --allow-env --allow-write ./test/edge-functions/", "test": "pnpm test-fn", "test:hosted": "mocha --exit --timeout 30000 test/hosted" }, "dependencies": { - "@astrojs/underscore-redirects": "workspace:*", + "@astrojs/underscore-redirects": "^0.3.0", "@netlify/functions": "^2.0.1", "esbuild": "^0.19.2" }, "peerDependencies": { - "astro": "workspace:^3.1.4" + "astro": "^3.1.4" }, "devDependencies": { "@netlify/edge-functions": "^2.0.0", "@netlify/edge-handler-types": "^0.34.1", "@types/node": "^18.17.8", - "astro": "workspace:*", - "astro-scripts": "workspace:*", + "astro": "^3.1.4", "chai": "^4.3.7", "chai-jest-snapshot": "^2.0.0", "cheerio": "1.0.0-rc.12", + "execa": "^8.0.1", + "fast-glob": "^3.3.1", "mocha": "^10.2.0", + "strip-ansi": "^7.1.0", + "typescript": "^5.2.2", "vite": "^4.4.9" }, "astro": { diff --git a/packages/integrations/netlify/src/env.d.ts b/packages/integrations/netlify/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index 33e7bade3..ed16e0200 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -77,9 +77,11 @@ function netlifyFunctions({ ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ''); if (config.output === 'static') { + // eslint-disable-next-line no-console console.warn( `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.` ); + // eslint-disable-next-line no-console console.warn( `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.` ); diff --git a/packages/integrations/netlify/test/functions/404.test.js b/packages/integrations/netlify/test/functions/404.test.js index f12919a39..1782507db 100644 --- a/packages/integrations/netlify/test/functions/404.test.js +++ b/packages/integrations/netlify/test/functions/404.test.js @@ -1,26 +1,18 @@ import { expect } from 'chai'; -import netlifyAdapter from '../../dist/index.js'; -import { loadFixture, testIntegration } from './test-utils.js'; +import fs from 'fs/promises'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; + +const root = new URL('./fixtures/404/', import.meta.url).toString(); describe('404 page', () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/404/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/404/dist/', import.meta.url), - }), - site: `http://example.com`, - integrations: [testIntegration()], - }); - await fixture.build(); + await cli('build', '--root', fileURLToPath(root)); }); it('404 route is included in the redirect file', async () => { - const redir = await fixture.readFile('/_redirects'); + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); const expr = new RegExp('/* /.netlify/functions/entry 404'); expect(redir).to.match(expr); }); diff --git a/packages/integrations/netlify/test/functions/base64-response.test.js b/packages/integrations/netlify/test/functions/base64-response.test.js index 6e59bd192..520eaf646 100644 --- a/packages/integrations/netlify/test/functions/base64-response.test.js +++ b/packages/integrations/netlify/test/functions/base64-response.test.js @@ -1,23 +1,13 @@ import { expect } from 'chai'; -import { loadFixture, testIntegration } from './test-utils.js'; -import netlifyAdapter from '../../dist/index.js'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; + +const root = new URL('./fixtures/base64-response/', import.meta.url).toString(); describe('Base64 Responses', () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ - let fixture; before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/base64-response/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/base64-response/dist/', import.meta.url), - binaryMediaTypes: ['font/otf'], - }), - site: `http://example.com`, - integrations: [testIntegration()], - }); - await fixture.build(); + await cli('build', '--root', fileURLToPath(root)); }); it('Can return base64 encoded strings', async () => { diff --git a/packages/integrations/netlify/test/functions/builders.test.js b/packages/integrations/netlify/test/functions/builders.test.js index d47af92c0..e927527a1 100644 --- a/packages/integrations/netlify/test/functions/builders.test.js +++ b/packages/integrations/netlify/test/functions/builders.test.js @@ -1,23 +1,12 @@ import { expect } from 'chai'; -import { loadFixture, testIntegration } from './test-utils.js'; -import netlifyAdapter from '../../dist/index.js'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -describe('Builders', () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ - let fixture; +const root = new URL('./fixtures/builders/', import.meta.url).toString(); +describe('Builders', () => { before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/builders/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/builders/dist/', import.meta.url), - builders: true, - }), - site: `http://example.com`, - integrations: [testIntegration()], - }); - await fixture.build(); + await cli('build', '--root', fileURLToPath(root)); }); it('A route can set builders ttl', async () => { diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index f15695235..328294d10 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,22 +1,12 @@ import { expect } from 'chai'; -import { loadFixture, testIntegration } from './test-utils.js'; -import netlifyAdapter from '../../dist/index.js'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -describe('Cookies', () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ - let fixture; +const root = new URL('./fixtures/cookies/', import.meta.url).toString(); +describe('Cookies', () => { before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/cookies/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/cookies/dist/', import.meta.url), - }), - site: `http://example.com`, - integrations: [testIntegration()], - }); - await fixture.build(); + await cli('build', '--root', fileURLToPath(root)); }); it('Can set multiple', async () => { diff --git a/packages/integrations/netlify/test/functions/dynamic-route.test.js b/packages/integrations/netlify/test/functions/dynamic-route.test.js index 6bb68eab8..2e20454e6 100644 --- a/packages/integrations/netlify/test/functions/dynamic-route.test.js +++ b/packages/integrations/netlify/test/functions/dynamic-route.test.js @@ -1,31 +1,22 @@ import { expect } from 'chai'; -import netlifyAdapter from '../../dist/index.js'; -import { loadFixture, testIntegration } from './test-utils.js'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; +import fs from 'fs/promises'; -describe('Dynamic pages', () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; +const root = new URL('./fixtures/dynamic-route/', import.meta.url).toString(); +describe('Dynamic pages', () => { before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/dynamic-route/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/dynamic-route/dist/', import.meta.url), - }), - site: `http://example.com`, - integrations: [testIntegration()], - }); - await fixture.build(); + await cli('build', '--root', fileURLToPath(root)); }); it('Dynamic pages are included in the redirects file', async () => { - const redir = await fixture.readFile('/_redirects'); + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); expect(redir).to.match(/\/products\/:id/); }); it('Prerendered routes are also included using placeholder syntax', async () => { - const redir = await fixture.readFile('/_redirects'); + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); expect(redir).to.include('/pets/:cat /pets/:cat/index.html 200'); expect(redir).to.include('/pets/:dog /pets/:dog/index.html 200'); expect(redir).to.include('/pets /.netlify/functions/entry 200'); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index a83720a4d..7e51b20da 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,44 +1,20 @@ -import netlifyAdapter from '../../dist/index.js'; -import { testIntegration, loadFixture } from './test-utils.js'; +import { fileURLToPath } from 'url'; +import { cli } from './test-utils.js'; +import fs from 'fs/promises'; import { expect } from 'chai'; describe('Middleware', () => { it('with edge handle file, should successfully build the middleware', async () => { - /** @type {import('./test-utils').Fixture} */ - const fixture = await loadFixture({ - root: new URL('./fixtures/middleware-with-handler-file/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/middleware-with-handler-file/dist/', import.meta.url), - edgeMiddleware: true, - }), - site: `http://example.com`, - integrations: [testIntegration()], - build: { - excludeMiddleware: true, - }, - }); - await fixture.build(); - const contents = await fixture.readFile('../.netlify/edge-functions/edgeMiddleware.js'); + const root = new URL('./fixtures/middleware-with-handler-file/', import.meta.url).toString(); + await cli('build', '--root', fileURLToPath(root)); + const contents = await fs.readFile(new URL('./.netlify/edge-functions/edgeMiddleware.js', root), 'utf-8'); expect(contents.includes('"Hello world"')).to.be.true; }); it('without edge handle file, should successfully build the middleware', async () => { - /** @type {import('./test-utils').Fixture} */ - const fixture = await loadFixture({ - root: new URL('./fixtures/middleware-without-handler-file/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/middleware-without-handler-file/dist/', import.meta.url), - }), - site: `http://example.com`, - integrations: [testIntegration()], - build: { - excludeMiddleware: true, - }, - }); - await fixture.build(); - const contents = await fixture.readFile('../.netlify/edge-functions/edgeMiddleware.js'); + const root = new URL('./fixtures/middleware-without-handler-file/', import.meta.url).toString(); + await cli('build', '--root', fileURLToPath(root)); + const contents = await fs.readFile(new URL('./.netlify/edge-functions/edgeMiddleware.js', root), 'utf-8'); expect(contents.includes('"Hello world"')).to.be.false; }); }); diff --git a/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs new file mode 100644 index 000000000..d5f8922f9 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs @@ -0,0 +1,8 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify(), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/404/package.json b/packages/integrations/netlify/test/functions/fixtures/404/package.json new file mode 100644 index 000000000..aa95a555b --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/404/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-fourohfour", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs new file mode 100644 index 000000000..db8774760 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + binaryMediaTypes: ['font/otf'], + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/package.json b/packages/integrations/netlify/test/functions/fixtures/base64-response/package.json new file mode 100644 index 000000000..d52dd0c8e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/base64-response/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-base64-response", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs new file mode 100644 index 000000000..752ce4bfb --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + builders: true, + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/package.json b/packages/integrations/netlify/test/functions/fixtures/builders/package.json new file mode 100644 index 000000000..5f8b5998a --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/builders/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-builders", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs new file mode 100644 index 000000000..dd3dac266 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/cookies/package.json b/packages/integrations/netlify/test/functions/fixtures/cookies/package.json new file mode 100644 index 000000000..2e3b3f5ed --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/cookies/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-cookies", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/cookies/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/cookies/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/cookies/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs new file mode 100644 index 000000000..d1d739289 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + edgeMiddleware: true, + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json new file mode 100644 index 000000000..8bf6a6164 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-dynamic", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs new file mode 100644 index 000000000..d1d739289 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + edgeMiddleware: true, + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json new file mode 100644 index 000000000..3eaaf8f76 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-middleware-with-handler-file", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs new file mode 100644 index 000000000..d1d739289 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + edgeMiddleware: true, + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json new file mode 100644 index 000000000..24ffca459 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-middleware-without-handler-file", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs new file mode 100644 index 000000000..e9e11092c --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs @@ -0,0 +1,11 @@ +import { defineConfig } from 'astro/config'; +import netlifyAdapter from '../../../../dist/index.js'; + + +export default defineConfig({ + output: process.env.ASTRO_OUTPUT || 'server', + adapter: netlifyAdapter({ + dist: new URL('./dist/', import.meta.url), + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/package.json b/packages/integrations/netlify/test/functions/fixtures/prerender/package.json new file mode 100644 index 000000000..c37740fce --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/prerender/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-prerender", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs new file mode 100644 index 000000000..3a69f76d4 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs @@ -0,0 +1,11 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'hybrid', + adapter: netlify(), + site: `http://example.com`, + redirects: { + '/other': '/', + }, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/redirects/package.json b/packages/integrations/netlify/test/functions/fixtures/redirects/package.json new file mode 100644 index 000000000..c2cf4c6ec --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/redirects/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-redirects", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/redirects/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/redirects/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/redirects/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs new file mode 100644 index 000000000..cf494e10b --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs @@ -0,0 +1,11 @@ +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + dist: new URL('./fixtures/split-support/dist/', import.meta.url), + functionPerRoute: true, + }), + site: `http://example.com`, +}); diff --git a/packages/integrations/netlify/test/functions/fixtures/split-support/package.json b/packages/integrations/netlify/test/functions/fixtures/split-support/package.json new file mode 100644 index 000000000..f8894eb0d --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/split-support/package.json @@ -0,0 +1,6 @@ +{ + "name": "@test/netlify-split-support", + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} diff --git a/packages/integrations/netlify/test/functions/prerender.test.js b/packages/integrations/netlify/test/functions/prerender.test.js index 2028e89c3..8acc5a519 100644 --- a/packages/integrations/netlify/test/functions/prerender.test.js +++ b/packages/integrations/netlify/test/functions/prerender.test.js @@ -1,23 +1,14 @@ import { expect } from 'chai'; -import netlifyAdapter from '../../dist/index.js'; -import { loadFixture, testIntegration } from './test-utils.js'; +import fs from 'fs/promises'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -describe('Mixed Prerendering with SSR', () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; +const root = new URL('./fixtures/prerender/', import.meta.url).toString(); +describe('Mixed Prerendering with SSR', () => { before(async () => { process.env.PRERENDER = true; - fixture = await loadFixture({ - root: new URL('./fixtures/prerender/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/prerender/dist/', import.meta.url), - }), - site: `http://example.com`, - integrations: [testIntegration()], - }); - await fixture.build(); + await cli('build', '--root', fileURLToPath(root)); }); after(() => { @@ -25,7 +16,7 @@ describe('Mixed Prerendering with SSR', () => { }); it('Wildcard 404 is sorted last', async () => { - const redir = await fixture.readFile('/_redirects'); + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); const baseRouteIndex = redir.indexOf('/ /.netlify/functions/entry 200'); const oneRouteIndex = redir.indexOf('/one /one/index.html 200'); const fourOhFourWildCardIndex = redir.indexOf('/* /.netlify/functions/entry 404'); @@ -37,21 +28,10 @@ describe('Mixed Prerendering with SSR', () => { }); describe('Mixed Hybrid rendering with SSR', () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; - before(async () => { process.env.PRERENDER = false; - fixture = await loadFixture({ - root: new URL('./fixtures/prerender/', import.meta.url).toString(), - output: 'hybrid', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/prerender/dist/', import.meta.url), - }), - site: `http://example.com`, - integrations: [testIntegration()], - }); - await fixture.build(); + process.env.ASTRO_OUTPUT = 'hybrid'; + await cli('build', '--root', fileURLToPath(root)); }); after(() => { @@ -59,8 +39,7 @@ describe('Mixed Hybrid rendering with SSR', () => { }); it('outputs a correct redirect file', async () => { - const redir = await fixture.readFile('/_redirects'); - console.log(redir); + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); const baseRouteIndex = redir.indexOf('/one /.netlify/functions/entry 200'); const rootRouteIndex = redir.indexOf('/ /index.html 200'); const fourOhFourIndex = redir.indexOf('/404 /404.html 200'); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index 8e3d46a68..855378ad4 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,31 +1,18 @@ import { expect } from 'chai'; -import netlifyAdapter from '../../dist/index.js'; -import { loadFixture, testIntegration } from './test-utils.js'; +import fs from 'fs/promises'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -describe('SSG - Redirects', () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ - let fixture; +const root = new URL('../functions/fixtures/redirects/', import.meta.url).toString(); +describe('SSG - Redirects', () => { before(async () => { - fixture = await loadFixture({ - root: new URL('../functions/fixtures/redirects/', import.meta.url).toString(), - output: 'hybrid', - adapter: netlifyAdapter({ - dist: new URL('../functions/fixtures/redirects/dist/', import.meta.url), - }), - site: `http://example.com`, - integrations: [testIntegration()], - redirects: { - '/other': '/', - }, - }); - await fixture.build(); + await cli('build', '--root', fileURLToPath(root)); }); it('Creates a redirects file', async () => { - let redirects = await fixture.readFile('/_redirects'); + let redirects = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); let parts = redirects.split(/\s+/); - console.log(parts); expect(parts).to.deep.equal([ '/other', '/', diff --git a/packages/integrations/netlify/test/functions/split-support.test.js b/packages/integrations/netlify/test/functions/split-support.test.js index 90427523c..6d3f9d7b3 100644 --- a/packages/integrations/netlify/test/functions/split-support.test.js +++ b/packages/integrations/netlify/test/functions/split-support.test.js @@ -1,63 +1,51 @@ -import { expect } from 'chai'; -import netlifyAdapter from '../../dist/index.js'; -import { loadFixture, testIntegration } from './test-utils.js'; +import { expect } from "chai"; +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { cli } from "./test-utils.js"; -describe('Split support', () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; - let _entryPoints; +const root = new URL( + "../functions/fixtures/split-support/", + import.meta.url +).toString(); - before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/split-support/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/split-support/dist/', import.meta.url), - functionPerRoute: true, - }), - site: `http://example.com`, - integrations: [ - testIntegration({ - setEntryPoints(ep) { - _entryPoints = ep; - }, - }), - ], - }); - await fixture.build(); - }); +describe("Split support", () => { + let _entryPoints; - it('outputs a correct redirect file', async () => { - const redir = await fixture.readFile('/_redirects'); - const lines = redir.split(/[\r\n]+/); - expect(lines.length).to.equal(3); + before(async () => { + await cli("build", "--root", fileURLToPath(root)); + }); - expect(lines[0].includes('/blog')).to.be.true; - expect(lines[0].includes('blog.astro')).to.be.true; - expect(lines[0].includes('200')).to.be.true; - expect(lines[1].includes('/')).to.be.true; - expect(lines[1].includes('index.astro')).to.be.true; - expect(lines[1].includes('200')).to.be.true; - }); + it("outputs a correct redirect file", async () => { + let redir = await fs.readFile(new URL("./dist/_redirects", root), "utf-8"); + const lines = redir.split(/[\r\n]+/); + expect(lines.length).to.equal(3); - describe('Should create multiple functions', () => { - it('and hit 200', async () => { - if (_entryPoints) { - for (const [routeData, filePath] of _entryPoints) { - if (routeData.route !== '/_image') { - const { handler } = await import(filePath.toString()); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: `http://example.com${routeData.route}`, - body: '{}', - }); - expect(resp.statusCode).to.equal(200); - } - } - } else { - expect(false).to.be.true; - } - }); - }); + expect(lines[0].includes("/blog")).to.be.true; + expect(lines[0].includes("blog.astro")).to.be.true; + expect(lines[0].includes("200")).to.be.true; + expect(lines[1].includes("/")).to.be.true; + expect(lines[1].includes("index.astro")).to.be.true; + expect(lines[1].includes("200")).to.be.true; + }); + + describe("Should create multiple functions", () => { + it("and hit 200", async () => { + if (_entryPoints) { + for (const [routeData, filePath] of _entryPoints) { + if (routeData.route !== "/_image") { + const { handler } = await import(filePath.toString()); + const resp = await handler({ + httpMethod: "GET", + headers: {}, + rawUrl: `http://example.com${routeData.route}`, + body: "{}", + }); + expect(resp.statusCode).to.equal(200); + } + } + } else { + expect(false).to.be.true; + } + }); + }); }); diff --git a/packages/integrations/netlify/test/functions/test-utils.js b/packages/integrations/netlify/test/functions/test-utils.js index bed187962..c977af42e 100644 --- a/packages/integrations/netlify/test/functions/test-utils.js +++ b/packages/integrations/netlify/test/functions/test-utils.js @@ -1,11 +1,11 @@ // @ts-check import { fileURLToPath } from 'node:url'; -export * from '../../../../astro/test/test-utils.js'; +export * from '../test-utils.js'; /** * - * @returns {import('../../../../astro/dist/types/@types/astro').AstroIntegration} + * @returns {import('astro').AstroIntegration} */ export function testIntegration({ setEntryPoints } = {}) { return { diff --git a/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json b/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json index 328f3048d..0445d216d 100644 --- a/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json +++ b/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json @@ -7,6 +7,6 @@ }, "dependencies": { "@astrojs/netlify": "workspace:*", - "astro": "workspace:*" + "astro": "^3.2.0" } } diff --git a/packages/integrations/netlify/test/static/fixtures/redirects/src/env.d.ts b/packages/integrations/netlify/test/static/fixtures/redirects/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/static/fixtures/redirects/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/static/test-utils.js b/packages/integrations/netlify/test/static/test-utils.js index f57abab1d..44fcf84e0 100644 --- a/packages/integrations/netlify/test/static/test-utils.js +++ b/packages/integrations/netlify/test/static/test-utils.js @@ -1,11 +1,11 @@ // @ts-check import { fileURLToPath } from 'node:url'; -export * from '../../../../astro/test/test-utils.js'; +export * from '../test-utils.js'; /** * - * @returns {import('../../../../astro/dist/types/@types/astro').AstroIntegration} + * @returns {import('astro').AstroIntegration} */ export function testIntegration() { return { diff --git a/packages/integrations/netlify/test/test-utils.js b/packages/integrations/netlify/test/test-utils.js new file mode 100644 index 000000000..5ec8b49ba --- /dev/null +++ b/packages/integrations/netlify/test/test-utils.js @@ -0,0 +1,12 @@ +import { execa } from 'execa'; + +/** Returns a process running the Astro CLI. */ +export function cli(/** @type {string[]} */ ...args) { + const spawned = execa('npx', ['astro', ...args], { + env: { ASTRO_TELEMETRY_DISABLED: true }, + }); + + spawned.stdout.setEncoding('utf8'); + + return spawned; +} \ No newline at end of file diff --git a/packages/integrations/netlify/tsconfig.json b/packages/integrations/netlify/tsconfig.json index 4095e9b83..ba0d27c76 100644 --- a/packages/integrations/netlify/tsconfig.json +++ b/packages/integrations/netlify/tsconfig.json @@ -1,8 +1,13 @@ { - "extends": "../../../tsconfig.base.json", - "include": ["src"], + "extends": "../../tsconfig.base.json", + "include": [ + "src" + ], "compilerOptions": { "outDir": "./dist", - "typeRoots": ["node_modules/@types", "node_modules/@netlify"] + "typeRoots": [ + "node_modules/@types", + "node_modules/@netlify" + ] } } -- cgit v1.2.3 From 3d1329414e507345b19e1dee81fe1bbff4eb3ae0 Mon Sep 17 00:00:00 2001 From: alexanderniebuhr Date: Mon, 16 Oct 2023 14:10:41 +0000 Subject: [ci] format --- packages/integrations/netlify/CHANGELOG.md | 54 +-- packages/integrations/netlify/README.md | 48 +-- packages/integrations/netlify/builders-types.d.ts | 14 +- packages/integrations/netlify/src/env.d.ts | 2 +- packages/integrations/netlify/src/index.ts | 7 +- .../netlify/src/integration-functions.ts | 273 +++++++------- .../integrations/netlify/src/integration-static.ts | 54 +-- packages/integrations/netlify/src/middleware.ts | 101 ++--- .../integrations/netlify/src/netlify-functions.ts | 413 +++++++++++---------- packages/integrations/netlify/src/shared.ts | 171 +++++---- .../netlify/test/functions/404.test.js | 32 +- .../netlify/test/functions/base64-response.test.js | 93 ++--- .../netlify/test/functions/builders.test.js | 44 +-- .../netlify/test/functions/cookies.test.js | 54 +-- .../netlify/test/functions/dynamic-route.test.js | 50 ++- .../netlify/test/functions/edge-middleware.test.js | 46 ++- .../netlify/test/functions/prerender.test.js | 120 +++--- .../netlify/test/functions/redirects.test.js | 92 ++--- .../netlify/test/functions/test-utils.js | 50 +-- .../hosted/hosted-astro-project/astro.config.mjs | 8 +- .../netlify/test/hosted/hosted.test.js | 29 +- packages/integrations/netlify/test/setup.js | 8 +- .../netlify/test/static/redirects.test.js | 90 ++--- .../integrations/netlify/test/static/test-utils.js | 40 +- packages/integrations/netlify/test/test-utils.js | 14 +- packages/integrations/netlify/tsconfig.json | 9 +- 26 files changed, 1017 insertions(+), 899 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/CHANGELOG.md b/packages/integrations/netlify/CHANGELOG.md index 0c09187a0..8d63cb181 100644 --- a/packages/integrations/netlify/CHANGELOG.md +++ b/packages/integrations/netlify/CHANGELOG.md @@ -58,18 +58,18 @@ can tell Astro if it can support it. ```ts - import { AstroIntegration } from './astro'; + import { AstroIntegration } from "./astro"; function myIntegration(): AstroIntegration { return { - name: 'astro-awesome-list', + name: "astro-awesome-list", // new feature map supportedAstroFeatures: { - hybridOutput: 'experimental', - staticOutput: 'stable', - serverOutput: 'stable', + hybridOutput: "experimental", + staticOutput: "stable", + serverOutput: "stable", assets: { - supportKind: 'stable', + supportKind: "stable", isSharpCompatible: false, isSquooshCompatible: false, }, @@ -176,18 +176,18 @@ can tell Astro if it can support it. ```ts - import { AstroIntegration } from './astro'; + import { AstroIntegration } from "./astro"; function myIntegration(): AstroIntegration { return { - name: 'astro-awesome-list', + name: "astro-awesome-list", // new feature map supportedAstroFeatures: { - hybridOutput: 'experimental', - staticOutput: 'stable', - serverOutput: 'stable', + hybridOutput: "experimental", + staticOutput: "stable", + serverOutput: "stable", assets: { - supportKind: 'stable', + supportKind: "stable", isSharpCompatible: false, isSquooshCompatible: false, }, @@ -303,11 +303,11 @@ ```js // astro.config.mjs - import { defineConfig } from 'astro/config'; - import netlify from '@astrojs/netlify/functions'; + import { defineConfig } from "astro/config"; + import netlify from "@astrojs/netlify/functions"; export default defineConfig({ - output: 'server', + output: "server", adapter: netlify(), build: { split: true, @@ -558,14 +558,14 @@ The ability to customize SSR build configuration more granularly is now available in Astro. You can now customize the output folder for `server` (the server code for SSR), `client` (your client-side JavaScript and assets), and `serverEntry` (the name of the entrypoint server module). Here are the defaults: ```js - import { defineConfig } from 'astro/config'; + import { defineConfig } from "astro/config"; export default defineConfig({ - output: 'server', + output: "server", build: { - server: './dist/server/', - client: './dist/client/', - serverEntry: 'entry.mjs', + server: "./dist/server/", + client: "./dist/client/", + serverEntry: "entry.mjs", }, }); ``` @@ -579,12 +579,12 @@ ```js export default function myIntegration() { return { - name: 'my-integration', + name: "my-integration", hooks: { - 'astro:config:setup': ({ updateConfig }) => { + "astro:config:setup": ({ updateConfig }) => { updateConfig({ build: { - server: '...', + server: "...", }, }); }, @@ -629,12 +629,12 @@ ```js export function post({ cookies }) { - cookies.set('loggedIn', false); + cookies.set("loggedIn", false); return new Response(null, { status: 302, headers: { - Location: '/login', + Location: "/login", }, }); } @@ -885,8 +885,8 @@ This change adds a Netlify adapter that uses Netlify Functions. You can use it like so: ```js - import { defineConfig } from 'astro/config'; - import netlify from '@astrojs/netlify/functions'; + import { defineConfig } from "astro/config"; + import netlify from "@astrojs/netlify/functions"; export default defineConfig({ adapter: netlify(), diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md index f448af1b3..e3257e1eb 100644 --- a/packages/integrations/netlify/README.md +++ b/packages/integrations/netlify/README.md @@ -50,7 +50,7 @@ If you prefer to install the adapter manually instead, complete the following tw // astro.config.mjs import { defineConfig } from 'astro/config'; + import netlify from '@astrojs/netlify/functions'; - + export default defineConfig({ + output: 'server', + adapter: netlify(), @@ -86,9 +86,15 @@ In this example, `visitorCountry` and `hasEdgeMiddleware` would both be added to ```ts // src/netlify-edge-middleware.ts -import type { Context } from 'https://edge.netlify.com'; - -export default function ({ request, context }: { request: Request; context: Context }) { +import type { Context } from "https://edge.netlify.com"; + +export default function ({ + request, + context, +}: { + request: Request; + context: Context; +}) { // Return serializable data to add to Astro.locals return { visitorCountry: context.geo.country.name, @@ -112,11 +118,11 @@ The Netlify adapter builds to a single function by default. Astro 2.7 added supp ```js // astro.config.mjs -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; +import { defineConfig } from "astro/config"; +import netlify from "@astrojs/netlify/functions"; export default defineConfig({ - output: 'server', + output: "server", adapter: netlify({ functionPerRoute: true, }), @@ -129,14 +135,14 @@ For static sites you usually don't need an adapter. However, if you use `redirec ```js // astro.config.mjs -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/static'; +import { defineConfig } from "astro/config"; +import netlify from "@astrojs/netlify/static"; export default defineConfig({ adapter: netlify(), redirects: { - '/blog/old-post': '/blog/new-post', + "/blog/old-post": "/blog/new-post", }, }); ``` @@ -195,20 +201,20 @@ We build to the `dist` directory at the base of your project. To change this, us ```js // astro.config.mjs -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; +import { defineConfig } from "astro/config"; +import netlify from "@astrojs/netlify/functions"; export default defineConfig({ - output: 'server', + output: "server", adapter: netlify({ - dist: new URL('./dist/', import.meta.url), + dist: new URL("./dist/", import.meta.url), }), }); ``` And then point to the dist in your `netlify.toml`: -```toml +```toml # netlify.toml [functions] directory = "dist/functions" @@ -220,11 +226,11 @@ You can enable On-demand Builders using the `builders` option: ```js // astro.config.mjs -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; +import { defineConfig } from "astro/config"; +import netlify from "@astrojs/netlify/functions"; export default defineConfig({ - output: 'server', + output: "server", adapter: netlify({ builders: true, }), @@ -243,16 +249,16 @@ We check for common mime types for audio, image, and video files. To include spe ```js // src/pages/image.jpg.ts -import fs from 'node:fs'; +import fs from "node:fs"; export function GET() { - const buffer = fs.readFileSync('../image.jpg'); + const buffer = fs.readFileSync("../image.jpg"); // Return the buffer directly, @astrojs/netlify will base64 encode the body return new Response(buffer, { status: 200, headers: { - 'content-type': 'image/jpeg', + "content-type": "image/jpeg", }, }); } diff --git a/packages/integrations/netlify/builders-types.d.ts b/packages/integrations/netlify/builders-types.d.ts index 7c778be4f..95e7b0ba9 100644 --- a/packages/integrations/netlify/builders-types.d.ts +++ b/packages/integrations/netlify/builders-types.d.ts @@ -1,9 +1,9 @@ interface NetlifyLocals { - runtime: { - /** - * On-demand Builders support an optional time to live (TTL) pattern that allows you to set a fixed duration of time after which a cached builder response is invalidated. This allows you to force a refresh of a builder-generated response without a new deploy. - * @param ttl time to live, in seconds - */ - setBuildersTtl(ttl: number): void; - }; + runtime: { + /** + * On-demand Builders support an optional time to live (TTL) pattern that allows you to set a fixed duration of time after which a cached builder response is invalidated. This allows you to force a refresh of a builder-generated response without a new deploy. + * @param ttl time to live, in seconds + */ + setBuildersTtl(ttl: number): void; + }; } diff --git a/packages/integrations/netlify/src/env.d.ts b/packages/integrations/netlify/src/env.d.ts index 8c34fb45e..f964fe0cf 100644 --- a/packages/integrations/netlify/src/env.d.ts +++ b/packages/integrations/netlify/src/env.d.ts @@ -1 +1 @@ -/// \ No newline at end of file +/// diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index a374020f9..f9a974d61 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -1,2 +1,5 @@ -export { netlifyFunctions as default, netlifyFunctions } from './integration-functions.js'; -export { netlifyStatic } from './integration-static.js'; +export { + netlifyFunctions as default, + netlifyFunctions, +} from "./integration-functions.js"; +export { netlifyStatic } from "./integration-static.js"; diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index ed16e0200..e4586d654 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -1,144 +1,165 @@ -import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; -import { writeFile } from 'node:fs/promises'; -import { extname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { generateEdgeMiddleware } from './middleware.js'; -import type { Args } from './netlify-functions.js'; -import { createRedirects } from './shared.js'; +import type { + AstroAdapter, + AstroConfig, + AstroIntegration, + RouteData, +} from "astro"; +import { writeFile } from "node:fs/promises"; +import { extname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { generateEdgeMiddleware } from "./middleware.js"; +import type { Args } from "./netlify-functions.js"; +import { createRedirects } from "./shared.js"; -export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware'; -export const ASTRO_LOCALS_HEADER = 'x-astro-locals'; +export const NETLIFY_EDGE_MIDDLEWARE_FILE = "netlify-edge-middleware"; +export const ASTRO_LOCALS_HEADER = "x-astro-locals"; -export function getAdapter({ functionPerRoute, edgeMiddleware, ...args }: Args): AstroAdapter { - return { - name: '@astrojs/netlify/functions', - serverEntrypoint: '@astrojs/netlify/netlify-functions.js', - exports: ['handler'], - args, - adapterFeatures: { - functionPerRoute, - edgeMiddleware, - }, - supportedAstroFeatures: { - hybridOutput: 'stable', - staticOutput: 'stable', - serverOutput: 'stable', - assets: { - supportKind: 'stable', - isSharpCompatible: true, - isSquooshCompatible: true, - }, - }, - }; +export function getAdapter({ + functionPerRoute, + edgeMiddleware, + ...args +}: Args): AstroAdapter { + return { + name: "@astrojs/netlify/functions", + serverEntrypoint: "@astrojs/netlify/netlify-functions.js", + exports: ["handler"], + args, + adapterFeatures: { + functionPerRoute, + edgeMiddleware, + }, + supportedAstroFeatures: { + hybridOutput: "stable", + staticOutput: "stable", + serverOutput: "stable", + assets: { + supportKind: "stable", + isSharpCompatible: true, + isSquooshCompatible: true, + }, + }, + }; } interface NetlifyFunctionsOptions { - dist?: URL; - builders?: boolean; - binaryMediaTypes?: string[]; - edgeMiddleware?: boolean; - functionPerRoute?: boolean; + dist?: URL; + builders?: boolean; + binaryMediaTypes?: string[]; + edgeMiddleware?: boolean; + functionPerRoute?: boolean; } function netlifyFunctions({ - dist, - builders, - binaryMediaTypes, - functionPerRoute = false, - edgeMiddleware = false, + dist, + builders, + binaryMediaTypes, + functionPerRoute = false, + edgeMiddleware = false, }: NetlifyFunctionsOptions = {}): AstroIntegration { - let _config: AstroConfig; - let _entryPoints: Map; - let ssrEntryFile: string; - let _middlewareEntryPoint: URL; - return { - name: '@astrojs/netlify', - hooks: { - 'astro:config:setup': ({ config, updateConfig }) => { - const outDir = dist ?? new URL('./dist/', config.root); - updateConfig({ - outDir, - build: { - redirects: false, - client: outDir, - server: new URL('./.netlify/functions-internal/', config.root), - }, - }); - }, - 'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => { - if (middlewareEntryPoint) { - _middlewareEntryPoint = middlewareEntryPoint; - } - _entryPoints = entryPoints; - }, - 'astro:config:done': ({ config, setAdapter }) => { - setAdapter(getAdapter({ binaryMediaTypes, builders, functionPerRoute, edgeMiddleware })); - _config = config; - ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ''); + let _config: AstroConfig; + let _entryPoints: Map; + let ssrEntryFile: string; + let _middlewareEntryPoint: URL; + return { + name: "@astrojs/netlify", + hooks: { + "astro:config:setup": ({ config, updateConfig }) => { + const outDir = dist ?? new URL("./dist/", config.root); + updateConfig({ + outDir, + build: { + redirects: false, + client: outDir, + server: new URL("./.netlify/functions-internal/", config.root), + }, + }); + }, + "astro:build:ssr": async ({ entryPoints, middlewareEntryPoint }) => { + if (middlewareEntryPoint) { + _middlewareEntryPoint = middlewareEntryPoint; + } + _entryPoints = entryPoints; + }, + "astro:config:done": ({ config, setAdapter }) => { + setAdapter( + getAdapter({ + binaryMediaTypes, + builders, + functionPerRoute, + edgeMiddleware, + }), + ); + _config = config; + ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ""); - if (config.output === 'static') { - // eslint-disable-next-line no-console - console.warn( - `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.` - ); - // eslint-disable-next-line no-console - console.warn( - `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.` - ); - } - }, - 'astro:build:done': async ({ routes, dir }) => { - const functionsConfig = { - version: 1, - config: { - nodeModuleFormat: 'esm', - }, - }; - const functionsConfigPath = join(fileURLToPath(_config.build.server), 'entry.json'); - await writeFile(functionsConfigPath, JSON.stringify(functionsConfig)); + if (config.output === "static") { + // eslint-disable-next-line no-console + console.warn( + `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`, + ); + // eslint-disable-next-line no-console + console.warn( + `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`, + ); + } + }, + "astro:build:done": async ({ routes, dir }) => { + const functionsConfig = { + version: 1, + config: { + nodeModuleFormat: "esm", + }, + }; + const functionsConfigPath = join( + fileURLToPath(_config.build.server), + "entry.json", + ); + await writeFile(functionsConfigPath, JSON.stringify(functionsConfig)); - const type = builders ? 'builders' : 'functions'; - const kind = type ?? 'functions'; + const type = builders ? "builders" : "functions"; + const kind = type ?? "functions"; - if (_entryPoints.size) { - const routeToDynamicTargetMap = new Map(); - for (const [route, entryFile] of _entryPoints) { - const wholeFileUrl = fileURLToPath(entryFile); + if (_entryPoints.size) { + const routeToDynamicTargetMap = new Map(); + for (const [route, entryFile] of _entryPoints) { + const wholeFileUrl = fileURLToPath(entryFile); - const extension = extname(wholeFileUrl); - const relative = wholeFileUrl - .replace(fileURLToPath(_config.build.server), '') - .replace(extension, '') - .replaceAll('\\', '/'); - const dynamicTarget = `/.netlify/${kind}/${relative}`; + const extension = extname(wholeFileUrl); + const relative = wholeFileUrl + .replace(fileURLToPath(_config.build.server), "") + .replace(extension, "") + .replaceAll("\\", "/"); + const dynamicTarget = `/.netlify/${kind}/${relative}`; - routeToDynamicTargetMap.set(route, dynamicTarget); - } - await createRedirects(_config, routeToDynamicTargetMap, dir); - } else { - const dynamicTarget = `/.netlify/${kind}/${ssrEntryFile}`; - const map: [RouteData, string][] = routes.map((route) => { - return [route, dynamicTarget]; - }); - const routeToDynamicTargetMap = new Map(Array.from(map)); + routeToDynamicTargetMap.set(route, dynamicTarget); + } + await createRedirects(_config, routeToDynamicTargetMap, dir); + } else { + const dynamicTarget = `/.netlify/${kind}/${ssrEntryFile}`; + const map: [RouteData, string][] = routes.map((route) => { + return [route, dynamicTarget]; + }); + const routeToDynamicTargetMap = new Map(Array.from(map)); - await createRedirects(_config, routeToDynamicTargetMap, dir); - } - if (_middlewareEntryPoint) { - const outPath = fileURLToPath(new URL('./.netlify/edge-functions/', _config.root)); - const netlifyEdgeMiddlewareHandlerPath = new URL( - NETLIFY_EDGE_MIDDLEWARE_FILE, - _config.srcDir - ); - await generateEdgeMiddleware( - _middlewareEntryPoint, - outPath, - netlifyEdgeMiddlewareHandlerPath - ); - } - }, - }, - }; + await createRedirects(_config, routeToDynamicTargetMap, dir); + } + if (_middlewareEntryPoint) { + const outPath = fileURLToPath( + new URL("./.netlify/edge-functions/", _config.root), + ); + const netlifyEdgeMiddlewareHandlerPath = new URL( + NETLIFY_EDGE_MIDDLEWARE_FILE, + _config.srcDir, + ); + await generateEdgeMiddleware( + _middlewareEntryPoint, + outPath, + netlifyEdgeMiddlewareHandlerPath, + ); + } + }, + }, + }; } export { netlifyFunctions as default, netlifyFunctions }; diff --git a/packages/integrations/netlify/src/integration-static.ts b/packages/integrations/netlify/src/integration-static.ts index af2849867..beebdef05 100644 --- a/packages/integrations/netlify/src/integration-static.ts +++ b/packages/integrations/netlify/src/integration-static.ts @@ -1,30 +1,30 @@ -import type { AstroIntegration, RouteData } from 'astro'; -import { createRedirects } from './shared.js'; +import type { AstroIntegration, RouteData } from "astro"; +import { createRedirects } from "./shared.js"; export function netlifyStatic(): AstroIntegration { - let _config: any; - return { - name: '@astrojs/netlify', - hooks: { - 'astro:config:setup': ({ updateConfig }) => { - updateConfig({ - build: { - // Do not output HTML redirects because we are building a `_redirects` file. - redirects: false, - }, - }); - }, - 'astro:config:done': ({ config }) => { - _config = config; - }, - 'astro:build:done': async ({ dir, routes }) => { - const mappedRoutes: [RouteData, string][] = routes.map((route) => [ - route, - `/.netlify/static/`, - ]); - const routesToDynamicTargetMap = new Map(Array.from(mappedRoutes)); - await createRedirects(_config, routesToDynamicTargetMap, dir); - }, - }, - }; + let _config: any; + return { + name: "@astrojs/netlify", + hooks: { + "astro:config:setup": ({ updateConfig }) => { + updateConfig({ + build: { + // Do not output HTML redirects because we are building a `_redirects` file. + redirects: false, + }, + }); + }, + "astro:config:done": ({ config }) => { + _config = config; + }, + "astro:build:done": async ({ dir, routes }) => { + const mappedRoutes: [RouteData, string][] = routes.map((route) => [ + route, + `/.netlify/static/`, + ]); + const routesToDynamicTargetMap = new Map(Array.from(mappedRoutes)); + await createRedirects(_config, routesToDynamicTargetMap, dir); + }, + }, + }; } diff --git a/packages/integrations/netlify/src/middleware.ts b/packages/integrations/netlify/src/middleware.ts index 3c2f4f697..c07dd04fa 100644 --- a/packages/integrations/netlify/src/middleware.ts +++ b/packages/integrations/netlify/src/middleware.ts @@ -1,55 +1,68 @@ -import { existsSync } from 'node:fs'; -import { join } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { ASTRO_LOCALS_HEADER } from './integration-functions.js'; -import { DENO_SHIM } from './shared.js'; +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { ASTRO_LOCALS_HEADER } from "./integration-functions.js"; +import { DENO_SHIM } from "./shared.js"; /** * It generates a Netlify edge function. * */ export async function generateEdgeMiddleware( - astroMiddlewareEntryPointPath: URL, - outPath: string, - netlifyEdgeMiddlewareHandlerPath: URL + astroMiddlewareEntryPointPath: URL, + outPath: string, + netlifyEdgeMiddlewareHandlerPath: URL, ): Promise { - const entryPointPathURLAsString = JSON.stringify( - fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/') - ); + const entryPointPathURLAsString = JSON.stringify( + fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, "/"), + ); - const code = edgeMiddlewareTemplate(entryPointPathURLAsString, netlifyEdgeMiddlewareHandlerPath); - const bundledFilePath = join(outPath, 'edgeMiddleware.js'); - const esbuild = await import('esbuild'); - await esbuild.build({ - stdin: { - contents: code, - resolveDir: process.cwd(), - }, - target: 'es2020', - platform: 'browser', - outfile: bundledFilePath, - allowOverwrite: true, - format: 'esm', - bundle: true, - minify: false, - banner: { - js: DENO_SHIM, - }, - }); - return pathToFileURL(bundledFilePath); + const code = edgeMiddlewareTemplate( + entryPointPathURLAsString, + netlifyEdgeMiddlewareHandlerPath, + ); + const bundledFilePath = join(outPath, "edgeMiddleware.js"); + const esbuild = await import("esbuild"); + await esbuild.build({ + stdin: { + contents: code, + resolveDir: process.cwd(), + }, + target: "es2020", + platform: "browser", + outfile: bundledFilePath, + allowOverwrite: true, + format: "esm", + bundle: true, + minify: false, + banner: { + js: DENO_SHIM, + }, + }); + return pathToFileURL(bundledFilePath); } -function edgeMiddlewareTemplate(middlewarePath: string, netlifyEdgeMiddlewareHandlerPath: URL) { - const filePathEdgeMiddleware = fileURLToPath(netlifyEdgeMiddlewareHandlerPath); - let handlerTemplateImport = ''; - let handlerTemplateCall = '{}'; - if (existsSync(filePathEdgeMiddleware + '.js') || existsSync(filePathEdgeMiddleware + '.ts')) { - const stringified = JSON.stringify(filePathEdgeMiddleware.replace(/\\/g, '/')); - handlerTemplateImport = `import handler from ${stringified}`; - handlerTemplateCall = `handler({ request, context })`; - } else { - } - return ` +function edgeMiddlewareTemplate( + middlewarePath: string, + netlifyEdgeMiddlewareHandlerPath: URL, +) { + const filePathEdgeMiddleware = fileURLToPath( + netlifyEdgeMiddlewareHandlerPath, + ); + let handlerTemplateImport = ""; + let handlerTemplateCall = "{}"; + if ( + existsSync(filePathEdgeMiddleware + ".js") || + existsSync(filePathEdgeMiddleware + ".ts") + ) { + const stringified = JSON.stringify( + filePathEdgeMiddleware.replace(/\\/g, "/"), + ); + handlerTemplateImport = `import handler from ${stringified}`; + handlerTemplateCall = `handler({ request, context })`; + } else { + } + return ` ${handlerTemplateImport} import { onRequest } from ${middlewarePath}; import { createContext, trySerializeLocals } from 'astro/middleware'; @@ -61,7 +74,9 @@ export default async function middleware(request, context) { }); ctx.locals = ${handlerTemplateCall}; const next = async () => { - request.headers.set(${JSON.stringify(ASTRO_LOCALS_HEADER)}, trySerializeLocals(ctx.locals)); + request.headers.set(${JSON.stringify( + ASTRO_LOCALS_HEADER, + )}, trySerializeLocals(ctx.locals)); return await context.next(); }; diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts index 8c051d9f6..54a9ea7d3 100644 --- a/packages/integrations/netlify/src/netlify-functions.ts +++ b/packages/integrations/netlify/src/netlify-functions.ts @@ -1,144 +1,157 @@ -import { builder, type Handler } from '@netlify/functions'; -import type { SSRManifest } from 'astro'; -import { App } from 'astro/app'; -import { applyPolyfills } from 'astro/app/node'; -import { ASTRO_LOCALS_HEADER } from './integration-functions.js'; +import { builder, type Handler } from "@netlify/functions"; +import type { SSRManifest } from "astro"; +import { App } from "astro/app"; +import { applyPolyfills } from "astro/app/node"; +import { ASTRO_LOCALS_HEADER } from "./integration-functions.js"; applyPolyfills(); export interface Args { - builders?: boolean; - binaryMediaTypes?: string[]; - edgeMiddleware: boolean; - functionPerRoute: boolean; + builders?: boolean; + binaryMediaTypes?: string[]; + edgeMiddleware: boolean; + functionPerRoute: boolean; } function parseContentType(header?: string) { - return header?.split(';')[0] ?? ''; + return header?.split(";")[0] ?? ""; } -const clientAddressSymbol = Symbol.for('astro.clientAddress'); +const clientAddressSymbol = Symbol.for("astro.clientAddress"); export const createExports = (manifest: SSRManifest, args: Args) => { - const app = new App(manifest); - - const builders = args.builders ?? false; - const binaryMediaTypes = args.binaryMediaTypes ?? []; - const knownBinaryMediaTypes = new Set([ - 'audio/3gpp', - 'audio/3gpp2', - 'audio/aac', - 'audio/midi', - 'audio/mpeg', - 'audio/ogg', - 'audio/opus', - 'audio/wav', - 'audio/webm', - 'audio/x-midi', - 'image/avif', - 'image/bmp', - 'image/gif', - 'image/vnd.microsoft.icon', - 'image/heif', - 'image/jpeg', - 'image/png', - 'image/svg+xml', - 'image/tiff', - 'image/webp', - 'video/3gpp', - 'video/3gpp2', - 'video/mp2t', - 'video/mp4', - 'video/mpeg', - 'video/ogg', - 'video/x-msvideo', - 'video/webm', - ...binaryMediaTypes, - ]); - - const myHandler: Handler = async (event) => { - const { httpMethod, headers, rawUrl, body: requestBody, isBase64Encoded } = event; - const init: RequestInit = { - method: httpMethod, - headers: new Headers(headers as any), - }; - // Attach the event body the request, with proper encoding. - if (httpMethod !== 'GET' && httpMethod !== 'HEAD') { - const encoding = isBase64Encoded ? 'base64' : 'utf-8'; - init.body = - typeof requestBody === 'string' ? Buffer.from(requestBody, encoding) : requestBody; - } - - const request = new Request(rawUrl, init); - - const routeData = app.match(request); - const ip = headers['x-nf-client-connection-ip']; - Reflect.set(request, clientAddressSymbol, ip); - - let locals: Record = {}; - - if (request.headers.has(ASTRO_LOCALS_HEADER)) { - let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER); - if (localsAsString) { - locals = JSON.parse(localsAsString); - } - } - - let responseTtl = undefined; - - locals.runtime = builders - ? { - setBuildersTtl(ttl: number) { - responseTtl = ttl; - }, - } - : {}; - - const response: Response = await app.render(request, routeData, locals); - const responseHeaders = Object.fromEntries(response.headers.entries()); - - const responseContentType = parseContentType(responseHeaders['content-type']); - const responseIsBase64Encoded = knownBinaryMediaTypes.has(responseContentType); - - let responseBody: string; - if (responseIsBase64Encoded) { - const ab = await response.arrayBuffer(); - responseBody = Buffer.from(ab).toString('base64'); - } else { - responseBody = await response.text(); - } - - const fnResponse: any = { - statusCode: response.status, - headers: responseHeaders, - body: responseBody, - isBase64Encoded: responseIsBase64Encoded, - ttl: responseTtl, - }; - - const cookies = response.headers.get('set-cookie'); - if (cookies) { - fnResponse.multiValueHeaders = { - 'set-cookie': Array.isArray(cookies) ? cookies : splitCookiesString(cookies), - }; - } - - // 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; - }; - - const handler = builders ? builder(myHandler) : myHandler; - - return { handler }; + const app = new App(manifest); + + const builders = args.builders ?? false; + const binaryMediaTypes = args.binaryMediaTypes ?? []; + const knownBinaryMediaTypes = new Set([ + "audio/3gpp", + "audio/3gpp2", + "audio/aac", + "audio/midi", + "audio/mpeg", + "audio/ogg", + "audio/opus", + "audio/wav", + "audio/webm", + "audio/x-midi", + "image/avif", + "image/bmp", + "image/gif", + "image/vnd.microsoft.icon", + "image/heif", + "image/jpeg", + "image/png", + "image/svg+xml", + "image/tiff", + "image/webp", + "video/3gpp", + "video/3gpp2", + "video/mp2t", + "video/mp4", + "video/mpeg", + "video/ogg", + "video/x-msvideo", + "video/webm", + ...binaryMediaTypes, + ]); + + const myHandler: Handler = async (event) => { + const { + httpMethod, + headers, + rawUrl, + body: requestBody, + isBase64Encoded, + } = event; + const init: RequestInit = { + method: httpMethod, + headers: new Headers(headers as any), + }; + // Attach the event body the request, with proper encoding. + if (httpMethod !== "GET" && httpMethod !== "HEAD") { + const encoding = isBase64Encoded ? "base64" : "utf-8"; + init.body = + typeof requestBody === "string" + ? Buffer.from(requestBody, encoding) + : requestBody; + } + + const request = new Request(rawUrl, init); + + const routeData = app.match(request); + const ip = headers["x-nf-client-connection-ip"]; + Reflect.set(request, clientAddressSymbol, ip); + + let locals: Record = {}; + + if (request.headers.has(ASTRO_LOCALS_HEADER)) { + let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER); + if (localsAsString) { + locals = JSON.parse(localsAsString); + } + } + + let responseTtl = undefined; + + locals.runtime = builders + ? { + setBuildersTtl(ttl: number) { + responseTtl = ttl; + }, + } + : {}; + + const response: Response = await app.render(request, routeData, locals); + const responseHeaders = Object.fromEntries(response.headers.entries()); + + const responseContentType = parseContentType( + responseHeaders["content-type"], + ); + const responseIsBase64Encoded = + knownBinaryMediaTypes.has(responseContentType); + + let responseBody: string; + if (responseIsBase64Encoded) { + const ab = await response.arrayBuffer(); + responseBody = Buffer.from(ab).toString("base64"); + } else { + responseBody = await response.text(); + } + + const fnResponse: any = { + statusCode: response.status, + headers: responseHeaders, + body: responseBody, + isBase64Encoded: responseIsBase64Encoded, + ttl: responseTtl, + }; + + const cookies = response.headers.get("set-cookie"); + if (cookies) { + fnResponse.multiValueHeaders = { + "set-cookie": Array.isArray(cookies) + ? cookies + : splitCookiesString(cookies), + }; + } + + // 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; + }; + + const handler = builders ? builder(myHandler) : myHandler; + + return { handler }; }; /* @@ -152,74 +165,74 @@ export const createExports = (manifest: SSRManifest, args: Args) => { Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation */ function splitCookiesString(cookiesString: string): string[] { - if (Array.isArray(cookiesString)) { - return cookiesString; - } - if (typeof cookiesString !== 'string') { - return []; - } - - let cookiesStrings = []; - let pos = 0; - let start; - let ch; - let lastComma; - let nextStart; - let cookiesSeparatorFound; - - function skipWhitespace() { - while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) { - pos += 1; - } - return pos < cookiesString.length; - } - - function notSpecialChar() { - ch = cookiesString.charAt(pos); - - return ch !== '=' && ch !== ';' && ch !== ','; - } - - while (pos < cookiesString.length) { - start = pos; - cookiesSeparatorFound = false; - - while (skipWhitespace()) { - ch = cookiesString.charAt(pos); - if (ch === ',') { - // ',' is a cookie separator if we have later first '=', not ';' or ',' - lastComma = pos; - pos += 1; - - skipWhitespace(); - nextStart = pos; - - while (pos < cookiesString.length && notSpecialChar()) { - pos += 1; - } - - // currently special character - if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') { - // we found cookies separator - cookiesSeparatorFound = true; - // pos is inside the next cookie, so back up and return it. - pos = nextStart; - cookiesStrings.push(cookiesString.substring(start, lastComma)); - start = pos; - } else { - // in param ',' or param separator ';', - // we continue from that comma - pos = lastComma + 1; - } - } else { - pos += 1; - } - } - - if (!cookiesSeparatorFound || pos >= cookiesString.length) { - cookiesStrings.push(cookiesString.substring(start, cookiesString.length)); - } - } - - return cookiesStrings; + if (Array.isArray(cookiesString)) { + return cookiesString; + } + if (typeof cookiesString !== "string") { + return []; + } + + let cookiesStrings = []; + let pos = 0; + let start; + let ch; + let lastComma; + let nextStart; + let cookiesSeparatorFound; + + function skipWhitespace() { + while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) { + pos += 1; + } + return pos < cookiesString.length; + } + + function notSpecialChar() { + ch = cookiesString.charAt(pos); + + return ch !== "=" && ch !== ";" && ch !== ","; + } + + while (pos < cookiesString.length) { + start = pos; + cookiesSeparatorFound = false; + + while (skipWhitespace()) { + ch = cookiesString.charAt(pos); + if (ch === ",") { + // ',' is a cookie separator if we have later first '=', not ';' or ',' + lastComma = pos; + pos += 1; + + skipWhitespace(); + nextStart = pos; + + while (pos < cookiesString.length && notSpecialChar()) { + pos += 1; + } + + // currently special character + if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") { + // we found cookies separator + cookiesSeparatorFound = true; + // pos is inside the next cookie, so back up and return it. + pos = nextStart; + cookiesStrings.push(cookiesString.substring(start, lastComma)); + start = pos; + } else { + // in param ',' or param separator ';', + // we continue from that comma + pos = lastComma + 1; + } + } else { + pos += 1; + } + } + + if (!cookiesSeparatorFound || pos >= cookiesString.length) { + cookiesStrings.push(cookiesString.substring(start, cookiesString.length)); + } + } + + return cookiesStrings; } diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts index 175b9d04f..4003f812f 100644 --- a/packages/integrations/netlify/src/shared.ts +++ b/packages/integrations/netlify/src/shared.ts @@ -1,9 +1,9 @@ -import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; -import type { AstroConfig, RouteData } from 'astro'; -import esbuild from 'esbuild'; -import fs from 'node:fs'; -import npath from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { createRedirectsFromAstroRoutes } from "@astrojs/underscore-redirects"; +import type { AstroConfig, RouteData } from "astro"; +import esbuild from "esbuild"; +import fs from "node:fs"; +import npath from "node:path"; +import { fileURLToPath } from "node:url"; export const DENO_SHIM = `globalThis.process = { argv: [], @@ -11,104 +11,113 @@ export const DENO_SHIM = `globalThis.process = { };`; export interface NetlifyEdgeFunctionsOptions { - dist?: URL; + dist?: URL; } export interface NetlifyEdgeFunctionManifestFunctionPath { - function: string; - path: string; + function: string; + path: string; } export interface NetlifyEdgeFunctionManifestFunctionPattern { - function: string; - pattern: string; + function: string; + pattern: string; } export type NetlifyEdgeFunctionManifestFunction = - | NetlifyEdgeFunctionManifestFunctionPath - | NetlifyEdgeFunctionManifestFunctionPattern; + | NetlifyEdgeFunctionManifestFunctionPath + | NetlifyEdgeFunctionManifestFunctionPattern; export interface NetlifyEdgeFunctionManifest { - functions: NetlifyEdgeFunctionManifestFunction[]; - version: 1; + functions: NetlifyEdgeFunctionManifestFunction[]; + version: 1; } export async function createRedirects( - config: AstroConfig, - routeToDynamicTargetMap: Map, - dir: URL + config: AstroConfig, + routeToDynamicTargetMap: Map, + dir: URL, ) { - const _redirectsURL = new URL('./_redirects', dir); + const _redirectsURL = new URL("./_redirects", dir); - const _redirects = createRedirectsFromAstroRoutes({ - config, - routeToDynamicTargetMap, - dir, - }); - const content = _redirects.print(); + const _redirects = createRedirectsFromAstroRoutes({ + config, + routeToDynamicTargetMap, + dir, + }); + const content = _redirects.print(); - // Always use appendFile() because the redirects file could already exist, - // e.g. due to a `/public/_redirects` file that got copied to the output dir. - // If the file does not exist yet, appendFile() automatically creates it. - await fs.promises.appendFile(_redirectsURL, content, 'utf-8'); + // Always use appendFile() because the redirects file could already exist, + // e.g. due to a `/public/_redirects` file that got copied to the output dir. + // If the file does not exist yet, appendFile() automatically creates it. + await fs.promises.appendFile(_redirectsURL, content, "utf-8"); } -export async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: URL) { - const functions: NetlifyEdgeFunctionManifestFunction[] = []; - for (const route of routes) { - if (route.pathname) { - functions.push({ - function: entryFile, - path: route.pathname, - }); - } else { - functions.push({ - function: entryFile, - // Make route pattern serializable to match expected - // Netlify Edge validation format. Mirrors Netlify's own edge bundler: - // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34 - pattern: route.pattern.source.replace(/\\\//g, '/').toString(), - }); - } - } +export async function createEdgeManifest( + routes: RouteData[], + entryFile: string, + dir: URL, +) { + const functions: NetlifyEdgeFunctionManifestFunction[] = []; + for (const route of routes) { + if (route.pathname) { + functions.push({ + function: entryFile, + path: route.pathname, + }); + } else { + functions.push({ + function: entryFile, + // Make route pattern serializable to match expected + // Netlify Edge validation format. Mirrors Netlify's own edge bundler: + // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34 + pattern: route.pattern.source.replace(/\\\//g, "/").toString(), + }); + } + } - const manifest: NetlifyEdgeFunctionManifest = { - functions, - version: 1, - }; + const manifest: NetlifyEdgeFunctionManifest = { + functions, + version: 1, + }; - const baseDir = new URL('./.netlify/edge-functions/', dir); - await fs.promises.mkdir(baseDir, { recursive: true }); + const baseDir = new URL("./.netlify/edge-functions/", dir); + await fs.promises.mkdir(baseDir, { recursive: true }); - const manifestURL = new URL('./manifest.json', baseDir); - const _manifest = JSON.stringify(manifest, null, ' '); - await fs.promises.writeFile(manifestURL, _manifest, 'utf-8'); + const manifestURL = new URL("./manifest.json", baseDir); + const _manifest = JSON.stringify(manifest, null, " "); + await fs.promises.writeFile(manifestURL, _manifest, "utf-8"); } -export async function bundleServerEntry(entryUrl: URL, serverUrl?: URL, vite?: any | undefined) { - const pth = fileURLToPath(entryUrl); - await esbuild.build({ - target: 'es2020', - platform: 'browser', - entryPoints: [pth], - outfile: pth, - allowOverwrite: true, - format: 'esm', - bundle: true, - external: ['@astrojs/markdown-remark', 'astro/middleware'], - banner: { - js: DENO_SHIM, - }, - }); +export async function bundleServerEntry( + entryUrl: URL, + serverUrl?: URL, + vite?: any | undefined, +) { + const pth = fileURLToPath(entryUrl); + await esbuild.build({ + target: "es2020", + platform: "browser", + entryPoints: [pth], + outfile: pth, + allowOverwrite: true, + format: "esm", + bundle: true, + external: ["@astrojs/markdown-remark", "astro/middleware"], + banner: { + js: DENO_SHIM, + }, + }); - // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash. - if (vite && serverUrl) { - try { - const chunkFileNames = - vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`; - const chunkPath = npath.dirname(chunkFileNames); - const chunksDirUrl = new URL(chunkPath + '/', serverUrl); - await fs.promises.rm(chunksDirUrl, { recursive: true, force: true }); - } catch {} - } + // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash. + if (vite && serverUrl) { + try { + const chunkFileNames = + vite?.build?.rollupOptions?.output?.chunkFileNames ?? + `chunks/chunk.[hash].mjs`; + const chunkPath = npath.dirname(chunkFileNames); + const chunksDirUrl = new URL(chunkPath + "/", serverUrl); + await fs.promises.rm(chunksDirUrl, { recursive: true, force: true }); + } catch {} + } } diff --git a/packages/integrations/netlify/test/functions/404.test.js b/packages/integrations/netlify/test/functions/404.test.js index 1782507db..a1f792ce0 100644 --- a/packages/integrations/netlify/test/functions/404.test.js +++ b/packages/integrations/netlify/test/functions/404.test.js @@ -1,19 +1,21 @@ -import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; +import { expect } from "chai"; +import fs from "fs/promises"; +import { cli } from "./test-utils.js"; +import { fileURLToPath } from "url"; -const root = new URL('./fixtures/404/', import.meta.url).toString(); +const root = new URL("./fixtures/404/", import.meta.url).toString(); -describe('404 page', () => { +describe("404 page", () => { + before(async () => { + await cli("build", "--root", fileURLToPath(root)); + }); - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); - - it('404 route is included in the redirect file', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - const expr = new RegExp('/* /.netlify/functions/entry 404'); - expect(redir).to.match(expr); - }); + it("404 route is included in the redirect file", async () => { + const redir = await fs.readFile( + new URL("./dist/_redirects", root), + "utf-8", + ); + const expr = new RegExp("/* /.netlify/functions/entry 404"); + expect(redir).to.match(expr); + }); }); diff --git a/packages/integrations/netlify/test/functions/base64-response.test.js b/packages/integrations/netlify/test/functions/base64-response.test.js index 520eaf646..07c1bbafe 100644 --- a/packages/integrations/netlify/test/functions/base64-response.test.js +++ b/packages/integrations/netlify/test/functions/base64-response.test.js @@ -1,52 +1,55 @@ -import { expect } from 'chai'; -import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; +import { expect } from "chai"; +import { cli } from "./test-utils.js"; +import { fileURLToPath } from "url"; -const root = new URL('./fixtures/base64-response/', import.meta.url).toString(); +const root = new URL("./fixtures/base64-response/", import.meta.url).toString(); -describe('Base64 Responses', () => { +describe("Base64 Responses", () => { + before(async () => { + await cli("build", "--root", fileURLToPath(root)); + }); - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); + it("Can return base64 encoded strings", async () => { + const entryURL = new URL( + "./fixtures/base64-response/.netlify/functions-internal/entry.mjs", + import.meta.url, + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: "GET", + headers: {}, + rawUrl: "http://example.com/image", + body: "{}", + isBase64Encoded: false, + }); + expect(resp.statusCode, "successful response").to.equal(200); + expect(resp.isBase64Encoded, "includes isBase64Encoded flag").to.be.true; - it('Can return base64 encoded strings', async () => { - const entryURL = new URL( - './fixtures/base64-response/.netlify/functions-internal/entry.mjs', - import.meta.url - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: 'http://example.com/image', - body: '{}', - isBase64Encoded: false, - }); - expect(resp.statusCode, 'successful response').to.equal(200); - expect(resp.isBase64Encoded, 'includes isBase64Encoded flag').to.be.true; + const buffer = Buffer.from(resp.body, "base64"); + expect(buffer.toString(), "decoded base64 string matches").to.equal( + "base64 test string", + ); + }); - const buffer = Buffer.from(resp.body, 'base64'); - expect(buffer.toString(), 'decoded base64 string matches').to.equal('base64 test string'); - }); + it("Can define custom binaryMediaTypes", async () => { + const entryURL = new URL( + "./fixtures/base64-response/.netlify/functions-internal/entry.mjs", + import.meta.url, + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: "GET", + headers: {}, + rawUrl: "http://example.com/font", + body: "{}", + isBase64Encoded: false, + }); + expect(resp.statusCode, "successful response").to.equal(200); + expect(resp.isBase64Encoded, "includes isBase64Encoded flag").to.be.true; - it('Can define custom binaryMediaTypes', async () => { - const entryURL = new URL( - './fixtures/base64-response/.netlify/functions-internal/entry.mjs', - import.meta.url - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: 'http://example.com/font', - body: '{}', - isBase64Encoded: false, - }); - expect(resp.statusCode, 'successful response').to.equal(200); - expect(resp.isBase64Encoded, 'includes isBase64Encoded flag').to.be.true; - - const buffer = Buffer.from(resp.body, 'base64'); - expect(buffer.toString(), 'decoded base64 string matches').to.equal('base64 test font'); - }); + const buffer = Buffer.from(resp.body, "base64"); + expect(buffer.toString(), "decoded base64 string matches").to.equal( + "base64 test font", + ); + }); }); diff --git a/packages/integrations/netlify/test/functions/builders.test.js b/packages/integrations/netlify/test/functions/builders.test.js index e927527a1..281c4f3da 100644 --- a/packages/integrations/netlify/test/functions/builders.test.js +++ b/packages/integrations/netlify/test/functions/builders.test.js @@ -1,26 +1,26 @@ -import { expect } from 'chai'; -import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; +import { expect } from "chai"; +import { cli } from "./test-utils.js"; +import { fileURLToPath } from "url"; -const root = new URL('./fixtures/builders/', import.meta.url).toString(); +const root = new URL("./fixtures/builders/", import.meta.url).toString(); -describe('Builders', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); +describe("Builders", () => { + before(async () => { + await cli("build", "--root", fileURLToPath(root)); + }); - it('A route can set builders ttl', async () => { - const entryURL = new URL( - './fixtures/builders/.netlify/functions-internal/entry.mjs', - import.meta.url - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: 'http://example.com/', - isBase64Encoded: false, - }); - expect(resp.ttl).to.equal(45); - }); + it("A route can set builders ttl", async () => { + const entryURL = new URL( + "./fixtures/builders/.netlify/functions-internal/entry.mjs", + import.meta.url, + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: "GET", + headers: {}, + rawUrl: "http://example.com/", + isBase64Encoded: false, + }); + expect(resp.ttl).to.equal(45); + }); }); diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index 328294d10..05727207e 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,31 +1,31 @@ -import { expect } from 'chai'; -import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; +import { expect } from "chai"; +import { cli } from "./test-utils.js"; +import { fileURLToPath } from "url"; -const root = new URL('./fixtures/cookies/', import.meta.url).toString(); +const root = new URL("./fixtures/cookies/", import.meta.url).toString(); -describe('Cookies', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); +describe("Cookies", () => { + before(async () => { + await cli("build", "--root", fileURLToPath(root)); + }); - it('Can set multiple', async () => { - const entryURL = new URL( - './fixtures/cookies/.netlify/functions-internal/entry.mjs', - import.meta.url - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'POST', - headers: {}, - rawUrl: 'http://example.com/login', - body: '{}', - isBase64Encoded: false, - }); - expect(resp.statusCode).to.equal(301); - expect(resp.headers.location).to.equal('/'); - expect(resp.multiValueHeaders).to.be.deep.equal({ - 'set-cookie': ['foo=foo; HttpOnly', 'bar=bar; HttpOnly'], - }); - }); + it("Can set multiple", async () => { + const entryURL = new URL( + "./fixtures/cookies/.netlify/functions-internal/entry.mjs", + import.meta.url, + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: "POST", + headers: {}, + rawUrl: "http://example.com/login", + body: "{}", + isBase64Encoded: false, + }); + expect(resp.statusCode).to.equal(301); + expect(resp.headers.location).to.equal("/"); + expect(resp.multiValueHeaders).to.be.deep.equal({ + "set-cookie": ["foo=foo; HttpOnly", "bar=bar; HttpOnly"], + }); + }); }); diff --git a/packages/integrations/netlify/test/functions/dynamic-route.test.js b/packages/integrations/netlify/test/functions/dynamic-route.test.js index 2e20454e6..69d25a11e 100644 --- a/packages/integrations/netlify/test/functions/dynamic-route.test.js +++ b/packages/integrations/netlify/test/functions/dynamic-route.test.js @@ -1,24 +1,36 @@ -import { expect } from 'chai'; -import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; -import fs from 'fs/promises'; +import { expect } from "chai"; +import { cli } from "./test-utils.js"; +import { fileURLToPath } from "url"; +import fs from "fs/promises"; -const root = new URL('./fixtures/dynamic-route/', import.meta.url).toString(); +const root = new URL("./fixtures/dynamic-route/", import.meta.url).toString(); -describe('Dynamic pages', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); +describe("Dynamic pages", () => { + before(async () => { + await cli("build", "--root", fileURLToPath(root)); + }); - it('Dynamic pages are included in the redirects file', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - expect(redir).to.match(/\/products\/:id/); - }); + it("Dynamic pages are included in the redirects file", async () => { + const redir = await fs.readFile( + new URL("./dist/_redirects", root), + "utf-8", + ); + expect(redir).to.match(/\/products\/:id/); + }); - it('Prerendered routes are also included using placeholder syntax', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - expect(redir).to.include('/pets/:cat /pets/:cat/index.html 200'); - expect(redir).to.include('/pets/:dog /pets/:dog/index.html 200'); - expect(redir).to.include('/pets /.netlify/functions/entry 200'); - }); + it("Prerendered routes are also included using placeholder syntax", async () => { + const redir = await fs.readFile( + new URL("./dist/_redirects", root), + "utf-8", + ); + expect(redir).to.include( + "/pets/:cat /pets/:cat/index.html 200", + ); + expect(redir).to.include( + "/pets/:dog /pets/:dog/index.html 200", + ); + expect(redir).to.include( + "/pets /.netlify/functions/entry 200", + ); + }); }); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 7e51b20da..90bcd69c7 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,20 +1,32 @@ -import { fileURLToPath } from 'url'; -import { cli } from './test-utils.js'; -import fs from 'fs/promises'; -import { expect } from 'chai'; +import { fileURLToPath } from "url"; +import { cli } from "./test-utils.js"; +import fs from "fs/promises"; +import { expect } from "chai"; -describe('Middleware', () => { - it('with edge handle file, should successfully build the middleware', async () => { - const root = new URL('./fixtures/middleware-with-handler-file/', import.meta.url).toString(); - await cli('build', '--root', fileURLToPath(root)); - const contents = await fs.readFile(new URL('./.netlify/edge-functions/edgeMiddleware.js', root), 'utf-8'); - expect(contents.includes('"Hello world"')).to.be.true; - }); +describe("Middleware", () => { + it("with edge handle file, should successfully build the middleware", async () => { + const root = new URL( + "./fixtures/middleware-with-handler-file/", + import.meta.url, + ).toString(); + await cli("build", "--root", fileURLToPath(root)); + const contents = await fs.readFile( + new URL("./.netlify/edge-functions/edgeMiddleware.js", root), + "utf-8", + ); + expect(contents.includes('"Hello world"')).to.be.true; + }); - it('without edge handle file, should successfully build the middleware', async () => { - const root = new URL('./fixtures/middleware-without-handler-file/', import.meta.url).toString(); - await cli('build', '--root', fileURLToPath(root)); - const contents = await fs.readFile(new URL('./.netlify/edge-functions/edgeMiddleware.js', root), 'utf-8'); - expect(contents.includes('"Hello world"')).to.be.false; - }); + it("without edge handle file, should successfully build the middleware", async () => { + const root = new URL( + "./fixtures/middleware-without-handler-file/", + import.meta.url, + ).toString(); + await cli("build", "--root", fileURLToPath(root)); + const contents = await fs.readFile( + new URL("./.netlify/edge-functions/edgeMiddleware.js", root), + "utf-8", + ); + expect(contents.includes('"Hello world"')).to.be.false; + }); }); diff --git a/packages/integrations/netlify/test/functions/prerender.test.js b/packages/integrations/netlify/test/functions/prerender.test.js index 8acc5a519..61af252cb 100644 --- a/packages/integrations/netlify/test/functions/prerender.test.js +++ b/packages/integrations/netlify/test/functions/prerender.test.js @@ -1,53 +1,73 @@ -import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; - -const root = new URL('./fixtures/prerender/', import.meta.url).toString(); - -describe('Mixed Prerendering with SSR', () => { - before(async () => { - process.env.PRERENDER = true; - await cli('build', '--root', fileURLToPath(root)); - }); - - after(() => { - delete process.env.PRERENDER; - }); - - it('Wildcard 404 is sorted last', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - const baseRouteIndex = redir.indexOf('/ /.netlify/functions/entry 200'); - const oneRouteIndex = redir.indexOf('/one /one/index.html 200'); - const fourOhFourWildCardIndex = redir.indexOf('/* /.netlify/functions/entry 404'); - - expect(oneRouteIndex).to.not.be.equal(-1); - expect(fourOhFourWildCardIndex).to.be.greaterThan(baseRouteIndex); - expect(fourOhFourWildCardIndex).to.be.greaterThan(oneRouteIndex); - }); +import { expect } from "chai"; +import fs from "fs/promises"; +import { cli } from "./test-utils.js"; +import { fileURLToPath } from "url"; + +const root = new URL("./fixtures/prerender/", import.meta.url).toString(); + +describe("Mixed Prerendering with SSR", () => { + before(async () => { + process.env.PRERENDER = true; + await cli("build", "--root", fileURLToPath(root)); + }); + + after(() => { + delete process.env.PRERENDER; + }); + + it("Wildcard 404 is sorted last", async () => { + const redir = await fs.readFile( + new URL("./dist/_redirects", root), + "utf-8", + ); + const baseRouteIndex = redir.indexOf( + "/ /.netlify/functions/entry 200", + ); + const oneRouteIndex = redir.indexOf( + "/one /one/index.html 200", + ); + const fourOhFourWildCardIndex = redir.indexOf( + "/* /.netlify/functions/entry 404", + ); + + expect(oneRouteIndex).to.not.be.equal(-1); + expect(fourOhFourWildCardIndex).to.be.greaterThan(baseRouteIndex); + expect(fourOhFourWildCardIndex).to.be.greaterThan(oneRouteIndex); + }); }); -describe('Mixed Hybrid rendering with SSR', () => { - before(async () => { - process.env.PRERENDER = false; - process.env.ASTRO_OUTPUT = 'hybrid'; - await cli('build', '--root', fileURLToPath(root)); - }); - - after(() => { - delete process.env.PRERENDER; - }); - - it('outputs a correct redirect file', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - const baseRouteIndex = redir.indexOf('/one /.netlify/functions/entry 200'); - const rootRouteIndex = redir.indexOf('/ /index.html 200'); - const fourOhFourIndex = redir.indexOf('/404 /404.html 200'); - const imageEndpoint = redir.indexOf('/_image /.netlify/functions/entry 200'); - - expect(rootRouteIndex).to.not.be.equal(-1); - expect(baseRouteIndex).to.not.be.equal(-1); - expect(fourOhFourIndex).to.not.be.equal(-1); - expect(imageEndpoint).to.not.be.equal(-1); - }); +describe("Mixed Hybrid rendering with SSR", () => { + before(async () => { + process.env.PRERENDER = false; + process.env.ASTRO_OUTPUT = "hybrid"; + await cli("build", "--root", fileURLToPath(root)); + }); + + after(() => { + delete process.env.PRERENDER; + }); + + it("outputs a correct redirect file", async () => { + const redir = await fs.readFile( + new URL("./dist/_redirects", root), + "utf-8", + ); + const baseRouteIndex = redir.indexOf( + "/one /.netlify/functions/entry 200", + ); + const rootRouteIndex = redir.indexOf( + "/ /index.html 200", + ); + const fourOhFourIndex = redir.indexOf( + "/404 /404.html 200", + ); + const imageEndpoint = redir.indexOf( + "/_image /.netlify/functions/entry 200", + ); + + expect(rootRouteIndex).to.not.be.equal(-1); + expect(baseRouteIndex).to.not.be.equal(-1); + expect(fourOhFourIndex).to.not.be.equal(-1); + expect(imageEndpoint).to.not.be.equal(-1); + }); }); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index 855378ad4..43fc0cd5d 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,50 +1,56 @@ -import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; +import { expect } from "chai"; +import fs from "fs/promises"; +import { cli } from "./test-utils.js"; +import { fileURLToPath } from "url"; -const root = new URL('../functions/fixtures/redirects/', import.meta.url).toString(); +const root = new URL( + "../functions/fixtures/redirects/", + import.meta.url, +).toString(); -describe('SSG - Redirects', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); +describe("SSG - Redirects", () => { + before(async () => { + await cli("build", "--root", fileURLToPath(root)); + }); - it('Creates a redirects file', async () => { - let redirects = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - let parts = redirects.split(/\s+/); - expect(parts).to.deep.equal([ - '/other', - '/', - '301', - // This uses the dynamic Astro.redirect, so we don't know that it's a redirect - // until runtime. This is correct! - '/nope', - '/.netlify/functions/entry', - '200', - '/', - '/.netlify/functions/entry', - '200', + it("Creates a redirects file", async () => { + let redirects = await fs.readFile( + new URL("./dist/_redirects", root), + "utf-8", + ); + let parts = redirects.split(/\s+/); + expect(parts).to.deep.equal([ + "/other", + "/", + "301", + // This uses the dynamic Astro.redirect, so we don't know that it's a redirect + // until runtime. This is correct! + "/nope", + "/.netlify/functions/entry", + "200", + "/", + "/.netlify/functions/entry", + "200", - // Image endpoint - '/_image', - '/.netlify/functions/entry', - '200', + // Image endpoint + "/_image", + "/.netlify/functions/entry", + "200", - // A real route - '/team/articles/*', - '/.netlify/functions/entry', - '200', - ]); - expect(redirects).to.matchSnapshot(); - }); + // A real route + "/team/articles/*", + "/.netlify/functions/entry", + "200", + ]); + expect(redirects).to.matchSnapshot(); + }); - it('Does not create .html files', async () => { - try { - await fixture.readFile('/other/index.html'); - expect(false).to.equal(true, 'this file should not exist'); - } catch { - expect(true).to.equal(true); - } - }); + it("Does not create .html files", async () => { + try { + await fixture.readFile("/other/index.html"); + expect(false).to.equal(true, "this file should not exist"); + } catch { + expect(true).to.equal(true); + } + }); }); diff --git a/packages/integrations/netlify/test/functions/test-utils.js b/packages/integrations/netlify/test/functions/test-utils.js index c977af42e..46128095e 100644 --- a/packages/integrations/netlify/test/functions/test-utils.js +++ b/packages/integrations/netlify/test/functions/test-utils.js @@ -1,34 +1,34 @@ // @ts-check -import { fileURLToPath } from 'node:url'; +import { fileURLToPath } from "node:url"; -export * from '../test-utils.js'; +export * from "../test-utils.js"; /** * * @returns {import('astro').AstroIntegration} */ export function testIntegration({ setEntryPoints } = {}) { - return { - name: '@astrojs/netlify/test-integration', - hooks: { - 'astro:config:setup': ({ updateConfig }) => { - updateConfig({ - vite: { - resolve: { - alias: { - '@astrojs/netlify/netlify-functions.js': fileURLToPath( - new URL('../../dist/netlify-functions.js', import.meta.url) - ), - }, - }, - }, - }); - }, - 'astro:build:ssr': ({ entryPoints }) => { - if (entryPoints.size) { - setEntryPoints(entryPoints); - } - }, - }, - }; + return { + name: "@astrojs/netlify/test-integration", + hooks: { + "astro:config:setup": ({ updateConfig }) => { + updateConfig({ + vite: { + resolve: { + alias: { + "@astrojs/netlify/netlify-functions.js": fileURLToPath( + new URL("../../dist/netlify-functions.js", import.meta.url), + ), + }, + }, + }, + }); + }, + "astro:build:ssr": ({ entryPoints }) => { + if (entryPoints.size) { + setEntryPoints(entryPoints); + } + }, + }, + }; } diff --git a/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs b/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs index 464c03a6c..6c8117f41 100644 --- a/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs +++ b/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs @@ -1,8 +1,8 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; +import netlify from "@astrojs/netlify"; +import { defineConfig } from "astro/config"; // https://astro.build/config export default defineConfig({ - output: 'server', - adapter: netlify(), + output: "server", + adapter: netlify(), }); diff --git a/packages/integrations/netlify/test/hosted/hosted.test.js b/packages/integrations/netlify/test/hosted/hosted.test.js index 2dc8c67ce..3ec8cb3f3 100644 --- a/packages/integrations/netlify/test/hosted/hosted.test.js +++ b/packages/integrations/netlify/test/hosted/hosted.test.js @@ -1,21 +1,22 @@ -import { expect } from 'chai'; +import { expect } from "chai"; -const NETLIFY_TEST_URL = 'https://curious-boba-495d6d.netlify.app'; +const NETLIFY_TEST_URL = "https://curious-boba-495d6d.netlify.app"; -describe('Hosted Netlify Tests', () => { - it('Image endpoint works', async () => { - const image = await fetch( - NETLIFY_TEST_URL + '/_image?href=%2F_astro%2Fpenguin.e9c64733.png&w=300&f=webp' - ); +describe("Hosted Netlify Tests", () => { + it("Image endpoint works", async () => { + const image = await fetch( + NETLIFY_TEST_URL + + "/_image?href=%2F_astro%2Fpenguin.e9c64733.png&w=300&f=webp", + ); - expect(image.status).to.equal(200); - }); + expect(image.status).to.equal(200); + }); - it('Server returns fresh content', async () => { - const responseOne = await fetch(NETLIFY_TEST_URL + '/time'); + it("Server returns fresh content", async () => { + const responseOne = await fetch(NETLIFY_TEST_URL + "/time"); - const responseTwo = await fetch(NETLIFY_TEST_URL + '/time'); + const responseTwo = await fetch(NETLIFY_TEST_URL + "/time"); - expect(responseOne.body).to.not.equal(responseTwo.body); - }); + expect(responseOne.body).to.not.equal(responseTwo.body); + }); }); diff --git a/packages/integrations/netlify/test/setup.js b/packages/integrations/netlify/test/setup.js index c53aa9894..e7e74f42f 100644 --- a/packages/integrations/netlify/test/setup.js +++ b/packages/integrations/netlify/test/setup.js @@ -1,12 +1,12 @@ -import { use } from 'chai'; -import chaiJestSnapshot from 'chai-jest-snapshot'; +import { use } from "chai"; +import chaiJestSnapshot from "chai-jest-snapshot"; use(chaiJestSnapshot); before(function () { - chaiJestSnapshot.resetSnapshotRegistry(); + chaiJestSnapshot.resetSnapshotRegistry(); }); beforeEach(function () { - chaiJestSnapshot.configureUsingMochaContext(this); + chaiJestSnapshot.configureUsingMochaContext(this); }); diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index ae3ff1eb8..9eafce0f8 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,50 +1,50 @@ -import { expect } from 'chai'; -import { loadFixture, testIntegration } from './test-utils.js'; -import { netlifyStatic } from '../../dist/index.js'; +import { expect } from "chai"; +import { loadFixture, testIntegration } from "./test-utils.js"; +import { netlifyStatic } from "../../dist/index.js"; -describe('SSG - Redirects', () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ - let fixture; +describe("SSG - Redirects", () => { + /** @type {import('../../../astro/test/test-utils').Fixture} */ + let fixture; - before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/redirects/', import.meta.url).toString(), - output: 'static', - adapter: netlifyStatic(), - site: `http://example.com`, - integrations: [testIntegration()], - redirects: { - '/other': '/', - '/two': { - status: 302, - destination: '/', - }, - '/blog/[...slug]': '/team/articles/[...slug]', - }, - }); - await fixture.build(); - }); + before(async () => { + fixture = await loadFixture({ + root: new URL("./fixtures/redirects/", import.meta.url).toString(), + output: "static", + adapter: netlifyStatic(), + site: `http://example.com`, + integrations: [testIntegration()], + redirects: { + "/other": "/", + "/two": { + status: 302, + destination: "/", + }, + "/blog/[...slug]": "/team/articles/[...slug]", + }, + }); + await fixture.build(); + }); - it('Creates a redirects file', async () => { - let redirects = await fixture.readFile('/_redirects'); - let parts = redirects.split(/\s+/); - expect(parts).to.deep.equal([ - '/two', - '/', - '302', - '/other', - '/', - '301', - '/nope', - '/', - '301', + it("Creates a redirects file", async () => { + let redirects = await fixture.readFile("/_redirects"); + let parts = redirects.split(/\s+/); + expect(parts).to.deep.equal([ + "/two", + "/", + "302", + "/other", + "/", + "301", + "/nope", + "/", + "301", - '/blog/*', - '/team/articles/*/index.html', - '301', - '/team/articles/*', - '/team/articles/*/index.html', - '200', - ]); - }); + "/blog/*", + "/team/articles/*/index.html", + "301", + "/team/articles/*", + "/team/articles/*/index.html", + "200", + ]); + }); }); diff --git a/packages/integrations/netlify/test/static/test-utils.js b/packages/integrations/netlify/test/static/test-utils.js index 44fcf84e0..dfabfb002 100644 --- a/packages/integrations/netlify/test/static/test-utils.js +++ b/packages/integrations/netlify/test/static/test-utils.js @@ -1,29 +1,29 @@ // @ts-check -import { fileURLToPath } from 'node:url'; +import { fileURLToPath } from "node:url"; -export * from '../test-utils.js'; +export * from "../test-utils.js"; /** * * @returns {import('astro').AstroIntegration} */ export function testIntegration() { - return { - name: '@astrojs/netlify/test-integration', - hooks: { - 'astro:config:setup': ({ updateConfig }) => { - updateConfig({ - vite: { - resolve: { - alias: { - '@astrojs/netlify/netlify-functions.js': fileURLToPath( - new URL('../../dist/netlify-functions.js', import.meta.url) - ), - }, - }, - }, - }); - }, - }, - }; + return { + name: "@astrojs/netlify/test-integration", + hooks: { + "astro:config:setup": ({ updateConfig }) => { + updateConfig({ + vite: { + resolve: { + alias: { + "@astrojs/netlify/netlify-functions.js": fileURLToPath( + new URL("../../dist/netlify-functions.js", import.meta.url), + ), + }, + }, + }, + }); + }, + }, + }; } diff --git a/packages/integrations/netlify/test/test-utils.js b/packages/integrations/netlify/test/test-utils.js index 5ec8b49ba..6da676180 100644 --- a/packages/integrations/netlify/test/test-utils.js +++ b/packages/integrations/netlify/test/test-utils.js @@ -1,12 +1,12 @@ -import { execa } from 'execa'; +import { execa } from "execa"; /** Returns a process running the Astro CLI. */ export function cli(/** @type {string[]} */ ...args) { - const spawned = execa('npx', ['astro', ...args], { - env: { ASTRO_TELEMETRY_DISABLED: true }, - }); + const spawned = execa("npx", ["astro", ...args], { + env: { ASTRO_TELEMETRY_DISABLED: true }, + }); - spawned.stdout.setEncoding('utf8'); + spawned.stdout.setEncoding("utf8"); - return spawned; -} \ No newline at end of file + return spawned; +} diff --git a/packages/integrations/netlify/tsconfig.json b/packages/integrations/netlify/tsconfig.json index ba0d27c76..1a4df9edb 100644 --- a/packages/integrations/netlify/tsconfig.json +++ b/packages/integrations/netlify/tsconfig.json @@ -1,13 +1,8 @@ { "extends": "../../tsconfig.base.json", - "include": [ - "src" - ], + "include": ["src"], "compilerOptions": { "outDir": "./dist", - "typeRoots": [ - "node_modules/@types", - "node_modules/@netlify" - ] + "typeRoots": ["node_modules/@types", "node_modules/@netlify"] } } -- cgit v1.2.3 From 31863b80230cb61390b5cc218c603090b299f204 Mon Sep 17 00:00:00 2001 From: alexanderniebuhr Date: Mon, 16 Oct 2023 14:26:36 +0000 Subject: [ci] format --- packages/integrations/netlify/CHANGELOG.md | 54 +-- packages/integrations/netlify/README.md | 44 +-- packages/integrations/netlify/builders-types.d.ts | 14 +- packages/integrations/netlify/src/index.ts | 7 +- .../netlify/src/integration-functions.ts | 280 +++++++------- .../integrations/netlify/src/integration-static.ts | 54 +-- packages/integrations/netlify/src/middleware.ts | 101 +++-- .../integrations/netlify/src/netlify-functions.ts | 413 ++++++++++----------- packages/integrations/netlify/src/shared.ts | 171 ++++----- .../netlify/test/functions/404.test.js | 31 +- .../netlify/test/functions/base64-response.test.js | 92 +++-- .../netlify/test/functions/builders.test.js | 44 +-- .../netlify/test/functions/cookies.test.js | 54 +-- .../netlify/test/functions/dynamic-route.test.js | 50 +-- .../netlify/test/functions/edge-middleware.test.js | 52 ++- .../netlify/test/functions/prerender.test.js | 120 +++--- .../netlify/test/functions/redirects.test.js | 92 +++-- .../netlify/test/functions/split-support.test.js | 116 +++--- .../netlify/test/functions/test-utils.js | 50 +-- .../hosted/hosted-astro-project/astro.config.mjs | 8 +- .../netlify/test/hosted/hosted.test.js | 29 +- packages/integrations/netlify/test/setup.js | 8 +- .../netlify/test/static/redirects.test.js | 90 ++--- .../integrations/netlify/test/static/test-utils.js | 40 +- packages/integrations/netlify/test/test-utils.js | 12 +- 25 files changed, 957 insertions(+), 1069 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/CHANGELOG.md b/packages/integrations/netlify/CHANGELOG.md index 8d63cb181..0c09187a0 100644 --- a/packages/integrations/netlify/CHANGELOG.md +++ b/packages/integrations/netlify/CHANGELOG.md @@ -58,18 +58,18 @@ can tell Astro if it can support it. ```ts - import { AstroIntegration } from "./astro"; + import { AstroIntegration } from './astro'; function myIntegration(): AstroIntegration { return { - name: "astro-awesome-list", + name: 'astro-awesome-list', // new feature map supportedAstroFeatures: { - hybridOutput: "experimental", - staticOutput: "stable", - serverOutput: "stable", + hybridOutput: 'experimental', + staticOutput: 'stable', + serverOutput: 'stable', assets: { - supportKind: "stable", + supportKind: 'stable', isSharpCompatible: false, isSquooshCompatible: false, }, @@ -176,18 +176,18 @@ can tell Astro if it can support it. ```ts - import { AstroIntegration } from "./astro"; + import { AstroIntegration } from './astro'; function myIntegration(): AstroIntegration { return { - name: "astro-awesome-list", + name: 'astro-awesome-list', // new feature map supportedAstroFeatures: { - hybridOutput: "experimental", - staticOutput: "stable", - serverOutput: "stable", + hybridOutput: 'experimental', + staticOutput: 'stable', + serverOutput: 'stable', assets: { - supportKind: "stable", + supportKind: 'stable', isSharpCompatible: false, isSquooshCompatible: false, }, @@ -303,11 +303,11 @@ ```js // astro.config.mjs - import { defineConfig } from "astro/config"; - import netlify from "@astrojs/netlify/functions"; + import { defineConfig } from 'astro/config'; + import netlify from '@astrojs/netlify/functions'; export default defineConfig({ - output: "server", + output: 'server', adapter: netlify(), build: { split: true, @@ -558,14 +558,14 @@ The ability to customize SSR build configuration more granularly is now available in Astro. You can now customize the output folder for `server` (the server code for SSR), `client` (your client-side JavaScript and assets), and `serverEntry` (the name of the entrypoint server module). Here are the defaults: ```js - import { defineConfig } from "astro/config"; + import { defineConfig } from 'astro/config'; export default defineConfig({ - output: "server", + output: 'server', build: { - server: "./dist/server/", - client: "./dist/client/", - serverEntry: "entry.mjs", + server: './dist/server/', + client: './dist/client/', + serverEntry: 'entry.mjs', }, }); ``` @@ -579,12 +579,12 @@ ```js export default function myIntegration() { return { - name: "my-integration", + name: 'my-integration', hooks: { - "astro:config:setup": ({ updateConfig }) => { + 'astro:config:setup': ({ updateConfig }) => { updateConfig({ build: { - server: "...", + server: '...', }, }); }, @@ -629,12 +629,12 @@ ```js export function post({ cookies }) { - cookies.set("loggedIn", false); + cookies.set('loggedIn', false); return new Response(null, { status: 302, headers: { - Location: "/login", + Location: '/login', }, }); } @@ -885,8 +885,8 @@ This change adds a Netlify adapter that uses Netlify Functions. You can use it like so: ```js - import { defineConfig } from "astro/config"; - import netlify from "@astrojs/netlify/functions"; + import { defineConfig } from 'astro/config'; + import netlify from '@astrojs/netlify/functions'; export default defineConfig({ adapter: netlify(), diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md index e3257e1eb..348587a0b 100644 --- a/packages/integrations/netlify/README.md +++ b/packages/integrations/netlify/README.md @@ -86,15 +86,9 @@ In this example, `visitorCountry` and `hasEdgeMiddleware` would both be added to ```ts // src/netlify-edge-middleware.ts -import type { Context } from "https://edge.netlify.com"; - -export default function ({ - request, - context, -}: { - request: Request; - context: Context; -}) { +import type { Context } from 'https://edge.netlify.com'; + +export default function ({ request, context }: { request: Request; context: Context }) { // Return serializable data to add to Astro.locals return { visitorCountry: context.geo.country.name, @@ -118,11 +112,11 @@ The Netlify adapter builds to a single function by default. Astro 2.7 added supp ```js // astro.config.mjs -import { defineConfig } from "astro/config"; -import netlify from "@astrojs/netlify/functions"; +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify/functions'; export default defineConfig({ - output: "server", + output: 'server', adapter: netlify({ functionPerRoute: true, }), @@ -135,14 +129,14 @@ For static sites you usually don't need an adapter. However, if you use `redirec ```js // astro.config.mjs -import { defineConfig } from "astro/config"; -import netlify from "@astrojs/netlify/static"; +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify/static'; export default defineConfig({ adapter: netlify(), redirects: { - "/blog/old-post": "/blog/new-post", + '/blog/old-post': '/blog/new-post', }, }); ``` @@ -201,13 +195,13 @@ We build to the `dist` directory at the base of your project. To change this, us ```js // astro.config.mjs -import { defineConfig } from "astro/config"; -import netlify from "@astrojs/netlify/functions"; +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify/functions'; export default defineConfig({ - output: "server", + output: 'server', adapter: netlify({ - dist: new URL("./dist/", import.meta.url), + dist: new URL('./dist/', import.meta.url), }), }); ``` @@ -226,11 +220,11 @@ You can enable On-demand Builders using the `builders` option: ```js // astro.config.mjs -import { defineConfig } from "astro/config"; -import netlify from "@astrojs/netlify/functions"; +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify/functions'; export default defineConfig({ - output: "server", + output: 'server', adapter: netlify({ builders: true, }), @@ -249,16 +243,16 @@ We check for common mime types for audio, image, and video files. To include spe ```js // src/pages/image.jpg.ts -import fs from "node:fs"; +import fs from 'node:fs'; export function GET() { - const buffer = fs.readFileSync("../image.jpg"); + const buffer = fs.readFileSync('../image.jpg'); // Return the buffer directly, @astrojs/netlify will base64 encode the body return new Response(buffer, { status: 200, headers: { - "content-type": "image/jpeg", + 'content-type': 'image/jpeg', }, }); } diff --git a/packages/integrations/netlify/builders-types.d.ts b/packages/integrations/netlify/builders-types.d.ts index 95e7b0ba9..7c778be4f 100644 --- a/packages/integrations/netlify/builders-types.d.ts +++ b/packages/integrations/netlify/builders-types.d.ts @@ -1,9 +1,9 @@ interface NetlifyLocals { - runtime: { - /** - * On-demand Builders support an optional time to live (TTL) pattern that allows you to set a fixed duration of time after which a cached builder response is invalidated. This allows you to force a refresh of a builder-generated response without a new deploy. - * @param ttl time to live, in seconds - */ - setBuildersTtl(ttl: number): void; - }; + runtime: { + /** + * On-demand Builders support an optional time to live (TTL) pattern that allows you to set a fixed duration of time after which a cached builder response is invalidated. This allows you to force a refresh of a builder-generated response without a new deploy. + * @param ttl time to live, in seconds + */ + setBuildersTtl(ttl: number): void; + }; } diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index f9a974d61..a374020f9 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -1,5 +1,2 @@ -export { - netlifyFunctions as default, - netlifyFunctions, -} from "./integration-functions.js"; -export { netlifyStatic } from "./integration-static.js"; +export { netlifyFunctions as default, netlifyFunctions } from './integration-functions.js'; +export { netlifyStatic } from './integration-static.js'; diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index e4586d654..2dd095ef6 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -1,165 +1,151 @@ -import type { - AstroAdapter, - AstroConfig, - AstroIntegration, - RouteData, -} from "astro"; -import { writeFile } from "node:fs/promises"; -import { extname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { generateEdgeMiddleware } from "./middleware.js"; -import type { Args } from "./netlify-functions.js"; -import { createRedirects } from "./shared.js"; +import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; +import { writeFile } from 'node:fs/promises'; +import { extname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { generateEdgeMiddleware } from './middleware.js'; +import type { Args } from './netlify-functions.js'; +import { createRedirects } from './shared.js'; -export const NETLIFY_EDGE_MIDDLEWARE_FILE = "netlify-edge-middleware"; -export const ASTRO_LOCALS_HEADER = "x-astro-locals"; +export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware'; +export const ASTRO_LOCALS_HEADER = 'x-astro-locals'; -export function getAdapter({ - functionPerRoute, - edgeMiddleware, - ...args -}: Args): AstroAdapter { - return { - name: "@astrojs/netlify/functions", - serverEntrypoint: "@astrojs/netlify/netlify-functions.js", - exports: ["handler"], - args, - adapterFeatures: { - functionPerRoute, - edgeMiddleware, - }, - supportedAstroFeatures: { - hybridOutput: "stable", - staticOutput: "stable", - serverOutput: "stable", - assets: { - supportKind: "stable", - isSharpCompatible: true, - isSquooshCompatible: true, - }, - }, - }; +export function getAdapter({ functionPerRoute, edgeMiddleware, ...args }: Args): AstroAdapter { + return { + name: '@astrojs/netlify/functions', + serverEntrypoint: '@astrojs/netlify/netlify-functions.js', + exports: ['handler'], + args, + adapterFeatures: { + functionPerRoute, + edgeMiddleware, + }, + supportedAstroFeatures: { + hybridOutput: 'stable', + staticOutput: 'stable', + serverOutput: 'stable', + assets: { + supportKind: 'stable', + isSharpCompatible: true, + isSquooshCompatible: true, + }, + }, + }; } interface NetlifyFunctionsOptions { - dist?: URL; - builders?: boolean; - binaryMediaTypes?: string[]; - edgeMiddleware?: boolean; - functionPerRoute?: boolean; + dist?: URL; + builders?: boolean; + binaryMediaTypes?: string[]; + edgeMiddleware?: boolean; + functionPerRoute?: boolean; } function netlifyFunctions({ - dist, - builders, - binaryMediaTypes, - functionPerRoute = false, - edgeMiddleware = false, + dist, + builders, + binaryMediaTypes, + functionPerRoute = false, + edgeMiddleware = false, }: NetlifyFunctionsOptions = {}): AstroIntegration { - let _config: AstroConfig; - let _entryPoints: Map; - let ssrEntryFile: string; - let _middlewareEntryPoint: URL; - return { - name: "@astrojs/netlify", - hooks: { - "astro:config:setup": ({ config, updateConfig }) => { - const outDir = dist ?? new URL("./dist/", config.root); - updateConfig({ - outDir, - build: { - redirects: false, - client: outDir, - server: new URL("./.netlify/functions-internal/", config.root), - }, - }); - }, - "astro:build:ssr": async ({ entryPoints, middlewareEntryPoint }) => { - if (middlewareEntryPoint) { - _middlewareEntryPoint = middlewareEntryPoint; - } - _entryPoints = entryPoints; - }, - "astro:config:done": ({ config, setAdapter }) => { - setAdapter( - getAdapter({ - binaryMediaTypes, - builders, - functionPerRoute, - edgeMiddleware, - }), - ); - _config = config; - ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ""); + let _config: AstroConfig; + let _entryPoints: Map; + let ssrEntryFile: string; + let _middlewareEntryPoint: URL; + return { + name: '@astrojs/netlify', + hooks: { + 'astro:config:setup': ({ config, updateConfig }) => { + const outDir = dist ?? new URL('./dist/', config.root); + updateConfig({ + outDir, + build: { + redirects: false, + client: outDir, + server: new URL('./.netlify/functions-internal/', config.root), + }, + }); + }, + 'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => { + if (middlewareEntryPoint) { + _middlewareEntryPoint = middlewareEntryPoint; + } + _entryPoints = entryPoints; + }, + 'astro:config:done': ({ config, setAdapter }) => { + setAdapter( + getAdapter({ + binaryMediaTypes, + builders, + functionPerRoute, + edgeMiddleware, + }) + ); + _config = config; + ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ''); - if (config.output === "static") { - // eslint-disable-next-line no-console - console.warn( - `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.`, - ); - // eslint-disable-next-line no-console - console.warn( - `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.`, - ); - } - }, - "astro:build:done": async ({ routes, dir }) => { - const functionsConfig = { - version: 1, - config: { - nodeModuleFormat: "esm", - }, - }; - const functionsConfigPath = join( - fileURLToPath(_config.build.server), - "entry.json", - ); - await writeFile(functionsConfigPath, JSON.stringify(functionsConfig)); + if (config.output === 'static') { + // eslint-disable-next-line no-console + console.warn( + `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.` + ); + // eslint-disable-next-line no-console + console.warn( + `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.` + ); + } + }, + 'astro:build:done': async ({ routes, dir }) => { + const functionsConfig = { + version: 1, + config: { + nodeModuleFormat: 'esm', + }, + }; + const functionsConfigPath = join(fileURLToPath(_config.build.server), 'entry.json'); + await writeFile(functionsConfigPath, JSON.stringify(functionsConfig)); - const type = builders ? "builders" : "functions"; - const kind = type ?? "functions"; + const type = builders ? 'builders' : 'functions'; + const kind = type ?? 'functions'; - if (_entryPoints.size) { - const routeToDynamicTargetMap = new Map(); - for (const [route, entryFile] of _entryPoints) { - const wholeFileUrl = fileURLToPath(entryFile); + if (_entryPoints.size) { + const routeToDynamicTargetMap = new Map(); + for (const [route, entryFile] of _entryPoints) { + const wholeFileUrl = fileURLToPath(entryFile); - const extension = extname(wholeFileUrl); - const relative = wholeFileUrl - .replace(fileURLToPath(_config.build.server), "") - .replace(extension, "") - .replaceAll("\\", "/"); - const dynamicTarget = `/.netlify/${kind}/${relative}`; + const extension = extname(wholeFileUrl); + const relative = wholeFileUrl + .replace(fileURLToPath(_config.build.server), '') + .replace(extension, '') + .replaceAll('\\', '/'); + const dynamicTarget = `/.netlify/${kind}/${relative}`; - routeToDynamicTargetMap.set(route, dynamicTarget); - } - await createRedirects(_config, routeToDynamicTargetMap, dir); - } else { - const dynamicTarget = `/.netlify/${kind}/${ssrEntryFile}`; - const map: [RouteData, string][] = routes.map((route) => { - return [route, dynamicTarget]; - }); - const routeToDynamicTargetMap = new Map(Array.from(map)); + routeToDynamicTargetMap.set(route, dynamicTarget); + } + await createRedirects(_config, routeToDynamicTargetMap, dir); + } else { + const dynamicTarget = `/.netlify/${kind}/${ssrEntryFile}`; + const map: [RouteData, string][] = routes.map((route) => { + return [route, dynamicTarget]; + }); + const routeToDynamicTargetMap = new Map(Array.from(map)); - await createRedirects(_config, routeToDynamicTargetMap, dir); - } - if (_middlewareEntryPoint) { - const outPath = fileURLToPath( - new URL("./.netlify/edge-functions/", _config.root), - ); - const netlifyEdgeMiddlewareHandlerPath = new URL( - NETLIFY_EDGE_MIDDLEWARE_FILE, - _config.srcDir, - ); - await generateEdgeMiddleware( - _middlewareEntryPoint, - outPath, - netlifyEdgeMiddlewareHandlerPath, - ); - } - }, - }, - }; + await createRedirects(_config, routeToDynamicTargetMap, dir); + } + if (_middlewareEntryPoint) { + const outPath = fileURLToPath(new URL('./.netlify/edge-functions/', _config.root)); + const netlifyEdgeMiddlewareHandlerPath = new URL( + NETLIFY_EDGE_MIDDLEWARE_FILE, + _config.srcDir + ); + await generateEdgeMiddleware( + _middlewareEntryPoint, + outPath, + netlifyEdgeMiddlewareHandlerPath + ); + } + }, + }, + }; } export { netlifyFunctions as default, netlifyFunctions }; diff --git a/packages/integrations/netlify/src/integration-static.ts b/packages/integrations/netlify/src/integration-static.ts index beebdef05..af2849867 100644 --- a/packages/integrations/netlify/src/integration-static.ts +++ b/packages/integrations/netlify/src/integration-static.ts @@ -1,30 +1,30 @@ -import type { AstroIntegration, RouteData } from "astro"; -import { createRedirects } from "./shared.js"; +import type { AstroIntegration, RouteData } from 'astro'; +import { createRedirects } from './shared.js'; export function netlifyStatic(): AstroIntegration { - let _config: any; - return { - name: "@astrojs/netlify", - hooks: { - "astro:config:setup": ({ updateConfig }) => { - updateConfig({ - build: { - // Do not output HTML redirects because we are building a `_redirects` file. - redirects: false, - }, - }); - }, - "astro:config:done": ({ config }) => { - _config = config; - }, - "astro:build:done": async ({ dir, routes }) => { - const mappedRoutes: [RouteData, string][] = routes.map((route) => [ - route, - `/.netlify/static/`, - ]); - const routesToDynamicTargetMap = new Map(Array.from(mappedRoutes)); - await createRedirects(_config, routesToDynamicTargetMap, dir); - }, - }, - }; + let _config: any; + return { + name: '@astrojs/netlify', + hooks: { + 'astro:config:setup': ({ updateConfig }) => { + updateConfig({ + build: { + // Do not output HTML redirects because we are building a `_redirects` file. + redirects: false, + }, + }); + }, + 'astro:config:done': ({ config }) => { + _config = config; + }, + 'astro:build:done': async ({ dir, routes }) => { + const mappedRoutes: [RouteData, string][] = routes.map((route) => [ + route, + `/.netlify/static/`, + ]); + const routesToDynamicTargetMap = new Map(Array.from(mappedRoutes)); + await createRedirects(_config, routesToDynamicTargetMap, dir); + }, + }, + }; } diff --git a/packages/integrations/netlify/src/middleware.ts b/packages/integrations/netlify/src/middleware.ts index c07dd04fa..3c2f4f697 100644 --- a/packages/integrations/netlify/src/middleware.ts +++ b/packages/integrations/netlify/src/middleware.ts @@ -1,68 +1,55 @@ -import { existsSync } from "node:fs"; -import { join } from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import { ASTRO_LOCALS_HEADER } from "./integration-functions.js"; -import { DENO_SHIM } from "./shared.js"; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { ASTRO_LOCALS_HEADER } from './integration-functions.js'; +import { DENO_SHIM } from './shared.js'; /** * It generates a Netlify edge function. * */ export async function generateEdgeMiddleware( - astroMiddlewareEntryPointPath: URL, - outPath: string, - netlifyEdgeMiddlewareHandlerPath: URL, + astroMiddlewareEntryPointPath: URL, + outPath: string, + netlifyEdgeMiddlewareHandlerPath: URL ): Promise { - const entryPointPathURLAsString = JSON.stringify( - fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, "/"), - ); + const entryPointPathURLAsString = JSON.stringify( + fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/') + ); - const code = edgeMiddlewareTemplate( - entryPointPathURLAsString, - netlifyEdgeMiddlewareHandlerPath, - ); - const bundledFilePath = join(outPath, "edgeMiddleware.js"); - const esbuild = await import("esbuild"); - await esbuild.build({ - stdin: { - contents: code, - resolveDir: process.cwd(), - }, - target: "es2020", - platform: "browser", - outfile: bundledFilePath, - allowOverwrite: true, - format: "esm", - bundle: true, - minify: false, - banner: { - js: DENO_SHIM, - }, - }); - return pathToFileURL(bundledFilePath); + const code = edgeMiddlewareTemplate(entryPointPathURLAsString, netlifyEdgeMiddlewareHandlerPath); + const bundledFilePath = join(outPath, 'edgeMiddleware.js'); + const esbuild = await import('esbuild'); + await esbuild.build({ + stdin: { + contents: code, + resolveDir: process.cwd(), + }, + target: 'es2020', + platform: 'browser', + outfile: bundledFilePath, + allowOverwrite: true, + format: 'esm', + bundle: true, + minify: false, + banner: { + js: DENO_SHIM, + }, + }); + return pathToFileURL(bundledFilePath); } -function edgeMiddlewareTemplate( - middlewarePath: string, - netlifyEdgeMiddlewareHandlerPath: URL, -) { - const filePathEdgeMiddleware = fileURLToPath( - netlifyEdgeMiddlewareHandlerPath, - ); - let handlerTemplateImport = ""; - let handlerTemplateCall = "{}"; - if ( - existsSync(filePathEdgeMiddleware + ".js") || - existsSync(filePathEdgeMiddleware + ".ts") - ) { - const stringified = JSON.stringify( - filePathEdgeMiddleware.replace(/\\/g, "/"), - ); - handlerTemplateImport = `import handler from ${stringified}`; - handlerTemplateCall = `handler({ request, context })`; - } else { - } - return ` +function edgeMiddlewareTemplate(middlewarePath: string, netlifyEdgeMiddlewareHandlerPath: URL) { + const filePathEdgeMiddleware = fileURLToPath(netlifyEdgeMiddlewareHandlerPath); + let handlerTemplateImport = ''; + let handlerTemplateCall = '{}'; + if (existsSync(filePathEdgeMiddleware + '.js') || existsSync(filePathEdgeMiddleware + '.ts')) { + const stringified = JSON.stringify(filePathEdgeMiddleware.replace(/\\/g, '/')); + handlerTemplateImport = `import handler from ${stringified}`; + handlerTemplateCall = `handler({ request, context })`; + } else { + } + return ` ${handlerTemplateImport} import { onRequest } from ${middlewarePath}; import { createContext, trySerializeLocals } from 'astro/middleware'; @@ -74,9 +61,7 @@ export default async function middleware(request, context) { }); ctx.locals = ${handlerTemplateCall}; const next = async () => { - request.headers.set(${JSON.stringify( - ASTRO_LOCALS_HEADER, - )}, trySerializeLocals(ctx.locals)); + request.headers.set(${JSON.stringify(ASTRO_LOCALS_HEADER)}, trySerializeLocals(ctx.locals)); return await context.next(); }; diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts index 54a9ea7d3..8c051d9f6 100644 --- a/packages/integrations/netlify/src/netlify-functions.ts +++ b/packages/integrations/netlify/src/netlify-functions.ts @@ -1,157 +1,144 @@ -import { builder, type Handler } from "@netlify/functions"; -import type { SSRManifest } from "astro"; -import { App } from "astro/app"; -import { applyPolyfills } from "astro/app/node"; -import { ASTRO_LOCALS_HEADER } from "./integration-functions.js"; +import { builder, type Handler } from '@netlify/functions'; +import type { SSRManifest } from 'astro'; +import { App } from 'astro/app'; +import { applyPolyfills } from 'astro/app/node'; +import { ASTRO_LOCALS_HEADER } from './integration-functions.js'; applyPolyfills(); export interface Args { - builders?: boolean; - binaryMediaTypes?: string[]; - edgeMiddleware: boolean; - functionPerRoute: boolean; + builders?: boolean; + binaryMediaTypes?: string[]; + edgeMiddleware: boolean; + functionPerRoute: boolean; } function parseContentType(header?: string) { - return header?.split(";")[0] ?? ""; + return header?.split(';')[0] ?? ''; } -const clientAddressSymbol = Symbol.for("astro.clientAddress"); +const clientAddressSymbol = Symbol.for('astro.clientAddress'); export const createExports = (manifest: SSRManifest, args: Args) => { - const app = new App(manifest); - - const builders = args.builders ?? false; - const binaryMediaTypes = args.binaryMediaTypes ?? []; - const knownBinaryMediaTypes = new Set([ - "audio/3gpp", - "audio/3gpp2", - "audio/aac", - "audio/midi", - "audio/mpeg", - "audio/ogg", - "audio/opus", - "audio/wav", - "audio/webm", - "audio/x-midi", - "image/avif", - "image/bmp", - "image/gif", - "image/vnd.microsoft.icon", - "image/heif", - "image/jpeg", - "image/png", - "image/svg+xml", - "image/tiff", - "image/webp", - "video/3gpp", - "video/3gpp2", - "video/mp2t", - "video/mp4", - "video/mpeg", - "video/ogg", - "video/x-msvideo", - "video/webm", - ...binaryMediaTypes, - ]); - - const myHandler: Handler = async (event) => { - const { - httpMethod, - headers, - rawUrl, - body: requestBody, - isBase64Encoded, - } = event; - const init: RequestInit = { - method: httpMethod, - headers: new Headers(headers as any), - }; - // Attach the event body the request, with proper encoding. - if (httpMethod !== "GET" && httpMethod !== "HEAD") { - const encoding = isBase64Encoded ? "base64" : "utf-8"; - init.body = - typeof requestBody === "string" - ? Buffer.from(requestBody, encoding) - : requestBody; - } - - const request = new Request(rawUrl, init); - - const routeData = app.match(request); - const ip = headers["x-nf-client-connection-ip"]; - Reflect.set(request, clientAddressSymbol, ip); - - let locals: Record = {}; - - if (request.headers.has(ASTRO_LOCALS_HEADER)) { - let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER); - if (localsAsString) { - locals = JSON.parse(localsAsString); - } - } - - let responseTtl = undefined; - - locals.runtime = builders - ? { - setBuildersTtl(ttl: number) { - responseTtl = ttl; - }, - } - : {}; - - const response: Response = await app.render(request, routeData, locals); - const responseHeaders = Object.fromEntries(response.headers.entries()); - - const responseContentType = parseContentType( - responseHeaders["content-type"], - ); - const responseIsBase64Encoded = - knownBinaryMediaTypes.has(responseContentType); - - let responseBody: string; - if (responseIsBase64Encoded) { - const ab = await response.arrayBuffer(); - responseBody = Buffer.from(ab).toString("base64"); - } else { - responseBody = await response.text(); - } - - const fnResponse: any = { - statusCode: response.status, - headers: responseHeaders, - body: responseBody, - isBase64Encoded: responseIsBase64Encoded, - ttl: responseTtl, - }; - - const cookies = response.headers.get("set-cookie"); - if (cookies) { - fnResponse.multiValueHeaders = { - "set-cookie": Array.isArray(cookies) - ? cookies - : splitCookiesString(cookies), - }; - } - - // 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; - }; - - const handler = builders ? builder(myHandler) : myHandler; - - return { handler }; + const app = new App(manifest); + + const builders = args.builders ?? false; + const binaryMediaTypes = args.binaryMediaTypes ?? []; + const knownBinaryMediaTypes = new Set([ + 'audio/3gpp', + 'audio/3gpp2', + 'audio/aac', + 'audio/midi', + 'audio/mpeg', + 'audio/ogg', + 'audio/opus', + 'audio/wav', + 'audio/webm', + 'audio/x-midi', + 'image/avif', + 'image/bmp', + 'image/gif', + 'image/vnd.microsoft.icon', + 'image/heif', + 'image/jpeg', + 'image/png', + 'image/svg+xml', + 'image/tiff', + 'image/webp', + 'video/3gpp', + 'video/3gpp2', + 'video/mp2t', + 'video/mp4', + 'video/mpeg', + 'video/ogg', + 'video/x-msvideo', + 'video/webm', + ...binaryMediaTypes, + ]); + + const myHandler: Handler = async (event) => { + const { httpMethod, headers, rawUrl, body: requestBody, isBase64Encoded } = event; + const init: RequestInit = { + method: httpMethod, + headers: new Headers(headers as any), + }; + // Attach the event body the request, with proper encoding. + if (httpMethod !== 'GET' && httpMethod !== 'HEAD') { + const encoding = isBase64Encoded ? 'base64' : 'utf-8'; + init.body = + typeof requestBody === 'string' ? Buffer.from(requestBody, encoding) : requestBody; + } + + const request = new Request(rawUrl, init); + + const routeData = app.match(request); + const ip = headers['x-nf-client-connection-ip']; + Reflect.set(request, clientAddressSymbol, ip); + + let locals: Record = {}; + + if (request.headers.has(ASTRO_LOCALS_HEADER)) { + let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER); + if (localsAsString) { + locals = JSON.parse(localsAsString); + } + } + + let responseTtl = undefined; + + locals.runtime = builders + ? { + setBuildersTtl(ttl: number) { + responseTtl = ttl; + }, + } + : {}; + + const response: Response = await app.render(request, routeData, locals); + const responseHeaders = Object.fromEntries(response.headers.entries()); + + const responseContentType = parseContentType(responseHeaders['content-type']); + const responseIsBase64Encoded = knownBinaryMediaTypes.has(responseContentType); + + let responseBody: string; + if (responseIsBase64Encoded) { + const ab = await response.arrayBuffer(); + responseBody = Buffer.from(ab).toString('base64'); + } else { + responseBody = await response.text(); + } + + const fnResponse: any = { + statusCode: response.status, + headers: responseHeaders, + body: responseBody, + isBase64Encoded: responseIsBase64Encoded, + ttl: responseTtl, + }; + + const cookies = response.headers.get('set-cookie'); + if (cookies) { + fnResponse.multiValueHeaders = { + 'set-cookie': Array.isArray(cookies) ? cookies : splitCookiesString(cookies), + }; + } + + // 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; + }; + + const handler = builders ? builder(myHandler) : myHandler; + + return { handler }; }; /* @@ -165,74 +152,74 @@ export const createExports = (manifest: SSRManifest, args: Args) => { Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation */ function splitCookiesString(cookiesString: string): string[] { - if (Array.isArray(cookiesString)) { - return cookiesString; - } - if (typeof cookiesString !== "string") { - return []; - } - - let cookiesStrings = []; - let pos = 0; - let start; - let ch; - let lastComma; - let nextStart; - let cookiesSeparatorFound; - - function skipWhitespace() { - while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) { - pos += 1; - } - return pos < cookiesString.length; - } - - function notSpecialChar() { - ch = cookiesString.charAt(pos); - - return ch !== "=" && ch !== ";" && ch !== ","; - } - - while (pos < cookiesString.length) { - start = pos; - cookiesSeparatorFound = false; - - while (skipWhitespace()) { - ch = cookiesString.charAt(pos); - if (ch === ",") { - // ',' is a cookie separator if we have later first '=', not ';' or ',' - lastComma = pos; - pos += 1; - - skipWhitespace(); - nextStart = pos; - - while (pos < cookiesString.length && notSpecialChar()) { - pos += 1; - } - - // currently special character - if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") { - // we found cookies separator - cookiesSeparatorFound = true; - // pos is inside the next cookie, so back up and return it. - pos = nextStart; - cookiesStrings.push(cookiesString.substring(start, lastComma)); - start = pos; - } else { - // in param ',' or param separator ';', - // we continue from that comma - pos = lastComma + 1; - } - } else { - pos += 1; - } - } - - if (!cookiesSeparatorFound || pos >= cookiesString.length) { - cookiesStrings.push(cookiesString.substring(start, cookiesString.length)); - } - } - - return cookiesStrings; + if (Array.isArray(cookiesString)) { + return cookiesString; + } + if (typeof cookiesString !== 'string') { + return []; + } + + let cookiesStrings = []; + let pos = 0; + let start; + let ch; + let lastComma; + let nextStart; + let cookiesSeparatorFound; + + function skipWhitespace() { + while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) { + pos += 1; + } + return pos < cookiesString.length; + } + + function notSpecialChar() { + ch = cookiesString.charAt(pos); + + return ch !== '=' && ch !== ';' && ch !== ','; + } + + while (pos < cookiesString.length) { + start = pos; + cookiesSeparatorFound = false; + + while (skipWhitespace()) { + ch = cookiesString.charAt(pos); + if (ch === ',') { + // ',' is a cookie separator if we have later first '=', not ';' or ',' + lastComma = pos; + pos += 1; + + skipWhitespace(); + nextStart = pos; + + while (pos < cookiesString.length && notSpecialChar()) { + pos += 1; + } + + // currently special character + if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') { + // we found cookies separator + cookiesSeparatorFound = true; + // pos is inside the next cookie, so back up and return it. + pos = nextStart; + cookiesStrings.push(cookiesString.substring(start, lastComma)); + start = pos; + } else { + // in param ',' or param separator ';', + // we continue from that comma + pos = lastComma + 1; + } + } else { + pos += 1; + } + } + + if (!cookiesSeparatorFound || pos >= cookiesString.length) { + cookiesStrings.push(cookiesString.substring(start, cookiesString.length)); + } + } + + return cookiesStrings; } diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts index 4003f812f..175b9d04f 100644 --- a/packages/integrations/netlify/src/shared.ts +++ b/packages/integrations/netlify/src/shared.ts @@ -1,9 +1,9 @@ -import { createRedirectsFromAstroRoutes } from "@astrojs/underscore-redirects"; -import type { AstroConfig, RouteData } from "astro"; -import esbuild from "esbuild"; -import fs from "node:fs"; -import npath from "node:path"; -import { fileURLToPath } from "node:url"; +import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; +import type { AstroConfig, RouteData } from 'astro'; +import esbuild from 'esbuild'; +import fs from 'node:fs'; +import npath from 'node:path'; +import { fileURLToPath } from 'node:url'; export const DENO_SHIM = `globalThis.process = { argv: [], @@ -11,113 +11,104 @@ export const DENO_SHIM = `globalThis.process = { };`; export interface NetlifyEdgeFunctionsOptions { - dist?: URL; + dist?: URL; } export interface NetlifyEdgeFunctionManifestFunctionPath { - function: string; - path: string; + function: string; + path: string; } export interface NetlifyEdgeFunctionManifestFunctionPattern { - function: string; - pattern: string; + function: string; + pattern: string; } export type NetlifyEdgeFunctionManifestFunction = - | NetlifyEdgeFunctionManifestFunctionPath - | NetlifyEdgeFunctionManifestFunctionPattern; + | NetlifyEdgeFunctionManifestFunctionPath + | NetlifyEdgeFunctionManifestFunctionPattern; export interface NetlifyEdgeFunctionManifest { - functions: NetlifyEdgeFunctionManifestFunction[]; - version: 1; + functions: NetlifyEdgeFunctionManifestFunction[]; + version: 1; } export async function createRedirects( - config: AstroConfig, - routeToDynamicTargetMap: Map, - dir: URL, + config: AstroConfig, + routeToDynamicTargetMap: Map, + dir: URL ) { - const _redirectsURL = new URL("./_redirects", dir); + const _redirectsURL = new URL('./_redirects', dir); - const _redirects = createRedirectsFromAstroRoutes({ - config, - routeToDynamicTargetMap, - dir, - }); - const content = _redirects.print(); + const _redirects = createRedirectsFromAstroRoutes({ + config, + routeToDynamicTargetMap, + dir, + }); + const content = _redirects.print(); - // Always use appendFile() because the redirects file could already exist, - // e.g. due to a `/public/_redirects` file that got copied to the output dir. - // If the file does not exist yet, appendFile() automatically creates it. - await fs.promises.appendFile(_redirectsURL, content, "utf-8"); + // Always use appendFile() because the redirects file could already exist, + // e.g. due to a `/public/_redirects` file that got copied to the output dir. + // If the file does not exist yet, appendFile() automatically creates it. + await fs.promises.appendFile(_redirectsURL, content, 'utf-8'); } -export async function createEdgeManifest( - routes: RouteData[], - entryFile: string, - dir: URL, -) { - const functions: NetlifyEdgeFunctionManifestFunction[] = []; - for (const route of routes) { - if (route.pathname) { - functions.push({ - function: entryFile, - path: route.pathname, - }); - } else { - functions.push({ - function: entryFile, - // Make route pattern serializable to match expected - // Netlify Edge validation format. Mirrors Netlify's own edge bundler: - // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34 - pattern: route.pattern.source.replace(/\\\//g, "/").toString(), - }); - } - } +export async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: URL) { + const functions: NetlifyEdgeFunctionManifestFunction[] = []; + for (const route of routes) { + if (route.pathname) { + functions.push({ + function: entryFile, + path: route.pathname, + }); + } else { + functions.push({ + function: entryFile, + // Make route pattern serializable to match expected + // Netlify Edge validation format. Mirrors Netlify's own edge bundler: + // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34 + pattern: route.pattern.source.replace(/\\\//g, '/').toString(), + }); + } + } - const manifest: NetlifyEdgeFunctionManifest = { - functions, - version: 1, - }; + const manifest: NetlifyEdgeFunctionManifest = { + functions, + version: 1, + }; - const baseDir = new URL("./.netlify/edge-functions/", dir); - await fs.promises.mkdir(baseDir, { recursive: true }); + const baseDir = new URL('./.netlify/edge-functions/', dir); + await fs.promises.mkdir(baseDir, { recursive: true }); - const manifestURL = new URL("./manifest.json", baseDir); - const _manifest = JSON.stringify(manifest, null, " "); - await fs.promises.writeFile(manifestURL, _manifest, "utf-8"); + const manifestURL = new URL('./manifest.json', baseDir); + const _manifest = JSON.stringify(manifest, null, ' '); + await fs.promises.writeFile(manifestURL, _manifest, 'utf-8'); } -export async function bundleServerEntry( - entryUrl: URL, - serverUrl?: URL, - vite?: any | undefined, -) { - const pth = fileURLToPath(entryUrl); - await esbuild.build({ - target: "es2020", - platform: "browser", - entryPoints: [pth], - outfile: pth, - allowOverwrite: true, - format: "esm", - bundle: true, - external: ["@astrojs/markdown-remark", "astro/middleware"], - banner: { - js: DENO_SHIM, - }, - }); +export async function bundleServerEntry(entryUrl: URL, serverUrl?: URL, vite?: any | undefined) { + const pth = fileURLToPath(entryUrl); + await esbuild.build({ + target: 'es2020', + platform: 'browser', + entryPoints: [pth], + outfile: pth, + allowOverwrite: true, + format: 'esm', + bundle: true, + external: ['@astrojs/markdown-remark', 'astro/middleware'], + banner: { + js: DENO_SHIM, + }, + }); - // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash. - if (vite && serverUrl) { - try { - const chunkFileNames = - vite?.build?.rollupOptions?.output?.chunkFileNames ?? - `chunks/chunk.[hash].mjs`; - const chunkPath = npath.dirname(chunkFileNames); - const chunksDirUrl = new URL(chunkPath + "/", serverUrl); - await fs.promises.rm(chunksDirUrl, { recursive: true, force: true }); - } catch {} - } + // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash. + if (vite && serverUrl) { + try { + const chunkFileNames = + vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`; + const chunkPath = npath.dirname(chunkFileNames); + const chunksDirUrl = new URL(chunkPath + '/', serverUrl); + await fs.promises.rm(chunksDirUrl, { recursive: true, force: true }); + } catch {} + } } diff --git a/packages/integrations/netlify/test/functions/404.test.js b/packages/integrations/netlify/test/functions/404.test.js index a1f792ce0..071e3f057 100644 --- a/packages/integrations/netlify/test/functions/404.test.js +++ b/packages/integrations/netlify/test/functions/404.test.js @@ -1,21 +1,18 @@ -import { expect } from "chai"; -import fs from "fs/promises"; -import { cli } from "./test-utils.js"; -import { fileURLToPath } from "url"; +import { expect } from 'chai'; +import fs from 'fs/promises'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -const root = new URL("./fixtures/404/", import.meta.url).toString(); +const root = new URL('./fixtures/404/', import.meta.url).toString(); -describe("404 page", () => { - before(async () => { - await cli("build", "--root", fileURLToPath(root)); - }); +describe('404 page', () => { + before(async () => { + await cli('build', '--root', fileURLToPath(root)); + }); - it("404 route is included in the redirect file", async () => { - const redir = await fs.readFile( - new URL("./dist/_redirects", root), - "utf-8", - ); - const expr = new RegExp("/* /.netlify/functions/entry 404"); - expect(redir).to.match(expr); - }); + it('404 route is included in the redirect file', async () => { + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); + const expr = new RegExp('/* /.netlify/functions/entry 404'); + expect(redir).to.match(expr); + }); }); diff --git a/packages/integrations/netlify/test/functions/base64-response.test.js b/packages/integrations/netlify/test/functions/base64-response.test.js index 07c1bbafe..f8f32edda 100644 --- a/packages/integrations/netlify/test/functions/base64-response.test.js +++ b/packages/integrations/netlify/test/functions/base64-response.test.js @@ -1,55 +1,51 @@ -import { expect } from "chai"; -import { cli } from "./test-utils.js"; -import { fileURLToPath } from "url"; +import { expect } from 'chai'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -const root = new URL("./fixtures/base64-response/", import.meta.url).toString(); +const root = new URL('./fixtures/base64-response/', import.meta.url).toString(); -describe("Base64 Responses", () => { - before(async () => { - await cli("build", "--root", fileURLToPath(root)); - }); +describe('Base64 Responses', () => { + before(async () => { + await cli('build', '--root', fileURLToPath(root)); + }); - it("Can return base64 encoded strings", async () => { - const entryURL = new URL( - "./fixtures/base64-response/.netlify/functions-internal/entry.mjs", - import.meta.url, - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: "GET", - headers: {}, - rawUrl: "http://example.com/image", - body: "{}", - isBase64Encoded: false, - }); - expect(resp.statusCode, "successful response").to.equal(200); - expect(resp.isBase64Encoded, "includes isBase64Encoded flag").to.be.true; + it('Can return base64 encoded strings', async () => { + const entryURL = new URL( + './fixtures/base64-response/.netlify/functions-internal/entry.mjs', + import.meta.url + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: 'GET', + headers: {}, + rawUrl: 'http://example.com/image', + body: '{}', + isBase64Encoded: false, + }); + expect(resp.statusCode, 'successful response').to.equal(200); + expect(resp.isBase64Encoded, 'includes isBase64Encoded flag').to.be.true; - const buffer = Buffer.from(resp.body, "base64"); - expect(buffer.toString(), "decoded base64 string matches").to.equal( - "base64 test string", - ); - }); + const buffer = Buffer.from(resp.body, 'base64'); + expect(buffer.toString(), 'decoded base64 string matches').to.equal('base64 test string'); + }); - it("Can define custom binaryMediaTypes", async () => { - const entryURL = new URL( - "./fixtures/base64-response/.netlify/functions-internal/entry.mjs", - import.meta.url, - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: "GET", - headers: {}, - rawUrl: "http://example.com/font", - body: "{}", - isBase64Encoded: false, - }); - expect(resp.statusCode, "successful response").to.equal(200); - expect(resp.isBase64Encoded, "includes isBase64Encoded flag").to.be.true; + it('Can define custom binaryMediaTypes', async () => { + const entryURL = new URL( + './fixtures/base64-response/.netlify/functions-internal/entry.mjs', + import.meta.url + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: 'GET', + headers: {}, + rawUrl: 'http://example.com/font', + body: '{}', + isBase64Encoded: false, + }); + expect(resp.statusCode, 'successful response').to.equal(200); + expect(resp.isBase64Encoded, 'includes isBase64Encoded flag').to.be.true; - const buffer = Buffer.from(resp.body, "base64"); - expect(buffer.toString(), "decoded base64 string matches").to.equal( - "base64 test font", - ); - }); + const buffer = Buffer.from(resp.body, 'base64'); + expect(buffer.toString(), 'decoded base64 string matches').to.equal('base64 test font'); + }); }); diff --git a/packages/integrations/netlify/test/functions/builders.test.js b/packages/integrations/netlify/test/functions/builders.test.js index 281c4f3da..e927527a1 100644 --- a/packages/integrations/netlify/test/functions/builders.test.js +++ b/packages/integrations/netlify/test/functions/builders.test.js @@ -1,26 +1,26 @@ -import { expect } from "chai"; -import { cli } from "./test-utils.js"; -import { fileURLToPath } from "url"; +import { expect } from 'chai'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -const root = new URL("./fixtures/builders/", import.meta.url).toString(); +const root = new URL('./fixtures/builders/', import.meta.url).toString(); -describe("Builders", () => { - before(async () => { - await cli("build", "--root", fileURLToPath(root)); - }); +describe('Builders', () => { + before(async () => { + await cli('build', '--root', fileURLToPath(root)); + }); - it("A route can set builders ttl", async () => { - const entryURL = new URL( - "./fixtures/builders/.netlify/functions-internal/entry.mjs", - import.meta.url, - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: "GET", - headers: {}, - rawUrl: "http://example.com/", - isBase64Encoded: false, - }); - expect(resp.ttl).to.equal(45); - }); + it('A route can set builders ttl', async () => { + const entryURL = new URL( + './fixtures/builders/.netlify/functions-internal/entry.mjs', + import.meta.url + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: 'GET', + headers: {}, + rawUrl: 'http://example.com/', + isBase64Encoded: false, + }); + expect(resp.ttl).to.equal(45); + }); }); diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index 05727207e..328294d10 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,31 +1,31 @@ -import { expect } from "chai"; -import { cli } from "./test-utils.js"; -import { fileURLToPath } from "url"; +import { expect } from 'chai'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -const root = new URL("./fixtures/cookies/", import.meta.url).toString(); +const root = new URL('./fixtures/cookies/', import.meta.url).toString(); -describe("Cookies", () => { - before(async () => { - await cli("build", "--root", fileURLToPath(root)); - }); +describe('Cookies', () => { + before(async () => { + await cli('build', '--root', fileURLToPath(root)); + }); - it("Can set multiple", async () => { - const entryURL = new URL( - "./fixtures/cookies/.netlify/functions-internal/entry.mjs", - import.meta.url, - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: "POST", - headers: {}, - rawUrl: "http://example.com/login", - body: "{}", - isBase64Encoded: false, - }); - expect(resp.statusCode).to.equal(301); - expect(resp.headers.location).to.equal("/"); - expect(resp.multiValueHeaders).to.be.deep.equal({ - "set-cookie": ["foo=foo; HttpOnly", "bar=bar; HttpOnly"], - }); - }); + it('Can set multiple', async () => { + const entryURL = new URL( + './fixtures/cookies/.netlify/functions-internal/entry.mjs', + import.meta.url + ); + const { handler } = await import(entryURL); + const resp = await handler({ + httpMethod: 'POST', + headers: {}, + rawUrl: 'http://example.com/login', + body: '{}', + isBase64Encoded: false, + }); + expect(resp.statusCode).to.equal(301); + expect(resp.headers.location).to.equal('/'); + expect(resp.multiValueHeaders).to.be.deep.equal({ + 'set-cookie': ['foo=foo; HttpOnly', 'bar=bar; HttpOnly'], + }); + }); }); diff --git a/packages/integrations/netlify/test/functions/dynamic-route.test.js b/packages/integrations/netlify/test/functions/dynamic-route.test.js index 69d25a11e..2e20454e6 100644 --- a/packages/integrations/netlify/test/functions/dynamic-route.test.js +++ b/packages/integrations/netlify/test/functions/dynamic-route.test.js @@ -1,36 +1,24 @@ -import { expect } from "chai"; -import { cli } from "./test-utils.js"; -import { fileURLToPath } from "url"; -import fs from "fs/promises"; +import { expect } from 'chai'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; +import fs from 'fs/promises'; -const root = new URL("./fixtures/dynamic-route/", import.meta.url).toString(); +const root = new URL('./fixtures/dynamic-route/', import.meta.url).toString(); -describe("Dynamic pages", () => { - before(async () => { - await cli("build", "--root", fileURLToPath(root)); - }); +describe('Dynamic pages', () => { + before(async () => { + await cli('build', '--root', fileURLToPath(root)); + }); - it("Dynamic pages are included in the redirects file", async () => { - const redir = await fs.readFile( - new URL("./dist/_redirects", root), - "utf-8", - ); - expect(redir).to.match(/\/products\/:id/); - }); + it('Dynamic pages are included in the redirects file', async () => { + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); + expect(redir).to.match(/\/products\/:id/); + }); - it("Prerendered routes are also included using placeholder syntax", async () => { - const redir = await fs.readFile( - new URL("./dist/_redirects", root), - "utf-8", - ); - expect(redir).to.include( - "/pets/:cat /pets/:cat/index.html 200", - ); - expect(redir).to.include( - "/pets/:dog /pets/:dog/index.html 200", - ); - expect(redir).to.include( - "/pets /.netlify/functions/entry 200", - ); - }); + it('Prerendered routes are also included using placeholder syntax', async () => { + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); + expect(redir).to.include('/pets/:cat /pets/:cat/index.html 200'); + expect(redir).to.include('/pets/:dog /pets/:dog/index.html 200'); + expect(redir).to.include('/pets /.netlify/functions/entry 200'); + }); }); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 90bcd69c7..ea214f82a 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,32 +1,26 @@ -import { fileURLToPath } from "url"; -import { cli } from "./test-utils.js"; -import fs from "fs/promises"; -import { expect } from "chai"; +import { fileURLToPath } from 'url'; +import { cli } from './test-utils.js'; +import fs from 'fs/promises'; +import { expect } from 'chai'; -describe("Middleware", () => { - it("with edge handle file, should successfully build the middleware", async () => { - const root = new URL( - "./fixtures/middleware-with-handler-file/", - import.meta.url, - ).toString(); - await cli("build", "--root", fileURLToPath(root)); - const contents = await fs.readFile( - new URL("./.netlify/edge-functions/edgeMiddleware.js", root), - "utf-8", - ); - expect(contents.includes('"Hello world"')).to.be.true; - }); +describe('Middleware', () => { + it('with edge handle file, should successfully build the middleware', async () => { + const root = new URL('./fixtures/middleware-with-handler-file/', import.meta.url).toString(); + await cli('build', '--root', fileURLToPath(root)); + const contents = await fs.readFile( + new URL('./.netlify/edge-functions/edgeMiddleware.js', root), + 'utf-8' + ); + expect(contents.includes('"Hello world"')).to.be.true; + }); - it("without edge handle file, should successfully build the middleware", async () => { - const root = new URL( - "./fixtures/middleware-without-handler-file/", - import.meta.url, - ).toString(); - await cli("build", "--root", fileURLToPath(root)); - const contents = await fs.readFile( - new URL("./.netlify/edge-functions/edgeMiddleware.js", root), - "utf-8", - ); - expect(contents.includes('"Hello world"')).to.be.false; - }); + it('without edge handle file, should successfully build the middleware', async () => { + const root = new URL('./fixtures/middleware-without-handler-file/', import.meta.url).toString(); + await cli('build', '--root', fileURLToPath(root)); + const contents = await fs.readFile( + new URL('./.netlify/edge-functions/edgeMiddleware.js', root), + 'utf-8' + ); + expect(contents.includes('"Hello world"')).to.be.false; + }); }); diff --git a/packages/integrations/netlify/test/functions/prerender.test.js b/packages/integrations/netlify/test/functions/prerender.test.js index 61af252cb..8acc5a519 100644 --- a/packages/integrations/netlify/test/functions/prerender.test.js +++ b/packages/integrations/netlify/test/functions/prerender.test.js @@ -1,73 +1,53 @@ -import { expect } from "chai"; -import fs from "fs/promises"; -import { cli } from "./test-utils.js"; -import { fileURLToPath } from "url"; - -const root = new URL("./fixtures/prerender/", import.meta.url).toString(); - -describe("Mixed Prerendering with SSR", () => { - before(async () => { - process.env.PRERENDER = true; - await cli("build", "--root", fileURLToPath(root)); - }); - - after(() => { - delete process.env.PRERENDER; - }); - - it("Wildcard 404 is sorted last", async () => { - const redir = await fs.readFile( - new URL("./dist/_redirects", root), - "utf-8", - ); - const baseRouteIndex = redir.indexOf( - "/ /.netlify/functions/entry 200", - ); - const oneRouteIndex = redir.indexOf( - "/one /one/index.html 200", - ); - const fourOhFourWildCardIndex = redir.indexOf( - "/* /.netlify/functions/entry 404", - ); - - expect(oneRouteIndex).to.not.be.equal(-1); - expect(fourOhFourWildCardIndex).to.be.greaterThan(baseRouteIndex); - expect(fourOhFourWildCardIndex).to.be.greaterThan(oneRouteIndex); - }); +import { expect } from 'chai'; +import fs from 'fs/promises'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; + +const root = new URL('./fixtures/prerender/', import.meta.url).toString(); + +describe('Mixed Prerendering with SSR', () => { + before(async () => { + process.env.PRERENDER = true; + await cli('build', '--root', fileURLToPath(root)); + }); + + after(() => { + delete process.env.PRERENDER; + }); + + it('Wildcard 404 is sorted last', async () => { + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); + const baseRouteIndex = redir.indexOf('/ /.netlify/functions/entry 200'); + const oneRouteIndex = redir.indexOf('/one /one/index.html 200'); + const fourOhFourWildCardIndex = redir.indexOf('/* /.netlify/functions/entry 404'); + + expect(oneRouteIndex).to.not.be.equal(-1); + expect(fourOhFourWildCardIndex).to.be.greaterThan(baseRouteIndex); + expect(fourOhFourWildCardIndex).to.be.greaterThan(oneRouteIndex); + }); }); -describe("Mixed Hybrid rendering with SSR", () => { - before(async () => { - process.env.PRERENDER = false; - process.env.ASTRO_OUTPUT = "hybrid"; - await cli("build", "--root", fileURLToPath(root)); - }); - - after(() => { - delete process.env.PRERENDER; - }); - - it("outputs a correct redirect file", async () => { - const redir = await fs.readFile( - new URL("./dist/_redirects", root), - "utf-8", - ); - const baseRouteIndex = redir.indexOf( - "/one /.netlify/functions/entry 200", - ); - const rootRouteIndex = redir.indexOf( - "/ /index.html 200", - ); - const fourOhFourIndex = redir.indexOf( - "/404 /404.html 200", - ); - const imageEndpoint = redir.indexOf( - "/_image /.netlify/functions/entry 200", - ); - - expect(rootRouteIndex).to.not.be.equal(-1); - expect(baseRouteIndex).to.not.be.equal(-1); - expect(fourOhFourIndex).to.not.be.equal(-1); - expect(imageEndpoint).to.not.be.equal(-1); - }); +describe('Mixed Hybrid rendering with SSR', () => { + before(async () => { + process.env.PRERENDER = false; + process.env.ASTRO_OUTPUT = 'hybrid'; + await cli('build', '--root', fileURLToPath(root)); + }); + + after(() => { + delete process.env.PRERENDER; + }); + + it('outputs a correct redirect file', async () => { + const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); + const baseRouteIndex = redir.indexOf('/one /.netlify/functions/entry 200'); + const rootRouteIndex = redir.indexOf('/ /index.html 200'); + const fourOhFourIndex = redir.indexOf('/404 /404.html 200'); + const imageEndpoint = redir.indexOf('/_image /.netlify/functions/entry 200'); + + expect(rootRouteIndex).to.not.be.equal(-1); + expect(baseRouteIndex).to.not.be.equal(-1); + expect(fourOhFourIndex).to.not.be.equal(-1); + expect(imageEndpoint).to.not.be.equal(-1); + }); }); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index 43fc0cd5d..855378ad4 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,56 +1,50 @@ -import { expect } from "chai"; -import fs from "fs/promises"; -import { cli } from "./test-utils.js"; -import { fileURLToPath } from "url"; +import { expect } from 'chai'; +import fs from 'fs/promises'; +import { cli } from './test-utils.js'; +import { fileURLToPath } from 'url'; -const root = new URL( - "../functions/fixtures/redirects/", - import.meta.url, -).toString(); +const root = new URL('../functions/fixtures/redirects/', import.meta.url).toString(); -describe("SSG - Redirects", () => { - before(async () => { - await cli("build", "--root", fileURLToPath(root)); - }); +describe('SSG - Redirects', () => { + before(async () => { + await cli('build', '--root', fileURLToPath(root)); + }); - it("Creates a redirects file", async () => { - let redirects = await fs.readFile( - new URL("./dist/_redirects", root), - "utf-8", - ); - let parts = redirects.split(/\s+/); - expect(parts).to.deep.equal([ - "/other", - "/", - "301", - // This uses the dynamic Astro.redirect, so we don't know that it's a redirect - // until runtime. This is correct! - "/nope", - "/.netlify/functions/entry", - "200", - "/", - "/.netlify/functions/entry", - "200", + it('Creates a redirects file', async () => { + let redirects = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); + let parts = redirects.split(/\s+/); + expect(parts).to.deep.equal([ + '/other', + '/', + '301', + // This uses the dynamic Astro.redirect, so we don't know that it's a redirect + // until runtime. This is correct! + '/nope', + '/.netlify/functions/entry', + '200', + '/', + '/.netlify/functions/entry', + '200', - // Image endpoint - "/_image", - "/.netlify/functions/entry", - "200", + // Image endpoint + '/_image', + '/.netlify/functions/entry', + '200', - // A real route - "/team/articles/*", - "/.netlify/functions/entry", - "200", - ]); - expect(redirects).to.matchSnapshot(); - }); + // A real route + '/team/articles/*', + '/.netlify/functions/entry', + '200', + ]); + expect(redirects).to.matchSnapshot(); + }); - it("Does not create .html files", async () => { - try { - await fixture.readFile("/other/index.html"); - expect(false).to.equal(true, "this file should not exist"); - } catch { - expect(true).to.equal(true); - } - }); + it('Does not create .html files', async () => { + try { + await fixture.readFile('/other/index.html'); + expect(false).to.equal(true, 'this file should not exist'); + } catch { + expect(true).to.equal(true); + } + }); }); diff --git a/packages/integrations/netlify/test/functions/split-support.test.js b/packages/integrations/netlify/test/functions/split-support.test.js index eb1a0913a..55f661403 100644 --- a/packages/integrations/netlify/test/functions/split-support.test.js +++ b/packages/integrations/netlify/test/functions/split-support.test.js @@ -1,64 +1,64 @@ -import { expect } from "chai"; -import netlifyAdapter from "../../dist/index.js"; -import { testIntegration } from "./test-utils.js"; -import { loadFixture } from "test-utils"; +import { expect } from 'chai'; +import netlifyAdapter from '../../dist/index.js'; +import { testIntegration } from './test-utils.js'; +import { loadFixture } from 'test-utils'; -describe("Split support", () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; - let _entryPoints; +describe('Split support', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + let _entryPoints; - before(async () => { - fixture = await loadFixture({ - root: new URL("./fixtures/split-support/", import.meta.url).toString(), - output: "server", - adapter: netlifyAdapter({ - dist: new URL("./fixtures/split-support/dist/", import.meta.url), - functionPerRoute: true, - }), - site: `http://example.com`, - integrations: [ - testIntegration({ - setEntryPoints(ep) { - _entryPoints = ep; - }, - }), - ], - }); - await fixture.build(); - }); + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/split-support/', import.meta.url).toString(), + output: 'server', + adapter: netlifyAdapter({ + dist: new URL('./fixtures/split-support/dist/', import.meta.url), + functionPerRoute: true, + }), + site: `http://example.com`, + integrations: [ + testIntegration({ + setEntryPoints(ep) { + _entryPoints = ep; + }, + }), + ], + }); + await fixture.build(); + }); - it("outputs a correct redirect file", async () => { - const redir = await fixture.readFile("/_redirects"); - const lines = redir.split(/[\r\n]+/); - expect(lines.length).to.equal(3); + it('outputs a correct redirect file', async () => { + const redir = await fixture.readFile('/_redirects'); + const lines = redir.split(/[\r\n]+/); + expect(lines.length).to.equal(3); - expect(lines[0].includes("/blog")).to.be.true; - expect(lines[0].includes("blog.astro")).to.be.true; - expect(lines[0].includes("200")).to.be.true; - expect(lines[1].includes("/")).to.be.true; - expect(lines[1].includes("index.astro")).to.be.true; - expect(lines[1].includes("200")).to.be.true; - }); + expect(lines[0].includes('/blog')).to.be.true; + expect(lines[0].includes('blog.astro')).to.be.true; + expect(lines[0].includes('200')).to.be.true; + expect(lines[1].includes('/')).to.be.true; + expect(lines[1].includes('index.astro')).to.be.true; + expect(lines[1].includes('200')).to.be.true; + }); - describe("Should create multiple functions", () => { - it("and hit 200", async () => { - if (_entryPoints) { - for (const [routeData, filePath] of _entryPoints) { - if (routeData.route !== "/_image") { - const { handler } = await import(filePath.toString()); - const resp = await handler({ - httpMethod: "GET", - headers: {}, - rawUrl: `http://example.com${routeData.route}`, - body: "{}", - }); - expect(resp.statusCode).to.equal(200); - } - } - } else { - expect(false).to.be.true; - } - }); - }); + describe('Should create multiple functions', () => { + it('and hit 200', async () => { + if (_entryPoints) { + for (const [routeData, filePath] of _entryPoints) { + if (routeData.route !== '/_image') { + const { handler } = await import(filePath.toString()); + const resp = await handler({ + httpMethod: 'GET', + headers: {}, + rawUrl: `http://example.com${routeData.route}`, + body: '{}', + }); + expect(resp.statusCode).to.equal(200); + } + } + } else { + expect(false).to.be.true; + } + }); + }); }); diff --git a/packages/integrations/netlify/test/functions/test-utils.js b/packages/integrations/netlify/test/functions/test-utils.js index 46128095e..c977af42e 100644 --- a/packages/integrations/netlify/test/functions/test-utils.js +++ b/packages/integrations/netlify/test/functions/test-utils.js @@ -1,34 +1,34 @@ // @ts-check -import { fileURLToPath } from "node:url"; +import { fileURLToPath } from 'node:url'; -export * from "../test-utils.js"; +export * from '../test-utils.js'; /** * * @returns {import('astro').AstroIntegration} */ export function testIntegration({ setEntryPoints } = {}) { - return { - name: "@astrojs/netlify/test-integration", - hooks: { - "astro:config:setup": ({ updateConfig }) => { - updateConfig({ - vite: { - resolve: { - alias: { - "@astrojs/netlify/netlify-functions.js": fileURLToPath( - new URL("../../dist/netlify-functions.js", import.meta.url), - ), - }, - }, - }, - }); - }, - "astro:build:ssr": ({ entryPoints }) => { - if (entryPoints.size) { - setEntryPoints(entryPoints); - } - }, - }, - }; + return { + name: '@astrojs/netlify/test-integration', + hooks: { + 'astro:config:setup': ({ updateConfig }) => { + updateConfig({ + vite: { + resolve: { + alias: { + '@astrojs/netlify/netlify-functions.js': fileURLToPath( + new URL('../../dist/netlify-functions.js', import.meta.url) + ), + }, + }, + }, + }); + }, + 'astro:build:ssr': ({ entryPoints }) => { + if (entryPoints.size) { + setEntryPoints(entryPoints); + } + }, + }, + }; } diff --git a/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs b/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs index 6c8117f41..464c03a6c 100644 --- a/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs +++ b/packages/integrations/netlify/test/hosted/hosted-astro-project/astro.config.mjs @@ -1,8 +1,8 @@ -import netlify from "@astrojs/netlify"; -import { defineConfig } from "astro/config"; +import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; // https://astro.build/config export default defineConfig({ - output: "server", - adapter: netlify(), + output: 'server', + adapter: netlify(), }); diff --git a/packages/integrations/netlify/test/hosted/hosted.test.js b/packages/integrations/netlify/test/hosted/hosted.test.js index 3ec8cb3f3..2dc8c67ce 100644 --- a/packages/integrations/netlify/test/hosted/hosted.test.js +++ b/packages/integrations/netlify/test/hosted/hosted.test.js @@ -1,22 +1,21 @@ -import { expect } from "chai"; +import { expect } from 'chai'; -const NETLIFY_TEST_URL = "https://curious-boba-495d6d.netlify.app"; +const NETLIFY_TEST_URL = 'https://curious-boba-495d6d.netlify.app'; -describe("Hosted Netlify Tests", () => { - it("Image endpoint works", async () => { - const image = await fetch( - NETLIFY_TEST_URL + - "/_image?href=%2F_astro%2Fpenguin.e9c64733.png&w=300&f=webp", - ); +describe('Hosted Netlify Tests', () => { + it('Image endpoint works', async () => { + const image = await fetch( + NETLIFY_TEST_URL + '/_image?href=%2F_astro%2Fpenguin.e9c64733.png&w=300&f=webp' + ); - expect(image.status).to.equal(200); - }); + expect(image.status).to.equal(200); + }); - it("Server returns fresh content", async () => { - const responseOne = await fetch(NETLIFY_TEST_URL + "/time"); + it('Server returns fresh content', async () => { + const responseOne = await fetch(NETLIFY_TEST_URL + '/time'); - const responseTwo = await fetch(NETLIFY_TEST_URL + "/time"); + const responseTwo = await fetch(NETLIFY_TEST_URL + '/time'); - expect(responseOne.body).to.not.equal(responseTwo.body); - }); + expect(responseOne.body).to.not.equal(responseTwo.body); + }); }); diff --git a/packages/integrations/netlify/test/setup.js b/packages/integrations/netlify/test/setup.js index e7e74f42f..c53aa9894 100644 --- a/packages/integrations/netlify/test/setup.js +++ b/packages/integrations/netlify/test/setup.js @@ -1,12 +1,12 @@ -import { use } from "chai"; -import chaiJestSnapshot from "chai-jest-snapshot"; +import { use } from 'chai'; +import chaiJestSnapshot from 'chai-jest-snapshot'; use(chaiJestSnapshot); before(function () { - chaiJestSnapshot.resetSnapshotRegistry(); + chaiJestSnapshot.resetSnapshotRegistry(); }); beforeEach(function () { - chaiJestSnapshot.configureUsingMochaContext(this); + chaiJestSnapshot.configureUsingMochaContext(this); }); diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index 9eafce0f8..ae3ff1eb8 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,50 +1,50 @@ -import { expect } from "chai"; -import { loadFixture, testIntegration } from "./test-utils.js"; -import { netlifyStatic } from "../../dist/index.js"; +import { expect } from 'chai'; +import { loadFixture, testIntegration } from './test-utils.js'; +import { netlifyStatic } from '../../dist/index.js'; -describe("SSG - Redirects", () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ - let fixture; +describe('SSG - Redirects', () => { + /** @type {import('../../../astro/test/test-utils').Fixture} */ + let fixture; - before(async () => { - fixture = await loadFixture({ - root: new URL("./fixtures/redirects/", import.meta.url).toString(), - output: "static", - adapter: netlifyStatic(), - site: `http://example.com`, - integrations: [testIntegration()], - redirects: { - "/other": "/", - "/two": { - status: 302, - destination: "/", - }, - "/blog/[...slug]": "/team/articles/[...slug]", - }, - }); - await fixture.build(); - }); + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/redirects/', import.meta.url).toString(), + output: 'static', + adapter: netlifyStatic(), + site: `http://example.com`, + integrations: [testIntegration()], + redirects: { + '/other': '/', + '/two': { + status: 302, + destination: '/', + }, + '/blog/[...slug]': '/team/articles/[...slug]', + }, + }); + await fixture.build(); + }); - it("Creates a redirects file", async () => { - let redirects = await fixture.readFile("/_redirects"); - let parts = redirects.split(/\s+/); - expect(parts).to.deep.equal([ - "/two", - "/", - "302", - "/other", - "/", - "301", - "/nope", - "/", - "301", + it('Creates a redirects file', async () => { + let redirects = await fixture.readFile('/_redirects'); + let parts = redirects.split(/\s+/); + expect(parts).to.deep.equal([ + '/two', + '/', + '302', + '/other', + '/', + '301', + '/nope', + '/', + '301', - "/blog/*", - "/team/articles/*/index.html", - "301", - "/team/articles/*", - "/team/articles/*/index.html", - "200", - ]); - }); + '/blog/*', + '/team/articles/*/index.html', + '301', + '/team/articles/*', + '/team/articles/*/index.html', + '200', + ]); + }); }); diff --git a/packages/integrations/netlify/test/static/test-utils.js b/packages/integrations/netlify/test/static/test-utils.js index dfabfb002..44fcf84e0 100644 --- a/packages/integrations/netlify/test/static/test-utils.js +++ b/packages/integrations/netlify/test/static/test-utils.js @@ -1,29 +1,29 @@ // @ts-check -import { fileURLToPath } from "node:url"; +import { fileURLToPath } from 'node:url'; -export * from "../test-utils.js"; +export * from '../test-utils.js'; /** * * @returns {import('astro').AstroIntegration} */ export function testIntegration() { - return { - name: "@astrojs/netlify/test-integration", - hooks: { - "astro:config:setup": ({ updateConfig }) => { - updateConfig({ - vite: { - resolve: { - alias: { - "@astrojs/netlify/netlify-functions.js": fileURLToPath( - new URL("../../dist/netlify-functions.js", import.meta.url), - ), - }, - }, - }, - }); - }, - }, - }; + return { + name: '@astrojs/netlify/test-integration', + hooks: { + 'astro:config:setup': ({ updateConfig }) => { + updateConfig({ + vite: { + resolve: { + alias: { + '@astrojs/netlify/netlify-functions.js': fileURLToPath( + new URL('../../dist/netlify-functions.js', import.meta.url) + ), + }, + }, + }, + }); + }, + }, + }; } diff --git a/packages/integrations/netlify/test/test-utils.js b/packages/integrations/netlify/test/test-utils.js index 6da676180..7c012e86a 100644 --- a/packages/integrations/netlify/test/test-utils.js +++ b/packages/integrations/netlify/test/test-utils.js @@ -1,12 +1,12 @@ -import { execa } from "execa"; +import { execa } from 'execa'; /** Returns a process running the Astro CLI. */ export function cli(/** @type {string[]} */ ...args) { - const spawned = execa("npx", ["astro", ...args], { - env: { ASTRO_TELEMETRY_DISABLED: true }, - }); + const spawned = execa('npx', ['astro', ...args], { + env: { ASTRO_TELEMETRY_DISABLED: true }, + }); - spawned.stdout.setEncoding("utf8"); + spawned.stdout.setEncoding('utf8'); - return spawned; + return spawned; } -- cgit v1.2.3 From d6f5cd071de14390d0584b69da19fbf418646fd1 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 30 Nov 2023 15:37:20 -0500 Subject: chore: use biome to sort imports (#77) --- packages/integrations/netlify/src/integration-functions.ts | 2 +- packages/integrations/netlify/src/netlify-functions.ts | 2 +- packages/integrations/netlify/src/shared.ts | 6 +++--- packages/integrations/netlify/test/functions/404.test.js | 2 +- packages/integrations/netlify/test/functions/base.test.js | 2 +- .../integrations/netlify/test/functions/base64-response.test.js | 2 +- packages/integrations/netlify/test/functions/builders.test.js | 2 +- packages/integrations/netlify/test/functions/cookies.test.js | 2 +- packages/integrations/netlify/test/functions/dynamic-route.test.js | 4 ++-- .../integrations/netlify/test/functions/edge-middleware.test.js | 4 ++-- .../netlify/test/functions/fixtures/404/astro.config.mjs | 2 +- .../netlify/test/functions/fixtures/base/astro.config.mjs | 2 +- .../test/functions/fixtures/base64-response/astro.config.mjs | 2 +- .../netlify/test/functions/fixtures/builders/astro.config.mjs | 2 +- .../netlify/test/functions/fixtures/cookies/astro.config.mjs | 2 +- .../netlify/test/functions/fixtures/dynamic-route/astro.config.mjs | 2 +- .../fixtures/middleware-with-handler-file/astro.config.mjs | 2 +- .../fixtures/middleware-without-handler-file/astro.config.mjs | 2 +- .../netlify/test/functions/fixtures/redirects/astro.config.mjs | 2 +- .../netlify/test/functions/fixtures/split-support/astro.config.mjs | 2 +- packages/integrations/netlify/test/functions/prerender.test.js | 2 +- packages/integrations/netlify/test/functions/redirects.test.js | 2 +- packages/integrations/netlify/test/functions/split-support.test.js | 2 +- packages/integrations/netlify/test/static/redirects.test.js | 2 +- 24 files changed, 28 insertions(+), 28 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts index 2dd095ef6..8812a8e89 100644 --- a/packages/integrations/netlify/src/integration-functions.ts +++ b/packages/integrations/netlify/src/integration-functions.ts @@ -1,7 +1,7 @@ -import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; import { writeFile } from 'node:fs/promises'; import { extname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; +import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; import { generateEdgeMiddleware } from './middleware.js'; import type { Args } from './netlify-functions.js'; import { createRedirects } from './shared.js'; diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts index 8c051d9f6..9c9d8ad3a 100644 --- a/packages/integrations/netlify/src/netlify-functions.ts +++ b/packages/integrations/netlify/src/netlify-functions.ts @@ -1,4 +1,4 @@ -import { builder, type Handler } from '@netlify/functions'; +import { type Handler, builder } from '@netlify/functions'; import type { SSRManifest } from 'astro'; import { App } from 'astro/app'; import { applyPolyfills } from 'astro/app/node'; diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts index 175b9d04f..fca3d5f0c 100644 --- a/packages/integrations/netlify/src/shared.ts +++ b/packages/integrations/netlify/src/shared.ts @@ -1,9 +1,9 @@ -import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; -import type { AstroConfig, RouteData } from 'astro'; -import esbuild from 'esbuild'; import fs from 'node:fs'; import npath from 'node:path'; import { fileURLToPath } from 'node:url'; +import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; +import type { AstroConfig, RouteData } from 'astro'; +import esbuild from 'esbuild'; export const DENO_SHIM = `globalThis.process = { argv: [], diff --git a/packages/integrations/netlify/test/functions/404.test.js b/packages/integrations/netlify/test/functions/404.test.js index 071e3f057..7a5103e37 100644 --- a/packages/integrations/netlify/test/functions/404.test.js +++ b/packages/integrations/netlify/test/functions/404.test.js @@ -1,7 +1,7 @@ +import { fileURLToPath } from 'url'; import { expect } from 'chai'; import fs from 'fs/promises'; import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; const root = new URL('./fixtures/404/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/base.test.js b/packages/integrations/netlify/test/functions/base.test.js index c614b9afd..afb3fe0b8 100644 --- a/packages/integrations/netlify/test/functions/base.test.js +++ b/packages/integrations/netlify/test/functions/base.test.js @@ -1,7 +1,7 @@ +import { fileURLToPath } from 'url'; import { expect } from 'chai'; import fs from 'fs/promises'; import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; const root = new URL('./fixtures/base/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/base64-response.test.js b/packages/integrations/netlify/test/functions/base64-response.test.js index f8f32edda..4ea36ade3 100644 --- a/packages/integrations/netlify/test/functions/base64-response.test.js +++ b/packages/integrations/netlify/test/functions/base64-response.test.js @@ -1,6 +1,6 @@ +import { fileURLToPath } from 'url'; import { expect } from 'chai'; import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; const root = new URL('./fixtures/base64-response/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/builders.test.js b/packages/integrations/netlify/test/functions/builders.test.js index e927527a1..b5b5c04c2 100644 --- a/packages/integrations/netlify/test/functions/builders.test.js +++ b/packages/integrations/netlify/test/functions/builders.test.js @@ -1,6 +1,6 @@ +import { fileURLToPath } from 'url'; import { expect } from 'chai'; import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; const root = new URL('./fixtures/builders/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index 328294d10..c183b34b3 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,6 +1,6 @@ +import { fileURLToPath } from 'url'; import { expect } from 'chai'; import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; const root = new URL('./fixtures/cookies/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/dynamic-route.test.js b/packages/integrations/netlify/test/functions/dynamic-route.test.js index 2e20454e6..d5616a925 100644 --- a/packages/integrations/netlify/test/functions/dynamic-route.test.js +++ b/packages/integrations/netlify/test/functions/dynamic-route.test.js @@ -1,7 +1,7 @@ -import { expect } from 'chai'; -import { cli } from './test-utils.js'; import { fileURLToPath } from 'url'; +import { expect } from 'chai'; import fs from 'fs/promises'; +import { cli } from './test-utils.js'; const root = new URL('./fixtures/dynamic-route/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index ea214f82a..0f5001c46 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,7 +1,7 @@ import { fileURLToPath } from 'url'; -import { cli } from './test-utils.js'; -import fs from 'fs/promises'; import { expect } from 'chai'; +import fs from 'fs/promises'; +import { cli } from './test-utils.js'; describe('Middleware', () => { it('with edge handle file, should successfully build the middleware', async () => { diff --git a/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs index d5f8922f9..933d0491e 100644 --- a/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs index 35116ffcb..120504360 100644 --- a/packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ base: "/test", diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs index db8774760..f79a23fb0 100644 --- a/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs index 752ce4bfb..e5339a765 100644 --- a/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs index dd3dac266..908422c34 100644 --- a/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs index d1d739289..3d9c59251 100644 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs index d1d739289..3d9c59251 100644 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs index d1d739289..3d9c59251 100644 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs index 3a69f76d4..1773ad73e 100644 --- a/packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/redirects/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'hybrid', diff --git a/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs index cf494e10b..22ac46ac6 100644 --- a/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', diff --git a/packages/integrations/netlify/test/functions/prerender.test.js b/packages/integrations/netlify/test/functions/prerender.test.js index 8acc5a519..59bc2495f 100644 --- a/packages/integrations/netlify/test/functions/prerender.test.js +++ b/packages/integrations/netlify/test/functions/prerender.test.js @@ -1,7 +1,7 @@ +import { fileURLToPath } from 'url'; import { expect } from 'chai'; import fs from 'fs/promises'; import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; const root = new URL('./fixtures/prerender/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index ce3042ed7..45b6140c3 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,7 +1,7 @@ +import { fileURLToPath } from 'url'; import { expect } from 'chai'; import fs from 'fs/promises'; import { cli } from './test-utils.js'; -import { fileURLToPath } from 'url'; const root = new URL('../functions/fixtures/redirects/', import.meta.url).toString(); diff --git a/packages/integrations/netlify/test/functions/split-support.test.js b/packages/integrations/netlify/test/functions/split-support.test.js index 894152c49..e3187adca 100644 --- a/packages/integrations/netlify/test/functions/split-support.test.js +++ b/packages/integrations/netlify/test/functions/split-support.test.js @@ -1,7 +1,7 @@ +import { loadFixture } from '@astrojs/test-utils'; import { expect } from 'chai'; import netlifyAdapter from '../../dist/index.js'; import { testIntegration } from './test-utils.js'; -import { loadFixture } from '@astrojs/test-utils'; describe('Split support', () => { /** @type {import('./test-utils').Fixture} */ diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index ae3ff1eb8..0c5751eb8 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { loadFixture, testIntegration } from './test-utils.js'; import { netlifyStatic } from '../../dist/index.js'; +import { loadFixture, testIntegration } from './test-utils.js'; describe('SSG - Redirects', () => { /** @type {import('../../../astro/test/test-utils').Fixture} */ -- cgit v1.2.3 From 94dcbfed0607d037c591001b5484de74661c90a2 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Sun, 17 Dec 2023 16:44:09 +0100 Subject: feat(netlify): Netlify Adapter v4 (#84) Co-authored-by: Matt Kane Co-authored-by: Jacklyn <70537879+jacklyn-net@users.noreply.github.com> Co-authored-by: Arsh <69170106+lilnasy@users.noreply.github.com> Co-authored-by: Emanuele Stoppa <602478+ematipico@users.noreply.github.com> Co-authored-by: Sarah Rainsberger --- packages/integrations/netlify/README.md | 209 +++++--------- packages/integrations/netlify/builders-types.d.ts | 9 - packages/integrations/netlify/package.json | 13 +- packages/integrations/netlify/src/env.d.ts | 1 - packages/integrations/netlify/src/functions.ts | 6 + packages/integrations/netlify/src/image-service.ts | 57 ++++ packages/integrations/netlify/src/index.ts | 313 ++++++++++++++++++++- .../netlify/src/integration-functions.ts | 151 ---------- .../integrations/netlify/src/integration-static.ts | 30 -- packages/integrations/netlify/src/middleware.ts | 75 ----- .../integrations/netlify/src/netlify-functions.ts | 225 --------------- packages/integrations/netlify/src/shared.ts | 114 -------- packages/integrations/netlify/src/ssr-function.ts | 56 ++++ packages/integrations/netlify/src/static.ts | 6 + packages/integrations/netlify/src/types.d.ts | 1 + .../netlify/test/functions/404.test.js | 18 -- .../netlify/test/functions/base.test.js | 21 -- .../netlify/test/functions/base64-response.test.js | 51 ---- .../netlify/test/functions/builders.test.js | 26 -- .../netlify/test/functions/cookies.test.js | 30 +- .../netlify/test/functions/dynamic-route.test.js | 24 -- .../netlify/test/functions/edge-middleware.test.js | 61 ++-- .../test/functions/fixtures/404/astro.config.mjs | 8 - .../test/functions/fixtures/404/package.json | 8 - .../test/functions/fixtures/404/src/env.d.ts | 1 - .../functions/fixtures/404/src/pages/404.astro | 11 - .../functions/fixtures/404/src/pages/index.astro | 11 - .../test/functions/fixtures/base/astro.config.mjs | 10 - .../test/functions/fixtures/base/package.json | 8 - .../test/functions/fixtures/base/src/env.d.ts | 1 - .../functions/fixtures/base/src/pages/index.astro | 11 - .../fixtures/base64-response/astro.config.mjs | 10 - .../fixtures/base64-response/package.json | 8 - .../fixtures/base64-response/src/env.d.ts | 1 - .../fixtures/base64-response/src/pages/font.js | 11 - .../fixtures/base64-response/src/pages/image.js | 11 - .../functions/fixtures/builders/astro.config.mjs | 10 - .../test/functions/fixtures/builders/package.json | 8 - .../test/functions/fixtures/builders/src/env.d.ts | 1 - .../fixtures/builders/src/pages/index.astro | 11 - .../functions/fixtures/cookies/astro.config.mjs | 4 +- .../fixtures/dynamic-route/astro.config.mjs | 10 - .../functions/fixtures/dynamic-route/package.json | 8 - .../functions/fixtures/dynamic-route/src/env.d.ts | 1 - .../dynamic-route/src/pages/pets/[cat].astro | 27 -- .../dynamic-route/src/pages/pets/[dog].astro | 27 -- .../dynamic-route/src/pages/pets/index.astro | 12 - .../dynamic-route/src/pages/products/[id].astro | 11 - .../middleware-with-handler-file/astro.config.mjs | 10 - .../middleware-with-handler-file/package.json | 8 - .../middleware-with-handler-file/src/env.d.ts | 1 - .../middleware-with-handler-file/src/middleware.ts | 5 - .../src/netlify-edge-middleware.js | 5 - .../src/pages/index.astro | 12 - .../astro.config.mjs | 10 - .../middleware-without-handler-file/package.json | 8 - .../middleware-without-handler-file/src/env.d.ts | 1 - .../src/middleware.ts | 5 - .../src/pages/index.astro | 12 - .../functions/fixtures/middleware/astro.config.mjs | 10 + .../functions/fixtures/middleware/package.json | 8 + .../functions/fixtures/middleware/src/env.d.ts | 1 + .../fixtures/middleware/src/middleware.ts | 5 + .../fixtures/middleware/src/pages/index.astro | 12 + .../fixtures/middleware/src/pages/prerender.astro | 13 + .../functions/fixtures/prerender/astro.config.mjs | 11 - .../test/functions/fixtures/prerender/package.json | 8 - .../test/functions/fixtures/prerender/src/env.d.ts | 1 - .../fixtures/prerender/src/pages/404.astro | 8 - .../fixtures/prerender/src/pages/index.astro | 8 - .../fixtures/prerender/src/pages/one.astro | 11 - .../fixtures/split-support/astro.config.mjs | 11 - .../functions/fixtures/split-support/package.json | 8 - .../fixtures/split-support/src/pages/blog.astro | 8 - .../fixtures/split-support/src/pages/index.astro | 8 - .../netlify/test/functions/prerender.test.js | 53 ---- .../netlify/test/functions/redirects.test.js | 33 +-- .../netlify/test/functions/redirects.test.js.snap | 14 +- .../netlify/test/functions/split-support.test.js | 64 ----- .../netlify/test/functions/test-utils.js | 34 --- .../static/fixtures/redirects/astro.config.mjs | 17 ++ .../test/static/fixtures/redirects/package.json | 8 + .../netlify/test/static/redirects.test.js | 33 +-- .../integrations/netlify/test/static/test-utils.js | 29 -- packages/integrations/netlify/tsconfig.json | 3 +- 85 files changed, 665 insertions(+), 1568 deletions(-) delete mode 100644 packages/integrations/netlify/builders-types.d.ts delete mode 100644 packages/integrations/netlify/src/env.d.ts create mode 100644 packages/integrations/netlify/src/functions.ts create mode 100644 packages/integrations/netlify/src/image-service.ts delete mode 100644 packages/integrations/netlify/src/integration-functions.ts delete mode 100644 packages/integrations/netlify/src/integration-static.ts delete mode 100644 packages/integrations/netlify/src/middleware.ts delete mode 100644 packages/integrations/netlify/src/netlify-functions.ts delete mode 100644 packages/integrations/netlify/src/shared.ts create mode 100644 packages/integrations/netlify/src/ssr-function.ts create mode 100644 packages/integrations/netlify/src/static.ts create mode 100644 packages/integrations/netlify/src/types.d.ts delete mode 100644 packages/integrations/netlify/test/functions/404.test.js delete mode 100644 packages/integrations/netlify/test/functions/base.test.js delete mode 100644 packages/integrations/netlify/test/functions/base64-response.test.js delete mode 100644 packages/integrations/netlify/test/functions/builders.test.js delete mode 100644 packages/integrations/netlify/test/functions/dynamic-route.test.js delete mode 100644 packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/404/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/404/src/pages/404.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/404/src/pages/index.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base/src/pages/index.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/font.js delete mode 100644 packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/image.js delete mode 100644 packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/builders/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/products/[id].astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/middleware.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/netlify-edge-middleware.js delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/pages/index.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/middleware.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/pages/index.astro create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware/astro.config.mjs create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware/package.json create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware/src/env.d.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware/src/middleware.ts create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/index.astro create mode 100644 packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/prerender.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts delete mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs delete mode 100644 packages/integrations/netlify/test/functions/fixtures/split-support/package.json delete mode 100644 packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/blog.astro delete mode 100644 packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/index.astro delete mode 100644 packages/integrations/netlify/test/functions/prerender.test.js delete mode 100644 packages/integrations/netlify/test/functions/split-support.test.js delete mode 100644 packages/integrations/netlify/test/functions/test-utils.js create mode 100644 packages/integrations/netlify/test/static/fixtures/redirects/astro.config.mjs create mode 100644 packages/integrations/netlify/test/static/fixtures/redirects/package.json delete mode 100644 packages/integrations/netlify/test/static/test-utils.js (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md index 03cc850ac..103af5e3d 100644 --- a/packages/integrations/netlify/README.md +++ b/packages/integrations/netlify/README.md @@ -1,13 +1,12 @@ # @astrojs/netlify -This adapter allows Astro to deploy your SSR site to [Netlify](https://www.netlify.com/). +This adapter allows Astro to deploy your [`hybrid` or `server` rendered site](https://docs.astro.build/en/core-concepts/rendering-modes/#on-demand-rendered) to [Netlify](https://www.netlify.com/). Learn how to deploy your Astro site in our [Netlify deployment guide](https://docs.astro.build/en/guides/deploy/netlify/). - [Why Astro Netlify](#why-astro-netlify) - [Installation](#installation) - [Usage](#usage) -- [Configuration](#configuration) - [Examples](#examples) - [Troubleshooting](#troubleshooting) - [Contributing](#contributing) @@ -17,13 +16,13 @@ Learn how to deploy your Astro site in our [Netlify deployment guide](https://do If you're using Astro as a static site builder—its behavior out of the box—you don't need an adapter. -If you wish to [use server-side rendering (SSR)](https://docs.astro.build/en/guides/server-side-rendering/), Astro requires an adapter that matches your deployment runtime. +If you wish to [use on-demand rendering, also known as server-side rendering (SSR)](https://docs.astro.build/en/guides/server-side-rendering/), Astro requires an adapter that matches your deployment runtime. [Netlify](https://www.netlify.com/) is a deployment platform that allows you to host your site by connecting directly to your GitHub repository. This adapter enhances the Astro build process to prepare your project for deployment through Netlify. ## Installation -Add the Netlify adapter to enable SSR in your Astro project with the following `astro add` command. This will install the adapter and make the appropriate changes to your `astro.config.mjs` file in one step. +Add the Netlify adapter with the following `astro add` command. This will install the adapter and make the appropriate changes to your `astro.config.mjs` file in one step. ```sh # Using NPM @@ -49,7 +48,7 @@ If you prefer to install the adapter manually instead, complete the following tw ```diff lang="js" // astro.config.mjs import { defineConfig } from 'astro/config'; - + import netlify from '@astrojs/netlify/functions'; + + import netlify from '@astrojs/netlify'; export default defineConfig({ + output: 'server', @@ -57,60 +56,59 @@ If you prefer to install the adapter manually instead, complete the following tw }); ``` -### Run middleware in Edge Functions +## Usage -When deploying to Netlify Functions, you can choose to use an Edge Function to run your Astro middleware. +[Read the full deployment guide here.](https://docs.astro.build/en/guides/deploy/netlify/) -To enable this, set the `edgeMiddleware` config option to `true`: +Follow the instructions to [build your site locally](https://docs.astro.build/en/guides/deploy/#building-your-site-locally). After building, you will have a `.netlify/` folder containing both [Netlify Functions](https://docs.netlify.com/functions/overview/) in the `.netlify/functions-internal/` folder and [Netlify Edge Functions](https://docs.netlify.com/edge-functions/overview/) in the`.netlify/edge-functions/` folder. -```diff lang="js" - // astro.config.mjs - import { defineConfig } from 'astro/config'; - import netlify from '@astrojs/netlify/functions'; - - export default defineConfig({ - output: 'server', - adapter: netlify({ -+ edgeMiddleware: true, - }), - }); +To deploy your site, install the [Netlify CLI](https://docs.netlify.com/cli/get-started/) and run: + +```sh +netlify deploy ``` -#### Pass edge context to your site +The [Netlify Blog post on Astro](https://www.netlify.com/blog/how-to-deploy-astro/) and the [Netlify Docs](https://docs.netlify.com/integrations/frameworks/astro/) provide more information on how to use this integration to deploy to Netlify. + -Netlify Edge Functions provide a [context object](https://docs.netlify.com/edge-functions/api/#netlify-specific-context-object) including metadata about the request, such as a user’s IP, geolocation data, and cookies. +### Accessing edge context from your site -To expose values from this context to your site, create a `netlify-edge-middleware.ts` (or `.js`) file in your project’s [source directory](https://docs.astro.build/en/reference/configuration-reference/#srcdir). This file must export a function that returns the data to add to [Astro’s `locals` object](https://docs.astro.build/en/reference/api-reference/#astrolocals), which is available in middleware and Astro routes. +Netlify Edge Functions provide a [context object](https://docs.netlify.com/edge-functions/api/#netlify-specific-context-object) that includes metadata about the request such as a user’s IP, geolocation data, and cookies. -In this example, `visitorCountry` and `hasEdgeMiddleware` would both be added to Astro’s `locals` object: +This can be accessed through the `Astro.locals.netlify.context` object: + +```astro +--- +const { geo: { city } } = Astro.locals.netlify.context +--- +

Hello there, friendly visitor from {city}!

+``` + +If you're using TypeScript, you can get proper typings by updating `src/env.d.ts` to use `NetlifyLocals`: ```ts -// src/netlify-edge-middleware.ts -import type { Context } from 'https://edge.netlify.com'; - -export default function ({ request, context }: { request: Request; context: Context }) { - // Return serializable data to add to Astro.locals - return { - visitorCountry: context.geo.country.name, - hasEdgeMiddleware: true, - }; +// src/env.d.ts +/// +/// + +type NetlifyLocals = import('@astrojs/netlify').NetlifyLocals + +declare namespace App { + interface Locals extends NetlifyLocals { + ... + } } ``` -> **Note** -> Netlify Edge Functions run in [a Deno environment](https://docs.netlify.com/edge-functions/api/#runtime-environment), so import statements in this file must use Deno’s URL syntax. - -`netlify-edge-middleware.ts` must provide a function as its default export. This function: +This is not available on prerendered pages. -- must return a JSON-serializable object, which cannot include types like `Map`, `function`, `Set`, etc. -- will always run first, before any other middleware and routes. -- cannot return a response or redirect. +### Running Astro middleware in Edge Functions -### Per-page functions +Any Astro middleware is applied to pre-rendered pages at build-time, and to on-demand-rendered pages at runtime. -The Netlify adapter builds to a single function by default. Astro 2.7 added support for splitting your build into separate entry points per page. If you use this configuration, the Netlify adapter will generate a separate function for each page. This can help reduce the size of each function so they are only bundling code used on that page. +To implement redirects, access control or custom response headers for pre-rendered pages, run your middleware on Netlify Edge Functions by enabling the `edgeMiddleware` option: -```js +```diff lang="js" // astro.config.mjs import { defineConfig } from 'astro/config'; import netlify from '@astrojs/netlify/functions'; @@ -118,12 +116,22 @@ import netlify from '@astrojs/netlify/functions'; export default defineConfig({ output: 'server', adapter: netlify({ - functionPerRoute: true, ++ edgeMiddleware: true, }), }); ``` -### Static sites +Configuring `edgeMiddleware: true` will deploy your middleware as an Edge Function, and run it on all routes - including pre-rendered pages. However, locals specified in the middleware won't be available to any pre-rendered pages, because they've already been fully-rendered at build-time. + +### Netlify Image CDN support + +This adapter uses the [Netlify Image CDN](https://docs.netlify.com/image-cdn/) to transform images on-the-fly without impacting build times. +It's implemented using an [Astro Image Service](https://docs.astro.build/en/reference/image-service-reference/) under the hood. + +> **Note** +> This adapter does not support the `image.domains` and `image.remotePatterns` config properties in your Astro config. To [specify remote paths for Netlify Image CDN](https://docs.netlify.com/image-cdn/overview/#remote-path), use the `remote_images` field in `netlify.toml`. + +### Static sites & Redirects For static sites you usually don't need an adapter. However, if you use `redirects` configuration in your Astro config, the Netlify adapter can be used to translate this to the proper `_redirects` format. @@ -146,117 +154,40 @@ Once you run `astro build` there will be a `dist/_redirects` file. Netlify will > **Note** > You can still include a `public/_redirects` file for manual redirects. Any redirects you specify in the redirects config are appended to the end of your own. -### On-demand Builders - -[Netlify On-demand Builders](https://docs.netlify.com/configure-builds/on-demand-builders/) are serverless functions used to generate web content as needed that’s automatically cached on Netlify’s Edge CDN. You can enable these functions using the [`builders` configuration](#builders). +### Caching Pages -By default, all pages will be rendered on first visit and the rendered result will be reused for every subsequent visit until you redeploy. To set a revalidation time, call the [`runtime.setBuildersTtl(ttl)` local](https://docs.astro.build/en/reference/api-reference/#astrolocals) with the duration (in seconds). - -The following example sets a revalidation time of 45, causing Netlify to store the rendered HTML for 45 seconds. - -```astro ---- -// src/pages/index.astro -import Layout from '../components/Layout.astro'; - -if (import.meta.env.PROD) { - Astro.locals.runtime.setBuildersTtl(45); -} ---- - - - {new Date(Date.now())} - -``` - -It is important to note that On-demand Builders ignore query params when checking for cached pages. For example, if `example.com/?x=y` is cached, it will be served for `example.com/?a=b` (different query params) and `example.com/` (no query params) as well. - -## Usage - -[Read the full deployment guide here.](https://docs.astro.build/en/guides/deploy/netlify/) +On-demand rendered pages without any dynamic content can be cached to improve performance and lower resource usage. +Enabling the `cacheOnDemandPages` option in the adapter will cache all server-rendered pages for up to one year: -After [performing a build](https://docs.astro.build/en/guides/deploy/#building-your-site-locally) the `netlify/` folder will contain [Netlify Functions](https://docs.netlify.com/functions/overview/) in the `netlify/functions/` folder. - -Now you can deploy. Install the [Netlify CLI](https://docs.netlify.com/cli/get-started/) and run: - -```sh -netlify deploy --build -``` - -The [Netlify Blog post on Astro](https://www.netlify.com/blog/how-to-deploy-astro/) and the [Netlify Documentation](https://docs.netlify.com/integrations/frameworks/astro/) provide more information on how to use this integration to deploy to Netlify. - -## Configuration - -To configure this adapter, pass an object to the `netlify()` function call in `astro.config.mjs` - there's only one possible configuration option: - -### dist - -We build to the `dist` directory at the base of your project. To change this, use the `dist` option: - -```js +```ts // astro.config.mjs -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; - export default defineConfig({ output: 'server', adapter: netlify({ - dist: new URL('./dist/', import.meta.url), + cacheOnDemandPages: true }), }); ``` -And then point to the dist in your `netlify.toml`: - -```toml -# netlify.toml -[functions] -directory = "dist/functions" -``` +This can be changed on a per-page basis by adding caching headers to your response: -### builders -You can enable On-demand Builders using the `builders` option: +```astro +--- +// src/pages/index.astro +import Layout from '../components/Layout.astro'; -```js -// astro.config.mjs -import { defineConfig } from 'astro/config'; -import netlify from '@astrojs/netlify/functions'; +Astro.response.headers.set('CDN-Cache-Control', "public, max-age=45, must-revalidate") +--- -export default defineConfig({ - output: 'server', - adapter: netlify({ - builders: true, - }), -}); + + {new Date()} + ``` -On-demand Builders are only available with the `@astrojs/netlify/functions` adapter and are not compatible with Edge Functions. - -### binaryMediaTypes - -> This option is only needed for the Functions adapter and is not needed for Edge Functions. - -Netlify Functions requires binary data in the `body` to be base64 encoded. The `@astrojs/netlify/functions` adapter handles this automatically based on the `Content-Type` header. - -We check for common mime types for audio, image, and video files. To include specific mime types that should be treated as binary data, include the `binaryMediaTypes` option with a list of binary mime types. - -```js -// src/pages/image.jpg.ts -import fs from 'node:fs'; - -export function GET() { - const buffer = fs.readFileSync('../image.jpg'); - - // Return the buffer directly, @astrojs/netlify will base64 encode the body - return new Response(buffer, { - status: 200, - headers: { - 'content-type': 'image/jpeg', - }, - }); -} -``` +With [fine-grained cache control](https://www.netlify.com/blog/swr-and-fine-grained-cache-control/), Netlify supports +standard caching headers like `CDN-Cache-Control` or `Vary`. +Refer to the docs to learn about implementing e.g. time to live (TTL) or stale while revalidate (SWR) caching: https://docs.netlify.com/platform/caching ## Examples diff --git a/packages/integrations/netlify/builders-types.d.ts b/packages/integrations/netlify/builders-types.d.ts deleted file mode 100644 index 7c778be4f..000000000 --- a/packages/integrations/netlify/builders-types.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -interface NetlifyLocals { - runtime: { - /** - * On-demand Builders support an optional time to live (TTL) pattern that allows you to set a fixed duration of time after which a cached builder response is invalidated. This allows you to force a refresh of a builder-generated response without a new deploy. - * @param ttl time to live, in seconds - */ - setBuildersTtl(ttl: number): void; - }; -} diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 468804173..18cf16d60 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -19,11 +19,10 @@ "homepage": "https://docs.astro.build/en/guides/integrations-guide/netlify/", "exports": { ".": "./dist/index.js", - "./functions": "./dist/integration-functions.js", - "./static": "./dist/integration-static.js", - "./netlify-functions.js": "./dist/netlify-functions.js", - "./edge-functions": "./dist/integration-edge-functions.js", - "./netlify-edge-functions.js": "./dist/netlify-edge-functions.js", + "./static": "./dist/static.js", + "./functions": "./dist/functions.js", + "./ssr-function.js": "./dist/ssr-function.js", + "./image-service.js": "./dist/image-service.js", "./package.json": "./package.json" }, "files": [ @@ -32,8 +31,8 @@ "scripts": { "build": "tsc", "test-fn": "mocha --exit --timeout 20000 --file \"./test/setup.js\" test/functions/", - "test-edge": "deno test --allow-run --allow-read --allow-net --allow-env --allow-write ./test/edge-functions/", - "test": "pnpm test-fn", + "test-static": "mocha --exit --timeout 20000 --file \"./test/setup.js\" test/static/", + "test": "pnpm test-fn && pnpm test-static", "test:hosted": "mocha --exit --timeout 30000 test/hosted" }, "dependencies": { diff --git a/packages/integrations/netlify/src/env.d.ts b/packages/integrations/netlify/src/env.d.ts deleted file mode 100644 index f964fe0cf..000000000 --- a/packages/integrations/netlify/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/packages/integrations/netlify/src/functions.ts b/packages/integrations/netlify/src/functions.ts new file mode 100644 index 000000000..7a84087af --- /dev/null +++ b/packages/integrations/netlify/src/functions.ts @@ -0,0 +1,6 @@ +import netlifyIntegration, { type NetlifyIntegrationConfig } from "./index.js" + +export default function functionsIntegration(config: NetlifyIntegrationConfig) { + console.warn("The @astrojs/netlify/functions import is deprecated and will be removed in a future release. Please use @astrojs/netlify instead.") + return netlifyIntegration(config) +} \ No newline at end of file diff --git a/packages/integrations/netlify/src/image-service.ts b/packages/integrations/netlify/src/image-service.ts new file mode 100644 index 000000000..385f6996f --- /dev/null +++ b/packages/integrations/netlify/src/image-service.ts @@ -0,0 +1,57 @@ +import type { ExternalImageService, ImageMetadata } from 'astro'; +import { AstroError } from 'astro/errors'; +import { baseService } from 'astro/assets' + +const SUPPORTED_FORMATS = ['avif', 'jpg', 'png', 'webp']; +const QUALITY_NAMES: Record = { low: 25, mid: 50, high: 90, max: 100 }; + +export function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata { + return typeof src === 'object'; +} + +function removeLeadingForwardSlash(path: string) { + return path.startsWith('/') ? path.substring(1) : path; +} + +const service: ExternalImageService = { + getURL(options) { + const query = new URLSearchParams(); + + const fileSrc = isESMImportedImage(options.src) + ? removeLeadingForwardSlash(options.src.src) + : options.src; + + query.set('url', fileSrc); + + if (options.format) query.set('fm', options.format); + if (options.width) query.set('w', '' + options.width); + if (options.height) query.set('h', '' + options.height); + if (options.quality) query.set('q', '' + options.quality); + + return '/.netlify/images?' + query; + }, + getHTMLAttributes: baseService.getHTMLAttributes, + getSrcSet: baseService.getSrcSet, + validateOptions(options) { + if (options.format && !SUPPORTED_FORMATS.includes(options.format)) { + throw new AstroError( + `Unsupported image format "${options.format}"`, + `Use one of ${SUPPORTED_FORMATS.join(', ')} instead.` + ); + } + + if (options.quality) { + options.quality = + typeof options.quality === 'string' ? QUALITY_NAMES[options.quality] : options.quality; + if (options.quality < 1 || options.quality > 100) { + throw new AstroError( + `Invalid quality for picture "${options.src}"`, + `Quality needs to be between 1 and 100.` + ); + } + } + return options; + }, +}; + +export default service; diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index a374020f9..09451a2da 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -1,2 +1,311 @@ -export { netlifyFunctions as default, netlifyFunctions } from './integration-functions.js'; -export { netlifyStatic } from './integration-static.js'; +import type { AstroConfig, AstroIntegration, RouteData } from 'astro'; +import { writeFile, mkdir, appendFile, rm } from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { build } from 'esbuild'; +import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; +import { version as packageVersion } from '../package.json'; +import type { Context } from '@netlify/functions'; +import { AstroError } from 'astro/errors'; +import type { IncomingMessage } from 'http'; + +export interface NetlifyLocals { + netlify: { + context: Context; + }; +} + +const isStaticRedirect = (route: RouteData) => + route.type === 'redirect' && (route.redirect || route.redirectRoute); + +const clearDirectory = (dir: URL) => rm(dir, { recursive: true }).catch(() => {}); + +export interface NetlifyIntegrationConfig { + /** + * If enabled, On-Demand-Rendered pages are cached for up to a year. + * This is useful for pages that are not updated often, like a blog post, + * but that you have too many of to pre-render at build time. + * + * You can override this behavior on a per-page basis + * by setting the `Cache-Control`, `CDN-Cache-Control` or `Netlify-CDN-Cache-Control` header + * from within the Page: + * + * ```astro + * // src/pages/cached-clock.astro + * Astro.response.headers.set('CDN-Cache-Control', "public, max-age=45, must-revalidate"); + * --- + *

{Date.now()}

+ * ``` + */ + cacheOnDemandPages?: boolean; + + /** + * If disabled, Middleware is applied to prerendered pages at build-time, and to on-demand-rendered pages at runtime. + * Only disable when your Middleware does not need to run on prerendered pages. + * If you use Middleware to implement authentication, redirects or similar things, you should should likely enabled it. + * + * If enabled, Astro Middleware is deployed as an Edge Function and applies to all routes. + * Caveat: Locals set in Middleware are not applied to prerendered pages, because they've been rendered at build-time and are served from the CDN. + * + * @default disabled + */ + edgeMiddleware?: boolean; +} + +export default function netlifyIntegration( + integrationConfig?: NetlifyIntegrationConfig +): AstroIntegration { + const isRunningInNetlify = Boolean( + process.env.NETLIFY || process.env.NETLIFY_LOCAL || process.env.NETLIFY_DEV + ); + + let _config: AstroConfig; + let outDir: URL; + let rootDir: URL; + let astroMiddlewareEntryPoint: URL | undefined = undefined; + + const ssrOutputDir = () => new URL('./.netlify/functions-internal/ssr/', rootDir); + const middlewareOutputDir = () => new URL('.netlify/edge-functions/middleware/', rootDir); + + const cleanFunctions = async () => + await Promise.all([clearDirectory(middlewareOutputDir()), clearDirectory(ssrOutputDir())]); + + async function writeRedirects(routes: RouteData[], dir: URL) { + const fallback = _config.output === 'static' ? '/.netlify/static' : '/.netlify/functions/ssr'; + const redirects = createRedirectsFromAstroRoutes({ + config: _config, + dir, + routeToDynamicTargetMap: new Map( + routes + .filter(isStaticRedirect) // all other routes are handled by SSR + .map((route) => { + // this is needed to support redirects to dynamic routes + // on static. not sure why this is needed, but it works. + route.distURL ??= route.redirectRoute?.distURL; + + return [route, fallback]; + }) + ), + }); + + if (!redirects.empty()) { + await appendFile(new URL('_redirects', outDir), '\n' + redirects.print() + '\n'); + } + } + + async function writeSSRFunction() { + await writeFile( + new URL('./ssr.mjs', ssrOutputDir()), + ` + import createSSRHandler from './entry.mjs'; + export default createSSRHandler(${JSON.stringify({ + cacheOnDemandPages: Boolean(integrationConfig?.cacheOnDemandPages), + })}); + export const config = { name: "Astro SSR", generator: "@astrojs/netlify@${packageVersion}", path: "/*", preferStatic: true }; + ` + ); + } + + async function writeMiddleware(entrypoint: URL) { + await mkdir(middlewareOutputDir(), { recursive: true }); + await writeFile( + new URL('./entry.mjs', middlewareOutputDir()), + ` + import { onRequest } from "${fileURLToPath(entrypoint).replaceAll('\\', '/')}"; + import { createContext, trySerializeLocals } from 'astro/middleware'; + + export default async (request, context) => { + const ctx = createContext({ + request, + params: {} + }); + ctx.locals = { netlify: { context } } + const next = () => { + const { netlify, ...otherLocals } = ctx.locals; + request.headers.set("x-astro-locals", trySerializeLocals(otherLocals)); + return context.next(); + }; + + return onRequest(ctx, next); + } + + export const config = { + name: "Astro Middleware", + generator: "@astrojs/netlify@${packageVersion}", + path: "/*", excludedPath: ["/_astro/*", "/.netlify/images/*"] + }; + ` + ); + + // taking over bundling, because Netlify bundling trips over NPM modules + await build({ + entryPoints: [fileURLToPath(new URL('./entry.mjs', middlewareOutputDir()))], + target: 'es2022', + platform: 'neutral', + outfile: fileURLToPath(new URL('./middleware.mjs', middlewareOutputDir())), + allowOverwrite: true, + format: 'esm', + bundle: true, + minify: false, + }); + } + + function getLocalDevNetlifyContext(req: IncomingMessage): Context { + const isHttps = req.headers['x-forwarded-proto'] === 'https'; + const parseBase64JSON = (header: string): T | undefined => { + if (typeof req.headers[header] === 'string') { + try { + return JSON.parse(Buffer.from(req.headers[header] as string, 'base64').toString('utf8')); + } catch {} + } + }; + + const context: Context = { + account: parseBase64JSON('x-nf-account-info') ?? { + id: 'mock-netlify-account-id', + }, + deploy: { + id: + typeof req.headers['x-nf-deploy-id'] === 'string' + ? req.headers['x-nf-deploy-id'] + : 'mock-netlify-deploy-id', + }, + site: parseBase64JSON('x-nf-site-info') ?? { + id: 'mock-netlify-site-id', + name: 'mock-netlify-site.netlify.app', + url: `${isHttps ? 'https' : 'http'}://localhost:${isRunningInNetlify ? 8888 : 4321}`, + }, + geo: parseBase64JSON('x-nf-geo') ?? { + city: 'Mock City', + country: { code: 'mock', name: 'Mock Country' }, + subdivision: { code: 'SD', name: 'Mock Subdivision' }, + + // @ts-expect-error: these are smhw missing from the Netlify types - fix is on the way + timezone: 'UTC', + longitude: 0, + latitude: 0, + }, + ip: + typeof req.headers['x-nf-client-connection-ip'] === 'string' + ? req.headers['x-nf-client-connection-ip'] + : req.socket.remoteAddress ?? '127.0.0.1', + server: { + region: 'local-dev', + }, + requestId: + typeof req.headers['x-nf-request-id'] === 'string' + ? req.headers['x-nf-request-id'] + : 'mock-netlify-request-id', + get cookies(): never { + throw new Error('Please use Astro.cookies instead.'); + }, + json: (input) => Response.json(input), + log: console.log, + next: () => { + throw new Error('`context.next` is not implemented for serverless functions'); + }, + get params(): never { + throw new Error("context.params don't contain any usable content in Astro."); + }, + rewrite() { + throw new Error('context.rewrite is not available in Astro.'); + }, + }; + + return context; + } + + return { + name: '@astrojs/netlify', + hooks: { + 'astro:config:setup': async ({ config, updateConfig }) => { + rootDir = config.root; + await cleanFunctions(); + + outDir = new URL('./dist/', rootDir); + + updateConfig({ + outDir, + build: { + redirects: false, + client: outDir, + server: ssrOutputDir(), + }, + vite: { + server: { + watch: { + ignored: [fileURLToPath(new URL('./.netlify/**', rootDir))], + }, + }, + }, + image: { + service: { + entrypoint: isRunningInNetlify ? '@astrojs/netlify/image-service.js' : undefined, + }, + }, + }); + }, + 'astro:config:done': ({ config, setAdapter }) => { + rootDir = config.root; + _config = config; + + if (config.image.domains.length || config.image.remotePatterns.length) { + throw new AstroError( + "config.image.domains and config.image.remotePatterns aren't supported by the Netlify adapter.", + 'See https://github.com/withastro/adapters/tree/main/packages/netlify#image-cdn for more.' + ); + } + + setAdapter({ + name: '@astrojs/netlify', + serverEntrypoint: '@astrojs/netlify/ssr-function.js', + exports: ['default'], + adapterFeatures: { + functionPerRoute: false, + edgeMiddleware: integrationConfig?.edgeMiddleware ?? false, + }, + supportedAstroFeatures: { + hybridOutput: 'stable', + staticOutput: 'stable', + serverOutput: 'stable', + assets: { + // keeping this as experimental at least until Netlify Image CDN is out of beta + supportKind: 'experimental', + // still using Netlify Image CDN instead + isSharpCompatible: true, + isSquooshCompatible: true, + }, + }, + }); + }, + 'astro:build:ssr': async ({ middlewareEntryPoint }) => { + astroMiddlewareEntryPoint = middlewareEntryPoint; + }, + 'astro:build:done': async ({ routes, dir, logger }) => { + await writeRedirects(routes, dir); + logger.info('Emitted _redirects'); + + if (_config.output !== 'static') { + await writeSSRFunction(); + logger.info('Generated SSR Function'); + } + + if (astroMiddlewareEntryPoint) { + await writeMiddleware(astroMiddlewareEntryPoint); + logger.info('Generated Middleware Edge Function'); + } + }, + + // local dev + 'astro:server:setup': async ({ server }) => { + server.middlewares.use((req, res, next) => { + const locals = Symbol.for('astro.locals'); + Reflect.set(req, locals, { + ...Reflect.get(req, locals), + netlify: { context: getLocalDevNetlifyContext(req) }, + }); + next(); + }); + }, + }, + }; +} diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts deleted file mode 100644 index 8812a8e89..000000000 --- a/packages/integrations/netlify/src/integration-functions.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { writeFile } from 'node:fs/promises'; -import { extname, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import type { AstroAdapter, AstroConfig, AstroIntegration, RouteData } from 'astro'; -import { generateEdgeMiddleware } from './middleware.js'; -import type { Args } from './netlify-functions.js'; -import { createRedirects } from './shared.js'; - -export const NETLIFY_EDGE_MIDDLEWARE_FILE = 'netlify-edge-middleware'; -export const ASTRO_LOCALS_HEADER = 'x-astro-locals'; - -export function getAdapter({ functionPerRoute, edgeMiddleware, ...args }: Args): AstroAdapter { - return { - name: '@astrojs/netlify/functions', - serverEntrypoint: '@astrojs/netlify/netlify-functions.js', - exports: ['handler'], - args, - adapterFeatures: { - functionPerRoute, - edgeMiddleware, - }, - supportedAstroFeatures: { - hybridOutput: 'stable', - staticOutput: 'stable', - serverOutput: 'stable', - assets: { - supportKind: 'stable', - isSharpCompatible: true, - isSquooshCompatible: true, - }, - }, - }; -} - -interface NetlifyFunctionsOptions { - dist?: URL; - builders?: boolean; - binaryMediaTypes?: string[]; - edgeMiddleware?: boolean; - functionPerRoute?: boolean; -} - -function netlifyFunctions({ - dist, - builders, - binaryMediaTypes, - functionPerRoute = false, - edgeMiddleware = false, -}: NetlifyFunctionsOptions = {}): AstroIntegration { - let _config: AstroConfig; - let _entryPoints: Map; - let ssrEntryFile: string; - let _middlewareEntryPoint: URL; - return { - name: '@astrojs/netlify', - hooks: { - 'astro:config:setup': ({ config, updateConfig }) => { - const outDir = dist ?? new URL('./dist/', config.root); - updateConfig({ - outDir, - build: { - redirects: false, - client: outDir, - server: new URL('./.netlify/functions-internal/', config.root), - }, - }); - }, - 'astro:build:ssr': async ({ entryPoints, middlewareEntryPoint }) => { - if (middlewareEntryPoint) { - _middlewareEntryPoint = middlewareEntryPoint; - } - _entryPoints = entryPoints; - }, - 'astro:config:done': ({ config, setAdapter }) => { - setAdapter( - getAdapter({ - binaryMediaTypes, - builders, - functionPerRoute, - edgeMiddleware, - }) - ); - _config = config; - ssrEntryFile = config.build.serverEntry.replace(/\.m?js/, ''); - - if (config.output === 'static') { - // eslint-disable-next-line no-console - console.warn( - `[@astrojs/netlify] \`output: "server"\` or \`output: "hybrid"\` is required to use this adapter.` - ); - // eslint-disable-next-line no-console - console.warn( - `[@astrojs/netlify] Otherwise, this adapter is not required to deploy a static site to Netlify.` - ); - } - }, - 'astro:build:done': async ({ routes, dir }) => { - const functionsConfig = { - version: 1, - config: { - nodeModuleFormat: 'esm', - }, - }; - const functionsConfigPath = join(fileURLToPath(_config.build.server), 'entry.json'); - await writeFile(functionsConfigPath, JSON.stringify(functionsConfig)); - - const type = builders ? 'builders' : 'functions'; - const kind = type ?? 'functions'; - - if (_entryPoints.size) { - const routeToDynamicTargetMap = new Map(); - for (const [route, entryFile] of _entryPoints) { - const wholeFileUrl = fileURLToPath(entryFile); - - const extension = extname(wholeFileUrl); - const relative = wholeFileUrl - .replace(fileURLToPath(_config.build.server), '') - .replace(extension, '') - .replaceAll('\\', '/'); - const dynamicTarget = `/.netlify/${kind}/${relative}`; - - routeToDynamicTargetMap.set(route, dynamicTarget); - } - await createRedirects(_config, routeToDynamicTargetMap, dir); - } else { - const dynamicTarget = `/.netlify/${kind}/${ssrEntryFile}`; - const map: [RouteData, string][] = routes.map((route) => { - return [route, dynamicTarget]; - }); - const routeToDynamicTargetMap = new Map(Array.from(map)); - - await createRedirects(_config, routeToDynamicTargetMap, dir); - } - if (_middlewareEntryPoint) { - const outPath = fileURLToPath(new URL('./.netlify/edge-functions/', _config.root)); - const netlifyEdgeMiddlewareHandlerPath = new URL( - NETLIFY_EDGE_MIDDLEWARE_FILE, - _config.srcDir - ); - await generateEdgeMiddleware( - _middlewareEntryPoint, - outPath, - netlifyEdgeMiddlewareHandlerPath - ); - } - }, - }, - }; -} - -export { netlifyFunctions as default, netlifyFunctions }; diff --git a/packages/integrations/netlify/src/integration-static.ts b/packages/integrations/netlify/src/integration-static.ts deleted file mode 100644 index af2849867..000000000 --- a/packages/integrations/netlify/src/integration-static.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { AstroIntegration, RouteData } from 'astro'; -import { createRedirects } from './shared.js'; - -export function netlifyStatic(): AstroIntegration { - let _config: any; - return { - name: '@astrojs/netlify', - hooks: { - 'astro:config:setup': ({ updateConfig }) => { - updateConfig({ - build: { - // Do not output HTML redirects because we are building a `_redirects` file. - redirects: false, - }, - }); - }, - 'astro:config:done': ({ config }) => { - _config = config; - }, - 'astro:build:done': async ({ dir, routes }) => { - const mappedRoutes: [RouteData, string][] = routes.map((route) => [ - route, - `/.netlify/static/`, - ]); - const routesToDynamicTargetMap = new Map(Array.from(mappedRoutes)); - await createRedirects(_config, routesToDynamicTargetMap, dir); - }, - }, - }; -} diff --git a/packages/integrations/netlify/src/middleware.ts b/packages/integrations/netlify/src/middleware.ts deleted file mode 100644 index 3c2f4f697..000000000 --- a/packages/integrations/netlify/src/middleware.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { existsSync } from 'node:fs'; -import { join } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { ASTRO_LOCALS_HEADER } from './integration-functions.js'; -import { DENO_SHIM } from './shared.js'; - -/** - * It generates a Netlify edge function. - * - */ -export async function generateEdgeMiddleware( - astroMiddlewareEntryPointPath: URL, - outPath: string, - netlifyEdgeMiddlewareHandlerPath: URL -): Promise { - const entryPointPathURLAsString = JSON.stringify( - fileURLToPath(astroMiddlewareEntryPointPath).replace(/\\/g, '/') - ); - - const code = edgeMiddlewareTemplate(entryPointPathURLAsString, netlifyEdgeMiddlewareHandlerPath); - const bundledFilePath = join(outPath, 'edgeMiddleware.js'); - const esbuild = await import('esbuild'); - await esbuild.build({ - stdin: { - contents: code, - resolveDir: process.cwd(), - }, - target: 'es2020', - platform: 'browser', - outfile: bundledFilePath, - allowOverwrite: true, - format: 'esm', - bundle: true, - minify: false, - banner: { - js: DENO_SHIM, - }, - }); - return pathToFileURL(bundledFilePath); -} - -function edgeMiddlewareTemplate(middlewarePath: string, netlifyEdgeMiddlewareHandlerPath: URL) { - const filePathEdgeMiddleware = fileURLToPath(netlifyEdgeMiddlewareHandlerPath); - let handlerTemplateImport = ''; - let handlerTemplateCall = '{}'; - if (existsSync(filePathEdgeMiddleware + '.js') || existsSync(filePathEdgeMiddleware + '.ts')) { - const stringified = JSON.stringify(filePathEdgeMiddleware.replace(/\\/g, '/')); - handlerTemplateImport = `import handler from ${stringified}`; - handlerTemplateCall = `handler({ request, context })`; - } else { - } - return ` - ${handlerTemplateImport} -import { onRequest } from ${middlewarePath}; -import { createContext, trySerializeLocals } from 'astro/middleware'; -export default async function middleware(request, context) { - const url = new URL(request.url); - const ctx = createContext({ - request, - params: {} - }); - ctx.locals = ${handlerTemplateCall}; - const next = async () => { - request.headers.set(${JSON.stringify(ASTRO_LOCALS_HEADER)}, trySerializeLocals(ctx.locals)); - return await context.next(); - }; - - return onRequest(ctx, next); -} - -export const config = { - path: "/*" -} -`; -} diff --git a/packages/integrations/netlify/src/netlify-functions.ts b/packages/integrations/netlify/src/netlify-functions.ts deleted file mode 100644 index 9c9d8ad3a..000000000 --- a/packages/integrations/netlify/src/netlify-functions.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { type Handler, builder } from '@netlify/functions'; -import type { SSRManifest } from 'astro'; -import { App } from 'astro/app'; -import { applyPolyfills } from 'astro/app/node'; -import { ASTRO_LOCALS_HEADER } from './integration-functions.js'; - -applyPolyfills(); - -export interface Args { - builders?: boolean; - binaryMediaTypes?: string[]; - edgeMiddleware: boolean; - functionPerRoute: boolean; -} - -function parseContentType(header?: string) { - return header?.split(';')[0] ?? ''; -} - -const clientAddressSymbol = Symbol.for('astro.clientAddress'); - -export const createExports = (manifest: SSRManifest, args: Args) => { - const app = new App(manifest); - - const builders = args.builders ?? false; - const binaryMediaTypes = args.binaryMediaTypes ?? []; - const knownBinaryMediaTypes = new Set([ - 'audio/3gpp', - 'audio/3gpp2', - 'audio/aac', - 'audio/midi', - 'audio/mpeg', - 'audio/ogg', - 'audio/opus', - 'audio/wav', - 'audio/webm', - 'audio/x-midi', - 'image/avif', - 'image/bmp', - 'image/gif', - 'image/vnd.microsoft.icon', - 'image/heif', - 'image/jpeg', - 'image/png', - 'image/svg+xml', - 'image/tiff', - 'image/webp', - 'video/3gpp', - 'video/3gpp2', - 'video/mp2t', - 'video/mp4', - 'video/mpeg', - 'video/ogg', - 'video/x-msvideo', - 'video/webm', - ...binaryMediaTypes, - ]); - - const myHandler: Handler = async (event) => { - const { httpMethod, headers, rawUrl, body: requestBody, isBase64Encoded } = event; - const init: RequestInit = { - method: httpMethod, - headers: new Headers(headers as any), - }; - // Attach the event body the request, with proper encoding. - if (httpMethod !== 'GET' && httpMethod !== 'HEAD') { - const encoding = isBase64Encoded ? 'base64' : 'utf-8'; - init.body = - typeof requestBody === 'string' ? Buffer.from(requestBody, encoding) : requestBody; - } - - const request = new Request(rawUrl, init); - - const routeData = app.match(request); - const ip = headers['x-nf-client-connection-ip']; - Reflect.set(request, clientAddressSymbol, ip); - - let locals: Record = {}; - - if (request.headers.has(ASTRO_LOCALS_HEADER)) { - let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER); - if (localsAsString) { - locals = JSON.parse(localsAsString); - } - } - - let responseTtl = undefined; - - locals.runtime = builders - ? { - setBuildersTtl(ttl: number) { - responseTtl = ttl; - }, - } - : {}; - - const response: Response = await app.render(request, routeData, locals); - const responseHeaders = Object.fromEntries(response.headers.entries()); - - const responseContentType = parseContentType(responseHeaders['content-type']); - const responseIsBase64Encoded = knownBinaryMediaTypes.has(responseContentType); - - let responseBody: string; - if (responseIsBase64Encoded) { - const ab = await response.arrayBuffer(); - responseBody = Buffer.from(ab).toString('base64'); - } else { - responseBody = await response.text(); - } - - const fnResponse: any = { - statusCode: response.status, - headers: responseHeaders, - body: responseBody, - isBase64Encoded: responseIsBase64Encoded, - ttl: responseTtl, - }; - - const cookies = response.headers.get('set-cookie'); - if (cookies) { - fnResponse.multiValueHeaders = { - 'set-cookie': Array.isArray(cookies) ? cookies : splitCookiesString(cookies), - }; - } - - // 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; - }; - - const handler = builders ? builder(myHandler) : myHandler; - - return { handler }; -}; - -/* - From: https://github.com/nfriedly/set-cookie-parser/blob/5cae030d8ef0f80eec58459e3583d43a07b984cb/lib/set-cookie.js#L144 - Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas - that are within a single set-cookie field-value, such as in the Expires portion. - This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2 - Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128 - React Native's fetch does this for *every* header, including set-cookie. - Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25 - Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation -*/ -function splitCookiesString(cookiesString: string): string[] { - if (Array.isArray(cookiesString)) { - return cookiesString; - } - if (typeof cookiesString !== 'string') { - return []; - } - - let cookiesStrings = []; - let pos = 0; - let start; - let ch; - let lastComma; - let nextStart; - let cookiesSeparatorFound; - - function skipWhitespace() { - while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) { - pos += 1; - } - return pos < cookiesString.length; - } - - function notSpecialChar() { - ch = cookiesString.charAt(pos); - - return ch !== '=' && ch !== ';' && ch !== ','; - } - - while (pos < cookiesString.length) { - start = pos; - cookiesSeparatorFound = false; - - while (skipWhitespace()) { - ch = cookiesString.charAt(pos); - if (ch === ',') { - // ',' is a cookie separator if we have later first '=', not ';' or ',' - lastComma = pos; - pos += 1; - - skipWhitespace(); - nextStart = pos; - - while (pos < cookiesString.length && notSpecialChar()) { - pos += 1; - } - - // currently special character - if (pos < cookiesString.length && cookiesString.charAt(pos) === '=') { - // we found cookies separator - cookiesSeparatorFound = true; - // pos is inside the next cookie, so back up and return it. - pos = nextStart; - cookiesStrings.push(cookiesString.substring(start, lastComma)); - start = pos; - } else { - // in param ',' or param separator ';', - // we continue from that comma - pos = lastComma + 1; - } - } else { - pos += 1; - } - } - - if (!cookiesSeparatorFound || pos >= cookiesString.length) { - cookiesStrings.push(cookiesString.substring(start, cookiesString.length)); - } - } - - return cookiesStrings; -} diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts deleted file mode 100644 index fca3d5f0c..000000000 --- a/packages/integrations/netlify/src/shared.ts +++ /dev/null @@ -1,114 +0,0 @@ -import fs from 'node:fs'; -import npath from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; -import type { AstroConfig, RouteData } from 'astro'; -import esbuild from 'esbuild'; - -export const DENO_SHIM = `globalThis.process = { - argv: [], - env: Deno.env.toObject(), -};`; - -export interface NetlifyEdgeFunctionsOptions { - dist?: URL; -} - -export interface NetlifyEdgeFunctionManifestFunctionPath { - function: string; - path: string; -} - -export interface NetlifyEdgeFunctionManifestFunctionPattern { - function: string; - pattern: string; -} - -export type NetlifyEdgeFunctionManifestFunction = - | NetlifyEdgeFunctionManifestFunctionPath - | NetlifyEdgeFunctionManifestFunctionPattern; - -export interface NetlifyEdgeFunctionManifest { - functions: NetlifyEdgeFunctionManifestFunction[]; - version: 1; -} - -export async function createRedirects( - config: AstroConfig, - routeToDynamicTargetMap: Map, - dir: URL -) { - const _redirectsURL = new URL('./_redirects', dir); - - const _redirects = createRedirectsFromAstroRoutes({ - config, - routeToDynamicTargetMap, - dir, - }); - const content = _redirects.print(); - - // Always use appendFile() because the redirects file could already exist, - // e.g. due to a `/public/_redirects` file that got copied to the output dir. - // If the file does not exist yet, appendFile() automatically creates it. - await fs.promises.appendFile(_redirectsURL, content, 'utf-8'); -} - -export async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: URL) { - const functions: NetlifyEdgeFunctionManifestFunction[] = []; - for (const route of routes) { - if (route.pathname) { - functions.push({ - function: entryFile, - path: route.pathname, - }); - } else { - functions.push({ - function: entryFile, - // Make route pattern serializable to match expected - // Netlify Edge validation format. Mirrors Netlify's own edge bundler: - // https://github.com/netlify/edge-bundler/blob/main/src/manifest.ts#L34 - pattern: route.pattern.source.replace(/\\\//g, '/').toString(), - }); - } - } - - const manifest: NetlifyEdgeFunctionManifest = { - functions, - version: 1, - }; - - const baseDir = new URL('./.netlify/edge-functions/', dir); - await fs.promises.mkdir(baseDir, { recursive: true }); - - const manifestURL = new URL('./manifest.json', baseDir); - const _manifest = JSON.stringify(manifest, null, ' '); - await fs.promises.writeFile(manifestURL, _manifest, 'utf-8'); -} - -export async function bundleServerEntry(entryUrl: URL, serverUrl?: URL, vite?: any | undefined) { - const pth = fileURLToPath(entryUrl); - await esbuild.build({ - target: 'es2020', - platform: 'browser', - entryPoints: [pth], - outfile: pth, - allowOverwrite: true, - format: 'esm', - bundle: true, - external: ['@astrojs/markdown-remark', 'astro/middleware'], - banner: { - js: DENO_SHIM, - }, - }); - - // Remove chunks, if they exist. Since we have bundled via esbuild these chunks are trash. - if (vite && serverUrl) { - try { - const chunkFileNames = - vite?.build?.rollupOptions?.output?.chunkFileNames ?? `chunks/chunk.[hash].mjs`; - const chunkPath = npath.dirname(chunkFileNames); - const chunksDirUrl = new URL(chunkPath + '/', serverUrl); - await fs.promises.rm(chunksDirUrl, { recursive: true, force: true }); - } catch {} - } -} diff --git a/packages/integrations/netlify/src/ssr-function.ts b/packages/integrations/netlify/src/ssr-function.ts new file mode 100644 index 000000000..c2b6ed14c --- /dev/null +++ b/packages/integrations/netlify/src/ssr-function.ts @@ -0,0 +1,56 @@ +import type { Context } from '@netlify/functions'; +import type { SSRManifest } from 'astro'; +import { App } from 'astro/app'; +import { applyPolyfills } from 'astro/app/node'; + +applyPolyfills(); + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface Args {} + +const clientAddressSymbol = Symbol.for('astro.clientAddress'); + +export const createExports = (manifest: SSRManifest, _args: Args) => { + const app = new App(manifest); + + function createHandler(integrationConfig: { cacheOnDemandPages: boolean }) { + return async function handler(request: Request, context: Context) { + const routeData = app.match(request); + Reflect.set(request, clientAddressSymbol, context.ip); + + let locals: Record = {}; + + if (request.headers.has('x-astro-locals')) { + locals = JSON.parse(request.headers.get('x-astro-locals')!); + } + + locals.netlify = { context }; + + const response = await app.render(request, routeData, locals); + + if (app.setCookieHeaders) { + for (const setCookieHeader of app.setCookieHeaders(response)) { + response.headers.append('Set-Cookie', setCookieHeader); + } + } + + if (integrationConfig.cacheOnDemandPages) { + // any user-provided Cache-Control headers take precedence + const hasCacheControl = [ + 'Cache-Control', + 'CDN-Cache-Control', + 'Netlify-CDN-Cache-Control', + ].some((header) => response.headers.has(header)); + + if (!hasCacheControl) { + // caches this page for up to a year + response.headers.append('CDN-Cache-Control', 'public, max-age=31536000, must-revalidate'); + } + } + + return response; + }; + } + + return { default: createHandler }; +}; diff --git a/packages/integrations/netlify/src/static.ts b/packages/integrations/netlify/src/static.ts new file mode 100644 index 000000000..4748f384a --- /dev/null +++ b/packages/integrations/netlify/src/static.ts @@ -0,0 +1,6 @@ +import netlifyIntegration from "./index.js" + +export default function staticIntegration() { + console.warn("The @astrojs/netlify/static import is deprecated and will be removed in a future release. Please use @astrojs/netlify instead.") + return netlifyIntegration() +} \ No newline at end of file diff --git a/packages/integrations/netlify/src/types.d.ts b/packages/integrations/netlify/src/types.d.ts new file mode 100644 index 000000000..0df35e9e9 --- /dev/null +++ b/packages/integrations/netlify/src/types.d.ts @@ -0,0 +1 @@ +declare module "*.json"; \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/404.test.js b/packages/integrations/netlify/test/functions/404.test.js deleted file mode 100644 index 7a5103e37..000000000 --- a/packages/integrations/netlify/test/functions/404.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { fileURLToPath } from 'url'; -import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; - -const root = new URL('./fixtures/404/', import.meta.url).toString(); - -describe('404 page', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); - - it('404 route is included in the redirect file', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - const expr = new RegExp('/* /.netlify/functions/entry 404'); - expect(redir).to.match(expr); - }); -}); diff --git a/packages/integrations/netlify/test/functions/base.test.js b/packages/integrations/netlify/test/functions/base.test.js deleted file mode 100644 index afb3fe0b8..000000000 --- a/packages/integrations/netlify/test/functions/base.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { fileURLToPath } from 'url'; -import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; - -const root = new URL('./fixtures/base/', import.meta.url).toString(); - -describe('Base', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); - - it('Path is prepended by base', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - const baseRouteIndex = redir.indexOf('/test/ /.netlify/functions/entry 200'); - const imageEndpoint = redir.indexOf('/test/_image /.netlify/functions/entry 200'); - - expect(baseRouteIndex).to.not.be.equal(-1); - expect(imageEndpoint).to.not.be.equal(-1); - }); -}); diff --git a/packages/integrations/netlify/test/functions/base64-response.test.js b/packages/integrations/netlify/test/functions/base64-response.test.js deleted file mode 100644 index 4ea36ade3..000000000 --- a/packages/integrations/netlify/test/functions/base64-response.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { fileURLToPath } from 'url'; -import { expect } from 'chai'; -import { cli } from './test-utils.js'; - -const root = new URL('./fixtures/base64-response/', import.meta.url).toString(); - -describe('Base64 Responses', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); - - it('Can return base64 encoded strings', async () => { - const entryURL = new URL( - './fixtures/base64-response/.netlify/functions-internal/entry.mjs', - import.meta.url - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: 'http://example.com/image', - body: '{}', - isBase64Encoded: false, - }); - expect(resp.statusCode, 'successful response').to.equal(200); - expect(resp.isBase64Encoded, 'includes isBase64Encoded flag').to.be.true; - - const buffer = Buffer.from(resp.body, 'base64'); - expect(buffer.toString(), 'decoded base64 string matches').to.equal('base64 test string'); - }); - - it('Can define custom binaryMediaTypes', async () => { - const entryURL = new URL( - './fixtures/base64-response/.netlify/functions-internal/entry.mjs', - import.meta.url - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: 'http://example.com/font', - body: '{}', - isBase64Encoded: false, - }); - expect(resp.statusCode, 'successful response').to.equal(200); - expect(resp.isBase64Encoded, 'includes isBase64Encoded flag').to.be.true; - - const buffer = Buffer.from(resp.body, 'base64'); - expect(buffer.toString(), 'decoded base64 string matches').to.equal('base64 test font'); - }); -}); diff --git a/packages/integrations/netlify/test/functions/builders.test.js b/packages/integrations/netlify/test/functions/builders.test.js deleted file mode 100644 index b5b5c04c2..000000000 --- a/packages/integrations/netlify/test/functions/builders.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { fileURLToPath } from 'url'; -import { expect } from 'chai'; -import { cli } from './test-utils.js'; - -const root = new URL('./fixtures/builders/', import.meta.url).toString(); - -describe('Builders', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); - - it('A route can set builders ttl', async () => { - const entryURL = new URL( - './fixtures/builders/.netlify/functions-internal/entry.mjs', - import.meta.url - ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: 'http://example.com/', - isBase64Encoded: false, - }); - expect(resp.ttl).to.equal(45); - }); -}); diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index c183b34b3..54f776499 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,31 +1,23 @@ -import { fileURLToPath } from 'url'; import { expect } from 'chai'; -import { cli } from './test-utils.js'; - -const root = new URL('./fixtures/cookies/', import.meta.url).toString(); +import { loadFixture } from "@astrojs/test-utils" describe('Cookies', () => { + let fixture; + before(async () => { - await cli('build', '--root', fileURLToPath(root)); + fixture = await loadFixture({ root: new URL('./fixtures/cookies/', import.meta.url) }); + await fixture.build(); }); it('Can set multiple', async () => { const entryURL = new URL( - './fixtures/cookies/.netlify/functions-internal/entry.mjs', + './fixtures/cookies/.netlify/functions-internal/ssr/ssr.mjs', import.meta.url ); - const { handler } = await import(entryURL); - const resp = await handler({ - httpMethod: 'POST', - headers: {}, - rawUrl: 'http://example.com/login', - body: '{}', - isBase64Encoded: false, - }); - expect(resp.statusCode).to.equal(301); - expect(resp.headers.location).to.equal('/'); - expect(resp.multiValueHeaders).to.be.deep.equal({ - 'set-cookie': ['foo=foo; HttpOnly', 'bar=bar; HttpOnly'], - }); + const { default: handler } = await import(entryURL); + const resp = await handler(new Request('http://example.com/login', { method: "POST", body: '{}' }), {}) + expect(resp.status).to.equal(301); + expect(resp.headers.get("location")).to.equal('/'); + expect(resp.headers.getSetCookie()).to.eql(['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); }); }); diff --git a/packages/integrations/netlify/test/functions/dynamic-route.test.js b/packages/integrations/netlify/test/functions/dynamic-route.test.js deleted file mode 100644 index d5616a925..000000000 --- a/packages/integrations/netlify/test/functions/dynamic-route.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { fileURLToPath } from 'url'; -import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; - -const root = new URL('./fixtures/dynamic-route/', import.meta.url).toString(); - -describe('Dynamic pages', () => { - before(async () => { - await cli('build', '--root', fileURLToPath(root)); - }); - - it('Dynamic pages are included in the redirects file', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - expect(redir).to.match(/\/products\/:id/); - }); - - it('Prerendered routes are also included using placeholder syntax', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - expect(redir).to.include('/pets/:cat /pets/:cat/index.html 200'); - expect(redir).to.include('/pets/:dog /pets/:dog/index.html 200'); - expect(redir).to.include('/pets /.netlify/functions/entry 200'); - }); -}); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 0f5001c46..412066d1d 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,26 +1,45 @@ -import { fileURLToPath } from 'url'; import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; +import { loadFixture } from "@astrojs/test-utils" describe('Middleware', () => { - it('with edge handle file, should successfully build the middleware', async () => { - const root = new URL('./fixtures/middleware-with-handler-file/', import.meta.url).toString(); - await cli('build', '--root', fileURLToPath(root)); - const contents = await fs.readFile( - new URL('./.netlify/edge-functions/edgeMiddleware.js', root), - 'utf-8' - ); - expect(contents.includes('"Hello world"')).to.be.true; - }); + const root = new URL('./fixtures/middleware/', import.meta.url) - it('without edge handle file, should successfully build the middleware', async () => { - const root = new URL('./fixtures/middleware-without-handler-file/', import.meta.url).toString(); - await cli('build', '--root', fileURLToPath(root)); - const contents = await fs.readFile( - new URL('./.netlify/edge-functions/edgeMiddleware.js', root), - 'utf-8' - ); - expect(contents.includes('"Hello world"')).to.be.false; - }); + describe("edgeMiddleware: false", () => { + let fixture + before(async () => { + process.env.EDGE_MIDDLEWARE = 'false'; + fixture = await loadFixture({ root }); + await fixture.build(); + }) + + it('emits no edge function', async () => { + expect(fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs')).to.be.false + }); + + it('applies middleware to static files at build-time', async () => { + // prerendered page has middleware applied at build time + const prerenderedPage = await fixture.readFile('prerender/index.html') + expect(prerenderedPage).to.contain("Middleware") + }); + }) + + + describe("edgeMiddleware: true", () => { + let fixture + before(async () => { + process.env.EDGE_MIDDLEWARE = 'true'; + fixture = await loadFixture({ root }); + await fixture.build(); + }) + + it('emits an edge function', async () => { + const contents = await fixture.readFile('../.netlify/edge-functions/middleware/middleware.mjs') + expect(contents.includes('"Hello world"')).to.be.false; + }) + + it('does not apply middleware during prerendering', async () => { + const prerenderedPage = await fixture.readFile('prerender/index.html') + expect(prerenderedPage).to.contain("") + }) + }) }); diff --git a/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs deleted file mode 100644 index 933d0491e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/404/astro.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - output: 'server', - adapter: netlify(), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/404/package.json b/packages/integrations/netlify/test/functions/fixtures/404/package.json deleted file mode 100644 index b08e71e06..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/404/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-fourohfour", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/404/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/404/src/pages/404.astro b/packages/integrations/netlify/test/functions/fixtures/404/src/pages/404.astro deleted file mode 100644 index b60b5e55a..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/404/src/pages/404.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- - ---- - - - Not found - - -

Not found

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/404/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/404/src/pages/index.astro deleted file mode 100644 index 5ed06d251..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/404/src/pages/index.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- - ---- - - - Testing - - -

Testing

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs deleted file mode 100644 index 120504360..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base/astro.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - base: "/test", - trailingSlash: "always", - output: 'server', - adapter: netlify(), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/base/package.json b/packages/integrations/netlify/test/functions/fixtures/base/package.json deleted file mode 100644 index 2b0b14125..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-base", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/base/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/base/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/base/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/base/src/pages/index.astro deleted file mode 100644 index 5ed06d251..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base/src/pages/index.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- - ---- - - - Testing - - -

Testing

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs deleted file mode 100644 index f79a23fb0..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base64-response/astro.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - output: 'server', - adapter: netlify({ - binaryMediaTypes: ['font/otf'], - }), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/package.json b/packages/integrations/netlify/test/functions/fixtures/base64-response/package.json deleted file mode 100644 index 1e4d64dd4..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base64-response/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-base64-response", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base64-response/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/font.js b/packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/font.js deleted file mode 100644 index abe2677f0..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/font.js +++ /dev/null @@ -1,11 +0,0 @@ - -export function GET() { - const buffer = Buffer.from('base64 test font', 'utf-8') - - return new Response(buffer, { - status: 200, - headers: { - 'Content-Type': 'font/otf' - } - }); -} diff --git a/packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/image.js b/packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/image.js deleted file mode 100644 index 3cd266481..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/base64-response/src/pages/image.js +++ /dev/null @@ -1,11 +0,0 @@ - -export function GET() { - const buffer = Buffer.from('base64 test string', 'utf-8') - - return new Response(buffer, { - status: 200, - headers: { - 'content-type': 'image/jpeg;foo=foo' - } - }); -} diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs deleted file mode 100644 index e5339a765..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/builders/astro.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - output: 'server', - adapter: netlify({ - builders: true, - }), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/package.json b/packages/integrations/netlify/test/functions/fixtures/builders/package.json deleted file mode 100644 index 998de0393..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/builders/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-builders", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/builders/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro deleted file mode 100644 index ab8853785..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/builders/src/pages/index.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -Astro.locals.runtime.setBuildersTtl(45) ---- - - - Astro on Netlify - - -

{new Date(Date.now())}

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs index 908422c34..933d0491e 100644 --- a/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs +++ b/packages/integrations/netlify/test/functions/fixtures/cookies/astro.config.mjs @@ -3,8 +3,6 @@ import { defineConfig } from 'astro/config'; export default defineConfig({ output: 'server', - adapter: netlify({ - - }), + adapter: netlify(), site: `http://example.com`, }); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs deleted file mode 100644 index 3d9c59251..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/astro.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - output: 'server', - adapter: netlify({ - edgeMiddleware: true, - }), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json deleted file mode 100644 index 040987227..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-dynamic", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro deleted file mode 100644 index f86ee6ca9..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -export const prerender = true - -export function getStaticPaths() { - return [ - { - params: {cat: 'cat1'}, - props: {cat: 'cat1'} - }, - { - params: {cat: 'cat2'}, - props: {cat: 'cat2'} - }, - { - params: {cat: 'cat3'}, - props: {cat: 'cat3'} - }, - ]; -} - -const { cat } = Astro.props; - ---- - -
Good cat, {cat}!
- -back diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro deleted file mode 100644 index 0f3300f04..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -export const prerender = true - -export function getStaticPaths() { - return [ - { - params: {dog: 'dog1'}, - props: {dog: 'dog1'} - }, - { - params: {dog: 'dog2'}, - props: {dog: 'dog2'} - }, - { - params: {dog: 'dog3'}, - props: {dog: 'dog3'} - }, - ]; -} - -const { dog } = Astro.props; - ---- - -
Good dog, {dog}!
- -back diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro deleted file mode 100644 index d1423f8ef..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - Astro - - -

Astro

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/products/[id].astro b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/products/[id].astro deleted file mode 100644 index 5ed06d251..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/products/[id].astro +++ /dev/null @@ -1,11 +0,0 @@ ---- - ---- - - - Testing - - -

Testing

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs deleted file mode 100644 index 3d9c59251..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/astro.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - output: 'server', - adapter: netlify({ - edgeMiddleware: true, - }), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json deleted file mode 100644 index 3fe59b19b..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-middleware-with-handler-file", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/middleware.ts b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/middleware.ts deleted file mode 100644 index 8cab418c1..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/middleware.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const onRequest = (context, next) => { - context.locals.title = 'Middleware'; - - return next(); -}; diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/netlify-edge-middleware.js b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/netlify-edge-middleware.js deleted file mode 100644 index bf69edb3e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/netlify-edge-middleware.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function ({ request, context }) { - return { - title: 'Hello world', - }; -} diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/pages/index.astro deleted file mode 100644 index d97f70698..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-with-handler-file/src/pages/index.astro +++ /dev/null @@ -1,12 +0,0 @@ ---- -const title = Astro.locals.title; ---- - - - - {title} - - -

{title}

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs deleted file mode 100644 index 3d9c59251..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/astro.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - output: 'server', - adapter: netlify({ - edgeMiddleware: true, - }), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json deleted file mode 100644 index 8c66b24b6..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-middleware-without-handler-file", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/middleware.ts b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/middleware.ts deleted file mode 100644 index 8cab418c1..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/middleware.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const onRequest = (context, next) => { - context.locals.title = 'Middleware'; - - return next(); -}; diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/pages/index.astro deleted file mode 100644 index d97f70698..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/middleware-without-handler-file/src/pages/index.astro +++ /dev/null @@ -1,12 +0,0 @@ ---- -const title = Astro.locals.title; ---- - - - - {title} - - -

{title}

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/middleware/astro.config.mjs new file mode 100644 index 000000000..aefd9805c --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware/astro.config.mjs @@ -0,0 +1,10 @@ +import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + edgeMiddleware: process.env.EDGE_MIDDLEWARE === 'true', + }), + site: `http://example.com`, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware/package.json b/packages/integrations/netlify/test/functions/fixtures/middleware/package.json new file mode 100644 index 000000000..8c66b24b6 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/netlify-middleware-without-handler-file", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/middleware/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware/src/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware/src/middleware.ts b/packages/integrations/netlify/test/functions/fixtures/middleware/src/middleware.ts new file mode 100644 index 000000000..8cab418c1 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware/src/middleware.ts @@ -0,0 +1,5 @@ +export const onRequest = (context, next) => { + context.locals.title = 'Middleware'; + + return next(); +}; diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/index.astro new file mode 100644 index 000000000..d97f70698 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/index.astro @@ -0,0 +1,12 @@ +--- +const title = Astro.locals.title; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/prerender.astro b/packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/prerender.astro new file mode 100644 index 000000000..f0314c053 --- /dev/null +++ b/packages/integrations/netlify/test/functions/fixtures/middleware/src/pages/prerender.astro @@ -0,0 +1,13 @@ +--- +export const prerender = true; +const title = Astro.locals.title; +--- + + + + {title} + + +

{title}

+ + diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs deleted file mode 100644 index e9e11092c..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/prerender/astro.config.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'astro/config'; -import netlifyAdapter from '../../../../dist/index.js'; - - -export default defineConfig({ - output: process.env.ASTRO_OUTPUT || 'server', - adapter: netlifyAdapter({ - dist: new URL('./dist/', import.meta.url), - }), - site: `http://example.com`, -}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/package.json b/packages/integrations/netlify/test/functions/fixtures/prerender/package.json deleted file mode 100644 index 78c1d7d82..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/prerender/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-prerender", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts b/packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts deleted file mode 100644 index 8c34fb45e..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/prerender/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro deleted file mode 100644 index ad5d44aa2..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro +++ /dev/null @@ -1,8 +0,0 @@ - - - Testing - - -

testing

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro deleted file mode 100644 index 852d00b7b..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro +++ /dev/null @@ -1,8 +0,0 @@ - - - Blog - - -

Blog

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro deleted file mode 100644 index 342e98cfa..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -export const prerender = import.meta.env.PRERENDER; ---- - - - Testing - - -

testing

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs b/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs deleted file mode 100644 index 22ac46ac6..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/split-support/astro.config.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import netlify from '@astrojs/netlify'; -import { defineConfig } from 'astro/config'; - -export default defineConfig({ - output: 'server', - adapter: netlify({ - dist: new URL('./fixtures/split-support/dist/', import.meta.url), - functionPerRoute: true, - }), - site: `http://example.com`, -}); diff --git a/packages/integrations/netlify/test/functions/fixtures/split-support/package.json b/packages/integrations/netlify/test/functions/fixtures/split-support/package.json deleted file mode 100644 index 806de870f..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/split-support/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "@test/netlify-split-support", - "version": "0.0.0", - "private": true, - "dependencies": { - "@astrojs/netlify": "workspace:" - } -} diff --git a/packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/blog.astro b/packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/blog.astro deleted file mode 100644 index 248c2218b..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/blog.astro +++ /dev/null @@ -1,8 +0,0 @@ - - - Testing - - -

testing

- - diff --git a/packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/index.astro deleted file mode 100644 index 852d00b7b..000000000 --- a/packages/integrations/netlify/test/functions/fixtures/split-support/src/pages/index.astro +++ /dev/null @@ -1,8 +0,0 @@ - - - Blog - - -

Blog

- - diff --git a/packages/integrations/netlify/test/functions/prerender.test.js b/packages/integrations/netlify/test/functions/prerender.test.js deleted file mode 100644 index 59bc2495f..000000000 --- a/packages/integrations/netlify/test/functions/prerender.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { fileURLToPath } from 'url'; -import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; - -const root = new URL('./fixtures/prerender/', import.meta.url).toString(); - -describe('Mixed Prerendering with SSR', () => { - before(async () => { - process.env.PRERENDER = true; - await cli('build', '--root', fileURLToPath(root)); - }); - - after(() => { - delete process.env.PRERENDER; - }); - - it('Wildcard 404 is sorted last', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - const baseRouteIndex = redir.indexOf('/ /.netlify/functions/entry 200'); - const oneRouteIndex = redir.indexOf('/one /one/index.html 200'); - const fourOhFourWildCardIndex = redir.indexOf('/* /.netlify/functions/entry 404'); - - expect(oneRouteIndex).to.not.be.equal(-1); - expect(fourOhFourWildCardIndex).to.be.greaterThan(baseRouteIndex); - expect(fourOhFourWildCardIndex).to.be.greaterThan(oneRouteIndex); - }); -}); - -describe('Mixed Hybrid rendering with SSR', () => { - before(async () => { - process.env.PRERENDER = false; - process.env.ASTRO_OUTPUT = 'hybrid'; - await cli('build', '--root', fileURLToPath(root)); - }); - - after(() => { - delete process.env.PRERENDER; - }); - - it('outputs a correct redirect file', async () => { - const redir = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); - const baseRouteIndex = redir.indexOf('/one /.netlify/functions/entry 200'); - const rootRouteIndex = redir.indexOf('/ /index.html 200'); - const fourOhFourIndex = redir.indexOf('/404 /404.html 200'); - const imageEndpoint = redir.indexOf('/_image /.netlify/functions/entry 200'); - - expect(rootRouteIndex).to.not.be.equal(-1); - expect(baseRouteIndex).to.not.be.equal(-1); - expect(fourOhFourIndex).to.not.be.equal(-1); - expect(imageEndpoint).to.not.be.equal(-1); - }); -}); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index 45b6140c3..c2a705b0b 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,40 +1,23 @@ -import { fileURLToPath } from 'url'; import { expect } from 'chai'; -import fs from 'fs/promises'; -import { cli } from './test-utils.js'; +import { loadFixture } from "@astrojs/test-utils" -const root = new URL('../functions/fixtures/redirects/', import.meta.url).toString(); +describe('SSR - Redirects', () => { + let fixture; -describe('SSG - Redirects', () => { before(async () => { - await cli('build', '--root', fileURLToPath(root)); + fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); + await fixture.build(); }); it('Creates a redirects file', async () => { - let redirects = await fs.readFile(new URL('./dist/_redirects', root), 'utf-8'); + let redirects = await fixture.readFile('./_redirects'); let parts = redirects.split(/\s+/); expect(parts).to.deep.equal([ + '', '/other', '/', '301', - // This uses the dynamic Astro.redirect, so we don't know that it's a redirect - // until runtime. This is correct! - '/nope', - '/.netlify/functions/entry', - '200', - '/', - '/.netlify/functions/entry', - '200', - - // Image endpoint - '/_image', - '/.netlify/functions/entry', - '200', - - // A real route - '/team/articles/*', - '/.netlify/functions/entry', - '200', + '', ]); expect(redirects).to.matchSnapshot(); }); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js.snap b/packages/integrations/netlify/test/functions/redirects.test.js.snap index 54095f052..b781001e4 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js.snap +++ b/packages/integrations/netlify/test/functions/redirects.test.js.snap @@ -1,9 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SSG - Redirects Creates a redirects file 1`] = ` -"/other / 301 -/nope /.netlify/functions/entry 200 -/ /.netlify/functions/entry 200 -/_image /.netlify/functions/entry 200 -/team/articles/* /.netlify/functions/entry 200" +" +/other / 301 +" +`; + +exports[`SSR - Redirects Creates a redirects file 1`] = ` +" +/other / 301 +" `; diff --git a/packages/integrations/netlify/test/functions/split-support.test.js b/packages/integrations/netlify/test/functions/split-support.test.js deleted file mode 100644 index e3187adca..000000000 --- a/packages/integrations/netlify/test/functions/split-support.test.js +++ /dev/null @@ -1,64 +0,0 @@ -import { loadFixture } from '@astrojs/test-utils'; -import { expect } from 'chai'; -import netlifyAdapter from '../../dist/index.js'; -import { testIntegration } from './test-utils.js'; - -describe('Split support', () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; - let _entryPoints; - - before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/split-support/', import.meta.url).toString(), - output: 'server', - adapter: netlifyAdapter({ - dist: new URL('./fixtures/split-support/dist/', import.meta.url), - functionPerRoute: true, - }), - site: `http://example.com`, - integrations: [ - testIntegration({ - setEntryPoints(ep) { - _entryPoints = ep; - }, - }), - ], - }); - await fixture.build(); - }); - - it('outputs a correct redirect file', async () => { - const redir = await fixture.readFile('/_redirects'); - const lines = redir.split(/[\r\n]+/); - expect(lines.length).to.equal(3); - - expect(lines[0].includes('/blog')).to.be.true; - expect(lines[0].includes('blog.astro')).to.be.true; - expect(lines[0].includes('200')).to.be.true; - expect(lines[1].includes('/')).to.be.true; - expect(lines[1].includes('index.astro')).to.be.true; - expect(lines[1].includes('200')).to.be.true; - }); - - describe('Should create multiple functions', () => { - it('and hit 200', async () => { - if (_entryPoints) { - for (const [routeData, filePath] of _entryPoints) { - if (routeData.route !== '/_image') { - const { handler } = await import(filePath.toString()); - const resp = await handler({ - httpMethod: 'GET', - headers: {}, - rawUrl: `http://example.com${routeData.route}`, - body: '{}', - }); - expect(resp.statusCode).to.equal(200); - } - } - } else { - expect(false).to.be.true; - } - }); - }); -}); diff --git a/packages/integrations/netlify/test/functions/test-utils.js b/packages/integrations/netlify/test/functions/test-utils.js deleted file mode 100644 index c977af42e..000000000 --- a/packages/integrations/netlify/test/functions/test-utils.js +++ /dev/null @@ -1,34 +0,0 @@ -// @ts-check -import { fileURLToPath } from 'node:url'; - -export * from '../test-utils.js'; - -/** - * - * @returns {import('astro').AstroIntegration} - */ -export function testIntegration({ setEntryPoints } = {}) { - return { - name: '@astrojs/netlify/test-integration', - hooks: { - 'astro:config:setup': ({ updateConfig }) => { - updateConfig({ - vite: { - resolve: { - alias: { - '@astrojs/netlify/netlify-functions.js': fileURLToPath( - new URL('../../dist/netlify-functions.js', import.meta.url) - ), - }, - }, - }, - }); - }, - 'astro:build:ssr': ({ entryPoints }) => { - if (entryPoints.size) { - setEntryPoints(entryPoints); - } - }, - }, - }; -} diff --git a/packages/integrations/netlify/test/static/fixtures/redirects/astro.config.mjs b/packages/integrations/netlify/test/static/fixtures/redirects/astro.config.mjs new file mode 100644 index 000000000..c2a53c274 --- /dev/null +++ b/packages/integrations/netlify/test/static/fixtures/redirects/astro.config.mjs @@ -0,0 +1,17 @@ +import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'static', + adapter: netlify(), + site: `http://example.com`, + site: `http://example.com`, + redirects: { + '/other': '/', + '/two': { + status: 302, + destination: '/', + }, + '/blog/[...slug]': '/team/articles/[...slug]', + }, +}); \ No newline at end of file diff --git a/packages/integrations/netlify/test/static/fixtures/redirects/package.json b/packages/integrations/netlify/test/static/fixtures/redirects/package.json new file mode 100644 index 000000000..3e543bf35 --- /dev/null +++ b/packages/integrations/netlify/test/static/fixtures/redirects/package.json @@ -0,0 +1,8 @@ +{ + "name": "@test/netlify-static-redirects", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/netlify": "workspace:" + } +} diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index 0c5751eb8..6e9adc00a 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,50 +1,33 @@ import { expect } from 'chai'; -import { netlifyStatic } from '../../dist/index.js'; -import { loadFixture, testIntegration } from './test-utils.js'; +import { loadFixture } from "@astrojs/test-utils" describe('SSG - Redirects', () => { - /** @type {import('../../../astro/test/test-utils').Fixture} */ let fixture; before(async () => { - fixture = await loadFixture({ - root: new URL('./fixtures/redirects/', import.meta.url).toString(), - output: 'static', - adapter: netlifyStatic(), - site: `http://example.com`, - integrations: [testIntegration()], - redirects: { - '/other': '/', - '/two': { - status: 302, - destination: '/', - }, - '/blog/[...slug]': '/team/articles/[...slug]', - }, - }); + fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); await fixture.build(); }); it('Creates a redirects file', async () => { - let redirects = await fixture.readFile('/_redirects'); + const redirects = await fixture.readFile('./_redirects'); let parts = redirects.split(/\s+/); expect(parts).to.deep.equal([ + '', + '/two', '/', '302', + '/other', '/', '301', - '/nope', - '/', - '301', '/blog/*', '/team/articles/*/index.html', '301', - '/team/articles/*', - '/team/articles/*/index.html', - '200', + + '', ]); }); }); diff --git a/packages/integrations/netlify/test/static/test-utils.js b/packages/integrations/netlify/test/static/test-utils.js deleted file mode 100644 index 44fcf84e0..000000000 --- a/packages/integrations/netlify/test/static/test-utils.js +++ /dev/null @@ -1,29 +0,0 @@ -// @ts-check -import { fileURLToPath } from 'node:url'; - -export * from '../test-utils.js'; - -/** - * - * @returns {import('astro').AstroIntegration} - */ -export function testIntegration() { - return { - name: '@astrojs/netlify/test-integration', - hooks: { - 'astro:config:setup': ({ updateConfig }) => { - updateConfig({ - vite: { - resolve: { - alias: { - '@astrojs/netlify/netlify-functions.js': fileURLToPath( - new URL('../../dist/netlify-functions.js', import.meta.url) - ), - }, - }, - }, - }); - }, - }, - }; -} diff --git a/packages/integrations/netlify/tsconfig.json b/packages/integrations/netlify/tsconfig.json index 1a4df9edb..18443cddf 100644 --- a/packages/integrations/netlify/tsconfig.json +++ b/packages/integrations/netlify/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { - "outDir": "./dist", - "typeRoots": ["node_modules/@types", "node_modules/@netlify"] + "outDir": "./dist" } } -- cgit v1.2.3 From a07737d545a8fbe6232c1174d36bd916884765ae Mon Sep 17 00:00:00 2001 From: alexanderniebuhr Date: Sun, 17 Dec 2023 15:44:42 +0000 Subject: [ci] format --- packages/integrations/netlify/README.md | 11 +++--- packages/integrations/netlify/src/functions.ts | 10 +++--- packages/integrations/netlify/src/image-service.ts | 2 +- packages/integrations/netlify/src/index.ts | 10 +++--- packages/integrations/netlify/src/static.ts | 10 +++--- packages/integrations/netlify/src/types.d.ts | 2 +- .../netlify/test/functions/cookies.test.js | 9 +++-- .../netlify/test/functions/edge-middleware.test.js | 42 +++++++++++----------- .../netlify/test/functions/redirects.test.js | 10 ++---- .../netlify/test/static/redirects.test.js | 4 +-- 10 files changed, 57 insertions(+), 53 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/README.md b/packages/integrations/netlify/README.md index 103af5e3d..5618850e6 100644 --- a/packages/integrations/netlify/README.md +++ b/packages/integrations/netlify/README.md @@ -70,7 +70,6 @@ netlify deploy The [Netlify Blog post on Astro](https://www.netlify.com/blog/how-to-deploy-astro/) and the [Netlify Docs](https://docs.netlify.com/integrations/frameworks/astro/) provide more information on how to use this integration to deploy to Netlify. - ### Accessing edge context from your site Netlify Edge Functions provide a [context object](https://docs.netlify.com/edge-functions/api/#netlify-specific-context-object) that includes metadata about the request such as a user’s IP, geolocation data, and cookies. @@ -79,8 +78,11 @@ This can be accessed through the `Astro.locals.netlify.context` object: ```astro --- -const { geo: { city } } = Astro.locals.netlify.context +const { + geo: { city }, +} = Astro.locals.netlify.context; --- +

Hello there, friendly visitor from {city}!

``` @@ -164,20 +166,19 @@ Enabling the `cacheOnDemandPages` option in the adapter will cache all server-re export default defineConfig({ output: 'server', adapter: netlify({ - cacheOnDemandPages: true + cacheOnDemandPages: true, }), }); ``` This can be changed on a per-page basis by adding caching headers to your response: - ```astro --- // src/pages/index.astro import Layout from '../components/Layout.astro'; -Astro.response.headers.set('CDN-Cache-Control', "public, max-age=45, must-revalidate") +Astro.response.headers.set('CDN-Cache-Control', 'public, max-age=45, must-revalidate'); --- diff --git a/packages/integrations/netlify/src/functions.ts b/packages/integrations/netlify/src/functions.ts index 7a84087af..f2ffd392a 100644 --- a/packages/integrations/netlify/src/functions.ts +++ b/packages/integrations/netlify/src/functions.ts @@ -1,6 +1,8 @@ -import netlifyIntegration, { type NetlifyIntegrationConfig } from "./index.js" +import netlifyIntegration, { type NetlifyIntegrationConfig } from './index.js'; export default function functionsIntegration(config: NetlifyIntegrationConfig) { - console.warn("The @astrojs/netlify/functions import is deprecated and will be removed in a future release. Please use @astrojs/netlify instead.") - return netlifyIntegration(config) -} \ No newline at end of file + console.warn( + 'The @astrojs/netlify/functions import is deprecated and will be removed in a future release. Please use @astrojs/netlify instead.' + ); + return netlifyIntegration(config); +} diff --git a/packages/integrations/netlify/src/image-service.ts b/packages/integrations/netlify/src/image-service.ts index 385f6996f..a313d7418 100644 --- a/packages/integrations/netlify/src/image-service.ts +++ b/packages/integrations/netlify/src/image-service.ts @@ -1,6 +1,6 @@ import type { ExternalImageService, ImageMetadata } from 'astro'; +import { baseService } from 'astro/assets'; import { AstroError } from 'astro/errors'; -import { baseService } from 'astro/assets' const SUPPORTED_FORMATS = ['avif', 'jpg', 'png', 'webp']; const QUALITY_NAMES: Record = { low: 25, mid: 50, high: 90, max: 100 }; diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index 09451a2da..faa06d946 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -1,12 +1,12 @@ -import type { AstroConfig, AstroIntegration, RouteData } from 'astro'; -import { writeFile, mkdir, appendFile, rm } from 'fs/promises'; +import type { IncomingMessage } from 'http'; import { fileURLToPath } from 'url'; -import { build } from 'esbuild'; import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; -import { version as packageVersion } from '../package.json'; import type { Context } from '@netlify/functions'; +import type { AstroConfig, AstroIntegration, RouteData } from 'astro'; import { AstroError } from 'astro/errors'; -import type { IncomingMessage } from 'http'; +import { build } from 'esbuild'; +import { appendFile, mkdir, rm, writeFile } from 'fs/promises'; +import { version as packageVersion } from '../package.json'; export interface NetlifyLocals { netlify: { diff --git a/packages/integrations/netlify/src/static.ts b/packages/integrations/netlify/src/static.ts index 4748f384a..1c809ef8f 100644 --- a/packages/integrations/netlify/src/static.ts +++ b/packages/integrations/netlify/src/static.ts @@ -1,6 +1,8 @@ -import netlifyIntegration from "./index.js" +import netlifyIntegration from './index.js'; export default function staticIntegration() { - console.warn("The @astrojs/netlify/static import is deprecated and will be removed in a future release. Please use @astrojs/netlify instead.") - return netlifyIntegration() -} \ No newline at end of file + console.warn( + 'The @astrojs/netlify/static import is deprecated and will be removed in a future release. Please use @astrojs/netlify instead.' + ); + return netlifyIntegration(); +} diff --git a/packages/integrations/netlify/src/types.d.ts b/packages/integrations/netlify/src/types.d.ts index 0df35e9e9..8cb293272 100644 --- a/packages/integrations/netlify/src/types.d.ts +++ b/packages/integrations/netlify/src/types.d.ts @@ -1 +1 @@ -declare module "*.json"; \ No newline at end of file +declare module '*.json'; diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index 54f776499..ea8df7980 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,5 +1,5 @@ +import { loadFixture } from '@astrojs/test-utils'; import { expect } from 'chai'; -import { loadFixture } from "@astrojs/test-utils" describe('Cookies', () => { let fixture; @@ -15,9 +15,12 @@ describe('Cookies', () => { import.meta.url ); const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://example.com/login', { method: "POST", body: '{}' }), {}) + const resp = await handler( + new Request('http://example.com/login', { method: 'POST', body: '{}' }), + {} + ); expect(resp.status).to.equal(301); - expect(resp.headers.get("location")).to.equal('/'); + expect(resp.headers.get('location')).to.equal('/'); expect(resp.headers.getSetCookie()).to.eql(['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); }); }); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 412066d1d..73dabf743 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,45 +1,47 @@ +import { loadFixture } from '@astrojs/test-utils'; import { expect } from 'chai'; -import { loadFixture } from "@astrojs/test-utils" describe('Middleware', () => { - const root = new URL('./fixtures/middleware/', import.meta.url) + const root = new URL('./fixtures/middleware/', import.meta.url); - describe("edgeMiddleware: false", () => { - let fixture + describe('edgeMiddleware: false', () => { + let fixture; before(async () => { process.env.EDGE_MIDDLEWARE = 'false'; fixture = await loadFixture({ root }); await fixture.build(); - }) - + }); + it('emits no edge function', async () => { - expect(fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs')).to.be.false + expect(fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs')).to.be + .false; }); it('applies middleware to static files at build-time', async () => { // prerendered page has middleware applied at build time - const prerenderedPage = await fixture.readFile('prerender/index.html') - expect(prerenderedPage).to.contain("Middleware") + const prerenderedPage = await fixture.readFile('prerender/index.html'); + expect(prerenderedPage).to.contain('Middleware'); }); - }) - + }); - describe("edgeMiddleware: true", () => { - let fixture + describe('edgeMiddleware: true', () => { + let fixture; before(async () => { process.env.EDGE_MIDDLEWARE = 'true'; fixture = await loadFixture({ root }); await fixture.build(); - }) + }); it('emits an edge function', async () => { - const contents = await fixture.readFile('../.netlify/edge-functions/middleware/middleware.mjs') + const contents = await fixture.readFile( + '../.netlify/edge-functions/middleware/middleware.mjs' + ); expect(contents.includes('"Hello world"')).to.be.false; - }) + }); it('does not apply middleware during prerendering', async () => { - const prerenderedPage = await fixture.readFile('prerender/index.html') - expect(prerenderedPage).to.contain("") - }) - }) + const prerenderedPage = await fixture.readFile('prerender/index.html'); + expect(prerenderedPage).to.contain(''); + }); + }); }); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index c2a705b0b..1769e5b01 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,5 +1,5 @@ +import { loadFixture } from '@astrojs/test-utils'; import { expect } from 'chai'; -import { loadFixture } from "@astrojs/test-utils" describe('SSR - Redirects', () => { let fixture; @@ -12,13 +12,7 @@ describe('SSR - Redirects', () => { it('Creates a redirects file', async () => { let redirects = await fixture.readFile('./_redirects'); let parts = redirects.split(/\s+/); - expect(parts).to.deep.equal([ - '', - '/other', - '/', - '301', - '', - ]); + expect(parts).to.deep.equal(['', '/other', '/', '301', '']); expect(redirects).to.matchSnapshot(); }); diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index 6e9adc00a..c24230117 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,5 +1,5 @@ +import { loadFixture } from '@astrojs/test-utils'; import { expect } from 'chai'; -import { loadFixture } from "@astrojs/test-utils" describe('SSG - Redirects', () => { let fixture; @@ -26,7 +26,7 @@ describe('SSG - Redirects', () => { '/blog/*', '/team/articles/*/index.html', '301', - + '', ]); }); -- cgit v1.2.3 From 5b089819f48a09a98faf86aa99dc77828e78cf38 Mon Sep 17 00:00:00 2001 From: Arsh <69170106+lilnasy@users.noreply.github.com> Date: Mon, 12 Feb 2024 09:36:32 -0700 Subject: fix(netlify): consider main fields when bundling (#160) --- packages/integrations/netlify/package.json | 2 +- packages/integrations/netlify/src/index.ts | 1 + packages/integrations/netlify/test/functions/edge-middleware.test.js | 2 +- .../integrations/netlify/test/hosted/hosted-astro-project/package.json | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index e96ef61d3..d14ba3d81 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -47,7 +47,7 @@ "@netlify/edge-functions": "^2.0.0", "@netlify/edge-handler-types": "^0.34.1", "@types/node": "^18.17.8", - "astro": "^4.2.0", + "astro": "^4.3.5", "chai": "^4.3.10", "chai-jest-snapshot": "^2.0.0", "cheerio": "1.0.0-rc.12", diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index 4fa58c627..afffb01d5 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -159,6 +159,7 @@ export default function netlifyIntegration( entryPoints: [fileURLToPath(new URL('./entry.mjs', middlewareOutputDir()))], target: 'es2022', platform: 'neutral', + mainFields: [ 'module', 'main' ], outfile: fileURLToPath(new URL('./middleware.mjs', middlewareOutputDir())), allowOverwrite: true, format: 'esm', diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 73dabf743..867dd2a11 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -39,7 +39,7 @@ describe('Middleware', () => { expect(contents.includes('"Hello world"')).to.be.false; }); - it('does not apply middleware during prerendering', async () => { + it.skip('does not apply middleware during prerendering', async () => { const prerenderedPage = await fixture.readFile('prerender/index.html'); expect(prerenderedPage).to.contain(''); }); diff --git a/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json b/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json index 6d346e808..2634908a9 100644 --- a/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json +++ b/packages/integrations/netlify/test/hosted/hosted-astro-project/package.json @@ -7,6 +7,6 @@ }, "dependencies": { "@astrojs/netlify": "workspace:*", - "astro": "^4.2.0" + "astro": "^4.3.5" } } -- cgit v1.2.3 From 5bbbeaff265de7e663124fa19170764b3d0372ba Mon Sep 17 00:00:00 2001 From: Abid <56756836+abidjappie@users.noreply.github.com> Date: Fri, 16 Feb 2024 18:57:14 +0900 Subject: chore(netlify): use Node.js for testing (#158) --- packages/integrations/netlify/package.json | 14 ++++++------- .../netlify/test/functions/cookies.test.js | 15 +++++++------- .../netlify/test/functions/edge-middleware.test.js | 22 +++++++++++++++------ .../netlify/test/functions/image-cdn.test.js | 23 +++++++++++----------- .../netlify/test/functions/redirects.test.js | 20 ++++++++++--------- .../netlify/test/functions/redirects.test.js.snap | 13 ------------ .../netlify/test/hosted/hosted.test.js | 7 ++++--- packages/integrations/netlify/test/setup.js | 12 ----------- .../netlify/test/static/redirects.test.js | 7 ++++--- 9 files changed, 61 insertions(+), 72 deletions(-) delete mode 100644 packages/integrations/netlify/test/functions/redirects.test.js.snap delete mode 100644 packages/integrations/netlify/test/setup.js (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 2f74806c9..9483bea9d 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -30,10 +30,10 @@ ], "scripts": { "build": "tsc", - "test-fn": "mocha --exit --timeout 20000 --file \"./test/setup.js\" test/functions/", - "test-static": "mocha --exit --timeout 20000 --file \"./test/setup.js\" test/static/", - "test": "pnpm test-fn && pnpm test-static", - "test:hosted": "mocha --exit --timeout 30000 test/hosted" + "test-fn": "astro-scripts test \"test/functions/*.test.js\"", + "test-static": "astro-scripts test \"test/static/*.test.js\"", + "test": "pnpm run test-fn && pnpm run test-static", + "test:hosted": "astro-scripts test \"test/hosted/*.test.js\"" }, "dependencies": { "@astrojs/underscore-redirects": "^0.3.3", @@ -48,16 +48,14 @@ "@netlify/edge-handler-types": "^0.34.1", "@types/node": "^18.17.8", "astro": "^4.3.5", - "chai": "^4.3.10", - "chai-jest-snapshot": "^2.0.0", "cheerio": "1.0.0-rc.12", "execa": "^8.0.1", "fast-glob": "^3.3.1", - "mocha": "^10.2.0", "strip-ansi": "^7.1.0", "typescript": "^5.2.2", "vite": "^4.5.0", - "@astrojs/test-utils": "workspace:*" + "@astrojs/test-utils": "workspace:*", + "astro-scripts": "workspace:*" }, "astro": { "external": true diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index cb81d097a..9d25a873e 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,5 +1,6 @@ import { loadFixture } from '@astrojs/test-utils'; -import { expect } from 'chai'; +import { describe, it, before } from 'node:test'; +import * as assert from 'node:assert/strict'; describe('Cookies', () => { let fixture; @@ -19,9 +20,9 @@ describe('Cookies', () => { new Request('http://example.com/login', { method: 'POST', body: '{}' }), {} ); - expect(resp.status).to.equal(301); - expect(resp.headers.get('location')).to.equal('/'); - expect(resp.headers.getSetCookie()).to.eql(['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); + assert.equal(resp.status,301); + assert.equal(resp.headers.get('location'),'/'); + assert.deepEqual(resp.headers.getSetCookie(),['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); }); it('renders dynamic 404 page', async () => { @@ -38,9 +39,9 @@ describe('Cookies', () => { }), {} ); - expect(resp.status).to.equal(404); + assert.equal(resp.status,404); const text = await resp.text(); - expect(text).to.contain('This is my custom 404 page'); - expect(text).to.contain('x-test: bar'); + assert.equal(text.includes('This is my custom 404 page'),true); + assert.equal(text.includes('x-test: bar'),true); }); }); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 867dd2a11..42de398c6 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,5 +1,6 @@ import { loadFixture } from '@astrojs/test-utils'; -import { expect } from 'chai'; +import { describe, it, before, after } from 'node:test'; +import * as assert from 'node:assert/strict'; describe('Middleware', () => { const root = new URL('./fixtures/middleware/', import.meta.url); @@ -13,15 +14,19 @@ describe('Middleware', () => { }); it('emits no edge function', async () => { - expect(fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs')).to.be - .false; + assert.equal(fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs'), false) }); it('applies middleware to static files at build-time', async () => { // prerendered page has middleware applied at build time const prerenderedPage = await fixture.readFile('prerender/index.html'); - expect(prerenderedPage).to.contain('Middleware'); + assert.equal(prerenderedPage.includes('Middleware'),true); }); + + after(async () => { + process.env.EDGE_MIDDLEWARE = undefined; + await fixture.clean(); + }) }); describe('edgeMiddleware: true', () => { @@ -36,12 +41,17 @@ describe('Middleware', () => { const contents = await fixture.readFile( '../.netlify/edge-functions/middleware/middleware.mjs' ); - expect(contents.includes('"Hello world"')).to.be.false; + assert.equal(contents.includes('"Hello world"'), false); }); it.skip('does not apply middleware during prerendering', async () => { const prerenderedPage = await fixture.readFile('prerender/index.html'); - expect(prerenderedPage).to.contain(''); + assert.equal(prerenderedPage.includes(''),true); }); + + after(async () => { + process.env.EDGE_MIDDLEWARE = undefined; + await fixture.clean(); + }) }); }); diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.js b/packages/integrations/netlify/test/functions/image-cdn.test.js index 1737513a4..7a0060bff 100644 --- a/packages/integrations/netlify/test/functions/image-cdn.test.js +++ b/packages/integrations/netlify/test/functions/image-cdn.test.js @@ -1,6 +1,6 @@ -import { describe } from 'node:test'; import { loadFixture } from '@astrojs/test-utils'; -import { expect } from 'chai'; +import { describe, it, after } from 'node:test'; +import * as assert from 'node:assert/strict'; describe('Image CDN', () => { const root = new URL('./fixtures/middleware/', import.meta.url); @@ -11,20 +11,24 @@ describe('Image CDN', () => { await fixture.build(); const astronautPage = await fixture.readFile('astronaut/index.html'); - expect(astronautPage).contains(`src="/_astro/astronaut.`); + assert.equal(astronautPage.includes(`src="/_astro/astronaut.`),true); }); + }); describe('when running inside of netlify', () => { - it('enables Netlify Image CDN', async () => { + after(() => { + process.env.NETLIFY = undefined; + process.env.DISABLE_IMAGE_CDN = undefined; + }); + + it('enables Netlify Image CDN',async () => { process.env.NETLIFY = 'true'; const fixture = await loadFixture({ root }); await fixture.build(); const astronautPage = await fixture.readFile('astronaut/index.html'); - expect(astronautPage).contains(`src="/.netlify/image`); - - process.env.NETLIFY = undefined; + assert.equal(astronautPage.includes(`src="/.netlify/image`),true); }); it('respects image CDN opt-out', async () => { @@ -34,10 +38,7 @@ describe('Image CDN', () => { await fixture.build(); const astronautPage = await fixture.readFile('astronaut/index.html'); - expect(astronautPage).contains(`src="/_astro/astronaut.`); - - process.env.NETLIFY = undefined; - process.env.DISABLE_IMAGE_CDN = undefined; + assert.equal(astronautPage.includes(`src="/_astro/astronaut.`),true); }); }); }); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index eb9b32945..ab4d426ba 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,6 +1,7 @@ import { createServer } from 'http'; import { loadFixture } from '@astrojs/test-utils'; -import { expect } from 'chai'; +import { describe, it, before } from 'node:test'; +import * as assert from 'node:assert/strict'; describe('SSR - Redirects', () => { let fixture; @@ -13,8 +14,9 @@ describe('SSR - Redirects', () => { it('Creates a redirects file', async () => { const redirects = await fixture.readFile('./_redirects'); const parts = redirects.split(/\s+/); - expect(parts).to.deep.equal(['', '/other', '/', '301', '']); - expect(redirects).to.matchSnapshot(); + assert.deepEqual(parts,['', '/other', '/', '301', '']); + // Snapshots are not supported in Node.js test yet (https://github.com/nodejs/node/issues/48260) + assert.equal(redirects,'\n/other / 301\n'); }); it('Does not create .html files', async () => { @@ -24,7 +26,7 @@ describe('SSR - Redirects', () => { } catch { hasErrored = true; } - expect(hasErrored).to.equal(true, 'this file should not exist'); + assert.equal(hasErrored,true, 'this file should not exist'); }); it('renders static 404 page', async () => { @@ -34,10 +36,10 @@ describe('SSR - Redirects', () => { ); const { default: handler } = await import(entryURL); const resp = await handler(new Request('http://example.com/nonexistant-page'), {}); - expect(resp.status).to.equal(404); - expect(resp.headers.get('content-type')).to.equal('text/html; charset=utf-8'); + assert.equal(resp.status,404); + assert.equal(resp.headers.get('content-type'),'text/html; charset=utf-8'); const text = await resp.text(); - expect(text).to.contain('This is my static 404 page'); + assert.equal(text.includes('This is my static 404 page'),true); }); it('does not pass through 404 request', async () => { @@ -54,8 +56,8 @@ describe('SSR - Redirects', () => { ); const { default: handler } = await import(entryURL); const resp = await handler(new Request('http://localhost:5678/nonexistant-page'), {}); - expect(resp.status).to.equal(404); - expect(testServerCalls).to.equal(0); + assert.equal(resp.status,404); + assert.equal(testServerCalls,0); testServer.close(); }); }); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js.snap b/packages/integrations/netlify/test/functions/redirects.test.js.snap deleted file mode 100644 index b781001e4..000000000 --- a/packages/integrations/netlify/test/functions/redirects.test.js.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SSG - Redirects Creates a redirects file 1`] = ` -" -/other / 301 -" -`; - -exports[`SSR - Redirects Creates a redirects file 1`] = ` -" -/other / 301 -" -`; diff --git a/packages/integrations/netlify/test/hosted/hosted.test.js b/packages/integrations/netlify/test/hosted/hosted.test.js index 2a53ce806..95d808031 100644 --- a/packages/integrations/netlify/test/hosted/hosted.test.js +++ b/packages/integrations/netlify/test/hosted/hosted.test.js @@ -1,4 +1,5 @@ -import { expect } from 'chai'; +import { describe, it, before } from 'node:test'; +import * as assert from 'node:assert/strict'; const NETLIFY_TEST_URL = 'https://curious-boba-495d6d.netlify.app'; @@ -8,7 +9,7 @@ describe('Hosted Netlify Tests', () => { `${NETLIFY_TEST_URL}/_image?href=%2F_astro%2Fpenguin.e9c64733.png&w=300&f=webp` ); - expect(image.status).to.equal(200); + assert.equal(image.status,200); }); it('Server returns fresh content', async () => { @@ -16,6 +17,6 @@ describe('Hosted Netlify Tests', () => { const responseTwo = await fetch(`${NETLIFY_TEST_URL}/time`); - expect(responseOne.body).to.not.equal(responseTwo.body); + assert.notEqual(responseOne.body,responseTwo.body); }); }); diff --git a/packages/integrations/netlify/test/setup.js b/packages/integrations/netlify/test/setup.js deleted file mode 100644 index 5862aed44..000000000 --- a/packages/integrations/netlify/test/setup.js +++ /dev/null @@ -1,12 +0,0 @@ -import { use } from 'chai'; -import chaiJestSnapshot from 'chai-jest-snapshot'; - -use(chaiJestSnapshot); - -before(() => { - chaiJestSnapshot.resetSnapshotRegistry(); -}); - -beforeEach(function () { - chaiJestSnapshot.configureUsingMochaContext(this); -}); diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index 2e39be303..ea6864a10 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,5 +1,6 @@ import { loadFixture } from '@astrojs/test-utils'; -import { expect } from 'chai'; +import { describe, it, before } from 'node:test'; +import * as assert from 'node:assert/strict'; describe('SSG - Redirects', () => { let fixture; @@ -12,7 +13,7 @@ describe('SSG - Redirects', () => { it('Creates a redirects file', async () => { const redirects = await fixture.readFile('./_redirects'); const parts = redirects.split(/\s+/); - expect(parts).to.deep.equal([ + assert.deepEqual(parts, [ '', '/two', @@ -28,6 +29,6 @@ describe('SSG - Redirects', () => { '301', '', - ]); + ]) }); }); -- cgit v1.2.3 From 768ef569a34f4e6e2e12df8946ee861823d0685e Mon Sep 17 00:00:00 2001 From: alexanderniebuhr Date: Fri, 16 Feb 2024 09:57:45 +0000 Subject: [ci] format --- .../netlify/test/functions/cookies.test.js | 16 ++++++++-------- .../netlify/test/functions/edge-middleware.test.js | 17 ++++++++++------- .../netlify/test/functions/image-cdn.test.js | 13 ++++++------- .../netlify/test/functions/redirects.test.js | 20 ++++++++++---------- .../integrations/netlify/test/hosted/hosted.test.js | 6 +++--- .../netlify/test/static/redirects.test.js | 6 +++--- 6 files changed, 40 insertions(+), 38 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index 9d25a873e..c4384f8e0 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,6 +1,6 @@ -import { loadFixture } from '@astrojs/test-utils'; -import { describe, it, before } from 'node:test'; import * as assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import { loadFixture } from '@astrojs/test-utils'; describe('Cookies', () => { let fixture; @@ -20,9 +20,9 @@ describe('Cookies', () => { new Request('http://example.com/login', { method: 'POST', body: '{}' }), {} ); - assert.equal(resp.status,301); - assert.equal(resp.headers.get('location'),'/'); - assert.deepEqual(resp.headers.getSetCookie(),['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); + assert.equal(resp.status, 301); + assert.equal(resp.headers.get('location'), '/'); + assert.deepEqual(resp.headers.getSetCookie(), ['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); }); it('renders dynamic 404 page', async () => { @@ -39,9 +39,9 @@ describe('Cookies', () => { }), {} ); - assert.equal(resp.status,404); + assert.equal(resp.status, 404); const text = await resp.text(); - assert.equal(text.includes('This is my custom 404 page'),true); - assert.equal(text.includes('x-test: bar'),true); + assert.equal(text.includes('This is my custom 404 page'), true); + assert.equal(text.includes('x-test: bar'), true); }); }); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 42de398c6..ec4fe66dd 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,6 +1,6 @@ -import { loadFixture } from '@astrojs/test-utils'; -import { describe, it, before, after } from 'node:test'; import * as assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { loadFixture } from '@astrojs/test-utils'; describe('Middleware', () => { const root = new URL('./fixtures/middleware/', import.meta.url); @@ -14,19 +14,22 @@ describe('Middleware', () => { }); it('emits no edge function', async () => { - assert.equal(fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs'), false) + assert.equal( + fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs'), + false + ); }); it('applies middleware to static files at build-time', async () => { // prerendered page has middleware applied at build time const prerenderedPage = await fixture.readFile('prerender/index.html'); - assert.equal(prerenderedPage.includes('Middleware'),true); + assert.equal(prerenderedPage.includes('Middleware'), true); }); after(async () => { process.env.EDGE_MIDDLEWARE = undefined; await fixture.clean(); - }) + }); }); describe('edgeMiddleware: true', () => { @@ -46,12 +49,12 @@ describe('Middleware', () => { it.skip('does not apply middleware during prerendering', async () => { const prerenderedPage = await fixture.readFile('prerender/index.html'); - assert.equal(prerenderedPage.includes(''),true); + assert.equal(prerenderedPage.includes(''), true); }); after(async () => { process.env.EDGE_MIDDLEWARE = undefined; await fixture.clean(); - }) + }); }); }); diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.js b/packages/integrations/netlify/test/functions/image-cdn.test.js index 7a0060bff..31032363f 100644 --- a/packages/integrations/netlify/test/functions/image-cdn.test.js +++ b/packages/integrations/netlify/test/functions/image-cdn.test.js @@ -1,6 +1,6 @@ -import { loadFixture } from '@astrojs/test-utils'; -import { describe, it, after } from 'node:test'; import * as assert from 'node:assert/strict'; +import { after, describe, it } from 'node:test'; +import { loadFixture } from '@astrojs/test-utils'; describe('Image CDN', () => { const root = new URL('./fixtures/middleware/', import.meta.url); @@ -11,9 +11,8 @@ describe('Image CDN', () => { await fixture.build(); const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/_astro/astronaut.`),true); + assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); }); - }); describe('when running inside of netlify', () => { @@ -22,13 +21,13 @@ describe('Image CDN', () => { process.env.DISABLE_IMAGE_CDN = undefined; }); - it('enables Netlify Image CDN',async () => { + it('enables Netlify Image CDN', async () => { process.env.NETLIFY = 'true'; const fixture = await loadFixture({ root }); await fixture.build(); const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/.netlify/image`),true); + assert.equal(astronautPage.includes(`src="/.netlify/image`), true); }); it('respects image CDN opt-out', async () => { @@ -38,7 +37,7 @@ describe('Image CDN', () => { await fixture.build(); const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/_astro/astronaut.`),true); + assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); }); }); }); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index ab4d426ba..24b831ee0 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,7 +1,7 @@ import { createServer } from 'http'; -import { loadFixture } from '@astrojs/test-utils'; -import { describe, it, before } from 'node:test'; import * as assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import { loadFixture } from '@astrojs/test-utils'; describe('SSR - Redirects', () => { let fixture; @@ -14,9 +14,9 @@ describe('SSR - Redirects', () => { it('Creates a redirects file', async () => { const redirects = await fixture.readFile('./_redirects'); const parts = redirects.split(/\s+/); - assert.deepEqual(parts,['', '/other', '/', '301', '']); + assert.deepEqual(parts, ['', '/other', '/', '301', '']); // Snapshots are not supported in Node.js test yet (https://github.com/nodejs/node/issues/48260) - assert.equal(redirects,'\n/other / 301\n'); + assert.equal(redirects, '\n/other / 301\n'); }); it('Does not create .html files', async () => { @@ -26,7 +26,7 @@ describe('SSR - Redirects', () => { } catch { hasErrored = true; } - assert.equal(hasErrored,true, 'this file should not exist'); + assert.equal(hasErrored, true, 'this file should not exist'); }); it('renders static 404 page', async () => { @@ -36,10 +36,10 @@ describe('SSR - Redirects', () => { ); const { default: handler } = await import(entryURL); const resp = await handler(new Request('http://example.com/nonexistant-page'), {}); - assert.equal(resp.status,404); - assert.equal(resp.headers.get('content-type'),'text/html; charset=utf-8'); + assert.equal(resp.status, 404); + assert.equal(resp.headers.get('content-type'), 'text/html; charset=utf-8'); const text = await resp.text(); - assert.equal(text.includes('This is my static 404 page'),true); + assert.equal(text.includes('This is my static 404 page'), true); }); it('does not pass through 404 request', async () => { @@ -56,8 +56,8 @@ describe('SSR - Redirects', () => { ); const { default: handler } = await import(entryURL); const resp = await handler(new Request('http://localhost:5678/nonexistant-page'), {}); - assert.equal(resp.status,404); - assert.equal(testServerCalls,0); + assert.equal(resp.status, 404); + assert.equal(testServerCalls, 0); testServer.close(); }); }); diff --git a/packages/integrations/netlify/test/hosted/hosted.test.js b/packages/integrations/netlify/test/hosted/hosted.test.js index 95d808031..237ba5998 100644 --- a/packages/integrations/netlify/test/hosted/hosted.test.js +++ b/packages/integrations/netlify/test/hosted/hosted.test.js @@ -1,5 +1,5 @@ -import { describe, it, before } from 'node:test'; import * as assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; const NETLIFY_TEST_URL = 'https://curious-boba-495d6d.netlify.app'; @@ -9,7 +9,7 @@ describe('Hosted Netlify Tests', () => { `${NETLIFY_TEST_URL}/_image?href=%2F_astro%2Fpenguin.e9c64733.png&w=300&f=webp` ); - assert.equal(image.status,200); + assert.equal(image.status, 200); }); it('Server returns fresh content', async () => { @@ -17,6 +17,6 @@ describe('Hosted Netlify Tests', () => { const responseTwo = await fetch(`${NETLIFY_TEST_URL}/time`); - assert.notEqual(responseOne.body,responseTwo.body); + assert.notEqual(responseOne.body, responseTwo.body); }); }); diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index ea6864a10..3cd69cbc0 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,6 +1,6 @@ -import { loadFixture } from '@astrojs/test-utils'; -import { describe, it, before } from 'node:test'; import * as assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import { loadFixture } from '@astrojs/test-utils'; describe('SSG - Redirects', () => { let fixture; @@ -29,6 +29,6 @@ describe('SSG - Redirects', () => { '301', '', - ]) + ]); }); }); -- cgit v1.2.3 From 7eea21b3c6ea585027053a700874eb025afd34ad Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 27 Jun 2024 12:50:51 +0100 Subject: perf(netlify): handle dependency tracing for SSR function (#296) * perf: take over function bundling * Fix test failures * Format * Changeset * Use shared fs helper * Format * Changes from review * Add logging * Test timeout * Use posix path * Remove logs --- packages/integrations/netlify/package.json | 10 +- packages/integrations/netlify/src/functions.ts | 3 +- packages/integrations/netlify/src/index.ts | 54 ++++- packages/integrations/netlify/src/lib/nft.ts | 85 ++++++++ .../netlify/test/functions/cookies.test.js | 86 ++++---- .../netlify/test/functions/edge-middleware.test.js | 102 +++++----- .../netlify/test/functions/image-cdn.test.js | 224 +++++++++++---------- .../netlify/test/functions/redirects.test.js | 110 +++++----- 8 files changed, 411 insertions(+), 263 deletions(-) create mode 100644 packages/integrations/netlify/src/lib/nft.ts (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index c1669dbd1..066c1ae18 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -31,26 +31,28 @@ "test:hosted": "astro-scripts test \"test/hosted/*.test.js\"" }, "dependencies": { + "@astrojs/internal-helpers": "0.4.1", "@astrojs/underscore-redirects": "^0.3.3", - "@netlify/functions": "^2.0.1", + "@netlify/functions": "^2.8.0", + "@vercel/nft": "^0.27.2", "esbuild": "^0.19.5" }, "peerDependencies": { "astro": "^4.2.0" }, "devDependencies": { + "@astrojs/test-utils": "workspace:*", "@netlify/edge-functions": "^2.0.0", "@netlify/edge-handler-types": "^0.34.1", "@types/node": "^18.17.8", "astro": "^4.10.1", + "astro-scripts": "workspace:*", "cheerio": "1.0.0-rc.12", "execa": "^8.0.1", "fast-glob": "^3.3.1", "strip-ansi": "^7.1.0", "typescript": "^5.2.2", - "vite": "^4.5.0", - "@astrojs/test-utils": "workspace:*", - "astro-scripts": "workspace:*" + "vite": "^4.5.0" }, "astro": { "external": true diff --git a/packages/integrations/netlify/src/functions.ts b/packages/integrations/netlify/src/functions.ts index f2ffd392a..58428fec0 100644 --- a/packages/integrations/netlify/src/functions.ts +++ b/packages/integrations/netlify/src/functions.ts @@ -1,6 +1,7 @@ +import type { AstroIntegration } from 'astro'; import netlifyIntegration, { type NetlifyIntegrationConfig } from './index.js'; -export default function functionsIntegration(config: NetlifyIntegrationConfig) { +export default function functionsIntegration(config: NetlifyIntegrationConfig): AstroIntegration { console.warn( 'The @astrojs/netlify/functions import is deprecated and will be removed in a future release. Please use @astrojs/netlify instead.' ); diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index cd13d9362..f5c4f9226 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -2,11 +2,12 @@ import { randomUUID } from 'node:crypto'; import { appendFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import type { IncomingMessage } from 'node:http'; import { fileURLToPath } from 'node:url'; +import { emptyDir } from '@astrojs/internal-helpers/fs'; import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; import type { Context } from '@netlify/functions'; import type { AstroConfig, AstroIntegration, AstroIntegrationLogger, RouteData } from 'astro'; -import { AstroError } from 'astro/errors'; import { build } from 'esbuild'; +import { copyDependenciesToFunction } from './lib/nft.js'; import type { Args } from './ssr-function.js'; const { version: packageVersion } = JSON.parse( @@ -22,8 +23,6 @@ export interface NetlifyLocals { const isStaticRedirect = (route: RouteData) => route.type === 'redirect' && (route.redirect || route.redirectRoute); -const clearDirectory = (dir: URL) => rm(dir, { recursive: true }).catch(() => {}); - type RemotePattern = AstroConfig['image']['remotePatterns'][number]; /** @@ -190,11 +189,18 @@ export default function netlifyIntegration( // Secret used to verify that the caller is the astro-generated edge middleware and not a third-party const middlewareSecret = randomUUID(); + const TRACE_CACHE = {}; + + const ssrBuildDir = () => new URL('./.netlify/build/', rootDir); const ssrOutputDir = () => new URL('./.netlify/functions-internal/ssr/', rootDir); const middlewareOutputDir = () => new URL('.netlify/edge-functions/middleware/', rootDir); const cleanFunctions = async () => - await Promise.all([clearDirectory(middlewareOutputDir()), clearDirectory(ssrOutputDir())]); + await Promise.all([ + emptyDir(middlewareOutputDir()), + emptyDir(ssrOutputDir()), + emptyDir(ssrBuildDir()), + ]); async function writeRedirects(routes: RouteData[], dir: URL) { const fallback = _config.output === 'static' ? '/.netlify/static' : '/.netlify/functions/ssr'; @@ -219,11 +225,27 @@ export default function netlifyIntegration( } } - async function writeSSRFunction(notFoundContent?: string) { + async function writeSSRFunction({ + notFoundContent, + logger, + }: { notFoundContent?: string; logger: AstroIntegrationLogger }) { + const entry = new URL('./entry.mjs', ssrBuildDir()); + + const { handler } = await copyDependenciesToFunction( + { + entry, + outDir: ssrOutputDir(), + includeFiles: [], + excludeFiles: [], + logger, + }, + TRACE_CACHE + ); + await writeFile( new URL('./ssr.mjs', ssrOutputDir()), ` - import createSSRHandler from './entry.mjs'; + import createSSRHandler from './${handler}'; export default createSSRHandler(${JSON.stringify({ cacheOnDemandPages: Boolean(integrationConfig?.cacheOnDemandPages), notFoundContent, @@ -231,6 +253,17 @@ export default function netlifyIntegration( export const config = { name: "Astro SSR", generator: "@astrojs/netlify@${packageVersion}", path: "/*", preferStatic: true }; ` ); + + await writeFile( + new URL('.netlify/functions-internal/ssr/ssr.json', rootDir), + JSON.stringify({ + config: { + nodeBundler: 'none', + includedFiles: ['.netlify/functions-internal/ssr/**/*'], + }, + version: 1, + }) + ); } async function writeMiddleware(entrypoint: URL) { @@ -312,8 +345,6 @@ export default function netlifyIntegration( city: 'Mock City', country: { code: 'mock', name: 'Mock Country' }, subdivision: { code: 'SD', name: 'Mock Subdivision' }, - - // @ts-expect-error: these are smhw missing from the Netlify types - fix is on the way timezone: 'UTC', longitude: 0, latitude: 0, @@ -332,6 +363,8 @@ export default function netlifyIntegration( get cookies(): never { throw new Error('Please use Astro.cookies instead.'); }, + // @ts-expect-error This is not currently included in the public Netlify types + flags: undefined, json: (input) => Response.json(input), log: console.log, next: () => { @@ -364,7 +397,7 @@ export default function netlifyIntegration( build: { redirects: false, client: outDir, - server: ssrOutputDir(), + server: ssrBuildDir(), }, vite: { server: { @@ -431,10 +464,9 @@ export default function netlifyIntegration( try { notFoundContent = await readFile(new URL('./404.html', dir), 'utf8'); } catch {} - await writeSSRFunction(notFoundContent); + await writeSSRFunction({ notFoundContent, logger }); logger.info('Generated SSR Function'); } - if (astroMiddlewareEntryPoint) { await writeMiddleware(astroMiddlewareEntryPoint); logger.info('Generated Middleware Edge Function'); diff --git a/packages/integrations/netlify/src/lib/nft.ts b/packages/integrations/netlify/src/lib/nft.ts new file mode 100644 index 000000000..bb52d6c69 --- /dev/null +++ b/packages/integrations/netlify/src/lib/nft.ts @@ -0,0 +1,85 @@ +import { posix, relative, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { copyFilesToFolder } from '@astrojs/internal-helpers/fs'; +import type { AstroIntegrationLogger } from 'astro'; + +// Based on the equivalent function in `@astrojs/vercel` +export async function copyDependenciesToFunction( + { + entry, + outDir, + includeFiles, + excludeFiles, + logger, + }: { + entry: URL; + outDir: URL; + includeFiles: URL[]; + excludeFiles: URL[]; + logger: AstroIntegrationLogger; + }, + // we want to pass the caching by reference, and not by value + cache: object +): Promise<{ handler: string }> { + const entryPath = fileURLToPath(entry); + logger.info(`Bundling function ${relative(fileURLToPath(outDir), entryPath)}`); + + // Get root of folder of the system (like C:\ on Windows or / on Linux) + let base = entry; + while (fileURLToPath(base) !== fileURLToPath(new URL('../', base))) { + base = new URL('../', base); + } + + // The Vite bundle includes an import to `@vercel/nft` for some reason, + // and that trips up `@vercel/nft` itself during the adapter build. Using a + // dynamic import helps prevent the issue. + // TODO: investigate why + const { nodeFileTrace } = await import('@vercel/nft'); + const result = await nodeFileTrace([entryPath], { + base: fileURLToPath(base), + // If you have a route of /dev this appears in source and NFT will try to + // scan your local /dev :8 + ignore: ['/dev/**'], + cache, + }); + + for (const error of result.warnings) { + if (error.message.startsWith('Failed to resolve dependency')) { + const [, module, file] = + /Cannot find module '(.+?)' loaded from (.+)/.exec(error.message) || []; + + // The import(astroRemark) sometimes fails to resolve, but it's not a problem + if (module === '@astrojs/') continue; + + // Sharp is always external and won't be able to be resolved, but that's also not a problem + if (module === 'sharp') continue; + + if (entryPath === file) { + logger.debug( + `The module "${module}" couldn't be resolved. This may not be a problem, but it's worth checking.` + ); + } else { + logger.debug( + `The module "${module}" inside the file "${file}" couldn't be resolved. This may not be a problem, but it's worth checking.` + ); + } + } + // parse errors are likely not js and can safely be ignored, + // such as this html file in "main" meant for nw instead of node: + // https://github.com/vercel/nft/issues/311 + else if (!error.message.startsWith('Failed to parse')) { + throw error; + } + } + + const commonAncestor = await copyFilesToFolder( + [...result.fileList].map((file) => new URL(file, base)).concat(includeFiles), + outDir, + excludeFiles + ); + + return { + // serverEntry location inside the outDir, converted to posix + handler: relative(commonAncestor, entryPath).split(sep).join(posix.sep), + }; +} diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index c4384f8e0..08a9da8ce 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -2,46 +2,52 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import { loadFixture } from '@astrojs/test-utils'; -describe('Cookies', () => { - let fixture; +describe( + 'Cookies', + () => { + let fixture; - before(async () => { - fixture = await loadFixture({ root: new URL('./fixtures/cookies/', import.meta.url) }); - await fixture.build(); - }); + before(async () => { + fixture = await loadFixture({ root: new URL('./fixtures/cookies/', import.meta.url) }); + await fixture.build(); + }); - it('Can set multiple', async () => { - const entryURL = new URL( - './fixtures/cookies/.netlify/functions-internal/ssr/ssr.mjs', - import.meta.url - ); - const { default: handler } = await import(entryURL); - const resp = await handler( - new Request('http://example.com/login', { method: 'POST', body: '{}' }), - {} - ); - assert.equal(resp.status, 301); - assert.equal(resp.headers.get('location'), '/'); - assert.deepEqual(resp.headers.getSetCookie(), ['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); - }); + it('Can set multiple', async () => { + const entryURL = new URL( + './fixtures/cookies/.netlify/functions-internal/ssr/ssr.mjs', + import.meta.url + ); + const { default: handler } = await import(entryURL); + const resp = await handler( + new Request('http://example.com/login', { method: 'POST', body: '{}' }), + {} + ); + assert.equal(resp.status, 301); + assert.equal(resp.headers.get('location'), '/'); + assert.deepEqual(resp.headers.getSetCookie(), ['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); + }); - it('renders dynamic 404 page', async () => { - const entryURL = new URL( - './fixtures/cookies/.netlify/functions-internal/ssr/ssr.mjs', - import.meta.url - ); - const { default: handler } = await import(entryURL); - const resp = await handler( - new Request('http://example.com/nonexistant-page', { - headers: { - 'x-test': 'bar', - }, - }), - {} - ); - assert.equal(resp.status, 404); - const text = await resp.text(); - assert.equal(text.includes('This is my custom 404 page'), true); - assert.equal(text.includes('x-test: bar'), true); - }); -}); + it('renders dynamic 404 page', async () => { + const entryURL = new URL( + './fixtures/cookies/.netlify/functions-internal/ssr/ssr.mjs', + import.meta.url + ); + const { default: handler } = await import(entryURL); + const resp = await handler( + new Request('http://example.com/nonexistant-page', { + headers: { + 'x-test': 'bar', + }, + }), + {} + ); + assert.equal(resp.status, 404); + const text = await resp.text(); + assert.equal(text.includes('This is my custom 404 page'), true); + assert.equal(text.includes('x-test: bar'), true); + }); + }, + { + timeout: 120000, + } +); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index ec4fe66dd..8d85d167c 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -2,59 +2,65 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { loadFixture } from '@astrojs/test-utils'; -describe('Middleware', () => { - const root = new URL('./fixtures/middleware/', import.meta.url); - - describe('edgeMiddleware: false', () => { - let fixture; - before(async () => { - process.env.EDGE_MIDDLEWARE = 'false'; - fixture = await loadFixture({ root }); - await fixture.build(); - }); +describe( + 'Middleware', + () => { + const root = new URL('./fixtures/middleware/', import.meta.url); - it('emits no edge function', async () => { - assert.equal( - fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs'), - false - ); - }); + describe('edgeMiddleware: false', () => { + let fixture; + before(async () => { + process.env.EDGE_MIDDLEWARE = 'false'; + fixture = await loadFixture({ root }); + await fixture.build(); + }); - it('applies middleware to static files at build-time', async () => { - // prerendered page has middleware applied at build time - const prerenderedPage = await fixture.readFile('prerender/index.html'); - assert.equal(prerenderedPage.includes('Middleware'), true); - }); + it('emits no edge function', async () => { + assert.equal( + fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs'), + false + ); + }); - after(async () => { - process.env.EDGE_MIDDLEWARE = undefined; - await fixture.clean(); - }); - }); - - describe('edgeMiddleware: true', () => { - let fixture; - before(async () => { - process.env.EDGE_MIDDLEWARE = 'true'; - fixture = await loadFixture({ root }); - await fixture.build(); - }); + it('applies middleware to static files at build-time', async () => { + // prerendered page has middleware applied at build time + const prerenderedPage = await fixture.readFile('prerender/index.html'); + assert.equal(prerenderedPage.includes('Middleware'), true); + }); - it('emits an edge function', async () => { - const contents = await fixture.readFile( - '../.netlify/edge-functions/middleware/middleware.mjs' - ); - assert.equal(contents.includes('"Hello world"'), false); + after(async () => { + process.env.EDGE_MIDDLEWARE = undefined; + await fixture.clean(); + }); }); - it.skip('does not apply middleware during prerendering', async () => { - const prerenderedPage = await fixture.readFile('prerender/index.html'); - assert.equal(prerenderedPage.includes(''), true); - }); + describe('edgeMiddleware: true', () => { + let fixture; + before(async () => { + process.env.EDGE_MIDDLEWARE = 'true'; + fixture = await loadFixture({ root }); + await fixture.build(); + }); + + it('emits an edge function', async () => { + const contents = await fixture.readFile( + '../.netlify/edge-functions/middleware/middleware.mjs' + ); + assert.equal(contents.includes('"Hello world"'), false); + }); + + it.skip('does not apply middleware during prerendering', async () => { + const prerenderedPage = await fixture.readFile('prerender/index.html'); + assert.equal(prerenderedPage.includes(''), true); + }); - after(async () => { - process.env.EDGE_MIDDLEWARE = undefined; - await fixture.clean(); + after(async () => { + process.env.EDGE_MIDDLEWARE = undefined; + await fixture.clean(); + }); }); - }); -}); + }, + { + timeout: 120000, + } +); diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.js b/packages/integrations/netlify/test/functions/image-cdn.test.js index 776a275bf..2ed2705dc 100644 --- a/packages/integrations/netlify/test/functions/image-cdn.test.js +++ b/packages/integrations/netlify/test/functions/image-cdn.test.js @@ -3,127 +3,137 @@ import { after, before, describe, it } from 'node:test'; import { remotePatternToRegex } from '@astrojs/netlify'; import { loadFixture } from '@astrojs/test-utils'; -describe('Image CDN', () => { - const root = new URL('./fixtures/middleware/', import.meta.url); +describe( + 'Image CDN', + () => { + const root = new URL('./fixtures/middleware/', import.meta.url); - describe('when running outside of netlify', () => { - it('does not enable Image CDN', async () => { - const fixture = await loadFixture({ root }); - await fixture.build(); + describe('when running outside of netlify', () => { + it('does not enable Image CDN', async () => { + const fixture = await loadFixture({ root }); + await fixture.build(); - const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); + const astronautPage = await fixture.readFile('astronaut/index.html'); + assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); + }); }); - }); - describe('when running inside of netlify', () => { - after(() => { - process.env.NETLIFY = undefined; - process.env.DISABLE_IMAGE_CDN = undefined; - }); + describe('when running inside of netlify', () => { + after(() => { + process.env.NETLIFY = undefined; + process.env.DISABLE_IMAGE_CDN = undefined; + }); - it('enables Netlify Image CDN', async () => { - process.env.NETLIFY = 'true'; - const fixture = await loadFixture({ root }); - await fixture.build(); + it('enables Netlify Image CDN', async () => { + process.env.NETLIFY = 'true'; + const fixture = await loadFixture({ root }); + await fixture.build(); - const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/.netlify/image`), true); - }); + const astronautPage = await fixture.readFile('astronaut/index.html'); + assert.equal(astronautPage.includes(`src="/.netlify/image`), true); + }); - it('respects image CDN opt-out', async () => { - process.env.NETLIFY = 'true'; - process.env.DISABLE_IMAGE_CDN = 'true'; - const fixture = await loadFixture({ root }); - await fixture.build(); + it('respects image CDN opt-out', async () => { + process.env.NETLIFY = 'true'; + process.env.DISABLE_IMAGE_CDN = 'true'; + const fixture = await loadFixture({ root }); + await fixture.build(); - const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); + const astronautPage = await fixture.readFile('astronaut/index.html'); + assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); + }); }); - }); - describe('remote image config', () => { - let regexes; + describe('remote image config', () => { + let regexes; - before(async () => { - const fixture = await loadFixture({ root }); - await fixture.build(); + before(async () => { + const fixture = await loadFixture({ root }); + await fixture.build(); - const config = await fixture.readFile('../.netlify/deploy/v1/config.json'); - if (config) { - regexes = JSON.parse(config).images.remote_images.map((pattern) => new RegExp(pattern)); - } - }); + const config = await fixture.readFile('../.netlify/deploy/v1/config.json'); + if (config) { + regexes = JSON.parse(config).images.remote_images.map((pattern) => new RegExp(pattern)); + } + }); - it('generates remote image config patterns', async () => { - assert.equal(regexes?.length, 3); - }); + it('generates remote image config patterns', async () => { + assert.equal(regexes?.length, 3); + }); - it('generates correct config for domains', async () => { - const domain = regexes[0]; - assert.equal(domain.test('https://example.net/image.jpg'), true); - assert.equal( - domain.test('https://www.example.net/image.jpg'), - false, - 'subdomain should not match' - ); - assert.equal(domain.test('http://example.net/image.jpg'), true, 'http should match'); - assert.equal( - domain.test('https://example.net/subdomain/image.jpg'), - true, - 'subpath should match' - ); - const subdomain = regexes[1]; - assert.equal( - subdomain.test('https://secret.example.edu/image.jpg'), - true, - 'should match subdomains' - ); - assert.equal( - subdomain.test('https://secretxexample.edu/image.jpg'), - false, - 'should not use dots in domains as wildcards' - ); - }); + it('generates correct config for domains', async () => { + const domain = regexes[0]; + assert.equal(domain.test('https://example.net/image.jpg'), true); + assert.equal( + domain.test('https://www.example.net/image.jpg'), + false, + 'subdomain should not match' + ); + assert.equal(domain.test('http://example.net/image.jpg'), true, 'http should match'); + assert.equal( + domain.test('https://example.net/subdomain/image.jpg'), + true, + 'subpath should match' + ); + const subdomain = regexes[1]; + assert.equal( + subdomain.test('https://secret.example.edu/image.jpg'), + true, + 'should match subdomains' + ); + assert.equal( + subdomain.test('https://secretxexample.edu/image.jpg'), + false, + 'should not use dots in domains as wildcards' + ); + }); - it('generates correct config for remotePatterns', async () => { - const patterns = regexes[2]; - assert.equal(patterns.test('https://example.org/images/1.jpg'), true, 'should match domain'); - assert.equal( - patterns.test('https://www.example.org/images/2.jpg'), - true, - 'www subdomain should match' - ); - assert.equal( - patterns.test('https://www.subdomain.example.org/images/2.jpg'), - false, - 'second level subdomain should not match' - ); - assert.equal( - patterns.test('https://example.org/not-images/2.jpg'), - false, - 'wrong path should not match' - ); - }); + it('generates correct config for remotePatterns', async () => { + const patterns = regexes[2]; + assert.equal( + patterns.test('https://example.org/images/1.jpg'), + true, + 'should match domain' + ); + assert.equal( + patterns.test('https://www.example.org/images/2.jpg'), + true, + 'www subdomain should match' + ); + assert.equal( + patterns.test('https://www.subdomain.example.org/images/2.jpg'), + false, + 'second level subdomain should not match' + ); + assert.equal( + patterns.test('https://example.org/not-images/2.jpg'), + false, + 'wrong path should not match' + ); + }); - it('warns when remotepatterns generates an invalid regex', async (t) => { - const logger = { - warn: t.mock.fn(), - }; - const regex = remotePatternToRegex( - { - hostname: '*.examp[le.org', - pathname: '/images/*', - }, - logger - ); - assert.strictEqual(regex, undefined); - const calls = logger.warn.mock.calls; - assert.strictEqual(calls.length, 1); - assert.equal( - calls[0].arguments[0], - 'Could not generate a valid regex from the remotePattern "{"hostname":"*.examp[le.org","pathname":"/images/*"}". Please check the syntax.' - ); + it('warns when remotepatterns generates an invalid regex', async (t) => { + const logger = { + warn: t.mock.fn(), + }; + const regex = remotePatternToRegex( + { + hostname: '*.examp[le.org', + pathname: '/images/*', + }, + logger + ); + assert.strictEqual(regex, undefined); + const calls = logger.warn.mock.calls; + assert.strictEqual(calls.length, 1); + assert.equal( + calls[0].arguments[0], + 'Could not generate a valid regex from the remotePattern "{"hostname":"*.examp[le.org","pathname":"/images/*"}". Please check the syntax.' + ); + }); }); - }); -}); + }, + { + timeout: 120000, + } +); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index a76428e2a..d6f9eed38 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -3,61 +3,67 @@ import { createServer } from 'node:http'; import { before, describe, it } from 'node:test'; import { loadFixture } from '@astrojs/test-utils'; -describe('SSR - Redirects', () => { - let fixture; +describe( + 'SSR - Redirects', + () => { + let fixture; - before(async () => { - fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); - await fixture.build(); - }); + before(async () => { + fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); + await fixture.build(); + }); - it('Creates a redirects file', async () => { - const redirects = await fixture.readFile('./_redirects'); - const parts = redirects.split(/\s+/); - assert.deepEqual(parts, ['', '/other', '/', '301', '']); - // Snapshots are not supported in Node.js test yet (https://github.com/nodejs/node/issues/48260) - assert.equal(redirects, '\n/other / 301\n'); - }); + it('Creates a redirects file', async () => { + const redirects = await fixture.readFile('./_redirects'); + const parts = redirects.split(/\s+/); + assert.deepEqual(parts, ['', '/other', '/', '301', '']); + // Snapshots are not supported in Node.js test yet (https://github.com/nodejs/node/issues/48260) + assert.equal(redirects, '\n/other / 301\n'); + }); - it('Does not create .html files', async () => { - let hasErrored = false; - try { - await fixture.readFile('/other/index.html'); - } catch { - hasErrored = true; - } - assert.equal(hasErrored, true, 'this file should not exist'); - }); + it('Does not create .html files', async () => { + let hasErrored = false; + try { + await fixture.readFile('/other/index.html'); + } catch { + hasErrored = true; + } + assert.equal(hasErrored, true, 'this file should not exist'); + }); - it('renders static 404 page', async () => { - const entryURL = new URL( - './fixtures/redirects/.netlify/functions-internal/ssr/ssr.mjs', - import.meta.url - ); - const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://example.com/nonexistant-page'), {}); - assert.equal(resp.status, 404); - assert.equal(resp.headers.get('content-type'), 'text/html; charset=utf-8'); - const text = await resp.text(); - assert.equal(text.includes('This is my static 404 page'), true); - }); + it('renders static 404 page', async () => { + const entryURL = new URL( + './fixtures/redirects/.netlify/functions-internal/ssr/ssr.mjs', + import.meta.url + ); + const { default: handler } = await import(entryURL); + const resp = await handler(new Request('http://example.com/nonexistant-page'), {}); + assert.equal(resp.status, 404); + assert.equal(resp.headers.get('content-type'), 'text/html; charset=utf-8'); + const text = await resp.text(); + assert.equal(text.includes('This is my static 404 page'), true); + }); - it('does not pass through 404 request', async () => { - let testServerCalls = 0; - const testServer = createServer((req, res) => { - testServerCalls++; - res.writeHead(200); - res.end(); + it('does not pass through 404 request', async () => { + let testServerCalls = 0; + const testServer = createServer((req, res) => { + testServerCalls++; + res.writeHead(200); + res.end(); + }); + testServer.listen(5678); + const entryURL = new URL( + './fixtures/redirects/.netlify/functions-internal/ssr/ssr.mjs', + import.meta.url + ); + const { default: handler } = await import(entryURL); + const resp = await handler(new Request('http://localhost:5678/nonexistant-page'), {}); + assert.equal(resp.status, 404); + assert.equal(testServerCalls, 0); + testServer.close(); }); - testServer.listen(5678); - const entryURL = new URL( - './fixtures/redirects/.netlify/functions-internal/ssr/ssr.mjs', - import.meta.url - ); - const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://localhost:5678/nonexistant-page'), {}); - assert.equal(resp.status, 404); - assert.equal(testServerCalls, 0); - testServer.close(); - }); -}); + }, + { + timeout: 120000, + } +); -- cgit v1.2.3 From 2f750cbe9d9837e087ddb8995399d36c19ac7b62 Mon Sep 17 00:00:00 2001 From: Eduardo Bouças Date: Mon, 8 Jul 2024 10:34:43 +0100 Subject: feat: use Netlify Frameworks API (#315) * feat: use Netlify Frameworks API * fix: fix formatting * refactor: inline config * chore: create changeset * Update changeset --------- Co-authored-by: Matt Kane --- packages/integrations/netlify/src/index.ts | 30 ++++++++++------------ .../netlify/test/functions/cookies.test.js | 4 +-- .../netlify/test/functions/edge-middleware.test.js | 4 +-- .../netlify/test/functions/image-cdn.test.js | 2 +- .../netlify/test/functions/redirects.test.js | 4 +-- 5 files changed, 20 insertions(+), 24 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index d411813de..6dccc4e51 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -99,7 +99,7 @@ export function remotePatternToRegex( return regexStr; } -async function writeNetlifyDeployConfig(config: AstroConfig, logger: AstroIntegrationLogger) { +async function writeNetlifyFrameworkConfig(config: AstroConfig, logger: AstroIntegrationLogger) { const remoteImages: Array = []; // Domains get a simple regex match remoteImages.push( @@ -113,7 +113,7 @@ async function writeNetlifyDeployConfig(config: AstroConfig, logger: AstroIntegr ); // See https://docs.netlify.com/image-cdn/create-integration/ - const deployConfigDir = new URL('.netlify/deploy/v1/', config.root); + const deployConfigDir = new URL('.netlify/v1/', config.root); await mkdir(deployConfigDir, { recursive: true }); await writeFile( new URL('./config.json', deployConfigDir), @@ -192,8 +192,8 @@ export default function netlifyIntegration( const TRACE_CACHE = {}; const ssrBuildDir = () => new URL('./.netlify/build/', rootDir); - const ssrOutputDir = () => new URL('./.netlify/functions-internal/ssr/', rootDir); - const middlewareOutputDir = () => new URL('.netlify/edge-functions/middleware/', rootDir); + const ssrOutputDir = () => new URL('./.netlify/v1/functions/ssr/', rootDir); + const middlewareOutputDir = () => new URL('.netlify/v1/edge-functions/middleware/', rootDir); const cleanFunctions = async () => await Promise.all([ @@ -250,19 +250,15 @@ export default function netlifyIntegration( cacheOnDemandPages: Boolean(integrationConfig?.cacheOnDemandPages), notFoundContent, })}); - export const config = { name: "Astro SSR", generator: "@astrojs/netlify@${packageVersion}", path: "/*", preferStatic: true }; - ` - ); - - await writeFile( - new URL('.netlify/functions-internal/ssr/ssr.json', rootDir), - JSON.stringify({ - config: { + export const config = { + includedFiles: ['**/*'], + name: 'Astro SSR', nodeBundler: 'none', - includedFiles: [fileURLToPath(new URL('.netlify/functions-internal/ssr/**/*', rootDir))], - }, - version: 1, - }) + generator: '@astrojs/netlify@${packageVersion}', + path: '/*', + preferStatic: true, + }; + ` ); } @@ -425,7 +421,7 @@ export default function netlifyIntegration( _config = config; if (config.image?.domains?.length || config.image?.remotePatterns?.length) { - await writeNetlifyDeployConfig(config, logger); + await writeNetlifyFrameworkConfig(config, logger); } const edgeMiddleware = integrationConfig?.edgeMiddleware ?? false; diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index 08a9da8ce..e848f6ac3 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -14,7 +14,7 @@ describe( it('Can set multiple', async () => { const entryURL = new URL( - './fixtures/cookies/.netlify/functions-internal/ssr/ssr.mjs', + './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', import.meta.url ); const { default: handler } = await import(entryURL); @@ -29,7 +29,7 @@ describe( it('renders dynamic 404 page', async () => { const entryURL = new URL( - './fixtures/cookies/.netlify/functions-internal/ssr/ssr.mjs', + './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', import.meta.url ); const { default: handler } = await import(entryURL); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index 8d85d167c..bb0286241 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -17,7 +17,7 @@ describe( it('emits no edge function', async () => { assert.equal( - fixture.pathExists('../.netlify/edge-functions/middleware/middleware.mjs'), + fixture.pathExists('../.netlify/v1/edge-functions/middleware/middleware.mjs'), false ); }); @@ -44,7 +44,7 @@ describe( it('emits an edge function', async () => { const contents = await fixture.readFile( - '../.netlify/edge-functions/middleware/middleware.mjs' + '../.netlify/v1/edge-functions/middleware/middleware.mjs' ); assert.equal(contents.includes('"Hello world"'), false); }); diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.js b/packages/integrations/netlify/test/functions/image-cdn.test.js index 2ed2705dc..e4981a51d 100644 --- a/packages/integrations/netlify/test/functions/image-cdn.test.js +++ b/packages/integrations/netlify/test/functions/image-cdn.test.js @@ -51,7 +51,7 @@ describe( const fixture = await loadFixture({ root }); await fixture.build(); - const config = await fixture.readFile('../.netlify/deploy/v1/config.json'); + const config = await fixture.readFile('../.netlify/v1/config.json'); if (config) { regexes = JSON.parse(config).images.remote_images.map((pattern) => new RegExp(pattern)); } diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index d6f9eed38..9ad00c58d 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -33,7 +33,7 @@ describe( it('renders static 404 page', async () => { const entryURL = new URL( - './fixtures/redirects/.netlify/functions-internal/ssr/ssr.mjs', + './fixtures/redirects/.netlify/v1/functions/ssr/ssr.mjs', import.meta.url ); const { default: handler } = await import(entryURL); @@ -53,7 +53,7 @@ describe( }); testServer.listen(5678); const entryURL = new URL( - './fixtures/redirects/.netlify/functions-internal/ssr/ssr.mjs', + './fixtures/redirects/.netlify/v1/functions/ssr/ssr.mjs', import.meta.url ); const { default: handler } = await import(entryURL); -- cgit v1.2.3 From 2000f523d19896095b0f75a345db9096d0344ef1 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Thu, 6 Feb 2025 13:24:08 +0000 Subject: chore: fix tests and build --- packages/integrations/netlify/package.json | 7 +- .../netlify/test/functions/cookies.test.js | 2 +- .../netlify/test/functions/edge-middleware.test.js | 2 +- .../netlify/test/functions/image-cdn.test.js | 2 +- .../netlify/test/functions/redirects.test.js | 2 +- .../netlify/test/hosted/hosted.test.js | 2 +- .../netlify/test/static/headers.test.js | 2 +- .../netlify/test/static/redirects.test.js | 2 +- packages/integrations/netlify/tsconfig.json | 2 +- pnpm-lock.yaml | 552 ++++++++++++++++++++- 10 files changed, 560 insertions(+), 15 deletions(-) (limited to 'packages/integrations/netlify/test/functions/edge-middleware.test.js') diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 7ea9d530e..a68201d21 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -24,14 +24,14 @@ }, "files": ["dist"], "scripts": { - "build": "tsc", + "build": "astro-scripts build \"src/**/*.ts\" && tsc", + "test": "astro-scripts test \"test/**/*.test.js\"", "test-fn": "astro-scripts test \"test/functions/*.test.js\"", "test-static": "astro-scripts test \"test/static/*.test.js\"", - "test": "pnpm run test-fn && pnpm run test-static", "test:hosted": "astro-scripts test \"test/hosted/*.test.js\"" }, "dependencies": { - "@astrojs/internal-helpers": "0.4.2", + "@astrojs/internal-helpers": "0.5.1", "@astrojs/underscore-redirects": "^0.6.0", "@netlify/functions": "^2.8.0", "@vercel/nft": "^0.29.0", @@ -42,7 +42,6 @@ "astro": "^5.0.0" }, "devDependencies": { - "@astrojs/test-utils": "workspace:*", "@netlify/edge-functions": "^2.11.1", "@netlify/edge-handler-types": "^0.34.1", "@types/node": "^22.10.6", diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js index e848f6ac3..9d2565d91 100644 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ b/packages/integrations/netlify/test/functions/cookies.test.js @@ -1,6 +1,6 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '@astrojs/test-utils'; +import { loadFixture } from '../../../../astro/test/test-utils.js'; describe( 'Cookies', diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js index bb0286241..683ec3b01 100644 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.js @@ -1,6 +1,6 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { loadFixture } from '@astrojs/test-utils'; +import { loadFixture } from '../../../../astro/test/test-utils.js'; describe( 'Middleware', diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.js b/packages/integrations/netlify/test/functions/image-cdn.test.js index e4981a51d..45b41e4de 100644 --- a/packages/integrations/netlify/test/functions/image-cdn.test.js +++ b/packages/integrations/netlify/test/functions/image-cdn.test.js @@ -1,7 +1,7 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { remotePatternToRegex } from '@astrojs/netlify'; -import { loadFixture } from '@astrojs/test-utils'; +import { loadFixture } from '../../../../astro/test/test-utils.js'; describe( 'Image CDN', diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js index 9ad00c58d..ac77056c4 100644 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ b/packages/integrations/netlify/test/functions/redirects.test.js @@ -1,7 +1,7 @@ import * as assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '@astrojs/test-utils'; +import { loadFixture } from '../../../../astro/test/test-utils.js'; describe( 'SSR - Redirects', diff --git a/packages/integrations/netlify/test/hosted/hosted.test.js b/packages/integrations/netlify/test/hosted/hosted.test.js index 2c40e3a69..b8562ce82 100644 --- a/packages/integrations/netlify/test/hosted/hosted.test.js +++ b/packages/integrations/netlify/test/hosted/hosted.test.js @@ -1,5 +1,5 @@ import * as assert from 'node:assert/strict'; -import { before, describe, it } from 'node:test'; +import { describe, it } from 'node:test'; const NETLIFY_TEST_URL = 'https://curious-boba-495d6d.netlify.app'; diff --git a/packages/integrations/netlify/test/static/headers.test.js b/packages/integrations/netlify/test/static/headers.test.js index 1a22e5151..5c1400098 100644 --- a/packages/integrations/netlify/test/static/headers.test.js +++ b/packages/integrations/netlify/test/static/headers.test.js @@ -1,6 +1,6 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '@astrojs/test-utils'; +import { loadFixture } from '../../../../astro/test/test-utils.js'; describe('SSG - headers', () => { let fixture; diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.js index 3cd69cbc0..cab954831 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.js @@ -1,6 +1,6 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '@astrojs/test-utils'; +import { loadFixture } from '../../../../astro/test/test-utils.js'; describe('SSG - Redirects', () => { let fixture; diff --git a/packages/integrations/netlify/tsconfig.json b/packages/integrations/netlify/tsconfig.json index 18443cddf..1504b4b6d 100644 --- a/packages/integrations/netlify/tsconfig.json +++ b/packages/integrations/netlify/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { "outDir": "./dist" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86eda9d02..6deb6c7a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4995,7 +4995,93 @@ importers: specifier: workspace:* version: link:../../../../../astro - packages/integrations/netlify: {} + packages/integrations/netlify: + dependencies: + '@astrojs/internal-helpers': + specifier: 0.5.1 + version: link:../../internal-helpers + '@astrojs/underscore-redirects': + specifier: ^0.6.0 + version: link:../../underscore-redirects + '@netlify/functions': + specifier: ^2.8.0 + version: 2.8.2 + '@vercel/nft': + specifier: ^0.29.0 + version: 0.29.1(rollup@4.34.2) + esbuild: + specifier: ^0.24.0 + version: 0.24.2 + vite: + specifier: ^6.0.7 + version: 6.0.11(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(sass@1.83.4)(yaml@2.5.1) + devDependencies: + '@netlify/edge-functions': + specifier: ^2.11.1 + version: 2.11.1 + '@netlify/edge-handler-types': + specifier: ^0.34.1 + version: 0.34.1 + '@types/node': + specifier: ^22.10.6 + version: 22.13.1 + astro: + specifier: ^5.1.6 + version: link:../../astro + astro-scripts: + specifier: workspace:* + version: link:../../../scripts + cheerio: + specifier: 1.0.0 + version: 1.0.0 + execa: + specifier: ^8.0.1 + version: 8.0.1 + fast-glob: + specifier: ^3.3.3 + version: 3.3.3 + strip-ansi: + specifier: ^7.1.0 + version: 7.1.0 + typescript: + specifier: ^5.7.3 + version: 5.7.3 + + packages/integrations/netlify/test/functions/fixtures/cookies: + dependencies: + '@astrojs/netlify': + specifier: 'workspace:' + version: link:../../../.. + + packages/integrations/netlify/test/functions/fixtures/middleware: + dependencies: + '@astrojs/netlify': + specifier: 'workspace:' + version: link:../../../.. + sharp: + specifier: ^0.33.5 + version: 0.33.5 + + packages/integrations/netlify/test/functions/fixtures/redirects: + dependencies: + '@astrojs/netlify': + specifier: 'workspace:' + version: link:../../../.. + + packages/integrations/netlify/test/hosted/hosted-astro-project: + dependencies: + '@astrojs/netlify': + specifier: workspace:* + version: link:../../.. + astro: + specifier: ^5.1.6 + version: link:../../../../../astro + + packages/integrations/netlify/test/static/fixtures/redirects: + dependencies: + '@astrojs/netlify': + specifier: 'workspace:' + version: link:../../../.. packages/integrations/node: dependencies: @@ -6432,6 +6518,9 @@ packages: '@emnapi/runtime@1.1.1': resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==} + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} @@ -6648,117 +6737,226 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-x64@0.33.3': resolution: {integrity: sha512-2QeSl7QDK9ru//YBT4sQkoq7L0EAJZA3rtV+v9p8xTKl4U1bUqTIaCnoC7Ctx2kCjQgwFXDasOtPTCT8eCTXvw==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [darwin] + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.0.2': resolution: {integrity: sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==} engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + '@img/sharp-libvips-darwin-x64@1.0.2': resolution: {integrity: sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==} engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [darwin] + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-linux-arm64@1.0.2': resolution: {integrity: sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==} engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + '@img/sharp-libvips-linux-arm@1.0.2': resolution: {integrity: sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==} engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm] os: [linux] + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + '@img/sharp-libvips-linux-s390x@1.0.2': resolution: {integrity: sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==} engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [s390x] os: [linux] + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + '@img/sharp-libvips-linux-x64@1.0.2': resolution: {integrity: sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==} engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + '@img/sharp-libvips-linuxmusl-arm64@1.0.2': resolution: {integrity: sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==} engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + '@img/sharp-libvips-linuxmusl-x64@1.0.2': resolution: {integrity: sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==} engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + '@img/sharp-linux-arm64@0.33.3': resolution: {integrity: sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + '@img/sharp-linux-arm@0.33.3': resolution: {integrity: sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==} engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm] os: [linux] + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + '@img/sharp-linux-s390x@0.33.3': resolution: {integrity: sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==} engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [s390x] os: [linux] + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + '@img/sharp-linux-x64@0.33.3': resolution: {integrity: sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + '@img/sharp-linuxmusl-arm64@0.33.3': resolution: {integrity: sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==} engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + '@img/sharp-linuxmusl-x64@0.33.3': resolution: {integrity: sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==} engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + '@img/sharp-wasm32@0.33.3': resolution: {integrity: sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [wasm32] + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + '@img/sharp-win32-ia32@0.33.3': resolution: {integrity: sha512-CyimAduT2whQD8ER4Ux7exKrtfoaUiVr7HG0zZvO0XTFn2idUWljjxv58GxNTkFb8/J9Ub9AqITGkJD6ZginxQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [ia32] os: [win32] + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + '@img/sharp-win32-x64@0.33.3': resolution: {integrity: sha512-viT4fUIDKnli3IfOephGnolMzhz5VaTvDRkYqtZxOMIoMQ4MrAziO7pT1nVnOt2FAm7qW5aa+CCc13aEY6Le0g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [win32] + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -6844,6 +7042,11 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@mapbox/node-pre-gyp@2.0.0': + resolution: {integrity: sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==} + engines: {node: '>=18'} + hasBin: true + '@markdoc/markdoc@0.4.0': resolution: {integrity: sha512-fSh4P3Y4E7oaKYc2oNzSIJVPDto7SMzAuQN1Iyx53UxzleA6QzRdNWRxmiPqtVDaDi5dELd2yICoG91csrGrAw==} engines: {node: '>=14.7.0'} @@ -6873,6 +7076,24 @@ packages: resolution: {integrity: sha512-9hIbusvAZjSGBJ42OyFC2AxsEph1LuKQahMWFcPGEIsOqIYHhMRkYA7wSUMhH7naydjNmllpcp3pJLOK4RhFaQ==} engines: {node: ^14.16.0 || >=16.0.0} + '@netlify/edge-functions@2.11.1': + resolution: {integrity: sha512-pyQOTZ8a+ge5lZlE+H/UAHyuqQqtL5gE0pXrHT9mOykr3YQqnkB2hZMtx12odatZ87gHg4EA+UPyMZUbLfnXvw==} + + '@netlify/edge-handler-types@0.34.1': + resolution: {integrity: sha512-YTwn8cw89M4lRTmoUhl9s8ljSGMDt7FOIsxsrx7YrRz/RZlbh4Yuh4RU13DDafDRBEVuRbjGo93cnN621ZfBjA==} + + '@netlify/functions@2.8.2': + resolution: {integrity: sha512-DeoAQh8LuNPvBE4qsKlezjKj0PyXDryOFJfJKo3Z1qZLKzQ21sT314KQKPVjfvw6knqijj+IO+0kHXy/TJiqNA==} + engines: {node: '>=14.0.0'} + + '@netlify/node-cookies@0.1.0': + resolution: {integrity: sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==} + engines: {node: ^14.16.0 || >=16.0.0} + + '@netlify/serverless-functions-api@1.26.1': + resolution: {integrity: sha512-q3L9i3HoNfz0SGpTIS4zTcKBbRkxzCRpd169eyiTuk3IwcPC3/85mzLHranlKo2b+HYT0gu37YxGB45aD8A3Tw==} + engines: {node: '>=18.0.0'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -7487,6 +7708,11 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vercel/nft@0.29.1': + resolution: {integrity: sha512-6239JJM1V9b3OjvZIjbe+47/hGxMr44FEzlbTErrqzebCaoKAHK+yvahn3gdNacybDUbTxVF8Zuh0vqaeM8aKQ==} + engines: {node: '>=18'} + hasBin: true + '@vitejs/plugin-react@4.3.4': resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} engines: {node: ^14.18.0 || >=16.0.0} @@ -7627,10 +7853,19 @@ packages: '@webcomponents/template-shadowroot@0.2.1': resolution: {integrity: sha512-fXL/vIUakyZL62hyvUh+EMwbVoTc0hksublmRz6ai6et8znHkJa6gtqMUZo1oc7dIz46exHSIImml9QTdknMHg==} + abbrev@3.0.0: + resolution: {integrity: sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==} + engines: {node: ^18.17.0 || >=20.5.0} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -7650,6 +7885,10 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -7743,6 +7982,9 @@ packages: resolution: {integrity: sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==} engines: {node: '>= 14'} + async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -7805,6 +8047,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + birpc@0.2.19: resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} @@ -7931,6 +8176,10 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -8631,6 +8880,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -8725,6 +8977,9 @@ packages: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -8781,6 +9036,10 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -8934,6 +9193,10 @@ packages: resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} engines: {node: '>= 14'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-id@1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} @@ -8977,6 +9240,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -9631,6 +9898,10 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} @@ -9642,6 +9913,11 @@ packages: engines: {node: '>=10'} hasBin: true + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -9738,6 +10014,11 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -9783,6 +10064,9 @@ packages: resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==} engines: {node: '>=9.4.0 || ^8.9.4'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -9920,6 +10204,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -10503,6 +10791,10 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + rollup@4.34.2: resolution: {integrity: sha512-sBDUoxZEaqLu9QeNalL8v3jw6WjPku4wfZGyTU7l7m1oC+rpRihXc/n/H+4148ZkGz5Xli8CHMns//fFGKvpIQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -10597,6 +10889,10 @@ packages: resolution: {integrity: sha512-vHUeXJU1UvlO/BNwTpT0x/r53WkLUVxrmb5JTgW92fdFCFk0ispLMAeu/jPO2vjkXM1fYUi3K7/qcLF47pwM1A==} engines: {libvips: '>=8.15.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -10853,6 +11149,10 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -11203,6 +11503,9 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + urlpattern-polyfill@8.0.2: + resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -11558,6 +11861,9 @@ packages: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.16.0: resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} engines: {node: '>=10.0.0'} @@ -11602,6 +11908,10 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml-language-server@1.15.0: resolution: {integrity: sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==} hasBin: true @@ -12506,6 +12816,11 @@ snapshots: tslib: 2.6.2 optional: true + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.6.2 + optional: true + '@esbuild/aix-ppc64@0.24.2': optional: true @@ -12645,76 +12960,151 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.0.2 optional: true + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + '@img/sharp-darwin-x64@0.33.3': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.0.2 optional: true + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + '@img/sharp-libvips-darwin-arm64@1.0.2': optional: true + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + '@img/sharp-libvips-darwin-x64@1.0.2': optional: true + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + '@img/sharp-libvips-linux-arm64@1.0.2': optional: true + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + '@img/sharp-libvips-linux-arm@1.0.2': optional: true + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + '@img/sharp-libvips-linux-s390x@1.0.2': optional: true + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + '@img/sharp-libvips-linux-x64@1.0.2': optional: true + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.0.2': optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + '@img/sharp-libvips-linuxmusl-x64@1.0.2': optional: true + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + '@img/sharp-linux-arm64@0.33.3': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.0.2 optional: true + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + '@img/sharp-linux-arm@0.33.3': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.0.2 optional: true + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + '@img/sharp-linux-s390x@0.33.3': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.2 optional: true + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + '@img/sharp-linux-x64@0.33.3': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.0.2 optional: true + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + '@img/sharp-linuxmusl-arm64@0.33.3': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.0.2 optional: true + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + '@img/sharp-linuxmusl-x64@0.33.3': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.0.2 optional: true + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + '@img/sharp-wasm32@0.33.3': dependencies: '@emnapi/runtime': 1.1.1 optional: true + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + '@img/sharp-win32-ia32@0.33.3': optional: true + '@img/sharp-win32-ia32@0.33.5': + optional: true + '@img/sharp-win32-x64@0.33.3': optional: true + '@img/sharp-win32-x64@0.33.5': + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -12724,6 +13114,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -12821,6 +13215,19 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@mapbox/node-pre-gyp@2.0.0': + dependencies: + consola: 3.2.3 + detect-libc: 2.0.3 + https-proxy-agent: 7.0.6 + node-fetch: 2.7.0 + nopt: 8.1.0 + semver: 7.7.1 + tar: 7.4.3 + transitivePeerDependencies: + - encoding + - supports-color + '@markdoc/markdoc@0.4.0(@types/react@18.3.18)(react@19.0.0)': optionalDependencies: '@types/markdown-it': 12.2.3 @@ -12866,6 +13273,23 @@ snapshots: '@netlify/blobs@8.1.0': {} + '@netlify/edge-functions@2.11.1': {} + + '@netlify/edge-handler-types@0.34.1': + dependencies: + web-streams-polyfill: 3.3.3 + + '@netlify/functions@2.8.2': + dependencies: + '@netlify/serverless-functions-api': 1.26.1 + + '@netlify/node-cookies@0.1.0': {} + + '@netlify/serverless-functions-api@1.26.1': + dependencies: + '@netlify/node-cookies': 0.1.0 + urlpattern-polyfill: 8.0.2 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -13331,7 +13755,7 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 18.19.50 + '@types/node': 22.13.1 '@types/semver@7.5.8': {} @@ -13356,7 +13780,7 @@ snapshots: '@types/ws@8.5.12': dependencies: - '@types/node': 18.19.50 + '@types/node': 22.13.1 '@types/xml2js@0.4.14': dependencies: @@ -13463,6 +13887,25 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vercel/nft@0.29.1(rollup@4.34.2)': + dependencies: + '@mapbox/node-pre-gyp': 2.0.0 + '@rollup/pluginutils': 5.1.4(rollup@4.34.2) + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + node-gyp-build: 4.8.2 + picomatch: 4.0.2 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - rollup + - supports-color + '@vitejs/plugin-react@4.3.4(vite@6.0.11(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(sass@1.83.4)(yaml@2.5.1))': dependencies: '@babel/core': 7.26.0 @@ -13697,11 +14140,17 @@ snapshots: '@webcomponents/template-shadowroot@0.2.1': {} + abbrev@3.0.0: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-import-attributes@1.9.5(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 @@ -13718,6 +14167,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.3: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -13809,6 +14260,8 @@ snapshots: async-listen@3.0.1: {} + async-sema@3.1.1: {} + asynckit@0.4.0: {} autocannon@7.15.0: @@ -13895,6 +14348,10 @@ snapshots: binary-extensions@2.3.0: {} + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + birpc@0.2.19: {} body-parser@1.20.3: @@ -14052,6 +14509,8 @@ snapshots: chownr@2.0.0: {} + chownr@3.0.0: {} + ci-info@3.9.0: {} ci-info@4.1.0: {} @@ -14684,6 +15143,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-uri-to-path@1.0.0: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -14784,6 +15245,8 @@ snapshots: dependencies: minipass: 3.3.6 + fs.realpath@1.0.0: {} + fsevents@2.3.2: optional: true @@ -14842,6 +15305,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@11.12.0: {} globals@14.0.0: {} @@ -15128,6 +15600,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + human-id@1.0.2: {} human-signals@5.0.0: {} @@ -15163,6 +15642,11 @@ snapshots: imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + inherits@2.0.4: {} inline-style-parser@0.1.1: {} @@ -16061,12 +16545,19 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + minizlib@3.0.1: + dependencies: + minipass: 7.1.2 + rimraf: 5.0.10 + mitt@3.0.1: {} mj-context-menu@0.6.1: {} mkdirp@1.0.4: {} + mkdirp@3.0.1: {} + mri@1.2.0: {} mrmime@2.0.0: {} @@ -16145,6 +16636,10 @@ snapshots: node-releases@2.0.18: {} + nopt@8.1.0: + dependencies: + abbrev: 3.0.0 + normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -16182,6 +16677,10 @@ snapshots: on-net-listen@1.1.2: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -16327,6 +16826,8 @@ snapshots: path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -17028,6 +17529,10 @@ snapshots: rfdc@1.4.1: {} + rimraf@5.0.10: + dependencies: + glob: 10.4.5 + rollup@4.34.2: dependencies: '@types/estree': 1.0.6 @@ -17185,6 +17690,32 @@ snapshots: '@img/sharp-win32-ia32': 0.33.3 '@img/sharp-win32-x64': 0.33.3 + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.7.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -17505,6 +18036,15 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 + term-size@2.2.1: {} terminal-link@3.0.0: @@ -17793,6 +18333,8 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 + urlpattern-polyfill@8.0.2: {} + util-deprecate@1.0.2: {} utils-merge@1.0.1: {} @@ -18172,6 +18714,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 + wrappy@1.0.2: {} + ws@8.16.0: {} xml-name-validator@5.0.0: {} @@ -18195,6 +18739,8 @@ snapshots: yallist@4.0.0: {} + yallist@5.0.0: {} + yaml-language-server@1.15.0: dependencies: ajv: 8.17.1 -- cgit v1.2.3