UNPKG

kaven-utils

Version:

Utils for Node.js.

495 lines (494 loc) 16 kB
/******************************************************************** * @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: 2024-11-01 14:34:18.998 * @version: 5.4.5 * @times: 349 * @lines: 585 * @copyright: Copyright © 2018-2024 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 { open, readFile } from "fs/promises"; import { AddQueryParameterToURL, AnsiTextCyan, AnsiTextGreen, AnsiTextRed, ConvertTo, GenerateFileNameByCurrentDate, GenerateGuid, GetBaseDir, GetFileNameWithoutExtension, IncreaseVersion, ReplaceAllStringBetween, Strings_BackSlash, Strings_Slash, TryParseVersion } from "kaven-basic"; import { spawn } from "node:child_process"; import { createHmac } from "node:crypto"; import { readdirSync } from "node:fs"; import { join } from "node:path"; import { URL } from "node:url"; import { CopyFile, GetFileContent, GetFileExtension, GetFileLines, GetFileSizeInBytes, IsPathExistSync, MakeDirectory, SaveStringToFile, TruncateFile } from "./KavenUtility.FileSystem.js"; import { InternalLogger } from "./KavenUtility.Internal.js"; /** * * @param code * @since 1.0.5 * @version 2023-03-28 */ export function GetColoredStatus(code) { 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) { InternalLogger()?.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; } /** * 2019-08-05: * remove default value "./data/logs/" for `backupFolder`; * change backup file name * @param fileName * @param backupFolder * @since 1.0.5 * @version 2022-04-22 */ export async function ResetLogFile(fileName, backupFolder) { try { if (!IsPathExistSync(fileName)) { InternalLogger()?.Warn(`file: ${fileName} not exist!`); return false; } const dest = await BackupFile(fileName, backupFolder); await TruncateFile(fileName, 0); InternalLogger()?.Info(`Reset log file: ${fileName}, backup to: ${dest}`); return true; } catch (ex) { InternalLogger()?.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; } /** * * @param outputDir * @param databaseName * @param host * @param databaseUser * @param databasePassword * @version 1.0.1 * @since 1.0.5 */ export async function BackupMongoDB(outputDir, databaseName, host = "127.0.0.1:27017", databaseUser, databasePassword) { // 1.0.1: 2018-08-15, make sure dest folder exist if (!IsPathExistSync(outputDir)) { await MakeDirectory(outputDir); } const saveDir = outputDir.TrimEnd(Strings_Slash).TrimEnd(Strings_BackSlash) + Strings_Slash + GenerateFileNameByCurrentDate(); let dumpArgs = ["-h", host, "-o", saveDir]; if (databaseUser && databasePassword) { dumpArgs = dumpArgs.concat(["-u", databaseUser, "-p", databasePassword]); } if (databaseName) { dumpArgs = dumpArgs.concat(["-d", databaseName]); } return new Promise((resolve, reject) => { spawn("mongodump", dumpArgs) .on("message", (message) => { InternalLogger()?.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)); } /** * @param {string | string[]} dirOrFiles * @param {string} [conditions=[ * ["link(rel='stylesheet', href=\"", "\")"], * ["link(rel='stylesheet', href='", "')"], * ["link(rel=\"stylesheet\", href=\"", "\")"], * ["link(rel=\"stylesheet\", href='", "')"], * ["script(src=\"", "\")"], * ["script(src='", "')"], * ]] * @param {(str: string) => string} [newStrMethod] * @param {string} [extensions=[ * ".jade", * ".pug" * ]] * @param {boolean?} showLog * @since 1.1.6 * @version 2018-09-01 */ export async function FindAndReplaceInFiles(dirOrFiles, conditions = [ ["link(rel='stylesheet', href=\"", "\")"], ["link(rel='stylesheet', href='", "')"], ["link(rel=\"stylesheet\", href=\"", "\")"], ["link(rel=\"stylesheet\", href='", "')"], ["script(src=\"", "\")"], ["script(src='", "')"], ], newStrMethod, extensions = [ ".jade", ".pug", ], showLog = true) { if (!newStrMethod) { const version = Date.Create().Format("YYYYMMDD"); newStrMethod = (url) => AddQueryParameterToURL(url, "version", version); } let files; if (typeof dirOrFiles === "string") { files = GetAllFilesRecursively(dirOrFiles, extensions); } else { files = dirOrFiles; } for (const file of files) { let content = await GetFileContent(file); for (const item of conditions) { // const list = GetStringPositionBetween(content, item[0], item[1]).Distinct(); // list.forEach((val) => { // const temp = newStrMethod(val.RelatedString); // // content = content.ReplaceAll(val, temp); // content = content.ReplaceAtPos(val, temp); // position changed after replaced // }); content = ReplaceAllStringBetween(newStrMethod, content, item[0], item[1]); } SaveStringToFile(content, file).catch(ex => InternalLogger()?.Error(ex)); if (showLog) { InternalLogger()?.Info(`Finished: ${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(); } } /** * * @param file * @since 4.3.10 * @version 2023-11-18 */ export async function ReadFileAsBase64(file) { const data = await readFile(file); return Buffer.from(data).toString("base64"); } /** * * @param url * @since 1.1.23 * @version 2019-02-27 */ export function GetProtocolFromURL(url) { try { const myURL = new URL(url); return myURL.protocol; } catch (ex) { InternalLogger()?.Error(ex); } return undefined; } /** * @since 3.0.5 * @version 2022-05-01 */ export async function TryParseVersionFromFile(file) { const { endOfLineSequence, lines } = await GetFileLines(file); const versionLines = lines.filter(p => TryParseVersion(p) !== undefined); if (versionLines.length === 0) { return undefined; } const versionLine = versionLines[0]; const version = TryParseVersion(versionLine); return { endOfLineSequence, lines, versionLine, version, }; } 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); }