sbg-utility
Version:
static-blog-generator utility
1,592 lines (1,560 loc) • 93.4 kB
JavaScript
import fs from 'fs-extra';
export { default as fs } from 'fs-extra';
import path$2 from 'upath';
export { default as path } from 'upath';
import url from 'node:url';
import * as yaml from 'yaml';
import yaml__default from 'yaml';
import ansiColors from 'ansi-colors';
import path$1 from 'path';
import slugify$1 from 'slugify';
import { fileURLToPath } from 'url';
import Bluebird from 'bluebird';
import { minimatch } from 'minimatch';
import { platform, EOL } from 'os';
import { promisify as promisify$1 } from 'util';
import debuglib from 'debug';
import Axios from 'axios';
import * as cryptolib from 'crypto';
import cryptolib__default from 'crypto';
import * as glob from 'glob';
import fs$1 from 'fs';
import micromatch from 'micromatch';
import hutil from 'hexo-util';
import nunjucks from 'nunjucks';
import { PassThrough } from 'stream';
import EventEmitter from 'events';
import through2 from 'through2';
import jsdom from 'jsdom';
import PluginError from 'plugin-error';
import async from 'async';
import { load } from 'cheerio';
import ProgressBar from 'progress';
import request from 'request';
/**
* pick random items from array
* @param items
* @returns
*/
function array_random(items) {
return items[Math.floor(Math.random() * items.length)];
}
/**
* unique array
* * array of string,number
* * array of object by object key
* @param arr
* @param field key name (for array of object)
* @returns
*
* @example
* arrayOfObjUniq({p:'x',n:'x'},{p:'23',n:'x'},{p:'x',n:'5g'}, 'p'); // [{p:'x',n:'x'},{p:'23',n:'x'}]
*
* @link https://stackoverflow.com/a/67322087/6404439
*/
function array_unique(arr, field) {
if (Array.isArray(arr)) {
if (typeof field !== 'string') {
arr = arr.filter(function (x, i, a) {
return a.indexOf(x) === i;
});
}
else {
arr = arr.filter((a, i) => arr.findIndex((s) => a[field] === s[field]) === i);
}
return arr.filter((item) => {
if (typeof item === 'string')
return item.trim().length > 0;
if (Array.isArray(item))
return item.length > 0;
return true;
});
}
else {
throw new Error('array param must be instance of ARRAY');
}
}
/**
* Remove empties from array
* @param arr
* @returns
*/
function array_remove_empty(arr) {
return arr.filter((target) => {
if (typeof target === 'string')
return target.trim().length > 0;
if (Array.isArray(target))
return target.length > 0;
if (typeof target === 'object')
return Object.keys(target).length > 0;
return true;
});
}
/**
* unique array of object by object key
* @param arr
* @param field key name
* @returns
* @see {@link https://stackoverflow.com/a/67322087/6404439}
* @example
* const arrobj = [{p:'x',n:'x'},{p:'23',n:'x'},{p:'x',n:'5g'}],
* arrayOfObjUniq(arrobj, 'p'); // [{p:'x',n:'x'},{p:'23',n:'x'}]
*/
function arrayOfObjUniq(arr, field) {
//console.log(array);
if (!Array.isArray(arr)) {
throw new Error('array param must be instance of ARRAY');
}
return arr.filter((a, i) => arr.findIndex((s) => a[field] === s[field]) === i);
}
/**
* array shuffler
* @param items
* @returns
*/
function array_shuffle(items) {
return items.sort(() => Math.random() - 0.5);
}
/**
* generate random number
* @see {@link https://stackoverflow.com/a/65638217/6404439}
* @param n
* @returns
*/
const rand = (n) => 0 | (Math.random() * n);
/**
* fast shuffle array (internal)
* @see {@link https://stackoverflow.com/a/65638217/6404439}
* @param t
*/
function swap(t, i, j) {
const q = t[i];
t[i] = t[j];
t[j] = q;
return t;
}
/**
* fast shuffle array using swap method
* @see {@link https://stackoverflow.com/a/65638217/6404439}
* @param t
*/
function array_shuffle_swap(t) {
let last = t.length;
let n;
while (last > 0) {
n = rand(last);
swap(t, n, --last);
}
}
/**
* flattern array
* @param arr
* @returns
*/
function array_flatten(arr, depth) {
if (typeof depth === 'number')
return arr.flat(depth);
return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? array_flatten(cur) : cur), []);
}
function isStream(stream, {checkOpen = true} = {}) {
return stream !== null
&& typeof stream === 'object'
&& (stream.writable || stream.readable || !checkOpen || (stream.writable === undefined && stream.readable === undefined))
&& typeof stream.pipe === 'function';
}
function isWritableStream(stream, {checkOpen = true} = {}) {
return isStream(stream, {checkOpen})
&& (stream.writable || !checkOpen)
&& typeof stream.write === 'function'
&& typeof stream.end === 'function'
&& typeof stream.writable === 'boolean'
&& typeof stream.writableObjectMode === 'boolean'
&& typeof stream.destroy === 'function'
&& typeof stream.destroyed === 'boolean';
}
function isReadableStream(stream, {checkOpen = true} = {}) {
return isStream(stream, {checkOpen})
&& (stream.readable || !checkOpen)
&& typeof stream.read === 'function'
&& typeof stream.readable === 'boolean'
&& typeof stream.readableObjectMode === 'boolean'
&& typeof stream.destroy === 'function'
&& typeof stream.destroyed === 'boolean';
}
/**
* copy file/folder recursively
* @param src
* @param dest
* @param options
* @returns
*/
function copyPath(src, dest, options) {
if (!fs.existsSync(path$1.dirname(dest))) {
fs.mkdirSync(path$1.dirname(dest), { recursive: true });
}
return fs.copy(src, dest, Object.assign({ overwrite: true, dereference: true, errorOnExist: false }, options || {}));
}
/**
* delete folder/file async
* @param path path of file/folder
* @param throws enable throwable
* @returns
*/
function del(path, throws) {
return new Bluebird((resolve, reject) => {
const rmOpt = { recursive: true, force: true };
if (fs.existsSync(path)) {
if (!throws) {
// always success
fs.rm(path, rmOpt).then(resolve).catch(resolve);
}
else {
fs.rm(path, rmOpt).then(resolve).catch(reject);
}
/*if (statSync(path).isDirectory()) {
rmdir(path, { maxRetries: 10 }).then(resolve).catch(resolve);
} else {
rm(path, rmOpt).then(resolve).catch(resolve);
}*/
}
else {
if (!throws) {
// always success
resolve(new Error(path + ' not found'));
}
else {
reject(new Error(path + ' not found'));
}
}
});
}
/**
* read directory recursive callback
* @param dir
* @returns
*/
const readDir = function (dir, done) {
let results = [];
fs.readdir(dir, function (err, list) {
if (err)
return done(err);
let i = 0;
(function next() {
let file = list[i++];
if (!file)
return done(undefined, results);
file = path$2.resolve(dir, file);
fs.stat(file, function (err, stat) {
if (!err && stat && stat.isDirectory()) {
readDir(file, function (err, res) {
if (!err && Array.isArray(res))
results = results.concat(res);
next();
});
}
else {
results.push(file);
next();
}
});
})();
});
};
/**
* read directory recursive async
* @param dir
* @returns
*/
const readDirAsync = function (dir) {
return new Bluebird((resolve, reject) => {
readDir(dir, function (err, files) {
if (err)
reject(err);
resolve(files);
});
});
};
/**
* empty dir with filters
* @param dir
* @param param1
*/
function emptyDir(dir, { ignore }) {
readDir(dir, function (err, fileList) {
if (err)
throw err;
// const filterFn = (pattern: string) => minimatch.filter(pattern, { matchBase: true });
const filter = fileList?.filter((file) => {
for (let i = 0; i < ignore.length; i++) {
const pattern = ignore[i];
if (typeof pattern === 'string') {
const match = minimatch(file, pattern, { matchBase: true, dot: true });
// console.log(file.replace(dir, ''), pattern, match);
// filter file if matched
if (match)
return false;
}
else {
// filter file if matched
if (pattern.test(file))
return false;
}
}
return true;
}) || [];
return filter;
});
}
//const _ex = path.join(__dirname, '../..');
//emptyDir(_ex, { ignore: ['**/config/**'] });
const readdir = promisify$1(fs.readdir);
const isWindows = platform() === 'win32';
const delimiter = isWindows ? '\\' : '/';
function trueCasePathNew(opt) {
const defaults = { sync: false };
const trueCase = _trueCasePath(Object.assign(defaults, opt || {}));
return (filePath, basePath, cbOpt) => {
if (filePath.length > 3) {
let result;
let bPath = undefined;
let callbackOpt = Object.assign({ unix: false }, cbOpt || {});
if (typeof basePath === 'string') {
bPath = basePath;
}
else if (typeof basePath === 'object') {
callbackOpt = Object.assign({ unix: false }, basePath || {});
}
// Join basePath if provided
let fPath = filePath;
if (typeof bPath === 'string')
fPath = path$1.join(bPath, filePath);
// Normalize the path before any checks
fPath = path$1.normalize(fPath);
// Check if the file exists
if (fs.existsSync(fPath)) {
result = trueCase(filePath, bPath);
}
else {
// Only capitalize the drive letter, don't touch the rest of the path
result = fPath.replace(/^([a-zA-Z]):/, (match, driveLetter) => {
return driveLetter.toUpperCase() + ':';
});
}
// Handle Unix conversion if requested
if (callbackOpt?.unix) {
return path$2.toUnix(result);
}
else {
return result;
}
}
else {
// Error handling for invalid paths
if (typeof basePath === 'string') {
if (opt?.debug)
console.error('Failed to convert case-path of', { basePath, filePath });
return path$1.join(basePath, filePath);
}
else {
if (opt?.debug)
console.error('Failed to convert case-path of', { filePath });
return filePath;
}
}
};
}
const trueCasePathSync = trueCasePathNew({ sync: true });
const trueCasePath = trueCasePathNew({ sync: false });
function getRelevantFilePathSegments(filePath) {
return filePath.split(delimiter).filter((s) => s !== '');
}
function escapeString(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function matchCaseInsensitive(fileOrDirectory, directoryContents, filePath) {
const caseInsensitiveRegex = new RegExp(`^${escapeString(fileOrDirectory)}$`, 'i');
for (const file of directoryContents) {
if (caseInsensitiveRegex.test(file))
return file;
}
throw new Error(`[true-case-path]: Called with ${filePath}, but no matching file exists`);
}
function _trueCasePath({ sync }) {
return (filePath, basePath) => {
if (!fs.existsSync(filePath))
return basePath ? path$1.join(basePath, filePath) : filePath;
if (basePath) {
if (!path$1.isAbsolute(basePath)) {
throw new Error(`[true-case-path]: basePath argument must be absolute. Received "${basePath}"`);
}
basePath = path$1.normalize(basePath);
}
filePath = path$1.normalize(filePath);
const segments = getRelevantFilePathSegments(filePath);
if (path$1.isAbsolute(filePath)) {
if (basePath) {
throw new Error('[true-case-path]: filePath must be relative when used with basePath');
}
basePath = isWindows
? segments.shift()?.toUpperCase() // drive letter
: '';
}
else if (!basePath) {
basePath = process.cwd();
}
return sync ? iterateSync(basePath, filePath, segments) : iterateAsync(basePath, filePath, segments);
};
}
function iterateSync(basePath, filePath, segments) {
return segments.reduce((realPath, fileOrDirectory) => realPath + delimiter + matchCaseInsensitive(fileOrDirectory, fs.readdirSync(realPath + delimiter), filePath), basePath);
}
async function iterateAsync(basePath, filePath, segments) {
return await segments.reduce(async (realPathPromise, fileOrDirectory) => (await realPathPromise) +
delimiter +
matchCaseInsensitive(fileOrDirectory, await readdir((await realPathPromise) + delimiter), filePath), basePath);
}
const __filename$7 = fileURLToPath(import.meta.url);
const __dirname$5 = path$2.dirname(__filename$7);
function getAppRootDir() {
let currentDir = __dirname$5;
while (!fs.existsSync(path$2.join(currentDir, 'package.json'))) {
currentDir = path$2.join(currentDir, '..');
}
return path$2.toUnix(trueCasePathSync(currentDir));
}
/**
* function to encode file data to base64 encoded string
* @param file path file
* @returns
*/
function image_base64_encode(file) {
// read binary data
const bitmap = fs.readFileSync(file);
// convert binary data to base64 encoded string
return bitmap.toString('base64');
}
function fixDriveLetter(filePath) {
// Handle Windows-style paths with lowercase drive letters
if (/^[a-z]:/.test(filePath)) {
// Ensure first letter is uppercase
return filePath.charAt(0).toUpperCase() + filePath.slice(1);
}
return filePath;
}
/**
* UNIX join path with true-case-path
* @description normalize path and make drive letter uppercase
* @param str
* @returns Unix Style Path
*/
function normalizePath(...str) {
const join = path$1.join(...str);
if (fs.existsSync(join)) {
const casePath = trueCasePathSync(join);
return fixDriveLetter(casePath);
}
else {
return fixDriveLetter(join);
}
}
/**
* UNIX join path with true-case-path
* @description normalize path and make drive letter uppercase
* @param str
* @returns Unix Style Path
*/
function normalizePathUnix(...str) {
const join = path$2.join(...str);
if (fs.existsSync(join)) {
const casePath = trueCasePathSync(join);
return fixDriveLetter(path$2.toUnix(casePath));
}
else {
return fixDriveLetter(join);
}
}
/**
* remove base path
* @param target path to remove
* @param toRemove cwd
*/
function removeCwd(target, toRemove) {
return normalizePath(target).replace(toRemove, '');
}
/**
* UNIX join path with auto create dirname when not exists
* @param path
* @returns
*/
function joinSolve(...paths) {
const merge = normalizePath(...paths);
if (!fs.existsSync(path$2.dirname(merge))) {
fs.mkdirSync(path$2.dirname(merge), { recursive: true });
}
return merge;
}
/**
* create writestream (auto create dirname)
* @param dest
* @param options
* @returns
*/
function createWriteStream(dest, options) {
if (!fs.existsSync(path$2.dirname(dest)))
fs.mkdirSync(path$2.dirname(dest));
return fs.createWriteStream(dest, options);
}
/**
* sync write to file recursively (auto create dirname)
* @param file
* @param content
* @param opt
*/
function writefile(file, content, opt = {}) {
// create dirname when not exist
if (!fs.existsSync(path$2.dirname(file)))
fs.mkdirSync(path$2.dirname(file), Object.assign({ recursive: true }, opt));
const result = {
file,
append: false
};
// transform object
if (typeof content === 'object') {
content = JSON.stringify(content, null, 2);
}
if (opt.append) {
result.append = true;
fs.appendFileSync(file, content);
}
else {
fs.writeFileSync(file, content);
}
if (opt.async)
return Promise.resolve(result);
return result;
}
// filemanager
/**
* is non-markdown file
* @param path
* @returns
*/
const isAsset = (path) => /.(js|css|scss|njk|ejs|png|jpe?g|gif|svg|webp|json|html|txt)$/.test(String(path));
/**
* is markdown file
* @param path
* @returns
*/
const isMarkdown = (path) => /.(md)$/i.test(String(path));
// import { areWeTestingWithJest } from './jest';
// import * as configs from '../config';
const __filename$6 = fileURLToPath(import.meta.url);
path$1.dirname(__filename$6);
const FOLDER = path$2.join(process.cwd(), 'tmp/logs');
// let cwd = process.cwd();
// disable console.log on jest
// if (areWeTestingWithJest()) {
// const log = console.log;
// console.log = function (...args: any[]) {
// if (typeof configs.getConfig === 'function') {
// const cfg = configs.getConfig();
// FOLDER = upath.join(cfg.cwd, 'tmp/logs/');
// cwd = cfg.cwd;
// }
// const stack = (new Error('').stack || '').split(/\r?\n/gm);
// let msg = (stack || [])[3] || '';
// if (msg.includes(__filename)) {
// msg = (stack || [])[2] || '';
// }
// // log(stack[2], stack[4]);
// const filename = slugify(upath.toUnix(msg).replace(upath.toUnix(cwd), ''), {
// lower: true,
// trim: true,
// replacement: '-',
// strict: true
// });
// const header = `\n\n ${new Date()} \n\n`;
// const write = writefile(upath.join(FOLDER, filename + '.log'), header + args.join('\n\n'), { append: true });
// log(write.file);
// };
// }
//const _log = typeof hexo === 'undefined' ? console : Object.assign({ log: console.log }, hexo.log);
const _log$1 = console;
/**
* @example
* const console = Logger
* Logger.log('hello world'); // should be written in <temp folder>/logs/[trace-name].log
*/
class Logger {
static log(...args) {
_log$1.log(...args);
this.tracer(...args);
}
static info(...args) {
_log$1.info.apply(null, args);
this.tracer(...args);
}
static error(...args) {
_log$1.error.apply(null, args);
this.tracer(...args);
}
static tracer(...args) {
const error = new Error();
const split = error.stack?.split(/\r?\n/gm).map((str) => {
const split2 = str.trim().split(' ');
return {
name: split2[1],
path: split2[2]?.replace(/\\+/gm, '/').replace(/^\(/, '').replace(/\)$/, '')
//trace: error.stack
};
});
if (split) {
split.splice(0, 3);
let logfile;
let templ;
// anonymous caller
if (typeof split[0].path === 'undefined' && split[1].path.includes('anonymous')) {
const id = split[1].name;
const filePath = split[0].name;
const base = path$2.basename(filePath.split(':')[0].length === 1
? filePath.split(':')[0] + ':' + filePath.split(':')[1]
: filePath.split(':')[0]);
logfile = path$2.join(FOLDER, slugify$1(id, { trim: true }) + '-' + slugify$1(base, { trim: true }) + '.log');
if (!fs.existsSync(logfile)) {
writefile(logfile, '');
}
templ = `${'='.repeat(20)}\nfile: ${filePath}\ndate: ${new Date()}\n${'='.repeat(20)}\n\n`;
args.forEach((o) => {
if (o === null)
o = 'null';
if (typeof o === 'object') {
try {
o = JSON.stringify(o, null, 2);
}
catch {
//
}
}
templ += String(o) + '\n\n';
});
fs.appendFileSync(logfile, templ);
}
// Logger.log(split);
}
}
}
/**
* Chainable function runner.
*
* @param schedule - An array of function objects, each containing a callback and optional `opt` properties for before and after functions.
* @example
* ```ts
* chain([
* {
* callback: () => 'stream process eg: gulp',
* opt: {
* before: () => 'run before callback called',
* after: () => 'run after callback called'
* }
* },
* {
* callback: () => 'promise process'
* },
* {
* callback: () => 'synchronous function'
* }
* ]);
* ```
*/
async function chain(schedule) {
// NodeJS.ReadWriteStream | Promise<any>
const run = function (instance) {
return new Promise((resolve) => {
const logname = ansiColors.blueBright('chain') + '.' + ansiColors.yellowBright('run');
if (instance.opt?.before) {
instance.opt.before();
}
const obj = instance.callback.call && instance.callback.call(null);
// Logger.log(
// `is readable stream ${isReadableStream(obj)}`,
// `is writable stream ${isWritableStream(obj)}`,
// `is promise ${isPromise(obj)}`,
// `is stream ${isStream(obj)}`,
// { keys: Object.keys(obj) }
// );
if (isReadableStream(obj)) {
Logger.log('readable stream');
return obj.once('end', async () => {
if (instance.opt?.after) {
await instance.opt.after();
return resolve(this);
}
else {
return resolve(this);
}
});
}
else if (isWritableStream(obj)) {
Logger.log('writable stream');
return obj.once('finish', async () => {
if (instance.opt?.after) {
await instance.opt.after();
return resolve(this);
}
else {
return resolve(this);
}
});
}
else if (isStream(obj)) {
// gulp instance / readwrite stream
return obj.once('end', async () => {
if (instance.opt?.after) {
await instance.opt.after();
return resolve(this);
}
else {
return resolve(this);
}
});
}
else if (isPromise(obj)) {
//Logger.log('promises');
return obj.then(async () => {
if (instance.opt?.after) {
await instance.opt.after();
return resolve(this);
}
else {
return resolve(this);
}
});
}
else {
if (typeof instance.callback !== 'function') {
Logger.log(logname, 'cannot determine method instances');
}
}
resolve.bind(this)(chain.bind(this));
});
};
while (schedule.length > 0) {
const instance = schedule.shift();
if (typeof instance !== 'undefined')
await run(instance);
}
}
/**
* check object is Promises
* @param p
* @returns
*/
function isPromise(p) {
return ((p && String(Object.prototype.toString.call(p)).toLowerCase() === '[object promise]') ||
// check ES6 Promises
(p &&
typeof p.constructor === 'function' &&
Function.prototype.toString.call(p.constructor).replace(/\(.*\)/, '()') ===
Function.prototype.toString
.call(/*native object*/ Function)
.replace('Function', 'Promise') // replacing Identifier
.replace(/\(.*\)/, '()')) || // removing possible FormalParameterList
// my own experiment
(p && String(Function.prototype.toString.call(p.constructor)).startsWith('function Promise(')));
}
/**
* convert hours,min,sec to milliseconds
* @param hrs
* @param min
* @param sec
* @returns
*/
const toMilliseconds = (hrs, min, sec) => (hrs * 60 * 60 + min * 60 + sec) * 1000;
/**
* Creates a debug logger with the specified name.
*
* @param name - The name of the debug logger. This will be used as a prefix for log messages.
* @returns A debug logger function that can be used to log messages.
*/
function debug(name) {
return debuglib(name);
}
/**
* Creates a debug logger with the default name `sbg`.
*
* This function allows for an optional subname to further specify the debug logger.
*
* @param subname - An optional suffix to append to the default logger name.
* @returns A debug logger function with the name `sbg` or `sbg-{subname}` if provided.
*/
function sbgDebug(subname) {
if (subname)
return debuglib(`sbg-${subname}`);
return debuglib('sbg');
}
const __filename$5 = fileURLToPath(import.meta.url);
const __dirname$4 = path$2.dirname(__filename$5);
/**
* MD5 file synchronously
* @param path
*/
function md5FileSync(path) {
if (!path || path.length === 0)
return undefined;
let fileBuffer = Buffer.from(path);
if (fs.existsSync(path)) {
if (fs.statSync(path).isFile())
fileBuffer = fs.readFileSync(path);
}
const hashSum = cryptolib__default.createHash('md5'); // sha256
hashSum.update(fileBuffer);
return hashSum.digest('hex');
}
/**
* PHP MD5 Equivalent
* @param data
*/
function md5(data) {
if (!data || data.length === 0)
return undefined;
return cryptolib__default.createHash('md5').update(data).digest('hex');
}
/**
* convert file to hash
* @param alogarithm
* @param path
* @param encoding
* @returns
*/
function file_to_hash(alogarithm, path, encoding = 'hex') {
return new Promise((resolve, reject) => {
const hash = cryptolib__default.createHash(alogarithm);
const rs = fs.createReadStream(path);
rs.on('error', reject);
rs.on('data', (chunk) => hash.update(chunk));
rs.on('end', () => resolve(hash.digest(encoding)));
});
}
/**
* convert data to hash (async)
* @param alogarithm
* @param data
* @param encoding
* @returns
*/
function data_to_hash(alogarithm = 'sha1', data, encoding = 'hex') {
return new Promise((resolve, reject) => {
try {
resolve(data_to_hash_sync(alogarithm, data, encoding));
}
catch (e) {
reject(e);
}
});
}
/**
* convert data to hash (sync)
* @param alogarithm
* @param data
* @param encoding
* @returns
*/
function data_to_hash_sync(alogarithm = 'sha1', data, encoding = 'hex') {
return cryptolib__default.createHash(alogarithm).update(data).digest(encoding);
}
/**
* get hashes from folder
* @param alogarithm
* @param folder
* @param options
* @returns
*/
async function folder_to_hash(alogarithm, folder, options) {
return new Promise((resolvePromise, rejectPromise) => {
options = Object.assign({
encoding: 'hex',
ignored: [],
pattern: ''
}, options || {});
if (folder.startsWith('file:'))
folder = folder.replace('file:', '');
// fix non exist
if (!fs.existsSync(folder))
folder = path$2.join(__dirname$4, folder);
// run only if exist
if (fs.existsSync(folder)) {
glob
.glob(options.pattern || '**/*', {
cwd: folder,
ignore: (options.ignored || [
'**/tmp/**',
'**/build/**',
'**/.cache/**',
'**/dist/**',
'**/.vscode/**',
'**/coverage/**',
'**/release/**',
'**/bin/**',
'**/*.json'
]).concat('**/.git*/**', '**/node_modules/**'),
dot: true,
noext: true
})
.then((matches) => {
const filesWithHash = {};
for (let i = 0; i < matches.length; i++) {
const item = matches[i];
const fullPath = path$2.join(folder, item);
const statInfo = fs.statSync(fullPath);
if (statInfo.isFile()) {
const fileInfo = `${fullPath}:${statInfo.size}:${statInfo.mtimeMs}`;
const hash = data_to_hash_sync(alogarithm, fileInfo, options.encoding);
filesWithHash[fullPath] = hash;
}
}
resolvePromise({
filesWithHash,
hash: data_to_hash_sync(alogarithm, Object.values(filesWithHash).join(''), options.encoding)
});
})
.catch(rejectPromise);
}
else {
console.log(folder + ' not found');
}
});
}
/**
* convert data to hash
* @param alogarithm
* @param url
* @param encoding
* @returns
*/
async function url_to_hash(alogarithm = 'sha1', url, encoding = 'hex') {
return new Promise((resolve, reject) => {
let outputLocationPath = path$2.join(__dirname$4, 'node_modules/.cache/postinstall', path$2.basename(url));
// remove slashes when url ends with slash
if (!path$2.basename(url).endsWith('/')) {
outputLocationPath = outputLocationPath.replace(/\/$/, '');
}
// add extension when dot not exist
if (!path$2.basename(url).includes('.')) {
outputLocationPath += '.tgz';
}
if (!fs.existsSync(path$2.dirname(outputLocationPath))) {
fs.mkdirSync(path$2.dirname(outputLocationPath), { recursive: true });
}
const writer = fs.createWriteStream(outputLocationPath, { flags: 'w' });
Axios(url, { responseType: 'stream' }).then((response) => {
response.data.pipe(writer);
let error;
writer.on('error', (err) => {
error = err;
writer.close();
reject(err);
});
writer.on('close', async () => {
if (!error) {
// console.log('package downloaded', outputLocationPath.replace(__dirname, ''));
file_to_hash(alogarithm, outputLocationPath, encoding).then((checksum) => {
resolve(checksum);
});
}
});
});
});
}
/**
* check development NODE_ENV
* @returns
*/
function isdev() {
return /dev/i.test(process.env.NODE_ENV || '');
}
/**
* Check if current runner is JEST
* * source: {@link https://stackoverflow.com/a/52231746}
* @returns
*/
function areWeTestingWithJest() {
return process.env.JEST_WORKER_ID !== undefined;
}
/* eslint no-fallthrough: ["error", { "commentPattern": "break[\\s\\w]*omitted" }] */
const __filename$4 = url.fileURLToPath(import.meta.url);
const { parse: $parse, stringify: $stringify } = JSON;
const { keys } = Object;
/**
* Generates an SHA-1 hash from the provided data.
* @param data - The input data to hash.
* @returns The SHA-1 hash of the data.
*/
function sha1(data) {
return cryptolib.createHash('sha1').update(data.toString(), 'binary').digest('hex');
}
/**
* Retrieves the current stack trace and creates an error log file.
* @returns An object containing the stack trace and the file path.
*/
function getStack() {
const stack = new Error('get caller').stack
?.split(/\r?\n/gim)
.filter((str) => /(dist|src)/i.test(str) && !str.includes(__filename$4)) ?? [];
const folder = path$1.join(process.cwd(), 'tmp/error/json-serializer');
// Create folder if it does not exist
if (!fs.existsSync(folder))
fs.mkdirSync(folder, { recursive: true });
const file = path$1.join(folder, `${sha1(stack[1])}.log`);
return { stack, file };
}
const Primitive = String; // It could be Number
const primitive = 'string'; // It could be 'number'
const ignore = {};
const object = 'object';
const noop$2 = (_, value) => value;
const primitives = (value) => (value instanceof Primitive ? Primitive(value) : value);
const Primitives = (_, value) => (typeof value === primitive ? new Primitive(value) : value);
/**
* Recursively revives circular references in a parsed object.
*/
const revive = (input, parsed, output, $) => {
const lazy = [];
for (let ke = keys(output), { length } = ke, y = 0; y < length; y++) {
const k = ke[y];
const value = output[k];
if (value instanceof Primitive) {
const tmp = input[value];
if (typeof tmp === object && !parsed.has(tmp)) {
parsed.add(tmp);
output[k] = ignore;
lazy.push({ k, a: [input, parsed, tmp, $] });
}
else
output[k] = $.call(output, k, tmp);
}
else if (output[k] !== ignore)
output[k] = $.call(output, k, value);
}
for (let { length } = lazy, i = 0; i < length; i++) {
const { k, a } = lazy[i];
// eslint-disable-next-line prefer-spread
output[k] = $.call(output, k, revive.apply(null, a));
}
return output;
};
/**
* Adds a value to a set of known values and returns its index.
*/
const set = (known, input, value) => {
const index = Primitive(input.push(value) - 1);
known.set(value, index);
return index;
};
/**
* Parses a JSON string with support for circular references.
* @param text - The JSON string to parse.
* @param reviver - Optional function to transform the parsed values.
* @returns The parsed object.
*/
const parse = (text, reviver) => {
try {
const input = $parse(text, Primitives).map(primitives);
const value = input[0];
const $ = reviver || noop$2;
const tmp = typeof value === object && value ? revive(input, new Set(), value, $) : value;
return $.call({ '': tmp }, '', tmp);
}
catch (e) {
const { stack, file } = getStack();
fs.writeFileSync(file, `${stack.join('\n')}\n\n${text}`);
console.log('fail parse ' + file + ' ' + e.message);
process.exit(1);
}
};
/**
* Stringifies an object into JSON with support for circular references.
* @param value - The object to stringify.
* @param replacer - Optional function to transform the values before stringifying.
* @param space - Optional number or string to use as a white space in the output.
* @returns The JSON string representation of the object.
*/
const stringify = (value, replacer, space) => {
const $ = replacer && typeof replacer === 'object'
? (k, v) => (k === '' || replacer.indexOf(k) !== -1 ? v : void 0)
: replacer || noop$2;
const known = new Map();
const input = [];
const output = [];
let i = +set(known, input, $.call({ '': value }, '', value));
let firstRun = !i;
while (i < input.length) {
firstRun = true;
output[i] = $stringify(input[i++], replace, space);
}
return '[' + output.join(',') + ']';
function replace(key, value) {
if (firstRun) {
firstRun = false;
return value;
}
const after = $.call(this, key, value);
switch (typeof after) {
case object:
if (after === null)
return after;
// break omitted
case primitive:
return known.get(after) || set(known, input, after);
}
return after;
}
};
/**
* Converts an object with circular references to JSON.
* @param any - The object to convert.
* @returns The JSON representation of the object.
*/
const toJSON = (any) => $parse(stringify(any));
/**
* Parses a circular object from JSON.
* @param any - The JSON string to parse.
* @returns The parsed object.
*/
const fromJSON = (any) => parse($stringify(any));
JSON.stringifyWithCircularRefs = toJSON;
/**
* transform any object to json. Suppress `TypeError: Converting circular structure to JSON`
* @param data
* @returns
*/
function jsonStringifyWithCircularRefs(data) {
return stringify(data);
}
/**
* parse json stringified with circular refs
*/
function jsonParseWithCircularRefs(data) {
return parse(data);
}
/** SCHEDULER JOB **/
/*** Postpone executing functions ***/
//const _log = typeof hexo !== 'undefined' ? hexo.log : console;
const _log = console;
const logname = ansiColors.magentaBright('[scheduler]');
const fns = {};
let triggered;
/**
* Bind functions to exit handler
* @param key
* @param fn
*/
function bindProcessExit(key, fn) {
fns[key] = fn;
// trigger once
if (!triggered) {
triggered = true;
triggerProcess();
}
}
/**
* Handler function on process exit
* @param options
* @param exitCode
*/
async function exitHandler(options, exitCode = 0) {
const funcs = [];
for (const key in fns) {
if (Object.prototype.hasOwnProperty.call(fns, key)) {
funcs.push({
callback: fns[key],
opt: {
before: () => {
if (scheduler.verbose)
_log.info(logname, `executing function key: ${key}`);
}
}
});
}
}
if (options?.cleanup)
chain(funcs);
if (options?.cleanup && scheduler.verbose)
_log.info(logname, `clean exit(${exitCode})`);
if (options?.exit)
process.exit();
}
/**
* Trigger Process Bindings
*/
function triggerProcess() {
// before exit
//process.on('beforeExit', exitHandler.bind(undefined, { exit: true }));
//do something when app is closing
process.on('exit', exitHandler.bind(undefined, { cleanup: true }));
// process.on('disconnect', exitHandler.bind(undefined, { exit: true }));
//catches ctrl+c event
process.on('SIGINT', exitHandler.bind(undefined, { exit: true }));
//process.on('SIGKILL', exitHandler.bind(undefined, { exit: true }));
// catches "kill pid" (for example: nodemon restart)
process.on('SIGUSR1', exitHandler.bind(undefined, { exit: true }));
process.on('SIGUSR2', exitHandler.bind(undefined, { exit: true }));
//catches uncaught exceptions
process.on('uncaughtException', exitHandler.bind(undefined, { exit: true }));
}
///// task queue manager
const functions = {};
/**
* @example
* ```js
* bindProcessExit("scheduler_on_exit", function () {
* _log.info("executing scheduled functions");
* scheduler.executeAll();
* });
* ```
* or
* ```js
* scheduler.register();
* ```
*/
class scheduler {
static verbose = true;
constructor() {
if (!scheduler.registered)
scheduler.register();
}
static registered = false;
/**
* Register scheduler to process system
*/
static register() {
if (scheduler.registered)
return;
scheduler.registered = true;
bindProcessExit('scheduler_on_exit', function () {
scheduler.executeAll();
});
}
/**
* Add function with key to list
* @param key existing key (duplicate) will be overwritten
* @param value
*/
static add(key, value) {
functions[key] = value;
const self = this;
new Promise((resolve) => {
setTimeout(() => {
resolve(self.register());
}, 3000);
});
}
static postponeCounter = 0;
/**
* Add function to postpone, the functions will be executed every 5 items added
*/
static postpone(key, value) {
functions['postpone-' + key] = value;
scheduler.postponeCounter += 1;
if (scheduler.postponeCounter == 5) {
scheduler.executeAll();
scheduler.postponeCounter = 0;
}
}
/**
* Execute functon in key and delete
* @param key
*/
static execute(key, deleteAfter = true) {
if (typeof functions[key] == 'function') {
functions[key]();
if (deleteAfter)
delete functions[key];
}
else {
if (scheduler.verbose)
console.error(`function with key: ${key} is not function`);
}
}
/**
* Execute all function lists
*/
static async executeAll() {
const keys = Object.keys(functions);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (scheduler.verbose)
_log.info(logname, 'executing', key);
await Bluebird.promisify(functions[key])();
}
}
}
const locks = [];
scheduler.add('clean locks', () => {
locks.forEach((lock) => lock.release());
});
const __filename$3 = fileURLToPath(import.meta.url);
const __dirname$3 = path$1.dirname(__filename$3);
/**
* search yarn root workspace folder
* @param ctx option with property `base_dir`
*/
function findYarnRootWorkspace(ctx) {
const baseDir = ctx.base_dir;
/**
* extract workspaces from package.json
* @param manifest
* @returns
*/
const extractWorkspaces = function (manifest) {
const workspaces = (manifest || {}).workspaces;
return (workspaces && workspaces.packages) || (Array.isArray(workspaces) ? workspaces : null);
};
/**
* read package.json from given folder
* @param dir
* @returns
*/
const readPackageJSON = function (dir) {
const file = path$1.join(dir, 'package.json');
if (fs$1.existsSync(file)) {
return JSON.parse(fs$1.readFileSync(file).toString());
}
};
let previous = 'THIS INITIATOR VALUE WILL NEVER EXECUTED';
let current = path$1.normalize(baseDir);
// loop searching
do {
const manifest = readPackageJSON(current);
if (!manifest)
continue;
const workspaces = extractWorkspaces(manifest);
if (workspaces) {
const relativePath = path$1.relative(current, baseDir);
if (relativePath === '' || micromatch([relativePath], workspaces).length > 0) {
return current;
}
return null;
}
previous = current;
current = path$1.dirname(current);
} while (current !== previous);
return null;
}
/**
* Resolves the path of a command binary from `node_modules/.bin`.
*
* This function searches for the specified command in various directories, including
* the current working directory, the module directory, and optionally, user-defined
* search directories. If the command is not found, it returns the original command name.
*
* @param commandName - The name of the command to resolve.
* @param options - Optional parameters for command resolution.
* @param options.searchDir - A custom directory or an array of directories to search for
* the command. If provided, these directories will be included in the search.
* @returns The resolved command path if found; otherwise, returns the original command name.
*/
function resolveCommand(commandName, options) {
const dirs = [__dirname$3, process.cwd()];
if (typeof process === 'object') {
if ('mainModule' in process)
dirs.push(process.mainModule?.paths[0].split('node_modules')[0].slice(0, -1));
if ('main' in process)
dirs.push(process.main?.paths[0].split('node_modules')[0].slice(0, -1));
}
if (options && options.searchDir) {
if (!Array.isArray(options.searchDir)) {
options.searchDir = [options.searchDir];
}
dirs.push(...options.searchDir);
}
// try {
// dirs.push(findYarnRootWorkspace({ base_dir: process.cwd() }));
// } catch (_) {
// //
// }
const cmdPath = dirs
.filter((str) => typeof str === 'string' && str.length > 0)
.map((cwd) => {
const nm = path$1.join(cwd, 'node_modules/.bin');
return path$1.join(nm, commandName);
})
.filter(fs$1.existsSync)[0];
if (!cmdPath) {
console.error(`Command '${commandName}' not found in node_modules/.bin`);
return commandName; // Return the original command name
}
return process.platform === 'win32' ? `${cmdPath}.cmd` : cmdPath;
}
const cmd = resolveCommand;
/**
* no operations
* @param _args
* @returns
*/
function noop$1(..._args) {
return;
}
function envNunjucks(loader, opts) {
const env = new nunjucks.Environment(loader, opts);
env.addFilter('uriencode', (str) => {
return hutil.encodeURL(str);
});
env.addFilter('noControlChars', (str) => {
return str.replace(/[\x00-\x1F\x7F]/g, ''); // eslint-disable-line no-control-regex
});
// Extract date from datetime
env.addFilter('formatDate', (input) => {
return input.toISOString().substring(0, 10);
});
return env;
}
/**
* sort alphabetically object by key
* @param obj
* @returns
*/
function orderKeys(obj) {
const keys = Object.keys(obj).sort(function keyOrder(k1, k2) {
if (k1 < k2)
return -1;
else if (k1 > k2)
return +1;
else
return 0;
});
const after = {};
for (let i = 0; i < keys.length; i++) {
after[keys[i]] = obj[keys[i]];
delete obj[keys[i]];
}
for (let i = 0; i < keys.length; i++) {
obj[keys[i]] = after[keys[i]];
}
return obj;
}
/**
* get object property by key, supress typescript error
* @param item
* @param key
* @returns
*/
function getObjectProperty(item, key) {
if (typeof item === 'undefined' || item === null)
return;
if (key in item)
return item[key];
}
class persistentCache {
base;
name;
duration;
memory;
persist;
memoryCache = {};
constructor(options = {}) {
this.base =
options.base ||
normalizePath((require.main ? path$2.dirname(require.main.filename) : undefined) || process.cwd(), 'tmp');
this.name = options.name || 'cache';
this.persist = typeof options.persist == 'boolean' ? options.persist : true;
this.memory = typeof options.memory == 'boolean' ? options.memory : true;
this.duration = options.duration || Infinity;
}
/**
* add cache deferred callback
* @param key
* @param data
* @param cb
* @returns
*/
put(key, data, cb) {
const put = this.putSync(key, data);
if (put === true) {
return safeCb(cb)(null);
}
else {
safeCb(cb)(put);
}
}
/**
* add cache sync
* @param key
* @param data
* @returns boolean=success, any=error
*/
putSync(key, data) {
const entry = this.buildCacheEntry(data);
if (this.persist) {
// save in file
try {
writefile(this.buildFilePath(key), jsonStringifyWithCircularRefs(entry));
}
catch (e) {
return e;
}
}
if (this.memory) {
// save in memory only
entry.data = jsonStringifyWithCircularRefs(entry.data);
this.memoryCache[key] = entry;
}
return true;
}
setSync = this.putSync;
/**
* add cache async
* @param key
* @param data
* @returns
*/
set(key, data) {
return new Promise((resolve, reject) => {
this.put(key, data, function (e) {
if (!e) {
resolve(true);
}
else {
reject(e);
}
});
});
}
/**
* get cache by key synchronously
* @param name
* @param fallback
* @returns
*/
getSync(name, fallback) {
if (this.memory && !!this.memoryCache[name]) {
const entry = this.memoryCache[name];
if (entry.cacheUntil && new Date().getTime() > entry.cacheUntil) {
// cache expired
return fallback;
}
return JSON.parse(entry.data);
}
let data;
try {
data = JSON.parse(fs.readFileSync(this.buildFilePath(name), 'utf8'));
}
catch (_e) {
return fallback;
}
if (data.cacheUntil && new Date().getTime() > data.cacheUntil)
return fallback;
return data.data;
}
get(name, fallback) {
if (typeof fallback !== 'function') {
return new Promise((resolve) => {
resolve(this.getSync(name, fallback));
});
}
else {
if (this.memory && !!this.memoryCache[name]) {
let entry = this.memoryCache[name];
if (!!entry.cacheUntil && new Date().getTime() > entry.cacheUntil) {
return safeCb(fallback)(null, undefined);
}
try {