@netlify/edge-bundler 0.11.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 (46) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +41 -0
  3. package/deno/bundle.ts +6 -0
  4. package/dist/bridge.d.ts +42 -0
  5. package/dist/bridge.js +136 -0
  6. package/dist/bundle.d.ts +6 -0
  7. package/dist/bundle.js +1 -0
  8. package/dist/bundle_error.d.ts +16 -0
  9. package/dist/bundle_error.js +24 -0
  10. package/dist/bundler.d.ts +17 -0
  11. package/dist/bundler.js +81 -0
  12. package/dist/declaration.d.ts +10 -0
  13. package/dist/declaration.js +1 -0
  14. package/dist/downloader.d.ts +2 -0
  15. package/dist/downloader.js +78 -0
  16. package/dist/edge_function.d.ts +4 -0
  17. package/dist/edge_function.js +1 -0
  18. package/dist/feature_flags.d.ts +6 -0
  19. package/dist/feature_flags.js +8 -0
  20. package/dist/finder.d.ts +3 -0
  21. package/dist/finder.js +55 -0
  22. package/dist/formats/eszip.d.ts +13 -0
  23. package/dist/formats/eszip.js +36 -0
  24. package/dist/formats/javascript.d.ts +25 -0
  25. package/dist/formats/javascript.js +83 -0
  26. package/dist/home_path.d.ts +2 -0
  27. package/dist/home_path.js +5 -0
  28. package/dist/import_map.d.ts +13 -0
  29. package/dist/import_map.js +30 -0
  30. package/dist/index.d.ts +5 -0
  31. package/dist/index.js +5 -0
  32. package/dist/manifest.d.ts +28 -0
  33. package/dist/manifest.js +35 -0
  34. package/dist/package_json.d.ts +2 -0
  35. package/dist/package_json.js +5 -0
  36. package/dist/platform.d.ts +3 -0
  37. package/dist/platform.js +13 -0
  38. package/dist/server/server.d.ts +20 -0
  39. package/dist/server/server.js +68 -0
  40. package/dist/server/util.d.ts +4 -0
  41. package/dist/server/util.js +48 -0
  42. package/dist/utils/non_nullable.d.ts +2 -0
  43. package/dist/utils/non_nullable.js +2 -0
  44. package/dist/utils/sha256.d.ts +2 -0
  45. package/dist/utils/sha256.js +16 -0
  46. package/package.json +92 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020 Netlify <team@netlify.com>
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ [![Build](https://github.com/netlify/edge-bundler/workflows/Build/badge.svg)](https://github.com/netlify/edge-bundler/actions)
2
+ [![Node](https://img.shields.io/node/v/@netlify/edge-bundler.svg?logo=node.js)](https://www.npmjs.com/package/@netlify/edge-bundler)
3
+
4
+ # Edge Bundler
5
+
6
+ Intelligently prepare Netlify Edge Functions for deployment.
7
+
8
+ ## Usage
9
+
10
+ 1. Install this module as a dependency in your project
11
+
12
+ ```
13
+ npm install @netlify/edge-bundler --save
14
+ ```
15
+
16
+ 2. Import it and create a bundle from a directory of Edge Functions and a list of declarations.
17
+
18
+ ```js
19
+ import { bundle } from '@netlify/edge-bundler'
20
+
21
+ // List of directories to search for Edge Functions.
22
+ const sourceDirectories = [
23
+ "/repo/netlify/edge-functions",
24
+ "/repo/.netlify/edge-functions"
25
+ ]
26
+
27
+ // Directory where bundle should be placed.
28
+ const distDirectory = "/repo/.netlify/edge-functions-dist"
29
+
30
+ // List of Edge Functions declarations.
31
+ const declarations = [
32
+ {function: "user-1", path: "/blog/*"},
33
+ {function: "internal-2", path: "/"}
34
+ ]
35
+
36
+ await bundle(sourceDirectories, distDirectory, declarations)
37
+ ```
38
+ ## Contributors
39
+
40
+ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on how to set up and work on this repository. Thanks
41
+ for contributing!
package/deno/bundle.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { writeStage2 } from 'https://625d32be1b90870009edfc99--edge-bootstrap.netlify.app/bundler/mod.ts'
2
+
3
+ const [payload] = Deno.args
4
+ const { basePath, destPath, functions } = JSON.parse(payload)
5
+
6
+ await writeStage2({ basePath, functions, destPath })
@@ -0,0 +1,42 @@
1
+ import { ExecaChildProcess } from 'execa';
2
+ declare type LifecycleHook = () => void | Promise<void>;
3
+ interface DenoOptions {
4
+ cacheDirectory?: string;
5
+ debug?: boolean;
6
+ onAfterDownload?: LifecycleHook;
7
+ onBeforeDownload?: LifecycleHook;
8
+ useGlobal?: boolean;
9
+ versionRange?: string;
10
+ }
11
+ interface ProcessRef {
12
+ ps?: ExecaChildProcess<string>;
13
+ }
14
+ interface RunOptions {
15
+ pipeOutput?: boolean;
16
+ }
17
+ declare class DenoBridge {
18
+ cacheDirectory: string;
19
+ currentDownload?: ReturnType<DenoBridge['downloadBinary']>;
20
+ debug: boolean;
21
+ onAfterDownload?: LifecycleHook;
22
+ onBeforeDownload?: LifecycleHook;
23
+ useGlobal: boolean;
24
+ versionRange: string;
25
+ constructor(options?: DenoOptions);
26
+ private downloadBinary;
27
+ static getBinaryVersion(binaryPath: string): Promise<string | undefined>;
28
+ private getCachedBinary;
29
+ private getGlobalBinary;
30
+ private getRemoteBinary;
31
+ private log;
32
+ private static runWithBinary;
33
+ private writeVersionFile;
34
+ getBinaryPath(): Promise<{
35
+ global: boolean;
36
+ path: string;
37
+ }>;
38
+ run(args: string[], { pipeOutput }?: RunOptions): Promise<import("execa").ExecaReturnValue<string>>;
39
+ runInBackground(args: string[], pipeOutput?: boolean, ref?: ProcessRef): Promise<void>;
40
+ }
41
+ export { DenoBridge };
42
+ export type { LifecycleHook, ProcessRef };
package/dist/bridge.js ADDED
@@ -0,0 +1,136 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import process from 'process';
4
+ import { execa } from 'execa';
5
+ import semver from 'semver';
6
+ import { download } from './downloader.js';
7
+ import { getPathInHome } from './home_path.js';
8
+ import { getBinaryExtension } from './platform.js';
9
+ const DENO_VERSION_FILE = 'version.txt';
10
+ const DENO_VERSION_RANGE = '^1.20.3';
11
+ class DenoBridge {
12
+ constructor(options = {}) {
13
+ var _a, _b, _c, _d;
14
+ this.cacheDirectory = (_a = options.cacheDirectory) !== null && _a !== void 0 ? _a : getPathInHome('deno-cli');
15
+ this.debug = (_b = options.debug) !== null && _b !== void 0 ? _b : false;
16
+ this.onAfterDownload = options.onAfterDownload;
17
+ this.onBeforeDownload = options.onBeforeDownload;
18
+ this.useGlobal = (_c = options.useGlobal) !== null && _c !== void 0 ? _c : true;
19
+ this.versionRange = (_d = options.versionRange) !== null && _d !== void 0 ? _d : DENO_VERSION_RANGE;
20
+ }
21
+ async downloadBinary() {
22
+ if (this.onBeforeDownload) {
23
+ this.onBeforeDownload();
24
+ }
25
+ await fs.mkdir(this.cacheDirectory, { recursive: true });
26
+ this.log(`Downloading Deno CLI to ${this.cacheDirectory}...`);
27
+ const binaryPath = await download(this.cacheDirectory, this.versionRange);
28
+ const downloadedVersion = await DenoBridge.getBinaryVersion(binaryPath);
29
+ // We should never get here, because it means that `DENO_VERSION_RANGE` is
30
+ // a malformed semver range. If this does happen, let's throw an error so
31
+ // that the tests catch it.
32
+ if (downloadedVersion === undefined) {
33
+ throw new Error('Could not read downloaded binary');
34
+ }
35
+ await this.writeVersionFile(downloadedVersion);
36
+ if (this.onAfterDownload) {
37
+ this.onAfterDownload();
38
+ }
39
+ return binaryPath;
40
+ }
41
+ static async getBinaryVersion(binaryPath) {
42
+ try {
43
+ const { stdout } = await execa(binaryPath, ['--version']);
44
+ const version = stdout.match(/^deno ([\d.]+)/);
45
+ if (!version) {
46
+ return;
47
+ }
48
+ return version[1];
49
+ }
50
+ catch {
51
+ // no-op
52
+ }
53
+ }
54
+ async getCachedBinary() {
55
+ const versionFilePath = path.join(this.cacheDirectory, DENO_VERSION_FILE);
56
+ let cachedVersion;
57
+ try {
58
+ cachedVersion = await fs.readFile(versionFilePath, 'utf8');
59
+ }
60
+ catch {
61
+ return;
62
+ }
63
+ if (!semver.satisfies(cachedVersion, this.versionRange)) {
64
+ return;
65
+ }
66
+ const binaryName = `deno${getBinaryExtension()}`;
67
+ return path.join(this.cacheDirectory, binaryName);
68
+ }
69
+ async getGlobalBinary() {
70
+ if (!this.useGlobal) {
71
+ return;
72
+ }
73
+ const globalBinaryName = 'deno';
74
+ const globalVersion = await DenoBridge.getBinaryVersion(globalBinaryName);
75
+ if (globalVersion === undefined || !semver.satisfies(globalVersion, this.versionRange)) {
76
+ return;
77
+ }
78
+ return globalBinaryName;
79
+ }
80
+ getRemoteBinary() {
81
+ if (this.currentDownload === undefined) {
82
+ this.currentDownload = this.downloadBinary();
83
+ }
84
+ return this.currentDownload;
85
+ }
86
+ log(...data) {
87
+ if (!this.debug) {
88
+ return;
89
+ }
90
+ console.log(...data);
91
+ }
92
+ static runWithBinary(binaryPath, args, pipeOutput) {
93
+ var _a, _b;
94
+ const runDeno = execa(binaryPath, args);
95
+ if (pipeOutput) {
96
+ (_a = runDeno.stdout) === null || _a === void 0 ? void 0 : _a.pipe(process.stdout);
97
+ (_b = runDeno.stderr) === null || _b === void 0 ? void 0 : _b.pipe(process.stderr);
98
+ }
99
+ return runDeno;
100
+ }
101
+ async writeVersionFile(version) {
102
+ const versionFilePath = path.join(this.cacheDirectory, DENO_VERSION_FILE);
103
+ await fs.writeFile(versionFilePath, version);
104
+ }
105
+ async getBinaryPath() {
106
+ const globalPath = await this.getGlobalBinary();
107
+ if (globalPath !== undefined) {
108
+ this.log('Using global installation of Deno CLI');
109
+ return { global: true, path: globalPath };
110
+ }
111
+ const cachedPath = await this.getCachedBinary();
112
+ if (cachedPath !== undefined) {
113
+ this.log('Using cached Deno CLI from', cachedPath);
114
+ return { global: false, path: cachedPath };
115
+ }
116
+ const downloadedPath = await this.getRemoteBinary();
117
+ return { global: false, path: downloadedPath };
118
+ }
119
+ // Runs the Deno CLI in the background and returns a reference to the child
120
+ // process, awaiting its execution.
121
+ async run(args, { pipeOutput } = {}) {
122
+ const { path: binaryPath } = await this.getBinaryPath();
123
+ return DenoBridge.runWithBinary(binaryPath, args, pipeOutput);
124
+ }
125
+ // Runs the Deno CLI in the background, assigning a reference of the child
126
+ // process to a `ps` property in the `ref` argument, if one is supplied.
127
+ async runInBackground(args, pipeOutput, ref) {
128
+ const { path: binaryPath } = await this.getBinaryPath();
129
+ const ps = DenoBridge.runWithBinary(binaryPath, args, pipeOutput);
130
+ if (ref !== undefined) {
131
+ // eslint-disable-next-line no-param-reassign
132
+ ref.ps = ps;
133
+ }
134
+ }
135
+ }
136
+ export { DenoBridge };
@@ -0,0 +1,6 @@
1
+ interface Bundle {
2
+ extension: string;
3
+ format: string;
4
+ hash: string;
5
+ }
6
+ export type { Bundle };
package/dist/bundle.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ interface BundleErrorOptions {
2
+ format: string;
3
+ }
4
+ declare const getCustomErrorInfo: (options: BundleErrorOptions) => {
5
+ location: {
6
+ format: string;
7
+ runtime: string;
8
+ };
9
+ type: string;
10
+ };
11
+ declare class BundleError extends Error {
12
+ customErrorInfo: ReturnType<typeof getCustomErrorInfo>;
13
+ constructor(originalError: Error, options: BundleErrorOptions);
14
+ }
15
+ declare const wrapBundleError: (input: unknown, options: BundleErrorOptions) => unknown;
16
+ export { BundleError, wrapBundleError };
@@ -0,0 +1,24 @@
1
+ const getCustomErrorInfo = (options) => ({
2
+ location: {
3
+ format: options.format,
4
+ runtime: 'deno',
5
+ },
6
+ type: 'functionsBundling',
7
+ });
8
+ class BundleError extends Error {
9
+ constructor(originalError, options) {
10
+ super(originalError.message);
11
+ this.customErrorInfo = getCustomErrorInfo(options);
12
+ this.name = 'BundleError';
13
+ this.stack = originalError.stack;
14
+ // https://github.com/microsoft/TypeScript-wiki/blob/8a66ecaf77118de456f7cd9c56848a40fe29b9b4/Breaking-Changes.md#implicit-any-error-raised-for-un-annotated-callback-arguments-with-no-matching-overload-arguments
15
+ Object.setPrototypeOf(this, BundleError.prototype);
16
+ }
17
+ }
18
+ const wrapBundleError = (input, options) => {
19
+ if (input instanceof Error) {
20
+ return new BundleError(input, options);
21
+ }
22
+ return input;
23
+ };
24
+ export { BundleError, wrapBundleError };
@@ -0,0 +1,17 @@
1
+ import { LifecycleHook } from './bridge.js';
2
+ import type { Declaration } from './declaration.js';
3
+ import { FeatureFlags } from './feature_flags.js';
4
+ import { ImportMapFile } from './import_map.js';
5
+ interface BundleOptions {
6
+ cacheDirectory?: string;
7
+ debug?: boolean;
8
+ distImportMapPath?: string;
9
+ featureFlags?: FeatureFlags;
10
+ importMaps?: ImportMapFile[];
11
+ onAfterDownload?: LifecycleHook;
12
+ onBeforeDownload?: LifecycleHook;
13
+ }
14
+ declare const bundle: (sourceDirectories: string[], distDirectory: string, declarations?: Declaration[], { cacheDirectory, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMaps, onAfterDownload, onBeforeDownload, }?: BundleOptions) => Promise<{
15
+ functions: import("./edge_function.js").EdgeFunction[];
16
+ }>;
17
+ export { bundle };
@@ -0,0 +1,81 @@
1
+ import { promises as fs } from 'fs';
2
+ import { join } from 'path';
3
+ import commonPathPrefix from 'common-path-prefix';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { DenoBridge } from './bridge.js';
6
+ import { getFlags } from './feature_flags.js';
7
+ import { findFunctions } from './finder.js';
8
+ import { bundle as bundleESZIP } from './formats/eszip.js';
9
+ import { bundle as bundleJS } from './formats/javascript.js';
10
+ import { ImportMap } from './import_map.js';
11
+ import { writeManifest } from './manifest.js';
12
+ const bundle = async (sourceDirectories, distDirectory, declarations = [], { cacheDirectory, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMaps, onAfterDownload, onBeforeDownload, } = {}) => {
13
+ const featureFlags = getFlags(inputFeatureFlags);
14
+ const deno = new DenoBridge({
15
+ debug,
16
+ cacheDirectory,
17
+ onAfterDownload,
18
+ onBeforeDownload,
19
+ });
20
+ const basePath = getBasePath(sourceDirectories);
21
+ // The name of the bundle will be the hash of its contents, which we can't
22
+ // compute until we run the bundle process. For now, we'll use a random ID
23
+ // to create the bundle artifacts and rename them later.
24
+ const buildID = uuidv4();
25
+ // Creating an ImportMap instance with any import maps supplied by the user,
26
+ // if any.
27
+ const importMap = new ImportMap(importMaps);
28
+ const functions = await findFunctions(sourceDirectories);
29
+ const bundleOps = [
30
+ bundleJS({
31
+ buildID,
32
+ debug,
33
+ deno,
34
+ distDirectory,
35
+ functions,
36
+ importMap,
37
+ }),
38
+ ];
39
+ if (featureFlags.edge_functions_produce_eszip) {
40
+ bundleOps.push(bundleESZIP({
41
+ basePath,
42
+ buildID,
43
+ debug,
44
+ deno,
45
+ distDirectory,
46
+ functions,
47
+ }));
48
+ }
49
+ const bundles = await Promise.all(bundleOps);
50
+ // The final file name of the bundles contains a SHA256 hash of the contents,
51
+ // which we can only compute now that the files have been generated. So let's
52
+ // rename the bundles to their permanent names.
53
+ await createFinalBundles(bundles, distDirectory, buildID);
54
+ await writeManifest({
55
+ bundles,
56
+ declarations,
57
+ distDirectory,
58
+ functions,
59
+ });
60
+ if (distImportMapPath) {
61
+ await importMap.writeToFile(distImportMapPath);
62
+ }
63
+ return { functions };
64
+ };
65
+ const createFinalBundles = async (bundles, distDirectory, buildID) => {
66
+ const renamingOps = bundles.map(async ({ extension, hash }) => {
67
+ const tempBundlePath = join(distDirectory, `${buildID}${extension}`);
68
+ const finalBundlePath = join(distDirectory, `${hash}${extension}`);
69
+ await fs.rename(tempBundlePath, finalBundlePath);
70
+ });
71
+ await Promise.all(renamingOps);
72
+ };
73
+ const getBasePath = (sourceDirectories) => {
74
+ // `common-path-prefix` returns an empty string when called with a single
75
+ // path, so we check for that case and return the path itself instead.
76
+ if (sourceDirectories.length === 1) {
77
+ return sourceDirectories[0];
78
+ }
79
+ return commonPathPrefix(sourceDirectories);
80
+ };
81
+ export { bundle };
@@ -0,0 +1,10 @@
1
+ interface DeclarationWithPath {
2
+ function: string;
3
+ path: string;
4
+ }
5
+ interface DeclarationWithPattern {
6
+ function: string;
7
+ pattern: string;
8
+ }
9
+ declare type Declaration = DeclarationWithPath | DeclarationWithPattern;
10
+ export { Declaration, DeclarationWithPath, DeclarationWithPattern };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ declare const download: (targetDirectory: string, versionRange: string) => Promise<string>;
2
+ export { download };
@@ -0,0 +1,78 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import fetch from 'node-fetch';
4
+ import StreamZip from 'node-stream-zip';
5
+ import semver from 'semver';
6
+ import { getBinaryExtension, getPlatformTarget } from './platform.js';
7
+ const download = async (targetDirectory, versionRange) => {
8
+ const zipPath = path.join(targetDirectory, 'deno-cli-latest.zip');
9
+ const data = await downloadVersion(versionRange);
10
+ const binaryName = `deno${getBinaryExtension()}`;
11
+ 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);
19
+ try {
20
+ await fs.promises.unlink(zipPath);
21
+ }
22
+ catch {
23
+ // no-op
24
+ }
25
+ return binaryPath;
26
+ };
27
+ const downloadVersion = async (versionRange) => {
28
+ const version = await getLatestVersionForRange(versionRange);
29
+ const url = getReleaseURL(version);
30
+ const res = await fetch(url);
31
+ if (res.body === null) {
32
+ throw new Error('Could not download Deno');
33
+ }
34
+ return res.body;
35
+ };
36
+ const extractBinaryFromZip = async (zipPath, binaryPath, binaryName) => {
37
+ const { async: StreamZipAsync } = StreamZip;
38
+ const zip = new StreamZipAsync({ file: zipPath });
39
+ await zip.extract(binaryName, binaryPath);
40
+ await zip.close();
41
+ await fs.promises.chmod(binaryPath, '755');
42
+ };
43
+ const getLatestVersion = async () => {
44
+ try {
45
+ const response = await fetch('https://dl.deno.land/release-latest.txt');
46
+ const data = await response.text();
47
+ // We want to extract <VERSION> from the format `v<VERSION>`.
48
+ const version = data.match(/^v?(\d+\.\d+\.\d+)/);
49
+ if (version === null) {
50
+ return;
51
+ }
52
+ return version[1];
53
+ }
54
+ catch {
55
+ // This is a no-op. If we failed to retrieve the latest version, let's
56
+ // return `undefined` and let the code upstream handle it.
57
+ }
58
+ };
59
+ const getLatestVersionForRange = async (range) => {
60
+ var _a;
61
+ const minimumVersion = (_a = semver.minVersion(range)) === null || _a === void 0 ? void 0 : _a.version;
62
+ // We should never get here, because it means that `DENO_VERSION_RANGE` is
63
+ // a malformed semver range. If this does happen, let's throw an error so
64
+ // that the tests catch it.
65
+ if (minimumVersion === undefined) {
66
+ throw new Error('Incorrect version range specified by Edge Bundler');
67
+ }
68
+ const latestVersion = await getLatestVersion();
69
+ if (latestVersion === undefined || !semver.satisfies(latestVersion, range)) {
70
+ return minimumVersion;
71
+ }
72
+ return latestVersion;
73
+ };
74
+ const getReleaseURL = (version) => {
75
+ const target = getPlatformTarget();
76
+ return `https://dl.deno.land/release/v${version}/deno-${target}.zip`;
77
+ };
78
+ export { download };
@@ -0,0 +1,4 @@
1
+ export interface EdgeFunction {
2
+ name: string;
3
+ path: string;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ declare const defaultFlags: Record<string, boolean>;
2
+ declare type FeatureFlag = keyof typeof defaultFlags;
3
+ declare type FeatureFlags = Record<FeatureFlag, boolean>;
4
+ declare const getFlags: (input?: Record<string, boolean>, flags?: Record<string, boolean>) => Record<FeatureFlag, string>;
5
+ export { defaultFlags, getFlags };
6
+ export type { FeatureFlag, FeatureFlags };
@@ -0,0 +1,8 @@
1
+ const defaultFlags = {
2
+ edge_functions_produce_eszip: false,
3
+ };
4
+ const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({
5
+ ...result,
6
+ [key]: input[key] === undefined ? defaultValue : input[key],
7
+ }), {});
8
+ export { defaultFlags, getFlags };
@@ -0,0 +1,3 @@
1
+ import { EdgeFunction } from './edge_function.js';
2
+ declare const findFunctions: (directories: string[]) => Promise<EdgeFunction[]>;
3
+ export { findFunctions };
package/dist/finder.js ADDED
@@ -0,0 +1,55 @@
1
+ import { promises as fs } from 'fs';
2
+ import { basename, extname, join } from 'path';
3
+ import { nonNullable } from './utils/non_nullable.js';
4
+ const ALLOWED_EXTENSIONS = new Set(['.js', '.ts']);
5
+ const findFunctionInDirectory = async (directory) => {
6
+ const name = basename(directory);
7
+ const candidatePaths = [`${name}.js`, `index.js`, `${name}.ts`, `index.ts`].map((filename) => join(directory, filename));
8
+ let functionPath;
9
+ for (const candidatePath of candidatePaths) {
10
+ try {
11
+ const stats = await fs.stat(candidatePath);
12
+ // eslint-disable-next-line max-depth
13
+ if (stats.isFile()) {
14
+ functionPath = candidatePath;
15
+ break;
16
+ }
17
+ }
18
+ catch {
19
+ // no-op
20
+ }
21
+ }
22
+ if (functionPath === undefined) {
23
+ return;
24
+ }
25
+ return {
26
+ name,
27
+ path: functionPath,
28
+ };
29
+ };
30
+ const findFunctionInPath = async (path) => {
31
+ const stats = await fs.stat(path);
32
+ if (stats.isDirectory()) {
33
+ return findFunctionInDirectory(path);
34
+ }
35
+ const extension = extname(path);
36
+ if (ALLOWED_EXTENSIONS.has(extension)) {
37
+ return { name: basename(path, extension), path };
38
+ }
39
+ };
40
+ const findFunctionsInDirectory = async (baseDirectory) => {
41
+ let items = [];
42
+ try {
43
+ items = await fs.readdir(baseDirectory);
44
+ }
45
+ catch {
46
+ // no-op
47
+ }
48
+ const functions = await Promise.all(items.map((item) => findFunctionInPath(join(baseDirectory, item))));
49
+ return functions.filter(nonNullable);
50
+ };
51
+ const findFunctions = async (directories) => {
52
+ const functions = await Promise.all(directories.map(findFunctionsInDirectory));
53
+ return functions.flat();
54
+ };
55
+ export { findFunctions };
@@ -0,0 +1,13 @@
1
+ import { DenoBridge } from '../bridge.js';
2
+ import type { Bundle } from '../bundle.js';
3
+ import { EdgeFunction } from '../edge_function.js';
4
+ interface BundleESZIPOptions {
5
+ basePath: string;
6
+ buildID: string;
7
+ debug?: boolean;
8
+ deno: DenoBridge;
9
+ distDirectory: string;
10
+ functions: EdgeFunction[];
11
+ }
12
+ declare const bundle: (options: BundleESZIPOptions) => Promise<Bundle>;
13
+ export { bundle };
@@ -0,0 +1,36 @@
1
+ import { join, resolve } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { wrapBundleError } from '../bundle_error.js';
4
+ import { getFileHash } from '../utils/sha256.js';
5
+ const bundle = async (options) => {
6
+ try {
7
+ return await bundleESZIP(options);
8
+ }
9
+ catch (error) {
10
+ throw wrapBundleError(error, { format: 'eszip' });
11
+ }
12
+ };
13
+ const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, functions, }) => {
14
+ const extension = '.eszip';
15
+ const destPath = join(distDirectory, `${buildID}${extension}`);
16
+ const bundler = getESZIPBundler();
17
+ const payload = {
18
+ basePath,
19
+ destPath,
20
+ functions,
21
+ };
22
+ const flags = ['--allow-all'];
23
+ if (!debug) {
24
+ flags.push('--quiet');
25
+ }
26
+ await deno.run(['run', ...flags, bundler, JSON.stringify(payload)], { pipeOutput: true });
27
+ const hash = await getFileHash(destPath);
28
+ return { extension, format: 'eszip2', hash };
29
+ };
30
+ const getESZIPBundler = () => {
31
+ const url = new URL(import.meta.url);
32
+ const pathname = fileURLToPath(url);
33
+ const bundlerPath = resolve(pathname, '../../../deno/bundle.ts');
34
+ return bundlerPath;
35
+ };
36
+ export { bundle };
@@ -0,0 +1,25 @@
1
+ import { DenoBridge } from '../bridge.js';
2
+ import type { Bundle } from '../bundle.js';
3
+ import { EdgeFunction } from '../edge_function.js';
4
+ import { ImportMap } from '../import_map.js';
5
+ import type { FormatFunction } from '../server/server.js';
6
+ interface BundleJSOptions {
7
+ buildID: string;
8
+ debug?: boolean;
9
+ deno: DenoBridge;
10
+ distDirectory: string;
11
+ functions: EdgeFunction[];
12
+ importMap: ImportMap;
13
+ }
14
+ declare const bundle: (options: BundleJSOptions) => Promise<Bundle>;
15
+ interface GenerateStage2Options {
16
+ distDirectory: string;
17
+ fileName: string;
18
+ formatExportTypeError?: FormatFunction;
19
+ formatImportError?: FormatFunction;
20
+ functions: EdgeFunction[];
21
+ type?: 'local' | 'production';
22
+ }
23
+ declare const generateStage2: ({ distDirectory, fileName, formatExportTypeError, formatImportError, functions, type, }: GenerateStage2Options) => Promise<string>;
24
+ declare const getBootstrapURL: () => string;
25
+ export { bundle, generateStage2, getBootstrapURL };
@@ -0,0 +1,83 @@
1
+ import { promises as fs } from 'fs';
2
+ import { join } from 'path';
3
+ import { env } from 'process';
4
+ import { pathToFileURL } from 'url';
5
+ import del from 'del';
6
+ import { wrapBundleError } from '../bundle_error.js';
7
+ import { getFileHash } from '../utils/sha256.js';
8
+ const BOOTSTRAP_LATEST = 'https://625d32be1b90870009edfc99--edge-bootstrap.netlify.app/bootstrap/index-combined.ts';
9
+ const bundle = async (options) => {
10
+ try {
11
+ return await bundleJS(options);
12
+ }
13
+ catch (error) {
14
+ throw wrapBundleError(error, { format: 'javascript' });
15
+ }
16
+ };
17
+ const bundleJS = async ({ buildID, debug, deno, distDirectory, functions, importMap, }) => {
18
+ const stage2Path = await generateStage2({ distDirectory, functions, fileName: `${buildID}-pre.js` });
19
+ const extension = '.js';
20
+ const jsBundlePath = join(distDirectory, `${buildID}${extension}`);
21
+ const flags = [`--import-map=${importMap.toDataURL()}`];
22
+ if (!debug) {
23
+ flags.push('--quiet');
24
+ }
25
+ await deno.run(['bundle', ...flags, stage2Path, jsBundlePath], { pipeOutput: true });
26
+ await fs.unlink(stage2Path);
27
+ const hash = await getFileHash(jsBundlePath);
28
+ return { extension, format: 'js', hash };
29
+ };
30
+ const defaultFormatExportTypeError = (name) => `The Edge Function "${name}" has failed to load. Does it have a function as the default export?`;
31
+ const defaultFormatImpoortError = (name) => `There was an error with Edge Function "${name}".`;
32
+ const generateStage2 = async ({ distDirectory, fileName, formatExportTypeError, formatImportError, functions, type = 'production', }) => {
33
+ await del(distDirectory, { force: true });
34
+ await fs.mkdir(distDirectory, { recursive: true });
35
+ const entryPoint = type === 'local'
36
+ ? getLocalEntryPoint(functions, { formatExportTypeError, formatImportError })
37
+ : getProductionEntryPoint(functions);
38
+ const stage2Path = join(distDirectory, fileName);
39
+ await fs.writeFile(stage2Path, entryPoint);
40
+ return stage2Path;
41
+ };
42
+ const getBootstrapURL = () => { var _a; return (_a = env.NETLIFY_EDGE_BOOTSTRAP) !== null && _a !== void 0 ? _a : BOOTSTRAP_LATEST; };
43
+ // For the local development environment, we import the user functions with
44
+ // dynamic imports to gracefully handle the case where the file doesn't have
45
+ // a valid default export.
46
+ const getLocalEntryPoint = (functions, { formatExportTypeError = defaultFormatExportTypeError, formatImportError = defaultFormatImpoortError, }) => {
47
+ const bootImport = `import { boot } from "${getBootstrapURL()}";`;
48
+ const declaration = `const functions = {};`;
49
+ const imports = functions.map((func) => `
50
+ try {
51
+ const { default: func } = await import("${pathToFileURL(func.path)}");
52
+
53
+ if (typeof func === "function") {
54
+ functions["${func.name}"] = func;
55
+ } else {
56
+ console.log(${JSON.stringify(formatExportTypeError(func.name))});
57
+ }
58
+ } catch (error) {
59
+ console.log(${JSON.stringify(formatImportError(func.name))});
60
+ console.error(error);
61
+ }
62
+ `);
63
+ const bootCall = `boot(functions);`;
64
+ return [bootImport, declaration, ...imports, bootCall].join('\n\n');
65
+ };
66
+ const getProductionEntryPoint = (functions) => {
67
+ const bootImport = `import { boot } from "${getBootstrapURL()}";`;
68
+ const lines = functions.map((func, index) => {
69
+ const importName = `func${index}`;
70
+ const exportLine = `"${func.name}": ${importName}`;
71
+ const url = pathToFileURL(func.path);
72
+ return {
73
+ exportLine,
74
+ importLine: `import ${importName} from "${url}";`,
75
+ };
76
+ });
77
+ const importLines = lines.map(({ importLine }) => importLine).join('\n');
78
+ const exportLines = lines.map(({ exportLine }) => exportLine).join(', ');
79
+ const exportDeclaration = `const functions = {${exportLines}};`;
80
+ const defaultExport = 'boot(functions);';
81
+ return [bootImport, importLines, exportDeclaration, defaultExport].join('\n\n');
82
+ };
83
+ export { bundle, generateStage2, getBootstrapURL };
@@ -0,0 +1,2 @@
1
+ declare const getPathInHome: (path: string) => string;
2
+ export { getPathInHome };
@@ -0,0 +1,5 @@
1
+ import { join } from 'path';
2
+ import envPaths from 'env-paths';
3
+ const OSBasedPaths = envPaths('netlify', { suffix: '' });
4
+ const getPathInHome = (path) => join(OSBasedPaths.config, path);
5
+ export { getPathInHome };
@@ -0,0 +1,13 @@
1
+ interface ImportMapFile {
2
+ imports: Record<string, string>;
3
+ scopes?: Record<string, string>;
4
+ }
5
+ declare class ImportMap {
6
+ imports: Record<string, string>;
7
+ constructor(input?: ImportMapFile[]);
8
+ getContents(): string;
9
+ toDataURL(): string;
10
+ writeToFile(path: string): Promise<void>;
11
+ }
12
+ export { ImportMap };
13
+ export type { ImportMapFile };
@@ -0,0 +1,30 @@
1
+ import { Buffer } from 'buffer';
2
+ import { promises as fs } from 'fs';
3
+ import { dirname } from 'path';
4
+ const DEFAULT_IMPORTS = {
5
+ 'netlify:edge': 'https://edge-bootstrap.netlify.app/v1/index.ts',
6
+ };
7
+ class ImportMap {
8
+ constructor(input = []) {
9
+ const inputImports = input.reduce((acc, { imports }) => ({ ...acc, ...imports }), {});
10
+ // `DEFAULT_IMPORTS` must come last because we want our internal imports to
11
+ // take precedence.
12
+ this.imports = { ...inputImports, ...DEFAULT_IMPORTS };
13
+ }
14
+ getContents() {
15
+ const contents = {
16
+ imports: this.imports,
17
+ };
18
+ return JSON.stringify(contents);
19
+ }
20
+ toDataURL() {
21
+ const encodedImportMap = Buffer.from(this.getContents()).toString('base64');
22
+ return `data:application/json;base64,${encodedImportMap}`;
23
+ }
24
+ async writeToFile(path) {
25
+ await fs.mkdir(dirname(path), { recursive: true });
26
+ const contents = this.getContents();
27
+ await fs.writeFile(path, contents);
28
+ }
29
+ }
30
+ export { ImportMap };
@@ -0,0 +1,5 @@
1
+ export { bundle } from './bundler.js';
2
+ export { DenoBridge } from './bridge.js';
3
+ export { findFunctions as find } from './finder.js';
4
+ export { generateManifest } from './manifest.js';
5
+ export { serve } from './server/server.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { bundle } from './bundler.js';
2
+ export { DenoBridge } from './bridge.js';
3
+ export { findFunctions as find } from './finder.js';
4
+ export { generateManifest } from './manifest.js';
5
+ export { serve } from './server/server.js';
@@ -0,0 +1,28 @@
1
+ import type { Bundle } from './bundle.js';
2
+ import type { Declaration } from './declaration.js';
3
+ import { EdgeFunction } from './edge_function.js';
4
+ interface GenerateManifestOptions {
5
+ bundles?: Bundle[];
6
+ functions: EdgeFunction[];
7
+ declarations?: Declaration[];
8
+ }
9
+ interface Manifest {
10
+ bundler_version: string;
11
+ bundles: {
12
+ asset: string;
13
+ format: string;
14
+ }[];
15
+ routes: {
16
+ function: string;
17
+ pattern: string;
18
+ }[];
19
+ }
20
+ declare const generateManifest: ({ bundles, declarations, functions }: GenerateManifestOptions) => Manifest;
21
+ interface WriteManifestOptions {
22
+ bundles: Bundle[];
23
+ declarations: Declaration[];
24
+ distDirectory: string;
25
+ functions: EdgeFunction[];
26
+ }
27
+ declare const writeManifest: ({ bundles, declarations, distDirectory, functions }: WriteManifestOptions) => Promise<void>;
28
+ export { generateManifest, Manifest, writeManifest };
@@ -0,0 +1,35 @@
1
+ import { promises as fs } from 'fs';
2
+ import { join } from 'path';
3
+ import globToRegExp from 'glob-to-regexp';
4
+ import { getPackageVersion } from './package_json.js';
5
+ import { nonNullable } from './utils/non_nullable.js';
6
+ const generateManifest = ({ bundles = [], declarations = [], functions }) => {
7
+ const routes = declarations.map((declaration) => {
8
+ const func = functions.find(({ name }) => declaration.function === name);
9
+ if (func === undefined) {
10
+ return;
11
+ }
12
+ const pattern = 'pattern' in declaration ? new RegExp(declaration.pattern) : globToRegExp(declaration.path);
13
+ const serializablePattern = pattern.source.replace(/\\\//g, '/');
14
+ return {
15
+ function: func.name,
16
+ pattern: serializablePattern,
17
+ };
18
+ });
19
+ const manifestBundles = bundles.map(({ extension, format, hash }) => ({
20
+ asset: hash + extension,
21
+ format,
22
+ }));
23
+ const manifest = {
24
+ bundles: manifestBundles,
25
+ routes: routes.filter(nonNullable),
26
+ bundler_version: getPackageVersion(),
27
+ };
28
+ return manifest;
29
+ };
30
+ const writeManifest = ({ bundles, declarations = [], distDirectory, functions }) => {
31
+ const manifest = generateManifest({ bundles, declarations, functions });
32
+ const manifestPath = join(distDirectory, 'manifest.json');
33
+ return fs.writeFile(manifestPath, JSON.stringify(manifest));
34
+ };
35
+ export { generateManifest, writeManifest };
@@ -0,0 +1,2 @@
1
+ declare const getPackageVersion: () => string;
2
+ export { getPackageVersion };
@@ -0,0 +1,5 @@
1
+ import { createRequire } from 'module';
2
+ const require = createRequire(import.meta.url);
3
+ const pkgJson = require('../package.json');
4
+ const getPackageVersion = () => pkgJson.version;
5
+ export { getPackageVersion };
@@ -0,0 +1,3 @@
1
+ declare const getBinaryExtension: () => ".exe" | "";
2
+ declare const getPlatformTarget: () => "x86_64-pc-windows-msvc" | "aarch64-apple-darwin" | "x86_64-apple-darwin" | "x86_64-unknown-linux-gnu";
3
+ export { getBinaryExtension, getPlatformTarget };
@@ -0,0 +1,13 @@
1
+ import { arch, platform } from 'process';
2
+ const getBinaryExtension = () => (platform === 'win32' ? '.exe' : '');
3
+ const getPlatformTarget = () => {
4
+ if (platform === 'win32') {
5
+ return 'x86_64-pc-windows-msvc';
6
+ }
7
+ const isArm64 = arch === 'arm64';
8
+ if (platform === 'darwin') {
9
+ return isArm64 ? 'aarch64-apple-darwin' : 'x86_64-apple-darwin';
10
+ }
11
+ return 'x86_64-unknown-linux-gnu';
12
+ };
13
+ export { getBinaryExtension, getPlatformTarget };
@@ -0,0 +1,20 @@
1
+ import { LifecycleHook } from '../bridge.js';
2
+ import type { EdgeFunction } from '../edge_function.js';
3
+ import { ImportMapFile } from '../import_map.js';
4
+ declare type FormatFunction = (name: string) => string;
5
+ interface ServeOptions {
6
+ debug?: boolean;
7
+ distImportMapPath?: string;
8
+ importMaps?: ImportMapFile[];
9
+ onAfterDownload?: LifecycleHook;
10
+ onBeforeDownload?: LifecycleHook;
11
+ formatExportTypeError?: FormatFunction;
12
+ formatImportError?: FormatFunction;
13
+ port: number;
14
+ }
15
+ declare const serve: ({ debug, distImportMapPath, formatExportTypeError, formatImportError, importMaps, onAfterDownload, onBeforeDownload, port, }: ServeOptions) => Promise<(newFunctions: EdgeFunction[]) => Promise<{
16
+ graph: any;
17
+ success: boolean;
18
+ }>>;
19
+ export { serve };
20
+ export type { FormatFunction };
@@ -0,0 +1,68 @@
1
+ import { tmpName } from 'tmp-promise';
2
+ import { DenoBridge } from '../bridge.js';
3
+ import { generateStage2 } from '../formats/javascript.js';
4
+ import { ImportMap } from '../import_map.js';
5
+ import { killProcess, waitForServer } from './util.js';
6
+ const prepareServer = ({ deno, distDirectory, flags, formatExportTypeError, formatImportError, port, }) => {
7
+ const processRef = {};
8
+ const startIsolate = async (newFunctions) => {
9
+ if ((processRef === null || processRef === void 0 ? void 0 : processRef.ps) !== undefined) {
10
+ await killProcess(processRef.ps);
11
+ }
12
+ let graph;
13
+ const stage2Path = await generateStage2({
14
+ distDirectory,
15
+ fileName: 'dev.js',
16
+ functions: newFunctions,
17
+ formatExportTypeError,
18
+ formatImportError,
19
+ type: 'local',
20
+ });
21
+ try {
22
+ // This command will print a JSON object with all the modules found in
23
+ // the `stage2Path` file as well as all of their dependencies.
24
+ // Consumers such as the CLI can use this information to watch all the
25
+ // relevant files and issue an isolate restart when one of them changes.
26
+ const { stdout } = await deno.run(['info', '--json', stage2Path]);
27
+ graph = JSON.parse(stdout);
28
+ }
29
+ catch {
30
+ // no-op
31
+ }
32
+ await deno.runInBackground(['run', ...flags, stage2Path, port.toString()], true, processRef);
33
+ const success = await waitForServer(port, processRef.ps);
34
+ return {
35
+ graph,
36
+ success,
37
+ };
38
+ };
39
+ return startIsolate;
40
+ };
41
+ const serve = async ({ debug, distImportMapPath, formatExportTypeError, formatImportError, importMaps, onAfterDownload, onBeforeDownload, port, }) => {
42
+ const deno = new DenoBridge({
43
+ debug,
44
+ onAfterDownload,
45
+ onBeforeDownload,
46
+ });
47
+ // We need to generate a stage 2 file and write it somewhere. We use a
48
+ // temporary directory for that.
49
+ const distDirectory = await tmpName();
50
+ // Wait for the binary to be downloaded if needed.
51
+ await deno.getBinaryPath();
52
+ // Creating an ImportMap instance with any import maps supplied by the user,
53
+ // if any.
54
+ const importMap = new ImportMap(importMaps);
55
+ const flags = ['--allow-all', '--unstable', `--import-map=${importMap.toDataURL()}`];
56
+ if (debug) {
57
+ flags.push('--log-level=debug');
58
+ }
59
+ else {
60
+ flags.push('--quiet');
61
+ }
62
+ const server = await prepareServer({ deno, distDirectory, flags, formatExportTypeError, formatImportError, port });
63
+ if (distImportMapPath) {
64
+ await importMap.writeToFile(distImportMapPath);
65
+ }
66
+ return server;
67
+ };
68
+ export { serve };
@@ -0,0 +1,4 @@
1
+ import { ExecaChildProcess } from 'execa';
2
+ declare const killProcess: (ps: ExecaChildProcess<string>) => Promise<unknown> | undefined;
3
+ declare const waitForServer: (port: number, ps?: ExecaChildProcess<string> | undefined) => Promise<boolean>;
4
+ export { killProcess, waitForServer };
@@ -0,0 +1,48 @@
1
+ import fetch from 'node-fetch';
2
+ import waitFor from 'p-wait-for';
3
+ // 1 second
4
+ const SERVER_KILL_TIMEOUT = 1e3;
5
+ // 1 second
6
+ const SERVER_POLL_INTERNAL = 1e3;
7
+ // 10 seconds
8
+ const SERVER_POLL_TIMEOUT = 1e4;
9
+ const isServerReady = async (port, successRef, ps) => {
10
+ // If the process has been killed or if it exited with an error, we return
11
+ // early with `success: false`.
12
+ if ((ps === null || ps === void 0 ? void 0 : ps.killed) || ((ps === null || ps === void 0 ? void 0 : ps.exitCode) && ps.exitCode > 0)) {
13
+ return true;
14
+ }
15
+ try {
16
+ await fetch(`http://127.0.0.1:${port}`);
17
+ // eslint-disable-next-line no-param-reassign
18
+ successRef.success = true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ return true;
24
+ };
25
+ const killProcess = (ps) => {
26
+ // If the process is no longer running, there's nothing left to do.
27
+ if ((ps === null || ps === void 0 ? void 0 : ps.exitCode) !== null) {
28
+ return;
29
+ }
30
+ return new Promise((resolve, reject) => {
31
+ ps.on('close', resolve);
32
+ ps.on('error', reject);
33
+ ps.kill('SIGTERM', {
34
+ forceKillAfterTimeout: SERVER_KILL_TIMEOUT,
35
+ });
36
+ });
37
+ };
38
+ const waitForServer = async (port, ps) => {
39
+ const successRef = {
40
+ success: false,
41
+ };
42
+ await waitFor(() => isServerReady(port, successRef, ps), {
43
+ interval: SERVER_POLL_INTERNAL,
44
+ timeout: SERVER_POLL_TIMEOUT,
45
+ });
46
+ return successRef.success;
47
+ };
48
+ export { killProcess, waitForServer };
@@ -0,0 +1,2 @@
1
+ declare const nonNullable: <T>(value: T) => value is NonNullable<T>;
2
+ export { nonNullable };
@@ -0,0 +1,2 @@
1
+ const nonNullable = (value) => value !== null && value !== undefined;
2
+ export { nonNullable };
@@ -0,0 +1,2 @@
1
+ declare const getFileHash: (path: string) => Promise<string>;
2
+ export { getFileHash };
@@ -0,0 +1,16 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ const getFileHash = (path) => {
4
+ const hash = crypto.createHash('sha256');
5
+ hash.setEncoding('hex');
6
+ return new Promise((resolve, reject) => {
7
+ const file = fs.createReadStream(path);
8
+ file.on('end', () => {
9
+ hash.end();
10
+ resolve(hash.read());
11
+ });
12
+ file.on('error', reject);
13
+ file.pipe(hash);
14
+ });
15
+ };
16
+ export { getFileHash };
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@netlify/edge-bundler",
3
+ "version": "0.11.0",
4
+ "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": "./dist/index.js",
8
+ "files": [
9
+ "deno/**",
10
+ "dist/**/*.js",
11
+ "dist/**/*.d.ts"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/",
16
+ "prepublishOnly": "npm ci && npm test",
17
+ "prepack": "npm run build",
18
+ "test": "run-s build format test:dev",
19
+ "format": "run-s build format:check-fix:*",
20
+ "format:ci": "run-s build format:check:*",
21
+ "format:check-fix:lint": "run-e format:check:lint format:fix:lint",
22
+ "format:check:lint": "cross-env-shell eslint $npm_package_config_eslint",
23
+ "format:fix:lint": "cross-env-shell eslint --fix $npm_package_config_eslint",
24
+ "format:check-fix:prettier": "run-e format:check:prettier format:fix:prettier",
25
+ "format:check:prettier": "cross-env-shell prettier --check $npm_package_config_prettier",
26
+ "format:fix:prettier": "cross-env-shell prettier --write $npm_package_config_prettier",
27
+ "test:dev": "run-s build test:dev:*",
28
+ "test:ci": "run-s build test:ci:*",
29
+ "test:dev:ava": "ava",
30
+ "test:ci:ava": "nyc -r lcovonly -r text -r json ava"
31
+ },
32
+ "config": {
33
+ "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,scripts,.github}/**/*.{js,ts,md,html}\" \"*.{js,ts,md,html}\"",
34
+ "prettier": "--ignore-path .gitignore --loglevel=warn \"{src,scripts,.github}/**/*.{js,ts,md,yml,json,html}\" \"*.{js,ts,yml,json,html}\" \".*.{js,ts,yml,json,html}\" \"!**/package-lock.json\" \"!package-lock.json\""
35
+ },
36
+ "ava": {
37
+ "files": [
38
+ "test/**/*.ts",
39
+ "!test/fixtures/**"
40
+ ],
41
+ "extensions": {
42
+ "ts": "module"
43
+ },
44
+ "nodeArguments": [
45
+ "--loader=ts-node/esm",
46
+ "--no-warnings"
47
+ ]
48
+ },
49
+ "keywords": [],
50
+ "license": "MIT",
51
+ "repository": "netlify-labs/edge-bundler",
52
+ "bugs": {
53
+ "url": "https://github.com/netlify-labs/edge-bundler/issues"
54
+ },
55
+ "author": "Netlify Inc.",
56
+ "directories": {
57
+ "test": "test"
58
+ },
59
+ "devDependencies": {
60
+ "@ava/typescript": "^3.0.1",
61
+ "@commitlint/cli": "^16.0.0",
62
+ "@commitlint/config-conventional": "^16.0.0",
63
+ "@netlify/eslint-config-node": "^4.1.7",
64
+ "@types/glob-to-regexp": "^0.4.1",
65
+ "@types/node": "^17.0.21",
66
+ "@types/semver": "^7.3.9",
67
+ "@types/sinon": "^10.0.8",
68
+ "@types/uuid": "^8.3.4",
69
+ "ava": "^4.0.1",
70
+ "husky": "^7.0.4",
71
+ "nyc": "^15.0.0",
72
+ "sinon": "^12.0.1",
73
+ "ts-node": "^10.4.0",
74
+ "typescript": "^4.5.4"
75
+ },
76
+ "engines": {
77
+ "node": "^12.20.0 || ^14.14.0 || >=16.0.0"
78
+ },
79
+ "dependencies": {
80
+ "common-path-prefix": "^3.0.0",
81
+ "del": "^6.0.0",
82
+ "env-paths": "^3.0.0",
83
+ "execa": "^6.0.0",
84
+ "glob-to-regexp": "^0.4.1",
85
+ "node-fetch": "^3.1.1",
86
+ "node-stream-zip": "^1.15.0",
87
+ "p-wait-for": "^4.1.0",
88
+ "semver": "^7.3.5",
89
+ "tmp-promise": "^3.0.3",
90
+ "uuid": "^8.3.2"
91
+ }
92
+ }