jiradc-cli 1.0.21 → 2.0.0-ga26d641.1
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/dist/index.js +414 -256
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import { readFileSync } from "fs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { styleText } from "util";
|
|
8
|
-
import { Command as Command11 } from "commander";
|
|
3
|
+
// ../../cli-utils/dist/cache.js
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
9
7
|
|
|
10
|
-
//
|
|
11
|
-
import {
|
|
8
|
+
// ../../cli-utils/dist/bootstrap.js
|
|
9
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
10
|
+
import { dirname, join as join2 } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
function readPackageVersion(importMetaUrl) {
|
|
13
|
+
try {
|
|
14
|
+
const here = dirname(fileURLToPath(importMetaUrl));
|
|
15
|
+
const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf-8"));
|
|
16
|
+
return pkg.version ?? "0.0.0";
|
|
17
|
+
} catch {
|
|
18
|
+
return "0.0.0";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
12
21
|
|
|
13
|
-
//
|
|
22
|
+
// ../../cli-utils/dist/validators.js
|
|
14
23
|
import { InvalidArgumentError } from "commander";
|
|
15
24
|
function intInRange(min, max) {
|
|
16
25
|
return (raw) => {
|
|
@@ -39,6 +48,236 @@ function positiveInt(raw) {
|
|
|
39
48
|
return n;
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
// ../../cli-utils/dist/registry.js
|
|
52
|
+
var SUB_ENTITY_ID_NUMERIC = {
|
|
53
|
+
comment: true,
|
|
54
|
+
worklog: true,
|
|
55
|
+
attachment: true,
|
|
56
|
+
request: true,
|
|
57
|
+
type: true,
|
|
58
|
+
employee: true,
|
|
59
|
+
message: false,
|
|
60
|
+
membership: false
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ../../cli-utils/dist/builders.js
|
|
64
|
+
function defineOption(cmd, flags, description, parser, mandatory) {
|
|
65
|
+
if (mandatory) {
|
|
66
|
+
return parser ? cmd.requiredOption(flags, description, parser) : cmd.requiredOption(flags, description);
|
|
67
|
+
}
|
|
68
|
+
return parser ? cmd.option(flags, description, parser) : cmd.option(flags, description);
|
|
69
|
+
}
|
|
70
|
+
function subjectArg(cmd, name, opts = {}) {
|
|
71
|
+
const inner = opts.variadic ? `${name}...` : name;
|
|
72
|
+
const token = opts.optional ? `[${inner}]` : `<${inner}>`;
|
|
73
|
+
return opts.parser ? cmd.argument(token, opts.description ?? "", opts.parser) : cmd.argument(token, opts.description ?? "");
|
|
74
|
+
}
|
|
75
|
+
function subEntityOption(cmd, entity, opts = {}) {
|
|
76
|
+
const numeric = opts.numeric ?? SUB_ENTITY_ID_NUMERIC[entity] ?? true;
|
|
77
|
+
const description = `${entity.charAt(0).toUpperCase()}${entity.slice(1)} id`;
|
|
78
|
+
return defineOption(cmd, `--${entity}-id <id>`, description, numeric ? positiveInt : void 0, opts.mandatory);
|
|
79
|
+
}
|
|
80
|
+
function bodyOption(cmd, opts = {}) {
|
|
81
|
+
return defineOption(cmd, "--body <text>", "Prose body content", void 0, opts.mandatory);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ../../cli-utils/dist/errors.js
|
|
85
|
+
import { CommanderError } from "commander";
|
|
86
|
+
var EXIT = {
|
|
87
|
+
SUCCESS: 0,
|
|
88
|
+
GENERIC: 1,
|
|
89
|
+
USAGE: 2,
|
|
90
|
+
NOT_FOUND: 3,
|
|
91
|
+
FORBIDDEN: 4,
|
|
92
|
+
CONFLICT: 5,
|
|
93
|
+
AUTH: 6
|
|
94
|
+
};
|
|
95
|
+
var TYPE_EXIT = {
|
|
96
|
+
usage: EXIT.USAGE,
|
|
97
|
+
not_found: EXIT.NOT_FOUND,
|
|
98
|
+
forbidden: EXIT.FORBIDDEN,
|
|
99
|
+
conflict: EXIT.CONFLICT,
|
|
100
|
+
auth: EXIT.AUTH,
|
|
101
|
+
rate_limited: EXIT.GENERIC,
|
|
102
|
+
server: EXIT.GENERIC,
|
|
103
|
+
network: EXIT.GENERIC,
|
|
104
|
+
unknown: EXIT.GENERIC
|
|
105
|
+
};
|
|
106
|
+
var NETWORK_CODES = ["ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"];
|
|
107
|
+
function httpStatus(err) {
|
|
108
|
+
const e = err;
|
|
109
|
+
return e?.response?.status ?? e?.statusCode;
|
|
110
|
+
}
|
|
111
|
+
function responseDetail(err) {
|
|
112
|
+
const e = err;
|
|
113
|
+
const data = e?.response?.data;
|
|
114
|
+
if (data && typeof data === "object")
|
|
115
|
+
return data;
|
|
116
|
+
const body = e?.body;
|
|
117
|
+
if (typeof body === "string") {
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(body);
|
|
120
|
+
return parsed.error ?? parsed;
|
|
121
|
+
} catch {
|
|
122
|
+
return void 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (body && typeof body === "object") {
|
|
126
|
+
return body.error ?? body;
|
|
127
|
+
}
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
function errorCode(err) {
|
|
131
|
+
return err?.code;
|
|
132
|
+
}
|
|
133
|
+
function normalize(err, opts) {
|
|
134
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
135
|
+
if (err instanceof CommanderError) {
|
|
136
|
+
return {
|
|
137
|
+
type: "usage",
|
|
138
|
+
message: message || "Invalid command usage",
|
|
139
|
+
recovery: "Check the command syntax and flags; run the command with --help.",
|
|
140
|
+
retryable: false
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const status = httpStatus(err);
|
|
144
|
+
const detail = responseDetail(err);
|
|
145
|
+
if (status !== void 0) {
|
|
146
|
+
switch (status) {
|
|
147
|
+
case 400:
|
|
148
|
+
return {
|
|
149
|
+
type: "usage",
|
|
150
|
+
status,
|
|
151
|
+
message: "Bad request (HTTP 400)",
|
|
152
|
+
recovery: "Check parameter values (ids, keys, query syntax) against the API.",
|
|
153
|
+
retryable: false,
|
|
154
|
+
detail
|
|
155
|
+
};
|
|
156
|
+
case 401:
|
|
157
|
+
return {
|
|
158
|
+
type: "auth",
|
|
159
|
+
status,
|
|
160
|
+
message: "Authentication failed (HTTP 401)",
|
|
161
|
+
recovery: opts.authRecovery,
|
|
162
|
+
retryable: false
|
|
163
|
+
};
|
|
164
|
+
case 403:
|
|
165
|
+
return {
|
|
166
|
+
type: "forbidden",
|
|
167
|
+
status,
|
|
168
|
+
message: "Forbidden (HTTP 403)",
|
|
169
|
+
recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
|
|
170
|
+
retryable: false,
|
|
171
|
+
detail
|
|
172
|
+
};
|
|
173
|
+
case 404:
|
|
174
|
+
return {
|
|
175
|
+
type: "not_found",
|
|
176
|
+
status,
|
|
177
|
+
message: "Not found (HTTP 404)",
|
|
178
|
+
recovery: "Verify the id/key exists and that you have access to it.",
|
|
179
|
+
retryable: false,
|
|
180
|
+
detail
|
|
181
|
+
};
|
|
182
|
+
case 409:
|
|
183
|
+
return {
|
|
184
|
+
type: "conflict",
|
|
185
|
+
status,
|
|
186
|
+
message: "Conflict (HTTP 409)",
|
|
187
|
+
recovery: "The resource changed or already exists; re-fetch current state and retry.",
|
|
188
|
+
retryable: false,
|
|
189
|
+
detail
|
|
190
|
+
};
|
|
191
|
+
case 429:
|
|
192
|
+
return {
|
|
193
|
+
type: "rate_limited",
|
|
194
|
+
status,
|
|
195
|
+
message: "Rate limited (HTTP 429)",
|
|
196
|
+
recovery: "Wait and retry the request.",
|
|
197
|
+
retryable: true
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (status >= 500) {
|
|
201
|
+
return {
|
|
202
|
+
type: "server",
|
|
203
|
+
status,
|
|
204
|
+
message: `Server error (HTTP ${status})`,
|
|
205
|
+
recovery: `${opts.service} returned an internal error; retry shortly.`,
|
|
206
|
+
retryable: true,
|
|
207
|
+
detail
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
type: "unknown",
|
|
212
|
+
status,
|
|
213
|
+
message: `${opts.service} error (HTTP ${status}): ${message}`,
|
|
214
|
+
recovery: "Inspect the detail field for the API response.",
|
|
215
|
+
retryable: false,
|
|
216
|
+
detail
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const code = errorCode(err);
|
|
220
|
+
if (code && NETWORK_CODES.includes(code) || NETWORK_CODES.some((c) => message.includes(c))) {
|
|
221
|
+
return {
|
|
222
|
+
type: "network",
|
|
223
|
+
message: `Cannot connect to ${opts.service}: ${message}`,
|
|
224
|
+
recovery: opts.networkRecovery ?? "Verify the *_URL is correct, the server is reachable, and you are on the VPN if required.",
|
|
225
|
+
retryable: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
|
|
229
|
+
}
|
|
230
|
+
function classifyError(err, opts) {
|
|
231
|
+
const base = normalize(err, opts);
|
|
232
|
+
const n = opts.adapt ? opts.adapt(base, err) : base;
|
|
233
|
+
return {
|
|
234
|
+
envelope: {
|
|
235
|
+
error: {
|
|
236
|
+
type: n.type,
|
|
237
|
+
message: n.message,
|
|
238
|
+
recovery: n.recovery,
|
|
239
|
+
retryable: n.retryable,
|
|
240
|
+
...n.detail !== void 0 ? { detail: n.detail } : {}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
exitCode: TYPE_EXIT[n.type]
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function createErrorHandler(opts) {
|
|
247
|
+
return (err) => {
|
|
248
|
+
const { envelope, exitCode } = classifyError(err, opts);
|
|
249
|
+
process.stderr.write(`${JSON.stringify(envelope)}
|
|
250
|
+
`);
|
|
251
|
+
return process.exit(exitCode);
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function isCleanCommanderExit(err) {
|
|
255
|
+
return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
|
|
256
|
+
}
|
|
257
|
+
function routeErrors(cmd) {
|
|
258
|
+
cmd.exitOverride();
|
|
259
|
+
cmd.configureOutput({ writeErr: () => void 0 });
|
|
260
|
+
cmd.commands.forEach(routeErrors);
|
|
261
|
+
}
|
|
262
|
+
async function runCli(program, opts) {
|
|
263
|
+
routeErrors(program);
|
|
264
|
+
try {
|
|
265
|
+
await program.parseAsync();
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (isCleanCommanderExit(err)) {
|
|
268
|
+
process.exit(err.exitCode ?? 0);
|
|
269
|
+
}
|
|
270
|
+
createErrorHandler(opts)(err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/program.ts
|
|
275
|
+
import { styleText } from "util";
|
|
276
|
+
import { Command as Command11 } from "commander";
|
|
277
|
+
|
|
278
|
+
// src/commands/board/issues.ts
|
|
279
|
+
import { Argument } from "commander";
|
|
280
|
+
|
|
42
281
|
// src/utils/client.ts
|
|
43
282
|
import { JiraClient } from "jira-data-center-client";
|
|
44
283
|
|
|
@@ -89,68 +328,6 @@ function output(data) {
|
|
|
89
328
|
process.stdout.write(`${JSON.stringify(data, null, prettyPrint ? 2 : void 0)}
|
|
90
329
|
`);
|
|
91
330
|
}
|
|
92
|
-
function handleError(err) {
|
|
93
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
-
const axiosStatus = err?.response?.status;
|
|
95
|
-
if (axiosStatus === 400) {
|
|
96
|
-
const responseData = err?.response?.data;
|
|
97
|
-
const jiraErrors = responseData && typeof responseData === "object" ? responseData : void 0;
|
|
98
|
-
process.stderr.write(
|
|
99
|
-
`${JSON.stringify({
|
|
100
|
-
error: `Bad request (HTTP 400)`,
|
|
101
|
-
detail: jiraErrors ?? message,
|
|
102
|
-
hint: "Check that all parameters are valid (JQL syntax, field names, project keys, etc.)."
|
|
103
|
-
})}
|
|
104
|
-
`
|
|
105
|
-
);
|
|
106
|
-
} else if (axiosStatus === 401) {
|
|
107
|
-
process.stderr.write(
|
|
108
|
-
`${JSON.stringify({
|
|
109
|
-
error: "Authentication failed (HTTP 401)",
|
|
110
|
-
...getCredentialInfo()
|
|
111
|
-
})}
|
|
112
|
-
`
|
|
113
|
-
);
|
|
114
|
-
} else if (axiosStatus === 403) {
|
|
115
|
-
const responseData = err?.response?.data;
|
|
116
|
-
process.stderr.write(
|
|
117
|
-
`${JSON.stringify({
|
|
118
|
-
error: "Forbidden (HTTP 403)",
|
|
119
|
-
detail: responseData && typeof responseData === "object" ? responseData : message,
|
|
120
|
-
hint: "Your account does not have permission for this operation. Check project permissions and token scope."
|
|
121
|
-
})}
|
|
122
|
-
`
|
|
123
|
-
);
|
|
124
|
-
} else if (message.includes("ENOTFOUND") || message.includes("ECONNREFUSED") || message.includes("ECONNRESET")) {
|
|
125
|
-
process.stderr.write(
|
|
126
|
-
`${JSON.stringify({
|
|
127
|
-
error: `Cannot connect to Jira server: ${message}`,
|
|
128
|
-
hint: "Verify that JIRA_URL is correct and the server is reachable."
|
|
129
|
-
})}
|
|
130
|
-
`
|
|
131
|
-
);
|
|
132
|
-
} else if (axiosStatus === 500) {
|
|
133
|
-
process.stderr.write(
|
|
134
|
-
`${JSON.stringify({
|
|
135
|
-
error: `Server error (HTTP 500): ${message}`,
|
|
136
|
-
hint: "The server returned an internal error. Check that all parameters are valid (project keys, issue keys, JQL query syntax)."
|
|
137
|
-
})}
|
|
138
|
-
`
|
|
139
|
-
);
|
|
140
|
-
} else {
|
|
141
|
-
const responseData = err?.response?.data;
|
|
142
|
-
const detail = responseData && typeof responseData === "object" ? responseData : void 0;
|
|
143
|
-
process.stderr.write(
|
|
144
|
-
`${JSON.stringify({
|
|
145
|
-
error: message,
|
|
146
|
-
...axiosStatus !== void 0 && { statusCode: axiosStatus },
|
|
147
|
-
...detail && { detail }
|
|
148
|
-
})}
|
|
149
|
-
`
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
331
|
|
|
155
332
|
// src/utils/transformers/base.ts
|
|
156
333
|
function jiraBaseUrl() {
|
|
@@ -513,8 +690,8 @@ function list(parent) {
|
|
|
513
690
|
}
|
|
514
691
|
|
|
515
692
|
// src/commands/board/index.ts
|
|
516
|
-
function registerBoardCommands(
|
|
517
|
-
const board =
|
|
693
|
+
function registerBoardCommands(program) {
|
|
694
|
+
const board = program.command("board").description("Board operations").addHelpText(
|
|
518
695
|
"after",
|
|
519
696
|
`
|
|
520
697
|
Examples:
|
|
@@ -635,8 +812,8 @@ Examples:
|
|
|
635
812
|
}
|
|
636
813
|
|
|
637
814
|
// src/commands/component/index.ts
|
|
638
|
-
function registerComponentCommands(
|
|
639
|
-
const component =
|
|
815
|
+
function registerComponentCommands(program) {
|
|
816
|
+
const component = program.command("component").description("Project component operations").addHelpText(
|
|
640
817
|
"after",
|
|
641
818
|
`
|
|
642
819
|
Examples:
|
|
@@ -658,16 +835,16 @@ Examples:
|
|
|
658
835
|
|
|
659
836
|
// src/commands/field/options.ts
|
|
660
837
|
function options(parent) {
|
|
661
|
-
parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--
|
|
838
|
+
parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--start <number>", "Page number (1-indexed)", positiveInt).addHelpText(
|
|
662
839
|
"after",
|
|
663
|
-
'\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --
|
|
840
|
+
'\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --start 2'
|
|
664
841
|
).action(async (id, opts) => {
|
|
665
842
|
const client = getClient();
|
|
666
843
|
const result = await client.fields.getFieldOptions({
|
|
667
844
|
fieldId: id,
|
|
668
845
|
query: opts.query,
|
|
669
846
|
maxResults: opts.limit,
|
|
670
|
-
page: opts.
|
|
847
|
+
page: opts.start
|
|
671
848
|
});
|
|
672
849
|
output(transformPaged({ ...result, startAt: result.startAt ?? 0 }, transformCustomFieldOption));
|
|
673
850
|
});
|
|
@@ -686,8 +863,8 @@ function search(parent) {
|
|
|
686
863
|
}
|
|
687
864
|
|
|
688
865
|
// src/commands/field/index.ts
|
|
689
|
-
function registerFieldCommands(
|
|
690
|
-
const field =
|
|
866
|
+
function registerFieldCommands(program) {
|
|
867
|
+
const field = program.command("field").description("Field operations").addHelpText(
|
|
691
868
|
"after",
|
|
692
869
|
`
|
|
693
870
|
Examples:
|
|
@@ -718,15 +895,15 @@ async function resolveUserToken(token) {
|
|
|
718
895
|
|
|
719
896
|
// src/commands/issue/assign.ts
|
|
720
897
|
function assign(parent) {
|
|
721
|
-
parent.command("assign <key>
|
|
898
|
+
parent.command("assign <key>").description('Assign an issue. --assignee is a username, "me", or "none" to unassign.').requiredOption("--assignee <user>", 'Username to assign, "me" for the current user, or "none" to unassign').addHelpText(
|
|
722
899
|
"after",
|
|
723
900
|
`
|
|
724
901
|
Examples:
|
|
725
|
-
jiradc issue assign PROJ-123 jsmith
|
|
726
|
-
jiradc issue assign PROJ-123 me
|
|
727
|
-
jiradc issue assign PROJ-123 none`
|
|
728
|
-
).action(async (key,
|
|
729
|
-
const resolved = await resolveUserToken(
|
|
902
|
+
jiradc issue assign PROJ-123 --assignee jsmith
|
|
903
|
+
jiradc issue assign PROJ-123 --assignee me
|
|
904
|
+
jiradc issue assign PROJ-123 --assignee none`
|
|
905
|
+
).action(async (key, opts) => {
|
|
906
|
+
const resolved = await resolveUserToken(opts.assignee);
|
|
730
907
|
const client = getClient();
|
|
731
908
|
await client.issues.update({
|
|
732
909
|
issueKeyOrId: key,
|
|
@@ -738,20 +915,22 @@ Examples:
|
|
|
738
915
|
|
|
739
916
|
// src/commands/issue/attachment/delete.ts
|
|
740
917
|
function deleteAttachment(parent) {
|
|
741
|
-
parent.command("delete").description("Delete an attachment by ID")
|
|
918
|
+
const cmd = parent.command("delete <key>").description("Delete an attachment by ID");
|
|
919
|
+
subEntityOption(cmd, "attachment", { mandatory: true });
|
|
920
|
+
cmd.addHelpText("after", "\nExamples:\n jiradc issue attachment delete PROJ-123 --attachment-id 12345").action(async (key, opts) => {
|
|
742
921
|
const client = getClient();
|
|
743
|
-
await client.issues.deleteAttachment({ attachmentId: opts.
|
|
744
|
-
output({ deleted: true, attachmentId: opts.
|
|
922
|
+
await client.issues.deleteAttachment({ attachmentId: String(opts.attachmentId) });
|
|
923
|
+
output({ deleted: true, issueKey: key, attachmentId: opts.attachmentId });
|
|
745
924
|
});
|
|
746
925
|
}
|
|
747
926
|
|
|
748
927
|
// src/commands/issue/attachment/download-all.ts
|
|
749
|
-
import { mkdirSync } from "fs";
|
|
750
|
-
import { join } from "path";
|
|
928
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
929
|
+
import { join as join3 } from "path";
|
|
751
930
|
function downloadAll(parent) {
|
|
752
931
|
parent.command("download-all <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into").addHelpText("after", "\nExamples:\n jiradc issue attachment download-all PROJ-123 --output ./downloads").action(async (key, opts) => {
|
|
753
932
|
const client = getClient();
|
|
754
|
-
|
|
933
|
+
mkdirSync2(opts.output, { recursive: true });
|
|
755
934
|
const issue = await client.issues.get({
|
|
756
935
|
issueKeyOrId: key,
|
|
757
936
|
fields: ["attachment"]
|
|
@@ -768,7 +947,7 @@ function downloadAll(parent) {
|
|
|
768
947
|
failed.push({ filename: att.filename, error: "No content URL" });
|
|
769
948
|
continue;
|
|
770
949
|
}
|
|
771
|
-
const destPath =
|
|
950
|
+
const destPath = join3(opts.output, att.filename);
|
|
772
951
|
try {
|
|
773
952
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
|
|
774
953
|
results.push({ filename: att.filename, size: att.size, path: destPath });
|
|
@@ -788,11 +967,17 @@ function downloadAll(parent) {
|
|
|
788
967
|
|
|
789
968
|
// src/commands/issue/attachment/download.ts
|
|
790
969
|
function download(parent) {
|
|
791
|
-
parent.command("download <key>").description("Download a single attachment by ID")
|
|
970
|
+
const cmd = parent.command("download <key>").description("Download a single attachment by ID");
|
|
971
|
+
subEntityOption(cmd, "attachment", { mandatory: true });
|
|
972
|
+
cmd.requiredOption("--output <path>", "Local file path to save the attachment").addHelpText(
|
|
973
|
+
"after",
|
|
974
|
+
"\nExamples:\n jiradc issue attachment download PROJ-123 --attachment-id 12345 --output ./report.pdf"
|
|
975
|
+
).action(async (key, opts) => {
|
|
792
976
|
const client = getClient();
|
|
793
|
-
const
|
|
977
|
+
const attachmentId = String(opts.attachmentId);
|
|
978
|
+
const attachment = await client.issues.getAttachment({ attachmentId });
|
|
794
979
|
if (!attachment.content) {
|
|
795
|
-
throw new Error(`Attachment ${
|
|
980
|
+
throw new Error(`Attachment ${attachmentId} has no content URL`);
|
|
796
981
|
}
|
|
797
982
|
await client.issues.downloadAttachment({
|
|
798
983
|
url: attachment.content,
|
|
@@ -841,8 +1026,8 @@ function upload(parent) {
|
|
|
841
1026
|
const filePaths = opts.files.split(",").map((f) => f.trim());
|
|
842
1027
|
const results = [];
|
|
843
1028
|
for (const filePath of filePaths) {
|
|
844
|
-
const
|
|
845
|
-
results.push(...
|
|
1029
|
+
const attachments = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
|
|
1030
|
+
results.push(...attachments);
|
|
846
1031
|
}
|
|
847
1032
|
output({
|
|
848
1033
|
issueKey: key,
|
|
@@ -866,9 +1051,9 @@ function registerAttachmentCommands(parent) {
|
|
|
866
1051
|
Examples:
|
|
867
1052
|
$ jiradc issue attachment list PROJ-123
|
|
868
1053
|
$ jiradc issue attachment upload PROJ-123 --files ./report.pdf
|
|
869
|
-
$ jiradc issue attachment download PROJ-123 --id 12345 --output ./report.pdf
|
|
1054
|
+
$ jiradc issue attachment download PROJ-123 --attachment-id 12345 --output ./report.pdf
|
|
870
1055
|
$ jiradc issue attachment download-all PROJ-123 --output ./downloads
|
|
871
|
-
$ jiradc issue attachment delete --id 12345
|
|
1056
|
+
$ jiradc issue attachment delete PROJ-123 --attachment-id 12345
|
|
872
1057
|
`
|
|
873
1058
|
);
|
|
874
1059
|
upload(attachment);
|
|
@@ -878,47 +1063,6 @@ Examples:
|
|
|
878
1063
|
deleteAttachment(attachment);
|
|
879
1064
|
}
|
|
880
1065
|
|
|
881
|
-
// src/commands/issue/attachments.ts
|
|
882
|
-
import { mkdirSync as mkdirSync2 } from "fs";
|
|
883
|
-
import { join as join2 } from "path";
|
|
884
|
-
function attachments(parent) {
|
|
885
|
-
parent.command("attachments <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into").addHelpText("after", "\nExamples:\n jiradc issue attachments PROJ-123 --output ./downloads").action(async (key, opts) => {
|
|
886
|
-
const client = getClient();
|
|
887
|
-
mkdirSync2(opts.output, { recursive: true });
|
|
888
|
-
const issue = await client.issues.get({
|
|
889
|
-
issueKeyOrId: key,
|
|
890
|
-
fields: ["attachment"]
|
|
891
|
-
});
|
|
892
|
-
const atts = issue.fields.attachment ?? [];
|
|
893
|
-
if (atts.length === 0) {
|
|
894
|
-
output({ issueKey: key, downloaded: 0, files: [] });
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
const results = [];
|
|
898
|
-
const failed = [];
|
|
899
|
-
for (const att of atts) {
|
|
900
|
-
if (!att.content) {
|
|
901
|
-
failed.push({ filename: att.filename, error: "No content URL" });
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
const destPath = join2(opts.output, att.filename);
|
|
905
|
-
try {
|
|
906
|
-
await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
|
|
907
|
-
results.push({ filename: att.filename, size: att.size, path: destPath });
|
|
908
|
-
} catch (err) {
|
|
909
|
-
failed.push({ filename: att.filename, error: String(err) });
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
output({
|
|
913
|
-
issueKey: key,
|
|
914
|
-
downloaded: results.length,
|
|
915
|
-
total: atts.length,
|
|
916
|
-
files: results,
|
|
917
|
-
...failed.length > 0 && { failed }
|
|
918
|
-
});
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
|
|
922
1066
|
// src/commands/issue/batch-changelog.ts
|
|
923
1067
|
function batchChangelog(parent) {
|
|
924
1068
|
parent.command("batch-changelog <keys>").description("Get changelogs for multiple issues at once").option("--limit <number>", "Max changelog entries per issue (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
|
|
@@ -979,7 +1123,7 @@ function changelog(parent) {
|
|
|
979
1123
|
// src/commands/issue/clone.ts
|
|
980
1124
|
import { unlink } from "fs/promises";
|
|
981
1125
|
import { tmpdir } from "os";
|
|
982
|
-
import { join as
|
|
1126
|
+
import { join as join4 } from "path";
|
|
983
1127
|
var CLONE_FIELDS = [
|
|
984
1128
|
"summary",
|
|
985
1129
|
"description",
|
|
@@ -1030,7 +1174,7 @@ Examples:
|
|
|
1030
1174
|
const tmpFiles = [];
|
|
1031
1175
|
const copied = await Promise.all(
|
|
1032
1176
|
f.attachment.map(async (att) => {
|
|
1033
|
-
const tmpPath =
|
|
1177
|
+
const tmpPath = join4(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
|
|
1034
1178
|
tmpFiles.push(tmpPath);
|
|
1035
1179
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
|
|
1036
1180
|
await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
|
|
@@ -1071,9 +1215,11 @@ Examples:
|
|
|
1071
1215
|
);
|
|
1072
1216
|
}
|
|
1073
1217
|
|
|
1074
|
-
// src/commands/issue/comment/
|
|
1075
|
-
function
|
|
1076
|
-
parent.command("
|
|
1218
|
+
// src/commands/issue/comment/create.ts
|
|
1219
|
+
function create2(parent) {
|
|
1220
|
+
const cmd = parent.command("create <key>").description("Add a comment to an issue").addHelpText("after", '\nExamples:\n jiradc issue comment create PROJ-123 --body "Fixed in latest build"');
|
|
1221
|
+
bodyOption(cmd, { mandatory: true });
|
|
1222
|
+
cmd.action(async (key, opts) => {
|
|
1077
1223
|
const client = getClient();
|
|
1078
1224
|
const result = await client.issues.addComment({ issueKeyOrId: key, body: opts.body });
|
|
1079
1225
|
output(transformComment(result));
|
|
@@ -1082,18 +1228,30 @@ function add(parent) {
|
|
|
1082
1228
|
|
|
1083
1229
|
// src/commands/issue/comment/delete.ts
|
|
1084
1230
|
function deleteComment(parent) {
|
|
1085
|
-
parent.command("delete <key>").description("Delete a comment from an issue").
|
|
1231
|
+
const cmd = parent.command("delete <key>").description("Delete a comment from an issue").addHelpText("after", "\nExamples:\n jiradc issue comment delete PROJ-123 --comment-id 12345");
|
|
1232
|
+
subEntityOption(cmd, "comment", { mandatory: true });
|
|
1233
|
+
cmd.action(async (key, opts) => {
|
|
1086
1234
|
const client = getClient();
|
|
1087
|
-
await client.issues.deleteComment({ issueKeyOrId: key, commentId: opts.
|
|
1088
|
-
output({ deleted: true, issueKey: key, commentId: opts.
|
|
1235
|
+
await client.issues.deleteComment({ issueKeyOrId: key, commentId: String(opts.commentId) });
|
|
1236
|
+
output({ deleted: true, issueKey: key, commentId: opts.commentId });
|
|
1089
1237
|
});
|
|
1090
1238
|
}
|
|
1091
1239
|
|
|
1092
|
-
// src/commands/issue/comment/
|
|
1093
|
-
function
|
|
1094
|
-
parent.command("
|
|
1240
|
+
// src/commands/issue/comment/update.ts
|
|
1241
|
+
function update2(parent) {
|
|
1242
|
+
const cmd = parent.command("update <key>").description("Update an existing comment").addHelpText(
|
|
1243
|
+
"after",
|
|
1244
|
+
'\nExamples:\n jiradc issue comment update PROJ-123 --comment-id 12345 --body "Updated comment text"'
|
|
1245
|
+
);
|
|
1246
|
+
subEntityOption(cmd, "comment", { mandatory: true });
|
|
1247
|
+
bodyOption(cmd, { mandatory: true });
|
|
1248
|
+
cmd.action(async (key, opts) => {
|
|
1095
1249
|
const client = getClient();
|
|
1096
|
-
const result = await client.issues.editComment({
|
|
1250
|
+
const result = await client.issues.editComment({
|
|
1251
|
+
issueKeyOrId: key,
|
|
1252
|
+
commentId: String(opts.commentId),
|
|
1253
|
+
body: opts.body
|
|
1254
|
+
});
|
|
1097
1255
|
output(transformComment(result));
|
|
1098
1256
|
});
|
|
1099
1257
|
}
|
|
@@ -1104,18 +1262,18 @@ function registerCommentCommands(parent) {
|
|
|
1104
1262
|
"after",
|
|
1105
1263
|
`
|
|
1106
1264
|
Examples:
|
|
1107
|
-
$ jiradc issue comment
|
|
1108
|
-
$ jiradc issue comment
|
|
1109
|
-
$ jiradc issue comment delete PROJ-123 --id 12345
|
|
1265
|
+
$ jiradc issue comment create PROJ-123 --body "Fixed in latest build"
|
|
1266
|
+
$ jiradc issue comment update PROJ-123 --comment-id 12345 --body "Updated comment text"
|
|
1267
|
+
$ jiradc issue comment delete PROJ-123 --comment-id 12345
|
|
1110
1268
|
`
|
|
1111
1269
|
);
|
|
1112
|
-
|
|
1113
|
-
|
|
1270
|
+
create2(comment);
|
|
1271
|
+
update2(comment);
|
|
1114
1272
|
deleteComment(comment);
|
|
1115
1273
|
}
|
|
1116
1274
|
|
|
1117
1275
|
// src/commands/issue/create.ts
|
|
1118
|
-
function
|
|
1276
|
+
function create3(parent) {
|
|
1119
1277
|
parent.command("create").description("Create a new issue").requiredOption("--project <key>", "Project key or ID").requiredOption("--type <name>", "Issue type name (e.g., Task, Bug, Story)").requiredOption("--summary <text>", "Issue summary/title").option("--description <text>", "Issue description in wiki markup").option("--assignee <username>", "Assignee username").option("--reporter <username>", "Reporter username").option("--priority <name>", "Priority name (e.g., High, Medium, Low)").option("--labels <labels>", "Comma-separated labels").option("--components <names>", "Comma-separated component names").option("--fix-versions <versions>", "Comma-separated fix version names").option("--due-date <date>", "Due date in YYYY-MM-DD format").option("--parent <key>", "Parent issue key (for subtasks)").option("--custom-fields <json>", `Additional custom fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`).addHelpText(
|
|
1120
1278
|
"after",
|
|
1121
1279
|
`
|
|
@@ -1449,7 +1607,7 @@ function buildSetValue(parsed, wrap) {
|
|
|
1449
1607
|
if (parsed.mode !== "set") return void 0;
|
|
1450
1608
|
return parsed.values.map(wrap);
|
|
1451
1609
|
}
|
|
1452
|
-
function
|
|
1610
|
+
function update3(parent) {
|
|
1453
1611
|
parent.command("update <key>").description("Update issue fields").option("--fields <json>", "JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)").option("--no-notify-users", "Suppress notification emails (default: notify)").option("--attachments <paths>", "Comma-separated local file paths to attach").option("--summary <text>", "Set the issue summary").option("--description <text>", "Set the issue description (wiki markup)").option("--priority <name>", "Set the priority by name (e.g. High)").option("--assignee <user>", 'Set the assignee. Username, "me", or "none" to unassign.').option("--labels <list>", 'Set labels ("a,b,c") or mutate ("+add,-remove")').option("--components <list>", 'Set components ("a,b") or mutate ("+add,-remove")').option("--fix-versions <list>", 'Set fix versions ("1.0,2.0") or mutate ("+1.0,-0.9")').addHelpText(
|
|
1454
1612
|
"after",
|
|
1455
1613
|
`
|
|
@@ -1530,11 +1688,11 @@ Examples:
|
|
|
1530
1688
|
});
|
|
1531
1689
|
}
|
|
1532
1690
|
|
|
1533
|
-
// src/commands/issue/worklog/
|
|
1534
|
-
function
|
|
1535
|
-
parent.command("
|
|
1691
|
+
// src/commands/issue/worklog/create.ts
|
|
1692
|
+
function create4(parent) {
|
|
1693
|
+
parent.command("create <key>").description("Log time spent on an issue").requiredOption("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')").option("--comment <text>", "Worklog comment").option("--started <datetime>", "Start time in ISO 8601 format").addHelpText(
|
|
1536
1694
|
"after",
|
|
1537
|
-
'\nExamples:\n jiradc issue worklog
|
|
1695
|
+
'\nExamples:\n jiradc issue worklog create PROJ-123 --time 2h\n jiradc issue worklog create PROJ-123 --time "1d 4h" --comment "Backend implementation"\n jiradc issue worklog create PROJ-123 --time 3h --started "2026-03-19T09:00:00.000+0000"'
|
|
1538
1696
|
).action(async (key, opts) => {
|
|
1539
1697
|
const client = getClient();
|
|
1540
1698
|
const result = await client.issues.addWorklog({
|
|
@@ -1551,37 +1709,57 @@ function add2(parent) {
|
|
|
1551
1709
|
import { Option as Option4 } from "commander";
|
|
1552
1710
|
var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
|
|
1553
1711
|
function deleteWorklog(parent) {
|
|
1554
|
-
parent.command("delete <key>").description("Delete a worklog entry")
|
|
1712
|
+
const cmd = parent.command("delete <key>").description("Delete a worklog entry");
|
|
1713
|
+
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
1714
|
+
cmd.addOption(new Option4("--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"').option("--increase-by <amount>", 'Amount to increase the estimate by; required when --adjust-estimate is "manual"').addHelpText(
|
|
1555
1715
|
"after",
|
|
1556
|
-
"\nExamples:\n jiradc issue worklog delete PROJ-123 --id 12345\n jiradc issue worklog delete PROJ-123 --id 12345 --adjust-estimate leave\n jiradc issue worklog delete PROJ-123 --id 12345 --adjust-estimate new --new-estimate 2h"
|
|
1716
|
+
"\nExamples:\n jiradc issue worklog delete PROJ-123 --worklog-id 12345\n jiradc issue worklog delete PROJ-123 --worklog-id 12345 --adjust-estimate leave\n jiradc issue worklog delete PROJ-123 --worklog-id 12345 --adjust-estimate new --new-estimate 2h"
|
|
1557
1717
|
).action(
|
|
1558
1718
|
async (key, opts) => {
|
|
1559
1719
|
const client = getClient();
|
|
1560
1720
|
await client.issues.deleteWorklog({
|
|
1561
1721
|
issueKeyOrId: key,
|
|
1562
|
-
worklogId: opts.
|
|
1722
|
+
worklogId: String(opts.worklogId),
|
|
1563
1723
|
adjustEstimate: opts.adjustEstimate,
|
|
1564
1724
|
newEstimate: opts.newEstimate,
|
|
1565
1725
|
increaseBy: opts.increaseBy
|
|
1566
1726
|
});
|
|
1567
|
-
output({ deleted: true, issueKey: key, worklogId: opts.
|
|
1727
|
+
output({ deleted: true, issueKey: key, worklogId: opts.worklogId });
|
|
1568
1728
|
}
|
|
1569
1729
|
);
|
|
1570
1730
|
}
|
|
1571
1731
|
|
|
1572
|
-
// src/commands/issue/worklog/
|
|
1732
|
+
// src/commands/issue/worklog/list.ts
|
|
1733
|
+
function list4(parent) {
|
|
1734
|
+
parent.command("list <key>").description("Get worklogs for an issue").option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--limit <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
|
|
1735
|
+
"after",
|
|
1736
|
+
"\nExamples:\n jiradc issue worklog list PROJ-123\n jiradc issue worklog list PROJ-123 --limit 10\n jiradc issue worklog list PROJ-123 --start 10 --limit 5"
|
|
1737
|
+
).action(async (key, opts) => {
|
|
1738
|
+
const client = getClient();
|
|
1739
|
+
const result = await client.issues.getWorklogs({
|
|
1740
|
+
issueKeyOrId: key,
|
|
1741
|
+
startAt: opts.start,
|
|
1742
|
+
maxResults: opts.limit
|
|
1743
|
+
});
|
|
1744
|
+
output(transformPaged(result, transformWorklog));
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// src/commands/issue/worklog/update.ts
|
|
1573
1749
|
import { Option as Option5 } from "commander";
|
|
1574
1750
|
var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
|
|
1575
|
-
function
|
|
1576
|
-
parent.command("
|
|
1751
|
+
function update4(parent) {
|
|
1752
|
+
const cmd = parent.command("update <key>").description("Update an existing worklog entry");
|
|
1753
|
+
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
1754
|
+
cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')").option("--comment <text>", "Worklog comment").option("--started <datetime>", "Start time in ISO 8601 format").addOption(new Option5("--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"').addHelpText(
|
|
1577
1755
|
"after",
|
|
1578
|
-
'\nExamples:\n jiradc issue worklog
|
|
1756
|
+
'\nExamples:\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --time "1h 30m"\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --comment "Revised note"\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --time 2h --adjust-estimate new --new-estimate 4h'
|
|
1579
1757
|
).action(
|
|
1580
1758
|
async (key, opts) => {
|
|
1581
1759
|
const client = getClient();
|
|
1582
1760
|
const result = await client.issues.updateWorklog({
|
|
1583
1761
|
issueKeyOrId: key,
|
|
1584
|
-
worklogId: opts.
|
|
1762
|
+
worklogId: String(opts.worklogId),
|
|
1585
1763
|
timeSpent: opts.time,
|
|
1586
1764
|
comment: opts.comment,
|
|
1587
1765
|
started: opts.started,
|
|
@@ -1593,43 +1771,27 @@ function edit2(parent) {
|
|
|
1593
1771
|
);
|
|
1594
1772
|
}
|
|
1595
1773
|
|
|
1596
|
-
// src/commands/issue/worklog/list.ts
|
|
1597
|
-
function list4(parent) {
|
|
1598
|
-
parent.command("list <key>").description("Get worklogs for an issue").option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--limit <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
|
|
1599
|
-
"after",
|
|
1600
|
-
"\nExamples:\n jiradc issue worklog list PROJ-123\n jiradc issue worklog list PROJ-123 --limit 10\n jiradc issue worklog list PROJ-123 --start 10 --limit 5"
|
|
1601
|
-
).action(async (key, opts) => {
|
|
1602
|
-
const client = getClient();
|
|
1603
|
-
const result = await client.issues.getWorklogs({
|
|
1604
|
-
issueKeyOrId: key,
|
|
1605
|
-
startAt: opts.start,
|
|
1606
|
-
maxResults: opts.limit
|
|
1607
|
-
});
|
|
1608
|
-
output(transformPaged(result, transformWorklog));
|
|
1609
|
-
});
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
1774
|
// src/commands/issue/worklog/index.ts
|
|
1613
1775
|
function registerWorklogCommands(parent) {
|
|
1614
1776
|
const worklog = parent.command("worklog").description("Worklog operations").addHelpText(
|
|
1615
1777
|
"after",
|
|
1616
1778
|
`
|
|
1617
1779
|
Examples:
|
|
1618
|
-
$ jiradc issue worklog
|
|
1780
|
+
$ jiradc issue worklog create PROJ-123 --time 2h --comment "Backend work"
|
|
1619
1781
|
$ jiradc issue worklog list PROJ-123 --limit 10
|
|
1620
|
-
$ jiradc issue worklog
|
|
1621
|
-
$ jiradc issue worklog delete PROJ-123 --id 12345
|
|
1782
|
+
$ jiradc issue worklog update PROJ-123 --worklog-id 12345 --time "1h 30m"
|
|
1783
|
+
$ jiradc issue worklog delete PROJ-123 --worklog-id 12345
|
|
1622
1784
|
`
|
|
1623
1785
|
);
|
|
1624
|
-
|
|
1786
|
+
create4(worklog);
|
|
1625
1787
|
list4(worklog);
|
|
1626
|
-
|
|
1788
|
+
update4(worklog);
|
|
1627
1789
|
deleteWorklog(worklog);
|
|
1628
1790
|
}
|
|
1629
1791
|
|
|
1630
1792
|
// src/commands/issue/index.ts
|
|
1631
|
-
function registerIssueCommands(
|
|
1632
|
-
const issue =
|
|
1793
|
+
function registerIssueCommands(program) {
|
|
1794
|
+
const issue = program.command("issue").description("Issue operations").addHelpText(
|
|
1633
1795
|
"after",
|
|
1634
1796
|
`
|
|
1635
1797
|
Examples:
|
|
@@ -1643,8 +1805,8 @@ Examples:
|
|
|
1643
1805
|
);
|
|
1644
1806
|
get2(issue);
|
|
1645
1807
|
search2(issue);
|
|
1646
|
-
|
|
1647
|
-
|
|
1808
|
+
create3(issue);
|
|
1809
|
+
update3(issue);
|
|
1648
1810
|
deleteIssue(issue);
|
|
1649
1811
|
transition(issue);
|
|
1650
1812
|
transitions(issue);
|
|
@@ -1658,7 +1820,6 @@ Examples:
|
|
|
1658
1820
|
linkTypes(issue);
|
|
1659
1821
|
linkEpic(issue);
|
|
1660
1822
|
registerAttachmentCommands(issue);
|
|
1661
|
-
attachments(issue);
|
|
1662
1823
|
batchCreate(issue);
|
|
1663
1824
|
clone(issue);
|
|
1664
1825
|
devStatus(issue);
|
|
@@ -1689,8 +1850,8 @@ function versions(parent) {
|
|
|
1689
1850
|
}
|
|
1690
1851
|
|
|
1691
1852
|
// src/commands/project/index.ts
|
|
1692
|
-
function registerProjectCommands(
|
|
1693
|
-
const project =
|
|
1853
|
+
function registerProjectCommands(program) {
|
|
1854
|
+
const project = program.command("project").description("Project operations").addHelpText(
|
|
1694
1855
|
"after",
|
|
1695
1856
|
`
|
|
1696
1857
|
Examples:
|
|
@@ -1703,7 +1864,7 @@ Examples:
|
|
|
1703
1864
|
}
|
|
1704
1865
|
|
|
1705
1866
|
// src/commands/sprint/create.ts
|
|
1706
|
-
function
|
|
1867
|
+
function create5(parent) {
|
|
1707
1868
|
parent.command("create").description("Create a new sprint").requiredOption("--board <id>", "Board ID to create sprint in", positiveInt).requiredOption("--name <name>", "Sprint name").option("--start-date <date>", "Start date in ISO 8601 format").option("--end-date <date>", "End date in ISO 8601 format").option("--goal <goal>", "Sprint goal").addHelpText(
|
|
1708
1869
|
"after",
|
|
1709
1870
|
'\nExamples:\n jiradc sprint create --board 42 --name "Sprint 10"\n jiradc sprint create --board 42 --name "Sprint 10" --start-date 2026-03-20 --end-date 2026-04-03 --goal "Complete auth module"'
|
|
@@ -1769,7 +1930,7 @@ function list6(parent) {
|
|
|
1769
1930
|
// src/commands/sprint/update.ts
|
|
1770
1931
|
import { Argument as Argument4, Option as Option7 } from "commander";
|
|
1771
1932
|
var SPRINT_STATES2 = ["future", "active", "closed"];
|
|
1772
|
-
function
|
|
1933
|
+
function update5(parent) {
|
|
1773
1934
|
parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name").addOption(new Option7("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format").option("--end-date <date>", "New end date in ISO 8601 format").option("--goal <goal>", "New sprint goal").addHelpText(
|
|
1774
1935
|
"after",
|
|
1775
1936
|
'\nExamples:\n jiradc sprint update 100 --name "Sprint 10 - Extended"\n jiradc sprint update 100 --state active\n jiradc sprint update 100 --end-date 2026-04-10 --goal "Updated goal"'
|
|
@@ -1790,8 +1951,8 @@ function update3(parent) {
|
|
|
1790
1951
|
}
|
|
1791
1952
|
|
|
1792
1953
|
// src/commands/sprint/index.ts
|
|
1793
|
-
function registerSprintCommands(
|
|
1794
|
-
const sprint =
|
|
1954
|
+
function registerSprintCommands(program) {
|
|
1955
|
+
const sprint = program.command("sprint").description("Sprint operations").addHelpText(
|
|
1795
1956
|
"after",
|
|
1796
1957
|
`
|
|
1797
1958
|
Examples:
|
|
@@ -1803,8 +1964,8 @@ Examples:
|
|
|
1803
1964
|
);
|
|
1804
1965
|
list6(sprint);
|
|
1805
1966
|
issues2(sprint);
|
|
1806
|
-
|
|
1807
|
-
|
|
1967
|
+
create5(sprint);
|
|
1968
|
+
update5(sprint);
|
|
1808
1969
|
deleteSprint(sprint);
|
|
1809
1970
|
}
|
|
1810
1971
|
|
|
@@ -1834,7 +1995,7 @@ function getTokenClient(options2 = {}) {
|
|
|
1834
1995
|
}
|
|
1835
1996
|
|
|
1836
1997
|
// src/commands/token/create.ts
|
|
1837
|
-
function
|
|
1998
|
+
function create6(parent) {
|
|
1838
1999
|
parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name").option(
|
|
1839
2000
|
"--expiration-duration <days>",
|
|
1840
2001
|
"Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
|
|
@@ -1880,35 +2041,37 @@ function list7(parent) {
|
|
|
1880
2041
|
|
|
1881
2042
|
// src/commands/token/revoke.ts
|
|
1882
2043
|
function revoke(parent) {
|
|
1883
|
-
parent.command("revoke").description("Revoke a Personal Access Token by id").
|
|
2044
|
+
const cmd = parent.command("revoke").description("Revoke a Personal Access Token by id").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
|
|
1884
2045
|
"--basic-password <p>",
|
|
1885
2046
|
"Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
|
|
1886
|
-
)
|
|
2047
|
+
);
|
|
2048
|
+
subjectArg(cmd, "tokenId", { parser: positiveInt, description: "Token id to revoke" });
|
|
2049
|
+
cmd.action(async (tokenId, opts) => {
|
|
1887
2050
|
const { client, username, password } = getTokenClient({
|
|
1888
2051
|
basicUsername: opts.basicUsername,
|
|
1889
2052
|
basicPassword: opts.basicPassword
|
|
1890
2053
|
});
|
|
1891
|
-
await client.accessTokens.revoke({ username, password, tokenId
|
|
1892
|
-
output({ revoked:
|
|
2054
|
+
await client.accessTokens.revoke({ username, password, tokenId });
|
|
2055
|
+
output({ revoked: tokenId });
|
|
1893
2056
|
});
|
|
1894
2057
|
}
|
|
1895
2058
|
|
|
1896
2059
|
// src/commands/token/index.ts
|
|
1897
|
-
function registerTokenCommands(
|
|
1898
|
-
const token =
|
|
2060
|
+
function registerTokenCommands(program) {
|
|
2061
|
+
const token = program.command("token").description("Personal Access Token management").addHelpText(
|
|
1899
2062
|
"after",
|
|
1900
2063
|
`
|
|
1901
2064
|
Examples:
|
|
1902
2065
|
$ jiradc token list
|
|
1903
2066
|
$ jiradc token create --name my-pat
|
|
1904
|
-
$ jiradc token revoke
|
|
2067
|
+
$ jiradc token revoke 173
|
|
1905
2068
|
|
|
1906
2069
|
Auth:
|
|
1907
2070
|
Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD
|
|
1908
2071
|
(or --basic-username / --basic-password on any subcommand).
|
|
1909
2072
|
`
|
|
1910
2073
|
);
|
|
1911
|
-
|
|
2074
|
+
create6(token);
|
|
1912
2075
|
list7(token);
|
|
1913
2076
|
revoke(token);
|
|
1914
2077
|
}
|
|
@@ -1950,8 +2113,8 @@ function search3(parent) {
|
|
|
1950
2113
|
}
|
|
1951
2114
|
|
|
1952
2115
|
// src/commands/user/index.ts
|
|
1953
|
-
function registerUserCommands(
|
|
1954
|
-
const user =
|
|
2116
|
+
function registerUserCommands(program) {
|
|
2117
|
+
const user = program.command("user").description("User operations").addHelpText(
|
|
1955
2118
|
"after",
|
|
1956
2119
|
`
|
|
1957
2120
|
Examples:
|
|
@@ -1965,31 +2128,22 @@ Examples:
|
|
|
1965
2128
|
search3(user);
|
|
1966
2129
|
}
|
|
1967
2130
|
|
|
1968
|
-
// src/
|
|
1969
|
-
function readPackageVersion() {
|
|
1970
|
-
try {
|
|
1971
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
1972
|
-
const pkgPath = join4(here, "..", "package.json");
|
|
1973
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1974
|
-
return pkg.version ?? "0.0.0";
|
|
1975
|
-
} catch {
|
|
1976
|
-
return "0.0.0";
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
2131
|
+
// src/program.ts
|
|
1979
2132
|
var DIM = "\x1B[2m";
|
|
1980
2133
|
var RESET = "\x1B[0m";
|
|
1981
|
-
|
|
1982
|
-
program
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2134
|
+
function buildProgram() {
|
|
2135
|
+
const program = new Command11();
|
|
2136
|
+
program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
|
|
2137
|
+
styleTitle: (str) => styleText("bold", str),
|
|
2138
|
+
styleUsage: (str) => styleText("dim", str),
|
|
2139
|
+
styleCommandDescription: (str) => styleText("dim", str),
|
|
2140
|
+
styleOptionDescription: (str) => styleText("dim", str),
|
|
2141
|
+
styleSubcommandDescription: (str) => styleText("dim", str)
|
|
2142
|
+
}).addHelpText("beforeAll", `
|
|
1989
2143
|
${styleText("bold", "jiradc")} ${DIM}\u2014 Jira Data Center CLI${RESET}
|
|
1990
2144
|
`).addHelpText(
|
|
1991
|
-
|
|
1992
|
-
|
|
2145
|
+
"after",
|
|
2146
|
+
`
|
|
1993
2147
|
${styleText("bold", "Environment:")}
|
|
1994
2148
|
JIRA_URL Jira Server base URL ${DIM}(e.g., https://jira.example.com)${RESET}
|
|
1995
2149
|
JIRA_TOKEN Personal Access Token ${DIM}(generate in Jira > Profile > Personal Access Tokens)${RESET}
|
|
@@ -2003,21 +2157,25 @@ ${styleText("bold", "Examples:")}
|
|
|
2003
2157
|
${DIM}$${RESET} jiradc board list --type scrum
|
|
2004
2158
|
${DIM}$${RESET} jiradc sprint list --board 42 --state active
|
|
2005
2159
|
`
|
|
2006
|
-
);
|
|
2007
|
-
program.option("--pretty", "Pretty-print JSON output");
|
|
2008
|
-
program.hook("preAction", (thisCommand) => {
|
|
2009
|
-
|
|
2010
|
-
});
|
|
2011
|
-
registerIssueCommands(program);
|
|
2012
|
-
registerProjectCommands(program);
|
|
2013
|
-
registerComponentCommands(program);
|
|
2014
|
-
registerBoardCommands(program);
|
|
2015
|
-
registerSprintCommands(program);
|
|
2016
|
-
registerFieldCommands(program);
|
|
2017
|
-
registerUserCommands(program);
|
|
2018
|
-
registerTokenCommands(program);
|
|
2019
|
-
|
|
2020
|
-
await program.parseAsync();
|
|
2021
|
-
} catch (err) {
|
|
2022
|
-
handleError(err);
|
|
2160
|
+
);
|
|
2161
|
+
program.option("--pretty", "Pretty-print JSON output");
|
|
2162
|
+
program.hook("preAction", (thisCommand) => {
|
|
2163
|
+
if (thisCommand.optsWithGlobals().pretty) setPretty(true);
|
|
2164
|
+
});
|
|
2165
|
+
registerIssueCommands(program);
|
|
2166
|
+
registerProjectCommands(program);
|
|
2167
|
+
registerComponentCommands(program);
|
|
2168
|
+
registerBoardCommands(program);
|
|
2169
|
+
registerSprintCommands(program);
|
|
2170
|
+
registerFieldCommands(program);
|
|
2171
|
+
registerUserCommands(program);
|
|
2172
|
+
registerTokenCommands(program);
|
|
2173
|
+
return program;
|
|
2023
2174
|
}
|
|
2175
|
+
|
|
2176
|
+
// src/index.ts
|
|
2177
|
+
await runCli(buildProgram(), {
|
|
2178
|
+
service: "Jira",
|
|
2179
|
+
authRecovery: "Set JIRA_URL and JIRA_TOKEN in your shell profile (~/.zshrc). Generate a token in Jira > Profile > Personal Access Tokens.",
|
|
2180
|
+
networkRecovery: "Verify JIRA_URL is correct, the server is reachable, and you are on the VPN if required."
|
|
2181
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiradc-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-ga26d641.1",
|
|
4
4
|
"publish": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"commander": "^13.1.0",
|
|
15
|
-
"jira-data-center-client": "1.0.37"
|
|
15
|
+
"jira-data-center-client": "1.0.37-ga26d641.1",
|
|
16
|
+
"cli-utils": "1.0.0"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@types/node": "24.10.4",
|
|
@@ -34,6 +35,6 @@
|
|
|
34
35
|
"lint": "eslint src",
|
|
35
36
|
"lint:fix": "eslint src --fix",
|
|
36
37
|
"test": "vitest run",
|
|
37
|
-
"test:integration": "vitest run
|
|
38
|
+
"test:integration": "vitest run --config vitest.integration.config.ts"
|
|
38
39
|
}
|
|
39
40
|
}
|