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.
Files changed (45) hide show
  1. package/README.md +364 -0
  2. package/dist/cli.js +87 -0
  3. package/dist/commands/about.js +83 -0
  4. package/dist/commands/add-comment.js +109 -0
  5. package/dist/commands/me.js +23 -0
  6. package/dist/commands/project-statuses.js +23 -0
  7. package/dist/commands/projects.js +35 -0
  8. package/dist/commands/run-jql.js +44 -0
  9. package/dist/commands/task-with-details.js +23 -0
  10. package/dist/commands/update-description.js +109 -0
  11. package/dist/lib/formatters.js +193 -0
  12. package/dist/lib/jira-client.js +216 -0
  13. package/dist/lib/settings.js +68 -0
  14. package/dist/lib/utils.js +79 -0
  15. package/jest.config.js +21 -0
  16. package/package.json +47 -0
  17. package/settings.yaml +24 -0
  18. package/src/cli.ts +97 -0
  19. package/src/commands/about.ts +98 -0
  20. package/src/commands/add-comment.ts +94 -0
  21. package/src/commands/me.ts +18 -0
  22. package/src/commands/project-statuses.ts +18 -0
  23. package/src/commands/projects.ts +32 -0
  24. package/src/commands/run-jql.ts +40 -0
  25. package/src/commands/task-with-details.ts +18 -0
  26. package/src/commands/update-description.ts +94 -0
  27. package/src/lib/formatters.ts +224 -0
  28. package/src/lib/jira-client.ts +319 -0
  29. package/src/lib/settings.ts +77 -0
  30. package/src/lib/utils.ts +76 -0
  31. package/src/types/md-to-adf.d.ts +14 -0
  32. package/tests/README.md +97 -0
  33. package/tests/__mocks__/jira.js.ts +4 -0
  34. package/tests/__mocks__/md-to-adf.ts +7 -0
  35. package/tests/__mocks__/mdast-util-from-adf.ts +4 -0
  36. package/tests/__mocks__/mdast-util-to-markdown.ts +1 -0
  37. package/tests/add-comment.test.ts +226 -0
  38. package/tests/cli-permissions.test.ts +156 -0
  39. package/tests/jira-client.test.ts +123 -0
  40. package/tests/projects.test.ts +205 -0
  41. package/tests/settings.test.ts +288 -0
  42. package/tests/task-with-details.test.ts +83 -0
  43. package/tests/update-description.test.ts +262 -0
  44. package/to-do.md +9 -0
  45. 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
+ }