@netlify/edge-bundler 9.4.1 → 10.0.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.
Files changed (44) hide show
  1. package/deno/lib/common.ts +2 -2
  2. package/deno/lib/stage2.test.ts +58 -0
  3. package/deno/lib/stage2.ts +1 -1
  4. package/deno/vendor/deno.land/x/dir@1.5.1/data_local_dir/mod.ts +34 -0
  5. package/deno/vendor/deno.land/x/{eszip@v0.40.0 → eszip@v0.55.2}/eszip_wasm.generated.js +136 -179
  6. package/deno/vendor/deno.land/x/eszip@v0.55.2/eszip_wasm_bg.wasm +0 -0
  7. package/deno/vendor/deno.land/x/wasmbuild@0.15.1/cache.ts +157 -0
  8. package/deno/vendor/deno.land/x/wasmbuild@0.15.1/loader.ts +126 -0
  9. package/dist/node/bootstrap.test.d.ts +1 -0
  10. package/dist/node/bootstrap.test.js +26 -0
  11. package/dist/node/bridge.d.ts +1 -1
  12. package/dist/node/bridge.js +1 -1
  13. package/dist/node/bundler.d.ts +2 -1
  14. package/dist/node/bundler.js +4 -5
  15. package/dist/node/bundler.test.js +51 -48
  16. package/dist/node/deno_config.d.ts +5 -0
  17. package/dist/node/deno_config.js +40 -0
  18. package/dist/node/deno_config.test.d.ts +1 -0
  19. package/dist/node/deno_config.test.js +37 -0
  20. package/dist/node/feature_flags.d.ts +2 -8
  21. package/dist/node/feature_flags.js +1 -4
  22. package/dist/node/formats/eszip.d.ts +1 -1
  23. package/dist/node/formats/eszip.js +2 -2
  24. package/dist/node/formats/javascript.js +1 -2
  25. package/dist/node/index.d.ts +1 -0
  26. package/dist/node/manifest.d.ts +6 -2
  27. package/dist/node/manifest.js +24 -20
  28. package/dist/node/manifest.test.js +46 -35
  29. package/dist/node/npm_dependencies.d.ts +3 -1
  30. package/dist/node/npm_dependencies.js +15 -10
  31. package/dist/node/npm_import_error.d.ts +2 -2
  32. package/dist/node/npm_import_error.js +5 -9
  33. package/dist/node/server/server.d.ts +2 -6
  34. package/dist/node/server/server.js +110 -16
  35. package/dist/node/server/server.test.js +50 -3
  36. package/dist/node/serving.test.d.ts +1 -0
  37. package/dist/node/serving.test.js +31 -0
  38. package/dist/node/utils/fs.d.ts +5 -0
  39. package/dist/node/utils/fs.js +12 -0
  40. package/dist/test/util.js +1 -1
  41. package/package.json +2 -2
  42. package/deno/vendor/deno.land/x/eszip@v0.40.0/eszip_wasm_bg.wasm +0 -0
  43. /package/deno/vendor/deno.land/x/{eszip@v0.40.0 → eszip@v0.55.2}/loader.ts +0 -0
  44. /package/deno/vendor/deno.land/x/{eszip@v0.40.0 → eszip@v0.55.2}/mod.ts +0 -0
