edge-functions 1.1.0 → 1.3.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.
Files changed (35) hide show
  1. package/.eslintrc.json +1 -1
  2. package/README.md +36 -0
  3. package/aliases.js +1 -1
  4. package/docs/overview.md +4 -3
  5. package/docs/presets.md +16 -4
  6. package/jsconfig.json +1 -1
  7. package/lib/build/dispatcher/dispatcher.js +62 -66
  8. package/lib/constants/framework-initializer.constants.js +51 -0
  9. package/lib/constants/index.js +4 -1
  10. package/lib/constants/messages/build.messages.js +2 -0
  11. package/lib/constants/messages/global.messages.js +4 -1
  12. package/lib/env/polyfills/FetchEvent.polyfills.js +13 -0
  13. package/lib/env/polyfills/fetch.polyfills.js +39 -0
  14. package/lib/env/polyfills/index.js +4 -0
  15. package/lib/env/runtime.env.js +9 -1
  16. package/lib/main.js +234 -94
  17. package/lib/platform/actions/core/propagation.actions.js +5 -4
  18. package/lib/presets/custom/angular/deliver/prebuild.js +6 -10
  19. package/lib/presets/custom/astro/deliver/prebuild.js +16 -20
  20. package/lib/presets/custom/hexo/deliver/prebuild.js +16 -20
  21. package/lib/presets/custom/next/deliver/prebuild.js +31 -35
  22. package/lib/presets/custom/react/deliver/prebuild.js +2 -6
  23. package/lib/presets/custom/vue/deliver/prebuild.js +28 -57
  24. package/lib/utils/exec/exec.utils.js +34 -24
  25. package/lib/utils/getAbsoluteLibDirPath/getAbsoluteLibDirPath.utils.js +1 -3
  26. package/lib/utils/getVulcanBuildId/getVulcanBuildId.utils.js +1 -1
  27. package/lib/utils/index.js +2 -2
  28. package/lib/utils/presets/index.js +3 -0
  29. package/lib/utils/presets/presets.utils.js +169 -0
  30. package/lib/utils/spinner/index.js +2 -2
  31. package/lib/utils/spinner/spinner.utils.js +1 -1
  32. package/package.json +10 -3
  33. package/lib/utils/getPresetsList/getPresetsList.utils.js +0 -68
  34. package/lib/utils/getPresetsList/index.js +0 -3
  35. /package/lib/utils/{getPresetsList/getPresetsList.utils.test.js → presets/presets.utils.test.js} +0 -0
package/.eslintrc.json CHANGED
@@ -36,7 +36,7 @@
36
36
  ],
37
37
  [
38
38
  "#polyfills",
39
- "./lib/polyfills/index.js"
39
+ "./lib/env/polyfills/index.js"
40
40
  ],
