edge-functions 2.5.0 → 2.6.0-stage.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.6.0-stage.1](https://github.com/aziontech/vulcan/compare/v2.5.0...v2.6.0-stage.1) (2024-03-05)
2
+
3
+
4
+ ### Features
5
+
6
+ * add support to network list api ([#260](https://github.com/aziontech/vulcan/issues/260)) ([79c20be](https://github.com/aziontech/vulcan/commit/79c20be283c32829a9eb2a1db2b0996bb3246d83))
7
+
1
8
  ## [2.5.0](https://github.com/aziontech/vulcan/compare/v2.4.0...v2.5.0) (2024-02-28)
2
9
 
3
10
 
package/README.md CHANGED
@@ -91,7 +91,7 @@ Here's a detailed breakdown of the configuration properties available in `vulcan
91
91
 
92
92
  **Type:** String
93
93
 
94
- **Description:**
94
+ **Description:**
95
95
  This represents the primary entry point for your application, where the building process begins.
96
96
 
97
97
  **Note:** `Entry` will be ignored for jamstack solutions.
@@ -100,28 +100,28 @@ This represents the primary entry point for your application, where the building
100
100
 
101
101
  **Type:** String ('esbuild' or 'webpack')
102
102
 
103
- **Description:**
103
+ **Description:**
104
104
  Defines which build tool to use. The available options are `esbuild` and `webpack`.
105
105
 
106
106
  ### UseNodePolyfills
107
107
 
108
108
  **Type:** Boolean
109
109
 
110
- **Description:**
110
+ **Description:**
111
111
  Determines whether Node.js polyfills should be applied. This is useful for projects that leverage specific Node.js functionality but target environments without these built-in features. The use of useNodePolyfills is ignored when used in mode `deliver` presets, as Node.js features must be resolved at build time by the framework process itself.
112
112
 
113
113
  ### UseOwnWorker
114
114
 
115
115
  **Type:** Boolean
116
116
 
117
- **Description:**
117
+ **Description:**
118
118
  This flag indicates that the constructed code inserts its own worker expression, such as `addEventListener("fetch")` or similar, without the need to inject a provider.
119
119
 
120
120
  ### Preset
121
121
 
122
122
  **Type:** Object
123
123
 
124
- **Description:**
124
+ **Description:**
125
125
  Provides preset-specific configurations.
126
126
 
127
127
  - **Name (Type: String):** Refers to the preset name, e.g., "vue" or "next".
@@ -131,7 +131,7 @@ Provides preset-specific configurations.
131
131
 
132
132
  **Type:** Object
133
133
 
134
- **Description:**
134
+ **Description:**
135
135
  Configurations related to the in-memory filesystem.
136
136
 
137
137
  - **InjectionDirs (Type: Array of Strings):** Directories to be injected into memory for runtime access via the fs API.
@@ -142,7 +142,7 @@ Configurations related to the in-memory filesystem.
142
142
 
143
143
  **Type:** Object
144
144
 
145
- **Description:**
145
+ **Description:**
146
146
  Allows you to extend the capabilities of the chosen bundler (either `webpack` or `esbuild`) with custom plugins or configurations.
147
147
 
148
148
  - **Plugins (Type: Object):** Add your custom plugins for your chosen bundler here.
@@ -177,8 +177,12 @@ module.exports = {
177
177
 
178
178
  - [Overview](docs/overview.md)
179
179
  - [Presets](docs/presets.md)
180
+ - [Nextjs](docs/nextjs.md)
180
181
  - [Rust/Wasm example](examples/rust-wasm-yew-ssr/)
181
182
  - [Emscripten/Wasm example](examples/emscripten-wasm/)
183
+ - [Env vars example](examples/simple-js-env-vars)
184
+ - [Storage example](examples/simple-js-esm-storage)
185
+ - [Firewall example](examples/simple-js-firewall-event)
182
186
 
183
187
  ## Wasm Notes
184
188
 
package/docs/nextjs.md ADDED
@@ -0,0 +1,66 @@
1
+ # Nextjs Support
2
+
3
+ Vulcan supports Nextjs in compute and deliver modes.
4
+
5
+ ## Deliver
6
+
7
+ Static site delivered by edge without a function.
8
+ Check static examples in [Nextjs examples dir](/examples//next/) for more details.
9
+
10
+ ### References
11
+
12
+ - [Pages Router - Static Export](https://nextjs.org/docs/pages/building-your-application/deploying/static-exports)
13
+ - [App Router - Static Export](https://nextjs.org/docs/app/building-your-application/deploying/static-exports)
14
+
15
+ ## Compute
16
+
17
+ In compute mode the nextjs handler uses a routing system (based on vercel multiple steps routing) to handle the request.
18
+
19
+ After a route match in the routing system takes one of these actions:
20
+
21
+ - deliver a static;
22
+ - make a request override;
23
+ - call a builded edge module;
24
+ - call a node custom server;
25
+
26
+ This solution was created based on fastly ([next-compute-js v1](https://github.com/fastly/next-compute-js)) and cloudflare ([next-on-pages](https://github.com/cloudflare/next-on-pages)) Nextjs solutions.
27
+
28
+ Check edge or node examples in [Nextjs examples dir](/examples//next/) for more details.
29
+
30
+ ### Supported Features
31
+
32
+ | Runtime | Versions | Format/Router | Feature |
33
+ | ------- | ---------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
34
+ | Edge | 12.2.x, 12.3.x | Pages Router | Static Pages |
35
+ | | | | SSR |
36
+ | | | | SSG |
37
+ | | | | Edge API Routes |
38
+ | | | | Dynamic Routes |
39
+ | | | | Middleware (rewrite, redirect, continue to response, set request header, throw error, set response header, set response cookie) |
40
+ | | | | Next configs (rewrite before files, rewrite after files, rewrite fallback, redirects, header definition) |
41
+ | | | | i18n routing |
42
+ | Edge | 13.0.x, 13.1.x, 13.2.x, 13.3.x, 13.4.x, 13.5.x | Pages Router | Static Pages |
43
+ | | | | SSR |
44
+ | | | | SSG |
45
+ | | | | Edge API Routes |
46
+ | | | | Dynamic Routes |
47
+ | | | | Middleware (rewrite, redirect, continue to response, set request header, throw error, return response, set response header, set response cookie) |
48
+ | | | | Next configs (rewrite before files, rewrite after files, rewrite fallback, redirects, header definition) |
49
+ | | | | i18n routing |
50
+ | | | | Custom Errors |
51
+ | Edge | 13.0.x, 13.1.x, 13.2.x, 13.3.x, 13.4.x, 13.5.x | App Router | App router (basic structure, routing, layouts) |
52
+ | | | | Server Components |
53
+ | | | | Route Handlers |
54
+ | | | | Dynamic Routes |
55
+ | | | | Middleware (rewrite, redirect, continue to response, set request header, throw error, return response, set response header, set response cookie) |
56
+ | | | | Next configs (rewrite before files, rewrite after files, redirects, header definition) |
57
+ | | | | Internationalization |
58
+ | | | | Custom Errors (error.js and not-found.js) |
59
+ | Node | 12.3.x | Pages Router | Static Pages |
60
+ | | | | SSR |
61
+ | | | | SSG |
62
+ | | | | API Routes |
63
+ | | | | Dynamic Routes |
64
+ | | | | Next configs (rewrite before files, rewrite after files, rewrite fallback, redirects, header definition) |
65
+ | | | | i18n routing |
66
+ | | | | Custom Errors |
package/docs/overview.md CHANGED
@@ -8,7 +8,8 @@ flowchart LR
8
8
  A[Trigger] -->|args| B(Dispatcher)
9
9
  B --> C(Prebuild)
10
10
  C -->|args| D(Common Build)
11
- D --> E[Artifacts]
11
+ D --> E(Postbuild)
12
+ E --> F[Artifacts]
12
13
  ```
13
14
 
14
15
  ### Trigger
@@ -35,6 +36,10 @@ Polyfills can be used to generate the worker(s) file(s).
35
36
 
36
37
  Some configs can be passed to the builder but if user tries to override `azion worker configs` this passed configs will be ignored.
37
38
 
39
+ ### Post Build
40
+
41
+ Optional step to run post build actions after common build (bundlers action).
42
+
38
43
  ### Artifacts
39
44
 
40
45
  The **'.edge'** folder will be generated representing the edge locally. Files generated to run on the infrastructure:
@@ -42,3 +47,5 @@ The **'.edge'** folder will be generated representing the edge locally. Files ge
42
47
  - JS worker(s) => '.edge/workers.js';
43
48
  - Assets => '.edge/storage/\*';
44
49
  - Environment variables => '.edge/.env'.
50
+
51
+ The **'.vulcan'** file also will be generated. This file contains build infos that can be used by local env or other tools.
package/docs/presets.md CHANGED
@@ -37,6 +37,8 @@ Each preset is made up of three primary files: `config.js`, `prebuild.js`, and `
37
37
  - mountSSG: This function takes the request and sets up routes according to the SSG structure.
38
38
  - ErrorHTML: This edgehook provides a return of an HTML template showing the error and the description passed as a parameter. You can pass the captured error as the third parameter, and it will be displayed on the screen (it's a good way to debug).
39
39
 
40
+ 4. `postbuild.js`: this file is optional. Here you can run actions after the common build done by bundlers.
41
+
40
42
  # How to add a new preset
41
43
 
42
44
  Here's a step-by-step guide on how to add a new preset in Vulcan:
@@ -161,9 +161,13 @@ class PolyfillsManager {
161
161
 
162
162
  // globalThis.Azion
163
163
  this.setExternal(
164
- 'Azion',
164
+ 'Azion.env',
165
165
  `${externalPolyfillsPath}/azion/env-vars/env-vars.polyfills.js`,
166
166
  );
167
+ this.setExternal(
168
+ 'Azion.networkList',
169
+ `${externalPolyfillsPath}/azion/network-list/network-list.polyfills.js`,
170
+ );
167
171
 
168
172
  return {
169
173
  libs: this.libs,
@@ -0,0 +1,3 @@
1
+ import NetworkListContext from './network-list.context.js';
2
+
3
+ export default NetworkListContext;
@@ -0,0 +1,232 @@
1
+ import ipLib from 'ip';
2
+ import nodePath from 'node:path';
3
+ import { readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const require = createRequire(import.meta.url);
8
+
9
+ /**
10
+ * This class is a VM context (NETWORK_LIST_CONTEXT) to handle with network list
11
+ * @class NetworkListContext
12
+ * @description Class to manage the network list
13
+ */
14
+ class NetworkListContext {
15
+ /**
16
+ * Cache Dynamic Import flag - If true, the file will be reloaded every time default: true
17
+ */
18
+ #cacheDynamicImport;
19
+
20
+ #networkList = [];
21
+
22
+ #workDir = '.edge';
23
+
24
+ #configFile = 'azion.config.js';
25
+
26
+ /**
27
+ * Creates an instance of NetworkListContext.
28
+ * @param {boolean} [cacheDynamicImport=true] - Cache Dynamic Import flag - If false, the file will be reloaded every time default: true
29
+ */
30
+ constructor(cacheDynamicImport = true) {
31
+ this.#cacheDynamicImport = cacheDynamicImport;
32
+ this.#init();
33
+ }
34
+
35
+ /**
36
+ * Check if the network list contains the value
37
+ * @param {string} networkListId - The network list id
38
+ * @param {string} value - The value to check
39
+ * @returns {boolean} - Return true if the network list contains the value
40
+ * @memberof NetworkListContext
41
+ */
42
+ contains(networkListId, value) {
43
+ const network = this.#networkList.find(
44
+ (networkItem) =>
45
+ parseInt(networkItem.id, 10) === parseInt(networkListId, 10),
46
+ );
47
+ return this.#containsType(network, value);
48
+ }
49
+
50
+ #containsType(network, value) {
51
+ switch (network?.listType) {
52
+ case 'ip_cidr':
53
+ return this.#networkCIDR(value, network);
54
+ case 'asn':
55
+ return this.#networkAsn(value, network);
56
+ case 'countries':
57
+ return this.#networkCountries(value, network);
58
+ default:
59
+ return false;
60
+ }
61
+ }
62
+
63
+ // eslint-disable-next-line class-methods-use-this
64
+ #networkCIDR(ipAddress, network) {
65
+ const listContent = network?.listContent;
66
+ if (!listContent || listContent.length === 0) return false;
67
+ return listContent.some((currentIp) => {
68
+ if (currentIp.includes('/')) {
69
+ return ipLib.cidrSubnet(currentIp).contains(ipAddress);
70
+ }
71
+ return currentIp === ipAddress;
72
+ });
73
+ }
74
+
75
+ // eslint-disable-next-line class-methods-use-this
76
+ #networkAsn(asn, network) {
77
+ const listContent = network?.listContent;
78
+ if (!listContent || listContent.length === 0) return false;
79
+ return listContent.some((currentAsn) => {
80
+ return parseInt(currentAsn, 10) === parseInt(asn, 10);
81
+ });
82
+ }
83
+
84
+ // eslint-disable-next-line class-methods-use-this
85
+ #networkCountries(country, network) {
86
+ const listContent = network?.listContent;
87
+ if (!listContent || listContent.length === 0) return false;
88
+ return listContent.some((currentCountry) => {
89
+ return currentCountry === country;
90
+ });
91
+ }
92
+
93
+ // eslint-disable-next-line class-methods-use-this
94
+ async #init() {
95
+ try {
96
+ const config = await this.#loadConfigFile();
97
+ this.#networkList = config.networkList;
98
+ } catch (error) {
99
+ this.#networkList = [];
100
+ }
101
+ }
102
+
103
+ // eslint-disable-next-line class-methods-use-this
104
+ async #loadConfigFile() {
105
+ const { configFilePath, rootPath } = this.#getConfigFilePath();
106
+
107
+ const {
108
+ type: typeImport,
109
+ changed,
110
+ currentConfigPath,
111
+ matchPaths,
112
+ } = this.#checkFileImportType(configFilePath);
113
+
114
+ let config;
115
+ if (typeImport === 'esm') {
116
+ config = await this.#importEsmModule(
117
+ rootPath,
118
+ currentConfigPath,
119
+ changed,
120
+ );
121
+ } else {
122
+ config = await this.#importCjsModule(
123
+ rootPath,
124
+ currentConfigPath,
125
+ changed,
126
+ matchPaths,
127
+ );
128
+ }
129
+
130
+ return config?.default || config;
131
+ }
132
+
133
+ // eslint-disable-next-line class-methods-use-this
134
+ #getConfigFilePath() {
135
+ const projectRoot = process.cwd();
136
+ const isWindows = process.platform === 'win32';
137
+ const rootPath = isWindows
138
+ ? fileURLToPath(new URL(`file:///${nodePath.resolve(projectRoot, '.')}`))
139
+ : nodePath.resolve(projectRoot, '.');
140
+ return {
141
+ configFilePath: nodePath.resolve(rootPath, this.#configFile),
142
+ rootPath,
143
+ };
144
+ }
145
+
146
+ // eslint-disable-next-line class-methods-use-this
147
+ async #importEsmModule(rootPath, originalConfigPath, changed) {
148
+ let pathCache = originalConfigPath;
149
+ if (!this.#cacheDynamicImport) {
150
+ pathCache = `${originalConfigPath}?u=${Date.now()}`;
151
+ }
152
+ const config = (await import(pathCache)).default;
153
+ if (changed) {
154
+ rmSync(originalConfigPath);
155
+ }
156
+ return config;
157
+ }
158
+
159
+ // eslint-disable-next-line class-methods-use-this
160
+ async #importCjsModule(rootPath, configFilePath, changed, matchPaths) {
161
+ if (!this.#cacheDynamicImport) {
162
+ delete require.cache[configFilePath];
163
+ if (changed && matchPaths?.length > 0) {
164
+ matchPaths.forEach((match) => {
165
+ delete require.cache[nodePath.resolve(rootPath, match)];
166
+ });
167
+ }
168
+ }
169
+ return new Promise((resolve) => {
170
+ // eslint-disable-next-line import/no-dynamic-require
171
+ resolve(require(configFilePath));
172
+ });
173
+ }
174
+
175
+ // eslint-disable-next-line class-methods-use-this
176
+ #checkFileImportType(originalConfigPath) {
177
+ const file = readFileSync(originalConfigPath, 'utf8');
178
+ if (file?.includes('export default')) {
179
+ const { changed, currentConfigPath } = this.#changeEsmImports(
180
+ originalConfigPath,
181
+ file,
182
+ );
183
+ return { type: 'esm', changed, currentConfigPath };
184
+ }
185
+ const { changed, matchPaths } = this.#changeCjsImports(file);
186
+ return {
187
+ type: 'cjs',
188
+ changed,
189
+ currentConfigPath: originalConfigPath,
190
+ matchPaths,
191
+ };
192
+ }
193
+
194
+ // eslint-disable-next-line class-methods-use-this
195
+ #changeEsmImports(originalConfigPath, file) {
196
+ const regex = /import\s+(.*)\s+from\s+['"]\.(.*)['"]/g;
197
+ let changed = false;
198
+ let fileUpdated = file;
199
+ if (file.match(regex)) {
200
+ changed = true;
201
+ fileUpdated = file.replace(
202
+ regex,
203
+ `import $1 from "..$2?u=${Date.now()}"`,
204
+ );
205
+ const tmpFile = this.#configFile.replace('.js', '.temp.js');
206
+ const tmpConfigPath = nodePath.join(
207
+ process.cwd(),
208
+ this.#workDir,
209
+ tmpFile,
210
+ );
211
+ writeFileSync(tmpConfigPath, fileUpdated, 'utf8');
212
+ return { changed, currentConfigPath: tmpConfigPath };
213
+ }
214
+ return { changed, currentConfigPath: originalConfigPath };
215
+ }
216
+
217
+ // eslint-disable-next-line class-methods-use-this
218
+ #changeCjsImports(file) {
219
+ let changed = false;
220
+ const regex = /require\(['"]([^'"]+)['"]\)/g;
221
+ const matchPaths = [];
222
+ let match = regex.exec(file);
223
+ while (match !== null) {
224
+ changed = true;
225
+ matchPaths.push(match[1]);
226
+ match = regex.exec(file);
227
+ }
228
+ return { changed, matchPaths };
229
+ }
230
+ }
231
+
232
+ export default NetworkListContext;
@@ -0,0 +1,44 @@
1
+ import { it } from '@jest/globals';
2
+ import mockFs from 'mock-fs';
3
+ import NetworkListContext from './network-list.context.js';
4
+
5
+ describe('Network List Context', () => {
6
+ let networkListContext;
7
+
8
+ beforeEach(async () => {
9
+ // eslint-disable-next-line jest/no-standalone-expect
10
+ const typeImport = expect.getState().currentTestName.includes('commonjs')
11
+ ? 'module.exports ='
12
+ : 'export default';
13
+ const code = `${typeImport} {
14
+ networkList: [
15
+ { id: 1, listType: "ip_cidr", listContent: ["10.0.0.1"] },
16
+ { id: 2, listType: "asn", listContent: [123, 456, 789]},
17
+ { id: 3, listType: "countries", listContent: ["United States", "Brazil"]}
18
+ ]};`;
19
+ mockFs({
20
+ 'azion.config.js': code,
21
+ });
22
+ networkListContext = new NetworkListContext();
23
+ });
24
+
25
+ afterEach(() => {
26
+ mockFs.restore();
27
+ });
28
+
29
+ it('should contain the valid ip in the network list and list type is ip_cidr', async () => {
30
+ expect(networkListContext.contains(1, '10.0.0.1')).toBe(true);
31
+ });
32
+
33
+ it('should contain the valid asn in the network list and list type is asn', async () => {
34
+ expect(networkListContext.contains(2, 123)).toBe(true);
35
+ });
36
+
37
+ it('should contain the valid country in the network list and list type is countries', async () => {
38
+ expect(networkListContext.contains(3, 'United States')).toBe(true);
39
+ });
40
+
41
+ it('should type import be commonjs and contain the valid ip in the network list and list type is ip_cidr', async () => {
42
+ expect(networkListContext.contains(1, '10.0.0.1')).toBe(true);
43
+ });
44
+ });
@@ -0,0 +1,3 @@
1
+ import NetworkListContext from './context/index.js';
2
+
3
+ export default NetworkListContext;
@@ -0,0 +1,16 @@
1
+ /* eslint-disable */
2
+ globalThis.Azion = globalThis.Azion || {};
3
+
4
+ globalThis.Azion.networkList = {};
5
+ // unique context for each instance
6
+ const instanceNetworkList = new NETWORK_LIST_CONTEXT(false);
7
+
8
+ /**
9
+ *
10
+ * @param {string} network_list_id - The network list id
11
+ * @param {string} value - The value to check
12
+ * @returns
13
+ */
14
+ globalThis.Azion.networkList.contains = (network_list_id, value) => {
15
+ return instanceNetworkList.contains(network_list_id, value);
16
+ };
@@ -3,6 +3,7 @@ import FetchEventContext from './azion/fetch-event/index.js';
3
3
  import { AsyncHooksContext } from './async-hooks/index.js';
