@netlify/zip-it-and-ship-it 16.2.3 → 16.3.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/config.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { FunctionSource } from './function.js';
3
+ export declare const serverRegion: z.ZodPreprocess<z.ZodEnum<{
4
+ fra: "fra";
5
+ iad: "iad";
6
+ }>>;
3
7
  export declare const functionConfigShape: z.ZodObject<{
4
8
  background: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
5
9
  externalNodeModules: z.ZodCatch<z.ZodOptional<z.ZodArray<z.ZodString>>>;
package/dist/config.js CHANGED
@@ -37,6 +37,14 @@ const FUNCTION_REGION_CODES = Object.keys(FUNCTION_REGION_KEYS);
37
37
  // Accept any casing in source (`iad`, `IAD`, `Iad`) but normalize to lower
38
38
  // case before validating and writing to the manifest.
39
39
  const functionRegion = z.preprocess((input) => (typeof input === 'string' ? input.toLowerCase() : input), z.enum(FUNCTION_REGION_CODES));
40
+ // Checked against `ServerRegion` the same way `FUNCTION_REGION_KEYS` is
41
+ // against `FunctionRegion`.
42
+ const SERVER_REGION_KEYS = {
43
+ fra: null,
44
+ iad: null,
45
+ };
46
+ const SERVER_REGION_CODES = Object.keys(SERVER_REGION_KEYS);
47
+ export const serverRegion = z.preprocess((input) => (typeof input === 'string' ? input.toLowerCase() : input), z.enum(SERVER_REGION_CODES));
40
48
  const FUNCTION_MEMORY_MIN_MB = 1024;
41
49
  const FUNCTION_MEMORY_MAX_MB = 4096;
42
50
  const functionMemory = z.preprocess((input) => (typeof input === 'string' || typeof input === 'number' ? parseMemoryMB(input) : input), z.number().int().min(FUNCTION_MEMORY_MIN_MB).max(FUNCTION_MEMORY_MAX_MB));
package/dist/main.d.ts CHANGED
@@ -7,7 +7,7 @@ import type { ExtendedRoute, Route } from './utils/routes.js';
7
7
  export { Config, FunctionConfig } from './config.js';
8
8
  export { zipFunction, zipFunctions, zipServer, ZipFunctionOptions, ZipFunctionsOptions } from './zip.js';
9
9
  export type { ServerOptions, ServerResult, ZipServerOptions } from './zip.js';
10
- export { findServerEntry } from './server.js';
10
+ export { findServerEntry, getServerRoutes } from './server.js';
11
11
  export type { FunctionsBag, MixedPaths } from './paths.js';
12
12
  export { ArchiveFormat, ARCHIVE_FORMAT } from './archive.js';
13
13
  export type { TrafficRules } from './rate_limit.js';
package/dist/main.js CHANGED
@@ -7,7 +7,7 @@ import { RUNTIME } from './runtimes/runtime.js';
7
7
  import { RuntimeCache } from './utils/cache.js';
8
8
  import { listFunctionsDirectories, resolveFunctionsDirectories } from './utils/fs.js';
9
9
  export { zipFunction, zipFunctions, zipServer } from './zip.js';
10
- export { findServerEntry } from './server.js';
10
+ export { findServerEntry, getServerRoutes } from './server.js';
11
11
  export { ARCHIVE_FORMAT } from './archive.js';
12
12
  export { NODE_BUNDLER } from './runtimes/node/bundlers/types.js';
13
13
  export { RUNTIME } from './runtimes/runtime.js';
@@ -16,6 +16,7 @@ export interface StaticAnalysisResult {
16
16
  }
17
17
  interface FindISCDeclarationsOptions {
18
18
  functionName: string;
19
+ isServer?: boolean;
19
20
  }
20
21
  export declare const inSourceConfig: z.ZodObject<{
21
22
  name: z.ZodCatch<z.ZodOptional<z.ZodString>>;
@@ -91,17 +92,25 @@ export declare const inSourceConfig: z.ZodObject<{
91
92
  }, z.core.$strip>>>;
92
93
  }, z.core.$strip>;
93
94
  export type InSourceConfig = z.infer<typeof inSourceConfig>;
95
+ export declare const serverInSourceConfig: z.ZodObject<{
96
+ includedFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
97
+ path: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>, z.ZodTransform<string[], string | string[]>>>;
98
+ region: z.ZodOptional<z.ZodPreprocess<z.ZodEnum<{
99
+ fra: "fra";
100
+ iad: "iad";
101
+ }>>>;
102
+ }, z.core.$strict>;
94
103
  /**
95
104
  * Loads a file at a given path, parses it into an AST, and returns a series of
96
105
  * data points, such as in-source configuration properties and other metadata.
97
106
  */
98
- export declare const parseFile: (sourcePath: string, { functionName }: FindISCDeclarationsOptions) => Promise<StaticAnalysisResult>;
107
+ export declare const parseFile: (sourcePath: string, { functionName, isServer }: FindISCDeclarationsOptions) => Promise<StaticAnalysisResult>;
99
108
  /**
100
109
  * Takes a JS/TS source as a string, parses it into an AST, and returns a
101
110
  * series of data points, such as in-source configuration properties and
102
111
  * other metadata.
103
112
  */
104
- export declare const parseSource: (source: string, { functionName }: FindISCDeclarationsOptions) => StaticAnalysisResult;
113
+ export declare const parseSource: (source: string, { functionName, isServer }: FindISCDeclarationsOptions) => StaticAnalysisResult;
105
114
  export declare const augmentFunctionConfig: (mainFile: string, tomlConfig: FunctionConfig, inSourceConfig?: InSourceConfig) => {
106
115
  background?: boolean | undefined;
107
116
  externalNodeModules?: string[] | undefined;
@@ -3,7 +3,7 @@ import { dirname } from 'path';
3
3
  // this dependency.
4
4
  import mergeOptions from 'merge-options';
5
5
  import { z } from 'zod';
6
- import { functionConfigShape, withComputeResources } from '../../../config.js';
6
+ import { functionConfigShape, serverRegion, withComputeResources } from '../../../config.js';
7
7
  import { INVOCATION_MODE } from '../../../function.js';
8
8
  import { rateLimit } from '../../../rate_limit.js';
9
9
  import { ensureArray } from '../../../utils/ensure_array.js';
@@ -20,6 +20,10 @@ export const IN_SOURCE_CONFIG_MODULE = '@netlify/functions';
20
20
  const httpMethod = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE', 'HEAD']);
21
21
  const httpMethods = z.preprocess((input) => (typeof input === 'string' ? input.toUpperCase() : input), httpMethod);
22
22
  const path = z.string().startsWith('/', { message: "Must start with a '/'" });
23
+ const paths = z
24
+ .union([path, z.array(path)], { error: () => ({ message: 'Must be a string or array of strings' }) })
25
+ .transform(ensureArray)
26
+ .optional();
23
27
  export const inSourceConfig = functionConfigShape
24
28
  .pick({
25
29
  background: true,
@@ -43,14 +47,8 @@ export const inSourceConfig = functionConfigShape
43
47
  })
44
48
  .transform(ensureArray)
45
49
  .optional(),
46
- path: z
47
- .union([path, z.array(path)], { error: () => ({ message: 'Must be a string or array of strings' }) })
48
- .transform(ensureArray)
49
- .optional(),
50
- excludedPath: z
51
- .union([path, z.array(path)], { error: () => ({ message: 'Must be a string or array of strings' }) })
52
- .transform(ensureArray)
53
- .optional(),
50
+ path: paths,
51
+ excludedPath: paths,
54
52
  preferStatic: z.boolean().optional().catch(undefined),
55
53
  rateLimit: rateLimit.optional().catch(undefined),
56
54
  })
