├── .eslintignore
├── .hound.yml
├── .commit-template
├── .github
├── FUNDING.yml
└── workflows
│ ├── tests.yml
│ ├── release.yml
│ └── announce-it.yaml
├── src
├── index.ts
├── announce-it-cli.test.ts
├── announce-it-cli.ts
├── __snapshots__
│ ├── read-package-details.test.ts.snap
│ ├── announce-it-cli-utils.test.ts.snap
│ └── announce-it.test.ts.snap
├── read-package-details.ts
├── read-package-details.test.ts
├── announce-it-cli-utils.ts
├── announce-it-cli-utils.test.ts
├── announce-it.ts
└── announce-it.test.ts
├── .env.example
├── bin
└── announce-it-cli.js
├── .vscode
├── extensions.json
└── settings.json
├── types
├── manakin.d.ts
└── twitter-lite.d.ts
├── commitlint.config.js
├── examples
└── as-a-node-module
│ ├── package.json
│ └── index.ts
├── jest.config.js
├── .eslintrc.js
├── .all-contributorsrc
├── LICENSE
├── .gitignore
├── package.json
├── tsconfig.json
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | .eslintrc.js
--------------------------------------------------------------------------------
/.hound.yml:
--------------------------------------------------------------------------------
1 | tslint:
2 | config_file: tslint.json
3 |
--------------------------------------------------------------------------------
/.commit-template:
--------------------------------------------------------------------------------
1 | type(scope): subject
2 |
3 | description
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom:
2 | - https://paypal.me/thatkookooguy?locale.x=en_US
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './announce-it';
2 | export * from './read-package-details';
3 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Twitter auth
2 | CONSUMER_KEY=
3 | CONSUMER_SECRET=
4 | ACCESS_TOKEN_KEY=
5 | ACCESS_TOKEN_SECRET=
6 |
--------------------------------------------------------------------------------
/bin/announce-it-cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // add console colors
3 | require('manakin').global;
4 |
5 | const cli = require('../lib/announce-it-cli');
6 |
7 | cli.run();
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "rbbit.typescript-hero",
4 | "orta.vscode-jest",
5 | "johnpapa.vscode-peacock",
6 | "coenraads.bracket-pair-colorizer"
7 | ]
8 | }
--------------------------------------------------------------------------------
/src/announce-it-cli.test.ts:
--------------------------------------------------------------------------------
1 | describe('announce-it-cli', () => {
2 | it.todo('should get parameters from either env or command line');
3 |
4 | it.todo('should run the cli if everything is set-up');
5 |
6 | it.todo('should throw meaningful error if something went wrong');
7 | });
8 |
--------------------------------------------------------------------------------
/types/manakin.d.ts:
--------------------------------------------------------------------------------
1 | export = index;
2 | declare const index: {
3 | error: Function;
4 | global: any;
5 | info: Function;
6 | local: any;
7 | log: Function;
8 | ok: Function;
9 | setBright: Function;
10 | success: Function;
11 | warn: Function;
12 | write: Function;
13 | };
14 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [ '@commitlint/config-angular' ],
3 | rules: {
4 | 'type-enum': [
5 | 2,
6 | 'always', [
7 | 'build',
8 | 'chore',
9 | 'ci',
10 | 'docs',
11 | 'feat',
12 | 'fix',
13 | 'perf',
14 | 'refactor',
15 | 'revert',
16 | 'style',
17 | 'test'
18 | ]
19 | ]
20 | }
21 | };
--------------------------------------------------------------------------------
/examples/as-a-node-module/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "as-a-node-module",
3 | "version": "1.0.0",
4 | "description": "this is an example for using announce it as a node module",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "k1b1b0t",
10 | "license": "ISC",
11 | "announcements": {
12 | "tweet": "Version <%= version %> of <%= package %> is live! check it out <%=homepage %>"
13 | },
14 | "dependencies": {
15 | "@kibibit/announce-it": "file:../.."
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | coverageReporters: ["json", "lcov", "text", "clover", "html"],
5 | modulePathIgnorePatterns: ["/lib/"],
6 | collectCoverageFrom: [
7 | "src/**/*.ts",
8 | "!src/**/index.ts",
9 | "!src/announce-it-cli.ts"
10 | ],
11 | watchPathIgnorePatterns: [
12 | ".*test-results.*\\.js"
13 | ],
14 | reporters: ["default", "jest-stare", "jest-github-actions-reporter"],
15 | testResultsProcessor: "./node_modules/jest-stare",
16 | "testLocationInResults": true
17 | };
18 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: "Run Tests"
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - next
8 | pull_request:
9 | branches:
10 | - master
11 | - next
12 |
13 | jobs:
14 | build:
15 | name: Run Tests Job
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v1
19 | - run: npm ci
20 | - run: npm run build --if-present
21 | - run: npm test
22 | # - name: Coveralls
23 | # uses: coverallsapp/github-action@master
24 | # with:
25 | # github-token: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/examples/as-a-node-module/index.ts:
--------------------------------------------------------------------------------
1 | import { KbAnnounceIt, PackageDetails } from '@kibibit/announce-it';
2 |
3 | const announceIt = new KbAnnounceIt({
4 | accessTokenKey: 'TWITTER_ACCESS_KEY',
5 | accessTokenSecret: 'TWITTER_ACCESS_SECRET',
6 | consumerKey: 'TWITTER_CONSUMER_KEY',
7 | consumerSecret: 'TWITTER_CONSUMER_SECRET'
8 | });
9 |
10 | const myPackage: PackageDetails = require('./package');
11 |
12 | // get generated tweet
13 | const tweet: string = announceIt.generateTweet(myPackage);
14 |
15 | console.log('going to publish this tweet!', tweet);
16 |
17 | // publish tweet to twitter
18 | announceIt.announceRelease(myPackage);
19 |
--------------------------------------------------------------------------------
/src/announce-it-cli.ts:
--------------------------------------------------------------------------------
1 | import nconf from 'nconf';
2 |
3 | import { AnnounceItCli } from './announce-it-cli-utils';
4 |
5 | export async function run(): Promise {
6 | nconf.argv()
7 | .env();
8 |
9 | const announceItCli = new AnnounceItCli();
10 |
11 | const parameters = nconf.get();
12 | const cwd = process.cwd();
13 |
14 | return announceItCli.areVariablesDefined(parameters)
15 | .then(() => announceItCli.findRoot(cwd))
16 | .then((root) => announceItCli.runAnnounceItCli(root, parameters))
17 | .catch((error: Error) => {
18 | console.error('ERROR: Something went wrong');
19 | console.error(error);
20 |
21 | throw error;
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/types/twitter-lite.d.ts:
--------------------------------------------------------------------------------
1 | export = Twitter;
2 |
3 | interface TwitterOptions {
4 | subdomain: string;
5 | consumer_key: string;
6 | consumer_secret: string;
7 | access_token_key: string;
8 | access_token_secret: string;
9 | }
10 |
11 | declare class Twitter {
12 | constructor(options: TwitterOptions);
13 |
14 | authType: any;
15 | client: any;
16 | token: any;
17 | url: any;
18 | oauth: any;
19 | config: any;
20 | get(e: any, t: any): Promise;
21 | getAccessToken(e: any): any;
22 | getBearerToken(): any;
23 | getRequestToken(e: any): any;
24 | post(e: any, t: any): Promise;
25 | stream(e: any, t: any): any;
26 | }
27 |
28 | declare interface KbTwitter {
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_run:
5 | workflows: [ "Run Tests" ]
6 | branches: [ master, next ]
7 | types:
8 | - completed
9 |
10 | jobs:
11 | release:
12 | name: Release
13 | runs-on: ubuntu-18.04
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 | with:
18 | fetch-depth: 0
19 | - name: Setup Node.js
20 | uses: actions/setup-node@v1
21 | with:
22 | node-version: 12
23 | - name: Install dependencies
24 | run: |
25 | npm ci
26 | npm install
27 | npm run build
28 | - name: Release
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
32 | run: npm run semantic-release
--------------------------------------------------------------------------------
/src/__snapshots__/read-package-details.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`readPackageDetails should return the package details 1`] = `
4 | Object {
5 | "announcements": Object {
6 | "tweet": "nice!",
7 | },
8 | "author": "test@test.com",
9 | "description": "package description",
10 | "homepage": "pizza.com",
11 | "name": "package-name",
12 | "release": Object {
13 | "branches": Array [
14 | "TEST",
15 | ],
16 | },
17 | "repository": Object {
18 | "type": "",
19 | "url": "",
20 | },
21 | "version": "1.0.0",
22 | }
23 | `;
24 |
25 | exports[`readPackageDetails should throw error if missing tweet from package.json 1`] = `"no tweet template found. please see the readme for more details"`;
26 |
27 | exports[`readPackageDetails should throw error if reading package.json failed 1`] = `"failed reading json file"`;
28 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin', 'jest'],
8 | extends: [
9 | 'plugin:@typescript-eslint/recommended'
10 | ],
11 | root: true,
12 | env: {
13 | "node": true,
14 | "es2021": true,
15 | "jest/globals": true
16 | },
17 | rules: {
18 | "@typescript-eslint/naming-convention": [
19 | "error",
20 | {
21 | "selector": "interface",
22 | "format": ["PascalCase"],
23 | "custom": {
24 | "regex": "^I[A-Z]",
25 | "match": true
26 | }
27 | }
28 | ],
29 | '@typescript-eslint/explicit-function-return-type': 'off',
30 | '@typescript-eslint/explicit-module-boundary-types': 'off',
31 | '@typescript-eslint/no-explicit-any': 'off',
32 | },
33 | };
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "announce-it",
3 | "projectOwner": "kibibit",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "contributors": [
12 | {
13 | "login": "ZimGil",
14 | "name": "Gil Tichon",
15 | "avatar_url": "https://avatars3.githubusercontent.com/u/39461857?v=4",
16 | "profile": "https://github.com/ZimGil",
17 | "contributions": [
18 | "infra",
19 | "code",
20 | "projectManagement"
21 | ]
22 | },
23 | {
24 | "login": "Thatkookooguy",
25 | "name": "Neil Kalman",
26 | "avatar_url": "https://avatars3.githubusercontent.com/u/10427304?v=4",
27 | "profile": "http://thatkookooguy.kibibit.io",
28 | "contributions": [
29 | "infra",
30 | "ideas"
31 | ]
32 | }
33 | ],
34 | "contributorsPerLine": 7
35 | }
36 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "peacock.color": "#ffa63f",
3 | "editor.tabSize": 2,
4 | "editor.insertSpaces": true,
5 | "editor.detectIndentation": false,
6 | "editor.rulers": [80],
7 | "editor.matchBrackets": "always",
8 | // Bracket Pair Colorizer
9 | "bracketPairColorizer.colorMode": "Consecutive",
10 | "bracketPairColorizer.forceUniqueOpeningColor": true,
11 | "bracketPairColorizer.showBracketsInGutter": true,
12 | "window.title": "${activeEditorShort}${separator}${rootName} [kibibit]",
13 | "typescriptHero.imports.stringQuoteStyle": "'",
14 | "typescriptHero.imports.grouping": [
15 | "Plains",
16 | "Modules",
17 | "/^@kibibit/",
18 | "/^@kb-/",
19 | "Workspace"
20 |
21 | ],
22 | "typescriptHero.imports.organizeOnSave": true,
23 | "typescriptHero.imports.multiLineTrailingComma": false,
24 | "editor.codeActionsOnSave": {
25 | "source.fixAll.eslint": true
26 | }
27 | }
--------------------------------------------------------------------------------
/src/__snapshots__/announce-it-cli-utils.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`AnnounceItCli areVariablesDefined should reject if at least 1 required variable is missing 1`] = `"These Variables are required: TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN_KEY, TWITTER_ACCESS_TOKEN_SECRET, branch"`;
4 |
5 | exports[`AnnounceItCli areVariablesDefined should reject if at least 1 required variable is missing 2`] = `"These Variables are required: TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, TWITTER_ACCESS_TOKEN_KEY, TWITTER_ACCESS_TOKEN_SECRET, branch"`;
6 |
7 | exports[`AnnounceItCli areVariablesDefined should resolve if all required variables exists 1`] = `undefined`;
8 |
9 | exports[`AnnounceItCli findRoot should return the project root folder if ran inside npm project 1`] = `"project-root"`;
10 |
11 | exports[`AnnounceItCli findRoot should throw error if not inside npm project 1`] = `"couldn't find npm project root: failure (test mock)"`;
12 |
--------------------------------------------------------------------------------
/src/read-package-details.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra';
2 | import { get, isString } from 'lodash';
3 | import { IBranchObject } from './announce-it';
4 |
5 | export interface IPackageDetails {
6 | name: string;
7 | version: string;
8 | description: string;
9 | author: string;
10 | homepage: string;
11 | repository: {
12 | type: string;
13 | url: string;
14 | };
15 | announcements: {
16 | tweet: string;
17 | includeUnstable?: boolean;
18 | };
19 | release: {
20 | branches: Array;
21 | }
22 | }
23 |
24 | export function readPackageDetails(root: string): Promise {
25 | return fs.readJson(`${ root }/package.json`)
26 | .then((packageDetails: Partial) => {
27 | if (!isString(get(packageDetails, 'announcements.tweet'))) {
28 | throw new Error('no tweet template found. please see the readme for more details');
29 | }
30 |
31 | // here, we KNOW the package has the announcement template string
32 | return packageDetails as IPackageDetails;
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/announce-it.yaml:
--------------------------------------------------------------------------------
1 | name: Announce Release
2 |
3 | on:
4 | workflow_run:
5 | workflows: [ "Release" ]
6 | branches: [ master ]
7 | types:
8 | - completed
9 |
10 | jobs:
11 | announce:
12 | runs-on: ubuntu-18.04
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v2
16 | with:
17 | fetch-depth: 0
18 | - name: Setup Node.js
19 | uses: actions/setup-node@v1
20 | with:
21 | node-version: 12
22 | - name: Install dependencies
23 | run: |
24 | npm ci
25 | npm install
26 | npm run build
27 | - name: announce-it
28 | env:
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
31 | TWITTER_ACCESS_TOKEN_KEY: ${{ secrets.TWITTER_ACCESS_TOKEN_KEY }}
32 | TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
33 | TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }}
34 | TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }}
35 | run: npm run start:ci
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 kibibit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dist files
2 | lib/
3 |
4 | # Environment variables
5 | .env
6 |
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | test-results
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # TypeScript v1 declaration files
47 | typings/
48 |
49 | # Optional npm cache directory
50 | .npm
51 |
52 | # Optional eslint cache
53 | .eslintcache
54 |
55 | # Optional REPL history
56 | .node_repl_history
57 |
58 | # Output of 'npm pack'
59 | *.tgz
60 |
61 | # Yarn Integrity file
62 | .yarn-integrity
63 |
64 | # dotenv environment variables file
65 | .env
66 |
67 | # next.js build output
68 | .next
69 |
--------------------------------------------------------------------------------
/src/read-package-details.test.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs-extra';
2 | import { assign } from 'lodash';
3 |
4 | import { IPackageDetails, readPackageDetails } from './read-package-details';
5 |
6 | const packageDetails: IPackageDetails = {
7 | name: 'package-name',
8 | description: 'package description',
9 | version: '1.0.0',
10 | author: 'test@test.com',
11 | homepage: 'pizza.com',
12 | repository: {
13 | type: '',
14 | url: ''
15 | },
16 | announcements: {
17 | tweet: 'nice!'
18 | },
19 | release: {
20 | branches: [ 'TEST' ]
21 | }
22 | };
23 |
24 | jest.mock('fs-extra');
25 | const fsMocked = fs as jest.Mocked;
26 |
27 | describe('readPackageDetails', () => {
28 | it('should return the package details', () => {
29 | fsMocked.readJson.mockResolvedValue(packageDetails);
30 | return expect(readPackageDetails('rootFolder')).resolves.toMatchSnapshot();
31 | });
32 |
33 | it('should throw error if reading package.json failed', () => {
34 | fsMocked.readJson.mockRejectedValue(new Error('failed reading json file'));
35 | return expect(readPackageDetails('rootFolder')).rejects.toThrowErrorMatchingSnapshot();
36 | });
37 |
38 | it('should throw error if missing tweet from package.json', () => {
39 | const missingDetails: Partial = assign({}, packageDetails);
40 | missingDetails.announcements = undefined;
41 | fsMocked.readJson.mockResolvedValue(missingDetails);
42 |
43 | return expect(readPackageDetails('rootFolder')).rejects.toThrowErrorMatchingSnapshot();
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/src/announce-it-cli-utils.ts:
--------------------------------------------------------------------------------
1 | import findRoot from 'find-root';
2 | import { every, isString } from 'lodash';
3 |
4 | import { KbAnnounceIt } from './announce-it';
5 | import { readPackageDetails } from './read-package-details';
6 |
7 | export class AnnounceItCli {
8 | areVariablesDefined(env: NodeJS.ProcessEnv): Promise {
9 | const variables = [
10 | 'TWITTER_CONSUMER_KEY',
11 | 'TWITTER_CONSUMER_SECRET',
12 | 'TWITTER_ACCESS_TOKEN_KEY',
13 | 'TWITTER_ACCESS_TOKEN_SECRET',
14 | 'branch'
15 | ];
16 |
17 | const areEnvVariablesDefined = every(variables, (varName) => {
18 | return isString(env[ varName ]);
19 | });
20 |
21 | if (!areEnvVariablesDefined) {
22 | return Promise.reject(
23 | new Error(`These Variables are required: ${ variables.join(', ') }`)
24 | );
25 | }
26 |
27 | return Promise.resolve();
28 | }
29 |
30 | findRoot(folder: string): Promise {
31 | return new Promise((resolve, reject) => {
32 | try {
33 | const root = findRoot(folder);
34 |
35 | resolve(root);
36 | } catch (err) {
37 | reject(new Error(`couldn't find npm project root: ${ err.message || err }`));
38 | }
39 | });
40 | }
41 |
42 | runAnnounceItCli(folder: string, env: NodeJS.ProcessEnv): Promise {
43 | return this.findRoot(folder)
44 | .then((root) => readPackageDetails(root))
45 | .then((packageDetails) => {
46 | const announceIt = new KbAnnounceIt({
47 | consumerKey: env.TWITTER_CONSUMER_KEY as string,
48 | consumerSecret: env.TWITTER_CONSUMER_SECRET as string,
49 | accessTokenKey: env.TWITTER_ACCESS_TOKEN_KEY as string,
50 | accessTokenSecret: env.TWITTER_ACCESS_TOKEN_SECRET as string,
51 | branch: env.branch as string
52 | });
53 |
54 | return announceIt.announceRelease(packageDetails);
55 | });
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/announce-it-cli-utils.test.ts:
--------------------------------------------------------------------------------
1 | import { AnnounceItCli } from './announce-it-cli-utils';
2 |
3 | jest.mock('find-root', () => (folder: string) => {
4 | if (folder === 'test-failure') {
5 | throw new Error('failure (test mock)');
6 | }
7 |
8 | return 'project-root';
9 | });
10 |
11 | describe('AnnounceItCli', () => {
12 | const announceItCli = new AnnounceItCli();
13 |
14 | describe('areVariablesDefined', () => {
15 | it('should resolve if all required variables exists', () => {
16 | return expect(announceItCli.areVariablesDefined({
17 | TWITTER_CONSUMER_KEY: 'TWITTER_CONSUMER_KEY',
18 | TWITTER_CONSUMER_SECRET: 'TWITTER_CONSUMER_SECRET',
19 | TWITTER_ACCESS_TOKEN_KEY: 'TWITTER_ACCESS_TOKEN_KEY',
20 | TWITTER_ACCESS_TOKEN_SECRET: 'TWITTER_ACCESS_TOKEN_SECRET',
21 | branch: 'TEST'
22 | })).resolves.toMatchSnapshot();
23 | });
24 | it('should reject if at least 1 required variable is missing', () => {
25 | return expect(announceItCli.areVariablesDefined({
26 | TWITTER_CONSUMER_KEY: 'TWITTER_CONSUMER_KEY',
27 | TWITTER_CONSUMER_SECRET: 'TWITTER_CONSUMER_SECRET',
28 | TWITTER_ACCESS_TOKEN_SECRET: 'TWITTER_ACCESS_TOKEN_SECRET'
29 | }))
30 | .rejects.toThrowErrorMatchingSnapshot()
31 | .then(() => expect(announceItCli.areVariablesDefined({}))
32 | .rejects.toThrowErrorMatchingSnapshot());
33 | });
34 | });
35 |
36 | describe('findRoot', () => {
37 | it('should return the project root folder if ran inside npm project', () => {
38 | return expect(announceItCli.findRoot('test-folder')).resolves.toMatchSnapshot();
39 | });
40 | it('should throw error if not inside npm project', () => {
41 | return expect(announceItCli.findRoot('test-failure')).rejects.toThrowErrorMatchingSnapshot();
42 | });
43 | });
44 |
45 | describe('runAnnounceItCli', () => {
46 | it.todo('should run the entire flow on correct input');
47 | it.todo('should throw error on incorrect input');
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/__snapshots__/announce-it.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`KbAnnounceIt should create instance when given correct parameters 1`] = `
4 | KbAnnounceIt {
5 | "branchName": "TEST",
6 | "client": Object {
7 | "get": [Function],
8 | "post": [Function],
9 | },
10 | }
11 | `;
12 |
13 | exports[`KbAnnounceIt should throw error when missing options 1`] = `"ERROR: missing required options"`;
14 |
15 | exports[`KbAnnounceIt should throw error when missing options 2`] = `"ERROR: missing required options"`;
16 |
17 | exports[`KbAnnounceIt should throw error when missing options 3`] = `"ERROR: missing required options"`;
18 |
19 | exports[`KbAnnounceIt should throw error when missing options 4`] = `"ERROR: missing required options"`;
20 |
21 | exports[`KbAnnounceIt should throw error when missing options 5`] = `"ERROR: missing required options"`;
22 |
23 | exports[`kbAnnounceIt.announceRelease should post to twitter when stable release 1`] = `"test-template"`;
24 |
25 | exports[`kbAnnounceIt.announceRelease should post to twitter when unstable release and mentioned in packageDetails 1`] = `"test-template"`;
26 |
27 | exports[`kbAnnounceIt.announceRelease should post to twitter when unstable release and mentioned in packageDetails as stable 1`] = `"test-template"`;
28 |
29 | exports[`kbAnnounceIt.announceRelease should throw an error when unstable release and mentioned in packageDetails as unstable 1`] = `"Not a stable release"`;
30 |
31 | exports[`kbAnnounceIt.announceRelease should throw an error when unstable release and not mentioned in packageDetails 1`] = `"Not a stable release"`;
32 |
33 | exports[`kbAnnounceIt.announceRelease should throw error when twitter get credentials throws an error 1`] = `"Twitter Get Error"`;
34 |
35 | exports[`kbAnnounceIt.announceRelease should throw error when twitter post tweet throws an error 1`] = `"Twitter Post Error"`;
36 |
37 | exports[`kbAnnounceIt.generateTweet should throw error on missing package details input 1`] = `"The key name is missing from given object"`;
38 |
39 | exports[`kbAnnounceIt.generateTweet should throw error on non object input 1`] = `"Expecting Object"`;
40 |
41 | exports[`kbAnnounceIt.generateTweet should throw error when using missing variables in template 1`] = `"Using missing variables in tweet template"`;
42 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kibibit/announce-it",
3 | "version": "2.0.0-next.2",
4 | "description": "Announcing your Releases To Twitter",
5 | "types": "lib/index.d.ts",
6 | "main": "lib/index.js",
7 | "files": [
8 | "/lib",
9 | "/bin"
10 | ],
11 | "bin": {
12 | "announce-it": "bin/announce-it-cli.js"
13 | },
14 | "scripts": {
15 | "build": "tsc",
16 | "contributors:add": "all-contributors add",
17 | "contributors:generate": "all-contributors generate",
18 | "coveralls": "cat ./coverage/lcov.info | coveralls",
19 | "lint:fix": "eslint src/**/*.ts --fix",
20 | "lint": "eslint src/**/*.ts",
21 | "semantic-release": "semantic-release",
22 | "start:dev": "ts-node ./src/announce-it-cli.ts",
23 | "start": "node ./bin/announce-it-cli.js",
24 | "test:watch": "jest --watchAll --coverage",
25 | "test": "jest --coverage",
26 | "start:ci": "npm start -- --branch master"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "https://github.com/Kibibit/announce-it.git"
31 | },
32 | "author": "ZimGil ",
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/Kibibit/announce-it/issues"
36 | },
37 | "homepage": "https://github.com/Kibibit/announce-it#readme",
38 | "announcements": {
39 | "tweet": "We have an announcement!\n<%= package %> <%= version %> is now live!\n\nGo check it out: <%= npmpage %>"
40 | },
41 | "devDependencies": {
42 | "@commitlint/cli": "^11.0.0",
43 | "@commitlint/config-angular": "^11.0.0",
44 | "@commitlint/config-conventional": "^11.0.0",
45 | "@semantic-release/commit-analyzer": "^8.0.1",
46 | "@semantic-release/exec": "^5.0.0",
47 | "@semantic-release/git": "^9.0.0",
48 | "@semantic-release/github": "^7.2.0",
49 | "@semantic-release/npm": "^7.0.9",
50 | "@semantic-release/release-notes-generator": "^9.0.1",
51 | "@types/dotenv": "^6.1.1",
52 | "@types/find-root": "^1.1.2",
53 | "@types/fs-extra": "^9.0.6",
54 | "@types/jest": "^26.0.20",
55 | "@types/lodash": "^4.14.167",
56 | "@types/nconf": "^0.10.0",
57 | "@typescript-eslint/eslint-plugin": "^4.13.0",
58 | "@typescript-eslint/parser": "^4.13.0",
59 | "all-contributors-cli": "^6.19.0",
60 | "commitizen": "^4.2.2",
61 | "coveralls": "3.0.9",
62 | "cz-conventional-changelog": "^3.3.0",
63 | "eslint": "^7.17.0",
64 | "eslint-plugin-jest": "^24.1.0",
65 | "husky": "^4.3.7",
66 | "jest": "^26.6.3",
67 | "jest-github-actions-reporter": "^1.0.3",
68 | "jest-stare": "^2.2.0",
69 | "npm-check": "^5.9.2",
70 | "semantic-release": "^17.3.3",
71 | "semantic-release-cli": "^5.4.1",
72 | "ts-jest": "^26.4.4",
73 | "ts-node": "^9.1.1",
74 | "typescript": "^4.1.3"
75 | },
76 | "dependencies": {
77 | "dotenv": "^8.2.0",
78 | "find-root": "^1.1.0",
79 | "fs-extra": "^9.0.1",
80 | "lodash": "^4.17.20",
81 | "manakin": "^0.5.2",
82 | "nconf": "^0.11.1",
83 | "twitter-lite": "^0.14.0"
84 | },
85 | "jest-stare": {
86 | "resultDir": "test-results/",
87 | "coverageLink": "../coverage/index.html"
88 | },
89 | "config": {
90 | "commitizen": {
91 | "path": "./node_modules/cz-conventional-changelog"
92 | }
93 | },
94 | "husky": {
95 | "hooks": {
96 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",
97 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
98 | }
99 | },
100 | "publishConfig": {
101 | "access": "public"
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/announce-it.ts:
--------------------------------------------------------------------------------
1 | import { forEach, get, isNil, isObject, template, chain, isString } from 'lodash';
2 | import Twitter from 'twitter-lite';
3 |
4 | import { IPackageDetails } from './read-package-details';
5 |
6 | export interface IKbAnnounceItOptions {
7 | consumerKey: string;
8 | consumerSecret: string;
9 | accessTokenKey: string;
10 | accessTokenSecret: string;
11 | branch: string;
12 | }
13 |
14 | export interface IBranchObject {
15 | name: string;
16 | prerelease: boolean;
17 | }
18 |
19 | export interface IStabilityGroups {
20 | stable: string[];
21 | unstable: string[];
22 | }
23 |
24 | export class KbAnnounceIt {
25 | private client: any;
26 | private branchName: string;
27 | constructor(options: IKbAnnounceItOptions) {
28 |
29 | if (!get(options, 'consumerKey') ||
30 | !get(options, 'consumerSecret') ||
31 | !get(options, 'accessTokenKey') ||
32 | !get(options, 'accessTokenSecret') ||
33 | !get(options, 'branch')) {
34 |
35 | throw new Error('ERROR: missing required options');
36 | }
37 |
38 | this.branchName = options.branch;
39 | this.client = new Twitter({
40 | subdomain: 'api',
41 | consumer_key: options.consumerKey,
42 | consumer_secret: options.consumerSecret,
43 | access_token_key: options.accessTokenKey,
44 | access_token_secret: options.accessTokenSecret
45 | });
46 | }
47 |
48 | announceRelease(packageDetails: IPackageDetails): Promise {
49 | const releaseBranches: any[] = get(packageDetails, 'release.branches');
50 |
51 | const branchesGroupedByStability: IStabilityGroups = chain(releaseBranches)
52 | .groupBy((branch: string | IBranchObject) => {
53 | if (isString(branch)) { return 'stable'; }
54 | return branch.prerelease ? 'unstable' : 'stable';
55 | })
56 | .mapValues((branches: Array) =>
57 | branches.map((branch) => isString(branch) ? branch : branch.name))
58 | .defaultsDeep({
59 | stable: [],
60 | unstable: []
61 | })
62 | .value() as any;
63 |
64 | const isIncludeUnstable = packageDetails.announcements.includeUnstable;
65 | const isUnstableRelease = branchesGroupedByStability
66 | .unstable.includes(this.branchName);
67 | const isStableRelease = branchesGroupedByStability
68 | .stable.includes(this.branchName);
69 | const isNotRelease = !isUnstableRelease && !isStableRelease;
70 |
71 | if ((!isIncludeUnstable && isUnstableRelease) || isNotRelease) {
72 | return Promise.reject(new Error('Not a stable release'));
73 | }
74 |
75 | const tweet = this.generateTweet(packageDetails);
76 |
77 | return this.client
78 | .get('account/verify_credentials')
79 | .then(() => this.client.post('statuses/update', { status: tweet }))
80 | .then(() => tweet);
81 | }
82 |
83 | generateTweet(packageDetails: IPackageDetails): string {
84 | this.ensureKeyAttributes(packageDetails);
85 |
86 | const tweetTemplate = template(packageDetails.announcements.tweet);
87 | let tweet: string;
88 | try {
89 | tweet = tweetTemplate({
90 | package: packageDetails.name,
91 | version: packageDetails.version,
92 | description: packageDetails.description,
93 | author: packageDetails.author,
94 | homepage: packageDetails.homepage,
95 | npmpage: `https://www.npmjs.com/package/${ packageDetails.name }/v/${ packageDetails.version }`
96 | });
97 | } catch (e) {
98 | throw new Error('Using missing variables in tweet template');
99 | }
100 |
101 | return tweet;
102 | }
103 |
104 | private ensureKeyAttributes(packageDetails: Partial): void {
105 | const requiredKeys = [ 'name', 'version', 'announcements.tweet' ];
106 |
107 | if (!isObject(packageDetails)) {
108 | throw new Error('Expecting Object');
109 | }
110 |
111 | forEach(requiredKeys, (key) => {
112 | if (isNil(get(packageDetails, key))) {
113 | throw new Error(`The key ${ key } is missing from given object`);
114 | }
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "*": ["types/*"]
6 | },
7 | /* Basic Options */
8 | "target": "es5",
9 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
10 | "module": "commonjs",
11 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
12 | // "lib": [], /* Specify library files to be included in the compilation. */
13 | // "allowJs": true, /* Allow javascript files to be compiled. */
14 | // "checkJs": true, /* Report errors in .js files. */
15 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
16 | "declaration": true,
17 | /* Generates corresponding '.d.ts' file. */
18 | "declarationMap": true,
19 | /* Generates a sourcemap for each corresponding '.d.ts' file. */
20 | // "sourceMap": true, /* Generates corresponding '.map' file. */
21 | // "outFile": "./", /* Concatenate and emit output to single file. */
22 | "outDir": "./lib",
23 | /* Redirect output structure to the directory. */
24 | "rootDir": "./src",
25 | /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
26 | // "composite": true, /* Enable project compilation */
27 | // "incremental": true, /* Enable incremental compilation */
28 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
29 | // "removeComments": true, /* Do not emit comments to output. */
30 | // "noEmit": true, /* Do not emit outputs. */
31 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
32 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
33 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
34 |
35 | /* Strict Type-Checking Options */
36 | "strict": true,
37 | /* Enable all strict type-checking options. */
38 | "noImplicitAny": false,
39 | /* Raise error on expressions and declarations with an implied 'any' type. */
40 | // "strictNullChecks": true, /* Enable strict null checks. */
41 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
42 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
43 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
44 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
45 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
46 |
47 | /* Additional Checks */
48 | // "noUnusedLocals": true, /* Report errors on unused locals. */
49 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
50 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
51 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
52 |
53 | /* Module Resolution Options */
54 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
55 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
56 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
57 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
58 | "typeRoots": ["./types", "./node_modules/@types/"],
59 | /* List of folders to include type definitions from. */
60 | // "types": [], /* Type declaration files to be included in compilation. */
61 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
62 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
63 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
64 |
65 | /* Source Map Options */
66 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
67 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
68 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
69 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
70 |
71 | /* Experimental Options */
72 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
73 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
74 | },
75 | "include": [
76 | "src/**/*"
77 | ],
78 | "exclude": [
79 | "*.test.ts"
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @kibibit/announce-it
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Announcing your application releases on social media
25 |
26 |
27 |
28 |
29 |
30 | ## How to install
31 |
32 | Install this package as a dev dependency:
33 | ```shell
34 | npm install --save-dev @kibibit/announce-it
35 | ```
36 |
37 | ## How to use
38 | Intended to run after a new release in your continues integration
39 |
40 | ### Twitter Setup
41 | You have to create a [Developer Account on Twitter](https://developer.twitter.com/).
42 |
43 | * Create an App
44 | * From your apps list go to your app Details
45 | * Select the `Keys and tokens` tab
46 | * You'll need all 4 variables available in that page:
47 | * API key
48 | * API secret key
49 | * Access token
50 | * Access token secret
51 |
52 |
53 | ### As a command line tool
54 | - You need to pass the following parameters by either ENV parameters or
55 | calling the cli with these as cli params:
56 |
57 | * `TWITTER_CONSUMER_KEY` = API key
58 | * `TWITTER_CONSUMER_SECRET` = API secret key
59 | * `TWITTER_ACCESS_TOKEN_KEY` = Access token
60 | * `TWITTER_ACCESS_TOKEN_SECRET` = Access token secret
61 | * `branch` = the current branch
62 |
63 | As CLI params:
64 | ```bash
65 | ./node_modules/.bin/announce-it --CONSUMER_KEY --CONSUMER_SECRET --ACCESS_TOKEN_KEY --ACCESS_TOKEN_SECRET --branch
66 | ```
67 | Both ENV parameters and the CLI arguments have the same names. If a parameter is found in both places, the CLI argument will be used.
68 |
69 | - You can add a dedicated script in your `package.json` file:
70 | ```js
71 | // ...
72 | "scripts": {
73 | // ...
74 | "announce": "announce-it"
75 | }
76 | // ...
77 | ```
78 | - If executed with @semanic-release/exec:
79 | > because of a current issue with `@semantic-release/exec`, you need
80 | to pass the current branch directly
81 | ```js
82 | "release": {
83 | "branches": [ /* ... */ ]
84 | // ...
85 | "success": [
86 | "@semantic-release/github",
87 | [
88 | "@semantic-release/exec",
89 | {
90 | "successCmd": "npm start -- --branch $TRAVIS_BRANCH"
91 | }
92 | ]
93 | ],
94 | }
95 | // ...
96 | // ...
97 | ```
98 | - If installed as a project dependency, you can run with npx:
99 | ```bash
100 | # should be ran inside your project
101 | # npx
102 | npx announce-it
103 |
104 | # directly
105 | ./node_modules/.bin/announce-it
106 | ```
107 | - If installed globally, you can run it from within any node package with
108 | the correct setup
109 |
110 | ### As a node module
111 |
112 | ```typescript
113 | import { KbAnnounceIt, PackageDetails } from '@kibibit/announce-it';
114 |
115 | const announceIt = new KbAnnounceIt({
116 | accessTokenKey: 'TWITTER_ACCESS_KEY',
117 | accessTokenSecret: 'TWITTER_ACCESS_SECRET',
118 | consumerKey: 'TWITTER_CONSUMER_KEY',
119 | consumerSecret: 'TWITTER_CONSUMER_SECRET',
120 | branch: 'CURRENT_BRANCH'
121 | });
122 |
123 | const myPackage: PackageDetails = require('./package');
124 |
125 | // get generated tweet
126 | const tweet: string = announceIt.generateTweet(myPackage);
127 |
128 | console.log('going to tweet: ', tweet);
129 |
130 | // publish tweet to twitter
131 | announceIt.announceRelease(myPackage);
132 |
133 | ```
134 |
135 | ### Defining your Templates
136 | Inside your `package.json` file, add an `announcements` object with `tweet` property.
137 |
138 | You can then create your own tweet message template that will be posted to twitter.
139 |
140 | ```javascript
141 | {
142 | "name": "my-package",
143 | "version": "2.4.3",
144 | // ...
145 | "announcements": {
146 | "tweet": "Version <%= version %> of <%= package %> is live! <%= npmpage %>"
147 | },
148 | }
149 | ```
150 |
151 | The tweet template is generated with [Lodash template](https://lodash.com/docs/4.17.11#template).
152 |
153 | You can use these variables:
154 | * Package name: `<%= package %>`
155 | * Version number: `<%= version %>`
156 | * Package description: `<%= description %>`
157 | * Package author: `<%= author %>`
158 | * Homepage link: `<%= homepage %>`
159 | * Package page on npm: `<%= npmpage %>`
160 |
161 | ## Contributing
162 |
163 | If you have suggestions for how announce-it could be improved, or want to report a bug, open an issue! We'd love all and any contributions.
164 |
165 | For more, check out the [Contributing Guide](CONTRIBUTING.md).
166 |
167 | ## Contributors
168 |
169 | Thanks goes to our contributors! ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
170 |
171 |
172 |
173 |
174 |
175 |
176 | Library logo is made by
177 | Freepik
178 | from
179 | www.flaticon.com
180 | and licensed by
181 | CC 3.0 BY
182 |
183 | ## License
184 |
185 | [MIT](LICENSE) © 2019 Gil Tichon
186 |
--------------------------------------------------------------------------------
/src/announce-it.test.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { assign, cloneDeep } from 'lodash';
3 |
4 | import { KbAnnounceIt } from './announce-it';
5 | import { IPackageDetails } from './read-package-details';
6 |
7 | const TwitterMocks = () => {
8 | const defaultMock = () => Promise.resolve();
9 |
10 | let getMock = defaultMock;
11 | let postMock = defaultMock;
12 |
13 | return {
14 | mock: {
15 | get: () => getMock(),
16 | post: () => postMock()
17 | },
18 | reset: (userDefinedMocks: any = {}) => {
19 | getMock = userDefinedMocks.get || getMock;
20 | postMock = userDefinedMocks.post || postMock;
21 | }
22 | };
23 | };
24 |
25 | const twitterMocks = TwitterMocks();
26 |
27 | jest.mock('twitter-lite', () => () => twitterMocks.mock);
28 |
29 | describe('KbAnnounceIt', () => {
30 | it('should throw error when missing options', () => {
31 | expect.assertions(5);
32 |
33 | // @ts-ignore
34 | expect(() => new KbAnnounceIt()).toThrowErrorMatchingSnapshot();
35 |
36 | // @ts-ignore
37 | expect(() => new KbAnnounceIt({
38 | accessTokenSecret: 'TEST',
39 | consumerKey: 'TEST',
40 | consumerSecret: 'TEST'
41 | })).toThrowErrorMatchingSnapshot();
42 |
43 | // @ts-ignore
44 | expect(() => new KbAnnounceIt({
45 | accessTokenKey: 'TEST',
46 | consumerKey: 'TEST',
47 | consumerSecret: 'TEST'
48 | })).toThrowErrorMatchingSnapshot();
49 |
50 | // @ts-ignore
51 | expect(() => new KbAnnounceIt({
52 | accessTokenKey: 'TEST',
53 | accessTokenSecret: 'TEST',
54 | consumerSecret: 'TEST'
55 | })).toThrowErrorMatchingSnapshot();
56 | // @ts-ignore
57 | expect(() => new KbAnnounceIt({
58 | accessTokenKey: 'TEST',
59 | accessTokenSecret: 'TEST',
60 | consumerKey: 'TEST'
61 | })).toThrowErrorMatchingSnapshot();
62 |
63 | });
64 |
65 | it('should create instance when given correct parameters', () => {
66 | const announceIt = new KbAnnounceIt({
67 | accessTokenKey: 'TEST',
68 | accessTokenSecret: 'TEST',
69 | consumerKey: 'TEST',
70 | consumerSecret: 'TEST',
71 | branch: 'TEST'
72 | });
73 |
74 | expect(announceIt).toBeDefined();
75 | expect(announceIt).toMatchSnapshot();
76 | });
77 | });
78 |
79 | describe('kbAnnounceIt.announceRelease', () => {
80 | const packageDetails: IPackageDetails = {
81 | name: 'test-repo',
82 | description: 'test-description',
83 | author: 'test-author',
84 | homepage: 'test-homepage',
85 | repository: {
86 | type: 'test-repo-type',
87 | url: 'test-repo-url'
88 | },
89 | version: '0.0.0',
90 | announcements: {
91 | tweet: 'test-template'
92 | },
93 | release: {
94 | branches: [ 'TEST' ]
95 | }
96 | };
97 |
98 | let announceIt: KbAnnounceIt;
99 |
100 | beforeEach(() => {
101 | announceIt = new KbAnnounceIt({
102 | accessTokenKey: 'TEST',
103 | accessTokenSecret: 'TEST',
104 | consumerKey: 'TEST',
105 | consumerSecret: 'TEST',
106 | branch: 'TEST'
107 | });
108 | });
109 |
110 | it('should post to twitter when stable release', () => {
111 | return expect(announceIt.announceRelease(packageDetails)).resolves.toMatchSnapshot();
112 | });
113 |
114 | it('should throw an error when unstable release and not mentioned in packageDetails', () => {
115 | const testPackageDetails = cloneDeep(packageDetails);
116 | announceIt = new KbAnnounceIt({
117 | accessTokenKey: 'TEST',
118 | accessTokenSecret: 'TEST',
119 | consumerKey: 'TEST',
120 | consumerSecret: 'TEST',
121 | branch: 'unstable'
122 | });
123 |
124 | return expect(announceIt.announceRelease(testPackageDetails)).rejects.toThrowErrorMatchingSnapshot();
125 | });
126 |
127 | it('should throw an error when unstable release and mentioned in packageDetails as unstable', () => {
128 | const testPackageDetails = cloneDeep(packageDetails);
129 | testPackageDetails.release.branches = [{
130 | name: 'unstable',
131 | prerelease: true
132 | }];
133 | announceIt = new KbAnnounceIt({
134 | accessTokenKey: 'TEST',
135 | accessTokenSecret: 'TEST',
136 | consumerKey: 'TEST',
137 | consumerSecret: 'TEST',
138 | branch: 'unstable'
139 | });
140 |
141 |
142 | return expect(announceIt.announceRelease(testPackageDetails)).rejects.toThrowErrorMatchingSnapshot();
143 | });
144 |
145 | it('should post to twitter when unstable release and mentioned in packageDetails as stable', () => {
146 | const testPackageDetails = cloneDeep(packageDetails);
147 | testPackageDetails.release.branches = [{
148 | name: 'unstable',
149 | prerelease: false
150 | }];
151 | announceIt = new KbAnnounceIt({
152 | accessTokenKey: 'TEST',
153 | accessTokenSecret: 'TEST',
154 | consumerKey: 'TEST',
155 | consumerSecret: 'TEST',
156 | branch: 'unstable'
157 | });
158 |
159 |
160 | return expect(announceIt.announceRelease(testPackageDetails)).resolves.toMatchSnapshot();
161 | });
162 |
163 | it('should post to twitter when unstable release and mentioned in packageDetails', () => {
164 | const testPackageDetails = cloneDeep(packageDetails);
165 | testPackageDetails.version = '0.0.0-next.1';
166 | testPackageDetails.announcements.includeUnstable = true;
167 |
168 | return expect(announceIt.announceRelease(testPackageDetails)).resolves.toMatchSnapshot();
169 | });
170 |
171 | it('should throw error when twitter get credentials throws an error', async () => {
172 | twitterMocks.reset({ get: () => Promise.reject(new Error('Twitter Get Error')) });
173 |
174 | // TODO: FEATURE: create a basic KbError class
175 | return expect(announceIt.announceRelease(packageDetails)).rejects
176 | .toThrowErrorMatchingSnapshot();
177 | });
178 |
179 | it('should throw error when twitter post tweet throws an error', async () => {
180 | expect.assertions(1);
181 |
182 | twitterMocks.reset({
183 | post: () => Promise.reject(new Error('Twitter Post Error')),
184 | get: () => Promise.resolve()
185 | });
186 |
187 | return expect(announceIt.announceRelease(packageDetails)).rejects
188 | .toThrowErrorMatchingSnapshot();
189 | });
190 | });
191 |
192 | describe('kbAnnounceIt.generateTweet', () => {
193 | const packageDetails: IPackageDetails = {
194 | name: 'test-repo',
195 | description: 'test-description',
196 | author: 'test-author',
197 | homepage: 'test-homepage',
198 | repository: {
199 | type: 'test-repo-type',
200 | url: 'test-repo-url'
201 | },
202 | version: '0.0.0',
203 | announcements: {
204 | tweet: 'test-template'
205 | },
206 | release: {
207 | branches: [ 'TEST' ]
208 | }
209 | };
210 |
211 | let announceIt: KbAnnounceIt;
212 |
213 | beforeEach(() => {
214 | announceIt = new KbAnnounceIt({
215 | accessTokenKey: 'TEST',
216 | accessTokenSecret: 'TEST',
217 | consumerKey: 'TEST',
218 | consumerSecret: 'TEST',
219 | branch: 'TEST'
220 | });
221 | });
222 |
223 | it('should throw error on non object input', () => {
224 | // @ts-ignore
225 | expect(() => announceIt.generateTweet()).toThrowErrorMatchingSnapshot();
226 | });
227 |
228 | it('should throw error on missing package details input', () => {
229 | const testPackageDetails: any = assign({}, packageDetails);
230 | testPackageDetails.name = null;
231 |
232 | expect(() => announceIt.generateTweet(testPackageDetails)).toThrowErrorMatchingSnapshot();
233 | });
234 |
235 | it('should throw error when using missing variables in template', async () => {
236 | const testPackageDetails = packageDetails;
237 | testPackageDetails.announcements.tweet = 'This should <%= blow %> an error';
238 | expect(() => announceIt.generateTweet(packageDetails)).toThrowErrorMatchingSnapshot();
239 |
240 | });
241 | });
242 |
--------------------------------------------------------------------------------