@netlify/edge-bundler 9.5.0 → 10.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/README.md +29 -19
- package/deno/vendor/deno.land/x/deno_graph@0.59.2/media_type.ts +20 -0
- package/deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.ts +182 -0
- package/dist/deno/vendor/deno.land/x/deno_graph@0.59.2/media_type.d.ts +18 -0
- package/dist/deno/vendor/deno.land/x/deno_graph@0.59.2/media_type.js +20 -0
- package/dist/node/bundler.d.ts +2 -1
- package/dist/node/bundler.js +4 -2
- package/dist/node/bundler.test.js +28 -0
- package/dist/node/index.d.ts +2 -1
- package/dist/node/manifest.d.ts +6 -2
- package/dist/node/manifest.js +17 -4
- package/dist/node/manifest.test.js +44 -16
- package/dist/node/npm_dependencies.d.ts +2 -1
- package/dist/node/npm_dependencies.js +13 -10
- package/dist/node/server/server.d.ts +5 -2
- package/dist/node/server/server.js +87 -3
- package/dist/node/server/server.test.js +49 -3
- package/dist/node/utils/fs.d.ts +5 -0
- package/dist/node/utils/fs.js +12 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -9,32 +9,42 @@ Intelligently prepare Netlify Edge Functions for deployment.
|
|
|
9
9
|
|
|
10
10
|
1. Install this module as a dependency in your project
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
```
|
|
13
|
+
npm install @netlify/edge-bundler --save
|
|
14
|
+
```
|
|
15
15
|
|
|
16
16
|
2. Import it and create a bundle from a directory of Edge Functions and a list of declarations.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
```js
|
|
19
|
+
import { bundle } from '@netlify/edge-bundler'
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"/repo/netlify/edge-functions",
|
|
24
|
-
"/repo/.netlify/edge-functions"
|
|
25
|
-
]
|
|
21
|
+
// List of directories to search for Edge Functions.
|
|
22
|
+
const sourceDirectories = ['/repo/netlify/edge-functions', '/repo/.netlify/edge-functions']
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
// Directory where bundle should be placed.
|
|
25
|
+
const distDirectory = '/repo/.netlify/edge-functions-dist'
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
// List of Edge Functions declarations.
|
|
28
|
+
const declarations = [
|
|
29
|
+
{ function: 'user-1', path: '/blog/*' },
|
|
30
|
+
{ function: 'internal-2', path: '/' },
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
await bundle(sourceDirectories, distDirectory, declarations)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Vendored modules
|
|
37
|
+
|
|
38
|
+
To avoid pulling in additional dependencies at runtime, this package vendors some Deno modules in the `deno/vendor`
|
|
39
|
+
directory.
|
|
40
|
+
|
|
41
|
+
You can recreate this directory by running `npm run vendor`.
|
|
42
|
+
|
|
43
|
+
> [!WARNING]
|
|
44
|
+
> At the time of writing, the underlying Deno CLI command doesn't correctly pull the WASM binary required by the ESZIP
|
|
45
|
+
> module. If you run the command to update the list of vendores modules, please ensure you're not deleting
|
|
46
|
+
> `eszip_wasm_bg.wasm`.
|
|
35
47
|
|
|
36
|
-
await bundle(sourceDirectories, distDirectory, declarations)
|
|
37
|
-
```
|
|
38
48
|
## Contributors
|
|
39
49
|
|
|
40
50
|
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on how to set up and work on this repository. Thanks
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
2
|
+
|
|
3
|
+
export enum MediaType {
|
|
4
|
+
JavaScript = "JavaScript",
|
|
5
|
+
Mjs = "Mjs",
|
|
6
|
+
Cjs = "Cjs",
|
|
7
|
+
Jsx = "Jsx",
|
|
8
|
+
TypeScript = "TypeScript",
|
|
9
|
+
Mts = "Mts",
|
|
10
|
+
Cts = "Cts",
|
|
11
|
+
Dts = "Dts",
|
|
12
|
+
Dmts = "Dmts",
|
|
13
|
+
Dcts = "Dcts",
|
|
14
|
+
Tsx = "Tsx",
|
|
15
|
+
Json = "Json",
|
|
16
|
+
Wasm = "Wasm",
|
|
17
|
+
TsBuildInfo = "TsBuildInfo",
|
|
18
|
+
SourceMap = "SourceMap",
|
|
19
|
+
Unknown = "Unknown",
|
|
20
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
2
|
+
|
|
3
|
+
import type { MediaType } from "./media_type.ts";
|
|
4
|
+
|
|
5
|
+
/** Additional meta data that is used to enrich the output of the module
|
|
6
|
+
* graph. */
|
|
7
|
+
export interface CacheInfo {
|
|
8
|
+
/** The string path to where the local version of the content is located. For
|
|
9
|
+
* non `file:` URLs, this is the location of the cached content, otherwise it
|
|
10
|
+
* is the absolute path to the local file. */
|
|
11
|
+
local?: string;
|
|
12
|
+
/** The string path to where a transpiled version of the source content is
|
|
13
|
+
* located, if any. */
|
|
14
|
+
emit?: string;
|
|
15
|
+
/** The string path to where an external source map of the transpiled source
|
|
16
|
+
* content is located, if any. */
|
|
17
|
+
map?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TypesDependency {
|
|
21
|
+
/** The string URL to the type information for the module. */
|
|
22
|
+
types: string;
|
|
23
|
+
/** An optional range which indicates the source of the dependency. */
|
|
24
|
+
source?: Range;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LoadResponseModule {
|
|
28
|
+
/** A module with code has been loaded. */
|
|
29
|
+
kind: "module";
|
|
30
|
+
/** The string URL of the resource. If there were redirects, the final
|
|
31
|
+
* specifier should be set here, otherwise the requested specifier. */
|
|
32
|
+
specifier: string;
|
|
33
|
+
/** For remote resources, a record of headers should be set, where the key's
|
|
34
|
+
* have been normalized to be lower case values. */
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
/** The string value of the loaded resources. */
|
|
37
|
+
content: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LoadResponseExternal {
|
|
41
|
+
/** The loaded module is either _external_ or _built-in_ to the runtime. */
|
|
42
|
+
kind: "external";
|
|
43
|
+
/** The strung URL of the resource. If there were redirects, the final
|
|
44
|
+
* specifier should be set here, otherwise the requested specifier. */
|
|
45
|
+
specifier: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type LoadResponse = LoadResponseModule | LoadResponseExternal;
|
|
49
|
+
|
|
50
|
+
export interface PositionJson {
|
|
51
|
+
/** The line number of a position within a source file. The number is a zero
|
|
52
|
+
* based index. */
|
|
53
|
+
line: number;
|
|
54
|
+
/** The character number of a position within a source file. The number is a
|
|
55
|
+
* zero based index. */
|
|
56
|
+
character: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface Range {
|
|
60
|
+
/** A string URL representing a source of a dependency. */
|
|
61
|
+
specifier: string;
|
|
62
|
+
/** The start location of a range of text in a source file. */
|
|
63
|
+
start?: PositionJson;
|
|
64
|
+
/** The end location of a range of text in a source file. */
|
|
65
|
+
end?: PositionJson;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface RangeJson {
|
|
69
|
+
/** The start location of a range of text in a source file. */
|
|
70
|
+
start: PositionJson;
|
|
71
|
+
/** The end location of a range of text in a source file. */
|
|
72
|
+
end: PositionJson;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ResolvedDependency {
|
|
76
|
+
/** The fully resolved string URL of the dependency, which should be
|
|
77
|
+
* resolvable in the module graph. If there was an error, `error` will be set
|
|
78
|
+
* and this will be undefined. */
|
|
79
|
+
specifier?: string;
|
|
80
|
+
/** Any error encountered when trying to resolved the specifier. If this is
|
|
81
|
+
* defined, `specifier` will be undefined. */
|
|
82
|
+
error?: string;
|
|
83
|
+
/** The range within the source code where the specifier was identified. */
|
|
84
|
+
span: RangeJson;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface TypesDependencyJson {
|
|
88
|
+
/** The string specifier that was used for the dependency. */
|
|
89
|
+
specifier: string;
|
|
90
|
+
/** An object pointing to the resolved dependency. */
|
|
91
|
+
dependency: ResolvedDependency;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The kind of module.
|
|
95
|
+
*
|
|
96
|
+
* For asserted modules, the value of the `asserted` property is set to the
|
|
97
|
+
* `type` value of the import attribute.
|
|
98
|
+
*
|
|
99
|
+
* Dependency analysis is not performed for asserted or Script modules
|
|
100
|
+
* currently. Synthetic modules were injected into the graph with their own
|
|
101
|
+
* dependencies provided. */
|
|
102
|
+
export type ModuleKind =
|
|
103
|
+
| "asserted"
|
|
104
|
+
| "esm"
|
|
105
|
+
| "npm"
|
|
106
|
+
| "external";
|
|
107
|
+
|
|
108
|
+
export interface DependencyJson {
|
|
109
|
+
/** The string specifier that was used for the dependency. */
|
|
110
|
+
specifier: string;
|
|
111
|
+
/** An object pointing to the resolved _code_ dependency. */
|
|
112
|
+
code?: ResolvedDependency;
|
|
113
|
+
/** An object pointing to the resolved _type_ dependency of a module. This is
|
|
114
|
+
* populated when the `@deno-types` directive was used to supply a type
|
|
115
|
+
* definition file for a dependency. */
|
|
116
|
+
type?: ResolvedDependency;
|
|
117
|
+
/** A flag indicating if the dependency was dynamic. (e.g.
|
|
118
|
+
* `await import("mod.ts")`) */
|
|
119
|
+
isDynamic?: true;
|
|
120
|
+
assertionType?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// todo(dsherret): split this up into separate types based on the "kind"
|
|
124
|
+
|
|
125
|
+
export interface ModuleJson extends CacheInfo {
|
|
126
|
+
/** The string URL of the module. */
|
|
127
|
+
specifier: string;
|
|
128
|
+
/** Any error encountered when attempting to load the module. */
|
|
129
|
+
error?: string;
|
|
130
|
+
/** The module kind that was determined when the module was resolved. This is
|
|
131
|
+
* used by loaders to indicate how a module needs to be loaded at runtime. */
|
|
132
|
+
kind?: ModuleKind;
|
|
133
|
+
/** An array of dependencies that were identified in the module. */
|
|
134
|
+
dependencies?: DependencyJson[];
|
|
135
|
+
/** If the module had a types dependency, the information about that
|
|
136
|
+
* dependency. */
|
|
137
|
+
typesDependency?: TypesDependencyJson;
|
|
138
|
+
/** The resolved media type of the module, which determines how Deno will
|
|
139
|
+
* handle the module. */
|
|
140
|
+
mediaType?: MediaType;
|
|
141
|
+
/** The size of the source content of the module in bytes. */
|
|
142
|
+
size?: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface GraphImportJson {
|
|
146
|
+
/** The referrer (URL string) that was used as a base when resolving the
|
|
147
|
+
* imports added to the graph. */
|
|
148
|
+
referrer: string;
|
|
149
|
+
/** An array of resolved dependencies which were imported using the
|
|
150
|
+
* referrer. */
|
|
151
|
+
dependencies?: DependencyJson[];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The plain-object representation of a module graph that is suitable for
|
|
155
|
+
* serialization to JSON. */
|
|
156
|
+
export interface ModuleGraphJson {
|
|
157
|
+
/** The module specifiers (URL string) of the _roots_ of the module graph of
|
|
158
|
+
* which the module graph was built for. */
|
|
159
|
+
roots: string[];
|
|
160
|
+
/** An array of modules that are part of the module graph. */
|
|
161
|
+
modules: ModuleJson[];
|
|
162
|
+
/** External imports that were added to the graph when it was being built.
|
|
163
|
+
* The key is the referrer which was used as a base to resolve the
|
|
164
|
+
* dependency. The value is the resolved dependency. */
|
|
165
|
+
imports?: GraphImportJson[];
|
|
166
|
+
/** A record/map of any redirects encountered when resolving modules. The
|
|
167
|
+
* key was the requested module specifier and the value is the redirected
|
|
168
|
+
* module specifier. */
|
|
169
|
+
redirects: Record<string, string>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface Dependency {
|
|
173
|
+
/** An object pointing to the resolved _code_ dependency. */
|
|
174
|
+
code?: ResolvedDependency;
|
|
175
|
+
/** An object pointing to the resolved _type_ dependency of a module. This is
|
|
176
|
+
* populated when the `@deno-types` directive was used to supply a type
|
|
177
|
+
* definition file for a dependency. */
|
|
178
|
+
type?: ResolvedDependency;
|
|
179
|
+
/** A flag indicating if the dependency was dynamic. (e.g.
|
|
180
|
+
* `await import("mod.ts")`) */
|
|
181
|
+
isDynamic?: true;
|
|
182
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare enum MediaType {
|
|
2
|
+
JavaScript = "JavaScript",
|
|
3
|
+
Mjs = "Mjs",
|
|
4
|
+
Cjs = "Cjs",
|
|
5
|
+
Jsx = "Jsx",
|
|
6
|
+
TypeScript = "TypeScript",
|
|
7
|
+
Mts = "Mts",
|
|
8
|
+
Cts = "Cts",
|
|
9
|
+
Dts = "Dts",
|
|
10
|
+
Dmts = "Dmts",
|
|
11
|
+
Dcts = "Dcts",
|
|
12
|
+
Tsx = "Tsx",
|
|
13
|
+
Json = "Json",
|
|
14
|
+
Wasm = "Wasm",
|
|
15
|
+
TsBuildInfo = "TsBuildInfo",
|
|
16
|
+
SourceMap = "SourceMap",
|
|
17
|
+
Unknown = "Unknown"
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
2
|
+
export var MediaType;
|
|
3
|
+
(function (MediaType) {
|
|
4
|
+
MediaType["JavaScript"] = "JavaScript";
|
|
5
|
+
MediaType["Mjs"] = "Mjs";
|
|
6
|
+
MediaType["Cjs"] = "Cjs";
|
|
7
|
+
MediaType["Jsx"] = "Jsx";
|
|
8
|
+
MediaType["TypeScript"] = "TypeScript";
|
|
9
|
+
MediaType["Mts"] = "Mts";
|
|
10
|
+
MediaType["Cts"] = "Cts";
|
|
11
|
+
MediaType["Dts"] = "Dts";
|
|
12
|
+
MediaType["Dmts"] = "Dmts";
|
|
13
|
+
MediaType["Dcts"] = "Dcts";
|
|
14
|
+
MediaType["Tsx"] = "Tsx";
|
|
15
|
+
MediaType["Json"] = "Json";
|
|
16
|
+
MediaType["Wasm"] = "Wasm";
|
|
17
|
+
MediaType["TsBuildInfo"] = "TsBuildInfo";
|
|
18
|
+
MediaType["SourceMap"] = "SourceMap";
|
|
19
|
+
MediaType["Unknown"] = "Unknown";
|
|
20
|
+
})(MediaType || (MediaType = {}));
|
package/dist/node/bundler.d.ts
CHANGED
|
@@ -15,11 +15,12 @@ export interface BundleOptions {
|
|
|
15
15
|
internalSrcFolder?: string;
|
|
16
16
|
onAfterDownload?: OnAfterDownloadHook;
|
|
17
17
|
onBeforeDownload?: OnBeforeDownloadHook;
|
|
18
|
+
rootPath?: string;
|
|
18
19
|
systemLogger?: LogFunction;
|
|
19
20
|
userLogger?: LogFunction;
|
|
20
21
|
vendorDirectory?: string;
|
|
21
22
|
}
|
|
22
|
-
export declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, internalSrcFolder, onAfterDownload, onBeforeDownload, userLogger, systemLogger, vendorDirectory, }?: BundleOptions) => Promise<{
|
|
23
|
+
export declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, internalSrcFolder, onAfterDownload, onBeforeDownload, rootPath, userLogger, systemLogger, vendorDirectory, }?: BundleOptions) => Promise<{
|
|
23
24
|
functions: EdgeFunction[];
|
|
24
25
|
manifest: import("./manifest.js").Manifest;
|
|
25
26
|
}>;
|
package/dist/node/bundler.js
CHANGED
|
@@ -15,7 +15,7 @@ import { getLogger } from './logger.js';
|
|
|
15
15
|
import { writeManifest } from './manifest.js';
|
|
16
16
|
import { vendorNPMSpecifiers } from './npm_dependencies.js';
|
|
17
17
|
import { ensureLatestTypes } from './types.js';
|
|
18
|
-
export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], internalSrcFolder, onAfterDownload, onBeforeDownload, userLogger, systemLogger, vendorDirectory, } = {}) => {
|
|
18
|
+
export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], internalSrcFolder, onAfterDownload, onBeforeDownload, rootPath, userLogger, systemLogger, vendorDirectory, } = {}) => {
|
|
19
19
|
const logger = getLogger(systemLogger, userLogger, debug);
|
|
20
20
|
const featureFlags = getFlags(inputFeatureFlags);
|
|
21
21
|
const options = {
|
|
@@ -53,6 +53,7 @@ export const bundle = async (sourceDirectories, distDirectory, tomlDeclarations
|
|
|
53
53
|
functions,
|
|
54
54
|
importMap,
|
|
55
55
|
logger,
|
|
56
|
+
rootPath: rootPath !== null && rootPath !== void 0 ? rootPath : basePath,
|
|
56
57
|
vendorDirectory,
|
|
57
58
|
});
|
|
58
59
|
if (vendor) {
|
|
@@ -147,7 +148,7 @@ const createFunctionConfig = ({ internalFunctionsWithConfig, declarations }) =>
|
|
|
147
148
|
[functionName]: addGeneratorFallback(mergedConfigFields),
|
|
148
149
|
};
|
|
149
150
|
}, {});
|
|
150
|
-
const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logger, vendorDirectory, }) => {
|
|
151
|
+
const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logger, rootPath, vendorDirectory, }) => {
|
|
151
152
|
try {
|
|
152
153
|
return await vendorNPMSpecifiers({
|
|
153
154
|
basePath,
|
|
@@ -156,6 +157,7 @@ const safelyVendorNPMSpecifiers = async ({ basePath, functions, importMap, logge
|
|
|
156
157
|
importMap,
|
|
157
158
|
logger,
|
|
158
159
|
referenceTypes: false,
|
|
160
|
+
rootPath,
|
|
159
161
|
});
|
|
160
162
|
}
|
|
161
163
|
catch (error) {
|
|
@@ -380,6 +380,34 @@ test('Loads npm modules from bare specifiers', async () => {
|
|
|
380
380
|
await cleanup();
|
|
381
381
|
await rm(vendorDirectory.path, { force: true, recursive: true });
|
|
382
382
|
});
|
|
383
|
+
test('Loads npm modules in a monorepo setup', async () => {
|
|
384
|
+
const systemLogger = vi.fn();
|
|
385
|
+
const { basePath: rootPath, cleanup, distPath } = await useFixture('monorepo_npm_module');
|
|
386
|
+
const basePath = join(rootPath, 'packages', 'frontend');
|
|
387
|
+
const sourceDirectory = join(basePath, 'functions');
|
|
388
|
+
const declarations = [
|
|
389
|
+
{
|
|
390
|
+
function: 'func1',
|
|
391
|
+
path: '/func1',
|
|
392
|
+
},
|
|
393
|
+
];
|
|
394
|
+
const vendorDirectory = await tmp.dir();
|
|
395
|
+
await bundle([sourceDirectory], distPath, declarations, {
|
|
396
|
+
basePath,
|
|
397
|
+
importMapPaths: [join(basePath, 'import_map.json')],
|
|
398
|
+
rootPath,
|
|
399
|
+
vendorDirectory: vendorDirectory.path,
|
|
400
|
+
systemLogger,
|
|
401
|
+
});
|
|
402
|
+
expect(systemLogger.mock.calls.find((call) => call[0] === 'Could not track dependencies in edge function:')).toBeUndefined();
|
|
403
|
+
const manifestFile = await readFile(resolve(distPath, 'manifest.json'), 'utf8');
|
|
404
|
+
const manifest = JSON.parse(manifestFile);
|
|
405
|
+
const bundlePath = join(distPath, manifest.bundles[0].asset);
|
|
406
|
+
const { func1 } = await runESZIP(bundlePath, vendorDirectory.path);
|
|
407
|
+
expect(func1).toBe(`<parent-1><child-1>JavaScript</child-1></parent-1>, <parent-2><child-2><grandchild-1>APIs<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-2>, <parent-3><child-2><grandchild-1>Markup<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-3>`);
|
|
408
|
+
await cleanup();
|
|
409
|
+
await rm(vendorDirectory.path, { force: true, recursive: true });
|
|
410
|
+
});
|
|
383
411
|
test('Loads JSON modules', async () => {
|
|
384
412
|
const { basePath, cleanup, distPath } = await useFixture('imports_json');
|
|
385
413
|
const sourceDirectory = join(basePath, 'functions');
|
package/dist/node/index.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ export { Declaration, mergeDeclarations } from './declaration.js';
|
|
|
5
5
|
export type { EdgeFunction } from './edge_function.js';
|
|
6
6
|
export { findFunctions as find } from './finder.js';
|
|
7
7
|
export { generateManifest } from './manifest.js';
|
|
8
|
-
export {
|
|
8
|
+
export type { EdgeFunctionConfig, Manifest } from './manifest.js';
|
|
9
|
+
export { ModuleGraph, serve } from './server/server.js';
|
|
9
10
|
export { validateManifest, ManifestValidationError } from './validation/manifest/index.js';
|
package/dist/node/manifest.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ interface Route {
|
|
|
11
11
|
path?: string;
|
|
12
12
|
methods?: string[];
|
|
13
13
|
}
|
|
14
|
-
interface EdgeFunctionConfig {
|
|
14
|
+
export interface EdgeFunctionConfig {
|
|
15
15
|
excluded_patterns: string[];
|
|
16
16
|
on_error?: string;
|
|
17
17
|
generator?: string;
|
|
@@ -42,7 +42,11 @@ interface GenerateManifestOptions {
|
|
|
42
42
|
layers?: Layer[];
|
|
43
43
|
userFunctionConfig?: Record<string, FunctionConfig>;
|
|
44
44
|
}
|
|
45
|
-
declare const generateManifest: ({ bundles, declarations, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) =>
|
|
45
|
+
declare const generateManifest: ({ bundles, declarations, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => {
|
|
46
|
+
declarationsWithoutFunction: string[];
|
|
47
|
+
manifest: Manifest;
|
|
48
|
+
unroutedFunctions: string[];
|
|
49
|
+
};
|
|
46
50
|
interface WriteManifestOptions extends GenerateManifestOptions {
|
|
47
51
|
distDirectory: string;
|
|
48
52
|
}
|
package/dist/node/manifest.js
CHANGED
|
@@ -29,7 +29,7 @@ const sanitizeEdgeFunctionConfig = (config) => {
|
|
|
29
29
|
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => {
|
|
30
30
|
if (excludedPath) {
|
|
31
31
|
const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
|
|
32
|
-
const excludedPatterns = paths.map(
|
|
32
|
+
const excludedPatterns = paths.map(pathToRegularExpression).filter(nonNullable).map(serializePattern);
|
|
33
33
|
manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
|
|
34
34
|
}
|
|
35
35
|
};
|
|
@@ -50,6 +50,8 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
|
|
|
50
50
|
const preCacheRoutes = [];
|
|
51
51
|
const postCacheRoutes = [];
|
|
52
52
|
const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
|
|
53
|
+
const routedFunctions = new Set();
|
|
54
|
+
const declarationsWithoutFunction = new Set();
|
|
53
55
|
for (const [name, { excludedPath, onError }] of Object.entries(userFunctionConfig)) {
|
|
54
56
|
// If the config block is for a function that is not defined, discard it.
|
|
55
57
|
if (manifestFunctionConfig[name] === undefined) {
|
|
@@ -69,9 +71,16 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
|
|
|
69
71
|
declarations.forEach((declaration) => {
|
|
70
72
|
const func = functions.find(({ name }) => declaration.function === name);
|
|
71
73
|
if (func === undefined) {
|
|
74
|
+
declarationsWithoutFunction.add(declaration.function);
|
|
72
75
|
return;
|
|
73
76
|
}
|
|
74
77
|
const pattern = getRegularExpression(declaration);
|
|
78
|
+
// If there is no `pattern`, the declaration will never be triggered, so we
|
|
79
|
+
// can discard it.
|
|
80
|
+
if (!pattern) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
routedFunctions.add(declaration.function);
|
|
75
84
|
const excludedPattern = getExcludedRegularExpressions(declaration);
|
|
76
85
|
const route = {
|
|
77
86
|
function: func.name,
|
|
@@ -104,9 +113,13 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
|
|
|
104
113
|
import_map: importMap,
|
|
105
114
|
function_config: sanitizeEdgeFunctionConfig(manifestFunctionConfig),
|
|
106
115
|
};
|
|
107
|
-
|
|
116
|
+
const unroutedFunctions = functions.filter(({ name }) => !routedFunctions.has(name)).map(({ name }) => name);
|
|
117
|
+
return { declarationsWithoutFunction: [...declarationsWithoutFunction], manifest, unroutedFunctions };
|
|
108
118
|
};
|
|
109
119
|
const pathToRegularExpression = (path) => {
|
|
120
|
+
if (!path) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
110
123
|
try {
|
|
111
124
|
const pattern = new ExtendedURLPattern({ pathname: path });
|
|
112
125
|
// Removing the `^` and `$` delimiters because we'll need to modify what's
|
|
@@ -149,12 +162,12 @@ const getExcludedRegularExpressions = (declaration) => {
|
|
|
149
162
|
}
|
|
150
163
|
if ('path' in declaration && declaration.excludedPath) {
|
|
151
164
|
const paths = Array.isArray(declaration.excludedPath) ? declaration.excludedPath : [declaration.excludedPath];
|
|
152
|
-
return paths.map(
|
|
165
|
+
return paths.map(pathToRegularExpression).filter(nonNullable);
|
|
153
166
|
}
|
|
154
167
|
return [];
|
|
155
168
|
};
|
|
156
169
|
const writeManifest = async ({ distDirectory, ...rest }) => {
|
|
157
|
-
const manifest = generateManifest(rest);
|
|
170
|
+
const { manifest } = generateManifest(rest);
|
|
158
171
|
const manifestPath = join(distDirectory, 'manifest.json');
|
|
159
172
|
await fs.writeFile(manifestPath, JSON.stringify(manifest));
|
|
160
173
|
return manifest;
|
|
@@ -17,7 +17,7 @@ test('Generates a manifest with different bundles', () => {
|
|
|
17
17
|
};
|
|
18
18
|
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
19
19
|
const declarations = [{ function: 'func-1', path: '/f1' }];
|
|
20
|
-
const manifest = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
|
|
20
|
+
const { manifest } = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
|
|
21
21
|
const expectedBundles = [
|
|
22
22
|
{ asset: bundle1.hash + bundle1.extension, format: bundle1.format },
|
|
23
23
|
{ asset: bundle2.hash + bundle2.extension, format: bundle2.format },
|
|
@@ -35,7 +35,7 @@ test('Generates a manifest with display names', () => {
|
|
|
35
35
|
name: 'Display Name',
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
|
-
const manifest = generateManifest({
|
|
38
|
+
const { manifest } = generateManifest({
|
|
39
39
|
bundles: [],
|
|
40
40
|
declarations,
|
|
41
41
|
functions,
|
|
@@ -56,7 +56,7 @@ test('Generates a manifest with a generator field', () => {
|
|
|
56
56
|
generator: '@netlify/fake-plugin@1.0.0',
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
|
-
const manifest = generateManifest({
|
|
59
|
+
const { manifest } = generateManifest({
|
|
60
60
|
bundles: [],
|
|
61
61
|
declarations,
|
|
62
62
|
functions,
|
|
@@ -79,7 +79,7 @@ test('Generates a manifest with excluded paths and patterns', () => {
|
|
|
79
79
|
{ function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excludedPattern: ['^/f2/exclude$', '^/f2/exclude-as-well$'] },
|
|
80
80
|
{ function: 'func-3', path: '/*', excludedPath: '/**/*.html' },
|
|
81
81
|
];
|
|
82
|
-
const manifest = generateManifest({
|
|
82
|
+
const { manifest } = generateManifest({
|
|
83
83
|
bundles: [],
|
|
84
84
|
declarations,
|
|
85
85
|
functions,
|
|
@@ -111,7 +111,7 @@ test('TOML-defined paths can be combined with ISC-defined excluded paths', () =>
|
|
|
111
111
|
const userFunctionConfig = {
|
|
112
112
|
'func-1': { excludedPath: '/f1/exclude' },
|
|
113
113
|
};
|
|
114
|
-
const manifest = generateManifest({
|
|
114
|
+
const { manifest } = generateManifest({
|
|
115
115
|
bundles: [],
|
|
116
116
|
declarations,
|
|
117
117
|
functions,
|
|
@@ -153,7 +153,7 @@ test('Filters out internal in-source configurations in user created functions',
|
|
|
153
153
|
generator: 'internal-generator',
|
|
154
154
|
},
|
|
155
155
|
};
|
|
156
|
-
const manifest = generateManifest({
|
|
156
|
+
const { manifest } = generateManifest({
|
|
157
157
|
bundles: [],
|
|
158
158
|
declarations,
|
|
159
159
|
functions,
|
|
@@ -186,7 +186,7 @@ test('excludedPath from ISC goes into function_config, TOML goes into routes', (
|
|
|
186
186
|
},
|
|
187
187
|
};
|
|
188
188
|
const internalFunctionConfig = {};
|
|
189
|
-
const manifest = generateManifest({
|
|
189
|
+
const { manifest } = generateManifest({
|
|
190
190
|
bundles: [],
|
|
191
191
|
declarations,
|
|
192
192
|
functions,
|
|
@@ -224,7 +224,7 @@ test('URLPattern named groups are supported', () => {
|
|
|
224
224
|
const declarations = [{ function: 'customisation', path: '/products/:productId' }];
|
|
225
225
|
const userFunctionConfig = {};
|
|
226
226
|
const internalFunctionConfig = {};
|
|
227
|
-
const manifest = generateManifest({
|
|
227
|
+
const { manifest } = generateManifest({
|
|
228
228
|
bundles: [],
|
|
229
229
|
declarations,
|
|
230
230
|
functions,
|
|
@@ -269,7 +269,7 @@ test('Includes failure modes in manifest', () => {
|
|
|
269
269
|
onError: '/custom-error',
|
|
270
270
|
},
|
|
271
271
|
};
|
|
272
|
-
const manifest = generateManifest({ bundles: [], declarations, functions, userFunctionConfig });
|
|
272
|
+
const { manifest } = generateManifest({ bundles: [], declarations, functions, userFunctionConfig });
|
|
273
273
|
expect(manifest.function_config).toEqual({
|
|
274
274
|
'func-1': { on_error: '/custom-error' },
|
|
275
275
|
});
|
|
@@ -285,7 +285,7 @@ test('Excludes functions for which there are function files but no matching conf
|
|
|
285
285
|
{ name: 'func-2', path: '/path/to/func-2.ts' },
|
|
286
286
|
];
|
|
287
287
|
const declarations = [{ function: 'func-1', path: '/f1' }];
|
|
288
|
-
const manifest = generateManifest({ bundles: [bundle1], declarations, functions });
|
|
288
|
+
const { manifest } = generateManifest({ bundles: [bundle1], declarations, functions });
|
|
289
289
|
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
|
|
290
290
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
291
291
|
});
|
|
@@ -300,14 +300,14 @@ test('Excludes functions for which there are config declarations but no matching
|
|
|
300
300
|
{ function: 'func-1', path: '/f1' },
|
|
301
301
|
{ function: 'func-2', path: '/f2' },
|
|
302
302
|
];
|
|
303
|
-
const manifest = generateManifest({ bundles: [bundle1], declarations, functions });
|
|
303
|
+
const { manifest } = generateManifest({ bundles: [bundle1], declarations, functions });
|
|
304
304
|
const expectedRoutes = [{ function: 'func-2', pattern: '^/f2/?$', excluded_patterns: [], path: '/f2' }];
|
|
305
305
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
306
306
|
});
|
|
307
307
|
test('Generates a manifest without bundles', () => {
|
|
308
308
|
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
309
309
|
const declarations = [{ function: 'func-1', path: '/f1' }];
|
|
310
|
-
const manifest = generateManifest({ bundles: [], declarations, functions });
|
|
310
|
+
const { manifest } = generateManifest({ bundles: [], declarations, functions });
|
|
311
311
|
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
|
|
312
312
|
expect(manifest.bundles).toEqual([]);
|
|
313
313
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
@@ -334,7 +334,7 @@ test('Generates a manifest with pre and post-cache routes', () => {
|
|
|
334
334
|
{ function: 'func-2', cache: 'not_a_supported_value', path: '/f2' },
|
|
335
335
|
{ function: 'func-3', cache: 'manual', path: '/f3' },
|
|
336
336
|
];
|
|
337
|
-
const manifest = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
|
|
337
|
+
const { manifest } = generateManifest({ bundles: [bundle1, bundle2], declarations, functions });
|
|
338
338
|
const expectedBundles = [
|
|
339
339
|
{ asset: bundle1.hash + bundle1.extension, format: bundle1.format },
|
|
340
340
|
{ asset: bundle2.hash + bundle2.extension, format: bundle2.format },
|
|
@@ -370,12 +370,12 @@ test('Generates a manifest with layers', () => {
|
|
|
370
370
|
flag: 'edge_functions_onion_layer',
|
|
371
371
|
},
|
|
372
372
|
];
|
|
373
|
-
const manifest1 = generateManifest({
|
|
373
|
+
const { manifest: manifest1 } = generateManifest({
|
|
374
374
|
bundles: [],
|
|
375
375
|
declarations,
|
|
376
376
|
functions,
|
|
377
377
|
});
|
|
378
|
-
const manifest2 = generateManifest({
|
|
378
|
+
const { manifest: manifest2 } = generateManifest({
|
|
379
379
|
bundles: [],
|
|
380
380
|
declarations,
|
|
381
381
|
functions,
|
|
@@ -398,6 +398,34 @@ test('Throws an error if the regular expression contains a negative lookahead',
|
|
|
398
398
|
test('Converts named capture groups to unnamed capture groups in regular expressions', () => {
|
|
399
399
|
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
400
400
|
const declarations = [{ function: 'func-1', pattern: '^/(?<name>\\w+)$' }];
|
|
401
|
-
const manifest = generateManifest({ bundles: [], declarations, functions });
|
|
401
|
+
const { manifest } = generateManifest({ bundles: [], declarations, functions });
|
|
402
402
|
expect(manifest.routes).toEqual([{ function: 'func-1', pattern: '^/(\\w+)$', excluded_patterns: [] }]);
|
|
403
403
|
});
|
|
404
|
+
test('Returns functions without a declaration and unrouted functions', () => {
|
|
405
|
+
const bundle = {
|
|
406
|
+
extension: '.ext1',
|
|
407
|
+
format: BundleFormat.ESZIP2,
|
|
408
|
+
hash: '123456',
|
|
409
|
+
};
|
|
410
|
+
const functions = [
|
|
411
|
+
{ name: 'func-1', path: '/path/to/func-1.ts' },
|
|
412
|
+
{ name: 'func-2', path: '/path/to/func-2.ts' },
|
|
413
|
+
{ name: 'func-4', path: '/path/to/func-4.ts' },
|
|
414
|
+
];
|
|
415
|
+
const declarations = [
|
|
416
|
+
{ function: 'func-1', path: '/f1' },
|
|
417
|
+
{ function: 'func-3', path: '/f3' },
|
|
418
|
+
// @ts-expect-error Error is expected due to neither `path` or `pattern`
|
|
419
|
+
// being present.
|
|
420
|
+
{ function: 'func-4', name: 'Some name' },
|
|
421
|
+
];
|
|
422
|
+
const { declarationsWithoutFunction, manifest, unroutedFunctions } = generateManifest({
|
|
423
|
+
bundles: [bundle],
|
|
424
|
+
declarations,
|
|
425
|
+
functions,
|
|
426
|
+
});
|
|
427
|
+
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
|
|
428
|
+
expect(manifest.routes).toEqual(expectedRoutes);
|
|
429
|
+
expect(declarationsWithoutFunction).toEqual(['func-3']);
|
|
430
|
+
expect(unroutedFunctions).toEqual(['func-2', 'func-4']);
|
|
431
|
+
});
|
|
@@ -8,8 +8,9 @@ interface VendorNPMSpecifiersOptions {
|
|
|
8
8
|
importMap: ImportMap;
|
|
9
9
|
logger: Logger;
|
|
10
10
|
referenceTypes: boolean;
|
|
11
|
+
rootPath?: string;
|
|
11
12
|
}
|
|
12
|
-
export declare const vendorNPMSpecifiers: ({ basePath, directory, functions, importMap, referenceTypes, }: VendorNPMSpecifiersOptions) => Promise<{
|
|
13
|
+
export declare const vendorNPMSpecifiers: ({ basePath, directory, functions, importMap, referenceTypes, rootPath, }: VendorNPMSpecifiersOptions) => Promise<{
|
|
13
14
|
cleanup: () => Promise<void>;
|
|
14
15
|
directory: string;
|
|
15
16
|
importMap: {
|
|
@@ -8,6 +8,7 @@ import { build } from 'esbuild';
|
|
|
8
8
|
import { findUp } from 'find-up';
|
|
9
9
|
import getPackageName from 'get-package-name';
|
|
10
10
|
import tmp from 'tmp-promise';
|
|
11
|
+
import { pathsBetween } from './utils/fs.js';
|
|
11
12
|
const TYPESCRIPT_EXTENSIONS = new Set(['.ts', '.cts', '.mts']);
|
|
12
13
|
const slugifyPackageName = (specifier) => {
|
|
13
14
|
if (!specifier.startsWith('@'))
|
|
@@ -76,16 +77,12 @@ const banner = {
|
|
|
76
77
|
/**
|
|
77
78
|
* Parses a set of functions and returns a list of specifiers that correspond
|
|
78
79
|
* to npm modules.
|
|
79
|
-
*
|
|
80
|
-
* @param basePath Root of the project
|
|
81
|
-
* @param functions Functions to parse
|
|
82
|
-
* @param importMap Import map to apply when resolving imports
|
|
83
|
-
* @param referenceTypes Whether to detect typescript declarations and reference them in the output
|
|
84
80
|
*/
|
|
85
|
-
const getNPMSpecifiers = async (basePath, functions, importMap, referenceTypes) => {
|
|
81
|
+
const getNPMSpecifiers = async ({ basePath, functions, importMap, referenceTypes, rootPath, }) => {
|
|
86
82
|
const baseURL = pathToFileURL(basePath);
|
|
87
83
|
const { reasons } = await nodeFileTrace(functions, {
|
|
88
|
-
base:
|
|
84
|
+
base: rootPath,
|
|
85
|
+
processCwd: basePath,
|
|
89
86
|
readFile: async (filePath) => {
|
|
90
87
|
// If this is a TypeScript file, we need to compile in before we can
|
|
91
88
|
// parse it.
|
|
@@ -165,17 +162,23 @@ const getNPMSpecifiers = async (basePath, functions, importMap, referenceTypes)
|
|
|
165
162
|
npmSpecifiersWithExtraneousFiles: [...npmSpecifiersWithExtraneousFiles],
|
|
166
163
|
};
|
|
167
164
|
};
|
|
168
|
-
export const vendorNPMSpecifiers = async ({ basePath, directory, functions, importMap, referenceTypes, }) => {
|
|
165
|
+
export const vendorNPMSpecifiers = async ({ basePath, directory, functions, importMap, referenceTypes, rootPath = basePath, }) => {
|
|
169
166
|
// The directories that esbuild will use when resolving Node modules. We must
|
|
170
167
|
// set these manually because esbuild will be operating from a temporary
|
|
171
168
|
// directory that will not live inside the project root, so the normal
|
|
172
169
|
// resolution logic won't work.
|
|
173
|
-
const nodePaths =
|
|
170
|
+
const nodePaths = pathsBetween(basePath, rootPath).map((directory) => path.join(directory, 'node_modules'));
|
|
174
171
|
// We need to create some files on disk, which we don't want to write to the
|
|
175
172
|
// project directory. If a custom directory has been specified, we use it.
|
|
176
173
|
// Otherwise, create a random temporary directory.
|
|
177
174
|
const temporaryDirectory = directory ? { path: directory } : await tmp.dir();
|
|
178
|
-
const { npmSpecifiers, npmSpecifiersWithExtraneousFiles } = await getNPMSpecifiers(
|
|
175
|
+
const { npmSpecifiers, npmSpecifiersWithExtraneousFiles } = await getNPMSpecifiers({
|
|
176
|
+
basePath,
|
|
177
|
+
functions,
|
|
178
|
+
importMap: importMap.getContentsWithURLObjects(),
|
|
179
|
+
referenceTypes,
|
|
180
|
+
rootPath,
|
|
181
|
+
});
|
|
179
182
|
// If we found no specifiers, there's nothing left to do here.
|
|
180
183
|
if (Object.keys(npmSpecifiers).length === 0) {
|
|
181
184
|
return;
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
/// <reference types="node" />
|
|
5
5
|
/// <reference types="node" />
|
|
6
|
+
import type { ModuleGraphJson } from '../../deno/vendor/deno.land/x/deno_graph@0.59.2/types.d.js';
|
|
6
7
|
import { OnAfterDownloadHook, OnBeforeDownloadHook } from '../bridge.js';
|
|
7
8
|
import { FunctionConfig } from '../config.js';
|
|
8
9
|
import type { EdgeFunction } from '../edge_function.js';
|
|
9
10
|
import type { FeatureFlags } from '../feature_flags.js';
|
|
10
11
|
import { LogFunction } from '../logger.js';
|
|
11
12
|
export type FormatFunction = (name: string) => string;
|
|
13
|
+
export type ModuleGraph = ModuleGraphJson;
|
|
12
14
|
interface StartServerOptions {
|
|
13
15
|
getFunctionsConfig?: boolean;
|
|
14
16
|
}
|
|
@@ -31,14 +33,15 @@ interface ServeOptions {
|
|
|
31
33
|
formatExportTypeError?: FormatFunction;
|
|
32
34
|
formatImportError?: FormatFunction;
|
|
33
35
|
port: number;
|
|
36
|
+
rootPath?: string;
|
|
34
37
|
servePath: string;
|
|
35
38
|
userLogger?: LogFunction;
|
|
36
39
|
systemLogger?: LogFunction;
|
|
37
40
|
}
|
|
38
|
-
export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, importMapPaths, onAfterDownload, onBeforeDownload, port, servePath, userLogger, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
|
|
41
|
+
export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, importMapPaths, onAfterDownload, onBeforeDownload, port, rootPath, servePath, userLogger, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
|
|
39
42
|
features: Record<string, boolean>;
|
|
40
43
|
functionsConfig: FunctionConfig[];
|
|
41
|
-
graph:
|
|
44
|
+
graph: ModuleGraphJson;
|
|
42
45
|
npmSpecifiersWithExtraneousFiles: string[];
|
|
43
46
|
success: boolean;
|
|
44
47
|
}>>;
|
|
@@ -18,13 +18,17 @@ const cleanDirectory = async (directory, except) => {
|
|
|
18
18
|
const toBeDeleted = files.filter((file) => !except.includes(join(directory, file)));
|
|
19
19
|
await Promise.all(toBeDeleted.map((file) => unlink(join(directory, file))));
|
|
20
20
|
};
|
|
21
|
-
const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, importMap: baseImportMap, logger, port, }) => {
|
|
21
|
+
const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, importMap: baseImportMap, logger, port, rootPath, }) => {
|
|
22
22
|
const processRef = {};
|
|
23
23
|
const startServer = async (functions, env = {}, options = {}) => {
|
|
24
24
|
if ((processRef === null || processRef === void 0 ? void 0 : processRef.ps) !== undefined) {
|
|
25
25
|
await killProcess(processRef.ps);
|
|
26
26
|
}
|
|
27
|
-
let graph
|
|
27
|
+
let graph = {
|
|
28
|
+
roots: [],
|
|
29
|
+
modules: [],
|
|
30
|
+
redirects: {},
|
|
31
|
+
};
|
|
28
32
|
const stage2Path = await generateStage2({
|
|
29
33
|
bootstrapURL,
|
|
30
34
|
distDirectory,
|
|
@@ -45,6 +49,7 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
|
|
|
45
49
|
importMap,
|
|
46
50
|
logger,
|
|
47
51
|
referenceTypes: true,
|
|
52
|
+
rootPath,
|
|
48
53
|
});
|
|
49
54
|
if (vendor) {
|
|
50
55
|
features.npmModules = true;
|
|
@@ -92,7 +97,85 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
|
|
|
92
97
|
};
|
|
93
98
|
return startServer;
|
|
94
99
|
};
|
|
95
|
-
export const serve = async ({
|
|
100
|
+
export const serve = async ({
|
|
101
|
+
/**
|
|
102
|
+
* Path that is common to all functions. Works as the root directory in the
|
|
103
|
+
* generated bundle.
|
|
104
|
+
*/
|
|
105
|
+
basePath,
|
|
106
|
+
/**
|
|
107
|
+
* URL of the bootstrap layer to use.
|
|
108
|
+
*/
|
|
109
|
+
bootstrapURL,
|
|
110
|
+
/**
|
|
111
|
+
* Path to an SSL certificate to run the Deno server with.
|
|
112
|
+
*/
|
|
113
|
+
certificatePath,
|
|
114
|
+
/**
|
|
115
|
+
* Whether to print verbose information about the server process.
|
|
116
|
+
*/
|
|
117
|
+
debug,
|
|
118
|
+
/**
|
|
119
|
+
* Path of an import map file to be generated using the built-in specifiers
|
|
120
|
+
* and any npm modules found during the bundling process.
|
|
121
|
+
*/
|
|
122
|
+
distImportMapPath,
|
|
123
|
+
/**
|
|
124
|
+
* Debug settings to use with Deno's `--inspect` and `--inspect-brk` flags.
|
|
125
|
+
*/
|
|
126
|
+
inspectSettings,
|
|
127
|
+
/**
|
|
128
|
+
* Map of feature flags.
|
|
129
|
+
*/
|
|
130
|
+
featureFlags,
|
|
131
|
+
/**
|
|
132
|
+
* Callback function to be triggered whenever a function has a default export
|
|
133
|
+
* with the wrong type.
|
|
134
|
+
*/
|
|
135
|
+
formatExportTypeError,
|
|
136
|
+
/**
|
|
137
|
+
* Callback function to be triggered whenever an error occurs while importing
|
|
138
|
+
* a function.
|
|
139
|
+
*/
|
|
140
|
+
formatImportError,
|
|
141
|
+
/**
|
|
142
|
+
* Paths to any additional import map files.
|
|
143
|
+
*/
|
|
144
|
+
importMapPaths = [],
|
|
145
|
+
/**
|
|
146
|
+
* Callback function to be triggered after the Deno CLI has been downloaded.
|
|
147
|
+
*/
|
|
148
|
+
onAfterDownload,
|
|
149
|
+
/**
|
|
150
|
+
* Callback function to be triggered before we attempt to download the Deno
|
|
151
|
+
* CLI.
|
|
152
|
+
*/
|
|
153
|
+
onBeforeDownload,
|
|
154
|
+
/**
|
|
155
|
+
* Port where the server should listen on.
|
|
156
|
+
*/
|
|
157
|
+
port,
|
|
158
|
+
/**
|
|
159
|
+
* Root path of the project. Defines a boundary outside of which files or npm
|
|
160
|
+
* modules cannot be included from. This is usually the same as `basePath`,
|
|
161
|
+
* with monorepos being the main exception, where `basePath` maps to the
|
|
162
|
+
* package path and `rootPath` is the repository root.
|
|
163
|
+
*/
|
|
164
|
+
rootPath,
|
|
165
|
+
/**
|
|
166
|
+
* Path to write ephemeral files that need to be generated for the server to
|
|
167
|
+
* operate.
|
|
168
|
+
*/
|
|
169
|
+
servePath,
|
|
170
|
+
/**
|
|
171
|
+
* Custom logging function to be used for user-facing messages. Defaults to
|
|
172
|
+
* `console.log`.
|
|
173
|
+
*/
|
|
174
|
+
userLogger,
|
|
175
|
+
/**
|
|
176
|
+
* Custom logging function to be used for system-level messages.
|
|
177
|
+
*/
|
|
178
|
+
systemLogger, }) => {
|
|
96
179
|
const logger = getLogger(systemLogger, userLogger, debug);
|
|
97
180
|
const deno = new DenoBridge({
|
|
98
181
|
debug,
|
|
@@ -137,6 +220,7 @@ export const serve = async ({ basePath, bootstrapURL, certificatePath, debug, di
|
|
|
137
220
|
importMap,
|
|
138
221
|
logger,
|
|
139
222
|
port,
|
|
223
|
+
rootPath,
|
|
140
224
|
});
|
|
141
225
|
return server;
|
|
142
226
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import process from 'process';
|
|
3
4
|
import getPort from 'get-port';
|
|
4
5
|
import fetch from 'node-fetch';
|
|
5
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
@@ -47,9 +48,7 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
47
48
|
expect(functionsConfig).toEqual([{ path: '/my-function' }, {}, { path: '/global-netlify' }]);
|
|
48
49
|
expect(npmSpecifiersWithExtraneousFiles).toEqual(['dictionary']);
|
|
49
50
|
for (const key in functions) {
|
|
50
|
-
const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(
|
|
51
|
-
// @ts-expect-error TODO: Module graph is currently not typed
|
|
52
|
-
({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path);
|
|
51
|
+
const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path);
|
|
53
52
|
expect(graphEntry).toBe(true);
|
|
54
53
|
}
|
|
55
54
|
const response1 = await fetch(`http://0.0.0.0:${port}/foo`, {
|
|
@@ -86,3 +85,50 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
86
85
|
const identidadeBarrelFile = await readFile(join(servePath, 'bundled-pt-committee__identidade.js'), 'utf-8');
|
|
87
86
|
expect(identidadeBarrelFile).toContain(`/// <reference types="${join('..', '..', 'node_modules', '@types', 'pt-committee__identidade', 'index.d.ts')}" />`);
|
|
88
87
|
});
|
|
88
|
+
test('Serves edge functions in a monorepo setup', async () => {
|
|
89
|
+
const rootPath = join(fixturesDir, 'monorepo_npm_module');
|
|
90
|
+
const basePath = join(rootPath, 'packages', 'frontend');
|
|
91
|
+
const paths = {
|
|
92
|
+
user: join(basePath, 'functions'),
|
|
93
|
+
};
|
|
94
|
+
const port = await getPort();
|
|
95
|
+
const importMapPaths = [join(basePath, 'import_map.json')];
|
|
96
|
+
const servePath = join(basePath, '.netlify', 'edge-functions-serve');
|
|
97
|
+
const server = await serve({
|
|
98
|
+
basePath,
|
|
99
|
+
bootstrapURL: 'https://edge.netlify.com/bootstrap/index-combined.ts',
|
|
100
|
+
importMapPaths,
|
|
101
|
+
port,
|
|
102
|
+
rootPath,
|
|
103
|
+
servePath,
|
|
104
|
+
});
|
|
105
|
+
const functions = [
|
|
106
|
+
{
|
|
107
|
+
name: 'func1',
|
|
108
|
+
path: join(paths.user, 'func1.ts'),
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
const options = {
|
|
112
|
+
getFunctionsConfig: true,
|
|
113
|
+
};
|
|
114
|
+
const { features, functionsConfig, graph, success, npmSpecifiersWithExtraneousFiles } = await server(functions, {
|
|
115
|
+
very_secret_secret: 'i love netlify',
|
|
116
|
+
}, options);
|
|
117
|
+
expect(features).toEqual({ npmModules: true });
|
|
118
|
+
expect(success).toBe(true);
|
|
119
|
+
expect(functionsConfig).toEqual([{ path: '/func1' }]);
|
|
120
|
+
expect(npmSpecifiersWithExtraneousFiles).toEqual(['child-1']);
|
|
121
|
+
for (const key in functions) {
|
|
122
|
+
const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(({ kind, mediaType, local }) => kind === 'esm' && mediaType === 'TypeScript' && local === functions[key].path);
|
|
123
|
+
expect(graphEntry).toBe(true);
|
|
124
|
+
}
|
|
125
|
+
const response1 = await fetch(`http://0.0.0.0:${port}/func1`, {
|
|
126
|
+
headers: {
|
|
127
|
+
'x-nf-edge-functions': 'func1',
|
|
128
|
+
'x-ef-passthrough': 'passthrough',
|
|
129
|
+
'X-NF-Request-ID': uuidv4(),
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
expect(response1.status).toBe(200);
|
|
133
|
+
expect(await response1.text()).toBe(`<parent-1><child-1>JavaScript</child-1></parent-1>, <parent-2><child-2><grandchild-1>APIs<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-2>, <parent-3><child-2><grandchild-1>Markup<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-3>`);
|
|
134
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* Returns all the directories obtained by traversing `inner` and its parents
|
|
4
|
+
* all the way to `outer`, inclusive.
|
|
5
|
+
*/
|
|
6
|
+
export const pathsBetween = (inner, outer, paths = []) => {
|
|
7
|
+
const parent = path.dirname(inner);
|
|
8
|
+
if (inner === outer || inner === parent) {
|
|
9
|
+
return [...paths, outer];
|
|
10
|
+
}
|
|
11
|
+
return [inner, ...pathsBetween(parent, outer)];
|
|
12
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"test:dev:deno": "deno test --allow-all deno",
|
|
35
35
|
"test:ci:vitest": "vitest run --coverage",
|
|
36
36
|
"test:ci:deno": "deno test --allow-all deno",
|
|
37
|
-
"test:integration": "node --experimental-modules test/integration/test.js"
|
|
37
|
+
"test:integration": "node --experimental-modules test/integration/test.js",
|
|
38
|
+
"vendor": "deno vendor --force --output deno/vendor https://deno.land/x/deno_graph@0.59.2/types.d.ts https://deno.land/x/eszip@v0.55.2/mod.ts https://deno.land/x/retry@v2.0.0/mod.ts https://deno.land/x/std@0.177.0/path/mod.ts"
|
|
38
39
|
},
|
|
39
40
|
"config": {
|
|
40
41
|
"eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{node,scripts,.github}/**/*.{js,ts,md,html}\" \"*.{js,ts,md,html}\"",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"better-ajv-errors": "^1.2.0",
|
|
81
82
|
"common-path-prefix": "^3.0.0",
|
|
82
83
|
"env-paths": "^3.0.0",
|
|
83
|
-
"esbuild": "0.19.
|
|
84
|
+
"esbuild": "0.19.5",
|
|
84
85
|
"execa": "^6.0.0",
|
|
85
86
|
"find-up": "^6.3.0",
|
|
86
87
|
"get-package-name": "^2.2.0",
|