diff --git a/README.md b/README.md index 5614a6b..7792a22 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Behave Pro NodeJS Client ``` -$ npm install behavepro -g +$ npm install @essent/behavepro -g ``` See [API Key setup](introduction.html) to retrieve the required credentials. @@ -86,9 +86,9 @@ BehavePro({ "id": 10000, "userId": "amlyYToyNDM0ZG.....ZiNzQwNGI=", "apiKey": "44993b0481838e.....a246c723e8e" -}, function() { +}).then(() => { // done -}); +}) ``` Available parameters: diff --git a/bin/cli.js b/bin/cli.js index dfc90c0..be28a04 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,9 +1,9 @@ #!/usr/bin/env node "use strict"; -var BehavePro = require("../lib/behavepro"); -var args = require("minimist")(process.argv.slice(2)); -var packageJson = require("../package.json"); -var chalk = require("chalk"); +const BehavePro = require("../lib/behavepro"); +const args = require("minimist")(process.argv.slice(2)); +const packageJson = require("../package.json"); +const chalk = require("chalk"); if (args.help) { console.log( @@ -22,18 +22,25 @@ if (args.help) { return; } -var settings = { +const settings = { host: args.host || "https://behave.pro", id: args.key || args.project || args.id, userId: args.user || args.userId, apiKey: args.api || args.apiKey || args.password, output: args.output || args.dir || args.directory || "features", manual: args.manual || args.m || false, - config: args.config || "config.json" + config: args.config || "config.json", +}; + +const errorHandler = (err) => { + console.error("There was an error:", err); + process.exit(1); //mandatory (as per the Node.js docs) }; if (settings.id && settings.userId && settings.apiKey) { - BehavePro.fetchFeatures(settings); + BehavePro.fetchFeatures(settings).catch(errorHandler); } else { - BehavePro.fetchFeaturesFromConfig(settings); + BehavePro.fetchFeaturesFromConfig(settings).catch(errorHandler); } + +process.on("uncaughtException", errorHandler); diff --git a/index.js b/index.js index c2fd4a6..b06f83d 100755 --- a/index.js +++ b/index.js @@ -1,18 +1,16 @@ #!/usr/bin/env node "use strict"; -var BehavePro = require("./lib/behavepro"); -var _ = require("lodash"); +const BehavePro = require("./lib/behavepro"); +const _ = require("lodash"); -var defaultSettings = { +const defaultSettings = { host: "https://behave.pro", output: "features", manual: false, config: "config.json" }; -module.exports = function(settings, callback) { +module.exports = function(settings) { _.defaults(settings, defaultSettings); - BehavePro.fetchFeatures(settings, function() { - if (callback) callback(); - }); + return BehavePro.fetchFeatures(settings) }; diff --git a/lib/behavepro.js b/lib/behavepro.js index f74faaa..ce1f57e 100644 --- a/lib/behavepro.js +++ b/lib/behavepro.js @@ -1,146 +1,171 @@ #!/usr/bin/env node +// @ts-check "use strict"; -const request = require("request"); +const fetch = require("node-fetch"); const unzipper = require("unzipper"); const fs = require("fs"); +const path = require("path"); const mkdir = require("mkdirp"); const _ = require("lodash"); const chalk = require("chalk"); -const red = chalk.red; - -const fetchFeaturesFromConfig = function(settings, callback) { - fs.exists(process.cwd() + "/" + settings.config, function findConfig(exists) { - if (exists) { - const configuration = require(process.cwd() + "/" + settings.config); - configuration.forEach(function(config) { - _.extend(settings, config); - fetchFeatures(settings, callback); - }); - } else { - const err = new Error( - red("Could not find config at " + process.cwd() + "/" + settings.config) - ); - throw err; - } - }); -}; -const fetchFeatures = function(settings, callback) { +/** + * @typedef {{ + * host?: string, + * id?: string, + * userId?: string, + * apiKey?: string, + * output?:string, + * manual?: boolean, + * config?: string + * }} Settings + */ + +/** + * + * @param {Settings} settings + */ +async function fetchFeaturesFromConfig(settings) { + const settingsPath = path.join(process.cwd(), settings.config); + try { + await fs.promises.access( + settingsPath, + fs.constants.F_OK | fs.constants.R_OK + ); + + const configuration = require(settingsPath); + const features = configuration.map(function (config) { + _.extend(settings, config); + return fetchFeatures(settings); + }); + return await Promise.all(features); + } catch (error) { + throw new Error( + chalk.red(`Could not find config at: ${chalk.bold(settingsPath)}`) + ); + } +} + +/** + * + * @param {Settings} settings + */ +async function fetchFeatures(settings) { const projectId = settings.id; const userId = settings.userId; const apiKey = settings.apiKey; - ensureSettingsExist(projectId, userId, apiKey, function() { - console.log( - chalk.cyan("Downloading features from JIRA project " + projectId + "...") - ); - const url = [ - settings.host, - "/rest/cucumber/1.0/project/", - projectId, - "/features?manual=", - settings.manual - ].join(""); - - request( - { - url: url, - headers: { - Authorization: - "Basic " + Buffer.from(userId + ":" + apiKey).toString("base64") - }, - encoding: null - }, - function(error, response, body) { - if (error) throw error; - let err = null; - switch (response.statusCode) { - case 500: - err = new Error(red("Server error: check your host url")); - throw err; - case 401: - err = new Error( - red("Unauthorized: ensure userId and apiKey are both valid") - ); - throw err; - case 200: - break; - default: - err = new Error( - red(response.statusCode + " http error when downloading") - ); - throw err; - } - - const path = settings.output + "/" + projectId; - mkdir(path, function(err) { - if (err) throw err; - writeFeatures(body, path, function() { - countFeatures(path, function(files) { - console.log( - chalk.green( - `Saved ${files.length} ${ - files.length > 1 ? "features" : "feature" - } to ${process.cwd()}/${path}/` - ) - ); - }); - if (callback) callback(); - }); - }); - } - ); + ensureSettingsExist(projectId, userId, apiKey); + + console.log( + chalk.cyan( + `Downloading features from JIRA project ${chalk.bold(projectId)} ...` + ) + ); + + const url = [ + settings.host, + "/rest/cucumber/1.0/project/", + projectId, + "/features?manual=", + settings.manual, + ].join(""); + + const response = await fetch(url, { + headers: { + Authorization: `Basic ${Buffer.from(`${userId}:${apiKey}`).toString( + "base64" + )}`, + }, }); -}; + + let err = null; + switch (response.status) { + case 500: + err = new Error(chalk.red("Server error: check your host url")); + throw err; + case 401: + err = new Error( + chalk.red("Unauthorized: ensure userId and apiKey are both valid") + ); + throw err; + case 200: + break; + default: + err = new Error( + chalk.red(response.status + " http error when downloading") + ); + throw err; + } + + const projectPath = path.join(settings.output, `${projectId}`); + await mkdir(projectPath); + const buffer = await response.buffer(); + await writeFeatures(buffer, projectPath); + const filesCount = await countFeatures(projectPath); + + console.log( + chalk.green( + `Saved ${filesCount} ${ + filesCount > 1 ? "features" : "feature" + } to ${path.join(process.cwd(), projectPath)}/` + ) + ); +} module.exports = { fetchFeatures: fetchFeatures, - fetchFeaturesFromConfig: fetchFeaturesFromConfig + fetchFeaturesFromConfig: fetchFeaturesFromConfig, }; -function writeFeatures(body, path, callback) { - fs.writeFile(path + ".zip", body, function(err) { - if (err) throw err; - const stream = fs.createReadStream(path + ".zip").pipe( +/** + * + * @param {Buffer} buffer + * @param {string} featuresPath + */ +async function writeFeatures(buffer, featuresPath) { + const zipPath = featuresPath + ".zip"; + await fs.promises.writeFile(zipPath, buffer); + + await fs + .createReadStream(zipPath) + .pipe( unzipper.Extract({ - path: path + path: featuresPath, }) - ); + ) + .promise(); - stream.on("close", function() { - removeZip(path + ".zip"); - callback(); - }); - }); + await await fs.promises.unlink(zipPath); } -function ensureSettingsExist(projectId, userId, apiKey, callback) { +/** + * + * @param {string} projectId + * @param {string} userId + * @param {string} apiKey + */ +function ensureSettingsExist(projectId, userId, apiKey) { let err = null; if (!projectId) { - err = new Error(red("projectId is missing")); + err = new Error(chalk.red("projectId is missing")); } else if (!userId) { - err = new Error(red("userId is missing")); + err = new Error(chalk.red("userId is missing")); } else if (!apiKey) { - err = new Error(red("apiKey is missing")); + err = new Error(chalk.red("apiKey is missing")); } if (err) { throw err; } - - return callback(); } -function countFeatures(path, callback) { - fs.readdir(path, function(err, files) { - callback(files); - }); -} - -function removeZip(file, callback) { - fs.unlink(file, function(err) { - if (err) throw err; - if (callback) callback(); - }); +/** + * + * @param {string} path + */ +async function countFeatures(path) { + const files = await fs.promises.readdir(path); + return files.length; }