@netlify/edge-bundler 5.5.0 → 6.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.js +12 -1
- package/dist/node/bundler.test.js +8 -4
- package/dist/node/deno_config.d.ts +6 -0
- package/dist/node/deno_config.js +43 -0
- package/dist/node/deno_config.test.d.ts +1 -0
- package/dist/node/deno_config.test.js +92 -0
- package/dist/node/feature_flags.js +1 -0
- package/dist/node/server/server.d.ts +2 -1
- package/dist/node/server/server.js +11 -2
- package/dist/node/server/server.test.js +40 -12
- package/package.json +2 -1
package/dist/node/bundler.js
CHANGED
|
@@ -6,11 +6,12 @@ import { importMapSpecifier } from '../shared/consts.js';
|
|
|
6
6
|
import { DenoBridge } from './bridge.js';
|
|
7
7
|
import { getFunctionConfig } from './config.js';
|
|
8
8
|
import { getDeclarationsFromConfig } from './declaration.js';
|
|
9
|
+
import { getConfig as getDenoConfig } from './deno_config.js';
|
|
9
10
|
import { load as loadDeployConfig } from './deploy_config.js';
|
|
10
11
|
import { getFlags } from './feature_flags.js';
|
|
11
12
|
import { findFunctions } from './finder.js';
|
|
12
13
|
import { bundle as bundleESZIP } from './formats/eszip.js';
|
|
13
|
-
import { ImportMap } from './import_map.js';
|
|
14
|
+
import { ImportMap, readFile as readImportMapFile } 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';
|
|
@@ -44,6 +45,16 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
|
|
|
44
45
|
if (deployConfig.importMap) {
|
|
45
46
|
importMap.add(deployConfig.importMap);
|
|
46
47
|
}
|
|
48
|
+
if (featureFlags.edge_functions_read_deno_config) {
|
|
49
|
+
// Look for a Deno config file and read it if one exists.
|
|
50
|
+
const denoConfig = await getDenoConfig(logger, basePath);
|
|
51
|
+
// If the Deno config file defines an import map, read the file and add the
|
|
52
|
+
// imports to the global import map.
|
|
53
|
+
if (denoConfig === null || denoConfig === void 0 ? void 0 : denoConfig.importMap) {
|
|
54
|
+
const importMapFile = await readImportMapFile(denoConfig.importMap);
|
|
55
|
+
importMap.add(importMapFile);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
47
58
|
const functions = await findFunctions(sourceDirectories);
|
|
48
59
|
const functionBundle = await bundleESZIP({
|
|
49
60
|
basePath,
|
|
@@ -18,13 +18,17 @@ test('Produces an ESZIP bundle', async () => {
|
|
|
18
18
|
path: '/func1',
|
|
19
19
|
},
|
|
20
20
|
];
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
const userDirectory = join(basePath, 'user-functions');
|
|
22
|
+
const internalDirectory = join(basePath, 'functions');
|
|
23
|
+
const result = await bundle([userDirectory, internalDirectory], distPath, declarations, {
|
|
23
24
|
basePath,
|
|
24
|
-
configPath: join(
|
|
25
|
+
configPath: join(internalDirectory, 'config.json'),
|
|
26
|
+
featureFlags: {
|
|
27
|
+
edge_functions_read_deno_config: true,
|
|
28
|
+
},
|
|
25
29
|
});
|
|
26
30
|
const generatedFiles = await fs.readdir(distPath);
|
|
27
|
-
expect(result.functions.length).toBe(
|
|
31
|
+
expect(result.functions.length).toBe(2);
|
|
28
32
|
expect(generatedFiles.length).toBe(2);
|
|
29
33
|
const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8');
|
|
30
34
|
const manifest = JSON.parse(manifestFile);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
|
+
import { parse as parseJSONC } from 'jsonc-parser';
|
|
4
|
+
import { isNodeError } from './utils/error.js';
|
|
5
|
+
const filenames = ['deno.json', 'deno.jsonc'];
|
|
6
|
+
export const getConfig = async (logger, basePath) => {
|
|
7
|
+
if (basePath === undefined) {
|
|
8
|
+
logger.system('No base path specified, will not attempt to read Deno config');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
for (const filename of filenames) {
|
|
12
|
+
const candidatePath = join(basePath, filename);
|
|
13
|
+
const config = await getConfigFromFile(candidatePath);
|
|
14
|
+
if (config !== undefined) {
|
|
15
|
+
logger.system('Loaded Deno config file from path', candidatePath);
|
|
16
|
+
return normalizeConfig(config, basePath);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
logger.system('No Deno config file found at base path', basePath);
|
|
20
|
+
};
|
|
21
|
+
const getConfigFromFile = async (filePath) => {
|
|
22
|
+
try {
|
|
23
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
24
|
+
const config = parseJSONC(data);
|
|
25
|
+
return config;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const normalizeConfig = (rawConfig, basePath) => {
|
|
35
|
+
const config = {};
|
|
36
|
+
if (rawConfig.importMap) {
|
|
37
|
+
if (typeof rawConfig.importMap !== 'string') {
|
|
38
|
+
throw new TypeError(`'importMap' property in Deno config must be a string`);
|
|
39
|
+
}
|
|
40
|
+
config.importMap = resolve(basePath, rawConfig.importMap);
|
|
41
|
+
}
|
|
42
|
+
return config;
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import tmp from 'tmp-promise';
|
|
4
|
+
import { expect, test } from 'vitest';
|
|
5
|
+
import { testLogger } from '../test/util.js';
|
|
6
|
+
import { getConfig } from './deno_config.js';
|
|
7
|
+
test('Returns `undefined` if no config file is found', async () => {
|
|
8
|
+
const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
|
|
9
|
+
const config = await getConfig(testLogger, path);
|
|
10
|
+
expect(config).toBeUndefined();
|
|
11
|
+
await cleanup();
|
|
12
|
+
});
|
|
13
|
+
test('Returns an empty object if the config file cannot be parsed', async () => {
|
|
14
|
+
const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
|
|
15
|
+
const configPath = join(path, 'deno.json');
|
|
16
|
+
await fs.writeFile(configPath, '{');
|
|
17
|
+
const config = await getConfig(testLogger, path);
|
|
18
|
+
expect(config).toEqual({});
|
|
19
|
+
await cleanup();
|
|
20
|
+
});
|
|
21
|
+
test('Throws a type error if the `importMap` contains anything other than a string', async () => {
|
|
22
|
+
const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
|
|
23
|
+
const configPath = join(path, 'deno.json');
|
|
24
|
+
const data = JSON.stringify({ importMap: { imports: { foo: './bar/' } } });
|
|
25
|
+
await fs.writeFile(configPath, data);
|
|
26
|
+
await expect(getConfig(testLogger, path)).rejects.toThrowError(TypeError);
|
|
27
|
+
await cleanup();
|
|
28
|
+
});
|
|
29
|
+
test('Excludes unsupported properties', async () => {
|
|
30
|
+
const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
|
|
31
|
+
const configPath = join(path, 'deno.json');
|
|
32
|
+
const data = JSON.stringify({
|
|
33
|
+
compilerOptions: {
|
|
34
|
+
allowJs: true,
|
|
35
|
+
lib: ['deno.window'],
|
|
36
|
+
strict: true,
|
|
37
|
+
},
|
|
38
|
+
importMap: 'import_map.json',
|
|
39
|
+
lint: {
|
|
40
|
+
files: {
|
|
41
|
+
include: ['src/'],
|
|
42
|
+
exclude: ['src/testdata/'],
|
|
43
|
+
},
|
|
44
|
+
rules: {
|
|
45
|
+
tags: ['recommended'],
|
|
46
|
+
include: ['ban-untagged-todo'],
|
|
47
|
+
exclude: ['no-unused-vars'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
fmt: {
|
|
51
|
+
files: {
|
|
52
|
+
include: ['src/'],
|
|
53
|
+
exclude: ['src/testdata/'],
|
|
54
|
+
},
|
|
55
|
+
options: {
|
|
56
|
+
useTabs: true,
|
|
57
|
+
lineWidth: 80,
|
|
58
|
+
indentWidth: 4,
|
|
59
|
+
singleQuote: true,
|
|
60
|
+
proseWrap: 'preserve',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
test: {
|
|
64
|
+
files: {
|
|
65
|
+
include: ['src/'],
|
|
66
|
+
exclude: ['src/testdata/'],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
await fs.writeFile(configPath, data);
|
|
71
|
+
const config = await getConfig(testLogger, path);
|
|
72
|
+
expect(Object.keys(config !== null && config !== void 0 ? config : {})).toEqual(['importMap']);
|
|
73
|
+
await cleanup();
|
|
74
|
+
});
|
|
75
|
+
test('Resolves `importMap` into an absolute path', async () => {
|
|
76
|
+
const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
|
|
77
|
+
const configPath = join(path, 'deno.json');
|
|
78
|
+
const data = JSON.stringify({ importMap: 'import_map.json' });
|
|
79
|
+
await fs.writeFile(configPath, data);
|
|
80
|
+
const config = await getConfig(testLogger, path);
|
|
81
|
+
expect(config).toEqual({ importMap: join(path, 'import_map.json') });
|
|
82
|
+
await cleanup();
|
|
83
|
+
});
|
|
84
|
+
test('Supports JSONC', async () => {
|
|
85
|
+
const { cleanup, path } = await tmp.dir({ unsafeCleanup: true });
|
|
86
|
+
const configPath = join(path, 'deno.jsonc');
|
|
87
|
+
const data = JSON.stringify({ importMap: 'import_map.json' });
|
|
88
|
+
await fs.writeFile(configPath, `// This is a comment\n${data}`);
|
|
89
|
+
const config = await getConfig(testLogger, path);
|
|
90
|
+
expect(config).toEqual({ importMap: join(path, 'import_map.json') });
|
|
91
|
+
await cleanup();
|
|
92
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const defaultFlags = {
|
|
2
2
|
edge_functions_cache_deno_dir: false,
|
|
3
3
|
edge_functions_config_export: false,
|
|
4
|
+
edge_functions_read_deno_config: false,
|
|
4
5
|
};
|
|
5
6
|
const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({
|
|
6
7
|
...result,
|
|
@@ -18,6 +18,7 @@ interface InspectSettings {
|
|
|
18
18
|
address?: string;
|
|
19
19
|
}
|
|
20
20
|
interface ServeOptions {
|
|
21
|
+
basePath: string;
|
|
21
22
|
certificatePath?: string;
|
|
22
23
|
debug?: boolean;
|
|
23
24
|
distImportMapPath?: string;
|
|
@@ -30,7 +31,7 @@ interface ServeOptions {
|
|
|
30
31
|
port: number;
|
|
31
32
|
systemLogger?: LogFunction;
|
|
32
33
|
}
|
|
33
|
-
declare const serve: ({ certificatePath, debug, distImportMapPath, inspectSettings, formatExportTypeError, formatImportError, importMaps, onAfterDownload, onBeforeDownload, port, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
|
|
34
|
+
declare const serve: ({ basePath, certificatePath, debug, distImportMapPath, inspectSettings, formatExportTypeError, formatImportError, importMaps, onAfterDownload, onBeforeDownload, port, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
|
|
34
35
|
functionsConfig: FunctionConfig[];
|
|
35
36
|
graph: any;
|
|
36
37
|
success: boolean;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { tmpName } from 'tmp-promise';
|
|
2
2
|
import { DenoBridge } from '../bridge.js';
|
|
3
3
|
import { getFunctionConfig } from '../config.js';
|
|
4
|
+
import { getConfig as getDenoConfig } from '../deno_config.js';
|
|
4
5
|
import { generateStage2 } from '../formats/javascript.js';
|
|
5
|
-
import { ImportMap } from '../import_map.js';
|
|
6
|
+
import { ImportMap, readFile as readImportMapFile } from '../import_map.js';
|
|
6
7
|
import { getLogger } from '../logger.js';
|
|
7
8
|
import { ensureLatestTypes } from '../types.js';
|
|
8
9
|
import { killProcess, waitForServer } from './util.js';
|
|
@@ -53,7 +54,7 @@ const prepareServer = ({ deno, distDirectory, flags: denoFlags, formatExportType
|
|
|
53
54
|
};
|
|
54
55
|
return startServer;
|
|
55
56
|
};
|
|
56
|
-
const serve = async ({ certificatePath, debug, distImportMapPath, inspectSettings, formatExportTypeError, formatImportError, importMaps, onAfterDownload, onBeforeDownload, port, systemLogger, }) => {
|
|
57
|
+
const serve = async ({ basePath, certificatePath, debug, distImportMapPath, inspectSettings, formatExportTypeError, formatImportError, importMaps, onAfterDownload, onBeforeDownload, port, systemLogger, }) => {
|
|
57
58
|
const logger = getLogger(systemLogger, debug);
|
|
58
59
|
const deno = new DenoBridge({
|
|
59
60
|
debug,
|
|
@@ -71,6 +72,14 @@ const serve = async ({ certificatePath, debug, distImportMapPath, inspectSetting
|
|
|
71
72
|
// Creating an ImportMap instance with any import maps supplied by the user,
|
|
72
73
|
// if any.
|
|
73
74
|
const importMap = new ImportMap(importMaps);
|
|
75
|
+
// Look for a Deno config file and read it if one exists.
|
|
76
|
+
const denoConfig = await getDenoConfig(logger, basePath);
|
|
77
|
+
// If the Deno config file defines an import map, read the file and add the
|
|
78
|
+
// imports to the global import map.
|
|
79
|
+
if (denoConfig === null || denoConfig === void 0 ? void 0 : denoConfig.importMap) {
|
|
80
|
+
const importMapFile = await readImportMapFile(denoConfig.importMap);
|
|
81
|
+
importMap.add(importMapFile);
|
|
82
|
+
}
|
|
74
83
|
const flags = ['--allow-all', '--unstable', `--import-map=${importMap.toDataURL()}`, '--no-config'];
|
|
75
84
|
if (certificatePath) {
|
|
76
85
|
flags.push(`--cert=${certificatePath}`);
|
|
@@ -1,19 +1,37 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
2
3
|
import getPort from 'get-port';
|
|
3
4
|
import fetch from 'node-fetch';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
6
|
import { test, expect } from 'vitest';
|
|
5
7
|
import { fixturesDir } from '../../test/util.js';
|
|
6
8
|
import { serve } from '../index.js';
|
|
7
9
|
test('Starts a server and serves requests for edge functions', async () => {
|
|
10
|
+
const basePath = join(fixturesDir, 'serve_test');
|
|
11
|
+
const functionPaths = {
|
|
12
|
+
internal: join(basePath, '.netlify', 'edge-functions', 'greet.ts'),
|
|
13
|
+
user: join(basePath, 'netlify', 'edge-functions', 'echo_env.ts'),
|
|
14
|
+
};
|
|
8
15
|
const port = await getPort();
|
|
16
|
+
const internalImportMap = {
|
|
17
|
+
baseURL: pathToFileURL(functionPaths.internal),
|
|
18
|
+
imports: {
|
|
19
|
+
'internal-helper': '../../helper.ts',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
9
22
|
const server = await serve({
|
|
23
|
+
basePath,
|
|
24
|
+
importMaps: [internalImportMap],
|
|
10
25
|
port,
|
|
11
26
|
});
|
|
12
|
-
const functionPath = join(fixturesDir, 'serve_test', 'echo_env.ts');
|
|
13
27
|
const functions = [
|
|
14
28
|
{
|
|
15
29
|
name: 'echo_env',
|
|
16
|
-
path:
|
|
30
|
+
path: functionPaths.user,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'greet',
|
|
34
|
+
path: functionPaths.internal,
|
|
17
35
|
},
|
|
18
36
|
];
|
|
19
37
|
const options = {
|
|
@@ -23,19 +41,29 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
23
41
|
very_secret_secret: 'i love netlify',
|
|
24
42
|
}, options);
|
|
25
43
|
expect(success).toBe(true);
|
|
26
|
-
expect(functionsConfig).toEqual([{ path: '/my-function' }]);
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
expect(functionsConfig).toEqual([{ path: '/my-function' }, {}]);
|
|
45
|
+
for (const key in functionPaths) {
|
|
46
|
+
const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(
|
|
47
|
+
// @ts-expect-error TODO: Module graph is currently not typed
|
|
48
|
+
({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functionPaths[key]);
|
|
49
|
+
expect(graphEntry).toBe(true);
|
|
50
|
+
}
|
|
51
|
+
const response1 = await fetch(`http://0.0.0.0:${port}/foo`, {
|
|
32
52
|
headers: {
|
|
33
53
|
'x-deno-functions': 'echo_env',
|
|
34
54
|
'x-deno-pass': 'passthrough',
|
|
35
|
-
'X-NF-Request-ID':
|
|
55
|
+
'X-NF-Request-ID': uuidv4(),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
expect(response1.status).toBe(200);
|
|
59
|
+
expect(await response1.text()).toBe('I LOVE NETLIFY');
|
|
60
|
+
const response2 = await fetch(`http://0.0.0.0:${port}/greet`, {
|
|
61
|
+
headers: {
|
|
62
|
+
'x-deno-functions': 'greet',
|
|
63
|
+
'x-deno-pass': 'passthrough',
|
|
64
|
+
'X-NF-Request-ID': uuidv4(),
|
|
36
65
|
},
|
|
37
66
|
});
|
|
38
|
-
expect(
|
|
39
|
-
|
|
40
|
-
expect(body.very_secret_secret).toBe('i love netlify');
|
|
67
|
+
expect(response2.status).toBe(200);
|
|
68
|
+
expect(await response2.text()).toBe('HELLO!');
|
|
41
69
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
"find-up": "^6.3.0",
|
|
85
85
|
"get-port": "^6.1.2",
|
|
86
86
|
"glob-to-regexp": "^0.4.1",
|
|
87
|
+
"jsonc-parser": "^3.2.0",
|
|
87
88
|
"node-fetch": "^3.1.1",
|
|
88
89
|
"node-stream-zip": "^1.15.0",
|
|
89
90
|
"p-retry": "^5.1.1",
|