@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/dist/node/config.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DenoBridge } from './bridge.js';
|
|
2
|
-
import { EdgeFunction } from './edge_function.js';
|
|
3
2
|
import { ImportMap } from './import_map.js';
|
|
4
3
|
import { Logger } from './logger.js';
|
|
5
4
|
import { RateLimit } from './rate_limit.js';
|
|
@@ -31,10 +30,10 @@ interface FunctionConfigWithPattern extends BaseFunctionConfig {
|
|
|
31
30
|
}
|
|
32
31
|
export type FunctionConfig = FunctionConfigWithPath | FunctionConfigWithPattern;
|
|
33
32
|
export type FunctionConfigWithAllPossibleFields = BaseFunctionConfig & Partial<FunctionConfigWithPath & FunctionConfigWithPattern>;
|
|
34
|
-
export declare const getFunctionConfig: ({
|
|
35
|
-
func: EdgeFunction;
|
|
36
|
-
importMap: ImportMap;
|
|
33
|
+
export declare const getFunctionConfig: ({ deno, functionPath, importMap, log, }: {
|
|
37
34
|
deno: DenoBridge;
|
|
35
|
+
functionPath: string;
|
|
36
|
+
importMap: ImportMap;
|
|
38
37
|
log: Logger;
|
|
39
38
|
}) => Promise<FunctionConfig>;
|
|
40
39
|
export {};
|
package/dist/node/config.js
CHANGED
|
@@ -27,7 +27,7 @@ const getConfigExtractor = () => {
|
|
|
27
27
|
const configExtractorPath = join(packagePath, 'deno', 'config.ts');
|
|
28
28
|
return configExtractorPath;
|
|
29
29
|
};
|
|
30
|
-
export const getFunctionConfig = async ({
|
|
30
|
+
export const getFunctionConfig = async ({ deno, functionPath, importMap, log, }) => {
|
|
31
31
|
// The extractor is a Deno script that will import the function and run its
|
|
32
32
|
// `config` export, if one exists.
|
|
33
33
|
const extractorPath = getConfigExtractor();
|
|
@@ -54,15 +54,12 @@ export const getFunctionConfig = async ({ func, importMap, deno, log, }) => {
|
|
|
54
54
|
'--node-modules-dir=false',
|
|
55
55
|
'--quiet',
|
|
56
56
|
extractorPath,
|
|
57
|
-
pathToFileURL(
|
|
57
|
+
pathToFileURL(functionPath).href,
|
|
58
58
|
pathToFileURL(collector.path).href,
|
|
59
59
|
JSON.stringify(ConfigExitCode),
|
|
60
60
|
].filter(Boolean), { rejectOnExitCode: false });
|
|
61
|
-
if (stderr.includes('Import assertions are deprecated')) {
|
|
62
|
-
log.system(`Edge function uses import assertions: ${func.path}`);
|
|
63
|
-
}
|
|
64
61
|
if (exitCode !== ConfigExitCode.Success) {
|
|
65
|
-
handleConfigError(
|
|
62
|
+
handleConfigError(functionPath, exitCode, stderr, log);
|
|
66
63
|
return {};
|
|
67
64
|
}
|
|
68
65
|
if (stdout !== '') {
|
|
@@ -74,35 +71,38 @@ export const getFunctionConfig = async ({ func, importMap, deno, log, }) => {
|
|
|
74
71
|
collectorData = JSON.parse(collectorDataJSON);
|
|
75
72
|
}
|
|
76
73
|
catch {
|
|
77
|
-
handleConfigError(
|
|
74
|
+
handleConfigError(functionPath, ConfigExitCode.UnhandledError, stderr, log);
|
|
78
75
|
}
|
|
79
76
|
finally {
|
|
80
77
|
await collector.cleanup();
|
|
81
78
|
}
|
|
82
79
|
if (!isValidOnError(collectorData.onError)) {
|
|
83
|
-
throw new BundleError(new Error(`The 'onError' configuration property in edge function at '${
|
|
80
|
+
throw new BundleError(new Error(`The 'onError' configuration property in edge function at '${functionPath}' must be one of 'fail', 'bypass', or a path starting with '/'. Got '${collectorData.onError}'. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
|
84
81
|
}
|
|
85
82
|
return collectorData;
|
|
86
83
|
};
|
|
87
|
-
const handleConfigError = (
|
|
84
|
+
const handleConfigError = (functionPath, exitCode, stderr, log) => {
|
|
85
|
+
let cause;
|
|
86
|
+
if (stderr.includes('Import assertions are deprecated')) {
|
|
87
|
+
log.system(`Edge function uses import assertions: ${functionPath}`);
|
|
88
|
+
cause = 'IMPORT_ASSERT';
|
|
89
|
+
}
|
|
88
90
|
switch (exitCode) {
|
|
89
91
|
case ConfigExitCode.ImportError:
|
|
90
92
|
log.user(stderr);
|
|
91
|
-
throw new BundleError(new Error(`Could not load edge function at '${
|
|
92
|
-
break;
|
|
93
|
+
throw new BundleError(new Error(`Could not load edge function at '${functionPath}'. More on the Edge Functions API at https://ntl.fyi/edge-api.`), { cause });
|
|
93
94
|
case ConfigExitCode.NoConfig:
|
|
94
|
-
log.system(`No in-source config found for edge function at '${
|
|
95
|
+
log.system(`No in-source config found for edge function at '${functionPath}'`);
|
|
95
96
|
break;
|
|
96
97
|
case ConfigExitCode.InvalidExport:
|
|
97
|
-
throw new BundleError(new Error(`The 'config' export in edge function at '${
|
|
98
|
-
break;
|
|
98
|
+
throw new BundleError(new Error(`The 'config' export in edge function at '${functionPath}' must be an object. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
|
99
99
|
case ConfigExitCode.SerializationError:
|
|
100
|
-
throw new BundleError(new Error(`The 'config' object in the edge function at '${
|
|
100
|
+
throw new BundleError(new Error(`The 'config' object in the edge function at '${functionPath}' must contain primitive values only. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
|
101
101
|
break;
|
|
102
102
|
case ConfigExitCode.InvalidDefaultExport:
|
|
103
|
-
throw new BundleError(new Error(`Default export in '${
|
|
103
|
+
throw new BundleError(new Error(`Default export in '${functionPath}' must be a function. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
|
104
104
|
default:
|
|
105
|
-
log.user(`Could not load configuration for edge function at '${
|
|
105
|
+
log.user(`Could not load configuration for edge function at '${functionPath}'`);
|
|
106
106
|
log.user(stderr);
|
|
107
107
|
}
|
|
108
108
|
};
|
package/dist/node/config.test.js
CHANGED
|
@@ -172,10 +172,7 @@ describe('`getFunctionConfig` extracts configuration properties from function fi
|
|
|
172
172
|
const path = join(tmpDir, `${func.name}.js`);
|
|
173
173
|
await fs.writeFile(path, func.source);
|
|
174
174
|
const funcCall = () => getFunctionConfig({
|
|
175
|
-
|
|
176
|
-
name: func.name,
|
|
177
|
-
path,
|
|
178
|
-
},
|
|
175
|
+
functionPath: path,
|
|
179
176
|
importMap: new ImportMap([importMapFile]),
|
|
180
177
|
deno,
|
|
181
178
|
log: logger,
|
|
@@ -349,10 +346,7 @@ test('Passes validation if default export exists and is a function', async () =>
|
|
|
349
346
|
const path = join(tmpDir, `${func.name}.ts`);
|
|
350
347
|
await fs.writeFile(path, func.source);
|
|
351
348
|
await expect(getFunctionConfig({
|
|
352
|
-
|
|
353
|
-
name: func.name,
|
|
354
|
-
path,
|
|
355
|
-
},
|
|
349
|
+
functionPath: path,
|
|
356
350
|
importMap: new ImportMap([importMapFile]),
|
|
357
351
|
deno,
|
|
358
352
|
log: logger,
|
|
@@ -378,10 +372,7 @@ test('Fails validation if default export is not function', async () => {
|
|
|
378
372
|
const path = join(tmpDir, `${func.name}.ts`);
|
|
379
373
|
await fs.writeFile(path, func.source);
|
|
380
374
|
const config = getFunctionConfig({
|
|
381
|
-
|
|
382
|
-
name: func.name,
|
|
383
|
-
path,
|
|
384
|
-
},
|
|
375
|
+
functionPath: path,
|
|
385
376
|
importMap: new ImportMap([importMapFile]),
|
|
386
377
|
deno,
|
|
387
378
|
log: logger,
|
|
@@ -407,10 +398,7 @@ test('Fails validation if default export is not present', async () => {
|
|
|
407
398
|
const path = join(tmpDir, `${func.name}.ts`);
|
|
408
399
|
await fs.writeFile(path, func.source);
|
|
409
400
|
const config = getFunctionConfig({
|
|
410
|
-
|
|
411
|
-
name: func.name,
|
|
412
|
-
path,
|
|
413
|
-
},
|
|
401
|
+
functionPath: path,
|
|
414
402
|
importMap: new ImportMap([importMapFile]),
|
|
415
403
|
deno,
|
|
416
404
|
log: logger,
|
package/dist/node/declaration.js
CHANGED
|
@@ -16,7 +16,6 @@ export const mergeDeclarations = (tomlDeclarations, userFunctionsConfig, interna
|
|
|
16
16
|
return declarations;
|
|
17
17
|
};
|
|
18
18
|
const getDeclarationsFromInput = (inputDeclarations, functionConfigs, functionsVisited) => {
|
|
19
|
-
var _a, _b;
|
|
20
19
|
const declarations = [];
|
|
21
20
|
// For any declaration for which we also have a function configuration object,
|
|
22
21
|
// we replace the path because that object takes precedence.
|
|
@@ -26,7 +25,7 @@ const getDeclarationsFromInput = (inputDeclarations, functionConfigs, functionsV
|
|
|
26
25
|
// If no config is found, add the declaration as is.
|
|
27
26
|
declarations.push(declaration);
|
|
28
27
|
}
|
|
29
|
-
else if ('pattern' in config &&
|
|
28
|
+
else if ('pattern' in config && config.pattern?.length) {
|
|
30
29
|
// If we have a pattern specified as either a string or non-empty array,
|
|
31
30
|
// create a declaration for each pattern.
|
|
32
31
|
const patterns = Array.isArray(config.pattern) ? config.pattern : [config.pattern];
|
|
@@ -34,7 +33,7 @@ const getDeclarationsFromInput = (inputDeclarations, functionConfigs, functionsV
|
|
|
34
33
|
declarations.push({ ...declaration, cache: config.cache, pattern });
|
|
35
34
|
});
|
|
36
35
|
}
|
|
37
|
-
else if ('path' in config &&
|
|
36
|
+
else if ('path' in config && config.path?.length) {
|
|
38
37
|
// If we have a path specified as either a string or non-empty array,
|
|
39
38
|
// create a declaration for each path.
|
|
40
39
|
const paths = Array.isArray(config.path) ? config.path : [config.path];
|
|
@@ -24,13 +24,12 @@ export const load = async (path, logger) => {
|
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
26
|
const parse = (data, path) => {
|
|
27
|
-
var _a, _b;
|
|
28
27
|
if (data.version !== 1) {
|
|
29
28
|
throw new Error(`Unsupported file version: ${data.version}`);
|
|
30
29
|
}
|
|
31
30
|
const config = {
|
|
32
|
-
declarations:
|
|
33
|
-
layers:
|
|
31
|
+
declarations: data.functions ?? [],
|
|
32
|
+
layers: data.layers ?? [],
|
|
34
33
|
};
|
|
35
34
|
if (data.import_map) {
|
|
36
35
|
const importMapPath = resolve(dirname(path), data.import_map);
|
package/dist/node/downloader.js
CHANGED
|
@@ -71,8 +71,7 @@ const getLatestVersion = async () => {
|
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
const getLatestVersionForRange = async (range) => {
|
|
74
|
-
|
|
75
|
-
const minimumVersion = (_a = semver.minVersion(range)) === null || _a === void 0 ? void 0 : _a.version;
|
|
74
|
+
const minimumVersion = semver.minVersion(range)?.version;
|
|
76
75
|
// We should never get here, because it means that `DENO_VERSION_RANGE` is
|
|
77
76
|
// a malformed semver range. If this does happen, let's throw an error so
|
|
78
77
|
// that the tests catch it.
|
|
@@ -3,6 +3,7 @@ import { Bundle } from '../bundle.js';
|
|
|
3
3
|
import { EdgeFunction } from '../edge_function.js';
|
|
4
4
|
import { FeatureFlags } from '../feature_flags.js';
|
|
5
5
|
import { ImportMap } from '../import_map.js';
|
|
6
|
+
export declare const extension = ".eszip";
|
|
6
7
|
interface BundleESZIPOptions {
|
|
7
8
|
basePath: string;
|
|
8
9
|
buildID: string;
|
|
@@ -15,5 +16,9 @@ interface BundleESZIPOptions {
|
|
|
15
16
|
importMap: ImportMap;
|
|
16
17
|
vendorDirectory?: string;
|
|
17
18
|
}
|
|
18
|
-
declare const
|
|
19
|
-
export
|
|
19
|
+
export declare const bundle: ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>;
|
|
20
|
+
export declare const extract: (deno: DenoBridge, functionPath: string) => Promise<{
|
|
21
|
+
cleanup: () => Promise<void>;
|
|
22
|
+
path: string;
|
|
23
|
+
}>;
|
|
24
|
+
export {};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { pathToFileURL } from 'url';
|
|
3
|
+
import tmp from 'tmp-promise';
|
|
3
4
|
import { virtualRoot, virtualVendorRoot } from '../../shared/consts.js';
|
|
4
5
|
import { BundleFormat } from '../bundle.js';
|
|
5
6
|
import { wrapBundleError } from '../bundle_error.js';
|
|
6
7
|
import { wrapNpmImportError } from '../npm_import_error.js';
|
|
7
8
|
import { getPackagePath } from '../package_json.js';
|
|
8
9
|
import { getFileHash } from '../utils/sha256.js';
|
|
9
|
-
const
|
|
10
|
-
|
|
10
|
+
export const extension = '.eszip';
|
|
11
|
+
export const bundle = async ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }) => {
|
|
11
12
|
const destPath = join(distDirectory, `${buildID}${extension}`);
|
|
12
13
|
const importMapPrefixes = {
|
|
13
14
|
[`${pathToFileURL(basePath)}/`]: virtualRoot,
|
|
@@ -44,7 +45,17 @@ const getESZIPPaths = () => {
|
|
|
44
45
|
const denoPath = join(getPackagePath(), 'deno');
|
|
45
46
|
return {
|
|
46
47
|
bundler: join(denoPath, 'bundle.ts'),
|
|
48
|
+
extractor: join(denoPath, 'extract.ts'),
|
|
47
49
|
importMap: join(denoPath, 'vendor', 'import_map.json'),
|
|
48
50
|
};
|
|
49
51
|
};
|
|
50
|
-
export
|
|
52
|
+
export const extract = async (deno, functionPath) => {
|
|
53
|
+
const tmpDir = await tmp.dir({ unsafeCleanup: true });
|
|
54
|
+
const { extractor, importMap } = getESZIPPaths();
|
|
55
|
+
const flags = ['--allow-all', '--no-config', '--no-lock', `--import-map=${importMap}`, '--quiet'];
|
|
56
|
+
await deno.run(['run', ...flags, extractor, functionPath, tmpDir.path], { pipeOutput: true });
|
|
57
|
+
return {
|
|
58
|
+
cleanup: tmpDir.cleanup,
|
|
59
|
+
path: join(tmpDir.path, 'source', 'root'),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -37,7 +37,7 @@ const getLocalEntryPoint = (functions, { bootstrapURL, formatExportTypeError = d
|
|
|
37
37
|
}
|
|
38
38
|
`;
|
|
39
39
|
});
|
|
40
|
-
const bootCall = `boot(functions
|
|
40
|
+
const bootCall = `boot(() => Promise.resolve(functions));`;
|
|
41
41
|
return [bootImport, declaration, ...imports, bootCall].join('\n\n');
|
|
42
42
|
};
|
|
43
43
|
export { generateStage2, getLocalEntryPoint };
|
package/dist/node/import_map.js
CHANGED
|
@@ -12,6 +12,11 @@ const INTERNAL_IMPORTS = {
|
|
|
12
12
|
// ImportMap can take several import map files and merge them into a final
|
|
13
13
|
// import map object, also adding the internal imports in the right order.
|
|
14
14
|
export class ImportMap {
|
|
15
|
+
// The root path which import maps can reference. If an import map attempts
|
|
16
|
+
// to reference a file outside this directory, an error will be thrown.
|
|
17
|
+
rootPath;
|
|
18
|
+
// The different import map files that make up the wider import map.
|
|
19
|
+
sources;
|
|
15
20
|
constructor(sources = [], rootPath = null) {
|
|
16
21
|
this.rootPath = rootPath;
|
|
17
22
|
this.sources = [];
|
package/dist/node/logger.js
CHANGED
|
@@ -5,8 +5,8 @@ const getLogger = (systemLogger, userLogger, debug = false) => {
|
|
|
5
5
|
// If there is a system logger configured, we'll use that. If there isn't,
|
|
6
6
|
// we'll pipe system logs to stdout if `debug` is enabled and swallow them
|
|
7
7
|
// otherwise.
|
|
8
|
-
const system = systemLogger
|
|
9
|
-
const user = userLogger
|
|
8
|
+
const system = systemLogger ?? (debug ? console.log : noopLogger);
|
|
9
|
+
const user = userLogger ?? console.log;
|
|
10
10
|
return {
|
|
11
11
|
system,
|
|
12
12
|
user,
|
package/dist/node/main.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
|
test('Downloads the Deno CLI on demand and caches it for subsequent calls', async () => {
|
|
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 mockBinaryOutput = `#!/usr/bin/env sh\n\necho "deno ${latestVersion}"`;
|
|
18
17
|
const data = new PassThrough();
|
|
19
18
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
@@ -39,8 +38,8 @@ test('Downloads the Deno CLI on demand and caches it for subsequent calls', asyn
|
|
|
39
38
|
const expectedOutput = /^deno [\d.]+/;
|
|
40
39
|
expect(latestReleaseMock.isDone()).toBe(true);
|
|
41
40
|
expect(downloadMock.isDone()).toBe(true);
|
|
42
|
-
expect(
|
|
43
|
-
expect(
|
|
41
|
+
expect(output1?.stdout ?? '').toMatch(expectedOutput);
|
|
42
|
+
expect(output2?.stdout ?? '').toMatch(expectedOutput);
|
|
44
43
|
expect(beforeDownload).toHaveBeenCalledTimes(1);
|
|
45
44
|
expect(afterDownload).toHaveBeenCalledTimes(1);
|
|
46
45
|
await rm(tmpDir.path, { force: true, recursive: true, maxRetries: 10 });
|
package/dist/node/manifest.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getHeaderMatchers, normalizePattern } from './declaration.js';
|
|
|
5
5
|
import { getPackageVersion } from './package_json.js';
|
|
6
6
|
import { RateLimitAction, RateLimitAlgorithm, RateLimitAggregator } from './rate_limit.js';
|
|
7
7
|
import { nonNullable } from './utils/non_nullable.js';
|
|
8
|
-
import {
|
|
8
|
+
import { getRegexpFromURLPatternPath } from './utils/urlpattern.js';
|
|
9
9
|
const removeEmptyConfigValues = (functionConfig) => Object.entries(functionConfig).reduce((acc, [key, value]) => {
|
|
10
10
|
if (value && !(Array.isArray(value) && value.length === 0)) {
|
|
11
11
|
return { ...acc, [key]: value };
|
|
@@ -176,10 +176,9 @@ const pathToRegularExpression = (path) => {
|
|
|
176
176
|
return null;
|
|
177
177
|
}
|
|
178
178
|
try {
|
|
179
|
-
const pattern = new ExtendedURLPattern({ pathname: path });
|
|
180
179
|
// Removing the `^` and `$` delimiters because we'll need to modify what's
|
|
181
180
|
// between them.
|
|
182
|
-
const source =
|
|
181
|
+
const source = getRegexpFromURLPatternPath(path).slice(1, -1);
|
|
183
182
|
// Wrapping the expression source with `^` and `$`. Also, adding an optional
|
|
184
183
|
// trailing slash, so that a declaration of `path: "/foo"` matches requests
|
|
185
184
|
// for both `/foo` and `/foo/`.
|
|
@@ -70,7 +70,6 @@ test('Generates a manifest with a generator field', () => {
|
|
|
70
70
|
expect(manifest.function_config).toEqual(expectedFunctionConfig);
|
|
71
71
|
});
|
|
72
72
|
test('Generates a manifest with excluded paths and patterns', () => {
|
|
73
|
-
var _a, _b;
|
|
74
73
|
const functions = [
|
|
75
74
|
{ name: 'func-1', path: '/path/to/func-1.ts' },
|
|
76
75
|
{ name: 'func-2', path: '/path/to/func-2.ts' },
|
|
@@ -104,8 +103,8 @@ test('Generates a manifest with excluded paths and patterns', () => {
|
|
|
104
103
|
expect(manifest.function_config).toEqual({});
|
|
105
104
|
expect(manifest.bundler_version).toBe(version);
|
|
106
105
|
const matcher = getRouteMatcher(manifest);
|
|
107
|
-
expect(
|
|
108
|
-
expect(
|
|
106
|
+
expect(matcher('/f1/hello')?.function).toBe('func-1');
|
|
107
|
+
expect(matcher('/grandparent/parent/child/grandchild.html')?.function).toBeUndefined();
|
|
109
108
|
});
|
|
110
109
|
test('TOML-defined paths can be combined with ISC-defined excluded paths', () => {
|
|
111
110
|
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
@@ -36,7 +36,7 @@ const getTypePathFromTypesPackage = async (packageName, packageJsonPath) => {
|
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
38
|
const { types, typings } = JSON.parse(await fs.readFile(typesPackagePath, 'utf8'));
|
|
39
|
-
const declarationPath = types
|
|
39
|
+
const declarationPath = types ?? typings;
|
|
40
40
|
if (typeof declarationPath === 'string') {
|
|
41
41
|
return path.join(typesPackagePath, '..', declarationPath);
|
|
42
42
|
}
|
|
@@ -51,7 +51,7 @@ const getTypesPath = async (packageJsonPath) => {
|
|
|
51
51
|
const { name, types, typings } = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
52
52
|
// this only looks at `.types` and `.typings` fields. there might also be data in `exports -> . -> types -> import/default`.
|
|
53
53
|
// we're ignoring that for now.
|
|
54
|
-
const declarationPath = types
|
|
54
|
+
const declarationPath = types ?? typings;
|
|
55
55
|
if (typeof declarationPath === 'string') {
|
|
56
56
|
return path.join(packageJsonPath, '..', declarationPath);
|
|
57
57
|
}
|
|
@@ -117,7 +117,6 @@ async function parseImportsForFile(file, rootPath) {
|
|
|
117
117
|
* to npm modules.
|
|
118
118
|
*/
|
|
119
119
|
const getNPMSpecifiers = async ({ basePath, functions, importMap, environment, rootPath }, alreadySeenPaths = new Set()) => {
|
|
120
|
-
var _a;
|
|
121
120
|
const baseURL = pathToFileURL(basePath);
|
|
122
121
|
const npmSpecifiers = [];
|
|
123
122
|
for (const func of functions) {
|
|
@@ -152,7 +151,7 @@ const getNPMSpecifiers = async ({ basePath, functions, importMap, environment, r
|
|
|
152
151
|
}
|
|
153
152
|
const { matched, resolvedImport } = resolve(specifier, importMap, baseURL);
|
|
154
153
|
if (matched) {
|
|
155
|
-
if (
|
|
154
|
+
if (resolvedImport?.protocol === 'file:') {
|
|
156
155
|
const newSpecifier = fileURLToPath(resolvedImport).replace(/\\/g, '/');
|
|
157
156
|
if (alreadySeenPaths.has(newSpecifier)) {
|
|
158
157
|
break;
|
|
@@ -161,7 +160,7 @@ const getNPMSpecifiers = async ({ basePath, functions, importMap, environment, r
|
|
|
161
160
|
npmSpecifiers.push(...(await getNPMSpecifiers({ basePath, functions: [newSpecifier], importMap, environment, rootPath }, alreadySeenPaths)));
|
|
162
161
|
}
|
|
163
162
|
}
|
|
164
|
-
else if (!
|
|
163
|
+
else if (!resolvedImport?.protocol?.startsWith('http')) {
|
|
165
164
|
const t = await safelyDetectTypes(specifier, basePath);
|
|
166
165
|
npmSpecifiers.push({
|
|
167
166
|
specifier: specifier,
|
|
@@ -235,8 +234,7 @@ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, impo
|
|
|
235
234
|
});
|
|
236
235
|
outputFiles.push(...outputFilesFromEsBuild.map((file) => file.path));
|
|
237
236
|
await Promise.all(outputFilesFromEsBuild.map(async (file) => {
|
|
238
|
-
|
|
239
|
-
const types = (_a = ops.find((op) => path.basename(file.path) === path.basename(op.filePath))) === null || _a === void 0 ? void 0 : _a.types;
|
|
237
|
+
const types = ops.find((op) => path.basename(file.path) === path.basename(op.filePath))?.types;
|
|
240
238
|
let content = file.text;
|
|
241
239
|
if (types) {
|
|
242
240
|
content = `/// <reference types="${path.relative(path.dirname(file.path), types)}" />\n${content}`;
|
|
@@ -22,8 +22,7 @@ const cleanDirectory = async (directory, except) => {
|
|
|
22
22
|
const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, logger, port, rootPath, stderr, stdout, }) => {
|
|
23
23
|
const processRef = {};
|
|
24
24
|
const startServer = async (functions, env = {}, options = {}) => {
|
|
25
|
-
|
|
26
|
-
if ((processRef === null || processRef === void 0 ? void 0 : processRef.ps) !== undefined) {
|
|
25
|
+
if (processRef?.ps !== undefined) {
|
|
27
26
|
await killProcess(processRef.ps);
|
|
28
27
|
}
|
|
29
28
|
let graph = {
|
|
@@ -41,7 +40,7 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
|
|
|
41
40
|
});
|
|
42
41
|
const features = {};
|
|
43
42
|
const importMap = new ImportMap();
|
|
44
|
-
await importMap.addFiles(
|
|
43
|
+
await importMap.addFiles(options.importMapPaths ?? [], logger);
|
|
45
44
|
// we keep track of the files that are relevant to the user's code, so we can clean up leftovers from past executions later
|
|
46
45
|
const relevantFiles = [stage2Path];
|
|
47
46
|
const vendor = await vendorNPMSpecifiers({
|
|
@@ -84,7 +83,7 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
|
|
|
84
83
|
});
|
|
85
84
|
let functionsConfig = [];
|
|
86
85
|
if (options.getFunctionsConfig) {
|
|
87
|
-
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno, log: logger })));
|
|
86
|
+
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ functionPath: func.path, importMap, deno, log: logger })));
|
|
88
87
|
}
|
|
89
88
|
if (distImportMapPath) {
|
|
90
89
|
await importMap.writeToFile(distImportMapPath);
|
|
@@ -52,9 +52,9 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
52
52
|
expect(features).toEqual({ npmModules: true });
|
|
53
53
|
expect(success).toBe(true);
|
|
54
54
|
expect(functionsConfig).toEqual([{ path: '/my-function' }, {}, { path: '/global-netlify' }]);
|
|
55
|
-
const modules = graph
|
|
55
|
+
const modules = graph?.modules.filter(({ kind, mediaType }) => kind === 'esm' && mediaType === 'TypeScript');
|
|
56
56
|
for (const key in functions) {
|
|
57
|
-
const graphEntry = modules
|
|
57
|
+
const graphEntry = modules?.some(({ local }) => local === functions[key].path);
|
|
58
58
|
expect(graphEntry).toBe(true);
|
|
59
59
|
}
|
|
60
60
|
const response1 = await fetch(`http://0.0.0.0:${port}/foo`, {
|
|
@@ -127,7 +127,7 @@ test('Serves edge functions in a monorepo setup', async () => {
|
|
|
127
127
|
expect(success).toBe(true);
|
|
128
128
|
expect(functionsConfig).toEqual([{ path: '/func1' }]);
|
|
129
129
|
for (const key in functions) {
|
|
130
|
-
const graphEntry = graph
|
|
130
|
+
const graphEntry = graph?.modules.some(({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path);
|
|
131
131
|
expect(graphEntry).toBe(true);
|
|
132
132
|
}
|
|
133
133
|
const response1 = await fetch(`http://0.0.0.0:${port}/func1`, {
|
package/dist/node/server/util.js
CHANGED
|
@@ -10,7 +10,7 @@ const SERVER_POLL_TIMEOUT = 1e4;
|
|
|
10
10
|
const isServerReady = async (port, successRef, ps) => {
|
|
11
11
|
// If the process has been killed or if it exited with an error, we return
|
|
12
12
|
// early with `success: false`.
|
|
13
|
-
if (
|
|
13
|
+
if (ps?.killed || (ps?.exitCode && ps.exitCode > 0)) {
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
16
|
try {
|
|
@@ -24,7 +24,7 @@ const isServerReady = async (port, successRef, ps) => {
|
|
|
24
24
|
};
|
|
25
25
|
const killProcess = (ps) => {
|
|
26
26
|
// If the process is no longer running, there's nothing left to do.
|
|
27
|
-
if (
|
|
27
|
+
if (ps?.exitCode !== null) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
return new Promise((resolve, reject) => {
|
|
@@ -10,7 +10,15 @@ test('`getLocalEntryPoint` returns a valid stage 2 file for local development',
|
|
|
10
10
|
// This is a fake bootstrap that we'll create just for the purpose of logging
|
|
11
11
|
// the functions and the metadata that are sent to the `boot` function.
|
|
12
12
|
const printer = `
|
|
13
|
-
export const boot = async (
|
|
13
|
+
export const boot = async (functionsLoader) => {
|
|
14
|
+
const functions = await functionsLoader()
|
|
15
|
+
const metadata = { functions: {} }
|
|
16
|
+
|
|
17
|
+
// Generate metadata for each function (simulating what the real bootstrap would have)
|
|
18
|
+
for (const name in functions) {
|
|
19
|
+
metadata.functions[name] = { url: new URL('./' + name + '.mjs', import.meta.url).href }
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
const responses = {}
|
|
15
23
|
|
|
16
24
|
for (const name in functions) {
|
package/dist/node/types.js
CHANGED
|
@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
const TYPES_URL = 'https://edge.netlify.com';
|
|
4
4
|
const ensureLatestTypes = async (deno, logger, customTypesURL) => {
|
|
5
|
-
const typesURL = customTypesURL
|
|
5
|
+
const typesURL = customTypesURL ?? TYPES_URL;
|
|
6
6
|
// eslint-disable-next-line prefer-const
|
|
7
7
|
let [localVersion, remoteVersion] = [await getLocalVersion(deno), ''];
|
|
8
8
|
try {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export default class ManifestValidationError extends Error {
|
|
2
|
+
customErrorInfo = {
|
|
3
|
+
type: 'functionsBundling',
|
|
4
|
+
};
|
|
2
5
|
constructor(message) {
|
|
3
6
|
super(`Validation of Edge Functions manifest failed\n${message}`);
|
|
4
|
-
this.customErrorInfo = {
|
|
5
|
-
type: 'functionsBundling',
|
|
6
|
-
};
|
|
7
7
|
this.name = 'ManifestValidationError';
|
|
8
8
|
// https://github.com/microsoft/TypeScript-wiki/blob/0fecbda7263f130c57394d779b8ca13f0a2e9123/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
|
9
9
|
Object.setPrototypeOf(this, ManifestValidationError.prototype);
|
package/dist/test/util.js
CHANGED
|
@@ -67,7 +67,6 @@ for (const functionName in manifest.functions) {
|
|
|
67
67
|
console.log(JSON.stringify(responses));
|
|
68
68
|
`;
|
|
69
69
|
export const getRouteMatcher = (manifest) => (candidate) => manifest.routes.find((route) => {
|
|
70
|
-
var _a, _b;
|
|
71
70
|
const regex = new RegExp(route.pattern);
|
|
72
71
|
if (!regex.test(candidate)) {
|
|
73
72
|
return false;
|
|
@@ -75,12 +74,11 @@ export const getRouteMatcher = (manifest) => (candidate) => manifest.routes.find
|
|
|
75
74
|
if (route.excluded_patterns.some((pattern) => new RegExp(pattern).test(candidate))) {
|
|
76
75
|
return false;
|
|
77
76
|
}
|
|
78
|
-
const excludedPatterns =
|
|
77
|
+
const excludedPatterns = manifest.function_config[route.function]?.excluded_patterns ?? [];
|
|
79
78
|
const isExcluded = excludedPatterns.some((pattern) => new RegExp(pattern).test(candidate));
|
|
80
79
|
return !isExcluded;
|
|
81
80
|
});
|
|
82
81
|
export const runESZIP = async (eszipPath, vendorDirectory) => {
|
|
83
|
-
var _a, _b, _c;
|
|
84
82
|
const tmpDir = await tmp.dir({ unsafeCleanup: true });
|
|
85
83
|
// Extract ESZIP into temporary directory.
|
|
86
84
|
const extractCommand = execa('deno', [
|
|
@@ -92,8 +90,8 @@ export const runESZIP = async (eszipPath, vendorDirectory) => {
|
|
|
92
90
|
eszipPath,
|
|
93
91
|
tmpDir.path,
|
|
94
92
|
]);
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
extractCommand.stderr?.pipe(stderr);
|
|
94
|
+
extractCommand.stdout?.pipe(stdout);
|
|
97
95
|
await extractCommand;
|
|
98
96
|
const virtualRootPath = join(tmpDir.path, 'source', 'root');
|
|
99
97
|
const stage2Path = join(virtualRootPath, '..', 'bootstrap-stage2');
|
|
@@ -109,13 +107,12 @@ export const runESZIP = async (eszipPath, vendorDirectory) => {
|
|
|
109
107
|
await fs.rename(stage2Path, `${stage2Path}.js`);
|
|
110
108
|
// Run function that imports the extracted stage 2 and invokes each function.
|
|
111
109
|
const evalCommand = execa('deno', ['eval', '--import-map', importMapPath, inspectESZIPFunction(stage2Path)]);
|
|
112
|
-
|
|
110
|
+
evalCommand.stderr?.pipe(stderr);
|
|
113
111
|
const result = await evalCommand;
|
|
114
112
|
await tmpDir.cleanup();
|
|
115
113
|
return JSON.parse(result.stdout);
|
|
116
114
|
};
|
|
117
115
|
export const runTarball = async (tarballPath) => {
|
|
118
|
-
var _a;
|
|
119
116
|
const tmpDir = await tmp.dir({ unsafeCleanup: true });
|
|
120
117
|
await tar.extract({
|
|
121
118
|
cwd: tmpDir.path,
|
|
@@ -124,7 +121,7 @@ export const runTarball = async (tarballPath) => {
|
|
|
124
121
|
const evalCommand = execa('deno', ['eval', inspectTarballFunction()], {
|
|
125
122
|
cwd: tmpDir.path,
|
|
126
123
|
});
|
|
127
|
-
|
|
124
|
+
evalCommand.stderr?.pipe(stderr);
|
|
128
125
|
const result = await evalCommand;
|
|
129
126
|
await tmpDir.cleanup();
|
|
130
127
|
return JSON.parse(result.stdout);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test": "test/node"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@netlify/edge-functions-bootstrap": "^2.
|
|
45
|
+
"@netlify/edge-functions-bootstrap": "^2.17.1",
|
|
46
46
|
"@types/node": "^18.19.111",
|
|
47
47
|
"@types/semver": "^7.3.9",
|
|
48
48
|
"@types/uuid": "^10.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"urlpattern-polyfill": "8.0.2",
|
|
81
81
|
"uuid": "^11.0.0"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "3dfa7bd62ac0c8fe4b9b61128b8c2315d1272b4f"
|
|
84
84
|
}
|