Skip to content

Commit e037fbf

Browse files
authoredJun 6, 2024··
feat: populate Blobs context in build plugins (#5571)
* feat: populate Blobs context in build plugins * chore: fix test * chore: update test * chore: fix test * chore: fix formatting * chore: fix test * chore: update test * chore: update tests
1 parent e4e9787 commit e037fbf

File tree

13 files changed

+130
-140
lines changed

13 files changed

+130
-140
lines changed
 

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { reportStatuses } from '../status/report.js'
1414
import { getDevSteps, getSteps } from '../steps/get.js'
1515
import { runSteps } from '../steps/run_steps.js'
1616
import { initTimers, measureDuration } from '../time/main.js'
17+
import { getBlobsEnvironmentContext } from '../utils/blobs.js'
1718

1819
import { getConfigOpts, loadConfig } from './config.js'
1920
import { getConstants } from './constants.js'
@@ -197,6 +198,7 @@ const tExecBuild = async function ({
197198
dry,
198199
mode,
199200
api,
201+
token,
200202
errorMonitor,
201203
deployId,
202204
errorParams,
@@ -258,6 +260,7 @@ export const runAndReportBuild = async function ({
258260
dry,
259261
mode,
260262
api,
263+
token,
261264
errorMonitor,
262265
deployId,
263266
errorParams,
@@ -310,6 +313,7 @@ export const runAndReportBuild = async function ({
310313
dry,
311314
mode,
312315
api,
316+
token,
313317
errorMonitor,
314318
deployId,
315319
errorParams,
@@ -414,6 +418,7 @@ const initAndRunBuild = async function ({
414418
dry,
415419
mode,
416420
api,
421+
token,
417422
errorMonitor,
418423
deployId,
419424
errorParams,
@@ -459,6 +464,10 @@ const initAndRunBuild = async function ({
459464
systemLog,
460465
})
461466

467+
const pluginsEnv = featureFlags.build_inject_blobs_context
468+
? { ...childEnv, ...getBlobsEnvironmentContext({ api, deployId: deployId, siteId: siteInfo?.id, token }) }
469+
: childEnv
470+
462471
if (pluginsOptionsA?.length) {
463472
const buildPlugins = {}
464473
for (const plugin of pluginsOptionsA) {
@@ -475,7 +484,7 @@ const initAndRunBuild = async function ({
475484
const { childProcesses, timers: timersB } = await startPlugins({
476485
pluginsOptions: pluginsOptionsA,
477486
buildDir,
478-
childEnv,
487+
childEnv: pluginsEnv,
479488
logs,
480489
debug,
481490
timers: timersA,

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

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const getFeatureFlag = function (name: string): FeatureFlags {
1515

1616
// Default values for feature flags
1717
export const DEFAULT_FEATURE_FLAGS: FeatureFlags = {
18+
build_inject_blobs_context: false,
1819
buildbot_zisi_trace_nft: false,
1920
buildbot_zisi_esbuild_parser: false,
2021
buildbot_zisi_system_log: false,

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { removeFalsy } from '../utils/remove_falsy.js'
66
import { DEFAULT_FEATURE_FLAGS } from './feature_flags.js'
77
import type { BuildFlags, Mode, TestOptions } from './types.js'
88

9+
export const DEFAULT_API_HOST = 'api.netlify.com'
910
const REQUIRE_MODE: Mode = 'require'
1011
const DEFAULT_EDGE_FUNCTIONS_DIST = '.netlify/edge-functions-dist/'
1112
const DEFAULT_FUNCTIONS_DIST = '.netlify/functions/'
@@ -91,7 +92,7 @@ const getDefaultFlags = function ({ env: envOpt = {} }, combinedEnv) {
9192
bugsnagKey: combinedEnv.BUGSNAG_KEY,
9293
sendStatus: false,
9394
saveConfig: false,
94-
apiHost: 'api.netlify.com',
95+
apiHost: DEFAULT_API_HOST,
9596
testOpts: {},
9697
featureFlags: DEFAULT_FEATURE_FLAGS,
9798
statsd: { port: DEFAULT_STATSD_PORT },

‎packages/build/src/plugins_core/blobs_upload/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getDeployStore } from '@netlify/blobs'
44
import pMap from 'p-map'
55
import semver from 'semver'
66

7+
import { DEFAULT_API_HOST } from '../../core/normalize_flags.js'
78
import { log, logError } from '../../log/logger.js'
89
import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js'
910
import { type CoreStep, type CoreStepCondition, type CoreStepFunction } from '../types.js'
@@ -22,7 +23,7 @@ const coreStep: CoreStepFunction = async function ({
2223
return {}
2324
}
2425
// for cli deploys with `netlify deploy --build` the `NETLIFY_API_HOST` is undefined
25-
const apiHost = NETLIFY_API_HOST || 'api.netlify.com'
26+
const apiHost = NETLIFY_API_HOST || DEFAULT_API_HOST
2627

2728
const storeOpts: Parameters<typeof getDeployStore>[0] = {
2829
siteID: SITE_ID,

‎packages/build/src/utils/blobs.ts

+36
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import path from 'node:path'
33

44
import { fdir } from 'fdir'
55

6+
import { DEFAULT_API_HOST } from '../core/normalize_flags.js'
7+
68
const LEGACY_BLOBS_PATH = '.netlify/blobs/deploy'
79
const DEPLOY_CONFIG_BLOBS_PATH = '.netlify/deploy/v1/blobs/deploy'
810

@@ -12,6 +14,40 @@ export const getBlobsDirs = (buildDir: string, packagePath?: string) => [
1214
path.resolve(buildDir, packagePath || '', LEGACY_BLOBS_PATH),
1315
]
1416

17+
interface EnvironmentContext {
18+
api?: {
19+
host: string
20+
scheme: string
21+
}
22+
deployId?: string
23+
siteId?: string
24+
token?: string
25+
}
26+
27+
// TODO: Move this work to a method exported by `@netlify/blobs`.
28+
export const getBlobsEnvironmentContext = ({
29+
api = { host: DEFAULT_API_HOST, scheme: 'https' },
30+
deployId,
31+
siteId,
32+
token,
33+
}: EnvironmentContext) => {
34+
if (!deployId || !siteId || !token) {
35+
return {}
36+
}
37+
38+
const payload = {
39+
apiURL: `${api.scheme}://${api.host}`,
40+
deployID: deployId,
41+
siteID: siteId,
42+
token,
43+
}
44+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64')
45+
46+
return {
47+
NETLIFY_BLOBS_CONTEXT: encodedPayload,
48+
}
49+
}
50+
1551
/**
1652
* Detect if there are any blobs to upload, and if so, what directory they're
1753
* in and whether that directory is the legacy `.netlify/blobs` path or the

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

-129
Original file line numberDiff line numberDiff line change
@@ -598,73 +598,6 @@ Generated by [AVA](https://avajs.dev).
598598
(Netlify Build completed in 1ms)␊
599599
Build step duration: Netlify Build completed in 1ms`
600600

601-
## Functions: install dependencies handles errors
602-
603-
> Snapshot 1
604-
605-
`␊
606-
Netlify Build ␊
607-
────────────────────────────────────────────────────────────────␊
608-
609-
> Version␊
610-
@netlify/build 1.0.0␊
611-
612-
> Flags␊
613-
debug: true␊
614-
repositoryRoot: packages/build/tests/install/fixtures/functions_error␊
615-
testOpts:␊
616-
pluginsListUrl: test␊
617-
silentLingeringProcesses: true␊
618-
619-
> Current directory␊
620-
packages/build/tests/install/fixtures/functions_error␊
621-
622-
> Config file␊
623-
packages/build/tests/install/fixtures/functions_error/netlify.toml␊
624-
625-
> Resolved config␊
626-
build:␊
627-
publish: packages/build/tests/install/fixtures/functions_error␊
628-
publishOrigin: default␊
629-
functionsDirectory: packages/build/tests/install/fixtures/functions_error/functions␊
630-
plugins:␊
631-
- inputs: {}␊
632-
origin: config␊
633-
package: '@netlify/plugin-functions-install-core'␊
634-
635-
> Context␊
636-
production␊
637-
638-
@netlify/plugin-functions-install-core (onPreBuild event) ␊
639-
────────────────────────────────────────────────────────────────␊
640-
641-
Installing functions dependencies␊
642-
643-
Dependencies installation error ␊
644-
────────────────────────────────────────────────────────────────␊
645-
646-
Error message␊
647-
Error while installing dependencies in packages/build/tests/install/fixtures/functions_error/functions␊
648-
npm ERR! code ENOVERSIONS␊
649-
npm ERR! No versions available for math-avg-does-not-exist␊
650-
651-
Plugin details␊
652-
Package: @netlify/plugin-functions-install-core␊
653-
Version: 1.0.0␊
654-
Repository: git+https://github.com/netlify/build.git␊
655-
npm link: https://www.npmjs.com/package/@netlify/build␊
656-
Report issues: https://github.com/netlify/build/issues␊
657-
658-
Resolved config␊
659-
build:␊
660-
publish: packages/build/tests/install/fixtures/functions_error␊
661-
publishOrigin: default␊
662-
functionsDirectory: packages/build/tests/install/fixtures/functions_error/functions␊
663-
plugins:␊
664-
- inputs: {}␊
665-
origin: config␊
666-
package: '@netlify/plugin-functions-install-core'`
667-
668601
## Install local plugin dependencies: with npm
669602

670603
> Snapshot 1
@@ -846,68 +779,6 @@ Generated by [AVA](https://avajs.dev).
846779
(Netlify Build completed in 1ms)␊
847780
Build step duration: Netlify Build completed in 1ms`
848781

849-
## Install local plugin dependencies: propagate errors
850-
851-
> Snapshot 1
852-
853-
`␊
854-
Netlify Build ␊
855-
────────────────────────────────────────────────────────────────␊
856-
857-
> Version␊
858-
@netlify/build 1.0.0␊
859-
860-
> Flags␊
861-
debug: true␊
862-
repositoryRoot: packages/build/tests/install/fixtures/error␊
863-
testOpts:␊
864-
pluginsListUrl: test␊
865-
silentLingeringProcesses: true␊
866-
867-
> Current directory␊
868-
packages/build/tests/install/fixtures/error␊
869-
870-
> Config file␊
871-
packages/build/tests/install/fixtures/error/netlify.toml␊
872-
873-
> Resolved config␊
874-
build:␊
875-
publish: packages/build/tests/install/fixtures/error␊
876-
publishOrigin: default␊
877-
plugins:␊
878-
- inputs: {}␊
879-
origin: config␊
880-
package: '@netlify/plugin-local-install-core'␊
881-
- inputs: {}␊
882-
origin: config␊
883-
package: ./plugin/main.js␊
884-
885-
> Context␊
886-
production␊
887-
888-
> Installing local plugins dependencies␊
889-
- ./plugin/main.js␊
890-
891-
Dependencies installation error ␊
892-
────────────────────────────────────────────────────────────────␊
893-
894-
Error message␊
895-
Error while installing dependencies in packages/build/tests/install/fixtures/error/plugin␊
896-
npm ERR! code ENOVERSIONS␊
897-
npm ERR! No versions available for this-dependency-does-not-exist␊
898-
899-
Resolved config␊
900-
build:␊
901-
publish: packages/build/tests/install/fixtures/error␊
902-
publishOrigin: default␊
903-
plugins:␊
904-
- inputs: {}␊
905-
origin: config␊
906-
package: '@netlify/plugin-local-install-core'␊
907-
- inputs: {}␊
908-
origin: config␊
909-
package: ./plugin/main.js`
910-
911782
## Install local plugin dependencies: already installed
912783

913784
> Snapshot 1
-257 Bytes
Binary file not shown.

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

+18-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { join } from 'path'
12
import { fileURLToPath } from 'url'
23

34
import { Fixture, normalizeOutput, removeDir } from '@netlify/testing'
@@ -11,18 +12,23 @@ const FIXTURES_DIR = fileURLToPath(new URL('fixtures', import.meta.url))
1112
// - specific directories are removed before/after test
1213
// TODO: once we have a test runner that supports before and after this would be way nicer to read to remove dirs there
1314

14-
const runInstallFixture = async (t, fixtureName, dirs = [], flags = {}, binary = false) => {
15+
const runInstallFixture = async (t, fixtureName, dirs = [], flags = {}, binary = false, useSnapshot = true) => {
1516
await removeDir(dirs)
1617
try {
1718
const fixture = new Fixture(`./fixtures/${fixtureName}`).withFlags(flags)
1819
const result = binary ? await fixture.runBuildBinary().then(({ output }) => output) : await fixture.runWithBuild()
1920

20-
t.snapshot(normalizeOutput(result))
21+
if (useSnapshot) {
22+
t.snapshot(normalizeOutput(result))
23+
}
24+
2125
await Promise.all(
2226
dirs.map(async (dir) => {
2327
t.true(await pathExists(dir))
2428
}),
2529
)
30+
31+
return { fixture, result }
2632
} finally {
2733
await removeDir(dirs)
2834
}
@@ -95,7 +101,10 @@ test('Functions: does not print warnings when dependency was local', async (t) =
95101
})
96102

97103
test('Functions: install dependencies handles errors', async (t) => {
98-
await runInstallFixture(t, 'functions_error')
104+
const { fixture, result } = await runInstallFixture(t, 'functions_error', [], {}, false, false)
105+
const functionsPath = join(fixture.repositoryRoot, 'functions')
106+
107+
t.true(result.includes(`Error while installing dependencies in ${functionsPath}`))
99108
})
100109

101110
test('Install local plugin dependencies: with npm', async (t) => {
@@ -114,8 +123,12 @@ test('Install local plugin dependencies: with yarn in CI', async (t) => {
114123
})
115124

116125
test('Install local plugin dependencies: propagate errors', async (t) => {
117-
const output = await new Fixture('./fixtures/error').runWithBuild()
118-
t.snapshot(normalizeOutput(output))
126+
const fixture = new Fixture('./fixtures/error')
127+
const { success, output } = await fixture.runWithBuildAndIntrospect()
128+
const pluginPath = join(fixture.repositoryRoot, 'plugin')
129+
130+
t.false(success)
131+
t.true(output.includes(`Error while installing dependencies in ${pluginPath}`))
119132
})
120133

121134
test('Install local plugin dependencies: already installed', async (t) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
name: test
2+
inputs: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[[plugins]]
2+
package = "./plugin"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { version as nodeVersion } from "process"
2+
3+
import { getDeployStore } from '@netlify/blobs'
4+
import semver from 'semver'
5+
6+
export const onPreBuild = async function ({netlifyConfig}) {
7+
const storeOptions = {}
8+
9+
if (semver.lt(nodeVersion, '18.0.0')) {
10+
const nodeFetch = await import('node-fetch')
11+
storeOptions.fetch = nodeFetch.default
12+
}
13+
14+
const store = getDeployStore(storeOptions)
15+
const value = await store.get("my-key")
16+
17+
netlifyConfig.build.command = `echo "${value}"`
18+
}

‎packages/build/tests/plugins/tests.js

+37-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import * as fs from 'fs/promises'
22
import { platform } from 'process'
33
import { fileURLToPath } from 'url'
44

5-
import { Fixture, normalizeOutput, removeDir } from '@netlify/testing'
5+
import { Fixture, normalizeOutput, removeDir, startServer } from '@netlify/testing'
66
import test from 'ava'
7+
import getPort from 'get-port'
78
import tmp, { tmpName } from 'tmp-promise'
89

910
import { DEFAULT_FEATURE_FLAGS } from '../../lib/core/feature_flags.js'
@@ -366,3 +367,38 @@ test('Plugin errors that occur during the loading phase are piped to system logs
366367

367368
t.snapshot(normalizeOutput(output))
368369
})
370+
371+
test('Plugins have a pre-populated Blobs context', async (t) => {
372+
const serverPort = await getPort()
373+
const deployId = 'deploy123'
374+
const siteId = 'site321'
375+
const token = 'some-token'
376+
const { scheme, host, stopServer } = await startServer(
377+
[
378+
{
379+
response: { url: `http://localhost:${serverPort}/some-signed-url` },
380+
path: `/api/v1/blobs/${siteId}/deploy:${deployId}/my-key`,
381+
},
382+
{
383+
response: 'Hello there',
384+
path: `/some-signed-url`,
385+
},
386+
],
387+
serverPort,
388+
)
389+
390+
const { netlifyConfig } = await new Fixture('./fixtures/blobs_read')
391+
.withFlags({
392+
apiHost: host,
393+
deployId,
394+
featureFlags: { build_inject_blobs_context: true },
395+
testOpts: { scheme },
396+
siteId,
397+
token,
398+
})
399+
.runWithBuildAndIntrospect()
400+
401+
await stopServer()
402+
403+
t.is(netlifyConfig.build.command, `echo ""Hello there""`)
404+
})

‎packages/testing/src/server.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ const setTimeoutPromise = promisify(setTimeout)
2626
// response: json payload response (defaults to {})
2727
// status: http status code (defaults to 200)
2828
// wait: number used to induce a certain time delay in milliseconds in the response (defaults to undefined)
29-
export const startServer = async (handler: ServerHandler) => {
29+
export const startServer = async (handler: ServerHandler, port = 0) => {
3030
const handlers = Array.isArray(handler) ? handler : [handler]
3131
const requests: Request[] = []
3232

3333
const server = createServer((req, res) => requestHandler(req, res, requests, handlers))
34-
await promisify(server.listen.bind(server))(0)
34+
await promisify(server.listen.bind(server))(port)
3535

3636
const host = getHost(server)
3737

0 commit comments

Comments
 (0)
Please sign in to comment.