Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gatsby-adapter-netlify): adapter use headerRoutes #38652

Merged
merged 29 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3f4eef1
simplified header rules
kathmbeck Oct 18, 2023
6bf8782
lint
kathmbeck Oct 18, 2023
342f1aa
Merge branch 'master' into adapter-header-rules
kathmbeck Oct 18, 2023
20cc896
lint
kathmbeck Oct 18, 2023
b58b9f7
update test/snapshot
kathmbeck Oct 19, 2023
45b6f5e
update snapshot
kathmbeck Oct 19, 2023
66859d6
Merge branch 'master' into adapter-header-rules
kathmbeck Oct 19, 2023
5254cb8
add snapshot for headerRoutes
kathmbeck Oct 20, 2023
1db7da7
Merge branch 'master' into adapter-header-rules
kathmbeck Oct 20, 2023
c1d54ae
adapter use headerRoutes
kathmbeck Oct 20, 2023
feee869
export type
kathmbeck Oct 20, 2023
cbbe1da
Merge branch 'adapter-header-rules' into use-header-routes
kathmbeck Oct 20, 2023
333e960
first pass at headers tests
kathmbeck Oct 24, 2023
c69f76a
Merge branch 'master' into use-header-routes
kathmbeck Oct 24, 2023
5627ed6
Merge branch 'use-header-routes' of https://github.com/gatsbyjs/gatsb…
kathmbeck Oct 24, 2023
d08a337
merge conflict fix
kathmbeck Oct 24, 2023
0beb1ab
lint error
kathmbeck Oct 24, 2023
d3555e6
remove accidental nesting
kathmbeck Oct 24, 2023
763cd13
tests
kathmbeck Oct 26, 2023
9578dec
tests
kathmbeck Oct 26, 2023
fe00ca5
static assets todo
kathmbeck Oct 26, 2023
c1a9ffd
example of permanent caching header assertion
pieh Oct 30, 2023
90694c1
ensure getServerData header has priority over config header for SSR p…
pieh Oct 30, 2023
5063e03
normalize header values before assertions
pieh Oct 30, 2023
6257dd8
add page- and app-data header checks
pieh Oct 30, 2023
58d94cb
tmp: skip deleting deploys for easier iteration
pieh Oct 30, 2023
e05986d
refactor test a bit so it's easier to assert same thing in multiple t…
pieh Oct 30, 2023
0353fe9
add slice-data headers check
pieh Oct 30, 2023
150e4e9
add static query result header test
pieh Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions e2e-tests/adapters/cypress/e2e/basics.cy.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { title } from "../../constants"
import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser"

