jiradc-cli 1.0.20 → 1.0.22
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 +13 -6
- package/dist/index.js +789 -545
- package/package.json +6 -5
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 Command8 } 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,43 +48,281 @@ function positiveInt(raw) {
|
|
|
39
48
|
return n;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
//
|
|
43
|
-
|
|
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
|
+
var EXAMPLES = /* @__PURE__ */ new WeakSet();
|
|
84
|
+
function commandPath(cmd) {
|
|
85
|
+
const parts = [];
|
|
86
|
+
for (let c = cmd; c; c = c.parent)
|
|
87
|
+
parts.unshift(c.name());
|
|
88
|
+
return parts.join(" ");
|
|
89
|
+
}
|
|
90
|
+
function exampleLine(path, entry) {
|
|
91
|
+
if (typeof entry === "string")
|
|
92
|
+
return entry ? ` ${path} ${entry}` : ` ${path}`;
|
|
93
|
+
if ("raw" in entry)
|
|
94
|
+
return ` ${entry.raw}`;
|
|
95
|
+
const [args, comment] = entry;
|
|
96
|
+
const line = args ? `${path} ${args}` : path;
|
|
97
|
+
return ` ${line} # ${comment}`;
|
|
98
|
+
}
|
|
99
|
+
function examples(cmd, entries) {
|
|
100
|
+
EXAMPLES.add(cmd);
|
|
101
|
+
return cmd.addHelpText("after", () => {
|
|
102
|
+
const path = commandPath(cmd);
|
|
103
|
+
return `
|
|
104
|
+
Examples:
|
|
105
|
+
${entries.map((e) => exampleLine(path, e)).join("\n")}`;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
44
108
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
109
|
+
// ../../cli-utils/dist/errors.js
|
|
110
|
+
import { CommanderError } from "commander";
|
|
111
|
+
var EXIT = {
|
|
112
|
+
SUCCESS: 0,
|
|
113
|
+
GENERIC: 1,
|
|
114
|
+
USAGE: 2,
|
|
115
|
+
NOT_FOUND: 3,
|
|
116
|
+
FORBIDDEN: 4,
|
|
117
|
+
CONFLICT: 5,
|
|
118
|
+
AUTH: 6
|
|
119
|
+
};
|
|
120
|
+
var CliAuthError = class extends Error {
|
|
121
|
+
constructor(message) {
|
|
122
|
+
super(message);
|
|
123
|
+
this.name = "CliAuthError";
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var TYPE_EXIT = {
|
|
127
|
+
usage: EXIT.USAGE,
|
|
128
|
+
not_found: EXIT.NOT_FOUND,
|
|
129
|
+
forbidden: EXIT.FORBIDDEN,
|
|
130
|
+
conflict: EXIT.CONFLICT,
|
|
131
|
+
auth: EXIT.AUTH,
|
|
132
|
+
rate_limited: EXIT.GENERIC,
|
|
133
|
+
server: EXIT.GENERIC,
|
|
134
|
+
network: EXIT.GENERIC,
|
|
135
|
+
unknown: EXIT.GENERIC
|
|
136
|
+
};
|
|
137
|
+
var NETWORK_CODES = ["ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"];
|
|
138
|
+
function httpStatus(err) {
|
|
139
|
+
const e = err;
|
|
140
|
+
return e?.response?.status ?? e?.statusCode;
|
|
141
|
+
}
|
|
142
|
+
function responseDetail(err) {
|
|
143
|
+
const e = err;
|
|
144
|
+
const data = e?.response?.data;
|
|
145
|
+
if (data && typeof data === "object")
|
|
146
|
+
return data;
|
|
147
|
+
const body = e?.body;
|
|
148
|
+
if (typeof body === "string") {
|
|
149
|
+
try {
|
|
150
|
+
const parsed = JSON.parse(body);
|
|
151
|
+
return parsed.error ?? parsed;
|
|
152
|
+
} catch {
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (body && typeof body === "object") {
|
|
157
|
+
return body.error ?? body;
|
|
158
|
+
}
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function errorCode(err) {
|
|
162
|
+
return err?.code;
|
|
163
|
+
}
|
|
164
|
+
function normalize(err, opts) {
|
|
165
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
166
|
+
if (err instanceof CommanderError) {
|
|
167
|
+
return {
|
|
168
|
+
type: "usage",
|
|
169
|
+
message: (message || "Invalid command usage").replace(/^error:\s+/, ""),
|
|
170
|
+
recovery: "Check the command syntax and flags; run the command with --help.",
|
|
171
|
+
retryable: false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (err instanceof CliAuthError) {
|
|
175
|
+
return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
|
|
176
|
+
}
|
|
177
|
+
const status = httpStatus(err);
|
|
178
|
+
const detail = responseDetail(err);
|
|
179
|
+
if (status !== void 0) {
|
|
180
|
+
switch (status) {
|
|
181
|
+
case 400:
|
|
182
|
+
return {
|
|
183
|
+
type: "usage",
|
|
184
|
+
status,
|
|
185
|
+
message: "Bad request (HTTP 400)",
|
|
186
|
+
recovery: "Check parameter values (ids, keys, query syntax) against the API.",
|
|
187
|
+
retryable: false,
|
|
188
|
+
detail
|
|
189
|
+
};
|
|
190
|
+
case 401:
|
|
191
|
+
return {
|
|
192
|
+
type: "auth",
|
|
193
|
+
status,
|
|
194
|
+
message: "Authentication failed (HTTP 401)",
|
|
195
|
+
recovery: opts.authRecovery,
|
|
196
|
+
retryable: false
|
|
197
|
+
};
|
|
198
|
+
case 403:
|
|
199
|
+
return {
|
|
200
|
+
type: "forbidden",
|
|
201
|
+
status,
|
|
202
|
+
message: "Forbidden (HTTP 403)",
|
|
203
|
+
recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
|
|
204
|
+
retryable: false,
|
|
205
|
+
detail
|
|
206
|
+
};
|
|
207
|
+
case 404:
|
|
208
|
+
return {
|
|
209
|
+
type: "not_found",
|
|
210
|
+
status,
|
|
211
|
+
message: "Not found (HTTP 404)",
|
|
212
|
+
recovery: "Verify the id/key exists and that you have access to it.",
|
|
213
|
+
retryable: false,
|
|
214
|
+
detail
|
|
215
|
+
};
|
|
216
|
+
case 409:
|
|
217
|
+
return {
|
|
218
|
+
type: "conflict",
|
|
219
|
+
status,
|
|
220
|
+
message: "Conflict (HTTP 409)",
|
|
221
|
+
recovery: "The resource changed or already exists; re-fetch current state and retry.",
|
|
222
|
+
retryable: false,
|
|
223
|
+
detail
|
|
224
|
+
};
|
|
225
|
+
case 429:
|
|
226
|
+
return {
|
|
227
|
+
type: "rate_limited",
|
|
228
|
+
status,
|
|
229
|
+
message: "Rate limited (HTTP 429)",
|
|
230
|
+
recovery: "Wait and retry the request.",
|
|
231
|
+
retryable: true
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (status >= 500) {
|
|
235
|
+
return {
|
|
236
|
+
type: "server",
|
|
237
|
+
status,
|
|
238
|
+
message: `Server error (HTTP ${status})`,
|
|
239
|
+
recovery: `${opts.service} returned an internal error; retry shortly.`,
|
|
240
|
+
retryable: true,
|
|
241
|
+
detail
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
type: "unknown",
|
|
246
|
+
status,
|
|
247
|
+
message: `${opts.service} error (HTTP ${status}): ${message}`,
|
|
248
|
+
recovery: "Inspect the detail field for the API response.",
|
|
249
|
+
retryable: false,
|
|
250
|
+
detail
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const code = errorCode(err);
|
|
254
|
+
if (code && NETWORK_CODES.includes(code) || NETWORK_CODES.some((c) => message.includes(c))) {
|
|
255
|
+
return {
|
|
256
|
+
type: "network",
|
|
257
|
+
message: `Cannot connect to ${opts.service}: ${message}`,
|
|
258
|
+
recovery: opts.networkRecovery ?? "Verify the *_URL is correct, the server is reachable, and you are on the VPN if required.",
|
|
259
|
+
retryable: true
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
|
|
263
|
+
}
|
|
264
|
+
function classifyError(err, opts) {
|
|
265
|
+
const base = normalize(err, opts);
|
|
266
|
+
const n = opts.adapt ? opts.adapt(base, err) : base;
|
|
267
|
+
if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
|
|
268
|
+
n.detail = opts.credentialInfo();
|
|
269
|
+
}
|
|
49
270
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
description: "Personal Access Token"
|
|
271
|
+
envelope: {
|
|
272
|
+
error: {
|
|
273
|
+
type: n.type,
|
|
274
|
+
message: n.message,
|
|
275
|
+
recovery: n.recovery,
|
|
276
|
+
retryable: n.retryable,
|
|
277
|
+
...n.detail !== void 0 ? { detail: n.detail } : {}
|
|
58
278
|
}
|
|
59
279
|
},
|
|
60
|
-
|
|
61
|
-
|
|
280
|
+
exitCode: TYPE_EXIT[n.type]
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function createErrorHandler(opts) {
|
|
284
|
+
return (err) => {
|
|
285
|
+
const { envelope, exitCode } = classifyError(err, opts);
|
|
286
|
+
process.stderr.write(`${JSON.stringify(envelope)}
|
|
287
|
+
`);
|
|
288
|
+
return process.exit(exitCode);
|
|
62
289
|
};
|
|
63
290
|
}
|
|
291
|
+
function isCleanCommanderExit(err) {
|
|
292
|
+
return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
|
|
293
|
+
}
|
|
294
|
+
function routeErrors(cmd) {
|
|
295
|
+
cmd.exitOverride();
|
|
296
|
+
cmd.configureOutput({ writeErr: () => void 0 });
|
|
297
|
+
cmd.commands.forEach(routeErrors);
|
|
298
|
+
}
|
|
299
|
+
async function runCli(program, opts) {
|
|
300
|
+
routeErrors(program);
|
|
301
|
+
try {
|
|
302
|
+
await program.parseAsync();
|
|
303
|
+
} catch (err) {
|
|
304
|
+
if (isCleanCommanderExit(err)) {
|
|
305
|
+
process.exit(err.exitCode ?? 0);
|
|
306
|
+
}
|
|
307
|
+
createErrorHandler(opts)(err);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/program.ts
|
|
312
|
+
import { styleText } from "util";
|
|
313
|
+
import { Command as Command11 } from "commander";
|
|
314
|
+
|
|
315
|
+
// src/commands/board/issues.ts
|
|
316
|
+
import { Argument } from "commander";
|
|
64
317
|
|
|
65
318
|
// src/utils/client.ts
|
|
319
|
+
import { JiraClient } from "jira-data-center-client";
|
|
66
320
|
function getClient() {
|
|
67
321
|
const baseUrl = process.env.JIRA_URL;
|
|
68
322
|
const token = process.env.JIRA_TOKEN;
|
|
69
323
|
if (!baseUrl || !token) {
|
|
70
324
|
const missing = [...!baseUrl ? ["JIRA_URL"] : [], ...!token ? ["JIRA_TOKEN"] : []];
|
|
71
|
-
|
|
72
|
-
`${JSON.stringify({
|
|
73
|
-
error: `Missing required environment variables: ${missing.join(", ")}`,
|
|
74
|
-
...getCredentialInfo()
|
|
75
|
-
})}
|
|
76
|
-
`
|
|
77
|
-
);
|
|
78
|
-
process.exit(1);
|
|
325
|
+
throw new CliAuthError(`Missing required environment variables: ${missing.join(", ")}`);
|
|
79
326
|
}
|
|
80
327
|
return new JiraClient({ baseUrl, token });
|
|
81
328
|
}
|
|
@@ -89,68 +336,6 @@ function output(data) {
|
|
|
89
336
|
process.stdout.write(`${JSON.stringify(data, null, prettyPrint ? 2 : void 0)}
|
|
90
337
|
`);
|
|
91
338
|
}
|
|
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
339
|
|
|
155
340
|
// src/utils/transformers/base.ts
|
|
156
341
|
function jiraBaseUrl() {
|
|
@@ -379,8 +564,8 @@ function transformIssueFields(fields) {
|
|
|
379
564
|
issuelinks,
|
|
380
565
|
subtasks,
|
|
381
566
|
parent,
|
|
382
|
-
comment
|
|
383
|
-
worklog
|
|
567
|
+
comment,
|
|
568
|
+
worklog,
|
|
384
569
|
attachment,
|
|
385
570
|
// required scalars / structured fields we always keep verbatim
|
|
386
571
|
summary,
|
|
@@ -412,20 +597,20 @@ function transformIssueFields(fields) {
|
|
|
412
597
|
// Drop empty comment / worklog containers entirely. The default Jira
|
|
413
598
|
// search response includes both wrappers on every issue regardless of
|
|
414
599
|
// count; on a 25-issue page that's 25 × 2 empty objects of pure noise.
|
|
415
|
-
...
|
|
600
|
+
...comment && comment.comments.length > 0 ? {
|
|
416
601
|
comment: {
|
|
417
|
-
comments:
|
|
418
|
-
maxResults:
|
|
419
|
-
total:
|
|
420
|
-
startAt:
|
|
602
|
+
comments: comment.comments.map(transformComment),
|
|
603
|
+
maxResults: comment.maxResults,
|
|
604
|
+
total: comment.total,
|
|
605
|
+
startAt: comment.startAt
|
|
421
606
|
}
|
|
422
607
|
} : {},
|
|
423
|
-
...
|
|
608
|
+
...worklog && worklog.worklogs.length > 0 ? {
|
|
424
609
|
worklog: {
|
|
425
|
-
worklogs:
|
|
426
|
-
maxResults:
|
|
427
|
-
total:
|
|
428
|
-
startAt:
|
|
610
|
+
worklogs: worklog.worklogs.map(transformWorklog),
|
|
611
|
+
maxResults: worklog.maxResults,
|
|
612
|
+
total: worklog.total,
|
|
613
|
+
startAt: worklog.startAt
|
|
429
614
|
}
|
|
430
615
|
} : {},
|
|
431
616
|
...attachment && attachment.length > 0 ? { attachment: attachment.map(transformAttachment) } : {}
|
|
@@ -477,10 +662,14 @@ function transformTransitions(response) {
|
|
|
477
662
|
|
|
478
663
|
// src/commands/board/issues.ts
|
|
479
664
|
function issues(parent) {
|
|
480
|
-
parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board")
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
665
|
+
const cmd = parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board");
|
|
666
|
+
examples(cmd, [
|
|
667
|
+
"42",
|
|
668
|
+
"42 --limit 10",
|
|
669
|
+
'42 --jql "status = Open" --fields summary,status',
|
|
670
|
+
"42 --start 50 --limit 25"
|
|
671
|
+
]);
|
|
672
|
+
cmd.action(async (id, opts) => {
|
|
484
673
|
const client = getClient();
|
|
485
674
|
const result = await client.agile.getBoardIssues({
|
|
486
675
|
boardId: id,
|
|
@@ -497,10 +686,9 @@ function issues(parent) {
|
|
|
497
686
|
import { Option } from "commander";
|
|
498
687
|
var BOARD_TYPES = ["scrum", "kanban", "simple"];
|
|
499
688
|
function list(parent) {
|
|
500
|
-
parent.command("list").description("List agile boards").option("--limit <number>", "Max results (1-1000)", intInRange(1, 1e3), 25).option("--project <key>", "Filter boards by project key or ID").addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name")
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
).action(async (opts) => {
|
|
689
|
+
const cmd = parent.command("list").description("List agile boards").option("--limit <number>", "Max results (1-1000)", intInRange(1, 1e3), 25).option("--project <key>", "Filter boards by project key or ID").addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name");
|
|
690
|
+
examples(cmd, ["", "--limit 10", "--project PROJ", '--type scrum --name "Team Board"']);
|
|
691
|
+
cmd.action(async (opts) => {
|
|
504
692
|
const client = getClient();
|
|
505
693
|
const result = await client.agile.getBoards({
|
|
506
694
|
maxResults: opts.limit,
|
|
@@ -513,16 +701,9 @@ function list(parent) {
|
|
|
513
701
|
}
|
|
514
702
|
|
|
515
703
|
// src/commands/board/index.ts
|
|
516
|
-
function registerBoardCommands(
|
|
517
|
-
const board =
|
|
518
|
-
|
|
519
|
-
`
|
|
520
|
-
Examples:
|
|
521
|
-
$ jiradc board list
|
|
522
|
-
$ jiradc board list --type scrum --project PROJ
|
|
523
|
-
$ jiradc board issues 42 --limit 20
|
|
524
|
-
`
|
|
525
|
-
);
|
|
704
|
+
function registerBoardCommands(program) {
|
|
705
|
+
const board = program.command("board").description("Board operations");
|
|
706
|
+
examples(board, ["list", "list --type scrum --project PROJ", "issues 42 --limit 20"]);
|
|
526
707
|
list(board);
|
|
527
708
|
issues(board);
|
|
528
709
|
}
|
|
@@ -536,14 +717,13 @@ var ASSIGNEE_TYPES = [
|
|
|
536
717
|
"UNASSIGNED"
|
|
537
718
|
];
|
|
538
719
|
function create(parent) {
|
|
539
|
-
parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., AI)").requiredOption("--name <name>", "Component name").option("--description <text>", "Component description").option("--lead <username>", "Username of the component lead").addOption(new Option2("--assignee-type <type>", "Assignee strategy").choices(ASSIGNEE_TYPES))
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
).action(
|
|
720
|
+
const cmd = parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., AI)").requiredOption("--name <name>", "Component name").option("--description <text>", "Component description").option("--lead <username>", "Username of the component lead").addOption(new Option2("--assignee-type <type>", "Assignee strategy").choices(ASSIGNEE_TYPES));
|
|
721
|
+
examples(cmd, [
|
|
722
|
+
"--project AI --name Backend",
|
|
723
|
+
'--project AI --name Frontend --description "UI work" --lead jsmith',
|
|
724
|
+
"--project AI --name Infra --assignee-type COMPONENT_LEAD --lead jsmith"
|
|
725
|
+
]);
|
|
726
|
+
cmd.action(
|
|
547
727
|
async (opts) => {
|
|
548
728
|
const client = getClient();
|
|
549
729
|
const result = await client.components.create({
|
|
@@ -560,13 +740,9 @@ Examples:
|
|
|
560
740
|
|
|
561
741
|
// src/commands/component/delete.ts
|
|
562
742
|
function deleteComponent(parent) {
|
|
563
|
-
parent.command("delete <id>").description("Delete a component, optionally reassigning its issues to another component").option("--move-issues-to <id>", "Reassign existing issues to this component ID before deletion")
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
Examples:
|
|
567
|
-
jiradc component delete 11289
|
|
568
|
-
jiradc component delete 11289 --move-issues-to 11290`
|
|
569
|
-
).action(async (id, opts) => {
|
|
743
|
+
const cmd = parent.command("delete <id>").description("Delete a component, optionally reassigning its issues to another component").option("--move-issues-to <id>", "Reassign existing issues to this component ID before deletion");
|
|
744
|
+
examples(cmd, ["11289", "11289 --move-issues-to 11290"]);
|
|
745
|
+
cmd.action(async (id, opts) => {
|
|
570
746
|
const client = getClient();
|
|
571
747
|
await client.components.delete({ id, moveIssuesTo: opts.moveIssuesTo });
|
|
572
748
|
output({ deleted: true, componentId: id, ...opts.moveIssuesTo && { movedIssuesTo: opts.moveIssuesTo } });
|
|
@@ -575,7 +751,9 @@ Examples:
|
|
|
575
751
|
|
|
576
752
|
// src/commands/component/get.ts
|
|
577
753
|
function get(parent) {
|
|
578
|
-
parent.command("get <id>").description("Get a component by ID")
|
|
754
|
+
const cmd = parent.command("get <id>").description("Get a component by ID");
|
|
755
|
+
examples(cmd, ["11289"]);
|
|
756
|
+
cmd.action(async (id) => {
|
|
579
757
|
const client = getClient();
|
|
580
758
|
const result = await client.components.get({ id });
|
|
581
759
|
output(transformComponent(result));
|
|
@@ -584,7 +762,9 @@ function get(parent) {
|
|
|
584
762
|
|
|
585
763
|
// src/commands/component/issue-count.ts
|
|
586
764
|
function issueCount(parent) {
|
|
587
|
-
parent.command("issue-count <id>").description("Get the number of issues currently using this component")
|
|
765
|
+
const cmd = parent.command("issue-count <id>").description("Get the number of issues currently using this component");
|
|
766
|
+
examples(cmd, ["11289"]);
|
|
767
|
+
cmd.action(async (id) => {
|
|
588
768
|
const client = getClient();
|
|
589
769
|
const result = await client.components.getRelatedIssueCounts({ id });
|
|
590
770
|
output(transformComponentIssueCounts(result));
|
|
@@ -593,7 +773,9 @@ function issueCount(parent) {
|
|
|
593
773
|
|
|
594
774
|
// src/commands/component/list.ts
|
|
595
775
|
function list2(parent) {
|
|
596
|
-
parent.command("list").description("List all components for a project").requiredOption("--project <key>", "Project key (e.g., AI)")
|
|
776
|
+
const cmd = parent.command("list").description("List all components for a project").requiredOption("--project <key>", "Project key (e.g., AI)");
|
|
777
|
+
examples(cmd, ["--project AI"]);
|
|
778
|
+
cmd.action(async (opts) => {
|
|
597
779
|
const client = getClient();
|
|
598
780
|
const result = await client.components.list({ projectKeyOrId: opts.project });
|
|
599
781
|
output(result.map(transformComponent));
|
|
@@ -609,14 +791,13 @@ var ASSIGNEE_TYPES2 = [
|
|
|
609
791
|
"UNASSIGNED"
|
|
610
792
|
];
|
|
611
793
|
function update(parent) {
|
|
612
|
-
parent.command("update <id>").description("Update an existing component (only provided fields are changed)").option("--name <name>", "New component name").option("--description <text>", "New component description").option("--lead <username>", "Username of the component lead (empty string clears it)").addOption(new Option3("--assignee-type <type>", "New assignee strategy").choices(ASSIGNEE_TYPES2))
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
).action(
|
|
794
|
+
const cmd = parent.command("update <id>").description("Update an existing component (only provided fields are changed)").option("--name <name>", "New component name").option("--description <text>", "New component description").option("--lead <username>", "Username of the component lead (empty string clears it)").addOption(new Option3("--assignee-type <type>", "New assignee strategy").choices(ASSIGNEE_TYPES2));
|
|
795
|
+
examples(cmd, [
|
|
796
|
+
"11289 --name Backend",
|
|
797
|
+
'11289 --description "Server-side code"',
|
|
798
|
+
"11289 --lead jsmith --assignee-type COMPONENT_LEAD"
|
|
799
|
+
]);
|
|
800
|
+
cmd.action(
|
|
620
801
|
async (id, opts) => {
|
|
621
802
|
if (opts.name === void 0 && opts.description === void 0 && opts.lead === void 0 && opts.assigneeType === void 0) {
|
|
622
803
|
throw new Error("Provide at least one of --name, --description, --lead, --assignee-type");
|
|
@@ -635,19 +816,16 @@ Examples:
|
|
|
635
816
|
}
|
|
636
817
|
|
|
637
818
|
// src/commands/component/index.ts
|
|
638
|
-
function registerComponentCommands(
|
|
639
|
-
const component =
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
$ jiradc component issue-count 11289
|
|
649
|
-
`
|
|
650
|
-
);
|
|
819
|
+
function registerComponentCommands(program) {
|
|
820
|
+
const component = program.command("component").description("Project component operations");
|
|
821
|
+
examples(component, [
|
|
822
|
+
"list --project AI",
|
|
823
|
+
"get 11289",
|
|
824
|
+
"create --project AI --name Backend",
|
|
825
|
+
"update 11289 --name Backend",
|
|
826
|
+
"delete 11289 --move-issues-to 11290",
|
|
827
|
+
"issue-count 11289"
|
|
828
|
+
]);
|
|
651
829
|
list2(component);
|
|
652
830
|
get(component);
|
|
653
831
|
create(component);
|
|
@@ -658,16 +836,15 @@ Examples:
|
|
|
658
836
|
|
|
659
837
|
// src/commands/field/options.ts
|
|
660
838
|
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("--
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
).action(async (id, opts) => {
|
|
839
|
+
const cmd = 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);
|
|
840
|
+
examples(cmd, ["10001", '10001 --query "High"', "10001 --limit 20 --start 2"]);
|
|
841
|
+
cmd.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
|
});
|
|
@@ -675,10 +852,9 @@ function options(parent) {
|
|
|
675
852
|
|
|
676
853
|
// src/commands/field/search.ts
|
|
677
854
|
function search(parent) {
|
|
678
|
-
parent.command("search <keyword>").description("Search for fields by name or ID").option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25)
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
).action(async (keyword, opts) => {
|
|
855
|
+
const cmd = parent.command("search <keyword>").description("Search for fields by name or ID").option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25);
|
|
856
|
+
examples(cmd, ["epic", "customfield_10100", "priority --limit 5"]);
|
|
857
|
+
cmd.action(async (keyword, opts) => {
|
|
682
858
|
const client = getClient();
|
|
683
859
|
const result = await client.fields.search(keyword, opts.limit);
|
|
684
860
|
output(result.map(transformField));
|
|
@@ -686,16 +862,9 @@ function search(parent) {
|
|
|
686
862
|
}
|
|
687
863
|
|
|
688
864
|
// src/commands/field/index.ts
|
|
689
|
-
function registerFieldCommands(
|
|
690
|
-
const field =
|
|
691
|
-
|
|
692
|
-
`
|
|
693
|
-
Examples:
|
|
694
|
-
$ jiradc field search "epic"
|
|
695
|
-
$ jiradc field search "priority"
|
|
696
|
-
$ jiradc field options 10120
|
|
697
|
-
`
|
|
698
|
-
);
|
|
865
|
+
function registerFieldCommands(program) {
|
|
866
|
+
const field = program.command("field").description("Field operations");
|
|
867
|
+
examples(field, ['search "epic"', 'search "priority"', "options 10120"]);
|
|
699
868
|
search(field);
|
|
700
869
|
options(field);
|
|
701
870
|
}
|
|
@@ -718,15 +887,10 @@ async function resolveUserToken(token) {
|
|
|
718
887
|
|
|
719
888
|
// src/commands/issue/assign.ts
|
|
720
889
|
function assign(parent) {
|
|
721
|
-
parent.command("assign <key>
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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, user) => {
|
|
729
|
-
const resolved = await resolveUserToken(user);
|
|
890
|
+
const cmd = 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');
|
|
891
|
+
examples(cmd, ["PROJ-123 --assignee jsmith", "PROJ-123 --assignee me", "PROJ-123 --assignee none"]);
|
|
892
|
+
cmd.action(async (key, opts) => {
|
|
893
|
+
const resolved = await resolveUserToken(opts.assignee);
|
|
730
894
|
const client = getClient();
|
|
731
895
|
await client.issues.update({
|
|
732
896
|
issueKeyOrId: key,
|
|
@@ -738,20 +902,25 @@ Examples:
|
|
|
738
902
|
|
|
739
903
|
// src/commands/issue/attachment/delete.ts
|
|
740
904
|
function deleteAttachment(parent) {
|
|
741
|
-
parent.command("delete").description("Delete an attachment by ID")
|
|
905
|
+
const cmd = parent.command("delete <key>").description("Delete an attachment by ID");
|
|
906
|
+
subEntityOption(cmd, "attachment", { mandatory: true });
|
|
907
|
+
examples(cmd, ["PROJ-123 --attachment-id 12345"]);
|
|
908
|
+
cmd.action(async (key, opts) => {
|
|
742
909
|
const client = getClient();
|
|
743
|
-
await client.issues.deleteAttachment({ attachmentId: opts.
|
|
744
|
-
output({ deleted: true, attachmentId: opts.
|
|
910
|
+
await client.issues.deleteAttachment({ attachmentId: String(opts.attachmentId) });
|
|
911
|
+
output({ deleted: true, issueKey: key, attachmentId: opts.attachmentId });
|
|
745
912
|
});
|
|
746
913
|
}
|
|
747
914
|
|
|
748
915
|
// src/commands/issue/attachment/download-all.ts
|
|
749
|
-
import { mkdirSync } from "fs";
|
|
750
|
-
import { join } from "path";
|
|
916
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
917
|
+
import { join as join3 } from "path";
|
|
751
918
|
function downloadAll(parent) {
|
|
752
|
-
parent.command("download-all <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into")
|
|
919
|
+
const cmd = parent.command("download-all <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into");
|
|
920
|
+
examples(cmd, ["PROJ-123 --output ./downloads"]);
|
|
921
|
+
cmd.action(async (key, opts) => {
|
|
753
922
|
const client = getClient();
|
|
754
|
-
|
|
923
|
+
mkdirSync2(opts.output, { recursive: true });
|
|
755
924
|
const issue = await client.issues.get({
|
|
756
925
|
issueKeyOrId: key,
|
|
757
926
|
fields: ["attachment"]
|
|
@@ -768,7 +937,7 @@ function downloadAll(parent) {
|
|
|
768
937
|
failed.push({ filename: att.filename, error: "No content URL" });
|
|
769
938
|
continue;
|
|
770
939
|
}
|
|
771
|
-
const destPath =
|
|
940
|
+
const destPath = join3(opts.output, att.filename);
|
|
772
941
|
try {
|
|
773
942
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
|
|
774
943
|
results.push({ filename: att.filename, size: att.size, path: destPath });
|
|
@@ -788,11 +957,16 @@ function downloadAll(parent) {
|
|
|
788
957
|
|
|
789
958
|
// src/commands/issue/attachment/download.ts
|
|
790
959
|
function download(parent) {
|
|
791
|
-
parent.command("download <key>").description("Download a single attachment by ID")
|
|
960
|
+
const cmd = parent.command("download <key>").description("Download a single attachment by ID");
|
|
961
|
+
subEntityOption(cmd, "attachment", { mandatory: true });
|
|
962
|
+
cmd.requiredOption("--output <path>", "Local file path to save the attachment");
|
|
963
|
+
examples(cmd, ["PROJ-123 --attachment-id 12345 --output ./report.pdf"]);
|
|
964
|
+
cmd.action(async (key, opts) => {
|
|
792
965
|
const client = getClient();
|
|
793
|
-
const
|
|
966
|
+
const attachmentId = String(opts.attachmentId);
|
|
967
|
+
const attachment = await client.issues.getAttachment({ attachmentId });
|
|
794
968
|
if (!attachment.content) {
|
|
795
|
-
throw new Error(`Attachment ${
|
|
969
|
+
throw new Error(`Attachment ${attachmentId} has no content URL`);
|
|
796
970
|
}
|
|
797
971
|
await client.issues.downloadAttachment({
|
|
798
972
|
url: attachment.content,
|
|
@@ -810,7 +984,9 @@ function download(parent) {
|
|
|
810
984
|
|
|
811
985
|
// src/commands/issue/attachment/list.ts
|
|
812
986
|
function list3(parent) {
|
|
813
|
-
parent.command("list <key>").description("List attachments on an issue")
|
|
987
|
+
const cmd = parent.command("list <key>").description("List attachments on an issue");
|
|
988
|
+
examples(cmd, ["PROJ-123"]);
|
|
989
|
+
cmd.action(async (key) => {
|
|
814
990
|
const client = getClient();
|
|
815
991
|
const issue = await client.issues.get({
|
|
816
992
|
issueKeyOrId: key,
|
|
@@ -833,16 +1009,15 @@ function list3(parent) {
|
|
|
833
1009
|
|
|
834
1010
|
// src/commands/issue/attachment/upload.ts
|
|
835
1011
|
function upload(parent) {
|
|
836
|
-
parent.command("upload <key>").description("Upload attachments to an issue").requiredOption("--files <paths>", "Comma-separated file paths to upload")
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
).action(async (key, opts) => {
|
|
1012
|
+
const cmd = parent.command("upload <key>").description("Upload attachments to an issue").requiredOption("--files <paths>", "Comma-separated file paths to upload");
|
|
1013
|
+
examples(cmd, ["PROJ-123 --files ./report.pdf", "PROJ-123 --files ./a.txt,./b.png"]);
|
|
1014
|
+
cmd.action(async (key, opts) => {
|
|
840
1015
|
const client = getClient();
|
|
841
1016
|
const filePaths = opts.files.split(",").map((f) => f.trim());
|
|
842
1017
|
const results = [];
|
|
843
1018
|
for (const filePath of filePaths) {
|
|
844
|
-
const
|
|
845
|
-
results.push(...
|
|
1019
|
+
const attachments = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
|
|
1020
|
+
results.push(...attachments);
|
|
846
1021
|
}
|
|
847
1022
|
output({
|
|
848
1023
|
issueKey: key,
|
|
@@ -860,17 +1035,14 @@ function upload(parent) {
|
|
|
860
1035
|
|
|
861
1036
|
// src/commands/issue/attachment/index.ts
|
|
862
1037
|
function registerAttachmentCommands(parent) {
|
|
863
|
-
const attachment = parent.command("attachment").description("Attachment operations")
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
$ jiradc issue attachment delete --id 12345
|
|
872
|
-
`
|
|
873
|
-
);
|
|
1038
|
+
const attachment = parent.command("attachment").description("Attachment operations");
|
|
1039
|
+
examples(attachment, [
|
|
1040
|
+
"list PROJ-123",
|
|
1041
|
+
"upload PROJ-123 --files ./report.pdf",
|
|
1042
|
+
"download PROJ-123 --attachment-id 12345 --output ./report.pdf",
|
|
1043
|
+
"download-all PROJ-123 --output ./downloads",
|
|
1044
|
+
"delete PROJ-123 --attachment-id 12345"
|
|
1045
|
+
]);
|
|
874
1046
|
upload(attachment);
|
|
875
1047
|
list3(attachment);
|
|
876
1048
|
download(attachment);
|
|
@@ -878,53 +1050,11 @@ Examples:
|
|
|
878
1050
|
deleteAttachment(attachment);
|
|
879
1051
|
}
|
|
880
1052
|
|
|
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
1053
|
// src/commands/issue/batch-changelog.ts
|
|
923
1054
|
function batchChangelog(parent) {
|
|
924
|
-
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)
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
).action(async (keys, opts) => {
|
|
1055
|
+
const cmd = 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);
|
|
1056
|
+
examples(cmd, ["PROJ-1,PROJ-2,PROJ-3", "PROJ-123,PROJ-124 --limit 10"]);
|
|
1057
|
+
cmd.action(async (keys, opts) => {
|
|
928
1058
|
const client = getClient();
|
|
929
1059
|
const keyList = keys.split(",").map((k) => k.trim());
|
|
930
1060
|
const entries = await Promise.all(
|
|
@@ -943,12 +1073,11 @@ function batchChangelog(parent) {
|
|
|
943
1073
|
|
|
944
1074
|
// src/commands/issue/batch-create.ts
|
|
945
1075
|
function batchCreate(parent) {
|
|
946
|
-
parent.command("batch-create").description("Create multiple issues from a JSON array").requiredOption("--issues <json>", "JSON array of issue objects")
|
|
947
|
-
|
|
948
|
-
`
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
).action(async (opts) => {
|
|
1076
|
+
const cmd = parent.command("batch-create").description("Create multiple issues from a JSON array").requiredOption("--issues <json>", "JSON array of issue objects");
|
|
1077
|
+
examples(cmd, [
|
|
1078
|
+
`--issues '[{"projectKeyOrId":"PROJ","issueTypeName":"Task","summary":"Task 1"},{"projectKeyOrId":"PROJ","issueTypeName":"Task","summary":"Task 2"}]'`
|
|
1079
|
+
]);
|
|
1080
|
+
cmd.action(async (opts) => {
|
|
952
1081
|
const client = getClient();
|
|
953
1082
|
const parsed = JSON.parse(opts.issues);
|
|
954
1083
|
const results = [];
|
|
@@ -966,10 +1095,9 @@ Examples:
|
|
|
966
1095
|
|
|
967
1096
|
// src/commands/issue/changelog.ts
|
|
968
1097
|
function changelog(parent) {
|
|
969
|
-
parent.command("changelog <key>").description("Get changelog for an issue").option("--limit <number>", "Max changelog entries (1-50, Jira DC caps at 50)", intInRange(1, 50), 25)
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
).action(async (key, opts) => {
|
|
1098
|
+
const cmd = parent.command("changelog <key>").description("Get changelog for an issue").option("--limit <number>", "Max changelog entries (1-50, Jira DC caps at 50)", intInRange(1, 50), 25);
|
|
1099
|
+
examples(cmd, ["PROJ-123", "PROJ-123 --limit 10"]);
|
|
1100
|
+
cmd.action(async (key, opts) => {
|
|
973
1101
|
const client = getClient();
|
|
974
1102
|
const result = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.limit });
|
|
975
1103
|
output(result);
|
|
@@ -979,7 +1107,7 @@ function changelog(parent) {
|
|
|
979
1107
|
// src/commands/issue/clone.ts
|
|
980
1108
|
import { unlink } from "fs/promises";
|
|
981
1109
|
import { tmpdir } from "os";
|
|
982
|
-
import { join as
|
|
1110
|
+
import { join as join4 } from "path";
|
|
983
1111
|
var CLONE_FIELDS = [
|
|
984
1112
|
"summary",
|
|
985
1113
|
"description",
|
|
@@ -995,14 +1123,13 @@ var CLONE_FIELDS = [
|
|
|
995
1123
|
"issuelinks"
|
|
996
1124
|
];
|
|
997
1125
|
function clone(parent) {
|
|
998
|
-
parent.command("clone <key>").description("Clone an issue (create a duplicate with the same fields)").option("--summary <text>", 'Override the summary (default: "CLONE - <original>")').option("--project <key>", "Create in a different project").option("--assignee <username>", "Override assignee").option("--include-attachments", "Copy attachments to the cloned issue").option("--include-links", "Copy issue links to the cloned issue")
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
).action(
|
|
1126
|
+
const cmd = parent.command("clone <key>").description("Clone an issue (create a duplicate with the same fields)").option("--summary <text>", 'Override the summary (default: "CLONE - <original>")').option("--project <key>", "Create in a different project").option("--assignee <username>", "Override assignee").option("--include-attachments", "Copy attachments to the cloned issue").option("--include-links", "Copy issue links to the cloned issue");
|
|
1127
|
+
examples(cmd, [
|
|
1128
|
+
"PROJ-123",
|
|
1129
|
+
'PROJ-123 --summary "Cloned: new title"',
|
|
1130
|
+
"PROJ-123 --include-attachments --include-links"
|
|
1131
|
+
]);
|
|
1132
|
+
cmd.action(
|
|
1006
1133
|
async (key, opts) => {
|
|
1007
1134
|
const client = getClient();
|
|
1008
1135
|
const source = await client.issues.get({ issueKeyOrId: key, fields: CLONE_FIELDS });
|
|
@@ -1030,7 +1157,7 @@ Examples:
|
|
|
1030
1157
|
const tmpFiles = [];
|
|
1031
1158
|
const copied = await Promise.all(
|
|
1032
1159
|
f.attachment.map(async (att) => {
|
|
1033
|
-
const tmpPath =
|
|
1160
|
+
const tmpPath = join4(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
|
|
1034
1161
|
tmpFiles.push(tmpPath);
|
|
1035
1162
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
|
|
1036
1163
|
await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
|
|
@@ -1071,35 +1198,69 @@ Examples:
|
|
|
1071
1198
|
);
|
|
1072
1199
|
}
|
|
1073
1200
|
|
|
1074
|
-
// src/commands/issue/comment
|
|
1075
|
-
function
|
|
1076
|
-
parent.command("
|
|
1201
|
+
// src/commands/issue/comment/create.ts
|
|
1202
|
+
function create2(parent) {
|
|
1203
|
+
const cmd = parent.command("create <key>").description("Add a comment to an issue");
|
|
1204
|
+
bodyOption(cmd, { mandatory: true });
|
|
1205
|
+
examples(cmd, ['PROJ-123 --body "Fixed in latest build"']);
|
|
1206
|
+
cmd.action(async (key, opts) => {
|
|
1077
1207
|
const client = getClient();
|
|
1078
|
-
const result = await client.issues.
|
|
1208
|
+
const result = await client.issues.addComment({ issueKeyOrId: key, body: opts.body });
|
|
1079
1209
|
output(transformComment(result));
|
|
1080
1210
|
});
|
|
1081
1211
|
}
|
|
1082
1212
|
|
|
1083
|
-
// src/commands/issue/comment.ts
|
|
1084
|
-
function
|
|
1085
|
-
parent.command("
|
|
1213
|
+
// src/commands/issue/comment/delete.ts
|
|
1214
|
+
function deleteComment(parent) {
|
|
1215
|
+
const cmd = parent.command("delete <key>").description("Delete a comment from an issue");
|
|
1216
|
+
subEntityOption(cmd, "comment", { mandatory: true });
|
|
1217
|
+
examples(cmd, ["PROJ-123 --comment-id 12345"]);
|
|
1218
|
+
cmd.action(async (key, opts) => {
|
|
1086
1219
|
const client = getClient();
|
|
1087
|
-
|
|
1220
|
+
await client.issues.deleteComment({ issueKeyOrId: key, commentId: String(opts.commentId) });
|
|
1221
|
+
output({ deleted: true, issueKey: key, commentId: opts.commentId });
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// src/commands/issue/comment/update.ts
|
|
1226
|
+
function update2(parent) {
|
|
1227
|
+
const cmd = parent.command("update <key>").description("Update an existing comment");
|
|
1228
|
+
subEntityOption(cmd, "comment", { mandatory: true });
|
|
1229
|
+
bodyOption(cmd, { mandatory: true });
|
|
1230
|
+
examples(cmd, ['PROJ-123 --comment-id 12345 --body "Updated comment text"']);
|
|
1231
|
+
cmd.action(async (key, opts) => {
|
|
1232
|
+
const client = getClient();
|
|
1233
|
+
const result = await client.issues.editComment({
|
|
1234
|
+
issueKeyOrId: key,
|
|
1235
|
+
commentId: String(opts.commentId),
|
|
1236
|
+
body: opts.body
|
|
1237
|
+
});
|
|
1088
1238
|
output(transformComment(result));
|
|
1089
1239
|
});
|
|
1090
1240
|
}
|
|
1091
1241
|
|
|
1242
|
+
// src/commands/issue/comment/index.ts
|
|
1243
|
+
function registerCommentCommands(parent) {
|
|
1244
|
+
const comment = parent.command("comment").description("Comment operations");
|
|
1245
|
+
examples(comment, [
|
|
1246
|
+
'create PROJ-123 --body "Fixed in latest build"',
|
|
1247
|
+
'update PROJ-123 --comment-id 12345 --body "Updated comment text"',
|
|
1248
|
+
"delete PROJ-123 --comment-id 12345"
|
|
1249
|
+
]);
|
|
1250
|
+
create2(comment);
|
|
1251
|
+
update2(comment);
|
|
1252
|
+
deleteComment(comment);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1092
1255
|
// src/commands/issue/create.ts
|
|
1093
|
-
function
|
|
1094
|
-
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"}')`)
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
jiradc issue create --project PROJ --type Bug --summary "Urgent" --labels urgent,production --components Backend`
|
|
1102
|
-
).action(
|
|
1256
|
+
function create3(parent) {
|
|
1257
|
+
const cmd = 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"}')`);
|
|
1258
|
+
examples(cmd, [
|
|
1259
|
+
'--project PROJ --type Task --summary "Fix login bug"',
|
|
1260
|
+
'--project PROJ --type Story --summary "New feature" --assignee jsmith --priority High',
|
|
1261
|
+
'--project PROJ --type Bug --summary "Urgent" --labels urgent,production --components Backend'
|
|
1262
|
+
]);
|
|
1263
|
+
cmd.action(
|
|
1103
1264
|
async (opts) => {
|
|
1104
1265
|
const client = getClient();
|
|
1105
1266
|
const result = await client.issues.create({
|
|
@@ -1124,10 +1285,9 @@ Examples:
|
|
|
1124
1285
|
|
|
1125
1286
|
// src/commands/issue/delete.ts
|
|
1126
1287
|
function deleteIssue(parent) {
|
|
1127
|
-
parent.command("delete <key>").description("Delete an issue").option("--delete-subtasks", "Also delete subtasks (default: false)")
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
).action(async (key, opts) => {
|
|
1288
|
+
const cmd = parent.command("delete <key>").description("Delete an issue").option("--delete-subtasks", "Also delete subtasks (default: false)");
|
|
1289
|
+
examples(cmd, ["PROJ-123", "PROJ-123 --delete-subtasks"]);
|
|
1290
|
+
cmd.action(async (key, opts) => {
|
|
1131
1291
|
const client = getClient();
|
|
1132
1292
|
await client.issues.delete({ issueKeyOrId: key, deleteSubtasks: opts.deleteSubtasks });
|
|
1133
1293
|
output({ deleted: true, issueKey: key });
|
|
@@ -1136,10 +1296,9 @@ function deleteIssue(parent) {
|
|
|
1136
1296
|
|
|
1137
1297
|
// src/commands/issue/dev-status.ts
|
|
1138
1298
|
function devStatus(parent) {
|
|
1139
|
-
parent.command("dev-status <key>").description("Get development status (PRs, commits, branches, builds) for an issue").option("--detail", "Include PR URLs, commit IDs, and other details")
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
).action(async (key, opts) => {
|
|
1299
|
+
const cmd = parent.command("dev-status <key>").description("Get development status (PRs, commits, branches, builds) for an issue").option("--detail", "Include PR URLs, commit IDs, and other details");
|
|
1300
|
+
examples(cmd, ["PROJ-123", "PROJ-123 --detail"]);
|
|
1301
|
+
cmd.action(async (key, opts) => {
|
|
1143
1302
|
const client = getClient();
|
|
1144
1303
|
const issue = await client.issues.get({ issueKeyOrId: key, fields: ["summary"] });
|
|
1145
1304
|
const issueId = issue.id;
|
|
@@ -1213,22 +1372,6 @@ function devStatus(parent) {
|
|
|
1213
1372
|
});
|
|
1214
1373
|
}
|
|
1215
1374
|
|
|
1216
|
-
// src/commands/issue/get-worklog.ts
|
|
1217
|
-
function getWorklog(parent) {
|
|
1218
|
-
parent.command("get-worklog <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(
|
|
1219
|
-
"after",
|
|
1220
|
-
"\nExamples:\n jiradc issue get-worklog PROJ-123\n jiradc issue get-worklog PROJ-123 --limit 10\n jiradc issue get-worklog PROJ-123 --start 10 --limit 5"
|
|
1221
|
-
).action(async (key, opts) => {
|
|
1222
|
-
const client = getClient();
|
|
1223
|
-
const result = await client.issues.getWorklogs({
|
|
1224
|
-
issueKeyOrId: key,
|
|
1225
|
-
startAt: opts.start,
|
|
1226
|
-
maxResults: opts.limit
|
|
1227
|
-
});
|
|
1228
|
-
output(transformPaged(result, transformWorklog));
|
|
1229
|
-
});
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
1375
|
// src/utils/constants.ts
|
|
1233
1376
|
var DEFAULT_FIELDS = [
|
|
1234
1377
|
"summary",
|
|
@@ -1247,10 +1390,14 @@ var DEFAULT_FIELDS = [
|
|
|
1247
1390
|
|
|
1248
1391
|
// src/commands/issue/get.ts
|
|
1249
1392
|
function get2(parent) {
|
|
1250
|
-
parent.command("get <key>").description("Get issue details").option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").option("--expand <expand>", 'Expand options (e.g., "transitions", "changelog")')
|
|
1251
|
-
|
|
1252
|
-
"
|
|
1253
|
-
|
|
1393
|
+
const cmd = parent.command("get <key>").description("Get issue details").option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").option("--expand <expand>", 'Expand options (e.g., "transitions", "changelog")');
|
|
1394
|
+
examples(cmd, [
|
|
1395
|
+
"PROJ-123",
|
|
1396
|
+
"PROJ-123 --fields summary,status,assignee",
|
|
1397
|
+
"PROJ-123 --all-fields",
|
|
1398
|
+
"PROJ-123 --expand changelog,transitions"
|
|
1399
|
+
]);
|
|
1400
|
+
cmd.action(async (key, opts) => {
|
|
1254
1401
|
const client = getClient();
|
|
1255
1402
|
const fields = opts.allFields ? void 0 : opts.fields?.split(",").map((f) => f.trim()) ?? DEFAULT_FIELDS;
|
|
1256
1403
|
const result = await client.issues.get({
|
|
@@ -1264,13 +1411,9 @@ function get2(parent) {
|
|
|
1264
1411
|
|
|
1265
1412
|
// src/commands/issue/link-epic.ts
|
|
1266
1413
|
function linkEpic(parent) {
|
|
1267
|
-
parent.command("link-epic <keys...>").description("Link one or more issues to an epic").requiredOption("--epic <epicKey>", "Epic issue key")
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
Examples:
|
|
1271
|
-
jiradc issue link-epic PROJ-456 --epic PROJ-123
|
|
1272
|
-
jiradc issue link-epic PROJ-456 PROJ-457 PROJ-458 --epic PROJ-123`
|
|
1273
|
-
).action(async (keys, opts) => {
|
|
1414
|
+
const cmd = parent.command("link-epic <keys...>").description("Link one or more issues to an epic").requiredOption("--epic <epicKey>", "Epic issue key");
|
|
1415
|
+
examples(cmd, ["PROJ-456 --epic PROJ-123", "PROJ-456 PROJ-457 PROJ-458 --epic PROJ-123"]);
|
|
1416
|
+
cmd.action(async (keys, opts) => {
|
|
1274
1417
|
const client = getClient();
|
|
1275
1418
|
const results = await Promise.allSettled(
|
|
1276
1419
|
keys.map(
|
|
@@ -1300,7 +1443,9 @@ Examples:
|
|
|
1300
1443
|
|
|
1301
1444
|
// src/commands/issue/link-types.ts
|
|
1302
1445
|
function linkTypes(parent) {
|
|
1303
|
-
parent.command("link-types").description("List all issue link types")
|
|
1446
|
+
const cmd = parent.command("link-types").description("List all issue link types");
|
|
1447
|
+
examples(cmd, [""]);
|
|
1448
|
+
cmd.action(async () => {
|
|
1304
1449
|
const client = getClient();
|
|
1305
1450
|
const result = await client.links.getTypes();
|
|
1306
1451
|
output(result.map(transformIssueLinkType));
|
|
@@ -1309,10 +1454,12 @@ function linkTypes(parent) {
|
|
|
1309
1454
|
|
|
1310
1455
|
// src/commands/issue/link.ts
|
|
1311
1456
|
function link(parent) {
|
|
1312
|
-
parent.command("link").description("Link two issues together").requiredOption("--type <name>", "Link type name (e.g., 'Blocks', 'Duplicate', 'Relates')").requiredOption("--from <key>", 'Source issue key (e.g., the issue that "blocks")').requiredOption("--to <key>", 'Target issue key (e.g., the issue that "is blocked by")').option("--comment <text>", "Optional comment")
|
|
1313
|
-
|
|
1314
|
-
"
|
|
1315
|
-
|
|
1457
|
+
const cmd = parent.command("link").description("Link two issues together").requiredOption("--type <name>", "Link type name (e.g., 'Blocks', 'Duplicate', 'Relates')").requiredOption("--from <key>", 'Source issue key (e.g., the issue that "blocks")').requiredOption("--to <key>", 'Target issue key (e.g., the issue that "is blocked by")').option("--comment <text>", "Optional comment");
|
|
1458
|
+
examples(cmd, [
|
|
1459
|
+
"--type Relates --from AI-154 --to AI-149",
|
|
1460
|
+
["--type Blocks --from PROJ-456 --to PROJ-123", "PROJ-456 blocks PROJ-123"]
|
|
1461
|
+
]);
|
|
1462
|
+
cmd.action(async (opts) => {
|
|
1316
1463
|
const client = getClient();
|
|
1317
1464
|
await client.links.create({
|
|
1318
1465
|
typeName: opts.type,
|
|
@@ -1331,10 +1478,13 @@ function link(parent) {
|
|
|
1331
1478
|
|
|
1332
1479
|
// src/commands/issue/search.ts
|
|
1333
1480
|
function search2(parent) {
|
|
1334
|
-
parent.command("search <jql>").description("Search issues using JQL").option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults")
|
|
1335
|
-
|
|
1336
|
-
'
|
|
1337
|
-
|
|
1481
|
+
const cmd = parent.command("search <jql>").description("Search issues using JQL").option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults");
|
|
1482
|
+
examples(cmd, [
|
|
1483
|
+
'"project = PROJ AND status = Open"',
|
|
1484
|
+
'"assignee = currentUser()" --limit 10 --fields summary,status',
|
|
1485
|
+
'"project = PROJ" --start 50 --limit 50'
|
|
1486
|
+
]);
|
|
1487
|
+
cmd.action(async (jql, opts) => {
|
|
1338
1488
|
const client = getClient();
|
|
1339
1489
|
const fields = opts.allFields ? void 0 : opts.fields?.split(",").map((f) => f.trim()) ?? DEFAULT_FIELDS;
|
|
1340
1490
|
const result = await client.issues.search({
|
|
@@ -1349,14 +1499,13 @@ function search2(parent) {
|
|
|
1349
1499
|
|
|
1350
1500
|
// src/commands/issue/transition.ts
|
|
1351
1501
|
function transition(parent) {
|
|
1352
|
-
parent.command("transition <key>").description("Transition issue to a new status").requiredOption("--to <idOrName>", "Transition ID, or status name (case-insensitive)").option("--comment <text>", "Comment to add during transition")
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
).action(async (key, opts) => {
|
|
1502
|
+
const cmd = parent.command("transition <key>").description("Transition issue to a new status").requiredOption("--to <idOrName>", "Transition ID, or status name (case-insensitive)").option("--comment <text>", "Comment to add during transition");
|
|
1503
|
+
examples(cmd, [
|
|
1504
|
+
"PROJ-123 --to 31",
|
|
1505
|
+
'PROJ-123 --to "In Review"',
|
|
1506
|
+
'PROJ-123 --to Done --comment "Verified in staging"'
|
|
1507
|
+
]);
|
|
1508
|
+
cmd.action(async (key, opts) => {
|
|
1360
1509
|
const client = getClient();
|
|
1361
1510
|
let transitionId;
|
|
1362
1511
|
if (/^\d+$/.test(opts.to)) {
|
|
@@ -1386,7 +1535,9 @@ Examples:
|
|
|
1386
1535
|
|
|
1387
1536
|
// src/commands/issue/transitions.ts
|
|
1388
1537
|
function transitions(parent) {
|
|
1389
|
-
parent.command("transitions <key>").description("Get available transitions for an issue")
|
|
1538
|
+
const cmd = parent.command("transitions <key>").description("Get available transitions for an issue");
|
|
1539
|
+
examples(cmd, ["PROJ-123"]);
|
|
1540
|
+
cmd.action(async (key) => {
|
|
1390
1541
|
const client = getClient();
|
|
1391
1542
|
const result = await client.issues.getTransitions({
|
|
1392
1543
|
issueKeyOrId: key,
|
|
@@ -1398,7 +1549,9 @@ function transitions(parent) {
|
|
|
1398
1549
|
|
|
1399
1550
|
// src/commands/issue/unlink.ts
|
|
1400
1551
|
function unlink2(parent) {
|
|
1401
|
-
parent.command("unlink <id>").description("Remove a link between two issues")
|
|
1552
|
+
const cmd = parent.command("unlink <id>").description("Remove a link between two issues");
|
|
1553
|
+
examples(cmd, ["12345"]);
|
|
1554
|
+
cmd.action(async (id) => {
|
|
1402
1555
|
const client = getClient();
|
|
1403
1556
|
await client.links.remove({ linkId: id });
|
|
1404
1557
|
output({ removed: true, linkId: id });
|
|
@@ -1440,19 +1593,17 @@ function buildSetValue(parsed, wrap) {
|
|
|
1440
1593
|
if (parsed.mode !== "set") return void 0;
|
|
1441
1594
|
return parsed.values.map(wrap);
|
|
1442
1595
|
}
|
|
1443
|
-
function
|
|
1444
|
-
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")')
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
jiradc issue update PROJ-123 --attachments /path/to/file.pdf,/path/to/image.png`
|
|
1455
|
-
).action(async (key, opts) => {
|
|
1596
|
+
function update3(parent) {
|
|
1597
|
+
const cmd = 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")');
|
|
1598
|
+
examples(cmd, [
|
|
1599
|
+
'PROJ-123 --summary "New title"',
|
|
1600
|
+
"PROJ-123 --priority High --assignee me",
|
|
1601
|
+
"PROJ-123 --labels backend,urgent",
|
|
1602
|
+
"PROJ-123 --labels +urgent,-backend",
|
|
1603
|
+
"PROJ-123 --components +Frontend",
|
|
1604
|
+
"PROJ-123 --attachments /path/to/file.pdf,/path/to/image.png"
|
|
1605
|
+
]);
|
|
1606
|
+
cmd.action(async (key, opts) => {
|
|
1456
1607
|
const hasShortcut = opts.summary !== void 0 || opts.description !== void 0 || opts.priority !== void 0 || opts.assignee !== void 0 || opts.labels !== void 0 || opts.components !== void 0 || opts.fixVersions !== void 0;
|
|
1457
1608
|
if (!opts.fields && !opts.attachments && !hasShortcut) {
|
|
1458
1609
|
throw new Error(
|
|
@@ -1521,12 +1672,15 @@ Examples:
|
|
|
1521
1672
|
});
|
|
1522
1673
|
}
|
|
1523
1674
|
|
|
1524
|
-
// src/commands/issue/worklog.ts
|
|
1525
|
-
function
|
|
1526
|
-
parent.command("
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1675
|
+
// src/commands/issue/worklog/create.ts
|
|
1676
|
+
function create4(parent) {
|
|
1677
|
+
const cmd = 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");
|
|
1678
|
+
examples(cmd, [
|
|
1679
|
+
"PROJ-123 --time 2h",
|
|
1680
|
+
'PROJ-123 --time "1d 4h" --comment "Backend implementation"',
|
|
1681
|
+
'PROJ-123 --time 3h --started "2026-03-19T09:00:00.000+0000"'
|
|
1682
|
+
]);
|
|
1683
|
+
cmd.action(async (key, opts) => {
|
|
1530
1684
|
const client = getClient();
|
|
1531
1685
|
const result = await client.issues.addWorklog({
|
|
1532
1686
|
issueKeyOrId: key,
|
|
@@ -1538,32 +1692,116 @@ function worklog(parent) {
|
|
|
1538
1692
|
});
|
|
1539
1693
|
}
|
|
1540
1694
|
|
|
1541
|
-
// src/commands/issue/
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
$ jiradc issue update PROJ-123 --fields '{"summary": "New title"}'
|
|
1551
|
-
$ jiradc issue transition PROJ-123 --to 11
|
|
1552
|
-
$ jiradc issue attachment list PROJ-123
|
|
1553
|
-
`
|
|
1695
|
+
// src/commands/issue/worklog/delete.ts
|
|
1696
|
+
import { Option as Option4 } from "commander";
|
|
1697
|
+
var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
|
|
1698
|
+
function deleteWorklog(parent) {
|
|
1699
|
+
const cmd = parent.command("delete <key>").description("Delete a worklog entry");
|
|
1700
|
+
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
1701
|
+
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(
|
|
1702
|
+
"--increase-by <amount>",
|
|
1703
|
+
'Amount to increase the estimate by; required when --adjust-estimate is "manual"'
|
|
1554
1704
|
);
|
|
1705
|
+
examples(cmd, [
|
|
1706
|
+
"PROJ-123 --worklog-id 12345",
|
|
1707
|
+
"PROJ-123 --worklog-id 12345 --adjust-estimate leave",
|
|
1708
|
+
"PROJ-123 --worklog-id 12345 --adjust-estimate new --new-estimate 2h"
|
|
1709
|
+
]);
|
|
1710
|
+
cmd.action(
|
|
1711
|
+
async (key, opts) => {
|
|
1712
|
+
const client = getClient();
|
|
1713
|
+
await client.issues.deleteWorklog({
|
|
1714
|
+
issueKeyOrId: key,
|
|
1715
|
+
worklogId: String(opts.worklogId),
|
|
1716
|
+
adjustEstimate: opts.adjustEstimate,
|
|
1717
|
+
newEstimate: opts.newEstimate,
|
|
1718
|
+
increaseBy: opts.increaseBy
|
|
1719
|
+
});
|
|
1720
|
+
output({ deleted: true, issueKey: key, worklogId: opts.worklogId });
|
|
1721
|
+
}
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// src/commands/issue/worklog/list.ts
|
|
1726
|
+
function list4(parent) {
|
|
1727
|
+
const cmd = 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);
|
|
1728
|
+
examples(cmd, ["PROJ-123", "PROJ-123 --limit 10", "PROJ-123 --start 10 --limit 5"]);
|
|
1729
|
+
cmd.action(async (key, opts) => {
|
|
1730
|
+
const client = getClient();
|
|
1731
|
+
const result = await client.issues.getWorklogs({
|
|
1732
|
+
issueKeyOrId: key,
|
|
1733
|
+
startAt: opts.start,
|
|
1734
|
+
maxResults: opts.limit
|
|
1735
|
+
});
|
|
1736
|
+
output(transformPaged(result, transformWorklog));
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// src/commands/issue/worklog/update.ts
|
|
1741
|
+
import { Option as Option5 } from "commander";
|
|
1742
|
+
var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
|
|
1743
|
+
function update4(parent) {
|
|
1744
|
+
const cmd = parent.command("update <key>").description("Update an existing worklog entry");
|
|
1745
|
+
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
1746
|
+
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"');
|
|
1747
|
+
examples(cmd, [
|
|
1748
|
+
'PROJ-123 --worklog-id 12345 --time "1h 30m"',
|
|
1749
|
+
'PROJ-123 --worklog-id 12345 --comment "Revised note"',
|
|
1750
|
+
"PROJ-123 --worklog-id 12345 --time 2h --adjust-estimate new --new-estimate 4h"
|
|
1751
|
+
]);
|
|
1752
|
+
cmd.action(
|
|
1753
|
+
async (key, opts) => {
|
|
1754
|
+
const client = getClient();
|
|
1755
|
+
const result = await client.issues.updateWorklog({
|
|
1756
|
+
issueKeyOrId: key,
|
|
1757
|
+
worklogId: String(opts.worklogId),
|
|
1758
|
+
timeSpent: opts.time,
|
|
1759
|
+
comment: opts.comment,
|
|
1760
|
+
started: opts.started,
|
|
1761
|
+
adjustEstimate: opts.adjustEstimate,
|
|
1762
|
+
newEstimate: opts.newEstimate
|
|
1763
|
+
});
|
|
1764
|
+
output(transformWorklog(result));
|
|
1765
|
+
}
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/commands/issue/worklog/index.ts
|
|
1770
|
+
function registerWorklogCommands(parent) {
|
|
1771
|
+
const worklog = parent.command("worklog").description("Worklog operations");
|
|
1772
|
+
examples(worklog, [
|
|
1773
|
+
'create PROJ-123 --time 2h --comment "Backend work"',
|
|
1774
|
+
"list PROJ-123 --limit 10",
|
|
1775
|
+
'update PROJ-123 --worklog-id 12345 --time "1h 30m"',
|
|
1776
|
+
"delete PROJ-123 --worklog-id 12345"
|
|
1777
|
+
]);
|
|
1778
|
+
create4(worklog);
|
|
1779
|
+
list4(worklog);
|
|
1780
|
+
update4(worklog);
|
|
1781
|
+
deleteWorklog(worklog);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// src/commands/issue/index.ts
|
|
1785
|
+
function registerIssueCommands(program) {
|
|
1786
|
+
const issue = program.command("issue").description("Issue operations");
|
|
1787
|
+
examples(issue, [
|
|
1788
|
+
"get PROJ-123",
|
|
1789
|
+
'search "project = PROJ AND status = Open" --limit 10',
|
|
1790
|
+
'create --project PROJ --type Task --summary "Fix the bug" --priority High',
|
|
1791
|
+
`update PROJ-123 --fields '{"summary": "New title"}'`,
|
|
1792
|
+
"transition PROJ-123 --to 11",
|
|
1793
|
+
"attachment list PROJ-123"
|
|
1794
|
+
]);
|
|
1555
1795
|
get2(issue);
|
|
1556
1796
|
search2(issue);
|
|
1557
|
-
|
|
1558
|
-
|
|
1797
|
+
create3(issue);
|
|
1798
|
+
update3(issue);
|
|
1559
1799
|
deleteIssue(issue);
|
|
1560
1800
|
transition(issue);
|
|
1561
1801
|
transitions(issue);
|
|
1562
1802
|
assign(issue);
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
worklog(issue);
|
|
1566
|
-
getWorklog(issue);
|
|
1803
|
+
registerCommentCommands(issue);
|
|
1804
|
+
registerWorklogCommands(issue);
|
|
1567
1805
|
changelog(issue);
|
|
1568
1806
|
batchChangelog(issue);
|
|
1569
1807
|
link(issue);
|
|
@@ -1571,18 +1809,16 @@ Examples:
|
|
|
1571
1809
|
linkTypes(issue);
|
|
1572
1810
|
linkEpic(issue);
|
|
1573
1811
|
registerAttachmentCommands(issue);
|
|
1574
|
-
attachments(issue);
|
|
1575
1812
|
batchCreate(issue);
|
|
1576
1813
|
clone(issue);
|
|
1577
1814
|
devStatus(issue);
|
|
1578
1815
|
}
|
|
1579
1816
|
|
|
1580
1817
|
// src/commands/project/list.ts
|
|
1581
|
-
function
|
|
1582
|
-
parent.command("list").description("List all projects").option("--expand <expand>", 'Expand options (e.g., "description,lead")').option("--include-archived", "Include archived projects (default: false)")
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
).action(async (opts) => {
|
|
1818
|
+
function list5(parent) {
|
|
1819
|
+
const cmd = parent.command("list").description("List all projects").option("--expand <expand>", 'Expand options (e.g., "description,lead")').option("--include-archived", "Include archived projects (default: false)");
|
|
1820
|
+
examples(cmd, ["", "--expand description,lead", "--include-archived"]);
|
|
1821
|
+
cmd.action(async (opts) => {
|
|
1586
1822
|
const client = getClient();
|
|
1587
1823
|
const result = await client.projects.getAll({ expand: opts.expand, includeArchived: opts.includeArchived });
|
|
1588
1824
|
output(result.map(transformProject));
|
|
@@ -1591,10 +1827,9 @@ function list4(parent) {
|
|
|
1591
1827
|
|
|
1592
1828
|
// src/commands/project/versions.ts
|
|
1593
1829
|
function versions(parent) {
|
|
1594
|
-
parent.command("versions <key>").description("Get all versions for a project").option("--expand <expand>", "Expand options")
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
).action(async (key, opts) => {
|
|
1830
|
+
const cmd = parent.command("versions <key>").description("Get all versions for a project").option("--expand <expand>", "Expand options");
|
|
1831
|
+
examples(cmd, ["PROJ", "PROJ --expand operations"]);
|
|
1832
|
+
cmd.action(async (key, opts) => {
|
|
1598
1833
|
const client = getClient();
|
|
1599
1834
|
const result = await client.projects.getVersions({ projectKeyOrId: key, expand: opts.expand });
|
|
1600
1835
|
output(result.map(transformVersion));
|
|
@@ -1602,25 +1837,21 @@ function versions(parent) {
|
|
|
1602
1837
|
}
|
|
1603
1838
|
|
|
1604
1839
|
// src/commands/project/index.ts
|
|
1605
|
-
function registerProjectCommands(
|
|
1606
|
-
const project =
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
Examples:
|
|
1610
|
-
$ jiradc project list
|
|
1611
|
-
$ jiradc project versions PROJ
|
|
1612
|
-
`
|
|
1613
|
-
);
|
|
1614
|
-
list4(project);
|
|
1840
|
+
function registerProjectCommands(program) {
|
|
1841
|
+
const project = program.command("project").description("Project operations");
|
|
1842
|
+
examples(project, ["list", "versions PROJ"]);
|
|
1843
|
+
list5(project);
|
|
1615
1844
|
versions(project);
|
|
1616
1845
|
}
|
|
1617
1846
|
|
|
1618
1847
|
// src/commands/sprint/create.ts
|
|
1619
|
-
function
|
|
1620
|
-
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")
|
|
1621
|
-
|
|
1622
|
-
'
|
|
1623
|
-
|
|
1848
|
+
function create5(parent) {
|
|
1849
|
+
const cmd = 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");
|
|
1850
|
+
examples(cmd, [
|
|
1851
|
+
'--board 42 --name "Sprint 10"',
|
|
1852
|
+
'--board 42 --name "Sprint 10" --start-date 2026-03-20 --end-date 2026-04-03 --goal "Complete auth module"'
|
|
1853
|
+
]);
|
|
1854
|
+
cmd.action(async (opts) => {
|
|
1624
1855
|
const client = getClient();
|
|
1625
1856
|
const result = await client.agile.createSprint({
|
|
1626
1857
|
name: opts.name,
|
|
@@ -1633,13 +1864,29 @@ function create3(parent) {
|
|
|
1633
1864
|
});
|
|
1634
1865
|
}
|
|
1635
1866
|
|
|
1636
|
-
// src/commands/sprint/
|
|
1867
|
+
// src/commands/sprint/delete.ts
|
|
1637
1868
|
import { Argument as Argument2 } from "commander";
|
|
1869
|
+
function deleteSprint(parent) {
|
|
1870
|
+
const cmd = parent.command("delete").description("Delete a sprint (returns its issues to the backlog)").addArgument(new Argument2("<id>", "Sprint ID").argParser(positiveInt));
|
|
1871
|
+
examples(cmd, ["100"]);
|
|
1872
|
+
cmd.action(async (id) => {
|
|
1873
|
+
const client = getClient();
|
|
1874
|
+
await client.agile.deleteSprint({ sprintId: id });
|
|
1875
|
+
output({ deleted: true, sprintId: id });
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/commands/sprint/issues.ts
|
|
1880
|
+
import { Argument as Argument3 } from "commander";
|
|
1638
1881
|
function issues2(parent) {
|
|
1639
|
-
parent.command("issues").description("Get issues in a sprint").addArgument(new
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1882
|
+
const cmd = parent.command("issues").description("Get issues in a sprint").addArgument(new Argument3("<id>", "Sprint ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the sprint");
|
|
1883
|
+
examples(cmd, [
|
|
1884
|
+
"100",
|
|
1885
|
+
"100 --limit 20",
|
|
1886
|
+
'100 --jql "status = Done" --fields summary,status',
|
|
1887
|
+
"100 --start 50 --limit 25"
|
|
1888
|
+
]);
|
|
1889
|
+
cmd.action(async (id, opts) => {
|
|
1643
1890
|
const client = getClient();
|
|
1644
1891
|
const result = await client.agile.getSprintIssues({
|
|
1645
1892
|
sprintId: id,
|
|
@@ -1653,13 +1900,12 @@ function issues2(parent) {
|
|
|
1653
1900
|
}
|
|
1654
1901
|
|
|
1655
1902
|
// src/commands/sprint/list.ts
|
|
1656
|
-
import { Option as
|
|
1903
|
+
import { Option as Option6 } from "commander";
|
|
1657
1904
|
var SPRINT_STATES = ["future", "active", "closed"];
|
|
1658
|
-
function
|
|
1659
|
-
parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
).action(async (opts) => {
|
|
1905
|
+
function list6(parent) {
|
|
1906
|
+
const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option6("--state <state>", "Filter by sprint state").choices(SPRINT_STATES));
|
|
1907
|
+
examples(cmd, ["--board 42", "--board 42 --state active"]);
|
|
1908
|
+
cmd.action(async (opts) => {
|
|
1663
1909
|
const client = getClient();
|
|
1664
1910
|
const result = await client.agile.getSprints({
|
|
1665
1911
|
boardId: opts.board,
|
|
@@ -1670,13 +1916,16 @@ function list5(parent) {
|
|
|
1670
1916
|
}
|
|
1671
1917
|
|
|
1672
1918
|
// src/commands/sprint/update.ts
|
|
1673
|
-
import { Argument as
|
|
1919
|
+
import { Argument as Argument4, Option as Option7 } from "commander";
|
|
1674
1920
|
var SPRINT_STATES2 = ["future", "active", "closed"];
|
|
1675
|
-
function
|
|
1676
|
-
parent.command("update").description("Update an existing sprint").addArgument(new
|
|
1677
|
-
|
|
1678
|
-
'
|
|
1679
|
-
|
|
1921
|
+
function update5(parent) {
|
|
1922
|
+
const cmd = 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");
|
|
1923
|
+
examples(cmd, [
|
|
1924
|
+
'100 --name "Sprint 10 - Extended"',
|
|
1925
|
+
"100 --state active",
|
|
1926
|
+
'100 --end-date 2026-04-10 --goal "Updated goal"'
|
|
1927
|
+
]);
|
|
1928
|
+
cmd.action(
|
|
1680
1929
|
async (id, opts) => {
|
|
1681
1930
|
const client = getClient();
|
|
1682
1931
|
const result = await client.agile.updateSprint({
|
|
@@ -1693,21 +1942,19 @@ function update3(parent) {
|
|
|
1693
1942
|
}
|
|
1694
1943
|
|
|
1695
1944
|
// src/commands/sprint/index.ts
|
|
1696
|
-
function registerSprintCommands(
|
|
1697
|
-
const sprint =
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
`
|
|
1706
|
-
);
|
|
1707
|
-
list5(sprint);
|
|
1945
|
+
function registerSprintCommands(program) {
|
|
1946
|
+
const sprint = program.command("sprint").description("Sprint operations");
|
|
1947
|
+
examples(sprint, [
|
|
1948
|
+
"list --board 42",
|
|
1949
|
+
"list --board 42 --state active",
|
|
1950
|
+
"issues 123 --limit 20",
|
|
1951
|
+
'create --board 42 --name "Sprint 10" --start-date 2026-04-01 --end-date 2026-04-14'
|
|
1952
|
+
]);
|
|
1953
|
+
list6(sprint);
|
|
1708
1954
|
issues2(sprint);
|
|
1709
|
-
|
|
1710
|
-
|
|
1955
|
+
create5(sprint);
|
|
1956
|
+
update5(sprint);
|
|
1957
|
+
deleteSprint(sprint);
|
|
1711
1958
|
}
|
|
1712
1959
|
|
|
1713
1960
|
// src/commands/token/client.ts
|
|
@@ -1722,36 +1969,24 @@ function getTokenClient(options2 = {}) {
|
|
|
1722
1969
|
...!username ? ["JIRA_BASIC_USERNAME (or --basic-username)"] : [],
|
|
1723
1970
|
...!password ? ["JIRA_BASIC_PASSWORD (or --basic-password)"] : []
|
|
1724
1971
|
];
|
|
1725
|
-
|
|
1726
|
-
`${JSON.stringify({
|
|
1727
|
-
error: `Missing required credentials: ${missing.join(", ")}`,
|
|
1728
|
-
hint: "Set JIRA_BASIC_USERNAME and JIRA_BASIC_PASSWORD in your shell profile, or pass --basic-username / --basic-password."
|
|
1729
|
-
})}
|
|
1730
|
-
`
|
|
1731
|
-
);
|
|
1732
|
-
process.exit(1);
|
|
1972
|
+
throw new CliAuthError(`Missing required credentials: ${missing.join(", ")}`);
|
|
1733
1973
|
}
|
|
1734
1974
|
const client = new JiraClient2({ baseUrl });
|
|
1735
1975
|
return { client, username, password };
|
|
1736
1976
|
}
|
|
1737
1977
|
|
|
1738
1978
|
// src/commands/token/create.ts
|
|
1739
|
-
function
|
|
1740
|
-
parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name").option(
|
|
1979
|
+
function create6(parent) {
|
|
1980
|
+
const cmd = parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name").option(
|
|
1741
1981
|
"--expiration-duration <days>",
|
|
1742
1982
|
"Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
|
|
1743
1983
|
positiveInt
|
|
1744
1984
|
).option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
|
|
1745
1985
|
"--basic-password <p>",
|
|
1746
1986
|
"Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
|
|
1747
|
-
)
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
Examples:
|
|
1751
|
-
$ jiradc token create --name my-pat # non-expiring
|
|
1752
|
-
$ jiradc token create --name svc-token --expiration-duration 365
|
|
1753
|
-
`
|
|
1754
|
-
).action(async (opts) => {
|
|
1987
|
+
);
|
|
1988
|
+
examples(cmd, [["--name my-pat", "non-expiring"], "--name svc-token --expiration-duration 365"]);
|
|
1989
|
+
cmd.action(async (opts) => {
|
|
1755
1990
|
const { client, username, password } = getTokenClient({
|
|
1756
1991
|
basicUsername: opts.basicUsername,
|
|
1757
1992
|
basicPassword: opts.basicPassword
|
|
@@ -1767,11 +2002,13 @@ Examples:
|
|
|
1767
2002
|
}
|
|
1768
2003
|
|
|
1769
2004
|
// src/commands/token/list.ts
|
|
1770
|
-
function
|
|
1771
|
-
parent.command("list").description("List Personal Access Tokens owned by the authenticated user (secrets not included)").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
|
|
2005
|
+
function list7(parent) {
|
|
2006
|
+
const cmd = parent.command("list").description("List Personal Access Tokens owned by the authenticated user (secrets not included)").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
|
|
1772
2007
|
"--basic-password <p>",
|
|
1773
2008
|
"Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
|
|
1774
|
-
)
|
|
2009
|
+
);
|
|
2010
|
+
examples(cmd, [""]);
|
|
2011
|
+
cmd.action(async (opts) => {
|
|
1775
2012
|
const { client, username, password } = getTokenClient({
|
|
1776
2013
|
basicUsername: opts.basicUsername,
|
|
1777
2014
|
basicPassword: opts.basicPassword
|
|
@@ -1782,42 +2019,40 @@ function list6(parent) {
|
|
|
1782
2019
|
|
|
1783
2020
|
// src/commands/token/revoke.ts
|
|
1784
2021
|
function revoke(parent) {
|
|
1785
|
-
parent.command("revoke").description("Revoke a Personal Access Token by id").
|
|
2022
|
+
const cmd = parent.command("revoke").description("Revoke a Personal Access Token by id").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
|
|
1786
2023
|
"--basic-password <p>",
|
|
1787
2024
|
"Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
|
|
1788
|
-
)
|
|
2025
|
+
);
|
|
2026
|
+
subjectArg(cmd, "tokenId", { parser: positiveInt, description: "Token id to revoke" });
|
|
2027
|
+
examples(cmd, ["173"]);
|
|
2028
|
+
cmd.action(async (tokenId, opts) => {
|
|
1789
2029
|
const { client, username, password } = getTokenClient({
|
|
1790
2030
|
basicUsername: opts.basicUsername,
|
|
1791
2031
|
basicPassword: opts.basicPassword
|
|
1792
2032
|
});
|
|
1793
|
-
await client.accessTokens.revoke({ username, password, tokenId
|
|
1794
|
-
output({ revoked:
|
|
2033
|
+
await client.accessTokens.revoke({ username, password, tokenId });
|
|
2034
|
+
output({ revoked: tokenId });
|
|
1795
2035
|
});
|
|
1796
2036
|
}
|
|
1797
2037
|
|
|
1798
2038
|
// src/commands/token/index.ts
|
|
1799
|
-
function registerTokenCommands(
|
|
1800
|
-
const token =
|
|
2039
|
+
function registerTokenCommands(program) {
|
|
2040
|
+
const token = program.command("token").description("Personal Access Token management");
|
|
2041
|
+
examples(token, ["list", "create --name my-pat", "revoke 173"]);
|
|
2042
|
+
token.addHelpText(
|
|
1801
2043
|
"after",
|
|
1802
|
-
|
|
1803
|
-
Examples:
|
|
1804
|
-
$ jiradc token list
|
|
1805
|
-
$ jiradc token create --name my-pat
|
|
1806
|
-
$ jiradc token revoke --id 173
|
|
1807
|
-
|
|
1808
|
-
Auth:
|
|
1809
|
-
Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD
|
|
1810
|
-
(or --basic-username / --basic-password on any subcommand).
|
|
1811
|
-
`
|
|
2044
|
+
"\nAuth:\n Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD\n (or --basic-username / --basic-password on any subcommand)."
|
|
1812
2045
|
);
|
|
1813
|
-
|
|
1814
|
-
|
|
2046
|
+
create6(token);
|
|
2047
|
+
list7(token);
|
|
1815
2048
|
revoke(token);
|
|
1816
2049
|
}
|
|
1817
2050
|
|
|
1818
2051
|
// src/commands/user/get.ts
|
|
1819
2052
|
function get3(parent) {
|
|
1820
|
-
parent.command("get <username>").description("Get a user profile by exact username or key").option("--by-key", "Treat the positional argument as a user key instead of a username")
|
|
2053
|
+
const cmd = parent.command("get <username>").description("Get a user profile by exact username or key").option("--by-key", "Treat the positional argument as a user key instead of a username");
|
|
2054
|
+
examples(cmd, ["jsmith", "JIRAUSER10100 --by-key"]);
|
|
2055
|
+
cmd.action(async (identifier, opts) => {
|
|
1821
2056
|
const client = getClient();
|
|
1822
2057
|
const result = await client.users.getUser(opts.byKey ? { key: identifier } : { username: identifier });
|
|
1823
2058
|
output(transformUser(result));
|
|
@@ -1826,7 +2061,9 @@ function get3(parent) {
|
|
|
1826
2061
|
|
|
1827
2062
|
// src/commands/user/me.ts
|
|
1828
2063
|
function me(parent) {
|
|
1829
|
-
parent.command("me").description("Get the authenticated user profile")
|
|
2064
|
+
const cmd = parent.command("me").description("Get the authenticated user profile");
|
|
2065
|
+
examples(cmd, [""]);
|
|
2066
|
+
cmd.action(async () => {
|
|
1830
2067
|
const client = getClient();
|
|
1831
2068
|
const result = await client.users.getMyself();
|
|
1832
2069
|
output(transformUser(result));
|
|
@@ -1835,10 +2072,9 @@ function me(parent) {
|
|
|
1835
2072
|
|
|
1836
2073
|
// src/commands/user/search.ts
|
|
1837
2074
|
function search3(parent) {
|
|
1838
|
-
parent.command("search <query>").description("Search users by partial username, display name or email").option("--limit <n>", "Maximum results (1-50)", intInRange(1, 50), 25).option("--start <n>", "Starting index (pagination offset)", nonNegativeInt, 0).option("--include-inactive", "Include inactive users in results", false)
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
).action(async (query, opts) => {
|
|
2075
|
+
const cmd = parent.command("search <query>").description("Search users by partial username, display name or email").option("--limit <n>", "Maximum results (1-50)", intInRange(1, 50), 25).option("--start <n>", "Starting index (pagination offset)", nonNegativeInt, 0).option("--include-inactive", "Include inactive users in results", false);
|
|
2076
|
+
examples(cmd, ["Smith", '"John Smith"', "jsmith@example.com --limit 5"]);
|
|
2077
|
+
cmd.action(async (query, opts) => {
|
|
1842
2078
|
const client = getClient();
|
|
1843
2079
|
const result = await client.users.searchUsers({
|
|
1844
2080
|
username: query,
|
|
@@ -1852,46 +2088,30 @@ function search3(parent) {
|
|
|
1852
2088
|
}
|
|
1853
2089
|
|
|
1854
2090
|
// src/commands/user/index.ts
|
|
1855
|
-
function registerUserCommands(
|
|
1856
|
-
const user =
|
|
1857
|
-
|
|
1858
|
-
`
|
|
1859
|
-
Examples:
|
|
1860
|
-
$ jiradc user me
|
|
1861
|
-
$ jiradc user get jsmith
|
|
1862
|
-
$ jiradc user search Drago
|
|
1863
|
-
`
|
|
1864
|
-
);
|
|
2091
|
+
function registerUserCommands(program) {
|
|
2092
|
+
const user = program.command("user").description("User operations");
|
|
2093
|
+
examples(user, ["me", "get jsmith", "search Smith"]);
|
|
1865
2094
|
me(user);
|
|
1866
2095
|
get3(user);
|
|
1867
2096
|
search3(user);
|
|
1868
2097
|
}
|
|
1869
2098
|
|
|
1870
|
-
// src/
|
|
1871
|
-
function readPackageVersion() {
|
|
1872
|
-
try {
|
|
1873
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
1874
|
-
const pkgPath = join4(here, "..", "package.json");
|
|
1875
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1876
|
-
return pkg.version ?? "0.0.0";
|
|
1877
|
-
} catch {
|
|
1878
|
-
return "0.0.0";
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
2099
|
+
// src/program.ts
|
|
1881
2100
|
var DIM = "\x1B[2m";
|
|
1882
2101
|
var RESET = "\x1B[0m";
|
|
1883
|
-
|
|
1884
|
-
program
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2102
|
+
function buildProgram() {
|
|
2103
|
+
const program = new Command11();
|
|
2104
|
+
program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
|
|
2105
|
+
styleTitle: (str) => styleText("bold", str),
|
|
2106
|
+
styleUsage: (str) => styleText("dim", str),
|
|
2107
|
+
styleCommandDescription: (str) => styleText("dim", str),
|
|
2108
|
+
styleOptionDescription: (str) => styleText("dim", str),
|
|
2109
|
+
styleSubcommandDescription: (str) => styleText("dim", str)
|
|
2110
|
+
}).addHelpText("beforeAll", `
|
|
1891
2111
|
${styleText("bold", "jiradc")} ${DIM}\u2014 Jira Data Center CLI${RESET}
|
|
1892
2112
|
`).addHelpText(
|
|
1893
|
-
|
|
1894
|
-
|
|
2113
|
+
"after",
|
|
2114
|
+
`
|
|
1895
2115
|
${styleText("bold", "Environment:")}
|
|
1896
2116
|
JIRA_URL Jira Server base URL ${DIM}(e.g., https://jira.example.com)${RESET}
|
|
1897
2117
|
JIRA_TOKEN Personal Access Token ${DIM}(generate in Jira > Profile > Personal Access Tokens)${RESET}
|
|
@@ -1905,21 +2125,45 @@ ${styleText("bold", "Examples:")}
|
|
|
1905
2125
|
${DIM}$${RESET} jiradc board list --type scrum
|
|
1906
2126
|
${DIM}$${RESET} jiradc sprint list --board 42 --state active
|
|
1907
2127
|
`
|
|
1908
|
-
);
|
|
1909
|
-
program.option("--pretty", "Pretty-print JSON output");
|
|
1910
|
-
program.hook("preAction", (thisCommand) => {
|
|
1911
|
-
|
|
1912
|
-
});
|
|
1913
|
-
registerIssueCommands(program);
|
|
1914
|
-
registerProjectCommands(program);
|
|
1915
|
-
registerComponentCommands(program);
|
|
1916
|
-
registerBoardCommands(program);
|
|
1917
|
-
registerSprintCommands(program);
|
|
1918
|
-
registerFieldCommands(program);
|
|
1919
|
-
registerUserCommands(program);
|
|
1920
|
-
registerTokenCommands(program);
|
|
1921
|
-
|
|
1922
|
-
await program.parseAsync();
|
|
1923
|
-
} catch (err) {
|
|
1924
|
-
handleError(err);
|
|
2128
|
+
);
|
|
2129
|
+
program.option("--pretty", "Pretty-print JSON output");
|
|
2130
|
+
program.hook("preAction", (thisCommand) => {
|
|
2131
|
+
if (thisCommand.optsWithGlobals().pretty) setPretty(true);
|
|
2132
|
+
});
|
|
2133
|
+
registerIssueCommands(program);
|
|
2134
|
+
registerProjectCommands(program);
|
|
2135
|
+
registerComponentCommands(program);
|
|
2136
|
+
registerBoardCommands(program);
|
|
2137
|
+
registerSprintCommands(program);
|
|
2138
|
+
registerFieldCommands(program);
|
|
2139
|
+
registerUserCommands(program);
|
|
2140
|
+
registerTokenCommands(program);
|
|
2141
|
+
return program;
|
|
1925
2142
|
}
|
|
2143
|
+
|
|
2144
|
+
// src/utils/credentials.ts
|
|
2145
|
+
function getCredentialInfo() {
|
|
2146
|
+
const baseUrl = process.env.JIRA_URL;
|
|
2147
|
+
const token = process.env.JIRA_TOKEN;
|
|
2148
|
+
return {
|
|
2149
|
+
environment: {
|
|
2150
|
+
JIRA_URL: {
|
|
2151
|
+
value: baseUrl ?? null,
|
|
2152
|
+
description: "Your Jira Server base URL (e.g., https://jira.example.com)"
|
|
2153
|
+
},
|
|
2154
|
+
JIRA_TOKEN: {
|
|
2155
|
+
value: token ? "<set>" : null,
|
|
2156
|
+
description: "Personal Access Token"
|
|
2157
|
+
}
|
|
2158
|
+
},
|
|
2159
|
+
tokenUrl: `${baseUrl ?? "https://jira.example.com"}/secure/ViewProfile.jspa`
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// src/index.ts
|
|
2164
|
+
await runCli(buildProgram(), {
|
|
2165
|
+
credentialInfo: getCredentialInfo,
|
|
2166
|
+
service: "Jira",
|
|
2167
|
+
authRecovery: "Set the JIRA_URL and JIRA_TOKEN environment variables.",
|
|
2168
|
+
networkRecovery: "Verify JIRA_URL is correct, the server is reachable, and you are on the VPN if required."
|
|
2169
|
+
});
|