diff --git a/license b/license index 49817f6..f8f27e3 100644 --- a/license +++ b/license @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Brad Garropy +Copyright (c) 2020 Brad Garropy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package-lock.json b/package-lock.json index f8fef66..c241c5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "labman", - "version": "0.4.0", + "version": "0.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -193,9 +193,9 @@ } }, "@octokit/rest": { - "version": "16.35.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.35.2.tgz", - "integrity": "sha512-iijaNZpn9hBpUdh8YdXqNiWazmq4R1vCUsmxpBB0kCQ0asHZpCx+HNs22eiHuwYKRhO31ZSAGBJLi0c+3XHaKQ==", + "version": "16.36.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.36.0.tgz", + "integrity": "sha512-zoZj7Ya4vWBK4fjTwK2Cnmu7XBB1p9ygSvTk2TthN6DVJXM4hQZQoAiknWFLJWSTix4dnA3vuHtjPZbExYoCZA==", "requires": { "@octokit/request": "^5.2.0", "@octokit/request-error": "^1.0.2", @@ -225,9 +225,9 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/node": { - "version": "12.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.18.tgz", - "integrity": "sha512-DBkZuIMFuAfjJHiunyRc+aNvmXYNwV1IPMgGKGlwCp6zh6MKrVtmvjSWK/axWcD25KJffkXgkfvFra8ndenXAw==" + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.0.tgz", + "integrity": "sha512-zwrxviZS08kRX40nqBrmERElF2vpw4IUTd5khkhBTfFH8AOaeoLVx48EC4+ZzS2/Iga7NevncqnsUSYjM4OWYA==" }, "acorn": { "version": "7.1.0", @@ -728,9 +728,9 @@ } }, "eslint-config-bradgarropy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-bradgarropy/-/eslint-config-bradgarropy-1.2.0.tgz", - "integrity": "sha512-xKTNbfa0RsEYL0JfkDJnZtZxbxswM1wlT/IU8sPeW6WGHLSuQEqcuFLIDoEmWXcavQnOnnnk/8USapBAOZTlgQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-bradgarropy/-/eslint-config-bradgarropy-1.3.0.tgz", + "integrity": "sha512-JWcV+SrTfIKUCZl5SlWnNiZtpGziTVAdlIwL34CCU7Sv6RwGWZpC/RwXPYnUv72vimxk2y0BjYICrVWB4yWZpg==", "dev": true }, "eslint-plugin-eslint-plugin": { @@ -2015,9 +2015,9 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", - "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -2077,9 +2077,9 @@ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yargs": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.0.2.tgz", - "integrity": "sha512-GH/X/hYt+x5hOat4LMnCqMd8r5Cv78heOMIJn1hr7QPPBqfeC6p89Y78+WB9yGDvfpCvgasfmWLzNzEioOUD9Q==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", + "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", diff --git a/package.json b/package.json index 5110e30..62cf064 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "labman", - "version": "0.4.0", + "version": "0.4.1", "description": "👨🏼‍🔬 github label manager cli", "keywords": [ "github", @@ -32,15 +32,15 @@ "start": "node src/cli/index.js" }, "dependencies": { - "@octokit/rest": "^16.35.2", + "@octokit/rest": "^16.36.0", "chalk": "^3.0.0", "conf": "^6.2.0", - "yargs": "^15.0.2" + "yargs": "^15.1.0" }, "devDependencies": { "babel-eslint": "^10.0.3", "eslint": "^6.8.0", - "eslint-config-bradgarropy": "^1.2.0", + "eslint-config-bradgarropy": "^1.3.0", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.17.0", "eslint-plugin-react-hooks": "^2.3.0", diff --git a/src/cli/default.js b/src/cli/default.js index 0b2781f..4b90d64 100644 --- a/src/cli/default.js +++ b/src/cli/default.js @@ -1,7 +1,17 @@ const conf = require("conf") -const chalk = require("chalk") -const copy = require("../copy") -const {createOctokit} = require("../octokit") +const { + errorTokenNotFound, + errorInvalidToken, + errorRepoNotFound, +} = require("../errors") +const { + createOctokit, + validToken, + validRepo, + getLabels, + deleteLabels, + createLabels, +} = require("../github") const config = new conf() @@ -21,29 +31,53 @@ const handler = async argv => { const {source, destination, labels, clobber} = argv const token = config.get("token") + // validate token if (!token) { - console.log( - `\nYou are not logged in, please run the ${chalk.cyanBright( - "login", - )} command.\n`, - ) + errorTokenNotFound() + return + } + + const isValidToken = await validToken(token) + + // validate token + if (!isValidToken) { + errorInvalidToken() + return + } + + const isValidSource = await validRepo(source) - console.log(chalk.cyanBright("labman login \n")) + // validate source + if (!isValidSource) { + errorRepoNotFound(source) + return + } + + const isValidDestination = await validRepo(destination) + + // validate destination + if (!isValidDestination) { + errorRepoNotFound(destination) return } createOctokit(token) - try { - await copy(source, destination, labels, clobber) - } catch (error) { - console.log( - `\n${chalk.redBright( - "Invalid token!", - )} Please run the ${chalk.cyanBright("login")} command again.\n`, - ) - console.log(chalk.cyanBright("labman login \n")) + // delete existing labels + if (clobber) { + const oldLabels = await getLabels(destination) + await deleteLabels(oldLabels, destination) } + + // get new labels + const sourceLabels = await getLabels(source) + + const newLabels = labels.length + ? sourceLabels.filter(label => labels.includes(label.name)) + : sourceLabels + + // create new labels + await createLabels(newLabels, destination) } module.exports = { diff --git a/src/cli/index.js b/src/cli/index.js old mode 100644 new mode 100755 diff --git a/src/cli/login.js b/src/cli/login.js index 14be94b..6138cf4 100644 --- a/src/cli/login.js +++ b/src/cli/login.js @@ -1,6 +1,7 @@ const conf = require("conf") const chalk = require("chalk") const {validToken} = require("../github") +const {errorLoginFailed} = require("../errors") const config = new conf() @@ -21,19 +22,22 @@ const handler = async argv => { const storedToken = config.get("token") if (!force && storedToken) { - console.log("\nYou are already logged in!\n") + console.log() + console.log("You are already logged in!") return } const valid = await validToken(token) if (!valid) { - console.log(`\n${chalk.redBright("Login failed!")} Please try again.\n`) + errorLoginFailed() return } config.set({username, token}) - console.log(chalk.greenBright("\nLogin successful!\n")) + + console.log() + console.log(chalk.greenBright("Login successful!")) return } diff --git a/src/cli/logout.js b/src/cli/logout.js index 09f0154..a9b6ae3 100644 --- a/src/cli/logout.js +++ b/src/cli/logout.js @@ -1,11 +1,20 @@ const conf = require("conf") +const chalk = require("chalk") const config = new conf() const command = "logout" const description = "Remove GitHub credentials" const builder = {} -const handler = () => config.clear() + +const handler = () => { + config.clear() + + console.log() + console.log(chalk.greenBright("Logout successful!")) + + return +} module.exports = { command, diff --git a/src/copy.js b/src/copy.js deleted file mode 100644 index 4825ee7..0000000 --- a/src/copy.js +++ /dev/null @@ -1,23 +0,0 @@ -const {getLabels, deleteLabels, createLabels} = require("./github") - -const copy = async (source, destination, labels = [], clobber = false) => { - const [sourceOwner, sourceRepo] = source.split("/") - const [destinationOwner, destinationRepo] = destination.split("/") - - // delete existing labels - if (clobber) { - const oldLabels = await getLabels(destinationOwner, destinationRepo) - await deleteLabels(oldLabels, destinationOwner, destinationRepo) - } - - // create new labels - const sourceLabels = await getLabels(sourceOwner, sourceRepo) - - const newLabels = labels.length - ? sourceLabels.filter(label => labels.includes(label.name)) - : sourceLabels - - await createLabels(newLabels, destinationOwner, destinationRepo) -} - -module.exports = copy diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 0000000..d4f1693 --- /dev/null +++ b/src/errors.js @@ -0,0 +1,54 @@ +const chalk = require("chalk") + +const errorTokenNotFound = () => { + const commandText = chalk.cyanBright("login") + + console.log() + console.log(`You are not logged in, please run the ${commandText} command.`) + console.log() + console.log(chalk.cyanBright("labman login ")) +} + +const errorInvalidToken = () => { + const errorText = chalk.redBright("Invalid token!") + const commandText = chalk.cyanBright("login") + + console.log() + console.log(`${errorText} Please run the ${commandText} command again.`) + console.log() + console.log(chalk.cyanBright("labman login ")) +} + +const errorLoginFailed = () => { + const errorText = chalk.redBright("Login failed!") + + console.log() + console.log(`${errorText} Please try again.`) +} + +const errorRepoNotFound = repo => { + const repoText = chalk.bold.cyanBright(repo) + const errorText = chalk.bold.redBright( + `Repository ${repoText} does not exist!`, + ) + + console.log() + console.log(errorText) +} + +const errorLabelExists = label => { + const labelText = chalk.bold.cyanBright(label) + const errorText = chalk.bold.redBright( + ` x Label ${labelText} already exists!`, + ) + + console.log(errorText) +} + +module.exports = { + errorTokenNotFound, + errorInvalidToken, + errorLoginFailed, + errorRepoNotFound, + errorLabelExists, +} diff --git a/src/github.js b/src/github.js deleted file mode 100644 index 4459072..0000000 --- a/src/github.js +++ /dev/null @@ -1,76 +0,0 @@ -const chalk = require("chalk") -const {createOctokit, getOctokit} = require("./octokit") - -const validToken = async token => { - const octokit = createOctokit(token) - - try { - await octokit.users.getAuthenticated() - } catch (error) { - return false - } - - return true -} - -const getLabels = async (owner, repo) => { - const parameters = { - owner, - repo, - } - - const octokit = getOctokit() - const response = await octokit.issues.listLabelsForRepo(parameters) - - const labels = response.data - return labels -} - -const deleteLabels = async (labels, owner, repo) => { - console.log( - `\nDeleting labels from ${chalk.cyanBright(`${owner}/${repo}`)}\n`, - ) - - const octokit = getOctokit() - - labels.forEach(label => { - console.log(` ${chalk.bold.redBright("-")} ${label.name}`) - - const parameters = { - owner, - repo, - name: label.name, - } - - octokit.issues.deleteLabel(parameters) - }) -} - -const createLabels = async (labels, owner, repo) => { - console.log( - `\nCreating labels in ${chalk.cyanBright(`${owner}/${repo}`)}\n`, - ) - - const octokit = getOctokit() - - labels.forEach(label => { - console.log(` ${chalk.bold.greenBright("+")} ${label.name}`) - - const parameters = { - owner, - repo, - name: label.name, - color: label.color, - description: label.description, - } - - octokit.issues.createLabel(parameters) - }) -} - -module.exports = { - validToken, - getLabels, - deleteLabels, - createLabels, -} diff --git a/src/github/index.js b/src/github/index.js new file mode 100644 index 0000000..91ebc24 --- /dev/null +++ b/src/github/index.js @@ -0,0 +1,9 @@ +const labels = require("./labels") +const octokit = require("./octokit") +const validate = require("./validate") + +module.exports = { + ...labels, + ...octokit, + ...validate, +} diff --git a/src/github/labels.js b/src/github/labels.js new file mode 100644 index 0000000..22c4821 --- /dev/null +++ b/src/github/labels.js @@ -0,0 +1,77 @@ +const chalk = require("chalk") +const {repoObject} = require("../utils") +const {getOctokit} = require("./octokit") +const {errorRepoNotFound, errorLabelExists} = require("../errors") + +const getLabels = async repo => { + const octokit = getOctokit() + + const parameters = repoObject(repo) + const response = await octokit.issues.listLabelsForRepo(parameters) + + const labels = response.data + return labels +} + +const deleteLabels = async (labels, repo) => { + console.log() + console.log(`Deleting labels from ${chalk.cyanBright(repo)}`) + console.log() + + const octokit = getOctokit() + + labels.forEach(label => { + const {name} = label + console.log(` ${chalk.bold.redBright("-")} ${name}`) + + const parameters = { + ...repoObject(repo), + name, + } + + octokit.issues.deleteLabel(parameters) + }) +} + +const createLabels = async (labels, repo) => { + console.log() + console.log(`Creating labels in ${chalk.cyanBright(repo)}`) + console.log() + + const octokit = getOctokit() + + labels.forEach(async label => { + const {name, color, description} = label + + const parameters = { + ...repoObject(repo), + name, + color, + description, + } + + try { + await octokit.issues.createLabel(parameters) + console.log(` ${chalk.bold.greenBright("+")} ${name}`) + } catch (error) { + const {status} = error + + switch (status) { + case 404: + errorRepoNotFound(repo) + process.exit() + break + + case 422: + errorLabelExists(name) + break + } + } + }) +} + +module.exports = { + getLabels, + deleteLabels, + createLabels, +} diff --git a/src/octokit.js b/src/github/octokit.js similarity index 100% rename from src/octokit.js rename to src/github/octokit.js diff --git a/src/github/validate.js b/src/github/validate.js new file mode 100644 index 0000000..db9058a --- /dev/null +++ b/src/github/validate.js @@ -0,0 +1,32 @@ +const {repoObject} = require("../utils") +const {createOctokit, getOctokit} = require("./octokit") + +const validToken = async token => { + const octokit = createOctokit(token) + + try { + await octokit.users.getAuthenticated() + } catch (error) { + return false + } + + return true +} + +const validRepo = async repo => { + const octokit = getOctokit() + const parameters = repoObject(repo) + + try { + await octokit.repos.get(parameters) + } catch (error) { + return false + } + + return true +} + +module.exports = { + validToken, + validRepo, +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..71343ec --- /dev/null +++ b/src/utils.js @@ -0,0 +1,19 @@ +const repoPath = object => { + const {owner, repo} = object + const path = `${owner}/${repo}` + + return path +} + +const repoObject = string => { + const [owner, repo] = string.split("/") + + const object = { + owner, + repo, + } + + return object +} + +module.exports = {repoPath, repoObject}