UNPKG

kaven-utils

Version:

Utils for Node.js.

385 lines (384 loc) 17.3 kB
#!/usr/bin/env node /******************************************************************** * @author: Kaven * @email: kaven@wuwenkai.com * @website: http://blog.kaven.xyz * @file: [Kaven-Utils] /bin.ts * @create: 2018-11-05 14:59:09.639 * @modify: 2025-04-29 15:39:54.826 * @version: 5.4.5 * @times: 219 * @lines: 469 * @copyright: Copyright © 2018-2025 Kaven. All Rights Reserved. * @description: [description] * @license: [license] ********************************************************************/ import { Command } from "commander"; import { CombinePath, KavenLog, LogLevel } from "kaven-basic"; import { promises } from "node:fs"; import { basename, dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { AppendPathToDirectory, ContinuousIntegrationForDocuments, CopyFileOrDirectory, CopyToDirectory, Delete, DeleteFilesByExtension, DockerRegistry, EnableInternalLogger, Execute, GenerateCertificate, GetExternalIp, GetExternalIpByUPnP, GetFileStats, IsFile, IsPathExistSync, IsWin32, KavenLogger, LoadJsonFile, Minify, SaveJsonConfig, StartProxy, } from "./index.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJson = join(__dirname, "package.json"); KavenLogger.Default.EnableAnsiColorForConsole = false; EnableInternalLogger(); LoadJsonFile(packageJson).then(data => { const program = new Command(); program .version(data.version, "-v, --version", "Output the version number") .helpOption("-h, --help", "Display help") .helpCommand("help [command]", "Display help for [command]") .name("kaven-utils|ku"); // copy /// ///////////////////////////////////////////////////////////////////////////////// program .command("copy [destination] [sourceFiles...]") .description("Copy files or directories to a specified destination") .option("-c, --config <config>", "Specify a JSON configuration file for advanced copying options") .action(async (destination, sourceFiles, options) => { // Check if a configuration file is provided if (options.config) { const configFile = resolve(options.config); KavenLogger.Default.Info(`Loading configuration from file: ${configFile}`); // Check if the configuration file exists if (await IsFile(configFile)) { const config = await LoadJsonFile(configFile); // Process each entry in the configuration if (config.entries) { for (const entry of config.entries) { const override = entry.override ?? config.override ?? false; // Skip copying if override is disabled and destination exists if (!override && IsPathExistSync(entry.dest)) { continue; } // Copy files or directories based on entry type if (typeof entry.src === "string") { await CopyFileOrDirectory(entry.src, entry.dest); } else if (Array.isArray(entry.src)) { await CopyToDirectory(entry.src, entry.dest); } } } } } // Copy additional source files if provided if (sourceFiles && sourceFiles.length > 0) { await CopyToDirectory(sourceFiles, destination); } }); // rm /// ///////////////////////////////////////////////////////////////////////////////// program .command("rm <file> [otherFiles...]") .description("Remove files or directories") .option("-r, --recursive", "Remove directories and their contents recursively") .action(async (file, otherFiles, options) => { // Concatenate all files including the first one const allFiles = [file].concat(otherFiles); // If the recursive option is provided, delete files and directories recursively if (options.recursive) { await Delete(...allFiles); } else { // Iterate through the provided files and remove them individually for (const item of allFiles) { // Get file stats to determine if it's a directory or file const stats = await GetFileStats(item); // If stats are undefined, continue to the next item if (stats === undefined) { continue; } // Check if it's a directory and remove it using promises.rmdir if (stats.isDirectory()) { await promises.rmdir(item); } else { // If it's a file, remove it using promises.unlink await promises.unlink(item); } } } }); // rm-files /// ///////////////////////////////////////////////////////////////////////////////// program .command("rm-files <dir> <ext> [otherExtensions...]") .description("Remove files or directories") .option("--caseSensitive") .option("--ignore, --ignoreFolderNames [ignoreFolderNames...]", "Specify a folder name to exclude when searching") .action(async (dir, ext, otherExtensions, options) => { await DeleteFilesByExtension(dir, [ext].concat(otherExtensions), options.caseSensitive, options.ignoreFolderNames); }); // run /// ///////////////////////////////////////////////////////////////////////////////// program .command("run <command> [otherCommands...]") .description("Run multiple scripts concurrently or sequentially") .option("-s, --sequential", "Run commands sequentially") .option("-e, --encoding <encoding>", "Specify the decoding encoding using iconv-lite") .option("--spawn", "Use spawn instead of the default exec for command execution") .option("--shell [shell]", "Specify an alternative shell for command execution") .option("--win32commands <win32commands...>", "Specify an alternative execution command on Windows. This overrides the default command on Windows") .action(async (command, otherCommands, options) => { // Concatenate all commands including the first one const allCommands = (IsWin32 && options.win32commands) ? options.win32commands : [command].concat(otherCommands); const executeOptions = { decoderEncoding: options.encoding, spawn: !!options.spawn, spawnOptions: { shell: (typeof options.shell === "string" || typeof options.shell === "boolean") ? options.shell : undefined, }, execOptions: { shell: typeof options.shell === "string" ? options.shell : undefined, }, }; // If running sequentially, iterate through and run each command one by one if (options.sequential) { for (const c of allCommands) { await Execute(c, executeOptions); } } else { // If not running sequentially, execute all commands concurrently for (const c of allCommands) { Execute(c, executeOptions); } } }); // gen_cert /// ///////////////////////////////////////////////////////////////////////////////// program .command("gen_cert") .description("Generate SSL/TLS certificates for secure communication") .option("--dir [dir]", "Specify the target directory for certificate generation") .option("--bits [bits]", "Specify the key size in bits for the generated certificates") .option("--days [days]", "Specify the validity period in days for the generated certificates") .option("--cn [cn]", "Specify the common name (CN) for the certificate subject") .option("--openssl [openssl]", "Specify the location of the OpenSSL executable") .option("--verbose", "Enable verbose logging for detailed information") .action(async (options) => { // Disable console logging if verbose mode is not enabled if (!options.verbose) { KavenLogger.Default.EnableConsole = false; } // Configure certificate generation options based on command-line inputs const certOptions = {}; if (options.dir) { certOptions.certGenerateDir = options.dir; } else { certOptions.certGenerateDir = "./generated"; } if (options.bits) { certOptions.size = Number(options.bits); } if (options.days) { certOptions.days = Number(options.days); } if (options.cn) { certOptions.serverSubj = { CN: options.cn, }; } if (options.openssl) { certOptions.openssl = options.openssl; } // Generate the certificate and save the result to a JSON file const result = await GenerateCertificate(certOptions); result.Save(CombinePath(certOptions.certGenerateDir, "cert.json")); }); // proxy /// ///////////////////////////////////////////////////////////////////////////////// program .command("proxy [dirOrFile]") .description("Start a local proxy server for forwarding HTTP requests") .action(async (dirOrFile) => { try { // If no directory or file is provided, default to the current directory if (!dirOrFile) { dirOrFile = process.cwd(); } // Start the proxy server with the specified directory or file await StartProxy(dirOrFile); } catch (ex) { KavenLogger.Default.Error(ex); } }); // ip /// ///////////////////////////////////////////////////////////////////////////////// program .command("ip") .description("Retrieve the external IP address of the current network") .option("--UPnP", "Use UPnP to discover the external IP address") .option("--verbose", "Enable verbose mode for additional information") .action(async (options) => { // Destructure options for clarity const { UPnP, verbose } = options; // Set trace based on verbose option const trace = !!verbose; if (UPnP) { KavenLogger.Default.Info(await GetExternalIpByUPnP({ trace })); } else { KavenLogger.Default.Info(await GetExternalIp({ trace })); } }); // ci /// ///////////////////////////////////////////////////////////////////////////////// program .command("ci") .description("Run continuous integration for documentation updates") .option("--config <config>", "Specify a JSON configuration file for advanced options") .option("--variables <variables...>", "Variables to use in the format key:value") .action(async (options) => { let cwd = process.cwd(); let config = { sourceDocumentFileOrDirectory: "./docs", sourceVersionFile: "./dist/package.json", targetRootDirectory: "../Kaven-Documents", targetDirectoryName: basename(cwd), }; if (options.config) { const configFile = AppendPathToDirectory(cwd, options.config); if (IsPathExistSync(configFile)) { KavenLogger.Default.Info(`Loading configuration from file: ${configFile}`); const json = await LoadJsonFile(configFile); config = Object.assign(config, json); cwd = dirname(configFile); } else { throw new Error(`Config file not exists: ${configFile}`); } } if (options.variables && options.variables.length > 0) { config.variables = {}; for (const variable of options.variables) { const items = variable.split(":"); if (items.length === 2) { config.variables[items[0].trim()] = items[1].trim(); } } } KavenLogger.Default.Info(`Current Working Directory: ${cwd}`); config.sourceDocumentFileOrDirectory = AppendPathToDirectory(cwd, config.sourceDocumentFileOrDirectory); config.sourceVersionFile = AppendPathToDirectory(cwd, config.sourceVersionFile); config.targetRootDirectory = AppendPathToDirectory(cwd, config.targetRootDirectory); if (config.commands) { for (const command of config.commands) { if (command.options?.execOptions?.cwd) { command.options.execOptions.cwd = AppendPathToDirectory(cwd, command.options.execOptions.cwd); } } } config.updateReadmeFile ??= true; config.gitCommit ??= true; await ContinuousIntegrationForDocuments(config); }); // docker /// ///////////////////////////////////////////////////////////////////////////////// program .command("docker <server> [command]") .option("-u, --username <username>", "username") .option("-p, --password <password>", "password") .action(async (server, command, options) => { const { username, password } = options; const registry = new DockerRegistry(server, (username && password) ? { username, password } : undefined); if (command) { // TODO } else { const list = await registry.ListImageWithTags(); for (const item of list) { const log = new KavenLog(`${item.name}: ${item.tags.join(", ")}`, LogLevel.Info); log.ShowDateTime = false; log.ShowLevel = false; KavenLogger.Default.Log(log); } } }); // where /// ///////////////////////////////////////////////////////////////////////////////// program .command("where") .description("Show the location of the command") .action(() => { KavenLogger.Default.Log(KavenLog.CreateInfo(__filename).HideDateTime().HideLevel()); }); // program // .command("exec <cmd>") // .alias("ex") // .description("execute the given remote cmd") // .option("-e, --exec_mode <mode>", "Which exec mode to use") // .action((cmd, options) => { // KavenLogger.Default.Info('exec "%s" using %s mode', cmd, options.exec_mode); // }).on("--help", () => { // KavenLogger.Default.Info(""); // KavenLogger.Default.Info("Examples:"); // KavenLogger.Default.Info(""); // KavenLogger.Default.Info(" $ deploy exec sequential"); // KavenLogger.Default.Info(" $ deploy exec async"); // }); // minify /// ///////////////////////////////////////////////////////////////////////////////// program .command("minify [file] [otherFiles...]") .description("JavaScript minifier") .option("--config <config>", "Read or generate a JSON configuration file.") .action(async (file, otherFiles, options) => { let cwd = process.cwd(); // Concatenate all files including the first one const allFiles = []; if (file) { allFiles.push(file); } if (otherFiles && Array.isArray(otherFiles)) { allFiles.push(...otherFiles); } let minifyOptions = { trace: true, src: allFiles, deleteSourceMap: false, setSourceMappingURL: true, deleteTypeScriptDeclarationFile: true, includeNodeModules: false, terserOptions: { format: { ecma: 2015, comments: false, }, }, }; if (options.config) { const configFile = AppendPathToDirectory(cwd, options.config); if (IsPathExistSync(configFile)) { KavenLogger.Default.Info(`Loading configuration from file: ${configFile}`); const json = await LoadJsonFile(configFile); minifyOptions = Object.assign(minifyOptions, json); cwd = dirname(configFile); process.chdir(cwd); } else { KavenLogger.Default.Info(`Generate configuration file: ${configFile}`); await SaveJsonConfig(minifyOptions, configFile); return; } } await Minify(minifyOptions); }); program .command("*", { hidden: true, }) .action((env) => { KavenLogger.Default.Error(`error: unknown command '${env}'`); program.outputHelp(); }); program.parse(process.argv); }).catch((err => { KavenLogger.Default.Error(err); process.exit(-1); }));