@randlabs/js-config-reader
Advanced tools
Comparing version 1.0.3 to 1.1.0
@@ -13,2 +13,3 @@ import Ajv from "ajv"; | ||
extendedValidator?: ExtendedValidatorCallback<S>; | ||
usingClusters?: boolean; | ||
} | ||
@@ -15,0 +16,0 @@ interface FailedConstraint { |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("ajv"),r=require("ajv-formats-draft2019"),t=require("fs"),o=require("json5"),s=require("path");function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=a(e),i=a(r),c=a(t),l=a(o),f=a(s);class d extends Error{constructor(e){super(e),this.name="ValidationError",this.failures=[],Object.setPrototypeOf(this,new.target.prototype)}}let u,p;exports.ValidationError=d,exports.get=function(){return u},exports.getSource=function(){return p},exports.initialize=async function(e){let r;if(void 0!==e){if("object"!=typeof e||Array.isArray(e))throw new Error("Options not set")}else e={};if("string"==typeof e.source&&(r=e.source),r||"string"!=typeof e.envVar||"string"==typeof process.env[e.envVar]&&(r=process.env[e.envVar]),!r){let t="settings";"string"==typeof e.cmdLineParam&&e.cmdLineParam.length>0&&(t=e.cmdLineParam),t="--"+t;for(let e=0;e<process.argv.length;e++)if(process.argv[e]===t){if(e+1>=process.argv.length)throw new Error("Missing source in '"+t+"' parameter.");r=process.argv[e+1];break}}if(!r)throw new Error("Source not defined");try{if(e.loader){const t=await e.loader(r);u=l.default.parse(t)}else if(r=f.default.resolve(process.cwd(),r),r.endsWith(".js"))u=require(r);else{const e=c.default.readFileSync(r);u=l.default.parse(e.toString())}}catch(e){throw new Error("Unable to load configuration ["+e.message+"].")}if(e.schema){let r;if("string"==typeof e.schema)try{const t=f.default.resolve(process.cwd(),e.schema),o=c.default.readFileSync(t);r=l.default.parse(o.toString())}catch(e){throw new Error("Unable to load schema.")}else{if("object"!=typeof e.schema)throw new Error("Invalid validator schema.");r=e.schema}const t="object"==typeof e.schemaOpts?e.schemaOpts:{},o=new n.default({...t,allErrors:!0,messages:!0,useDefaults:!0,format:"full"});i.default(o);const s=o.compile(r);if(!s(u)){const e=new d("Validation failed.");if(s.errors)for(const r of s.errors)e.failures.push({location:r.schemaPath,message:r.message||""});throw e}}return p=r,e.extendedValidator&&await e.extendedValidator(u),u}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("ajv"),t=require("ajv-formats-draft2019"),r=require("fs"),s=require("json5"),o=require("path");function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=a(e),i=a(t),c=a(r),l=a(s),f=a(o);let u;class p extends Error{constructor(e){super(e),this.name="ValidationError",this.failures=[],Object.setPrototypeOf(this,new.target.prototype)}}let d,g,m;exports.ValidationError=p,exports.get=function(){return d},exports.getSource=function(){return g},exports.initialize=async function(e){let t;if(void 0!==e){if("object"!=typeof e||Array.isArray(e))throw new Error("Options not set")}else e={};if(e.usingClusters&&(u||(u=require("cluster")),m=u),!m||m.isMaster){if("string"==typeof e.source&&(t=e.source),t||"string"!=typeof e.envVar||"string"==typeof process.env[e.envVar]&&(t=process.env[e.envVar]),!t){let r="settings";"string"==typeof e.cmdLineParam&&e.cmdLineParam.length>0&&(r=e.cmdLineParam),r="--"+r;for(let e=0;e<process.argv.length;e++)if(process.argv[e]===r){if(e+1>=process.argv.length)throw new Error("Missing source in '"+r+"' parameter.");t=process.argv[e+1];break}}if(!t)throw new Error("Source not defined");try{if(e.loader){const r=await e.loader(t);d=l.default.parse(r)}else if(t=f.default.resolve(process.cwd(),t),t.endsWith(".js"))d=require(t);else{const e=c.default.readFileSync(t);d=l.default.parse(e.toString())}}catch(e){throw new Error("Unable to load configuration ["+e.message+"].")}if(e.schema){let t;if("string"==typeof e.schema)try{const r=f.default.resolve(process.cwd(),e.schema),s=c.default.readFileSync(r);t=l.default.parse(s.toString())}catch(e){throw new Error("Unable to load schema.")}else{if("object"!=typeof e.schema)throw new Error("Invalid validator schema.");t=e.schema}const r="object"==typeof e.schemaOpts?e.schemaOpts:{},s=new n.default({...r,allErrors:!0,messages:!0,useDefaults:!0,format:"full"});i.default(s);const o=s.compile(t);if(!o(d)){const e=new p("Validation failed.");if(o.errors)for(const t of o.errors)e.failures.push({location:t.schemaPath,message:t.message||""});throw e}}g=t,e.extendedValidator&&await e.extendedValidator(d),m&&m.on("message",((e,t)=>{e&&"RANDLABS:JS:CONFIG:READER:getSettingsRequest"===t.type&&e.isConnected()&&e.send({type:"RANDLABS:JS:CONFIG:READER:getSettingsResponse",settings:d,settingsSource:g})}))}else await new Promise(((e,t)=>{let r=null,s=!1;function o(e){"RANDLABS:JS:CONFIG:READER:getSettingsResponse"===e.type&&(d=e.settings,g=e.settingsSource,a())}function a(a){s||(s=!0,process.off("message",o),r&&(clearTimeout(r),r=null),a?t(a):e())}process.on("message",o),r=setTimeout((()=>{a(new Error("Operation timed out"))}),2e3),process.send({type:"RANDLABS:JS:CONFIG:READER:getSettingsRequest"})}));return d}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@randlabs/js-config-reader", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "Configuration settings loader", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -40,2 +40,3 @@ # js-config-reader | ||
* `extendedValidator` - Specifies an additional settings validator. Optional. | ||
* `usingClusters` - Prepares usage for a cluster environment. Optional. | ||
@@ -42,0 +43,0 @@ # License |
253
src/index.ts
@@ -6,5 +6,11 @@ import Ajv from "ajv"; | ||
import path from "path"; | ||
import { ClusterModule, loadCluster, Worker } from "./dynamicImport"; | ||
// ----------------------------------------------------------------------------- | ||
const GET_SETTINGS_REQUEST = "RANDLABS:JS:CONFIG:READER:getSettingsRequest"; | ||
const GET_SETTINGS_RESPONSE = "RANDLABS:JS:CONFIG:READER:getSettingsResponse"; | ||
// ----------------------------------------------------------------------------- | ||
export type DefaultSettings = Record<string, unknown>; | ||
@@ -24,2 +30,3 @@ | ||
extendedValidator?: ExtendedValidatorCallback<S>; | ||
usingClusters?: boolean; | ||
} | ||
@@ -47,2 +54,3 @@ | ||
let settingsSource: string; | ||
let cluster: ClusterModule; | ||
@@ -64,3 +72,3 @@ // ----------------------------------------------------------------------------- | ||
* @param {ExtendedValidatorCallback} options.extendedValidator - Specifies an additional settings validator. Optional. | ||
* | ||
* @param {boolean} options.usingClusters - Prepares usage for a cluster environment. Optional. | ||
* @returns {Settings} - Loaded configuration settings. | ||
@@ -80,124 +88,197 @@ */ | ||
// if a source was passed, use it | ||
if (typeof options.source === "string") { | ||
source = options.source; | ||
if (options.usingClusters) { | ||
cluster = loadCluster(); | ||
} | ||
// if no source, try to get source from environment variable | ||
if (!source && typeof options.envVar === "string") { | ||
if (typeof process.env[options.envVar] === "string") { | ||
source = process.env[options.envVar]; | ||
if ((!cluster) || cluster.isMaster) { | ||
// Master or single instance | ||
// If a source was passed, use it | ||
if (typeof options.source === "string") { | ||
source = options.source; | ||
} | ||
} | ||
// if still no source, parse command-line arguments | ||
if (!source) { | ||
let cmdLineOption = "settings"; | ||
if (typeof options.cmdLineParam === "string" && options.cmdLineParam.length > 0) { | ||
cmdLineOption = options.cmdLineParam; | ||
// If no source, try to get source from environment variable | ||
if (!source && typeof options.envVar === "string") { | ||
if (typeof process.env[options.envVar] === "string") { | ||
source = process.env[options.envVar]; | ||
} | ||
} | ||
cmdLineOption = "--" + cmdLineOption; | ||
// If still no source, parse command-line arguments | ||
if (!source) { | ||
let cmdLineOption = "settings"; | ||
//lookup the command-line parameter | ||
for (let idx = 0; idx < process.argv.length; idx++) { | ||
if (process.argv[idx] === cmdLineOption) { | ||
if (idx + 1 >= process.argv.length) { | ||
throw new Error("Missing source in '" + cmdLineOption + "' parameter."); | ||
if (typeof options.cmdLineParam === "string" && options.cmdLineParam.length > 0) { | ||
cmdLineOption = options.cmdLineParam; | ||
} | ||
cmdLineOption = "--" + cmdLineOption; | ||
// Lookup the command-line parameter | ||
for (let idx = 0; idx < process.argv.length; idx++) { | ||
if (process.argv[idx] === cmdLineOption) { | ||
if (idx + 1 >= process.argv.length) { | ||
throw new Error("Missing source in '" + cmdLineOption + "' parameter."); | ||
} | ||
source = process.argv[idx + 1]; | ||
break; | ||
} | ||
source = process.argv[idx + 1]; | ||
break; | ||
} | ||
} | ||
} | ||
// if we reach here and no source, throw error | ||
if (!source) { | ||
throw new Error("Source not defined"); | ||
} | ||
// If we reach here and no source, throw error | ||
if (!source) { | ||
throw new Error("Source not defined"); | ||
} | ||
// check for loader, if none provided, read from disk file | ||
try { | ||
if (!options.loader) { | ||
source = path.resolve(process.cwd(), source); | ||
// Check for loader, if none provided, read from disk file | ||
try { | ||
if (!options.loader) { | ||
source = path.resolve(process.cwd(), source); | ||
// eslint-disable-next-line global-require | ||
if (source.endsWith(".js")) { | ||
// eslint-disable-next-line global-require | ||
settings = require(source); | ||
if (source.endsWith(".js")) { | ||
// eslint-disable-next-line global-require | ||
settings = require(source); | ||
} | ||
else { | ||
const contents: Buffer = fs.readFileSync(source); | ||
settings = JSON5.parse(contents.toString()); | ||
} | ||
} | ||
else { | ||
const contents: Buffer = fs.readFileSync(source); | ||
settings = JSON5.parse(contents.toString()); | ||
const contents = await options.loader(source); | ||
settings = JSON5.parse(contents); | ||
} | ||
} | ||
else { | ||
const contents = await options.loader(source); | ||
settings = JSON5.parse(contents); | ||
catch (err) { | ||
throw new Error("Unable to load configuration [" + err.message + "]."); | ||
} | ||
} | ||
catch (err) { | ||
throw new Error("Unable to load configuration [" + err.message + "]."); | ||
} | ||
// validate settings against a schema if one is provided | ||
if (options.schema) { | ||
let schema: Record<string, unknown>; | ||
// Validate settings against a schema if one is provided | ||
if (options.schema) { | ||
let schema: Record<string, unknown>; | ||
if (typeof options.schema === "string") { | ||
try { | ||
const filename = path.resolve(process.cwd(), options.schema); | ||
if (typeof options.schema === "string") { | ||
try { | ||
const filename = path.resolve(process.cwd(), options.schema); | ||
const contents: Buffer = fs.readFileSync(filename); | ||
schema = JSON5.parse(contents.toString()); | ||
const contents: Buffer = fs.readFileSync(filename); | ||
schema = JSON5.parse(contents.toString()); | ||
} | ||
catch (err) { | ||
throw new Error("Unable to load schema."); | ||
} | ||
} | ||
catch (err) { | ||
throw new Error("Unable to load schema."); | ||
else if (typeof options.schema === "object") { | ||
schema = options.schema; | ||
} | ||
else { | ||
throw new Error("Invalid validator schema."); | ||
} | ||
// Create schema validator | ||
const schemaOpts = (typeof options.schemaOpts === "object") ? options.schemaOpts : {}; | ||
const ajv = new Ajv({ | ||
...schemaOpts, | ||
...{ | ||
allErrors: true, | ||
messages: true, | ||
useDefaults: true, | ||
format: "full" | ||
} | ||
}); | ||
avjDraft2019(ajv); | ||
const validate = ajv.compile(schema); | ||
// Validate settings | ||
if (!validate(settings)) { | ||
const err = new ValidationError("Validation failed."); | ||
if (validate.errors) { | ||
for (const e of validate.errors) { | ||
err.failures.push({ | ||
location: e.schemaPath, | ||
message: e.message || "" | ||
}); | ||
} | ||
} | ||
throw err; | ||
} | ||
} | ||
else if (typeof options.schema === "object") { | ||
schema = options.schema; | ||
settingsSource = source; | ||
// Run extended validator if provided | ||
if (options.extendedValidator) { | ||
await options.extendedValidator(settings as S); | ||
} | ||
else { | ||
throw new Error("Invalid validator schema."); | ||
// After loading the settings, if running in a cluster, set up the message listener | ||
if (cluster) { | ||
cluster.on("message", (worker: Worker, message: any): void => { | ||
if (worker && message.type === GET_SETTINGS_REQUEST) { | ||
// If the worker exits abruptly, it may still be in the workers list but not able to communicate. | ||
if (worker.isConnected()) { | ||
worker.send({ | ||
type: GET_SETTINGS_RESPONSE, | ||
settings, | ||
settingsSource | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
else { | ||
// Worker-side, ask the master for the settings | ||
await new Promise<void>((resolve, reject) => { | ||
let timer: NodeJS.Timer | null = null; | ||
let fulfilled = false; | ||
// create schema validator | ||
const schemaOpts = (typeof options.schemaOpts === "object") ? options.schemaOpts : {}; | ||
const ajv = new Ajv({ | ||
...schemaOpts, | ||
...{ | ||
allErrors: true, | ||
messages: true, | ||
useDefaults: true, | ||
format: "full" | ||
//setup a message listener that processes the response from the master | ||
function msgListener(message: any): void { | ||
if (message.type === GET_SETTINGS_RESPONSE) { | ||
settings = message.settings as S; | ||
settingsSource = message.settingsSource as string; | ||
fulfill(); | ||
} | ||
} | ||
}); | ||
avjDraft2019(ajv); | ||
const validate = ajv.compile(schema); | ||
process.on("message", msgListener); | ||
//validate settings | ||
if (!validate(settings)) { | ||
const err = new ValidationError("Validation failed."); | ||
function fulfill(err?: Error) { | ||
if (!fulfilled) { | ||
fulfilled = true; | ||
if (validate.errors) { | ||
for (const e of validate.errors) { | ||
err.failures.push({ | ||
location: e.schemaPath, | ||
message: e.message || "" | ||
}); | ||
process.off("message", msgListener); | ||
if (timer) { | ||
clearTimeout(timer); | ||
timer = null; | ||
} | ||
if (!err) { | ||
resolve(); | ||
} | ||
else { | ||
reject(err); | ||
} | ||
} | ||
} | ||
throw err; | ||
} | ||
} | ||
settingsSource = source; | ||
// Setup a timer if, for some reason, we don't get a response in time | ||
timer = setTimeout(() => { | ||
fulfill(new Error("Operation timed out")); | ||
}, 2000); | ||
if (options.extendedValidator) { | ||
await options.extendedValidator(settings as S); | ||
// Send the request to the master process | ||
process.send!({ | ||
type: GET_SETTINGS_REQUEST | ||
}); | ||
}); | ||
} | ||
//done | ||
// Done | ||
return settings as S; | ||
@@ -204,0 +285,0 @@ } |
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
40418
9
306
45