jiradc-cli 1.0.32 → 1.0.34
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 +65 -59
- package/dist/index.js +491 -134
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,6 +17,11 @@ export JIRA_TOKEN="your-personal-access-token" # HTTP Access Token from Jira
|
|
|
17
17
|
|
|
18
18
|
## Breaking changes in this release
|
|
19
19
|
|
|
20
|
+
- **`field options` is removed.** It called a Jira 9 endpoint and returned 404 for every field on Jira 8.x. Jira scopes a custom field's options to a context, so there is no context-free replacement — use `issue createmeta --project <key> --type <name>` (create screen), `issue editmeta <key>` (edit screen), or `issue transitions <key>` (transition screen).
|
|
21
|
+
- **`issue transitions` no longer reports `required`** on screen fields. Jira reports field-configuration requiredness there, which cannot see the workflow validator that actually gates the transition, so it returned `false` for fields that then rejected the transition. Supply everything in a transition's `fields` block. (`required` from `createmeta`/`editmeta` reflects field configuration and is reliable.)
|
|
22
|
+
- **`issue get` / `issue search` return issue-picker custom fields as a bare key string** (or an array of keys) instead of the whole nested issue. `fields.customfield_NNNNN.key` becomes `fields.customfield_NNNNN`.
|
|
23
|
+
- **`issue create --custom-fields` is now `--fields`**, matching `issue update`. The old name still works as a deprecated alias.
|
|
24
|
+
- **Argument mistakes now exit 2/3/6** (`usage`/`not_found`/`auth`) instead of 1.
|
|
20
25
|
- `issue transitions` now returns a bare JSON array of transitions (previously wrapped in `{ transitions: [...] }`). Each transition is reduced to `{ id, name, to, fields? }` — verbose fields like `to.iconUrl`, `to.statusCategory`, `isGlobal`, `isInitial`, `isConditional`, `hasScreen` are stripped.
|
|
21
26
|
- `component list` now requires `--project <key>` instead of a positional `<projectKey>` argument.
|
|
22
27
|
- `issue link-epic` is now variadic on the issue argument: `link-epic <issueKey...> --epic <epicKey>`. Single-issue calls keep working.
|
|
@@ -27,88 +32,89 @@ All commands output JSON. Add `--pretty` to pretty-print.
|
|
|
27
32
|
|
|
28
33
|
### issue
|
|
29
34
|
|
|
30
|
-
| Command
|
|
31
|
-
|
|
32
|
-
| `jiradc issue get <key>`
|
|
33
|
-
| `jiradc issue search <jql>`
|
|
34
|
-
| `jiradc issue create`
|
|
35
|
-
| `jiradc issue update <key>`
|
|
36
|
-
| `jiradc issue delete <key>`
|
|
37
|
-
| `jiradc issue assign <key> <user>`
|
|
38
|
-
| `jiradc issue transition <key>`
|
|
39
|
-
| `jiradc issue transitions <key>`
|
|
40
|
-
| `jiradc issue
|
|
41
|
-
| `jiradc issue
|
|
42
|
-
| `jiradc issue comment
|
|
43
|
-
| `jiradc issue
|
|
44
|
-
| `jiradc issue
|
|
45
|
-
| `jiradc issue link
|
|
46
|
-
| `jiradc issue
|
|
47
|
-
| `jiradc issue
|
|
48
|
-
| `jiradc issue
|
|
49
|
-
| `jiradc issue worklog
|
|
50
|
-
| `jiradc issue worklog
|
|
51
|
-
| `jiradc issue
|
|
52
|
-
| `jiradc issue
|
|
53
|
-
| `jiradc issue
|
|
54
|
-
| `jiradc issue batch-
|
|
55
|
-
| `jiradc issue
|
|
35
|
+
| Command | Description |
|
|
36
|
+
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| `jiradc issue get <key>` | Get issue details (`--fields` to select, `--expand` for changelog/transitions) |
|
|
38
|
+
| `jiradc issue search <jql>` | Search issues with JQL |
|
|
39
|
+
| `jiradc issue create` | Create an issue (`--project`, `--type`, `--summary`, `--description`, `--custom-fields`) |
|
|
40
|
+
| `jiradc issue update <key>` | Update an issue (`--summary`, `--description`, `--priority`, `--assignee`, `--labels`, `--components`, `--fix-versions`, or `--fields` JSON) |
|
|
41
|
+
| `jiradc issue delete <key>` | Delete an issue |
|
|
42
|
+
| `jiradc issue assign <key> <user>` | Assign issue (user can be a username, `me`, or `none` to unassign) |
|
|
43
|
+
| `jiradc issue transition <key>` | Transition issue to a new status (`--to` accepts ID or status name, `--comment` to add a note) |
|
|
44
|
+
| `jiradc issue transitions <key>` | List available transitions (with any fields their screens require) |
|
|
45
|
+
| `jiradc issue createmeta` | Fields a new issue accepts, with allowed values (`--project`, `--type`) |
|
|
46
|
+
| `jiradc issue editmeta <key>` | Fields an existing issue's edit screen accepts, with allowed values |
|
|
47
|
+
| `jiradc issue comment add <key>` | Add a comment (`--body`) |
|
|
48
|
+
| `jiradc issue comment edit <key>` | Edit a comment (`--id`, `--body`) |
|
|
49
|
+
| `jiradc issue comment delete <key>` | Delete a comment (`--id`) |
|
|
50
|
+
| `jiradc issue link <key> <targetKey>` | Link two issues (`--type` link type name) |
|
|
51
|
+
| `jiradc issue unlink <linkId>` | Remove a link |
|
|
52
|
+
| `jiradc issue link-types` | List available link types |
|
|
53
|
+
| `jiradc issue link-epic <keys...>` | Link one or more issues to an epic (`--epic <epicKey>`) |
|
|
54
|
+
| `jiradc issue worklog add <key>` | Add a work log entry (`--time`, `--comment`, `--started`) |
|
|
55
|
+
| `jiradc issue worklog list <key>` | Get work log entries |
|
|
56
|
+
| `jiradc issue worklog edit <key>` | Update a work log entry (`--id`, `--time`, `--comment`, `--started`, `--adjust-estimate`, `--new-estimate`) |
|
|
57
|
+
| `jiradc issue worklog delete <key>` | Delete a work log entry (`--id`, `--adjust-estimate`, `--new-estimate`, `--increase-by`) |
|
|
58
|
+
| `jiradc issue changelog <key>` | Get issue changelog |
|
|
59
|
+
| `jiradc issue batch-changelog` | Get changelog for multiple issues (`--keys`) |
|
|
60
|
+
| `jiradc issue clone <key>` | Clone an issue with subtasks |
|
|
61
|
+
| `jiradc issue batch-create` | Create multiple issues from JSON |
|
|
62
|
+
| `jiradc issue dev-status <key>` | Get development status (branches, PRs, commits) |
|
|
56
63
|
|
|
57
64
|
#### issue attachment
|
|
58
65
|
|
|
59
|
-
| Command
|
|
60
|
-
|
|
61
|
-
| `jiradc issue attachment list <key>`
|
|
66
|
+
| Command | Description |
|
|
67
|
+
| ------------------------------------------------------- | ------------------------------ |
|
|
68
|
+
| `jiradc issue attachment list <key>` | List attachments |
|
|
62
69
|
| `jiradc issue attachment download <key> <attachmentId>` | Download a specific attachment |
|
|
63
|
-
| `jiradc issue attachment download-all <key>`
|
|
64
|
-
| `jiradc issue attachment upload <key> <files...>`
|
|
65
|
-
| `jiradc issue attachment delete <attachmentId>`
|
|
70
|
+
| `jiradc issue attachment download-all <key>` | Download all attachments |
|
|
71
|
+
| `jiradc issue attachment upload <key> <files...>` | Upload files |
|
|
72
|
+
| `jiradc issue attachment delete <attachmentId>` | Delete an attachment |
|
|
66
73
|
|
|
67
74
|
### project
|
|
68
75
|
|
|
69
|
-
| Command
|
|
70
|
-
|
|
71
|
-
| `jiradc project list`
|
|
76
|
+
| Command | Description |
|
|
77
|
+
| ------------------------------- | --------------------------- |
|
|
78
|
+
| `jiradc project list` | List projects |
|
|
72
79
|
| `jiradc project versions <key>` | List versions for a project |
|
|
73
80
|
|
|
74
81
|
### component
|
|
75
82
|
|
|
76
|
-
| Command
|
|
77
|
-
|
|
78
|
-
| `jiradc component list`
|
|
79
|
-
| `jiradc component get <id>`
|
|
80
|
-
| `jiradc component create`
|
|
81
|
-
| `jiradc component update <id>`
|
|
82
|
-
| `jiradc component issue-count <id>` | Get the number of issues using a component
|
|
83
|
+
| Command | Description |
|
|
84
|
+
| ----------------------------------- | --------------------------------------------------------------------- |
|
|
85
|
+
| `jiradc component list` | List components for a project (`--project <key>`) |
|
|
86
|
+
| `jiradc component get <id>` | Get a component by ID |
|
|
87
|
+
| `jiradc component create` | Create a component (`--project`, `--name`, `--description`, `--lead`) |
|
|
88
|
+
| `jiradc component update <id>` | Update a component |
|
|
89
|
+
| `jiradc component issue-count <id>` | Get the number of issues using a component |
|
|
83
90
|
|
|
84
91
|
### board
|
|
85
92
|
|
|
86
|
-
| Command
|
|
87
|
-
|
|
88
|
-
| `jiradc board list`
|
|
89
|
-
| `jiradc board issues <boardId>` | Get issues on a board
|
|
93
|
+
| Command | Description |
|
|
94
|
+
| ------------------------------- | ------------------------------------------------------------------ |
|
|
95
|
+
| `jiradc board list` | List boards (`--project`, `--type`: scrum/kanban/simple, `--name`) |
|
|
96
|
+
| `jiradc board issues <boardId>` | Get issues on a board |
|
|
90
97
|
|
|
91
98
|
### sprint
|
|
92
99
|
|
|
93
|
-
| Command
|
|
94
|
-
|
|
95
|
-
| `jiradc sprint list <boardId>`
|
|
96
|
-
| `jiradc sprint issues <boardId> <sprintId>` | Get issues in a sprint
|
|
97
|
-
| `jiradc sprint create <boardId>`
|
|
98
|
-
| `jiradc sprint update <sprintId>`
|
|
99
|
-
| `jiradc sprint delete <sprintId>`
|
|
100
|
+
| Command | Description |
|
|
101
|
+
| ------------------------------------------- | --------------------------------------------------- |
|
|
102
|
+
| `jiradc sprint list <boardId>` | List sprints (`--state`: future/active/closed) |
|
|
103
|
+
| `jiradc sprint issues <boardId> <sprintId>` | Get issues in a sprint |
|
|
104
|
+
| `jiradc sprint create <boardId>` | Create a sprint |
|
|
105
|
+
| `jiradc sprint update <sprintId>` | Update a sprint |
|
|
106
|
+
| `jiradc sprint delete <sprintId>` | Delete a sprint (returns its issues to the backlog) |
|
|
100
107
|
|
|
101
108
|
### field
|
|
102
109
|
|
|
103
|
-
| Command
|
|
104
|
-
|
|
105
|
-
| `jiradc field search
|
|
106
|
-
| `jiradc field options <fieldKey>` | Get allowed values for a field (`--project`, `--issue-type`) |
|
|
110
|
+
| Command | Description |
|
|
111
|
+
| ------------------------------- | ----------------------------------------------------------------------------- |
|
|
112
|
+
| `jiradc field search <keyword>` | Search for fields by name or ID (returns name + type, **not** allowed values) |
|
|
107
113
|
|
|
108
114
|
### user
|
|
109
115
|
|
|
110
|
-
| Command
|
|
111
|
-
|
|
116
|
+
| Command | Description |
|
|
117
|
+
| ---------------- | --------------------- |
|
|
112
118
|
| `jiradc user me` | Get current user info |
|
|
113
119
|
|
|
114
120
|
## Pagination
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,45 @@
|
|
|
4
4
|
import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { join } from "path";
|
|
7
|
+
var DEFAULT_TTL = 36e5;
|
|
8
|
+
function getCacheDir(name) {
|
|
9
|
+
return join(homedir(), ".cache", name);
|
|
10
|
+
}
|
|
11
|
+
function getCachePath(name, key) {
|
|
12
|
+
return join(getCacheDir(name), `${key}.json`);
|
|
13
|
+
}
|
|
14
|
+
function cacheGet(options, key) {
|
|
15
|
+
const ttl = options.ttl ?? DEFAULT_TTL;
|
|
16
|
+
const path = getCachePath(options.name, key);
|
|
17
|
+
try {
|
|
18
|
+
const stat = statSync(path);
|
|
19
|
+
if (Date.now() - stat.mtimeMs > ttl)
|
|
20
|
+
return null;
|
|
21
|
+
const raw = readFileSync(path, "utf-8");
|
|
22
|
+
const entry = JSON.parse(raw);
|
|
23
|
+
return entry.data;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function cacheSet(options, key, data) {
|
|
29
|
+
const dir = getCacheDir(options.name);
|
|
30
|
+
const path = getCachePath(options.name, key);
|
|
31
|
+
const entry = { data, timestamp: Date.now() };
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
writeFileSync(path, JSON.stringify(entry));
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function cacheGetOrFetch(options, key, fetcher) {
|
|
39
|
+
const cached = cacheGet(options, key);
|
|
40
|
+
if (cached !== null)
|
|
41
|
+
return cached;
|
|
42
|
+
const data = await fetcher();
|
|
43
|
+
cacheSet(options, key, data);
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
7
46
|
|
|
8
47
|
// ../../cli-utils/dist/bootstrap.js
|
|
9
48
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -239,6 +278,7 @@ var CliAuthError = class extends Error {
|
|
|
239
278
|
};
|
|
240
279
|
var NOT_FOUND_RECOVERY = "Verify the id/key, then check your access.";
|
|
241
280
|
var NOT_FOUND_MESSAGE = "Not found: it may not exist, or you may not have permission to see it";
|
|
281
|
+
var USAGE_RECOVERY = "Check the command syntax and flags; run the command with --help.";
|
|
242
282
|
var CliNotFoundError = class extends Error {
|
|
243
283
|
recovery;
|
|
244
284
|
constructor(message, recovery = NOT_FOUND_RECOVERY) {
|
|
@@ -247,6 +287,16 @@ var CliNotFoundError = class extends Error {
|
|
|
247
287
|
this.recovery = recovery;
|
|
248
288
|
}
|
|
249
289
|
};
|
|
290
|
+
var CliUsageError = class extends Error {
|
|
291
|
+
recovery;
|
|
292
|
+
detail;
|
|
293
|
+
constructor(message, recovery = USAGE_RECOVERY, detail) {
|
|
294
|
+
super(message);
|
|
295
|
+
this.name = "CliUsageError";
|
|
296
|
+
this.recovery = recovery;
|
|
297
|
+
this.detail = detail;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
250
300
|
var SubcommandRequiredError = class extends Error {
|
|
251
301
|
subcommands;
|
|
252
302
|
constructor(commandPath3, subcommands) {
|
|
@@ -255,6 +305,16 @@ var SubcommandRequiredError = class extends Error {
|
|
|
255
305
|
this.subcommands = subcommands;
|
|
256
306
|
}
|
|
257
307
|
};
|
|
308
|
+
var UnknownSubcommandError = class extends Error {
|
|
309
|
+
subcommands;
|
|
310
|
+
suggestion;
|
|
311
|
+
constructor(commandPath3, token, subcommands, suggestion) {
|
|
312
|
+
super(`'${token}' is not a subcommand of '${commandPath3}'`);
|
|
313
|
+
this.name = "UnknownSubcommandError";
|
|
314
|
+
this.subcommands = subcommands;
|
|
315
|
+
this.suggestion = suggestion;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
258
318
|
var TYPE_EXIT = {
|
|
259
319
|
usage: EXIT.USAGE,
|
|
260
320
|
not_found: EXIT.NOT_FOUND,
|
|
@@ -357,12 +417,31 @@ function normalize(err, opts) {
|
|
|
357
417
|
detail: { subcommands: err.subcommands }
|
|
358
418
|
};
|
|
359
419
|
}
|
|
420
|
+
if (err instanceof UnknownSubcommandError) {
|
|
421
|
+
const didYouMean = err.suggestion === void 0 ? "" : `Did you mean '${err.suggestion}'? `;
|
|
422
|
+
return {
|
|
423
|
+
type: "usage",
|
|
424
|
+
message,
|
|
425
|
+
recovery: `${didYouMean}Valid subcommands: ${err.subcommands.join(", ")}.`,
|
|
426
|
+
retryable: false,
|
|
427
|
+
detail: { subcommands: err.subcommands }
|
|
428
|
+
};
|
|
429
|
+
}
|
|
360
430
|
if (err instanceof CliAuthError) {
|
|
361
431
|
return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
|
|
362
432
|
}
|
|
363
433
|
if (err instanceof CliNotFoundError) {
|
|
364
434
|
return { type: "not_found", message, recovery: err.recovery, retryable: false };
|
|
365
435
|
}
|
|
436
|
+
if (err instanceof CliUsageError) {
|
|
437
|
+
return {
|
|
438
|
+
type: "usage",
|
|
439
|
+
message,
|
|
440
|
+
recovery: err.recovery,
|
|
441
|
+
retryable: false,
|
|
442
|
+
...err.detail !== void 0 ? { detail: err.detail } : {}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
366
445
|
const status = httpStatus(err);
|
|
367
446
|
const detail = responseDetail(err);
|
|
368
447
|
if (status !== void 0) {
|
|
@@ -548,11 +627,39 @@ function attachSubcommandGuards(cmd) {
|
|
|
548
627
|
const hasAction = Boolean(cmd._actionHandler);
|
|
549
628
|
if (hasAction)
|
|
550
629
|
return;
|
|
551
|
-
cmd.
|
|
630
|
+
cmd.allowExcessArguments(true);
|
|
631
|
+
cmd.action((...params) => {
|
|
632
|
+
const invoked = params[params.length - 1];
|
|
552
633
|
const names = cmd.commands.map((c) => c.name()).filter((name) => name !== "help");
|
|
553
|
-
|
|
634
|
+
const [token] = invoked.args;
|
|
635
|
+
if (token === void 0)
|
|
636
|
+
throw new SubcommandRequiredError(commandPath2(cmd), names);
|
|
637
|
+
throw new UnknownSubcommandError(commandPath2(cmd), token, names, suggestSubcommand(token, names));
|
|
554
638
|
});
|
|
555
639
|
}
|
|
640
|
+
function editDistance(a, b) {
|
|
641
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
642
|
+
for (let i = 1; i <= a.length; i++) {
|
|
643
|
+
const row = [i];
|
|
644
|
+
for (let j = 1; j <= b.length; j++) {
|
|
645
|
+
const substitution = prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
646
|
+
row[j] = Math.min(row[j - 1] + 1, prev[j] + 1, substitution);
|
|
647
|
+
}
|
|
648
|
+
prev = row;
|
|
649
|
+
}
|
|
650
|
+
return prev[b.length];
|
|
651
|
+
}
|
|
652
|
+
function suggestSubcommand(token, names) {
|
|
653
|
+
const MAX_EDITS = 2;
|
|
654
|
+
let best;
|
|
655
|
+
for (const name of names) {
|
|
656
|
+
const distance = editDistance(token.toLowerCase(), name.toLowerCase());
|
|
657
|
+
if (distance <= MAX_EDITS && distance < token.length && (!best || distance < best.distance)) {
|
|
658
|
+
best = { name, distance };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return best?.name;
|
|
662
|
+
}
|
|
556
663
|
async function runCli(program, opts) {
|
|
557
664
|
routeErrors(program);
|
|
558
665
|
attachSubcommandGuards(program);
|
|
@@ -568,7 +675,7 @@ async function runCli(program, opts) {
|
|
|
568
675
|
|
|
569
676
|
// src/program.ts
|
|
570
677
|
import { styleText } from "util";
|
|
571
|
-
import { Command as
|
|
678
|
+
import { Command as Command17 } from "commander";
|
|
572
679
|
|
|
573
680
|
// src/commands/board/issues.ts
|
|
574
681
|
import { Argument } from "commander";
|
|
@@ -623,9 +730,28 @@ function transformUser(user) {
|
|
|
623
730
|
function transformField(field) {
|
|
624
731
|
return field;
|
|
625
732
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
733
|
+
|
|
734
|
+
// src/utils/transformers/field-meta.ts
|
|
735
|
+
function slimAllowedValue(raw) {
|
|
736
|
+
const out = {};
|
|
737
|
+
if (typeof raw.id === "string") out.id = raw.id;
|
|
738
|
+
if (typeof raw.name === "string") out.name = raw.name;
|
|
739
|
+
if (typeof raw.value === "string") out.value = raw.value;
|
|
740
|
+
if (Array.isArray(raw.children) && raw.children.length > 0) out.children = raw.children.map(slimAllowedValue);
|
|
741
|
+
return out;
|
|
742
|
+
}
|
|
743
|
+
function transformFieldMetaEntry(id, meta) {
|
|
744
|
+
const out = { id, required: meta.required === true };
|
|
745
|
+
if (typeof meta.name === "string") out.name = meta.name;
|
|
746
|
+
if (typeof meta.schema?.type === "string") out.type = meta.schema.type;
|
|
747
|
+
if (Array.isArray(meta.allowedValues) && meta.allowedValues.length > 0) {
|
|
748
|
+
out.allowedValues = meta.allowedValues.map(slimAllowedValue);
|
|
749
|
+
}
|
|
750
|
+
return out;
|
|
751
|
+
}
|
|
752
|
+
function transformFieldMeta(fields) {
|
|
753
|
+
const entries = Object.entries(fields).map(([id, meta]) => transformFieldMetaEntry(id, meta));
|
|
754
|
+
return [...entries.filter((f) => f.required), ...entries.filter((f) => !f.required)];
|
|
629
755
|
}
|
|
630
756
|
|
|
631
757
|
// src/utils/transformers/project.ts
|
|
@@ -766,12 +892,22 @@ function transformIssueLink(l) {
|
|
|
766
892
|
...outwardIssue ? { outwardIssue: transformIssueBasic(outwardIssue) } : {}
|
|
767
893
|
};
|
|
768
894
|
}
|
|
895
|
+
function customFieldNames(names, fields) {
|
|
896
|
+
if (!names) return void 0;
|
|
897
|
+
const kept = Object.entries(names).filter(
|
|
898
|
+
([id]) => id.startsWith("customfield_") && fields[id] !== void 0
|
|
899
|
+
);
|
|
900
|
+
return kept.length > 0 ? Object.fromEntries(kept) : void 0;
|
|
901
|
+
}
|
|
769
902
|
function transformIssue(issue) {
|
|
770
|
-
const { self: _self, expand: _expand, names
|
|
903
|
+
const { self: _self, expand: _expand, names, schema: _schema, fields, ...rest } = issue;
|
|
904
|
+
const transformed = transformIssueFields(fields, issue.key);
|
|
905
|
+
const fieldNames = customFieldNames(names, transformed);
|
|
771
906
|
return {
|
|
772
907
|
...rest,
|
|
773
908
|
url: issueBrowseUrl(issue.key),
|
|
774
|
-
fields:
|
|
909
|
+
fields: transformed,
|
|
910
|
+
...fieldNames ? { names: fieldNames } : {}
|
|
775
911
|
};
|
|
776
912
|
}
|
|
777
913
|
function isEmpty(v) {
|
|
@@ -807,7 +943,45 @@ function pruneSentinels(fields) {
|
|
|
807
943
|
if (watches && watches.watchCount === 0) delete out.watches;
|
|
808
944
|
return out;
|
|
809
945
|
}
|
|
810
|
-
|
|
946
|
+
var DEV_STATUS_DUMP = /com\.atlassian\.jira\.plugin\.devstatus/;
|
|
947
|
+
function collapseDevStatus(fields, key) {
|
|
948
|
+
const out = { ...fields };
|
|
949
|
+
for (const [field, value] of Object.entries(out)) {
|
|
950
|
+
if (typeof value === "string" && DEV_STATUS_DUMP.test(value)) {
|
|
951
|
+
out[field] = `(use: jiradc issue dev-status ${key})`;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return out;
|
|
955
|
+
}
|
|
956
|
+
function transformInlineComponent(c) {
|
|
957
|
+
const { self: _self, description: _description, ...rest } = c;
|
|
958
|
+
return rest;
|
|
959
|
+
}
|
|
960
|
+
var ISSUE_KEY_PATTERN = /^[A-Z][A-Z0-9_]*-\d+$/;
|
|
961
|
+
function asIssueRefKey(value) {
|
|
962
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
963
|
+
const o = value;
|
|
964
|
+
if (typeof o.key !== "string" || !ISSUE_KEY_PATTERN.test(o.key)) return void 0;
|
|
965
|
+
if (typeof o.self !== "string" || !o.self.includes("/issue/")) return void 0;
|
|
966
|
+
return o.key;
|
|
967
|
+
}
|
|
968
|
+
function collapseIssueRefs(fields) {
|
|
969
|
+
const out = { ...fields };
|
|
970
|
+
for (const [key, value] of Object.entries(out)) {
|
|
971
|
+
const single = asIssueRefKey(value);
|
|
972
|
+
if (single !== void 0) {
|
|
973
|
+
out[key] = single;
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
977
|
+
const keys = value.map(asIssueRefKey);
|
|
978
|
+
if (keys.every((k) => k !== void 0)) out[key] = keys;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return out;
|
|
982
|
+
}
|
|
983
|
+
function transformIssueFields(fields, key) {
|
|
984
|
+
if (!fields) return {};
|
|
811
985
|
const {
|
|
812
986
|
// shaped sub-entities (recursed individually)
|
|
813
987
|
issuetype,
|
|
@@ -833,9 +1007,11 @@ function transformIssueFields(fields) {
|
|
|
833
1007
|
// everything else: optional scalars, customfield_*, etc. — gets compacted
|
|
834
1008
|
...rest
|
|
835
1009
|
} = fields;
|
|
836
|
-
const compacted = pruneSentinels(compactRecord(rest));
|
|
1010
|
+
const compacted = collapseDevStatus(collapseIssueRefs(pruneSentinels(compactRecord(rest))), key);
|
|
1011
|
+
const components = compacted.components;
|
|
837
1012
|
return {
|
|
838
1013
|
...compacted,
|
|
1014
|
+
...Array.isArray(components) ? { components: components.map(transformInlineComponent) } : {},
|
|
839
1015
|
summary,
|
|
840
1016
|
project,
|
|
841
1017
|
created,
|
|
@@ -876,23 +1052,23 @@ function transformIssueFields(fields) {
|
|
|
876
1052
|
}
|
|
877
1053
|
|
|
878
1054
|
// src/utils/transformers/transition.ts
|
|
879
|
-
function
|
|
1055
|
+
function slimAllowedValue2(raw) {
|
|
880
1056
|
if (!raw || typeof raw !== "object") return {};
|
|
881
1057
|
const av = raw;
|
|
882
1058
|
const slim = {};
|
|
883
1059
|
if (typeof av.id === "string") slim.id = av.id;
|
|
884
1060
|
if (typeof av.name === "string") slim.name = av.name;
|
|
885
1061
|
if (typeof av.value === "string") slim.value = av.value;
|
|
1062
|
+
if (Array.isArray(av.children) && av.children.length > 0) slim.children = av.children.map(slimAllowedValue2);
|
|
886
1063
|
return slim;
|
|
887
1064
|
}
|
|
888
1065
|
function slimField(raw) {
|
|
889
1066
|
if (!raw || typeof raw !== "object") return {};
|
|
890
1067
|
const field = raw;
|
|
891
1068
|
const out = {};
|
|
892
|
-
if (typeof field.required === "boolean") out.required = field.required;
|
|
893
1069
|
if (typeof field.name === "string") out.name = field.name;
|
|
894
1070
|
if (Array.isArray(field.allowedValues)) {
|
|
895
|
-
out.allowedValues = field.allowedValues.map(
|
|
1071
|
+
out.allowedValues = field.allowedValues.map(slimAllowedValue2);
|
|
896
1072
|
}
|
|
897
1073
|
return out;
|
|
898
1074
|
}
|
|
@@ -1190,7 +1366,7 @@ function update(parent) {
|
|
|
1190
1366
|
async (id, opts) => {
|
|
1191
1367
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
1192
1368
|
if (opts.name === void 0 && description === void 0 && opts.lead === void 0 && opts.assigneeType === void 0) {
|
|
1193
|
-
throw new
|
|
1369
|
+
throw new CliUsageError("Provide at least one of --name, --description, --lead, --assignee-type");
|
|
1194
1370
|
}
|
|
1195
1371
|
const client = getClient();
|
|
1196
1372
|
const result = await client.components.update({
|
|
@@ -1224,22 +1400,6 @@ function registerComponentCommands(program) {
|
|
|
1224
1400
|
issueCount(component);
|
|
1225
1401
|
}
|
|
1226
1402
|
|
|
1227
|
-
// src/commands/field/options.ts
|
|
1228
|
-
function options(parent) {
|
|
1229
|
-
const cmd = parent.command("options").description("Get available options for a custom field").argument("<id>", "Field ID", text).option("--query <text>", "Filter options by text", text).option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--start <number>", "Page number (1-indexed)", positiveInt);
|
|
1230
|
-
examples(cmd, ["10001", '10001 --query "High"', "10001 --limit 20 --start 2"]);
|
|
1231
|
-
cmd.action(async (id, opts) => {
|
|
1232
|
-
const client = getClient();
|
|
1233
|
-
const result = await client.fields.getFieldOptions({
|
|
1234
|
-
fieldId: id,
|
|
1235
|
-
query: opts.query,
|
|
1236
|
-
maxResults: opts.limit,
|
|
1237
|
-
page: opts.start
|
|
1238
|
-
});
|
|
1239
|
-
output(transformPaged({ ...result, startAt: result.startAt ?? 0 }, transformCustomFieldOption));
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
1403
|
// src/commands/field/search.ts
|
|
1244
1404
|
function search(parent) {
|
|
1245
1405
|
const cmd = parent.command("search").description("Search for fields by name or ID").argument("<keyword>", "Search keyword", text).option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25);
|
|
@@ -1254,9 +1414,8 @@ function search(parent) {
|
|
|
1254
1414
|
// src/commands/field/index.ts
|
|
1255
1415
|
function registerFieldCommands(program) {
|
|
1256
1416
|
const field = program.command("field").description("Field operations");
|
|
1257
|
-
examples(field, ['search "epic"', 'search "priority"'
|
|
1417
|
+
examples(field, ['search "epic"', 'search "priority"']);
|
|
1258
1418
|
search(field);
|
|
1259
|
-
options(field);
|
|
1260
1419
|
}
|
|
1261
1420
|
|
|
1262
1421
|
// src/utils/resolve-user.ts
|
|
@@ -1667,13 +1826,16 @@ function registerCommentCommands(parent) {
|
|
|
1667
1826
|
}
|
|
1668
1827
|
|
|
1669
1828
|
// src/commands/issue/create.ts
|
|
1829
|
+
import { Option as Option5 } from "commander";
|
|
1670
1830
|
import { z as z2 } from "zod";
|
|
1671
|
-
var
|
|
1831
|
+
var fieldsSchema = z2.record(z2.unknown());
|
|
1672
1832
|
function create3(parent) {
|
|
1673
1833
|
const cmd = parent.command("create").description("Create a new issue").requiredOption("--project <key>", "Project key or ID", text).requiredOption("--type <name>", "Issue type name (e.g., Task, Bug, Story)", text).requiredOption("--summary <text>", "Issue summary/title", text).option("--assignee <user>", 'Assignee. Username, "me", or "none" to leave unassigned.', text).option("--reporter <user>", 'Reporter. Username or "me".', text).option("--priority <name>", "Priority name (e.g., High, Medium, Low)", text).option("--labels <labels>", "Comma-separated labels", text).option("--components <names>", "Comma-separated component names", text).option("--fix-versions <versions>", "Comma-separated fix version names", text).option("--due-date <date>", "Due date in YYYY-MM-DD format", date).option("--parent <key>", "Parent issue key (for subtasks)", issueKey).option(
|
|
1674
|
-
"--
|
|
1675
|
-
`Additional
|
|
1676
|
-
jsonShape(
|
|
1834
|
+
"--fields <json>",
|
|
1835
|
+
`Additional fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
|
|
1836
|
+
jsonShape(fieldsSchema)
|
|
1837
|
+
).addOption(
|
|
1838
|
+
new Option5("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
|
|
1677
1839
|
);
|
|
1678
1840
|
textOrFileOption(cmd, "description", { description: "Issue description in wiki markup" });
|
|
1679
1841
|
examples(cmd, [
|
|
@@ -1684,6 +1846,13 @@ function create3(parent) {
|
|
|
1684
1846
|
cmd.action(
|
|
1685
1847
|
async (opts) => {
|
|
1686
1848
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
1849
|
+
if (opts.fields && opts.customFields) {
|
|
1850
|
+
throw new CliUsageError(
|
|
1851
|
+
"Pass either --fields or --custom-fields, not both",
|
|
1852
|
+
"--custom-fields is a deprecated alias for --fields. Merge the two JSON objects into a single --fields value."
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
const fields = opts.fields ?? opts.customFields;
|
|
1687
1856
|
const assignee = opts.assignee !== void 0 ? await resolveUserToken(opts.assignee) : void 0;
|
|
1688
1857
|
const reporter = opts.reporter !== void 0 ? await resolveUserToken(opts.reporter) : void 0;
|
|
1689
1858
|
const client = getClient();
|
|
@@ -1700,13 +1869,31 @@ function create3(parent) {
|
|
|
1700
1869
|
fixVersions: opts.fixVersions?.split(",").map((v) => v.trim()),
|
|
1701
1870
|
dueDate: opts.dueDate,
|
|
1702
1871
|
parent: opts.parent,
|
|
1703
|
-
customFields:
|
|
1872
|
+
customFields: fields
|
|
1704
1873
|
});
|
|
1705
1874
|
output(transformCreatedIssue(result));
|
|
1706
1875
|
}
|
|
1707
1876
|
);
|
|
1708
1877
|
}
|
|
1709
1878
|
|
|
1879
|
+
// src/commands/issue/createmeta.ts
|
|
1880
|
+
function createmeta(parent) {
|
|
1881
|
+
const cmd = parent.command("createmeta").description("Show which fields a new issue of a given type accepts, and the values they allow").requiredOption("--project <key>", "Project key", text).requiredOption("--type <name>", "Issue type name (e.g., Task, Bug, Story)", text);
|
|
1882
|
+
examples(cmd, ["--project PROJ --type Task", "--project PROJ --type Bug"]);
|
|
1883
|
+
cmd.action(async (opts) => {
|
|
1884
|
+
const client = getClient();
|
|
1885
|
+
const meta = await client.issues.getCreateMeta({ projectKey: opts.project, issueTypeName: opts.type });
|
|
1886
|
+
const issueType = meta.projects?.[0]?.issuetypes?.[0];
|
|
1887
|
+
if (!issueType?.fields) {
|
|
1888
|
+
throw new CliNotFoundError(
|
|
1889
|
+
`No create metadata for issue type "${opts.type}" in project "${opts.project}"`,
|
|
1890
|
+
"Verify the project key and issue type name, then check you may create that type there."
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1893
|
+
output(transformFieldMeta(issueType.fields));
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1710
1897
|
// src/commands/issue/delete.ts
|
|
1711
1898
|
function deleteIssue(parent) {
|
|
1712
1899
|
const cmd = parent.command("delete").description("Delete an issue").argument("<key>", "Issue key", issueKey).option("--delete-subtasks", "Also delete subtasks (default: false)");
|
|
@@ -1796,10 +1983,20 @@ function devStatus(parent) {
|
|
|
1796
1983
|
});
|
|
1797
1984
|
}
|
|
1798
1985
|
|
|
1986
|
+
// src/commands/issue/editmeta.ts
|
|
1987
|
+
function editmeta(parent) {
|
|
1988
|
+
const cmd = parent.command("editmeta").description("Show which fields this issue's edit screen accepts, and the values they allow").argument("<key>", "Issue key", issueKey);
|
|
1989
|
+
examples(cmd, ["PROJ-123"]);
|
|
1990
|
+
cmd.action(async (key) => {
|
|
1991
|
+
const client = getClient();
|
|
1992
|
+
const meta = await client.issues.getEditMeta({ issueKeyOrId: key });
|
|
1993
|
+
output(transformFieldMeta(meta.fields ?? {}));
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1799
1997
|
// src/utils/constants.ts
|
|
1800
|
-
var
|
|
1998
|
+
var SEARCH_DEFAULT_FIELDS = [
|
|
1801
1999
|
"summary",
|
|
1802
|
-
"description",
|
|
1803
2000
|
"status",
|
|
1804
2001
|
"assignee",
|
|
1805
2002
|
"reporter",
|
|
@@ -1808,13 +2005,78 @@ var DEFAULT_FIELDS = [
|
|
|
1808
2005
|
"created",
|
|
1809
2006
|
"updated",
|
|
1810
2007
|
"issuetype",
|
|
1811
|
-
"components"
|
|
1812
|
-
"comment"
|
|
2008
|
+
"components"
|
|
1813
2009
|
];
|
|
2010
|
+
var DEFAULT_FIELDS = [...SEARCH_DEFAULT_FIELDS, "description", "comment"];
|
|
2011
|
+
|
|
2012
|
+
// src/utils/field-selectors.ts
|
|
2013
|
+
var WILDCARD = "*";
|
|
2014
|
+
var NEGATION = "-";
|
|
2015
|
+
function buildFieldNameIndex(fields) {
|
|
2016
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2017
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2018
|
+
for (const field of fields) {
|
|
2019
|
+
ids.add(field.id);
|
|
2020
|
+
byName.set(field.name.toLowerCase(), field.id);
|
|
2021
|
+
for (const clause of field.clauseNames ?? []) byName.set(clause.toLowerCase(), field.id);
|
|
2022
|
+
}
|
|
2023
|
+
for (const field of fields) byName.set(field.id.toLowerCase(), field.id);
|
|
2024
|
+
return { ids, byName };
|
|
2025
|
+
}
|
|
2026
|
+
function resolveOne(token, index) {
|
|
2027
|
+
if (index.ids.has(token)) return token;
|
|
2028
|
+
return index.byName.get(token.toLowerCase());
|
|
2029
|
+
}
|
|
2030
|
+
function resolveFieldSelectors(tokens, index) {
|
|
2031
|
+
const resolved = [];
|
|
2032
|
+
const unresolved = [];
|
|
2033
|
+
for (const token of tokens) {
|
|
2034
|
+
if (token.startsWith(WILDCARD)) {
|
|
2035
|
+
resolved.push(token);
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
const negated = token.startsWith(NEGATION);
|
|
2039
|
+
const bare = negated ? token.slice(1) : token;
|
|
2040
|
+
const id = resolveOne(bare, index);
|
|
2041
|
+
if (id === void 0) {
|
|
2042
|
+
unresolved.push(token);
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
resolved.push(negated ? `${NEGATION}${id}` : id);
|
|
2046
|
+
}
|
|
2047
|
+
if (unresolved.length > 0) {
|
|
2048
|
+
const plural = unresolved.length > 1 ? "s" : "";
|
|
2049
|
+
throw new CliUsageError(
|
|
2050
|
+
`Unknown field name${plural}: ${unresolved.join(", ")}`,
|
|
2051
|
+
`Jira selects fields by id, and these matched no field id, name or JQL clause name on this instance. Run jiradc field search <keyword> to find the right name or id. Left unresolved these would be dropped silently, returning an answer missing the field${plural} you asked for.`,
|
|
2052
|
+
{ unresolved }
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
return [...new Set(resolved)];
|
|
2056
|
+
}
|
|
2057
|
+
function parseFieldSelectors(raw) {
|
|
2058
|
+
return raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
2059
|
+
}
|
|
2060
|
+
var FIELD_CACHE = { name: "jiradc", ttl: 36e5 };
|
|
2061
|
+
async function loadFieldNameIndex(client) {
|
|
2062
|
+
const fields = await cacheGetOrFetch(
|
|
2063
|
+
FIELD_CACHE,
|
|
2064
|
+
"field-names",
|
|
2065
|
+
async () => (await client.fields.getAll()).map(({ id, name, clauseNames }) => ({ id, name, clauseNames }))
|
|
2066
|
+
);
|
|
2067
|
+
return buildFieldNameIndex(fields);
|
|
2068
|
+
}
|
|
2069
|
+
async function selectFields(client, opts, defaults) {
|
|
2070
|
+
if (opts.allFields === true) return void 0;
|
|
2071
|
+
if (opts.fields === void 0) return defaults;
|
|
2072
|
+
const tokens = parseFieldSelectors(opts.fields);
|
|
2073
|
+
if (tokens.length === 0) return defaults;
|
|
2074
|
+
return resolveFieldSelectors(tokens, await loadFieldNameIndex(client));
|
|
2075
|
+
}
|
|
1814
2076
|
|
|
1815
2077
|
// src/commands/issue/get.ts
|
|
1816
2078
|
function get2(parent) {
|
|
1817
|
-
const cmd = parent.command("get").description("Get issue details").argument("<key>", "Issue key", issueKey).option("--fields <fields>", "Comma-separated fields to return
|
|
2079
|
+
const cmd = parent.command("get").description("Get issue details").argument("<key>", "Issue key", issueKey).option("--fields <fields>", "Comma-separated fields to return, by name or id", text).option("--all-fields", "Return all fields instead of defaults").option("--expand <expand>", 'Expand options (e.g., "transitions", "changelog")', text);
|
|
1818
2080
|
examples(cmd, [
|
|
1819
2081
|
"PROJ-123",
|
|
1820
2082
|
"PROJ-123 --fields summary,status,assignee",
|
|
@@ -1823,7 +2085,7 @@ function get2(parent) {
|
|
|
1823
2085
|
]);
|
|
1824
2086
|
cmd.action(async (key, opts) => {
|
|
1825
2087
|
const client = getClient();
|
|
1826
|
-
const fields =
|
|
2088
|
+
const fields = await selectFields(client, opts, DEFAULT_FIELDS);
|
|
1827
2089
|
const result = await client.issues.get({
|
|
1828
2090
|
issueKeyOrId: key,
|
|
1829
2091
|
fields,
|
|
@@ -1902,63 +2164,126 @@ function link(parent) {
|
|
|
1902
2164
|
});
|
|
1903
2165
|
}
|
|
1904
2166
|
|
|
2167
|
+
// src/utils/paging.ts
|
|
2168
|
+
var ALL_RESULTS_CAP = 1e3;
|
|
2169
|
+
function planSearch(opts, maxPageSize) {
|
|
2170
|
+
const start = opts.start ?? 0;
|
|
2171
|
+
if (opts.all !== true) return { all: false, pageSize: opts.limit, start };
|
|
2172
|
+
if (opts.limitGiven) {
|
|
2173
|
+
throw new CliUsageError(
|
|
2174
|
+
"Pass either --all or --limit, not both",
|
|
2175
|
+
"--all collects every match; --limit caps how many come back. Drop --limit to collect everything, or drop --all and page with --limit and --start."
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
return { all: true, pageSize: maxPageSize, start };
|
|
2179
|
+
}
|
|
2180
|
+
async function fetchEveryPage(fetchPage, opts) {
|
|
2181
|
+
const first = await fetchPage(opts.start, opts.pageSize);
|
|
2182
|
+
const outstanding = first.total - opts.start;
|
|
2183
|
+
if (outstanding > ALL_RESULTS_CAP) {
|
|
2184
|
+
throw new CliUsageError(
|
|
2185
|
+
`--all would collect ${outstanding} issues, above the ${ALL_RESULTS_CAP} limit`,
|
|
2186
|
+
`Narrow the query, or page it yourself with --start and --limit. Returning the first ${ALL_RESULTS_CAP} instead would look like a complete answer and would not be one.`,
|
|
2187
|
+
{ total: first.total, cap: ALL_RESULTS_CAP }
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
const offsets = [];
|
|
2191
|
+
for (let at = opts.start + first.issues.length; at < first.total; at += opts.pageSize) offsets.push(at);
|
|
2192
|
+
const rest = await Promise.all(offsets.map(async (at) => fetchPage(at, opts.pageSize)));
|
|
2193
|
+
const issues3 = [first, ...rest].flatMap((page) => page.issues);
|
|
2194
|
+
return { startAt: opts.start, maxResults: issues3.length, total: first.total, isLast: true, issues: issues3 };
|
|
2195
|
+
}
|
|
2196
|
+
|
|
1905
2197
|
// src/commands/issue/search.ts
|
|
2198
|
+
var MAX_PAGE_SIZE = 50;
|
|
1906
2199
|
function search2(parent) {
|
|
1907
|
-
const cmd = parent.command("search").description("Search issues using JQL").argument("<jql>", "JQL query string", text).option(
|
|
2200
|
+
const cmd = parent.command("search").description("Search issues using JQL").argument("<jql>", "JQL query string", text).option(
|
|
2201
|
+
"--limit <number>",
|
|
2202
|
+
`Max results (1-${MAX_PAGE_SIZE}, Jira DC caps at ${MAX_PAGE_SIZE})`,
|
|
2203
|
+
intInRange(1, MAX_PAGE_SIZE),
|
|
2204
|
+
25
|
|
2205
|
+
).option("--start <number>", "Starting index for pagination", nonNegativeInt).option(
|
|
2206
|
+
"--fields <fields>",
|
|
2207
|
+
"Comma-separated fields to return, by name or id (defaults to a lean set without description/comment)",
|
|
2208
|
+
text
|
|
2209
|
+
).option("--all-fields", "Return all fields instead of defaults").option("--all", `Collect every match, not just one page (up to ${ALL_RESULTS_CAP}; fails above it)`);
|
|
1908
2210
|
examples(cmd, [
|
|
1909
2211
|
'"project = PROJ AND status = Open"',
|
|
1910
2212
|
'"assignee = currentUser()" --limit 10 --fields summary,status',
|
|
2213
|
+
'"project = PROJ AND status = Open" --all',
|
|
1911
2214
|
'"project = PROJ" --start 50 --limit 50'
|
|
1912
2215
|
]);
|
|
1913
|
-
cmd.action(
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
startAt
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2216
|
+
cmd.action(
|
|
2217
|
+
async (jql, opts) => {
|
|
2218
|
+
const client = getClient();
|
|
2219
|
+
const fields = await selectFields(client, opts, SEARCH_DEFAULT_FIELDS);
|
|
2220
|
+
const plan = planSearch({ ...opts, limitGiven: cmd.getOptionValueSource("limit") === "cli" }, MAX_PAGE_SIZE);
|
|
2221
|
+
const page = async (startAt, maxResults) => client.issues.search({ jql, startAt, maxResults, fields });
|
|
2222
|
+
if (!plan.all) {
|
|
2223
|
+
output(transformPaged(await page(plan.start, plan.pageSize), transformIssue));
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
const every = await fetchEveryPage(page, { pageSize: plan.pageSize, start: plan.start });
|
|
2227
|
+
output({ ...every, issues: every.issues.map(transformIssue) });
|
|
2228
|
+
}
|
|
2229
|
+
);
|
|
1924
2230
|
}
|
|
1925
2231
|
|
|
1926
2232
|
// src/commands/issue/transition.ts
|
|
2233
|
+
import { z as z3 } from "zod";
|
|
2234
|
+
var fieldsSchema2 = z3.record(z3.unknown());
|
|
2235
|
+
function resolveTransitionId(to, transitions2) {
|
|
2236
|
+
const target = to.toLowerCase();
|
|
2237
|
+
const byName = transitions2.filter((t) => t.name.toLowerCase() === target);
|
|
2238
|
+
const matches = byName.length > 0 ? byName : transitions2.filter((t) => t.to.toLowerCase() === target);
|
|
2239
|
+
if (matches.length === 1) return matches[0].id;
|
|
2240
|
+
const available = transitions2.map((t) => ({ id: t.id, name: t.name, to: t.to }));
|
|
2241
|
+
if (matches.length === 0) {
|
|
2242
|
+
throw new CliUsageError(
|
|
2243
|
+
`Transition "${to}" not available from current status`,
|
|
2244
|
+
"Re-run --to with one of the listed transitions (by name, id, or target status).",
|
|
2245
|
+
{ available }
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
const ids = matches.map((t) => t.id).join(", ");
|
|
2249
|
+
throw new CliUsageError(
|
|
2250
|
+
`"${to}" matches more than one available transition`,
|
|
2251
|
+
`Re-run --to with one of these ids: ${ids}.`,
|
|
2252
|
+
{ available }
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
1927
2255
|
function transition(parent) {
|
|
1928
|
-
const cmd = parent.command("transition").description("Transition issue to a new status").argument("<key>", "Issue key", issueKey).requiredOption("--to <idOrName>", "Transition ID, or status name (case-insensitive)", text)
|
|
2256
|
+
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(
|
|
2257
|
+
"--fields <json>",
|
|
2258
|
+
`Fields required by the transition screen, as JSON (e.g. '{"customfield_10100": {"value": "Approved"}}')`,
|
|
2259
|
+
jsonShape(fieldsSchema2)
|
|
2260
|
+
);
|
|
1929
2261
|
commentOption(cmd, { description: "Comment to add during transition" });
|
|
1930
2262
|
examples(cmd, [
|
|
1931
2263
|
"PROJ-123 --to 31",
|
|
2264
|
+
'PROJ-123 --to "Start Review"',
|
|
1932
2265
|
'PROJ-123 --to "In Review"',
|
|
1933
|
-
'PROJ-123 --to Done --comment "Verified in staging"'
|
|
2266
|
+
'PROJ-123 --to Done --comment "Verified in staging"',
|
|
2267
|
+
`PROJ-123 --to Approve --fields '{"customfield_10100": {"value": "Approved"}}'`
|
|
1934
2268
|
]);
|
|
1935
|
-
cmd.action(
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
const matches = transitions2.filter((t) => t.name.toLowerCase() === target);
|
|
1949
|
-
if (matches.length === 0) {
|
|
1950
|
-
const names = transitions2.map((t) => `"${t.name}"`).join(", ");
|
|
1951
|
-
throw new Error(`Transition "${opts.to}" not available from current status. Available: ${names || "(none)"}`);
|
|
1952
|
-
}
|
|
1953
|
-
if (matches.length > 1) {
|
|
1954
|
-
const ids = matches.map((t) => t.id).join(", ");
|
|
1955
|
-
throw new Error(`Multiple transitions named "${opts.to}". Use --to <id> with one of: ${ids}`);
|
|
2269
|
+
cmd.action(
|
|
2270
|
+
async (key, opts) => {
|
|
2271
|
+
const comment = resolveTextOrFile(opts, "comment", { required: false });
|
|
2272
|
+
const client = getClient();
|
|
2273
|
+
let transitionId;
|
|
2274
|
+
if (/^\d+$/.test(opts.to)) {
|
|
2275
|
+
transitionId = opts.to;
|
|
2276
|
+
} else {
|
|
2277
|
+
const raw = await client.issues.getTransitions({
|
|
2278
|
+
issueKeyOrId: key,
|
|
2279
|
+
expand: "transitions.fields"
|
|
2280
|
+
});
|
|
2281
|
+
transitionId = resolveTransitionId(opts.to, transformTransitions(raw));
|
|
1956
2282
|
}
|
|
1957
|
-
transitionId
|
|
2283
|
+
await client.issues.transition({ issueKeyOrId: key, transitionId, fields: opts.fields, comment });
|
|
2284
|
+
output({ transitioned: true, issue: transformIssueRef(key) });
|
|
1958
2285
|
}
|
|
1959
|
-
|
|
1960
|
-
output({ transitioned: true, issue: transformIssueRef(key) });
|
|
1961
|
-
});
|
|
2286
|
+
);
|
|
1962
2287
|
}
|
|
1963
2288
|
|
|
1964
2289
|
// src/commands/issue/transitions.ts
|
|
@@ -1987,7 +2312,7 @@ function unlink2(parent) {
|
|
|
1987
2312
|
}
|
|
1988
2313
|
|
|
1989
2314
|
// src/commands/issue/update.ts
|
|
1990
|
-
import { z as
|
|
2315
|
+
import { z as z4 } from "zod";
|
|
1991
2316
|
|
|
1992
2317
|
// src/utils/multi-value.ts
|
|
1993
2318
|
import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
|
|
@@ -2016,7 +2341,7 @@ function parseMultiValue(flagName, raw) {
|
|
|
2016
2341
|
}
|
|
2017
2342
|
|
|
2018
2343
|
// src/commands/issue/update.ts
|
|
2019
|
-
var
|
|
2344
|
+
var fieldsSchema3 = z4.record(z4.unknown());
|
|
2020
2345
|
function buildUpdateOps(parsed, wrap) {
|
|
2021
2346
|
if (parsed.mode !== "mutate") return void 0;
|
|
2022
2347
|
return [...parsed.adds.map((v) => ({ add: wrap(v) })), ...parsed.removes.map((v) => ({ remove: wrap(v) }))];
|
|
@@ -2029,7 +2354,7 @@ function update3(parent) {
|
|
|
2029
2354
|
const cmd = parent.command("update").description("Update issue fields").argument("<key>", "Issue key", issueKey).option(
|
|
2030
2355
|
"--fields <json>",
|
|
2031
2356
|
"JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)",
|
|
2032
|
-
jsonShape(
|
|
2357
|
+
jsonShape(fieldsSchema3)
|
|
2033
2358
|
).option("--no-notify-users", "Suppress notification emails (default: notify)").option("--attachments <paths>", "Comma-separated local file paths to attach", text).option("--summary <text>", "Set the issue summary", text).option("--priority <name>", "Set the priority by name (e.g. High)", text).option("--assignee <user>", 'Set the assignee. Username, "me", or "none" to unassign.', text).option("--labels <list>", 'Set labels ("a,b,c") or mutate ("+add,-remove")', text).option("--components <list>", 'Set components ("a,b") or mutate ("+add,-remove")', text).option("--fix-versions <list>", 'Set fix versions ("1.0,2.0") or mutate ("+1.0,-0.9")', text);
|
|
2034
2359
|
textOrFileOption(cmd, "description", { description: "Set the issue description (wiki markup)" });
|
|
2035
2360
|
examples(cmd, [
|
|
@@ -2044,7 +2369,7 @@ function update3(parent) {
|
|
|
2044
2369
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
2045
2370
|
const hasShortcut = opts.summary !== void 0 || description !== void 0 || opts.priority !== void 0 || opts.assignee !== void 0 || opts.labels !== void 0 || opts.components !== void 0 || opts.fixVersions !== void 0;
|
|
2046
2371
|
if (!opts.fields && !opts.attachments && !hasShortcut) {
|
|
2047
|
-
throw new
|
|
2372
|
+
throw new CliUsageError(
|
|
2048
2373
|
"Provide at least one of --fields, --attachments, or a shortcut flag (--summary, --priority, --labels, ...)"
|
|
2049
2374
|
);
|
|
2050
2375
|
}
|
|
@@ -2132,12 +2457,12 @@ function create4(parent) {
|
|
|
2132
2457
|
}
|
|
2133
2458
|
|
|
2134
2459
|
// src/commands/issue/worklog/delete.ts
|
|
2135
|
-
import { Option as
|
|
2460
|
+
import { Option as Option6 } from "commander";
|
|
2136
2461
|
var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
|
|
2137
2462
|
function deleteWorklog(parent) {
|
|
2138
2463
|
const cmd = parent.command("delete").description("Delete a worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2139
2464
|
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
2140
|
-
cmd.addOption(new
|
|
2465
|
+
cmd.addOption(new Option6("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text).option(
|
|
2141
2466
|
"--increase-by <amount>",
|
|
2142
2467
|
'Amount to increase the estimate by; required when --adjust-estimate is "manual"',
|
|
2143
2468
|
text
|
|
@@ -2178,12 +2503,12 @@ function list4(parent) {
|
|
|
2178
2503
|
}
|
|
2179
2504
|
|
|
2180
2505
|
// src/commands/issue/worklog/update.ts
|
|
2181
|
-
import { Option as
|
|
2506
|
+
import { Option as Option7 } from "commander";
|
|
2182
2507
|
var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
|
|
2183
2508
|
function update4(parent) {
|
|
2184
2509
|
const cmd = parent.command("update").description("Update an existing worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2185
2510
|
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
2186
|
-
cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new
|
|
2511
|
+
cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new Option7("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE2)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text);
|
|
2187
2512
|
commentOption(cmd, { description: "Worklog comment" });
|
|
2188
2513
|
examples(cmd, [
|
|
2189
2514
|
'PROJ-123 --worklog-id 12345 --time "1h 30m"',
|
|
@@ -2241,6 +2566,8 @@ function registerIssueCommands(program) {
|
|
|
2241
2566
|
deleteIssue(issue);
|
|
2242
2567
|
transition(issue);
|
|
2243
2568
|
transitions(issue);
|
|
2569
|
+
createmeta(issue);
|
|
2570
|
+
editmeta(issue);
|
|
2244
2571
|
assign(issue);
|
|
2245
2572
|
registerCommentCommands(issue);
|
|
2246
2573
|
registerWorklogCommands(issue);
|
|
@@ -2342,10 +2669,10 @@ function issues2(parent) {
|
|
|
2342
2669
|
}
|
|
2343
2670
|
|
|
2344
2671
|
// src/commands/sprint/list.ts
|
|
2345
|
-
import { Option as
|
|
2672
|
+
import { Option as Option8 } from "commander";
|
|
2346
2673
|
var SPRINT_STATES = ["future", "active", "closed"];
|
|
2347
2674
|
function list6(parent) {
|
|
2348
|
-
const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new
|
|
2675
|
+
const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option8("--state <state>", "Filter by sprint state").choices(SPRINT_STATES));
|
|
2349
2676
|
examples(cmd, ["--board 42", "--board 42 --state active"]);
|
|
2350
2677
|
cmd.action(async (opts) => {
|
|
2351
2678
|
const client = getClient();
|
|
@@ -2358,10 +2685,10 @@ function list6(parent) {
|
|
|
2358
2685
|
}
|
|
2359
2686
|
|
|
2360
2687
|
// src/commands/sprint/update.ts
|
|
2361
|
-
import { Argument as Argument4, Option as
|
|
2688
|
+
import { Argument as Argument4, Option as Option9 } from "commander";
|
|
2362
2689
|
var SPRINT_STATES2 = ["future", "active", "closed"];
|
|
2363
2690
|
function update5(parent) {
|
|
2364
|
-
const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new
|
|
2691
|
+
const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new Option9("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format", text).option("--end-date <date>", "New end date in ISO 8601 format", text).option("--goal <goal>", "New sprint goal", text);
|
|
2365
2692
|
examples(cmd, [
|
|
2366
2693
|
'100 --name "Sprint 10 - Extended"',
|
|
2367
2694
|
"100 --state active",
|
|
@@ -2401,10 +2728,10 @@ function registerSprintCommands(program) {
|
|
|
2401
2728
|
|
|
2402
2729
|
// src/commands/token/client.ts
|
|
2403
2730
|
import { JiraClient as JiraClient2 } from "jira-data-center-client";
|
|
2404
|
-
function getTokenClient(
|
|
2731
|
+
function getTokenClient(options = {}) {
|
|
2405
2732
|
const baseUrl = process.env.JIRA_URL;
|
|
2406
|
-
const username =
|
|
2407
|
-
const password =
|
|
2733
|
+
const username = options.basicUsername ?? process.env.JIRA_BASIC_USERNAME;
|
|
2734
|
+
const password = options.basicPassword ?? process.env.JIRA_BASIC_PASSWORD;
|
|
2408
2735
|
if (!baseUrl || !username || !password) {
|
|
2409
2736
|
const missing = [
|
|
2410
2737
|
...!baseUrl ? ["JIRA_URL"] : [],
|
|
@@ -2545,12 +2872,12 @@ function registerUserCommands(program) {
|
|
|
2545
2872
|
function assertExactlyOne(opts, keys, label) {
|
|
2546
2873
|
const present = keys.filter((k) => opts[k] !== void 0);
|
|
2547
2874
|
if (present.length !== 1)
|
|
2548
|
-
throw new
|
|
2875
|
+
throw new CliUsageError(`Provide exactly one of ${keys.map((k) => `--${k}`).join(", ")} (${label}).`);
|
|
2549
2876
|
return present[0];
|
|
2550
2877
|
}
|
|
2551
2878
|
function assertAtLeastOne(opts, keys) {
|
|
2552
2879
|
if (!keys.some((k) => opts[k] !== void 0))
|
|
2553
|
-
throw new
|
|
2880
|
+
throw new CliUsageError(`Provide at least one of ${keys.map((k) => `--${k}`).join(", ")}.`);
|
|
2554
2881
|
}
|
|
2555
2882
|
|
|
2556
2883
|
// src/commands/xray/execution/add.ts
|
|
@@ -2581,7 +2908,9 @@ function add(parent) {
|
|
|
2581
2908
|
}
|
|
2582
2909
|
const allKeys = [...keys];
|
|
2583
2910
|
if (allKeys.length === 0) {
|
|
2584
|
-
throw new
|
|
2911
|
+
throw new CliUsageError(
|
|
2912
|
+
"No tests to add \u2014 the given --set(s) contain no tests and no --test keys were provided."
|
|
2913
|
+
);
|
|
2585
2914
|
}
|
|
2586
2915
|
const result = await client.testExecutions.addTests({ execKey, keys: allKeys });
|
|
2587
2916
|
output(result ?? { added: allKeys });
|
|
@@ -2825,7 +3154,7 @@ function update7(parent) {
|
|
|
2825
3154
|
]);
|
|
2826
3155
|
cmd.action(async (folderId, opts) => {
|
|
2827
3156
|
if (opts.name === void 0 && opts.rank === void 0) {
|
|
2828
|
-
throw new
|
|
3157
|
+
throw new CliUsageError("Provide at least one of --name or --rank.");
|
|
2829
3158
|
}
|
|
2830
3159
|
const client = getClient();
|
|
2831
3160
|
await client.testRepository.updateFolder({
|
|
@@ -2855,12 +3184,12 @@ function registerFolderCommands(xray) {
|
|
|
2855
3184
|
// src/commands/xray/import/execution.ts
|
|
2856
3185
|
import { readFileSync as readFileSync4 } from "fs";
|
|
2857
3186
|
import { basename as basename2 } from "path";
|
|
2858
|
-
import { Option as
|
|
3187
|
+
import { Option as Option10 } from "commander";
|
|
2859
3188
|
var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
|
|
2860
3189
|
var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
|
|
2861
3190
|
function execution(parent) {
|
|
2862
3191
|
const cmd = parent.command("execution").description("Import test execution results into Xray").requiredOption("--file <path>", "Path to the result file", filePath).addOption(
|
|
2863
|
-
new
|
|
3192
|
+
new Option10(
|
|
2864
3193
|
"--format <format>",
|
|
2865
3194
|
"Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires --project OR --execution."
|
|
2866
3195
|
).choices(FORMATS).makeOptionMandatory()
|
|
@@ -2881,7 +3210,7 @@ function execution(parent) {
|
|
|
2881
3210
|
cmd.action(
|
|
2882
3211
|
async (opts) => {
|
|
2883
3212
|
if (NEEDS_SCOPE.has(opts.format) && !opts.project && !opts.execution) {
|
|
2884
|
-
throw new
|
|
3213
|
+
throw new CliUsageError(
|
|
2885
3214
|
`--project or --execution is required for ${opts.format} format. Provide --project <key> to create a new execution, or --execution <key> to import into an existing one.`
|
|
2886
3215
|
);
|
|
2887
3216
|
}
|
|
@@ -3058,7 +3387,7 @@ function add4(parent) {
|
|
|
3058
3387
|
}
|
|
3059
3388
|
|
|
3060
3389
|
// src/commands/xray/precondition/create.ts
|
|
3061
|
-
import { Option as
|
|
3390
|
+
import { Option as Option11 } from "commander";
|
|
3062
3391
|
|
|
3063
3392
|
// src/utils/xray/fields.ts
|
|
3064
3393
|
var CONCEPT_SUFFIX = {
|
|
@@ -3089,18 +3418,22 @@ function resolveTestTypeValue(allowedValues, type) {
|
|
|
3089
3418
|
if (type === "manual") {
|
|
3090
3419
|
const match2 = allowedValues.find((v) => /manual/i.test(v));
|
|
3091
3420
|
if (!match2)
|
|
3092
|
-
throw new
|
|
3421
|
+
throw new CliNotFoundError(
|
|
3422
|
+
`No 'manual' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
|
|
3423
|
+
);
|
|
3093
3424
|
return match2;
|
|
3094
3425
|
}
|
|
3095
3426
|
if (type === "generic") {
|
|
3096
3427
|
const match2 = allowedValues.find((v) => /generic/i.test(v));
|
|
3097
3428
|
if (!match2)
|
|
3098
|
-
throw new
|
|
3429
|
+
throw new CliNotFoundError(
|
|
3430
|
+
`No 'generic' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
|
|
3431
|
+
);
|
|
3099
3432
|
return match2;
|
|
3100
3433
|
}
|
|
3101
3434
|
const match = allowedValues.find((v) => !/manual/i.test(v) && !/generic/i.test(v));
|
|
3102
3435
|
if (!match)
|
|
3103
|
-
throw new
|
|
3436
|
+
throw new CliNotFoundError(
|
|
3104
3437
|
`No 'cucumber' (gherkin/automation) Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
|
|
3105
3438
|
);
|
|
3106
3439
|
return match;
|
|
@@ -3121,7 +3454,7 @@ async function resolveTypeValue(client, params) {
|
|
|
3121
3454
|
}
|
|
3122
3455
|
function requireField(map, concept, label) {
|
|
3123
3456
|
const id = map[concept];
|
|
3124
|
-
if (!id) throw new
|
|
3457
|
+
if (!id) throw new CliNotFoundError(`Xray field for '${label}' not found on this instance.`);
|
|
3125
3458
|
return id;
|
|
3126
3459
|
}
|
|
3127
3460
|
function buildTestCustomFields(map, opts) {
|
|
@@ -3159,7 +3492,7 @@ function create10(parent) {
|
|
|
3159
3492
|
const cmd = parent.command("create").description(
|
|
3160
3493
|
"Create a new Xray Pre-Condition issue. Note: the Pre-Condition issue type is only available in projects that have it configured (e.g. BD); it is not on your project scheme."
|
|
3161
3494
|
).requiredOption("--project <key>", "Project key (e.g. BD)", text).requiredOption("--summary <text>", "Pre-Condition summary (title)", text).addOption(
|
|
3162
|
-
new
|
|
3495
|
+
new Option11("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
|
|
3163
3496
|
);
|
|
3164
3497
|
textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
|
|
3165
3498
|
textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
|
|
@@ -3230,9 +3563,9 @@ function remove4(parent) {
|
|
|
3230
3563
|
}
|
|
3231
3564
|
|
|
3232
3565
|
// src/commands/xray/precondition/update.ts
|
|
3233
|
-
import { Option as
|
|
3566
|
+
import { Option as Option12 } from "commander";
|
|
3234
3567
|
function update9(parent) {
|
|
3235
|
-
const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new
|
|
3568
|
+
const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option12("--type <type>", "New Pre-Condition type").choices(["manual", "generic", "cucumber"]));
|
|
3236
3569
|
textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
|
|
3237
3570
|
textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
|
|
3238
3571
|
examples(cmd, ['PROJ-50 --summary "Updated precondition"', 'PROJ-50 --type generic --condition "apiKey != null"']);
|
|
@@ -3255,12 +3588,12 @@ function update9(parent) {
|
|
|
3255
3588
|
fieldMap: map
|
|
3256
3589
|
});
|
|
3257
3590
|
const typeId = map["precondition-type"];
|
|
3258
|
-
if (!typeId) throw new
|
|
3591
|
+
if (!typeId) throw new CliNotFoundError("Xray field for 'Pre-Condition Type' not found on this instance.");
|
|
3259
3592
|
customFields[typeId] = { value: typeValue };
|
|
3260
3593
|
}
|
|
3261
3594
|
if (condition !== void 0) {
|
|
3262
3595
|
const bodyId = map["precondition-body"];
|
|
3263
|
-
if (!bodyId) throw new
|
|
3596
|
+
if (!bodyId) throw new CliNotFoundError("Xray field for 'Conditions' not found on this instance.");
|
|
3264
3597
|
customFields[bodyId] = condition;
|
|
3265
3598
|
}
|
|
3266
3599
|
}
|
|
@@ -3291,9 +3624,10 @@ function registerPreconditionCommands(xray) {
|
|
|
3291
3624
|
// src/utils/xray/run-id.ts
|
|
3292
3625
|
async function resolveRunId(client, o) {
|
|
3293
3626
|
if (o.runId !== void 0) return o.runId;
|
|
3294
|
-
if (!o.execution || !o.test)
|
|
3627
|
+
if (!o.execution || !o.test)
|
|
3628
|
+
throw new CliUsageError("Provide a run id, or both --execution and --test to resolve the run.");
|
|
3295
3629
|
const run = await client.testRuns.resolve({ execKey: o.execution, testKey: o.test });
|
|
3296
|
-
if (!run?.id) throw new
|
|
3630
|
+
if (!run?.id) throw new CliNotFoundError(`No test run found for ${o.test} in ${o.execution}.`);
|
|
3297
3631
|
return Number(run.id);
|
|
3298
3632
|
}
|
|
3299
3633
|
|
|
@@ -3665,7 +3999,7 @@ function update12(parent) {
|
|
|
3665
3999
|
const data = resolveTextOrFile(opts, "data", { required: false });
|
|
3666
4000
|
const result = resolveTextOrFile(opts, "result", { required: false });
|
|
3667
4001
|
if (action === void 0 && data === void 0 && result === void 0) {
|
|
3668
|
-
throw new
|
|
4002
|
+
throw new CliUsageError(
|
|
3669
4003
|
"Provide at least one of --action (or --action-file), --data (or --data-file), --result (or --result-file)."
|
|
3670
4004
|
);
|
|
3671
4005
|
}
|
|
@@ -3689,16 +4023,16 @@ function registerStepCommands(xray) {
|
|
|
3689
4023
|
}
|
|
3690
4024
|
|
|
3691
4025
|
// src/commands/xray/test/create.ts
|
|
3692
|
-
import { Option as
|
|
3693
|
-
import { z as
|
|
3694
|
-
var stepsSchema =
|
|
3695
|
-
|
|
4026
|
+
import { Option as Option13 } from "commander";
|
|
4027
|
+
import { z as z5 } from "zod";
|
|
4028
|
+
var stepsSchema = z5.array(
|
|
4029
|
+
z5.object({ action: z5.string(), data: z5.string().optional(), result: z5.string().optional() })
|
|
3696
4030
|
);
|
|
3697
4031
|
function create11(parent) {
|
|
3698
4032
|
const cmd = parent.command("create").description("Create a new Xray Test issue").requiredOption("--project <key>", "Project key (e.g. AI)", text).requiredOption("--summary <text>", "Test summary (title)", text).addOption(
|
|
3699
|
-
new
|
|
4033
|
+
new Option13("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
|
|
3700
4034
|
).addOption(
|
|
3701
|
-
new
|
|
4035
|
+
new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
|
|
3702
4036
|
"scenario",
|
|
3703
4037
|
"scenario-outline"
|
|
3704
4038
|
])
|
|
@@ -3726,10 +4060,10 @@ function create11(parent) {
|
|
|
3726
4060
|
const definition = resolveTextOrFile(opts, "definition", { required: false });
|
|
3727
4061
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
3728
4062
|
if (opts.type === "cucumber" && !gherkin) {
|
|
3729
|
-
throw new
|
|
4063
|
+
throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
|
|
3730
4064
|
}
|
|
3731
4065
|
if (opts.type === "generic" && !definition) {
|
|
3732
|
-
throw new
|
|
4066
|
+
throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
|
|
3733
4067
|
}
|
|
3734
4068
|
const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
|
|
3735
4069
|
const client = getClient();
|
|
@@ -3804,13 +4138,13 @@ function list18(parent) {
|
|
|
3804
4138
|
const client = getClient();
|
|
3805
4139
|
if (opts.folder !== void 0) {
|
|
3806
4140
|
if (!opts.project) {
|
|
3807
|
-
throw new
|
|
4141
|
+
throw new CliUsageError("--project is required when --folder is specified.");
|
|
3808
4142
|
}
|
|
3809
4143
|
const otherScopes = ["set", "plan", "execution", "precondition"].filter(
|
|
3810
4144
|
(k) => opts[k] !== void 0
|
|
3811
4145
|
);
|
|
3812
4146
|
if (otherScopes.length > 0) {
|
|
3813
|
-
throw new
|
|
4147
|
+
throw new CliUsageError(
|
|
3814
4148
|
`--folder cannot be combined with ${otherScopes.map((k) => `--${k}`).join(", ")}. Use --folder with --project only.`
|
|
3815
4149
|
);
|
|
3816
4150
|
}
|
|
@@ -3868,10 +4202,10 @@ function list18(parent) {
|
|
|
3868
4202
|
}
|
|
3869
4203
|
|
|
3870
4204
|
// src/commands/xray/test/update.ts
|
|
3871
|
-
import { Option as
|
|
4205
|
+
import { Option as Option14 } from "commander";
|
|
3872
4206
|
function update13(parent) {
|
|
3873
|
-
const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new
|
|
3874
|
-
new
|
|
4207
|
+
const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option14("--type <type>", "Test type").choices(["manual", "cucumber", "generic"])).addOption(
|
|
4208
|
+
new Option14("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
|
|
3875
4209
|
"scenario",
|
|
3876
4210
|
"scenario-outline"
|
|
3877
4211
|
])
|
|
@@ -3902,10 +4236,10 @@ function update13(parent) {
|
|
|
3902
4236
|
if (opts.reporter !== void 0) fields.reporter = { name: opts.reporter };
|
|
3903
4237
|
if (opts.type !== void 0) {
|
|
3904
4238
|
if (opts.type === "cucumber" && !gherkin) {
|
|
3905
|
-
throw new
|
|
4239
|
+
throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
|
|
3906
4240
|
}
|
|
3907
4241
|
if (opts.type === "generic" && !definition) {
|
|
3908
|
-
throw new
|
|
4242
|
+
throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
|
|
3909
4243
|
}
|
|
3910
4244
|
const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
|
|
3911
4245
|
const projectKey = testKey.split("-")[0];
|
|
@@ -4059,7 +4393,7 @@ function registerXrayCommands(program) {
|
|
|
4059
4393
|
var DIM = "\x1B[2m";
|
|
4060
4394
|
var RESET = "\x1B[0m";
|
|
4061
4395
|
function buildProgram() {
|
|
4062
|
-
const program = new
|
|
4396
|
+
const program = new Command17();
|
|
4063
4397
|
program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
|
|
4064
4398
|
styleTitle: (str) => styleText("bold", str),
|
|
4065
4399
|
styleUsage: (str) => styleText("dim", str),
|
|
@@ -4120,10 +4454,33 @@ function getCredentialInfo() {
|
|
|
4120
4454
|
};
|
|
4121
4455
|
}
|
|
4122
4456
|
|
|
4457
|
+
// src/utils/field-error-recovery.ts
|
|
4458
|
+
var CUSTOM_FIELD_KEY = /^customfield_(\d+)$/;
|
|
4459
|
+
function customFieldIds(detail) {
|
|
4460
|
+
if (!detail || typeof detail !== "object") return [];
|
|
4461
|
+
const errors = detail.errors;
|
|
4462
|
+
if (!errors || typeof errors !== "object") return [];
|
|
4463
|
+
return Object.keys(errors).filter((k) => CUSTOM_FIELD_KEY.test(k));
|
|
4464
|
+
}
|
|
4465
|
+
function adaptFieldErrors(normalized) {
|
|
4466
|
+
const ids = customFieldIds(normalized.detail);
|
|
4467
|
+
if (ids.length === 0) return normalized;
|
|
4468
|
+
const numbers = ids.map((id) => CUSTOM_FIELD_KEY.exec(id)?.[1] ?? id);
|
|
4469
|
+
const lookup = numbers.map((n) => `jiradc field search ${n}`).join(", ");
|
|
4470
|
+
const plural = ids.length > 1 ? "s" : "";
|
|
4471
|
+
return {
|
|
4472
|
+
...normalized,
|
|
4473
|
+
recovery: `Jira rejected field${plural} ${ids.join(", ")}. Run ${lookup} to see the field's name and type before retrying. For the values a field accepts, use issue transitions (transition screens), issue createmeta (create) or issue editmeta (edit).`
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4123
4477
|
// src/index.ts
|
|
4124
4478
|
await runCli(buildProgram(), {
|
|
4125
4479
|
credentialInfo: getCredentialInfo,
|
|
4126
4480
|
service: "Jira",
|
|
4127
4481
|
authRecovery: "Set the JIRA_URL and JIRA_TOKEN environment variables.",
|
|
4128
|
-
urlEnvVar: "JIRA_URL"
|
|
4482
|
+
urlEnvVar: "JIRA_URL",
|
|
4483
|
+
// Jira names rejected fields by custom-field id. The generic recovery leaves
|
|
4484
|
+
// an agent holding an opaque id, so point it at the command that explains one.
|
|
4485
|
+
adapt: adaptFieldErrors
|
|
4129
4486
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiradc-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
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.44"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "24.10.4",
|