From 5bc0b1f419693ff515f68852224b7754c6f3d471 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 18 May 2023 11:14:26 +0200 Subject: [PATCH 001/161] initial - cache restoration --- packages/gatsby/src/commands/build.ts | 6 ++ packages/gatsby/src/redux/index.ts | 55 ++++++++++++------- .../gatsby/src/utils/adapter/adapter-demo.ts | 16 ++++++ packages/gatsby/src/utils/adapter/manager.ts | 48 ++++++++++++++++ packages/gatsby/src/utils/adapter/types.ts | 12 ++++ 5 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 packages/gatsby/src/utils/adapter/adapter-demo.ts create mode 100644 packages/gatsby/src/utils/adapter/manager.ts create mode 100644 packages/gatsby/src/utils/adapter/types.ts diff --git a/packages/gatsby/src/commands/build.ts b/packages/gatsby/src/commands/build.ts index f8f58c7bc89ff..f597d268397b7 100644 --- a/packages/gatsby/src/commands/build.ts +++ b/packages/gatsby/src/commands/build.ts @@ -71,6 +71,7 @@ import { constructConfigObject } from "../utils/gatsby-cloud-config" import { waitUntilWorkerJobsAreComplete } from "../utils/jobs/worker-messaging" import { getSSRChunkHashes } from "../utils/webpack/get-ssr-chunk-hashes" import { writeTypeScriptTypes } from "../utils/graphql-typegen/ts-codegen" +import { initAdapterManager } from "../utils/adapter/manager" module.exports = async function build( program: IBuildArgs, @@ -114,6 +115,9 @@ module.exports = async function build( ) } + const adapterManager = initAdapterManager() + await adapterManager.restoreCache() + const buildActivity = report.phantomActivity(`build`) buildActivity.start() @@ -696,6 +700,8 @@ module.exports = async function build( report.info(`.cache/deletedPages.txt created`) } + await adapterManager.storeCache() + showExperimentNotices() if (await userGetsSevenDayFeedback()) { diff --git a/packages/gatsby/src/redux/index.ts b/packages/gatsby/src/redux/index.ts index 51a9db83fdaa3..f80e8fb3399ac 100644 --- a/packages/gatsby/src/redux/index.ts +++ b/packages/gatsby/src/redux/index.ts @@ -12,10 +12,42 @@ import telemetry from "gatsby-telemetry" import { mett } from "../utils/mett" import thunk, { ThunkMiddleware, ThunkAction, ThunkDispatch } from "redux-thunk" -import * as reducers from "./reducers" +import * as rawReducers from "./reducers" import { writeToCache, readFromCache } from "./persist" import { IGatsbyState, ActionsUnion, GatsbyStateKeys } from "./types" +const persistedReduxKeys = [ + `nodes`, + `typeOwners`, + `statefulSourcePlugins`, + `status`, + `components`, + `jobsV2`, + `staticQueriesByTemplate`, + `webpackCompilationHash`, + `pageDataStats`, + `pages`, + `staticQueriesByTemplate`, + `pendingPageDataWrites`, + `queries`, + `html`, + `slices`, + `slicesByTemplate`, +] + +const reducers = persistedReduxKeys.reduce((acc, key) => { + const rawReducer = rawReducers[key] + acc[key] = function (state, action): any { + if (action.type === `RESTORE_CACHE`) { + return action.payload[key] + } else { + return rawReducer(state, action) + } + } + + return acc +}, rawReducers) + // Create event emitter for actions export const emitter = mett() @@ -108,24 +140,9 @@ export const saveState = (): void => { const state = store.getState() - return writeToCache({ - nodes: state.nodes, - typeOwners: state.typeOwners, - statefulSourcePlugins: state.statefulSourcePlugins, - status: state.status, - components: state.components, - jobsV2: state.jobsV2, - staticQueryComponents: state.staticQueryComponents, - webpackCompilationHash: state.webpackCompilationHash, - pageDataStats: state.pageDataStats, - pages: state.pages, - pendingPageDataWrites: state.pendingPageDataWrites, - staticQueriesByTemplate: state.staticQueriesByTemplate, - queries: state.queries, - html: state.html, - slices: state.slices, - slicesByTemplate: state.slicesByTemplate, - }) + const sliceOfStateToPersist = _.pick(state, persistedReduxKeys) + + return writeToCache(sliceOfStateToPersist) } export const savePartialStateToDisk = ( diff --git a/packages/gatsby/src/utils/adapter/adapter-demo.ts b/packages/gatsby/src/utils/adapter/adapter-demo.ts new file mode 100644 index 0000000000000..9e2df3ba0f791 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/adapter-demo.ts @@ -0,0 +1,16 @@ +import type { IAdapterCtor } from "./types" + +const createAdapter: IAdapterCtor = ({ reporter }) => { + return { + cache: { + restore(directories): void { + reporter.info(`[dev-adapter] cache.restore()`) + }, + store(directories): void { + reporter.info(`[dev-adapter] cache.store()`) + }, + }, + } +} + +export default createAdapter diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts new file mode 100644 index 0000000000000..7e3f60e97d070 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -0,0 +1,48 @@ +import { store, readState } from "../../redux" +import reporter from "gatsby-cli/lib/reporter" +import adapterFn from "./adapter-demo" + +interface IAdapterManager { + restoreCache: () => Promise + storeCache: () => Promise +} + +export function initAdapterManager(): IAdapterManager { + const directories = [`.cache`, `public`] + + const adapter = adapterFn({ reporter }) + + reporter.info(`[dev-adapter-manager] using an adapter`) + + return { + restoreCache: async (): Promise => { + reporter.info(`[dev-adapter-manager] restoreCache()`) + if (!adapter.cache) { + return + } + + const result = await adapter.cache.restore(directories) + if (result === false) { + // if adapter reports `false`, we can skip trying to re-hydrate state + return + } + + const cachedState = readState() + + if (Object.keys(cachedState).length > 0) { + store.dispatch({ + type: `RESTORE_CACHE`, + payload: cachedState, + }) + } + }, + storeCache: async (): Promise => { + reporter.info(`[dev-adapter-manager] storeCache()`) + if (!adapter.cache) { + return + } + + await adapter.cache.store(directories) + }, + } +} diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts new file mode 100644 index 0000000000000..4567786882a2f --- /dev/null +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -0,0 +1,12 @@ +import type reporter from "gatsby-cli/lib/reporter" + +export interface IAdapter { + cache?: { + restore: ( + directories: Array + ) => Promise | boolean | void + store: (directories: Array) => Promise | void + } +} + +export type IAdapterCtor = (args: { reporter: typeof reporter }) => IAdapter From 72f6ecde160a1ff1c559032f54ed5e3d549b3221 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 19 May 2023 12:01:08 +0200 Subject: [PATCH 002/161] snapshot --- packages/gatsby/src/commands/build.ts | 1 + .../internal-plugins/functions/gatsby-node.ts | 2 + packages/gatsby/src/redux/types.ts | 2 + .../gatsby/src/utils/adapter/adapter-demo.ts | 19 ++- packages/gatsby/src/utils/adapter/manager.ts | 152 +++++++++++++++++- packages/gatsby/src/utils/adapter/types.ts | 87 +++++++++- 6 files changed, 252 insertions(+), 11 deletions(-) diff --git a/packages/gatsby/src/commands/build.ts b/packages/gatsby/src/commands/build.ts index f597d268397b7..c3d320cb3a55a 100644 --- a/packages/gatsby/src/commands/build.ts +++ b/packages/gatsby/src/commands/build.ts @@ -700,6 +700,7 @@ module.exports = async function build( report.info(`.cache/deletedPages.txt created`) } + await adapterManager.adapt() await adapterManager.storeCache() showExperimentNotices() diff --git a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts index 49bf730cd3e99..66420c2c31bd1 100644 --- a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts +++ b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts @@ -183,6 +183,8 @@ const createWebpackConfig = async ({ relativeCompiledFilePath: compiledFunctionName, absoluteCompiledFilePath: compiledPath, matchPath: getMatchPath(finalName), + // TODO: maybe figure out better functionId + functionId: compiledFunctionName, }) }) diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 127d4a12b884b..595ef1003b6e2 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -86,6 +86,8 @@ export interface IGatsbyFunction { matchPath: string | undefined /** The plugin that owns this function route **/ pluginName: string + /** Function identifier used to match functions usage in routes manifest */ + functionId: string } export interface IGraphQLTypegenOptions { diff --git a/packages/gatsby/src/utils/adapter/adapter-demo.ts b/packages/gatsby/src/utils/adapter/adapter-demo.ts index 9e2df3ba0f791..0eed02305dccf 100644 --- a/packages/gatsby/src/utils/adapter/adapter-demo.ts +++ b/packages/gatsby/src/utils/adapter/adapter-demo.ts @@ -1,16 +1,25 @@ -import type { IAdapterCtor } from "./types" +import type { AdapterInit } from "./types" + +const createDemoAdapter: AdapterInit = ({ reporter }) => { + reporter.info(`[dev-adapter] createAdapter()`) -const createAdapter: IAdapterCtor = ({ reporter }) => { return { + name: `gatsby-adapter-demo`, cache: { restore(directories): void { - reporter.info(`[dev-adapter] cache.restore()`) + console.log(`[dev-adapter] cache.restore()`, directories) }, store(directories): void { - reporter.info(`[dev-adapter] cache.store()`) + console.log(`[dev-adapter] cache.store()`, directories) }, }, + adapt({ routesManifest, functionsManifest }): void { + console.log(`[dev-adapter] cache.adapt()`, { + routesManifest, + functionsManifest, + }) + }, } } -export default createAdapter +export default createDemoAdapter diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 7e3f60e97d070..ecc0bb1f84063 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -1,19 +1,33 @@ import { store, readState } from "../../redux" import reporter from "gatsby-cli/lib/reporter" -import adapterFn from "./adapter-demo" +import { posix } from "path" +import { + FunctionsManifest, + IAdaptContext, + AdapterInit, + RoutesManifest, +} from "./types" +import { preferDefault } from "../../bootstrap/prefer-default" +import { generateHtmlPath } from "gatsby-core-utils/page-html" +import { getPageMode } from "../page-mode" +import { generatePageDataPath } from "gatsby-core-utils/page-data" interface IAdapterManager { restoreCache: () => Promise storeCache: () => Promise + adapt: () => Promise } export function initAdapterManager(): IAdapterManager { - const directories = [`.cache`, `public`] + // TODO: figure out adapter to use (and potentially install) based on environent + // for now, just use the demo adapter to work out details of Adapter API + const adapterFn = preferDefault(require(`./adapter-demo`)) as AdapterInit const adapter = adapterFn({ reporter }) - reporter.info(`[dev-adapter-manager] using an adapter`) + reporter.info(`[dev-adapter-manager] using an adapter named ${adapter.name}`) + const directoriesToCache = [`.cache`, `public`] return { restoreCache: async (): Promise => { reporter.info(`[dev-adapter-manager] restoreCache()`) @@ -21,7 +35,7 @@ export function initAdapterManager(): IAdapterManager { return } - const result = await adapter.cache.restore(directories) + const result = await adapter.cache.restore(directoriesToCache) if (result === false) { // if adapter reports `false`, we can skip trying to re-hydrate state return @@ -29,6 +43,8 @@ export function initAdapterManager(): IAdapterManager { const cachedState = readState() + // readState() returns empty object if there is no cached state or it's corrupted etc + // so we want to avoid dispatching RESTORE_CACHE action in that case if (Object.keys(cachedState).length > 0) { store.dispatch({ type: `RESTORE_CACHE`, @@ -42,7 +58,133 @@ export function initAdapterManager(): IAdapterManager { return } - await adapter.cache.store(directories) + await adapter.cache.store(directoriesToCache) }, + adapt: async (): Promise => { + reporter.info(`[dev-adapter-manager] adapt()`) + if (!adapter.adapt) { + return + } + + let _routesManifest: RoutesManifest | undefined = undefined + let _functionsManifest: FunctionsManifest | undefined = undefined + const adaptContext: IAdaptContext = { + get routesManifest(): RoutesManifest { + if (!_routesManifest) { + _routesManifest = getRoutesManifest() + } + + return _routesManifest + }, + get functionsManifest(): FunctionsManifest { + if (!_functionsManifest) { + _functionsManifest = getFunctionsManifest() + } + + return _functionsManifest + }, + } + + await adapter.adapt(adaptContext) + }, + } +} + +const STATIC_PAGE_HEADERS = [ + `cache-control: public, max-age=0, must-revalidate`, + `x-xss-protection: 1; mode=block`, + `x-content-type-options: nosniff`, + `referrer-policy: same-origin`, + `x-frame-options: DENY`, +] + +const STATIC_JS_CHUNK_HEADERS = [ + `cache-control: public, max-age=31536000, immutable`, + `x-xss-protection: 1; mode=block`, + `x-content-type-options: nosniff`, + `referrer-policy: same-origin`, + `x-frame-options: DENY`, +] + +function getRoutesManifest(): RoutesManifest { + const routes = [] as RoutesManifest + + // routes - pages - static (SSG) or lambda (DSG/SSR) + for (const page of store.getState().pages.values()) { + const routePath = page.matchPath ?? page.path + const htmlPath = generateHtmlPath(`public`, page.path) + const pageDataPath = generatePageDataPath(`public`, page.path) + + if (getPageMode(page) === `SSG`) { + routes.push({ + path: routePath, + type: `static`, + filePath: htmlPath, + headers: STATIC_PAGE_HEADERS, + }) + + routes.push({ + path: pageDataPath, + type: `static`, + filePath: pageDataPath, + headers: STATIC_PAGE_HEADERS, + }) + } else { + // TODO: generate lambda function for SSR/DSG + // TODO: figure out caching behavior metadata - maybe take a look at https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions for inspiration + // routes.push({ + // path: routePath, + // type: `lambda`, + // functionId: `ssr-engine`, + // }) + // routes.push({ + // path: pageDataPath, + // type: `lambda`, + // functionId: `ssr-engine`, + // }) + } + } + + // TODO: static asset routes - bundles + + // redirect routes + for (const redirect of store.getState().redirects.values()) { + routes.push({ + path: redirect.fromPath, + type: `redirect`, + toPath: redirect.toPath, + status: redirect.statusCode, + ignoreCase: redirect.ignoreCase, + }) + } + + // function routes + for (const functionInfo of store.getState().functions.values()) { + routes.push({ + path: `/api/${functionInfo.matchPath ?? functionInfo.functionRoute}`, + type: `lambda`, + functionId: functionInfo.functionId, + }) } + + // TODO: figure out any random files copied to public (e.g. static folder) + + return routes +} + +function getFunctionsManifest(): FunctionsManifest { + const functions = [] as FunctionsManifest + + for (const functionInfo of store.getState().functions.values()) { + functions.push({ + functionId: functionInfo.functionId, + pathToCompiledFunction: posix.join( + `.cache`, + `functions`, + functionInfo.relativeCompiledFilePath + ), + }) + } + + return functions } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 4567786882a2f..9380f358d6d73 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -1,12 +1,97 @@ import type reporter from "gatsby-cli/lib/reporter" +interface IBaseRoute { + /** + * Request path that should be matched for this route. + * It can be: + * - static: `/about/` + * - dynamic: + * - parameterized: `/blog/:slug/` + * - catch-all / wildcard: `/app/*` + */ + path: string +} + +interface IStaticRoute extends IBaseRoute { + type: `static` + /** + * Location of the file that should be served for this route. + */ + filePath: string + headers: Array +} + +interface ILambdaRoute extends IBaseRoute { + type: `lambda` + /** + * Identifier of the function. Definition of that function is in function manifest. + * Definition of function is not left directly here as some lambdas will be shared for multiple routes - such as DSG/SSR engine. + */ + functionId: string + // TODO: caching behavior - DSG wants to cache result indefinitely (for current build). On Netlify - use OBD for DSG + // maybe take inspiration from https://vercel.com/docs/build-output-api/v3/primitives#prerender-configuration-file +} + +interface IRedirectRoute extends IBaseRoute { + type: `redirect` + toPath: string + status: number // narrow down types to cocnrete status code that are acceptible here + ignoreCase: boolean // this is not supported by Netlify, but is supported by Gatsby ... + // TODO: createRedirect does accept any random props that might be specific to platform, so this route should have those as well + // maybe they should be just dumped as-is? or maybe it will be better to have a separate field for them? For now dumping things as-is + [key: string]: unknown +} + +type Route = IStaticRoute | ILambdaRoute | IRedirectRoute + +export type RoutesManifest = Array + +export type FunctionsManifest = Array<{ + /** + * Identifier of the function. Referenced in routes manifest in lambda routes. + */ + functionId: string + /** + * Path to function entrypoint that will be used to create lambda. + */ + pathToCompiledFunction: string + // TODO: auxiliaryFilesAndDirecotries: Array - files and directories that should be copied to the function directory - do we need to figure out if platform supports bundling auxilary files to decide how to bundle ssr-engine lambda (wether datastore is bundled in function or deployed to CDN) +}> + +export interface IAdaptContext { + routesManifest: RoutesManifest + functionsManifest: FunctionsManifest +} export interface IAdapter { + name: string cache?: { + /** + * Hook to restore .cache and public directories from previous builds. Executed very early on in the build process. + */ restore: ( directories: Array ) => Promise | boolean | void + /** + * Hook to store .cache and public directories from previous builds. Executed as one of last steps in build process. + */ store: (directories: Array) => Promise | void } + /** + * Hook to prepare platform specific deployment of the build. Executed as one of last steps in build process. + * Routes and Functions manifests are being passed in as arguments and implementation should configure: + * - headers for static assets + * - redirects and rewrites (both user defined ones as well as anything needed for lambda execution) + * - wrap lambda functions with platform specific code if needed (produced ones will be express-like route handlers) + * - possibly upload static assets to CDN (unless platform is configured to just deploy "public" dir, in which case this will be skipped - we won't be doing that for Netlify) + */ + adapt: (context: IAdaptContext) => Promise | void + // TODO: should we have "private storage" handling defining a way to "upload" and "download those private assets? + // this could be used for lmdb datastore in case it's not being bundled with ssr-engine lambda as well as File nodes to handle + // current limitation in Netlify's implementation of DSG/SSR ( https://github.com/netlify/netlify-plugin-gatsby#caveats ) +} + +export interface IAdapterInitArgs { + reporter: typeof reporter } -export type IAdapterCtor = (args: { reporter: typeof reporter }) => IAdapter +export type AdapterInit = (initArgs: IAdapterInitArgs) => IAdapter From ec6e85b0a197719479a26991a8a0869ca40d7a0c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 19 May 2023 12:05:34 +0200 Subject: [PATCH 003/161] cache.restore() return comment --- packages/gatsby/src/utils/adapter/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 9380f358d6d73..0a010db2769f2 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -67,6 +67,7 @@ export interface IAdapter { cache?: { /** * Hook to restore .cache and public directories from previous builds. Executed very early on in the build process. + * If `false` is returned gatsby will skip trying to rehydrate state from fs. */ restore: ( directories: Array From 18a6ef868c8a143dca74bec40f5d4dffbe71d75d Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 19 May 2023 12:23:43 +0200 Subject: [PATCH 004/161] redirect status --- packages/gatsby/src/utils/adapter/adapter-demo.ts | 14 ++++++++++---- packages/gatsby/src/utils/adapter/manager.ts | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/adapter-demo.ts b/packages/gatsby/src/utils/adapter/adapter-demo.ts index 0eed02305dccf..d0098381b17f8 100644 --- a/packages/gatsby/src/utils/adapter/adapter-demo.ts +++ b/packages/gatsby/src/utils/adapter/adapter-demo.ts @@ -14,10 +14,16 @@ const createDemoAdapter: AdapterInit = ({ reporter }) => { }, }, adapt({ routesManifest, functionsManifest }): void { - console.log(`[dev-adapter] cache.adapt()`, { - routesManifest, - functionsManifest, - }) + console.log( + `[dev-adapter] adapt()`, + require(`util`).inspect( + { + routesManifest, + functionsManifest, + }, + { depth: Infinity, colors: true } + ) + ) }, } } diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index ecc0bb1f84063..c4b6da10b3990 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -153,7 +153,7 @@ function getRoutesManifest(): RoutesManifest { path: redirect.fromPath, type: `redirect`, toPath: redirect.toPath, - status: redirect.statusCode, + status: redirect.statusCode ?? redirect.isPermanent ? 301 : 302, ignoreCase: redirect.ignoreCase, }) } From cd8d2c80983d4851ec7f2f2682da5ef1b03eb01d Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 19 May 2023 12:38:53 +0200 Subject: [PATCH 005/161] note about named wildcard paths --- packages/gatsby/src/utils/adapter/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 0a010db2769f2..53cf7a8a44dfe 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -7,7 +7,7 @@ interface IBaseRoute { * - static: `/about/` * - dynamic: * - parameterized: `/blog/:slug/` - * - catch-all / wildcard: `/app/*` + * - catch-all / wildcard: `/app/*` (we also have named wildcards like `/app/*foo` - but this is just how path param props are named inside page template props and shouldn't need to be exposed for platform itself) */ path: string } From 60dfb1bbe2b1f4491da5ec528b652e6999c5480b Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 19 May 2023 12:58:50 +0200 Subject: [PATCH 006/161] don't pass named wildcards to function manifest --- packages/gatsby/src/utils/adapter/manager.ts | 17 +++++++++++++++-- packages/gatsby/src/utils/adapter/types.ts | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index c4b6da10b3990..1b72f0eb9fe15 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -106,12 +106,22 @@ const STATIC_JS_CHUNK_HEADERS = [ `x-frame-options: DENY`, ] +function maybeDropNamedPartOfWildcard( + path: string | undefined +): string | undefined { + if (!path) { + return path + } + + return path.replace(/\*.+$/, `*`) +} + function getRoutesManifest(): RoutesManifest { const routes = [] as RoutesManifest // routes - pages - static (SSG) or lambda (DSG/SSR) for (const page of store.getState().pages.values()) { - const routePath = page.matchPath ?? page.path + const routePath = maybeDropNamedPartOfWildcard(page.matchPath) ?? page.path const htmlPath = generateHtmlPath(`public`, page.path) const pageDataPath = generatePageDataPath(`public`, page.path) @@ -161,7 +171,10 @@ function getRoutesManifest(): RoutesManifest { // function routes for (const functionInfo of store.getState().functions.values()) { routes.push({ - path: `/api/${functionInfo.matchPath ?? functionInfo.functionRoute}`, + path: `/api/${ + maybeDropNamedPartOfWildcard(functionInfo.matchPath) ?? + functionInfo.functionRoute + }`, type: `lambda`, functionId: functionInfo.functionId, }) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 53cf7a8a44dfe..0a010db2769f2 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -7,7 +7,7 @@ interface IBaseRoute { * - static: `/about/` * - dynamic: * - parameterized: `/blog/:slug/` - * - catch-all / wildcard: `/app/*` (we also have named wildcards like `/app/*foo` - but this is just how path param props are named inside page template props and shouldn't need to be exposed for platform itself) + * - catch-all / wildcard: `/app/*` */ path: string } From c70cebbbef4338d34ed8bd1597d6b8ae6dd9a8ba Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 19 May 2023 13:05:07 +0200 Subject: [PATCH 007/161] fix status code resolution for redirects --- packages/gatsby/src/utils/adapter/manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 1b72f0eb9fe15..00431f2a9e1dd 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -163,7 +163,7 @@ function getRoutesManifest(): RoutesManifest { path: redirect.fromPath, type: `redirect`, toPath: redirect.toPath, - status: redirect.statusCode ?? redirect.isPermanent ? 301 : 302, + status: redirect.statusCode ?? (redirect.isPermanent ? 301 : 302), ignoreCase: redirect.ignoreCase, }) } From 01fb83da6be3eb45236b40c8cfc0e6518e01cc73 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 22 May 2023 12:50:58 +0200 Subject: [PATCH 008/161] scaffold initial gatsby-adapter-netlify package and use if for dev purposes (hardcoded for now) --- packages/gatsby-adapter-netlify/.babelrc | 3 ++ packages/gatsby-adapter-netlify/README.md | 3 ++ packages/gatsby-adapter-netlify/package.json | 44 +++++++++++++++++++ packages/gatsby-adapter-netlify/src/index.ts | 33 ++++++++++++++ packages/gatsby-adapter-netlify/tsconfig.json | 3 ++ packages/gatsby/package.json | 1 + .../gatsby/src/utils/adapter/adapter-demo.ts | 31 ------------- packages/gatsby/src/utils/adapter/manager.ts | 42 ++++++++++-------- 8 files changed, 111 insertions(+), 49 deletions(-) create mode 100644 packages/gatsby-adapter-netlify/.babelrc create mode 100644 packages/gatsby-adapter-netlify/README.md create mode 100644 packages/gatsby-adapter-netlify/package.json create mode 100644 packages/gatsby-adapter-netlify/src/index.ts create mode 100644 packages/gatsby-adapter-netlify/tsconfig.json delete mode 100644 packages/gatsby/src/utils/adapter/adapter-demo.ts diff --git a/packages/gatsby-adapter-netlify/.babelrc b/packages/gatsby-adapter-netlify/.babelrc new file mode 100644 index 0000000000000..ac0ad292bb087 --- /dev/null +++ b/packages/gatsby-adapter-netlify/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["babel-preset-gatsby-package"]] +} diff --git a/packages/gatsby-adapter-netlify/README.md b/packages/gatsby-adapter-netlify/README.md new file mode 100644 index 0000000000000..7511e4360f857 --- /dev/null +++ b/packages/gatsby-adapter-netlify/README.md @@ -0,0 +1,3 @@ +# `gatsby-adapter-netlify` + +TODO: fill this out diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json new file mode 100644 index 0000000000000..c39ddef6f20cd --- /dev/null +++ b/packages/gatsby-adapter-netlify/package.json @@ -0,0 +1,44 @@ +{ + "name": "gatsby-adapter-netlify", + "version": "0.0.0", + "description": "Gatsby adapter for Netlify", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": "./dist/index.js" + }, + "scripts": { + "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\"", + "typegen": "rimraf --glob \"dist/**/*.d.ts\" && tsc --emitDeclarationOnly --declaration --declarationDir dist/", + "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\"", + "prepare": "cross-env NODE_ENV=production npm run build && npm run typegen" + }, + "keywords": [ + "gatsby" + ], + "author": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/gatsbyjs/gatsby.git", + "directory": "packages/gatsby-adapter-netlify" + }, + "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-adapter-netlify#readme", + "dependencies": { + "@babel/runtime": "^7.20.13" + }, + "devDependencies": { + "@babel/cli": "^7.20.7", + "@babel/core": "^7.20.12", + "babel-preset-gatsby-package": "^3.10.0-next.2", + "cross-env": "^7.0.3", + "rimraf": "^5.0.0", + "typescript": "^5.0.3" + }, + "files": [ + "dist/" + ], + "engines": { + "node": ">=18.0.0" + } +} diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts new file mode 100644 index 0000000000000..9dcebf1bfe4e8 --- /dev/null +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -0,0 +1,33 @@ +// TODO: make those types publicly exported from gatsby (?) +// for now we can just reference types we have in monorepo +import type { AdapterInit } from "gatsby/src/utils/adapter/types" + +const createNetlifyAdapter: AdapterInit = ({ reporter }) => { + reporter.info(`[gatsby-adapter-netlify] createAdapter()`) + + return { + name: `gatsby-adapter-netlify`, + cache: { + restore(directories): void { + console.log(`[gatsby-adapter-netlify] cache.restore()`, directories) + }, + store(directories): void { + console.log(`[gatsby-adapter-netlify] cache.store()`, directories) + }, + }, + adapt({ routesManifest, functionsManifest }): void { + console.log( + `[gatsby-adapter-netlify] adapt()`, + require(`util`).inspect( + { + routesManifest, + functionsManifest, + }, + { depth: Infinity, colors: true } + ) + ) + }, + } +} + +export default createNetlifyAdapter diff --git a/packages/gatsby-adapter-netlify/tsconfig.json b/packages/gatsby-adapter-netlify/tsconfig.json new file mode 100644 index 0000000000000..4082f16a5d91c --- /dev/null +++ b/packages/gatsby-adapter-netlify/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 657155d21daff..fb88f8a30ff70 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -90,6 +90,7 @@ "find-cache-dir": "^3.3.2", "fs-exists-cached": "1.0.0", "fs-extra": "^11.1.1", + "gatsby-adapter-netlify": "0.0.0", "gatsby-cli": "^5.10.0-next.2", "gatsby-core-utils": "^4.10.0-next.2", "gatsby-graphiql-explorer": "^3.10.0-next.2", diff --git a/packages/gatsby/src/utils/adapter/adapter-demo.ts b/packages/gatsby/src/utils/adapter/adapter-demo.ts deleted file mode 100644 index d0098381b17f8..0000000000000 --- a/packages/gatsby/src/utils/adapter/adapter-demo.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { AdapterInit } from "./types" - -const createDemoAdapter: AdapterInit = ({ reporter }) => { - reporter.info(`[dev-adapter] createAdapter()`) - - return { - name: `gatsby-adapter-demo`, - cache: { - restore(directories): void { - console.log(`[dev-adapter] cache.restore()`, directories) - }, - store(directories): void { - console.log(`[dev-adapter] cache.store()`, directories) - }, - }, - adapt({ routesManifest, functionsManifest }): void { - console.log( - `[dev-adapter] adapt()`, - require(`util`).inspect( - { - routesManifest, - functionsManifest, - }, - { depth: Infinity, colors: true } - ) - ) - }, - } -} - -export default createDemoAdapter diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 00431f2a9e1dd..2bbf67022a96a 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -20,8 +20,10 @@ interface IAdapterManager { export function initAdapterManager(): IAdapterManager { // TODO: figure out adapter to use (and potentially install) based on environent - // for now, just use the demo adapter to work out details of Adapter API - const adapterFn = preferDefault(require(`./adapter-demo`)) as AdapterInit + // for now, just hardcode work-in-progress Netlify adapter to work out details of Adapter API + const adapterFn = preferDefault( + require(`gatsby-adapter-netlify`) + ) as AdapterInit const adapter = adapterFn({ reporter }) @@ -98,13 +100,14 @@ const STATIC_PAGE_HEADERS = [ `x-frame-options: DENY`, ] -const STATIC_JS_CHUNK_HEADERS = [ - `cache-control: public, max-age=31536000, immutable`, - `x-xss-protection: 1; mode=block`, - `x-content-type-options: nosniff`, - `referrer-policy: same-origin`, - `x-frame-options: DENY`, -] +// TODO: gather assets that need JS chunk headers +// const STATIC_JS_CHUNK_HEADERS = [ +// `cache-control: public, max-age=31536000, immutable`, +// `x-xss-protection: 1; mode=block`, +// `x-content-type-options: nosniff`, +// `referrer-policy: same-origin`, +// `x-frame-options: DENY`, +// ] function maybeDropNamedPartOfWildcard( path: string | undefined @@ -121,34 +124,37 @@ function getRoutesManifest(): RoutesManifest { // routes - pages - static (SSG) or lambda (DSG/SSR) for (const page of store.getState().pages.values()) { - const routePath = maybeDropNamedPartOfWildcard(page.matchPath) ?? page.path - const htmlPath = generateHtmlPath(`public`, page.path) - const pageDataPath = generatePageDataPath(`public`, page.path) + const htmlRoutePath = + maybeDropNamedPartOfWildcard(page.matchPath) ?? page.path + const pageDataRoutePath = generatePageDataPath(`public`, htmlRoutePath) if (getPageMode(page) === `SSG`) { + const htmlFilePath = generateHtmlPath(`public`, page.path) + const pageDataFilePath = generatePageDataPath(`public`, htmlFilePath) + routes.push({ - path: routePath, + path: htmlRoutePath, type: `static`, - filePath: htmlPath, + filePath: htmlFilePath, headers: STATIC_PAGE_HEADERS, }) routes.push({ - path: pageDataPath, + path: pageDataRoutePath, type: `static`, - filePath: pageDataPath, + filePath: pageDataFilePath, headers: STATIC_PAGE_HEADERS, }) } else { // TODO: generate lambda function for SSR/DSG // TODO: figure out caching behavior metadata - maybe take a look at https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions for inspiration // routes.push({ - // path: routePath, + // path: htmlRoutePath, // type: `lambda`, // functionId: `ssr-engine`, // }) // routes.push({ - // path: pageDataPath, + // path: pageDataRoutePath, // type: `lambda`, // functionId: `ssr-engine`, // }) From 9e6a35ae17e89ea17ec31606a07378abd543ac72 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 22 May 2023 13:36:16 +0200 Subject: [PATCH 009/161] build adapter package as ESM and load it as such, so we can use ESM-only utils - like @netlify/cache-utils --- packages/gatsby-adapter-netlify/.babelrc | 9 ++++++++- packages/gatsby-adapter-netlify/package.json | 8 ++++---- packages/gatsby/babel.config.js | 3 ++- packages/gatsby/src/commands/build.ts | 2 +- packages/gatsby/src/utils/adapter/manager.ts | 4 ++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/gatsby-adapter-netlify/.babelrc b/packages/gatsby-adapter-netlify/.babelrc index ac0ad292bb087..d191a28e60d5c 100644 --- a/packages/gatsby-adapter-netlify/.babelrc +++ b/packages/gatsby-adapter-netlify/.babelrc @@ -1,3 +1,10 @@ { - "presets": [["babel-preset-gatsby-package"]] + "presets": [ + [ + "babel-preset-gatsby-package", + { + "esm": true + } + ] + ] } diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index c39ddef6f20cd..2b47a39271db2 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -2,15 +2,15 @@ "name": "gatsby-adapter-netlify", "version": "0.0.0", "description": "Gatsby adapter for Netlify", - "main": "dist/index.js", + "main": "dist/index.mjs", "types": "dist/index.d.ts", "exports": { - ".": "./dist/index.js" + ".": "./dist/index.mjs" }, "scripts": { - "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\"", + "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\" --out-file-extension .mjs", "typegen": "rimraf --glob \"dist/**/*.d.ts\" && tsc --emitDeclarationOnly --declaration --declarationDir dist/", - "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\"", + "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\" --out-file-extension .mjs", "prepare": "cross-env NODE_ENV=production npm run build && npm run typegen" }, "keywords": [ diff --git a/packages/gatsby/babel.config.js b/packages/gatsby/babel.config.js index ba3256026d691..f9625ad353c3e 100644 --- a/packages/gatsby/babel.config.js +++ b/packages/gatsby/babel.config.js @@ -11,7 +11,8 @@ module.exports = { `./src/bootstrap/get-config-file.ts`, `./src/bootstrap/resolve-module-exports.ts`, `./src/bootstrap/load-plugins/validate.ts`, - `./src/utils/import-gatsby-plugin.ts` + `./src/utils/adapter/manager.ts`, + `./src/utils/import-gatsby-plugin.ts`, ] }]], } diff --git a/packages/gatsby/src/commands/build.ts b/packages/gatsby/src/commands/build.ts index c3d320cb3a55a..f12cc343aa098 100644 --- a/packages/gatsby/src/commands/build.ts +++ b/packages/gatsby/src/commands/build.ts @@ -115,7 +115,7 @@ module.exports = async function build( ) } - const adapterManager = initAdapterManager() + const adapterManager = await initAdapterManager() await adapterManager.restoreCache() const buildActivity = report.phantomActivity(`build`) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 2bbf67022a96a..44b7ecf5ff098 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -18,11 +18,11 @@ interface IAdapterManager { adapt: () => Promise } -export function initAdapterManager(): IAdapterManager { +export async function initAdapterManager(): Promise { // TODO: figure out adapter to use (and potentially install) based on environent // for now, just hardcode work-in-progress Netlify adapter to work out details of Adapter API const adapterFn = preferDefault( - require(`gatsby-adapter-netlify`) + await import(`gatsby-adapter-netlify`) ) as AdapterInit const adapter = adapterFn({ reporter }) From 32670e112f824e3a829bf1bb8ecba926d4b4d772 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 22 May 2023 15:00:44 +0200 Subject: [PATCH 010/161] no require in esm world --- packages/gatsby-adapter-netlify/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 9dcebf1bfe4e8..7e2723d271e67 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -2,6 +2,9 @@ // for now we can just reference types we have in monorepo import type { AdapterInit } from "gatsby/src/utils/adapter/types" +// just for debugging +import { inspect } from "util" + const createNetlifyAdapter: AdapterInit = ({ reporter }) => { reporter.info(`[gatsby-adapter-netlify] createAdapter()`) @@ -18,7 +21,7 @@ const createNetlifyAdapter: AdapterInit = ({ reporter }) => { adapt({ routesManifest, functionsManifest }): void { console.log( `[gatsby-adapter-netlify] adapt()`, - require(`util`).inspect( + inspect( { routesManifest, functionsManifest, From 671a7d29aca4b85f29f64140a2c4d1d8424e3bee Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 24 May 2023 13:27:07 +0200 Subject: [PATCH 011/161] add redirect headers --- packages/gatsby/src/utils/adapter/manager.ts | 8 ++++++++ packages/gatsby/src/utils/adapter/types.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 44b7ecf5ff098..b01da8e28c29b 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -100,6 +100,13 @@ const STATIC_PAGE_HEADERS = [ `x-frame-options: DENY`, ] +const REDIRECT_HEADERS = [ + `x-xss-protection: 1; mode=block`, + `x-content-type-options: nosniff`, + `referrer-policy: same-origin`, + `x-frame-options: DENY`, +] + // TODO: gather assets that need JS chunk headers // const STATIC_JS_CHUNK_HEADERS = [ // `cache-control: public, max-age=31536000, immutable`, @@ -171,6 +178,7 @@ function getRoutesManifest(): RoutesManifest { toPath: redirect.toPath, status: redirect.statusCode ?? (redirect.isPermanent ? 301 : 302), ignoreCase: redirect.ignoreCase, + headers: REDIRECT_HEADERS, }) } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 0a010db2769f2..4cbd9dfa49d07 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -37,6 +37,7 @@ interface IRedirectRoute extends IBaseRoute { toPath: string status: number // narrow down types to cocnrete status code that are acceptible here ignoreCase: boolean // this is not supported by Netlify, but is supported by Gatsby ... + headers: Array // TODO: createRedirect does accept any random props that might be specific to platform, so this route should have those as well // maybe they should be just dumped as-is? or maybe it will be better to have a separate field for them? For now dumping things as-is [key: string]: unknown From 100c8d17f873f2a12ee5295cfb14bd53be96ab3f Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 24 May 2023 15:27:06 +0200 Subject: [PATCH 012/161] start scaffolding autoinstallation of adapters --- packages/gatsby-adapter-netlify/package.json | 3 +- packages/gatsby/src/utils/adapter/manager.ts | 90 ++++++++++++++++++-- 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 2b47a39271db2..bab3dc7db3e2d 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -5,7 +5,8 @@ "main": "dist/index.mjs", "types": "dist/index.d.ts", "exports": { - ".": "./dist/index.mjs" + ".": "./dist/index.mjs", + "./package.json": "./package.json" }, "scripts": { "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\" --out-file-extension .mjs", diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index b01da8e28c29b..ea76023fdcf71 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -9,23 +9,34 @@ import { } from "./types" import { preferDefault } from "../../bootstrap/prefer-default" import { generateHtmlPath } from "gatsby-core-utils/page-html" +import { createRequireFromPath } from "gatsby-core-utils/create-require-from-path" import { getPageMode } from "../page-mode" import { generatePageDataPath } from "gatsby-core-utils/page-data" +import { satisfies } from "semver" interface IAdapterManager { - restoreCache: () => Promise - storeCache: () => Promise - adapt: () => Promise + restoreCache: () => Promise | void + storeCache: () => Promise | void + adapt: () => Promise | void +} + +function noOpAdapterManager(): IAdapterManager { + return { + restoreCache: (): void => {}, + storeCache: (): void => {}, + adapt: (): void => {}, + } } export async function initAdapterManager(): Promise { - // TODO: figure out adapter to use (and potentially install) based on environent - // for now, just hardcode work-in-progress Netlify adapter to work out details of Adapter API - const adapterFn = preferDefault( - await import(`gatsby-adapter-netlify`) - ) as AdapterInit + const adapterInit = await getAdapterInit() - const adapter = adapterFn({ reporter }) + if (!adapterInit) { + // if we don't have adapter - use no-op adapter manager + return noOpAdapterManager() + } + + const adapter = adapterInit({ reporter }) reporter.info(`[dev-adapter-manager] using an adapter named ${adapter.name}`) @@ -215,3 +226,64 @@ function getFunctionsManifest(): FunctionsManifest { return functions } + +async function getAdapterInit(): Promise { + // TODO: figure out adapter to use (and potentially install) based on environent + // for now, just hardcode work-in-progress Netlify adapter to work out details of Adapter API + + // 1. figure out which adapter (and its version) to use + + // this is just random npm package to test autoinstallation soon + // const adapterToUse = { + // packageName: `ascii-cats`, + // version: `^1.1.1`, + // } + + const adapterToUse = { + packageName: `gatsby-adapter-netlify`, + version: `*`, + } + + if (!adapterToUse) { + reporter.info( + `[dev-adapter-manager] using no-op adapter, because nothing was discovered` + ) + return undefined + } + + // 2. check siteDir + // try to resolve from siteDir + try { + const siteRequire = createRequireFromPath(`${process.cwd()}/:internal:`) + const adapterPackageJson = siteRequire( + `${adapterToUse.packageName}/package.json` + ) + + if ( + satisfies(adapterPackageJson.version, adapterToUse.version, { + includePrerelease: true, + }) + ) { + // console.log(`SATISFIED`, adapterPackageJson.version, adapterToUse.version) + return preferDefault( + await import(siteRequire.resolve(adapterToUse.packageName)) + ) as AdapterInit + } + // else { + // console.log( + // `NOT SATISFIED`, + // adapterPackageJson.version, + // adapterToUse.version + // ) + // } + } catch (e) { + // no-op + } + + // 3. check .cache/adapters + // const gatsbyManagedAdaptersLocation = `.cache/adapters` + + // 4. install to .cache/adapters if still not available + + return undefined +} From c7676c3255c42052fa1584a4d557c3e6460dbc45 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 30 May 2023 13:54:04 +0200 Subject: [PATCH 013/161] webpack assets + unmanaged assets (start) --- packages/gatsby/src/utils/adapter/manager.ts | 93 ++++++++++++++++--- .../utils/gatsby-webpack-stats-extractor.ts | 35 ++++++- 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index ea76023fdcf71..cc76870d73732 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -13,6 +13,7 @@ import { createRequireFromPath } from "gatsby-core-utils/create-require-from-pat import { getPageMode } from "../page-mode" import { generatePageDataPath } from "gatsby-core-utils/page-data" import { satisfies } from "semver" +import { sync as globSync } from "glob" interface IAdapterManager { restoreCache: () => Promise | void @@ -118,14 +119,20 @@ const REDIRECT_HEADERS = [ `x-frame-options: DENY`, ] -// TODO: gather assets that need JS chunk headers -// const STATIC_JS_CHUNK_HEADERS = [ -// `cache-control: public, max-age=31536000, immutable`, -// `x-xss-protection: 1; mode=block`, -// `x-content-type-options: nosniff`, -// `referrer-policy: same-origin`, -// `x-frame-options: DENY`, -// ] +const ASSET_HEADERS = [ + `x-xss-protection: 1; mode=block`, + `x-content-type-options: nosniff`, + `referrer-policy: same-origin`, + `x-frame-options: DENY`, +] + +const WEBPACK_ASSET_HEADERS = [ + `cache-control: public, max-age=31536000, immutable`, + `x-xss-protection: 1; mode=block`, + `x-content-type-options: nosniff`, + `referrer-policy: same-origin`, + `x-frame-options: DENY`, +] function maybeDropNamedPartOfWildcard( path: string | undefined @@ -137,32 +144,59 @@ function maybeDropNamedPartOfWildcard( return path.replace(/\*.+$/, `*`) } +let webpackAssets: Set | undefined +export function setWebpackAssets(assets: Set): void { + webpackAssets = assets +} + function getRoutesManifest(): RoutesManifest { const routes = [] as RoutesManifest + const fileAssets = new Set( + globSync(`**/**`, { + cwd: posix.join(process.cwd(), `public`), + nodir: true, + dot: true, + }) + ) + + function removeFromAssets(path: string): void { + if (fileAssets.has(path)) { + fileAssets.delete(path) + } else { + reporter.warn( + `[dev-adapter-manager] tried to remove "${path}" from assets but it wasn't there` + ) + } + } + // routes - pages - static (SSG) or lambda (DSG/SSR) for (const page of store.getState().pages.values()) { const htmlRoutePath = maybeDropNamedPartOfWildcard(page.matchPath) ?? page.path - const pageDataRoutePath = generatePageDataPath(`public`, htmlRoutePath) + const pageDataRoutePath = generatePageDataPath(``, htmlRoutePath) if (getPageMode(page) === `SSG`) { - const htmlFilePath = generateHtmlPath(`public`, page.path) - const pageDataFilePath = generatePageDataPath(`public`, htmlFilePath) + const htmlFilePath = generateHtmlPath(``, page.path) + const pageDataFilePath = generatePageDataPath(``, page.path) routes.push({ path: htmlRoutePath, type: `static`, - filePath: htmlFilePath, + filePath: posix.join(`public`, htmlFilePath), headers: STATIC_PAGE_HEADERS, }) + removeFromAssets(htmlFilePath) + routes.push({ path: pageDataRoutePath, type: `static`, - filePath: pageDataFilePath, + filePath: posix.join(`public`, pageDataFilePath), headers: STATIC_PAGE_HEADERS, }) + + removeFromAssets(pageDataFilePath) } else { // TODO: generate lambda function for SSR/DSG // TODO: figure out caching behavior metadata - maybe take a look at https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions for inspiration @@ -179,7 +213,24 @@ function getRoutesManifest(): RoutesManifest { } } - // TODO: static asset routes - bundles + // webpack assets + if (!webpackAssets) { + throw new Error(`[dev-adapter-manager] webpackAssets is not defined`) + } + + for (const asset of webpackAssets) { + routes.push({ + path: asset, + type: `static`, + filePath: posix.join(`public`, asset), + headers: WEBPACK_ASSET_HEADERS, + }) + removeFromAssets(asset) + } + + // TODO: slices + + // TODO: static query json assets // redirect routes for (const redirect of store.getState().redirects.values()) { @@ -205,7 +256,19 @@ function getRoutesManifest(): RoutesManifest { }) } - // TODO: figure out any random files copied to public (e.g. static folder) + console.log( + `[dev-adapter-manager] unmanaged (or not yet handled) assets`, + fileAssets + ) + + for (const fileAsset of fileAssets) { + routes.push({ + type: `static`, + path: fileAsset, + filePath: posix.join(`public`, fileAsset), + headers: ASSET_HEADERS, + }) + } return routes } diff --git a/packages/gatsby/src/utils/gatsby-webpack-stats-extractor.ts b/packages/gatsby/src/utils/gatsby-webpack-stats-extractor.ts index 78dcd65d1ee45..b09dba3d65154 100644 --- a/packages/gatsby/src/utils/gatsby-webpack-stats-extractor.ts +++ b/packages/gatsby/src/utils/gatsby-webpack-stats-extractor.ts @@ -4,6 +4,7 @@ import { Compiler } from "webpack" import { PARTIAL_HYDRATION_CHUNK_REASON } from "./webpack/plugins/partial-hydration" import { store } from "../redux" import { ensureFileContent } from "./ensure-file-content" +import { setWebpackAssets } from "./adapter/manager" let previousChunkMapJson: string | undefined let previousWebpackStatsJson: string | undefined @@ -60,12 +61,44 @@ export class GatsbyWebpackStatsExtractor { } } + const { + namedChunkGroups = {}, + name = ``, + ...assetsRelatedStats + } = stats.toJson({ + all: false, + chunkGroups: true, + cachedAssets: true, + assets: true, + }) + const webpackStats = { - ...stats.toJson({ all: false, chunkGroups: true }), + name, + namedChunkGroups, assetsByChunkName: assets, childAssetsByChunkName: childAssets, } + if (assetsRelatedStats.assets) { + const assets = new Set() + for (const asset of assetsRelatedStats.assets) { + assets.add(asset.name) + + if (asset.info.related) { + for (const relatedAssets of Object.values(asset.info.related)) { + if (Array.isArray(relatedAssets)) { + for (const relatedAsset of relatedAssets) { + assets.add(relatedAsset) + } + } else { + assets.add(relatedAssets) + } + } + } + } + setWebpackAssets(assets) + } + const newChunkMapJson = JSON.stringify(assetsMap) if (newChunkMapJson !== previousChunkMapJson) { await fs.writeFile( From ceaf74081571ec48d8c07e6d02f14fea607e9fa9 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 30 May 2023 17:54:08 +0200 Subject: [PATCH 014/161] static queries, app-data.json, minor refactors and initial setup for having routes sorted by specificity --- packages/gatsby/src/utils/adapter/manager.ts | 89 +++++++++++--------- packages/gatsby/src/utils/adapter/types.ts | 2 +- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index cc76870d73732..8be8c9ee94ac3 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -6,6 +6,7 @@ import { IAdaptContext, AdapterInit, RoutesManifest, + Route, } from "./types" import { preferDefault } from "../../bootstrap/prefer-default" import { generateHtmlPath } from "gatsby-core-utils/page-html" @@ -14,6 +15,7 @@ import { getPageMode } from "../page-mode" import { generatePageDataPath } from "gatsby-core-utils/page-data" import { satisfies } from "semver" import { sync as globSync } from "glob" +import { getStaticQueryPath } from "../static-query-utils" interface IAdapterManager { restoreCache: () => Promise | void @@ -150,6 +152,8 @@ export function setWebpackAssets(assets: Set): void { } function getRoutesManifest(): RoutesManifest { + // TODO: have routes list sorted by specifity so more specific ones are before less specific ones (/static should be before /:param and that should be before /*), + // so routing can just handle first match const routes = [] as RoutesManifest const fileAssets = new Set( @@ -160,12 +164,31 @@ function getRoutesManifest(): RoutesManifest { }) ) - function removeFromAssets(path: string): void { - if (fileAssets.has(path)) { - fileAssets.delete(path) + function addSortedRoute(route: Route): void { + if (!route.path.startsWith(`/`)) { + route.path = `/${route.path}` + } + // TODO: calculate specifity of route's path and insert route in correct place + routes.push(route) + } + + function addStaticRoute( + path: string, + pathToFilInPublicDir: string, + headers: Array + ): void { + addSortedRoute({ + path, + type: `static`, + filePath: posix.join(`public`, pathToFilInPublicDir), + headers, + }) + + if (fileAssets.has(pathToFilInPublicDir)) { + fileAssets.delete(pathToFilInPublicDir) } else { reporter.warn( - `[dev-adapter-manager] tried to remove "${path}" from assets but it wasn't there` + `[dev-adapter-manager] tried to remove "${pathToFilInPublicDir}" from assets but it wasn't there` ) } } @@ -180,23 +203,8 @@ function getRoutesManifest(): RoutesManifest { const htmlFilePath = generateHtmlPath(``, page.path) const pageDataFilePath = generatePageDataPath(``, page.path) - routes.push({ - path: htmlRoutePath, - type: `static`, - filePath: posix.join(`public`, htmlFilePath), - headers: STATIC_PAGE_HEADERS, - }) - - removeFromAssets(htmlFilePath) - - routes.push({ - path: pageDataRoutePath, - type: `static`, - filePath: posix.join(`public`, pageDataFilePath), - headers: STATIC_PAGE_HEADERS, - }) - - removeFromAssets(pageDataFilePath) + addStaticRoute(htmlRoutePath, htmlFilePath, STATIC_PAGE_HEADERS) + addStaticRoute(pageDataRoutePath, pageDataFilePath, STATIC_PAGE_HEADERS) } else { // TODO: generate lambda function for SSR/DSG // TODO: figure out caching behavior metadata - maybe take a look at https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions for inspiration @@ -213,28 +221,38 @@ function getRoutesManifest(): RoutesManifest { } } + // static query json assets + for (const staticQueryComponent of store + .getState() + .staticQueryComponents.values()) { + const staticQueryResultPath = getStaticQueryPath(staticQueryComponent.hash) + addStaticRoute( + staticQueryResultPath, + staticQueryResultPath, + STATIC_PAGE_HEADERS + ) + } + + // app-data.json + { + const appDataFilePath = posix.join(`page-data`, `app-data.json`) + addStaticRoute(appDataFilePath, appDataFilePath, STATIC_PAGE_HEADERS) + } + // webpack assets if (!webpackAssets) { throw new Error(`[dev-adapter-manager] webpackAssets is not defined`) } for (const asset of webpackAssets) { - routes.push({ - path: asset, - type: `static`, - filePath: posix.join(`public`, asset), - headers: WEBPACK_ASSET_HEADERS, - }) - removeFromAssets(asset) + addStaticRoute(asset, asset, WEBPACK_ASSET_HEADERS) } // TODO: slices - // TODO: static query json assets - // redirect routes for (const redirect of store.getState().redirects.values()) { - routes.push({ + addSortedRoute({ path: redirect.fromPath, type: `redirect`, toPath: redirect.toPath, @@ -246,7 +264,7 @@ function getRoutesManifest(): RoutesManifest { // function routes for (const functionInfo of store.getState().functions.values()) { - routes.push({ + addSortedRoute({ path: `/api/${ maybeDropNamedPartOfWildcard(functionInfo.matchPath) ?? functionInfo.functionRoute @@ -262,12 +280,7 @@ function getRoutesManifest(): RoutesManifest { ) for (const fileAsset of fileAssets) { - routes.push({ - type: `static`, - path: fileAsset, - filePath: posix.join(`public`, fileAsset), - headers: ASSET_HEADERS, - }) + addStaticRoute(fileAsset, fileAsset, ASSET_HEADERS) } return routes diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 4cbd9dfa49d07..490f680aaf863 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -43,7 +43,7 @@ interface IRedirectRoute extends IBaseRoute { [key: string]: unknown } -type Route = IStaticRoute | ILambdaRoute | IRedirectRoute +export type Route = IStaticRoute | ILambdaRoute | IRedirectRoute export type RoutesManifest = Array From 62cdff2765c0f4edd950ceee502fa366291234d5 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 30 May 2023 12:08:31 +0200 Subject: [PATCH 015/161] move adapter version to 1.0.0 --- packages/gatsby-adapter-netlify/package.json | 2 +- packages/gatsby/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index bab3dc7db3e2d..887243d9d121b 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -1,6 +1,6 @@ { "name": "gatsby-adapter-netlify", - "version": "0.0.0", + "version": "1.0.0", "description": "Gatsby adapter for Netlify", "main": "dist/index.mjs", "types": "dist/index.d.ts", diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index fb88f8a30ff70..ee024b2f40579 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -90,7 +90,7 @@ "find-cache-dir": "^3.3.2", "fs-exists-cached": "1.0.0", "fs-extra": "^11.1.1", - "gatsby-adapter-netlify": "0.0.0", + "gatsby-adapter-netlify": "^1.0.0", "gatsby-cli": "^5.10.0-next.2", "gatsby-core-utils": "^4.10.0-next.2", "gatsby-graphiql-explorer": "^3.10.0-next.2", From 98da13d7433d2b80e7398725b4bf894ff5a692b5 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 30 May 2023 14:02:49 +0200 Subject: [PATCH 016/161] generalize get-latest-apis for adapters --- packages/gatsby/.gitignore | 1 + packages/gatsby/adapters.js | 33 ++++++++++ packages/gatsby/package.json | 1 + packages/gatsby/scripts/postinstall.js | 3 +- .../load-plugins/__tests__/validate.ts | 2 +- .../src/bootstrap/load-plugins/validate.ts | 2 +- ...est-apis.ts => get-latest-gatsby-files.ts} | 6 +- packages/gatsby/src/utils/adapter/types.ts | 41 +++++++++++- packages/gatsby/src/utils/get-latest-apis.ts | 35 ---------- .../src/utils/get-latest-gatsby-files.ts | 65 +++++++++++++++++++ 10 files changed, 147 insertions(+), 42 deletions(-) create mode 100644 packages/gatsby/adapters.js rename packages/gatsby/src/utils/__tests__/{get-latest-apis.ts => get-latest-gatsby-files.ts} (91%) delete mode 100644 packages/gatsby/src/utils/get-latest-apis.ts create mode 100644 packages/gatsby/src/utils/get-latest-gatsby-files.ts diff --git a/packages/gatsby/.gitignore b/packages/gatsby/.gitignore index dba058b704eb1..e4ab68e97a3a5 100644 --- a/packages/gatsby/.gitignore +++ b/packages/gatsby/.gitignore @@ -35,3 +35,4 @@ cache-dir/commonjs/ # cached files /latest-apis.json +/latest-adapters.js diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js new file mode 100644 index 0000000000000..367b3123c0dc0 --- /dev/null +++ b/packages/gatsby/adapters.js @@ -0,0 +1,33 @@ +/** + * List of adapters that should be automatically installed if not present already. + * The first item which test function returns `true` will be used. + * + * If you're the author of an adapter and want to add it to this list, please open a PR! + * If you want to create an adapter, please see: TODO + * + * @type {import("./src/utils/adapter/types").IAdapterManifestEntry} + */ +export const adaptersManifest = [ + { + name: `TESTING`, + module: `ascii-cats`, + test: () => true, + versions: [ + { + gatsbyVersion: `^5.0.0`, + moduleVersion: `^1.1.1` + } + ] + }, + { + name: `Netlify`, + module: `gatsby-adapter-netlify`, + test: () => !!process.env.NETLIFY, + versions: [ + { + gatsbyVersion: `^5.0.0`, + moduleVersion: `^1.0.0` + } + ] + }, +] diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index ee024b2f40579..b72a5cee6bca9 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -213,6 +213,7 @@ "node": ">=18.0.0" }, "files": [ + "adapters.js", "apis.json", "ipc.json", "cache-dir/", diff --git a/packages/gatsby/scripts/postinstall.js b/packages/gatsby/scripts/postinstall.js index a6667923f6041..ea53766964b27 100644 --- a/packages/gatsby/scripts/postinstall.js +++ b/packages/gatsby/scripts/postinstall.js @@ -1,6 +1,7 @@ try { - const { getLatestAPIs } = require('../dist/utils/get-latest-apis') + const { getLatestAPIs, getLatestAdapters } = require('../dist/utils/get-latest-gatsby-files') getLatestAPIs() + getLatestAdapters() } catch (e) { // we're probably just bootstrapping and not published yet! } diff --git a/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts index d27e9601bcf02..2ef2311f8d9de 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts @@ -18,7 +18,7 @@ import { ExportType, IEntry, } from "../validate" -import { getLatestAPIs } from "../../../utils/get-latest-apis" +import { getLatestAPIs } from "../../../utils/get-latest-gatsby-files" import { resolveModuleExports } from "../../resolve-module-exports" beforeEach(() => { diff --git a/packages/gatsby/src/bootstrap/load-plugins/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/validate.ts index 6bca15195da53..059a38d72775a 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/validate.ts @@ -10,7 +10,7 @@ import { stripIndent } from "common-tags" import { trackCli } from "gatsby-telemetry" import { isWorker } from "gatsby-worker" import { resolveModuleExports } from "../resolve-module-exports" -import { getLatestAPIs } from "../../utils/get-latest-apis" +import { getLatestAPIs } from "../../utils/get-latest-gatsby-files" import { GatsbyNode, PackageJson } from "../../../" import { IPluginInfo, diff --git a/packages/gatsby/src/utils/__tests__/get-latest-apis.ts b/packages/gatsby/src/utils/__tests__/get-latest-gatsby-files.ts similarity index 91% rename from packages/gatsby/src/utils/__tests__/get-latest-apis.ts rename to packages/gatsby/src/utils/__tests__/get-latest-gatsby-files.ts index d1d31e70761fd..274d4a0685589 100644 --- a/packages/gatsby/src/utils/__tests__/get-latest-apis.ts +++ b/packages/gatsby/src/utils/__tests__/get-latest-gatsby-files.ts @@ -15,7 +15,7 @@ jest.mock(`axios`, () => { const path = require(`path`) const fs = require(`fs-extra`) const axios = require(`axios`) -import { getLatestAPIs, IAPIResponse } from "../get-latest-apis" +import { getLatestAPIs, IAPIResponse } from "../get-latest-gatsby-files" beforeEach(() => { ;[fs, axios].forEach(mock => @@ -47,7 +47,7 @@ describe(`default behavior: has network connectivity`, () => { expect(data).toEqual(getMockAPIFile()) }) - it(`writes api file`, async () => { + it(`writes apis.json file`, async () => { const data = await getLatestAPIs() expect(fs.writeFile).toHaveBeenCalledWith( @@ -77,7 +77,7 @@ describe(`downloading APIs failure`, () => { expect(data).toEqual(apis) }) - it(`falls back to local api.json if latest-apis.json not cached`, async () => { + it(`falls back to local apis.json if latest-apis.json not cached`, async () => { const apis = getMockAPIFile() fs.pathExists.mockResolvedValueOnce(false) fs.readJSON.mockResolvedValueOnce(apis) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 490f680aaf863..02393ef0791f2 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -64,6 +64,9 @@ export interface IAdaptContext { functionsManifest: FunctionsManifest } export interface IAdapter { + /** + * Unique name of the adapter. Used to identify adapter in manifest. + */ name: string cache?: { /** @@ -84,7 +87,7 @@ export interface IAdapter { * - headers for static assets * - redirects and rewrites (both user defined ones as well as anything needed for lambda execution) * - wrap lambda functions with platform specific code if needed (produced ones will be express-like route handlers) - * - possibly upload static assets to CDN (unless platform is configured to just deploy "public" dir, in which case this will be skipped - we won't be doing that for Netlify) + * - possibly upload static assets to CDN (unless platform is configured to just deploy "public" dir, in which case this will be skipped) */ adapt: (context: IAdaptContext) => Promise | void // TODO: should we have "private storage" handling defining a way to "upload" and "download those private assets? @@ -97,3 +100,39 @@ export interface IAdapterInitArgs { } export type AdapterInit = (initArgs: IAdapterInitArgs) => IAdapter + +export interface IAdapterManager { + restoreCache: () => Promise | void + storeCache: () => Promise | void + adapt: () => Promise | void +} + +export interface IAdapterManifestEntry { + /** + * Name of the adapter + */ + name: string + /** + * Test function to determine if adapter should be used + */ + test: () => boolean + /** + * npm module name of the adapter + */ + module: string + /** + * List of version pairs that are supported by the adapter. + * This allows to have multiple versions of the adapter for different versions of Gatsby. + * This is useful for when APIs change or bugs are fixed that require different implementations. + */ + versions: Array<{ + /** + * Version of the `gatsby` package. This is a semver range. + */ + gatsbyVersion: string + /** + * Version of the adapter. This is a semver range. + */ + moduleVersion: string + }> +} diff --git a/packages/gatsby/src/utils/get-latest-apis.ts b/packages/gatsby/src/utils/get-latest-apis.ts deleted file mode 100644 index 9ecb77db66b25..0000000000000 --- a/packages/gatsby/src/utils/get-latest-apis.ts +++ /dev/null @@ -1,35 +0,0 @@ -import path from "path" -import fs from "fs-extra" -import axios from "axios" - -const API_FILE = `https://unpkg.com/gatsby/apis.json` -const ROOT = path.join(__dirname, `..`, `..`) -const OUTPUT_FILE = path.join(ROOT, `latest-apis.json`) - -export interface IAPIResponse { - browser: Record - node: Record - ssr: Record -} - -export const getLatestAPIs = async (): Promise => { - try { - const { data } = await axios.get(API_FILE, { timeout: 5000 }) - - await fs.writeFile(OUTPUT_FILE, JSON.stringify(data, null, 2), `utf8`) - - return data - } catch (e) { - if (await fs.pathExists(OUTPUT_FILE)) { - return fs.readJSON(OUTPUT_FILE) - } - // possible offline/network issue - return fs.readJSON(path.join(ROOT, `apis.json`)).catch(() => { - return { - browser: {}, - node: {}, - ssr: {}, - } - }) - } -} diff --git a/packages/gatsby/src/utils/get-latest-gatsby-files.ts b/packages/gatsby/src/utils/get-latest-gatsby-files.ts new file mode 100644 index 0000000000000..00ac47e5005c6 --- /dev/null +++ b/packages/gatsby/src/utils/get-latest-gatsby-files.ts @@ -0,0 +1,65 @@ +import path from "path" +import fs from "fs-extra" +import axios from "axios" +import { IAdapterManifestEntry } from "./adapter/types" + +const ROOT = path.join(__dirname, `..`, `..`) +const UNPKG_ROOT = `https://unpkg.com/gatsby/` + +const FILE_NAMES = { + APIS: `apis.json`, + ADAPTERS: `adapters.js`, +} + +const OUTPUT_FILES = { + APIS: path.join(ROOT, `latest-apis.json`), + ADAPTERS: path.join(ROOT, `latest-adapters.js`), +} + +export interface IAPIResponse { + browser: Record + node: Record + ssr: Record +} + +const _getFile = async ({ + fileName, + outputFileName, + defaultReturn, +}): Promise => { + try { + const { data } = await axios.get(`${UNPKG_ROOT}${fileName}`, { + timeout: 5000, + }) + + await fs.writeFile(outputFileName, JSON.stringify(data, null, 2), `utf8`) + + return data + } catch (e) { + if (await fs.pathExists(outputFileName)) { + return fs.readJSON(outputFileName) + } + // possible offline/network issue + return fs.readJSON(path.join(ROOT, fileName)).catch(() => defaultReturn) + } +} + +export const getLatestAPIs = async (): Promise => + _getFile({ + fileName: FILE_NAMES.APIS, + outputFileName: OUTPUT_FILES.APIS, + defaultReturn: { + browser: {}, + node: {}, + ssr: {}, + }, + }) + +export const getLatestAdapters = async (): Promise< + Array +> => + _getFile({ + fileName: FILE_NAMES.ADAPTERS, + outputFileName: OUTPUT_FILES.ADAPTERS, + defaultReturn: [], + }) From 1975ae9424eefcc07aebf7c35f37adb97f4be08b Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 07:19:08 +0200 Subject: [PATCH 017/161] handle JS files in get-latest-gatsby-files --- .../src/utils/get-latest-gatsby-files.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/src/utils/get-latest-gatsby-files.ts b/packages/gatsby/src/utils/get-latest-gatsby-files.ts index 00ac47e5005c6..13d4b006b1765 100644 --- a/packages/gatsby/src/utils/get-latest-gatsby-files.ts +++ b/packages/gatsby/src/utils/get-latest-gatsby-files.ts @@ -1,7 +1,8 @@ import path from "path" -import fs from "fs-extra" +import * as fs from "fs-extra" import axios from "axios" import { IAdapterManifestEntry } from "./adapter/types" +import { preferDefault } from "../bootstrap/prefer-default" const ROOT = path.join(__dirname, `..`, `..`) const UNPKG_ROOT = `https://unpkg.com/gatsby/` @@ -26,6 +27,10 @@ const _getFile = async ({ fileName, outputFileName, defaultReturn, +}: { + fileName: string + outputFileName: string + defaultReturn: T }): Promise => { try { const { data } = await axios.get(`${UNPKG_ROOT}${fileName}`, { @@ -39,8 +44,19 @@ const _getFile = async ({ if (await fs.pathExists(outputFileName)) { return fs.readJSON(outputFileName) } - // possible offline/network issue - return fs.readJSON(path.join(ROOT, fileName)).catch(() => defaultReturn) + + if (fileName.endsWith(`.json`)) { + return fs.readJSON(path.join(ROOT, fileName)).catch(() => defaultReturn) + } else { + try { + const importedFile = await import(path.join(ROOT, fileName)) + const adapters = preferDefault(importedFile) + return adapters + } catch (e) { + // no-op + return defaultReturn + } + } } } From 606c604a36081c4f71e8b66a97977cf644df4117 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 07:19:21 +0200 Subject: [PATCH 018/161] set peerDep --- packages/gatsby-adapter-netlify/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 887243d9d121b..b87c5b0f06f77 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -36,6 +36,9 @@ "rimraf": "^5.0.0", "typescript": "^5.0.3" }, + "peerDependencies": { + "gatsby": "^5.10.0-next" + }, "files": [ "dist/" ], From 0292cb29c619deac5d26dee7443792da09177ac4 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 07:38:03 +0200 Subject: [PATCH 019/161] use other testing pkg --- packages/gatsby-adapter-netlify/package.json | 4 +++- packages/gatsby/adapters.js | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index b87c5b0f06f77..ae0c3797e085b 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -15,7 +15,9 @@ "prepare": "cross-env NODE_ENV=production npm run build && npm run typegen" }, "keywords": [ - "gatsby" + "gatsby", + "gatsby-plugin", + "gatsby-adapter" ], "author": "", "license": "MIT", diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index 367b3123c0dc0..4d2abd14034da 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -7,15 +7,15 @@ * * @type {import("./src/utils/adapter/types").IAdapterManifestEntry} */ -export const adaptersManifest = [ +const adaptersManifest = [ { - name: `TESTING`, - module: `ascii-cats`, + name: `gatsby-adapter-testing`, + module: `@lekoarts/gatsby-adapter-testing`, test: () => true, versions: [ { gatsbyVersion: `^5.0.0`, - moduleVersion: `^1.1.1` + moduleVersion: `^1.0.0` } ] }, @@ -31,3 +31,5 @@ export const adaptersManifest = [ ] }, ] + +module.exports = adaptersManifest From 7da3c6656ebde570361f101a1ac439108e7f6a35 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 07:41:12 +0200 Subject: [PATCH 020/161] get installation, discovery, re-using working --- packages/gatsby/src/services/initialize.ts | 1 + packages/gatsby/src/utils/adapter/manager.ts | 154 +++++++++++++------ 2 files changed, 106 insertions(+), 49 deletions(-) diff --git a/packages/gatsby/src/services/initialize.ts b/packages/gatsby/src/services/initialize.ts index edbb546936837..8204040e026bf 100644 --- a/packages/gatsby/src/services/initialize.ts +++ b/packages/gatsby/src/services/initialize.ts @@ -432,6 +432,7 @@ export async function initialize({ `!.cache/compiled`, // Add webpack `!.cache/webpack`, + `!.cache/adapters` ] if (process.env.GATSBY_EXPERIMENTAL_PRESERVE_FILE_DOWNLOAD_CACHE) { diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 8be8c9ee94ac3..337092a060e28 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -1,27 +1,26 @@ -import { store, readState } from "../../redux" import reporter from "gatsby-cli/lib/reporter" -import { posix } from "path" +import { generateHtmlPath } from "gatsby-core-utils/page-html" +import { createRequireFromPath } from "gatsby-core-utils/create-require-from-path" +import { generatePageDataPath } from "gatsby-core-utils/page-data" +import { join, posix } from "path" +import { emptyDir, ensureDir, outputJson } from "fs-extra" +import execa, { Options as ExecaOptions } from "execa" +import { sync as globSync } from "glob" import { FunctionsManifest, IAdaptContext, AdapterInit, RoutesManifest, Route, + IAdapterManager, } from "./types" +import { store, readState } from "../../redux" import { preferDefault } from "../../bootstrap/prefer-default" -import { generateHtmlPath } from "gatsby-core-utils/page-html" -import { createRequireFromPath } from "gatsby-core-utils/create-require-from-path" import { getPageMode } from "../page-mode" -import { generatePageDataPath } from "gatsby-core-utils/page-data" -import { satisfies } from "semver" -import { sync as globSync } from "glob" +import { getLatestAdapters } from "../get-latest-gatsby-files" import { getStaticQueryPath } from "../static-query-utils" -interface IAdapterManager { - restoreCache: () => Promise | void - storeCache: () => Promise | void - adapt: () => Promise | void -} +const getAdaptersCacheDir = (): string => join(process.cwd(), `.cache/adapters`) function noOpAdapterManager(): IAdapterManager { return { @@ -34,8 +33,8 @@ function noOpAdapterManager(): IAdapterManager { export async function initAdapterManager(): Promise { const adapterInit = await getAdapterInit() + // If we don't have adapter, use no-op adapter manager if (!adapterInit) { - // if we don't have adapter - use no-op adapter manager return noOpAdapterManager() } @@ -304,62 +303,119 @@ function getFunctionsManifest(): FunctionsManifest { } async function getAdapterInit(): Promise { - // TODO: figure out adapter to use (and potentially install) based on environent - // for now, just hardcode work-in-progress Netlify adapter to work out details of Adapter API - - // 1. figure out which adapter (and its version) to use + const latestAdapters = await getLatestAdapters() - // this is just random npm package to test autoinstallation soon - // const adapterToUse = { - // packageName: `ascii-cats`, - // version: `^1.1.1`, - // } - - const adapterToUse = { - packageName: `gatsby-adapter-netlify`, - version: `*`, - } + // Find the correct adapter and its details (e.g. version) + const adapterToUse = latestAdapters.find((candidate) => candidate.test()) if (!adapterToUse) { - reporter.info( - `[dev-adapter-manager] using no-op adapter, because nothing was discovered` + reporter.verbose( + `No adapter was found for the current environment. Skipping adapter initialization.` ) return undefined } - // 2. check siteDir - // try to resolve from siteDir + // TODO: Add handling to allow for an env var to force a specific adapter to be used + + // Check if the user has manually installed the adapter and try to resolve it from there try { const siteRequire = createRequireFromPath(`${process.cwd()}/:internal:`) + /* const adapterPackageJson = siteRequire( - `${adapterToUse.packageName}/package.json` + `${adapterToUse.module}/package.json` ) + */ + + // TODO: Add handling for checking if installed version is compatible with current Gatsby version + + const required = siteRequire.resolve(adapterToUse.module) + + if (required) { + reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside node_modules`) - if ( - satisfies(adapterPackageJson.version, adapterToUse.version, { - includePrerelease: true, - }) - ) { - // console.log(`SATISFIED`, adapterPackageJson.version, adapterToUse.version) return preferDefault( - await import(siteRequire.resolve(adapterToUse.packageName)) + await import(required) ) as AdapterInit } - // else { - // console.log( - // `NOT SATISFIED`, - // adapterPackageJson.version, - // adapterToUse.version - // ) - // } } catch (e) { // no-op } - // 3. check .cache/adapters - // const gatsbyManagedAdaptersLocation = `.cache/adapters` + // Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there + try { + const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) + const required = adaptersRequire.resolve(adapterToUse.module) + + if (required) { + reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside .cache/adapters`) + + return preferDefault(await import(required)) as AdapterInit + } + } catch (e) { + // no-op + } + + const installTimer = reporter.activityTimer(`Installing adapter ${adapterToUse.module}`) + // If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters + try { + installTimer.start() + await createAdaptersCacheDir() + + const options: ExecaOptions = { + stderr: `inherit`, + cwd: getAdaptersCacheDir(), + } + + const npmAdditionalCliArgs = [ + `--no-progress`, + `--no-audit`, + `--no-fund`, + `--loglevel`, + `error`, + `--color`, + `always`, + `--legacy-peer-deps`, + `--save-exact` + ] + + await execa( + `npm`, + [`install`, ...npmAdditionalCliArgs, adapterToUse.module], + options + ) - // 4. install to .cache/adapters if still not available + installTimer.end() + + reporter.info(`If you plan on staying on this deployment platform, consider installing ${adapterToUse.module} as a devDependency in your project. This will give you faster and more robust installs.`) + + const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) + const required = adaptersRequire.resolve(adapterToUse.module) + + if (required) { + reporter.verbose(`Using installed adapter ${adapterToUse.module} inside .cache/adapters`) + + return preferDefault(await import(required)) as AdapterInit + } + } catch (e) { + installTimer.end() + reporter.warn(`Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`) + } return undefined } + +const createAdaptersCacheDir = async (): Promise => { + await ensureDir(getAdaptersCacheDir()) + await emptyDir(getAdaptersCacheDir()) + + const packageJsonPath = join(getAdaptersCacheDir(), `package.json`) + + await outputJson(packageJsonPath, { + name: `gatsby-adapters`, + description: `This directory contains adapters that have been automatically installed by Gatsby.`, + version: `1.0.0`, + private: true, + author: `Gatsby`, + license: `MIT`, + }) +} From 3f1659af8f84d98ee9df690b8b31130151f627d5 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 07:50:51 +0200 Subject: [PATCH 021/161] update versions --- packages/gatsby-adapter-netlify/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index ae0c3797e085b..9cfee72f1daf2 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -33,13 +33,13 @@ "devDependencies": { "@babel/cli": "^7.20.7", "@babel/core": "^7.20.12", - "babel-preset-gatsby-package": "^3.10.0-next.2", + "babel-preset-gatsby-package": "^3.11.0-next.0", "cross-env": "^7.0.3", "rimraf": "^5.0.0", "typescript": "^5.0.3" }, "peerDependencies": { - "gatsby": "^5.10.0-next" + "gatsby": "^5.11.0-next" }, "files": [ "dist/" From ac6ecf3183901f5d2ec82dce3c8065a9f080225f Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 08:10:17 +0200 Subject: [PATCH 022/161] move adapter init into its own file --- packages/gatsby/babel.config.js | 2 +- packages/gatsby/src/utils/adapter/init.ts | 131 +++++++++++++++++++ packages/gatsby/src/utils/adapter/manager.ts | 129 +----------------- 3 files changed, 134 insertions(+), 128 deletions(-) create mode 100644 packages/gatsby/src/utils/adapter/init.ts diff --git a/packages/gatsby/babel.config.js b/packages/gatsby/babel.config.js index f9625ad353c3e..9a2ea474eb6ce 100644 --- a/packages/gatsby/babel.config.js +++ b/packages/gatsby/babel.config.js @@ -11,7 +11,7 @@ module.exports = { `./src/bootstrap/get-config-file.ts`, `./src/bootstrap/resolve-module-exports.ts`, `./src/bootstrap/load-plugins/validate.ts`, - `./src/utils/adapter/manager.ts`, + `./src/utils/adapter/init.ts`, `./src/utils/import-gatsby-plugin.ts`, ] }]], diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts new file mode 100644 index 0000000000000..da73da0ed31c3 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -0,0 +1,131 @@ +import reporter from "gatsby-cli/lib/reporter" +import { createRequireFromPath } from "gatsby-core-utils/create-require-from-path" +import { join } from "path" +import { emptyDir, ensureDir, outputJson } from "fs-extra" +import execa, { Options as ExecaOptions } from "execa" +import { + AdapterInit, +} from "./types" +import { preferDefault } from "../../bootstrap/prefer-default" +import { getLatestAdapters } from "../get-latest-gatsby-files" + +const getAdaptersCacheDir = (): string => join(process.cwd(), `.cache/adapters`) + +export async function getAdapterInit(): Promise { + const latestAdapters = await getLatestAdapters() + + // Find the correct adapter and its details (e.g. version) + const adapterToUse = latestAdapters.find((candidate) => candidate.test()) + + if (!adapterToUse) { + reporter.verbose( + `No adapter was found for the current environment. Skipping adapter initialization.` + ) + return undefined + } + + // TODO: Add handling to allow for an env var to force a specific adapter to be used + + // Check if the user has manually installed the adapter and try to resolve it from there + try { + const siteRequire = createRequireFromPath(`${process.cwd()}/:internal:`) + /* + const adapterPackageJson = siteRequire( + `${adapterToUse.module}/package.json` + ) + */ + + // TODO: Add handling for checking if installed version is compatible with current Gatsby version + + const required = siteRequire.resolve(adapterToUse.module) + + if (required) { + reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside node_modules`) + + return preferDefault( + await import(required) + ) as AdapterInit + } + } catch (e) { + // no-op + } + + // Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there + try { + const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) + const required = adaptersRequire.resolve(adapterToUse.module) + + if (required) { + reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside .cache/adapters`) + + return preferDefault(await import(required)) as AdapterInit + } + } catch (e) { + // no-op + } + + const installTimer = reporter.activityTimer(`Installing adapter ${adapterToUse.module}`) + // If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters + try { + installTimer.start() + await createAdaptersCacheDir() + + const options: ExecaOptions = { + stderr: `inherit`, + cwd: getAdaptersCacheDir(), + } + + const npmAdditionalCliArgs = [ + `--no-progress`, + `--no-audit`, + `--no-fund`, + `--loglevel`, + `error`, + `--color`, + `always`, + `--legacy-peer-deps`, + `--save-exact` + ] + + await execa( + `npm`, + [`install`, ...npmAdditionalCliArgs, adapterToUse.module], + options + ) + + installTimer.end() + + reporter.info(`If you plan on staying on this deployment platform, consider installing ${adapterToUse.module} as a devDependency in your project. This will give you faster and more robust installs.`) + + const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) + const required = adaptersRequire.resolve(adapterToUse.module) + + if (required) { + reporter.verbose(`Using installed adapter ${adapterToUse.module} inside .cache/adapters`) + + return preferDefault(await import(required)) as AdapterInit + } + } catch (e) { + installTimer.end() + console.log({ e }) + reporter.warn(`Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`) + } + + return undefined +} + +const createAdaptersCacheDir = async (): Promise => { + await ensureDir(getAdaptersCacheDir()) + await emptyDir(getAdaptersCacheDir()) + + const packageJsonPath = join(getAdaptersCacheDir(), `package.json`) + + await outputJson(packageJsonPath, { + name: `gatsby-adapters`, + description: `This directory contains adapters that have been automatically installed by Gatsby.`, + version: `1.0.0`, + private: true, + author: `Gatsby`, + license: `MIT`, + }) +} diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 337092a060e28..c3322eb772b85 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -1,26 +1,19 @@ import reporter from "gatsby-cli/lib/reporter" import { generateHtmlPath } from "gatsby-core-utils/page-html" -import { createRequireFromPath } from "gatsby-core-utils/create-require-from-path" import { generatePageDataPath } from "gatsby-core-utils/page-data" -import { join, posix } from "path" -import { emptyDir, ensureDir, outputJson } from "fs-extra" -import execa, { Options as ExecaOptions } from "execa" +import { posix } from "path" import { sync as globSync } from "glob" import { FunctionsManifest, IAdaptContext, - AdapterInit, RoutesManifest, Route, IAdapterManager, } from "./types" import { store, readState } from "../../redux" -import { preferDefault } from "../../bootstrap/prefer-default" import { getPageMode } from "../page-mode" -import { getLatestAdapters } from "../get-latest-gatsby-files" import { getStaticQueryPath } from "../static-query-utils" - -const getAdaptersCacheDir = (): string => join(process.cwd(), `.cache/adapters`) +import { getAdapterInit } from "./init" function noOpAdapterManager(): IAdapterManager { return { @@ -301,121 +294,3 @@ function getFunctionsManifest(): FunctionsManifest { return functions } - -async function getAdapterInit(): Promise { - const latestAdapters = await getLatestAdapters() - - // Find the correct adapter and its details (e.g. version) - const adapterToUse = latestAdapters.find((candidate) => candidate.test()) - - if (!adapterToUse) { - reporter.verbose( - `No adapter was found for the current environment. Skipping adapter initialization.` - ) - return undefined - } - - // TODO: Add handling to allow for an env var to force a specific adapter to be used - - // Check if the user has manually installed the adapter and try to resolve it from there - try { - const siteRequire = createRequireFromPath(`${process.cwd()}/:internal:`) - /* - const adapterPackageJson = siteRequire( - `${adapterToUse.module}/package.json` - ) - */ - - // TODO: Add handling for checking if installed version is compatible with current Gatsby version - - const required = siteRequire.resolve(adapterToUse.module) - - if (required) { - reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside node_modules`) - - return preferDefault( - await import(required) - ) as AdapterInit - } - } catch (e) { - // no-op - } - - // Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there - try { - const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) - const required = adaptersRequire.resolve(adapterToUse.module) - - if (required) { - reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside .cache/adapters`) - - return preferDefault(await import(required)) as AdapterInit - } - } catch (e) { - // no-op - } - - const installTimer = reporter.activityTimer(`Installing adapter ${adapterToUse.module}`) - // If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters - try { - installTimer.start() - await createAdaptersCacheDir() - - const options: ExecaOptions = { - stderr: `inherit`, - cwd: getAdaptersCacheDir(), - } - - const npmAdditionalCliArgs = [ - `--no-progress`, - `--no-audit`, - `--no-fund`, - `--loglevel`, - `error`, - `--color`, - `always`, - `--legacy-peer-deps`, - `--save-exact` - ] - - await execa( - `npm`, - [`install`, ...npmAdditionalCliArgs, adapterToUse.module], - options - ) - - installTimer.end() - - reporter.info(`If you plan on staying on this deployment platform, consider installing ${adapterToUse.module} as a devDependency in your project. This will give you faster and more robust installs.`) - - const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) - const required = adaptersRequire.resolve(adapterToUse.module) - - if (required) { - reporter.verbose(`Using installed adapter ${adapterToUse.module} inside .cache/adapters`) - - return preferDefault(await import(required)) as AdapterInit - } - } catch (e) { - installTimer.end() - reporter.warn(`Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`) - } - - return undefined -} - -const createAdaptersCacheDir = async (): Promise => { - await ensureDir(getAdaptersCacheDir()) - await emptyDir(getAdaptersCacheDir()) - - const packageJsonPath = join(getAdaptersCacheDir(), `package.json`) - - await outputJson(packageJsonPath, { - name: `gatsby-adapters`, - description: `This directory contains adapters that have been automatically installed by Gatsby.`, - version: `1.0.0`, - private: true, - author: `Gatsby`, - license: `MIT`, - }) -} From df07cda2bc22817470275e83fa5493416fce6900 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 09:08:50 +0200 Subject: [PATCH 023/161] add version checking --- packages/gatsby/src/utils/adapter/init.ts | 48 ++++++++++++++++---- packages/gatsby/src/utils/adapter/manager.ts | 2 +- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index da73da0ed31c3..03edf6f420cdd 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -1,8 +1,11 @@ import reporter from "gatsby-cli/lib/reporter" +import _ from "lodash" import { createRequireFromPath } from "gatsby-core-utils/create-require-from-path" import { join } from "path" import { emptyDir, ensureDir, outputJson } from "fs-extra" import execa, { Options as ExecaOptions } from "execa" +import { version as gatsbyVersion } from "gatsby/package.json" +import { satisfies } from "semver" import { AdapterInit, } from "./types" @@ -14,7 +17,7 @@ const getAdaptersCacheDir = (): string => join(process.cwd(), `.cache/adapters`) export async function getAdapterInit(): Promise { const latestAdapters = await getLatestAdapters() - // Find the correct adapter and its details (e.g. version) + // 0. Find the correct adapter and its details (e.g. version) const adapterToUse = latestAdapters.find((candidate) => candidate.test()) if (!adapterToUse) { @@ -26,16 +29,43 @@ export async function getAdapterInit(): Promise { // TODO: Add handling to allow for an env var to force a specific adapter to be used - // Check if the user has manually installed the adapter and try to resolve it from there + // 1. Check if the user has manually installed the adapter and try to resolve it from there try { const siteRequire = createRequireFromPath(`${process.cwd()}/:internal:`) - /* const adapterPackageJson = siteRequire( `${adapterToUse.module}/package.json` ) - */ + const adapterGatsbyPeerDependency = _.get(adapterPackageJson, `peerDependencies.gatsby`) + const moduleVersion = adapterPackageJson?.version + + // Check if the peerDependency of the adapter is compatible with the current Gatsby version + if ( + adapterGatsbyPeerDependency && + !satisfies(gatsbyVersion, adapterGatsbyPeerDependency, { + includePrerelease: true, + }) + ) { + reporter.warn( + `The ${adapterToUse.name} adapter is not compatible with your current Gatsby version ${gatsbyVersion} - It requires gatsby@${adapterGatsbyPeerDependency}` + ) + return undefined + } + + // Cross-check the adapter version with the version manifest and see if the adapter version is correct for the current Gatsby version + const versionForCurrentGatsbyVersion = adapterToUse.versions.find((entry) => satisfies(gatsbyVersion, entry.gatsbyVersion, { includePrerelease: true })) + const isAdapterCompatible = versionForCurrentGatsbyVersion && satisfies(moduleVersion, versionForCurrentGatsbyVersion.moduleVersion) + + if (!versionForCurrentGatsbyVersion) { + reporter.warn(`The ${adapterToUse.name} adapter is not compatible with your current Gatsby version ${gatsbyVersion}.`) + + return undefined + } + + if (!isAdapterCompatible) { + reporter.warn(`${adapterToUse.module}@${moduleVersion} is not compatible with your current Gatsby version ${gatsbyVersion} - Install ${adapterToUse.module}@${versionForCurrentGatsbyVersion.moduleVersion} or later.`) - // TODO: Add handling for checking if installed version is compatible with current Gatsby version + return undefined + } const required = siteRequire.resolve(adapterToUse.module) @@ -50,7 +80,7 @@ export async function getAdapterInit(): Promise { // no-op } - // Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there + // 2. Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there try { const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) const required = adaptersRequire.resolve(adapterToUse.module) @@ -64,8 +94,8 @@ export async function getAdapterInit(): Promise { // no-op } - const installTimer = reporter.activityTimer(`Installing adapter ${adapterToUse.module}`) - // If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters + const installTimer = reporter.activityTimer(`Installing ${adapterToUse.name} adapter`) + // 3. If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters try { installTimer.start() await createAdaptersCacheDir() @@ -107,7 +137,7 @@ export async function getAdapterInit(): Promise { } } catch (e) { installTimer.end() - console.log({ e }) + reporter.warn(`Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`) } diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index c3322eb772b85..c99042e67d985 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -33,7 +33,7 @@ export async function initAdapterManager(): Promise { const adapter = adapterInit({ reporter }) - reporter.info(`[dev-adapter-manager] using an adapter named ${adapter.name}`) + reporter.info(`Using ${adapter.name} adapter`) const directoriesToCache = [`.cache`, `public`] return { From 818c1edaf8118ec89b02045d000248ab94186206 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 10:25:05 +0200 Subject: [PATCH 024/161] adjust comment --- packages/gatsby/src/utils/adapter/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index 03edf6f420cdd..d0f19b16228a5 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -27,7 +27,7 @@ export async function getAdapterInit(): Promise { return undefined } - // TODO: Add handling to allow for an env var to force a specific adapter to be used + // TODO: Add a way for someone to use an unpublished adapter (e.g. local developing) // 1. Check if the user has manually installed the adapter and try to resolve it from there try { From 19828e979e5eed8f9b03fea87f4fbb3163b3f0e6 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 10:26:01 +0200 Subject: [PATCH 025/161] move stuff around --- packages/gatsby/src/utils/adapter/init.ts | 101 +++++++++++++--------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index d0f19b16228a5..79698d53e83d9 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -6,19 +6,33 @@ import { emptyDir, ensureDir, outputJson } from "fs-extra" import execa, { Options as ExecaOptions } from "execa" import { version as gatsbyVersion } from "gatsby/package.json" import { satisfies } from "semver" -import { - AdapterInit, -} from "./types" +import { AdapterInit } from "./types" import { preferDefault } from "../../bootstrap/prefer-default" import { getLatestAdapters } from "../get-latest-gatsby-files" const getAdaptersCacheDir = (): string => join(process.cwd(), `.cache/adapters`) +const createAdaptersCacheDir = async (): Promise => { + await ensureDir(getAdaptersCacheDir()) + await emptyDir(getAdaptersCacheDir()) + + const packageJsonPath = join(getAdaptersCacheDir(), `package.json`) + + await outputJson(packageJsonPath, { + name: `gatsby-adapters`, + description: `This directory contains adapters that have been automatically installed by Gatsby.`, + version: `1.0.0`, + private: true, + author: `Gatsby`, + license: `MIT`, + }) +} + export async function getAdapterInit(): Promise { const latestAdapters = await getLatestAdapters() // 0. Find the correct adapter and its details (e.g. version) - const adapterToUse = latestAdapters.find((candidate) => candidate.test()) + const adapterToUse = latestAdapters.find(candidate => candidate.test()) if (!adapterToUse) { reporter.verbose( @@ -35,7 +49,10 @@ export async function getAdapterInit(): Promise { const adapterPackageJson = siteRequire( `${adapterToUse.module}/package.json` ) - const adapterGatsbyPeerDependency = _.get(adapterPackageJson, `peerDependencies.gatsby`) + const adapterGatsbyPeerDependency = _.get( + adapterPackageJson, + `peerDependencies.gatsby` + ) const moduleVersion = adapterPackageJson?.version // Check if the peerDependency of the adapter is compatible with the current Gatsby version @@ -52,29 +69,37 @@ export async function getAdapterInit(): Promise { } // Cross-check the adapter version with the version manifest and see if the adapter version is correct for the current Gatsby version - const versionForCurrentGatsbyVersion = adapterToUse.versions.find((entry) => satisfies(gatsbyVersion, entry.gatsbyVersion, { includePrerelease: true })) - const isAdapterCompatible = versionForCurrentGatsbyVersion && satisfies(moduleVersion, versionForCurrentGatsbyVersion.moduleVersion) + const versionForCurrentGatsbyVersion = adapterToUse.versions.find(entry => + satisfies(gatsbyVersion, entry.gatsbyVersion, { includePrerelease: true }) + ) + const isAdapterCompatible = + versionForCurrentGatsbyVersion && + satisfies(moduleVersion, versionForCurrentGatsbyVersion.moduleVersion) if (!versionForCurrentGatsbyVersion) { - reporter.warn(`The ${adapterToUse.name} adapter is not compatible with your current Gatsby version ${gatsbyVersion}.`) + reporter.warn( + `The ${adapterToUse.name} adapter is not compatible with your current Gatsby version ${gatsbyVersion}.` + ) return undefined } if (!isAdapterCompatible) { - reporter.warn(`${adapterToUse.module}@${moduleVersion} is not compatible with your current Gatsby version ${gatsbyVersion} - Install ${adapterToUse.module}@${versionForCurrentGatsbyVersion.moduleVersion} or later.`) + reporter.warn( + `${adapterToUse.module}@${moduleVersion} is not compatible with your current Gatsby version ${gatsbyVersion} - Install ${adapterToUse.module}@${versionForCurrentGatsbyVersion.moduleVersion} or later.` + ) return undefined } - + const required = siteRequire.resolve(adapterToUse.module) - + if (required) { - reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside node_modules`) + reporter.verbose( + `Reusing existing adapter ${adapterToUse.module} inside node_modules` + ) - return preferDefault( - await import(required) - ) as AdapterInit + return preferDefault(await import(required)) as AdapterInit } } catch (e) { // no-op @@ -82,11 +107,15 @@ export async function getAdapterInit(): Promise { // 2. Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there try { - const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) + const adaptersRequire = createRequireFromPath( + `${getAdaptersCacheDir()}/:internal:` + ) const required = adaptersRequire.resolve(adapterToUse.module) if (required) { - reporter.verbose(`Reusing existing adapter ${adapterToUse.module} inside .cache/adapters`) + reporter.verbose( + `Reusing existing adapter ${adapterToUse.module} inside .cache/adapters` + ) return preferDefault(await import(required)) as AdapterInit } @@ -94,7 +123,9 @@ export async function getAdapterInit(): Promise { // no-op } - const installTimer = reporter.activityTimer(`Installing ${adapterToUse.name} adapter`) + const installTimer = reporter.activityTimer( + `Installing ${adapterToUse.name} adapter` + ) // 3. If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters try { installTimer.start() @@ -114,7 +145,7 @@ export async function getAdapterInit(): Promise { `--color`, `always`, `--legacy-peer-deps`, - `--save-exact` + `--save-exact`, ] await execa( @@ -125,37 +156,29 @@ export async function getAdapterInit(): Promise { installTimer.end() - reporter.info(`If you plan on staying on this deployment platform, consider installing ${adapterToUse.module} as a devDependency in your project. This will give you faster and more robust installs.`) + reporter.info( + `If you plan on staying on this deployment platform, consider installing ${adapterToUse.module} as a devDependency in your project. This will give you faster and more robust installs.` + ) - const adaptersRequire = createRequireFromPath(`${getAdaptersCacheDir()}/:internal:`) + const adaptersRequire = createRequireFromPath( + `${getAdaptersCacheDir()}/:internal:` + ) const required = adaptersRequire.resolve(adapterToUse.module) if (required) { - reporter.verbose(`Using installed adapter ${adapterToUse.module} inside .cache/adapters`) + reporter.verbose( + `Using installed adapter ${adapterToUse.module} inside .cache/adapters` + ) return preferDefault(await import(required)) as AdapterInit } } catch (e) { installTimer.end() - reporter.warn(`Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`) + reporter.warn( + `Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.` + ) } return undefined } - -const createAdaptersCacheDir = async (): Promise => { - await ensureDir(getAdaptersCacheDir()) - await emptyDir(getAdaptersCacheDir()) - - const packageJsonPath = join(getAdaptersCacheDir(), `package.json`) - - await outputJson(packageJsonPath, { - name: `gatsby-adapters`, - description: `This directory contains adapters that have been automatically installed by Gatsby.`, - version: `1.0.0`, - private: true, - author: `Gatsby`, - license: `MIT`, - }) -} From 207bddfe873492258fff0314423df91fd5737d06 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 13:50:55 +0200 Subject: [PATCH 026/161] feat: add headers to gatsby-config --- packages/gatsby/index.d.ts | 18 ++++++ .../gatsby/src/joi-schemas/__tests__/joi.ts | 55 +++++++++++++++++++ packages/gatsby/src/joi-schemas/joi.ts | 11 ++++ packages/gatsby/src/redux/types.ts | 9 +++ packages/gatsby/src/utils/adapter/manager.ts | 6 ++ packages/gatsby/src/utils/did-you-mean.ts | 1 + 6 files changed, 100 insertions(+) diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 14d700d3ed84f..1466e887f2784 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -318,6 +318,20 @@ type Proxy = { url: string } +type Header = { + /** + * The path to match requests against. + */ + source: string + /** + * Your custom response headers. + */ + headers: Array<{ + key: string + value: string + }> +} + /** * Gatsby configuration API. * @@ -355,6 +369,10 @@ export interface GatsbyConfig { partytownProxiedURLs?: Array /** Sometimes you need more granular/flexible access to the development server. Gatsby exposes the Express.js development server to your site’s gatsby-config.js where you can add Express middleware as needed. */ developMiddleware?(app: any): void + /** + * You can set custom HTTP headers for incoming requests + */ + headers?: Array
} /** diff --git a/packages/gatsby/src/joi-schemas/__tests__/joi.ts b/packages/gatsby/src/joi-schemas/__tests__/joi.ts index 7a2e7591c8510..a5bdb2178a2ff 100644 --- a/packages/gatsby/src/joi-schemas/__tests__/joi.ts +++ b/packages/gatsby/src/joi-schemas/__tests__/joi.ts @@ -265,6 +265,61 @@ describe(`gatsby config`, () => { }) ) }) + + it(`lets you create custom HTTP headers for a path`, () => { + const config = { + headers: [ + { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `some value`, + }, + ], + }, + ], + } + + const result = gatsbyConfigSchema.validate(config) + expect(result.value?.headers).toEqual(config.headers) + }) + + it(`throws on incorrect headers definitions`, () => { + const configOne = { + headers: { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `some value`, + }, + ], + }, + } + + const resultOne = gatsbyConfigSchema.validate(configOne) + expect(resultOne.error).toMatchInlineSnapshot( + `[ValidationError: "headers" must be an array]` + ) + + const configTwo = { + headers: [ + { + source: `*`, + headers: { + key: `x-custom-header`, + value: `some value`, + }, + }, + ], + } + + const resultTwo = gatsbyConfigSchema.validate(configTwo) + expect(resultTwo.error).toMatchInlineSnapshot( + `[ValidationError: "headers[0].headers" must be an array]` + ) + }) }) describe(`node schema`, () => { diff --git a/packages/gatsby/src/joi-schemas/joi.ts b/packages/gatsby/src/joi-schemas/joi.ts index 6f36a53bcc371..331b82cf3257e 100644 --- a/packages/gatsby/src/joi-schemas/joi.ts +++ b/packages/gatsby/src/joi-schemas/joi.ts @@ -83,6 +83,17 @@ export const gatsbyConfigSchema: Joi.ObjectSchema = Joi.object() return value }), + headers: Joi.array().items( + Joi.object().keys({ + source: Joi.string().required(), + headers: Joi.array().items( + Joi.object().keys({ + key: Joi.string().required(), + value: Joi.string().required(), + }).required().unknown(false) + ).required() + }).unknown(false) + ) }) // throws when both assetPrefix and pathPrefix are defined .when( diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 595ef1003b6e2..7677ea9beb260 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -96,6 +96,14 @@ export interface IGraphQLTypegenOptions { generateOnBuild: boolean } +export type Header = { + source: string + headers: Array<{ + key: string + value: string + }> +} + export interface IGatsbyConfig { plugins?: Array<{ // This is the name of the plugin like `gatsby-plugin-manifest` @@ -124,6 +132,7 @@ export interface IGatsbyConfig { jsxImportSource?: string trailingSlash?: TrailingSlash graphqlTypegen?: IGraphQLTypegenOptions + headers?: Array
} export interface IGatsbyNode { diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index c99042e67d985..a2a1e7c8f233f 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -3,6 +3,7 @@ import { generateHtmlPath } from "gatsby-core-utils/page-html" import { generatePageDataPath } from "gatsby-core-utils/page-data" import { posix } from "path" import { sync as globSync } from "glob" +import telemetry from "gatsby-telemetry" import { FunctionsManifest, IAdaptContext, @@ -35,6 +36,8 @@ export async function initAdapterManager(): Promise { reporter.info(`Using ${adapter.name} adapter`) + telemetry.trackFeatureIsUsed(`adapter:${adapter.name}`) + const directoriesToCache = [`.cache`, `public`] return { restoreCache: async (): Promise => { @@ -74,6 +77,9 @@ export async function initAdapterManager(): Promise { return } + const { headers } = store.getState().config + console.log({ headers }) + let _routesManifest: RoutesManifest | undefined = undefined let _functionsManifest: FunctionsManifest | undefined = undefined const adaptContext: IAdaptContext = { diff --git a/packages/gatsby/src/utils/did-you-mean.ts b/packages/gatsby/src/utils/did-you-mean.ts index 014636ea88f1a..ef7629920f379 100644 --- a/packages/gatsby/src/utils/did-you-mean.ts +++ b/packages/gatsby/src/utils/did-you-mean.ts @@ -14,6 +14,7 @@ export const KNOWN_CONFIG_KEYS = [ `jsxImportSource`, `trailingSlash`, `graphqlTypegen`, + `headers`, ] export function didYouMean( From c74bd88d70e15ac5f6a296f86e5d76d319b46f72 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 31 May 2023 14:10:58 +0200 Subject: [PATCH 027/161] misc stuff --- packages/gatsby-adapter-netlify/README.md | 14 ++++++++++++-- packages/gatsby-adapter-netlify/package.json | 3 ++- packages/gatsby-adapter-netlify/src/index.ts | 4 +--- packages/gatsby/index.d.ts | 2 ++ packages/gatsby/src/utils/adapter/init.ts | 4 ++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/gatsby-adapter-netlify/README.md b/packages/gatsby-adapter-netlify/README.md index 7511e4360f857..6ebf50df74778 100644 --- a/packages/gatsby-adapter-netlify/README.md +++ b/packages/gatsby-adapter-netlify/README.md @@ -1,3 +1,13 @@ -# `gatsby-adapter-netlify` +# gatsby-adapter-netlify -TODO: fill this out +TODO: Description + +## Installation + +```shell +npm install gatsby-adapter-netlify +``` + +## Usage + +TODO diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 9cfee72f1daf2..94176dd9568f7 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -19,7 +19,8 @@ "gatsby-plugin", "gatsby-adapter" ], - "author": "", + "author": "pieh", + "contributors": ["LekoArts"], "license": "MIT", "repository": { "type": "git", diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 7e2723d271e67..b92a2b8a1af47 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -1,6 +1,4 @@ -// TODO: make those types publicly exported from gatsby (?) -// for now we can just reference types we have in monorepo -import type { AdapterInit } from "gatsby/src/utils/adapter/types" +import type { AdapterInit } from "gatsby" // just for debugging import { inspect } from "util" diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 1466e887f2784..2a05c339b9543 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -33,6 +33,8 @@ export { export * from "gatsby-script" +export { AdapterInit } from "gatsby/dist/utils/adapter/types" + export const useScrollRestoration: (key: string) => { ref: React.MutableRefObject onScroll(): void diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index 79698d53e83d9..b3685c863d94f 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -157,7 +157,7 @@ export async function getAdapterInit(): Promise { installTimer.end() reporter.info( - `If you plan on staying on this deployment platform, consider installing ${adapterToUse.module} as a devDependency in your project. This will give you faster and more robust installs.` + `If you plan on staying on this deployment platform, consider installing ${adapterToUse.module} as a dependency in your project. This will give you faster and more robust installs.` ) const adaptersRequire = createRequireFromPath( @@ -176,7 +176,7 @@ export async function getAdapterInit(): Promise { installTimer.end() reporter.warn( - `Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.` + `Could not install adapter ${adapterToUse.module}. Please install it yourself by adding it to your package.json's dependencies and try building your project again.` ) } From 89cbde7f05cb3216fe7f4a5c1bc44a8daf6fed27 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 31 May 2023 17:09:19 +0200 Subject: [PATCH 028/161] initial engine lambda --- packages/gatsby/src/utils/adapter/manager.ts | 43 ++++--- packages/gatsby/src/utils/adapter/types.ts | 8 +- .../utils/page-ssr-module/bundle-webpack.ts | 5 + .../src/utils/page-ssr-module/lambda.ts | 110 ++++++++++++++++++ 4 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 packages/gatsby/src/utils/page-ssr-module/lambda.ts diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index a2a1e7c8f233f..87ef9601f9e5e 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -10,11 +10,13 @@ import { RoutesManifest, Route, IAdapterManager, + ILambdaRoute, } from "./types" import { store, readState } from "../../redux" import { getPageMode } from "../page-mode" import { getStaticQueryPath } from "../static-query-utils" import { getAdapterInit } from "./init" +import { shouldGenerateEngines } from "../engines-helpers" function noOpAdapterManager(): IAdapterManager { return { @@ -197,25 +199,33 @@ function getRoutesManifest(): RoutesManifest { maybeDropNamedPartOfWildcard(page.matchPath) ?? page.path const pageDataRoutePath = generatePageDataPath(``, htmlRoutePath) - if (getPageMode(page) === `SSG`) { + const pageMode = getPageMode(page) + + if (pageMode === `SSG`) { const htmlFilePath = generateHtmlPath(``, page.path) const pageDataFilePath = generatePageDataPath(``, page.path) addStaticRoute(htmlRoutePath, htmlFilePath, STATIC_PAGE_HEADERS) addStaticRoute(pageDataRoutePath, pageDataFilePath, STATIC_PAGE_HEADERS) } else { - // TODO: generate lambda function for SSR/DSG - // TODO: figure out caching behavior metadata - maybe take a look at https://vercel.com/docs/build-output-api/v3/primitives#prerender-functions for inspiration - // routes.push({ - // path: htmlRoutePath, - // type: `lambda`, - // functionId: `ssr-engine`, - // }) - // routes.push({ - // path: pageDataRoutePath, - // type: `lambda`, - // functionId: `ssr-engine`, - // }) + const commonFields: Omit = { + type: `lambda`, + functionId: `ssr-engine`, + } + + if (pageMode === `DSG`) { + commonFields.cache = true + } + + addSortedRoute({ + path: htmlRoutePath, + ...commonFields, + }) + + addSortedRoute({ + path: pageDataRoutePath, + ...commonFields, + }) } } @@ -298,5 +308,12 @@ function getFunctionsManifest(): FunctionsManifest { }) } + if (shouldGenerateEngines()) { + functions.push({ + functionId: `ssr-engine`, + pathToCompiledFunction: posix.join(`.cache`, `page-ssr`, `lambda.js`), + }) + } + return functions } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 02393ef0791f2..ff5f38b3d63cc 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -21,15 +21,17 @@ interface IStaticRoute extends IBaseRoute { headers: Array } -interface ILambdaRoute extends IBaseRoute { +export interface ILambdaRoute extends IBaseRoute { type: `lambda` /** * Identifier of the function. Definition of that function is in function manifest. * Definition of function is not left directly here as some lambdas will be shared for multiple routes - such as DSG/SSR engine. */ functionId: string - // TODO: caching behavior - DSG wants to cache result indefinitely (for current build). On Netlify - use OBD for DSG - // maybe take inspiration from https://vercel.com/docs/build-output-api/v3/primitives#prerender-configuration-file + /** + * If `cache` is true, response of lambda should be cached for current deployed and served on subsequent requests for this route. + */ + cache?: true } interface IRedirectRoute extends IBaseRoute { diff --git a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts index c85b298bcda0f..294724f435949 100644 --- a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts +++ b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts @@ -217,6 +217,11 @@ export async function createPageSSRBundle({ ].filter(Boolean) as Array, }) + await fs.copyFile( + path.join(__dirname, `lambda.js`), + path.join(outputDir, `lambda.js`) + ) + return new Promise((resolve, reject) => { compiler.run((err, stats) => { compiler.close(closeErr => { diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts new file mode 100644 index 0000000000000..964447272f6b7 --- /dev/null +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -0,0 +1,110 @@ +import { GatsbyFunctionResponse, GatsbyFunctionRequest } from "gatsby" +import * as path from "path" +import { IGatsbyPage } from "../../internal" +import { ISSRData } from "./entry" + +// using require instead of import here for now because of type hell + import path doesn't exist in current context +// as this file will be copied elsewhere + +const { GraphQLEngine } = + require(`../query-engine`) as typeof import("../../schema/graphql-engine/entry") + +const { getData, renderPageData, renderHTML } = + require(`./index`) as typeof import("./entry") + +const graphqlEngine = new GraphQLEngine({ + dbPath: path.join(__dirname, `..`, `data`, `datastore`), +}) + +function reverseFixedPagePath(pageDataRequestPath: string): string { + return pageDataRequestPath === `index` ? `/` : pageDataRequestPath +} + +function getPathInfo(req: GatsbyFunctionRequest): + | { + isPageData: boolean + pagePath: string + } + | undefined { + // @ts-ignore GatsbyFunctionRequest.path is not in types ... there is no property in types that can be used to get a path currently + const matches = req.path.matchAll(/^\/?page-data\/(.+)\/page-data.json$/gm) + for (const [, requestedPagePath] of matches) { + return { + isPageData: true, + pagePath: reverseFixedPagePath(requestedPagePath), + } + } + + // if not matched + return { + isPageData: false, + // @ts-ignore GatsbyFunctionRequest.path is not in types ... there is no property in types that can be used to get a path currently + pagePath: req.path, + } +} + +function setStatusAndHeaders({ + page, + data, + res, +}: { + page: IGatsbyPage + data: ISSRData + res: GatsbyFunctionResponse +}): void { + if (page.mode === `SSR`) { + if (data.serverDataStatus) { + res.status(data.serverDataStatus) + } + if (data.serverDataHeaders) { + for (const [name, value] of Object.entries(data.serverDataHeaders)) { + res.setHeader(name, value) + } + } + } +} + +async function engineHandler( + req: GatsbyFunctionRequest, + res: GatsbyFunctionResponse +): Promise { + try { + const pathInfo = getPathInfo(req) + console.log(`hello`, pathInfo) + if (!pathInfo) { + res.status(404).send(`Not found`) + return + } + + const { isPageData, pagePath } = pathInfo + + const page = graphqlEngine.findPageByPath(pagePath) + if (!page) { + res.status(404).send(`Not found`) + return + } + + const data = await getData({ + pathName: pagePath, + graphqlEngine, + req, + }) + + if (isPageData) { + const results = await renderPageData({ data }) + setStatusAndHeaders({ page, data, res }) + res.json(results) + return + } else { + const results = await renderHTML({ data }) + setStatusAndHeaders({ page, data, res }) + res.send(results) + return + } + } catch (e) { + console.error(`Engine failed to handle request`, e) + res.status(500).send(`Internal server error.`) + } +} + +export default engineHandler From 0947ec22f5dfda6877d4fec451ea962bd860d5aa Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 1 Jun 2023 16:49:49 +0200 Subject: [PATCH 029/161] start headers utils --- .../src/utils/adapter/__tests__/utils.ts | 75 +++++++++++++++++++ packages/gatsby/src/utils/adapter/utils.ts | 13 ++++ 2 files changed, 88 insertions(+) create mode 100644 packages/gatsby/src/utils/adapter/__tests__/utils.ts create mode 100644 packages/gatsby/src/utils/adapter/utils.ts diff --git a/packages/gatsby/src/utils/adapter/__tests__/utils.ts b/packages/gatsby/src/utils/adapter/__tests__/utils.ts new file mode 100644 index 0000000000000..1f6984c48223b --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/utils.ts @@ -0,0 +1,75 @@ +import type { Header } from "../../../redux/types" +import { splitInStaticAndDynamicBuckets } from "../utils" + +const DEFAULTS = [ + { + key: `cache-control`, + value: `public, max-age=0, must-revalidate`, + }, + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, +] satisfies Header["headers"] + +const dynamicHeader = { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `a`, + }, + { + key: `x-another-custom-header`, + value: `a`, + }, + ], +} + +const staticHeader = { + source: `/some-path/`, + headers: [ + { + key: `x-custom-header`, + value: `b`, + }, + ], +} + +const HEADERS_MINIMAL = [dynamicHeader, staticHeader] satisfies Header[] + +describe(`splitInStaticAndDynamicBuckets`, () => { + it(`works with minimal data`, () => { + const output = splitInStaticAndDynamicBuckets(HEADERS_MINIMAL) + + expect(output.dynamicHeaders).toEqual([dynamicHeader]) + expect(output.staticHeaders).toEqual([staticHeader]) + }) + it(`recognizes all dynamic identifiers`, () => { + const dynamic = [ + dynamicHeader, + { + source: `/blog/:slug`, + headers: [ + { + key: `x-custom-header`, + value: `c`, + }, + ], + }, + { + source: `/blog/:slug*`, + headers: [ + { + key: `x-custom-header`, + value: `d`, + }, + ], + } + ] + const output = splitInStaticAndDynamicBuckets([...dynamic, staticHeader]) + + expect(output.dynamicHeaders).toEqual([...dynamic]) + expect(output.staticHeaders).toEqual([staticHeader]) + }) +}) diff --git a/packages/gatsby/src/utils/adapter/utils.ts b/packages/gatsby/src/utils/adapter/utils.ts new file mode 100644 index 0000000000000..8cbbae136dca2 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/utils.ts @@ -0,0 +1,13 @@ +import _ from "lodash" +import type { Header } from "../../redux/types" + +export const splitInStaticAndDynamicBuckets = (headers: Array
) => { + const isDynamic = (header: Header) => header.source.includes(`:`) || header.source.includes(`*`) + + const [dynamicHeaders, staticHeaders] = _.partition(headers, isDynamic) + + return { + dynamicHeaders, + staticHeaders, + } +} \ No newline at end of file From ead37758f31d808e9ea2e8775646932b89f4dc7a Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 2 Jun 2023 07:00:58 +0200 Subject: [PATCH 030/161] update deps --- packages/gatsby-adapter-netlify/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 94176dd9568f7..a849636e2da72 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -36,8 +36,8 @@ "@babel/core": "^7.20.12", "babel-preset-gatsby-package": "^3.11.0-next.0", "cross-env": "^7.0.3", - "rimraf": "^5.0.0", - "typescript": "^5.0.3" + "rimraf": "^5.0.1", + "typescript": "^5.0.4" }, "peerDependencies": { "gatsby": "^5.11.0-next" From afde61d5be2d8d98cb02e5f67b8b66021ed10da8 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 2 Jun 2023 08:03:04 +0200 Subject: [PATCH 031/161] rewrite util --- .../src/utils/adapter/__tests__/utils.ts | 14 +++++++------- packages/gatsby/src/utils/adapter/utils.ts | 19 +++++++++++++++---- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/utils.ts b/packages/gatsby/src/utils/adapter/__tests__/utils.ts index 1f6984c48223b..ba76be5e694b0 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/utils.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/utils.ts @@ -1,7 +1,7 @@ import type { Header } from "../../../redux/types" -import { splitInStaticAndDynamicBuckets } from "../utils" +import { splitInDynamicAndStaticBuckets } from "../utils" -const DEFAULTS = [ +const DEFAULTS: Header["headers"] = [ { key: `cache-control`, value: `public, max-age=0, must-revalidate`, @@ -10,9 +10,9 @@ const DEFAULTS = [ key: `x-xss-protection`, value: `1; mode=block`, }, -] satisfies Header["headers"] +] -const dynamicHeader = { +const dynamicHeader: Header = { source: `*`, headers: [ { @@ -26,7 +26,7 @@ const dynamicHeader = { ], } -const staticHeader = { +const staticHeader: Header = { source: `/some-path/`, headers: [ { @@ -40,7 +40,7 @@ const HEADERS_MINIMAL = [dynamicHeader, staticHeader] satisfies Header[] describe(`splitInStaticAndDynamicBuckets`, () => { it(`works with minimal data`, () => { - const output = splitInStaticAndDynamicBuckets(HEADERS_MINIMAL) + const output = splitInDynamicAndStaticBuckets(HEADERS_MINIMAL) expect(output.dynamicHeaders).toEqual([dynamicHeader]) expect(output.staticHeaders).toEqual([staticHeader]) @@ -67,7 +67,7 @@ describe(`splitInStaticAndDynamicBuckets`, () => { ], } ] - const output = splitInStaticAndDynamicBuckets([...dynamic, staticHeader]) + const output = splitInDynamicAndStaticBuckets([...dynamic, staticHeader]) expect(output.dynamicHeaders).toEqual([...dynamic]) expect(output.staticHeaders).toEqual([staticHeader]) diff --git a/packages/gatsby/src/utils/adapter/utils.ts b/packages/gatsby/src/utils/adapter/utils.ts index 8cbbae136dca2..01dc770a079a5 100644 --- a/packages/gatsby/src/utils/adapter/utils.ts +++ b/packages/gatsby/src/utils/adapter/utils.ts @@ -1,10 +1,21 @@ -import _ from "lodash" import type { Header } from "../../redux/types" -export const splitInStaticAndDynamicBuckets = (headers: Array
) => { - const isDynamic = (header: Header) => header.source.includes(`:`) || header.source.includes(`*`) +/** + * Takes in the Headers array and splits it into two buckets: + * - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) + * - staticHeaders: Headers with fully static paths (e.g. /static/) + */ +export const splitInDynamicAndStaticBuckets = (headers: Array
) => { + const dynamicHeaders: Array
= [] + const staticHeaders: Array
= [] - const [dynamicHeaders, staticHeaders] = _.partition(headers, isDynamic) + for (const header of headers) { + if (header.source.includes(`:`) || header.source.includes(`*`)) { + dynamicHeaders.push(header) + } else { + staticHeaders.push(header) + } + } return { dynamicHeaders, From d50aee2028fada1996e9216e5458d26346c0a89c Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 2 Jun 2023 08:30:24 +0200 Subject: [PATCH 032/161] linting --- packages/gatsby/src/joi-schemas/joi.ts | 27 +++-- packages/gatsby/src/redux/types.ts | 4 +- .../src/utils/adapter/__tests__/utils.ts | 12 +-- packages/gatsby/src/utils/adapter/manager.ts | 101 ++++++++++++++---- packages/gatsby/src/utils/adapter/types.ts | 5 +- packages/gatsby/src/utils/adapter/utils.ts | 14 +-- 6 files changed, 114 insertions(+), 49 deletions(-) diff --git a/packages/gatsby/src/joi-schemas/joi.ts b/packages/gatsby/src/joi-schemas/joi.ts index 331b82cf3257e..9dff8a8b82130 100644 --- a/packages/gatsby/src/joi-schemas/joi.ts +++ b/packages/gatsby/src/joi-schemas/joi.ts @@ -84,16 +84,23 @@ export const gatsbyConfigSchema: Joi.ObjectSchema = Joi.object() return value }), headers: Joi.array().items( - Joi.object().keys({ - source: Joi.string().required(), - headers: Joi.array().items( - Joi.object().keys({ - key: Joi.string().required(), - value: Joi.string().required(), - }).required().unknown(false) - ).required() - }).unknown(false) - ) + Joi.object() + .keys({ + source: Joi.string().required(), + headers: Joi.array() + .items( + Joi.object() + .keys({ + key: Joi.string().required(), + value: Joi.string().required(), + }) + .required() + .unknown(false) + ) + .required(), + }) + .unknown(false) + ), }) // throws when both assetPrefix and pathPrefix are defined .when( diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 7677ea9beb260..7fca4665220bc 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -96,7 +96,7 @@ export interface IGraphQLTypegenOptions { generateOnBuild: boolean } -export type Header = { +export interface IHeader { source: string headers: Array<{ key: string @@ -132,7 +132,7 @@ export interface IGatsbyConfig { jsxImportSource?: string trailingSlash?: TrailingSlash graphqlTypegen?: IGraphQLTypegenOptions - headers?: Array
+ headers?: Array } export interface IGatsbyNode { diff --git a/packages/gatsby/src/utils/adapter/__tests__/utils.ts b/packages/gatsby/src/utils/adapter/__tests__/utils.ts index ba76be5e694b0..a568fa8f335fd 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/utils.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/utils.ts @@ -1,7 +1,7 @@ -import type { Header } from "../../../redux/types" +import type { IHeader } from "../../../redux/types" import { splitInDynamicAndStaticBuckets } from "../utils" -const DEFAULTS: Header["headers"] = [ +const DEFAULTS: IHeader["headers"] = [ { key: `cache-control`, value: `public, max-age=0, must-revalidate`, @@ -12,7 +12,7 @@ const DEFAULTS: Header["headers"] = [ }, ] -const dynamicHeader: Header = { +const dynamicHeader: IHeader = { source: `*`, headers: [ { @@ -26,7 +26,7 @@ const dynamicHeader: Header = { ], } -const staticHeader: Header = { +const staticHeader: IHeader = { source: `/some-path/`, headers: [ { @@ -36,7 +36,7 @@ const staticHeader: Header = { ], } -const HEADERS_MINIMAL = [dynamicHeader, staticHeader] satisfies Header[] +const HEADERS_MINIMAL: Array = [dynamicHeader, staticHeader] describe(`splitInStaticAndDynamicBuckets`, () => { it(`works with minimal data`, () => { @@ -65,7 +65,7 @@ describe(`splitInStaticAndDynamicBuckets`, () => { value: `d`, }, ], - } + }, ] const output = splitInDynamicAndStaticBuckets([...dynamic, staticHeader]) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 87ef9601f9e5e..2d0df2ec1df0b 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -17,6 +17,7 @@ import { getPageMode } from "../page-mode" import { getStaticQueryPath } from "../static-query-utils" import { getAdapterInit } from "./init" import { shouldGenerateEngines } from "../engines-helpers" +import type { IHeader } from "../../redux/types" function noOpAdapterManager(): IAdapterManager { return { @@ -106,34 +107,88 @@ export async function initAdapterManager(): Promise { } } -const STATIC_PAGE_HEADERS = [ - `cache-control: public, max-age=0, must-revalidate`, - `x-xss-protection: 1; mode=block`, - `x-content-type-options: nosniff`, - `referrer-policy: same-origin`, - `x-frame-options: DENY`, +const STATIC_PAGE_HEADERS: IHeader["headers"] = [ + { + key: `cache-control`, + value: `public, max-age=0, must-revalidate`, + }, + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, ] -const REDIRECT_HEADERS = [ - `x-xss-protection: 1; mode=block`, - `x-content-type-options: nosniff`, - `referrer-policy: same-origin`, - `x-frame-options: DENY`, +const REDIRECT_HEADERS: IHeader["headers"] = [ + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, ] -const ASSET_HEADERS = [ - `x-xss-protection: 1; mode=block`, - `x-content-type-options: nosniff`, - `referrer-policy: same-origin`, - `x-frame-options: DENY`, +const ASSET_HEADERS: IHeader["headers"] = [ + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, ] -const WEBPACK_ASSET_HEADERS = [ - `cache-control: public, max-age=31536000, immutable`, - `x-xss-protection: 1; mode=block`, - `x-content-type-options: nosniff`, - `referrer-policy: same-origin`, - `x-frame-options: DENY`, +const WEBPACK_ASSET_HEADERS: IHeader["headers"] = [ + { + key: `cache-control`, + value: `public, max-age=31536000, immutable`, + }, + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, ] function maybeDropNamedPartOfWildcard( @@ -175,7 +230,7 @@ function getRoutesManifest(): RoutesManifest { function addStaticRoute( path: string, pathToFilInPublicDir: string, - headers: Array + headers: IHeader["headers"] ): void { addSortedRoute({ path, diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index ff5f38b3d63cc..32788d4d6b419 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -1,4 +1,5 @@ import type reporter from "gatsby-cli/lib/reporter" +import type { IHeader } from "../../redux/types" interface IBaseRoute { /** @@ -18,7 +19,7 @@ interface IStaticRoute extends IBaseRoute { * Location of the file that should be served for this route. */ filePath: string - headers: Array + headers: IHeader["headers"] } export interface ILambdaRoute extends IBaseRoute { @@ -39,7 +40,7 @@ interface IRedirectRoute extends IBaseRoute { toPath: string status: number // narrow down types to cocnrete status code that are acceptible here ignoreCase: boolean // this is not supported by Netlify, but is supported by Gatsby ... - headers: Array + headers: IHeader["headers"] // TODO: createRedirect does accept any random props that might be specific to platform, so this route should have those as well // maybe they should be just dumped as-is? or maybe it will be better to have a separate field for them? For now dumping things as-is [key: string]: unknown diff --git a/packages/gatsby/src/utils/adapter/utils.ts b/packages/gatsby/src/utils/adapter/utils.ts index 01dc770a079a5..ac141d99cf66a 100644 --- a/packages/gatsby/src/utils/adapter/utils.ts +++ b/packages/gatsby/src/utils/adapter/utils.ts @@ -1,13 +1,15 @@ -import type { Header } from "../../redux/types" +import type { IHeader } from "../../redux/types" /** * Takes in the Headers array and splits it into two buckets: - * - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) + * - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) * - staticHeaders: Headers with fully static paths (e.g. /static/) */ -export const splitInDynamicAndStaticBuckets = (headers: Array
) => { - const dynamicHeaders: Array
= [] - const staticHeaders: Array
= [] +export const splitInDynamicAndStaticBuckets = ( + headers: Array +): { dynamicHeaders: Array; staticHeaders: Array } => { + const dynamicHeaders: Array = [] + const staticHeaders: Array = [] for (const header of headers) { if (header.source.includes(`:`) || header.source.includes(`*`)) { @@ -21,4 +23,4 @@ export const splitInDynamicAndStaticBuckets = (headers: Array
) => { dynamicHeaders, staticHeaders, } -} \ No newline at end of file +} From ec3c64365983cd731734276c90797027641f6269 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 2 Jun 2023 08:45:38 +0200 Subject: [PATCH 033/161] convert to obj args --- packages/gatsby/src/utils/adapter/manager.ts | 52 ++++++++++++++------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 2d0df2ec1df0b..9ba7e6ec26636 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -227,11 +227,15 @@ function getRoutesManifest(): RoutesManifest { routes.push(route) } - function addStaticRoute( - path: string, - pathToFilInPublicDir: string, + function addStaticRoute({ + path, + pathToFilInPublicDir, + headers, + }: { + path: string + pathToFilInPublicDir: string headers: IHeader["headers"] - ): void { + }): void { addSortedRoute({ path, type: `static`, @@ -260,8 +264,16 @@ function getRoutesManifest(): RoutesManifest { const htmlFilePath = generateHtmlPath(``, page.path) const pageDataFilePath = generatePageDataPath(``, page.path) - addStaticRoute(htmlRoutePath, htmlFilePath, STATIC_PAGE_HEADERS) - addStaticRoute(pageDataRoutePath, pageDataFilePath, STATIC_PAGE_HEADERS) + addStaticRoute({ + path: htmlRoutePath, + pathToFilInPublicDir: htmlFilePath, + headers: STATIC_PAGE_HEADERS, + }) + addStaticRoute({ + path: pageDataRoutePath, + pathToFilInPublicDir: pageDataFilePath, + headers: STATIC_PAGE_HEADERS, + }) } else { const commonFields: Omit = { type: `lambda`, @@ -289,17 +301,21 @@ function getRoutesManifest(): RoutesManifest { .getState() .staticQueryComponents.values()) { const staticQueryResultPath = getStaticQueryPath(staticQueryComponent.hash) - addStaticRoute( - staticQueryResultPath, - staticQueryResultPath, - STATIC_PAGE_HEADERS - ) + addStaticRoute({ + path: staticQueryResultPath, + pathToFilInPublicDir: staticQueryResultPath, + headers: STATIC_PAGE_HEADERS, + }) } // app-data.json { const appDataFilePath = posix.join(`page-data`, `app-data.json`) - addStaticRoute(appDataFilePath, appDataFilePath, STATIC_PAGE_HEADERS) + addStaticRoute({ + path: appDataFilePath, + pathToFilInPublicDir: appDataFilePath, + headers: STATIC_PAGE_HEADERS, + }) } // webpack assets @@ -308,7 +324,11 @@ function getRoutesManifest(): RoutesManifest { } for (const asset of webpackAssets) { - addStaticRoute(asset, asset, WEBPACK_ASSET_HEADERS) + addStaticRoute({ + path: asset, + pathToFilInPublicDir: asset, + headers: WEBPACK_ASSET_HEADERS, + }) } // TODO: slices @@ -343,7 +363,11 @@ function getRoutesManifest(): RoutesManifest { ) for (const fileAsset of fileAssets) { - addStaticRoute(fileAsset, fileAsset, ASSET_HEADERS) + addStaticRoute({ + path: fileAsset, + pathToFilInPublicDir: fileAsset, + headers: ASSET_HEADERS, + }) } return routes From f8396638c10aff03c14cad2df7c6f72b1f4f93e2 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 2 Jun 2023 10:29:16 +0200 Subject: [PATCH 034/161] remove todo comment --- packages/gatsby/src/utils/adapter/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 32788d4d6b419..67838f651829f 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -41,8 +41,6 @@ interface IRedirectRoute extends IBaseRoute { status: number // narrow down types to cocnrete status code that are acceptible here ignoreCase: boolean // this is not supported by Netlify, but is supported by Gatsby ... headers: IHeader["headers"] - // TODO: createRedirect does accept any random props that might be specific to platform, so this route should have those as well - // maybe they should be just dumped as-is? or maybe it will be better to have a separate field for them? For now dumping things as-is [key: string]: unknown } From f46f850718da16d61d5c7057609a11b5b0ef0f23 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 2 Jun 2023 11:16:08 +0200 Subject: [PATCH 035/161] add requiredFiles to functions manifest --- packages/gatsby/src/utils/adapter/manager.ts | 27 +++++++++++++++----- packages/gatsby/src/utils/adapter/types.ts | 7 +++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 9ba7e6ec26636..727cb705e53a8 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -377,20 +377,35 @@ function getFunctionsManifest(): FunctionsManifest { const functions = [] as FunctionsManifest for (const functionInfo of store.getState().functions.values()) { + const pathToEntryPoint = posix.join( + `.cache`, + `functions`, + functionInfo.relativeCompiledFilePath + ) functions.push({ functionId: functionInfo.functionId, - pathToCompiledFunction: posix.join( - `.cache`, - `functions`, - functionInfo.relativeCompiledFilePath - ), + pathToEntryPoint, + requiredFiles: [pathToEntryPoint], }) } if (shouldGenerateEngines()) { + function getFilesFrom(dir: string): Array { + return globSync(`**/**`, { + cwd: posix.join(process.cwd(), dir), + nodir: true, + dot: true, + }).map(file => posix.join(dir, file)) + } + functions.push({ functionId: `ssr-engine`, - pathToCompiledFunction: posix.join(`.cache`, `page-ssr`, `lambda.js`), + pathToEntryPoint: posix.join(`.cache`, `page-ssr`, `lambda.js`), + requiredFiles: [ + ...getFilesFrom(posix.join(`.cache`, `data`, `datastore`)), + ...getFilesFrom(posix.join(`.cache`, `page-ssr`)), + ...getFilesFrom(posix.join(`.cache`, `query-engine`)), + ], }) } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 67838f651829f..e20101588f630 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -56,8 +56,11 @@ export type FunctionsManifest = Array<{ /** * Path to function entrypoint that will be used to create lambda. */ - pathToCompiledFunction: string - // TODO: auxiliaryFilesAndDirecotries: Array - files and directories that should be copied to the function directory - do we need to figure out if platform supports bundling auxilary files to decide how to bundle ssr-engine lambda (wether datastore is bundled in function or deployed to CDN) + pathToEntryPoint: string + /** + * List of all required files that this function needs to run + */ + requiredFiles: Array }> export interface IAdaptContext { From a7c37463632e2f03f739a595f1e73e700b086b29 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 2 Jun 2023 10:59:15 +0200 Subject: [PATCH 036/161] make headers default to [] --- .../gatsby/src/joi-schemas/__tests__/joi.ts | 7 ++++ packages/gatsby/src/joi-schemas/joi.ts | 38 ++++++++++--------- packages/gatsby/src/redux/types.ts | 2 +- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/gatsby/src/joi-schemas/__tests__/joi.ts b/packages/gatsby/src/joi-schemas/__tests__/joi.ts index a5bdb2178a2ff..df70e625c1ed1 100644 --- a/packages/gatsby/src/joi-schemas/__tests__/joi.ts +++ b/packages/gatsby/src/joi-schemas/__tests__/joi.ts @@ -266,6 +266,13 @@ describe(`gatsby config`, () => { ) }) + it(`returns empty array when headers are not set`, () => { + const config = {} + + const result = gatsbyConfigSchema.validate(config) + expect(result.value?.headers).toEqual([]) + }) + it(`lets you create custom HTTP headers for a path`, () => { const config = { headers: [ diff --git a/packages/gatsby/src/joi-schemas/joi.ts b/packages/gatsby/src/joi-schemas/joi.ts index 9dff8a8b82130..59a0259438d4e 100644 --- a/packages/gatsby/src/joi-schemas/joi.ts +++ b/packages/gatsby/src/joi-schemas/joi.ts @@ -83,24 +83,26 @@ export const gatsbyConfigSchema: Joi.ObjectSchema = Joi.object() return value }), - headers: Joi.array().items( - Joi.object() - .keys({ - source: Joi.string().required(), - headers: Joi.array() - .items( - Joi.object() - .keys({ - key: Joi.string().required(), - value: Joi.string().required(), - }) - .required() - .unknown(false) - ) - .required(), - }) - .unknown(false) - ), + headers: Joi.array() + .items( + Joi.object() + .keys({ + source: Joi.string().required(), + headers: Joi.array() + .items( + Joi.object() + .keys({ + key: Joi.string().required(), + value: Joi.string().required(), + }) + .required() + .unknown(false) + ) + .required(), + }) + .unknown(false) + ) + .default([]), }) // throws when both assetPrefix and pathPrefix are defined .when( diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 7fca4665220bc..c57b6b2a1bad5 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -132,7 +132,7 @@ export interface IGatsbyConfig { jsxImportSource?: string trailingSlash?: TrailingSlash graphqlTypegen?: IGraphQLTypegenOptions - headers?: Array + headers: Array } export interface IGatsbyNode { From 9ac144514c369ef6b93a6512d9acf2161491f294 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 2 Jun 2023 10:59:41 +0200 Subject: [PATCH 037/161] move constants to own file --- .../gatsby/src/utils/adapter/constants.ts | 85 ++++++++++++++++ packages/gatsby/src/utils/adapter/manager.ts | 99 +++---------------- 2 files changed, 96 insertions(+), 88 deletions(-) create mode 100644 packages/gatsby/src/utils/adapter/constants.ts diff --git a/packages/gatsby/src/utils/adapter/constants.ts b/packages/gatsby/src/utils/adapter/constants.ts new file mode 100644 index 0000000000000..86fc43784012a --- /dev/null +++ b/packages/gatsby/src/utils/adapter/constants.ts @@ -0,0 +1,85 @@ +import type { IHeader } from "../../redux/types" + +export const STATIC_PAGE_HEADERS: IHeader["headers"] = [ + { + key: `cache-control`, + value: `public, max-age=0, must-revalidate`, + }, + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, +] + +export const REDIRECT_HEADERS: IHeader["headers"] = [ + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, +] + +export const ASSET_HEADERS: IHeader["headers"] = [ + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, +] + +export const WEBPACK_ASSET_HEADERS: IHeader["headers"] = [ + { + key: `cache-control`, + value: `public, max-age=31536000, immutable`, + }, + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + { + key: `x-content-type-options`, + value: `nosniff`, + }, + { + key: `referrer-policy`, + value: `same-origin`, + }, + { + key: `x-frame-options`, + value: `DENY`, + }, +] diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 727cb705e53a8..98f5456fec919 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -17,6 +17,12 @@ import { getPageMode } from "../page-mode" import { getStaticQueryPath } from "../static-query-utils" import { getAdapterInit } from "./init" import { shouldGenerateEngines } from "../engines-helpers" +import { + ASSET_HEADERS, + REDIRECT_HEADERS, + STATIC_PAGE_HEADERS, + WEBPACK_ASSET_HEADERS, +} from "./constants" import type { IHeader } from "../../redux/types" function noOpAdapterManager(): IAdapterManager { @@ -80,9 +86,6 @@ export async function initAdapterManager(): Promise { return } - const { headers } = store.getState().config - console.log({ headers }) - let _routesManifest: RoutesManifest | undefined = undefined let _functionsManifest: FunctionsManifest | undefined = undefined const adaptContext: IAdaptContext = { @@ -107,90 +110,6 @@ export async function initAdapterManager(): Promise { } } -const STATIC_PAGE_HEADERS: IHeader["headers"] = [ - { - key: `cache-control`, - value: `public, max-age=0, must-revalidate`, - }, - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, - { - key: `x-content-type-options`, - value: `nosniff`, - }, - { - key: `referrer-policy`, - value: `same-origin`, - }, - { - key: `x-frame-options`, - value: `DENY`, - }, -] - -const REDIRECT_HEADERS: IHeader["headers"] = [ - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, - { - key: `x-content-type-options`, - value: `nosniff`, - }, - { - key: `referrer-policy`, - value: `same-origin`, - }, - { - key: `x-frame-options`, - value: `DENY`, - }, -] - -const ASSET_HEADERS: IHeader["headers"] = [ - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, - { - key: `x-content-type-options`, - value: `nosniff`, - }, - { - key: `referrer-policy`, - value: `same-origin`, - }, - { - key: `x-frame-options`, - value: `DENY`, - }, -] - -const WEBPACK_ASSET_HEADERS: IHeader["headers"] = [ - { - key: `cache-control`, - value: `public, max-age=31536000, immutable`, - }, - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, - { - key: `x-content-type-options`, - value: `nosniff`, - }, - { - key: `referrer-policy`, - value: `same-origin`, - }, - { - key: `x-frame-options`, - value: `DENY`, - }, -] - function maybeDropNamedPartOfWildcard( path: string | undefined ): string | undefined { @@ -210,6 +129,7 @@ function getRoutesManifest(): RoutesManifest { // TODO: have routes list sorted by specifity so more specific ones are before less specific ones (/static should be before /:param and that should be before /*), // so routing can just handle first match const routes = [] as RoutesManifest + // const match = matcher() const fileAssets = new Set( globSync(`**/**`, { @@ -223,7 +143,10 @@ function getRoutesManifest(): RoutesManifest { if (!route.path.startsWith(`/`)) { route.path = `/${route.path}` } - // TODO: calculate specifity of route's path and insert route in correct place + + // if (!route.type === 'lambda') route.headers = match(route.path, route.headers) + + // match() routes.push(route) } From 53be6ec54ea245f6a347cd97f579eadcd860ab6e Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 2 Jun 2023 10:59:55 +0200 Subject: [PATCH 038/161] export rankRoute --- packages/gatsby/src/bootstrap/requires-writer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/src/bootstrap/requires-writer.ts b/packages/gatsby/src/bootstrap/requires-writer.ts index 53f3dff141508..bdad5b50a4bd1 100644 --- a/packages/gatsby/src/bootstrap/requires-writer.ts +++ b/packages/gatsby/src/bootstrap/requires-writer.ts @@ -13,6 +13,9 @@ import { import { getPageMode } from "../utils/page-mode" import { devSSRWillInvalidate } from "../commands/build-html" +const hasContentFilePath = (componentPath: string): boolean => + componentPath.includes(`?__contentFilePath=`) + interface IGatsbyPageComponent { componentPath: string componentChunkName: string @@ -37,8 +40,6 @@ const ROOT_POINTS = 1 const isRootSegment = (segment: string): boolean => segment === `` const isDynamic = (segment: string): boolean => paramRe.test(segment) const isSplat = (segment: string): boolean => segment === `*` -const hasContentFilePath = (componentPath: string): boolean => - componentPath.includes(`?__contentFilePath=`) const segmentize = (uri: string): Array => uri @@ -46,7 +47,7 @@ const segmentize = (uri: string): Array => .replace(/(^\/+|\/+$)/g, ``) .split(`/`) -const rankRoute = (path: string): number => +export const rankRoute = (path: string): number => segmentize(path).reduce((score, segment) => { score += SEGMENT_POINTS if (isRootSegment(segment)) score += ROOT_POINTS From 0fc101fd05fd6882a0728a96ec967ed4795e784b Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 07:49:26 +0200 Subject: [PATCH 039/161] delete unneeded util --- .../src/utils/adapter/__tests__/utils.ts | 75 ------------------- packages/gatsby/src/utils/adapter/utils.ts | 26 ------- 2 files changed, 101 deletions(-) delete mode 100644 packages/gatsby/src/utils/adapter/__tests__/utils.ts delete mode 100644 packages/gatsby/src/utils/adapter/utils.ts diff --git a/packages/gatsby/src/utils/adapter/__tests__/utils.ts b/packages/gatsby/src/utils/adapter/__tests__/utils.ts deleted file mode 100644 index a568fa8f335fd..0000000000000 --- a/packages/gatsby/src/utils/adapter/__tests__/utils.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { IHeader } from "../../../redux/types" -import { splitInDynamicAndStaticBuckets } from "../utils" - -const DEFAULTS: IHeader["headers"] = [ - { - key: `cache-control`, - value: `public, max-age=0, must-revalidate`, - }, - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, -] - -const dynamicHeader: IHeader = { - source: `*`, - headers: [ - { - key: `x-custom-header`, - value: `a`, - }, - { - key: `x-another-custom-header`, - value: `a`, - }, - ], -} - -const staticHeader: IHeader = { - source: `/some-path/`, - headers: [ - { - key: `x-custom-header`, - value: `b`, - }, - ], -} - -const HEADERS_MINIMAL: Array = [dynamicHeader, staticHeader] - -describe(`splitInStaticAndDynamicBuckets`, () => { - it(`works with minimal data`, () => { - const output = splitInDynamicAndStaticBuckets(HEADERS_MINIMAL) - - expect(output.dynamicHeaders).toEqual([dynamicHeader]) - expect(output.staticHeaders).toEqual([staticHeader]) - }) - it(`recognizes all dynamic identifiers`, () => { - const dynamic = [ - dynamicHeader, - { - source: `/blog/:slug`, - headers: [ - { - key: `x-custom-header`, - value: `c`, - }, - ], - }, - { - source: `/blog/:slug*`, - headers: [ - { - key: `x-custom-header`, - value: `d`, - }, - ], - }, - ] - const output = splitInDynamicAndStaticBuckets([...dynamic, staticHeader]) - - expect(output.dynamicHeaders).toEqual([...dynamic]) - expect(output.staticHeaders).toEqual([staticHeader]) - }) -}) diff --git a/packages/gatsby/src/utils/adapter/utils.ts b/packages/gatsby/src/utils/adapter/utils.ts deleted file mode 100644 index ac141d99cf66a..0000000000000 --- a/packages/gatsby/src/utils/adapter/utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { IHeader } from "../../redux/types" - -/** - * Takes in the Headers array and splits it into two buckets: - * - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) - * - staticHeaders: Headers with fully static paths (e.g. /static/) - */ -export const splitInDynamicAndStaticBuckets = ( - headers: Array -): { dynamicHeaders: Array; staticHeaders: Array } => { - const dynamicHeaders: Array = [] - const staticHeaders: Array = [] - - for (const header of headers) { - if (header.source.includes(`:`) || header.source.includes(`*`)) { - dynamicHeaders.push(header) - } else { - staticHeaders.push(header) - } - } - - return { - dynamicHeaders, - staticHeaders, - } -} From dd2dd60ad5281fd8e1677cb7aba202f7ab2181b6 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 07:49:43 +0200 Subject: [PATCH 040/161] createHeadersMatcher initial impl --- .../utils/adapter/__tests__/create-headers.ts | 79 +++++++++++++++++++ .../src/utils/adapter/create-headers.ts | 65 +++++++++++++++ packages/gatsby/src/utils/adapter/types.ts | 2 +- 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 packages/gatsby/src/utils/adapter/__tests__/create-headers.ts create mode 100644 packages/gatsby/src/utils/adapter/create-headers.ts diff --git a/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts b/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts new file mode 100644 index 0000000000000..c702f1f14d10f --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts @@ -0,0 +1,79 @@ +import { store } from "../../../redux" +import { createHeadersMatcher } from "../create-headers" +import type { IHeader } from "../../../redux/types" + +jest.mock(`../../../redux`, () => { + return { + emitter: { + on: jest.fn(), + }, + store: { + getState: jest.fn(), + }, + } +}) + +function mockHeaders(headers: Array): void { + ;(store.getState as jest.Mock).mockImplementation(() => { + return { + config: { + headers, + }, + } + }) +} + +describe(`createHeadersMatcher`, () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + // TODO: What if path has trailing slash and in another place not? + + it(`works`, () => { + mockHeaders([ + { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `a`, + }, + { + key: `x-another-custom-header`, + value: `a`, + }, + ], + }, + { + source: `/some-path/`, + headers: [ + { + key: `x-custom-header`, + value: `b`, + }, + ], + }, + ]) + const matcher = createHeadersMatcher() + + const defaults = [ + { + key: `cache-control`, + value: `public, max-age=0, must-revalidate`, + }, + { + key: `x-xss-protection`, + value: `1; mode=block`, + }, + ] + + const foo = matcher(`/some-path/`, defaults) + + expect(foo).toEqual([ + ...defaults, + { key: `x-custom-header`, value: `b` }, + { key: `x-another-custom-header`, value: `a` }, + ]) + }) +}) diff --git a/packages/gatsby/src/utils/adapter/create-headers.ts b/packages/gatsby/src/utils/adapter/create-headers.ts new file mode 100644 index 0000000000000..83b5926fa9f71 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/create-headers.ts @@ -0,0 +1,65 @@ +import { match } from "@gatsbyjs/reach-router" +import type { IHeader } from "../../redux/types" +import { store } from "../../redux" + +export const createHeadersMatcher = (): (( + path: string, + defaultHeaders: IHeader["headers"] +) => IHeader["headers"]) => { + const { headers } = store.getState().config + + // Split the incoming user headers into two buckets: + // - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) + // - staticHeaders: Headers with fully static paths (e.g. /static/) + // Also add a score using the rankRoute function to each header + const dynamicHeaders: Array = [] + const staticHeaders: Array = [] + + for (const header of headers) { + if (header.source.includes(`:`) || header.source.includes(`*`)) { + dynamicHeaders.push(header) + } else { + staticHeaders.push(header) + } + } + + return ( + path: string, + defaultHeaders: IHeader["headers"] + ): IHeader["headers"] => { + // Create a map of headers for the given path + // The key will be the header key. Since a key may only appear once in a map, the last header with the same key will win + const uniqueHeaders: Map = new Map() + + // 1. Add default headers + for (const h of defaultHeaders) { + uniqueHeaders.set(h.key, h.value) + } + + // 2. Add dynamic headers that match the current path + for (const d of dynamicHeaders) { + if (match(d.source, path)) { + for (const h of d.headers) { + uniqueHeaders.set(h.key, h.value) + } + } + } + + const staticEntry = staticHeaders.find(s => s.source === path) + + // 3. Add static headers that match the current path + if (staticEntry) { + for (const h of staticEntry.headers) { + uniqueHeaders.set(h.key, h.value) + } + } + + // Convert the map back to an array of objects + return Array.from(uniqueHeaders.entries()).map(([key, value]) => { + return { + key, + value, + } + }) + } +} diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index e20101588f630..cb6430102c291 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -38,7 +38,7 @@ export interface ILambdaRoute extends IBaseRoute { interface IRedirectRoute extends IBaseRoute { type: `redirect` toPath: string - status: number // narrow down types to cocnrete status code that are acceptible here + status: number // TODO: narrow down types to concrete status code that are acceptable here ignoreCase: boolean // this is not supported by Netlify, but is supported by Gatsby ... headers: IHeader["headers"] [key: string]: unknown From 63913a795b365654af40b10f75e32103fb3f97cd Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 07:52:57 +0200 Subject: [PATCH 041/161] use createHeadersMatcher --- packages/gatsby/src/utils/adapter/manager.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 98f5456fec919..febdfe7503999 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -23,6 +23,7 @@ import { STATIC_PAGE_HEADERS, WEBPACK_ASSET_HEADERS, } from "./constants" +import { createHeadersMatcher } from "./create-headers" import type { IHeader } from "../../redux/types" function noOpAdapterManager(): IAdapterManager { @@ -129,7 +130,7 @@ function getRoutesManifest(): RoutesManifest { // TODO: have routes list sorted by specifity so more specific ones are before less specific ones (/static should be before /:param and that should be before /*), // so routing can just handle first match const routes = [] as RoutesManifest - // const match = matcher() + const createHeaders = createHeadersMatcher() const fileAssets = new Set( globSync(`**/**`, { @@ -144,9 +145,10 @@ function getRoutesManifest(): RoutesManifest { route.path = `/${route.path}` } - // if (!route.type === 'lambda') route.headers = match(route.path, route.headers) + if (route.type !== `lambda`) { + route.headers = createHeaders(route.path, route.headers) + } - // match() routes.push(route) } From 20e57963e1bd8b1a96226d457fd9eb4852f76b9a Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 08:13:48 +0200 Subject: [PATCH 042/161] fix types --- packages/gatsby/src/redux/types.ts | 3 ++- .../gatsby/src/utils/adapter/create-headers.ts | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index c57b6b2a1bad5..f7065914eb66c 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -104,6 +104,7 @@ export interface IHeader { }> } +// TODO: The keys of IGatsbyConfig are all optional so that in reducers like reducers/config.ts the default state for the config can be an empty object. This isn't ideal because some of those options are actually always defined because Joi validation sets defaults. Somehow fix this :D export interface IGatsbyConfig { plugins?: Array<{ // This is the name of the plugin like `gatsby-plugin-manifest` @@ -132,7 +133,7 @@ export interface IGatsbyConfig { jsxImportSource?: string trailingSlash?: TrailingSlash graphqlTypegen?: IGraphQLTypegenOptions - headers: Array + headers?: Array } export interface IGatsbyNode { diff --git a/packages/gatsby/src/utils/adapter/create-headers.ts b/packages/gatsby/src/utils/adapter/create-headers.ts index 83b5926fa9f71..810ccd8e1d4b7 100644 --- a/packages/gatsby/src/utils/adapter/create-headers.ts +++ b/packages/gatsby/src/utils/adapter/create-headers.ts @@ -2,10 +2,12 @@ import { match } from "@gatsbyjs/reach-router" import type { IHeader } from "../../redux/types" import { store } from "../../redux" +type Headers = IHeader["headers"] + export const createHeadersMatcher = (): (( path: string, - defaultHeaders: IHeader["headers"] -) => IHeader["headers"]) => { + defaultHeaders: Headers +) => Headers) => { const { headers } = store.getState().config // Split the incoming user headers into two buckets: @@ -15,6 +17,11 @@ export const createHeadersMatcher = (): (( const dynamicHeaders: Array = [] const staticHeaders: Array = [] + // If no custom headers are defined by the user in the gatsby-config, we can return only the default headers + if (!headers) { + return (_path: string, defaultHeaders: Headers) => defaultHeaders + } + for (const header of headers) { if (header.source.includes(`:`) || header.source.includes(`*`)) { dynamicHeaders.push(header) @@ -23,10 +30,7 @@ export const createHeadersMatcher = (): (( } } - return ( - path: string, - defaultHeaders: IHeader["headers"] - ): IHeader["headers"] => { + return (path: string, defaultHeaders: Headers): Headers => { // Create a map of headers for the given path // The key will be the header key. Since a key may only appear once in a map, the last header with the same key will win const uniqueHeaders: Map = new Map() From e805df72d2a37832c510bbed95bff0b5334e8414 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 10:45:35 +0200 Subject: [PATCH 043/161] add http status code type --- packages/gatsby/src/redux/types.ts | 379 +++++++++++++++++++ packages/gatsby/src/utils/adapter/manager.ts | 7 +- packages/gatsby/src/utils/adapter/types.ts | 4 +- 3 files changed, 387 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index f7065914eb66c..27410d88fa1d3 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -24,6 +24,7 @@ export interface IRedirect { isPermanent?: boolean redirectInBrowser?: boolean ignoreCase: boolean + statusCode?: HttpStatusCode // Users can add anything to this createRedirect API [key: string]: any } @@ -1215,3 +1216,381 @@ export interface IClearJobV2Context { requestId: string } } + +export const HTTP_STATUS_CODE = { + /** + * The server has received the request headers and the client should proceed to send the request body + * (in the case of a request for which a body needs to be sent; for example, a POST request). + * Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient. + * To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request + * and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates the request should not be continued. + */ + CONTINUE_100: 100, + + /** + * The requester has asked the server to switch protocols and the server has agreed to do so. + */ + SWITCHING_PROTOCOLS_101: 101, + + /** + * A WebDAV request may contain many sub-requests involving file operations, requiring a long time to complete the request. + * This code indicates that the server has received and is processing the request, but no response is available yet. + * This prevents the client from timing out and assuming the request was lost. + */ + PROCESSING_102: 102, + + /** + * Standard response for successful HTTP requests. + * The actual response will depend on the request method used. + * In a GET request, the response will contain an entity corresponding to the requested resource. + * In a POST request, the response will contain an entity describing or containing the result of the action. + */ + OK_200: 200, + + /** + * The request has been fulfilled, resulting in the creation of a new resource. + */ + CREATED_201: 201, + + /** + * The request has been accepted for processing, but the processing has not been completed. + * The request might or might not be eventually acted upon, and may be disallowed when processing occurs. + */ + ACCEPTED_202: 202, + + /** + * SINCE HTTP/1.1 + * The server is a transforming proxy that received a 200 OK from its origin, + * but is returning a modified version of the origin's response. + */ + NON_AUTHORITATIVE_INFORMATION_203: 203, + + /** + * The server successfully processed the request and is not returning any content. + */ + NO_CONTENT_204: 204, + + /** + * The server successfully processed the request, but is not returning any content. + * Unlike a 204 response, this response requires that the requester reset the document view. + */ + RESET_CONTENT_205: 205, + + /** + * The server is delivering only part of the resource (byte serving) due to a range header sent by the client. + * The range header is used by HTTP clients to enable resuming of interrupted downloads, + * or split a download into multiple simultaneous streams. + */ + PARTIAL_CONTENT_206: 206, + + /** + * The message body that follows is an XML message and can contain a number of separate response codes, + * depending on how many sub-requests were made. + */ + MULTI_STATUS_207: 207, + + /** + * The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response, + * and are not being included again. + */ + ALREADY_REPORTED_208: 208, + + /** + * The server has fulfilled a request for the resource, + * and the response is a representation of the result of one or more instance-manipulations applied to the current instance. + */ + IM_USED_226: 226, + + /** + * Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation). + * For example, this code could be used to present multiple video format options, + * to list files with different filename extensions, or to suggest word-sense disambiguation. + */ + MULTIPLE_CHOICES_300: 300, + + /** + * This and all future requests should be directed to the given URI. + */ + MOVED_PERMANENTLY_301: 301, + + /** + * This is an example of industry practice contradicting the standard. + * The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect + * (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 + * with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 + * to distinguish between the two behaviours. However, some Web applications and frameworks + * use the 302 status code as if it were the 303. + */ + FOUND_302: 302, + + /** + * SINCE HTTP/1.1 + * The response to the request can be found under another URI using a GET method. + * When received in response to a POST (or PUT/DELETE), the client should presume that + * the server has received the data and should issue a redirect with a separate GET message. + */ + SEE_OTHER_303: 303, + + /** + * Indicates that the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match. + * In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy. + */ + NOT_MODIFIED_304: 304, + + /** + * SINCE HTTP/1.1 + * The requested resource is available only through a proxy, the address for which is provided in the response. + * Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status code, primarily for security reasons. + */ + USE_PROXY_305: 305, + + /** + * No longer used. Originally meant "Subsequent requests should use the specified proxy." + */ + SWITCH_PROXY_306: 306, + + /** + * SINCE HTTP/1.1 + * In this case, the request should be repeated with another URI; however, future requests should still use the original URI. + * In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. + * For example, a POST request should be repeated using another POST request. + */ + TEMPORARY_REDIRECT_307: 307, + + /** + * The request and all future requests should be repeated using another URI. + * 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change. + * So, for example, submitting a form to a permanently redirected resource may continue smoothly. + */ + PERMANENT_REDIRECT_308: 308, + + /** + * The server cannot or will not process the request due to an apparent client error + * (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing). + */ + BAD_REQUEST_400: 400, + + /** + * Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet + * been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the + * requested resource. See Basic access authentication and Digest access authentication. 401 semantically means + * "unauthenticated",i.e. the user does not have the necessary credentials. + */ + UNAUTHORIZED_401: 401, + + /** + * Reserved for future use. The original intention was that this code might be used as part of some form of digital + * cash or micro payment scheme, but that has not happened, and this code is not usually used. + * Google Developers API uses this status if a particular developer has exceeded the daily limit on requests. + */ + PAYMENT_REQUIRED_402: 402, + + /** + * The request was valid, but the server is refusing action. + * The user might not have the necessary permissions for a resource. + */ + FORBIDDEN_403: 403, + + /** + * The requested resource could not be found but may be available in the future. + * Subsequent requests by the client are permissible. + */ + NOT_FOUND_404: 404, + + /** + * A request method is not supported for the requested resource; + * for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource. + */ + METHOD_NOT_ALLOWED_405: 405, + + /** + * The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request. + */ + NOT_ACCEPTABLE_406: 406, + + /** + * The client must first authenticate itself with the proxy. + */ + PROXY_AUTHENTICATION_REQUIRED_407: 407, + + /** + * The server timed out waiting for the request. + * According to HTTP specifications: + * "The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time." + */ + REQUEST_TIMEOUT_408: 408, + + /** + * Indicates that the request could not be processed because of conflict in the request, + * such as an edit conflict between multiple simultaneous updates. + */ + CONFLICT_409: 409, + + /** + * Indicates that the resource requested is no longer available and will not be available again. + * This should be used when a resource has been intentionally removed and the resource should be purged. + * Upon receiving a 410 status code, the client should not request the resource in the future. + * Clients such as search engines should remove the resource from their indices. + * Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead. + */ + GONE_410: 410, + + /** + * The request did not specify the length of its content, which is required by the requested resource. + */ + LENGTH_REQUIRED_411: 411, + + /** + * The server does not meet one of the preconditions that the requester put on the request. + */ + PRECONDITION_FAILED_412: 412, + + /** + * The request is larger than the server is willing or able to process. Previously called "Request Entity Too Large". + */ + PAYLOAD_TOO_LARGE_413: 413, + + /** + * The URI provided was too long for the server to process. Often the result of too much data being encoded as a query-string of a GET request, + * in which case it should be converted to a POST request. + * Called "Request-URI Too Long" previously. + */ + URI_TOO_LONG_414: 414, + + /** + * The request entity has a media type which the server or resource does not support. + * For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format. + */ + UNSUPPORTED_MEDIA_TYPE_415: 415, + + /** + * The client has asked for a portion of the file (byte serving), but the server cannot supply that portion. + * For example, if the client asked for a part of the file that lies beyond the end of the file. + * Called "Requested Range Not Satisfiable" previously. + */ + RANGE_NOT_SATISFIABLE_416: 416, + + /** + * The server cannot meet the requirements of the Expect request-header field. + */ + EXPECTATION_FAILED_417: 417, + + /** + * This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol, + * and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by + * teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com. + */ + I_AM_A_TEAPOT_418: 418, + + /** + * The request was directed at a server that is not able to produce a response (for example because a connection reuse). + */ + MISDIRECTED_REQUEST_421: 421, + + /** + * The request was well-formed but was unable to be followed due to semantic errors. + */ + UNPROCESSABLE_ENTITY_422: 422, + + /** + * The resource that is being accessed is locked. + */ + LOCKED_423: 423, + + /** + * The request failed due to failure of a previous request (e.g., a PROPPATCH). + */ + FAILED_DEPENDENCY_424: 424, + + /** + * The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field. + */ + UPGRADE_REQUIRED_426: 426, + + /** + * The origin server requires the request to be conditional. + * Intended to prevent "the 'lost update' problem, where a client + * GETs a resource's state, modifies it, and PUTs it back to the server, + * when meanwhile a third party has modified the state on the server, leading to a conflict." + */ + PRECONDITION_REQUIRED_428: 428, + + /** + * The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes. + */ + TOO_MANY_REQUESTS_429: 429, + + /** + * The server is unwilling to process the request because either an individual header field, + * or all the header fields collectively, are too large. + */ + REQUEST_HEADER_FIELDS_TOO_LARGE_431: 431, + + /** + * A server operator has received a legal demand to deny access to a resource or to a set of resources + * that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451. + */ + UNAVAILABLE_FOR_LEGAL_REASONS_451: 451, + + /** + * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. + */ + INTERNAL_SERVER_ERROR_500: 500, + + /** + * The server either does not recognize the request method, or it lacks the ability to fulfill the request. + * Usually this implies future availability (e.g., a new feature of a web-service API). + */ + NOT_IMPLEMENTED_501: 501, + + /** + * The server was acting as a gateway or proxy and received an invalid response from the upstream server. + */ + BAD_GATEWAY_502: 502, + + /** + * The server is currently unavailable (because it is overloaded or down for maintenance). + * Generally, this is a temporary state. + */ + SERVICE_UNAVAILABLE_503: 503, + + /** + * The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. + */ + GATEWAY_TIMEOUT_504: 504, + + /** + * The server does not support the HTTP protocol version used in the request + */ + HTTP_VERSION_NOT_SUPPORTED_505: 505, + + /** + * Transparent content negotiation for the request results in a circular reference. + */ + VARIANT_ALSO_NEGOTIATES_506: 506, + + /** + * The server is unable to store the representation needed to complete the request. + */ + INSUFFICIENT_STORAGE_507: 507, + + /** + * The server detected an infinite loop while processing the request. + */ + LOOP_DETECTED_508: 508, + + /** + * Further extensions to the request are required for the server to fulfill it. + */ + NOT_EXTENDED_510: 510, + + /** + * The client needs to authenticate to gain network access. + * Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used + * to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot). + */ + NETWORK_AUTHENTICATION_REQUIRED_511: 511, +} as const + +export type HttpStatusCode = + (typeof HTTP_STATUS_CODE)[keyof typeof HTTP_STATUS_CODE] diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index febdfe7503999..cf08b027de409 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -24,6 +24,7 @@ import { WEBPACK_ASSET_HEADERS, } from "./constants" import { createHeadersMatcher } from "./create-headers" +import { HTTP_STATUS_CODE } from "../../redux/types" import type { IHeader } from "../../redux/types" function noOpAdapterManager(): IAdapterManager { @@ -264,7 +265,11 @@ function getRoutesManifest(): RoutesManifest { path: redirect.fromPath, type: `redirect`, toPath: redirect.toPath, - status: redirect.statusCode ?? (redirect.isPermanent ? 301 : 302), + status: + redirect.statusCode ?? + (redirect.isPermanent + ? HTTP_STATUS_CODE.MOVED_PERMANENTLY_301 + : HTTP_STATUS_CODE.FOUND_302), ignoreCase: redirect.ignoreCase, headers: REDIRECT_HEADERS, }) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index cb6430102c291..1b9945954223e 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -1,5 +1,5 @@ import type reporter from "gatsby-cli/lib/reporter" -import type { IHeader } from "../../redux/types" +import type { IHeader, HttpStatusCode } from "../../redux/types" interface IBaseRoute { /** @@ -38,7 +38,7 @@ export interface ILambdaRoute extends IBaseRoute { interface IRedirectRoute extends IBaseRoute { type: `redirect` toPath: string - status: number // TODO: narrow down types to concrete status code that are acceptable here + status: HttpStatusCode ignoreCase: boolean // this is not supported by Netlify, but is supported by Gatsby ... headers: IHeader["headers"] [key: string]: unknown From 3eba52cd7ae383066d7ab75bcc08801888b292ad Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 10:56:23 +0200 Subject: [PATCH 044/161] improve createHeadersMatcher and add tests --- .../utils/adapter/__tests__/create-headers.ts | 292 +++++++++++++++++- .../src/utils/adapter/create-headers.ts | 33 +- 2 files changed, 304 insertions(+), 21 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts b/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts index c702f1f14d10f..a6f13038e23bd 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts @@ -28,20 +28,90 @@ describe(`createHeadersMatcher`, () => { jest.clearAllMocks() }) - // TODO: What if path has trailing slash and in another place not? + it(`returns default headers if no custom headers are defined`, () => { + ;(store.getState as jest.Mock).mockImplementation(() => { + return { + config: {}, + } + }) - it(`works`, () => { + const matcher = createHeadersMatcher() + + const defaults = [ + { + key: `x-default-header`, + value: `win`, + }, + ] + + const result = matcher(`/some-path/`, defaults) + + expect(result).toEqual(defaults) + }) + + it(`returns default headers if an empty array as headers is defined`, () => { + ;(store.getState as jest.Mock).mockImplementation(() => { + return { + config: { + headers: [], + }, + } + }) + + const matcher = createHeadersMatcher() + + const defaults = [ + { + key: `x-default-header`, + value: `win`, + }, + ] + + const result = matcher(`/some-path/`, defaults) + + expect(result).toEqual(defaults) + }) + + it(`gracefully handles trailing slash inconsistencies`, () => { mockHeaders([ { - source: `*`, + source: `/some-path`, headers: [ { key: `x-custom-header`, - value: `a`, + value: `win`, }, + ], + }, + { + source: `/another-path/`, + headers: [ + { + key: `x-custom-header`, + value: `win`, + }, + ], + }, + ]) + const matcher = createHeadersMatcher() + + const defaults = [] + + const resultOne = matcher(`/some-path/`, defaults) + const resultTwo = matcher(`/another-path`, defaults) + + expect(resultOne).toEqual([{ key: `x-custom-header`, value: `win` }]) + expect(resultTwo).toEqual([{ key: `x-custom-header`, value: `win` }]) + }) + + it(`combines with non-overlapping keys`, () => { + mockHeaders([ + { + source: `*`, + headers: [ { key: `x-another-custom-header`, - value: `a`, + value: `win`, }, ], }, @@ -50,7 +120,7 @@ describe(`createHeadersMatcher`, () => { headers: [ { key: `x-custom-header`, - value: `b`, + value: `win`, }, ], }, @@ -59,21 +129,213 @@ describe(`createHeadersMatcher`, () => { const defaults = [ { - key: `cache-control`, - value: `public, max-age=0, must-revalidate`, + key: `x-default-header`, + value: `win`, }, + ] + + const result = matcher(`/some-path/`, defaults) + + expect(result).toEqual([ + ...defaults, + { key: `x-another-custom-header`, value: `win` }, + { key: `x-custom-header`, value: `win` }, + ]) + }) + + it(`combines with overlapping keys`, () => { + mockHeaders([ + { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `lose-2`, + }, + ], + }, + { + source: `/some-path/`, + headers: [ + { + key: `x-custom-header`, + value: `win`, + }, + ], + }, + ]) + const matcher = createHeadersMatcher() + + const defaults = [ { - key: `x-xss-protection`, - value: `1; mode=block`, + key: `x-custom-header`, + value: `lose-1`, }, ] - const foo = matcher(`/some-path/`, defaults) + const result = matcher(`/some-path/`, defaults) - expect(foo).toEqual([ - ...defaults, - { key: `x-custom-header`, value: `b` }, - { key: `x-another-custom-header`, value: `a` }, + expect(result).toEqual([{ key: `x-custom-header`, value: `win` }]) + }) + + it(`combines with overlapping & non-overlapping keys`, () => { + mockHeaders([ + { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `lose-2`, + }, + { + key: `x-dynamic-header`, + value: `win`, + }, + ], + }, + { + source: `/some-path/`, + headers: [ + { + key: `x-custom-header`, + value: `win`, + }, + { + key: `x-static-header`, + value: `win`, + }, + ], + }, + ]) + const matcher = createHeadersMatcher() + + const defaults = [ + { + key: `x-custom-header`, + value: `lose-1`, + }, + { + key: `x-default-header`, + value: `win`, + }, + ] + + const result = matcher(`/some-path/`, defaults) + + expect(result).toEqual([ + { + key: `x-custom-header`, + value: `win`, + }, + { + key: `x-default-header`, + value: `win`, + }, + { + key: `x-dynamic-header`, + value: `win`, + }, + { + key: `x-static-header`, + value: `win`, + }, + ]) + }) + + it(`static wins over dynamic`, () => { + mockHeaders([ + { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `lose-1`, + }, + ], + }, + { + source: `/some-path/foo`, + headers: [ + { + key: `x-custom-header`, + value: `win`, + }, + ], + }, + { + source: `/some-path/*`, + headers: [ + { + key: `x-custom-header`, + value: `lose-2`, + }, + ], + }, + { + source: `/some-path/:slug`, + headers: [ + { + key: `x-custom-header`, + value: `lose-3`, + }, + ], + }, + ]) + const matcher = createHeadersMatcher() + + const defaults = [] + + const result = matcher(`/some-path/foo`, defaults) + + expect(result).toEqual([ + { + key: `x-custom-header`, + value: `win`, + }, + ]) + }) + + it(`dynamic entries have correct specificity`, () => { + mockHeaders([ + { + source: `*`, + headers: [ + { + key: `x-custom-header`, + value: `lose-1`, + }, + ], + }, + { + source: `/some-path/*`, + headers: [ + { + key: `x-custom-header`, + value: `lose-2`, + }, + ], + }, + { + source: `/some-path/:slug`, + headers: [ + { + key: `x-custom-header`, + value: `win`, + }, + ], + }, + ]) + const matcher = createHeadersMatcher() + + const defaults = [] + + const result = matcher(`/some-path/foo`, defaults) + + expect(result).toEqual([ + { + key: `x-custom-header`, + value: `win`, + }, ]) }) }) diff --git a/packages/gatsby/src/utils/adapter/create-headers.ts b/packages/gatsby/src/utils/adapter/create-headers.ts index 810ccd8e1d4b7..ee984d01ddadc 100644 --- a/packages/gatsby/src/utils/adapter/create-headers.ts +++ b/packages/gatsby/src/utils/adapter/create-headers.ts @@ -1,8 +1,16 @@ import { match } from "@gatsbyjs/reach-router" import type { IHeader } from "../../redux/types" import { store } from "../../redux" +import { rankRoute } from "../../bootstrap/requires-writer" type Headers = IHeader["headers"] +interface IHeaderWithScore extends IHeader { + score: number +} + +// We don't care if the path has a trailing slash or not, but to be able to compare stuff we need to normalize it +const normalizePath = (input: string): string => + input.endsWith(`/`) ? input : `${input}/` export const createHeadersMatcher = (): (( path: string, @@ -14,22 +22,35 @@ export const createHeadersMatcher = (): (( // - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) // - staticHeaders: Headers with fully static paths (e.g. /static/) // Also add a score using the rankRoute function to each header - const dynamicHeaders: Array = [] - const staticHeaders: Array = [] + let dynamicHeaders: Array = [] + const staticHeaders: Map = new Map() // If no custom headers are defined by the user in the gatsby-config, we can return only the default headers - if (!headers) { + if (!headers || headers.length === 0) { return (_path: string, defaultHeaders: Headers) => defaultHeaders } for (const header of headers) { if (header.source.includes(`:`) || header.source.includes(`*`)) { - dynamicHeaders.push(header) + // rankRoute is the internal function that also "match" uses + const score = rankRoute(header.source) + + dynamicHeaders.push({ ...header, score }) } else { - staticHeaders.push(header) + staticHeaders.set(normalizePath(header.source), header) } } + // Sort the dynamic headers by score, moving the ones with the highest specificity to the end of the array + // If the score is the same, do a lexigraphic comparison of the source + dynamicHeaders = dynamicHeaders.sort((a, b) => { + const order = a.score - b.score + if (order !== 0) { + return order + } + return a.source.localeCompare(b.source) + }) + return (path: string, defaultHeaders: Headers): Headers => { // Create a map of headers for the given path // The key will be the header key. Since a key may only appear once in a map, the last header with the same key will win @@ -49,7 +70,7 @@ export const createHeadersMatcher = (): (( } } - const staticEntry = staticHeaders.find(s => s.source === path) + const staticEntry = staticHeaders.get(normalizePath(path)) // 3. Add static headers that match the current path if (staticEntry) { From 4ca653b365b02de2388834e90f286b5240f3a89e Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 13:21:53 +0200 Subject: [PATCH 045/161] move adapterManager init to initialize func --- packages/gatsby/src/bootstrap/index.ts | 3 +++ packages/gatsby/src/commands/build.ts | 13 +++++-------- packages/gatsby/src/services/initialize.ts | 9 ++++++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/gatsby/src/bootstrap/index.ts b/packages/gatsby/src/bootstrap/index.ts index 31efa1cea8132..218d45da89ae2 100644 --- a/packages/gatsby/src/bootstrap/index.ts +++ b/packages/gatsby/src/bootstrap/index.ts @@ -18,6 +18,7 @@ import type { GatsbyWorkerPool } from "../utils/worker/pool" import { handleStalePageData } from "../utils/page-data" import { savePartialStateToDisk } from "../redux" import { IProgram } from "../commands/types" +import type { IAdapterManager } from "../utils/adapter/types" const tracer = globalTracer() @@ -26,6 +27,7 @@ export async function bootstrap( ): Promise<{ gatsbyNodeGraphQLFunction: Runner workerPool: GatsbyWorkerPool + adapterManager: IAdapterManager }> { const spanArgs = initialContext.parentSpan ? { childOf: initialContext.parentSpan } @@ -86,5 +88,6 @@ export async function bootstrap( return { gatsbyNodeGraphQLFunction: context.gatsbyNodeGraphQLFunction, workerPool, + adapterManager: context.adapterManager, } } diff --git a/packages/gatsby/src/commands/build.ts b/packages/gatsby/src/commands/build.ts index f12cc343aa098..048eca9e284d0 100644 --- a/packages/gatsby/src/commands/build.ts +++ b/packages/gatsby/src/commands/build.ts @@ -71,7 +71,6 @@ import { constructConfigObject } from "../utils/gatsby-cloud-config" import { waitUntilWorkerJobsAreComplete } from "../utils/jobs/worker-messaging" import { getSSRChunkHashes } from "../utils/webpack/get-ssr-chunk-hashes" import { writeTypeScriptTypes } from "../utils/graphql-typegen/ts-codegen" -import { initAdapterManager } from "../utils/adapter/manager" module.exports = async function build( program: IBuildArgs, @@ -115,9 +114,6 @@ module.exports = async function build( ) } - const adapterManager = await initAdapterManager() - await adapterManager.restoreCache() - const buildActivity = report.phantomActivity(`build`) buildActivity.start() @@ -138,10 +134,11 @@ module.exports = async function build( }) } - const { gatsbyNodeGraphQLFunction, workerPool } = await bootstrap({ - program, - parentSpan: buildSpan, - }) + const { gatsbyNodeGraphQLFunction, workerPool, adapterManager } = + await bootstrap({ + program, + parentSpan: buildSpan, + }) await apiRunnerNode(`onPreBuild`, { graphql: gatsbyNodeGraphQLFunction, diff --git a/packages/gatsby/src/services/initialize.ts b/packages/gatsby/src/services/initialize.ts index 8204040e026bf..9e3bf398705f2 100644 --- a/packages/gatsby/src/services/initialize.ts +++ b/packages/gatsby/src/services/initialize.ts @@ -26,6 +26,8 @@ import { enableNodeMutationsDetection } from "../utils/detect-node-mutations" import { compileGatsbyFiles } from "../utils/parcel/compile-gatsby-files" import { resolveModule } from "../utils/module-resolver" import { writeGraphQLConfig } from "../utils/graphql-typegen/file-writes" +import { initAdapterManager } from "../utils/adapter/manager" +import type { IAdapterManager } from "../utils/adapter/types" interface IPluginResolution { resolve: string @@ -81,6 +83,7 @@ export async function initialize({ store: Store workerPool: WorkerPool.GatsbyWorkerPool webhookBody?: WebhookBody + adapterManager: IAdapterManager }> { if (process.env.GATSBY_DISABLE_CACHE_PERSISTENCE) { reporter.info( @@ -184,6 +187,9 @@ export async function initialize({ }) activity.end() + const adapterManager = await initAdapterManager() + await adapterManager.restoreCache() + // Load plugins activity = reporter.activityTimer(`load plugins`, { parentSpan, @@ -432,7 +438,7 @@ export async function initialize({ `!.cache/compiled`, // Add webpack `!.cache/webpack`, - `!.cache/adapters` + `!.cache/adapters`, ] if (process.env.GATSBY_EXPERIMENTAL_PRESERVE_FILE_DOWNLOAD_CACHE) { @@ -672,5 +678,6 @@ export async function initialize({ store, workerPool, webhookBody: initialWebhookBody, + adapterManager, } } From 610d3c0d157fb92b70a64a2bef4d518ce31a446d Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 13:22:50 +0200 Subject: [PATCH 046/161] adjust func args to move reporter and allow adapter options --- packages/gatsby-adapter-netlify/src/index.ts | 17 +++++++----- packages/gatsby/src/utils/adapter/manager.ts | 10 +++++--- packages/gatsby/src/utils/adapter/types.ts | 27 ++++++++++++++------ 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index b92a2b8a1af47..4033d51b86a0f 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -3,20 +3,23 @@ import type { AdapterInit } from "gatsby" // just for debugging import { inspect } from "util" -const createNetlifyAdapter: AdapterInit = ({ reporter }) => { - reporter.info(`[gatsby-adapter-netlify] createAdapter()`) +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface INetlifyAdapterOptions {} +const createNetlifyAdapter: AdapterInit = () => { return { name: `gatsby-adapter-netlify`, cache: { - restore(directories): void { - console.log(`[gatsby-adapter-netlify] cache.restore()`, directories) + restore({ directories, reporter }): void { + reporter.info(`[gatsby-adapter-netlify] cache.restore() ${directories}`) }, - store(directories): void { - console.log(`[gatsby-adapter-netlify] cache.store()`, directories) + store({ directories, reporter }): void { + reporter.info(`[gatsby-adapter-netlify] cache.store() ${directories}`) }, }, - adapt({ routesManifest, functionsManifest }): void { + adapt({ routesManifest, functionsManifest, reporter }): void { + reporter.info(`[gatsby-adapter-netlify] adapt()`) + console.log( `[gatsby-adapter-netlify] adapt()`, inspect( diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index cf08b027de409..f26b14e1b4e06 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -43,7 +43,7 @@ export async function initAdapterManager(): Promise { return noOpAdapterManager() } - const adapter = adapterInit({ reporter }) + const adapter = adapterInit() reporter.info(`Using ${adapter.name} adapter`) @@ -57,7 +57,10 @@ export async function initAdapterManager(): Promise { return } - const result = await adapter.cache.restore(directoriesToCache) + const result = await adapter.cache.restore({ + directories: directoriesToCache, + reporter, + }) if (result === false) { // if adapter reports `false`, we can skip trying to re-hydrate state return @@ -80,7 +83,7 @@ export async function initAdapterManager(): Promise { return } - await adapter.cache.store(directoriesToCache) + await adapter.cache.store({ directories: directoriesToCache, reporter }) }, adapt: async (): Promise => { reporter.info(`[dev-adapter-manager] adapt()`) @@ -105,6 +108,7 @@ export async function initAdapterManager(): Promise { return _functionsManifest }, + reporter, } await adapter.adapt(adaptContext) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 1b9945954223e..a1de3da12f1ee 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -63,10 +63,23 @@ export type FunctionsManifest = Array<{ requiredFiles: Array }> -export interface IAdaptContext { +interface IDefaultContext { + /** + * Reporter instance that can be used to log messages to terminal. + * Read its [API documentation](https://www.gatsbyjs.com/docs/reference/config-files/node-api-helpers/#reporter) + */ + reporter: typeof reporter +} + +export interface IAdaptContext extends IDefaultContext { routesManifest: RoutesManifest functionsManifest: FunctionsManifest } + +export interface ICacheContext extends IDefaultContext { + directories: Array +} + export interface IAdapter { /** * Unique name of the adapter. Used to identify adapter in manifest. @@ -78,12 +91,12 @@ export interface IAdapter { * If `false` is returned gatsby will skip trying to rehydrate state from fs. */ restore: ( - directories: Array + context: ICacheContext ) => Promise | boolean | void /** * Hook to store .cache and public directories from previous builds. Executed as one of last steps in build process. */ - store: (directories: Array) => Promise | void + store: (context: ICacheContext) => Promise | void } /** * Hook to prepare platform specific deployment of the build. Executed as one of last steps in build process. @@ -99,11 +112,9 @@ export interface IAdapter { // current limitation in Netlify's implementation of DSG/SSR ( https://github.com/netlify/netlify-plugin-gatsby#caveats ) } -export interface IAdapterInitArgs { - reporter: typeof reporter -} - -export type AdapterInit = (initArgs: IAdapterInitArgs) => IAdapter +export type AdapterInit> = ( + adapterOptions?: T +) => IAdapter export interface IAdapterManager { restoreCache: () => Promise | void From 8bc6351295bd3a5d6c5c0dc1b78dc78a9ef1c9fc Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 5 Jun 2023 14:50:44 +0200 Subject: [PATCH 047/161] add "adapter" option to gatsby-config --- packages/gatsby/index.d.ts | 8 +++++- packages/gatsby/src/joi-schemas/joi.ts | 12 +++++++++ packages/gatsby/src/redux/types.ts | 5 +++- packages/gatsby/src/utils/adapter/init.ts | 11 +++----- packages/gatsby/src/utils/adapter/manager.ts | 27 +++++++++++++++----- packages/gatsby/src/utils/did-you-mean.ts | 1 + packages/gatsby/src/utils/start-server.ts | 2 +- 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 2a05c339b9543..c5d0f07bc78b4 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -16,6 +16,7 @@ import { import { GraphQLOutputType } from "graphql" import { PluginOptionsSchemaJoi, ObjectSchema } from "gatsby-plugin-utils" import { IncomingMessage, ServerResponse } from "http" +import { AdapterInit, IAdapter } from "gatsby/dist/utils/adapter/types" export type AvailableFeatures = | "image-cdn" @@ -33,7 +34,7 @@ export { export * from "gatsby-script" -export { AdapterInit } from "gatsby/dist/utils/adapter/types" +export { AdapterInit, IAdapter } export const useScrollRestoration: (key: string) => { ref: React.MutableRefObject @@ -373,8 +374,13 @@ export interface GatsbyConfig { developMiddleware?(app: any): void /** * You can set custom HTTP headers for incoming requests + * TODO */ headers?: Array
+ /** + * TODO + */ + adapter?: IAdapter } /** diff --git a/packages/gatsby/src/joi-schemas/joi.ts b/packages/gatsby/src/joi-schemas/joi.ts index 59a0259438d4e..377dbb7699b88 100644 --- a/packages/gatsby/src/joi-schemas/joi.ts +++ b/packages/gatsby/src/joi-schemas/joi.ts @@ -103,6 +103,18 @@ export const gatsbyConfigSchema: Joi.ObjectSchema = Joi.object() .unknown(false) ) .default([]), + adapter: Joi.object() + .keys({ + name: Joi.string().required(), + cache: Joi.object() + .keys({ + restore: Joi.func(), + store: Joi.func(), + }) + .unknown(false), + adapt: Joi.func().required(), + }) + .unknown(false), }) // throws when both assetPrefix and pathPrefix are defined .when( diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 27410d88fa1d3..824b2cb2f6d5a 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -1,4 +1,5 @@ import type { TrailingSlash } from "gatsby-page-utils" +import type { Express } from "express" import { IProgram, Stage } from "../commands/types" import { GraphQLFieldExtensionDefinition } from "../schema/extensions" import { @@ -14,6 +15,7 @@ import { InternalJob, JobResultInterface } from "../utils/jobs/manager" import { ITypeMetadata } from "../schema/infer/inference-metadata" import { Span } from "opentracing" import { ICollectedSlices } from "../utils/babel/find-slices" +import type { IAdapter } from "../utils/adapter/types" type SystemPath = string type Identifier = string @@ -124,7 +126,7 @@ export interface IGatsbyConfig { } // @deprecated polyfill?: boolean - developMiddleware?: any + developMiddleware?: (app: Express) => void proxy?: any partytownProxiedURLs?: Array pathPrefix?: string @@ -135,6 +137,7 @@ export interface IGatsbyConfig { trailingSlash?: TrailingSlash graphqlTypegen?: IGraphQLTypegenOptions headers?: Array + adapter?: IAdapter } export interface IGatsbyNode { diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index b3685c863d94f..9af14b50c0fda 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -29,9 +29,8 @@ const createAdaptersCacheDir = async (): Promise => { } export async function getAdapterInit(): Promise { + // 1. Find the correct adapter and its details (e.g. version) const latestAdapters = await getLatestAdapters() - - // 0. Find the correct adapter and its details (e.g. version) const adapterToUse = latestAdapters.find(candidate => candidate.test()) if (!adapterToUse) { @@ -41,9 +40,7 @@ export async function getAdapterInit(): Promise { return undefined } - // TODO: Add a way for someone to use an unpublished adapter (e.g. local developing) - - // 1. Check if the user has manually installed the adapter and try to resolve it from there + // 2. Check if the user has manually installed the adapter and try to resolve it from there try { const siteRequire = createRequireFromPath(`${process.cwd()}/:internal:`) const adapterPackageJson = siteRequire( @@ -105,7 +102,7 @@ export async function getAdapterInit(): Promise { // no-op } - // 2. Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there + // 3. Check if a previous run has installed the correct adapter into .cache/adapters already and try to resolve it from there try { const adaptersRequire = createRequireFromPath( `${getAdaptersCacheDir()}/:internal:` @@ -126,7 +123,7 @@ export async function getAdapterInit(): Promise { const installTimer = reporter.activityTimer( `Installing ${adapterToUse.name} adapter` ) - // 3. If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters + // 4. If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters try { installTimer.start() await createAdaptersCacheDir() diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index f26b14e1b4e06..107c027128688 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -11,6 +11,7 @@ import { Route, IAdapterManager, ILambdaRoute, + IAdapter, } from "./types" import { store, readState } from "../../redux" import { getPageMode } from "../page-mode" @@ -36,14 +37,28 @@ function noOpAdapterManager(): IAdapterManager { } export async function initAdapterManager(): Promise { - const adapterInit = await getAdapterInit() + let adapter: IAdapter - // If we don't have adapter, use no-op adapter manager - if (!adapterInit) { - return noOpAdapterManager() - } + const config = store.getState().config + const { adapter: adapterFromGatsbyConfig } = config + + // If the user specified an adapter inside their gatsby-config, use that instead of trying to figure out an adapter for the current environment + if (adapterFromGatsbyConfig) { + adapter = adapterFromGatsbyConfig + + reporter.verbose(`Using adapter ${adapter.name} from gatsby-config`) + } else { + const adapterInit = await getAdapterInit() + + // If we don't have adapter, use no-op adapter manager + if (!adapterInit) { + telemetry.trackFeatureIsUsed(`adapter:no-op`) - const adapter = adapterInit() + return noOpAdapterManager() + } + + adapter = adapterInit() + } reporter.info(`Using ${adapter.name} adapter`) diff --git a/packages/gatsby/src/utils/did-you-mean.ts b/packages/gatsby/src/utils/did-you-mean.ts index ef7629920f379..5273eae04fffa 100644 --- a/packages/gatsby/src/utils/did-you-mean.ts +++ b/packages/gatsby/src/utils/did-you-mean.ts @@ -15,6 +15,7 @@ export const KNOWN_CONFIG_KEYS = [ `trailingSlash`, `graphqlTypegen`, `headers`, + `adapter`, ] export function didYouMean( diff --git a/packages/gatsby/src/utils/start-server.ts b/packages/gatsby/src/utils/start-server.ts index f1cec341ed901..2e3d177ca00c7 100644 --- a/packages/gatsby/src/utils/start-server.ts +++ b/packages/gatsby/src/utils/start-server.ts @@ -594,7 +594,7 @@ export async function startServer( const { developMiddleware } = store.getState().config if (developMiddleware) { - developMiddleware(app, program) + developMiddleware(app) } const { proxy, trailingSlash } = store.getState().config From 3916d79f55a43de8bd406311b1e0017bffa0387c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 2 Jun 2023 12:28:31 +0200 Subject: [PATCH 048/161] put netlify adapter first in the list - it will only match when env var is set, so won't change default behavior of using testing one --- packages/gatsby/adapters.js | 16 ++++++++-------- packages/gatsby/src/utils/adapter/init.ts | 4 +++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index 4d2abd14034da..6d8dcc774a88c 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -8,21 +8,21 @@ * @type {import("./src/utils/adapter/types").IAdapterManifestEntry} */ const adaptersManifest = [ - { - name: `gatsby-adapter-testing`, - module: `@lekoarts/gatsby-adapter-testing`, - test: () => true, + { + name: `Netlify`, + module: `gatsby-adapter-netlify`, + test: () => !!process.env.NETLIFY, versions: [ { gatsbyVersion: `^5.0.0`, - moduleVersion: `^1.0.0` + moduleVersion: `*` } ] }, { - name: `Netlify`, - module: `gatsby-adapter-netlify`, - test: () => !!process.env.NETLIFY, + name: `gatsby-adapter-testing`, + module: `@lekoarts/gatsby-adapter-testing`, + test: () => true, versions: [ { gatsbyVersion: `^5.0.0`, diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index 9af14b50c0fda..2b201eb9c763c 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -71,7 +71,9 @@ export async function getAdapterInit(): Promise { ) const isAdapterCompatible = versionForCurrentGatsbyVersion && - satisfies(moduleVersion, versionForCurrentGatsbyVersion.moduleVersion) + satisfies(moduleVersion, versionForCurrentGatsbyVersion.moduleVersion, { + includePrerelease: true, + }) if (!versionForCurrentGatsbyVersion) { reporter.warn( From 86429b1000d1d91417cbd60c785caa5f0dff41e7 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 5 Jun 2023 17:03:29 +0200 Subject: [PATCH 049/161] kebabcase function name as function id --- packages/gatsby/src/internal-plugins/functions/gatsby-node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts index 66420c2c31bd1..debe90f7e4e5e 100644 --- a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts +++ b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts @@ -184,7 +184,7 @@ const createWebpackConfig = async ({ absoluteCompiledFilePath: compiledPath, matchPath: getMatchPath(finalName), // TODO: maybe figure out better functionId - functionId: compiledFunctionName, + functionId: _.kebabCase(compiledFunctionName), }) }) From b8bc1940c5029da1b28a05a7f6604fcd6ff78b90 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 5 Jun 2023 17:03:58 +0200 Subject: [PATCH 050/161] export FunctionDefinition type --- packages/gatsby/src/utils/adapter/types.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index a1de3da12f1ee..cbbde37d44561 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -48,7 +48,7 @@ export type Route = IStaticRoute | ILambdaRoute | IRedirectRoute export type RoutesManifest = Array -export type FunctionsManifest = Array<{ +export interface IFunctionDefinition { /** * Identifier of the function. Referenced in routes manifest in lambda routes. */ @@ -61,7 +61,9 @@ export type FunctionsManifest = Array<{ * List of all required files that this function needs to run */ requiredFiles: Array -}> +} + +export type FunctionsManifest = Array interface IDefaultContext { /** From 8fcca54946912f5fdc3df0dfc308b6d403f05471 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 5 Jun 2023 17:04:25 +0200 Subject: [PATCH 051/161] req.path -> req.url --- packages/gatsby/src/utils/page-ssr-module/lambda.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts index 964447272f6b7..6b28809583d2a 100644 --- a/packages/gatsby/src/utils/page-ssr-module/lambda.ts +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -27,7 +27,7 @@ function getPathInfo(req: GatsbyFunctionRequest): } | undefined { // @ts-ignore GatsbyFunctionRequest.path is not in types ... there is no property in types that can be used to get a path currently - const matches = req.path.matchAll(/^\/?page-data\/(.+)\/page-data.json$/gm) + const matches = req.url.matchAll(/^\/?page-data\/(.+)\/page-data.json$/gm) for (const [, requestedPagePath] of matches) { return { isPageData: true, @@ -39,7 +39,7 @@ function getPathInfo(req: GatsbyFunctionRequest): return { isPageData: false, // @ts-ignore GatsbyFunctionRequest.path is not in types ... there is no property in types that can be used to get a path currently - pagePath: req.path, + pagePath: req.url, } } @@ -70,7 +70,6 @@ async function engineHandler( ): Promise { try { const pathInfo = getPathInfo(req) - console.log(`hello`, pathInfo) if (!pathInfo) { res.status(404).send(`Not found`) return From 657659a1988ece7adb5fa4015f734264891deef3 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 5 Jun 2023 17:05:25 +0200 Subject: [PATCH 052/161] initial functions wrapping/bundling in gatsby-adapter-netlify --- packages/gatsby-adapter-netlify/package.json | 9 +- packages/gatsby-adapter-netlify/src/index.ts | 16 +- .../src/lambda-handler.ts | 233 ++++++++++++++++++ yarn.lock | 77 +++++- 4 files changed, 328 insertions(+), 7 deletions(-) create mode 100644 packages/gatsby-adapter-netlify/src/lambda-handler.ts diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index a849636e2da72..002fbce257eb5 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -20,7 +20,9 @@ "gatsby-adapter" ], "author": "pieh", - "contributors": ["LekoArts"], + "contributors": [ + "LekoArts" + ], "license": "MIT", "repository": { "type": "git", @@ -29,7 +31,10 @@ }, "homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-adapter-netlify#readme", "dependencies": { - "@babel/runtime": "^7.20.13" + "@babel/runtime": "^7.20.13", + "@netlify/functions": "^1.6.0", + "@netlify/cache-utils": "^5.1.5", + "fs-extra": "^11.1.1" }, "devDependencies": { "@babel/cli": "^7.20.7", diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 4033d51b86a0f..2368235f276f8 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -1,10 +1,14 @@ -import type { AdapterInit } from "gatsby" +import type { AdapterInit } from "gatsby/src/utils/adapter/types" // just for debugging import { inspect } from "util" // eslint-disable-next-line @typescript-eslint/no-empty-interface interface INetlifyAdapterOptions {} +// @ts-ignore sigh, we compile to mjs, but it doesn't exist in source code, skipping extension result in error at runtime when +// loading this module because we need to supply mjs extension to actually load it. Adding extension makes typescript unhappy +// TODO: adjust build to convert import paths so typescript is happy and runtime actually works +import { prepareFunction } from "./lambda-handler.mjs" const createNetlifyAdapter: AdapterInit = () => { return { @@ -17,9 +21,7 @@ const createNetlifyAdapter: AdapterInit = () => { reporter.info(`[gatsby-adapter-netlify] cache.store() ${directories}`) }, }, - adapt({ routesManifest, functionsManifest, reporter }): void { - reporter.info(`[gatsby-adapter-netlify] adapt()`) - + async adapt({ routesManifest, functionsManifest }): Promise { console.log( `[gatsby-adapter-netlify] adapt()`, inspect( @@ -30,6 +32,12 @@ const createNetlifyAdapter: AdapterInit = () => { { depth: Infinity, colors: true } ) ) + + // functions handling + for (const fun of functionsManifest) { + console.log(`[gatsby-adapter-netlify] adapt() function`, fun) + await prepareFunction(fun) + } }, } } diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts new file mode 100644 index 0000000000000..c5ea7fa40e966 --- /dev/null +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -0,0 +1,233 @@ +import type { IFunctionDefinition } from "gatsby/src/utils/adapter/types" + +import fs from "fs-extra" +import * as path from "path" + +export async function prepareFunction(fun: IFunctionDefinition): Promise { + const internalFunctionsDir = path.join( + process.cwd(), + `.netlify`, + `functions-internal`, + fun.functionId + ) + + await fs.ensureDir(internalFunctionsDir) + + console.log(`hello`) + + const functionManifest: any = { + config: { + includedFiles: fun.requiredFiles, + externalNodeModules: [`msgpackr-extract`, `babel-runtime`], + }, + version: 1, + } + + if (fun.functionId === `ssr-engine`) { + functionManifest.config.nodeBundler = `none` + } + + await fs.writeJSON( + path.join(internalFunctionsDir, `${fun.functionId}.json`), + functionManifest + ) + + const handlerSource = /* javascript */ ` + const Stream = require("stream") + const http = require("http") + const { Buffer } = require("buffer") + const cookie = require("cookie") + + const preferDefault = m => (m && m.default) || m + + const functionModule = require("./../../../${fun.pathToEntryPoint}") + + const functionHandler = preferDefault(functionModule) + + const createRequestObject = ({ event, context }) => { + const { + path = "", + multiValueQueryStringParameters, + queryStringParameters, + httpMethod, + multiValueHeaders = {}, + body, + isBase64Encoded, + } = event + const newStream = new Stream.Readable() + const req = Object.assign(newStream, http.IncomingMessage.prototype) + req.url = path + req.originalUrl = req.url + req.query = queryStringParameters + req.multiValueQuery = multiValueQueryStringParameters + req.method = httpMethod + req.rawHeaders = [] + req.headers = {} + // Expose Netlify Function event and context on request object. + req.netlifyFunctionParams = { event, context } + for (const key of Object.keys(multiValueHeaders)) { + for (const value of multiValueHeaders[key]) { + req.rawHeaders.push(key, value) + } + req.headers[key.toLowerCase()] = multiValueHeaders[key].toString() + } + req.getHeader = name => req.headers[name.toLowerCase()] + req.getHeaders = () => req.headers + // Gatsby includes cookie middleware + const cookies = req.headers.cookie + if (cookies) { + req.cookies = cookie.parse(cookies) + } + // req.connection = {} + if (body) { + req.push(body, isBase64Encoded ? "base64" : undefined) + } + req.push(null) + return req +} + +const createResponseObject = ({ onResEnd }) => { + const response = { + isBase64Encoded: true, + multiValueHeaders: {}, + }; + const res = new Stream(); + Object.defineProperty(res, 'statusCode', { + get() { + return response.statusCode; + }, + set(statusCode) { + response.statusCode = statusCode; + }, + }); + res.headers = { 'content-type': 'text/html; charset=utf-8' }; + res.writeHead = (status, headers) => { + response.statusCode = status; + if (headers) { + res.headers = Object.assign(res.headers, headers); + } + // Return res object to allow for chaining + // Fixes: https://github.com/netlify/next-on-netlify/pull/74 + return res; + }; + res.write = (chunk) => { + if (!response.body) { + response.body = Buffer.from(''); + } + response.body = Buffer.concat([ + Buffer.isBuffer(response.body) + ? response.body + : Buffer.from(response.body), + Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), + ]); + return true; + }; + res.setHeader = (name, value) => { + res.headers[name.toLowerCase()] = value; + return res; + }; + res.removeHeader = (name) => { + delete res.headers[name.toLowerCase()]; + }; + res.getHeader = (name) => res.headers[name.toLowerCase()]; + res.getHeaders = () => res.headers; + res.hasHeader = (name) => Boolean(res.getHeader(name)); + res.end = (text) => { + if (text) + res.write(text); + if (!res.statusCode) { + res.statusCode = 200; + } + if (response.body) { + response.body = Buffer.from(response.body).toString('base64'); + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore These types are a mess, and need sorting out + response.multiValueHeaders = res.headers; + res.writeHead(response.statusCode); + // Convert all multiValueHeaders into arrays + for (const key of Object.keys(response.multiValueHeaders)) { + const header = response.multiValueHeaders[key]; + if (!Array.isArray(header)) { + response.multiValueHeaders[key] = [header]; + } + } + res.finished = true; + res.writableEnded = true; + // Call onResEnd handler with the response object + onResEnd(response); + return res; + }; + // Gatsby Functions additions + res.send = (data) => { + if (res.finished) { + return res; + } + if (typeof data === 'number') { + return res + .status(data) + .setHeader('content-type', 'text/plain; charset=utf-8') + .end(statuses_1.default.message[data] || String(data)); + } + if (typeof data === 'boolean' || typeof data === 'object') { + if (Buffer.isBuffer(data)) { + res.setHeader('content-type', 'application/octet-Stream'); + } + else if (data !== null) { + return res.json(data); + } + } + res.end(data); + return res; + }; + res.json = (data) => { + if (res.finished) { + return res; + } + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify(data)); + return res; + }; + res.status = (code) => { + const numericCode = Number.parseInt(code); + if (!Number.isNaN(numericCode)) { + response.statusCode = numericCode; + } + return res; + }; + res.redirect = (statusCodeOrUrl, url) => { + let statusCode = statusCodeOrUrl; + let Location = url; + if (!url && typeof statusCodeOrUrl === 'string') { + Location = statusCodeOrUrl; + statusCode = 302; + } + res.writeHead(statusCode, { Location }); + res.end(); + return res; + }; + return res; +}; + + exports.handler = async (event, context) => { + console.log({ functionHandler }) + + const req = createRequestObject({ event, context }) + + return new Promise(async resolve => { + try { + const res = createResponseObject({ onResEnd: resolve }) + await functionHandler(req, res) + } catch(error) { + console.error("Error executing " + event.path, error) + resolve({ statusCode: 500 }) + } + }) + } + ` + + await fs.writeFile( + path.join(internalFunctionsDir, `${fun.functionId}.js`), + handlerSource + ) +} diff --git a/yarn.lock b/yarn.lock index 25fa9b1e835f5..a0d005e1d11f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3152,6 +3152,27 @@ resolved "https://registry.yarnpkg.com/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz#c15791112db68dd9315d329d652b7e797f737655" integrity sha512-3fkKj25kEjsfObL6IlKPAlHYPq/oYwUkkQ03zsTTiDjD7vg/RxjdiLeCydqtxHZP0JgsXL3D/X5oAkMGzuUp/Q== +"@netlify/cache-utils@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@netlify/cache-utils/-/cache-utils-5.1.5.tgz#848c59003e576fa0b2f9c6ca270eff27af938b25" + integrity sha512-lMNdFmy2Yu3oVquSPooRDLxJ8QOsIX6X6vzA2pKz/9V2LQFJiqBukggXM+Rnqzk1regPpdJ0jK3dPGvOKaRQgg== + dependencies: + cpy "^9.0.0" + get-stream "^6.0.0" + globby "^13.0.0" + junk "^4.0.0" + locate-path "^7.0.0" + move-file "^3.0.0" + path-exists "^5.0.0" + readdirp "^3.4.0" + +"@netlify/functions@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@netlify/functions/-/functions-1.6.0.tgz#c373423e6fef0e6f7422ac0345e8bbf2cb692366" + integrity sha512-6G92AlcpFrQG72XU8YH8pg94eDnq7+Q0YJhb8x4qNpdGsvuzvrfHWBmqFGp/Yshmv4wex9lpsTRZOocdrA2erQ== + dependencies: + is-promise "^4.0.0" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" @@ -12014,6 +12035,17 @@ globby@^11.0.1, globby@^11.0.3, globby@^11.0.4, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +globby@^13.0.0: + version "13.1.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.4.tgz#2f91c116066bcec152465ba36e5caa4a13c01317" + integrity sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + globby@^13.1.1, globby@^13.1.2: version "13.1.3" resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff" @@ -13769,6 +13801,11 @@ is-promise@^2.1.0, is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" @@ -15277,6 +15314,13 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +locate-path@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a" + integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== + dependencies: + p-locate "^6.0.0" + lock@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/lock/-/lock-1.1.0.tgz#53157499d1653b136ca66451071fca615703fa55" @@ -17026,6 +17070,13 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +move-file@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/move-file/-/move-file-3.1.0.tgz#ea9675d54852708242462bfe60d56b3c3854cdf7" + integrity sha512-4aE3U7CCBWgrQlQDMq8da4woBWDGHioJFiOZ8Ie6Yq2uwYQ9V2kGhTz4x3u6Wc+OU17nw0yc3rJ/lQ4jIiPe3A== + dependencies: + path-exists "^5.0.0" + mri@^1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" @@ -17959,6 +18010,13 @@ p-limit@^2.0.0, p-limit@^2.1.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644" + integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== + dependencies: + yocto-queue "^1.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -17984,6 +18042,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-locate@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f" + integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== + dependencies: + p-limit "^4.0.0" + p-map-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" @@ -18368,6 +18433,11 @@ path-exists@^4.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== +path-exists@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" + integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -20220,7 +20290,7 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" -readdirp@~3.6.0: +readdirp@^3.4.0, readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== @@ -25251,6 +25321,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yocto-queue@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + yoga-layout-prebuilt@^1.10.0, yoga-layout-prebuilt@^1.9.6: version "1.10.0" resolved "https://registry.yarnpkg.com/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz#2936fbaf4b3628ee0b3e3b1df44936d6c146faa6" From fee841d7ed0179782e608ebeddbbfa2bed7986bc Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 6 Jun 2023 10:28:22 +0200 Subject: [PATCH 053/161] remove netlify adapter from gatsby deps, add gatsby as devDep to adapter (to access types) --- packages/gatsby-adapter-netlify/package.json | 1 + packages/gatsby/package.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 002fbce257eb5..dcd55648d95eb 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -40,6 +40,7 @@ "@babel/cli": "^7.20.7", "@babel/core": "^7.20.12", "babel-preset-gatsby-package": "^3.11.0-next.0", + "gatsby": "^5.11.0-next.1", "cross-env": "^7.0.3", "rimraf": "^5.0.1", "typescript": "^5.0.4" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 10703794af4f8..3f6d09a1e5074 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -90,7 +90,6 @@ "find-cache-dir": "^3.3.2", "fs-exists-cached": "1.0.0", "fs-extra": "^11.1.1", - "gatsby-adapter-netlify": "^1.0.0", "gatsby-cli": "^5.11.0-next.1", "gatsby-core-utils": "^4.11.0-next.1", "gatsby-graphiql-explorer": "^3.11.0-next.1", From be62f1dede6bfb61b8b8643954b9e6d720f2a206 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 6 Jun 2023 10:28:40 +0200 Subject: [PATCH 054/161] fix bundling function files containing [ ] --- packages/gatsby-adapter-netlify/src/lambda-handler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index c5ea7fa40e966..456875857f5ea 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -17,7 +17,9 @@ export async function prepareFunction(fun: IFunctionDefinition): Promise { const functionManifest: any = { config: { - includedFiles: fun.requiredFiles, + includedFiles: fun.requiredFiles.map(file => + file.replace(/\[/g, `*`).replace(/]/g, `*`) + ), externalNodeModules: [`msgpackr-extract`, `babel-runtime`], }, version: 1, From 90423bf58a54db6824b67c81da1d75277c00fec2 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 6 Jun 2023 10:34:51 +0200 Subject: [PATCH 055/161] unify tsconfig for adapter --- packages/gatsby-adapter-netlify/tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/tsconfig.json b/packages/gatsby-adapter-netlify/tsconfig.json index 4082f16a5d91c..9f57aee168802 100644 --- a/packages/gatsby-adapter-netlify/tsconfig.json +++ b/packages/gatsby-adapter-netlify/tsconfig.json @@ -1,3 +1,5 @@ { - "extends": "../../tsconfig.json" + "extends": "../../tsconfig.json", + "include": ["src"], + "exclude": ["node_modules", "src/__tests__", "dist"], } From 900ed5eb0b429648b78326a03d7660bd50ea2a30 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 6 Jun 2023 10:35:00 +0200 Subject: [PATCH 056/161] add joi testing for adapter setting --- .../gatsby/src/joi-schemas/__tests__/joi.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/gatsby/src/joi-schemas/__tests__/joi.ts b/packages/gatsby/src/joi-schemas/__tests__/joi.ts index df70e625c1ed1..91c65b026ae68 100644 --- a/packages/gatsby/src/joi-schemas/__tests__/joi.ts +++ b/packages/gatsby/src/joi-schemas/__tests__/joi.ts @@ -327,6 +327,44 @@ describe(`gatsby config`, () => { `[ValidationError: "headers[0].headers" must be an array]` ) }) + + it(`lets you add an adapter`, () => { + const config = { + adapter: { + name: `gatsby-adapter-name`, + cache: { + restore: (): Promise => Promise.resolve(), + store: (): Promise => Promise.resolve(), + }, + adapt: (): Promise => Promise.resolve(), + }, + } + + const result = gatsbyConfigSchema.validate(config) + expect(result.value?.adapter).toEqual(config.adapter) + }) + + it(`throws on incorrect adapter setting`, () => { + const configOne = { + adapter: `gatsby-adapter-name`, + } + + const resultOne = gatsbyConfigSchema.validate(configOne) + expect(resultOne.error).toMatchInlineSnapshot( + `[ValidationError: "adapter" must be of type object]` + ) + + const configTwo = { + adapter: { + name: `gatsby-adapter-name`, + }, + } + + const resultTwo = gatsbyConfigSchema.validate(configTwo) + expect(resultTwo.error).toMatchInlineSnapshot( + `[ValidationError: "adapter.adapt" is required]` + ) + }) }) describe(`node schema`, () => { From eb9f676878b5d6470de1238a53baabd7cca81f93 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 6 Jun 2023 12:27:53 +0200 Subject: [PATCH 057/161] typescript: make bootstrap work again --- package.json | 6 +++--- packages/gatsby-cli/src/reporter/redux/utils.ts | 2 +- packages/gatsby-core-utils/package.json | 2 +- packages/gatsby-sharp/tsconfig.json | 3 ++- packages/gatsby-source-shopify/tsconfig.json | 1 - 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f9e491ef6d328..995cc778b70a6 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ }, "private": true, "scripts": { - "bootstrap": "cross-env COMPILER_OPTIONS=\"GATSBY_MAJOR=5\" npm-run-all -s check-versions \"lerna-prepare -- --{@}\" --", + "bootstrap": "cross-env COMPILER_OPTIONS=\"GATSBY_MAJOR=5\" npm-run-all -s \"lerna-prepare -- --{@}\" --", "check-repo-fields": "node scripts/check-repo-fields.js", "check-versions": "node scripts/check-versions.js", "format": "npm run format:code && npm run format:other", @@ -100,13 +100,13 @@ "jest:inspect": "node --inspect node_modules/.bin/jest --runInBand", "jest:inspect-brk": "node --inspect-brk node_modules/.bin/jest --runInBand", "lerna": "lerna", - "lerna-prepare": "lerna run prepare", + "lerna-prepare": "lerna run prepare --ignore gatsby-core-utils", "lint": "npm-run-all --continue-on-error -p lint:code lint:other", "lint:code": "eslint --ext .js,.jsx,.ts,.tsx --quiet .", "lint:other": "npm run prettier -- --check", "markdown": "md-magic --path \"starters/**/README.md\"", "postmarkdown": "prettier --write \"starters/**/README.md\"", - "prebootstrap": "yarn", + "prebootstrap": "yarn && npm run check-versions && cross-env COMPILER_OPTIONS=\"GATSBY_MAJOR=5\" lerna run prepare --scope gatsby-core-utils", "prettier": "prettier \"**/*.{md,css,scss,yaml,yml}\"", "publish": "echo \"Use 'yarn publish-next' or 'yarn publish-release' instead of 'yarn run publish'\"", "publish-canary": "node scripts/check-publish-access && lerna publish --canary --yes", diff --git a/packages/gatsby-cli/src/reporter/redux/utils.ts b/packages/gatsby-cli/src/reporter/redux/utils.ts index 1ac999adc0f63..79c26583f891a 100644 --- a/packages/gatsby-cli/src/reporter/redux/utils.ts +++ b/packages/gatsby-cli/src/reporter/redux/utils.ts @@ -45,7 +45,7 @@ export const getGlobalStatus = ( return generatedStatus }, ActivityStatuses.Success - ) + ) as ActivityStatuses } export const getActivity = (id: string): IActivity | null => diff --git a/packages/gatsby-core-utils/package.json b/packages/gatsby-core-utils/package.json index d48fd236404ee..36ed861954751 100644 --- a/packages/gatsby-core-utils/package.json +++ b/packages/gatsby-core-utils/package.json @@ -48,7 +48,7 @@ "build:cjs": "babel src --out-dir dist/ --ignore \"**/__tests__\" --ignore \"**/__mocks__\" --extensions \".ts\"", "build:mjs": "cross-env BABEL_ENV=modern babel src --out-dir dist/ --ignore \"**/__tests__\" --ignore \"**/__mocks__\" --extensions \".ts\" --out-file-extension .mjs", "typegen": "tsc --emitDeclarationOnly --declaration --declarationDir dist/", - "prepare": "cross-env NODE_ENV=production npm run build && npm run typegen", + "prepare": "cross-env NODE_ENV=production npm-run-all --npm-path npm -s build typegen", "watch": "npm-run-all --npm-path npm -p watch:*", "watch:cjs": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --ignore \"**/__mocks__\" --extensions \".ts\"", "watch:mjs": "cross-env BABEL_ENV=modern babel -w src --out-dir dist/ --ignore \"**/__tests__\" --ignore \"**/__mocks__\" --extensions \".ts\" --out-file-extension .mjs", diff --git a/packages/gatsby-sharp/tsconfig.json b/packages/gatsby-sharp/tsconfig.json index 68027cd6d44d0..293e138b586d5 100644 --- a/packages/gatsby-sharp/tsconfig.json +++ b/packages/gatsby-sharp/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "target": "ES5", - "module": "CommonJS" + "module": "CommonJS", + "downlevelIteration": true }, "exclude": [ "src/__tests__", diff --git a/packages/gatsby-source-shopify/tsconfig.json b/packages/gatsby-source-shopify/tsconfig.json index cab823f261c46..51c9bbd568de6 100644 --- a/packages/gatsby-source-shopify/tsconfig.json +++ b/packages/gatsby-source-shopify/tsconfig.json @@ -2,7 +2,6 @@ "include": ["src/**/*.ts", "types"], "exclude": ["node_modules"], "compilerOptions": { - "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, From 9d0e6f81f90c7ff4a7e321704f69990243edb251 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 6 Jun 2023 13:47:29 +0200 Subject: [PATCH 058/161] generate redirect/rewrite rules, generate 2 variants of ssr-engine (odb and regular) --- packages/gatsby-adapter-netlify/src/index.ts | 117 +++++++++++++++++- .../src/lambda-handler.ts | 40 ++++-- 2 files changed, 144 insertions(+), 13 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 2368235f276f8..863cf7ea40a7b 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -5,10 +5,32 @@ import { inspect } from "util" // eslint-disable-next-line @typescript-eslint/no-empty-interface interface INetlifyAdapterOptions {} + +import fs from "fs-extra" + // @ts-ignore sigh, we compile to mjs, but it doesn't exist in source code, skipping extension result in error at runtime when // loading this module because we need to supply mjs extension to actually load it. Adding extension makes typescript unhappy // TODO: adjust build to convert import paths so typescript is happy and runtime actually works -import { prepareFunction } from "./lambda-handler.mjs" +import { prepareFunctionVariants } from "./lambda-handler.mjs" + +const NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST = new Set([ + `query`, + `conditions`, + `headers`, + `signed`, + `edge_handler`, +]) + +const NETLIFY_CONDITIONS_ALLOWLIST = new Set([`language`, `country`]) + +const toNetlifyPath = (fromPath: string, toPath: string): Array => { + // Modifies query parameter redirects, having no effect on other fromPath strings + const netlifyFromPath = fromPath.replace(/[&?]/, ` `) + // Modifies wildcard & splat redirects, having no effect on other toPath strings + const netlifyToPath = toPath.replace(/\*/, `:splat`) + + return [netlifyFromPath, netlifyToPath] +} const createNetlifyAdapter: AdapterInit = () => { return { @@ -33,10 +55,99 @@ const createNetlifyAdapter: AdapterInit = () => { ) ) + const lambdasThatUseCaching = new Map() + + let _redirects = `` + for (const route of routesManifest) { + const fromPath = route.path.replace(/\*.*/, `*`) + + if (route.type === `lambda`) { + let functionName = route.functionId + if (route.cache) { + functionName = `${route.functionId}-odb` + if (!lambdasThatUseCaching.has(route.functionId)) { + lambdasThatUseCaching.set(route.functionId, functionName) + } + } + + const invocationURL = `/.netlify/${ + route.cache ? `builders` : `functions` + }/${functionName}` + _redirects += `${fromPath} ${invocationURL} 200\n` + } else if (route.type === `redirect`) { + const { + status: routeStatus, + toPath, + force, + // TODO: add headers handling + headers, + ...rest + } = route + let status = String(routeStatus) + + if (force) { + status = `${status}!` + } + + const [netlifyFromPath, netlifyToPath] = toNetlifyPath( + fromPath, + toPath + ) + + // The order of the first 3 parameters is significant. + // The order for rest params (key-value pairs) is arbitrary. + const pieces = [netlifyFromPath, netlifyToPath, status] + + for (const [key, value] of Object.entries(rest)) { + if (NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST.has(key)) { + if (key === `conditions`) { + // "conditions" key from Gatsby contains only "language" and "country" + // which need special transformation to match Netlify _redirects + // https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect + if (value && typeof value === `object`) { + for (const [ + conditionKey, + conditionValueRaw, + ] of Object.entries(value)) { + if (NETLIFY_CONDITIONS_ALLOWLIST.has(conditionKey)) { + const conditionValue = Array.isArray(conditionValueRaw) + ? conditionValueRaw.join(`,`) + : conditionValueRaw + // Gatsby gives us "country", we want "Country" + const conditionName = + conditionKey.charAt(0).toUpperCase() + + conditionKey.slice(1) + + pieces.push(`${conditionName}:${conditionValue}`) + } + } + } + } else { + pieces.push(`${key}=${value}`) + } + } + } + _redirects += pieces.join(` `) + `\n` + } else if (route.type === `static`) { + // regular static asset without dynamic paths will just work, so skipping those + if (route.path.includes(`:`) || route.path.includes(`*`)) { + _redirects += `${fromPath} ${route.filePath.replace( + /^public/, + `` + )} 200\n` + } + } + } + + // TODO: add markers around generated redirects so we can update them and merge with user provided ones + await fs.outputFile(`public/_redirects`, _redirects) + // functions handling for (const fun of functionsManifest) { - console.log(`[gatsby-adapter-netlify] adapt() function`, fun) - await prepareFunction(fun) + await prepareFunctionVariants( + fun, + lambdasThatUseCaching.get(fun.functionId) + ) } }, } diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 456875857f5ea..8f3a59384e26d 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -3,18 +3,27 @@ import type { IFunctionDefinition } from "gatsby/src/utils/adapter/types" import fs from "fs-extra" import * as path from "path" -export async function prepareFunction(fun: IFunctionDefinition): Promise { +async function prepareFunction( + fun: IFunctionDefinition, + odbfunctionName?: string +): Promise { + let functionId = fun.functionId + let isODB = false + + if (odbfunctionName) { + functionId = odbfunctionName + isODB = true + } + const internalFunctionsDir = path.join( process.cwd(), `.netlify`, `functions-internal`, - fun.functionId + functionId ) await fs.ensureDir(internalFunctionsDir) - console.log(`hello`) - const functionManifest: any = { config: { includedFiles: fun.requiredFiles.map(file => @@ -30,7 +39,7 @@ export async function prepareFunction(fun: IFunctionDefinition): Promise { } await fs.writeJSON( - path.join(internalFunctionsDir, `${fun.functionId}.json`), + path.join(internalFunctionsDir, `${functionId}.json`), functionManifest ) @@ -39,6 +48,7 @@ export async function prepareFunction(fun: IFunctionDefinition): Promise { const http = require("http") const { Buffer } = require("buffer") const cookie = require("cookie") + ${isODB ? `const { builder } = require("@netlify/functions")` : ``} const preferDefault = m => (m && m.default) || m @@ -211,9 +221,7 @@ const createResponseObject = ({ onResEnd }) => { return res; }; - exports.handler = async (event, context) => { - console.log({ functionHandler }) - + const handler = async (event, context) => { const req = createRequestObject({ event, context }) return new Promise(async resolve => { @@ -226,10 +234,22 @@ const createResponseObject = ({ onResEnd }) => { } }) } - ` + + exports.handler = ${isODB ? `builder(handler)` : `handler`} + ` await fs.writeFile( - path.join(internalFunctionsDir, `${fun.functionId}.js`), + path.join(internalFunctionsDir, `${functionId}.js`), handlerSource ) } + +export async function prepareFunctionVariants( + fun: IFunctionDefinition, + odbfunctionName?: string +): Promise { + await prepareFunction(fun) + if (odbfunctionName) { + await prepareFunction(fun, odbfunctionName) + } +} From 80c45a4d671b691c4901825ef978a3982187151d Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 6 Jun 2023 15:10:00 +0200 Subject: [PATCH 059/161] move routes manifest handling into its own module --- packages/gatsby-adapter-netlify/src/index.ts | 112 +---------------- .../src/route-handler.ts | 115 ++++++++++++++++++ 2 files changed, 120 insertions(+), 107 deletions(-) create mode 100644 packages/gatsby-adapter-netlify/src/route-handler.ts diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 863cf7ea40a7b..6991811874806 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -6,31 +6,12 @@ import { inspect } from "util" // eslint-disable-next-line @typescript-eslint/no-empty-interface interface INetlifyAdapterOptions {} -import fs from "fs-extra" - // @ts-ignore sigh, we compile to mjs, but it doesn't exist in source code, skipping extension result in error at runtime when // loading this module because we need to supply mjs extension to actually load it. Adding extension makes typescript unhappy // TODO: adjust build to convert import paths so typescript is happy and runtime actually works import { prepareFunctionVariants } from "./lambda-handler.mjs" - -const NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST = new Set([ - `query`, - `conditions`, - `headers`, - `signed`, - `edge_handler`, -]) - -const NETLIFY_CONDITIONS_ALLOWLIST = new Set([`language`, `country`]) - -const toNetlifyPath = (fromPath: string, toPath: string): Array => { - // Modifies query parameter redirects, having no effect on other fromPath strings - const netlifyFromPath = fromPath.replace(/[&?]/, ` `) - // Modifies wildcard & splat redirects, having no effect on other toPath strings - const netlifyToPath = toPath.replace(/\*/, `:splat`) - - return [netlifyFromPath, netlifyToPath] -} +// @ts-ignore same as above +import { handleRoutesManifest } from "./route-handler.mjs" const createNetlifyAdapter: AdapterInit = () => { return { @@ -55,92 +36,9 @@ const createNetlifyAdapter: AdapterInit = () => { ) ) - const lambdasThatUseCaching = new Map() - - let _redirects = `` - for (const route of routesManifest) { - const fromPath = route.path.replace(/\*.*/, `*`) - - if (route.type === `lambda`) { - let functionName = route.functionId - if (route.cache) { - functionName = `${route.functionId}-odb` - if (!lambdasThatUseCaching.has(route.functionId)) { - lambdasThatUseCaching.set(route.functionId, functionName) - } - } - - const invocationURL = `/.netlify/${ - route.cache ? `builders` : `functions` - }/${functionName}` - _redirects += `${fromPath} ${invocationURL} 200\n` - } else if (route.type === `redirect`) { - const { - status: routeStatus, - toPath, - force, - // TODO: add headers handling - headers, - ...rest - } = route - let status = String(routeStatus) - - if (force) { - status = `${status}!` - } - - const [netlifyFromPath, netlifyToPath] = toNetlifyPath( - fromPath, - toPath - ) - - // The order of the first 3 parameters is significant. - // The order for rest params (key-value pairs) is arbitrary. - const pieces = [netlifyFromPath, netlifyToPath, status] - - for (const [key, value] of Object.entries(rest)) { - if (NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST.has(key)) { - if (key === `conditions`) { - // "conditions" key from Gatsby contains only "language" and "country" - // which need special transformation to match Netlify _redirects - // https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect - if (value && typeof value === `object`) { - for (const [ - conditionKey, - conditionValueRaw, - ] of Object.entries(value)) { - if (NETLIFY_CONDITIONS_ALLOWLIST.has(conditionKey)) { - const conditionValue = Array.isArray(conditionValueRaw) - ? conditionValueRaw.join(`,`) - : conditionValueRaw - // Gatsby gives us "country", we want "Country" - const conditionName = - conditionKey.charAt(0).toUpperCase() + - conditionKey.slice(1) - - pieces.push(`${conditionName}:${conditionValue}`) - } - } - } - } else { - pieces.push(`${key}=${value}`) - } - } - } - _redirects += pieces.join(` `) + `\n` - } else if (route.type === `static`) { - // regular static asset without dynamic paths will just work, so skipping those - if (route.path.includes(`:`) || route.path.includes(`*`)) { - _redirects += `${fromPath} ${route.filePath.replace( - /^public/, - `` - )} 200\n` - } - } - } - - // TODO: add markers around generated redirects so we can update them and merge with user provided ones - await fs.outputFile(`public/_redirects`, _redirects) + const { lambdasThatUseCaching } = await handleRoutesManifest( + routesManifest + ) // functions handling for (const fun of functionsManifest) { diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts new file mode 100644 index 0000000000000..c9b18116fb545 --- /dev/null +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -0,0 +1,115 @@ +import type { RoutesManifest } from "gatsby/src/utils/adapter/types" + +import fs from "fs-extra" + +const NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST = new Set([ + `query`, + `conditions`, + `headers`, + `signed`, + `edge_handler`, +]) + +const NETLIFY_CONDITIONS_ALLOWLIST = new Set([`language`, `country`]) + +const toNetlifyPath = (fromPath: string, toPath: string): Array => { + // Modifies query parameter redirects, having no effect on other fromPath strings + const netlifyFromPath = fromPath.replace(/[&?]/, ` `) + // Modifies wildcard & splat redirects, having no effect on other toPath strings + const netlifyToPath = toPath.replace(/\*/, `:splat`) + + return [netlifyFromPath, netlifyToPath] +} + +export async function handleRoutesManifest( + routesManifest: RoutesManifest +): Promise<{ + lambdasThatUseCaching: Map +}> { + console.log(`hello from handleRoutesManifest()`) + const lambdasThatUseCaching = new Map() + + let _redirects = `` + for (const route of routesManifest) { + const fromPath = route.path.replace(/\*.*/, `*`) + + if (route.type === `lambda`) { + let functionName = route.functionId + if (route.cache) { + functionName = `${route.functionId}-odb` + if (!lambdasThatUseCaching.has(route.functionId)) { + lambdasThatUseCaching.set(route.functionId, functionName) + } + } + + const invocationURL = `/.netlify/${ + route.cache ? `builders` : `functions` + }/${functionName}` + _redirects += `${fromPath} ${invocationURL} 200\n` + } else if (route.type === `redirect`) { + const { + status: routeStatus, + toPath, + force, + // TODO: add headers handling + headers, + ...rest + } = route + let status = String(routeStatus) + + if (force) { + status = `${status}!` + } + + const [netlifyFromPath, netlifyToPath] = toNetlifyPath(fromPath, toPath) + + // The order of the first 3 parameters is significant. + // The order for rest params (key-value pairs) is arbitrary. + const pieces = [netlifyFromPath, netlifyToPath, status] + + for (const [key, value] of Object.entries(rest)) { + if (NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST.has(key)) { + if (key === `conditions`) { + // "conditions" key from Gatsby contains only "language" and "country" + // which need special transformation to match Netlify _redirects + // https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect + if (value && typeof value === `object`) { + for (const [conditionKey, conditionValueRaw] of Object.entries( + value + )) { + if (NETLIFY_CONDITIONS_ALLOWLIST.has(conditionKey)) { + const conditionValue = Array.isArray(conditionValueRaw) + ? conditionValueRaw.join(`,`) + : conditionValueRaw + // Gatsby gives us "country", we want "Country" + const conditionName = + conditionKey.charAt(0).toUpperCase() + conditionKey.slice(1) + + pieces.push(`${conditionName}:${conditionValue}`) + } + } + } + } else { + pieces.push(`${key}=${value}`) + } + } + } + _redirects += pieces.join(` `) + `\n` + } else if (route.type === `static`) { + // regular static asset without dynamic paths will just work, so skipping those + if (route.path.includes(`:`) || route.path.includes(`*`)) { + _redirects += `${fromPath} ${route.filePath.replace( + /^public/, + `` + )} 200\n` + } + } + } + + // TODO: add markers around generated redirects so we can update them and merge with user provided ones + await fs.outputFile(`public/_redirects`, _redirects) + + return { + lambdasThatUseCaching, + } +} From b16febb04dd02530d4738f9475f3e6c29fdb8133 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 6 Jun 2023 16:07:49 +0200 Subject: [PATCH 060/161] generate _headers rules --- packages/gatsby-adapter-netlify/src/route-handler.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index c9b18116fb545..4ad90642fe785 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -30,6 +30,7 @@ export async function handleRoutesManifest( const lambdasThatUseCaching = new Map() let _redirects = `` + let _headers = `` for (const route of routesManifest) { const fromPath = route.path.replace(/\*.*/, `*`) @@ -103,11 +104,17 @@ export async function handleRoutesManifest( `` )} 200\n` } + + _headers += `${fromPath}\n${route.headers.reduce((acc, curr) => { + acc += ` ${curr.key}: ${curr.value}\n` + return acc + }, ``)}` } } - // TODO: add markers around generated redirects so we can update them and merge with user provided ones + // TODO: add markers around generated redirects and headers so we can update them and merge with user provided ones await fs.outputFile(`public/_redirects`, _redirects) + await fs.outputFile(`public/_headers`, _headers) return { lambdasThatUseCaching, From ff7575f5f8028d5609a64418269bc205c9b90bfd Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 6 Jun 2023 16:33:55 +0200 Subject: [PATCH 061/161] add sorting to routesManifest --- packages/gatsby/src/utils/adapter/manager.ts | 43 +++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 107c027128688..d33a0d488d09e 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -27,6 +27,7 @@ import { import { createHeadersMatcher } from "./create-headers" import { HTTP_STATUS_CODE } from "../../redux/types" import type { IHeader } from "../../redux/types" +import { rankRoute } from "../../bootstrap/requires-writer" function noOpAdapterManager(): IAdapterManager { return { @@ -146,10 +147,10 @@ export function setWebpackAssets(assets: Set): void { webpackAssets = assets } +type RouteWithScore = { score: number } & Route + function getRoutesManifest(): RoutesManifest { - // TODO: have routes list sorted by specifity so more specific ones are before less specific ones (/static should be before /:param and that should be before /*), - // so routing can just handle first match - const routes = [] as RoutesManifest + const routes: Array = [] const createHeaders = createHeadersMatcher() const fileAssets = new Set( @@ -160,7 +161,8 @@ function getRoutesManifest(): RoutesManifest { }) ) - function addSortedRoute(route: Route): void { + // TODO: This could be a "addSortedRoute" function that would add route to the list in sorted order. TBD if necessary performance-wise + function addRoute(route: Route): void { if (!route.path.startsWith(`/`)) { route.path = `/${route.path}` } @@ -169,7 +171,9 @@ function getRoutesManifest(): RoutesManifest { route.headers = createHeaders(route.path, route.headers) } - routes.push(route) + ;(route as RouteWithScore).score = rankRoute(route.path) + + routes.push(route as RouteWithScore) } function addStaticRoute({ @@ -181,7 +185,7 @@ function getRoutesManifest(): RoutesManifest { pathToFilInPublicDir: string headers: IHeader["headers"] }): void { - addSortedRoute({ + addRoute({ path, type: `static`, filePath: posix.join(`public`, pathToFilInPublicDir), @@ -229,12 +233,12 @@ function getRoutesManifest(): RoutesManifest { commonFields.cache = true } - addSortedRoute({ + addRoute({ path: htmlRoutePath, ...commonFields, }) - addSortedRoute({ + addRoute({ path: pageDataRoutePath, ...commonFields, }) @@ -280,7 +284,7 @@ function getRoutesManifest(): RoutesManifest { // redirect routes for (const redirect of store.getState().redirects.values()) { - addSortedRoute({ + addRoute({ path: redirect.fromPath, type: `redirect`, toPath: redirect.toPath, @@ -296,7 +300,7 @@ function getRoutesManifest(): RoutesManifest { // function routes for (const functionInfo of store.getState().functions.values()) { - addSortedRoute({ + addRoute({ path: `/api/${ maybeDropNamedPartOfWildcard(functionInfo.matchPath) ?? functionInfo.functionRoute @@ -319,7 +323,24 @@ function getRoutesManifest(): RoutesManifest { }) } - return routes + return ( + routes + .sort((a, b) => { + // The higher the score, the higher the specificity of our path + const order = b.score - a.score + if (order !== 0) { + return order + } + + // if specificity is the same we do lexigraphic comparison of path to ensure + // deterministic order regardless of order pages where created + return a.path.localeCompare(b.path) + }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(({ score, ...rest }): Route => { + return { ...rest } + }) + ) } function getFunctionsManifest(): FunctionsManifest { From dcec3236497807d67b88173c38a765acd428af44 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 7 Jun 2023 09:38:01 +0200 Subject: [PATCH 062/161] adjust graphql-engine bundling to not leave unreasolvable imports --- .../gatsby-adapter-netlify/src/lambda-handler.ts | 6 +----- .../src/schema/graphql-engine/bundle-webpack.ts | 15 ++++++++++++++- .../schema/graphql-engine/sharp-bundling-patch.ts | 6 ++++++ 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 packages/gatsby/src/schema/graphql-engine/sharp-bundling-patch.ts diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 8f3a59384e26d..a054a6a09f36f 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -29,15 +29,11 @@ async function prepareFunction( includedFiles: fun.requiredFiles.map(file => file.replace(/\[/g, `*`).replace(/]/g, `*`) ), - externalNodeModules: [`msgpackr-extract`, `babel-runtime`], + externalNodeModules: [`msgpackr-extract`], }, version: 1, } - if (fun.functionId === `ssr-engine`) { - functionManifest.config.nodeBundler = `none` - } - await fs.writeJSON( path.join(internalFunctionsDir, `${functionId}.json`), functionManifest diff --git a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts index 1ffb9dbe56ac5..4a9a1a20932c5 100644 --- a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts +++ b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts @@ -81,7 +81,6 @@ export async function createGraphqlEngineBundle( // those are required in some runtime paths, but we don't need them externals: [ `cbor-x`, // optional dep of lmdb-store, but we are using `msgpack` (default) encoding, so we don't need it - `babel-runtime/helpers/asyncToGenerator`, // undeclared dep of yurnalist (but used in code path we don't use) `electron`, // :shrug: `got` seems to have electron specific code path mod.builtinModules.reduce((acc, builtinModule) => { if (builtinModule === `fs`) { @@ -99,6 +98,18 @@ export async function createGraphqlEngineBundle( rules: [ { oneOf: [ + { + // specific set of loaders for sharp + test: /node_modules[/\\]sharp[/\\].*\.[cm]?js$/, + // it is recommended for Node builds to turn off AMD support + parser: { amd: false }, + use: [ + assetRelocatorUseEntry, + { + loader: require.resolve(`./sharp-bundling-patch`), + }, + ], + }, { // specific set of loaders for LMBD - our custom patch to massage lmdb to work with relocator -> relocator test: /node_modules[/\\]lmdb[/\\].*\.[cm]?js/, @@ -189,6 +200,8 @@ export async function createGraphqlEngineBundle( "graphql-import-node$": require.resolve(`./shims/no-op-module`), "graphql-import-node/register$": require.resolve(`./shims/no-op-module`), + "babel-runtime/helpers/asyncToGenerator": + require.resolve(`./shims/no-op-module`), // undeclared dep of yurnalist (but used in code path we don't use) }, }, plugins: [ diff --git a/packages/gatsby/src/schema/graphql-engine/sharp-bundling-patch.ts b/packages/gatsby/src/schema/graphql-engine/sharp-bundling-patch.ts new file mode 100644 index 0000000000000..11f6d51527e1c --- /dev/null +++ b/packages/gatsby/src/schema/graphql-engine/sharp-bundling-patch.ts @@ -0,0 +1,6 @@ +export default function (this: any, source: string): string { + return source?.replace( + `versions = require(\`../vendor/\${versions.vips}/\${platformAndArch}/versions.json\`);`, + `` + ) +} From e2c2ed249d9e5bb67deed80fdfdfb9dea6fe1cae Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 7 Jun 2023 17:13:42 +0200 Subject: [PATCH 063/161] ssr lambda handling when it executes in read-only dir (use tmpdir() then) --- packages/gatsby-adapter-netlify/src/index.ts | 23 +++--- packages/gatsby/package.json | 1 + .../graphql-engine/lmdb-bundling-patch.ts | 55 +++++++------ .../src/utils/page-ssr-module/lambda.ts | 80 ++++++++++++++++++- yarn.lock | 5 ++ 5 files changed, 126 insertions(+), 38 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 6991811874806..438e4e296c16d 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -1,7 +1,7 @@ import type { AdapterInit } from "gatsby/src/utils/adapter/types" // just for debugging -import { inspect } from "util" +// import { inspect } from "util" // eslint-disable-next-line @typescript-eslint/no-empty-interface interface INetlifyAdapterOptions {} @@ -25,16 +25,17 @@ const createNetlifyAdapter: AdapterInit = () => { }, }, async adapt({ routesManifest, functionsManifest }): Promise { - console.log( - `[gatsby-adapter-netlify] adapt()`, - inspect( - { - routesManifest, - functionsManifest, - }, - { depth: Infinity, colors: true } - ) - ) + // this is noisy, so for now commented out to easily restore if some routes/functions debugging is needed + // console.log( + // `[gatsby-adapter-netlify] adapt()`, + // inspect( + // { + // routesManifest, + // functionsManifest, + // }, + // { depth: Infinity, colors: true } + // ) + // ) const { lambdasThatUseCaching } = await handleRoutesManifest( routesManifest diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 3f6d09a1e5074..186ba3cbf4b6a 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -118,6 +118,7 @@ "joi": "^17.9.2", "json-loader": "^0.5.7", "latest-version": "^7.0.0", + "linkfs": "^2.1.0", "lmdb": "2.5.3", "lodash": "^4.17.21", "meant": "^1.0.3", diff --git a/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts b/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts index adb892311b9c7..492195c680a66 100644 --- a/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts +++ b/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts @@ -25,34 +25,43 @@ const { createRequire } = require(`module`) // eslint-disable-next-line @typescript-eslint/no-explicit-any export default function (this: any, source: string): string { let lmdbBinaryLocation: string | undefined - try { - const lmdbRoot = - this?._module.resourceResolveData?.descriptionFileRoot || - path.dirname(this.resourcePath).replace(`/dist`, ``) - const lmdbRequire = createRequire(this.resourcePath) - let nodeGypBuild + // TODO: streamline using alternative binary - this should be automatically figured out + // and not provided by user/site + if (process.env.GATSBY_FORCE_LMDB_BINARY_LOCATION) { + lmdbBinaryLocation = process.env.GATSBY_FORCE_LMDB_BINARY_LOCATION + } + + if (!lmdbBinaryLocation) { try { - nodeGypBuild = lmdbRequire(`node-gyp-build-optional-packages`) - } catch (e) { - // lmdb@2.3.8 way of loading binaries failed, we will try to fallback to - // old way before failing completely - } + const lmdbRoot = + this?._module.resourceResolveData?.descriptionFileRoot || + path.dirname(this.resourcePath).replace(`/dist`, ``) - if (!nodeGypBuild) { - // if lmdb@2.3.8 didn't import expected node-gyp-build fork (node-gyp-build-optional-packages) - // let's try falling back to upstream package - if that doesn't work, we will fail compilation - nodeGypBuild = lmdbRequire(`node-gyp-build`) - } + const lmdbRequire = createRequire(this.resourcePath) + let nodeGypBuild + try { + nodeGypBuild = lmdbRequire(`node-gyp-build-optional-packages`) + } catch (e) { + // lmdb@2.3.8 way of loading binaries failed, we will try to fallback to + // old way before failing completely + } - lmdbBinaryLocation = slash( - path.relative( - path.dirname(this.resourcePath), - nodeGypBuild.path(lmdbRoot) + if (!nodeGypBuild) { + // if lmdb@2.3.8 didn't import expected node-gyp-build fork (node-gyp-build-optional-packages) + // let's try falling back to upstream package - if that doesn't work, we will fail compilation + nodeGypBuild = lmdbRequire(`node-gyp-build`) + } + + lmdbBinaryLocation = slash( + path.relative( + path.dirname(this.resourcePath), + nodeGypBuild.path(lmdbRoot) + ) ) - ) - } catch (e) { - return source + } catch (e) { + return source + } } return source .replace( diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts index 6b28809583d2a..f91a2ed6f4683 100644 --- a/packages/gatsby/src/utils/page-ssr-module/lambda.ts +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -1,7 +1,79 @@ -import { GatsbyFunctionResponse, GatsbyFunctionRequest } from "gatsby" +import type { GatsbyFunctionResponse, GatsbyFunctionRequest } from "gatsby" import * as path from "path" -import { IGatsbyPage } from "../../internal" -import { ISSRData } from "./entry" +import * as fs from "fs-extra" +import { tmpdir } from "os" +import type { IGatsbyPage } from "../../internal" +import type { ISSRData } from "./entry" +import { link } from "linkfs" + +function setupFsWrapper(): string { + // setup global._fsWrapper + try { + fs.accessSync(__filename, fs.constants.W_OK) + // TODO: this seems funky - not sure if this is correct way to handle this, so just marking TODO to revisit this + return path.join(__dirname, `..`, `data`, `datastore`) + } catch (e) { + // we are in a read-only filesystem, so we need to use a temp dir + + const TEMP_CACHE_DIR = path.join(tmpdir(), `gatsby`, `.cache`) + + // TODO: don't hardcode this + const cacheDir = `/var/task/.cache` + + // we need to rewrite fs + const rewrites = [ + [path.join(cacheDir, `caches`), path.join(TEMP_CACHE_DIR, `caches`)], + [ + path.join(cacheDir, `caches-lmdb`), + path.join(TEMP_CACHE_DIR, `caches-lmdb`), + ], + [path.join(cacheDir, `data`), path.join(TEMP_CACHE_DIR, `data`)], + ] + + console.log(`Preparing Gatsby filesystem`, { + from: cacheDir, + to: TEMP_CACHE_DIR, + rewrites, + }) + // Alias the cache dir paths to the temp dir + const lfs = link(fs, rewrites) as typeof import("fs") + + // linkfs doesn't pass across the `native` prop, which graceful-fs needs + for (const key in lfs) { + if (Object.hasOwnProperty.call(fs[key], `native`)) { + lfs[key].native = fs[key].native + } + } + + const dbPath = path.join(TEMP_CACHE_DIR, `data`, `datastore`) + + // 'promises' is not initially linked within the 'linkfs' + // package, and is needed by underlying Gatsby code (the + // @graphql-tools/code-file-loader) + lfs.promises = link(fs.promises, rewrites) + + // Gatsby uses this instead of fs if present + // eslint-disable-next-line no-underscore-dangle + global._fsWrapper = lfs + + const dir = `data` + if ( + !process.env.NETLIFY_LOCAL && + fs.existsSync(path.join(TEMP_CACHE_DIR, dir)) + ) { + console.log(`directory already exists`) + return dbPath + } + console.log(`Start copying ${dir}`) + + fs.copySync(path.join(cacheDir, dir), path.join(TEMP_CACHE_DIR, dir)) + console.log(`End copying ${dir}`) + + return dbPath + } +} + +const dbPath = setupFsWrapper() // using require instead of import here for now because of type hell + import path doesn't exist in current context // as this file will be copied elsewhere @@ -13,7 +85,7 @@ const { getData, renderPageData, renderHTML } = require(`./index`) as typeof import("./entry") const graphqlEngine = new GraphQLEngine({ - dbPath: path.join(__dirname, `..`, `data`, `datastore`), + dbPath, }) function reverseFixedPagePath(pageDataRequestPath: string): string { diff --git a/yarn.lock b/yarn.lock index a0d005e1d11f6..8df0bc9775745 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15129,6 +15129,11 @@ lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" +linkfs@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/linkfs/-/linkfs-2.1.0.tgz#5cc774ad8ed6b0aae5a858bd67e3334cc300a917" + integrity sha512-kmsGcmpvjStZ0ATjuHycBujtNnXiZR28BTivEu0gAMDTT7GEyodcK6zSRtu6xsrdorrPZEIN380x7BD7xEYkew== + linkify-it@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" From 05e82f63537eb0e9722b00f38e6e8978a9736b23 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 12 Jun 2023 10:38:53 +0200 Subject: [PATCH 064/161] inject functions matchPath into function bundle and generate req.params inside of it --- .../internal-plugins/functions/gatsby-node.ts | 15 +++++++- .../functions/match-path-webpack-loader.ts | 35 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts diff --git a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts index debe90f7e4e5e..c18d2a5edf4bb 100644 --- a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts +++ b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts @@ -271,7 +271,13 @@ const createWebpackConfig = async ({ parsedFile.name ) - entries[compiledNameWithoutExtension] = functionObj.originalAbsoluteFilePath + let entryToTheFunction = functionObj.originalAbsoluteFilePath + // if function has dynamic path, we inject it with webpack loader via query param + // see match-path-webpack-loader.ts for more info + if (functionObj.matchPath) { + entryToTheFunction += `?matchPath=` + functionObj.matchPath + } + entries[compiledNameWithoutExtension] = entryToTheFunction }) activeEntries = entries @@ -318,6 +324,13 @@ const createWebpackConfig = async ({ // Webpack expects extensions when importing ESM modules as that's what the spec describes. // Not all libraries have adapted so we don't enforce its behaviour // @see https://github.com/webpack/webpack/issues/11467 + { + test: /\.[tj]sx?$/, + resourceQuery: /matchPath/, + use: { + loader: require.resolve(`./match-path-webpack-loader`), + }, + }, { test: /\.mjs$/i, resolve: { diff --git a/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts b/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts new file mode 100644 index 0000000000000..58a8f28874304 --- /dev/null +++ b/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts @@ -0,0 +1,35 @@ +import type { LoaderDefinition } from "webpack" + +const MatchPathLoader: LoaderDefinition = async function () { + const params = new URLSearchParams(this.resourceQuery) + const matchPath = params.get(`matchPath`) + + const modulePath = this.resourcePath + + return /* javascript */ ` + const preferDefault = m => (m && m.default) || m + + const functionToExecute = preferDefault(require('${modulePath}')); + const matchPath = '${matchPath}'; + const { match: reachMatch } = require('@gatsbyjs/reach-router'); + + module.exports = function(req, res) { + // TODO: strip path prefix + const functionPath = req.originalUrl.replace('/api/', ''); + + const matchResult = reachMatch(matchPath, functionPath) + if (matchResult) { + req.params = matchResult.params + if (req.params['*']) { + // Backwards compatability for v3 + // TODO remove in v5 + req.params['0'] = req.params['*'] + } + } + + return functionToExecute(req, res); + } + ` +} + +export default MatchPathLoader From 12410e3e10fa4280ad4ac0b8f95af7db227982a8 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 12 Jun 2023 12:58:07 +0200 Subject: [PATCH 065/161] serve api from path prefixed path as well --- packages/gatsby/src/commands/serve.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/gatsby/src/commands/serve.ts b/packages/gatsby/src/commands/serve.ts index c4a79fdab3617..635c5551aef22 100644 --- a/packages/gatsby/src/commands/serve.ts +++ b/packages/gatsby/src/commands/serve.ts @@ -172,14 +172,15 @@ module.exports = async (program: IServeProgram): Promise => { } if (functions) { - app.use( - `/api/*`, - ...functionMiddlewares({ - getFunctions(): Array { - return functions - }, - }) - ) + const functionMiddlewaresInstances = functionMiddlewares({ + getFunctions(): Array { + return functions + }, + }) + + router.use(`/api/*`, ...functionMiddlewaresInstances) + // TODO(v6) remove handler from app and only keep it on router (router is setup on pathPrefix, while app is always root) + app.use(`/api/*`, ...functionMiddlewaresInstances) } // Handle SSR & DSG Pages From d603ebb2758a0868033b786fd60829430f668945 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 12 Jun 2023 13:28:31 +0200 Subject: [PATCH 066/161] add path prefix stripping in function wrapper --- .../src/internal-plugins/functions/gatsby-node.ts | 12 +++++++++++- .../functions/match-path-webpack-loader.ts | 10 ++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts index c18d2a5edf4bb..eb59b96bcb0ac 100644 --- a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts +++ b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts @@ -205,6 +205,11 @@ const createWebpackConfig = async ({ JSON.stringify(knownFunctions, null, 4) ) + const { + config: { pathPrefix }, + program, + } = store.getState() + // Load environment variables from process.env.* and .env.* files. // Logic is shared with webpack.config.js @@ -373,7 +378,12 @@ const createWebpackConfig = async ({ ], }, plugins: [ - new webpack.DefinePlugin(processEnvVars), + new webpack.DefinePlugin({ + PREFIX_TO_STRIP: JSON.stringify( + program.prefixPaths ? pathPrefix?.replace(/(^\/+|\/+$)/g, ``) : `` + ), + ...processEnvVars, + }), new webpack.IgnorePlugin({ checkResource(resource): boolean { if (resource === `lmdb`) { diff --git a/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts b/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts index 58a8f28874304..bc4657bacb9b1 100644 --- a/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts +++ b/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts @@ -14,11 +14,13 @@ const MatchPathLoader: LoaderDefinition = async function () { const { match: reachMatch } = require('@gatsbyjs/reach-router'); module.exports = function(req, res) { - // TODO: strip path prefix - const functionPath = req.originalUrl.replace('/api/', ''); - + let functionPath = req.originalUrl + + functionPath = functionPath.replace(new RegExp('^/*' + PREFIX_TO_STRIP), '') + functionPath = functionPath.replace(new RegExp('^/*api/?'), '') + const matchResult = reachMatch(matchPath, functionPath) - if (matchResult) { + if (matchResult) { req.params = matchResult.params if (req.params['*']) { // Backwards compatability for v3 From a3db59732a34b8c1f96e09ab2c6361a4b91522c9 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 12 Jun 2023 15:39:32 +0200 Subject: [PATCH 067/161] add cache store and restore in gatsby-adapter-netlify --- packages/gatsby-adapter-netlify/src/index.ts | 35 ++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 438e4e296c16d..f4e294e5c807a 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -13,15 +13,46 @@ import { prepareFunctionVariants } from "./lambda-handler.mjs" // @ts-ignore same as above import { handleRoutesManifest } from "./route-handler.mjs" +interface INetlifyCacheUtils { + restore: (paths: Array) => Promise + save: (paths: Array) => Promise +} + +async function getCacheUtils(): Promise { + if (process.env.NETLIFY) { + const CACHE_DIR = `/opt/build/cache` + return (await import(`@netlify/cache-utils`)).bindOpts({ + cacheDir: CACHE_DIR, + }) + } + return undefined +} + const createNetlifyAdapter: AdapterInit = () => { return { name: `gatsby-adapter-netlify`, cache: { - restore({ directories, reporter }): void { + async restore({ directories, reporter }): Promise { reporter.info(`[gatsby-adapter-netlify] cache.restore() ${directories}`) + const utils = await getCacheUtils() + if (utils) { + reporter.info( + `[gatsby-adapter-netlify] using @netlify/cache-utils restore` + ) + return await utils.restore(directories) + } + + return false }, - store({ directories, reporter }): void { + async store({ directories, reporter }): Promise { reporter.info(`[gatsby-adapter-netlify] cache.store() ${directories}`) + const utils = await getCacheUtils() + if (utils) { + reporter.info( + `[gatsby-adapter-netlify] using @netlify/cache-utils save` + ) + await utils.save(directories) + } }, }, async adapt({ routesManifest, functionsManifest }): Promise { From fd1e6107e4e3ea3cd5253fb8cb109af5c36f439c Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 12 Jun 2023 15:24:38 +0200 Subject: [PATCH 068/161] adjust internal 'lambda' name to 'function' --- packages/gatsby/src/utils/adapter/manager.ts | 10 +-- packages/gatsby/src/utils/adapter/types.ts | 64 ++++++++++++++------ 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index d33a0d488d09e..4a40018576753 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -10,7 +10,7 @@ import { RoutesManifest, Route, IAdapterManager, - ILambdaRoute, + IFunctionRoute, IAdapter, } from "./types" import { store, readState } from "../../redux" @@ -167,7 +167,7 @@ function getRoutesManifest(): RoutesManifest { route.path = `/${route.path}` } - if (route.type !== `lambda`) { + if (route.type !== `function`) { route.headers = createHeaders(route.path, route.headers) } @@ -224,8 +224,8 @@ function getRoutesManifest(): RoutesManifest { headers: STATIC_PAGE_HEADERS, }) } else { - const commonFields: Omit = { - type: `lambda`, + const commonFields: Omit = { + type: `function`, functionId: `ssr-engine`, } @@ -305,7 +305,7 @@ function getRoutesManifest(): RoutesManifest { maybeDropNamedPartOfWildcard(functionInfo.matchPath) ?? functionInfo.functionRoute }`, - type: `lambda`, + type: `function`, functionId: functionInfo.functionId, }) } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index cbbde37d44561..1c19f0c4273a7 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -19,46 +19,64 @@ interface IStaticRoute extends IBaseRoute { * Location of the file that should be served for this route. */ filePath: string + /** + * HTTP headers that should be set for this route. + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/headers/ + */ headers: IHeader["headers"] } -export interface ILambdaRoute extends IBaseRoute { - type: `lambda` +export interface IFunctionRoute extends IBaseRoute { + type: `function` /** - * Identifier of the function. Definition of that function is in function manifest. - * Definition of function is not left directly here as some lambdas will be shared for multiple routes - such as DSG/SSR engine. + * Unique identifier of this function. Corresponds to the `functionId` inside the `functionsManifest`. + * Some functions will be shared for multiple routes, e.g. SSR or DSG functions. */ functionId: string /** - * If `cache` is true, response of lambda should be cached for current deployed and served on subsequent requests for this route. + * If `cache` is true, response of function should be cached for current deployment and served on subsequent requests for this route. */ cache?: true } +/** + * Redirects are being created through the `createRedirect` action. + * @see https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect + */ interface IRedirectRoute extends IBaseRoute { type: `redirect` + /** + * The redirect should happen from `path` to `toPath`. + */ toPath: string + /** + * HTTP status code that should be used for this redirect. + */ status: HttpStatusCode - ignoreCase: boolean // this is not supported by Netlify, but is supported by Gatsby ... + ignoreCase?: boolean + /** + * HTTP headers that should be used for this redirect. + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/headers/ + */ headers: IHeader["headers"] [key: string]: unknown } -export type Route = IStaticRoute | ILambdaRoute | IRedirectRoute +export type Route = IStaticRoute | IFunctionRoute | IRedirectRoute export type RoutesManifest = Array export interface IFunctionDefinition { /** - * Identifier of the function. Referenced in routes manifest in lambda routes. + * Unique identifier of this function. Corresponds to the `functionId` inside the `routesManifest`. */ functionId: string /** - * Path to function entrypoint that will be used to create lambda. + * Path to function entrypoint that will be used to create function. */ pathToEntryPoint: string /** - * List of all required files that this function needs to run + * List of all required files that this function needs to run. */ requiredFiles: Array } @@ -68,7 +86,7 @@ export type FunctionsManifest = Array interface IDefaultContext { /** * Reporter instance that can be used to log messages to terminal. - * Read its [API documentation](https://www.gatsbyjs.com/docs/reference/config-files/node-api-helpers/#reporter) + * @see https://www.gatsbyjs.com/docs/reference/config-files/node-api-helpers/#reporter */ reporter: typeof reporter } @@ -89,24 +107,26 @@ export interface IAdapter { name: string cache?: { /** - * Hook to restore .cache and public directories from previous builds. Executed very early on in the build process. - * If `false` is returned gatsby will skip trying to rehydrate state from fs. + * Hook to restore `directories` from previous builds. This is executed very early on in the build process. If `false` is returned Gatsby will skip its cache restoration. */ restore: ( context: ICacheContext ) => Promise | boolean | void /** - * Hook to store .cache and public directories from previous builds. Executed as one of last steps in build process. + * Hook to store `directories` for the current build. Executed as one of the last steps in the build process. */ store: (context: ICacheContext) => Promise | void } /** - * Hook to prepare platform specific deployment of the build. Executed as one of last steps in build process. - * Routes and Functions manifests are being passed in as arguments and implementation should configure: - * - headers for static assets - * - redirects and rewrites (both user defined ones as well as anything needed for lambda execution) - * - wrap lambda functions with platform specific code if needed (produced ones will be express-like route handlers) - * - possibly upload static assets to CDN (unless platform is configured to just deploy "public" dir, in which case this will be skipped) + * Hook to take Gatsby’s output and preparing it for deployment on the adapter’s platform. Executed as one of the last steps in the build process. + * + * The `adapt` hook should do the following things: + * - Apply HTTP headers to assets + * - Apply redirects and rewrites. The adapter should can also create its own redirects/rewrites if necessary (e.g. mapping serverless functions to internal URLs). + * - Wrap serverless functions coming from Gatsby with platform-specific code (if necessary). Gatsby will produce [Express-like](https://expressjs.com/) handlers. + * - Possibly upload assets to CDN + * + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ */ adapt: (context: IAdaptContext) => Promise | void // TODO: should we have "private storage" handling defining a way to "upload" and "download those private assets? @@ -114,6 +134,10 @@ export interface IAdapter { // current limitation in Netlify's implementation of DSG/SSR ( https://github.com/netlify/netlify-plugin-gatsby#caveats ) } +/** + * Adapter initialization function that returns an instance of the adapter. + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ + */ export type AdapterInit> = ( adapterOptions?: T ) => IAdapter From 7b05c8935314ec7cc473cb88fb5ce7bb384433cf Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 12 Jun 2023 15:45:52 +0200 Subject: [PATCH 069/161] format lambda-handler --- .../src/lambda-handler.ts | 308 ++++++++++-------- 1 file changed, 165 insertions(+), 143 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index a054a6a09f36f..b58ddd45ade2e 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -3,6 +3,28 @@ import type { IFunctionDefinition } from "gatsby/src/utils/adapter/types" import fs from "fs-extra" import * as path from "path" +interface INetlifyFunctionConfig { + externalNodeModules?: Array + includedFiles?: Array + includedFilesBasePath?: string + ignoredNodeModules?: Array + nodeBundler?: "esbuild" | "esbuild_zisi" | "nft" | "zisi" | "none" + nodeSourcemap?: boolean + nodeVersion?: string + processDynamicNodeImports?: boolean + rustTargetDirectory?: string + schedule?: string + zipGo?: boolean + name?: string + generator?: string + nodeModuleFormat?: "cjs" | "esm" +} + +interface INetlifyFunctionManifest { + config: INetlifyFunctionConfig + version: number +} + async function prepareFunction( fun: IFunctionDefinition, odbfunctionName?: string @@ -24,7 +46,7 @@ async function prepareFunction( await fs.ensureDir(internalFunctionsDir) - const functionManifest: any = { + const functionManifest: INetlifyFunctionManifest = { config: { includedFiles: fun.requiredFiles.map(file => file.replace(/\[/g, `*`).replace(/]/g, `*`) @@ -40,19 +62,19 @@ async function prepareFunction( ) const handlerSource = /* javascript */ ` - const Stream = require("stream") - const http = require("http") - const { Buffer } = require("buffer") - const cookie = require("cookie") - ${isODB ? `const { builder } = require("@netlify/functions")` : ``} +const Stream = require("stream") +const http = require("http") +const { Buffer } = require("buffer") +const cookie = require("cookie") +${isODB ? `const { builder } = require("@netlify/functions")` : ``} - const preferDefault = m => (m && m.default) || m +const preferDefault = m => (m && m.default) || m - const functionModule = require("./../../../${fun.pathToEntryPoint}") +const functionModule = require("./../../../${fun.pathToEntryPoint}") - const functionHandler = preferDefault(functionModule) +const functionHandler = preferDefault(functionModule) - const createRequestObject = ({ event, context }) => { +const createRequestObject = ({ event, context }) => { const { path = "", multiValueQueryStringParameters, @@ -95,144 +117,144 @@ async function prepareFunction( } const createResponseObject = ({ onResEnd }) => { - const response = { - isBase64Encoded: true, - multiValueHeaders: {}, - }; - const res = new Stream(); - Object.defineProperty(res, 'statusCode', { - get() { - return response.statusCode; - }, - set(statusCode) { - response.statusCode = statusCode; - }, - }); - res.headers = { 'content-type': 'text/html; charset=utf-8' }; - res.writeHead = (status, headers) => { - response.statusCode = status; - if (headers) { - res.headers = Object.assign(res.headers, headers); - } - // Return res object to allow for chaining - // Fixes: https://github.com/netlify/next-on-netlify/pull/74 - return res; - }; - res.write = (chunk) => { - if (!response.body) { - response.body = Buffer.from(''); - } - response.body = Buffer.concat([ - Buffer.isBuffer(response.body) - ? response.body - : Buffer.from(response.body), - Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), - ]); - return true; - }; - res.setHeader = (name, value) => { - res.headers[name.toLowerCase()] = value; - return res; - }; - res.removeHeader = (name) => { - delete res.headers[name.toLowerCase()]; - }; - res.getHeader = (name) => res.headers[name.toLowerCase()]; - res.getHeaders = () => res.headers; - res.hasHeader = (name) => Boolean(res.getHeader(name)); - res.end = (text) => { - if (text) - res.write(text); - if (!res.statusCode) { - res.statusCode = 200; - } - if (response.body) { - response.body = Buffer.from(response.body).toString('base64'); - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore These types are a mess, and need sorting out - response.multiValueHeaders = res.headers; - res.writeHead(response.statusCode); - // Convert all multiValueHeaders into arrays - for (const key of Object.keys(response.multiValueHeaders)) { - const header = response.multiValueHeaders[key]; - if (!Array.isArray(header)) { - response.multiValueHeaders[key] = [header]; - } - } - res.finished = true; - res.writableEnded = true; - // Call onResEnd handler with the response object - onResEnd(response); - return res; - }; - // Gatsby Functions additions - res.send = (data) => { - if (res.finished) { - return res; - } - if (typeof data === 'number') { - return res - .status(data) - .setHeader('content-type', 'text/plain; charset=utf-8') - .end(statuses_1.default.message[data] || String(data)); - } - if (typeof data === 'boolean' || typeof data === 'object') { - if (Buffer.isBuffer(data)) { - res.setHeader('content-type', 'application/octet-Stream'); - } - else if (data !== null) { - return res.json(data); - } - } - res.end(data); - return res; - }; - res.json = (data) => { - if (res.finished) { - return res; - } - res.setHeader('content-type', 'application/json'); - res.end(JSON.stringify(data)); - return res; - }; - res.status = (code) => { - const numericCode = Number.parseInt(code); - if (!Number.isNaN(numericCode)) { - response.statusCode = numericCode; - } - return res; - }; - res.redirect = (statusCodeOrUrl, url) => { - let statusCode = statusCodeOrUrl; - let Location = url; - if (!url && typeof statusCodeOrUrl === 'string') { - Location = statusCodeOrUrl; - statusCode = 302; - } - res.writeHead(statusCode, { Location }); - res.end(); - return res; - }; + const response = { + isBase64Encoded: true, + multiValueHeaders: {}, + }; + const res = new Stream(); + Object.defineProperty(res, 'statusCode', { + get() { + return response.statusCode; + }, + set(statusCode) { + response.statusCode = statusCode; + }, + }); + res.headers = { 'content-type': 'text/html; charset=utf-8' }; + res.writeHead = (status, headers) => { + response.statusCode = status; + if (headers) { + res.headers = Object.assign(res.headers, headers); + } + // Return res object to allow for chaining + // Fixes: https://github.com/netlify/next-on-netlify/pull/74 + return res; + }; + res.write = (chunk) => { + if (!response.body) { + response.body = Buffer.from(''); + } + response.body = Buffer.concat([ + Buffer.isBuffer(response.body) + ? response.body + : Buffer.from(response.body), + Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk), + ]); + return true; + }; + res.setHeader = (name, value) => { + res.headers[name.toLowerCase()] = value; + return res; + }; + res.removeHeader = (name) => { + delete res.headers[name.toLowerCase()]; + }; + res.getHeader = (name) => res.headers[name.toLowerCase()]; + res.getHeaders = () => res.headers; + res.hasHeader = (name) => Boolean(res.getHeader(name)); + res.end = (text) => { + if (text) + res.write(text); + if (!res.statusCode) { + res.statusCode = 200; + } + if (response.body) { + response.body = Buffer.from(response.body).toString('base64'); + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore These types are a mess, and need sorting out + response.multiValueHeaders = res.headers; + res.writeHead(response.statusCode); + // Convert all multiValueHeaders into arrays + for (const key of Object.keys(response.multiValueHeaders)) { + const header = response.multiValueHeaders[key]; + if (!Array.isArray(header)) { + response.multiValueHeaders[key] = [header]; + } + } + res.finished = true; + res.writableEnded = true; + // Call onResEnd handler with the response object + onResEnd(response); return res; + }; + // Gatsby Functions additions + res.send = (data) => { + if (res.finished) { + return res; + } + if (typeof data === 'number') { + return res + .status(data) + .setHeader('content-type', 'text/plain; charset=utf-8') + .end(statuses_1.default.message[data] || String(data)); + } + if (typeof data === 'boolean' || typeof data === 'object') { + if (Buffer.isBuffer(data)) { + res.setHeader('content-type', 'application/octet-Stream'); + } + else if (data !== null) { + return res.json(data); + } + } + res.end(data); + return res; + }; + res.json = (data) => { + if (res.finished) { + return res; + } + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify(data)); + return res; + }; + res.status = (code) => { + const numericCode = Number.parseInt(code); + if (!Number.isNaN(numericCode)) { + response.statusCode = numericCode; + } + return res; + }; + res.redirect = (statusCodeOrUrl, url) => { + let statusCode = statusCodeOrUrl; + let Location = url; + if (!url && typeof statusCodeOrUrl === 'string') { + Location = statusCodeOrUrl; + statusCode = 302; + } + res.writeHead(statusCode, { Location }); + res.end(); + return res; + }; + return res; }; - const handler = async (event, context) => { - const req = createRequestObject({ event, context }) - - return new Promise(async resolve => { - try { - const res = createResponseObject({ onResEnd: resolve }) - await functionHandler(req, res) - } catch(error) { - console.error("Error executing " + event.path, error) - resolve({ statusCode: 500 }) - } - }) +const handler = async (event, context) => { + const req = createRequestObject({ event, context }) + + return new Promise(async resolve => { + try { + const res = createResponseObject({ onResEnd: resolve }) + await functionHandler(req, res) + } catch(error) { + console.error("Error executing " + event.path, error) + resolve({ statusCode: 500 }) } + }) +} - exports.handler = ${isODB ? `builder(handler)` : `handler`} - ` +exports.handler = ${isODB ? `builder(handler)` : `handler`} +` await fs.writeFile( path.join(internalFunctionsDir, `${functionId}.js`), From 591a26ba7ad282185d7a0ae7157698662e678870 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 12 Jun 2023 15:50:40 +0200 Subject: [PATCH 070/161] misc changes --- packages/gatsby/adapters.js | 1 + packages/gatsby/src/utils/adapter/init.ts | 2 +- packages/gatsby/src/utils/adapter/manager.ts | 2 +- packages/gatsby/src/utils/adapter/types.ts | 4 ++++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index 6d8dcc774a88c..de9196a229759 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -6,6 +6,7 @@ * If you want to create an adapter, please see: TODO * * @type {import("./src/utils/adapter/types").IAdapterManifestEntry} + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/zero-configuration-deployments/ */ const adaptersManifest = [ { diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index 2b201eb9c763c..3433e062c9df0 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -6,7 +6,7 @@ import { emptyDir, ensureDir, outputJson } from "fs-extra" import execa, { Options as ExecaOptions } from "execa" import { version as gatsbyVersion } from "gatsby/package.json" import { satisfies } from "semver" -import { AdapterInit } from "./types" +import type { AdapterInit } from "./types" import { preferDefault } from "../../bootstrap/prefer-default" import { getLatestAdapters } from "../get-latest-gatsby-files" diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 4a40018576753..6dc5215d534f4 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -4,7 +4,7 @@ import { generatePageDataPath } from "gatsby-core-utils/page-data" import { posix } from "path" import { sync as globSync } from "glob" import telemetry from "gatsby-telemetry" -import { +import type { FunctionsManifest, IAdaptContext, RoutesManifest, diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 1c19f0c4273a7..3c96b2825f68e 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -148,6 +148,10 @@ export interface IAdapterManager { adapt: () => Promise | void } +/** + * Types for gatsby/adapters.js + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/zero-configuration-deployments/ + */ export interface IAdapterManifestEntry { /** * Name of the adapter From d6565657be4ed2741457e5f37c25b6fed833c40a Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 12 Jun 2023 16:36:40 +0200 Subject: [PATCH 071/161] missing rename --- packages/gatsby-adapter-netlify/src/route-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index 4ad90642fe785..8d0c7e8e19316 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -34,7 +34,7 @@ export async function handleRoutesManifest( for (const route of routesManifest) { const fromPath = route.path.replace(/\*.*/, `*`) - if (route.type === `lambda`) { + if (route.type === `function`) { let functionName = route.functionId if (route.cache) { functionName = `${route.functionId}-odb` From d6b252cdc91018a6944e354e120afe7dcd9ed04d Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 12 Jun 2023 17:16:09 +0200 Subject: [PATCH 072/161] compile gatsby-adapter-netlify to cjs --- packages/gatsby-adapter-netlify/.babelrc | 2 +- packages/gatsby-adapter-netlify/package.json | 8 ++++---- packages/gatsby-adapter-netlify/src/index.ts | 8 ++------ packages/gatsby/src/utils/adapter/init.ts | 10 +++++++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/gatsby-adapter-netlify/.babelrc b/packages/gatsby-adapter-netlify/.babelrc index d191a28e60d5c..84af48678d3f0 100644 --- a/packages/gatsby-adapter-netlify/.babelrc +++ b/packages/gatsby-adapter-netlify/.babelrc @@ -3,7 +3,7 @@ [ "babel-preset-gatsby-package", { - "esm": true + "keepDynamicImports": ["./src/index.ts"] } ] ] diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index dcd55648d95eb..3a12803a5f971 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -2,16 +2,16 @@ "name": "gatsby-adapter-netlify", "version": "1.0.0", "description": "Gatsby adapter for Netlify", - "main": "dist/index.mjs", + "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { - ".": "./dist/index.mjs", + ".": "./dist/index.js", "./package.json": "./package.json" }, "scripts": { - "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\" --out-file-extension .mjs", + "build": "babel src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\"", "typegen": "rimraf --glob \"dist/**/*.d.ts\" && tsc --emitDeclarationOnly --declaration --declarationDir dist/", - "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\" --out-file-extension .mjs", + "watch": "babel -w src --out-dir dist/ --ignore \"**/__tests__\" --extensions \".ts\"", "prepare": "cross-env NODE_ENV=production npm run build && npm run typegen" }, "keywords": [ diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index f4e294e5c807a..6cde250f9ec1a 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -6,12 +6,8 @@ import type { AdapterInit } from "gatsby/src/utils/adapter/types" // eslint-disable-next-line @typescript-eslint/no-empty-interface interface INetlifyAdapterOptions {} -// @ts-ignore sigh, we compile to mjs, but it doesn't exist in source code, skipping extension result in error at runtime when -// loading this module because we need to supply mjs extension to actually load it. Adding extension makes typescript unhappy -// TODO: adjust build to convert import paths so typescript is happy and runtime actually works -import { prepareFunctionVariants } from "./lambda-handler.mjs" -// @ts-ignore same as above -import { handleRoutesManifest } from "./route-handler.mjs" +import { prepareFunctionVariants } from "./lambda-handler" +import { handleRoutesManifest } from "./route-handler" interface INetlifyCacheUtils { restore: (paths: Array) => Promise diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index 3433e062c9df0..f8f55d53fa510 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -98,9 +98,11 @@ export async function getAdapterInit(): Promise { `Reusing existing adapter ${adapterToUse.module} inside node_modules` ) - return preferDefault(await import(required)) as AdapterInit + // TODO: double preferDefault is most ceirtainly wrong - figure it out + return preferDefault(preferDefault(await import(required))) as AdapterInit } } catch (e) { + console.error(e) // no-op } @@ -116,7 +118,8 @@ export async function getAdapterInit(): Promise { `Reusing existing adapter ${adapterToUse.module} inside .cache/adapters` ) - return preferDefault(await import(required)) as AdapterInit + // TODO: double preferDefault is most ceirtainly wrong - figure it out + return preferDefault(preferDefault(await import(required))) as AdapterInit } } catch (e) { // no-op @@ -169,7 +172,8 @@ export async function getAdapterInit(): Promise { `Using installed adapter ${adapterToUse.module} inside .cache/adapters` ) - return preferDefault(await import(required)) as AdapterInit + // TODO: double preferDefault is most ceirtainly wrong - figure it out + return preferDefault(preferDefault(await import(required))) as AdapterInit } } catch (e) { installTimer.end() From 5f65225e915113a43ff10f605b8c213975257c45 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 13 Jun 2023 09:47:23 +0200 Subject: [PATCH 073/161] add generator field --- packages/gatsby-adapter-netlify/src/lambda-handler.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index b58ddd45ade2e..34ba46eeee72c 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -1,5 +1,7 @@ import type { IFunctionDefinition } from "gatsby/src/utils/adapter/types" +import packageJson from "gatsby-adapter-netlify/package.json" + import fs from "fs-extra" import * as path from "path" @@ -48,6 +50,7 @@ async function prepareFunction( const functionManifest: INetlifyFunctionManifest = { config: { + generator: `gatsby-adapter-netlify@${packageJson?.version ?? `unknown`}`, includedFiles: fun.requiredFiles.map(file => file.replace(/\[/g, `*`).replace(/]/g, `*`) ), From 27b1eff608a3de162c3b6f09d9f7e31fab4ec6b9 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 13 Jun 2023 12:35:19 +0200 Subject: [PATCH 074/161] use netlify adpter when NETLIFY or NETLIFY_LOCAL env var is defined --- packages/gatsby/adapters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index de9196a229759..150e33ab862f1 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -12,7 +12,7 @@ const adaptersManifest = [ { name: `Netlify`, module: `gatsby-adapter-netlify`, - test: () => !!process.env.NETLIFY, + test: () => !!process.env.NETLIFY || !!process.env.NETLIFY_LOCAL, versions: [ { gatsbyVersion: `^5.0.0`, From 94b9e9d2e74a9e8febf86a04d71b26f553edc44c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 13 Jun 2023 12:37:17 +0200 Subject: [PATCH 075/161] use headers from config for ssg/dsg --- .../gatsby/src/bootstrap/requires-writer.ts | 32 +--------- packages/gatsby/src/commands/serve.ts | 4 +- .../utils/adapter/__tests__/create-headers.ts | 59 +++---------------- .../src/utils/adapter/create-headers.ts | 12 ++-- .../src/utils/adapter/get-route-path.ts | 24 ++++++++ packages/gatsby/src/utils/adapter/manager.ts | 26 +++----- .../utils/page-ssr-module/bundle-webpack.ts | 1 + .../gatsby/src/utils/page-ssr-module/entry.ts | 40 ++++++++++++- .../src/utils/page-ssr-module/lambda.ts | 8 +-- packages/gatsby/src/utils/rank-route.ts | 30 ++++++++++ 10 files changed, 120 insertions(+), 116 deletions(-) create mode 100644 packages/gatsby/src/utils/adapter/get-route-path.ts create mode 100644 packages/gatsby/src/utils/rank-route.ts diff --git a/packages/gatsby/src/bootstrap/requires-writer.ts b/packages/gatsby/src/bootstrap/requires-writer.ts index bdad5b50a4bd1..7f596eed42282 100644 --- a/packages/gatsby/src/bootstrap/requires-writer.ts +++ b/packages/gatsby/src/bootstrap/requires-writer.ts @@ -12,6 +12,7 @@ import { } from "../utils/gatsby-webpack-virtual-modules" import { getPageMode } from "../utils/page-mode" import { devSSRWillInvalidate } from "../commands/build-html" +import { rankRoute } from "../utils/rank-route" const hasContentFilePath = (componentPath: string): boolean => componentPath.includes(`?__contentFilePath=`) @@ -27,37 +28,6 @@ interface IGatsbyPageMatchPath { matchPath: string | undefined } -// path ranking algorithm copied (with small adjustments) from `@reach/router` (internal util, not exported from the package) -// https://github.com/reach/router/blob/28a79e7fc3a3487cb3304210dc3501efb8a50eba/src/lib/utils.js#L216-L254 -const paramRe = /^:(.+)/ - -const SEGMENT_POINTS = 4 -const STATIC_POINTS = 3 -const DYNAMIC_POINTS = 2 -const SPLAT_PENALTY = 1 -const ROOT_POINTS = 1 - -const isRootSegment = (segment: string): boolean => segment === `` -const isDynamic = (segment: string): boolean => paramRe.test(segment) -const isSplat = (segment: string): boolean => segment === `*` - -const segmentize = (uri: string): Array => - uri - // strip starting/ending slashes - .replace(/(^\/+|\/+$)/g, ``) - .split(`/`) - -export const rankRoute = (path: string): number => - segmentize(path).reduce((score, segment) => { - score += SEGMENT_POINTS - if (isRootSegment(segment)) score += ROOT_POINTS - else if (isDynamic(segment)) score += DYNAMIC_POINTS - else if (isSplat(segment)) score -= SEGMENT_POINTS + SPLAT_PENALTY - else score += STATIC_POINTS - return score - }, 0) -// end of copied `@reach/router` internals - let lastHash: string | null = null export const resetLastHash = (): void => { diff --git a/packages/gatsby/src/commands/serve.ts b/packages/gatsby/src/commands/serve.ts index 635c5551aef22..3ce95bb8fa96c 100644 --- a/packages/gatsby/src/commands/serve.ts +++ b/packages/gatsby/src/commands/serve.ts @@ -224,7 +224,7 @@ module.exports = async (program: IServeProgram): Promise => { spanContext, }) const results = await renderPageData({ data, spanContext }) - if (page.mode === `SSR` && data.serverDataHeaders) { + if (data.serverDataHeaders) { for (const [name, value] of Object.entries( data.serverDataHeaders )) { @@ -274,7 +274,7 @@ module.exports = async (program: IServeProgram): Promise => { spanContext, }) const results = await renderHTML({ data, spanContext }) - if (page.mode === `SSR` && data.serverDataHeaders) { + if (data.serverDataHeaders) { for (const [name, value] of Object.entries( data.serverDataHeaders )) { diff --git a/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts b/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts index a6f13038e23bd..30b7204a441b0 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/create-headers.ts @@ -1,27 +1,4 @@ -import { store } from "../../../redux" import { createHeadersMatcher } from "../create-headers" -import type { IHeader } from "../../../redux/types" - -jest.mock(`../../../redux`, () => { - return { - emitter: { - on: jest.fn(), - }, - store: { - getState: jest.fn(), - }, - } -}) - -function mockHeaders(headers: Array): void { - ;(store.getState as jest.Mock).mockImplementation(() => { - return { - config: { - headers, - }, - } - }) -} describe(`createHeadersMatcher`, () => { beforeEach(() => { @@ -29,13 +6,7 @@ describe(`createHeadersMatcher`, () => { }) it(`returns default headers if no custom headers are defined`, () => { - ;(store.getState as jest.Mock).mockImplementation(() => { - return { - config: {}, - } - }) - - const matcher = createHeadersMatcher() + const matcher = createHeadersMatcher(undefined) const defaults = [ { @@ -50,15 +21,7 @@ describe(`createHeadersMatcher`, () => { }) it(`returns default headers if an empty array as headers is defined`, () => { - ;(store.getState as jest.Mock).mockImplementation(() => { - return { - config: { - headers: [], - }, - } - }) - - const matcher = createHeadersMatcher() + const matcher = createHeadersMatcher([]) const defaults = [ { @@ -73,7 +36,7 @@ describe(`createHeadersMatcher`, () => { }) it(`gracefully handles trailing slash inconsistencies`, () => { - mockHeaders([ + const matcher = createHeadersMatcher([ { source: `/some-path`, headers: [ @@ -93,7 +56,6 @@ describe(`createHeadersMatcher`, () => { ], }, ]) - const matcher = createHeadersMatcher() const defaults = [] @@ -105,7 +67,7 @@ describe(`createHeadersMatcher`, () => { }) it(`combines with non-overlapping keys`, () => { - mockHeaders([ + const matcher = createHeadersMatcher([ { source: `*`, headers: [ @@ -125,7 +87,6 @@ describe(`createHeadersMatcher`, () => { ], }, ]) - const matcher = createHeadersMatcher() const defaults = [ { @@ -144,7 +105,7 @@ describe(`createHeadersMatcher`, () => { }) it(`combines with overlapping keys`, () => { - mockHeaders([ + const matcher = createHeadersMatcher([ { source: `*`, headers: [ @@ -164,7 +125,6 @@ describe(`createHeadersMatcher`, () => { ], }, ]) - const matcher = createHeadersMatcher() const defaults = [ { @@ -179,7 +139,7 @@ describe(`createHeadersMatcher`, () => { }) it(`combines with overlapping & non-overlapping keys`, () => { - mockHeaders([ + const matcher = createHeadersMatcher([ { source: `*`, headers: [ @@ -207,7 +167,6 @@ describe(`createHeadersMatcher`, () => { ], }, ]) - const matcher = createHeadersMatcher() const defaults = [ { @@ -243,7 +202,7 @@ describe(`createHeadersMatcher`, () => { }) it(`static wins over dynamic`, () => { - mockHeaders([ + const matcher = createHeadersMatcher([ { source: `*`, headers: [ @@ -281,7 +240,6 @@ describe(`createHeadersMatcher`, () => { ], }, ]) - const matcher = createHeadersMatcher() const defaults = [] @@ -296,7 +254,7 @@ describe(`createHeadersMatcher`, () => { }) it(`dynamic entries have correct specificity`, () => { - mockHeaders([ + const matcher = createHeadersMatcher([ { source: `*`, headers: [ @@ -325,7 +283,6 @@ describe(`createHeadersMatcher`, () => { ], }, ]) - const matcher = createHeadersMatcher() const defaults = [] diff --git a/packages/gatsby/src/utils/adapter/create-headers.ts b/packages/gatsby/src/utils/adapter/create-headers.ts index ee984d01ddadc..65605dc3b0d97 100644 --- a/packages/gatsby/src/utils/adapter/create-headers.ts +++ b/packages/gatsby/src/utils/adapter/create-headers.ts @@ -1,7 +1,6 @@ import { match } from "@gatsbyjs/reach-router" import type { IHeader } from "../../redux/types" -import { store } from "../../redux" -import { rankRoute } from "../../bootstrap/requires-writer" +import { rankRoute } from "../rank-route" type Headers = IHeader["headers"] interface IHeaderWithScore extends IHeader { @@ -12,12 +11,9 @@ interface IHeaderWithScore extends IHeader { const normalizePath = (input: string): string => input.endsWith(`/`) ? input : `${input}/` -export const createHeadersMatcher = (): (( - path: string, - defaultHeaders: Headers -) => Headers) => { - const { headers } = store.getState().config - +export const createHeadersMatcher = ( + headers: Array | undefined +): ((path: string, defaultHeaders: Headers) => Headers) => { // Split the incoming user headers into two buckets: // - dynamicHeaders: Headers with dynamic paths (e.g. /* or /:tests) // - staticHeaders: Headers with fully static paths (e.g. /static/) diff --git a/packages/gatsby/src/utils/adapter/get-route-path.ts b/packages/gatsby/src/utils/adapter/get-route-path.ts new file mode 100644 index 0000000000000..a021a9ea14687 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/get-route-path.ts @@ -0,0 +1,24 @@ +import { IGatsbyFunction, IGatsbyPage } from "../../internal" + +function maybeDropNamedPartOfWildcard( + path: string | undefined +): string | undefined { + if (!path) { + return path + } + + return path.replace(/\*.+$/, `*`) +} + +export function getRoutePathFromPage(page: IGatsbyPage): string { + return maybeDropNamedPartOfWildcard(page.matchPath) ?? page.path +} + +export function getRoutePathFromFunction( + functionInfo: IGatsbyFunction +): string { + return ( + maybeDropNamedPartOfWildcard(functionInfo.matchPath) ?? + functionInfo.functionRoute + ) +} diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 6dc5215d534f4..881ac3c8def6e 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -27,7 +27,11 @@ import { import { createHeadersMatcher } from "./create-headers" import { HTTP_STATUS_CODE } from "../../redux/types" import type { IHeader } from "../../redux/types" -import { rankRoute } from "../../bootstrap/requires-writer" +import { rankRoute } from "../rank-route" +import { + getRoutePathFromFunction, + getRoutePathFromPage, +} from "./get-route-path" function noOpAdapterManager(): IAdapterManager { return { @@ -132,16 +136,6 @@ export async function initAdapterManager(): Promise { } } -function maybeDropNamedPartOfWildcard( - path: string | undefined -): string | undefined { - if (!path) { - return path - } - - return path.replace(/\*.+$/, `*`) -} - let webpackAssets: Set | undefined export function setWebpackAssets(assets: Set): void { webpackAssets = assets @@ -151,7 +145,7 @@ type RouteWithScore = { score: number } & Route function getRoutesManifest(): RoutesManifest { const routes: Array = [] - const createHeaders = createHeadersMatcher() + const createHeaders = createHeadersMatcher(store.getState().config.headers) const fileAssets = new Set( globSync(`**/**`, { @@ -203,8 +197,7 @@ function getRoutesManifest(): RoutesManifest { // routes - pages - static (SSG) or lambda (DSG/SSR) for (const page of store.getState().pages.values()) { - const htmlRoutePath = - maybeDropNamedPartOfWildcard(page.matchPath) ?? page.path + const htmlRoutePath = getRoutePathFromPage(page) const pageDataRoutePath = generatePageDataPath(``, htmlRoutePath) const pageMode = getPageMode(page) @@ -301,10 +294,7 @@ function getRoutesManifest(): RoutesManifest { // function routes for (const functionInfo of store.getState().functions.values()) { addRoute({ - path: `/api/${ - maybeDropNamedPartOfWildcard(functionInfo.matchPath) ?? - functionInfo.functionRoute - }`, + path: `/api/${getRoutePathFromFunction(functionInfo)}`, type: `function`, functionId: functionInfo.functionId, }) diff --git a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts index 294724f435949..a84a7ad1d4829 100644 --- a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts +++ b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts @@ -187,6 +187,7 @@ export async function createPageSSRBundle({ plugins: [ new webpack.DefinePlugin({ INLINED_TEMPLATE_TO_DETAILS: JSON.stringify(toInline), + INLINED_HEADERS_CONFIG: JSON.stringify(state.config.headers), WEBPACK_COMPILATION_HASH: JSON.stringify(webpackCompilationHash), GATSBY_SLICES: JSON.stringify(slicesStateObject), GATSBY_SLICES_BY_TEMPLATE: JSON.stringify(slicesByTemplateStateObject), diff --git a/packages/gatsby/src/utils/page-ssr-module/entry.ts b/packages/gatsby/src/utils/page-ssr-module/entry.ts index 1069d24c51441..e90e2375a0fad 100644 --- a/packages/gatsby/src/utils/page-ssr-module/entry.ts +++ b/packages/gatsby/src/utils/page-ssr-module/entry.ts @@ -5,7 +5,12 @@ import "../engines-fs-provider" // just types - those should not be bundled import type { GraphQLEngine } from "../../schema/graphql-engine/entry" import type { IExecutionResult } from "../../query/types" -import type { IGatsbyPage, IGatsbySlice, IGatsbyState } from "../../redux/types" +import type { + IGatsbyPage, + IGatsbySlice, + IGatsbyState, + IHeader, +} from "../../redux/types" import { IGraphQLTelemetryRecord } from "../../schema/type-definitions" import type { IScriptsAndStyles } from "../client-assets-for-template" import type { IPageDataWithQueryResult, ISliceData } from "../page-data" @@ -26,6 +31,9 @@ import reporter from "gatsby-cli/lib/reporter" import { initTracer } from "../tracer" import { getCodeFrame } from "../../query/graphql-errors-codeframe" import { ICollectedSlice } from "../babel/find-slices" +import { createHeadersMatcher } from "../adapter/create-headers" +import { STATIC_PAGE_HEADERS } from "../adapter/constants" +import { getRoutePathFromPage } from "../adapter/get-route-path" export interface ITemplateDetails { query: string @@ -37,6 +45,10 @@ export interface ISSRData { page: IGatsbyPage templateDetails: ITemplateDetails potentialPagePath: string + /** + * This is no longer really just serverDataHeaders, as we add headers + * from user defined in gatsby-config + */ serverDataHeaders?: Record serverDataStatus?: number searchString: string @@ -46,6 +58,7 @@ export interface ISSRData { // with DefinePlugin declare global { const INLINED_TEMPLATE_TO_DETAILS: Record + const INLINED_HEADERS_CONFIG: Array | undefined const WEBPACK_COMPILATION_HASH: string const GATSBY_SLICES_SCRIPT: string } @@ -58,6 +71,8 @@ type MaybePhantomActivity = | ReturnType | undefined +const createHeaders = createHeadersMatcher(INLINED_HEADERS_CONFIG) + export async function getData({ pathName, graphqlEngine, @@ -210,6 +225,27 @@ export async function getData({ } results.pageContext = page.context + const serverDataHeaders = {} + + // get headers from defaults and config + const headersFromConfig = createHeaders( + getRoutePathFromPage(page), + STATIC_PAGE_HEADERS + ) + // convert headers array to object + for (const header of headersFromConfig) { + serverDataHeaders[header.key] = header.value + } + + if (serverData?.headers) { + // add headers from getServerData to object (which will overwrite headers from config if overlapping) + for (const [headerKey, headerValue] of Object.entries( + serverData.headers + )) { + serverDataHeaders[headerKey] = headerValue + } + } + let searchString = `` if (req?.query) { @@ -230,7 +266,7 @@ export async function getData({ page, templateDetails, potentialPagePath, - serverDataHeaders: serverData?.headers, + serverDataHeaders, serverDataStatus: serverData?.status, searchString, } diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts index f91a2ed6f4683..656a65f6b8563 100644 --- a/packages/gatsby/src/utils/page-ssr-module/lambda.ts +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -128,10 +128,10 @@ function setStatusAndHeaders({ if (data.serverDataStatus) { res.status(data.serverDataStatus) } - if (data.serverDataHeaders) { - for (const [name, value] of Object.entries(data.serverDataHeaders)) { - res.setHeader(name, value) - } + } + if (data.serverDataHeaders) { + for (const [name, value] of Object.entries(data.serverDataHeaders)) { + res.setHeader(name, value) } } } diff --git a/packages/gatsby/src/utils/rank-route.ts b/packages/gatsby/src/utils/rank-route.ts new file mode 100644 index 0000000000000..ef0b7a3af0c00 --- /dev/null +++ b/packages/gatsby/src/utils/rank-route.ts @@ -0,0 +1,30 @@ +// path ranking algorithm copied (with small adjustments) from `@reach/router` (internal util, not exported from the package) +// https://github.com/reach/router/blob/28a79e7fc3a3487cb3304210dc3501efb8a50eba/src/lib/utils.js#L216-L254 +const paramRe = /^:(.+)/ + +const SEGMENT_POINTS = 4 +const STATIC_POINTS = 3 +const DYNAMIC_POINTS = 2 +const SPLAT_PENALTY = 1 +const ROOT_POINTS = 1 + +const isRootSegment = (segment: string): boolean => segment === `` +const isDynamic = (segment: string): boolean => paramRe.test(segment) +const isSplat = (segment: string): boolean => segment === `*` + +const segmentize = (uri: string): Array => + uri + // strip starting/ending slashes + .replace(/(^\/+|\/+$)/g, ``) + .split(`/`) + +export const rankRoute = (path: string): number => + segmentize(path).reduce((score, segment) => { + score += SEGMENT_POINTS + if (isRootSegment(segment)) score += ROOT_POINTS + else if (isDynamic(segment)) score += DYNAMIC_POINTS + else if (isSplat(segment)) score -= SEGMENT_POINTS + SPLAT_PENALTY + else score += STATIC_POINTS + return score + }, 0) +// end of copied `@reach/router` internals From 80c01428c49dcb8806086fe6f488ef5a3b4a1344 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 13 Jun 2023 16:56:45 +0200 Subject: [PATCH 076/161] allow specyfing different lmdb binary than current process, use abi83 if adapters are used (it works on node14, 16 and 18) --- .../gatsby/src/schema/graphql-engine/bundle-webpack.ts | 6 ++++++ .../src/schema/graphql-engine/lmdb-bundling-patch.ts | 7 ++----- packages/gatsby/src/utils/adapter/manager.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts index 4a9a1a20932c5..8880e74624d0f 100644 --- a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts +++ b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts @@ -11,6 +11,7 @@ import reporter from "gatsby-cli/lib/reporter" import { schemaCustomizationAPIs } from "./print-plugins" import type { GatsbyNodeAPI } from "../../redux/types" import * as nodeApis from "../../utils/api-node-docs" +import { isUsingAdapter } from "../../utils/adapter/manager" type Reporter = typeof reporter @@ -119,6 +120,11 @@ export async function createGraphqlEngineBundle( assetRelocatorUseEntry, { loader: require.resolve(`./lmdb-bundling-patch`), + options: { + forcedBinaryLocation: isUsingAdapter() + ? `../../@lmdb/lmdb-${process.platform}-${process.arch}/node.abi83.glibc.node` + : undefined, + }, }, ], }, diff --git a/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts b/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts index 492195c680a66..346babd0ad7c4 100644 --- a/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts +++ b/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts @@ -26,11 +26,8 @@ const { createRequire } = require(`module`) export default function (this: any, source: string): string { let lmdbBinaryLocation: string | undefined - // TODO: streamline using alternative binary - this should be automatically figured out - // and not provided by user/site - if (process.env.GATSBY_FORCE_LMDB_BINARY_LOCATION) { - lmdbBinaryLocation = process.env.GATSBY_FORCE_LMDB_BINARY_LOCATION - } + // use loader option if provided + lmdbBinaryLocation = this.getOptions()?.forcedBinaryLocation if (!lmdbBinaryLocation) { try { diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 881ac3c8def6e..49c9ae365d3be 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -41,6 +41,11 @@ function noOpAdapterManager(): IAdapterManager { } } +let usingAdapter = false +export function isUsingAdapter(): boolean { + return usingAdapter +} + export async function initAdapterManager(): Promise { let adapter: IAdapter @@ -59,12 +64,15 @@ export async function initAdapterManager(): Promise { if (!adapterInit) { telemetry.trackFeatureIsUsed(`adapter:no-op`) + usingAdapter = false return noOpAdapterManager() } adapter = adapterInit() } + usingAdapter = true + reporter.info(`Using ${adapter.name} adapter`) telemetry.trackFeatureIsUsed(`adapter:${adapter.name}`) From c93dfded5346345ea70480fb4e82b806407d7fac Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 14 Jun 2023 07:10:18 +0200 Subject: [PATCH 077/161] get-route-path tests --- .../utils/adapter/__tests__/get-route-path.ts | 39 +++++++++++++++++++ .../src/utils/adapter/get-route-path.ts | 3 +- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 packages/gatsby/src/utils/adapter/__tests__/get-route-path.ts diff --git a/packages/gatsby/src/utils/adapter/__tests__/get-route-path.ts b/packages/gatsby/src/utils/adapter/__tests__/get-route-path.ts new file mode 100644 index 0000000000000..d9d1572568d89 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/get-route-path.ts @@ -0,0 +1,39 @@ +import type { IGatsbyFunction, IGatsbyPage } from "../../../redux/types" +import { + getRoutePathFromPage, + getRoutePathFromFunction, +} from "../get-route-path" + +describe(`getRoutePathFromPage`, () => { + it(`returns the page path if no matchPath is defined`, () => { + const page = { + path: `/`, + } as IGatsbyPage + + expect(getRoutePathFromPage(page)).toEqual(`/`) + }) + it(`replaces the named part of a wildcard matchPath with a wildcard`, () => { + const page = { + matchPath: `/foo/*bar`, + } as IGatsbyPage + + expect(getRoutePathFromPage(page)).toEqual(`/foo/*`) + }) +}) + +describe(`getRoutePathFromFunction`, () => { + it(`returns the functionRoute if no matchPath is defined`, () => { + const functionInfo = { + functionRoute: `/`, + } as IGatsbyFunction + + expect(getRoutePathFromFunction(functionInfo)).toEqual(`/`) + }) + it(`replaces the named part of a wildcard matchPath with a wildcard`, () => { + const functionInfo = { + matchPath: `/foo/*bar`, + } as IGatsbyFunction + + expect(getRoutePathFromFunction(functionInfo)).toEqual(`/foo/*`) + }) +}) diff --git a/packages/gatsby/src/utils/adapter/get-route-path.ts b/packages/gatsby/src/utils/adapter/get-route-path.ts index a021a9ea14687..a4018bc19684b 100644 --- a/packages/gatsby/src/utils/adapter/get-route-path.ts +++ b/packages/gatsby/src/utils/adapter/get-route-path.ts @@ -1,4 +1,4 @@ -import { IGatsbyFunction, IGatsbyPage } from "../../internal" +import type { IGatsbyFunction, IGatsbyPage } from "../../redux/types" function maybeDropNamedPartOfWildcard( path: string | undefined @@ -7,6 +7,7 @@ function maybeDropNamedPartOfWildcard( return path } + // Replaces `/foo/*bar` with `/foo/*` return path.replace(/\*.+$/, `*`) } From 0a869b66751836605fa0069789bf167d03a2d26e Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 14 Jun 2023 07:22:43 +0200 Subject: [PATCH 078/161] manager refactoring + typo fix --- packages/gatsby/src/utils/adapter/manager.ts | 60 ++++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 49c9ae365d3be..5e4dedd947d0d 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -72,15 +72,14 @@ export async function initAdapterManager(): Promise { } usingAdapter = true - reporter.info(`Using ${adapter.name} adapter`) - telemetry.trackFeatureIsUsed(`adapter:${adapter.name}`) const directoriesToCache = [`.cache`, `public`] return { restoreCache: async (): Promise => { - reporter.info(`[dev-adapter-manager] restoreCache()`) + // TODO: Remove before final merge + reporter.info(`[Adapters] restoreCache()`) if (!adapter.cache) { return } @@ -106,7 +105,8 @@ export async function initAdapterManager(): Promise { } }, storeCache: async (): Promise => { - reporter.info(`[dev-adapter-manager] storeCache()`) + // TODO: Remove before final merge + reporter.info(`[Adapters] storeCache()`) if (!adapter.cache) { return } @@ -114,7 +114,8 @@ export async function initAdapterManager(): Promise { await adapter.cache.store({ directories: directoriesToCache, reporter }) }, adapt: async (): Promise => { - reporter.info(`[dev-adapter-manager] adapt()`) + // TODO: Remove before final merge + reporter.info(`[Adapters] adapt()`) if (!adapter.adapt) { return } @@ -153,7 +154,8 @@ type RouteWithScore = { score: number } & Route function getRoutesManifest(): RoutesManifest { const routes: Array = [] - const createHeaders = createHeadersMatcher(store.getState().config.headers) + const state = store.getState() + const createHeaders = createHeadersMatcher(state.config.headers) const fileAssets = new Set( globSync(`**/**`, { @@ -180,31 +182,31 @@ function getRoutesManifest(): RoutesManifest { function addStaticRoute({ path, - pathToFilInPublicDir, + pathToFillInPublicDir, headers, }: { path: string - pathToFilInPublicDir: string + pathToFillInPublicDir: string headers: IHeader["headers"] }): void { addRoute({ path, type: `static`, - filePath: posix.join(`public`, pathToFilInPublicDir), + filePath: posix.join(`public`, pathToFillInPublicDir), headers, }) - if (fileAssets.has(pathToFilInPublicDir)) { - fileAssets.delete(pathToFilInPublicDir) + if (fileAssets.has(pathToFillInPublicDir)) { + fileAssets.delete(pathToFillInPublicDir) } else { reporter.warn( - `[dev-adapter-manager] tried to remove "${pathToFilInPublicDir}" from assets but it wasn't there` + `[Adapters] Tried to remove "${pathToFillInPublicDir}" from assets but it wasn't there` ) } } - // routes - pages - static (SSG) or lambda (DSG/SSR) - for (const page of store.getState().pages.values()) { + // routes - pages - static (SSG) or function (DSG/SSR) + for (const page of state.pages.values()) { const htmlRoutePath = getRoutePathFromPage(page) const pageDataRoutePath = generatePageDataPath(``, htmlRoutePath) @@ -216,12 +218,12 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: htmlRoutePath, - pathToFilInPublicDir: htmlFilePath, + pathToFillInPublicDir: htmlFilePath, headers: STATIC_PAGE_HEADERS, }) addStaticRoute({ path: pageDataRoutePath, - pathToFilInPublicDir: pageDataFilePath, + pathToFillInPublicDir: pageDataFilePath, headers: STATIC_PAGE_HEADERS, }) } else { @@ -247,13 +249,11 @@ function getRoutesManifest(): RoutesManifest { } // static query json assets - for (const staticQueryComponent of store - .getState() - .staticQueryComponents.values()) { + for (const staticQueryComponent of state.staticQueryComponents.values()) { const staticQueryResultPath = getStaticQueryPath(staticQueryComponent.hash) addStaticRoute({ path: staticQueryResultPath, - pathToFilInPublicDir: staticQueryResultPath, + pathToFillInPublicDir: staticQueryResultPath, headers: STATIC_PAGE_HEADERS, }) } @@ -263,20 +263,21 @@ function getRoutesManifest(): RoutesManifest { const appDataFilePath = posix.join(`page-data`, `app-data.json`) addStaticRoute({ path: appDataFilePath, - pathToFilInPublicDir: appDataFilePath, + pathToFillInPublicDir: appDataFilePath, headers: STATIC_PAGE_HEADERS, }) } // webpack assets if (!webpackAssets) { - throw new Error(`[dev-adapter-manager] webpackAssets is not defined`) + // TODO: Make this a structured error + throw new Error(`[Adapters] webpackAssets is not defined`) } for (const asset of webpackAssets) { addStaticRoute({ path: asset, - pathToFilInPublicDir: asset, + pathToFillInPublicDir: asset, headers: WEBPACK_ASSET_HEADERS, }) } @@ -284,7 +285,7 @@ function getRoutesManifest(): RoutesManifest { // TODO: slices // redirect routes - for (const redirect of store.getState().redirects.values()) { + for (const redirect of state.redirects.values()) { addRoute({ path: redirect.fromPath, type: `redirect`, @@ -300,7 +301,7 @@ function getRoutesManifest(): RoutesManifest { } // function routes - for (const functionInfo of store.getState().functions.values()) { + for (const functionInfo of state.functions.values()) { addRoute({ path: `/api/${getRoutePathFromFunction(functionInfo)}`, type: `function`, @@ -308,15 +309,13 @@ function getRoutesManifest(): RoutesManifest { }) } - console.log( - `[dev-adapter-manager] unmanaged (or not yet handled) assets`, - fileAssets - ) + // TODO: Remove before final merge + console.log(`[Adapters] unmanaged (or not yet handled) assets`, fileAssets) for (const fileAsset of fileAssets) { addStaticRoute({ path: fileAsset, - pathToFilInPublicDir: fileAsset, + pathToFillInPublicDir: fileAsset, headers: ASSET_HEADERS, }) } @@ -334,6 +333,7 @@ function getRoutesManifest(): RoutesManifest { // deterministic order regardless of order pages where created return a.path.localeCompare(b.path) }) + // The score should be internal only, so we remove it from the final manifest // eslint-disable-next-line @typescript-eslint/no-unused-vars .map(({ score, ...rest }): Route => { return { ...rest } From b46a3907ea529f8e38db005e27f4b36363b5672a Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 14 Jun 2023 09:24:59 +0200 Subject: [PATCH 079/161] initialize adapters e2e test --- .circleci/config.yml | 11 ++++ e2e-tests/adapters/.gitignore | 10 +++ e2e-tests/adapters/README.md | 8 +++ e2e-tests/adapters/cypress.config.ts | 11 ++++ e2e-tests/adapters/cypress/configs/netlify.ts | 11 ++++ e2e-tests/adapters/cypress/e2e/smoke.cy.ts | 5 ++ e2e-tests/adapters/cypress/support/e2e.ts | 1 + e2e-tests/adapters/cypress/tsconfig.json | 8 +++ e2e-tests/adapters/debug-adapter.ts | 33 ++++++++++ e2e-tests/adapters/gatsby-config.ts | 23 +++++++ e2e-tests/adapters/netlify.toml | 3 + e2e-tests/adapters/package.json | 34 ++++++++++ e2e-tests/adapters/src/components/layout.jsx | 33 ++++++++++ e2e-tests/adapters/src/images/icon.png | Bin 0 -> 11189 bytes e2e-tests/adapters/src/pages/404.jsx | 49 ++++++++++++++ e2e-tests/adapters/src/pages/index.jsx | 60 ++++++++++++++++++ e2e-tests/adapters/src/pages/routes/dsg.jsx | 23 +++++++ e2e-tests/adapters/src/pages/routes/ssr.jsx | 30 +++++++++ .../adapters/src/pages/routes/static.jsx | 15 +++++ 19 files changed, 368 insertions(+) create mode 100644 e2e-tests/adapters/.gitignore create mode 100644 e2e-tests/adapters/README.md create mode 100644 e2e-tests/adapters/cypress.config.ts create mode 100644 e2e-tests/adapters/cypress/configs/netlify.ts create mode 100644 e2e-tests/adapters/cypress/e2e/smoke.cy.ts create mode 100644 e2e-tests/adapters/cypress/support/e2e.ts create mode 100644 e2e-tests/adapters/cypress/tsconfig.json create mode 100644 e2e-tests/adapters/debug-adapter.ts create mode 100644 e2e-tests/adapters/gatsby-config.ts create mode 100644 e2e-tests/adapters/netlify.toml create mode 100644 e2e-tests/adapters/package.json create mode 100644 e2e-tests/adapters/src/components/layout.jsx create mode 100644 e2e-tests/adapters/src/images/icon.png create mode 100644 e2e-tests/adapters/src/pages/404.jsx create mode 100644 e2e-tests/adapters/src/pages/index.jsx create mode 100644 e2e-tests/adapters/src/pages/routes/dsg.jsx create mode 100644 e2e-tests/adapters/src/pages/routes/ssr.jsx create mode 100644 e2e-tests/adapters/src/pages/routes/static.jsx diff --git a/.circleci/config.yml b/.circleci/config.yml index 9fe269388f054..31d482ba479d7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -444,6 +444,15 @@ jobs: - store_test_results: path: e2e-tests/trailing-slash/cypress/results + e2e_tests_adapters: + <<: *e2e-executor + steps: + - run: echo 'export CYPRESS_RECORD_KEY="${CY_CLOUD_ADAPTERS}"' >> "$BASH_ENV" + - e2e-test: + test_path: e2e-tests/adapters + - store_test_results: + path: e2e-tests/adapters/cypress/results + starters_validate: executor: node steps: @@ -594,6 +603,8 @@ workflows: <<: *e2e-test-workflow - e2e_tests_trailing-slash: <<: *e2e-test-workflow + - e2e_tests_adapters: + <<: *e2e-test-workflow - e2e_tests_development_runtime_with_react_18: <<: *e2e-test-workflow - e2e_tests_production_runtime_with_react_18: diff --git a/e2e-tests/adapters/.gitignore b/e2e-tests/adapters/.gitignore new file mode 100644 index 0000000000000..6c70650d2b850 --- /dev/null +++ b/e2e-tests/adapters/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +.cache/ +public + +# Local Netlify folder +.netlify + +# Cypress output +cypress/videos/ +cypress/screenshots/ diff --git a/e2e-tests/adapters/README.md b/e2e-tests/adapters/README.md new file mode 100644 index 0000000000000..dbde8fc732799 --- /dev/null +++ b/e2e-tests/adapters/README.md @@ -0,0 +1,8 @@ +# adapters + +E2E testing suite for Gatsby's [adapters](http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/adapters/) feature. +If possible, run the tests locally with a CLI. Otherwise deploy the site to the target platform and run Cypress on the deployed URL. + +Adapters being tested: + +- [gatsby-adapter-netlify](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-adapter-netlify) diff --git a/e2e-tests/adapters/cypress.config.ts b/e2e-tests/adapters/cypress.config.ts new file mode 100644 index 0000000000000..57b87ae56e056 --- /dev/null +++ b/e2e-tests/adapters/cypress.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "cypress" + +export default defineConfig({ + e2e: { + baseUrl: `http://localhost:9000`, + projectId: `4enh4m`, + viewportWidth: 1440, + viewportHeight: 900, + videoUploadOnPasses: false, + }, +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/configs/netlify.ts b/e2e-tests/adapters/cypress/configs/netlify.ts new file mode 100644 index 0000000000000..48aad0e2177d9 --- /dev/null +++ b/e2e-tests/adapters/cypress/configs/netlify.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "cypress" + +export default defineConfig({ + e2e: { + baseUrl: `http://localhost:8888`, + projectId: `4enh4m`, + viewportWidth: 1440, + viewportHeight: 900, + videoUploadOnPasses: false, + }, +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/smoke.cy.ts b/e2e-tests/adapters/cypress/e2e/smoke.cy.ts new file mode 100644 index 0000000000000..9c1d8337ffc0e --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/smoke.cy.ts @@ -0,0 +1,5 @@ +describe('Smoke test', () => { + it('should visit homepage', () => { + cy.visit('/') + }) +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/support/e2e.ts b/e2e-tests/adapters/cypress/support/e2e.ts new file mode 100644 index 0000000000000..885630a9c2527 --- /dev/null +++ b/e2e-tests/adapters/cypress/support/e2e.ts @@ -0,0 +1 @@ +// https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Support-file \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/tsconfig.json b/e2e-tests/adapters/cypress/tsconfig.json new file mode 100644 index 0000000000000..83fb87e55fd8f --- /dev/null +++ b/e2e-tests/adapters/cypress/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["es5", "dom"], + "types": ["cypress", "node"] + }, + "include": ["**/*.ts"] +} \ No newline at end of file diff --git a/e2e-tests/adapters/debug-adapter.ts b/e2e-tests/adapters/debug-adapter.ts new file mode 100644 index 0000000000000..22e79834876c6 --- /dev/null +++ b/e2e-tests/adapters/debug-adapter.ts @@ -0,0 +1,33 @@ +import { inspect } from "util" +import type { AdapterInit } from "gatsby" + +const createTestingAdapter: AdapterInit = (adapterOptions) => { + return { + name: `gatsby-adapter-debug`, + cache: { + restore({ directories, reporter }) { + reporter.info(`[gatsby-adapter-debug] cache.restore() ${directories}`) + }, + store({ directories, reporter }) { + reporter.info(`[gatsby-adapter-debug] cache.store() ${directories}`) + } + }, + adapt({ + routesManifest, + functionsManifest, + reporter, + }) { + reporter.info(`[gatsby-adapter-debug] adapt()`) + + console.log(`[gatsby-adapter-debug] adapt()`, inspect({ + routesManifest, + functionsManifest + }, { + depth: Infinity, + colors: true + })) + } + } +} + +export default createTestingAdapter \ No newline at end of file diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts new file mode 100644 index 0000000000000..b5f30418a12c0 --- /dev/null +++ b/e2e-tests/adapters/gatsby-config.ts @@ -0,0 +1,23 @@ +import type { GatsbyConfig } from "gatsby" +import debugAdapter from "./debug-adapter" + +const shouldUseDebugAdapter = process.env.USE_DEBUG_ADAPTER ?? false + +let configOverrides: GatsbyConfig = {} + +// Should conditionally add debug adapter to config +if (shouldUseDebugAdapter) { + configOverrides = { + adapter: debugAdapter(), + } +} + +const config: GatsbyConfig = { + siteMetadata: { + title: `adapters`, + }, + plugins: [], + ...configOverrides, +} + +export default config diff --git a/e2e-tests/adapters/netlify.toml b/e2e-tests/adapters/netlify.toml new file mode 100644 index 0000000000000..d1e965d4a8f4c --- /dev/null +++ b/e2e-tests/adapters/netlify.toml @@ -0,0 +1,3 @@ +[build] + command = "npm run build" + publish = "public/" \ No newline at end of file diff --git a/e2e-tests/adapters/package.json b/e2e-tests/adapters/package.json new file mode 100644 index 0000000000000..48f3d7cc8764b --- /dev/null +++ b/e2e-tests/adapters/package.json @@ -0,0 +1,34 @@ +{ + "name": "adapters", + "version": "1.0.0", + "private": true, + "description": "E2E test site for testing official adapters", + "author": "LekoArts", + "scripts": { + "develop": "cross-env CYPRESS_SUPPORT=y gatsby develop", + "build": "cross-env CYPRESS_SUPPORT=y gatsby build", + "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", + "ssat:debug": "start-server-and-test serve http://localhost:9000 cy:open", + "test:template": "cross-env-shell CYPRESS_GROUP_NAME=$ADAPTER node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --e2e --config-file \"cypress/configs/$ADAPTER.ts\"", + "ssat:template": "cross-env-shell ADAPTER=$ADAPTER start-server-and-test", + "test:debug": "npm-run-all -s build:debug ssat:debug", + "test:netlify": "start-server-and-test 'BROWSER=none ntl serve --port 8888' http://localhost:8888 'cross-env ADAPTER=netlify npm run test:template'", + "test": "npm-run-all -c -s test:netlify" + }, + "dependencies": { + "gatsby": "next", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "cypress": "^12.14.0", + "netlify-cli": "^15.5.1", + "npm-run-all": "^4.1.5", + "start-server-and-test": "^2.0.0", + "typescript": "^5.1.3" + } +} diff --git a/e2e-tests/adapters/src/components/layout.jsx b/e2e-tests/adapters/src/components/layout.jsx new file mode 100644 index 0000000000000..c721d6b412cef --- /dev/null +++ b/e2e-tests/adapters/src/components/layout.jsx @@ -0,0 +1,33 @@ +import * as React from "react" +import { Link } from "gatsby" + +const Layout = ({ children, hideBackToHome = false }) => ( + <> + {!hideBackToHome && ( + + Back to Home + + )} +
+ {children} +
+ +) + +export default Layout + +const pageStyles = { + color: "#232129", + padding: 96, + fontFamily: "-apple-system, Roboto, sans-serif, serif", +} + +const backToHomeStyle = { + position: "absolute", + textDecoration: "none", + fontFamily: "-apple-system, Roboto, sans-serif, serif", + color: "#232129", + border: '2px solid #8954A8', + padding: '5px 10px', + borderRadius: '8px', +} diff --git a/e2e-tests/adapters/src/images/icon.png b/e2e-tests/adapters/src/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..38b2fb0e467e023806c846454e35ede8af67105e GIT binary patch literal 11189 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 R^|Id+(7vmu{#Fee^?z+!bVL9E literal 0 HcmV?d00001 diff --git a/e2e-tests/adapters/src/pages/404.jsx b/e2e-tests/adapters/src/pages/404.jsx new file mode 100644 index 0000000000000..d7b40627e81ae --- /dev/null +++ b/e2e-tests/adapters/src/pages/404.jsx @@ -0,0 +1,49 @@ +import * as React from "react" +import { Link } from "gatsby" + +const pageStyles = { + color: "#232129", + padding: "96px", + fontFamily: "-apple-system, Roboto, sans-serif, serif", +} +const headingStyles = { + marginTop: 0, + marginBottom: 64, + maxWidth: 320, +} + +const paragraphStyles = { + marginBottom: 48, +} +const codeStyles = { + color: "#8A6534", + padding: 4, + backgroundColor: "#FFF4DB", + fontSize: "1.25rem", + borderRadius: 4, +} + +const NotFoundPage = () => { + return ( +
+

Page not found

+

+ Sorry 😔, we couldn’t find what you were looking for. +
+ {process.env.NODE_ENV === "development" ? ( + <> +
+ Try creating a page in src/pages/. +
+ + ) : null} +
+ Go home. +

+
+ ) +} + +export default NotFoundPage + +export const Head = () => Not found diff --git a/e2e-tests/adapters/src/pages/index.jsx b/e2e-tests/adapters/src/pages/index.jsx new file mode 100644 index 0000000000000..b037c384f7b6b --- /dev/null +++ b/e2e-tests/adapters/src/pages/index.jsx @@ -0,0 +1,60 @@ +import * as React from "react" +import { Link } from "gatsby" +import Layout from "../components/layout" + +const links = [ + { + text: "Static", + url: "/routes/static", + }, + { + text: "SSR", + url: "/routes/ssr", + }, + { + text: "DSG", + url: "/routes/dsg", + }, +] + +const IndexPage = () => { + return ( + +

Adapters

+
    + {links.map(link => ( +
  • + + {link.text} + +
  • + ))} +
+
+ ) +} + +export default IndexPage + +export const Head = () => Home + +const listStyles = { + marginTop: 30, + marginBottom: 96, + paddingLeft: 0, +} + +const listItemStyles = { + fontWeight: 300, + fontSize: 24, + maxWidth: 560, + marginBottom: 16, +} + +const linkStyle = { + color: "#8954A8", + fontWeight: "bold", + fontSize: 16, + verticalAlign: "5%", + textDecoration: "none", +} diff --git a/e2e-tests/adapters/src/pages/routes/dsg.jsx b/e2e-tests/adapters/src/pages/routes/dsg.jsx new file mode 100644 index 0000000000000..0f8f22880f550 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/dsg.jsx @@ -0,0 +1,23 @@ +import * as React from "react" +import Layout from "../../components/layout"; + +const DSG = () => { + return ( + +

DSG

+

Hello World!

+
+ ) +} + +export default DSG + +export const Head = () => DSG + +export async function config() { + return () => { + return { + defer: true, + } + } +} \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/routes/ssr.jsx b/e2e-tests/adapters/src/pages/routes/ssr.jsx new file mode 100644 index 0000000000000..0bdae509cf64c --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/ssr.jsx @@ -0,0 +1,30 @@ +import * as React from "react" +import Layout from "../../components/layout"; + +const SSR = ({ serverData, params }) => { + return ( + +

SSR

+
+ +
+            {JSON.stringify({ params, serverData }, null, 2)}
+          
+
+
+
+ ) +} + +export default SSR + +export const Head = () => SSR + +export function getServerData({ params }) { + return { + props: { + ssr: true, + params, + }, + }; +} \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/routes/static.jsx b/e2e-tests/adapters/src/pages/routes/static.jsx new file mode 100644 index 0000000000000..090cafdb17fa7 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/static.jsx @@ -0,0 +1,15 @@ +import * as React from "react" +import Layout from "../../components/layout" + +const StaticPage = () => { + return ( + +

Static

+

Hello World!

+
+ ) +} + +export default StaticPage + +export const Head = () => Static \ No newline at end of file From 8c7d99e507d461760c2df55bfed4b739175a622a Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 14 Jun 2023 09:26:33 +0200 Subject: [PATCH 080/161] cypress: remove viewPort configs --- e2e-tests/adapters/cypress.config.ts | 2 -- e2e-tests/adapters/cypress/configs/netlify.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/e2e-tests/adapters/cypress.config.ts b/e2e-tests/adapters/cypress.config.ts index 57b87ae56e056..4fb4f428d35d3 100644 --- a/e2e-tests/adapters/cypress.config.ts +++ b/e2e-tests/adapters/cypress.config.ts @@ -4,8 +4,6 @@ export default defineConfig({ e2e: { baseUrl: `http://localhost:9000`, projectId: `4enh4m`, - viewportWidth: 1440, - viewportHeight: 900, videoUploadOnPasses: false, }, }) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/configs/netlify.ts b/e2e-tests/adapters/cypress/configs/netlify.ts index 48aad0e2177d9..5e707bfecdf59 100644 --- a/e2e-tests/adapters/cypress/configs/netlify.ts +++ b/e2e-tests/adapters/cypress/configs/netlify.ts @@ -4,8 +4,6 @@ export default defineConfig({ e2e: { baseUrl: `http://localhost:8888`, projectId: `4enh4m`, - viewportWidth: 1440, - viewportHeight: 900, videoUploadOnPasses: false, }, }) \ No newline at end of file From 5f2f652036a227bcdcd9164cc9a0c3547dd3bf5e Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 09:54:19 +0200 Subject: [PATCH 081/161] use next preid for netlify adapter --- packages/gatsby-adapter-netlify/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 3a12803a5f971..7922a09de44e3 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -1,6 +1,6 @@ { "name": "gatsby-adapter-netlify", - "version": "1.0.0", + "version": "1.0.0-next.0", "description": "Gatsby adapter for Netlify", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -46,7 +46,7 @@ "typescript": "^5.0.4" }, "peerDependencies": { - "gatsby": "^5.11.0-next" + "gatsby": "^5.11.0-alpha" }, "files": [ "dist/" From cc34cc7cf0bd71c4b98e456fd29b60b3a139bb48 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 09:58:12 +0200 Subject: [PATCH 082/161] remove test adapter from adapters manifest --- packages/gatsby/adapters.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index 150e33ab862f1..ed3b945f81f69 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -19,18 +19,7 @@ const adaptersManifest = [ moduleVersion: `*` } ] - }, - { - name: `gatsby-adapter-testing`, - module: `@lekoarts/gatsby-adapter-testing`, - test: () => true, - versions: [ - { - gatsbyVersion: `^5.0.0`, - moduleVersion: `^1.0.0` - } - ] - }, + } ] module.exports = adaptersManifest From 8bdaa2e4fc4e79bcacc0582af3d2229f10682d28 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 10:09:28 +0200 Subject: [PATCH 083/161] don't log errors when testing for user installed adapters --- packages/gatsby/src/utils/adapter/init.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index f8f55d53fa510..c96ac84a5cd0c 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -102,7 +102,6 @@ export async function getAdapterInit(): Promise { return preferDefault(preferDefault(await import(required))) as AdapterInit } } catch (e) { - console.error(e) // no-op } From 6956ad573347afd3d7fabadb6ce8c987590ffdc0 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 10:20:50 +0200 Subject: [PATCH 084/161] update adapters manifest --- packages/gatsby/adapters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index ed3b945f81f69..086233eb2c5e0 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -16,7 +16,7 @@ const adaptersManifest = [ versions: [ { gatsbyVersion: `^5.0.0`, - moduleVersion: `*` + moduleVersion: `^1.0.0-alpha` } ] } From a3d42788eff34667a1e7ea9a204318eba71bb74f Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 12:57:05 +0200 Subject: [PATCH 085/161] cleanup gatsby-adapter-netlify a bit, add more public adapter related types to gatsby --- packages/gatsby-adapter-netlify/package.json | 3 +-- packages/gatsby-adapter-netlify/src/index.ts | 17 +---------------- .../src/lambda-handler.ts | 2 +- .../gatsby-adapter-netlify/src/route-handler.ts | 3 +-- packages/gatsby/index.d.ts | 12 ++++++++++-- packages/gatsby/src/utils/adapter/types.ts | 4 ++-- 6 files changed, 16 insertions(+), 25 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 7922a09de44e3..c5c7861ab6a7c 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -39,8 +39,7 @@ "devDependencies": { "@babel/cli": "^7.20.7", "@babel/core": "^7.20.12", - "babel-preset-gatsby-package": "^3.11.0-next.0", - "gatsby": "^5.11.0-next.1", + "babel-preset-gatsby-package": "^3.11.0-next.1", "cross-env": "^7.0.3", "rimraf": "^5.0.1", "typescript": "^5.0.4" diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 6cde250f9ec1a..4c6dadb1a4f92 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -1,7 +1,4 @@ -import type { AdapterInit } from "gatsby/src/utils/adapter/types" - -// just for debugging -// import { inspect } from "util" +import type { AdapterInit } from "gatsby" // eslint-disable-next-line @typescript-eslint/no-empty-interface interface INetlifyAdapterOptions {} @@ -52,18 +49,6 @@ const createNetlifyAdapter: AdapterInit = () => { }, }, async adapt({ routesManifest, functionsManifest }): Promise { - // this is noisy, so for now commented out to easily restore if some routes/functions debugging is needed - // console.log( - // `[gatsby-adapter-netlify] adapt()`, - // inspect( - // { - // routesManifest, - // functionsManifest, - // }, - // { depth: Infinity, colors: true } - // ) - // ) - const { lambdasThatUseCaching } = await handleRoutesManifest( routesManifest ) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 34ba46eeee72c..46aa5f7688985 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -1,4 +1,4 @@ -import type { IFunctionDefinition } from "gatsby/src/utils/adapter/types" +import type { IFunctionDefinition } from "gatsby" import packageJson from "gatsby-adapter-netlify/package.json" diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index 8d0c7e8e19316..2a8d2e3346989 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -1,4 +1,4 @@ -import type { RoutesManifest } from "gatsby/src/utils/adapter/types" +import type { RoutesManifest } from "gatsby" import fs from "fs-extra" @@ -26,7 +26,6 @@ export async function handleRoutesManifest( ): Promise<{ lambdasThatUseCaching: Map }> { - console.log(`hello from handleRoutesManifest()`) const lambdasThatUseCaching = new Map() let _redirects = `` diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index c5d0f07bc78b4..5627b2475d8b8 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -16,7 +16,6 @@ import { import { GraphQLOutputType } from "graphql" import { PluginOptionsSchemaJoi, ObjectSchema } from "gatsby-plugin-utils" import { IncomingMessage, ServerResponse } from "http" -import { AdapterInit, IAdapter } from "gatsby/dist/utils/adapter/types" export type AvailableFeatures = | "image-cdn" @@ -34,7 +33,16 @@ export { export * from "gatsby-script" -export { AdapterInit, IAdapter } +export { + AdapterInit, + IAdapter, + IStaticRoute, + IFunctionRoute, + IRedirectRoute, + IFunctionDefinition, + RoutesManifest, + FunctionsManifest, +} from "./dist/utils/adapter/types" export const useScrollRestoration: (key: string) => { ref: React.MutableRefObject diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 3c96b2825f68e..8605d0e59d978 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -13,7 +13,7 @@ interface IBaseRoute { path: string } -interface IStaticRoute extends IBaseRoute { +export interface IStaticRoute extends IBaseRoute { type: `static` /** * Location of the file that should be served for this route. @@ -43,7 +43,7 @@ export interface IFunctionRoute extends IBaseRoute { * Redirects are being created through the `createRedirect` action. * @see https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect */ -interface IRedirectRoute extends IBaseRoute { +export interface IRedirectRoute extends IBaseRoute { type: `redirect` /** * The redirect should happen from `path` to `toPath`. From 083fd85e7537191046f9c60656533746425bcff9 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 14 Jun 2023 13:23:35 +0200 Subject: [PATCH 086/161] e2e: update gitignore --- e2e-tests/adapters/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e-tests/adapters/.gitignore b/e2e-tests/adapters/.gitignore index 6c70650d2b850..abb27db5d05c7 100644 --- a/e2e-tests/adapters/.gitignore +++ b/e2e-tests/adapters/.gitignore @@ -8,3 +8,6 @@ public # Cypress output cypress/videos/ cypress/screenshots/ + +# Custom .yarnrc file for gatsby-dev on Yarn 3 +.yarnrc.yml From 0a7b66344480d0841370f978c4d15ec2e7f4ea7c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 14:20:43 +0200 Subject: [PATCH 087/161] resolve lmdb binary from lmdb package and not hardcode the forced path --- .../schema/graphql-engine/bundle-webpack.ts | 4 +- .../graphql-engine/lmdb-bundling-patch.ts | 44 ++++++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts index 8880e74624d0f..7973e761ecfbb 100644 --- a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts +++ b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts @@ -121,8 +121,8 @@ export async function createGraphqlEngineBundle( { loader: require.resolve(`./lmdb-bundling-patch`), options: { - forcedBinaryLocation: isUsingAdapter() - ? `../../@lmdb/lmdb-${process.platform}-${process.arch}/node.abi83.glibc.node` + forcedBinaryModule: isUsingAdapter() + ? `@lmdb/lmdb-${process.platform}-${process.arch}/node.abi83.glibc.node` : undefined, }, }, diff --git a/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts b/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts index 346babd0ad7c4..d40cc391a0c3d 100644 --- a/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts +++ b/packages/gatsby/src/schema/graphql-engine/lmdb-bundling-patch.ts @@ -26,16 +26,23 @@ const { createRequire } = require(`module`) export default function (this: any, source: string): string { let lmdbBinaryLocation: string | undefined - // use loader option if provided - lmdbBinaryLocation = this.getOptions()?.forcedBinaryLocation + try { + const lmdbRoot = + this?._module.resourceResolveData?.descriptionFileRoot || + path.dirname(this.resourcePath).replace(`/dist`, ``) - if (!lmdbBinaryLocation) { - try { - const lmdbRoot = - this?._module.resourceResolveData?.descriptionFileRoot || - path.dirname(this.resourcePath).replace(`/dist`, ``) + const lmdbRequire = createRequire(this.resourcePath) + const forcedBinaryModule = this.getOptions()?.forcedBinaryModule + let absoluteModulePath + if (forcedBinaryModule) { + try { + absoluteModulePath = lmdbRequire.resolve(forcedBinaryModule) + } catch (e) { + // no-op + } + } - const lmdbRequire = createRequire(this.resourcePath) + if (!absoluteModulePath) { let nodeGypBuild try { nodeGypBuild = lmdbRequire(`node-gyp-build-optional-packages`) @@ -49,17 +56,20 @@ export default function (this: any, source: string): string { // let's try falling back to upstream package - if that doesn't work, we will fail compilation nodeGypBuild = lmdbRequire(`node-gyp-build`) } - - lmdbBinaryLocation = slash( - path.relative( - path.dirname(this.resourcePath), - nodeGypBuild.path(lmdbRoot) - ) - ) - } catch (e) { - return source + absoluteModulePath = nodeGypBuild.path(lmdbRoot) } + + lmdbBinaryLocation = slash( + path.relative(path.dirname(this.resourcePath), absoluteModulePath) + ) + } catch (e) { + return source + } + + if (!lmdbBinaryLocation) { + return source } + return source .replace( `require$1('node-gyp-build-optional-packages')(dirName)`, From 5dc65765188f3500833730e2c68ab2e6433860bd Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 15:14:58 +0200 Subject: [PATCH 088/161] gatsby-plugin-image add downlevelIteration --- packages/gatsby-plugin-image/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-plugin-image/tsconfig.json b/packages/gatsby-plugin-image/tsconfig.json index adcd64efa9520..cd53d61feda91 100644 --- a/packages/gatsby-plugin-image/tsconfig.json +++ b/packages/gatsby-plugin-image/tsconfig.json @@ -11,7 +11,8 @@ "esModuleInterop": true, "skipLibCheck": true, "module": "ESNext", - "moduleResolution": "node" + "moduleResolution": "node", + "downlevelIteration": true // "jsxFactory": "createElement" }, "files": ["./src/global.ts", "./src/index.ts", "./src/index.browser.ts"] From 8ff7b2226dd5a5049cf35b030cd0f07b6396321c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 15:36:36 +0200 Subject: [PATCH 089/161] fix persisted redux keys --- packages/gatsby/src/redux/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/redux/index.ts b/packages/gatsby/src/redux/index.ts index f80e8fb3399ac..d9ca6cd5c114a 100644 --- a/packages/gatsby/src/redux/index.ts +++ b/packages/gatsby/src/redux/index.ts @@ -23,7 +23,7 @@ const persistedReduxKeys = [ `status`, `components`, `jobsV2`, - `staticQueriesByTemplate`, + `staticQueryComponents`, `webpackCompilationHash`, `pageDataStats`, `pages`, From ae403c081434f12f13647f441fee073f751944c9 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 15:38:49 +0200 Subject: [PATCH 090/161] update snapshot and mocks --- .../gatsby/src/bootstrap/load-plugins/__tests__/validate.ts | 2 +- packages/gatsby/src/redux/actions/__tests__/internal.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts b/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts index 2ef2311f8d9de..21d3b48e9f239 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/__tests__/validate.ts @@ -6,7 +6,7 @@ jest.mock(`gatsby-cli/lib/reporter`, () => { } }) jest.mock(`../../resolve-module-exports`) -jest.mock(`../../../utils/get-latest-apis`) +jest.mock(`../../../utils/get-latest-gatsby-files`) import reporter from "gatsby-cli/lib/reporter" import { diff --git a/packages/gatsby/src/redux/actions/__tests__/internal.ts b/packages/gatsby/src/redux/actions/__tests__/internal.ts index ea01df2b77a0f..d42eea6fd4b7b 100644 --- a/packages/gatsby/src/redux/actions/__tests__/internal.ts +++ b/packages/gatsby/src/redux/actions/__tests__/internal.ts @@ -22,6 +22,7 @@ describe(`setSiteConfig`, () => { Object { "payload": Object { "graphqlTypegen": false, + "headers": Array [], "jsxRuntime": "classic", "pathPrefix": "", "polyfill": true, @@ -41,6 +42,7 @@ describe(`setSiteConfig`, () => { Object { "payload": Object { "graphqlTypegen": false, + "headers": Array [], "jsxRuntime": "classic", "pathPrefix": "", "polyfill": true, From f5711f37fda5f06adcba94dd9dc761e039995d58 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 14 Jun 2023 16:23:05 +0200 Subject: [PATCH 091/161] fix: only run adapters during gatsby build --- packages/gatsby/src/bootstrap/index.ts | 2 +- packages/gatsby/src/commands/build.ts | 6 ++++-- packages/gatsby/src/services/initialize.ts | 11 ++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/gatsby/src/bootstrap/index.ts b/packages/gatsby/src/bootstrap/index.ts index 218d45da89ae2..51e26407a7d76 100644 --- a/packages/gatsby/src/bootstrap/index.ts +++ b/packages/gatsby/src/bootstrap/index.ts @@ -27,7 +27,7 @@ export async function bootstrap( ): Promise<{ gatsbyNodeGraphQLFunction: Runner workerPool: GatsbyWorkerPool - adapterManager: IAdapterManager + adapterManager?: IAdapterManager }> { const spanArgs = initialContext.parentSpan ? { childOf: initialContext.parentSpan } diff --git a/packages/gatsby/src/commands/build.ts b/packages/gatsby/src/commands/build.ts index 048eca9e284d0..05dbc1f7bb2a6 100644 --- a/packages/gatsby/src/commands/build.ts +++ b/packages/gatsby/src/commands/build.ts @@ -697,8 +697,10 @@ module.exports = async function build( report.info(`.cache/deletedPages.txt created`) } - await adapterManager.adapt() - await adapterManager.storeCache() + if (adapterManager) { + await adapterManager.adapt() + await adapterManager.storeCache() + } showExperimentNotices() diff --git a/packages/gatsby/src/services/initialize.ts b/packages/gatsby/src/services/initialize.ts index 9e3bf398705f2..4bc9e75df2103 100644 --- a/packages/gatsby/src/services/initialize.ts +++ b/packages/gatsby/src/services/initialize.ts @@ -83,7 +83,7 @@ export async function initialize({ store: Store workerPool: WorkerPool.GatsbyWorkerPool webhookBody?: WebhookBody - adapterManager: IAdapterManager + adapterManager?: IAdapterManager }> { if (process.env.GATSBY_DISABLE_CACHE_PERSISTENCE) { reporter.info( @@ -187,8 +187,13 @@ export async function initialize({ }) activity.end() - const adapterManager = await initAdapterManager() - await adapterManager.restoreCache() + let adapterManager: IAdapterManager | undefined = undefined + + // Only initialize adapters during "gatsby build" + if (process.env.gatsby_executing_command === `build`) { + adapterManager = await initAdapterManager() + await adapterManager.restoreCache() + } // Load plugins activity = reporter.activityTimer(`load plugins`, { From c615f0405002dfed4e7b3adf3ed801a5e79bedf8 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 14 Jun 2023 16:48:26 +0200 Subject: [PATCH 092/161] resolve netlify functions runtime deps from adapter context --- packages/gatsby-adapter-netlify/package.json | 1 + packages/gatsby-adapter-netlify/src/lambda-handler.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index c5c7861ab6a7c..7632efc366252 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -34,6 +34,7 @@ "@babel/runtime": "^7.20.13", "@netlify/functions": "^1.6.0", "@netlify/cache-utils": "^5.1.5", + "cookie": "^0.5.0", "fs-extra": "^11.1.1" }, "devDependencies": { diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 46aa5f7688985..605ba3330354f 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -68,8 +68,12 @@ async function prepareFunction( const Stream = require("stream") const http = require("http") const { Buffer } = require("buffer") -const cookie = require("cookie") -${isODB ? `const { builder } = require("@netlify/functions")` : ``} +const cookie = require("${require.resolve(`cookie`)}") +${ + isODB + ? `const { builder } = require("${require.resolve(`@netlify/functions`)}")` + : `` +} const preferDefault = m => (m && m.default) || m From 62a298a82c6284eb892081020cf115d067339f8a Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 15 Jun 2023 09:25:24 +0200 Subject: [PATCH 093/161] improve public typings --- packages/gatsby/index.d.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 5627b2475d8b8..0eb52e783359e 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -381,12 +381,13 @@ export interface GatsbyConfig { /** Sometimes you need more granular/flexible access to the development server. Gatsby exposes the Express.js development server to your site’s gatsby-config.js where you can add Express middleware as needed. */ developMiddleware?(app: any): void /** - * You can set custom HTTP headers for incoming requests - * TODO + * You can set custom HTTP headers on the response of a given path. This allows you to, e.g. modify the caching behavior or configure access control. You can apply HTTP headers to static routes and redirects. + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/headers/ */ headers?: Array
/** - * TODO + * Adapters are responsible for taking the production output from Gatsby and turning it into something your deployment platform understands. They make it easier to build and deploy Gatsby on any deployment platform. + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/adapters/ */ adapter?: IAdapter } From 36df35819ba5eb82502a36b543af1591c6ba111c Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 15 Jun 2023 09:32:14 +0200 Subject: [PATCH 094/161] adding adapter to e2e test so that dev-cli copies stuff over --- e2e-tests/adapters/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e-tests/adapters/package.json b/e2e-tests/adapters/package.json index 48f3d7cc8764b..675e872b3a7a7 100644 --- a/e2e-tests/adapters/package.json +++ b/e2e-tests/adapters/package.json @@ -19,7 +19,8 @@ "test": "npm-run-all -c -s test:netlify" }, "dependencies": { - "gatsby": "next", + "gatsby": "5.12.0-next.0", + "gatsby-adapter-netlify": "latest", "react": "^18.2.0", "react-dom": "^18.2.0" }, From f6326af895be37dd6773fb2aa79c93886f89ba33 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 15 Jun 2023 13:26:06 +0200 Subject: [PATCH 095/161] update babel-preset-gatsby-package dep --- packages/gatsby-adapter-netlify/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 7632efc366252..545618e07c4bc 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@babel/cli": "^7.20.7", "@babel/core": "^7.20.12", - "babel-preset-gatsby-package": "^3.11.0-next.1", + "babel-preset-gatsby-package": "^3.12.0-next.0", "cross-env": "^7.0.3", "rimraf": "^5.0.1", "typescript": "^5.0.4" From 9791bd29f076a23e1ca627592fc7bd18c9b3c734 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 15 Jun 2023 15:05:12 +0200 Subject: [PATCH 096/161] e2e: test functions and assets --- e2e-tests/adapters/cypress.config.ts | 2 + e2e-tests/adapters/cypress/configs/netlify.ts | 2 + e2e-tests/adapters/cypress/e2e/basics.cy.ts | 29 ++++++++ .../adapters/cypress/e2e/functions.cy.ts | 27 +++++++ e2e-tests/adapters/cypress/e2e/smoke.cy.ts | 5 -- e2e-tests/adapters/cypress/support/e2e.ts | 4 +- e2e-tests/adapters/gatsby-config.ts | 4 +- e2e-tests/adapters/package.json | 2 + .../src/api/named-wildcard/[...slug].js | 3 + e2e-tests/adapters/src/api/param/[slug].js | 3 + e2e-tests/adapters/src/api/static/index.js | 3 + e2e-tests/adapters/src/api/wildcard/[...].js | 3 + e2e-tests/adapters/src/components/layout.jsx | 2 +- e2e-tests/adapters/src/images/astro.png | Bin 0 -> 17634 bytes e2e-tests/adapters/src/images/icon.png | Bin 11189 -> 0 bytes e2e-tests/adapters/src/pages/404.jsx | 17 +---- e2e-tests/adapters/src/pages/index.jsx | 70 ++++++++++++++++-- e2e-tests/adapters/static/gatsby-icon.png | Bin 0 -> 21212 bytes 18 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 e2e-tests/adapters/cypress/e2e/basics.cy.ts create mode 100644 e2e-tests/adapters/cypress/e2e/functions.cy.ts delete mode 100644 e2e-tests/adapters/cypress/e2e/smoke.cy.ts create mode 100644 e2e-tests/adapters/src/api/named-wildcard/[...slug].js create mode 100644 e2e-tests/adapters/src/api/param/[slug].js create mode 100644 e2e-tests/adapters/src/api/static/index.js create mode 100644 e2e-tests/adapters/src/api/wildcard/[...].js create mode 100644 e2e-tests/adapters/src/images/astro.png delete mode 100644 e2e-tests/adapters/src/images/icon.png create mode 100644 e2e-tests/adapters/static/gatsby-icon.png diff --git a/e2e-tests/adapters/cypress.config.ts b/e2e-tests/adapters/cypress.config.ts index 4fb4f428d35d3..ce7740301e527 100644 --- a/e2e-tests/adapters/cypress.config.ts +++ b/e2e-tests/adapters/cypress.config.ts @@ -5,5 +5,7 @@ export default defineConfig({ baseUrl: `http://localhost:9000`, projectId: `4enh4m`, videoUploadOnPasses: false, + experimentalRunAllSpecs: true, + retries: 2, }, }) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/configs/netlify.ts b/e2e-tests/adapters/cypress/configs/netlify.ts index 5e707bfecdf59..be4ef2877f4e6 100644 --- a/e2e-tests/adapters/cypress/configs/netlify.ts +++ b/e2e-tests/adapters/cypress/configs/netlify.ts @@ -5,5 +5,7 @@ export default defineConfig({ baseUrl: `http://localhost:8888`, projectId: `4enh4m`, videoUploadOnPasses: false, + experimentalRunAllSpecs: true, + retries: 2, }, }) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/basics.cy.ts b/e2e-tests/adapters/cypress/e2e/basics.cy.ts new file mode 100644 index 0000000000000..148011c327502 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/basics.cy.ts @@ -0,0 +1,29 @@ +describe('Basics', () => { + beforeEach(() => { + cy.visit('/').waitForRouteChange() + }) + + it('should display index page', () => { + cy.get('h1').should('contain', 'Adapters') + cy.title().should('eq', 'Adapters E2E') + }) + // If this test fails, run "gatsby build" and retry + it('should serve assets from "static" folder', () => { + cy.intercept("/gatsby-icon.png").as("static-folder-image") + + cy.wait("@static-folder-image").should(req => { + expect(req.response.statusCode).to.be.gte(200).and.lt(400) + }) + + cy.get('[alt="Gatsby Monogram Logo"]').should('be.visible') + }) + it('should serve assets imported through webpack', () => { + cy.intercept("/static/astro-**.png").as("img-import") + + cy.wait("@img-import").should(req => { + expect(req.response.statusCode).to.be.gte(200).and.lt(400) + }) + + cy.get('[alt="Gatsby Astronaut"]').should('be.visible') + }) +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/functions.cy.ts b/e2e-tests/adapters/cypress/e2e/functions.cy.ts new file mode 100644 index 0000000000000..b69c4048c86d5 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/functions.cy.ts @@ -0,0 +1,27 @@ +const routes = [ + { + name: 'static', + param: '', + }, + { + name: 'param', + param: 'dune', + }, + { + name: 'wildcard', + param: 'atreides/harkonnen' + }, + { + name: 'named-wildcard', + param: 'corinno/fenring' + } +] as const + +describe('Functions', () => { + for (const route of routes) { + it(`should return "${route.name}" result`, () => { + cy.request(`/api/${route.name}${route.param ? `/${route.param}` : ''}`).as(`req-${route.name}`) + cy.get(`@req-${route.name}`).its('body').should('contain', `Hello World${route.param ? ` from ${route.param}` : ``}`) + }) + } +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/smoke.cy.ts b/e2e-tests/adapters/cypress/e2e/smoke.cy.ts deleted file mode 100644 index 9c1d8337ffc0e..0000000000000 --- a/e2e-tests/adapters/cypress/e2e/smoke.cy.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('Smoke test', () => { - it('should visit homepage', () => { - cy.visit('/') - }) -}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/support/e2e.ts b/e2e-tests/adapters/cypress/support/e2e.ts index 885630a9c2527..b1cdce8d51fd9 100644 --- a/e2e-tests/adapters/cypress/support/e2e.ts +++ b/e2e-tests/adapters/cypress/support/e2e.ts @@ -1 +1,3 @@ -// https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Support-file \ No newline at end of file +// https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Support-file + +import "gatsby-cypress" diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts index b5f30418a12c0..40798d52ce7ab 100644 --- a/e2e-tests/adapters/gatsby-config.ts +++ b/e2e-tests/adapters/gatsby-config.ts @@ -14,8 +14,10 @@ if (shouldUseDebugAdapter) { const config: GatsbyConfig = { siteMetadata: { - title: `adapters`, + title: "adapters", + siteDescription: "E2E tests for Gatsby adapters", }, + trailingSlash: "never", plugins: [], ...configOverrides, } diff --git a/e2e-tests/adapters/package.json b/e2e-tests/adapters/package.json index 675e872b3a7a7..f833c0c7fb5c8 100644 --- a/e2e-tests/adapters/package.json +++ b/e2e-tests/adapters/package.json @@ -11,6 +11,7 @@ "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 node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --e2e --config-file \"cypress/configs/$ADAPTER.ts\"", "ssat:template": "cross-env-shell ADAPTER=$ADAPTER start-server-and-test", @@ -27,6 +28,7 @@ "devDependencies": { "cross-env": "^7.0.3", "cypress": "^12.14.0", + "gatsby-cypress": "^3.11.0", "netlify-cli": "^15.5.1", "npm-run-all": "^4.1.5", "start-server-and-test": "^2.0.0", diff --git a/e2e-tests/adapters/src/api/named-wildcard/[...slug].js b/e2e-tests/adapters/src/api/named-wildcard/[...slug].js new file mode 100644 index 0000000000000..39c741258ffa8 --- /dev/null +++ b/e2e-tests/adapters/src/api/named-wildcard/[...slug].js @@ -0,0 +1,3 @@ +export default function (req, res) { + res.send(`Hello World from ${req.params.slug}`) +} diff --git a/e2e-tests/adapters/src/api/param/[slug].js b/e2e-tests/adapters/src/api/param/[slug].js new file mode 100644 index 0000000000000..39c741258ffa8 --- /dev/null +++ b/e2e-tests/adapters/src/api/param/[slug].js @@ -0,0 +1,3 @@ +export default function (req, res) { + res.send(`Hello World from ${req.params.slug}`) +} diff --git a/e2e-tests/adapters/src/api/static/index.js b/e2e-tests/adapters/src/api/static/index.js new file mode 100644 index 0000000000000..a98959dc324be --- /dev/null +++ b/e2e-tests/adapters/src/api/static/index.js @@ -0,0 +1,3 @@ +export default function (req, res) { + res.send(`Hello World`) +} diff --git a/e2e-tests/adapters/src/api/wildcard/[...].js b/e2e-tests/adapters/src/api/wildcard/[...].js new file mode 100644 index 0000000000000..c519fa4ce7e9d --- /dev/null +++ b/e2e-tests/adapters/src/api/wildcard/[...].js @@ -0,0 +1,3 @@ +export default function (req, res) { + res.send(`Hello World from ${req.params['*']}`) +} diff --git a/e2e-tests/adapters/src/components/layout.jsx b/e2e-tests/adapters/src/components/layout.jsx index c721d6b412cef..39a9289463078 100644 --- a/e2e-tests/adapters/src/components/layout.jsx +++ b/e2e-tests/adapters/src/components/layout.jsx @@ -18,7 +18,7 @@ export default Layout const pageStyles = { color: "#232129", - padding: 96, + padding: 72, fontFamily: "-apple-system, Roboto, sans-serif, serif", } diff --git a/e2e-tests/adapters/src/images/astro.png b/e2e-tests/adapters/src/images/astro.png new file mode 100644 index 0000000000000000000000000000000000000000..3844bf5975b11ddc9a303c46b83a96521eecf987 GIT binary patch literal 17634 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

UHf> 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 R^|Id+(7vmu{#Fee^?z+!bVL9E diff --git a/e2e-tests/adapters/src/pages/404.jsx b/e2e-tests/adapters/src/pages/404.jsx index d7b40627e81ae..a9c4c826920b1 100644 --- a/e2e-tests/adapters/src/pages/404.jsx +++ b/e2e-tests/adapters/src/pages/404.jsx @@ -6,6 +6,7 @@ const pageStyles = { padding: "96px", fontFamily: "-apple-system, Roboto, sans-serif, serif", } + const headingStyles = { marginTop: 0, marginBottom: 64, @@ -15,13 +16,7 @@ const headingStyles = { const paragraphStyles = { marginBottom: 48, } -const codeStyles = { - color: "#8A6534", - padding: 4, - backgroundColor: "#FFF4DB", - fontSize: "1.25rem", - borderRadius: 4, -} + const NotFoundPage = () => { return ( @@ -30,14 +25,6 @@ const NotFoundPage = () => {

Sorry 😔, we couldn’t find what you were looking for.
- {process.env.NODE_ENV === "development" ? ( - <> -
- Try creating a page in src/pages/. -
- - ) : null} -
Go home.

diff --git a/e2e-tests/adapters/src/pages/index.jsx b/e2e-tests/adapters/src/pages/index.jsx index b037c384f7b6b..d0c27e95cf6e4 100644 --- a/e2e-tests/adapters/src/pages/index.jsx +++ b/e2e-tests/adapters/src/pages/index.jsx @@ -1,8 +1,9 @@ import * as React from "react" import { Link } from "gatsby" import Layout from "../components/layout" +import gatsbyAstronaut from "../images/astro.png" -const links = [ +const routes = [ { text: "Static", url: "/routes/static", @@ -17,18 +18,50 @@ const links = [ }, ] +const functions = [ + { + text: "Functions (Static)", + url: "/api/static", + }, + { + text: "Functions (Param)", + url: "/api/param/dune", + }, + { + text: "Functions (Wildcard)", + url: "/api/wildcard/atreides/harkonnen", + }, + { + text: "Functions (Named Wildcard)", + url: "/api/named-wildcard/corinno/fenring", + }, +] + const IndexPage = () => { return ( -

Adapters

+
+ Gatsby Astronaut +
+
+ Gatsby Monogram Logo +

Adapters

+
    - {links.map(link => ( + {routes.map(link => (
  • {link.text}
  • ))} + {functions.map(link => ( +
  • + + {link.text} + +
  • + ))}
) @@ -36,11 +69,36 @@ const IndexPage = () => { export default IndexPage -export const Head = () => Home +export const Head = () => ( + <> + Adapters E2E + + +) + +const titleStyles = { + display: "flex", + alignItems: "center", + flexDirection: "row", +} + +const astroWrapper = { + transform: "rotate(-20deg)", + position: "absolute", + top: "1.25rem", + right: "1.25rem" +} + +const astro = { + maxHeight: "128px", + animationName: "float", + animationDuration: "5s", + animationIterationCount: "infinite", +} const listStyles = { marginTop: 30, - marginBottom: 96, + marginBottom: 72, paddingLeft: 0, } @@ -48,7 +106,7 @@ const listItemStyles = { fontWeight: 300, fontSize: 24, maxWidth: 560, - marginBottom: 16, + marginBottom: 8, } const linkStyle = { diff --git a/e2e-tests/adapters/static/gatsby-icon.png b/e2e-tests/adapters/static/gatsby-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..908bc78a7f5596fab5a638d6ac07e331dc4c74d4 GIT binary patch literal 21212 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 Pwx}U%bK8?;ra1Edyv(7f literal 0 HcmV?d00001 From 0fc4bd1ab5a10fd5023207cd6e2b62b9593ecfb8 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 15 Jun 2023 15:52:33 +0200 Subject: [PATCH 097/161] e2e: client-only WIP --- .../adapters/cypress/e2e/client-only.cy.ts | 55 +++++++++++++++++++ e2e-tests/adapters/src/pages/index.jsx | 24 ++++++-- .../src/pages/routes/client-only/[id].jsx | 13 +++++ .../client-only/named-wildcard/[...slug].jsx | 13 +++++ .../routes/client-only/wildcard/[...].jsx | 13 +++++ e2e-tests/adapters/src/pages/routes/dsg.jsx | 1 - .../adapters/src/pages/routes/static.jsx | 1 - .../src/pages/routes/sub-router/[...].jsx | 48 ++++++++++++++++ .../src/pages/routes/sub-router/static.jsx | 15 +++++ 9 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 e2e-tests/adapters/cypress/e2e/client-only.cy.ts create mode 100644 e2e-tests/adapters/src/pages/routes/client-only/[id].jsx create mode 100644 e2e-tests/adapters/src/pages/routes/client-only/named-wildcard/[...slug].jsx create mode 100644 e2e-tests/adapters/src/pages/routes/client-only/wildcard/[...].jsx create mode 100644 e2e-tests/adapters/src/pages/routes/sub-router/[...].jsx create mode 100644 e2e-tests/adapters/src/pages/routes/sub-router/static.jsx diff --git a/e2e-tests/adapters/cypress/e2e/client-only.cy.ts b/e2e-tests/adapters/cypress/e2e/client-only.cy.ts new file mode 100644 index 0000000000000..31693bc350a59 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/client-only.cy.ts @@ -0,0 +1,55 @@ +describe('Sub-Router', () => { + const routes = [ + { + path: "/routes/sub-router", + marker: "index", + label: "Index route" + }, + { + path: `/routes/sub-router/page/profile`, + marker: `profile`, + label: `Dynamic route`, + }, + { + path: `/routes/sub-router/not-found`, + marker: `NotFound`, + label: `Default route (not found)`, + }, + { + path: `/routes/sub-router/nested`, + marker: `nested-page/index`, + label: `Index route inside nested router`, + }, + { + path: `/routes/sub-router/nested/foo`, + marker: `nested-page/foo`, + label: `Dynamic route inside nested router`, + }, + { + path: `/routes/sub-router/static`, + marker: `static-sibling`, + label: `Static route that is a sibling to client only path`, + }, + ] + + routes.forEach(({ path, marker, label }) => { + it(label, () => { + cy.on('uncaught:exception', (err, runnable) => { + if (err.message.includes('Minified React error')) { + return false + } + }) + cy.visit(path).waitForRouteChange() + cy.get(`[data-testid="dom-marker"]`).contains(marker) + + cy.url().should( + `match`, + new RegExp(`^${Cypress.config().baseUrl + path}/?$`) + ) + }) + }) +}) + +describe('Paths', () => { + // TODO +}) \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/index.jsx b/e2e-tests/adapters/src/pages/index.jsx index d0c27e95cf6e4..8dfbdf471696c 100644 --- a/e2e-tests/adapters/src/pages/index.jsx +++ b/e2e-tests/adapters/src/pages/index.jsx @@ -16,6 +16,22 @@ const routes = [ text: "DSG", url: "/routes/dsg", }, + { + text: "Sub-Router", + url: "/routes/sub-router", + }, + { + text: "Client-Only Params", + url: "/routes/client-only/dune", + }, + { + text: "Client-Only Wildcard", + url: "/routes/client-only/wildcard/atreides/harkonnen", + }, + { + text: "Client-Only Named Wildcard", + url: "/routes/client-only/named-wildcard/corinno/fenring", + } ] const functions = [ @@ -72,7 +88,7 @@ export default IndexPage export const Head = () => ( <> Adapters E2E - + ) @@ -83,10 +99,10 @@ const titleStyles = { } const astroWrapper = { - transform: "rotate(-20deg)", + transform: "rotate(-18deg)", position: "absolute", - top: "1.25rem", - right: "1.25rem" + top: "1.5rem", + right: "1.75rem" } const astro = { diff --git a/e2e-tests/adapters/src/pages/routes/client-only/[id].jsx b/e2e-tests/adapters/src/pages/routes/client-only/[id].jsx new file mode 100644 index 0000000000000..d369944c269e4 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/client-only/[id].jsx @@ -0,0 +1,13 @@ +import React from "react" +import Layout from "../../../components/layout" + +const ClientOnlyParams = props => ( + +

client-only

+

{props.params.id}

+
+) + +export const Head = () => Client-Only Params + +export default ClientOnlyParams diff --git a/e2e-tests/adapters/src/pages/routes/client-only/named-wildcard/[...slug].jsx b/e2e-tests/adapters/src/pages/routes/client-only/named-wildcard/[...slug].jsx new file mode 100644 index 0000000000000..d36f7910629fb --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/client-only/named-wildcard/[...slug].jsx @@ -0,0 +1,13 @@ +import React from "react" +import Layout from "../../../../components/layout" + +const ClientOnlyNamedWildcard = props => ( + +

client-only/named-wildcard

+

{props.params.slug}

+
+) + +export const Head = () => Client-Only Named Wildcard + +export default ClientOnlyNamedWildcard diff --git a/e2e-tests/adapters/src/pages/routes/client-only/wildcard/[...].jsx b/e2e-tests/adapters/src/pages/routes/client-only/wildcard/[...].jsx new file mode 100644 index 0000000000000..bb30eeff36a12 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/client-only/wildcard/[...].jsx @@ -0,0 +1,13 @@ +import React from "react" +import Layout from "../../../../components/layout" + +const ClientOnlyWildcard = props => ( + +

client-only/wildcard

+

{props.params["*"]}

+
+) + +export const Head = () => Client-Only Wildcard + +export default ClientOnlyWildcard diff --git a/e2e-tests/adapters/src/pages/routes/dsg.jsx b/e2e-tests/adapters/src/pages/routes/dsg.jsx index 0f8f22880f550..1d45392347b39 100644 --- a/e2e-tests/adapters/src/pages/routes/dsg.jsx +++ b/e2e-tests/adapters/src/pages/routes/dsg.jsx @@ -5,7 +5,6 @@ const DSG = () => { return (

DSG

-

Hello World!

) } diff --git a/e2e-tests/adapters/src/pages/routes/static.jsx b/e2e-tests/adapters/src/pages/routes/static.jsx index 090cafdb17fa7..709b5b24559f1 100644 --- a/e2e-tests/adapters/src/pages/routes/static.jsx +++ b/e2e-tests/adapters/src/pages/routes/static.jsx @@ -5,7 +5,6 @@ const StaticPage = () => { return (

Static

-

Hello World!

) } diff --git a/e2e-tests/adapters/src/pages/routes/sub-router/[...].jsx b/e2e-tests/adapters/src/pages/routes/sub-router/[...].jsx new file mode 100644 index 0000000000000..564bc2e801504 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/sub-router/[...].jsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { Router } from "@reach/router" +import { Link } from "gatsby" +import Layout from "../../../components/layout" + +const routes = [`/`, `/not-found`, `/page/profile`, `/nested`, `/nested/foo`] +const basePath = `/routes/sub-router` + +const Page = ({ page }) => ( +
[client-only-path] {page}
+) + +const NestedRouterRoute = props => ( + +) + +const PageWithNestedRouter = () => ( + + + + +) + +const NotFound = () => + +const ClientOnlyPathPage = () => ( + + + + + + + +
    + {routes.map(route => ( +
  • + + {route} + +
  • + ))} +
+
+) + +export const Head = () => Sub-Router + +export default ClientOnlyPathPage diff --git a/e2e-tests/adapters/src/pages/routes/sub-router/static.jsx b/e2e-tests/adapters/src/pages/routes/sub-router/static.jsx new file mode 100644 index 0000000000000..2bc278990d864 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/sub-router/static.jsx @@ -0,0 +1,15 @@ +import * as React from "react" +import Layout from "../../../components/layout" + +const StaticPage = () => { + return ( + +

Static

+
[client-only-path] static-sibling
+
+ ) +} + +export default StaticPage + +export const Head = () => Static \ No newline at end of file From 047be986c178b84885ae6d4c0fbe75e67df439fc Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 16 Jun 2023 09:05:45 +0200 Subject: [PATCH 098/161] e2e: improve basics --- e2e-tests/adapters/cypress/e2e/basics.cy.ts | 21 +++++-- e2e-tests/adapters/src/pages/index.css | 49 ++++++++++++++++ e2e-tests/adapters/src/pages/index.jsx | 65 ++++----------------- 3 files changed, 76 insertions(+), 59 deletions(-) create mode 100644 e2e-tests/adapters/src/pages/index.css diff --git a/e2e-tests/adapters/cypress/e2e/basics.cy.ts b/e2e-tests/adapters/cypress/e2e/basics.cy.ts index 148011c327502..0f6902bb81d80 100644 --- a/e2e-tests/adapters/cypress/e2e/basics.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/basics.cy.ts @@ -1,5 +1,8 @@ describe('Basics', () => { beforeEach(() => { + cy.intercept("/gatsby-icon.png").as("static-folder-image") + cy.intercept("/static/astro-**.png").as("img-import") + cy.visit('/').waitForRouteChange() }) @@ -9,8 +12,6 @@ describe('Basics', () => { }) // If this test fails, run "gatsby build" and retry it('should serve assets from "static" folder', () => { - cy.intercept("/gatsby-icon.png").as("static-folder-image") - cy.wait("@static-folder-image").should(req => { expect(req.response.statusCode).to.be.gte(200).and.lt(400) }) @@ -18,12 +19,24 @@ describe('Basics', () => { cy.get('[alt="Gatsby Monogram Logo"]').should('be.visible') }) it('should serve assets imported through webpack', () => { - cy.intercept("/static/astro-**.png").as("img-import") - cy.wait("@img-import").should(req => { expect(req.response.statusCode).to.be.gte(200).and.lt(400) }) cy.get('[alt="Gatsby Astronaut"]').should('be.visible') }) + it(`should show custom 404 page on invalid URL`, () => { + cy.visit(`/non-existent-page`, { + failOnStatusCode: false, + }) + + cy.get('h1').should('contain', 'Page not found') + }) + it('should apply CSS', () => { + cy.get(`h1`).should( + `have.css`, + `color`, + `rgb(21, 21, 22)` + ) + }) }) \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/index.css b/e2e-tests/adapters/src/pages/index.css new file mode 100644 index 0000000000000..2af5c216e1316 --- /dev/null +++ b/e2e-tests/adapters/src/pages/index.css @@ -0,0 +1,49 @@ +@keyframes float { + 50% { + transform: translateY(24px); + } +} + +.titleStyles { + display: flex; + align-items: center; + flex-direction: row; + color: rgb(21, 21, 22); +} + +.astroWrapper { + transform: rotate(-18deg); + position: absolute; + top: 1.5rem; + right: 1.75rem; +} + +.astro { + max-height: 128px; + animation-name: float; + animation-duration: 5s; + animation-iteration-count: infinite; +} + +.listStyles { + margin-top: 30px; + margin-bottom: 72px; + padding-left: 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-gap: 8px; +} + +.listItemStyles { + font-weight: 300; + font-size: 24px; + max-width: 560px; +} + +.linkStyle { + color: #8954a8; + font-weight: bold; + font-size: 16px; + vertical-align: 5%; + text-decoration: none; +} diff --git a/e2e-tests/adapters/src/pages/index.jsx b/e2e-tests/adapters/src/pages/index.jsx index 8dfbdf471696c..413e2784d8e6d 100644 --- a/e2e-tests/adapters/src/pages/index.jsx +++ b/e2e-tests/adapters/src/pages/index.jsx @@ -2,6 +2,7 @@ import * as React from "react" import { Link } from "gatsby" import Layout from "../components/layout" import gatsbyAstronaut from "../images/astro.png" +import "./index.css" const routes = [ { @@ -56,24 +57,24 @@ const functions = [ const IndexPage = () => { return ( -
- Gatsby Astronaut +
+ Gatsby Astronaut
-
+
Gatsby Monogram Logo

Adapters

-
    +
      {routes.map(link => ( -
    • - +
    • + {link.text}
    • ))} {functions.map(link => ( -
    • - +
    • + {link.text}
    • @@ -85,50 +86,4 @@ const IndexPage = () => { export default IndexPage -export const Head = () => ( - <> - Adapters E2E - - -) - -const titleStyles = { - display: "flex", - alignItems: "center", - flexDirection: "row", -} - -const astroWrapper = { - transform: "rotate(-18deg)", - position: "absolute", - top: "1.5rem", - right: "1.75rem" -} - -const astro = { - maxHeight: "128px", - animationName: "float", - animationDuration: "5s", - animationIterationCount: "infinite", -} - -const listStyles = { - marginTop: 30, - marginBottom: 72, - paddingLeft: 0, -} - -const listItemStyles = { - fontWeight: 300, - fontSize: 24, - maxWidth: 560, - marginBottom: 8, -} - -const linkStyle = { - color: "#8954A8", - fontWeight: "bold", - fontSize: 16, - verticalAlign: "5%", - textDecoration: "none", -} +export const Head = () => Adapters E2E From c5329783e061b96d08059dde180587c96a8580bf Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 16 Jun 2023 09:06:02 +0200 Subject: [PATCH 099/161] e2e: improve client-only --- .../adapters/cypress/e2e/client-only.cy.ts | 50 ++++++++++++++++--- .../routes/client-only/prioritize/[...].jsx | 13 +++++ .../routes/client-only/prioritize/index.jsx | 12 +++++ 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 e2e-tests/adapters/src/pages/routes/client-only/prioritize/[...].jsx create mode 100644 e2e-tests/adapters/src/pages/routes/client-only/prioritize/index.jsx diff --git a/e2e-tests/adapters/cypress/e2e/client-only.cy.ts b/e2e-tests/adapters/cypress/e2e/client-only.cy.ts index 31693bc350a59..6d63c3b8befbb 100644 --- a/e2e-tests/adapters/cypress/e2e/client-only.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/client-only.cy.ts @@ -1,3 +1,9 @@ +Cypress.on('uncaught:exception', (err) => { + if (err.message.includes('Minified React error')) { + return false + } +}) + describe('Sub-Router', () => { const routes = [ { @@ -30,15 +36,10 @@ describe('Sub-Router', () => { marker: `static-sibling`, label: `Static route that is a sibling to client only path`, }, - ] + ] as const routes.forEach(({ path, marker, label }) => { it(label, () => { - cy.on('uncaught:exception', (err, runnable) => { - if (err.message.includes('Minified React error')) { - return false - } - }) cy.visit(path).waitForRouteChange() cy.get(`[data-testid="dom-marker"]`).contains(marker) @@ -51,5 +52,38 @@ describe('Sub-Router', () => { }) describe('Paths', () => { - // TODO -}) \ No newline at end of file + const routes = [ + { + name: 'client-only', + param: 'dune', + }, + { + name: 'client-only/wildcard', + param: 'atreides/harkonnen', + }, + { + 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.get("[data-testid=title]").should("contain", route.name) + cy.get("[data-testid=params]").should("contain", 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("contain", "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("contain", "client-only/prioritize matchpath") + cy.get("[data-testid=params]").should("contain", "nested") + }) +}) diff --git a/e2e-tests/adapters/src/pages/routes/client-only/prioritize/[...].jsx b/e2e-tests/adapters/src/pages/routes/client-only/prioritize/[...].jsx new file mode 100644 index 0000000000000..b19c063843ddf --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/client-only/prioritize/[...].jsx @@ -0,0 +1,13 @@ +import React from "react" +import Layout from "../../../../components/layout" + +const ClientOnlyPrioritizeMatchPath = props => ( + +

      client-only/prioritize matchpath

      +

      {props.params["*"]}

      +
      +) + +export const Head = () => Client-Only Prioritize Matchpath + +export default ClientOnlyPrioritizeMatchPath diff --git a/e2e-tests/adapters/src/pages/routes/client-only/prioritize/index.jsx b/e2e-tests/adapters/src/pages/routes/client-only/prioritize/index.jsx new file mode 100644 index 0000000000000..a03a354944955 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/client-only/prioritize/index.jsx @@ -0,0 +1,12 @@ +import React from "react" +import Layout from "../../../../components/layout" + +const ClientOnlyPrioritizeStatic = props => ( + +

      client-only/prioritize static

      +
      +) + +export const Head = () => Client-Only Prioritize static + +export default ClientOnlyPrioritizeStatic From 1740f9e9a481666cc853e28d498c38ea2e18233d Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 16 Jun 2023 09:46:18 +0200 Subject: [PATCH 100/161] e2e: redirects --- .../adapters/cypress/e2e/redirects.cy.ts | 51 +++++++++++++++++++ e2e-tests/adapters/gatsby-node.ts | 12 +++++ .../src/pages/routes/redirect/existing.jsx | 14 +++++ .../src/pages/routes/redirect/hit.jsx | 14 +++++ 4 files changed, 91 insertions(+) create mode 100644 e2e-tests/adapters/cypress/e2e/redirects.cy.ts create mode 100644 e2e-tests/adapters/gatsby-node.ts create mode 100644 e2e-tests/adapters/src/pages/routes/redirect/existing.jsx create mode 100644 e2e-tests/adapters/src/pages/routes/redirect/hit.jsx diff --git a/e2e-tests/adapters/cypress/e2e/redirects.cy.ts b/e2e-tests/adapters/cypress/e2e/redirects.cy.ts new file mode 100644 index 0000000000000..901f577b6e087 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/redirects.cy.ts @@ -0,0 +1,51 @@ +Cypress.on("uncaught:exception", (err) => { + if (err.message.includes("Minified React error")) { + return false + } +}) + +describe("Redirects", () => { + it("should redirect from non-existing page to existing", () => { + cy.visit(`/redirect`, { + failOnStatusCode: false, + }).waitForRouteChange() + + cy.get(`h1`).should(`contain`, `Hit`) + cy.url().should(`equal`, `${window.location.origin}/routes/redirect/hit`) + }) + it("should respect that pages take precedence over redirects", () => { + cy.visit(`/routes/redirect/existing`, { + failOnStatusCode: false, + }).waitForRouteChange() + + cy.get(`h1`).should(`contain`, `Existing`) + cy.url().should(`equal`, `${window.location.origin}/routes/redirect/existing`) + }) + it("should support hash parameter on direct visit", () => { + cy.visit(`/redirect#anchor`, { + failOnStatusCode: false, + }).waitForRouteChange() + + cy.location(`pathname`).should(`equal`, `/routes/redirect/hit`) + cy.location(`hash`).should(`equal`, `#anchor`) + cy.location(`search`).should(`equal`, ``) + }) + it("should support search parameter on direct visit", () => { + cy.visit(`/redirect/?query_param=hello`, { + failOnStatusCode: false, + }).waitForRouteChange() + + cy.location(`pathname`).should(`equal`, `/routes/redirect/hit`) + cy.location(`hash`).should(`equal`, ``) + cy.location(`search`).should(`equal`, `?query_param=hello`) + }) + it("should support search & hash parameter on direct visit", () => { + cy.visit(`/redirect/?query_param=hello#anchor`, { + failOnStatusCode: false, + }).waitForRouteChange() + + cy.location(`pathname`).should(`equal`, `/routes/redirect/hit`) + cy.location(`hash`).should(`equal`, `#anchor`) + cy.location(`search`).should(`equal`, `?query_param=hello`) + }) +}) \ No newline at end of file diff --git a/e2e-tests/adapters/gatsby-node.ts b/e2e-tests/adapters/gatsby-node.ts new file mode 100644 index 0000000000000..c194af4a7b789 --- /dev/null +++ b/e2e-tests/adapters/gatsby-node.ts @@ -0,0 +1,12 @@ +import type { GatsbyNode } from "gatsby" + +export const createPages: GatsbyNode["createPages"] = ({ actions: { createRedirect } }) => { + createRedirect({ + fromPath: "/redirect", + toPath: "/routes/redirect/hit" + }) + createRedirect({ + fromPath: "/routes/redirect/existing", + toPath: "/routes/redirect/hit" + }) +} \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/routes/redirect/existing.jsx b/e2e-tests/adapters/src/pages/routes/redirect/existing.jsx new file mode 100644 index 0000000000000..24d70c8c9fa7b --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/redirect/existing.jsx @@ -0,0 +1,14 @@ +import * as React from "react" +import Layout from "../../../components/layout" + +const ExistingPage = () => { + return ( + +

      Existing

      +
      + ) +} + +export default ExistingPage + +export const Head = () => Existing \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/routes/redirect/hit.jsx b/e2e-tests/adapters/src/pages/routes/redirect/hit.jsx new file mode 100644 index 0000000000000..3d7bdda1fc222 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/redirect/hit.jsx @@ -0,0 +1,14 @@ +import * as React from "react" +import Layout from "../../../components/layout" + +const HitPage = () => { + return ( + +

      Hit

      +
      + ) +} + +export default HitPage + +export const Head = () => Hit \ No newline at end of file From 1e31d1979ccce8c7f46ac7896ece75b7526859dd Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 16 Jun 2023 12:00:28 +0200 Subject: [PATCH 101/161] merge _headers and _redirects instead of overwriting it --- .../src/route-handler.ts | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index 2a8d2e3346989..4b7fe44329fa9 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -1,5 +1,6 @@ import type { RoutesManifest } from "gatsby" +import { EOL } from "os" import fs from "fs-extra" const NETLIFY_REDIRECT_KEYWORDS_ALLOWLIST = new Set([ @@ -20,6 +21,31 @@ const toNetlifyPath = (fromPath: string, toPath: string): Array => { return [netlifyFromPath, netlifyToPath] } +const MARKER_START = `# gatsby-adapter-netlify start` +const MARKER_END = `# gatsby-adapter-netlify end` + +async function injectEntries(fileName: string, content: string): Promise { + await fs.ensureFile(fileName) + + const data = await fs.readFile(fileName, `utf8`) + const [initial = ``, rest = ``] = data.split(MARKER_START) + const [, final = ``] = rest.split(MARKER_END) + const out = [ + initial === EOL ? `` : initial, + initial.endsWith(EOL) ? `` : EOL, + MARKER_START, + EOL, + content, + EOL, + MARKER_END, + final.startsWith(EOL) ? `` : EOL, + final === EOL ? `` : final, + ] + .filter(Boolean) + .join(``) + + await fs.outputFile(fileName, out) +} export async function handleRoutesManifest( routesManifest: RoutesManifest @@ -111,9 +137,8 @@ export async function handleRoutesManifest( } } - // TODO: add markers around generated redirects and headers so we can update them and merge with user provided ones - await fs.outputFile(`public/_redirects`, _redirects) - await fs.outputFile(`public/_headers`, _headers) + await injectEntries(`public/_redirects`, _redirects) + await injectEntries(`public/_headers`, _headers) return { lambdasThatUseCaching, From 30f12b099d5b642d3f463a49c66bdbca784dc52c Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 26 Jun 2023 11:50:31 +0200 Subject: [PATCH 102/161] apply trailing slash option + pass through trailingSlash & pathPrefix --- .../__tests__/apply-trailing-slash-option.ts | 8 +++++++ .../src/apply-trailing-slash-option.ts | 24 ++++++++++++------- packages/gatsby/src/utils/adapter/manager.ts | 14 ++++++++++- packages/gatsby/src/utils/adapter/types.ts | 12 ++++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/packages/gatsby-page-utils/src/__tests__/apply-trailing-slash-option.ts b/packages/gatsby-page-utils/src/__tests__/apply-trailing-slash-option.ts index 92b8bd877c951..c22ce92f43b2f 100644 --- a/packages/gatsby-page-utils/src/__tests__/apply-trailing-slash-option.ts +++ b/packages/gatsby-page-utils/src/__tests__/apply-trailing-slash-option.ts @@ -8,6 +8,14 @@ describe(`applyTrailingSlashOption`, () => { it(`returns / for root index page`, () => { expect(applyTrailingSlashOption(indexPage)).toEqual(indexPage) }) + it(`should leave non-trailing paths for certain suffixes`, () => { + expect(applyTrailingSlashOption(`/nested/path.html`)).toEqual( + `/nested/path.html` + ) + expect(applyTrailingSlashOption(`/nested/path.xml`)).toEqual( + `/nested/path.xml` + ) + }) describe(`always`, () => { it(`should add trailing slash`, () => { expect(applyTrailingSlashOption(withoutSlash, `always`)).toEqual( diff --git a/packages/gatsby-page-utils/src/apply-trailing-slash-option.ts b/packages/gatsby-page-utils/src/apply-trailing-slash-option.ts index 4a7266a52d568..ae91cd26059a0 100644 --- a/packages/gatsby-page-utils/src/apply-trailing-slash-option.ts +++ b/packages/gatsby-page-utils/src/apply-trailing-slash-option.ts @@ -1,22 +1,30 @@ export type TrailingSlash = "always" | "never" | "ignore" +const endsWithSuffixes = (suffixes: Array, input): boolean => { + for (const suffix of suffixes) { + if (input.endsWith(suffix)) return true + } + return false +} + +const suffixes = [`.html`, `.json`, `.js`, `.map`, `.txt`, `.xml`, `.pdf`] + export const applyTrailingSlashOption = ( input: string, option: TrailingSlash = `always` ): string => { - const hasHtmlSuffix = input.endsWith(`.html`) - const hasXmlSuffix = input.endsWith(`.xml`) - const hasPdfSuffix = input.endsWith(`.pdf`) - if (input === `/`) return input - if (hasHtmlSuffix || hasXmlSuffix || hasPdfSuffix) { - option = `never` + + const hasTrailingSlash = input.endsWith(`/`) + + if (endsWithSuffixes(suffixes, input)) { + return input } if (option === `always`) { - return input.endsWith(`/`) ? input : `${input}/` + return hasTrailingSlash ? input : `${input}/` } if (option === `never`) { - return input.endsWith(`/`) ? input.slice(0, -1) : input + return hasTrailingSlash ? input.slice(0, -1) : input } return input diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 5e4dedd947d0d..1071449509dd5 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -1,4 +1,5 @@ import reporter from "gatsby-cli/lib/reporter" +import { applyTrailingSlashOption, TrailingSlash } from "gatsby-page-utils" import { generateHtmlPath } from "gatsby-core-utils/page-html" import { generatePageDataPath } from "gatsby-core-utils/page-data" import { posix } from "path" @@ -50,7 +51,7 @@ export async function initAdapterManager(): Promise { let adapter: IAdapter const config = store.getState().config - const { adapter: adapterFromGatsbyConfig } = config + const { adapter: adapterFromGatsbyConfig, trailingSlash, pathPrefix } = config // If the user specified an adapter inside their gatsby-config, use that instead of trying to figure out an adapter for the current environment if (adapterFromGatsbyConfig) { @@ -138,6 +139,9 @@ export async function initAdapterManager(): Promise { return _functionsManifest }, reporter, + // Our internal Gatsby config allows this to be undefined but for the adapter we should always pass through the default values and correctly show this in the TypeScript types + trailingSlash: trailingSlash as TrailingSlash, + pathPrefix: pathPrefix as string, } await adapter.adapt(adaptContext) @@ -171,6 +175,14 @@ function getRoutesManifest(): RoutesManifest { route.path = `/${route.path}` } + // Apply trailing slash behavior unless it's a redirect. Redirects should always be exact matches + if (route.type !== `redirect`) { + route.path = applyTrailingSlashOption( + route.path, + state.config.trailingSlash + ) + } + if (route.type !== `function`) { route.headers = createHeaders(route.path, route.headers) } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 8605d0e59d978..7d63bf3cd1c36 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -1,4 +1,5 @@ import type reporter from "gatsby-cli/lib/reporter" +import type { TrailingSlash } from "gatsby-page-utils" import type { IHeader, HttpStatusCode } from "../../redux/types" interface IBaseRoute { @@ -94,6 +95,16 @@ interface IDefaultContext { export interface IAdaptContext extends IDefaultContext { routesManifest: RoutesManifest functionsManifest: FunctionsManifest + /** + * Default: "" + * @see https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/path-prefix/ + */ + pathPrefix: string + /** + * Default: "always" + * @see https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/#trailingslash + */ + trailingSlash: TrailingSlash } export interface ICacheContext extends IDefaultContext { @@ -124,6 +135,7 @@ export interface IAdapter { * - Apply HTTP headers to assets * - Apply redirects and rewrites. The adapter should can also create its own redirects/rewrites if necessary (e.g. mapping serverless functions to internal URLs). * - Wrap serverless functions coming from Gatsby with platform-specific code (if necessary). Gatsby will produce [Express-like](https://expressjs.com/) handlers. + * - Apply trailing slash behavior and path prefix to URLs * - Possibly upload assets to CDN * * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ From cbd0374f6684f874e97379b44e3869afab2b3821 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 26 Jun 2023 15:13:47 +0200 Subject: [PATCH 103/161] add unit tests for manager --- .../__tests__/__snapshots__/manager.ts.snap | 216 ++++++++++++++++++ .../fixtures/.cache/data/datastore/data.mdb | 1 + .../fixtures/.cache/functions/static/index.js | 1 + .../fixtures/.cache/page-ssr/lambda.js | 1 + .../fixtures/.cache/query-engine/index.js | 1 + .../adapter/__tests__/fixtures/.gitignore | 2 + .../__tests__/fixtures/public/app-123.js | 1 + .../__tests__/fixtures/public/index.html | 1 + .../fixtures/public/page-data/app-data.json | 1 + .../public/page-data/index/page-data.json | 1 + .../fixtures/public/page-data/sq/d/1.json | 1 + .../fixtures/public/webpack.stats.json | 1 + .../utils/adapter/__tests__/fixtures/state.ts | 133 +++++++++++ .../src/utils/adapter/__tests__/manager.ts | 86 +++++++ packages/gatsby/src/utils/adapter/manager.ts | 2 + 15 files changed, 449 insertions(+) create mode 100644 packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/data/datastore/data.mdb create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/functions/static/index.js create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/page-ssr/lambda.js create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/query-engine/index.js create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/.gitignore create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/app-123.js create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/index.html create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/app-data.json create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/index/page-data.json create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/sq/d/1.json create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/webpack.stats.json create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts create mode 100644 packages/gatsby/src/utils/adapter/__tests__/manager.ts diff --git a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap new file mode 100644 index 0000000000000..267a5909a5efd --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap @@ -0,0 +1,216 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getRoutesManifest should return routes manifest 1`] = ` +Array [ + Object { + "filePath": "public/page-data/sq/d/1.json", + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/page-data/sq/d/1.json", + "type": "static", + }, + Object { + "cache": true, + "functionId": "ssr-engine", + "path": "/page-data/dsg/page-data.json", + "type": "function", + }, + Object { + "filePath": "public/page-data/index/page-data.json", + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/page-data/index/page-data.json", + "type": "static", + }, + Object { + "functionId": "ssr-engine", + "path": "/page-data/ssr/page-data.json", + "type": "function", + }, + Object { + "functionId": "static-index-js", + "path": "/api/static/", + "type": "function", + }, + Object { + "filePath": "public/page-data/app-data.json", + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/page-data/app-data.json", + "type": "static", + }, + Object { + "filePath": "public/app-123.js", + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=31536000, immutable", + }, + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/app-123.js", + "type": "static", + }, + Object { + "cache": true, + "functionId": "ssr-engine", + "path": "/dsg/", + "type": "function", + }, + Object { + "headers": Array [ + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "ignoreCase": true, + "path": "/old-url", + "status": 301, + "toPath": "/new-url", + "type": "redirect", + }, + Object { + "functionId": "ssr-engine", + "path": "/ssr/", + "type": "function", + }, + Object { + "filePath": "public/webpack.stats.json", + "headers": Array [ + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/webpack.stats.json", + "type": "static", + }, + Object { + "filePath": "public/index.html", + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/", + "type": "static", + }, +] +`; diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/data/datastore/data.mdb b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/data/datastore/data.mdb new file mode 100644 index 0000000000000..625c0891b2c30 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/data/datastore/data.mdb @@ -0,0 +1 @@ +// noop \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/functions/static/index.js b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/functions/static/index.js new file mode 100644 index 0000000000000..625c0891b2c30 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/functions/static/index.js @@ -0,0 +1 @@ +// noop \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/page-ssr/lambda.js b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/page-ssr/lambda.js new file mode 100644 index 0000000000000..625c0891b2c30 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/page-ssr/lambda.js @@ -0,0 +1 @@ +// noop \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/query-engine/index.js b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/query-engine/index.js new file mode 100644 index 0000000000000..625c0891b2c30 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.cache/query-engine/index.js @@ -0,0 +1 @@ +// noop \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/.gitignore b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.gitignore new file mode 100644 index 0000000000000..449d3d123040e --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/.gitignore @@ -0,0 +1,2 @@ +!.cache +!public \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/app-123.js b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/app-123.js new file mode 100644 index 0000000000000..625c0891b2c30 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/app-123.js @@ -0,0 +1 @@ +// noop \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/index.html b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/index.html new file mode 100644 index 0000000000000..6668b30218583 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/app-data.json b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/app-data.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/app-data.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/index/page-data.json b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/index/page-data.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/index/page-data.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/sq/d/1.json b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/sq/d/1.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/page-data/sq/d/1.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/webpack.stats.json b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/webpack.stats.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/webpack.stats.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts b/packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts new file mode 100644 index 0000000000000..b68132172fda8 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts @@ -0,0 +1,133 @@ +import { IGatsbyState } from "../../../../internal" + +const pages: IGatsbyState["pages"] = new Map() + +pages.set(`/`, { + internalComponentName: 'ComponentIndex', + path: '/', + matchPath: undefined, + component: '/x/src/pages/index.tsx', + componentPath: '/x/src/pages/index.tsx', + componentChunkName: 'component---src-pages-index-tsx', + isCreatedByStatefulCreatePages: true, + context: {}, + updatedAt: 1, + slices: {}, + pluginCreator___NODE: '', + pluginCreatorId: '', + mode: 'SSG', + ownerNodeId: `` +}) + +pages.set(`/dsg`, { + internalComponentName: 'Component/dsg', + path: '/dsg', + matchPath: undefined, + component: '/x/src/pages/dsg.tsx', + componentPath: '/x/src/pages/dsg.tsx', + componentChunkName: 'component---src-pages-dsg-tsx', + isCreatedByStatefulCreatePages: true, + context: {}, + updatedAt: 1, + slices: {}, + pluginCreator___NODE: '', + pluginCreatorId: '', + mode: 'DSG', + ownerNodeId: ``, + defer: true, +}) + +pages.set(`/ssr`, { + internalComponentName: 'Component/ssr', + path: '/ssr', + matchPath: undefined, + component: '/x/src/pages/ssr.tsx', + componentPath: '/x/src/pages/ssr.tsx', + componentChunkName: 'component---src-pages-ssr-tsx', + isCreatedByStatefulCreatePages: true, + context: {}, + updatedAt: 1, + slices: {}, + pluginCreator___NODE: '', + pluginCreatorId: '', + mode: 'SSR', + ownerNodeId: `` +}) + +const staticQueryComponents: IGatsbyState["staticQueryComponents"] = new Map() + +staticQueryComponents.set(`sq--src-pages-index-tsx`, { + name: 'TitleQuery', + componentPath: '/x/src/pages/index.tsx', + id: 'sq--src-pages-index-tsx', + query: 'query BioQuery {\n site {\n siteMetadata {\n title\n }\n }\n}', + hash: '1' +}) + +const redirects: IGatsbyState["redirects"] = [{ + fromPath: '/old-url', + isPermanent: true, + ignoreCase: true, + redirectInBrowser: false, + toPath: '/new-url' +}] + +const functions: IGatsbyState["functions"] = [{ + functionRoute: 'static', + pluginName: 'default-site-plugin', + originalAbsoluteFilePath: '/x/src/api/static/index.js', + originalRelativeFilePath: 'static/index.js', + relativeCompiledFilePath: 'static/index.js', + absoluteCompiledFilePath: '/x/.cache/functions/static/index.js', + matchPath: undefined, + functionId: 'static-index-js' +}] + +const components: IGatsbyState["components"] = new Map() + +components.set('/x/src/pages/dsg.tsx', { + componentPath: '/x/src/pages/dsg.tsx', + componentChunkName: 'component---src-pages-dsg-tsx', + query: '', + pages: new Set([``]), + isInBootstrap: true, + serverData: false, + config: false, + isSlice: false, + Head: true +}) + +components.set('/x/src/pages/index.tsx', { + componentPath: '/x/src/pages/index.tsx', + componentChunkName: 'component---src-pages-index-tsx', + query: '', + pages: new Set([``]), + isInBootstrap: true, + serverData: false, + config: false, + isSlice: false, + Head: true +}) + +components.set('/x/src/pages/ssr.tsx', { + componentPath: '/x/src/pages/ssr.tsx', + componentChunkName: 'component---src-pages-ssr-tsx', + query: '', + pages: new Set([``]), + isInBootstrap: true, + serverData: true, + config: false, + isSlice: false, + Head: false +}) + +export const state = { + pages, + staticQueryComponents, + redirects, + functions, + config: { + headers: [], + }, + components, +} as unknown as IGatsbyState \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts new file mode 100644 index 0000000000000..d5a7a76a775ac --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -0,0 +1,86 @@ +import { store } from "../../../redux" +import { + getRoutesManifest, + getFunctionsManifest, + setWebpackAssets, +} from "../manager" +import { state as stateDefault } from "./fixtures/state" +import { IGatsbyState } from "../../../internal" + +jest.mock(`../../../redux`, () => { + return { + emitter: { + on: jest.fn(), + }, + store: { + getState: jest.fn(), + }, + } +}) + +jest.mock(`../../engines-helpers`, () => { + return { + shouldGenerateEngines: jest.fn().mockReturnValue(true), + } +}) + +function mockStoreState( + state: IGatsbyState, + additionalState: IGatsbyState = {} as IGatsbyState +): void { + const mergedState = { ...state, ...additionalState } + ;(store.getState as jest.Mock).mockReturnValue(mergedState) +} + +const fixturesDir = `${__dirname}/fixtures` + +let cwdToRestore +beforeAll(() => { + cwdToRestore = process.cwd() +}) + +afterAll(() => { + process.chdir(cwdToRestore) +}) + +describe(`getRoutesManifest`, () => { + it(`should return routes manifest`, () => { + mockStoreState(stateDefault) + process.chdir(fixturesDir) + setWebpackAssets(new Set([`app-123.js`])) + + const routesManifest = getRoutesManifest() + + expect(routesManifest).toMatchSnapshot() + }) +}) + +describe(`getFunctionsManifest`, () => { + it(`should return functions manifest`, () => { + mockStoreState(stateDefault) + process.chdir(fixturesDir) + + const functionsManifest = getFunctionsManifest() + + expect(functionsManifest).toMatchInlineSnapshot(` + Array [ + Object { + "functionId": "static-index-js", + "pathToEntryPoint": ".cache/functions/static/index.js", + "requiredFiles": Array [ + ".cache/functions/static/index.js", + ], + }, + Object { + "functionId": "ssr-engine", + "pathToEntryPoint": ".cache/page-ssr/lambda.js", + "requiredFiles": Array [ + ".cache/data/datastore/data.mdb", + ".cache/page-ssr/lambda.js", + ".cache/query-engine/index.js", + ], + }, + ] + `) + }) +}) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 1071449509dd5..4827efc3a21a8 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -391,3 +391,5 @@ function getFunctionsManifest(): FunctionsManifest { return functions } + +export { getRoutesManifest, getFunctionsManifest } From f358c438fa161507d474e0257f957df6739babe9 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Mon, 26 Jun 2023 15:41:02 +0200 Subject: [PATCH 104/161] improve manager tests --- .../src/utils/adapter/__tests__/manager.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index d5a7a76a775ac..2959acea996f3 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -26,7 +26,7 @@ jest.mock(`../../engines-helpers`, () => { function mockStoreState( state: IGatsbyState, - additionalState: IGatsbyState = {} as IGatsbyState + additionalState: Partial = {} ): void { const mergedState = { ...state, ...additionalState } ;(store.getState as jest.Mock).mockReturnValue(mergedState) @@ -53,6 +53,44 @@ describe(`getRoutesManifest`, () => { expect(routesManifest).toMatchSnapshot() }) + + it(`should respect "never" trailingSlash config option`, () => { + mockStoreState(stateDefault, { + config: { ...stateDefault.config, trailingSlash: `never` }, + }) + process.chdir(fixturesDir) + setWebpackAssets(new Set([`app-123.js`])) + + const routesManifest = getRoutesManifest() + + expect(routesManifest).toEqual( + expect.arrayContaining([ + expect.objectContaining({ path: `/` }), + expect.objectContaining({ path: `/ssr` }), + expect.objectContaining({ path: `/dsg` }), + expect.objectContaining({ path: `/api/static` }), + ]) + ) + }) + + it(`should respect "always" trailingSlash config option`, () => { + mockStoreState(stateDefault, { + config: { ...stateDefault.config, trailingSlash: `always` }, + }) + process.chdir(fixturesDir) + setWebpackAssets(new Set([`app-123.js`])) + + const routesManifest = getRoutesManifest() + + expect(routesManifest).toEqual( + expect.arrayContaining([ + expect.objectContaining({ path: `/` }), + expect.objectContaining({ path: `/ssr/` }), + expect.objectContaining({ path: `/dsg/` }), + expect.objectContaining({ path: `/api/static/` }), + ]) + ) + }) }) describe(`getFunctionsManifest`, () => { From d5d1720ac65aad066225caa1e864f6e73e799938 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 28 Jun 2023 14:23:29 +0200 Subject: [PATCH 105/161] update types --- packages/gatsby/src/utils/adapter/types.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 7d63bf3cd1c36..fa150db38fb98 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -96,12 +96,10 @@ export interface IAdaptContext extends IDefaultContext { routesManifest: RoutesManifest functionsManifest: FunctionsManifest /** - * Default: "" - * @see https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/path-prefix/ + * @see https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/#pathprefix */ pathPrefix: string /** - * Default: "always" * @see https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/#trailingslash */ trailingSlash: TrailingSlash From faa4b2af104eea35acdce13df6572254c39ee656 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 28 Jun 2023 16:10:30 +0200 Subject: [PATCH 106/161] improve e2e tests --- e2e-tests/adapters/constants.ts | 2 + e2e-tests/adapters/cypress/e2e/basics.cy.ts | 6 ++- .../adapters/cypress/e2e/client-only.cy.ts | 10 ++--- e2e-tests/adapters/cypress/e2e/dsg.cy.ts | 14 +++++++ .../adapters/cypress/e2e/redirects.cy.ts | 4 +- e2e-tests/adapters/cypress/e2e/slices.cy.ts | 9 +++++ e2e-tests/adapters/cypress/e2e/ssr.cy.ts | 40 +++++++++++++++++++ .../adapters/cypress/e2e/trailing-slash.cy.ts | 30 ++++++++++++++ e2e-tests/adapters/debug-adapter.ts | 6 ++- e2e-tests/adapters/gatsby-config.ts | 5 ++- e2e-tests/adapters/gatsby-node.ts | 9 ++++- e2e-tests/adapters/src/components/footer.jsx | 29 ++++++++++++++ e2e-tests/adapters/src/components/layout.jsx | 3 +- e2e-tests/adapters/src/pages/500.jsx | 31 ++++++++++++++ e2e-tests/adapters/src/pages/index.jsx | 28 ++++++++++--- .../src/pages/routes/dsg/graphql-query.jsx | 34 ++++++++++++++++ .../pages/routes/{dsg.jsx => dsg/static.jsx} | 2 +- e2e-tests/adapters/src/pages/routes/ssr.jsx | 30 -------------- .../src/pages/routes/ssr/error-path.jsx | 11 +++++ .../src/pages/routes/ssr/param/[param].jsx | 30 ++++++++++++++ .../adapters/src/pages/routes/ssr/static.jsx | 36 +++++++++++++++++ 21 files changed, 318 insertions(+), 51 deletions(-) create mode 100644 e2e-tests/adapters/constants.ts create mode 100644 e2e-tests/adapters/cypress/e2e/dsg.cy.ts create mode 100644 e2e-tests/adapters/cypress/e2e/slices.cy.ts create mode 100644 e2e-tests/adapters/cypress/e2e/ssr.cy.ts create mode 100644 e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts create mode 100644 e2e-tests/adapters/src/components/footer.jsx create mode 100644 e2e-tests/adapters/src/pages/500.jsx create mode 100644 e2e-tests/adapters/src/pages/routes/dsg/graphql-query.jsx rename e2e-tests/adapters/src/pages/routes/{dsg.jsx => dsg/static.jsx} (85%) delete mode 100644 e2e-tests/adapters/src/pages/routes/ssr.jsx create mode 100644 e2e-tests/adapters/src/pages/routes/ssr/error-path.jsx create mode 100644 e2e-tests/adapters/src/pages/routes/ssr/param/[param].jsx create mode 100644 e2e-tests/adapters/src/pages/routes/ssr/static.jsx diff --git a/e2e-tests/adapters/constants.ts b/e2e-tests/adapters/constants.ts new file mode 100644 index 0000000000000..ebec159409f08 --- /dev/null +++ b/e2e-tests/adapters/constants.ts @@ -0,0 +1,2 @@ +export const title = "Adapters" +export const siteDescription = "End-to-End tests for Gatsby Adapters" \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/basics.cy.ts b/e2e-tests/adapters/cypress/e2e/basics.cy.ts index 0f6902bb81d80..51707bd32e1c4 100644 --- a/e2e-tests/adapters/cypress/e2e/basics.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/basics.cy.ts @@ -1,3 +1,5 @@ +import { title } from "../../constants" + describe('Basics', () => { beforeEach(() => { cy.intercept("/gatsby-icon.png").as("static-folder-image") @@ -7,7 +9,7 @@ describe('Basics', () => { }) it('should display index page', () => { - cy.get('h1').should('contain', 'Adapters') + cy.get('h1').should('have.text', title) cy.title().should('eq', 'Adapters E2E') }) // If this test fails, run "gatsby build" and retry @@ -30,7 +32,7 @@ describe('Basics', () => { failOnStatusCode: false, }) - cy.get('h1').should('contain', 'Page not found') + cy.get('h1').should('have.text', 'Page not found') }) it('should apply CSS', () => { cy.get(`h1`).should( diff --git a/e2e-tests/adapters/cypress/e2e/client-only.cy.ts b/e2e-tests/adapters/cypress/e2e/client-only.cy.ts index 6d63c3b8befbb..58b24efa98cb1 100644 --- a/e2e-tests/adapters/cypress/e2e/client-only.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/client-only.cy.ts @@ -70,8 +70,8 @@ describe('Paths', () => { for (const route of routes) { it(`should return "${route.name}" result`, () => { cy.visit(`/routes/${route.name}${route.param ? `/${route.param}` : ''}`).waitForRouteChange() - cy.get("[data-testid=title]").should("contain", route.name) - cy.get("[data-testid=params]").should("contain", route.param) + cy.get("[data-testid=title]").should("have.text", route.name) + cy.get("[data-testid=params]").should("have.text", route.param) }) } }) @@ -79,11 +79,11 @@ describe('Paths', () => { 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("contain", "client-only/prioritize static") + 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("contain", "client-only/prioritize matchpath") - cy.get("[data-testid=params]").should("contain", "nested") + cy.get("[data-testid=title]").should("have.text", "client-only/prioritize matchpath") + cy.get("[data-testid=params]").should("have.text", "nested") }) }) diff --git a/e2e-tests/adapters/cypress/e2e/dsg.cy.ts b/e2e-tests/adapters/cypress/e2e/dsg.cy.ts new file mode 100644 index 0000000000000..f1ea9e3e063d4 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/dsg.cy.ts @@ -0,0 +1,14 @@ +import { title } from "../../constants" + +describe("Deferred Static Generation (DSG)", () => { + it("should work correctly", () => { + cy.visit("/routes/dsg/static").waitForRouteChange() + + cy.get("h1").contains("DSG") + }) + it("should work with page queries", () => { + cy.visit("/routes/dsg/graphql-query").waitForRouteChange() + + cy.get(`[data-testid="title"]`).should("have.text", title) + }) +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/redirects.cy.ts b/e2e-tests/adapters/cypress/e2e/redirects.cy.ts index 901f577b6e087..e95346aff37e5 100644 --- a/e2e-tests/adapters/cypress/e2e/redirects.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/redirects.cy.ts @@ -10,7 +10,7 @@ describe("Redirects", () => { failOnStatusCode: false, }).waitForRouteChange() - cy.get(`h1`).should(`contain`, `Hit`) + cy.get(`h1`).should(`have.text`, `Hit`) cy.url().should(`equal`, `${window.location.origin}/routes/redirect/hit`) }) it("should respect that pages take precedence over redirects", () => { @@ -18,7 +18,7 @@ describe("Redirects", () => { failOnStatusCode: false, }).waitForRouteChange() - cy.get(`h1`).should(`contain`, `Existing`) + cy.get(`h1`).should(`have.text`, `Existing`) cy.url().should(`equal`, `${window.location.origin}/routes/redirect/existing`) }) it("should support hash parameter on direct visit", () => { diff --git a/e2e-tests/adapters/cypress/e2e/slices.cy.ts b/e2e-tests/adapters/cypress/e2e/slices.cy.ts new file mode 100644 index 0000000000000..a0778170f0ac6 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/slices.cy.ts @@ -0,0 +1,9 @@ +import { siteDescription } from "../../constants" + +describe("Slices", () => { + it("should work correctly", () => { + cy.visit('/').waitForRouteChange() + + cy.get(`footer`).should("have.text", siteDescription) + }) +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/ssr.cy.ts b/e2e-tests/adapters/cypress/e2e/ssr.cy.ts new file mode 100644 index 0000000000000..ec9d47af6d842 --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/ssr.cy.ts @@ -0,0 +1,40 @@ +const staticPath = "/routes/ssr/static" +const paramPath = "/routes/ssr/param" + +describe("Server Side Rendering (SSR)", () => { + it(`direct visit no query params (${staticPath})`, () => { + cy.visit(staticPath).waitForRouteChange() + cy.get(`[data-testid="query"]`).contains(`{}`) + cy.get(`[data-testid="params"]`).contains(`{}`) + }) + + it(`direct visit with query params (${staticPath})`, () => { + cy.visit(staticPath + `?foo=bar`).waitForRouteChange() + cy.get(`[data-testid="query"]`).contains(`{"foo":"bar"}`) + cy.get(`[data-testid="params"]`).contains(`{}`) + }) + + it(`direct visit no query params (${paramPath})`, () => { + cy.visit(paramPath + `/foo`).waitForRouteChange() + cy.get(`[data-testid="query"]`).contains(`{}`) + cy.get(`[data-testid="params"]`).contains(`{"param":"foo"}`) + }) + + it(`direct visit with query params (${paramPath})`, () => { + cy.visit(paramPath + `/foo` + `?foo=bar`).waitForRouteChange() + cy.get(`[data-testid="query"]`).contains(`{"foo":"bar"}`) + cy.get(`[data-testid="params"]`).contains(`{"param":"foo"}`) + }) + + // TODO: Implement 400 & 500 handling + it.skip(`should display custom 500 page`, () => { + const errorPath = `/routes/ssr/error-path` + + cy.visit(errorPath, { failOnStatusCode: false }).waitForRouteChange() + + cy.location(`pathname`) + .should(`equal`, errorPath) + .get(`h1`) + .should(`have.text`, `INTERNAL SERVER ERROR`) + }) +}) \ No newline at end of file diff --git a/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts b/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts new file mode 100644 index 0000000000000..491a29a2894fd --- /dev/null +++ b/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts @@ -0,0 +1,30 @@ +Cypress.on("uncaught:exception", (err) => { + if (err.message.includes("Minified React error")) { + return false + } +}) + +describe("trailingSlash", () => { + it("should work when using Gatsby Link (without slash)", () => { + cy.visit('/').waitForRouteChange() + + cy.get(`[data-testid="static-without-slash"]`).click().waitForRouteChange() + cy.url().should(`equal`, `${window.location.origin}/routes/static`) + }) + it("should work when using Gatsby Link (with slash)", () => { + cy.visit('/').waitForRouteChange() + + cy.get(`[data-testid="static-with-slash"]`).click().waitForRouteChange() + cy.url().should(`equal`, `${window.location.origin}/routes/static`) + }) + it("should work on direct visit (without slash)", () => { + cy.visit(`/routes/static`).waitForRouteChange() + + cy.url().should(`equal`, `${window.location.origin}/routes/static`) + }) + it("should work on direct visit (with slash)", () => { + cy.visit(`/routes/static/`).waitForRouteChange() + + cy.url().should(`equal`, `${window.location.origin}/routes/static`) + }) +}) diff --git a/e2e-tests/adapters/debug-adapter.ts b/e2e-tests/adapters/debug-adapter.ts index 22e79834876c6..9333d1bff0733 100644 --- a/e2e-tests/adapters/debug-adapter.ts +++ b/e2e-tests/adapters/debug-adapter.ts @@ -15,13 +15,17 @@ const createTestingAdapter: AdapterInit = (adapterOptions) => { adapt({ routesManifest, functionsManifest, + pathPrefix, + trailingSlash, reporter, }) { reporter.info(`[gatsby-adapter-debug] adapt()`) console.log(`[gatsby-adapter-debug] adapt()`, inspect({ routesManifest, - functionsManifest + functionsManifest, + pathPrefix, + trailingSlash, }, { depth: Infinity, colors: true diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts index 40798d52ce7ab..b881c30a77afb 100644 --- a/e2e-tests/adapters/gatsby-config.ts +++ b/e2e-tests/adapters/gatsby-config.ts @@ -1,5 +1,6 @@ import type { GatsbyConfig } from "gatsby" import debugAdapter from "./debug-adapter" +import { siteDescription, title } from "./constants" const shouldUseDebugAdapter = process.env.USE_DEBUG_ADAPTER ?? false @@ -14,8 +15,8 @@ if (shouldUseDebugAdapter) { const config: GatsbyConfig = { siteMetadata: { - title: "adapters", - siteDescription: "E2E tests for Gatsby adapters", + title, + siteDescription, }, trailingSlash: "never", plugins: [], diff --git a/e2e-tests/adapters/gatsby-node.ts b/e2e-tests/adapters/gatsby-node.ts index c194af4a7b789..981b1ae3c4f20 100644 --- a/e2e-tests/adapters/gatsby-node.ts +++ b/e2e-tests/adapters/gatsby-node.ts @@ -1,6 +1,7 @@ +import * as path from "path" import type { GatsbyNode } from "gatsby" -export const createPages: GatsbyNode["createPages"] = ({ actions: { createRedirect } }) => { +export const createPages: GatsbyNode["createPages"] = ({ actions: { createRedirect, createSlice } }) => { createRedirect({ fromPath: "/redirect", toPath: "/routes/redirect/hit" @@ -9,4 +10,10 @@ export const createPages: GatsbyNode["createPages"] = ({ actions: { createRedire fromPath: "/routes/redirect/existing", toPath: "/routes/redirect/hit" }) + + createSlice({ + id: `footer`, + component: path.resolve(`./src/components/footer.jsx`), + context: {}, + }) } \ No newline at end of file diff --git a/e2e-tests/adapters/src/components/footer.jsx b/e2e-tests/adapters/src/components/footer.jsx new file mode 100644 index 0000000000000..a931738313bd9 --- /dev/null +++ b/e2e-tests/adapters/src/components/footer.jsx @@ -0,0 +1,29 @@ +import * as React from "react" +import { useStaticQuery, graphql } from "gatsby" + +const Footer = () => { + const data = useStaticQuery(graphql` + { + site { + siteMetadata { + siteDescription + } + } + } + `) + + return ( +
      + {data.site.siteMetadata.siteDescription} +
      + ) +} + +export default Footer + +const footerStyles = { + color: "#787483", + paddingLeft: 72, + paddingRight: 72, + fontFamily: "-apple-system, Roboto, sans-serif, serif", +} \ No newline at end of file diff --git a/e2e-tests/adapters/src/components/layout.jsx b/e2e-tests/adapters/src/components/layout.jsx index 39a9289463078..ca1b4584473d2 100644 --- a/e2e-tests/adapters/src/components/layout.jsx +++ b/e2e-tests/adapters/src/components/layout.jsx @@ -1,5 +1,5 @@ import * as React from "react" -import { Link } from "gatsby" +import { Link, Slice } from "gatsby" const Layout = ({ children, hideBackToHome = false }) => ( <> @@ -11,6 +11,7 @@ const Layout = ({ children, hideBackToHome = false }) => (
      {children}
      + ) diff --git a/e2e-tests/adapters/src/pages/500.jsx b/e2e-tests/adapters/src/pages/500.jsx new file mode 100644 index 0000000000000..01ebb0f9c7992 --- /dev/null +++ b/e2e-tests/adapters/src/pages/500.jsx @@ -0,0 +1,31 @@ +import * as React from "react" +import { Link } from "gatsby" + +const pageStyles = { + color: "#232129", + padding: "96px", + fontFamily: "-apple-system, Roboto, sans-serif, serif", +} + +const headingStyles = { + marginTop: 0, + marginBottom: 64, + maxWidth: 320, +} + +const paragraphStyles = { + marginBottom: 48, +} + +const InternalServerErrorPage = () => ( +
      +

      INTERNAL SERVER ERROR

      +

      + Go home +

      +
      +) + +export const Head = () => 500: Internal Server Error + +export default InternalServerErrorPage diff --git a/e2e-tests/adapters/src/pages/index.jsx b/e2e-tests/adapters/src/pages/index.jsx index 413e2784d8e6d..a6c5365026e47 100644 --- a/e2e-tests/adapters/src/pages/index.jsx +++ b/e2e-tests/adapters/src/pages/index.jsx @@ -1,5 +1,5 @@ import * as React from "react" -import { Link } from "gatsby" +import { Link, graphql } from "gatsby" import Layout from "../components/layout" import gatsbyAstronaut from "../images/astro.png" import "./index.css" @@ -8,14 +8,20 @@ const routes = [ { text: "Static", url: "/routes/static", + id: "static-without-slash" + }, + { + text: "Static (With Slash)", + url: "/routes/static/", + id: "static-with-slash" }, { text: "SSR", - url: "/routes/ssr", + url: "/routes/ssr/static", }, { text: "DSG", - url: "/routes/dsg", + url: "/routes/dsg/static", }, { text: "Sub-Router", @@ -54,7 +60,7 @@ const functions = [ }, ] -const IndexPage = () => { +const IndexPage = ({ data }) => { return (
      @@ -62,12 +68,12 @@ const IndexPage = () => {
      Gatsby Monogram Logo -

      Adapters

      +

      {data.site.siteMetadata.title}

        {routes.map(link => (
      • - + {link.text}
      • @@ -87,3 +93,13 @@ const IndexPage = () => { export default IndexPage export const Head = () => Adapters E2E + +export const query = graphql` + { + site { + siteMetadata { + title + } + } + } +` diff --git a/e2e-tests/adapters/src/pages/routes/dsg/graphql-query.jsx b/e2e-tests/adapters/src/pages/routes/dsg/graphql-query.jsx new file mode 100644 index 0000000000000..358c8517f12ad --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/dsg/graphql-query.jsx @@ -0,0 +1,34 @@ +import * as React from "react" +import { graphql } from "gatsby" +import Layout from "../../../components/layout" + +const DSGWithGraphQLQuery = ({ data: { site: { siteMetadata } } }) => { + return ( + +

        DSG

        +

        {siteMetadata.title}

        +
        + ) +} + +export default DSGWithGraphQLQuery + +export const Head = () => DSG + +export async function config() { + return () => { + return { + defer: true, + } + } +} + +export const query = graphql` + { + site { + siteMetadata { + title + } + } + } +` \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/routes/dsg.jsx b/e2e-tests/adapters/src/pages/routes/dsg/static.jsx similarity index 85% rename from e2e-tests/adapters/src/pages/routes/dsg.jsx rename to e2e-tests/adapters/src/pages/routes/dsg/static.jsx index 1d45392347b39..0e964be012d8e 100644 --- a/e2e-tests/adapters/src/pages/routes/dsg.jsx +++ b/e2e-tests/adapters/src/pages/routes/dsg/static.jsx @@ -1,5 +1,5 @@ import * as React from "react" -import Layout from "../../components/layout"; +import Layout from "../../../components/layout" const DSG = () => { return ( diff --git a/e2e-tests/adapters/src/pages/routes/ssr.jsx b/e2e-tests/adapters/src/pages/routes/ssr.jsx deleted file mode 100644 index 0bdae509cf64c..0000000000000 --- a/e2e-tests/adapters/src/pages/routes/ssr.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as React from "react" -import Layout from "../../components/layout"; - -const SSR = ({ serverData, params }) => { - return ( - -

        SSR

        -
        - -
        -            {JSON.stringify({ params, serverData }, null, 2)}
        -          
        -
        -
        -
        - ) -} - -export default SSR - -export const Head = () => SSR - -export function getServerData({ params }) { - return { - props: { - ssr: true, - params, - }, - }; -} \ No newline at end of file diff --git a/e2e-tests/adapters/src/pages/routes/ssr/error-path.jsx b/e2e-tests/adapters/src/pages/routes/ssr/error-path.jsx new file mode 100644 index 0000000000000..445d56a97e89e --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/ssr/error-path.jsx @@ -0,0 +1,11 @@ +import React from "react" + +export default function ErrorPath({ serverData }) { + return ( +
        This will never render
        + ) +} + +export async function getServerData() { + throw new Error(`Some runtime error`) +} diff --git a/e2e-tests/adapters/src/pages/routes/ssr/param/[param].jsx b/e2e-tests/adapters/src/pages/routes/ssr/param/[param].jsx new file mode 100644 index 0000000000000..0dde25d8589d9 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/ssr/param/[param].jsx @@ -0,0 +1,30 @@ +import React from "react" + +export default function Params({ serverData }) { + return ( +
        +

        SSR

        +
        + +
        +            {JSON.stringify({ serverData }, null, 2)}
        +          
        +
        +
        +
        + +
        {JSON.stringify(serverData?.arg?.query)}
        +
        {JSON.stringify(serverData?.arg?.params)}
        +
        +
        +
        + ) +} + +export async function getServerData(arg) { + return { + props: { + arg, + }, + } +} diff --git a/e2e-tests/adapters/src/pages/routes/ssr/static.jsx b/e2e-tests/adapters/src/pages/routes/ssr/static.jsx new file mode 100644 index 0000000000000..971c95cbd4827 --- /dev/null +++ b/e2e-tests/adapters/src/pages/routes/ssr/static.jsx @@ -0,0 +1,36 @@ +import * as React from "react" +import Layout from "../../../components/layout" + +const SSR = ({ serverData }) => { + return ( + +

        SSR

        +
        + +
        +            {JSON.stringify({ serverData }, null, 2)}
        +          
        +
        +
        +
        + +
        {JSON.stringify(serverData?.arg?.query)}
        +
        {JSON.stringify(serverData?.arg?.params)}
        +
        +
        +
        + ) +} + +export default SSR + +export const Head = () => SSR + +export function getServerData(arg) { + return { + props: { + ssr: true, + arg, + }, + } +} \ No newline at end of file From 9e2935c29ff9b8e0fb804243d481aa4fa28867ec Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 09:09:20 +0200 Subject: [PATCH 107/161] add excludeDatastoreFromEngineFunction flow --- packages/gatsby-adapter-netlify/src/index.ts | 38 ++++++++- packages/gatsby/index.d.ts | 1 + .../src/datastore/lmdb/lmdb-datastore.ts | 2 +- packages/gatsby/src/joi-schemas/joi.ts | 1 + packages/gatsby/src/redux/reducers/adapter.ts | 20 +++++ packages/gatsby/src/redux/reducers/index.ts | 2 + packages/gatsby/src/redux/types.ts | 21 ++++- .../schema/graphql-engine/bundle-webpack.ts | 4 +- packages/gatsby/src/utils/adapter/manager.ts | 85 ++++++++++++++----- .../gatsby/src/utils/adapter/no-op-manager.ts | 14 +++ packages/gatsby/src/utils/adapter/types.ts | 19 ++++- packages/gatsby/src/utils/engines-helpers.ts | 15 +++- .../utils/page-ssr-module/bundle-webpack.ts | 14 ++- .../src/utils/page-ssr-module/lambda.ts | 79 ++++++++++++++++- 14 files changed, 281 insertions(+), 34 deletions(-) create mode 100644 packages/gatsby/src/redux/reducers/adapter.ts create mode 100644 packages/gatsby/src/utils/adapter/no-op-manager.ts diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 4c6dadb1a4f92..7bfa7029324ef 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -1,7 +1,9 @@ -import type { AdapterInit } from "gatsby" +import type { AdapterInit, IAdapterGatsbyConfig } from "gatsby" // eslint-disable-next-line @typescript-eslint/no-empty-interface -interface INetlifyAdapterOptions {} +interface INetlifyAdapterOptions { + excludeDatastoreFromEngineFunction?: boolean +} import { prepareFunctionVariants } from "./lambda-handler" import { handleRoutesManifest } from "./route-handler" @@ -21,7 +23,7 @@ async function getCacheUtils(): Promise { return undefined } -const createNetlifyAdapter: AdapterInit = () => { +const createNetlifyAdapter: AdapterInit = options => { return { name: `gatsby-adapter-netlify`, cache: { @@ -61,6 +63,36 @@ const createNetlifyAdapter: AdapterInit = () => { ) } }, + config: (): IAdapterGatsbyConfig => { + // if (process.env.DEPLOY_URL && process.env.NETLIFY) { + // TODO: use env var as additional toggle on top on adapter options to ease migration from netlify plugins + + let deployURL = process.env.NETLIFY_LOCAL + ? `http://localhost:8888` + : process.env.DEPLOY_URL + + if (!deployURL) { + // for dev purposes - remove later + deployURL = `http://localhost:9000` + } + + return { + excludeDatastoreFromEngineFunction: + options?.excludeDatastoreFromEngineFunction ?? false, + deployURL, + } + // } + + // if (options?.excludeDatastoreFromEngineFunction) { + // reporter.warn( + // `[gatsby-adapter-netlify] excludeDatastoreFromEngineFunction is set to true but no DEPLOY_URL is set (running netlify command locally). Disabling excludeDatastoreFromEngineFunction.` + // ) + // } + + // return { + // excludeDatastoreFromEngineFunction: false, + // } + }, } } diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index 0eb52e783359e..f17cf2c79415c 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -42,6 +42,7 @@ export { IFunctionDefinition, RoutesManifest, FunctionsManifest, + IAdapterGatsbyConfig, } from "./dist/utils/adapter/types" export const useScrollRestoration: (key: string) => { diff --git a/packages/gatsby/src/datastore/lmdb/lmdb-datastore.ts b/packages/gatsby/src/datastore/lmdb/lmdb-datastore.ts index e9908afcea6a3..3a567cb1dc9cf 100644 --- a/packages/gatsby/src/datastore/lmdb/lmdb-datastore.ts +++ b/packages/gatsby/src/datastore/lmdb/lmdb-datastore.ts @@ -29,7 +29,7 @@ const lmdbDatastore = { const preSyncDeletedNodeIdsCache = new Set() -function getDefaultDbPath(): string { +export function getDefaultDbPath(): string { const dbFileName = process.env.NODE_ENV === `test` ? `test-datastore-${ diff --git a/packages/gatsby/src/joi-schemas/joi.ts b/packages/gatsby/src/joi-schemas/joi.ts index 377dbb7699b88..cb5402fd55295 100644 --- a/packages/gatsby/src/joi-schemas/joi.ts +++ b/packages/gatsby/src/joi-schemas/joi.ts @@ -113,6 +113,7 @@ export const gatsbyConfigSchema: Joi.ObjectSchema = Joi.object() }) .unknown(false), adapt: Joi.func().required(), + config: Joi.func(), }) .unknown(false), }) diff --git a/packages/gatsby/src/redux/reducers/adapter.ts b/packages/gatsby/src/redux/reducers/adapter.ts new file mode 100644 index 0000000000000..6a0bb94f7cb88 --- /dev/null +++ b/packages/gatsby/src/redux/reducers/adapter.ts @@ -0,0 +1,20 @@ +import { noOpAdapterManager } from "../../utils/adapter/no-op-manager" +import type { ActionsUnion, IGatsbyState } from "../types" + +export const adapterReducer = ( + state: IGatsbyState["adapter"] = { + instance: undefined, + manager: noOpAdapterManager(), + config: { + excludeDatastoreFromEngineFunction: false, + }, + }, + action: ActionsUnion +): IGatsbyState["adapter"] => { + switch (action.type) { + case `SET_ADAPTER`: + return action.payload + default: + return state + } +} diff --git a/packages/gatsby/src/redux/reducers/index.ts b/packages/gatsby/src/redux/reducers/index.ts index b23897cf61a3b..54e4ef76e47ed 100644 --- a/packages/gatsby/src/redux/reducers/index.ts +++ b/packages/gatsby/src/redux/reducers/index.ts @@ -37,6 +37,7 @@ import { statefulSourcePluginsReducer } from "./stateful-source-plugins" import { slicesReducer } from "./slices" import { componentsUsingSlicesReducer } from "./components-using-slices" import { slicesByTemplateReducer } from "./slices-by-template" +import { adapterReducer } from "./adapter" /** * @property exports.nodesTouched Set @@ -81,4 +82,5 @@ export { componentsUsingSlicesReducer as componentsUsingSlices, slicesByTemplateReducer as slicesByTemplate, telemetryReducer as telemetry, + adapterReducer as adapter, } diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 824b2cb2f6d5a..6d9f5d9585691 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -15,7 +15,11 @@ import { InternalJob, JobResultInterface } from "../utils/jobs/manager" import { ITypeMetadata } from "../schema/infer/inference-metadata" import { Span } from "opentracing" import { ICollectedSlices } from "../utils/babel/find-slices" -import type { IAdapter } from "../utils/adapter/types" +import type { + IAdapter, + IAdapterFinalGatsbyConfig, + IAdapterManager, +} from "../utils/adapter/types" type SystemPath = string type Identifier = string @@ -415,6 +419,11 @@ export interface IGatsbyState { slices: Map componentsUsingSlices: Map slicesByTemplate: Map + adapter: { + instance?: IAdapter + manager: IAdapterManager + config: IAdapterFinalGatsbyConfig + } } export type GatsbyStateKeys = keyof IGatsbyState @@ -526,6 +535,7 @@ export type ActionsUnion = | ISlicesScriptsRegenerated | IProcessGatsbyImageSourceUrlAction | IClearGatsbyImageSourceUrlAction + | ISetAdapterAction export interface ISetComponentFeatures { type: `SET_COMPONENT_FEATURES` @@ -1184,6 +1194,15 @@ export interface IClearGatsbyImageSourceUrlAction { type: `CLEAR_GATSBY_IMAGE_SOURCE_URL` } +export interface ISetAdapterAction { + type: `SET_ADAPTER` + payload: { + instance?: IAdapter + manager: IAdapterManager + config: IAdapterFinalGatsbyConfig + } +} + export interface ITelemetry { gatsbyImageSourceUrls: Set } diff --git a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts index 7973e761ecfbb..8b5d99f4e41c2 100644 --- a/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts +++ b/packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts @@ -11,7 +11,7 @@ import reporter from "gatsby-cli/lib/reporter" import { schemaCustomizationAPIs } from "./print-plugins" import type { GatsbyNodeAPI } from "../../redux/types" import * as nodeApis from "../../utils/api-node-docs" -import { isUsingAdapter } from "../../utils/adapter/manager" +import { store } from "../../redux" type Reporter = typeof reporter @@ -121,7 +121,7 @@ export async function createGraphqlEngineBundle( { loader: require.resolve(`./lmdb-bundling-patch`), options: { - forcedBinaryModule: isUsingAdapter() + forcedBinaryModule: store.getState().adapter.instance ? `@lmdb/lmdb-${process.platform}-${process.arch}/node.abi83.glibc.node` : undefined, }, diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 4827efc3a21a8..90cfa74968021 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -5,6 +5,7 @@ import { generatePageDataPath } from "gatsby-core-utils/page-data" import { posix } from "path" import { sync as globSync } from "glob" import telemetry from "gatsby-telemetry" +import { copy } from "fs-extra" import type { FunctionsManifest, IAdaptContext, @@ -13,12 +14,18 @@ import type { IAdapterManager, IFunctionRoute, IAdapter, + IAdapterFinalGatsbyConfig, + IAdapterGatsbyConfig, } from "./types" import { store, readState } from "../../redux" import { getPageMode } from "../page-mode" import { getStaticQueryPath } from "../static-query-utils" import { getAdapterInit } from "./init" -import { shouldGenerateEngines } from "../engines-helpers" +import { + LmdbOnCdnPath, + shouldBundleDatastore, + shouldGenerateEngines, +} from "../engines-helpers" import { ASSET_HEADERS, REDIRECT_HEADERS, @@ -33,19 +40,8 @@ import { getRoutePathFromFunction, getRoutePathFromPage, } from "./get-route-path" - -function noOpAdapterManager(): IAdapterManager { - return { - restoreCache: (): void => {}, - storeCache: (): void => {}, - adapt: (): void => {}, - } -} - -let usingAdapter = false -export function isUsingAdapter(): boolean { - return usingAdapter -} +import { noOpAdapterManager } from "./no-op-manager" +import { getDefaultDbPath } from "../../datastore/lmdb/lmdb-datastore" export async function initAdapterManager(): Promise { let adapter: IAdapter @@ -65,19 +61,27 @@ export async function initAdapterManager(): Promise { if (!adapterInit) { telemetry.trackFeatureIsUsed(`adapter:no-op`) - usingAdapter = false - return noOpAdapterManager() + const manager = noOpAdapterManager() + const configFromAdapter = await manager.config() + + store.dispatch({ + type: `SET_ADAPTER`, + payload: { + manager, + config: configFromAdapter, + }, + }) + return manager } adapter = adapterInit() } - usingAdapter = true reporter.info(`Using ${adapter.name} adapter`) telemetry.trackFeatureIsUsed(`adapter:${adapter.name}`) const directoriesToCache = [`.cache`, `public`] - return { + const manager: IAdapterManager = { restoreCache: async (): Promise => { // TODO: Remove before final merge reporter.info(`[Adapters] restoreCache()`) @@ -121,6 +125,13 @@ export async function initAdapterManager(): Promise { return } + // handle lmdb stuff + if (!shouldBundleDatastore()) { + const mdbPath = getDefaultDbPath() + `/data.mdb` + reporter.info(`[Adapters] Skipping datastore bundling`) + copy(mdbPath, `public/${LmdbOnCdnPath}`) + } + let _routesManifest: RoutesManifest | undefined = undefined let _functionsManifest: FunctionsManifest | undefined = undefined const adaptContext: IAdaptContext = { @@ -146,7 +157,41 @@ export async function initAdapterManager(): Promise { await adapter.adapt(adaptContext) }, + config: async (): Promise => { + let configFromAdapter: undefined | IAdapterGatsbyConfig = undefined + if (adapter.config) { + configFromAdapter = await adapter.config({ reporter }) + + if ( + configFromAdapter?.excludeDatastoreFromEngineFunction && + !configFromAdapter?.deployURL + ) { + throw new Error( + `Can't exclude datastore from engine function without adapter providing deployURL` + ) + } + } + + return { + excludeDatastoreFromEngineFunction: + configFromAdapter?.excludeDatastoreFromEngineFunction ?? false, + deployURL: configFromAdapter?.deployURL, + } + }, } + + const configFromAdapter = await manager.config() + + store.dispatch({ + type: `SET_ADAPTER`, + payload: { + manager, + instance: adapter, + config: configFromAdapter, + }, + }) + + return manager } let webpackAssets: Set | undefined @@ -382,7 +427,9 @@ function getFunctionsManifest(): FunctionsManifest { functionId: `ssr-engine`, pathToEntryPoint: posix.join(`.cache`, `page-ssr`, `lambda.js`), requiredFiles: [ - ...getFilesFrom(posix.join(`.cache`, `data`, `datastore`)), + ...(shouldBundleDatastore() + ? getFilesFrom(posix.join(`.cache`, `data`, `datastore`)) + : []), ...getFilesFrom(posix.join(`.cache`, `page-ssr`)), ...getFilesFrom(posix.join(`.cache`, `query-engine`)), ], diff --git a/packages/gatsby/src/utils/adapter/no-op-manager.ts b/packages/gatsby/src/utils/adapter/no-op-manager.ts new file mode 100644 index 0000000000000..aec3018e342ff --- /dev/null +++ b/packages/gatsby/src/utils/adapter/no-op-manager.ts @@ -0,0 +1,14 @@ +import { IAdapterFinalGatsbyConfig, IAdapterManager } from "./types" + +export function noOpAdapterManager(): IAdapterManager { + return { + restoreCache: (): void => {}, + storeCache: (): void => {}, + adapt: (): void => {}, + config: async (): Promise => { + return { + excludeDatastoreFromEngineFunction: false, + } + }, + } +} diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index fa150db38fb98..e5643c880b468 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -109,6 +109,16 @@ export interface ICacheContext extends IDefaultContext { directories: Array } +export interface IAdapterGatsbyConfig { + deployURL?: string + excludeDatastoreFromEngineFunction?: boolean +} + +export interface IAdapterFinalGatsbyConfig { + deployURL?: string + excludeDatastoreFromEngineFunction: boolean +} + export interface IAdapter { /** * Unique name of the adapter. Used to identify adapter in manifest. @@ -139,9 +149,14 @@ export interface IAdapter { * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ */ adapt: (context: IAdaptContext) => Promise | void + // TODO: should we have "private storage" handling defining a way to "upload" and "download those private assets? - // this could be used for lmdb datastore in case it's not being bundled with ssr-engine lambda as well as File nodes to handle + // this could be used for lmdb datastore in case it's not being bundled with ssr-engine function as well as File nodes to handle // current limitation in Netlify's implementation of DSG/SSR ( https://github.com/netlify/netlify-plugin-gatsby#caveats ) + config?: ( + context: IDefaultContext + ) => Promise | IAdapterGatsbyConfig + // getDeployURL?: () => Promise | string | undefined } /** @@ -156,8 +171,8 @@ export interface IAdapterManager { restoreCache: () => Promise | void storeCache: () => Promise | void adapt: () => Promise | void + config: () => Promise } - /** * Types for gatsby/adapters.js * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/zero-configuration-deployments/ diff --git a/packages/gatsby/src/utils/engines-helpers.ts b/packages/gatsby/src/utils/engines-helpers.ts index 3f73842bcf1a3..ff7f89f7d3785 100644 --- a/packages/gatsby/src/utils/engines-helpers.ts +++ b/packages/gatsby/src/utils/engines-helpers.ts @@ -1,4 +1,5 @@ -import { emitter } from "../redux" +import { uuid } from "gatsby-core-utils/index" +import { emitter, store } from "../redux" import { ICreatePageAction, ISetComponentFeatures } from "../redux/types" import { trackFeatureIsUsed } from "gatsby-telemetry" @@ -23,3 +24,15 @@ emitter.on(`SET_COMPONENT_FEATURES`, (action: ISetComponentFeatures) => { shouldSendTelemetryForHeadAPI = false } }) + +export function shouldBundleDatastore(): boolean { + return !store.getState().adapter.config.excludeDatastoreFromEngineFunction +} + +// TODO: we should preserve randomized from previous builds to not keep accumulating different versions of datastore in public dir +const randomized = uuid.v4() +function getCDNObfuscatedPath(path: string): string { + return `${randomized}-${path}` +} + +export const LmdbOnCdnPath = getCDNObfuscatedPath(`data.mdb`) diff --git a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts index a84a7ad1d4829..b9f2f612857ec 100644 --- a/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts +++ b/packages/gatsby/src/utils/page-ssr-module/bundle-webpack.ts @@ -12,6 +12,7 @@ import { } from "../client-assets-for-template" import { IGatsbyState } from "../../redux/types" import { store } from "../../redux" +import { LmdbOnCdnPath, shouldBundleDatastore } from "../engines-helpers" type Reporter = typeof reporter @@ -218,11 +219,20 @@ export async function createPageSSRBundle({ ].filter(Boolean) as Array, }) - await fs.copyFile( + let functionCode = await fs.readFile( path.join(__dirname, `lambda.js`), - path.join(outputDir, `lambda.js`) + `utf-8` ) + functionCode = functionCode.replace( + `%CDN_DATASTORE_PATH%`, + shouldBundleDatastore() + ? `` + : `${state.adapter.config.deployURL ?? ``}/${LmdbOnCdnPath}` + ) + + await fs.outputFile(path.join(outputDir, `lambda.js`), functionCode) + return new Promise((resolve, reject) => { compiler.run((err, stats) => { compiler.close(closeErr => { diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts index 656a65f6b8563..fe44b3a4b55ab 100644 --- a/packages/gatsby/src/utils/page-ssr-module/lambda.ts +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -1,11 +1,19 @@ import type { GatsbyFunctionResponse, GatsbyFunctionRequest } from "gatsby" import * as path from "path" import * as fs from "fs-extra" +import { get as httpsGet } from "https" +import { get as httpGet, IncomingMessage, ClientRequest } from "http" import { tmpdir } from "os" +import { pipeline } from "stream" +import { URL } from "url" +import { promisify } from "util" + import type { IGatsbyPage } from "../../internal" import type { ISSRData } from "./entry" import { link } from "linkfs" +const cdnDatastore = `%CDN_DATASTORE_PATH%` + function setupFsWrapper(): string { // setup global._fsWrapper try { @@ -78,15 +86,79 @@ const dbPath = setupFsWrapper() // using require instead of import here for now because of type hell + import path doesn't exist in current context // as this file will be copied elsewhere +type GraphQLEngineType = + import("../../schema/graphql-engine/entry").GraphQLEngine + const { GraphQLEngine } = require(`../query-engine`) as typeof import("../../schema/graphql-engine/entry") const { getData, renderPageData, renderHTML } = require(`./index`) as typeof import("./entry") -const graphqlEngine = new GraphQLEngine({ - dbPath, -}) +const streamPipeline = promisify(pipeline) + +function get( + url: string, + callback?: (res: IncomingMessage) => void +): ClientRequest { + return new URL(url).protocol === `https:` + ? httpsGet(url, callback) + : httpGet(url, callback) +} + +async function getEngine(): Promise { + if (cdnDatastore) { + // if this variable is set we need to download the datastore from the CDN + const downloadPath = dbPath + `/data.mdb` + console.log( + `Downloading datastore from CDN (${cdnDatastore} -> ${downloadPath})` + ) + + await fs.ensureDir(dbPath) + await new Promise((resolve, reject) => { + const req = get(cdnDatastore, response => { + if ( + !response.statusCode || + response.statusCode < 200 || + response.statusCode > 299 + ) { + reject( + new Error( + `Failed to download ${cdnDatastore}: ${response.statusCode} ${ + response.statusMessage || `` + }` + ) + ) + return + } + + const fileStream = fs.createWriteStream(downloadPath) + streamPipeline(response, fileStream) + .then(resolve) + .catch(error => { + console.log(`Error downloading ${cdnDatastore}`, error) + reject(error) + }) + }) + + req.on(`error`, error => { + console.log(`Error downloading ${cdnDatastore}`, error) + reject(error) + }) + }) + } + console.log(`Downloaded datastore from CDN`) + + const graphqlEngine = new GraphQLEngine({ + dbPath, + }) + + await graphqlEngine.ready + + return graphqlEngine +} + +const engineReadyPromise = getEngine() function reverseFixedPagePath(pageDataRequestPath: string): string { return pageDataRequestPath === `index` ? `/` : pageDataRequestPath @@ -141,6 +213,7 @@ async function engineHandler( res: GatsbyFunctionResponse ): Promise { try { + const graphqlEngine = await engineReadyPromise const pathInfo = getPathInfo(req) if (!pathInfo) { res.status(404).send(`Not found`) From 903dd0434245d8ba9120d756f5b19eed689691eb Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 10:03:00 +0200 Subject: [PATCH 108/161] normalize path after globbing --- packages/gatsby/src/utils/adapter/manager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 90cfa74968021..e0acd37a64cbe 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -1,6 +1,7 @@ import reporter from "gatsby-cli/lib/reporter" import { applyTrailingSlashOption, TrailingSlash } from "gatsby-page-utils" import { generateHtmlPath } from "gatsby-core-utils/page-html" +import { slash } from "gatsby-core-utils/path" import { generatePageDataPath } from "gatsby-core-utils/page-data" import { posix } from "path" import { sync as globSync } from "glob" @@ -211,7 +212,7 @@ function getRoutesManifest(): RoutesManifest { cwd: posix.join(process.cwd(), `public`), nodir: true, dot: true, - }) + }).map(filePath => slash(filePath)) ) // TODO: This could be a "addSortedRoute" function that would add route to the list in sorted order. TBD if necessary performance-wise From d288e86f9ba292108138b4027dc54b055db9177c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 10:08:26 +0200 Subject: [PATCH 109/161] mock shouldBundleDatastore --- packages/gatsby/src/utils/adapter/__tests__/manager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index 2959acea996f3..00c2ef381710a 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -21,6 +21,7 @@ jest.mock(`../../../redux`, () => { jest.mock(`../../engines-helpers`, () => { return { shouldGenerateEngines: jest.fn().mockReturnValue(true), + shouldBundleDatastore: jest.fn().mockReturnValue(true), } }) From 46ee1f3d95fcbb16adaef7751fea7ecb2f1d6d3e Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 10:13:02 +0200 Subject: [PATCH 110/161] rename adapter config types to be less confusing with gatsby-config --- packages/gatsby/src/redux/types.ts | 6 +++--- packages/gatsby/src/utils/adapter/manager.ts | 8 ++++---- packages/gatsby/src/utils/adapter/no-op-manager.ts | 4 ++-- packages/gatsby/src/utils/adapter/types.ts | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 6d9f5d9585691..237bdfd3c388c 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -17,7 +17,7 @@ import { Span } from "opentracing" import { ICollectedSlices } from "../utils/babel/find-slices" import type { IAdapter, - IAdapterFinalGatsbyConfig, + IAdapterFinalConfig, IAdapterManager, } from "../utils/adapter/types" @@ -422,7 +422,7 @@ export interface IGatsbyState { adapter: { instance?: IAdapter manager: IAdapterManager - config: IAdapterFinalGatsbyConfig + config: IAdapterFinalConfig } } @@ -1199,7 +1199,7 @@ export interface ISetAdapterAction { payload: { instance?: IAdapter manager: IAdapterManager - config: IAdapterFinalGatsbyConfig + config: IAdapterFinalConfig } } diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index e0acd37a64cbe..1de674be0b8f9 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -15,8 +15,8 @@ import type { IAdapterManager, IFunctionRoute, IAdapter, - IAdapterFinalGatsbyConfig, - IAdapterGatsbyConfig, + IAdapterFinalConfig, + IAdapterConfig, } from "./types" import { store, readState } from "../../redux" import { getPageMode } from "../page-mode" @@ -158,8 +158,8 @@ export async function initAdapterManager(): Promise { await adapter.adapt(adaptContext) }, - config: async (): Promise => { - let configFromAdapter: undefined | IAdapterGatsbyConfig = undefined + config: async (): Promise => { + let configFromAdapter: undefined | IAdapterConfig = undefined if (adapter.config) { configFromAdapter = await adapter.config({ reporter }) diff --git a/packages/gatsby/src/utils/adapter/no-op-manager.ts b/packages/gatsby/src/utils/adapter/no-op-manager.ts index aec3018e342ff..0f1a63f312d6d 100644 --- a/packages/gatsby/src/utils/adapter/no-op-manager.ts +++ b/packages/gatsby/src/utils/adapter/no-op-manager.ts @@ -1,11 +1,11 @@ -import { IAdapterFinalGatsbyConfig, IAdapterManager } from "./types" +import { IAdapterFinalConfig, IAdapterManager } from "./types" export function noOpAdapterManager(): IAdapterManager { return { restoreCache: (): void => {}, storeCache: (): void => {}, adapt: (): void => {}, - config: async (): Promise => { + config: async (): Promise => { return { excludeDatastoreFromEngineFunction: false, } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index e5643c880b468..6cb445f951000 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -109,12 +109,12 @@ export interface ICacheContext extends IDefaultContext { directories: Array } -export interface IAdapterGatsbyConfig { +export interface IAdapterConfig { deployURL?: string excludeDatastoreFromEngineFunction?: boolean } -export interface IAdapterFinalGatsbyConfig { +export interface IAdapterFinalConfig { deployURL?: string excludeDatastoreFromEngineFunction: boolean } @@ -155,7 +155,7 @@ export interface IAdapter { // current limitation in Netlify's implementation of DSG/SSR ( https://github.com/netlify/netlify-plugin-gatsby#caveats ) config?: ( context: IDefaultContext - ) => Promise | IAdapterGatsbyConfig + ) => Promise | IAdapterConfig // getDeployURL?: () => Promise | string | undefined } @@ -171,7 +171,7 @@ export interface IAdapterManager { restoreCache: () => Promise | void storeCache: () => Promise | void adapt: () => Promise | void - config: () => Promise + config: () => Promise } /** * Types for gatsby/adapters.js From a00a63439cfa56a979eb54ded6f97f71929df594 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 10:57:43 +0200 Subject: [PATCH 111/161] keep same obfuscated path between builds --- packages/gatsby/src/redux/index.ts | 13 +++++++++++-- packages/gatsby/src/redux/reducers/status.ts | 13 ++++++++++++- packages/gatsby/src/redux/types.ts | 6 ++++++ packages/gatsby/src/services/initialize.ts | 2 +- packages/gatsby/src/utils/adapter/manager.ts | 13 +++++++++---- packages/gatsby/src/utils/engines-helpers.ts | 5 +---- 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/gatsby/src/redux/index.ts b/packages/gatsby/src/redux/index.ts index d9ca6cd5c114a..84ada92859fb2 100644 --- a/packages/gatsby/src/redux/index.ts +++ b/packages/gatsby/src/redux/index.ts @@ -107,13 +107,22 @@ export type GatsbyReduxStore = Store & { dispatch: ThunkDispatch & IMultiDispatch } -export const configureStore = (initialState: IGatsbyState): GatsbyReduxStore => - createStore( +export const configureStore = ( + initialState: IGatsbyState +): GatsbyReduxStore => { + const store = createStore( combineReducers({ ...reducers }), initialState, applyMiddleware(thunk as ThunkMiddleware, multi) ) + store.dispatch({ + type: `INIT`, + }) + + return store +} + export const store: GatsbyReduxStore = configureStore( process.env.GATSBY_WORKER_POOL_WORKER ? ({} as IGatsbyState) : readState() ) diff --git a/packages/gatsby/src/redux/reducers/status.ts b/packages/gatsby/src/redux/reducers/status.ts index 46b7222543898..2098c980a6d60 100644 --- a/packages/gatsby/src/redux/reducers/status.ts +++ b/packages/gatsby/src/redux/reducers/status.ts @@ -1,10 +1,12 @@ import _ from "lodash" +import { uuid } from "gatsby-core-utils/index" import { ActionsUnion, IGatsbyState } from "../types" const defaultState: IGatsbyState["status"] = { PLUGINS_HASH: ``, LAST_NODE_COUNTER: 0, plugins: {}, + cdnObfuscatedPrefix: ``, } export const statusReducer = ( @@ -13,7 +15,16 @@ export const statusReducer = ( ): IGatsbyState["status"] => { switch (action.type) { case `DELETE_CACHE`: - return defaultState + return { + ...defaultState, + cdnObfuscatedPrefix: state.cdnObfuscatedPrefix ?? ``, + } + case `INIT`: { + if (!state.cdnObfuscatedPrefix) { + state.cdnObfuscatedPrefix = uuid.v4() + } + return state + } case `UPDATE_PLUGINS_HASH`: return { ...state, diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 237bdfd3c388c..866bfc629c1ee 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -324,6 +324,7 @@ export interface IGatsbyState { plugins: Record PLUGINS_HASH: Identifier LAST_NODE_COUNTER: number + cdnObfuscatedPrefix: string } queries: { byNode: Map> @@ -448,6 +449,7 @@ export interface ICachedReduxState { } export type ActionsUnion = + | IInitAction | IAddChildNodeToParentNodeAction | IAddFieldToNodeAction | IAddThirdPartySchema @@ -537,6 +539,10 @@ export type ActionsUnion = | IClearGatsbyImageSourceUrlAction | ISetAdapterAction +export interface IInitAction { + type: `INIT` +} + export interface ISetComponentFeatures { type: `SET_COMPONENT_FEATURES` payload: { diff --git a/packages/gatsby/src/services/initialize.ts b/packages/gatsby/src/services/initialize.ts index 4bc9e75df2103..fda5e3f1248dd 100644 --- a/packages/gatsby/src/services/initialize.ts +++ b/packages/gatsby/src/services/initialize.ts @@ -292,7 +292,7 @@ export async function initialize({ activity.start() const files = await glob( [ - `public/**/*.{html,css}`, + `public/**/*.{html,css,mdb}`, `!public/page-data/**/*`, `!public/static`, `!public/static/**/*.{html,css}`, diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 1de674be0b8f9..d81cd873823f6 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -6,7 +6,7 @@ import { generatePageDataPath } from "gatsby-core-utils/page-data" import { posix } from "path" import { sync as globSync } from "glob" import telemetry from "gatsby-telemetry" -import { copy } from "fs-extra" +import { copy, pathExists, unlink } from "fs-extra" import type { FunctionsManifest, IAdaptContext, @@ -126,11 +126,16 @@ export async function initAdapterManager(): Promise { return } - // handle lmdb stuff + // handle lmdb file + const mdbInPublicPath = `public/${LmdbOnCdnPath}` if (!shouldBundleDatastore()) { const mdbPath = getDefaultDbPath() + `/data.mdb` - reporter.info(`[Adapters] Skipping datastore bundling`) - copy(mdbPath, `public/${LmdbOnCdnPath}`) + copy(mdbPath, mdbInPublicPath) + } else { + // ensure public dir doesn't have lmdb file + if (await pathExists(mdbInPublicPath)) { + await unlink(mdbInPublicPath) + } } let _routesManifest: RoutesManifest | undefined = undefined diff --git a/packages/gatsby/src/utils/engines-helpers.ts b/packages/gatsby/src/utils/engines-helpers.ts index ff7f89f7d3785..20c7814986339 100644 --- a/packages/gatsby/src/utils/engines-helpers.ts +++ b/packages/gatsby/src/utils/engines-helpers.ts @@ -1,4 +1,3 @@ -import { uuid } from "gatsby-core-utils/index" import { emitter, store } from "../redux" import { ICreatePageAction, ISetComponentFeatures } from "../redux/types" import { trackFeatureIsUsed } from "gatsby-telemetry" @@ -29,10 +28,8 @@ export function shouldBundleDatastore(): boolean { return !store.getState().adapter.config.excludeDatastoreFromEngineFunction } -// TODO: we should preserve randomized from previous builds to not keep accumulating different versions of datastore in public dir -const randomized = uuid.v4() function getCDNObfuscatedPath(path: string): string { - return `${randomized}-${path}` + return `${store.getState().status.cdnObfuscatedPrefix}-${path}` } export const LmdbOnCdnPath = getCDNObfuscatedPath(`data.mdb`) From 140f62f172a541aa1b3c319103b66fa5aca2ef5b Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 10:58:23 +0200 Subject: [PATCH 112/161] normalize more paths --- packages/gatsby/src/utils/adapter/manager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index d81cd873823f6..f4c8af7d83a92 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -270,14 +270,14 @@ function getRoutesManifest(): RoutesManifest { // routes - pages - static (SSG) or function (DSG/SSR) for (const page of state.pages.values()) { - const htmlRoutePath = getRoutePathFromPage(page) - const pageDataRoutePath = generatePageDataPath(``, htmlRoutePath) + const htmlRoutePath = slash(getRoutePathFromPage(page)) + const pageDataRoutePath = slash(generatePageDataPath(``, htmlRoutePath)) const pageMode = getPageMode(page) if (pageMode === `SSG`) { - const htmlFilePath = generateHtmlPath(``, page.path) - const pageDataFilePath = generatePageDataPath(``, page.path) + const htmlFilePath = slash(generateHtmlPath(``, page.path)) + const pageDataFilePath = slash(generatePageDataPath(``, page.path)) addStaticRoute({ path: htmlRoutePath, From ce2f6929af2f258290bd9f95aefc80ceca7de8d0 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 29 Jun 2023 11:06:12 +0200 Subject: [PATCH 113/161] support custom 404/500 page for serverless functions --- e2e-tests/adapters/cypress/e2e/ssr.cy.ts | 3 +-- .../src/lambda-handler.ts | 12 +++++++--- .../src/utils/page-ssr-module/lambda.ts | 22 ++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/e2e-tests/adapters/cypress/e2e/ssr.cy.ts b/e2e-tests/adapters/cypress/e2e/ssr.cy.ts index ec9d47af6d842..7826ae8ec7c19 100644 --- a/e2e-tests/adapters/cypress/e2e/ssr.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/ssr.cy.ts @@ -26,8 +26,7 @@ describe("Server Side Rendering (SSR)", () => { cy.get(`[data-testid="params"]`).contains(`{"param":"foo"}`) }) - // TODO: Implement 400 & 500 handling - it.skip(`should display custom 500 page`, () => { + it(`should display custom 500 page`, () => { const errorPath = `/routes/ssr/error-path` cy.visit(errorPath, { failOnStatusCode: false }).waitForRouteChange() diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 605ba3330354f..9c4491992cb54 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -48,12 +48,18 @@ async function prepareFunction( await fs.ensureDir(internalFunctionsDir) + const requiredFilesNormalized = fun.requiredFiles.map(file => + file.replace(/\[/g, `*`).replace(/]/g, `*`) + ) + const functionManifest: INetlifyFunctionManifest = { config: { generator: `gatsby-adapter-netlify@${packageJson?.version ?? `unknown`}`, - includedFiles: fun.requiredFiles.map(file => - file.replace(/\[/g, `*`).replace(/]/g, `*`) - ), + includedFiles: [ + `public/404.html`, + `public/500.html`, + ...requiredFilesNormalized, + ], externalNodeModules: [`msgpackr-extract`], }, version: 1, diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts index fe44b3a4b55ab..62bbf9386ce78 100644 --- a/packages/gatsby/src/utils/page-ssr-module/lambda.ts +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -208,6 +208,22 @@ function setStatusAndHeaders({ } } +function getErrorBody(statusCode: number): string { + let body = `

        ${statusCode}

        ${ + statusCode === 404 ? `Not found` : `Internal Server Error` + }

        ` + + if (statusCode === 404 || statusCode === 500) { + const filename = path.join(process.cwd(), `public`, `${statusCode}.html`) + + if (fs.existsSync(filename)) { + body = fs.readFileSync(filename, `utf8`) + } + } + + return body +} + async function engineHandler( req: GatsbyFunctionRequest, res: GatsbyFunctionResponse @@ -216,7 +232,7 @@ async function engineHandler( const graphqlEngine = await engineReadyPromise const pathInfo = getPathInfo(req) if (!pathInfo) { - res.status(404).send(`Not found`) + res.status(404).send(getErrorBody(404)) return } @@ -224,7 +240,7 @@ async function engineHandler( const page = graphqlEngine.findPageByPath(pagePath) if (!page) { - res.status(404).send(`Not found`) + res.status(404).send(getErrorBody(404)) return } @@ -247,7 +263,7 @@ async function engineHandler( } } catch (e) { console.error(`Engine failed to handle request`, e) - res.status(500).send(`Internal server error.`) + res.status(500).send(getErrorBody(500)) } } From 63d3f9dcc340499fe537019cabb89f6e878ba7a8 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 29 Jun 2023 11:40:32 +0200 Subject: [PATCH 114/161] update snapshot --- packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap b/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap index eae952bbb1063..5f33ef0ca69cf 100644 --- a/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap +++ b/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap @@ -129,6 +129,7 @@ Object { "status": Object { "LAST_NODE_COUNTER": 0, "PLUGINS_HASH": "", + "cdnObfuscatedPrefix": "c0119079-e1d5-4d8a-a437-22060b3552c6", "plugins": Object {}, }, "typeOwners": Object { From 305e96f3ae240f00321b33516cea55aa80095c9a Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 11:58:43 +0200 Subject: [PATCH 115/161] generate relative imports in function --- .../gatsby-adapter-netlify/src/lambda-handler.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 9c4491992cb54..56ce714990662 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -70,20 +70,30 @@ async function prepareFunction( functionManifest ) + function getRelativePathToModule(modulePath: string): string { + const absolutePath = require.resolve(modulePath) + + return `./` + path.relative(internalFunctionsDir, absolutePath) + } + const handlerSource = /* javascript */ ` const Stream = require("stream") const http = require("http") const { Buffer } = require("buffer") -const cookie = require("${require.resolve(`cookie`)}") +const cookie = require("${getRelativePathToModule(`cookie`)}") ${ isODB - ? `const { builder } = require("${require.resolve(`@netlify/functions`)}")` + ? `const { builder } = require("${getRelativePathToModule( + `@netlify/functions` + )}")` : `` } const preferDefault = m => (m && m.default) || m -const functionModule = require("./../../../${fun.pathToEntryPoint}") +const functionModule = require("${getRelativePathToModule( + path.join(process.cwd(), fun.pathToEntryPoint) + )}") const functionHandler = preferDefault(functionModule) From b4e5c56ed9cae2cfebb4769a8bd53ddd1cd6ef64 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 12:15:10 +0200 Subject: [PATCH 116/161] skip trying to copy data to tmp if we are downloading from cdn --- .../src/utils/page-ssr-module/lambda.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/gatsby/src/utils/page-ssr-module/lambda.ts b/packages/gatsby/src/utils/page-ssr-module/lambda.ts index 62bbf9386ce78..c40c4416d3605 100644 --- a/packages/gatsby/src/utils/page-ssr-module/lambda.ts +++ b/packages/gatsby/src/utils/page-ssr-module/lambda.ts @@ -64,18 +64,20 @@ function setupFsWrapper(): string { // eslint-disable-next-line no-underscore-dangle global._fsWrapper = lfs - const dir = `data` - if ( - !process.env.NETLIFY_LOCAL && - fs.existsSync(path.join(TEMP_CACHE_DIR, dir)) - ) { - console.log(`directory already exists`) - return dbPath - } - console.log(`Start copying ${dir}`) + if (!cdnDatastore) { + const dir = `data` + if ( + !process.env.NETLIFY_LOCAL && + fs.existsSync(path.join(TEMP_CACHE_DIR, dir)) + ) { + console.log(`directory already exists`) + return dbPath + } + console.log(`Start copying ${dir}`) - fs.copySync(path.join(cacheDir, dir), path.join(TEMP_CACHE_DIR, dir)) - console.log(`End copying ${dir}`) + fs.copySync(path.join(cacheDir, dir), path.join(TEMP_CACHE_DIR, dir)) + console.log(`End copying ${dir}`) + } return dbPath } From e9624aa21afbbd666b35df393710939135a8e3b1 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 29 Jun 2023 12:09:13 +0200 Subject: [PATCH 117/161] improve TS types --- packages/gatsby/src/utils/adapter/types.ts | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 6cb445f951000..b1c6a9c412fae 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -110,14 +110,27 @@ export interface ICacheContext extends IDefaultContext { } export interface IAdapterConfig { + /** + * URL representing the unique URL for an individual deploy + */ deployURL?: string + /** + * If `true`, Gatsby will not include the LMDB datastore in the serverless functions used for SSR/DSG. + * Instead, it will try to download the datastore from the given `deployURL`. + */ excludeDatastoreFromEngineFunction?: boolean } -export interface IAdapterFinalConfig { - deployURL?: string - excludeDatastoreFromEngineFunction: boolean -} +type WithRequired = T & { [P in K]-?: T[P] } + +/** + * This is the internal version of "IAdapterConfig" to enforce that certain keys must be present. + * Authors of adapters will only see "IAdapterConfig". + */ +export type IAdapterFinalConfig = WithRequired< + IAdapterConfig, + "excludeDatastoreFromEngineFunction" +> export interface IAdapter { /** @@ -149,14 +162,17 @@ export interface IAdapter { * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ */ adapt: (context: IAdaptContext) => Promise | void - - // TODO: should we have "private storage" handling defining a way to "upload" and "download those private assets? - // this could be used for lmdb datastore in case it's not being bundled with ssr-engine function as well as File nodes to handle - // current limitation in Netlify's implementation of DSG/SSR ( https://github.com/netlify/netlify-plugin-gatsby#caveats ) + /** + * Hook to pass information from the adapter to Gatsby. You must return an object with a predefined shape. + * Gatsby uses this information to adjust its build process. The information can be e.g. things that are only known once the project is deployed. + * + * This hook is considered to be an advanced features of adapters and it is not required to implement it. + * + * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ + */ config?: ( context: IDefaultContext ) => Promise | IAdapterConfig - // getDeployURL?: () => Promise | string | undefined } /** From e42ab8f5f6030911126b656fce842b846773f12b Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 29 Jun 2023 12:21:44 +0200 Subject: [PATCH 118/161] mock uuid --- .../redux/__tests__/__snapshots__/index.js.snap | 2 +- packages/gatsby/src/redux/__tests__/index.js | 15 ++++++++++++--- packages/gatsby/src/redux/reducers/status.ts | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap b/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap index 5f33ef0ca69cf..d4df325fae6ed 100644 --- a/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap +++ b/packages/gatsby/src/redux/__tests__/__snapshots__/index.js.snap @@ -129,7 +129,7 @@ Object { "status": Object { "LAST_NODE_COUNTER": 0, "PLUGINS_HASH": "", - "cdnObfuscatedPrefix": "c0119079-e1d5-4d8a-a437-22060b3552c6", + "cdnObfuscatedPrefix": "1234567890", "plugins": Object {}, }, "typeOwners": Object { diff --git a/packages/gatsby/src/redux/__tests__/index.js b/packages/gatsby/src/redux/__tests__/index.js index bf0b4e670d69a..009cc669ce44e 100644 --- a/packages/gatsby/src/redux/__tests__/index.js +++ b/packages/gatsby/src/redux/__tests__/index.js @@ -3,7 +3,6 @@ const path = require(`path`) const v8 = require(`v8`) const telemetry = require(`gatsby-telemetry`) const reporter = require(`gatsby-cli/lib/reporter`) -const { murmurhash } = require(`gatsby-core-utils/murmurhash`) const writeToCache = jest.spyOn(require(`../persist`), `writeToCache`) const v8Serialize = jest.spyOn(v8, `serialize`) const v8Deserialize = jest.spyOn(v8, `deserialize`) @@ -87,7 +86,18 @@ jest.mock(`glob`, () => { }), } }) -jest.mock(`gatsby-core-utils/murmurhash`) + +jest.mock(`gatsby-core-utils`, () => { + return { + ...jest.requireActual(`gatsby-core-utils`), + murmurhash: { + murmurhash: jest.fn(() => `1234567890`), + }, + uuid: { + v4: jest.fn(() => `1234567890`), + }, + } +}) function getFakeNodes() { // Set nodes to something or the cache will fail because it asserts this @@ -151,7 +161,6 @@ describe(`redux db`, () => { mockWrittenContent.set(pageTemplatePath, `foo`) reporterWarn.mockClear() reporterInfo.mockClear() - murmurhash.mockReturnValue(`1234567890`) }) it(`should have cache status telemetry event`, async () => { diff --git a/packages/gatsby/src/redux/reducers/status.ts b/packages/gatsby/src/redux/reducers/status.ts index 2098c980a6d60..43c86a9639a71 100644 --- a/packages/gatsby/src/redux/reducers/status.ts +++ b/packages/gatsby/src/redux/reducers/status.ts @@ -1,5 +1,5 @@ import _ from "lodash" -import { uuid } from "gatsby-core-utils/index" +import { uuid } from "gatsby-core-utils" import { ActionsUnion, IGatsbyState } from "../types" const defaultState: IGatsbyState["status"] = { From ddda443889f6db78cfbffdf71b9322e081a40adb Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 29 Jun 2023 12:33:02 +0200 Subject: [PATCH 119/161] put requiredFiles in correct place heh --- .../gatsby-adapter-netlify/src/lambda-handler.ts | 12 +++--------- packages/gatsby/src/utils/adapter/manager.ts | 2 ++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 56ce714990662..ff8be2ee5bab8 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -48,18 +48,12 @@ async function prepareFunction( await fs.ensureDir(internalFunctionsDir) - const requiredFilesNormalized = fun.requiredFiles.map(file => - file.replace(/\[/g, `*`).replace(/]/g, `*`) - ) - const functionManifest: INetlifyFunctionManifest = { config: { generator: `gatsby-adapter-netlify@${packageJson?.version ?? `unknown`}`, - includedFiles: [ - `public/404.html`, - `public/500.html`, - ...requiredFilesNormalized, - ], + includedFiles: fun.requiredFiles.map(file => + file.replace(/\[/g, `*`).replace(/]/g, `*`) + ), externalNodeModules: [`msgpackr-extract`], }, version: 1, diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index f4c8af7d83a92..95e69908a56c1 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -433,6 +433,8 @@ function getFunctionsManifest(): FunctionsManifest { functionId: `ssr-engine`, pathToEntryPoint: posix.join(`.cache`, `page-ssr`, `lambda.js`), requiredFiles: [ + `public/404.html`, + `public/500.html`, ...(shouldBundleDatastore() ? getFilesFrom(posix.join(`.cache`, `data`, `datastore`)) : []), From 56824a177aa4ed15157c65b0f1adce2aa06ecbcf Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 29 Jun 2023 12:35:44 +0200 Subject: [PATCH 120/161] typo --- packages/gatsby/src/utils/adapter/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index b1c6a9c412fae..2dcc3e723ecaa 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -166,7 +166,7 @@ export interface IAdapter { * Hook to pass information from the adapter to Gatsby. You must return an object with a predefined shape. * Gatsby uses this information to adjust its build process. The information can be e.g. things that are only known once the project is deployed. * - * This hook is considered to be an advanced features of adapters and it is not required to implement it. + * This hook can enable advanced feature of adapters and it is not required to implement it. * * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ */ From 3e88e97a0a467d7430cf57d147c9e14be2fd77a6 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Thu, 29 Jun 2023 14:02:06 +0200 Subject: [PATCH 121/161] snapshot --- packages/gatsby/src/utils/adapter/__tests__/manager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index 00c2ef381710a..ccbf5136a3896 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -114,6 +114,8 @@ describe(`getFunctionsManifest`, () => { "functionId": "ssr-engine", "pathToEntryPoint": ".cache/page-ssr/lambda.js", "requiredFiles": Array [ + "public/404.html", + "public/500.html", ".cache/data/datastore/data.mdb", ".cache/page-ssr/lambda.js", ".cache/query-engine/index.js", From 016d697e2639efcae476da511d73ce374e31f260 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 29 Jun 2023 15:35:19 +0200 Subject: [PATCH 122/161] handle GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE env var --- packages/gatsby-adapter-netlify/src/index.ts | 44 +++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 7bfa7029324ef..c3885d242b0e8 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -63,35 +63,39 @@ const createNetlifyAdapter: AdapterInit = options => { ) } }, - config: (): IAdapterGatsbyConfig => { - // if (process.env.DEPLOY_URL && process.env.NETLIFY) { - // TODO: use env var as additional toggle on top on adapter options to ease migration from netlify plugins + config: ({ reporter }): IAdapterGatsbyConfig => { + // excludeDatastoreFromEngineFunction can be enabled either via options or via env var (to preserve handling of env var that existed in Netlify build plugin). + let excludeDatastoreFromEngineFunction = + options?.excludeDatastoreFromEngineFunction - let deployURL = process.env.NETLIFY_LOCAL + if ( + typeof excludeDatastoreFromEngineFunction === `undefined` && + typeof process.env.GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE !== `undefined` + ) { + excludeDatastoreFromEngineFunction = + process.env.GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE === `true` || + process.env.GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE === `1` + } + + if (typeof excludeDatastoreFromEngineFunction === `undefined`) { + excludeDatastoreFromEngineFunction = false + } + + const deployURL = process.env.NETLIFY_LOCAL ? `http://localhost:8888` : process.env.DEPLOY_URL - if (!deployURL) { - // for dev purposes - remove later - deployURL = `http://localhost:9000` + if (excludeDatastoreFromEngineFunction && !deployURL) { + reporter.warn( + `[gatsby-adapter-netlify] excludeDatastoreFromEngineFunction is set to true but no DEPLOY_URL is set. Disabling excludeDatastoreFromEngineFunction.` + ) + excludeDatastoreFromEngineFunction = false } return { - excludeDatastoreFromEngineFunction: - options?.excludeDatastoreFromEngineFunction ?? false, + excludeDatastoreFromEngineFunction, deployURL, } - // } - - // if (options?.excludeDatastoreFromEngineFunction) { - // reporter.warn( - // `[gatsby-adapter-netlify] excludeDatastoreFromEngineFunction is set to true but no DEPLOY_URL is set (running netlify command locally). Disabling excludeDatastoreFromEngineFunction.` - // ) - // } - - // return { - // excludeDatastoreFromEngineFunction: false, - // } }, } } From 342f2f946de07be04ca590c02557b32e92d30869 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 30 Jun 2023 09:00:24 +0200 Subject: [PATCH 123/161] improve README & update types --- packages/gatsby-adapter-netlify/README.md | 32 +++++++++++++++++-- packages/gatsby-adapter-netlify/src/index.ts | 16 ++++------ .../src/lambda-handler.ts | 2 -- .../src/route-handler.ts | 1 - packages/gatsby/index.d.ts | 2 +- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/packages/gatsby-adapter-netlify/README.md b/packages/gatsby-adapter-netlify/README.md index 6ebf50df74778..d53c56331d186 100644 --- a/packages/gatsby-adapter-netlify/README.md +++ b/packages/gatsby-adapter-netlify/README.md @@ -1,6 +1,18 @@ # gatsby-adapter-netlify -TODO: Description +Gatsby [adapter](https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/adapters/) for [Netlify](https://www.netlify.com/). + +This adapter enables following features on Netlify: + +- [Redirects](https://www.gatsbyjs.com/docs/reference/config-files/actions/#createRedirect) +- [HTTP Headers](https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/headers/) +- Application of [default caching headers](https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/caching/) +- [Deferred Static Generation (DSG)](https://www.gatsbyjs.com/docs/how-to/rendering-options/using-deferred-static-generation/) +- [Server-Side Rendering (SSR)](https://www.gatsbyjs.com/docs/how-to/rendering-options/using-server-side-rendering/) +- [Gatsby Functions](https://www.gatsbyjs.com/docs/reference/functions/) +- Caching of builds between deploys + +This adapter is part of Gatsby's [zero-configuration deployments](https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/zero-configuration-deployments/) feature and will be installed automatically on Netlify. You can add `gatsby-adapter-netlify` to your `dependencies` and `gatsby-config` to have more robust installs and to be able to change its options. ## Installation @@ -10,4 +22,20 @@ npm install gatsby-adapter-netlify ## Usage -TODO +Add `gatsby-adapter-netlify` to your [`gatsby-config`](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/) and configure the [`adapter`](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/#adapter) option. + +```js:title=gatsby-config.js +const adapter = require("gatsby-adapter-netlify") + +module.exports = { + adapter: adapter({ + excludeDatastoreFromEngineFunction: false, + }) +} +``` + +### Options + +**excludeDatastoreFromEngineFunction** (optional, default: `false`) + +If `true`, Gatsby will not include the LMDB datastore in the serverless functions used for SSR/DSG. Instead, it will upload the datastore to Netlify's CDN and download it on first load of the functions. diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index c3885d242b0e8..3f47794d2c486 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -1,10 +1,4 @@ -import type { AdapterInit, IAdapterGatsbyConfig } from "gatsby" - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface INetlifyAdapterOptions { - excludeDatastoreFromEngineFunction?: boolean -} - +import type { AdapterInit, IAdapterConfig } from "gatsby" import { prepareFunctionVariants } from "./lambda-handler" import { handleRoutesManifest } from "./route-handler" @@ -13,6 +7,10 @@ interface INetlifyCacheUtils { save: (paths: Array) => Promise } +interface INetlifyAdapterOptions { + excludeDatastoreFromEngineFunction?: boolean +} + async function getCacheUtils(): Promise { if (process.env.NETLIFY) { const CACHE_DIR = `/opt/build/cache` @@ -28,7 +26,6 @@ const createNetlifyAdapter: AdapterInit = options => { name: `gatsby-adapter-netlify`, cache: { async restore({ directories, reporter }): Promise { - reporter.info(`[gatsby-adapter-netlify] cache.restore() ${directories}`) const utils = await getCacheUtils() if (utils) { reporter.info( @@ -40,7 +37,6 @@ const createNetlifyAdapter: AdapterInit = options => { return false }, async store({ directories, reporter }): Promise { - reporter.info(`[gatsby-adapter-netlify] cache.store() ${directories}`) const utils = await getCacheUtils() if (utils) { reporter.info( @@ -63,7 +59,7 @@ const createNetlifyAdapter: AdapterInit = options => { ) } }, - config: ({ reporter }): IAdapterGatsbyConfig => { + config: ({ reporter }): IAdapterConfig => { // excludeDatastoreFromEngineFunction can be enabled either via options or via env var (to preserve handling of env var that existed in Netlify build plugin). let excludeDatastoreFromEngineFunction = options?.excludeDatastoreFromEngineFunction diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index ff8be2ee5bab8..cb116e353b4bf 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -1,7 +1,5 @@ import type { IFunctionDefinition } from "gatsby" - import packageJson from "gatsby-adapter-netlify/package.json" - import fs from "fs-extra" import * as path from "path" diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index 4b7fe44329fa9..cec7da1772fcc 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -1,5 +1,4 @@ import type { RoutesManifest } from "gatsby" - import { EOL } from "os" import fs from "fs-extra" diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index f17cf2c79415c..f37f6dfd5f8e6 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -42,7 +42,7 @@ export { IFunctionDefinition, RoutesManifest, FunctionsManifest, - IAdapterGatsbyConfig, + IAdapterConfig, } from "./dist/utils/adapter/types" export const useScrollRestoration: (key: string) => { From 81780af779209cd2709989a4ed342a9aaf54f79c Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 30 Jun 2023 09:02:14 +0200 Subject: [PATCH 124/161] code block --- packages/gatsby-adapter-netlify/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-adapter-netlify/README.md b/packages/gatsby-adapter-netlify/README.md index d53c56331d186..945d9fe60f9e8 100644 --- a/packages/gatsby-adapter-netlify/README.md +++ b/packages/gatsby-adapter-netlify/README.md @@ -24,13 +24,13 @@ npm install gatsby-adapter-netlify Add `gatsby-adapter-netlify` to your [`gatsby-config`](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/) and configure the [`adapter`](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/#adapter) option. -```js:title=gatsby-config.js +```js const adapter = require("gatsby-adapter-netlify") module.exports = { adapter: adapter({ excludeDatastoreFromEngineFunction: false, - }) + }), } ``` From 6a18b5cfbec8aa5bb69deadb8efdfe1a0af62138 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 10:47:11 +0200 Subject: [PATCH 125/161] handle partytown routes --- packages/gatsby/src/utils/adapter/manager.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 95e69908a56c1..3a7e3cacc8528 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -373,16 +373,31 @@ function getRoutesManifest(): RoutesManifest { } // TODO: Remove before final merge - console.log(`[Adapters] unmanaged (or not yet handled) assets`, fileAssets) + const notYetHandled = new Set() for (const fileAsset of fileAssets) { + // try to classify remaining assets + let headers: IHeader["headers"] | undefined = undefined + + if (fileAsset.startsWith(`~partytown`)) { + // no hashes, must revalidate + headers = STATIC_PAGE_HEADERS + } + + if (!headers) { + headers = ASSET_HEADERS + notYetHandled.add(fileAsset) + } + addStaticRoute({ path: fileAsset, pathToFillInPublicDir: fileAsset, - headers: ASSET_HEADERS, + headers, }) } + console.log(`[Adapters] unmanaged (or not yet handled) assets`, notYetHandled) + return ( routes .sort((a, b) => { From bac54123cfe6f68ccdb50fa525aaef98abcb54b9 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 10:47:43 +0200 Subject: [PATCH 126/161] handle slices (html and slice-data) --- packages/gatsby/src/utils/adapter/manager.ts | 35 +++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 3a7e3cacc8528..f7ff2ddd71744 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -345,7 +345,40 @@ function getRoutesManifest(): RoutesManifest { }) } - // TODO: slices + for (const slice of state.slices.values()) { + const sliceDataPath = posix.join(`slice-data`, `${slice.name}.json`) + + addStaticRoute({ + path: sliceDataPath, + pathToFillInPublicDir: sliceDataPath, + headers: STATIC_PAGE_HEADERS, + }) + } + + function addSliceHtmlRoute(name: string, hasChildren: boolean): void { + const sliceHtml1Path = posix.join(`_gatsby`, `slices`, `${name}-1.html`) + addStaticRoute({ + path: sliceHtml1Path, + pathToFillInPublicDir: sliceHtml1Path, + headers: STATIC_PAGE_HEADERS, + }) + if (hasChildren) { + const sliceHtml2Path = posix.join(`_gatsby`, `slices`, `${name}-2.html`) + addStaticRoute({ + path: sliceHtml2Path, + pathToFillInPublicDir: sliceHtml2Path, + headers: STATIC_PAGE_HEADERS, + }) + } + } + + addSliceHtmlRoute(`_gatsby-scripts`, false) + for (const [ + name, + { hasChildren }, + ] of state.html.slicesProps.bySliceId.entries()) { + addSliceHtmlRoute(name, hasChildren) + } // redirect routes for (const redirect of state.redirects.values()) { From 363e6c54238be54008b231cee664c91647012621 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 11:00:25 +0200 Subject: [PATCH 127/161] handle chunk-map and webpack.stats --- packages/gatsby/src/utils/adapter/manager.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index f7ff2ddd71744..63dd1e7914b4d 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -345,6 +345,26 @@ function getRoutesManifest(): RoutesManifest { }) } + // chunk-map.json + { + const chunkMapFilePath = posix.join(`chunk-map.json`) + addStaticRoute({ + path: chunkMapFilePath, + pathToFillInPublicDir: chunkMapFilePath, + headers: STATIC_PAGE_HEADERS, + }) + } + + // webpack.stats.json + { + const webpackStatsFilePath = posix.join(`webpack.stats.json`) + addStaticRoute({ + path: webpackStatsFilePath, + pathToFillInPublicDir: webpackStatsFilePath, + headers: STATIC_PAGE_HEADERS, + }) + } + for (const slice of state.slices.values()) { const sliceDataPath = posix.join(`slice-data`, `${slice.name}.json`) From 4ddabf3ce17d0ccd1d05a540649e021f0f94f236 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 30 Jun 2023 11:15:55 +0200 Subject: [PATCH 128/161] feat: add name to functionsManifest & displayName to Netlify --- packages/gatsby-adapter-netlify/src/lambda-handler.ts | 8 ++++++++ packages/gatsby/src/utils/adapter/manager.ts | 7 +++++++ packages/gatsby/src/utils/adapter/types.ts | 10 +++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index cb116e353b4bf..74b3685a17cd0 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -46,8 +46,16 @@ async function prepareFunction( await fs.ensureDir(internalFunctionsDir) + // This is a temporary hacky approach, eventually it should be just `fun.name` + const displayName = isODB + ? `DSG` + : fun.name === `SSR & DSG` + ? `SSR` + : fun.name + const functionManifest: INetlifyFunctionManifest = { config: { + name: displayName, generator: `gatsby-adapter-netlify@${packageJson?.version ?? `unknown`}`, includedFiles: fun.requiredFiles.map(file => file.replace(/\[/g, `*`).replace(/]/g, `*`) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 63dd1e7914b4d..75bc3e967f4fd 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -481,8 +481,14 @@ function getFunctionsManifest(): FunctionsManifest { `functions`, functionInfo.relativeCompiledFilePath ) + const relativePathWithoutFileExtension = posix.join( + posix.parse(functionInfo.originalRelativeFilePath).dir, + posix.parse(functionInfo.originalRelativeFilePath).name + ) + functions.push({ functionId: functionInfo.functionId, + name: `/api/${relativePathWithoutFileExtension}`, pathToEntryPoint, requiredFiles: [pathToEntryPoint], }) @@ -500,6 +506,7 @@ function getFunctionsManifest(): FunctionsManifest { functions.push({ functionId: `ssr-engine`, pathToEntryPoint: posix.join(`.cache`, `page-ssr`, `lambda.js`), + name: `SSR & DSG`, requiredFiles: [ `public/404.html`, `public/500.html`, diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 2dcc3e723ecaa..7da14155cbabb 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -73,11 +73,15 @@ export interface IFunctionDefinition { */ functionId: string /** - * Path to function entrypoint that will be used to create function. + * Unique name of this function. Use this as a display name for the function. + */ + name: string + /** + * Path to function entrypoint that will be used to create function */ pathToEntryPoint: string /** - * List of all required files that this function needs to run. + * List of all required files that this function needs to run */ requiredFiles: Array } @@ -86,7 +90,7 @@ export type FunctionsManifest = Array interface IDefaultContext { /** - * Reporter instance that can be used to log messages to terminal. + * Reporter instance that can be used to log messages to terminal * @see https://www.gatsbyjs.com/docs/reference/config-files/node-api-helpers/#reporter */ reporter: typeof reporter From e1e220160b9e23435418de80673e2ebed210b482 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 30 Jun 2023 11:17:23 +0200 Subject: [PATCH 129/161] update snapshot --- packages/gatsby/src/utils/adapter/__tests__/manager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/gatsby/src/utils/adapter/__tests__/manager.ts b/packages/gatsby/src/utils/adapter/__tests__/manager.ts index ccbf5136a3896..8fc17c0e90b60 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/manager.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/manager.ts @@ -105,6 +105,7 @@ describe(`getFunctionsManifest`, () => { Array [ Object { "functionId": "static-index-js", + "name": "/api/static/index", "pathToEntryPoint": ".cache/functions/static/index.js", "requiredFiles": Array [ ".cache/functions/static/index.js", @@ -112,6 +113,7 @@ describe(`getFunctionsManifest`, () => { }, Object { "functionId": "ssr-engine", + "name": "SSR & DSG", "pathToEntryPoint": ".cache/page-ssr/lambda.js", "requiredFiles": Array [ "public/404.html", From 9b2e632296b0c52da1c99bb1391f9e2671901387 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 12:10:56 +0200 Subject: [PATCH 130/161] rename headers constants --- .../gatsby/src/utils/adapter/constants.ts | 63 +++---------------- packages/gatsby/src/utils/adapter/manager.ts | 32 +++++----- .../gatsby/src/utils/page-ssr-module/entry.ts | 4 +- 3 files changed, 25 insertions(+), 74 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/constants.ts b/packages/gatsby/src/utils/adapter/constants.ts index 86fc43784012a..ff396fcebb415 100644 --- a/packages/gatsby/src/utils/adapter/constants.ts +++ b/packages/gatsby/src/utils/adapter/constants.ts @@ -1,10 +1,6 @@ import type { IHeader } from "../../redux/types" -export const STATIC_PAGE_HEADERS: IHeader["headers"] = [ - { - key: `cache-control`, - value: `public, max-age=0, must-revalidate`, - }, +export const BASE_HEADERS: IHeader["headers"] = [ { key: `x-xss-protection`, value: `1; mode=block`, @@ -23,63 +19,18 @@ export const STATIC_PAGE_HEADERS: IHeader["headers"] = [ }, ] -export const REDIRECT_HEADERS: IHeader["headers"] = [ - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, - { - key: `x-content-type-options`, - value: `nosniff`, - }, - { - key: `referrer-policy`, - value: `same-origin`, - }, - { - key: `x-frame-options`, - value: `DENY`, - }, -] - -export const ASSET_HEADERS: IHeader["headers"] = [ - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, - { - key: `x-content-type-options`, - value: `nosniff`, - }, - { - key: `referrer-policy`, - value: `same-origin`, - }, +export const MUST_REVALIDATE_HEADERS: IHeader["headers"] = [ { - key: `x-frame-options`, - value: `DENY`, + key: `cache-control`, + value: `public, max-age=0, must-revalidate`, }, + ...BASE_HEADERS, ] -export const WEBPACK_ASSET_HEADERS: IHeader["headers"] = [ +export const PERMAMENT_CACHING_HEADERS: IHeader["headers"] = [ { key: `cache-control`, value: `public, max-age=31536000, immutable`, }, - { - key: `x-xss-protection`, - value: `1; mode=block`, - }, - { - key: `x-content-type-options`, - value: `nosniff`, - }, - { - key: `referrer-policy`, - value: `same-origin`, - }, - { - key: `x-frame-options`, - value: `DENY`, - }, + ...BASE_HEADERS, ] diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 75bc3e967f4fd..d6a1131633afb 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -28,10 +28,9 @@ import { shouldGenerateEngines, } from "../engines-helpers" import { - ASSET_HEADERS, - REDIRECT_HEADERS, - STATIC_PAGE_HEADERS, - WEBPACK_ASSET_HEADERS, + BASE_HEADERS, + MUST_REVALIDATE_HEADERS, + PERMAMENT_CACHING_HEADERS, } from "./constants" import { createHeadersMatcher } from "./create-headers" import { HTTP_STATUS_CODE } from "../../redux/types" @@ -282,12 +281,12 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: htmlRoutePath, pathToFillInPublicDir: htmlFilePath, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) addStaticRoute({ path: pageDataRoutePath, pathToFillInPublicDir: pageDataFilePath, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) } else { const commonFields: Omit = { @@ -317,7 +316,7 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: staticQueryResultPath, pathToFillInPublicDir: staticQueryResultPath, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) } @@ -327,7 +326,7 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: appDataFilePath, pathToFillInPublicDir: appDataFilePath, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) } @@ -341,7 +340,7 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: asset, pathToFillInPublicDir: asset, - headers: WEBPACK_ASSET_HEADERS, + headers: PERMAMENT_CACHING_HEADERS, }) } @@ -351,7 +350,7 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: chunkMapFilePath, pathToFillInPublicDir: chunkMapFilePath, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) } @@ -361,7 +360,7 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: webpackStatsFilePath, pathToFillInPublicDir: webpackStatsFilePath, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) } @@ -371,7 +370,7 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: sliceDataPath, pathToFillInPublicDir: sliceDataPath, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) } @@ -380,14 +379,14 @@ function getRoutesManifest(): RoutesManifest { addStaticRoute({ path: sliceHtml1Path, pathToFillInPublicDir: sliceHtml1Path, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) if (hasChildren) { const sliceHtml2Path = posix.join(`_gatsby`, `slices`, `${name}-2.html`) addStaticRoute({ path: sliceHtml2Path, pathToFillInPublicDir: sliceHtml2Path, - headers: STATIC_PAGE_HEADERS, + headers: MUST_REVALIDATE_HEADERS, }) } } @@ -412,7 +411,7 @@ function getRoutesManifest(): RoutesManifest { ? HTTP_STATUS_CODE.MOVED_PERMANENTLY_301 : HTTP_STATUS_CODE.FOUND_302), ignoreCase: redirect.ignoreCase, - headers: REDIRECT_HEADERS, + headers: BASE_HEADERS, }) } @@ -434,11 +433,12 @@ function getRoutesManifest(): RoutesManifest { if (fileAsset.startsWith(`~partytown`)) { // no hashes, must revalidate + headers = MUST_REVALIDATE_HEADERS headers = STATIC_PAGE_HEADERS } if (!headers) { - headers = ASSET_HEADERS + headers = BASE_HEADERS notYetHandled.add(fileAsset) } diff --git a/packages/gatsby/src/utils/page-ssr-module/entry.ts b/packages/gatsby/src/utils/page-ssr-module/entry.ts index e90e2375a0fad..de6c333f53d13 100644 --- a/packages/gatsby/src/utils/page-ssr-module/entry.ts +++ b/packages/gatsby/src/utils/page-ssr-module/entry.ts @@ -32,7 +32,7 @@ import { initTracer } from "../tracer" import { getCodeFrame } from "../../query/graphql-errors-codeframe" import { ICollectedSlice } from "../babel/find-slices" import { createHeadersMatcher } from "../adapter/create-headers" -import { STATIC_PAGE_HEADERS } from "../adapter/constants" +import { MUST_REVALIDATE_HEADERS } from "../adapter/constants" import { getRoutePathFromPage } from "../adapter/get-route-path" export interface ITemplateDetails { @@ -230,7 +230,7 @@ export async function getData({ // get headers from defaults and config const headersFromConfig = createHeaders( getRoutePathFromPage(page), - STATIC_PAGE_HEADERS + MUST_REVALIDATE_HEADERS ) // convert headers array to object for (const header of headersFromConfig) { From c0a5402690b4f36503d55149cf36f8e9ece01342 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 12:12:24 +0200 Subject: [PATCH 131/161] handle image-cdn and file-cdn --- packages/gatsby/src/utils/adapter/manager.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index d6a1131633afb..23c1bc85b2302 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -434,7 +434,11 @@ function getRoutesManifest(): RoutesManifest { if (fileAsset.startsWith(`~partytown`)) { // no hashes, must revalidate headers = MUST_REVALIDATE_HEADERS - headers = STATIC_PAGE_HEADERS + } else if ( + fileAsset.startsWith(`_gatsby/image`) || + fileAsset.startsWith(`_gatsby/file`) + ) { + headers = PERMAMENT_CACHING_HEADERS } if (!headers) { From d294c7da3afff64ab03ef2adb53942a744ff9bb4 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 12:24:46 +0200 Subject: [PATCH 132/161] update routesManifest test fixture and snapshot --- .../__tests__/__snapshots__/manager.ts.snap | 58 +++++++++++++++++++ .../_gatsby/slices/_gatsby-scripts-1.html | 0 .../__tests__/fixtures/public/chunk-map.json | 1 + .../utils/adapter/__tests__/fixtures/state.ts | 21 ++++++- 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/_gatsby/slices/_gatsby-scripts-1.html create mode 100644 packages/gatsby/src/utils/adapter/__tests__/fixtures/public/chunk-map.json diff --git a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap index 267a5909a5efd..d1a544ca1a1a8 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap +++ b/packages/gatsby/src/utils/adapter/__tests__/__snapshots__/manager.ts.snap @@ -29,6 +29,33 @@ Array [ "path": "/page-data/sq/d/1.json", "type": "static", }, + Object { + "filePath": "public/_gatsby/slices/_gatsby-scripts-1.html", + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/_gatsby/slices/_gatsby-scripts-1.html", + "type": "static", + }, Object { "cache": true, "functionId": "ssr-engine", @@ -126,6 +153,33 @@ Array [ "path": "/app-123.js", "type": "static", }, + Object { + "filePath": "public/chunk-map.json", + "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, + Object { + "key": "x-xss-protection", + "value": "1; mode=block", + }, + Object { + "key": "x-content-type-options", + "value": "nosniff", + }, + Object { + "key": "referrer-policy", + "value": "same-origin", + }, + Object { + "key": "x-frame-options", + "value": "DENY", + }, + ], + "path": "/chunk-map.json", + "type": "static", + }, Object { "cache": true, "functionId": "ssr-engine", @@ -165,6 +219,10 @@ Array [ Object { "filePath": "public/webpack.stats.json", "headers": Array [ + Object { + "key": "cache-control", + "value": "public, max-age=0, must-revalidate", + }, Object { "key": "x-xss-protection", "value": "1; mode=block", diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/_gatsby/slices/_gatsby-scripts-1.html b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/_gatsby/slices/_gatsby-scripts-1.html new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/chunk-map.json b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/chunk-map.json new file mode 100644 index 0000000000000..9e26dfeeb6e64 --- /dev/null +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/public/chunk-map.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts b/packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts index b68132172fda8..471317128314f 100644 --- a/packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts +++ b/packages/gatsby/src/utils/adapter/__tests__/fixtures/state.ts @@ -121,6 +121,23 @@ components.set('/x/src/pages/ssr.tsx', { Head: false }) +const slices: IGatsbyState["slices"] = new Map() + +const html: IGatsbyState["html"] = { + trackedHtmlFiles: new Map(), + browserCompilationHash: ``, + ssrCompilationHash: ``, + trackedStaticQueryResults: new Map(), + unsafeBuiltinWasUsedInSSR: false, + templateCompilationHashes: {}, + slicesProps: { + bySliceId: new Map(), + byPagePath: new Map(), + bySliceName: new Map(), + }, + pagesThatNeedToStitchSlices: new Set() +} + export const state = { pages, staticQueryComponents, @@ -129,5 +146,7 @@ export const state = { config: { headers: [], }, + slices, + html, components, -} as unknown as IGatsbyState \ No newline at end of file +} as unknown as IGatsbyState From 15ec313cfea10078b7d0654744134abe268abcbf Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 13:54:41 +0200 Subject: [PATCH 133/161] don't log unmanaged static assets anymore --- packages/gatsby/src/utils/adapter/manager.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 23c1bc85b2302..55d33ddf22105 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -424,9 +424,6 @@ function getRoutesManifest(): RoutesManifest { }) } - // TODO: Remove before final merge - const notYetHandled = new Set() - for (const fileAsset of fileAssets) { // try to classify remaining assets let headers: IHeader["headers"] | undefined = undefined @@ -443,7 +440,6 @@ function getRoutesManifest(): RoutesManifest { if (!headers) { headers = BASE_HEADERS - notYetHandled.add(fileAsset) } addStaticRoute({ @@ -453,8 +449,6 @@ function getRoutesManifest(): RoutesManifest { }) } - console.log(`[Adapters] unmanaged (or not yet handled) assets`, notYetHandled) - return ( routes .sort((a, b) => { From b5ac1e389bd7cbaaf31eaf551b24f1cbbab46856 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Fri, 30 Jun 2023 14:50:12 +0200 Subject: [PATCH 134/161] handle some TODOs --- .../gatsby-cli/src/structured-errors/error-map.ts | 8 ++++++++ packages/gatsby-cli/src/structured-errors/types.ts | 1 + packages/gatsby/adapters.js | 2 +- .../src/internal-plugins/functions/gatsby-node.ts | 1 - .../functions/match-path-webpack-loader.ts | 3 +-- packages/gatsby/src/utils/adapter/manager.ts | 14 +++++--------- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/gatsby-cli/src/structured-errors/error-map.ts b/packages/gatsby-cli/src/structured-errors/error-map.ts index 47b18bb7af70e..0237ac5e3faf6 100644 --- a/packages/gatsby-cli/src/structured-errors/error-map.ts +++ b/packages/gatsby-cli/src/structured-errors/error-map.ts @@ -918,6 +918,14 @@ const errors: Record = { category: ErrorCategory.USER, docsUrl: `https://gatsby.dev/graphql-typegen`, }, + // Gatsby Adapters + "12200": { + text: (): string => + `[Adapters] Tried to create routes for webpack assets but failed. If the issue persists, please open an issue with a reproduction at https://gatsby.dev/bug-report for more help.`, + level: Level.ERROR, + type: Type.ADAPTER, + category: ErrorCategory.SYSTEM, + }, // Partial hydration "80000": { text: (context): string => diff --git a/packages/gatsby-cli/src/structured-errors/types.ts b/packages/gatsby-cli/src/structured-errors/types.ts index 1383b50f5666b..9557279a0e7b7 100644 --- a/packages/gatsby-cli/src/structured-errors/types.ts +++ b/packages/gatsby-cli/src/structured-errors/types.ts @@ -82,6 +82,7 @@ export enum Type { FUNCTIONS_COMPILATION = `FUNCTIONS.COMPILATION`, FUNCTIONS_EXECUTION = `FUNCTIONS.EXECUTION`, CLI_VALIDATION = `CLI.VALIDATION`, + ADAPTER = `ADAPTER`, // webpack errors for each stage enum: packages/gatsby/src/commands/types.ts WEBPACK_DEVELOP = `WEBPACK.DEVELOP`, WEBPACK_DEVELOP_HTML = `WEBPACK.DEVELOP-HTML`, diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index 086233eb2c5e0..565b8fb2049cd 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -3,7 +3,7 @@ * The first item which test function returns `true` will be used. * * If you're the author of an adapter and want to add it to this list, please open a PR! - * If you want to create an adapter, please see: TODO + * If you want to create an adapter, please see: http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ * * @type {import("./src/utils/adapter/types").IAdapterManifestEntry} * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/zero-configuration-deployments/ diff --git a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts index eb59b96bcb0ac..4fe353dcae5ce 100644 --- a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts +++ b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts @@ -183,7 +183,6 @@ const createWebpackConfig = async ({ relativeCompiledFilePath: compiledFunctionName, absoluteCompiledFilePath: compiledPath, matchPath: getMatchPath(finalName), - // TODO: maybe figure out better functionId functionId: _.kebabCase(compiledFunctionName), }) }) diff --git a/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts b/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts index bc4657bacb9b1..0c9422f6d9b6a 100644 --- a/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts +++ b/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts @@ -23,8 +23,7 @@ const MatchPathLoader: LoaderDefinition = async function () { if (matchResult) { req.params = matchResult.params if (req.params['*']) { - // Backwards compatability for v3 - // TODO remove in v5 + // TODO(v6): Remove this backwards compatability for v3 req.params['0'] = req.params['*'] } } diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 55d33ddf22105..83bb1b74e1628 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -83,8 +83,6 @@ export async function initAdapterManager(): Promise { const directoriesToCache = [`.cache`, `public`] const manager: IAdapterManager = { restoreCache: async (): Promise => { - // TODO: Remove before final merge - reporter.info(`[Adapters] restoreCache()`) if (!adapter.cache) { return } @@ -110,8 +108,6 @@ export async function initAdapterManager(): Promise { } }, storeCache: async (): Promise => { - // TODO: Remove before final merge - reporter.info(`[Adapters] storeCache()`) if (!adapter.cache) { return } @@ -119,8 +115,6 @@ export async function initAdapterManager(): Promise { await adapter.cache.store({ directories: directoriesToCache, reporter }) }, adapt: async (): Promise => { - // TODO: Remove before final merge - reporter.info(`[Adapters] adapt()`) if (!adapter.adapt) { return } @@ -262,7 +256,7 @@ function getRoutesManifest(): RoutesManifest { fileAssets.delete(pathToFillInPublicDir) } else { reporter.warn( - `[Adapters] Tried to remove "${pathToFillInPublicDir}" from assets but it wasn't there` + `[Adapters] Tried to remove "${pathToFillInPublicDir}" from fileAssets but it wasn't there` ) } } @@ -332,8 +326,10 @@ function getRoutesManifest(): RoutesManifest { // webpack assets if (!webpackAssets) { - // TODO: Make this a structured error - throw new Error(`[Adapters] webpackAssets is not defined`) + reporter.panic({ + id: `12200`, + context: {}, + }) } for (const asset of webpackAssets) { From 6496cf97bdf36031a63214c5497cfcb2c9533acc Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 30 Jun 2023 15:52:01 +0200 Subject: [PATCH 135/161] add 'adapters' to feature list --- packages/gatsby/index.d.ts | 1 + packages/gatsby/scripts/__tests__/api.js | 1 + packages/gatsby/scripts/output-api-file.js | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/gatsby/index.d.ts b/packages/gatsby/index.d.ts index f37f6dfd5f8e6..00bb8ed93ef5f 100644 --- a/packages/gatsby/index.d.ts +++ b/packages/gatsby/index.d.ts @@ -22,6 +22,7 @@ export type AvailableFeatures = | "graphql-typegen" | "content-file-path" | "stateful-source-nodes" + | "adapters" export { Link, diff --git a/packages/gatsby/scripts/__tests__/api.js b/packages/gatsby/scripts/__tests__/api.js index c3bd68d38e038..dc1c6ce7ca41e 100644 --- a/packages/gatsby/scripts/__tests__/api.js +++ b/packages/gatsby/scripts/__tests__/api.js @@ -36,6 +36,7 @@ it("generates the expected api output", done => { "content-file-path", "slices", "stateful-source-nodes", + "adapters", ], "node": Object { "createPages": Object {}, diff --git a/packages/gatsby/scripts/output-api-file.js b/packages/gatsby/scripts/output-api-file.js index 8863f26421d7a..8f3cf1b5f7c32 100644 --- a/packages/gatsby/scripts/output-api-file.js +++ b/packages/gatsby/scripts/output-api-file.js @@ -41,7 +41,7 @@ async function outputFile() { }, {}) /** @type {Array} */ - output.features = ["image-cdn", "graphql-typegen", "content-file-path", "slices", "stateful-source-nodes"]; + output.features = ["image-cdn", "graphql-typegen", "content-file-path", "slices", "stateful-source-nodes", "adapters"]; return fs.writeFile( path.resolve(OUTPUT_FILE_NAME), From 6662f45034e4b9f5c1fbadf25efcf7bda191cc51 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 4 Jul 2023 09:35:59 +0200 Subject: [PATCH 136/161] tmp: make peer dependency allow to use with canaries --- packages/gatsby-adapter-netlify/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index 545618e07c4bc..a0c83d976f9bf 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -46,7 +46,7 @@ "typescript": "^5.0.4" }, "peerDependencies": { - "gatsby": "^5.11.0-alpha" + "gatsby": "^5.10.0-alpha" }, "files": [ "dist/" From 8ba5fae71ada084e9157a4cf962c53743379cab3 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 4 Jul 2023 17:51:32 +0200 Subject: [PATCH 137/161] feat: add 'supports' to config to let adapters provide some capabilities and potentially fail builds with clear explanation instead of producing faulty deploy --- packages/gatsby-adapter-netlify/src/index.ts | 4 + .../src/structured-errors/error-map.ts | 11 +++ packages/gatsby/src/utils/adapter/manager.ts | 88 +++++++++++++++---- packages/gatsby/src/utils/adapter/types.ts | 13 +++ 4 files changed, 98 insertions(+), 18 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 3f47794d2c486..858de6ca742ba 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -91,6 +91,10 @@ const createNetlifyAdapter: AdapterInit = options => { return { excludeDatastoreFromEngineFunction, deployURL, + supports: { + pathPrefix: false, + trailingSlash: [`always`], + }, } }, } diff --git a/packages/gatsby-cli/src/structured-errors/error-map.ts b/packages/gatsby-cli/src/structured-errors/error-map.ts index 0237ac5e3faf6..d938ad18f7ccf 100644 --- a/packages/gatsby-cli/src/structured-errors/error-map.ts +++ b/packages/gatsby-cli/src/structured-errors/error-map.ts @@ -926,6 +926,17 @@ const errors: Record = { type: Type.ADAPTER, category: ErrorCategory.SYSTEM, }, + "12201": { + text: (context): string => + `Adapter "${ + context.adapterName + }" is not compatible with following settings:\n${context.incompatibleFeatures + .map(line => ` - ${line}`) + .join(`\n`)}`, + level: Level.ERROR, + type: Type.ADAPTER, + category: ErrorCategory.THIRD_PARTY, + }, // Partial hydration "80000": { text: (context): string => diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 83bb1b74e1628..1e37baeee1208 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -43,6 +43,72 @@ import { import { noOpAdapterManager } from "./no-op-manager" import { getDefaultDbPath } from "../../datastore/lmdb/lmdb-datastore" +async function setAdapter({ + instance, + manager, +}: { + instance?: IAdapter + manager: IAdapterManager +}): Promise { + const configFromAdapter = await manager.config() + + store.dispatch({ + type: `SET_ADAPTER`, + payload: { + manager, + instance, + config: configFromAdapter, + }, + }) + + if (instance) { + // if adapter reports that it doesn't support certain features, we need to fail the build + // to avoid broken deploys + + const incompatibleFeatures: Array = [] + + // pathPrefix support + if ( + configFromAdapter?.supports?.pathPrefix === false && + store.getState().program.prefixPaths && + store.getState().config.pathPrefix + ) { + incompatibleFeatures.push( + `pathPrefix is not supported. Please remove the pathPrefix option from your gatsby-config.js, don't use "--prefix-paths" CLI toggle or PREFIX_PATHS environment variable.` + ) + } + + // trailingSlash support + if (configFromAdapter?.supports?.trailingSlash) { + const { trailingSlash } = store.getState().config + + if ( + !configFromAdapter.supports.trailingSlash.includes( + trailingSlash ?? `always` + ) + ) { + incompatibleFeatures.push( + `trailingSlash option "${trailingSlash}". Supported option${ + configFromAdapter.supports.trailingSlash.length > 1 ? `s` : `` + }: ${configFromAdapter.supports.trailingSlash + .map(option => `"${option}"`) + .join(`, `)}` + ) + } + } + + if (incompatibleFeatures.length > 0) { + reporter.panic({ + id: `12201`, + context: { + adapterName: instance.name, + incompatibleFeatures, + }, + }) + } + } +} + export async function initAdapterManager(): Promise { let adapter: IAdapter @@ -62,15 +128,9 @@ export async function initAdapterManager(): Promise { telemetry.trackFeatureIsUsed(`adapter:no-op`) const manager = noOpAdapterManager() - const configFromAdapter = await manager.config() - store.dispatch({ - type: `SET_ADAPTER`, - payload: { - manager, - config: configFromAdapter, - }, - }) + await setAdapter({ manager }) + return manager } @@ -175,20 +235,12 @@ export async function initAdapterManager(): Promise { excludeDatastoreFromEngineFunction: configFromAdapter?.excludeDatastoreFromEngineFunction ?? false, deployURL: configFromAdapter?.deployURL, + supports: configFromAdapter?.supports, } }, } - const configFromAdapter = await manager.config() - - store.dispatch({ - type: `SET_ADAPTER`, - payload: { - manager, - instance: adapter, - config: configFromAdapter, - }, - }) + await setAdapter({ manager, instance: adapter }) return manager } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 7da14155cbabb..98fac6ad4508f 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -123,6 +123,19 @@ export interface IAdapterConfig { * Instead, it will try to download the datastore from the given `deployURL`. */ excludeDatastoreFromEngineFunction?: boolean + /** + * Adapters can optionally describe which features they support to prevent potentially faulty deployments. + */ + supports?: { + /** + * If `false`, Gatsby will fail the build if user tries to use pathPrefix. + */ + pathPrefix?: boolean + /** + * Provide array of supported traling slash options ("always", "never", "ignore"). + */ + trailingSlash?: Array + } } type WithRequired = T & { [P in K]-?: T[P] } From 2adc3065e95fca4b22eb52784d309dc5400f17a1 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 5 Jul 2023 08:26:04 +0200 Subject: [PATCH 138/161] adjust some text --- packages/gatsby-cli/src/structured-errors/error-map.ts | 2 +- packages/gatsby/src/utils/adapter/manager.ts | 2 +- packages/gatsby/src/utils/adapter/types.ts | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/gatsby-cli/src/structured-errors/error-map.ts b/packages/gatsby-cli/src/structured-errors/error-map.ts index d938ad18f7ccf..53494e9fe9786 100644 --- a/packages/gatsby-cli/src/structured-errors/error-map.ts +++ b/packages/gatsby-cli/src/structured-errors/error-map.ts @@ -921,7 +921,7 @@ const errors: Record = { // Gatsby Adapters "12200": { text: (): string => - `[Adapters] Tried to create routes for webpack assets but failed. If the issue persists, please open an issue with a reproduction at https://gatsby.dev/bug-report for more help.`, + `Tried to create adapter routes for webpack assets but failed. If the issue persists, please open an issue with a reproduction at https://gatsby.dev/bug-report for more help.`, level: Level.ERROR, type: Type.ADAPTER, category: ErrorCategory.SYSTEM, diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 1e37baeee1208..3a154f2731222 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -74,7 +74,7 @@ async function setAdapter({ store.getState().config.pathPrefix ) { incompatibleFeatures.push( - `pathPrefix is not supported. Please remove the pathPrefix option from your gatsby-config.js, don't use "--prefix-paths" CLI toggle or PREFIX_PATHS environment variable.` + `pathPrefix is not supported. Please remove the pathPrefix option from your gatsby-config, don't use "--prefix-paths" CLI toggle or PREFIX_PATHS environment variable.` ) } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 98fac6ad4508f..1ddab6357701a 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -124,7 +124,7 @@ export interface IAdapterConfig { */ excludeDatastoreFromEngineFunction?: boolean /** - * Adapters can optionally describe which features they support to prevent potentially faulty deployments. + * Adapters can optionally describe which features they support to prevent potentially faulty deployments */ supports?: { /** @@ -132,7 +132,8 @@ export interface IAdapterConfig { */ pathPrefix?: boolean /** - * Provide array of supported traling slash options ("always", "never", "ignore"). + * Provide array of supported traling slash options + * @example [`always`] */ trailingSlash?: Array } From 46cbbff379d7fd7b9583600275dd612536ca48af Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 5 Jul 2023 11:53:13 +0200 Subject: [PATCH 139/161] apply trailingSlash to tests and other stuff, add utils --- e2e-tests/adapters/cypress/configs/netlify.ts | 2 + .../adapters/cypress/e2e/redirects.cy.ts | 26 +++++---- .../adapters/cypress/e2e/trailing-slash.cy.ts | 53 +++++++++++++------ e2e-tests/adapters/cypress/support/e2e.ts | 17 ++++++ .../cypress/utils/assert-page-visits.ts | 16 ++++++ e2e-tests/adapters/gatsby-config.ts | 3 +- e2e-tests/adapters/gatsby-node.ts | 13 +++-- e2e-tests/adapters/utils.ts | 20 +++++++ 8 files changed, 117 insertions(+), 33 deletions(-) create mode 100644 e2e-tests/adapters/cypress/utils/assert-page-visits.ts create mode 100644 e2e-tests/adapters/utils.ts diff --git a/e2e-tests/adapters/cypress/configs/netlify.ts b/e2e-tests/adapters/cypress/configs/netlify.ts index be4ef2877f4e6..361e9a861ed20 100644 --- a/e2e-tests/adapters/cypress/configs/netlify.ts +++ b/e2e-tests/adapters/cypress/configs/netlify.ts @@ -3,6 +3,8 @@ import { defineConfig } from "cypress" export default defineConfig({ e2e: { baseUrl: `http://localhost:8888`, + // Netlify doesn't handle trailing slash behaviors really, so no use in testing it + excludeSpecPattern: [`cypress/e2e/trailing-slash.cy.ts`,], projectId: `4enh4m`, videoUploadOnPasses: false, experimentalRunAllSpecs: true, diff --git a/e2e-tests/adapters/cypress/e2e/redirects.cy.ts b/e2e-tests/adapters/cypress/e2e/redirects.cy.ts index e95346aff37e5..2ef68bd05c522 100644 --- a/e2e-tests/adapters/cypress/e2e/redirects.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/redirects.cy.ts @@ -1,50 +1,56 @@ +import { applyTrailingSlashOption } from "../../utils" + Cypress.on("uncaught:exception", (err) => { if (err.message.includes("Minified React error")) { return false } }) +const TRAILING_SLASH = Cypress.env(`TRAILING_SLASH`) || `never` + +// Those tests won't work using `gatsby serve` because it doesn't support redirects + describe("Redirects", () => { it("should redirect from non-existing page to existing", () => { - cy.visit(`/redirect`, { + cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH), { failOnStatusCode: false, }).waitForRouteChange() + .assertRoute(applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)) cy.get(`h1`).should(`have.text`, `Hit`) - cy.url().should(`equal`, `${window.location.origin}/routes/redirect/hit`) }) it("should respect that pages take precedence over redirects", () => { - cy.visit(`/routes/redirect/existing`, { + cy.visit(applyTrailingSlashOption(`/routes/redirect/existing`, TRAILING_SLASH), { failOnStatusCode: false, }).waitForRouteChange() + .assertRoute(applyTrailingSlashOption(`/routes/redirect/existing`, TRAILING_SLASH)) cy.get(`h1`).should(`have.text`, `Existing`) - cy.url().should(`equal`, `${window.location.origin}/routes/redirect/existing`) }) it("should support hash parameter on direct visit", () => { - cy.visit(`/redirect#anchor`, { + cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) + `#anchor`, { failOnStatusCode: false, }).waitForRouteChange() - cy.location(`pathname`).should(`equal`, `/routes/redirect/hit`) + cy.location(`pathname`).should(`equal`, applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)) cy.location(`hash`).should(`equal`, `#anchor`) cy.location(`search`).should(`equal`, ``) }) it("should support search parameter on direct visit", () => { - cy.visit(`/redirect/?query_param=hello`, { + cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) + `?query_param=hello`, { failOnStatusCode: false, }).waitForRouteChange() - cy.location(`pathname`).should(`equal`, `/routes/redirect/hit`) + cy.location(`pathname`).should(`equal`, applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)) cy.location(`hash`).should(`equal`, ``) cy.location(`search`).should(`equal`, `?query_param=hello`) }) it("should support search & hash parameter on direct visit", () => { - cy.visit(`/redirect/?query_param=hello#anchor`, { + cy.visit(applyTrailingSlashOption(`/redirect`, TRAILING_SLASH) + `?query_param=hello#anchor`, { failOnStatusCode: false, }).waitForRouteChange() - cy.location(`pathname`).should(`equal`, `/routes/redirect/hit`) + cy.location(`pathname`).should(`equal`, applyTrailingSlashOption(`/routes/redirect/hit`, TRAILING_SLASH)) cy.location(`hash`).should(`equal`, `#anchor`) cy.location(`search`).should(`equal`, `?query_param=hello`) }) diff --git a/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts b/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts index 491a29a2894fd..c595f655907ce 100644 --- a/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts +++ b/e2e-tests/adapters/cypress/e2e/trailing-slash.cy.ts @@ -1,30 +1,49 @@ +import { assertPageVisits } from "../utils/assert-page-visits" +import { applyTrailingSlashOption } from "../../utils" + Cypress.on("uncaught:exception", (err) => { if (err.message.includes("Minified React error")) { return false } }) +const TRAILING_SLASH = Cypress.env(`TRAILING_SLASH`) || `never` + describe("trailingSlash", () => { - it("should work when using Gatsby Link (without slash)", () => { - cy.visit('/').waitForRouteChange() + describe(TRAILING_SLASH, () => { + it("should work when using Gatsby Link (without slash)", () => { + cy.visit('/').waitForRouteChange() - cy.get(`[data-testid="static-without-slash"]`).click().waitForRouteChange() - cy.url().should(`equal`, `${window.location.origin}/routes/static`) - }) - it("should work when using Gatsby Link (with slash)", () => { - cy.visit('/').waitForRouteChange() + cy.get(`[data-testid="static-without-slash"]`).click().waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + }) + it("should work when using Gatsby Link (with slash)", () => { + cy.visit('/').waitForRouteChange() - cy.get(`[data-testid="static-with-slash"]`).click().waitForRouteChange() - cy.url().should(`equal`, `${window.location.origin}/routes/static`) - }) - it("should work on direct visit (without slash)", () => { - cy.visit(`/routes/static`).waitForRouteChange() + cy.get(`[data-testid="static-with-slash"]`).click().waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + }) + it("should work on direct visit (with other setting)", () => { + const destination = applyTrailingSlashOption("/routes/static", TRAILING_SLASH) + const inverse = TRAILING_SLASH === `always` ? "/routes/static" : "/routes/static/" - cy.url().should(`equal`, `${window.location.origin}/routes/static`) - }) - it("should work on direct visit (with slash)", () => { - cy.visit(`/routes/static/`).waitForRouteChange() + assertPageVisits([ + { + path: destination, + status: 200, + }, + { path: inverse, status: 301, destinationPath: destination } + ]) + + cy.visit(inverse).waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + }) + it("should work on direct visit (with current setting)", () => { + assertPageVisits([ + { + path: applyTrailingSlashOption("/routes/static", TRAILING_SLASH), + status: 200, + }, + ]) - cy.url().should(`equal`, `${window.location.origin}/routes/static`) + cy.visit(applyTrailingSlashOption("/routes/static", TRAILING_SLASH)).waitForRouteChange().assertRoute(applyTrailingSlashOption(`/routes/static`, TRAILING_SLASH)) + }) }) }) diff --git a/e2e-tests/adapters/cypress/support/e2e.ts b/e2e-tests/adapters/cypress/support/e2e.ts index b1cdce8d51fd9..198a0c3b8202b 100644 --- a/e2e-tests/adapters/cypress/support/e2e.ts +++ b/e2e-tests/adapters/cypress/support/e2e.ts @@ -1,3 +1,20 @@ // https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Support-file import "gatsby-cypress" + +declare global { + namespace Cypress { + interface Chainable { + /** + * Assert the current URL + * @param route + * @example cy.assertRoute('/page-2') + */ + assertRoute(value: string): Chainable> + } + } +} + +Cypress.Commands.add(`assertRoute`, route => { + cy.url().should(`equal`, `${window.location.origin}${route}`) +}) diff --git a/e2e-tests/adapters/cypress/utils/assert-page-visits.ts b/e2e-tests/adapters/cypress/utils/assert-page-visits.ts new file mode 100644 index 0000000000000..0fe806f5666f7 --- /dev/null +++ b/e2e-tests/adapters/cypress/utils/assert-page-visits.ts @@ -0,0 +1,16 @@ +export function assertPageVisits(pages: Array<{ path: string; status: number; destinationPath?: string }>) { + for (let i = 0; i < pages.length; i++) { + const page = pages[i] + + cy.intercept(new RegExp(`^${page.path}$`), req => { + req.continue(res => { + expect(res.statusCode).to.equal(page.status) + if (page.destinationPath) { + expect(res.headers.location).to.equal(page.destinationPath) + } else { + expect(res.headers.location).to.be.undefined + } + }) + }) + } +} diff --git a/e2e-tests/adapters/gatsby-config.ts b/e2e-tests/adapters/gatsby-config.ts index b881c30a77afb..8ccc03130cc54 100644 --- a/e2e-tests/adapters/gatsby-config.ts +++ b/e2e-tests/adapters/gatsby-config.ts @@ -3,6 +3,7 @@ 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"] let configOverrides: GatsbyConfig = {} @@ -18,7 +19,7 @@ const config: GatsbyConfig = { title, siteDescription, }, - trailingSlash: "never", + trailingSlash, plugins: [], ...configOverrides, } diff --git a/e2e-tests/adapters/gatsby-node.ts b/e2e-tests/adapters/gatsby-node.ts index 981b1ae3c4f20..0df93fcc92857 100644 --- a/e2e-tests/adapters/gatsby-node.ts +++ b/e2e-tests/adapters/gatsby-node.ts @@ -1,14 +1,17 @@ import * as path from "path" -import type { GatsbyNode } from "gatsby" +import type { GatsbyNode, GatsbyConfig } from "gatsby" +import { applyTrailingSlashOption } from "./utils" + +const TRAILING_SLASH = (process.env.TRAILING_SLASH || `never`) as GatsbyConfig["trailingSlash"] export const createPages: GatsbyNode["createPages"] = ({ actions: { createRedirect, createSlice } }) => { createRedirect({ - fromPath: "/redirect", - toPath: "/routes/redirect/hit" + fromPath: applyTrailingSlashOption("/redirect", TRAILING_SLASH), + toPath: applyTrailingSlashOption("/routes/redirect/hit", TRAILING_SLASH), }) createRedirect({ - fromPath: "/routes/redirect/existing", - toPath: "/routes/redirect/hit" + fromPath: applyTrailingSlashOption("/routes/redirect/existing", TRAILING_SLASH), + toPath: applyTrailingSlashOption("/routes/redirect/hit", TRAILING_SLASH), }) createSlice({ diff --git a/e2e-tests/adapters/utils.ts b/e2e-tests/adapters/utils.ts new file mode 100644 index 0000000000000..59ac1df851aa8 --- /dev/null +++ b/e2e-tests/adapters/utils.ts @@ -0,0 +1,20 @@ +import type { GatsbyConfig } from "gatsby" + +export const applyTrailingSlashOption = ( + input: string, + // Normally this is "always" but as part of this test suite we default to "never" + option: GatsbyConfig["trailingSlash"] = `never` +): string => { + if (input === `/`) return input + + const hasTrailingSlash = input.endsWith(`/`) + + if (option === `always`) { + return hasTrailingSlash ? input : `${input}/` + } + if (option === `never`) { + return hasTrailingSlash ? input.slice(0, -1) : input + } + + return input +} \ No newline at end of file From d68acb2bdbe88ddf18995dea8b6a1ece9e94c206 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 5 Jul 2023 12:03:19 +0200 Subject: [PATCH 140/161] readme and package.json update --- e2e-tests/adapters/README.md | 19 +++++++++++++++++++ e2e-tests/adapters/package.json | 13 +++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/e2e-tests/adapters/README.md b/e2e-tests/adapters/README.md index dbde8fc732799..eab3c8f5265a3 100644 --- a/e2e-tests/adapters/README.md +++ b/e2e-tests/adapters/README.md @@ -6,3 +6,22 @@ If possible, run the tests locally with a CLI. Otherwise deploy the site to the Adapters being tested: - [gatsby-adapter-netlify](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-adapter-netlify) + +## Usage + +- To run all tests, use `npm run test` +- To run individual tests, use `npm run test:%NAME` where `test:%NAME` is the script, e.g. `npm run test:netlify` + +If you want to open Cypress locally as a UI, you can run the `:debug` scripts. For example, `npm run test:netlify:debug` to test the Netlify Adapter with Cypress open. + +### Adding a new adapter + +- Add a new Cypress config inside `cypress/configs` +- Add a new `test:` script that should run `start-server-and-test`. You can check what e.g. `test:netlify` is doing. +- Run the Cypress test suites that should work. If you want to exclude a spec, you can use Cypress' [excludeSpecPattern](https://docs.cypress.io/guides/references/configuration#excludeSpecPattern) + +## External adapters + +As mentioned in [Creating an Adapter](https://gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/#testing) you can use this test suite for your own adapter. + +Copy the whole `adapters` folder, and follow [adding a new adapter](#adding-a-new-adapter). diff --git a/e2e-tests/adapters/package.json b/e2e-tests/adapters/package.json index f833c0c7fb5c8..cb46f8a6a0c4e 100644 --- a/e2e-tests/adapters/package.json +++ b/e2e-tests/adapters/package.json @@ -13,14 +13,15 @@ "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 node ../../scripts/cypress-run-with-conditional-record-flag.js --browser chrome --e2e --config-file \"cypress/configs/$ADAPTER.ts\"", - "ssat:template": "cross-env-shell ADAPTER=$ADAPTER start-server-and-test", + "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:debug": "npm-run-all -s build:debug ssat:debug", - "test:netlify": "start-server-and-test 'BROWSER=none ntl serve --port 8888' http://localhost:8888 'cross-env ADAPTER=netlify npm run test:template'", + "test:netlify": "start-server-and-test 'cross-env TRAILING_SLASH=always BROWSER=none ntl serve --port 8888' http://localhost:8888 'cross-env ADAPTER=netlify TRAILING_SLASH=always npm run test:template'", + "test:netlify:debug": "start-server-and-test 'cross-env TRAILING_SLASH=always BROWSER=none ntl serve --port 8888' http://localhost:8888 'cross-env ADAPTER=netlify TRAILING_SLASH=always npm run test:template:debug'", "test": "npm-run-all -c -s test:netlify" }, "dependencies": { - "gatsby": "5.12.0-next.0", + "gatsby": "next", "gatsby-adapter-netlify": "latest", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -29,9 +30,9 @@ "cross-env": "^7.0.3", "cypress": "^12.14.0", "gatsby-cypress": "^3.11.0", - "netlify-cli": "^15.5.1", + "netlify-cli": "^15.8.0", "npm-run-all": "^4.1.5", "start-server-and-test": "^2.0.0", - "typescript": "^5.1.3" + "typescript": "^5.1.6" } } From d1db998c936be23fa458a4922bcb5e0effc53960 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Wed, 5 Jul 2023 12:54:41 +0200 Subject: [PATCH 141/161] fix ts versions --- packages/gatsby-adapter-netlify/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/package.json b/packages/gatsby-adapter-netlify/package.json index a0c83d976f9bf..4c371e95b36d8 100644 --- a/packages/gatsby-adapter-netlify/package.json +++ b/packages/gatsby-adapter-netlify/package.json @@ -43,7 +43,7 @@ "babel-preset-gatsby-package": "^3.12.0-next.0", "cross-env": "^7.0.3", "rimraf": "^5.0.1", - "typescript": "^5.0.4" + "typescript": "^5.1.6" }, "peerDependencies": { "gatsby": "^5.10.0-alpha" From 2cef29672f40cc428aa8f24490f6c59205fb7dc3 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 Jul 2023 13:06:43 +0200 Subject: [PATCH 142/161] enable typecheck for adapters.js --- packages/gatsby/adapters.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/gatsby/adapters.js b/packages/gatsby/adapters.js index 565b8fb2049cd..ac0dc9bc1c641 100644 --- a/packages/gatsby/adapters.js +++ b/packages/gatsby/adapters.js @@ -1,3 +1,5 @@ +// @ts-check + /** * List of adapters that should be automatically installed if not present already. * The first item which test function returns `true` will be used. @@ -5,7 +7,7 @@ * If you're the author of an adapter and want to add it to this list, please open a PR! * If you want to create an adapter, please see: http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/creating-an-adapter/ * - * @type {import("./src/utils/adapter/types").IAdapterManifestEntry} + * @type {Array} * @see http://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/zero-configuration-deployments/ */ const adaptersManifest = [ @@ -16,9 +18,9 @@ const adaptersManifest = [ versions: [ { gatsbyVersion: `^5.0.0`, - moduleVersion: `^1.0.0-alpha` + moduleVersion: `^1.0.0-alpha`, } - ] + ], } ] From 24bae2ac0885070d58a365961af30cec2c343178 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 Jul 2023 13:09:38 +0200 Subject: [PATCH 143/161] don't try to use install adapter if no version matches, install version specified in adapters.js --- packages/gatsby/src/utils/adapter/init.ts | 38 +++++++++++++---------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index c96ac84a5cd0c..8b1b633fb9756 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -40,6 +40,17 @@ export async function getAdapterInit(): Promise { return undefined } + const versionForCurrentGatsbyVersion = adapterToUse.versions.find(entry => + satisfies(gatsbyVersion, entry.gatsbyVersion, { includePrerelease: true }) + ) + + if (!versionForCurrentGatsbyVersion) { + reporter.verbose( + `The ${adapterToUse.name} adapter is not compatible with your current Gatsby version ${gatsbyVersion}.` + ) + return undefined + } + // 2. Check if the user has manually installed the adapter and try to resolve it from there try { const siteRequire = createRequireFromPath(`${process.cwd()}/:internal:`) @@ -66,22 +77,13 @@ export async function getAdapterInit(): Promise { } // Cross-check the adapter version with the version manifest and see if the adapter version is correct for the current Gatsby version - const versionForCurrentGatsbyVersion = adapterToUse.versions.find(entry => - satisfies(gatsbyVersion, entry.gatsbyVersion, { includePrerelease: true }) - ) - const isAdapterCompatible = - versionForCurrentGatsbyVersion && - satisfies(moduleVersion, versionForCurrentGatsbyVersion.moduleVersion, { + const isAdapterCompatible = satisfies( + moduleVersion, + versionForCurrentGatsbyVersion.moduleVersion, + { includePrerelease: true, - }) - - if (!versionForCurrentGatsbyVersion) { - reporter.warn( - `The ${adapterToUse.name} adapter is not compatible with your current Gatsby version ${gatsbyVersion}.` - ) - - return undefined - } + } + ) if (!isAdapterCompatible) { reporter.warn( @@ -151,7 +153,11 @@ export async function getAdapterInit(): Promise { await execa( `npm`, - [`install`, ...npmAdditionalCliArgs, adapterToUse.module], + [ + `install`, + ...npmAdditionalCliArgs, + `${adapterToUse.module}@${versionForCurrentGatsbyVersion.moduleVersion}`, + ], options ) From 6d76ebc4a4d30560ac2fa86e094aa5fb035b2834 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 Jul 2023 17:12:13 +0200 Subject: [PATCH 144/161] allow adapter to disable prior deployment plugins --- .../src/bootstrap/load-plugins/index.ts | 23 ++++++++++++++++++- packages/gatsby/src/commands/types.ts | 4 ++++ packages/gatsby/src/redux/reducers/program.ts | 23 +++++++++++++++++++ packages/gatsby/src/redux/types.ts | 9 ++++++++ packages/gatsby/src/utils/adapter/manager.ts | 11 +++++++++ packages/gatsby/src/utils/adapter/types.ts | 8 ++++++- 6 files changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/gatsby/src/bootstrap/load-plugins/index.ts b/packages/gatsby/src/bootstrap/load-plugins/index.ts index 36696adf4f747..bdeb46afdd72c 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/index.ts +++ b/packages/gatsby/src/bootstrap/load-plugins/index.ts @@ -1,3 +1,4 @@ +import reporter from "gatsby-cli/lib/reporter" import { store } from "../../redux" import { IGatsbyState } from "../../redux/types" import * as nodeAPIs from "../../utils/api-node-docs" @@ -38,11 +39,31 @@ export async function loadPlugins( // Create a flattened array of the plugins const pluginArray = flattenPlugins(pluginInfos) + const { disablePlugins } = store.getState().program + const pluginArrayWithoutDisabledPlugins = pluginArray.filter(plugin => { + const disabledInfo = disablePlugins?.find( + entry => entry.name === plugin.name + ) + + if (disabledInfo) { + if (!process.env.GATSBY_WORKER_ID) { + // show this warning only once in main process + reporter.warn( + `Disabling plugin "${plugin.name}":\n${disabledInfo.reasons + .map(line => ` - ${line}`) + .join(`\n`)}` + ) + } + return false + } + return true + }) + // Work out which plugins use which APIs, including those which are not // valid Gatsby APIs, aka 'badExports' let { flattenedPlugins, badExports } = await collatePluginAPIs({ currentAPIs, - flattenedPlugins: pluginArray, + flattenedPlugins: pluginArrayWithoutDisabledPlugins, rootDir, }) diff --git a/packages/gatsby/src/commands/types.ts b/packages/gatsby/src/commands/types.ts index 5fd0afe9737f8..9f481af90fd90 100644 --- a/packages/gatsby/src/commands/types.ts +++ b/packages/gatsby/src/commands/types.ts @@ -35,6 +35,10 @@ export interface IProgram { verbose?: boolean prefixPaths?: boolean setStore?: (store: Store) => void + disablePlugins?: Array<{ + name: string + reasons: Array + }> } /** diff --git a/packages/gatsby/src/redux/reducers/program.ts b/packages/gatsby/src/redux/reducers/program.ts index 07bcbbd70ffa6..e5487a054fa83 100644 --- a/packages/gatsby/src/redux/reducers/program.ts +++ b/packages/gatsby/src/redux/reducers/program.ts @@ -15,6 +15,7 @@ const initialState: IStateProgram = { extensions: [], browserslist: [], report: reporter, + disablePlugins: [], } export const programReducer = ( @@ -24,6 +25,7 @@ export const programReducer = ( switch (action.type) { case `SET_PROGRAM`: return { + ...state, ...action.payload, } @@ -39,6 +41,27 @@ export const programReducer = ( status: `BOOTSTRAP_FINISHED`, } + case `DISABLE_PLUGINS_BY_NAME`: { + if (!state.disablePlugins) { + state.disablePlugins = [] + } + for (const pluginToDisable of action.payload.pluginsToDisable) { + let disabledPlugin = state.disablePlugins.find( + entry => entry.name === pluginToDisable + ) + if (!disabledPlugin) { + disabledPlugin = { + name: pluginToDisable, + reasons: [], + } + state.disablePlugins.push(disabledPlugin) + } + disabledPlugin.reasons.push(action.payload.reason) + } + + return state + } + default: return state } diff --git a/packages/gatsby/src/redux/types.ts b/packages/gatsby/src/redux/types.ts index 866bfc629c1ee..9f03fbbba753e 100644 --- a/packages/gatsby/src/redux/types.ts +++ b/packages/gatsby/src/redux/types.ts @@ -538,6 +538,7 @@ export type ActionsUnion = | IProcessGatsbyImageSourceUrlAction | IClearGatsbyImageSourceUrlAction | ISetAdapterAction + | IDisablePluginsByNameAction export interface IInitAction { type: `INIT` @@ -1209,6 +1210,14 @@ export interface ISetAdapterAction { } } +export interface IDisablePluginsByNameAction { + type: `DISABLE_PLUGINS_BY_NAME` + payload: { + pluginsToDisable: Array + reason: string + } +} + export interface ITelemetry { gatsbyImageSourceUrls: Set } diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 3a154f2731222..04610c58f8401 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -106,6 +106,16 @@ async function setAdapter({ }, }) } + + if (configFromAdapter.pluginsToDisable.length > 0) { + store.dispatch({ + type: `DISABLE_PLUGINS_BY_NAME`, + payload: { + pluginsToDisable: configFromAdapter.pluginsToDisable, + reason: `Not compatible with the "${instance.name}" adapter. Please remove it from your gatsby-config.`, + }, + }) + } } } @@ -236,6 +246,7 @@ export async function initAdapterManager(): Promise { configFromAdapter?.excludeDatastoreFromEngineFunction ?? false, deployURL: configFromAdapter?.deployURL, supports: configFromAdapter?.supports, + pluginsToDisable: configFromAdapter?.pluginsToDisable ?? [], } }, } diff --git a/packages/gatsby/src/utils/adapter/types.ts b/packages/gatsby/src/utils/adapter/types.ts index 1ddab6357701a..b9915427228eb 100644 --- a/packages/gatsby/src/utils/adapter/types.ts +++ b/packages/gatsby/src/utils/adapter/types.ts @@ -137,6 +137,12 @@ export interface IAdapterConfig { */ trailingSlash?: Array } + /** + * List of plugins that should be disabled when using this adapter. Purpose of this is to disable + * any potential plugins that serve similar role as adapter that would cause conflicts when both + * plugin and adapter is used at the same time. + */ + pluginsToDisable?: Array } type WithRequired = T & { [P in K]-?: T[P] } @@ -147,7 +153,7 @@ type WithRequired = T & { [P in K]-?: T[P] } */ export type IAdapterFinalConfig = WithRequired< IAdapterConfig, - "excludeDatastoreFromEngineFunction" + "excludeDatastoreFromEngineFunction" | "pluginsToDisable" > export interface IAdapter { From f837c5479e7c24b5fd7c7e2d463d73b272e20fbb Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 10 Jul 2023 17:12:36 +0200 Subject: [PATCH 145/161] disable gatsby-plugin-netlify when using gatsby-adapter-netlify --- packages/gatsby-adapter-netlify/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 858de6ca742ba..2e93ceafef22d 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -95,6 +95,10 @@ const createNetlifyAdapter: AdapterInit = options => { pathPrefix: false, trailingSlash: [`always`], }, + pluginsToDisable: [ + `gatsby-plugin-netlify-cache`, + `gatsby-plugin-netlify`, + ], } }, } From 8bb775e926c90f94c17e95868f446815f577d3de Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 11 Jul 2023 08:50:36 +0200 Subject: [PATCH 146/161] fix ts --- packages/gatsby/src/redux/reducers/adapter.ts | 1 + packages/gatsby/src/utils/adapter/no-op-manager.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/gatsby/src/redux/reducers/adapter.ts b/packages/gatsby/src/redux/reducers/adapter.ts index 6a0bb94f7cb88..29215e318adb6 100644 --- a/packages/gatsby/src/redux/reducers/adapter.ts +++ b/packages/gatsby/src/redux/reducers/adapter.ts @@ -7,6 +7,7 @@ export const adapterReducer = ( manager: noOpAdapterManager(), config: { excludeDatastoreFromEngineFunction: false, + pluginsToDisable: [], }, }, action: ActionsUnion diff --git a/packages/gatsby/src/utils/adapter/no-op-manager.ts b/packages/gatsby/src/utils/adapter/no-op-manager.ts index 0f1a63f312d6d..edc5d74c207bf 100644 --- a/packages/gatsby/src/utils/adapter/no-op-manager.ts +++ b/packages/gatsby/src/utils/adapter/no-op-manager.ts @@ -8,6 +8,7 @@ export function noOpAdapterManager(): IAdapterManager { config: async (): Promise => { return { excludeDatastoreFromEngineFunction: false, + pluginsToDisable: [], } }, } From 7d848f63457495878b844c37eb44e362e8defde2 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 11 Jul 2023 17:26:48 +0200 Subject: [PATCH 147/161] handle non-alpha-numerc paths --- .../src/route-handler.ts | 15 +++++++++------ .../internal-plugins/functions/gatsby-node.ts | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index cec7da1772fcc..b08a5dae026ec 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -70,7 +70,7 @@ export async function handleRoutesManifest( const invocationURL = `/.netlify/${ route.cache ? `builders` : `functions` }/${functionName}` - _redirects += `${fromPath} ${invocationURL} 200\n` + _redirects += `${encodeURI(fromPath)} ${invocationURL} 200\n` } else if (route.type === `redirect`) { const { status: routeStatus, @@ -123,16 +123,19 @@ export async function handleRoutesManifest( } else if (route.type === `static`) { // regular static asset without dynamic paths will just work, so skipping those if (route.path.includes(`:`) || route.path.includes(`*`)) { - _redirects += `${fromPath} ${route.filePath.replace( + _redirects += `${encodeURI(fromPath)} ${route.filePath.replace( /^public/, `` )} 200\n` } - _headers += `${fromPath}\n${route.headers.reduce((acc, curr) => { - acc += ` ${curr.key}: ${curr.value}\n` - return acc - }, ``)}` + _headers += `${encodeURI(fromPath)}\n${route.headers.reduce( + (acc, curr) => { + acc += ` ${curr.key}: ${curr.value}\n` + return acc + }, + `` + )}` } } diff --git a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts index 4fe353dcae5ce..75bb45d407beb 100644 --- a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts +++ b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts @@ -154,6 +154,7 @@ const createWebpackConfig = async ({ store.getState().flattenedPlugins ) + const seenFunctionIds = new Set() // Glob and return object with relative/absolute paths + which plugin // they belong to. const allFunctions = await Promise.all( @@ -175,6 +176,22 @@ const createWebpackConfig = async ({ ) const finalName = urlResolve(dir, name === `index` ? `` : name) + // functionId should have only alphanumeric characters and dashes + const functionIdBase = _.kebabCase(compiledFunctionName).replace( + /[^a-zA-Z0-9-]/g, + `-` + ) + + let functionId = functionIdBase + + if (seenFunctionIds.has(functionId)) { + let counter = 2 + do { + functionId = `${functionIdBase}-${counter}` + counter++ + } while (seenFunctionIds.has(functionId)) + } + knownFunctions.push({ functionRoute: finalName, pluginName: glob.pluginName, @@ -183,7 +200,7 @@ const createWebpackConfig = async ({ relativeCompiledFilePath: compiledFunctionName, absoluteCompiledFilePath: compiledPath, matchPath: getMatchPath(finalName), - functionId: _.kebabCase(compiledFunctionName), + functionId, }) }) From ec8839f3614e60f3a4a44ac38aebbd71bdf84972 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 11 Jul 2023 17:27:13 +0200 Subject: [PATCH 148/161] verbose log adapter package that is being installed --- packages/gatsby/src/utils/adapter/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/init.ts b/packages/gatsby/src/utils/adapter/init.ts index 8b1b633fb9756..236aa278af0a5 100644 --- a/packages/gatsby/src/utils/adapter/init.ts +++ b/packages/gatsby/src/utils/adapter/init.ts @@ -127,7 +127,7 @@ export async function getAdapterInit(): Promise { } const installTimer = reporter.activityTimer( - `Installing ${adapterToUse.name} adapter` + `Installing ${adapterToUse.name} adapter (${adapterToUse.module}@${versionForCurrentGatsbyVersion.moduleVersion})` ) // 4. If both a manually installed version and a cached version are not found, install the adapter into .cache/adapters try { From f9857be7a3fcddff05a17aa7a1381644bc5213fe Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 11 Jul 2023 18:16:51 +0200 Subject: [PATCH 149/161] only try to restore cache if payload provided --- packages/gatsby/src/redux/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/redux/index.ts b/packages/gatsby/src/redux/index.ts index 84ada92859fb2..5b4b5e81a9131 100644 --- a/packages/gatsby/src/redux/index.ts +++ b/packages/gatsby/src/redux/index.ts @@ -38,7 +38,7 @@ const persistedReduxKeys = [ const reducers = persistedReduxKeys.reduce((acc, key) => { const rawReducer = rawReducers[key] acc[key] = function (state, action): any { - if (action.type === `RESTORE_CACHE`) { + if (action.type === `RESTORE_CACHE` && action.payload[key]) { return action.payload[key] } else { return rawReducer(state, action) From 36891f6fc9beee6e4ae79b1b03cba96c3ea6da84 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 12 Jul 2023 09:50:32 +0200 Subject: [PATCH 150/161] memoize cache utils --- packages/gatsby-adapter-netlify/src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 2e93ceafef22d..4ec9c1b9e7d44 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -11,12 +11,18 @@ interface INetlifyAdapterOptions { excludeDatastoreFromEngineFunction?: boolean } +let _cacheUtils: INetlifyCacheUtils | undefined async function getCacheUtils(): Promise { + if (_cacheUtils) { + return _cacheUtils + } if (process.env.NETLIFY) { const CACHE_DIR = `/opt/build/cache` - return (await import(`@netlify/cache-utils`)).bindOpts({ + _cacheUtils = (await import(`@netlify/cache-utils`)).bindOpts({ cacheDir: CACHE_DIR, }) + + return _cacheUtils } return undefined } From 643629530020a933ec3fe07d09222e8e8c84877f Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 12 Jul 2023 09:50:49 +0200 Subject: [PATCH 151/161] log adapter version --- packages/gatsby-adapter-netlify/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 4ec9c1b9e7d44..62473e71cb143 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -1,6 +1,7 @@ import type { AdapterInit, IAdapterConfig } from "gatsby" import { prepareFunctionVariants } from "./lambda-handler" import { handleRoutesManifest } from "./route-handler" +import packageJson from "gatsby-adapter-netlify/package.json" interface INetlifyCacheUtils { restore: (paths: Array) => Promise @@ -66,6 +67,9 @@ const createNetlifyAdapter: AdapterInit = options => { } }, config: ({ reporter }): IAdapterConfig => { + reporter.info( + `[gatsby-adapter-netlify] version: ${packageJson?.version ?? `unknown`}` + ) // excludeDatastoreFromEngineFunction can be enabled either via options or via env var (to preserve handling of env var that existed in Netlify build plugin). let excludeDatastoreFromEngineFunction = options?.excludeDatastoreFromEngineFunction From d07fe0dd52cc275f663cb12a601b9fa8efbad90d Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 12 Jul 2023 12:33:17 +0200 Subject: [PATCH 152/161] handle body parsing in produced api functions --- packages/gatsby/package.json | 1 + .../functions/api-function-webpack-loader.ts | 61 +++++++++++++++++++ .../internal-plugins/functions/gatsby-node.ts | 10 ++- .../functions/match-path-webpack-loader.ts | 36 ----------- 4 files changed, 66 insertions(+), 42 deletions(-) create mode 100644 packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts delete mode 100644 packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 846d73928dbfa..71f73b6879140 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -52,6 +52,7 @@ "babel-preset-gatsby": "^3.12.0-next.1", "better-opn": "^2.1.1", "bluebird": "^3.7.2", + "body-parser": "1.20.1", "browserslist": "^4.21.7", "cache-manager": "^2.11.1", "chalk": "^4.1.2", diff --git a/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts b/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts new file mode 100644 index 0000000000000..284786ace4874 --- /dev/null +++ b/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts @@ -0,0 +1,61 @@ +import type { LoaderDefinition } from "webpack" + +const APIFunctionLoader: LoaderDefinition = async function () { + const params = new URLSearchParams(this.resourceQuery) + const matchPath = params.get(`matchPath`) + + const modulePath = this.resourcePath + + return /* javascript */ ` + const preferDefault = m => (m && m.default) || m + + const functionModule = require('${modulePath}'); + const functionToExecute = preferDefault(functionModule); + const matchPath = '${matchPath}'; + const { match: reachMatch } = require('@gatsbyjs/reach-router'); + const { urlencoded, text, json, raw } = require('body-parser') + const multer = require('multer') + + module.exports = function(req, res) { + if (matchPath) { + let functionPath = req.originalUrl + + functionPath = functionPath.replace(new RegExp('^/*' + PREFIX_TO_STRIP), '') + functionPath = functionPath.replace(new RegExp('^/*api/?'), '') + + const matchResult = reachMatch(matchPath, functionPath) + if (matchResult) { + req.params = matchResult.params + if (req.params['*']) { + // TODO(v6): Remove this backwards compatability for v3 + req.params['0'] = req.params['*'] + } + } + } + + // handle body parsing + // TODO: support user config + const middlewares = [ + multer().any(), + raw({ limit: '100kb' }), + text({ limit: '100kb' }), + urlencoded({ limit: '100kb', extended: true }), + json({ limit: '100kb' }) + ] + + let i = 0 + function runMiddlewareOrFunction() { + if (i >= middlewares.length) { + functionToExecute(req, res); + } else { + middlewares[i++](req, res, runMiddlewareOrFunction) + } + } + + + runMiddlewareOrFunction() + } + ` +} + +export default APIFunctionLoader diff --git a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts index 75bb45d407beb..5f4b5b8141d0e 100644 --- a/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts +++ b/packages/gatsby/src/internal-plugins/functions/gatsby-node.ts @@ -293,11 +293,9 @@ const createWebpackConfig = async ({ ) let entryToTheFunction = functionObj.originalAbsoluteFilePath - // if function has dynamic path, we inject it with webpack loader via query param - // see match-path-webpack-loader.ts for more info - if (functionObj.matchPath) { - entryToTheFunction += `?matchPath=` + functionObj.matchPath - } + // we wrap user defined function with our preamble that handles matchPath as well as body parsing + // see api-function-webpack-loader.ts for more info + entryToTheFunction += `?matchPath=` + (functionObj.matchPath ?? ``) entries[compiledNameWithoutExtension] = entryToTheFunction }) @@ -349,7 +347,7 @@ const createWebpackConfig = async ({ test: /\.[tj]sx?$/, resourceQuery: /matchPath/, use: { - loader: require.resolve(`./match-path-webpack-loader`), + loader: require.resolve(`./api-function-webpack-loader`), }, }, { diff --git a/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts b/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts deleted file mode 100644 index 0c9422f6d9b6a..0000000000000 --- a/packages/gatsby/src/internal-plugins/functions/match-path-webpack-loader.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { LoaderDefinition } from "webpack" - -const MatchPathLoader: LoaderDefinition = async function () { - const params = new URLSearchParams(this.resourceQuery) - const matchPath = params.get(`matchPath`) - - const modulePath = this.resourcePath - - return /* javascript */ ` - const preferDefault = m => (m && m.default) || m - - const functionToExecute = preferDefault(require('${modulePath}')); - const matchPath = '${matchPath}'; - const { match: reachMatch } = require('@gatsbyjs/reach-router'); - - module.exports = function(req, res) { - let functionPath = req.originalUrl - - functionPath = functionPath.replace(new RegExp('^/*' + PREFIX_TO_STRIP), '') - functionPath = functionPath.replace(new RegExp('^/*api/?'), '') - - const matchResult = reachMatch(matchPath, functionPath) - if (matchResult) { - req.params = matchResult.params - if (req.params['*']) { - // TODO(v6): Remove this backwards compatability for v3 - req.params['0'] = req.params['*'] - } - } - - return functionToExecute(req, res); - } - ` -} - -export default MatchPathLoader From ce9e7a8994b72f3af4d82cd36f2480d02cdb88ec Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 12 Jul 2023 15:55:10 +0200 Subject: [PATCH 153/161] properly handle cases when body parsing already happened and when there is no function handler export --- .../functions/api-function-webpack-loader.ts | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts b/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts index 284786ace4874..20af3cc5b40ff 100644 --- a/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts +++ b/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts @@ -16,7 +16,7 @@ const APIFunctionLoader: LoaderDefinition = async function () { const { urlencoded, text, json, raw } = require('body-parser') const multer = require('multer') - module.exports = function(req, res) { + function functionWrapper(req, res) { if (matchPath) { let functionPath = req.originalUrl @@ -33,15 +33,18 @@ const APIFunctionLoader: LoaderDefinition = async function () { } } - // handle body parsing + // handle body parsing if request stream was not yet consumed // TODO: support user config - const middlewares = [ - multer().any(), - raw({ limit: '100kb' }), - text({ limit: '100kb' }), - urlencoded({ limit: '100kb', extended: true }), - json({ limit: '100kb' }) - ] + const middlewares = + req.readableEnded + ? [] + : [ + multer().any(), + raw({ limit: '100kb' }), + text({ limit: '100kb' }), + urlencoded({ limit: '100kb', extended: true }), + json({ limit: '100kb' }) + ] let i = 0 function runMiddlewareOrFunction() { @@ -55,6 +58,8 @@ const APIFunctionLoader: LoaderDefinition = async function () { runMiddlewareOrFunction() } + + module.exports = typeof functionToExecute === 'function' ? functionWrapper : functionModule ` } From 902d30f7abc73205f08642a5062e7872d734e261 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 12 Jul 2023 16:47:12 +0200 Subject: [PATCH 154/161] handle body parsing config in generated function --- .../functions/__tests__/config.ts | 64 ++++++++++++------- .../functions/api-function-webpack-loader.ts | 17 +++-- .../src/internal-plugins/functions/config.ts | 40 +++--------- .../internal-plugins/functions/middleware.ts | 34 +++++++++- 4 files changed, 94 insertions(+), 61 deletions(-) diff --git a/packages/gatsby/src/internal-plugins/functions/__tests__/config.ts b/packages/gatsby/src/internal-plugins/functions/__tests__/config.ts index 2cd8fa6bf7471..36823933d2784 100644 --- a/packages/gatsby/src/internal-plugins/functions/__tests__/config.ts +++ b/packages/gatsby/src/internal-plugins/functions/__tests__/config.ts @@ -1,4 +1,5 @@ import { createConfig } from "../config" +import { printConfigWarnings } from "../middleware" import reporter from "gatsby-cli/lib/reporter" import type { IGatsbyFunction } from "../../../redux/types" const reporterWarnSpy = jest.spyOn(reporter, `warn`) @@ -7,6 +8,15 @@ beforeEach(() => { reporterWarnSpy.mockReset() }) +function createConfigAndPrintWarnings( + userConfig: any, + functionObj: IGatsbyFunction +): any { + const { config, warnings } = createConfig(userConfig) + printConfigWarnings(warnings, functionObj) + return config +} + const testFunction: IGatsbyFunction = { functionRoute: `a-directory/function`, matchPath: undefined, @@ -15,11 +25,13 @@ const testFunction: IGatsbyFunction = { originalRelativeFilePath: `a-directory/function.js`, relativeCompiledFilePath: `a-directory/function.js`, absoluteCompiledFilePath: `/Users/misiek/dev/functions-test/.cache/functions/a-directory/function.js`, + functionId: `a-directory/function`, } -describe(`createConfig`, () => { +describe(`createConfigAndPrintWarnings`, () => { it(`defaults`, () => { - expect(createConfig(undefined, testFunction)).toMatchInlineSnapshot(` + expect(createConfigAndPrintWarnings(undefined, testFunction)) + .toMatchInlineSnapshot(` Object { "bodyParser": Object { "json": Object { @@ -43,7 +55,7 @@ describe(`createConfig`, () => { describe(`input not matching schema (fallback to default and warnings)`, () => { it(`{ bodyParser: false }`, () => { - expect(createConfig({ bodyParser: false }, testFunction)) + expect(createConfigAndPrintWarnings({ bodyParser: false }, testFunction)) .toMatchInlineSnapshot(` Object { "bodyParser": Object { @@ -101,7 +113,7 @@ describe(`createConfig`, () => { }) it(`{ bodyParser: "foo" }`, () => { - expect(createConfig({ bodyParser: `foo` }, testFunction)) + expect(createConfigAndPrintWarnings({ bodyParser: `foo` }, testFunction)) .toMatchInlineSnapshot(` Object { "bodyParser": Object { @@ -159,7 +171,7 @@ describe(`createConfig`, () => { }) it(`{ unrelated: true }`, () => { - expect(createConfig({ unrelated: true }, testFunction)) + expect(createConfigAndPrintWarnings({ unrelated: true }, testFunction)) .toMatchInlineSnapshot(` Object { "bodyParser": Object { @@ -216,8 +228,12 @@ describe(`createConfig`, () => { }) it(`{ bodyParser: { unrelated: true } }`, () => { - expect(createConfig({ bodyParser: { unrelated: true } }, testFunction)) - .toMatchInlineSnapshot(` + expect( + createConfigAndPrintWarnings( + { bodyParser: { unrelated: true } }, + testFunction + ) + ).toMatchInlineSnapshot(` Object { "bodyParser": Object { "json": Object { @@ -280,7 +296,7 @@ describe(`createConfig`, () => { const customTextConfig = { limit: `1mb`, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { text: customTextConfig, @@ -300,7 +316,7 @@ describe(`createConfig`, () => { const customTextConfig = { type: `lorem/*`, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { text: customTextConfig, @@ -318,7 +334,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - not an config object`, () => { const customTextConfig = `foo` - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { text: customTextConfig, @@ -355,7 +371,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - config object not matching schema`, () => { const customTextConfig = { wat: true } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { text: customTextConfig, @@ -396,7 +412,7 @@ describe(`createConfig`, () => { const customTextConfig = { limit: `1mb`, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { json: customTextConfig, @@ -417,7 +433,7 @@ describe(`createConfig`, () => { const customTextConfig = { type: `lorem/*`, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { json: customTextConfig, @@ -435,7 +451,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - not an config object`, () => { const customTextConfig = `foo` - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { json: customTextConfig, @@ -473,7 +489,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - config object not matching schema`, () => { const customTextConfig = { wat: true } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { json: customTextConfig, @@ -515,7 +531,7 @@ describe(`createConfig`, () => { const customTextConfig = { limit: `1mb`, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { raw: customTextConfig, @@ -535,7 +551,7 @@ describe(`createConfig`, () => { const customTextConfig = { type: `lorem/*`, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { raw: customTextConfig, @@ -553,7 +569,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - not an config object`, () => { const customTextConfig = `foo` - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { raw: customTextConfig, @@ -591,7 +607,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - config object not matching schema`, () => { const customTextConfig = { wat: true } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { raw: customTextConfig, @@ -634,7 +650,7 @@ describe(`createConfig`, () => { limit: `1mb`, extended: true, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { urlencoded: customTextConfig, @@ -656,7 +672,7 @@ describe(`createConfig`, () => { type: `lorem/*`, extended: true, } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { urlencoded: customTextConfig, @@ -675,7 +691,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - not an config object`, () => { const customTextConfig = `foo` - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { urlencoded: customTextConfig, @@ -716,7 +732,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - config object not matching schema`, () => { const customTextConfig = { wat: true } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { urlencoded: customTextConfig, @@ -757,7 +773,7 @@ describe(`createConfig`, () => { it(`input not matching schema (fallback to default) - "extended" is required"`, () => { const customTextConfig = { limit: `200kb` } - const generatedConfig = createConfig( + const generatedConfig = createConfigAndPrintWarnings( { bodyParser: { urlencoded: customTextConfig, diff --git a/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts b/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts index 20af3cc5b40ff..1e2578d320dd4 100644 --- a/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts +++ b/packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts @@ -15,6 +15,7 @@ const APIFunctionLoader: LoaderDefinition = async function () { const { match: reachMatch } = require('@gatsbyjs/reach-router'); const { urlencoded, text, json, raw } = require('body-parser') const multer = require('multer') + const { createConfig } = require('gatsby/dist/internal-plugins/functions/config') function functionWrapper(req, res) { if (matchPath) { @@ -34,16 +35,16 @@ const APIFunctionLoader: LoaderDefinition = async function () { } // handle body parsing if request stream was not yet consumed - // TODO: support user config + const { config } = createConfig(functionModule?.config) const middlewares = req.readableEnded ? [] : [ multer().any(), - raw({ limit: '100kb' }), - text({ limit: '100kb' }), - urlencoded({ limit: '100kb', extended: true }), - json({ limit: '100kb' }) + raw(config?.bodyParser?.raw ?? { limit: '100kb' }), + text(config?.bodyParser?.text ?? { limit: '100kb' }), + urlencoded(config?.bodyParser?.urlencoded ?? { limit: '100kb', extended: true }), + json(config?.bodyParser?.json ?? { limit: '100kb' }) ] let i = 0 @@ -59,7 +60,11 @@ const APIFunctionLoader: LoaderDefinition = async function () { runMiddlewareOrFunction() } - module.exports = typeof functionToExecute === 'function' ? functionWrapper : functionModule + module.exports = typeof functionToExecute === 'function' + ? { + default: functionWrapper, + config: functionModule?.config + } : functionModule ` } diff --git a/packages/gatsby/src/internal-plugins/functions/config.ts b/packages/gatsby/src/internal-plugins/functions/config.ts index 0f4a1d17608e4..c9b6cbe2ef171 100644 --- a/packages/gatsby/src/internal-plugins/functions/config.ts +++ b/packages/gatsby/src/internal-plugins/functions/config.ts @@ -35,12 +35,14 @@ const defaultConfig = { }, } -let warnings: Array<{ +export interface IAPIFunctionWarning { property: string | null original: any expectedType: string replacedWith: any -}> = [] +} + +let warnings: Array = [] function bodyParserConfigFailover( property: keyof IGatsbyBodyParserConfigProcessed, @@ -141,34 +143,12 @@ const functionConfigSchema: Joi.ObjectSchema = return defaultConfig }) -export function createConfig( - userConfig: unknown, - functionObj: IGatsbyFunction -): IGatsbyFunctionConfigProcessed { +export function createConfig(userConfig: unknown): { + config: IGatsbyFunctionConfigProcessed + warnings: Array +} { warnings = [] const { value } = functionConfigSchema.validate(userConfig) - - if (warnings.length) { - for (const warning of warnings) { - reporter.warn( - `${ - warning.property - ? `\`${warning.property}\` property of exported config` - : `Exported config` - } in \`${ - functionObj.originalRelativeFilePath - }\` is misconfigured.\nExpected object:\n\n${ - warning.expectedType - }\n\nGot:\n\n${JSON.stringify( - warning.original - )}\n\nUsing default:\n\n${JSON.stringify( - warning.replacedWith, - null, - 2 - )}` - ) - } - } - - return value as IGatsbyFunctionConfigProcessed + const config = value as IGatsbyFunctionConfigProcessed + return { config, warnings } } diff --git a/packages/gatsby/src/internal-plugins/functions/middleware.ts b/packages/gatsby/src/internal-plugins/functions/middleware.ts index a17ce677426fe..461d65eff34e4 100644 --- a/packages/gatsby/src/internal-plugins/functions/middleware.ts +++ b/packages/gatsby/src/internal-plugins/functions/middleware.ts @@ -9,6 +9,7 @@ import { createConfig, IGatsbyFunctionConfigProcessed, IGatsbyBodyParserConfigProcessed, + IAPIFunctionWarning, } from "./config" import type { IGatsbyFunction } from "../../redux/types" @@ -45,6 +46,33 @@ interface ICreateMiddlewareConfig { showDebugMessageInResponse?: boolean } +export function printConfigWarnings( + warnings: Array, + functionObj: IGatsbyFunction +): void { + if (warnings.length) { + for (const warning of warnings) { + reporter.warn( + `${ + warning.property + ? `\`${warning.property}\` property of exported config` + : `Exported config` + } in \`${ + functionObj.originalRelativeFilePath + }\` is misconfigured.\nExpected object:\n\n${ + warning.expectedType + }\n\nGot:\n\n${JSON.stringify( + warning.original + )}\n\nUsing default:\n\n${JSON.stringify( + warning.replacedWith, + null, + 2 + )}` + ) + } + } +} + function createSetContextFunctionMiddleware({ getFunctions, prepareFn, @@ -122,11 +150,15 @@ function createSetContextFunctionMiddleware({ } if (fnToExecute) { + const { config, warnings } = createConfig(userConfig) + + printConfigWarnings(warnings, functionObj) + req.context = { functionObj, fnToExecute, params: req.params, - config: createConfig(userConfig, functionObj), + config, showDebugMessageInResponse: showDebugMessageInResponse ?? false, } } From ca225bf2aa81279ce8428cb8f5255dec3dcfacb9 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 12 Jul 2023 17:00:22 +0200 Subject: [PATCH 155/161] drop unused --- packages/gatsby/src/internal-plugins/functions/config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/gatsby/src/internal-plugins/functions/config.ts b/packages/gatsby/src/internal-plugins/functions/config.ts index c9b6cbe2ef171..a9e3610651bc7 100644 --- a/packages/gatsby/src/internal-plugins/functions/config.ts +++ b/packages/gatsby/src/internal-plugins/functions/config.ts @@ -1,6 +1,4 @@ import Joi from "joi" -import reporter from "gatsby-cli/lib/reporter" -import type { IGatsbyFunction } from "../../internal" import type { GatsbyFunctionBodyParserCommonMiddlewareConfig, GatsbyFunctionBodyParserUrlencodedConfig, From 092b92e76f80469ef67ce45d6a2eddeb27af7bbc Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 13 Jul 2023 09:55:23 +0200 Subject: [PATCH 156/161] remove redirects created by previous deployment plugins --- packages/gatsby-adapter-netlify/src/route-handler.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/gatsby-adapter-netlify/src/route-handler.ts b/packages/gatsby-adapter-netlify/src/route-handler.ts index b08a5dae026ec..17c5b8f346aa4 100644 --- a/packages/gatsby-adapter-netlify/src/route-handler.ts +++ b/packages/gatsby-adapter-netlify/src/route-handler.ts @@ -42,6 +42,11 @@ async function injectEntries(fileName: string, content: string): Promise { ] .filter(Boolean) .join(``) + .replace( + /# @netlify\/plugin-gatsby redirects start(.|\n|\r)*# @netlify\/plugin-gatsby redirects end/gm, + `` + ) + .replace(/## Created with gatsby-plugin-netlify(.|\n|\r)*$/gm, ``) await fs.outputFile(fileName, out) } From fa7dc927576ec91e6c7fcc3c93cfd48a6d3d042c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 14 Jul 2023 12:43:25 +0200 Subject: [PATCH 157/161] properly handle default body for status responses in api functions --- .../src/lambda-handler.ts | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/gatsby-adapter-netlify/src/lambda-handler.ts b/packages/gatsby-adapter-netlify/src/lambda-handler.ts index 74b3685a17cd0..ab617a0b7edc7 100644 --- a/packages/gatsby-adapter-netlify/src/lambda-handler.ts +++ b/packages/gatsby-adapter-netlify/src/lambda-handler.ts @@ -97,6 +97,72 @@ const functionModule = require("${getRelativePathToModule( const functionHandler = preferDefault(functionModule) +const statuses = { + "100": "Continue", + "101": "Switching Protocols", + "102": "Processing", + "103": "Early Hints", + "200": "OK", + "201": "Created", + "202": "Accepted", + "203": "Non-Authoritative Information", + "204": "No Content", + "205": "Reset Content", + "206": "Partial Content", + "207": "Multi-Status", + "208": "Already Reported", + "226": "IM Used", + "300": "Multiple Choices", + "301": "Moved Permanently", + "302": "Found", + "303": "See Other", + "304": "Not Modified", + "305": "Use Proxy", + "307": "Temporary Redirect", + "308": "Permanent Redirect", + "400": "Bad Request", + "401": "Unauthorized", + "402": "Payment Required", + "403": "Forbidden", + "404": "Not Found", + "405": "Method Not Allowed", + "406": "Not Acceptable", + "407": "Proxy Authentication Required", + "408": "Request Timeout", + "409": "Conflict", + "410": "Gone", + "411": "Length Required", + "412": "Precondition Failed", + "413": "Payload Too Large", + "414": "URI Too Long", + "415": "Unsupported Media Type", + "416": "Range Not Satisfiable", + "417": "Expectation Failed", + "418": "I'm a Teapot", + "421": "Misdirected Request", + "422": "Unprocessable Entity", + "423": "Locked", + "424": "Failed Dependency", + "425": "Too Early", + "426": "Upgrade Required", + "428": "Precondition Required", + "429": "Too Many Requests", + "431": "Request Header Fields Too Large", + "451": "Unavailable For Legal Reasons", + "500": "Internal Server Error", + "501": "Not Implemented", + "502": "Bad Gateway", + "503": "Service Unavailable", + "504": "Gateway Timeout", + "505": "HTTP Version Not Supported", + "506": "Variant Also Negotiates", + "507": "Insufficient Storage", + "508": "Loop Detected", + "509": "Bandwidth Limit Exceeded", + "510": "Not Extended", + "511": "Network Authentication Required" +} + const createRequestObject = ({ event, context }) => { const { path = "", @@ -220,7 +286,7 @@ const createResponseObject = ({ onResEnd }) => { return res .status(data) .setHeader('content-type', 'text/plain; charset=utf-8') - .end(statuses_1.default.message[data] || String(data)); + .end(statuses[data] || String(data)); } if (typeof data === 'boolean' || typeof data === 'object') { if (Buffer.isBuffer(data)) { From 17d4a587a14cd6778256c6a572d0ee612330af2d Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 19 Jul 2023 10:43:38 +0200 Subject: [PATCH 158/161] put some dev logs as verbose to limit regular terminal output --- packages/gatsby-adapter-netlify/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/gatsby-adapter-netlify/src/index.ts b/packages/gatsby-adapter-netlify/src/index.ts index 62473e71cb143..65494e7df3dbf 100644 --- a/packages/gatsby-adapter-netlify/src/index.ts +++ b/packages/gatsby-adapter-netlify/src/index.ts @@ -35,7 +35,7 @@ const createNetlifyAdapter: AdapterInit = options => { async restore({ directories, reporter }): Promise { const utils = await getCacheUtils() if (utils) { - reporter.info( + reporter.verbose( `[gatsby-adapter-netlify] using @netlify/cache-utils restore` ) return await utils.restore(directories) @@ -46,7 +46,7 @@ const createNetlifyAdapter: AdapterInit = options => { async store({ directories, reporter }): Promise { const utils = await getCacheUtils() if (utils) { - reporter.info( + reporter.verbose( `[gatsby-adapter-netlify] using @netlify/cache-utils save` ) await utils.save(directories) @@ -67,7 +67,7 @@ const createNetlifyAdapter: AdapterInit = options => { } }, config: ({ reporter }): IAdapterConfig => { - reporter.info( + reporter.verbose( `[gatsby-adapter-netlify] version: ${packageJson?.version ?? `unknown`}` ) // excludeDatastoreFromEngineFunction can be enabled either via options or via env var (to preserve handling of env var that existed in Netlify build plugin). From 3787d92d56b4eb6eb68900e504077f10bd188426 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 19 Jul 2023 12:41:06 +0200 Subject: [PATCH 159/161] put some dev logs as verbose to limit regular terminal output vol2 --- packages/gatsby/src/utils/adapter/manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gatsby/src/utils/adapter/manager.ts b/packages/gatsby/src/utils/adapter/manager.ts index 04610c58f8401..1cc8ab48265f0 100644 --- a/packages/gatsby/src/utils/adapter/manager.ts +++ b/packages/gatsby/src/utils/adapter/manager.ts @@ -318,7 +318,7 @@ function getRoutesManifest(): RoutesManifest { if (fileAssets.has(pathToFillInPublicDir)) { fileAssets.delete(pathToFillInPublicDir) } else { - reporter.warn( + reporter.verbose( `[Adapters] Tried to remove "${pathToFillInPublicDir}" from fileAssets but it wasn't there` ) } From 3dbe5674969cdb6c042deb4f01ae7fa6548991aa Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 21 Jul 2023 12:54:27 +0200 Subject: [PATCH 160/161] maybe deflake function tests --- integration-tests/functions/test-helpers.js | 167 +++++++++++--------- 1 file changed, 93 insertions(+), 74 deletions(-) diff --git a/integration-tests/functions/test-helpers.js b/integration-tests/functions/test-helpers.js index b428fc0208151..0c457df8bc215 100644 --- a/integration-tests/functions/test-helpers.js +++ b/integration-tests/functions/test-helpers.js @@ -7,32 +7,47 @@ const FormData = require("form-data") jest.setTimeout(15000) +const FETCH_RETRY_COUNT = 2 +async function fetchWithRetry(...args) { + for (let i = 0; i <= FETCH_RETRY_COUNT; i++) { + try { + const response = await fetch(...args) + return response + } catch (e) { + // ignore unless last retry + if (i === FETCH_RETRY_COUNT) { + throw e + } + } + } +} + export function runTests(env, host) { describe(env, () => { test(`top-level API`, async () => { - const result = await fetch(`${host}/api/top-level`).then(res => + const result = await fetchWithRetry(`${host}/api/top-level`).then(res => res.text() ) expect(result).toMatchSnapshot() }) test(`secondary-level API`, async () => { - const result = await fetch(`${host}/api/a-directory/function`).then(res => - res.text() - ) + const result = await fetchWithRetry( + `${host}/api/a-directory/function` + ).then(res => res.text()) expect(result).toMatchSnapshot() }) test(`secondary-level API with index.js`, async () => { - const result = await fetch(`${host}/api/a-directory`).then(res => + const result = await fetchWithRetry(`${host}/api/a-directory`).then(res => res.text() ) expect(result).toMatchSnapshot() }) test(`secondary-level API`, async () => { - const result = await fetch(`${host}/api/dir/function`).then(res => - res.text() + const result = await fetchWithRetry(`${host}/api/dir/function`).then( + res => res.text() ) expect(result).toMatchSnapshot() @@ -48,7 +63,7 @@ export function runTests(env, host) { ] for (const route of routes) { - const result = await fetch(route).then(res => res.text()) + const result = await fetchWithRetry(route).then(res => res.text()) expect(result).toMatchSnapshot() } @@ -59,7 +74,7 @@ export function runTests(env, host) { const routes = [`${host}/api/users/23/additional`] for (const route of routes) { - const result = await fetch(route).then(res => res.json()) + const result = await fetchWithRetry(route).then(res => res.json()) expect(result).toMatchSnapshot() } @@ -67,14 +82,14 @@ export function runTests(env, host) { test(`unnamed wildcard routes`, async () => { const routes = [`${host}/api/dir/super`] for (const route of routes) { - const result = await fetch(route).then(res => res.json()) + const result = await fetchWithRetry(route).then(res => res.json()) expect(result).toMatchSnapshot() } }) test(`named wildcard routes`, async () => { const route = `${host}/api/named-wildcard/super` - const result = await fetch(route).then(res => res.json()) + const result = await fetchWithRetry(route).then(res => res.json()) expect(result).toMatchInlineSnapshot(` Object { @@ -86,8 +101,8 @@ export function runTests(env, host) { describe(`environment variables`, () => { test(`can use inside functions`, async () => { - const result = await fetch(`${host}/api/env-variables`).then(res => - res.text() + const result = await fetchWithRetry(`${host}/api/env-variables`).then( + res => res.text() ) expect(result).toEqual(`word`) @@ -96,8 +111,8 @@ export function runTests(env, host) { describe(`typescript`, () => { test(`typescript functions work`, async () => { - const result = await fetch(`${host}/api/i-am-typescript`).then(res => - res.text() + const result = await fetchWithRetry(`${host}/api/i-am-typescript`).then( + res => res.text() ) expect(result).toMatchSnapshot() @@ -107,13 +122,15 @@ export function runTests(env, host) { describe(`function errors don't crash the server`, () => { // This test mainly just shows that the server doesn't crash. test(`normal`, async () => { - const result = await fetch(`${host}/api/error-send-function-twice`) + const result = await fetchWithRetry( + `${host}/api/error-send-function-twice` + ) expect(result.status).toEqual(200) }) test(`no handler function export`, async () => { - const result = await fetch(`${host}/api/no-function-export`) + const result = await fetchWithRetry(`${host}/api/no-function-export`) expect(result.status).toEqual(500) const body = await result.text() @@ -128,7 +145,7 @@ export function runTests(env, host) { }) test(`function throws`, async () => { - const result = await fetch(`${host}/api/function-throw`) + const result = await fetchWithRetry(`${host}/api/function-throw`) expect(result.status).toEqual(500) const body = await result.text() @@ -144,7 +161,7 @@ export function runTests(env, host) { describe(`response formats`, () => { test(`returns json correctly`, async () => { - const res = await fetch(`${host}/api/i-am-json`) + const res = await fetchWithRetry(`${host}/api/i-am-json`) const result = await res.json() const { date, ...headers } = Object.fromEntries(res.headers) @@ -152,7 +169,7 @@ export function runTests(env, host) { expect(headers).toMatchSnapshot() }) test(`returns text correctly`, async () => { - const res = await fetch(`${host}/api/i-am-typescript`) + const res = await fetchWithRetry(`${host}/api/i-am-typescript`) const result = await res.text() const { date, ...headers } = Object.fromEntries(res.headers) @@ -163,19 +180,19 @@ export function runTests(env, host) { describe(`functions can send custom statuses`, () => { test(`can return 200 status`, async () => { - const res = await fetch(`${host}/api/status`) + const res = await fetchWithRetry(`${host}/api/status`) expect(res.status).toEqual(200) }) test(`can return 404 status`, async () => { - const res = await fetch(`${host}/api/status?code=404`) + const res = await fetchWithRetry(`${host}/api/status?code=404`) expect(res.status).toEqual(404) }) test(`can return 500 status`, async () => { - const res = await fetch(`${host}/api/status?code=500`) + const res = await fetchWithRetry(`${host}/api/status?code=500`) expect(res.status).toEqual(500) }) @@ -183,9 +200,9 @@ export function runTests(env, host) { describe(`functions can parse different ways of sending data`, () => { test(`query string`, async () => { - const result = await fetch(`${host}/api/parser?amIReal=true`).then( - res => res.json() - ) + const result = await fetchWithRetry( + `${host}/api/parser?amIReal=true` + ).then(res => res.json()) expect(result).toMatchSnapshot() }) @@ -194,7 +211,7 @@ export function runTests(env, host) { const { URLSearchParams } = require("url") const params = new URLSearchParams() params.append("a", `form parameters`) - const result = await fetch(`${host}/api/parser`, { + const result = await fetchWithRetry(`${host}/api/parser`, { method: `POST`, body: params, }).then(res => res.json()) @@ -207,7 +224,7 @@ export function runTests(env, host) { const form = new FormData() form.append("a", `form-data`) - const result = await fetch(`${host}/api/parser`, { + const result = await fetchWithRetry(`${host}/api/parser`, { method: `POST`, body: form, }).then(res => res.json()) @@ -217,7 +234,7 @@ export function runTests(env, host) { test(`json body`, async () => { const body = { a: `json` } - const result = await fetch(`${host}/api/parser`, { + const result = await fetchWithRetry(`${host}/api/parser`, { method: `POST`, body: JSON.stringify(body), headers: { "Content-Type": "application/json" }, @@ -233,7 +250,7 @@ export function runTests(env, host) { const form = new FormData() form.append("file", file) - const result = await fetch(`${host}/api/parser`, { + const result = await fetchWithRetry(`${host}/api/parser`, { method: `POST`, body: form, }).then(res => res.json()) @@ -246,7 +263,7 @@ export function runTests(env, host) { // const { createReadStream } = require("fs") // const stream = createReadStream(path.join(__dirname, "./fixtures/test.txt")) - // const res = await fetch(`${host}/api/parser`, { + // const res = await fetchWithRetry(`${host}/api/parser`, { // method: `POST`, // body: stream, // }) @@ -259,7 +276,7 @@ export function runTests(env, host) { describe(`functions get parsed cookies`, () => { test(`cookie`, async () => { - const result = await fetch(`${host}/api/cookie-me`, { + const result = await fetchWithRetry(`${host}/api/cookie-me`, { headers: { cookie: `foo=blue;` }, }).then(res => res.json()) @@ -269,7 +286,7 @@ export function runTests(env, host) { describe(`functions can redirect`, () => { test(`normal`, async () => { - const result = await fetch(`${host}/api/redirect-me`) + const result = await fetchWithRetry(`${host}/api/redirect-me`) expect(result.url).toEqual(host + `/`) }) @@ -277,7 +294,7 @@ export function runTests(env, host) { describe(`functions can have custom middleware`, () => { test(`normal`, async () => { - const result = await fetch(`${host}/api/cors`) + const result = await fetchWithRetry(`${host}/api/cors`) const headers = Object.fromEntries(result.headers) expect(headers[`access-control-allow-origin`]).toEqual(`*`) @@ -289,7 +306,7 @@ export function runTests(env, host) { describe(`50kb string`, () => { const body = `x`.repeat(50 * 1024) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -308,7 +325,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { text: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-text-limit`, { method: `POST`, @@ -333,7 +350,7 @@ export function runTests(env, host) { describe(`50mb string`, () => { const body = `x`.repeat(50 * 1024 * 1024) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -345,7 +362,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { text: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-text-limit`, { method: `POST`, @@ -370,7 +387,7 @@ export function runTests(env, host) { describe(`custom type`, () => { const body = `test-string` it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -389,7 +406,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { text: { type: "*/*" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-text-type`, { method: `POST`, @@ -418,7 +435,7 @@ export function runTests(env, host) { content: `x`.repeat(50 * 1024), }) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -439,7 +456,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { json: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-json-limit`, { method: `POST`, @@ -468,7 +485,7 @@ export function runTests(env, host) { content: `x`.repeat(50 * 1024 * 1024), }) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -480,7 +497,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { json: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-json-limit`, { method: `POST`, @@ -509,7 +526,7 @@ export function runTests(env, host) { content: `test-string`, }) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -528,7 +545,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { json: { type: "*/*" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-json-type`, { method: `POST`, @@ -557,7 +574,7 @@ export function runTests(env, host) { content: `x`.repeat(50 * 1024), }) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -576,7 +593,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { raw: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-raw-limit`, { method: `POST`, @@ -603,7 +620,7 @@ export function runTests(env, host) { content: `x`.repeat(50 * 1024 * 1024), }) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -615,7 +632,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { raw: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-raw-limit`, { method: `POST`, @@ -642,7 +659,7 @@ export function runTests(env, host) { content: `test-string`, }) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -661,7 +678,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { raw: { type: "*/*" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-raw-type`, { method: `POST`, @@ -688,7 +705,7 @@ export function runTests(env, host) { content: `test-string`, }) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -708,7 +725,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { raw: { type: "*/*" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-raw-type`, { method: `POST`, @@ -743,7 +760,7 @@ export function runTests(env, host) { body.append(`content`, `x`.repeat(50 * 1024)) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -764,7 +781,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { urlencoded: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-urlencoded-limit`, { method: `POST`, @@ -796,7 +813,7 @@ export function runTests(env, host) { body.append(`content`, `x`.repeat(50 * 1024 * 1024)) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -808,7 +825,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { urlencoded: { limit: "100mb" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-urlencoded-limit`, { method: `POST`, @@ -840,7 +857,7 @@ export function runTests(env, host) { body.append(`content`, `test-string`) it(`on default config`, async () => { - const result = await fetch(`${host}/api/config/defaults`, { + const result = await fetchWithRetry(`${host}/api/config/defaults`, { method: `POST`, body, headers: { @@ -859,7 +876,7 @@ export function runTests(env, host) { }) it(`on { bodyParser: { urlencoded: { type: "*/*" }}}`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/config/body-parser-urlencoded-type`, { method: `POST`, @@ -885,18 +902,18 @@ export function runTests(env, host) { describe(`plugins can declare functions and they can be shadowed`, () => { test(`shadowing`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/gatsby-plugin-cool/shadowed` ).then(res => res.text()) expect(result).toEqual(`I am shadowed`) - const result2 = await fetch( + const result2 = await fetchWithRetry( `${host}/api/gatsby-plugin-cool/not-shadowed` ).then(res => res.text()) expect(result2).toEqual(`I am not shadowed`) }) test(`plugins can't declare functions outside of their namespace`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/i-will-not-work-cause-namespacing` ) expect(result.status).toEqual(404) @@ -905,8 +922,8 @@ export function runTests(env, host) { describe(`typescript files are resolved without needing to specify their extension`, () => { test(`typescript`, async () => { - const result = await fetch(`${host}/api/extensions`).then(res => - res.text() + const result = await fetchWithRetry(`${host}/api/extensions`).then( + res => res.text() ) expect(result).toEqual(`hi`) }) @@ -914,23 +931,25 @@ export function runTests(env, host) { describe(`ignores files that match the pattern`, () => { test(`dotfile`, async () => { - const result = await fetch(`${host}/api/ignore/.config`) + const result = await fetchWithRetry(`${host}/api/ignore/.config`) expect(result.status).toEqual(404) }) test(`.d.ts file`, async () => { - const result = await fetch(`${host}/api/ignore/foo.d`) + const result = await fetchWithRetry(`${host}/api/ignore/foo.d`) expect(result.status).toEqual(404) }) test(`test file`, async () => { - const result = await fetch(`${host}/api/ignore/hello.test`) + const result = await fetchWithRetry(`${host}/api/ignore/hello.test`) expect(result.status).toEqual(404) }) test(`test directory`, async () => { - const result = await fetch(`${host}/api/ignore/__tests__/hello`) + const result = await fetchWithRetry( + `${host}/api/ignore/__tests__/hello` + ) expect(result.status).toEqual(404) }) test(`test file in plugin`, async () => { - const result = await fetch( + const result = await fetchWithRetry( `${host}/api/gatsby-plugin-cool/shadowed.test` ) expect(result.status).toEqual(404) @@ -939,9 +958,9 @@ export function runTests(env, host) { describe(`bundling`, () => { test(`should succeed when gatsby-core-utils is imported`, async () => { - const result = await fetch(`${host}/api/ignore-lmdb-require`).then( - res => res.text() - ) + const result = await fetchWithRetry( + `${host}/api/ignore-lmdb-require` + ).then(res => res.text()) expect(result).toEqual(`hello world`) }) }) @@ -968,7 +987,7 @@ export function runTests(env, host) { // path.join(apiDir, `function-a.js`) // ) // setTimeout(async () => { - // const result = await fetch( + // const result = await fetchWithRetry( // `${host}/api/function-a` // ).then(res => res.text()) From 92a7996e797cb943e075733337f304a6a6d7bc6b Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 24 Jul 2023 12:46:32 +0200 Subject: [PATCH 161/161] maybe precompile api functions in develop function tests --- integration-tests/functions/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/functions/package.json b/integration-tests/functions/package.json index 3c75ffb5479c3..739ee4c5f3688 100644 --- a/integration-tests/functions/package.json +++ b/integration-tests/functions/package.json @@ -10,7 +10,7 @@ "scripts": { "clean": "gatsby clean", "build": "gatsby build", - "develop": "gatsby develop", + "develop": "GATSBY_PRECOMPILE_DEVELOP_FUNCTIONS=true gatsby develop", "serve": "gatsby serve", "test-prod": "start-server-and-test serve 9000 jest:prod", "test-dev": "start-server-and-test develop 8000 jest:dev",