2017-04-03 45 views
2

我剛剛寫了一個腳本來發布我正在使用的其中一個產品的版本。腳本完成這項工作,但我不太喜歡代碼本身,看起來像意大利麪條代碼和回調地獄加起來。有沒有更好的方式來使用Node.js運行CLI命令?

有沒有更乾淨的方法來做到這一點?我希望能夠串行運行命令,記錄輸出(stdout.on('data'))以及任務完成時間。 (更容易進一步調試和等待任務完成時,讓人放心,知道背景發生了什麼)

也許使用Promise可以幫助清理混亂一點,但仍然,我覺得應該有一個更清潔處理多個命令的方式。


有關代碼所做的一些解釋:

  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.jsdoc/changelog.txt
  6. 壓縮3個文件。
  7. 提交一切提交消息:Release 1.2.11(例如)
  8. 推。
  9. 使用剛剛推送的提交和相同的標記在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}`); 
     }); 
    }); 
    }); 
}); 

編輯:

下面是使用異步I使用的特殊mkdirpexec模塊/等待(節點7.8.0) 的實施,其允許用法與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和許多其他模塊的方式。

這裏有什麼重要的是,這個代碼是還是異步無阻塞

請注意,您只能在使用async關鍵字創建的函數內部使用await。欲瞭解更多信息,請參見:

對於瀏覽器的支持,請參閱:

對於節點支持,請參閱:

在你沒有原生支持asyncawait你可以用巴貝爾地方:

或略有不同語法基於生成器的方法,如co或Bluebird c oroutines:

+0

謝謝!我會試試看。 :) – Vadorequest

+0

感謝您的提示,我完成了實施。無法找到運行'spawn'的異步方式。 – Vadorequest

相關問題