Skip to content

Commit

Permalink
feat(gatsby,gatsby-adapter-netlify): support pathPrefix and trailingS…
Browse files Browse the repository at this point in the history
…lash options (#38666)

* initial wip

* prefix all the things

* lambda handler stripping path prefix

* test: adjust e2e setup to also run variant with path prefix and no trailing slashes

* chore: use queue for file moving to limit concurrency

* tmp: don't clean up deploys while debugging things

* test: added variant variables to deploy title

* fix TS

* fix unit tests

* try different cypress group

* try different cypress group2

* maybe fix passing cypress env?

* fix ssr path_prefix

* keep 404/500 status pages in original place

* fix typo

* fix assertion?

* streamline file moving logic

* cache (and restore) publishdir locally too

* add pretty-url unit tests

* handle dynamic paths when generating pretty url file names

* update intercepting glob to handle path prefix

* update intercepting glob to handle path prefix 2

* restore automatic deploys deletion

* test: jest ensure we mount files that we test filepaths for

* handle path prefix in header rules

* drop debug helpers

* first check if local

* drop debug log

* handle external redirects when pathPrefix is used. Thanks @techfg

* make placeholder syntax consistent

* update comment

* move dynamic route path normalization to its own function
  • Loading branch information
pieh authored Nov 13, 2023
1 parent 22c2412 commit 63e57cf
Show file tree
Hide file tree
Showing 26 changed files with 721 additions and 127 deletions.
3 changes: 1 addition & 2 deletions e2e-tests/adapters/cypress/configs/netlify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { defineConfig } from "cypress"
export default defineConfig({
e2e: {
baseUrl: process.env.DEPLOY_URL || `http://localhost:8888`,
// Netlify doesn't handle trailing slash behaviors really, so no use in testing it
excludeSpecPattern: [`cypress/e2e/trailing-slash.cy.ts`],
excludeSpecPattern: [],
projectId: `4enh4m`,
videoUploadOnPasses: false,
experimentalRunAllSpecs: true,
Expand Down
13 changes: 8 additions & 5 deletions e2e-tests/adapters/cypress/e2e/basics.cy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { title } from "../../constants"
import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser"

const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || ``

describe("Basics", () => {
beforeEach(() => {
cy.intercept("/gatsby-icon.png").as("static-folder-image")
cy.intercept("/static/astro-**.png", WorkaroundCachedResponse).as(
"img-import"
)
cy.intercept(PATH_PREFIX + "/gatsby-icon.png").as("static-folder-image")
cy.intercept(
PATH_PREFIX + "/static/astro-**.png",
WorkaroundCachedResponse
).as("img-import")

cy.visit("/").waitForRouteChange()
})
Expand Down Expand Up @@ -35,7 +38,7 @@ describe("Basics", () => {
failOnStatusCode: false,
})

cy.get("h1").should("have.text", "Page not found")
cy.get("h1").should("have.text", "Page not found (custom)")
})
it("should apply CSS", () => {
cy.get(`h1`).should(`have.css`, `color`, `rgb(21, 21, 22)`)
Expand Down
46 changes: 27 additions & 19 deletions e2e-tests/adapters/cypress/e2e/client-only.cy.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Cypress.on('uncaught:exception', (err) => {
if (err.message.includes('Minified React error')) {
Cypress.on("uncaught:exception", err => {
if (err.message.includes("Minified React error")) {
return false
}
})

describe('Sub-Router', () => {
describe("Sub-Router", () => {
const routes = [
{
path: "/routes/sub-router",
marker: "index",
label: "Index route"
label: "Index route",
},
{
path: `/routes/sub-router/page/profile`,
Expand Down Expand Up @@ -51,39 +51,47 @@ describe('Sub-Router', () => {
})
})

describe('Paths', () => {
describe("Paths", () => {
const routes = [
{
name: 'client-only',
param: 'dune',
name: "client-only",
param: "dune",
},
{
name: 'client-only/wildcard',
param: 'atreides/harkonnen',
name: "client-only/wildcard",
param: "atreides/harkonnen",
},
{
name: 'client-only/named-wildcard',
param: 'corinno/fenring',
name: "client-only/named-wildcard",
param: "corinno/fenring",
},
] as const

for (const route of routes) {
it(`should return "${route.name}" result`, () => {
cy.visit(`/routes/${route.name}${route.param ? `/${route.param}` : ''}`).waitForRouteChange()
cy.visit(
`/routes/${route.name}${route.param ? `/${route.param}` : ""}`
).waitForRouteChange()
cy.get("[data-testid=title]").should("have.text", route.name)
cy.get("[data-testid=params]").should("have.text", route.param)
})
}
})

describe('Prioritize', () => {
it('should prioritize static page over matchPath page with wildcard', () => {
cy.visit('/routes/client-only/prioritize').waitForRouteChange()
cy.get("[data-testid=title]").should("have.text", "client-only/prioritize static")
describe("Prioritize", () => {
it("should prioritize static page over matchPath page with wildcard", () => {
cy.visit("/routes/client-only/prioritize").waitForRouteChange()
cy.get("[data-testid=title]").should(
"have.text",
"client-only/prioritize static"
)
})
it('should return result for wildcard on nested prioritized path', () => {
cy.visit('/routes/client-only/prioritize/nested').waitForRouteChange()
cy.get("[data-testid=title]").should("have.text", "client-only/prioritize matchpath")
it("should return result for wildcard on nested prioritized path", () => {
cy.visit("/routes/client-only/prioritize/nested").waitForRouteChange()
cy.get("[data-testid=title]").should(
"have.text",
"client-only/prioritize matchpath"
)
cy.get("[data-testid=params]").should("have.text", "nested")
})
})
51 changes: 34 additions & 17 deletions e2e-tests/adapters/cypress/e2e/headers.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { WorkaroundCachedResponse } from "../utils/dont-cache-responses-in-browser"

const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || ``

describe("Headers", () => {
const defaultHeaders = {
"x-xss-protection": "1; mode=block",
Expand Down Expand Up @@ -73,23 +75,38 @@ describe("Headers", () => {
}

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")
cy.intercept(PATH_PREFIX + "/", WorkaroundCachedResponse).as("index")
cy.intercept(
PATH_PREFIX + "/routes/ssr/static",
WorkaroundCachedResponse
).as("ssr")
cy.intercept(
PATH_PREFIX + "/routes/dsg/static",
WorkaroundCachedResponse
).as("dsg")

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

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

it("should contain correct headers for index page", () => {
Expand Down
10 changes: 7 additions & 3 deletions e2e-tests/adapters/cypress/e2e/redirects.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Cypress.on("uncaught:exception", err => {
})

const TRAILING_SLASH = Cypress.env(`TRAILING_SLASH`) || `never`
const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || ``

// Those tests won't work using `gatsby serve` because it doesn't support redirects

Expand Down Expand Up @@ -122,7 +123,8 @@ describe("Redirects", () => {

cy.location(`pathname`).should(
`equal`,
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
PATH_PREFIX +
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
)
cy.location(`hash`).should(`equal`, `#anchor`)
cy.location(`search`).should(`equal`, ``)
Expand All @@ -138,7 +140,8 @@ describe("Redirects", () => {

cy.location(`pathname`).should(
`equal`,
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
PATH_PREFIX +
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
)
cy.location(`hash`).should(`equal`, ``)
cy.location(`search`).should(`equal`, `?query_param=hello`)
Expand All @@ -154,7 +157,8 @@ describe("Redirects", () => {

cy.location(`pathname`).should(
`equal`,
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
PATH_PREFIX +
applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)
)
cy.location(`hash`).should(`equal`, `#anchor`)
cy.location(`search`).should(`equal`, `?query_param=hello`)
Expand Down
8 changes: 5 additions & 3 deletions e2e-tests/adapters/cypress/e2e/ssr.cy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const staticPath = "/routes/ssr/static"
const paramPath = "/routes/ssr/param"

const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || ``

describe("Server Side Rendering (SSR)", () => {
it(`direct visit no query params (${staticPath})`, () => {
cy.visit(staticPath).waitForRouteChange()
Expand Down Expand Up @@ -32,8 +34,8 @@ describe("Server Side Rendering (SSR)", () => {
cy.visit(errorPath, { failOnStatusCode: false }).waitForRouteChange()

cy.location(`pathname`)
.should(`equal`, errorPath)
.should(`equal`, PATH_PREFIX + errorPath)
.get(`h1`)
.should(`have.text`, `INTERNAL SERVER ERROR`)
.should(`have.text`, `INTERNAL SERVER ERROR (custom)`)
})
})
})
4 changes: 3 additions & 1 deletion e2e-tests/adapters/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ declare global {
}
}

const PATH_PREFIX = Cypress.env(`PATH_PREFIX`) || ``

Cypress.Commands.add(`assertRoute`, route => {
cy.url().should(`equal`, `${window.location.origin}${route}`)
cy.url().should(`equal`, `${window.location.origin}${PATH_PREFIX}${route}`)
})
3 changes: 3 additions & 0 deletions e2e-tests/adapters/gatsby-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { siteDescription, title } from "./constants"
const shouldUseDebugAdapter = process.env.USE_DEBUG_ADAPTER ?? false
const trailingSlash = (process.env.TRAILING_SLASH ||
`never`) as GatsbyConfig["trailingSlash"]
const pathPrefix = (process.env.PATH_PREFIX ||
undefined) as GatsbyConfig["pathPrefix"]

let configOverrides: GatsbyConfig = {}

Expand All @@ -21,6 +23,7 @@ const config: GatsbyConfig = {
siteDescription,
},
trailingSlash,
pathPrefix,
plugins: [],
headers: [
{
Expand Down
11 changes: 7 additions & 4 deletions e2e-tests/adapters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@
"author": "LekoArts",
"scripts": {
"develop": "cross-env CYPRESS_SUPPORT=y gatsby develop",
"build": "cross-env CYPRESS_SUPPORT=y gatsby build",
"build": "cross-env CYPRESS_SUPPORT=y gatsby build --prefix-paths",
"build:debug": "cross-env USE_DEBUG_ADAPTER=y CYPRESS_SUPPORT=y npm run build",
"serve": "gatsby serve",
"clean": "gatsby clean",
"cy:open": "cypress open --browser chrome --e2e",
"develop:debug": "start-server-and-test develop http://localhost:8000 'npm run cy:open -- --config baseUrl=http://localhost:8000'",
"ssat:debug": "start-server-and-test serve http://localhost:9000 cy:open",
"test:template": "cross-env-shell CYPRESS_GROUP_NAME=$ADAPTER TRAILING_SLASH=$TRAILING_SLASH node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --e2e --config-file \"cypress/configs/$ADAPTER.ts\" --env TRAILING_SLASH=$TRAILING_SLASH",
"test:template:debug": "cross-env-shell CYPRESS_GROUP_NAME=$ADAPTER TRAILING_SLASH=$TRAILING_SLASH npm run cy:open -- --config-file \"cypress/configs/$ADAPTER.ts\" --env TRAILING_SLASH=$TRAILING_SLASH",
"test:template": "cross-env-shell CYPRESS_GROUP_NAME=\"adapter:$ADAPTER / trailingSlash:${TRAILING_SLASH:-always} / pathPrefix:${PATH_PREFIX:--}\" TRAILING_SLASH=$TRAILING_SLASH PATH_PREFIX=$PATH_PREFIX node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --e2e --config-file \"cypress/configs/$ADAPTER.ts\" --env TRAILING_SLASH=$TRAILING_SLASH,PATH_PREFIX=$PATH_PREFIX",
"test:template:debug": "cross-env-shell CYPRESS_GROUP_NAME=\"adapter:$ADAPTER / trailingSlash:${TRAILING_SLASH:-always} / pathPrefix:${PATH_PREFIX:--}\" TRAILING_SLASH=$TRAILING_SLASH PATH_PREFIX=$PATH_PREFIX npm run cy:open -- --config-file \"cypress/configs/$ADAPTER.ts\" --env TRAILING_SLASH=$TRAILING_SLASH,PATH_PREFIX=$PATH_PREFIX",
"test:debug": "npm-run-all -s build:debug ssat:debug",
"test:netlify": "cross-env TRAILING_SLASH=always node scripts/deploy-and-run/netlify.mjs test:template",
"test:netlify:debug": "cross-env TRAILING_SLASH=always node scripts/deploy-and-run/netlify.mjs test:template:debug",
"test": "npm-run-all -c -s test:netlify"
"test:netlify:prefix-never": "cross-env TRAILING_SLASH=never PATH_PREFIX=/prefix node scripts/deploy-and-run/netlify.mjs test:template",
"test:netlify:prefix-never:debug": "cross-env TRAILING_SLASH=never PATH_PREFIX=/prefix node scripts/deploy-and-run/netlify.mjs test:template:debug",
"test": "npm-run-all -c -s test:netlify test:netlify:prefix-never"
},
"dependencies": {
"gatsby": "next",
Expand All @@ -29,6 +31,7 @@
"devDependencies": {
"cross-env": "^7.0.3",
"cypress": "^12.14.0",
"dotenv": "^8.6.0",
"gatsby-cypress": "^3.11.0",
"netlify-cli": "^15.8.0",
"npm-run-all": "^4.1.5",
Expand Down
57 changes: 35 additions & 22 deletions e2e-tests/adapters/scripts/deploy-and-run/netlify.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
// @ts-check

import { execa } from "execa"

process.env.NETLIFY_SITE_ID = process.env.E2E_ADAPTERS_NETLIFY_SITE_ID
// only set NETLIFY_SITE_ID from E2E_ADAPTERS_NETLIFY_SITE_ID if it's set
if (process.env.E2E_ADAPTERS_NETLIFY_SITE_ID) {
process.env.NETLIFY_SITE_ID = process.env.E2E_ADAPTERS_NETLIFY_SITE_ID
}
process.env.ADAPTER = "netlify"

const deployTitle = process.env.CIRCLE_SHA1 || "N/A"
const deployTitle = `${
process.env.CIRCLE_SHA1 || "N/A commit"
} - trailingSlash:${process.env.TRAILING_SLASH || `always`} / pathPrefix:${
process.env.PATH_PREFIX || `-`
}`

const npmScriptToRun = process.argv[2] || "test:netlify"

// ensure clean build
await execa(`npm`, [`run`, `clean`], { stdio: `inherit` })

const deployResults = await execa(
"ntl",
["deploy", "--build", "--json", "--message", deployTitle],
Expand All @@ -30,28 +39,32 @@ if (deployResults.exitCode !== 0) {

const deployInfo = JSON.parse(deployResults.stdout)

process.env.DEPLOY_URL = deployInfo.deploy_url
const deployUrl = deployInfo.deploy_url + (process.env.PATH_PREFIX ?? ``)
process.env.DEPLOY_URL = deployUrl

console.log(`Deployed to ${deployInfo.deploy_url}`)
console.log(`Deployed to ${deployUrl}`)

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}`
)
}
}
3 changes: 1 addition & 2 deletions e2e-tests/adapters/src/pages/404.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ const paragraphStyles = {
marginBottom: 48,
}


const NotFoundPage = () => {
return (
<main style={pageStyles}>
<h1 style={headingStyles}>Page not found</h1>
<h1 style={headingStyles}>Page not found (custom)</h1>
<p style={paragraphStyles}>
Sorry 😔, we couldn’t find what you were looking for.
<br />
Expand Down
Loading

0 comments on commit 63e57cf

Please sign in to comment.