@@ -0,0 +1,157 @@
1
+ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
2
+ import { default as localDataDir } from "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts";
3
+
4
+ export async function cacheToLocalDir(
5
+ url: URL,
6
+ decompress: (bytes: Uint8Array) => Uint8Array,
7
+ ) {
8
+ const localPath = await getUrlLocalPath(url);
9
+ if (localPath == null) {
10
+ return undefined;
11
+ }
12
+ if (!await exists(localPath)) {
13
+ const fileBytes = decompress(new Uint8Array(await getUrlBytes(url)));
14
+ try {
15
+ await Deno.writeFile(localPath, fileBytes);
16
+ } catch {
17
+ // ignore and return the wasm bytes
18
+ return fileBytes;
19
+ }
20
+ }
21
+ return toFileUrl(localPath);
22
+ }
23
+
24
+ async function getUrlLocalPath(url: URL) {
25
+ try {
26
+ const dataDirPath = await getInitializedLocalDataDirPath();
27
+ const hash = await getUrlHash(url);
28
+ return `${dataDirPath}/${hash}.wasm`;
29
+ } catch {
30
+ return undefined;
31
+ }
32
+ }
33
+
34
+ async function getInitializedLocalDataDirPath() {
35
+ const dataDir = localDataDir();
36
+ if (dataDir == null) {
37
+ throw new Error(`Could not find local data directory.`);
38
+ }
39
+ const dirPath = `${dataDir}/deno-wasmbuild`;
40
+ await ensureDir(dirPath);
41
+ return dirPath;
42
+ }
43
+
44
+ async function exists(filePath: string | URL): Promise<boolean> {
45
+ try {
46
+ await Deno.lstat(filePath);
47
+ return true;
48
+ } catch (error) {
49
+ if (error instanceof Deno.errors.NotFound) {
50
+ return false;
51
+ }
52
+ throw error;
53
+ }
54
+ }
55
+
56
+ async function ensureDir(dir: string) {
57
+ try {
58
+ const fileInfo = await Deno.lstat(dir);
59
+ if (!fileInfo.isDirectory) {
60
+ throw new Error(`Path was not a directory '${dir}'`);
61
+ }
62
+ } catch (err) {
63
+ if (err instanceof Deno.errors.NotFound) {
64
+ // if dir not exists. then create it.
65
+ await Deno.mkdir(dir, { recursive: true });
66
+ return;
67
+ }
68
+ throw err;
69
+ }
70
+ }
71
+
72
+ async function getUrlHash(url: URL) {
73
+ // Taken from MDN: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
74
+ const hashBuffer = await crypto.subtle.digest(
75
+ "SHA-256",
76
+ new TextEncoder().encode(url.href),
77
+ );
78
+ // convert buffer to byte array
79
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
80
+ // convert bytes to hex string
81
+ const hashHex = hashArray
82
+ .map((b) => b.toString(16).padStart(2, "0"))
83
+ .join("");
84
+ return hashHex;
85
+ }
86
+
87
+ async function getUrlBytes(url: URL) {
88
+ const response = await fetchWithRetries(url);
89
+ return await response.arrayBuffer();
90
+ }
91
+
92
+ // the below is extracted from deno_std/path
93
+
94
+ const WHITESPACE_ENCODINGS: Record<string, string> = {
95
+ "\u0009": "%09",
96
+ "\u000A": "%0A",
97
+ "\u000B": "%0B",
98
+ "\u000C": "%0C",
99
+ "\u000D": "%0D",
100
+ "\u0020": "%20",
101
+ };
102
+
103
+ function encodeWhitespace(string: string): string {
104
+ return string.replaceAll(/[\s]/g, (c) => {
105
+ return WHITESPACE_ENCODINGS[c] ?? c;
106
+ });
107
+ }
108
+
109
+ function toFileUrl(path: string): URL {
110
+ return Deno.build.os === "windows"
111
+ ? windowsToFileUrl(path)
112
+ : posixToFileUrl(path);
113
+ }
114
+
115
+ function posixToFileUrl(path: string): URL {
116
+ const url = new URL("file:///");
117
+ url.pathname = encodeWhitespace(
118
+ path.replace(/%/g, "%25").replace(/\\/g, "%5C"),
119
+ );
120
+ return url;
121
+ }
122
+
123
+ function windowsToFileUrl(path: string): URL {
124
+ const [, hostname, pathname] = path.match(
125
+ /^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/,
126
+ )!;
127
+ const url = new URL("file:///");
128
+ url.pathname = encodeWhitespace(pathname.replace(/%/g, "%25"));
129
+ if (hostname != null && hostname != "localhost") {
130
+ url.hostname = hostname;
131
+ if (!url.hostname) {
132
+ throw new TypeError("Invalid hostname.");
133
+ }
134
+ }
135
+ return url;
136
+ }
137
+
138
+ export async function fetchWithRetries(url: URL | string, maxRetries = 5) {
139
+ let sleepMs = 250;
140
+ let iterationCount = 0;
141
+ while (true) {
142
+ iterationCount++;
143
+ try {
144
+ const res = await fetch(url);
145
+ if (res.ok || iterationCount > maxRetries) {
146
+ return res;
147
+ }
148
+ } catch (err) {
149
+ if (iterationCount > maxRetries) {
150
+ throw err;
151
+ }
152
+ }
153
+ console.warn(`Failed fetching. Retrying in ${sleepMs}ms...`);
154
+ await new Promise((resolve) => setTimeout(resolve, sleepMs));
155
+ sleepMs = Math.min(sleepMs * 2, 10_000);
156
+ }
157
+ }
@@ -0,0 +1,126 @@
1
+ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
2
+ import { fetchWithRetries } from "./cache.ts";
3
+
4
+ export type DecompressCallback = (bytes: Uint8Array) => Uint8Array;
5
+
6
+ export interface LoaderOptions {
7
+ /** The Wasm module's imports. */
8
+ imports: WebAssembly.Imports | undefined;
9
+ /** A function that caches the Wasm module to a local path so that
10
+ * so that a network request isn't required on every load.
11
+ *
12
+ * Returns an ArrayBuffer with the bytes on download success, but
13
+ * cache save failure.
14
+ */
15
+ cache?: (
16
+ url: URL,
17
+ decompress: DecompressCallback | undefined,
18
+ ) => Promise<URL | Uint8Array>;
19
+ }
20
+
21
+ export class Loader {
22
+ #options: LoaderOptions;
23
+ #lastLoadPromise:
24
+ | Promise<WebAssembly.WebAssemblyInstantiatedSource>
25
+ | undefined;
26
+ #instantiated: WebAssembly.WebAssemblyInstantiatedSource | undefined;
27
+
28
+ constructor(options: LoaderOptions) {
29
+ this.#options = options;
30
+ }
31
+
32
+ get instance() {
33
+ return this.#instantiated?.instance;
34
+ }
35
+
36
+ get module() {
37
+ return this.#instantiated?.module;
38
+ }
39
+
40
+ load(
41
+ url: URL,
42
+ decompress: DecompressCallback | undefined,
43
+ ): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
44
+ if (this.#instantiated) {
45
+ return Promise.resolve(this.#instantiated);
46
+ } else if (this.#lastLoadPromise == null) {
47
+ this.#lastLoadPromise = (async () => {
48
+ try {
49
+ this.#instantiated = await this.#instantiate(url, decompress);
50
+ return this.#instantiated;
51
+ } finally {
52
+ this.#lastLoadPromise = undefined;
53
+ }
54
+ })();
55
+ }
56
+ return this.#lastLoadPromise;
57
+ }
58
+
59
+ async #instantiate(url: URL, decompress: DecompressCallback | undefined) {
60
+ const imports = this.#options.imports;
61
+ if (this.#options.cache != null && url.protocol !== "file:") {
62
+ try {
63
+ const result = await this.#options.cache(
64
+ url,
65
+ decompress ?? ((bytes) => bytes),
66
+ );
67
+ if (result instanceof URL) {
68
+ url = result;
69
+ decompress = undefined; // already decompressed
70
+ } else if (result != null) {
71
+ return WebAssembly.instantiate(result, imports);
72
+ }
73
+ } catch {
74
+ // ignore if caching ever fails (ex. when on deploy)
75
+ }
76
+ }
77
+
78
+ const isFile = url.protocol === "file:";
79
+
80
+ // make file urls work in Node via dnt
81
+ // deno-lint-ignore no-explicit-any
82
+ const isNode = (globalThis as any).process?.versions?.node != null;
83
+ if (isFile && typeof Deno !== "object") {
84
+ throw new Error(
85
+ "Loading local files are not supported in this environment",
86
+ );
87
+ }
88
+ if (isNode && isFile) {
89
+ // the deno global will be shimmed by dnt
90
+ const wasmCode = await Deno.readFile(url);
91
+ return WebAssembly.instantiate(
92
+ decompress ? decompress(wasmCode) : wasmCode,
93
+ imports,
94
+ );
95
+ }
96
+
97
+ switch (url.protocol) {
98
+ case "file:":
99
+ case "https:":
100
+ case "http:": {
101
+ const wasmResponse = await fetchWithRetries(url);
102
+ if (decompress) {
103
+ const wasmCode = new Uint8Array(await wasmResponse.arrayBuffer());
104
+ return WebAssembly.instantiate(decompress(wasmCode), imports);
105
+ }
106
+ if (
107
+ isFile ||
108
+ wasmResponse.headers.get("content-type")?.toLowerCase()
109
+ .startsWith("application/wasm")
110
+ ) {
111
+ // Cast to any so there's no type checking issues with dnt
112
+ // (https://github.com/denoland/wasmbuild/issues/92)
113
+ // deno-lint-ignore no-explicit-any
114
+ return WebAssembly.instantiateStreaming(wasmResponse as any, imports);
115
+ } else {
116
+ return WebAssembly.instantiate(
117
+ await wasmResponse.arrayBuffer(),
118
+ imports,
119
+ );
120
+ }
121
+ }
122
+ default:
123
+ throw new Error(`Unsupported protocol: ${url.protocol}`);
124
+ }
125
+ }
126
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { Buffer } from 'buffer';
2
+ import { env } from 'process';
3
+ import fetch, { Headers } from 'node-fetch';
4
+ import { test, expect } from 'vitest';
5
+ import { getBootstrapURL } from './formats/javascript.js';
6
+ test('Imports the bootstrap layer from a valid URL', async () => {
7
+ const importURL = new URL(getBootstrapURL());
8
+ const headers = new Headers();
9
+ // `node-fetch` doesn't let us send credentials as part of the URL, so we
10
+ // have to transform them into an `Authorization` header.
11
+ if (importURL.username) {
12
+ const auth = Buffer.from(`${importURL.username}:${importURL.password}`);
13
+ importURL.username = '';
14
+ importURL.password = '';
15
+ headers.set('Authorization', `Basic ${auth.toString('base64')}`);
16
+ }
17
+ const canonicalURL = importURL.toString();
18
+ const { status } = await fetch(canonicalURL, { headers });
19
+ expect(status).toBe(200);
20
+ });
21
+ test('Imports the bootstrap layer from the URL present in the `NETLIFY_EDGE_BOOTSTRAP` environment variable, if present', () => {
22
+ const mockURL = 'https://example.com/boot.ts';
23
+ env.NETLIFY_EDGE_BOOTSTRAP = mockURL;
24
+ expect(getBootstrapURL()).toBe(mockURL);
25
+ env.NETLIFY_EDGE_BOOTSTRAP = undefined;
26
+ });
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { ExecaChildProcess } from 'execa';
3
3
  import { Logger } from './logger.js';
4
- declare const DENO_VERSION_RANGE = "^1.32.5";
4
+ declare const DENO_VERSION_RANGE = "^1.37.0";
5
5
  type OnBeforeDownloadHook = () => void | Promise<void>;
6
6
  type OnAfterDownloadHook = (error?: Error) => void | Promise<void>;
7
7
  interface DenoOptions {
@@ -11,7 +11,7 @@ import { getBinaryExtension } from './platform.js';
11
11
  const DENO_VERSION_FILE = 'version.txt';
12
12
  // When updating DENO_VERSION_RANGE, ensure that the deno version installed in the
13
13
  // build-image/buildbot does satisfy this range!
14
- const DENO_VERSION_RANGE = '^1.32.5';
14
+ const DENO_VERSION_RANGE = '^1.37.0';
15
15
  class DenoBridge {
16
16
  constructor(options) {
17
17
  var _a, _b, _c, _d, _e;
@@ -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,10 +148,7 @@ const createFunctionConfig = ({ internalFunctionsWithConfig, declarations }) =>
147
148
  [functionName]: addGeneratorFallback(mergedConfigFields),
148
149
  };
149
150
  }, {});
150
- const safelyVendorNPMSpecifiers = async ({ basePath, featureFlags, functions, importMap, logger, vendorDirectory, }) => {
151
- if (!featureFlags.edge_functions_npm_modules) {
152
- return;
153
- }
151
+ const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logger, rootPath, vendorDirectory, }) => {
154
152
  try {
155
153
  return await vendorNPMSpecifiers({
156
154
  basePath,
@@ -159,6 +157,7 @@ const safelyVendorNPMSpecifiers = async ({ basePath, featureFlags, functions, im
159
157
  importMap,
160
158
  logger,
161
159
  referenceTypes: false,
160
+ rootPath,
162
161
  });
163
162
  }
164
163
  catch (error) {
@@ -105,31 +105,7 @@ test('Adds a custom error property to user errors during bundling', async () =>
105
105
  await cleanup();
106
106
  }
107
107
  });
108
- test('Prints a nice error message when user tries importing an npm module and npm support is disabled', async () => {
109
- expect.assertions(2);
110
- const { basePath, cleanup, distPath } = await useFixture('imports_npm_module');
111
- const sourceDirectory = join(basePath, 'functions');
112
- const declarations = [
113
- {
114
- function: 'func1',
115
- path: '/func1',
116
- },
117
- ];
118
- try {
119
- await bundle([sourceDirectory], distPath, declarations, {
120
- basePath,
121
- importMapPaths: [join(basePath, 'import_map.json')],
122
- });
123
- }
124
- catch (error) {
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-1"'?`);
127
- }
128
- finally {
129
- await cleanup();
130
- }
131
- });
132
- test('Prints a nice error message when user tries importing an npm module and npm support is enabled', async () => {
108
+ test('Prints a nice error message when user tries importing an npm module', async () => {
133
109
  expect.assertions(2);
134
110
  const { basePath, cleanup, distPath } = await useFixture('imports_npm_module_scheme');
135
111
  const sourceDirectory = join(basePath, 'functions');
@@ -142,7 +118,6 @@ test('Prints a nice error message when user tries importing an npm module and np
142
118
  try {
143
119
  await bundle([sourceDirectory], distPath, declarations, {
144
120
  basePath,
145
- featureFlags: { edge_functions_npm_modules: true },
146
121
  });
147
122
  }
148
123
  catch (error) {
@@ -153,27 +128,6 @@ test('Prints a nice error message when user tries importing an npm module and np
153
128
  await cleanup();
154
129
  }
155
130
  });
156
- test('Prints a nice error message when user tries importing NPM module with npm: scheme', async () => {
157
- expect.assertions(2);
158
- const { basePath, cleanup, distPath } = await useFixture('imports_npm_module_scheme');
159
- const sourceDirectory = join(basePath, 'functions');
160
- const declarations = [
161
- {
162
- function: 'func1',
163
- path: '/func1',
164
- },
165
- ];
166
- try {
167
- await bundle([sourceDirectory], distPath, declarations, { basePath });
168
- }
169
- catch (error) {
170
- expect(error).toBeInstanceOf(BundleError);
171
- 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/p-retry"'?`);
172
- }
173
- finally {
174
- await cleanup();
175
- }
176
- });
177
131
  test('Does not add a custom error property to system errors during bundling', async () => {
178
132
  expect.assertions(1);
179
133
  try {
@@ -413,7 +367,6 @@ test('Loads npm modules from bare specifiers', async () => {
413
367
  const vendorDirectory = await tmp.dir();
414
368
  await bundle([sourceDirectory], distPath, declarations, {
415
369
  basePath,
416
- featureFlags: { edge_functions_npm_modules: true },
417
370
  importMapPaths: [join(basePath, 'import_map.json')],
418
371
  vendorDirectory: vendorDirectory.path,
419
372
  systemLogger,
@@ -427,3 +380,53 @@ test('Loads npm modules from bare specifiers', async () => {
427
380
  await cleanup();
428
381
  await rm(vendorDirectory.path, { force: true, recursive: true });
429
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
+ });
411
+ test('Loads JSON modules', async () => {
412
+ const { basePath, cleanup, distPath } = await useFixture('imports_json');
413
+ const sourceDirectory = join(basePath, 'functions');
414
+ const declarations = [
415
+ {
416
+ function: 'func1',
417
+ path: '/func1',
418
+ },
419
+ ];
420
+ const vendorDirectory = await tmp.dir();
421
+ await bundle([sourceDirectory], distPath, declarations, {
422
+ basePath,
423
+ vendorDirectory: vendorDirectory.path,
424
+ });
425
+ const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8');
426
+ const manifest = JSON.parse(manifestFile);
427
+ const bundlePath = join(distPath, manifest.bundles[0].asset);
428
+ const { func1 } = await runESZIP(bundlePath, vendorDirectory.path);
429
+ expect(func1).toBe(`{"foo":"bar"}`);
430
+ await cleanup();
431
+ await rm(vendorDirectory.path, { force: true, recursive: true });
432
+ });
@@ -0,0 +1,5 @@
1
+ interface DenoConfigFile {
2
+ importMap?: string;
3
+ }
4
+ export declare const getConfig: (basePath?: string) => Promise<DenoConfigFile | undefined>;
5
+ export {};
@@ -0,0 +1,40 @@
1
+ import { promises as fs } from 'fs';
2
+ import { join, resolve } from 'path';
3
+ import { parse as parseJSONC } from 'jsonc-parser';
4
+ import { isNodeError } from './utils/error.js';
5
+ const filenames = ['deno.json', 'deno.jsonc'];
6
+ export const getConfig = async (basePath) => {
7
+ if (basePath === undefined) {
8
+ return;
9
+ }
10
+ for (const filename of filenames) {
11
+ const candidatePath = join(basePath, filename);
12
+ const config = await getConfigFromFile(candidatePath);
13
+ if (config !== undefined) {
14
+ return normalizeConfig(config, basePath);
15
+ }
16
+ }
17
+ };
18
+ const getConfigFromFile = async (filePath) => {
19
+ try {
20
+ const data = await fs.readFile(filePath, 'utf8');
21
+ const config = parseJSONC(data);
22
+ return config;
23
+ }
24
+ catch (error) {
25
+ if (isNodeError(error) && error.code === 'ENOENT') {
26
+ return;
27
+ }
28
+ return {};
29
+ }
30
+ };
31
+ const normalizeConfig = (rawConfig, basePath) => {
32
+ const config = {};
33
+ if (rawConfig.importMap) {
34
+ if (typeof rawConfig.importMap !== 'string') {
35
+ throw new TypeError(`'importMap' property in Deno config must be a string`);
36
+ }
37
+ config.importMap = resolve(basePath, rawConfig.importMap);
38
+ }
39
+ return config;
40
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { promises as fs } from 'fs';
2
+ import { join } from 'path';
3
+ import tmp from 'tmp-promise';
4
+ import { expect, test } from 'vitest';
5
+ import { getConfig } from './deno_config.js';
6
+ test('Returns `undefined` if no config file is found', async () => {
7
+ const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
8
+ const config = await getConfig(path);
9
+ expect(config).toBeUndefined();
10
+ await cleanup();
11
+ });
12
+ test('Returns an empty object if the config file cannot be parsed', async () => {
13
+ const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
14
+ const configPath = join(path, 'deno.json');
15
+ await fs.writeFile(configPath, '{');
16
+ const config = await getConfig(path);
17
+ expect(config).toEqual({});
18
+ await cleanup();
19
+ });
20
+ test('Resolves `importMap` into an absolute path', async () => {
21
+ const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
22
+ const configPath = join(path, 'deno.json');
23
+ const data = JSON.stringify({ importMap: 'import_map.json' });
24
+ await fs.writeFile(configPath, data);
25
+ const config = await getConfig(path);
26
+ expect(config).toEqual({ importMap: join(path, 'import_map.json') });
27
+ await cleanup();
28
+ });
29
+ test('Supports JSONC', async () => {
30
+ const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
31
+ const configPath = join(path, 'deno.jsonc');
32
+ const data = JSON.stringify({ importMap: 'import_map.json' });
33
+ await fs.writeFile(configPath, `// This is a comment\n${data}`);
34
+ const config = await getConfig(path);
35
+ expect(config).toEqual({ importMap: join(path, 'import_map.json') });
36
+ await cleanup();
37
+ });
@@ -1,12 +1,6 @@
1
- declare const defaultFlags: {
2
- edge_functions_fail_unsupported_regex: boolean;
3
- edge_functions_npm_modules: boolean;
4
- };
1
+ declare const defaultFlags: {};
5
2
  type FeatureFlag = keyof typeof defaultFlags;
6
3
  type FeatureFlags = Partial<Record<FeatureFlag, boolean>>;
7
- declare const getFlags: (input?: Record<string, boolean>, flags?: {
8
- edge_functions_fail_unsupported_regex: boolean;
9
- edge_functions_npm_modules: boolean;
10
- }) => FeatureFlags;
4
+ declare const getFlags: (input?: Record<string, boolean>, flags?: {}) => FeatureFlags;
11
5
  export { defaultFlags, getFlags };
12
6
  export type { FeatureFlag, FeatureFlags };
@@ -1,7 +1,4 @@
1
- const defaultFlags = {
2
- edge_functions_fail_unsupported_regex: false,
3
- edge_functions_npm_modules: false,
4
- };
1
+ const defaultFlags = {};
5
2
  const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({
6
3
  ...result,
7
4
  [key]: input[key] === undefined ? defaultValue : input[key],
@@ -15,5 +15,5 @@ interface BundleESZIPOptions {
15
15
  importMap: ImportMap;
16
16
  vendorDirectory?: string;
17
17
  }
18
- declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, featureFlags, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>;
18
+ declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>;
19
19
  export { bundleESZIP as bundle };
@@ -6,7 +6,7 @@ import { wrapBundleError } from '../bundle_error.js';
6
6
  import { wrapNpmImportError } from '../npm_import_error.js';
7
7
  import { getPackagePath } from '../package_json.js';
8
8
  import { getFileHash } from '../utils/sha256.js';
9
- const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, featureFlags, functions, importMap, vendorDirectory, }) => {
9
+ const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }) => {
10
10
  const extension = '.eszip';
11
11
  const destPath = join(distDirectory, `${buildID}${extension}`);
12
12
  const importMapPrefixes = {
@@ -33,7 +33,7 @@ const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, exte
33
33
  await deno.run(['run', ...flags, bundler, JSON.stringify(payload)], { pipeOutput: true });
34
34
  }
35
35
  catch (error) {
36
- throw wrapBundleError(wrapNpmImportError(error, Boolean(featureFlags.edge_functions_npm_modules)), {
36
+ throw wrapBundleError(wrapNpmImportError(error), {
37
37
  format: 'eszip',
38
38
  });
39
39
  }
@@ -1,10 +1,9 @@
1
- import { mkdir, rm, writeFile } from 'fs/promises';
1
+ import { mkdir, writeFile } from 'fs/promises';
2
2
  import { join } from 'path';
3
3
  import { pathToFileURL } from 'url';
4
4
  const defaultFormatExportTypeError = (name) => `The Edge Function "${name}" has failed to load. Does it have a function as the default export?`;
5
5
  const defaultFormatImportError = (name) => `There was an error with Edge Function "${name}".`;
6
6
  const generateStage2 = async ({ bootstrapURL, distDirectory, fileName, formatExportTypeError, formatImportError, functions, }) => {
7
- await rm(distDirectory, { force: true, recursive: true, maxRetries: 3 });
8
7
  await mkdir(distDirectory, { recursive: true });
9
8
  const entryPoint = getLocalEntryPoint(functions, { bootstrapURL, formatExportTypeError, formatImportError });
10
9
  const stage2Path = join(distDirectory, fileName);