@netlify/edge-bundler 9.5.0 → 10.1.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/README.md CHANGED
@@ -9,32 +9,42 @@ Intelligently prepare Netlify Edge Functions for deployment.
9
9
 
10
10
  1. Install this module as a dependency in your project
11
11
 
12
- ```
13
- npm install @netlify/edge-bundler --save
14
- ```
12
+ ```
13
+ npm install @netlify/edge-bundler --save
14
+ ```
15
15
 
16
16
  2. Import it and create a bundle from a directory of Edge Functions and a list of declarations.
17
17
 
18
- ```js
19
- import { bundle } from '@netlify/edge-bundler'
18
+ ```js
19
+ import { bundle } from '@netlify/edge-bundler'
20
20
 
21
- // List of directories to search for Edge Functions.
22
- const sourceDirectories = [
23
- "/repo/netlify/edge-functions",
24
- "/repo/.netlify/edge-functions"
25
- ]
21
+ // List of directories to search for Edge Functions.
22
+ const sourceDirectories = ['/repo/netlify/edge-functions', '/repo/.netlify/edge-functions']
26
23
 
27
- // Directory where bundle should be placed.
28
- const distDirectory = "/repo/.netlify/edge-functions-dist"
24
+ // Directory where bundle should be placed.
25
+ const distDirectory = '/repo/.netlify/edge-functions-dist'
29
26
 
30
- // List of Edge Functions declarations.
31
- const declarations = [
32
- {function: "user-1", path: "/blog/*"},
33
- {function: "internal-2", path: "/"}
34
- ]
27
+ // List of Edge Functions declarations.
28
+ const declarations = [
29
+ { function: 'user-1', path: '/blog/*' },
30
+ { function: 'internal-2', path: '/' },
31
+ ]
32
+
33
+ await bundle(sourceDirectories, distDirectory, declarations)
34
+ ```
35
+
36
+ ## Vendored modules
37
+
38
+ To avoid pulling in additional dependencies at runtime, this package vendors some Deno modules in the `deno/vendor`
39
+ directory.
40
+
41
+ You can recreate this directory by running `npm run vendor`.
42
+
43
+ > [!WARNING]
44
+ > At the time of writing, the underlying Deno CLI command doesn't correctly pull the WASM binary required by the ESZIP
45
+ > module. If you run the command to update the list of vendores modules, please ensure you're not deleting
46
+ > `eszip_wasm_bg.wasm`.
35
47
 
36
- await bundle(sourceDirectories, distDirectory, declarations)
37
- ```
38
48
  ## Contributors
39
49
 
40
50
  Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on how to set up and work on this repository. Thanks
