有没有更好的方法来使用Node.js运行CLI命令? [英] Is there a better way to run CLI commands with Node.js?
问题描述
我刚刚写了一个脚本来发布我正在开发的产品之一.该脚本可以完成任务,但是我不太喜欢代码本身,看起来像是意大利面条代码和回调地狱的结合.
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:
- 使用所需的提交和所需的标签版本创建标签,即
git tag 1.2.5
. - 使用
gulp build
构建发布文件. - 创建文件夹
doc/< tag>
. - 将
doc/doc_reader.odt
转换为doc/< tag>/documentation.pdf
.(将其打开并导出为PDF) - 在创建的文件夹中复制
build/reader.js
和doc/changelog.txt
. - 压缩3个文件.
- 使用提交消息提交所有内容:
版本1.2.11
(例如) - 推.
- 使用您刚刚推送的提交和相同的标记在GitHub上创建新版本.
- Create a tag with the commit you want and the tag version you want, i.e:
git tag 1.2.5
. - Build the release file with
gulp build
. - Create a folder
doc/<tag>
. - Convert
doc/doc_reader.odt
todoc/<tag>/documentation.pdf
. (Open it and export as PDF) - Copy
build/reader.js
anddoc/changelog.txt
in the created folder. - Zip the 3 files.
- Commit everything with commit message:
Release 1.2.11
(for example) - Push.
- 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:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
有关浏览器的支持,请参见:
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屋!