diff --git a/src/commands/dev/dev.ts b/src/commands/dev/dev.ts index 50f1da5a745..b2ae872e4b1 100644 --- a/src/commands/dev/dev.ts +++ b/src/commands/dev/dev.ts @@ -2,7 +2,7 @@ import process from 'process' import { OptionValues, Option } from 'commander' -import { getBlobsContext } from '../../lib/blobs/blobs.js' +import { BLOBS_CONTEXT_VARIABLE, encodeBlobsContext, getBlobsContext } from '../../lib/blobs/blobs.js' import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.js' import { startFunctionsServer } from '../../lib/functions/server.js' import { printBanner } from '../../utils/banner.js' @@ -105,6 +105,14 @@ export const dev = async (options: OptionValues, command: BaseCommand) => { env.NETLIFY_DEV = { sources: ['internal'], value: 'true' } + const blobsContext = await getBlobsContext({ + debug: options.debug, + projectRoot: command.workingDir, + siteID: site.id ?? 'unknown-site-id', + }) + + env[BLOBS_CONTEXT_VARIABLE] = { sources: ['internal'], value: encodeBlobsContext(blobsContext) } + if (!options.offline && siteInfo.use_envelope) { env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo }) log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`) @@ -155,12 +163,6 @@ export const dev = async (options: OptionValues, command: BaseCommand) => { }, }) - const blobsContext = await getBlobsContext({ - debug: options.debug, - projectRoot: command.workingDir, - siteID: site.id ?? 'unknown-site-id', - }) - const functionsRegistry = await startFunctionsServer({ api, blobsContext, diff --git a/src/commands/serve/serve.ts b/src/commands/serve/serve.ts index 2c3a5c1095c..c474b342db4 100644 --- a/src/commands/serve/serve.ts +++ b/src/commands/serve/serve.ts @@ -2,7 +2,7 @@ import process from 'process' import { OptionValues } from 'commander' -import { getBlobsContext } from '../../lib/blobs/blobs.js' +import { BLOBS_CONTEXT_VARIABLE, encodeBlobsContext, getBlobsContext } from '../../lib/blobs/blobs.js' import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.js' import { startFunctionsServer } from '../../lib/functions/server.js' import { printBanner } from '../../utils/banner.js' @@ -100,6 +100,8 @@ export const serve = async (options: OptionValues, command: BaseCommand) => { siteID: site.id ?? 'unknown-site-id', }) + process.env[BLOBS_CONTEXT_VARIABLE] = encodeBlobsContext(blobsContext) + const functionsRegistry = await startFunctionsServer({ api, blobsContext, diff --git a/src/lib/blobs/blobs.ts b/src/lib/blobs/blobs.ts index 94e9bdf8f1a..54168eeff4f 100644 --- a/src/lib/blobs/blobs.ts +++ b/src/lib/blobs/blobs.ts @@ -1,3 +1,4 @@ +import { Buffer } from 'buffer' import path from 'path' import { BlobsServer } from '@netlify/blobs' @@ -8,6 +9,8 @@ import { getPathInProject } from '../settings.js' let hasPrintedLocalBlobsNotice = false +export const BLOBS_CONTEXT_VARIABLE = 'NETLIFY_BLOBS_CONTEXT' + export interface BlobsContext { deployID: string edgeURL: string @@ -64,3 +67,9 @@ export const getBlobsContext = async ({ debug, projectRoot, siteID }: GetBlobsCo return context } + +/** + * Returns a Base-64, JSON-encoded representation of the Blobs context. This is + * the format that the `@netlify/blobs` package expects to find the context in. + */ +export const encodeBlobsContext = (context: BlobsContext) => Buffer.from(JSON.stringify(context)).toString('base64') diff --git a/src/lib/functions/runtimes/js/index.ts b/src/lib/functions/runtimes/js/index.ts index c35ea04b057..fd79efb6480 100644 --- a/src/lib/functions/runtimes/js/index.ts +++ b/src/lib/functions/runtimes/js/index.ts @@ -5,6 +5,8 @@ import { Worker } from 'worker_threads' import lambdaLocal from 'lambda-local' +import { BLOBS_CONTEXT_VARIABLE } from '../../../blobs/blobs.js' + import detectNetlifyLambdaBuilder from './builders/netlify-lambda.js' import detectZisiBuilder, { parseFunctionForMetadata } from './builders/zisi.js' import { SECONDS_TO_MILLISECONDS } from './constants.js' @@ -104,6 +106,14 @@ export const invokeFunctionDirectly = async ({ context, event, func, timeout }) const lambdaPath = func.buildData?.buildPath ?? func.mainFile const result = await lambdaLocal.execute({ clientContext: JSON.stringify(context), + environment: { + // We've set the Blobs context on the parent process, which means it will + // be available to the Lambda. This would be inconsistent with production + // where only V2 functions get the context injected. To fix it, unset the + // context variable before invoking the function. + // This has the side-effect of also removing the variable from `process.env`. + [BLOBS_CONTEXT_VARIABLE]: undefined, + }, event, lambdaPath, timeoutMs: timeout * SECONDS_TO_MILLISECONDS, diff --git a/tests/integration/commands/dev/dev.config.test.js b/tests/integration/commands/dev/dev.config.test.js index 8463789251e..cccf367d1ad 100644 --- a/tests/integration/commands/dev/dev.config.test.js +++ b/tests/integration/commands/dev/dev.config.test.js @@ -1,4 +1,4 @@ -// Handlers are meant to be async outside tests +import { Buffer } from 'buffer' import { version } from 'process' import FormData from 'form-data' @@ -143,7 +143,7 @@ describe.concurrent('commands/dev/config', () => { }) }) - test('should provide CLI version in env var', async (t) => { + test('should provide environment variables to framework server', async (t) => { await withSiteBuilder(t, async (builder) => { const port = await getPort() @@ -154,6 +154,7 @@ describe.concurrent('commands/dev/config', () => { http.createServer((req, res) => { res.write(JSON.stringify({ + NETLIFY_BLOBS_CONTEXT: process.env.NETLIFY_BLOBS_CONTEXT, NETLIFY_CLI_VERSION: process.env.NETLIFY_CLI_VERSION, })) res.end() @@ -174,7 +175,17 @@ describe.concurrent('commands/dev/config', () => { await withDevServer({ cwd: builder.directory }, async (server) => { const resp = await fetch(server.url) - const { NETLIFY_CLI_VERSION } = await resp.json() + const { NETLIFY_BLOBS_CONTEXT, NETLIFY_CLI_VERSION } = await resp.json() + + t.expect(NETLIFY_BLOBS_CONTEXT).toBeTypeOf('string') + + const { deployID, edgeURL, siteID, token } = JSON.parse(Buffer.from(NETLIFY_BLOBS_CONTEXT, 'base64').toString()) + + t.expect(deployID).toBe('0') + t.expect(edgeURL.startsWith('http://localhost:')).toBeTruthy() + t.expect(siteID).toBeTypeOf('string') + t.expect(token).toBeTypeOf('string') + t.expect(NETLIFY_CLI_VERSION).toMatch(/\d+\.\d+\.\d+/) }) })