node 处理文件及执行npm命令

115 阅读3分钟

我有一个多包组件库,希望在修改某个包后可以将该包的版本+1并执行build命令,接着执行pack命令,将文件复制到特定的文件夹中。

根目录package.json执行的命令

  "scripts": {
    "tgz": "node ./run_script/index.js"
  },

在run_script/index.js

const readDirFn = require("./readDir.js");
// npm run tgz 启动函数
readDirFn();

在readDir.js

const path = require("path");
const fs = require("fs");
const { promisify } = require("util");

// 要读取的目录
const pkgPath = path.join(__dirname, '../packages');
// promise 包装
const readDir = promisify(fs.readdir);
const stat = promisify(fs.stat);
// utils
const checkboxPrompt = require('./prompt.js');
const startPack = require("./startPack.js");

/**
 * 读区文件目录
 */
async function readDirFn() {
    try {
        const dirs = await readDir(pkgPath);
        // 去掉系统文件的干扰项
        const filterDirs = dirs.filter(d => !d?.startsWith?.('.'));
        // 多选交互
        const answers = await checkboxPrompt(filterDirs);
        if (answers?.length) {
            console.log('\x1B[36m%s\x1B[0m', '---> npm pack正在执行🚀... <---');
            // 对选择的目录开始打包
            for (dir of answers) {
                const dirPath = path.join(pkgPath, dir);
                const dirInfo = await stat(dirPath);
                const isFileDir = dirInfo?.isDirectory();
                if (isFileDir) {
                    await startPack(dirPath);
                }
            }
            console.log('\x1B[32m', '---> npm pack执行完成✅ <---');
        } else {
            console.log('\x1B[31m%s\x1B[0m', '---> 未选择任何npm pack的包🙅 <---');
        }
    } catch (error) {
        console.log('readDirFn function error', error);
    } finally {
        process.exit();
    }
}

module.exports = readDirFn;

promisify: 取自node util核心库,作用是将异部函数转换成可以使用await的形式,node自带不需要npm安装
stat:读取文件的基本信息,可以判断该文件是否是一个文件夹。
for of: 在for of中支持使用await,其他的for循环,如果包在async函数内也支持使用await

在prompt.js

const inquirer = require("inquirer");
const { CHECKBOX_KEY } = require('./constants.js');

/**
 * 多选prompt交互
 */
async function checkboxPrompt(filterDirs = []) {
    try {
        const answer = await inquirer.prompt([  
            {
                type: 'checkbox',
                name: CHECKBOX_KEY,
                message: '请选择npm pack构建的包:',
                loop: false,
                choices: filterDirs,
            }
        ]);
        return answer[CHECKBOX_KEY] || [];
    } catch (error) {
        console.log('checkboxPrompt function error', error);
    }
}

module.exports = checkboxPrompt;

inquirer: 处理命令行交互

startPack.js


const path = require("path");
const fs = require("fs");
const { promisify } = require("util");
const { exec } = require("child_process");

// promise 包装
const mkdir = promisify(fs.mkdir);
const execShell = promisify(exec);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
// lib
const libPath = path.join(process.cwd(), 'lib');

/**
 * cd && 开始打包
 * @param {*} dirPath 目标源代码文件目录
 */
async function startPack(dirPath, isUpgrade, isPack) {
    try {
        // 切换目录
        process.chdir(dirPath);
        // lib目录是否存在
        const isLibExist = fs.existsSync(libPath);
        if (!isLibExist) {
            await mkdir(libPath);
        }
        if (isUpgrade) {
          const packageJsonPath = path.join(process.cwd(), 'package.json');
          // 版本号+1
          await modifyPackageJson(packageJsonPath);
        }
       

        await buildAndPack(isPack);
    } catch (error) {
        console.log('startPack function error', error);
    }
}

/**
 * package.json文件修改,入参文件地址
 */
async function modifyPackageJson(dirPath) {
    try {
        const data = await readFile(dirPath, 'utf8');
        const packageJson = JSON.parse(data);
        const versionComponents = packageJson.version.split('.');
    if (versionComponents.length === 3) {
      versionComponents[2] = parseInt(versionComponents[2], 10) + 1; // 递增修订号
      packageJson.version = versionComponents.join('.');
    }

    // 将修改后的对象转换回 JSON 字符串并写入文件
    const updatedPackageJsonContent = JSON.stringify(packageJson, null, 2);
    await writeFile(dirPath, updatedPackageJsonContent,'utf8');
     
    } catch (error) {
        console.log('modifyPackageJson function error', error);
    }
}

async function buildAndPack(isPack) {
    try {
      // 执行 npm run build 并实时打印输出
      const buildProcess = execShell('npm run build');
  
      // 同时打印标准输出和标准错误流到控制台
      buildProcess.child.stdout.pipe(process.stdout);
      buildProcess.child.stderr.pipe(process.stderr);
  
      // 等待构建完成
      await buildProcess;
  
      // 执行 npm pack 并实时打印输出
      if (isPack) {
        const packProcess = execShell('npm pack');
  
        // 同样打印
        packProcess.child.stdout.pipe(process.stdout);
        packProcess.child.stderr.pipe(process.stderr);
    
        // 等待打包完成
        const { stdout: packResult } = await packProcess;
        const packageName = packResult.trim(); // 获取包文件名
    
        // 移动包文件到指定目录
        const moveProcess = execShell(`mv ${packageName} ../../lib`);
    
        // 打印移动过程的输出
        moveProcess.child.stdout.pipe(process.stdout);
        moveProcess.child.stderr.pipe(process.stderr);
    
        // 等待移动完成
        await moveProcess;
    
        console.log(`Package moved to ../../lib/${packageName}`);
      }
      
    } catch (error) {
      // 输出错误信息
      console.error('Error occurred:', error);
    }
  }
module.exports = startPack;

readFile: 读取文件内容

moveProcess.child.stdout.pipe:执行命令时输出日志。

writeFile: 修改文件内容

execShell(mv ${packageName} ../../lib): 移动文件

总结

使用到的node命令有inqurer做命令交互,promisify将函数转换为可以使用await的版本,文件使用相关fs.stat查询文件信息,fs.readFile/fs.writeFile读写文件,fs.mkdir创建目录。process.chdir 切换目录。文件路径相关,path.join取得绝对地址。执行命令相关 execShell,以及如何输出命令日志的 child.stdout.pipe(process.stdout);child.stderr.pipe(process.stderr);