jiradc-cli 1.0.32 → 1.0.33
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 +246 -111
- package/package.json +4 -4
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
|
@@ -239,6 +239,7 @@ var CliAuthError = class extends Error {
|
|
|
239
239
|
};
|
|
240
240
|
var NOT_FOUND_RECOVERY = "Verify the id/key, then check your access.";
|
|
241
241
|
var NOT_FOUND_MESSAGE = "Not found: it may not exist, or you may not have permission to see it";
|
|
242
|
+
var USAGE_RECOVERY = "Check the command syntax and flags; run the command with --help.";
|
|
242
243
|
var CliNotFoundError = class extends Error {
|
|
243
244
|
recovery;
|
|
244
245
|
constructor(message, recovery = NOT_FOUND_RECOVERY) {
|
|
@@ -247,6 +248,16 @@ var CliNotFoundError = class extends Error {
|
|
|
247
248
|
this.recovery = recovery;
|
|
248
249
|
}
|
|
249
250
|
};
|
|
251
|
+
var CliUsageError = class extends Error {
|
|
252
|
+
recovery;
|
|
253
|
+
detail;
|
|
254
|
+
constructor(message, recovery = USAGE_RECOVERY, detail) {
|
|
255
|
+
super(message);
|
|
256
|
+
this.name = "CliUsageError";
|
|
257
|
+
this.recovery = recovery;
|
|
258
|
+
this.detail = detail;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
250
261
|
var SubcommandRequiredError = class extends Error {
|
|
251
262
|
subcommands;
|
|
252
263
|
constructor(commandPath3, subcommands) {
|
|
@@ -363,6 +374,15 @@ function normalize(err, opts) {
|
|
|
363
374
|
if (err instanceof CliNotFoundError) {
|
|
364
375
|
return { type: "not_found", message, recovery: err.recovery, retryable: false };
|
|
365
376
|
}
|
|
377
|
+
if (err instanceof CliUsageError) {
|
|
378
|
+
return {
|
|
379
|
+
type: "usage",
|
|
380
|
+
message,
|
|
381
|
+
recovery: err.recovery,
|
|
382
|
+
retryable: false,
|
|
383
|
+
...err.detail !== void 0 ? { detail: err.detail } : {}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
366
386
|
const status = httpStatus(err);
|
|
367
387
|
const detail = responseDetail(err);
|
|
368
388
|
if (status !== void 0) {
|
|
@@ -568,7 +588,7 @@ async function runCli(program, opts) {
|
|
|
568
588
|
|
|
569
589
|
// src/program.ts
|
|
570
590
|
import { styleText } from "util";
|
|
571
|
-
import { Command as
|
|
591
|
+
import { Command as Command17 } from "commander";
|
|
572
592
|
|
|
573
593
|
// src/commands/board/issues.ts
|
|
574
594
|
import { Argument } from "commander";
|
|
@@ -623,9 +643,28 @@ function transformUser(user) {
|
|
|
623
643
|
function transformField(field) {
|
|
624
644
|
return field;
|
|
625
645
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
646
|
+
|
|
647
|
+
// src/utils/transformers/field-meta.ts
|
|
648
|
+
function slimAllowedValue(raw) {
|
|
649
|
+
const out = {};
|
|
650
|
+
if (typeof raw.id === "string") out.id = raw.id;
|
|
651
|
+
if (typeof raw.name === "string") out.name = raw.name;
|
|
652
|
+
if (typeof raw.value === "string") out.value = raw.value;
|
|
653
|
+
if (Array.isArray(raw.children) && raw.children.length > 0) out.children = raw.children.map(slimAllowedValue);
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
function transformFieldMetaEntry(id, meta) {
|
|
657
|
+
const out = { id, required: meta.required === true };
|
|
658
|
+
if (typeof meta.name === "string") out.name = meta.name;
|
|
659
|
+
if (typeof meta.schema?.type === "string") out.type = meta.schema.type;
|
|
660
|
+
if (Array.isArray(meta.allowedValues) && meta.allowedValues.length > 0) {
|
|
661
|
+
out.allowedValues = meta.allowedValues.map(slimAllowedValue);
|
|
662
|
+
}
|
|
663
|
+
return out;
|
|
664
|
+
}
|
|
665
|
+
function transformFieldMeta(fields) {
|
|
666
|
+
const entries = Object.entries(fields).map(([id, meta]) => transformFieldMetaEntry(id, meta));
|
|
667
|
+
return [...entries.filter((f) => f.required), ...entries.filter((f) => !f.required)];
|
|
629
668
|
}
|
|
630
669
|
|
|
631
670
|
// src/utils/transformers/project.ts
|
|
@@ -807,6 +846,29 @@ function pruneSentinels(fields) {
|
|
|
807
846
|
if (watches && watches.watchCount === 0) delete out.watches;
|
|
808
847
|
return out;
|
|
809
848
|
}
|
|
849
|
+
var ISSUE_KEY_PATTERN = /^[A-Z][A-Z0-9_]*-\d+$/;
|
|
850
|
+
function asIssueRefKey(value) {
|
|
851
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
852
|
+
const o = value;
|
|
853
|
+
if (typeof o.key !== "string" || !ISSUE_KEY_PATTERN.test(o.key)) return void 0;
|
|
854
|
+
if (typeof o.self !== "string" || !o.self.includes("/issue/")) return void 0;
|
|
855
|
+
return o.key;
|
|
856
|
+
}
|
|
857
|
+
function collapseIssueRefs(fields) {
|
|
858
|
+
const out = { ...fields };
|
|
859
|
+
for (const [key, value] of Object.entries(out)) {
|
|
860
|
+
const single = asIssueRefKey(value);
|
|
861
|
+
if (single !== void 0) {
|
|
862
|
+
out[key] = single;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
866
|
+
const keys = value.map(asIssueRefKey);
|
|
867
|
+
if (keys.every((k) => k !== void 0)) out[key] = keys;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
return out;
|
|
871
|
+
}
|
|
810
872
|
function transformIssueFields(fields) {
|
|
811
873
|
const {
|
|
812
874
|
// shaped sub-entities (recursed individually)
|
|
@@ -833,7 +895,7 @@ function transformIssueFields(fields) {
|
|
|
833
895
|
// everything else: optional scalars, customfield_*, etc. — gets compacted
|
|
834
896
|
...rest
|
|
835
897
|
} = fields;
|
|
836
|
-
const compacted = pruneSentinels(compactRecord(rest));
|
|
898
|
+
const compacted = collapseIssueRefs(pruneSentinels(compactRecord(rest)));
|
|
837
899
|
return {
|
|
838
900
|
...compacted,
|
|
839
901
|
summary,
|
|
@@ -876,23 +938,23 @@ function transformIssueFields(fields) {
|
|
|
876
938
|
}
|
|
877
939
|
|
|
878
940
|
// src/utils/transformers/transition.ts
|
|
879
|
-
function
|
|
941
|
+
function slimAllowedValue2(raw) {
|
|
880
942
|
if (!raw || typeof raw !== "object") return {};
|
|
881
943
|
const av = raw;
|
|
882
944
|
const slim = {};
|
|
883
945
|
if (typeof av.id === "string") slim.id = av.id;
|
|
884
946
|
if (typeof av.name === "string") slim.name = av.name;
|
|
885
947
|
if (typeof av.value === "string") slim.value = av.value;
|
|
948
|
+
if (Array.isArray(av.children) && av.children.length > 0) slim.children = av.children.map(slimAllowedValue2);
|
|
886
949
|
return slim;
|
|
887
950
|
}
|
|
888
951
|
function slimField(raw) {
|
|
889
952
|
if (!raw || typeof raw !== "object") return {};
|
|
890
953
|
const field = raw;
|
|
891
954
|
const out = {};
|
|
892
|
-
if (typeof field.required === "boolean") out.required = field.required;
|
|
893
955
|
if (typeof field.name === "string") out.name = field.name;
|
|
894
956
|
if (Array.isArray(field.allowedValues)) {
|
|
895
|
-
out.allowedValues = field.allowedValues.map(
|
|
957
|
+
out.allowedValues = field.allowedValues.map(slimAllowedValue2);
|
|
896
958
|
}
|
|
897
959
|
return out;
|
|
898
960
|
}
|
|
@@ -1190,7 +1252,7 @@ function update(parent) {
|
|
|
1190
1252
|
async (id, opts) => {
|
|
1191
1253
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
1192
1254
|
if (opts.name === void 0 && description === void 0 && opts.lead === void 0 && opts.assigneeType === void 0) {
|
|
1193
|
-
throw new
|
|
1255
|
+
throw new CliUsageError("Provide at least one of --name, --description, --lead, --assignee-type");
|
|
1194
1256
|
}
|
|
1195
1257
|
const client = getClient();
|
|
1196
1258
|
const result = await client.components.update({
|
|
@@ -1224,22 +1286,6 @@ function registerComponentCommands(program) {
|
|
|
1224
1286
|
issueCount(component);
|
|
1225
1287
|
}
|
|
1226
1288
|
|
|
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
1289
|
// src/commands/field/search.ts
|
|
1244
1290
|
function search(parent) {
|
|
1245
1291
|
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 +1300,8 @@ function search(parent) {
|
|
|
1254
1300
|
// src/commands/field/index.ts
|
|
1255
1301
|
function registerFieldCommands(program) {
|
|
1256
1302
|
const field = program.command("field").description("Field operations");
|
|
1257
|
-
examples(field, ['search "epic"', 'search "priority"'
|
|
1303
|
+
examples(field, ['search "epic"', 'search "priority"']);
|
|
1258
1304
|
search(field);
|
|
1259
|
-
options(field);
|
|
1260
1305
|
}
|
|
1261
1306
|
|
|
1262
1307
|
// src/utils/resolve-user.ts
|
|
@@ -1667,13 +1712,16 @@ function registerCommentCommands(parent) {
|
|
|
1667
1712
|
}
|
|
1668
1713
|
|
|
1669
1714
|
// src/commands/issue/create.ts
|
|
1715
|
+
import { Option as Option5 } from "commander";
|
|
1670
1716
|
import { z as z2 } from "zod";
|
|
1671
|
-
var
|
|
1717
|
+
var fieldsSchema = z2.record(z2.unknown());
|
|
1672
1718
|
function create3(parent) {
|
|
1673
1719
|
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(
|
|
1720
|
+
"--fields <json>",
|
|
1721
|
+
`Additional fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
|
|
1722
|
+
jsonShape(fieldsSchema)
|
|
1723
|
+
).addOption(
|
|
1724
|
+
new Option5("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
|
|
1677
1725
|
);
|
|
1678
1726
|
textOrFileOption(cmd, "description", { description: "Issue description in wiki markup" });
|
|
1679
1727
|
examples(cmd, [
|
|
@@ -1684,6 +1732,13 @@ function create3(parent) {
|
|
|
1684
1732
|
cmd.action(
|
|
1685
1733
|
async (opts) => {
|
|
1686
1734
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
1735
|
+
if (opts.fields && opts.customFields) {
|
|
1736
|
+
throw new CliUsageError(
|
|
1737
|
+
"Pass either --fields or --custom-fields, not both",
|
|
1738
|
+
"--custom-fields is a deprecated alias for --fields. Merge the two JSON objects into a single --fields value."
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
const fields = opts.fields ?? opts.customFields;
|
|
1687
1742
|
const assignee = opts.assignee !== void 0 ? await resolveUserToken(opts.assignee) : void 0;
|
|
1688
1743
|
const reporter = opts.reporter !== void 0 ? await resolveUserToken(opts.reporter) : void 0;
|
|
1689
1744
|
const client = getClient();
|
|
@@ -1700,13 +1755,31 @@ function create3(parent) {
|
|
|
1700
1755
|
fixVersions: opts.fixVersions?.split(",").map((v) => v.trim()),
|
|
1701
1756
|
dueDate: opts.dueDate,
|
|
1702
1757
|
parent: opts.parent,
|
|
1703
|
-
customFields:
|
|
1758
|
+
customFields: fields
|
|
1704
1759
|
});
|
|
1705
1760
|
output(transformCreatedIssue(result));
|
|
1706
1761
|
}
|
|
1707
1762
|
);
|
|
1708
1763
|
}
|
|
1709
1764
|
|
|
1765
|
+
// src/commands/issue/createmeta.ts
|
|
1766
|
+
function createmeta(parent) {
|
|
1767
|
+
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);
|
|
1768
|
+
examples(cmd, ["--project PROJ --type Task", "--project PROJ --type Bug"]);
|
|
1769
|
+
cmd.action(async (opts) => {
|
|
1770
|
+
const client = getClient();
|
|
1771
|
+
const meta = await client.issues.getCreateMeta({ projectKey: opts.project, issueTypeName: opts.type });
|
|
1772
|
+
const issueType = meta.projects?.[0]?.issuetypes?.[0];
|
|
1773
|
+
if (!issueType?.fields) {
|
|
1774
|
+
throw new CliNotFoundError(
|
|
1775
|
+
`No create metadata for issue type "${opts.type}" in project "${opts.project}"`,
|
|
1776
|
+
"Verify the project key and issue type name, then check you may create that type there."
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
output(transformFieldMeta(issueType.fields));
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1710
1783
|
// src/commands/issue/delete.ts
|
|
1711
1784
|
function deleteIssue(parent) {
|
|
1712
1785
|
const cmd = parent.command("delete").description("Delete an issue").argument("<key>", "Issue key", issueKey).option("--delete-subtasks", "Also delete subtasks (default: false)");
|
|
@@ -1796,6 +1869,17 @@ function devStatus(parent) {
|
|
|
1796
1869
|
});
|
|
1797
1870
|
}
|
|
1798
1871
|
|
|
1872
|
+
// src/commands/issue/editmeta.ts
|
|
1873
|
+
function editmeta(parent) {
|
|
1874
|
+
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);
|
|
1875
|
+
examples(cmd, ["PROJ-123"]);
|
|
1876
|
+
cmd.action(async (key) => {
|
|
1877
|
+
const client = getClient();
|
|
1878
|
+
const meta = await client.issues.getEditMeta({ issueKeyOrId: key });
|
|
1879
|
+
output(transformFieldMeta(meta.fields ?? {}));
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1799
1883
|
// src/utils/constants.ts
|
|
1800
1884
|
var DEFAULT_FIELDS = [
|
|
1801
1885
|
"summary",
|
|
@@ -1924,41 +2008,60 @@ function search2(parent) {
|
|
|
1924
2008
|
}
|
|
1925
2009
|
|
|
1926
2010
|
// src/commands/issue/transition.ts
|
|
2011
|
+
import { z as z3 } from "zod";
|
|
2012
|
+
var fieldsSchema2 = z3.record(z3.unknown());
|
|
2013
|
+
function resolveTransitionId(to, transitions2) {
|
|
2014
|
+
const target = to.toLowerCase();
|
|
2015
|
+
const byName = transitions2.filter((t) => t.name.toLowerCase() === target);
|
|
2016
|
+
const matches = byName.length > 0 ? byName : transitions2.filter((t) => t.to.toLowerCase() === target);
|
|
2017
|
+
if (matches.length === 1) return matches[0].id;
|
|
2018
|
+
const available = transitions2.map((t) => ({ id: t.id, name: t.name, to: t.to }));
|
|
2019
|
+
if (matches.length === 0) {
|
|
2020
|
+
throw new CliUsageError(
|
|
2021
|
+
`Transition "${to}" not available from current status`,
|
|
2022
|
+
"Re-run --to with one of the listed transitions (by name, id, or target status).",
|
|
2023
|
+
{ available }
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
const ids = matches.map((t) => t.id).join(", ");
|
|
2027
|
+
throw new CliUsageError(
|
|
2028
|
+
`"${to}" matches more than one available transition`,
|
|
2029
|
+
`Re-run --to with one of these ids: ${ids}.`,
|
|
2030
|
+
{ available }
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
1927
2033
|
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)
|
|
2034
|
+
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(
|
|
2035
|
+
"--fields <json>",
|
|
2036
|
+
`Fields required by the transition screen, as JSON (e.g. '{"customfield_10100": {"value": "Approved"}}')`,
|
|
2037
|
+
jsonShape(fieldsSchema2)
|
|
2038
|
+
);
|
|
1929
2039
|
commentOption(cmd, { description: "Comment to add during transition" });
|
|
1930
2040
|
examples(cmd, [
|
|
1931
2041
|
"PROJ-123 --to 31",
|
|
2042
|
+
'PROJ-123 --to "Start Review"',
|
|
1932
2043
|
'PROJ-123 --to "In Review"',
|
|
1933
|
-
'PROJ-123 --to Done --comment "Verified in staging"'
|
|
2044
|
+
'PROJ-123 --to Done --comment "Verified in staging"',
|
|
2045
|
+
`PROJ-123 --to Approve --fields '{"customfield_10100": {"value": "Approved"}}'`
|
|
1934
2046
|
]);
|
|
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}`);
|
|
2047
|
+
cmd.action(
|
|
2048
|
+
async (key, opts) => {
|
|
2049
|
+
const comment = resolveTextOrFile(opts, "comment", { required: false });
|
|
2050
|
+
const client = getClient();
|
|
2051
|
+
let transitionId;
|
|
2052
|
+
if (/^\d+$/.test(opts.to)) {
|
|
2053
|
+
transitionId = opts.to;
|
|
2054
|
+
} else {
|
|
2055
|
+
const raw = await client.issues.getTransitions({
|
|
2056
|
+
issueKeyOrId: key,
|
|
2057
|
+
expand: "transitions.fields"
|
|
2058
|
+
});
|
|
2059
|
+
transitionId = resolveTransitionId(opts.to, transformTransitions(raw));
|
|
1956
2060
|
}
|
|
1957
|
-
transitionId
|
|
2061
|
+
await client.issues.transition({ issueKeyOrId: key, transitionId, fields: opts.fields, comment });
|
|
2062
|
+
output({ transitioned: true, issue: transformIssueRef(key) });
|
|
1958
2063
|
}
|
|
1959
|
-
|
|
1960
|
-
output({ transitioned: true, issue: transformIssueRef(key) });
|
|
1961
|
-
});
|
|
2064
|
+
);
|
|
1962
2065
|
}
|
|
1963
2066
|
|
|
1964
2067
|
// src/commands/issue/transitions.ts
|
|
@@ -1987,7 +2090,7 @@ function unlink2(parent) {
|
|
|
1987
2090
|
}
|
|
1988
2091
|
|
|
1989
2092
|
// src/commands/issue/update.ts
|
|
1990
|
-
import { z as
|
|
2093
|
+
import { z as z4 } from "zod";
|
|
1991
2094
|
|
|
1992
2095
|
// src/utils/multi-value.ts
|
|
1993
2096
|
import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
|
|
@@ -2016,7 +2119,7 @@ function parseMultiValue(flagName, raw) {
|
|
|
2016
2119
|
}
|
|
2017
2120
|
|
|
2018
2121
|
// src/commands/issue/update.ts
|
|
2019
|
-
var
|
|
2122
|
+
var fieldsSchema3 = z4.record(z4.unknown());
|
|
2020
2123
|
function buildUpdateOps(parsed, wrap) {
|
|
2021
2124
|
if (parsed.mode !== "mutate") return void 0;
|
|
2022
2125
|
return [...parsed.adds.map((v) => ({ add: wrap(v) })), ...parsed.removes.map((v) => ({ remove: wrap(v) }))];
|
|
@@ -2029,7 +2132,7 @@ function update3(parent) {
|
|
|
2029
2132
|
const cmd = parent.command("update").description("Update issue fields").argument("<key>", "Issue key", issueKey).option(
|
|
2030
2133
|
"--fields <json>",
|
|
2031
2134
|
"JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)",
|
|
2032
|
-
jsonShape(
|
|
2135
|
+
jsonShape(fieldsSchema3)
|
|
2033
2136
|
).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
2137
|
textOrFileOption(cmd, "description", { description: "Set the issue description (wiki markup)" });
|
|
2035
2138
|
examples(cmd, [
|
|
@@ -2044,7 +2147,7 @@ function update3(parent) {
|
|
|
2044
2147
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
2045
2148
|
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
2149
|
if (!opts.fields && !opts.attachments && !hasShortcut) {
|
|
2047
|
-
throw new
|
|
2150
|
+
throw new CliUsageError(
|
|
2048
2151
|
"Provide at least one of --fields, --attachments, or a shortcut flag (--summary, --priority, --labels, ...)"
|
|
2049
2152
|
);
|
|
2050
2153
|
}
|
|
@@ -2132,12 +2235,12 @@ function create4(parent) {
|
|
|
2132
2235
|
}
|
|
2133
2236
|
|
|
2134
2237
|
// src/commands/issue/worklog/delete.ts
|
|
2135
|
-
import { Option as
|
|
2238
|
+
import { Option as Option6 } from "commander";
|
|
2136
2239
|
var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
|
|
2137
2240
|
function deleteWorklog(parent) {
|
|
2138
2241
|
const cmd = parent.command("delete").description("Delete a worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2139
2242
|
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
2140
|
-
cmd.addOption(new
|
|
2243
|
+
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
2244
|
"--increase-by <amount>",
|
|
2142
2245
|
'Amount to increase the estimate by; required when --adjust-estimate is "manual"',
|
|
2143
2246
|
text
|
|
@@ -2178,12 +2281,12 @@ function list4(parent) {
|
|
|
2178
2281
|
}
|
|
2179
2282
|
|
|
2180
2283
|
// src/commands/issue/worklog/update.ts
|
|
2181
|
-
import { Option as
|
|
2284
|
+
import { Option as Option7 } from "commander";
|
|
2182
2285
|
var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
|
|
2183
2286
|
function update4(parent) {
|
|
2184
2287
|
const cmd = parent.command("update").description("Update an existing worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2185
2288
|
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
|
|
2289
|
+
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
2290
|
commentOption(cmd, { description: "Worklog comment" });
|
|
2188
2291
|
examples(cmd, [
|
|
2189
2292
|
'PROJ-123 --worklog-id 12345 --time "1h 30m"',
|
|
@@ -2241,6 +2344,8 @@ function registerIssueCommands(program) {
|
|
|
2241
2344
|
deleteIssue(issue);
|
|
2242
2345
|
transition(issue);
|
|
2243
2346
|
transitions(issue);
|
|
2347
|
+
createmeta(issue);
|
|
2348
|
+
editmeta(issue);
|
|
2244
2349
|
assign(issue);
|
|
2245
2350
|
registerCommentCommands(issue);
|
|
2246
2351
|
registerWorklogCommands(issue);
|
|
@@ -2342,10 +2447,10 @@ function issues2(parent) {
|
|
|
2342
2447
|
}
|
|
2343
2448
|
|
|
2344
2449
|
// src/commands/sprint/list.ts
|
|
2345
|
-
import { Option as
|
|
2450
|
+
import { Option as Option8 } from "commander";
|
|
2346
2451
|
var SPRINT_STATES = ["future", "active", "closed"];
|
|
2347
2452
|
function list6(parent) {
|
|
2348
|
-
const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new
|
|
2453
|
+
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
2454
|
examples(cmd, ["--board 42", "--board 42 --state active"]);
|
|
2350
2455
|
cmd.action(async (opts) => {
|
|
2351
2456
|
const client = getClient();
|
|
@@ -2358,10 +2463,10 @@ function list6(parent) {
|
|
|
2358
2463
|
}
|
|
2359
2464
|
|
|
2360
2465
|
// src/commands/sprint/update.ts
|
|
2361
|
-
import { Argument as Argument4, Option as
|
|
2466
|
+
import { Argument as Argument4, Option as Option9 } from "commander";
|
|
2362
2467
|
var SPRINT_STATES2 = ["future", "active", "closed"];
|
|
2363
2468
|
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
|
|
2469
|
+
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
2470
|
examples(cmd, [
|
|
2366
2471
|
'100 --name "Sprint 10 - Extended"',
|
|
2367
2472
|
"100 --state active",
|
|
@@ -2401,10 +2506,10 @@ function registerSprintCommands(program) {
|
|
|
2401
2506
|
|
|
2402
2507
|
// src/commands/token/client.ts
|
|
2403
2508
|
import { JiraClient as JiraClient2 } from "jira-data-center-client";
|
|
2404
|
-
function getTokenClient(
|
|
2509
|
+
function getTokenClient(options = {}) {
|
|
2405
2510
|
const baseUrl = process.env.JIRA_URL;
|
|
2406
|
-
const username =
|
|
2407
|
-
const password =
|
|
2511
|
+
const username = options.basicUsername ?? process.env.JIRA_BASIC_USERNAME;
|
|
2512
|
+
const password = options.basicPassword ?? process.env.JIRA_BASIC_PASSWORD;
|
|
2408
2513
|
if (!baseUrl || !username || !password) {
|
|
2409
2514
|
const missing = [
|
|
2410
2515
|
...!baseUrl ? ["JIRA_URL"] : [],
|
|
@@ -2545,12 +2650,12 @@ function registerUserCommands(program) {
|
|
|
2545
2650
|
function assertExactlyOne(opts, keys, label) {
|
|
2546
2651
|
const present = keys.filter((k) => opts[k] !== void 0);
|
|
2547
2652
|
if (present.length !== 1)
|
|
2548
|
-
throw new
|
|
2653
|
+
throw new CliUsageError(`Provide exactly one of ${keys.map((k) => `--${k}`).join(", ")} (${label}).`);
|
|
2549
2654
|
return present[0];
|
|
2550
2655
|
}
|
|
2551
2656
|
function assertAtLeastOne(opts, keys) {
|
|
2552
2657
|
if (!keys.some((k) => opts[k] !== void 0))
|
|
2553
|
-
throw new
|
|
2658
|
+
throw new CliUsageError(`Provide at least one of ${keys.map((k) => `--${k}`).join(", ")}.`);
|
|
2554
2659
|
}
|
|
2555
2660
|
|
|
2556
2661
|
// src/commands/xray/execution/add.ts
|
|
@@ -2581,7 +2686,9 @@ function add(parent) {
|
|
|
2581
2686
|
}
|
|
2582
2687
|
const allKeys = [...keys];
|
|
2583
2688
|
if (allKeys.length === 0) {
|
|
2584
|
-
throw new
|
|
2689
|
+
throw new CliUsageError(
|
|
2690
|
+
"No tests to add \u2014 the given --set(s) contain no tests and no --test keys were provided."
|
|
2691
|
+
);
|
|
2585
2692
|
}
|
|
2586
2693
|
const result = await client.testExecutions.addTests({ execKey, keys: allKeys });
|
|
2587
2694
|
output(result ?? { added: allKeys });
|
|
@@ -2825,7 +2932,7 @@ function update7(parent) {
|
|
|
2825
2932
|
]);
|
|
2826
2933
|
cmd.action(async (folderId, opts) => {
|
|
2827
2934
|
if (opts.name === void 0 && opts.rank === void 0) {
|
|
2828
|
-
throw new
|
|
2935
|
+
throw new CliUsageError("Provide at least one of --name or --rank.");
|
|
2829
2936
|
}
|
|
2830
2937
|
const client = getClient();
|
|
2831
2938
|
await client.testRepository.updateFolder({
|
|
@@ -2855,12 +2962,12 @@ function registerFolderCommands(xray) {
|
|
|
2855
2962
|
// src/commands/xray/import/execution.ts
|
|
2856
2963
|
import { readFileSync as readFileSync4 } from "fs";
|
|
2857
2964
|
import { basename as basename2 } from "path";
|
|
2858
|
-
import { Option as
|
|
2965
|
+
import { Option as Option10 } from "commander";
|
|
2859
2966
|
var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
|
|
2860
2967
|
var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
|
|
2861
2968
|
function execution(parent) {
|
|
2862
2969
|
const cmd = parent.command("execution").description("Import test execution results into Xray").requiredOption("--file <path>", "Path to the result file", filePath).addOption(
|
|
2863
|
-
new
|
|
2970
|
+
new Option10(
|
|
2864
2971
|
"--format <format>",
|
|
2865
2972
|
"Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires --project OR --execution."
|
|
2866
2973
|
).choices(FORMATS).makeOptionMandatory()
|
|
@@ -2881,7 +2988,7 @@ function execution(parent) {
|
|
|
2881
2988
|
cmd.action(
|
|
2882
2989
|
async (opts) => {
|
|
2883
2990
|
if (NEEDS_SCOPE.has(opts.format) && !opts.project && !opts.execution) {
|
|
2884
|
-
throw new
|
|
2991
|
+
throw new CliUsageError(
|
|
2885
2992
|
`--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
2993
|
);
|
|
2887
2994
|
}
|
|
@@ -3058,7 +3165,7 @@ function add4(parent) {
|
|
|
3058
3165
|
}
|
|
3059
3166
|
|
|
3060
3167
|
// src/commands/xray/precondition/create.ts
|
|
3061
|
-
import { Option as
|
|
3168
|
+
import { Option as Option11 } from "commander";
|
|
3062
3169
|
|
|
3063
3170
|
// src/utils/xray/fields.ts
|
|
3064
3171
|
var CONCEPT_SUFFIX = {
|
|
@@ -3089,18 +3196,22 @@ function resolveTestTypeValue(allowedValues, type) {
|
|
|
3089
3196
|
if (type === "manual") {
|
|
3090
3197
|
const match2 = allowedValues.find((v) => /manual/i.test(v));
|
|
3091
3198
|
if (!match2)
|
|
3092
|
-
throw new
|
|
3199
|
+
throw new CliNotFoundError(
|
|
3200
|
+
`No 'manual' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
|
|
3201
|
+
);
|
|
3093
3202
|
return match2;
|
|
3094
3203
|
}
|
|
3095
3204
|
if (type === "generic") {
|
|
3096
3205
|
const match2 = allowedValues.find((v) => /generic/i.test(v));
|
|
3097
3206
|
if (!match2)
|
|
3098
|
-
throw new
|
|
3207
|
+
throw new CliNotFoundError(
|
|
3208
|
+
`No 'generic' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
|
|
3209
|
+
);
|
|
3099
3210
|
return match2;
|
|
3100
3211
|
}
|
|
3101
3212
|
const match = allowedValues.find((v) => !/manual/i.test(v) && !/generic/i.test(v));
|
|
3102
3213
|
if (!match)
|
|
3103
|
-
throw new
|
|
3214
|
+
throw new CliNotFoundError(
|
|
3104
3215
|
`No 'cucumber' (gherkin/automation) Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
|
|
3105
3216
|
);
|
|
3106
3217
|
return match;
|
|
@@ -3121,7 +3232,7 @@ async function resolveTypeValue(client, params) {
|
|
|
3121
3232
|
}
|
|
3122
3233
|
function requireField(map, concept, label) {
|
|
3123
3234
|
const id = map[concept];
|
|
3124
|
-
if (!id) throw new
|
|
3235
|
+
if (!id) throw new CliNotFoundError(`Xray field for '${label}' not found on this instance.`);
|
|
3125
3236
|
return id;
|
|
3126
3237
|
}
|
|
3127
3238
|
function buildTestCustomFields(map, opts) {
|
|
@@ -3159,7 +3270,7 @@ function create10(parent) {
|
|
|
3159
3270
|
const cmd = parent.command("create").description(
|
|
3160
3271
|
"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
3272
|
).requiredOption("--project <key>", "Project key (e.g. BD)", text).requiredOption("--summary <text>", "Pre-Condition summary (title)", text).addOption(
|
|
3162
|
-
new
|
|
3273
|
+
new Option11("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
|
|
3163
3274
|
);
|
|
3164
3275
|
textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
|
|
3165
3276
|
textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
|
|
@@ -3230,9 +3341,9 @@ function remove4(parent) {
|
|
|
3230
3341
|
}
|
|
3231
3342
|
|
|
3232
3343
|
// src/commands/xray/precondition/update.ts
|
|
3233
|
-
import { Option as
|
|
3344
|
+
import { Option as Option12 } from "commander";
|
|
3234
3345
|
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
|
|
3346
|
+
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
3347
|
textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
|
|
3237
3348
|
textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
|
|
3238
3349
|
examples(cmd, ['PROJ-50 --summary "Updated precondition"', 'PROJ-50 --type generic --condition "apiKey != null"']);
|
|
@@ -3255,12 +3366,12 @@ function update9(parent) {
|
|
|
3255
3366
|
fieldMap: map
|
|
3256
3367
|
});
|
|
3257
3368
|
const typeId = map["precondition-type"];
|
|
3258
|
-
if (!typeId) throw new
|
|
3369
|
+
if (!typeId) throw new CliNotFoundError("Xray field for 'Pre-Condition Type' not found on this instance.");
|
|
3259
3370
|
customFields[typeId] = { value: typeValue };
|
|
3260
3371
|
}
|
|
3261
3372
|
if (condition !== void 0) {
|
|
3262
3373
|
const bodyId = map["precondition-body"];
|
|
3263
|
-
if (!bodyId) throw new
|
|
3374
|
+
if (!bodyId) throw new CliNotFoundError("Xray field for 'Conditions' not found on this instance.");
|
|
3264
3375
|
customFields[bodyId] = condition;
|
|
3265
3376
|
}
|
|
3266
3377
|
}
|
|
@@ -3291,9 +3402,10 @@ function registerPreconditionCommands(xray) {
|
|
|
3291
3402
|
// src/utils/xray/run-id.ts
|
|
3292
3403
|
async function resolveRunId(client, o) {
|
|
3293
3404
|
if (o.runId !== void 0) return o.runId;
|
|
3294
|
-
if (!o.execution || !o.test)
|
|
3405
|
+
if (!o.execution || !o.test)
|
|
3406
|
+
throw new CliUsageError("Provide a run id, or both --execution and --test to resolve the run.");
|
|
3295
3407
|
const run = await client.testRuns.resolve({ execKey: o.execution, testKey: o.test });
|
|
3296
|
-
if (!run?.id) throw new
|
|
3408
|
+
if (!run?.id) throw new CliNotFoundError(`No test run found for ${o.test} in ${o.execution}.`);
|
|
3297
3409
|
return Number(run.id);
|
|
3298
3410
|
}
|
|
3299
3411
|
|
|
@@ -3665,7 +3777,7 @@ function update12(parent) {
|
|
|
3665
3777
|
const data = resolveTextOrFile(opts, "data", { required: false });
|
|
3666
3778
|
const result = resolveTextOrFile(opts, "result", { required: false });
|
|
3667
3779
|
if (action === void 0 && data === void 0 && result === void 0) {
|
|
3668
|
-
throw new
|
|
3780
|
+
throw new CliUsageError(
|
|
3669
3781
|
"Provide at least one of --action (or --action-file), --data (or --data-file), --result (or --result-file)."
|
|
3670
3782
|
);
|
|
3671
3783
|
}
|
|
@@ -3689,16 +3801,16 @@ function registerStepCommands(xray) {
|
|
|
3689
3801
|
}
|
|
3690
3802
|
|
|
3691
3803
|
// src/commands/xray/test/create.ts
|
|
3692
|
-
import { Option as
|
|
3693
|
-
import { z as
|
|
3694
|
-
var stepsSchema =
|
|
3695
|
-
|
|
3804
|
+
import { Option as Option13 } from "commander";
|
|
3805
|
+
import { z as z5 } from "zod";
|
|
3806
|
+
var stepsSchema = z5.array(
|
|
3807
|
+
z5.object({ action: z5.string(), data: z5.string().optional(), result: z5.string().optional() })
|
|
3696
3808
|
);
|
|
3697
3809
|
function create11(parent) {
|
|
3698
3810
|
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
|
|
3811
|
+
new Option13("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
|
|
3700
3812
|
).addOption(
|
|
3701
|
-
new
|
|
3813
|
+
new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
|
|
3702
3814
|
"scenario",
|
|
3703
3815
|
"scenario-outline"
|
|
3704
3816
|
])
|
|
@@ -3726,10 +3838,10 @@ function create11(parent) {
|
|
|
3726
3838
|
const definition = resolveTextOrFile(opts, "definition", { required: false });
|
|
3727
3839
|
const description = resolveTextOrFile(opts, "description", { required: false });
|
|
3728
3840
|
if (opts.type === "cucumber" && !gherkin) {
|
|
3729
|
-
throw new
|
|
3841
|
+
throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
|
|
3730
3842
|
}
|
|
3731
3843
|
if (opts.type === "generic" && !definition) {
|
|
3732
|
-
throw new
|
|
3844
|
+
throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
|
|
3733
3845
|
}
|
|
3734
3846
|
const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
|
|
3735
3847
|
const client = getClient();
|
|
@@ -3804,13 +3916,13 @@ function list18(parent) {
|
|
|
3804
3916
|
const client = getClient();
|
|
3805
3917
|
if (opts.folder !== void 0) {
|
|
3806
3918
|
if (!opts.project) {
|
|
3807
|
-
throw new
|
|
3919
|
+
throw new CliUsageError("--project is required when --folder is specified.");
|
|
3808
3920
|
}
|
|
3809
3921
|
const otherScopes = ["set", "plan", "execution", "precondition"].filter(
|
|
3810
3922
|
(k) => opts[k] !== void 0
|
|
3811
3923
|
);
|
|
3812
3924
|
if (otherScopes.length > 0) {
|
|
3813
|
-
throw new
|
|
3925
|
+
throw new CliUsageError(
|
|
3814
3926
|
`--folder cannot be combined with ${otherScopes.map((k) => `--${k}`).join(", ")}. Use --folder with --project only.`
|
|
3815
3927
|
);
|
|
3816
3928
|
}
|
|
@@ -3868,10 +3980,10 @@ function list18(parent) {
|
|
|
3868
3980
|
}
|
|
3869
3981
|
|
|
3870
3982
|
// src/commands/xray/test/update.ts
|
|
3871
|
-
import { Option as
|
|
3983
|
+
import { Option as Option14 } from "commander";
|
|
3872
3984
|
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
|
|
3985
|
+
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(
|
|
3986
|
+
new Option14("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
|
|
3875
3987
|
"scenario",
|
|
3876
3988
|
"scenario-outline"
|
|
3877
3989
|
])
|
|
@@ -3902,10 +4014,10 @@ function update13(parent) {
|
|
|
3902
4014
|
if (opts.reporter !== void 0) fields.reporter = { name: opts.reporter };
|
|
3903
4015
|
if (opts.type !== void 0) {
|
|
3904
4016
|
if (opts.type === "cucumber" && !gherkin) {
|
|
3905
|
-
throw new
|
|
4017
|
+
throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
|
|
3906
4018
|
}
|
|
3907
4019
|
if (opts.type === "generic" && !definition) {
|
|
3908
|
-
throw new
|
|
4020
|
+
throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
|
|
3909
4021
|
}
|
|
3910
4022
|
const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
|
|
3911
4023
|
const projectKey = testKey.split("-")[0];
|
|
@@ -4059,7 +4171,7 @@ function registerXrayCommands(program) {
|
|
|
4059
4171
|
var DIM = "\x1B[2m";
|
|
4060
4172
|
var RESET = "\x1B[0m";
|
|
4061
4173
|
function buildProgram() {
|
|
4062
|
-
const program = new
|
|
4174
|
+
const program = new Command17();
|
|
4063
4175
|
program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
|
|
4064
4176
|
styleTitle: (str) => styleText("bold", str),
|
|
4065
4177
|
styleUsage: (str) => styleText("dim", str),
|
|
@@ -4120,10 +4232,33 @@ function getCredentialInfo() {
|
|
|
4120
4232
|
};
|
|
4121
4233
|
}
|
|
4122
4234
|
|
|
4235
|
+
// src/utils/field-error-recovery.ts
|
|
4236
|
+
var CUSTOM_FIELD_KEY = /^customfield_(\d+)$/;
|
|
4237
|
+
function customFieldIds(detail) {
|
|
4238
|
+
if (!detail || typeof detail !== "object") return [];
|
|
4239
|
+
const errors = detail.errors;
|
|
4240
|
+
if (!errors || typeof errors !== "object") return [];
|
|
4241
|
+
return Object.keys(errors).filter((k) => CUSTOM_FIELD_KEY.test(k));
|
|
4242
|
+
}
|
|
4243
|
+
function adaptFieldErrors(normalized) {
|
|
4244
|
+
const ids = customFieldIds(normalized.detail);
|
|
4245
|
+
if (ids.length === 0) return normalized;
|
|
4246
|
+
const numbers = ids.map((id) => CUSTOM_FIELD_KEY.exec(id)?.[1] ?? id);
|
|
4247
|
+
const lookup = numbers.map((n) => `jiradc field search ${n}`).join(", ");
|
|
4248
|
+
const plural = ids.length > 1 ? "s" : "";
|
|
4249
|
+
return {
|
|
4250
|
+
...normalized,
|
|
4251
|
+
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).`
|
|
4252
|
+
};
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4123
4255
|
// src/index.ts
|
|
4124
4256
|
await runCli(buildProgram(), {
|
|
4125
4257
|
credentialInfo: getCredentialInfo,
|
|
4126
4258
|
service: "Jira",
|
|
4127
4259
|
authRecovery: "Set the JIRA_URL and JIRA_TOKEN environment variables.",
|
|
4128
|
-
urlEnvVar: "JIRA_URL"
|
|
4260
|
+
urlEnvVar: "JIRA_URL",
|
|
4261
|
+
// Jira names rejected fields by custom-field id. The generic recovery leaves
|
|
4262
|
+
// an agent holding an opaque id, so point it at the command that explains one.
|
|
4263
|
+
adapt: adaptFieldErrors
|
|
4129
4264
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiradc-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
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",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"tsx": "^4.19.2",
|
|
24
24
|
"typescript": "^5.7.2",
|
|
25
25
|
"vitest": "^4.0.16",
|
|
26
|
-
"
|
|
26
|
+
"config-typescript": "0.0.0",
|
|
27
27
|
"config-eslint": "0.0.0",
|
|
28
|
-
"
|
|
28
|
+
"cli-utils": "1.0.0"
|
|
29
29
|
},
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=22.0.0"
|