describe("Basics", () => {
beforeEach(() => {
cy.intercept("/gatsby-icon.png").as("static-folder-image")
cy.intercept("/static/astro-**.png", req => {
req.on("before:response", res => {
// this generally should be permamently cached, but that cause problems with intercepting
// see https://docs.cypress.io/api/commands/intercept#cyintercept-and-request-caching
// so we disable caching for this response
// tests for cache-control headers should be done elsewhere

res.headers["cache-control"] = "no-store"
})
}).as("img-import")
cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as(
"img-import"
)

cy.visit("/").waitForRouteChange()
})
Expand Down
147 changes: 147 additions & 0 deletions e2e-tests/adapters/cypress/e2e/headers.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser"

describe("Headers", () => {
const defaultHeaders = {
"x-xss-protection": "1; mode=block",
"x-content-type-options": "nosniff",
"referrer-policy": "same-origin",
"x-frame-options": "DENY",
}

// DRY for repeated assertions in multple tests
const expectedHeadersByRouteAlias = {
"@app-data": {
...defaultHeaders,
"cache-control": "public,max-age=0,must-revalidate",
},
"@page-data": {
...defaultHeaders,
"cache-control": "public,max-age=0,must-revalidate",
},
"@slice-data": {
...defaultHeaders,
"cache-control": "public,max-age=0,must-revalidate",
},
"@static-query-result": {
...defaultHeaders,
"cache-control": "public,max-age=0,must-revalidate",
},
"@img-webpack-import": {
...defaultHeaders,
"cache-control": "public,max-age=31536000,immutable",
},
"@js": {
...defaultHeaders,
"cache-control": "public,max-age=31536000,immutable",
},
}

// `ntl serve` and actual deploy seem to have possible slight differences around header value formatting
// so this just remove spaces around commas to make it easier to compare
function normalizeHeaderValue(value: string | undefined): string | undefined {
if (typeof value === "undefined") {
return value
}
// Remove spaces around commas
return value.replace(/\s*,\s*/gm, `,`)
}
function checkHeaders(
routeAlias: string,
expectedHeaders?: Record<string, string>
) {
if (!expectedHeaders) {
expectedHeaders = expectedHeadersByRouteAlias[routeAlias]
}

if (!expectedHeaders) {
throw new Error(`No expected headers provided for "${routeAlias}`)
}

cy.wait(routeAlias).then(interception => {
Object.keys(expectedHeaders).forEach(headerKey => {
const headers = interception.response.headers[headerKey]

const firstHeader: string = Array.isArray(headers)
? headers[0]
: headers

expect(normalizeHeaderValue(firstHeader)).to.eq(
normalizeHeaderValue(expectedHeaders[headerKey])
)
})
})
}

beforeEach(() => {
cy.intercept("/", WorkaroundCachedResponse).as("index")
cy.intercept("routes/ssr/static", WorkaroundCachedResponse).as("ssr")
cy.intercept("routes/dsg/static", WorkaroundCachedResponse).as("dsg")

cy.intercept("**/page-data.json", WorkaroundCachedResponse).as("page-data")
cy.intercept("**/app-data.json", WorkaroundCachedResponse).as("app-data")
cy.intercept("**/slice-data/*.json", WorkaroundCachedResponse).as(
"slice-data"
)
cy.intercept("**/page-data/sq/d/*.json", WorkaroundCachedResponse).as(
"static-query-result"
)

cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as(
"img-webpack-import"
)
cy.intercept("*.js", WorkaroundCachedResponse).as("js")
})

it("should contain correct headers for index page", () => {
cy.visit("/").waitForRouteChange()

checkHeaders("@index", {
...defaultHeaders,
"x-custom-header": "my custom header value",
"cache-control": "public,max-age=0,must-revalidate",
})

checkHeaders("@app-data")
checkHeaders("@page-data")
checkHeaders("@slice-data")
checkHeaders("@static-query-result")

// index page is only one showing webpack imported image
checkHeaders("@img-webpack-import")
checkHeaders("@js")
})

it("should contain correct headers for ssr page", () => {
cy.visit("routes/ssr/static").waitForRouteChange()

checkHeaders("@ssr", {
...defaultHeaders,
"x-custom-header": "my custom header value",
"x-ssr-header": "my custom header value from config",
"x-ssr-header-getserverdata": "my custom header value from getServerData",
"x-ssr-header-overwrite": "getServerData wins",
})

checkHeaders("@app-data")
// page-data is baked into SSR page so it's not fetched and we don't assert it
checkHeaders("@slice-data")
checkHeaders("@static-query-result")
checkHeaders("@js")
})

it("should contain correct headers for dsg page", () => {
cy.visit("routes/dsg/static").waitForRouteChange()

checkHeaders("@dsg", {
...defaultHeaders,
"x-custom-header": "my custom header value",
"x-dsg-header": "my custom header value",
})

checkHeaders("@app-data")
checkHeaders("@page-data")
checkHeaders("@slice-data")
checkHeaders("@static-query-result")
checkHeaders("@js")
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CyHttpMessages } from "cypress/types/net-stubbing"

/**
* https://docs.cypress.io/api/commands/intercept#cyintercept-and-request-caching
*
* For responses that are to be cached we need to use a trick so browser doesn't cache them
* So this enforces `no-store` cache-control header before response hits the browser
* and then restore original cache-control value for assertions.
*/
export const WorkaroundCachedResponse = (
req: CyHttpMessages.IncomingHttpRequest
): void | Promise<void> => {
req.on("before:response", res => {
res.headers["x-original-cache-control"] = res.headers["cache-control"]
res.headers["cache-control"] = "no-store"
})
req.on("after:response", res => {
res.headers["cache-control"] = res.headers["x-original-cache-control"]
delete res.headers["x-original-cache-control"]
})
}
34 changes: 21 additions & 13 deletions e2e-tests/adapters/debug-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inspect } from "util"
import type { AdapterInit } from "gatsby"

const createTestingAdapter: AdapterInit = (adapterOptions) => {
const createTestingAdapter: AdapterInit = adapterOptions => {
return {
name: `gatsby-adapter-debug`,
cache: {
Expand All @@ -10,28 +10,36 @@ const createTestingAdapter: AdapterInit = (adapterOptions) => {
},
store({ directories, reporter }) {
reporter.info(`[gatsby-adapter-debug] cache.store() ${directories}`)
}
},
},
adapt({
routesManifest,
headerRoutes,
functionsManifest,
pathPrefix,
trailingSlash,
reporter,
}) {
reporter.info(`[gatsby-adapter-debug] adapt()`)

console.log(`[gatsby-adapter-debug] adapt()`, inspect({
routesManifest,
functionsManifest,
pathPrefix,
trailingSlash,
}, {
depth: Infinity,
colors: true
}))
}
console.log(
`[gatsby-adapter-debug] adapt()`,
inspect(
{
routesManifest,
headerRoutes,
functionsManifest,
pathPrefix,
trailingSlash,
},
{
depth: Infinity,
colors: true,
}
)
)
},
}
}

export default createTestingAdapter
export default createTestingAdapter
36 changes: 35 additions & 1 deletion e2e-tests/adapters/gatsby-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import debugAdapter from "./debug-adapter"
import { siteDescription, title } from "./constants"

const shouldUseDebugAdapter = process.env.USE_DEBUG_ADAPTER ?? false
const trailingSlash = (process.env.TRAILING_SLASH || `never`) as GatsbyConfig["trailingSlash"]
const trailingSlash = (process.env.TRAILING_SLASH ||
`never`) as GatsbyConfig["trailingSlash"]

let configOverrides: GatsbyConfig = {}

Expand All @@ -21,6 +22,39 @@ const config: GatsbyConfig = {
},
trailingSlash,
plugins: [],
headers: [
{
source: `/*`,
headers: [
{
key: "x-custom-header",
value: "my custom header value",
},
],
},
{
source: `routes/ssr/*`,
headers: [
{
key: "x-ssr-header",
value: "my custom header value from config",
},
{
key: "x-ssr-header-overwrite",
value: "config wins",
},
],
},
{
source: `routes/dsg/*`,
headers: [
{
key: "x-dsg-header",
value: "my custom header value",
},
],
},
],
...configOverrides,
}

Expand Down
37 changes: 17 additions & 20 deletions e2e-tests/adapters/scripts/deploy-and-run/netlify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,21 @@ console.log(`Deployed to ${deployInfo.deploy_url}`)
try {
await execa(`npm`, [`run`, npmScriptToRun], { stdio: `inherit` })
} finally {
if (!process.env.GATSBY_TEST_SKIP_CLEANUP) {
console.log(`Deleting project with deploy_id ${deployInfo.deploy_id}`)

const deleteResponse = await execa("ntl", [
"api",
"deleteDeploy",
"--data",
`{ "deploy_id": "${deployInfo.deploy_id}" }`,
])

if (deleteResponse.exitCode !== 0) {
throw new Error(
`Failed to delete project ${deleteResponse.stdout} ${deleteResponse.stderr} (${deleteResponse.exitCode})`
)
}

console.log(
`Successfully deleted project with deploy_id ${deployInfo.deploy_id}`
)
}
// if (!process.env.GATSBY_TEST_SKIP_CLEANUP) {
// console.log(`Deleting project with deploy_id ${deployInfo.deploy_id}`)
// const deleteResponse = await execa("ntl", [
// "api",
// "deleteDeploy",
// "--data",
// `{ "deploy_id": "${deployInfo.deploy_id}" }`,
// ])
// if (deleteResponse.exitCode !== 0) {
// throw new Error(
// `Failed to delete project ${deleteResponse.stdout} ${deleteResponse.stderr} (${deleteResponse.exitCode})`
// )
// }
// console.log(
// `Successfully deleted project with deploy_id ${deployInfo.deploy_id}`
// )
// }
}
18 changes: 12 additions & 6 deletions e2e-tests/adapters/src/pages/routes/ssr/static.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ const SSR = ({ serverData }) => {
<h1>SSR</h1>
<div>
<code>
<pre>
{JSON.stringify({ serverData }, null, 2)}
</pre>
<pre>{JSON.stringify({ serverData }, null, 2)}</pre>
</code>
</div>
<div>
<code>
<pre data-testid="query">{JSON.stringify(serverData?.arg?.query)}</pre>
<pre data-testid="params">{JSON.stringify(serverData?.arg?.params)}</pre>
<pre data-testid="query">
{JSON.stringify(serverData?.arg?.query)}
</pre>
<pre data-testid="params">
{JSON.stringify(serverData?.arg?.params)}
</pre>
</code>
</div>
</Layout>
Expand All @@ -32,5 +34,9 @@ export function getServerData(arg) {
ssr: true,
arg,
},
headers: {
"x-ssr-header-getserverdata": "my custom header value from getServerData",
"x-ssr-header-overwrite": "getServerData wins",
},
}
}
}
9 changes: 7 additions & 2 deletions packages/gatsby-adapter-netlify/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,14 @@ const createNetlifyAdapter: AdapterInit<INetlifyAdapterOptions> = options => {
}
},
},
async adapt({ routesManifest, functionsManifest }): Promise<void> {
async adapt({
routesManifest,
functionsManifest,
headerRoutes,
}): Promise<void> {
const { lambdasThatUseCaching } = await handleRoutesManifest(
routesManifest
routesManifest,
headerRoutes
)

// functions handling
Expand Down
Loading