From 087f7dd47e1158e8955c365baa2d8a51fac9056e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 23 Dec 2022 13:02:25 +0000 Subject: [PATCH 01/29] feat: add `--serve` flag to `dev` command --- npm-shrinkwrap.json | 11 +++ package.json | 1 + src/commands/dev/dev.mjs | 166 ++++++++++++++++++++++++++++++++++----- 3 files changed, 157 insertions(+), 21 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 13f74f2d9bb..5fac066527e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -99,6 +99,7 @@ "static-server": "^2.2.1", "string-similarity": "^4.0.4", "strip-ansi-control-characters": "^2.0.0", + "symlink-or-copy": "^1.3.1", "tabtab": "^3.0.2", "tempy": "^1.0.0", "terminal-link": "^2.1.1", @@ -21984,6 +21985,11 @@ "node": ">=0.10.0" } }, + "node_modules/symlink-or-copy": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", + "integrity": "sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==" + }, "node_modules/synckit": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.1.tgz", @@ -40319,6 +40325,11 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, + "symlink-or-copy": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", + "integrity": "sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==" + }, "synckit": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.1.tgz", diff --git a/package.json b/package.json index 2d0ef4c7723..ee8e1740519 100644 --- a/package.json +++ b/package.json @@ -319,6 +319,7 @@ "static-server": "^2.2.1", "string-similarity": "^4.0.4", "strip-ansi-control-characters": "^2.0.0", + "symlink-or-copy": "^1.3.1", "tabtab": "^3.0.2", "tempy": "^1.0.0", "terminal-link": "^2.1.1", diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index eecf81e56e3..0e2ab6cd927 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -1,5 +1,6 @@ // @ts-check import events from 'events' +import { promises as fs } from 'fs' import path from 'path' import process from 'process' import { promisify } from 'util' @@ -9,8 +10,10 @@ import { Option } from 'commander' import execa from 'execa' import StaticServer from 'static-server' import stripAnsiCc from 'strip-ansi-control-characters' +import { sync as symlinkOrCopy } from 'symlink-or-copy' import waitPort from 'wait-port' +import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from '../../lib/edge-functions/consts.mjs' import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.mjs' import { startFunctionsServer } from '../../lib/functions/server.mjs' import { @@ -26,6 +29,7 @@ import { getNetlifyGraphConfig, readGraphQLOperationsSourceFile, } from '../../lib/one-graph/cli-netlify-graph.mjs' +import { getPathInProject } from '../../lib/settings.cjs' import { startSpinner, stopSpinner } from '../../lib/spinner.cjs' import { BANG, @@ -45,6 +49,7 @@ import { import detectServerSettings from '../../utils/detect-server-settings.mjs' import { generateNetlifyGraphJWT, getSiteInformation, injectEnvVariables, processOnExit } from '../../utils/dev.mjs' import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs' +import { getInternalFunctionsDir } from '../../utils/functions/index.mjs' import { ensureNetlifyIgnore } from '../../utils/gitignore.mjs' import { startLiveTunnel } from '../../utils/live-tunnel.mjs' import openBrowser from '../../utils/open-browser.mjs' @@ -244,6 +249,7 @@ const FRAMEWORK_PORT_TIMEOUT = 6e5 * @param {object} params * @param {*} params.addonsUrls * @param {import('../base-command.mjs').NetlifyOptions["config"]} params.config + * @param {string} [params.configPath] An override for the Netlify config path * @param {import('../base-command.mjs').NetlifyOptions["cachedConfig"]['env']} params.env * @param {InspectSettings} params.inspectSettings * @param {() => Promise} params.getUpdatedConfig @@ -259,6 +265,7 @@ const FRAMEWORK_PORT_TIMEOUT = 6e5 const startProxyServer = async ({ addonsUrls, config, + configPath, env, geoCountry, geolocationMode, @@ -273,7 +280,7 @@ const startProxyServer = async ({ const url = await startProxy({ addonsUrls, config, - configPath: site.configPath, + configPath: configPath || site.configPath, env, geolocationMode, geoCountry, @@ -428,7 +435,6 @@ const validateGeoCountryCode = (arg) => { const dev = async (options, command) => { log(`${NETLIFYDEV}`) const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify - const netlifyBuild = await netlifyBuildPromise config.dev = { ...config.dev } config.build = { ...config.build } /** @type {import('./types').DevConfig} */ @@ -442,6 +448,12 @@ const dev = async (options, command) => { let { env } = cachedConfig + if (options.serve) { + ensureNodeModulesForPlugins({ siteRoot: site.root }) + + devConfig.framework = '#static' + } + 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')}`) @@ -494,6 +506,10 @@ const dev = async (options, command) => { startPollingForAPIAuthentication({ api, command, config, site, siteInfo }) } + log(`${NETLIFYDEVWARN} Setting up local development server`) + + const { configPath: configPathOverride } = await runBuild({ cachedConfig, options, settings, site }) + await startFunctionsServer({ api, command, @@ -506,23 +522,6 @@ const dev = async (options, command) => { timeouts, }) - log(`${NETLIFYDEVWARN} Setting up local development server`) - - const devCommand = async () => { - const { ipVersion } = await startFrameworkServer({ settings }) - - settings.frameworkHost = ipVersion === 6 ? '::1' : '127.0.0.1' - } - const startDevOptions = getBuildOptions({ - cachedConfig, - options, - }) - const { error: startDevError, success } = await netlifyBuild.startDev(devCommand, startDevOptions) - - if (!success) { - error(`Could not start local development server\n\n${startDevError.message}\n\n${startDevError.stack}`) - } - // Try to add `.netlify` to `.gitignore`. try { await ensureNetlifyIgnore(repositoryRoot) @@ -544,6 +543,7 @@ const dev = async (options, command) => { let url = await startProxyServer({ addonsUrls, config, + configPath: configPathOverride, env: command.netlify.cachedConfig.env, geolocationMode: options.geo, geoCountry: options.country, @@ -668,9 +668,90 @@ const dev = async (options, command) => { printBanner({ url }) } -const getBuildOptions = ({ cachedConfig, options: { context, cwd = process.cwd(), debug, dry, offline }, token }) => ({ +// Copies `netlify.toml`, if one is defined, into the `.netlify` internal +// directory and returns the path to its new location. +const copyConfig = async ({ configPath, siteRoot }) => { + const newConfigPath = path.resolve(siteRoot, getPathInProject(['netlify.toml'])) + + try { + await fs.copyFile(configPath, newConfigPath) + } catch { + // no-op + } + + return newConfigPath +} + +// Loads and runs Netlify Build. Chooses the right flags and entry point based +// on the options supplied. +const runBuild = async ({ cachedConfig, options, settings, site }) => { + const { default: buildSite, startDev } = await netlifyBuildPromise + const sharedOptions = getBuildOptions({ + cachedConfig, + options, + }) + const devCommand = async (settingsOverrides = {}) => { + const { ipVersion } = await startFrameworkServer({ + settings: { + ...settings, + ...settingsOverrides, + }, + }) + + settings.frameworkHost = ipVersion === 6 ? '::1' : '127.0.0.1' + } + + if (options.serve) { + // Start by cleaning the internal directory, as it may have artifacts left + // by previous builds. + await cleanInternalDirectory(site.root) + + // Copy `netlify.toml` into the internal directory. This will be the new + // location of the config file for the duration of the command. + const tempConfigPath = await copyConfig({ configPath: cachedConfig.configPath, siteRoot: site.root }) + const buildSiteOptions = { + ...sharedOptions, + outputConfigPath: tempConfigPath, + saveConfig: true, + } + + // Run Netlify Build using the main entry point. + await buildSite(buildSiteOptions) + + // Start the dev server, forcing the usage of a static server as opposed to + // the framework server. + await devCommand({ + command: undefined, + useStaticServer: true, + }) + + return { configPath: tempConfigPath } + } + + // Enable `quiet` to suppress non-essential output from Netlify Build. + const startDevOptions = { + ...sharedOptions, + quiet: true, + } + + // Run Netlify Build using the `startDev` entry point. + const { error: startDevError, success } = await startDev(devCommand, startDevOptions) + + if (!success) { + error(`Could not start local development server\n\n${startDevError.message}\n\n${startDevError.stack}`) + } + + return {} +} + +const getBuildOptions = ({ cachedConfig, - token, + options: { configPath, context, cwd = process.cwd(), debug, dry, offline, quiet, saveConfig }, +}) => ({ + cachedConfig, + configPath, + siteId: cachedConfig.siteInfo.id, + token: cachedConfig.token, dry, debug, context, @@ -679,8 +760,50 @@ const getBuildOptions = ({ cachedConfig, options: { context, cwd = process.cwd() buffer: false, offline, cwd, + quiet, + saveConfig, }) +// Any Node modules used by internal serverless functions will be installed at +// `.netlify/plugins/node_modules`, which isn't part of the resolution paths +// used by code in `.netlify/functions`. To make sure modules can resolve, we +// create a symlink for the plugin's node_modules at `.netlify/node_modules`. +const ensureNodeModulesForPlugins = ({ siteRoot }) => { + const targetPath = path.resolve(siteRoot, getPathInProject(['plugins', 'node_modules'])) + const linkPath = path.resolve(siteRoot, getPathInProject(['node_modules'])) + + try { + symlinkOrCopy(targetPath, linkPath) + } catch (symlinkError) { + if (symlinkError.code === 'EEXIST') { + return + } + + warn( + `There was an error setting up the node_modules directory at ${linkPath}, which may cause some serverless functions to not work as expected. Please ensure that you have write permissions on this directory.`, + ) + } +} + +const cleanInternalDirectory = async (basePath) => { + const internalFunctionsDirectory = await getInternalFunctionsDir({ base: basePath }) + + // Remove any internal serverless functions. + if (internalFunctionsDirectory) { + await fs.rm(internalFunctionsDirectory, { force: true, recursive: true }) + } + + const internalEdgeFunctionsDirectory = path.resolve(basePath, getPathInProject([INTERNAL_EDGE_FUNCTIONS_FOLDER])) + + // Remove any internal edge functions. + await fs.rm(internalEdgeFunctionsDirectory, { force: true, recursive: true }) + + const configPath = path.join(basePath, 'netlify.toml') + + // Remove any config file. + await fs.rm(configPath, { force: true }) +} + /** * Creates the `netlify dev` command * @param {import('../base-command.mjs').default} program @@ -710,6 +833,7 @@ export const createDevCommand = (program) => { .option('-o ,--offline', 'disables any features that require network access') .option('-l, --live', 'start a public live session', false) .option('--functionsPort ', 'port of functions server', (value) => Number.parseInt(value)) + .option('-s, --serve', 'run in "serve" mode', false) .addOption( new Option( '--geo ', From b3f582d7a8558cfef4b3eba1532f72c8d7aa130b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Fri, 23 Dec 2022 15:22:07 +0000 Subject: [PATCH 02/29] refactor: simplify internal directory clean up --- src/commands/dev/dev.mjs | 21 ++++++--------------- src/utils/functions/functions.mjs | 4 +++- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 0e2ab6cd927..2fdd3d654c4 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -49,7 +49,7 @@ import { import detectServerSettings from '../../utils/detect-server-settings.mjs' import { generateNetlifyGraphJWT, getSiteInformation, injectEnvVariables, processOnExit } from '../../utils/dev.mjs' import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs' -import { getInternalFunctionsDir } from '../../utils/functions/index.mjs' +import { INTERNAL_FUNCTIONS_FOLDER } from '../../utils/functions/index.mjs' import { ensureNetlifyIgnore } from '../../utils/gitignore.mjs' import { startLiveTunnel } from '../../utils/live-tunnel.mjs' import openBrowser from '../../utils/open-browser.mjs' @@ -786,22 +786,13 @@ const ensureNodeModulesForPlugins = ({ siteRoot }) => { } const cleanInternalDirectory = async (basePath) => { - const internalFunctionsDirectory = await getInternalFunctionsDir({ base: basePath }) + const ops = [INTERNAL_FUNCTIONS_FOLDER, INTERNAL_EDGE_FUNCTIONS_FOLDER, 'netlify.toml'].map((name) => { + const fullPath = path.resolve(basePath, getPathInProject([name])) - // Remove any internal serverless functions. - if (internalFunctionsDirectory) { - await fs.rm(internalFunctionsDirectory, { force: true, recursive: true }) - } - - const internalEdgeFunctionsDirectory = path.resolve(basePath, getPathInProject([INTERNAL_EDGE_FUNCTIONS_FOLDER])) - - // Remove any internal edge functions. - await fs.rm(internalEdgeFunctionsDirectory, { force: true, recursive: true }) - - const configPath = path.join(basePath, 'netlify.toml') + return fs.rm(fullPath, { force: true, recursive: true }) + }) - // Remove any config file. - await fs.rm(configPath, { force: true }) + await Promise.all(ops) } /** diff --git a/src/utils/functions/functions.mjs b/src/utils/functions/functions.mjs index e9f2f85a474..963aaa64a22 100644 --- a/src/utils/functions/functions.mjs +++ b/src/utils/functions/functions.mjs @@ -4,6 +4,8 @@ import { resolve } from 'path' import { isDirectoryAsync, isFileAsync } from '../../lib/fs.cjs' import { getPathInProject } from '../../lib/settings.cjs' +export const INTERNAL_FUNCTIONS_FOLDER = 'functions-internal' + /** * retrieves the function directory out of the flags or config * @param {object} param @@ -27,7 +29,7 @@ export const getFunctionsManifestPath = async ({ base }) => { } export const getInternalFunctionsDir = async ({ base }) => { - const path = resolve(base, getPathInProject(['functions-internal'])) + const path = resolve(base, getPathInProject([INTERNAL_FUNCTIONS_FOLDER])) const isDirectory = await isDirectoryAsync(path) return isDirectory ? path : null From 90402fdc759596b7c7442b79676ce4adea386983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 2 Jan 2023 12:13:10 +0000 Subject: [PATCH 03/29] refactor: load built functions --- npm-shrinkwrap.json | 84 +++++++++++++++++++++++++++---- package.json | 2 +- src/commands/dev/dev.mjs | 31 +++--------- src/lib/functions/registry.mjs | 75 +++++++++++++++------------ src/lib/functions/server.mjs | 47 +++++++++++------ src/utils/functions/functions.mjs | 7 +++ 6 files changed, 162 insertions(+), 84 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5fac066527e..5143380d871 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -45,6 +45,7 @@ "execa": "^5.0.0", "express": "^4.17.1", "express-logging": "^1.1.1", + "extract-zip": "^2.0.1", "find-up": "^5.0.0", "flush-write-stream": "^2.0.0", "folder-walker": "^3.2.0", @@ -99,7 +100,6 @@ "static-server": "^2.2.1", "string-similarity": "^4.0.4", "strip-ansi-control-characters": "^2.0.0", - "symlink-or-copy": "^1.3.1", "tabtab": "^3.0.2", "tempy": "^1.0.0", "terminal-link": "^2.1.1", @@ -5665,6 +5665,15 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz", @@ -12369,6 +12378,39 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -21985,11 +22027,6 @@ "node": ">=0.10.0" } }, - "node_modules/symlink-or-copy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", - "integrity": "sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==" - }, "node_modules/synckit": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.1.tgz", @@ -28025,6 +28062,15 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz", @@ -33094,6 +33140,27 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -40325,11 +40392,6 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, - "symlink-or-copy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/symlink-or-copy/-/symlink-or-copy-1.3.1.tgz", - "integrity": "sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==" - }, "synckit": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.1.tgz", diff --git a/package.json b/package.json index ee8e1740519..220f4d9859b 100644 --- a/package.json +++ b/package.json @@ -265,6 +265,7 @@ "execa": "^5.0.0", "express": "^4.17.1", "express-logging": "^1.1.1", + "extract-zip": "^2.0.1", "find-up": "^5.0.0", "flush-write-stream": "^2.0.0", "folder-walker": "^3.2.0", @@ -319,7 +320,6 @@ "static-server": "^2.2.1", "string-similarity": "^4.0.4", "strip-ansi-control-characters": "^2.0.0", - "symlink-or-copy": "^1.3.1", "tabtab": "^3.0.2", "tempy": "^1.0.0", "terminal-link": "^2.1.1", diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 2fdd3d654c4..813f9d5ca94 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -10,7 +10,6 @@ import { Option } from 'commander' import execa from 'execa' import StaticServer from 'static-server' import stripAnsiCc from 'strip-ansi-control-characters' -import { sync as symlinkOrCopy } from 'symlink-or-copy' import waitPort from 'wait-port' import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from '../../lib/edge-functions/consts.mjs' @@ -448,9 +447,9 @@ const dev = async (options, command) => { let { env } = cachedConfig + // If the `serve` flag is present, we override the `framework` value so that + // we start a static server and not the framework's development server. if (options.serve) { - ensureNodeModulesForPlugins({ siteRoot: site.root }) - devConfig.framework = '#static' } @@ -510,10 +509,15 @@ const dev = async (options, command) => { const { configPath: configPathOverride } = await runBuild({ cachedConfig, options, settings, site }) + // When using the `serve` flag, we want to use the production functions built + // by Netlify Build rather than building them from source. + const dist = Boolean(options.serve) + await startFunctionsServer({ api, command, config, + dist, settings, site, siteInfo, @@ -764,27 +768,6 @@ const getBuildOptions = ({ saveConfig, }) -// Any Node modules used by internal serverless functions will be installed at -// `.netlify/plugins/node_modules`, which isn't part of the resolution paths -// used by code in `.netlify/functions`. To make sure modules can resolve, we -// create a symlink for the plugin's node_modules at `.netlify/node_modules`. -const ensureNodeModulesForPlugins = ({ siteRoot }) => { - const targetPath = path.resolve(siteRoot, getPathInProject(['plugins', 'node_modules'])) - const linkPath = path.resolve(siteRoot, getPathInProject(['node_modules'])) - - try { - symlinkOrCopy(targetPath, linkPath) - } catch (symlinkError) { - if (symlinkError.code === 'EEXIST') { - return - } - - warn( - `There was an error setting up the node_modules directory at ${linkPath}, which may cause some serverless functions to not work as expected. Please ensure that you have write permissions on this directory.`, - ) - } -} - const cleanInternalDirectory = async (basePath) => { const ops = [INTERNAL_FUNCTIONS_FOLDER, INTERNAL_EDGE_FUNCTIONS_FOLDER, 'netlify.toml'].map((name) => { const fullPath = path.resolve(basePath, getPathInProject([name])) diff --git a/src/lib/functions/registry.mjs b/src/lib/functions/registry.mjs index c985a6cec15..b24792233dc 100644 --- a/src/lib/functions/registry.mjs +++ b/src/lib/functions/registry.mjs @@ -1,19 +1,21 @@ // @ts-check import { mkdir } from 'fs/promises' -import { extname, isAbsolute, join } from 'path' +import { extname, isAbsolute, join, resolve } from 'path' import { env } from 'process' +import extractZip from 'extract-zip' + import { chalk, getTerminalLink, log, NETLIFYDEVERR, NETLIFYDEVLOG, - NETLIFYDEVWARN, warn, watchDebounced, } from '../../utils/command-helpers.mjs' import { getLogMessage } from '../log.mjs' +import { getPathInProject } from '../settings.cjs' import NetlifyFunction from './netlify-function.mjs' import runtimes from './runtimes/index.mjs' @@ -123,7 +125,7 @@ export class FunctionsRegistry { return this.functions.get(name) } - registerFunction(name, funcBeforeHook) { + async registerFunction(name, funcBeforeHook) { const { runtime } = funcBeforeHook // The `onRegister` hook allows runtimes to modify the function before it's @@ -148,8 +150,7 @@ export class FunctionsRegistry { // This fixes the bug described here https://github.com/netlify/zip-it-and-ship-it/issues/637 // If the current function's file is a zip bundle, we ignore it and log a helpful message. if (extname(func.mainFile) === ZIP_EXTENSION) { - log(`${NETLIFYDEVWARN} Skipped bundled function ${chalk.yellow(name)}. Unzip the archive to load it from source.`) - return + await this.unzipFunction(func) } this.functions.set(name, func) @@ -197,34 +198,36 @@ export class FunctionsRegistry { await Promise.all(deletedFunctions.map((func) => this.unregisterFunction(func.name))) - functions.forEach(({ mainFile, name, runtime: runtimeName }) => { - const runtime = runtimes[runtimeName] - - // If there is no matching runtime, it means this function is not yet - // supported in Netlify Dev. - if (runtime === undefined) { - return - } - - // If this function has already been registered, we skip it. - if (this.functions.has(name)) { - return - } - - const func = new NetlifyFunction({ - config: this.config, - directory: directories.find((directory) => mainFile.startsWith(directory)), - mainFile, - name, - projectRoot: this.projectRoot, - runtime, - timeoutBackground: this.timeouts.backgroundFunctions, - timeoutSynchronous: this.timeouts.syncFunctions, - settings: this.settings, - }) + await Promise.all( + functions.map(async ({ mainFile, name, runtime: runtimeName }) => { + const runtime = runtimes[runtimeName] - this.registerFunction(name, func) - }) + // If there is no matching runtime, it means this function is not yet + // supported in Netlify Dev. + if (runtime === undefined) { + return + } + + // If this function has already been registered, we skip it. + if (this.functions.has(name)) { + return + } + + const func = new NetlifyFunction({ + config: this.config, + directory: directories.find((directory) => mainFile.startsWith(directory)), + mainFile, + name, + projectRoot: this.projectRoot, + runtime, + timeoutBackground: this.timeouts.backgroundFunctions, + timeoutSynchronous: this.timeouts.syncFunctions, + settings: this.settings, + }) + + await this.registerFunction(name, func) + }), + ) await Promise.all(directories.map((path) => this.setupDirectoryWatcher(path))) } @@ -261,4 +264,12 @@ export class FunctionsRegistry { await watcher.close() } } + + async unzipFunction(func) { + const targetDirectory = resolve(this.projectRoot, getPathInProject(['functions-serve', '.unzipped', func.name])) + + await extractZip(func.mainFile, { dir: targetDirectory }) + + func.mainFile = join(targetDirectory, `${func.name}.js`) + } } diff --git a/src/lib/functions/server.mjs b/src/lib/functions/server.mjs index f53d6c164d8..5659d6c592f 100644 --- a/src/lib/functions/server.mjs +++ b/src/lib/functions/server.mjs @@ -4,7 +4,7 @@ import jwtDecode from 'jwt-decode' import { NETLIFYDEVERR, NETLIFYDEVLOG, error as errorExit, log } from '../../utils/command-helpers.mjs' import { generateNetlifyGraphJWT } from '../../utils/dev.mjs' -import { CLOCKWORK_USERAGENT, getInternalFunctionsDir } from '../../utils/functions/index.mjs' +import { CLOCKWORK_USERAGENT, getFunctionsDistPath, getInternalFunctionsDir } from '../../utils/functions/index.mjs' import { handleBackgroundFunction, handleBackgroundFunctionResult } from './background.mjs' import { createFormSubmissionHandler } from './form-submissions-handler.mjs' @@ -208,29 +208,44 @@ const getFunctionsServer = async function (options) { } export const startFunctionsServer = async (options) => { - const { capabilities, config, settings, site, siteUrl, timeouts } = options + const { capabilities, config, dist, settings, site, siteUrl, timeouts } = options const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root }) // The order of the function directories matters. Leftmost directories take // precedence. - const functionsDirectories = [settings.functions, internalFunctionsDir].filter(Boolean) - - if (functionsDirectories.length !== 0) { - const functionsRegistry = new FunctionsRegistry({ - capabilities, - config, - isConnected: Boolean(siteUrl), - projectRoot: site.root, - settings, - timeouts, - }) + const functionsDirectories = [] - await functionsRegistry.scan(functionsDirectories) + // If the `dist` parameter is sent, the functions server will use the built + // functions created by zip-it-and-ship-it rather than building them from + // source. + if (dist) { + const distPath = await getFunctionsDistPath({ base: site.root }) - const server = await getFunctionsServer(Object.assign(options, { functionsRegistry })) + if (distPath) { + functionsDirectories.push(distPath) + } + } else { + functionsDirectories.push(...[settings.functions, internalFunctionsDir].filter(Boolean)) + } - await startWebServer({ server, settings }) + if (functionsDirectories.length === 0) { + return } + + const functionsRegistry = new FunctionsRegistry({ + capabilities, + config, + isConnected: Boolean(siteUrl), + projectRoot: site.root, + settings, + timeouts, + }) + + await functionsRegistry.scan(functionsDirectories) + + const server = await getFunctionsServer(Object.assign(options, { functionsRegistry })) + + await startWebServer({ server, settings }) } const startWebServer = async ({ server, settings }) => { diff --git a/src/utils/functions/functions.mjs b/src/utils/functions/functions.mjs index 963aaa64a22..bb6d1942f03 100644 --- a/src/utils/functions/functions.mjs +++ b/src/utils/functions/functions.mjs @@ -28,6 +28,13 @@ export const getFunctionsManifestPath = async ({ base }) => { return isFile ? path : null } +export const getFunctionsDistPath = async ({ base }) => { + const path = resolve(base, getPathInProject(['functions'])) + const isDirectory = await isDirectoryAsync(path) + + return isDirectory ? path : null +} + export const getInternalFunctionsDir = async ({ base }) => { const path = resolve(base, getPathInProject([INTERNAL_FUNCTIONS_FOLDER])) const isDirectory = await isDirectoryAsync(path) From d5652e75f69be02fa26b7333c4ce1a82b53cacc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 2 Jan 2023 12:24:14 +0000 Subject: [PATCH 04/29] refactor: make things nicer --- src/lib/functions/registry.mjs | 10 ++++++---- src/lib/functions/server.mjs | 5 ++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib/functions/registry.mjs b/src/lib/functions/registry.mjs index b24792233dc..e41969b8863 100644 --- a/src/lib/functions/registry.mjs +++ b/src/lib/functions/registry.mjs @@ -147,10 +147,12 @@ export class FunctionsRegistry { ) } - // This fixes the bug described here https://github.com/netlify/zip-it-and-ship-it/issues/637 - // If the current function's file is a zip bundle, we ignore it and log a helpful message. + // If the function file is a ZIP, we extract it and rewire its main file to + // the new location. if (extname(func.mainFile) === ZIP_EXTENSION) { - await this.unzipFunction(func) + const unzippedDirectory = await this.unzipFunction(func) + + func.mainFile = join(unzippedDirectory, `${func.name}.js`) } this.functions.set(name, func) @@ -270,6 +272,6 @@ export class FunctionsRegistry { await extractZip(func.mainFile, { dir: targetDirectory }) - func.mainFile = join(targetDirectory, `${func.name}.js`) + return targetDirectory } } diff --git a/src/lib/functions/server.mjs b/src/lib/functions/server.mjs index 5659d6c592f..155640a3a0f 100644 --- a/src/lib/functions/server.mjs +++ b/src/lib/functions/server.mjs @@ -210,9 +210,6 @@ const getFunctionsServer = async function (options) { export const startFunctionsServer = async (options) => { const { capabilities, config, dist, settings, site, siteUrl, timeouts } = options const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root }) - - // The order of the function directories matters. Leftmost directories take - // precedence. const functionsDirectories = [] // If the `dist` parameter is sent, the functions server will use the built @@ -225,6 +222,8 @@ export const startFunctionsServer = async (options) => { functionsDirectories.push(distPath) } } else { + // The order of the function directories matters. Leftmost directories take + // precedence. functionsDirectories.push(...[settings.functions, internalFunctionsDir].filter(Boolean)) } From ac6a8458b752c65f57131ce12b3da9b0c22b3bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 2 Jan 2023 12:29:07 +0000 Subject: [PATCH 05/29] refactor: add const --- src/lib/functions/registry.mjs | 6 +++++- src/lib/functions/runtimes/js/builders/zisi.mjs | 3 ++- src/lib/functions/runtimes/rust/index.mjs | 3 ++- src/utils/functions/functions.mjs | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/functions/registry.mjs b/src/lib/functions/registry.mjs index e41969b8863..e4b75d13ba7 100644 --- a/src/lib/functions/registry.mjs +++ b/src/lib/functions/registry.mjs @@ -14,6 +14,7 @@ import { warn, watchDebounced, } from '../../utils/command-helpers.mjs' +import { SERVE_FUNCTIONS_FOLDER } from '../../utils/functions/functions.mjs' import { getLogMessage } from '../log.mjs' import { getPathInProject } from '../settings.cjs' @@ -268,7 +269,10 @@ export class FunctionsRegistry { } async unzipFunction(func) { - const targetDirectory = resolve(this.projectRoot, getPathInProject(['functions-serve', '.unzipped', func.name])) + const targetDirectory = resolve( + this.projectRoot, + getPathInProject([SERVE_FUNCTIONS_FOLDER, '.unzipped', func.name]), + ) await extractZip(func.mainFile, { dir: targetDirectory }) diff --git a/src/lib/functions/runtimes/js/builders/zisi.mjs b/src/lib/functions/runtimes/js/builders/zisi.mjs index 39b46307672..5c70f1fdbdd 100644 --- a/src/lib/functions/runtimes/js/builders/zisi.mjs +++ b/src/lib/functions/runtimes/js/builders/zisi.mjs @@ -7,6 +7,7 @@ import readPkgUp from 'read-pkg-up' import sourceMapSupport from 'source-map-support' import { NETLIFYDEVERR } from '../../../../../utils/command-helpers.mjs' +import { SERVE_FUNCTIONS_FOLDER } from '../../../../../utils/functions/functions.mjs' import { getPathInProject } from '../../../../settings.cjs' import { normalizeFunctionsConfig } from '../../../config.mjs' import { memoizedBuild } from '../../../memoized-build.mjs' @@ -94,7 +95,7 @@ const clearFunctionsCache = (functionsPath) => { } const getTargetDirectory = async ({ errorExit }) => { - const targetDirectory = path.resolve(getPathInProject(['functions-serve'])) + const targetDirectory = path.resolve(getPathInProject([SERVE_FUNCTIONS_FOLDER])) try { await mkdir(targetDirectory, { recursive: true }) diff --git a/src/lib/functions/runtimes/rust/index.mjs b/src/lib/functions/runtimes/rust/index.mjs index cc3c5cb40d0..e5ae6feca96 100644 --- a/src/lib/functions/runtimes/rust/index.mjs +++ b/src/lib/functions/runtimes/rust/index.mjs @@ -7,6 +7,7 @@ import findUp from 'find-up' import toml from 'toml' import execa from '../../../../utils/execa.mjs' +import { SERVE_FUNCTIONS_FOLDER } from '../../../../utils/functions/functions.mjs' import { getPathInProject } from '../../../settings.cjs' import { runFunctionsProxy } from '../../local-proxy.mjs' @@ -16,7 +17,7 @@ export const name = 'rs' const build = async ({ func }) => { const functionDirectory = dirname(func.mainFile) - const cacheDirectory = resolve(getPathInProject(['functions-serve'])) + const cacheDirectory = resolve(getPathInProject([SERVE_FUNCTIONS_FOLDER])) const targetDirectory = join(cacheDirectory, func.name) const crateName = await getCrateName(functionDirectory) const binaryName = `${crateName}${isWindows ? '.exe' : ''}` diff --git a/src/utils/functions/functions.mjs b/src/utils/functions/functions.mjs index bb6d1942f03..7b09e79b51e 100644 --- a/src/utils/functions/functions.mjs +++ b/src/utils/functions/functions.mjs @@ -5,6 +5,7 @@ import { isDirectoryAsync, isFileAsync } from '../../lib/fs.cjs' import { getPathInProject } from '../../lib/settings.cjs' export const INTERNAL_FUNCTIONS_FOLDER = 'functions-internal' +export const SERVE_FUNCTIONS_FOLDER = 'functions-serve' /** * retrieves the function directory out of the flags or config From 134bf3f175fcaaf1b9fe16e572c890f46be2b717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 10:15:37 +0000 Subject: [PATCH 06/29] feat: show debug message --- src/commands/dev/dev.mjs | 1 + src/commands/functions/functions-serve.mjs | 1 + src/lib/functions/registry.mjs | 7 ++++++- src/lib/functions/server.mjs | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 813f9d5ca94..1d2664c66e8 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -517,6 +517,7 @@ const dev = async (options, command) => { api, command, config, + debug: options.debug, dist, settings, site, diff --git a/src/commands/functions/functions-serve.mjs b/src/commands/functions/functions-serve.mjs index f5a0abde131..a349d5f4b84 100644 --- a/src/commands/functions/functions-serve.mjs +++ b/src/commands/functions/functions-serve.mjs @@ -34,6 +34,7 @@ const functionsServe = async (options, command) => { await startFunctionsServer({ config, + debug: options.debug, api, settings: { functions: functionsDir, functionsPort }, site, diff --git a/src/lib/functions/registry.mjs b/src/lib/functions/registry.mjs index e4b75d13ba7..cdc26afca4c 100644 --- a/src/lib/functions/registry.mjs +++ b/src/lib/functions/registry.mjs @@ -24,9 +24,10 @@ import runtimes from './runtimes/index.mjs' const ZIP_EXTENSION = '.zip' export class FunctionsRegistry { - constructor({ capabilities, config, isConnected = false, projectRoot, settings, timeouts }) { + constructor({ capabilities, config, debug = false, isConnected = false, projectRoot, settings, timeouts }) { this.capabilities = capabilities this.config = config + this.debug = debug this.isConnected = isConnected this.projectRoot = projectRoot this.timeouts = timeouts @@ -153,6 +154,10 @@ export class FunctionsRegistry { if (extname(func.mainFile) === ZIP_EXTENSION) { const unzippedDirectory = await this.unzipFunction(func) + if (this.debug) { + log(`${NETLIFYDEVLOG} ${chalk.green('Extracted')} function ${chalk.yellow(name)} from ${func.mainFile}.`) + } + func.mainFile = join(unzippedDirectory, `${func.name}.js`) } diff --git a/src/lib/functions/server.mjs b/src/lib/functions/server.mjs index 155640a3a0f..ef605daf8fe 100644 --- a/src/lib/functions/server.mjs +++ b/src/lib/functions/server.mjs @@ -208,7 +208,7 @@ const getFunctionsServer = async function (options) { } export const startFunctionsServer = async (options) => { - const { capabilities, config, dist, settings, site, siteUrl, timeouts } = options + const { capabilities, config, debug, dist, settings, site, siteUrl, timeouts } = options const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root }) const functionsDirectories = [] @@ -234,6 +234,7 @@ export const startFunctionsServer = async (options) => { const functionsRegistry = new FunctionsRegistry({ capabilities, config, + debug, isConnected: Boolean(siteUrl), projectRoot: site.root, settings, From d782283cd00af0cb6b0a5cec751ff1c4a901d349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 13:35:27 +0000 Subject: [PATCH 07/29] refactor: rename variable --- src/commands/dev/dev.mjs | 4 ++-- src/lib/functions/server.mjs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 1d2664c66e8..908329c41a3 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -511,14 +511,14 @@ const dev = async (options, command) => { // When using the `serve` flag, we want to use the production functions built // by Netlify Build rather than building them from source. - const dist = Boolean(options.serve) + const loadDistFunctions = Boolean(options.serve) await startFunctionsServer({ api, command, config, debug: options.debug, - dist, + loadDistFunctions, settings, site, siteInfo, diff --git a/src/lib/functions/server.mjs b/src/lib/functions/server.mjs index ef605daf8fe..dfffe37e989 100644 --- a/src/lib/functions/server.mjs +++ b/src/lib/functions/server.mjs @@ -208,14 +208,14 @@ const getFunctionsServer = async function (options) { } export const startFunctionsServer = async (options) => { - const { capabilities, config, debug, dist, settings, site, siteUrl, timeouts } = options + const { capabilities, config, debug, loadDistFunctions, settings, site, siteUrl, timeouts } = options const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root }) const functionsDirectories = [] - // If the `dist` parameter is sent, the functions server will use the built - // functions created by zip-it-and-ship-it rather than building them from - // source. - if (dist) { + // If the `loadDistFunctions` parameter is sent, the functions server will + // use the built functions created by zip-it-and-ship-it rather than building + // them from source. + if (loadDistFunctions) { const distPath = await getFunctionsDistPath({ base: site.root }) if (distPath) { From 5411c3c2d39bfddc63cd2f32540d69de40480e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 14:17:15 +0000 Subject: [PATCH 08/29] chore: add `extract-zip` dependency --- npm-shrinkwrap.json | 73 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 74 insertions(+) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index bb7528af171..8fae414e033 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -46,6 +46,7 @@ "execa": "^5.0.0", "express": "^4.17.1", "express-logging": "^1.1.1", + "extract-zip": "^2.0.1", "fastify": "^4.10.2", "find-up": "^5.0.0", "flush-write-stream": "^2.0.0", @@ -5613,6 +5614,15 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz", @@ -12390,6 +12400,39 @@ "node": ">=0.10.0" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -28152,6 +28195,15 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz", @@ -33278,6 +33330,27 @@ } } }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + } + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", diff --git a/package.json b/package.json index c91c9be3da1..f35fe990d00 100644 --- a/package.json +++ b/package.json @@ -266,6 +266,7 @@ "execa": "^5.0.0", "express": "^4.17.1", "express-logging": "^1.1.1", + "extract-zip": "^2.0.1", "fastify": "^4.10.2", "find-up": "^5.0.0", "flush-write-stream": "^2.0.0", From 05e9dc393a3dcef4edf3f9291d5f362d08bb234a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 14:48:16 +0000 Subject: [PATCH 09/29] chore: update snapshots --- .../600.framework-detection.test.cjs.md | 320 ------------------ .../600.framework-detection.test.cjs.snap | Bin 1975 -> 1655 bytes 2 files changed, 320 deletions(-) diff --git a/tests/integration/snapshots/600.framework-detection.test.cjs.md b/tests/integration/snapshots/600.framework-detection.test.cjs.md index 89ceb9a4e71..674ecf6d3c6 100644 --- a/tests/integration/snapshots/600.framework-detection.test.cjs.md +++ b/tests/integration/snapshots/600.framework-detection.test.cjs.md @@ -15,32 +15,8 @@ Generated by [AVA](https://avajs.dev). ◈ See docs at: https://cli.netlify.com/netlify-dev#project-detection␊ ◈ Running static server from "site-with-index-file"␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ␊ ◈ Static server listening to 88888␊ - ​␊ - (dev.command completed in Xms)␊ ␊ ┌──────────────────────────────────────────────────┐␊ │ │␊ @@ -56,32 +32,8 @@ Generated by [AVA](https://avajs.dev). ◈ Using simple static server because '--dir' flag was specified␊ ◈ Running static server from "site-with-index-file/public"␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ␊ ◈ Static server listening to 88888␊ - ​␊ - (dev.command completed in Xms)␊ ␊ ┌──────────────────────────────────────────────────┐␊ │ │␊ @@ -100,32 +52,8 @@ Generated by [AVA](https://avajs.dev). ◈ See docs at: https://cli.netlify.com/netlify-dev#project-detection␊ ◈ Running static server from "site-with-index-file"␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - /file/path␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ␊ ◈ Static server listening to 88888␊ - ​␊ - (dev.command completed in Xms)␊ ␊ ┌──────────────────────────────────────────────────┐␊ │ │␊ @@ -141,32 +69,8 @@ Generated by [AVA](https://avajs.dev). ◈ Using simple static server because '--dir' flag was specified␊ ◈ Running static server from "site-with-index-file/public"␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ␊ ◈ Static server listening to 88888␊ - ​␊ - (dev.command completed in Xms)␊ ␊ ┌──────────────────────────────────────────────────┐␊ │ │␊ @@ -184,32 +88,8 @@ Generated by [AVA](https://avajs.dev). ◈ Use --staticServerPort or [dev.staticServerPort] to configure the static server port␊ ◈ Running static server from "site-with-index-file/public"␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ␊ ◈ Static server listening to 88888␊ - ​␊ - (dev.command completed in Xms)␊ ␊ ┌──────────────────────────────────────────────────┐␊ │ │␊ @@ -223,28 +103,6 @@ Generated by [AVA](https://avajs.dev). `◈ Netlify Dev ◈␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - /file/path␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with custom config␊ hello␊ ◈ "echo hello" exited with code *. Shutting down Netlify Dev server` @@ -255,28 +113,6 @@ Generated by [AVA](https://avajs.dev). `◈ Netlify Dev ◈␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - /file/path␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with Create React App␊ ◈ Failed running command: react-scripts start. Please verify 'react-scripts' exists` @@ -293,28 +129,6 @@ Generated by [AVA](https://avajs.dev). `◈ Netlify Dev ◈␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with Create React App␊ ␊ > start␊ @@ -342,28 +156,6 @@ Generated by [AVA](https://avajs.dev). `◈ Netlify Dev ◈␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - /file/path␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with #custom␊ hello␊ ◈ "echo hello" exited with code *. Shutting down Netlify Dev server` @@ -377,28 +169,6 @@ Generated by [AVA](https://avajs.dev). ◈ Setup a netlify.toml file with a [dev] section to specify your dev server settings.␊ ◈ See docs at: https://cli.netlify.com/netlify-dev#project-detection␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with #custom␊ ◈ Failed running command: oops-i-did-it-again forgot-to-use-a-valid-command. Please verify 'oops-i-did-it-again' exists` @@ -414,28 +184,6 @@ Generated by [AVA](https://avajs.dev). > [Create React App] 'npm run start' ? Multiple possible start commands found Create React App-npm run start␊ Add 'framework = "create-react-app"' to the [dev] section of your netlify.toml to avoid this selection prompt next time␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with Create React App␊ ␊ > start␊ @@ -452,28 +200,6 @@ Generated by [AVA](https://avajs.dev). ◈ Setup a netlify.toml file with a [dev] section to specify your dev server settings.␊ ◈ See docs at: https://cli.netlify.com/netlify-dev#project-detection␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with custom config␊ hello␊ ◈ "echo hello" exited with code *. Shutting down Netlify Dev server` @@ -489,32 +215,8 @@ Generated by [AVA](https://avajs.dev). ◈ See docs at: https://cli.netlify.com/netlify-dev#project-detection␊ ◈ Running static server from "site-with-gulp"␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ␊ ◈ Static server listening to 88888␊ - ​␊ - (dev.command completed in Xms)␊ ␊ ┌──────────────────────────────────────────────────┐␊ │ │␊ @@ -528,28 +230,6 @@ Generated by [AVA](https://avajs.dev). `◈ Netlify Dev ◈␊ ◈ Setting up local development server␊ - ​␊ - Netlify Build ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ - > Version␊ - @netlify/build 0.0.0␊ - ​␊ - > Flags␊ - offline: true␊ - ​␊ - > Current directory␊ - /file/path␊ - ​␊ - > Config file␊ - No config file was defined: using default values.␊ - ​␊ - > Context␊ - dev␊ - ​␊ - 1. Run command for local development ␊ - ────────────────────────────────────────────────────────────────␊ - ​␊ ◈ Starting Netlify Dev with Nuxt 3␊ ␊ > dev␊ diff --git a/tests/integration/snapshots/600.framework-detection.test.cjs.snap b/tests/integration/snapshots/600.framework-detection.test.cjs.snap index 8a3083ebf9d0373b8e9f4e3e0a73e5f67570ef54..15d926fb618c28a6194d76c9c4124a281499efca 100644 GIT binary patch literal 1655 zcmV--28j7VRzV(4PU?6AO*2zui*X8BUQcmUBGMQ|;VnBJ z5|&a0i4?6uYLrV!xrUCA+vYlE61581wW4oOoo7IRJ8NrNv{EYsI!rh5#%4_0TPQrz zOmM3wqmfxxSL9|1?P4{GYK`($&C8__i%0>{E6~(BQ!7hLQOatCqDZus{Qn_JIf_od zgGxf@6x3zKT+cbTt{L=+T^>{w(=;?zhK$E_Cp1=9i$xhz09S^aO;TefO-0r+e(-*E z15094rZlzcaGB@Kqf!BY-Mb&}-hJ<}e178W`v4yJfs^`kozJWfxafeSB<=$4w%Iq_ zHtWNtPOWD+?{CIP6^h!`URt1@spJ4AUfG8*p-ElZ9>W}umY=HNi{;&jj?lC-GFMFumN zGfdbVb>qe));~(v`DJ2a-3xY1Cad+-Zv_rO&Vs<35X|!JEZ@$k;}KEEx~Gm*U`RA6 zV9d5m`OOk!lhLp=w{H6}w+)>{f+wtz%UK$pVd+W7(yGT&he$pZK$^$~)z^g7_r=h{ zhb0WP|5ZAkj-mAi7t#m``t}7y&O^yOqJBwate%>;gJPBLZnz*ZazIF%W^2PYwhVZB z4zLtdHa^2aGl*C~lKtY(=V{whHVvHNV{@3(cuAOS0Q z6-kz9Ww=P{8eB^$LE1A1l_)qUjR}k=l~#psPJ7?zrbJ-5{tVsZXq6+xRGt~mL*+eh zje2K$&^z;6lbo~8+azTJlAx*ua%93d)7=nP6xL~1;M*Nlt-(5a%vo2bh<1D>D{!K) z{JGw-t!ZD-+HG#aV2YK0mGIDYW166+OEL5J*n@V)YF==dGCR)|ITM!gUz;8kTSgURh=bM3XR%ZZIw$Tk}H+YsS!rz|e=@gPkmTFqUe2PSWFAD~=26Bp)JUDEEg_5PUuReZ|B5 zzNe9=+3e_Qc64=cM^|TyQj93orvq~sAb|3=36p*vQF*=iP1a~bGgQGJVnef$V-`Pc zaxG59zub^unDu@xhIZFBu3o(K_8XV3-&nu8QK>ASaWpzmAMKAv{{dbCNWr!y003mN B9$Wwb literal 1975 zcmV;o2T1rqRzV2YAgPA=J z@|NTt(jSWm00000000B+T+NRZMHB}YFxi{uJ@~R58VEh>0t&7HU6v0wL?9ptZZ=ZW zT{BZ`Pgh%2y|a^Of}Tw9pb3c{#dtH(7;h#fo=p4$Og!_K`0As3rguJe_*__OsLe3b z)vsQ?dhhppud8`NDP(y)!CnoX#3%|c znf}mV36;PrBJ@CIk^;K9!Vf-0a{9Ba$#;RN?5HJDB|ro_x}*3 z>_?;D#z8`B6dcQlxss=;F-^Zs%=BPZGDSnJWysQ)ZiHIv*Ig)+@dD(=Z;OCj zCrM7VR~1qe-B2h2*VHZId`q9kEbQ4>8FwI}q;^gMu8 zsTH=ucZ3|oAYdunXv>4sXis=>@9S-!)rj_u)$hxPj#YhA?_P@%#!M;ifS@Gq0^*xS zDg-LyJ}oZQg0b7?yB~Grd)sZjQSJMJ4UafB%;hE?f=-L35QZ4x&_E;rCLtqI(s+;# z_x@L*!+-XUqQlX&2Xz_|twdmk4D6U1B`p0g8b1<0%vsv>H1SZ))5K0$L{AeFgSDrL zfmYJQ+v91XAxN9*dPB1W)&<(L_S=3#il<*IZ1Ka$;%O4L(A7$<9DX&hlwmapoC<-j zseDcK^xC$O<*=)s1cxk+C&7uS$dlkeD@kzKkzm5nYg)97Db{eQX%%4grCYAgElrFz zvCV}qp|*bQ4B+u4)b=mLV=tm-kBKqdv&TRy*<;bMM~8@XEM+Jnnp9opLT$+!`#KfY zXgyXbHJ&w=n<*DsNYK+SDC!lIoJQ15i3}a5=JcQthW3JW5-kT1R!kwc`NFD>uhIa9 zbgRl|*vXUHGi#pIcFH1pQk$4sJgE(|lGNUJq$V()dbN&uCRpdH)%V>1wGlzba@!QE zaNEaIqqxnZ_k_(qsCtf@v4R6-Vf?w@*UfX}D3=QJ85=ayEt(|U+%Zd|76w%u&s7=irEwXG#21cq513O!A!k*h1#yA8m>lb zd2{N|3fBDMvF3%v8eiaTFD9WS4cOrO5D2p6;!fh-HkE*btx404 z6BWo}jb6x~-0yOpdaiogEfU&HYL^(qtm;A91+EU7%v$^_3ST8&Cmp$PhE zjy@b)Bn?W6`18g`DE92S#GJ}BP&VBhjM_cFSEBZ( zJtMawUfOe59hf|&D6hAfO(95NM>CSj(aZE_W2shLX}lF$^Pp}S?E zTpl7}RMCua+s>55y*92g)7zM3W@EFb;+Ob_oZ$_Y&a50ger9In_?t`k=`{Xam|2-$tiGS0SZ_V<7_Ck{q*lk~pNLkQ#|$eY z;^gPG1?HEg7vs1uC5DqQ8<|@}TNaTln+^0D=)LGFo$!XGkqY}@PqNOLt_4hTow&}< z(^3QtP^?WoL3o1j1o2!yrnulpBIU~5-UEp;&C`YOBUZh8?pDa-GoL^E{Mo~!hsSN* z5I$UN2(v_)<8);vY{aG9#JpRfA;%N{s9@U9{?BAQ+h6Hw=1JyHIC|)ODWTJ!aNO$U zPdJWl Date: Tue, 3 Jan 2023 14:49:44 +0000 Subject: [PATCH 10/29] refactor: conditionally set `quiet` flag --- src/commands/dev/dev.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 6f6844dd54c..59da5fc105a 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -746,10 +746,12 @@ const runBuild = async ({ cachedConfig, options, settings, site }) => { return { configPath: tempConfigPath } } - // Enable `quiet` to suppress non-essential output from Netlify Build. const startDevOptions = { ...sharedOptions, - quiet: true, + + // Set `quiet` to suppress non-essential output from Netlify Build unless + // the `debug` flag is set. + quiet: !options.debug, } // Run Netlify Build using the `startDev` entry point. From 1a890502d74a7510e8c737ac4d5a92d98f3eda1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 14:55:22 +0000 Subject: [PATCH 11/29] refactor: simplify code --- src/lib/functions/server.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/functions/server.mjs b/src/lib/functions/server.mjs index dfffe37e989..49d497f9717 100644 --- a/src/lib/functions/server.mjs +++ b/src/lib/functions/server.mjs @@ -224,7 +224,9 @@ export const startFunctionsServer = async (options) => { } else { // The order of the function directories matters. Leftmost directories take // precedence. - functionsDirectories.push(...[settings.functions, internalFunctionsDir].filter(Boolean)) + const sourceDirectories = [settings.functions, internalFunctionsDir].filter(Boolean) + + functionsDirectories.push(...sourceDirectories) } if (functionsDirectories.length === 0) { From 2d8d2d0c0e39a12b64d64aae62c67f355ebfeba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 14:55:27 +0000 Subject: [PATCH 12/29] chore: update docs --- docs/commands/dev.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/commands/dev.md b/docs/commands/dev.md index 637361953c4..e62d6190420 100644 --- a/docs/commands/dev.md +++ b/docs/commands/dev.md @@ -30,6 +30,7 @@ netlify dev - `live` (*boolean*) - start a public live session - `offline` (*boolean*) - disables any features that require network access - `port` (*string*) - port of netlify dev +- `serve` (*boolean*) - run in "serve" mode - `sessionId` (*string*) - (Graph) connect to cloud session with ID [sessionId] - `targetPort` (*string*) - port of target app server - `debug` (*boolean*) - Print debugging information From 78c35b68bf54712bc6230c0c7d874f5a9ac5c780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 15:59:30 +0000 Subject: [PATCH 13/29] refactor: rename flag to `prod` --- docs/commands/dev.md | 2 +- src/commands/dev/dev.mjs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/commands/dev.md b/docs/commands/dev.md index e62d6190420..6f8782c19fc 100644 --- a/docs/commands/dev.md +++ b/docs/commands/dev.md @@ -30,7 +30,7 @@ netlify dev - `live` (*boolean*) - start a public live session - `offline` (*boolean*) - disables any features that require network access - `port` (*string*) - port of netlify dev -- `serve` (*boolean*) - run in "serve" mode +- `prod` (*boolean*) - build the site for production - `sessionId` (*string*) - (Graph) connect to cloud session with ID [sessionId] - `targetPort` (*string*) - port of target app server - `debug` (*boolean*) - Print debugging information diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 59da5fc105a..a6422dcc75a 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -460,9 +460,9 @@ const dev = async (options, command) => { let { env } = cachedConfig - // If the `serve` flag is present, we override the `framework` value so that + // If the `prod` flag is present, we override the `framework` value so that // we start a static server and not the framework's development server. - if (options.serve) { + if (options.prod) { devConfig.framework = '#static' } @@ -524,7 +524,7 @@ const dev = async (options, command) => { // When using the `serve` flag, we want to use the production functions built // by Netlify Build rather than building them from source. - const loadDistFunctions = Boolean(options.serve) + const loadDistFunctions = Boolean(options.prod) await startFunctionsServer({ api, @@ -719,7 +719,7 @@ const runBuild = async ({ cachedConfig, options, settings, site }) => { settings.frameworkHost = ipVersion === 6 ? '::1' : '127.0.0.1' } - if (options.serve) { + if (options.prod) { // Start by cleaning the internal directory, as it may have artifacts left // by previous builds. await cleanInternalDirectory(site.root) @@ -823,7 +823,7 @@ export const createDevCommand = (program) => { .option('-o ,--offline', 'disables any features that require network access') .option('-l, --live', 'start a public live session', false) .option('--functionsPort ', 'port of functions server', (value) => Number.parseInt(value)) - .option('-s, --serve', 'run in "serve" mode', false) + .option('-P, --prod', 'build the site for production', false) .addOption( new Option( '--geo ', From 0bcc9a26965ca1c84525f17ad429c5607e0063dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 16:02:41 +0000 Subject: [PATCH 14/29] refactor: update message --- src/commands/dev/dev.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index a6422dcc75a..a1511544679 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -518,7 +518,11 @@ const dev = async (options, command) => { startPollingForAPIAuthentication({ api, command, config, site, siteInfo }) } - log(`${NETLIFYDEVWARN} Setting up local development server`) + if (options.prod) { + log(`${NETLIFYDEVWARN} Building site for production`) + } else { + log(`${NETLIFYDEVWARN} Setting up local development server`) + } const { configPath: configPathOverride } = await runBuild({ cachedConfig, options, settings, site }) From 40900139baf949b843d0184b2a47cd12863cfcd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 17:07:07 +0000 Subject: [PATCH 15/29] feat: do not set `NETLIFY_DEV` when `prod` is set --- .eslintrc.cjs | 6 ++ src/commands/dev/dev.mjs | 2 +- src/utils/dev.mjs | 6 +- .../600.framework-detection.test.cjs | 44 ++++++++++++ .../600.framework-detection.test.cjs.md | 68 ++++++++++++++++++ .../600.framework-detection.test.cjs.snap | Bin 1655 -> 2075 bytes tests/integration/utils/dev-server.cjs | 14 +++- 7 files changed, 136 insertions(+), 4 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f6c67316b8e..b7c1e863718 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -98,5 +98,11 @@ module.exports = { ], }, }, + { + files: ['tests/integration/**/*'], + rules: { + 'require-await': 'off', + }, + }, ], } diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index a1511544679..4f2b4ed8dd7 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -471,7 +471,7 @@ const dev = async (options, command) => { log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`) } - await injectEnvVariables({ devConfig, env, site }) + await injectEnvVariables({ devConfig, env, prod: options.prod, site }) await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state }) const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({ diff --git a/src/utils/dev.mjs b/src/utils/dev.mjs index 294c2347e2b..23a5b369556 100644 --- a/src/utils/dev.mjs +++ b/src/utils/dev.mjs @@ -139,7 +139,7 @@ const getEnvSourceName = (source) => { // Takes a set of environment variables in the format provided by @netlify/config, augments it with variables from both // dot-env files and the process itself, and injects into `process.env`. -export const injectEnvVariables = async ({ devConfig, env, site }) => { +export const injectEnvVariables = async ({ devConfig, env, prod, site }) => { const environment = new Map(Object.entries(env)) const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root }) @@ -183,7 +183,9 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => { } } - process.env.NETLIFY_DEV = 'true' + if (prod !== true) { + process.env.NETLIFY_DEV = 'true' + } } export const acquirePort = async ({ configuredPort, defaultPort, errorMessage }) => { diff --git a/tests/integration/600.framework-detection.test.cjs b/tests/integration/600.framework-detection.test.cjs index a365abd91f5..7b48707f583 100644 --- a/tests/integration/600.framework-detection.test.cjs +++ b/tests/integration/600.framework-detection.test.cjs @@ -332,3 +332,47 @@ test('should start static service for frameworks without port, detected framewor t.true(error.stdout.includes(`Failed running command: remix watch. Please verify 'remix' exists`)) }) }) + +test('should run and serve a production build when the `--prod` flag is set', async (t) => { + await withSiteBuilder('site-with-framework', async (builder) => { + await builder + .withNetlifyToml({ + config: { + build: { publish: 'public' }, + functions: { directory: 'functions' }, + plugins: [{ package: './plugins/frameworker' }], + }, + }) + .withBuildPlugin({ + name: 'frameworker', + plugin: { + onPreBuild: async ({ netlifyConfig }) => { + // eslint-disable-next-line n/global-require + const { mkdir, writeFile } = require('fs').promises + + const generatedFunctionsDir = 'new_functions' + netlifyConfig.functions.directory = generatedFunctionsDir + + netlifyConfig.redirects.push({ + from: '/hello', + to: '/.netlify/functions/hello', + }) + + await mkdir(generatedFunctionsDir) + await writeFile( + `${generatedFunctionsDir}/hello.js`, + `exports.handler = async () => ({ statusCode: 200, body: 'Hello! NETLIFY_DEV is ' + process.env.NETLIFY_DEV })`, + ) + }, + }, + }) + .buildAsync() + + await withDevServer({ cwd: builder.directory, debug: true, prod: true }, async ({ output, url }) => { + const response = await got(`${url}/hello`).text() + t.is(response, 'Hello! NETLIFY_DEV is undefined') + + t.snapshot(normalize(output, { duration: true, filePath: true })) + }) + }) +}) diff --git a/tests/integration/snapshots/600.framework-detection.test.cjs.md b/tests/integration/snapshots/600.framework-detection.test.cjs.md index 674ecf6d3c6..550d5cc7747 100644 --- a/tests/integration/snapshots/600.framework-detection.test.cjs.md +++ b/tests/integration/snapshots/600.framework-detection.test.cjs.md @@ -237,3 +237,71 @@ Generated by [AVA](https://avajs.dev). ␊ 14␊ ◈ "npm run dev" exited with code *. Shutting down Netlify Dev server` + +## should run and serve a production build when the `--prod` flag is set + +> Snapshot 1 + + `◈ Netlify Dev ◈␊ + ◈ Using simple static server because '[dev.framework]' was set to '#static'␊ + ◈ Running static server from "site-with-framework/public"␊ + ◈ Building site for production␊ + ​␊ + Netlify Build ␊ + ────────────────────────────────────────────────────────────────␊ + ​␊ + > Version␊ + @netlify/build 0.0.0␊ + ​␊ + > Flags␊ + offline: true␊ + outputConfigPath:/file/path␊ + ​␊ + > Current directory␊ + /file/path␊ + ​␊ + > Config file␊ + /file/path␊ + ​␊ + > Context␊ + dev␊ + ​␊ + > Loading plugins␊ + -/file/path from netlify.toml␊ + ​␊ + 1./file/path (onPreBuild event) ␊ + ────────────────────────────────────────────────────────────────␊ + ​␊ + Netlify configuration property "redirects" value changed to [ { from: /file/path', to: /file/path' } ].␊ + ​␊ + /file/path onPreBuild completed in Xms)␊ + ​␊ + 2. Functions bundling ␊ + ────────────────────────────────────────────────────────────────␊ + ​␊ + Packaging Functions from new_functions directory:␊ + - hello.js␊ + ​␊ + ​␊ + (Functions bundling completed in Xms)␊ + ​␊ + 3. Save deploy artifacts ␊ + ────────────────────────────────────────────────────────────────␊ + ​␊ + ​␊ + (Save deploy artifacts completed in Xms)␊ + ​␊ + Netlify Build Complete ␊ + ────────────────────────────────────────────────────────────────␊ + ​␊ + (Netlify Build completed in Xms)␊ + ␊ + ◈ Static server listening to 88888␊ + ◈ Loaded function hello http://localhost:88888/.netlify/functions/hello.␊ + ◈ Functions server is listening on 88888␊ + ␊ + ┌──────────────────────────────────────────────────┐␊ + │ │␊ + │ ◈ Server now ready on http://localhost:88888 │␊ + │ │␊ + └──────────────────────────────────────────────────┘` diff --git a/tests/integration/snapshots/600.framework-detection.test.cjs.snap b/tests/integration/snapshots/600.framework-detection.test.cjs.snap index 15d926fb618c28a6194d76c9c4124a281499efca..4613081fb5ec0c7e9e42706b6dff75c947c1a2f0 100644 GIT binary patch literal 2075 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 B9$Wwb diff --git a/tests/integration/utils/dev-server.cjs b/tests/integration/utils/dev-server.cjs index 743dda887d9..4802afe4e5c 100644 --- a/tests/integration/utils/dev-server.cjs +++ b/tests/integration/utils/dev-server.cjs @@ -29,6 +29,7 @@ const startServer = async ({ env = {}, args = [], expectFailure = false, + prod = false, prompt, }) => { const port = await getPort() @@ -40,7 +41,18 @@ const startServer = async ({ const ps = execa( cliPath, - ['dev', offline ? '--offline' : '', '-p', port, '--staticServerPort', staticPort, '--context', context, ...args], + [ + 'dev', + offline ? '--offline' : '', + '-p', + port, + '--staticServerPort', + staticPort, + '--context', + context, + prod ? '--prod' : '', + ...args, + ], getExecaOptions({ cwd, env }), ) From 9be736bb94d78f17957c0470951d1af3addb956b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 17:12:12 +0000 Subject: [PATCH 16/29] chore: remove lint exceptions --- .eslintrc.cjs | 2 +- tests/integration/100.command.dev.test.cjs | 3 --- tests/integration/20.command.functions.test.cjs | 3 --- tests/integration/200.command.dev.test.cjs | 2 -- tests/integration/210.command.deploy.test.cjs | 2 -- tests/integration/300.command.dev.test.cjs | 2 -- tests/integration/330.serving-functions.test.cjs | 2 -- tests/integration/400.command.dev.test.cjs | 2 -- tests/integration/500.command.dev.test.cjs | 2 -- tests/integration/eleventy-site/functions/echo.cjs | 1 - tests/unit/utils/deploy/hash-fns.test.mjs | 2 -- 11 files changed, 1 insertion(+), 22 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b7c1e863718..3c72e3fff64 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -99,7 +99,7 @@ module.exports = { }, }, { - files: ['tests/integration/**/*'], + files: ['tests/**/*'], rules: { 'require-await': 'off', }, diff --git a/tests/integration/100.command.dev.test.cjs b/tests/integration/100.command.dev.test.cjs index f1ce6a11d76..bdc83820d58 100644 --- a/tests/integration/100.command.dev.test.cjs +++ b/tests/integration/100.command.dev.test.cjs @@ -1,5 +1,4 @@ // Handlers are meant to be async outside tests -/* eslint-disable require-await */ const path = require('path') // eslint-disable-next-line ava/use-test @@ -933,5 +932,3 @@ test('should have only allowed environment variables set', async (t) => { }) }) }) - -/* eslint-enable require-await */ diff --git a/tests/integration/20.command.functions.test.cjs b/tests/integration/20.command.functions.test.cjs index 9b033939e1f..7ceeb15dc43 100644 --- a/tests/integration/20.command.functions.test.cjs +++ b/tests/integration/20.command.functions.test.cjs @@ -1,5 +1,4 @@ // Handlers are meant to be async outside tests -/* eslint-disable require-await */ // eslint-disable-next-line ava/use-test const avaTest = require('ava') const { isCI } = require('ci-info') @@ -937,5 +936,3 @@ test('should handle content-types with charset', async (t) => { }) }) }) - -/* eslint-enable require-await */ diff --git a/tests/integration/200.command.dev.test.cjs b/tests/integration/200.command.dev.test.cjs index 964c89b6f72..ee773b90a12 100644 --- a/tests/integration/200.command.dev.test.cjs +++ b/tests/integration/200.command.dev.test.cjs @@ -1,5 +1,4 @@ // Handlers are meant to be async outside tests -/* eslint-disable require-await */ const { copyFile } = require('fs').promises const os = require('os') const path = require('path') @@ -439,4 +438,3 @@ export const handler = async function () { }) }) }) -/* eslint-enable require-await */ diff --git a/tests/integration/210.command.deploy.test.cjs b/tests/integration/210.command.deploy.test.cjs index 723347acc0e..c3dccc425b6 100644 --- a/tests/integration/210.command.deploy.test.cjs +++ b/tests/integration/210.command.deploy.test.cjs @@ -1,4 +1,3 @@ -/* eslint-disable require-await */ const { join } = require('path') const process = require('process') @@ -795,4 +794,3 @@ if (process.env.NETLIFY_TEST_DISABLE_LIVE !== 'true') { test('always pass, used for forked PRs since ava fails when no tests are present', (t) => { t.pass() }) -/* eslint-enable require-await */ diff --git a/tests/integration/300.command.dev.test.cjs b/tests/integration/300.command.dev.test.cjs index 9a9ba7ce2cf..85887a9063d 100644 --- a/tests/integration/300.command.dev.test.cjs +++ b/tests/integration/300.command.dev.test.cjs @@ -1,5 +1,4 @@ // Handlers are meant to be async outside tests -/* eslint-disable require-await */ const path = require('path') const process = require('process') @@ -281,4 +280,3 @@ test('should inject env vars based on [dev].envFiles file order', async (t) => { }) }) }) -/* eslint-enable require-await */ diff --git a/tests/integration/330.serving-functions.test.cjs b/tests/integration/330.serving-functions.test.cjs index abc756702f2..29d3139eda6 100644 --- a/tests/integration/330.serving-functions.test.cjs +++ b/tests/integration/330.serving-functions.test.cjs @@ -1,4 +1,3 @@ -/* eslint-disable require-await */ const { join } = require('path') // eslint-disable-next-line ava/use-test @@ -962,4 +961,3 @@ test('Ensures watcher watches included files', async (t) => { }) }) }) -/* eslint-enable require-await */ diff --git a/tests/integration/400.command.dev.test.cjs b/tests/integration/400.command.dev.test.cjs index 1ba6b103a58..aeb01aa62c5 100644 --- a/tests/integration/400.command.dev.test.cjs +++ b/tests/integration/400.command.dev.test.cjs @@ -1,5 +1,4 @@ // Handlers are meant to be async outside tests -/* eslint-disable require-await */ const process = require('process') // eslint-disable-next-line ava/use-test @@ -413,4 +412,3 @@ test('should handle multipart form data when redirecting', async (t) => { }) }) }) -/* eslint-enable require-await */ diff --git a/tests/integration/500.command.dev.test.cjs b/tests/integration/500.command.dev.test.cjs index 9cbe348a7c3..a509df87394 100644 --- a/tests/integration/500.command.dev.test.cjs +++ b/tests/integration/500.command.dev.test.cjs @@ -1,5 +1,4 @@ // Handlers are meant to be async outside tests -/* eslint-disable require-await */ const { promises: fs } = require('fs') const path = require('path') @@ -472,4 +471,3 @@ test('Handles errors from the `onPreDev` event', async (t) => { ) }) }) -/* eslint-enable require-await */ diff --git a/tests/integration/eleventy-site/functions/echo.cjs b/tests/integration/eleventy-site/functions/echo.cjs index 357343c7ace..a46ecb8192b 100644 --- a/tests/integration/eleventy-site/functions/echo.cjs +++ b/tests/integration/eleventy-site/functions/echo.cjs @@ -1,4 +1,3 @@ -// eslint-disable-next-line require-await const handler = async (event) => ({ statusCode: 200, body: JSON.stringify(event), diff --git a/tests/unit/utils/deploy/hash-fns.test.mjs b/tests/unit/utils/deploy/hash-fns.test.mjs index eef29ec6aff..5054ced118a 100644 --- a/tests/unit/utils/deploy/hash-fns.test.mjs +++ b/tests/unit/utils/deploy/hash-fns.test.mjs @@ -11,12 +11,10 @@ test('Hashes files in a folder', async () => { .withNetlifyToml({ config: { functions: { directory: 'functions' } } }) .withFunction({ path: 'hello.js', - // eslint-disable-next-line require-await handler: async () => ({ statusCode: 200, body: 'Hello' }), }) .withFunction({ path: 'goodbye.js', - // eslint-disable-next-line require-await handler: async () => ({ statusCode: 200, body: 'Goodbye' }), }) .buildAsync() From e3eea4a645576a0dbe58f0378badfb8b8a485b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 17:36:41 +0000 Subject: [PATCH 17/29] Apply suggestions from code review Co-authored-by: Matt Kane --- docs/commands/dev.md | 2 +- src/commands/dev/dev.mjs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/commands/dev.md b/docs/commands/dev.md index 6f8782c19fc..80501e90a69 100644 --- a/docs/commands/dev.md +++ b/docs/commands/dev.md @@ -30,7 +30,7 @@ netlify dev - `live` (*boolean*) - start a public live session - `offline` (*boolean*) - disables any features that require network access - `port` (*string*) - port of netlify dev -- `prod` (*boolean*) - build the site for production +- `prod` (*boolean*) - build the site for production and serve locally. Changes to source files will not be visible until you restart the server. - `sessionId` (*string*) - (Graph) connect to cloud session with ID [sessionId] - `targetPort` (*string*) - port of target app server - `debug` (*boolean*) - Print debugging information diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 4f2b4ed8dd7..c5dbe35b83b 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -526,7 +526,7 @@ const dev = async (options, command) => { const { configPath: configPathOverride } = await runBuild({ cachedConfig, options, settings, site }) - // When using the `serve` flag, we want to use the production functions built + // When using the `prod` flag, we want to use the production functions built // by Netlify Build rather than building them from source. const loadDistFunctions = Boolean(options.prod) @@ -827,7 +827,7 @@ export const createDevCommand = (program) => { .option('-o ,--offline', 'disables any features that require network access') .option('-l, --live', 'start a public live session', false) .option('--functionsPort ', 'port of functions server', (value) => Number.parseInt(value)) - .option('-P, --prod', 'build the site for production', false) + .option('-P, --prod', 'build the site for production and serve locally', false) .addOption( new Option( '--geo ', From 29342333651a437275e85bfb28d991e190e7d92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 18:00:18 +0000 Subject: [PATCH 18/29] refactor: treat `NETLIFY_DEV` consistently across function types --- src/commands/dev/dev.mjs | 7 ++++++- src/lib/edge-functions/registry.mjs | 7 ++----- src/utils/dev.mjs | 6 +----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index c5dbe35b83b..0572624af7a 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -460,6 +460,11 @@ const dev = async (options, command) => { let { env } = cachedConfig + // Add `NETLIFY_DEV` to the environment variables unless `prod` is set. + if (!options.prod) { + env.NETLIFY_DEV = { sources: ['general'], value: 'true' } + } + // If the `prod` flag is present, we override the `framework` value so that // we start a static server and not the framework's development server. if (options.prod) { @@ -566,7 +571,7 @@ const dev = async (options, command) => { addonsUrls, config, configPath: configPathOverride, - env: command.netlify.cachedConfig.env, + env, geolocationMode: options.geo, geoCountry: options.country, getUpdatedConfig, diff --git a/src/lib/edge-functions/registry.mjs b/src/lib/edge-functions/registry.mjs index f5aeb7d5077..92689b7c3be 100644 --- a/src/lib/edge-functions/registry.mjs +++ b/src/lib/edge-functions/registry.mjs @@ -213,17 +213,14 @@ export class EdgeFunctionsRegistry { if ( variable.sources.includes('ui') || variable.sources.includes('account') || - variable.sources.includes('addons') + variable.sources.includes('addons') || + key === 'NETLIFY_DEV' ) { env[key] = variable.value } }) env.DENO_REGION = 'local' - env.NETLIFY_DEV = 'true' - // We use it in the bootstrap layer to detect whether we're running in production or not - // (see https://github.com/netlify/edge-functions-bootstrap/blob/main/src/bootstrap/environment.ts#L2) - // env.DENO_DEPLOYMENT_ID = 'xxx=' return env } diff --git a/src/utils/dev.mjs b/src/utils/dev.mjs index 23a5b369556..22f3ea3a401 100644 --- a/src/utils/dev.mjs +++ b/src/utils/dev.mjs @@ -139,7 +139,7 @@ const getEnvSourceName = (source) => { // Takes a set of environment variables in the format provided by @netlify/config, augments it with variables from both // dot-env files and the process itself, and injects into `process.env`. -export const injectEnvVariables = async ({ devConfig, env, prod, site }) => { +export const injectEnvVariables = async ({ devConfig, env, site }) => { const environment = new Map(Object.entries(env)) const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root }) @@ -182,10 +182,6 @@ export const injectEnvVariables = async ({ devConfig, env, prod, site }) => { process.env[key] = variable.value } } - - if (prod !== true) { - process.env.NETLIFY_DEV = 'true' - } } export const acquirePort = async ({ configuredPort, defaultPort, errorMessage }) => { From ebb233467d788abbc071a82adab463c7680519cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 18:23:03 +0000 Subject: [PATCH 19/29] refactor: add `internal` source for env vars --- src/commands/dev/dev.mjs | 2 +- src/commands/functions/functions-serve.mjs | 5 ++++- src/lib/edge-functions/registry.mjs | 2 +- src/utils/dev.mjs | 8 ++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 0572624af7a..aec82494778 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -462,7 +462,7 @@ const dev = async (options, command) => { // Add `NETLIFY_DEV` to the environment variables unless `prod` is set. if (!options.prod) { - env.NETLIFY_DEV = { sources: ['general'], value: 'true' } + env.NETLIFY_DEV = { sources: ['internal'], value: 'true' } } // If the `prod` flag is present, we override the `framework` value so that diff --git a/src/commands/functions/functions-serve.mjs b/src/commands/functions/functions-serve.mjs index a349d5f4b84..9b077fc2157 100644 --- a/src/commands/functions/functions-serve.mjs +++ b/src/commands/functions/functions-serve.mjs @@ -16,8 +16,11 @@ const functionsServe = async (options, command) => { const { api, config, site, siteInfo } = command.netlify const functionsDir = getFunctionsDir({ options, config }, join('netlify', 'functions')) + const { env } = command.netlify.cachedConfig - await injectEnvVariables({ devConfig: { ...config.dev }, env: command.netlify.cachedConfig.env, site }) + env.NETLIFY_DEV = { sources: ['internal'], value: 'true' } + + await injectEnvVariables({ devConfig: { ...config.dev }, env, site }) const { capabilities, siteUrl, timeouts } = await getSiteInformation({ offline: options.offline, diff --git a/src/lib/edge-functions/registry.mjs b/src/lib/edge-functions/registry.mjs index 92689b7c3be..c658b9529e8 100644 --- a/src/lib/edge-functions/registry.mjs +++ b/src/lib/edge-functions/registry.mjs @@ -214,7 +214,7 @@ export class EdgeFunctionsRegistry { variable.sources.includes('ui') || variable.sources.includes('account') || variable.sources.includes('addons') || - key === 'NETLIFY_DEV' + variable.sources.includes('internal') ) { env[key] = variable.value } diff --git a/src/utils/dev.mjs b/src/utils/dev.mjs index 22f3ea3a401..54f0db3c272 100644 --- a/src/utils/dev.mjs +++ b/src/utils/dev.mjs @@ -148,6 +148,10 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => { const newSourceName = `${file} file` const sources = environment.has(key) ? [newSourceName, ...environment.get(key).sources] : [newSourceName] + if (sources.includes('internal')) { + return + } + environment.set(key, { sources, value: fileEnv[key], @@ -174,8 +178,8 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => { }) if (!existsInProcess) { - // Omitting `general` env vars to reduce noise in the logs. - if (usedSource !== 'general') { + // Omitting `general` and `internal` env vars to reduce noise in the logs. + if (usedSource !== 'general' && usedSource !== 'internal') { log(`${NETLIFYDEVLOG} Injected ${usedSourceName} env var: ${chalk.yellow(key)}`) } From 01df8a357254bb894d27a64e430ea251a2663269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 18:39:09 +0000 Subject: [PATCH 20/29] fix: handle internal vars correctly --- src/utils/dev.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/dev.mjs b/src/utils/dev.mjs index 54f0db3c272..9bb7e7b3ee6 100644 --- a/src/utils/dev.mjs +++ b/src/utils/dev.mjs @@ -164,6 +164,7 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => { const existsInProcess = process.env[key] !== undefined const [usedSource, ...overriddenSources] = existsInProcess ? ['process', ...variable.sources] : variable.sources const usedSourceName = getEnvSourceName(usedSource) + const isInternal = variable.sources.includes('internal') overriddenSources.forEach((source) => { const sourceName = getEnvSourceName(source) @@ -177,9 +178,9 @@ export const injectEnvVariables = async ({ devConfig, env, site }) => { ) }) - if (!existsInProcess) { + if (!existsInProcess || isInternal) { // Omitting `general` and `internal` env vars to reduce noise in the logs. - if (usedSource !== 'general' && usedSource !== 'internal') { + if (usedSource !== 'general' && !isInternal) { log(`${NETLIFYDEVLOG} Injected ${usedSourceName} env var: ${chalk.yellow(key)}`) } From 59297c19eb9a1c2c60beeaaad6a7f7a5f704d755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 19:22:59 +0000 Subject: [PATCH 21/29] chore: update docs --- docs/commands/dev.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/commands/dev.md b/docs/commands/dev.md index 80501e90a69..b96bb77cc86 100644 --- a/docs/commands/dev.md +++ b/docs/commands/dev.md @@ -30,7 +30,7 @@ netlify dev - `live` (*boolean*) - start a public live session - `offline` (*boolean*) - disables any features that require network access - `port` (*string*) - port of netlify dev -- `prod` (*boolean*) - build the site for production and serve locally. Changes to source files will not be visible until you restart the server. +- `prod` (*boolean*) - build the site for production and serve locally - `sessionId` (*string*) - (Graph) connect to cloud session with ID [sessionId] - `targetPort` (*string*) - port of target app server - `debug` (*boolean*) - Print debugging information From 917fc13cd311307ae163a1cf7e3cfa45e8ebe237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Tue, 3 Jan 2023 19:29:52 +0000 Subject: [PATCH 22/29] refactor: remove unused variable --- src/commands/dev/dev.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index aec82494778..fd104e9c254 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -476,7 +476,7 @@ const dev = async (options, command) => { log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`) } - await injectEnvVariables({ devConfig, env, prod: options.prod, site }) + await injectEnvVariables({ devConfig, env, site }) await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state }) const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({ From ae74853c17f5113dae181fe57230fef02fd3c7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Wed, 4 Jan 2023 11:18:11 +0000 Subject: [PATCH 23/29] feat: set `context=production` when `prod` is used --- src/commands/base-command.mjs | 24 ++++++++++--- src/commands/dev/dev.mjs | 1 - .../600.framework-detection.test.cjs | 12 ++++--- .../600.framework-detection.test.cjs.md | 3 +- .../600.framework-detection.test.cjs.snap | Bin 2075 -> 2108 bytes tests/integration/utils/dev-server.cjs | 33 +++++++++--------- 6 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/commands/base-command.mjs b/src/commands/base-command.mjs index 5c30ce90a62..93c12f4f848 100644 --- a/src/commands/base-command.mjs +++ b/src/commands/base-command.mjs @@ -469,11 +469,7 @@ export default class BaseCommand extends Command { return await resolveConfig({ config: options.config, cwd, - context: - options.context || - process.env.CONTEXT || - // Dev commands have a default context of `dev`, otherwise we let netlify/config handle default behavior - (['dev', 'dev:exec'].includes(this.name()) ? 'dev' : undefined), + context: options.context || process.env.CONTEXT || this.getDefaultContext(), debug: this.opts().debug, siteId: options.siteId || (typeof options.site === 'string' && options.site) || state.get('siteId'), token, @@ -505,4 +501,22 @@ export default class BaseCommand extends Command { exit(1) } } + + /** + * Returns the context that should be used in case one hasn't been explicitly + * set. The default context is `dev` most of the time, but some commands may + * wish to override that. + * + * @returns {string} + */ + getDefaultContext() { + const { prod } = this.opts() + const isDevCommand = ['dev', 'dev:exec'].includes(this.name()) + + if (isDevCommand && prod) { + return 'production' + } + + return 'dev' + } } diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index fd104e9c254..6a9ade431cc 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -822,7 +822,6 @@ export const createDevCommand = (program) => { '--context ', 'Specify a deploy context or branch for environment variables (contexts: "production", "deploy-preview", "branch-deploy", "dev")', normalizeContext, - 'dev', ) .option('-p ,--port ', 'port of netlify dev', (value) => Number.parseInt(value)) .option('--targetPort ', 'port of target app server', (value) => Number.parseInt(value)) diff --git a/tests/integration/600.framework-detection.test.cjs b/tests/integration/600.framework-detection.test.cjs index 7b48707f583..c220ddd6754 100644 --- a/tests/integration/600.framework-detection.test.cjs +++ b/tests/integration/600.framework-detection.test.cjs @@ -339,6 +339,10 @@ test('should run and serve a production build when the `--prod` flag is set', as .withNetlifyToml({ config: { build: { publish: 'public' }, + context: { + dev: { environment: { CONTEXT_CHECK: 'DEV' } }, + production: { environment: { CONTEXT_CHECK: 'PRODUCTION' } }, + }, functions: { directory: 'functions' }, plugins: [{ package: './plugins/frameworker' }], }, @@ -361,16 +365,16 @@ test('should run and serve a production build when the `--prod` flag is set', as await mkdir(generatedFunctionsDir) await writeFile( `${generatedFunctionsDir}/hello.js`, - `exports.handler = async () => ({ statusCode: 200, body: 'Hello! NETLIFY_DEV is ' + process.env.NETLIFY_DEV })`, + `const { CONTEXT_CHECK, NETLIFY_DEV } = process.env; exports.handler = async () => ({ statusCode: 200, body: JSON.stringify({ CONTEXT_CHECK, NETLIFY_DEV }) })`, ) }, }, }) .buildAsync() - await withDevServer({ cwd: builder.directory, debug: true, prod: true }, async ({ output, url }) => { - const response = await got(`${url}/hello`).text() - t.is(response, 'Hello! NETLIFY_DEV is undefined') + await withDevServer({ cwd: builder.directory, context: null, debug: true, prod: true }, async ({ output, url }) => { + const response = await got(`${url}/hello`).json() + t.deepEqual(response, { CONTEXT_CHECK: 'PRODUCTION' }) t.snapshot(normalize(output, { duration: true, filePath: true })) }) diff --git a/tests/integration/snapshots/600.framework-detection.test.cjs.md b/tests/integration/snapshots/600.framework-detection.test.cjs.md index 550d5cc7747..626a9b17c4d 100644 --- a/tests/integration/snapshots/600.framework-detection.test.cjs.md +++ b/tests/integration/snapshots/600.framework-detection.test.cjs.md @@ -243,6 +243,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `◈ Netlify Dev ◈␊ + ◈ Injected netlify.toml file env var: CONTEXT_CHECK␊ ◈ Using simple static server because '[dev.framework]' was set to '#static'␊ ◈ Running static server from "site-with-framework/public"␊ ◈ Building site for production␊ @@ -264,7 +265,7 @@ Generated by [AVA](https://avajs.dev). /file/path␊ ​␊ > Context␊ - dev␊ + production␊ ​␊ > Loading plugins␊ -/file/path from netlify.toml␊ diff --git a/tests/integration/snapshots/600.framework-detection.test.cjs.snap b/tests/integration/snapshots/600.framework-detection.test.cjs.snap index 4613081fb5ec0c7e9e42706b6dff75c947c1a2f0..97490bb2fa7419083592d4c303bf264dc3aeaf13 100644 GIT binary patch literal 2108 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 literal 2075 zcmV+$2;}!cRzV;q5ZL$4$E3~L7*2F0*h+hG;rtZXGD zTCL)m?&_KL_Ea@h)iWNUNFh=rBsieR5?sTb6DOodi8yhB6L3VbfFoC~yjNA-(>*_% z_0QTNJL4J8_H@;&SMR;=d+*iECrLk((V_nMx7?&A`P`YWpP|Rb*Uo(7>}Tg5{`%)| zi<>M>rf{8)0k2#3GYKruy&PQ9@W}AE18?b63_z!&Jmby9?6kK8>*Ebnji#tkDmdgt zKf_Up%wiOUx9oUG(u`|JlpK{(qe3Yz3{0dtr0Y0UxT;iBEBX#MMGg#v)3#&eD6>Lf zlA0mj*pK=60EI^;mBOx*^N98}l?82KT<%75V^F^Cc)1*68EIgq2Sa0W-RpLvEbWvF zMRL^je}^dL0vi1uE)qJU;96G9?Lr7^nz~Kw_Moj(!$ZY&SusI zLQX(&7Eb}aO@2eSSsMu$PzjSE7Z8SsB}g(h zfK)@ywC3?5Ih_4>gB<>Tb`?3SCIRU4h>;S3T{3WMPMG#!OnLlRXz4lQhR@r@t#pLX>1)wv?g>W!;2lb zGc4TNv7Cs8T(|E00be2&at14TCaQHJ)?`PS<1YA+sb8PXmdRHERZF(x2OYNJcrT=BLsLxsR{{TZ6c z(W*d*=^`i2!^JCWEn4TWZk>&TRmRyBW0s{gNzgVDIWkE_YNjFXDD3m8#J798-GN=S znA5J$5$*U&dhlYY`8TU$TWViW+e1NNaDvw#un>b%v zK@|2r-g_YOkwv<9^Krxf<%Wx~5U5P_$_0C8D=4O?bnAjN-DV0Rq5$O|gXg4)?Y;I$0O4Snx;8=5Wqf3T+?`#o9T&M2|! zeipWPsk%$A+Frki*gtKc;CCm80!R?JX9x z8~<#W*#of^9Q%<{Y)kPoN@Y~oKQ4tEW>;z%}@DJ3P#@|hp(BTex8$ouJt ze-#Ui*FCC)G{k~^VWcm8&YJGI|^p38smZccedPWCy7$AX*F@?!2kLaRb{w8a& zp_!}TPq3l6RbUpsLb(>F;{Sq3FwCo;i=p0~TW?;!@!s1vZr|B`^H!_1^^&8}T7C3n zJbI&c;%9%CW;uzG3UT2Wq+g_&Z$7Dbxf_Ob^KSJs6{L|xf^E%Z+19i9p8a_2Q;BLE zqQ6S}bjn~UguOz|2(5=7`~s0sZom$GWHhy&H&~wZX{~S#-sVb^O#pu2^Fi0e$hSK9 z&kwqZ(V=mOOp*+INXw>q^!n^Np4?wpTU7v|zu*2`t2mz6Xt zyAL{lJdTlPT<0`9P!IZ+rbc&H@atf5u5^M|gX|lSt*>WqaGFvlY?D$cqD_A$1Mp!h} z@ydG|L?lk%9Qn9)A-$sX?Us#8w*o}hLBO3GR6D Date: Wed, 4 Jan 2023 11:18:52 +0000 Subject: [PATCH 24/29] refactor: add beta label --- docs/commands/dev.md | 2 +- src/commands/dev/dev.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/commands/dev.md b/docs/commands/dev.md index b96bb77cc86..b1b1f6f288f 100644 --- a/docs/commands/dev.md +++ b/docs/commands/dev.md @@ -30,7 +30,7 @@ netlify dev - `live` (*boolean*) - start a public live session - `offline` (*boolean*) - disables any features that require network access - `port` (*string*) - port of netlify dev -- `prod` (*boolean*) - build the site for production and serve locally +- `prod` (*boolean*) - (Beta) build the site for production and serve locally - `sessionId` (*string*) - (Graph) connect to cloud session with ID [sessionId] - `targetPort` (*string*) - port of target app server - `debug` (*boolean*) - Print debugging information diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index 6a9ade431cc..668d0229912 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -831,7 +831,7 @@ export const createDevCommand = (program) => { .option('-o ,--offline', 'disables any features that require network access') .option('-l, --live', 'start a public live session', false) .option('--functionsPort ', 'port of functions server', (value) => Number.parseInt(value)) - .option('-P, --prod', 'build the site for production and serve locally', false) + .option('-P, --prod', '(Beta) build the site for production and serve locally', false) .addOption( new Option( '--geo ', From dbd9dcc7b83890ef04ffa0ba14e288e00d70071f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 9 Jan 2023 12:33:25 +0000 Subject: [PATCH 25/29] chore: remove ESLint directive --- .../commands/functions-invoke/functions-invoke.test.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration/commands/functions-invoke/functions-invoke.test.mjs b/tests/integration/commands/functions-invoke/functions-invoke.test.mjs index 67e4d8c010b..20423c1bdee 100644 --- a/tests/integration/commands/functions-invoke/functions-invoke.test.mjs +++ b/tests/integration/commands/functions-invoke/functions-invoke.test.mjs @@ -1,4 +1,3 @@ -/* eslint-disable require-await */ import { describe, expect, test } from 'vitest' import callCli from '../../utils/call-cli.cjs' @@ -161,4 +160,3 @@ describe('functions:invoke command', () => { }) }) }) -/* eslint-enable require-await */ From bc5c59d2ee8af455c014852681d9eb238426b703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 9 Jan 2023 12:37:19 +0000 Subject: [PATCH 26/29] fix: fix imports --- src/commands/dev/dev.mjs | 2 +- src/lib/functions/registry.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index ade75172ff3..b57cf8a377d 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -28,7 +28,7 @@ import { getNetlifyGraphConfig, readGraphQLOperationsSourceFile, } from '../../lib/one-graph/cli-netlify-graph.mjs' -import { getPathInProject } from '../../lib/settings.cjs' +import { getPathInProject } from '../../lib/settings.mjs' import { startSpinner, stopSpinner } from '../../lib/spinner.mjs' import { BANG, diff --git a/src/lib/functions/registry.mjs b/src/lib/functions/registry.mjs index cdc26afca4c..e5e46c0b6ab 100644 --- a/src/lib/functions/registry.mjs +++ b/src/lib/functions/registry.mjs @@ -16,7 +16,7 @@ import { } from '../../utils/command-helpers.mjs' import { SERVE_FUNCTIONS_FOLDER } from '../../utils/functions/functions.mjs' import { getLogMessage } from '../log.mjs' -import { getPathInProject } from '../settings.cjs' +import { getPathInProject } from '../settings.mjs' import NetlifyFunction from './netlify-function.mjs' import runtimes from './runtimes/index.mjs' From 496783a9b9ee69d6f7d48526fc88fe9a1d3f8f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 9 Jan 2023 12:40:35 +0000 Subject: [PATCH 27/29] refactor: always start functions server --- src/utils/detect-server-settings.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils/detect-server-settings.mjs b/src/utils/detect-server-settings.mjs index 1e8c8b073e2..97796374c00 100644 --- a/src/utils/detect-server-settings.mjs +++ b/src/utils/detect-server-settings.mjs @@ -11,7 +11,6 @@ import isPlainObject from 'is-plain-obj' import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs' import { acquirePort } from './dev.mjs' -import { getInternalFunctionsDir } from './functions/index.mjs' const formatProperty = (str) => chalk.magenta(`'${str}'`) const formatValue = (str) => chalk.green(`'${str}'`) @@ -328,8 +327,7 @@ const detectServerSettings = async (devConfig, options, projectDir) => { errorMessage: `Could not acquire required ${formatProperty('port')}`, }) const functionsDir = devConfig.functions || settings.functions - const internalFunctionsDir = await getInternalFunctionsDir({ base: projectDir }) - const shouldStartFunctionsServer = Boolean(functionsDir || internalFunctionsDir) + const functionsPort = await getPort({ port: devConfig.functionsPort || 0 }) return { ...settings, @@ -337,7 +335,7 @@ const detectServerSettings = async (devConfig, options, projectDir) => { jwtSecret: devConfig.jwtSecret || 'secret', jwtRolePath: devConfig.jwtRolePath || 'app_metadata.authorization.roles', functions: functionsDir, - ...(shouldStartFunctionsServer && { functionsPort: await getPort({ port: devConfig.functionsPort || 0 }) }), + functionsPort, ...(devConfig.https && { https: await readHttpsSettings(devConfig.https) }), } } From a92f755757d80bc9535f92f6e3d10faca3782b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 9 Jan 2023 13:54:28 +0000 Subject: [PATCH 28/29] refactor: ensure internal functions directory --- src/commands/dev/dev.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/commands/dev/dev.mjs b/src/commands/dev/dev.mjs index b57cf8a377d..c56db4bc41a 100644 --- a/src/commands/dev/dev.mjs +++ b/src/commands/dev/dev.mjs @@ -460,9 +460,15 @@ const dev = async (options, command) => { let { env } = cachedConfig - // Add `NETLIFY_DEV` to the environment variables unless `prod` is set. if (!options.prod) { + // Add `NETLIFY_DEV` to the environment variables. env.NETLIFY_DEV = { sources: ['internal'], value: 'true' } + + // Ensure the internal functions directory exists so that any functions + // created by Netlify Build are loaded. + const fullPath = path.resolve(site.root, INTERNAL_FUNCTIONS_FOLDER) + + await fs.mkdir(fullPath, { recursive: true }) } // If the `prod` flag is present, we override the `framework` value so that From af007580b4b49c99444ea4413c8f502921c68df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Mon, 9 Jan 2023 13:59:56 +0000 Subject: [PATCH 29/29] refactor: reinstate functions server check --- src/utils/detect-server-settings.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/detect-server-settings.mjs b/src/utils/detect-server-settings.mjs index 97796374c00..1e8c8b073e2 100644 --- a/src/utils/detect-server-settings.mjs +++ b/src/utils/detect-server-settings.mjs @@ -11,6 +11,7 @@ import isPlainObject from 'is-plain-obj' import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs' import { acquirePort } from './dev.mjs' +import { getInternalFunctionsDir } from './functions/index.mjs' const formatProperty = (str) => chalk.magenta(`'${str}'`) const formatValue = (str) => chalk.green(`'${str}'`) @@ -327,7 +328,8 @@ const detectServerSettings = async (devConfig, options, projectDir) => { errorMessage: `Could not acquire required ${formatProperty('port')}`, }) const functionsDir = devConfig.functions || settings.functions - const functionsPort = await getPort({ port: devConfig.functionsPort || 0 }) + const internalFunctionsDir = await getInternalFunctionsDir({ base: projectDir }) + const shouldStartFunctionsServer = Boolean(functionsDir || internalFunctionsDir) return { ...settings, @@ -335,7 +337,7 @@ const detectServerSettings = async (devConfig, options, projectDir) => { jwtSecret: devConfig.jwtSecret || 'secret', jwtRolePath: devConfig.jwtRolePath || 'app_metadata.authorization.roles', functions: functionsDir, - functionsPort, + ...(shouldStartFunctionsServer && { functionsPort: await getPort({ port: devConfig.functionsPort || 0 }) }), ...(devConfig.https && { https: await readHttpsSettings(devConfig.https) }), } }