edge-functions 2.5.0 → 2.6.0-stage.2

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,17 @@
1
+ ## [2.6.0-stage.2](https://github.com/aziontech/vulcan/compare/v2.6.0-stage.1...v2.6.0-stage.2) (2024-03-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * nextjs config file read ([#261](https://github.com/aziontech/vulcan/issues/261)) ([39dbe3e](https://github.com/aziontech/vulcan/commit/39dbe3e840aba42802c442b240ad35551261d2db))
7
+
8
+ ## [2.6.0-stage.1](https://github.com/aziontech/vulcan/compare/v2.5.0...v2.6.0-stage.1) (2024-03-05)
9
+
10
+
11
+ ### Features
12
+
13
+ * add support to network list api ([#260](https://github.com/aziontech/vulcan/issues/260)) ([79c20be](https://github.com/aziontech/vulcan/commit/79c20be283c32829a9eb2a1db2b0996bb3246d83))
14
+
1
15
  ## [2.5.0](https://github.com/aziontech/vulcan/compare/v2.4.0...v2.5.0) (2024-02-28)
2
16
 
3
17
 
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
 
@@ -1,4 +1,7 @@
1
+ import { readdir, stat, mkdir, rm, copyFile } from 'fs/promises';
2
+ import { join, extname, basename, dirname } from 'path';
1
3
  import { gte, coerce, valid } from 'semver';
4
+
2
5
  import {
3
6
  exec,
4
7
  feedback,
@@ -7,9 +10,7 @@ import {
7
10
  copyDirectory,
8
11
  Manifest,
9
12
  } from '#utils';
10
-
11
- import { readdir, stat, mkdir, readFile, rm, copyFile } from 'fs/promises';
12
- import { join, extname, basename, dirname, resolve } from 'path';
13
+ import { getNextConfig, readManifestFile } from '../utils.next.js';
13
14
 
14
15
  const packageManager = await getPackageManager();
15
16
 
@@ -84,18 +85,6 @@ async function moveFiles(directory) {
84
85
  );
85
86
  }
86
87
 
87
- /**
88
- * Get a project manifest generated after build
89
- * @param {string} file - file name
90
- * @returns {object} - the manifest
91
- */
92
- async function readManifestFile(file) {
93
- const manifestPath = resolve(process.cwd(), '.next', file);
94
- const manifest = await readFile(manifestPath, 'utf-8');
95
-
96
- return JSON.parse(manifest);
97
- }
98
-
99
88
  /**
100
89
  * Fix Nextjs routing problems using a default storage
101
90
  */
@@ -151,6 +140,7 @@ async function fixAppDirRoutes() {
151
140
  * In this version the static site had significant changes in how to use and how to build.
152
141
  * doc reference:
153
142
  * https://nextjs.org/docs/pages/building-your-application/deploying/static-exports
143
+ * https://nextjs.org/docs/app/building-your-application/deploying/static-exports
154
144
  * @param {string} version - Next.js version ("latest" or in "x.x.x" format).
155
145
  * @returns {boolean} - a boolean indicating if is a recent version or not.
156
146
  * @example
@@ -179,30 +169,15 @@ function isANewerVersion(version) {
179
169
  return false;
180
170
  }
181
171
 
182
- /**
183
- * Get next config file
184
- * @returns {object} - next config as a JSON
185
- */
186
- async function getNextConfig() {
187
- try {
188
- const configPath = join(process.cwd(), 'next.config.js');
189
- const configModule = await import(configPath);
190
-
191
- return configModule.default;
192
- } catch (error) {
193
- throw Error('Error reading next config file:', error);
194
- }
195
- }
196
-
197
172
  /**
198
173
  * Validates if static site mode is enabled in next config.
199
174
  * @param {object} nextConfig - the config as JSON object.
200
175
  */
201
176
  function validateStaticSiteMode(nextConfig) {
202
- if (!nextConfig.output || nextConfig.output !== 'export') {
177
+ if (!nextConfig || !nextConfig.output || nextConfig.output !== 'export') {
203
178
  const errorMessage = `Static site mode not enabled in project config.
204
- You must add 'output: "export"' in your 'next.config.js' file if you are trying to build a Next.js v >= 13.3 static project.
205
- For more details go to https://nextjs.org/docs/pages/building-your-application/deploying/static-exports \n`;
179
+ You must add 'output: "export"' in your 'next.config.js' or 'next.config.mjs' file if you are trying to build a Next.js v >= 13.3 static project.
180
+ For more details go to https://nextjs.org/docs/pages/building-your-application/deploying/static-exports or https://nextjs.org/docs/app/building-your-application/deploying/static-exports \n`;
206
181
  throw Error(errorMessage);
207
182
  }
208
183
  }
@@ -218,6 +193,7 @@ async function prebuild() {
218
193
 
219
194
  const staticsOutputDir = '.edge/storage';
220
195
 
196
+ // new build format
221
197
  if (isANewerVersion(nextVersion)) {
222
198
  const nextConfig = await getNextConfig();
223
199
 
@@ -225,14 +201,9 @@ async function prebuild() {
225
201
 
226
202
  // check if an output path is specified in config file
227
203
  let outDir = 'out';
228
- const configFileContent = await readFile('./next.config.js', 'utf-8');
229
- const attributeMatch = Array.from(
230
- configFileContent.matchAll(/distDir:(.*),/g),
231
- (match) => match,
232
- )[0];
233
- if (attributeMatch) {
234
- // get the specified value in config
235
- outDir = attributeMatch[1].trim().replace(/["']/g, '');
204
+
205
+ if (nextConfig.distDir) {
206
+ outDir = nextConfig.distDir;
236
207
  }
237
208
 
238
209
  await exec(`${packageManager} run build`, `Next ${nextVersion}`, true);
@@ -245,6 +216,8 @@ async function prebuild() {
245
216
  await moveFiles(`${process.cwd()}/${staticsOutputDir}`);
246
217
  await fixAppDirRoutes();
247
218
  } else {
219
+ // old build format
220
+
248
221
  await exec(`${packageManager} run build`, `Next ${nextVersion}`, true);
249
222
 
250
223
  await exec(
@@ -0,0 +1,66 @@
1
+ import { readFile, access, constants } from 'fs/promises';
2
+ import { join, resolve } from 'path';
3
+
4
+ import { feedback } from '#utils';
5
+
6
+ /**
7
+ * Get a project manifest generated after build
8
+ * @param {string} file - file name
9
+ * @returns {object} - the manifest
10
+ */
11
+ async function readManifestFile(file) {
12
+ const manifestPath = resolve(process.cwd(), '.next', file);
13
+ const manifest = await readFile(manifestPath, 'utf-8');
14
+
15
+ return JSON.parse(manifest);
16
+ }
17
+
18
+ /**
19
+ * Check if a file exists
20
+ * @param {string} path - file path
21
+ * @returns {boolean} indicates if exists or not
22
+ */
23
+ async function fileExists(path) {
24
+ try {
25
+ await access(path, constants.F_OK);
26
+ } catch (err) {
27
+ // file does not exists
28
+ if (err.code === 'ENOENT') {
29
+ return false;
30
+ }
31
+ // other error cases
32
+ feedback.prebuild.error('Error reading file:', err);
33
+ throw err;
34
+ }
35
+
36
+ return true;
37
+ }
38
+
39
+ /**
40
+ * Get next config file
41
+ * @returns {object} - next config as a JSON
42
+ */
43
+ async function getNextConfig() {
44
+ try {
45
+ let configFile = null;
46
+ const jsConfigExists = await fileExists('next.config.js');
47
+ const mjsConfigExists = await fileExists('next.config.mjs');
48
+ if (jsConfigExists) {
49
+ configFile = 'next.config.js';
50
+ } else if (mjsConfigExists) {
51
+ configFile = 'next.config.mjs';
52
+ } else {
53
+ feedback.prebuild.info('Nextjs config file does not exists!');
54
+ return null;
55
+ }
56
+
57
+ const configPath = join(process.cwd(), configFile);
58
+ const configModule = await import(configPath);
59
+
60
+ return configModule.default;
61
+ } catch (error) {
62
+ throw Error('Error reading next config file:', error);
63
+ }
64
+ }
65
+
66
+ export { readManifestFile, getNextConfig };
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.2",
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",