kaven-utils
Version:
Utils for Node.js.
450 lines (449 loc) • 14.6 kB
JavaScript
/********************************************************************
* @author: Kaven
* @email: kaven@wuwenkai.com
* @website: http://blog.kaven.xyz
* @file: [Kaven-Utils] /src/KavenUtility.ts
* @create: 2018-08-25 08:19:55.120
* @modify: 2025-10-15 17:31:12.753
* @version: 6.1.1
* @times: 360
* @lines: 529
* @copyright: Copyright © 2018-2025 Kaven. All Rights Reserved.
* @description: Utils for Node.js.
* @license:
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { AddQueryParameterToURL, AnsiTextCyan, AnsiTextGreen, AnsiTextRed, ConvertTo, FormatCurrentDate, GenerateFileNameByCurrentDate, GenerateGuid, GetBaseDir, GetFileNameWithoutExtension, IncreaseVersion, ReplaceAllStringBetween, Strings_BackSlash, Strings_Slash, TrimEnd, TryParseVersionFromMultiLineContent } from "kaven-basic";
import { spawn } from "node:child_process";
import { createHmac } from "node:crypto";
import { readdirSync } from "node:fs";
import { open, readFile } from "node:fs/promises";
import { join } from "node:path";
import { URL } from "node:url";
import { CopyFile, GetFileContent, GetFileExtension, GetFileLines, GetFileSizeInBytes, IsPathExistSync, MakeDirectory, SaveStringToFile, TruncateFile } from "./KavenUtility.FileSystem.js";
/**
* @since 1.0.5
* @version 2025-10-14
*/
export function GetColoredStatus(code, logger) {
try {
if (code === undefined) {
return code;
}
if (typeof code === "string") {
code = Number(code);
}
if (code >= 200 && code < 300) {
return AnsiTextGreen(code.toString());
}
else if (code >= 300 && code < 400) {
return AnsiTextCyan(code.toString());
}
return AnsiTextRed(code.toString());
}
catch (ex) {
logger?.Error(ex);
return code;
}
}
/**
*
* @param fileName
* @param backupFolder
* @since 4.1.0
* @version 2022-04-22
*/
export async function BackupFile(fileName, backupFolder) {
if (backupFolder === undefined) {
backupFolder = GetBaseDir(fileName);
}
let dest = GenerateFileNameByCurrentDate();
const name = GetFileNameWithoutExtension(fileName);
const extension = GetFileExtension(fileName);
dest = join(backupFolder, `${name}.${dest}${extension}`);
// createReadStream(fileName).pipe(createWriteStream(copyName));
await CopyFile(fileName, dest);
return dest;
}
/**
* @since 1.0.5
* @version 2025-10-14
*/
export async function ResetLogFile(fileName, backupFolder, logger) {
try {
if (!IsPathExistSync(fileName)) {
logger?.Warn(`file: ${fileName} not exist!`);
return false;
}
const dest = await BackupFile(fileName, backupFolder);
await TruncateFile(fileName, 0);
logger?.Info(`Reset log file: ${fileName}, backup to: ${dest}`);
return true;
}
catch (ex) {
logger?.Error(ex);
return false;
}
}
/**
* 2019-08-05:
* add param: `logFile`
* @param overBytes
* @since 1.0.9
* @version 2020-07-11
*/
export async function ResetLogFileIf(overBytes = 1024 * 1024 * 2, logFile) {
if (logFile) {
const size = await GetFileSizeInBytes(logFile);
if (size === undefined) {
return false;
}
if (size > overBytes) {
return ResetLogFile(logFile);
}
}
return false;
}
/**
* @since 1.0.5
* @version 2025-10-14
*/
export async function BackupMongoDB(outputDir, options) {
options ??= {};
options.host ??= "127.0.0.1:27017";
// TODO: use Execute
// 1.0.1: 2018-08-15, make sure dest folder exist
if (!IsPathExistSync(outputDir)) {
await MakeDirectory(outputDir);
}
const saveDir = TrimEnd(outputDir, [Strings_Slash, Strings_BackSlash]) +
Strings_Slash +
GenerateFileNameByCurrentDate();
let dumpArgs = ["-h", options.host, "-o", saveDir];
if (options.databaseUser && options.databasePassword) {
dumpArgs = dumpArgs.concat(["-u", options.databaseUser, "-p", options.databasePassword]);
}
if (options.databaseName) {
dumpArgs = dumpArgs.concat(["-d", options.databaseName]);
}
return new Promise((resolve, reject) => {
spawn("mongodump", dumpArgs)
.on("message", (message) => {
options.logger?.Info(message);
})
.on("error", (err) => {
reject(err);
})
.on("exit", (code) => {
if (code !== 0) {
reject(new Error(`ps process exited with code ${code}`));
}
else {
if (IsPathExistSync(saveDir)) {
resolve(saveDir);
}
else {
reject(new Error(`Directory ${saveDir} not exist.`));
}
}
});
});
}
/**
* @param {string} data
* @param {BufferEncoding} [encoding="utf8"]
* @version 1.0.0
* @since 1.0.6
* @see http://www.rfc-editor.org/rfc/rfc4648.txt
*/
export function Base64Encode(data,
/**
* ascii, base64, binary, hex, ucs2/ucs-2/utf16le/utf-16le, utf8/utf-8, latin1 (ISO8859-1, only in node 6.4.0+)
*/
encoding = "utf8") {
// Buffer is deprecated: since v10.0.0 - Use `Buffer.from(string[, encoding])` instead. (deprecation)
// return new Buffer(data, encoding).toString("base64");
return Buffer.from(data, encoding).toString("base64");
}
/**
* @param {string} base64Data
* @param {string} [encoding="ascii"]
* @version 1.0.0
* @since 1.0.6
* @see http://www.rfc-editor.org/rfc/rfc4648.txt
*/
export function Base64Decode(base64Data,
/**
* ascii, base64, binary, hex, ucs2/ucs-2/utf16le/utf-16le, utf8/utf-8, latin1 (ISO8859-1, only in node 6.4.0+)
*/
encoding = "utf8") {
// return new Buffer(base64Data, "base64").toString(encoding);
return Buffer.from(base64Data, "base64").toString(encoding);
}
/**
* In cryptography, an HMAC (sometimes expanded as either keyed-hash message authentication code or
* hash-based message authentication code) is a specific type of
* message authentication code (MAC)
* involving a cryptographic hash function and a secret cryptographic key.
* @param {string} stringToSign
* @param {string} secret
* @since 1.1.6
* @version 2018-08-20
*/
export function HMAC_SHA1(stringToSign, secret) {
return createHmac("sha1", secret)
.update(stringToSign)
.digest();
// .toString("base64");
}
/**
* List all files in a directory in Node.js recursively in a synchronous fashion
* @param {string} dir
* @returns
* @since 1.1.6
* @version 2018-08-27
*/
export function GetAllFilesRecursively(dir, extensions, ignorePaths) {
let fileList = [];
if (ignorePaths && ignorePaths.includes(dir)) {
return fileList;
}
readdirSync(dir).forEach((file) => {
const dirFile = join(dir, file);
try {
fileList = fileList.concat(GetAllFilesRecursively(dirFile, extensions, ignorePaths));
}
catch (err) {
const { code } = ConvertTo(err);
if (code === "ENOTDIR" || code === "EBUSY") {
fileList = [...fileList, dirFile];
}
else {
throw err;
}
}
});
if (extensions) {
fileList = fileList.filter((f) => extensions.find((p) => f.endsWith(p)));
}
return fileList;
}
/**
* Simple wrap for readdirSync
* @param dir
* @since 1.1.18
* @version 2018-10-22
*/
export function GetAllFiles(dir) {
return readdirSync(dir).map(p => join(dir, p));
}
/**
* @since 1.1.6
* @version 2025-10-14
*/
export async function FindAndReplaceInFiles(dirOrFiles, options) {
options ??= {};
if (!options.conditions) {
options.conditions = [
["link(rel='stylesheet', href=\"", "\")"],
["link(rel='stylesheet', href='", "')"],
["link(rel=\"stylesheet\", href=\"", "\")"],
["link(rel=\"stylesheet\", href='", "')"],
["script(src=\"", "\")"],
["script(src='", "')"],
];
}
if (!options.newStrMethod) {
const version = FormatCurrentDate("YYYYMMDD");
options.newStrMethod = (url) => AddQueryParameterToURL(url, "version", version);
}
if (!options.extensions) {
options.extensions = [".jade", ".pug"];
}
let files;
if (typeof dirOrFiles === "string") {
files = GetAllFilesRecursively(dirOrFiles, options.extensions);
}
else {
files = dirOrFiles;
}
for (const file of files) {
let content = await GetFileContent(file);
for (const item of options.conditions) {
content = ReplaceAllStringBetween(options.newStrMethod, content, item[0], item[1]);
}
await SaveStringToFile(content, file);
options.logger?.Info(`Replaced file: ${file}`);
}
}
/**
*
* @param filesOrFolders
* @param excludeFilesOrFolders
* @param fileTypes
* @since 1.1.7
* @version 2018-09-01
*/
export function GetFileList(filesOrFolders, excludeFilesOrFolders, fileTypes) {
let fileList = [];
// if (excludeFilesOrFolders && excludeFilesOrFolders.IsNotEmpty()) {
// filesOrFolders = filesOrFolders.filter((file) => !excludeFilesOrFolders.includes(file));
// }
for (const item of filesOrFolders) {
fileList = fileList.concat(GetAllFilesRecursively(item, fileTypes, excludeFilesOrFolders));
}
// if (excludeFilesOrFolders && excludeFilesOrFolders.IsNotEmpty()) {
// fileList = fileList.filter((file) => !excludeFilesOrFolders.includes(file));
// }
return fileList;
}
/**
*
* @param file
* @param length
* @param offset
* @version 1.1.18
* @since 2023-11-18
*/
export async function ReadFileBytes(file, length, offset = 0) {
const fd = await open(file, "r");
try {
// const buffer = new Buffer(length);
const buffer = Buffer.alloc(length);
const result = await fd.read(buffer, 0, length, offset);
return Buffer.from(result.buffer.buffer, 0, result.bytesRead);
}
finally {
await fd.close();
}
}
/**
* @since 4.3.10
* @version 2023-11-18
*/
export async function ReadFileAsBase64(file) {
const data = await readFile(file);
return Buffer.from(data).toString("base64");
}
/**
* @since 1.1.23
* @version 2025-10-14
*/
export function GetProtocolFromURL(url, logger) {
try {
const myURL = new URL(url);
return myURL.protocol;
}
catch (ex) {
logger?.Error(ex);
}
return undefined;
}
/**
* @since 3.0.5
* @version 2025-06-18
*/
export async function TryParseVersionFromFile(file) {
const content = await GetFileContent(file);
return TryParseVersionFromMultiLineContent(content);
}
export async function TryUpdateVersion(file, index, increment) {
const result = await TryParseVersionFromFile(file);
if (result?.version === undefined) {
throw new Error(`parse version failed: ${file}`);
}
const { version, lines, versionLine, endOfLineSequence } = result;
const newVersion = index !== undefined && increment !== undefined ? IncreaseVersion(version, index, increment) : IncreaseVersion(version);
if (newVersion === undefined) {
throw new Error(`update failed: ${version}`);
}
const lineIndex = lines.indexOf(versionLine);
lines[lineIndex] = lines[lineIndex].replace(version, newVersion);
await SaveStringToFile(lines.join(endOfLineSequence), file);
return newVersion;
}
/**
*
* @param subj
* @since 4.1.0
* @version 2022-04-21
*/
export function CertificateSubjectToString(subj) {
const C = subj?.C ?? "";
const ST = subj?.ST ?? "";
const L = subj?.L ?? "";
const O = subj?.O ?? "";
const OU = subj?.OU ?? "";
const CN = subj?.CN ?? `kaven-${GenerateGuid()}`;
return `-subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}"`;
}
export async function RemoveFileComments(srcFile, arg) {
let backup = true;
let destFile = srcFile;
if (arg !== undefined) {
backup = false;
if (typeof arg === "boolean") {
backup = arg;
}
else if (typeof arg === "string") {
destFile = arg;
}
}
if (backup) {
await BackupFile(srcFile);
}
const item = await GetFileLines(srcFile);
const result = [];
let isComment = false;
for (const line of item.lines) {
let newLine = line;
if (isComment) {
if (line.includes("*/")) {
newLine = line.substring(line.indexOf("*/") + 2);
isComment = false;
}
else {
continue;
}
}
const lIndex = newLine.lastIndexOf("/*");
const rIndex = newLine.lastIndexOf("*/");
if (lIndex > rIndex) {
isComment = true;
continue;
}
if (newLine.trimStart().startsWith("//")) {
continue;
}
if (newLine.trimStart().startsWith("/*") && newLine.trimEnd().endsWith("*/")) {
continue;
}
const index = newLine.indexOf("//");
if (index !== -1) {
if (!newLine.substring(index + 2).includes("\"")) {
newLine = newLine.substring(0, index);
}
}
if (newLine.length === 0) {
continue;
}
result.push(newLine);
}
return await SaveStringToFile(result.join(item.endOfLineSequence), destFile);
}