@netlify/edge-bundler 8.20.0 → 9.1.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/bundler.test.js +27 -3
- package/dist/node/formats/eszip.d.ts +1 -1
- package/dist/node/formats/eszip.js +4 -2
- package/dist/node/import_map.d.ts +2 -1
- package/dist/node/import_map.js +5 -2
- package/dist/node/import_map.test.js +43 -1
- package/dist/node/npm_dependencies.js +15 -21
- package/dist/node/npm_import_error.d.ts +2 -2
- package/dist/node/npm_import_error.js +9 -5
- package/dist/node/server/server.d.ts +8 -4
- package/dist/node/server/server.js +32 -16
- package/dist/node/server/server.test.js +6 -1
- package/package.json +1 -1
|
@@ -105,7 +105,7 @@ test('Adds a custom error property to user errors during bundling', async () =>
|
|
|
105
105
|
await cleanup();
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
-
test('Prints a nice error message when user tries importing
|
|
108
|
+
test('Prints a nice error message when user tries importing an npm module and npm support is disabled', async () => {
|
|
109
109
|
expect.assertions(2);
|
|
110
110
|
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module');
|
|
111
111
|
const sourceDirectory = join(basePath, 'functions');
|
|
@@ -120,7 +120,31 @@ test('Prints a nice error message when user tries importing NPM module', async (
|
|
|
120
120
|
}
|
|
121
121
|
catch (error) {
|
|
122
122
|
expect(error).toBeInstanceOf(BundleError);
|
|
123
|
-
expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/parent-
|
|
123
|
+
expect(error.message).toEqual(`It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/parent-2"'?`);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
await cleanup();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
test('Prints a nice error message when user tries importing an npm module and npm support is enabled', async () => {
|
|
130
|
+
expect.assertions(2);
|
|
131
|
+
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module_scheme');
|
|
132
|
+
const sourceDirectory = join(basePath, 'functions');
|
|
133
|
+
const declarations = [
|
|
134
|
+
{
|
|
135
|
+
function: 'func1',
|
|
136
|
+
path: '/func1',
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
try {
|
|
140
|
+
await bundle([sourceDirectory], distPath, declarations, {
|
|
141
|
+
basePath,
|
|
142
|
+
featureFlags: { edge_functions_npm_modules: true },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
expect(error).toBeInstanceOf(BundleError);
|
|
147
|
+
expect(error.message).toEqual(`There was an error when loading the 'p-retry' npm module. Support for npm modules in edge functions is an experimental feature. Refer to https://ntl.fyi/edge-functions-npm for more information.`);
|
|
124
148
|
}
|
|
125
149
|
finally {
|
|
126
150
|
await cleanup();
|
|
@@ -373,7 +397,7 @@ test('Handles imports with the `node:` prefix', async () => {
|
|
|
373
397
|
expect(func1).toBe('ok');
|
|
374
398
|
await cleanup();
|
|
375
399
|
});
|
|
376
|
-
test('Loads npm modules from bare specifiers
|
|
400
|
+
test('Loads npm modules from bare specifiers', async () => {
|
|
377
401
|
const { basePath, cleanup, distPath } = await useFixture('imports_npm_module');
|
|
378
402
|
const sourceDirectory = join(basePath, 'functions');
|
|
379
403
|
const declarations = [
|
|
@@ -15,5 +15,5 @@ interface BundleESZIPOptions {
|
|
|
15
15
|
importMap: ImportMap;
|
|
16
16
|
vendorDirectory?: string;
|
|
17
17
|
}
|
|
18
|
-
declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>;
|
|
18
|
+
declare const bundleESZIP: ({ basePath, buildID, debug, deno, distDirectory, externals, featureFlags, functions, importMap, vendorDirectory, }: BundleESZIPOptions) => Promise<Bundle>;
|
|
19
19
|
export { bundleESZIP as bundle };
|
|
@@ -6,7 +6,7 @@ import { wrapBundleError } from '../bundle_error.js';
|
|
|
6
6
|
import { wrapNpmImportError } from '../npm_import_error.js';
|
|
7
7
|
import { getPackagePath } from '../package_json.js';
|
|
8
8
|
import { getFileHash } from '../utils/sha256.js';
|
|
9
|
-
const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, functions, importMap, vendorDirectory, }) => {
|
|
9
|
+
const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, externals, featureFlags, functions, importMap, vendorDirectory, }) => {
|
|
10
10
|
const extension = '.eszip';
|
|
11
11
|
const destPath = join(distDirectory, `${buildID}${extension}`);
|
|
12
12
|
const importMapPrefixes = {
|
|
@@ -33,7 +33,9 @@ const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, exte
|
|
|
33
33
|
await deno.run(['run', ...flags, bundler, JSON.stringify(payload)], { pipeOutput: true });
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
|
-
throw wrapBundleError(wrapNpmImportError(error), {
|
|
36
|
+
throw wrapBundleError(wrapNpmImportError(error, Boolean(featureFlags.edge_functions_npm_modules)), {
|
|
37
|
+
format: 'eszip',
|
|
38
|
+
});
|
|
37
39
|
}
|
|
38
40
|
const hash = await getFileHash(destPath);
|
|
39
41
|
return { extension, format: BundleFormat.ESZIP2, hash };
|
|
@@ -9,11 +9,12 @@ export interface ImportMapFile {
|
|
|
9
9
|
export declare class ImportMap {
|
|
10
10
|
rootPath: string | null;
|
|
11
11
|
sources: ImportMapFile[];
|
|
12
|
-
constructor(sources?: ImportMapFile[],
|
|
12
|
+
constructor(sources?: ImportMapFile[], rootPath?: string | null);
|
|
13
13
|
add(source: ImportMapFile): void;
|
|
14
14
|
addFile(path: string, logger: Logger): Promise<void>;
|
|
15
15
|
addFiles(paths: (string | undefined)[], logger: Logger): Promise<void>;
|
|
16
16
|
static applyPrefixesToImports(imports: Imports, prefixes: Record<string, string>): Imports;
|
|
17
|
+
clone(): ImportMap;
|
|
17
18
|
static convertImportsToURLObjects(imports: Imports): Record<string, URL>;
|
|
18
19
|
static convertScopesToURLObjects(scopes: Record<string, Imports>): Record<string, Record<string, URL>>;
|
|
19
20
|
static applyPrefixesToPath(path: string, prefixes: Record<string, string>): string;
|
package/dist/node/import_map.js
CHANGED
|
@@ -11,8 +11,8 @@ const INTERNAL_IMPORTS = {
|
|
|
11
11
|
// ImportMap can take several import map files and merge them into a final
|
|
12
12
|
// import map object, also adding the internal imports in the right order.
|
|
13
13
|
export class ImportMap {
|
|
14
|
-
constructor(sources = [],
|
|
15
|
-
this.rootPath =
|
|
14
|
+
constructor(sources = [], rootPath = null) {
|
|
15
|
+
this.rootPath = rootPath;
|
|
16
16
|
this.sources = [];
|
|
17
17
|
sources.forEach((file) => {
|
|
18
18
|
this.add(file);
|
|
@@ -44,6 +44,9 @@ export class ImportMap {
|
|
|
44
44
|
[key]: ImportMap.applyPrefixesToPath(value, prefixes),
|
|
45
45
|
}), {});
|
|
46
46
|
}
|
|
47
|
+
clone() {
|
|
48
|
+
return new ImportMap(this.sources, this.rootPath);
|
|
49
|
+
}
|
|
47
50
|
static convertImportsToURLObjects(imports) {
|
|
48
51
|
return Object.entries(imports).reduce((acc, [key, value]) => ({
|
|
49
52
|
...acc,
|
|
@@ -121,7 +121,7 @@ test('Throws when an import map uses a relative path to reference a file outside
|
|
|
121
121
|
'alias:file': '../file.js',
|
|
122
122
|
},
|
|
123
123
|
};
|
|
124
|
-
const map = new ImportMap([inputFile1],
|
|
124
|
+
const map = new ImportMap([inputFile1], cwd());
|
|
125
125
|
expect(() => map.getContents()).toThrowError(`Import map cannot reference '${join(cwd(), '..', 'file.js')}' as it's outside of the base directory '${cwd()}'`);
|
|
126
126
|
});
|
|
127
127
|
test('Writes import map file to disk', async () => {
|
|
@@ -143,3 +143,45 @@ test('Writes import map file to disk', async () => {
|
|
|
143
143
|
expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
|
|
144
144
|
expect(imports['alias:pets']).toBe(pathToFileURL(expectedPath).toString());
|
|
145
145
|
});
|
|
146
|
+
test('Clones an import map', () => {
|
|
147
|
+
const basePath = join(cwd(), 'my-cool-site', 'import-map.json');
|
|
148
|
+
const inputFile1 = {
|
|
149
|
+
baseURL: pathToFileURL(basePath),
|
|
150
|
+
imports: {
|
|
151
|
+
'alias:jamstack': 'https://jamstack.org',
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
const inputFile2 = {
|
|
155
|
+
baseURL: pathToFileURL(basePath),
|
|
156
|
+
imports: {
|
|
157
|
+
'alias:pets': 'https://petsofnetlify.com/',
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
const map1 = new ImportMap([inputFile1, inputFile2]);
|
|
161
|
+
const map2 = map1.clone();
|
|
162
|
+
map2.add({
|
|
163
|
+
baseURL: pathToFileURL(basePath),
|
|
164
|
+
imports: {
|
|
165
|
+
netlify: 'https://netlify.com',
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
expect(map1.getContents()).toStrictEqual({
|
|
169
|
+
imports: {
|
|
170
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
171
|
+
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
172
|
+
'alias:jamstack': 'https://jamstack.org/',
|
|
173
|
+
'alias:pets': 'https://petsofnetlify.com/',
|
|
174
|
+
},
|
|
175
|
+
scopes: {},
|
|
176
|
+
});
|
|
177
|
+
expect(map2.getContents()).toStrictEqual({
|
|
178
|
+
imports: {
|
|
179
|
+
'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
|
|
180
|
+
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
|
|
181
|
+
'alias:jamstack': 'https://jamstack.org/',
|
|
182
|
+
'alias:pets': 'https://petsofnetlify.com/',
|
|
183
|
+
netlify: 'https://netlify.com/',
|
|
184
|
+
},
|
|
185
|
+
scopes: {},
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -11,9 +11,9 @@ const require = createRequire(import.meta.url);
|
|
|
11
11
|
// Workaround for https://github.com/evanw/esbuild/issues/1921.
|
|
12
12
|
const banner = {
|
|
13
13
|
js: `
|
|
14
|
-
import {createRequire as ___nfyCreateRequire} from "module";
|
|
15
|
-
import {fileURLToPath as ___nfyFileURLToPath} from "url";
|
|
16
|
-
import {dirname as ___nfyPathDirname} from "path";
|
|
14
|
+
import {createRequire as ___nfyCreateRequire} from "node:module";
|
|
15
|
+
import {fileURLToPath as ___nfyFileURLToPath} from "node:url";
|
|
16
|
+
import {dirname as ___nfyPathDirname} from "node:path";
|
|
17
17
|
let __filename=___nfyFileURLToPath(import.meta.url);
|
|
18
18
|
let __dirname=___nfyPathDirname(___nfyFileURLToPath(import.meta.url));
|
|
19
19
|
let require=___nfyCreateRequire(import.meta.url);
|
|
@@ -44,16 +44,12 @@ export const getDependencyTrackerPlugin = (specifiers, importMap, baseURL) => ({
|
|
|
44
44
|
if (specifier.startsWith(nodePrefix) || builtinModulesSet.has(specifier)) {
|
|
45
45
|
return { external: true };
|
|
46
46
|
}
|
|
47
|
-
//
|
|
48
|
-
// the
|
|
47
|
+
// We don't support the `npm:` prefix yet. Mark the specifier as external
|
|
48
|
+
// and the ESZIP bundler will handle the failure.
|
|
49
49
|
if (specifier.startsWith(npmPrefix)) {
|
|
50
|
-
|
|
51
|
-
return build.resolve(canonicalPath, {
|
|
52
|
-
kind: args.kind,
|
|
53
|
-
resolveDir: args.resolveDir,
|
|
54
|
-
});
|
|
50
|
+
return { external: true };
|
|
55
51
|
}
|
|
56
|
-
const isLocalImport = specifier.startsWith(path.sep) || specifier.startsWith('.');
|
|
52
|
+
const isLocalImport = specifier.startsWith(path.sep) || specifier.startsWith('.') || path.isAbsolute(specifier);
|
|
57
53
|
// If this is a local import, return so that esbuild visits that path.
|
|
58
54
|
if (isLocalImport) {
|
|
59
55
|
return result;
|
|
@@ -92,8 +88,8 @@ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, impo
|
|
|
92
88
|
// resolution logic won't work.
|
|
93
89
|
const nodePaths = [path.join(basePath, 'node_modules')];
|
|
94
90
|
// We need to create some files on disk, which we don't want to write to the
|
|
95
|
-
// project directory. If a custom directory has been specified,
|
|
96
|
-
//
|
|
91
|
+
// project directory. If a custom directory has been specified, we use it.
|
|
92
|
+
// Otherwise, create a random temporary directory.
|
|
97
93
|
const temporaryDirectory = directory ? { path: directory } : await tmp.dir();
|
|
98
94
|
// Do a first pass at bundling to gather a list of specifiers that should be
|
|
99
95
|
// loaded as npm dependencies, because they either use the `npm:` prefix or
|
|
@@ -119,20 +115,19 @@ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, impo
|
|
|
119
115
|
if (specifiers.size === 0) {
|
|
120
116
|
return;
|
|
121
117
|
}
|
|
122
|
-
|
|
123
|
-
// To bundle an entire module and all its dependencies, we create a stub file
|
|
118
|
+
// To bundle an entire module and all its dependencies, create a barrel file
|
|
124
119
|
// where we re-export everything from that specifier. We do this for every
|
|
125
|
-
// specifier, and each of these files will
|
|
120
|
+
// specifier, and each of these files will become entry points to esbuild.
|
|
126
121
|
const ops = await Promise.all([...specifiers].map(async (specifier, index) => {
|
|
127
122
|
const code = `import * as mod from "${specifier}"; export default mod.default; export * from "${specifier}";`;
|
|
128
|
-
const filePath = path.join(temporaryDirectory.path, `
|
|
123
|
+
const filePath = path.join(temporaryDirectory.path, `barrel-${index}.js`);
|
|
129
124
|
await fs.writeFile(filePath, code);
|
|
130
125
|
return { filePath, specifier };
|
|
131
126
|
}));
|
|
132
127
|
const entryPoints = ops.map(({ filePath }) => filePath);
|
|
133
|
-
// Bundle each of the
|
|
134
|
-
// version of each of the
|
|
135
|
-
//
|
|
128
|
+
// Bundle each of the barrel files we created. We'll end up with a compiled
|
|
129
|
+
// version of each of the barrel files, plus any chunks of shared code
|
|
130
|
+
// between them (such that a common module isn't bundled twice).
|
|
136
131
|
await build({
|
|
137
132
|
allowOverwrite: true,
|
|
138
133
|
banner,
|
|
@@ -164,7 +159,6 @@ export const vendorNPMSpecifiers = async ({ basePath, directory, functions, impo
|
|
|
164
159
|
return {
|
|
165
160
|
...acc,
|
|
166
161
|
[op.specifier]: url,
|
|
167
|
-
[npmPrefix + op.specifier]: url,
|
|
168
162
|
};
|
|
169
163
|
}, builtIns),
|
|
170
164
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
declare class NPMImportError extends Error {
|
|
2
|
-
constructor(originalError: Error, moduleName: string);
|
|
2
|
+
constructor(originalError: Error, moduleName: string, supportsNPM: boolean);
|
|
3
3
|
}
|
|
4
|
-
declare const wrapNpmImportError: (input: unknown) => unknown;
|
|
4
|
+
declare const wrapNpmImportError: (input: unknown, supportsNPM: boolean) => unknown;
|
|
5
5
|
export { NPMImportError, wrapNpmImportError };
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
class NPMImportError extends Error {
|
|
2
|
-
constructor(originalError, moduleName) {
|
|
3
|
-
|
|
2
|
+
constructor(originalError, moduleName, supportsNPM) {
|
|
3
|
+
let message = `It seems like you're trying to import an npm module. This is only supported via CDNs like esm.sh. Have you tried 'import mod from "https://esm.sh/${moduleName}"'?`;
|
|
4
|
+
if (supportsNPM) {
|
|
5
|
+
message = `There was an error when loading the '${moduleName}' npm module. Support for npm modules in edge functions is an experimental feature. Refer to https://ntl.fyi/edge-functions-npm for more information.`;
|
|
6
|
+
}
|
|
7
|
+
super(message);
|
|
4
8
|
this.name = 'NPMImportError';
|
|
5
9
|
this.stack = originalError.stack;
|
|
6
10
|
// https://github.com/microsoft/TypeScript-wiki/blob/8a66ecaf77118de456f7cd9c56848a40fe29b9b4/Breaking-Changes.md#implicit-any-error-raised-for-un-annotated-callback-arguments-with-no-matching-overload-arguments
|
|
7
11
|
Object.setPrototypeOf(this, NPMImportError.prototype);
|
|
8
12
|
}
|
|
9
13
|
}
|
|
10
|
-
const wrapNpmImportError = (input) => {
|
|
14
|
+
const wrapNpmImportError = (input, supportsNPM) => {
|
|
11
15
|
if (input instanceof Error) {
|
|
12
16
|
const match = input.message.match(/Relative import path "(.*)" not prefixed with/);
|
|
13
17
|
if (match !== null) {
|
|
14
18
|
const [, moduleName] = match;
|
|
15
|
-
return new NPMImportError(input, moduleName);
|
|
19
|
+
return new NPMImportError(input, moduleName, supportsNPM);
|
|
16
20
|
}
|
|
17
21
|
const schemeMatch = input.message.match(/Error: Module not found "npm:(.*)"/);
|
|
18
22
|
if (schemeMatch !== null) {
|
|
19
23
|
const [, moduleName] = schemeMatch;
|
|
20
|
-
return new NPMImportError(input, moduleName);
|
|
24
|
+
return new NPMImportError(input, moduleName, supportsNPM);
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
return input;
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import { OnAfterDownloadHook, OnBeforeDownloadHook } from '../bridge.js';
|
|
7
7
|
import { FunctionConfig } from '../config.js';
|
|
8
8
|
import type { EdgeFunction } from '../edge_function.js';
|
|
9
|
+
import type { FeatureFlags } from '../feature_flags.js';
|
|
9
10
|
import { LogFunction } from '../logger.js';
|
|
10
|
-
type FormatFunction = (name: string) => string;
|
|
11
|
+
export type FormatFunction = (name: string) => string;
|
|
11
12
|
interface StartServerOptions {
|
|
12
13
|
getFunctionsConfig?: boolean;
|
|
13
14
|
}
|
|
@@ -17,10 +18,12 @@ interface InspectSettings {
|
|
|
17
18
|
address?: string;
|
|
18
19
|
}
|
|
19
20
|
interface ServeOptions {
|
|
21
|
+
basePath: string;
|
|
20
22
|
bootstrapURL: string;
|
|
21
23
|
certificatePath?: string;
|
|
22
24
|
debug?: boolean;
|
|
23
25
|
distImportMapPath?: string;
|
|
26
|
+
featureFlags?: FeatureFlags;
|
|
24
27
|
inspectSettings?: InspectSettings;
|
|
25
28
|
importMapPaths?: string[];
|
|
26
29
|
onAfterDownload?: OnAfterDownloadHook;
|
|
@@ -28,12 +31,13 @@ interface ServeOptions {
|
|
|
28
31
|
formatExportTypeError?: FormatFunction;
|
|
29
32
|
formatImportError?: FormatFunction;
|
|
30
33
|
port: number;
|
|
34
|
+
servePath: string;
|
|
31
35
|
systemLogger?: LogFunction;
|
|
32
36
|
}
|
|
33
|
-
declare const serve: ({ bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, formatExportTypeError, formatImportError, importMapPaths, onAfterDownload, onBeforeDownload, port, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
|
|
37
|
+
export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, importMapPaths, onAfterDownload, onBeforeDownload, port, servePath, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
|
|
38
|
+
features: Record<string, boolean>;
|
|
34
39
|
functionsConfig: FunctionConfig[];
|
|
35
40
|
graph: any;
|
|
36
41
|
success: boolean;
|
|
37
42
|
}>>;
|
|
38
|
-
export {
|
|
39
|
-
export type { FormatFunction };
|
|
43
|
+
export {};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { tmpName } from 'tmp-promise';
|
|
2
1
|
import { DenoBridge } from '../bridge.js';
|
|
3
2
|
import { getFunctionConfig } from '../config.js';
|
|
4
3
|
import { generateStage2 } from '../formats/javascript.js';
|
|
5
4
|
import { ImportMap } from '../import_map.js';
|
|
6
5
|
import { getLogger } from '../logger.js';
|
|
6
|
+
import { vendorNPMSpecifiers } from '../npm_dependencies.js';
|
|
7
7
|
import { ensureLatestTypes } from '../types.js';
|
|
8
8
|
import { killProcess, waitForServer } from './util.js';
|
|
9
|
-
const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, formatExportTypeError, formatImportError, importMap, logger, port, }) => {
|
|
9
|
+
const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, featureFlags, flags: denoFlags, formatExportTypeError, formatImportError, importMap: baseImportMap, logger, port, }) => {
|
|
10
10
|
const processRef = {};
|
|
11
11
|
const startServer = async (functions, env = {}, options = {}) => {
|
|
12
12
|
if ((processRef === null || processRef === void 0 ? void 0 : processRef.ps) !== undefined) {
|
|
@@ -21,6 +21,21 @@ const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, fo
|
|
|
21
21
|
formatExportTypeError,
|
|
22
22
|
formatImportError,
|
|
23
23
|
});
|
|
24
|
+
const features = {};
|
|
25
|
+
const importMap = baseImportMap.clone();
|
|
26
|
+
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_npm_modules) {
|
|
27
|
+
const vendor = await vendorNPMSpecifiers({
|
|
28
|
+
basePath,
|
|
29
|
+
directory: distDirectory,
|
|
30
|
+
functions: functions.map(({ path }) => path),
|
|
31
|
+
importMap,
|
|
32
|
+
logger,
|
|
33
|
+
});
|
|
34
|
+
if (vendor) {
|
|
35
|
+
features.npmModules = true;
|
|
36
|
+
importMap.add(vendor.importMap);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
24
39
|
try {
|
|
25
40
|
// This command will print a JSON object with all the modules found in
|
|
26
41
|
// the `stage2Path` file as well as all of their dependencies.
|
|
@@ -32,11 +47,12 @@ const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, fo
|
|
|
32
47
|
catch {
|
|
33
48
|
// no-op
|
|
34
49
|
}
|
|
35
|
-
const
|
|
50
|
+
const extraDenoFlags = [`--import-map=${importMap.toDataURL()}`];
|
|
51
|
+
const applicationFlags = ['--port', port.toString()];
|
|
36
52
|
// We set `extendEnv: false` to avoid polluting the edge function context
|
|
37
53
|
// with variables from the user's system, since those will not be available
|
|
38
54
|
// in the production environment.
|
|
39
|
-
await deno.runInBackground(['run', ...denoFlags, stage2Path, ...
|
|
55
|
+
await deno.runInBackground(['run', ...denoFlags, ...extraDenoFlags, stage2Path, ...applicationFlags], processRef, {
|
|
40
56
|
pipeOutput: true,
|
|
41
57
|
env,
|
|
42
58
|
extendEnv: false,
|
|
@@ -45,8 +61,12 @@ const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, fo
|
|
|
45
61
|
if (options.getFunctionsConfig) {
|
|
46
62
|
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno, log: logger })));
|
|
47
63
|
}
|
|
64
|
+
if (distImportMapPath) {
|
|
65
|
+
await importMap.writeToFile(distImportMapPath);
|
|
66
|
+
}
|
|
48
67
|
const success = await waitForServer(port, processRef.ps);
|
|
49
68
|
return {
|
|
69
|
+
features,
|
|
50
70
|
functionsConfig,
|
|
51
71
|
graph,
|
|
52
72
|
success,
|
|
@@ -54,7 +74,7 @@ const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, fo
|
|
|
54
74
|
};
|
|
55
75
|
return startServer;
|
|
56
76
|
};
|
|
57
|
-
const serve = async ({ bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, formatExportTypeError, formatImportError, importMapPaths = [], onAfterDownload, onBeforeDownload, port, systemLogger, }) => {
|
|
77
|
+
export const serve = async ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, importMapPaths = [], onAfterDownload, onBeforeDownload, port, servePath, systemLogger, }) => {
|
|
58
78
|
const logger = getLogger(systemLogger, debug);
|
|
59
79
|
const deno = new DenoBridge({
|
|
60
80
|
debug,
|
|
@@ -62,16 +82,11 @@ const serve = async ({ bootstrapURL, certificatePath, debug, distImportMapPath,
|
|
|
62
82
|
onAfterDownload,
|
|
63
83
|
onBeforeDownload,
|
|
64
84
|
});
|
|
65
|
-
// We need to generate a stage 2 file and write it somewhere. We use a
|
|
66
|
-
// temporary directory for that.
|
|
67
|
-
const distDirectory = await tmpName();
|
|
68
85
|
// Wait for the binary to be downloaded if needed.
|
|
69
86
|
await deno.getBinaryPath();
|
|
70
87
|
// Downloading latest types if needed.
|
|
71
88
|
await ensureLatestTypes(deno, logger);
|
|
72
|
-
const
|
|
73
|
-
await importMap.addFiles(importMapPaths, logger);
|
|
74
|
-
const flags = ['--allow-all', `--import-map=${importMap.toDataURL()}`, '--no-config'];
|
|
89
|
+
const flags = ['--allow-all', '--no-config'];
|
|
75
90
|
if (certificatePath) {
|
|
76
91
|
flags.push(`--cert=${certificatePath}`);
|
|
77
92
|
}
|
|
@@ -89,10 +104,15 @@ const serve = async ({ bootstrapURL, certificatePath, debug, distImportMapPath,
|
|
|
89
104
|
flags.push(inspectSettings.address ? `--inspect=${inspectSettings.address}` : '--inspect');
|
|
90
105
|
}
|
|
91
106
|
}
|
|
107
|
+
const importMap = new ImportMap();
|
|
108
|
+
await importMap.addFiles(importMapPaths, logger);
|
|
92
109
|
const server = prepareServer({
|
|
110
|
+
basePath,
|
|
93
111
|
bootstrapURL,
|
|
94
112
|
deno,
|
|
95
|
-
distDirectory,
|
|
113
|
+
distDirectory: servePath,
|
|
114
|
+
distImportMapPath,
|
|
115
|
+
featureFlags,
|
|
96
116
|
flags,
|
|
97
117
|
formatExportTypeError,
|
|
98
118
|
formatImportError,
|
|
@@ -100,9 +120,5 @@ const serve = async ({ bootstrapURL, certificatePath, debug, distImportMapPath,
|
|
|
100
120
|
logger,
|
|
101
121
|
port,
|
|
102
122
|
});
|
|
103
|
-
if (distImportMapPath) {
|
|
104
|
-
await importMap.writeToFile(distImportMapPath);
|
|
105
|
-
}
|
|
106
123
|
return server;
|
|
107
124
|
};
|
|
108
|
-
export { serve };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import getPort from 'get-port';
|
|
3
3
|
import fetch from 'node-fetch';
|
|
4
|
+
import { tmpName } from 'tmp-promise';
|
|
4
5
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
6
|
import { test, expect } from 'vitest';
|
|
6
7
|
import { fixturesDir } from '../../test/util.js';
|
|
@@ -13,10 +14,13 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
13
14
|
};
|
|
14
15
|
const port = await getPort();
|
|
15
16
|
const importMapPaths = [join(paths.internal, 'import_map.json'), join(paths.user, 'import-map.json')];
|
|
17
|
+
const servePath = await tmpName();
|
|
16
18
|
const server = await serve({
|
|
19
|
+
basePath,
|
|
17
20
|
bootstrapURL: 'https://edge.netlify.com/bootstrap/index-combined.ts',
|
|
18
21
|
importMapPaths,
|
|
19
22
|
port,
|
|
23
|
+
servePath,
|
|
20
24
|
});
|
|
21
25
|
const functions = [
|
|
22
26
|
{
|
|
@@ -35,9 +39,10 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
35
39
|
const options = {
|
|
36
40
|
getFunctionsConfig: true,
|
|
37
41
|
};
|
|
38
|
-
const { functionsConfig, graph, success } = await server(functions, {
|
|
42
|
+
const { features, functionsConfig, graph, success } = await server(functions, {
|
|
39
43
|
very_secret_secret: 'i love netlify',
|
|
40
44
|
}, options);
|
|
45
|
+
expect(features).toEqual({});
|
|
41
46
|
expect(success).toBe(true);
|
|
42
47
|
expect(functionsConfig).toEqual([{ path: '/my-function' }, {}, { path: '/global-netlify' }]);
|
|
43
48
|
for (const key in functions) {
|