diff --git a/src/node_dependencies/index.js b/src/node_dependencies/index.js index 51e5a3b21..4b7c7d0d1 100644 --- a/src/node_dependencies/index.js +++ b/src/node_dependencies/index.js @@ -29,13 +29,14 @@ const listFilesUsingLegacyBundler = async function ({ featureFlags, srcPath, mainFile, + name, srcDir, stat, pluginsModulesPath, }) { const [treeFiles, depFiles] = await Promise.all([ getTreeFiles(srcPath, stat), - getDependencies(mainFile, srcDir, pluginsModulesPath, featureFlags), + getDependencies({ featureFlags, functionName: name, mainFile, pluginsModulesPath, srcDir }), ]) const files = [...treeFiles, ...depFiles].map(normalize) const uniqueFiles = [...new Set(files)] @@ -52,12 +53,19 @@ const isNotJunk = function (file) { } // Retrieve all the files recursively required by a Node.js file -const getDependencies = async function (mainFile, srcDir, pluginsModulesPath, featureFlags) { +const getDependencies = async function ({ featureFlags, functionName, mainFile, pluginsModulesPath, srcDir }) { const packageJson = await getPackageJson(srcDir) const state = getNewCache() try { - return await getFileDependencies({ featureFlags, path: mainFile, packageJson, pluginsModulesPath, state }) + return await getFileDependencies({ + featureFlags, + functionName, + path: mainFile, + packageJson, + pluginsModulesPath, + state, + }) } catch (error) { error.message = `In file "${mainFile}"\n${error.message}` throw error @@ -66,6 +74,7 @@ const getDependencies = async function (mainFile, srcDir, pluginsModulesPath, fe const getFileDependencies = async function ({ featureFlags, + functionName, path, packageJson, pluginsModulesPath, @@ -80,7 +89,7 @@ const getFileDependencies = async function ({ const basedir = dirname(path) const dependencies = featureFlags.parseWithEsbuild - ? await listImports({ path }) + ? await listImports({ functionName, path }) : precinct.paperwork(path, { includeCore: false }) const depsPaths = await Promise.all( dependencies.filter(Boolean).map((dependency) => @@ -88,6 +97,7 @@ const getFileDependencies = async function ({ dependency, basedir, featureFlags, + functionName, packageJson, pluginsModulesPath, state, @@ -104,6 +114,7 @@ const getImportDependencies = function ({ dependency, basedir, featureFlags, + functionName, packageJson, pluginsModulesPath, state, @@ -115,6 +126,7 @@ const getImportDependencies = function ({ dependency, basedir, featureFlags, + functionName, packageJson, pluginsModulesPath, state, @@ -134,6 +146,7 @@ const getTreeShakedDependencies = async function ({ dependency, basedir, featureFlags, + functionName, packageJson, pluginsModulesPath, state, @@ -142,6 +155,7 @@ const getTreeShakedDependencies = async function ({ const path = await resolvePathPreserveSymlinks(dependency, [basedir, pluginsModulesPath].filter(Boolean)) const depsPath = await getFileDependencies({ featureFlags, + functionName, path, packageJson, pluginsModulesPath, diff --git a/src/runtimes/node/bundler.js b/src/runtimes/node/bundler.js index 0c1d625e8..190eb9f93 100644 --- a/src/runtimes/node/bundler.js +++ b/src/runtimes/node/bundler.js @@ -3,7 +3,7 @@ const { basename, dirname, extname, resolve, join } = require('path') const esbuild = require('@netlify/esbuild') const { tmpName } = require('tmp-promise') -const { RUNTIME_JS } = require('../../utils/consts') +const { JS_BUNDLER_ESBUILD, RUNTIME_JS } = require('../../utils/consts') const { getPathWithExtension, safeUnlink } = require('../../utils/fs') const { getBundlerTarget } = require('./bundler_target') @@ -106,7 +106,10 @@ const bundleJsFile = async function ({ warnings, } } catch (error) { - error.customErrorInfo = { type: 'functionsBundling', location: { functionName: name, runtime: RUNTIME_JS } } + error.customErrorInfo = { + type: 'functionsBundling', + location: { bundler: JS_BUNDLER_ESBUILD, functionName: name, runtime: RUNTIME_JS }, + } throw error } diff --git a/src/runtimes/node/index.js b/src/runtimes/node/index.js index 64198ced7..c39175565 100644 --- a/src/runtimes/node/index.js +++ b/src/runtimes/node/index.js @@ -70,6 +70,7 @@ const zipFunction = async function ({ featureFlags, filename, mainFile, + name, pluginsModulesPath, srcDir, srcPath, diff --git a/src/runtimes/node/list_imports.js b/src/runtimes/node/list_imports.js index f67b4a858..a46f61f47 100644 --- a/src/runtimes/node/list_imports.js +++ b/src/runtimes/node/list_imports.js @@ -2,6 +2,7 @@ const esbuild = require('@netlify/esbuild') const isBuiltinModule = require('is-builtin-module') const { tmpName } = require('tmp-promise') +const { JS_BUNDLER_ZISI, RUNTIME_JS } = require('../../utils/consts') const { safeUnlink } = require('../../utils/fs') // Maximum number of log messages that an esbuild instance will produce. This @@ -28,7 +29,7 @@ const getListImportsPlugin = ({ imports, path }) => ({ }, }) -const listImports = async ({ path }) => { +const listImports = async ({ functionName, path }) => { // We're not interested in the output that esbuild generates, we're just // using it for its parsing capabilities in order to find import/require // statements. However, if we don't give esbuild a path in `outfile`, it @@ -49,6 +50,13 @@ const listImports = async ({ path }) => { plugins: [getListImportsPlugin({ imports, path })], target: 'esnext', }) + } catch (error) { + error.customErrorInfo = { + type: 'functionsBundling', + location: { bundler: JS_BUNDLER_ZISI, functionName, runtime: RUNTIME_JS }, + } + + throw error } finally { await safeUnlink(targetPath) } diff --git a/src/runtimes/node/src_files.js b/src/runtimes/node/src_files.js index 8df3adf8e..cd4e6bf25 100644 --- a/src/runtimes/node/src_files.js +++ b/src/runtimes/node/src_files.js @@ -57,6 +57,7 @@ const getSrcFilesAndExternalModules = async function ({ includedFiles = [], includedFilesBasePath, mainFile, + name, pluginsModulesPath, srcDir, srcPath, @@ -69,6 +70,7 @@ const getSrcFilesAndExternalModules = async function ({ featureFlags, srcPath, mainFile, + name, srcDir, stat, pluginsModulesPath, diff --git a/src/runtimes/node/zip_esbuild.js b/src/runtimes/node/zip_esbuild.js index 9639f58e6..d46eb3e4d 100644 --- a/src/runtimes/node/zip_esbuild.js +++ b/src/runtimes/node/zip_esbuild.js @@ -76,6 +76,7 @@ const zipEsbuild = async ({ includedFiles: [...(config.includedFiles || []), ...additionalPaths], includedFilesBasePath: config.includedFilesBasePath || basePath, mainFile, + name, srcPath, srcDir, pluginsModulesPath, diff --git a/src/runtimes/node/zip_zisi.js b/src/runtimes/node/zip_zisi.js index 3eead747a..374e990c6 100644 --- a/src/runtimes/node/zip_zisi.js +++ b/src/runtimes/node/zip_zisi.js @@ -15,6 +15,7 @@ const zipZisi = async ({ featureFlags, filename, mainFile, + name, pluginsModulesPath, srcDir, srcPath, @@ -27,6 +28,7 @@ const zipZisi = async ({ includedFiles: config.includedFiles, includedFilesBasePath: config.includedFilesBasePath || basePath, mainFile, + name, pluginsModulesPath, srcDir, srcPath, diff --git a/tests/fixtures/node-syntax-error/function.js b/tests/fixtures/node-syntax-error/function.js new file mode 100644 index 000000000..e6f310e74 --- /dev/null +++ b/tests/fixtures/node-syntax-error/function.js @@ -0,0 +1,5 @@ +const immutable = 1 + +immutable = 3 + +module.exports = { immutable } diff --git a/tests/main.js b/tests/main.js index b971bf68a..44c407330 100644 --- a/tests/main.js +++ b/tests/main.js @@ -24,7 +24,12 @@ const shellUtilsStub = sinon.stub(shellUtils, 'runCommand') const { zipFunction, listFunctions, listFunctionsFiles } = require('..') const { ESBUILD_LOG_LIMIT } = require('../src/runtimes/node/bundler') -const { JS_BUNDLER_ESBUILD: ESBUILD, JS_BUNDLER_ESBUILD_ZISI, JS_BUNDLER_ZISI } = require('../src/utils/consts') +const { + JS_BUNDLER_ESBUILD: ESBUILD, + JS_BUNDLER_ESBUILD_ZISI, + JS_BUNDLER_ZISI, + JS_BUNDLER_ESBUILD, +} = require('../src/utils/consts') const { getRequires, zipNode, zipFixture, unzipFiles, zipCheckFunctions, FIXTURES_DIR } = require('./helpers/main') const { computeSha1 } = require('./helpers/sha') @@ -1570,20 +1575,28 @@ test('Throws an error if the `archiveFormat` property contains an invalid value` ) }) -test('Adds `type: "functionsBundling"` to esbuild bundling errors', async (t) => { - try { - await zipNode(t, 'node-module-missing', { - opts: { config: { '*': { nodeBundler: ESBUILD } } }, - }) +testMany( + 'Adds `type: "functionsBundling"` to user errors (esbuild)', + ['bundler_default_parse_esbuild', 'bundler_esbuild'], + async (options, t) => { + const bundler = options.config['*'].nodeBundler - t.fail('Function did not throw') - } catch (error) { - t.deepEqual(error.customErrorInfo, { - type: 'functionsBundling', - location: { functionName: 'function', runtime: 'js' }, - }) - } -}) + try { + await zipNode(t, 'node-syntax-error', { + opts: options, + }) + + t.fail('Bundling should have thrown') + } catch (error) { + const { customErrorInfo } = error + + t.is(customErrorInfo.type, 'functionsBundling') + t.is(customErrorInfo.location.bundler, bundler === JS_BUNDLER_ESBUILD ? ESBUILD : JS_BUNDLER_ZISI) + t.is(customErrorInfo.location.functionName, 'function') + t.is(customErrorInfo.location.runtime, 'js') + } + }, +) test('Returns a list of all modules with dynamic imports in a `nodeModulesWithDynamicImports` property', async (t) => { const fixtureName = 'node-module-dynamic-import'