fixdiscover 1.2.1 → 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/README.md +1 -0
- package/dist/main.js +42336 -44449
- package/package.json +18 -18
- package/src/cli.ts +29 -15
- package/src/jira.ts +32 -21
- package/src/util.ts +16 -1
- package/vitest.config.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fixdiscover",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Small CLI tool to search for Jira issues with linked PRs and Issues that are fixed in an upstream projects.",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"test": "tests"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "esbuild ./src/main.js --bundle --outdir=dist --platform=node --target=
|
|
12
|
+
"build": "esbuild ./src/main.js --bundle --outdir=dist --platform=node --target=node24.0.0 --packages=bundle",
|
|
13
13
|
"format": "prettier --write '**/*.ts'",
|
|
14
14
|
"format-check": "prettier --check '**/*.ts'",
|
|
15
15
|
"test": "vitest run --coverage",
|
|
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.14.1",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
22
|
"url": "git+https://github.com/redhat-plumbers-in-action/storypointer.git"
|
|
@@ -34,25 +34,25 @@
|
|
|
34
34
|
"patch"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@actions/core": "^
|
|
38
|
-
"@octokit/core": "^7.0.
|
|
39
|
-
"@octokit/plugin-throttling": "^11.0.
|
|
40
|
-
"@octokit/types": "^
|
|
37
|
+
"@actions/core": "^3.0.1",
|
|
38
|
+
"@octokit/core": "^7.0.6",
|
|
39
|
+
"@octokit/plugin-throttling": "^11.0.3",
|
|
40
|
+
"@octokit/types": "^16.0.0",
|
|
41
41
|
"@total-typescript/ts-reset": "0.6.1",
|
|
42
|
-
"bugzilla": "^3.1.
|
|
42
|
+
"bugzilla": "^3.1.6",
|
|
43
43
|
"chalk": "5.6.2",
|
|
44
|
-
"commander": "14.0.
|
|
45
|
-
"dotenv": "17.2
|
|
46
|
-
"jira.js": "5.
|
|
47
|
-
"zod": "4.
|
|
44
|
+
"commander": "14.0.3",
|
|
45
|
+
"dotenv": "17.4.2",
|
|
46
|
+
"jira.js": "5.3.1",
|
|
47
|
+
"zod": "4.4.3"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@types/node": "
|
|
51
|
-
"@vitest/coverage-v8": "
|
|
52
|
-
"esbuild": "0.
|
|
53
|
-
"prettier": "3.
|
|
50
|
+
"@types/node": "25.9.1",
|
|
51
|
+
"@vitest/coverage-v8": "4.1.7",
|
|
52
|
+
"esbuild": "0.28.0",
|
|
53
|
+
"prettier": "3.8.3",
|
|
54
54
|
"ts-node": "10.9.2",
|
|
55
|
-
"typescript": "
|
|
56
|
-
"vitest": "
|
|
55
|
+
"typescript": "6.0.3",
|
|
56
|
+
"vitest": "4.1.7"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { Comment } from 'jira.js/out/version2/models';
|
|
3
2
|
|
|
4
3
|
import { Bugzilla } from './bugzilla';
|
|
5
4
|
import { Jira } from './jira';
|
|
6
5
|
import { Logger } from './logger';
|
|
7
6
|
import { getOctokit } from './octokit';
|
|
8
|
-
import { getDefaultValue, getOptions, tokenUnavailable } from './util';
|
|
7
|
+
import { getDefaultValue, getOptions, raise, tokenUnavailable } from './util';
|
|
9
8
|
import { LinkFinder } from './linkfinder';
|
|
10
9
|
|
|
11
10
|
import { IssueLinks, LinkObject } from './schema/link';
|
|
@@ -18,7 +17,7 @@ export function cli(): Command {
|
|
|
18
17
|
.description(
|
|
19
18
|
'🔍 A small CLI tool is used to search for Jira issues with linked PRs and issues that are fixed in upstream projects'
|
|
20
19
|
)
|
|
21
|
-
.version('
|
|
20
|
+
.version('2.0.0');
|
|
22
21
|
|
|
23
22
|
program
|
|
24
23
|
.requiredOption(
|
|
@@ -31,6 +30,7 @@ export function cli(): Command {
|
|
|
31
30
|
'upstream project',
|
|
32
31
|
getDefaultValue('UPSTREAM')
|
|
33
32
|
)
|
|
33
|
+
.option('-l, --login <email>', 'Jira login email', getDefaultValue('LOGIN'))
|
|
34
34
|
.option('--migrate', 'migrate links from Bugzilla to Jira')
|
|
35
35
|
.option('-n, --nocolor', 'disable color output', getDefaultValue('NOCOLOR'))
|
|
36
36
|
.option('-x, --dry', 'dry run', getDefaultValue('DRY'));
|
|
@@ -46,7 +46,18 @@ const runProgram = async () => {
|
|
|
46
46
|
const logger = new Logger(!!options.nocolor);
|
|
47
47
|
|
|
48
48
|
const jiraToken = process.env.JIRA_API_TOKEN ?? tokenUnavailable('jira');
|
|
49
|
-
const
|
|
49
|
+
const jiraLogin =
|
|
50
|
+
typeof options.login === 'string' && options.login
|
|
51
|
+
? options.login
|
|
52
|
+
: raise(
|
|
53
|
+
'Jira login email is required. Use --login <email> or set the LOGIN environment variable.'
|
|
54
|
+
);
|
|
55
|
+
const jira = new Jira(
|
|
56
|
+
'https://redhat.atlassian.net',
|
|
57
|
+
jiraToken,
|
|
58
|
+
options.dry,
|
|
59
|
+
jiraLogin
|
|
60
|
+
);
|
|
50
61
|
|
|
51
62
|
const bugzillaToken = options.migrate
|
|
52
63
|
? (process.env.BUGZILLA_API_TOKEN ?? tokenUnavailable('bugzilla'))
|
|
@@ -68,19 +79,19 @@ const runProgram = async () => {
|
|
|
68
79
|
for (const issue of issues) {
|
|
69
80
|
let links: LinkObject[] = [];
|
|
70
81
|
|
|
71
|
-
const bugzillaBug: { bugid: number } | null =
|
|
72
|
-
issue.fields[jira.fields.bugzillaBug];
|
|
73
|
-
|
|
74
82
|
const externalLinks = await jira.getLinks(issue.id);
|
|
83
|
+
const bugzillaBugId = jira.getBugzillaBugId(externalLinks);
|
|
84
|
+
|
|
85
|
+
const renderedComments =
|
|
86
|
+
(issue.renderedFields as Record<string, any>)?.comment?.comments ?? [];
|
|
75
87
|
|
|
76
|
-
for (const comment of
|
|
77
|
-
body
|
|
78
|
-
|
|
79
|
-
if (!comment?.body) {
|
|
88
|
+
for (const comment of renderedComments) {
|
|
89
|
+
const body: unknown = comment?.body;
|
|
90
|
+
if (typeof body !== 'string' || !body) {
|
|
80
91
|
continue;
|
|
81
92
|
}
|
|
82
93
|
|
|
83
|
-
const commentLinks = linkFinder.getLinks(
|
|
94
|
+
const commentLinks = linkFinder.getLinks(body);
|
|
84
95
|
|
|
85
96
|
if (commentLinks) {
|
|
86
97
|
links.push(...(await linkFinder.checkUpstream(commentLinks, octokit)));
|
|
@@ -95,8 +106,8 @@ const runProgram = async () => {
|
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
|
|
98
|
-
if (options.migrate &&
|
|
99
|
-
const bug = (await bugzilla.getBugs(
|
|
109
|
+
if (options.migrate && bugzillaBugId !== null) {
|
|
110
|
+
const bug = (await bugzilla.getBugs(bugzillaBugId))[0];
|
|
100
111
|
|
|
101
112
|
let linksForMigration: LinkObject[] = [];
|
|
102
113
|
|
|
@@ -138,7 +149,10 @@ const runProgram = async () => {
|
|
|
138
149
|
await jira.setLabels(issue.key, ['backport']);
|
|
139
150
|
data.push({
|
|
140
151
|
key: jira.getIssueURL(issue.key),
|
|
141
|
-
bz:
|
|
152
|
+
bz:
|
|
153
|
+
bugzillaBugId !== null
|
|
154
|
+
? bugzilla.getIssueURL(bugzillaBugId)
|
|
155
|
+
: undefined,
|
|
142
156
|
links,
|
|
143
157
|
});
|
|
144
158
|
}
|
package/src/jira.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Version3Client } from 'jira.js';
|
|
2
2
|
|
|
3
3
|
import { raise } from './util';
|
|
4
4
|
|
|
5
5
|
export class Jira {
|
|
6
|
-
readonly api:
|
|
7
|
-
readonly fields = {
|
|
8
|
-
bugzillaBug: 'customfield_12316840',
|
|
9
|
-
};
|
|
6
|
+
readonly api: Version3Client;
|
|
10
7
|
|
|
11
8
|
readonly baseJQL = 'Project = RHEL AND statusCategory = "To Do"';
|
|
12
9
|
JQL = '';
|
|
@@ -14,12 +11,16 @@ export class Jira {
|
|
|
14
11
|
constructor(
|
|
15
12
|
readonly instance: string,
|
|
16
13
|
apiToken: string,
|
|
17
|
-
readonly dry: boolean
|
|
14
|
+
readonly dry: boolean,
|
|
15
|
+
email: string
|
|
18
16
|
) {
|
|
19
|
-
this.api = new
|
|
17
|
+
this.api = new Version3Client({
|
|
20
18
|
host: instance,
|
|
21
19
|
authentication: {
|
|
22
|
-
|
|
20
|
+
basic: {
|
|
21
|
+
email,
|
|
22
|
+
apiToken,
|
|
23
|
+
},
|
|
23
24
|
},
|
|
24
25
|
});
|
|
25
26
|
}
|
|
@@ -34,19 +35,14 @@ export class Jira {
|
|
|
34
35
|
this.JQL += component ? ` AND component = ${component}` : '';
|
|
35
36
|
this.JQL += ' ORDER BY id DESC';
|
|
36
37
|
|
|
37
|
-
const response =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'id',
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.fields.bugzillaBug,
|
|
46
|
-
],
|
|
47
|
-
// We should paginate this, let's set 300 for now.
|
|
48
|
-
maxResults: 300,
|
|
49
|
-
});
|
|
38
|
+
const response =
|
|
39
|
+
await this.api.issueSearch.searchForIssuesUsingJqlEnhancedSearchPost({
|
|
40
|
+
jql: this.JQL,
|
|
41
|
+
fields: ['id', 'issuetype', 'summary', 'assignee', 'comment'],
|
|
42
|
+
expand: 'renderedFields',
|
|
43
|
+
// We should paginate this, let's set 300 for now.
|
|
44
|
+
maxResults: 300,
|
|
45
|
+
});
|
|
50
46
|
|
|
51
47
|
return response.issues ?? raise('Jira.getIssues(): missing issues.');
|
|
52
48
|
}
|
|
@@ -59,6 +55,21 @@ export class Jira {
|
|
|
59
55
|
return response ?? [];
|
|
60
56
|
}
|
|
61
57
|
|
|
58
|
+
static readonly bugzillaUrlRegex =
|
|
59
|
+
/^https?:\/\/bugzilla\.redhat\.com\/show_bug\.cgi\?id=(\d+)$/;
|
|
60
|
+
|
|
61
|
+
getBugzillaBugId(
|
|
62
|
+
links: Awaited<ReturnType<typeof this.getLinks>>
|
|
63
|
+
): number | null {
|
|
64
|
+
for (const link of links) {
|
|
65
|
+
const match = link.object?.url?.match(Jira.bugzillaUrlRegex);
|
|
66
|
+
if (match) {
|
|
67
|
+
return Number(match[1]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
async setLabels(key: string, labels: string[]) {
|
|
63
74
|
if (this.dry) {
|
|
64
75
|
//console.debug(`DRY: setLabels(${key}, ${labels})`);
|
package/src/util.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OptionValues } from 'commander';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
|
|
3
4
|
export function raise(error: string): never {
|
|
4
5
|
throw new Error(error);
|
|
@@ -23,12 +24,21 @@ export function tokenUnavailable(type: 'jira' | 'bugzilla' | 'github'): never {
|
|
|
23
24
|
);
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
export function getUserFromLogin(): string | undefined {
|
|
28
|
+
try {
|
|
29
|
+
const login = os.userInfo().username;
|
|
30
|
+
return `${login}@redhat.com`;
|
|
31
|
+
} catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
export function isDefaultValuesDisabled(): boolean {
|
|
27
37
|
return process.env['NODEFAULTS'] ? true : false;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
export function getDefaultValue(
|
|
31
|
-
envName: 'COMPONENT' | 'UPSTREAM' | 'NOCOLOR' | 'DRY'
|
|
41
|
+
envName: 'COMPONENT' | 'UPSTREAM' | 'NOCOLOR' | 'DRY' | 'LOGIN'
|
|
32
42
|
) {
|
|
33
43
|
if (isDefaultValuesDisabled()) {
|
|
34
44
|
return undefined;
|
|
@@ -36,6 +46,10 @@ export function getDefaultValue(
|
|
|
36
46
|
|
|
37
47
|
const value = process.env[envName];
|
|
38
48
|
|
|
49
|
+
if (envName === 'LOGIN' && !value) {
|
|
50
|
+
return getUserFromLogin();
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
if ((envName === 'NOCOLOR' || envName === 'DRY') && !value) {
|
|
40
54
|
return false;
|
|
41
55
|
}
|
|
@@ -48,6 +62,7 @@ export function getOptions(inputs: OptionValues): OptionValues {
|
|
|
48
62
|
...inputs,
|
|
49
63
|
component: inputs.component || getDefaultValue('COMPONENT'),
|
|
50
64
|
upstream: inputs.upstream || getDefaultValue('UPSTREAM'),
|
|
65
|
+
login: inputs.login || getDefaultValue('LOGIN'),
|
|
51
66
|
};
|
|
52
67
|
}
|
|
53
68
|
|
package/vitest.config.ts
ADDED