jiradc-cli 1.0.21 → 2.0.0-g9960cad.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +443 -281
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import { readFileSync } from "fs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { styleText } from "util";
|
|
8
|
-
import { Command as Command11 } from "commander";
|
|
3
|
+
// ../../cli-utils/dist/cache.js
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
9
7
|
|
|
10
|
-
//
|
|
11
|
-
import {
|
|
8
|
+
// ../../cli-utils/dist/bootstrap.js
|
|
9
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
10
|
+
import { dirname, join as join2 } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
function readPackageVersion(importMetaUrl) {
|
|
13
|
+
try {
|
|
14
|
+
const here = dirname(fileURLToPath(importMetaUrl));
|
|
15
|
+
const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf-8"));
|
|
16
|
+
return pkg.version ?? "0.0.0";
|
|
17
|
+
} catch {
|
|
18
|
+
return "0.0.0";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
12
21
|
|
|
13
|
-
//
|
|
22
|
+
// ../../cli-utils/dist/validators.js
|
|
14
23
|
import { InvalidArgumentError } from "commander";
|
|
15
24
|
function intInRange(min, max) {
|
|
16
25
|
return (raw) => {
|
|
@@ -39,43 +48,256 @@ function positiveInt(raw) {
|
|
|
39
48
|
return n;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
// ../../cli-utils/dist/registry.js
|
|
52
|
+
var SUB_ENTITY_ID_NUMERIC = {
|
|
53
|
+
comment: true,
|
|
54
|
+
worklog: true,
|
|
55
|
+
attachment: true,
|
|
56
|
+
request: true,
|
|
57
|
+
type: true,
|
|
58
|
+
employee: true,
|
|
59
|
+
message: false,
|
|
60
|
+
membership: false
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ../../cli-utils/dist/builders.js
|
|
64
|
+
function defineOption(cmd, flags, description, parser, mandatory) {
|
|
65
|
+
if (mandatory) {
|
|
66
|
+
return parser ? cmd.requiredOption(flags, description, parser) : cmd.requiredOption(flags, description);
|
|
67
|
+
}
|
|
68
|
+
return parser ? cmd.option(flags, description, parser) : cmd.option(flags, description);
|
|
69
|
+
}
|
|
70
|
+
function subjectArg(cmd, name, opts = {}) {
|
|
71
|
+
const inner = opts.variadic ? `${name}...` : name;
|
|
72
|
+
const token = opts.optional ? `[${inner}]` : `<${inner}>`;
|
|
73
|
+
return opts.parser ? cmd.argument(token, opts.description ?? "", opts.parser) : cmd.argument(token, opts.description ?? "");
|
|
74
|
+
}
|
|
75
|
+
function subEntityOption(cmd, entity, opts = {}) {
|
|
76
|
+
const numeric = opts.numeric ?? SUB_ENTITY_ID_NUMERIC[entity] ?? true;
|
|
77
|
+
const description = `${entity.charAt(0).toUpperCase()}${entity.slice(1)} id`;
|
|
78
|
+
return defineOption(cmd, `--${entity}-id <id>`, description, numeric ? positiveInt : void 0, opts.mandatory);
|
|
79
|
+
}
|
|
80
|
+
function bodyOption(cmd, opts = {}) {
|
|
81
|
+
return defineOption(cmd, "--body <text>", "Prose body content", void 0, opts.mandatory);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ../../cli-utils/dist/errors.js
|
|
85
|
+
import { CommanderError } from "commander";
|
|
86
|
+
var EXIT = {
|
|
87
|
+
SUCCESS: 0,
|
|
88
|
+
GENERIC: 1,
|
|
89
|
+
USAGE: 2,
|
|
90
|
+
NOT_FOUND: 3,
|
|
91
|
+
FORBIDDEN: 4,
|
|
92
|
+
CONFLICT: 5,
|
|
93
|
+
AUTH: 6
|
|
94
|
+
};
|
|
95
|
+
var CliAuthError = class extends Error {
|
|
96
|
+
constructor(message) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.name = "CliAuthError";
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var TYPE_EXIT = {
|
|
102
|
+
usage: EXIT.USAGE,
|
|
103
|
+
not_found: EXIT.NOT_FOUND,
|
|
104
|
+
forbidden: EXIT.FORBIDDEN,
|
|
105
|
+
conflict: EXIT.CONFLICT,
|
|
106
|
+
auth: EXIT.AUTH,
|
|
107
|
+
rate_limited: EXIT.GENERIC,
|
|
108
|
+
server: EXIT.GENERIC,
|
|
109
|
+
network: EXIT.GENERIC,
|
|
110
|
+
unknown: EXIT.GENERIC
|
|
111
|
+
};
|
|
112
|
+
var NETWORK_CODES = ["ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"];
|
|
113
|
+
function httpStatus(err) {
|
|
114
|
+
const e = err;
|
|
115
|
+
return e?.response?.status ?? e?.statusCode;
|
|
116
|
+
}
|
|
117
|
+
function responseDetail(err) {
|
|
118
|
+
const e = err;
|
|
119
|
+
const data = e?.response?.data;
|
|
120
|
+
if (data && typeof data === "object")
|
|
121
|
+
return data;
|
|
122
|
+
const body = e?.body;
|
|
123
|
+
if (typeof body === "string") {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(body);
|
|
126
|
+
return parsed.error ?? parsed;
|
|
127
|
+
} catch {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (body && typeof body === "object") {
|
|
132
|
+
return body.error ?? body;
|
|
133
|
+
}
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
function errorCode(err) {
|
|
137
|
+
return err?.code;
|
|
138
|
+
}
|
|
139
|
+
function normalize(err, opts) {
|
|
140
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
+
if (err instanceof CommanderError) {
|
|
142
|
+
return {
|
|
143
|
+
type: "usage",
|
|
144
|
+
message: message || "Invalid command usage",
|
|
145
|
+
recovery: "Check the command syntax and flags; run the command with --help.",
|
|
146
|
+
retryable: false
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (err instanceof CliAuthError) {
|
|
150
|
+
return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
|
|
151
|
+
}
|
|
152
|
+
const status = httpStatus(err);
|
|
153
|
+
const detail = responseDetail(err);
|
|
154
|
+
if (status !== void 0) {
|
|
155
|
+
switch (status) {
|
|
156
|
+
case 400:
|
|
157
|
+
return {
|
|
158
|
+
type: "usage",
|
|
159
|
+
status,
|
|
160
|
+
message: "Bad request (HTTP 400)",
|
|
161
|
+
recovery: "Check parameter values (ids, keys, query syntax) against the API.",
|
|
162
|
+
retryable: false,
|
|
163
|
+
detail
|
|
164
|
+
};
|
|
165
|
+
case 401:
|
|
166
|
+
return {
|
|
167
|
+
type: "auth",
|
|
168
|
+
status,
|
|
169
|
+
message: "Authentication failed (HTTP 401)",
|
|
170
|
+
recovery: opts.authRecovery,
|
|
171
|
+
retryable: false
|
|
172
|
+
};
|
|
173
|
+
case 403:
|
|
174
|
+
return {
|
|
175
|
+
type: "forbidden",
|
|
176
|
+
status,
|
|
177
|
+
message: "Forbidden (HTTP 403)",
|
|
178
|
+
recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
|
|
179
|
+
retryable: false,
|
|
180
|
+
detail
|
|
181
|
+
};
|
|
182
|
+
case 404:
|
|
183
|
+
return {
|
|
184
|
+
type: "not_found",
|
|
185
|
+
status,
|
|
186
|
+
message: "Not found (HTTP 404)",
|
|
187
|
+
recovery: "Verify the id/key exists and that you have access to it.",
|
|
188
|
+
retryable: false,
|
|
189
|
+
detail
|
|
190
|
+
};
|
|
191
|
+
case 409:
|
|
192
|
+
return {
|
|
193
|
+
type: "conflict",
|
|
194
|
+
status,
|
|
195
|
+
message: "Conflict (HTTP 409)",
|
|
196
|
+
recovery: "The resource changed or already exists; re-fetch current state and retry.",
|
|
197
|
+
retryable: false,
|
|
198
|
+
detail
|
|
199
|
+
};
|
|
200
|
+
case 429:
|
|
201
|
+
return {
|
|
202
|
+
type: "rate_limited",
|
|
203
|
+
status,
|
|
204
|
+
message: "Rate limited (HTTP 429)",
|
|
205
|
+
recovery: "Wait and retry the request.",
|
|
206
|
+
retryable: true
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (status >= 500) {
|
|
210
|
+
return {
|
|
211
|
+
type: "server",
|
|
212
|
+
status,
|
|
213
|
+
message: `Server error (HTTP ${status})`,
|
|
214
|
+
recovery: `${opts.service} returned an internal error; retry shortly.`,
|
|
215
|
+
retryable: true,
|
|
216
|
+
detail
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
type: "unknown",
|
|
221
|
+
status,
|
|
222
|
+
message: `${opts.service} error (HTTP ${status}): ${message}`,
|
|
223
|
+
recovery: "Inspect the detail field for the API response.",
|
|
224
|
+
retryable: false,
|
|
225
|
+
detail
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const code = errorCode(err);
|
|
229
|
+
if (code && NETWORK_CODES.includes(code) || NETWORK_CODES.some((c) => message.includes(c))) {
|
|
230
|
+
return {
|
|
231
|
+
type: "network",
|
|
232
|
+
message: `Cannot connect to ${opts.service}: ${message}`,
|
|
233
|
+
recovery: opts.networkRecovery ?? "Verify the *_URL is correct, the server is reachable, and you are on the VPN if required.",
|
|
234
|
+
retryable: true
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
|
|
238
|
+
}
|
|
239
|
+
function classifyError(err, opts) {
|
|
240
|
+
const base = normalize(err, opts);
|
|
241
|
+
const n = opts.adapt ? opts.adapt(base, err) : base;
|
|
242
|
+
if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
|
|
243
|
+
n.detail = opts.credentialInfo();
|
|
244
|
+
}
|
|
49
245
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
description: "Personal Access Token"
|
|
246
|
+
envelope: {
|
|
247
|
+
error: {
|
|
248
|
+
type: n.type,
|
|
249
|
+
message: n.message,
|
|
250
|
+
recovery: n.recovery,
|
|
251
|
+
retryable: n.retryable,
|
|
252
|
+
...n.detail !== void 0 ? { detail: n.detail } : {}
|
|
58
253
|
}
|
|
59
254
|
},
|
|
60
|
-
|
|
61
|
-
|
|
255
|
+
exitCode: TYPE_EXIT[n.type]
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function createErrorHandler(opts) {
|
|
259
|
+
return (err) => {
|
|
260
|
+
const { envelope, exitCode } = classifyError(err, opts);
|
|
261
|
+
process.stderr.write(`${JSON.stringify(envelope)}
|
|
262
|
+
`);
|
|
263
|
+
return process.exit(exitCode);
|
|
62
264
|
};
|
|
63
265
|
}
|
|
266
|
+
function isCleanCommanderExit(err) {
|
|
267
|
+
return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
|
|
268
|
+
}
|
|
269
|
+
function routeErrors(cmd) {
|
|
270
|
+
cmd.exitOverride();
|
|
271
|
+
cmd.configureOutput({ writeErr: () => void 0 });
|
|
272
|
+
cmd.commands.forEach(routeErrors);
|
|
273
|
+
}
|
|
274
|
+
async function runCli(program, opts) {
|
|
275
|
+
routeErrors(program);
|
|
276
|
+
try {
|
|
277
|
+
await program.parseAsync();
|
|
278
|
+
} catch (err) {
|
|
279
|
+
if (isCleanCommanderExit(err)) {
|
|
280
|
+
process.exit(err.exitCode ?? 0);
|
|
281
|
+
}
|
|
282
|
+
createErrorHandler(opts)(err);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/program.ts
|
|
287
|
+
import { styleText } from "util";
|
|
288
|
+
import { Command as Command11 } from "commander";
|
|
289
|
+
|
|
290
|
+
// src/commands/board/issues.ts
|
|
291
|
+
import { Argument } from "commander";
|
|
64
292
|
|
|
65
293
|
// src/utils/client.ts
|
|
294
|
+
import { JiraClient } from "jira-data-center-client";
|
|
66
295
|
function getClient() {
|
|
67
296
|
const baseUrl = process.env.JIRA_URL;
|
|
68
297
|
const token = process.env.JIRA_TOKEN;
|
|
69
298
|
if (!baseUrl || !token) {
|
|
70
299
|
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);
|
|
300
|
+
throw new CliAuthError(`Missing required environment variables: ${missing.join(", ")}`);
|
|
79
301
|
}
|
|
80
302
|
return new JiraClient({ baseUrl, token });
|
|
81
303
|
}
|
|
@@ -89,68 +311,6 @@ function output(data) {
|
|
|
89
311
|
process.stdout.write(`${JSON.stringify(data, null, prettyPrint ? 2 : void 0)}
|
|
90
312
|
`);
|
|
91
313
|
}
|
|
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
314
|
|
|
155
315
|
// src/utils/transformers/base.ts
|
|
156
316
|
function jiraBaseUrl() {
|
|
@@ -513,8 +673,8 @@ function list(parent) {
|
|
|
513
673
|
}
|
|
514
674
|
|
|
515
675
|
// src/commands/board/index.ts
|
|
516
|
-
function registerBoardCommands(
|
|
517
|
-
const board =
|
|
676
|
+
function registerBoardCommands(program) {
|
|
677
|
+
const board = program.command("board").description("Board operations").addHelpText(
|
|
518
678
|
"after",
|
|
519
679
|
`
|
|
520
680
|
Examples:
|
|
@@ -635,8 +795,8 @@ Examples:
|
|
|
635
795
|
}
|
|
636
796
|
|
|
637
797
|
// src/commands/component/index.ts
|
|
638
|
-
function registerComponentCommands(
|
|
639
|
-
const component =
|
|
798
|
+
function registerComponentCommands(program) {
|
|
799
|
+
const component = program.command("component").description("Project component operations").addHelpText(
|
|
640
800
|
"after",
|
|
641
801
|
`
|
|
642
802
|
Examples:
|
|
@@ -658,16 +818,16 @@ Examples:
|
|
|
658
818
|
|
|
659
819
|
// src/commands/field/options.ts
|
|
660
820
|
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("--
|
|
821
|
+
parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--start <number>", "Page number (1-indexed)", positiveInt).addHelpText(
|
|
662
822
|
"after",
|
|
663
|
-
'\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --
|
|
823
|
+
'\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --start 2'
|
|
664
824
|
).action(async (id, opts) => {
|
|
665
825
|
const client = getClient();
|
|
666
826
|
const result = await client.fields.getFieldOptions({
|
|
667
827
|
fieldId: id,
|
|
668
828
|
query: opts.query,
|
|
669
829
|
maxResults: opts.limit,
|
|
670
|
-
page: opts.
|
|
830
|
+
page: opts.start
|
|
671
831
|
});
|
|
672
832
|
output(transformPaged({ ...result, startAt: result.startAt ?? 0 }, transformCustomFieldOption));
|
|
673
833
|
});
|
|
@@ -686,8 +846,8 @@ function search(parent) {
|
|
|
686
846
|
}
|
|
687
847
|
|
|
688
848
|
// src/commands/field/index.ts
|
|
689
|
-
function registerFieldCommands(
|
|
690
|
-
const field =
|
|
849
|
+
function registerFieldCommands(program) {
|
|
850
|
+
const field = program.command("field").description("Field operations").addHelpText(
|
|
691
851
|
"after",
|
|
692
852
|
`
|
|
693
853
|
Examples:
|
|
@@ -718,15 +878,15 @@ async function resolveUserToken(token) {
|
|
|
718
878
|
|
|
719
879
|
// src/commands/issue/assign.ts
|
|
720
880
|
function assign(parent) {
|
|
721
|
-
parent.command("assign <key>
|
|
881
|
+
parent.command("assign <key>").description('Assign an issue. --assignee is a username, "me", or "none" to unassign.').requiredOption("--assignee <user>", 'Username to assign, "me" for the current user, or "none" to unassign').addHelpText(
|
|
722
882
|
"after",
|
|
723
883
|
`
|
|
724
884
|
Examples:
|
|
725
|
-
jiradc issue assign PROJ-123 jsmith
|
|
726
|
-
jiradc issue assign PROJ-123 me
|
|
727
|
-
jiradc issue assign PROJ-123 none`
|
|
728
|
-
).action(async (key,
|
|
729
|
-
const resolved = await resolveUserToken(
|
|
885
|
+
jiradc issue assign PROJ-123 --assignee jsmith
|
|
886
|
+
jiradc issue assign PROJ-123 --assignee me
|
|
887
|
+
jiradc issue assign PROJ-123 --assignee none`
|
|
888
|
+
).action(async (key, opts) => {
|
|
889
|
+
const resolved = await resolveUserToken(opts.assignee);
|
|
730
890
|
const client = getClient();
|
|
731
891
|
await client.issues.update({
|
|
732
892
|
issueKeyOrId: key,
|
|
@@ -738,20 +898,22 @@ Examples:
|
|
|
738
898
|
|
|
739
899
|
// src/commands/issue/attachment/delete.ts
|
|
740
900
|
function deleteAttachment(parent) {
|
|
741
|
-
parent.command("delete").description("Delete an attachment by ID")
|
|
901
|
+
const cmd = parent.command("delete <key>").description("Delete an attachment by ID");
|
|
902
|
+
subEntityOption(cmd, "attachment", { mandatory: true });
|
|
903
|
+
cmd.addHelpText("after", "\nExamples:\n jiradc issue attachment delete PROJ-123 --attachment-id 12345").action(async (key, opts) => {
|
|
742
904
|
const client = getClient();
|
|
743
|
-
await client.issues.deleteAttachment({ attachmentId: opts.
|
|
744
|
-
output({ deleted: true, attachmentId: opts.
|
|
905
|
+
await client.issues.deleteAttachment({ attachmentId: String(opts.attachmentId) });
|
|
906
|
+
output({ deleted: true, issueKey: key, attachmentId: opts.attachmentId });
|
|
745
907
|
});
|
|
746
908
|
}
|
|
747
909
|
|
|
748
910
|
// src/commands/issue/attachment/download-all.ts
|
|
749
|
-
import { mkdirSync } from "fs";
|
|
750
|
-
import { join } from "path";
|
|
911
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
912
|
+
import { join as join3 } from "path";
|
|
751
913
|
function downloadAll(parent) {
|
|
752
914
|
parent.command("download-all <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into").addHelpText("after", "\nExamples:\n jiradc issue attachment download-all PROJ-123 --output ./downloads").action(async (key, opts) => {
|
|
753
915
|
const client = getClient();
|
|
754
|
-
|
|
916
|
+
mkdirSync2(opts.output, { recursive: true });
|
|
755
917
|
const issue = await client.issues.get({
|
|
756
918
|
issueKeyOrId: key,
|
|
757
919
|
fields: ["attachment"]
|
|
@@ -768,7 +930,7 @@ function downloadAll(parent) {
|
|
|
768
930
|
failed.push({ filename: att.filename, error: "No content URL" });
|
|
769
931
|
continue;
|
|
770
932
|
}
|
|
771
|
-
const destPath =
|
|
933
|
+
const destPath = join3(opts.output, att.filename);
|
|
772
934
|
try {
|
|
773
935
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
|
|
774
936
|
results.push({ filename: att.filename, size: att.size, path: destPath });
|
|
@@ -788,11 +950,17 @@ function downloadAll(parent) {
|
|
|
788
950
|
|
|
789
951
|
// src/commands/issue/attachment/download.ts
|
|
790
952
|
function download(parent) {
|
|
791
|
-
parent.command("download <key>").description("Download a single attachment by ID")
|
|
953
|
+
const cmd = parent.command("download <key>").description("Download a single attachment by ID");
|
|
954
|
+
subEntityOption(cmd, "attachment", { mandatory: true });
|
|
955
|
+
cmd.requiredOption("--output <path>", "Local file path to save the attachment").addHelpText(
|
|
956
|
+
"after",
|
|
957
|
+
"\nExamples:\n jiradc issue attachment download PROJ-123 --attachment-id 12345 --output ./report.pdf"
|
|
958
|
+
).action(async (key, opts) => {
|
|
792
959
|
const client = getClient();
|
|
793
|
-
const
|
|
960
|
+
const attachmentId = String(opts.attachmentId);
|
|
961
|
+
const attachment = await client.issues.getAttachment({ attachmentId });
|
|
794
962
|
if (!attachment.content) {
|
|
795
|
-
throw new Error(`Attachment ${
|
|
963
|
+
throw new Error(`Attachment ${attachmentId} has no content URL`);
|
|
796
964
|
}
|
|
797
965
|
await client.issues.downloadAttachment({
|
|
798
966
|
url: attachment.content,
|
|
@@ -841,8 +1009,8 @@ function upload(parent) {
|
|
|
841
1009
|
const filePaths = opts.files.split(",").map((f) => f.trim());
|
|
842
1010
|
const results = [];
|
|
843
1011
|
for (const filePath of filePaths) {
|
|
844
|
-
const
|
|
845
|
-
results.push(...
|
|
1012
|
+
const attachments = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
|
|
1013
|
+
results.push(...attachments);
|
|
846
1014
|
}
|
|
847
1015
|
output({
|
|
848
1016
|
issueKey: key,
|
|
@@ -866,9 +1034,9 @@ function registerAttachmentCommands(parent) {
|
|
|
866
1034
|
Examples:
|
|
867
1035
|
$ jiradc issue attachment list PROJ-123
|
|
868
1036
|
$ jiradc issue attachment upload PROJ-123 --files ./report.pdf
|
|
869
|
-
$ jiradc issue attachment download PROJ-123 --id 12345 --output ./report.pdf
|
|
1037
|
+
$ jiradc issue attachment download PROJ-123 --attachment-id 12345 --output ./report.pdf
|
|
870
1038
|
$ jiradc issue attachment download-all PROJ-123 --output ./downloads
|
|
871
|
-
$ jiradc issue attachment delete --id 12345
|
|
1039
|
+
$ jiradc issue attachment delete PROJ-123 --attachment-id 12345
|
|
872
1040
|
`
|
|
873
1041
|
);
|
|
874
1042
|
upload(attachment);
|
|
@@ -878,47 +1046,6 @@ Examples:
|
|
|
878
1046
|
deleteAttachment(attachment);
|
|
879
1047
|
}
|
|
880
1048
|
|
|
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
1049
|
// src/commands/issue/batch-changelog.ts
|
|
923
1050
|
function batchChangelog(parent) {
|
|
924
1051
|
parent.command("batch-changelog <keys>").description("Get changelogs for multiple issues at once").option("--limit <number>", "Max changelog entries per issue (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
|
|
@@ -979,7 +1106,7 @@ function changelog(parent) {
|
|
|
979
1106
|
// src/commands/issue/clone.ts
|
|
980
1107
|
import { unlink } from "fs/promises";
|
|
981
1108
|
import { tmpdir } from "os";
|
|
982
|
-
import { join as
|
|
1109
|
+
import { join as join4 } from "path";
|
|
983
1110
|
var CLONE_FIELDS = [
|
|
984
1111
|
"summary",
|
|
985
1112
|
"description",
|
|
@@ -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,9 +1198,11 @@ 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").addHelpText("after", '\nExamples:\n jiradc issue comment create PROJ-123 --body "Fixed in latest build"');
|
|
1204
|
+
bodyOption(cmd, { mandatory: true });
|
|
1205
|
+
cmd.action(async (key, opts) => {
|
|
1077
1206
|
const client = getClient();
|
|
1078
1207
|
const result = await client.issues.addComment({ issueKeyOrId: key, body: opts.body });
|
|
1079
1208
|
output(transformComment(result));
|
|
@@ -1082,18 +1211,30 @@ function add(parent) {
|
|
|
1082
1211
|
|
|
1083
1212
|
// src/commands/issue/comment/delete.ts
|
|
1084
1213
|
function deleteComment(parent) {
|
|
1085
|
-
parent.command("delete <key>").description("Delete a comment from an issue").
|
|
1214
|
+
const cmd = parent.command("delete <key>").description("Delete a comment from an issue").addHelpText("after", "\nExamples:\n jiradc issue comment delete PROJ-123 --comment-id 12345");
|
|
1215
|
+
subEntityOption(cmd, "comment", { mandatory: true });
|
|
1216
|
+
cmd.action(async (key, opts) => {
|
|
1086
1217
|
const client = getClient();
|
|
1087
|
-
await client.issues.deleteComment({ issueKeyOrId: key, commentId: opts.
|
|
1088
|
-
output({ deleted: true, issueKey: key, commentId: opts.
|
|
1218
|
+
await client.issues.deleteComment({ issueKeyOrId: key, commentId: String(opts.commentId) });
|
|
1219
|
+
output({ deleted: true, issueKey: key, commentId: opts.commentId });
|
|
1089
1220
|
});
|
|
1090
1221
|
}
|
|
1091
1222
|
|
|
1092
|
-
// src/commands/issue/comment/
|
|
1093
|
-
function
|
|
1094
|
-
parent.command("
|
|
1223
|
+
// src/commands/issue/comment/update.ts
|
|
1224
|
+
function update2(parent) {
|
|
1225
|
+
const cmd = parent.command("update <key>").description("Update an existing comment").addHelpText(
|
|
1226
|
+
"after",
|
|
1227
|
+
'\nExamples:\n jiradc issue comment update PROJ-123 --comment-id 12345 --body "Updated comment text"'
|
|
1228
|
+
);
|
|
1229
|
+
subEntityOption(cmd, "comment", { mandatory: true });
|
|
1230
|
+
bodyOption(cmd, { mandatory: true });
|
|
1231
|
+
cmd.action(async (key, opts) => {
|
|
1095
1232
|
const client = getClient();
|
|
1096
|
-
const result = await client.issues.editComment({
|
|
1233
|
+
const result = await client.issues.editComment({
|
|
1234
|
+
issueKeyOrId: key,
|
|
1235
|
+
commentId: String(opts.commentId),
|
|
1236
|
+
body: opts.body
|
|
1237
|
+
});
|
|
1097
1238
|
output(transformComment(result));
|
|
1098
1239
|
});
|
|
1099
1240
|
}
|
|
@@ -1104,18 +1245,18 @@ function registerCommentCommands(parent) {
|
|
|
1104
1245
|
"after",
|
|
1105
1246
|
`
|
|
1106
1247
|
Examples:
|
|
1107
|
-
$ jiradc issue comment
|
|
1108
|
-
$ jiradc issue comment
|
|
1109
|
-
$ jiradc issue comment delete PROJ-123 --id 12345
|
|
1248
|
+
$ jiradc issue comment create PROJ-123 --body "Fixed in latest build"
|
|
1249
|
+
$ jiradc issue comment update PROJ-123 --comment-id 12345 --body "Updated comment text"
|
|
1250
|
+
$ jiradc issue comment delete PROJ-123 --comment-id 12345
|
|
1110
1251
|
`
|
|
1111
1252
|
);
|
|
1112
|
-
|
|
1113
|
-
|
|
1253
|
+
create2(comment);
|
|
1254
|
+
update2(comment);
|
|
1114
1255
|
deleteComment(comment);
|
|
1115
1256
|
}
|
|
1116
1257
|
|
|
1117
1258
|
// src/commands/issue/create.ts
|
|
1118
|
-
function
|
|
1259
|
+
function create3(parent) {
|
|
1119
1260
|
parent.command("create").description("Create a new issue").requiredOption("--project <key>", "Project key or ID").requiredOption("--type <name>", "Issue type name (e.g., Task, Bug, Story)").requiredOption("--summary <text>", "Issue summary/title").option("--description <text>", "Issue description in wiki markup").option("--assignee <username>", "Assignee username").option("--reporter <username>", "Reporter username").option("--priority <name>", "Priority name (e.g., High, Medium, Low)").option("--labels <labels>", "Comma-separated labels").option("--components <names>", "Comma-separated component names").option("--fix-versions <versions>", "Comma-separated fix version names").option("--due-date <date>", "Due date in YYYY-MM-DD format").option("--parent <key>", "Parent issue key (for subtasks)").option("--custom-fields <json>", `Additional custom fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`).addHelpText(
|
|
1120
1261
|
"after",
|
|
1121
1262
|
`
|
|
@@ -1449,7 +1590,7 @@ function buildSetValue(parsed, wrap) {
|
|
|
1449
1590
|
if (parsed.mode !== "set") return void 0;
|
|
1450
1591
|
return parsed.values.map(wrap);
|
|
1451
1592
|
}
|
|
1452
|
-
function
|
|
1593
|
+
function update3(parent) {
|
|
1453
1594
|
parent.command("update <key>").description("Update issue fields").option("--fields <json>", "JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)").option("--no-notify-users", "Suppress notification emails (default: notify)").option("--attachments <paths>", "Comma-separated local file paths to attach").option("--summary <text>", "Set the issue summary").option("--description <text>", "Set the issue description (wiki markup)").option("--priority <name>", "Set the priority by name (e.g. High)").option("--assignee <user>", 'Set the assignee. Username, "me", or "none" to unassign.').option("--labels <list>", 'Set labels ("a,b,c") or mutate ("+add,-remove")').option("--components <list>", 'Set components ("a,b") or mutate ("+add,-remove")').option("--fix-versions <list>", 'Set fix versions ("1.0,2.0") or mutate ("+1.0,-0.9")').addHelpText(
|
|
1454
1595
|
"after",
|
|
1455
1596
|
`
|
|
@@ -1530,11 +1671,11 @@ Examples:
|
|
|
1530
1671
|
});
|
|
1531
1672
|
}
|
|
1532
1673
|
|
|
1533
|
-
// src/commands/issue/worklog/
|
|
1534
|
-
function
|
|
1535
|
-
parent.command("
|
|
1674
|
+
// src/commands/issue/worklog/create.ts
|
|
1675
|
+
function create4(parent) {
|
|
1676
|
+
parent.command("create <key>").description("Log time spent on an issue").requiredOption("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')").option("--comment <text>", "Worklog comment").option("--started <datetime>", "Start time in ISO 8601 format").addHelpText(
|
|
1536
1677
|
"after",
|
|
1537
|
-
'\nExamples:\n jiradc issue worklog
|
|
1678
|
+
'\nExamples:\n jiradc issue worklog create PROJ-123 --time 2h\n jiradc issue worklog create PROJ-123 --time "1d 4h" --comment "Backend implementation"\n jiradc issue worklog create PROJ-123 --time 3h --started "2026-03-19T09:00:00.000+0000"'
|
|
1538
1679
|
).action(async (key, opts) => {
|
|
1539
1680
|
const client = getClient();
|
|
1540
1681
|
const result = await client.issues.addWorklog({
|
|
@@ -1551,37 +1692,57 @@ function add2(parent) {
|
|
|
1551
1692
|
import { Option as Option4 } from "commander";
|
|
1552
1693
|
var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
|
|
1553
1694
|
function deleteWorklog(parent) {
|
|
1554
|
-
parent.command("delete <key>").description("Delete a worklog entry")
|
|
1695
|
+
const cmd = parent.command("delete <key>").description("Delete a worklog entry");
|
|
1696
|
+
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
1697
|
+
cmd.addOption(new Option4("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"').option("--increase-by <amount>", 'Amount to increase the estimate by; required when --adjust-estimate is "manual"').addHelpText(
|
|
1555
1698
|
"after",
|
|
1556
|
-
"\nExamples:\n jiradc issue worklog delete PROJ-123 --id 12345\n jiradc issue worklog delete PROJ-123 --id 12345 --adjust-estimate leave\n jiradc issue worklog delete PROJ-123 --id 12345 --adjust-estimate new --new-estimate 2h"
|
|
1699
|
+
"\nExamples:\n jiradc issue worklog delete PROJ-123 --worklog-id 12345\n jiradc issue worklog delete PROJ-123 --worklog-id 12345 --adjust-estimate leave\n jiradc issue worklog delete PROJ-123 --worklog-id 12345 --adjust-estimate new --new-estimate 2h"
|
|
1557
1700
|
).action(
|
|
1558
1701
|
async (key, opts) => {
|
|
1559
1702
|
const client = getClient();
|
|
1560
1703
|
await client.issues.deleteWorklog({
|
|
1561
1704
|
issueKeyOrId: key,
|
|
1562
|
-
worklogId: opts.
|
|
1705
|
+
worklogId: String(opts.worklogId),
|
|
1563
1706
|
adjustEstimate: opts.adjustEstimate,
|
|
1564
1707
|
newEstimate: opts.newEstimate,
|
|
1565
1708
|
increaseBy: opts.increaseBy
|
|
1566
1709
|
});
|
|
1567
|
-
output({ deleted: true, issueKey: key, worklogId: opts.
|
|
1710
|
+
output({ deleted: true, issueKey: key, worklogId: opts.worklogId });
|
|
1568
1711
|
}
|
|
1569
1712
|
);
|
|
1570
1713
|
}
|
|
1571
1714
|
|
|
1572
|
-
// src/commands/issue/worklog/
|
|
1715
|
+
// src/commands/issue/worklog/list.ts
|
|
1716
|
+
function list4(parent) {
|
|
1717
|
+
parent.command("list <key>").description("Get worklogs for an issue").option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--limit <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
|
|
1718
|
+
"after",
|
|
1719
|
+
"\nExamples:\n jiradc issue worklog list PROJ-123\n jiradc issue worklog list PROJ-123 --limit 10\n jiradc issue worklog list PROJ-123 --start 10 --limit 5"
|
|
1720
|
+
).action(async (key, opts) => {
|
|
1721
|
+
const client = getClient();
|
|
1722
|
+
const result = await client.issues.getWorklogs({
|
|
1723
|
+
issueKeyOrId: key,
|
|
1724
|
+
startAt: opts.start,
|
|
1725
|
+
maxResults: opts.limit
|
|
1726
|
+
});
|
|
1727
|
+
output(transformPaged(result, transformWorklog));
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/commands/issue/worklog/update.ts
|
|
1573
1732
|
import { Option as Option5 } from "commander";
|
|
1574
1733
|
var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
|
|
1575
|
-
function
|
|
1576
|
-
parent.command("
|
|
1734
|
+
function update4(parent) {
|
|
1735
|
+
const cmd = parent.command("update <key>").description("Update an existing worklog entry");
|
|
1736
|
+
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
1737
|
+
cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')").option("--comment <text>", "Worklog comment").option("--started <datetime>", "Start time in ISO 8601 format").addOption(new Option5("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE2)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"').addHelpText(
|
|
1577
1738
|
"after",
|
|
1578
|
-
'\nExamples:\n jiradc issue worklog
|
|
1739
|
+
'\nExamples:\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --time "1h 30m"\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --comment "Revised note"\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --time 2h --adjust-estimate new --new-estimate 4h'
|
|
1579
1740
|
).action(
|
|
1580
1741
|
async (key, opts) => {
|
|
1581
1742
|
const client = getClient();
|
|
1582
1743
|
const result = await client.issues.updateWorklog({
|
|
1583
1744
|
issueKeyOrId: key,
|
|
1584
|
-
worklogId: opts.
|
|
1745
|
+
worklogId: String(opts.worklogId),
|
|
1585
1746
|
timeSpent: opts.time,
|
|
1586
1747
|
comment: opts.comment,
|
|
1587
1748
|
started: opts.started,
|
|
@@ -1593,43 +1754,27 @@ function edit2(parent) {
|
|
|
1593
1754
|
);
|
|
1594
1755
|
}
|
|
1595
1756
|
|
|
1596
|
-
// src/commands/issue/worklog/list.ts
|
|
1597
|
-
function list4(parent) {
|
|
1598
|
-
parent.command("list <key>").description("Get worklogs for an issue").option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--limit <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
|
|
1599
|
-
"after",
|
|
1600
|
-
"\nExamples:\n jiradc issue worklog list PROJ-123\n jiradc issue worklog list PROJ-123 --limit 10\n jiradc issue worklog list PROJ-123 --start 10 --limit 5"
|
|
1601
|
-
).action(async (key, opts) => {
|
|
1602
|
-
const client = getClient();
|
|
1603
|
-
const result = await client.issues.getWorklogs({
|
|
1604
|
-
issueKeyOrId: key,
|
|
1605
|
-
startAt: opts.start,
|
|
1606
|
-
maxResults: opts.limit
|
|
1607
|
-
});
|
|
1608
|
-
output(transformPaged(result, transformWorklog));
|
|
1609
|
-
});
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
1757
|
// src/commands/issue/worklog/index.ts
|
|
1613
1758
|
function registerWorklogCommands(parent) {
|
|
1614
1759
|
const worklog = parent.command("worklog").description("Worklog operations").addHelpText(
|
|
1615
1760
|
"after",
|
|
1616
1761
|
`
|
|
1617
1762
|
Examples:
|
|
1618
|
-
$ jiradc issue worklog
|
|
1763
|
+
$ jiradc issue worklog create PROJ-123 --time 2h --comment "Backend work"
|
|
1619
1764
|
$ jiradc issue worklog list PROJ-123 --limit 10
|
|
1620
|
-
$ jiradc issue worklog
|
|
1621
|
-
$ jiradc issue worklog delete PROJ-123 --id 12345
|
|
1765
|
+
$ jiradc issue worklog update PROJ-123 --worklog-id 12345 --time "1h 30m"
|
|
1766
|
+
$ jiradc issue worklog delete PROJ-123 --worklog-id 12345
|
|
1622
1767
|
`
|
|
1623
1768
|
);
|
|
1624
|
-
|
|
1769
|
+
create4(worklog);
|
|
1625
1770
|
list4(worklog);
|
|
1626
|
-
|
|
1771
|
+
update4(worklog);
|
|
1627
1772
|
deleteWorklog(worklog);
|
|
1628
1773
|
}
|
|
1629
1774
|
|
|
1630
1775
|
// src/commands/issue/index.ts
|
|
1631
|
-
function registerIssueCommands(
|
|
1632
|
-
const issue =
|
|
1776
|
+
function registerIssueCommands(program) {
|
|
1777
|
+
const issue = program.command("issue").description("Issue operations").addHelpText(
|
|
1633
1778
|
"after",
|
|
1634
1779
|
`
|
|
1635
1780
|
Examples:
|
|
@@ -1643,8 +1788,8 @@ Examples:
|
|
|
1643
1788
|
);
|
|
1644
1789
|
get2(issue);
|
|
1645
1790
|
search2(issue);
|
|
1646
|
-
|
|
1647
|
-
|
|
1791
|
+
create3(issue);
|
|
1792
|
+
update3(issue);
|
|
1648
1793
|
deleteIssue(issue);
|
|
1649
1794
|
transition(issue);
|
|
1650
1795
|
transitions(issue);
|
|
@@ -1658,7 +1803,6 @@ Examples:
|
|
|
1658
1803
|
linkTypes(issue);
|
|
1659
1804
|
linkEpic(issue);
|
|
1660
1805
|
registerAttachmentCommands(issue);
|
|
1661
|
-
attachments(issue);
|
|
1662
1806
|
batchCreate(issue);
|
|
1663
1807
|
clone(issue);
|
|
1664
1808
|
devStatus(issue);
|
|
@@ -1689,8 +1833,8 @@ function versions(parent) {
|
|
|
1689
1833
|
}
|
|
1690
1834
|
|
|
1691
1835
|
// src/commands/project/index.ts
|
|
1692
|
-
function registerProjectCommands(
|
|
1693
|
-
const project =
|
|
1836
|
+
function registerProjectCommands(program) {
|
|
1837
|
+
const project = program.command("project").description("Project operations").addHelpText(
|
|
1694
1838
|
"after",
|
|
1695
1839
|
`
|
|
1696
1840
|
Examples:
|
|
@@ -1703,7 +1847,7 @@ Examples:
|
|
|
1703
1847
|
}
|
|
1704
1848
|
|
|
1705
1849
|
// src/commands/sprint/create.ts
|
|
1706
|
-
function
|
|
1850
|
+
function create5(parent) {
|
|
1707
1851
|
parent.command("create").description("Create a new sprint").requiredOption("--board <id>", "Board ID to create sprint in", positiveInt).requiredOption("--name <name>", "Sprint name").option("--start-date <date>", "Start date in ISO 8601 format").option("--end-date <date>", "End date in ISO 8601 format").option("--goal <goal>", "Sprint goal").addHelpText(
|
|
1708
1852
|
"after",
|
|
1709
1853
|
'\nExamples:\n jiradc sprint create --board 42 --name "Sprint 10"\n jiradc sprint create --board 42 --name "Sprint 10" --start-date 2026-03-20 --end-date 2026-04-03 --goal "Complete auth module"'
|
|
@@ -1769,7 +1913,7 @@ function list6(parent) {
|
|
|
1769
1913
|
// src/commands/sprint/update.ts
|
|
1770
1914
|
import { Argument as Argument4, Option as Option7 } from "commander";
|
|
1771
1915
|
var SPRINT_STATES2 = ["future", "active", "closed"];
|
|
1772
|
-
function
|
|
1916
|
+
function update5(parent) {
|
|
1773
1917
|
parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name").addOption(new Option7("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format").option("--end-date <date>", "New end date in ISO 8601 format").option("--goal <goal>", "New sprint goal").addHelpText(
|
|
1774
1918
|
"after",
|
|
1775
1919
|
'\nExamples:\n jiradc sprint update 100 --name "Sprint 10 - Extended"\n jiradc sprint update 100 --state active\n jiradc sprint update 100 --end-date 2026-04-10 --goal "Updated goal"'
|
|
@@ -1790,8 +1934,8 @@ function update3(parent) {
|
|
|
1790
1934
|
}
|
|
1791
1935
|
|
|
1792
1936
|
// src/commands/sprint/index.ts
|
|
1793
|
-
function registerSprintCommands(
|
|
1794
|
-
const sprint =
|
|
1937
|
+
function registerSprintCommands(program) {
|
|
1938
|
+
const sprint = program.command("sprint").description("Sprint operations").addHelpText(
|
|
1795
1939
|
"after",
|
|
1796
1940
|
`
|
|
1797
1941
|
Examples:
|
|
@@ -1803,8 +1947,8 @@ Examples:
|
|
|
1803
1947
|
);
|
|
1804
1948
|
list6(sprint);
|
|
1805
1949
|
issues2(sprint);
|
|
1806
|
-
|
|
1807
|
-
|
|
1950
|
+
create5(sprint);
|
|
1951
|
+
update5(sprint);
|
|
1808
1952
|
deleteSprint(sprint);
|
|
1809
1953
|
}
|
|
1810
1954
|
|
|
@@ -1834,7 +1978,7 @@ function getTokenClient(options2 = {}) {
|
|
|
1834
1978
|
}
|
|
1835
1979
|
|
|
1836
1980
|
// src/commands/token/create.ts
|
|
1837
|
-
function
|
|
1981
|
+
function create6(parent) {
|
|
1838
1982
|
parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name").option(
|
|
1839
1983
|
"--expiration-duration <days>",
|
|
1840
1984
|
"Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
|
|
@@ -1880,35 +2024,37 @@ function list7(parent) {
|
|
|
1880
2024
|
|
|
1881
2025
|
// src/commands/token/revoke.ts
|
|
1882
2026
|
function revoke(parent) {
|
|
1883
|
-
parent.command("revoke").description("Revoke a Personal Access Token by id").
|
|
2027
|
+
const cmd = parent.command("revoke").description("Revoke a Personal Access Token by id").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
|
|
1884
2028
|
"--basic-password <p>",
|
|
1885
2029
|
"Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
|
|
1886
|
-
)
|
|
2030
|
+
);
|
|
2031
|
+
subjectArg(cmd, "tokenId", { parser: positiveInt, description: "Token id to revoke" });
|
|
2032
|
+
cmd.action(async (tokenId, opts) => {
|
|
1887
2033
|
const { client, username, password } = getTokenClient({
|
|
1888
2034
|
basicUsername: opts.basicUsername,
|
|
1889
2035
|
basicPassword: opts.basicPassword
|
|
1890
2036
|
});
|
|
1891
|
-
await client.accessTokens.revoke({ username, password, tokenId
|
|
1892
|
-
output({ revoked:
|
|
2037
|
+
await client.accessTokens.revoke({ username, password, tokenId });
|
|
2038
|
+
output({ revoked: tokenId });
|
|
1893
2039
|
});
|
|
1894
2040
|
}
|
|
1895
2041
|
|
|
1896
2042
|
// src/commands/token/index.ts
|
|
1897
|
-
function registerTokenCommands(
|
|
1898
|
-
const token =
|
|
2043
|
+
function registerTokenCommands(program) {
|
|
2044
|
+
const token = program.command("token").description("Personal Access Token management").addHelpText(
|
|
1899
2045
|
"after",
|
|
1900
2046
|
`
|
|
1901
2047
|
Examples:
|
|
1902
2048
|
$ jiradc token list
|
|
1903
2049
|
$ jiradc token create --name my-pat
|
|
1904
|
-
$ jiradc token revoke
|
|
2050
|
+
$ jiradc token revoke 173
|
|
1905
2051
|
|
|
1906
2052
|
Auth:
|
|
1907
2053
|
Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD
|
|
1908
2054
|
(or --basic-username / --basic-password on any subcommand).
|
|
1909
2055
|
`
|
|
1910
2056
|
);
|
|
1911
|
-
|
|
2057
|
+
create6(token);
|
|
1912
2058
|
list7(token);
|
|
1913
2059
|
revoke(token);
|
|
1914
2060
|
}
|
|
@@ -1950,8 +2096,8 @@ function search3(parent) {
|
|
|
1950
2096
|
}
|
|
1951
2097
|
|
|
1952
2098
|
// src/commands/user/index.ts
|
|
1953
|
-
function registerUserCommands(
|
|
1954
|
-
const user =
|
|
2099
|
+
function registerUserCommands(program) {
|
|
2100
|
+
const user = program.command("user").description("User operations").addHelpText(
|
|
1955
2101
|
"after",
|
|
1956
2102
|
`
|
|
1957
2103
|
Examples:
|
|
@@ -1965,31 +2111,22 @@ Examples:
|
|
|
1965
2111
|
search3(user);
|
|
1966
2112
|
}
|
|
1967
2113
|
|
|
1968
|
-
// src/
|
|
1969
|
-
function readPackageVersion() {
|
|
1970
|
-
try {
|
|
1971
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
1972
|
-
const pkgPath = join4(here, "..", "package.json");
|
|
1973
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1974
|
-
return pkg.version ?? "0.0.0";
|
|
1975
|
-
} catch {
|
|
1976
|
-
return "0.0.0";
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
2114
|
+
// src/program.ts
|
|
1979
2115
|
var DIM = "\x1B[2m";
|
|
1980
2116
|
var RESET = "\x1B[0m";
|
|
1981
|
-
|
|
1982
|
-
program
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2117
|
+
function buildProgram() {
|
|
2118
|
+
const program = new Command11();
|
|
2119
|
+
program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
|
|
2120
|
+
styleTitle: (str) => styleText("bold", str),
|
|
2121
|
+
styleUsage: (str) => styleText("dim", str),
|
|
2122
|
+
styleCommandDescription: (str) => styleText("dim", str),
|
|
2123
|
+
styleOptionDescription: (str) => styleText("dim", str),
|
|
2124
|
+
styleSubcommandDescription: (str) => styleText("dim", str)
|
|
2125
|
+
}).addHelpText("beforeAll", `
|
|
1989
2126
|
${styleText("bold", "jiradc")} ${DIM}\u2014 Jira Data Center CLI${RESET}
|
|
1990
2127
|
`).addHelpText(
|
|
1991
|
-
|
|
1992
|
-
|
|
2128
|
+
"after",
|
|
2129
|
+
`
|
|
1993
2130
|
${styleText("bold", "Environment:")}
|
|
1994
2131
|
JIRA_URL Jira Server base URL ${DIM}(e.g., https://jira.example.com)${RESET}
|
|
1995
2132
|
JIRA_TOKEN Personal Access Token ${DIM}(generate in Jira > Profile > Personal Access Tokens)${RESET}
|
|
@@ -2003,21 +2140,46 @@ ${styleText("bold", "Examples:")}
|
|
|
2003
2140
|
${DIM}$${RESET} jiradc board list --type scrum
|
|
2004
2141
|
${DIM}$${RESET} jiradc sprint list --board 42 --state active
|
|
2005
2142
|
`
|
|
2006
|
-
);
|
|
2007
|
-
program.option("--pretty", "Pretty-print JSON output");
|
|
2008
|
-
program.hook("preAction", (thisCommand) => {
|
|
2009
|
-
|
|
2010
|
-
});
|
|
2011
|
-
registerIssueCommands(program);
|
|
2012
|
-
registerProjectCommands(program);
|
|
2013
|
-
registerComponentCommands(program);
|
|
2014
|
-
registerBoardCommands(program);
|
|
2015
|
-
registerSprintCommands(program);
|
|
2016
|
-
registerFieldCommands(program);
|
|
2017
|
-
registerUserCommands(program);
|
|
2018
|
-
registerTokenCommands(program);
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2143
|
+
);
|
|
2144
|
+
program.option("--pretty", "Pretty-print JSON output");
|
|
2145
|
+
program.hook("preAction", (thisCommand) => {
|
|
2146
|
+
if (thisCommand.optsWithGlobals().pretty) setPretty(true);
|
|
2147
|
+
});
|
|
2148
|
+
registerIssueCommands(program);
|
|
2149
|
+
registerProjectCommands(program);
|
|
2150
|
+
registerComponentCommands(program);
|
|
2151
|
+
registerBoardCommands(program);
|
|
2152
|
+
registerSprintCommands(program);
|
|
2153
|
+
registerFieldCommands(program);
|
|
2154
|
+
registerUserCommands(program);
|
|
2155
|
+
registerTokenCommands(program);
|
|
2156
|
+
return program;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// src/utils/credentials.ts
|
|
2160
|
+
function getCredentialInfo() {
|
|
2161
|
+
const baseUrl = process.env.JIRA_URL;
|
|
2162
|
+
const token = process.env.JIRA_TOKEN;
|
|
2163
|
+
return {
|
|
2164
|
+
environment: {
|
|
2165
|
+
JIRA_URL: {
|
|
2166
|
+
value: baseUrl ?? null,
|
|
2167
|
+
description: "Your Jira Server base URL (e.g., https://jira.example.com)"
|
|
2168
|
+
},
|
|
2169
|
+
JIRA_TOKEN: {
|
|
2170
|
+
value: token ? "<set>" : null,
|
|
2171
|
+
description: "Personal Access Token"
|
|
2172
|
+
}
|
|
2173
|
+
},
|
|
2174
|
+
tokenUrl: `${baseUrl ?? "https://jira.example.com"}/secure/ViewProfile.jspa`,
|
|
2175
|
+
hint: "Set these as environment variables in the process that runs this CLI (e.g. shell export, container env, or CI/secret store)."
|
|
2176
|
+
};
|
|
2023
2177
|
}
|
|
2178
|
+
|
|
2179
|
+
// src/index.ts
|
|
2180
|
+
await runCli(buildProgram(), {
|
|
2181
|
+
credentialInfo: getCredentialInfo,
|
|
2182
|
+
service: "Jira",
|
|
2183
|
+
authRecovery: "Provide JIRA_URL and JIRA_TOKEN as environment variables to this process (shell export, container env, or CI secret). error.detail lists which are currently set and where to create a token.",
|
|
2184
|
+
networkRecovery: "Verify JIRA_URL is correct, the server is reachable, and you are on the VPN if required."
|
|
2185
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiradc-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-g9960cad.8",
|
|
4
4
|
"publish": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"commander": "^13.1.0",
|
|
15
|
+
"cli-utils": "1.0.0",
|
|
15
16
|
"jira-data-center-client": "1.0.37"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
@@ -34,6 +35,6 @@
|
|
|
34
35
|
"lint": "eslint src",
|
|
35
36
|
"lint:fix": "eslint src --fix",
|
|
36
37
|
"test": "vitest run",
|
|
37
|
-
"test:integration": "vitest run
|
|
38
|
+
"test:integration": "vitest run --config vitest.integration.config.ts"
|
|
38
39
|
}
|
|
39
40
|
}
|