diff --git a/.gitignore b/.gitignore index 404cded..62af023 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules docs dist coverage +bin +.rpt2_cache diff --git a/package.json b/package.json index db51eaa..1befee2 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,15 @@ "version": "0.0.0", "description": "", "keywords": [], - "main": "dist/dev-to-git.umd.js", - "module": "dist/dev-to-git.es5.js", - "typings": "dist/types/dev-to-git.d.ts", + "main": "bin/dev-to-git.umd.js", + "module": "bin/dev-to-git.es5.js", + "typings": "bin/types/dev-to-git.d.ts", "files": [ - "dist" + "bin" ], + "bin": { + "dev-to-git": "bin/index.js" + }, "author": "Maxime Robert ", "repository": { "type": "git", @@ -19,11 +22,12 @@ "node": ">=6.0.0" }, "scripts": { + "copy-index": "cp ./src/index.js ./bin", "prettier:base": "yarn run prettier \"./{src,test}/**/*.ts\" \"./**/*.{yml,md,json}\"", "prettier:fix": "yarn run prettier:base --write", "lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'", - "prebuild": "rimraf dist", - "build": "tsc --module commonjs && rollup -c rollup.config.ts && typedoc --out docs --target es6 --theme minimal --mode file src", + "prebuild": "rimraf bin", + "build": "tsc --module commonjs && rollup -c rollup.config.ts && typedoc --out docs --target es6 --theme minimal --mode file src && yarn run copy-index", "start": "rollup -c rollup.config.ts -w", "test": "jest --coverage", "test:watch": "jest --coverage --watch", @@ -86,7 +90,10 @@ "devDependencies": { "@commitlint/cli": "^7.1.2", "@commitlint/config-conventional": "^7.1.2", + "@types/dotenv": "6.1.1", + "@types/got": "9.6.0", "@types/jest": "^23.3.2", + "@types/minimist": "1.2.0", "@types/node": "^10.11.0", "colors": "^1.3.2", "commitizen": "^3.0.0", @@ -118,5 +125,10 @@ "tslint-config-standard": "^8.0.1", "typedoc": "^0.12.0", "typescript": "^3.0.3" + }, + "dependencies": { + "dotenv": "8.0.0", + "got": "9.6.0", + "minimist": "1.2.0" } } diff --git a/src/article.ts b/src/article.ts new file mode 100644 index 0000000..0069c20 --- /dev/null +++ b/src/article.ts @@ -0,0 +1,32 @@ +import { ArticleConfig, ArticleApi } from './dev-to-git.interface' +import got from 'got' +import fs from 'fs' + +export class Article { + constructor(private articleConfig: ArticleConfig, private token: string) {} + + public readArticleOnDisk(): string { + return fs.readFileSync(this.articleConfig.relativePathToArticle).toString() + } + + public publishArticle(): got.GotPromise { + const body: ArticleApi = { + title: this.articleConfig.title, + description: this.articleConfig.description, + body_markdown: this.readArticleOnDisk(), + published: this.articleConfig.published, + tags: this.articleConfig.tags, + series: this.articleConfig.series, + publish_under_org: this.articleConfig.publishUnderOrg, + main_image: this.articleConfig.urlToMainImage, + canonical_url: this.articleConfig.canonicalUrl + } + + return got(`https://dev.to/api/articles/${this.articleConfig.id}`, { + json: true, + method: 'PUT', + headers: { 'api-key': this.token }, + body + }) + } +} diff --git a/src/dev-to-git.interface.ts b/src/dev-to-git.interface.ts new file mode 100644 index 0000000..a63c7b6 --- /dev/null +++ b/src/dev-to-git.interface.ts @@ -0,0 +1,25 @@ +export interface ArticleConfig { + title: string + description: string + id: string + published: string + urlToMainImage: string + tags: string[] + relativePathToArticle: string + series: string + publishUnderOrg: boolean + canonicalUrl: string +} + +// https://dev.to/api#available-json-parameters +export interface ArticleApi { + title: string + description: string + body_markdown: string + published: string + tags: string[] + series: string + publish_under_org: boolean + main_image?: string + canonical_url?: string +} diff --git a/src/dev-to-git.ts b/src/dev-to-git.ts index a2d64a3..5a9b9c7 100644 --- a/src/dev-to-git.ts +++ b/src/dev-to-git.ts @@ -1,6 +1,52 @@ -// Import here Polyfills if needed. Recommended core-js (npm i -D core-js) - // import "core-js/fn/array.find" - // ... -export default class DummyClass { +import minimist from 'minimist' +import fs from 'fs' +import dotenv from 'dotenv' +import { ArticleConfig, ArticleApi } from './dev-to-git.interface' +import { Article } from './article' +export const DEFAULT_CONFIG_PATH: string = './dev-to-git.json' + +export class DevToGit { + private configPath: string = DEFAULT_CONFIG_PATH + private token: string = '' + + constructor() { + dotenv.config() + + const { config } = minimist(process.argv.slice(2)) + + if (config && typeof config === 'string') { + this.configPath = config + } + + if (!process.env.DEV_TO_GIT_TOKEN) { + throw new Error('Token is required') + } + + this.token = process.env.DEV_TO_GIT_TOKEN + } + + public getConfigPath(): string { + return this.configPath + } + + public readConfigFile(): ArticleConfig[] { + // @todo check structure of the object + + return JSON.parse( + fs.readFileSync(this.getConfigPath()).toString() + ) as ArticleConfig[] + } + + public publishArticles() { + const articles = this.readConfigFile() + articles.forEach(articleConf => { + const article = new Article(articleConf, this.token) + article.publishArticle() + }) + } } + +// @todo move to main file? +const devToGit = new DevToGit() +devToGit.publishArticles() diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e356757 --- /dev/null +++ b/src/index.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('./dev-to-git.umd.js'); diff --git a/test/article.md b/test/article.md new file mode 100644 index 0000000..8c48886 --- /dev/null +++ b/test/article.md @@ -0,0 +1,3 @@ +# This is my awesome article! + +Hey, some text! diff --git a/test/dev-to-git.json b/test/dev-to-git.json index 298cd7d..9afd0d6 100644 --- a/test/dev-to-git.json +++ b/test/dev-to-git.json @@ -1,13 +1,14 @@ [ { - "title": "", - "description": "", - "published": "", - "urlToMainImage": "", - "tags": [], - "relativePathToArticle": "", - "series": "", - "publishUnderOrg": "", - "canonicalUrl": "" + "id": 132750, + "title": "Updated title", + "description": "Great description", + "published": "false", + "urlToMainImage": null, + "tags": ["typescript"], + "relativePathToArticle": "./test/article.md", + "series": null, + "publishUnderOrg": false, + "canonicalUrl": null } ] diff --git a/test/dev-to-git.test.ts b/test/dev-to-git.test.ts index d7d620f..f790577 100644 --- a/test/dev-to-git.test.ts +++ b/test/dev-to-git.test.ts @@ -1,14 +1,82 @@ -import DummyClass from "../src/dev-to-git" - -/** - * Dummy test - */ -describe("Dummy test", () => { - it("works if true is truthy", () => { - expect(true).toBeTruthy() +import { DevToGit, DEFAULT_CONFIG_PATH } from '../src/dev-to-git' + +describe(`DevToGit`, () => { + beforeEach(() => { + process.argv = ['don-t-care', 'don-t-care'] + process.env.DEV_TO_GIT_TOKEN = 'token' }) - it("DummyClass is instantiable", () => { - expect(new DummyClass()).toBeInstanceOf(DummyClass) + describe(`Config`, () => { + describe(`Get config`, () => { + it(`should have by default a path "./dev-to-git.json"`, () => { + const devToGit = new DevToGit() + expect(devToGit.getConfigPath()).toBe(DEFAULT_CONFIG_PATH) + }) + + it(`should accept a "config" argument to change the path to the config`, () => { + const CUSTOM_CONFIG_PATH: string = './custom/dev-to-git.json' + process.argv = [ + 'don-t-care', + 'don-t-care', + '--config', + CUSTOM_CONFIG_PATH + ] + const devToGit = new DevToGit() + expect(devToGit.getConfigPath()).toBe(CUSTOM_CONFIG_PATH) + }) + + it(`should use the default path if the "config" flag is passed without nothing`, () => { + process.argv = ['don-t-care', 'don-t-care', '--config'] + const devToGit = new DevToGit() + expect(devToGit.getConfigPath()).toBe(DEFAULT_CONFIG_PATH) + }) + }) + + describe(`Read config from file`, () => { + it(`test`, () => { + process.argv = [ + 'don-t-care', + 'don-t-care', + '--config', + './test/dev-to-git.json' + ] + + const devToGit = new DevToGit() + + expect(devToGit.readConfigFile()).toEqual(require('./dev-to-git.json')) + }) + }) }) + + // describe(`Article`, () => { + // describe(`Read`, () => { + // it(`should read an article from the configuration`, () => { + // const CUSTOM_CONFIG_PATH: string = './test/dev-to-git.json' + // process.argv = [ + // 'don-t-care', + // 'don-t-care', + // '--config', + // CUSTOM_CONFIG_PATH + // ] + // const devToGit = new DevToGit() + // expect(devToGit.readArticleOnDisk()).toContain( + // 'This is my awesome article!' + // ) + // expect(devToGit.readArticleOnDisk()).toContain('Hey, some text!') + // }) + // }) + // describe(`Publish`, () => { + // it(`should publish the article`, () => { + // const CUSTOM_CONFIG_PATH: string = './test/dev-to-git.json' + // process.argv = [ + // 'don-t-care', + // 'don-t-care', + // '--config', + // CUSTOM_CONFIG_PATH + // ] + // const devToGit = new DevToGit() + // devToGit.publishArticle(devToGit.readConfigFile()[0]) + // }) + // }) + // }) }) diff --git a/tsconfig.json b/tsconfig.json index f64f431..acedf4c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,8 +10,8 @@ "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "declarationDir": "dist/types", - "outDir": "dist/lib", + "declarationDir": "bin/types", + "outDir": "bin/lib", "typeRoots": ["node_modules/@types"] }, "include": ["src"] diff --git a/yarn.lock b/yarn.lock index 4601b5d..6300eae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -968,6 +968,13 @@ dependencies: defer-to-connect "^1.0.1" +"@types/dotenv@6.1.1": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-6.1.1.tgz#f7ce1cc4fe34f0a4373ba99fefa437b0bec54b46" + integrity sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg== + dependencies: + "@types/node" "*" + "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" @@ -994,6 +1001,14 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/got@9.6.0": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@types/got/-/got-9.6.0.tgz#bba73ff0a46b2691ddb3d86e89f8b2f43ebb829d" + integrity sha512-t00QebxBllcawe7uMxi9jsfWfbki4+/zpJEPyoGWjUvnFOy4EkAzoYOI2UJt2+Fx0yIcDX2+us93k6ensKTddA== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + "@types/handlebars@^4.0.38": version "4.1.0" resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.1.0.tgz#3fcce9bf88f85fe73dc932240ab3fb682c624850" @@ -1059,6 +1074,11 @@ "@types/glob" "*" "@types/node" "*" +"@types/tough-cookie@*": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" + integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg== + JSONStream@^1.0.4, JSONStream@^1.3.4, JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -2671,6 +2691,11 @@ dot-prop@^4.1.0: dependencies: is-obj "^1.0.0" +dotenv@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.0.0.tgz#ed310c165b4e8a97bb745b0a9d99c31bda566440" + integrity sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg== + dotenv@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" @@ -3557,6 +3582,23 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +got@9.6.0, got@^9.1.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -3574,23 +3616,6 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -got@^9.1.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"