@netlify/edge-bundler 9.2.1 → 9.4.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.
@@ -158,6 +158,7 @@ const safelyVendorNPMSpecifiers = async ({ basePath, featureFlags, functions, im
158
158
  functions: functions.map(({ path }) => path),
159
159
  importMap,
160
160
  logger,
161
+ referenceTypes: false,
161
162
  });
162
163
  }
163
164
  catch (error) {
@@ -123,7 +123,7 @@ test('Prints a nice error message when user tries importing an npm module and np
123
123
  }
124
124
  catch (error) {
125
125
  expect(error).toBeInstanceOf(BundleError);
126
- expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/parent-2"'?`);
126
+ expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/parent-1"'?`);
127
127
  }
128
128
  finally {
129
129
  await cleanup();
@@ -3,7 +3,7 @@ import { Logger } from './logger.js';
3
3
  type Imports = Record<string, string>;
4
4
  export interface ImportMapFile {
5
5
  baseURL: URL;
6
- imports: Imports;
6
+ imports?: Imports;
7
7
  scopes?: Record<string, Imports>;
8
8
  }
9
9
  export declare class ImportMap {
@@ -23,9 +23,6 @@ export class ImportMap {
23
23
  }
24
24
  async addFile(path, logger) {
25
25
  const source = await ImportMap.readFile(path, logger);
26
- if (Object.keys(source.imports).length === 0) {
27
- return;
28
- }
29
26
  return this.add(source);
30
27
  }
31
28
  async addFiles(paths, logger) {
@@ -5,6 +5,7 @@ import { pathToFileURL } from 'url';
5
5
  import tmp from 'tmp-promise';
6
6
  import { describe, test, expect } from 'vitest';
7
7
  import { ImportMap } from './import_map.js';
8
+ import { getLogger } from './logger.js';
8
9
  test('Handles import maps with full URLs without specifying a base URL', () => {
9
10
  const basePath = join(cwd(), 'my-cool-site', 'import-map.json');
10
11
  const inputFile1 = {
@@ -143,6 +144,30 @@ test('Writes import map file to disk', async () => {
143
144
  expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
144
145
  expect(imports['alias:pets']).toBe(pathToFileURL(expectedPath).toString());
145
146
  });
147
+ test('Respects import map when it has only scoped key', async () => {
148
+ const file = await tmp.file();
149
+ const importMap = {
150
+ scopes: {
151
+ './foo': {
152
+ 'alias:pets': './heart/pets/file.ts',
153
+ },
154
+ },
155
+ };
156
+ await fs.writeFile(file.path, JSON.stringify(importMap));
157
+ const map = new ImportMap();
158
+ await map.addFile(file.path, getLogger());
159
+ expect(map.getContents()).toEqual({
160
+ imports: {
161
+ 'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
162
+ '@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
163
+ },
164
+ scopes: {
165
+ [pathToFileURL(join(file.path, '../foo')).href]: {
166
+ 'alias:pets': pathToFileURL(join(file.path, '../heart/pets/file.ts')).href,
167
+ },
168
+ },
169
+ });
170
+ });
146
171
  test('Clones an import map', () => {
147
172
  const basePath = join(cwd(), 'my-cool-site', 'import-map.json');
148
173
  const inputFile1 = {
@@ -1,22 +1,21 @@
1
1
  /// <reference types="node" />
2
- import { ParsedImportMap } from '@import-maps/resolve';
3
- import { Plugin } from 'esbuild';
4
2
  import { ImportMap } from './import_map.js';
5
3
  import { Logger } from './logger.js';
6
- export declare const getDependencyTrackerPlugin: (specifiers: Set<string>, importMap: ParsedImportMap, baseURL: URL) => Plugin;
7
4
  interface VendorNPMSpecifiersOptions {
8
5
  basePath: string;
9
6
  directory?: string;
10
7
  functions: string[];
11
8
  importMap: ImportMap;
12
9
  logger: Logger;
10
+ referenceTypes: boolean;
13
11
  }
14
- export declare const vendorNPMSpecifiers: ({ basePath, directory, functions, importMap, logger, }: VendorNPMSpecifiersOptions) => Promise<{
12
+ export declare const vendorNPMSpecifiers: ({ basePath, directory, functions, importMap, referenceTypes, }: VendorNPMSpecifiersOptions) => Promise<{
15
13
  cleanup: () => Promise<void>;
16
14
  directory: string;
17
15
  importMap: {
18
16
  baseURL: import("url").URL;
19
17
  imports: Record<string, string>;
20
18
  };
19
+ npmSpecifiersWithExtraneousFiles: string[];
21
20
  } | undefined>;
22
21
  export {};
@@ -1,13 +1,67 @@
1
1
  import { promises as fs } from 'fs';
2
- import { builtinModules, createRequire } from 'module';
2
+ import { builtinModules } from 'module';
3
3
  import path from 'path';
4
4
  import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { resolve } from '@import-maps/resolve';
6
+ import { nodeFileTrace, resolve as nftResolve } from '@vercel/nft';
6
7
  import { build } from 'esbuild';
8
+ import { findUp } from 'find-up';
9
+ import getPackageName from 'get-package-name';
7
10
  import tmp from 'tmp-promise';
8
- import { nodePrefix, npmPrefix } from '../shared/consts.js';
9
- const builtinModulesSet = new Set(builtinModules);
10
- const require = createRequire(import.meta.url);
11
+ const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.cts', '.mts']);
12
+ const slugifyPackageName = (specifier) => {
13
+ if (!specifier.startsWith('@'))
14
+ return specifier;
15
+ const [scope, pkg] = specifier.split('/');
16
+ return `${scope.replace('@', '')}__${pkg}`;
17
+ };
18
+ /**
19
+ * Returns the name of the `@types/` package used by a given specifier. Most of
20
+ * the times this is just the specifier itself, but scoped packages suffer a
21
+ * transformation (e.g. `@netlify/functions` -> `@types/netlify__functions`).
22
+ * https://github.com/DefinitelyTyped/DefinitelyTyped#what-about-scoped-packages
23
+ */
24
+ const getTypesPackageName = (specifier) => path.join('@types', slugifyPackageName(specifier));
25
+ /**
26
+ * Finds corresponding DefinitelyTyped packages (`@types/...`) and returns path to declaration file.
27
+ */
28
+ const getTypePathFromTypesPackage = async (packageName, packageJsonPath) => {
29
+ const typesPackagePath = await findUp(`node_modules/${getTypesPackageName(packageName)}/package.json`, {
30
+ cwd: packageJsonPath,
31
+ });
32
+ if (!typesPackagePath) {
33
+ return undefined;
34
+ }
35
+ const { types, typings } = JSON.parse(await fs.readFile(typesPackagePath, 'utf8'));
36
+ const declarationPath = types !== null && types !== void 0 ? types : typings;
37
+ if (typeof declarationPath === 'string') {
38
+ return path.join(typesPackagePath, '..', declarationPath);
39
+ }
40
+ return undefined;
41
+ };
42
+ /**
43
+ * Starting from a `package.json` file, this tries detecting a TypeScript declaration file.
44
+ * It first looks at the `types` and `typings` fields in `package.json`.
45
+ * If it doesn't find them, it falls back to DefinitelyTyped packages (`@types/...`).
46
+ */
47
+ const getTypesPath = async (packageJsonPath) => {
48
+ const { name, types, typings } = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
49
+ // this only looks at `.types` and `.typings` fields. there might also be data in `exports -> . -> types -> import/default`.
50
+ // we're ignoring that for now.
51
+ const declarationPath = types !== null && types !== void 0 ? types : typings;
52
+ if (typeof declarationPath === 'string') {
53
+ return path.join(packageJsonPath, '..', declarationPath);
54
+ }
55
+ return await getTypePathFromTypesPackage(name, packageJsonPath);
56
+ };
57
+ const safelyDetectTypes = async (packageJsonPath) => {
58
+ try {
59
+ return await getTypesPath(packageJsonPath);
60
+ }
61
+ catch {
62
+ return undefined;
63
+ }
64
+ };
11
65
  // Workaround for https://github.com/evanw/esbuild/issues/1921.
12
66
  const banner = {
13
67
  js: `
@@ -19,72 +73,99 @@ const banner = {
19
73
  let require=___nfyCreateRequire(import.meta.url);
20
74
  `,
21
75
  };
22
- // esbuild plugin that will traverse the code and look for imports of external
23
- // dependencies (i.e. Node modules). It stores the specifiers found in the Set
24
- // provided.
25
- export const getDependencyTrackerPlugin = (specifiers, importMap, baseURL) => ({
26
- name: 'dependency-tracker',
27
- setup(build) {
28
- build.onResolve({ filter: /^(.*)$/ }, (args) => {
29
- if (args.kind !== 'import-statement') {
30
- return;
76
+ /**
77
+ * Parses a set of functions and returns a list of specifiers that correspond
78
+ * to npm modules.
79
+ *
80
+ * @param basePath Root of the project
81
+ * @param functions Functions to parse
82
+ * @param importMap Import map to apply when resolving imports
83
+ * @param referenceTypes Whether to detect typescript declarations and reference them in the output
84
+ */
85
+ const getNPMSpecifiers = async (basePath, functions, importMap, referenceTypes) => {
86
+ const baseURL = pathToFileURL(basePath);
87
+ const { reasons } = await nodeFileTrace(functions, {
88
+ base: basePath,
89
+ readFile: async (filePath) => {
90
+ // If this is a TypeScript file, we need to compile in before we can
91
+ // parse it.
92
+ if (TYPESCRIPT_EXTENSIONS.has(path.extname(filePath))) {
93
+ const compiled = await build({
94
+ bundle: false,
95
+ entryPoints: [filePath],
96
+ logLevel: 'silent',
97
+ platform: 'node',
98
+ write: false,
99
+ });
100
+ return compiled.outputFiles[0].text;
31
101
  }
32
- const result = {};
33
- let specifier = args.path;
102
+ return fs.readFile(filePath, 'utf8');
103
+ },
104
+ // eslint-disable-next-line require-await
105
+ resolve: async (specifier, ...args) => {
34
106
  // Start by checking whether the specifier matches any import map defined
35
107
  // by the user.
36
108
  const { matched, resolvedImport } = resolve(specifier, importMap, baseURL);
37
109
  // If it does, the resolved import is the specifier we'll evaluate going
38
110
  // forward.
39
- if (matched) {
40
- if (resolvedImport.protocol !== 'file:') {
41
- return { external: true };
42
- }
43
- specifier = fileURLToPath(resolvedImport).replace(/\\/g, '/');
44
- result.path = specifier;
45
- }
46
- // If the specifier is a Node.js built-in, we don't want to bundle it.
47
- if (specifier.startsWith(nodePrefix) || builtinModulesSet.has(specifier)) {
48
- return { external: true };
49
- }
50
- // We don't support the `npm:` prefix yet. Mark the specifier as external
51
- // and the ESZIP bundler will handle the failure.
52
- if (specifier.startsWith(npmPrefix)) {
53
- return { external: true };
54
- }
55
- const isLocalImport = specifier.startsWith(path.sep) || specifier.startsWith('.') || path.isAbsolute(specifier);
56
- // If this is a local import, return so that esbuild visits that path.
57
- if (isLocalImport) {
58
- return result;
59
- }
60
- const isRemoteURLImport = specifier.startsWith('https://') || specifier.startsWith('http://');
61
- if (isRemoteURLImport) {
62
- return { external: true };
63
- }
64
- // At this point we know we're dealing with a bare specifier that should
65
- // be treated as an external module. We first try to resolve it, because
66
- // in the event that it doesn't exist (e.g. user is referencing a module
67
- // that they haven't installed) we won't even attempt to bundle it. This
68
- // lets the ESZIP bundler handle and report the missing import instead of
69
- // esbuild, which is a better experience for the user.
70
- try {
71
- require.resolve(specifier, { paths: [args.resolveDir] });
72
- specifiers.add(specifier);
111
+ if (matched && resolvedImport.protocol === 'file:') {
112
+ const newSpecifier = fileURLToPath(resolvedImport).replace(/\\/g, '/');
113
+ return nftResolve(newSpecifier, ...args);
73
114
  }
74
- catch {
75
- // no-op
76
- }
77
- // Mark the specifier as external, because we don't want to traverse the
78
- // entire module tree — i.e. if user code imports module `foo` and that
79
- // imports `bar`, we only want to add `foo` to the list of specifiers,
80
- // since the whole module — including its dependencies like `bar` —
81
- // will be bundled.
82
- return { external: true };
115
+ return nftResolve(specifier, ...args);
116
+ },
117
+ });
118
+ const npmSpecifiers = [];
119
+ const npmSpecifiersWithExtraneousFiles = new Set();
120
+ for (const [filePath, reason] of reasons.entries()) {
121
+ const parents = [...reason.parents];
122
+ const isExtraneousFile = reason.type.every((type) => type === 'asset');
123
+ // An extraneous file is a dependency that was traced by NFT and marked
124
+ // as not being statically imported. We can't process dynamic importing
125
+ // at runtime, so we gather the list of modules that may use these files
126
+ // so that we can warn users about this caveat.
127
+ if (isExtraneousFile) {
128
+ parents.forEach((path) => {
129
+ const specifier = getPackageName(path);
130
+ if (specifier) {
131
+ npmSpecifiersWithExtraneousFiles.add(specifier);
132
+ }
133
+ });
134
+ }
135
+ // every dependency will have its `package.json` in `reasons` exactly once.
136
+ // by only looking at this file, we save us from doing duplicate work.
137
+ const isPackageJson = filePath.endsWith('package.json');
138
+ if (!isPackageJson)
139
+ continue;
140
+ const packageName = getPackageName(filePath);
141
+ if (packageName === undefined)
142
+ continue;
143
+ const isDirectDependency = parents.some((parentPath) => {
144
+ var _a, _b;
145
+ if (!parentPath.startsWith(`node_modules${path.sep}`))
146
+ return true;
147
+ // typically, edge functions have no direct dependency on the `package.json` of a module.
148
+ // it's the impl files that depend on `package.json`, so we need to check the parents of
149
+ // the `package.json` file as well to see if the module is a direct dependency.
150
+ const parents = [...((_b = (_a = reasons.get(parentPath)) === null || _a === void 0 ? void 0 : _a.parents) !== null && _b !== void 0 ? _b : [])];
151
+ return parents.some((parentPath) => !parentPath.startsWith(`node_modules${path.sep}`));
83
152
  });
84
- },
85
- });
86
- export const vendorNPMSpecifiers = async ({ basePath, directory, functions, importMap, logger, }) => {
87
- const specifiers = new Set();
153
+ // We're only interested in capturing the specifiers that are first-level
154
+ // dependencies. Because we'll bundle all modules in a subsequent step,
155
+ // any transitive dependencies will be handled then.
156
+ if (isDirectDependency) {
157
+ npmSpecifiers.push({
158
+ specifier: packageName,
159
+ types: referenceTypes ? await safelyDetectTypes(path.join(basePath, filePath)) : undefined,
160
+ });
161
+ }
162
+ }
163
+ return {
164
+ npmSpecifiers,
165
+ npmSpecifiersWithExtraneousFiles: [...npmSpecifiersWithExtraneousFiles],
166
+ };
167
+ };
168
+ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, importMap, referenceTypes, }) => {
88
169
  // The directories that esbuild will use when resolving Node modules. We must
89
170
  // set these manually because esbuild will be operating from a temporary
90
171
  // directory that will not live inside the project root, so the normal
@@ -94,51 +175,25 @@ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, impo
94
175
  // project directory. If a custom directory has been specified, we use it.
95
176
  // Otherwise, create a random temporary directory.
96
177
  const temporaryDirectory = directory ? { path: directory } : await tmp.dir();
97
- // Do a first pass at bundling to gather a list of specifiers that should be
98
- // loaded as npm dependencies, because they either use the `npm:` prefix or
99
- // they are bare specifiers. We'll collect them in `specifiers`.
100
- try {
101
- const { errors, warnings } = await build({
102
- banner,
103
- bundle: true,
104
- entryPoints: functions,
105
- logLevel: 'silent',
106
- nodePaths,
107
- outdir: temporaryDirectory.path,
108
- platform: 'node',
109
- plugins: [getDependencyTrackerPlugin(specifiers, importMap.getContentsWithURLObjects(), pathToFileURL(basePath))],
110
- write: false,
111
- format: 'esm',
112
- });
113
- if (errors.length !== 0) {
114
- logger.system('ESBuild errored while tracking dependencies in edge function:', errors);
115
- }
116
- if (warnings.length !== 0) {
117
- logger.system('ESBuild warned while tracking dependencies in edge function:', warnings);
118
- }
119
- }
120
- catch (error) {
121
- logger.system('Could not track dependencies in edge function:', error);
122
- logger.user('An error occurred when trying to scan your edge functions for npm modules, which is an experimental feature. If you are loading npm modules, please share the link to this deploy in https://ntl.fyi/edge-functions-npm. If you are not loading npm modules, you can ignore this message.');
123
- }
178
+ const { npmSpecifiers, npmSpecifiersWithExtraneousFiles } = await getNPMSpecifiers(basePath, functions, importMap.getContentsWithURLObjects(), referenceTypes);
124
179
  // If we found no specifiers, there's nothing left to do here.
125
- if (specifiers.size === 0) {
180
+ if (Object.keys(npmSpecifiers).length === 0) {
126
181
  return;
127
182
  }
128
- // To bundle an entire module and all its dependencies, create a barrel file
183
+ // To bundle an entire module and all its dependencies, create a entrypoint file
129
184
  // where we re-export everything from that specifier. We do this for every
130
185
  // specifier, and each of these files will become entry points to esbuild.
131
- const ops = await Promise.all([...specifiers].map(async (specifier, index) => {
186
+ const ops = await Promise.all(npmSpecifiers.map(async ({ specifier, types }) => {
132
187
  const code = `import * as mod from "${specifier}"; export default mod.default; export * from "${specifier}";`;
133
- const filePath = path.join(temporaryDirectory.path, `barrel-${index}.js`);
188
+ const filePath = path.join(temporaryDirectory.path, `bundled-${slugifyPackageName(specifier)}.js`);
134
189
  await fs.writeFile(filePath, code);
135
- return { filePath, specifier };
190
+ return { filePath, specifier, types };
136
191
  }));
137
192
  const entryPoints = ops.map(({ filePath }) => filePath);
138
- // Bundle each of the barrel files we created. We'll end up with a compiled
139
- // version of each of the barrel files, plus any chunks of shared code
193
+ // Bundle each of the entrypoints we created. We'll end up with a compiled
194
+ // version of each, plus any chunks of shared code
140
195
  // between them (such that a common module isn't bundled twice).
141
- await build({
196
+ const { outputFiles } = await build({
142
197
  allowOverwrite: true,
143
198
  banner,
144
199
  bundle: true,
@@ -150,7 +205,17 @@ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, impo
150
205
  platform: 'node',
151
206
  splitting: true,
152
207
  target: 'es2020',
208
+ write: false,
153
209
  });
210
+ await Promise.all(outputFiles.map(async (file) => {
211
+ var _a;
212
+ const types = (_a = ops.find((op) => path.basename(file.path) === path.basename(op.filePath))) === null || _a === void 0 ? void 0 : _a.types;
213
+ let content = file.text;
214
+ if (types) {
215
+ content = `/// <reference types="${path.relative(file.path, types)}" />\n${content}`;
216
+ }
217
+ await fs.writeFile(file.path, content);
218
+ }));
154
219
  // Add all Node.js built-ins to the import map, so any unprefixed specifiers
155
220
  // (e.g. `process`) resolve to the prefixed versions (e.g. `node:prefix`),
156
221
  // which Deno can process.
@@ -189,5 +254,6 @@ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, impo
189
254
  cleanup,
190
255
  directory: temporaryDirectory.path,
191
256
  importMap: newImportMap,
257
+ npmSpecifiersWithExtraneousFiles,
192
258
  };
193
259
  };
@@ -39,6 +39,7 @@ export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, d
39
39
  features: Record<string, boolean>;
40
40
  functionsConfig: FunctionConfig[];
41
41
  graph: any;
42
+ npmSpecifiersWithExtraneousFiles: string[];
42
43
  success: boolean;
43
44
  }>>;
44
45
  export {};
@@ -23,6 +23,7 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
23
23
  });
24
24
  const features = {};
25
25
  const importMap = baseImportMap.clone();
26
+ const npmSpecifiersWithExtraneousFiles = [];
26
27
  if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_npm_modules) {
27
28
  const vendor = await vendorNPMSpecifiers({
28
29
  basePath,
@@ -30,10 +31,12 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
30
31
  functions: functions.map(({ path }) => path),
31
32
  importMap,
32
33
  logger,
34
+ referenceTypes: true,
33
35
  });
34
36
  if (vendor) {
35
37
  features.npmModules = true;
36
38
  importMap.add(vendor.importMap);
39
+ npmSpecifiersWithExtraneousFiles.push(...vendor.npmSpecifiersWithExtraneousFiles);
37
40
  }
38
41
  }
39
42
  try {
@@ -69,6 +72,7 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
69
72
  features,
70
73
  functionsConfig,
71
74
  graph,
75
+ npmSpecifiersWithExtraneousFiles,
72
76
  success,
73
77
  };
74
78
  };
@@ -1,7 +1,7 @@
1
+ import { readFile } from 'fs/promises';
1
2
  import { join } from 'path';
2
3
  import getPort from 'get-port';
3
4
  import fetch from 'node-fetch';
4
- import { tmpName } from 'tmp-promise';
5
5
  import { v4 as uuidv4 } from 'uuid';
6
6
  import { test, expect } from 'vitest';
7
7
  import { fixturesDir } from '../../test/util.js';
@@ -14,13 +14,16 @@ test('Starts a server and serves requests for edge functions', async () => {
14
14
  };
15
15
  const port = await getPort();
16
16
  const importMapPaths = [join(paths.internal, 'import_map.json'), join(paths.user, 'import-map.json')];
17
- const servePath = await tmpName();
17
+ const servePath = join(basePath, '.netlify', 'edge-functions-serve');
18
18
  const server = await serve({
19
19
  basePath,
20
20
  bootstrapURL: 'https://edge.netlify.com/bootstrap/index-combined.ts',
21
21
  importMapPaths,
22
22
  port,
23
23
  servePath,
24
+ featureFlags: {
25
+ edge_functions_npm_modules: true,
26
+ },
24
27
  });
25
28
  const functions = [
26
29
  {
@@ -39,12 +42,13 @@ test('Starts a server and serves requests for edge functions', async () => {
39
42
  const options = {
40
43
  getFunctionsConfig: true,
41
44
  };
42
- const { features, functionsConfig, graph, success } = await server(functions, {
45
+ const { features, functionsConfig, graph, success, npmSpecifiersWithExtraneousFiles } = await server(functions, {
43
46
  very_secret_secret: 'i love netlify',
44
47
  }, options);
45
- expect(features).toEqual({});
48
+ expect(features).toEqual({ npmModules: true });
46
49
  expect(success).toBe(true);
47
50
  expect(functionsConfig).toEqual([{ path: '/my-function' }, {}, { path: '/global-netlify' }]);
51
+ expect(npmSpecifiersWithExtraneousFiles).toEqual(['dictionary']);
48
52
  for (const key in functions) {
49
53
  const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(
50
54
  // @ts-expect-error TODO: Module graph is currently not typed
@@ -80,4 +84,8 @@ test('Starts a server and serves requests for edge functions', async () => {
80
84
  global: 'i love netlify',
81
85
  local: 'i love netlify',
82
86
  });
87
+ const idBarrelFile = await readFile(join(servePath, 'bundled-id.js'), 'utf-8');
88
+ expect(idBarrelFile).toContain(`/// <reference types="${join('..', '..', '..', 'node_modules', 'id', 'types.d.ts')}" />`);
89
+ const identidadeBarrelFile = await readFile(join(servePath, 'bundled-pt-committee__identidade.js'), 'utf-8');
90
+ expect(identidadeBarrelFile).toContain(`/// <reference types="${join('..', '..', '..', 'node_modules', '@types', 'pt-committee__identidade', 'index.d.ts')}" />`);
83
91
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "9.2.1",
3
+ "version": "9.4.0",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -74,6 +74,7 @@
74
74
  },
75
75
  "dependencies": {
76
76
  "@import-maps/resolve": "^1.0.1",
77
+ "@vercel/nft": "^0.24.3",
77
78
  "ajv": "^8.11.2",
78
79
  "ajv-errors": "^3.0.0",
79
80
  "better-ajv-errors": "^1.2.0",
@@ -82,6 +83,7 @@
82
83
  "esbuild": "0.19.4",
83
84
  "execa": "^6.0.0",
84
85
  "find-up": "^6.3.0",
86
+ "get-package-name": "^2.2.0",
85
87
  "get-port": "^6.1.2",
86
88
  "is-path-inside": "^4.0.0",
87
89
  "jsonc-parser": "^3.2.0",