jira-ai 0.1.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 +364 -0
- package/dist/cli.js +87 -0
- package/dist/commands/about.js +83 -0
- package/dist/commands/add-comment.js +109 -0
- package/dist/commands/me.js +23 -0
- package/dist/commands/project-statuses.js +23 -0
- package/dist/commands/projects.js +35 -0
- package/dist/commands/run-jql.js +44 -0
- package/dist/commands/task-with-details.js +23 -0
- package/dist/commands/update-description.js +109 -0
- package/dist/lib/formatters.js +193 -0
- package/dist/lib/jira-client.js +216 -0
- package/dist/lib/settings.js +68 -0
- package/dist/lib/utils.js +79 -0
- package/jest.config.js +21 -0
- package/package.json +47 -0
- package/settings.yaml +24 -0
- package/src/cli.ts +97 -0
- package/src/commands/about.ts +98 -0
- package/src/commands/add-comment.ts +94 -0
- package/src/commands/me.ts +18 -0
- package/src/commands/project-statuses.ts +18 -0
- package/src/commands/projects.ts +32 -0
- package/src/commands/run-jql.ts +40 -0
- package/src/commands/task-with-details.ts +18 -0
- package/src/commands/update-description.ts +94 -0
- package/src/lib/formatters.ts +224 -0
- package/src/lib/jira-client.ts +319 -0
- package/src/lib/settings.ts +77 -0
- package/src/lib/utils.ts +76 -0
- package/src/types/md-to-adf.d.ts +14 -0
- package/tests/README.md +97 -0
- package/tests/__mocks__/jira.js.ts +4 -0
- package/tests/__mocks__/md-to-adf.ts +7 -0
- package/tests/__mocks__/mdast-util-from-adf.ts +4 -0
- package/tests/__mocks__/mdast-util-to-markdown.ts +1 -0
- package/tests/add-comment.test.ts +226 -0
- package/tests/cli-permissions.test.ts +156 -0
- package/tests/jira-client.test.ts +123 -0
- package/tests/projects.test.ts +205 -0
- package/tests/settings.test.ts +288 -0
- package/tests/task-with-details.test.ts +83 -0
- package/tests/update-description.test.ts +262 -0
- package/to-do.md +9 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.projectStatusesCommand = projectStatusesCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const jira_client_1 = require("../lib/jira-client");
|
|
10
|
+
const formatters_1 = require("../lib/formatters");
|
|
11
|
+
async function projectStatusesCommand(projectId) {
|
|
12
|
+
const spinner = (0, ora_1.default)(`Fetching statuses for project ${projectId}...`).start();
|
|
13
|
+
try {
|
|
14
|
+
const statuses = await (0, jira_client_1.getProjectStatuses)(projectId);
|
|
15
|
+
spinner.succeed(chalk_1.default.green('Project statuses retrieved'));
|
|
16
|
+
console.log((0, formatters_1.formatProjectStatuses)(projectId, statuses));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
spinner.fail(chalk_1.default.red('Failed to fetch project statuses'));
|
|
20
|
+
console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.projectsCommand = projectsCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const jira_client_1 = require("../lib/jira-client");
|
|
10
|
+
const formatters_1 = require("../lib/formatters");
|
|
11
|
+
const settings_1 = require("../lib/settings");
|
|
12
|
+
async function projectsCommand() {
|
|
13
|
+
const spinner = (0, ora_1.default)('Fetching projects...').start();
|
|
14
|
+
try {
|
|
15
|
+
const allProjects = await (0, jira_client_1.getProjects)();
|
|
16
|
+
const allowedProjectKeys = (0, settings_1.getAllowedProjects)();
|
|
17
|
+
// Filter projects based on settings
|
|
18
|
+
const filteredProjects = allowedProjectKeys.includes('all')
|
|
19
|
+
? allProjects
|
|
20
|
+
: allProjects.filter(project => (0, settings_1.isProjectAllowed)(project.key));
|
|
21
|
+
spinner.succeed(chalk_1.default.green('Projects retrieved'));
|
|
22
|
+
if (filteredProjects.length === 0) {
|
|
23
|
+
console.log(chalk_1.default.yellow('\nNo projects match your settings configuration.'));
|
|
24
|
+
console.log(chalk_1.default.gray('Allowed projects: ' + allowedProjectKeys.join(', ')));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log((0, formatters_1.formatProjects)(filteredProjects));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
spinner.fail(chalk_1.default.red('Failed to fetch projects'));
|
|
32
|
+
console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runJqlCommand = runJqlCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const jira_client_1 = require("../lib/jira-client");
|
|
10
|
+
const formatters_1 = require("../lib/formatters");
|
|
11
|
+
async function runJqlCommand(jqlQuery, options) {
|
|
12
|
+
// Validate JQL query is not empty
|
|
13
|
+
if (!jqlQuery || jqlQuery.trim() === '') {
|
|
14
|
+
console.error(chalk_1.default.red('\nError: JQL query cannot be empty'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
// Parse and validate limit parameter
|
|
18
|
+
let maxResults = 50; // default
|
|
19
|
+
if (options.limit) {
|
|
20
|
+
const parsedLimit = parseInt(options.limit, 10);
|
|
21
|
+
if (isNaN(parsedLimit) || parsedLimit < 1) {
|
|
22
|
+
console.error(chalk_1.default.red('\nError: Limit must be a positive number'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
if (parsedLimit > 1000) {
|
|
26
|
+
console.warn(chalk_1.default.yellow('\nWarning: Limit is very high. Using 1000 as maximum.'));
|
|
27
|
+
maxResults = 1000;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
maxResults = parsedLimit;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const spinner = (0, ora_1.default)('Executing JQL query...').start();
|
|
34
|
+
try {
|
|
35
|
+
const issues = await (0, jira_client_1.searchIssuesByJql)(jqlQuery, maxResults);
|
|
36
|
+
spinner.succeed(chalk_1.default.green('Query executed successfully'));
|
|
37
|
+
console.log((0, formatters_1.formatJqlResults)(issues));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
spinner.fail(chalk_1.default.red('Failed to execute JQL query'));
|
|
41
|
+
console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.taskWithDetailsCommand = taskWithDetailsCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const jira_client_1 = require("../lib/jira-client");
|
|
10
|
+
const formatters_1 = require("../lib/formatters");
|
|
11
|
+
async function taskWithDetailsCommand(taskId) {
|
|
12
|
+
const spinner = (0, ora_1.default)(`Fetching details for ${taskId}...`).start();
|
|
13
|
+
try {
|
|
14
|
+
const task = await (0, jira_client_1.getTaskWithDetails)(taskId);
|
|
15
|
+
spinner.succeed(chalk_1.default.green('Task details retrieved'));
|
|
16
|
+
console.log((0, formatters_1.formatTaskDetails)(task));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
spinner.fail(chalk_1.default.red('Failed to fetch task details'));
|
|
20
|
+
console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.updateDescriptionCommand = updateDescriptionCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const md_to_adf_1 = __importDefault(require("md-to-adf"));
|
|
45
|
+
const jira_client_1 = require("../lib/jira-client");
|
|
46
|
+
async function updateDescriptionCommand(taskId, options) {
|
|
47
|
+
// Validate taskId
|
|
48
|
+
if (!taskId || taskId.trim() === '') {
|
|
49
|
+
console.error(chalk_1.default.red('\nError: Task ID cannot be empty'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
// Validate file path
|
|
53
|
+
const filePath = options.fromFile;
|
|
54
|
+
if (!filePath || filePath.trim() === '') {
|
|
55
|
+
console.error(chalk_1.default.red('\nError: File path is required (use --from-file)'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
// Resolve file path to absolute
|
|
59
|
+
const absolutePath = path.resolve(filePath);
|
|
60
|
+
// Check file exists
|
|
61
|
+
if (!fs.existsSync(absolutePath)) {
|
|
62
|
+
console.error(chalk_1.default.red(`\nError: File not found: ${absolutePath}`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
// Read file
|
|
66
|
+
let markdownContent;
|
|
67
|
+
try {
|
|
68
|
+
markdownContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(chalk_1.default.red('\nError reading file: ' +
|
|
72
|
+
(error instanceof Error ? error.message : 'Unknown error')));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Validate file is not empty
|
|
76
|
+
if (markdownContent.trim() === '') {
|
|
77
|
+
console.error(chalk_1.default.red('\nError: File is empty'));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
// Convert Markdown to ADF
|
|
81
|
+
let adfContent;
|
|
82
|
+
try {
|
|
83
|
+
adfContent = (0, md_to_adf_1.default)(markdownContent);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error(chalk_1.default.red('\nError converting Markdown to ADF: ' +
|
|
87
|
+
(error instanceof Error ? error.message : 'Unknown error')));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
// Update issue description with spinner
|
|
91
|
+
const spinner = (0, ora_1.default)(`Updating description for ${taskId}...`).start();
|
|
92
|
+
try {
|
|
93
|
+
await (0, jira_client_1.updateIssueDescription)(taskId, adfContent);
|
|
94
|
+
spinner.succeed(chalk_1.default.green(`Description updated successfully for ${taskId}`));
|
|
95
|
+
console.log(chalk_1.default.gray(`\nFile: ${absolutePath}`));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
spinner.fail(chalk_1.default.red('Failed to update description'));
|
|
99
|
+
console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
100
|
+
// Provide helpful hints based on error
|
|
101
|
+
if (error instanceof Error && error.message.includes('404')) {
|
|
102
|
+
console.log(chalk_1.default.yellow('\nHint: Check that the task ID is correct'));
|
|
103
|
+
}
|
|
104
|
+
else if (error instanceof Error && error.message.includes('403')) {
|
|
105
|
+
console.log(chalk_1.default.yellow('\nHint: You may not have permission to edit this issue'));
|
|
106
|
+
}
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatUserInfo = formatUserInfo;
|
|
7
|
+
exports.formatProjects = formatProjects;
|
|
8
|
+
exports.formatTaskDetails = formatTaskDetails;
|
|
9
|
+
exports.formatProjectStatuses = formatProjectStatuses;
|
|
10
|
+
exports.formatJqlResults = formatJqlResults;
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
13
|
+
const utils_1 = require("./utils");
|
|
14
|
+
/**
|
|
15
|
+
* Create a styled table
|
|
16
|
+
*/
|
|
17
|
+
function createTable(headers, colWidths) {
|
|
18
|
+
return new cli_table3_1.default({
|
|
19
|
+
head: headers.map((h) => chalk_1.default.cyan.bold(h)),
|
|
20
|
+
style: {
|
|
21
|
+
head: [],
|
|
22
|
+
border: ['gray'],
|
|
23
|
+
},
|
|
24
|
+
colWidths,
|
|
25
|
+
wordWrap: true,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Format user info
|
|
30
|
+
*/
|
|
31
|
+
function formatUserInfo(user) {
|
|
32
|
+
const table = createTable(['Property', 'Value'], [20, 50]);
|
|
33
|
+
table.push(['Host', process.env.JIRA_HOST || 'N/A'], ['Display Name', user.displayName], ['Email', user.emailAddress], ['Account ID', user.accountId], ['Status', user.active ? chalk_1.default.green('Active') : chalk_1.default.red('Inactive')], ['Time Zone', user.timeZone]);
|
|
34
|
+
return '\n' + chalk_1.default.bold('User Information:') + '\n' + table.toString() + '\n';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Format projects list
|
|
38
|
+
*/
|
|
39
|
+
function formatProjects(projects) {
|
|
40
|
+
if (projects.length === 0) {
|
|
41
|
+
return chalk_1.default.yellow('No projects found.');
|
|
42
|
+
}
|
|
43
|
+
const table = createTable(['Key', 'Name', 'Type', 'Lead'], [12, 35, 15, 25]);
|
|
44
|
+
projects.forEach((project) => {
|
|
45
|
+
table.push([
|
|
46
|
+
chalk_1.default.cyan(project.key),
|
|
47
|
+
(0, utils_1.truncate)(project.name, 35),
|
|
48
|
+
project.projectTypeKey,
|
|
49
|
+
project.lead?.displayName || chalk_1.default.gray('N/A'),
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
let output = '\n' + chalk_1.default.bold(`Projects (${projects.length} total)`) + '\n\n';
|
|
53
|
+
output += table.toString() + '\n';
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Format task details with comments
|
|
58
|
+
*/
|
|
59
|
+
function formatTaskDetails(task) {
|
|
60
|
+
let output = '\n' + chalk_1.default.bold.cyan(`${task.key}: ${task.summary}`) + '\n\n';
|
|
61
|
+
// Basic info table
|
|
62
|
+
const infoTable = createTable(['Property', 'Value'], [15, 65]);
|
|
63
|
+
infoTable.push(['Status', chalk_1.default.green(task.status.name)], ['Assignee', task.assignee?.displayName || chalk_1.default.gray('Unassigned')], ['Reporter', task.reporter?.displayName || chalk_1.default.gray('N/A')], ['Created', (0, utils_1.formatTimestamp)(task.created)], ['Updated', (0, utils_1.formatTimestamp)(task.updated)]);
|
|
64
|
+
output += infoTable.toString() + '\n\n';
|
|
65
|
+
// Parent Task
|
|
66
|
+
if (task.parent) {
|
|
67
|
+
output += chalk_1.default.bold('Parent Task:') + '\n';
|
|
68
|
+
const parentTable = createTable(['Key', 'Summary', 'Status'], [12, 50, 18]);
|
|
69
|
+
parentTable.push([
|
|
70
|
+
chalk_1.default.cyan(task.parent.key),
|
|
71
|
+
(0, utils_1.truncate)(task.parent.summary, 50),
|
|
72
|
+
task.parent.status.name,
|
|
73
|
+
]);
|
|
74
|
+
output += parentTable.toString() + '\n\n';
|
|
75
|
+
}
|
|
76
|
+
// Subtasks
|
|
77
|
+
if (task.subtasks && task.subtasks.length > 0) {
|
|
78
|
+
output += chalk_1.default.bold(`Subtasks (${task.subtasks.length}):`) + '\n';
|
|
79
|
+
const subtasksTable = createTable(['Key', 'Summary', 'Status'], [12, 50, 18]);
|
|
80
|
+
task.subtasks.forEach((subtask) => {
|
|
81
|
+
subtasksTable.push([
|
|
82
|
+
chalk_1.default.cyan(subtask.key),
|
|
83
|
+
(0, utils_1.truncate)(subtask.summary, 50),
|
|
84
|
+
subtask.status.name,
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
output += subtasksTable.toString() + '\n\n';
|
|
88
|
+
}
|
|
89
|
+
// Description
|
|
90
|
+
if (task.description) {
|
|
91
|
+
output += chalk_1.default.bold('Description:') + '\n';
|
|
92
|
+
output += chalk_1.default.dim('─'.repeat(80)) + '\n';
|
|
93
|
+
output += task.description + '\n';
|
|
94
|
+
output += chalk_1.default.dim('─'.repeat(80)) + '\n\n';
|
|
95
|
+
}
|
|
96
|
+
// Comments
|
|
97
|
+
if (task.comments.length > 0) {
|
|
98
|
+
output += chalk_1.default.bold(`Comments (${task.comments.length}):`) + '\n\n';
|
|
99
|
+
task.comments.forEach((comment, index) => {
|
|
100
|
+
output += chalk_1.default.cyan(`${index + 1}. ${comment.author.displayName}`) +
|
|
101
|
+
chalk_1.default.gray(` - ${(0, utils_1.formatTimestamp)(comment.created)}`) + '\n';
|
|
102
|
+
output += chalk_1.default.dim('─'.repeat(80)) + '\n';
|
|
103
|
+
output += comment.body + '\n';
|
|
104
|
+
output += chalk_1.default.dim('─'.repeat(80)) + '\n\n';
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
output += chalk_1.default.gray('No comments yet.\n\n');
|
|
109
|
+
}
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Format project statuses list
|
|
114
|
+
*/
|
|
115
|
+
function formatProjectStatuses(projectKey, statuses) {
|
|
116
|
+
if (statuses.length === 0) {
|
|
117
|
+
return chalk_1.default.yellow('No statuses found for this project.');
|
|
118
|
+
}
|
|
119
|
+
const table = createTable(['Status Name', 'Category', 'Description'], [25, 20, 45]);
|
|
120
|
+
// Sort statuses by category for better readability
|
|
121
|
+
const sortedStatuses = [...statuses].sort((a, b) => {
|
|
122
|
+
const categoryOrder = ['TODO', 'IN_PROGRESS', 'DONE'];
|
|
123
|
+
const aIndex = categoryOrder.indexOf(a.statusCategory.key.toUpperCase());
|
|
124
|
+
const bIndex = categoryOrder.indexOf(b.statusCategory.key.toUpperCase());
|
|
125
|
+
return aIndex - bIndex;
|
|
126
|
+
});
|
|
127
|
+
sortedStatuses.forEach((status) => {
|
|
128
|
+
// Color code based on status category
|
|
129
|
+
let categoryColor = chalk_1.default.white;
|
|
130
|
+
if (status.statusCategory.key.toLowerCase() === 'done') {
|
|
131
|
+
categoryColor = chalk_1.default.green;
|
|
132
|
+
}
|
|
133
|
+
else if (status.statusCategory.key.toLowerCase() === 'indeterminate') {
|
|
134
|
+
categoryColor = chalk_1.default.yellow;
|
|
135
|
+
}
|
|
136
|
+
else if (status.statusCategory.key.toLowerCase() === 'new') {
|
|
137
|
+
categoryColor = chalk_1.default.blue;
|
|
138
|
+
}
|
|
139
|
+
table.push([
|
|
140
|
+
chalk_1.default.cyan(status.name),
|
|
141
|
+
categoryColor(status.statusCategory.name),
|
|
142
|
+
(0, utils_1.truncate)(status.description || chalk_1.default.gray('No description'), 45),
|
|
143
|
+
]);
|
|
144
|
+
});
|
|
145
|
+
let output = '\n' + chalk_1.default.bold(`Project ${projectKey} - Available Statuses (${statuses.length} total)`) + '\n\n';
|
|
146
|
+
output += table.toString() + '\n';
|
|
147
|
+
return output;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Format JQL query results
|
|
151
|
+
*/
|
|
152
|
+
function formatJqlResults(issues) {
|
|
153
|
+
if (issues.length === 0) {
|
|
154
|
+
return chalk_1.default.yellow('\nNo issues found matching your JQL query.\n');
|
|
155
|
+
}
|
|
156
|
+
const table = createTable(['Key', 'Summary', 'Status', 'Assignee', 'Priority'], [12, 40, 18, 20, 12]);
|
|
157
|
+
issues.forEach((issue) => {
|
|
158
|
+
// Color-code status
|
|
159
|
+
let statusColor = chalk_1.default.white;
|
|
160
|
+
const statusLower = issue.status.name.toLowerCase();
|
|
161
|
+
if (statusLower.includes('done') || statusLower.includes('closed') || statusLower.includes('resolved')) {
|
|
162
|
+
statusColor = chalk_1.default.green;
|
|
163
|
+
}
|
|
164
|
+
else if (statusLower.includes('progress') || statusLower.includes('review')) {
|
|
165
|
+
statusColor = chalk_1.default.yellow;
|
|
166
|
+
}
|
|
167
|
+
// Color-code priority
|
|
168
|
+
let priorityColor = chalk_1.default.white;
|
|
169
|
+
const priorityName = issue.priority?.name || chalk_1.default.gray('None');
|
|
170
|
+
if (issue.priority) {
|
|
171
|
+
const priorityLower = issue.priority.name.toLowerCase();
|
|
172
|
+
if (priorityLower.includes('highest') || priorityLower.includes('high')) {
|
|
173
|
+
priorityColor = chalk_1.default.red;
|
|
174
|
+
}
|
|
175
|
+
else if (priorityLower.includes('medium')) {
|
|
176
|
+
priorityColor = chalk_1.default.yellow;
|
|
177
|
+
}
|
|
178
|
+
else if (priorityLower.includes('low')) {
|
|
179
|
+
priorityColor = chalk_1.default.blue;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
table.push([
|
|
183
|
+
chalk_1.default.cyan(issue.key),
|
|
184
|
+
(0, utils_1.truncate)(issue.summary, 40),
|
|
185
|
+
statusColor(issue.status.name),
|
|
186
|
+
issue.assignee ? (0, utils_1.truncate)(issue.assignee.displayName, 20) : chalk_1.default.gray('Unassigned'),
|
|
187
|
+
typeof priorityName === 'string' ? priorityColor(priorityName) : priorityName,
|
|
188
|
+
]);
|
|
189
|
+
});
|
|
190
|
+
let output = '\n' + chalk_1.default.bold(`Results (${issues.length} total)`) + '\n\n';
|
|
191
|
+
output += table.toString() + '\n';
|
|
192
|
+
return output;
|
|
193
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getJiraClient = getJiraClient;
|
|
4
|
+
exports.getCurrentUser = getCurrentUser;
|
|
5
|
+
exports.getProjects = getProjects;
|
|
6
|
+
exports.getTaskWithDetails = getTaskWithDetails;
|
|
7
|
+
exports.getProjectStatuses = getProjectStatuses;
|
|
8
|
+
exports.searchIssuesByJql = searchIssuesByJql;
|
|
9
|
+
exports.updateIssueDescription = updateIssueDescription;
|
|
10
|
+
exports.addIssueComment = addIssueComment;
|
|
11
|
+
const jira_js_1 = require("jira.js");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
let jiraClient = null;
|
|
14
|
+
/**
|
|
15
|
+
* Get or create Jira client instance
|
|
16
|
+
*/
|
|
17
|
+
function getJiraClient() {
|
|
18
|
+
if (!jiraClient) {
|
|
19
|
+
jiraClient = new jira_js_1.Version3Client({
|
|
20
|
+
host: process.env.JIRA_HOST,
|
|
21
|
+
authentication: {
|
|
22
|
+
basic: {
|
|
23
|
+
email: process.env.JIRA_USER_EMAIL,
|
|
24
|
+
apiToken: process.env.JIRA_API_TOKEN,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return jiraClient;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get current user information
|
|
33
|
+
*/
|
|
34
|
+
async function getCurrentUser() {
|
|
35
|
+
const client = getJiraClient();
|
|
36
|
+
const user = await client.myself.getCurrentUser();
|
|
37
|
+
return {
|
|
38
|
+
accountId: user.accountId || '',
|
|
39
|
+
displayName: user.displayName || '',
|
|
40
|
+
emailAddress: user.emailAddress || '',
|
|
41
|
+
active: user.active || false,
|
|
42
|
+
timeZone: user.timeZone || '',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get all projects
|
|
47
|
+
*/
|
|
48
|
+
async function getProjects() {
|
|
49
|
+
const client = getJiraClient();
|
|
50
|
+
const response = await client.projects.searchProjects({
|
|
51
|
+
expand: 'lead',
|
|
52
|
+
});
|
|
53
|
+
return response.values.map((project) => ({
|
|
54
|
+
id: project.id,
|
|
55
|
+
key: project.key,
|
|
56
|
+
name: project.name,
|
|
57
|
+
projectTypeKey: project.projectTypeKey,
|
|
58
|
+
lead: project.lead ? {
|
|
59
|
+
displayName: project.lead.displayName,
|
|
60
|
+
} : undefined,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get task details with comments
|
|
65
|
+
*/
|
|
66
|
+
async function getTaskWithDetails(taskId) {
|
|
67
|
+
const client = getJiraClient();
|
|
68
|
+
// Get issue details
|
|
69
|
+
const issue = await client.issues.getIssue({
|
|
70
|
+
issueIdOrKey: taskId,
|
|
71
|
+
fields: [
|
|
72
|
+
'summary',
|
|
73
|
+
'description',
|
|
74
|
+
'status',
|
|
75
|
+
'assignee',
|
|
76
|
+
'reporter',
|
|
77
|
+
'created',
|
|
78
|
+
'updated',
|
|
79
|
+
'comment',
|
|
80
|
+
'parent',
|
|
81
|
+
'subtasks',
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
// Extract comments
|
|
85
|
+
const comments = issue.fields.comment?.comments?.map((comment) => ({
|
|
86
|
+
id: comment.id,
|
|
87
|
+
author: {
|
|
88
|
+
displayName: comment.author?.displayName || 'Unknown',
|
|
89
|
+
emailAddress: comment.author?.emailAddress,
|
|
90
|
+
},
|
|
91
|
+
body: (0, utils_1.convertADFToMarkdown)(comment.body),
|
|
92
|
+
created: comment.created || '',
|
|
93
|
+
updated: comment.updated || '',
|
|
94
|
+
})) || [];
|
|
95
|
+
// Convert description from ADF to Markdown
|
|
96
|
+
const descriptionMarkdown = (0, utils_1.convertADFToMarkdown)(issue.fields.description);
|
|
97
|
+
const description = descriptionMarkdown || undefined;
|
|
98
|
+
// Extract parent if exists
|
|
99
|
+
const parent = issue.fields.parent ? {
|
|
100
|
+
id: issue.fields.parent.id,
|
|
101
|
+
key: issue.fields.parent.key,
|
|
102
|
+
summary: issue.fields.parent.fields?.summary || '',
|
|
103
|
+
status: {
|
|
104
|
+
name: issue.fields.parent.fields?.status?.name || 'Unknown',
|
|
105
|
+
},
|
|
106
|
+
} : undefined;
|
|
107
|
+
// Extract subtasks
|
|
108
|
+
const subtasks = issue.fields.subtasks?.map((subtask) => ({
|
|
109
|
+
id: subtask.id,
|
|
110
|
+
key: subtask.key,
|
|
111
|
+
summary: subtask.fields?.summary || '',
|
|
112
|
+
status: {
|
|
113
|
+
name: subtask.fields?.status?.name || 'Unknown',
|
|
114
|
+
},
|
|
115
|
+
})) || [];
|
|
116
|
+
return {
|
|
117
|
+
id: issue.id || '',
|
|
118
|
+
key: issue.key || '',
|
|
119
|
+
summary: issue.fields.summary || '',
|
|
120
|
+
description,
|
|
121
|
+
status: {
|
|
122
|
+
name: issue.fields.status?.name || 'Unknown',
|
|
123
|
+
},
|
|
124
|
+
assignee: issue.fields.assignee ? {
|
|
125
|
+
displayName: issue.fields.assignee.displayName || 'Unknown',
|
|
126
|
+
} : undefined,
|
|
127
|
+
reporter: issue.fields.reporter ? {
|
|
128
|
+
displayName: issue.fields.reporter.displayName || 'Unknown',
|
|
129
|
+
} : undefined,
|
|
130
|
+
created: issue.fields.created || '',
|
|
131
|
+
updated: issue.fields.updated || '',
|
|
132
|
+
comments,
|
|
133
|
+
parent,
|
|
134
|
+
subtasks,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get all possible statuses for a project
|
|
139
|
+
*/
|
|
140
|
+
async function getProjectStatuses(projectIdOrKey) {
|
|
141
|
+
const client = getJiraClient();
|
|
142
|
+
// Get all statuses for the project
|
|
143
|
+
const statuses = await client.projects.getAllStatuses({
|
|
144
|
+
projectIdOrKey,
|
|
145
|
+
});
|
|
146
|
+
// Flatten and deduplicate statuses from all issue types
|
|
147
|
+
const statusMap = new Map();
|
|
148
|
+
statuses.forEach((issueTypeStatuses) => {
|
|
149
|
+
issueTypeStatuses.statuses?.forEach((status) => {
|
|
150
|
+
if (!statusMap.has(status.id)) {
|
|
151
|
+
statusMap.set(status.id, {
|
|
152
|
+
id: status.id || '',
|
|
153
|
+
name: status.name || '',
|
|
154
|
+
description: status.description,
|
|
155
|
+
statusCategory: {
|
|
156
|
+
id: status.statusCategory?.id || '',
|
|
157
|
+
key: status.statusCategory?.key || '',
|
|
158
|
+
name: status.statusCategory?.name || '',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
return Array.from(statusMap.values());
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Search for issues using JQL query
|
|
168
|
+
*/
|
|
169
|
+
async function searchIssuesByJql(jqlQuery, maxResults) {
|
|
170
|
+
const client = getJiraClient();
|
|
171
|
+
const response = await client.issueSearch.searchForIssuesUsingJqlEnhancedSearch({
|
|
172
|
+
jql: jqlQuery,
|
|
173
|
+
maxResults,
|
|
174
|
+
fields: ['summary', 'status', 'assignee', 'priority'],
|
|
175
|
+
});
|
|
176
|
+
return response.issues?.map((issue) => ({
|
|
177
|
+
key: issue.key || '',
|
|
178
|
+
summary: issue.fields?.summary || '',
|
|
179
|
+
status: {
|
|
180
|
+
name: issue.fields?.status?.name || 'Unknown',
|
|
181
|
+
},
|
|
182
|
+
assignee: issue.fields?.assignee ? {
|
|
183
|
+
displayName: issue.fields.assignee.displayName || 'Unknown',
|
|
184
|
+
} : null,
|
|
185
|
+
priority: issue.fields?.priority ? {
|
|
186
|
+
name: issue.fields.priority.name || 'Unknown',
|
|
187
|
+
} : null,
|
|
188
|
+
})) || [];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Update the description of a Jira issue
|
|
192
|
+
* @param taskId - The issue key (e.g., "PROJ-123")
|
|
193
|
+
* @param adfContent - The description content in ADF format
|
|
194
|
+
*/
|
|
195
|
+
async function updateIssueDescription(taskId, adfContent) {
|
|
196
|
+
const client = getJiraClient();
|
|
197
|
+
await client.issues.editIssue({
|
|
198
|
+
issueIdOrKey: taskId,
|
|
199
|
+
fields: {
|
|
200
|
+
description: adfContent,
|
|
201
|
+
},
|
|
202
|
+
notifyUsers: false,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Add a comment to a Jira issue
|
|
207
|
+
* @param taskId - The issue key (e.g., "PROJ-123")
|
|
208
|
+
* @param adfContent - The comment content in ADF format
|
|
209
|
+
*/
|
|
210
|
+
async function addIssueComment(taskId, adfContent) {
|
|
211
|
+
const client = getJiraClient();
|
|
212
|
+
await client.issueComments.addComment({
|
|
213
|
+
issueIdOrKey: taskId,
|
|
214
|
+
comment: adfContent,
|
|
215
|
+
});
|
|
216
|
+
}
|