Skip to content

Commit 07e567c

Browse files
authoredOct 21, 2024··
fix: various fixes to extension dev/test flow (#5885)
* fix: pass pluginsEnv to integration build hooks build step For context: `@netlify/build` automatically builds integrations when `context` is set to `dev`. This PR targets that functionality. Without this change, `buildSite`'s programmatic interface doesn't pass programatically defined `env` variables into the task that builds ~integrations~ extensions' build hooks. ```ts import buildSite from "@netlify/build"; await buildSite({ env: { // This is passed to the build plugin when it's executed, but is not // passed to the task that builds the plugin. SOME_ENV_VAR: "1", }, }); ``` I don't know if `pluginsEnv` or `childEnv` is the more appropriate value to pass here, but `pluginsEnv` seems more consistent with how build treats integrations as plugins, so I chose to use it. Two total digressions I thought of while writing this: - We should probably be passing `extendEnv: false` to the `execa` invocation that builds the integration, but I'm not sure if that would break something at this point, so I'm leaving it as is for now. - I sort of hate that `@netlify/build` builds the integration automatically. It's challenging to use it in tests: every test rebuilds the integration (which is expensive) and you have to make sure that no two tests that auto-build the integration build it concurrently, otherwise you might end up with test flakes when one test writes to the built tarball while another test is installing from it. I don't yet know what the best way to solve this problem is, so I'm punting on solving it for now. * fix: resolve integration dev path relative to buildDir Currently, programmatic executions of `buildSite` resolve a development integration's path (specified via `netlify.toml#integrations[].dev.path`) relative to the current working directory. I don't think this makes any sense, honestly. I find this undocumented behavior unintuitive; if I specify a relative path in a `netlify.toml` I would expect it to be resolved relative to the `netlify.toml`. This is _technically_ a breaking change, but in practice I really doubt any users are testing extension build hooks, it's unlikely to break anything for the platform team (_maybe_ a few tests, but we can update the paths in those tests if it does). I'm tempted to remove `testOpts.cwd` here because I can't find anywhere that we use it internally and it's unintuitive, too, but I'm leaving it in for now as an easy escape hatch in case this change breaks any tests Composable Platform has. * fix: return integration build plugin path in development Currently, when running a build in `context=dev` mode for a site that installs an extension never actually installs the extension's build plugin, if it has one. That's because the integration package resolver doesn't return the path to the built package (tarball), so build never picks it up as a plugin. This changeset fixes that issue. * refactor: print stack on failure to build integration in dev mode * style: run prettier * fix: remove duplicative integration install step In 0cfe6d8 I introduced a duplicative integration install step when `context=dev`, not realizing that we have a special case for resolving integrations in dev mode: https://github.com/netlify/build/blob/main/packages/build/src/plugins/resolve.js#L189-L195 Returning the tarball location meant we were installing the integration's packed tarball and then also installing from the pre-pack build directory. This changeset removes that duplication. It's... weird that we don't just return the location of a packed npm package (tarball) and instead have a special case that points at the pre-pack build artifact directory. This sort of special-cased action at a distance makes it super hard to understand how this works. I'm going to circle back on de-confusing this sometime this quarter when I make the extension build artifact path configurable, which will solve a lot of testing pain points we currently have. The failing test I also fix in this changeset was never realistic and didn't exercise some of the code paths it was supposed to put under test, so I've updated that test fixture to be realistic.
1 parent 447f98f commit 07e567c

File tree

14 files changed

+76
-28
lines changed

14 files changed

+76
-28
lines changed
 

‎packages/build/src/core/build.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,11 @@ const initAndRunBuild = async function ({
449449
edgeFunctionsBootstrapURL,
450450
eventHandlers,
451451
}) {
452+
const pluginsEnv = {
453+
...childEnv,
454+
...getBlobsEnvironmentContext({ api, deployId: deployId, siteId: siteInfo?.id, token }),
455+
}
456+
452457
const { pluginsOptions: pluginsOptionsA, timers: timersA } = await getPluginsOptions({
453458
pluginsOptions,
454459
netlifyConfig,
@@ -469,13 +474,9 @@ const initAndRunBuild = async function ({
469474
integrations,
470475
context,
471476
systemLog,
477+
pluginsEnv,
472478
})
473479

474-
const pluginsEnv = {
475-
...childEnv,
476-
...getBlobsEnvironmentContext({ api, deployId: deployId, siteId: siteInfo?.id, token }),
477-
}
478-
479480
if (pluginsOptionsA?.length) {
480481
const buildPlugins = {}
481482
for (const plugin of pluginsOptionsA) {

‎packages/build/src/install/missing.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export const installIntegrationPlugins = async function ({
3434
logs,
3535
context,
3636
testOpts,
37+
pluginsEnv,
38+
buildDir,
3739
}) {
3840
const integrationsToBuild = integrations.filter(
3941
(integration) => typeof integration.dev !== 'undefined' && context === 'dev',
@@ -46,7 +48,11 @@ export const installIntegrationPlugins = async function ({
4648
)
4749
}
4850
const packages = (
49-
await Promise.all(integrations.map((integration) => getIntegrationPackage({ integration, context, testOpts })))
51+
await Promise.all(
52+
integrations.map((integration) =>
53+
getIntegrationPackage({ integration, context, testOpts, buildDir, pluginsEnv }),
54+
),
55+
)
5056
).filter(Boolean)
5157
logInstallIntegrations(
5258
logs,
@@ -64,25 +70,32 @@ export const installIntegrationPlugins = async function ({
6470
await addExactDependencies({ packageRoot: autoPluginsDir, isLocal: mode !== 'buildbot', packages })
6571
}
6672

67-
const getIntegrationPackage = async function ({ integration: { version, dev }, context, testOpts = {} }) {
73+
const getIntegrationPackage = async function ({
74+
integration: { version, dev },
75+
context,
76+
testOpts = {},
77+
buildDir,
78+
pluginsEnv,
79+
}) {
6880
if (typeof version !== 'undefined') {
6981
return `${version}/packages/buildhooks.tgz`
7082
}
7183

7284
if (typeof dev !== 'undefined' && context === 'dev') {
7385
const { path } = dev
7486

75-
const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(path)
87+
const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(buildDir, path)
88+
7689
try {
77-
const res = await execa('npm', ['run', 'build'], { cwd: integrationDir })
90+
const res = await execa('npm', ['run', 'build'], { cwd: integrationDir, env: pluginsEnv })
7891

7992
// This is horrible and hacky, but `npm run build` will
8093
// return status code 0 even if it fails
8194
if (!res.stdout.includes('Build complete!')) {
8295
throw new Error(res.stdout)
8396
}
8497
} catch (e) {
85-
throw new Error(`Failed to build integration`)
98+
throw new Error(`Failed to build integration. Error:\n\n${e.stack}`)
8699
}
87100

88101
return undefined

‎packages/build/src/plugins/options.ts

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const tGetPluginsOptions = async function ({
3232
integrations,
3333
context,
3434
systemLog,
35+
pluginsEnv,
3536
}) {
3637
const pluginsOptionsA = await resolvePluginsPath({
3738
pluginsOptions,
@@ -51,6 +52,7 @@ const tGetPluginsOptions = async function ({
5152
integrations,
5253
context,
5354
systemLog,
55+
pluginsEnv,
5456
})
5557
const pluginsOptionsB = await Promise.all(
5658
pluginsOptionsA.map((pluginOptions) => loadPluginFiles({ pluginOptions, debug })),

‎packages/build/src/plugins/resolve.js

+23-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const resolvePluginsPath = async function ({
3333
integrations,
3434
context,
3535
systemLog,
36+
pluginsEnv,
3637
}) {
3738
const autoPluginsDir = getAutoPluginsDir(buildDir, packagePath)
3839
const pluginsOptionsA = await Promise.all(
@@ -77,6 +78,7 @@ export const resolvePluginsPath = async function ({
7778
buildDir,
7879
context,
7980
testOpts,
81+
pluginsEnv,
8082
})
8183

8284
return [...pluginsOptionsE, ...integrationPluginOptions]
@@ -164,9 +166,27 @@ const handleMissingPlugins = async function ({ pluginsOptions, autoPluginsDir, m
164166
return Promise.all(pluginsOptions.map((pluginOptions) => resolveMissingPluginPath({ pluginOptions, autoPluginsDir })))
165167
}
166168

167-
const handleIntegrations = async function ({ integrations, autoPluginsDir, mode, logs, buildDir, context, testOpts }) {
169+
const handleIntegrations = async function ({
170+
integrations,
171+
autoPluginsDir,
172+
mode,
173+
logs,
174+
buildDir,
175+
context,
176+
testOpts,
177+
pluginsEnv,
178+
}) {
168179
const toInstall = integrations.filter((integration) => integration.has_build)
169-
await installIntegrationPlugins({ integrations: toInstall, autoPluginsDir, mode, logs, context, testOpts })
180+
await installIntegrationPlugins({
181+
integrations: toInstall,
182+
autoPluginsDir,
183+
mode,
184+
logs,
185+
context,
186+
testOpts,
187+
buildDir,
188+
pluginsEnv,
189+
})
170190

171191
if (toInstall.length === 0) {
172192
return []
@@ -188,7 +208,7 @@ const handleIntegrations = async function ({ integrations, autoPluginsDir, mode,
188208
const resolveIntegration = async function ({ integration, autoPluginsDir, buildDir, context, testOpts }) {
189209
if (typeof integration.dev !== 'undefined' && context === 'dev') {
190210
const { path } = integration.dev
191-
const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(path)
211+
const integrationDir = testOpts.cwd ? resolve(testOpts.cwd, path) : resolve(buildDir, path)
192212
const pluginPath = await resolvePath(`${integrationDir}/.ntli/build`, buildDir)
193213

194214
return { pluginPath, packageName: `${integration.slug}`, isIntegration: true, integration, loadedFrom: 'local' }

‎packages/build/tests/install/fixtures/local_missing_integration/integration/.ntli/build/.gitkeep

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const onPreBuild = function () {
2+
console.log("Hello world");
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name: abc-integration
2+
inputs: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"main": "index.js",
3+
"type": "module",
4+
"version": "0.0.0",
5+
"name": "abc-integration",
6+
"dependencies": {}
7+
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
name: test
1+
name: abc-integration
22
inputs: []

‎packages/build/tests/install/fixtures/local_missing_integration/integration/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "plugin_deps_plugin",
2+
"name": "abc-integration",
33
"version": "0.0.1",
44
"type": "module",
55
"scripts": {

‎packages/build/tests/install/fixtures/local_missing_integration/netlify.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[[integrations]]
2-
name = "abc-integration"
2+
name = "test"
33

44
[integrations.dev]
55
path = "./integration"

‎packages/build/tests/install/snapshots/tests.js.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,6 @@ Generated by [AVA](https://avajs.dev).
10511051
debug: true␊
10521052
repositoryRoot: packages/build/tests/install/fixtures/local_missing_integration␊
10531053
testOpts:␊
1054-
cwd: ./tests/install/fixtures/local_missing_integration/␊
10551054
pluginsListUrl: test␊
10561055
silentLingeringProcesses: true␊
10571056
@@ -1070,10 +1069,18 @@ Generated by [AVA](https://avajs.dev).
10701069
dev␊
10711070
10721071
> Building integrations␊
1073-
- abc-integration from ./integration␊
1072+
- test from ./integration␊
10741073
10751074
> Loading integrations␊
1076-
- abc-integration␊
1075+
- test␊
1076+
1077+
test (onPreBuild event) ␊
1078+
────────────────────────────────────────────────────────────────␊
1079+
1080+
Hello world␊
1081+
1082+
(test onPreBuild completed in 1ms)␊
1083+
Build step duration: test onPreBuild completed in 1ms␊
10771084
10781085
Netlify Build Complete ␊
10791086
────────────────────────────────────────────────────────────────␊
Binary file not shown.

‎packages/build/tests/install/tests.js

+2-9
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,8 @@ test('Install local plugin dependencies: missing plugin in netlify.toml', async
153153
t.snapshot(normalizeOutput(output))
154154
})
155155

156-
test('In integration dev mode, install local plugins and install the integration when forcing build', async (t) => {
157-
const output = await new Fixture('./fixtures/local_missing_integration')
158-
.withFlags({
159-
context: 'dev',
160-
testOpts: {
161-
cwd: './tests/install/fixtures/local_missing_integration/',
162-
},
163-
})
164-
.runWithBuild()
156+
test.only('In integration dev mode, install local plugins and install the integration when forcing build', async (t) => {
157+
const output = await new Fixture('./fixtures/local_missing_integration').withFlags({ context: 'dev' }).runWithBuild()
165158

166159
t.snapshot(normalizeOutput(output))
167160
})

0 commit comments

Comments
 (0)
Please sign in to comment.