@netlify/zip-it-and-ship-it 14.4.2 → 14.5.1

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.
@@ -6,6 +6,7 @@ interface ManifestFunction {
6
6
  buildData?: Record<string, unknown>;
7
7
  bundler?: string;
8
8
  displayName?: string;
9
+ eventSubscriptions?: string[];
9
10
  excludedRoutes?: Route[];
10
11
  generator?: string;
11
12
  invocationMode?: InvocationMode;
package/dist/manifest.js CHANGED
@@ -12,7 +12,7 @@ export const createManifest = async ({ functions, path }) => {
12
12
  };
13
13
  await fs.writeFile(path, JSON.stringify(payload));
14
14
  };
15
- const formatFunctionForManifest = ({ bootstrapVersion, bundler, displayName, excludedRoutes, generator, invocationMode, mainFile, name, path, priority, trafficRules, routes, runtime, runtimeVersion, runtimeAPIVersion, schedule, timeout, }) => {
15
+ const formatFunctionForManifest = ({ bootstrapVersion, bundler, displayName, eventSubscriptions, excludedRoutes, generator, invocationMode, mainFile, name, path, priority, trafficRules, routes, runtime, runtimeVersion, runtimeAPIVersion, schedule, timeout, }) => {
16
16
  const manifestFunction = {
17
17
  bundler,
18
18
  displayName,
@@ -29,6 +29,9 @@ const formatFunctionForManifest = ({ bootstrapVersion, bundler, displayName, exc
29
29
  runtime,
30
30
  schedule,
31
31
  };
32
+ if (eventSubscriptions?.length) {
33
+ manifestFunction.eventSubscriptions = eventSubscriptions;
34
+ }
32
35
  if (routes?.length !== 0) {
33
36
  manifestFunction.routes = routes;
34
37
  }
@@ -7,6 +7,7 @@ import type { ModuleFormat } from '../utils/module_format.js';
7
7
  export declare const IN_SOURCE_CONFIG_MODULE = "@netlify/functions";
8
8
  export interface StaticAnalysisResult {
9
9
  config: InSourceConfig;
10
+ eventSubscriptions?: string[];
10
11
  excludedRoutes?: Route[];
11
12
  inputModuleFormat?: ModuleFormat;
12
13
  invocationMode?: InvocationMode;
@@ -14,6 +14,7 @@ import { createBindingsMethod } from '../parser/bindings.js';
14
14
  import { traverseNodes } from '../parser/exports.js';
15
15
  import { getImports } from '../parser/imports.js';
16
16
  import { safelyParseSource, safelyReadSource } from '../parser/index.js';
17
+ import { eventHandlers } from '@netlify/serverless-functions-api';
17
18
  import { parse as parseSchedule } from './properties/schedule.js';
18
19
  export const IN_SOURCE_CONFIG_MODULE = '@netlify/functions';
19
20
  const httpMethod = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE', 'HEAD']);
@@ -49,6 +50,37 @@ export const inSourceConfig = functionConfig
49
50
  preferStatic: z.boolean().optional().catch(undefined),
50
51
  rateLimit: rateLimit.optional().catch(undefined),
51
52
  });
53
+ /**
54
+ * Extracts event subscription slugs from the default export expression,
55
+ * if it's an object whose property names match known event handlers.
56
+ */
57
+ const getEventSubscriptions = (expression, getAllBindings) => {
58
+ let objectExpression;
59
+ if (expression?.type === 'ObjectExpression') {
60
+ objectExpression = expression;
61
+ }
62
+ else if (expression?.type === 'Identifier') {
63
+ const binding = getAllBindings().get(expression.name);
64
+ if (binding?.type === 'ObjectExpression') {
65
+ objectExpression = binding;
66
+ }
67
+ }
68
+ if (!objectExpression) {
69
+ return [];
70
+ }
71
+ const events = [];
72
+ for (const property of objectExpression.properties) {
73
+ let name;
74
+ if ((property.type === 'ObjectMethod' || property.type === 'ObjectProperty') &&
75
+ property.key.type === 'Identifier') {
76
+ name = property.key.name;
77
+ }
78
+ if (name && name in eventHandlers) {
79
+ events.push(eventHandlers[name]);
80
+ }
81
+ }
82
+ return events;
83
+ };
52
84
  const validateScheduleFunction = (functionFound, scheduleFound, functionName) => {
53
85
  if (!functionFound) {
54
86
  throw new FunctionBundlingUserError("The `schedule` helper was imported but we couldn't find any usages. If you meant to schedule a function, please check that `schedule` is invoked and `handler` correctly exported.", { functionName, runtime: RUNTIME.JAVASCRIPT });
@@ -87,7 +119,7 @@ export const parseSource = (source, { functionName }) => {
87
119
  let scheduledFunctionFound = false;
88
120
  let scheduleFound = false;
89
121
  const getAllBindings = createBindingsMethod(ast.body);
90
- const { configExport, handlerExports, hasDefaultExport, inputModuleFormat } = traverseNodes(ast.body, getAllBindings);
122
+ const { configExport, handlerExports, hasDefaultExport, defaultExportExpression, inputModuleFormat } = traverseNodes(ast.body, getAllBindings);
91
123
  const isV2API = handlerExports.length === 0 && hasDefaultExport;
92
124
  if (isV2API) {
93
125
  const result = {
@@ -95,6 +127,10 @@ export const parseSource = (source, { functionName }) => {
95
127
  inputModuleFormat,
96
128
  runtimeAPIVersion: 2,
97
129
  };
130
+ const eventSubscriptions = getEventSubscriptions(defaultExportExpression, getAllBindings);
131
+ if (eventSubscriptions.length > 0) {
132
+ result.eventSubscriptions = eventSubscriptions;
133
+ }
98
134
  const { data, error, success } = inSourceConfig.safeParse(configExport);
99
135
  if (success) {
100
136
  result.config = data;
@@ -1,4 +1,4 @@
1
- import type { Statement } from '@babel/types';
1
+ import type { Declaration, Expression, Statement } from '@babel/types';
2
2
  import type { ISCExport } from '../in_source_config/index.js';
3
3
  import type { BindingMethod } from './bindings.js';
4
4
  /**
@@ -14,5 +14,6 @@ export declare const traverseNodes: (nodes: Statement[], getAllBindings: Binding
14
14
  configExport: Record<string, unknown>;
15
15
  handlerExports: ISCExport[];
16
16
  hasDefaultExport: boolean;
17
+ defaultExportExpression: Expression | Declaration | undefined;
17
18
  inputModuleFormat: "cjs";
18
19
  };
@@ -13,6 +13,7 @@ export const traverseNodes = (nodes, getAllBindings) => {
13
13
  const handlerExports = [];
14
14
  let configExport = {};
15
15
  let hasDefaultExport = false;
16
+ let defaultExportExpression;
16
17
  let inputModuleFormat = MODULE_FORMAT.COMMONJS;
17
18
  nodes.forEach((node) => {
18
19
  if (isESMImportExport(node)) {
@@ -39,10 +40,18 @@ export const traverseNodes = (nodes, getAllBindings) => {
39
40
  const cjsDefaultExports = getCJSExports(node, 'default');
40
41
  if (cjsDefaultExports.length !== 0) {
41
42
  hasDefaultExport = true;
43
+ defaultExportExpression = getCJSDefaultExpression(node);
42
44
  return;
43
45
  }
44
- if (isESMDefaultExport(node)) {
46
+ if (node.type === 'ExportDefaultDeclaration') {
45
47
  hasDefaultExport = true;
48
+ defaultExportExpression = node.declaration;
49
+ return;
50
+ }
51
+ if (node.type === 'ExportNamedDeclaration' &&
52
+ node.specifiers.some((exportSpecifier) => exportSpecifier.exported.type === 'Identifier' && exportSpecifier.exported.name === 'default')) {
53
+ hasDefaultExport = true;
54
+ defaultExportExpression = getESMReexportedDefaultExpression(node, getAllBindings);
46
55
  }
47
56
  const esmConfig = parseConfigESMExport(node);
48
57
  if (esmConfig !== undefined) {
@@ -54,7 +63,7 @@ export const traverseNodes = (nodes, getAllBindings) => {
54
63
  configExport = cjsConfigExports[0].object;
55
64
  }
56
65
  });
57
- return { configExport, handlerExports, hasDefaultExport, inputModuleFormat };
66
+ return { configExport, handlerExports, hasDefaultExport, defaultExportExpression, inputModuleFormat };
58
67
  };
59
68
  // Finds the main handler export in a CJS AST.
60
69
  const getCJSExports = (node, name) => {
@@ -112,10 +121,6 @@ const isNamedExport = (node, name) => {
112
121
  ((exported.type === 'Identifier' && exported.name === name) ||
113
122
  (exported.type === 'StringLiteral' && exported.value === name)));
114
123
  };
115
- // Returns whether a given node is or contains a default export declaration.
116
- const isESMDefaultExport = (node) => node.type === 'ExportDefaultDeclaration' ||
117
- (node.type === 'ExportNamedDeclaration' &&
118
- node.specifiers.some((exportSpecifier) => exportSpecifier.exported.type === 'Identifier' && exportSpecifier.exported.name === 'default'));
119
124
  /**
120
125
  * Finds a `config` named CJS export that maps to an object variable
121
126
  * declaration, like:
@@ -213,6 +218,22 @@ const getExportsFromBindings = (specifiers, name, getAllBindings) => {
213
218
  const exports = getExportsFromExpression(binding);
214
219
  return exports;
215
220
  };
221
+ // Extracts the right-hand side expression from a CJS default export
222
+ // (e.g. `exports.default = expr` or `module.exports.default = expr`).
223
+ const getCJSDefaultExpression = (node) => {
224
+ if (node.type === 'ExpressionStatement' && node.expression.type === 'AssignmentExpression' && node.expression.right) {
225
+ return node.expression.right;
226
+ }
227
+ return undefined;
228
+ };
229
+ // Resolves the expression for `export { x as default }` by looking up `x` in bindings.
230
+ const getESMReexportedDefaultExpression = (node, getAllBindings) => {
231
+ const defaultSpecifier = node.specifiers.find((spec) => spec.type === 'ExportSpecifier' && spec.exported.type === 'Identifier' && spec.exported.name === 'default');
232
+ if (defaultSpecifier && defaultSpecifier.type === 'ExportSpecifier') {
233
+ return getAllBindings().get(defaultSpecifier.local.name);
234
+ }
235
+ return undefined;
236
+ };
216
237
  const getExportsFromExpression = (node) => {
217
238
  switch (node?.type) {
218
239
  case 'CallExpression': {
@@ -3,6 +3,7 @@ import { RuntimeName } from '../runtimes/runtime.js';
3
3
  import type { ExtendedRoute, Route } from './routes.js';
4
4
  export type FunctionResult = Omit<FunctionArchive, 'runtime'> & {
5
5
  bootstrapVersion?: string;
6
+ eventSubscriptions?: string[];
6
7
  routes?: ExtendedRoute[];
7
8
  excludedRoutes?: Route[];
8
9
  runtime: RuntimeName;
@@ -4,6 +4,7 @@ export const formatZipResult = (archive) => {
4
4
  const functionResult = {
5
5
  ...archive,
6
6
  staticAnalysisResult: undefined,
7
+ eventSubscriptions: archive.staticAnalysisResult?.eventSubscriptions,
7
8
  routes: archive.staticAnalysisResult?.routes,
8
9
  excludedRoutes: archive.staticAnalysisResult?.excludedRoutes,
9
10
  runtime: archive.runtime.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/zip-it-and-ship-it",
3
- "version": "14.4.2",
3
+ "version": "14.5.1",
4
4
  "description": "Zip it and ship it",
5
5
  "main": "./dist/main.js",
6
6
  "type": "module",
@@ -44,7 +44,7 @@
44
44
  "@babel/parser": "^7.22.5",
45
45
  "@babel/types": "^7.28.5",
46
46
  "@netlify/binary-info": "^1.0.0",
47
- "@netlify/serverless-functions-api": "^2.10.0",
47
+ "@netlify/serverless-functions-api": "2.12.0",
48
48
  "@vercel/nft": "0.29.4",
49
49
  "archiver": "^7.0.0",
50
50
  "common-path-prefix": "^3.0.0",
@@ -99,5 +99,5 @@
99
99
  "engines": {
100
100
  "node": ">=18.14.0"
101
101
  },
102
- "gitHead": "0ddf077b1055b90e13b678c3516afc0dfdb9c76d"
102
+ "gitHead": "d215694819e87e39ff42e863ed05124f5fd3da98"
103
103
  }