有没有更好的方法来使用Node.js运行CLI命令? [英] Is there a better way to run CLI commands with Node.js?

查看:96
本文介绍了有没有更好的方法来使用Node.js运行CLI命令?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚写了一个脚本来发布我正在开发的产品之一.该脚本可以完成任务,但是我不太喜欢代码本身,看起来像是意大利面条代码和回调地狱的结合.

I just wrote a script to release a build of one of the products I'm working on. The script does the job, but I don't really like the code itself, looks like spaghetti code and callback hell combined.

是否有一种更清洁的方式来做到这一点?我希望能够连续运行命令,记录输出( stdout.on('data'))以及完成任务的时间.(更容易进行调试,并且在等待任务完成时,可以放心地了解后台发生了什么)

Is there a cleaner way to do this? I'd like to be able to run commands in series, log the outputs (stdout.on('data')) and when the task is finished. (easier for further debug and when waiting for the task to be done, reassuring to know what's happening on the background)

也许使用Promises可以稍微清除混乱,但我仍然认为应该有一种更清洁的方式来处理多个命令.

Maybe using Promises would help clean the mess a bit, but still, I feel like there should be a cleaner way to deal with multiple commands.

有关代码功能的一些解释:

Some explanation about what the code does:

  1. 使用所需的提交和所需的标签版本创建标签,即 git tag 1.2.5 .
  2. 使用 gulp build 构建发布文件.
  3. 创建文件夹 doc/< tag> .
  4. doc/doc_reader.odt 转换为 doc/< tag>/documentation.pdf .(将其打开并导出为PDF)
  5. 在创建的文件夹中复制 build/reader.js doc/changelog.txt .
  6. 压缩3个文件.
  7. 使用提交消息提交所有内容:版本1.2.11 (例如)
  8. 推.
  9. 使用您刚刚推送的提交和相同的标记在GitHub上创建新版本.
  1. Create a tag with the commit you want and the tag version you want, i.e: git tag 1.2.5.
  2. Build the release file with gulp build.
  3. Create a folder doc/<tag>.
  4. Convert doc/doc_reader.odt to doc/<tag>/documentation.pdf. (Open it and export as PDF)
  5. Copy build/reader.js and doc/changelog.txt in the created folder.
  6. Zip the 3 files.
  7. Commit everything with commit message: Release 1.2.11 (for example)
  8. Push.
  9. Create a new release on GitHub using the commit you just pushed and the same tag.


这里是代码,例如.(ES5,节点4.6.0 +)


Here is the code, as an example. (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)我使用了特殊的 mkdirp exec 模块,这些模块允许与 await 一起使用.但是我找不到 spawn 的等效项.

Here is the implementation using async/await (node 7.8.0) I used special mkdirp and exec modules, that allow usage with await. But I couldn't find an equivalent for 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();

推荐答案

有一个 mz 模块在这里可能会非常有帮助.参见:

There is an mz module that can be very helpful here. See:

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

This, combined with async/await will allow you to write code like this:

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_process fs 和所有其他模块中的所有功能.

This is a simple example but you can use all functions from the child_process, fs and many other modules that way.

这里重要的是,此代码仍然异步非阻塞.

What's important here is that this code is still asynchronous and non-blocking.

请注意,您只能在通过 async 关键字创建的函数中使用 await .有关更多信息,请参见:

Note that you can only use await inside of a function created with the async keyword. For more info, see:

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

For support in browsers, see:

有关Node的支持,请参见:

For support in Node, see:

在不支持 async await 的本地支持中,可以使用Babel:

In places where you don't have native support for async and await you can use Babel:

或使用稍微不同的语法的基于生成器的方法,例如 co <​​/code>或Bluebird协程:

or with a slightly different syntax a generator based approach like in co or Bluebird coroutines:

这篇关于有没有更好的方法来使用Node.js运行CLI命令?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