@netlify/zip-it-and-ship-it 9.20.0 → 9.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Config } from './config.js';
2
2
  import { FeatureFlags } from './feature_flags.js';
3
+ import { ModuleFormat } from './runtimes/node/utils/module_format.js';
3
4
  import { RuntimeName } from './runtimes/runtime.js';
4
5
  export { Config, FunctionConfig } from './config.js';
5
6
  export { zipFunction, zipFunctions, ZipFunctionOptions, ZipFunctionsOptions } from './zip.js';
@@ -16,6 +17,7 @@ export interface ListedFunction {
16
17
  schedule?: string;
17
18
  displayName?: string;
18
19
  generator?: string;
20
+ inputModuleFormat?: ModuleFormat;
19
21
  }
20
22
  type ListedFunctionFile = ListedFunction & {
21
23
  srcFile: string;
@@ -27,16 +29,20 @@ interface ListFunctionsOptions {
27
29
  featureFlags?: FeatureFlags;
28
30
  parseISC?: boolean;
29
31
  }
30
- export declare const listFunctions: (relativeSrcFolders: string | string[], { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC, }?: {
31
- featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_unique_entry_file" | "zisi_esbuild_fail_double_glob" | "zisi_golang_use_al2", boolean>> | undefined;
32
- config?: Config | undefined;
33
- configFileDirectories?: string[] | undefined;
34
- parseISC?: boolean | undefined;
35
- }) => Promise<ListedFunction[]>;
36
- export declare const listFunction: (path: string, { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC, }?: {
37
- featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_unique_entry_file" | "zisi_esbuild_fail_double_glob" | "zisi_golang_use_al2", boolean>> | undefined;
38
- config?: Config | undefined;
39
- configFileDirectories?: string[] | undefined;
40
- parseISC?: boolean | undefined;
41
- }) => Promise<ListedFunction | undefined>;
32
+ interface ListFunctionsOptions {
33
+ basePath?: string;
34
+ config?: Config;
35
+ configFileDirectories?: string[];
36
+ featureFlags?: FeatureFlags;
37
+ parseISC?: boolean;
38
+ }
39
+ export declare const listFunctions: (relativeSrcFolders: string | string[], { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC }?: ListFunctionsOptions) => Promise<ListedFunction[]>;
40
+ interface ListFunctionOptions {
41
+ basePath?: string;
42
+ config?: Config;
43
+ configFileDirectories?: string[];
44
+ featureFlags?: FeatureFlags;
45
+ parseISC?: boolean;
46
+ }
47
+ export declare const listFunction: (path: string, { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC }?: ListFunctionOptions) => Promise<ListedFunction | undefined>;
42
48
  export declare const listFunctionsFiles: (relativeSrcFolders: string | string[], { basePath, config, configFileDirectories, featureFlags: inputFeatureFlags, parseISC, }?: ListFunctionsOptions) => Promise<ListedFunctionFile[]>;
package/dist/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { extname } from 'path';
2
2
  import { getFlags } from './feature_flags.js';
3
3
  import { getFunctionFromPath, getFunctionsFromPaths } from './runtimes/index.js';
4
- import { findISCDeclarationsInPath } from './runtimes/node/in_source_config/index.js';
4
+ import { parseFile } from './runtimes/node/in_source_config/index.js';
5
5
  import { RUNTIME } from './runtimes/runtime.js';
6
6
  import { RuntimeCache } from './utils/cache.js';
7
7
  import { listFunctionsDirectories, resolveFunctionsDirectories } from './utils/fs.js';
@@ -11,38 +11,38 @@ export { ARCHIVE_FORMAT } from './archive.js';
11
11
  export { NODE_BUNDLER } from './runtimes/node/bundlers/types.js';
12
12
  export { RUNTIME } from './runtimes/runtime.js';
13
13
  export { MODULE_FORMAT } from './runtimes/node/utils/module_format.js';
14
- const augmentWithISC = async (func) => {
15
- // ISC is currently only supported in JavaScript and TypeScript functions
16
- // and only supports scheduled functions.
14
+ const augmentWithStaticAnalysis = async (func) => {
17
15
  if (func.runtime.name !== RUNTIME.JAVASCRIPT) {
18
16
  return func;
19
17
  }
20
- const inSourceConfig = await findISCDeclarationsInPath(func.mainFile, {
18
+ const staticAnalysisResult = await parseFile(func.mainFile, {
21
19
  functionName: func.name,
22
20
  logger: getLogger(),
23
21
  });
24
- return { ...func, inSourceConfig };
22
+ return { ...func, staticAnalysisResult };
25
23
  };
26
24
  // List all Netlify Functions main entry files for a specific directory
27
- export const listFunctions = async function (relativeSrcFolders, { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC = false, } = {}) {
25
+ export const listFunctions = async function (relativeSrcFolders, { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC = false } = {}) {
28
26
  const featureFlags = getFlags(inputFeatureFlags);
29
27
  const srcFolders = resolveFunctionsDirectories(relativeSrcFolders);
30
28
  const paths = await listFunctionsDirectories(srcFolders);
31
29
  const cache = new RuntimeCache();
32
30
  const functionsMap = await getFunctionsFromPaths(paths, { cache, config, configFileDirectories, featureFlags });
33
31
  const functions = [...functionsMap.values()];
34
- const augmentedFunctions = parseISC ? await Promise.all(functions.map((func) => augmentWithISC(func))) : functions;
32
+ const augmentedFunctions = parseISC
33
+ ? await Promise.all(functions.map((func) => augmentWithStaticAnalysis(func)))
34
+ : functions;
35
35
  return augmentedFunctions.map(getListedFunction);
36
36
  };
37
37
  // Finds a function at a specific path.
38
- export const listFunction = async function (path, { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC = false, } = {}) {
38
+ export const listFunction = async function (path, { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC = false } = {}) {
39
39
  const featureFlags = getFlags(inputFeatureFlags);
40
40
  const cache = new RuntimeCache();
41
41
  const func = await getFunctionFromPath(path, { cache, config, configFileDirectories, featureFlags });
42
42
  if (!func) {
43
43
  return;
44
44
  }
45
- const augmentedFunction = parseISC ? await augmentWithISC(func) : func;
45
+ const augmentedFunction = parseISC ? await augmentWithStaticAnalysis(func) : func;
46
46
  return getListedFunction(augmentedFunction);
47
47
  };
48
48
  // List all Netlify Functions files for a specific directory
@@ -53,11 +53,13 @@ export const listFunctionsFiles = async function (relativeSrcFolders, { basePath
53
53
  const cache = new RuntimeCache();
54
54
  const functionsMap = await getFunctionsFromPaths(paths, { cache, config, configFileDirectories, featureFlags });
55
55
  const functions = [...functionsMap.values()];
56
- const augmentedFunctions = parseISC ? await Promise.all(functions.map((func) => augmentWithISC(func))) : functions;
56
+ const augmentedFunctions = parseISC
57
+ ? await Promise.all(functions.map((func) => augmentWithStaticAnalysis(func)))
58
+ : functions;
57
59
  const listedFunctionsFiles = await Promise.all(augmentedFunctions.map((func) => getListedFunctionFiles(func, { basePath, featureFlags })));
58
60
  return listedFunctionsFiles.flat();
59
61
  };
60
- const getListedFunction = function ({ config, extension, inSourceConfig, mainFile, name, runtime, }) {
62
+ const getListedFunction = function ({ config, extension, staticAnalysisResult, mainFile, name, runtime, }) {
61
63
  return {
62
64
  displayName: config.name,
63
65
  extension,
@@ -65,15 +67,16 @@ const getListedFunction = function ({ config, extension, inSourceConfig, mainFil
65
67
  mainFile,
66
68
  name,
67
69
  runtime: runtime.name,
68
- runtimeAPIVersion: inSourceConfig ? inSourceConfig?.runtimeAPIVersion ?? 1 : undefined,
69
- schedule: inSourceConfig?.schedule ?? config.schedule,
70
+ runtimeAPIVersion: staticAnalysisResult ? staticAnalysisResult?.runtimeAPIVersion ?? 1 : undefined,
71
+ schedule: staticAnalysisResult?.schedule ?? config.schedule,
72
+ inputModuleFormat: staticAnalysisResult?.inputModuleFormat,
70
73
  };
71
74
  };
72
75
  const getListedFunctionFiles = async function (func, options) {
73
76
  const srcFiles = await getSrcFiles({
74
77
  ...func,
75
78
  ...options,
76
- runtimeAPIVersion: func.inSourceConfig?.runtimeAPIVersion ?? 1,
79
+ runtimeAPIVersion: func.staticAnalysisResult?.runtimeAPIVersion ?? 1,
77
80
  });
78
81
  return srcFiles.map((srcFile) => ({ ...getListedFunction(func), srcFile, extension: extname(srcFile) }));
79
82
  };
@@ -6,6 +6,7 @@ import { FunctionBundlingUserError } from '../../../../utils/error.js';
6
6
  import { getPathWithExtension, safeUnlink } from '../../../../utils/fs.js';
7
7
  import { glob } from '../../../../utils/matching.js';
8
8
  import { RUNTIME } from '../../../runtime.js';
9
+ import { CJS_SHIM } from '../../utils/esm_cjs_compat.js';
9
10
  import { getFileExtensionForFormat, MODULE_FORMAT } from '../../utils/module_format.js';
10
11
  import { NODE_BUNDLER } from '../types.js';
11
12
  import { getBundlerTarget, getModuleFormat } from './bundler_target.js';
@@ -88,19 +89,9 @@ export const bundleJsFile = async function ({ additionalModulePaths, config, ext
88
89
  });
89
90
  // The extension of the output file.
90
91
  const outputExtension = getFileExtensionForFormat(moduleFormat, featureFlags, runtimeAPIVersion);
91
- // We add this banner so that calls to require() still work in ESM modules, especially when importing node built-ins
92
- // We have to do this until this is fixed in esbuild: https://github.com/evanw/esbuild/pull/2067
93
- const esmJSBanner = `
94
- import {createRequire as ___nfyCreateRequire} from "module";
95
- import {fileURLToPath as ___nfyFileURLToPath} from "url";
96
- import {dirname as ___nfyPathDirname} from "path";
97
- let __filename=___nfyFileURLToPath(import.meta.url);
98
- let __dirname=___nfyPathDirname(___nfyFileURLToPath(import.meta.url));
99
- let require=___nfyCreateRequire(import.meta.url);
100
- `;
101
92
  try {
102
93
  const { metafile = { inputs: {}, outputs: {} }, warnings } = await build({
103
- banner: moduleFormat === MODULE_FORMAT.ESM ? { js: esmJSBanner } : undefined,
94
+ banner: moduleFormat === MODULE_FORMAT.ESM ? { js: CJS_SHIM } : undefined,
104
95
  bundle: true,
105
96
  entryPoints: [srcFile],
106
97
  external,
@@ -22,18 +22,20 @@ export const getBundler = (name) => {
22
22
  }
23
23
  };
24
24
  export const getBundlerName = async ({ config: { nodeBundler }, extension, featureFlags, mainFile, runtimeAPIVersion, }) => {
25
+ // For V2 functions, we force the bundler to NFT. The only exception is when
26
+ // a `none` override was provided.
27
+ if (runtimeAPIVersion === 2) {
28
+ return nodeBundler === NODE_BUNDLER.NONE ? NODE_BUNDLER.NONE : NODE_BUNDLER.NFT;
29
+ }
25
30
  if (nodeBundler) {
26
31
  return nodeBundler;
27
32
  }
28
- return await getDefaultBundler({ extension, featureFlags, mainFile, runtimeAPIVersion });
33
+ return await getDefaultBundler({ extension, featureFlags, mainFile });
29
34
  };
30
35
  const ESBUILD_EXTENSIONS = new Set(['.mjs', '.ts', '.tsx', '.cts', '.mts']);
31
36
  // We use ZISI as the default bundler, except for certain extensions, for which
32
37
  // esbuild is the only option.
33
- const getDefaultBundler = async ({ extension, featureFlags, mainFile, runtimeAPIVersion, }) => {
34
- if (runtimeAPIVersion === 2) {
35
- return NODE_BUNDLER.NFT;
36
- }
38
+ const getDefaultBundler = async ({ extension, featureFlags, mainFile, }) => {
37
39
  if (extension === MODULE_FILE_EXTENSION.MJS && featureFlags.zisi_pure_esm_mjs) {
38
40
  return NODE_BUNDLER.NFT;
39
41
  }
@@ -8,14 +8,13 @@ import { filterExcludedPaths, getPathsOfIncludedFiles } from '../../utils/includ
8
8
  import { MODULE_FORMAT, MODULE_FILE_EXTENSION, tsExtensions } from '../../utils/module_format.js';
9
9
  import { getNodeSupportMatrix } from '../../utils/node_version.js';
10
10
  import { getClosestPackageJson } from '../../utils/package_json.js';
11
- import { getModuleFormat as getModuleFormatFromTsConfig } from '../../utils/tsconfig.js';
12
11
  import { processESM } from './es_modules.js';
13
12
  import { transpileTS } from './transpile.js';
14
13
  const appearsToBeModuleName = (name) => !name.startsWith('.');
15
14
  const bundle = async ({ basePath, cache, config, featureFlags, mainFile, name, pluginsModulesPath, repositoryRoot = basePath, runtimeAPIVersion, }) => {
16
15
  const { includedFiles = [], includedFilesBasePath } = config;
17
16
  const { excludePatterns, paths: includedFilePaths } = await getPathsOfIncludedFiles(includedFiles, includedFilesBasePath || basePath);
18
- const { aliases, mainFile: normalizedMainFile, moduleFormat, paths: dependencyPaths, rewrites, } = await traceFilesAndTranspile({
17
+ const { aliases, bundledPaths = [], mainFile: normalizedMainFile, moduleFormat, rewrites, tracedPaths, } = await traceFilesAndTranspile({
19
18
  basePath: repositoryRoot,
20
19
  cache,
21
20
  config,
@@ -27,15 +26,18 @@ const bundle = async ({ basePath, cache, config, featureFlags, mainFile, name, p
27
26
  runtimeAPIVersion,
28
27
  });
29
28
  const includedPaths = filterExcludedPaths(includedFilePaths, excludePatterns);
30
- const filteredIncludedPaths = [...filterExcludedPaths(dependencyPaths, excludePatterns), ...includedPaths];
29
+ const filteredIncludedPaths = [...filterExcludedPaths(tracedPaths, excludePatterns), ...includedPaths];
31
30
  const dirnames = filteredIncludedPaths.map((filePath) => normalize(dirname(filePath))).sort();
32
31
  // Sorting the array to make the checksum deterministic.
33
32
  const srcFiles = [...filteredIncludedPaths].sort();
33
+ // The inputs are the union of any traced paths (included as files in the end
34
+ // result) and any bundled paths (merged together in the bundle).
35
+ const inputs = bundledPaths.length === 0 ? tracedPaths : [...new Set([...tracedPaths, ...bundledPaths])];
34
36
  return {
35
37
  aliases,
36
38
  basePath: getBasePath(dirnames),
37
39
  includedFiles: includedPaths,
38
- inputs: dependencyPaths,
40
+ inputs,
39
41
  mainFile: normalizedMainFile,
40
42
  moduleFormat,
41
43
  rewrites,
@@ -55,11 +57,17 @@ const getIgnoreFunction = (config) => {
55
57
  * Returns the module format that should be used when transpiling a TypeScript
56
58
  * file.
57
59
  */
58
- const getTSModuleFormat = async (mainFile, repositoryRoot) => {
59
- const fromTsConfig = getModuleFormatFromTsConfig(mainFile, repositoryRoot);
60
- // If we can infer the module type from a `tsconfig.json` file, use that.
61
- if (fromTsConfig !== undefined) {
62
- return fromTsConfig;
60
+ const getTSModuleFormat = async (mainFile, runtimeAPIVersion, repositoryRoot) => {
61
+ // TODO: This check should go away. We should always respect the format from
62
+ // the extension. We'll do this at a later stage, after we roll out the V2
63
+ // API with no side-effects on V1 functions.
64
+ if (runtimeAPIVersion === 2) {
65
+ if (extname(mainFile) === MODULE_FILE_EXTENSION.MTS) {
66
+ return MODULE_FORMAT.ESM;
67
+ }
68
+ if (extname(mainFile) === MODULE_FILE_EXTENSION.CTS) {
69
+ return MODULE_FORMAT.COMMONJS;
70
+ }
63
71
  }
64
72
  // At this point, we need to infer the module type from the `type` field in
65
73
  // the closest `package.json`.
@@ -79,25 +87,28 @@ const getTypeScriptTransformer = async (runtimeAPIVersion, mainFile, repositoryR
79
87
  if (!isTypeScript) {
80
88
  return;
81
89
  }
90
+ const format = await getTSModuleFormat(mainFile, runtimeAPIVersion, repositoryRoot);
82
91
  const aliases = new Map();
83
92
  const rewrites = new Map();
84
- // For functions using the V2 API, the format is always ESM.
93
+ const transformer = {
94
+ aliases,
95
+ format,
96
+ rewrites,
97
+ };
85
98
  if (runtimeAPIVersion === 2) {
99
+ // For V2 functions, we want to emit a main file with an unambiguous
100
+ // extension (i.e. `.cjs` or `.mjs`), so that the file is loaded with
101
+ // the correct format regardless of what is set in `package.json`.
102
+ const newExtension = format === MODULE_FORMAT.COMMONJS ? MODULE_FILE_EXTENSION.CJS : MODULE_FILE_EXTENSION.MJS;
103
+ const newMainFile = getPathWithExtension(mainFile, newExtension);
86
104
  return {
87
- aliases,
105
+ ...transformer,
88
106
  bundle: true,
89
- format: MODULE_FORMAT.ESM,
90
- rewrites,
107
+ bundledPaths: [],
108
+ newMainFile,
91
109
  };
92
110
  }
93
- // For functions using the V1 API, the format is inferred from the settings
94
- // in `tsconfig.json` and `package.json`.
95
- const format = await getTSModuleFormat(mainFile, repositoryRoot);
96
- return {
97
- aliases,
98
- format,
99
- rewrites,
100
- };
111
+ return transformer;
101
112
  };
102
113
  const traceFilesAndTranspile = async function ({ basePath, cache, config, featureFlags, mainFile, pluginsModulesPath, name, repositoryRoot, runtimeAPIVersion, }) {
103
114
  const tsTransformer = await getTypeScriptTransformer(runtimeAPIVersion, mainFile, repositoryRoot);
@@ -111,19 +122,29 @@ const traceFilesAndTranspile = async function ({ basePath, cache, config, featur
111
122
  try {
112
123
  const extension = extname(path);
113
124
  if (tsExtensions.has(extension)) {
114
- const transpiledSource = await transpileTS({
125
+ const { bundledPaths, transpiled } = await transpileTS({
115
126
  bundle: tsTransformer?.bundle,
116
127
  config,
117
128
  name,
118
129
  format: tsTransformer?.format,
119
130
  path,
120
131
  });
121
- const newPath = getPathWithExtension(path, MODULE_FILE_EXTENSION.JS);
132
+ const isMainFile = path === mainFile;
133
+ // If this is the main file, the final path of the compiled file may
134
+ // have been set by the transformer. It's fine to do this, since the
135
+ // only place where this file will be imported from is our entry file
136
+ // and we'll know the right path to use.
137
+ const newPath = isMainFile && tsTransformer?.newMainFile
138
+ ? tsTransformer.newMainFile
139
+ : getPathWithExtension(path, MODULE_FILE_EXTENSION.JS);
122
140
  // Overriding the contents of the `.ts` file.
123
- tsTransformer?.rewrites.set(path, transpiledSource);
141
+ tsTransformer?.rewrites.set(path, transpiled);
124
142
  // Rewriting the `.ts` path to `.js` in the bundle.
125
143
  tsTransformer?.aliases.set(path, newPath);
126
- return transpiledSource;
144
+ // Registering the input files that were bundled into the transpiled
145
+ // file.
146
+ tsTransformer?.bundledPaths?.push(...bundledPaths);
147
+ return transpiled;
127
148
  }
128
149
  return await cachedReadFile(cache.fileCache, path);
129
150
  }
@@ -150,14 +171,15 @@ const traceFilesAndTranspile = async function ({ basePath, cache, config, featur
150
171
  }
151
172
  },
152
173
  });
153
- const normalizedDependencyPaths = [...dependencyPaths].map((path) => basePath ? resolve(basePath, path) : resolve(path));
174
+ const normalizedTracedPaths = [...dependencyPaths].map((path) => (basePath ? resolve(basePath, path) : resolve(path)));
154
175
  if (tsTransformer) {
155
176
  return {
156
177
  aliases: tsTransformer.aliases,
157
- mainFile: getPathWithExtension(mainFile, MODULE_FILE_EXTENSION.JS),
178
+ bundledPaths: tsTransformer.bundledPaths,
179
+ mainFile: tsTransformer.newMainFile ?? getPathWithExtension(mainFile, MODULE_FILE_EXTENSION.JS),
158
180
  moduleFormat: tsTransformer.format,
159
- paths: normalizedDependencyPaths,
160
181
  rewrites: tsTransformer.rewrites,
182
+ tracedPaths: normalizedTracedPaths,
161
183
  };
162
184
  }
163
185
  const { moduleFormat, rewrites } = await processESM({
@@ -174,8 +196,8 @@ const traceFilesAndTranspile = async function ({ basePath, cache, config, featur
174
196
  return {
175
197
  mainFile,
176
198
  moduleFormat,
177
- paths: normalizedDependencyPaths,
178
199
  rewrites,
200
+ tracedPaths: normalizedTracedPaths,
179
201
  };
180
202
  };
181
203
  const getSrcFiles = async function ({ basePath, config, mainFile }) {
@@ -1,13 +1,11 @@
1
1
  import type { FunctionConfig } from '../../../../config.js';
2
2
  import { ModuleFormat } from '../../utils/module_format.js';
3
- import type { TsConfig } from '../../utils/tsconfig.js';
4
3
  interface TranspileESMToCJSOptions {
5
4
  config: FunctionConfig;
6
5
  name: string;
7
6
  path: string;
8
- tsConfig?: TsConfig;
9
7
  }
10
- export declare const transpileESMToCJS: ({ config, name, path, tsConfig }: TranspileESMToCJSOptions) => Promise<string>;
8
+ export declare const transpileESMToCJS: ({ config, name, path }: TranspileESMToCJSOptions) => Promise<string>;
11
9
  interface TranspileTSOptions {
12
10
  bundle?: boolean;
13
11
  config: FunctionConfig;
@@ -15,5 +13,8 @@ interface TranspileTSOptions {
15
13
  name: string;
16
14
  path: string;
17
15
  }
18
- export declare const transpileTS: ({ bundle, config, format, name, path }: TranspileTSOptions) => Promise<string>;
16
+ export declare const transpileTS: ({ bundle, config, format, name, path }: TranspileTSOptions) => Promise<{
17
+ bundledPaths: string[];
18
+ transpiled: string;
19
+ }>;
19
20
  export {};
@@ -1,10 +1,12 @@
1
+ import { resolve } from 'path';
1
2
  import { build } from 'esbuild';
2
3
  import { FunctionBundlingUserError } from '../../../../utils/error.js';
3
4
  import { RUNTIME } from '../../../runtime.js';
5
+ import { CJS_SHIM } from '../../utils/esm_cjs_compat.js';
4
6
  import { MODULE_FORMAT } from '../../utils/module_format.js';
5
7
  import { getBundlerTarget } from '../esbuild/bundler_target.js';
6
8
  import { NODE_BUNDLER } from '../types.js';
7
- export const transpileESMToCJS = async ({ config, name, path, tsConfig }) => {
9
+ export const transpileESMToCJS = async ({ config, name, path }) => {
8
10
  // The version of ECMAScript to use as the build target. This will determine
9
11
  // whether certain features are transpiled down or left untransformed.
10
12
  const nodeTarget = getBundlerTarget(config.nodeVersion);
@@ -17,7 +19,6 @@ export const transpileESMToCJS = async ({ config, name, path, tsConfig }) => {
17
19
  platform: 'node',
18
20
  sourcemap: Boolean(config.nodeSourcemap),
19
21
  target: [nodeTarget],
20
- tsconfigRaw: tsConfig ? JSON.stringify(tsConfig) : undefined,
21
22
  write: false,
22
23
  });
23
24
  return transpiled.outputFiles[0].text;
@@ -34,19 +35,30 @@ export const transpileTS = async ({ bundle = false, config, format, name, path }
34
35
  // The version of ECMAScript to use as the build target. This will determine
35
36
  // whether certain features are transpiled down or left untransformed.
36
37
  const nodeTarget = getBundlerTarget(config.nodeVersion);
37
- const bundleOptions = bundle ? { bundle: true, packages: 'external' } : { bundle: false };
38
+ const bundleOptions = {
39
+ bundle: false,
40
+ };
41
+ if (bundle) {
42
+ bundleOptions.bundle = true;
43
+ bundleOptions.packages = 'external';
44
+ if (format === MODULE_FORMAT.ESM) {
45
+ bundleOptions.banner = { js: CJS_SHIM };
46
+ }
47
+ }
38
48
  try {
39
49
  const transpiled = await build({
40
50
  ...bundleOptions,
41
51
  entryPoints: [path],
42
52
  format,
43
53
  logLevel: 'error',
54
+ metafile: true,
44
55
  platform: 'node',
45
56
  sourcemap: Boolean(config.nodeSourcemap),
46
57
  target: [nodeTarget],
47
58
  write: false,
48
59
  });
49
- return transpiled.outputFiles[0].text;
60
+ const bundledPaths = bundle ? Object.keys(transpiled.metafile.inputs).map((inputPath) => resolve(inputPath)) : [];
61
+ return { bundledPaths, transpiled: transpiled.outputFiles[0].text };
50
62
  }
51
63
  catch (error) {
52
64
  throw FunctionBundlingUserError.addCustomErrorInfo(error, {
@@ -2,28 +2,45 @@ import type { ArgumentPlaceholder, Expression, SpreadElement, JSXNamespacedName
2
2
  import { InvocationMode } from '../../../function.js';
3
3
  import { Logger } from '../../../utils/logger.js';
4
4
  import { Route } from '../../../utils/routes.js';
5
+ import type { ModuleFormat } from '../utils/module_format.js';
5
6
  export declare const IN_SOURCE_CONFIG_MODULE = "@netlify/functions";
6
7
  export type ISCValues = {
7
- invocationMode?: InvocationMode;
8
8
  routes?: Route[];
9
- runtimeAPIVersion?: number;
10
9
  schedule?: string;
11
10
  methods?: string[];
12
11
  };
12
+ export interface StaticAnalysisResult extends ISCValues {
13
+ inputModuleFormat?: ModuleFormat;
14
+ invocationMode?: InvocationMode;
15
+ runtimeAPIVersion?: number;
16
+ }
13
17
  interface FindISCDeclarationsOptions {
14
18
  functionName: string;
15
19
  logger: Logger;
16
20
  }
17
- export declare const findISCDeclarationsInPath: (sourcePath: string, { functionName, logger }: FindISCDeclarationsOptions) => Promise<ISCValues>;
18
- export declare const findISCDeclarations: (source: string, { functionName, logger }: FindISCDeclarationsOptions) => ISCValues;
21
+ /**
22
+ * Loads a file at a given path, parses it into an AST, and returns a series of
23
+ * data points, such as in-source configuration properties and other metadata.
24
+ */
25
+ export declare const parseFile: (sourcePath: string, { functionName, logger }: FindISCDeclarationsOptions) => Promise<StaticAnalysisResult>;
26
+ /**
27
+ * Takes a JS/TS source as a string, parses it into an AST, and returns a
28
+ * series of data points, such as in-source configuration properties and
29
+ * other metadata.
30
+ */
31
+ export declare const parseSource: (source: string, { functionName, logger }: FindISCDeclarationsOptions) => StaticAnalysisResult;
19
32
  export type ISCHandlerArg = ArgumentPlaceholder | Expression | SpreadElement | JSXNamespacedName;
20
33
  export type ISCExportWithCallExpression = {
21
34
  type: 'call-expression';
22
35
  args: ISCHandlerArg[];
23
36
  local: string;
24
37
  };
38
+ export type ISCExportWithObject = {
39
+ type: 'object-expression';
40
+ object: Record<string, unknown>;
41
+ };
25
42
  export type ISCExportOther = {
26
43
  type: 'other';
27
44
  };
28
- export type ISCExport = ISCExportWithCallExpression | ISCExportOther;
45
+ export type ISCExport = ISCExportWithCallExpression | ISCExportWithObject | ISCExportOther;
29
46
  export {};
@@ -5,7 +5,7 @@ import { getRoutes } from '../../../utils/routes.js';
5
5
  import { RUNTIME } from '../../runtime.js';
6
6
  import { NODE_BUNDLER } from '../bundlers/types.js';
7
7
  import { createBindingsMethod } from '../parser/bindings.js';
8
- import { getExports } from '../parser/exports.js';
8
+ import { traverseNodes } from '../parser/exports.js';
9
9
  import { getImports } from '../parser/imports.js';
10
10
  import { safelyParseSource, safelyReadSource } from '../parser/index.js';
11
11
  import { parse as parseSchedule } from './properties/schedule.js';
@@ -18,16 +18,6 @@ const validateScheduleFunction = (functionFound, scheduleFound, functionName) =>
18
18
  throw new FunctionBundlingUserError('Unable to find cron expression for scheduled function. The cron expression (first argument) for the `schedule` helper needs to be accessible inside the file and cannot be imported.', { functionName, runtime: RUNTIME.JAVASCRIPT });
19
19
  }
20
20
  };
21
- // Parses a JS/TS file and looks for in-source config declarations. It returns
22
- // an array of all declarations found, with `property` indicating the name of
23
- // the property and `data` its value.
24
- export const findISCDeclarationsInPath = async (sourcePath, { functionName, logger }) => {
25
- const source = await safelyReadSource(sourcePath);
26
- if (source === null) {
27
- return {};
28
- }
29
- return findISCDeclarations(source, { functionName, logger });
30
- };
31
21
  /**
32
22
  * Normalizes method names into arrays of uppercase strings.
33
23
  * (e.g. "get" becomes ["GET"])
@@ -45,7 +35,23 @@ const normalizeMethods = (input, name) => {
45
35
  return method.toUpperCase();
46
36
  });
47
37
  };
48
- export const findISCDeclarations = (source, { functionName, logger }) => {
38
+ /**
39
+ * Loads a file at a given path, parses it into an AST, and returns a series of
40
+ * data points, such as in-source configuration properties and other metadata.
41
+ */
42
+ export const parseFile = async (sourcePath, { functionName, logger }) => {
43
+ const source = await safelyReadSource(sourcePath);
44
+ if (source === null) {
45
+ return {};
46
+ }
47
+ return parseSource(source, { functionName, logger });
48
+ };
49
+ /**
50
+ * Takes a JS/TS source as a string, parses it into an AST, and returns a
51
+ * series of data points, such as in-source configuration properties and
52
+ * other metadata.
53
+ */
54
+ export const parseSource = (source, { functionName, logger }) => {
49
55
  const ast = safelyParseSource(source);
50
56
  if (ast === null) {
51
57
  return {};
@@ -55,21 +61,22 @@ export const findISCDeclarations = (source, { functionName, logger }) => {
55
61
  let scheduledFunctionFound = false;
56
62
  let scheduleFound = false;
57
63
  const getAllBindings = createBindingsMethod(ast.body);
58
- const { configExport, defaultExport, handlerExports } = getExports(ast.body, getAllBindings);
59
- const isV2API = handlerExports.length === 0 && defaultExport !== undefined;
64
+ const { configExport, handlerExports, hasDefaultExport, inputModuleFormat } = traverseNodes(ast.body, getAllBindings);
65
+ const isV2API = handlerExports.length === 0 && hasDefaultExport;
60
66
  if (isV2API) {
61
- const config = {
67
+ const result = {
68
+ inputModuleFormat,
62
69
  runtimeAPIVersion: 2,
63
70
  };
64
71
  logger.system('detected v2 function');
65
72
  if (typeof configExport.schedule === 'string') {
66
- config.schedule = configExport.schedule;
73
+ result.schedule = configExport.schedule;
67
74
  }
68
75
  if (configExport.method !== undefined) {
69
- config.methods = normalizeMethods(configExport.method, functionName);
76
+ result.methods = normalizeMethods(configExport.method, functionName);
70
77
  }
71
- config.routes = getRoutes(configExport.path, functionName, config.methods ?? []);
72
- return config;
78
+ result.routes = getRoutes(configExport.path, functionName, result.methods ?? []);
79
+ return result;
73
80
  }
74
81
  const iscExports = handlerExports
75
82
  .map((node) => {
@@ -107,6 +114,6 @@ export const findISCDeclarations = (source, { functionName, logger }) => {
107
114
  validateScheduleFunction(scheduledFunctionFound, scheduleFound, functionName);
108
115
  }
109
116
  const mergedExports = iscExports.reduce((acc, obj) => ({ ...acc, ...obj }), {});
110
- return { ...mergedExports, runtimeAPIVersion: 1 };
117
+ return { ...mergedExports, inputModuleFormat, runtimeAPIVersion: 1 };
111
118
  };
112
119
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,4 @@
1
- import { join } from 'path';
1
+ import { extname, join } from 'path';
2
2
  import { copyFile } from 'cp-file';
3
3
  import { INVOCATION_MODE } from '../../function.js';
4
4
  import getInternalValue from '../../utils/get_internal_value.js';
@@ -6,7 +6,8 @@ import { RUNTIME } from '../runtime.js';
6
6
  import { getBundler, getBundlerName } from './bundlers/index.js';
7
7
  import { NODE_BUNDLER } from './bundlers/types.js';
8
8
  import { findFunctionsInPaths, findFunctionInPath } from './finder.js';
9
- import { findISCDeclarationsInPath } from './in_source_config/index.js';
9
+ import { parseFile } from './in_source_config/index.js';
10
+ import { MODULE_FORMAT, MODULE_FILE_EXTENSION } from './utils/module_format.js';
10
11
  import { getNodeRuntime, getNodeRuntimeForV2 } from './utils/node_runtime.js';
11
12
  import { createAliases as createPluginsModulesPathAliases, getPluginsModulesPath } from './utils/plugin_modules_path.js';
12
13
  import { zipNodeJs } from './utils/zip.js';
@@ -33,8 +34,8 @@ const zipFunction = async function ({ archiveFormat, basePath, cache, config = {
33
34
  await copyFile(srcPath, destPath);
34
35
  return { config, path: destPath, entryFilename: '' };
35
36
  }
36
- const inSourceConfig = await findISCDeclarationsInPath(mainFile, { functionName: name, logger });
37
- const runtimeAPIVersion = inSourceConfig.runtimeAPIVersion === 2 ? 2 : 1;
37
+ const staticAnalysisResult = await parseFile(mainFile, { functionName: name, logger });
38
+ const runtimeAPIVersion = staticAnalysisResult.runtimeAPIVersion === 2 ? 2 : 1;
38
39
  const pluginsModulesPath = await getPluginsModulesPath(srcDir);
39
40
  const bundlerName = await getBundlerName({
40
41
  config,
@@ -83,7 +84,7 @@ const zipFunction = async function ({ archiveFormat, basePath, cache, config = {
83
84
  await cleanupFunction?.();
84
85
  // Getting the invocation mode from ISC, in case the function is using the
85
86
  // `stream` helper.
86
- let { invocationMode } = inSourceConfig;
87
+ let { invocationMode } = staticAnalysisResult;
87
88
  // If we're using the V2 API, force the invocation to "stream".
88
89
  if (runtimeAPIVersion === 2) {
89
90
  invocationMode = INVOCATION_MODE.Stream;
@@ -92,6 +93,7 @@ const zipFunction = async function ({ archiveFormat, basePath, cache, config = {
92
93
  if (name.endsWith('-background')) {
93
94
  invocationMode = INVOCATION_MODE.Background;
94
95
  }
96
+ const outputModuleFormat = extname(finalMainFile) === MODULE_FILE_EXTENSION.MJS ? MODULE_FORMAT.ESM : MODULE_FORMAT.COMMONJS;
95
97
  return {
96
98
  bundler: bundlerName,
97
99
  bundlerWarnings,
@@ -101,8 +103,9 @@ const zipFunction = async function ({ archiveFormat, basePath, cache, config = {
101
103
  generator: config?.generator || getInternalValue(isInternal),
102
104
  inputs,
103
105
  includedFiles,
104
- inSourceConfig,
106
+ staticAnalysisResult,
105
107
  invocationMode,
108
+ outputModuleFormat,
106
109
  nativeNodeModules,
107
110
  path: zipPath.path,
108
111
  runtimeVersion: runtimeAPIVersion === 2 ? getNodeRuntimeForV2(config.nodeVersion) : getNodeRuntime(config.nodeVersion),
@@ -1,8 +1,18 @@
1
- import type { ExportDefaultDeclaration, Statement } from '@babel/types';
1
+ import type { Statement } from '@babel/types';
2
2
  import type { ISCExport } from '../in_source_config/index.js';
3
3
  import type { BindingMethod } from './bindings.js';
4
- export declare const getExports: (nodes: Statement[], getAllBindings: BindingMethod) => {
4
+ /**
5
+ * Traverses a list of nodes and returns:
6
+ *
7
+ * 1. Named `config` object export (ESM or CJS)
8
+ * 2. Whether there is a default export (ESM or CJS)
9
+ * 3. Named `handler` function exports (ESM or CJS)
10
+ * 4. The module format syntax used in the file: if any `import` or `export`
11
+ * declarations are found, this is ESM; if not, this is CJS
12
+ */
13
+ export declare const traverseNodes: (nodes: Statement[], getAllBindings: BindingMethod) => {
5
14
  configExport: Record<string, unknown>;
6
- defaultExport: ExportDefaultDeclaration | undefined;
7
15
  handlerExports: ISCExport[];
16
+ hasDefaultExport: boolean;
17
+ inputModuleFormat: "cjs";
8
18
  };
@@ -1,39 +1,59 @@
1
- import { isModuleExports } from './helpers.js';
2
- // Finds and returns the following types of exports in an AST:
3
- // 1. Named `handler` function exports
4
- // 2. Default function export
5
- // 3. Named `config` object export
6
- export const getExports = (nodes, getAllBindings) => {
1
+ import { MODULE_FORMAT } from '../utils/module_format.js';
2
+ import { isESMImportExport, isModuleExports } from './helpers.js';
3
+ /**
4
+ * Traverses a list of nodes and returns:
5
+ *
6
+ * 1. Named `config` object export (ESM or CJS)
7
+ * 2. Whether there is a default export (ESM or CJS)
8
+ * 3. Named `handler` function exports (ESM or CJS)
9
+ * 4. The module format syntax used in the file: if any `import` or `export`
10
+ * declarations are found, this is ESM; if not, this is CJS
11
+ */
12
+ export const traverseNodes = (nodes, getAllBindings) => {
7
13
  const handlerExports = [];
8
14
  let configExport = {};
9
- let defaultExport;
15
+ let hasDefaultExport = false;
16
+ let inputModuleFormat = MODULE_FORMAT.COMMONJS;
10
17
  nodes.forEach((node) => {
11
- const esmExports = getMainExportFromESM(node, getAllBindings);
12
- if (esmExports.length !== 0) {
13
- handlerExports.push(...esmExports);
18
+ if (isESMImportExport(node)) {
19
+ inputModuleFormat = MODULE_FORMAT.ESM;
20
+ }
21
+ const esmHandlerExports = getNamedESMExport(node, 'handler', getAllBindings);
22
+ if (esmHandlerExports.length !== 0) {
23
+ handlerExports.push(...esmHandlerExports);
24
+ return;
25
+ }
26
+ const cjsHandlerExports = getCJSExports(node, 'handler');
27
+ if (cjsHandlerExports.length !== 0) {
28
+ handlerExports.push(...cjsHandlerExports);
29
+ return;
30
+ }
31
+ if (isESMDefaultExport(node)) {
32
+ hasDefaultExport = true;
14
33
  return;
15
34
  }
16
- const cjsExports = getMainExportFromCJS(node);
17
- if (cjsExports.length !== 0) {
18
- handlerExports.push(...cjsExports);
35
+ const cjsDefaultExports = getCJSExports(node, 'default');
36
+ if (cjsDefaultExports.length !== 0) {
37
+ hasDefaultExport = true;
19
38
  return;
20
39
  }
21
- if (isDefaultExport(node)) {
22
- defaultExport = node;
40
+ const esmConfig = parseConfigESMExport(node);
41
+ if (esmConfig !== undefined) {
42
+ configExport = esmConfig;
23
43
  return;
24
44
  }
25
- const config = parseConfigExport(node);
26
- if (config !== undefined) {
27
- configExport = config;
45
+ const cjsConfigExports = getCJSExports(node, 'config');
46
+ if (cjsConfigExports.length !== 0 && cjsConfigExports[0].type === 'object-expression') {
47
+ configExport = cjsConfigExports[0].object;
28
48
  }
29
49
  });
30
- return { configExport, defaultExport, handlerExports };
50
+ return { configExport, handlerExports, hasDefaultExport, inputModuleFormat };
31
51
  };
32
52
  // Finds the main handler export in a CJS AST.
33
- const getMainExportFromCJS = (node) => {
53
+ const getCJSExports = (node, name) => {
34
54
  const handlerPaths = [
35
- ['module', 'exports', 'handler'],
36
- ['exports', 'handler'],
55
+ ['module', 'exports', name],
56
+ ['exports', name],
37
57
  ];
38
58
  return handlerPaths.flatMap((handlerPath) => {
39
59
  if (!isModuleExports(node, handlerPath)) {
@@ -42,41 +62,50 @@ const getMainExportFromCJS = (node) => {
42
62
  return getExportsFromExpression(node.expression.right);
43
63
  });
44
64
  };
45
- // Finds the main handler export in an ESM AST.
46
- const getMainExportFromESM = (node, getAllBindings) => {
65
+ /**
66
+ * Finds a named ESM export with a given name. It's capable of finding exports
67
+ * with a variable declaration (`export const foo = "bar"`), but also resolve
68
+ * bindings and find things like `const baz = "1"; export { baz as foo }`.
69
+ */
70
+ const getNamedESMExport = (node, name, getAllBindings) => {
47
71
  if (node.type !== 'ExportNamedDeclaration' || node.exportKind !== 'value') {
48
72
  return [];
49
73
  }
50
74
  const { declaration, specifiers } = node;
51
75
  if (specifiers?.length > 0) {
52
- return getExportsFromBindings(specifiers, getAllBindings);
76
+ return getExportsFromBindings(specifiers, name, getAllBindings);
53
77
  }
54
78
  if (declaration?.type !== 'VariableDeclaration') {
55
79
  return [];
56
80
  }
57
81
  const handlerDeclaration = declaration.declarations.find((childDeclaration) => {
58
82
  const { id, type } = childDeclaration;
59
- return type === 'VariableDeclarator' && id.type === 'Identifier' && id.name === 'handler';
83
+ return type === 'VariableDeclarator' && id.type === 'Identifier' && id.name === name;
60
84
  });
61
85
  const exports = getExportsFromExpression(handlerDeclaration?.init);
62
86
  return exports;
63
87
  };
64
- // Check if the Node is an ExportSpecifier that has a named export called `handler`
65
- // either with Identifier `export { handler }`
66
- // or with StringLiteral `export { x as "handler" }`
67
- const isHandlerExport = (node) => {
88
+ /**
89
+ * Check if the node is an `ExportSpecifier` that has a named export with
90
+ * the given name, either as:
91
+ * - `export { handler }`, or
92
+ * - `export { x as "handler" }`
93
+ */
94
+ const isNamedExport = (node, name) => {
68
95
  const { type, exported } = node;
69
96
  return (type === 'ExportSpecifier' &&
70
- ((exported.type === 'Identifier' && exported.name === 'handler') ||
71
- (exported.type === 'StringLiteral' && exported.value === 'handler')));
97
+ ((exported.type === 'Identifier' && exported.name === name) ||
98
+ (exported.type === 'StringLiteral' && exported.value === name)));
72
99
  };
73
100
  // Returns whether a given node is a default export declaration.
74
- const isDefaultExport = (node) => node.type === 'ExportDefaultDeclaration';
75
- // Finds a `config` named export that maps to an object variable declaration,
76
- // like:
77
- //
78
- // export const config = { prop1: "value 1" }
79
- const parseConfigExport = (node) => {
101
+ const isESMDefaultExport = (node) => node.type === 'ExportDefaultDeclaration';
102
+ /**
103
+ * Finds a `config` named CJS export that maps to an object variable
104
+ * declaration, like:
105
+ *
106
+ * `export const config = { prop1: "value 1" }`
107
+ */
108
+ const parseConfigESMExport = (node) => {
80
109
  if (node.type === 'ExportNamedDeclaration' &&
81
110
  node.declaration?.type === 'VariableDeclaration' &&
82
111
  node.declaration.declarations[0].type === 'VariableDeclarator' &&
@@ -131,12 +160,15 @@ const parsePrimitive = (exp) => {
131
160
  return null;
132
161
  }
133
162
  };
134
- // Tries to resolve the export from a binding (variable)
135
- // for example `let handler; handler = () => {}; export { handler }` would
136
- // resolve correctly to the handler function
137
- const getExportsFromBindings = (specifiers, getAllBindings) => {
138
- const specifier = specifiers.find(isHandlerExport);
139
- if (!specifier) {
163
+ /**
164
+ * Tries to resolve the export with a given name from a binding (variable).
165
+ * For example, the following would resolve correctly to the handler function:
166
+ *
167
+ * `let handler; handler = () => {}; export { handler }`
168
+ */
169
+ const getExportsFromBindings = (specifiers, name, getAllBindings) => {
170
+ const specifier = specifiers.find((node) => isNamedExport(node, name));
171
+ if (!specifier || specifier.type !== 'ExportSpecifier') {
140
172
  return [];
141
173
  }
142
174
  const binding = getAllBindings().get(specifier.local.name);
@@ -152,6 +184,10 @@ const getExportsFromExpression = (node) => {
152
184
  }
153
185
  return [{ args, local: callee.name, type: 'call-expression' }];
154
186
  }
187
+ case 'ObjectExpression': {
188
+ const object = parseObject(node);
189
+ return [{ object, type: 'object-expression' }];
190
+ }
155
191
  default: {
156
192
  if (node !== undefined) {
157
193
  return [{ type: 'other' }];
@@ -1,4 +1,5 @@
1
1
  import type { AssignmentExpression, Expression, ExpressionStatement, ImportDeclaration, Statement } from '@babel/types';
2
+ export declare const isESMImportExport: (node: Statement) => boolean;
2
3
  export declare const isImport: (node: Statement, importPath: string) => node is ImportDeclaration;
3
4
  export declare const isModuleExports: (node: Statement, dotExpression?: string[]) => node is ExpressionStatement & {
4
5
  expression: AssignmentExpression;
@@ -12,6 +12,10 @@ const isDotExpression = (node, expression) => {
12
12
  }
13
13
  return node.object.type === 'Identifier' && object[0] === node.object.name && property === node.property.name;
14
14
  };
15
+ export const isESMImportExport = (node) => node.type === 'ImportDeclaration' ||
16
+ node.type === 'ExportNamedDeclaration' ||
17
+ node.type === 'ExportDefaultDeclaration' ||
18
+ node.type === 'ExportAllDeclaration';
15
19
  export const isImport = (node, importPath) => node.type === 'ImportDeclaration' && node.source.value === importPath;
16
20
  export const isModuleExports = (node, dotExpression = ['module', 'exports']) => node.type === 'ExpressionStatement' &&
17
21
  node.expression.type === 'AssignmentExpression' &&
@@ -0,0 +1,7 @@
1
+ /**
2
+ * This shim makes calls to `require`, `__filename` and `__dirname` work when
3
+ * bundling to ESM with esbuild.
4
+ *
5
+ * https://github.com/evanw/esbuild/pull/2067
6
+ */
7
+ export declare const CJS_SHIM = "\nimport {createRequire as ___nfyCreateRequire} from \"module\";\nimport {fileURLToPath as ___nfyFileURLToPath} from \"url\";\nimport {dirname as ___nfyPathDirname} from \"path\";\nlet __filename=___nfyFileURLToPath(import.meta.url);\nlet __dirname=___nfyPathDirname(___nfyFileURLToPath(import.meta.url));\nlet require=___nfyCreateRequire(import.meta.url);\n";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * This shim makes calls to `require`, `__filename` and `__dirname` work when
3
+ * bundling to ESM with esbuild.
4
+ *
5
+ * https://github.com/evanw/esbuild/pull/2067
6
+ */
7
+ export const CJS_SHIM = `
8
+ import {createRequire as ___nfyCreateRequire} from "module";
9
+ import {fileURLToPath as ___nfyFileURLToPath} from "url";
10
+ import {dirname as ___nfyPathDirname} from "path";
11
+ let __filename=___nfyFileURLToPath(import.meta.url);
12
+ let __dirname=___nfyPathDirname(___nfyFileURLToPath(import.meta.url));
13
+ let require=___nfyCreateRequire(import.meta.url);
14
+ `;
15
+ //# sourceMappingURL=esm_cjs_compat.js.map
@@ -8,8 +8,10 @@ export declare const MODULE_FORMAT: {
8
8
  export type ModuleFormat = ObjectValues<typeof MODULE_FORMAT>;
9
9
  export declare const MODULE_FILE_EXTENSION: {
10
10
  readonly CJS: ".cjs";
11
+ readonly CTS: ".cts";
11
12
  readonly JS: ".js";
12
13
  readonly MJS: ".mjs";
14
+ readonly MTS: ".mts";
13
15
  };
14
16
  export type ModuleFileExtension = ObjectValues<typeof MODULE_FILE_EXTENSION>;
15
17
  export declare const getFileExtensionForFormat: (moduleFormat: ModuleFormat, featureFlags: FeatureFlags, runtimeAPIVersion: number) => ModuleFileExtension;
@@ -5,8 +5,10 @@ export const MODULE_FORMAT = {
5
5
  };
6
6
  export const MODULE_FILE_EXTENSION = {
7
7
  CJS: '.cjs',
8
+ CTS: '.cts',
8
9
  JS: '.js',
9
10
  MJS: '.mjs',
11
+ MTS: '.mts',
10
12
  };
11
13
  export const getFileExtensionForFormat = (moduleFormat, featureFlags, runtimeAPIVersion) => {
12
14
  if (moduleFormat === MODULE_FORMAT.ESM && (runtimeAPIVersion === 2 || featureFlags.zisi_pure_esm_mjs)) {
@@ -1,18 +1,14 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import { mkdir, rm, writeFile } from 'fs/promises';
3
3
  import os from 'os';
4
- import { basename, dirname, extname, join } from 'path';
4
+ import { basename, extname, join } from 'path';
5
5
  import { getPath as getV2APIPath } from '@netlify/serverless-functions-api';
6
6
  import { copyFile } from 'cp-file';
7
7
  import pMap from 'p-map';
8
8
  import { startZip, addZipFile, addZipContent, endZip, ARCHIVE_FORMAT, } from '../../../archive.js';
9
- import { FunctionBundlingUserError } from '../../../utils/error.js';
10
9
  import { cachedLstat, mkdirAndWriteFile } from '../../../utils/fs.js';
11
- import { RUNTIME } from '../../runtime.js';
12
10
  import { BOOTSTRAP_FILE_NAME, conflictsWithEntryFile, getEntryFile, isNamedLikeEntryFile, } from './entry_file.js';
13
- import { tsExtensions, MODULE_FORMAT } from './module_format.js';
14
11
  import { normalizeFilePath } from './normalize_path.js';
15
- import { getClosestPackageJson } from './package_json.js';
16
12
  // Taken from https://www.npmjs.com/package/cpy.
17
13
  const COPY_FILE_CONCURRENCY = os.cpus().length === 0 ? 2 : os.cpus().length * 2;
18
14
  // Sub-directory to place all user-defined files (i.e. everything other than
@@ -26,7 +22,7 @@ const addBootstrapFile = function (srcFiles, aliases) {
26
22
  srcFiles.push(v2APIPath);
27
23
  aliases.set(v2APIPath, BOOTSTRAP_FILE_NAME);
28
24
  };
29
- const createDirectory = async function ({ aliases = new Map(), basePath, destFolder, extension, featureFlags, filename, mainFile, moduleFormat, name, repositoryRoot, rewrites = new Map(), runtimeAPIVersion, srcFiles, }) {
25
+ const createDirectory = async function ({ aliases = new Map(), basePath, destFolder, extension, featureFlags, filename, mainFile, moduleFormat, rewrites = new Map(), runtimeAPIVersion, srcFiles, }) {
30
26
  const { contents: entryContents, filename: entryFilename } = getEntryFile({
31
27
  commonPrefix: basePath,
32
28
  featureFlags,
@@ -59,22 +55,9 @@ const createDirectory = async function ({ aliases = new Map(), basePath, destFol
59
55
  }
60
56
  return copyFile(srcFile, absoluteDestPath);
61
57
  }, { concurrency: COPY_FILE_CONCURRENCY });
62
- if (tsExtensions.has(extension) && moduleFormat === MODULE_FORMAT.ESM && runtimeAPIVersion === 2) {
63
- const packageJSON = await ensurePackageJSONWithModuleType({
64
- basePath,
65
- mainFile,
66
- name,
67
- repositoryRoot,
68
- userNamespace: DEFAULT_USER_SUBDIRECTORY,
69
- });
70
- if (packageJSON) {
71
- const absoluteDestPath = join(functionFolder, packageJSON.path);
72
- await writeFile(absoluteDestPath, packageJSON.contents);
73
- }
74
- }
75
58
  return { path: functionFolder, entryFilename };
76
59
  };
77
- const createZipArchive = async function ({ aliases = new Map(), basePath, cache, destFolder, extension, featureFlags, filename, mainFile, moduleFormat, name, repositoryRoot, rewrites, runtimeAPIVersion, srcFiles, }) {
60
+ const createZipArchive = async function ({ aliases = new Map(), basePath, cache, destFolder, extension, featureFlags, filename, mainFile, moduleFormat, rewrites, runtimeAPIVersion, srcFiles, }) {
78
61
  const destPath = join(destFolder, `${basename(filename, extension)}.zip`);
79
62
  const { archive, output } = startZip(destPath);
80
63
  // There is a naming conflict with the entry file if one of the supporting
@@ -114,18 +97,6 @@ const createZipArchive = async function ({ aliases = new Map(), basePath, cache,
114
97
  if (runtimeAPIVersion === 2) {
115
98
  addBootstrapFile(srcFiles, aliases);
116
99
  }
117
- if (tsExtensions.has(extension) && moduleFormat === MODULE_FORMAT.ESM && runtimeAPIVersion === 2) {
118
- const packageJSON = await ensurePackageJSONWithModuleType({
119
- basePath,
120
- mainFile,
121
- name,
122
- repositoryRoot,
123
- userNamespace,
124
- });
125
- if (packageJSON) {
126
- addZipContent(archive, packageJSON.contents, packageJSON.path);
127
- }
128
- }
129
100
  const deduplicatedSrcFiles = [...new Set(srcFiles)];
130
101
  const srcFilesInfos = await Promise.all(deduplicatedSrcFiles.map((file) => addStat(cache, file)));
131
102
  // We ensure this is not async, so that the archive's checksum is
@@ -158,40 +129,6 @@ const addStat = async function (cache, srcFile) {
158
129
  const stat = await cachedLstat(cache.lstatCache, srcFile);
159
130
  return { srcFile, stat };
160
131
  };
161
- const ensurePackageJSONWithModuleType = async function ({ basePath, mainFile, name, repositoryRoot, userNamespace, }) {
162
- const functionDirectory = dirname(mainFile);
163
- let packageJSON = null;
164
- try {
165
- packageJSON = await getClosestPackageJson(functionDirectory, repositoryRoot);
166
- }
167
- catch {
168
- // There is no valid `package.json`. This is a no-op.
169
- }
170
- // If the closest `package.json` already has a `module` type, there's nothing
171
- // else we need to do.
172
- if (packageJSON?.contents?.type === 'module') {
173
- return;
174
- }
175
- // If there is already a `package.json` adjacent to the function entry point,
176
- // we won't simply overwrite it because that might cause unexpected problems.
177
- // Instead, we fail the build and tell the user that we found contradictory
178
- // configuration settings.
179
- if (packageJSON !== null && dirname(packageJSON.path) === functionDirectory) {
180
- throw new FunctionBundlingUserError(`The function defined in '${mainFile}' has a contradictory configuration: a 'tsconfig.json' file is defining the module format as ESM, but '${packageJSON.path}' specifies CommonJS. If you want to use ESM, consider adding '"type": "module"' to 'package.json'; if you'd like to use CommonJS, consider setting the 'compilerOptions.module' property in 'tsconfig.json' to 'commonjs'.`, {
181
- functionName: name,
182
- runtime: RUNTIME.JAVASCRIPT,
183
- });
184
- }
185
- // To ensure that the entry file and any local imports run as ESM, plant a
186
- // `package.json` with `"type": "module"` adjacent to the entry point.
187
- const path = join(functionDirectory, 'package.json');
188
- const normalizedPath = normalizeFilePath({ commonPrefix: basePath, path, userNamespace });
189
- const contents = JSON.stringify({ type: 'module' });
190
- return {
191
- contents,
192
- path: normalizedPath,
193
- };
194
- };
195
132
  const zipJsFile = function ({ aliases = new Map(), archive, commonPrefix, rewrites = new Map(), stat, srcFile, userNamespace, }) {
196
133
  const destPath = aliases.get(srcFile) || srcFile;
197
134
  const normalizedDestPath = normalizeFilePath({ commonPrefix, path: destPath, userNamespace });
@@ -2,11 +2,12 @@ import type { ArchiveFormat } from '../archive.js';
2
2
  import type { FunctionConfig } from '../config.js';
3
3
  import type { FeatureFlags } from '../feature_flags.js';
4
4
  import type { FunctionSource, InvocationMode, SourceFile } from '../function.js';
5
+ import type { ModuleFormat } from '../main.js';
5
6
  import { ObjectValues } from '../types/utils.js';
6
7
  import type { RuntimeCache } from '../utils/cache.js';
7
8
  import { Logger } from '../utils/logger.js';
8
9
  import type { NodeBundlerName } from './node/bundlers/types.js';
9
- import type { ISCValues } from './node/in_source_config/index.js';
10
+ import type { StaticAnalysisResult } from './node/in_source_config/index.js';
10
11
  export declare const RUNTIME: {
11
12
  readonly GO: "go";
12
13
  readonly JAVASCRIPT: "js";
@@ -39,11 +40,12 @@ export interface ZipFunctionResult {
39
40
  generator?: string;
40
41
  inputs?: string[];
41
42
  includedFiles?: string[];
42
- inSourceConfig?: ISCValues;
43
43
  invocationMode?: InvocationMode;
44
+ outputModuleFormat?: ModuleFormat;
44
45
  nativeNodeModules?: object;
45
46
  path: string;
46
47
  runtimeVersion?: string;
48
+ staticAnalysisResult?: StaticAnalysisResult;
47
49
  entryFilename: string;
48
50
  }
49
51
  export type ZipFunction = (args: {
@@ -3,11 +3,11 @@ import { removeUndefined } from './remove_undefined.js';
3
3
  export const formatZipResult = (archive) => {
4
4
  const functionResult = {
5
5
  ...archive,
6
- inSourceConfig: undefined,
7
- routes: archive.inSourceConfig?.routes,
6
+ staticAnalysisResult: undefined,
7
+ routes: archive.staticAnalysisResult?.routes,
8
8
  runtime: archive.runtime.name,
9
- schedule: archive.inSourceConfig?.schedule ?? archive?.config?.schedule,
10
- runtimeAPIVersion: archive.inSourceConfig?.runtimeAPIVersion,
9
+ schedule: archive.staticAnalysisResult?.schedule ?? archive?.config?.schedule,
10
+ runtimeAPIVersion: archive.staticAnalysisResult?.runtimeAPIVersion,
11
11
  };
12
12
  return removeUndefined(functionResult);
13
13
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/zip-it-and-ship-it",
3
- "version": "9.20.0",
3
+ "version": "9.22.0",
4
4
  "description": "Zip it and ship it",
5
5
  "main": "./dist/main.js",
6
6
  "type": "module",
@@ -66,7 +66,6 @@
66
66
  "execa": "^6.0.0",
67
67
  "filter-obj": "^5.0.0",
68
68
  "find-up": "^6.0.0",
69
- "get-tsconfig": "^4.6.2",
70
69
  "glob": "^8.0.3",
71
70
  "is-builtin-module": "^3.1.0",
72
71
  "is-path-inside": "^4.0.0",
@@ -96,12 +95,12 @@
96
95
  "@types/is-ci": "3.0.1",
97
96
  "@types/node": "14.18.63",
98
97
  "@types/normalize-path": "3.0.0",
99
- "@types/resolve": "1.20.2",
100
- "@types/semver": "7.5.2",
98
+ "@types/resolve": "1.20.3",
99
+ "@types/semver": "7.5.3",
101
100
  "@types/tmp": "0.2.4",
102
101
  "@types/unixify": "1.0.0",
103
- "@types/yargs": "17.0.24",
104
- "@vitest/coverage-v8": "0.34.4",
102
+ "@types/yargs": "17.0.28",
103
+ "@vitest/coverage-v8": "0.34.6",
105
104
  "adm-zip": "0.5.10",
106
105
  "browserslist": "4.21.11",
107
106
  "cardinal": "2.1.1",
@@ -113,7 +112,7 @@
113
112
  "npm-run-all": "4.1.5",
114
113
  "source-map-support": "0.5.21",
115
114
  "typescript": "5.2.2",
116
- "vitest": "0.34.4"
115
+ "vitest": "0.34.6"
117
116
  },
118
117
  "engines": {
119
118
  "node": "^14.18.0 || >=16.0.0"
@@ -1,7 +0,0 @@
1
- export type { TsConfigJson as TsConfig } from 'get-tsconfig';
2
- /**
3
- * Looks for a `tsconfig.json` file on a given path and, if one exists, returns
4
- * the module format inferred from the `module` property. If no file is found
5
- * or if no `module` property is defined, the function returns `undefined`.
6
- */
7
- export declare const getModuleFormat: (path: string, boundary?: string) => "cjs" | "esm" | undefined;
@@ -1,38 +0,0 @@
1
- import { dirname, relative } from 'path';
2
- import { getTsconfig } from 'get-tsconfig';
3
- import { MODULE_FORMAT } from './module_format.js';
4
- const esmModuleValues = new Set(['es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node16', 'nodenext']);
5
- /**
6
- * Looks for a `tsconfig.json` file applicable to a given path and returns the
7
- * contents as an object. If a boundary is set, we'll stop traversing the file
8
- * system once that path is reached.
9
- */
10
- const getTSConfigInProject = (path, boundary) => {
11
- const file = getTsconfig(path);
12
- if (!file) {
13
- return;
14
- }
15
- // If there is a boundary defined and the file we found is outside of it,
16
- // discard the file.
17
- if (boundary !== undefined && relative(boundary, dirname(file.path)).startsWith('..')) {
18
- return;
19
- }
20
- return file.config;
21
- };
22
- /**
23
- * Looks for a `tsconfig.json` file on a given path and, if one exists, returns
24
- * the module format inferred from the `module` property. If no file is found
25
- * or if no `module` property is defined, the function returns `undefined`.
26
- */
27
- export const getModuleFormat = (path, boundary) => {
28
- const config = getTSConfigInProject(path, boundary);
29
- if (!config) {
30
- return;
31
- }
32
- const moduleProp = config.compilerOptions?.module;
33
- if (!moduleProp) {
34
- return;
35
- }
36
- return esmModuleValues.has(moduleProp) ? MODULE_FORMAT.ESM : MODULE_FORMAT.COMMONJS;
37
- };
38
- //# sourceMappingURL=tsconfig.js.map