Skip to content

Commit edaf016

Browse files
gatsbybotpieh
andauthoredApr 10, 2024··
fix(gatsby-adapter-netlify): handler generation on windows (#38900) (#38929)
* test: add unit test for produced handler * actually failing test in windows * fix(gatsby-adapter-netlify): produce working function handlers on windows * fix(gatsby): functions compilation on windows * tmp: prepare cross-platform binaries for SSR/DSG * fix: lint * feat: add a way to configure functions executing platform/arch and add early check in DSG/SSR * refactor: move some utility functions around, cleanup standalone-regenrate, restore engine validation, add structured error and better error messages * chore: add jsdocs description for functionsPlatform and functionsArch optional config values passed by adapter * chore: make sure fs wrapper is first * fix: actually use values reported by adapter * test: try to setup windows adapters smoke test * test: typo * test: maybe cd into dirs? * test: no powershell fro smoke test * chore: single quote to double * chore: install node-gyp requirements * chore: install deps in win smoke * ? * newer node needed for ntl-cli * run ntl through yarn * Revert "run ntl through yarn" This reverts commit 8c55e40. * install ntl-cli in circleci pipeline * test: adjust lmdb regeneration test to changed internal-packages location * test: run windows deploy/smoke test after unit tests passed * chore: use path.posix to load engines in serve command * chore: use default value when destructuring instead of nullish coalescing later (cherry picked from commit c91ed28) Co-authored-by: Michal Piechowiak <misiek.piechowiak@gmail.com>

File tree

28 files changed

+716
-204
lines changed

28 files changed

+716
-204
lines changed
 

‎.circleci/config.yml

+45
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,43 @@ jobs:
580580
- store_test_results:
581581
path: ./test-results/jest-node/
582582

583+
windows_adapters_smoke:
584+
executor:
585+
name: win/default
586+
shell: bash.exe
587+
steps:
588+
- checkout
589+
- run:
590+
command: ./scripts/assert-changed-files.sh "packages/*|(e2e|integration)-tests/*|.circleci/*|scripts/e2e-test.sh|yarn.lock"
591+
- <<: *attach_to_bootstrap
592+
- run:
593+
name: Install node 18.19.0, yarn and netlify-cli
594+
command: |
595+
nvm install 18.19.0
596+
nvm alias default 18.19.0
597+
nvm use 18.19.0
598+
npm install -g yarn netlify-cli
599+
- run:
600+
name: Clear out sharp
601+
command: |
602+
Remove-Item -Recurse -Force -Path "node_modules/sharp/"
603+
shell: powershell.exe
604+
- run:
605+
command: yarn
606+
- run:
607+
command: mkdir -p /tmp/e2e-tests/
608+
- run:
609+
command: cp -r ./e2e-tests/adapters /tmp/e2e-tests/adapters
610+
- run:
611+
command: pwd && ls
612+
working_directory: /tmp/e2e-tests/adapters
613+
- run: # Set project dir
614+
command: node ./packages/gatsby-dev-cli/dist/index.js --set-path-to-repo .
615+
- run: # Copy over packages
616+
command: cd /tmp/e2e-tests/adapters && node ~/project/packages/gatsby-dev-cli/dist/index.js --force-install --scan-once
617+
- run: # run smoke test
618+
command: cd /tmp/e2e-tests/adapters && node scripts/deploy-and-run/netlify.mjs test:smoke
619+
583620
workflows:
584621
version: 2
585622

@@ -611,6 +648,14 @@ workflows:
611648
requires:
612649
- lint
613650
- bootstrap
651+
- windows_adapters_smoke:
652+
requires:
653+
# ideally we wait for windows unit tests here, but because those are flaky
654+
# feedback loop would be not practical, so at least wait for linux unit tests
655+
# to resemble setup for more robust E2E tests
656+
- lint
657+
- bootstrap
658+
- unit_tests_node18
614659
- unit_tests_node18:
615660
<<: *ignore_docs
616661
requires:

‎e2e-tests/adapters/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"test:template:debug": "cross-env-shell CYPRESS_GROUP_NAME=\"adapter:$ADAPTER / trailingSlash:${TRAILING_SLASH:-always} / pathPrefix:${PATH_PREFIX:--}\" TRAILING_SLASH=$TRAILING_SLASH PATH_PREFIX=$PATH_PREFIX npm run cy:open -- --config-file \"cypress/configs/$ADAPTER.ts\" --env TRAILING_SLASH=$TRAILING_SLASH,PATH_PREFIX=$PATH_PREFIX",
1818
"test:debug": "npm-run-all -s build:debug ssat:debug",
1919
"test:netlify": "cross-env TRAILING_SLASH=always node scripts/deploy-and-run/netlify.mjs test:template",
20+
"test:smoke": "node smoke-test.mjs",
2021
"test:netlify:debug": "cross-env TRAILING_SLASH=always node scripts/deploy-and-run/netlify.mjs test:template:debug",
2122
"test:netlify:prefix-never": "cross-env TRAILING_SLASH=never PATH_PREFIX=/prefix node scripts/deploy-and-run/netlify.mjs test:template",
2223
"test:netlify:prefix-never:debug": "cross-env TRAILING_SLASH=never PATH_PREFIX=/prefix node scripts/deploy-and-run/netlify.mjs test:template:debug",

‎e2e-tests/adapters/smoke-test.mjs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import assert from "node:assert"
2+
3+
{
4+
// check index page (SSG)
5+
const response = await fetch(process.env.DEPLOY_URL)
6+
assert.equal(response.status, 200)
7+
8+
const body = await response.text()
9+
assert.match(body, /<h1>Adapters<\/h1>/)
10+
assert.match(body, /<title[^>]*>Adapters E2E<\/title>/)
11+
}
12+
13+
{
14+
// check SSR page
15+
const response = await fetch(
16+
process.env.DEPLOY_URL + `/routes/ssr/remote-file/`
17+
)
18+
assert.equal(response.status, 200)
19+
20+
const body = await response.text()
21+
// inline css for placeholder - this tests both LMDB and SHARP
22+
// (LMDB because of page query and sharp because page query will use sharp to generate placeholder values)
23+
assert.match(body, /background-color:rgb\(232,184,8\)/)
24+
}

‎integration-tests/lmdb-regeneration/__tests__/index.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ describe(`Lmdb regeneration`, () => {
3838

3939
// If the fix worked correctly we should have installed the prebuilt binary for our platform under our `.cache` directory
4040
const lmdbRequire = mod.createRequire(
41-
path.resolve(rootPath, ".cache", "internal-packages", "package.json")
41+
path.resolve(
42+
rootPath,
43+
".cache",
44+
"internal-packages",
45+
`${process.platform}-${process.arch}`,
46+
"package.json"
47+
)
4248
)
4349
expect(() => {
4450
lmdbRequire.resolve(lmdbPackage)

‎packages/gatsby-adapter-netlify/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"@netlify/functions": "^1.6.0",
3838
"cookie": "^0.5.0",
3939
"fastq": "^1.15.0",
40-
"fs-extra": "^11.1.1"
40+
"fs-extra": "^11.1.1",
41+
"gatsby-core-utils": "^4.13.1"
4142
},
4243
"devDependencies": {
4344
"@babel/cli": "^7.20.7",

‎packages/gatsby-adapter-netlify/src/__tests__/fixtures/lambda-handler/entry.js

Whitespace-only changes.

‎packages/gatsby-adapter-netlify/src/__tests__/fixtures/lambda-handler/included.js

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import fs from "fs-extra"
2+
import { prepareFunction } from "../lambda-handler"
3+
import { join, relative } from "path"
4+
import { slash } from "gatsby-core-utils/path"
5+
6+
const writeFileSpy = jest
7+
.spyOn(fs, `writeFile`)
8+
.mockImplementation(async () => {})
9+
const writeJsonSpy = jest
10+
.spyOn(fs, `writeJSON`)
11+
.mockImplementation(async () => {})
12+
13+
const fixturePath = join(
14+
relative(process.cwd(), __dirname),
15+
`fixtures`,
16+
`lambda-handler`
17+
)
18+
const pathToEntryPoint = join(fixturePath, `entry.js`)
19+
const requiredFile = join(fixturePath, `included.js`)
20+
21+
test(`produced handler is correct`, async () => {
22+
await prepareFunction({
23+
functionId: `test`,
24+
name: `test`,
25+
pathToEntryPoint,
26+
requiredFiles: [requiredFile],
27+
})
28+
const handlerCode = writeFileSpy.mock.calls[0][1]
29+
// expect require in produced code (this is to mostly to make sure handlerCode is actual handler code)
30+
expect(handlerCode).toMatch(/require\(["'][^"']*["']\)/)
31+
// require paths should not have backward slashes (win paths)
32+
expect(handlerCode).not.toMatch(/require\(["'][^"']*\\[^"']*["']\)/)
33+
34+
expect(writeJsonSpy).toBeCalledWith(
35+
expect.any(String),
36+
expect.objectContaining({
37+
config: expect.objectContaining({
38+
name: `test`,
39+
generator: expect.stringContaining(`gatsby-adapter-netlify`),
40+
includedFiles: [slash(requiredFile)],
41+
externalNodeModules: [`msgpackr-extract`],
42+
}),
43+
version: 1,
44+
})
45+
)
46+
})

‎packages/gatsby-adapter-netlify/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ const createNetlifyAdapter: AdapterInit<INetlifyAdapterOptions> = options => {
162162
fileCDNUrlGeneratorModulePath: useNetlifyImageCDN
163163
? require.resolve(`./file-cdn-url-generator`)
164164
: undefined,
165+
functionsPlatform: `linux`,
166+
functionsArch: `x64`,
165167
}
166168
},
167169
}

‎packages/gatsby-adapter-netlify/src/lambda-handler.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { IFunctionDefinition } from "gatsby"
22
import packageJson from "gatsby-adapter-netlify/package.json"
33
import fs from "fs-extra"
44
import * as path from "path"
5+
import { slash } from "gatsby-core-utils/path"
56

67
interface INetlifyFunctionConfig {
78
externalNodeModules?: Array<string>
@@ -25,7 +26,7 @@ interface INetlifyFunctionManifest {
2526
version: number
2627
}
2728

28-
async function prepareFunction(
29+
export async function prepareFunction(
2930
fun: IFunctionDefinition,
3031
odbfunctionName?: string
3132
): Promise<void> {
@@ -58,7 +59,7 @@ async function prepareFunction(
5859
name: displayName,
5960
generator: `gatsby-adapter-netlify@${packageJson?.version ?? `unknown`}`,
6061
includedFiles: fun.requiredFiles.map(file =>
61-
file.replace(/\[/g, `*`).replace(/]/g, `*`)
62+
slash(file).replace(/\[/g, `*`).replace(/]/g, `*`)
6263
),
6364
externalNodeModules: [`msgpackr-extract`],
6465
},
@@ -73,7 +74,10 @@ async function prepareFunction(
7374
function getRelativePathToModule(modulePath: string): string {
7475
const absolutePath = require.resolve(modulePath)
7576

76-
return `./` + path.relative(internalFunctionsDir, absolutePath)
77+
return (
78+
`./` +
79+
path.posix.relative(slash(internalFunctionsDir), slash(absolutePath))
80+
)
7781
}
7882

7983
const handlerSource = /* javascript */ `

‎packages/gatsby-cli/src/create-cli.ts

+8
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,14 @@ function buildLocalCommands(cli: yargs.Argv, isLocalSite: boolean): void {
273273
default: false,
274274
describe: `Save the log of changed pages for future comparison.`,
275275
hidden: true,
276+
})
277+
.option(`functions-platform`, {
278+
type: `string`,
279+
describe: `The platform bundled functions will execute on. Defaults to current platform or settings provided by used adapter.`,
280+
})
281+
.option(`functions-arch`, {
282+
type: `string`,
283+
describe: `The architecture bundled functions will execute on. Defaults to current architecture or settings provided by used adapter.`,
276284
}),
277285
handler: handlerP(
278286
getCommandHandler(

‎packages/gatsby-cli/src/structured-errors/error-map.ts

+6
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ const errors: Record<string, IErrorMapEntry> = {
8383
level: Level.ERROR,
8484
category: ErrorCategory.USER,
8585
},
86+
"98051": {
87+
text: (): string => `Built Rendering Engines failed to load.`,
88+
type: Type.ENGINE_EXECUTION,
89+
level: Level.ERROR,
90+
category: ErrorCategory.UNKNOWN,
91+
},
8692
"98123": {
8793
text: (context): string =>
8894
`${context.stageLabel} failed\n\n${

‎packages/gatsby-legacy-polyfills/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"license": "MIT",
1717
"scripts": {
1818
"build": "npm-run-all --npm-path npm -p build:*",
19-
"build:exclude": "cpy 'exclude.js' '../dist' --cwd=./src",
19+
"build:exclude": "cpy \"exclude.js\" \"../dist\" --cwd=./src",
2020
"build:polyfills": "microbundle -f iife -i src/polyfills.js --no-sourcemap --external=none",
2121
"prepare": "cross-env NODE_ENV=production npm run build",
2222
"watch": "npm-run-all --npm-path npm -p watch:*",

‎packages/gatsby-plugin-offline/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"scripts": {
4848
"build": "npm run build:src && npm run build:sw-append",
4949
"build:src": "babel src --out-dir . --ignore \"**/__tests__,src/sw-append.js\"",
50-
"build:sw-append": "cpy 'sw-append.js' '../' --cwd=./src",
50+
"build:sw-append": "cpy \"sw-append.js\" \"../\" --cwd=./src",
5151
"prepare": "cross-env NODE_ENV=production npm run build",
5252
"watch": "npm run build:sw-append -- --watch & npm run build:src -- --watch"
5353
},

‎packages/gatsby/src/commands/build-html.ts

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export interface IBuildArgs extends IProgram {
4242
profile: boolean
4343
graphqlTracing: boolean
4444
openTracingConfigFile: string
45+
functionsPlatform?: string
46+
functionsArch?: string
4547
// TODO remove in v4
4648
keepPageRenderer: boolean
4749
}

‎packages/gatsby/src/commands/build.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ import {
6666
getPageMode,
6767
preparePageTemplateConfigs,
6868
} from "../utils/page-mode"
69-
import { validateEngines } from "../utils/validate-engines"
69+
import { validateEnginesWithActivity } from "../utils/validate-engines"
7070
import { constructConfigObject } from "../utils/gatsby-cloud-config"
7171
import { waitUntilWorkerJobsAreComplete } from "../utils/jobs/worker-messaging"
7272
import { getSSRChunkHashes } from "../utils/webpack/get-ssr-chunk-hashes"
@@ -295,20 +295,7 @@ module.exports = async function build(
295295
}
296296

297297
if (shouldGenerateEngines()) {
298-
const validateEnginesActivity = report.activityTimer(
299-
`Validating Rendering Engines`,
300-
{
301-
parentSpan: buildSpan,
302-
}
303-
)
304-
validateEnginesActivity.start()
305-
try {
306-
await validateEngines(store.getState().program.directory)
307-
} catch (error) {
308-
validateEnginesActivity.panic({ id: `98001`, context: {}, error })
309-
} finally {
310-
validateEnginesActivity.end()
311-
}
298+
await validateEnginesWithActivity(program.directory, buildSpan)
312299
}
313300

314301
const cacheActivity = report.activityTimer(`Caching Webpack compilations`, {

‎packages/gatsby/src/commands/serve.ts

+127-105
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
thirdPartyProxyPath,
3030
partytownProxy,
3131
} from "../internal-plugins/partytown/proxy"
32+
import { slash } from "gatsby-core-utils/path"
3233

3334
interface IMatchPath {
3435
path: string
@@ -184,125 +185,146 @@ module.exports = async (program: IServeProgram): Promise<void> => {
184185
}
185186

186187
// Handle SSR & DSG Pages
188+
let graphqlEnginePath: string | undefined
189+
let pageSSRModule: string | undefined
187190
try {
188-
const { GraphQLEngine } = require(path.join(
189-
program.directory,
190-
`.cache`,
191-
`query-engine`
192-
)) as typeof import("../schema/graphql-engine/entry")
193-
const { getData, renderPageData, renderHTML } = require(path.join(
194-
program.directory,
195-
`.cache`,
196-
`page-ssr`
197-
)) as typeof import("../utils/page-ssr-module/entry")
198-
const graphqlEngine = new GraphQLEngine({
199-
dbPath: path.join(program.directory, `.cache`, `data`, `datastore`),
200-
})
191+
graphqlEnginePath = require.resolve(
192+
path.posix.join(slash(program.directory), `.cache`, `query-engine`)
193+
)
194+
pageSSRModule = require.resolve(
195+
path.posix.join(slash(program.directory), `.cache`, `page-ssr`)
196+
)
197+
} catch (error) {
198+
// TODO: Handle case of engine not being generated
199+
}
201200

202-
router.get(
203-
`/page-data/:pagePath(*)/page-data.json`,
204-
async (req, res, next) => {
205-
const requestedPagePath = req.params.pagePath
206-
if (!requestedPagePath) {
207-
return void next()
208-
}
201+
if (graphqlEnginePath && pageSSRModule) {
202+
try {
203+
const { GraphQLEngine } =
204+
require(graphqlEnginePath) as typeof import("../schema/graphql-engine/entry")
205+
const { getData, renderPageData, renderHTML } =
206+
require(pageSSRModule) as typeof import("../utils/page-ssr-module/entry")
207+
const graphqlEngine = new GraphQLEngine({
208+
dbPath: path.posix.join(
209+
slash(program.directory),
210+
`.cache`,
211+
`data`,
212+
`datastore`
213+
),
214+
})
215+
216+
router.get(
217+
`/page-data/:pagePath(*)/page-data.json`,
218+
async (req, res, next) => {
219+
const requestedPagePath = req.params.pagePath
220+
if (!requestedPagePath) {
221+
return void next()
222+
}
223+
224+
const potentialPagePath = reverseFixedPagePath(requestedPagePath)
225+
const page = graphqlEngine.findPageByPath(potentialPagePath)
209226

210-
const potentialPagePath = reverseFixedPagePath(requestedPagePath)
211-
const page = graphqlEngine.findPageByPath(potentialPagePath)
212-
213-
if (page && (page.mode === `DSG` || page.mode === `SSR`)) {
214-
const requestActivity = report.phantomActivity(
215-
`request for "${req.path}"`
216-
)
217-
requestActivity.start()
218-
try {
219-
const spanContext = requestActivity.span.context()
220-
const data = await getData({
221-
pathName: req.path,
222-
graphqlEngine,
223-
req,
224-
spanContext,
225-
})
226-
const results = await renderPageData({ data, spanContext })
227-
if (data.serverDataHeaders) {
228-
for (const [name, value] of Object.entries(
229-
data.serverDataHeaders
230-
)) {
231-
res.setHeader(name, value)
227+
if (page && (page.mode === `DSG` || page.mode === `SSR`)) {
228+
const requestActivity = report.phantomActivity(
229+
`request for "${req.path}"`
230+
)
231+
requestActivity.start()
232+
try {
233+
const spanContext = requestActivity.span.context()
234+
const data = await getData({
235+
pathName: req.path,
236+
graphqlEngine,
237+
req,
238+
spanContext,
239+
})
240+
const results = await renderPageData({ data, spanContext })
241+
if (data.serverDataHeaders) {
242+
for (const [name, value] of Object.entries(
243+
data.serverDataHeaders
244+
)) {
245+
res.setHeader(name, value)
246+
}
232247
}
233-
}
234248

235-
if (page.mode === `SSR` && data.serverDataStatus) {
236-
return void res.status(data.serverDataStatus).send(results)
237-
} else {
238-
return void res.send(results)
249+
if (page.mode === `SSR` && data.serverDataStatus) {
250+
return void res.status(data.serverDataStatus).send(results)
251+
} else {
252+
return void res.send(results)
253+
}
254+
} catch (e) {
255+
report.error(
256+
`Generating page-data for "${requestedPagePath}" / "${potentialPagePath}" failed.`,
257+
e
258+
)
259+
return res
260+
.status(500)
261+
.contentType(`text/plain`)
262+
.send(`Internal server error.`)
263+
} finally {
264+
requestActivity.end()
239265
}
240-
} catch (e) {
241-
report.error(
242-
`Generating page-data for "${requestedPagePath}" / "${potentialPagePath}" failed.`,
243-
e
244-
)
245-
return res
246-
.status(500)
247-
.contentType(`text/plain`)
248-
.send(`Internal server error.`)
249-
} finally {
250-
requestActivity.end()
251266
}
252-
}
253267

254-
return void next()
255-
}
256-
)
268+
return void next()
269+
}
270+
)
257271

258-
router.use(async (req, res, next) => {
259-
if (req.accepts(`html`)) {
260-
const potentialPagePath = req.path
261-
const page = graphqlEngine.findPageByPath(potentialPagePath)
262-
if (page && (page.mode === `DSG` || page.mode === `SSR`)) {
263-
const requestActivity = report.phantomActivity(
264-
`request for "${req.path}"`
265-
)
266-
requestActivity.start()
267-
268-
try {
269-
const spanContext = requestActivity.span.context()
270-
const data = await getData({
271-
pathName: potentialPagePath,
272-
graphqlEngine,
273-
req,
274-
spanContext,
275-
})
276-
const results = await renderHTML({ data, spanContext })
277-
if (data.serverDataHeaders) {
278-
for (const [name, value] of Object.entries(
279-
data.serverDataHeaders
280-
)) {
281-
res.setHeader(name, value)
272+
router.use(async (req, res, next) => {
273+
if (req.accepts(`html`)) {
274+
const potentialPagePath = req.path
275+
const page = graphqlEngine.findPageByPath(potentialPagePath)
276+
if (page && (page.mode === `DSG` || page.mode === `SSR`)) {
277+
const requestActivity = report.phantomActivity(
278+
`request for "${req.path}"`
279+
)
280+
requestActivity.start()
281+
282+
try {
283+
const spanContext = requestActivity.span.context()
284+
const data = await getData({
285+
pathName: potentialPagePath,
286+
graphqlEngine,
287+
req,
288+
spanContext,
289+
})
290+
const results = await renderHTML({ data, spanContext })
291+
if (data.serverDataHeaders) {
292+
for (const [name, value] of Object.entries(
293+
data.serverDataHeaders
294+
)) {
295+
res.setHeader(name, value)
296+
}
282297
}
283-
}
284298

285-
if (page.mode === `SSR` && data.serverDataStatus) {
286-
return void res.status(data.serverDataStatus).send(results)
287-
} else {
288-
return void res.send(results)
289-
}
290-
} catch (e) {
291-
report.error(`Rendering html for "${potentialPagePath}" failed.`, e)
292-
return res.status(500).sendFile(`500.html`, { root }, err => {
293-
if (err) {
294-
res.contentType(`text/plain`).send(`Internal server error.`)
299+
if (page.mode === `SSR` && data.serverDataStatus) {
300+
return void res.status(data.serverDataStatus).send(results)
301+
} else {
302+
return void res.send(results)
295303
}
296-
})
297-
} finally {
298-
requestActivity.end()
304+
} catch (e) {
305+
report.error(
306+
`Rendering html for "${potentialPagePath}" failed.`,
307+
e
308+
)
309+
return res.status(500).sendFile(`500.html`, { root }, err => {
310+
if (err) {
311+
res.contentType(`text/plain`).send(`Internal server error.`)
312+
}
313+
})
314+
} finally {
315+
requestActivity.end()
316+
}
299317
}
300318
}
301-
}
302-
return next()
303-
})
304-
} catch (error) {
305-
// TODO: Handle case of engine not being generated
319+
return next()
320+
})
321+
} catch (error) {
322+
report.panic({
323+
id: `98051`,
324+
error,
325+
context: {},
326+
})
327+
}
306328
}
307329

308330
const matchPaths = await readMatchPaths(program)

‎packages/gatsby/src/commands/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export interface IProgram {
3434
graphqlTracing?: boolean
3535
verbose?: boolean
3636
prefixPaths?: boolean
37+
functionsPlatform?: string
38+
functionsArch?: string
3739
setStore?: (store: Store<IGatsbyState, AnyAction>) => void
3840
disablePlugins?: Array<{
3941
name: string

‎packages/gatsby/src/internal-plugins/functions/api-function-webpack-loader.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ const APIFunctionLoader: LoaderDefinition = async function () {
1010
return /* javascript */ `
1111
const preferDefault = m => (m && m.default) || m
1212
13-
const functionModule = require('${modulePath}');
13+
const functionModule = require('${slash(modulePath)}');
1414
const functionToExecute = preferDefault(functionModule);
1515
const matchPath = '${matchPath}';
16-
const { match: reachMatch } = require('${require.resolve(
17-
`@gatsbyjs/reach-router`
16+
const { match: reachMatch } = require('${slash(
17+
require.resolve(`@gatsbyjs/reach-router`)
1818
)}');
19-
const { urlencoded, text, json, raw } = require('${require.resolve(
20-
`body-parser`
19+
const { urlencoded, text, json, raw } = require('${slash(
20+
require.resolve(`body-parser`)
2121
)}')
22-
const multer = require('${require.resolve(`multer`)}')
23-
const { createConfig } = require('${require.resolve(`./config`)}')
22+
const multer = require('${slash(require.resolve(`multer`))}')
23+
const { createConfig } = require('${slash(require.resolve(`./config`))}')
2424
2525
function functionWrapper(req, res) {
2626
if (matchPath) {

‎packages/gatsby/src/schema/graphql-engine/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// "engines-fs-provider" must be first import, as it sets up global
22
// fs and this need to happen before anything else tries to import fs
33
import "../../utils/engines-fs-provider"
4+
import "./platform-and-arch-check"
45

56
import { getCache as getGatsbyCache } from "../../utils/get-cache"
67

‎packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts

+311-47
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
if (
2+
process.env.GATSBY_FUNCTIONS_PLATFORM !== process.platform ||
3+
process.env.GATSBY_FUNCTIONS_ARCH !== process.arch
4+
) {
5+
throw new Error(
6+
`Incompatible DSG/SSR executing environment. Function was built for "${process.env.GATSBY_FUNCTIONS_PLATFORM}/${process.env.GATSBY_FUNCTIONS_ARCH}" but is executing on "${process.platform}/${process.arch}".` +
7+
(process.env.gatsby_executing_command === `serve`
8+
? `\n\nIf you are trying to run DSG/SSR engine locally, consider using experimental utility to rebuild functions for your local platform:\n\nnode node_modules/gatsby/dist/schema/graphql-engine/standalone-regenerate.js`
9+
: ``) +
10+
`\n\nTo generate engines for "${process.platform}/${process.arch}" run 'gatsby build --functions-platform=${process.platform} --functions-arch=${process.arch}' or run 'gatsby build' with following envirnment variables:\n\nGATSBY_FUNCTIONS_PLATFORM=${process.platform}\nGATSBY_FUNCTIONS_ARCH=${process.arch}`
11+
)
12+
}

‎packages/gatsby/src/schema/graphql-engine/standalone-regenerate.ts

+13-21
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
#!/usr/bin/env node
22

33
/*
4-
this is used for development purposes only
5-
to be able to run `gatsby build` once to source data
6-
and print schema and then just rebundle graphql-engine
4+
This is used mostly for development purposes, but can be attempted to be used
5+
to regenerate just engines for local platform/arch if previous full build
6+
was done to deploy on platform with different arch/platform.
7+
8+
For development purposes this is used to be able to run `gatsby build` once to
9+
source data and print schema and then just rebundle graphql-engine
710
with source file changes and test re-built engine quickly
811
912
Usage:
1013
There need to be at least one successful `gatsby build`
1114
before starting to use this script (warm up datastore,
1215
generate "page-ssr" bundle). Once that's done you can
13-
run following command in test site directory:
16+
run following command in site directory:
1417
1518
```shell
1619
node node_modules/gatsby/dist/schema/graphql-engine/standalone-regenerate.js
@@ -23,18 +26,18 @@ import reporter from "gatsby-cli/lib/reporter"
2326
import { loadConfigAndPlugins } from "../../utils/worker/child/load-config-and-plugins"
2427
import * as fs from "fs-extra"
2528
import { store } from "../../redux"
26-
import { validateEngines } from "../../utils/validate-engines"
29+
import { validateEnginesWithActivity } from "../../utils/validate-engines"
2730

2831
async function run(): Promise<void> {
2932
process.env.GATSBY_SLICES = `1`
3033
// load config
31-
console.log(`loading config and plugins`)
34+
reporter.verbose(`loading config and plugins`)
3235
await loadConfigAndPlugins({
3336
siteDirectory: process.cwd(),
3437
})
3538

3639
try {
37-
console.log(`clearing webpack cache\n\n`)
40+
reporter.verbose(`clearing webpack cache`)
3841
// get rid of cache if it exist
3942
await fs.remove(process.cwd() + `/.cache/webpack/query-engine`)
4043
await fs.remove(process.cwd() + `/.cache/webpack/page-ssr`)
@@ -46,7 +49,7 @@ async function run(): Promise<void> {
4649

4750
// recompile
4851
const buildActivityTimer = reporter.activityTimer(
49-
`Building Rendering Engines`
52+
`(Re)Building Rendering Engines`
5053
)
5154
try {
5255
buildActivityTimer.start()
@@ -67,20 +70,9 @@ async function run(): Promise<void> {
6770
buildActivityTimer.end()
6871
}
6972

70-
// validate
71-
const validateEnginesActivity = reporter.activityTimer(
72-
`Validating Rendering Engines`
73-
)
74-
validateEnginesActivity.start()
75-
try {
76-
await validateEngines(process.cwd())
77-
} catch (error) {
78-
validateEnginesActivity.panic({ id: `98001`, context: {}, error })
79-
} finally {
80-
validateEnginesActivity.end()
81-
}
73+
await validateEnginesWithActivity(process.cwd())
8274

83-
console.log(`DONE`)
75+
reporter.info(`Rebuilding Rendering Engines finished`)
8476
}
8577

8678
run()

‎packages/gatsby/src/utils/adapter/init.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { satisfies } from "semver"
99
import type { AdapterInit } from "./types"
1010
import { preferDefault } from "../../bootstrap/prefer-default"
1111
import { getLatestAdapters } from "../get-latest-gatsby-files"
12+
import { maybeAddFileProtocol } from "../../bootstrap/resolve-js-file-path"
1213

1314
export const getAdaptersCacheDir = (): string =>
1415
join(process.cwd(), `.cache/adapters`)
@@ -85,7 +86,9 @@ const tryLoadingAlreadyInstalledAdapter = async ({
8586
}
8687
}
8788

88-
const required = locationRequire.resolve(adapterToUse.module)
89+
const required = maybeAddFileProtocol(
90+
locationRequire.resolve(adapterToUse.module)
91+
)
8992
if (required) {
9093
return {
9194
found: true,

‎packages/gatsby/src/utils/adapter/manager.ts

+2
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,8 @@ export async function initAdapterManager(): Promise<IAdapterManager> {
286286
deployURL: configFromAdapter?.deployURL,
287287
supports: configFromAdapter?.supports,
288288
pluginsToDisable: configFromAdapter?.pluginsToDisable ?? [],
289+
functionsArch: configFromAdapter?.functionsArch,
290+
functionsPlatform: configFromAdapter?.functionsPlatform,
289291
}
290292
},
291293
}

‎packages/gatsby/src/utils/adapter/types.ts

+12
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,18 @@ export interface IAdapterConfig {
201201
* example for the Netlify adapter.
202202
*/
203203
fileCDNUrlGeneratorModulePath?: string
204+
/**
205+
* The platform bundled functions will execute on. Usually should be `linux`.
206+
* This will be used if user didn't specify `GATSBY_FUNCTIONS_PLATFORM` environment variable
207+
* or used `-functions-platform` CLI toggle. If none is defined current platform (process.platform) will be used.
208+
*/
209+
functionsPlatform?: string
210+
/**
211+
* The architecture bundled functions will execute on. Usually should be `x64`.
212+
* This will be used if user didn't specify `GATSBY_FUNCTIONS_ARCH` environment variable
213+
* or used `-functions-arch` CLI toggle. If none is defined current arch (process.arch) will be used.
214+
*/
215+
functionsArch?: string
204216
}
205217

206218
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }

‎packages/gatsby/src/utils/engines-helpers.ts

+31
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,34 @@ function getCDNObfuscatedPath(path: string): string {
3333
}
3434

3535
export const LmdbOnCdnPath = getCDNObfuscatedPath(`data.mdb`)
36+
37+
export interface IPlatformAndArch {
38+
platform: string
39+
arch: string
40+
}
41+
42+
const currentTarget: IPlatformAndArch = {
43+
platform: process.platform,
44+
arch: process.arch,
45+
}
46+
47+
export function getCurrentPlatformAndTarget(): IPlatformAndArch {
48+
return currentTarget
49+
}
50+
51+
export function getFunctionsTargetPlatformAndTarget(): IPlatformAndArch {
52+
const state = store.getState()
53+
54+
return {
55+
platform:
56+
process.env.GATSBY_FUNCTIONS_PLATFORM ??
57+
state.program.functionsPlatform ??
58+
state.adapter.config.functionsPlatform ??
59+
currentTarget.platform,
60+
arch:
61+
process.env.GATSBY_FUNCTIONS_ARCH ??
62+
state.program.functionsArch ??
63+
state.adapter.config.functionsArch ??
64+
currentTarget.arch,
65+
}
66+
}

‎packages/gatsby/src/utils/validate-engines/index.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
1+
import reporter from "gatsby-cli/lib/reporter"
12
import { WorkerPool } from "gatsby-worker"
3+
import { isEqual } from "lodash"
4+
import type { Span } from "opentracing"
5+
import {
6+
getCurrentPlatformAndTarget,
7+
getFunctionsTargetPlatformAndTarget,
8+
} from "../engines-helpers"
29

3-
export async function validateEngines(directory: string): Promise<void> {
10+
export async function validateEnginesWithActivity(
11+
directory: string,
12+
buildSpan?: Span
13+
): Promise<void> {
14+
if (
15+
!isEqual(
16+
getCurrentPlatformAndTarget(),
17+
getFunctionsTargetPlatformAndTarget()
18+
)
19+
) {
20+
reporter.info(
21+
`Skipping Rendering Engines validation as they are build for different platform and/or architecture`
22+
)
23+
return
24+
}
25+
26+
const validateEnginesActivity = reporter.activityTimer(
27+
`Validating Rendering Engines`,
28+
{
29+
parentSpan: buildSpan,
30+
}
31+
)
32+
validateEnginesActivity.start()
33+
try {
34+
await validateEngines(directory)
35+
} catch (error) {
36+
validateEnginesActivity.panic({ id: `98001`, context: {}, error })
37+
} finally {
38+
validateEnginesActivity.end()
39+
}
40+
}
41+
42+
async function validateEngines(directory: string): Promise<void> {
443
const worker = new WorkerPool<typeof import("./child")>(
544
require.resolve(`./child`),
645
{

0 commit comments

Comments
 (0)
Please sign in to comment.