@misterzik/espressojs 3.2.6 → 3.3.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.
@@ -0,0 +1,71 @@
1
+ /*
2
+ * _| _| _| _| _|_|_|
3
+ * _| _| _|_| _|_| _| _|
4
+ * _| _| _| _| _| _| _|
5
+ * _| _| _| _| _| _|
6
+ * _| _| _| _|_|_|
7
+ * EspressoJS - Health Check Middleware
8
+ */
9
+
10
+ const mongoose = require("mongoose");
11
+ const os = require("os");
12
+
13
+ const healthCheck = (req, res) => {
14
+ const healthData = {
15
+ status: "OK",
16
+ timestamp: new Date().toISOString(),
17
+ uptime: process.uptime(),
18
+ environment: process.env.NODE_ENV || "development",
19
+ memory: {
20
+ total: os.totalmem(),
21
+ free: os.freemem(),
22
+ usage: process.memoryUsage(),
23
+ },
24
+ cpu: {
25
+ cores: os.cpus().length,
26
+ loadAverage: os.loadavg(),
27
+ },
28
+ };
29
+
30
+ if (mongoose.connection.readyState === 1) {
31
+ healthData.database = {
32
+ status: "connected",
33
+ name: mongoose.connection.name,
34
+ };
35
+ } else if (mongoose.connection.readyState === 0) {
36
+ healthData.database = {
37
+ status: "disconnected",
38
+ };
39
+ }
40
+
41
+ res.status(200).json(healthData);
42
+ };
43
+
44
+ const readinessCheck = (req, res) => {
45
+ const isReady = mongoose.connection.readyState === 1 || mongoose.connection.readyState === 0;
46
+
47
+ if (isReady) {
48
+ res.status(200).json({
49
+ status: "ready",
50
+ timestamp: new Date().toISOString(),
51
+ });
52
+ } else {
53
+ res.status(503).json({
54
+ status: "not ready",
55
+ timestamp: new Date().toISOString(),
56
+ });
57
+ }
58
+ };
59
+
60
+ const livenessCheck = (req, res) => {
61
+ res.status(200).json({
62
+ status: "alive",
63
+ timestamp: new Date().toISOString(),
64
+ });
65
+ };
66
+
67
+ module.exports = {
68
+ healthCheck,
69
+ readinessCheck,
70
+ livenessCheck,
71
+ };
@@ -0,0 +1,60 @@
1
+ /*
2
+ * _| _| _| _| _|_|_|
3
+ * _| _| _|_| _|_| _| _|
4
+ * _| _| _| _| _| _| _|
5
+ * _| _| _| _| _| _|
6
+ * _| _| _| _|_|_|
7
+ * EspressoJS - Security Middleware
8
+ */
9
+
10
+ const helmet = require("helmet");
11
+ const rateLimit = require("express-rate-limit");
12
+
13
+ const helmetConfig = () => {
14
+ return helmet({
15
+ contentSecurityPolicy: {
16
+ directives: {
17
+ defaultSrc: ["'self'"],
18
+ styleSrc: ["'self'", "'unsafe-inline'"],
19
+ scriptSrc: ["'self'", "'unsafe-inline'"],
20
+ imgSrc: ["'self'", "data:", "https:"],
21
+ },
22
+ },
23
+ crossOriginEmbedderPolicy: false,
24
+ crossOriginResourcePolicy: { policy: "cross-origin" },
25
+ });
26
+ };
27
+
28
+ const rateLimiter = rateLimit({
29
+ windowMs: 15 * 60 * 1000,
30
+ max: 100,
31
+ message: "Too many requests from this IP, please try again later.",
32
+ standardHeaders: true,
33
+ legacyHeaders: false,
34
+ });
35
+
36
+ const strictRateLimiter = rateLimit({
37
+ windowMs: 15 * 60 * 1000,
38
+ max: 10,
39
+ message: "Too many requests from this IP, please try again later.",
40
+ standardHeaders: true,
41
+ legacyHeaders: false,
42
+ });
43
+
44
+ const apiRateLimiter = rateLimit({
45
+ windowMs: 15 * 60 * 1000,
46
+ max: 200,
47
+ message: {
48
+ status: "error",
49
+ message: "Too many API requests from this IP, please try again later.",
50
+ },
51
+ standardHeaders: true,
52
+ legacyHeaders: false,
53
+ });
54
+
55
+ module.exports = {
56
+ helmetConfig,
57
+ rateLimiter,
58
+ strictRateLimiter,
59
+ apiRateLimiter,
60
+ };
@@ -0,0 +1,110 @@
1
+ /*
2
+ * _| _| _| _| _|_|_|
3
+ * _| _| _|_| _|_| _| _|
4
+ * _| _| _| _| _| _| _|
5
+ * _| _| _| _| _| _|
6
+ * _| _| _| _|_|_|
7
+ * EspressoJS - API Manager Utility
8
+ */
9
+
10
+ const axios = require("axios");
11
+ const logger = require("./logger");
12
+
13
+ class APIManager {
14
+ constructor(config) {
15
+ this.apis = {};
16
+ this.loadAPIs(config);
17
+ }
18
+
19
+ loadAPIs(config) {
20
+ const apiKeys = Object.keys(config).filter(key => key.match(/^api\d*$/));
21
+
22
+ apiKeys.forEach(key => {
23
+ const apiConfig = config[key];
24
+
25
+ if (apiConfig && (apiConfig.enabled !== false)) {
26
+ this.apis[key] = {
27
+ name: key,
28
+ baseURL: apiConfig.uri || apiConfig.url || "",
29
+ method: (apiConfig.method || "GET").toUpperCase(),
30
+ headers: apiConfig.headers || { "Content-Type": "application/json" },
31
+ timeout: apiConfig.timeout || 30000,
32
+ retries: apiConfig.retries || 0,
33
+ };
34
+
35
+ logger.info(`API endpoint '${key}' loaded: ${this.apis[key].baseURL}`);
36
+ }
37
+ });
38
+
39
+ logger.info(`Total API endpoints loaded: ${Object.keys(this.apis).length}`);
40
+ }
41
+
42
+ getAPI(name = "api") {
43
+ return this.apis[name];
44
+ }
45
+
46
+ getAllAPIs() {
47
+ return this.apis;
48
+ }
49
+
50
+ hasAPI(name) {
51
+ return !!this.apis[name];
52
+ }
53
+
54
+ async request(apiName, endpoint = "", options = {}) {
55
+ const api = this.getAPI(apiName);
56
+
57
+ if (!api) {
58
+ throw new Error(`API '${apiName}' not found in configuration`);
59
+ }
60
+
61
+ const url = endpoint ? `${api.baseURL}${endpoint}` : api.baseURL;
62
+ const config = {
63
+ method: options.method || api.method,
64
+ url,
65
+ headers: { ...api.headers, ...options.headers },
66
+ timeout: options.timeout || api.timeout,
67
+ ...options,
68
+ };
69
+
70
+ let lastError;
71
+ const maxRetries = options.retries !== undefined ? options.retries : api.retries;
72
+
73
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
74
+ try {
75
+ logger.debug(`API request to ${apiName}: ${config.method} ${url} (attempt ${attempt + 1}/${maxRetries + 1})`);
76
+ const response = await axios(config);
77
+
78
+ logger.info(`API ${apiName} request successful: ${response.status}`);
79
+ return response.data;
80
+ } catch (error) {
81
+ lastError = error;
82
+ logger.warn(`API ${apiName} request failed (attempt ${attempt + 1}/${maxRetries + 1}): ${error.message}`);
83
+
84
+ if (attempt < maxRetries) {
85
+ const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
86
+ await new Promise(resolve => setTimeout(resolve, delay));
87
+ }
88
+ }
89
+ }
90
+
91
+ logger.error(`API ${apiName} request failed after ${maxRetries + 1} attempts`);
92
+ throw lastError;
93
+ }
94
+
95
+ createAxiosInstance(apiName) {
96
+ const api = this.getAPI(apiName);
97
+
98
+ if (!api) {
99
+ throw new Error(`API '${apiName}' not found in configuration`);
100
+ }
101
+
102
+ return axios.create({
103
+ baseURL: api.baseURL,
104
+ timeout: api.timeout,
105
+ headers: api.headers,
106
+ });
107
+ }
108
+ }
109
+
110
+ module.exports = APIManager;
@@ -1,15 +1,65 @@
1
1
  const Static = require("serve-static");
