jiradc-cli 1.0.38 → 1.0.40
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 +662 -819
- package/package.json +1 -1
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",
|
|
@@ -1450,13 +793,29 @@ function registerComponentCommands(program) {
|
|
|
1450
793
|
issueCount(component);
|
|
1451
794
|
}
|
|
1452
795
|
|
|
796
|
+
// src/utils/field-search.ts
|
|
797
|
+
function tier(field, keyword) {
|
|
798
|
+
const id = field.id.toLowerCase();
|
|
799
|
+
const name = (field.name ?? "").toLowerCase();
|
|
800
|
+
if (id === keyword) return 0;
|
|
801
|
+
if (name === keyword) return field.custom === true ? 2 : 1;
|
|
802
|
+
return field.custom === true ? 4 : 3;
|
|
803
|
+
}
|
|
804
|
+
function rankFieldMatches(fields, keyword, limit) {
|
|
805
|
+
const needle = keyword.toLowerCase();
|
|
806
|
+
const matches = fields.filter(
|
|
807
|
+
(f) => f.id.toLowerCase().includes(needle) || (f.name ?? "").toLowerCase().includes(needle)
|
|
808
|
+
);
|
|
809
|
+
return matches.sort((a, b) => tier(a, needle) - tier(b, needle)).slice(0, limit);
|
|
810
|
+
}
|
|
811
|
+
|
|
1453
812
|
// src/commands/field/search.ts
|
|
1454
813
|
function search(parent) {
|
|
1455
814
|
const cmd = parent.command("search").description("Search for fields by name or ID").argument("<keyword>", "Search keyword", nonEmpty).option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25);
|
|
1456
815
|
examples(cmd, ["epic", "customfield_10100", "priority --limit 5"]);
|
|
1457
816
|
cmd.action(async (keyword, opts) => {
|
|
1458
817
|
const client = getClient();
|
|
1459
|
-
const result = await client.fields.
|
|
818
|
+
const result = rankFieldMatches(await client.fields.getAll(), keyword, opts.limit);
|
|
1460
819
|
output(result.map(transformField));
|
|
1461
820
|
});
|
|
1462
821
|
}
|
|
@@ -1486,21 +845,21 @@ async function resolveUserToken(token) {
|
|
|
1486
845
|
|
|
1487
846
|
// src/utils/validators.ts
|
|
1488
847
|
import { existsSync } from "fs";
|
|
1489
|
-
import { InvalidArgumentError
|
|
848
|
+
import { InvalidArgumentError } from "commander";
|
|
1490
849
|
function issueKey(raw) {
|
|
1491
850
|
if (!/^(\d+|[A-Z][A-Z0-9]+-\d+)$/.test(raw)) {
|
|
1492
|
-
throw new
|
|
851
|
+
throw new InvalidArgumentError("Must be a Jira issue key (e.g. PROJ-123) or a numeric issue id.");
|
|
1493
852
|
}
|
|
1494
853
|
return raw;
|
|
1495
854
|
}
|
|
1496
855
|
function keyList(raw) {
|
|
1497
856
|
const keys = raw.split(",").map((k) => k.trim()).filter(Boolean);
|
|
1498
|
-
if (keys.length === 0) throw new
|
|
857
|
+
if (keys.length === 0) throw new InvalidArgumentError("Provide at least one comma-separated issue key.");
|
|
1499
858
|
for (const k of keys) issueKey(k);
|
|
1500
859
|
return keys;
|
|
1501
860
|
}
|
|
1502
861
|
function filePath(raw) {
|
|
1503
|
-
if (!existsSync(raw)) throw new
|
|
862
|
+
if (!existsSync(raw)) throw new InvalidArgumentError(`File not found: ${raw}`);
|
|
1504
863
|
return raw;
|
|
1505
864
|
}
|
|
1506
865
|
|
|
@@ -1532,14 +891,14 @@ function deleteAttachment(parent) {
|
|
|
1532
891
|
}
|
|
1533
892
|
|
|
1534
893
|
// src/commands/issue/attachment/download-all.ts
|
|
1535
|
-
import { mkdirSync
|
|
1536
|
-
import { join
|
|
894
|
+
import { mkdirSync } from "fs";
|
|
895
|
+
import { join } from "path";
|
|
1537
896
|
function downloadAll(parent) {
|
|
1538
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);
|
|
1539
898
|
examples(cmd, ["PROJ-123 --output ./downloads"]);
|
|
1540
899
|
cmd.action(async (key, opts) => {
|
|
1541
900
|
const client = getClient();
|
|
1542
|
-
|
|
901
|
+
mkdirSync(opts.output, { recursive: true });
|
|
1543
902
|
const issue = await client.issues.get({
|
|
1544
903
|
issueKeyOrId: key,
|
|
1545
904
|
fields: ["attachment"]
|
|
@@ -1556,7 +915,7 @@ function downloadAll(parent) {
|
|
|
1556
915
|
failed.push({ filename: att.filename, error: "No content URL" });
|
|
1557
916
|
continue;
|
|
1558
917
|
}
|
|
1559
|
-
const destPath =
|
|
918
|
+
const destPath = join(opts.output, att.filename);
|
|
1560
919
|
try {
|
|
1561
920
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
|
|
1562
921
|
results.push({ filename: att.filename, size: att.size, path: destPath });
|
|
@@ -1727,7 +1086,7 @@ function changelog(parent) {
|
|
|
1727
1086
|
// src/commands/issue/clone.ts
|
|
1728
1087
|
import { unlink } from "fs/promises";
|
|
1729
1088
|
import { tmpdir } from "os";
|
|
1730
|
-
import { join as
|
|
1089
|
+
import { join as join2 } from "path";
|
|
1731
1090
|
var CLONE_FIELDS = [
|
|
1732
1091
|
"summary",
|
|
1733
1092
|
"description",
|
|
@@ -1777,7 +1136,7 @@ function clone(parent) {
|
|
|
1777
1136
|
const tmpFiles = [];
|
|
1778
1137
|
const copied = await Promise.all(
|
|
1779
1138
|
f.attachment.map(async (att) => {
|
|
1780
|
-
const tmpPath =
|
|
1139
|
+
const tmpPath = join2(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
|
|
1781
1140
|
tmpFiles.push(tmpPath);
|
|
1782
1141
|
await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
|
|
1783
1142
|
await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
|
|
@@ -1910,7 +1269,7 @@ function registerCommentCommands(parent) {
|
|
|
1910
1269
|
}
|
|
1911
1270
|
|
|
1912
1271
|
// src/commands/issue/create.ts
|
|
1913
|
-
import { Option as
|
|
1272
|
+
import { Option as Option4 } from "commander";
|
|
1914
1273
|
import { z as z2 } from "zod";
|
|
1915
1274
|
var fieldsSchema = z2.record(z2.unknown());
|
|
1916
1275
|
function create3(parent) {
|
|
@@ -1919,7 +1278,7 @@ function create3(parent) {
|
|
|
1919
1278
|
`Additional fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
|
|
1920
1279
|
jsonShape(fieldsSchema)
|
|
1921
1280
|
).addOption(
|
|
1922
|
-
new
|
|
1281
|
+
new Option4("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
|
|
1923
1282
|
);
|
|
1924
1283
|
textOrFileOption(cmd, "description", { description: "Issue description in wiki markup" });
|
|
1925
1284
|
examples(cmd, [
|
|
@@ -2096,6 +1455,7 @@ var DEFAULT_FIELDS = [...SEARCH_DEFAULT_FIELDS, "description", "comment"];
|
|
|
2096
1455
|
// src/utils/field-selectors.ts
|
|
2097
1456
|
var WILDCARD = "*";
|
|
2098
1457
|
var NEGATION = "-";
|
|
1458
|
+
var UNPUBLISHED_SELECTORS = ["parent"];
|
|
2099
1459
|
function buildFieldNameIndex(fields) {
|
|
2100
1460
|
const ids = /* @__PURE__ */ new Set();
|
|
2101
1461
|
const idsByLower = /* @__PURE__ */ new Map();
|
|
@@ -2115,6 +1475,10 @@ function buildFieldNameIndex(fields) {
|
|
|
2115
1475
|
record(field.name, field.id);
|
|
2116
1476
|
for (const clause of field.clauseNames ?? []) record(clause, field.id);
|
|
2117
1477
|
}
|
|
1478
|
+
for (const id of UNPUBLISHED_SELECTORS) {
|
|
1479
|
+
ids.add(id);
|
|
1480
|
+
if (!idsByLower.has(id)) idsByLower.set(id, id);
|
|
1481
|
+
}
|
|
2118
1482
|
return { ids, idsByLower, byName };
|
|
2119
1483
|
}
|
|
2120
1484
|
function candidatesFor(token, index) {
|
|
@@ -2438,20 +1802,20 @@ function unlink2(parent) {
|
|
|
2438
1802
|
import { z as z4 } from "zod";
|
|
2439
1803
|
|
|
2440
1804
|
// src/utils/multi-value.ts
|
|
2441
|
-
import { InvalidArgumentError as
|
|
1805
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
2442
1806
|
function parseMultiValue(flagName, raw) {
|
|
2443
1807
|
const items = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2444
1808
|
if (items.length === 0) {
|
|
2445
|
-
throw new
|
|
1809
|
+
throw new InvalidArgumentError2(`${flagName} cannot be empty`);
|
|
2446
1810
|
}
|
|
2447
1811
|
const lonePrefix = items.find((s) => s === "+" || s === "-");
|
|
2448
1812
|
if (lonePrefix !== void 0) {
|
|
2449
|
-
throw new
|
|
1813
|
+
throw new InvalidArgumentError2(`${flagName} has an empty value after '${lonePrefix}' prefix`);
|
|
2450
1814
|
}
|
|
2451
1815
|
const prefixed = items.filter((s) => s.startsWith("+") || s.startsWith("-"));
|
|
2452
1816
|
const bare = items.filter((s) => !s.startsWith("+") && !s.startsWith("-"));
|
|
2453
1817
|
if (prefixed.length > 0 && bare.length > 0) {
|
|
2454
|
-
throw new
|
|
1818
|
+
throw new InvalidArgumentError2(
|
|
2455
1819
|
`${flagName} mixes set and mutate syntax. Either all values have +/- prefix, or none do.`
|
|
2456
1820
|
);
|
|
2457
1821
|
}
|
|
@@ -2580,12 +1944,12 @@ function create4(parent) {
|
|
|
2580
1944
|
}
|
|
2581
1945
|
|
|
2582
1946
|
// src/commands/issue/worklog/delete.ts
|
|
2583
|
-
import { Option as
|
|
1947
|
+
import { Option as Option5 } from "commander";
|
|
2584
1948
|
var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
|
|
2585
1949
|
function deleteWorklog(parent) {
|
|
2586
1950
|
const cmd = parent.command("delete").description("Delete a worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2587
1951
|
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
2588
|
-
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(
|
|
2589
1953
|
"--increase-by <amount>",
|
|
2590
1954
|
'Amount to increase the estimate by; required when --adjust-estimate is "manual"',
|
|
2591
1955
|
text
|
|
@@ -2626,12 +1990,12 @@ function list5(parent) {
|
|
|
2626
1990
|
}
|
|
2627
1991
|
|
|
2628
1992
|
// src/commands/issue/worklog/update.ts
|
|
2629
|
-
import { Option as
|
|
1993
|
+
import { Option as Option6 } from "commander";
|
|
2630
1994
|
var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
|
|
2631
1995
|
function update4(parent) {
|
|
2632
1996
|
const cmd = parent.command("update").description("Update an existing worklog entry").argument("<key>", "Issue key", issueKey);
|
|
2633
1997
|
subEntityOption(cmd, "worklog", { mandatory: true });
|
|
2634
|
-
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);
|
|
2635
1999
|
commentOption(cmd, { description: "Worklog comment" });
|
|
2636
2000
|
examples(cmd, [
|
|
2637
2001
|
'PROJ-123 --worklog-id 12345 --time "1h 30m"',
|
|
@@ -2815,10 +2179,10 @@ function issues2(parent) {
|
|
|
2815
2179
|
}
|
|
2816
2180
|
|
|
2817
2181
|
// src/commands/sprint/list.ts
|
|
2818
|
-
import { Option as
|
|
2182
|
+
import { Option as Option7 } from "commander";
|
|
2819
2183
|
var SPRINT_STATES = ["future", "active", "closed"];
|
|
2820
2184
|
function list7(parent) {
|
|
2821
|
-
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));
|
|
2822
2186
|
examples(cmd, ["--board 42", "--board 42 --state active"]);
|
|
2823
2187
|
cmd.action(async (opts) => {
|
|
2824
2188
|
const client = getClient();
|
|
@@ -2831,10 +2195,10 @@ function list7(parent) {
|
|
|
2831
2195
|
}
|
|
2832
2196
|
|
|
2833
2197
|
// src/commands/sprint/update.ts
|
|
2834
|
-
import { Argument as Argument4, Option as
|
|
2198
|
+
import { Argument as Argument4, Option as Option8 } from "commander";
|
|
2835
2199
|
var SPRINT_STATES2 = ["future", "active", "closed"];
|
|
2836
2200
|
function update5(parent) {
|
|
2837
|
-
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);
|
|
2838
2202
|
examples(cmd, [
|
|
2839
2203
|
'100 --name "Sprint 10 - Extended"',
|
|
2840
2204
|
"100 --state active",
|
|
@@ -2874,6 +2238,484 @@ function registerSprintCommands(program) {
|
|
|
2874
2238
|
deleteSprint(sprint);
|
|
2875
2239
|
}
|
|
2876
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
|
+
|
|
2877
2719
|
// src/commands/token/client.ts
|
|
2878
2720
|
import { JiraClient as JiraClient2 } from "jira-data-center-client";
|
|
2879
2721
|
function getTokenClient(options = {}) {
|
|
@@ -2893,7 +2735,7 @@ function getTokenClient(options = {}) {
|
|
|
2893
2735
|
}
|
|
2894
2736
|
|
|
2895
2737
|
// src/commands/token/create.ts
|
|
2896
|
-
function
|
|
2738
|
+
function create8(parent) {
|
|
2897
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(
|
|
2898
2740
|
"--expiration-duration <days>",
|
|
2899
2741
|
"Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
|
|
@@ -2920,7 +2762,7 @@ function create6(parent) {
|
|
|
2920
2762
|
}
|
|
2921
2763
|
|
|
2922
2764
|
// src/commands/token/list.ts
|
|
2923
|
-
function
|
|
2765
|
+
function list11(parent) {
|
|
2924
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(
|
|
2925
2767
|
"--basic-password <p>",
|
|
2926
2768
|
"Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)",
|
|
@@ -2963,13 +2805,13 @@ function registerTokenCommands(program) {
|
|
|
2963
2805
|
"after",
|
|
2964
2806
|
"\nAuth:\n Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD\n (or --basic-username / --basic-password on any subcommand)."
|
|
2965
2807
|
);
|
|
2966
|
-
|
|
2967
|
-
|
|
2808
|
+
create8(token);
|
|
2809
|
+
list11(token);
|
|
2968
2810
|
revoke(token);
|
|
2969
2811
|
}
|
|
2970
2812
|
|
|
2971
2813
|
// src/commands/user/get.ts
|
|
2972
|
-
function
|
|
2814
|
+
function get9(parent) {
|
|
2973
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");
|
|
2974
2816
|
examples(cmd, ["jsmith", "JIRAUSER10100 --by-key"]);
|
|
2975
2817
|
cmd.action(async (identifier, opts) => {
|
|
@@ -2991,7 +2833,7 @@ function me(parent) {
|
|
|
2991
2833
|
}
|
|
2992
2834
|
|
|
2993
2835
|
// src/commands/user/search.ts
|
|
2994
|
-
function
|
|
2836
|
+
function search4(parent) {
|
|
2995
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);
|
|
2996
2838
|
examples(cmd, ["Smith", '"John Smith"', "jsmith@example.com --limit 5"]);
|
|
2997
2839
|
cmd.action(async (query, opts) => {
|
|
@@ -3012,8 +2854,8 @@ function registerUserCommands(program) {
|
|
|
3012
2854
|
const user = program.command("user").description("User operations");
|
|
3013
2855
|
examples(user, ["me", "get jsmith", "search Smith"]);
|
|
3014
2856
|
me(user);
|
|
3015
|
-
|
|
3016
|
-
|
|
2857
|
+
get9(user);
|
|
2858
|
+
search4(user);
|
|
3017
2859
|
}
|
|
3018
2860
|
|
|
3019
2861
|
// src/utils/xray/guards.ts
|
|
@@ -3029,7 +2871,7 @@ function assertAtLeastOne(opts, keys) {
|
|
|
3029
2871
|
}
|
|
3030
2872
|
|
|
3031
2873
|
// src/commands/xray/execution/add.ts
|
|
3032
|
-
function
|
|
2874
|
+
function add2(parent) {
|
|
3033
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(
|
|
3034
2876
|
"--set <keys>",
|
|
3035
2877
|
"Comma-separated Test Set keys; each set's current tests are added to the execution (e.g. PROJ-300)",
|
|
@@ -3047,8 +2889,8 @@ function add(parent) {
|
|
|
3047
2889
|
const keys = new Set(opts.test ?? []);
|
|
3048
2890
|
if (opts.set) {
|
|
3049
2891
|
const lists = await Promise.all(opts.set.map((setKey) => client.testSets.listTests({ setKey })));
|
|
3050
|
-
for (const
|
|
3051
|
-
const tests = Array.isArray(
|
|
2892
|
+
for (const list24 of lists) {
|
|
2893
|
+
const tests = Array.isArray(list24) ? list24 : list24.tests ?? [];
|
|
3052
2894
|
for (const t of tests) {
|
|
3053
2895
|
if (t.key) keys.add(String(t.key));
|
|
3054
2896
|
}
|
|
@@ -3094,7 +2936,7 @@ function deleteXrayIssue(client, key) {
|
|
|
3094
2936
|
}
|
|
3095
2937
|
|
|
3096
2938
|
// src/commands/xray/execution/create.ts
|
|
3097
|
-
function
|
|
2939
|
+
function create9(parent) {
|
|
3098
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);
|
|
3099
2941
|
textOrFileOption(cmd, "description", { description: "Test Execution description" });
|
|
3100
2942
|
examples(cmd, [
|
|
@@ -3126,7 +2968,7 @@ function deleteExecution(parent) {
|
|
|
3126
2968
|
}
|
|
3127
2969
|
|
|
3128
2970
|
// src/commands/xray/execution/list.ts
|
|
3129
|
-
function
|
|
2971
|
+
function list12(parent) {
|
|
3130
2972
|
const cmd = parent.command("list").description(
|
|
3131
2973
|
"List Test Executions that contain a given test (inverse view). For the tests inside an execution, use: xray test list --execution <key>."
|
|
3132
2974
|
).requiredOption("--test <key>", "Test issue key whose executions to list (e.g. PROJ-584)", issueKey);
|
|
@@ -3139,7 +2981,7 @@ function list9(parent) {
|
|
|
3139
2981
|
}
|
|
3140
2982
|
|
|
3141
2983
|
// src/commands/xray/execution/remove.ts
|
|
3142
|
-
function
|
|
2984
|
+
function remove2(parent) {
|
|
3143
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);
|
|
3144
2986
|
examples(cmd, ["PROJ-200 --test PROJ-1", "PROJ-200 --test PROJ-1,PROJ-2"]);
|
|
3145
2987
|
cmd.action(async (execKey, opts) => {
|
|
@@ -3172,17 +3014,17 @@ function registerExecutionCommands(xray) {
|
|
|
3172
3014
|
"add PROJ-200 --test PROJ-1,PROJ-2",
|
|
3173
3015
|
"list --test PROJ-584"
|
|
3174
3016
|
]);
|
|
3175
|
-
|
|
3017
|
+
create9(execution2);
|
|
3176
3018
|
update6(execution2);
|
|
3177
3019
|
deleteExecution(execution2);
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3020
|
+
add2(execution2);
|
|
3021
|
+
remove2(execution2);
|
|
3022
|
+
list12(execution2);
|
|
3181
3023
|
}
|
|
3182
3024
|
|
|
3183
3025
|
// src/commands/xray/export/feature.ts
|
|
3184
|
-
import { writeFileSync
|
|
3185
|
-
import { basename, join as
|
|
3026
|
+
import { writeFileSync } from "fs";
|
|
3027
|
+
import { basename, join as join3 } from "path";
|
|
3186
3028
|
function feature(parent) {
|
|
3187
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");
|
|
3188
3030
|
examples(cmd, ["--test PROJ-584 --output ai-584.feature"]);
|
|
@@ -3192,8 +3034,8 @@ function feature(parent) {
|
|
|
3192
3034
|
keys: opts.test,
|
|
3193
3035
|
zip: opts.zip
|
|
3194
3036
|
});
|
|
3195
|
-
const writePath = opts.output ??
|
|
3196
|
-
|
|
3037
|
+
const writePath = opts.output ?? join3(".", basename(filename));
|
|
3038
|
+
writeFileSync(writePath, data);
|
|
3197
3039
|
output({ written: writePath, bytes: data.length });
|
|
3198
3040
|
});
|
|
3199
3041
|
}
|
|
@@ -3205,7 +3047,7 @@ function registerExportCommands(xray) {
|
|
|
3205
3047
|
}
|
|
3206
3048
|
|
|
3207
3049
|
// src/commands/xray/field/list.ts
|
|
3208
|
-
function
|
|
3050
|
+
function list13(parent) {
|
|
3209
3051
|
const cmd = parent.command("list").description("List Xray custom fields available on this instance");
|
|
3210
3052
|
examples(cmd, [""]);
|
|
3211
3053
|
cmd.action(async () => {
|
|
@@ -3218,11 +3060,11 @@ function list10(parent) {
|
|
|
3218
3060
|
function registerFieldCommands2(xray) {
|
|
3219
3061
|
const field = xray.command("field").description("Xray field discovery");
|
|
3220
3062
|
examples(field, ["list"]);
|
|
3221
|
-
|
|
3063
|
+
list13(field);
|
|
3222
3064
|
}
|
|
3223
3065
|
|
|
3224
3066
|
// src/commands/xray/folder/add.ts
|
|
3225
|
-
function
|
|
3067
|
+
function add3(parent) {
|
|
3226
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);
|
|
3227
3069
|
examples(cmd, ["818 --project PROJ --test PROJ-1", "818 --project PROJ --test PROJ-1,PROJ-2,PROJ-3"]);
|
|
3228
3070
|
cmd.action(async (folderId, opts) => {
|
|
@@ -3237,7 +3079,7 @@ function add2(parent) {
|
|
|
3237
3079
|
}
|
|
3238
3080
|
|
|
3239
3081
|
// src/commands/xray/folder/create.ts
|
|
3240
|
-
function
|
|
3082
|
+
function create10(parent) {
|
|
3241
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);
|
|
3242
3084
|
examples(cmd, [
|
|
3243
3085
|
"--project PROJ --name Smoke",
|
|
@@ -3267,7 +3109,7 @@ function deleteFolder(parent) {
|
|
|
3267
3109
|
}
|
|
3268
3110
|
|
|
3269
3111
|
// src/commands/xray/folder/list.ts
|
|
3270
|
-
function
|
|
3112
|
+
function list14(parent) {
|
|
3271
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);
|
|
3272
3114
|
examples(cmd, ["--project PROJ", "--project PROJ"]);
|
|
3273
3115
|
cmd.action(async (opts) => {
|
|
@@ -3278,7 +3120,7 @@ function list11(parent) {
|
|
|
3278
3120
|
}
|
|
3279
3121
|
|
|
3280
3122
|
// src/commands/xray/folder/remove.ts
|
|
3281
|
-
function
|
|
3123
|
+
function remove3(parent) {
|
|
3282
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);
|
|
3283
3125
|
examples(cmd, ["818 --project PROJ --test PROJ-1", "818 --project PROJ --test PROJ-1,PROJ-2"]);
|
|
3284
3126
|
cmd.action(async (folderId, opts) => {
|
|
@@ -3316,28 +3158,28 @@ function update7(parent) {
|
|
|
3316
3158
|
}
|
|
3317
3159
|
|
|
3318
3160
|
// src/commands/xray/folder/index.ts
|
|
3319
|
-
function
|
|
3161
|
+
function registerFolderCommands2(xray) {
|
|
3320
3162
|
const folder = xray.command("folder").description(
|
|
3321
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>"
|
|
3322
3164
|
);
|
|
3323
3165
|
examples(folder, ["list --project PROJ", "create --project PROJ --parent-id -1 --name Smoke"]);
|
|
3324
|
-
|
|
3325
|
-
|
|
3166
|
+
list14(folder);
|
|
3167
|
+
create10(folder);
|
|
3326
3168
|
update7(folder);
|
|
3327
3169
|
deleteFolder(folder);
|
|
3328
|
-
|
|
3329
|
-
|
|
3170
|
+
add3(folder);
|
|
3171
|
+
remove3(folder);
|
|
3330
3172
|
}
|
|
3331
3173
|
|
|
3332
3174
|
// src/commands/xray/import/execution.ts
|
|
3333
|
-
import { readFileSync
|
|
3175
|
+
import { readFileSync } from "fs";
|
|
3334
3176
|
import { basename as basename2 } from "path";
|
|
3335
|
-
import { Option as
|
|
3177
|
+
import { Option as Option9 } from "commander";
|
|
3336
3178
|
var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
|
|
3337
3179
|
var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
|
|
3338
3180
|
function execution(parent) {
|
|
3339
3181
|
const cmd = parent.command("execution").description("Import test execution results into Xray").requiredOption("--file <path>", "Path to the result file", filePath).addOption(
|
|
3340
|
-
new
|
|
3182
|
+
new Option9(
|
|
3341
3183
|
"--format <format>",
|
|
3342
3184
|
"Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires either --project or --execution."
|
|
3343
3185
|
).choices(FORMATS).makeOptionMandatory()
|
|
@@ -3362,7 +3204,7 @@ function execution(parent) {
|
|
|
3362
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.`
|
|
3363
3205
|
);
|
|
3364
3206
|
}
|
|
3365
|
-
const body =
|
|
3207
|
+
const body = readFileSync(opts.file);
|
|
3366
3208
|
const query = {};
|
|
3367
3209
|
if (opts.project) query["projectKey"] = opts.project;
|
|
3368
3210
|
if (opts.execution) query["testExecKey"] = opts.execution;
|
|
@@ -3383,13 +3225,13 @@ function execution(parent) {
|
|
|
3383
3225
|
}
|
|
3384
3226
|
|
|
3385
3227
|
// src/commands/xray/import/feature.ts
|
|
3386
|
-
import { readFileSync as
|
|
3228
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3387
3229
|
import { basename as basename3 } from "path";
|
|
3388
3230
|
function feature2(parent) {
|
|
3389
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);
|
|
3390
3232
|
examples(cmd, ["--file login.feature --project PROJ"]);
|
|
3391
3233
|
cmd.action(async (opts) => {
|
|
3392
|
-
const fileBuffer =
|
|
3234
|
+
const fileBuffer = readFileSync2(opts.file);
|
|
3393
3235
|
const filename = basename3(opts.file);
|
|
3394
3236
|
const client = getClient();
|
|
3395
3237
|
const result = await client.xrayImport.importFeature({
|
|
@@ -3409,7 +3251,7 @@ function registerImportCommands(xray) {
|
|
|
3409
3251
|
}
|
|
3410
3252
|
|
|
3411
3253
|
// src/commands/xray/plan/add.ts
|
|
3412
|
-
function
|
|
3254
|
+
function add4(parent) {
|
|
3413
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);
|
|
3414
3256
|
examples(cmd, [
|
|
3415
3257
|
"PROJ-573 --test PROJ-1,PROJ-2",
|
|
@@ -3428,7 +3270,7 @@ function add3(parent) {
|
|
|
3428
3270
|
}
|
|
3429
3271
|
|
|
3430
3272
|
// src/commands/xray/plan/create.ts
|
|
3431
|
-
function
|
|
3273
|
+
function create11(parent) {
|
|
3432
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);
|
|
3433
3275
|
textOrFileOption(cmd, "description", { description: "Test Plan description" });
|
|
3434
3276
|
examples(cmd, [
|
|
@@ -3460,7 +3302,7 @@ function deletePlan(parent) {
|
|
|
3460
3302
|
}
|
|
3461
3303
|
|
|
3462
3304
|
// src/commands/xray/plan/list.ts
|
|
3463
|
-
function
|
|
3305
|
+
function list15(parent) {
|
|
3464
3306
|
const cmd = parent.command("list").description(
|
|
3465
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>."
|
|
3466
3308
|
).requiredOption("--test <key>", "Test issue key whose plans to list (e.g. PROJ-574)", issueKey);
|
|
@@ -3473,7 +3315,7 @@ function list12(parent) {
|
|
|
3473
3315
|
}
|
|
3474
3316
|
|
|
3475
3317
|
// src/commands/xray/plan/remove.ts
|
|
3476
|
-
function
|
|
3318
|
+
function remove4(parent) {
|
|
3477
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);
|
|
3478
3320
|
examples(cmd, [
|
|
3479
3321
|
"PROJ-573 --test PROJ-1",
|
|
@@ -3515,16 +3357,16 @@ function registerPlanCommands(xray) {
|
|
|
3515
3357
|
"add PROJ-573 --test PROJ-1 --execution PROJ-200",
|
|
3516
3358
|
"list --test PROJ-574"
|
|
3517
3359
|
]);
|
|
3518
|
-
|
|
3360
|
+
create11(plan);
|
|
3519
3361
|
update8(plan);
|
|
3520
3362
|
deletePlan(plan);
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3363
|
+
add4(plan);
|
|
3364
|
+
remove4(plan);
|
|
3365
|
+
list15(plan);
|
|
3524
3366
|
}
|
|
3525
3367
|
|
|
3526
3368
|
// src/commands/xray/precondition/add.ts
|
|
3527
|
-
function
|
|
3369
|
+
function add5(parent) {
|
|
3528
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);
|
|
3529
3371
|
examples(cmd, ["PROJ-50 --test PROJ-1", "PROJ-50 --test PROJ-1,PROJ-2,PROJ-3"]);
|
|
3530
3372
|
cmd.action(async (preKey, opts) => {
|
|
@@ -3535,7 +3377,7 @@ function add4(parent) {
|
|
|
3535
3377
|
}
|
|
3536
3378
|
|
|
3537
3379
|
// src/commands/xray/precondition/create.ts
|
|
3538
|
-
import { Option as
|
|
3380
|
+
import { Option as Option10 } from "commander";
|
|
3539
3381
|
|
|
3540
3382
|
// src/utils/xray/fields.ts
|
|
3541
3383
|
var CONCEPT_SUFFIX = {
|
|
@@ -3636,11 +3478,11 @@ function buildPreconditionCustomFields(map, opts) {
|
|
|
3636
3478
|
}
|
|
3637
3479
|
|
|
3638
3480
|
// src/commands/xray/precondition/create.ts
|
|
3639
|
-
function
|
|
3481
|
+
function create12(parent) {
|
|
3640
3482
|
const cmd = parent.command("create").description(
|
|
3641
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."
|
|
3642
3484
|
).requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Pre-Condition summary (title)", text).addOption(
|
|
3643
|
-
new
|
|
3485
|
+
new Option10("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
|
|
3644
3486
|
);
|
|
3645
3487
|
textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
|
|
3646
3488
|
textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
|
|
@@ -3687,7 +3529,7 @@ function deletePrecondition(parent) {
|
|
|
3687
3529
|
}
|
|
3688
3530
|
|
|
3689
3531
|
// src/commands/xray/precondition/list.ts
|
|
3690
|
-
function
|
|
3532
|
+
function list16(parent) {
|
|
3691
3533
|
const cmd = parent.command("list").description(
|
|
3692
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."
|
|
3693
3535
|
).requiredOption("--test <key>", "Test issue key whose pre-conditions to list (e.g. PROJ-584)", issueKey);
|
|
@@ -3700,7 +3542,7 @@ function list13(parent) {
|
|
|
3700
3542
|
}
|
|
3701
3543
|
|
|
3702
3544
|
// src/commands/xray/precondition/remove.ts
|
|
3703
|
-
function
|
|
3545
|
+
function remove5(parent) {
|
|
3704
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);
|
|
3705
3547
|
examples(cmd, ["PROJ-50 --test PROJ-1", "PROJ-50 --test PROJ-1,PROJ-2"]);
|
|
3706
3548
|
cmd.action(async (preKey, opts) => {
|
|
@@ -3711,9 +3553,9 @@ function remove4(parent) {
|
|
|
3711
3553
|
}
|
|
3712
3554
|
|
|
3713
3555
|
// src/commands/xray/precondition/update.ts
|
|
3714
|
-
import { Option as
|
|
3556
|
+
import { Option as Option11 } from "commander";
|
|
3715
3557
|
function update9(parent) {
|
|
3716
|
-
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"]));
|
|
3717
3559
|
textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
|
|
3718
3560
|
textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
|
|
3719
3561
|
examples(cmd, ['PROJ-50 --summary "Updated precondition"', 'PROJ-50 --type generic --condition "apiKey != null"']);
|
|
@@ -3761,12 +3603,12 @@ function registerPreconditionCommands(xray) {
|
|
|
3761
3603
|
"add PROJ-50 --test PROJ-1,PROJ-2",
|
|
3762
3604
|
"list --test PROJ-584"
|
|
3763
3605
|
]);
|
|
3764
|
-
|
|
3606
|
+
create12(precondition);
|
|
3765
3607
|
update9(precondition);
|
|
3766
3608
|
deletePrecondition(precondition);
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3609
|
+
add5(precondition);
|
|
3610
|
+
remove5(precondition);
|
|
3611
|
+
list16(precondition);
|
|
3770
3612
|
}
|
|
3771
3613
|
|
|
3772
3614
|
// src/utils/xray/run-id.ts
|
|
@@ -3780,7 +3622,7 @@ async function resolveRunId(client, o) {
|
|
|
3780
3622
|
}
|
|
3781
3623
|
|
|
3782
3624
|
// src/commands/xray/run/defect/add.ts
|
|
3783
|
-
function
|
|
3625
|
+
function add6(parent) {
|
|
3784
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);
|
|
3785
3627
|
examples(cmd, ["42 --defect BUG-1", "--execution PROJ-1 --test PROJ-2 --defect BUG-1,BUG-2"]);
|
|
3786
3628
|
cmd.action(async (runId, opts) => {
|
|
@@ -3792,7 +3634,7 @@ function add5(parent) {
|
|
|
3792
3634
|
}
|
|
3793
3635
|
|
|
3794
3636
|
// src/commands/xray/run/defect/remove.ts
|
|
3795
|
-
function
|
|
3637
|
+
function remove6(parent) {
|
|
3796
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);
|
|
3797
3639
|
examples(cmd, ["42 --defect BUG-1", "--execution PROJ-1 --test PROJ-2 --defect BUG-1,BUG-2"]);
|
|
3798
3640
|
cmd.action(async (runId, opts) => {
|
|
@@ -3829,12 +3671,12 @@ function registerDefectCommands(run) {
|
|
|
3829
3671
|
"Manage defects linked to a test run. See also: run update --defect for the one-command fail+link loop."
|
|
3830
3672
|
);
|
|
3831
3673
|
examples(defect, ["add --execution PROJ-1 --test PROJ-2 --defect BUG-1", "remove 42 --defect BUG-1"]);
|
|
3832
|
-
|
|
3833
|
-
|
|
3674
|
+
add6(defect);
|
|
3675
|
+
remove6(defect);
|
|
3834
3676
|
}
|
|
3835
3677
|
|
|
3836
3678
|
// src/commands/xray/run/evidence/add.ts
|
|
3837
|
-
import { readFileSync as
|
|
3679
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
3838
3680
|
import { basename as basename4, extname } from "path";
|
|
3839
3681
|
var CONTENT_TYPES = {
|
|
3840
3682
|
".png": "image/png",
|
|
@@ -3852,13 +3694,13 @@ var CONTENT_TYPES = {
|
|
|
3852
3694
|
function inferContentType(fp) {
|
|
3853
3695
|
return CONTENT_TYPES[extname(fp).toLowerCase()] ?? "application/octet-stream";
|
|
3854
3696
|
}
|
|
3855
|
-
function
|
|
3697
|
+
function add7(parent) {
|
|
3856
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);
|
|
3857
3699
|
examples(cmd, ["42 --file screenshot.png", "--execution PROJ-1 --test PROJ-2 --file report.pdf"]);
|
|
3858
3700
|
cmd.action(async (runId, opts) => {
|
|
3859
3701
|
const client = getClient();
|
|
3860
3702
|
const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
|
|
3861
|
-
const fileBuf =
|
|
3703
|
+
const fileBuf = readFileSync3(opts.file);
|
|
3862
3704
|
const filename = basename4(opts.file);
|
|
3863
3705
|
const contentType = inferContentType(opts.file);
|
|
3864
3706
|
const data = fileBuf.toString("base64");
|
|
@@ -3880,7 +3722,7 @@ function deleteEvidence(parent) {
|
|
|
3880
3722
|
}
|
|
3881
3723
|
|
|
3882
3724
|
// src/commands/xray/run/evidence/list.ts
|
|
3883
|
-
function
|
|
3725
|
+
function list17(parent) {
|
|
3884
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);
|
|
3885
3727
|
examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
|
|
3886
3728
|
cmd.action(async (runId, opts) => {
|
|
@@ -3899,13 +3741,13 @@ function registerEvidenceCommands(run) {
|
|
|
3899
3741
|
"list 42",
|
|
3900
3742
|
"delete 42 --evidence-id 7"
|
|
3901
3743
|
]);
|
|
3902
|
-
|
|
3903
|
-
|
|
3744
|
+
add7(evidence);
|
|
3745
|
+
list17(evidence);
|
|
3904
3746
|
deleteEvidence(evidence);
|
|
3905
3747
|
}
|
|
3906
3748
|
|
|
3907
3749
|
// src/commands/xray/run/field/get.ts
|
|
3908
|
-
function
|
|
3750
|
+
function get10(parent) {
|
|
3909
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);
|
|
3910
3752
|
examples(cmd, ["42 --field-id customfield_10100", "--execution PROJ-1 --test PROJ-2 --field-id customfield_10100"]);
|
|
3911
3753
|
cmd.action(async (runId, opts) => {
|
|
@@ -3937,12 +3779,12 @@ function set(parent) {
|
|
|
3937
3779
|
function registerRunFieldCommands(run) {
|
|
3938
3780
|
const field = run.command("field").description("Get or set Xray custom field values on a test run");
|
|
3939
3781
|
examples(field, ["get 42 --field-id customfield_10100", 'set 42 --field-id customfield_10100 --value "approved"']);
|
|
3940
|
-
|
|
3782
|
+
get10(field);
|
|
3941
3783
|
set(field);
|
|
3942
3784
|
}
|
|
3943
3785
|
|
|
3944
3786
|
// src/commands/xray/run/get.ts
|
|
3945
|
-
function
|
|
3787
|
+
function get11(parent) {
|
|
3946
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);
|
|
3947
3789
|
examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
|
|
3948
3790
|
cmd.action(async (runId, opts) => {
|
|
@@ -3954,7 +3796,7 @@ function get9(parent) {
|
|
|
3954
3796
|
}
|
|
3955
3797
|
|
|
3956
3798
|
// src/commands/xray/run/list.ts
|
|
3957
|
-
function
|
|
3799
|
+
function list18(parent) {
|
|
3958
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);
|
|
3959
3801
|
examples(cmd, ["--execution PROJ-1", "--execution PROJ-1 --limit 10", "--execution PROJ-1 --limit 10 --start 10"]);
|
|
3960
3802
|
cmd.action(async (opts) => {
|
|
@@ -3967,7 +3809,7 @@ function list15(parent) {
|
|
|
3967
3809
|
}
|
|
3968
3810
|
|
|
3969
3811
|
// src/commands/xray/run/step/list.ts
|
|
3970
|
-
function
|
|
3812
|
+
function list19(parent) {
|
|
3971
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);
|
|
3972
3814
|
examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
|
|
3973
3815
|
cmd.action(async (runId, opts) => {
|
|
@@ -4014,7 +3856,7 @@ function update10(parent) {
|
|
|
4014
3856
|
function registerRunStepCommands(run) {
|
|
4015
3857
|
const step = run.command("step").description("Manage step results within a test run. See also: xray step for test step definitions.");
|
|
4016
3858
|
examples(step, ["list --execution PROJ-1 --test PROJ-2", "update 42 --step-id 1 --status PASS"]);
|
|
4017
|
-
|
|
3859
|
+
list19(step);
|
|
4018
3860
|
update10(step);
|
|
4019
3861
|
}
|
|
4020
3862
|
|
|
@@ -4060,8 +3902,8 @@ function registerRunCommands(xray) {
|
|
|
4060
3902
|
"step list --execution PROJ-1 --test PROJ-2",
|
|
4061
3903
|
"field get 42 --field-id customfield_10100"
|
|
4062
3904
|
]);
|
|
4063
|
-
|
|
4064
|
-
|
|
3905
|
+
get11(run);
|
|
3906
|
+
list18(run);
|
|
4065
3907
|
update11(run);
|
|
4066
3908
|
registerDefectCommands(run);
|
|
4067
3909
|
registerEvidenceCommands(run);
|
|
@@ -4070,7 +3912,7 @@ function registerRunCommands(xray) {
|
|
|
4070
3912
|
}
|
|
4071
3913
|
|
|
4072
3914
|
// src/commands/xray/status/list.ts
|
|
4073
|
-
function
|
|
3915
|
+
function list20(parent) {
|
|
4074
3916
|
const cmd = parent.command("list").description("List configured Xray test (run) statuses").option("--step", "List test-step statuses instead of run statuses");
|
|
4075
3917
|
examples(cmd, ["", ["--step", "step statuses"]]);
|
|
4076
3918
|
cmd.action(async (opts) => {
|
|
@@ -4084,11 +3926,11 @@ function list17(parent) {
|
|
|
4084
3926
|
function registerStatusCommands(xray) {
|
|
4085
3927
|
const status = xray.command("status").description("Xray status discovery");
|
|
4086
3928
|
examples(status, ["list", ["list --step", "step statuses"]]);
|
|
4087
|
-
|
|
3929
|
+
list20(status);
|
|
4088
3930
|
}
|
|
4089
3931
|
|
|
4090
3932
|
// src/commands/xray/step/add.ts
|
|
4091
|
-
function
|
|
3933
|
+
function add8(parent) {
|
|
4092
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);
|
|
4093
3935
|
textOrFileOption(cmd, "action", { description: "Step action text (required: provide --action or --action-file)" });
|
|
4094
3936
|
textOrFileOption(cmd, "data", { description: "Step test data text" });
|
|
@@ -4121,7 +3963,7 @@ function deleteStep(parent) {
|
|
|
4121
3963
|
}
|
|
4122
3964
|
|
|
4123
3965
|
// src/commands/xray/step/list.ts
|
|
4124
|
-
function
|
|
3966
|
+
function list21(parent) {
|
|
4125
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);
|
|
4126
3968
|
examples(cmd, ["--test PROJ-584"]);
|
|
4127
3969
|
cmd.action(async (opts) => {
|
|
@@ -4164,23 +4006,23 @@ function registerStepCommands(xray) {
|
|
|
4164
4006
|
"Manage manual test steps for a Test issue (scope: --test). Steps are ordered by insertion; use step list to see current ids."
|
|
4165
4007
|
);
|
|
4166
4008
|
examples(step, ['add --test PROJ-584 --action "Open login" --result "Form shows"', "list --test PROJ-584"]);
|
|
4167
|
-
|
|
4168
|
-
|
|
4009
|
+
list21(step);
|
|
4010
|
+
add8(step);
|
|
4169
4011
|
update12(step);
|
|
4170
4012
|
deleteStep(step);
|
|
4171
4013
|
}
|
|
4172
4014
|
|
|
4173
4015
|
// src/commands/xray/test/create.ts
|
|
4174
|
-
import { Option as
|
|
4016
|
+
import { Option as Option12 } from "commander";
|
|
4175
4017
|
import { z as z5 } from "zod";
|
|
4176
4018
|
var stepsSchema = z5.array(
|
|
4177
4019
|
z5.object({ action: z5.string(), data: z5.string().optional(), result: z5.string().optional() })
|
|
4178
4020
|
);
|
|
4179
|
-
function
|
|
4021
|
+
function create13(parent) {
|
|
4180
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(
|
|
4181
|
-
new
|
|
4023
|
+
new Option12("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
|
|
4182
4024
|
).addOption(
|
|
4183
|
-
new
|
|
4025
|
+
new Option12("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
|
|
4184
4026
|
"scenario",
|
|
4185
4027
|
"scenario-outline"
|
|
4186
4028
|
])
|
|
@@ -4265,7 +4107,7 @@ function deleteTest(parent) {
|
|
|
4265
4107
|
}
|
|
4266
4108
|
|
|
4267
4109
|
// src/commands/xray/test/get.ts
|
|
4268
|
-
function
|
|
4110
|
+
function get12(parent) {
|
|
4269
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));
|
|
4270
4112
|
examples(cmd, ["PROJ-584", "PROJ-584 PROJ-585"]);
|
|
4271
4113
|
cmd.action(async (testKeys) => {
|
|
@@ -4276,7 +4118,7 @@ function get10(parent) {
|
|
|
4276
4118
|
}
|
|
4277
4119
|
|
|
4278
4120
|
// src/commands/xray/test/list.ts
|
|
4279
|
-
function
|
|
4121
|
+
function list22(parent) {
|
|
4280
4122
|
const cmd = parent.command("list").description(
|
|
4281
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."
|
|
4282
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);
|
|
@@ -4350,10 +4192,10 @@ function list19(parent) {
|
|
|
4350
4192
|
}
|
|
4351
4193
|
|
|
4352
4194
|
// src/commands/xray/test/update.ts
|
|
4353
|
-
import { Option as
|
|
4195
|
+
import { Option as Option13 } from "commander";
|
|
4354
4196
|
function update13(parent) {
|
|
4355
|
-
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
|
|
4356
|
-
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([
|
|
4357
4199
|
"scenario",
|
|
4358
4200
|
"scenario-outline"
|
|
4359
4201
|
])
|
|
@@ -4418,15 +4260,15 @@ function update13(parent) {
|
|
|
4418
4260
|
function registerTestCommands(xray) {
|
|
4419
4261
|
const test = xray.command("test").description("Xray Test issue management (CRUD)");
|
|
4420
4262
|
examples(test, ["get PROJ-584", 'create --project PROJ --summary "Login" --type manual']);
|
|
4421
|
-
|
|
4422
|
-
|
|
4263
|
+
get12(test);
|
|
4264
|
+
create13(test);
|
|
4423
4265
|
update13(test);
|
|
4424
4266
|
deleteTest(test);
|
|
4425
|
-
|
|
4267
|
+
list22(test);
|
|
4426
4268
|
}
|
|
4427
4269
|
|
|
4428
4270
|
// src/commands/xray/testset/add.ts
|
|
4429
|
-
function
|
|
4271
|
+
function add9(parent) {
|
|
4430
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);
|
|
4431
4273
|
examples(cmd, ["PROJ-100 --test PROJ-1", "PROJ-100 --test PROJ-1,PROJ-2,PROJ-3"]);
|
|
4432
4274
|
cmd.action(async (setKey, opts) => {
|
|
@@ -4437,7 +4279,7 @@ function add8(parent) {
|
|
|
4437
4279
|
}
|
|
4438
4280
|
|
|
4439
4281
|
// src/commands/xray/testset/create.ts
|
|
4440
|
-
function
|
|
4282
|
+
function create14(parent) {
|
|
4441
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);
|
|
4442
4284
|
textOrFileOption(cmd, "description", { description: "Test Set description" });
|
|
4443
4285
|
examples(cmd, [
|
|
@@ -4469,7 +4311,7 @@ function deleteTestset(parent) {
|
|
|
4469
4311
|
}
|
|
4470
4312
|
|
|
4471
4313
|
// src/commands/xray/testset/list.ts
|
|
4472
|
-
function
|
|
4314
|
+
function list23(parent) {
|
|
4473
4315
|
const cmd = parent.command("list").description(
|
|
4474
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>"
|
|
4475
4317
|
).requiredOption("--test <key>", "Test issue key whose sets to list (e.g. PROJ-584)", issueKey);
|
|
@@ -4482,7 +4324,7 @@ function list20(parent) {
|
|
|
4482
4324
|
}
|
|
4483
4325
|
|
|
4484
4326
|
// src/commands/xray/testset/remove.ts
|
|
4485
|
-
function
|
|
4327
|
+
function remove7(parent) {
|
|
4486
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);
|
|
4487
4329
|
examples(cmd, ["PROJ-100 --test PROJ-1", "PROJ-100 --test PROJ-1,PROJ-2"]);
|
|
4488
4330
|
cmd.action(async (setKey, opts) => {
|
|
@@ -4511,12 +4353,12 @@ function registerTestsetCommands(xray) {
|
|
|
4511
4353
|
"Xray Test Set issue management (CRUD, membership). To list tests inside a set, use: xray test list --set <key>"
|
|
4512
4354
|
);
|
|
4513
4355
|
examples(testset, ['create --project PROJ --summary "Smoke tests"', "add PROJ-100 --test PROJ-1,PROJ-2"]);
|
|
4514
|
-
|
|
4356
|
+
create14(testset);
|
|
4515
4357
|
update14(testset);
|
|
4516
4358
|
deleteTestset(testset);
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4359
|
+
add9(testset);
|
|
4360
|
+
remove7(testset);
|
|
4361
|
+
list23(testset);
|
|
4520
4362
|
}
|
|
4521
4363
|
|
|
4522
4364
|
// src/commands/xray/index.ts
|
|
@@ -4532,7 +4374,7 @@ function registerXrayCommands(program) {
|
|
|
4532
4374
|
registerPlanCommands(xray);
|
|
4533
4375
|
registerStepCommands(xray);
|
|
4534
4376
|
registerRunCommands(xray);
|
|
4535
|
-
|
|
4377
|
+
registerFolderCommands2(xray);
|
|
4536
4378
|
registerImportCommands(xray);
|
|
4537
4379
|
registerExportCommands(xray);
|
|
4538
4380
|
}
|
|
@@ -4579,6 +4421,7 @@ ${styleText("bold", "Examples:")}
|
|
|
4579
4421
|
registerFieldCommands(program);
|
|
4580
4422
|
registerUserCommands(program);
|
|
4581
4423
|
registerTokenCommands(program);
|
|
4424
|
+
registerStructureCommands(program);
|
|
4582
4425
|
registerXrayCommands(program);
|
|
4583
4426
|
return program;
|
|
4584
4427
|
}
|