@@ -58,6 +56,13 @@ export const inSourceConfig = functionConfigShape
58
56
  message: '`memory` and `vcpu` cannot both be set.',
59
57
  path: ['vcpu'],
60
58
  });
59
+ export const serverInSourceConfig = z
60
+ .object({
61
+ includedFiles: z.array(z.string()).optional(),
62
+ path: paths,
63
+ region: serverRegion.optional(),
64
+ })
65
+ .strict();
61
66
  /**
62
67
  * Resolves the default export expression to an ObjectExpression if possible,
63
68
  * following identifier bindings when needed.
@@ -148,21 +153,47 @@ const validateScheduleFunction = (functionFound, scheduleFound, functionName) =>
148
153
  * Loads a file at a given path, parses it into an AST, and returns a series of
149
154
  * data points, such as in-source configuration properties and other metadata.
150
155
  */
151
- export const parseFile = async (sourcePath, { functionName }) => {
156
+ export const parseFile = async (sourcePath, { functionName, isServer }) => {
152
157
  const source = await safelyReadSource(sourcePath);
153
158
  if (source === null) {
154
159
  return {
155
160
  config: {},
156
161
  };
157
162
  }
158
- return parseSource(source, { functionName });
163
+ return parseSource(source, { functionName, isServer });
164
+ };
165
+ // Reads a named `config` export wrapped in `as const` or `satisfies`, which the
166
+ // shared export traversal doesn't unwrap.
167
+ const getWrappedConfigExport = (nodes, getAllBindings) => {
168
+ for (const node of nodes) {
169
+ if (node.type !== 'ExportNamedDeclaration' || node.declaration?.type !== 'VariableDeclaration') {
170
+ continue;
171
+ }
172
+ for (const declarator of node.declaration.declarations) {
173
+ if (declarator.id.type === 'Identifier' && declarator.id.name === 'config') {
174
+ const object = resolveObjectExpression(declarator.init ?? undefined, getAllBindings);
175
+ return object ? parseObject(object) : undefined;
176
+ }
177
+ }
178
+ }
179
+ };
180
+ const parseServerConfig = (config, functionName) => {
181
+ const { data, error, success } = serverInSourceConfig.safeParse(config);
182
+ if (success) {
183
+ return data;
184
+ }
185
+ const [issue] = error.issues;
186
+ const message = issue.code === 'unrecognized_keys'
187
+ ? `Netlify Server does not support the ${issue.keys.map((key) => `'${key}'`).join(', ')} configuration ${issue.keys.length === 1 ? 'property' : 'properties'}`
188
+ : `Netlify Server has a configuration error on '${issue.path.join('.')}': ${issue.message}`;
189
+ throw new FunctionBundlingUserError(message, { functionName, runtime: RUNTIME.JAVASCRIPT });
159
190
  };
160
191
  /**
161
192
  * Takes a JS/TS source as a string, parses it into an AST, and returns a
162
193
  * series of data points, such as in-source configuration properties and
163
194
  * other metadata.
164
195
  */
165
- export const parseSource = (source, { functionName }) => {
196
+ export const parseSource = (source, { functionName, isServer }) => {
166
197
  const ast = safelyParseSource(source);
167
198
  if (ast === null) {
168
199
  return {
@@ -175,6 +206,17 @@ export const parseSource = (source, { functionName }) => {
175
206
  let scheduleFound = false;
176
207
  const getAllBindings = createBindingsMethod(ast.body);
177
208
  const { configExport, handlerExports, hasDefaultExport, defaultExportExpression, inputModuleFormat } = traverseNodes(ast.body, getAllBindings);
209
+ if (isServer) {
210
+ const config = configExport ??
211
+ getWrappedConfigExport(ast.body, getAllBindings) ??
212
+ getConfigFromDefaultExport(defaultExportExpression, getAllBindings) ??
213
+ {};
214
+ return {
215
+ config: parseServerConfig(config, functionName),
216
+ inputModuleFormat,
217
+ runtimeAPIVersion: 2,
218
+ };
219
+ }
178
220
  const isV2API = handlerExports.length === 0 && hasDefaultExport;
179
221
  if (isV2API) {
180
222
  const result = {
@@ -51,7 +51,7 @@ const zipFunction = async function ({ archiveFormat, basePath, branch, cache, co
51
51
  basePath = dirname(pluginsModulesPath);
52
52
  pluginsModulesPath = undefined;
53
53
  }
54
- const staticAnalysisResult = await parseFile(mainFile, { functionName: name });
54
+ const staticAnalysisResult = await parseFile(mainFile, { functionName: name, isServer });
55
55
  const runtimeAPIVersion = isServer === true || staticAnalysisResult.runtimeAPIVersion === 2 ? 2 : 1;
56
56
  const mergedConfig = augmentFunctionConfig(mainFile, config, staticAnalysisResult.config);
57
57
  const { name: bundlerName, reason: bundlerReason } = await getBundlerName({
package/dist/server.d.ts CHANGED
@@ -8,6 +8,11 @@ import type { ExtendedRoute, Route } from './utils/routes.js';
8
8
  * none.
9
9
  */
10
10
  export declare const findServerEntry: (directory: string) => Promise<string | undefined>;
11
+ /**
12
+ * Returns the routes the server at `entryPath` claims, read from its in-source
13
+ * configuration, without bundling it.
14
+ */
15
+ export declare const getServerRoutes: (entryPath: string) => Promise<ExtendedRoute[]>;
11
16
  export type ServerResult = {
12
17
  bundler?: string;
13
18
  excludedRoutes?: Route[];
package/dist/server.js CHANGED
@@ -2,13 +2,13 @@ import { promises as fs } from 'fs';
2
2
  import { extname, join, resolve } from 'path';
3
3
  import { ARCHIVE_FORMAT } from './archive.js';
4
4
  import { getFunctionFromPath } from './runtimes/index.js';
5
+ import { parseFile } from './runtimes/node/in_source_config/index.js';
5
6
  import { RUNTIME } from './runtimes/runtime.js';
6
7
  import { FunctionBundlingUserError } from './utils/error.js';
7
8
  import { formatZipResult } from './utils/format_result.js';
8
9
  import { removeUndefined } from './utils/remove_undefined.js';
9
10
  import { getRoutes } from './utils/routes.js';
10
- // A server claims every path the rest of the deploy does not.
11
- const SERVER_ROUTE = '/*';
11
+ const DEFAULT_SERVER_PATHS = ['/*'];
12
12
  // Names the server's archive. Everywhere else it is only a label, identifying
13
13
  // the server in bundling errors, since a server has no name of its own.
14
14
  const SERVER_NAME = 'server';
@@ -38,6 +38,16 @@ export const findServerEntry = async (directory) => {
38
38
  }
39
39
  return join(directory, candidates[0]);
40
40
  };
41
+ // A server always sets `preferStatic: true`.
42
+ const toServerRoutes = (paths = DEFAULT_SERVER_PATHS) => getRoutes(SERVER_NAME, paths).map((route) => ({ ...route, prefer_static: true }));
43
+ /**
44
+ * Returns the routes the server at `entryPath` claims, read from its in-source
45
+ * configuration, without bundling it.
46
+ */
47
+ export const getServerRoutes = async (entryPath) => {
48
+ const { config } = await parseFile(resolve(entryPath), { functionName: SERVER_NAME, isServer: true });
49
+ return toServerRoutes(config.path);
50
+ };
41
51
  const readServerSource = async (srcPath, { cache, featureFlags }) => {
42
52
  let source;
43
53
  // A server that cannot be read is the user's file being wrong, so it is
@@ -96,7 +106,7 @@ export const bundleServer = async (server, destFolder, { basePath, cache, featur
96
106
  memory: result.memory,
97
107
  path: result.path,
98
108
  region: result.region,
99
- routes: getRoutes(SERVER_NAME, SERVER_ROUTE).map((route) => ({ ...route, prefer_static: true })),
109
+ routes: toServerRoutes(zipResult.staticAnalysisResult?.config.path),
100
110
  runtime: result.runtime,
101
111
  runtimeVersion: result.runtimeVersion,
102
112
  size: result.size,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/zip-it-and-ship-it",
3
- "version": "16.2.3",
3
+ "version": "16.3.0",
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.18.0",
47
+ "@netlify/serverless-functions-api": "2.24.0",
48
48
  "@opentelemetry/api": "~1.9.0",
49
49
  "@vercel/nft": "1.3.1",
50
50
  "archiver": "^8.0.0",
@@ -76,7 +76,7 @@
76
76
  "zod": "^4.0.5"
77
77
  },
78
78
  "devDependencies": {
79
- "@netlify/types": "2.7.0",
79
+ "@netlify/types": "3.1.0",
80
80
  "@opentelemetry/sdk-trace-base": "~2.8.0",
81
81
  "@types/archiver": "8.0.0",
82
82
  "@types/node": "^22.12.0",
@@ -100,5 +100,5 @@
100
100
  "engines": {
101
101
  "node": ">=22.12.0"
102
102
  },
103
- "gitHead": "227492a17c9821682390f6b7554bd69dfa282d9e"
103
+ "gitHead": "56df60307c59031b2ab044446d634b10851459e3"
104
104
  }