/*
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
/**
* RuntimeEnvChecker:
* this module exports some useful generic functions
* for checks of some environment properties as runtime.
*/
const semver = require('semver')
const os = require('os')
const path = require('path')
/** Get the host name where this code is runninng */
const hostname = os.hostname()
/** Get the process id (pid) where this code is runninng */
const pid = require('process').pid
/** Get the list of engines needed, if specified in 'package.json' */
const engines = require('../package.json').engines
/** Get the number of available CPU */
const { cpus } = require('os')
/**
* Checker for Runtime Environment properties.
*
* Note that all methods here are static, so no need to instance this class;
* see it as an Utility/Companion class.
*/
class RuntimeEnvChecker {
/**
* Create a new instance of a RuntimeEnvChecker class.
*
* Note that instancing is not allowed for this class because all its methods are static.
*
* @throws {Error} because instancing not allowed for this class
*/
constructor () {
throw new Error('Instancing not allowed for this class')
}
/**
* Utility method that get some process-related info
* and wraps them into an object.
*
* @static
* @return {object} the object representation of process-related info data
*/
static processInfo () {
return {
hostname,
pid
}
}
/**
* Utility method that get some info
* about memory (total, available) and wraps them into an object.
*
* @static
* @return {object} the object representation of some memory-related info
*/
static memoryInfo () {
return {
total: os.totalmem(),
free: os.freemem()
}
}
/**
* Tell if the given argument is defined and not null.
*
* @static
* @param {object} arg the object to check
* @return {boolean} true if defined and not null, false otherwise
*/
static isDefinedAndNotNull (arg) {
return (arg !== undefined && arg !== null)
}
/**
* Tell if the given argument is defined and not null,
* is a string and is not empty.
*
* See {@link RuntimeEnvChecker.isDefinedAndNotNull}.
*
* @static
* @param {object} arg the object to check
* @return {boolean} true if it's a not empty string, false otherwise
*/
static isStringNotEmpty (arg) {
return ((RuntimeEnvChecker.isDefinedAndNotNull(arg) &&
(typeof arg === 'string') &&
(arg.length > 0))
)
}
/**
* Tell if the given argument is defined and not null,
* and is a boolean.
*
* See {@link RuntimeEnvChecker.isDefinedAndNotNull}.
*
* @static
* @param {object} arg the object to check
* @return {boolean} true if it's a boolean, false otherwise
*/
static isBoolean (arg) {
return (RuntimeEnvChecker.isDefinedAndNotNull(arg) &&
(typeof arg === 'boolean')
)
}
/**
* Tell if the environment variable with the given name
* is defined and has a value.
*
* See {@link RuntimeEnvChecker.isStringNotEmpty}.
*
* @static
* @param {!string} name the name to check
* @return {boolean} true if it's defined and has a value, false otherwise
*/
static isEnvVarDefined (name) {
return RuntimeEnvChecker.isStringNotEmpty(process.env[name])
}
/**
* Tell if the current Node.js environment is production.
*
* See {@link RuntimeEnvChecker.isEnvVarDefined}.
*
* @static
* @return {boolean} true if it's production, false otherwise
*/
static isNodeEnvProduction () {
const nodeEnv = RuntimeEnvChecker.getNodeEnv()
return (RuntimeEnvChecker.isEnvVarDefined('NODE_ENV') &&
nodeEnv === 'production')
}
/**
* Utility method that tell if the given version is compatible
* with the expected version (using then semver syntax).
*
* @static
* @param {!string} version the version to check (as a string)
* @param {!string} expectedVersion the expected version for the comparison (as a semver string)
* @return {boolean} true if version is compatible with the given constraint, otherwise false
* @throws {Error} if at least an argument is wrong
*/
static isVersionCompatible (version, expectedVersion) {
if (RuntimeEnvChecker.isStringNotEmpty(version) && RuntimeEnvChecker.isStringNotEmpty(expectedVersion)) {
return semver.satisfies(version, expectedVersion)
}
return false
}
/**
* Ensure that the given argument is a not empty string.
*
* See {@link RuntimeEnvChecker.isStringNotEmpty}.
*
* @static
* @param {object} arg the object to check
* @param {string} name the name to use in generated error (if any), empty name as default
* @return {boolean} true if it's a not empty string
* @throws {Error} if it's an empty string or it's null or undefined
*/
static checkStringNotEmpty (arg, name = '') {
if (RuntimeEnvChecker.isStringNotEmpty(arg) !== true) {
let msg
if (RuntimeEnvChecker.isStringNotEmpty(name)) {
msg = `RuntimeEnvChecker - the string '${name}' must be not empty`
} else {
msg = 'RuntimeEnvChecker - the string var/const must be not empty'
}
throw new Error(msg)
}
return true
}
/**
* Ensure that the given environment variable is defined and has a value.
*
* See {@link RuntimeEnvChecker.isEnvVarDefined}.
*
* @static
* @param {string} name the name of the variable to check
* @return {boolean} true if it's defined and has a value
* @throws {Error} if it's not defined or does not have a value
*/
static checkEnvVarDefined (name) {
if (RuntimeEnvChecker.isEnvVarDefined(name) !== true) {
let msg
if (RuntimeEnvChecker.isStringNotEmpty(name)) {
msg = `RuntimeEnvChecker - the env var '${name}' must be defined and not empty`
} else {
msg = 'RuntimeEnvChecker - the env var must be defined and not empty'
}
throw new Error(msg)
}
return true
}
/**
* Ensure that the current Node.js environment is production.
*
* See {@link RuntimeEnvChecker.isEnvVarDefined}.
*
* @static
* @return {boolean} true if it's a not empty string
* @throws {Error} if it's not production
*/
static checkNodeEnvProduction () {
if (RuntimeEnvChecker.isNodeEnvProduction() !== true) {
throw new Error(`RuntimeEnvChecker - Node.js environment is '${RuntimeEnvChecker.getNodeEnv()}' and not 'production'`)
}
return true
}
/**
* Utility method that check if the given version is compatible
* with the given expected version (using then semver syntax).
*
* @static
* @param {!string} version the version to check (as a string)
* @param {!string} expectedVersion the expected version for the comparison (as a semver string)
* @return {boolean} true if version matches
* @throws {Error} if at least an argument is wrong
* @throws {Error} if versions does not matches
* @see isVersionCompatible
*/
static checkVersion (version, expectedVersion) {
RuntimeEnvChecker.checkStringNotEmpty(version, 'version')
RuntimeEnvChecker.checkStringNotEmpty(expectedVersion, 'expectedVersion')
const compatible = RuntimeEnvChecker.isVersionCompatible(version, expectedVersion)
if (compatible !== true) {
throw new Error(`RuntimeEnvChecker - found version '${version}', but expected version '${expectedVersion}'`)
}
return true
}
/**
* Utility method that check if the given Node.js version is compatible
* with the given expected version (using then semver syntax),
* usually read from a specific field in 'package.json'.
*
* @static
* @param {string} version the version to check (as a string), by default current Node.js version
* @param {string} versionExpected the expected version for the comparison (as a semver string), by default current value for 'node', under 'engines' in 'package.json' (if set)
* @return {boolean} true if version matches, false if one of versions is null
* @throws {Error} if at least an argument is wrong
* @throws {Error} if versions are comparable but does not matches
* @see checkVersion
*/
static checkVersionOfNode (
version = process.version,
versionExpected = engines.node
) {
return RuntimeEnvChecker.checkVersion(version, versionExpected)
}
/**
* Utility method that check if the given NPM version is compatible
* with the given expected version (using then semver syntax),
* usually read from a specific field in 'package.json'.
*
* @static
* @param {string} version the version to check (as a string), default null
* @param {string} versionExpected the expected version for the comparison (as a semver string), by default current value for 'npm', under 'engines' in 'package.json' (if set)
* @return {boolean} true if version matches, false if one of versions is null
* @throws {Error} if at least an argument is wrong
* @throws {Error} if versions are comparable but does not matches
* @see checkVersion
*/
static checkVersionOfNpm (
version = null,
versionExpected = engines.npm
) {
return RuntimeEnvChecker.checkVersion(version, versionExpected)
}
/**
* Ensure that the given argument is a true value.
*
* See {@link RuntimeEnvChecker.isBoolean}.
*
* @static
* @param {object} arg the object to check
* @param {string} name the name to use in generated error (if any), empty name as default
* @return {boolean} true if it's a true value
* @throws {Error} if it's a false value or it's null or undefined
*/
static checkBoolean (arg, name = '') {
if (RuntimeEnvChecker.isBoolean(arg) !== true || arg !== true) {
let msg
if (RuntimeEnvChecker.isStringNotEmpty(name)) {
msg = `RuntimeEnvChecker - the boolean '${name}' must be true`
} else {
msg = 'RuntimeEnvChecker - the boolean var/const must be true'
}
throw new Error(msg)
}
return true
}
/**
* Utility method that gets current NPM version.
* Note that NPM is executed in a child process (but only to get its version),
* in a synchronous way.
*
* @static
* @return {string} npm version (as a string) if found, otherwise null
*/
static getVersionOfNpm () {
const { execSync } = require('child_process')
let npmVersion = null
try {
npmVersion = execSync('npm -version')
npmVersion = npmVersion.toString().replace(/(\r\n|\n|\r)/gm, '')
} catch (e) {
// error running the command, maybe npm not installed or not found
}
return npmVersion
}
/**
* Utility method that gets the value of Node.js environment variable.
*
* @static
* @return {string} Node.js env var NODE_ENV value (as a string)
*/
static getNodeEnv () {
return process.env.NODE_ENV
}
/**
* Utility method that returns the number of total CPU available.
*
* @static
* @return {int} the number of available CPU
*/
static getAvailableCpu () {
return cpus().length
}
/**
* Utility method that returns if current code is running
* with JavaScript in strict mode (as generally should be now).
*
* @static
* @return {boolean} true if strict mode is enabled
*/
static isStrictMode () {
const isStrict = (function () { return !this })()
return isStrict
}
/**
* Ensure that current code is running
* with JavaScript in strict mode (as generally should be now).
*
* See {@link RuntimeEnvChecker.isStrictMode}.
*
* @static
* @return {boolean} true if strict mode is enabled
* @throws {Error} if it's a false value or it's null or undefined
*/
static checkStrictMode () {
if (RuntimeEnvChecker.isStrictMode() !== true) {
throw new Error('RuntimeEnvChecker - JavaScript strict mode must be enabled')
}
return true
}
/**
* Utility method that returns if the given source file is running
* with JavaScript in ES Module.
* Note that the logic here is approximated and uses:
* - first the given filename (pass `__filename` to analyze current file where this function is called)
* - the then given folder name to try to read 'package.json' from there (pass `__dirname` for example)
* - other simple logic.
* but in some cases (for example if given both) I don't check here the correctness of both values
* (for example the given filename could not belong to the given foldername nor its parent,
* so different rules could be applied by the JavaScript engine, and results here could not be precise).
*
* @static
* @param {string} filename the name of the file to analyze
* @param {string} foldername the name of the folder where to search the project definition file
* @return {boolean} true if it seems an ESModule
* @throws {Error} if both arguments are undefined, null or empty
*/
static isESModule (filename, foldername) {
if (!RuntimeEnvChecker.isStringNotEmpty(filename) && !RuntimeEnvChecker.isStringNotEmpty(foldername)) {
throw new Error('RuntimeEnvChecker - specify at least filename or foldername')
}
let isModule = false
// console.log(`DEBUG: file:${filename}, folder:${foldername}`)
if (RuntimeEnvChecker.isStringNotEmpty(filename)) {
const ext = path.extname(filename)
switch (ext) {
case '.js':
// console.log('JS source found, check related "package.json" file for ESModule type')
break
case '.cjs':
// console.log('CommonJS module found')
return false
// break
case '.mjs':
// console.log('ES module found')
return true
// break
default:
// console.log(`Unable to match module extension for ${ext}`)
isModule = false
}
}
if (RuntimeEnvChecker.isStringNotEmpty(foldername)) {
// try to read related project definition file and its "type" attribute (if present)
let projectType
try {
projectType = require(`${foldername}/package.json`).type
} catch (e) {
// probably file not found, retry with parent folder
try {
projectType = require(`${foldername}/../package.json`).type
} catch (e) {
// probably file not found, but stop here
}
}
if (RuntimeEnvChecker.isStringNotEmpty(projectType)) {
// console.log(`Found "package.json" in the given folder (or in its parent), with type attribute value: ${projectType}`)
switch (projectType) {
case 'commonjs':
// console.log('CommonJS module found')
return false
// break
case 'module':
// console.log('ES module found')
return true
// break
default:
// console.log(`Unable to match module type for ${projectType}`)
isModule = false
}
}
}
return isModule
}
/**
* Ensure that current code is running
* with JavaScript as ES Module.
*
* See {@link RuntimeEnvChecker.isESModule}.
*
* @static
* @param {string} filename the name of the file to analyze
* @param {string} foldername the name of the folder where to search the project definition file
* @return {boolean} true if ES Module is enabled
* @throws {Error} if it's a false value or it's null or undefined
*/
static checkESModule (filename, foldername) {
if (RuntimeEnvChecker.isESModule(filename, foldername) !== true) {
throw new Error('RuntimeEnvChecker - JavaScript ES Module required')
}
return true
}
}
module.exports = RuntimeEnvChecker