@netlify/edge-bundler 14.5.5 → 14.6.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/extract.ts +7 -0
- package/deno/lib/stage2.ts +1 -3
- package/deno/vendor/deno.land/x/eszip@v0.55.2/eszip.ts +189 -0
- package/dist/node/bridge.d.ts +1 -1
- package/dist/node/bridge.js +24 -20
- package/dist/node/bridge.test.js +7 -11
- package/dist/node/bundle_error.d.ts +5 -4
- package/dist/node/bundle_error.js +5 -5
- package/dist/node/bundler.js +57 -19
- package/dist/node/bundler.test.js +12 -17
- package/dist/node/config.d.ts +3 -4
- package/dist/node/config.js +17 -17
- package/dist/node/config.test.js +4 -16
- package/dist/node/declaration.js +2 -3
- package/dist/node/deploy_config.js +2 -3
- package/dist/node/downloader.js +1 -2
- package/dist/node/formats/eszip.d.ts +7 -2
- package/dist/node/formats/eszip.js +14 -3
- package/dist/node/formats/javascript.js +1 -1
- package/dist/node/formats/tarball.js +1 -0
- package/dist/node/import_map.js +5 -0
- package/dist/node/logger.js +2 -2
- package/dist/node/main.test.js +3 -4
- package/dist/node/manifest.js +2 -3
- package/dist/node/manifest.test.js +2 -3
- package/dist/node/npm_dependencies.js +5 -7
- package/dist/node/server/server.js +3 -4
- package/dist/node/server/server.test.js +3 -3
- package/dist/node/server/util.js +2 -2
- package/dist/node/stage_2.test.js +9 -1
- package/dist/node/types.js +1 -1
- package/dist/node/utils/urlpattern.d.ts +1 -4
- package/dist/node/utils/urlpattern.js +4 -2
- package/dist/node/validation/manifest/error.js +3 -3
- package/dist/test/util.js +5 -8
- package/package.json +3 -3
package/deno/extract.ts
ADDED
package/deno/lib/stage2.ts
CHANGED
|
@@ -105,7 +105,7 @@ const stage2Loader = (
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const writeStage2 = async ({
|
|
108
|
+
export const writeStage2 = async ({
|
|
109
109
|
basePath,
|
|
110
110
|
destPath,
|
|
111
111
|
externals,
|
|
@@ -122,5 +122,3 @@ const writeStage2 = async ({
|
|
|
122
122
|
|
|
123
123
|
return await Deno.writeFile(destPath, bytes)
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
export { writeStage2 }
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// CLI utility to build/list/extract/run ESZIPs
|
|
2
|
+
|
|
3
|
+
import { build, Parser } from "./mod.ts";
|
|
4
|
+
import { dirname, join } from "https://deno.land/std@0.177.0/path/mod.ts";
|
|
5
|
+
|
|
6
|
+
function hasV2Header(bytes: Uint8Array) {
|
|
7
|
+
const magicV2 = new TextDecoder().decode(bytes.slice(0, 8));
|
|
8
|
+
return magicV2 === "ESZIP_V2";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ESZIP {
|
|
12
|
+
extract(dest: string): Promise<void>;
|
|
13
|
+
list(): string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface V1Entry {
|
|
17
|
+
Source: {
|
|
18
|
+
source: string;
|
|
19
|
+
transpiled: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class V1 {
|
|
24
|
+
inner: Record<string, V1Entry>;
|
|
25
|
+
|
|
26
|
+
constructor(bytes: Uint8Array) {
|
|
27
|
+
const json = new TextDecoder().decode(bytes);
|
|
28
|
+
const eszip = JSON.parse(json);
|
|
29
|
+
this.inner = eszip;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static load(bytes: Uint8Array) {
|
|
33
|
+
return Promise.resolve(new V1(bytes));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
*entries() {
|
|
37
|
+
for (
|
|
38
|
+
const [
|
|
39
|
+
url,
|
|
40
|
+
{
|
|
41
|
+
Source: { source, transpiled },
|
|
42
|
+
},
|
|
43
|
+
] of Object.entries(this.inner.modules)
|
|
44
|
+
) {
|
|
45
|
+
yield { url, source, transpiled };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async extract(dest: string) {
|
|
50
|
+
for (const { url, source, transpiled } of this.entries()) {
|
|
51
|
+
await write(join(dest, "source", url2path(url)), source);
|
|
52
|
+
await write(
|
|
53
|
+
join(dest, "transpiled", url2path(url)),
|
|
54
|
+
transpiled ?? source,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
list() {
|
|
60
|
+
return Array.from(this.entries()).map((e) => e.url);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class V2 {
|
|
65
|
+
parser: Parser;
|
|
66
|
+
specifiers: string[];
|
|
67
|
+
|
|
68
|
+
constructor(parser: Parser, specifiers: string[]) {
|
|
69
|
+
this.parser = parser;
|
|
70
|
+
this.specifiers = specifiers;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static async load(bytes: Uint8Array) {
|
|
74
|
+
const parser = await Parser.createInstance();
|
|
75
|
+
const specifiers = await parser.parseBytes(bytes);
|
|
76
|
+
await parser.load();
|
|
77
|
+
return new V2(parser, specifiers as string[]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async extract(dest: string) {
|
|
81
|
+
const imports: Record<string, string> = {};
|
|
82
|
+
|
|
83
|
+
for (const specifier of this.specifiers) {
|
|
84
|
+
const module = await this.parser.getModuleSource(specifier);
|
|
85
|
+
await write(join(dest, "source", url2path(specifier)), module);
|
|
86
|
+
// Track import
|
|
87
|
+
imports[specifier] = `./${url2path(specifier)}`;
|
|
88
|
+
}
|
|
89
|
+
// Write import map
|
|
90
|
+
const importMap = JSON.stringify({ imports }, null, 2);
|
|
91
|
+
await Deno.writeTextFile(
|
|
92
|
+
join(dest, "source", "import_map.json"),
|
|
93
|
+
importMap,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
list() {
|
|
98
|
+
return this.specifiers;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function loadESZIP(filename: string): Promise<ESZIP> {
|
|
103
|
+
const bytes = await Deno.readFile(filename);
|
|
104
|
+
if (hasV2Header(bytes)) {
|
|
105
|
+
return await V2.load(bytes);
|
|
106
|
+
}
|
|
107
|
+
return await V1.load(bytes);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function url2path(url: string) {
|
|
111
|
+
return join(...(new URL(url).pathname.split("/").filter(Boolean)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function write(path: string, content: string) {
|
|
115
|
+
await Deno.mkdir(dirname(path), { recursive: true });
|
|
116
|
+
await Deno.writeTextFile(path, content);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function run(eszip: ESZIP, specifier: string) {
|
|
120
|
+
// Extract to tmp directory
|
|
121
|
+
const tmpDir = await Deno.makeTempDir({ prefix: "esz" });
|
|
122
|
+
try {
|
|
123
|
+
// Extract
|
|
124
|
+
await eszip.extract(tmpDir);
|
|
125
|
+
const importMap = join(tmpDir, "source", "import_map.json");
|
|
126
|
+
// Run
|
|
127
|
+
const p = new Deno.Command("deno", {
|
|
128
|
+
args: [
|
|
129
|
+
"run",
|
|
130
|
+
"-A",
|
|
131
|
+
"--no-check",
|
|
132
|
+
"--import-map",
|
|
133
|
+
importMap,
|
|
134
|
+
specifier,
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
await p.output();
|
|
138
|
+
} finally {
|
|
139
|
+
// Cleanup
|
|
140
|
+
await Deno.remove(tmpDir, { recursive: true });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Main
|
|
145
|
+
async function main() {
|
|
146
|
+
const args = Deno.args;
|
|
147
|
+
const [subcmd, filename, ...rest] = args;
|
|
148
|
+
|
|
149
|
+
if (subcmd === "help") {
|
|
150
|
+
return console.log("TODO");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
switch (subcmd) {
|
|
154
|
+
case "build":
|
|
155
|
+
case "b": {
|
|
156
|
+
const eszip = await build([filename]);
|
|
157
|
+
let out = rest[0];
|
|
158
|
+
if (!out) {
|
|
159
|
+
// Create outfile name from url filename
|
|
160
|
+
out = new URL(filename).pathname.split("/").pop() || "out";
|
|
161
|
+
}
|
|
162
|
+
console.log(`${out}.eszip: ${eszip.length} bytes`);
|
|
163
|
+
await Deno.writeFile(`${out}.eszip`, eszip);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
case "x":
|
|
167
|
+
case "extract": {
|
|
168
|
+
const eszip = await loadESZIP(filename);
|
|
169
|
+
return await eszip.extract(rest[0] ?? Deno.cwd());
|
|
170
|
+
}
|
|
171
|
+
case "l":
|
|
172
|
+
case "ls":
|
|
173
|
+
case "list": {
|
|
174
|
+
const eszip = await loadESZIP(filename);
|
|
175
|
+
return console.log(eszip.list().join("\n"));
|
|
176
|
+
}
|
|
177
|
+
case "r":
|
|
178
|
+
case "run": {
|
|
179
|
+
const eszip = await loadESZIP(filename);
|
|
180
|
+
const specifier = rest[0];
|
|
181
|
+
if (!specifier) {
|
|
182
|
+
return console.error("Please provide a specifier to run");
|
|
183
|
+
}
|
|
184
|
+
return await run(eszip, specifier);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await main();
|
package/dist/node/bridge.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type WriteStream } from 'fs';
|
|
|
2
2
|
import { type ExecaChildProcess } from 'execa';
|
|
3
3
|
import { FeatureFlags } from './feature_flags.js';
|
|
4
4
|
import { Logger } from './logger.js';
|
|
5
|
-
export declare const DENO_VERSION_RANGE = "
|
|
5
|
+
export declare const DENO_VERSION_RANGE = "^2.4.2";
|
|
6
6
|
export type OnBeforeDownloadHook = () => void | Promise<void>;
|
|
7
7
|
export type OnAfterDownloadHook = (error?: Error) => void | Promise<void>;
|
|
8
8
|
export interface DenoOptions {
|
package/dist/node/bridge.js
CHANGED
|
@@ -12,24 +12,29 @@ const DENO_VERSION_FILE = 'version.txt';
|
|
|
12
12
|
// When updating DENO_VERSION_RANGE, ensure that the deno version
|
|
13
13
|
// on the netlify/buildbot build image satisfies this range!
|
|
14
14
|
// https://github.com/netlify/buildbot/blob/f9c03c9dcb091d6570e9d0778381560d469e78ad/build-image/noble/Dockerfile#L410
|
|
15
|
-
export const DENO_VERSION_RANGE = '
|
|
16
|
-
const NEXT_DENO_VERSION_RANGE = '^2.4.2';
|
|
15
|
+
export const DENO_VERSION_RANGE = '^2.4.2';
|
|
17
16
|
export class DenoBridge {
|
|
17
|
+
cacheDirectory;
|
|
18
|
+
currentDownload;
|
|
19
|
+
debug;
|
|
20
|
+
denoDir;
|
|
21
|
+
logger;
|
|
22
|
+
onAfterDownload;
|
|
23
|
+
onBeforeDownload;
|
|
24
|
+
useGlobal;
|
|
25
|
+
versionRange;
|
|
18
26
|
constructor(options) {
|
|
19
|
-
|
|
20
|
-
this.
|
|
21
|
-
this.debug = (_b = options.debug) !== null && _b !== void 0 ? _b : false;
|
|
27
|
+
this.cacheDirectory = options.cacheDirectory ?? getPathInHome('deno-cli');
|
|
28
|
+
this.debug = options.debug ?? false;
|
|
22
29
|
this.denoDir = options.denoDir;
|
|
23
|
-
this.logger =
|
|
30
|
+
this.logger = options.logger ?? getLogger(undefined, undefined, options.debug);
|
|
24
31
|
this.onAfterDownload = options.onAfterDownload;
|
|
25
32
|
this.onBeforeDownload = options.onBeforeDownload;
|
|
26
|
-
this.useGlobal =
|
|
27
|
-
this.versionRange =
|
|
28
|
-
(_e = options.versionRange) !== null && _e !== void 0 ? _e : (((_f = options.featureFlags) === null || _f === void 0 ? void 0 : _f.edge_bundler_generate_tarball) ? NEXT_DENO_VERSION_RANGE : DENO_VERSION_RANGE);
|
|
33
|
+
this.useGlobal = options.useGlobal ?? true;
|
|
34
|
+
this.versionRange = options.versionRange ?? DENO_VERSION_RANGE;
|
|
29
35
|
}
|
|
30
36
|
async downloadBinary() {
|
|
31
|
-
|
|
32
|
-
await ((_a = this.onBeforeDownload) === null || _a === void 0 ? void 0 : _a.call(this));
|
|
37
|
+
await this.onBeforeDownload?.();
|
|
33
38
|
await this.ensureCacheDirectory();
|
|
34
39
|
this.logger.system(`Downloading Deno CLI to ${this.cacheDirectory}`);
|
|
35
40
|
const binaryPath = await download(this.cacheDirectory, this.versionRange, this.logger);
|
|
@@ -39,12 +44,12 @@ export class DenoBridge {
|
|
|
39
44
|
// that the tests catch it.
|
|
40
45
|
if (downloadedVersion === undefined) {
|
|
41
46
|
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.');
|
|
42
|
-
await
|
|
47
|
+
await this.onAfterDownload?.(error);
|
|
43
48
|
this.logger.system('Could not run downloaded Deno CLI', error);
|
|
44
49
|
throw error;
|
|
45
50
|
}
|
|
46
51
|
await this.writeVersionFile(downloadedVersion);
|
|
47
|
-
await
|
|
52
|
+
await this.onAfterDownload?.();
|
|
48
53
|
return binaryPath;
|
|
49
54
|
}
|
|
50
55
|
async getBinaryVersion(binaryPath) {
|
|
@@ -97,19 +102,18 @@ export class DenoBridge {
|
|
|
97
102
|
return this.currentDownload;
|
|
98
103
|
}
|
|
99
104
|
static runWithBinary(binaryPath, args, { options, pipeOutput, stderr, stdout, }) {
|
|
100
|
-
var _a, _b, _c, _d;
|
|
101
105
|
const runDeno = execa(binaryPath, args, options);
|
|
102
106
|
if (stderr) {
|
|
103
|
-
|
|
107
|
+
runDeno.stderr?.pipe(stderr);
|
|
104
108
|
}
|
|
105
109
|
else if (pipeOutput) {
|
|
106
|
-
|
|
110
|
+
runDeno.stderr?.pipe(process.stderr);
|
|
107
111
|
}
|
|
108
112
|
if (stdout) {
|
|
109
|
-
|
|
113
|
+
runDeno.stdout?.pipe(stdout);
|
|
110
114
|
}
|
|
111
115
|
else if (pipeOutput) {
|
|
112
|
-
|
|
116
|
+
runDeno.stdout?.pipe(process.stdout);
|
|
113
117
|
}
|
|
114
118
|
return runDeno;
|
|
115
119
|
}
|
|
@@ -124,14 +128,14 @@ export class DenoBridge {
|
|
|
124
128
|
async getBinaryPath(options) {
|
|
125
129
|
const globalPath = await this.getGlobalBinary();
|
|
126
130
|
if (globalPath !== undefined) {
|
|
127
|
-
if (!
|
|
131
|
+
if (!options?.silent) {
|
|
128
132
|
this.logger.system('Using global installation of Deno CLI');
|
|
129
133
|
}
|
|
130
134
|
return { global: true, path: globalPath };
|
|
131
135
|
}
|
|
132
136
|
const cachedPath = await this.getCachedBinary();
|
|
133
137
|
if (cachedPath !== undefined) {
|
|
134
|
-
if (!
|
|
138
|
+
if (!options?.silent) {
|
|
135
139
|
this.logger.system('Using cached Deno CLI from', cachedPath);
|
|
136
140
|
}
|
|
137
141
|
return { global: false, path: cachedPath };
|
package/dist/node/bridge.test.js
CHANGED
|
@@ -12,8 +12,7 @@ import { getPlatformTarget } from './platform.js';
|
|
|
12
12
|
const require = createRequire(import.meta.url);
|
|
13
13
|
const archiver = require('archiver');
|
|
14
14
|
const getMockDenoBridge = function (tmpDir, mockBinaryOutput) {
|
|
15
|
-
|
|
16
|
-
const latestVersion = (_b = (_a = semver.minVersion(DENO_VERSION_RANGE)) === null || _a === void 0 ? void 0 : _a.version) !== null && _b !== void 0 ? _b : '';
|
|
15
|
+
const latestVersion = semver.minVersion(DENO_VERSION_RANGE)?.version ?? '';
|
|
17
16
|
const data = new PassThrough();
|
|
18
17
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
19
18
|
archive.pipe(data);
|
|
@@ -32,7 +31,6 @@ const getMockDenoBridge = function (tmpDir, mockBinaryOutput) {
|
|
|
32
31
|
});
|
|
33
32
|
};
|
|
34
33
|
test('Does not inherit environment variables if `extendEnv` is false', async () => {
|
|
35
|
-
var _a;
|
|
36
34
|
const tmpDir = await tmp.dir();
|
|
37
35
|
const deno = getMockDenoBridge(tmpDir, `#!/usr/bin/env sh
|
|
38
36
|
|
|
@@ -46,9 +44,9 @@ test('Does not inherit environment variables if `extendEnv` is false', async ()
|
|
|
46
44
|
const referenceOutput = await deno.run(['test'], { env: {}, extendEnv: false });
|
|
47
45
|
env.TADA = 'TUDU';
|
|
48
46
|
const result = await deno.run(['test'], { env: { LULU: 'LALA' }, extendEnv: false });
|
|
49
|
-
let output =
|
|
47
|
+
let output = result?.stdout ?? '';
|
|
50
48
|
delete env.TADA;
|
|
51
|
-
referenceOutput
|
|
49
|
+
referenceOutput?.stdout.split('\n').forEach((line) => {
|
|
52
50
|
output = output.replace(line.trim(), '');
|
|
53
51
|
});
|
|
54
52
|
output = output.trim().replace(/\n+/g, '\n');
|
|
@@ -56,7 +54,6 @@ test('Does not inherit environment variables if `extendEnv` is false', async ()
|
|
|
56
54
|
await rm(tmpDir.path, { force: true, recursive: true, maxRetries: 10 });
|
|
57
55
|
});
|
|
58
56
|
test('Does inherit environment variables if `extendEnv` is true', async () => {
|
|
59
|
-
var _a;
|
|
60
57
|
const tmpDir = await tmp.dir();
|
|
61
58
|
const deno = getMockDenoBridge(tmpDir, `#!/usr/bin/env sh
|
|
62
59
|
|
|
@@ -70,9 +67,9 @@ test('Does inherit environment variables if `extendEnv` is true', async () => {
|
|
|
70
67
|
const referenceOutput = await deno.run(['test'], { env: {}, extendEnv: true });
|
|
71
68
|
env.TADA = 'TUDU';
|
|
72
69
|
const result = await deno.run(['test'], { env: { LULU: 'LALA' }, extendEnv: true });
|
|
73
|
-
let output =
|
|
70
|
+
let output = result?.stdout ?? '';
|
|
74
71
|
delete env.TADA;
|
|
75
|
-
referenceOutput
|
|
72
|
+
referenceOutput?.stdout.split('\n').forEach((line) => {
|
|
76
73
|
output = output.replace(line.trim(), '');
|
|
77
74
|
});
|
|
78
75
|
// lets remove holes, split lines and sort lines by name, as different OSes might order them different
|
|
@@ -81,7 +78,6 @@ test('Does inherit environment variables if `extendEnv` is true', async () => {
|
|
|
81
78
|
await rm(tmpDir.path, { force: true, recursive: true, maxRetries: 10 });
|
|
82
79
|
});
|
|
83
80
|
test('Does inherit environment variables if `extendEnv` is not set', async () => {
|
|
84
|
-
var _a;
|
|
85
81
|
const tmpDir = await tmp.dir();
|
|
86
82
|
const deno = getMockDenoBridge(tmpDir, `#!/usr/bin/env sh
|
|
87
83
|
|
|
@@ -95,9 +91,9 @@ test('Does inherit environment variables if `extendEnv` is not set', async () =>
|
|
|
95
91
|
const referenceOutput = await deno.run(['test'], { env: {}, extendEnv: true });
|
|
96
92
|
env.TADA = 'TUDU';
|
|
97
93
|
const result = await deno.run(['test'], { env: { LULU: 'LALA' } });
|
|
98
|
-
let output =
|
|
94
|
+
let output = result?.stdout ?? '';
|
|
99
95
|
delete env.TADA;
|
|
100
|
-
referenceOutput
|
|
96
|
+
referenceOutput?.stdout.split('\n').forEach((line) => {
|
|
101
97
|
output = output.replace(line.trim(), '');
|
|
102
98
|
});
|
|
103
99
|
// lets remove holes, split lines and sort lines by name, as different OSes might order them different
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
interface BundleErrorOptions {
|
|
2
|
-
|
|
2
|
+
cause?: unknown;
|
|
3
|
+
format?: string;
|
|
3
4
|
}
|
|
4
5
|
declare const getCustomErrorInfo: (options?: BundleErrorOptions) => {
|
|
5
6
|
location: {
|
|
@@ -8,12 +9,12 @@ declare const getCustomErrorInfo: (options?: BundleErrorOptions) => {
|
|
|
8
9
|
};
|
|
9
10
|
type: string;
|
|
10
11
|
};
|
|
11
|
-
declare class BundleError extends Error {
|
|
12
|
+
export declare class BundleError extends Error {
|
|
12
13
|
customErrorInfo: ReturnType<typeof getCustomErrorInfo>;
|
|
13
14
|
constructor(originalError: Error, options?: BundleErrorOptions);
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* BundleErrors are treated as user-error, so Netlify Team is not alerted about them.
|
|
17
18
|
*/
|
|
18
|
-
declare const wrapBundleError: (input: unknown, options?: BundleErrorOptions) => unknown;
|
|
19
|
-
export {
|
|
19
|
+
export declare const wrapBundleError: (input: unknown, options?: BundleErrorOptions) => unknown;
|
|
20
|
+
export {};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
const getCustomErrorInfo = (options) => ({
|
|
2
2
|
location: {
|
|
3
|
-
format: options
|
|
3
|
+
format: options?.format,
|
|
4
4
|
runtime: 'deno',
|
|
5
5
|
},
|
|
6
6
|
type: 'functionsBundling',
|
|
7
7
|
});
|
|
8
|
-
class BundleError extends Error {
|
|
8
|
+
export class BundleError extends Error {
|
|
9
|
+
customErrorInfo;
|
|
9
10
|
constructor(originalError, options) {
|
|
10
|
-
super(originalError.message);
|
|
11
|
+
super(originalError.message, { cause: options?.cause });
|
|
11
12
|
this.customErrorInfo = getCustomErrorInfo(options);
|
|
12
13
|
this.name = 'BundleError';
|
|
13
14
|
this.stack = originalError.stack;
|
|
@@ -18,7 +19,7 @@ class BundleError extends Error {
|
|
|
18
19
|
/**
|
|
19
20
|
* BundleErrors are treated as user-error, so Netlify Team is not alerted about them.
|
|
20
21
|
*/
|
|
21
|
-
const wrapBundleError = (input, options) => {
|
|
22
|
+
export const wrapBundleError = (input, options) => {
|
|
22
23
|
if (input instanceof Error) {
|
|
23
24
|
if (input.message.includes("The module's source code could not be parsed")) {
|
|
24
25
|
input.message = input.stderr;
|
|
@@ -27,4 +28,3 @@ const wrapBundleError = (input, options) => {
|
|
|
27
28
|
}
|
|
28
29
|
return input;
|
|
29
30
|
};
|
|
30
|
-
export { BundleError, wrapBundleError };
|
package/dist/node/bundler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
2
|
+
import { join, relative } from 'path';
|
|
3
3
|
import commonPathPrefix from 'common-path-prefix';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
import { importMapSpecifier } from '../shared/consts.js';
|
|
@@ -9,7 +9,7 @@ import { mergeDeclarations } from './declaration.js';
|
|
|
9
9
|
import { load as loadDeployConfig } from './deploy_config.js';
|
|
10
10
|
import { getFlags } from './feature_flags.js';
|
|
11
11
|
import { findFunctions } from './finder.js';
|
|
12
|
-
import { bundle as bundleESZIP } from './formats/eszip.js';
|
|
12
|
+
import { bundle as bundleESZIP, extension as eszipExtension, extract as extractESZIP } from './formats/eszip.js';
|
|
13
13
|
import { bundle as bundleTarball } from './formats/tarball.js';
|
|
14
14
|
import { ImportMap } from './import_map.js';
|
|
15
15
|
import { getLogger } from './logger.js';
|
|
@@ -47,7 +47,7 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
|
|
|
47
47
|
const internalSrcFolders = (Array.isArray(internalSrcFolder) ? internalSrcFolder : [internalSrcFolder]).filter(nonNullable);
|
|
48
48
|
const userSourceDirectories = sourceDirectories.filter((dir) => !internalSrcFolders.includes(dir));
|
|
49
49
|
const importMap = new ImportMap();
|
|
50
|
-
await importMap.addFiles([deployConfig
|
|
50
|
+
await importMap.addFiles([deployConfig?.importMap, ...importMapPaths], logger);
|
|
51
51
|
const userFunctions = userSourceDirectories.length === 0 ? [] : await findFunctions(userSourceDirectories);
|
|
52
52
|
const internalFunctions = internalSrcFolder ? await findFunctions(internalSrcFolders) : [];
|
|
53
53
|
const functions = [...internalFunctions, ...userFunctions];
|
|
@@ -57,7 +57,7 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
|
|
|
57
57
|
functions,
|
|
58
58
|
importMap,
|
|
59
59
|
logger,
|
|
60
|
-
rootPath: rootPath
|
|
60
|
+
rootPath: rootPath ?? basePath,
|
|
61
61
|
vendorDirectory,
|
|
62
62
|
});
|
|
63
63
|
const bundles = [];
|
|
@@ -71,7 +71,7 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
|
|
|
71
71
|
functions,
|
|
72
72
|
featureFlags,
|
|
73
73
|
importMap: importMap.clone(),
|
|
74
|
-
vendorDirectory: vendor
|
|
74
|
+
vendorDirectory: vendor?.directory,
|
|
75
75
|
}));
|
|
76
76
|
}
|
|
77
77
|
if (vendor) {
|
|
@@ -87,19 +87,22 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
|
|
|
87
87
|
functions,
|
|
88
88
|
featureFlags,
|
|
89
89
|
importMap,
|
|
90
|
-
vendorDirectory: vendor
|
|
90
|
+
vendorDirectory: vendor?.directory,
|
|
91
91
|
}));
|
|
92
92
|
// The final file name of the bundles contains a SHA256 hash of the contents,
|
|
93
93
|
// which we can only compute now that the files have been generated. So let's
|
|
94
94
|
// rename the bundles to their permanent names.
|
|
95
|
-
await createFinalBundles(bundles, distDirectory, buildID);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
const bundlePaths = await createFinalBundles(bundles, distDirectory, buildID);
|
|
96
|
+
const eszipPath = bundlePaths.find((path) => path.endsWith(eszipExtension));
|
|
97
|
+
const { internalFunctions: internalFunctionsWithConfig, userFunctions: userFunctionsWithConfig } = await getFunctionConfigs({
|
|
98
|
+
basePath,
|
|
99
|
+
deno,
|
|
100
|
+
eszipPath,
|
|
101
|
+
importMap,
|
|
102
|
+
internalFunctions,
|
|
103
|
+
log: logger,
|
|
104
|
+
userFunctions,
|
|
105
|
+
});
|
|
103
106
|
// Creating a final declarations array by combining the TOML file with the
|
|
104
107
|
// deploy configuration API and the in-source configuration.
|
|
105
108
|
const declarations = mergeDeclarations(tomlDeclarations, userFunctionsWithConfig, internalFunctionsWithConfig, deployConfig.declarations, featureFlags);
|
|
@@ -118,19 +121,54 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
|
|
|
118
121
|
importMap: importMapSpecifier,
|
|
119
122
|
layers: deployConfig.layers,
|
|
120
123
|
});
|
|
121
|
-
await
|
|
124
|
+
await vendor?.cleanup();
|
|
122
125
|
if (distImportMapPath) {
|
|
123
126
|
await importMap.writeToFile(distImportMapPath);
|
|
124
127
|
}
|
|
125
128
|
return { functions, manifest };
|
|
126
129
|
};
|
|
130
|
+
const getFunctionConfigs = async ({ basePath, deno, eszipPath, importMap, log, internalFunctions, userFunctions, }) => {
|
|
131
|
+
try {
|
|
132
|
+
const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig({ functionPath: func.path, importMap, deno, log })]);
|
|
133
|
+
const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig({ functionPath: func.path, importMap, deno, log })]);
|
|
134
|
+
// Creating a hash of function names to configuration objects.
|
|
135
|
+
const internalFunctionsWithConfig = Object.fromEntries(await Promise.all(internalConfigPromises));
|
|
136
|
+
const userFunctionsWithConfig = Object.fromEntries(await Promise.all(userConfigPromises));
|
|
137
|
+
return {
|
|
138
|
+
internalFunctions: internalFunctionsWithConfig,
|
|
139
|
+
userFunctions: userFunctionsWithConfig,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
if (!(err instanceof Error && err.cause === 'IMPORT_ASSERT') || !eszipPath) {
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
// We failed to extract the configuration because there is an import assert
|
|
147
|
+
// in the function code, a deprecated feature that we used to support with
|
|
148
|
+
// Deno 1.x. To avoid a breaking change, we treat this error as a special
|
|
149
|
+
// case, using the generated ESZIP to extract the configuration. This works
|
|
150
|
+
// because import asserts are transpiled to import attributes.
|
|
151
|
+
const extractedESZIP = await extractESZIP(deno, eszipPath);
|
|
152
|
+
const configs = await Promise.all([...internalFunctions, ...userFunctions].map(async (func) => {
|
|
153
|
+
const relativePath = relative(basePath, func.path);
|
|
154
|
+
const functionPath = join(extractedESZIP.path, relativePath);
|
|
155
|
+
return [func.name, await getFunctionConfig({ functionPath, importMap, deno, log })];
|
|
156
|
+
}));
|
|
157
|
+
await extractedESZIP.cleanup();
|
|
158
|
+
return {
|
|
159
|
+
internalFunctions: Object.fromEntries(configs.slice(0, internalFunctions.length)),
|
|
160
|
+
userFunctions: Object.fromEntries(configs.slice(internalFunctions.length)),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
};
|
|
127
164
|
const createFinalBundles = async (bundles, distDirectory, buildID) => {
|
|
128
165
|
const renamingOps = bundles.map(async ({ extension, hash }) => {
|
|
129
166
|
const tempBundlePath = join(distDirectory, `${buildID}${extension}`);
|
|
130
167
|
const finalBundlePath = join(distDirectory, `${hash}${extension}`);
|
|
131
168
|
await fs.rename(tempBundlePath, finalBundlePath);
|
|
169
|
+
return finalBundlePath;
|
|
132
170
|
});
|
|
133
|
-
await Promise.all(renamingOps);
|
|
171
|
+
return await Promise.all(renamingOps);
|
|
134
172
|
};
|
|
135
173
|
const getBasePath = (sourceDirectories, inputBasePath) => {
|
|
136
174
|
// If there's a specific base path supplied, that takes precedence.
|
|
@@ -148,11 +186,11 @@ const getBasePath = (sourceDirectories, inputBasePath) => {
|
|
|
148
186
|
// declaration level. We want these properties to live at the function level
|
|
149
187
|
// in their config object, so we translate that for backwards-compatibility.
|
|
150
188
|
const mergeWithDeclarationConfig = ({ functionName, config, declarations }) => {
|
|
151
|
-
const declaration = declarations
|
|
189
|
+
const declaration = declarations?.find((decl) => decl.function === functionName);
|
|
152
190
|
return {
|
|
153
191
|
...config,
|
|
154
|
-
name:
|
|
155
|
-
generator:
|
|
192
|
+
name: declaration?.name || config.name,
|
|
193
|
+
generator: declaration?.generator || config.generator,
|
|
156
194
|
};
|
|
157
195
|
};
|
|
158
196
|
const addGeneratorFallback = (config) => ({
|
|
@@ -3,7 +3,7 @@ import { access, readdir, readFile, rm, writeFile } from 'fs/promises';
|
|
|
3
3
|
import { join, resolve } from 'path';
|
|
4
4
|
import process from 'process';
|
|
5
5
|
import { pathToFileURL } from 'url';
|
|
6
|
-
import {
|
|
6
|
+
import { lt } from 'semver';
|
|
7
7
|
import * as tar from 'tar';
|
|
8
8
|
import tmp from 'tmp-promise';
|
|
9
9
|
import { test, expect, vi, describe } from 'vitest';
|
|
@@ -498,22 +498,12 @@ test('Loads JSON modules with `with` attribute', async () => {
|
|
|
498
498
|
await cleanup();
|
|
499
499
|
await rm(vendorDirectory.path, { force: true, recursive: true });
|
|
500
500
|
});
|
|
501
|
-
|
|
502
|
-
// entirely, and what we're asserting here is that we emit a system log when
|
|
503
|
-
// import assertions are detected on successful builds. Also, running it on
|
|
504
|
-
// earlier versions won't work either, since those won't even show a warning.
|
|
505
|
-
test.skipIf(lt(denoVersion, '1.46.3') || gte(denoVersion, '2.0.0'))('Emits a system log when import assertions are used', async () => {
|
|
501
|
+
test('Emits a system log when import assertions are used', async () => {
|
|
506
502
|
const { basePath, cleanup, distPath } = await useFixture('with_import_assert');
|
|
507
503
|
const sourceDirectory = join(basePath, 'functions');
|
|
508
|
-
const declarations = [
|
|
509
|
-
{
|
|
510
|
-
function: 'func1',
|
|
511
|
-
path: '/func1',
|
|
512
|
-
},
|
|
513
|
-
];
|
|
514
504
|
const vendorDirectory = await tmp.dir();
|
|
515
505
|
const systemLogger = vi.fn();
|
|
516
|
-
await bundle([sourceDirectory], distPath,
|
|
506
|
+
await bundle([sourceDirectory], distPath, [], {
|
|
517
507
|
basePath,
|
|
518
508
|
systemLogger,
|
|
519
509
|
vendorDirectory: vendorDirectory.path,
|
|
@@ -524,6 +514,12 @@ test.skipIf(lt(denoVersion, '1.46.3') || gte(denoVersion, '2.0.0'))('Emits a sys
|
|
|
524
514
|
const { func1 } = await runESZIP(bundlePath, vendorDirectory.path);
|
|
525
515
|
expect(func1).toBe(`{"foo":"bar"}`);
|
|
526
516
|
expect(systemLogger).toHaveBeenCalledWith(`Edge function uses import assertions: ${join(sourceDirectory, 'func1.ts')}`);
|
|
517
|
+
expect(manifest.routes[0]).toEqual({
|
|
518
|
+
function: 'func1',
|
|
519
|
+
pattern: '^/with-import-assert/?$',
|
|
520
|
+
excluded_patterns: [],
|
|
521
|
+
path: '/with-import-assert',
|
|
522
|
+
});
|
|
527
523
|
await cleanup();
|
|
528
524
|
await rm(vendorDirectory.path, { force: true, recursive: true });
|
|
529
525
|
});
|
|
@@ -581,7 +577,7 @@ test('Loads edge functions from the Frameworks API', async () => {
|
|
|
581
577
|
});
|
|
582
578
|
await cleanup();
|
|
583
579
|
});
|
|
584
|
-
describe.skipIf(lt(denoVersion, '2.4.
|
|
580
|
+
describe.skipIf(lt(denoVersion, '2.4.3'))('Produces a tarball bundle', () => {
|
|
585
581
|
test('With only local imports', async () => {
|
|
586
582
|
const systemLogger = vi.fn();
|
|
587
583
|
const { basePath, cleanup, distPath } = await useFixture('imports_node_builtin', { copyDirectory: true });
|
|
@@ -623,8 +619,7 @@ describe.skipIf(lt(denoVersion, '2.4.2'))('Produces a tarball bundle', () => {
|
|
|
623
619
|
await cleanup();
|
|
624
620
|
await rm(vendorDirectory.path, { force: true, recursive: true });
|
|
625
621
|
});
|
|
626
|
-
|
|
627
|
-
test.todo('Using npm modules', async () => {
|
|
622
|
+
test('Using npm and remote modules', async () => {
|
|
628
623
|
const systemLogger = vi.fn();
|
|
629
624
|
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module', { copyDirectory: true });
|
|
630
625
|
const sourceDirectory = join(basePath, 'functions');
|
|
@@ -657,4 +652,4 @@ describe.skipIf(lt(denoVersion, '2.4.2'))('Produces a tarball bundle', () => {
|
|
|
657
652
|
await cleanup();
|
|
658
653
|
await rm(vendorDirectory.path, { force: true, recursive: true });
|
|
659
654
|
});
|
|
660
|
-
},
|
|
655
|
+
}, 10_000);
|