@netlify/edge-bundler 1.12.1 → 1.14.1

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/bundle.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { writeStage2 } from 'https://62ea8d05a6858300091547ed--edge.netlify.com/bundler/mod.ts'
1
+ import { writeStage2 } from './lib/stage2.ts'
2
2
 
3
3
  const [payload] = Deno.args
4
4
  const { basePath, destPath, functions, imports } = JSON.parse(payload)
@@ -0,0 +1,47 @@
1
+ import { load } from "https://deno.land/x/eszip@v0.18.0/loader.ts";
2
+ import { LoadResponse } from "https://deno.land/x/eszip@v0.18.0/mod.ts";
3
+ import * as path from "https://deno.land/std@0.127.0/path/mod.ts";
4
+ import { retryAsync } from "https://deno.land/x/retry@v2.0.0/mod.ts";
5
+
6
+ const inlineModule = (
7
+ specifier: string,
8
+ content: string,
9
+ ): LoadResponse => {
10
+ return {
11
+ content,
12
+ headers: {
13
+ "content-type": "application/typescript",
14
+ },
15
+ kind: "module",
16
+ specifier,
17
+ };
18
+ };
19
+
20
+ const loadFromVirtualRoot = async (
21
+ specifier: string,
22
+ virtualRoot: string,
23
+ basePath: string,
24
+ ) => {
25
+ const basePathURL = path.toFileUrl(basePath).toString();
26
+ const filePath = specifier.replace(virtualRoot.slice(0, -1), basePathURL);
27
+ const file = await load(filePath);
28
+
29
+ if (file === undefined) {
30
+ throw new Error(`Could not find file: ${filePath}`);
31
+ }
32
+
33
+ return { ...file, specifier };
34
+ };
35
+
36
+ const loadWithRetry = (specifier: string, delay = 1000, maxTry = 3) => {
37
+ if (!specifier.startsWith("https://")) {
38
+ return load(specifier);
39
+ }
40
+
41
+ return retryAsync(() => load(specifier), {
42
+ delay,
43
+ maxTry,
44
+ });
45
+ };
46
+
47
+ export { inlineModule, loadFromVirtualRoot, loadWithRetry };
@@ -0,0 +1,4 @@
1
+ export const PUBLIC_SPECIFIER = "netlify:edge";
2
+ export const STAGE1_SPECIFIER = "netlify:bootstrap-stage1";
3
+ export const STAGE2_SPECIFIER = "netlify:bootstrap-stage2";
4
+ export const virtualRoot = "file:///root/";
@@ -0,0 +1,81 @@
1
+ import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.18.0/mod.ts'
2
+
3
+ import * as path from 'https://deno.land/std@0.127.0/path/mod.ts'
4
+
5
+ import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER, virtualRoot } from './consts.ts'
6
+ import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts'
7
+
8
+ interface InputFunction {
9
+ name: string
10
+ path: string
11
+ }
12
+
13
+ interface WriteStage2Options {
14
+ basePath: string
15
+ destPath: string
16
+ functions: InputFunction[]
17
+ imports?: Record<string, string>
18
+ }
19
+
20
+ const getFunctionReference = (basePath: string, func: InputFunction, index: number) => {
21
+ const importName = `func${index}`
22
+ const exportLine = `"${func.name}": ${importName}`
23
+ const url = getVirtualPath(basePath, func.path)
24
+
25
+ return {
26
+ exportLine,
27
+ importLine: `import ${importName} from "${url}";`,
28
+ }
29
+ }
30
+
31
+ const getStage2Entry = (basePath: string, functions: InputFunction[]) => {
32
+ const lines = functions.map((func, index) => getFunctionReference(basePath, func, index))
33
+ const importLines = lines.map(({ importLine }) => importLine).join('\n')
34
+ const exportLines = lines.map(({ exportLine }) => exportLine).join(', ')
35
+ const exportDeclaration = `export const functions = {${exportLines}};`
36
+
37
+ return [importLines, exportDeclaration].join('\n\n')
38
+ }
39
+
40
+ const getVirtualPath = (basePath: string, filePath: string) => {
41
+ const relativePath = path.relative(basePath, filePath)
42
+ const url = new URL(relativePath, virtualRoot)
43
+
44
+ return url
45
+ }
46
+
47
+ const stage2Loader = (basePath: string, functions: InputFunction[], imports: Record<string, string> = {}) => {
48
+ return async (specifier: string): Promise<LoadResponse | undefined> => {
49
+ if (specifier === STAGE2_SPECIFIER) {
50
+ const stage2Entry = getStage2Entry(basePath, functions)
51
+
52
+ return inlineModule(specifier, stage2Entry)
53
+ }
54
+
55
+ if (specifier === PUBLIC_SPECIFIER) {
56
+ return {
57
+ kind: 'external',
58
+ specifier,
59
+ }
60
+ }
61
+
62
+ if (imports[specifier] !== undefined) {
63
+ return await loadWithRetry(imports[specifier])
64
+ }
65
+
66
+ if (specifier.startsWith(virtualRoot)) {
67
+ return loadFromVirtualRoot(specifier, virtualRoot, basePath)
68
+ }
69
+
70
+ return await loadWithRetry(specifier)
71
+ }
72
+ }
73
+
74
+ const writeStage2 = async ({ basePath, destPath, functions, imports }: WriteStage2Options) => {
75
+ const loader = stage2Loader(basePath, functions, imports)
76
+ const bytes = await build([STAGE2_SPECIFIER], loader)
77
+
78
+ return await Deno.writeFile(destPath, bytes)
79
+ }
80
+
81
+ export { writeStage2 }
package/dist/bridge.js CHANGED
@@ -27,7 +27,7 @@ class DenoBridge {
27
27
  await ((_a = this.onBeforeDownload) === null || _a === void 0 ? void 0 : _a.call(this));
28
28
  await this.ensureCacheDirectory();
29
29
  this.logger.system(`Downloading Deno CLI to ${this.cacheDirectory}`);
30
- const binaryPath = await download(this.cacheDirectory, this.versionRange);
30
+ const binaryPath = await download(this.cacheDirectory, this.versionRange, this.logger);
31
31
  const downloadedVersion = await this.getBinaryVersion(binaryPath);
32
32
  // We should never get here, because it means that `DENO_VERSION_RANGE` is
33
33
  // a malformed semver range. If this does happen, let's throw an error so
@@ -35,6 +35,7 @@ class DenoBridge {
35
35
  if (downloadedVersion === undefined) {
36
36
  const error = new Error('There was a problem setting up the Edge Functions environment. To try a manual installation, visit https://ntl.fyi/install-deno.');
37
37
  await ((_b = this.onAfterDownload) === null || _b === void 0 ? void 0 : _b.call(this, error));
38
+ this.logger.system('Could not run downloaded Deno CLI', error);
38
39
  throw error;
39
40
  }
40
41
  await this.writeVersionFile(downloadedVersion);
@@ -46,13 +47,12 @@ class DenoBridge {
46
47
  const { stdout } = await execa(binaryPath, ['--version']);
47
48
  const version = stdout.match(/^deno ([\d.]+)/);
48
49
  if (!version) {
50
+ this.logger.system(`getBinaryVersion no version found. binaryPath ${binaryPath}`);
49
51
  return;
50
52
  }
51
53
  return version[1];
52
54
  }
53
- catch (error) {
54
- this.logger.system('Error checking Deno binary version', error);
55
- }
55
+ catch { }
56
56
  }
57
57
  async getCachedBinary() {
58
58
  const versionFilePath = path.join(this.cacheDirectory, DENO_VERSION_FILE);
@@ -60,10 +60,12 @@ class DenoBridge {
60
60
  try {
61
61
  cachedVersion = await fs.readFile(versionFilePath, 'utf8');
62
62
  }
63
- catch {
63
+ catch (error) {
64
+ this.logger.system('Error getting cached binary', error);
64
65
  return;
65
66
  }
66
67
  if (!semver.satisfies(cachedVersion, this.versionRange)) {
68
+ this.logger.system(`semver not satisfied. cachedVersion: ${cachedVersion}, versionRange: ${this.versionRange}`);
67
69
  return;
68
70
  }
69
71
  const binaryName = `deno${getBinaryExtension()}`;
@@ -76,6 +78,7 @@ class DenoBridge {
76
78
  const globalBinaryName = 'deno';
77
79
  const globalVersion = await this.getBinaryVersion(globalBinaryName);
78
80
  if (globalVersion === undefined || !semver.satisfies(globalVersion, this.versionRange)) {
81
+ this.logger.system(`No globalVersion or semver not satisfied. globalVersion: ${globalVersion}, versionRange: ${this.versionRange}`);
79
82
  return;
80
83
  }
81
84
  return globalBinaryName;
@@ -1,2 +1,3 @@
1
- declare const download: (targetDirectory: string, versionRange: string) => Promise<string>;
2
- export { download };
1
+ import { Logger } from './logger.js';
2
+ declare const downloadWithRetry: (targetDirectory: string, versionRange: string, logger: Logger) => Promise<string>;
3
+ export { downloadWithRetry as download };
@@ -1,35 +1,50 @@
1
- import fs from 'fs';
1
+ import { createWriteStream, promises as fs } from 'fs';
2
2
  import path from 'path';
3
+ import { promisify } from 'util';
3
4
  import fetch from 'node-fetch';
4
5
  import StreamZip from 'node-stream-zip';
6
+ import pRetry from 'p-retry';
5
7
  import semver from 'semver';
6
8
  import { getBinaryExtension, getPlatformTarget } from './platform.js';
9
+ const downloadWithRetry = async (targetDirectory, versionRange, logger) => await pRetry(async () => await download(targetDirectory, versionRange), {
10
+ retries: 3,
11
+ onFailedAttempt: (error) => {
12
+ logger.system('Deno download with retry failed', error);
13
+ },
14
+ });
7
15
  const download = async (targetDirectory, versionRange) => {
8
16
  const zipPath = path.join(targetDirectory, 'deno-cli-latest.zip');
9
17
  const data = await downloadVersion(versionRange);
10
18
  const binaryName = `deno${getBinaryExtension()}`;
11
19
  const binaryPath = path.join(targetDirectory, binaryName);
12
- const file = fs.createWriteStream(zipPath);
13
- await new Promise((resolve, reject) => {
14
- data.pipe(file);
15
- data.on('error', reject);
16
- file.on('finish', resolve);
17
- });
18
- await extractBinaryFromZip(zipPath, binaryPath, binaryName);
20
+ const file = createWriteStream(zipPath);
19
21
  try {
20
- await fs.promises.unlink(zipPath);
22
+ await new Promise((resolve, reject) => {
23
+ data.pipe(file);
24
+ data.on('error', reject);
25
+ file.on('finish', resolve);
26
+ });
27
+ await extractBinaryFromZip(zipPath, binaryPath, binaryName);
28
+ return binaryPath;
21
29
  }
22
- catch {
23
- // no-op
30
+ finally {
31
+ // Try closing and deleting the zip file in any case, error or not
32
+ await promisify(file.close.bind(file))();
33
+ try {
34
+ await fs.unlink(zipPath);
35
+ }
36
+ catch {
37
+ // no-op
38
+ }
24
39
  }
25
- return binaryPath;
26
40
  };
27
41
  const downloadVersion = async (versionRange) => {
28
42
  const version = await getLatestVersionForRange(versionRange);
29
43
  const url = getReleaseURL(version);
30
44
  const res = await fetch(url);
31
- if (res.body === null) {
32
- throw new Error('Could not download Deno');
45
+ // eslint-disable-next-line no-magic-numbers
46
+ if (res.body === null || res.status < 200 || res.status > 299) {
47
+ throw new Error(`Download failed with status code ${res.status}`);
33
48
  }
34
49
  return res.body;
35
50
  };
@@ -38,7 +53,7 @@ const extractBinaryFromZip = async (zipPath, binaryPath, binaryName) => {
38
53
  const zip = new StreamZipAsync({ file: zipPath });
39
54
  await zip.extract(binaryName, binaryPath);
40
55
  await zip.close();
41
- await fs.promises.chmod(binaryPath, '755');
56
+ await fs.chmod(binaryPath, '755');
42
57
  };
43
58
  const getLatestVersion = async () => {
44
59
  try {
@@ -75,4 +90,4 @@ const getReleaseURL = (version) => {
75
90
  const target = getPlatformTarget();
76
91
  return `https://dl.deno.land/release/v${version}/deno-${target}.zip`;
77
92
  };
78
- export { download };
93
+ export { downloadWithRetry as download };
@@ -5,7 +5,7 @@ import { pathToFileURL } from 'url';
5
5
  import del from 'del';
6
6
  import { wrapBundleError } from '../bundle_error.js';
7
7
  import { getFileHash } from '../utils/sha256.js';
8
- const BOOTSTRAP_LATEST = 'https://62ea8d05a6858300091547ed--edge.netlify.com/bootstrap/index-combined.ts';
8
+ const BOOTSTRAP_LATEST = 'https://62f5f45fbc76ed0009624267--edge.netlify.com/bootstrap/index-combined.ts';
9
9
  const bundleJS = async ({ buildID, debug, deno, distDirectory, functions, importMap, }) => {
10
10
  const stage2Path = await generateStage2({ distDirectory, functions, fileName: `${buildID}-pre.js` });
11
11
  const extension = '.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "1.12.1",
3
+ "version": "1.14.1",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -86,6 +86,7 @@
86
86
  "glob-to-regexp": "^0.4.1",
87
87
  "node-fetch": "^3.1.1",
88
88
  "node-stream-zip": "^1.15.0",
89
+ "p-retry": "^5.1.1",
89
90
  "p-wait-for": "^4.1.0",
90
91
  "path-key": "^4.0.0",
91
92
  "semver": "^7.3.5",