2
2
  const fs = require("fs");
3
+ const path = require("path");
4
+ const { validateConfig } = require("./configValidator");
5
+ const logger = require("./logger");
3
6
 
4
7
  function readConfigFile() {
5
- const cfgBuffer = fs.readFileSync("./config.json");
6
- const cfgBuJSON = cfgBuffer.toString();
7
- return JSON.parse(cfgBuJSON);
8
+ try {
9
+ const configPath = path.join(process.cwd(), "config.json");
10
+
11
+ if (!fs.existsSync(configPath)) {
12
+ logger.warn("config.json not found, using default configuration");
13
+ return getDefaultConfig();
14
+ }
15
+
16
+ const cfgBuffer = fs.readFileSync(configPath);
17
+ const cfgBuJSON = cfgBuffer.toString();
18
+ const config = JSON.parse(cfgBuJSON);
19
+
20
+ return validateConfig(config);
21
+ } catch (error) {
22
+ logger.error(`Error reading config file: ${error.message}`);
23
+ return getDefaultConfig();
24
+ }
8
25
  }
9
26
 
10
27
  function writeConfigFile(cfg) {
11
- const instUpdate = JSON.stringify(cfg);
12
- fs.writeFileSync("config.json", instUpdate);
28
+ try {
29
+ const validatedConfig = validateConfig(cfg);
30
+ const instUpdate = JSON.stringify(validatedConfig, null, 2);
31
+ fs.writeFileSync("config.json", instUpdate);
32
+ logger.info("Configuration file updated successfully");
33
+ } catch (error) {
34
+ logger.error(`Error writing config file: ${error.message}`);
35
+ throw error;
36
+ }
37
+ }
38
+
39
+ function getDefaultConfig() {
40
+ return {
41
+ instance: "development",
42
+ port: 8080,
43
+ hostname: "",
44
+ publicDirectory: "/public",
45
+ mongoDB: {
46
+ enabled: false,
47
+ port: null,
48
+ uri: "",
49
+ instance: "database",
50
+ },
51
+ api: {
52
+ enabled: false,
53
+ uri: "",
54
+ url: "",
55
+ method: "GET",
56
+ headers: {
57
+ "Content-Type": "application/json",
58
+ },
59
+ timeout: 30000,
60
+ retries: 0,
61
+ },
62
+ };
13
63
  }
