jiradc-cli 1.0.39 → 1.0.41
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 +24 -0
- package/dist/chunk-3GFKV36K.js +1133 -0
- package/dist/dist-JXPWCMQP.js +102 -0
- package/dist/index.js +640 -818
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,688 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const dir = getCacheDir(options.name);
|
|
30
|
-
const path = getCachePath(options.name, key);
|
|
31
|
-
const entry = { data, timestamp: Date.now() };
|
|
32
|
-
try {
|
|
33
|
-
mkdirSync(dir, { recursive: true });
|
|
34
|
-
writeFileSync(path, JSON.stringify(entry));
|
|
35
|
-
} catch {
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function cacheGetOrFetch(options, key, fetcher) {
|
|
39
|
-
const cached = cacheGet(options, key);
|
|
40
|
-
if (cached !== null)
|
|
41
|
-
return cached;
|
|
42
|
-
const data = await fetcher();
|
|
43
|
-
cacheSet(options, key, data);
|
|
44
|
-
return data;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ../../cli-utils/dist/bootstrap.js
|
|
48
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
49
|
-
import { dirname, join as join2 } from "path";
|
|
50
|
-
import { fileURLToPath } from "url";
|
|
51
|
-
function readPackageVersion(importMetaUrl) {
|
|
52
|
-
try {
|
|
53
|
-
const here = dirname(fileURLToPath(importMetaUrl));
|
|
54
|
-
const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf-8"));
|
|
55
|
-
return pkg.version ?? "0.0.0";
|
|
56
|
-
} catch {
|
|
57
|
-
return "0.0.0";
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ../../cli-utils/dist/validators.js
|
|
62
|
-
import { InvalidArgumentError } from "commander";
|
|
63
|
-
function intInRange(min, max) {
|
|
64
|
-
return (raw) => {
|
|
65
|
-
const n = parseInt(raw, 10);
|
|
66
|
-
if (Number.isNaN(n) || !Number.isFinite(n)) {
|
|
67
|
-
throw new InvalidArgumentError("Must be an integer.");
|
|
68
|
-
}
|
|
69
|
-
if (n < min || n > max) {
|
|
70
|
-
throw new InvalidArgumentError(`Must be between ${min} and ${max}.`);
|
|
71
|
-
}
|
|
72
|
-
return n;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function nonNegativeInt(raw) {
|
|
76
|
-
const n = parseInt(raw, 10);
|
|
77
|
-
if (Number.isNaN(n) || n < 0) {
|
|
78
|
-
throw new InvalidArgumentError("Must be a non-negative integer.");
|
|
79
|
-
}
|
|
80
|
-
return n;
|
|
81
|
-
}
|
|
82
|
-
function positiveInt(raw) {
|
|
83
|
-
const n = parseInt(raw, 10);
|
|
84
|
-
if (Number.isNaN(n) || n < 1) {
|
|
85
|
-
throw new InvalidArgumentError("Must be a positive integer.");
|
|
86
|
-
}
|
|
87
|
-
return n;
|
|
88
|
-
}
|
|
89
|
-
function listOf(item) {
|
|
90
|
-
return (raw, previous) => [...Array.isArray(previous) ? previous : [], item(raw)];
|
|
91
|
-
}
|
|
92
|
-
function text(raw) {
|
|
93
|
-
return raw;
|
|
94
|
-
}
|
|
95
|
-
function nonEmpty(raw) {
|
|
96
|
-
if (raw.trim() === "") {
|
|
97
|
-
throw new InvalidArgumentError("Must not be empty.");
|
|
98
|
-
}
|
|
99
|
-
return raw;
|
|
100
|
-
}
|
|
101
|
-
function integer(raw) {
|
|
102
|
-
if (!/^[+-]?\d+$/.test(raw.trim())) {
|
|
103
|
-
throw new InvalidArgumentError("Must be an integer.");
|
|
104
|
-
}
|
|
105
|
-
return Number(raw);
|
|
106
|
-
}
|
|
107
|
-
function date(raw) {
|
|
108
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
|
|
109
|
-
if (!m) {
|
|
110
|
-
throw new InvalidArgumentError("Must be a date in YYYY-MM-DD format.");
|
|
111
|
-
}
|
|
112
|
-
const [, y, mo, d] = m;
|
|
113
|
-
const year = Number(y);
|
|
114
|
-
const month = Number(mo);
|
|
115
|
-
const day = Number(d);
|
|
116
|
-
const dt = new Date(Date.UTC(year, month - 1, day));
|
|
117
|
-
if (dt.getUTCFullYear() !== year || dt.getUTCMonth() !== month - 1 || dt.getUTCDate() !== day) {
|
|
118
|
-
throw new InvalidArgumentError("Not a real calendar date.");
|
|
119
|
-
}
|
|
120
|
-
return raw;
|
|
121
|
-
}
|
|
122
|
-
function dateTime(raw) {
|
|
123
|
-
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(raw) || Number.isNaN(Date.parse(raw))) {
|
|
124
|
-
throw new InvalidArgumentError("Must be an ISO 8601 date-time, e.g. 2026-06-08T14:30:00Z.");
|
|
125
|
-
}
|
|
126
|
-
return raw;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ../../cli-utils/dist/json.js
|
|
130
|
-
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
131
|
-
function jsonShape(schema) {
|
|
132
|
-
return (raw) => {
|
|
133
|
-
let parsed;
|
|
134
|
-
try {
|
|
135
|
-
parsed = JSON.parse(raw);
|
|
136
|
-
} catch {
|
|
137
|
-
throw new InvalidArgumentError2("Must be valid JSON.");
|
|
138
|
-
}
|
|
139
|
-
const result = schema.safeParse(parsed);
|
|
140
|
-
if (!result.success) {
|
|
141
|
-
const issues3 = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
142
|
-
throw new InvalidArgumentError2(`Invalid JSON shape \u2014 ${issues3}`);
|
|
143
|
-
}
|
|
144
|
-
return result.data;
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ../../cli-utils/dist/text-or-file.js
|
|
149
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
150
|
-
import { InvalidArgumentError as InvalidArgumentError3, Option } from "commander";
|
|
151
|
-
var STDIN_REF = "-";
|
|
152
|
-
function rejectStdinSentinel(value) {
|
|
153
|
-
if (value === "@-" || value === STDIN_REF) {
|
|
154
|
-
throw new InvalidArgumentError3(`"${value}" looks like a stdin redirect, which is not supported here. Pass the text directly, or read it from a file/stdin with the matching --\u2026-file <path|-> option.`);
|
|
155
|
-
}
|
|
156
|
-
return value;
|
|
157
|
-
}
|
|
158
|
-
function readFileOrStdin(ref) {
|
|
159
|
-
if (ref === STDIN_REF) {
|
|
160
|
-
if (process.stdin.isTTY) {
|
|
161
|
-
throw new InvalidArgumentError3('"--\u2026-file -" reads stdin, but stdin is a terminal (nothing piped).');
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
return readFileSync3(0, "utf8");
|
|
165
|
-
} catch (err) {
|
|
166
|
-
throw new InvalidArgumentError3(`failed to read stdin for "--\u2026-file -": ${err.message}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
try {
|
|
170
|
-
return readFileSync3(ref, "utf8");
|
|
171
|
-
} catch (err) {
|
|
172
|
-
const e = err;
|
|
173
|
-
if (e.code === "ENOENT") {
|
|
174
|
-
throw new InvalidArgumentError3(`file not found: "${ref}".`);
|
|
175
|
-
}
|
|
176
|
-
throw new InvalidArgumentError3(`failed to read file "${ref}": ${e.message}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
function textOrFileOption(cmd, name, opts = {}) {
|
|
180
|
-
const label = `${name.charAt(0).toUpperCase()}${name.slice(1)} content`;
|
|
181
|
-
cmd.option(`--${name} <text>`, opts.description ?? label, rejectStdinSentinel);
|
|
182
|
-
cmd.addOption(new Option(`--${name}-file <path>`, `Read --${name} from a file, or "-" for stdin (mutually exclusive with --${name})`).conflicts(name).argParser(text));
|
|
183
|
-
return cmd;
|
|
184
|
-
}
|
|
185
|
-
function resolveTextOrFile(opts, name, { required = true } = {}) {
|
|
186
|
-
const record = opts;
|
|
187
|
-
const literal = record[name];
|
|
188
|
-
const ref = record[`${name}File`];
|
|
189
|
-
if (literal !== void 0 && ref !== void 0) {
|
|
190
|
-
throw new InvalidArgumentError3(`--${name} and --${name}-file are mutually exclusive; provide only one.`);
|
|
191
|
-
}
|
|
192
|
-
if (ref !== void 0) {
|
|
193
|
-
return readFileOrStdin(ref);
|
|
194
|
-
}
|
|
195
|
-
if (literal === void 0 && required) {
|
|
196
|
-
throw new InvalidArgumentError3(`a ${name} is required: provide --${name} <text> or --${name}-file <path>.`);
|
|
197
|
-
}
|
|
198
|
-
return literal;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// ../../cli-utils/dist/registry.js
|
|
202
|
-
var SUB_ENTITY_ID_NUMERIC = {
|
|
203
|
-
comment: true,
|
|
204
|
-
worklog: true,
|
|
205
|
-
attachment: true,
|
|
206
|
-
request: true,
|
|
207
|
-
type: true,
|
|
208
|
-
employee: true,
|
|
209
|
-
message: false,
|
|
210
|
-
membership: false
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// ../../cli-utils/dist/builders.js
|
|
214
|
-
function defineOption(cmd, flags, description, parser, mandatory) {
|
|
215
|
-
if (mandatory) {
|
|
216
|
-
return parser ? cmd.requiredOption(flags, description, parser) : cmd.requiredOption(flags, description);
|
|
217
|
-
}
|
|
218
|
-
return parser ? cmd.option(flags, description, parser) : cmd.option(flags, description);
|
|
219
|
-
}
|
|
220
|
-
function subjectArg(cmd, name, opts = {}) {
|
|
221
|
-
const inner = opts.variadic ? `${name}...` : name;
|
|
222
|
-
const token = opts.optional ? `[${inner}]` : `<${inner}>`;
|
|
223
|
-
return opts.parser ? cmd.argument(token, opts.description ?? "", opts.parser) : cmd.argument(token, opts.description ?? "");
|
|
224
|
-
}
|
|
225
|
-
function subEntityOption(cmd, entity, opts = {}) {
|
|
226
|
-
const numeric = opts.numeric ?? SUB_ENTITY_ID_NUMERIC[entity] ?? true;
|
|
227
|
-
const description = `${entity.charAt(0).toUpperCase()}${entity.slice(1)} id`;
|
|
228
|
-
return defineOption(cmd, `--${entity}-id <id>`, description, numeric ? positiveInt : text, opts.mandatory);
|
|
229
|
-
}
|
|
230
|
-
function paginationOptions(cmd, opts = {}) {
|
|
231
|
-
const max = opts.maxLimit ?? 1e3;
|
|
232
|
-
const def = opts.defaultLimit ?? 25;
|
|
233
|
-
cmd.option("--limit <n>", `Max results per page (1-${max})`, intInRange(1, max), def);
|
|
234
|
-
if (opts.startIsToken) {
|
|
235
|
-
cmd.option("--start <indexOrToken>", "Pagination cursor: 0-based offset or opaque next-page token", text);
|
|
236
|
-
} else {
|
|
237
|
-
cmd.option("--start <index>", "Pagination offset (0-based)", nonNegativeInt);
|
|
238
|
-
}
|
|
239
|
-
return cmd;
|
|
240
|
-
}
|
|
241
|
-
function bodyOption(cmd, opts = {}) {
|
|
242
|
-
return textOrFileOption(cmd, "body", { description: "Prose body content", ...opts });
|
|
243
|
-
}
|
|
244
|
-
function commentOption(cmd, opts = {}) {
|
|
245
|
-
return textOrFileOption(cmd, "comment", { description: "Optional note attached to the action", ...opts });
|
|
246
|
-
}
|
|
247
|
-
var EXAMPLES = /* @__PURE__ */ new WeakSet();
|
|
248
|
-
function commandPath(cmd) {
|
|
249
|
-
const parts = [];
|
|
250
|
-
for (let c = cmd; c; c = c.parent)
|
|
251
|
-
parts.unshift(c.name());
|
|
252
|
-
return parts.join(" ");
|
|
253
|
-
}
|
|
254
|
-
function exampleLine(path, entry) {
|
|
255
|
-
if (typeof entry === "string")
|
|
256
|
-
return entry ? ` ${path} ${entry}` : ` ${path}`;
|
|
257
|
-
if ("raw" in entry)
|
|
258
|
-
return ` ${entry.raw}`;
|
|
259
|
-
const [args, comment] = entry;
|
|
260
|
-
const line = args ? `${path} ${args}` : path;
|
|
261
|
-
return ` ${line} # ${comment}`;
|
|
262
|
-
}
|
|
263
|
-
function examples(cmd, entries) {
|
|
264
|
-
EXAMPLES.add(cmd);
|
|
265
|
-
return cmd.addHelpText("after", () => {
|
|
266
|
-
const path = commandPath(cmd);
|
|
267
|
-
return `
|
|
268
|
-
Examples:
|
|
269
|
-
${entries.map((e) => exampleLine(path, e)).join("\n")}`;
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ../../cli-utils/dist/errors.js
|
|
274
|
-
import { CommanderError } from "commander";
|
|
275
|
-
var EXIT = {
|
|
276
|
-
SUCCESS: 0,
|
|
277
|
-
GENERIC: 1,
|
|
278
|
-
USAGE: 2,
|
|
279
|
-
NOT_FOUND: 3,
|
|
280
|
-
FORBIDDEN: 4,
|
|
281
|
-
CONFLICT: 5,
|
|
282
|
-
AUTH: 6
|
|
283
|
-
};
|
|
284
|
-
var CliAuthError = class extends Error {
|
|
285
|
-
constructor(message) {
|
|
286
|
-
super(message);
|
|
287
|
-
this.name = "CliAuthError";
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
var NOT_FOUND_RECOVERY = "Verify the id/key, then check your access.";
|
|
291
|
-
var NOT_FOUND_MESSAGE = "Not found: it may not exist, or you may not have permission to see it";
|
|
292
|
-
var USAGE_RECOVERY = "Check the command syntax and flags; run the command with --help.";
|
|
293
|
-
var CliNotFoundError = class extends Error {
|
|
294
|
-
recovery;
|
|
295
|
-
constructor(message, recovery = NOT_FOUND_RECOVERY) {
|
|
296
|
-
super(message);
|
|
297
|
-
this.name = "CliNotFoundError";
|
|
298
|
-
this.recovery = recovery;
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
var CliUsageError = class extends Error {
|
|
302
|
-
recovery;
|
|
303
|
-
detail;
|
|
304
|
-
constructor(message, recovery = USAGE_RECOVERY, detail) {
|
|
305
|
-
super(message);
|
|
306
|
-
this.name = "CliUsageError";
|
|
307
|
-
this.recovery = recovery;
|
|
308
|
-
this.detail = detail;
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
var SubcommandRequiredError = class extends Error {
|
|
312
|
-
subcommands;
|
|
313
|
-
constructor(commandPath3, subcommands) {
|
|
314
|
-
super(`'${commandPath3}' requires a subcommand`);
|
|
315
|
-
this.name = "SubcommandRequiredError";
|
|
316
|
-
this.subcommands = subcommands;
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
var UnknownSubcommandError = class extends Error {
|
|
320
|
-
subcommands;
|
|
321
|
-
suggestion;
|
|
322
|
-
constructor(commandPath3, token, subcommands, suggestion) {
|
|
323
|
-
super(`'${token}' is not a subcommand of '${commandPath3}'`);
|
|
324
|
-
this.name = "UnknownSubcommandError";
|
|
325
|
-
this.subcommands = subcommands;
|
|
326
|
-
this.suggestion = suggestion;
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
var TYPE_EXIT = {
|
|
330
|
-
usage: EXIT.USAGE,
|
|
331
|
-
not_found: EXIT.NOT_FOUND,
|
|
332
|
-
forbidden: EXIT.FORBIDDEN,
|
|
333
|
-
conflict: EXIT.CONFLICT,
|
|
334
|
-
auth: EXIT.AUTH,
|
|
335
|
-
rate_limited: EXIT.GENERIC,
|
|
336
|
-
server: EXIT.GENERIC,
|
|
337
|
-
timeout: EXIT.GENERIC,
|
|
338
|
-
network: EXIT.GENERIC,
|
|
339
|
-
unknown: EXIT.GENERIC
|
|
340
|
-
};
|
|
341
|
-
var TRANSPORT_CODES = {
|
|
342
|
-
/** Our own timeout fired: connected (or tried to), no response in time. */
|
|
343
|
-
readTimeout: ["ECONNABORTED"],
|
|
344
|
-
/** The OS gave up establishing the connection. */
|
|
345
|
-
connectTimeout: ["ETIMEDOUT"],
|
|
346
|
-
/** No such host, or DNS itself is unavailable. */
|
|
347
|
-
unresolved: ["ENOTFOUND", "EAI_AGAIN"],
|
|
348
|
-
/** Host is there, nothing is listening on that port. */
|
|
349
|
-
refused: ["ECONNREFUSED"],
|
|
350
|
-
/** Established, then died mid-flight. */
|
|
351
|
-
dropped: ["ECONNRESET", "EPIPE"]
|
|
352
|
-
};
|
|
353
|
-
function humanMs(ms) {
|
|
354
|
-
return ms % 1e3 === 0 ? `${ms / 1e3}s` : `${ms}ms`;
|
|
355
|
-
}
|
|
356
|
-
function targetHost(err) {
|
|
357
|
-
const config = err?.config;
|
|
358
|
-
const raw = config?.baseURL ?? config?.url;
|
|
359
|
-
if (raw === void 0 || raw === "")
|
|
360
|
-
return void 0;
|
|
361
|
-
try {
|
|
362
|
-
return new URL(raw).host;
|
|
363
|
-
} catch {
|
|
364
|
-
return void 0;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
function configuredTimeoutMs(err) {
|
|
368
|
-
const t = err?.config?.timeout;
|
|
369
|
-
return typeof t === "number" && t > 0 ? t : void 0;
|
|
370
|
-
}
|
|
371
|
-
function retryAfterSeconds(err) {
|
|
372
|
-
const headers = err?.response?.headers;
|
|
373
|
-
const raw = headers?.["retry-after"];
|
|
374
|
-
if (raw === void 0 || raw === "")
|
|
375
|
-
return void 0;
|
|
376
|
-
const seconds = Number(raw);
|
|
377
|
-
if (Number.isFinite(seconds))
|
|
378
|
-
return Math.max(0, Math.round(seconds));
|
|
379
|
-
if (typeof raw !== "string")
|
|
380
|
-
return void 0;
|
|
381
|
-
const at = Date.parse(raw);
|
|
382
|
-
if (Number.isNaN(at))
|
|
383
|
-
return void 0;
|
|
384
|
-
return Math.max(0, Math.round((at - Date.now()) / 1e3));
|
|
385
|
-
}
|
|
386
|
-
function httpStatus(err) {
|
|
387
|
-
const e = err;
|
|
388
|
-
return e?.response?.status ?? e?.statusCode;
|
|
389
|
-
}
|
|
390
|
-
function responseDetail(err) {
|
|
391
|
-
const e = err;
|
|
392
|
-
const data = e?.response?.data;
|
|
393
|
-
if (data && typeof data === "object")
|
|
394
|
-
return data;
|
|
395
|
-
const body = e?.body;
|
|
396
|
-
if (typeof body === "string") {
|
|
397
|
-
try {
|
|
398
|
-
const parsed = JSON.parse(body);
|
|
399
|
-
return parsed.error ?? parsed;
|
|
400
|
-
} catch {
|
|
401
|
-
return void 0;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
if (body && typeof body === "object") {
|
|
405
|
-
return body.error ?? body;
|
|
406
|
-
}
|
|
407
|
-
return void 0;
|
|
408
|
-
}
|
|
409
|
-
function errorCode(err) {
|
|
410
|
-
return err?.code;
|
|
411
|
-
}
|
|
412
|
-
function normalize(err, opts) {
|
|
413
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
414
|
-
if (err instanceof CommanderError) {
|
|
415
|
-
return {
|
|
416
|
-
type: "usage",
|
|
417
|
-
message: (message || "Invalid command usage").replace(/^error:\s+/, ""),
|
|
418
|
-
recovery: "Check the command syntax and flags; run the command with --help.",
|
|
419
|
-
retryable: false
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
if (err instanceof SubcommandRequiredError) {
|
|
423
|
-
return {
|
|
424
|
-
type: "usage",
|
|
425
|
-
message,
|
|
426
|
-
recovery: `Re-run with one of these subcommands: ${err.subcommands.join(", ")}.`,
|
|
427
|
-
retryable: false,
|
|
428
|
-
detail: { subcommands: err.subcommands }
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
if (err instanceof UnknownSubcommandError) {
|
|
432
|
-
const didYouMean = err.suggestion === void 0 ? "" : `Did you mean '${err.suggestion}'? `;
|
|
433
|
-
return {
|
|
434
|
-
type: "usage",
|
|
435
|
-
message,
|
|
436
|
-
recovery: `${didYouMean}Valid subcommands: ${err.subcommands.join(", ")}.`,
|
|
437
|
-
retryable: false,
|
|
438
|
-
detail: { subcommands: err.subcommands }
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
if (err instanceof CliAuthError) {
|
|
442
|
-
return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
|
|
443
|
-
}
|
|
444
|
-
if (err instanceof CliNotFoundError) {
|
|
445
|
-
return { type: "not_found", message, recovery: err.recovery, retryable: false };
|
|
446
|
-
}
|
|
447
|
-
if (err instanceof CliUsageError) {
|
|
448
|
-
return {
|
|
449
|
-
type: "usage",
|
|
450
|
-
message,
|
|
451
|
-
recovery: err.recovery,
|
|
452
|
-
retryable: false,
|
|
453
|
-
...err.detail !== void 0 ? { detail: err.detail } : {}
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
const status = httpStatus(err);
|
|
457
|
-
const detail = responseDetail(err);
|
|
458
|
-
if (status !== void 0) {
|
|
459
|
-
switch (status) {
|
|
460
|
-
case 400:
|
|
461
|
-
return {
|
|
462
|
-
type: "usage",
|
|
463
|
-
status,
|
|
464
|
-
message: "Bad request",
|
|
465
|
-
recovery: "Check parameter values (ids, keys, query syntax) against the API.",
|
|
466
|
-
retryable: false,
|
|
467
|
-
detail
|
|
468
|
-
};
|
|
469
|
-
case 401:
|
|
470
|
-
return {
|
|
471
|
-
type: "auth",
|
|
472
|
-
status,
|
|
473
|
-
message: "Authentication failed",
|
|
474
|
-
recovery: opts.authRecovery,
|
|
475
|
-
retryable: false
|
|
476
|
-
};
|
|
477
|
-
case 403:
|
|
478
|
-
return {
|
|
479
|
-
type: "forbidden",
|
|
480
|
-
status,
|
|
481
|
-
message: "Forbidden",
|
|
482
|
-
recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
|
|
483
|
-
retryable: false,
|
|
484
|
-
detail
|
|
485
|
-
};
|
|
486
|
-
case 404:
|
|
487
|
-
return {
|
|
488
|
-
type: "not_found",
|
|
489
|
-
status,
|
|
490
|
-
message: NOT_FOUND_MESSAGE,
|
|
491
|
-
recovery: NOT_FOUND_RECOVERY,
|
|
492
|
-
retryable: false,
|
|
493
|
-
detail
|
|
494
|
-
};
|
|
495
|
-
case 409:
|
|
496
|
-
return {
|
|
497
|
-
type: "conflict",
|
|
498
|
-
status,
|
|
499
|
-
message: "Conflict",
|
|
500
|
-
recovery: "The resource changed or already exists; re-fetch current state and retry.",
|
|
501
|
-
retryable: false,
|
|
502
|
-
detail
|
|
503
|
-
};
|
|
504
|
-
case 429: {
|
|
505
|
-
const wait = retryAfterSeconds(err);
|
|
506
|
-
return {
|
|
507
|
-
type: "rate_limited",
|
|
508
|
-
status,
|
|
509
|
-
message: `${opts.service} is rate limiting these requests`,
|
|
510
|
-
recovery: wait === void 0 ? `${opts.service} did not say how long to wait. Pause a few seconds before retrying, and make fewer requests at once.` : `${opts.service} asked for a ${wait}s pause before the next request. Wait at least that long, then retry.`,
|
|
511
|
-
retryable: true,
|
|
512
|
-
...wait === void 0 ? {} : { detail: { retryAfterSeconds: wait } }
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
if (status >= 500) {
|
|
517
|
-
return {
|
|
518
|
-
type: "server",
|
|
519
|
-
status,
|
|
520
|
-
message: status === 503 ? `${opts.service} returned 503 (service unavailable)` : `${opts.service} returned ${status}`,
|
|
521
|
-
recovery: status === 503 ? `${opts.service} is temporarily refusing work. Retry shortly.` : `${opts.service} failed to complete the request. Usually temporary, so retry. If it persists, the request itself may be at fault.`,
|
|
522
|
-
retryable: true,
|
|
523
|
-
detail
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
return {
|
|
527
|
-
type: "unknown",
|
|
528
|
-
status,
|
|
529
|
-
message: `${opts.service} error (HTTP ${status}): ${message}`,
|
|
530
|
-
recovery: "Inspect the detail field for the API response.",
|
|
531
|
-
retryable: false,
|
|
532
|
-
detail
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
const code = errorCode(err);
|
|
536
|
-
const transport = classifyTransport(err, code, message, opts);
|
|
537
|
-
if (transport)
|
|
538
|
-
return transport;
|
|
539
|
-
return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
|
|
540
|
-
}
|
|
541
|
-
function classifyTransport(err, code, message, opts) {
|
|
542
|
-
const matches = (codes) => code !== void 0 && codes.includes(code) || codes.some((c) => message.includes(c));
|
|
543
|
-
const host = targetHost(err);
|
|
544
|
-
const where = host ?? opts.service;
|
|
545
|
-
const urlVar = opts.urlEnvVar ?? "the base-URL environment variable";
|
|
546
|
-
if (matches(TRANSPORT_CODES.readTimeout)) {
|
|
547
|
-
const limit = configuredTimeoutMs(err);
|
|
548
|
-
return {
|
|
549
|
-
type: "timeout",
|
|
550
|
-
message: `${opts.service} did not respond${limit === void 0 ? " in time" : ` within ${humanMs(limit)}`}`,
|
|
551
|
-
recovery: `The request reached ${opts.service} but no response came back in time. Usually the server is busy or the query is too large. Retry, or ask for less: fewer results per page, fewer fields, a narrower query.`,
|
|
552
|
-
retryable: true
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
if (matches(TRANSPORT_CODES.connectTimeout)) {
|
|
556
|
-
const limit = configuredTimeoutMs(err);
|
|
557
|
-
return {
|
|
558
|
-
type: "timeout",
|
|
559
|
-
message: `Could not open a connection to ${where}${limit === void 0 ? "" : ` within ${humanMs(limit)}`}`,
|
|
560
|
-
recovery: `The host did not accept a connection in time. If it is reachable at all it is likely overloaded, so retry. If this repeats, confirm ${urlVar} points at a host this machine can reach.`,
|
|
561
|
-
retryable: true
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
-
if (matches(TRANSPORT_CODES.unresolved)) {
|
|
565
|
-
return {
|
|
566
|
-
type: "network",
|
|
567
|
-
message: `The host ${where} does not resolve`,
|
|
568
|
-
recovery: `${urlVar} points at a hostname that cannot be looked up from this machine. Check it for a typo. Retrying will not help until it changes.`,
|
|
569
|
-
retryable: false
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
if (matches(TRANSPORT_CODES.refused)) {
|
|
573
|
-
return {
|
|
574
|
-
type: "network",
|
|
575
|
-
message: `Nothing accepted a connection at ${where}`,
|
|
576
|
-
recovery: `The host resolved but refused the connection, usually a wrong port or a service that is not running. Retrying will not help until that changes.`,
|
|
577
|
-
retryable: false
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
if (matches(TRANSPORT_CODES.dropped)) {
|
|
581
|
-
return {
|
|
582
|
-
type: "network",
|
|
583
|
-
message: `The connection to ${opts.service} closed before a response arrived`,
|
|
584
|
-
recovery: `The connection dropped mid-request. This is usually a blip, so retry.`,
|
|
585
|
-
retryable: true
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
return void 0;
|
|
589
|
-
}
|
|
590
|
-
function classifyError(err, opts) {
|
|
591
|
-
const base = normalize(err, opts);
|
|
592
|
-
const n = opts.adapt ? opts.adapt(base, err) : base;
|
|
593
|
-
if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
|
|
594
|
-
n.detail = opts.credentialInfo();
|
|
595
|
-
}
|
|
596
|
-
return {
|
|
597
|
-
envelope: {
|
|
598
|
-
error: {
|
|
599
|
-
type: n.type,
|
|
600
|
-
message: n.message,
|
|
601
|
-
recovery: n.recovery,
|
|
602
|
-
retryable: n.retryable,
|
|
603
|
-
...n.detail !== void 0 ? { detail: n.detail } : {}
|
|
604
|
-
}
|
|
605
|
-
},
|
|
606
|
-
exitCode: TYPE_EXIT[n.type]
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
function createErrorHandler(opts) {
|
|
610
|
-
return (err) => {
|
|
611
|
-
const { envelope, exitCode } = classifyError(err, opts);
|
|
612
|
-
process.stderr.write(`${JSON.stringify(envelope)}
|
|
613
|
-
`);
|
|
614
|
-
return process.exit(exitCode);
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
function isCleanCommanderExit(err) {
|
|
618
|
-
return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
|
|
619
|
-
}
|
|
620
|
-
function routeErrors(cmd) {
|
|
621
|
-
cmd.exitOverride();
|
|
622
|
-
cmd.configureOutput({ writeErr: () => void 0 });
|
|
623
|
-
cmd.commands.forEach(routeErrors);
|
|
624
|
-
}
|
|
625
|
-
function commandPath2(cmd) {
|
|
626
|
-
const parts = [];
|
|
627
|
-
let cur = cmd;
|
|
628
|
-
while (cur) {
|
|
629
|
-
parts.unshift(cur.name());
|
|
630
|
-
cur = cur.parent;
|
|
631
|
-
}
|
|
632
|
-
return parts.join(" ");
|
|
633
|
-
}
|
|
634
|
-
function attachSubcommandGuards(cmd) {
|
|
635
|
-
cmd.commands.forEach(attachSubcommandGuards);
|
|
636
|
-
if (cmd.commands.length === 0)
|
|
637
|
-
return;
|
|
638
|
-
const hasAction = Boolean(cmd._actionHandler);
|
|
639
|
-
if (hasAction)
|
|
640
|
-
return;
|
|
641
|
-
cmd.allowExcessArguments(true);
|
|
642
|
-
cmd.action((...params) => {
|
|
643
|
-
const invoked = params[params.length - 1];
|
|
644
|
-
const names = cmd.commands.map((c) => c.name()).filter((name) => name !== "help");
|
|
645
|
-
const [token] = invoked.args;
|
|
646
|
-
if (token === void 0)
|
|
647
|
-
throw new SubcommandRequiredError(commandPath2(cmd), names);
|
|
648
|
-
throw new UnknownSubcommandError(commandPath2(cmd), token, names, suggestSubcommand(token, names));
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
function editDistance(a, b) {
|
|
652
|
-
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
653
|
-
for (let i = 1; i <= a.length; i++) {
|
|
654
|
-
const row = [i];
|
|
655
|
-
for (let j = 1; j <= b.length; j++) {
|
|
656
|
-
const substitution = prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
657
|
-
row[j] = Math.min(row[j - 1] + 1, prev[j] + 1, substitution);
|
|
658
|
-
}
|
|
659
|
-
prev = row;
|
|
660
|
-
}
|
|
661
|
-
return prev[b.length];
|
|
662
|
-
}
|
|
663
|
-
function suggestSubcommand(token, names) {
|
|
664
|
-
const MAX_EDITS = 2;
|
|
665
|
-
let best;
|
|
666
|
-
for (const name of names) {
|
|
667
|
-
const distance = editDistance(token.toLowerCase(), name.toLowerCase());
|
|
668
|
-
if (distance <= MAX_EDITS && distance < token.length && (!best || distance < best.distance)) {
|
|
669
|
-
best = { name, distance };
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
return best?.name;
|
|
673
|
-
}
|
|
674
|
-
async function runCli(program, opts) {
|
|
675
|
-
routeErrors(program);
|
|
676
|
-
attachSubcommandGuards(program);
|
|
677
|
-
try {
|
|
678
|
-
await program.parseAsync();
|
|
679
|
-
} catch (err) {
|
|
680
|
-
if (isCleanCommanderExit(err)) {
|
|
681
|
-
process.exit(err.exitCode ?? 0);
|
|
682
|
-
}
|
|
683
|
-
createErrorHandler(opts)(err);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
CliAuthError,
|
|
4
|
+
CliNotFoundError,
|
|
5
|
+
CliUsageError,
|
|
6
|
+
bodyOption,
|
|
7
|
+
cacheGetOrFetch,
|
|
8
|
+
commentOption,
|
|
9
|
+
date,
|
|
10
|
+
dateTime,
|
|
11
|
+
examples,
|
|
12
|
+
intInRange,
|
|
13
|
+
integer,
|
|
14
|
+
jsonShape,
|
|
15
|
+
listOf,
|
|
16
|
+
nonEmpty,
|
|
17
|
+
nonNegativeInt,
|
|
18
|
+
paginationOptions,
|
|
19
|
+
positiveInt,
|
|
20
|
+
readPackageVersion,
|
|
21
|
+
resolveTextOrFile,
|
|
22
|
+
runCli,
|
|
23
|
+
scopeIdOption,
|
|
24
|
+
subEntityOption,
|
|
25
|
+
subjectArg,
|
|
26
|
+
text,
|
|
27
|
+
textOrFileOption
|
|
28
|
+
} from "./chunk-3GFKV36K.js";
|
|
686
29
|
|
|
687
30
|
// src/program.ts
|
|
688
31
|
import { styleText } from "util";
|
|
@@ -1294,10 +637,10 @@ function issues(parent) {
|
|
|
1294
637
|
}
|
|
1295
638
|
|
|
1296
639
|
// src/commands/board/list.ts
|
|
1297
|
-
import { Option
|
|
640
|
+
import { Option } from "commander";
|
|
1298
641
|
var BOARD_TYPES = ["scrum", "kanban", "simple"];
|
|
1299
642
|
function list(parent) {
|
|
1300
|
-
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", text).addOption(new
|
|
643
|
+
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", text).addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name", text);
|
|
1301
644
|
examples(cmd, ["", "--limit 10", "--project PROJ", '--type scrum --name "Team Board"']);
|
|
1302
645
|
cmd.action(async (opts) => {
|
|
1303
646
|
const client = getClient();
|
|
@@ -1321,7 +664,7 @@ function registerBoardCommands(program) {
|
|
|
1321
664
|
}
|
|
1322
665
|
|
|
1323
666
|
// src/commands/component/create.ts
|
|
1324
|
-
import { Option as
|
|
667
|
+
import { Option as Option2 } from "commander";
|
|
1325
668
|
var ASSIGNEE_TYPES = [
|
|
1326
669
|
"PROJECT_DEFAULT",
|
|
1327
670
|
"COMPONENT_LEAD",
|
|
@@ -1329,7 +672,7 @@ var ASSIGNEE_TYPES = [
|
|
|
1329
672
|
"UNASSIGNED"
|
|
1330
673
|
];
|
|
1331
674
|
function create(parent) {
|
|
1332
|
-
const cmd = parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., PROJ)", text).requiredOption("--name <name>", "Component name", text).option("--lead <username>", "Username of the component lead", text).addOption(new
|
|
675
|
+
const cmd = parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., PROJ)", text).requiredOption("--name <name>", "Component name", text).option("--lead <username>", "Username of the component lead", text).addOption(new Option2("--assignee-type <type>", "Assignee strategy").choices(ASSIGNEE_TYPES));
|
|
1333
676
|
textOrFileOption(cmd, "description", { description: "Component description" });
|
|
1334
677
|
examples(cmd, [
|
|
1335
678
|
"--project PROJ --name Backend",
|
|
@@ -1397,7 +740,7 @@ function list2(parent) {
|
|
|
1397
740
|
}
|
|
1398
741
|
|
|
1399
742
|
// src/commands/component/update.ts
|
|
1400
|
-
import { Option as
|
|
743
|
+
import { Option as Option3 } from "commander";
|
|
1401
744
|
var ASSIGNEE_TYPES2 = [
|
|
1402
745
|
"PROJECT_DEFAULT",
|
|
1403
746
|
"COMPONENT_LEAD",
|
|
@@ -1405,7 +748,7 @@ var ASSIGNEE_TYPES2 = [
|
|
|
1405
748
|
"UNASSIGNED"
|
|
1406
749
|
];
|
|
1407
750
|
function update(parent) {
|
|
1408
|
-
const cmd = parent.command("update").description("Update an existing component (only provided fields are changed)").argument("<id>", "Component ID", positiveInt).option("--name <name>", "New component name", text).option("--lead <username>", "Username of the component lead (empty string clears it)", text).addOption(new
|
|
751
|
+
const cmd = parent.command("update").description("Update an existing component (only provided fields are changed)").argument("<id>", "Component ID", positiveInt).option("--name <name>", "New component name", text).option("--lead <username>", "Username of the component lead (empty string clears it)", text).addOption(new Option3("--assignee-type <type>", "New assignee strategy").choices(ASSIGNEE_TYPES2));
|
|
1409
752
|
textOrFileOption(cmd, "description", { description: "New component description" });
|
|
1410
753
|
examples(cmd, [
|
|
1411
754
|
"11289 --name Backend",
|
|
@@ -1502,21 +845,21 @@ async function resolveUserToken(token) {
|
|
|
1502
845
|
|
|
1503
846
|
// src/utils/validators.ts
|
|
1504
847
|
import { existsSync } from "fs";
|
|
1505
|
-
import { InvalidArgumentError
|
|
848
|
+
import { InvalidArgumentError } from "commander";
|
|
1506
849
|
function issueKey(raw) {
|
|
1507
850
|
if (!/^(\d+|[A-Z][A-Z0-9]+-\d+)$/.test(raw)) {
|
|
1508
|
-
throw new
|
|
851
|
+
throw new InvalidArgumentError("Must be a Jira issue key (e.g. PROJ-123) or a numeric issue id.");
|
|
1509
852
|
}
|
|
1510
853
|
return raw;
|
|
1511
854
|
}
|
|
1512
855
|
function keyList(raw) {
|
|
1513
856
|
const keys = raw.split(",").map((k) => k.trim()).filter(Boolean);
|
|
1514
|
-
if (keys.length === 0) throw new
|
|
857
|
+
if (keys.length === 0) throw new InvalidArgumentError("Provide at least one comma-separated issue key.");
|
|
1515
858
|
for (const k of keys) issueKey(k);
|
|
1516
859
|
return keys;
|
|
1517
860
|
}
|
|
1518
861
|
function filePath(raw) {
|
|
1519
|
-
if (!existsSync(raw)) throw new
|
|
862
|
+
if (!existsSync(raw)) throw new InvalidArgumentError(`File not found: ${raw}`);
|
|
1520
863
|
return raw;
|
|
1521
864
|
}
|
|
1522
865
|
|
|
@@ -1548,14 +891,14 @@ function deleteAttachment(parent) {
|
|
|
1548
891
|
}
|
|
1549
892
|
|
|
1550
893
|
// src/commands/issue/attachment/download-all.ts
|
|
1551
|
-
import { mkdirSync
|
|
1552
|
-
import { join
|
|
894
|
+
import { mkdirSync } from "fs";
|
|
895
|
+
import { join } from "path";
|
|
1553
896
|
function downloadAll(parent) {
|
|
1554
897
|
const cmd = parent.command("download-all").description("Download all attachments from an issue").argument("<key>", "Issue key", issueKey).requiredOption("--output <dir>", "Local directory to save attachments into", text);
|
|
1555
898
|
examples(cmd, ["PROJ-123 --output ./downloads"]);
|
|
1556
899
|
cmd.action(async (key, opts) => {
|
|
1557
900
|
const client = getClient();
|
|
1558
|
-
|
|
901
|
+
mkdirSync(opts.output, { recursive: true });
|
|
1559
902
|
const issue = await client.issues.get({
|
|
1560
903
|
issueKeyOrId: key,
|
|
1561
904
|
fields: ["attachment"]
|
|
@@ -1572,7 +915,7 @@ function downloadAll(parent) {
|
|
|
1572
915
|
failed.push({ filename: att.filename, error: "No content URL" });
|
|
1573
916
|
continue;
|
|
1574
917
|
}
|
|
1575
|
-
const destPath =
|
|
918
|
+
const destPath = join(opts.output, att.filename);
|
|
1576
919
|
try {
|
|
1577
920
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
|
|
1578
921
|
results.push({ filename: att.filename, size: att.size, path: destPath });
|
|
@@ -1743,7 +1086,7 @@ function changelog(parent) {
|
|
|
1743
1086
|
// src/commands/issue/clone.ts
|
|
1744
1087
|
import { unlink } from "fs/promises";
|
|
1745
1088
|
import { tmpdir } from "os";
|
|
1746
|
-
import { join as
|
|
1089
|
+
import { join as join2 } from "path";
|
|
1747
1090
|
var CLONE_FIELDS = [
|
|
1748
1091
|
"summary",
|
|
1749
1092
|
"description",
|
|
@@ -1793,7 +1136,7 @@ function clone(parent) {
|
|
|
1793
1136
|
const tmpFiles = [];
|
|
1794
1137
|
const copied = await Promise.all(
|
|
1795
1138
|
f.attachment.map(async (att) => {
|
|
1796
|
-
const tmpPath =
|
|
1139
|
+
const tmpPath = join2(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
|
|
1797
1140
|
tmpFiles.push(tmpPath);
|
|
1798
1141
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
|
|
1799
1142
|
await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
|
|
@@ -1926,7 +1269,7 @@ function registerCommentCommands(parent) {
|
|
|
1926
1269
|
}
|
|
1927
1270
|
|
|
1928
1271
|
// src/commands/issue/create.ts
|
|
1929
|
-
import { Option as
|
|
1272
|
+
import { Option as Option4 } from "commander";
|
|
1930
1273
|
import { z as z2 } from "zod";
|
|
1931
1274
|
var fieldsSchema = z2.record(z2.unknown());
|
|
1932
1275
|
function create3(parent) {
|
|
@@ -1935,7 +1278,7 @@ function create3(parent) {
|
|
|
1935
1278
|
`Additional fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
|
|
1936
1279
|
jsonShape(fieldsSchema)
|
|
1937
1280
|
).addOption(
|
|
1938
|
-
new
|
|
1281
|
+
new Option4("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
|
|
1939
1282
|
);
|
|
1940
1283
|
textOrFileOption(cmd, "description", { description: "Issue description in wiki markup" });
|
|
1941
1284
|
examples(cmd, [
|
|
@@ -2459,20 +1802,20 @@ function unlink2(parent) {
|
|
|
2459
1802
|
import { z as z4 } from "zod";
|
|
2460
1803
|
|
|
2461
1804
|
// src/utils/multi-value.ts
|
|
2462
|
-
import { InvalidArgumentError as
|
|
1805
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
2463
1806
|
function parseMultiValue(flagName, raw) {
|
|
2464
1807
|
const items = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2465
1808
|
if (items.length === 0) {
|
|
2466
|
-
throw new
|
|
1809
|
+
throw new InvalidArgumentError2(`${flagName} cannot be empty`);
|
|
2467
1810
|
}
|
|
2468
1811
|
const lonePrefix = items.find((s) => s === "+" || s === "-");
|
|
2469
1812
|
if (lonePrefix !== void 0) {
|
|
2470
|
-
throw new
|
|
1813
|
+
throw new InvalidArgumentError2(`${flagName} has an empty value after '${lonePrefix}' prefix`);
|
|
2471
1814
|
}
|
|
2472
1815
|
const prefixed = items.filter((s) => s.startsWith("+") || s.startsWith("-"));
|
|
2473
1816
|
const bare = items.filter((s) => !s.startsWith("+") && !s.startsWith("-"));
|
|
2474
1817
|
if (prefixed.length > 0 && bare.length > 0) {
|
|
2475
|
-
throw new
|
|
1818
|
+
throw new InvalidArgumentError2(
|
|
2476
1819
|
`${flagName} mixes set and mutate syntax. Either all values have +/- prefix, or none do.`
|
|
2477
1820
|
);
|
|
2478
1821
|
}
|
|
@@ -2601,12 +1944,12 @@ function create4(parent) {
|
|
|
2601
1944
|
}
|
|
2602
1945
|
|
|
2603
1946
|
// src/commands/issue/worklog/delete.ts
|
|
2604
|
-
import { Option as
|
|
1947
|
+
import { Option as Option5 } from "commander";
|
|
2605
1948
|
var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
|
|
2606
1949
|
function deleteWorklog(parent) {
|
|
2607
1950
|
const cmd = parent.command("delete").description("Delete a worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2608
1951
|
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
2609
|
-
cmd.addOption(new
|
|
1952
|
+
cmd.addOption(new Option5("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text).option(
|
|
2610
1953
|
"--increase-by <amount>",
|
|
2611
1954
|
'Amount to increase the estimate by; required when --adjust-estimate is "manual"',
|
|
2612
1955
|
text
|
|
@@ -2647,12 +1990,12 @@ function list5(parent) {
|
|
|
2647
1990
|
}
|
|
2648
1991
|
|
|
2649
1992
|
// src/commands/issue/worklog/update.ts
|
|
2650
|
-
import { Option as
|
|
1993
|
+
import { Option as Option6 } from "commander";
|
|
2651
1994
|
var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
|
|
2652
1995
|
function update4(parent) {
|
|
2653
1996
|
const cmd = parent.command("update").description("Update an existing worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2654
1997
|
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
2655
|
-
cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new
|
|
1998
|
+
cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new Option6("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE2)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text);
|
|
2656
1999
|
commentOption(cmd, { description: "Worklog comment" });
|
|
2657
2000
|
examples(cmd, [
|
|
2658
2001
|
'PROJ-123 --worklog-id 12345 --time "1h 30m"',
|
|
@@ -2836,10 +2179,10 @@ function issues2(parent) {
|
|
|
2836
2179
|
}
|
|
2837
2180
|
|
|
2838
2181
|
// src/commands/sprint/list.ts
|
|
2839
|
-
import { Option as
|
|
2182
|
+
import { Option as Option7 } from "commander";
|
|
2840
2183
|
var SPRINT_STATES = ["future", "active", "closed"];
|
|
2841
2184
|
function list7(parent) {
|
|
2842
|
-
const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new
|
|
2185
|
+
const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option7("--state <state>", "Filter by sprint state").choices(SPRINT_STATES));
|
|
2843
2186
|
examples(cmd, ["--board 42", "--board 42 --state active"]);
|
|
2844
2187
|
cmd.action(async (opts) => {
|
|
2845
2188
|
const client = getClient();
|
|
@@ -2852,10 +2195,10 @@ function list7(parent) {
|
|
|
2852
2195
|
}
|
|
2853
2196
|
|
|
2854
2197
|
// src/commands/sprint/update.ts
|
|
2855
|
-
import { Argument as Argument4, Option as
|
|
2198
|
+
import { Argument as Argument4, Option as Option8 } from "commander";
|
|
2856
2199
|
var SPRINT_STATES2 = ["future", "active", "closed"];
|
|
2857
2200
|
function update5(parent) {
|
|
2858
|
-
const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new
|
|
2201
|
+
const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new Option8("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format", text).option("--end-date <date>", "New end date in ISO 8601 format", text).option("--goal <goal>", "New sprint goal", text);
|
|
2859
2202
|
examples(cmd, [
|
|
2860
2203
|
'100 --name "Sprint 10 - Extended"',
|
|
2861
2204
|
"100 --state active",
|
|
@@ -2895,6 +2238,484 @@ function registerSprintCommands(program) {
|
|
|
2895
2238
|
deleteSprint(sprint);
|
|
2896
2239
|
}
|
|
2897
2240
|
|
|
2241
|
+
// src/commands/structure/create.ts
|
|
2242
|
+
function create6(parent) {
|
|
2243
|
+
const cmd = parent.command("create").description("Create a new, empty structure").requiredOption("--name <text>", "Structure name", text).option("--description <text>", "Structure description", text);
|
|
2244
|
+
examples(cmd, ['--name "Release 24.3"', '--name Scratch --description "Throwaway"']);
|
|
2245
|
+
cmd.action(async (opts) => {
|
|
2246
|
+
const client = getClient();
|
|
2247
|
+
output(await client.structure.create({ name: opts.name, description: opts.description }));
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// src/commands/structure/delete.ts
|
|
2252
|
+
function deleteStructure(parent) {
|
|
2253
|
+
const cmd = parent.command("delete").description("Delete a structure and every row in it. The rows go; the underlying Jira issues do not.");
|
|
2254
|
+
subjectArg(cmd, "structureId", { description: "Structure id", parser: positiveInt });
|
|
2255
|
+
examples(cmd, ["119"]);
|
|
2256
|
+
cmd.action(async (structureId) => {
|
|
2257
|
+
const client = getClient();
|
|
2258
|
+
await client.structure.delete({ structureId });
|
|
2259
|
+
output({ deleted: true, structureId });
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// src/utils/structure/forest.ts
|
|
2264
|
+
var FOLDER_TYPE_KEY = "com.almworks.jira.structure:type-folder";
|
|
2265
|
+
function parseForest(forest) {
|
|
2266
|
+
const formula = forest.formula ?? "";
|
|
2267
|
+
if (formula.trim() === "") return [];
|
|
2268
|
+
const rows = [];
|
|
2269
|
+
const lastRowAtDepth = /* @__PURE__ */ new Map();
|
|
2270
|
+
for (const component of formula.split(",")) {
|
|
2271
|
+
const parts = component.split(":");
|
|
2272
|
+
if (parts.length < 3) {
|
|
2273
|
+
throw new CliUsageError(`Malformed forest component "${component}": expected rowId:depth:itemId`);
|
|
2274
|
+
}
|
|
2275
|
+
const [rawRowId, rawDepth, rawItemId] = parts;
|
|
2276
|
+
const rowId = Number(rawRowId);
|
|
2277
|
+
const depth = Number(rawDepth);
|
|
2278
|
+
if (!Number.isInteger(rowId) || !Number.isInteger(depth) || depth < 0) {
|
|
2279
|
+
throw new CliUsageError(`Malformed forest component "${component}": rowId and depth must be integers`);
|
|
2280
|
+
}
|
|
2281
|
+
const slash = rawItemId.indexOf("/");
|
|
2282
|
+
const prefix = slash === -1 ? void 0 : rawItemId.slice(0, slash);
|
|
2283
|
+
const numeric = Number(slash === -1 ? rawItemId : rawItemId.slice(slash + 1));
|
|
2284
|
+
if (!Number.isInteger(numeric)) {
|
|
2285
|
+
throw new CliUsageError(`Malformed forest component "${component}": item id is not numeric`);
|
|
2286
|
+
}
|
|
2287
|
+
const itemType = prefix === void 0 ? "issue" : forest.itemTypes?.[prefix] ?? `unknown:${prefix}`;
|
|
2288
|
+
rows.push({
|
|
2289
|
+
rowId,
|
|
2290
|
+
depth,
|
|
2291
|
+
itemId: rawItemId,
|
|
2292
|
+
itemType,
|
|
2293
|
+
itemNumericId: numeric,
|
|
2294
|
+
parentRowId: depth === 0 ? 0 : lastRowAtDepth.get(depth - 1) ?? 0,
|
|
2295
|
+
isFolder: itemType === FOLDER_TYPE_KEY
|
|
2296
|
+
});
|
|
2297
|
+
lastRowAtDepth.set(depth, rowId);
|
|
2298
|
+
for (const seen of [...lastRowAtDepth.keys()]) {
|
|
2299
|
+
if (seen > depth) lastRowAtDepth.delete(seen);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
return rows;
|
|
2303
|
+
}
|
|
2304
|
+
function childrenOf(rows, parentRowId) {
|
|
2305
|
+
return rows.filter((r) => r.parentRowId === parentRowId);
|
|
2306
|
+
}
|
|
2307
|
+
function descendantsOf(rows, parentRowId) {
|
|
2308
|
+
const start = rows.findIndex((r) => r.rowId === parentRowId);
|
|
2309
|
+
if (start === -1) return [];
|
|
2310
|
+
const parentDepth = rows[start].depth;
|
|
2311
|
+
const out = [];
|
|
2312
|
+
for (let i = start + 1; i < rows.length && rows[i].depth > parentDepth; i += 1) out.push(rows[i]);
|
|
2313
|
+
return out;
|
|
2314
|
+
}
|
|
2315
|
+
function withinDepth(rows, parentRowId, maxDepth) {
|
|
2316
|
+
const base = parentRowId === 0 ? -1 : rows.find((r) => r.rowId === parentRowId)?.depth ?? -1;
|
|
2317
|
+
const scope = parentRowId === 0 ? rows : descendantsOf(rows, parentRowId);
|
|
2318
|
+
return scope.filter((r) => r.depth - base <= maxDepth);
|
|
2319
|
+
}
|
|
2320
|
+
function addSpec(itemId, placeholderRowId = -100) {
|
|
2321
|
+
if (placeholderRowId >= 0) {
|
|
2322
|
+
throw new CliUsageError(`Placeholder row id must be negative, got ${placeholderRowId}`);
|
|
2323
|
+
}
|
|
2324
|
+
return `${placeholderRowId}:0:${itemId}`;
|
|
2325
|
+
}
|
|
2326
|
+
function assertRowExists(rows, rowId, structureId, what = "Row") {
|
|
2327
|
+
if (!rows.some((r) => r.rowId === rowId)) {
|
|
2328
|
+
throw new CliNotFoundError(`${what} ${rowId} is not in structure ${structureId}`);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// src/utils/structure/resolve.ts
|
|
2333
|
+
var VALUE_BATCH_SIZE = 500;
|
|
2334
|
+
async function resolveStructureId(client, idOrName) {
|
|
2335
|
+
if (/^\d+$/.test(idOrName)) return Number(idOrName);
|
|
2336
|
+
const { structures } = await client.structure.list();
|
|
2337
|
+
const wanted = idOrName.trim().toLowerCase();
|
|
2338
|
+
const matches = structures.filter((s) => s.name?.trim().toLowerCase() === wanted);
|
|
2339
|
+
if (matches.length === 1) return matches[0].id;
|
|
2340
|
+
if (matches.length === 0) {
|
|
2341
|
+
throw new CliUsageError(
|
|
2342
|
+
`No structure named "${idOrName}"`,
|
|
2343
|
+
"Pass a structure id, or one of the names in detail.available.",
|
|
2344
|
+
{ available: structures.map((s) => ({ id: s.id, name: s.name })).slice(0, 50) }
|
|
2345
|
+
);
|
|
2346
|
+
}
|
|
2347
|
+
throw new CliUsageError(
|
|
2348
|
+
`"${idOrName}" matches ${matches.length} structures`,
|
|
2349
|
+
"Names are not unique here; pass the numeric id of the one you meant.",
|
|
2350
|
+
{ matches: matches.map((s) => ({ id: s.id, name: s.name })) }
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
async function resolveRowNames(client, structureId, rowIds) {
|
|
2354
|
+
const out = /* @__PURE__ */ new Map();
|
|
2355
|
+
if (rowIds.length === 0) return out;
|
|
2356
|
+
for (let i = 0; i < rowIds.length; i += VALUE_BATCH_SIZE) {
|
|
2357
|
+
const batch = rowIds.slice(i, i + VALUE_BATCH_SIZE);
|
|
2358
|
+
const res = await client.structure.getValues({
|
|
2359
|
+
requests: [
|
|
2360
|
+
{
|
|
2361
|
+
forestSpec: { structureId },
|
|
2362
|
+
rows: batch,
|
|
2363
|
+
// `format` is required; without it the API returns an empty `data` array.
|
|
2364
|
+
attributes: [
|
|
2365
|
+
{ id: "summary", format: "text" },
|
|
2366
|
+
{ id: "key", format: "text" }
|
|
2367
|
+
]
|
|
2368
|
+
}
|
|
2369
|
+
]
|
|
2370
|
+
});
|
|
2371
|
+
const data = res.responses?.[0]?.data ?? [];
|
|
2372
|
+
const summaries = data.find((d) => d.attribute.id === "summary")?.values ?? [];
|
|
2373
|
+
const keys = data.find((d) => d.attribute.id === "key")?.values ?? [];
|
|
2374
|
+
batch.forEach((rowId, idx) => {
|
|
2375
|
+
out.set(rowId, { name: summaries[idx] ?? null, key: keys[idx] ?? null });
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
return out;
|
|
2379
|
+
}
|
|
2380
|
+
async function withNames(client, structureId, rows) {
|
|
2381
|
+
const names = await resolveRowNames(
|
|
2382
|
+
client,
|
|
2383
|
+
structureId,
|
|
2384
|
+
rows.map((r) => r.rowId)
|
|
2385
|
+
);
|
|
2386
|
+
return rows.map((r) => ({
|
|
2387
|
+
rowId: r.rowId,
|
|
2388
|
+
depth: r.depth,
|
|
2389
|
+
parentRowId: r.parentRowId,
|
|
2390
|
+
type: r.isFolder ? "folder" : r.itemType === "issue" ? "issue" : r.itemType,
|
|
2391
|
+
name: names.get(r.rowId)?.name ?? null,
|
|
2392
|
+
key: names.get(r.rowId)?.key ?? null,
|
|
2393
|
+
itemId: r.itemId
|
|
2394
|
+
}));
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
// src/commands/structure/folder/create.ts
|
|
2398
|
+
function create7(parent) {
|
|
2399
|
+
const cmd = parent.command("create").description("Create a folder in a structure");
|
|
2400
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2401
|
+
cmd.requiredOption("--name <text>", "Folder name", text);
|
|
2402
|
+
cmd.option("--under <rowId>", "Parent folder row id (0 = top level)", integer, 0);
|
|
2403
|
+
examples(cmd, ["--structure 42 --name Backlog", '--structure 42 --name "Phase Two" --under 1001']);
|
|
2404
|
+
cmd.action(async (opts) => {
|
|
2405
|
+
const client = getClient();
|
|
2406
|
+
const structureId = await resolveStructureId(client, opts.structure);
|
|
2407
|
+
const forest = await client.structure.getForest({ structureId });
|
|
2408
|
+
if (opts.under !== 0) {
|
|
2409
|
+
assertRowExists(parseForest(forest), opts.under, structureId, "Target parent row");
|
|
2410
|
+
}
|
|
2411
|
+
const result = await client.structure.createFolder({
|
|
2412
|
+
structureId,
|
|
2413
|
+
version: forest.version,
|
|
2414
|
+
name: opts.name,
|
|
2415
|
+
under: opts.under
|
|
2416
|
+
});
|
|
2417
|
+
output({
|
|
2418
|
+
structureId,
|
|
2419
|
+
itemId: result.itemId,
|
|
2420
|
+
rowId: result.newRowIds?.[0] ?? null,
|
|
2421
|
+
name: opts.name,
|
|
2422
|
+
under: opts.under
|
|
2423
|
+
});
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// src/commands/structure/folder/get.ts
|
|
2428
|
+
function get7(parent) {
|
|
2429
|
+
const cmd = parent.command("get").description("Get one folder row, with its path and child counts");
|
|
2430
|
+
subjectArg(cmd, "rowId", { description: "Folder row id", parser: positiveInt });
|
|
2431
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2432
|
+
examples(cmd, ['1001 --structure "My Structure"', "1001 --structure 42"]);
|
|
2433
|
+
cmd.action(async (rowId, opts) => {
|
|
2434
|
+
const client = getClient();
|
|
2435
|
+
const structureId = await resolveStructureId(client, opts.structure);
|
|
2436
|
+
const rows = parseForest(await client.structure.getForest({ structureId }));
|
|
2437
|
+
const row = rows.find((r) => r.rowId === rowId);
|
|
2438
|
+
if (!row) throw new CliNotFoundError(`Row ${rowId} is not in structure ${structureId}`);
|
|
2439
|
+
const ancestry = [];
|
|
2440
|
+
for (let cur = row; cur.parentRowId !== 0; ) {
|
|
2441
|
+
const next = rows.find((r) => r.rowId === cur.parentRowId);
|
|
2442
|
+
if (!next) break;
|
|
2443
|
+
ancestry.unshift(next);
|
|
2444
|
+
cur = next;
|
|
2445
|
+
}
|
|
2446
|
+
const children = childrenOf(rows, rowId);
|
|
2447
|
+
const [self, ...path] = await withNames(client, structureId, [row, ...ancestry]);
|
|
2448
|
+
output({
|
|
2449
|
+
structureId,
|
|
2450
|
+
...self,
|
|
2451
|
+
path: path.map((p) => p.name),
|
|
2452
|
+
childCount: children.length,
|
|
2453
|
+
childFolderCount: children.filter((c) => c.isFolder).length,
|
|
2454
|
+
descendantCount: descendantsOf(rows, rowId).length
|
|
2455
|
+
});
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
// src/commands/structure/folder/list.ts
|
|
2460
|
+
function list8(parent) {
|
|
2461
|
+
const cmd = parent.command("list").description("List folders in a structure");
|
|
2462
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2463
|
+
cmd.option("--parent <rowId>", "Only look under this folder row (default: top level)", integer, 0);
|
|
2464
|
+
cmd.option("--depth <n>", "How many levels below the parent to include", integer, 1);
|
|
2465
|
+
cmd.option("--query <text>", "Only folders whose name contains this text, at any depth", text);
|
|
2466
|
+
paginationOptions(cmd, { defaultLimit: 25 });
|
|
2467
|
+
cmd.option("--all", "Collect every match, not just one page");
|
|
2468
|
+
examples(cmd, [
|
|
2469
|
+
'--structure "My Structure"',
|
|
2470
|
+
'--structure "My Structure" --query billing',
|
|
2471
|
+
"--structure 42 --parent 1001 --depth 2"
|
|
2472
|
+
]);
|
|
2473
|
+
cmd.action(
|
|
2474
|
+
async (opts) => {
|
|
2475
|
+
const client = getClient();
|
|
2476
|
+
const structureId = await resolveStructureId(client, opts.structure);
|
|
2477
|
+
const forest = await client.structure.getForest({ structureId });
|
|
2478
|
+
const rows = parseForest(forest);
|
|
2479
|
+
const scoped = opts.query ? opts.parent === 0 ? rows : descendantsOf(rows, opts.parent) : withinDepth(rows, opts.parent, opts.depth);
|
|
2480
|
+
const folders = scoped.filter((r) => r.isFolder);
|
|
2481
|
+
let named = await withNames(client, structureId, folders);
|
|
2482
|
+
if (opts.query) {
|
|
2483
|
+
const needle = opts.query.toLowerCase();
|
|
2484
|
+
named = named.filter((r) => (r.name ?? "").toLowerCase().includes(needle));
|
|
2485
|
+
}
|
|
2486
|
+
const start = opts.start ?? 0;
|
|
2487
|
+
const page = opts.all ? named.slice(start) : named.slice(start, start + opts.limit);
|
|
2488
|
+
output({ structureId, total: named.length, start, count: page.length, folders: page });
|
|
2489
|
+
}
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// src/commands/structure/folder/index.ts
|
|
2494
|
+
function registerFolderCommands(structure) {
|
|
2495
|
+
const folder = structure.command("folder").description("Find and create folders inside a structure");
|
|
2496
|
+
examples(folder, ['list --structure "My Structure" --query billing']);
|
|
2497
|
+
list8(folder);
|
|
2498
|
+
get7(folder);
|
|
2499
|
+
create7(folder);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// src/commands/structure/get.ts
|
|
2503
|
+
function get8(parent) {
|
|
2504
|
+
const cmd = parent.command("get").description("Get one structure by id");
|
|
2505
|
+
subjectArg(cmd, "structureId", { description: "Structure id", parser: positiveInt });
|
|
2506
|
+
cmd.option("--permissions", "Include the structure\u2019s permission rules");
|
|
2507
|
+
examples(cmd, ["1", "1 --permissions"]);
|
|
2508
|
+
cmd.action(async (structureId, opts) => {
|
|
2509
|
+
const client = getClient();
|
|
2510
|
+
output(await client.structure.get({ structureId, withPermissions: opts.permissions }));
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/commands/structure/list.ts
|
|
2515
|
+
function list9(parent) {
|
|
2516
|
+
const cmd = parent.command("list").description("List every structure visible to you");
|
|
2517
|
+
cmd.option("--permissions", "Include each structure\u2019s permission rules");
|
|
2518
|
+
paginationOptions(cmd, { defaultLimit: 25 });
|
|
2519
|
+
cmd.option("--all", "Collect every match, not just one page");
|
|
2520
|
+
examples(cmd, ["", "--limit 50", "--all --permissions"]);
|
|
2521
|
+
cmd.action(async (opts) => {
|
|
2522
|
+
if (opts.all && process.argv.includes("--limit")) {
|
|
2523
|
+
const { CliUsageError: CliUsageError2 } = await import("./dist-JXPWCMQP.js");
|
|
2524
|
+
throw new CliUsageError2("--all and --limit ask for opposite things; pass one or the other");
|
|
2525
|
+
}
|
|
2526
|
+
const client = getClient();
|
|
2527
|
+
const { structures } = await client.structure.list({ withPermissions: opts.permissions });
|
|
2528
|
+
const start = opts.start ?? 0;
|
|
2529
|
+
const page = opts.all ? structures.slice(start) : structures.slice(start, start + opts.limit);
|
|
2530
|
+
output({ total: structures.length, start, count: page.length, structures: page });
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// src/commands/structure/row/add.ts
|
|
2535
|
+
function add(parent) {
|
|
2536
|
+
const cmd = parent.command("add").description("Add an issue to a structure, under a folder");
|
|
2537
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2538
|
+
cmd.requiredOption("--issue <key>", "Issue key to place, e.g. PROJ-123", text);
|
|
2539
|
+
cmd.option("--under <rowId>", "Parent folder row id (0 = top level)", integer, 0);
|
|
2540
|
+
examples(cmd, ["--structure 42 --issue PROJ-123 --under 1001", '--structure "My Structure" --issue PROJ-123']);
|
|
2541
|
+
cmd.action(async (opts) => {
|
|
2542
|
+
const client = getClient();
|
|
2543
|
+
const structureId = await resolveStructureId(client, opts.structure);
|
|
2544
|
+
const issue = await client.issues.get({ issueKeyOrId: opts.issue, fields: ["summary"] });
|
|
2545
|
+
const issueId = Number(issue.id);
|
|
2546
|
+
const forest = await client.structure.getForest({ structureId });
|
|
2547
|
+
const rows = parseForest(forest);
|
|
2548
|
+
if (opts.under !== 0) assertRowExists(rows, opts.under, structureId, "Target parent row");
|
|
2549
|
+
const already = childrenOf(rows, opts.under).find((r) => r.itemType === "issue" && r.itemNumericId === issueId);
|
|
2550
|
+
const elsewhere = rows.filter((r) => r.itemType === "issue" && r.itemNumericId === issueId && r.parentRowId !== opts.under).map((r) => ({ rowId: r.rowId, parentRowId: r.parentRowId }));
|
|
2551
|
+
if (already) {
|
|
2552
|
+
output({
|
|
2553
|
+
structureId,
|
|
2554
|
+
issue: issue.key,
|
|
2555
|
+
rowId: already.rowId,
|
|
2556
|
+
under: opts.under,
|
|
2557
|
+
added: false,
|
|
2558
|
+
reason: "already under this parent",
|
|
2559
|
+
alsoAppearsAt: elsewhere
|
|
2560
|
+
});
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
const result = await client.structure.updateForest({
|
|
2564
|
+
structureId,
|
|
2565
|
+
version: forest.version,
|
|
2566
|
+
actions: [{ action: "add", under: opts.under, after: 0, before: 0, forest: addSpec(issueId) }]
|
|
2567
|
+
});
|
|
2568
|
+
output({
|
|
2569
|
+
structureId,
|
|
2570
|
+
issue: issue.key,
|
|
2571
|
+
rowId: result.newRowIds?.[0] ?? null,
|
|
2572
|
+
under: opts.under,
|
|
2573
|
+
added: true,
|
|
2574
|
+
alsoAppearsAt: elsewhere
|
|
2575
|
+
});
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// src/commands/structure/row/list.ts
|
|
2580
|
+
function list10(parent) {
|
|
2581
|
+
const cmd = parent.command("list").description("List rows (issues and folders) under a parent row");
|
|
2582
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2583
|
+
cmd.option("--parent <rowId>", "Parent row id (0 = top level)", integer, 0);
|
|
2584
|
+
cmd.option("--depth <n>", "How many levels below the parent to include", integer, 1);
|
|
2585
|
+
paginationOptions(cmd, { defaultLimit: 25 });
|
|
2586
|
+
cmd.option("--all", "Collect every match, not just one page");
|
|
2587
|
+
examples(cmd, ["--structure 42", '--structure "My Structure" --parent 1001 --depth 2']);
|
|
2588
|
+
cmd.action(
|
|
2589
|
+
async (opts) => {
|
|
2590
|
+
const client = getClient();
|
|
2591
|
+
const structureId = await resolveStructureId(client, opts.structure);
|
|
2592
|
+
const rows = parseForest(await client.structure.getForest({ structureId }));
|
|
2593
|
+
const scoped = withinDepth(rows, opts.parent, opts.depth);
|
|
2594
|
+
const start = opts.start ?? 0;
|
|
2595
|
+
const page = opts.all ? scoped.slice(start) : scoped.slice(start, start + opts.limit);
|
|
2596
|
+
output({
|
|
2597
|
+
structureId,
|
|
2598
|
+
total: scoped.length,
|
|
2599
|
+
start,
|
|
2600
|
+
count: page.length,
|
|
2601
|
+
rows: await withNames(client, structureId, page)
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
// src/commands/structure/row/move.ts
|
|
2608
|
+
function move(parent) {
|
|
2609
|
+
const cmd = parent.command("move").description("Move a row to a different parent");
|
|
2610
|
+
subjectArg(cmd, "rowId", { description: "Row id to move (not the issue id)", parser: positiveInt });
|
|
2611
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2612
|
+
cmd.requiredOption("--under <rowId>", "New parent row id (0 = top level)", integer);
|
|
2613
|
+
examples(cmd, ["1002 --structure 42 --under 1001", '1002 --structure "My Structure" --under 0']);
|
|
2614
|
+
cmd.action(async (rowId, opts) => {
|
|
2615
|
+
const client = getClient();
|
|
2616
|
+
const structureId = await resolveStructureId(client, opts.structure);
|
|
2617
|
+
const forest = await client.structure.getForest({ structureId });
|
|
2618
|
+
const rows = parseForest(forest);
|
|
2619
|
+
assertRowExists(rows, rowId, structureId);
|
|
2620
|
+
if (opts.under !== 0) assertRowExists(rows, opts.under, structureId, "Target parent row");
|
|
2621
|
+
const row = rows.find((r) => r.rowId === rowId);
|
|
2622
|
+
const result = await client.structure.updateForest({
|
|
2623
|
+
structureId,
|
|
2624
|
+
version: forest.version,
|
|
2625
|
+
actions: [{ action: "move", rowId, under: opts.under, after: 0, before: 0 }]
|
|
2626
|
+
});
|
|
2627
|
+
output({ structureId, rowId, from: row.parentRowId, under: opts.under, moved: result.successfulActions > 0 });
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// src/commands/structure/row/remove.ts
|
|
2632
|
+
function remove(parent) {
|
|
2633
|
+
const cmd = parent.command("remove").description("Remove a row from a structure. The row goes; the underlying Jira issue does not.");
|
|
2634
|
+
subjectArg(cmd, "rowId", { description: "Row id to remove (not the issue id)", parser: positiveInt });
|
|
2635
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2636
|
+
examples(cmd, ["1002 --structure 42", '1002 --structure "My Structure"']);
|
|
2637
|
+
cmd.action(async (rowId, opts) => {
|
|
2638
|
+
const client = getClient();
|
|
2639
|
+
const structureId = await resolveStructureId(client, opts.structure);
|
|
2640
|
+
const forest = await client.structure.getForest({ structureId });
|
|
2641
|
+
const rows = parseForest(forest);
|
|
2642
|
+
assertRowExists(rows, rowId, structureId);
|
|
2643
|
+
const removedDescendants = descendantsOf(rows, rowId).length;
|
|
2644
|
+
const result = await client.structure.updateForest({
|
|
2645
|
+
structureId,
|
|
2646
|
+
version: forest.version,
|
|
2647
|
+
actions: [{ action: "remove", rowId }]
|
|
2648
|
+
});
|
|
2649
|
+
output({ structureId, rowId, removed: result.successfulActions > 0, removedDescendants });
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
// src/commands/structure/row/index.ts
|
|
2654
|
+
function registerRowCommands(structure) {
|
|
2655
|
+
const row = structure.command("row").description("Place, move and remove rows in a structure. A row is one appearance of an issue, not the issue.");
|
|
2656
|
+
examples(row, ["add --structure 42 --issue PROJ-123 --under 1001"]);
|
|
2657
|
+
list10(row);
|
|
2658
|
+
add(row);
|
|
2659
|
+
move(row);
|
|
2660
|
+
remove(row);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
// src/commands/structure/search.ts
|
|
2664
|
+
function search3(parent) {
|
|
2665
|
+
const cmd = parent.command("search").description("Find issues by their place in a structure, using S-JQL");
|
|
2666
|
+
subjectArg(cmd, "sjql", {
|
|
2667
|
+
description: `S-JQL query, e.g. "child of folder('Billing')"`,
|
|
2668
|
+
parser: nonEmpty
|
|
2669
|
+
});
|
|
2670
|
+
scopeIdOption(cmd, "structure", { mandatory: true });
|
|
2671
|
+
cmd.option("--fields <fields>", "Comma-separated fields to return", text);
|
|
2672
|
+
paginationOptions(cmd, { defaultLimit: 25, maxLimit: 50 });
|
|
2673
|
+
examples(cmd, [
|
|
2674
|
+
`"child of folder('Billing')" --structure "My Structure"`,
|
|
2675
|
+
`"descendants of folder('Billing')" --structure 42 --limit 50`
|
|
2676
|
+
]);
|
|
2677
|
+
cmd.action(async (sjql, opts) => {
|
|
2678
|
+
for (const [name, value] of [
|
|
2679
|
+
["--structure", opts.structure],
|
|
2680
|
+
["<sjql>", sjql]
|
|
2681
|
+
]) {
|
|
2682
|
+
if (value.includes('"')) {
|
|
2683
|
+
throw new CliUsageError(
|
|
2684
|
+
`${name} must not contain a double quote \u2014 S-JQL uses single quotes inside, e.g. "child of folder('Billing')"`
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
const client = getClient();
|
|
2689
|
+
const jql = `issue in structure("${opts.structure}", "${sjql}")`;
|
|
2690
|
+
const res = await client.issues.search({
|
|
2691
|
+
jql,
|
|
2692
|
+
startAt: opts.start ?? 0,
|
|
2693
|
+
maxResults: opts.limit,
|
|
2694
|
+
fields: opts.fields
|
|
2695
|
+
});
|
|
2696
|
+
output({
|
|
2697
|
+
jql,
|
|
2698
|
+
total: res.total,
|
|
2699
|
+
startAt: res.startAt,
|
|
2700
|
+
count: res.issues?.length ?? 0,
|
|
2701
|
+
issues: (res.issues ?? []).map(transformIssue)
|
|
2702
|
+
});
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
// src/commands/structure/index.ts
|
|
2707
|
+
function registerStructureCommands(program) {
|
|
2708
|
+
const structure = program.command("structure").description("Structure plugin \u2014 browse and edit the hierarchies issues are filed into");
|
|
2709
|
+
examples(structure, ["list", `search "child of folder('Billing')" --structure "My Structure"`]);
|
|
2710
|
+
list9(structure);
|
|
2711
|
+
get8(structure);
|
|
2712
|
+
create6(structure);
|
|
2713
|
+
deleteStructure(structure);
|
|
2714
|
+
search3(structure);
|
|
2715
|
+
registerFolderCommands(structure);
|
|
2716
|
+
registerRowCommands(structure);
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2898
2719
|
// src/commands/token/client.ts
|
|
2899
2720
|
import { JiraClient as JiraClient2 } from "jira-data-center-client";
|
|
2900
2721
|
function getTokenClient(options = {}) {
|
|
@@ -2914,7 +2735,7 @@ function getTokenClient(options = {}) {
|
|
|
2914
2735
|
}
|
|
2915
2736
|
|
|
2916
2737
|
// src/commands/token/create.ts
|
|
2917
|
-
function
|
|
2738
|
+
function create8(parent) {
|
|
2918
2739
|
const cmd = parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name", text).option(
|
|
2919
2740
|
"--expiration-duration <days>",
|
|
2920
2741
|
"Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
|
|
@@ -2941,7 +2762,7 @@ function create6(parent) {
|
|
|
2941
2762
|
}
|
|
2942
2763
|
|
|
2943
2764
|
// src/commands/token/list.ts
|
|
2944
|
-
function
|
|
2765
|
+
function list11(parent) {
|
|
2945
2766
|
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", text).option(
|
|
2946
2767
|
"--basic-password <p>",
|
|
2947
2768
|
"Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)",
|
|
@@ -2984,13 +2805,13 @@ function registerTokenCommands(program) {
|
|
|
2984
2805
|
"after",
|
|
2985
2806
|
"\nAuth:\n Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD\n (or --basic-username / --basic-password on any subcommand)."
|
|
2986
2807
|
);
|
|
2987
|
-
|
|
2988
|
-
|
|
2808
|
+
create8(token);
|
|
2809
|
+
list11(token);
|
|
2989
2810
|
revoke(token);
|
|
2990
2811
|
}
|
|
2991
2812
|
|
|
2992
2813
|
// src/commands/user/get.ts
|
|
2993
|
-
function
|
|
2814
|
+
function get9(parent) {
|
|
2994
2815
|
const cmd = parent.command("get").description("Get a user profile by exact username or key").argument("<username>", "Username or user key", nonEmpty).option("--by-key", "Treat the positional argument as a user key instead of a username");
|
|
2995
2816
|
examples(cmd, ["jsmith", "JIRAUSER10100 --by-key"]);
|
|
2996
2817
|
cmd.action(async (identifier, opts) => {
|
|
@@ -3012,7 +2833,7 @@ function me(parent) {
|
|
|
3012
2833
|
}
|
|
3013
2834
|
|
|
3014
2835
|
// src/commands/user/search.ts
|
|
3015
|
-
function
|
|
2836
|
+
function search4(parent) {
|
|
3016
2837
|
const cmd = parent.command("search").description("Search users by partial username, display name or email").argument("<query>", "Search query", nonEmpty).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);
|
|
3017
2838
|
examples(cmd, ["Smith", '"John Smith"', "jsmith@example.com --limit 5"]);
|
|
3018
2839
|
cmd.action(async (query, opts) => {
|
|
@@ -3033,8 +2854,8 @@ function registerUserCommands(program) {
|
|
|
3033
2854
|
const user = program.command("user").description("User operations");
|
|
3034
2855
|
examples(user, ["me", "get jsmith", "search Smith"]);
|
|
3035
2856
|
me(user);
|
|
3036
|
-
|
|
3037
|
-
|
|
2857
|
+
get9(user);
|
|
2858
|
+
search4(user);
|
|
3038
2859
|
}
|
|
3039
2860
|
|
|
3040
2861
|
// src/utils/xray/guards.ts
|
|
@@ -3050,7 +2871,7 @@ function assertAtLeastOne(opts, keys) {
|
|
|
3050
2871
|
}
|
|
3051
2872
|
|
|
3052
2873
|
// src/commands/xray/execution/add.ts
|
|
3053
|
-
function
|
|
2874
|
+
function add2(parent) {
|
|
3054
2875
|
const cmd = parent.command("add").description("Add tests to a Test Execution, directly (--test) and/or by expanding Test Sets (--set)").argument("<execKey>", "Test Execution issue key (e.g. PROJ-200)", issueKey).option("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList).option(
|
|
3055
2876
|
"--set <keys>",
|
|
3056
2877
|
"Comma-separated Test Set keys; each set's current tests are added to the execution (e.g. PROJ-300)",
|
|
@@ -3068,8 +2889,8 @@ function add(parent) {
|
|
|
3068
2889
|
const keys = new Set(opts.test ?? []);
|
|
3069
2890
|
if (opts.set) {
|
|
3070
2891
|
const lists = await Promise.all(opts.set.map((setKey) => client.testSets.listTests({ setKey })));
|
|
3071
|
-
for (const
|
|
3072
|
-
const tests = Array.isArray(
|
|
2892
|
+
for (const list24 of lists) {
|
|
2893
|
+
const tests = Array.isArray(list24) ? list24 : list24.tests ?? [];
|
|
3073
2894
|
for (const t of tests) {
|
|
3074
2895
|
if (t.key) keys.add(String(t.key));
|
|
3075
2896
|
}
|
|
@@ -3115,7 +2936,7 @@ function deleteXrayIssue(client, key) {
|
|
|
3115
2936
|
}
|
|
3116
2937
|
|
|
3117
2938
|
// src/commands/xray/execution/create.ts
|
|
3118
|
-
function
|
|
2939
|
+
function create9(parent) {
|
|
3119
2940
|
const cmd = parent.command("create").description("Create a new Xray Test Execution issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test Execution summary (title)", text);
|
|
3120
2941
|
textOrFileOption(cmd, "description", { description: "Test Execution description" });
|
|
3121
2942
|
examples(cmd, [
|
|
@@ -3147,7 +2968,7 @@ function deleteExecution(parent) {
|
|
|
3147
2968
|
}
|
|
3148
2969
|
|
|
3149
2970
|
// src/commands/xray/execution/list.ts
|
|
3150
|
-
function
|
|
2971
|
+
function list12(parent) {
|
|
3151
2972
|
const cmd = parent.command("list").description(
|
|
3152
2973
|
"List Test Executions that contain a given test (inverse view). For the tests inside an execution, use: xray test list --execution <key>."
|
|
3153
2974
|
).requiredOption("--test <key>", "Test issue key whose executions to list (e.g. PROJ-584)", issueKey);
|
|
@@ -3160,7 +2981,7 @@ function list9(parent) {
|
|
|
3160
2981
|
}
|
|
3161
2982
|
|
|
3162
2983
|
// src/commands/xray/execution/remove.ts
|
|
3163
|
-
function
|
|
2984
|
+
function remove2(parent) {
|
|
3164
2985
|
const cmd = parent.command("remove").description("Remove tests from a Test Execution").argument("<execKey>", "Test Execution issue key (e.g. PROJ-200)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
|
|
3165
2986
|
examples(cmd, ["PROJ-200 --test PROJ-1", "PROJ-200 --test PROJ-1,PROJ-2"]);
|
|
3166
2987
|
cmd.action(async (execKey, opts) => {
|
|
@@ -3193,17 +3014,17 @@ function registerExecutionCommands(xray) {
|
|
|
3193
3014
|
"add PROJ-200 --test PROJ-1,PROJ-2",
|
|
3194
3015
|
"list --test PROJ-584"
|
|
3195
3016
|
]);
|
|
3196
|
-
|
|
3017
|
+
create9(execution2);
|
|
3197
3018
|
update6(execution2);
|
|
3198
3019
|
deleteExecution(execution2);
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3020
|
+
add2(execution2);
|
|
3021
|
+
remove2(execution2);
|
|
3022
|
+
list12(execution2);
|
|
3202
3023
|
}
|
|
3203
3024
|
|
|
3204
3025
|
// src/commands/xray/export/feature.ts
|
|
3205
|
-
import { writeFileSync
|
|
3206
|
-
import { basename, join as
|
|
3026
|
+
import { writeFileSync } from "fs";
|
|
3027
|
+
import { basename, join as join3 } from "path";
|
|
3207
3028
|
function feature(parent) {
|
|
3208
3029
|
const cmd = parent.command("feature").description("Export Gherkin .feature files for the given test keys").requiredOption("--test <keys>", "Comma-separated Test, Set, Plan, or Execution issue keys", keyList).option("--output <path>", "Write the exported file to this path (default: ./<filename> from response)", text).option("--zip", "Request a zip archive instead of a single .feature file");
|
|
3209
3030
|
examples(cmd, ["--test PROJ-584 --output ai-584.feature"]);
|
|
@@ -3213,8 +3034,8 @@ function feature(parent) {
|
|
|
3213
3034
|
keys: opts.test,
|
|
3214
3035
|
zip: opts.zip
|
|
3215
3036
|
});
|
|
3216
|
-
const writePath = opts.output ??
|
|
3217
|
-
|
|
3037
|
+
const writePath = opts.output ?? join3(".", basename(filename));
|
|
3038
|
+
writeFileSync(writePath, data);
|
|
3218
3039
|
output({ written: writePath, bytes: data.length });
|
|
3219
3040
|
});
|
|
3220
3041
|
}
|
|
@@ -3226,7 +3047,7 @@ function registerExportCommands(xray) {
|
|
|
3226
3047
|
}
|
|
3227
3048
|
|
|
3228
3049
|
// src/commands/xray/field/list.ts
|
|
3229
|
-
function
|
|
3050
|
+
function list13(parent) {
|
|
3230
3051
|
const cmd = parent.command("list").description("List Xray custom fields available on this instance");
|
|
3231
3052
|
examples(cmd, [""]);
|
|
3232
3053
|
cmd.action(async () => {
|
|
@@ -3239,11 +3060,11 @@ function list10(parent) {
|
|
|
3239
3060
|
function registerFieldCommands2(xray) {
|
|
3240
3061
|
const field = xray.command("field").description("Xray field discovery");
|
|
3241
3062
|
examples(field, ["list"]);
|
|
3242
|
-
|
|
3063
|
+
list13(field);
|
|
3243
3064
|
}
|
|
3244
3065
|
|
|
3245
3066
|
// src/commands/xray/folder/add.ts
|
|
3246
|
-
function
|
|
3067
|
+
function add3(parent) {
|
|
3247
3068
|
const cmd = parent.command("add").description("Add tests to a folder in the test repository").argument("<folderId>", "Folder id (from folder list)", positiveInt).requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList);
|
|
3248
3069
|
examples(cmd, ["818 --project PROJ --test PROJ-1", "818 --project PROJ --test PROJ-1,PROJ-2,PROJ-3"]);
|
|
3249
3070
|
cmd.action(async (folderId, opts) => {
|
|
@@ -3258,7 +3079,7 @@ function add2(parent) {
|
|
|
3258
3079
|
}
|
|
3259
3080
|
|
|
3260
3081
|
// src/commands/xray/folder/create.ts
|
|
3261
|
-
function
|
|
3082
|
+
function create10(parent) {
|
|
3262
3083
|
const cmd = parent.command("create").description("Create a new folder in the test repository").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).option("--parent-id <id>", "Parent folder id (-1 = root)", integer, -1).requiredOption("--name <text>", "Folder name", text);
|
|
3263
3084
|
examples(cmd, [
|
|
3264
3085
|
"--project PROJ --name Smoke",
|
|
@@ -3288,7 +3109,7 @@ function deleteFolder(parent) {
|
|
|
3288
3109
|
}
|
|
3289
3110
|
|
|
3290
3111
|
// src/commands/xray/folder/list.ts
|
|
3291
|
-
function
|
|
3112
|
+
function list14(parent) {
|
|
3292
3113
|
const cmd = parent.command("list").description("List the test repository folder tree for a project").requiredOption("--project <key>", "Project key (e.g. PROJ)", text);
|
|
3293
3114
|
examples(cmd, ["--project PROJ", "--project PROJ"]);
|
|
3294
3115
|
cmd.action(async (opts) => {
|
|
@@ -3299,7 +3120,7 @@ function list11(parent) {
|
|
|
3299
3120
|
}
|
|
3300
3121
|
|
|
3301
3122
|
// src/commands/xray/folder/remove.ts
|
|
3302
|
-
function
|
|
3123
|
+
function remove3(parent) {
|
|
3303
3124
|
const cmd = parent.command("remove").description("Remove tests from a folder in the test repository").argument("<folderId>", "Folder id (from folder list)", positiveInt).requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
|
|
3304
3125
|
examples(cmd, ["818 --project PROJ --test PROJ-1", "818 --project PROJ --test PROJ-1,PROJ-2"]);
|
|
3305
3126
|
cmd.action(async (folderId, opts) => {
|
|
@@ -3337,28 +3158,28 @@ function update7(parent) {
|
|
|
3337
3158
|
}
|
|
3338
3159
|
|
|
3339
3160
|
// src/commands/xray/folder/index.ts
|
|
3340
|
-
function
|
|
3161
|
+
function registerFolderCommands2(xray) {
|
|
3341
3162
|
const folder = xray.command("folder").description(
|
|
3342
3163
|
"Xray Test Repository folder management (rename/reorder within parent only \u2014 cross-parent moves are UI-only, not supported by the API). To list tests in a folder use: xray test list --folder <id> --project <key>"
|
|
3343
3164
|
);
|
|
3344
3165
|
examples(folder, ["list --project PROJ", "create --project PROJ --parent-id -1 --name Smoke"]);
|
|
3345
|
-
|
|
3346
|
-
|
|
3166
|
+
list14(folder);
|
|
3167
|
+
create10(folder);
|
|
3347
3168
|
update7(folder);
|
|
3348
3169
|
deleteFolder(folder);
|
|
3349
|
-
|
|
3350
|
-
|
|
3170
|
+
add3(folder);
|
|
3171
|
+
remove3(folder);
|
|
3351
3172
|
}
|
|
3352
3173
|
|
|
3353
3174
|
// src/commands/xray/import/execution.ts
|
|
3354
|
-
import { readFileSync
|
|
3175
|
+
import { readFileSync } from "fs";
|
|
3355
3176
|
import { basename as basename2 } from "path";
|
|
3356
|
-
import { Option as
|
|
3177
|
+
import { Option as Option9 } from "commander";
|
|
3357
3178
|
var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
|
|
3358
3179
|
var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
|
|
3359
3180
|
function execution(parent) {
|
|
3360
3181
|
const cmd = parent.command("execution").description("Import test execution results into Xray").requiredOption("--file <path>", "Path to the result file", filePath).addOption(
|
|
3361
|
-
new
|
|
3182
|
+
new Option9(
|
|
3362
3183
|
"--format <format>",
|
|
3363
3184
|
"Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires either --project or --execution."
|
|
3364
3185
|
).choices(FORMATS).makeOptionMandatory()
|
|
@@ -3383,7 +3204,7 @@ function execution(parent) {
|
|
|
3383
3204
|
`--project or --execution is required for ${opts.format} format. Provide --project <key> to create a new execution, or --execution <key> to import into an existing one.`
|
|
3384
3205
|
);
|
|
3385
3206
|
}
|
|
3386
|
-
const body =
|
|
3207
|
+
const body = readFileSync(opts.file);
|
|
3387
3208
|
const query = {};
|
|
3388
3209
|
if (opts.project) query["projectKey"] = opts.project;
|
|
3389
3210
|
if (opts.execution) query["testExecKey"] = opts.execution;
|
|
@@ -3404,13 +3225,13 @@ function execution(parent) {
|
|
|
3404
3225
|
}
|
|
3405
3226
|
|
|
3406
3227
|
// src/commands/xray/import/feature.ts
|
|
3407
|
-
import { readFileSync as
|
|
3228
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3408
3229
|
import { basename as basename3 } from "path";
|
|
3409
3230
|
function feature2(parent) {
|
|
3410
3231
|
const cmd = parent.command("feature").description("Import a Gherkin .feature file into the Xray test repository (multipart)").requiredOption("--file <path>", "Path to the .feature file", filePath).requiredOption("--project <text>", "Project key to import the feature into", text);
|
|
3411
3232
|
examples(cmd, ["--file login.feature --project PROJ"]);
|
|
3412
3233
|
cmd.action(async (opts) => {
|
|
3413
|
-
const fileBuffer =
|
|
3234
|
+
const fileBuffer = readFileSync2(opts.file);
|
|
3414
3235
|
const filename = basename3(opts.file);
|
|
3415
3236
|
const client = getClient();
|
|
3416
3237
|
const result = await client.xrayImport.importFeature({
|
|
@@ -3430,7 +3251,7 @@ function registerImportCommands(xray) {
|
|
|
3430
3251
|
}
|
|
3431
3252
|
|
|
3432
3253
|
// src/commands/xray/plan/add.ts
|
|
3433
|
-
function
|
|
3254
|
+
function add4(parent) {
|
|
3434
3255
|
const cmd = parent.command("add").description("Add tests and/or executions to a Test Plan").argument("<planKey>", "Test Plan issue key (e.g. PROJ-573)", issueKey).option("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList).option("--execution <keys>", "Comma-separated test execution keys to add (e.g. PROJ-200,PROJ-201)", keyList);
|
|
3435
3256
|
examples(cmd, [
|
|
3436
3257
|
"PROJ-573 --test PROJ-1,PROJ-2",
|
|
@@ -3449,7 +3270,7 @@ function add3(parent) {
|
|
|
3449
3270
|
}
|
|
3450
3271
|
|
|
3451
3272
|
// src/commands/xray/plan/create.ts
|
|
3452
|
-
function
|
|
3273
|
+
function create11(parent) {
|
|
3453
3274
|
const cmd = parent.command("create").description("Create a new Xray Test Plan issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test Plan summary (title)", text);
|
|
3454
3275
|
textOrFileOption(cmd, "description", { description: "Test Plan description" });
|
|
3455
3276
|
examples(cmd, [
|
|
@@ -3481,7 +3302,7 @@ function deletePlan(parent) {
|
|
|
3481
3302
|
}
|
|
3482
3303
|
|
|
3483
3304
|
// src/commands/xray/plan/list.ts
|
|
3484
|
-
function
|
|
3305
|
+
function list15(parent) {
|
|
3485
3306
|
const cmd = parent.command("list").description(
|
|
3486
3307
|
"List Test Plans that contain a given test (inverse view). For the tests inside a plan, use: xray test list --plan <key>. For executions in a plan, use: xray execution list --plan <key>."
|
|
3487
3308
|
).requiredOption("--test <key>", "Test issue key whose plans to list (e.g. PROJ-574)", issueKey);
|
|
@@ -3494,7 +3315,7 @@ function list12(parent) {
|
|
|
3494
3315
|
}
|
|
3495
3316
|
|
|
3496
3317
|
// src/commands/xray/plan/remove.ts
|
|
3497
|
-
function
|
|
3318
|
+
function remove4(parent) {
|
|
3498
3319
|
const cmd = parent.command("remove").description("Remove tests and/or executions from a Test Plan").argument("<planKey>", "Test Plan issue key (e.g. PROJ-573)", issueKey).option("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList).option("--execution <keys>", "Comma-separated test execution keys to remove (e.g. PROJ-200)", keyList);
|
|
3499
3320
|
examples(cmd, [
|
|
3500
3321
|
"PROJ-573 --test PROJ-1",
|
|
@@ -3536,16 +3357,16 @@ function registerPlanCommands(xray) {
|
|
|
3536
3357
|
"add PROJ-573 --test PROJ-1 --execution PROJ-200",
|
|
3537
3358
|
"list --test PROJ-574"
|
|
3538
3359
|
]);
|
|
3539
|
-
|
|
3360
|
+
create11(plan);
|
|
3540
3361
|
update8(plan);
|
|
3541
3362
|
deletePlan(plan);
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3363
|
+
add4(plan);
|
|
3364
|
+
remove4(plan);
|
|
3365
|
+
list15(plan);
|
|
3545
3366
|
}
|
|
3546
3367
|
|
|
3547
3368
|
// src/commands/xray/precondition/add.ts
|
|
3548
|
-
function
|
|
3369
|
+
function add5(parent) {
|
|
3549
3370
|
const cmd = parent.command("add").description("Add tests to a Pre-Condition").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList);
|
|
3550
3371
|
examples(cmd, ["PROJ-50 --test PROJ-1", "PROJ-50 --test PROJ-1,PROJ-2,PROJ-3"]);
|
|
3551
3372
|
cmd.action(async (preKey, opts) => {
|
|
@@ -3556,7 +3377,7 @@ function add4(parent) {
|
|
|
3556
3377
|
}
|
|
3557
3378
|
|
|
3558
3379
|
// src/commands/xray/precondition/create.ts
|
|
3559
|
-
import { Option as
|
|
3380
|
+
import { Option as Option10 } from "commander";
|
|
3560
3381
|
|
|
3561
3382
|
// src/utils/xray/fields.ts
|
|
3562
3383
|
var CONCEPT_SUFFIX = {
|
|
@@ -3657,11 +3478,11 @@ function buildPreconditionCustomFields(map, opts) {
|
|
|
3657
3478
|
}
|
|
3658
3479
|
|
|
3659
3480
|
// src/commands/xray/precondition/create.ts
|
|
3660
|
-
function
|
|
3481
|
+
function create12(parent) {
|
|
3661
3482
|
const cmd = parent.command("create").description(
|
|
3662
3483
|
"Create a new Xray Pre-Condition issue. Note: the Pre-Condition issue type is only available in projects configured for it; it may not be on your project scheme."
|
|
3663
3484
|
).requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Pre-Condition summary (title)", text).addOption(
|
|
3664
|
-
new
|
|
3485
|
+
new Option10("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
|
|
3665
3486
|
);
|
|
3666
3487
|
textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
|
|
3667
3488
|
textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
|
|
@@ -3708,7 +3529,7 @@ function deletePrecondition(parent) {
|
|
|
3708
3529
|
}
|
|
3709
3530
|
|
|
3710
3531
|
// src/commands/xray/precondition/list.ts
|
|
3711
|
-
function
|
|
3532
|
+
function list16(parent) {
|
|
3712
3533
|
const cmd = parent.command("list").description(
|
|
3713
3534
|
"List Pre-Conditions that apply to a given test (inverse view). For the forward view (tests covered by a precondition), use: xray precondition add/remove."
|
|
3714
3535
|
).requiredOption("--test <key>", "Test issue key whose pre-conditions to list (e.g. PROJ-584)", issueKey);
|
|
@@ -3721,7 +3542,7 @@ function list13(parent) {
|
|
|
3721
3542
|
}
|
|
3722
3543
|
|
|
3723
3544
|
// src/commands/xray/precondition/remove.ts
|
|
3724
|
-
function
|
|
3545
|
+
function remove5(parent) {
|
|
3725
3546
|
const cmd = parent.command("remove").description("Remove tests from a Pre-Condition").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
|
|
3726
3547
|
examples(cmd, ["PROJ-50 --test PROJ-1", "PROJ-50 --test PROJ-1,PROJ-2"]);
|
|
3727
3548
|
cmd.action(async (preKey, opts) => {
|
|
@@ -3732,9 +3553,9 @@ function remove4(parent) {
|
|
|
3732
3553
|
}
|
|
3733
3554
|
|
|
3734
3555
|
// src/commands/xray/precondition/update.ts
|
|
3735
|
-
import { Option as
|
|
3556
|
+
import { Option as Option11 } from "commander";
|
|
3736
3557
|
function update9(parent) {
|
|
3737
|
-
const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new
|
|
3558
|
+
const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option11("--type <type>", "New Pre-Condition type").choices(["manual", "generic", "cucumber"]));
|
|
3738
3559
|
textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
|
|
3739
3560
|
textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
|
|
3740
3561
|
examples(cmd, ['PROJ-50 --summary "Updated precondition"', 'PROJ-50 --type generic --condition "apiKey != null"']);
|
|
@@ -3782,12 +3603,12 @@ function registerPreconditionCommands(xray) {
|
|
|
3782
3603
|
"add PROJ-50 --test PROJ-1,PROJ-2",
|
|
3783
3604
|
"list --test PROJ-584"
|
|
3784
3605
|
]);
|
|
3785
|
-
|
|
3606
|
+
create12(precondition);
|
|
3786
3607
|
update9(precondition);
|
|
3787
3608
|
deletePrecondition(precondition);
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3609
|
+
add5(precondition);
|
|
3610
|
+
remove5(precondition);
|
|
3611
|
+
list16(precondition);
|
|
3791
3612
|
}
|
|
3792
3613
|
|
|
3793
3614
|
// src/utils/xray/run-id.ts
|
|
@@ -3801,7 +3622,7 @@ async function resolveRunId(client, o) {
|
|
|
3801
3622
|
}
|
|
3802
3623
|
|
|
3803
3624
|
// src/commands/xray/run/defect/add.ts
|
|
3804
|
-
function
|
|
3625
|
+
function add6(parent) {
|
|
3805
3626
|
const cmd = parent.command("add").description("Link defect issues to a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--defect <keys>", "Comma-separated defect issue keys to link", keyList).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
|
|
3806
3627
|
examples(cmd, ["42 --defect BUG-1", "--execution PROJ-1 --test PROJ-2 --defect BUG-1,BUG-2"]);
|
|
3807
3628
|
cmd.action(async (runId, opts) => {
|
|
@@ -3813,7 +3634,7 @@ function add5(parent) {
|
|
|
3813
3634
|
}
|
|
3814
3635
|
|
|
3815
3636
|
// src/commands/xray/run/defect/remove.ts
|
|
3816
|
-
function
|
|
3637
|
+
function remove6(parent) {
|
|
3817
3638
|
const cmd = parent.command("remove").description("Unlink defect issues from a test run (per-key, reports failures without aborting)").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--defect <keys>", "Comma-separated defect issue keys to unlink", keyList).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
|
|
3818
3639
|
examples(cmd, ["42 --defect BUG-1", "--execution PROJ-1 --test PROJ-2 --defect BUG-1,BUG-2"]);
|
|
3819
3640
|
cmd.action(async (runId, opts) => {
|
|
@@ -3850,12 +3671,12 @@ function registerDefectCommands(run) {
|
|
|
3850
3671
|
"Manage defects linked to a test run. See also: run update --defect for the one-command fail+link loop."
|
|
3851
3672
|
);
|
|
3852
3673
|
examples(defect, ["add --execution PROJ-1 --test PROJ-2 --defect BUG-1", "remove 42 --defect BUG-1"]);
|
|
3853
|
-
|
|
3854
|
-
|
|
3674
|
+
add6(defect);
|
|
3675
|
+
remove6(defect);
|
|
3855
3676
|
}
|
|
3856
3677
|
|
|
3857
3678
|
// src/commands/xray/run/evidence/add.ts
|
|
3858
|
-
import { readFileSync as
|
|
3679
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
3859
3680
|
import { basename as basename4, extname } from "path";
|
|
3860
3681
|
var CONTENT_TYPES = {
|
|
3861
3682
|
".png": "image/png",
|
|
@@ -3873,13 +3694,13 @@ var CONTENT_TYPES = {
|
|
|
3873
3694
|
function inferContentType(fp) {
|
|
3874
3695
|
return CONTENT_TYPES[extname(fp).toLowerCase()] ?? "application/octet-stream";
|
|
3875
3696
|
}
|
|
3876
|
-
function
|
|
3697
|
+
function add7(parent) {
|
|
3877
3698
|
const cmd = parent.command("add").description("Attach a file as evidence to a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--file <path>", "Path to the file to attach", filePath).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
|
|
3878
3699
|
examples(cmd, ["42 --file screenshot.png", "--execution PROJ-1 --test PROJ-2 --file report.pdf"]);
|
|
3879
3700
|
cmd.action(async (runId, opts) => {
|
|
3880
3701
|
const client = getClient();
|
|
3881
3702
|
const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
|
|
3882
|
-
const fileBuf =
|
|
3703
|
+
const fileBuf = readFileSync3(opts.file);
|
|
3883
3704
|
const filename = basename4(opts.file);
|
|
3884
3705
|
const contentType = inferContentType(opts.file);
|
|
3885
3706
|
const data = fileBuf.toString("base64");
|
|
@@ -3901,7 +3722,7 @@ function deleteEvidence(parent) {
|
|
|
3901
3722
|
}
|
|
3902
3723
|
|
|
3903
3724
|
// src/commands/xray/run/evidence/list.ts
|
|
3904
|
-
function
|
|
3725
|
+
function list17(parent) {
|
|
3905
3726
|
const cmd = parent.command("list").description("List evidence (attachments) on a test run").argument("[runId]", "Test run id (from run list)", positiveInt).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
|
|
3906
3727
|
examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
|
|
3907
3728
|
cmd.action(async (runId, opts) => {
|
|
@@ -3920,13 +3741,13 @@ function registerEvidenceCommands(run) {
|
|
|
3920
3741
|
"list 42",
|
|
3921
3742
|
"delete 42 --evidence-id 7"
|
|
3922
3743
|
]);
|
|
3923
|
-
|
|
3924
|
-
|
|
3744
|
+
add7(evidence);
|
|
3745
|
+
list17(evidence);
|
|
3925
3746
|
deleteEvidence(evidence);
|
|
3926
3747
|
}
|
|
3927
3748
|
|
|
3928
3749
|
// src/commands/xray/run/field/get.ts
|
|
3929
|
-
function
|
|
3750
|
+
function get10(parent) {
|
|
3930
3751
|
const cmd = parent.command("get").description("Get a custom field value on a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--field-id <id>", "Custom field id (from xray field list)", text).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
|
|
3931
3752
|
examples(cmd, ["42 --field-id customfield_10100", "--execution PROJ-1 --test PROJ-2 --field-id customfield_10100"]);
|
|
3932
3753
|
cmd.action(async (runId, opts) => {
|
|
@@ -3958,12 +3779,12 @@ function set(parent) {
|
|
|
3958
3779
|
function registerRunFieldCommands(run) {
|
|
3959
3780
|
const field = run.command("field").description("Get or set Xray custom field values on a test run");
|
|
3960
3781
|
examples(field, ["get 42 --field-id customfield_10100", 'set 42 --field-id customfield_10100 --value "approved"']);
|
|
3961
|
-
|
|
3782
|
+
get10(field);
|
|
3962
3783
|
set(field);
|
|
3963
3784
|
}
|
|
3964
3785
|
|
|
3965
3786
|
// src/commands/xray/run/get.ts
|
|
3966
|
-
function
|
|
3787
|
+
function get11(parent) {
|
|
3967
3788
|
const cmd = parent.command("get").description("Get a test run by id or by execution + test key").argument("[runId]", "Test run id (from run list)", positiveInt).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
|
|
3968
3789
|
examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
|
|
3969
3790
|
cmd.action(async (runId, opts) => {
|
|
@@ -3975,7 +3796,7 @@ function get9(parent) {
|
|
|
3975
3796
|
}
|
|
3976
3797
|
|
|
3977
3798
|
// src/commands/xray/run/list.ts
|
|
3978
|
-
function
|
|
3799
|
+
function list18(parent) {
|
|
3979
3800
|
const cmd = parent.command("list").description("List test runs for a test execution (CLI-side slice with --limit/--start)").requiredOption("--execution <key>", "Test execution issue key", issueKey).option("--limit <number>", "Max results to return (1-200)", intInRange(1, 200), 50).option("--start <number>", "Starting index for pagination", nonNegativeInt);
|
|
3980
3801
|
examples(cmd, ["--execution PROJ-1", "--execution PROJ-1 --limit 10", "--execution PROJ-1 --limit 10 --start 10"]);
|
|
3981
3802
|
cmd.action(async (opts) => {
|
|
@@ -3988,7 +3809,7 @@ function list15(parent) {
|
|
|
3988
3809
|
}
|
|
3989
3810
|
|
|
3990
3811
|
// src/commands/xray/run/step/list.ts
|
|
3991
|
-
function
|
|
3812
|
+
function list19(parent) {
|
|
3992
3813
|
const cmd = parent.command("list").description("List step results for a test run").argument("[runId]", "Test run id (from run list)", positiveInt).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
|
|
3993
3814
|
examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
|
|
3994
3815
|
cmd.action(async (runId, opts) => {
|
|
@@ -4035,7 +3856,7 @@ function update10(parent) {
|
|
|
4035
3856
|
function registerRunStepCommands(run) {
|
|
4036
3857
|
const step = run.command("step").description("Manage step results within a test run. See also: xray step for test step definitions.");
|
|
4037
3858
|
examples(step, ["list --execution PROJ-1 --test PROJ-2", "update 42 --step-id 1 --status PASS"]);
|
|
4038
|
-
|
|
3859
|
+
list19(step);
|
|
4039
3860
|
update10(step);
|
|
4040
3861
|
}
|
|
4041
3862
|
|
|
@@ -4081,8 +3902,8 @@ function registerRunCommands(xray) {
|
|
|
4081
3902
|
"step list --execution PROJ-1 --test PROJ-2",
|
|
4082
3903
|
"field get 42 --field-id customfield_10100"
|
|
4083
3904
|
]);
|
|
4084
|
-
|
|
4085
|
-
|
|
3905
|
+
get11(run);
|
|
3906
|
+
list18(run);
|
|
4086
3907
|
update11(run);
|
|
4087
3908
|
registerDefectCommands(run);
|
|
4088
3909
|
registerEvidenceCommands(run);
|
|
@@ -4091,7 +3912,7 @@ function registerRunCommands(xray) {
|
|
|
4091
3912
|
}
|
|
4092
3913
|
|
|
4093
3914
|
// src/commands/xray/status/list.ts
|
|
4094
|
-
function
|
|
3915
|
+
function list20(parent) {
|
|
4095
3916
|
const cmd = parent.command("list").description("List configured Xray test (run) statuses").option("--step", "List test-step statuses instead of run statuses");
|
|
4096
3917
|
examples(cmd, ["", ["--step", "step statuses"]]);
|
|
4097
3918
|
cmd.action(async (opts) => {
|
|
@@ -4105,11 +3926,11 @@ function list17(parent) {
|
|
|
4105
3926
|
function registerStatusCommands(xray) {
|
|
4106
3927
|
const status = xray.command("status").description("Xray status discovery");
|
|
4107
3928
|
examples(status, ["list", ["list --step", "step statuses"]]);
|
|
4108
|
-
|
|
3929
|
+
list20(status);
|
|
4109
3930
|
}
|
|
4110
3931
|
|
|
4111
3932
|
// src/commands/xray/step/add.ts
|
|
4112
|
-
function
|
|
3933
|
+
function add8(parent) {
|
|
4113
3934
|
const cmd = parent.command("add").description("Add a manual test step to a Test issue").requiredOption("--test <key>", "Test issue key (e.g. PROJ-584)", issueKey);
|
|
4114
3935
|
textOrFileOption(cmd, "action", { description: "Step action text (required: provide --action or --action-file)" });
|
|
4115
3936
|
textOrFileOption(cmd, "data", { description: "Step test data text" });
|
|
@@ -4142,7 +3963,7 @@ function deleteStep(parent) {
|
|
|
4142
3963
|
}
|
|
4143
3964
|
|
|
4144
3965
|
// src/commands/xray/step/list.ts
|
|
4145
|
-
function
|
|
3966
|
+
function list21(parent) {
|
|
4146
3967
|
const cmd = parent.command("list").description("List manual test steps for a Test issue").requiredOption("--test <key>", "Test issue key (e.g. PROJ-584)", issueKey);
|
|
4147
3968
|
examples(cmd, ["--test PROJ-584"]);
|
|
4148
3969
|
cmd.action(async (opts) => {
|
|
@@ -4185,23 +4006,23 @@ function registerStepCommands(xray) {
|
|
|
4185
4006
|
"Manage manual test steps for a Test issue (scope: --test). Steps are ordered by insertion; use step list to see current ids."
|
|
4186
4007
|
);
|
|
4187
4008
|
examples(step, ['add --test PROJ-584 --action "Open login" --result "Form shows"', "list --test PROJ-584"]);
|
|
4188
|
-
|
|
4189
|
-
|
|
4009
|
+
list21(step);
|
|
4010
|
+
add8(step);
|
|
4190
4011
|
update12(step);
|
|
4191
4012
|
deleteStep(step);
|
|
4192
4013
|
}
|
|
4193
4014
|
|
|
4194
4015
|
// src/commands/xray/test/create.ts
|
|
4195
|
-
import { Option as
|
|
4016
|
+
import { Option as Option12 } from "commander";
|
|
4196
4017
|
import { z as z5 } from "zod";
|
|
4197
4018
|
var stepsSchema = z5.array(
|
|
4198
4019
|
z5.object({ action: z5.string(), data: z5.string().optional(), result: z5.string().optional() })
|
|
4199
4020
|
);
|
|
4200
|
-
function
|
|
4021
|
+
function create13(parent) {
|
|
4201
4022
|
const cmd = parent.command("create").description("Create a new Xray Test issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test summary (title)", text).addOption(
|
|
4202
|
-
new
|
|
4023
|
+
new Option12("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
|
|
4203
4024
|
).addOption(
|
|
4204
|
-
new
|
|
4025
|
+
new Option12("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
|
|
4205
4026
|
"scenario",
|
|
4206
4027
|
"scenario-outline"
|
|
4207
4028
|
])
|
|
@@ -4286,7 +4107,7 @@ function deleteTest(parent) {
|
|
|
4286
4107
|
}
|
|
4287
4108
|
|
|
4288
4109
|
// src/commands/xray/test/get.ts
|
|
4289
|
-
function
|
|
4110
|
+
function get12(parent) {
|
|
4290
4111
|
const cmd = parent.command("get").description("Get one or more Xray test issues by key").argument("<testKey...>", "One or more test issue keys (e.g. PROJ-584)", listOf(issueKey));
|
|
4291
4112
|
examples(cmd, ["PROJ-584", "PROJ-584 PROJ-585"]);
|
|
4292
4113
|
cmd.action(async (testKeys) => {
|
|
@@ -4297,7 +4118,7 @@ function get10(parent) {
|
|
|
4297
4118
|
}
|
|
4298
4119
|
|
|
4299
4120
|
// src/commands/xray/test/list.ts
|
|
4300
|
-
function
|
|
4121
|
+
function list22(parent) {
|
|
4301
4122
|
const cmd = parent.command("list").description(
|
|
4302
4123
|
"List Xray Test issues by scope: all tests in a project, or tests belonging to a set, plan, execution, precondition, or folder. Note: use `jiradc issue search` for richer JQL when scoping by project."
|
|
4303
4124
|
).option("--project <text>", "Project key \u2014 list all tests in the project (or companion to --folder)", text).option("--set <key>", "Test Set issue key \u2014 list tests in this set", issueKey).option("--plan <key>", "Test Plan issue key \u2014 list tests in this plan", issueKey).option("--execution <key>", "Test Execution issue key \u2014 list tests in this execution", issueKey).option("--precondition <key>", "Precondition issue key \u2014 list tests with this precondition", issueKey).option("--folder <id>", "Folder id \u2014 list tests in this folder (requires --project)", positiveInt).option("--limit <number>", "Max results (1-50, applies to --project scope)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination (applies to --project scope)", nonNegativeInt);
|
|
@@ -4371,10 +4192,10 @@ function list19(parent) {
|
|
|
4371
4192
|
}
|
|
4372
4193
|
|
|
4373
4194
|
// src/commands/xray/test/update.ts
|
|
4374
|
-
import { Option as
|
|
4195
|
+
import { Option as Option13 } from "commander";
|
|
4375
4196
|
function update13(parent) {
|
|
4376
|
-
const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new
|
|
4377
|
-
new
|
|
4197
|
+
const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option13("--type <type>", "Test type").choices(["manual", "cucumber", "generic"])).addOption(
|
|
4198
|
+
new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
|
|
4378
4199
|
"scenario",
|
|
4379
4200
|
"scenario-outline"
|
|
4380
4201
|
])
|
|
@@ -4439,15 +4260,15 @@ function update13(parent) {
|
|
|
4439
4260
|
function registerTestCommands(xray) {
|
|
4440
4261
|
const test = xray.command("test").description("Xray Test issue management (CRUD)");
|
|
4441
4262
|
examples(test, ["get PROJ-584", 'create --project PROJ --summary "Login" --type manual']);
|
|
4442
|
-
|
|
4443
|
-
|
|
4263
|
+
get12(test);
|
|
4264
|
+
create13(test);
|
|
4444
4265
|
update13(test);
|
|
4445
4266
|
deleteTest(test);
|
|
4446
|
-
|
|
4267
|
+
list22(test);
|
|
4447
4268
|
}
|
|
4448
4269
|
|
|
4449
4270
|
// src/commands/xray/testset/add.ts
|
|
4450
|
-
function
|
|
4271
|
+
function add9(parent) {
|
|
4451
4272
|
const cmd = parent.command("add").description("Add tests to a Test Set").argument("<setKey>", "Test Set issue key (e.g. PROJ-100)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList);
|
|
4452
4273
|
examples(cmd, ["PROJ-100 --test PROJ-1", "PROJ-100 --test PROJ-1,PROJ-2,PROJ-3"]);
|
|
4453
4274
|
cmd.action(async (setKey, opts) => {
|
|
@@ -4458,7 +4279,7 @@ function add8(parent) {
|
|
|
4458
4279
|
}
|
|
4459
4280
|
|
|
4460
4281
|
// src/commands/xray/testset/create.ts
|
|
4461
|
-
function
|
|
4282
|
+
function create14(parent) {
|
|
4462
4283
|
const cmd = parent.command("create").description("Create a new Xray Test Set issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test Set summary (title)", text);
|
|
4463
4284
|
textOrFileOption(cmd, "description", { description: "Test Set description" });
|
|
4464
4285
|
examples(cmd, [
|
|
@@ -4490,7 +4311,7 @@ function deleteTestset(parent) {
|
|
|
4490
4311
|
}
|
|
4491
4312
|
|
|
4492
4313
|
// src/commands/xray/testset/list.ts
|
|
4493
|
-
function
|
|
4314
|
+
function list23(parent) {
|
|
4494
4315
|
const cmd = parent.command("list").description(
|
|
4495
4316
|
"List Test Sets that contain a given test (inverse view). For the forward view (tests inside a set), use: xray test list --set <key>"
|
|
4496
4317
|
).requiredOption("--test <key>", "Test issue key whose sets to list (e.g. PROJ-584)", issueKey);
|
|
@@ -4503,7 +4324,7 @@ function list20(parent) {
|
|
|
4503
4324
|
}
|
|
4504
4325
|
|
|
4505
4326
|
// src/commands/xray/testset/remove.ts
|
|
4506
|
-
function
|
|
4327
|
+
function remove7(parent) {
|
|
4507
4328
|
const cmd = parent.command("remove").description("Remove tests from a Test Set").argument("<setKey>", "Test Set issue key (e.g. PROJ-100)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
|
|
4508
4329
|
examples(cmd, ["PROJ-100 --test PROJ-1", "PROJ-100 --test PROJ-1,PROJ-2"]);
|
|
4509
4330
|
cmd.action(async (setKey, opts) => {
|
|
@@ -4532,12 +4353,12 @@ function registerTestsetCommands(xray) {
|
|
|
4532
4353
|
"Xray Test Set issue management (CRUD, membership). To list tests inside a set, use: xray test list --set <key>"
|
|
4533
4354
|
);
|
|
4534
4355
|
examples(testset, ['create --project PROJ --summary "Smoke tests"', "add PROJ-100 --test PROJ-1,PROJ-2"]);
|
|
4535
|
-
|
|
4356
|
+
create14(testset);
|
|
4536
4357
|
update14(testset);
|
|
4537
4358
|
deleteTestset(testset);
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4359
|
+
add9(testset);
|
|
4360
|
+
remove7(testset);
|
|
4361
|
+
list23(testset);
|
|
4541
4362
|
}
|
|
4542
4363
|
|
|
4543
4364
|
// src/commands/xray/index.ts
|
|
@@ -4553,7 +4374,7 @@ function registerXrayCommands(program) {
|
|
|
4553
4374
|
registerPlanCommands(xray);
|
|
4554
4375
|
registerStepCommands(xray);
|
|
4555
4376
|
registerRunCommands(xray);
|
|
4556
|
-
|
|
4377
|
+
registerFolderCommands2(xray);
|
|
4557
4378
|
registerImportCommands(xray);
|
|
4558
4379
|
registerExportCommands(xray);
|
|
4559
4380
|
}
|
|
@@ -4600,6 +4421,7 @@ ${styleText("bold", "Examples:")}
|
|
|
4600
4421
|
registerFieldCommands(program);
|
|
4601
4422
|
registerUserCommands(program);
|
|
4602
4423
|
registerTokenCommands(program);
|
|
4424
|
+
registerStructureCommands(program);
|
|
4603
4425
|
registerXrayCommands(program);
|
|
4604
4426
|
return program;
|
|
4605
4427
|
}
|