diff --git a/package.json b/package.json index b630333..c220701 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "fs-extra": "^9.1.0", "got": "^11.8.1", "graphql": "^15.5.0", + "inquirer": "^7.3.3", "simple-git": "^2.31.0" }, "devDependencies": { @@ -55,6 +56,8 @@ "@oclif/dev-cli": "^1.26.0", "@types/fs-extra": "^9.0.6", "@types/iarna__toml": "^2.0.1", + "@types/inquirer": "^7.3.1", + "@types/node": "^14.14.25", "eslint": "^7.19.0", "eslint-config-airbnb-typescript": "^12.0.0", "eslint-config-prettier": "^7.2.0", diff --git a/src/cli/account.ts b/src/cli/account.ts new file mode 100644 index 0000000..259a561 --- /dev/null +++ b/src/cli/account.ts @@ -0,0 +1,165 @@ +import { Command, flags } from '@oclif/command'; +import assert from 'assert'; +import child from 'child_process'; +import inquirer from 'inquirer'; +import { + CopycatProfile, + CopycatProfileBase, + Vendor, + VENDOR_TYPE, + CopycatVendorConfig, +} from '../types'; +import { getConfig, getConfigPath, setConfig } from '../utils'; + +export default class Account extends Command { + static description = 'manage accounts used by copycat'; + + static flags = { + help: flags.help({ char: 'h' }), + }; + + static args = [ + { + name: 'subcommand', + required: true, + options: ['get', 'list', 'edit', 'create'], + description: 'the action to run with the accounts', + }, + { + name: 'name', + description: 'account name (for "get" subcommand)', + }, + ]; + + static strict = true; + + protected getPrintableAccountInfo(account: CopycatProfile) { + return ` + Name:\t${account.name} + Vendor:\t${account.vendor.type} + Domain:\t${account.config.domain} + ` + .trim() + .split('\n') + .map((l) => l.trim()) + .join('\n'); + } + + async run() { + const { args } = this.parse(Account); + + const config = getConfig(); + const { vendorConfigs } = config; + + switch (args.subcommand) { + case 'list': + console.log(vendorConfigs.map(this.getPrintableAccountInfo).join('\n---\n')); + break; + + case 'get': + const account = vendorConfigs.find((con) => con.name === args.name); + if (account) { + console.log(this.getPrintableAccountInfo(account)); + } else { + console.log('Account not found'); + } + break; + + case 'edit': + child.spawn(process.env.EDITOR || 'micro', [getConfigPath()]); + break; + + case 'create': + const answers = (await inquirer.prompt([ + { + type: 'list', + name: 'type', + message: 'Vendor type', + choices: Object.values(VENDOR_TYPE), + }, + { + type: 'input', + name: 'domain', + message: 'Vendor domain', + default: ({ type }: { type: VENDOR_TYPE }) => { + if (type === VENDOR_TYPE.GITHUB) { + return 'github.com'; + } + return null; + }, + validate: (val: string) => { + try { + const url = new URL('https://' + val); + assert(url.pathname === '/'); + return true; + } catch (e) { + return 'Enter valid domain name'; + } + }, + }, + { + type: 'password', + name: 'token', + message: ({ type, domain }: { type: VENDOR_TYPE; domain: string }) => { + let tokenGettingURL: string | null = null; + let scopes: string = '[unknown]'; + if (type === VENDOR_TYPE.GITHUB) { + tokenGettingURL = `https://${domain}/settings/tokens/new`; + scopes = 'repo or public_repo'; + } else if (type === VENDOR_TYPE.GITLAB) { + tokenGettingURL = `https://${domain}/-/profile/personal_access_tokens`; + scopes = 'api'; + } + return ( + 'Authentication token' + + (tokenGettingURL + ? ` (you can get a one here: ${tokenGettingURL} , required scopes are: ${scopes})` + : '') + ); + }, + validate: (val: string) => { + if (!val) { + return 'Enter a token'; + } + return true; + }, + mask: '*', + }, + { + type: 'input', + name: 'name', + message: 'Choose a name for your account to reference to it', + validate: (val: string) => { + if (!val) { + return 'Enter a name'; + } + if (config.vendorConfigs.find((v) => v.name === val)) { + return 'Account name must be unique'; + } + return true; + }, + }, + ])) as { + type: VENDOR_TYPE; + domain: string; + token: string; + name: string; + }; + + config.vendorConfigs.push({ + vendor: { + type: answers.type, + domain: answers.domain, + }, + config: { + domain: answers.domain, + token: answers.token, + }, + name: answers.name, + } as CopycatProfile); + + setConfig(config); + break; + } + } +} diff --git a/yarn.lock b/yarn.lock index bb9373a..c3be69d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1218,6 +1218,14 @@ dependencies: "@types/node" "*" +"@types/inquirer@^7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.1.tgz#1f231224e7df11ccfaf4cf9acbcc3b935fea292d" + integrity sha512-osD38QVIfcdgsPCT0V3lD7eH0OFurX71Jft18bZrsVQWVRt6TuxRzlr0GJLrxoHZR2V5ph7/qP8se/dcnI7o0g== + dependencies: + "@types/through" "*" + rxjs "^6.4.0" + "@types/js-yaml@^3.12.5": version "3.12.5" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb" @@ -1257,6 +1265,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== +"@types/node@^14.14.25": + version "14.14.25" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.25.tgz#15967a7b577ff81383f9b888aa6705d43fbbae93" + integrity sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -1269,6 +1282,13 @@ dependencies: "@types/node" "*" +"@types/through@*": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" + integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== + dependencies: + "@types/node" "*" + "@types/websocket@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.1.tgz#039272c196c2c0e4868a0d8a1a27bbb86e9e9138" @@ -4971,6 +4991,13 @@ rxjs@^6.3.3, rxjs@^6.6.0: dependencies: tslib "^1.9.0" +rxjs@^6.4.0: + version "6.6.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552" + integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ== + dependencies: + tslib "^1.9.0" + safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"