有没有更好的方法来运行Node.js的CLI命令?

时间:2017-04-03 14:57:27

标签: node.js command-line-interface

我刚刚写了一个脚本来发布我正在研究的产品之一的构建。该脚本完成了这项工作,但我并不喜欢代码本身,看起来像意大利面条代码和回调地狱组合。

有更清洁的方法吗?我希望能够串行运行命令,记录输出(stdout.on('data'))以及任务完成时。 (更容易进行进一步调试,等待任务完成时,可以放心地知道后台发生了什么)

也许使用Promises可以帮助清理一下这个烂摊子,但是,我觉得应该有更清洁的方法来处理多个命令。

关于代码的作用的一些解释:

  1. 使用您想要的提交和所需的标记版本创建标记,即:git tag 1.2.5
  2. 使用gulp build
  3. 构建发布文件
  4. 创建文件夹doc/<tag>
  5. doc/doc_reader.odt转换为doc/<tag>/documentation.pdf。 (打开并导出为PDF格式)
  6. 在创建的文件夹中复制build/reader.jsdoc/changelog.txt
  7. 压缩3个文件。
  8. 使用提交消息提交所有内容:Release 1.2.11(例如)
  9. 推。
  10. 使用刚刚推送的提交和相同的标记在GitHub上创建一个新版本。
  11. 这是代码,作为一个例子。 (ES5,Node 4.6.0 +)

    var mkdirp = require('mkdirp');
    var fs = require('fs-extra');
    var path = require('path');
    var spawn = require('child_process').spawn;
    var zip = new require('node-zip')();
    
    var package = require('../package.json');
    var version = package.version;
    var releaseDirectory = 'doc'
    var gitTagProcess = spawn('git', ['tag', version]);
    var gulpBuildProcess = spawn('gulp', ['build']);
    
    console.log(`Running "git tag ${version}"...`);
    gitTagProcess.stdout.on('data', function (chunk) {
      console.log(chunk.toString('utf8'));
    });
    
    gitTagProcess.on('close', function () {
      console.log('Tag created.')
    
      console.log('Running "gulp build"...');
      gulpBuildProcess.stdout.on('data', function (chunk) {
        console.log(chunk.toString('utf8'));
      });
    
      gulpBuildProcess.on('close', function () {
        console.log('"gulp build" done.')
    
        console.log(`Creating "${releaseDirectory}/${version}" directory.`)
        mkdirp(`${releaseDirectory}/${version}`, function () {
          console.log('Directory created.');
          var docFile = `${releaseDirectory}/doc_reader.md`;
          console.log(`Converting ${docFile} to pdf ...`);
          var docBuildProcess = spawn('npm', ['run', 'build:doc']);
    
          docBuildProcess.stdout.on('data', function (chunk) {
            console.log(chunk.toString('utf8'));
          });
    
          docBuildProcess.on('close', function () {
            console.log('Doc created.');
    
            console.log('Copying changelog.txt ...');
            fs.copySync('doc/changelog.txt', `doc/${version}/changelog.txt`);
            console.log('changelog.txt copied.');
    
            console.log(`Copying "build/reader.js" to "doc/reader-${version}.js" and "doc/reader.js" ...`);
            fs.copySync('build/reader.js', `doc/${version}/reader.js`);
            fs.copySync('build/reader.js', `doc/${version}/reader-${version}.js`);
            console.log('reader.js copied.');
    
            console.log('Zipping all files ...');
            zip.file('changelog.txt', fs.readFileSync(`doc/${version}/changelog.txt`));
            zip.file('doc_reader.pdf', fs.readFileSync(`doc/${version}/doc_reader.pdf`));
            zip.file('reader.js', fs.readFileSync(`doc/${version}/reader.js`));
            zip.file(`reader-${version}.js`, fs.readFileSync(`doc/${version}/reader-${version}.js`));
    
            var data = zip.generate({ base64: false, compression: 'DEFLATE' });
            var zipFilename = `doc/${version}/HTML5Reader_${version}.zip`;
            fs.writeFileSync(zipFilename, data, 'binary'); // it's important to use *binary* encode
            console.log(`${zipFilename} created.`);
    
            console.log(`\nRelease ${version} done. Please add generated files and commit using:`);
            console.log(`\n\tgit add * && git commit -m "Release ${version}"`);
            console.log(`\n\nDon't forget to push and create a new release on GitHub at https://github.com/$domain/$product/releases/new?tag=${version}`);
          });
        });
      });
    });
    

    编辑:

    以下是使用async / await的实现(节点7.8.0) 我使用了特殊的mkdirpexec模块,允许使用await。但我找不到spawn的等价物。

    const mkdirp = require('async-mkdirp');
    const fs = require('fs-extra');
    const spawn = require('child-process-promise').spawn;
    const exec = require('mz/child_process').exec;
    const zip = new require('node-zip')();
    const c = require('chalk');
    
    const error = c.bold.red;
    const warn = c.yellow;
    const info = c.cyan;
    const info2 = c.magenta;
    
    const version = require('../package.json').version;
    const releaseDirectory = 'doc'
    
    async function git_tag() {
      async function exec_git_tag() {
        return await exec(`git tag ${version}`);
      }
    
      console.log(info(`Creating git tag ${version}`));
      return exec_git_tag()
        .then(() => {
          console.log(info(`Git tag created for ${version}`))
        })
        .catch((err) => {
          console.log(warn('warn', err));
        })
        // Finally
        .then(() => {
          console.log(info(`"git tag ${version}" - Completed`))
        });
    };
    
    async function gulp_build() {
      async function exec_gulp_build() {
        const promise = spawn('gulp', ['build'])
        const childProcess = promise.childProcess;
    
        childProcess.stdout.on('data', (data) => {
          console.log(info2(data.toString()));
        });
        childProcess.stderr.on('data', (data) => {
          console.log(error(data.toString()));
        });
    
        return promise
          .catch((err) => {
            console.error(error(err));
          })
          // Finally
          .then(() => {
            console.log(info('"gulp build" - Completed'))
          });
      }
    
      console.log(info('Running "gulp build"...'))
      return exec_gulp_build()
    }
    
    async function create_dir() {
      const dirPath = `${releaseDirectory}/${version}`;
      console.log(info(`Creating "${dirPath}" directory.`))
      await mkdirp(`${dirPath}`);
      console.log(info(`Directory ${dirPath} created.`));
    }
    
    async function build_doc() {
      const docFile = `${releaseDirectory}/doc_reader.md`;
      console.log(info(`Converting ${docFile} to pdf ...`));
    
      async function exec_build_doc() {
        return await exec(`npm run build:doc`);
      }
    
      return exec_build_doc()
        .catch((err) => {
          console.error(error(err));
        })
        .then(() => {
          console.log(info(`Doc "${docFile}" created.`));
        })
    }
    
    function copy_files() {
      console.log(info('Copying changelog.txt ...'));
      fs.copySync('doc/changelog.txt', `doc/${version}/changelog.txt`);
      console.log(info('changelog.txt copied.'));
    
      console.log(info(`Copying "build/reader.js" to "doc/reader-${version}.js" and "doc/reader.js" ...`));
      fs.copySync('build/reader.js', `doc/${version}/reader.js`);
      fs.copySync('build/reader.js', `doc/${version}/reader-${version}.js`);
      console.log(info('reader.js copied.'));
    }
    
    function zip_files() {
      console.log(info('Zipping all files ...'));
      zip.file('changelog.txt', fs.readFileSync(`doc/${version}/changelog.txt`));
      zip.file('doc_reader.pdf', fs.readFileSync(`doc/${version}/doc_reader.pdf`));
      zip.file('reader.js', fs.readFileSync(`doc/${version}/reader.js`));
      zip.file(`reader-${version}.js`, fs.readFileSync(`doc/${version}/reader-${version}.js`));
    
      const data = zip.generate({ base64: false, compression: 'DEFLATE' });
      const zipFilename = `doc/${version}/HTML5Reader_${version}.zip`;
      fs.writeFileSync(zipFilename, data, 'binary'); // it's important to use *binary* encode
      console.log(info(`${zipFilename} created.`));
    }
    
    async function release() {
      await git_tag();
      await gulp_build();
      await create_dir();
      await build_doc();
      copy_files();
      zip_files();
    
      console.log(`\nRelease ${version} done. Please add generated files and commit using:`);
      console.log(`\n\tgit add . && git commit -m "Release ${version}"`);
    }
    
    release();
    

1 个答案:

答案 0 :(得分:1)

这里有一个mz模块非常有用。参见:

这与async / await结合使用,可以编写如下代码:

let exec = require('mz/child_process').exec;

(async () => {
  let version = await exec('node --version');
  console.log(version);
  let result = await exec('some other command');
  console.log(result);
  // ...
})();

这是一个简单的示例,但您可以使用child_processfs以及许多其他模块中的所有函数。

此处重要的是此代码仍然是异步非阻止

请注意,您只能在使用await关键字创建的函数中使用async。有关详细信息,请参阅:

有关浏览器的支持,请参阅:

有关Node的支持,请参阅:

在您没有asyncawait原生支持的地方,您可以使用Babel:

或略有不同的语法基于生成器的方法,如co或Bluebird协同程序: