@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.
- package/deno/lib/common.ts +2 -2
- package/deno/lib/stage2.test.ts +58 -0
- package/deno/lib/stage2.ts +1 -1
- package/deno/vendor/deno.land/x/dir@1.5.1/data_local_dir/mod.ts +34 -0
- package/deno/vendor/deno.land/x/{eszip@v0.40.0 → eszip@v0.55.2}/eszip_wasm.generated.js +136 -179
- package/deno/vendor/deno.land/x/eszip@v0.55.2/eszip_wasm_bg.wasm +0 -0
- package/deno/vendor/deno.land/x/wasmbuild@0.15.1/cache.ts +157 -0
- package/deno/vendor/deno.land/x/wasmbuild@0.15.1/loader.ts +126 -0
- package/dist/node/bootstrap.test.d.ts +1 -0
- package/dist/node/bootstrap.test.js +26 -0
- package/dist/node/bridge.d.ts +1 -1
- package/dist/node/bridge.js +1 -1
- package/dist/node/bundler.d.ts +2 -1
- package/dist/node/bundler.js +4 -5
- package/dist/node/bundler.test.js +51 -48
- package/dist/node/deno_config.d.ts +5 -0
- package/dist/node/deno_config.js +40 -0
- package/dist/node/deno_config.test.d.ts +1 -0
- package/dist/node/deno_config.test.js +37 -0
- package/dist/node/feature_flags.d.ts +2 -8
- package/dist/node/feature_flags.js +1 -4
- package/dist/node/formats/eszip.d.ts +1 -1
- package/dist/node/formats/eszip.js +2 -2
- package/dist/node/formats/javascript.js +1 -2
- package/dist/node/index.d.ts +1 -0
- package/dist/node/manifest.d.ts +6 -2
- package/dist/node/manifest.js +24 -20
- package/dist/node/manifest.test.js +46 -35
- package/dist/node/npm_dependencies.d.ts +3 -1
- package/dist/node/npm_dependencies.js +15 -10
- package/dist/node/npm_import_error.d.ts +2 -2
- package/dist/node/npm_import_error.js +5 -9
- package/dist/node/server/server.d.ts +2 -6
- package/dist/node/server/server.js +110 -16
- package/dist/node/server/server.test.js +50 -3
- package/dist/node/serving.test.d.ts +1 -0
- package/dist/node/serving.test.js +31 -0
- package/dist/node/utils/fs.d.ts +5 -0
- package/dist/node/utils/fs.js +12 -0
- package/dist/test/util.js +1 -1
- package/package.json +2 -2
- package/deno/vendor/deno.land/x/eszip@v0.40.0/eszip_wasm_bg.wasm +0 -0
- /package/deno/vendor/deno.land/x/{eszip@v0.40.0 → eszip@v0.55.2}/loader.ts +0 -0
- /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
|
+
});
|
package/dist/node/bridge.d.ts
CHANGED
|
@@ -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.
|
|
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 {
|
package/dist/node/bridge.js
CHANGED
|
@@ -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.
|
|
14
|
+
const DENO_VERSION_RANGE = '^1.37.0';
|
|
15
15
|
class DenoBridge {
|
|
16
16
|
constructor(options) {
|
|
17
17
|
var _a, _b, _c, _d, _e;
|
package/dist/node/bundler.d.ts
CHANGED
|
@@ -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
|
}>;
|
package/dist/node/bundler.js
CHANGED
|
@@ -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,
|
|
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
|
|
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,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,
|
|
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,
|
|
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
|
|
36
|
+
throw wrapBundleError(wrapNpmImportError(error), {
|
|
37
37
|
format: 'eszip',
|
|
38
38
|
});
|
|
39
39
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { mkdir,
|
|
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);
|