Есть ли лучший способ запуска команд CLI с Node.js?
Я только что написал сценарий, чтобы выпустить сборку одного из продуктов, над которыми я работаю. Сценарий выполняет свою работу, но мне не очень нравится сам код, он выглядит как спагетти-код и адский колбэк.
Есть ли более чистый способ сделать это? Я хотел бы иметь возможность запускать команды последовательно, регистрировать результаты (stdout.on('data')
) и когда задача будет завершена. (проще для дальнейшей отладки и при ожидании выполнения задачи, обнадеживающе узнать, что происходит в фоновом режиме)
Возможно, использование Promises поможет немного убрать беспорядок, но, тем не менее, я чувствую, что должен быть более понятный способ справиться с несколькими командами.
Некоторое объяснение того, что делает код:
- Создайте тег с нужным вам коммитом и версией тега, а именно:
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 файла.
- Зафиксируйте все с помощью сообщения:
Release 1.2.11
(например) - От себя.
- Создайте новый выпуск на GitHub, используя только что отправленный коммит и тот же тег.
Вот код, в качестве примера. (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), я использовал специальный mkdirp
а также exec
модули, которые позволяют использовать с 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 ответ
Есть 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_process
, fs
и многие другие модули таким образом.
Здесь важно то, что этот код все еще асинхронный и неблокирующий.
Обратите внимание, что вы можете использовать только await
внутри функции, созданной с помощью async
ключевое слово. Для получения дополнительной информации см.:
- 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
Для поддержки в браузерах, смотрите:
Для поддержки в узле см.:
В местах, где у вас нет собственной поддержки async
а также await
Вы можете использовать Babel:
или с немного другим синтаксисом подход на основе генератора, как в co
или сопрограммы Bluebird: