├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _config.yml ├── assets ├── assign_issues_1.png ├── assign_issues_2.png ├── board_subcommands.png ├── clear_1.png ├── clear_2.png ├── details.png ├── login_preview.png ├── preview.png └── task_subcommands.png ├── azure-pipelines.yml ├── package-lock.json ├── package.json └── src ├── api ├── IssueApis.js ├── JqlClient.js ├── board.js ├── questions.js ├── task.js └── user.js ├── commands ├── Tasks.js └── boards.js ├── index.js ├── operations ├── AssignIssue.js ├── PrintIIssue.js ├── Task.js └── boards.js ├── services ├── AuthServices.js └── AxiosService.js ├── store.js ├── url.js └── utility ├── console.js └── utils.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es2021: true, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master, update-*, v-*] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '35 21 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /.DS_Store 3 | src/.DS_Store 4 | .DS_Store 5 | src/test.js -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "proseWrap": "always", 3 | "tabWidth": 4, 4 | "printWidth": 80, 5 | "bracketSpacing": true, 6 | "arrowParens": "avoid", 7 | "singleQuote": true 8 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to Jirax 2 | 3 | Thanks for your interest on contributing to Jirax ! Here are a few general guidelines on contributing and reporting bugs to Jirax that we ask you to take a look first. Notice that all of your interactions in the project are expected to follow our [Code of Conduct](#code-of-conduct). 4 | 5 | 6 | When contributing to this repository, please first discuss the change you wish to make via issue, 7 | email, or any other method with the owners of this repository before making a change. 8 | 9 | Please note we have a code of conduct, please follow it in all your interactions with the project. 10 | 11 | ## Pull Request Process 12 | 13 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 14 | build. 15 | 2. Update the README.md with details of changes to the interface, this includes new environment 16 | variables, exposed ports, useful file locations and container parameters. 17 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 18 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 19 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 20 | do not have permission to do that, you may request the second reviewer to merge it for you. 21 | 22 | Thanks again for your interest on contributing to the project! 23 | 24 | ❤️ 25 | 26 | ## Code of Conduct 27 | 28 | ### Our Pledge 29 | 30 | In the interest of fostering an open and welcoming environment, we as 31 | contributors and maintainers pledge to making participation in our project and 32 | our community a harassment-free experience for everyone, regardless of age, body 33 | size, disability, ethnicity, gender identity and expression, level of experience, 34 | nationality, personal appearance, race, religion, or sexual identity and 35 | orientation. 36 | 37 | ### Our Standards 38 | 39 | Examples of behavior that contributes to creating a positive environment 40 | include: 41 | 42 | * Using welcoming and inclusive language 43 | * Being respectful of differing viewpoints and experiences 44 | * Gracefully accepting constructive criticism 45 | * Focusing on what is best for the community 46 | * Showing empathy towards other community members 47 | 48 | Examples of unacceptable behavior by participants include: 49 | 50 | * The use of sexualized language or imagery and unwelcome sexual attention or 51 | advances 52 | * Trolling, insulting/derogatory comments, and personal or political attacks 53 | * Public or private harassment 54 | * Publishing others' private information, such as a physical or electronic 55 | address, without explicit permission 56 | * Other conduct which could reasonably be considered inappropriate in a 57 | professional setting 58 | 59 | ### Our Responsibilities 60 | 61 | Project maintainers are responsible for clarifying the standards of acceptable 62 | behavior and are expected to take appropriate and fair corrective action in 63 | response to any instances of unacceptable behavior. 64 | 65 | Project maintainers have the right and responsibility to remove, edit, or 66 | reject comments, commits, code, wiki edits, issues, and other contributions 67 | that are not aligned to this Code of Conduct, or to ban temporarily or 68 | permanently any contributor for other behaviors that they deem inappropriate, 69 | threatening, offensive, or harmful. 70 | 71 | ### Scope 72 | 73 | This Code of Conduct applies both within project spaces and in public spaces 74 | when an individual is representing the project or its community. Examples of 75 | representing a project or community include using an official project e-mail 76 | address, posting via an official social media account, or acting as an appointed 77 | representative at an online or offline event. Representation of a project may be 78 | further defined and clarified by project maintainers. 79 | 80 | ### Enforcement 81 | 82 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 83 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 84 | complaints will be reviewed and investigated and will result in a response that 85 | is deemed necessary and appropriate to the circumstances. The project team is 86 | obligated to maintain confidentiality with regard to the reporter of an incident. 87 | Further details of specific enforcement policies may be posted separately. 88 | 89 | Project maintainers who do not follow or enforce the Code of Conduct in good 90 | faith may face temporary or permanent repercussions as determined by other 91 | members of the project's leadership. 92 | 93 | ### Attribution 94 | 95 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 96 | available at [http://contributor-covenant.org/version/1/4][version] 97 | 98 | [homepage]: http://contributor-covenant.org 99 | [version]: http://contributor-covenant.org/version/1/4/ 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Junip Dewan 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jirax 2 | [![forthebadge](https://forthebadge.com/images/badges/made-with-javascript.svg)]() [![forthebadge](https://forthebadge.com/images/badges/built-with-love.svg)]() [![forthebadge](https://forthebadge.com/images/badges/for-you.svg)]() [![forthebadge](https://forthebadge.com/images/badges/check-it-out.svg)](https://forthebadge.com) 3 | 4 | 5 | 6 | 7 | [![Build Status](https://dev.azure.com/junipd2/jirax/_apis/build/status/junipdewan.jirax?branchName=master)](https://dev.azure.com/junipd2/jirax/_build/latest?definitionId=4&branchName=master) ![npm](https://img.shields.io/npm/dm/jirax.svg) ![npm](https://img.shields.io/npm/v/jirax.svg) ![made with nodejs](https://img.shields.io/badge/madewith-node.js-green.svg) [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![license](https://img.shields.io/github/license/visionmedia/superagent.svg)](LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=shields)](http://makeapullrequest.com) 8 | 9 | ``` 10 | ``` 11 | 12 | > :star: If you are using this tool or you like it, Star on GitHub — it helps! 13 | 14 | 15 | 16 | A CLI tool for [JIRA](`https://www.atlassian.com/software/jira`) for day to day usage with JIRA.Speed up your JIRA activity with this CLI tool. 17 | 18 | 19 | 20 | ## Prerequisites 21 | 22 | You are required to have [Node.js](https://nodejs.org/) installed to run the cli tool or after installing [Node.js](https://nodejs.org/) you can make executable and run the excutable to use the tool. [Make executable](#making-executable) 23 | 24 | ## Getting Started 25 | 26 | 1. Log in to [Atlassian](https://id.atlassian.com/manage/api-tokens) and generate your API TOKEN. 27 | 2. Copy the API TOKEN 28 | 29 | ## Install Using NPM 30 | 31 | You can use directly install the package using 32 | [NPM](https://www.npmjs.com/package/jirax) or [YARN](https://yarnpkg.com/en/package/jirax) 33 | 34 | ```sh 35 | npm install -g jirax 36 | ``` 37 | ``` 38 | yarn add jirax 39 | ``` 40 | 41 | ### use the below command to get started 42 | 43 | ```sh 44 | npx jirax 45 | ``` 46 | 47 | ## By Cloning the repository 48 | 49 | Install all dependency 50 | 51 | 52 | ```sh 53 | npm install 54 | ``` 55 | 56 | ### Create the symlink. This command will help you execute `jirax` commands at global level 57 | 58 | 59 | ``` 60 | npm link or sudo npm link 61 | ``` 62 | 63 | # Usages 64 | 65 | ## Login In Cli 66 | 67 | You need to login before using any of JIRAX features. 68 | 69 | ```sh 70 | jirax -l 71 | ``` 72 | 73 | This will prompt few questions to enter your credentials please enter the credentials to use the CLI.) 74 | 75 | ```sh 76 | $ Your JIRA Host Name (eg: something.atlassian.net) 77 | $ Your JIRA User Name 78 | $ Your API Token 79 | ``` 80 | It will authenticate with JIRA Server and after successfull login, your name will prompt with message 81 | 82 | ![Interface](assets/login_preview.png?raw=true "Login Preview") 83 | 84 | Your Login details will be stored in a JSON file located in 85 | 86 | ```sh 87 | $XDG_CONFIG_HOME or ~/.config. 88 | 89 | # access it 90 | ~/.config/configstore/jiraconfig.json 91 | 92 | ``` 93 | 94 | 95 | ## JIRAX Commands 96 | 97 | Jirax CLI tool is madeup with the subcommands for various jira activities. 98 | 99 | ```sh 100 | # prints all available commands 101 | jirax --help 102 | ``` 103 | 104 |
105 | #### For Subcommands options 106 | 107 | ```sh 108 | # prints all available commands for a specific subcommand 109 | jirax command --help 110 | 111 | ``` 112 | 113 | 114 |
115 |
116 |
117 | 118 | 119 | 120 | ## Clear Stored Credentials 121 | In case of your API token revoked or you have revoked the previous API token then you need to clear the previous credentials using the following command and 122 | Relogin using command `jirax login` 123 | 124 | ```sh 125 | jirax logout 126 | ``` 127 | 128 | ## Making executable 129 | After cloning the repository. Run this command this command will automatically create plateform specific executables. 130 | 131 | ```sh 132 | npm run build 133 | ``` 134 | 135 | ## Contribution 136 | 137 | We hope that you will consider contributing to Jirax. Please read this short overview [Contribution Guidelines](https://github.com/junipdewan/jirax/blob/master/CONTRIBUTING.md) for some information about how to get started 138 | 139 | ## MIT License 140 | 141 | **jirax** is available under the **MIT license**. See the [LICENSE](https://github.com/junipdewan/jirax/blob/master/LICENSE) file for more info. 142 | 143 | Copyright (c) 2021 144 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-dinky -------------------------------------------------------------------------------- /assets/assign_issues_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/assign_issues_1.png -------------------------------------------------------------------------------- /assets/assign_issues_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/assign_issues_2.png -------------------------------------------------------------------------------- /assets/board_subcommands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/board_subcommands.png -------------------------------------------------------------------------------- /assets/clear_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/clear_1.png -------------------------------------------------------------------------------- /assets/clear_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/clear_2.png -------------------------------------------------------------------------------- /assets/details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/details.png -------------------------------------------------------------------------------- /assets/login_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/login_preview.png -------------------------------------------------------------------------------- /assets/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/preview.png -------------------------------------------------------------------------------- /assets/task_subcommands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/task_subcommands.png -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js 2 | # Build a general Node.js project with npm. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'ubuntu-latest' 11 | 12 | steps: 13 | - task: NodeTool@0 14 | inputs: 15 | versionSpec: '10.x' 16 | displayName: 'Install Node.js' 17 | - task: CopyFiles@2 18 | inputs: 19 | SourceFolder: '$(System.DefaultWorkingDirectory)' 20 | Contents: | 21 | jirax-macos 22 | jirax-linux 23 | jirax-win.exe 24 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 25 | - task: PublishBuildArtifacts@1 26 | inputs: 27 | PathtoPublish: '$(System.DefaultWorkingDirectory)' 28 | 29 | - script: | 30 | npm install 31 | npm run build 32 | displayName: 'npm install' 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jirax", 3 | "version": "2.0.1", 4 | "description": "A cli tool for daily JIRA activities", 5 | "author": "Junip Dewan ", 6 | "main": "index.js", 7 | "bin": { 8 | "jirax": "./src/index.js" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "format": "prettier --write \"src/**/*.js\"", 13 | "build": "pkg .", 14 | "lint": "eslint" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/junipdewan/jirax.git" 19 | }, 20 | "keywords": [ 21 | "jira", 22 | "jira-cli", 23 | "jira-tool", 24 | "jira-cmd", 25 | "jirajs", 26 | "jira-in-terminal", 27 | "terminal", 28 | "console", 29 | "jira-shell", 30 | "jirax", 31 | "jira-cmd" 32 | ], 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/junipdewan/jirax/issues" 36 | }, 37 | "homepage": "https://github.com/junipdewan/jirax#readme", 38 | "devDependencies": { 39 | "eslint": "^7.32.0", 40 | "eslint-config-airbnb-base": "^14.2.1", 41 | "eslint-plugin-import": "^2.24.0", 42 | "prettier": "^1.17.1" 43 | }, 44 | "pkg": { 45 | "assets": "./node_modules/figlet/fonts/Standard.flf" 46 | }, 47 | "dependencies": { 48 | "axios": "^0.24.0", 49 | "chalk": "^2.4.2", 50 | "commander": "^8.2.0", 51 | "configstore": "^4.0.0", 52 | "dateformat": "^3.0.3", 53 | "figlet": "^1.2.1", 54 | "fuzzy": "^0.1.3", 55 | "inquirer": "^6.3.1", 56 | "inquirer-autocomplete-prompt": "^1.4.0", 57 | "jira-connector": "^3.1.0", 58 | "jira.js": "^2.8.0", 59 | "log-update": "^3.2.0", 60 | "minimist": "^1.2.0", 61 | "open": "^8.2.1", 62 | "ora": "^3.4.0", 63 | "pkg": "^4.4.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/api/IssueApis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to connect JIRA issue APIS 3 | */ 4 | const open = require('open'); 5 | const Configstore = require('configstore'); 6 | const authService = require('../services/AuthServices'); 7 | const util = require('../utility/utils'); 8 | const consoleApi = require('../utility/console'); 9 | const configStore = new Configstore('jiraconfig'); 10 | 11 | module.exports = { 12 | /** 13 | * this method opens the issues for the given project key 14 | * @param {*} projectKey 15 | */ 16 | openProjectIssues(projectKey) { 17 | const hostName = util.getHostName(); 18 | const URL = `https://${hostName}/jira/software/c/projects/${projectKey}/issues`; 19 | open(URL); 20 | }, 21 | // /** 22 | // * A method return array of transtions available for the specific issues 23 | // * @param options.issuekey isssue keys to which this 24 | // * @param {*} options 25 | // */ 26 | 27 | assignIssue(issueKey, accountId, username) { 28 | const options = { 29 | issueKey, 30 | accountId 31 | }; 32 | const spinner = util.spinner( 33 | `Assigning the issue ${issueKey} to ${username}` 34 | ); 35 | spinner.start(); 36 | authService.jiraConnector() 37 | .issue.assignIssue(options, (error, success) => { 38 | if (success || error) { 39 | spinner.stop(); 40 | } 41 | if (error) { 42 | consoleApi.printError('Issue Cannnot be assigned'); 43 | } 44 | if (success) { 45 | const message = `Issue ${consoleApi.chalkRed( 46 | issueKey 47 | )} ${consoleApi.chalkGreen( 48 | 'is assigned to' 49 | )} ${consoleApi.printbgCyan(username)}`; 50 | consoleApi.printInfo(message); 51 | } 52 | }); 53 | }, 54 | /** 55 | * Assign the issue to self i.e logged in user 56 | * @param issueKey 57 | * @param {*} issueKey 58 | */ 59 | assignSelf(issueKey) { 60 | const accountId = configStore.get('accountId'); 61 | const username = configStore.get('displayName'); 62 | module.exports.assignIssue(issueKey, accountId, username); 63 | }, 64 | // /** 65 | // * Get the issue statuses of project for the given issue 66 | // * @param {*} issueKey 67 | // */ 68 | 69 | getStoredTranstions(issueKey, cb) { 70 | const key = issueKey.split('-')[0]; 71 | const keyPresent = configStore.get(key); 72 | if (!keyPresent) { 73 | module.exports.getTranstions(issueKey, data => cb(data)); 74 | } else { 75 | const transitions = configStore.get(key); 76 | return cb(transitions); 77 | } 78 | }, 79 | // ------------------------------COMMENTS RELATED FUNCTIONS------------------> 80 | /** 81 | * Add comment to the issue 82 | * * { issueKey: 'SFMAC-19', comment: 'some comment'"} 83 | * @param {*} options 84 | */ 85 | addComment(options) { 86 | const spinner = util.spinner('Posting your comment. Please wait'); 87 | spinner.start(); 88 | authService.jiraConnector() 89 | .issue.addComment(options, (error, response) => { 90 | if (response) { 91 | spinner.stop(); 92 | consoleApi.printInfo('Comment added successfully'); 93 | } 94 | if (error) { 95 | spinner.stop(); 96 | consoleApi.printError('No issue with mentioned key found'); 97 | } 98 | }); 99 | }, 100 | 101 | /** 102 | * Get all the comments for the specific issue 103 | * @param {*} issueKey 104 | */ 105 | getComments(issueKey) { 106 | const spinner = util.spinner({ 107 | text: 'Fetching data...', 108 | spinner: 'earth' 109 | }); 110 | spinner.start(); 111 | authService.jiraConnector() 112 | .issue.getComments({ issueKey }, (error, response) => { 113 | if (response) { 114 | spinner.stop(); 115 | if (response.comments.length === 0) { 116 | consoleApi.printInfo('No Comments Found'); 117 | } else { 118 | response.comments.map(comment => { 119 | console.log( 120 | `${comment.id} ${consoleApi.chalkGreen( 121 | comment.author.displayName.split(' ')[0] 122 | )} ${comment.body} \n` 123 | ); 124 | }); 125 | } 126 | } 127 | }); 128 | }, 129 | /** 130 | * * Delete the comment for the given issueKey 131 | * @param {string} [opts.issueKey] The Key of the issue. EX: JWR-3 132 | * @param {string} opts.commentId The id of the comment. 133 | * @param {*} opts 134 | */ 135 | 136 | deleteComment(opts) { 137 | const spinner = util.spinner('Deleting comment .....'); 138 | spinner.start(); 139 | authService.jiraConnector() 140 | .issue.deleteComment(opts, (error, response) => { 141 | if (error) { 142 | spinner.stop(); 143 | consoleApi.printError('Error while deleting the comment'); 144 | } 145 | if (response) { 146 | spinner.stop(); 147 | consoleApi.printInfo('Comment deleted'); 148 | } 149 | }); 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /src/api/JqlClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JIRA Apis response with jira JQL custom quesry search 3 | */ 4 | const authenticate = require('../services/AuthServices'); 5 | const util = require('../utility/utils'); 6 | const consoleApi = require('../utility/console'); 7 | 8 | const spinner = util.spinner({ text: 'Fetching data...', spinner: 'earth' }); 9 | 10 | const todoJQL = "assignee = currentUser() AND status='To Do'"; 11 | const completedJQL = "assignee = currentUser() AND status='Done'"; 12 | const inReviewJQL = "assignee = currentUser() AND status='In Review'"; 13 | 14 | module.exports = { 15 | formatIssuesData(response) { 16 | let issues; 17 | if (response) { 18 | spinner.stop(); 19 | } 20 | if (response.issues.length) { 21 | issues = response.issues.map(issue => ({ 22 | key: issue.key, 23 | summary: issue.fields.summary, 24 | type: issue.fields.issuetype.name 25 | })); 26 | } 27 | return issues; 28 | }, 29 | 30 | /** 31 | * Print the issue listing in a proper format 32 | * @param {*} issues 33 | */ 34 | printIssues(issues) { 35 | if (issues) { 36 | issues.map(issue => { 37 | console.log( 38 | `${issue.key} ${util.setIssueColor(issue.type)} ${ 39 | issue.summary 40 | } \n` 41 | ); 42 | }); 43 | } else { 44 | consoleApi.printInfo('No issues found'); 45 | } 46 | }, 47 | /** 48 | * 49 | * @param {*} param0 50 | * @param {*} callback 51 | * @return CurrentUser ToDo Issues 52 | */ 53 | myOpenIssues(projectKey, callback) { 54 | spinner.start(); 55 | const JQL = 56 | projectKey === true 57 | ? todoJQL 58 | : `${todoJQL} AND project = ${projectKey}`; 59 | authenticate 60 | .currentUser() 61 | .search.search({ jql: JQL }, (error, response) => 62 | callback(module.exports.formatIssuesData(response)) 63 | ); 64 | }, 65 | /** 66 | * @return current user issues which are in review 67 | * @param {*} param0 68 | * @param {*} callback 69 | */ 70 | myInReviewIssues(projectKey, callback) { 71 | spinner.start(); 72 | const JQL = 73 | projectKey === true 74 | ? inReviewJQL 75 | : `${inReviewJQL} AND project = ${projectKey}`; 76 | authenticate 77 | .currentUser() 78 | .search.search({ jql: JQL }, (error, response) => 79 | callback(module.exports.formatIssuesData(response)) 80 | ); 81 | }, 82 | /** 83 | * @return Current User completed issues list 84 | * @param {*} param0 85 | * @param {*} callback 86 | */ 87 | myCompletedIssues(projectKey, callback) { 88 | spinner.start(); 89 | const JQL = 90 | projectKey === true 91 | ? completedJQL 92 | : `${completedJQL} AND project = ${projectKey}`; 93 | authenticate 94 | .currentUser() 95 | .search.search({ jql: JQL }, (error, response) => 96 | callback(module.exports.formatIssuesData(response)) 97 | ); 98 | }, 99 | 100 | fetchMyOpenIssues(projectKey) { 101 | module.exports.myOpenIssues(projectKey, response => { 102 | module.exports.printIssues(response); 103 | }); 104 | }, 105 | 106 | fetchMyInReviewIssues(projectKey) { 107 | module.exports.myInReviewIssues(projectKey, response => { 108 | module.exports.printIssues(response); 109 | }); 110 | }, 111 | 112 | fetchMyCompletedIssues(projectKey) { 113 | module.exports.myCompletedIssues(projectKey, response => { 114 | module.exports.printIssues(response); 115 | }); 116 | } 117 | }; 118 | -------------------------------------------------------------------------------- /src/api/board.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JIRA APIS related to jira board 3 | */ 4 | 5 | const fuzzy = require('fuzzy'); 6 | const inquirer = require('inquirer'); 7 | const autocompletePrompt = require('inquirer-autocomplete-prompt'); 8 | const authService = require('../services/AuthServices'); 9 | inquirer.registerPrompt('autocomplete', autocompletePrompt); 10 | const log = require('../utility/console'); 11 | const util = require('../utility/utils'); 12 | const spinner = util.spinner('Fetching boards.....'); 13 | 14 | let names = []; 15 | module.exports = { 16 | getBoards() { 17 | spinner.start(); 18 | authService.jiraConnector().board.getAllBoards({}, (err, response) => { 19 | if (response) { 20 | spinner.stop(); 21 | names = response.values.map(el => el.name); 22 | inquirer 23 | .prompt([ 24 | { 25 | type: 'autocomplete', 26 | name: 'name', 27 | message: 28 | 'Please search for the board you want to open', 29 | source: module.exports.searchProjects, 30 | pageSize: 6 31 | } 32 | ]) 33 | .then(answers => { 34 | // assign the issue to selected user 35 | const boardId = response.values.filter( 36 | el => el.name === answers.name 37 | )?.[0]?.id; 38 | if (boardId) { 39 | util.setConfig({ defaultJiraBoard: boardId }); 40 | } 41 | log.printInfo('Your default board is set'); 42 | }); 43 | } else { 44 | console.log('---', err); 45 | } 46 | }); 47 | }, 48 | 49 | searchProjects(answers, input) { 50 | input = input || ''; 51 | return new Promise(resolve => { 52 | setTimeout(() => { 53 | const fuzzyResult = fuzzy.filter(input, names); 54 | resolve(fuzzyResult.map(el => el.original)); 55 | }, 300); 56 | }); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/api/questions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Question Prompt Module 3 | */ 4 | 5 | const inquirer = require('inquirer'); 6 | //const issue = require('./issue_client'); 7 | 8 | const credentialQuestion = [ 9 | { 10 | type: 'input', 11 | name: 'host_name', 12 | message: 'Your JIRA Host Name (eg: something.atlassian.net)', 13 | validate: value => (value.length ? true : 'Please enter Host Name') 14 | }, 15 | { 16 | type: 'input', 17 | name: 'user_name', 18 | message: 'Your JIRA User Name', 19 | validate: value => (value.length ? true : 'Please enter Jira User Name') 20 | }, 21 | { 22 | type: 'password', 23 | name: 'api_token', 24 | message: 'Your API Token', 25 | mask: '*', 26 | validate: value => (value.length ? true : 'Please Enter Your API Token') 27 | } 28 | ]; 29 | 30 | module.exports = { 31 | // asking credential for the input from user 32 | askCredential: () => inquirer.prompt(credentialQuestion), 33 | // Confirmation prompt for the JIRA login cred removal from configStore 34 | confirmRemoval: () => 35 | inquirer.prompt([ 36 | { 37 | type: 'list', 38 | name: 'remove', 39 | message: 40 | 'Are you sure? This command will remove the login credentials from system. ' + 41 | 'You need to login again for further usages of JIRAX', 42 | choices: [ 43 | { key: 'Yes', value: 'Yes' }, 44 | { key: 'No', value: 'No' } 45 | ] 46 | } 47 | ]) 48 | }; 49 | -------------------------------------------------------------------------------- /src/api/task.js: -------------------------------------------------------------------------------- 1 | /** 2 | * All Task Related APIs goes here 3 | */ 4 | 5 | const fuzzy = require('fuzzy'); 6 | const inquirer = require('inquirer'); 7 | const autocompletePrompt = require('inquirer-autocomplete-prompt'); 8 | const authenticate = require('../services/AuthServices'); 9 | inquirer.registerPrompt('autocomplete', autocompletePrompt); 10 | const axiosConfig = require('../services/AxiosService'); 11 | const authService = require('../services/AuthServices'); 12 | const store = require('../Store'); 13 | const log = require('../utility/console'); 14 | const util = require('../utility/utils'); 15 | const print = require('../operations/PrintIIssue'); 16 | const statusSpinner = util.spinner('Fetching available statuses.....'); 17 | const fetchingIssueSpinner = util.spinner({ 18 | text: 'Loading details..', 19 | spinner: 'moon' 20 | }); 21 | 22 | // Initialize 23 | module.exports = { 24 | changeIssueStatus(issueKey) { 25 | statusSpinner.start(); 26 | 27 | authService 28 | .jira() 29 | .issues.getTransitions({ 30 | issueIdOrKey: issueKey 31 | }) 32 | .then(response => { 33 | statusSpinner.stop(); 34 | let availableTranstions = response.transitions.map(t => ({ 35 | name: t.name, 36 | value: t.id 37 | })); 38 | 39 | var names = availableTranstions.map(el => el.name); 40 | inquirer 41 | .prompt([ 42 | { 43 | type: 'autocomplete', 44 | name: 'name', 45 | message: 46 | 'Search & select for transition you want to move the task', 47 | source: (answers, input) => { 48 | input = input || ''; 49 | return new Promise(resolve => { 50 | setTimeout(() => { 51 | const fuzzyResult = fuzzy.filter( 52 | input, 53 | names 54 | ); 55 | resolve( 56 | fuzzyResult.map(el => el.original) 57 | ); 58 | }, 300); 59 | }); 60 | }, 61 | pageSize: 6 62 | } 63 | ]) 64 | .then(answers => { 65 | let selectedTrans = availableTranstions.find( 66 | e => e.name === answers.name 67 | ); 68 | module.exports.doTransition(issueKey, selectedTrans); 69 | }); 70 | }) 71 | .catch(error => { 72 | statusSpinner.stop(); 73 | console.log('error', error.response.status); 74 | }); 75 | }, 76 | 77 | searchTransitions(answers, input) { 78 | input = input || ''; 79 | return new Promise(resolve => { 80 | setTimeout(() => { 81 | const fuzzyResult = fuzzy.filter(input, names); 82 | resolve(fuzzyResult.map(el => el.original)); 83 | }, 300); 84 | }); 85 | }, 86 | 87 | // Change issue to another status 88 | doTransition(issueKey, transition) { 89 | let transspin = util.spinner('Transitioning....'); 90 | transspin.start(); 91 | let params = { 92 | issueIdOrKey: issueKey, 93 | transition: { 94 | id: parseInt(transition.value), 95 | name: transition.name 96 | } 97 | }; 98 | authService 99 | .jira() 100 | .issues.doTransition(params, response => { 101 | if (response) { 102 | transspin.stop(); 103 | log.printInfo('Your issue changed to ' + transition.name); 104 | } 105 | }) 106 | .then(response => { 107 | transspin.stop(); 108 | log.printInfo('Your issue changed to ' + transition.name); 109 | }) 110 | .catch(error => { 111 | console.log('----', error.response); 112 | }); 113 | }, 114 | 115 | fetchTaskDetails(issueKey) { 116 | fetchingIssueSpinner.start(); 117 | authService 118 | .jira() 119 | .issues.getIssue({ issueIdOrKey: issueKey }) 120 | .then(res => { 121 | if (res) { 122 | fetchingIssueSpinner.stop(); 123 | print.printIssueDetails(res); 124 | } 125 | }) 126 | .catch(error => { 127 | console.log(error.response.status); 128 | }); 129 | } 130 | }; 131 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User Related APIs 3 | */ 4 | const auth = require('../services/AuthServices'); 5 | 6 | module.exports = { 7 | /** 8 | * Returns a list of users that match the search string. 9 | * Please note that this resource should be called with an issue key 10 | * when a list of assignable users is retrieved for editing 11 | * 12 | * @param {Object} opts The request options sent to the Jira API 13 | * @param {*} cb 14 | * @param {string} opts.query The username 15 | * @param {string} opts.issueKey The issue key for the issue being edited we need to find assignable users 16 | */ 17 | searchAssignableUser: function(issueKey, input, cb) { 18 | let options = { issueKey: issueKey, query: input }; 19 | auth.jira().userSearch.findAssignableUsers(options, function(err, res) { 20 | let users = []; 21 | if (err) { 22 | // if err then return the empty users array 23 | return cb(users); 24 | } 25 | if (res) { 26 | res.map(user => { 27 | users.push({ 28 | accountId: user.accountId, 29 | name: user.displayName 30 | }); 31 | }); 32 | return cb(users); 33 | } 34 | }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/commands/Tasks.js: -------------------------------------------------------------------------------- 1 | /** Issues Related commands */ 2 | const program = require('commander'); 3 | /** 4 | * 1. Fetch all the issue transitions. 5 | * 2. jirax task change KEY - Change the status of the task (transition to another status) 6 | * 3. jirax task detail KEY - get detail of the task. ( show the details of the task) 7 | * 4. jirax task assign KEY - Assign issue to another user in the same task 8 | */ 9 | const task = require('../api/Task'); 10 | const taskOperation = require('../operations/Task'); 11 | const assignTask = require('../operations/AssignIssue'); 12 | 13 | exports.loadTasksCommands = () => { 14 | const openTasksCommands = program 15 | .command('task') 16 | .description('JIRA task operations') 17 | .action(() => { 18 | // show the available issues commands 19 | openTasksCommands.help(); 20 | }); 21 | 22 | // change status of the issue 23 | openTasksCommands 24 | .command('move') 25 | .argument('', 'Task KEY that you want to change') 26 | .description('Change the status of the task') 27 | .action(key => { 28 | task.changeIssueStatus(key); 29 | }); 30 | 31 | // show details 32 | openTasksCommands 33 | .command('details') 34 | .argument('', 'Task KEY to view') 35 | .description('Details of the Task') 36 | .action(key => { 37 | task.fetchTaskDetails(key); 38 | }); 39 | 40 | // assign issue 41 | openTasksCommands 42 | .command('assign') 43 | .argument('', 'Task key that to assign') 44 | .description('Assign task to another user') 45 | .action(key => { 46 | assignTask.searchUser(key); 47 | }); 48 | 49 | // open issue 50 | openTasksCommands 51 | .command('assign-me') 52 | .argument('', 'Task key to assign') 53 | .description('Assign Issue to Self(logged in user') 54 | .action(key => { 55 | taskOperation.assignToSelf(key) 56 | }); 57 | 58 | // open issue 59 | openTasksCommands 60 | .command('open') 61 | .argument('', 'Task key to open in browser') 62 | .description('Opens task in browser') 63 | .action(key => { 64 | taskOperation.openTask(key); 65 | }); 66 | 67 | }; 68 | -------------------------------------------------------------------------------- /src/commands/boards.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module contains `command` sub-commands related to JIRA board 3 | */ 4 | /** 5 | * --- Available Commands 6 | * jirax board mine - 'Opens your default JIRA board with with your assigned tasks' 7 | * jirax board set - 'Set Your Default JIRA Board' 8 | * jirax board show - 'Opens The JIRA Board that You had Set as Default.' 9 | */ 10 | const program = require('commander'); 11 | const { openMyboard } = require('../operations/Boards'); 12 | const board = require('../api/board'); 13 | exports.loadBoardCommands = () => { 14 | const openBoardCommand = program 15 | .command('board') 16 | .description('JIRA boards activities') 17 | .action(() => { 18 | openBoardCommand.help(); 19 | }); 20 | 21 | /** 22 | * Opens The JIRA Board that You had Set as Default with your assigned tasks. 23 | */ 24 | openBoardCommand 25 | .command('mine') 26 | .description( 27 | 'Opens your default JIRA board with with your assigned tasks' 28 | ) 29 | .action(() => { 30 | openMyboard(true); // opens default with your id preselected 31 | }); 32 | 33 | openBoardCommand 34 | .command('show') 35 | .description('Opens The JIRA Board that You had Set as Default.') 36 | .action(() => { 37 | openMyboard(false); // opens default 38 | }); 39 | 40 | // Set Your Default JIRA Board so that you can open it directly 41 | openBoardCommand 42 | .command('set') 43 | .description('Set Your Default JIRA Board') 44 | .action(answers => { 45 | // select from the default board if present 46 | // if not then open board with selected my profile 47 | board.getBoards(); 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const input = require('./Store'); 5 | 6 | const { loadBoardCommands } = require('./commands/Boards'); 7 | const { loadTasksCommands } = require('./commands/Tasks'); 8 | 9 | program.version('1.0.0').description('CLI Tool for accessing JIRA'); 10 | program 11 | .command('login') 12 | .description('Login using JIRA API Token') 13 | .action(() => { 14 | input.signUpUser(); 15 | }); 16 | 17 | program 18 | .command('logout') 19 | .description('Remove you JIRA credentials') 20 | .action(() => { 21 | input.removeCredentials(); 22 | }); 23 | 24 | // Add other sub commands here 25 | loadBoardCommands(); 26 | loadTasksCommands(); 27 | 28 | program.parse(process.argv); 29 | -------------------------------------------------------------------------------- /src/operations/AssignIssue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Operations for searching user with autocomplete and find user 3 | * 4 | */ 5 | const inquirer = require('inquirer'); 6 | const autocompletePrompt = require('inquirer-autocomplete-prompt'); 7 | 8 | inquirer.registerPrompt('autocomplete', autocompletePrompt); 9 | const fuzzy = require('fuzzy'); 10 | const user = require('../api/User'); 11 | const issue = require('../api/IssueApis'); 12 | 13 | let issueKey; 14 | let fetchedUsersArray; 15 | 16 | module.exports = { 17 | /** 18 | * prompt user search option to assign issue 19 | * @param {*} issue 20 | */ 21 | searchUser(issue) { 22 | issueKey = issue; 23 | inquirer 24 | .prompt([ 25 | { 26 | type: 'autocomplete', 27 | name: 'name', 28 | message: 'Please search for the user to assign', 29 | source: module.exports.searchUsers, 30 | pageSize: 5 31 | } 32 | ]) 33 | .then(answers => { 34 | // assign the issue to selected user 35 | module.exports.findAccountAndAssign(answers.name); 36 | }); 37 | }, 38 | 39 | /** 40 | * Returns the list of available users for the input and assignable issueKey 41 | * @param {*} answers 42 | * @param {*} input 43 | * @returns Name of available users 44 | */ 45 | searchUsers(answers, input) { 46 | input = input || ''; 47 | const names = []; 48 | user.searchAssignableUser(issueKey, input, response => { 49 | fetchedUsersArray = response; 50 | response.map(el => names.push(el.name)); 51 | }); 52 | 53 | return new Promise(resolve => { 54 | setTimeout(() => { 55 | const fuzzyResult = fuzzy.filter(input, names); 56 | resolve(fuzzyResult.map(el => el.original)); 57 | }, 2000); 58 | }); 59 | }, 60 | /** 61 | * Assign the issue for the selected user 62 | * @param {*} username 63 | */ 64 | findAccountAndAssign(username) { 65 | const options = { 66 | /** 67 | * Each element in fetchedUsersArray array is an object, not a string. 68 | * We can pass in a function that is called on each element in the array to 69 | * extract the name to fuzzy search against. In this case, element.name 70 | */ 71 | extract(el) { 72 | return el.name; 73 | } 74 | }; 75 | const fuzzyResult = fuzzy.filter(username, fetchedUsersArray, options); 76 | /** 77 | * @returns ['accountId'] of the selected User 78 | */ 79 | const accountId = fuzzyResult.map(el => el.original.accountId); 80 | 81 | issue.assignIssue(issueKey, accountId[0], username); 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /src/operations/PrintIIssue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Print the response in tabular format 3 | */ 4 | 5 | const consoleApi = require('../utility/console'); 6 | 7 | module.exports = { 8 | /** 9 | * Print the issue details 10 | * @param {issueKey: 'SFMAC-165'} 11 | */ 12 | printIssueDetails(response) { 13 | 14 | let issue = { 15 | key: response.key, 16 | description: response.fields.description, 17 | reporterName: response.fields.reporter.displayName, 18 | assigneeName: response.fields.assignee.displayName, 19 | summary: response.fields.summary, 20 | status: response.fields.status.name, 21 | issueType: response.fields.issuetype.name, 22 | priority: response.fields.priority.name 23 | }; 24 | 25 | consoleApi.printInfo(`Details for ${issue.key}\n`); 26 | 27 | console.log( 28 | `## Summary: ${consoleApi.printBgGreenBright(issue.summary)}\n` 29 | ); 30 | 31 | console.log( 32 | `## ${consoleApi.printBgGreen( 33 | issue.key 34 | )} ${consoleApi.printbgBlueBright( 35 | 'STATUS' 36 | )}: ${issue.status} ## ${consoleApi.printbgCyan( 37 | 'Assignee' 38 | )} - ${issue.assigneeName} \n` 39 | ); 40 | console.log( 41 | `## ${consoleApi.printbgCyan('Type')}: ${ 42 | issue.issueType 43 | } ## ${consoleApi.printbgCyan( 44 | 'Reporter' 45 | )} - ${issue.reporterName} \n` 46 | ); 47 | console.log( 48 | `## ${consoleApi.printbgCyan('Priority')}: ${issue.priority} \n` 49 | ); 50 | console.log(`## ${consoleApi.printbgBlueBright('Description')} \n`); 51 | console.log(`${issue.description}\n`); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /src/operations/Task.js: -------------------------------------------------------------------------------- 1 | const url = require('../Url'); 2 | const issue = require('../api/IssueApis'); 3 | 4 | module.exports = { 5 | openTask: key => { 6 | let issueUrl = url.issueURL(key); 7 | url.openURL(issueUrl); 8 | }, 9 | 10 | assignToSelf: key => { 11 | issue.assignSelf(key) 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/operations/boards.js: -------------------------------------------------------------------------------- 1 | const { openURL, boardURL } = require('../Url'); 2 | const config = require('../utility/utils'); 3 | const print = require('../utility/console'); 4 | 5 | /** 6 | * Open the default set JIRA RAPID BOARD 7 | */ 8 | exports.openMyboard = (myprofile = false) => { 9 | const boardId = config.getConfig('defaultJiraBoard'); 10 | const accountId = myprofile ? config.getConfig('accountId') : null; 11 | if (boardId) { 12 | const url = boardURL(boardId, accountId); 13 | openURL(url); 14 | } else { 15 | print.printError('No board found! Please select a default board.'); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/services/AuthServices.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Authentication Module 3 | * ------------------------------------------------------------- 4 | * return the jiraAuth BASE object and then we can call the apis 5 | * on the base of the main object. 6 | */ 7 | const Configstore = require('configstore'); 8 | const configStore = new Configstore('jiraconfig'); 9 | const { Version2Client } = require('jira.js'); 10 | const jiraConnector = require('jira-connector'); 11 | const emailAddress = configStore.get('emailAddress'); 12 | const apiToken = configStore.get('apiToken'); 13 | const hostname = configStore.get('hostname'); 14 | 15 | module.exports = { 16 | /** 17 | * Config Object contains the username, hostname and API token 18 | * @param {*} config 19 | */ 20 | authenticate(config, callback) { 21 | const HOST_NAME = config.host_name; 22 | let client = new Version2Client({ 23 | host: `https://${config.host_name}`, 24 | authentication: { 25 | basic: { 26 | email: config.user_name, 27 | apiToken: config.api_token 28 | } 29 | } 30 | }); 31 | 32 | client.myself 33 | .getCurrentUser() 34 | .then(user => { 35 | if (user) { 36 | return callback({ 37 | user, 38 | url: `https://${config.host_name}`, 39 | hostname: HOST_NAME, 40 | apiToken: config.api_token 41 | }); 42 | } 43 | }) 44 | .catch(err => { 45 | return callback({ error: 'UnAuthorized' }); 46 | }); 47 | }, 48 | 49 | /** 50 | * Returns the recently authenticated user JIRA-Connector Object 51 | * JIRA.js 52 | */ 53 | jira() { 54 | const emailAddress = configStore.get('emailAddress'); 55 | const apiToken = configStore.get('apiToken'); 56 | const hostUrl = configStore.get('url'); 57 | if (!apiToken) { 58 | console.log('Please login using your API Token'); 59 | } else { 60 | const JIRA_CONFIG = new Version2Client({ 61 | host: hostUrl, 62 | authentication: { 63 | basic: { 64 | email: emailAddress, 65 | apiToken: apiToken 66 | } 67 | } 68 | }); 69 | return JIRA_CONFIG; 70 | } 71 | }, 72 | 73 | /** 74 | * Returns the recently authenticated jira Object 75 | * JIRA -CONNECTOR 76 | */ 77 | jiraConnector() { 78 | if (!apiToken) { 79 | console.log('Please login using your API Token'); 80 | } else { 81 | const jira = new jiraConnector({ 82 | host: hostname, 83 | basic_auth: { 84 | email: emailAddress, 85 | api_token: apiToken 86 | }, 87 | strictSSL: true 88 | }); 89 | return jira; 90 | } 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /src/services/AxiosService.js: -------------------------------------------------------------------------------- 1 | /** 2 | * we generally use JIRA_CONNECTOR and JIRA.js for various JIRA APIS access. 3 | * For some cases we can use this service to call the API directly. 4 | */ 5 | const Configstore = require('configstore'); 6 | const configStore = new Configstore('jiraconfig'); 7 | const axios = require('axios'); 8 | const emailAddress = configStore.get('emailAddress'); 9 | const apiToken = configStore.get('apiToken'); 10 | const hostname = configStore.get('hostname'); 11 | 12 | // encode email and token for basic auth as per JIRA authentications 13 | const encodeEmailToken = Buffer.from(`${emailAddress}:${apiToken}`).toString( 14 | 'base64' 15 | ); 16 | 17 | const instance = axios.create({ 18 | baseURL: `https://${hostname}`, 19 | headers: { 20 | Authorization: `Basic ${encodeEmailToken}`, 21 | 'Cache-Control': 'no-cache', 22 | 'Content-Type': 'application/json' 23 | } 24 | }); 25 | 26 | instance.interceptors.request.use( 27 | function(config) { 28 | return config; 29 | }, 30 | function(error) { 31 | return Promise.reject(error); 32 | } 33 | ); 34 | 35 | axios.interceptors.response.use( 36 | function(response) { 37 | // Any status code that lie within the range of 2xx cause this function to trigger 38 | // Do something with response data 39 | return response; 40 | }, 41 | function(error) { 42 | // Any status codes that falls outside the range of 2xx cause this function to trigger 43 | // Do something with response error 44 | return Promise.reject(error); 45 | } 46 | ); 47 | 48 | module.exports = instance; 49 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * After taking input from user parse the data and validate 3 | * save input to configstore , remove the saved credentials 4 | * when user opted to remove the credentials 5 | */ 6 | 7 | const Configstore = require('configstore'); 8 | const configStore = new Configstore('jiraconfig'); 9 | // apis 10 | const authService = require('./services/AuthServices'); 11 | const print = require('./utility/console'); 12 | const question = require('./api/Questions'); 13 | const util = require('./utility/utils'); 14 | 15 | const spinner = util.spinner('Authenticating...'); 16 | /** 17 | * Verify the user and save the object 18 | * @config object 19 | */ 20 | module.exports = { 21 | signUpUser: () => { 22 | const isLoggedIn = configStore.get('apiToken'); 23 | if (!isLoggedIn) { 24 | question.askCredential().then(answers => { 25 | spinner.start(); 26 | module.exports.verifyAndSave(answers); 27 | }); 28 | } else { 29 | print.printInfo('You are already logged in'); 30 | } 31 | }, 32 | 33 | verifyAndSave: config => { 34 | // authenticate and save the inputs in config store 35 | authService.authenticate(config, data => { 36 | if (data) { 37 | spinner.stop(); 38 | } 39 | if (data.error) { 40 | print.printError( 41 | 'Unauthorized - Please re-enter your credentials' 42 | ); 43 | } else { 44 | module.exports.storeLoginInfo(data); 45 | } 46 | }); 47 | }, 48 | /** 49 | * Store user data after authentication 50 | * @param {*} data 51 | */ 52 | storeLoginInfo: data => { 53 | const message = data.user.displayName.split(' ')[0]; 54 | print.printFigletYellow(`Hi ${message}`); 55 | // saving the data 56 | const { hostname, user, apiToken, url } = data; 57 | const { accountId, timeZone, displayName, emailAddress } = user; 58 | 59 | configStore.set({ 60 | hostname, 61 | timeZone, 62 | accountId, 63 | displayName, 64 | apiToken, 65 | url, 66 | emailAddress 67 | }); 68 | print.printInfo('You have Logged in Successfully 🍺🎉🎊🚀'); 69 | }, 70 | 71 | /** 72 | * @warning 73 | * This method will remove the login credentials from System which is used to 74 | * authenticate with JIRA apis 75 | * Use Case - 76 | * After the revoking the API key you may need to re-loggin with new JIRAX 77 | * then you need to remove the STORED Credentials 78 | * 79 | */ 80 | removeCredentials: () => { 81 | question.confirmRemoval().then(answers => { 82 | if (answers.remove === 'Yes') { 83 | configStore.clear(); 84 | print.printInfo( 85 | 'You Have Successfully removed the login credentials.' 86 | ); 87 | print.printInfo('Use `jirax --login` command for login'); 88 | } 89 | }); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /src/url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module contains the utlity methods related to the URL 3 | * mostly used in opening URL with some base and secondary value. 4 | */ 5 | const open = require('open'); 6 | const util = require('./utility/utils'); 7 | const BOARD_URL = `${util.getBaseUrl()}/secure/RapidBoard.jspa?rapidView=`; 8 | const ISSUE_URL = `${util.getBaseUrl()}/browse`; 9 | 10 | /** 11 | * Opens the given URL 12 | * @param {*} url 13 | */ 14 | exports.openURL = async url => { 15 | await open(url); 16 | }; 17 | 18 | /** 19 | * @return {string} boardURL 20 | * https://yourcompany.atlassian.net/secure/RapidBoard.jspa?rapidView=${boardId} 21 | * @param {*} brandId 22 | */ 23 | exports.boardURL = (boardId, accountId = null) => 24 | accountId 25 | ? `${BOARD_URL}${boardId}&assignee=${accountId}` 26 | : `${BOARD_URL}${boardId}`; 27 | 28 | /** 29 | * https://yourcompany.atlassian.net/browse/Key 30 | * @param {*} issueKey 31 | * @returns 32 | */ 33 | 34 | exports.issueURL = issueKey => { 35 | return `${ISSUE_URL}/${issueKey}`; 36 | }; 37 | -------------------------------------------------------------------------------- /src/utility/console.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const figlet = require('figlet'); 3 | 4 | module.exports = { 5 | printFigletRed: message => 6 | console.log( 7 | chalk.red(figlet.textSync(message, { horizontalLayout: 'full' })) 8 | ), 9 | 10 | printFigletYellow: message => 11 | console.log( 12 | chalk.yellow(figlet.textSync(message, { horizontalLayout: 'full' })) 13 | ), 14 | 15 | printKeyColor: message => chalk.hex('#5e6c84').bold(`${message}`), 16 | 17 | chalkRed: message => chalk.red.bold(`${message}`), 18 | 19 | chalkGreen: message => chalk.green.bold(`${message}`), 20 | 21 | printError: message => console.log(chalk.red.bold(message)), 22 | 23 | printInfo: message => console.log(chalk.green.bold(message)), 24 | 25 | printBgRed: message => chalk.black.bgRed.bold(`${message}`), 26 | 27 | printBgGreen: message => chalk.black.bgGreen.bold(`${message}`), 28 | 29 | printBgBlue: message => chalk.black.bgBlue.bold(`${message}`), 30 | 31 | printBgYellow: message => chalk.black.bgYellow.bold(`${message}`), 32 | 33 | bgMagentaBright: message => chalk.white.bgMagentaBright.bold(`${message}`), 34 | 35 | bgRedBright: message => chalk.white.bgRedBright.bold(`${message}`), 36 | 37 | printBgGreenBright: message => 38 | chalk.hex('#2c3331').bgGreenBright.bold(`${message}`), 39 | 40 | printbgCyan: message => chalk.black.bgCyan.bold(`${message}`), 41 | 42 | printbgBlueBright: message => chalk.white.bgBlueBright.bold(`${message}`) 43 | }; 44 | -------------------------------------------------------------------------------- /src/utility/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utils for the common funcional Usages 3 | */ 4 | const datetimeformat = require('dateformat'); 5 | const ora = require('ora'); 6 | const Configstore = require('configstore'); 7 | 8 | // apis 9 | const consoleApi = require('./console'); 10 | 11 | const jiraconfig = new Configstore('jiraconfig'); 12 | 13 | module.exports = { 14 | /** 15 | * @returns `https://yourcompanyjira.net` 16 | */ 17 | getBaseUrl() { 18 | const hostname = jiraconfig.get('hostname'); 19 | const url = `https://${hostname}`; 20 | return url; 21 | }, 22 | 23 | /** 24 | * @returns `yourcompanyjira.net` 25 | */ 26 | getHostName() { 27 | const hostname = jiraconfig.get('hostname'); 28 | return hostname; 29 | }, 30 | 31 | getEncodedString() { 32 | const encodedString64 = jiraconfig.get(''); 33 | return encodedString64; 34 | }, 35 | 36 | /** 37 | * Returns the value that is stored in the configStore 38 | * { 39 | "hostname": "yourhostname", 40 | "encodedString": "username:password:base64", 41 | ......values you have saved 42 | }% 43 | * 44 | * @param {string} key 45 | * @returns {string} 46 | */ 47 | getConfig: key => jiraconfig.get(key), 48 | /** 49 | * property = { key: value } 50 | * @param {Object} property 51 | * @returns null 52 | */ 53 | setConfig: property => jiraconfig.set(property), 54 | /** 55 | * @returns Date & Time 56 | * @format 10/May/19 12:11 PM 57 | * @param {*} datetime 58 | */ 59 | formatDate(datetime) { 60 | return datetimeformat(datetime, ''); 61 | }, 62 | 63 | /** 64 | * Retruns the bgcolor for text on basis of issueType 65 | * @param {} key issueType 66 | */ 67 | setIssueColor(key) { 68 | switch (key) { 69 | case 'Bug': 70 | return consoleApi.bgRedBright(key); 71 | case 'Improvement': 72 | return consoleApi.printBgGreenBright(key); 73 | case 'New Feature': 74 | return consoleApi.printBgGreenBright(key); 75 | case 'Epic': 76 | return consoleApi.bgMagentaBright(key); 77 | case 'Story': 78 | return consoleApi.printBgGreenBright(key); 79 | case 'Sub-task': 80 | return consoleApi.printbgBlueBright(key); 81 | case 'Task': 82 | return consoleApi.printbgBlueBright(key); 83 | default: 84 | return consoleApi.printbgBlueBright(key); 85 | } 86 | }, 87 | /** 88 | * returns the ora object 89 | * we can use to start and stop the loader using the object 90 | * @param {*} options 91 | */ 92 | spinner(options) { 93 | let modifiedOptions; 94 | if (typeof options === 'string') { 95 | modifiedOptions = { text: options }; 96 | } else { 97 | modifiedOptions = options; 98 | } 99 | const spinnerParams = { 100 | ...modifiedOptions, 101 | color: 'blue', 102 | spinner: 'point' 103 | }; 104 | return ora(spinnerParams); 105 | } 106 | }; 107 | --------------------------------------------------------------------------------