@@ -0,0 +1,20 @@
1
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2
+
3
+ export enum MediaType {
4
+ JavaScript = "JavaScript",
5
+ Mjs = "Mjs",
6
+ Cjs = "Cjs",
7
+ Jsx = "Jsx",
8
+ TypeScript = "TypeScript",
9
+ Mts = "Mts",
10
+ Cts = "Cts",
11
+ Dts = "Dts",
12
+ Dmts = "Dmts",
13
+ Dcts = "Dcts",
14
+ Tsx = "Tsx",
15
+ Json = "Json",
16
+ Wasm = "Wasm",
17
+ TsBuildInfo = "TsBuildInfo",
18
+ SourceMap = "SourceMap",
19
+ Unknown = "Unknown",
20
+ }
@@ -0,0 +1,182 @@
1
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2
+
3
+ import type { MediaType } from "./media_type.ts";
4
+
5
+ /** Additional meta data that is used to enrich the output of the module
6
+ * graph. */
7
+ export interface CacheInfo {
8
+ /** The string path to where the local version of the content is located. For
9
+ * non `file:` URLs, this is the location of the cached content, otherwise it
10
+ * is the absolute path to the local file. */
11
+ local?: string;
12
+ /** The string path to where a transpiled version of the source content is
13
+ * located, if any. */
14
+ emit?: string;
15
+ /** The string path to where an external source map of the transpiled source
16
+ * content is located, if any. */
17
+ map?: string;
18
+ }
19
+
20
+ export interface TypesDependency {
21
+ /** The string URL to the type information for the module. */
22
+ types: string;
23
+ /** An optional range which indicates the source of the dependency. */
24
+ source?: Range;
25
+ }
26
+
27
+ export interface LoadResponseModule {
28
+ /** A module with code has been loaded. */
29
+ kind: "module";
30
+ /** The string URL of the resource. If there were redirects, the final
31
+ * specifier should be set here, otherwise the requested specifier. */
32
+ specifier: string;
33
+ /** For remote resources, a record of headers should be set, where the key's
34
+ * have been normalized to be lower case values. */
35
+ headers?: Record<string, string>;
36
+ /** The string value of the loaded resources. */
37
+ content: string;
38
+ }
39
+
40
+ export interface LoadResponseExternal {
41
+ /** The loaded module is either _external_ or _built-in_ to the runtime. */
42
+ kind: "external";
43
+ /** The strung URL of the resource. If there were redirects, the final
44
+ * specifier should be set here, otherwise the requested specifier. */
45
+ specifier: string;
46
+ }
47
+
48
+ export type LoadResponse = LoadResponseModule | LoadResponseExternal;
49
+
50
+ export interface PositionJson {
51
+ /** The line number of a position within a source file. The number is a zero
52
+ * based index. */
53
+ line: number;
54
+ /** The character number of a position within a source file. The number is a
55
+ * zero based index. */
56
+ character: number;
57
+ }
58
+
59
+ export interface Range {
60
+ /** A string URL representing a source of a dependency. */
61
+ specifier: string;
62
+ /** The start location of a range of text in a source file. */
63
+ start?: PositionJson;
64
+ /** The end location of a range of text in a source file. */
65
+ end?: PositionJson;
66
+ }
67
+
68
+ export interface RangeJson {
69
+ /** The start location of a range of text in a source file. */
70
+ start: PositionJson;
71
+ /** The end location of a range of text in a source file. */
72
+ end: PositionJson;
73
+ }
74
+
75
+ export interface ResolvedDependency {
76
+ /** The fully resolved string URL of the dependency, which should be
77
+ * resolvable in the module graph. If there was an error, `error` will be set
78
+ * and this will be undefined. */
79
+ specifier?: string;
80
+ /** Any error encountered when trying to resolved the specifier. If this is
81
+ * defined, `specifier` will be undefined. */
82
+ error?: string;
83
+ /** The range within the source code where the specifier was identified. */
84
+ span: RangeJson;
85
+ }
86
+
87
+ export interface TypesDependencyJson {
88
+ /** The string specifier that was used for the dependency. */
89
+ specifier: string;
90
+ /** An object pointing to the resolved dependency. */
91
+ dependency: ResolvedDependency;
92
+ }
93
+
94
+ /** The kind of module.
95
+ *
96
+ * For asserted modules, the value of the `asserted` property is set to the
97
+ * `type` value of the import attribute.
98
+ *
99
+ * Dependency analysis is not performed for asserted or Script modules
100
+ * currently. Synthetic modules were injected into the graph with their own
101
+ * dependencies provided. */
102
+ export type ModuleKind =
103
+ | "asserted"
104
+ | "esm"
105
+ | "npm"
106
+ | "external";
107
+
108
+ export interface DependencyJson {
109
+ /** The string specifier that was used for the dependency. */
110
+ specifier: string;
111
+ /** An object pointing to the resolved _code_ dependency. */
112
+ code?: ResolvedDependency;
113
+ /** An object pointing to the resolved _type_ dependency of a module. This is
114
+ * populated when the `@deno-types` directive was used to supply a type
115
+ * definition file for a dependency. */
116
+ type?: ResolvedDependency;
117
+ /** A flag indicating if the dependency was dynamic. (e.g.
118
+ * `await import("mod.ts")`) */
119
+ isDynamic?: true;
120
+ assertionType?: string;
121
+ }
122
+
123
+ // todo(dsherret): split this up into separate types based on the "kind"
124
+
125
+ export interface ModuleJson extends CacheInfo {
126
+ /** The string URL of the module. */
127
+ specifier: string;
128
+ /** Any error encountered when attempting to load the module. */
129
+ error?: string;
130
+ /** The module kind that was determined when the module was resolved. This is
131
+ * used by loaders to indicate how a module needs to be loaded at runtime. */
132
+ kind?: ModuleKind;
133
+ /** An array of dependencies that were identified in the module. */
134
+ dependencies?: DependencyJson[];
135
+ /** If the module had a types dependency, the information about that
136
+ * dependency. */
137
+ typesDependency?: TypesDependencyJson;
138
+ /** The resolved media type of the module, which determines how Deno will
139
+ * handle the module. */
140
+ mediaType?: MediaType;
141
+ /** The size of the source content of the module in bytes. */
142
+ size?: number;
143
+ }
144
+
145
+ export interface GraphImportJson {
146
+ /** The referrer (URL string) that was used as a base when resolving the
147
+ * imports added to the graph. */
148
+ referrer: string;
149
+ /** An array of resolved dependencies which were imported using the
150
+ * referrer. */
151
+ dependencies?: DependencyJson[];
152
+ }
153
+
154
+ /** The plain-object representation of a module graph that is suitable for
155
+ * serialization to JSON. */
156
+ export interface ModuleGraphJson {
157
+ /** The module specifiers (URL string) of the _roots_ of the module graph of
158
+ * which the module graph was built for. */
159
+ roots: string[];
160
+ /** An array of modules that are part of the module graph. */
161
+ modules: ModuleJson[];
162
+ /** External imports that were added to the graph when it was being built.
163
+ * The key is the referrer which was used as a base to resolve the
164
+ * dependency. The value is the resolved dependency. */
165
+ imports?: GraphImportJson[];
166
+ /** A record/map of any redirects encountered when resolving modules. The
167
+ * key was the requested module specifier and the value is the redirected
168
+ * module specifier. */
169
+ redirects: Record<string, string>;
170
+ }
171
+
172
+ export interface Dependency {
173
+ /** An object pointing to the resolved _code_ dependency. */
174
+ code?: ResolvedDependency;
175
+ /** An object pointing to the resolved _type_ dependency of a module. This is
176
+ * populated when the `@deno-types` directive was used to supply a type
177
+ * definition file for a dependency. */
178
+ type?: ResolvedDependency;
179
+ /** A flag indicating if the dependency was dynamic. (e.g.
180
+ * `await import("mod.ts")`) */
181
+ isDynamic?: true;
182
+ }
@@ -0,0 +1,18 @@
1
+ export declare enum MediaType {
2
+ JavaScript = "JavaScript",
3
+ Mjs = "Mjs",
4
+ Cjs = "Cjs",
5
+ Jsx = "Jsx",
6
+ TypeScript = "TypeScript",
7
+ Mts = "Mts",
8
+ Cts = "Cts",
9
+ Dts = "Dts",
10
+ Dmts = "Dmts",
11
+ Dcts = "Dcts",
12
+ Tsx = "Tsx",
13
+ Json = "Json",
14
+ Wasm = "Wasm",
15
+ TsBuildInfo = "TsBuildInfo",
16
+ SourceMap = "SourceMap",
17
+ Unknown = "Unknown"
18
+ }
@@ -0,0 +1,20 @@
1
+ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
2
+ export var MediaType;
3
+ (function (MediaType) {
4
+ MediaType["JavaScript"] = "JavaScript";
5
+ MediaType["Mjs"] = "Mjs";
6
+ MediaType["Cjs"] = "Cjs";
7
+ MediaType["Jsx"] = "Jsx";
8
+ MediaType["TypeScript"] = "TypeScript";
9
+ MediaType["Mts"] = "Mts";
10
+ MediaType["Cts"] = "Cts";
11
+ MediaType["Dts"] = "Dts";
12
+ MediaType["Dmts"] = "Dmts";
13
+ MediaType["Dcts"] = "Dcts";
14
+ MediaType["Tsx"] = "Tsx";
15
+ MediaType["Json"] = "Json";
16
+ MediaType["Wasm"] = "Wasm";
17
+ MediaType["TsBuildInfo"] = "TsBuildInfo";
18
+ MediaType["SourceMap"] = "SourceMap";
19
+ MediaType["Unknown"] = "Unknown";
20
+ })(MediaType || (MediaType = {}));
@@ -15,11 +15,12 @@ export interface BundleOptions {
15
15
  internalSrcFolder?: string;
16
16
  onAfterDownload?: OnAfterDownloadHook;
17
17
  onBeforeDownload?: OnBeforeDownloadHook;
18
+ rootPath?: string;
18
19
  systemLogger?: LogFunction;
19
20
  userLogger?: LogFunction;
20
21
  vendorDirectory?: string;
21
22
  }
