From 0d650c65a1715c51363a5242751b20d0f06b4a09 Mon Sep 17 00:00:00 2001 From: Dor Itzhaki Date: Sun, 19 Apr 2020 23:54:32 +0300 Subject: [PATCH] config file support in ecp, local & validate modes --- docworks-cli/package.json | 4 +- docworks-cli/src/docworks.js | 167 +++++++------- docworks-cli/test/config-file.unit.spec.js | 251 +++++++++++++++++++++ 3 files changed, 343 insertions(+), 79 deletions(-) create mode 100644 docworks-cli/test/config-file.unit.spec.js diff --git a/docworks-cli/package.json b/docworks-cli/package.json index d4905a6..cf93a40 100644 --- a/docworks-cli/package.json +++ b/docworks-cli/package.json @@ -38,7 +38,9 @@ "chai-subset": "^1.5.0", "eslint": "^6.0.1", "lodash": "^4.17.15", - "mocha": "^3.3.0" + "mocha": "^3.3.0", + "proxyquire": "^2.1.3", + "sinon": "^9.0.2" }, "gitHead": "d56ca65a3a1c3e474fe632f909c6a8ad98493cf6" } diff --git a/docworks-cli/src/docworks.js b/docworks-cli/src/docworks.js index 43b5296..038cd78 100755 --- a/docworks-cli/src/docworks.js +++ b/docworks-cli/src/docworks.js @@ -13,24 +13,20 @@ function docworks() { process.exit(1) } - let command = process.argv[2] + const command = process.argv[2] + const cliArgs = process.argv.slice(3) if (command === 'ecp') { - ecp() - } - else if (command === 'validate' || command === 'val') { - validateCommand() - } - else if (command === 'tern') { - tern() - } - else if (command === 'dts') { - dts() - } - else if (command === 'local') { - ldw() - } - else { + return ecp() + } else if (command === 'validate' || command === 'val') { + return validateCommand() + } else if (command === 'tern') { + return tern() + } else if (command === 'dts') { + return dts() + } else if (command === 'local') { + return ldw() + } else { printUsage(1) process.exit(1) } @@ -50,49 +46,52 @@ function docworks() { /* eslint-enable no-console */ function ecp() { - let argv = optimist - .usage('Usage: $0 ecp -r [remote repo] [-b [remote branch]] -s [local sources] -p [file pattern] [-ed [enrichment docs directory]] [--plug [plugin]] [--dryrun]') - .demand('r') + const commandConfig = optimist + .usage('Usage: $0 ecp -r [remote repo] [-b [remote branch]] -fs [local sources] -fp [file pattern] [-ed [enrichment docs directory]] [--plug [plugin]] [--dryrun]') .alias('r', 'remote') .describe('r', 'remote repository to merge docs into') .alias('b', 'branch') .describe('b', 'branch on the remote repository to work with') - .demand('fs') .alias('fs', 'sources') .describe('fs', 'one or more folders containing the source files to extract docs from') .alias('fx', 'excludes') .describe('fx', 'one or more folders to exclude (including their children) from extracting docs') - .default('fp', '.+\\.js?$') .alias('fp', 'pattern') .describe('fp', 'file pattern, defaults to ".+\\.js$"') - .demand('p') .alias('p', 'project') .describe('p', 'project folder name in the docs repo') .describe('ed', 'project enrichment docs relative directory') .describe('plug', 'a module name that is a jsdoc or docworks plugin') .describe('dryrun', 'dry run - do not push to remote repo') - .parse(process.argv.slice(3)) + .describe('config', 'js/json file to load configurations from') - let remote = argv.remote - let branch = argv.branch - let sources = argv.sources - let excludes = argv.excludes ? (Array.isArray(argv.excludes) ? argv.excludes : [argv.excludes]) : [] - let pattern = argv.pattern - let project = argv.project - let dryrun = !!argv.dryrun - let enrichmentDocsDir = argv.ed - let plugins = resolveAndInitPlugins(argv.plug) + const argv = commandConfig.parse(cliArgs) + + const config = argv.config + ? Object.assign(require(argv.config), argv) + : argv + + if (!config.remote || !config.sources || !config.project) { + commandConfig.showHelp() + process.exit(1) + } - tmp.dir().then(o => { + return tmp + .dir() + .then(({ path: tmpWorkingDir }) => { return extractComparePush({ - remoteRepo: remote, - remoteBranch: branch, - workingDir: o.path, - projectSubdir: project, - jsDocSources: {'include': sources, 'includePattern': pattern, 'exclude': excludes}, - plugins, - enrichmentDocsDir, - dryrun + remoteRepo: config.remote, + remoteBranch: config.branch, + workingDir: tmpWorkingDir, + projectSubdir: config.project, + jsDocSources: { + include: config.sources, + includePattern: config.pattern || '.+\\.js?$', + exclude: [].concat(config.excludes), + }, + plugins: resolveAndInitPlugins(config.plug), + enrichmentDocsDir: config.ed, + dryrun: !!config.dryrun, }) }) .catch(() => { @@ -101,26 +100,38 @@ function docworks() { } function validateCommand() { - let argv = optimist + let commandConfig = optimist .usage('Usage: $0 validate -fs [local sources] -p [file pattern] -fp [file pattern]') - .demand('fs') .alias('fs', 'sources') .describe('fs', 'folder containing the source files to extract docs from') - .default('fp', '.+\\.js?$') + .alias('fx', 'excludes') + .describe('fx', 'one or more folders to exclude (including their children) from extracting docs') .alias('fp', 'pattern') .describe('fp', 'file pattern, defaults to ".+\\.js$"') - .alias('plug', 'jsdocplugin') .describe('plug', 'a module name that is a jsdoc plugin') - .parse(process.argv.slice(3)) + .describe('config', 'js/json file to load configurations from') + + const argv = commandConfig.parse(cliArgs) - let sources = argv.sources - let pattern = argv.pattern - let plugins = resolveAndInitPlugins(argv.jsdocplugin) + const config = argv.config + ? Object.assign(require(argv.config), argv) + : argv - if (!validate({'include': sources, 'includePattern': pattern}, plugins)) + if (!config.sources) { + commandConfig.showHelp() process.exit(1) } + const jsDocSources = { + include: config.sources, + includePattern: config.pattern || '.+\\.js?$', + exclude: [].concat(config.excludes), + } + const plugins = resolveAndInitPlugins(config.plug) + + if (!validate(jsDocSources, plugins)) process.exit(1) + } + function tern() { const cmdDefinition = optimist .usage('Usage: $0 tern (-r [remote repo] [-b [remote branch]] | -l [local services folder] ) -u [base url] -n [api name] -o [output file]') @@ -199,54 +210,54 @@ function docworks() { } function ldw() { - let argv = optimist + let commandConfig = optimist .usage('Usage: $0 local -r [remote repo] -d [local directory] -fs [local sources] -fp [file pattern] -p [project name] [-ed [enrichment docs directory]] [--plug [plugin]]') - .demand('r') .alias('r', 'remote') .describe('r', 'remote repository to merge docs into') .alias('b', 'branch') .describe('b', 'branch on the remote repository to work with') - .demand('d') .alias('d', 'dist') .describe('d', 'local directory to output docs into') - .demand('fs') .alias('fs', 'sources') .describe('fs', 'one or more folders containing the source files to extract docs from') .alias('fx', 'excludes') .describe('fx', 'one or more folders to exclude (including their children) from extracting docs') - .default('fp', '.+\\.js?$') .alias('fp', 'pattern') .describe('fp', 'file pattern, defaults to ".+\\.js$"') - .demand('p') .alias('p', 'project') .describe('p', 'project folder name in the docs repo') .describe('ed', 'project enrichment docs relative directory') .describe('plug', 'a module name that is a jsdoc or docworks plugin') - .parse(process.argv.slice(3)) + .describe('config', 'js/json file to load configurations from') - const remote = argv.remote - const branch = argv.branch - const dist = argv.dist - const sources = argv.sources - const excludes = argv.excludes ? (Array.isArray(argv.excludes) ? argv.excludes : [argv.excludes]) : [] - const pattern = argv.pattern - const project = argv.project - const dryrun = !!argv.dryrun - const enrichmentDocsDir = argv.ed - const plugins = resolveAndInitPlugins(argv.plug) + const argv = commandConfig.parse(cliArgs) + + const config = argv.config + ? Object.assign(require(argv.config), argv) + : argv + + if (!config.remote || !config.dist || !config.sources || !config.project) { + commandConfig.showHelp() + process.exit(1) + } - tmp.dir() - .then(wd => { + return tmp + .dir() + .then(({ path: tmpWorkingDir }) => { return localDocworks({ - remoteRepo: remote, - branch, - outputDirectory: dist, - tmpDir: wd.path, - projectDir: project, - jsDocSources: {'include': sources, 'includePattern': pattern, 'exclude': excludes}, - plugins, - enrichmentDocsDir, - dryrun + remoteRepo: config.remote, + branch: config.branch, + outputDirectory: config.dist, + tmpDir: tmpWorkingDir, + projectDir: config.project, + jsDocSources: { + include: config.sources, + includePattern: config.pattern || '.+\\.js?$', + exclude: [].concat(config.excludes), + }, + plugins: resolveAndInitPlugins(config.plug), + enrichmentDocsDir: config.ed, + dryrun: !!config.dryrun, }) }) .catch(() => { diff --git a/docworks-cli/test/config-file.unit.spec.js b/docworks-cli/test/config-file.unit.spec.js new file mode 100644 index 0000000..a96115d --- /dev/null +++ b/docworks-cli/test/config-file.unit.spec.js @@ -0,0 +1,251 @@ +const chai = require('chai') +const expect = chai.expect +const fs = require('fs') +const tmp = require('tmp-promise') +tmp.setGracefulCleanup() + +const proxyquire = require('proxyquire').noCallThru() +const sinon = require('sinon') + +const writeTempConfigFile = (content) => { + const { name: tempFilePath } = tmp.fileSync() + fs.writeFileSync(tempFilePath, content) + return tempFilePath +} + +describe('config file unit tests', function () { + const sinonSandbox = sinon.createSandbox() + afterEach(() => { + sinonSandbox.restore() + }) + + let ecpStub, localStub, validateStub + let docworksCliWithStubs + beforeEach(() => { + ecpStub = sinonSandbox.stub().returns(Promise.resolve()) + localStub = sinonSandbox.stub().returns(Promise.resolve()) + validateStub = sinonSandbox.stub().returns(true) + docworksCliWithStubs = proxyquire('../src/docworks', { + './extract-compare-push': ecpStub, + './local-docworks': localStub, + './validate': validateStub, + }) + }) + + const execDocworks = async (commandLineArgs) => { + sinonSandbox + .stub(process, 'argv') + .value(`node docworks ${commandLineArgs}`.split(' ')) + return docworksCliWithStubs() + } + + describe('ecp mode (extract compare push)', function () { + it('should use options from the given configuration file', async function () { + const configFilePath = writeTempConfigFile(` + module.exports = { + remote: 'test-remote-url', + branch: 'test-branch', + project: 'test-project', + sources: ['test-source1', 'test-source2'], + excludes: 'test-excludes', + pattern: '.*.test', + ed: 'test-enrichments-dir', + dryrun: true, + } + `) + + await execDocworks(`ecp --config ${configFilePath}`) + + expect(ecpStub.calledOnce).to.be.true + expect(ecpStub.firstCall.args).to.be.deep.equal([ + { + remoteRepo: 'test-remote-url', + remoteBranch: 'test-branch', + projectSubdir: 'test-project', + jsDocSources: { + include: ['test-source1', 'test-source2'], + includePattern: '.*.test', + exclude: ['test-excludes'], + }, + enrichmentDocsDir: 'test-enrichments-dir', + plugins: [], + dryrun: true, + workingDir: ecpStub.firstCall.args[0].workingDir, // duh + }, + ]) + }) + + it('should allow overwriting params from the command line', async function () { + const configFilePath = writeTempConfigFile(` + module.exports = { + remote: 'test-remote-url', + branch: 'test-branch', + project: 'test-project', + sources: ['test-source1', 'test-source2'], + excludes: 'test-excludes', + pattern: '.*.test', + ed: 'test-enrichments-dir', + dryrun: true, + } + `) + + await execDocworks( + `ecp --config ${configFilePath} --branch command-line-branch --dryrun 0` + ) + + expect(ecpStub.calledOnce).to.be.true + expect(ecpStub.firstCall.args).to.be.deep.equal([ + { + remoteRepo: 'test-remote-url', + remoteBranch: 'command-line-branch', + projectSubdir: 'test-project', + jsDocSources: { + include: ['test-source1', 'test-source2'], + includePattern: '.*.test', + exclude: ['test-excludes'], + }, + enrichmentDocsDir: 'test-enrichments-dir', + plugins: [], + dryrun: false, + workingDir: ecpStub.firstCall.args[0].workingDir, // duh + }, + ]) + }) + }) + + describe('local mode', function () { + it('should use options from the given configuration file', async function () { + const configFilePath = writeTempConfigFile(` + module.exports = { + remote: 'test-remote-url', + branch: 'test-branch', + project: 'test-project', + sources: ['test-source1', 'test-source2'], + excludes: 'test-excludes', + pattern: '.*.test', + ed: 'test-enrichments-dir', + dist: 'test-dist-folder', + dryrun: true, + } + `) + + await execDocworks(`local --config ${configFilePath}`) + + expect(localStub.calledOnce).to.be.true + expect(localStub.firstCall.args).to.be.deep.equal([ + { + remoteRepo: 'test-remote-url', + branch: 'test-branch', + outputDirectory: 'test-dist-folder', + projectDir: 'test-project', + jsDocSources: { + include: ['test-source1', 'test-source2'], + includePattern: '.*.test', + exclude: ['test-excludes'], + }, + enrichmentDocsDir: 'test-enrichments-dir', + plugins: [], + dryrun: true, + tmpDir: localStub.firstCall.args[0].tmpDir, // duh, + }, + ]) + }) + + it('should allow overwriting params from the command line', async function () { + const configFilePath = writeTempConfigFile(` + module.exports = { + remote: 'test-remote-url', + branch: 'test-branch', + project: 'test-project', + sources: ['test-source1', 'test-source2'], + excludes: 'test-excludes', + pattern: '.*.test', + ed: 'test-enrichments-dir', + dist: 'test-dist-folder', + dryrun: true, + } + `) + + await execDocworks( + `local --config ${configFilePath} --dist command-line-dist-folder --branch master` + ) + + expect(localStub.calledOnce).to.be.true + expect(localStub.firstCall.args).to.be.deep.equal([ + { + remoteRepo: 'test-remote-url', + branch: 'master', + outputDirectory: 'command-line-dist-folder', + projectDir: 'test-project', + jsDocSources: { + include: ['test-source1', 'test-source2'], + includePattern: '.*.test', + exclude: ['test-excludes'], + }, + enrichmentDocsDir: 'test-enrichments-dir', + plugins: [], + dryrun: true, + tmpDir: localStub.firstCall.args[0].tmpDir, // duh, + }, + ]) + }) + }) + + describe('validate mode', function () { + it('should use options from the given configuration file', async function () { + const configFilePath = writeTempConfigFile(` + module.exports = { + remote: 'test-remote-url', + branch: 'test-branch', + project: 'test-project', + sources: ['test-source1', 'test-source2'], + excludes: 'test-excludes', + pattern: '.*.test', + ed: 'test-enrichments-dir', + dryrun: true, + } + `) + + await execDocworks(`validate --config ${configFilePath}`) + + expect(validateStub.calledOnce).to.be.true + expect(validateStub.firstCall.args).to.be.deep.equal([ + { + include: ['test-source1', 'test-source2'], + includePattern: '.*.test', + exclude: ['test-excludes'], + }, + [], + ]) + }) + + it('should allow overwriting params from the command line', async function () { + const configFilePath = writeTempConfigFile(` + module.exports = { + remote: 'test-remote-url', + branch: 'test-branch', + project: 'test-project', + sources: ['test-source1', 'test-source2'], + excludes: 'test-excludes', + pattern: '.*.test', + ed: 'test-enrichments-dir', + dryrun: true, + } + `) + + await execDocworks( + `validate --config ${configFilePath} --sources command-line-source1 --sources command-line-source2` + ) + + expect(validateStub.calledOnce).to.be.true + expect(validateStub.firstCall.args).to.be.deep.equal([ + { + include: ['command-line-source1', 'command-line-source2'], + includePattern: '.*.test', + exclude: ['test-excludes'], + }, + [], + ]) + }) + }) +})