@netlify/edge-bundler 14.10.2 → 14.10.3

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.
@@ -165,33 +165,95 @@ async function getRequiredSourceFiles(deno, entryPoints, importMap) {
165
165
  pathToFileURL(entryPoint).href,
166
166
  ]);
167
167
  const graph = JSON.parse(stdout);
168
+ // Collect the specifiers of every module reachable through a *code* (runtime)
169
+ // import edge. Deno's module graph classifies each dependency as either a `code`
170
+ // edge (a real runtime import) or a `type`-only edge (`import type`, `@deno-types`).
171
+ // Type-only edges are erased during transpilation and are never loaded at runtime
172
+ // (`deno run` doesn't type-check), so the files they point to don't belong in the
173
+ // bundle. The entry points themselves are always runtime.
174
+ const runtimeSpecifiers = new Set(graph.roots);
175
+ for (const module of graph.modules) {
176
+ for (const dependency of module.dependencies ?? []) {
177
+ if (dependency.code?.specifier) {
178
+ runtimeSpecifiers.add(dependency.code.specifier);
179
+ }
180
+ }
181
+ }
168
182
  // Extract all local files from the module graph
169
183
  for (const module of graph.modules) {
170
- if (module.specifier.startsWith('file://')) {
171
- if (module.error?.startsWith('Module not found')) {
172
- // Module graph contains all found imported/required modules, even if they don't actually exist
173
- // This can happen for optional dependencies (dynamic import or require in try/catch).
184
+ if (!module.specifier.startsWith('file://')) {
185
+ continue;
186
+ }
187
+ if (module.error) {
188
+ // A module reachable only through type-only edges (e.g. a directory specifier
189
+ // behind `import type`) can fail to resolve as an ES module. That's safe to
190
+ // ignore: the runtime never loads it.
191
+ if (!runtimeSpecifiers.has(module.specifier)) {
192
+ continue;
193
+ }
194
+ if (module.error.startsWith('Module not found')) {
195
+ // Module graph contains all found imported/required modules, even if they don't
196
+ // actually exist. This can happen for optional dependencies (dynamic import or
197
+ // require in try/catch).
174
198
  continue;
175
199
  }
176
- const filePath = fileURLToPath(module.specifier);
177
- localFiles.add(filePath);
178
200
  }
201
+ localFiles.add(fileURLToPath(module.specifier));
179
202
  }
180
203
  }
181
204
  return localFiles;
182
205
  }
206
+ // WebAssembly binary magic bytes: `\0asm` (0x00 0x61 0x73 0x6d).
207
+ const WASM_MAGIC = Buffer.from([0x00, 0x61, 0x73, 0x6d]);
208
+ /**
209
+ * Detects whether a file is a raw WebAssembly module by its magic bytes.
210
+ * Deno <2.6 vendors imported `.wasm` modules under a `.d.mts` extension even
211
+ * though the content is the raw WASM binary, so we cannot rely on extension
212
+ * alone to decide whether a file is safe to read as UTF-8 and rewrite.
213
+ */
214
+ async function isWasm(sourceFile) {
215
+ const fd = await fs.open(sourceFile, 'r');
216
+ try {
217
+ const buf = Buffer.alloc(WASM_MAGIC.length);
218
+ const { bytesRead } = await fd.read(buf, 0, buf.length, 0);
219
+ return bytesRead === WASM_MAGIC.length && buf.equals(WASM_MAGIC);
220
+ }
221
+ finally {
222
+ await fd.close();
223
+ }
224
+ }
225
+ /**
226
+ * Decides whether a source file should be parsed and rewritten. Cheap extension
227
+ * check first; only if it passes do we open the file to rule out raw WebAssembly
228
+ * binaries (Deno <2.6 vendors `.wasm` imports under `.d.mts`) — reading those
229
+ * as UTF-8 would corrupt the binary on round-trip.
230
+ */
231
+ async function shouldRewrite(sourceFile) {
232
+ if (!REWRITABLE_EXTENSIONS.has(path.extname(sourceFile))) {
233
+ return false;
234
+ }
235
+ if (await isWasm(sourceFile)) {
236
+ return false;
237
+ }
238
+ return true;
239
+ }
183
240
  /**
184
241
  * Rewrites import assert into import with in the bundle directory
185
242
  * Defaults to copying the file in its current form
186
243
  */
