bjira 0.0.3 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,72 @@
1
1
  # Jira cli
2
2
 
3
- This is a simple Jira CLI tool. Vaguely inspired by [jira-cl](https://jiracli.com/).
4
- License: MIT
3
+ This is a simple Jira CLI tool. License: MIT
4
+
5
+ ## How to install it
6
+
7
+ ```
8
+ $ npm install -g bjira
9
+ ```
10
+
11
+ ## How to configure it
12
+
13
+ Run `bjira init` to set up the tool. You can optain the API token from your
14
+ jira settings.
15
+
16
+ ```
17
+ $ bjira init
18
+ ? Provide your jira host: your-config.atlassian.net
19
+ ? Please provide your jira username: username
20
+ ? API token: [hidden]
21
+ ? Enable HTTPS Protocol? Yes
22
+ Config file succesfully created in: /home/baku/.bjira.json
23
+ ```
24
+
25
+ ## How to use it
26
+
27
+ Run `bjira help` to see the main help menu. Each command is well documented.
28
+
29
+ There are 2 main concepts to know:
30
+ - presets
31
+ - custom fields.
32
+
33
+ ### Presets
34
+
35
+ Let's say you want to retrieve all the open issues assigned to you for project
36
+ FOO. The query is something like this:
37
+
38
+ ```
39
+ bjira query 'project = "FOO" AND status != "Done" AND status != "Cancelled" AND assignee = currentUser()'
40
+ ```
41
+
42
+ You can save this query as a preset:
43
+ ```
44
+ bjira create mine 'project = "FOO" AND status != "Done" AND status != "Cancelled" AND assignee = currentUser()'
45
+ ```
46
+
47
+ Then, you can run it using its query name:
48
+ ```
49
+ bjira run mine
50
+ ```
51
+
52
+ If you want to have parameters in your query, use `$$$` as placeholder. For instance:
53
+ ```
54
+ bjira preset create search 'project = "FOO" AND text ~ "$$$" ORDER BY created DESC'
55
+ bjira run search "hello world"
56
+ ```
57
+
58
+
59
+ ### Custom fields
60
+ Jira is strongly configurable via custom fields. You can retrieve the list of custom fields using:
61
+
62
+ ```
63
+ bjira field listall
64
+ ```
65
+
66
+ If you want to see some of them in the issue report, add them:
67
+
68
+ ```
69
+ bjira field add "Story Points"
70
+ ```
71
+
72
+ Any custom fields added to the list will be shown in the issue report (See `bjira issue`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bjira",
3
- "version": "0.0.3",
3
+ "version": "0.0.7",
4
4
  "description": "A simple jira CLI tool",
5
5
  "main": "src/index.js",
6
6
  "author": {
@@ -12,19 +12,18 @@
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git://github.com/bakulf/jira.git"
15
+ "url": "git://github.com/bakulf/bjira.git"
16
16
  },
17
17
  "bugs": {
18
- "url": "https://github.com/bakulf/jira/issues"
18
+ "url": "https://github.com/bakulf/bjira/issues"
19
19
  },
20
20
  "license": "MIT",
21
21
  "type": "module",
22
- "engines" : {
23
- "node" : ">=13.2.0"
22
+ "engines": {
23
+ "node": ">=13.2.0"
24
24
  },
25
25
  "engineStrict": true,
26
26
  "dependencies": {
27
- "cli-table3": "^0.6.0",
28
27
  "commander": "^8.2.0",
29
28
  "execa": "^5.1.1",
30
29
  "inquirer": "^8.2.0",
package/src/ask.js ADDED
@@ -0,0 +1,57 @@
1
+ import inquirer from 'inquirer';
2
+
3
+ class Ask {
4
+ static async askString(message, defaultValue = undefined) {
5
+ const question = {
6
+ type: 'input',
7
+ name: 'value',
8
+ message,
9
+ };
10
+
11
+ if (defaultValue !== undefined) question.default = defaultValue;
12
+ const answer = await inquirer.prompt([question]);
13
+ return answer.value;
14
+ }
15
+
16
+ static async askPassword(message) {
17
+ const answer = await inquirer.prompt([{
18
+ type: 'password',
19
+ name: 'value',
20
+ message,
21
+ }]);
22
+ return answer.value;
23
+ }
24
+
25
+ static async askBoolean(message) {
26
+ const answer = await inquirer.prompt([{
27
+ type: 'confirm',
28
+ name: 'value',
29
+ message,
30
+ }]);
31
+ return answer.value;
32
+ }
33
+
34
+ static async askNumber(message) {
35
+ const question = {
36
+ type: 'number',
37
+ name: 'value',
38
+ message,
39
+ };
40
+
41
+ const answer = await inquirer.prompt([question]);
42
+ return answer.value;
43
+ }
44
+
45
+ static async askList(message, list) {
46
+ const answer = await inquirer.prompt([{
47
+ type: 'list',
48
+ name: 'value',
49
+ message,
50
+ choices: list,
51
+ filter: name => list.indexOf(name),
52
+ }]);
53
+ return answer.value;
54
+ }
55
+ };
56
+
57
+ export default Ask;
package/src/comment.js CHANGED
@@ -1,11 +1,9 @@
1
1
  import execa from 'execa';
2
- import inquirer from 'inquirer';
3
2
  import fs from 'fs';
4
3
  import temp from 'temp';
5
4
 
6
5
  import Command from './command.js';
7
6
  import Jira from './jira.js';
8
- import ErrorHandler from './errorhandler.js';
9
7
 
10
8
  class Comment extends Command {
11
9
  addOptions(program) {
@@ -47,7 +45,7 @@ class Comment extends Command {
47
45
 
48
46
  const jira = new Jira(program);
49
47
 
50
- const data = await jira.spin('Adding the comment...', jira.api.addComment(id, comment));
48
+ await jira.spin('Adding the comment...', jira.api.addComment(id, comment));
51
49
  });
52
50
  }
53
51
  };
package/src/create.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import color from 'chalk';
2
- import inquirer from 'inquirer';
3
- import Table from 'cli-table3';
4
2
 
3
+ import Ask from './ask.js';
5
4
  import Command from './command.js';
6
5
  import Jira from './jira.js';
7
6
  import Issue from './issue.js';
8
7
  import Project from './project.js';
8
+ import Set from './set.js';
9
9
  import User from './user.js';
10
10
 
11
11
  class Create extends Command {
@@ -16,30 +16,7 @@ class Create extends Command {
16
16
  const jira = new Jira(program);
17
17
 
18
18
  const project = await Project.pickProject(jira);
19
-
20
- const issueQuestions = [{
21
- type: 'list',
22
- name: 'issueType',
23
- message: 'Issue type:',
24
- choices: project.issueTypes,
25
- filter: name => project.issueTypes.find(obj => obj.name === name)
26
- }, {
27
- type: 'input',
28
- name: 'summary',
29
- message: 'Summary:',
30
- default: 'New Issue'
31
- }, {
32
- type: 'input',
33
- name: 'description',
34
- message: 'Description:'
35
- }, {
36
- type: 'confirm',
37
- name: 'assign',
38
- message: 'Do you want to assign it?'
39
- }, ];
40
-
41
- // Ask for the issue name and type
42
- const issueAnswers = await inquirer.prompt(issueQuestions);
19
+ const issueTypePos = await Ask.askList('Issue type:', project.issueTypes.map(issueType => issueType.name));
43
20
 
44
21
  // Create the issue object
45
22
  const newIssue = {
@@ -47,61 +24,23 @@ class Create extends Command {
47
24
  project: {
48
25
  key: project.key
49
26
  },
50
- summary: issueAnswers.summary,
27
+ summary: await Ask.askString('Summary:', 'New Issue'),
51
28
  issuetype: {
52
- id: issueAnswers.issueType.id
29
+ id: project.issueTypes[issueTypePos].id
53
30
  }
54
31
  }
55
32
  };
56
33
 
57
- if (issueAnswers.description) {
58
- newIssue.fields.description = issueAnswers.description;
59
- }
60
-
61
- if (issueAnswers.assign) {
62
- const userList = await User.pickUser(jira);
63
-
64
- const userNames = [];
65
- const userIds = [];
66
- userList.forEach(user => {
67
- if (user.active) {
68
- userNames.push(user.displayName);
69
- userIds.push(user.accountId);
70
- }
71
- });
72
-
73
- const assigneeQuestion = [{
74
- type: 'list',
75
- name: 'assignee',
76
- message: 'Assignee:',
77
- choices: userNames,
78
- filter: name => {
79
- const pos = userNames.indexOf(name);
80
- return {
81
- pos,
82
- name,
83
- id: userIds[pos]
84
- };
85
- }
86
- }];
87
-
88
- const assigneeAnswer = await inquirer.prompt(assigneeQuestion);
89
- newIssue.fields.assignee = {
90
- accountId: assigneeAnswer.assignee.id
91
- };
34
+ const description = await Ask.askString('Description:');
35
+ if (description) {
36
+ newIssue.fields.description = description;
92
37
  }
93
38
 
94
- if (issueAnswers.issueType.name === 'Task') {
95
- const parentIssueQuestion = [{
96
- type: 'input',
97
- name: 'issueParentName',
98
- message: 'Please provide the epic:'
99
- }];
100
-
101
- const parentIssueAnswer = await inquirer.prompt(parentIssueQuestion);
102
- if (parentIssueAnswer.issueParentName !== '') {
39
+ if (project.issueTypes[issueTypePos].name === 'Task') {
40
+ const parentIssue = await Ask.askString('Please provide the epic:');
41
+ if (parentIssue !== '') {
103
42
  newIssue.fields.parent = {
104
- key: parentIssueAnswer.issueParentName
43
+ key: parentIssue
105
44
  };
106
45
  }
107
46
  }
@@ -112,6 +51,21 @@ class Create extends Command {
112
51
  console.log('New issue: ' + color.bold.red(issue.key));
113
52
  console.log(color.blue(Issue.url(jira, issue.key)));
114
53
  console.log('');
54
+
55
+ if (await Ask.askBoolean('Do you want to assign it?')) {
56
+ await Set.assignIssue(jira, issue.key);
57
+ }
58
+
59
+ if (jira.fields && jira.fields.length > 0 &&
60
+ await Ask.askBoolean('Do you want to set custom fields?')) {
61
+ for (let fealdName of jira.fields) {
62
+ await Set.setCustomField(jira, filedName, issue.key);
63
+ }
64
+ }
65
+
66
+ if (await Ask.askBoolean('Do you want to set a status?')) {
67
+ await Set.setStatus(jira, issue.key);
68
+ }
115
69
  });
116
70
  }
117
71
  };
@@ -1,24 +1,27 @@
1
- import color from 'chalk';
2
- import Table from 'cli-table3';
1
+ import Table from './table.js';
3
2
 
4
3
  class ErrorHandler {
5
4
  static showError(jira, e) {
6
5
  const table = new Table({
7
- chars: jira.tableChars,
8
6
  head: ['Errors']
9
7
  });
10
8
 
11
- e.error.errorMessages.forEach(error => table.push([color.blue(error)]));
9
+ e.error.errorMessages.forEach(error => table.addRow([{
10
+ color: "blue",
11
+ text: error
12
+ }]));
12
13
  console.log(table.toString());
13
14
  }
14
15
 
15
16
  static showWarningMessages(jira, messages) {
16
17
  const table = new Table({
17
- chars: jira.tableChars,
18
18
  head: ['Warnings']
19
19
  });
20
20
 
21
- messages.forEach(warning => table.push([color.blue(warning)]));
21
+ messages.forEach(warning => table.addRow([{
22
+ color: "blue",
23
+ text: warning
24
+ }]));
22
25
  console.log(table.toString());
23
26
  }
24
27
  };
package/src/field.js CHANGED
@@ -1,9 +1,7 @@
1
- import color from 'chalk';
2
- import Table from 'cli-table3';
3
-
1
+ import Ask from './ask.js';
4
2
  import Command from './command.js';
5
- import ErrorHandler from './errorhandler.js';
6
3
  import Jira from './jira.js';
4
+ import Table from './table.js';
7
5
 
8
6
  class Field extends Command {
9
7
  addOptions(program) {
@@ -14,22 +12,18 @@ class Field extends Command {
14
12
  .action(async () => {
15
13
  const jira = new Jira(program);
16
14
 
17
- let resultFields;
18
- try {
19
- resultFields = await Field.listFields(jira);
20
- } catch (e) {
21
- ErrorHandler.showError(jira, e);
22
- return;
23
- }
15
+ const resultFields = await Field.listFields(jira);
24
16
 
25
17
  const table = new Table({
26
- chars: jira.tableChars,
27
18
  head: ['Name', 'Supported', 'Type']
28
19
  });
29
20
 
30
21
  resultFields.forEach(field => {
31
22
  const supported = Field.isSupported(field.schema?.type);
32
- table.push([color.blue(field.name), supported, supported ? field.schema?.type : ""]);
23
+ table.addRow([{
24
+ color: "blue",
25
+ text: field.name
26
+ }, supported, supported ? field.schema?.type : ""]);
33
27
  });
34
28
  console.log(table.toString());
35
29
  });
@@ -40,13 +34,7 @@ class Field extends Command {
40
34
  .action(async fieldName => {
41
35
  const jira = new Jira(program);
42
36
 
43
- let resultFields;
44
- try {
45
- resultFields = await Field.listFields(jira);
46
- } catch (e) {
47
- ErrorHandler.showError(jira, e);
48
- return;
49
- }
37
+ const resultFields = await Field.listFields(jira);
50
38
 
51
39
  const fieldData = resultFields.find(field => field.name === fieldName);
52
40
  if (!fieldData) {
@@ -88,11 +76,13 @@ class Field extends Command {
88
76
  const jira = new Jira(program);
89
77
 
90
78
  const table = new Table({
91
- chars: jira.tableChars,
92
79
  head: ['Name']
93
80
  });
94
81
 
95
- jira.fields.forEach(fieldName => table.push([color.blue(fieldName)]));
82
+ jira.fields.forEach(fieldName => table.addRow([{
83
+ color: "blue",
84
+ text: fieldName
85
+ }]));
96
86
  console.log(table.toString());
97
87
  });
98
88
  }
@@ -105,14 +95,8 @@ class Field extends Command {
105
95
  return ["string", "number"].includes(fieldType);
106
96
  }
107
97
 
108
- static async getFieldDataIfSupported(jira, fieldName) {
109
- let resultFields;
110
- try {
111
- resultFields = await Field.listFields(jira);
112
- } catch (e) {
113
- ErrorHandler.showError(jira, e);
114
- return null;
115
- }
98
+ static async askFieldIfSupported(jira, fieldName) {
99
+ const resultFields = await Field.listFields(jira);
116
100
 
117
101
  let fieldData;
118
102
  resultFields.forEach(field => {
@@ -132,17 +116,16 @@ class Field extends Command {
132
116
  let type;
133
117
  switch (fieldData.schema.type) {
134
118
  case 'number':
135
- type = 'number';
136
- break;
119
+ return {
120
+ value: await Ask.askNumber(`${fieldName}:`), key: fieldData.key
121
+ };
137
122
  case 'string':
138
- type = 'input';
139
- break;
123
+ return {
124
+ value: Ask.askString(`${fieldName}:`), key: fieldData.key
125
+ };
140
126
  }
141
127
 
142
- return {
143
- type,
144
- key: fieldData.key
145
- };
128
+ return null;
146
129
  }
147
130
  };
148
131
 
package/src/index.js CHANGED
@@ -17,7 +17,7 @@ import Run from './run.js';
17
17
  import Set from './set.js';
18
18
  import Sprint from './sprint.js';
19
19
 
20
- const DEFAULT_CONFIG_FILE = path.join(os.homedir(), ".jira.json")
20
+ const DEFAULT_CONFIG_FILE = path.join(os.homedir(), ".bjira.json")
21
21
 
22
22
  const commands = [
23
23
  new Comment(),
package/src/init.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import color from 'chalk';
2
- import inquirer from 'inquirer';
3
2
  import fs from 'fs';
4
3
 
4
+ import Ask from './ask.js';
5
5
  import Command from './command.js';
6
6
 
7
7
  class Init extends Command {
@@ -10,33 +10,13 @@ class Init extends Command {
10
10
  .description('Create the initial config file')
11
11
  .action(async () => {
12
12
  const opts = program.opts();
13
- const questions = [{
14
- type: ' input',
15
- name: 'host',
16
- message: 'Provide your jira host:',
17
- default: 'example.atlassian.net'
18
- }, {
19
- type: 'input',
20
- name: 'username',
21
- message: 'Please provide your jira username:'
22
- }, {
23
- type: 'password',
24
- name: 'password',
25
- message: 'API token:'
26
- }, {
27
- type: 'confirm',
28
- name: 'protocol',
29
- message: 'Enable HTTPS Protocol?'
30
- }];
31
-
32
- const answers = await inquirer.prompt(questions);
33
13
 
34
14
  const config = {
35
15
  jira: {
36
- protocol: answers.protocol ? 'https' : 'http',
37
- host: answers.host.trim(),
38
- username: answers.username.trim(),
39
- password: answers.password.trim(),
16
+ host: (await Ask.askString('Provide your jira host:', 'example.atlassian.net')).trim(),
17
+ protocol: await Ask.askBoolean('Enable HTTPS Protocol?') ? 'https' : 'http',
18
+ username: (await Ask.askString('Please provide your jira username:')).trim(),
19
+ password: (await Ask.askPassword('API token:')).trim(),
40
20
  apiVersion: '2',
41
21
  strictSSL: true,
42
22
  },