diff --git a/src/commands/dev/dev.ts b/src/commands/dev/dev.ts index 3c968a25e9b..a51e01d3603 100644 --- a/src/commands/dev/dev.ts +++ b/src/commands/dev/dev.ts @@ -4,7 +4,7 @@ import process from 'process' import { applyMutations } from '@netlify/config' import { OptionValues, Option } from 'commander' -import { BLOBS_CONTEXT_VARIABLE, encodeBlobsContext, getBlobsContext } from '../../lib/blobs/blobs.js' +import { BLOBS_CONTEXT_VARIABLE, encodeBlobsContext, getBlobsContextWithEdgeAccess } 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' @@ -107,7 +107,7 @@ export const dev = async (options: OptionValues, command: BaseCommand) => { env.NETLIFY_DEV = { sources: ['internal'], value: 'true' } - const blobsContext = await getBlobsContext({ + const blobsContext = await getBlobsContextWithEdgeAccess({ debug: options.debug, projectRoot: command.workingDir, siteID: site.id ?? UNLINKED_SITE_MOCK_ID, diff --git a/src/commands/functions/functions-serve.ts b/src/commands/functions/functions-serve.ts index 01d16759fa9..ca5a42c29c8 100644 --- a/src/commands/functions/functions-serve.ts +++ b/src/commands/functions/functions-serve.ts @@ -2,7 +2,7 @@ import { join } from 'path' import { OptionValues } from 'commander' -import { getBlobsContext } from '../../lib/blobs/blobs.js' +import { getBlobsContextWithEdgeAccess } from '../../lib/blobs/blobs.js' import { startFunctionsServer } from '../../lib/functions/server.js' import { printBanner } from '../../utils/banner.js' import { @@ -42,7 +42,7 @@ export const functionsServe = async (options: OptionValues, command: BaseCommand errorMessage: 'Could not acquire configured functions port', }) - const blobsContext = await getBlobsContext({ + const blobsContext = await getBlobsContextWithEdgeAccess({ debug: options.debug, projectRoot: command.workingDir, siteID: site.id ?? UNLINKED_SITE_MOCK_ID, diff --git a/src/commands/serve/serve.ts b/src/commands/serve/serve.ts index e09f8c5dcf4..07ccca020b0 100644 --- a/src/commands/serve/serve.ts +++ b/src/commands/serve/serve.ts @@ -2,7 +2,12 @@ import process from 'process' import { OptionValues } from 'commander' -import { BLOBS_CONTEXT_VARIABLE, encodeBlobsContext, getBlobsContext } from '../../lib/blobs/blobs.js' +import { + BLOBS_CONTEXT_VARIABLE, + encodeBlobsContext, + getBlobsContextWithAPIAccess, + getBlobsContextWithEdgeAccess, +} 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' @@ -92,22 +97,31 @@ export const serve = async (options: OptionValues, command: BaseCommand) => { `${NETLIFYDEVWARN} Changes will not be hot-reloaded, so if you need to rebuild your site you must exit and run 'netlify serve' again`, ) - const blobsContext = await getBlobsContext({ + const blobsOptions = { debug: options.debug, projectRoot: command.workingDir, siteID: site.id ?? UNLINKED_SITE_MOCK_ID, - }) + } - process.env[BLOBS_CONTEXT_VARIABLE] = encodeBlobsContext(blobsContext) + // We start by running a build, so we want a Blobs context with API access, + // which is what build plugins use. + process.env[BLOBS_CONTEXT_VARIABLE] = encodeBlobsContext(await getBlobsContextWithAPIAccess(blobsOptions)) const { configPath: configPathOverride } = await runBuildTimeline({ command, settings, options, + env: {}, }) + // Now we generate a second Blobs context object, this time with edge access + // for runtime access (i.e. from functions and edge functions). + const runtimeBlobsContext = await getBlobsContextWithEdgeAccess(blobsOptions) + + process.env[BLOBS_CONTEXT_VARIABLE] = encodeBlobsContext(runtimeBlobsContext) + const functionsRegistry = await startFunctionsServer({ - blobsContext, + blobsContext: runtimeBlobsContext, command, config, debug: options.debug, @@ -143,6 +157,7 @@ export const serve = async (options: OptionValues, command: BaseCommand) => { const inspectSettings = generateInspectSettings(options.edgeInspect, options.edgeInspectBrk) const url = await startProxyServer({ addonsUrls, + blobsContext: runtimeBlobsContext, command, config, configPath: configPathOverride, diff --git a/src/lib/blobs/blobs.ts b/src/lib/blobs/blobs.ts index 29270846a40..64b989264a6 100644 --- a/src/lib/blobs/blobs.ts +++ b/src/lib/blobs/blobs.ts @@ -7,18 +7,27 @@ import { v4 as uuidv4 } from 'uuid' import { log, NETLIFYDEVLOG } from '../../utils/command-helpers.js' import { getPathInProject } from '../settings.js' -let hasPrintedLocalBlobsNotice = false - -export const BLOBS_CONTEXT_VARIABLE = 'NETLIFY_BLOBS_CONTEXT' - -export interface BlobsContext { +interface BaseBlobsContext { deployID: string - edgeURL: string siteID: string token: string +} + +export interface BlobsContextWithAPIAccess extends BaseBlobsContext { + apiURL: string +} + +export interface BlobsContextWithEdgeAccess extends BaseBlobsContext { + edgeURL: string uncachedEdgeURL: string } +export type BlobsContext = BlobsContextWithAPIAccess | BlobsContextWithEdgeAccess + +let hasPrintedLocalBlobsNotice = false + +export const BLOBS_CONTEXT_VARIABLE = 'NETLIFY_BLOBS_CONTEXT' + const printLocalBlobsNotice = () => { if (hasPrintedLocalBlobsNotice) { return @@ -31,7 +40,12 @@ const printLocalBlobsNotice = () => { ) } -const startBlobsServer = async (debug: boolean, projectRoot: string, token: string) => { +/** + * Starts a local Blobs server on a random port and generates a random token + * for its authentication. + */ +const initializeBlobsServer = async (projectRoot: string, debug: boolean) => { + const token = uuidv4() const directory = path.resolve(projectRoot, getPathInProject(['blobs-serve'])) const server = new BlobsServer({ debug, @@ -42,8 +56,9 @@ const startBlobsServer = async (debug: boolean, projectRoot: string, token: stri token, }) const { port } = await server.start() + const url = `http://localhost:${port}` - return { port } + return { url, token } } interface GetBlobsContextOptions { @@ -52,15 +67,29 @@ interface GetBlobsContextOptions { siteID: string } +/** + * Starts a local Blobs server and returns a context object that lets build + * plugins connect to it. + */ +export const getBlobsContextWithAPIAccess = async ({ debug, projectRoot, siteID }: GetBlobsContextOptions) => { + const { token, url } = await initializeBlobsServer(projectRoot, debug) + const context: BlobsContextWithAPIAccess = { + apiURL: url, + deployID: '0', + siteID, + token, + } + + return context +} + /** * Starts a local Blobs server and returns a context object that lets functions - * connect to it. + * and edge functions connect to it. */ -export const getBlobsContext = async ({ debug, projectRoot, siteID }: GetBlobsContextOptions) => { - const token = uuidv4() - const { port } = await startBlobsServer(debug, projectRoot, token) - const url = `http://localhost:${port}` - const context: BlobsContext = { +export const getBlobsContextWithEdgeAccess = async ({ debug, projectRoot, siteID }: GetBlobsContextOptions) => { + const { token, url } = await initializeBlobsServer(projectRoot, debug) + const context: BlobsContextWithEdgeAccess = { deployID: '0', edgeURL: url, siteID, diff --git a/src/lib/edge-functions/proxy.ts b/src/lib/edge-functions/proxy.ts index d0f70952a3d..061274ec661 100644 --- a/src/lib/edge-functions/proxy.ts +++ b/src/lib/edge-functions/proxy.ts @@ -10,7 +10,7 @@ import BaseCommand from '../../commands/base-command.js' import { $TSFixMe } from '../../commands/types.js' import { NETLIFYDEVERR, chalk, error as printError } from '../../utils/command-helpers.js' import { FeatureFlags, getFeatureFlagsFromSiteInfo } from '../../utils/feature-flags.js' -import { BlobsContext } from '../blobs/blobs.js' +import { BlobsContextWithEdgeAccess } from '../blobs/blobs.js' import { getGeoLocation } from '../geo-location.js' import { getPathInProject } from '../settings.js' import { startSpinner, stopSpinner } from '../spinner.js' @@ -94,7 +94,7 @@ export const initializeProxy = async ({ state, }: { accountId: string - blobsContext: BlobsContext + blobsContext: BlobsContextWithEdgeAccess command: BaseCommand config: $TSFixMe configPath: string diff --git a/src/lib/functions/netlify-function.ts b/src/lib/functions/netlify-function.ts index 3fe6ed5794b..7c9957ea4c8 100644 --- a/src/lib/functions/netlify-function.ts +++ b/src/lib/functions/netlify-function.ts @@ -7,7 +7,7 @@ import semver from 'semver' import { error as errorExit } from '../../utils/command-helpers.js' import { BACKGROUND } from '../../utils/functions/get-functions.js' -import type { BlobsContext } from '../blobs/blobs.js' +import type { BlobsContextWithEdgeAccess } from '../blobs/blobs.js' const TYPESCRIPT_EXTENSIONS = new Set(['.cts', '.mts', '.ts']) const V2_MIN_NODE_VERSION = '18.14.0' @@ -34,7 +34,7 @@ export default class NetlifyFunction { private readonly directory: string private readonly projectRoot: string - private readonly blobsContext: BlobsContext + private readonly blobsContext: BlobsContextWithEdgeAccess private readonly timeoutBackground: number private readonly timeoutSynchronous: number diff --git a/src/utils/proxy-server.ts b/src/utils/proxy-server.ts index 91e7476dec4..5d7749a333e 100644 --- a/src/utils/proxy-server.ts +++ b/src/utils/proxy-server.ts @@ -1,6 +1,6 @@ import BaseCommand from '../commands/base-command.js' import { $TSFixMe, NetlifyOptions } from '../commands/types.js' -import { BlobsContext } from '../lib/blobs/blobs.js' +import { BlobsContextWithEdgeAccess } from '../lib/blobs/blobs.js' import { FunctionsRegistry } from '../lib/functions/registry.js' import { exit, log, NETLIFYDEVERR } from './command-helpers.js' @@ -64,7 +64,7 @@ export const startProxyServer = async ({ }: { accountId: string addonsUrls: $TSFixMe - blobsContext?: BlobsContext + blobsContext?: BlobsContextWithEdgeAccess command: BaseCommand config: NetlifyOptions['config'] // An override for the Netlify config path diff --git a/src/utils/run-build.ts b/src/utils/run-build.ts index c55a6458fe8..a02ee90714b 100644 --- a/src/utils/run-build.ts +++ b/src/utils/run-build.ts @@ -164,11 +164,8 @@ export const runNetlifyBuild = async ({ return { configMutations } } -export const runDevTimeline = (options: Omit[0], 'timeline'>) => - runNetlifyBuild({ ...options, timeline: 'dev' }) +type RunTimelineOptions = Omit[0], 'timeline'> -/** - * @param {Omit[0], 'timeline'>} options - */ -// @ts-expect-error TS(7006) FIXME: Parameter 'options' implicitly has an 'any' type. -export const runBuildTimeline = (options) => runNetlifyBuild({ ...options, timeline: 'build' }) +export const runDevTimeline = (options: RunTimelineOptions) => runNetlifyBuild({ ...options, timeline: 'dev' }) + +export const runBuildTimeline = (options: RunTimelineOptions) => runNetlifyBuild({ ...options, timeline: 'build' })