import { extname, join } from 'path'; import { copyFile } from 'copy-file'; import { INVOCATION_MODE } from '../../function.js'; import { Priority } from '../../priority.js'; import { getTrafficRulesConfig } from '../../rate_limit.js'; import getInternalValue from '../../utils/get_internal_value.js'; import { RUNTIME } from '../runtime.js'; import { getBundler, getBundlerName } from './bundlers/index.js'; import { NODE_BUNDLER } from './bundlers/types.js'; import { findFunctionsInPaths, findFunctionInPath } from './finder.js'; import { augmentFunctionConfig, parseFile } from './in_source_config/index.js'; import { MODULE_FORMAT, MODULE_FILE_EXTENSION } from './utils/module_format.js'; import { getNodeRuntime, getNodeRuntimeForV2 } from './utils/node_runtime.js'; import { createAliases as createPluginsModulesPathAliases, getPluginsModulesPath } from './utils/plugin_modules_path.js'; import { zipNodeJs } from './utils/zip.js'; // A proxy for the `getSrcFiles` that calls `getSrcFiles` on the bundler const getSrcFilesWithBundler = async (parameters) => { const { config, extension, featureFlags, mainFile, runtimeAPIVersion, srcDir } = parameters; const pluginsModulesPath = await getPluginsModulesPath(srcDir); const bundlerName = await getBundlerName({ config, extension, featureFlags, mainFile, runtimeAPIVersion, }); const bundler = getBundler(bundlerName); const result = await bundler.getSrcFiles({ ...parameters, pluginsModulesPath }); return result.srcFiles; }; const zipFunction = async function ({ archiveFormat, basePath, branch, cache, config = {}, destFolder, extension, featureFlags, filename, isInternal, logger, mainFile, name, repositoryRoot, runtime, srcDir, srcPath, stat, }) { // If the file is a zip, we assume the function is bundled and ready to go. // We simply copy it to the destination path with no further processing. if (extension === '.zip') { const destPath = join(destFolder, filename); await copyFile(srcPath, destPath); return { config, path: destPath, entryFilename: '' }; } const staticAnalysisResult = await parseFile(mainFile, { functionName: name }); const runtimeAPIVersion = staticAnalysisResult.runtimeAPIVersion === 2 ? 2 : 1; const mergedConfig = augmentFunctionConfig(mainFile, config, staticAnalysisResult.config); const pluginsModulesPath = await getPluginsModulesPath(srcDir); const bundlerName = await getBundlerName({ config: mergedConfig, extension, featureFlags, mainFile, runtimeAPIVersion, }); const bundler = getBundler(bundlerName); const { aliases = new Map(), cleanupFunction, basePath: finalBasePath, bundlerWarnings, includedFiles, inputs, mainFile: finalMainFile = mainFile, moduleFormat, nativeNodeModules, rewrites = new Map(), srcFiles, } = await bundler.bundle({ basePath, cache, config: mergedConfig, extension, featureFlags, filename, logger, mainFile, name, pluginsModulesPath, repositoryRoot, runtime, runtimeAPIVersion, srcDir, srcPath, stat, }); createPluginsModulesPathAliases(srcFiles, pluginsModulesPath, aliases, finalBasePath); const generator = mergedConfig?.generator || getInternalValue(isInternal); const zipResult = await zipNodeJs({ aliases, archiveFormat, basePath: finalBasePath, branch, cache, destFolder, extension, featureFlags, filename, mainFile: finalMainFile, moduleFormat, name, repositoryRoot, rewrites, runtimeAPIVersion, srcFiles, generator, }); await cleanupFunction?.(); // Getting the invocation mode from ISC, in case the function is using the // `stream` helper. let { invocationMode } = staticAnalysisResult; // If we're using the V2 API, force the invocation to "stream". if (runtimeAPIVersion === 2) { invocationMode = INVOCATION_MODE.Stream; } // If this is a background function, set the right `invocationMode` value. if (name.endsWith('-background')) { invocationMode = INVOCATION_MODE.Background; } const outputModuleFormat = extname(finalMainFile) === MODULE_FILE_EXTENSION.MJS ? MODULE_FORMAT.ESM : MODULE_FORMAT.COMMONJS; const priority = isInternal ? Priority.GeneratedFunction : Priority.UserFunction; const trafficRules = mergedConfig?.rateLimit ? getTrafficRulesConfig(mergedConfig.rateLimit) : undefined; return { bootstrapVersion: zipResult.bootstrapVersion, bundler: bundlerName, bundlerWarnings, config: mergedConfig, displayName: mergedConfig?.name, entryFilename: zipResult.entryFilename, generator, timeout: mergedConfig?.timeout, inputs, includedFiles, staticAnalysisResult, invocationMode, outputModuleFormat, nativeNodeModules, path: zipResult.path, priority, trafficRules, runtimeVersion: runtimeAPIVersion === 2 ? getNodeRuntimeForV2(mergedConfig.nodeVersion) : getNodeRuntime(mergedConfig.nodeVersion), }; }; const zipWithFunctionWithFallback = async ({ config = {}, ...parameters }) => { // If a specific JS bundler version is specified, we'll use it. if (config.nodeBundler !== NODE_BUNDLER.ESBUILD_ZISI) { return zipFunction({ ...parameters, config }); } // Otherwise, we'll try to bundle with esbuild and, if that fails, fallback // to zisi. try { return await zipFunction({ ...parameters, config: { ...config, nodeBundler: NODE_BUNDLER.ESBUILD } }); } catch (esbuildError) { try { const data = await zipFunction({ ...parameters, config: { ...config, nodeBundler: NODE_BUNDLER.ZISI } }); return { ...data, bundlerErrors: esbuildError.errors }; } catch { throw esbuildError; } } }; const runtime = { findFunctionsInPaths, findFunctionInPath, getSrcFiles: getSrcFilesWithBundler, name: RUNTIME.JAVASCRIPT, zipFunction: zipWithFunctionWithFallback, }; export default runtime;