Building a Node.js CLI Tool for Automated Command Execution and File Watching
This article provides a comprehensive guide to developing a lightweight Node.js command-line interface tool that automates repetitive compilation tasks, supports configuration-driven execution, implements file watching with debouncing, and integrates seamlessly with legacy frontend projects and IDEs like WebStorm.
The article addresses the inefficiency of manually compiling preprocessed files like CSS and TypeScript in legacy frontend projects, proposing a lightweight Node.js CLI tool as an alternative to heavy bundlers. It begins by detailing how to parse command-line arguments and execute single or sequential shell commands using Node's child_process module.
Advanced capabilities are introduced, including lifecycle hooks for pre- and post-execution tasks, recursive directory traversal with regex matching, and a dynamic template replacement system that injects variables like $FilePath$ and $FileName$ into commands. Configuration files are used to define execution rules, exclusions, and custom JavaScript callbacks.
Real-time file monitoring is implemented using Node's fs.watch API, with custom logic to handle cross-platform directory tracking and prevent infinite loops. To address rapid editor saves triggering multiple events, a custom debouncePromise utility is developed to cancel pending asynchronous executions and only run the final command.
Additional utilities such as regex-based file searching, opening files in system explorers or terminals, and CLI help documentation are integrated. The guide concludes with steps to publish the package to npm for global installation and demonstrates seamless integration with WebStorm's File Watcher to automate Less and PostCSS compilation.
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
);
} async function mulExec(command: string[]) {
for (const cmd of command) {
await execute(cmd);
}
} export function debouncePromise
Promise
>(callback: CB, delay: number): CB {
let timer: any = null;
let rej: Function;
return function (this: unknown, ...args: any[]) {
return new Promise
((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;
}ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.