Skip to content

Commit edaf016

Browse files
gatsbybotpieh
andauthored
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 <[email protected]>
1 parent b0960f5 commit edaf016

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`, {

0 commit comments

Comments
 (0)