jiradc-cli 1.0.43 → 1.0.45
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 +62 -21
- package/dist/{chunk-3GFKV36K.js → chunk-KEI7DXAL.js} +12 -0
- package/dist/{dist-JXPWCMQP.js → dist-YSXRGQRB.js} +3 -1
- package/dist/index.js +144 -77
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -31,27 +31,68 @@ All commands output JSON. Add `--pretty` to pretty-print.
|
|
|
31
31
|
|
|
32
32
|
### issue
|
|
33
33
|
|
|
34
|
-
| Command | Description
|
|
35
|
-
| ------------------------------------- |
|
|
36
|
-
| `jiradc issue get <key>` | One issue. `--fields` selects fields by id or name, `--all-fields` returns everything Jira holds, `--expand` adds changelog/names
|
|
37
|
-
| `jiradc issue search <jql>` | Issues matching a JQL query. `--all` collects every match (capped at 1,000); `description` and `comment` are excluded unless asked for via `--fields`
|
|
38
|
-
| `jiradc issue create` | The new issue's key and id. Requires `--project`, `--type`, `--summary`; `--fields <json>` sets anything else
|
|
39
|
-
| `jiradc issue update <key>` | Applies field changes. Shortcuts for `--summary`/`--description`/`--priority`/`--assignee`, list flags for `--labels`/`--components`/`--fix-versions` |
|
|
40
|
-
| `jiradc issue delete <key>` | Deletes the issue. `--delete-subtasks` to take its subtasks with it
|
|
41
|
-
| `jiradc issue assign <key>` | Sets the assignee. `--assignee` takes a username, `me`, or `none` to unassign
|
|
42
|
-
| `jiradc issue transition <key>` | Moves the issue
|
|
43
|
-
| `jiradc issue transitions <key>` | The transitions available from the current status, each with the screen fields it requires
|
|
44
|
-
| `jiradc issue createmeta` | The fields a new issue accepts and the values they allow. Requires `--project` and `--type`
|
|
45
|
-
| `jiradc issue editmeta <key>` | The fields this issue's edit screen accepts, with their allowed values
|
|
46
|
-
| `jiradc issue link` | Links two issues. Requires `--from`, `--to` and `--type` (a link type name)
|
|
47
|
-
| `jiradc issue unlink <id>` | Removes one issue link by its link id
|
|
48
|
-
| `jiradc issue link-types` | The link type names this instance accepts, for `issue link --type`
|
|
49
|
-
| `jiradc issue link-epic <keys...>` | Links one or more issues to `--epic <epicKey>`; reports a `failed` array if any link is rejected
|
|
50
|
-
| `jiradc issue changelog <key>` | The issue's change history
|
|
51
|
-
| `jiradc issue batch-changelog <keys>` |
|
|
52
|
-
| `jiradc issue clone <key>` | Creates a duplicate. `--include-links`, `--include-attachments`, `--summary` and `--project` control what carries over
|
|
53
|
-
| `jiradc issue batch-create` | Creates many issues from `--issues <json>` (an array of field objects)
|
|
54
|
-
| `jiradc issue dev-status <key>` | Branches, pull requests, commits and builds linked to the issue. `--detail` for the full records
|
|
34
|
+
| Command | Description |
|
|
35
|
+
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `jiradc issue get <key>` | One issue, with every dimension hanging off it (see below). `--fields` selects fields by id or name, `--all-fields` returns everything Jira holds, `--expand` adds changelog/names |
|
|
37
|
+
| `jiradc issue search <jql>` | Issues matching a JQL query. `--all` collects every match (capped at 1,000); `description` and `comment` are excluded unless asked for via `--fields` |
|
|
38
|
+
| `jiradc issue create` | The new issue's key and id. Requires `--project`, `--type`, `--summary`; `--fields <json>` sets anything else |
|
|
39
|
+
| `jiradc issue update <key>` | Applies field changes and reads them back: `fields` holds the touched fields as Jira now has them. Shortcuts for `--summary`/`--description`/`--priority`/`--assignee`, list flags for `--labels`/`--components`/`--fix-versions` |
|
|
40
|
+
| `jiradc issue delete <key>` | Deletes the issue. `--delete-subtasks` to take its subtasks with it |
|
|
41
|
+
| `jiradc issue assign <key>` | Sets the assignee. `--assignee` takes a username, `me`, or `none` to unassign |
|
|
42
|
+
| `jiradc issue transition <key>` | Moves the issue and reads back `status` and `resolution`. `--to` accepts a transition id, name, or target status, each checked against what is available; `--fields` supplies screen fields, `--comment` a note |
|
|
43
|
+
| `jiradc issue transitions <key>` | The transitions available from the current status, each with the screen fields it requires |
|
|
44
|
+
| `jiradc issue createmeta` | The fields a new issue accepts and the values they allow. Requires `--project` and `--type` |
|
|
45
|
+
| `jiradc issue editmeta <key>` | The fields this issue's edit screen accepts, with their allowed values |
|
|
46
|
+
| `jiradc issue link` | Links two issues. Requires `--from`, `--to` and `--type` (a link type name) |
|
|
47
|
+
| `jiradc issue unlink <id>` | Removes one issue link by its link id |
|
|
48
|
+
| `jiradc issue link-types` | The link type names this instance accepts, for `issue link --type` |
|
|
49
|
+
| `jiradc issue link-epic <keys...>` | Links one or more issues to `--epic <epicKey>`; reports a `failed` array if any link is rejected |
|
|
50
|
+
| `jiradc issue changelog <key>` | The issue's whole change history, every entry. `--limit` caps it; `total` always says how many exist |
|
|
51
|
+
| `jiradc issue batch-changelog <keys>` | The same, for several issues at once, from a comma-separated key list |
|
|
52
|
+
| `jiradc issue clone <key>` | Creates a duplicate. `--include-links`, `--include-attachments`, `--summary` and `--project` control what carries over |
|
|
53
|
+
| `jiradc issue batch-create` | Creates many issues from `--issues <json>` (an array of field objects) |
|
|
54
|
+
| `jiradc issue dev-status <key>` | Branches, pull requests, commits and builds linked to the issue. `--detail` for the full records |
|
|
55
|
+
|
|
56
|
+
#### What a bare `issue get` returns
|
|
57
|
+
|
|
58
|
+
With no `--fields`, one issue comes back with every dimension that hangs off it, out of the
|
|
59
|
+
single request the command already makes:
|
|
60
|
+
|
|
61
|
+
| Default field | What arrives |
|
|
62
|
+
| --------------------------------------------------------------- | -------------------------------------------------------- |
|
|
63
|
+
| `summary` `status` `issuetype` `priority` `labels` `components` | The issue itself |
|
|
64
|
+
| `assignee` `reporter` `created` `updated` | Who and when |
|
|
65
|
+
| `description` | In full — it is the issue, not a dimension of it |
|
|
66
|
+
| `issuelinks` | `{ id, type, key, summary, status, issuetype }` per link |
|
|
67
|
+
| `epic` | The key of the epic this issue sits under, if any |
|
|
68
|
+
| `parent` `subtasks` | `{ key, summary, status, issuetype }` each |
|
|
69
|
+
| `attachment` | `{ files: [{ id, filename, size }], use }` |
|
|
70
|
+
| `comment` | `{ count, use }` — the thread is named, not carried |
|
|
71
|
+
|
|
72
|
+
The rule: **the default names every dimension, and carries it only when carrying it is
|
|
73
|
+
complete.** A link stub is the whole link, so links arrive whole and point at no command. An
|
|
74
|
+
attachment's bytes and a comment's body are not something to inline, so those arrive as an
|
|
75
|
+
inventory plus the command that reads them — the same `use` convention `project get` uses for
|
|
76
|
+
components and versions. A field with nothing in it is absent altogether, pointer included:
|
|
77
|
+
no `attachment` key means there are no attachments, which is readable only because the field
|
|
78
|
+
was asked for.
|
|
79
|
+
|
|
80
|
+
`type` on a link is the phrase true **from this issue's side** — `blocks`, `is blocked by`,
|
|
81
|
+
`relates to` — so the direction needs no decoding and there is no nested `inwardIssue` /
|
|
82
|
+
`outwardIssue`. The link's own id is `linkId`, not `id`, because `id` beside `key` reads as the
|
|
83
|
+
linked issue's id. `--fields issuelinks` returns the same flat shape.
|
|
84
|
+
|
|
85
|
+
Every row above is a `--fields` selector except `epic`, which is an output key: the Epic Link is
|
|
86
|
+
a custom field, so select it by name with `--fields "Epic Link"`.
|
|
87
|
+
|
|
88
|
+
Naming `attachment` or `comment` in `--fields`, or passing `--all-fields`, returns that field
|
|
89
|
+
in full instead. `worklog` and `watches` are not requested at all; ask for them by name.
|
|
90
|
+
|
|
91
|
+
Nothing here costs a second request. Development activity and the change history are separate
|
|
92
|
+
endpoints and stay behind `issue dev-status` and `issue changelog`.
|
|
93
|
+
|
|
94
|
+
`issue search` deliberately keeps none of this — what is one link list on one issue is fifty of
|
|
95
|
+
them on a page of results.
|
|
55
96
|
|
|
56
97
|
#### issue comment
|
|
57
98
|
|
|
@@ -668,6 +668,7 @@ function classifyError(err, opts) {
|
|
|
668
668
|
envelope: {
|
|
669
669
|
error: {
|
|
670
670
|
type: n.type,
|
|
671
|
+
...n.status !== void 0 ? { status: n.status } : {},
|
|
671
672
|
message: n.message,
|
|
672
673
|
recovery: n.recovery,
|
|
673
674
|
retryable: n.retryable,
|
|
@@ -742,9 +743,19 @@ function suggestSubcommand(token, names) {
|
|
|
742
743
|
}
|
|
743
744
|
return best?.name;
|
|
744
745
|
}
|
|
746
|
+
function exitCleanlyOnClosedPipe(stream) {
|
|
747
|
+
stream.on("error", (err) => {
|
|
748
|
+
if (err.code === "EPIPE") {
|
|
749
|
+
process.exit(0);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
throw err;
|
|
753
|
+
});
|
|
754
|
+
}
|
|
745
755
|
async function runCli(program, opts) {
|
|
746
756
|
routeErrors(program);
|
|
747
757
|
attachSubcommandGuards(program);
|
|
758
|
+
exitCleanlyOnClosedPipe(process.stdout);
|
|
748
759
|
try {
|
|
749
760
|
await program.parseAsync();
|
|
750
761
|
} catch (err) {
|
|
@@ -1126,6 +1137,7 @@ export {
|
|
|
1126
1137
|
createErrorHandler,
|
|
1127
1138
|
routeErrors,
|
|
1128
1139
|
attachSubcommandGuards,
|
|
1140
|
+
exitCleanlyOnClosedPipe,
|
|
1129
1141
|
runCli,
|
|
1130
1142
|
assertConventions,
|
|
1131
1143
|
serializeCommandTree,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
dateTime,
|
|
23
23
|
enumArg,
|
|
24
24
|
examples,
|
|
25
|
+
exitCleanlyOnClosedPipe,
|
|
25
26
|
fieldsOption,
|
|
26
27
|
finiteNumber,
|
|
27
28
|
formatOption,
|
|
@@ -48,7 +49,7 @@ import {
|
|
|
48
49
|
text,
|
|
49
50
|
textOrFileOption,
|
|
50
51
|
uuid
|
|
51
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-KEI7DXAL.js";
|
|
52
53
|
export {
|
|
53
54
|
BANNED_FLAGS,
|
|
54
55
|
CliAuthError,
|
|
@@ -73,6 +74,7 @@ export {
|
|
|
73
74
|
dateTime,
|
|
74
75
|
enumArg,
|
|
75
76
|
examples,
|
|
77
|
+
exitCleanlyOnClosedPipe,
|
|
76
78
|
fieldsOption,
|
|
77
79
|
finiteNumber,
|
|
78
80
|
formatOption,
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
subjectArg,
|
|
26
26
|
text,
|
|
27
27
|
textOrFileOption
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-KEI7DXAL.js";
|
|
29
29
|
|
|
30
30
|
// src/program.ts
|
|
31
31
|
import { styleText } from "util";
|
|
@@ -187,6 +187,31 @@ function transformSprint(s) {
|
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// src/utils/constants.ts
|
|
191
|
+
var SEARCH_DEFAULT_FIELDS = [
|
|
192
|
+
"summary",
|
|
193
|
+
"status",
|
|
194
|
+
"assignee",
|
|
195
|
+
"reporter",
|
|
196
|
+
"labels",
|
|
197
|
+
"priority",
|
|
198
|
+
"created",
|
|
199
|
+
"updated",
|
|
200
|
+
"issuetype",
|
|
201
|
+
"components"
|
|
202
|
+
];
|
|
203
|
+
var EPIC_LINK_FIELD_ID = "customfield_10100";
|
|
204
|
+
var DEFAULT_FIELDS = [
|
|
205
|
+
...SEARCH_DEFAULT_FIELDS,
|
|
206
|
+
"description",
|
|
207
|
+
"comment",
|
|
208
|
+
"issuelinks",
|
|
209
|
+
EPIC_LINK_FIELD_ID,
|
|
210
|
+
"parent",
|
|
211
|
+
"subtasks",
|
|
212
|
+
"attachment"
|
|
213
|
+
];
|
|
214
|
+
|
|
190
215
|
// src/utils/transformers/issue.ts
|
|
191
216
|
function issueBrowseUrl(key) {
|
|
192
217
|
return `${jiraBaseUrl()}/browse/${key}`;
|
|
@@ -248,28 +273,24 @@ function transformAttachment(a) {
|
|
|
248
273
|
...author ? { author: transformUser(author) } : {}
|
|
249
274
|
};
|
|
250
275
|
}
|
|
251
|
-
function
|
|
252
|
-
const
|
|
276
|
+
function transformIssueStub(b) {
|
|
277
|
+
const fields = b.fields;
|
|
253
278
|
return {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
...fields ? {
|
|
257
|
-
|
|
258
|
-
...fields.summary !== void 0 ? { summary: fields.summary } : {},
|
|
259
|
-
...fields.status ? { status: transformStatus(fields.status) } : {},
|
|
260
|
-
...fields.priority ? { priority: transformPriority(fields.priority) } : {},
|
|
261
|
-
...fields.issuetype ? { issuetype: transformIssueType(fields.issuetype) } : {}
|
|
262
|
-
}
|
|
263
|
-
} : {}
|
|
279
|
+
key: b.key,
|
|
280
|
+
...fields?.summary !== void 0 ? { summary: fields.summary } : {},
|
|
281
|
+
...fields?.status ? { status: fields.status.name } : {},
|
|
282
|
+
...fields?.issuetype ? { issuetype: fields.issuetype.name } : {}
|
|
264
283
|
};
|
|
265
284
|
}
|
|
266
285
|
function transformIssueLink(l) {
|
|
267
|
-
const
|
|
286
|
+
const other = l.outwardIssue ?? l.inwardIssue;
|
|
268
287
|
return {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
288
|
+
linkId: l.id,
|
|
289
|
+
// A link with an issue on neither end is not something Jira sends. If it
|
|
290
|
+
// ever does, the type's name is the honest answer — a direction phrase with
|
|
291
|
+
// nothing on the far end of it would not be.
|
|
292
|
+
type: other ? l.outwardIssue ? l.type.outward : l.type.inward : l.type.name,
|
|
293
|
+
...other ? transformIssueStub(other) : {}
|
|
273
294
|
};
|
|
274
295
|
}
|
|
275
296
|
function customFieldNames(names, fields) {
|
|
@@ -279,9 +300,9 @@ function customFieldNames(names, fields) {
|
|
|
279
300
|
);
|
|
280
301
|
return kept.length > 0 ? Object.fromEntries(kept) : void 0;
|
|
281
302
|
}
|
|
282
|
-
function transformIssue(issue) {
|
|
303
|
+
function transformIssue(issue, opts = {}) {
|
|
283
304
|
const { self: _self, expand: _expand, names, schema: _schema, fields, ...rest } = issue;
|
|
284
|
-
const transformed = transformIssueFields(fields, issue.key);
|
|
305
|
+
const transformed = transformIssueFields(fields, issue.key, opts);
|
|
285
306
|
const fieldNames = customFieldNames(names, transformed);
|
|
286
307
|
return {
|
|
287
308
|
...rest,
|
|
@@ -323,6 +344,12 @@ function pruneSentinels(fields) {
|
|
|
323
344
|
if (watches && watches.watchCount === 0) delete out.watches;
|
|
324
345
|
return out;
|
|
325
346
|
}
|
|
347
|
+
function nameEpicLink(fields) {
|
|
348
|
+
const value = fields[EPIC_LINK_FIELD_ID];
|
|
349
|
+
if (typeof value !== "string" || !ISSUE_KEY_PATTERN.test(value)) return fields;
|
|
350
|
+
const { [EPIC_LINK_FIELD_ID]: _epicLink, ...rest } = fields;
|
|
351
|
+
return { ...rest, epic: value };
|
|
352
|
+
}
|
|
326
353
|
var DEV_STATUS_DUMP = /com\.atlassian\.jira\.plugin\.devstatus/;
|
|
327
354
|
function collapseDevStatus(fields, key) {
|
|
328
355
|
const out = { ...fields };
|
|
@@ -360,7 +387,7 @@ function collapseIssueRefs(fields) {
|
|
|
360
387
|
}
|
|
361
388
|
return out;
|
|
362
389
|
}
|
|
363
|
-
function transformIssueFields(fields, key) {
|
|
390
|
+
function transformIssueFields(fields, key, opts) {
|
|
364
391
|
if (!fields) return {};
|
|
365
392
|
const {
|
|
366
393
|
// shaped sub-entities (recursed individually)
|
|
@@ -387,7 +414,7 @@ function transformIssueFields(fields, key) {
|
|
|
387
414
|
// everything else: optional scalars, customfield_*, etc. — gets compacted
|
|
388
415
|
...rest
|
|
389
416
|
} = fields;
|
|
390
|
-
const compacted = collapseDevStatus(collapseIssueRefs(pruneSentinels(compactRecord(rest))), key);
|
|
417
|
+
const compacted = collapseDevStatus(collapseIssueRefs(nameEpicLink(pruneSentinels(compactRecord(rest)))), key);
|
|
391
418
|
const components = compacted.components;
|
|
392
419
|
return {
|
|
393
420
|
...compacted,
|
|
@@ -406,13 +433,21 @@ function transformIssueFields(fields, key) {
|
|
|
406
433
|
...fixVersions && fixVersions.length > 0 ? { fixVersions: fixVersions.map(transformVersion) } : {},
|
|
407
434
|
...versions2 && versions2.length > 0 ? { versions: versions2.map(transformVersion) } : {},
|
|
408
435
|
...issuelinks && issuelinks.length > 0 ? { issuelinks: issuelinks.map(transformIssueLink) } : {},
|
|
409
|
-
...subtasks && subtasks.length > 0 ? { subtasks: subtasks.map(
|
|
410
|
-
...parent ? { parent:
|
|
436
|
+
...subtasks && subtasks.length > 0 ? { subtasks: subtasks.map(transformIssueStub) } : {},
|
|
437
|
+
...parent ? { parent: transformIssueStub(parent) } : {},
|
|
411
438
|
// Drop empty comment / worklog containers entirely. The default Jira
|
|
412
439
|
// search response includes both wrappers on every issue regardless of
|
|
413
440
|
// count; on a 25-issue page that's 25 × 2 empty objects of pure noise.
|
|
414
|
-
|
|
415
|
-
|
|
441
|
+
// The thread is named, not carried — see `JiraCommentSummaryOutput`.
|
|
442
|
+
//
|
|
443
|
+
// The compact branch gates on `total`, not on the array, because `total` is
|
|
444
|
+
// the only thing it reads. A deployment that caps embedded comments answers
|
|
445
|
+
// with a populated total and an empty `comments`; gating on the array there
|
|
446
|
+
// would drop the key, and a missing key is documented to mean "there are
|
|
447
|
+
// none" — reporting a long thread as no thread, in the one direction the
|
|
448
|
+
// caller was told to trust.
|
|
449
|
+
...comment && (opts.compact ? comment.total > 0 : comment.comments.length > 0) ? {
|
|
450
|
+
comment: opts.compact ? { count: comment.total, use: `jiradc issue comment list ${key}` } : {
|
|
416
451
|
comments: comment.comments.map(transformComment),
|
|
417
452
|
maxResults: comment.maxResults,
|
|
418
453
|
total: comment.total,
|
|
@@ -427,9 +462,35 @@ function transformIssueFields(fields, key) {
|
|
|
427
462
|
startAt: worklog.startAt
|
|
428
463
|
}
|
|
429
464
|
} : {},
|
|
430
|
-
|
|
465
|
+
// `attachment` is in the default field set to say that files are attached —
|
|
466
|
+
// not to carry their records. An empty list yields no key at all rather
|
|
467
|
+
// than a pointer to nothing: the absence is the answer, and it is readable
|
|
468
|
+
// as one only because the field was asked for.
|
|
469
|
+
...attachment && attachment.length > 0 ? {
|
|
470
|
+
attachment: opts.compact ? {
|
|
471
|
+
files: attachment.map(({ id, filename, size }) => ({ id, filename, size })),
|
|
472
|
+
// Both commands spelled in full. Abbreviating the second to
|
|
473
|
+
// `download …` saved 24 characters and invited a caller to run
|
|
474
|
+
// exactly that, which is not a command.
|
|
475
|
+
use: `jiradc issue attachment download-all ${key} --output <dir> (one file: jiradc issue attachment download ${key} --attachment-id <id> --output <path>)`
|
|
476
|
+
} : attachment.map(transformAttachment)
|
|
477
|
+
} : {}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/utils/transformers/changelog.ts
|
|
482
|
+
function transformEntry(h) {
|
|
483
|
+
return {
|
|
484
|
+
id: h.id,
|
|
485
|
+
...h.author ? { author: transformUser(h.author) } : {},
|
|
486
|
+
created: h.created,
|
|
487
|
+
items: h.items
|
|
431
488
|
};
|
|
432
489
|
}
|
|
490
|
+
function transformChangelog(raw, opts = {}) {
|
|
491
|
+
const histories = opts.limit === void 0 ? raw.histories : raw.histories.slice(0, opts.limit);
|
|
492
|
+
return { total: raw.total, values: histories.map(transformEntry) };
|
|
493
|
+
}
|
|
433
494
|
|
|
434
495
|
// src/utils/transformers/transition.ts
|
|
435
496
|
function slimAllowedValue2(raw) {
|
|
@@ -1030,15 +1091,19 @@ function registerAttachmentCommands(parent) {
|
|
|
1030
1091
|
|
|
1031
1092
|
// src/commands/issue/batch-changelog.ts
|
|
1032
1093
|
function batchChangelog(parent) {
|
|
1033
|
-
const cmd = parent.command("batch-changelog").description("Get
|
|
1094
|
+
const cmd = parent.command("batch-changelog").description("Get the whole change history of several issues at once").argument("<keys>", "Comma-separated issue keys", keyList).option(
|
|
1095
|
+
"--limit <number>",
|
|
1096
|
+
"Return at most this many entries per issue, oldest first (default: every entry)",
|
|
1097
|
+
positiveInt
|
|
1098
|
+
);
|
|
1034
1099
|
examples(cmd, ["PROJ-1,PROJ-2,PROJ-3", "PROJ-123,PROJ-124 --limit 10"]);
|
|
1035
1100
|
cmd.action(async (keys, opts) => {
|
|
1036
1101
|
const client = getClient();
|
|
1037
1102
|
const entries = await Promise.all(
|
|
1038
1103
|
keys.map(async (key) => {
|
|
1039
1104
|
try {
|
|
1040
|
-
const data = await client.issues.getChangelog({ issueKeyOrId: key
|
|
1041
|
-
return [key, data];
|
|
1105
|
+
const data = await client.issues.getChangelog({ issueKeyOrId: key });
|
|
1106
|
+
return [key, transformChangelog(data, { limit: opts.limit })];
|
|
1042
1107
|
} catch (error) {
|
|
1043
1108
|
return [key, { error: error instanceof Error ? error.message : "Unknown error" }];
|
|
1044
1109
|
}
|
|
@@ -1074,12 +1139,12 @@ function batchCreate(parent) {
|
|
|
1074
1139
|
|
|
1075
1140
|
// src/commands/issue/changelog.ts
|
|
1076
1141
|
function changelog(parent) {
|
|
1077
|
-
const cmd = parent.command("changelog").description("Get
|
|
1142
|
+
const cmd = parent.command("changelog").description("Get the whole change history of an issue (several issues at once: batch-changelog)").argument("<key>", "Issue key", issueKey).option("--limit <number>", "Return at most this many entries, oldest first (default: every entry)", positiveInt);
|
|
1078
1143
|
examples(cmd, ["PROJ-123", "PROJ-123 --limit 10"]);
|
|
1079
1144
|
cmd.action(async (key, opts) => {
|
|
1080
1145
|
const client = getClient();
|
|
1081
|
-
const result = await client.issues.getChangelog({ issueKeyOrId: key
|
|
1082
|
-
output(result);
|
|
1146
|
+
const result = await client.issues.getChangelog({ issueKeyOrId: key });
|
|
1147
|
+
output(transformChangelog(result, { limit: opts.limit }));
|
|
1083
1148
|
});
|
|
1084
1149
|
}
|
|
1085
1150
|
|
|
@@ -1437,21 +1502,6 @@ function editmeta(parent) {
|
|
|
1437
1502
|
});
|
|
1438
1503
|
}
|
|
1439
1504
|
|
|
1440
|
-
// src/utils/constants.ts
|
|
1441
|
-
var SEARCH_DEFAULT_FIELDS = [
|
|
1442
|
-
"summary",
|
|
1443
|
-
"status",
|
|
1444
|
-
"assignee",
|
|
1445
|
-
"reporter",
|
|
1446
|
-
"labels",
|
|
1447
|
-
"priority",
|
|
1448
|
-
"created",
|
|
1449
|
-
"updated",
|
|
1450
|
-
"issuetype",
|
|
1451
|
-
"components"
|
|
1452
|
-
];
|
|
1453
|
-
var DEFAULT_FIELDS = [...SEARCH_DEFAULT_FIELDS, "description", "comment"];
|
|
1454
|
-
|
|
1455
1505
|
// src/utils/field-selectors.ts
|
|
1456
1506
|
var WILDCARD = "*";
|
|
1457
1507
|
var NEGATION = "-";
|
|
@@ -1555,10 +1605,13 @@ async function loadFieldNameIndex(client) {
|
|
|
1555
1605
|
}
|
|
1556
1606
|
async function selectFields(client, opts, defaults) {
|
|
1557
1607
|
if (opts.allFields === true) return void 0;
|
|
1558
|
-
if (opts
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1608
|
+
if (usesDefaultFields(opts)) return defaults;
|
|
1609
|
+
return resolveFieldSelectors(parseFieldSelectors(opts.fields), await loadFieldNameIndex(client));
|
|
1610
|
+
}
|
|
1611
|
+
function usesDefaultFields(opts) {
|
|
1612
|
+
if (opts.allFields === true) return false;
|
|
1613
|
+
if (opts.fields === void 0) return true;
|
|
1614
|
+
return parseFieldSelectors(opts.fields).length === 0;
|
|
1562
1615
|
}
|
|
1563
1616
|
|
|
1564
1617
|
// src/commands/issue/get.ts
|
|
@@ -1578,7 +1631,7 @@ function get4(parent) {
|
|
|
1578
1631
|
fields,
|
|
1579
1632
|
expand: opts.expand
|
|
1580
1633
|
});
|
|
1581
|
-
output(transformIssue(result));
|
|
1634
|
+
output(transformIssue(result, { compact: usesDefaultFields(opts) }));
|
|
1582
1635
|
});
|
|
1583
1636
|
}
|
|
1584
1637
|
|
|
@@ -1590,7 +1643,7 @@ function linkEpic(parent) {
|
|
|
1590
1643
|
const client = getClient();
|
|
1591
1644
|
const results = await Promise.allSettled(
|
|
1592
1645
|
keys.map(
|
|
1593
|
-
(key) => client.issues.update({ issueKeyOrId: key, fields: {
|
|
1646
|
+
(key) => client.issues.update({ issueKeyOrId: key, fields: { [EPIC_LINK_FIELD_ID]: opts.epic } }).then(() => key)
|
|
1594
1647
|
)
|
|
1595
1648
|
);
|
|
1596
1649
|
const linked = [];
|
|
@@ -1711,26 +1764,41 @@ function search2(parent) {
|
|
|
1711
1764
|
return;
|
|
1712
1765
|
}
|
|
1713
1766
|
const every = await fetchEveryPage(page, { pageSize: plan.pageSize, start: plan.start });
|
|
1714
|
-
output({ ...every, issues: every.issues.map(transformIssue) });
|
|
1767
|
+
output({ ...every, issues: every.issues.map((i) => transformIssue(i)) });
|
|
1715
1768
|
}
|
|
1716
1769
|
);
|
|
1717
1770
|
}
|
|
1718
1771
|
|
|
1719
1772
|
// src/commands/issue/transition.ts
|
|
1720
1773
|
import { z as z3 } from "zod";
|
|
1721
|
-
|
|
1774
|
+
|
|
1775
|
+
// src/utils/read-back.ts
|
|
1776
|
+
async function readBack(client, key, fields) {
|
|
1777
|
+
try {
|
|
1778
|
+
const issue = await client.issues.get({ issueKeyOrId: key, fields });
|
|
1779
|
+
return { fields: transformIssue(issue).fields };
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1782
|
+
return {
|
|
1783
|
+
warning: `The write succeeded, but reading ${key} back failed: ${message}. Run jiradc issue get ${key} to see its current state.`
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// src/utils/resolve-transition.ts
|
|
1722
1789
|
function resolveTransitionId(to, transitions2) {
|
|
1790
|
+
const available = transitions2.map((t) => ({ id: t.id, name: t.name, to: t.to }));
|
|
1791
|
+
const recovery = "Re-run --to with one of the listed transitions (by id, name, or target status).";
|
|
1792
|
+
if (/^\d+$/.test(to)) {
|
|
1793
|
+
if (transitions2.some((t) => t.id === to)) return to;
|
|
1794
|
+
throw new CliUsageError(`Transition id ${to} is not available from the current status`, recovery, { available });
|
|
1795
|
+
}
|
|
1723
1796
|
const target = to.toLowerCase();
|
|
1724
1797
|
const byName = transitions2.filter((t) => t.name.toLowerCase() === target);
|
|
1725
1798
|
const matches = byName.length > 0 ? byName : transitions2.filter((t) => t.to.toLowerCase() === target);
|
|
1726
1799
|
if (matches.length === 1) return matches[0].id;
|
|
1727
|
-
const available = transitions2.map((t) => ({ id: t.id, name: t.name, to: t.to }));
|
|
1728
1800
|
if (matches.length === 0) {
|
|
1729
|
-
throw new CliUsageError(
|
|
1730
|
-
`Transition "${to}" not available from current status`,
|
|
1731
|
-
"Re-run --to with one of the listed transitions (by name, id, or target status).",
|
|
1732
|
-
{ available }
|
|
1733
|
-
);
|
|
1801
|
+
throw new CliUsageError(`Transition "${to}" not available from current status`, recovery, { available });
|
|
1734
1802
|
}
|
|
1735
1803
|
const ids = matches.map((t) => t.id).join(", ");
|
|
1736
1804
|
throw new CliUsageError(
|
|
@@ -1739,6 +1807,9 @@ function resolveTransitionId(to, transitions2) {
|
|
|
1739
1807
|
{ available }
|
|
1740
1808
|
);
|
|
1741
1809
|
}
|
|
1810
|
+
|
|
1811
|
+
// src/commands/issue/transition.ts
|
|
1812
|
+
var fieldsSchema2 = z3.record(z3.unknown());
|
|
1742
1813
|
function transition(parent) {
|
|
1743
1814
|
const cmd = parent.command("transition").description("Transition issue to a new status").argument("<key>", "Issue key", issueKey).requiredOption("--to <idOrName>", "Transition ID, transition name, or target status name (case-insensitive)", text).option(
|
|
1744
1815
|
"--fields <json>",
|
|
@@ -1757,18 +1828,11 @@ function transition(parent) {
|
|
|
1757
1828
|
async (key, opts) => {
|
|
1758
1829
|
const comment = resolveTextOrFile(opts, "comment", { required: false });
|
|
1759
1830
|
const client = getClient();
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
transitionId = opts.to;
|
|
1763
|
-
} else {
|
|
1764
|
-
const raw = await client.issues.getTransitions({
|
|
1765
|
-
issueKeyOrId: key,
|
|
1766
|
-
expand: "transitions.fields"
|
|
1767
|
-
});
|
|
1768
|
-
transitionId = resolveTransitionId(opts.to, transformTransitions(raw));
|
|
1769
|
-
}
|
|
1831
|
+
const raw = await client.issues.getTransitions({ issueKeyOrId: key, expand: "transitions.fields" });
|
|
1832
|
+
const transitionId = resolveTransitionId(opts.to, transformTransitions(raw));
|
|
1770
1833
|
await client.issues.transition({ issueKeyOrId: key, transitionId, fields: opts.fields, comment });
|
|
1771
|
-
|
|
1834
|
+
const after = await readBack(client, key, ["status", "resolution", ...Object.keys(opts.fields ?? {})]);
|
|
1835
|
+
output({ transitioned: true, issue: transformIssueRef(key), ...after });
|
|
1772
1836
|
}
|
|
1773
1837
|
);
|
|
1774
1838
|
}
|
|
@@ -1903,6 +1967,8 @@ function update3(parent) {
|
|
|
1903
1967
|
notifyUsers: opts.notifyUsers
|
|
1904
1968
|
});
|
|
1905
1969
|
}
|
|
1970
|
+
const touched = [.../* @__PURE__ */ new Set([...Object.keys(fields), ...Object.keys(updateOps)])];
|
|
1971
|
+
const after = didFieldUpdate ? await readBack(client, key, touched) : {};
|
|
1906
1972
|
const uploaded = [];
|
|
1907
1973
|
if (opts.attachments) {
|
|
1908
1974
|
const filePaths = opts.attachments.split(",").map((p) => p.trim()).filter(Boolean);
|
|
@@ -1915,8 +1981,9 @@ function update3(parent) {
|
|
|
1915
1981
|
}
|
|
1916
1982
|
output({
|
|
1917
1983
|
...didFieldUpdate && { updated: true },
|
|
1918
|
-
|
|
1919
|
-
|
|
1984
|
+
issue: transformIssueRef(key),
|
|
1985
|
+
...after,
|
|
1986
|
+
...uploaded.length > 0 && { attached: true, attachments: uploaded }
|
|
1920
1987
|
});
|
|
1921
1988
|
});
|
|
1922
1989
|
}
|
|
@@ -2535,7 +2602,7 @@ function list9(parent) {
|
|
|
2535
2602
|
examples(cmd, ["", "--query billing", "--limit 50", "--all --permissions"]);
|
|
2536
2603
|
cmd.action(async (opts) => {
|
|
2537
2604
|
if (opts.all && process.argv.includes("--limit")) {
|
|
2538
|
-
const { CliUsageError: CliUsageError2 } = await import("./dist-
|
|
2605
|
+
const { CliUsageError: CliUsageError2 } = await import("./dist-YSXRGQRB.js");
|
|
2539
2606
|
throw new CliUsageError2("--all and --limit ask for opposite things; pass one or the other");
|
|
2540
2607
|
}
|
|
2541
2608
|
const client = getClient();
|
|
@@ -2757,7 +2824,7 @@ function search3(parent) {
|
|
|
2757
2824
|
total: res.total,
|
|
2758
2825
|
startAt: res.startAt,
|
|
2759
2826
|
count: res.issues?.length ?? 0,
|
|
2760
|
-
issues: (res.issues ?? []).map(transformIssue)
|
|
2827
|
+
issues: (res.issues ?? []).map((i) => transformIssue(i))
|
|
2761
2828
|
});
|
|
2762
2829
|
}
|
|
2763
2830
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiradc-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.45",
|
|
4
4
|
"publish": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"commander": "^13.1.0",
|
|
15
15
|
"zod": "^3.25.67",
|
|
16
|
-
"jira-data-center-client": "1.0.
|
|
16
|
+
"jira-data-center-client": "1.0.48"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "24.10.4",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"tsx": "^4.19.2",
|
|
24
24
|
"typescript": "^5.7.2",
|
|
25
25
|
"vitest": "^4.0.16",
|
|
26
|
+
"config-typescript": "0.0.0",
|
|
26
27
|
"cli-utils": "1.0.0",
|
|
27
|
-
"config-eslint": "0.0.0"
|
|
28
|
-
"config-typescript": "0.0.0"
|
|
28
|
+
"config-eslint": "0.0.0"
|
|
29
29
|
},
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=22.0.0"
|