@netlify/edge-bundler 8.18.0 → 8.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno/bundle.ts +9 -1
- package/deno/config.ts +4 -2
- package/deno/lib/consts.ts +2 -1
- package/deno/lib/stage2.ts +7 -2
- package/dist/node/bundle_error.js +4 -0
- package/dist/node/bundler.d.ts +1 -1
- package/dist/node/bundler.js +3 -3
- package/dist/node/bundler.test.js +12 -1
- package/dist/node/config.d.ts +3 -2
- package/dist/node/config.js +1 -2
- package/dist/node/config.test.js +4 -6
- package/dist/node/declaration.d.ts +2 -1
- package/dist/node/declaration.js +4 -1
- package/dist/node/feature_flags.d.ts +0 -2
- package/dist/node/feature_flags.js +0 -1
- package/dist/node/import_map.js +2 -1
- package/dist/node/import_map.test.js +10 -5
- package/dist/node/manifest.d.ts +1 -0
- package/dist/node/manifest.js +36 -31
- package/dist/node/manifest.test.js +0 -9
- package/dist/node/server/server.js +1 -1
- package/dist/node/validation/manifest/schema.d.ts +14 -0
- package/dist/node/validation/manifest/schema.js +4 -0
- package/package.json +1 -2
package/deno/bundle.ts
CHANGED
|
@@ -3,4 +3,12 @@ import { writeStage2 } from './lib/stage2.ts'
|
|
|
3
3
|
const [payload] = Deno.args
|
|
4
4
|
const { basePath, destPath, externals, functions, importMapData } = JSON.parse(payload)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
try {
|
|
7
|
+
await writeStage2({ basePath, destPath, externals, functions, importMapData })
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error instanceof Error && error.message.includes("The module's source code could not be parsed")) {
|
|
10
|
+
delete error.stack
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
throw error
|
|
14
|
+
}
|
package/deno/config.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
// this needs to be updated whenever there's a change to globalThis.Netlify in bootstrap
|
|
2
|
+
import { Netlify } from "https://64e8753eae24930008fac6d9--edge.netlify.app/bootstrap/index-combined.ts"
|
|
3
|
+
|
|
4
|
+
const [functionURL, collectorURL, rawExitCodes] = Deno.args
|
|
2
5
|
const exitCodes = JSON.parse(rawExitCodes)
|
|
3
6
|
|
|
4
|
-
const { Netlify } = await import(bootstrapURL)
|
|
5
7
|
globalThis.Netlify = Netlify
|
|
6
8
|
|
|
7
9
|
let func
|
package/deno/lib/consts.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const LEGACY_PUBLIC_SPECIFIER = 'netlify:edge'
|
|
2
|
+
export const PUBLIC_SPECIFIER = '@netlify/edge-functions'
|
|
2
3
|
export const STAGE1_SPECIFIER = 'netlify:bootstrap-stage1'
|
|
3
4
|
export const STAGE2_SPECIFIER = 'netlify:bootstrap-stage2'
|
|
4
5
|
export const virtualRoot = 'file:///root/'
|
package/deno/lib/stage2.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as path from 'https://deno.land/std@0.177.0/path/mod.ts'
|
|
|
4
4
|
|
|
5
5
|
import type { InputFunction, WriteStage2Options } from '../../shared/stage2.ts'
|
|
6
6
|
import { importMapSpecifier, virtualRoot } from '../../shared/consts.ts'
|
|
7
|
-
import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts'
|
|
7
|
+
import { LEGACY_PUBLIC_SPECIFIER, PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts'
|
|
8
8
|
import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts'
|
|
9
9
|
|
|
10
10
|
interface FunctionReference {
|
|
@@ -75,7 +75,12 @@ const stage2Loader = (basePath: string, functions: InputFunction[], externals: S
|
|
|
75
75
|
return inlineModule(specifier, importMapData)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
if (
|
|
78
|
+
if (
|
|
79
|
+
specifier === LEGACY_PUBLIC_SPECIFIER ||
|
|
80
|
+
specifier === PUBLIC_SPECIFIER ||
|
|
81
|
+
externals.has(specifier) ||
|
|
82
|
+
specifier.startsWith('node:')
|
|
83
|
+
) {
|
|
79
84
|
return {
|
|
80
85
|
kind: 'external',
|
|
81
86
|
specifier,
|
|
@@ -20,6 +20,10 @@ class BundleError extends Error {
|
|
|
20
20
|
*/
|
|
21
21
|
const wrapBundleError = (input, options) => {
|
|
22
22
|
if (input instanceof Error) {
|
|
23
|
+
if (input.message.includes("The module's source code could not be parsed")) {
|
|
24
|
+
// eslint-disable-next-line no-param-reassign
|
|
25
|
+
input.message = input.stderr;
|
|
26
|
+
}
|
|
23
27
|
return new BundleError(input, options);
|
|
24
28
|
}
|
|
25
29
|
return input;
|
package/dist/node/bundler.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ interface BundleOptions {
|
|
|
16
16
|
internalSrcFolder?: string;
|
|
17
17
|
bootstrapURL?: string;
|
|
18
18
|
}
|
|
19
|
-
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder,
|
|
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
20
|
functions: import("./edge_function.js").EdgeFunction[];
|
|
21
21
|
manifest: import("./manifest.js").Manifest;
|
|
22
22
|
}>;
|
package/dist/node/bundler.js
CHANGED
|
@@ -14,7 +14,7 @@ import { ImportMap } from './import_map.js';
|
|
|
14
14
|
import { getLogger } from './logger.js';
|
|
15
15
|
import { writeManifest } from './manifest.js';
|
|
16
16
|
import { ensureLatestTypes } from './types.js';
|
|
17
|
-
const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder,
|
|
17
|
+
const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, } = {}) => {
|
|
18
18
|
const logger = getLogger(systemLogger, debug);
|
|
19
19
|
const featureFlags = getFlags(inputFeatureFlags);
|
|
20
20
|
const options = {
|
|
@@ -63,8 +63,8 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
63
63
|
await createFinalBundles([functionBundle], distDirectory, buildID);
|
|
64
64
|
// Retrieving a configuration object for each function.
|
|
65
65
|
// Run `getFunctionConfig` in parallel as it is a non-trivial operation and spins up deno
|
|
66
|
-
const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger
|
|
67
|
-
const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger
|
|
66
|
+
const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger })]);
|
|
67
|
+
const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger })]);
|
|
68
68
|
// Creating a hash of function names to configuration objects.
|
|
69
69
|
const internalFunctionsWithConfig = Object.fromEntries(await Promise.all(internalConfigPromises));
|
|
70
70
|
const userFunctionsWithConfig = Object.fromEntries(await Promise.all(userConfigPromises));
|
|
@@ -68,7 +68,8 @@ test('Uses the vendored eszip module instead of fetching it from deno.land', asy
|
|
|
68
68
|
await cleanup();
|
|
69
69
|
});
|
|
70
70
|
test('Adds a custom error property to user errors during bundling', async () => {
|
|
71
|
-
|
|
71
|
+
process.env.NO_COLOR = 'true';
|
|
72
|
+
expect.assertions(3);
|
|
72
73
|
const { basePath, cleanup, distPath } = await useFixture('invalid_functions');
|
|
73
74
|
const sourceDirectory = join(basePath, 'functions');
|
|
74
75
|
const declarations = [
|
|
@@ -82,6 +83,16 @@ test('Adds a custom error property to user errors during bundling', async () =>
|
|
|
82
83
|
}
|
|
83
84
|
catch (error) {
|
|
84
85
|
expect(error).toBeInstanceOf(BundleError);
|
|
86
|
+
const [messageBeforeStack] = error.message.split('at <anonymous> (file://');
|
|
87
|
+
expect(messageBeforeStack).toMatchInlineSnapshot(`
|
|
88
|
+
"error: Uncaught (in promise) Error: The module's source code could not be parsed: Unexpected eof at file:///root/functions/func1.ts:1:27
|
|
89
|
+
|
|
90
|
+
export default async () =>
|
|
91
|
+
~
|
|
92
|
+
const ret = new Error(getStringFromWasm0(arg0, arg1));
|
|
93
|
+
^
|
|
94
|
+
"
|
|
95
|
+
`);
|
|
85
96
|
expect(error.customErrorInfo).toEqual({
|
|
86
97
|
location: {
|
|
87
98
|
format: 'eszip',
|
package/dist/node/config.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare const enum Cache {
|
|
|
6
6
|
Off = "off",
|
|
7
7
|
Manual = "manual"
|
|
8
8
|
}
|
|
9
|
+
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
|
|
9
10
|
export type Path = `/${string}`;
|
|
10
11
|
export type OnError = 'fail' | 'bypass' | Path;
|
|
11
12
|
export declare const isValidOnError: (value: unknown) => value is OnError;
|
|
@@ -16,11 +17,11 @@ export interface FunctionConfig {
|
|
|
16
17
|
onError?: OnError;
|
|
17
18
|
name?: string;
|
|
18
19
|
generator?: string;
|
|
20
|
+
method?: HTTPMethod | HTTPMethod[];
|
|
19
21
|
}
|
|
20
|
-
export declare const getFunctionConfig: ({ func, importMap, deno,
|
|
22
|
+
export declare const getFunctionConfig: ({ func, importMap, deno, log, }: {
|
|
21
23
|
func: EdgeFunction;
|
|
22
24
|
importMap: ImportMap;
|
|
23
25
|
deno: DenoBridge;
|
|
24
|
-
bootstrapURL: string;
|
|
25
26
|
log: Logger;
|
|
26
27
|
}) => Promise<FunctionConfig>;
|
package/dist/node/config.js
CHANGED
|
@@ -26,7 +26,7 @@ const getConfigExtractor = () => {
|
|
|
26
26
|
const configExtractorPath = join(packagePath, 'deno', 'config.ts');
|
|
27
27
|
return configExtractorPath;
|
|
28
28
|
};
|
|
29
|
-
export const getFunctionConfig = async ({ func, importMap, deno,
|
|
29
|
+
export const getFunctionConfig = async ({ func, importMap, deno, log, }) => {
|
|
30
30
|
// The extractor is a Deno script that will import the function and run its
|
|
31
31
|
// `config` export, if one exists.
|
|
32
32
|
const extractorPath = getConfigExtractor();
|
|
@@ -50,7 +50,6 @@ export const getFunctionConfig = async ({ func, importMap, deno, bootstrapURL, l
|
|
|
50
50
|
extractorPath,
|
|
51
51
|
pathToFileURL(func.path).href,
|
|
52
52
|
pathToFileURL(collector.path).href,
|
|
53
|
-
bootstrapURL,
|
|
54
53
|
JSON.stringify(ConfigExitCode),
|
|
55
54
|
], { rejectOnExitCode: false });
|
|
56
55
|
if (exitCode !== ConfigExitCode.Success) {
|
package/dist/node/config.test.js
CHANGED
|
@@ -9,7 +9,6 @@ import { DenoBridge } from './bridge.js';
|
|
|
9
9
|
import { bundle } from './bundler.js';
|
|
10
10
|
import { getFunctionConfig } from './config.js';
|
|
11
11
|
import { ImportMap } from './import_map.js';
|
|
12
|
-
const bootstrapURL = 'https://edge.netlify.com/bootstrap/index-combined.ts';
|
|
13
12
|
const importMapFile = {
|
|
14
13
|
baseURL: new URL('file:///some/path/import-map.json'),
|
|
15
14
|
imports: {
|
|
@@ -122,7 +121,6 @@ describe('`getFunctionConfig` extracts configuration properties from function fi
|
|
|
122
121
|
importMap: new ImportMap([importMapFile]),
|
|
123
122
|
deno,
|
|
124
123
|
log: logger,
|
|
125
|
-
bootstrapURL,
|
|
126
124
|
});
|
|
127
125
|
if (func.error) {
|
|
128
126
|
await expect(funcCall()).rejects.toThrowError(func.error);
|
|
@@ -151,12 +149,12 @@ test('Loads function paths from the in-source `config` function', async () => {
|
|
|
151
149
|
{
|
|
152
150
|
function: 'user-func2',
|
|
153
151
|
path: '/user-func2',
|
|
152
|
+
method: ['PATCH'],
|
|
154
153
|
},
|
|
155
154
|
];
|
|
156
155
|
const result = await bundle([internalDirectory, userDirectory], distPath, declarations, {
|
|
157
156
|
basePath,
|
|
158
157
|
configPath: join(internalDirectory, 'config.json'),
|
|
159
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
160
158
|
});
|
|
161
159
|
const generatedFiles = await fs.readdir(distPath);
|
|
162
160
|
expect(result.functions.length).toBe(7);
|
|
@@ -179,6 +177,7 @@ test('Loads function paths from the in-source `config` function', async () => {
|
|
|
179
177
|
pattern: '^/user-func2/?$',
|
|
180
178
|
excluded_patterns: [],
|
|
181
179
|
path: '/user-func2',
|
|
180
|
+
methods: ['PATCH'],
|
|
182
181
|
});
|
|
183
182
|
expect(routes[2]).toEqual({
|
|
184
183
|
function: 'framework-func1',
|
|
@@ -203,6 +202,7 @@ test('Loads function paths from the in-source `config` function', async () => {
|
|
|
203
202
|
pattern: '^/user-func5(?:/(.*))/?$',
|
|
204
203
|
excluded_patterns: [],
|
|
205
204
|
path: '/user-func5/*',
|
|
205
|
+
methods: ['GET'],
|
|
206
206
|
});
|
|
207
207
|
expect(postCacheRoutes.length).toBe(1);
|
|
208
208
|
expect(postCacheRoutes[0]).toEqual({
|
|
@@ -210,6 +210,7 @@ test('Loads function paths from the in-source `config` function', async () => {
|
|
|
210
210
|
pattern: '^/user-func4/?$',
|
|
211
211
|
excluded_patterns: [],
|
|
212
212
|
path: '/user-func4',
|
|
213
|
+
methods: ['POST', 'PUT'],
|
|
213
214
|
});
|
|
214
215
|
expect(Object.keys(functionConfig)).toHaveLength(1);
|
|
215
216
|
expect(functionConfig['user-func5']).toEqual({
|
|
@@ -243,7 +244,6 @@ test('Passes validation if default export exists and is a function', async () =>
|
|
|
243
244
|
importMap: new ImportMap([importMapFile]),
|
|
244
245
|
deno,
|
|
245
246
|
log: logger,
|
|
246
|
-
bootstrapURL,
|
|
247
247
|
})).resolves.not.toThrow();
|
|
248
248
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
249
249
|
});
|
|
@@ -273,7 +273,6 @@ test('Fails validation if default export is not function', async () => {
|
|
|
273
273
|
importMap: new ImportMap([importMapFile]),
|
|
274
274
|
deno,
|
|
275
275
|
log: logger,
|
|
276
|
-
bootstrapURL,
|
|
277
276
|
});
|
|
278
277
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
279
278
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
@@ -303,7 +302,6 @@ test('Fails validation if default export is not present', async () => {
|
|
|
303
302
|
importMap: new ImportMap([importMapFile]),
|
|
304
303
|
deno,
|
|
305
304
|
log: logger,
|
|
306
|
-
bootstrapURL,
|
|
307
305
|
});
|
|
308
306
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
309
307
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { FunctionConfig, Path } from './config.js';
|
|
1
|
+
import { FunctionConfig, HTTPMethod, Path } from './config.js';
|
|
2
2
|
import { FeatureFlags } from './feature_flags.js';
|
|
3
3
|
interface BaseDeclaration {
|
|
4
4
|
cache?: string;
|
|
5
5
|
function: string;
|
|
6
|
+
method?: HTTPMethod | HTTPMethod[];
|
|
6
7
|
name?: string;
|
|
7
8
|
generator?: string;
|
|
8
9
|
}
|
package/dist/node/declaration.js
CHANGED
|
@@ -48,7 +48,7 @@ const getDeclarationsFromInput = (inputDeclarations, functionConfigs, functionsV
|
|
|
48
48
|
const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited) => {
|
|
49
49
|
const declarations = [];
|
|
50
50
|
for (const name in functionConfigs) {
|
|
51
|
-
const { cache, path } = functionConfigs[name];
|
|
51
|
+
const { cache, path, method } = functionConfigs[name];
|
|
52
52
|
// If we have a path specified, create a declaration for each path.
|
|
53
53
|
if (!functionsVisited.has(name) && path) {
|
|
54
54
|
const paths = Array.isArray(path) ? path : [path];
|
|
@@ -57,6 +57,9 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
|
|
|
57
57
|
if (cache) {
|
|
58
58
|
declaration.cache = cache;
|
|
59
59
|
}
|
|
60
|
+
if (method) {
|
|
61
|
+
declaration.method = method;
|
|
62
|
+
}
|
|
60
63
|
declarations.push(declaration);
|
|
61
64
|
});
|
|
62
65
|
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
declare const defaultFlags: {
|
|
2
2
|
edge_functions_fail_unsupported_regex: boolean;
|
|
3
|
-
edge_functions_path_urlpattern: boolean;
|
|
4
3
|
};
|
|
5
4
|
type FeatureFlag = keyof typeof defaultFlags;
|
|
6
5
|
type FeatureFlags = Partial<Record<FeatureFlag, boolean>>;
|
|
7
6
|
declare const getFlags: (input?: Record<string, boolean>, flags?: {
|
|
8
7
|
edge_functions_fail_unsupported_regex: boolean;
|
|
9
|
-
edge_functions_path_urlpattern: boolean;
|
|
10
8
|
}) => FeatureFlags;
|
|
11
9
|
export { defaultFlags, getFlags };
|
|
12
10
|
export type { FeatureFlag, FeatureFlags };
|
package/dist/node/import_map.js
CHANGED
|
@@ -5,7 +5,8 @@ import { fileURLToPath, pathToFileURL } from 'url';
|
|
|
5
5
|
import { parse } from '@import-maps/resolve';
|
|
6
6
|
import { isFileNotFoundError } from './utils/error.js';
|
|
7
7
|
const INTERNAL_IMPORTS = {
|
|
8
|
-
'netlify
|
|
8
|
+
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
9
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
9
10
|
};
|
|
10
11
|
// ImportMap can take several import map files and merge them into a final
|
|
11
12
|
// import map object, also adding the internal imports in the right order.
|
|
@@ -21,7 +21,8 @@ test('Handles import maps with full URLs without specifying a base URL', () => {
|
|
|
21
21
|
};
|
|
22
22
|
const map = new ImportMap([inputFile1, inputFile2]);
|
|
23
23
|
const { imports } = map.getContents();
|
|
24
|
-
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
24
|
+
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
|
|
25
|
+
expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
25
26
|
expect(imports['alias:jamstack']).toBe('https://jamstack.org/');
|
|
26
27
|
expect(imports['alias:pets']).toBe('https://petsofnetlify.com/');
|
|
27
28
|
});
|
|
@@ -36,7 +37,8 @@ test('Resolves relative paths to absolute paths if a base path is not provided',
|
|
|
36
37
|
const map = new ImportMap([inputFile1]);
|
|
37
38
|
const { imports } = map.getContents();
|
|
38
39
|
const expectedPath = join(cwd(), 'my-cool-site', 'heart', 'pets');
|
|
39
|
-
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
40
|
+
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
|
|
41
|
+
expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
40
42
|
expect(imports['alias:pets']).toBe(`${pathToFileURL(expectedPath).toString()}/`);
|
|
41
43
|
});
|
|
42
44
|
describe('Returns the fully resolved import map', () => {
|
|
@@ -69,7 +71,8 @@ describe('Returns the fully resolved import map', () => {
|
|
|
69
71
|
specifier3: 'file:///different/full/path/file3.js',
|
|
70
72
|
specifier2: 'file:///some/full/path/file2.js',
|
|
71
73
|
specifier1: 'file:///some/full/path/file.js',
|
|
72
|
-
'netlify
|
|
74
|
+
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
75
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
73
76
|
});
|
|
74
77
|
expect(scopes).toStrictEqual({
|
|
75
78
|
'file:///some/cool/path/with/scopes/': {
|
|
@@ -90,7 +93,8 @@ describe('Returns the fully resolved import map', () => {
|
|
|
90
93
|
specifier3: 'file:///vendor/full/path/file3.js',
|
|
91
94
|
specifier2: 'file:///root/full/path/file2.js',
|
|
92
95
|
specifier1: 'file:///root/full/path/file.js',
|
|
93
|
-
'netlify
|
|
96
|
+
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
97
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
94
98
|
});
|
|
95
99
|
expect(scopes).toStrictEqual({
|
|
96
100
|
'file:///root/cool/path/with/scopes/': {
|
|
@@ -126,6 +130,7 @@ test('Writes import map file to disk', async () => {
|
|
|
126
130
|
const { imports } = JSON.parse(createdFile);
|
|
127
131
|
const expectedPath = join(cwd(), 'my-cool-site', 'heart', 'pets', 'file.ts');
|
|
128
132
|
await file.cleanup();
|
|
129
|
-
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
133
|
+
expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
|
|
134
|
+
expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
130
135
|
expect(imports['alias:pets']).toBe(pathToFileURL(expectedPath).toString());
|
|
131
136
|
});
|
package/dist/node/manifest.d.ts
CHANGED
package/dist/node/manifest.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import globToRegExp from 'glob-to-regexp';
|
|
4
3
|
import { wrapBundleError } from './bundle_error.js';
|
|
5
4
|
import { parsePattern } from './declaration.js';
|
|
6
5
|
import { getPackageVersion } from './package_json.js';
|
|
@@ -27,13 +26,26 @@ const sanitizeEdgeFunctionConfig = (config) => {
|
|
|
27
26
|
}
|
|
28
27
|
return newConfig;
|
|
29
28
|
};
|
|
30
|
-
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath
|
|
29
|
+
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => {
|
|
31
30
|
if (excludedPath) {
|
|
32
31
|
const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
|
|
33
|
-
const excludedPatterns = paths.map((path) => pathToRegularExpression(path
|
|
32
|
+
const excludedPatterns = paths.map((path) => pathToRegularExpression(path)).map(serializePattern);
|
|
34
33
|
manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
|
|
35
34
|
}
|
|
36
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Normalizes method names into arrays of uppercase strings.
|
|
38
|
+
* (e.g. "get" becomes ["GET"])
|
|
39
|
+
*/
|
|
40
|
+
const normalizeMethods = (method, name) => {
|
|
41
|
+
const methods = Array.isArray(method) ? method : [method];
|
|
42
|
+
return methods.map((method) => {
|
|
43
|
+
if (typeof method !== 'string') {
|
|
44
|
+
throw new TypeError(`Could not parse method declaration of function '${name}'. Expecting HTTP Method, got ${method}`);
|
|
45
|
+
}
|
|
46
|
+
return method.toUpperCase();
|
|
47
|
+
});
|
|
48
|
+
};
|
|
37
49
|
const generateManifest = ({ bundles = [], declarations = [], featureFlags, functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => {
|
|
38
50
|
const preCacheRoutes = [];
|
|
39
51
|
const postCacheRoutes = [];
|
|
@@ -43,7 +55,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
43
55
|
if (manifestFunctionConfig[name] === undefined) {
|
|
44
56
|
continue;
|
|
45
57
|
}
|
|
46
|
-
addExcludedPatterns(name, manifestFunctionConfig, excludedPath
|
|
58
|
+
addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
|
|
47
59
|
manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError };
|
|
48
60
|
}
|
|
49
61
|
for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries(internalFunctionConfig)) {
|
|
@@ -51,7 +63,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
51
63
|
if (manifestFunctionConfig[name] === undefined) {
|
|
52
64
|
continue;
|
|
53
65
|
}
|
|
54
|
-
addExcludedPatterns(name, manifestFunctionConfig, excludedPath
|
|
66
|
+
addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
|
|
55
67
|
manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest };
|
|
56
68
|
}
|
|
57
69
|
declarations.forEach((declaration) => {
|
|
@@ -66,6 +78,9 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
66
78
|
pattern: serializePattern(pattern),
|
|
67
79
|
excluded_patterns: excludedPattern.map(serializePattern),
|
|
68
80
|
};
|
|
81
|
+
if ('method' in declaration) {
|
|
82
|
+
route.methods = normalizeMethods(declaration.method, func.name);
|
|
83
|
+
}
|
|
69
84
|
if ('path' in declaration) {
|
|
70
85
|
route.path = declaration.path;
|
|
71
86
|
}
|
|
@@ -91,31 +106,21 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
91
106
|
};
|
|
92
107
|
return manifest;
|
|
93
108
|
};
|
|
94
|
-
const pathToRegularExpression = (path
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
throw wrapBundleError(error);
|
|
109
|
-
}
|
|
109
|
+
const pathToRegularExpression = (path) => {
|
|
110
|
+
try {
|
|
111
|
+
const pattern = new ExtendedURLPattern({ pathname: path });
|
|
112
|
+
// Removing the `^` and `$` delimiters because we'll need to modify what's
|
|
113
|
+
// between them.
|
|
114
|
+
const source = pattern.regexp.pathname.source.slice(1, -1);
|
|
115
|
+
// Wrapping the expression source with `^` and `$`. Also, adding an optional
|
|
116
|
+
// trailing slash, so that a declaration of `path: "/foo"` matches requests
|
|
117
|
+
// for both `/foo` and `/foo/`.
|
|
118
|
+
const normalizedSource = `^${source}\\/?$`;
|
|
119
|
+
return normalizedSource;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw wrapBundleError(error);
|
|
110
123
|
}
|
|
111
|
-
// We use the global flag so that `globToRegExp` will not wrap the expression
|
|
112
|
-
// with `^` and `$`. We'll do that ourselves.
|
|
113
|
-
const regularExpression = globToRegExp(path, { flags: 'g' });
|
|
114
|
-
// Wrapping the expression source with `^` and `$`. Also, adding an optional
|
|
115
|
-
// trailing slash, so that a declaration of `path: "/foo"` matches requests
|
|
116
|
-
// for both `/foo` and `/foo/`.
|
|
117
|
-
const normalizedSource = `^${regularExpression.source}\\/?$`;
|
|
118
|
-
return normalizedSource;
|
|
119
124
|
};
|
|
120
125
|
const getRegularExpression = (declaration, featureFlags) => {
|
|
121
126
|
if ('pattern' in declaration) {
|
|
@@ -131,7 +136,7 @@ const getRegularExpression = (declaration, featureFlags) => {
|
|
|
131
136
|
return declaration.pattern;
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
|
-
return pathToRegularExpression(declaration.path
|
|
139
|
+
return pathToRegularExpression(declaration.path);
|
|
135
140
|
};
|
|
136
141
|
const getExcludedRegularExpressions = (declaration, featureFlags) => {
|
|
137
142
|
if ('excludedPattern' in declaration && declaration.excludedPattern) {
|
|
@@ -153,7 +158,7 @@ const getExcludedRegularExpressions = (declaration, featureFlags) => {
|
|
|
153
158
|
}
|
|
154
159
|
if ('path' in declaration && declaration.excludedPath) {
|
|
155
160
|
const paths = Array.isArray(declaration.excludedPath) ? declaration.excludedPath : [declaration.excludedPath];
|
|
156
|
-
return paths.map((path) => pathToRegularExpression(path
|
|
161
|
+
return paths.map((path) => pathToRegularExpression(path));
|
|
157
162
|
}
|
|
158
163
|
return [];
|
|
159
164
|
};
|
|
@@ -40,7 +40,6 @@ test('Generates a manifest with display names', () => {
|
|
|
40
40
|
declarations,
|
|
41
41
|
functions,
|
|
42
42
|
internalFunctionConfig,
|
|
43
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
44
43
|
});
|
|
45
44
|
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
|
|
46
45
|
expect(manifest.function_config).toEqual({
|
|
@@ -62,7 +61,6 @@ test('Generates a manifest with a generator field', () => {
|
|
|
62
61
|
declarations,
|
|
63
62
|
functions,
|
|
64
63
|
internalFunctionConfig,
|
|
65
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
66
64
|
});
|
|
67
65
|
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
|
|
68
66
|
const expectedFunctionConfig = { 'func-1': { generator: '@netlify/fake-plugin@1.0.0' } };
|
|
@@ -85,7 +83,6 @@ test('Generates a manifest with excluded paths and patterns', () => {
|
|
|
85
83
|
bundles: [],
|
|
86
84
|
declarations,
|
|
87
85
|
functions,
|
|
88
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
89
86
|
});
|
|
90
87
|
const expectedRoutes = [
|
|
91
88
|
{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: ['^/f1/exclude/?$'], path: '/f1/*' },
|
|
@@ -119,7 +116,6 @@ test('TOML-defined paths can be combined with ISC-defined excluded paths', () =>
|
|
|
119
116
|
declarations,
|
|
120
117
|
functions,
|
|
121
118
|
userFunctionConfig,
|
|
122
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
123
119
|
});
|
|
124
120
|
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
|
|
125
121
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
@@ -196,7 +192,6 @@ test('excludedPath from ISC goes into function_config, TOML goes into routes', (
|
|
|
196
192
|
functions,
|
|
197
193
|
userFunctionConfig,
|
|
198
194
|
internalFunctionConfig,
|
|
199
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
200
195
|
});
|
|
201
196
|
expect(manifest.routes).toEqual([
|
|
202
197
|
{
|
|
@@ -235,7 +230,6 @@ test('URLPattern named groups are supported', () => {
|
|
|
235
230
|
functions,
|
|
236
231
|
userFunctionConfig,
|
|
237
232
|
internalFunctionConfig,
|
|
238
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
239
233
|
});
|
|
240
234
|
expect(manifest.routes).toEqual([
|
|
241
235
|
{
|
|
@@ -259,7 +253,6 @@ test('Invalid Path patterns throw bundling errors', () => {
|
|
|
259
253
|
functions,
|
|
260
254
|
userFunctionConfig,
|
|
261
255
|
internalFunctionConfig,
|
|
262
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
263
256
|
})).toThrowError(BundleError);
|
|
264
257
|
});
|
|
265
258
|
test('Includes failure modes in manifest', () => {
|
|
@@ -381,14 +374,12 @@ test('Generates a manifest with layers', () => {
|
|
|
381
374
|
bundles: [],
|
|
382
375
|
declarations,
|
|
383
376
|
functions,
|
|
384
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
385
377
|
});
|
|
386
378
|
const manifest2 = generateManifest({
|
|
387
379
|
bundles: [],
|
|
388
380
|
declarations,
|
|
389
381
|
functions,
|
|
390
382
|
layers,
|
|
391
|
-
featureFlags: { edge_functions_path_urlpattern: true },
|
|
392
383
|
});
|
|
393
384
|
expect(manifest1.routes).toEqual(expectedRoutes);
|
|
394
385
|
expect(manifest1.layers).toEqual([]);
|
|
@@ -43,7 +43,7 @@ const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, fo
|
|
|
43
43
|
});
|
|
44
44
|
let functionsConfig = [];
|
|
45
45
|
if (options.getFunctionsConfig) {
|
|
46
|
-
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno,
|
|
46
|
+
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno, log: logger })));
|
|
47
47
|
}
|
|
48
48
|
const success = await waitForServer(port, processRef.ps);
|
|
49
49
|
return {
|
|
@@ -50,6 +50,13 @@ declare const edgeManifestSchema: {
|
|
|
50
50
|
path: {
|
|
51
51
|
type: string;
|
|
52
52
|
};
|
|
53
|
+
methods: {
|
|
54
|
+
type: string;
|
|
55
|
+
items: {
|
|
56
|
+
type: string;
|
|
57
|
+
enum: string[];
|
|
58
|
+
};
|
|
59
|
+
};
|
|
53
60
|
};
|
|
54
61
|
additionalProperties: boolean;
|
|
55
62
|
};
|
|
@@ -85,6 +92,13 @@ declare const edgeManifestSchema: {
|
|
|
85
92
|
path: {
|
|
86
93
|
type: string;
|
|
87
94
|
};
|
|
95
|
+
methods: {
|
|
96
|
+
type: string;
|
|
97
|
+
items: {
|
|
98
|
+
type: string;
|
|
99
|
+
enum: string[];
|
|
100
|
+
};
|
|
101
|
+
};
|
|
88
102
|
};
|
|
89
103
|
additionalProperties: boolean;
|
|
90
104
|
};
|
|
@@ -29,6 +29,10 @@ const routesSchema = {
|
|
|
29
29
|
excluded_patterns: excludedPatternsSchema,
|
|
30
30
|
generator: { type: 'string' },
|
|
31
31
|
path: { type: 'string' },
|
|
32
|
+
methods: {
|
|
33
|
+
type: 'array',
|
|
34
|
+
items: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] },
|
|
35
|
+
},
|
|
32
36
|
},
|
|
33
37
|
additionalProperties: false,
|
|
34
38
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.19.1",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
"execa": "^6.0.0",
|
|
83
83
|
"find-up": "^6.3.0",
|
|
84
84
|
"get-port": "^6.1.2",
|
|
85
|
-
"glob-to-regexp": "^0.4.1",
|
|
86
85
|
"is-path-inside": "^4.0.0",
|
|
87
86
|
"jsonc-parser": "^3.2.0",
|
|
88
87
|
"node-fetch": "^3.1.1",
|