@netlify/zip-it-and-ship-it 9.15.1 → 9.17.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.
Files changed (31) hide show
  1. package/README.md +0 -5
  2. package/dist/config.d.ts +0 -1
  3. package/dist/feature_flags.d.ts +0 -2
  4. package/dist/feature_flags.js +0 -3
  5. package/dist/main.d.ts +2 -2
  6. package/dist/manifest.d.ts +2 -0
  7. package/dist/manifest.js +18 -12
  8. package/dist/runtimes/index.d.ts +2 -2
  9. package/dist/runtimes/node/bundlers/esbuild/bundler.d.ts +2 -6
  10. package/dist/runtimes/node/bundlers/esbuild/bundler.js +44 -25
  11. package/dist/runtimes/node/bundlers/esbuild/index.js +2 -5
  12. package/dist/runtimes/node/bundlers/esbuild/plugin_native_modules.d.ts +1 -1
  13. package/dist/runtimes/node/bundlers/esbuild/plugin_node_builtin.d.ts +1 -1
  14. package/dist/runtimes/node/bundlers/nft/transpile.js +1 -1
  15. package/dist/runtimes/node/bundlers/types.d.ts +1 -2
  16. package/dist/runtimes/node/bundlers/zisi/list_imports.js +1 -1
  17. package/dist/runtimes/node/in_source_config/index.d.ts +11 -3
  18. package/dist/runtimes/node/in_source_config/index.js +30 -1
  19. package/dist/runtimes/node/index.js +1 -2
  20. package/dist/runtimes/node/parser/exports.js +21 -10
  21. package/dist/runtimes/runtime.d.ts +0 -1
  22. package/dist/utils/format_result.d.ts +2 -0
  23. package/dist/utils/format_result.js +1 -0
  24. package/dist/utils/routes.d.ts +9 -0
  25. package/dist/utils/routes.js +49 -0
  26. package/dist/utils/urlpattern.d.ts +4 -0
  27. package/dist/utils/urlpattern.js +4 -0
  28. package/dist/zip.d.ts +1 -0
  29. package/package.json +11 -8
  30. package/dist/runtimes/node/bundlers/esbuild/plugin_dynamic_imports.d.ts +0 -10
  31. package/dist/runtimes/node/bundlers/esbuild/plugin_dynamic_imports.js +0 -95
package/README.md CHANGED
@@ -278,11 +278,6 @@ Additionally, the following properties also exist for Node.js functions:
278
278
  }
