@netlify/edge-bundler 8.9.0 → 8.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.
- package/dist/node/bridge.test.js +4 -4
- package/dist/node/bundler.d.ts +19 -2
- package/dist/node/bundler.js +20 -10
- package/dist/node/bundler.test.js +18 -30
- package/dist/node/config.d.ts +5 -1
- package/dist/node/config.js +36 -10
- package/dist/node/config.test.js +107 -76
- package/dist/node/deploy_config.test.js +1 -0
- package/dist/node/downloader.test.js +2 -2
- package/dist/node/feature_flags.d.ts +9 -3
- package/dist/node/feature_flags.js +1 -2
- package/dist/node/formats/javascript.js +1 -1
- package/dist/node/main.test.js +2 -2
- package/dist/node/manifest.d.ts +1 -6
- package/dist/node/manifest.js +5 -2
- package/dist/node/manifest.test.js +19 -0
- package/dist/node/server/server.js +1 -1
- package/dist/node/types.test.js +6 -6
- package/dist/node/validation/manifest/index.test.js +4 -0
- package/dist/node/validation/manifest/schema.d.ts +3 -0
- package/dist/node/validation/manifest/schema.js +1 -0
- package/package.json +8 -5
package/dist/node/bridge.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
-
import
|
|
2
|
+
import { rm } from 'fs/promises';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { platform, env } from 'process';
|
|
5
5
|
import { PassThrough } from 'stream';
|
|
@@ -53,7 +53,7 @@ test('Does not inherit environment variables if `extendEnv` is false', async ()
|
|
|
53
53
|
});
|
|
54
54
|
output = output.trim().replace(/\n+/g, '\n');
|
|
55
55
|
expect(output).toBe('LULU=LALA');
|
|
56
|
-
await
|
|
56
|
+
await rm(tmpDir.path, { force: true, recursive: true });
|
|
57
57
|
});
|
|
58
58
|
test('Does inherit environment variables if `extendEnv` is true', async () => {
|
|
59
59
|
var _a;
|
|
@@ -78,7 +78,7 @@ test('Does inherit environment variables if `extendEnv` is true', async () => {
|
|
|
78
78
|
// lets remove holes, split lines and sort lines by name, as different OSes might order them different
|
|
79
79
|
const environmentVariables = output.trim().replace(/\n+/g, '\n').split('\n').sort();
|
|
80
80
|
expect(environmentVariables).toEqual(['LULU=LALA', 'TADA=TUDU']);
|
|
81
|
-
await
|
|
81
|
+
await rm(tmpDir.path, { force: true, recursive: true });
|
|
82
82
|
});
|
|
83
83
|
test('Does inherit environment variables if `extendEnv` is not set', async () => {
|
|
84
84
|
var _a;
|
|
@@ -103,5 +103,5 @@ test('Does inherit environment variables if `extendEnv` is not set', async () =>
|
|
|
103
103
|
// lets remove holes, split lines and sort lines by name, as different OSes might order them different
|
|
104
104
|
const environmentVariables = output.trim().replace(/\n+/g, '\n').split('\n').sort();
|
|
105
105
|
expect(environmentVariables).toEqual(['LULU=LALA', 'TADA=TUDU']);
|
|
106
|
-
await
|
|
106
|
+
await rm(tmpDir.path, { force: true, recursive: true });
|
|
107
107
|
});
|
package/dist/node/bundler.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OnAfterDownloadHook, OnBeforeDownloadHook } from './bridge.js';
|
|
2
2
|
import { Declaration } from './declaration.js';
|
|
3
|
+
import { EdgeFunction } from './edge_function.js';
|
|
3
4
|
import { FeatureFlags } from './feature_flags.js';
|
|
4
5
|
import { LogFunction } from './logger.js';
|
|
5
6
|
interface BundleOptions {
|
|
@@ -13,10 +14,26 @@ interface BundleOptions {
|
|
|
13
14
|
onAfterDownload?: OnAfterDownloadHook;
|
|
14
15
|
onBeforeDownload?: OnBeforeDownloadHook;
|
|
15
16
|
systemLogger?: LogFunction;
|
|
17
|
+
internalSrcFolder?: string;
|
|
16
18
|
}
|
|
17
|
-
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger, }?: BundleOptions) => Promise<{
|
|
18
|
-
functions:
|
|
19
|
+
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, }?: BundleOptions) => Promise<{
|
|
20
|
+
functions: EdgeFunction[];
|
|
19
21
|
manifest: import("./manifest.js").Manifest;
|
|
20
22
|
}>;
|
|
23
|
+
export declare const addGeneratorFieldIfMissing: (declaration: Declaration, functions: EdgeFunction[], internalFunctionsPath?: string) => {
|
|
24
|
+
generator: string | undefined;
|
|
25
|
+
cache?: string | undefined;
|
|
26
|
+
function: string;
|
|
27
|
+
name?: string | undefined;
|
|
28
|
+
path: string;
|
|
29
|
+
excludedPath?: string | undefined;
|
|
30
|
+
} | {
|
|
31
|
+
generator: string | undefined;
|
|
32
|
+
cache?: string | undefined;
|
|
33
|
+
function: string;
|
|
34
|
+
name?: string | undefined;
|
|
35
|
+
pattern: string;
|
|
36
|
+
excludedPattern?: string | undefined;
|
|
37
|
+
};
|
|
21
38
|
export { bundle };
|
|
22
39
|
export type { BundleOptions };
|
package/dist/node/bundler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
3
|
import commonPathPrefix from 'common-path-prefix';
|
|
4
|
+
import isPathInside from 'is-path-inside';
|
|
4
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
6
|
import { importMapSpecifier } from '../shared/consts.js';
|
|
6
7
|
import { DenoBridge } from './bridge.js';
|
|
@@ -14,7 +15,7 @@ import { ImportMap } from './import_map.js';
|
|
|
14
15
|
import { getLogger } from './logger.js';
|
|
15
16
|
import { writeManifest } from './manifest.js';
|
|
16
17
|
import { ensureLatestTypes } from './types.js';
|
|
17
|
-
const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], onAfterDownload, onBeforeDownload, systemLogger, } = {}) => {
|
|
18
|
+
const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, } = {}) => {
|
|
18
19
|
const logger = getLogger(systemLogger, debug);
|
|
19
20
|
const featureFlags = getFlags(inputFeatureFlags);
|
|
20
21
|
const options = {
|
|
@@ -24,7 +25,8 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
24
25
|
onAfterDownload,
|
|
25
26
|
onBeforeDownload,
|
|
26
27
|
};
|
|
27
|
-
|
|
28
|
+
const internalFunctionsPath = internalSrcFolder && resolve(internalSrcFolder);
|
|
29
|
+
if (cacheDirectory !== undefined) {
|
|
28
30
|
options.denoDir = join(cacheDirectory, 'deno_dir');
|
|
29
31
|
}
|
|
30
32
|
const deno = new DenoBridge(options);
|
|
@@ -59,17 +61,17 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
59
61
|
// rename the bundles to their permanent names.
|
|
60
62
|
await createFinalBundles([functionBundle], distDirectory, buildID);
|
|
61
63
|
// Retrieving a configuration object for each function.
|
|
62
|
-
const functionsConfig = await Promise.all(functions.map((func) =>
|
|
63
|
-
if (!featureFlags.edge_functions_config_export) {
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
return getFunctionConfig(func, importMap, deno, logger);
|
|
67
|
-
}));
|
|
64
|
+
const functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig(func, importMap, deno, logger, featureFlags)));
|
|
68
65
|
// Creating a hash of function names to configuration objects.
|
|
69
66
|
const functionsWithConfig = functions.reduce((acc, func, index) => ({ ...acc, [func.name]: functionsConfig[index] }), {});
|
|
70
67
|
// Creating a final declarations array by combining the TOML file with the
|
|
71
68
|
// deploy configuration API and the in-source configuration.
|
|
72
|
-
const
|
|
69
|
+
const declarationsFromConfig = getDeclarationsFromConfig(tomlDeclarations, functionsWithConfig, deployConfig);
|
|
70
|
+
// If any declarations are autogenerated and are missing the generator field
|
|
71
|
+
// add a default string.
|
|
72
|
+
const declarations = internalFunctionsPath
|
|
73
|
+
? declarationsFromConfig.map((declaration) => addGeneratorFieldIfMissing(declaration, functions, internalFunctionsPath))
|
|
74
|
+
: declarationsFromConfig;
|
|
73
75
|
const manifest = await writeManifest({
|
|
74
76
|
bundles: [functionBundle],
|
|
75
77
|
declarations,
|
|
@@ -105,4 +107,12 @@ const getBasePath = (sourceDirectories, inputBasePath) => {
|
|
|
105
107
|
}
|
|
106
108
|
return commonPathPrefix(sourceDirectories);
|
|
107
109
|
};
|
|
110
|
+
export const addGeneratorFieldIfMissing = (declaration, functions, internalFunctionsPath) => {
|
|
111
|
+
var _a;
|
|
112
|
+
const fullFuncPath = (_a = functions === null || functions === void 0 ? void 0 : functions.find((func) => func.name === declaration.function)) === null || _a === void 0 ? void 0 : _a.path;
|
|
113
|
+
// If function path is in the internalFunctionsPath, we assume it is autogenerated.
|
|
114
|
+
const isInternal = Boolean(internalFunctionsPath && fullFuncPath && isPathInside(fullFuncPath, internalFunctionsPath));
|
|
115
|
+
const generatorFallback = isInternal ? 'internalFunc' : undefined;
|
|
116
|
+
return { ...declaration, generator: declaration.generator || generatorFallback };
|
|
117
|
+
};
|
|
108
118
|
export { bundle };
|
|
@@ -147,8 +147,8 @@ test('Does not add a custom error property to system errors during bundling', as
|
|
|
147
147
|
expect(error).not.toBeInstanceOf(BundleError);
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
|
-
test('Uses the cache directory as the `DENO_DIR` value
|
|
151
|
-
expect.assertions(
|
|
150
|
+
test('Uses the cache directory as the `DENO_DIR` value', async () => {
|
|
151
|
+
expect.assertions(3);
|
|
152
152
|
const { basePath, cleanup, distPath } = await useFixture('with_import_maps');
|
|
153
153
|
const sourceDirectory = join(basePath, 'functions');
|
|
154
154
|
const cacheDir = await tmp.dir();
|
|
@@ -163,29 +163,12 @@ test('Uses the cache directory as the `DENO_DIR` value if the `edge_functions_ca
|
|
|
163
163
|
cacheDirectory: cacheDir.path,
|
|
164
164
|
configPath: join(sourceDirectory, 'config.json'),
|
|
165
165
|
};
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
expect(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
await fs.readdir(join(cacheDir.path, 'deno_dir'));
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
expect(error).toBeInstanceOf(Error);
|
|
176
|
-
}
|
|
177
|
-
// Run #2, feature flag on: The directory should be populated.
|
|
178
|
-
const result2 = await bundle([sourceDirectory], distPath, declarations, {
|
|
179
|
-
...options,
|
|
180
|
-
featureFlags: {
|
|
181
|
-
edge_functions_cache_deno_dir: true,
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
const outFiles2 = await fs.readdir(distPath);
|
|
185
|
-
expect(result2.functions.length).toBe(1);
|
|
186
|
-
expect(outFiles2.length).toBe(2);
|
|
187
|
-
const denoDir2 = await fs.readdir(join(cacheDir.path, 'deno_dir'));
|
|
188
|
-
expect(denoDir2.includes('gen')).toBe(true);
|
|
166
|
+
const result = await bundle([sourceDirectory], distPath, declarations, options);
|
|
167
|
+
const outFiles = await fs.readdir(distPath);
|
|
168
|
+
expect(result.functions.length).toBe(1);
|
|
169
|
+
expect(outFiles.length).toBe(2);
|
|
170
|
+
const denoDir = await fs.readdir(join(cacheDir.path, 'deno_dir'));
|
|
171
|
+
expect(denoDir.includes('gen')).toBe(true);
|
|
189
172
|
await cleanup();
|
|
190
173
|
});
|
|
191
174
|
test('Supports import maps with relative paths', async () => {
|
|
@@ -264,7 +247,7 @@ test('Processes a function that imports a custom layer', async () => {
|
|
|
264
247
|
path: '/func1',
|
|
265
248
|
},
|
|
266
249
|
];
|
|
267
|
-
const layer = { name: 'layer
|
|
250
|
+
const layer = { name: 'https://edge-function-layer-template.netlify.app/mod.ts', flag: 'edge-functions-layer-test' };
|
|
268
251
|
const result = await bundle([sourceDirectory], distPath, declarations, {
|
|
269
252
|
basePath,
|
|
270
253
|
configPath: join(sourceDirectory, 'config.json'),
|
|
@@ -292,17 +275,22 @@ test('Loads declarations and import maps from the deploy configuration', async (
|
|
|
292
275
|
const directories = [join(basePath, 'netlify', 'edge-functions'), join(basePath, '.netlify', 'edge-functions')];
|
|
293
276
|
const result = await bundle(directories, distPath, declarations, {
|
|
294
277
|
basePath,
|
|
295
|
-
configPath: join(basePath, '.netlify', 'edge-functions', '
|
|
278
|
+
configPath: join(basePath, '.netlify', 'edge-functions', 'manifest.json'),
|
|
279
|
+
internalSrcFolder: directories[1],
|
|
296
280
|
});
|
|
297
281
|
const generatedFiles = await fs.readdir(distPath);
|
|
298
|
-
expect(result.functions.length).toBe(
|
|
282
|
+
expect(result.functions.length).toBe(3);
|
|
299
283
|
expect(generatedFiles.length).toBe(2);
|
|
300
284
|
const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8');
|
|
301
285
|
const manifest = JSON.parse(manifestFile);
|
|
302
|
-
const { bundles, function_config: functionConfig } = manifest;
|
|
286
|
+
const { routes, bundles, function_config: functionConfig } = manifest;
|
|
303
287
|
expect(bundles.length).toBe(1);
|
|
304
288
|
expect(bundles[0].format).toBe('eszip2');
|
|
305
289
|
expect(generatedFiles.includes(bundles[0].asset)).toBe(true);
|
|
290
|
+
expect(routes[0].generator).toBeUndefined();
|
|
291
|
+
expect(routes[1].name).toBe('Function two');
|
|
292
|
+
expect(routes[1].generator).toBe('@netlify/fake-plugin@1.0.0');
|
|
293
|
+
expect(routes[2].generator).toBe('internalFunc');
|
|
306
294
|
// respects excludedPath from deploy config
|
|
307
295
|
expect(functionConfig.func2).toEqual({ excluded_patterns: ['^/func2/skip/?$'] });
|
|
308
296
|
await cleanup();
|
|
@@ -318,7 +306,7 @@ test("Ignores entries in `importMapPaths` that don't point to an existing import
|
|
|
318
306
|
helper: pathToFileURL(join(basePath, 'helper.ts')).toString(),
|
|
319
307
|
},
|
|
320
308
|
scopes: {
|
|
321
|
-
[pathToFileURL(join(sourceDirectory, 'func3')).toString()]: {
|
|
309
|
+
[pathToFileURL(join(sourceDirectory, 'func3/func3.ts')).toString()]: {
|
|
322
310
|
helper: pathToFileURL(join(basePath, 'helper2.ts')).toString(),
|
|
323
311
|
},
|
|
324
312
|
},
|
package/dist/node/config.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { DenoBridge } from './bridge.js';
|
|
2
2
|
import { EdgeFunction } from './edge_function.js';
|
|
3
|
+
import { FeatureFlags } from './feature_flags.js';
|
|
3
4
|
import { ImportMap } from './import_map.js';
|
|
4
5
|
import { Logger } from './logger.js';
|
|
5
6
|
export declare const enum Cache {
|
|
6
7
|
Off = "off",
|
|
7
8
|
Manual = "manual"
|
|
8
9
|
}
|
|
10
|
+
export type OnError = 'fail' | 'bypass' | `/${string}`;
|
|
11
|
+
export declare const isValidOnError: (value: unknown) => value is OnError;
|
|
9
12
|
export interface FunctionConfig {
|
|
10
13
|
cache?: Cache;
|
|
11
14
|
path?: string | string[];
|
|
12
15
|
excludedPath?: string | string[];
|
|
16
|
+
onError?: OnError;
|
|
13
17
|
}
|
|
14
|
-
export declare const getFunctionConfig: (func: EdgeFunction, importMap: ImportMap, deno: DenoBridge, log: Logger) => Promise<FunctionConfig>;
|
|
18
|
+
export declare const getFunctionConfig: (func: EdgeFunction, importMap: ImportMap, deno: DenoBridge, log: Logger, featureFlags: FeatureFlags) => Promise<FunctionConfig>;
|
package/dist/node/config.js
CHANGED
|
@@ -14,12 +14,19 @@ var ConfigExitCode;
|
|
|
14
14
|
ConfigExitCode[ConfigExitCode["SerializationError"] = 5] = "SerializationError";
|
|
15
15
|
ConfigExitCode[ConfigExitCode["InvalidDefaultExport"] = 6] = "InvalidDefaultExport";
|
|
16
16
|
})(ConfigExitCode || (ConfigExitCode = {}));
|
|
17
|
+
export const isValidOnError = (value) => {
|
|
18
|
+
if (typeof value === 'undefined')
|
|
19
|
+
return true;
|
|
20
|
+
if (typeof value !== 'string')
|
|
21
|
+
return false;
|
|
22
|
+
return value === 'fail' || value === 'bypass' || value.startsWith('/');
|
|
23
|
+
};
|
|
17
24
|
const getConfigExtractor = () => {
|
|
18
25
|
const packagePath = getPackagePath();
|
|
19
26
|
const configExtractorPath = join(packagePath, 'deno', 'config.ts');
|
|
20
27
|
return configExtractorPath;
|
|
21
28
|
};
|
|
22
|
-
export const getFunctionConfig = async (func, importMap, deno, log) => {
|
|
29
|
+
export const getFunctionConfig = async (func, importMap, deno, log, featureFlags) => {
|
|
23
30
|
// The extractor is a Deno script that will import the function and run its
|
|
24
31
|
// `config` export, if one exists.
|
|
25
32
|
const extractorPath = getConfigExtractor();
|
|
@@ -46,38 +53,57 @@ export const getFunctionConfig = async (func, importMap, deno, log) => {
|
|
|
46
53
|
JSON.stringify(ConfigExitCode),
|
|
47
54
|
], { rejectOnExitCode: false });
|
|
48
55
|
if (exitCode !== ConfigExitCode.Success) {
|
|
49
|
-
|
|
56
|
+
handleConfigError(func, exitCode, stderr, log, featureFlags);
|
|
50
57
|
return {};
|
|
51
58
|
}
|
|
52
59
|
if (stdout !== '') {
|
|
53
60
|
log.user(stdout);
|
|
54
61
|
}
|
|
62
|
+
let collectorData = {};
|
|
55
63
|
try {
|
|
56
|
-
const
|
|
57
|
-
|
|
64
|
+
const collectorDataJSON = await fs.readFile(collector.path, 'utf8');
|
|
65
|
+
collectorData = JSON.parse(collectorDataJSON);
|
|
58
66
|
}
|
|
59
67
|
catch {
|
|
60
|
-
|
|
61
|
-
return {};
|
|
68
|
+
handleConfigError(func, ConfigExitCode.UnhandledError, stderr, log, featureFlags);
|
|
62
69
|
}
|
|
63
70
|
finally {
|
|
64
71
|
await collector.cleanup();
|
|
65
72
|
}
|
|
73
|
+
if (!isValidOnError(collectorData.onError)) {
|
|
74
|
+
throw new BundleError(new Error(`The 'onError' configuration property in edge function at '${func.path}' 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.`));
|
|
75
|
+
}
|
|
76
|
+
return collectorData;
|
|
66
77
|
};
|
|
67
|
-
const
|
|
78
|
+
const handleConfigError = (func, exitCode, stderr, log, featureFlags) => {
|
|
68
79
|
switch (exitCode) {
|
|
69
80
|
case ConfigExitCode.ImportError:
|
|
70
|
-
log.user(`Could not load edge function at '${func.path}'`);
|
|
71
81
|
log.user(stderr);
|
|
82
|
+
if (featureFlags.edge_functions_invalid_config_throw) {
|
|
83
|
+
throw new BundleError(new Error(`Could not load edge function at '${func.path}'. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
log.user(`Could not load edge function at '${func.path}'`);
|
|
87
|
+
}
|
|
72
88
|
break;
|
|
73
89
|
case ConfigExitCode.NoConfig:
|
|
74
90
|
log.system(`No in-source config found for edge function at '${func.path}'`);
|
|
75
91
|
break;
|
|
76
92
|
case ConfigExitCode.InvalidExport:
|
|
77
|
-
|
|
93
|
+
if (featureFlags.edge_functions_invalid_config_throw) {
|
|
94
|
+
throw new BundleError(new Error(`The 'config' export in edge function at '${func.path}' must be an object. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
log.user(`'config' export in edge function at '${func.path}' must be an object`);
|
|
98
|
+
}
|
|
78
99
|
break;
|
|
79
100
|
case ConfigExitCode.SerializationError:
|
|
80
|
-
|
|
101
|
+
if (featureFlags.edge_functions_invalid_config_throw) {
|
|
102
|
+
throw new BundleError(new Error(`The 'config' object in the edge function at '${func.path}' must contain primitive values only. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
log.user(`'config' object in edge function at '${func.path}' must contain primitive values only`);
|
|
106
|
+
}
|
|
81
107
|
break;
|
|
82
108
|
case ConfigExitCode.InvalidDefaultExport:
|
|
83
109
|
throw new BundleError(new Error(`Default export in '${func.path}' must be a function. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
|
package/dist/node/config.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join, resolve } from 'path';
|
|
|
3
3
|
import { pathToFileURL } from 'url';
|
|
4
4
|
import { deleteAsync } from 'del';
|
|
5
5
|
import tmp from 'tmp-promise';
|
|
6
|
-
import { test, expect, vi } from 'vitest';
|
|
6
|
+
import { test, expect, vi, describe } from 'vitest';
|
|
7
7
|
import { fixturesDir, useFixture } from '../test/util.js';
|
|
8
8
|
import { DenoBridge } from './bridge.js';
|
|
9
9
|
import { bundle } from './bundler.js';
|
|
@@ -16,102 +16,138 @@ const importMapFile = {
|
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
18
|
const invalidDefaultExportErr = (path) => `Default export in '${path}' must be a function. More on the Edge Functions API at https://ntl.fyi/edge-api.`;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Empty config
|
|
32
|
-
{
|
|
33
|
-
expectedConfig: {},
|
|
34
|
-
name: 'func2',
|
|
35
|
-
source: `
|
|
19
|
+
const functions = [
|
|
20
|
+
{
|
|
21
|
+
testName: 'no config',
|
|
22
|
+
expectedConfig: {},
|
|
23
|
+
name: 'func1',
|
|
24
|
+
source: `export default async () => new Response("Hello from function one")`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
testName: 'empty config',
|
|
28
|
+
expectedConfig: {},
|
|
29
|
+
name: 'func2',
|
|
30
|
+
source: `
|
|
36
31
|
export default async () => new Response("Hello from function two")
|
|
37
32
|
|
|
38
33
|
export const config = {}
|
|
39
34
|
`,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
testName: 'config with wrong type (throw)',
|
|
38
|
+
expectedConfig: {},
|
|
39
|
+
name: 'func3',
|
|
40
|
+
source: `
|
|
41
|
+
export default async () => new Response("Hello from function two")
|
|
42
|
+
|
|
43
|
+
export const config = () => ({})
|
|
44
|
+
`,
|
|
45
|
+
error: /^The 'config' export in edge function at '(.*)' must be an object\. More on the Edge Functions API at https:\/\/ntl\.fyi\/edge-api\.$/,
|
|
46
|
+
featureFlags: {
|
|
47
|
+
edge_functions_invalid_config_throw: true,
|
|
40
48
|
},
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
testName: 'config with wrong type (log)',
|
|
52
|
+
expectedConfig: {},
|
|
53
|
+
name: 'func3',
|
|
54
|
+
source: `
|
|
46
55
|
export default async () => new Response("Hello from function two")
|
|
47
56
|
|
|
48
57
|
export const config = () => ({})
|
|
49
58
|
`,
|
|
50
|
-
|
|
59
|
+
userLog: /^'config' export in edge function at '(.*)' must be an object$/,
|
|
60
|
+
featureFlags: {
|
|
61
|
+
edge_functions_invalid_config_throw: false,
|
|
51
62
|
},
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
testName: 'config with syntax error (throw)',
|
|
66
|
+
expectedConfig: {},
|
|
67
|
+
name: 'func4',
|
|
68
|
+
source: `
|
|
69
|
+
export default async () => new Response("Hello from function two")
|
|
70
|
+
|
|
71
|
+
export const config
|
|
72
|
+
`,
|
|
73
|
+
error: /^Could not load edge function at '(.*)'\. More on the Edge Functions API at https:\/\/ntl\.fyi\/edge-api\.$/,
|
|
74
|
+
featureFlags: {
|
|
75
|
+
edge_functions_invalid_config_throw: true,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
testName: 'config with syntax error (log)',
|
|
80
|
+
expectedConfig: {},
|
|
81
|
+
name: 'func4',
|
|
82
|
+
source: `
|
|
57
83
|
export default async () => new Response("Hello from function two")
|
|
58
84
|
|
|
59
85
|
export const config
|
|
60
86
|
`,
|
|
61
|
-
|
|
87
|
+
userLog: /^Could not load edge function at '(.*)'$/,
|
|
88
|
+
featureFlags: {
|
|
89
|
+
edge_functions_invalid_config_throw: false,
|
|
62
90
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
testName: 'config with correct onError',
|
|
94
|
+
expectedConfig: { onError: 'bypass' },
|
|
95
|
+
name: 'func5',
|
|
96
|
+
source: `
|
|
97
|
+
export default async () => new Response("Hello from function two")
|
|
98
|
+
export const config = { onError: "bypass" }
|
|
99
|
+
`,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
testName: 'config with wrong onError',
|
|
103
|
+
name: 'func7',
|
|
104
|
+
source: `
|
|
105
|
+
export default async () => new Response("Hello from function two")
|
|
106
|
+
export const config = { onError: "foo" }
|
|
107
|
+
`,
|
|
108
|
+
error: /The 'onError' configuration property in edge function at .*/,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
testName: 'config with `path`',
|
|
112
|
+
expectedConfig: { path: '/home' },
|
|
113
|
+
name: 'func6',
|
|
114
|
+
source: `
|
|
68
115
|
export default async () => new Response("Hello from function three")
|
|
69
116
|
|
|
70
117
|
export const config = { path: "/home" }
|
|
71
118
|
`,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
describe('`getFunctionConfig` extracts configuration properties from function file', () => {
|
|
122
|
+
test.each(functions)('$testName', async (func) => {
|
|
123
|
+
const { path: tmpDir } = await tmp.dir();
|
|
124
|
+
const deno = new DenoBridge({
|
|
125
|
+
cacheDirectory: tmpDir,
|
|
126
|
+
});
|
|
75
127
|
const logger = {
|
|
76
128
|
user: vi.fn().mockResolvedValue(null),
|
|
77
129
|
system: vi.fn().mockResolvedValue(null),
|
|
78
130
|
};
|
|
79
131
|
const path = join(tmpDir, `${func.name}.js`);
|
|
80
132
|
await fs.writeFile(path, func.source);
|
|
81
|
-
const
|
|
133
|
+
const funcCall = () => getFunctionConfig({
|
|
82
134
|
name: func.name,
|
|
83
135
|
path,
|
|
84
|
-
}, new ImportMap([importMapFile]), deno, logger);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
136
|
+
}, new ImportMap([importMapFile]), deno, logger, func.featureFlags || {});
|
|
137
|
+
if (func.error) {
|
|
138
|
+
await expect(funcCall()).rejects.toThrowError(func.error);
|
|
139
|
+
}
|
|
140
|
+
else if (func.userLog) {
|
|
141
|
+
await expect(funcCall()).resolves.not.toThrowError();
|
|
142
|
+
expect(logger.user).toHaveBeenCalledWith(expect.stringMatching(func.userLog));
|
|
88
143
|
}
|
|
89
144
|
else {
|
|
145
|
+
const config = await funcCall();
|
|
146
|
+
expect(config).toEqual(func.expectedConfig);
|
|
90
147
|
expect(logger.user).not.toHaveBeenCalled();
|
|
91
148
|
}
|
|
92
|
-
|
|
93
|
-
await deleteAsync(tmpDir, { force: true });
|
|
94
|
-
});
|
|
95
|
-
test('Ignores function paths from the in-source `config` function if the feature flag is off', async () => {
|
|
96
|
-
const { basePath, cleanup, distPath } = await useFixture('with_config');
|
|
97
|
-
const userDirectory = resolve(basePath, 'netlify', 'edge-functions');
|
|
98
|
-
const internalDirectory = resolve(basePath, '.netlify', 'edge-functions');
|
|
99
|
-
const declarations = [];
|
|
100
|
-
const result = await bundle([internalDirectory, userDirectory], distPath, declarations, {
|
|
101
|
-
basePath,
|
|
102
|
-
configPath: join(internalDirectory, 'config.json'),
|
|
149
|
+
await deleteAsync(tmpDir, { force: true });
|
|
103
150
|
});
|
|
104
|
-
const generatedFiles = await fs.readdir(distPath);
|
|
105
|
-
expect(result.functions.length).toBe(7);
|
|
106
|
-
expect(generatedFiles.length).toBe(2);
|
|
107
|
-
const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8');
|
|
108
|
-
const manifest = JSON.parse(manifestFile);
|
|
109
|
-
const { bundles, routes } = manifest;
|
|
110
|
-
expect(bundles.length).toBe(1);
|
|
111
|
-
expect(bundles[0].format).toBe('eszip2');
|
|
112
|
-
expect(generatedFiles.includes(bundles[0].asset)).toBe(true);
|
|
113
|
-
expect(routes.length).toBe(0);
|
|
114
|
-
await cleanup();
|
|
115
151
|
});
|
|
116
152
|
test('Loads function paths from the in-source `config` function', async () => {
|
|
117
153
|
const { basePath, cleanup, distPath } = await useFixture('with_config');
|
|
@@ -130,9 +166,6 @@ test('Loads function paths from the in-source `config` function', async () => {
|
|
|
130
166
|
const result = await bundle([internalDirectory, userDirectory], distPath, declarations, {
|
|
131
167
|
basePath,
|
|
132
168
|
configPath: join(internalDirectory, 'config.json'),
|
|
133
|
-
featureFlags: {
|
|
134
|
-
edge_functions_config_export: true,
|
|
135
|
-
},
|
|
136
169
|
});
|
|
137
170
|
const generatedFiles = await fs.readdir(distPath);
|
|
138
171
|
expect(result.functions.length).toBe(7);
|
|
@@ -176,12 +209,10 @@ test('Passes validation if default export exists and is a function', async () =>
|
|
|
176
209
|
};
|
|
177
210
|
const path = join(tmpDir, `${func.name}.ts`);
|
|
178
211
|
await fs.writeFile(path, func.source);
|
|
179
|
-
expect(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}, new ImportMap([importMapFile]), deno, logger);
|
|
184
|
-
}).not.toThrow();
|
|
212
|
+
await expect(getFunctionConfig({
|
|
213
|
+
name: func.name,
|
|
214
|
+
path,
|
|
215
|
+
}, new ImportMap([importMapFile]), deno, logger, {})).resolves.not.toThrow();
|
|
185
216
|
await deleteAsync(tmpDir, { force: true });
|
|
186
217
|
});
|
|
187
218
|
test('Fails validation if default export is not function', async () => {
|
|
@@ -205,7 +236,7 @@ test('Fails validation if default export is not function', async () => {
|
|
|
205
236
|
const config = getFunctionConfig({
|
|
206
237
|
name: func.name,
|
|
207
238
|
path,
|
|
208
|
-
}, new ImportMap([importMapFile]), deno, logger);
|
|
239
|
+
}, new ImportMap([importMapFile]), deno, logger, {});
|
|
209
240
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
210
241
|
await deleteAsync(tmpDir, { force: true });
|
|
211
242
|
});
|
|
@@ -229,7 +260,7 @@ test('Fails validation if default export is not present', async () => {
|
|
|
229
260
|
const config = getFunctionConfig({
|
|
230
261
|
name: func.name,
|
|
231
262
|
path,
|
|
232
|
-
}, new ImportMap([importMapFile]), deno, logger);
|
|
263
|
+
}, new ImportMap([importMapFile]), deno, logger, {});
|
|
233
264
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
234
265
|
await deleteAsync(tmpDir, { force: true });
|
|
235
266
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rm } from 'fs/promises';
|
|
2
2
|
import { platform } from 'process';
|
|
3
3
|
import { PassThrough } from 'stream';
|
|
4
4
|
import { execa } from 'execa';
|
|
@@ -20,7 +20,7 @@ beforeEach(async (ctx) => {
|
|
|
20
20
|
ctx.tmpDir = tmpDir.path;
|
|
21
21
|
});
|
|
22
22
|
afterEach(async (ctx) => {
|
|
23
|
-
await
|
|
23
|
+
await rm(ctx.tmpDir, { force: true, recursive: true });
|
|
24
24
|
});
|
|
25
25
|
test('tries downloading binary up to 4 times', async (ctx) => {
|
|
26
26
|
nock.disableNetConnect();
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
declare const defaultFlags:
|
|
1
|
+
declare const defaultFlags: {
|
|
2
|
+
edge_functions_fail_unsupported_regex: boolean;
|
|
3
|
+
edge_functions_invalid_config_throw: boolean;
|
|
4
|
+
};
|
|
2
5
|
type FeatureFlag = keyof typeof defaultFlags;
|
|
3
|
-
type FeatureFlags = Record<FeatureFlag, boolean
|
|
4
|
-
declare const getFlags: (input?: Record<string, boolean>, flags?:
|
|
6
|
+
type FeatureFlags = Partial<Record<FeatureFlag, boolean>>;
|
|
7
|
+
declare const getFlags: (input?: Record<string, boolean>, flags?: {
|
|
8
|
+
edge_functions_fail_unsupported_regex: boolean;
|
|
9
|
+
edge_functions_invalid_config_throw: boolean;
|
|
10
|
+
}) => FeatureFlags;
|
|
5
11
|
export { defaultFlags, getFlags };
|
|
6
12
|
export type { FeatureFlag, FeatureFlags };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const defaultFlags = {
|
|
2
|
-
edge_functions_cache_deno_dir: false,
|
|
3
|
-
edge_functions_config_export: false,
|
|
4
2
|
edge_functions_fail_unsupported_regex: false,
|
|
3
|
+
edge_functions_invalid_config_throw: false,
|
|
5
4
|
};
|
|
6
5
|
const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({
|
|
7
6
|
...result,
|
|
@@ -3,7 +3,7 @@ import { join } from 'path';
|
|
|
3
3
|
import { env } from 'process';
|
|
4
4
|
import { pathToFileURL } from 'url';
|
|
5
5
|
import { deleteAsync } from 'del';
|
|
6
|
-
const BOOTSTRAP_LATEST = 'https://
|
|
6
|
+
const BOOTSTRAP_LATEST = 'https://64071f3033a1800007cc20f8--edge.netlify.com/bootstrap/index-combined.ts';
|
|
7
7
|
const defaultFormatExportTypeError = (name) => `The Edge Function "${name}" has failed to load. Does it have a function as the default export?`;
|
|
8
8
|
const defaultFormatImpoortError = (name) => `There was an error with Edge Function "${name}".`;
|
|
9
9
|
const generateStage2 = async ({ distDirectory, fileName, formatExportTypeError, formatImportError, functions, }) => {
|
package/dist/node/main.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
|
-
import
|
|
2
|
+
import { rm } from 'fs/promises';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { platform } from 'process';
|
|
5
5
|
import { PassThrough } from 'stream';
|
|
@@ -43,5 +43,5 @@ test('Downloads the Deno CLI on demand and caches it for subsequent calls', asyn
|
|
|
43
43
|
expect((_d = output2 === null || output2 === void 0 ? void 0 : output2.stdout) !== null && _d !== void 0 ? _d : '').toMatch(expectedOutput);
|
|
44
44
|
expect(beforeDownload).toHaveBeenCalledTimes(1);
|
|
45
45
|
expect(afterDownload).toHaveBeenCalledTimes(1);
|
|
46
|
-
await
|
|
46
|
+
await rm(tmpDir.path, { force: true, recursive: true });
|
|
47
47
|
});
|
package/dist/node/manifest.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface Route {
|
|
|
12
12
|
}
|
|
13
13
|
interface EdgeFunctionConfig {
|
|
14
14
|
excluded_patterns: string[];
|
|
15
|
+
on_error?: string;
|
|
15
16
|
}
|
|
16
17
|
interface Manifest {
|
|
17
18
|
bundler_version: string;
|
|
@@ -37,12 +38,6 @@ interface GenerateManifestOptions {
|
|
|
37
38
|
importMap?: string;
|
|
38
39
|
layers?: Layer[];
|
|
39
40
|
}
|
|
40
|
-
interface Route {
|
|
41
|
-
function: string;
|
|
42
|
-
name?: string;
|
|
43
|
-
pattern: string;
|
|
44
|
-
generator?: string;
|
|
45
|
-
}
|
|
46
41
|
declare const generateManifest: ({ bundles, declarations, featureFlags, functions, functionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest;
|
|
47
42
|
interface WriteManifestOptions extends GenerateManifestOptions {
|
|
48
43
|
distDirectory: string;
|
package/dist/node/manifest.js
CHANGED
|
@@ -12,7 +12,7 @@ const serializePattern = (pattern) => pattern.replace(/\\\//g, '/');
|
|
|
12
12
|
const sanitizeEdgeFunctionConfig = (config) => {
|
|
13
13
|
const newConfig = {};
|
|
14
14
|
for (const [name, functionConfig] of Object.entries(config)) {
|
|
15
|
-
if (functionConfig.excluded_patterns.length !== 0) {
|
|
15
|
+
if (functionConfig.excluded_patterns.length !== 0 || functionConfig.on_error) {
|
|
16
16
|
newConfig[name] = functionConfig;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -22,12 +22,15 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
22
22
|
const preCacheRoutes = [];
|
|
23
23
|
const postCacheRoutes = [];
|
|
24
24
|
const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
|
|
25
|
-
for (const [name, { excludedPath }] of Object.entries(functionConfig)) {
|
|
25
|
+
for (const [name, { excludedPath, onError }] of Object.entries(functionConfig)) {
|
|
26
26
|
if (excludedPath) {
|
|
27
27
|
const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
|
|
28
28
|
const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern);
|
|
29
29
|
manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
|
|
30
30
|
}
|
|
31
|
+
if (onError) {
|
|
32
|
+
manifestFunctionConfig[name].on_error = onError;
|
|
33
|
+
}
|
|
31
34
|
}
|
|
32
35
|
declarations.forEach((declaration) => {
|
|
33
36
|
const func = functions.find(({ name }) => declaration.function === name);
|
|
@@ -84,6 +84,25 @@ test('Generates a manifest with excluded paths and patterns', () => {
|
|
|
84
84
|
});
|
|
85
85
|
expect(manifest.bundler_version).toBe(env.npm_package_version);
|
|
86
86
|
});
|
|
87
|
+
test('Includes failure modes in manifest', () => {
|
|
88
|
+
const functions = [
|
|
89
|
+
{ name: 'func-1', path: '/path/to/func-1.ts' },
|
|
90
|
+
{ name: 'func-2', path: '/path/to/func-2.ts' },
|
|
91
|
+
];
|
|
92
|
+
const declarations = [
|
|
93
|
+
{ function: 'func-1', name: 'Display Name', path: '/f1/*' },
|
|
94
|
+
{ function: 'func-2', pattern: '^/f2/.*/?$' },
|
|
95
|
+
];
|
|
96
|
+
const functionConfig = {
|
|
97
|
+
'func-1': {
|
|
98
|
+
onError: '/custom-error',
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const manifest = generateManifest({ bundles: [], declarations, functions, functionConfig });
|
|
102
|
+
expect(manifest.function_config).toEqual({
|
|
103
|
+
'func-1': { excluded_patterns: [], on_error: '/custom-error' },
|
|
104
|
+
});
|
|
105
|
+
});
|
|
87
106
|
test('Excludes functions for which there are function files but no matching config declarations', () => {
|
|
88
107
|
const bundle1 = {
|
|
89
108
|
extension: '.ext2',
|
|
@@ -42,7 +42,7 @@ const prepareServer = ({ deno, distDirectory, flags: denoFlags, formatExportType
|
|
|
42
42
|
});
|
|
43
43
|
let functionsConfig = [];
|
|
44
44
|
if (options.getFunctionsConfig) {
|
|
45
|
-
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig(func, importMap, deno, logger)));
|
|
45
|
+
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig(func, importMap, deno, logger, {})));
|
|
46
46
|
}
|
|
47
47
|
const success = await waitForServer(port, processRef.ps);
|
|
48
48
|
return {
|
package/dist/node/types.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, rm, writeFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import nock from 'nock';
|
|
4
4
|
import tmp from 'tmp-promise';
|
|
@@ -18,20 +18,20 @@ test('`ensureLatestTypes` updates the Deno CLI cache if the local version of typ
|
|
|
18
18
|
// @ts-expect-error return value not used
|
|
19
19
|
const mock = vi.spyOn(deno, 'run').mockResolvedValue({});
|
|
20
20
|
await ensureLatestTypes(deno, testLogger, mockURL);
|
|
21
|
-
const versionFile = await
|
|
21
|
+
const versionFile = await readFile(join(tmpDir.path, 'types-version.txt'), 'utf8');
|
|
22
22
|
expect(latestVersionMock.isDone()).toBe(true);
|
|
23
23
|
expect(mock).toHaveBeenCalledTimes(1);
|
|
24
24
|
expect(mock).toHaveBeenCalledWith(['cache', '-r', mockURL]);
|
|
25
25
|
expect(versionFile).toBe(mockVersion);
|
|
26
26
|
mock.mockRestore();
|
|
27
|
-
await
|
|
27
|
+
await rm(tmpDir.path, { force: true, recursive: true });
|
|
28
28
|
});
|
|
29
29
|
test('`ensureLatestTypes` does not update the Deno CLI cache if the local version of types is up-to-date', async () => {
|
|
30
30
|
const mockURL = 'https://edge.netlify';
|
|
31
31
|
const mockVersion = '987654321';
|
|
32
32
|
const tmpDir = await tmp.dir();
|
|
33
33
|
const versionFilePath = join(tmpDir.path, 'types-version.txt');
|
|
34
|
-
await
|
|
34
|
+
await writeFile(versionFilePath, mockVersion);
|
|
35
35
|
const latestVersionMock = nock(mockURL).get('/version.txt').reply(200, mockVersion);
|
|
36
36
|
const deno = new DenoBridge({
|
|
37
37
|
cacheDirectory: tmpDir.path,
|
|
@@ -43,7 +43,7 @@ test('`ensureLatestTypes` does not update the Deno CLI cache if the local versio
|
|
|
43
43
|
expect(latestVersionMock.isDone()).toBe(true);
|
|
44
44
|
expect(mock).not.toHaveBeenCalled();
|
|
45
45
|
mock.mockRestore();
|
|
46
|
-
await
|
|
46
|
+
await rm(tmpDir.path, { force: true, recursive: true });
|
|
47
47
|
});
|
|
48
48
|
test('`ensureLatestTypes` does not throw if the types URL is not available', async () => {
|
|
49
49
|
const mockURL = 'https://edge.netlify';
|
|
@@ -59,5 +59,5 @@ test('`ensureLatestTypes` does not throw if the types URL is not available', asy
|
|
|
59
59
|
expect(latestVersionMock.isDone()).toBe(true);
|
|
60
60
|
expect(mock).not.toHaveBeenCalled();
|
|
61
61
|
mock.mockRestore();
|
|
62
|
-
await
|
|
62
|
+
await rm(tmpDir.path, { force: true, recursive: true });
|
|
63
63
|
});
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import { test, expect, describe } from 'vitest';
|
|
2
3
|
import { validateManifest, ManifestValidationError } from './index.js';
|
|
4
|
+
// We need to disable all color outputs for the tests as they are different on different platforms, CI, etc.
|
|
5
|
+
// This only works if this is the same instance of chalk that better-ajv-errors uses
|
|
6
|
+
chalk.level = 0;
|
|
3
7
|
// Factory so we have a new object per test
|
|
4
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
9
|
const getBaseManifest = () => ({
|
|
@@ -34,6 +34,7 @@ const functionConfigSchema = {
|
|
|
34
34
|
errorMessage: 'excluded_patterns needs to be an array of regex that starts with ^ and ends with $ without any additional slashes before and afterwards',
|
|
35
35
|
},
|
|
36
36
|
},
|
|
37
|
+
on_error: { type: 'string' },
|
|
37
38
|
},
|
|
38
39
|
};
|
|
39
40
|
const layersSchema = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.11.0",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc",
|
|
17
|
+
"build:dev": "tsc -w",
|
|
17
18
|
"prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/",
|
|
18
19
|
"prepublishOnly": "npm ci && npm test",
|
|
19
20
|
"prepack": "npm run build",
|
|
@@ -28,10 +29,10 @@
|
|
|
28
29
|
"format:fix:prettier": "cross-env-shell prettier --write $npm_package_config_prettier",
|
|
29
30
|
"test:dev": "run-s test:dev:*",
|
|
30
31
|
"test:ci": "run-s test:ci:*",
|
|
31
|
-
"test:dev:vitest": "
|
|
32
|
-
"test:dev:vitest:watch": "
|
|
32
|
+
"test:dev:vitest": "vitest run",
|
|
33
|
+
"test:dev:vitest:watch": "vitest watch",
|
|
33
34
|
"test:dev:deno": "deno test --allow-all deno",
|
|
34
|
-
"test:ci:vitest": "
|
|
35
|
+
"test:ci:vitest": "vitest run",
|
|
35
36
|
"test:ci:deno": "deno test --allow-all deno",
|
|
36
37
|
"test:integration": "node --experimental-modules test/integration/test.js"
|
|
37
38
|
},
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@commitlint/cli": "^17.0.0",
|
|
54
55
|
"@commitlint/config-conventional": "^17.0.0",
|
|
55
|
-
"@netlify/eslint-config-node": "^
|
|
56
|
+
"@netlify/eslint-config-node": "^7.0.1",
|
|
56
57
|
"@types/glob-to-regexp": "^0.4.1",
|
|
57
58
|
"@types/node": "^14.18.32",
|
|
58
59
|
"@types/semver": "^7.3.9",
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
"@types/uuid": "^9.0.0",
|
|
61
62
|
"@vitest/coverage-c8": "^0.25.0",
|
|
62
63
|
"archiver": "^5.3.1",
|
|
64
|
+
"chalk": "^4.1.2",
|
|
63
65
|
"cpy": "^9.0.1",
|
|
64
66
|
"cross-env": "^7.0.3",
|
|
65
67
|
"husky": "^8.0.0",
|
|
@@ -84,6 +86,7 @@
|
|
|
84
86
|
"find-up": "^6.3.0",
|
|
85
87
|
"get-port": "^6.1.2",
|
|
86
88
|
"glob-to-regexp": "^0.4.1",
|
|
89
|
+
"is-path-inside": "^4.0.0",
|
|
87
90
|
"jsonc-parser": "^3.2.0",
|
|
88
91
|
"node-fetch": "^3.1.1",
|
|
89
92
|
"node-stream-zip": "^1.15.0",
|