41
41
  [
42
42
  "#build",
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
 
2
2
  # Vulcan - Forging The Edge
3
+ ![vulcan](https://github.com/aziontech/vulcan/assets/12740219/a5043e6f-11cb-4498-a300-5bdb617a9989)
4
+
3
5
 
4
6
  Vulcan is a powerful tool designed to streamline the development and deployment of JavaScript applications and frameworks. This powerful utility automates polyfills for Edge Computing, significantly simplifying the process of creating Workers, particularly for the Azion platform.
5
7
 
@@ -45,6 +47,40 @@ Follow these steps to start using Vulcan:
45
47
 
46
48
  5. Start developing: Once the project is set up, you can start developing your JavaScript applications or frameworks using the power of Vulcan. Leverage the automated polyfills, Worker creation assistance, and other features provided by Vulcan to enhance your development workflow.
47
49
 
50
+ ## Using Vulcan
51
+
52
+ See some examples below:
53
+
54
+ * Build a JavaScript/Node project (back-end)
55
+
56
+ ```shell
57
+ vulcan build
58
+ ```
59
+
60
+ * Build a TypeScript/Node (back-end)
61
+
62
+ ```shell
63
+ vulcan build --preset typescript
64
+ ```
65
+
66
+ * Build a Static Next.js project
67
+
68
+ ```shell
69
+ vulcan build --preset next --mode deliver
70
+ ```
71
+
72
+ * Build a Static Astro.js project
73
+
74
+ ```shell
75
+ vulcan build --preset astro --mode deliver
76
+ ```
77
+
78
+ * Test your project locally (after build)
79
+
80
+ ```shell
81
+ vulcan dev
82
+ ```
83
+
48
84
 
49
85
  ## Docs
50
86
  * [Overview](docs/overview.md)
package/aliases.js CHANGED
@@ -2,7 +2,7 @@ export default [
2
2
  ['#root/*', './'],
3
3
  ['#lib/*', './lib'],
4
4
  ['#utils', './lib/utils/index.js'],
5
- ['#polyfills', './lib/polyfills/index.js'],
5
+ ['#polyfills', './lib/env/polyfills/index.js'],
6
6
  ['#build', './lib/build/dispatcher/index.js'],
7
7
  ['#bundlers', './lib/build/bundlers/index.js'],
8
8
  ['#notations/*', './lib/notations'],
package/docs/overview.md CHANGED
@@ -31,6 +31,7 @@ Polyfills can be used to generate the worker(s) file(s).
31
31
  Some configs can be passed to the builder but if user tries to override `azion worker configs` this passed configs will be ignored.
32
32
 
33
33
  ### Artifacts
34
- Files generated to run in azion structure:
35
- * js worker(s);
36
- * Storage assets files;
34
+ The **'.edge'** folder will be generated representing the edge locally. Files generated to run on the infrastructure:
35
+ * JS worker(s) => '.edge/workers.js';
36
+ * Assets => '.edge/storage/*';
37
+ * Environment variables => '.edge/.env'.
package/docs/presets.md CHANGED
@@ -9,8 +9,9 @@ Vulcan is an extensible platform that allows you to easily create new presets fo
9
9
  To add a new preset, you need to create appropriate folders in two directories: `presets/default` or `presets/custom`. The folder representing your framework or library will automatically be included in the preset listings. Each preset has two modes, represented by folders of the same name: `compute` and `deliver`.
10
10
 
11
11
 
12
+ https://github.com/aziontech/vulcan/assets/12740219/84c7d7a1-4167-4e7e-993f-41a6eb653758
13
+
12
14
 
13
- https://github.com/aziontech/vulcan/assets/12740219/06edb9a0-26cd-4055-bd2e-d400b6a06f3c
14
15
 
15
16
 
16
17
 
@@ -54,7 +55,18 @@ Each preset is made up of three primary files: `config.js`, `prebuild.js`, and `
54
55
 
55
56
  Here's a step-by-step guide on how to add a new preset in Vulcan:
56
57
 
57
- 1. ## **Create a folder inside `./lib/presets/custom`:**
58
+ ## **Use the command to automatic creation:**
59
+
60
+ vulcan presets create
61
+
62
+ https://github.com/aziontech/vulcan/assets/12740219/9ca7371e-713a-4b29-a99b-c1a18d28bc67
63
+
64
+
65
+
66
+ ### Or do it manually:
67
+
68
+
69
+ ## 1. **Create a folder inside `./lib/presets/custom`:**
58
70
  https://github.com/aziontech/vulcan/assets/12740219/abb1b2cc-5f74-473d-b731-c0b7157cb95e
59
71
 
60
72
  - The name of this folder should represent the name of your framework or library.
@@ -65,7 +77,7 @@ Here's a step-by-step guide on how to add a new preset in Vulcan:
65
77
 
66
78
  - **Deliver**: This mode should be used when you intend to use the worker only for routing requests and delivering static files that will be computed on the client side.
67
79
 
68
- 3. ## **Create the following files in your preset's folder:**
80
+ 2. ## **Create the following files in your preset's folder:**
69
81
 
70
82
  ## handler.js
71
83
  This file contains the code that is executed within the worker in the edge function. Essentially, it is the code that runs directly on the edge. In the context of the `deliver` mode, this may simply act as a router. However, in cases where computation is needed, it can be designed to perform more complex tasks. Remember, the capabilities of your handler.js are dependent on your use case and the mode of operation you've chosen for your preset.
@@ -112,4 +124,4 @@ For `deliver` mode:
112
124
 
113
125
  vulcan build --preset <name> --mode deliver
114
126
 
115
- Replace `<name>` with the name of your preset. This will initiate Vulcan's build process for your preset, allowing you to verify its functionality.
127
+ Replace `<name>` with the name of your preset. This will initiate Vulcan's build process for your preset, allowing you to verify its functionality.
package/jsconfig.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "./lib/utils/index.js"
13
13
  ],
14
14
  "#polyfills": [
15
- "./lib/polyfills/index.js"
15
+ "./lib/env/polyfills/index.js"
16
16
  ],
17
17
  "#build": [
18
18
  "./lib/build/dispatcher/index.js"
@@ -1,10 +1,12 @@
1
1
  import { join, resolve } from 'path';
2
2
  import {
3
- readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync, rmSync,
3
+ readFileSync, existsSync, mkdirSync, writeFileSync, rmSync,
4
4
  } from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { Esbuild, Webpack } from '#bundlers';
7
- import { feedback, generateTimestamp, getAbsoluteLibDirPath } from '#utils';
7
+ import {
8
+ feedback, debug, generateTimestamp, getAbsoluteLibDirPath, presets,
9
+ } from '#utils';
8
10
  import { Messages } from '#constants';
9
11
 
10
12
  const vulcanLibPath = getAbsoluteLibDirPath();
@@ -12,26 +14,10 @@ const vulcanRootPath = resolve(vulcanLibPath, '..');
12
14
  const isWindows = process.platform === 'win32';
13
15
 
14
16
  /**
15
- * Get the valid build presets based on the folders inside the presets/default
16
- * and presets/custom directories.
17
- * @returns {string[]} An array of valid build presets.
17
+ * Get the path corresponding to a specific alias defined in the package.json.
18
+ * @param {string} alias - The desired alias.
19
+ * @returns {string} The path corresponding to the alias.
18
20
  */
19
- function getValidPresets() {
20
- const types = ['default', 'custom'];
21
- const validPresets = [];
22
-
23
- types.forEach((type) => {
24
- const presetsPath = join(vulcanLibPath, 'presets', type);
25
- const directories = readdirSync(presetsPath, { withFileTypes: true })
26
- .filter((dirent) => dirent.isDirectory())
27
- .map((dirent) => dirent.name);
28
-
29
- validPresets.push(...directories);
30
- });
31
-
32
- return validPresets;
33
- }
34
-
35
21
  /**
36
22
  * Get the path corresponding to a specific alias defined in the package.json.
37
23
  * @param {string} alias - The desired alias.
@@ -83,13 +69,13 @@ function fixImportsAndRequestsPlace(entryContent) {
83
69
  * @returns {any} The context that will be used in build.
84
70
  */
85
71
  async function loadBuildContext(preset, entry, mode) {
86
- const VALID_BUILD_PRESETS = getValidPresets().concat(',').slice(0, -1);
72
+ const VALID_BUILD_PRESETS = presets.getKeys();
73
+
87
74
  const validPreset = VALID_BUILD_PRESETS.includes(preset);
88
75
 
89
76
  if (!validPreset) {
90
- throw Error(
91
- `Invalid build preset. Available presets: ${VALID_BUILD_PRESETS}.`,
92
- );
77
+ feedback.build.error(Messages.build.error.invalid_preset);
78
+ process.exit(1);
93
79
  }
94
80
 
95
81
  let configFilePath;
@@ -112,7 +98,7 @@ async function loadBuildContext(preset, entry, mode) {
112
98
  prebuildFilePath = join(modePath, 'prebuild.js');
113
99
  handlerFilePath = join(modePath, 'handler.js');
114
100
  } else {
115
- feedback.build.error(`Mode '${mode}' does not exists in preset '${preset}'. Try 'deliver' or 'compute'.`);
101
+ feedback.build.error(Messages.build.error.invalid_preset_mode(mode, preset));
116
102
  process.exit(1);
117
103
  }
118
104
 
@@ -139,9 +125,15 @@ async function loadBuildContext(preset, entry, mode) {
139
125
  newEntryContent = newEntryContent.replace('#edge', edgehooksPath);
140
126
 
141
127
  if ((preset === 'javascript' || preset === 'typescript') && (mode === 'compute')) {
142
- const filePath = join(process.cwd(), entry);
143
- const entryContent = readFileSync(filePath, 'utf-8');
144
- newEntryContent = newEntryContent.replace('__JS_CODE__', entryContent);
128
+ try {
129
+ const filePath = join(process.cwd(), entry);
130
+ const entryContent = readFileSync(filePath, 'utf-8');
131
+ newEntryContent = newEntryContent.replace('__JS_CODE__', entryContent);
132
+ } catch (error) {
133
+ feedback.build.error(Messages.errors.file_doesnt_exist(entry));
134
+ debug.error(error);
135
+ process.exit(1);
136
+ }
145
137
  }
146
138
 
147
139
  newEntryContent = fixImportsAndRequestsPlace(newEntryContent);
@@ -201,8 +193,6 @@ class Dispatcher {
201
193
  */
202
194
  run = async () => {
203
195
  // Load Context based on preset
204
- feedback.prebuild.info(Messages.build.info.prebuild_starting);
205
-
206
196
  const { entryContent, prebuild, config } = await loadBuildContext(
207
197
  this.preset,
208
198
  this.entry,
@@ -223,42 +213,48 @@ class Dispatcher {
223
213
  };
224
214
 
225
215
  // Run prebuild actions
226
- await prebuild(buildContext); // TODO: send context to prebuild
227
- feedback.prebuild.success(Messages.build.success.prebuild_succeeded);
228
- feedback.prebuild.info(Messages.build.info.vulcan_build_starting);
229
-
230
- // create tmp entrypoint
231
- const currentDir = process.cwd();
232
- let tempEntryFile = `vulcan-${buildId}.temp.`;
233
- tempEntryFile += (this.preset === 'typescript') ? 'ts' : 'js';
234
- const tempBuilderEntryPath = join(currentDir, tempEntryFile);
235
-
236
- writeFileSync(tempBuilderEntryPath, entryContent);
237
-
238
- // builder entry
239
- config.entry = tempBuilderEntryPath;
240
- config.buildId = buildId;
241
- config.useNodePolyfills = this.useNodePolyfills;
242
-
243
- let builder;
244
- switch (config.builder) {
245
- case 'webpack':
246
- builder = new Webpack(config);
247
- break;
248
- case 'esbuild':
249
- builder = new Esbuild(config);
250
- break;
251
- default:
252
- builder = new Webpack(config);
253
- break;
216
+ try {
217
+ feedback.prebuild.info(Messages.build.info.prebuild_starting);
218
+ await prebuild(buildContext);
219
+ feedback.prebuild.success(Messages.build.success.prebuild_succeeded);
220
+
221
+ feedback.build.info(Messages.build.info.vulcan_build_starting);
222
+ // create tmp entrypoint
223
+ const currentDir = process.cwd();
224
+ let tempEntryFile = `vulcan-${buildId}.temp.`;
225
+ tempEntryFile += (this.preset === 'typescript') ? 'ts' : 'js';
226
+ const tempBuilderEntryPath = join(currentDir, tempEntryFile);
227
+
228
+ writeFileSync(tempBuilderEntryPath, entryContent);
229
+
230
+ // builder entry
231
+ config.entry = tempBuilderEntryPath;
232
+ config.buildId = buildId;
233
+ config.useNodePolyfills = this.useNodePolyfills;
234
+
235
+ let builder;
236
+ switch (config.builder) {
237
+ case 'webpack':
238
+ builder = new Webpack(config);
239
+ break;
240
+ case 'esbuild':
241
+ builder = new Esbuild(config);
242
+ break;
243
+ default:
244
+ builder = new Webpack(config);
245
+ break;
246
+ }
247
+
248
+ // Run common build
249
+ await builder.run();
250
+
251
+ // delete .temp files
252
+ rmSync(tempBuilderEntryPath);
253
+ feedback.build.success(Messages.build.success.vulcan_build_succeeded);
254
+ } catch (error) {
255
+ debug.error(error);
256
+ process.exit(1);
254
257
  }
255
-
256
- // Run common build
257
- await builder.run();
258
-
259
- // delete .temp files
260
- rmSync(tempBuilderEntryPath);
261
- feedback.build.success(Messages.build.success.vulcan_build_succeeded);
262
258
  };
263
259
  }
264
260
 
@@ -0,0 +1,51 @@
1
+ /**
2
+ * FrameworkInitializer contains various methods to initialize new projects
3
+ * in different JavaScript frameworks.
4
+ *
5
+ * Each method is an asynchronous function that takes a `projectName` as an argument.
6
+ * This `projectName` is then used to initialize a new project in the respective framework.
7
+ * @namespace FrameworkInitializer
8
+ * @typedef {object} FrameworkInitializer
9
+ * @property {Function} Angular - Initializes a new Angular project.
10
+ * @property {Function} Astro - Initializes a new Astro project.
11
+ * @property {Function} Hexo - Initializes a new Hexo project.
12
+ * @property {Function} Next - Initializes a new Next.js project.
13
+ * @property {Function} React - Initializes a new React project.
14
+ * @property {Function} Vue - Initializes a new Vue project.
15
+ * @property {Function} Vite - Initializes a new Vue project with Vite.
16
+ * @example
17
+ * const { Angular, React } = FrameworkInitializer;
18
+ *
19
+ * // Initialize a new Angular project called 'myAngularProject'
20
+ * await Angular('myAngularProject');
21
+ *
22
+ * // Initialize a new React project called 'myReactProject'
23
+ * await React('myReactProject');
24
+ */
25
+ import { exec } from '#utils';
26
+
27
+ const FrameworkInitializer = {
28
+ Angular: async (projectName) => {
29
+ await exec(`npx ng new ${projectName}`, 'Angular', false, true);
30
+ },
31
+ Astro: async (projectName) => {
32
+ await exec(`npx create-astro ${projectName}`, 'Astro', false, true);
33
+ },
34
+ Hexo: async (projectName) => {
35
+ await exec(`npx hexo init ${projectName}`, 'Hexo', false, true);
36
+ },
37
+ Next: async (projectName) => {
38
+ await exec(`npx create-next-app ${projectName}`, 'Next', false, true);
39
+ },
40
+ React: async (projectName) => {
41
+ await exec(`npx create-react-app ${projectName}`, 'React', false, true);
42
+ },
43
+ Vue: async (projectName) => {
44
+ await exec(`npx vue create ${projectName}`, 'Vue', false, true);
45
+ },
46
+ Vite: async (projectName) => {
47
+ await exec(`npx create-vue ${projectName}`, 'Vue/Vite', false, true);
48
+ },
49
+ };
50
+
51
+ export default FrameworkInitializer;
@@ -1,5 +1,8 @@
1
1
  import RuntimeApis from './runtime-apis.constants.js';
2
2
  import AzionEdges from './azion-edges.constants.js';
3
3
  import Messages from './messages/index.js';
4
+ import FrameworkInitializer from './framework-initializer.constants.js';
4
5
 
5
- export { AzionEdges, RuntimeApis, Messages };
6
+ export {
7
+ AzionEdges, RuntimeApis, Messages, FrameworkInitializer,
8
+ };
@@ -16,6 +16,8 @@ const build = {
16
16
  },
17
17
  error: {
18
18
  vulcan_build_failed: 'Vulcan build failed.',
19
+ invalid_preset: 'Invalid build preset. Run "vulcan preset ls" to view available presets.',
20
+ invalid_preset_mode: (mode, preset) => `Mode '${mode}' does not exists in preset '${preset}'. Try 'deliver' or 'compute'.`,
19
21
  },
20
22
 
21
23
  };
@@ -6,9 +6,12 @@ Global messages object.
6
6
  */
7
7
  const global = {
8
8
  success: {},
9
- info: {},
9
+ info: {
10
+ name_required: 'A name is required.',
11
+ },
10
12
  errors: {
11
13
  unknown_error: 'An error occurred.',
14
+ invalid_choice: 'Invalid choice.',
12
15
  folder_creation_failed: (folder) => `An error occurred while creating the ${folder} folder.`,
13
16
  write_file_failed: (file) => `An error occurred while writing the ${file} file.`,
14
17
  file_doesnt_exist: (file) => `An error occurred while reading the ${file} file.`,
@@ -0,0 +1,13 @@
1
+ import primitives from '@edge-runtime/primitives';
2
+ import { feedback } from '#utils';
3
+
4
+ class FetchEventPolyfill extends primitives.FetchEvent {
5
+ constructor(request) {
6
+ super(request);
7
+ this.console = {
8
+ log: (log) => feedback.server.log(log),
9
+ };
10
+ }
11
+ }
12
+
13
+ export default FetchEventPolyfill;
@@ -0,0 +1,39 @@
1
+ import { join } from 'path';
2
+ import { readFileSync } from 'fs';
3
+ import mime from 'mime-types';
4
+ import { EdgeRuntime } from 'edge-runtime';
5
+
6
+ /**
7
+ * A custom fetch implementation that adds an additional path to the URL if it starts with 'file://'.
8
+ * This function is used to simulate the local edge environment. When a 'file://' request is made,
9
+ * it behaves as if the request is made from within the edge itself. In this case, an additional
10
+ * '.edge/storage' folder is appended to the URL to represent the edge environment.
11
+ * @param {EdgeRuntime} context - VMContext
12
+ * @param {URL} url - The URL to fetch.
13
+ * @param {object} [options] - The fetch options.
14
+ * @returns {Promise<Response>} A Promise that resolves to the Response object.
15
+ */
16
+ async function fetchPolyfill(context, url, options) {
17
+ const {
18
+ URL, Headers, Response,
19
+ } = context;
20
+
21
+ const urlOBJ = new URL(url);
22
+ if (urlOBJ.href.startsWith('file://')) {
23
+ // url pathname = /VERSION_ID/filePath
24
+ const file = url.pathname.slice(15);
25
+ const filePath = join(process.cwd(), '.edge', 'storage', file);
26
+ const fileContent = readFileSync(filePath);
27
+ const contentType = mime.lookup(filePath) || 'application/octet-stream';
28
+
29
+ const headers = new Headers();
30
+ headers.append('Content-Type', contentType);
31
+
32
+ const response = new Response(fileContent, { headers, ...options });
33
+ return response;
34
+ }
35
+
36
+ return fetch(url, options);
37
+ }
38
+
39
+ export default fetchPolyfill;
@@ -0,0 +1,4 @@
1
+ import fetchPolyfill from './fetch.polyfills.js';
2
+ import FetchEventPolyfill from './FetchEvent.polyfills.js';
3
+
4
+ export { fetchPolyfill, FetchEventPolyfill };
@@ -1,6 +1,6 @@
1
1
  import { EdgeRuntime } from 'edge-runtime';
2
2
 
3
- import { fetchPolyfill, FetchEventPolyfill } from '#polyfills';
3
+ import { fetchPolyfill, FetchEventPolyfill } from './polyfills/index.js';
4
4
 
5
5
  /**
6
6
  * Executes the specified JavaScript code within a sandbox environment,
@@ -39,6 +39,14 @@ function runtime(code) {
39
39
  context.fetch = (url, options) => fetchPolyfill(context, url, options);
40
40
  context.FetchEvent = FetchEventPolyfill;
41
41
  context.FirewallEvent = {}; // TODO: Firewall Event
42
+ /*
43
+ * According to the Vercel documentation at https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime#unsupported-apis,
44
+ * the default runtime doesn't support `eval`.
45
+ * However, in our Runtime environment (Cells Runtime/Azion)
46
+ * we've enabled and support its functionality.
47
+ */
48
+ // eslint-disable-next-line no-eval
49
+ context.eval = eval;
42
50
 
43
51
  /* ==== Cells Runtime/Azion does not have this interface ==== */
44
52
  context.File = undefined;