187
244
  export async function rewriteImportAssertions(sourceFile, destPath) {
188
- if (!REWRITABLE_EXTENSIONS.has(path.extname(sourceFile))) {
245
+ if (!(await shouldRewrite(sourceFile))) {
189
246
  if (sourceFile !== destPath) {
190
247
  await fs.copyFile(sourceFile, destPath);
191
248
  }
192
249
  return;
193
250
  }
194
- const source = await fs.readFile(sourceFile, 'utf-8');
195
- const modified = rewriteSourceImportAssertions(source);
196
- await fs.writeFile(destPath, modified);
251
+ try {
252
+ const source = await fs.readFile(sourceFile, 'utf-8');
253
+ const modified = rewriteSourceImportAssertions(source);
254
+ await fs.writeFile(destPath, modified);
255
+ }
256
+ catch (error) {
257
+ throw new Error(`Failed to rewrite import assertions in ${sourceFile}`, { cause: error });
258
+ }
197
259
  }
@@ -1,6 +1,25 @@
1
1
  import { Parser } from 'acorn';
2
2
  import { tsPlugin } from '@sveltejs/acorn-typescript';
3
- const acorn = Parser.extend(tsPlugin({ jsx: true }));
3
+ const acornNoJSX = Parser.extend(tsPlugin({ jsx: false }));
4
+ const acornJSX = Parser.extend(tsPlugin({ jsx: true }));
5
+ const parseOptions = {
6
+ ecmaVersion: 'latest',
7
+ sourceType: 'module',
8
+ locations: true,
9
+ };
10
+ const parseAST = (source) => {
11
+ try {
12
+ return acornJSX.parse(source, parseOptions);
13
+ }
14
+ catch (error) {
15
+ // for non-jsx typescript casting to type via "<type> value" (normally done with "value as type") will throw an "Unexpected token" error in acorn-jsx,
16
+ // but is valid syntax in TypeScript. In this case, we can retry parsing with the non-jsx parser.
17
+ if (error instanceof SyntaxError) {
18
+ return acornNoJSX.parse(source, parseOptions);
19
+ }
20
+ throw error;
21
+ }
22
+ };
4
23
  /**
5
24
  * Given source code rewrites import assert into import with
6
25
  */
@@ -10,11 +29,7 @@ export function rewriteSourceImportAssertions(source) {
10
29
  }
11
30
  let modified = source;
12
31
  try {
13
- const parsedAST = acorn.parse(source, {
14
- ecmaVersion: 'latest',
15
- sourceType: 'module',
16
- locations: true,
17
- });
32
+ const parsedAST = parseAST(source);
18
33
  const statements = collectImportAssertions(source, parsedAST.body);
19
34
  // Bulk replacement of import assertions
20
35
  for (const statement of statements.sort((a, b) => b.start - a.start)) {
package/dist/test/util.js CHANGED
@@ -49,16 +49,18 @@ const inspectESZIPFunction = (path) => `
49
49
  console.log(JSON.stringify(responses));
50
50
  `;
51
51
  const inspectTarballFunction = () => `
52
- import path from "node:path";
53
- import { pathToFileURL } from "node:url";
54
52
  import manifest from "./___netlify-edge-functions.json" with { type: "json" };
55
53
 
56
54
  const responses = {};
57
55
 
58
56
  for (const functionName in manifest.functions) {
59
57
  const req = new Request("https://test.netlify");
60
- const entrypoint = path.resolve(manifest.functions[functionName]);
61
- const func = await import(pathToFileURL(entrypoint))
58
+ // Import via a relative specifier so Deno resolves the module URL itself,
59
+ // keeping its encoding consistent with the import map base (both derived from
60
+ // cwd). Pre-building an absolute file:// URL on the Node side encodes paths
61
+ // differently from Deno (e.g. '~' in Windows 8.3 short names), which breaks
62
+ // import map prefix matching on Deno 2.8+.
63
+ const func = await import("./" + manifest.functions[functionName]);
62
64
  const res = await func.default(req);
63
65
 
64
66
  responses[functionName] = await res.text();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "14.10.2",
3
+ "version": "14.10.3",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -80,5 +80,5 @@
80
80
  "tmp-promise": "^3.0.3",
81
81
  "urlpattern-polyfill": "8.0.2"
82
82
  },
83
- "gitHead": "0f6098d436437ff65fa97f6c191137f83e42918c"
83
+ "gitHead": "42d439a0a7c6e12f2444ce3feb66b030e8e4be81"
84
84
  }