UNPKG

9 kBJavaScriptView Raw
1
2const fs = require("fs");
3const {join} = require("path");
4
5
6
7const defaultWarningLevel = "message";
8
9
10
11function leeJSON(_ruta,) {
12
13 let buff;
14
15 try {
16
17 if (!fs.existsSync(_ruta)) throw false;
18 buff = fs.readFileSync(_ruta);
19
20 } catch(err) {
21 return "noFile";
22 };
23
24 const jsonStr = buff.toString();
25
26
27
28 let json = null;
29
30 try {
31 json = JSON.parse(jsonStr);
32 } catch(err) {
33 return "parsingError";
34 };
35
36
37 if (!json) return "empty";
38
39
40 return json;
41
42};
43
44
45
46/**
47 * @typedef {Object} props
48 * @property {string} env Environment type, it prepends the files like `entor.<env>.json`. Example: `env = "local"` Entor will search the file `entor.local.json` on the defined `path`.
49 * @property {string} path Default `./`. The `entor.<env>.json` folder path from the root.
50 * @property {string} sharedEnvPath Default `undefined`. The **absolute path** to the shared environment folder path from the root. With `env = "local"` Entor will search for `local.shared.entor.json`.
51 * @property {function} getEnv Function that returns the `<env>`.
52 * @property {object} override JSON that will be merged with the `entor.<env>.json` content.
53 * @property {"none" | "message" | "throw"} warningLevel Default `message`
54 * Handles warnings behaviour:
55 * - `"none"`: Ignores all warnings.
56 * - `"message"`: Prints a warning message.
57 * - `"throw"`: Throws an error.
58 * @property {boolean} addToProcessEnv Default `true`. If `true` adds the `entor.<env>.json` content to the `process.env` object.
59 * @property {boolean} syncExamples Default `false`. If `true` syncs the `entor.<env>.json` file with the `entorExample.<env>.json` file.
60 *
61*/
62
63
64
65/**
66 * Run your node app with `--env=prod` to set the `env` on startup.
67 * @param {props} props
68*/
69function entor({
70 env,
71 getEnv = args => args.env,
72 path,
73 sharedEnvPath,
74 override,
75 addToProcessEnv = true,
76 warningLevel = defaultWarningLevel,
77 syncExamples = false,
78} = {}) {
79
80
81 function showError({forceWarningLevel, message, errorType = Error, color}) {
82
83 let _warningLevel = forceWarningLevel || warningLevel;
84
85 message = `[entor] ${message}`;
86
87 switch (color) {
88 case "red": color = `\x1b[31m%s\x1b[0m`; break;
89 case "green": color = `\x1b[32m%s\x1b[0m`; break;
90 case "yellow": color = `\x1b[33m%s\x1b[0m`; break;
91 case "magenta": color = `\x1b[35m%s\x1b[0m`; break;
92 case "blue": color = `\x1b[34m%s\x1b[0m`; break;
93 case "cyan": color = `\x1b[36m%s\x1b[0m`; break;
94 default: color = `\x1b[37m%s\x1b[0m`; break;
95 };
96
97 switch (_warningLevel) {
98 case "throw": throw new errorType(message);
99 case "message": console.log(color, message);
100 };
101
102 };
103
104
105
106 // Si no me viene env directo, uso getEnv
107 if (!env) {
108
109 const arrArgv = process.argv.slice(2);
110 const entorArguments = {};
111
112 arrArgv.forEach( _arg => {
113 let [key, value] = _arg.split("="); // '--env=prod' → ['--env', 'prod']
114 key = key.replace(/^--/, ""); // '--env' → 'env'
115 entorArguments[key] = value;
116 });
117
118
119 env = getEnv(entorArguments);
120
121 };
122
123
124
125 let ruta = process.cwd(); // c:\Users\admin\Desktop\entor
126 if (path) ruta = join(ruta, path);
127
128
129
130 // ***********************************************************
131 // Leo shared
132 // ***********************************************************
133
134 let sharedJson = null;
135
136
137 if (sharedEnvPath) {
138
139 const sharedFilename = `entor.${env}.json`;
140 const json = leeJSON( join(sharedEnvPath, sharedFilename ));
141
142 if (json === "noFile") {
143 return showError({warningLevel, message: `Couldn't find '${sharedFilename}' on sharedEnvPath ${sharedEnvPath}.`, errorType: SyntaxError, color: "red"});
144 } else if (json === "parsingError") {
145 return showError({warningLevel, message: `Error parsing '${sharedFilename}' on sharedEnvPath ${sharedEnvPath}.`, errorType: SyntaxError, color: "red"});
146 } else if (json === "empty") {
147 showError({warningLevel, message: `${_archivo} seems empty on sharedEnvPath.`, color: "yellow"});
148 } else if (! json) {
149 return showError({
150 message: `Can't resolve sharedEnvPath ${sharedEnvPath} ${sharedEnvPath}.`,
151 forceWarningLevel: warningLevel === "none" ? "message" : warningLevel,
152 color: "red",
153 errorType: ReferenceError,
154 });
155 } else {
156 sharedJson = json;
157 console.log("\x1b[32m%s\x1b[0m", `[entor] ${sharedFilename} (shared) has been loaded.`);
158 };
159
160 };
161
162
163 if (!env && !sharedJson) return showError({
164 message: "No env provided, getEnv failed to provide the env and/or sharedEnvPath was not provided. Check if you started your app with --env=<yourEnv>",
165 forceWarningLevel: warningLevel === "none" ? "message" : warningLevel,
166 color: "red"
167 });
168
169
170
171 // ***********************************************************
172 // Leo entor
173 // ***********************************************************
174
175 let loadedEntor = null;
176 let archivo = `entor.${env}.json`;
177
178 const resLeeJSON = leeJSON( join(ruta, archivo) );
179
180
181 if (!sharedJson) {
182 if (resLeeJSON === "noFile") {
183 return showError({warningLevel, message: `Couldn't find '${archivo}'.`, errorType: SyntaxError, color: "red"});
184 } else if (resLeeJSON === "parsingError") {
185 return showError({warningLevel, message: `Error parsing ${archivo}`, errorType: SyntaxError, color: "red"});
186 } else if (resLeeJSON === "empty") {
187 showError({warningLevel, message: `${archivo} seems empty`, color: "yellow"});
188 } else {
189 loadedEntor = resLeeJSON;
190 };
191 } else {
192 if (resLeeJSON === "noFile") {
193 showError({warningLevel, message: `Couldn't find '${archivo}'.`, errorType: SyntaxError, color: "yellow"});
194 } else if (resLeeJSON === "parsingError") {
195 showError({warningLevel, message: `Error parsing ${archivo}`, errorType: SyntaxError, color: "yellow"});
196 } else if (resLeeJSON === "empty") {
197 showError({warningLevel, message: `${archivo} seems empty`, color: "yellow"});
198 } else {
199 loadedEntor = resLeeJSON;
200 };
201 };
202
203
204
205 if (! loadedEntor && !sharedJson) {
206 return showError({
207 message: `'entor.${env}.json' couldn't be found.`,
208 forceWarningLevel: warningLevel === "none" ? "message" : warningLevel,
209 color: "red",
210 });
211 } else if (loadedEntor) {
212 console.log("\x1b[32m%s\x1b[0m", `[entor] ${archivo} has been loaded.`);
213 };
214
215
216
217 // Merge shared
218 if (sharedJson) loadedEntor = {
219 ...sharedJson,
220 ...loadedEntor,
221 };
222
223
224
225 // Merge override
226 if (override) {
227
228 const isObject = typeof override === "object" && override !== null;
229 if (!isObject) throw TypeError(`The override must be an object.`);
230
231 loadedEntor = {
232 ...loadedEntor,
233 ...override,
234 };
235
236 };
237
238
239
240 // ***********************************************************
241 // Sync examples
242 // ***********************************************************
243
244
245 if (syncExamples) {
246
247 const arrEntors = [];
248
249 fs.readdirSync(ruta).forEach( _archivo => {
250 const esEntor = /^entor\.(.*)\.json/.test(_archivo);
251 if (esEntor) arrEntors.push(_archivo);
252 });
253
254
255 let showCreatedExampleFiles = false;
256
257 arrEntors.forEach( _archivo => {
258
259 const resLeeJSON = leeJSON( join(ruta, _archivo) );
260
261 if (typeof resLeeJSON === "object" && resLeeJSON !== null) {
262
263 const exampleKeys = Object.keys(resLeeJSON);
264 const sameKeys = Object.keys(loadedEntor).every( _key => exampleKeys.includes(_key));
265
266 const nombreArchivoExample = _archivo.replace(/^entor/, "entorExample"); // entor.<env>.json → entorExample.<env>.json
267 const isExamplePresent = fs.existsSync(join(ruta, nombreArchivoExample));
268
269
270 if (!sameKeys || !isExamplePresent) {
271
272 exampleKeys.forEach( _key => resLeeJSON[_key] = resLeeJSON[_key] = "" ); // quito todos los valores
273
274 fs.writeFileSync( // escribo el archivo
275 join(ruta, nombreArchivoExample),
276 JSON.stringify(resLeeJSON, null, 4)
277 );
278
279 showCreatedExampleFiles = true;
280
281 };
282
283 };
284
285 });
286
287
288 if (showCreatedExampleFiles) console.log("\x1b[32m%s\x1b[0m", `[entor] Example files created.`);
289
290 }
291
292
293 // ***********************************************************
294 // process.env
295 // ***********************************************************
296
297 if (addToProcessEnv) {
298
299 const currentKeys = Object.keys(process.env);
300 const keysWithConflict = []; // lista de keys que hacen conflicto con process.env
301
302 Object.keys(loadedEntor).forEach( _key => {
303 if (currentKeys.includes(_key)) keysWithConflict.push(_key);
304 process.env[_key] = loadedEntor[_key];
305 });
306
307
308 if (keysWithConflict.length) showError({
309 message: `${archivo} has overwritten the following process.env variables: ${keysWithConflict.join(", ")}`,
310 color: "yellow",
311 });
312 };
313
314
315 return loadedEntor;
316
317};
318
319
320module.exports = entor;
321