14
64
 
15
65
  const vmdLogo = `
@@ -30,6 +80,7 @@ const setCustomCacheControl = (res, path) => {
30
80
  module.exports = {
31
81
  readConfigFile,
32
82
  writeConfigFile,
83
+ getDefaultConfig,
33
84
  vmdLogo,
34
85
  setCustomCacheControl,
35
86
  };
@@ -0,0 +1,90 @@
1
+ /*
2
+ * _| _| _| _| _|_|_|
3
+ * _| _| _|_| _|_| _| _|
4
+ * _| _| _| _| _| _| _|
5
+ * _| _| _| _| _| _|
6
+ * _| _| _| _|_|_|
7
+ * EspressoJS - Configuration Validator
8
+ */
9
+
10
+ const Joi = require("joi");
11
+ const logger = require("./logger");
12
+
13
+ const apiSchema = Joi.object({
14
+ enabled: Joi.boolean().default(true),
15
+ uri: Joi.string().uri().allow("").default(""),
16
+ url: Joi.string().allow("").default(""),
17
+ method: Joi.string()
18
+ .valid("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS")
19
+ .default("GET"),
20
+ headers: Joi.object().default({ "Content-Type": "application/json" }),
21
+ timeout: Joi.number().integer().min(0).default(30000),
22
+ retries: Joi.number().integer().min(0).max(5).default(0),
23
+ });
24
+
25
+ const configSchema = Joi.object({
26
+ instance: Joi.string()
27
+ .valid("development", "production", "global")
28
+ .default("development"),
29
+ port: Joi.number().port().default(8080),
30
+ hostname: Joi.string().hostname().allow("").default(""),
31
+ publicDirectory: Joi.string().default("/public"),
32
+ mongoDB: Joi.object({
33
+ enabled: Joi.boolean().default(false),
34
+ port: Joi.number().port().allow(null).default(null),
35
+ uri: Joi.string().allow("").default(""),
36
+ instance: Joi.string().default("database"),
37
+ }).default(),
38
+ api: apiSchema.default(),
39
+ }).unknown(true);
40
+
41
+ const validateConfig = (config) => {
42
+ const { error, value } = configSchema.validate(config, {
43
+ abortEarly: false,
44
+ allowUnknown: true,
45
+ });
46
+
47
+ if (error) {
48
+ const errorMessages = error.details.map((detail) => detail.message);
49
+ logger.error(`Configuration validation failed: ${errorMessages.join(", ")}`);
50
+ throw new Error(`Invalid configuration: ${errorMessages.join(", ")}`);
51
+ }
52
+
53
+ const validatedConfig = { ...value };
54
+ const apiKeys = Object.keys(config).filter(key => key.match(/^api\d*$/));
55
+
56
+ apiKeys.forEach(key => {
57
+ const { error: apiError, value: apiValue } = apiSchema.validate(config[key], {
58
+ abortEarly: false,
59
+ });
60
+
61
+ if (apiError) {
62
+ const errorMessages = apiError.details.map((detail) => detail.message);
63
+ logger.warn(`API configuration '${key}' validation warning: ${errorMessages.join(", ")}`);
64
+ }
65
+
66
+ validatedConfig[key] = apiValue || config[key];
67
+ });
68
+
69
+ logger.info(`Configuration validated successfully with ${apiKeys.length} API endpoint(s)`);
70
+ return validatedConfig;
71
+ };
72
+
73
+ const validateEnvVariables = () => {
74
+ const requiredEnvVars = [];
75
+ const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]);
76
+
77
+ if (missingVars.length > 0) {
78
+ logger.warn(
79
+ `Missing optional environment variables: ${missingVars.join(", ")}`
80
+ );
81
+ }
82
+
83
+ return true;
84
+ };
85
+
86
+ module.exports = {
87
+ validateConfig,
88
+ validateEnvVariables,
89
+ configSchema,
90
+ };
@@ -7,20 +7,28 @@
7
7
  * EspressoJS - CLI
8
8
  */
9
9
 
10
- const { exec } = require("child_process");
11
- const { string, number } = require("yargs");
10
+ const { exec, spawn } = require("child_process");
11
+ const { string, number, boolean } = require("yargs");
12
12
  const yargs = require("yargs");
13
- const { readConfigFile, writeConfigFile, vmdLogo } = require("./config.utils");
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const { readConfigFile, writeConfigFile, vmdLogo, getDefaultConfig } = require("./config.utils");
14
16
 
15
17
  const cfgB = readConfigFile();
18
+
16
19
  function showCommand() {
17
- console.log(cfgB);
20
+ console.log("\n╔═══════════════════════════════════════════════════════╗");
21
+ console.log("║ ESPRESSO.JS CONFIGURATION ║");
22
+ console.log("╚═══════════════════════════════════════════════════════╝\n");
23
+ console.log(JSON.stringify(cfgB, null, 2));
24
+ console.log("\n");
18
25
  }
26
+
19
27
  function runCommand() {
20
28
  if (cfgB !== undefined) {
21
29
  const cmd_ct = vmdLogo;
22
- console.warn(`${cmd_ct}`);
23
- console.warn(
30
+ console.log(`${cmd_ct}`);
31
+ console.log(
24
32
  `Espresso CLI