279
279
  ```
280
280
 
281
- - `nodeModulesWithDynamicImports`: `Array<string>`
282
-
283
- A list of Node modules that reference other files with a dynamic expression (e.g. `require(someFunction())` as opposed
284
- to `require('./some-file')`). This is an array containing the module names.
285
-
286
281
  - `runtimeAPIVersion` `number | undefined`
287
282
 
288
283
  When a function with the new API will be detected this is set to `2`. Otherwise it is set to `1`. `undefined` is only
package/dist/config.d.ts CHANGED
@@ -9,7 +9,6 @@ interface FunctionConfig {
9
9
  nodeBundler?: NodeBundlerName;
10
10
  nodeSourcemap?: boolean;
11
11
  nodeVersion?: string;
12
- processDynamicNodeImports?: boolean;
13
12
  rustTargetDirectory?: string;
14
13
  schedule?: string;
15
14
  zipGo?: boolean;
@@ -3,7 +3,6 @@ export declare const defaultFlags: {
3
3
  readonly parseWithEsbuild: false;
4
4
  readonly traceWithNft: false;
5
5
  readonly functions_inherit_build_nodejs_version: false;
6
- readonly zisi_log_dynamic_imports: false;
7
6
  readonly zisi_pure_esm: false;
8
7
  readonly zisi_pure_esm_mjs: false;
9
8
  readonly zisi_output_cjs_extension: false;
@@ -16,7 +15,6 @@ export declare const getFlags: (input?: Record<string, boolean>, flags?: {
16
15
  readonly parseWithEsbuild: false;
17
16
  readonly traceWithNft: false;
18
17
  readonly functions_inherit_build_nodejs_version: false;
19
- readonly zisi_log_dynamic_imports: false;
20
18
  readonly zisi_pure_esm: false;
21
19
  readonly zisi_pure_esm_mjs: false;
22
20
  readonly zisi_output_cjs_extension: false;
@@ -8,9 +8,6 @@ export const defaultFlags = {
8
8
  traceWithNft: false,
9
9
  // Should Lambda functions inherit the build Node.js version
10
10
  functions_inherit_build_nodejs_version: false,
11
- // Emit a system log for every path that has been included in the bundle due
12
- // to the parsing of a dynamic import.
13
- zisi_log_dynamic_imports: false,
14
11
  // Output pure (i.e. untranspiled) ESM files when the function file has ESM
15
12
  // syntax and the parent `package.json` file has `{"type": "module"}`.
16
13
  zisi_pure_esm: false,
package/dist/main.d.ts CHANGED
@@ -28,13 +28,13 @@ interface ListFunctionsOptions {
28
28
  parseISC?: boolean;
29
29
  }
30
30
  export declare const listFunctions: (relativeSrcFolders: string | string[], { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC, }?: {
31
- featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_log_dynamic_imports" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
31
+ featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
32
32
  config?: Config | undefined;
33
33
  configFileDirectories?: string[] | undefined;
34
34
  parseISC?: boolean | undefined;
35
35
  }) => Promise<ListedFunction[]>;
36
36
  export declare const listFunction: (path: string, { featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC, }?: {
37
- featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_log_dynamic_imports" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
37
+ featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
38
38
  config?: Config | undefined;
39
39
  configFileDirectories?: string[] | undefined;
40
40
  parseISC?: boolean | undefined;
@@ -1,11 +1,13 @@
1
1
  import type { FeatureFlags } from './feature_flags.js';
2
2
  import type { InvocationMode } from './function.js';
3
3
  import type { FunctionResult } from './utils/format_result.js';
4
+ import type { Route } from './utils/routes.js';
4
5
  interface ManifestFunction {
5
6
  invocationMode?: InvocationMode;
6
7
  mainFile: string;
7
8
  name: string;
8
9
  path: string;
10
+ routes?: Route[];
9
11
  runtime: string;
10
12
  runtimeVersion?: string;
11
13
  schedule?: string;
package/dist/manifest.js CHANGED
@@ -12,16 +12,22 @@ export const createManifest = async ({ featureFlags, functions, path, }) => {
12
12
  };
13
13
  await fs.writeFile(path, JSON.stringify(payload));
14
14
  };
15
- const formatFunctionForManifest = ({ bundler, displayName, generator, invocationMode, mainFile, name, path, runtime, runtimeVersion, schedule, }, featureFlags) => ({
16
- bundler,
17
- displayName,
18
- generator,
19
- invocationMode,
20
- mainFile,
21
- name,
22
- runtimeVersion: featureFlags.functions_inherit_build_nodejs_version ? runtimeVersion : undefined,
23
- path: resolve(path),
24
- runtime,
25
- schedule,
26
- });
15
+ const formatFunctionForManifest = ({ bundler, displayName, generator, invocationMode, mainFile, name, path, routes, runtime, runtimeVersion, schedule, }, featureFlags) => {
16
+ const manifestFunction = {
17
+ bundler,
18
+ displayName,
19
+ generator,
20
+ invocationMode,
21
+ mainFile,
22
+ name,
23
+ runtimeVersion: featureFlags.functions_inherit_build_nodejs_version ? runtimeVersion : undefined,
24
+ path: resolve(path),
25
+ runtime,
26
+ schedule,
27
+ };
28
+ if (routes?.length !== 0) {
29
+ manifestFunction.routes = routes;
30
+ }
31
+ return manifestFunction;
32
+ };
27
33
  //# sourceMappingURL=manifest.js.map
@@ -11,7 +11,7 @@ export declare const getFunctionsFromPaths: (paths: string[], { cache, config, c
11
11
  config?: Config | undefined;
12
12
  configFileDirectories?: string[] | undefined;
13
13
  dedupe?: boolean | undefined;
14
- featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_log_dynamic_imports" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
14
+ featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
15
15
  }) => Promise<FunctionMap>;
16
16
  /**
17
17
  * Gets a list of functions found in a list of paths.
@@ -20,6 +20,6 @@ export declare const getFunctionFromPath: (path: string, { cache, config, config
20
20
  cache: RuntimeCache;
21
21
  config?: Config | undefined;
22
22
  configFileDirectories?: string[] | undefined;
23
- featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_log_dynamic_imports" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
23
+ featureFlags?: Partial<Record<"buildRustSource" | "parseWithEsbuild" | "traceWithNft" | "functions_inherit_build_nodejs_version" | "zisi_pure_esm" | "zisi_pure_esm_mjs" | "zisi_output_cjs_extension" | "zisi_functions_api_v2" | "zisi_unique_entry_file", boolean>> | undefined;
24
24
  }) => Promise<FunctionSource | undefined>;
25
25
  export {};
@@ -1,15 +1,12 @@
1
1
  import type { FunctionConfig } from '../../../../config.js';
2
2
  import { FeatureFlags } from '../../../../feature_flags.js';
3
- import { Logger } from '../../../../utils/logger.js';
4
3
  export declare const ESBUILD_LOG_LIMIT = 10;
5
- export declare const bundleJsFile: ({ additionalModulePaths, basePath, config, externalModules, featureFlags, ignoredModules, logger, mainFile, name, srcDir, srcFile, runtimeAPIVersion, }: {
4
+ export declare const bundleJsFile: ({ additionalModulePaths, config, externalModules, featureFlags, ignoredModules, mainFile, name, srcDir, srcFile, runtimeAPIVersion, }: {
6
5
  additionalModulePaths?: string[] | undefined;
7
- basePath?: string | undefined;
8
6
  config: FunctionConfig;
9
7
  externalModules: string[];
10
8
  featureFlags: FeatureFlags;
11
9
  ignoredModules: string[];
12
- logger: Logger;
13
10
  mainFile: string;
14
11
  name: string;
15
12
  srcDir: string;
@@ -22,7 +19,6 @@ export declare const bundleJsFile: ({ additionalModulePaths, basePath, config, e
22
19
  inputs: string[];
23
20
  moduleFormat: import("../../utils/module_format.js").ModuleFormat;
24
21
  nativeNodeModules: {};
25
- nodeModulesWithDynamicImports: string[];
26
22
  outputExtension: import("../../utils/module_format.js").ModuleFileExtension;
27
- warnings: import("@netlify/esbuild").Message[];
23
+ warnings: import("esbuild").Message[];
28
24
  }>;
@@ -1,5 +1,6 @@
1
+ import { readFile, writeFile } from 'fs/promises';
1
2
  import { basename, dirname, extname, resolve, join } from 'path';
2
- import { build } from '@netlify/esbuild';
3
+ import { build } from 'esbuild';
3
4
  import { tmpName } from 'tmp-promise';
4
5
  import { FunctionBundlingUserError } from '../../../../utils/error.js';
5
6
  import { getPathWithExtension, safeUnlink } from '../../../../utils/fs.js';
@@ -7,7 +8,6 @@ import { RUNTIME } from '../../../runtime.js';
7
8
  import { getFileExtensionForFormat, MODULE_FORMAT } from '../../utils/module_format.js';
8
9
  import { NODE_BUNDLER } from '../types.js';
9
10
  import { getBundlerTarget, getModuleFormat } from './bundler_target.js';
10
- import { getDynamicImportsPlugin } from './plugin_dynamic_imports.js';
11
11
  import { getNativeModulesPlugin } from './plugin_native_modules.js';
12
12
  import { getNodeBuiltinPlugin } from './plugin_node_builtin.js';
13
13
  // Maximum number of log messages that an esbuild instance will produce. This
@@ -17,35 +17,33 @@ export const ESBUILD_LOG_LIMIT = 10;
17
17
  // When resolving imports with no extension (e.g. require('./foo')), these are
18
18
  // the extensions that esbuild will look for, in this order.
19
19
  const RESOLVE_EXTENSIONS = ['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx', '.mts', '.cts', '.json'];
20
- export const bundleJsFile = async function ({ additionalModulePaths, basePath, config, externalModules = [], featureFlags, ignoredModules = [], logger, mainFile, name, srcDir, srcFile, runtimeAPIVersion, }) {
20
+ /**
21
+ * Our own `includedFiles` syntax is slightly different from what esbuild expects as `externals`.
22
+ *
23
+ * Turns !node_modules/test/** into ./node_modules/test/*
24
+ */
25
+ const includedFilesToEsbuildExternals = (includedFiles) => includedFiles
26
+ // only keep negated patterns
27
+ .filter((pattern) => pattern.startsWith('!'))
28
+ // remove the !
29
+ .map((pattern) => pattern.slice(1))
30
+ // esbuild expects a relative path for local files
31
+ .map((pattern) => `./${pattern}`)
32
+ // esbuild doesn't like **
33
+ .map((pattern) => pattern.replace(/\*\*/g, '*'));
34
+ export const bundleJsFile = async function ({ additionalModulePaths, config, externalModules = [], featureFlags, ignoredModules = [], mainFile, name, srcDir, srcFile, runtimeAPIVersion, }) {
21
35
  // We use a temporary directory as the destination for esbuild files to avoid
22
36
  // any naming conflicts with files generated by other functions.
23
37
  const targetDirectory = await tmpName();
38
+ // files matching negated patterns, like `!lang/en.*`, should be excluded from the bundle
39
+ const excludedFiles = includedFilesToEsbuildExternals(config.includedFiles ?? []);
24
40
  // De-duping external and ignored modules.
25
- const external = [...new Set([...externalModules, ...ignoredModules])];
41
+ const external = [...new Set([...externalModules, ...ignoredModules, ...excludedFiles])];
26
42
  // To be populated by the native modules plugin with the names, versions and
27
43
  // paths of any Node modules with native dependencies.
28
44
  const nativeNodeModules = {};
29
- // To be populated by the dynamic imports plugin with the names of the Node
30
- // modules that include imports with dynamic expressions.
31
- const nodeModulesWithDynamicImports = new Set();
32
- // To be populated by the dynamic imports plugin with any paths (in a glob
33
- // format) to be included in the bundle in order to make a dynamic import
34
- // work at runtime.
35
- const dynamicImportsIncludedPaths = new Set();
36
45
  // The list of esbuild plugins to enable for this build.
37
- const plugins = [
38
- getNodeBuiltinPlugin(),
39
- getNativeModulesPlugin(nativeNodeModules),
40
- getDynamicImportsPlugin({
41
- basePath,
42
- includedPaths: dynamicImportsIncludedPaths,
43
- logger: featureFlags.zisi_log_dynamic_imports ? logger : undefined,
44
- moduleNames: nodeModulesWithDynamicImports,
45
- processImports: config.processDynamicNodeImports !== false,
46
- srcDir,
47
- }),
48
- ];
46
+ const plugins = [getNodeBuiltinPlugin(), getNativeModulesPlugin(nativeNodeModules)];
49
47
  // The version of ECMAScript to use as the build target. This will determine
50
48
  // whether certain features are transpiled down or left untransformed.
51
49
  const nodeTarget = getBundlerTarget(config.nodeVersion);
@@ -101,9 +99,31 @@ let require=___nfyCreateRequire(import.meta.url);
101
99
  srcFile,
102
100
  outputExtension,
103
101
  });
102
+ // workaround for https://github.com/evanw/esbuild/issues/3328
103
+ await Promise.all(Object.keys(metafile.outputs)
104
+ .filter((filename) => filename.endsWith('.js'))
105
+ .map(async (filename) => {
106
+ const content = await readFile(filename, { encoding: 'utf-8' });
107
+ const updated = content.replace(`
108
+ var __glob = (map) => (path) => {
109
+ var fn = map[path];
110
+ if (fn)
111
+ return fn();
112
+ throw new Error("Module not found in bundle: " + path);
113
+ };
114
+ `.trim(), `
115
+ var __glob = (map) => (path) => {
116
+ var fn = map[path] || map[path + '.js'] || map[path + '.json'] || map[path + '/index.js'] || map[path + '/index.json'];
117
+ if (fn)
118
+ return fn();
119
+ throw new Error("Module not found in bundle: " + path);
120
+ };
121
+ `.trim());
122
+ await writeFile(filename, updated, { encoding: 'utf-8' });
123
+ }));
104
124
  const inputs = Object.keys(metafile.inputs).map((path) => resolve(path));
105
125
  const cleanTempFiles = getCleanupFunction([...bundlePaths.keys()]);
106
- const additionalPaths = [...dynamicImportsIncludedPaths, ...includedFilesFromModuleDetection];
126
+ const additionalPaths = includedFilesFromModuleDetection;
107
127
  return {
108
128
  additionalPaths,
109
129
  bundlePaths,
@@ -111,7 +131,6 @@ let require=___nfyCreateRequire(import.meta.url);
111
131
  inputs,
112
132
  moduleFormat,
113
133
  nativeNodeModules,
114
- nodeModulesWithDynamicImports: [...nodeModulesWithDynamicImports],
115
134
  outputExtension,
116
135
  warnings,
117
136
  };
@@ -26,16 +26,14 @@ const getExternalAndIgnoredModules = async ({ config, srcDir }) => {
26
26
  const ignoredModules = [...ignoredModulesFromConfig, ...ignoredModulesFromSpecialCases];
27
27
  return { externalModules, ignoredModules };
28
28
  };
29
- const bundle = async ({ basePath, config = {}, extension, featureFlags, filename, logger, mainFile, name, pluginsModulesPath, repositoryRoot, runtime, srcDir, srcPath, stat, runtimeAPIVersion, }) => {
29
+ const bundle = async ({ basePath, config = {}, extension, featureFlags, filename, mainFile, name, pluginsModulesPath, repositoryRoot, runtime, srcDir, srcPath, stat, runtimeAPIVersion, }) => {
30
30
  const { externalModules, ignoredModules } = await getExternalAndIgnoredModules({ config, srcDir });
31
- const { additionalPaths, bundlePaths, cleanTempFiles, inputs, moduleFormat, nativeNodeModules = {}, nodeModulesWithDynamicImports, outputExtension, warnings, } = await bundleJsFile({
31
+ const { additionalPaths, bundlePaths, cleanTempFiles, inputs, moduleFormat, nativeNodeModules = {}, outputExtension, warnings, } = await bundleJsFile({
32
32
  additionalModulePaths: pluginsModulesPath ? [pluginsModulesPath] : [],
33
- basePath,
34
33
  config,
35
34
  externalModules,
36
35
  featureFlags,
37
36
  ignoredModules,
38
- logger,
39
37
  mainFile,
40
38
  name,
41
39
  srcDir,
@@ -83,7 +81,6 @@ const bundle = async ({ basePath, config = {}, extension, featureFlags, filename
83
81
  mainFile: normalizedMainFile,
84
82
  moduleFormat,
85
83
  nativeNodeModules,
86
- nodeModulesWithDynamicImports,
87
84
  srcFiles: [...supportingSrcFiles, ...bundlePaths.keys()],
88
85
  };
89
86
  };
@@ -1,3 +1,3 @@
1
- import type { Plugin } from '@netlify/esbuild';
1
+ import type { Plugin } from 'esbuild';
2
2
  import type { NativeNodeModules } from '../types.js';
3
3
  export declare const getNativeModulesPlugin: (externalizedModules: NativeNodeModules) => Plugin;
@@ -1,2 +1,2 @@
1
- import type { Plugin } from '@netlify/esbuild';
1
+ import type { Plugin } from 'esbuild';
2
2
  export declare const getNodeBuiltinPlugin: () => Plugin;
@@ -1,4 +1,4 @@
1
- import { build } from '@netlify/esbuild';
1
+ import { build } from 'esbuild';
2
2
  import { FunctionBundlingUserError } from '../../../../utils/error.js';
3
3
  import { RUNTIME } from '../../../runtime.js';
4
4
  import { getBundlerTarget } from '../esbuild/bundler_target.js';
@@ -1,4 +1,4 @@
1
- import type { Message } from '@netlify/esbuild';
1
+ import type { Message } from 'esbuild';
2
2
  import type { FunctionConfig } from '../../../config.js';
3
3
  import type { FeatureFlags } from '../../../feature_flags.js';
4
4
  import type { FunctionSource } from '../../../function.js';
@@ -37,7 +37,6 @@ export type BundleFunction = (args: {
37
37
  mainFile: string;
38
38
  moduleFormat: ModuleFormat;
39
39
  nativeNodeModules?: NativeNodeModules;
40
- nodeModulesWithDynamicImports?: string[];
41
40
  srcFiles: string[];
42
41
  }>;
43
42
  export type GetSrcFilesFunction = (args: {
@@ -1,4 +1,4 @@
1
- import * as esbuild from '@netlify/esbuild';
1
+ import * as esbuild from 'esbuild';
2
2
  import isBuiltinModule from 'is-builtin-module';
3
3
  import precinct from 'precinct';
4
4
  import { tmpName } from 'tmp-promise';
@@ -2,11 +2,14 @@ import type { ArgumentPlaceholder, Expression, SpreadElement, JSXNamespacedName
2
2
  import type { FeatureFlags } from '../../../feature_flags.js';
3
3
  import { InvocationMode } from '../../../function.js';
4
4
  import { Logger } from '../../../utils/logger.js';
5
+ import { Route } from '../../../utils/routes.js';
5
6
  export declare const IN_SOURCE_CONFIG_MODULE = "@netlify/functions";
6
7
  export type ISCValues = {
7
8
  invocationMode?: InvocationMode;
9
+ routes?: Route[];
8
10
  runtimeAPIVersion?: number;
9
11
  schedule?: string;
12
+ methods?: string[];
10
13
  };
11
14
  interface FindISCDeclarationsOptions {
12
15
  functionName: string;
@@ -16,8 +19,13 @@ interface FindISCDeclarationsOptions {
16
19
  export declare const findISCDeclarationsInPath: (sourcePath: string, { functionName, featureFlags, logger }: FindISCDeclarationsOptions) => Promise<ISCValues>;
17
20
  export declare const findISCDeclarations: (source: string, { functionName, featureFlags, logger }: FindISCDeclarationsOptions) => ISCValues;
18
21
  export type ISCHandlerArg = ArgumentPlaceholder | Expression | SpreadElement | JSXNamespacedName;
19
- export interface ISCExport {
20
- local: string;
22
+ export type ISCExportWithCallExpression = {
23
+ type: 'call-expression';
21
24
  args: ISCHandlerArg[];
22
- }
25
+ local: string;
26
+ };
27
+ export type ISCExportOther = {
28
+ type: 'other';
29
+ };
30
+ export type ISCExport = ISCExportWithCallExpression | ISCExportOther;
23
31
  export {};
@@ -1,7 +1,9 @@
1
1
  import { INVOCATION_MODE } from '../../../function.js';
2
2
  import { FunctionBundlingUserError } from '../../../utils/error.js';
3
3
  import { nonNullable } from '../../../utils/non_nullable.js';
4
+ import { getRoutesFromPath } from '../../../utils/routes.js';
4
5
  import { RUNTIME } from '../../runtime.js';
6
+ import { NODE_BUNDLER } from '../bundlers/types.js';
5
7
  import { createBindingsMethod } from '../parser/bindings.js';
6
8
  import { getExports } from '../parser/exports.js';
7
9
  import { getImports } from '../parser/imports.js';
@@ -26,6 +28,23 @@ export const findISCDeclarationsInPath = async (sourcePath, { functionName, feat
26
28
  }
27
29
  return findISCDeclarations(source, { functionName, featureFlags, logger });
28
30
  };
31
+ /**
32
+ * Normalizes method names into arrays of uppercase strings.
33
+ * (e.g. "get" becomes ["GET"])
34
+ */
35
+ const normalizeMethods = (input, name) => {
36
+ const methods = Array.isArray(input) ? input : [input];
37
+ return methods.map((method) => {
38
+ if (typeof method !== 'string') {
39
+ throw new FunctionBundlingUserError(`Could not parse method declaration of function '${name}'. Expecting HTTP Method, got ${method}`, {
40
+ functionName: name,
41
+ runtime: RUNTIME.JAVASCRIPT,
42
+ bundler: NODE_BUNDLER.ESBUILD,
43
+ });
44
+ }
45
+ return method.toUpperCase();
46
+ });
47
+ };
29
48
  export const findISCDeclarations = (source, { functionName, featureFlags, logger }) => {
30
49
  const ast = safelyParseSource(source);
31
50
  if (ast === null) {
@@ -46,10 +65,20 @@ export const findISCDeclarations = (source, { functionName, featureFlags, logger
46
65
  if (typeof configExport.schedule === 'string') {
47
66
  config.schedule = configExport.schedule;
48
67
  }
68
+ if (configExport.method !== undefined) {
69
+ config.methods = normalizeMethods(configExport.method, functionName);
70
+ }
71
+ config.routes = getRoutesFromPath(configExport.path, functionName, config.methods ?? []);
49
72
  return config;
50
73
  }
51
74
  const iscExports = handlerExports
52
- .map(({ args, local: exportName }) => {
75
+ .map((node) => {
76
+ // We're only interested in exports with call expressions, since that's
77
+ // the pattern we use for the wrapper functions.
78
+ if (node.type !== 'call-expression') {
79
+ return null;
80
+ }
81
+ const { args, local: exportName } = node;
53
82
  const matchingImport = imports.find(({ local: importName }) => importName === exportName);
54
83
  if (matchingImport === undefined) {
55
84
  return null;
@@ -44,7 +44,7 @@ const zipFunction = async function ({ archiveFormat, basePath, cache, config = {
44
44
  runtimeAPIVersion,
45
45
  });
46
46
  const bundler = getBundler(bundlerName);
47
- const { aliases = new Map(), cleanupFunction, basePath: finalBasePath, bundlerWarnings, includedFiles, inputs, mainFile: finalMainFile = mainFile, moduleFormat, nativeNodeModules, nodeModulesWithDynamicImports, rewrites = new Map(), srcFiles, } = await bundler.bundle({
47
+ const { aliases = new Map(), cleanupFunction, basePath: finalBasePath, bundlerWarnings, includedFiles, inputs, mainFile: finalMainFile = mainFile, moduleFormat, nativeNodeModules, rewrites = new Map(), srcFiles, } = await bundler.bundle({
48
48
  basePath,
49
49
  cache,
50
50
  config,
@@ -100,7 +100,6 @@ const zipFunction = async function ({ archiveFormat, basePath, cache, config = {
100
100
  inSourceConfig,
101
101
  invocationMode,
102
102
  nativeNodeModules,
103
- nodeModulesWithDynamicImports,
104
103
  path: zipPath.path,
105
104
  runtimeVersion: runtimeAPIVersion === 2 ? getNodeRuntimeForV2(config.nodeVersion) : getNodeRuntime(config.nodeVersion),
106
105
  };
@@ -94,6 +94,7 @@ const parseConfigExport = (node) => {
94
94
  // - number
95
95
  // - object
96
96
  // - string
97
+ // - array of strings
97
98
  const parseObject = (node) => node.properties.reduce((acc, property) => {
98
99
  if (property.type !== 'ObjectProperty' || property.key.type !== 'Identifier') {
99
100
  return acc;
@@ -106,6 +107,12 @@ const parseObject = (node) => node.properties.reduce((acc, property) => {
106
107
  [property.key.name]: property.value.value,
107
108
  };
108
109
  }
110
+ if (property.value.type === 'ArrayExpression') {
111
+ return {
112
+ ...acc,
113
+ [property.key.name]: property.value.elements.flatMap((element) => element?.type === 'StringLiteral' ? [element.value] : []),
114
+ };
115
+ }
109
116
  if (property.value.type === 'ObjectExpression') {
110
117
  return {
111
118
  ...acc,
@@ -127,16 +134,20 @@ const getExportsFromBindings = (specifiers, getAllBindings) => {
127
134
  return exports;
128
135
  };
129
136
  const getExportsFromExpression = (node) => {
130
- // We're only interested in expressions representing function calls, because
131
- // the ISC patterns we implement at the moment are all helper functions.
132
- if (node?.type !== 'CallExpression') {
133
- return [];
134
- }
135
- const { arguments: args, callee } = node;
136
- if (callee.type !== 'Identifier') {
137
- return [];
137
+ switch (node?.type) {
138
+ case 'CallExpression': {
139
+ const { arguments: args, callee } = node;
140
+ if (callee.type !== 'Identifier') {
141
+ return [];
142
+ }
143
+ return [{ args, local: callee.name, type: 'call-expression' }];
144
+ }
145
+ default: {
146
+ if (node !== undefined) {
147
+ return [{ type: 'other' }];
148
+ }
149
+ return [];
150
+ }
138
151
  }
139
- const exports = [{ local: callee.name, args }];
140
- return exports;
141
152
  };
142
153
  //# sourceMappingURL=exports.js.map
@@ -42,7 +42,6 @@ export interface ZipFunctionResult {
42
42
  inSourceConfig?: ISCValues;
43
43
  invocationMode?: InvocationMode;
44
44
  nativeNodeModules?: object;
45
- nodeModulesWithDynamicImports?: string[];
46
45
  path: string;
47
46
  runtimeVersion?: string;
48
47
  entryFilename: string;
@@ -1,6 +1,8 @@
1
1
  import { FunctionArchive } from '../function.js';
2
2
  import { RuntimeName } from '../runtimes/runtime.js';
3
+ import type { Route } from './routes.js';
3
4
  export type FunctionResult = Omit<FunctionArchive, 'runtime'> & {
5
+ routes?: Route[];
4
6
  runtime: RuntimeName;
5
7
  schedule?: string;
6
8
  runtimeAPIVersion?: number;
@@ -4,6 +4,7 @@ export const formatZipResult = (archive) => {
4
4
  const functionResult = {
5
5
  ...archive,
6
6
  inSourceConfig: undefined,
7
+ routes: archive.inSourceConfig?.routes,
7
8
  runtime: archive.runtime.name,
8
9
  schedule: archive.inSourceConfig?.schedule ?? archive?.config?.schedule,
9
10
  runtimeAPIVersion: archive.inSourceConfig?.runtimeAPIVersion,
@@ -0,0 +1,9 @@
1
+ export type Route = {
2
+ pattern: string;
3
+ methods: string[];
4
+ } & ({
5
+ literal: string;
6
+ } | {
7
+ expression: string;
8
+ });
9
+ export declare const getRoutesFromPath: (path: unknown, functionName: string, methods: string[]) => Route[];
@@ -0,0 +1,49 @@
1
+ import { RUNTIME } from '../runtimes/runtime.js';
2
+ import { FunctionBundlingUserError } from './error.js';
3
+ import { ExtendedURLPattern } from './urlpattern.js';
4
+ // Based on https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API.
5
+ const isExpression = (part) => part.includes('*') || part.startsWith(':') || part.includes('{') || part.includes('[') || part.includes('(');
6
+ // Detects whether a path can be represented as a literal or whether it needs
7
+ // a regular expression.
8
+ const isPathLiteral = (path) => {
9
+ const parts = path.split('/');
10
+ return parts.every((part) => !isExpression(part));
11
+ };
12
+ export const getRoutesFromPath = (path, functionName, methods) => {
13
+ if (!path) {
14
+ return [];
15
+ }
16
+ if (typeof path !== 'string') {
17
+ throw new FunctionBundlingUserError(`'path' property must be a string, found '${typeof path}'`, {
18
+ functionName,
19
+ runtime: RUNTIME.JAVASCRIPT,
20
+ });
21
+ }
22
+ if (!path.startsWith('/')) {
23
+ throw new FunctionBundlingUserError(`'path' property must start with a '/'`, {
24
+ functionName,
25
+ runtime: RUNTIME.JAVASCRIPT,
26
+ });
27
+ }
28
+ if (isPathLiteral(path)) {
29
+ return [{ pattern: path, literal: path, methods }];
30
+ }
31
+ try {
32
+ const pattern = new ExtendedURLPattern({ pathname: path });
33
+ // Removing the `^` and `$` delimiters because we'll need to modify what's
34
+ // between them.
35
+ const regex = pattern.regexp.pathname.source.slice(1, -1);
36
+ // Wrapping the expression source with `^` and `$`. Also, adding an optional
37
+ // trailing slash, so that a declaration of `path: "/foo"` matches requests
38
+ // for both `/foo` and `/foo/`.
39
+ const normalizedRegex = `^${regex}\\/?$`;
40
+ return [{ pattern: path, expression: normalizedRegex, methods }];
41
+ }
42
+ catch {
43
+ throw new FunctionBundlingUserError(`'${path}' is not a valid path according to the URLPattern specification`, {
44
+ functionName,
45
+ runtime: RUNTIME.JAVASCRIPT,
46
+ });
47
+ }
48
+ };
49
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1,4 @@
1
+ import { URLPattern } from 'urlpattern-polyfill';
2
+ export declare class ExtendedURLPattern extends URLPattern {
3
+ regexp: Record<string, RegExp>;
4
+ }
@@ -0,0 +1,4 @@
1
+ import { URLPattern } from 'urlpattern-polyfill';
2
+ export class ExtendedURLPattern extends URLPattern {
3
+ }
4
+ //# sourceMappingURL=urlpattern.js.map
package/dist/zip.d.ts CHANGED
@@ -20,6 +20,7 @@ export type ZipFunctionsOptions = ZipFunctionOptions & {
20
20
  internalSrcFolder?: string;
21
21
  };
22
22
  export declare const zipFunctions: (relativeSrcFolders: string | string[], destFolder: string, { archiveFormat, basePath, config, configFileDirectories, featureFlags: inputFeatureFlags, manifest, parallelLimit, repositoryRoot, systemLog, debug, internalSrcFolder, }?: ZipFunctionsOptions) => Promise<(Omit<import("./function.js").FunctionArchive, "runtime"> & {
23
+ routes?: import("./utils/routes.js").Route[] | undefined;
23
24
  runtime: import("./main.js").RuntimeName;
24
25
  schedule?: string | undefined;
25
26
  runtimeAPIVersion?: number | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/zip-it-and-ship-it",
3
- "version": "9.15.1",
3
+ "version": "9.17.0",
4
4
  "description": "Zip it and ship it",
5
5
  "main": "./dist/main.js",
6
6
  "type": "module",
@@ -56,13 +56,13 @@
56
56
  "dependencies": {
57
57
  "@babel/parser": "^7.22.5",
58
58
  "@netlify/binary-info": "^1.0.0",
59
- "@netlify/esbuild": "0.14.39-1",
60
- "@netlify/serverless-functions-api": "^1.6.0",
59
+ "@netlify/serverless-functions-api": "^1.7.3",
61
60
  "@vercel/nft": "^0.23.0",
62
61
  "archiver": "^5.3.0",
63
62
  "common-path-prefix": "^3.0.0",
64
63
  "cp-file": "^10.0.0",
65
64
  "es-module-lexer": "^1.0.0",
65
+ "esbuild": "0.19.2",
66
66
  "execa": "^6.0.0",
67
67
  "filter-obj": "^5.0.0",
68
68
  "find-up": "^6.0.0",
@@ -84,23 +84,26 @@
84
84
  "tmp-promise": "^3.0.2",
85
85
  "toml": "^3.0.0",
86
86
  "unixify": "^1.0.0",
87
+ "urlpattern-polyfill": "8.0.2",
87
88
  "yargs": "^17.0.0"
88
89
  },
89
90
  "devDependencies": {
90
- "@babel/types": "7.22.5",
91
+ "@babel/types": "7.22.11",
91
92
  "@netlify/eslint-config-node": "7.0.1",
92
93
  "@skn0tt/lambda-local": "2.0.3",
93
94
  "@types/archiver": "5.3.2",
94
95
  "@types/glob": "8.1.0",
95
96
  "@types/is-ci": "3.0.0",
96
- "@types/node": "14.18.54",
97
+ "@types/node": "14.18.56",
97
98
  "@types/normalize-path": "3.0.0",
98
99
  "@types/resolve": "1.20.2",
99
100
  "@types/semver": "7.5.0",
100
101
  "@types/tmp": "0.2.3",
101
102
  "@types/unixify": "1.0.0",
102
103
  "@types/yargs": "17.0.24",
103
- "@vitest/coverage-v8": "0.33.0",
104
+ "@vitest/coverage-v8": "0.34.3",
105
+ "browserslist": "4.21.10",
106
+ "cardinal": "2.1.1",
104
107
  "cpy": "9.0.1",
105
108
  "deepmerge": "4.3.1",
106
109
  "get-stream": "6.0.1",
@@ -108,8 +111,8 @@
108
111
  "is-ci": "3.0.1",
109
112
  "npm-run-all": "4.1.5",
110
113
  "source-map-support": "0.5.21",
111
- "typescript": "5.1.6",
112
- "vitest": "0.33.0"
114
+ "typescript": "5.2.2",
115
+ "vitest": "0.34.3"
113
116
  },
114
117
  "engines": {
115
118
  "node": "^14.18.0 || >=16.0.0"
@@ -1,10 +0,0 @@
1
- import type { Plugin } from '@netlify/esbuild';
2
- import { Logger } from '../../../../utils/logger.js';
3
- export declare const getDynamicImportsPlugin: ({ basePath, includedPaths, logger, moduleNames, processImports, srcDir, }: {
4
- basePath?: string | undefined;
5
- includedPaths: Set<string>;
6
- logger?: Logger | undefined;
7
- moduleNames: Set<string>;
8
- processImports: boolean;
9
- srcDir: string;
10
- }) => Plugin;
@@ -1,95 +0,0 @@
1
- import { basename, join, relative } from 'path';
2
- import { findUp, findUpStop, pathExists } from 'find-up';
3
- import normalizePath from 'normalize-path';
4
- import { parseExpression } from '../../parser/index.js';
5
- import { readPackageJson } from '../../utils/package_json.js';
6
- // This plugin intercepts module imports using dynamic expressions and does a
7
- // couple of things with them. First of all, it figures out whether the call
8
- // is being made from within a Node module, and if so it adds the name of the
9
- // module to `moduleNames`, so that we can warn the user of potential runtime
10
- // issues. Secondly, it parses the dynamic expressions and tries to include in
11
- // the bundle all the files that are possibly needed to make the import work at
12
- // runtime. This is not always possible, but we do our best.
13
- export const getDynamicImportsPlugin = ({ basePath, includedPaths, logger, moduleNames, processImports, srcDir, }) => ({
14
- name: 'dynamic-imports',
15
- setup(build) {
16
- const cache = new Map();
17
- build.onDynamicImport({ filter: /.*/ }, async (args) => {
18
- const { expression, resolveDir } = args;
19
- // Don't attempt to parse the expression if the base path isn't defined,
20
- // since we won't be able to generate the globs for the included paths.
21
- // Also don't parse the expression if we're not interested in processing
22
- // the dynamic import expressions.
23
- if (basePath && processImports) {
24
- const { includedPathsGlob, type: expressionType } = parseExpression({ basePath, expression, resolveDir }) || {};
25
- if (includedPathsGlob) {
26
- // The parser has found a glob of paths that should be included in the
27
- // bundle to make this import work, so we add it to `includedPaths`.
28
- includedPaths.add(includedPathsGlob);
29
- logger?.system(`Functions bundling processed dynamic import with expression: ${expression}`);
30
- // Create the shim that will handle the import at runtime.
31
- const contents = getShimContents({ expressionType, resolveDir, srcDir });
32
- // This is the only branch where we actually solve a dynamic import.
33
- // eslint-disable-next-line max-depth
34
- if (contents) {
35
- return {
36
- contents,
37
- };
38
- }
39
- }
40
- }
41
- // If we're here, it means we weren't able to solve the dynamic import.
42
- // We add it to the list of modules with dynamic imports, which allows
43
- // consumers like Netlify Build or CLI to advise users on how to proceed.
44
- await registerModuleWithDynamicImports({ cache, moduleNames, resolveDir, srcDir });
45
- });
46
- },
47
- });
48
- const getPackageName = async ({ resolveDir, srcDir }) => {
49
- const packageJsonPath = await findUp(async (directory) => {
50
- // We stop traversing if we're about to leave the boundaries of the
51
- // function directory or any node_modules directory.
52
- if (directory === srcDir || basename(directory) === 'node_modules') {
53
- return findUpStop;
54
- }
55
- const path = join(directory, 'package.json');
56
- const hasPackageJson = await pathExists(path);
57
- return hasPackageJson ? path : undefined;
58
- }, { cwd: resolveDir });
59
- if (packageJsonPath !== undefined) {
60
- const { name } = await readPackageJson(packageJsonPath);
61
- return name;
62
- }
63
- };
64
- const getPackageNameCached = ({ cache, resolveDir, srcDir, }) => {
65
- let result = cache.get(resolveDir);
66
- if (result === undefined) {
67
- result = getPackageName({ resolveDir, srcDir });
68
- cache.set(resolveDir, result);
69
- }
70
- return result;
71
- };
72
- const getShimContents = ({ expressionType, resolveDir, srcDir, }) => {
73
- // The shim needs to modify the path of the import, since originally it was
74
- // relative to wherever the importer sat in the file tree (i.e. anywhere in
75
- // the user space or inside `node_modules`), but at runtime paths must be
76
- // relative to the main bundle file, since esbuild will flatten everything
77
- // into a single file.
78
- const relativeResolveDir = relative(srcDir, resolveDir);
79
- const requireArg = relativeResolveDir ? `\`./${normalizePath(relativeResolveDir)}/$\{args}\`` : 'args';
80
- if (expressionType === 'require') {
81
- return `module.exports = args => require(${requireArg})`;
82
- }
83
- };
84
- const registerModuleWithDynamicImports = async ({ cache, moduleNames, resolveDir, srcDir, }) => {
85
- try {
86
- const packageName = await getPackageNameCached({ cache, resolveDir, srcDir });
87
- if (packageName !== undefined) {
88
- moduleNames.add(packageName);
89
- }
90
- }
91
- catch {
92
- // no-op
93
- }
94
- };
95
- //# sourceMappingURL=plugin_dynamic_imports.js.map