From 9a8d1d17c4beb6890457058611ee4d01727f3ae1 Mon Sep 17 00:00:00 2001 From: Zak Henry Date: Sat, 13 Jul 2019 17:57:04 +0100 Subject: [PATCH] refactor(Lib): Use commander library rather than minimist for more expansible command structure. --- .gitignore | 1 + package.json | 5 +- src/dev-to-git.interface.ts | 7 +++ src/dev-to-git.ts | 96 ++++++++++++++++++++++++++----------- src/helpers.ts | 10 +++- yarn.lock | 2 +- 6 files changed, 88 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 62af023..a7abf88 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist coverage bin .rpt2_cache +.idea diff --git a/package.json b/package.json index 0720657..fb6c60e 100644 --- a/package.json +++ b/package.json @@ -123,9 +123,10 @@ "typescript": "^3.0.3" }, "dependencies": { + "chalk": "^2.4.2", + "commander": "^2.20.0", "dotenv": "8.0.0", "front-matter": "3.0.2", - "got": "9.6.0", - "minimist": "1.2.0" + "got": "9.6.0" } } diff --git a/src/dev-to-git.interface.ts b/src/dev-to-git.interface.ts index 38a0230..61c5cc3 100644 --- a/src/dev-to-git.interface.ts +++ b/src/dev-to-git.interface.ts @@ -25,6 +25,13 @@ export enum UpdateStatus { FAILED_TO_EXTRACT_FRONT_MATTER = 'FailedToExtractFrontMatter', } +export interface ConfigurationOptions { + silent: boolean; + config: string; // the config file path + devToToken: string; + repository: Repository; +} + export type ArticlePublishedStatus = { articleId: number; } & ( diff --git a/src/dev-to-git.ts b/src/dev-to-git.ts index ab1a6c4..b892da4 100644 --- a/src/dev-to-git.ts +++ b/src/dev-to-git.ts @@ -1,58 +1,94 @@ -import minimist from 'minimist'; -import fs from 'fs'; +import chalk from 'chalk'; +import program from 'commander'; import dotenv from 'dotenv'; -import { ArticleConfig, ArticleConfigFile, Repository, ArticlePublishedStatus } from './dev-to-git.interface'; +import fs from 'fs'; import { Article } from './article'; -import { formatArticlePublishedStatuses } from './helpers'; +import { + ArticleConfig, + ArticleConfigFile, + ArticlePublishedStatus, + ConfigurationOptions, + Repository, +} from './dev-to-git.interface'; +import { formatArticlePublishedStatuses, logBuilder, Logger } from './helpers'; export const DEFAULT_CONFIG_PATH: string = './dev-to-git.json'; const repositoryRe: RegExp = /.*\/(.*)\/(.*)\.git/; export class DevToGit { - private configPath: string = DEFAULT_CONFIG_PATH; - private token: string = ''; - private repository: Repository = { username: '', name: '' }; + private configuration: ConfigurationOptions; + + public logger: Logger; constructor() { dotenv.config(); - const { config } = minimist(process.argv.slice(2)); + const pkg = require('../package.json'); + + program + .version(pkg.version) + .arguments('[...files]') + .option('--config ', `Pass custom path to .dev-to-git.json file`, DEFAULT_CONFIG_PATH) + .option( + '--dev-to-token ', + 'Token for publishing to dev.to', + userValue => userValue || process.env.DEV_TO_GIT_TOKEN, + ) + .option('--repository-url ', 'Url of your repository you keep your articles in.') + .option('--silent', `No console output`) + .parse(process.argv); + + const configuration: ConfigurationOptions = (program as unknown) as ConfigurationOptions; + this.configuration = configuration; - if (config && typeof config === 'string') { - this.configPath = config; + this.logger = logBuilder(this.configuration); + + this.configuration.repository = this.parseRepository(program.repositoryUrl) || this.extractRepository(); + + if (!this.configuration.devToToken) { + this.logger(chalk.red('DEV_TO_GIT_TOKEN environment variable, or --dev-to-token argument is required')); + process.exit(1); } + } - this.extractRepository(); + private parseRepository(repo: string): Repository | null { + const match = repo.match(repositoryRe); - if (!process.env.DEV_TO_GIT_TOKEN) { - throw new Error('Token is required'); + if (!match) { + return null; } - this.token = process.env.DEV_TO_GIT_TOKEN; + return { + username: match![1], + name: match![2], + }; } - private extractRepository(): void { + private extractRepository(): Repository { try { const packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); - const matchRepositoryUrl = (packageJson.repository.url as string).match(repositoryRe); + const repo = this.parseRepository(packageJson.repository.url); - if (matchRepositoryUrl) { - const [_, username, name] = matchRepositoryUrl; - this.repository = { username, name }; - } else { - throw new Error(); + if (!repo) { + throw Error(); } + + return repo; } catch (error) { - throw new Error( - 'You must have within your "package.json" a "repository" attribute which is an object and contains itself an attribute "url" like the following: https://github-gitlab-whatever.com/username/repository-name.git - this will be used to generate images links if necessary', + this.logger( + chalk.red( + 'If you do not specify --repository-url, you must have within your "package.json" a "repository" attribute which is an object and contains itself an attribute "url" like the following: https://github-gitlab-whatever.com/username/repository-name.git - this will be used to generate images links if necessary', + ), ); + process.exit(1); } + throw new Error('Should not be reached'); } public getConfigPath(): string { - return this.configPath; + return this.configuration.config; } public readConfigFile(): ArticleConfig[] { @@ -64,7 +100,7 @@ export class DevToGit { return articleConfigFiles.map(articleConfigFile => ({ ...articleConfigFile, - repository: this.repository, + repository: this.configuration.repository, })); } @@ -74,7 +110,7 @@ export class DevToGit { return Promise.all( articles.map(articleConf => { const article = new Article(articleConf); - return article.publishArticle(this.token); + return article.publishArticle(this.configuration.devToToken); }), ); } @@ -85,7 +121,9 @@ const devToGit = new DevToGit(); devToGit .publishArticles() .then(formatArticlePublishedStatuses) - .then(console.log) - .catch(() => { - throw new Error(`An error occured while publishing the articles`); + .then(statuses => devToGit.logger(statuses)) + .catch(err => { + devToGit.logger(chalk.red(`An error occurred while publishing the articles`)); + console.error(err); + process.exit(1); }); diff --git a/src/helpers.ts b/src/helpers.ts index 9691c89..0212f6d 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,4 +1,4 @@ -import { ArticlePublishedStatus, UpdateStatus } from './dev-to-git.interface'; +import { ArticlePublishedStatus, ConfigurationOptions, UpdateStatus } from './dev-to-git.interface'; export const formatArticlePublishedStatuses = (articlePublishedStatuses: ArticlePublishedStatus[]): string => { return articlePublishedStatuses @@ -33,3 +33,11 @@ class UnreachabelCase { // tslint:disable-next-line:no-empty constructor(payload: never) {} } + +export type Logger = (...messages: string[]) => void; + +export const logBuilder = (options: ConfigurationOptions): Logger => (...messages: string[]) => { + if (!options.silent) { + console.log(...messages); + } +}; diff --git a/yarn.lock b/yarn.lock index 28dc4cf..8e1be88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2114,7 +2114,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.12.1, commander@^2.14.1, commander@^2.9.0, commander@~2.20.0: +commander@^2.12.1, commander@^2.14.1, commander@^2.20.0, commander@^2.9.0, commander@~2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==