jira-pilot 2.0.4 → 2.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 +216 -173
- package/bin/{jira.js → jira.ts} +10 -1
- package/dist/bin/jira.js +64 -0
- package/package.json +21 -15
- package/src/commands/ai-actions/{plan.js → plan.ts} +9 -9
- package/src/commands/ai-actions/{review.js → review.ts} +2 -2
- package/src/commands/ai-actions/{standup.js → standup.ts} +4 -4
- package/src/commands/{ai.js → ai.ts} +11 -11
- package/src/commands/{board.js → board.ts} +11 -11
- package/src/commands/bulk.ts +230 -0
- package/src/commands/{config.js → config.ts} +57 -8
- package/src/commands/dashboard.ts +222 -0
- package/src/commands/filter.ts +84 -0
- package/src/commands/{git.js → git.ts} +4 -4
- package/src/commands/issue-attach.ts +44 -0
- package/src/commands/issue-pr.ts +87 -0
- package/src/commands/issue-worklog.ts +90 -0
- package/src/commands/{issue.js → issue.ts} +359 -68
- package/src/commands/{mcp.js → mcp.ts} +2 -2
- package/src/commands/{project.js → project.ts} +11 -11
- package/src/commands/sprint.ts +269 -0
- package/src/server/{mcp-server.js → mcp-server.ts} +235 -8
- package/src/services/{ai-service.js → ai-service.ts} +16 -16
- package/src/services/{api-service.js → api-service.ts} +33 -9
- package/src/services/config-service.ts +21 -0
- package/src/types.ts +68 -0
- package/src/utils/{adf-parser.js → adf-parser.ts} +12 -12
- package/src/utils/config-store.ts +109 -0
- package/src/utils/{config.js → config.ts} +14 -41
- package/src/utils/{error-handler.js → error-handler.ts} +2 -1
- package/src/utils/{text-to-adf.js → text-to-adf.ts} +1 -1
- package/src/utils/{validators.js → validators.ts} +4 -4
- package/src/commands/bulk.js +0 -108
- package/src/commands/dashboard.js +0 -89
- package/src/commands/sprint.js +0 -153
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
4
|
import { api } from '../services/api-service.js';
|
|
5
5
|
import { aiService } from '../services/ai-service.js';
|
|
6
6
|
import ora from 'ora';
|
|
@@ -8,9 +8,14 @@ import enquirer from 'enquirer';
|
|
|
8
8
|
import { parseADF } from '../utils/adf-parser.js';
|
|
9
9
|
import { textToADF } from '../utils/text-to-adf.js';
|
|
10
10
|
import { validateIssueKey } from '../utils/validators.js';
|
|
11
|
+
|
|
11
12
|
import { handleCommandError } from '../utils/error-handler.js';
|
|
13
|
+
import { registerWorklogCommand } from './issue-worklog.js';
|
|
14
|
+
import { registerPrCommand } from './issue-pr.js';
|
|
15
|
+
import { registerAttachCommand } from './issue-attach.js';
|
|
16
|
+
import { ConfigService } from '../services/config-service.js';
|
|
12
17
|
|
|
13
|
-
export function registerIssueCommand(program) {
|
|
18
|
+
export function registerIssueCommand(program: Command) {
|
|
14
19
|
const issueCmd = new Command('issue')
|
|
15
20
|
.description('Manage Jira issues')
|
|
16
21
|
.addHelpText('after', `
|
|
@@ -39,7 +44,7 @@ Examples:
|
|
|
39
44
|
$ jira issue list --jql "created >= -7d"
|
|
40
45
|
$ jira issue list --export json
|
|
41
46
|
`)
|
|
42
|
-
.action(async (options) => {
|
|
47
|
+
.action(async (options: any) => {
|
|
43
48
|
const spinner = ora('Fetching issues...').start();
|
|
44
49
|
try {
|
|
45
50
|
// Natural Language JQL
|
|
@@ -49,7 +54,7 @@ Examples:
|
|
|
49
54
|
const generatedJql = await aiService.generateJql(options.ask);
|
|
50
55
|
aiSpinner.succeed(`JQL: ${chalk.cyan(generatedJql)}`);
|
|
51
56
|
options.jql = generatedJql; // Override/Set JQL
|
|
52
|
-
} catch (e) {
|
|
57
|
+
} catch (e: any) {
|
|
53
58
|
aiSpinner.fail('Failed to translate query.');
|
|
54
59
|
console.error(chalk.red(e.message));
|
|
55
60
|
return;
|
|
@@ -105,7 +110,7 @@ Examples:
|
|
|
105
110
|
mdContent += `| Key | Summary | Status | Assignee |\n`;
|
|
106
111
|
mdContent += `|---|---|---|---|\n`;
|
|
107
112
|
|
|
108
|
-
data.issues.forEach(i => {
|
|
113
|
+
data.issues.forEach((i: any) => {
|
|
109
114
|
const key = i.key;
|
|
110
115
|
const summary = i.fields.summary || '';
|
|
111
116
|
const status = i.fields.status?.name || '';
|
|
@@ -120,7 +125,7 @@ Examples:
|
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
if (options.output === 'json') {
|
|
123
|
-
console.log(JSON.stringify(data.issues.map(i => ({
|
|
128
|
+
console.log(JSON.stringify(data.issues.map((i: any) => ({
|
|
124
129
|
key: i.key, summary: i.fields.summary,
|
|
125
130
|
status: i.fields.status?.name, assignee: i.fields.assignee?.displayName || null,
|
|
126
131
|
created: i.fields.created, updated: i.fields.updated
|
|
@@ -128,12 +133,12 @@ Examples:
|
|
|
128
133
|
return;
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
const
|
|
132
|
-
[chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Assignee'), chalk.bold('Created'), chalk.bold('Updated')]
|
|
133
|
-
|
|
136
|
+
const table = new Table({
|
|
137
|
+
head: [chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Assignee'), chalk.bold('Created'), chalk.bold('Updated')]
|
|
138
|
+
});
|
|
134
139
|
|
|
135
|
-
data.issues.forEach(i => {
|
|
136
|
-
|
|
140
|
+
data.issues.forEach((i: any) => {
|
|
141
|
+
table.push([
|
|
137
142
|
chalk.cyan(i.key),
|
|
138
143
|
i.fields.summary ? (i.fields.summary.length > 50 ? i.fields.summary.substring(0, 47) + '...' : i.fields.summary) : '',
|
|
139
144
|
i.fields.status ? i.fields.status.name : '',
|
|
@@ -143,9 +148,9 @@ Examples:
|
|
|
143
148
|
]);
|
|
144
149
|
});
|
|
145
150
|
|
|
146
|
-
console.log(table(
|
|
151
|
+
console.log(table.toString());
|
|
147
152
|
|
|
148
|
-
} catch (e) {
|
|
153
|
+
} catch (e: any) {
|
|
149
154
|
handleCommandError(spinner, e, 'Failed to list issues');
|
|
150
155
|
}
|
|
151
156
|
});
|
|
@@ -160,7 +165,7 @@ Examples:
|
|
|
160
165
|
$ jira issue view PROJ-123
|
|
161
166
|
$ jira issue view PROJ-123 --output json
|
|
162
167
|
`)
|
|
163
|
-
.action(async (issueKey, options) => {
|
|
168
|
+
.action(async (issueKey: string, options: any) => {
|
|
164
169
|
const check = validateIssueKey(issueKey);
|
|
165
170
|
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
166
171
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
@@ -189,14 +194,30 @@ Examples:
|
|
|
189
194
|
console.log(chalk.bold('\nAssignee: ') + issue.fields.assignee.displayName);
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
if (issue.fields.components && issue.fields.components.length > 0) {
|
|
198
|
+
console.log(chalk.bold('Components: ') + issue.fields.components.map((c: any) => c.name).join(', '));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (issue.fields.labels && issue.fields.labels.length > 0) {
|
|
202
|
+
console.log(chalk.bold('Labels: ') + issue.fields.labels.join(', '));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (issue.fields.duedate) {
|
|
206
|
+
console.log(chalk.bold('Due Date: ') + issue.fields.duedate);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (issue.fields.fixVersions && issue.fields.fixVersions.length > 0) {
|
|
210
|
+
console.log(chalk.bold('Fix Versions: ') + issue.fields.fixVersions.map((v: any) => v.name).join(', '));
|
|
211
|
+
}
|
|
212
|
+
|
|
192
213
|
if (issue.fields.comment && issue.fields.comment.comments.length > 0) {
|
|
193
214
|
console.log(chalk.bold('\nComments:'));
|
|
194
|
-
issue.fields.comment.comments.forEach(c => {
|
|
215
|
+
issue.fields.comment.comments.forEach((c: any) => {
|
|
195
216
|
console.log(chalk.cyan(c.author.displayName) + ': ' + c.body);
|
|
196
217
|
});
|
|
197
218
|
}
|
|
198
219
|
console.log('');
|
|
199
|
-
} catch (e) {
|
|
220
|
+
} catch (e: any) {
|
|
200
221
|
handleCommandError(spinner, e, 'Failed to fetch issue');
|
|
201
222
|
}
|
|
202
223
|
});
|
|
@@ -211,14 +232,17 @@ Examples:
|
|
|
211
232
|
.option('-d, --description <text>', 'Issue description')
|
|
212
233
|
.option('--priority <name>', 'Priority name (e.g., High, Medium, Low)')
|
|
213
234
|
.option('-a, --assignee <id>', 'Assignee account ID (use "me" for self)')
|
|
235
|
+
.option('--custom <key=value>', 'Custom fields (key=value, repeatable)', (v: string, l: string[]) => l.concat([v]), [])
|
|
214
236
|
.addHelpText('after', `
|
|
215
237
|
Examples:
|
|
216
238
|
$ jira issue create # Interactive wizard
|
|
217
239
|
$ jira issue create -p PROJ -s "Fix login bug" # Quick create
|
|
218
240
|
$ jira issue create -p PROJ -t Bug -s "Crash on save" --priority High
|
|
219
241
|
$ jira issue create -p PROJ -s "New feature" -a me
|
|
242
|
+
$ jira issue create -p PROJ -s "Story" --custom "storyPoints=5"
|
|
220
243
|
`)
|
|
221
|
-
.action(async (options) => {
|
|
244
|
+
.action(async (options: any) => {
|
|
245
|
+
let spinner: any = null;
|
|
222
246
|
try {
|
|
223
247
|
// ── Step 1: Select Project ──────────────────────────
|
|
224
248
|
let projectKey = options.project;
|
|
@@ -232,7 +256,7 @@ Examples:
|
|
|
232
256
|
return;
|
|
233
257
|
}
|
|
234
258
|
|
|
235
|
-
const projectChoices = projectData.values.map(p => ({
|
|
259
|
+
const projectChoices = projectData.values.map((p: any) => ({
|
|
236
260
|
name: p.key,
|
|
237
261
|
message: `${p.key} — ${p.name}`
|
|
238
262
|
}));
|
|
@@ -242,7 +266,7 @@ Examples:
|
|
|
242
266
|
name: 'selectedProject',
|
|
243
267
|
message: 'Select Project:',
|
|
244
268
|
choices: projectChoices
|
|
245
|
-
});
|
|
269
|
+
}) as any;
|
|
246
270
|
projectKey = selectedProject;
|
|
247
271
|
}
|
|
248
272
|
|
|
@@ -277,16 +301,16 @@ Examples:
|
|
|
277
301
|
}
|
|
278
302
|
|
|
279
303
|
// Filter out sub-tasks if present
|
|
280
|
-
const filteredTypes = issueTypes.filter(t => !t.subtask);
|
|
304
|
+
const filteredTypes = issueTypes.filter((t: any) => !t.subtask);
|
|
281
305
|
const typeChoices = (filteredTypes.length > 0 ? filteredTypes : issueTypes)
|
|
282
|
-
.map(t => ({ name: t.name, message: t.name }));
|
|
306
|
+
.map((t: any) => ({ name: t.name, message: t.name }));
|
|
283
307
|
|
|
284
308
|
const { selectedType } = await enquirer.prompt({
|
|
285
309
|
type: 'select',
|
|
286
310
|
name: 'selectedType',
|
|
287
311
|
message: 'Select Issue Type:',
|
|
288
312
|
choices: typeChoices
|
|
289
|
-
});
|
|
313
|
+
}) as any;
|
|
290
314
|
issueTypeName = selectedType;
|
|
291
315
|
}
|
|
292
316
|
|
|
@@ -297,8 +321,8 @@ Examples:
|
|
|
297
321
|
type: 'input',
|
|
298
322
|
name: 'inputSummary',
|
|
299
323
|
message: 'Summary (required):',
|
|
300
|
-
validate: (val) => val.trim().length > 0 || 'Summary cannot be empty'
|
|
301
|
-
});
|
|
324
|
+
validate: (val: any) => val.trim().length > 0 || 'Summary cannot be empty'
|
|
325
|
+
}) as any;
|
|
302
326
|
summary = inputSummary;
|
|
303
327
|
}
|
|
304
328
|
|
|
@@ -309,7 +333,7 @@ Examples:
|
|
|
309
333
|
type: 'input',
|
|
310
334
|
name: 'inputDescription',
|
|
311
335
|
message: 'Description (optional, press Enter to skip):'
|
|
312
|
-
});
|
|
336
|
+
}) as any;
|
|
313
337
|
description = inputDescription || null;
|
|
314
338
|
}
|
|
315
339
|
|
|
@@ -322,7 +346,7 @@ Examples:
|
|
|
322
346
|
spinner.stop();
|
|
323
347
|
|
|
324
348
|
if (Array.isArray(priorities) && priorities.length > 0) {
|
|
325
|
-
const priorityChoices = priorities.map(p => ({
|
|
349
|
+
const priorityChoices = priorities.map((p: any) => ({
|
|
326
350
|
name: p.name,
|
|
327
351
|
message: p.name
|
|
328
352
|
}));
|
|
@@ -332,7 +356,7 @@ Examples:
|
|
|
332
356
|
name: 'selectedPriority',
|
|
333
357
|
message: 'Select Priority:',
|
|
334
358
|
choices: priorityChoices
|
|
335
|
-
});
|
|
359
|
+
}) as any;
|
|
336
360
|
priorityName = selectedPriority;
|
|
337
361
|
}
|
|
338
362
|
} catch {
|
|
@@ -341,6 +365,75 @@ Examples:
|
|
|
341
365
|
}
|
|
342
366
|
}
|
|
343
367
|
|
|
368
|
+
// ── Step 5.5: Components ────────────────────────────
|
|
369
|
+
let componentIds: string[] = [];
|
|
370
|
+
// Interactive only for now (TODO: add flags)
|
|
371
|
+
const compSpinner = ora('Fetching components...').start();
|
|
372
|
+
try {
|
|
373
|
+
const components = await api.get(`/project/${projectKey}/components`);
|
|
374
|
+
compSpinner.stop();
|
|
375
|
+
|
|
376
|
+
if (Array.isArray(components) && components.length > 0) {
|
|
377
|
+
const { selectedComponents } = await enquirer.prompt({
|
|
378
|
+
type: 'multiselect',
|
|
379
|
+
name: 'selectedComponents',
|
|
380
|
+
message: 'Select Components (Space to select, Enter to confirm):',
|
|
381
|
+
choices: components.map((c: any) => ({ name: c.id, message: c.name }))
|
|
382
|
+
}) as any;
|
|
383
|
+
componentIds = selectedComponents;
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
compSpinner.stop();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── Step 5.6: Labels ────────────────────────────────
|
|
390
|
+
let labels: string[] = [];
|
|
391
|
+
const { inputLabels } = await enquirer.prompt({
|
|
392
|
+
type: 'input',
|
|
393
|
+
name: 'inputLabels',
|
|
394
|
+
message: 'Labels (comma-separated, optional):'
|
|
395
|
+
}) as any;
|
|
396
|
+
|
|
397
|
+
if (inputLabels && inputLabels.trim().length > 0) {
|
|
398
|
+
labels = inputLabels.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── Step 5.7: Fix Versions ──────────────────────────
|
|
402
|
+
let fixVersionIds: string[] = [];
|
|
403
|
+
const verSpinner = ora('Fetching versions...').start();
|
|
404
|
+
try {
|
|
405
|
+
const versions = await api.get(`/project/${projectKey}/versions`);
|
|
406
|
+
verSpinner.stop();
|
|
407
|
+
|
|
408
|
+
// Filter unreleased versions usually
|
|
409
|
+
const unreleased = versions.filter((v: any) => !v.released);
|
|
410
|
+
|
|
411
|
+
if (Array.isArray(unreleased) && unreleased.length > 0) {
|
|
412
|
+
const { selectedVersions } = await enquirer.prompt({
|
|
413
|
+
type: 'multiselect',
|
|
414
|
+
name: 'selectedVersions',
|
|
415
|
+
message: 'Fix Versions:',
|
|
416
|
+
choices: unreleased.map((v: any) => ({ name: v.id, message: v.name }))
|
|
417
|
+
}) as any;
|
|
418
|
+
fixVersionIds = selectedVersions;
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
verSpinner.stop();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ── Step 5.8: Due Date ──────────────────────────────
|
|
425
|
+
let duedate: string | null = null;
|
|
426
|
+
const { inputDueDate } = await enquirer.prompt({
|
|
427
|
+
type: 'input',
|
|
428
|
+
name: 'inputDueDate',
|
|
429
|
+
message: 'Due Date (YYYY-MM-DD, optional):',
|
|
430
|
+
validate: (val: string) => {
|
|
431
|
+
if (!val) return true;
|
|
432
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(val) || 'Format must be YYYY-MM-DD';
|
|
433
|
+
}
|
|
434
|
+
}) as any;
|
|
435
|
+
if (inputDueDate) duedate = inputDueDate;
|
|
436
|
+
|
|
344
437
|
// ── Step 6: Assignee ────────────────────────────────
|
|
345
438
|
let assigneeId = options.assignee;
|
|
346
439
|
if (!assigneeId) {
|
|
@@ -353,7 +446,7 @@ Examples:
|
|
|
353
446
|
{ name: 'unassigned', message: 'Leave Unassigned' },
|
|
354
447
|
{ name: 'search', message: 'Search for a user...' }
|
|
355
448
|
]
|
|
356
|
-
});
|
|
449
|
+
}) as any;
|
|
357
450
|
|
|
358
451
|
if (assigneeChoice === 'me') {
|
|
359
452
|
const spinner = ora('Fetching your account...').start();
|
|
@@ -370,7 +463,7 @@ Examples:
|
|
|
370
463
|
type: 'input',
|
|
371
464
|
name: 'searchQuery',
|
|
372
465
|
message: 'Search user by name or email:'
|
|
373
|
-
});
|
|
466
|
+
}) as any;
|
|
374
467
|
|
|
375
468
|
if (searchQuery.trim()) {
|
|
376
469
|
const spinner = ora('Searching users...').start();
|
|
@@ -379,7 +472,7 @@ Examples:
|
|
|
379
472
|
spinner.stop();
|
|
380
473
|
|
|
381
474
|
if (Array.isArray(users) && users.length > 0) {
|
|
382
|
-
const userChoices = users.map(u => ({
|
|
475
|
+
const userChoices = users.map((u: any) => ({
|
|
383
476
|
name: u.accountId,
|
|
384
477
|
message: `${u.displayName} (${u.emailAddress || u.accountId})`
|
|
385
478
|
}));
|
|
@@ -389,7 +482,7 @@ Examples:
|
|
|
389
482
|
name: 'selectedUser',
|
|
390
483
|
message: 'Select User:',
|
|
391
484
|
choices: userChoices
|
|
392
|
-
});
|
|
485
|
+
}) as any;
|
|
393
486
|
assigneeId = selectedUser;
|
|
394
487
|
} else {
|
|
395
488
|
console.log(chalk.yellow('No users found. Leaving unassigned.'));
|
|
@@ -431,7 +524,7 @@ Examples:
|
|
|
431
524
|
name: 'confirmed',
|
|
432
525
|
message: 'Create this issue?',
|
|
433
526
|
initial: true
|
|
434
|
-
});
|
|
527
|
+
}) as any;
|
|
435
528
|
|
|
436
529
|
if (!confirmed) {
|
|
437
530
|
console.log(chalk.yellow('Issue creation cancelled.'));
|
|
@@ -439,7 +532,7 @@ Examples:
|
|
|
439
532
|
}
|
|
440
533
|
|
|
441
534
|
// ── Build Request Body ──────────────────────────────
|
|
442
|
-
const issueBody = {
|
|
535
|
+
const issueBody: any = {
|
|
443
536
|
fields: {
|
|
444
537
|
project: { key: projectKey },
|
|
445
538
|
issuetype: { name: issueTypeName },
|
|
@@ -459,6 +552,35 @@ Examples:
|
|
|
459
552
|
issueBody.fields.assignee = { accountId: assigneeId };
|
|
460
553
|
}
|
|
461
554
|
|
|
555
|
+
if (componentIds.length > 0) {
|
|
556
|
+
issueBody.fields.components = componentIds.map(id => ({ id }));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (labels.length > 0) {
|
|
560
|
+
issueBody.fields.labels = labels;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (fixVersionIds.length > 0) {
|
|
564
|
+
issueBody.fields.fixVersions = fixVersionIds.map(id => ({ id }));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (duedate) {
|
|
568
|
+
issueBody.fields.duedate = duedate;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ── Step 5.9: Custom Fields ─────────────────────────
|
|
572
|
+
if (options.custom && options.custom.length > 0) {
|
|
573
|
+
options.custom.forEach((cf: string) => {
|
|
574
|
+
const [key, ...rest] = cf.split('=');
|
|
575
|
+
const value = rest.join('=');
|
|
576
|
+
if (!key || !value) return;
|
|
577
|
+
|
|
578
|
+
const fieldId = ConfigService.get(`customFields.${key}`) || key;
|
|
579
|
+
const parsedValue = isNaN(Number(value)) ? value : Number(value);
|
|
580
|
+
issueBody.fields[fieldId] = parsedValue;
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
462
584
|
// ── Create Issue ────────────────────────────────────
|
|
463
585
|
const spinner = ora('Creating issue...').start();
|
|
464
586
|
const result = await api.post('/issue', issueBody);
|
|
@@ -466,7 +588,7 @@ Examples:
|
|
|
466
588
|
|
|
467
589
|
console.log(chalk.grey(`View it: jira issue view ${result.key}`));
|
|
468
590
|
|
|
469
|
-
} catch (e) {
|
|
591
|
+
} catch (e: any) {
|
|
470
592
|
handleCommandError(spinner, e, 'Failed to create issue');
|
|
471
593
|
}
|
|
472
594
|
});
|
|
@@ -483,7 +605,7 @@ Examples:
|
|
|
483
605
|
$ jira issue transition PROJ-123 --status "In Progress"
|
|
484
606
|
$ jira issue transition PROJ-123 -s Done
|
|
485
607
|
`)
|
|
486
|
-
.action(async (issueKey, options) => {
|
|
608
|
+
.action(async (issueKey: string, options: any) => {
|
|
487
609
|
const check = validateIssueKey(issueKey);
|
|
488
610
|
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
489
611
|
const spinner = ora(`Fetching transitions for ${issueKey}...`).start();
|
|
@@ -509,21 +631,21 @@ Examples:
|
|
|
509
631
|
if (options.status) {
|
|
510
632
|
// Non-interactive: find matching transition
|
|
511
633
|
targetTransition = transData.transitions.find(
|
|
512
|
-
t => t.name.toLowerCase() === options.status.toLowerCase() ||
|
|
634
|
+
(t: any) => t.name.toLowerCase() === options.status.toLowerCase() ||
|
|
513
635
|
t.to.name.toLowerCase() === options.status.toLowerCase()
|
|
514
636
|
);
|
|
515
637
|
|
|
516
638
|
if (!targetTransition) {
|
|
517
639
|
console.error(chalk.red(`Status "${options.status}" is not a valid transition from "${currentStatus}".`));
|
|
518
640
|
console.log(chalk.grey('Available transitions:'));
|
|
519
|
-
transData.transitions.forEach(t => {
|
|
641
|
+
transData.transitions.forEach((t: any) => {
|
|
520
642
|
console.log(chalk.grey(` • ${t.name} → ${t.to.name}`));
|
|
521
643
|
});
|
|
522
644
|
return;
|
|
523
645
|
}
|
|
524
646
|
} else {
|
|
525
647
|
// Interactive: show selection
|
|
526
|
-
const transitionChoices = transData.transitions.map(t => ({
|
|
648
|
+
const transitionChoices = transData.transitions.map((t: any) => ({
|
|
527
649
|
name: t.id,
|
|
528
650
|
message: `${t.name} → ${chalk.cyan(t.to.name)}`
|
|
529
651
|
}));
|
|
@@ -533,9 +655,9 @@ Examples:
|
|
|
533
655
|
name: 'selectedTransition',
|
|
534
656
|
message: 'Select transition:',
|
|
535
657
|
choices: transitionChoices
|
|
536
|
-
});
|
|
658
|
+
}) as any;
|
|
537
659
|
|
|
538
|
-
targetTransition = transData.transitions.find(t => t.id === selectedTransition);
|
|
660
|
+
targetTransition = transData.transitions.find((t: any) => t.id === selectedTransition);
|
|
539
661
|
}
|
|
540
662
|
|
|
541
663
|
// Execute transition
|
|
@@ -545,7 +667,7 @@ Examples:
|
|
|
545
667
|
});
|
|
546
668
|
execSpinner.succeed(chalk.green(`${issueKey} transitioned: ${currentStatus} → ${chalk.bold(targetTransition.to.name)}`));
|
|
547
669
|
|
|
548
|
-
} catch (e) {
|
|
670
|
+
} catch (e: any) {
|
|
549
671
|
handleCommandError(spinner, e, 'Failed to transition issue');
|
|
550
672
|
}
|
|
551
673
|
});
|
|
@@ -561,9 +683,10 @@ Examples:
|
|
|
561
683
|
$ jira issue assign PROJ-123 -a me # Assign to yourself
|
|
562
684
|
$ jira issue assign PROJ-123 -a none # Unassign
|
|
563
685
|
`)
|
|
564
|
-
.action(async (issueKey, options) => {
|
|
686
|
+
.action(async (issueKey: string, options: any) => {
|
|
565
687
|
const check = validateIssueKey(issueKey);
|
|
566
688
|
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
689
|
+
let spinner: any = null;
|
|
567
690
|
try {
|
|
568
691
|
let assigneeId = options.assignee;
|
|
569
692
|
|
|
@@ -586,7 +709,7 @@ Examples:
|
|
|
586
709
|
{ name: 'none', message: 'Unassign' },
|
|
587
710
|
{ name: 'search', message: 'Search for a user...' }
|
|
588
711
|
]
|
|
589
|
-
});
|
|
712
|
+
}) as any;
|
|
590
713
|
assigneeId = assignChoice;
|
|
591
714
|
}
|
|
592
715
|
|
|
@@ -602,7 +725,7 @@ Examples:
|
|
|
602
725
|
type: 'input',
|
|
603
726
|
name: 'searchQuery',
|
|
604
727
|
message: 'Search user by name or email:'
|
|
605
|
-
});
|
|
728
|
+
}) as any;
|
|
606
729
|
|
|
607
730
|
const spinner = ora('Searching users...').start();
|
|
608
731
|
const users = await api.get(`/user/search?query=${encodeURIComponent(searchQuery)}`);
|
|
@@ -617,11 +740,11 @@ Examples:
|
|
|
617
740
|
type: 'select',
|
|
618
741
|
name: 'selectedUser',
|
|
619
742
|
message: 'Select User:',
|
|
620
|
-
choices: users.map(u => ({
|
|
743
|
+
choices: users.map((u: any) => ({
|
|
621
744
|
name: u.accountId,
|
|
622
745
|
message: `${u.displayName} (${u.emailAddress || u.accountId})`
|
|
623
746
|
}))
|
|
624
|
-
});
|
|
747
|
+
}) as any;
|
|
625
748
|
assigneeId = selectedUser;
|
|
626
749
|
}
|
|
627
750
|
|
|
@@ -633,7 +756,7 @@ Examples:
|
|
|
633
756
|
await api.put(`/issue/${issueKey}/assignee`, body);
|
|
634
757
|
spinner.succeed(chalk.green(`${issueKey} ${assigneeId === 'none' ? 'unassigned' : 'assigned'} successfully.`));
|
|
635
758
|
|
|
636
|
-
} catch (e) {
|
|
759
|
+
} catch (e: any) {
|
|
637
760
|
handleCommandError(spinner, e, 'Failed to assign issue');
|
|
638
761
|
}
|
|
639
762
|
});
|
|
@@ -649,9 +772,10 @@ Examples:
|
|
|
649
772
|
$ jira issue comment PROJ-123 # Interactive
|
|
650
773
|
$ jira issue comment PROJ-123 -m "Fixed in latest build"
|
|
651
774
|
`)
|
|
652
|
-
.action(async (issueKey, options) => {
|
|
775
|
+
.action(async (issueKey: string, options: any) => {
|
|
653
776
|
const check = validateIssueKey(issueKey);
|
|
654
777
|
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
778
|
+
let spinner: any = null;
|
|
655
779
|
try {
|
|
656
780
|
let commentText = options.message;
|
|
657
781
|
|
|
@@ -668,8 +792,8 @@ Examples:
|
|
|
668
792
|
type: 'input',
|
|
669
793
|
name: 'inputComment',
|
|
670
794
|
message: 'Enter your comment:',
|
|
671
|
-
validate: (val) => val.trim().length > 0 || 'Comment cannot be empty'
|
|
672
|
-
});
|
|
795
|
+
validate: (val: any) => val.trim().length > 0 || 'Comment cannot be empty'
|
|
796
|
+
}) as any;
|
|
673
797
|
commentText = inputComment;
|
|
674
798
|
}
|
|
675
799
|
|
|
@@ -679,7 +803,7 @@ Examples:
|
|
|
679
803
|
});
|
|
680
804
|
spinner.succeed(chalk.green(`Comment added to ${issueKey}.`));
|
|
681
805
|
|
|
682
|
-
} catch (e) {
|
|
806
|
+
} catch (e: any) {
|
|
683
807
|
handleCommandError(spinner, e, 'Failed to add comment');
|
|
684
808
|
}
|
|
685
809
|
});
|
|
@@ -699,7 +823,7 @@ Examples:
|
|
|
699
823
|
$ jira issue edit PROJ-123 --priority High
|
|
700
824
|
$ jira issue edit PROJ-123 -d "New description"
|
|
701
825
|
`)
|
|
702
|
-
.action(async (issueKey, options) => {
|
|
826
|
+
.action(async (issueKey: string, options: any) => {
|
|
703
827
|
const check = validateIssueKey(issueKey);
|
|
704
828
|
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
705
829
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
@@ -707,18 +831,30 @@ Examples:
|
|
|
707
831
|
const issue = await api.get(`/issue/${issueKey}?fields=summary,description,priority`);
|
|
708
832
|
spinner.stop();
|
|
709
833
|
|
|
710
|
-
const updateBody = { fields: {} };
|
|
711
|
-
const hasFlags = options.summary || options.description || options.priority;
|
|
834
|
+
const updateBody: any = { fields: {} };
|
|
835
|
+
const hasFlags = options.summary || options.description || options.priority || (options.custom && options.custom.length > 0);
|
|
712
836
|
|
|
713
837
|
if (hasFlags) {
|
|
714
838
|
if (options.summary) updateBody.fields.summary = options.summary;
|
|
715
839
|
if (options.description) updateBody.fields.description = textToADF(options.description);
|
|
716
840
|
if (options.priority) updateBody.fields.priority = { name: options.priority };
|
|
841
|
+
|
|
842
|
+
if (options.custom && options.custom.length > 0) {
|
|
843
|
+
options.custom.forEach((cf: string) => {
|
|
844
|
+
const [key, ...rest] = cf.split('=');
|
|
845
|
+
const value = rest.join('=');
|
|
846
|
+
if (!key || !value) return;
|
|
847
|
+
|
|
848
|
+
const fieldId = ConfigService.get(`customFields.${key}`) || key;
|
|
849
|
+
const parsedValue = isNaN(Number(value)) ? value : Number(value);
|
|
850
|
+
updateBody.fields[fieldId] = parsedValue;
|
|
851
|
+
});
|
|
852
|
+
}
|
|
717
853
|
} else {
|
|
718
854
|
// Interactive: pick which fields to edit
|
|
719
855
|
console.log(chalk.bold(`\nEditing ${chalk.cyan(issueKey)}: ${issue.fields.summary}\n`));
|
|
720
856
|
|
|
721
|
-
const { Select, Input } = enquirer;
|
|
857
|
+
const { Select, Input } = enquirer as any;
|
|
722
858
|
|
|
723
859
|
const fieldSelect = new Select({
|
|
724
860
|
name: 'fields',
|
|
@@ -726,7 +862,9 @@ Examples:
|
|
|
726
862
|
choices: [
|
|
727
863
|
{ name: 'summary', message: `Summary: ${issue.fields.summary}` },
|
|
728
864
|
{ name: 'description', message: 'Description' },
|
|
729
|
-
{ name: 'priority', message: `Priority: ${issue.fields.priority?.name || 'None'}` }
|
|
865
|
+
{ name: 'priority', message: `Priority: ${issue.fields.priority?.name || 'None'}` },
|
|
866
|
+
{ name: 'components', message: `Components: ${(issue.fields.components || []).map((c: any) => c.name).join(', ')}` },
|
|
867
|
+
{ name: 'labels', message: `Labels: ${(issue.fields.labels || []).join(', ')}` }
|
|
730
868
|
],
|
|
731
869
|
multiple: true
|
|
732
870
|
});
|
|
@@ -752,10 +890,68 @@ Examples:
|
|
|
752
890
|
const prioSelect = new Select({
|
|
753
891
|
name: 'priority',
|
|
754
892
|
message: 'Select priority',
|
|
755
|
-
choices: priorities.map(p => ({ name: p.name, message: p.name }))
|
|
893
|
+
choices: priorities.map((p: any) => ({ name: p.name, message: p.name }))
|
|
756
894
|
});
|
|
757
895
|
updateBody.fields.priority = { name: await prioSelect.run() };
|
|
758
896
|
}
|
|
897
|
+
if (field === 'components') {
|
|
898
|
+
const components = await api.get(`/project/${issue.fields.project.key}/components`);
|
|
899
|
+
if (components.length > 0) {
|
|
900
|
+
const compSelect = new Select({ // Using Enquirer directly via 'any' above, but actually Select is single select?
|
|
901
|
+
// Wait, fieldSelect was initialized from enquirer as any.
|
|
902
|
+
// Multiselect is needed here.
|
|
903
|
+
name: 'components',
|
|
904
|
+
message: 'Select components',
|
|
905
|
+
multiple: true,
|
|
906
|
+
choices: components.map((c: any) => ({ name: c.id, message: c.name, enabled: (issue.fields.components || []).some((ic: any) => ic.id === c.id) }))
|
|
907
|
+
});
|
|
908
|
+
// Enquirer 'Select' with 'multiple: true' is actually 'MultiSelect'? No, standard Enquirer has 'MultiSelect'.
|
|
909
|
+
// We cast enquirer to any so we can check if MultiSelect exists or use Select with multiple: true (which might not work in all versions).
|
|
910
|
+
// Let's try to use 'MultiSelect' if available, or 'Select' with multiple.
|
|
911
|
+
// Actually, in step 5.5 I used type: 'multiselect'. Here I am instantiating classes.
|
|
912
|
+
// Let's use the prompt method for consistency.
|
|
913
|
+
const { selectedComps } = await enquirer.prompt({
|
|
914
|
+
type: 'multiselect',
|
|
915
|
+
name: 'selectedComps',
|
|
916
|
+
message: 'Select Components:',
|
|
917
|
+
choices: components.map((c: any) => ({
|
|
918
|
+
name: c.id,
|
|
919
|
+
message: c.name,
|
|
920
|
+
initial: (issue.fields.components || []).some((ic: any) => ic.id === c.id) // Enquirer uses 'initial' or 'enabled'? Checks docs... usually 'initial' for multiselect is index or name list?
|
|
921
|
+
// Simple approach: Pre-select not easy without specific logic.
|
|
922
|
+
// Let's just show the list.
|
|
923
|
+
}))
|
|
924
|
+
}) as any;
|
|
925
|
+
updateBody.fields.components = selectedComps.map((id: string) => ({ id }));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (field === 'labels') {
|
|
929
|
+
const prompt = new Input({ message: 'New labels (comma separated)', initial: (issue.fields.labels || []).join(', ') });
|
|
930
|
+
const labelStr = await prompt.run();
|
|
931
|
+
updateBody.fields.labels = labelStr.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
|
|
932
|
+
}
|
|
933
|
+
if (field === 'fixVersions') {
|
|
934
|
+
const versions = await api.get(`/project/${issue.fields.project.key}/versions`);
|
|
935
|
+
const unreleased = versions.filter((v: any) => !v.released);
|
|
936
|
+
if (unreleased.length > 0) {
|
|
937
|
+
const { selectedVersions } = await enquirer.prompt({
|
|
938
|
+
type: 'multiselect',
|
|
939
|
+
name: 'selectedVersions',
|
|
940
|
+
message: 'Select Fix Versions:',
|
|
941
|
+
choices: unreleased.map((v: any) => ({ name: v.id, message: v.name }))
|
|
942
|
+
}) as any;
|
|
943
|
+
updateBody.fields.fixVersions = selectedVersions.map((id: string) => ({ id }));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (field === 'duedate') {
|
|
947
|
+
const prompt = new Input({
|
|
948
|
+
message: 'Due Date (YYYY-MM-DD)',
|
|
949
|
+
initial: issue.fields.duedate,
|
|
950
|
+
validate: (val: string) => !val || /^\d{4}-\d{2}-\d{2}$/.test(val) || 'Format must be YYYY-MM-DD'
|
|
951
|
+
});
|
|
952
|
+
const date = await prompt.run();
|
|
953
|
+
updateBody.fields.duedate = date || null;
|
|
954
|
+
}
|
|
759
955
|
}
|
|
760
956
|
}
|
|
761
957
|
|
|
@@ -768,7 +964,7 @@ Examples:
|
|
|
768
964
|
await api.put(`/issue/${issueKey}`, updateBody);
|
|
769
965
|
updateSpinner.succeed(`${chalk.cyan(issueKey)} updated successfully`);
|
|
770
966
|
|
|
771
|
-
} catch (e) {
|
|
967
|
+
} catch (e: any) {
|
|
772
968
|
handleCommandError(spinner, e, `Failed to edit ${issueKey}`);
|
|
773
969
|
}
|
|
774
970
|
});
|
|
@@ -787,7 +983,7 @@ Examples:
|
|
|
787
983
|
$ jira issue search "payment" -p PROJ
|
|
788
984
|
$ jira issue search "crash" --output json
|
|
789
985
|
`)
|
|
790
|
-
.action(async (query, options) => {
|
|
986
|
+
.action(async (query: string, options: any) => {
|
|
791
987
|
const spinner = ora(`Searching for "${query}"...`).start();
|
|
792
988
|
try {
|
|
793
989
|
const jqlParts = [`text ~ "${query.replace(/"/g, '\\"')}"`];
|
|
@@ -807,7 +1003,7 @@ Examples:
|
|
|
807
1003
|
}
|
|
808
1004
|
|
|
809
1005
|
if (options.output === 'json') {
|
|
810
|
-
console.log(JSON.stringify(data.issues.map(i => ({
|
|
1006
|
+
console.log(JSON.stringify(data.issues.map((i: any) => ({
|
|
811
1007
|
key: i.key, summary: i.fields.summary,
|
|
812
1008
|
status: i.fields.status?.name, assignee: i.fields.assignee?.displayName || null,
|
|
813
1009
|
updated: i.fields.updated
|
|
@@ -815,18 +1011,18 @@ Examples:
|
|
|
815
1011
|
return;
|
|
816
1012
|
}
|
|
817
1013
|
|
|
818
|
-
const
|
|
819
|
-
[chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Assignee')]
|
|
820
|
-
|
|
821
|
-
data.issues.forEach(i => {
|
|
822
|
-
|
|
1014
|
+
const table = new Table({
|
|
1015
|
+
head: [chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Assignee')]
|
|
1016
|
+
});
|
|
1017
|
+
data.issues.forEach((i: any) => {
|
|
1018
|
+
table.push([
|
|
823
1019
|
chalk.cyan(i.key),
|
|
824
1020
|
i.fields.summary ? (i.fields.summary.length > 55 ? i.fields.summary.substring(0, 52) + '...' : i.fields.summary) : '',
|
|
825
1021
|
i.fields.status?.name || '',
|
|
826
1022
|
i.fields.assignee?.displayName || 'Unassigned'
|
|
827
1023
|
]);
|
|
828
1024
|
});
|
|
829
|
-
console.log(table(
|
|
1025
|
+
console.log(table.toString());
|
|
830
1026
|
console.log(chalk.grey(`Found ${data.issues.length} result(s)`));
|
|
831
1027
|
|
|
832
1028
|
} catch (e) {
|
|
@@ -861,11 +1057,11 @@ Examples:
|
|
|
861
1057
|
const linkTypes = await api.get('/issueLinkType');
|
|
862
1058
|
spinner.stop();
|
|
863
1059
|
|
|
864
|
-
const { Select } = enquirer;
|
|
1060
|
+
const { Select } = enquirer as any;
|
|
865
1061
|
const typeSelect = new Select({
|
|
866
1062
|
name: 'linkType',
|
|
867
1063
|
message: `Link type: ${chalk.cyan(sourceKey)} → ${chalk.cyan(targetKey)}`,
|
|
868
|
-
choices: linkTypes.issueLinkTypes.map(lt => ({
|
|
1064
|
+
choices: linkTypes.issueLinkTypes.map((lt: any) => ({
|
|
869
1065
|
name: lt.name,
|
|
870
1066
|
message: `${lt.name} (${lt.inward} / ${lt.outward})`
|
|
871
1067
|
}))
|
|
@@ -921,5 +1117,100 @@ Examples:
|
|
|
921
1117
|
}
|
|
922
1118
|
});
|
|
923
1119
|
|
|
1120
|
+
// ── SUBTASK ───────────────────────────────────────────────────────
|
|
1121
|
+
issueCmd
|
|
1122
|
+
.command('subtask')
|
|
1123
|
+
.description('Create a subtask for an existing issue')
|
|
1124
|
+
.argument('<parentKey>', 'Parent Issue Key')
|
|
1125
|
+
.option('-s, --summary <text>', 'Subtask summary')
|
|
1126
|
+
.option('--priority <name>', 'Priority')
|
|
1127
|
+
.option('-a, --assignee <id>', 'Assignee')
|
|
1128
|
+
.addHelpText('after', `
|
|
1129
|
+
Examples:
|
|
1130
|
+
$ jira issue subtask PROJ-123 # Interactive
|
|
1131
|
+
$ jira issue subtask PROJ-123 -s "Dev task"
|
|
1132
|
+
`)
|
|
1133
|
+
.action(async (parentKey: string, options: any) => {
|
|
1134
|
+
const check = validateIssueKey(parentKey);
|
|
1135
|
+
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
1136
|
+
|
|
1137
|
+
const spinner = ora(`Fetching parent ${parentKey}...`).start();
|
|
1138
|
+
try {
|
|
1139
|
+
const parent = await api.get(`/issue/${parentKey}?fields=project,summary`);
|
|
1140
|
+
const projectKey = parent.fields.project.key;
|
|
1141
|
+
spinner.text = 'Fetching subtask types...';
|
|
1142
|
+
|
|
1143
|
+
// Get valid subtask types for project
|
|
1144
|
+
const meta = await api.get(`/issue/createmeta/${projectKey}/issuetypes`);
|
|
1145
|
+
const allTypes = meta.issueTypes || meta.values || [];
|
|
1146
|
+
const subtaskTypes = allTypes.filter((t: any) => t.subtask);
|
|
1147
|
+
spinner.stop();
|
|
1148
|
+
|
|
1149
|
+
if (subtaskTypes.length === 0) {
|
|
1150
|
+
console.error(chalk.red(`No subtask types found in project ${projectKey}.`));
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
console.log(chalk.bold(`\nParent: ${chalk.cyan(parentKey)} ${parent.fields.summary}`));
|
|
1155
|
+
|
|
1156
|
+
let subtaskTypeId = subtaskTypes[0].id;
|
|
1157
|
+
if (subtaskTypes.length > 1) {
|
|
1158
|
+
const { selectedType } = await enquirer.prompt({
|
|
1159
|
+
type: 'select',
|
|
1160
|
+
name: 'selectedType',
|
|
1161
|
+
message: 'Select Subtask Type:',
|
|
1162
|
+
choices: subtaskTypes.map((t: any) => ({ name: t.id, message: t.name }))
|
|
1163
|
+
}) as any;
|
|
1164
|
+
subtaskTypeId = selectedType;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
let summary = options.summary;
|
|
1168
|
+
if (!summary) {
|
|
1169
|
+
const { inputSummary } = await enquirer.prompt({
|
|
1170
|
+
type: 'input',
|
|
1171
|
+
name: 'inputSummary',
|
|
1172
|
+
message: 'Subtask Summary:',
|
|
1173
|
+
validate: (val: string) => val.trim().length > 0 || 'Summary required'
|
|
1174
|
+
}) as any;
|
|
1175
|
+
summary = inputSummary;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// Optional: Priority
|
|
1179
|
+
let priorityName = options.priority;
|
|
1180
|
+
// Optional: Assignee
|
|
1181
|
+
let assigneeId = options.assignee;
|
|
1182
|
+
|
|
1183
|
+
const issueBody: any = {
|
|
1184
|
+
fields: {
|
|
1185
|
+
project: { key: projectKey },
|
|
1186
|
+
parent: { key: parentKey },
|
|
1187
|
+
issuetype: { id: subtaskTypeId },
|
|
1188
|
+
summary: summary
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
if (priorityName) issueBody.fields.priority = { name: priorityName };
|
|
1193
|
+
if (assigneeId === 'me') {
|
|
1194
|
+
const me = await api.get('/myself');
|
|
1195
|
+
issueBody.fields.assignee = { accountId: me.accountId };
|
|
1196
|
+
} else if (assigneeId) {
|
|
1197
|
+
issueBody.fields.assignee = { accountId: assigneeId };
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const createSpinner = ora('Creating subtask...').start();
|
|
1201
|
+
const result = await api.post('/issue', issueBody);
|
|
1202
|
+
createSpinner.succeed(chalk.green(`Subtask created: ${chalk.bold(result.key)}`));
|
|
1203
|
+
|
|
1204
|
+
} catch (e: any) {
|
|
1205
|
+
handleCommandError(spinner, e, 'Failed to create subtask');
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
registerWorklogCommand(issueCmd);
|
|
1212
|
+
registerPrCommand(issueCmd);
|
|
1213
|
+
registerAttachCommand(issueCmd);
|
|
1214
|
+
|
|
924
1215
|
program.addCommand(issueCmd);
|
|
925
1216
|
}
|