jira-ai 0.9.96 → 0.9.97
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 +29 -0
- package/dist/cli.js +8 -18
- package/dist/commands/about.js +13 -8
- package/dist/commands/add-comment.js +2 -1
- package/dist/commands/add-label.js +2 -2
- package/dist/commands/auth.js +14 -8
- package/dist/commands/confluence.js +23 -20
- package/dist/commands/create-issue-link.js +2 -2
- package/dist/commands/create-task.js +11 -7
- package/dist/commands/delete-issue-link.js +2 -1
- package/dist/commands/delete-label.js +2 -2
- package/dist/commands/epic.js +23 -15
- package/dist/commands/get-issue-statistics.js +15 -9
- package/dist/commands/get-person-worklog.js +14 -7
- package/dist/commands/issue.js +2 -0
- package/dist/commands/list-colleagues.js +8 -2
- package/dist/commands/list-issue-links.js +2 -1
- package/dist/commands/list-issue-types.js +2 -1
- package/dist/commands/list-link-types.js +2 -1
- package/dist/commands/me.js +2 -1
- package/dist/commands/project-fields.js +21 -12
- package/dist/commands/project-statuses.js +2 -1
- package/dist/commands/projects.js +3 -2
- package/dist/commands/run-jql.js +5 -2
- package/dist/commands/settings.js +2 -1
- package/dist/commands/task-with-details.js +2 -1
- package/dist/commands/transition.js +2 -0
- package/dist/commands/update-description.js +2 -2
- package/dist/commands/update-issue.js +2 -0
- package/dist/lib/jira-client.js +1 -0
- package/dist/lib/json-mode.js +45 -0
- package/dist/lib/ui.js +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,35 @@ See all available commands:
|
|
|
63
63
|
jira-ai --help
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
## JSON Output Mode
|
|
67
|
+
|
|
68
|
+
All commands support structured JSON output via global flags. This is designed for AI agents and scripting integrations where parsed data is more useful than formatted tables.
|
|
69
|
+
|
|
70
|
+
### `--json`
|
|
71
|
+
|
|
72
|
+
Outputs pretty-printed JSON (2-space indentation):
|
|
73
|
+
```bash
|
|
74
|
+
jira-ai --json issue get PROJ-123
|
|
75
|
+
jira-ai --json project list
|
|
76
|
+
jira-ai --json issue search "project = PROJ AND status = Open"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `--json-compact`
|
|
80
|
+
|
|
81
|
+
Outputs single-line JSON for maximum token efficiency — ideal for AI agent workflows:
|
|
82
|
+
```bash
|
|
83
|
+
jira-ai --json-compact issue get PROJ-123
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Error handling in JSON mode
|
|
87
|
+
|
|
88
|
+
Errors are returned as structured JSON to stdout:
|
|
89
|
+
```json
|
|
90
|
+
{ "error": true, "message": "Issue not found", "hints": ["Check the issue key"], "exitCode": 1 }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Default table output is unchanged when neither flag is used.
|
|
94
|
+
|
|
66
95
|
### Rich Issue Management
|
|
67
96
|
|
|
68
97
|
Create issues with detailed field support:
|
package/dist/cli.js
CHANGED
|
@@ -34,6 +34,7 @@ import { checkForUpdate, formatUpdateMessage, checkForUpdateSync } from './lib/u
|
|
|
34
34
|
import { CliError } from './types/errors.js';
|
|
35
35
|
import { CommandError } from './lib/errors.js';
|
|
36
36
|
import { ui } from './lib/ui.js';
|
|
37
|
+
import { initJsonMode, outputError } from './lib/json-mode.js';
|
|
37
38
|
import { CreateTaskSchema, UpdateDescriptionSchema, ProjectFieldsSchema, ConfluenceAddCommentSchema, RunJqlSchema, GetPersonWorklogSchema, GetIssueStatisticsSchema, EpicListSchema, EpicCreateSchema, EpicUpdateSchema, EpicLinkSchema, EpicMaxSchema, validateOptions, IssueKeySchema, ProjectKeySchema, TimeframeSchema } from './lib/validation.js';
|
|
38
39
|
import { realpathSync } from 'fs';
|
|
39
40
|
// Create CLI program
|
|
@@ -42,6 +43,8 @@ program
|
|
|
42
43
|
.name('jira-ai')
|
|
43
44
|
.description('CLI tool for interacting with Atlassian Jira')
|
|
44
45
|
.version(getVersion())
|
|
46
|
+
.option('--json', 'Output as JSON')
|
|
47
|
+
.option('--json-compact', 'Output as compact JSON')
|
|
45
48
|
.addHelpText('after', () => {
|
|
46
49
|
const latestVersion = checkForUpdateSync();
|
|
47
50
|
if (latestVersion) {
|
|
@@ -506,6 +509,7 @@ export function configureCommandVisibility(program) {
|
|
|
506
509
|
// Parse command line arguments
|
|
507
510
|
export async function main() {
|
|
508
511
|
try {
|
|
512
|
+
initJsonMode();
|
|
509
513
|
// Background update check (non-blocking for the user)
|
|
510
514
|
checkForUpdate().catch(() => { });
|
|
511
515
|
configureCommandVisibility(program);
|
|
@@ -514,30 +518,16 @@ export async function main() {
|
|
|
514
518
|
catch (error) {
|
|
515
519
|
ui.failSpinner();
|
|
516
520
|
if (error instanceof CommandError) {
|
|
517
|
-
|
|
518
|
-
if (error.hints.length > 0) {
|
|
519
|
-
error.hints.forEach(hint => {
|
|
520
|
-
console.error(chalk.yellow(` Hint: ${hint}`));
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
console.log(); // Add a newline
|
|
524
|
-
process.exit(error.exitCode);
|
|
521
|
+
outputError(error.message, error.hints, error.exitCode);
|
|
525
522
|
}
|
|
526
523
|
else if (error instanceof CliError) {
|
|
527
|
-
|
|
528
|
-
process.exit(1);
|
|
524
|
+
outputError(error.message, [], 1);
|
|
529
525
|
}
|
|
530
526
|
else if (error instanceof Error) {
|
|
531
|
-
|
|
532
|
-
if (process.env.DEBUG) {
|
|
533
|
-
console.error(chalk.gray(error.stack));
|
|
534
|
-
}
|
|
535
|
-
console.error(chalk.gray('\nPlease report this issue if it persists.\n'));
|
|
536
|
-
process.exit(1);
|
|
527
|
+
outputError(`Unexpected Error: ${error.message}`, ['Please report this issue if it persists.'], 1);
|
|
537
528
|
}
|
|
538
529
|
else {
|
|
539
|
-
|
|
540
|
-
process.exit(1);
|
|
530
|
+
outputError(`An unknown error occurred: ${String(error)}`, [], 1);
|
|
541
531
|
}
|
|
542
532
|
}
|
|
543
533
|
}
|
package/dist/commands/about.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { getVersion } from '../lib/utils.js';
|
|
3
3
|
import { checkForUpdate, formatUpdateMessage } from '../lib/update-check.js';
|
|
4
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
4
5
|
export async function aboutCommand() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
console.log(`${chalk.bold('GitHub:')} https://github.com/festoinc/jira-ai\n`);
|
|
6
|
+
const version = getVersion();
|
|
7
|
+
let latestVersion = null;
|
|
8
8
|
try {
|
|
9
|
-
|
|
10
|
-
if (latestVersion) {
|
|
11
|
-
console.log(formatUpdateMessage(latestVersion));
|
|
12
|
-
console.log();
|
|
13
|
-
}
|
|
9
|
+
latestVersion = await checkForUpdate() ?? null;
|
|
14
10
|
}
|
|
15
11
|
catch (error) {
|
|
16
12
|
// Ignore update check errors in about command
|
|
17
13
|
}
|
|
14
|
+
outputResult({ version, updateAvailable: latestVersion ?? undefined }, (data) => {
|
|
15
|
+
let out = chalk.bold.cyan('\n📋 Jira AI\n');
|
|
16
|
+
out += `${chalk.bold('Version:')} ${data.version}\n`;
|
|
17
|
+
out += `${chalk.bold('GitHub:')} https://github.com/festoinc/jira-ai\n`;
|
|
18
|
+
if (data.updateAvailable) {
|
|
19
|
+
out += `\n${formatUpdateMessage(data.updateAvailable)}\n`;
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
});
|
|
18
23
|
}
|
|
@@ -7,6 +7,7 @@ import { processMentionsInADF } from '../lib/adf-mentions.js';
|
|
|
7
7
|
import { CommandError } from '../lib/errors.js';
|
|
8
8
|
import { ui } from '../lib/ui.js';
|
|
9
9
|
import { validateOptions, AddCommentSchema } from '../lib/validation.js';
|
|
10
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
10
11
|
export async function addCommentCommand(options) {
|
|
11
12
|
// Validate options
|
|
12
13
|
validateOptions(AddCommentSchema, options);
|
|
@@ -47,7 +48,7 @@ export async function addCommentCommand(options) {
|
|
|
47
48
|
try {
|
|
48
49
|
await addIssueComment(issueKey, adfContent);
|
|
49
50
|
ui.succeedSpinner(chalk.green(`Comment added successfully to ${issueKey}`));
|
|
50
|
-
|
|
51
|
+
outputResult({ success: true, issueKey, file: absolutePath }, (data) => chalk.gray(`\nFile: ${data.file}`));
|
|
51
52
|
}
|
|
52
53
|
catch (error) {
|
|
53
54
|
if (error instanceof CommandError)
|
|
@@ -3,6 +3,7 @@ import { addIssueLabels, validateIssuePermissions } from '../lib/jira-client.js'
|
|
|
3
3
|
import { CommandError } from '../lib/errors.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { validateOptions, IssueKeySchema } from '../lib/validation.js';
|
|
6
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
6
7
|
export async function addLabelCommand(taskId, labelsString) {
|
|
7
8
|
// Validate input
|
|
8
9
|
validateOptions(IssueKeySchema, taskId);
|
|
@@ -21,8 +22,7 @@ export async function addLabelCommand(taskId, labelsString) {
|
|
|
21
22
|
try {
|
|
22
23
|
await addIssueLabels(taskId, labels);
|
|
23
24
|
ui.succeedSpinner(chalk.green(`Labels added successfully to ${taskId}`));
|
|
24
|
-
|
|
25
|
-
Labels: ${labels.join(', ')}`));
|
|
25
|
+
outputResult({ success: true, issueKey: taskId, labels }, (data) => chalk.gray(`\nLabels: ${data.labels.join(', ')}`));
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
28
|
if (error instanceof CommandError)
|
package/dist/commands/auth.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createTemporaryClient } from '../lib/jira-client.js';
|
|
|
6
6
|
import { saveCredentials, clearCredentials } from '../lib/auth-storage.js';
|
|
7
7
|
import { CommandError } from '../lib/errors.js';
|
|
8
8
|
import { ui } from '../lib/ui.js';
|
|
9
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
9
10
|
/**
|
|
10
11
|
* Auto-discover the Atlassian Cloud ID for a given site hostname.
|
|
11
12
|
*/
|
|
@@ -28,7 +29,7 @@ async function discoverCloudId(host) {
|
|
|
28
29
|
}
|
|
29
30
|
export async function logoutCommand() {
|
|
30
31
|
clearCredentials();
|
|
31
|
-
|
|
32
|
+
outputResult({ success: true, message: 'Successfully logged out. Authentication credentials cleared.' }, (data) => chalk.green(data.message));
|
|
32
33
|
}
|
|
33
34
|
export async function authCommand(options = {}) {
|
|
34
35
|
if (options.logout) {
|
|
@@ -98,7 +99,9 @@ export async function authCommand(options = {}) {
|
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
if (!host || !email || !apiToken) {
|
|
101
|
-
|
|
102
|
+
if (!isJsonMode()) {
|
|
103
|
+
console.log(chalk.cyan('\n--- Jira Authentication Setup ---\n'));
|
|
104
|
+
}
|
|
102
105
|
try {
|
|
103
106
|
host = await ask('Jira URL (e.g., https://your-domain.atlassian.net): ');
|
|
104
107
|
if (!host) {
|
|
@@ -139,13 +142,16 @@ export async function authCommand(options = {}) {
|
|
|
139
142
|
const tempClient = createTemporaryClient(host, email, apiToken, { authType, cloudId });
|
|
140
143
|
const user = await tempClient.myself.getCurrentUser();
|
|
141
144
|
ui.succeedSpinner(chalk.green('Authentication successful!'));
|
|
142
|
-
console.log(chalk.blue(`\nWelcome, ${user.displayName} (${user.emailAddress})`));
|
|
143
|
-
if (authType === 'service_account') {
|
|
144
|
-
console.log(chalk.gray(`Auth type: service_account (via api.atlassian.com gateway)`));
|
|
145
|
-
}
|
|
146
145
|
saveCredentials({ host, email, apiToken, authType, cloudId });
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
outputResult({ success: true, displayName: user.displayName, email: user.emailAddress, authType }, (data) => {
|
|
147
|
+
let out = chalk.blue(`\nWelcome, ${data.displayName} (${data.email})`);
|
|
148
|
+
if (data.authType === 'service_account') {
|
|
149
|
+
out += `\n${chalk.gray('Auth type: service_account (via api.atlassian.com gateway)')}`;
|
|
150
|
+
}
|
|
151
|
+
out += `\n${chalk.green('\nCredentials saved successfully to ~/.jira-ai/config.json')}`;
|
|
152
|
+
out += `\n${chalk.gray('These credentials will be used for future commands on this machine.')}`;
|
|
153
|
+
return out;
|
|
154
|
+
});
|
|
149
155
|
}
|
|
150
156
|
catch (error) {
|
|
151
157
|
const hints = [];
|
|
@@ -7,6 +7,7 @@ import { formatConfluencePage, formatConfluenceSpaces, formatConfluencePageHiera
|
|
|
7
7
|
import { ui } from '../lib/ui.js';
|
|
8
8
|
import { CommandError } from '../lib/errors.js';
|
|
9
9
|
import { isConfluenceSpaceAllowed } from '../lib/settings.js';
|
|
10
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
10
11
|
export async function confluenceCreatePageCommand(space, title, parentPage, options = {}) {
|
|
11
12
|
// Validate space key
|
|
12
13
|
if (!isConfluenceSpaceAllowed(space)) {
|
|
@@ -16,13 +17,12 @@ export async function confluenceCreatePageCommand(space, title, parentPage, opti
|
|
|
16
17
|
try {
|
|
17
18
|
const result = await createPage(space, title, parentPage, { returnBoth: options.returnBothUrls });
|
|
18
19
|
ui.succeedSpinner(chalk.green('Confluence page created successfully'));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
20
|
+
outputResult(typeof result === 'object' ? result : { url: result }, (data) => {
|
|
21
|
+
if ('shortUrl' in data) {
|
|
22
|
+
return chalk.cyan(`\nFull URL: ${data.url}\nShort URL: ${data.shortUrl}`);
|
|
23
|
+
}
|
|
24
|
+
return chalk.cyan(`\nURL: ${data.url}`);
|
|
25
|
+
});
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
28
|
ui.failSpinner();
|
|
@@ -81,8 +81,7 @@ export async function confluenceAddCommentCommand(url, options) {
|
|
|
81
81
|
try {
|
|
82
82
|
await addPageComment(url, adfContent);
|
|
83
83
|
ui.succeedSpinner(chalk.green('Comment added successfully to Confluence page'));
|
|
84
|
-
|
|
85
|
-
console.log(chalk.gray(`File: ${absolutePath}`));
|
|
84
|
+
outputResult({ success: true, page: url, file: absolutePath }, (data) => chalk.gray(`\nPage: ${data.page}\nFile: ${data.file}`));
|
|
86
85
|
}
|
|
87
86
|
catch (error) {
|
|
88
87
|
ui.failSpinner();
|
|
@@ -123,7 +122,7 @@ export async function confluenceGetPageCommand(url, options = {}) {
|
|
|
123
122
|
throw new CommandError(`Access to Confluence space '${page.space}' is restricted by your settings.`);
|
|
124
123
|
}
|
|
125
124
|
ui.succeedSpinner(chalk.green('Confluence page details retrieved'));
|
|
126
|
-
|
|
125
|
+
outputResult({ page, comments }, (data) => formatConfluencePage(data.page, data.comments));
|
|
127
126
|
}
|
|
128
127
|
catch (error) {
|
|
129
128
|
ui.failSpinner();
|
|
@@ -152,15 +151,20 @@ export async function confluenceListSpacesCommand() {
|
|
|
152
151
|
const allowedSpaces = spaces.filter(space => isConfluenceSpaceAllowed(space.key));
|
|
153
152
|
if (allowedSpaces.length === 0) {
|
|
154
153
|
ui.failSpinner('No allowed Confluence spaces found.');
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
if (!isJsonMode()) {
|
|
155
|
+
console.log(chalk.yellow('\nHint: Add allowed spaces to your settings.yaml under "allowed-confluence-spaces".'));
|
|
156
|
+
console.log(chalk.gray('Example:'));
|
|
157
|
+
console.log(chalk.gray(' allowed-confluence-spaces:'));
|
|
158
|
+
console.log(chalk.gray(' - SPACE1'));
|
|
159
|
+
console.log(chalk.gray(' - SPACE2'));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
outputResult([]);
|
|
163
|
+
}
|
|
160
164
|
return;
|
|
161
165
|
}
|
|
162
166
|
ui.succeedSpinner(chalk.green('Confluence spaces retrieved'));
|
|
163
|
-
|
|
167
|
+
outputResult(allowedSpaces, formatConfluenceSpaces);
|
|
164
168
|
}
|
|
165
169
|
catch (error) {
|
|
166
170
|
ui.failSpinner();
|
|
@@ -176,7 +180,7 @@ export async function confluenceGetSpacePagesHierarchyCommand(spaceKey) {
|
|
|
176
180
|
try {
|
|
177
181
|
const hierarchy = await getSpacePagesHierarchy(spaceKey);
|
|
178
182
|
ui.succeedSpinner(chalk.green('Confluence page hierarchy retrieved'));
|
|
179
|
-
|
|
183
|
+
outputResult(hierarchy, formatConfluencePageHierarchy);
|
|
180
184
|
}
|
|
181
185
|
catch (error) {
|
|
182
186
|
ui.failSpinner();
|
|
@@ -224,8 +228,7 @@ export async function confluenceUpdateDescriptionCommand(url, options) {
|
|
|
224
228
|
try {
|
|
225
229
|
await updatePageContent(url, adfContent);
|
|
226
230
|
ui.succeedSpinner(chalk.green('Confluence page content updated successfully'));
|
|
227
|
-
|
|
228
|
-
console.log(chalk.gray(`File: ${absolutePath}`));
|
|
231
|
+
outputResult({ success: true, page: url, file: absolutePath }, (data) => chalk.gray(`\nPage: ${data.page}\nFile: ${data.file}`));
|
|
229
232
|
}
|
|
230
233
|
catch (error) {
|
|
231
234
|
ui.failSpinner();
|
|
@@ -256,7 +259,7 @@ export async function confluenceSearchCommand(query, options = {}) {
|
|
|
256
259
|
return isConfluenceSpaceAllowed(result.space);
|
|
257
260
|
});
|
|
258
261
|
ui.succeedSpinner(chalk.green('Confluence search completed'));
|
|
259
|
-
|
|
262
|
+
outputResult(filteredResults, formatConfluenceSearchResults);
|
|
260
263
|
}
|
|
261
264
|
catch (error) {
|
|
262
265
|
ui.failSpinner();
|
|
@@ -3,6 +3,7 @@ import { createIssueLink, validateIssuePermissions } from '../lib/jira-client.js
|
|
|
3
3
|
import { CommandError } from '../lib/errors.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { validateOptions, IssueKeySchema } from '../lib/validation.js';
|
|
6
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
6
7
|
export async function createIssueLinkCommand(inwardKey, linkType, outwardKey) {
|
|
7
8
|
validateOptions(IssueKeySchema, inwardKey);
|
|
8
9
|
validateOptions(IssueKeySchema, outwardKey);
|
|
@@ -12,8 +13,7 @@ export async function createIssueLinkCommand(inwardKey, linkType, outwardKey) {
|
|
|
12
13
|
try {
|
|
13
14
|
await createIssueLink(inwardKey, outwardKey, linkType.trim());
|
|
14
15
|
ui.succeedSpinner(chalk.green(`Link created successfully`));
|
|
15
|
-
|
|
16
|
-
${inwardKey} --[${linkType.trim()}]--> ${outwardKey}`));
|
|
16
|
+
outputResult({ success: true, inwardKey, linkType: linkType.trim(), outwardKey }, (data) => chalk.gray(`\n${data.inwardKey} --[${data.linkType}]--> ${data.outwardKey}`));
|
|
17
17
|
}
|
|
18
18
|
catch (error) {
|
|
19
19
|
if (error instanceof CommandError)
|
|
@@ -7,6 +7,7 @@ import { CommandError } from '../lib/errors.js';
|
|
|
7
7
|
import { ui } from '../lib/ui.js';
|
|
8
8
|
import { validateOptions, CreateTaskSchema } from '../lib/validation.js';
|
|
9
9
|
import { isCommandAllowed, isProjectAllowed } from '../lib/settings.js';
|
|
10
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
10
11
|
export async function createTaskCommand(options) {
|
|
11
12
|
validateOptions(CreateTaskSchema, options);
|
|
12
13
|
const { title, project, issueType, parent, priority, description, descriptionFile, labels, component, fixVersion, dueDate, assignee, customField } = options;
|
|
@@ -85,13 +86,16 @@ export async function createTaskCommand(options) {
|
|
|
85
86
|
...issueFields,
|
|
86
87
|
});
|
|
87
88
|
ui.succeedSpinner(chalk.green(`Issue created successfully: ${result.key}`));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
outputResult({ key: result.key, title, project, issueType, parent }, (data) => {
|
|
90
|
+
let out = chalk.gray(`\nTitle: ${data.title}`);
|
|
91
|
+
out += `\n${chalk.gray(`Project: ${data.project}`)}`;
|
|
92
|
+
out += `\n${chalk.gray(`Issue Type: ${data.issueType}`)}`;
|
|
93
|
+
if (data.parent) {
|
|
94
|
+
out += `\n${chalk.gray(`Parent: ${data.parent}`)}`;
|
|
95
|
+
}
|
|
96
|
+
out += `\n${chalk.cyan(`\nIssue Key: ${data.key}`)}`;
|
|
97
|
+
return out;
|
|
98
|
+
});
|
|
95
99
|
}
|
|
96
100
|
catch (error) {
|
|
97
101
|
const errorMsg = error.message?.toLowerCase() || '';
|
|
@@ -3,6 +3,7 @@ import { getIssueLinks, deleteIssueLink, validateIssuePermissions } from '../lib
|
|
|
3
3
|
import { CommandError } from '../lib/errors.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { validateOptions, IssueKeySchema } from '../lib/validation.js';
|
|
6
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
6
7
|
export async function deleteIssueLinkCommand(sourceKey, targetKey) {
|
|
7
8
|
validateOptions(IssueKeySchema, sourceKey);
|
|
8
9
|
validateOptions(IssueKeySchema, targetKey);
|
|
@@ -26,7 +27,7 @@ export async function deleteIssueLinkCommand(sourceKey, targetKey) {
|
|
|
26
27
|
ui.startSpinner(`Deleting link ${linkId}...`);
|
|
27
28
|
await deleteIssueLink(linkId);
|
|
28
29
|
ui.succeedSpinner(chalk.green(`Link deleted successfully`));
|
|
29
|
-
|
|
30
|
+
outputResult({ success: true, sourceKey, targetKey, linkType: matchingLinks[0].type.name }, (data) => chalk.gray(`\nRemoved: ${data.sourceKey} <--> ${data.targetKey} (${data.linkType})`));
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
33
|
if (error instanceof CommandError)
|
|
@@ -3,6 +3,7 @@ import { removeIssueLabels, validateIssuePermissions } from '../lib/jira-client.
|
|
|
3
3
|
import { CommandError } from '../lib/errors.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { validateOptions, IssueKeySchema } from '../lib/validation.js';
|
|
6
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
6
7
|
export async function deleteLabelCommand(taskId, labelsString) {
|
|
7
8
|
// Validate input
|
|
8
9
|
validateOptions(IssueKeySchema, taskId);
|
|
@@ -21,8 +22,7 @@ export async function deleteLabelCommand(taskId, labelsString) {
|
|
|
21
22
|
try {
|
|
22
23
|
await removeIssueLabels(taskId, labels);
|
|
23
24
|
ui.succeedSpinner(chalk.green(`Labels removed successfully from ${taskId}`));
|
|
24
|
-
|
|
25
|
-
Labels: ${labels.join(', ')}`));
|
|
25
|
+
outputResult({ success: true, issueKey: taskId, labels }, (data) => chalk.gray(`\nLabels: ${data.labels.join(', ')}`));
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
28
|
if (error instanceof CommandError)
|
package/dist/commands/epic.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { getEpics, getEpic, createEpic, updateEpic, getEpicIssues, linkIssueToEpic, unlinkIssueFromEpic, getEpicProgress, } from '../lib/jira-client.js';
|
|
3
3
|
import { formatEpicList, formatEpicDetails, formatEpicProgress, formatEpicIssues, } from '../lib/formatters.js';
|
|
4
4
|
import { CommandError } from '../lib/errors.js';
|
|
5
5
|
import { ui } from '../lib/ui.js';
|
|
6
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
6
7
|
// =============================================================================
|
|
7
8
|
// epic list <project-key> [--done] [--max <n>]
|
|
8
9
|
// =============================================================================
|
|
9
|
-
export async function epicListCommand(projectKey, options) {
|
|
10
|
+
export async function epicListCommand(projectKey, options = {}) {
|
|
10
11
|
ui.startSpinner(`Fetching epics for project ${projectKey}...`);
|
|
11
12
|
try {
|
|
12
|
-
const epics = await
|
|
13
|
+
const epics = await getEpics(projectKey, {
|
|
13
14
|
includeDone: !!options.done,
|
|
14
15
|
max: options.max,
|
|
15
16
|
});
|
|
16
17
|
ui.stopSpinner();
|
|
17
|
-
|
|
18
|
+
outputResult(epics, formatEpicList);
|
|
18
19
|
}
|
|
19
20
|
catch (error) {
|
|
20
21
|
ui.failSpinner();
|
|
@@ -38,7 +39,7 @@ export async function epicGetCommand(epicKey) {
|
|
|
38
39
|
try {
|
|
39
40
|
const epic = await getEpic(epicKey);
|
|
40
41
|
ui.stopSpinner();
|
|
41
|
-
|
|
42
|
+
outputResult(epic, formatEpicDetails);
|
|
42
43
|
}
|
|
43
44
|
catch (error) {
|
|
44
45
|
ui.failSpinner();
|
|
@@ -68,10 +69,13 @@ export async function epicCreateCommand(projectKey, options) {
|
|
|
68
69
|
labels,
|
|
69
70
|
});
|
|
70
71
|
ui.succeedSpinner(chalk.green(`Epic created successfully: ${result.key}`));
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
outputResult({ key: result.key, name: options.name, summary: options.summary, project: projectKey }, (data) => {
|
|
73
|
+
let out = chalk.gray(`\nName: ${data.name}`);
|
|
74
|
+
out += `\n${chalk.gray(`Summary: ${data.summary}`)}`;
|
|
75
|
+
out += `\n${chalk.gray(`Project: ${data.project}`)}`;
|
|
76
|
+
out += `\n${chalk.cyan(`\nEpic Key: ${data.key}`)}`;
|
|
77
|
+
return out;
|
|
78
|
+
});
|
|
75
79
|
}
|
|
76
80
|
catch (error) {
|
|
77
81
|
ui.failSpinner();
|
|
@@ -99,10 +103,14 @@ export async function epicUpdateCommand(epicKey, options) {
|
|
|
99
103
|
try {
|
|
100
104
|
await updateEpic(epicKey, { name: options.name, summary: options.summary });
|
|
101
105
|
ui.succeedSpinner(chalk.green(`Epic ${epicKey} updated successfully.`));
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
outputResult({ success: true, epicKey, name: options.name, summary: options.summary }, (data) => {
|
|
107
|
+
const lines = [];
|
|
108
|
+
if (data.name)
|
|
109
|
+
lines.push(chalk.gray(`Name: ${data.name}`));
|
|
110
|
+
if (data.summary)
|
|
111
|
+
lines.push(chalk.gray(`Summary: ${data.summary}`));
|
|
112
|
+
return lines.join('\n');
|
|
113
|
+
});
|
|
106
114
|
}
|
|
107
115
|
catch (error) {
|
|
108
116
|
ui.failSpinner();
|
|
@@ -125,7 +133,7 @@ export async function epicIssuesCommand(epicKey, options) {
|
|
|
125
133
|
try {
|
|
126
134
|
const issues = await getEpicIssues(epicKey, { max: options.max });
|
|
127
135
|
ui.stopSpinner();
|
|
128
|
-
|
|
136
|
+
outputResult(issues, formatEpicIssues);
|
|
129
137
|
}
|
|
130
138
|
catch (error) {
|
|
131
139
|
ui.failSpinner();
|
|
@@ -196,7 +204,7 @@ export async function epicProgressCommand(epicKey) {
|
|
|
196
204
|
try {
|
|
197
205
|
const progress = await getEpicProgress(epicKey);
|
|
198
206
|
ui.stopSpinner();
|
|
199
|
-
|
|
207
|
+
outputResult(progress, formatEpicProgress);
|
|
200
208
|
}
|
|
201
209
|
catch (error) {
|
|
202
210
|
ui.failSpinner();
|
|
@@ -2,10 +2,16 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { getIssueStatistics, validateIssuePermissions } from '../lib/jira-client.js';
|
|
3
3
|
import { formatIssueStatistics } from '../lib/formatters.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
5
6
|
export async function getIssueStatisticsCommand(taskIds, options = {}) {
|
|
6
7
|
const ids = taskIds.split(',').map(id => id.trim()).filter(id => id !== '');
|
|
7
8
|
if (ids.length === 0) {
|
|
8
|
-
|
|
9
|
+
if (!isJsonMode()) {
|
|
10
|
+
console.error(chalk.red('Please provide at least one issue ID.'));
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
outputResult({ error: true, message: 'Please provide at least one issue ID.' });
|
|
14
|
+
}
|
|
9
15
|
return;
|
|
10
16
|
}
|
|
11
17
|
ui.startSpinner(`Fetching statistics for ${ids.length} issue(s)...`);
|
|
@@ -22,19 +28,19 @@ export async function getIssueStatisticsCommand(taskIds, options = {}) {
|
|
|
22
28
|
if (!(error instanceof Error))
|
|
23
29
|
continue;
|
|
24
30
|
const isPermissionError = error.message.includes('not allowed') || error.message.includes('restricted');
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if (!isJsonMode()) {
|
|
32
|
+
if (isPermissionError) {
|
|
33
|
+
console.warn(chalk.yellow(`\nSkipping ${id}: ${error.message}`));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.error(chalk.red(`\nFailed to fetch statistics for ${id}: ${error.message}`));
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
if (results.length > 0) {
|
|
36
42
|
ui.succeedSpinner(chalk.green('Statistics retrieved'));
|
|
37
|
-
|
|
43
|
+
outputResult(results, (data) => formatIssueStatistics(data, options.fullBreakdown));
|
|
38
44
|
}
|
|
39
45
|
else {
|
|
40
46
|
ui.failSpinner('Failed to retrieve statistics or all issues were filtered out');
|
|
@@ -4,6 +4,7 @@ import { searchIssuesByJql, getIssueWorklogs } from '../lib/jira-client.js';
|
|
|
4
4
|
import { parseTimeframe, formatDateForJql } from '../lib/utils.js';
|
|
5
5
|
import { formatWorklogs } from '../lib/formatters.js';
|
|
6
6
|
import { CommandError } from '../lib/errors.js';
|
|
7
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
7
8
|
export async function getPersonWorklogCommand(person, timeframe, options) {
|
|
8
9
|
ui.startSpinner(`Fetching worklogs for ${person}...`);
|
|
9
10
|
try {
|
|
@@ -16,9 +17,12 @@ export async function getPersonWorklogCommand(person, timeframe, options) {
|
|
|
16
17
|
const issues = await searchIssuesByJql(jql, 100);
|
|
17
18
|
if (issues.length === 0) {
|
|
18
19
|
ui.stopSpinner();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if (!isJsonMode()) {
|
|
21
|
+
console.log(chalk.yellow(`\nNo worklogs found for ${person} between ${startJql} and ${endJql}.\n`));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
outputResult([]);
|
|
25
|
+
}
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
24
28
|
const allWorklogs = [];
|
|
@@ -40,12 +44,15 @@ No worklogs found for ${person} between ${startJql} and ${endJql}.
|
|
|
40
44
|
}
|
|
41
45
|
ui.stopSpinner();
|
|
42
46
|
if (allWorklogs.length === 0) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
if (!isJsonMode()) {
|
|
48
|
+
console.log(chalk.yellow(`\nNo worklogs found for ${person} after detailed filtering.\n`));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
outputResult([]);
|
|
52
|
+
}
|
|
46
53
|
return;
|
|
47
54
|
}
|
|
48
|
-
|
|
55
|
+
outputResult(allWorklogs, (data) => formatWorklogs(data, options.groupByIssue));
|
|
49
56
|
}
|
|
50
57
|
catch (error) {
|
|
51
58
|
ui.failSpinner(`Failed to fetch worklogs: ${error.message}`);
|
package/dist/commands/issue.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { assignIssue, validateIssuePermissions } from '../lib/jira-client.js';
|
|
3
3
|
import { CommandError } from '../lib/errors.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
5
6
|
export async function issueAssignCommand(issueKey, accountId) {
|
|
6
7
|
const actualAccountId = accountId === 'null' ? null : accountId;
|
|
7
8
|
// Check permissions and filters
|
|
@@ -11,6 +12,7 @@ export async function issueAssignCommand(issueKey, accountId) {
|
|
|
11
12
|
try {
|
|
12
13
|
await assignIssue(issueKey, actualAccountId);
|
|
13
14
|
ui.succeedSpinner(chalk.green(`Issue ${issueKey} successfully assigned to ${actualAccountId || 'Unassigned'}.`));
|
|
15
|
+
outputResult({ success: true, issueKey, assignee: actualAccountId || null }, (data) => chalk.green(`Issue ${data.issueKey} successfully assigned to ${data.assignee || 'Unassigned'}.`));
|
|
14
16
|
}
|
|
15
17
|
catch (error) {
|
|
16
18
|
if (error instanceof CommandError) {
|
|
@@ -4,6 +4,7 @@ import { formatUsers } from '../lib/formatters.js';
|
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { isCommandAllowed, isProjectAllowed } from '../lib/settings.js';
|
|
6
6
|
import { CommandError } from '../lib/errors.js';
|
|
7
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
7
8
|
export async function listColleaguesCommand(projectKey) {
|
|
8
9
|
if (projectKey) {
|
|
9
10
|
if (!isProjectAllowed(projectKey)) {
|
|
@@ -21,10 +22,15 @@ export async function listColleaguesCommand(projectKey) {
|
|
|
21
22
|
const users = await getUsers(projectKey);
|
|
22
23
|
ui.succeedSpinner(chalk.green('Colleagues retrieved'));
|
|
23
24
|
if (users.length === 0) {
|
|
24
|
-
|
|
25
|
+
if (!isJsonMode()) {
|
|
26
|
+
console.log(chalk.yellow('\nNo active colleagues found.'));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
outputResult(users);
|
|
30
|
+
}
|
|
25
31
|
}
|
|
26
32
|
else {
|
|
27
|
-
|
|
33
|
+
outputResult(users, formatUsers);
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
catch (error) {
|
|
@@ -4,6 +4,7 @@ import { formatIssueLinks } from '../lib/formatters.js';
|
|
|
4
4
|
import { CommandError } from '../lib/errors.js';
|
|
5
5
|
import { ui } from '../lib/ui.js';
|
|
6
6
|
import { validateOptions, IssueKeySchema } from '../lib/validation.js';
|
|
7
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
7
8
|
export async function listIssueLinksCommand(issueKey) {
|
|
8
9
|
validateOptions(IssueKeySchema, issueKey);
|
|
9
10
|
ui.startSpinner(`Validating permissions for ${issueKey}...`);
|
|
@@ -12,7 +13,7 @@ export async function listIssueLinksCommand(issueKey) {
|
|
|
12
13
|
try {
|
|
13
14
|
const links = await getIssueLinks(issueKey);
|
|
14
15
|
ui.succeedSpinner(chalk.green(`Issue links retrieved for ${issueKey}`));
|
|
15
|
-
|
|
16
|
+
outputResult(links, (data) => formatIssueLinks(issueKey, data));
|
|
16
17
|
}
|
|
17
18
|
catch (error) {
|
|
18
19
|
if (error instanceof CommandError)
|
|
@@ -4,6 +4,7 @@ import { formatProjectIssueTypes } from '../lib/formatters.js';
|
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { isCommandAllowed, isProjectAllowed } from '../lib/settings.js';
|
|
6
6
|
import { CommandError } from '../lib/errors.js';
|
|
7
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
7
8
|
export async function listIssueTypesCommand(projectKey) {
|
|
8
9
|
// Check if project is allowed
|
|
9
10
|
if (!isProjectAllowed(projectKey)) {
|
|
@@ -16,5 +17,5 @@ export async function listIssueTypesCommand(projectKey) {
|
|
|
16
17
|
ui.startSpinner(`Fetching issue types for project ${projectKey}...`);
|
|
17
18
|
const issueTypes = await getProjectIssueTypes(projectKey);
|
|
18
19
|
ui.succeedSpinner(chalk.green('Issue types retrieved'));
|
|
19
|
-
|
|
20
|
+
outputResult(issueTypes, (data) => formatProjectIssueTypes(projectKey, data));
|
|
20
21
|
}
|
|
@@ -3,12 +3,13 @@ import { getAvailableLinkTypes } from '../lib/jira-client.js';
|
|
|
3
3
|
import { formatLinkTypes } from '../lib/formatters.js';
|
|
4
4
|
import { CommandError } from '../lib/errors.js';
|
|
5
5
|
import { ui } from '../lib/ui.js';
|
|
6
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
6
7
|
export async function listLinkTypesCommand() {
|
|
7
8
|
ui.startSpinner('Fetching available link types...');
|
|
8
9
|
try {
|
|
9
10
|
const linkTypes = await getAvailableLinkTypes();
|
|
10
11
|
ui.succeedSpinner(chalk.green('Link types retrieved'));
|
|
11
|
-
|
|
12
|
+
outputResult(linkTypes, formatLinkTypes);
|
|
12
13
|
}
|
|
13
14
|
catch (error) {
|
|
14
15
|
if (error instanceof CommandError)
|
package/dist/commands/me.js
CHANGED
|
@@ -2,9 +2,10 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { getCurrentUser } from '../lib/jira-client.js';
|
|
3
3
|
import { formatUserInfo } from '../lib/formatters.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
5
6
|
export async function meCommand() {
|
|
6
7
|
ui.startSpinner('Fetching user information...');
|
|
7
8
|
const user = await getCurrentUser();
|
|
8
9
|
ui.succeedSpinner(chalk.green('User information retrieved'));
|
|
9
|
-
|
|
10
|
+
outputResult(user, formatUserInfo);
|
|
10
11
|
}
|
|
@@ -3,6 +3,7 @@ import { getProjectFields } from '../lib/field-resolver.js';
|
|
|
3
3
|
import { CommandError } from '../lib/errors.js';
|
|
4
4
|
import { validateOptions, ProjectKeySchema } from '../lib/validation.js';
|
|
5
5
|
import { isCommandAllowed, isProjectAllowed } from '../lib/settings.js';
|
|
6
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
6
7
|
export async function projectFieldsCommand(projectKey, options) {
|
|
7
8
|
if (!projectKey) {
|
|
8
9
|
throw new CommandError('Project key is required');
|
|
@@ -24,20 +25,28 @@ export async function projectFieldsCommand(projectKey, options) {
|
|
|
24
25
|
fields = fields.filter(f => f.name.toLowerCase().includes(lower) || f.id.toLowerCase().includes(lower));
|
|
25
26
|
}
|
|
26
27
|
if (fields.length === 0) {
|
|
27
|
-
|
|
28
|
+
if (!isJsonMode()) {
|
|
29
|
+
console.log(chalk.yellow(`No fields found for project ${projectKey}`));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
outputResult(fields);
|
|
33
|
+
}
|
|
28
34
|
return;
|
|
29
35
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
outputResult(fields, (data) => {
|
|
37
|
+
let out = chalk.bold(`\nFields for project ${projectKey}:`);
|
|
38
|
+
out += `\n${chalk.gray(`${'ID'.padEnd(30)} ${'Name'.padEnd(30)} ${'Type'.padEnd(20)} Required`)}`;
|
|
39
|
+
out += `\n${chalk.gray('-'.repeat(90))}`;
|
|
40
|
+
for (const field of data) {
|
|
41
|
+
const required = field.required ? chalk.red('yes') : chalk.gray('no');
|
|
42
|
+
const idLabel = field.custom ? chalk.cyan(field.id.padEnd(30)) : field.id.padEnd(30);
|
|
43
|
+
const nameLabel = field.custom ? chalk.cyan(field.name.padEnd(30)) : field.name.padEnd(30);
|
|
44
|
+
const type = (field.schema?.type || 'unknown').padEnd(20);
|
|
45
|
+
out += `\n${idLabel} ${nameLabel} ${type} ${required}`;
|
|
46
|
+
}
|
|
47
|
+
out += `\n${chalk.gray(`\nTotal: ${data.length} field(s)`)}`;
|
|
48
|
+
return out;
|
|
49
|
+
});
|
|
41
50
|
}
|
|
42
51
|
catch (error) {
|
|
43
52
|
if (error instanceof CommandError)
|
|
@@ -4,6 +4,7 @@ import { formatProjectStatuses } from '../lib/formatters.js';
|
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
5
|
import { isCommandAllowed, isProjectAllowed } from '../lib/settings.js';
|
|
6
6
|
import { CommandError } from '../lib/errors.js';
|
|
7
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
7
8
|
export async function projectStatusesCommand(projectIdOrKey) {
|
|
8
9
|
// Check if project is allowed
|
|
9
10
|
if (!isProjectAllowed(projectIdOrKey)) {
|
|
@@ -17,7 +18,7 @@ export async function projectStatusesCommand(projectIdOrKey) {
|
|
|
17
18
|
try {
|
|
18
19
|
const statuses = await getProjectStatuses(projectIdOrKey);
|
|
19
20
|
ui.succeedSpinner(chalk.green('Project statuses retrieved'));
|
|
20
|
-
|
|
21
|
+
outputResult(statuses, (data) => formatProjectStatuses(projectIdOrKey, data));
|
|
21
22
|
}
|
|
22
23
|
catch (error) {
|
|
23
24
|
ui.failSpinner(chalk.red('Failed to fetch project statuses'));
|
|
@@ -3,6 +3,7 @@ import { getProjects } from '../lib/jira-client.js';
|
|
|
3
3
|
import { formatProjects } from '../lib/formatters.js';
|
|
4
4
|
import { getAllowedProjects, isProjectAllowed } from '../lib/settings.js';
|
|
5
5
|
import { ui } from '../lib/ui.js';
|
|
6
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
6
7
|
export async function projectsCommand() {
|
|
7
8
|
ui.startSpinner('Fetching projects...');
|
|
8
9
|
const allProjects = await getProjects();
|
|
@@ -13,12 +14,12 @@ export async function projectsCommand() {
|
|
|
13
14
|
? allProjects
|
|
14
15
|
: allProjects.filter(project => isProjectAllowed(project.key));
|
|
15
16
|
ui.succeedSpinner(chalk.green('Projects retrieved'));
|
|
16
|
-
if (filteredProjects.length === 0) {
|
|
17
|
+
if (!isJsonMode() && filteredProjects.length === 0) {
|
|
17
18
|
console.log(chalk.yellow('\nNo projects match your settings configuration.'));
|
|
18
19
|
const displayKeys = allowedProjects.map(p => typeof p === 'string' ? p : p.key);
|
|
19
20
|
console.log(chalk.gray('Allowed projects: ' + displayKeys.join(', ')));
|
|
20
21
|
}
|
|
21
22
|
else {
|
|
22
|
-
|
|
23
|
+
outputResult(filteredProjects, formatProjects);
|
|
23
24
|
}
|
|
24
25
|
}
|
package/dist/commands/run-jql.js
CHANGED
|
@@ -2,15 +2,18 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { searchIssuesByJql } from '../lib/jira-client.js';
|
|
3
3
|
import { formatJqlResults } from '../lib/formatters.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
|
+
import { outputResult, isJsonMode } from '../lib/json-mode.js';
|
|
5
6
|
export async function runJqlCommand(jqlQuery, options) {
|
|
6
7
|
// Parse and validate limit parameter
|
|
7
8
|
let maxResults = options.limit || 50;
|
|
8
9
|
if (maxResults > 1000) {
|
|
9
|
-
|
|
10
|
+
if (!isJsonMode()) {
|
|
11
|
+
console.warn(chalk.yellow('\nWarning: Limit is very high. Using 1000 as maximum.'));
|
|
12
|
+
}
|
|
10
13
|
maxResults = 1000;
|
|
11
14
|
}
|
|
12
15
|
ui.startSpinner('Executing JQL query...');
|
|
13
16
|
const issues = await searchIssuesByJql(jqlQuery, maxResults);
|
|
14
17
|
ui.succeedSpinner(chalk.green('Query executed successfully'));
|
|
15
|
-
|
|
18
|
+
outputResult(issues, formatJqlResults);
|
|
16
19
|
}
|
|
@@ -8,6 +8,7 @@ import { SettingsSchema } from '../lib/validation.js';
|
|
|
8
8
|
import { getProjects } from '../lib/jira-client.js';
|
|
9
9
|
import { CommandError } from '../lib/errors.js';
|
|
10
10
|
import { validateEnvVars } from '../lib/utils.js';
|
|
11
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
11
12
|
export async function settingsCommand(options) {
|
|
12
13
|
if (options.reset) {
|
|
13
14
|
ui.startSpinner('Resetting settings to default...');
|
|
@@ -31,7 +32,7 @@ export async function settingsCommand(options) {
|
|
|
31
32
|
}
|
|
32
33
|
// Default: Show current settings
|
|
33
34
|
const settings = loadSettings();
|
|
34
|
-
|
|
35
|
+
outputResult(settings, formatSettings);
|
|
35
36
|
}
|
|
36
37
|
async function validateSettingsFile(filePath) {
|
|
37
38
|
ui.startSpinner(`Validating ${filePath}...`);
|
|
@@ -3,6 +3,7 @@ import { validateIssuePermissions } from '../lib/jira-client.js';
|
|
|
3
3
|
import { formatTaskDetails } from '../lib/formatters.js';
|
|
4
4
|
import { CommandError } from '../lib/errors.js';
|
|
5
5
|
import { ui } from '../lib/ui.js';
|
|
6
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
6
7
|
export async function taskWithDetailsCommand(taskId, options = {}) {
|
|
7
8
|
ui.startSpinner(`Fetching details for ${taskId}...`);
|
|
8
9
|
try {
|
|
@@ -12,7 +13,7 @@ export async function taskWithDetailsCommand(taskId, options = {}) {
|
|
|
12
13
|
historyOffset: options.historyOffset ? parseInt(options.historyOffset, 10) : undefined,
|
|
13
14
|
});
|
|
14
15
|
ui.succeedSpinner(chalk.green('Task details retrieved'));
|
|
15
|
-
|
|
16
|
+
outputResult(task, formatTaskDetails);
|
|
16
17
|
}
|
|
17
18
|
catch (error) {
|
|
18
19
|
if (error instanceof CommandError)
|
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { getIssueTransitions, transitionIssue, validateIssuePermissions } from '../lib/jira-client.js';
|
|
3
3
|
import { CommandError } from '../lib/errors.js';
|
|
4
4
|
import { ui } from '../lib/ui.js';
|
|
5
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
5
6
|
export async function transitionCommand(taskId, toStatus) {
|
|
6
7
|
// Check permissions and filters
|
|
7
8
|
ui.startSpinner(`Validating permissions for ${taskId}...`);
|
|
@@ -33,6 +34,7 @@ export async function transitionCommand(taskId, toStatus) {
|
|
|
33
34
|
ui.startSpinner(`Transitioning ${taskId} to ${transition.to.name}...`);
|
|
34
35
|
await transitionIssue(taskId, transition.id);
|
|
35
36
|
ui.succeedSpinner(chalk.green(`Issue ${taskId} successfully transitioned to ${transition.to.name}.`));
|
|
37
|
+
outputResult({ success: true, issueKey: taskId, status: transition.to.name }, (data) => chalk.green(`Issue ${data.issueKey} successfully transitioned to ${data.status}.`));
|
|
36
38
|
}
|
|
37
39
|
catch (error) {
|
|
38
40
|
if (error instanceof CommandError) {
|
|
@@ -7,6 +7,7 @@ import { processMentionsInADF } from '../lib/adf-mentions.js';
|
|
|
7
7
|
import { CommandError } from '../lib/errors.js';
|
|
8
8
|
import { ui } from '../lib/ui.js';
|
|
9
9
|
import { validateOptions, UpdateDescriptionSchema, IssueKeySchema } from '../lib/validation.js';
|
|
10
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
10
11
|
export async function updateDescriptionCommand(taskId, options) {
|
|
11
12
|
// Validate taskId (positional)
|
|
12
13
|
validateOptions(IssueKeySchema, taskId);
|
|
@@ -49,8 +50,7 @@ export async function updateDescriptionCommand(taskId, options) {
|
|
|
49
50
|
try {
|
|
50
51
|
await updateIssueDescription(taskId, adfContent);
|
|
51
52
|
ui.succeedSpinner(chalk.green(`Description updated successfully for ${taskId}`));
|
|
52
|
-
|
|
53
|
-
File: ${absolutePath}`));
|
|
53
|
+
outputResult({ success: true, issueKey: taskId, file: absolutePath }, (data) => chalk.gray(`\nFile: ${data.file}`));
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
56
56
|
if (error instanceof CommandError)
|
|
@@ -7,6 +7,7 @@ import { CommandError } from '../lib/errors.js';
|
|
|
7
7
|
import { ui } from '../lib/ui.js';
|
|
8
8
|
import { validateOptions, UpdateIssueSchema, IssueKeySchema } from '../lib/validation.js';
|
|
9
9
|
import { isCommandAllowed, isProjectAllowed } from '../lib/settings.js';
|
|
10
|
+
import { outputResult } from '../lib/json-mode.js';
|
|
10
11
|
export async function updateIssueCommand(issueKey, options) {
|
|
11
12
|
validateOptions(IssueKeySchema, issueKey);
|
|
12
13
|
validateOptions(UpdateIssueSchema, options);
|
|
@@ -86,6 +87,7 @@ export async function updateIssueCommand(issueKey, options) {
|
|
|
86
87
|
try {
|
|
87
88
|
await updateIssue(issueKey, fields);
|
|
88
89
|
ui.succeedSpinner(chalk.green(`Issue ${issueKey} updated successfully`));
|
|
90
|
+
outputResult({ success: true, issueKey }, (data) => chalk.green(`Issue ${data.issueKey} updated successfully`));
|
|
89
91
|
}
|
|
90
92
|
catch (error) {
|
|
91
93
|
if (error instanceof CommandError)
|
package/dist/lib/jira-client.js
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
let _jsonMode = false;
|
|
3
|
+
let _compactMode = false;
|
|
4
|
+
export function initJsonMode() {
|
|
5
|
+
_jsonMode = process.argv.includes('--json') || process.argv.includes('--json-compact');
|
|
6
|
+
_compactMode = process.argv.includes('--json-compact');
|
|
7
|
+
}
|
|
8
|
+
export function isJsonMode() {
|
|
9
|
+
return _jsonMode;
|
|
10
|
+
}
|
|
11
|
+
export function isCompactMode() {
|
|
12
|
+
return _compactMode;
|
|
13
|
+
}
|
|
14
|
+
export function outputResult(data, formatter) {
|
|
15
|
+
if (_jsonMode) {
|
|
16
|
+
const output = _compactMode
|
|
17
|
+
? JSON.stringify(data)
|
|
18
|
+
: JSON.stringify(data, null, 2);
|
|
19
|
+
console.log(output);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
if (formatter) {
|
|
23
|
+
console.log(formatter(data));
|
|
24
|
+
}
|
|
25
|
+
else if (typeof data === 'object' && data !== null) {
|
|
26
|
+
console.log(JSON.stringify(data, null, 2));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log(data);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function outputError(message, hints = [], exitCode = 1) {
|
|
34
|
+
if (_jsonMode) {
|
|
35
|
+
console.log(JSON.stringify({ error: true, message, hints, exitCode }));
|
|
36
|
+
process.exit(exitCode);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.error(chalk.red(`\n❌ Error: ${message}`));
|
|
40
|
+
hints.forEach(hint => {
|
|
41
|
+
console.error(chalk.yellow(` Hint: ${hint}`));
|
|
42
|
+
});
|
|
43
|
+
process.exit(exitCode);
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/lib/ui.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
|
+
import { isJsonMode } from './json-mode.js';
|
|
2
3
|
class UI {
|
|
3
4
|
spinnerInstance = null;
|
|
4
5
|
startSpinner(message) {
|
|
6
|
+
if (isJsonMode()) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
5
9
|
if (this.spinnerInstance) {
|
|
6
10
|
this.spinnerInstance.stop();
|
|
7
11
|
}
|
|
@@ -9,24 +13,36 @@ class UI {
|
|
|
9
13
|
return this.spinnerInstance;
|
|
10
14
|
}
|
|
11
15
|
stopSpinner() {
|
|
16
|
+
if (isJsonMode()) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
12
19
|
if (this.spinnerInstance) {
|
|
13
20
|
this.spinnerInstance.stop();
|
|
14
21
|
this.spinnerInstance = null;
|
|
15
22
|
}
|
|
16
23
|
}
|
|
17
24
|
succeedSpinner(message) {
|
|
25
|
+
if (isJsonMode()) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
18
28
|
if (this.spinnerInstance) {
|
|
19
29
|
this.spinnerInstance.succeed(message);
|
|
20
30
|
this.spinnerInstance = null;
|
|
21
31
|
}
|
|
22
32
|
}
|
|
23
33
|
failSpinner(message) {
|
|
34
|
+
if (isJsonMode()) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
24
37
|
if (this.spinnerInstance) {
|
|
25
38
|
this.spinnerInstance.fail(message);
|
|
26
39
|
this.spinnerInstance = null;
|
|
27
40
|
}
|
|
28
41
|
}
|
|
29
42
|
updateSpinner(message) {
|
|
43
|
+
if (isJsonMode()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
30
46
|
if (this.spinnerInstance) {
|
|
31
47
|
this.spinnerInstance.text = message;
|
|
32
48
|
}
|