@netlify/edge-bundler 14.9.19 → 14.10.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.
@@ -13,7 +13,7 @@ import { bundle as bundleESZIP } from './formats/eszip.js';
13
13
  import { bundle as bundleTarball } from './formats/tarball.js';
14
14
  import { ImportMap } from './import_map.js';
15
15
  import { getLogger } from './logger.js';
16
- import { writeManifest } from './manifest.js';
16
+ import { generateManifestFunctionConfig, generateManifestRoutes, writeManifest } from './manifest.js';
17
17
  import { vendorNPMSpecifiers } from './npm_dependencies.js';
18
18
  import { ensureLatestTypes } from './types.js';
19
19
  import { nonNullable } from './utils/non_nullable.js';
@@ -66,9 +66,10 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
66
66
  }
67
67
  const bundles = [];
68
68
  let tarballBundleDurationMs;
69
- let tarballLogMsg;
69
+ let tarballDryRunError;
70
+ let finalizeTarballBundle;
70
71
  if (featureFlags.edge_bundler_generate_tarball || featureFlags.edge_bundler_dry_run_generate_tarball) {
71
- const tarballPromise = (async () => {
72
+ const tarballInitialPromise = (async () => {
72
73
  const start = Date.now();
73
74
  try {
74
75
  return await bundleTarball({
@@ -90,21 +91,15 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
90
91
  let tarballPromiseResolved = false;
91
92
  if (featureFlags.edge_bundler_dry_run_generate_tarball) {
92
93
  try {
93
- await tarballPromise;
94
- tarballLogMsg = 'Dry run: Eszip and tarball bundle generated successfully.';
94
+ await tarballInitialPromise;
95
95
  tarballPromiseResolved = true;
96
96
  }
97
97
  catch (error) {
98
- if (error instanceof Error) {
99
- tarballLogMsg = `Dry run: Eszip successful, tarball bundle generation failed: ${error.message}`;
100
- }
101
- else {
102
- tarballLogMsg = `Dry run: Eszip successful, tarball bundle generation failed: ${String(error)}`;
103
- }
98
+ tarballDryRunError = error ?? new Error('Unknown error during tarball bundle generation');
104
99
  }
105
100
  }
106
101
  if (featureFlags.edge_bundler_generate_tarball || tarballPromiseResolved) {
107
- bundles.push(await tarballPromise);
102
+ finalizeTarballBundle = await tarballInitialPromise;
108
103
  }
109
104
  }
110
105
  bundles.push(await bundleESZIP({
@@ -119,16 +114,6 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
119
114
  importMap,
120
115
  vendorDirectory: vendor?.directory,
121
116
  }));
122
- // Log tarball generation status after eszip bundling succeeds (only set during dry runs).
123
- if (tarballLogMsg) {
124
- // Reported errors might be multiple lines, so we replace newlines with the literal string '\n' to get a single log line,
125
- // while still ensuring it could be expanded into the original multi-line message if needed.
126
- logger.system(tarballLogMsg.replaceAll('\n', '\\n'));
127
- }
128
- // The final file name of the bundles contains a SHA256 hash of the contents,
129
- // which we can only compute now that the files have been generated. So let's
130
- // rename the bundles to their permanent names.
131
- await createFinalBundles(bundles, distDirectory, buildID);
132
117
  const { internalFunctions: internalFunctionsWithConfig, userFunctions: userFunctionsWithConfig } = await getFunctionConfigs({
133
118
  deno,
134
119
  importMap,
@@ -143,14 +128,59 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
143
128
  internalFunctionsWithConfig,
144
129
  declarations,
145
130
  });
131
+ const manifestFunctionConfig = generateManifestFunctionConfig({
132
+ functions,
133
+ internalFunctionConfig,
134
+ userFunctionConfig: userFunctionsWithConfig,
135
+ });
136
+ const manifestRoutes = generateManifestRoutes({
137
+ functions,
138
+ declarations,
139
+ });
140
+ if (!tarballDryRunError && finalizeTarballBundle) {
141
+ try {
142
+ bundles.unshift(await finalizeTarballBundle({ manifestFunctionConfig, manifestRoutes }));
143
+ }
144
+ catch (error) {
145
+ if (featureFlags.edge_bundler_dry_run_generate_tarball) {
146
+ tarballDryRunError = error ?? new Error('Unknown error during tarball bundle finalization');
147
+ }
148
+ else {
149
+ throw error;
150
+ }
151
+ }
152
+ }
153
+ if (featureFlags.edge_bundler_dry_run_generate_tarball) {
154
+ let tarballLogMsg;
155
+ if (tarballDryRunError) {
156
+ if (tarballDryRunError instanceof Error) {
157
+ tarballLogMsg = `Dry run: Eszip successful, tarball bundle generation failed: ${tarballDryRunError.message}`;
158
+ }
159
+ else {
160
+ tarballLogMsg = `Dry run: Eszip successful, tarball bundle generation failed: ${String(tarballDryRunError)}`;
161
+ }
162
+ }
163
+ else {
164
+ tarballLogMsg = 'Dry run: Eszip and tarball bundle generated successfully.';
165
+ }
166
+ if (tarballLogMsg) {
167
+ // Log tarball generation status after eszip bundling succeeds (only set during dry runs).
168
+ // Reported errors might be multiple lines, so we replace newlines with the literal string '\n' to get a single log line,
169
+ // while still ensuring it could be expanded into the original multi-line message if needed.
170
+ logger.system(tarballLogMsg.replaceAll('\n', '\\n'));
171
+ }
172
+ }
173
+ // The final file name of the bundles contains a SHA256 hash of the contents,
174
+ // which we can only compute now that the files have been generated. So let's
175
+ // rename the bundles to their permanent names.
176
+ await createFinalBundles(bundles, distDirectory, buildID);
146
177
  const manifest = await writeManifest({
147
178
  bundles,
148
- declarations,
149
179
  distDirectory,
150
180
  featureFlags,
151
181
  functions,
152
- userFunctionConfig: userFunctionsWithConfig,
153
- internalFunctionConfig,
182
+ manifestFunctionConfig,
183
+ manifestRoutes,
154
184
  importMap: importMapSpecifier,
155
185
  layers: deployConfig.layers,
156
186
  bundlingTiming: tarballBundleDurationMs === undefined ? undefined : { tarball_ms: tarballBundleDurationMs },
@@ -6,7 +6,7 @@ export declare const enum Cache {
6
6
  Off = "off",
7
7
  Manual = "manual"
8
8
  }
9
- export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
9
+ export type HTTPMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
10
10
  export type Path = `/${string}`;
11
11
  export type OnError = 'fail' | 'bypass' | Path;
12
12
  export declare const isValidOnError: (value: unknown) => value is OnError;
@@ -3,6 +3,8 @@ import { Bundle } from '../bundle.js';
3
3
  import { EdgeFunction } from '../edge_function.js';
4
4
  import { FeatureFlags } from '../feature_flags.js';
5
5
  import { ImportMap } from '../import_map.js';
6
+ import { EdgeFunctionConfig } from '../index.js';
7
+ import { generateManifestRoutes } from '../manifest.js';
6
8
  interface BundleTarballOptions {
7
9
  basePath: string;
8
10
  buildID: string;
@@ -14,7 +16,11 @@ interface BundleTarballOptions {
14
16
  importMap: ImportMap;
15
17
  vendorDirectory?: string;
16
18
  }
17
- export declare const bundle: ({ buildID, deno, distDirectory, functions, importMap, vendorDirectory, }: BundleTarballOptions) => Promise<Bundle>;
19
+ interface FinalizeTarballBundleOptions {
20
+ manifestFunctionConfig: Record<string, EdgeFunctionConfig>;
21
+ manifestRoutes: ReturnType<typeof generateManifestRoutes>;
22
+ }
23
+ export declare const bundle: ({ buildID, deno, distDirectory, functions, importMap, vendorDirectory, }: BundleTarballOptions) => Promise<(arg: FinalizeTarballBundleOptions) => Promise<Bundle>>;
18
24
  /**
19
25
  * Rewrites import assert into import with in the bundle directory
20
26
  * Defaults to copying the file in its current form
@@ -13,9 +13,9 @@ const getUnixPath = (input) => input.split(path.sep).join('/');
13
13
  export const bundle = async ({ buildID, deno, distDirectory, functions, importMap, vendorDirectory, }) => {
14
14
  const bundleDir = await tmp.dir({ unsafeCleanup: true });
15
15
  const cleanup = [bundleDir.cleanup];
16
- const manifest = {
16
+ const initialManifest = {
17
17
  functions: {},
18
- version: 1,
18
+ version: 2,
19
19
  };
20
20
  const entryPoints = functions.map((func) => func.path);
21
21
  // Use deno info to get the module graph and identify which local files are actually needed.
@@ -52,7 +52,7 @@ export const bundle = async ({ buildID, deno, distDirectory, functions, importMa
52
52
  // Build the manifest mapping function names to their relative paths
53
53
  for (const func of functions) {
54
54
  const relativePath = path.relative(commonPath, func.path);
55
- manifest.functions[func.name] = getUnixPath(relativePath);
55
+ initialManifest.functions[func.name] = getUnixPath(relativePath);
56
56
  }
57
57
  for (const sourceFile of sourceFilesSet) {
58
58
  let relativePath = path.relative(commonPath, sourceFile);
@@ -87,7 +87,7 @@ export const bundle = async ({ buildID, deno, distDirectory, functions, importMa
87
87
  '--node-modules-dir=manual',
88
88
  '--vendor',
89
89
  '--entrypoint',
90
- ...Object.values(manifest.functions),
90
+ ...Object.values(initialManifest.functions),
91
91
  ], {
92
92
  cwd: bundleDir.path,
93
93
  });
@@ -99,37 +99,49 @@ export const bundle = async ({ buildID, deno, distDirectory, functions, importMa
99
99
  await rewriteImportAssertions(denoVendorFile, denoVendorFile);
100
100
  }
101
101
  }
102
- const manifestPath = path.join(bundleDir.path, '___netlify-edge-functions.json');
103
- const manifestContents = JSON.stringify(manifest);
104
- await fs.writeFile(manifestPath, manifestContents);
105
- const tarballPath = path.join(distDirectory, buildID + TARBALL_EXTENSION);
106
- await fs.mkdir(path.dirname(tarballPath), { recursive: true });
107
- // List files to include in the tarball as paths relative to the bundle dir.
108
- // Using absolute paths here leads to platform-specific quirks (notably on Windows),
109
- // where entries can include drive letters and break extraction/imports.
110
- // The './' prefix is required to prevent node-tar from interpreting entries
111
- // starting with '@' as GNU tar archive-include directives, which would cause
112
- // it to strip the '@' and stat a non-existent path (ENOENT).
113
- const files = (await listRecursively(bundleDir.path))
114
- .map((p) => path.relative(bundleDir.path, p))
115
- .map((p) => './' + getUnixPath(p))
116
- .sort();
117
- await tar.create({
118
- cwd: bundleDir.path,
119
- file: tarballPath,
120
- gzip: true,
121
- noDirRecurse: true,
122
- // Ensure forward slashes inside the tarball for cross-platform consistency.
123
- onWriteEntry(entry) {
124
- entry.path = getUnixPath(entry.path);
125
- },
126
- }, files);
127
- const hash = await getFileHash(tarballPath);
128
- await Promise.allSettled(cleanup);
129
- return {
130
- extension: TARBALL_EXTENSION,
131
- format: BundleFormat.TARBALL,
132
- hash,
102
+ // First stage of bundling is now done. To finalize bundling we require functionConfig, routes and postCacheRoutes
103
+ // so we could inject those into bundle manifest. Tarball bundling is done in 2-step process process to preserve ordering
104
+ // and potential errors messages that could be thrown to make sure we don't impact behaviors. Otherwise we would throw different
105
+ // kind of errors than we used to and introduce confusion for users.
106
+ return async function finalizeBundle({ manifestFunctionConfig, manifestRoutes }) {
107
+ const manifest = {
108
+ ...initialManifest,
109
+ function_config: manifestFunctionConfig,
110
+ routes: manifestRoutes.preCacheRoutes,
111
+ post_cache_routes: manifestRoutes.postCacheRoutes,
112
+ };
113
+ const manifestPath = path.join(bundleDir.path, '___netlify-edge-functions.json');
114
+ const manifestContents = JSON.stringify(manifest);
115
+ await fs.writeFile(manifestPath, manifestContents);
116
+ const tarballPath = path.join(distDirectory, buildID + TARBALL_EXTENSION);
117
+ await fs.mkdir(path.dirname(tarballPath), { recursive: true });
118
+ // List files to include in the tarball as paths relative to the bundle dir.
119
+ // Using absolute paths here leads to platform-specific quirks (notably on Windows),
120
+ // where entries can include drive letters and break extraction/imports.
121
+ // The './' prefix is required to prevent node-tar from interpreting entries
122
+ // starting with '@' as GNU tar archive-include directives, which would cause
123
+ // it to strip the '@' and stat a non-existent path (ENOENT).
124
+ const files = (await listRecursively(bundleDir.path))
125
+ .map((p) => path.relative(bundleDir.path, p))
126
+ .map((p) => './' + getUnixPath(p))
127
+ .sort();
128
+ await tar.create({
129
+ cwd: bundleDir.path,
130
+ file: tarballPath,
131
+ gzip: true,
132
+ noDirRecurse: true,
133
+ // Ensure forward slashes inside the tarball for cross-platform consistency.
134
+ onWriteEntry(entry) {
135
+ entry.path = getUnixPath(entry.path);
136
+ },
137
+ }, files);
138
+ const hash = await getFileHash(tarballPath);
139
+ await Promise.allSettled(cleanup);
140
+ return {
141
+ extension: TARBALL_EXTENSION,
142
+ format: BundleFormat.TARBALL,
143
+ hash,
144
+ };
133
145
  };
134
146
  };
135
147
  // Source file extensions that may contain import statements.
@@ -56,24 +56,42 @@ interface Manifest {
56
56
  post_cache_routes: Route[];
57
57
  function_config: Record<string, EdgeFunctionConfig>;
58
58
  }
59
- interface GenerateManifestOptions {
60
- bundles?: Bundle[];
59
+ interface GenerateManifestFunctionConfigOptions {
60
+ functions: EdgeFunction[];
61
+ internalFunctionConfig?: Record<string, FunctionConfig>;
62
+ userFunctionConfig?: Record<string, FunctionConfig>;
63
+ }
64
+ interface GenerateManifestRoutesOptions {
65
+ functions: EdgeFunction[];
61
66
  declarations?: Declaration[];
67
+ }
68
+ interface GenerateManifestOptionsBase {
69
+ bundles?: Bundle[];
62
70
  featureFlags?: FeatureFlags;
63
- functions: EdgeFunction[];
64
71
  importMap?: string;
65
- internalFunctionConfig?: Record<string, FunctionConfig>;
66
72
  layers?: Layer[];
67
- userFunctionConfig?: Record<string, FunctionConfig>;
68
73
  bundlingTiming?: BundlingTiming;
74
+ functions: EdgeFunction[];
69
75
  }
70
- declare const generateManifest: ({ bundles, declarations, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, bundlingTiming, }: GenerateManifestOptions) => {
76
+ export declare const generateManifestFunctionConfig: ({ functions, userFunctionConfig, internalFunctionConfig, }: GenerateManifestFunctionConfigOptions) => Record<string, EdgeFunctionConfig>;
77
+ export declare const generateManifestRoutes: ({ functions, declarations }: GenerateManifestRoutesOptions) => {
78
+ preCacheRoutes: Route[];
79
+ postCacheRoutes: Route[];
80
+ unroutedFunctions: string[];
81
+ declarationsWithoutFunction: Set<string>;
82
+ };
83
+ type GenerateManifestOptions = GenerateManifestOptionsBase & (GenerateManifestFunctionConfigOptions | {
84
+ manifestFunctionConfig: ReturnType<typeof generateManifestFunctionConfig>;
85
+ }) & (GenerateManifestRoutesOptions | {
86
+ manifestRoutes: ReturnType<typeof generateManifestRoutes>;
87
+ });
88
+ declare const generateManifest: ({ bundles, importMap, layers, bundlingTiming, functions, ...rest }: GenerateManifestOptions) => {
71
89
  declarationsWithoutFunction: string[];
72
90
  manifest: Manifest;
73
91
  unroutedFunctions: string[];
74
92
  };
75
- interface WriteManifestOptions extends GenerateManifestOptions {
93
+ type WriteManifestOptions = GenerateManifestOptions & {
76
94
  distDirectory: string;
77
- }
95
+ };
78
96
  declare const writeManifest: ({ distDirectory, ...rest }: WriteManifestOptions) => Promise<Manifest>;
79
97
  export { generateManifest, Manifest, Route, writeManifest };
@@ -54,12 +54,8 @@ const normalizeMethods = (method, name) => {
54
54
  return method.toUpperCase();
55
55
  });
56
56
  };
57
- const generateManifest = ({ bundles = [], declarations = [], functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], bundlingTiming, }) => {
58
- const preCacheRoutes = [];
59
- const postCacheRoutes = [];
57
+ export const generateManifestFunctionConfig = ({ functions, userFunctionConfig = {}, internalFunctionConfig = {}, }) => {
60
58
  const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
61
- const routedFunctions = new Set();
62
- const declarationsWithoutFunction = new Set();
63
59
  for (const [name, singleUserFunctionConfig] of Object.entries(userFunctionConfig)) {
64
60
  // If the config block is for a function that is not defined, discard it.
65
61
  if (manifestFunctionConfig[name] === undefined) {
@@ -97,6 +93,13 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
97
93
  ...rest,
98
94
  };
99
95
  }
96
+ return sanitizeEdgeFunctionConfig(manifestFunctionConfig);
97
+ };
98
+ export const generateManifestRoutes = ({ functions, declarations = [] }) => {
99
+ const preCacheRoutes = [];
100
+ const postCacheRoutes = [];
101
+ const routedFunctions = new Set();
102
+ const declarationsWithoutFunction = new Set();
100
103
  declarations.forEach((declaration) => {
101
104
  const func = functions.find(({ name }) => declaration.function === name);
102
105
  if (func === undefined) {
@@ -132,14 +135,32 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
132
135
  preCacheRoutes.push(route);
133
136
  }
134
137
  });
138
+ const unroutedFunctions = functions.filter(({ name }) => !routedFunctions.has(name)).map(({ name }) => name);
139
+ return {
140
+ preCacheRoutes: preCacheRoutes.filter(nonNullable),
141
+ postCacheRoutes: postCacheRoutes.filter(nonNullable),
142
+ unroutedFunctions,
143
+ declarationsWithoutFunction,
144
+ };
145
+ };
146
+ const generateManifest = ({ bundles = [], importMap, layers = [], bundlingTiming, functions, ...rest }) => {
147
+ const manifestFunctionConfig = 'manifestFunctionConfig' in rest
148
+ ? rest.manifestFunctionConfig
149
+ : generateManifestFunctionConfig({ functions, ...rest });
150
+ const { preCacheRoutes, postCacheRoutes, unroutedFunctions, declarationsWithoutFunction } = 'manifestRoutes' in rest
151
+ ? rest.manifestRoutes
152
+ : generateManifestRoutes({
153
+ functions,
154
+ ...rest,
155
+ });
135
156
  const manifestBundles = bundles.map(({ extension, format, hash }) => ({
136
157
  asset: hash + extension,
137
158
  format,
138
159
  }));
139
160
  const manifest = {
140
161
  bundles: manifestBundles,
141
- routes: preCacheRoutes.filter(nonNullable),
142
- post_cache_routes: postCacheRoutes.filter(nonNullable),
162
+ routes: preCacheRoutes,
163
+ post_cache_routes: postCacheRoutes,
143
164
  bundler_version: getPackageVersion(),
144
165
  layers,
145
166
  import_map: importMap,
@@ -148,7 +169,6 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
148
169
  ? { bundling_timing: bundlingTiming }
149
170
  : {}),
150
171
  };
151
- const unroutedFunctions = functions.filter(({ name }) => !routedFunctions.has(name)).map(({ name }) => name);
152
172
  return { declarationsWithoutFunction: [...declarationsWithoutFunction], manifest, unroutedFunctions };
153
173
  };
154
174
  const getTrafficRulesConfig = (rl) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "14.9.19",
3
+ "version": "14.10.1",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -81,5 +81,5 @@
81
81
  "urlpattern-polyfill": "8.0.2",
82
82
  "uuid": "^11.0.0"
83
83
  },
84
- "gitHead": "3694352be4959c56e96a6c15dd04ed4d7aa31344"
84
+ "gitHead": "d17373f5f6ab58289a5bc78d90bd0b520c9c652f"
85
85
  }