22
- export declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, internalSrcFolder, onAfterDownload, onBeforeDownload, userLogger, systemLogger, vendorDirectory, }?: BundleOptions) => Promise<{
23
+ export declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, internalSrcFolder, onAfterDownload, onBeforeDownload, rootPath, userLogger, systemLogger, vendorDirectory, }?: BundleOptions) => Promise<{
23
24
  functions: EdgeFunction[];
24
25
  manifest: import("./manifest.js").Manifest;
25
26
  }>;
@@ -15,7 +15,7 @@ import { getLogger } from './logger.js';
15
15
  import { writeManifest } from './manifest.js';
16
16
  import { vendorNPMSpecifiers } from './npm_dependencies.js';
17
17
  import { ensureLatestTypes } from './types.js';
18
- export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], internalSrcFolder, onAfterDownload, onBeforeDownload, userLogger, systemLogger, vendorDirectory, } = {}) => {
18
+ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], internalSrcFolder, onAfterDownload, onBeforeDownload, rootPath, userLogger, systemLogger, vendorDirectory, } = {}) => {
19
19
  const logger = getLogger(systemLogger, userLogger, debug);
20
20
  const featureFlags = getFlags(inputFeatureFlags);
21
21
  const options = {
@@ -53,6 +53,7 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
53
53
  functions,
54
54
  importMap,
55
55
  logger,
56
+ rootPath: rootPath !== null && rootPath !== void 0 ? rootPath : basePath,
56
57
  vendorDirectory,
57
58
  });
58
59
  if (vendor) {
@@ -147,7 +148,7 @@ const createFunctionConfig = ({ internalFunctionsWithConfig, declarations }) =>
147
148
  [functionName]: addGeneratorFallback(mergedConfigFields),
148
149
  };
149
150
  }, {});