25
33
  --------------
26
34
  Warming Up Configs..
@@ -30,63 +38,141 @@ Instance Config:
30
38
  Configuration: ${cfgB.instance}
31
39
  Endpoint: http://localhost:${cfgB.port}
32
40
  JSON:`,
33
- cfgB,
34
- `\n\nTo stop process press CTRL+C`
41
+ JSON.stringify(cfgB, null, 2),
42
+ `\n\nTo stop process press CTRL+C\n`
35
43
  );
36
- exec(
37
- `node ./node_modules/cross-env/src/bin/cross-env NODE_ENV=${cfgB.instance} PORT=${cfgB.port} node index.js`,
38
- (error, stdout, stderr) => {
39
- if (error) {
40
- console.log(`error: ${error.message}`);
41
- return;
42
- }
43
- if (stderr) {
44
- console.log(`stderr: ${stderr}`);
45
- return;
46
- }
47
- console.log(`stdout: ${stdout}`);
48
- }
44
+
45
+ const child = spawn(
46
+ "node",
47
+ ["./node_modules/cross-env/src/bin/cross-env", `NODE_ENV=${cfgB.instance}`, `PORT=${cfgB.port}`, "node", "index.js"],
48
+ { stdio: "inherit", shell: true }
49
49
  );
50
+
51
+ child.on("error", (error) => {
52
+ console.error(`Error: ${error.message}`);
53
+ });
54
+
55
+ child.on("exit", (code) => {
56
+ if (code !== 0) {
57
+ console.error(`Process exited with code ${code}`);
58
+ }
59
+ });
50
60
  }
51
61
  }
62
+
52
63
  function envCommand(argv) {
53
- if (argv.instance !== undefined) {
54
- cfgB.instance = argv.instance;
64
+ try {
65
+ if (argv.instance !== undefined) {
66
+ cfgB.instance = argv.instance;
67
+ console.log(`✓ Instance set to: ${argv.instance}`);
68
+ }
69
+ if (argv.port !== undefined && cfgB.port !== argv.port) {
70
+ cfgB.port = argv.port;
71
+ console.log(`✓ Port set to: ${argv.port}`);
72
+ }
73
+ writeConfigFile(cfgB);
74
+ console.log("✓ Configuration saved successfully");
75
+ } catch (error) {
76
+ console.error(`✗ Error saving configuration: ${error.message}`);
77
+ process.exit(1);
78
+ }
79
+ }
80
+
81
+ function initCommand(argv) {
82
+ const configPath = path.join(process.cwd(), "config.json");
83
+
84
+ if (fs.existsSync(configPath) && !argv.force) {
85
+ console.error("✗ config.json already exists. Use --force to overwrite.");
86
+ process.exit(1);
55
87
  }
56
- if (cfgB.port !== argv.port) {
57
- cfgB.port = argv.port;
58
- console.warn("New config saved...");
59
- } else {
60
- console.warn("Config already saved...");
88
+
89
+ const defaultConfig = getDefaultConfig();
90
+ writeConfigFile(defaultConfig);
91
+ console.log("✓ config.json created successfully");
92
+ console.log("\nDefault configuration:");
93
+ console.log(JSON.stringify(defaultConfig, null, 2));
94
+ }
95
+
96
+ function validateCommand() {
97
+ try {
98
+ const configPath = path.join(process.cwd(), "config.json");
99
+
100
+ if (!fs.existsSync(configPath)) {
101
+ console.error("✗ config.json not found");
102
+ process.exit(1);
103
+ }
104
+
105
+ readConfigFile();
106
+ console.log("✓ Configuration is valid");
107
+ } catch (error) {
108
+ console.error(`✗ Configuration validation failed: ${error.message}`);
109
+ process.exit(1);
61
110
  }
62
- writeConfigFile(cfgB);
63
111
  }
64
112
 
65
- yargs.command({
66
- command: "show",
67
- describe: "Show pre-compiled Instance",
68
- handler: showCommand,
69
- });
70
- yargs.command({
71
- command: "run",
72
- describe: "Run pre-compiled Instance",
73
- handler: runCommand,
74
- });
75
- yargs.command({
76
- command: "env",
77
- describe: "Environment Configurations",
78
- builder: {
79
- instance: {
80
- describe: "Instance to run global, dev, prod --instance=dev",
81
- demandOption: false,
82
- type: string,
113
+ function versionCommand() {
114
+ const packagePath = path.join(__dirname, "../../package.json");
115
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
116
+ console.log(`\nEspressoJS v${pkg.version}`);
117
+ console.log(`Node ${process.version}`);
118
+ console.log(`Platform: ${process.platform}\n`);
119
+ }
120
+
121
+ yargs
122
+ .scriptName("espresso")
123
+ .usage("$0 <command> [options]")
124
+ .command({
125
+ command: "show",
126
+ describe: "Show current configuration",
127
+ handler: showCommand,
128
+ })
129
+ .command({
130
+ command: "run",
131
+ describe: "Run the Express server with current configuration",
132
+ handler: runCommand,
133
+ })
134
+ .command({
135
+ command: "env",
136
+ describe: "Update environment configuration",
137
+ builder: {
138
+ instance: {
139
+ describe: "Instance to run: development, production, or global",
140
+ demandOption: false,
141
+ type: string,
142
+ choices: ["development", "production", "global"],
143
+ },
144
+ port: {
145
+ describe: "Port number for the server",
146
+ demandOption: false,
147
+ type: number,
148
+ },
83
149
  },
84
- port: {
85
- describe: "Instance Port --port=8080",
86
- demandOption: true,
87
- type: number,
150
+ handler: envCommand,
151
+ })
152
+ .command({
153
+ command: "init",
154
+ describe: "Initialize a new config.json file",
155
+ builder: {
156
+ force: {
157
+ describe: "Overwrite existing config.json",
158
+ type: boolean,
159
+ default: false,
160
+ },
88
161
  },
89
- },
90
- handler: envCommand,
91
- });
92
- yargs.parse();
162
+ handler: initCommand,
163
+ })
164
+ .command({
165
+ command: "validate",
166
+ describe: "Validate the current configuration",
167
+ handler: validateCommand,
168
+ })
169
+ .command({
170
+ command: "version",
171
+ describe: "Show version information",
172
+ handler: versionCommand,
173
+ })
174
+ .demandCommand(1, "You need to specify a command")
175
+ .help()
176
+ .alias("h", "help")
177
+ .alias("v", "version")
178
+ .parse();