有没有更好的方式与Node.js运行CLI命令?

我只写了一个脚本来发布我正在开发的产品之一。 脚本做的工作,但我真的不喜欢代码本身,看起来像意大利面代码和callback地狱加起来。

有没有更干净的方法来做到这一点? 我希望能够串行运行命令,logging输出( stdout.on('data') )和任务完成时间。 (更容易进一步debugging,等待任务完成后,放心,知道后台发生了什么)

也许使用Promise可以帮助清理一些混乱,但是我仍然觉得应该有一个更清晰的方法来处理多个命令。


关于代码的一些解释:

  1. 用你想要的提交和你想要的标签版本创build一个标签,例如: git tag 1.2.5
  2. 使用gulp build版本文件。
  3. 创build一个文件夹doc/<tag>
  4. doc/doc_reader.odt转换为doc/<tag>/documentation.pdf 。 (打开并导出为PDF)
  5. 在创build的文件夹中复制build/reader.jsdoc/changelog.txt
  6. 压缩3个文件。
  7. 使用提交消息提交所有内容: Release 1.2.11 (例如)
  8. 推。
  9. 使用刚刚推送的提交和相同的标记在GitHub上创build一个新版本。

这里是代码,作为一个例子。 (ES5,节点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(); 

有一个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和其他许多模块中的所有函数。

这里重要的是这个代码仍然asynchronous非阻塞的

请注意,您只能在使用async关键字创build的函数内部使用await 。 有关更多信息,请参阅:

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

有关Node的支持,请参阅:

在你没有本地支持asyncawait你可以使用Babel:

或者使用稍微不同的语法,如co或Bluebird协程中的基于生成器的方法: