Node.js + typescript 寫一個命令批處理輔助工具
共 47308字,需瀏覽 95分鐘
·
2024-08-05 09:15
點擊上方 前端Q,關(guān)注公眾號
回復(fù)加群,加入前端Q技術(shù)交流群
工作中遇到這樣一些場景:在php混合html的老項目中寫css,但是css寫著不太好用,然后就想使用預(yù)編譯語言來處理,或者寫上ts。然后問題來了: 每次寫完以后都要手動執(zhí)行一次命令行把文件編譯成css文件,然后又要再輸入一行命令把css壓縮添加前綴;或者把ts編譯成js,然后js壓縮混淆。
那么有沒有辦法不用手動輸入命令行呢?如果只是為了不手動輸入的話,那么可以在vscode上安裝compile hero插件,或者在webstorm上開啟file watch功能。可惜的是這些工具或功能只能對當(dāng)前文件做處理,處理編譯后的文件又要手動去執(zhí)行命令,不能連續(xù)監(jiān)聽或監(jiān)聽一次執(zhí)行多個命令,比如webstorm的file watch監(jiān)聽了sass文件變化, 那么它不能再監(jiān)聽css變化去壓縮代碼,否則會無限編譯下去。
那么為什么不使用webpack或者rollup之類的打包工具呢?首先是這些打包工具太重了不夠靈活,畢竟原項目沒到重構(gòu)的時候, 要想使用新一點的技術(shù),那么只能寫一點手動編譯一點了。
好在這些預(yù)編譯語言都提供cli工具可在控制臺輸入命令行編譯,那么完全可以把它們的命令關(guān)聯(lián)起來,做一個批量執(zhí)行的工具。其實shell腳本也可以完成這些功能, 但是其一:shell在windows上的話只能在git bash里運行,在cmd控制臺上不能運行,需要專門打開一個git bash,少了一點便利性;其二:在windows上不能監(jiān)聽文件變化。那么既然nodejs能夠勝任,那么用前端熟悉的js做那是再好不過了。
2.目標
-
基礎(chǔ)功能 -
通過控制臺輸入指令啟動:獲取控制臺輸入的命令 -
運行命令 -
運行多個命令 -
通過指定配置文件執(zhí)行 -
進階功能 -
前后生命周期 -
遍歷文件夾查找匹配運行- url模板替換- 執(zhí)行配置中的命令- 執(zhí)行配置中的js -
監(jiān)聽文件改動 -
可通過指令顯示隱藏log -
可通過指令顯示隱藏運行時間 -
npm全局一次安裝,隨處執(zhí)行 -
額外功能 -
搜索文件或文件夾- 忽略大小寫- 忽略文件夾 -
幫助功能 -
打開文件- 直接運行文件- 在打開資源管理器并選中目標文件- 在cmd控制臺打開對應(yīng)的路徑 -
配置 -
依次執(zhí)行多個命令; -
生命周期回調(diào) -
忽略文件夾 -
匹配規(guī)則- 匹配成功- 執(zhí)行相應(yīng)命令;- 執(zhí)行相應(yīng)js;
ok,那么接下來進入正文吧(源碼見底部github鏈接)。
3.基本功能
1.獲取控制臺輸入的命令
首先是獲取到控制臺輸入的命令,這里抽取出來做為一個工具函數(shù)。格式為以"="隔開的鍵值對,鍵名以"-"開頭,值為空時設(shè)置該值為true,變量之間用空格隔開。
// util.ts
/**
* 獲取命令行的參數(shù)
* @param prefix 前綴
*/
export function getParams(prefix = "-"): { [k: string]: string | true } {
return process.argv.slice(2).reduce((obj, it) => {
const sp = it.split("=");
const key = sp[0].replace(prefix, "");
obj[key] = sp[1] || true;
return obj;
}, {} as ReturnType<typeof getParams>);
}
調(diào)用
console.log(getParams());
運行結(jié)果
2.運行單個命令
能獲取到命令行參數(shù)那就好辦了,接下來實現(xiàn)執(zhí)行命令功能。
先實現(xiàn)一個簡單的執(zhí)行命令函數(shù),這要用到child_process模塊里的exec函數(shù)。
const util = require("util");
const childProcess = require('child_process');
const exec = util.promisify(childProcess.exec); // 這里把exec promisify
需要知道執(zhí)行狀態(tài),所以把它封裝一下,不能try catch,出錯就直接reject掉,避免后面的命令繼續(xù)執(zhí)行。
async function execute(cmd: string): Promise<string> {
console.log('執(zhí)行"' + cmd + '"命令...');
const {stdout} = await exec(cmd);
console.log('success!');
console.log(stdout);
return stdout;
}
設(shè)定命令參數(shù)為-command,且必須用”” ““包起來,多個則用“,”隔開
在工具中通過-command/-cmd=啟用
調(diào)用
const args = getParams();
execute(args.command as string);
運行
3.運行多個命令
現(xiàn)在運行單個命令是沒問題的,但是運行多個命令呢?
看結(jié)果可以發(fā)現(xiàn):結(jié)果馬上就報錯了,把它改成順序執(zhí)行
async function mulExec(command: string[]) {
for (const cmd of command) {
await execute(cmd);
}
}
運行
mulExec((args.command as string).split(","));
4.通過指定配置文件運行命令
在工具中通過-config/-c=設(shè)置配置的路徑
這樣通過命令行命令,執(zhí)行相應(yīng)的功能就完成了,但是可能會有情況下是要運行很多條命令的,每次都輸入一長串命令就不那么好了,所以要添加一個通過配置文件執(zhí)行的功能。
首先是定義配置文件格式。先來個最簡單的
export interface ExecCmdConfig{
command: string[]; // 直接執(zhí)行命令列表
}
定義一下命令行配置文件變量名為-config
-config= 配置的路徑
例如:cmd-que -config="test/cmd.config.js"
配置文件 test/cmd.config.js
module.exports = {
command: [
"stylus E:\\project\\cmd-que\\test\\test.styl",
"stylus test/test1.styl",
]
};
加載配置文件
const Path = require("path");
const configPath = Path.resolve(process.cwd(), args.config);
try {
const config = require(configPath);
mulExec(config.command);
} catch (e) {
console.error("加載配置文件出錯", process.cwd(), configPath);
}
運行
搞定
4.進階功能
到這里,一個簡單的命令批量執(zhí)行工具代碼就已經(jīng)基本完成了。但是需求總是會變的。
1.前后生命周期
為什么要添加生命周期?因為編譯pug文件總是需要在編譯完js、css之后,不可能總是需要手動給pug編譯命令加上debounce,所以加上結(jié)束的回調(diào)就很有必要了。
生命周期回調(diào)函數(shù)類型:
type execFn = (command: string) => Promise<string>;
export interface Config {
beforeStart: (exec: execFn) => Promise<unknown> | unknown;
beforeEnd: (exec: execFn) => Promise<unknown> | unknown;
}
代碼
const Path = require("path");
const configPath = Path.resolve(process.cwd(), args.config);
try {
const config = require(configPath);
// beforeStart調(diào)用
if (config.beforeStart) await config.beforeStart(execute);
await mulExec(config.command);
// beforeEnd調(diào)用
config.beforeEnd && config.beforeEnd(execute);
} catch (e) {
console.error("加載配置文件出錯", process.cwd(), configPath);
}
配置文件
cmd.config.js
module.exports = {
beforeStart() {
console.time("time");
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("start");
resolve();
}, 1000);
});
},
beforeEnd() {
console.log("end");
console.timeEnd("time");
},
command: [
// "stylus D:\\project\\cmd-que\\test\\test.styl",
"stylus E:\\project\\cmd-que\\test\\test.styl",
"stylus test/test1.styl",
]
};
運行
2. 遍歷文件夾查找匹配運行
到現(xiàn)在,如果只是執(zhí)行確定的命令,那么已經(jīng)完全沒問題了,但是有時候需要編譯的文件會有很多,像stylus、pug這些可以直接編譯整個文件夾的還好, 像ts的話就只能一個文件寫一條命令,那也太麻煩了。
所以得增加一個需求:遍歷文件夾查找目標文件, 然后執(zhí)行命令的功能。
寫一個遍歷文件夾的函數(shù):
// util.ts
const fs = require("fs");
const Path = require("path");
/**
* 遍歷文件夾
* @param path
* @param exclude
* @param cb
* @param showLog
*/
export async function forEachDir(
path: string,
exclude: RegExp[] = [],
cb?: (path: string, basename: string, isDir: boolean) => true | void | Promise<true | unknown>,
showLog = false,
) {
showLog && console.log("遍歷", path);
try {
const stats = fs.statSync(path);
const isDir = stats.isDirectory();
const basename = Path.basename(path);
const isExclude = () => {
const raw = String.raw`${path}`;
return exclude.some((item) => item.test(raw));
};
if (isDir && isExclude()) return;
const callback = cb || ((path, isDir) => undefined);
const isStop = await callback(path, basename, isDir);
if (!isDir || isStop === true) {
return;
}
const dir = fs.readdirSync(path);
for (const d of dir) {
const p = Path.resolve(path, d);
await forEachDir(p, exclude, cb, showLog);
}
} catch (e) {
showLog && console.log("forEachDir error", path, e);
// 不能拋出異常,否則遍歷到System Volume Information文件夾報錯會中斷遍歷
// return Promise.reject(e);
}
}
然后正則驗證文件名,如果符合就執(zhí)行命令
forEachDir("../test", [], (path, basename, isDir) => {
if (isDir) return;
const test = /\.styl$/;
if (!test.test(basename)) return;
return execute("stylus " + path);
});
運行
3.通過配置遍歷文件夾
url模板替換
看上面的執(zhí)行情況可以看出,執(zhí)行的每一條命令路徑都是具體的,但是如果我們要遍歷文件夾執(zhí)行命令的話那么這樣就不夠用了。因為命令都是字符形式的無法根據(jù)情況改變,那么有兩種方法解決這樣的情況:
1.使用字符串模板替換掉對應(yīng)的字符
2.使用js執(zhí)行,根據(jù)傳回的字符來替換掉對應(yīng)的字符,再執(zhí)行命令
現(xiàn)在實現(xiàn)一個模板替換的功能(模板來源于webstorm上的file watcher功能,有所增減)
export function executeTemplate(command: string, path = "") {
const cwd = process.cwd();
path = path || cwd;
const basename = Path.basename(path);
const map: { [k: string]: string } = {
"\\$FilePath\\$": path, // 文件完整路徑
"\\$FileName\\$": basename, // 文件名
"\\$FileNameWithoutExtension\\$": basename.split(".").slice(0, -1).join("."), // 不含文件后綴的路徑
"\\$FileNameWithoutAllExtensions\\$": basename.split(".")[0], // 不含任何文件后綴的路徑
"\\$FileDir\\$": Path.dirname(path), // 不含文件名的路徑
"\\$Cwd\\$": cwd, // 啟動命令所在路徑
"\\$SourceFileDir\\$": __dirname, // 代碼所在路徑
};
const mapKeys = Object.keys(map);
command = mapKeys.reduce((c, k) => c.replace(new RegExp(k, "g"), map[k]), String.raw`${command}`);
return execute(command);
}
配置文件格式最終版如下:
type execFn = (command: string) => Promise<string>;
/**
* @param eventName watch模式下觸發(fā)的事件名
* @param path 觸發(fā)改動事件的路徑
* @param ext 觸發(fā)改動事件的文件后綴
* @param exec 執(zhí)行命令函數(shù)
*/
type onFn = (eventName: string, path: string, ext: string, exec: execFn) => Promise<void>
type Rule = {
test: RegExp,
on: onFn,
command: string[];
};
export type RuleOn = Omit<Rule, "command">;
type RuleCmd = Omit<Rule, "on">;
export type Rules = Array<RuleOn | RuleCmd>;
export interface Config {
beforeStart: (exec: execFn) => Promise<unknown> | unknown;
beforeEnd: (exec: execFn) => Promise<unknown> | unknown;
}
export interface ExecCmdConfig extends Config {
command: string[]; // 直接執(zhí)行命令列表 占位符會被替換
}
export interface WatchConfig extends Config {
exclude?: RegExp[]; // 遍歷時忽略的文件夾
include?: string[] | string; // 要遍歷/監(jiān)聽的文件夾路徑 // 默認為當(dāng)前文件夾
rules: Rules
}
export function isRuleOn(rule: RuleOn | RuleCmd): rule is RuleOn {
return (rule as RuleOn).on !== undefined;
}
實現(xiàn)
import {getParams, mulExec, forEachDir, executeTemplate} from "../src/utils";
import {isRuleOn, Rules} from "../src/configFileTypes";
(async function () {
// 獲取命令行參數(shù)
const args = getParams();
// 匹配正則
async function test(eventName: string, path: string, basename: string, rules: Rules = []) {
for (const rule of rules) {
if (!rule.test.test(basename)) continue;
if (isRuleOn(rule)) {
await rule.on(
eventName,
path,
Path.extname(path).substr(1),
(cmd: string) => executeTemplate(cmd, path),
);
} else {
await mulExec(rule.command, path);
}
}
}
// 遍歷文件夾
function foreach(
path: string,
exclude: RegExp[] = [],
cb: (path: string, basename: string, isDir: boolean) => true | void | Promise<true | void>,
) {
return forEachDir(path, exclude, (path: string, basename: string, isDir: boolean) => {
return cb(path, basename, isDir);
});
}
const Path = require("path");
const configPath = Path.resolve(process.cwd(), args.config);
try {
const config = require(configPath);
// beforeStart調(diào)用
if (config.beforeStart) await config.beforeStart(executeTemplate);
const include = config.include;
// 設(shè)置默認路徑為命令啟動所在路徑
const includes = include ? (Array.isArray(include) ? include : [include]) : ["./"];
const rules = config.rules;
for (const path of includes) {
await foreach(path, config.exclude, (path, basename) => {
return test("", path, basename, rules);
});
}
// beforeEnd調(diào)用
config.beforeEnd && config.beforeEnd(executeTemplate);
} catch (e) {
console.error("加載配置文件出錯", process.cwd(), configPath);
}
})();
執(zhí)行配置中的命令
配置文件如下:
// test-cmd.config.js
module.exports = {
exclude: [
/node_modules/,
/\.git/,
/\.idea/,
],
rules: [
{
test: /\.styl$/,
command: [
"stylus <$FilePath$> $FileDir$\\$FileNameWithoutAllExtensions$.wxss",
"node -v"
]
}
]
};
運行結(jié)果
執(zhí)行配置中的js
module.exports = {
beforeEnd(exec) {
return exec("pug $Cwd$")
},
exclude: [
/node_modules/,
/\.git/,
/\.idea/,
/src/,
/bin/,
],
include: ["./test"],
rules: [
{
test: /\.styl$/,
on: async (eventName, path, ext, exec) => {
if (eventName === "delete") return;
const result = await exec("stylus $FilePath$");
console.log("on", result);
}
},
{
test: /\.ts$/,
on: (eventName, path, ext, exec) => {
if (eventName === "delete") return;
return exec("tsc $FilePath$");
}
},
]
};
運行結(jié)果
4.監(jiān)聽文件變動
在工具中通過-watch/-w開啟 需要與-config搭配使用
監(jiān)聽文件變動nodejs提供了兩個函數(shù)可供調(diào)用:
-
fs.watch(filename[, options][, listener])
-
filename <string> | <Buffer> | <URL> -
options <string> | <Object>- persistent <boolean> 指示如果文件已正被監(jiān)視,進程是否應(yīng)繼續(xù)運行。默認值: true。- recursive <boolean> 指示應(yīng)該監(jiān)視所有子目錄,還是僅監(jiān)視當(dāng)前目錄。這適用于監(jiān)視目錄時,并且僅適用于受支持的平臺(參見注意事項)。默認值: false。- encoding <string> 指定用于傳給監(jiān)聽器的文件名的字符編碼。默認值: 'utf8'。 -
listener <Function> | <undefined> 默認值: undefined。- eventType <string>- filename <string> | <Buffer> -
返回: <fs.FSWatcher>
監(jiān)視 filename 的更改,其中 filename 是文件或目錄。
2. fs.watchFile(filename[, options], listener)
filename <string> | <Buffer> | <URL> options <Object>
bigint <boolean> 默認值: false。 persistent <boolean> 默認值: true。 interval <integer> 默認值: 5007。 listener <Function>
current <fs.Stats> previous <fs.Stats> Returns: <fs.StatWatcher>
監(jiān)視 filename 的更改。每當(dāng)訪問文件時都會調(diào)用 listener 回調(diào)。
因為watchFile必須監(jiān)聽每個文件,所以選watch函數(shù)
文檔顯示options的recursive參數(shù)為true時 監(jiān)視所有子目錄
但是文檔又說
僅在 macOS 和 Windows 上支持 recursive 選項。當(dāng)在不支持該選項的平臺上使用該選項時,則會拋出 ERR_FEATURE_UNAVAILABLE_ON_PLATFORM 異常。
在 Windows 上,如果監(jiān)視的目錄被移動或重命名,則不會觸發(fā)任何事件。當(dāng)監(jiān)視的目錄被刪除時,則報告 EPERM 錯誤。
所以我這里在判斷子文件是否文件夾后,需要手動添加監(jiān)聽子文件夾
import {getParams, mulExec, forEachDir, executeTemplate, debouncePromise} from "../src/utils";
import {isRuleOn, RuleOn, Rules, WatchConfig} from "../src/configFileTypes";
(async function () {
// 獲取命令行參數(shù)
const args = getParams();
/**
* @param config 配置
* @param watchedList watch列表用于遍歷文件夾時判斷是否已經(jīng)watch過的文件夾
*/
async function watch(config: WatchConfig, watchedList: string[]) {
if (!config.rules) throw new TypeError("rules required");
// 編輯器修改保存時會觸發(fā)多次change事件
config.rules.forEach(item => {
// 可能會有機器會慢一點 如果有再把間隔調(diào)大一點
(item as RuleOn).on = debouncePromise(isRuleOn(item) ? item.on : (e, p) => {
return mulExec(item.command, p);
}, 1);
});
const FS = require("fs");
const HandleForeach = (path: string) => {
if (watchedList.indexOf(path) > -1) return;
console.log("對" + path + "文件夾添加監(jiān)聽\n");
const watchCB = async (eventType: string, filename: string) => {
if (!filename) throw new Error("文件名未提供");
const filePath = Path.resolve(path, filename);
console.log(eventType, filePath);
// 判斷是否需要監(jiān)聽的文件類型
try {
const exist = FS.existsSync(filePath);
await test(exist ? eventType : "delete", filePath, filename);
if (!exist) {
console.log(filePath, "已刪除!");
// 刪除過的需要在watchArr里面去掉,否則重新建一個相同名稱的目錄不會添加監(jiān)聽
const index = watchedList.indexOf(filePath);
if (index > -1) {
watchedList.splice(index, 1);
}
return;
}
// 如果是新增的目錄,必須添加監(jiān)聽否則不能監(jiān)聽到該目錄的文件變化
const stat = FS.statSync(filePath);
if (stat.isDirectory()) {
foreach(filePath, config.exclude, HandleForeach);
}
} catch (e) {
console.log("watch try catch", e, filePath);
}
};
const watcher = FS.watch(path, null, watchCB);
watchedList.push(path); // 記錄已watch的
watcher.addListener("error", function (e: any) {
console.log("addListener error", e);
});
};
const include = config.include;
const includes = include ? (Array.isArray(include) ? include : [include]) : ["./"];
for (const path of includes) {
await foreach(path, config.exclude, (path, basename, isDir) => {
if (isDir) HandleForeach(path);
});
}
}
// 匹配正則
async function test(eventName: string, path: string, basename: string, rules: Rules = []) {
for (const rule of rules) {
if (!rule.test.test(basename)) continue;
if (isRuleOn(rule)) {
await rule.on(
eventName,
path,
Path.extname(path).substr(1),
(cmd: string) => executeTemplate(cmd, path),
);
} else {
await mulExec(rule.command, path);
}
}
}
// 遍歷文件夾
function foreach(
path: string,
exclude: RegExp[] = [],
cb: (path: string, basename: string, isDir: boolean) => true | void | Promise<true | void>,
) {
return forEachDir(path, exclude, (path: string, basename: string, isDir: boolean) => {
return cb(path, basename, isDir);
});
}
const Path = require("path");
const configPath = Path.resolve(process.cwd(), args.config);
try {
const config = require(configPath);
// beforeStart調(diào)用
if (config.beforeStart) await config.beforeStart(executeTemplate);
await watch(config, []);
// beforeEnd調(diào)用
config.beforeEnd && config.beforeEnd(executeTemplate);
} catch (e) {
console.error("加載配置文件出錯", process.cwd(), configPath);
}
})();
配置文件
// watch-cmd.config.js
module.exports = {
beforeEnd() {
console.log("end")
},
rules: [
{
test: /\.styl$/,
command: [
"stylus $FilePath$",
"node -v"
]
},
],
exclude: [
/node_modules/,
/\.git/,
/\.idea/,
/src/,
/bin/,
],
include: ["./test"],
};
運行
當(dāng)我改動文件時
從結(jié)果可以看出,文件watch回調(diào)觸發(fā)了多次。其實我們不用編輯器改動文件的話,回調(diào)只會觸發(fā)一次,這是編輯器的問題。
那么細心的讀者可能會想到為什么命令不會執(zhí)行多次呢?
是因為我用debouncePromise把rule.on包裹了一層。
普通的防抖函數(shù)是這樣的
export function debounce<CB extends (...args: any[]) => void>(callback: CB, delay: number): CB {
let timer: any = null;
return function (...args: any[]) {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
timer = null;
callback.apply(this, args);
}, delay);
} as CB;
}
但是這種沒辦法處理原函數(shù)返回promise的情況,也沒辦法await
所以要改造一下,讓它可以處理promise:每次在間隔內(nèi)執(zhí)行的時候,都把上一次的promise reject掉
export function debouncePromise<T, CB extends (...args: any[]) => Promise<T>>(callback: CB, delay: number): CB {
let timer: any = null;
let rej: Function;
return function (this: unknown, ...args: any[]) {
return new Promise<T>((resolve, reject) => {
if (timer) {
clearTimeout(timer);
timer = null;
rej("debounce promise reject");
}
rej = reject;
timer = setTimeout(async () => {
timer = null;
const result = await callback.apply(this, args);
resolve(result);
}, delay);
});
} as CB;
}
加到邏輯上
為什么不加到watch的回調(diào)上,則是因為部分瀏覽器最后保存的是目標文件的副本,如果加到watch回調(diào)上的話,那就會漏掉目標文件變動了
這樣就雖然還是會觸發(fā)多次監(jiān)聽回調(diào),但只執(zhí)行最后一次回調(diào)。
5.額外功能
1.幫助功能
在工具中通過-help/-h啟動
console.log(`
-config/-c= 配置的路徑
-help/-h 幫助
-search/-s= 搜索文件或文件夾
-search-flag/-sf= 搜索文件或文件夾 /\\w+/flag
-search-exclude/-se= 搜索文件或文件夾 忽略文件夾 多個用逗號(,)隔開
-open/-o= 打開資源管理器并選中文件或文件夾
-open-type/-ot= 打開資源管理器并選中文件或文件夾
-watch/-w 監(jiān)聽文件改變 與-config搭配使用
-log 遍歷文件夾時是否顯示遍歷log
-time/t 顯示執(zhí)行代碼所花費的時間
-command/-cmd= 通過命令行執(zhí)行命令 多個則用逗號(,)隔開 必須要用引號引起來
`);
2.搜索文件或文件夾
在工具中通過-search/-s啟動
其實這功能和我這工具相關(guān)性不大,為什么會加上這樣的功能呢?是因為windows上搜索文件,經(jīng)常目標文件存在都搜索不到,而且這工具遍歷文件夾已經(jīng)很方便了,所以就把搜索文件功能集成到這個工具上了
實現(xiàn)
import {getParams, forEachDir} from "../src/utils";
const args = getParams()
const search = args.search;
const flag = args["search-flag"];
const se = args["search-exclude"];
if (search === true || search === undefined || flag === true || se === true) {
throw new TypeError();
}
const reg = new RegExp(search, flag);
console.log("search", reg);
const exclude = se?.split(",").filter(i => i).map(i => new RegExp(i));
forEachDir("./", exclude, (path, basename) => {
if (reg.test(basename)) console.log("result ", path);
});
忽略大小寫
在工具中-search-flag/-sf=
未忽略大小寫
忽略大小寫
忽略文件夾
在工具中-search-exclude/-se=
3.打開文件功能
搜索到文件之后,自然是要打開文件了(只支持windows)
工具中通過-open/o=打開對應(yīng)的文件
代碼
import {getParams} from "../src/utils";
const Path = require("path")
enum OpenTypes {
select = "select",
cmd = "cmd",
run = "run",
}
type ExecParams = [string, string[]];
const args = getParams();
const open = args.open;
const path = Path.resolve(process.cwd(), open === true ? "./" : open);
const stat = require("fs").statSync(path);
const isDir = stat.isDirectory();
const ot = args["open-type"];
const type: string = !ot || ot === true ? OpenTypes.select : ot;
const spawnSync = require('child_process').spawnSync;
const match: { [k in OpenTypes]: ExecParams } = {
// 運行一次就會打開一個資源管理器,不能只打開一個相同的
[OpenTypes.select]: ["explorer", [`/select,"${path}"`]],
[OpenTypes.run]: ['start', [path]],
[OpenTypes.cmd]: ["start", ["cmd", "/k", `"cd ${isDir ? path : Path.dirname(path)}"`]],
};
const exec = ([command, path]: ExecParams) => spawnSync(command, path, {shell: true});
console.log(path);
exec(match[type as OpenTypes] || match[OpenTypes.select]);
打開資源管理器并且選中文件
命令
結(jié)果
在cmd中打開
命令
結(jié)果
用默認app打開
命令
結(jié)果
上傳到npm
接下來就把它發(fā)布到npm上,到時候全局安裝后就可以在任意路徑上運行了
發(fā)布
安裝
npm i -g @mxssfd/cmd-que
測試
配合webstorm file watcher自動編譯less并postcss編譯
-
首先安裝cmd-que
-
開啟file watcher
-
新建less文件
-
修改less文件
-
結(jié)果
這樣配置好以后,每次修改文件就不用手動開啟命令而是會自動執(zhí)行編譯命令了
最后
寫到這里,功能總算完成了,其實再叫做命令隊列執(zhí)行工具已經(jīng)有點超綱了,不過常用功能還是用于執(zhí)行命令的
git地址
https://github.com/mengxinssfd/cmd-que
作者:用戶名還沒想好
https://juejin.cn/post/6930565860348461063
往期推薦
最后
歡迎加我微信,拉你進技術(shù)群,長期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認真學(xué)前端,做個專業(yè)的技術(shù)人...