4
4
  import { StorageContext } from './azion/storage/index.js';
5
5
  import EnvVarsContext from './azion/env-vars/index.js';
6
+ import NetworkListContext from './azion/network-list/index.js';
6
7
 
7
8
  export {
8
9
  fetchContext,
@@ -10,4 +11,5 @@ export {
10
11
  AsyncHooksContext,
11
12
  StorageContext,
12
13
  EnvVarsContext,
14
+ NetworkListContext,
13
15
  };
@@ -6,6 +6,7 @@ import {
6
6
  AsyncHooksContext,
7
7
  StorageContext,
8
8
  EnvVarsContext,
9
+ NetworkListContext,
9
10
  } from './polyfills/index.js';
10
11
  import FirewallEventContext from './polyfills/azion/firewall-event/index.js';
11
12
 
@@ -77,6 +78,9 @@ function runtime(code, isFirewallEvent = false) {
77
78
  // EnvVars Context
78
79
  context.ENV_VARS_CONTEXT = EnvVarsContext;
79
80
 
81
+ // Network List Context
82
+ context.NETWORK_LIST_CONTEXT = NetworkListContext;
83
+
80
84
  return context;
81
85
  };
82
86
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "edge-functions",
3
3
  "type": "module",
4
- "version": "2.5.0",
4
+ "version": "2.6.0-stage.1",
5
5
  "description": "Tool to launch and build JavaScript/Frameworks. This tool automates polyfills for Edge Computing and assists in creating Workers, notably for the Azion platform.",
6
6
  "main": "lib/main.js",
7
7
  "bin": {
@@ -68,6 +68,7 @@
68
68
  "https-browserify": "^1.0.0",
69
69
  "inquirer": "^9.2.7",
70
70
  "install": "^0.13.0",
71
+ "ip": "^2.0.1",
71
72
  "lodash": "^4.17.21",
72
73
  "lodash.merge": "^4.6.2",
73
74
  "log-update": "^5.0.1",