From 209a0c643d7550b8c02bafe414501e7b47be2690 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Tue, 24 May 2022 15:27:59 -0400 Subject: [PATCH 01/24] feat: added better custom header support --- plugin/src/helpers/config.ts | 44 +++++++++++++++++++++++++++++++++--- plugin/src/index.ts | 7 +++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 53551a8ae0..9d97d64da1 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -1,10 +1,20 @@ +import type { NetlifyConfig } from '@netlify/build' import { readJSON, writeJSON } from 'fs-extra' +import type { Header } from 'next/dist/lib/load-custom-routes' import type { NextConfigComplete } from 'next/dist/server/config-shared' import { join, dirname, relative } from 'pathe' import slash from 'slash' import { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME } from '../constants' +const ROUTES_MANIFEST_FILE = 'routes-manifest.json' + +type NetlifyHeaders = NetlifyConfig['headers'] + +// This is the minimal amount of typing required for now +// add other properties as needed from the routes-manifest.json +export type RoutesManifest = { headers: Header[] } + export interface RequiredServerFiles { version?: number config?: NextConfigComplete @@ -13,7 +23,10 @@ export interface RequiredServerFiles { ignore?: string[] } -export type NextConfig = Pick & NextConfigComplete +export type NextConfig = Pick & + NextConfigComplete & { + routesManifest?: RoutesManifest + } const defaultFailBuild = (message: string, { error }): never => { throw new Error(`${message}\n${error && error.stack}`) @@ -24,13 +37,19 @@ export const getNextConfig = async function getNextConfig({ failBuild = defaultFailBuild, }): Promise { try { - const { config, appDir, ignore }: RequiredServerFiles = await readJSON(join(publish, 'required-server-files.json')) + const { config, appDir, ignore, files }: RequiredServerFiles = await readJSON( + join(publish, 'required-server-files.json'), + ) if (!config) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return failBuild('Error loading your Next config') } - return { ...config, appDir, ignore } + + const routesManifest = (await readJSON(files.find((f) => f.endsWith(ROUTES_MANIFEST_FILE)))) as RoutesManifest + + // If you need access to other manifest files, you can add them here as well + return { ...config, appDir, ignore, routesManifest } } catch (error: unknown) { return failBuild('Error loading your Next config', { error }) } @@ -108,3 +127,22 @@ export const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) }) } + +/** + * Persist NEXT.js custom headers to the Netlify configuration so the headers work with static files + * + * @param customHeaders - Custom headers defined in the Next.js configuration + * @param netlifyHeaders - Existing headers that are already configured in the Netlify configuration + */ +export const generateCustomHeaders = (customHeaders: Header[] = [], netlifyHeaders: NetlifyHeaders = []) => { + for (const { source, headers } of customHeaders) { + netlifyHeaders.push({ + for: source, + values: headers.reduce((builtHeaders, { key, value }) => { + builtHeaders[key] = value + + return builtHeaders + }, {}), + }) + } +} diff --git a/plugin/src/index.ts b/plugin/src/index.ts index 0e12bb11ce..9231738140 100644 --- a/plugin/src/index.ts +++ b/plugin/src/index.ts @@ -12,6 +12,7 @@ import { getRequiredServerFiles, updateRequiredServerFiles, configureHandlerFunctions, + generateCustomHeaders, } from './helpers/config' import { updateConfig, writeMiddleware } from './helpers/edge' import { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } from './helpers/files' @@ -136,6 +137,7 @@ const plugin: NetlifyPlugin = { netlifyConfig: { build: { publish }, redirects, + headers, }, utils: { status, @@ -161,7 +163,10 @@ const plugin: NetlifyPlugin = { await checkForOldFunctions({ functions }) await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`)) - const { basePath, appDir } = await getNextConfig({ publish, failBuild }) + const { basePath, appDir, routesManifest } = await getNextConfig({ publish, failBuild }) + + generateCustomHeaders(routesManifest.headers, headers) + warnForProblematicUserRewrites({ basePath, redirects }) warnForRootRedirects({ appDir }) await unpatchNextFiles(basePath) From 9761de1dbbe2837cd47e08ca26a74d41392fae3c Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Tue, 24 May 2022 17:04:17 -0400 Subject: [PATCH 02/24] test: added some tests for custom headers --- test/__snapshots__/index.js.snap | 42 ++++++++++++++ test/index.js | 95 ++++++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index 59ea733fef..9787c0c7e5 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -1,5 +1,47 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`custom headers appends custom headers in the Netlify configuration 1`] = ` +Array [ + Object { + "for": "/", + "values": Object { + "X-Existing-Header": "true", + }, + }, + Object { + "for": "/", + "values": Object { + "X-Unit-Test": "true", + }, + }, + Object { + "for": "/unit-test", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, + }, +] +`; + +exports[`custom headers sets custom headers in the Netlify configuration 1`] = ` +Array [ + Object { + "for": "/", + "values": Object { + "X-Unit-Test": "true", + }, + }, + Object { + "for": "/unit-test", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, + }, +] +`; + exports[`onBuild() generates a file referencing all page sources 1`] = ` "// This file is purely to allow nft to know about these pages. It should be temporary. exports.resolvePages = () => { diff --git a/test/index.js b/test/index.js index 8d697aea8e..88778bb090 100644 --- a/test/index.js +++ b/test/index.js @@ -26,9 +26,14 @@ const { patchNextFiles, unpatchNextFiles, } = require('../plugin/src/helpers/files') -const { getRequiredServerFiles, updateRequiredServerFiles } = require('../plugin/src/helpers/config') +const { + getRequiredServerFiles, + updateRequiredServerFiles, + generateCustomHeaders, +} = require('../plugin/src/helpers/config') const { dirname } = require('path') const { getProblematicUserRewrites } = require('../plugin/src/helpers/verification') +const { onPostBuild } = require('../plugin/lib') const chance = new Chance() const FIXTURES_DIR = `${__dirname}/fixtures` @@ -269,11 +274,11 @@ describe('onBuild()', () => { process.env.URL = mockSiteUrl await moveNextDist() - + const initialConfig = await getRequiredServerFiles(netlifyConfig.build.publish) - initialConfig.config.basePath = "/foo" + initialConfig.config.basePath = '/foo' await updateRequiredServerFiles(netlifyConfig.build.publish, initialConfig) - + await plugin.onBuild(defaultArgs) expect(onBuildHasRun(netlifyConfig)).toBe(true) @@ -809,3 +814,85 @@ describe('function helpers', () => { ) }) }) + +describe('custom headers', () => { + it('sets custom headers in the Netlify configuration', async () => { + netlifyConfig.headers = [] + const headers = [ + // single header for a route + { + source: '/', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/unit-test', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ] + + generateCustomHeaders(headers, netlifyConfig.headers) + + expect(netlifyConfig.headers).toMatchSnapshot() + }) + + it('appends custom headers in the Netlify configuration', async () => { + netlifyConfig.headers = [ + { + for: '/', + values: { + 'X-Existing-Header': 'true', + }, + }, + ] + + const headers = [ + // single header for a route + { + source: '/', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/unit-test', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ] + + generateCustomHeaders(headers, netlifyConfig.headers) + + expect(netlifyConfig.headers).toMatchSnapshot() + }) +}) From 8dcf91a55d26fdb065be32a6543cdd3126a31571 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Tue, 24 May 2022 17:36:18 -0400 Subject: [PATCH 03/24] fix: fixed issue if there is no routes manifest --- plugin/src/helpers/config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 9d97d64da1..8a4128782e 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -46,7 +46,8 @@ export const getNextConfig = async function getNextConfig({ return failBuild('Error loading your Next config') } - const routesManifest = (await readJSON(files.find((f) => f.endsWith(ROUTES_MANIFEST_FILE)))) as RoutesManifest + const routeManifestFile = files.find((f) => f.endsWith(ROUTES_MANIFEST_FILE)) + const routesManifest = (routeManifestFile ? await readJSON(routeManifestFile) : {}) as RoutesManifest // If you need access to other manifest files, you can add them here as well return { ...config, appDir, ignore, routesManifest } From 32020751dd43782fb57512b49d755ccdeabe29fd Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Tue, 24 May 2022 18:02:52 -0400 Subject: [PATCH 04/24] test: added custom header E2E test --- cypress/integration/default/custom-headers.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 cypress/integration/default/custom-headers.spec.ts diff --git a/cypress/integration/default/custom-headers.spec.ts b/cypress/integration/default/custom-headers.spec.ts new file mode 100644 index 0000000000..39e0b88b3d --- /dev/null +++ b/cypress/integration/default/custom-headers.spec.ts @@ -0,0 +1,7 @@ +describe('custom headers', () => { + it('should load custom headers', () => { + cy.request('/').then((request) => { + cy.wrap(request.headers['x-custom-header']).should('equal', 'my custom header value') + }) + }) +}) From 87880608510661b6f10b28ea5907da2588a792fa Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Wed, 25 May 2022 07:32:24 -0400 Subject: [PATCH 05/24] chore: updated type for the route manifest --- plugin/src/helpers/config.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 8a4128782e..9a9e72ce27 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -7,14 +7,12 @@ import slash from 'slash' import { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME } from '../constants' +import type { RoutesManifest } from './types' + const ROUTES_MANIFEST_FILE = 'routes-manifest.json' type NetlifyHeaders = NetlifyConfig['headers'] -// This is the minimal amount of typing required for now -// add other properties as needed from the routes-manifest.json -export type RoutesManifest = { headers: Header[] } - export interface RequiredServerFiles { version?: number config?: NextConfigComplete From 33f135afdfeb3c847b7d32d4fe8b7734db21ba2f Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Wed, 25 May 2022 07:57:45 -0400 Subject: [PATCH 06/24] chore: updated loading of routes manifest --- plugin/src/helpers/config.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 9a9e72ce27..7d918fd596 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -35,17 +35,14 @@ export const getNextConfig = async function getNextConfig({ failBuild = defaultFailBuild, }): Promise { try { - const { config, appDir, ignore, files }: RequiredServerFiles = await readJSON( - join(publish, 'required-server-files.json'), - ) + const { config, appDir, ignore }: RequiredServerFiles = await readJSON(join(publish, 'required-server-files.json')) if (!config) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return failBuild('Error loading your Next config') } - const routeManifestFile = files.find((f) => f.endsWith(ROUTES_MANIFEST_FILE)) - const routesManifest = (routeManifestFile ? await readJSON(routeManifestFile) : {}) as RoutesManifest + const routesManifest: RoutesManifest = await readJSON(join(publish, ROUTES_MANIFEST_FILE)) // If you need access to other manifest files, you can add them here as well return { ...config, appDir, ignore, routesManifest } From 0d49d3ead9787a10622dbc9bbb5005da2c3ea5e3 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Wed, 25 May 2022 10:25:44 -0400 Subject: [PATCH 07/24] test: some tests are no longer async --- test/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index 88778bb090..224601efae 100644 --- a/test/index.js +++ b/test/index.js @@ -816,7 +816,7 @@ describe('function helpers', () => { }) describe('custom headers', () => { - it('sets custom headers in the Netlify configuration', async () => { + it('sets custom headers in the Netlify configuration', () => { netlifyConfig.headers = [] const headers = [ // single header for a route @@ -852,7 +852,7 @@ describe('custom headers', () => { expect(netlifyConfig.headers).toMatchSnapshot() }) - it('appends custom headers in the Netlify configuration', async () => { + it('appends custom headers in the Netlify configuration', () => { netlifyConfig.headers = [ { for: '/', From ebcb1050b7ee2242b89e63bccd9e293ff26dc546 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Wed, 25 May 2022 14:44:57 -0400 Subject: [PATCH 08/24] test: some more test for custom headers --- .../default/custom-headers.spec.ts | 25 ++++++++++++++++++- demos/default/next.config.js | 18 +++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/cypress/integration/default/custom-headers.spec.ts b/cypress/integration/default/custom-headers.spec.ts index 39e0b88b3d..a11015f898 100644 --- a/cypress/integration/default/custom-headers.spec.ts +++ b/cypress/integration/default/custom-headers.spec.ts @@ -1,7 +1,30 @@ describe('custom headers', () => { - it('should load custom headers', () => { + it('should load custom headers for an SSG page', () => { cy.request('/').then((request) => { cy.wrap(request.headers['x-custom-header']).should('equal', 'my custom header value') + cy.wrap(request.headers['x-custom-header-for-everything']).should( + 'equal', + 'my custom header for everything value', + ) + }) + }) + + it('should load custom headers for a Netlify function', () => { + cy.request('/api/hello/').then((request) => { + cy.wrap(request.headers['x-custom-api-header']).should('equal', 'my custom api header value') + cy.wrap(request.headers['x-custom-header-for-everything']).should( + 'equal', + 'my custom header for everything value', + ) + }) + }) + + it('should load custom headers for an SSR page', () => { + cy.request('/shows/250').then((request) => { + cy.wrap(request.headers['x-custom-header-for-everything']).should( + 'equal', + 'my custom header for everything value', + ) }) }) }) diff --git a/demos/default/next.config.js b/demos/default/next.config.js index 0407f86a63..f56fd31259 100644 --- a/demos/default/next.config.js +++ b/demos/default/next.config.js @@ -19,6 +19,24 @@ module.exports = { }, ], }, + { + source: '/api/:path*', + headers: [ + { + key: 'x-custom-api-header', + value: 'my custom api header value', + }, + ], + }, + { + source: '/:path*', + headers: [ + { + key: 'x-custom-header-for-everything', + value: 'my custom header for everything value', + }, + ], + }, ] }, trailingSlash: true, From 4491a1dd7e7e23c7cd64b081d0eec3a6828d3c11 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Fri, 27 May 2022 12:03:50 -0400 Subject: [PATCH 09/24] test: added some tests for custom headers --- plugin/src/helpers/config.ts | 3 +- test/__snapshots__/index.js.snap | 24 +++++++++++++++ test/index.js | 50 ++++++++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 7d918fd596..26c4990e34 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -133,7 +133,8 @@ export const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] export const generateCustomHeaders = (customHeaders: Header[] = [], netlifyHeaders: NetlifyHeaders = []) => { for (const { source, headers } of customHeaders) { netlifyHeaders.push({ - for: source, + // Replace the pattern :path* with * since it's a named splat + for: source.replace(/:[^*]+\*/g, '*'), values: headers.reduce((builtHeaders, { key, value }) => { builtHeaders[key] = value diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index 9787c0c7e5..50268eb69a 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -42,6 +42,30 @@ Array [ ] `; +exports[`custom headers sets custom headers using a splat instead of a named splat in the Netlify configuration 1`] = ` +Array [ + Object { + "for": "/*", + "values": Object { + "X-Unit-Test": "true", + }, + }, + Object { + "for": "/some-other-path/*", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, + }, + Object { + "for": "/some-other-path/*/yolo/:path", + "values": Object { + "X-Another-Unit-Test": "true", + }, + }, +] +`; + exports[`onBuild() generates a file referencing all page sources 1`] = ` "// This file is purely to allow nft to know about these pages. It should be temporary. exports.resolvePages = () => { diff --git a/test/index.js b/test/index.js index 224601efae..f37f2eb10e 100644 --- a/test/index.js +++ b/test/index.js @@ -237,9 +237,9 @@ describe('onBuild()', () => { process.env.URL = chance.url() await moveNextDist() - + const initialConfig = await getRequiredServerFiles(netlifyConfig.build.publish) - + initialConfig.config.env.NEXTAUTH_URL = mockUserDefinedSiteUrl await updateRequiredServerFiles(netlifyConfig.build.publish, initialConfig) @@ -852,6 +852,52 @@ describe('custom headers', () => { expect(netlifyConfig.headers).toMatchSnapshot() }) + it('sets custom headers using a splat instead of a named splat in the Netlify configuration', () => { + netlifyConfig.headers = [] + const headers = [ + // single header for a route + { + source: '/:path*', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/some-other-path/:path*', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + { + source: '/some-other-path/:path*/yolo/:path', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ] + + generateCustomHeaders(headers, netlifyConfig.headers) + + expect(netlifyConfig.headers).toMatchSnapshot() + }) + it('appends custom headers in the Netlify configuration', () => { netlifyConfig.headers = [ { From deb269a1475504e86232195935e0ea511f2595b6 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Fri, 27 May 2022 13:34:44 -0400 Subject: [PATCH 10/24] test: removed redundant test --- cypress/integration/default/headers.spec.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 cypress/integration/default/headers.spec.ts diff --git a/cypress/integration/default/headers.spec.ts b/cypress/integration/default/headers.spec.ts deleted file mode 100644 index 2b5551ecae..0000000000 --- a/cypress/integration/default/headers.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -describe('Headers', () => { - it('should set headers from the next.config.js', () => { - cy.request({ - url: '/', - }).then((response) => { - expect(response.headers).to.have.property('x-custom-header', 'my custom header value') - }) - }) -}) \ No newline at end of file From 0be2f283fa3b6a440be6c3ace820e0f1f2ca935d Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Mon, 30 May 2022 15:01:44 -0400 Subject: [PATCH 11/24] feat: added support for basePath in custom headers --- plugin/src/helpers/config.ts | 41 ++++++++++++++++++++++++------------ plugin/src/index.ts | 6 ++++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 26c4990e34..b3405305b7 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -1,6 +1,5 @@ import type { NetlifyConfig } from '@netlify/build' import { readJSON, writeJSON } from 'fs-extra' -import type { Header } from 'next/dist/lib/load-custom-routes' import type { NextConfigComplete } from 'next/dist/server/config-shared' import { join, dirname, relative } from 'pathe' import slash from 'slash' @@ -127,19 +126,35 @@ export const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] /** * Persist NEXT.js custom headers to the Netlify configuration so the headers work with static files * - * @param customHeaders - Custom headers defined in the Next.js configuration + * @param nextConfig - The NextJS configuration * @param netlifyHeaders - Existing headers that are already configured in the Netlify configuration */ -export const generateCustomHeaders = (customHeaders: Header[] = [], netlifyHeaders: NetlifyHeaders = []) => { - for (const { source, headers } of customHeaders) { - netlifyHeaders.push({ - // Replace the pattern :path* with * since it's a named splat - for: source.replace(/:[^*]+\*/g, '*'), - values: headers.reduce((builtHeaders, { key, value }) => { - builtHeaders[key] = value - - return builtHeaders - }, {}), - }) +export const generateCustomHeaders = (nextConfig: NextConfig, netlifyHeaders: NetlifyHeaders = []) => { + const { + basePath = '', + routesManifest: { headers: customHeaders = [] }, + } = nextConfig + + for (const { source, headers, has, basePath: useBasePath } of customHeaders) { + // Skip has based routes as they are more complex dynamic conditional header logic + // that currently isn't supported by the Netlify configuration. + // Also, this type of dynamic header logic is most likely not for static + if (!has) { + // Explicitly checkling false to make the check simpler. + // Basepath is excluded only if useBasePath is false. There is no true value for useBasePath. It's either false or undefined. + // eslint-disable-next-line no-negated-condition + const pathPrefix = useBasePath !== false ? basePath : '' + const path = pathPrefix + source.replace(/:[^*/]+\*$/, '*') + + netlifyHeaders.push({ + // Replace the pattern :path* at the end of a path with * since it's a named splat + for: path, + values: headers.reduce((builtHeaders, { key, value }) => { + builtHeaders[key] = value + + return builtHeaders + }, {}), + }) + } } } diff --git a/plugin/src/index.ts b/plugin/src/index.ts index 9231738140..834e28c060 100644 --- a/plugin/src/index.ts +++ b/plugin/src/index.ts @@ -163,9 +163,11 @@ const plugin: NetlifyPlugin = { await checkForOldFunctions({ functions }) await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`)) - const { basePath, appDir, routesManifest } = await getNextConfig({ publish, failBuild }) + const nextConfig = await getNextConfig({ publish, failBuild }) - generateCustomHeaders(routesManifest.headers, headers) + const { basePath, appDir } = nextConfig + + generateCustomHeaders(nextConfig, headers) warnForProblematicUserRewrites({ basePath, redirects }) warnForRootRedirects({ appDir }) From 26c5b1bb788524805535e81d1436ce9b170234cc Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Mon, 30 May 2022 15:01:52 -0400 Subject: [PATCH 12/24] test: tests for basePath in custom headers --- test/__snapshots__/index.js.snap | 66 ----- test/index.js | 414 +++++++++++++++++++++++-------- 2 files changed, 315 insertions(+), 165 deletions(-) diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index 50268eb69a..59ea733fef 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -1,71 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`custom headers appends custom headers in the Netlify configuration 1`] = ` -Array [ - Object { - "for": "/", - "values": Object { - "X-Existing-Header": "true", - }, - }, - Object { - "for": "/", - "values": Object { - "X-Unit-Test": "true", - }, - }, - Object { - "for": "/unit-test", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, - }, -] -`; - -exports[`custom headers sets custom headers in the Netlify configuration 1`] = ` -Array [ - Object { - "for": "/", - "values": Object { - "X-Unit-Test": "true", - }, - }, - Object { - "for": "/unit-test", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, - }, -] -`; - -exports[`custom headers sets custom headers using a splat instead of a named splat in the Netlify configuration 1`] = ` -Array [ - Object { - "for": "/*", - "values": Object { - "X-Unit-Test": "true", - }, - }, - Object { - "for": "/some-other-path/*", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, - }, - Object { - "for": "/some-other-path/*/yolo/:path", - "values": Object { - "X-Another-Unit-Test": "true", - }, - }, -] -`; - exports[`onBuild() generates a file referencing all page sources 1`] = ` "// This file is purely to allow nft to know about these pages. It should be temporary. exports.resolvePages = () => { diff --git a/test/index.js b/test/index.js index f37f2eb10e..51942afc8c 100644 --- a/test/index.js +++ b/test/index.js @@ -34,6 +34,7 @@ const { const { dirname } = require('path') const { getProblematicUserRewrites } = require('../plugin/src/helpers/verification') const { onPostBuild } = require('../plugin/lib') +const { basePath } = require('../demos/next-i18next/next.config') const chance = new Chance() const FIXTURES_DIR = `${__dirname}/fixtures` @@ -813,132 +814,347 @@ describe('function helpers', () => { 'Failed to download https://example.com/nonexistentfile: 404 Not Found', ) }) -}) -describe('custom headers', () => { - it('sets custom headers in the Netlify configuration', () => { - netlifyConfig.headers = [] - const headers = [ - // single header for a route - { - source: '/', - headers: [ - { - key: 'X-Unit-Test', - value: 'true', + describe('config', () => { + describe('generateCustomHeaders', () => { + it('sets custom headers in the Netlify configuration', () => { + netlifyConfig.headers = [] + + const nextConfig = { + routesManifest: { + headers: [ + // single header for a route + { + source: '/', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/unit-test', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ], }, - ], - regex: '^/(?:/)?$', - }, - // multiple headers for a route - { - source: '/unit-test', - headers: [ + } + + generateCustomHeaders(nextConfig, netlifyConfig.headers) + + expect(netlifyConfig.headers).toEqual([ { - key: 'X-Another-Unit-Test', - value: 'true', + for: '/', + values: { + 'X-Unit-Test': 'true', + }, }, { - key: 'X-Another-Unit-Test-Again', - value: 'true', + for: '/unit-test', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', + }, }, - ], - regex: '^/(?:/)?$', - }, - ] - - generateCustomHeaders(headers, netlifyConfig.headers) + ]) + }) + + it('sets custom headers using a splat instead of a named splat in the Netlify configuration', () => { + netlifyConfig.headers = [] + + const nextConfig = { + routesManifest: { + headers: [ + // single header for a route + { + source: '/:path*', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/some-other-path/:path*', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + { + source: '/some-other-path/yolo/:path*', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ], + }, + } - expect(netlifyConfig.headers).toMatchSnapshot() - }) + generateCustomHeaders(nextConfig, netlifyConfig.headers) - it('sets custom headers using a splat instead of a named splat in the Netlify configuration', () => { - netlifyConfig.headers = [] - const headers = [ - // single header for a route - { - source: '/:path*', - headers: [ + expect(netlifyConfig.headers).toEqual([ { - key: 'X-Unit-Test', - value: 'true', + for: '/*', + values: { + 'X-Unit-Test': 'true', + }, }, - ], - regex: '^/(?:/)?$', - }, - // multiple headers for a route - { - source: '/some-other-path/:path*', - headers: [ { - key: 'X-Another-Unit-Test', - value: 'true', + for: '/some-other-path/*', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', + }, }, { - key: 'X-Another-Unit-Test-Again', - value: 'true', + for: '/some-other-path/yolo/*', + values: { + 'X-Another-Unit-Test': 'true', + }, }, - ], - regex: '^/(?:/)?$', - }, - { - source: '/some-other-path/:path*/yolo/:path', - headers: [ + ]) + }) + + it('appends custom headers in the Netlify configuration', () => { + netlifyConfig.headers = [ { - key: 'X-Another-Unit-Test', - value: 'true', + for: '/', + values: { + 'X-Existing-Header': 'true', + }, }, - ], - regex: '^/(?:/)?$', - }, - ] + ] + + const nextConfig = { + routesManifest: { + headers: [ + // single header for a route + { + source: '/', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/unit-test', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ], + }, + } - generateCustomHeaders(headers, netlifyConfig.headers) + generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchSnapshot() - }) + expect(netlifyConfig.headers).toEqual([ + { + for: '/', + values: { + 'X-Existing-Header': 'true', + }, + }, + { + for: '/', + values: { + 'X-Unit-Test': 'true', + }, + }, + { + for: '/unit-test', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', + }, + }, + ]) + }) + + it('sets custom headers using basePath in the Next.js configuration', () => { + netlifyConfig.headers = [] + + const nextConfig = { + basePath: '/base-path', + routesManifest: { + headers: [ + // single header for a route + { + source: '/:path*', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/some-other-path/:path*', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + { + source: '/some-other-path/yolo/:path*', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ], + }, + } - it('appends custom headers in the Netlify configuration', () => { - netlifyConfig.headers = [ - { - for: '/', - values: { - 'X-Existing-Header': 'true', - }, - }, - ] + generateCustomHeaders(nextConfig, netlifyConfig.headers) - const headers = [ - // single header for a route - { - source: '/', - headers: [ + expect(netlifyConfig.headers).toEqual([ { - key: 'X-Unit-Test', - value: 'true', + for: '/base-path/*', + values: { + 'X-Unit-Test': 'true', + }, }, - ], - regex: '^/(?:/)?$', - }, - // multiple headers for a route - { - source: '/unit-test', - headers: [ { - key: 'X-Another-Unit-Test', - value: 'true', + for: '/base-path/some-other-path/*', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', + }, }, { - key: 'X-Another-Unit-Test-Again', - value: 'true', + for: '/base-path/some-other-path/yolo/*', + values: { + 'X-Another-Unit-Test': 'true', + }, }, - ], - regex: '^/(?:/)?$', - }, - ] + ]) + }) + + it('sets custom headers omitting basePath when a header has basePath set to false', () => { + netlifyConfig.headers = [] + + const nextConfig = { + basePath: '/base-path', + routesManifest: { + headers: [ + // single header for a route + { + source: '/:path*', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + basePath: false, + regex: '^/(?:/)?$', + }, + // multiple headers for a route + { + source: '/some-other-path/:path*', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + { + key: 'X-Another-Unit-Test-Again', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + { + source: '/some-other-path/yolo/:path*', + headers: [ + { + key: 'X-Another-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ], + }, + } - generateCustomHeaders(headers, netlifyConfig.headers) + generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchSnapshot() + expect(netlifyConfig.headers).toEqual([ + { + for: '/*', + values: { + 'X-Unit-Test': 'true', + }, + }, + { + for: '/base-path/some-other-path/*', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', + }, + }, + { + for: '/base-path/some-other-path/yolo/*', + values: { + 'X-Another-Unit-Test': 'true', + }, + }, + ]) + }) + }) }) }) From 1cc650665c77a1ae40a8ad77687aa01d20a91675 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Mon, 30 May 2022 21:20:03 -0400 Subject: [PATCH 13/24] feat: implemented locale paths for custom headers in the Netlify configuration --- plugin/src/helpers/config.ts | 70 ++++++--- test/index.js | 281 ++++++++++++++++++++++++----------- 2 files changed, 248 insertions(+), 103 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index b3405305b7..d15145f5cd 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -1,5 +1,6 @@ import type { NetlifyConfig } from '@netlify/build' import { readJSON, writeJSON } from 'fs-extra' +import type { Header } from 'next/dist/lib/load-custom-routes' import type { NextConfigComplete } from 'next/dist/server/config-shared' import { join, dirname, relative } from 'pathe' import slash from 'slash' @@ -123,8 +124,30 @@ export const configureHandlerFunctions = ({ netlifyConfig, publish, ignore = [] }) } +interface BuildHeaderParams { + path: string + headers: Header['headers'] + locale?: string +} + +const buildHeader = (buildHeaderParams: BuildHeaderParams) => { + const { locale, path, headers } = buildHeaderParams + const localePart = locale ? `/${locale}` : '' + + return { + for: `${localePart}${path}`, + values: headers.reduce((builtHeaders, { key, value }) => { + builtHeaders[key] = value + + return builtHeaders + }, {}), + } +} + /** * Persist NEXT.js custom headers to the Netlify configuration so the headers work with static files + * See {@link https://nextjs.org/docs/api-reference/next.config.js/headers} for more information on custom + * headers in Next.js * * @param nextConfig - The NextJS configuration * @param netlifyHeaders - Existing headers that are already configured in the Netlify configuration @@ -133,28 +156,35 @@ export const generateCustomHeaders = (nextConfig: NextConfig, netlifyHeaders: Ne const { basePath = '', routesManifest: { headers: customHeaders = [] }, + i18n, } = nextConfig - for (const { source, headers, has, basePath: useBasePath } of customHeaders) { - // Skip has based routes as they are more complex dynamic conditional header logic - // that currently isn't supported by the Netlify configuration. - // Also, this type of dynamic header logic is most likely not for static - if (!has) { - // Explicitly checkling false to make the check simpler. - // Basepath is excluded only if useBasePath is false. There is no true value for useBasePath. It's either false or undefined. - // eslint-disable-next-line no-negated-condition - const pathPrefix = useBasePath !== false ? basePath : '' - const path = pathPrefix + source.replace(/:[^*/]+\*$/, '*') - - netlifyHeaders.push({ - // Replace the pattern :path* at the end of a path with * since it's a named splat - for: path, - values: headers.reduce((builtHeaders, { key, value }) => { - builtHeaders[key] = value - - return builtHeaders - }, {}), - }) + // Skip `has` based custom headers as they have more complex dynamic conditional header logic + // that currently isn't supported by the Netlify configuration. + // Also, this type of dynamic header logic is most likely not for SSG pages. + for (const { source, headers, basePath: useBasePath, locale: localeEnabled } of customHeaders.filter( + (customHeader) => !customHeader.has, + )) { + // Explicitly checkling false to make the check simpler. + // Locale specific paths are excluded only if localeEnabled is false. There is no true value for localeEnabled. It's either + // false or undefined, where undefined means it's true. + const useLocale = i18n?.locales?.length > 0 && localeEnabled !== false + + // Explicitly checkling false to make the check simpler. + // Basepath is excluded only if useBasePath is false. There is no true value for useBasePath. It's either false or undefined, + // where undefined means it's true. + // eslint-disable-next-line no-negated-condition + const pathPrefix = useBasePath !== false ? basePath : '' + + // Replace the pattern :path* at the end of a path with * since it's a named splat + const path = pathPrefix + source.replace(/:[^*/]+\*$/, '*') + + if (useLocale) { + for (const locale of i18n.locales) { + netlifyHeaders.push(buildHeader({ path, headers, locale })) + } + } else { + netlifyHeaders.push(buildHeader({ path, headers })) } } } diff --git a/test/index.js b/test/index.js index 51942afc8c..135b759cba 100644 --- a/test/index.js +++ b/test/index.js @@ -855,21 +855,23 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toEqual([ - { - for: '/', - values: { - 'X-Unit-Test': 'true', + expect(netlifyConfig.headers).toMatchInlineSnapshot(` + Array [ + Object { + "for": "/", + "values": Object { + "X-Unit-Test": "true", + }, }, - }, - { - for: '/unit-test', - values: { - 'X-Another-Unit-Test': 'true', - 'X-Another-Unit-Test-Again': 'true', + Object { + "for": "/unit-test", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, }, - }, - ]) + ] + `) }) it('sets custom headers using a splat instead of a named splat in the Netlify configuration', () => { @@ -920,27 +922,29 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toEqual([ - { - for: '/*', - values: { - 'X-Unit-Test': 'true', + expect(netlifyConfig.headers).toMatchInlineSnapshot(` + Array [ + Object { + "for": "/*", + "values": Object { + "X-Unit-Test": "true", + }, }, - }, - { - for: '/some-other-path/*', - values: { - 'X-Another-Unit-Test': 'true', - 'X-Another-Unit-Test-Again': 'true', + Object { + "for": "/some-other-path/*", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, }, - }, - { - for: '/some-other-path/yolo/*', - values: { - 'X-Another-Unit-Test': 'true', + Object { + "for": "/some-other-path/yolo/*", + "values": Object { + "X-Another-Unit-Test": "true", + }, }, - }, - ]) + ] + `) }) it('appends custom headers in the Netlify configuration', () => { @@ -988,27 +992,29 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toEqual([ - { - for: '/', - values: { - 'X-Existing-Header': 'true', + expect(netlifyConfig.headers).toMatchInlineSnapshot(` + Array [ + Object { + "for": "/", + "values": Object { + "X-Existing-Header": "true", + }, }, - }, - { - for: '/', - values: { - 'X-Unit-Test': 'true', + Object { + "for": "/", + "values": Object { + "X-Unit-Test": "true", + }, }, - }, - { - for: '/unit-test', - values: { - 'X-Another-Unit-Test': 'true', - 'X-Another-Unit-Test-Again': 'true', + Object { + "for": "/unit-test", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, }, - }, - ]) + ] + `) }) it('sets custom headers using basePath in the Next.js configuration', () => { @@ -1060,27 +1066,29 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toEqual([ - { - for: '/base-path/*', - values: { - 'X-Unit-Test': 'true', + expect(netlifyConfig.headers).toMatchInlineSnapshot(` + Array [ + Object { + "for": "/base-path/*", + "values": Object { + "X-Unit-Test": "true", + }, }, - }, - { - for: '/base-path/some-other-path/*', - values: { - 'X-Another-Unit-Test': 'true', - 'X-Another-Unit-Test-Again': 'true', + Object { + "for": "/base-path/some-other-path/*", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, }, - }, - { - for: '/base-path/some-other-path/yolo/*', - values: { - 'X-Another-Unit-Test': 'true', + Object { + "for": "/base-path/some-other-path/yolo/*", + "values": Object { + "X-Another-Unit-Test": "true", + }, }, - }, - ]) + ] + `) }) it('sets custom headers omitting basePath when a header has basePath set to false', () => { @@ -1133,27 +1141,134 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toEqual([ - { - for: '/*', - values: { - 'X-Unit-Test': 'true', + expect(netlifyConfig.headers).toMatchInlineSnapshot(` + Array [ + Object { + "for": "/*", + "values": Object { + "X-Unit-Test": "true", + }, }, - }, - { - for: '/base-path/some-other-path/*', - values: { - 'X-Another-Unit-Test': 'true', - 'X-Another-Unit-Test-Again': 'true', + Object { + "for": "/base-path/some-other-path/*", + "values": Object { + "X-Another-Unit-Test": "true", + "X-Another-Unit-Test-Again": "true", + }, + }, + Object { + "for": "/base-path/some-other-path/yolo/*", + "values": Object { + "X-Another-Unit-Test": "true", + }, }, + ] + `) + }) + + it('prepends locales set in the next.config to paths for custom headers', () => { + netlifyConfig.headers = [] + + const nextConfig = { + i18n: { + locales: ['en', 'fr'], + defaultLocale: 'en', }, - { - for: '/base-path/some-other-path/yolo/*', - values: { - 'X-Another-Unit-Test': 'true', + routesManifest: { + headers: [ + { + source: '/with-locale/:path*', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ], + }, + } + + generateCustomHeaders(nextConfig, netlifyConfig.headers) + + expect(netlifyConfig.headers).toMatchInlineSnapshot(` + Array [ + Object { + "for": "/en/with-locale/*", + "values": Object { + "X-Unit-Test": "true", + }, + }, + Object { + "for": "/fr/with-locale/*", + "values": Object { + "X-Unit-Test": "true", + }, }, + ] + `) + }) + + it('does not prepend locales set in the next.config to custom headers that have locale set to false', () => { + netlifyConfig.headers = [] + + const nextConfig = { + i18n: { + locales: ['en', 'fr'], + defaultLocale: 'en', + }, + routesManifest: { + headers: [ + { + source: '/with-locale/:path*', + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + { + source: '/fr/le-custom-locale-path/:path*', + locale: false, + headers: [ + { + key: 'X-Unit-Test', + value: 'true', + }, + ], + regex: '^/(?:/)?$', + }, + ], }, - ]) + } + + generateCustomHeaders(nextConfig, netlifyConfig.headers) + + expect(netlifyConfig.headers).toMatchInlineSnapshot(` + Array [ + Object { + "for": "/en/with-locale/*", + "values": Object { + "X-Unit-Test": "true", + }, + }, + Object { + "for": "/fr/with-locale/*", + "values": Object { + "X-Unit-Test": "true", + }, + }, + Object { + "for": "/fr/le-custom-locale-path/*", + "values": Object { + "X-Unit-Test": "true", + }, + }, + ] + `) }) }) }) From b6d01b837b9daebaabc3a264b1973f6f67641f6a Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Wed, 1 Jun 2022 11:23:10 -0400 Subject: [PATCH 14/24] test: removed tests that aren't actually testing the feature --- .../default/custom-headers.spec.ts | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 cypress/integration/default/custom-headers.spec.ts diff --git a/cypress/integration/default/custom-headers.spec.ts b/cypress/integration/default/custom-headers.spec.ts deleted file mode 100644 index a11015f898..0000000000 --- a/cypress/integration/default/custom-headers.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -describe('custom headers', () => { - it('should load custom headers for an SSG page', () => { - cy.request('/').then((request) => { - cy.wrap(request.headers['x-custom-header']).should('equal', 'my custom header value') - cy.wrap(request.headers['x-custom-header-for-everything']).should( - 'equal', - 'my custom header for everything value', - ) - }) - }) - - it('should load custom headers for a Netlify function', () => { - cy.request('/api/hello/').then((request) => { - cy.wrap(request.headers['x-custom-api-header']).should('equal', 'my custom api header value') - cy.wrap(request.headers['x-custom-header-for-everything']).should( - 'equal', - 'my custom header for everything value', - ) - }) - }) - - it('should load custom headers for an SSR page', () => { - cy.request('/shows/250').then((request) => { - cy.wrap(request.headers['x-custom-header-for-everything']).should( - 'equal', - 'my custom header for everything value', - ) - }) - }) -}) From 610fb458ba2f9391df8a3f4057f0214f1d0fbfea Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Wed, 1 Jun 2022 11:23:34 -0400 Subject: [PATCH 15/24] test: move back to toEqual from toMatchInlineSnapshot --- test/index.js | 242 ++++++++++++++++++++++++-------------------------- 1 file changed, 114 insertions(+), 128 deletions(-) diff --git a/test/index.js b/test/index.js index 135b759cba..d6e6f49bf6 100644 --- a/test/index.js +++ b/test/index.js @@ -855,23 +855,21 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchInlineSnapshot(` - Array [ - Object { - "for": "/", - "values": Object { - "X-Unit-Test": "true", - }, + expect(netlifyConfig.headers).toEqual([ + { + for: '/', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/unit-test", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, + }, + { + for: '/unit-test', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', }, - ] - `) + }, + ]) }) it('sets custom headers using a splat instead of a named splat in the Netlify configuration', () => { @@ -922,29 +920,27 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchInlineSnapshot(` - Array [ - Object { - "for": "/*", - "values": Object { - "X-Unit-Test": "true", - }, + expect(netlifyConfig.headers).toEqual([ + { + for: '/*', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/some-other-path/*", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, + }, + { + for: '/some-other-path/*', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', }, - Object { - "for": "/some-other-path/yolo/*", - "values": Object { - "X-Another-Unit-Test": "true", - }, + }, + { + for: '/some-other-path/yolo/*', + values: { + 'X-Another-Unit-Test': 'true', }, - ] - `) + }, + ]) }) it('appends custom headers in the Netlify configuration', () => { @@ -992,29 +988,27 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchInlineSnapshot(` - Array [ - Object { - "for": "/", - "values": Object { - "X-Existing-Header": "true", - }, + expect(netlifyConfig.headers).toEqual([ + { + for: '/', + values: { + 'X-Existing-Header': 'true', }, - Object { - "for": "/", - "values": Object { - "X-Unit-Test": "true", - }, + }, + { + for: '/', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/unit-test", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, + }, + { + for: '/unit-test', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', }, - ] - `) + }, + ]) }) it('sets custom headers using basePath in the Next.js configuration', () => { @@ -1066,29 +1060,27 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchInlineSnapshot(` - Array [ - Object { - "for": "/base-path/*", - "values": Object { - "X-Unit-Test": "true", - }, + expect(netlifyConfig.headers).toEqual([ + { + for: '/base-path/*', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/base-path/some-other-path/*", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, + }, + { + for: '/base-path/some-other-path/*', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', }, - Object { - "for": "/base-path/some-other-path/yolo/*", - "values": Object { - "X-Another-Unit-Test": "true", - }, + }, + { + for: '/base-path/some-other-path/yolo/*', + values: { + 'X-Another-Unit-Test': 'true', }, - ] - `) + }, + ]) }) it('sets custom headers omitting basePath when a header has basePath set to false', () => { @@ -1141,29 +1133,27 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchInlineSnapshot(` - Array [ - Object { - "for": "/*", - "values": Object { - "X-Unit-Test": "true", - }, + expect(netlifyConfig.headers).toEqual([ + { + for: '/*', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/base-path/some-other-path/*", - "values": Object { - "X-Another-Unit-Test": "true", - "X-Another-Unit-Test-Again": "true", - }, + }, + { + for: '/base-path/some-other-path/*', + values: { + 'X-Another-Unit-Test': 'true', + 'X-Another-Unit-Test-Again': 'true', }, - Object { - "for": "/base-path/some-other-path/yolo/*", - "values": Object { - "X-Another-Unit-Test": "true", - }, + }, + { + for: '/base-path/some-other-path/yolo/*', + values: { + 'X-Another-Unit-Test': 'true', }, - ] - `) + }, + ]) }) it('prepends locales set in the next.config to paths for custom headers', () => { @@ -1192,22 +1182,20 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchInlineSnapshot(` - Array [ - Object { - "for": "/en/with-locale/*", - "values": Object { - "X-Unit-Test": "true", - }, + expect(netlifyConfig.headers).toEqual([ + { + for: '/en/with-locale/*', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/fr/with-locale/*", - "values": Object { - "X-Unit-Test": "true", - }, + }, + { + for: '/fr/with-locale/*', + values: { + 'X-Unit-Test': 'true', }, - ] - `) + }, + ]) }) it('does not prepend locales set in the next.config to custom headers that have locale set to false', () => { @@ -1247,28 +1235,26 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) - expect(netlifyConfig.headers).toMatchInlineSnapshot(` - Array [ - Object { - "for": "/en/with-locale/*", - "values": Object { - "X-Unit-Test": "true", - }, + expect(netlifyConfig.headers).toEqual([ + { + for: '/en/with-locale/*', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/fr/with-locale/*", - "values": Object { - "X-Unit-Test": "true", - }, + }, + { + for: '/fr/with-locale/*', + values: { + 'X-Unit-Test': 'true', }, - Object { - "for": "/fr/le-custom-locale-path/*", - "values": Object { - "X-Unit-Test": "true", - }, + }, + { + for: '/fr/le-custom-locale-path/*', + values: { + 'X-Unit-Test': 'true', }, - ] - `) + }, + ]) }) }) }) From 373779cbd19255ff6312762f1f75a81d602595e0 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Thu, 2 Jun 2022 09:19:55 -0400 Subject: [PATCH 16/24] feat: updated custom headers logic for headers generated in the Netlify configuration --- plugin/src/helpers/config.ts | 48 ++++++++++++++++++----------- test/index.js | 58 +++++++++--------------------------- 2 files changed, 44 insertions(+), 62 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index d15145f5cd..8e4ad30f16 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -131,11 +131,12 @@ interface BuildHeaderParams { } const buildHeader = (buildHeaderParams: BuildHeaderParams) => { - const { locale, path, headers } = buildHeaderParams - const localePart = locale ? `/${locale}` : '' + const { /* locale, */ path, headers } = buildHeaderParams + // const localePart = locale ? `/${locale}` : '' return { - for: `${localePart}${path}`, + // for: `${localePart}${path}`, + for: path, values: headers.reduce((builtHeaders, { key, value }) => { builtHeaders[key] = value @@ -144,6 +145,10 @@ const buildHeader = (buildHeaderParams: BuildHeaderParams) => { } } +// Replace the pattern :path* at the end of a path with * since it's a named splat which the Netlify +// configuration does not support. +const sanitizePath = (path: string) => path.replace(/:[^*/]+\*$/, '*') + /** * Persist NEXT.js custom headers to the Netlify configuration so the headers work with static files * See {@link https://nextjs.org/docs/api-reference/next.config.js/headers} for more information on custom @@ -153,8 +158,10 @@ const buildHeader = (buildHeaderParams: BuildHeaderParams) => { * @param netlifyHeaders - Existing headers that are already configured in the Netlify configuration */ export const generateCustomHeaders = (nextConfig: NextConfig, netlifyHeaders: NetlifyHeaders = []) => { + // The routesManifest is the contents of the routes-manifest.json file which will already contain the generated + // header paths which take locales and base path into account since this runs after the build. The routes-manifest.json + // file is located at demos/default/.next/routes-manifest.json once you've build the demo site. const { - basePath = '', routesManifest: { headers: customHeaders = [] }, i18n, } = nextConfig @@ -162,28 +169,33 @@ export const generateCustomHeaders = (nextConfig: NextConfig, netlifyHeaders: Ne // Skip `has` based custom headers as they have more complex dynamic conditional header logic // that currently isn't supported by the Netlify configuration. // Also, this type of dynamic header logic is most likely not for SSG pages. - for (const { source, headers, basePath: useBasePath, locale: localeEnabled } of customHeaders.filter( - (customHeader) => !customHeader.has, - )) { + for (const { source, headers, locale: localeEnabled } of customHeaders.filter((customHeader) => !customHeader.has)) { // Explicitly checkling false to make the check simpler. // Locale specific paths are excluded only if localeEnabled is false. There is no true value for localeEnabled. It's either // false or undefined, where undefined means it's true. + // + // Again, the routesManifest has already been generated taking locales into account, but the check is required + // so the paths can be properly set in the Netlify configuration. const useLocale = i18n?.locales?.length > 0 && localeEnabled !== false - // Explicitly checkling false to make the check simpler. - // Basepath is excluded only if useBasePath is false. There is no true value for useBasePath. It's either false or undefined, - // where undefined means it's true. - // eslint-disable-next-line no-negated-condition - const pathPrefix = useBasePath !== false ? basePath : '' - - // Replace the pattern :path* at the end of a path with * since it's a named splat - const path = pathPrefix + source.replace(/:[^*/]+\*$/, '*') - if (useLocale) { - for (const locale of i18n.locales) { - netlifyHeaders.push(buildHeader({ path, headers, locale })) + const { locales } = i18n + const joinedLocales = locales.join('|') + + /** + * converts e.g. + * /:nextInternalLocale(en|fr)/some-path + * to a path for each locale + * /en/some-path and /fr/some-path + */ + for (const locale of locales) { + const path = sanitizePath(source).replace(`:nextInternalLocale(${joinedLocales})`, locale) + + netlifyHeaders.push(buildHeader({ path, headers })) } } else { + const path = sanitizePath(source) + netlifyHeaders.push(buildHeader({ path, headers })) } } diff --git a/test/index.js b/test/index.js index d6e6f49bf6..b152df83f1 100644 --- a/test/index.js +++ b/test/index.js @@ -817,6 +817,10 @@ describe('function helpers', () => { describe('config', () => { describe('generateCustomHeaders', () => { + // The routesManifest is the contents of the routes-manifest.json file which will already contain the generated + // header paths which take locales and base path into account which is why you'll see them in the paths already + // in test data. + it('sets custom headers in the Netlify configuration', () => { netlifyConfig.headers = [] @@ -1014,13 +1018,13 @@ describe('function helpers', () => { it('sets custom headers using basePath in the Next.js configuration', () => { netlifyConfig.headers = [] + const basePath = '/base-path' const nextConfig = { - basePath: '/base-path', routesManifest: { headers: [ // single header for a route { - source: '/:path*', + source: `${basePath}/:path*`, headers: [ { key: 'X-Unit-Test', @@ -1031,7 +1035,7 @@ describe('function helpers', () => { }, // multiple headers for a route { - source: '/some-other-path/:path*', + source: `${basePath}/some-other-path/:path*`, headers: [ { key: 'X-Another-Unit-Test', @@ -1044,16 +1048,6 @@ describe('function helpers', () => { ], regex: '^/(?:/)?$', }, - { - source: '/some-other-path/yolo/:path*', - headers: [ - { - key: 'X-Another-Unit-Test', - value: 'true', - }, - ], - regex: '^/(?:/)?$', - }, ], }, } @@ -1074,20 +1068,15 @@ describe('function helpers', () => { 'X-Another-Unit-Test-Again': 'true', }, }, - { - for: '/base-path/some-other-path/yolo/*', - values: { - 'X-Another-Unit-Test': 'true', - }, - }, ]) }) it('sets custom headers omitting basePath when a header has basePath set to false', () => { netlifyConfig.headers = [] + const basePath = '/base-path' + const nextConfig = { - basePath: '/base-path', routesManifest: { headers: [ // single header for a route @@ -1104,21 +1093,7 @@ describe('function helpers', () => { }, // multiple headers for a route { - source: '/some-other-path/:path*', - headers: [ - { - key: 'X-Another-Unit-Test', - value: 'true', - }, - { - key: 'X-Another-Unit-Test-Again', - value: 'true', - }, - ], - regex: '^/(?:/)?$', - }, - { - source: '/some-other-path/yolo/:path*', + source: `${basePath}/some-other-path/:path*`, headers: [ { key: 'X-Another-Unit-Test', @@ -1142,13 +1117,6 @@ describe('function helpers', () => { }, { for: '/base-path/some-other-path/*', - values: { - 'X-Another-Unit-Test': 'true', - 'X-Another-Unit-Test-Again': 'true', - }, - }, - { - for: '/base-path/some-other-path/yolo/*', values: { 'X-Another-Unit-Test': 'true', }, @@ -1159,6 +1127,8 @@ describe('function helpers', () => { it('prepends locales set in the next.config to paths for custom headers', () => { netlifyConfig.headers = [] + // I'm not setting locales in the nextConfig, because at this point in the post build when this runs, + // Next.js has modified the routesManifest to have the locales in the source. const nextConfig = { i18n: { locales: ['en', 'fr'], @@ -1167,7 +1137,7 @@ describe('function helpers', () => { routesManifest: { headers: [ { - source: '/with-locale/:path*', + source: '/:nextInternalLocale(en|fr)/with-locale/:path*', headers: [ { key: 'X-Unit-Test', @@ -1209,7 +1179,7 @@ describe('function helpers', () => { routesManifest: { headers: [ { - source: '/with-locale/:path*', + source: '/:nextInternalLocale(en|fr)/with-locale/:path*', headers: [ { key: 'X-Unit-Test', From 8f28878b2bcd7870ddb7ea31336b626cb232211d Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Thu, 2 Jun 2022 09:53:01 -0400 Subject: [PATCH 17/24] chore: cleaned up some dead code --- plugin/src/helpers/config.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 8e4ad30f16..0d8ee0b33e 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -131,11 +131,9 @@ interface BuildHeaderParams { } const buildHeader = (buildHeaderParams: BuildHeaderParams) => { - const { /* locale, */ path, headers } = buildHeaderParams - // const localePart = locale ? `/${locale}` : '' + const { path, headers } = buildHeaderParams return { - // for: `${localePart}${path}`, for: path, values: headers.reduce((builtHeaders, { key, value }) => { builtHeaders[key] = value From 375cca77c051faef2b8c43df9dcb14ee73f22d5d Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Thu, 2 Jun 2022 23:48:24 -0400 Subject: [PATCH 18/24] fix: now generated headers in Netlify configuration take default locale into consideration --- plugin/src/helpers/config.ts | 6 +++++- test/index.js | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 0d8ee0b33e..819c06c6a6 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -184,8 +184,12 @@ export const generateCustomHeaders = (nextConfig: NextConfig, netlifyHeaders: Ne * converts e.g. * /:nextInternalLocale(en|fr)/some-path * to a path for each locale - * /en/some-path and /fr/some-path + * /en/some-path and /fr/some-path as well as /some-path (default locale) */ + const defaultLocalePath = sanitizePath(source).replace(`/:nextInternalLocale(${joinedLocales})`, '') + + netlifyHeaders.push(buildHeader({ path: defaultLocalePath, headers })) + for (const locale of locales) { const path = sanitizePath(source).replace(`:nextInternalLocale(${joinedLocales})`, locale) diff --git a/test/index.js b/test/index.js index b152df83f1..a6abd33cd3 100644 --- a/test/index.js +++ b/test/index.js @@ -1153,6 +1153,12 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) expect(netlifyConfig.headers).toEqual([ + { + for: '/with-locale/*', + values: { + 'X-Unit-Test': 'true', + }, + }, { for: '/en/with-locale/*', values: { @@ -1206,6 +1212,12 @@ describe('function helpers', () => { generateCustomHeaders(nextConfig, netlifyConfig.headers) expect(netlifyConfig.headers).toEqual([ + { + for: '/with-locale/*', + values: { + 'X-Unit-Test': 'true', + }, + }, { for: '/en/with-locale/*', values: { From 0d35e36ac5ccd4abc2e26013bfbda2f51754a5e0 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Fri, 3 Jun 2022 10:56:14 -0400 Subject: [PATCH 19/24] Update plugin/src/helpers/config.ts Co-authored-by: Erica Pisani --- plugin/src/helpers/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts index 819c06c6a6..ebbbdfafe1 100644 --- a/plugin/src/helpers/config.ts +++ b/plugin/src/helpers/config.ts @@ -168,7 +168,7 @@ export const generateCustomHeaders = (nextConfig: NextConfig, netlifyHeaders: Ne // that currently isn't supported by the Netlify configuration. // Also, this type of dynamic header logic is most likely not for SSG pages. for (const { source, headers, locale: localeEnabled } of customHeaders.filter((customHeader) => !customHeader.has)) { - // Explicitly checkling false to make the check simpler. + // Explicitly checking false to make the check simpler. // Locale specific paths are excluded only if localeEnabled is false. There is no true value for localeEnabled. It's either // false or undefined, where undefined means it's true. // From b50d92cd6c386085b78ade067308742272d2aa6e Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Fri, 3 Jun 2022 11:39:01 -0400 Subject: [PATCH 20/24] test: added onPostBuild test to check for header generation --- test/index.js | 92 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/test/index.js b/test/index.js index a6abd33cd3..7c89e947eb 100644 --- a/test/index.js +++ b/test/index.js @@ -138,7 +138,7 @@ const useFixture = async function (fixtureName) { await cpy('**', process.cwd(), { cwd: fixtureDir, parents: true, overwrite: true, dot: true }) } -const netlifyConfig = { build: { command: 'npm run build' }, functions: {}, redirects: [] } +const netlifyConfig = { build: { command: 'npm run build' }, functions: {}, redirects: [], headers: [] } const defaultArgs = { netlifyConfig, utils, @@ -159,6 +159,7 @@ beforeEach(async () => { netlifyConfig.build.environment = {} netlifyConfig.redirects = [] + netlifyConfig.headers = [] netlifyConfig.functions[HANDLER_FUNCTION_NAME] && (netlifyConfig.functions[HANDLER_FUNCTION_NAME].included_files = []) netlifyConfig.functions[ODB_FUNCTION_NAME] && (netlifyConfig.functions[ODB_FUNCTION_NAME].included_files = []) await useFixture('serverless_next_config') @@ -632,6 +633,93 @@ describe('onPostBuild', () => { }, ]) }) + + test('adds headers to Netlify configuration', async () => { + await moveNextDist() + + const show = jest.fn() + + await plugin.onPostBuild({ + ...defaultArgs, + + utils: { ...defaultArgs.utils, status: { show }, functions: { list: jest.fn().mockResolvedValue([]) } }, + }) + + expect(netlifyConfig.headers).toEqual([ + { + for: '/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/en/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/es/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/fr/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/en/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/es/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/fr/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/en/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/es/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/fr/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + ]) + }) }) describe('utility functions', () => { @@ -822,8 +910,6 @@ describe('function helpers', () => { // in test data. it('sets custom headers in the Netlify configuration', () => { - netlifyConfig.headers = [] - const nextConfig = { routesManifest: { headers: [ From 9781416d8bcfacc7ef8f97b54c8d7c771ddf869b Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Fri, 3 Jun 2022 11:56:16 -0400 Subject: [PATCH 21/24] test: added more onPostBuild test to check for header generation --- test/index.js | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/test/index.js b/test/index.js index 7c89e947eb..109fd1300e 100644 --- a/test/index.js +++ b/test/index.js @@ -720,6 +720,245 @@ describe('onPostBuild', () => { }, ]) }) + + test('appends headers to existing headers in the Netlify configuration', async () => { + await moveNextDist() + + netlifyConfig.headers = [ + { + for: '/', + values: { + 'x-existing-header-in-configuration': 'existing header in configuration value', + }, + }, + ] + + const show = jest.fn() + + await plugin.onPostBuild({ + ...defaultArgs, + + utils: { ...defaultArgs.utils, status: { show }, functions: { list: jest.fn().mockResolvedValue([]) } }, + }) + + expect(netlifyConfig.headers).toEqual([ + { + for: '/', + values: { + 'x-existing-header-in-configuration': 'existing header in configuration value', + }, + }, + { + for: '/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/en/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/es/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/fr/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/en/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/es/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/fr/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/en/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/es/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/fr/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + ]) + }) + + test('appends headers to existing headers in the Netlify configuration', async () => { + await moveNextDist() + + netlifyConfig.headers = [ + { + for: '/', + values: { + 'x-existing-header-in-configuration': 'existing header in configuration value', + }, + }, + ] + + const show = jest.fn() + + await plugin.onPostBuild({ + ...defaultArgs, + + utils: { ...defaultArgs.utils, status: { show }, functions: { list: jest.fn().mockResolvedValue([]) } }, + }) + + expect(netlifyConfig.headers).toEqual([ + { + for: '/', + values: { + 'x-existing-header-in-configuration': 'existing header in configuration value', + }, + }, + { + for: '/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/en/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/es/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/fr/', + values: { + 'x-custom-header': 'my custom header value', + }, + }, + { + for: '/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/en/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/es/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/fr/api/*', + values: { + 'x-custom-api-header': 'my custom api header value', + }, + }, + { + for: '/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/en/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/es/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + { + for: '/fr/*', + values: { + 'x-custom-header-for-everything': 'my custom header for everything value', + }, + }, + ]) + }) + + test('appends no additional headers in the Netlify configuration when none are in the routes manifest', async () => { + await moveNextDist() + + netlifyConfig.headers = [ + { + for: '/', + values: { + 'x-existing-header-in-configuration': 'existing header in configuration value', + }, + }, + ] + + const show = jest.fn() + + const manifestPath = path.resolve('.next/routes-manifest.json') + const routesManifest = await readJson(manifestPath) + delete routesManifest.headers + await writeJSON(manifestPath, routesManifest) + + await plugin.onPostBuild({ + ...defaultArgs, + + utils: { ...defaultArgs.utils, status: { show }, functions: { list: jest.fn().mockResolvedValue([]) } }, + }) + + expect(netlifyConfig.headers).toEqual([ + { + for: '/', + values: { + 'x-existing-header-in-configuration': 'existing header in configuration value', + }, + }, + ]) + }) }) describe('utility functions', () => { From 0ca1f5e11d5fb29b451e9d485611ddc8ed6f2f6f Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Fri, 3 Jun 2022 13:27:56 -0400 Subject: [PATCH 22/24] chore: undoing a copy pasta error --- test/index.js | 102 -------------------------------------------------- 1 file changed, 102 deletions(-) diff --git a/test/index.js b/test/index.js index 109fd1300e..7dcc3a0244 100644 --- a/test/index.js +++ b/test/index.js @@ -823,108 +823,6 @@ describe('onPostBuild', () => { ]) }) - test('appends headers to existing headers in the Netlify configuration', async () => { - await moveNextDist() - - netlifyConfig.headers = [ - { - for: '/', - values: { - 'x-existing-header-in-configuration': 'existing header in configuration value', - }, - }, - ] - - const show = jest.fn() - - await plugin.onPostBuild({ - ...defaultArgs, - - utils: { ...defaultArgs.utils, status: { show }, functions: { list: jest.fn().mockResolvedValue([]) } }, - }) - - expect(netlifyConfig.headers).toEqual([ - { - for: '/', - values: { - 'x-existing-header-in-configuration': 'existing header in configuration value', - }, - }, - { - for: '/', - values: { - 'x-custom-header': 'my custom header value', - }, - }, - { - for: '/en/', - values: { - 'x-custom-header': 'my custom header value', - }, - }, - { - for: '/es/', - values: { - 'x-custom-header': 'my custom header value', - }, - }, - { - for: '/fr/', - values: { - 'x-custom-header': 'my custom header value', - }, - }, - { - for: '/api/*', - values: { - 'x-custom-api-header': 'my custom api header value', - }, - }, - { - for: '/en/api/*', - values: { - 'x-custom-api-header': 'my custom api header value', - }, - }, - { - for: '/es/api/*', - values: { - 'x-custom-api-header': 'my custom api header value', - }, - }, - { - for: '/fr/api/*', - values: { - 'x-custom-api-header': 'my custom api header value', - }, - }, - { - for: '/*', - values: { - 'x-custom-header-for-everything': 'my custom header for everything value', - }, - }, - { - for: '/en/*', - values: { - 'x-custom-header-for-everything': 'my custom header for everything value', - }, - }, - { - for: '/es/*', - values: { - 'x-custom-header-for-everything': 'my custom header for everything value', - }, - }, - { - for: '/fr/*', - values: { - 'x-custom-header-for-everything': 'my custom header for everything value', - }, - }, - ]) - }) - test('appends no additional headers in the Netlify configuration when none are in the routes manifest', async () => { await moveNextDist() From e3c8ed1a7a1cba8093de16958a4ca0e38d165482 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Fri, 3 Jun 2022 15:29:45 -0400 Subject: [PATCH 23/24] chore: package-lock.json was out of date for nx-next-monorepo-demo --- demos/nx-next-monorepo-demo/package-lock.json | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/demos/nx-next-monorepo-demo/package-lock.json b/demos/nx-next-monorepo-demo/package-lock.json index 5a102ad391..9e0064a000 100644 --- a/demos/nx-next-monorepo-demo/package-lock.json +++ b/demos/nx-next-monorepo-demo/package-lock.json @@ -31,7 +31,7 @@ "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "7.0.2", "@types/jest": "27.4.0", - "@types/node": "16.11.38", + "@types/node": "16.11.36", "@types/react": "17.0.45", "@types/react-dom": "17.0.17", "@typescript-eslint/eslint-plugin": "~5.10.0", @@ -5321,9 +5321,9 @@ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "node_modules/@types/node": { - "version": "16.11.38", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.38.tgz", - "integrity": "sha512-hjO/0K140An3GWDw2HJfq7gko3wWeznbjXgg+rzPdVzhe198hp4x2i1dgveAOEiFKd8sOilAxzoSJiVv5P/CUg==" + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==" }, "node_modules/@types/npmlog": { "version": "4.1.4", @@ -10737,6 +10737,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -24007,9 +24015,9 @@ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "@types/node": { - "version": "16.11.38", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.38.tgz", - "integrity": "sha512-hjO/0K140An3GWDw2HJfq7gko3wWeznbjXgg+rzPdVzhe198hp4x2i1dgveAOEiFKd8sOilAxzoSJiVv5P/CUg==" + "version": "16.11.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", + "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==" }, "@types/npmlog": { "version": "4.1.4", @@ -27163,7 +27171,8 @@ "eslint-plugin-react-hooks": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz", - "integrity": "sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==" + "integrity": "sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==", + "requires": {} }, "minimatch": { "version": "3.1.2", @@ -27996,6 +28005,11 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -33485,7 +33499,8 @@ "styled-jsx": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.2.tgz", - "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==" + "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==", + "requires": {} }, "stylehacks": { "version": "5.0.1", From d175a92c7d153f4aaadeaa9137698220a83b2246 Mon Sep 17 00:00:00 2001 From: Nick Taylor Date: Mon, 6 Jun 2022 14:00:40 -0400 Subject: [PATCH 24/24] chore: package lock updated again --- demos/nx-next-monorepo-demo/package-lock.json | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/demos/nx-next-monorepo-demo/package-lock.json b/demos/nx-next-monorepo-demo/package-lock.json index 9e0064a000..5a102ad391 100644 --- a/demos/nx-next-monorepo-demo/package-lock.json +++ b/demos/nx-next-monorepo-demo/package-lock.json @@ -31,7 +31,7 @@ "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "7.0.2", "@types/jest": "27.4.0", - "@types/node": "16.11.36", + "@types/node": "16.11.38", "@types/react": "17.0.45", "@types/react-dom": "17.0.17", "@typescript-eslint/eslint-plugin": "~5.10.0", @@ -5321,9 +5321,9 @@ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "node_modules/@types/node": { - "version": "16.11.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", - "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==" + "version": "16.11.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.38.tgz", + "integrity": "sha512-hjO/0K140An3GWDw2HJfq7gko3wWeznbjXgg+rzPdVzhe198hp4x2i1dgveAOEiFKd8sOilAxzoSJiVv5P/CUg==" }, "node_modules/@types/npmlog": { "version": "4.1.4", @@ -10737,14 +10737,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -24015,9 +24007,9 @@ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "@types/node": { - "version": "16.11.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz", - "integrity": "sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==" + "version": "16.11.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.38.tgz", + "integrity": "sha512-hjO/0K140An3GWDw2HJfq7gko3wWeznbjXgg+rzPdVzhe198hp4x2i1dgveAOEiFKd8sOilAxzoSJiVv5P/CUg==" }, "@types/npmlog": { "version": "4.1.4", @@ -27171,8 +27163,7 @@ "eslint-plugin-react-hooks": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz", - "integrity": "sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==", - "requires": {} + "integrity": "sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==" }, "minimatch": { "version": "3.1.2", @@ -28005,11 +27996,6 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" - }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -33499,8 +33485,7 @@ "styled-jsx": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.2.tgz", - "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==", - "requires": {} + "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==" }, "stylehacks": { "version": "5.0.1",