jira-sprinter 1.1.0 → 2.0.0
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/dist/main.js +23286 -27271
- package/package.json +13 -13
- package/src/cli.ts +65 -24
- package/src/jira.ts +57 -27
- package/vitest.config.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jira-sprinter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Small CLI tool to manage sprints in JIRA Board",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"update-snapshots": "vitest run --update",
|
|
17
17
|
"all": "yarn && yarn run build && yarn run format && yarn test"
|
|
18
18
|
},
|
|
19
|
-
"packageManager": "yarn@4.
|
|
19
|
+
"packageManager": "yarn@4.12.0",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
22
|
"url": "git+https://github.com/redhat-plumbers-in-action/sprinter.git"
|
|
@@ -34,22 +34,22 @@
|
|
|
34
34
|
"scrum"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@inquirer/checkbox": "^
|
|
38
|
-
"@inquirer/select": "
|
|
37
|
+
"@inquirer/checkbox": "^5.1.2",
|
|
38
|
+
"@inquirer/select": "5.1.2",
|
|
39
39
|
"@total-typescript/ts-reset": "0.6.1",
|
|
40
40
|
"chalk": "5.6.2",
|
|
41
|
-
"commander": "14.0.
|
|
42
|
-
"dotenv": "17.
|
|
43
|
-
"jira.js": "5.
|
|
44
|
-
"zod": "4.
|
|
41
|
+
"commander": "14.0.3",
|
|
42
|
+
"dotenv": "17.3.1",
|
|
43
|
+
"jira.js": "5.3.1",
|
|
44
|
+
"zod": "4.3.6"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@types/node": "
|
|
48
|
-
"@vitest/coverage-v8": "
|
|
49
|
-
"esbuild": "0.
|
|
50
|
-
"prettier": "3.
|
|
47
|
+
"@types/node": "25.5.0",
|
|
48
|
+
"@vitest/coverage-v8": "4.1.0",
|
|
49
|
+
"esbuild": "0.27.4",
|
|
50
|
+
"prettier": "3.8.1",
|
|
51
51
|
"ts-node": "10.9.2",
|
|
52
52
|
"typescript": "5.9.3",
|
|
53
|
-
"vitest": "
|
|
53
|
+
"vitest": "4.1.0"
|
|
54
54
|
}
|
|
55
55
|
}
|
package/src/cli.ts
CHANGED
|
@@ -5,7 +5,12 @@ import checkbox from '@inquirer/checkbox';
|
|
|
5
5
|
|
|
6
6
|
import { Jira } from './jira';
|
|
7
7
|
import { Logger } from './logger';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getDefaultValue,
|
|
10
|
+
getOptions,
|
|
11
|
+
getUserFromLogin,
|
|
12
|
+
tokenUnavailable,
|
|
13
|
+
} from './util';
|
|
9
14
|
|
|
10
15
|
import { SearchResults } from 'jira.js/dist/esm/types/agile/models';
|
|
11
16
|
import {
|
|
@@ -15,7 +20,7 @@ import {
|
|
|
15
20
|
colorSizeSchema,
|
|
16
21
|
Size,
|
|
17
22
|
} from './schema/jira';
|
|
18
|
-
import { Issue } from 'jira.js/dist/esm/types/
|
|
23
|
+
import { Issue } from 'jira.js/dist/esm/types/version3/models/issue';
|
|
19
24
|
|
|
20
25
|
export function cli(): Command {
|
|
21
26
|
const program = new Command();
|
|
@@ -23,7 +28,7 @@ export function cli(): Command {
|
|
|
23
28
|
program
|
|
24
29
|
.name('jira-sprinter')
|
|
25
30
|
.description('🏃 Small CLI tool to manage sprints in JIRA Board')
|
|
26
|
-
.version('
|
|
31
|
+
.version('2.0.0');
|
|
27
32
|
|
|
28
33
|
program
|
|
29
34
|
.option('-b, --board [board]', 'Jira Board ID', getDefaultValue('BOARD'))
|
|
@@ -52,10 +57,11 @@ const runProgram = async () => {
|
|
|
52
57
|
|
|
53
58
|
const token = process.env.JIRA_API_TOKEN ?? tokenUnavailable();
|
|
54
59
|
const jira = new Jira(
|
|
55
|
-
'https://
|
|
60
|
+
'https://redhat.atlassian.net',
|
|
56
61
|
token,
|
|
57
62
|
options.dry,
|
|
58
|
-
logger
|
|
63
|
+
logger,
|
|
64
|
+
getUserFromLogin()
|
|
59
65
|
);
|
|
60
66
|
|
|
61
67
|
const version = await jira.getVersion();
|
|
@@ -93,11 +99,6 @@ const runProgram = async () => {
|
|
|
93
99
|
process.exit(0);
|
|
94
100
|
}
|
|
95
101
|
|
|
96
|
-
// TODO:
|
|
97
|
-
// Show issue - allow to split it into tasks
|
|
98
|
-
// Allow to set story points and assignee
|
|
99
|
-
// add task into sprint
|
|
100
|
-
|
|
101
102
|
for (const issue of issues) {
|
|
102
103
|
logger.log(
|
|
103
104
|
`\n${issueTypeSchema.parse(issue.fields?.issuetype.name)} ${issue.key} - ${chalk.bold(issueStatusSchema.parse(issue.fields?.status.name))} - ${chalk.italic(issue.fields?.assignee?.displayName ?? '')}`
|
|
@@ -110,12 +111,44 @@ const runProgram = async () => {
|
|
|
110
111
|
);
|
|
111
112
|
|
|
112
113
|
const availableTasks = [
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
{
|
|
115
|
+
name: 'DEV Task',
|
|
116
|
+
summary: '[DEV Task]:',
|
|
117
|
+
label: 'dev_task',
|
|
118
|
+
value: 14476,
|
|
119
|
+
checked: true,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'QE Task',
|
|
123
|
+
summary: '[QE Task]:',
|
|
124
|
+
label: 'qe_task',
|
|
125
|
+
value: 14480,
|
|
126
|
+
checked: true,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'Upstream',
|
|
130
|
+
summary: '[Upstream]:',
|
|
131
|
+
label: 'upstream_task',
|
|
132
|
+
value: 14475,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'Root Cause Analysis Task',
|
|
136
|
+
summary: '[Root Cause Analysis Task]:',
|
|
137
|
+
label: 'root_cause_analysis_task',
|
|
138
|
+
value: 14483,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'Preliminary Testing Task',
|
|
142
|
+
summary: '[Preliminary Testing Task]:',
|
|
143
|
+
label: 'preliminary_testing_task',
|
|
144
|
+
value: 14478,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'Integration Testing',
|
|
148
|
+
summary: '[Integration Testing Task]:',
|
|
149
|
+
label: 'integration_testing_task',
|
|
150
|
+
value: 14481,
|
|
151
|
+
},
|
|
119
152
|
];
|
|
120
153
|
|
|
121
154
|
let answer: number[] = [];
|
|
@@ -123,11 +156,21 @@ const runProgram = async () => {
|
|
|
123
156
|
answer = await checkbox({
|
|
124
157
|
message: `Split ${chalk.bold(issue.key)} into following tasks:\n`,
|
|
125
158
|
choices: [
|
|
126
|
-
...availableTasks.map(task =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
159
|
+
...availableTasks.map(task => {
|
|
160
|
+
const isTaskOpen = issue.fields?.issuelinks?.some(
|
|
161
|
+
l =>
|
|
162
|
+
l.type?.outward === 'split to' &&
|
|
163
|
+
l.outwardIssue?.fields?.summary.startsWith(task.summary) &&
|
|
164
|
+
l.outwardIssue?.fields?.status.name !== 'Closed'
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
name: colorTaskSchema(task.name),
|
|
169
|
+
value: task.value,
|
|
170
|
+
checked: task.checked && !isTaskOpen,
|
|
171
|
+
disabled: isTaskOpen,
|
|
172
|
+
};
|
|
173
|
+
}),
|
|
131
174
|
new Separator(),
|
|
132
175
|
{ name: 'SKIP', value: -1 },
|
|
133
176
|
{ name: 'EXIT', value: -2 },
|
|
@@ -171,7 +214,6 @@ const runProgram = async () => {
|
|
|
171
214
|
}
|
|
172
215
|
}
|
|
173
216
|
|
|
174
|
-
// loop through tasks and and set sprint, assignee and story points
|
|
175
217
|
for (const task of tasks) {
|
|
176
218
|
// Skip QE Task
|
|
177
219
|
if (task.fields.summary.includes('QE Task')) {
|
|
@@ -240,9 +282,8 @@ const runProgram = async () => {
|
|
|
240
282
|
);
|
|
241
283
|
}
|
|
242
284
|
|
|
243
|
-
// update task
|
|
244
285
|
await jira.setValues(task.key, {
|
|
245
|
-
assignee: issue.fields?.assignee?.
|
|
286
|
+
assignee: issue.fields?.assignee?.accountId,
|
|
246
287
|
size: storyPointsAnswer,
|
|
247
288
|
sprint:
|
|
248
289
|
addToSprintAnswer && sprintOrBacklog != -1
|
package/src/jira.ts
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
import { AgileClient,
|
|
1
|
+
import { AgileClient, Version3Client } from 'jira.js';
|
|
2
2
|
import { SearchResults, Sprint } from 'jira.js/dist/esm/types/agile/models';
|
|
3
|
+
import { Issue } from 'jira.js/dist/esm/types/version3/models/issue';
|
|
3
4
|
|
|
4
5
|
import { raise } from './util';
|
|
5
6
|
import { Size } from './schema/jira';
|
|
6
7
|
import { Logger } from './logger';
|
|
7
8
|
|
|
8
9
|
export class Jira {
|
|
9
|
-
readonly api:
|
|
10
|
+
readonly api: Version3Client;
|
|
10
11
|
readonly agile: AgileClient;
|
|
11
12
|
readonly fields = {
|
|
12
|
-
automation: '
|
|
13
|
+
automation: 'customfield_10553',
|
|
13
14
|
assignee: 'assignee',
|
|
14
15
|
priority: 'priority',
|
|
15
|
-
severity: '
|
|
16
|
-
sprint: '
|
|
17
|
-
storyPoints: '
|
|
16
|
+
severity: 'customfield_10840',
|
|
17
|
+
sprint: 'customfield_10020',
|
|
18
|
+
storyPoints: 'customfield_10028',
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
readonly issuesWithoutTasksJQL = `
|
|
21
|
+
readonly issuesWithoutTasksJQL = `(labels not in (upstream_task, dev_task, qe_task, root_cause_analysis_task, preliminary_testing_task, integration_testing_task) OR labels is EMPTY) AND type in (Bug, "Story", Vulnerability) AND Project = RHEL AND statusCategory != Done`;
|
|
21
22
|
|
|
22
23
|
constructor(
|
|
23
24
|
readonly instance: string,
|
|
24
25
|
apiToken: string,
|
|
25
26
|
readonly dry: boolean,
|
|
26
|
-
readonly logger: Logger
|
|
27
|
+
readonly logger: Logger,
|
|
28
|
+
email: string
|
|
27
29
|
) {
|
|
28
|
-
this.api = new
|
|
30
|
+
this.api = new Version3Client({
|
|
29
31
|
host: instance,
|
|
30
32
|
authentication: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
basic: {
|
|
34
|
+
email,
|
|
35
|
+
apiToken,
|
|
33
36
|
},
|
|
34
37
|
},
|
|
35
38
|
});
|
|
@@ -37,8 +40,9 @@ export class Jira {
|
|
|
37
40
|
this.agile = new AgileClient({
|
|
38
41
|
host: instance,
|
|
39
42
|
authentication: {
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
basic: {
|
|
44
|
+
email,
|
|
45
|
+
apiToken,
|
|
42
46
|
},
|
|
43
47
|
},
|
|
44
48
|
});
|
|
@@ -78,11 +82,12 @@ export class Jira {
|
|
|
78
82
|
'assignee',
|
|
79
83
|
'priority',
|
|
80
84
|
'components',
|
|
81
|
-
// sprint
|
|
82
85
|
this.fields.storyPoints,
|
|
83
86
|
this.fields.severity,
|
|
87
|
+
'issuelinks',
|
|
84
88
|
],
|
|
85
89
|
});
|
|
90
|
+
|
|
86
91
|
return response.issues;
|
|
87
92
|
}
|
|
88
93
|
|
|
@@ -115,18 +120,43 @@ export class Jira {
|
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
async getlinkedTasks(issue: string, expectedTasks: string[]) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
if (this.dry) {
|
|
124
|
+
this.logger.log(`Would get linked tasks for issue: ${issue}`);
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
key: 'RHEL-1234',
|
|
128
|
+
fields: {
|
|
129
|
+
summary: '[DEV Task] Test Task',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: 'RHEL-1235',
|
|
134
|
+
fields: {
|
|
135
|
+
summary: '[QE Task] Test Task',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
key: 'RHEL-1236',
|
|
140
|
+
fields: {
|
|
141
|
+
summary: '[Upstream] Test Task',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
] as unknown as Issue[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const response =
|
|
148
|
+
await this.api.issueSearch.searchForIssuesUsingJqlEnhancedSearchPost({
|
|
149
|
+
jql: `issue in linkedIssues("${issue}") AND type = Task AND status = New AND (${this.composeTaskSummaryJQL(expectedTasks)})`,
|
|
150
|
+
fields: [
|
|
151
|
+
'id',
|
|
152
|
+
'issuetype',
|
|
153
|
+
'status',
|
|
154
|
+
'components',
|
|
155
|
+
'summary',
|
|
156
|
+
'assignee',
|
|
157
|
+
this.fields.storyPoints,
|
|
158
|
+
],
|
|
159
|
+
});
|
|
130
160
|
|
|
131
161
|
return response.issues ?? [];
|
|
132
162
|
}
|
|
@@ -158,7 +188,7 @@ export class Jira {
|
|
|
158
188
|
}
|
|
159
189
|
) {
|
|
160
190
|
const assigneeValue = values.assignee
|
|
161
|
-
? { [this.fields.assignee]: {
|
|
191
|
+
? { [this.fields.assignee]: { accountId: values.assignee } }
|
|
162
192
|
: {};
|
|
163
193
|
const storyPointsValue =
|
|
164
194
|
values.size !== undefined
|
package/vitest.config.ts
ADDED