weldable
Version:
A webpack configuration tool.
207 lines (172 loc) • 4.37 kB
JavaScript
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { consoleMessage } = require('./logger');
/**
* @module Global
*/
/**
* JS file extensions
*
* @type {string[]}
*/
const jsFileExtensions = ['js', 'jsx', 'mjs', 'cjs'];
/**
* TS file extensions
*
* @type {string[]}
*/
const tsFileExtensions = ['ts', 'tsx', 'mts', 'cts'];
/**
* Handle a variety of error types consistently.
*
* @param {string|Array|object|any} errors
* @returns {string|any|any[]}
*/
const errorMessageHandler = errors => {
const parseErrorObject = err => {
if (typeof err === 'string') {
return err;
}
if (err.message) {
return err.message;
}
const updatedEntries = [];
Object.entries(err).forEach(([key, value]) => updatedEntries.push(`${key}: ${value}`));
return updatedEntries.join('\n');
};
if (typeof errors === 'string') {
return errors;
}
if (Array.isArray(errors)) {
const updatedErrors = [];
errors.forEach(err => updatedErrors.push(parseErrorObject(err)));
return updatedErrors;
}
if (typeof errors === 'object') {
return parseErrorObject(errors);
}
return errors;
};
/**
* Check if "is a Promise", "Promise like".
*
* @param {Promise|*} obj
* @returns {boolean}
*/
const isPromise = obj => /^\[object (Promise|Async|AsyncFunction)]/.test(Object.prototype.toString.call(obj));
/**
* Import module regardless of CommonJS or ES.
*
* @param {string} file
* @returns {Promise<any>}
*/
const dynamicImport = async file => {
let result;
try {
if (process.env._WELDABLE_TEST === 'true') {
result = require(file);
} else {
result = await import(file);
}
} catch (e) {
consoleMessage.warn(`Dynamic import for ${file}: ${e.message}`);
}
return result;
};
/**
* Global context path. On load set path.
*
* @type {string}
*/
const contextPath = (() => {
if (process.env._WELDABLE_TEST === 'true') {
return path.join('..', '__fixtures__');
}
if (process.env._WELDABLE_DEV === 'true') {
return path.join(__dirname, '..', './__fixtures__');
}
return process.cwd();
})();
/**
* Create a file with a fallback name based on hashed contents.
*
* @param {string} contents
* @param {object} options
* @param {string} options.dir
* @param {string} options.ext
* @param {string} options.encoding
* @param {string} options.filename
* @param {boolean} options.resetDir
* @returns {{path: string, file: (*|string), contents: string, dir: string}}
*/
const createFile = (
contents,
{ dir = path.resolve(contextPath), ext = 'txt', encoding = 'utf8', filename, resetDir = false } = {}
) => {
const updatedFileName = filename || crypto.createHash('md5').update(contents).digest('hex');
const file = path.extname(updatedFileName) ? updatedFileName : `${updatedFileName}.${ext}`;
const filePath = path.join(dir, file);
if (resetDir && fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true });
}
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
let updatedContents;
try {
fs.writeFileSync(filePath, contents, { encoding });
updatedContents = fs.readFileSync(filePath, { encoding });
} catch (e) {
consoleMessage.error(e.message);
}
return { dir, file, path: filePath, contents: updatedContents };
};
/**
* Execute a command
*
* @param {string} cmd
* @param {object} settings
* @param {string} settings.errorMessage
* @returns {string}
*/
const runCmd = (cmd, { errorMessage = 'Skipping... {0}' } = {}) => {
let stdout = '';
try {
stdout = execSync(cmd);
} catch (e) {
consoleMessage.error(errorMessage.replace('{0}', e.message));
}
return stdout.toString();
};
/**
* Global options/settings. One time _set, then freeze.
*
* @type {{contextPath: string, _set: *}}
*/
const OPTIONS = {
contextPath,
set _set(obj) {
Object.entries(obj).forEach(([key, value]) => {
if (typeof value === 'function') {
this[key] = value.call(this);
return;
}
this[key] = value;
});
delete this._set;
Object.freeze(this);
}
};
module.exports = {
contextPath,
createFile,
dynamicImport,
errorMessageHandler,
jsFileExtensions,
isPromise,
OPTIONS,
runCmd,
tsFileExtensions
};