150
- const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logger, vendorDirectory, }) => {
151
+ const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logger, rootPath, vendorDirectory, }) => {
151
152
  try {
152
153
  return await vendorNPMSpecifiers({
153
154
  basePath,
@@ -156,6 +157,7 @@ const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logge
156
157
  importMap,
157
158
  logger,
158
159
  referenceTypes: false,
160
+ rootPath,
159
161
  });
160
162
  }
161
163
  catch (error) {
@@ -380,6 +380,34 @@ test('Loads npm modules from bare specifiers', async () => {
380
380
  await cleanup();
381
381
  await rm(vendorDirectory.path, { force: true, recursive: true });
382
382
  });
383
+ test('Loads npm modules in a monorepo setup', async () => {
384
+ const systemLogger = vi.fn();
385
+ const { basePath: rootPath, cleanup, distPath } = await useFixture('monorepo_npm_module');
386
+ const basePath = join(rootPath, 'packages', 'frontend');
387
+ const sourceDirectory = join(basePath, 'functions');
388
+ const declarations = [
389
+ {
390
+ function: 'func1',
391
+ path: '/func1',
392
+ },
393
+ ];
394
+ const vendorDirectory = await tmp.dir();
395
+ await bundle([sourceDirectory], distPath, declarations, {
396
+ basePath,
397
+ importMapPaths: [join(basePath, 'import_map.json')],
398
+ rootPath,
399
+ vendorDirectory: vendorDirectory.path,
400
+ systemLogger,
401
+ });
402
+ expect(systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:')).toBeUndefined();
403
+ const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8');
404
+ const manifest = JSON.parse(manifestFile);
405
+ const bundlePath = join(distPath, manifest.bundles[0].asset);
406
+ const { func1 } = await runESZIP(bundlePath, vendorDirectory.path);
407
+ expect(func1).toBe(`<parent-1><child-1>JavaScript</child-1></parent-1>, <parent-2><child-2><grandchild-1>APIs<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-2>, <parent-3><child-2><grandchild-1>Markup<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-3>`);
408
+ await cleanup();
409
+ await rm(vendorDirectory.path, { force: true, recursive: true });
410
+ });
383
411
  test('Loads JSON modules', async () => {
384
412
  const { basePath, cleanup, distPath } = await useFixture('imports_json');
385
413
  const sourceDirectory = join(basePath, 'functions');
@@ -5,5 +5,6 @@ export { Declaration, mergeDeclarations } from './declaration.js';
5
5
  export type { EdgeFunction } from './edge_function.js';
6
6
  export { findFunctions as find } from './finder.js';
7
7
  export { generateManifest } from './manifest.js';
8
- export { serve } from './server/server.js';
8
+ export type { EdgeFunctionConfig, Manifest } from './manifest.js';
9
+ export { ModuleGraph, serve } from './server/server.js';
9
10
  export { validateManifest, ManifestValidationError } from './validation/manifest/index.js';
@@ -11,7 +11,7 @@ interface Route {
11
11
  path?: string;
12
12
  methods?: string[];
13
13
  }
14
- interface EdgeFunctionConfig {
14
+ export interface EdgeFunctionConfig {
15
15
  excluded_patterns: string[];
16
16
  on_error?: string;
17
17
  generator?: string;
@@ -42,7 +42,11 @@ interface GenerateManifestOptions {
42
42
  layers?: Layer[];
43
43
  userFunctionConfig?: Record<string, FunctionConfig>;
44
44
  }
45
- declare const generateManifest: ({ bundles, declarations, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest;
45
+ declare const generateManifest: ({ bundles, declarations, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => {
46
+ declarationsWithoutFunction: string[];
47
+ manifest: Manifest;
48
+ unroutedFunctions: string[];
49
+ };
46
50
  interface WriteManifestOptions extends GenerateManifestOptions {
47
51
  distDirectory: string;
48
52
  }
@@ -29,7 +29,7 @@ const sanitizeEdgeFunctionConfig = (config) => {
29
29
  const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => {
30
30
  if (excludedPath) {
31
31
  const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
32
- const excludedPatterns = paths.map((path) => pathToRegularExpression(path)).map(serializePattern);
32
+ const excludedPatterns = paths.map(pathToRegularExpression).filter(nonNullable).map(serializePattern);
33
33
  manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
34
34
  }
35
35
  };
@@ -50,6 +50,8 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
50
50
  const preCacheRoutes = [];
51
51
  const postCacheRoutes = [];
52
52
  const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
53
+ const routedFunctions = new Set();
54
+ const declarationsWithoutFunction = new Set();
53
55
  for (const [name, { excludedPath, onError }] of Object.entries(userFunctionConfig)) {
54
56
  // If the config block is for a function that is not defined, discard it.
55
57
  if (manifestFunctionConfig[name] === undefined) {
@@ -69,9 +71,16 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
69
71
  declarations.forEach((declaration) => {
70
72
  const func = functions.find(({ name }) => declaration.function === name);
71
73
  if (func === undefined) {
74
+ declarationsWithoutFunction.add(declaration.function);
72
75
  return;
73
76
  }
74
77
  const pattern = getRegularExpression(declaration);
78
+ // If there is no `pattern`, the declaration will never be triggered, so we
79
+ // can discard it.
80
+ if (!pattern) {
81
+ return;
82
+ }
83
+ routedFunctions.add(declaration.function);
75
84
  const excludedPattern = getExcludedRegularExpressions(declaration);
76
85
  const route = {
77
86
  function: func.name,
@@ -104,9 +113,13 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
104
113
  import_map: importMap,
105
114
  function_config: sanitizeEdgeFunctionConfig(manifestFunctionConfig),
106
115
  };
107
- return manifest;
116
+ const unroutedFunctions = functions.filter(({ name }) => !routedFunctions.has(name)).map(({ name }) => name);
117
+ return { declarationsWithoutFunction: [...declarationsWithoutFunction], manifest, unroutedFunctions };
108
118
  };
109
119
  const pathToRegularExpression = (path) => {
120
+ if (!path) {
121
+ return null;
122
+ }
110
123
  try {
111
124
  const pattern = new ExtendedURLPattern({ pathname: path });
112
125
  // Removing the `^` and `$` delimiters because we'll need to modify what's
@@ -149,12 +162,12 @@ const getExcludedRegularExpressions = (declaration) => {
149
162
  }
150
163
  if ('path' in declaration && declaration.excludedPath) {
151
164
  const paths = Array.isArray(declaration.excludedPath) ? declaration.excludedPath : [declaration.excludedPath];
152
- return paths.map((path) => pathToRegularExpression(path));
165
+ return paths.map(pathToRegularExpression).filter(nonNullable);
153
166
  }
154
167
  return [];
155
168
  };
156
169
  const writeManifest = async ({ distDirectory, ...rest }) => {
157
- const manifest = generateManifest(rest);
170
+ const { manifest } = generateManifest(rest);
158
171
  const manifestPath = join(distDirectory, 'manifest.json');
159
172
  await fs.writeFile(manifestPath, JSON.stringify(manifest));
160
173
  return manifest;
@@ -17,7 +17,7 @@ test('Generates a manifest with different bundles', () => {
17
17
  };
18
18
  const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
19
19
  const declarations = [{ function: 'func-1', path: '/f1' }];
20
- const manifest = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
20
+ const { manifest } = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
21
21
  const expectedBundles = [
22
22
  { asset: bundle1.hash + bundle1.extension, format: bundle1.format },
23
23
  { asset: bundle2.hash + bundle2.extension, format: bundle2.format },
@@ -35,7 +35,7 @@ test('Generates a manifest with display names', () => {
35
35
  name: 'Display Name',
36
36
  },
37
37
  };
38
- const manifest = generateManifest({
38
+ const { manifest } = generateManifest({
39
39
  bundles: [],
40
40
  declarations,
41
41
  functions,
@@ -56,7 +56,7 @@ test('Generates a manifest with a generator field', () => {
56
56
  generator: '@netlify/fake-plugin@1.0.0',
57
57
  },
58
58
  };
59
- const manifest = generateManifest({
59
+ const { manifest } = generateManifest({
60
60
  bundles: [],
61
61
  declarations,
62
62
  functions,
@@ -79,7 +79,7 @@ test('Generates a manifest with excluded paths and patterns', () => {
79
79
  { function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excludedPattern: ['^/f2/exclude$', '^/f2/exclude-as-well$'] },
80
80
  { function: 'func-3', path: '/*', excludedPath: '/**/*.html' },
81
81
  ];
82
- const manifest = generateManifest({
82
+ const { manifest } = generateManifest({
83
83
  bundles: [],
84
84
  declarations,
85
85
  functions,
@@ -111,7 +111,7 @@ test('TOML-defined paths can be combined with ISC-defined excluded paths', () =>
111
111
  const userFunctionConfig = {
112
112
  'func-1': { excludedPath: '/f1/exclude' },
113
113
  };
114
- const manifest = generateManifest({
114
+ const { manifest } = generateManifest({
115
115
  bundles: [],
116
116
  declarations,
117
117
  functions,
@@ -153,7 +153,7 @@ test('Filters out internal in-source configurations in user created functions',
153
153
  generator: 'internal-generator',
154
154
  },
155
155
  };
156
- const manifest = generateManifest({
156
+ const { manifest } = generateManifest({
157
157
  bundles: [],
158
158
  declarations,
159
159
  functions,
@@ -186,7 +186,7 @@ test('excludedPath from ISC goes into function_config, TOML goes into routes', (
186
186
  },
187
187
  };
188
188
  const internalFunctionConfig = {};
189
- const manifest = generateManifest({
189
+ const { manifest } = generateManifest({
190
190
  bundles: [],
191
191
  declarations,
192
192
  functions,
@@ -224,7 +224,7 @@ test('URLPattern named groups are supported', () => {
224
224
  const declarations = [{ function: 'customisation', path: '/products/:productId' }];
225
225
  const userFunctionConfig = {};
226
226
  const internalFunctionConfig = {};
227
- const manifest = generateManifest({
227
+ const { manifest } = generateManifest({
228
228
  bundles: [],
229
229
  declarations,
230
230
  functions,
@@ -269,7 +269,7 @@ test('Includes failure modes in manifest', () => {
269
269
  onError: '/custom-error',
270
270
  },
271
271
  };
272
- const manifest = generateManifest({ bundles: [], declarations, functions, userFunctionConfig });
272
+ const { manifest } = generateManifest({ bundles: [], declarations, functions, userFunctionConfig });
273
273
  expect(manifest.function_config).toEqual({
274
274
  'func-1': { on_error: '/custom-error' },
275
275
  });
@@ -285,7 +285,7 @@ test('Excludes functions for which there are function files but no matching conf
285
285
  { name: 'func-2', path: '/path/to/func-2.ts' },
286
286
  ];
287
287
  const declarations = [{ function: 'func-1', path: '/f1' }];
288
- const manifest = generateManifest({ bundles: [bundle1], declarations, functions });
288
+ const { manifest } = generateManifest({ bundles: [bundle1], declarations, functions });
289
289
  const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
290
290
  expect(manifest.routes).toEqual(expectedRoutes);
291
291
  });
@@ -300,14 +300,14 @@ test('Excludes functions for which there are config declarations but no matching
300
300
  { function: 'func-1', path: '/f1' },
301
301
  { function: 'func-2', path: '/f2' },
302
302
  ];
303
- const manifest = generateManifest({ bundles: [bundle1], declarations, functions });
303
+ const { manifest } = generateManifest({ bundles: [bundle1], declarations, functions });
304
304
  const expectedRoutes = [{ function: 'func-2', pattern: '^/f2/?$', excluded_patterns: [], path: '/f2' }];
305
305
  expect(manifest.routes).toEqual(expectedRoutes);
306
306
  });
307
307
  test('Generates a manifest without bundles', () => {
308
308
  const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
309
309
  const declarations = [{ function: 'func-1', path: '/f1' }];
310
- const manifest = generateManifest({ bundles: [], declarations, functions });
310
+ const { manifest } = generateManifest({ bundles: [], declarations, functions });
311
311
  const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
312
312
  expect(manifest.bundles).toEqual([]);
313
313
  expect(manifest.routes).toEqual(expectedRoutes);
@@ -334,7 +334,7 @@ test('Generates a manifest with pre and post-cache routes', () => {
334
334
  { function: 'func-2', cache: 'not_a_supported_value', path: '/f2' },
335
335
  { function: 'func-3', cache: 'manual', path: '/f3' },
336
336
  ];
337
- const manifest = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
337
+ const { manifest } = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
338
338
  const expectedBundles = [
339
339
  { asset: bundle1.hash + bundle1.extension, format: bundle1.format },
340
340
  { asset: bundle2.hash + bundle2.extension, format: bundle2.format },
@@ -370,12 +370,12 @@ test('Generates a manifest with layers', () => {
370
370
  flag: 'edge_functions_onion_layer',
371
371
  },
372
372
  ];
373
- const manifest1 = generateManifest({
373
+ const { manifest: manifest1 } = generateManifest({
374
374
  bundles: [],
375
375
  declarations,
376
376
  functions,
377
377
  });
378
- const manifest2 = generateManifest({
378
+ const { manifest: manifest2 } = generateManifest({
379
379
  bundles: [],
380
380
  declarations,
381
381
  functions,
@@ -398,6 +398,34 @@ test('Throws an error if the regular expression contains a negative lookahead',
398
398
  test('Converts named capture groups to unnamed capture groups in regular expressions', () => {
399
399
  const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
400
400
  const declarations = [{ function: 'func-1', pattern: '^/(?<name>\\w+)$' }];
401
- const manifest = generateManifest({ bundles: [], declarations, functions });
401
+ const { manifest } = generateManifest({ bundles: [], declarations, functions });
402
402
  expect(manifest.routes).toEqual([{ function: 'func-1', pattern: '^/(\\w+)$', excluded_patterns: [] }]);
403
403
  });
404
+ test('Returns functions without a declaration and unrouted functions', () => {
405
+ const bundle = {
406
+ extension: '.ext1',
407
+ format: BundleFormat.ESZIP2,
408
+ hash: '123456',
409
+ };
410
+ const functions = [
411
+ { name: 'func-1', path: '/path/to/func-1.ts' },
412
+ { name: 'func-2', path: '/path/to/func-2.ts' },
413
+ { name: 'func-4', path: '/path/to/func-4.ts' },
414
+ ];
415
+ const declarations = [
416
+ { function: 'func-1', path: '/f1' },
417
+ { function: 'func-3', path: '/f3' },
418
+ // @ts-expect-error Error is expected due to neither `path` or `pattern`
419
+ // being present.
420
+ { function: 'func-4', name: 'Some name' },
421
+ ];
422
+ const { declarationsWithoutFunction, manifest, unroutedFunctions } = generateManifest({
423
+ bundles: [bundle],
424
+ declarations,
425
+ functions,
426
+ });
427
+ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
428
+ expect(manifest.routes).toEqual(expectedRoutes);
429
+ expect(declarationsWithoutFunction).toEqual(['func-3']);
430
+ expect(unroutedFunctions).toEqual(['func-2', 'func-4']);
431
+ });
@@ -8,8 +8,9 @@ interface VendorNPMSpecifiersOptions {
8
8
  importMap: ImportMap;
9
9
  logger: Logger;
10
10
  referenceTypes: boolean;
11
+ rootPath?: string;
11
12
  }
12
- export declare const vendorNPMSpecifiers: ({ basePath, directory, functions, importMap, referenceTypes, }: VendorNPMSpecifiersOptions) => Promise<{
13
+ export declare const vendorNPMSpecifiers: ({ basePath, directory, functions, importMap, referenceTypes, rootPath, }: VendorNPMSpecifiersOptions) => Promise<{
13
14
  cleanup: () => Promise<void>;
14
15
  directory: string;
15
16
  importMap: {
@@ -8,6 +8,7 @@ import { build } from 'esbuild';
8
8
  import { findUp } from 'find-up';
9
9
  import getPackageName from 'get-package-name';
10
10
  import tmp from 'tmp-promise';
11
+ import { pathsBetween } from './utils/fs.js';
11
12
  const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.cts', '.mts']);
12
13
  const slugifyPackageName = (specifier) => {
13
14
  if (!specifier.startsWith('@'))
@@ -76,16 +77,12 @@ const banner = {
76
77
  /**
77
78
  * Parses a set of functions and returns a list of specifiers that correspond
78
79
  * 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
80
  */
85
- const getNPMSpecifiers = async (basePath, functions, importMap, referenceTypes) => {
81
+ const getNPMSpecifiers = async ({ basePath, functions, importMap, referenceTypes, rootPath, }) => {
86
82
  const baseURL = pathToFileURL(basePath);
87
83
  const { reasons } = await nodeFileTrace(functions, {
88
- base: basePath,
84
+ base: rootPath,
85
+ processCwd: basePath,
89
86
  readFile: async (filePath) => {
90
87
  // If this is a TypeScript file, we need to compile in before we can
91
88
  // parse it.
@@ -165,17 +162,23 @@ const getNPMSpecifiers = async (basePath, functions, importMap, referenceTypes)
165
162
  npmSpecifiersWithExtraneousFiles: [...npmSpecifiersWithExtraneousFiles],
166
163
  };
167
164
  };
168
- export const vendorNPMSpecifiers = async ({ basePath, directory, functions, importMap, referenceTypes, }) => {
165
+ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, importMap, referenceTypes, rootPath = basePath, }) => {
169
166
  // The directories that esbuild will use when resolving Node modules. We must
170
167
  // set these manually because esbuild will be operating from a temporary
171
168
  // directory that will not live inside the project root, so the normal
172
169
  // resolution logic won't work.
173
- const nodePaths = [path.join(basePath, 'node_modules')];
170
+ const nodePaths = pathsBetween(basePath, rootPath).map((directory) => path.join(directory, 'node_modules'));
174
171
  // We need to create some files on disk, which we don't want to write to the
175
172
  // project directory. If a custom directory has been specified, we use it.
176
173
  // Otherwise, create a random temporary directory.
177
174
  const temporaryDirectory = directory ? { path: directory } : await tmp.dir();
178
- const { npmSpecifiers, npmSpecifiersWithExtraneousFiles } = await getNPMSpecifiers(basePath, functions, importMap.getContentsWithURLObjects(), referenceTypes);
175
+ const { npmSpecifiers, npmSpecifiersWithExtraneousFiles } = await getNPMSpecifiers({
176
+ basePath,
177
+ functions,
178
+ importMap: importMap.getContentsWithURLObjects(),
179
+ referenceTypes,
180
+ rootPath,
181
+ });
179
182
  // If we found no specifiers, there's nothing left to do here.
180
183
  if (Object.keys(npmSpecifiers).length === 0) {
181
184
  return;
@@ -3,12 +3,14 @@
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
5
  /// <reference types="node" />
6
+ import type { ModuleGraphJson } from '../../deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.js';
6
7
  import { OnAfterDownloadHook, OnBeforeDownloadHook } from '../bridge.js';
7
8
  import { FunctionConfig } from '../config.js';
8
9
  import type { EdgeFunction } from '../edge_function.js';
9
10
  import type { FeatureFlags } from '../feature_flags.js';
10
11
  import { LogFunction } from '../logger.js';
11
12
  export type FormatFunction = (name: string) => string;
13
+ export type ModuleGraph = ModuleGraphJson;
12
14
  interface StartServerOptions {
13
15
  getFunctionsConfig?: boolean;
14
16
  }
@@ -31,14 +33,15 @@ interface ServeOptions {
31
33
  formatExportTypeError?: FormatFunction;
32
34
  formatImportError?: FormatFunction;
33
35
  port: number;
36
+ rootPath?: string;
34
37
  servePath: string;
35
38
  userLogger?: LogFunction;
36
39
  systemLogger?: LogFunction;
37
40
  }
38
- export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, importMapPaths, onAfterDownload, onBeforeDownload, port, servePath, userLogger, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
41
+ export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, importMapPaths, onAfterDownload, onBeforeDownload, port, rootPath, servePath, userLogger, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
39
42
  features: Record<string, boolean>;
40
43
  functionsConfig: FunctionConfig[];
41
- graph: any;
44
+ graph: ModuleGraphJson;
42
45
  npmSpecifiersWithExtraneousFiles: string[];
43
46
  success: boolean;
44
47
  }>>;
@@ -18,13 +18,17 @@ const cleanDirectory = async (directory, except) => {
18
18
  const toBeDeleted = files.filter((file) => !except.includes(join(directory, file)));
19
19
  await Promise.all(toBeDeleted.map((file) => unlink(join(directory, file))));
20
20
  };
21
- const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, importMap: baseImportMap, logger, port, }) => {
21
+ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, importMap: baseImportMap, logger, port, rootPath, }) => {
22
22
  const processRef = {};
23
23
  const startServer = async (functions, env = {}, options = {}) => {
24
24
  if ((processRef === null || processRef === void 0 ? void 0 : processRef.ps) !== undefined) {
25
25
  await killProcess(processRef.ps);
26
26
  }
27
- let graph;
27
+ let graph = {
28
+ roots: [],
29
+ modules: [],
30
+ redirects: {},
31
+ };
28
32
  const stage2Path = await generateStage2({
29
33
  bootstrapURL,
30
34
  distDirectory,
@@ -45,6 +49,7 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
45
49
  importMap,
46
50
  logger,
47
51
  referenceTypes: true,
52
+ rootPath,
48
53
  });
49
54
  if (vendor) {
50
55
  features.npmModules = true;
@@ -92,7 +97,85 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
92
97
  };
93
98
  return startServer;
94
99
  };
95
- export const serve = async ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, importMapPaths = [], onAfterDownload, onBeforeDownload, port, servePath, userLogger, systemLogger, }) => {
100
+ export const serve = async ({
101
+ /**
102
+ * Path that is common to all functions. Works as the root directory in the
103
+ * generated bundle.
104
+ */
105
+ basePath,
106
+ /**
107
+ * URL of the bootstrap layer to use.
108
+ */
109
+ bootstrapURL,
110
+ /**
111
+ * Path to an SSL certificate to run the Deno server with.
112
+ */
113
+ certificatePath,
114
+ /**
115
+ * Whether to print verbose information about the server process.
116
+ */
117
+ debug,
118
+ /**
119
+ * Path of an import map file to be generated using the built-in specifiers
120
+ * and any npm modules found during the bundling process.
121
+ */
122
+ distImportMapPath,
123
+ /**
124
+ * Debug settings to use with Deno's `--inspect` and `--inspect-brk` flags.
125
+ */
126
+ inspectSettings,
127
+ /**
128
+ * Map of feature flags.
129
+ */
130
+ featureFlags,
131
+ /**
132
+ * Callback function to be triggered whenever a function has a default export
133
+ * with the wrong type.
134
+ */
135
+ formatExportTypeError,
136
+ /**
137
+ * Callback function to be triggered whenever an error occurs while importing
138
+ * a function.
139
+ */
140
+ formatImportError,
141
+ /**
142
+ * Paths to any additional import map files.
143
+ */
144
+ importMapPaths = [],
145
+ /**
146
+ * Callback function to be triggered after the Deno CLI has been downloaded.
147
+ */
148
+ onAfterDownload,
149
+ /**
150
+ * Callback function to be triggered before we attempt to download the Deno
151
+ * CLI.
152
+ */
153
+ onBeforeDownload,
154
+ /**
155
+ * Port where the server should listen on.
156
+ */
157
+ port,
158
+ /**
159
+ * Root path of the project. Defines a boundary outside of which files or npm
160
+ * modules cannot be included from. This is usually the same as `basePath`,
161
+ * with monorepos being the main exception, where `basePath` maps to the
162
+ * package path and `rootPath` is the repository root.
163
+ */
164
+ rootPath,
165
+ /**
166
+ * Path to write ephemeral files that need to be generated for the server to
167
+ * operate.
168
+ */
169
+ servePath,
170
+ /**
171
+ * Custom logging function to be used for user-facing messages. Defaults to
172
+ * `console.log`.
173
+ */
174
+ userLogger,
175
+ /**
176
+ * Custom logging function to be used for system-level messages.
177
+ */
178
+ systemLogger, }) => {
96
179
  const logger = getLogger(systemLogger, userLogger, debug);
97
180
  const deno = new DenoBridge({
98
181
  debug,
@@ -137,6 +220,7 @@ export const serve = async ({ basePath, bootstrapURL, certificatePath, debug, di
137
220
  importMap,
138
221
  logger,
139
222
  port,
223
+ rootPath,
140
224
  });
141
225
  return server;
142
226
  };
@@ -1,5 +1,6 @@
1
1
  import { readFile } from 'fs/promises';
2
2
  import { join } from 'path';
3
+ import process from 'process';
3
4
  import getPort from 'get-port';
4
5
  import fetch from 'node-fetch';
5
6
  import { v4 as uuidv4 } from 'uuid';
@@ -47,9 +48,7 @@ test('Starts a server and serves requests for edge functions', async () => {
47
48
  expect(functionsConfig).toEqual([{ path: '/my-function' }, {}, { path: '/global-netlify' }]);
48
49
  expect(npmSpecifiersWithExtraneousFiles).toEqual(['dictionary']);
49
50
  for (const key in functions) {
50
- const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(
51
- // @ts-expect-error TODO: Module graph is currently not typed
52
- ({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path);
51
+ const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path);
53
52
  expect(graphEntry).toBe(true);
54
53
  }
55
54
  const response1 = await fetch(`http://0.0.0.0:${port}/foo`, {
@@ -86,3 +85,50 @@ test('Starts a server and serves requests for edge functions', async () => {
86
85
  const identidadeBarrelFile = await readFile(join(servePath, 'bundled-pt-committee__identidade.js'), 'utf-8');
87
86
  expect(identidadeBarrelFile).toContain(`/// <reference types="${join('..', '..', 'node_modules', '@types', 'pt-committee__identidade', 'index.d.ts')}" />`);
88
87
  });
88
+ test('Serves edge functions in a monorepo setup', async () => {
89
+ const rootPath = join(fixturesDir, 'monorepo_npm_module');
90
+ const basePath = join(rootPath, 'packages', 'frontend');
91
+ const paths = {
92
+ user: join(basePath, 'functions'),
93
+ };
94
+ const port = await getPort();
95
+ const importMapPaths = [join(basePath, 'import_map.json')];
96
+ const servePath = join(basePath, '.netlify', 'edge-functions-serve');
97
+ const server = await serve({
98
+ basePath,
99
+ bootstrapURL: 'https://edge.netlify.com/bootstrap/index-combined.ts',
100
+ importMapPaths,
101
+ port,
102
+ rootPath,
103
+ servePath,
104
+ });
105
+ const functions = [
106
+ {
107
+ name: 'func1',
108
+ path: join(paths.user, 'func1.ts'),
109
+ },
110
+ ];
111
+ const options = {
112
+ getFunctionsConfig: true,
113
+ };
114
+ const { features, functionsConfig, graph, success, npmSpecifiersWithExtraneousFiles } = await server(functions, {
115
+ very_secret_secret: 'i love netlify',
116
+ }, options);
117
+ expect(features).toEqual({ npmModules: true });
118
+ expect(success).toBe(true);
119
+ expect(functionsConfig).toEqual([{ path: '/func1' }]);
120
+ expect(npmSpecifiersWithExtraneousFiles).toEqual(['child-1']);
121
+ for (const key in functions) {
122
+ const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path);
123
+ expect(graphEntry).toBe(true);
124
+ }
125
+ const response1 = await fetch(`http://0.0.0.0:${port}/func1`, {
126
+ headers: {
127
+ 'x-nf-edge-functions': 'func1',
128
+ 'x-ef-passthrough': 'passthrough',
129
+ 'X-NF-Request-ID': uuidv4(),
130
+ },
131
+ });
132
+ expect(response1.status).toBe(200);
133
+ expect(await response1.text()).toBe(`<parent-1><child-1>JavaScript</child-1></parent-1>, <parent-2><child-2><grandchild-1>APIs<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-2>, <parent-3><child-2><grandchild-1>Markup<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-3>`);
134
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Returns all the directories obtained by traversing `inner` and its parents
3
+ * all the way to `outer`, inclusive.
4
+ */
5
+ export declare const pathsBetween: (inner: string, outer: string, paths?: string[]) => string[];
@@ -0,0 +1,12 @@
1
+ import path from 'path';
2
+ /**
3
+ * Returns all the directories obtained by traversing `inner` and its parents
4
+ * all the way to `outer`, inclusive.
5
+ */
6
+ export const pathsBetween = (inner, outer, paths = []) => {
7
+ const parent = path.dirname(inner);
8
+ if (inner === outer || inner === parent) {
9
+ return [...paths, outer];
10
+ }
11
+ return [inner, ...pathsBetween(parent, outer)];
12
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "9.5.0",
3
+ "version": "10.1.0",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -34,7 +34,8 @@
34
34
  "test:dev:deno": "deno test --allow-all deno",
35
35
  "test:ci:vitest": "vitest run --coverage",
36
36
  "test:ci:deno": "deno test --allow-all deno",
37
- "test:integration": "node --experimental-modules test/integration/test.js"
37
+ "test:integration": "node --experimental-modules test/integration/test.js",
38
+ "vendor": "deno vendor --force --output deno/vendor https://deno.land/x/deno_graph@0.59.2/types.d.ts https://deno.land/x/eszip@v0.55.2/mod.ts https://deno.land/x/retry@v2.0.0/mod.ts https://deno.land/x/std@0.177.0/path/mod.ts"
38
39
  },
39
40
  "config": {
40
41
  "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{node,scripts,.github}/**/*.{js,ts,md,html}\" \"*.{js,ts,md,html}\"",
@@ -80,7 +81,7 @@
80
81
  "better-ajv-errors": "^1.2.0",
81
82
  "common-path-prefix": "^3.0.0",
82
83
  "env-paths": "^3.0.0",
83
- "esbuild": "0.19.4",
84
+ "esbuild": "0.19.5",
84
85
  "execa": "^6.0.0",
85
86
  "find-up": "^6.3.0",
86
87
  "get-package-name": "^2.2.0",