UNPKG

3.25 kBJavaScriptView Raw
1import { exec as cpExec, spawn as cpSpawn } from "child_process";
2import { lstatSync, readdirSync } from "fs";
3import { lstat, readdir } from "fs/promises";
4import { join } from "path";
5import { pipeline } from "stream";
6import { promisify } from "util";
7
8const internalExec = promisify(cpExec);
9const internalPipeline = promisify(pipeline);
10
11export { join as pathJoin };
12
13/**
14 * @callback Exec
15 * @param {string} command
16 * @param {ExecOptions} [opts={}]
17 * @returns {Promise<{stdout: string, stderr: string, exitCode: number}>}
18 */
19export async function exec(command, opts = {}) {
20 try {
21 const promise = internalExec(command, { encoding: "utf8", ...opts });
22 const { stdout, stderr } = await promise;
23
24 return {
25 stdout,
26 stderr,
27 exitCode: promise.child.exitCode ?? 0,
28 };
29 } catch (e) {
30 return {
31 stdout: e.stdout ?? "",
32 stderr: e.stderr ?? "",
33 exitCode: e.code ?? 1,
34 };
35 }
36}
37
38/**
39 * @param {string} command
40 * @param {string[]} args
41 * @param {object} [opts={}]
42 * @returns {Promise<{exitCode: number}>}
43 */
44export function spawn(command, args, opts = {}) {
45 return new Promise((resolve, reject) => {
46 const sp = cpSpawn(command, args, { stdio: "inherit", ...opts });
47
48 sp.once("error", reject);
49 sp.once("exit", (code) => {
50 resolve({ exitCode: code ?? 0 });
51 });
52 });
53}
54
55/**
56 * Read a readable stream completely, and return as Buffer
57 * @param {ReadableStream} stream
58 * @returns {Promise<Buffer>}
59 */
60export async function streamToBuffer(stream) {
61 const buffers = [];
62 await internalPipeline(stream, async function* (transform) {
63 for await (const chunk of transform) {
64 buffers.push(chunk);
65 yield chunk;
66 }
67 });
68
69 return Buffer.concat(buffers);
70}
71
72/**
73 * @param {string} dir
74 * @param {Function} cb
75 * @param {ProcessDirectoryOptions} [opts]
76 */
77export async function processDirectoryRecursive(
78 dir,
79 cb,
80 opts = {
81 skipDotFiles: true,
82 skipNodeModules: true,
83 },
84) {
85 for (const file of await readdir(dir, { encoding: "utf8" })) {
86 if (opts.skipNodeModules && file === "node_modules") {
87 continue;
88 }
89 if (opts.skipDotFiles && file[0] === ".") {
90 continue;
91 }
92
93 const newPath = join(dir, file);
94 const stat = await lstat(newPath);
95 if (stat.isDirectory()) {
96 await processDirectoryRecursive(newPath, cb, opts);
97 } else if (stat.isFile()) {
98 await Promise.resolve(cb(newPath));
99 }
100 }
101}
102
103/**
104 * Sync version of processDirectoryRecursive
105 *
106 * @param {string} dir
107 * @param {Function} cb
108 * @param {object} [opts={}]
109 * @param {boolean} [opts.skipNodeModules=true]
110 * @param {boolean} [opts.skipDotFiles=true]
111 */
112export function processDirectoryRecursiveSync(
113 dir,
114 cb,
115 opts = {
116 skipDotFiles: true,
117 skipNodeModules: true,
118 },
119) {
120 for (const file of readdirSync(dir, { encoding: "utf8" })) {
121 if (opts.skipNodeModules && file === "node_modules") {
122 continue;
123 }
124 if (opts.skipDotFiles && file[0] === ".") {
125 continue;
126 }
127
128 const newPath = join(dir, file);
129 const stat = lstatSync(newPath);
130 if (stat.isDirectory()) {
131 processDirectoryRecursiveSync(newPath, cb, opts);
132 } else if (stat.isFile()) {
133 cb(newPath);
134 }
135 }
136}