jiradc-cli 1.0.28 → 1.0.30

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.
Files changed (2) hide show
  1. package/dist/index.js +1666 -8
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -53,6 +53,18 @@ function listOf(item) {
53
53
  function text(raw) {
54
54
  return raw;
55
55
  }
56
+ function nonEmpty(raw) {
57
+ if (raw.trim() === "") {
58
+ throw new InvalidArgumentError("Must not be empty.");
59
+ }
60
+ return raw;
61
+ }
62
+ function integer(raw) {
63
+ if (!/^[+-]?\d+$/.test(raw.trim())) {
64
+ throw new InvalidArgumentError("Must be an integer.");
65
+ }
66
+ return Number(raw);
67
+ }
56
68
  function date(raw) {
57
69
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
58
70
  if (!m) {
@@ -412,7 +424,7 @@ async function runCli(program, opts) {
412
424
 
413
425
  // src/program.ts
414
426
  import { styleText } from "util";
415
- import { Command as Command11 } from "commander";
427
+ import { Command as Command16 } from "commander";
416
428
 
417
429
  // src/commands/board/issues.ts
418
430
  import { Argument } from "commander";
@@ -762,6 +774,134 @@ function transformTransitions(response) {
762
774
  return response.transitions.map(transformTransition);
763
775
  }
764
776
 
777
+ // src/utils/transformers/xray.ts
778
+ function extractStepFields(s) {
779
+ const out = {};
780
+ const raw = s.fields?.Action?.value?.raw;
781
+ if (raw !== void 0) out.action = raw;
782
+ const data = s.fields?.Data?.value?.raw;
783
+ if (data !== void 0) out.data = data;
784
+ const result = s.fields?.["Expected Result"]?.value?.raw;
785
+ if (result !== void 0) out.result = result;
786
+ return out;
787
+ }
788
+ function extractDefinitionStep(s) {
789
+ const out = {};
790
+ if (s.id !== void 0) out.id = s.id;
791
+ if (s.index !== void 0) out.index = s.index;
792
+ if (s.step?.raw !== void 0) out.action = s.step.raw;
793
+ if (s.data?.raw !== void 0) out.data = s.data.raw;
794
+ if (s.result?.raw !== void 0) out.result = s.result.raw;
795
+ return out;
796
+ }
797
+ function transformStep(s) {
798
+ return {
799
+ ...s.id !== void 0 && { id: s.id },
800
+ ...s.index !== void 0 && { index: s.index },
801
+ ...extractStepFields(s)
802
+ };
803
+ }
804
+ function transformTest(test) {
805
+ const out = {};
806
+ if (test.key !== void 0) out.key = test.key;
807
+ if (test.type !== void 0) out.type = test.type;
808
+ if (test.status !== void 0) out.status = test.status;
809
+ const rawSteps = test.definition?.steps;
810
+ if (Array.isArray(rawSteps) && rawSteps.length > 0) {
811
+ out.steps = rawSteps.map((s) => extractDefinitionStep(s));
812
+ }
813
+ return out;
814
+ }
815
+ function transformTestList(result) {
816
+ const tests = Array.isArray(result) ? result : result.tests ?? [];
817
+ return tests.map((t) => ({
818
+ ...t.key !== void 0 && { key: String(t.key) },
819
+ ...t.summary !== void 0 && { summary: String(t.summary) },
820
+ ...typeof t.status === "string" && { status: t.status },
821
+ ...typeof t.type === "string" && { type: t.type }
822
+ }));
823
+ }
824
+ function transformLink(link2) {
825
+ const out = {};
826
+ if (link2.key !== void 0) out.key = String(link2.key);
827
+ if (link2.summary !== void 0) out.summary = String(link2.summary);
828
+ if (typeof link2.status === "string") out.status = link2.status;
829
+ return out;
830
+ }
831
+ function transformLinks(links) {
832
+ return links.map(transformLink);
833
+ }
834
+ function transformStatuses(statuses) {
835
+ return statuses.map((s) => ({
836
+ ...s.name !== void 0 && { name: s.name },
837
+ ...s.description !== void 0 && { description: s.description },
838
+ ...s.final !== void 0 && { final: s.final },
839
+ ...s.color !== void 0 && { color: s.color },
840
+ ...s.requirementStatusName !== void 0 && { requirementStatusName: s.requirementStatusName }
841
+ }));
842
+ }
843
+ var XRAY_CUSTOM_MARKER = "xpandit";
844
+ function isXrayField(f) {
845
+ return f.schema?.custom?.includes(XRAY_CUSTOM_MARKER) === true;
846
+ }
847
+ function transformFields(fields) {
848
+ return fields.filter(isXrayField).map((f) => ({
849
+ id: f.id,
850
+ name: f.name,
851
+ ...f.schema?.type !== void 0 && { type: f.schema.type }
852
+ }));
853
+ }
854
+ function transformEvidence(evidence) {
855
+ return evidence.map((e) => ({
856
+ ...e.id !== void 0 && { id: e.id },
857
+ ...e.fileName !== void 0 && { fileName: e.fileName },
858
+ ...e.fileSize !== void 0 && { fileSize: e.fileSize },
859
+ ...e.contentType !== void 0 && { contentType: e.contentType },
860
+ ...e.created !== void 0 && { created: e.created }
861
+ }));
862
+ }
863
+ function transformRunSteps(steps) {
864
+ return steps.map((s) => ({
865
+ ...s.id !== void 0 && { id: s.id },
866
+ ...s.status !== void 0 && { status: s.status },
867
+ ...s.comment !== void 0 && { comment: s.comment },
868
+ ...s.actualResult !== void 0 && { actualResult: s.actualResult }
869
+ }));
870
+ }
871
+ function transformRun(run) {
872
+ const out = {};
873
+ if (run.id !== void 0) out.id = run.id;
874
+ if (run.status !== void 0) out.status = run.status;
875
+ if (run["testKey"] !== void 0) out.testKey = run["testKey"];
876
+ if (run["testExecKey"] !== void 0) out.testExecKey = run["testExecKey"];
877
+ if (run.assignee !== void 0) out.assignee = run.assignee;
878
+ if (run.executedBy !== void 0) out.executedBy = run.executedBy;
879
+ if (run.comment !== void 0) out.comment = run.comment;
880
+ if (run["defects"] !== void 0) out.defects = run["defects"];
881
+ if (Array.isArray(run["steps"])) {
882
+ out.steps = run["steps"].map((s) => ({
883
+ ...s.id !== void 0 && { id: s.id },
884
+ ...s.index !== void 0 && { index: s.index },
885
+ ...extractStepFields(s)
886
+ }));
887
+ }
888
+ return out;
889
+ }
890
+ function transformFolderNode(folder) {
891
+ const out = {};
892
+ if (folder.id !== void 0) out.id = folder.id;
893
+ if (folder.name !== void 0) out.name = folder.name;
894
+ if (folder.testRepositoryPath !== void 0) out.path = folder.testRepositoryPath;
895
+ if (folder.testCount !== void 0) out.testCount = folder.testCount;
896
+ if (Array.isArray(folder.folders) && folder.folders.length > 0) {
897
+ out.folders = folder.folders.map(transformFolderNode);
898
+ }
899
+ return out;
900
+ }
901
+ function transformFolderTree(root) {
902
+ return transformFolderNode(root);
903
+ }
904
+
765
905
  // src/commands/board/issues.ts
766
906
  function issues(parent) {
767
907
  const cmd = parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return", text).option("--jql <jql>", "Additional JQL filter within the board", text);
@@ -992,6 +1132,7 @@ async function resolveUserToken(token) {
992
1132
  }
993
1133
 
994
1134
  // src/utils/validators.ts
1135
+ import { existsSync } from "fs";
995
1136
  import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
996
1137
  function issueKey(raw) {
997
1138
  if (!/^(\d+|[A-Z][A-Z0-9]+-\d+)$/.test(raw)) {
@@ -999,6 +1140,16 @@ function issueKey(raw) {
999
1140
  }
1000
1141
  return raw;
1001
1142
  }
1143
+ function keyList(raw) {
1144
+ const keys = raw.split(",").map((k) => k.trim()).filter(Boolean);
1145
+ if (keys.length === 0) throw new InvalidArgumentError4("Provide at least one comma-separated issue key.");
1146
+ for (const k of keys) issueKey(k);
1147
+ return keys;
1148
+ }
1149
+ function filePath(raw) {
1150
+ if (!existsSync(raw)) throw new InvalidArgumentError4(`File not found: ${raw}`);
1151
+ return raw;
1152
+ }
1002
1153
 
1003
1154
  // src/commands/issue/assign.ts
1004
1155
  function assign(parent) {
@@ -1130,8 +1281,8 @@ function upload(parent) {
1130
1281
  const client = getClient();
1131
1282
  const filePaths = opts.files.split(",").map((f) => f.trim());
1132
1283
  const results = [];
1133
- for (const filePath of filePaths) {
1134
- const attachments = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
1284
+ for (const filePath2 of filePaths) {
1285
+ const attachments = await client.issues.addAttachment({ issueKeyOrId: key, filePath: filePath2 });
1135
1286
  results.push(...attachments);
1136
1287
  }
1137
1288
  output({
@@ -1171,9 +1322,9 @@ function batchChangelog(parent) {
1171
1322
  examples(cmd, ["PROJ-1,PROJ-2,PROJ-3", "PROJ-123,PROJ-124 --limit 10"]);
1172
1323
  cmd.action(async (keys, opts) => {
1173
1324
  const client = getClient();
1174
- const keyList = keys.split(",").map((k) => k.trim());
1325
+ const keyList2 = keys.split(",").map((k) => k.trim());
1175
1326
  const entries = await Promise.all(
1176
- keyList.map(async (key) => {
1327
+ keyList2.map(async (key) => {
1177
1328
  try {
1178
1329
  const data = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.limit });
1179
1330
  return [key, data];
@@ -1799,8 +1950,8 @@ function update3(parent) {
1799
1950
  const uploaded = [];
1800
1951
  if (opts.attachments) {
1801
1952
  const filePaths = opts.attachments.split(",").map((p) => p.trim()).filter(Boolean);
1802
- for (const filePath of filePaths) {
1803
- const result = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
1953
+ for (const filePath2 of filePaths) {
1954
+ const result = await client.issues.addAttachment({ issueKeyOrId: key, filePath: filePath2 });
1804
1955
  for (const att of result) {
1805
1956
  uploaded.push({ filename: att.filename, id: att.id });
1806
1957
  }
@@ -2246,11 +2397,1517 @@ function registerUserCommands(program) {
2246
2397
  search3(user);
2247
2398
  }
2248
2399
 
2400
+ // src/utils/xray/guards.ts
2401
+ function assertExactlyOne(opts, keys, label) {
2402
+ const present = keys.filter((k) => opts[k] !== void 0);
2403
+ if (present.length !== 1)
2404
+ throw new Error(`Provide exactly one of ${keys.map((k) => `--${k}`).join(", ")} (${label}).`);
2405
+ return present[0];
2406
+ }
2407
+ function assertAtLeastOne(opts, keys) {
2408
+ if (!keys.some((k) => opts[k] !== void 0))
2409
+ throw new Error(`Provide at least one of ${keys.map((k) => `--${k}`).join(", ")}.`);
2410
+ }
2411
+
2412
+ // src/commands/xray/execution/add.ts
2413
+ function add(parent) {
2414
+ 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. AI-200)", issueKey).option("--test <keys>", "Comma-separated test issue keys to add (e.g. AI-1,AI-2)", keyList).option(
2415
+ "--set <keys>",
2416
+ "Comma-separated Test Set keys; each set's current tests are added to the execution (e.g. AI-300)",
2417
+ keyList
2418
+ );
2419
+ examples(cmd, [
2420
+ "AI-200 --test AI-1",
2421
+ "AI-200 --test AI-1,AI-2",
2422
+ "AI-200 --set AI-300",
2423
+ "AI-200 --test AI-1 --set AI-300"
2424
+ ]);
2425
+ cmd.action(async (execKey, opts) => {
2426
+ assertAtLeastOne(opts, ["test", "set"]);
2427
+ const client = getClient();
2428
+ const keys = new Set(opts.test ?? []);
2429
+ if (opts.set) {
2430
+ const lists = await Promise.all(opts.set.map((setKey) => client.testSets.listTests({ setKey })));
2431
+ for (const list20 of lists) {
2432
+ const tests = Array.isArray(list20) ? list20 : list20.tests ?? [];
2433
+ for (const t of tests) {
2434
+ if (t.key) keys.add(String(t.key));
2435
+ }
2436
+ }
2437
+ }
2438
+ const allKeys = [...keys];
2439
+ if (allKeys.length === 0) {
2440
+ throw new Error("No tests to add \u2014 the given --set(s) contain no tests and no --test keys were provided.");
2441
+ }
2442
+ const result = await client.testExecutions.addTests({ execKey, keys: allKeys });
2443
+ output(result ?? { added: allKeys });
2444
+ });
2445
+ }
2446
+
2447
+ // src/utils/xray/issue-entity.ts
2448
+ var XRAY_ISSUE_TYPES = {
2449
+ Test: "Test",
2450
+ TestSet: "Test Set",
2451
+ TestPlan: "Test Plan",
2452
+ TestExecution: "Test Execution",
2453
+ PreCondition: "Pre-Condition"
2454
+ };
2455
+ function createXrayIssue(client, params) {
2456
+ return client.issues.create({
2457
+ projectKeyOrId: params.project,
2458
+ issueTypeName: params.type,
2459
+ summary: params.summary,
2460
+ description: params.description,
2461
+ customFields: params.customFields
2462
+ });
2463
+ }
2464
+ function updateXrayIssue(client, key, params) {
2465
+ const fields = {};
2466
+ if (params.summary !== void 0) fields.summary = params.summary;
2467
+ if (params.description !== void 0) fields.description = params.description;
2468
+ if (params.customFields !== void 0) Object.assign(fields, params.customFields);
2469
+ return client.issues.update({ issueKeyOrId: key, fields });
2470
+ }
2471
+ function deleteXrayIssue(client, key) {
2472
+ return client.issues.delete({ issueKeyOrId: key });
2473
+ }
2474
+
2475
+ // src/commands/xray/execution/create.ts
2476
+ function create7(parent) {
2477
+ const cmd = parent.command("create").description("Create a new Xray Test Execution issue").requiredOption("--project <key>", "Project key (e.g. AI)", text).requiredOption("--summary <text>", "Test Execution summary (title)", text);
2478
+ textOrFileOption(cmd, "description", { description: "Test Execution description" });
2479
+ examples(cmd, [
2480
+ '--project AI --summary "Sprint 42 regression run"',
2481
+ '--project AI --summary "Smoke run" --description "Post-deploy smoke"'
2482
+ ]);
2483
+ cmd.action(async (opts) => {
2484
+ const description = resolveTextOrFile(opts, "description", { required: false });
2485
+ const client = getClient();
2486
+ const result = await createXrayIssue(client, {
2487
+ project: opts.project,
2488
+ type: XRAY_ISSUE_TYPES.TestExecution,
2489
+ summary: opts.summary,
2490
+ description
2491
+ });
2492
+ output(transformCreatedIssue(result));
2493
+ });
2494
+ }
2495
+
2496
+ // src/commands/xray/execution/delete.ts
2497
+ function deleteExecution(parent) {
2498
+ const cmd = parent.command("delete").description("Delete an Xray Test Execution issue").argument("<execKey>", "Test Execution issue key (e.g. AI-200)", issueKey);
2499
+ examples(cmd, ["AI-200"]);
2500
+ cmd.action(async (execKey) => {
2501
+ const client = getClient();
2502
+ await deleteXrayIssue(client, execKey);
2503
+ output({ deleted: true, key: execKey });
2504
+ });
2505
+ }
2506
+
2507
+ // src/commands/xray/execution/list.ts
2508
+ function list8(parent) {
2509
+ const cmd = parent.command("list").description(
2510
+ "List Test Executions that contain a given test (inverse view). For the tests inside an execution, use: xray test list --execution <key>."
2511
+ ).requiredOption("--test <key>", "Test issue key whose executions to list (e.g. AI-584)", issueKey);
2512
+ examples(cmd, ["--test AI-584"]);
2513
+ cmd.action(async (opts) => {
2514
+ const client = getClient();
2515
+ const executions = await client.xrayTests.getTestExecutions({ testKey: opts.test });
2516
+ output(transformLinks(executions));
2517
+ });
2518
+ }
2519
+
2520
+ // src/commands/xray/execution/remove.ts
2521
+ function remove(parent) {
2522
+ const cmd = parent.command("remove").description("Remove tests from a Test Execution").argument("<execKey>", "Test Execution issue key (e.g. AI-200)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. AI-1,AI-2)", keyList);
2523
+ examples(cmd, ["AI-200 --test AI-1", "AI-200 --test AI-1,AI-2"]);
2524
+ cmd.action(async (execKey, opts) => {
2525
+ const client = getClient();
2526
+ const result = await client.testExecutions.removeTests({ execKey, keys: opts.test });
2527
+ output(result ?? { removed: true });
2528
+ });
2529
+ }
2530
+
2531
+ // src/commands/xray/execution/update.ts
2532
+ function update6(parent) {
2533
+ const cmd = parent.command("update").description("Update an Xray Test Execution issue").argument("<execKey>", "Test Execution issue key (e.g. AI-200)", issueKey).option("--summary <text>", "New summary (title)", text);
2534
+ textOrFileOption(cmd, "description", { description: "New Test Execution description" });
2535
+ examples(cmd, ['AI-200 --summary "Updated execution title"', 'AI-200 --description "Post-hotfix run"']);
2536
+ cmd.action(async (execKey, opts) => {
2537
+ const description = resolveTextOrFile(opts, "description", { required: false });
2538
+ const client = getClient();
2539
+ await updateXrayIssue(client, execKey, { summary: opts.summary, description });
2540
+ output({ key: execKey });
2541
+ });
2542
+ }
2543
+
2544
+ // src/commands/xray/execution/index.ts
2545
+ function registerExecutionCommands(xray) {
2546
+ const execution2 = xray.command("execution").description(
2547
+ "Xray Test Execution issue management (CRUD, membership). To list tests inside an execution, use: xray test list --execution <key>."
2548
+ );
2549
+ examples(execution2, [
2550
+ 'create --project AI --summary "Sprint 42 regression run"',
2551
+ "add AI-200 --test AI-1,AI-2",
2552
+ "list --test AI-584"
2553
+ ]);
2554
+ create7(execution2);
2555
+ update6(execution2);
2556
+ deleteExecution(execution2);
2557
+ add(execution2);
2558
+ remove(execution2);
2559
+ list8(execution2);
2560
+ }
2561
+
2562
+ // src/commands/xray/export/feature.ts
2563
+ import { writeFileSync as writeFileSync2 } from "fs";
2564
+ import { basename, join as join5 } from "path";
2565
+ function feature(parent) {
2566
+ 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");
2567
+ examples(cmd, ["--test AI-584 --output ai-584.feature"]);
2568
+ cmd.action(async (opts) => {
2569
+ const client = getClient();
2570
+ const { data, filename } = await client.xrayExport.exportFeature({
2571
+ keys: opts.test,
2572
+ zip: opts.zip
2573
+ });
2574
+ const writePath = opts.output ?? join5(".", basename(filename));
2575
+ writeFileSync2(writePath, data);
2576
+ output({ written: writePath, bytes: data.length });
2577
+ });
2578
+ }
2579
+
2580
+ // src/commands/xray/export/index.ts
2581
+ function registerExportCommands(xray) {
2582
+ const exportGroup = xray.command("export").description("Export Xray test artifacts (feature files)");
2583
+ feature(exportGroup);
2584
+ }
2585
+
2586
+ // src/commands/xray/field/list.ts
2587
+ function list9(parent) {
2588
+ const cmd = parent.command("list").description("List Xray custom fields available on this instance");
2589
+ examples(cmd, [""]);
2590
+ cmd.action(async () => {
2591
+ const client = getClient();
2592
+ output(transformFields(await client.fields.getAll()));
2593
+ });
2594
+ }
2595
+
2596
+ // src/commands/xray/field/index.ts
2597
+ function registerFieldCommands2(xray) {
2598
+ const field = xray.command("field").description("Xray field discovery");
2599
+ examples(field, ["list"]);
2600
+ list9(field);
2601
+ }
2602
+
2603
+ // src/commands/xray/folder/add.ts
2604
+ function add2(parent) {
2605
+ 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. QA)", text).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. QA-1,QA-2)", keyList);
2606
+ examples(cmd, ["818 --project QA --test QA-1", "818 --project QA --test QA-1,QA-2,QA-3"]);
2607
+ cmd.action(async (folderId, opts) => {
2608
+ const client = getClient();
2609
+ const result = await client.testRepository.updateFolderTests({
2610
+ projectKey: opts.project,
2611
+ folderId,
2612
+ add: opts.test
2613
+ });
2614
+ output(result ?? { added: true });
2615
+ });
2616
+ }
2617
+
2618
+ // src/commands/xray/folder/create.ts
2619
+ function create8(parent) {
2620
+ const cmd = parent.command("create").description("Create a new folder in the test repository").requiredOption("--project <key>", "Project key (e.g. QA)", text).option("--parent-id <id>", "Parent folder id (-1 = root)", integer, -1).requiredOption("--name <text>", "Folder name", text);
2621
+ examples(cmd, [
2622
+ "--project QA --name Smoke",
2623
+ '--project QA --parent-id -1 --name "Regression Suite"',
2624
+ '--project AI --parent-id 818 --name "Sub-folder"'
2625
+ ]);
2626
+ cmd.action(async (opts) => {
2627
+ const client = getClient();
2628
+ const result = await client.testRepository.createFolder({
2629
+ projectKey: opts.project,
2630
+ parentId: opts.parentId,
2631
+ name: opts.name
2632
+ });
2633
+ output(result);
2634
+ });
2635
+ }
2636
+
2637
+ // src/commands/xray/folder/delete.ts
2638
+ function deleteFolder(parent) {
2639
+ const cmd = parent.command("delete").description("Delete a folder from the test repository").argument("<folderId>", "Folder id to delete (from folder list)", positiveInt).requiredOption("--project <key>", "Project key (e.g. QA)", text);
2640
+ examples(cmd, ["818 --project QA", "42 --project AI"]);
2641
+ cmd.action(async (folderId, opts) => {
2642
+ const client = getClient();
2643
+ await client.testRepository.deleteFolder({ projectKey: opts.project, folderId });
2644
+ output({ folderId, deleted: true });
2645
+ });
2646
+ }
2647
+
2648
+ // src/commands/xray/folder/list.ts
2649
+ function list10(parent) {
2650
+ const cmd = parent.command("list").description("List the test repository folder tree for a project").requiredOption("--project <key>", "Project key (e.g. QA)", text);
2651
+ examples(cmd, ["--project QA", "--project AI"]);
2652
+ cmd.action(async (opts) => {
2653
+ const client = getClient();
2654
+ const tree = await client.testRepository.getFolders({ projectKey: opts.project });
2655
+ output(transformFolderTree(tree));
2656
+ });
2657
+ }
2658
+
2659
+ // src/commands/xray/folder/remove.ts
2660
+ function remove2(parent) {
2661
+ 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. QA)", text).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. QA-1,QA-2)", keyList);
2662
+ examples(cmd, ["818 --project QA --test QA-1", "818 --project QA --test QA-1,QA-2"]);
2663
+ cmd.action(async (folderId, opts) => {
2664
+ const client = getClient();
2665
+ const result = await client.testRepository.updateFolderTests({
2666
+ projectKey: opts.project,
2667
+ folderId,
2668
+ remove: opts.test
2669
+ });
2670
+ output(result ?? { removed: true });
2671
+ });
2672
+ }
2673
+
2674
+ // src/commands/xray/folder/update.ts
2675
+ function update7(parent) {
2676
+ const cmd = parent.command("update").description("Rename or reorder a folder in the test repository").argument("<folderId>", "Folder id (from folder list)", positiveInt).requiredOption("--project <key>", "Project key (e.g. QA)", text).option("--name <text>", "New folder name", text).option("--rank <id>", "New folder rank/position", positiveInt);
2677
+ examples(cmd, [
2678
+ '818 --project QA --name "New Name"',
2679
+ "818 --project QA --rank 2",
2680
+ "818 --project AI --name Smoke --rank 1"
2681
+ ]);
2682
+ cmd.action(async (folderId, opts) => {
2683
+ if (opts.name === void 0 && opts.rank === void 0) {
2684
+ throw new Error("Provide at least one of --name or --rank.");
2685
+ }
2686
+ const client = getClient();
2687
+ await client.testRepository.updateFolder({
2688
+ projectKey: opts.project,
2689
+ folderId,
2690
+ name: opts.name,
2691
+ rank: opts.rank
2692
+ });
2693
+ output({ folderId, updated: true });
2694
+ });
2695
+ }
2696
+
2697
+ // src/commands/xray/folder/index.ts
2698
+ function registerFolderCommands(xray) {
2699
+ const folder = xray.command("folder").description(
2700
+ "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>"
2701
+ );
2702
+ examples(folder, ["list --project QA", "create --project QA --parent-id -1 --name Smoke"]);
2703
+ list10(folder);
2704
+ create8(folder);
2705
+ update7(folder);
2706
+ deleteFolder(folder);
2707
+ add2(folder);
2708
+ remove2(folder);
2709
+ }
2710
+
2711
+ // src/commands/xray/import/execution.ts
2712
+ import { readFileSync as readFileSync4 } from "fs";
2713
+ import { basename as basename2 } from "path";
2714
+ import { Option as Option9 } from "commander";
2715
+ var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
2716
+ var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
2717
+ function execution(parent) {
2718
+ const cmd = parent.command("execution").description("Import test execution results into Xray").requiredOption("--file <path>", "Path to the result file", filePath).addOption(
2719
+ new Option9(
2720
+ "--format <format>",
2721
+ "Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires --project OR --execution."
2722
+ ).choices(FORMATS).makeOptionMandatory()
2723
+ ).option(
2724
+ "--project <text>",
2725
+ "Project key \u2014 required for junit/testng/nunit/xunit/robot when --execution is not provided",
2726
+ text
2727
+ ).option(
2728
+ "--execution <key>",
2729
+ "Import into an existing Test Execution \u2014 required for junit/testng/nunit/xunit/robot when --project is not provided",
2730
+ issueKey
2731
+ ).option("--plan <key>", "Associate with a Test Plan issue key", issueKey).option("--fix-version <text>", "Fix version to set on the Test Execution", text).option("--revision <text>", "Source control revision", text).option("--environments <text>", "Comma-separated test environment names", text);
2732
+ examples(cmd, [
2733
+ "--file results.xml --format junit --project AI",
2734
+ "--file results.xml --format junit --execution AI-100",
2735
+ "--file results.json --format xray"
2736
+ ]);
2737
+ cmd.action(
2738
+ async (opts) => {
2739
+ if (NEEDS_SCOPE.has(opts.format) && !opts.project && !opts.execution) {
2740
+ throw new Error(
2741
+ `--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.`
2742
+ );
2743
+ }
2744
+ const body = readFileSync4(opts.file);
2745
+ const query = {};
2746
+ if (opts.project) query["projectKey"] = opts.project;
2747
+ if (opts.execution) query["testExecKey"] = opts.execution;
2748
+ if (opts.plan) query["testPlanKey"] = opts.plan;
2749
+ if (opts.fixVersion) query["fixVersion"] = opts.fixVersion;
2750
+ if (opts.revision) query["revision"] = opts.revision;
2751
+ if (opts.environments) query["testEnvironments"] = opts.environments;
2752
+ const client = getClient();
2753
+ const result = await client.xrayImport.importExecution({
2754
+ format: opts.format,
2755
+ body,
2756
+ filename: basename2(opts.file),
2757
+ query: Object.keys(query).length > 0 ? query : void 0
2758
+ });
2759
+ output(result);
2760
+ }
2761
+ );
2762
+ }
2763
+
2764
+ // src/commands/xray/import/feature.ts
2765
+ import { readFileSync as readFileSync5 } from "fs";
2766
+ import { basename as basename3 } from "path";
2767
+ function feature2(parent) {
2768
+ 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);
2769
+ examples(cmd, ["--file login.feature --project AI"]);
2770
+ cmd.action(async (opts) => {
2771
+ const fileBuffer = readFileSync5(opts.file);
2772
+ const filename = basename3(opts.file);
2773
+ const client = getClient();
2774
+ const result = await client.xrayImport.importFeature({
2775
+ projectKey: opts.project,
2776
+ file: fileBuffer,
2777
+ filename
2778
+ });
2779
+ output(result);
2780
+ });
2781
+ }
2782
+
2783
+ // src/commands/xray/import/index.ts
2784
+ function registerImportCommands(xray) {
2785
+ const importGroup = xray.command("import").description("Import test results and feature files into Xray");
2786
+ execution(importGroup);
2787
+ feature2(importGroup);
2788
+ }
2789
+
2790
+ // src/commands/xray/plan/add.ts
2791
+ function add3(parent) {
2792
+ const cmd = parent.command("add").description("Add tests and/or executions to a Test Plan").argument("<planKey>", "Test Plan issue key (e.g. QA-573)", issueKey).option("--test <keys>", "Comma-separated test issue keys to add (e.g. QA-1,QA-2)", keyList).option("--execution <keys>", "Comma-separated test execution keys to add (e.g. QA-200,QA-201)", keyList);
2793
+ examples(cmd, ["QA-573 --test QA-1,QA-2", "QA-573 --execution QA-200", "QA-573 --test QA-1 --execution QA-200"]);
2794
+ cmd.action(async (planKey, opts) => {
2795
+ assertAtLeastOne(opts, ["test", "execution"]);
2796
+ const client = getClient();
2797
+ const promises = [];
2798
+ if (opts.test) promises.push(client.testPlans.addTests({ planKey, keys: opts.test }));
2799
+ if (opts.execution) promises.push(client.testPlans.addExecutions({ planKey, keys: opts.execution }));
2800
+ const out = await Promise.all(promises);
2801
+ output(out);
2802
+ });
2803
+ }
2804
+
2805
+ // src/commands/xray/plan/create.ts
2806
+ function create9(parent) {
2807
+ const cmd = parent.command("create").description("Create a new Xray Test Plan issue").requiredOption("--project <key>", "Project key (e.g. QA)", text).requiredOption("--summary <text>", "Test Plan summary (title)", text);
2808
+ textOrFileOption(cmd, "description", { description: "Test Plan description" });
2809
+ examples(cmd, [
2810
+ '--project QA --summary "Q3 regression plan"',
2811
+ '--project QA --summary "Sprint 42 plan" --description "Covers all sprint stories"'
2812
+ ]);
2813
+ cmd.action(async (opts) => {
2814
+ const description = resolveTextOrFile(opts, "description", { required: false });
2815
+ const client = getClient();
2816
+ const result = await createXrayIssue(client, {
2817
+ project: opts.project,
2818
+ type: XRAY_ISSUE_TYPES.TestPlan,
2819
+ summary: opts.summary,
2820
+ description
2821
+ });
2822
+ output(transformCreatedIssue(result));
2823
+ });
2824
+ }
2825
+
2826
+ // src/commands/xray/plan/delete.ts
2827
+ function deletePlan(parent) {
2828
+ const cmd = parent.command("delete").description("Delete an Xray Test Plan issue").argument("<planKey>", "Test Plan issue key (e.g. QA-573)", issueKey);
2829
+ examples(cmd, ["QA-573"]);
2830
+ cmd.action(async (planKey) => {
2831
+ const client = getClient();
2832
+ await deleteXrayIssue(client, planKey);
2833
+ output({ deleted: true, key: planKey });
2834
+ });
2835
+ }
2836
+
2837
+ // src/commands/xray/plan/list.ts
2838
+ function list11(parent) {
2839
+ const cmd = parent.command("list").description(
2840
+ "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>."
2841
+ ).requiredOption("--test <key>", "Test issue key whose plans to list (e.g. QA-574)", issueKey);
2842
+ examples(cmd, ["--test QA-574"]);
2843
+ cmd.action(async (opts) => {
2844
+ const client = getClient();
2845
+ const plans = await client.xrayTests.getTestPlans({ testKey: opts.test });
2846
+ output(transformLinks(plans));
2847
+ });
2848
+ }
2849
+
2850
+ // src/commands/xray/plan/remove.ts
2851
+ function remove3(parent) {
2852
+ const cmd = parent.command("remove").description("Remove tests and/or executions from a Test Plan").argument("<planKey>", "Test Plan issue key (e.g. QA-573)", issueKey).option("--test <keys>", "Comma-separated test issue keys to remove (e.g. QA-1,QA-2)", keyList).option("--execution <keys>", "Comma-separated test execution keys to remove (e.g. QA-200)", keyList);
2853
+ examples(cmd, ["QA-573 --test QA-1", "QA-573 --execution QA-200", "QA-573 --test QA-1 --execution QA-200"]);
2854
+ cmd.action(async (planKey, opts) => {
2855
+ assertAtLeastOne(opts, ["test", "execution"]);
2856
+ const client = getClient();
2857
+ const promises = [];
2858
+ if (opts.test) promises.push(client.testPlans.removeTests({ planKey, keys: opts.test }));
2859
+ if (opts.execution) promises.push(client.testPlans.removeExecutions({ planKey, keys: opts.execution }));
2860
+ const out = await Promise.all(promises);
2861
+ output(out);
2862
+ });
2863
+ }
2864
+
2865
+ // src/commands/xray/plan/update.ts
2866
+ function update8(parent) {
2867
+ const cmd = parent.command("update").description("Update an Xray Test Plan issue").argument("<planKey>", "Test Plan issue key (e.g. QA-573)", issueKey).option("--summary <text>", "New summary (title)", text);
2868
+ textOrFileOption(cmd, "description", { description: "New Test Plan description" });
2869
+ examples(cmd, ['QA-573 --summary "Updated plan title"', 'QA-573 --description "Revised scope"']);
2870
+ cmd.action(async (planKey, opts) => {
2871
+ const description = resolveTextOrFile(opts, "description", { required: false });
2872
+ const client = getClient();
2873
+ await updateXrayIssue(client, planKey, { summary: opts.summary, description });
2874
+ output({ key: planKey });
2875
+ });
2876
+ }
2877
+
2878
+ // src/commands/xray/plan/index.ts
2879
+ function registerPlanCommands(xray) {
2880
+ const plan = xray.command("plan").description(
2881
+ "Xray Test Plan issue management (CRUD, membership). To list tests inside a plan, use: xray test list --plan <key>."
2882
+ );
2883
+ examples(plan, [
2884
+ 'create --project QA --summary "Q3 regression plan"',
2885
+ "add QA-573 --test QA-1,QA-2",
2886
+ "add QA-573 --test QA-1 --execution QA-200",
2887
+ "list --test QA-574"
2888
+ ]);
2889
+ create9(plan);
2890
+ update8(plan);
2891
+ deletePlan(plan);
2892
+ add3(plan);
2893
+ remove3(plan);
2894
+ list11(plan);
2895
+ }
2896
+
2897
+ // src/commands/xray/precondition/add.ts
2898
+ function add4(parent) {
2899
+ const cmd = parent.command("add").description("Add tests to a Pre-Condition").argument("<preKey>", "Pre-Condition issue key (e.g. BD-50)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. AI-1,AI-2)", keyList);
2900
+ examples(cmd, ["BD-50 --test AI-1", "BD-50 --test AI-1,AI-2,AI-3"]);
2901
+ cmd.action(async (preKey, opts) => {
2902
+ const client = getClient();
2903
+ const result = await client.preconditions.addTests({ preKey, keys: opts.test });
2904
+ output(result ?? { added: true });
2905
+ });
2906
+ }
2907
+
2908
+ // src/commands/xray/precondition/create.ts
2909
+ import { Option as Option10 } from "commander";
2910
+
2911
+ // src/utils/xray/fields.ts
2912
+ var CONCEPT_SUFFIX = {
2913
+ "test-type": "test-type-custom-field",
2914
+ "manual-steps": "manual-test-steps-custom-field",
2915
+ "cucumber-test-type": "automated-test-type-custom-field",
2916
+ "cucumber-scenario": "steps-editor-custom-field",
2917
+ "generic-definition": "path-editor-custom-field",
2918
+ "test-repository-path": "test-repository-path-custom-field",
2919
+ "precondition-type": "precondition-test-type-custom-field",
2920
+ "precondition-body": "precondition-editor-custom-field"
2921
+ };
2922
+ async function resolveXrayFields(client) {
2923
+ const fields = await client.fields.getAll();
2924
+ const map = {};
2925
+ for (const [concept, suffix] of Object.entries(CONCEPT_SUFFIX)) {
2926
+ const field = fields.find((f) => {
2927
+ const custom = f.schema?.custom;
2928
+ return typeof custom === "string" && custom.endsWith(`:${suffix}`);
2929
+ });
2930
+ if (field) {
2931
+ map[concept] = field.id;
2932
+ }
2933
+ }
2934
+ return map;
2935
+ }
2936
+ function resolveTestTypeValue(allowedValues, type) {
2937
+ if (type === "manual") {
2938
+ const match2 = allowedValues.find((v) => /manual/i.test(v));
2939
+ if (!match2)
2940
+ throw new Error(`No 'manual' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`);
2941
+ return match2;
2942
+ }
2943
+ if (type === "generic") {
2944
+ const match2 = allowedValues.find((v) => /generic/i.test(v));
2945
+ if (!match2)
2946
+ throw new Error(`No 'generic' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`);
2947
+ return match2;
2948
+ }
2949
+ const match = allowedValues.find((v) => !/manual/i.test(v) && !/generic/i.test(v));
2950
+ if (!match)
2951
+ throw new Error(
2952
+ `No 'cucumber' (gherkin/automation) Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
2953
+ );
2954
+ return match;
2955
+ }
2956
+ async function resolveTypeValue(client, params) {
2957
+ const meta = await client.issues.getCreateMeta({
2958
+ projectKey: params.projectKey,
2959
+ issueTypeName: params.issueTypeName
2960
+ });
2961
+ const fieldId = params.fieldMap[params.fieldConceptKey];
2962
+ const issuetype = meta.projects?.[0]?.issuetypes?.[0];
2963
+ const fieldMeta = fieldId ? issuetype?.fields?.[fieldId] : void 0;
2964
+ const allowedValues = (fieldMeta?.allowedValues ?? []).map((av) => av.value ?? "").filter(Boolean);
2965
+ if (allowedValues.length > 0) {
2966
+ return resolveTestTypeValue(allowedValues, params.type);
2967
+ }
2968
+ return params.type === "manual" ? "Manual" : params.type === "generic" ? "Generic" : "Cucumber";
2969
+ }
2970
+ function requireField(map, concept, label) {
2971
+ const id = map[concept];
2972
+ if (!id) throw new Error(`Xray field for '${label}' not found on this instance.`);
2973
+ return id;
2974
+ }
2975
+ function buildTestCustomFields(map, opts) {
2976
+ const fields = {};
2977
+ const testTypeId = requireField(map, "test-type", "Test Type");
2978
+ fields[testTypeId] = { value: opts.typeValue };
2979
+ if (opts.type === "cucumber") {
2980
+ const scenarioId = requireField(map, "cucumber-scenario", "Cucumber Scenario");
2981
+ const cucumberTypeId = requireField(map, "cucumber-test-type", "Cucumber Test Type");
2982
+ if (opts.gherkin !== void 0) fields[scenarioId] = opts.gherkin;
2983
+ fields[cucumberTypeId] = { value: opts.cucumberType ?? "Scenario" };
2984
+ } else if (opts.type === "generic") {
2985
+ const defId = requireField(map, "generic-definition", "Generic Definition");
2986
+ if (opts.definition !== void 0) fields[defId] = opts.definition;
2987
+ }
2988
+ if (opts.repositoryPath !== void 0) {
2989
+ const pathId = requireField(map, "test-repository-path", "Test Repository Path");
2990
+ fields[pathId] = opts.repositoryPath;
2991
+ }
2992
+ return fields;
2993
+ }
2994
+ function buildPreconditionCustomFields(map, opts) {
2995
+ const fields = {};
2996
+ const typeId = requireField(map, "precondition-type", "Pre-Condition Type");
2997
+ fields[typeId] = { value: opts.typeValue };
2998
+ if (opts.condition !== void 0) {
2999
+ const bodyId = requireField(map, "precondition-body", "Conditions");
3000
+ fields[bodyId] = opts.condition;
3001
+ }
3002
+ return fields;
3003
+ }
3004
+
3005
+ // src/commands/xray/precondition/create.ts
3006
+ function create10(parent) {
3007
+ const cmd = parent.command("create").description(
3008
+ "Create a new Xray Pre-Condition issue. Note: the Pre-Condition issue type is only available in projects that have it configured (e.g. BD); it is not on the AI/QA scheme."
3009
+ ).requiredOption("--project <key>", "Project key (e.g. BD)", text).requiredOption("--summary <text>", "Pre-Condition summary (title)", text).addOption(
3010
+ new Option10("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
3011
+ );
3012
+ textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
3013
+ textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
3014
+ examples(cmd, [
3015
+ '--project BD --summary "User is logged in"',
3016
+ '--project BD --summary "API key valid" --type generic --condition "apiKey != null"',
3017
+ '--project BD --summary "Auth scenario" --type cucumber --condition "Given the user is authenticated"'
3018
+ ]);
3019
+ cmd.action(
3020
+ async (opts) => {
3021
+ const condition = resolveTextOrFile(opts, "condition", { required: false });
3022
+ const description = resolveTextOrFile(opts, "description", { required: false });
3023
+ const client = getClient();
3024
+ const map = await resolveXrayFields(client);
3025
+ const typeValue = await resolveTypeValue(client, {
3026
+ projectKey: opts.project,
3027
+ issueTypeName: XRAY_ISSUE_TYPES.PreCondition,
3028
+ fieldConceptKey: "precondition-type",
3029
+ type: opts.type,
3030
+ fieldMap: map
3031
+ });
3032
+ const customFields = buildPreconditionCustomFields(map, { typeValue, condition });
3033
+ const result = await createXrayIssue(client, {
3034
+ project: opts.project,
3035
+ type: XRAY_ISSUE_TYPES.PreCondition,
3036
+ summary: opts.summary,
3037
+ description,
3038
+ customFields
3039
+ });
3040
+ output(transformCreatedIssue(result));
3041
+ }
3042
+ );
3043
+ }
3044
+
3045
+ // src/commands/xray/precondition/delete.ts
3046
+ function deletePrecondition(parent) {
3047
+ const cmd = parent.command("delete").description("Delete an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. BD-50)", issueKey);
3048
+ examples(cmd, ["BD-50"]);
3049
+ cmd.action(async (preKey) => {
3050
+ const client = getClient();
3051
+ await deleteXrayIssue(client, preKey);
3052
+ output({ deleted: true, key: preKey });
3053
+ });
3054
+ }
3055
+
3056
+ // src/commands/xray/precondition/list.ts
3057
+ function list12(parent) {
3058
+ const cmd = parent.command("list").description(
3059
+ "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."
3060
+ ).requiredOption("--test <key>", "Test issue key whose pre-conditions to list (e.g. AI-584)", issueKey);
3061
+ examples(cmd, ["--test AI-584"]);
3062
+ cmd.action(async (opts) => {
3063
+ const client = getClient();
3064
+ const preconditions = await client.xrayTests.getPreconditions({ testKey: opts.test });
3065
+ output(transformLinks(preconditions));
3066
+ });
3067
+ }
3068
+
3069
+ // src/commands/xray/precondition/remove.ts
3070
+ function remove4(parent) {
3071
+ const cmd = parent.command("remove").description("Remove tests from a Pre-Condition").argument("<preKey>", "Pre-Condition issue key (e.g. BD-50)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. AI-1,AI-2)", keyList);
3072
+ examples(cmd, ["BD-50 --test AI-1", "BD-50 --test AI-1,AI-2"]);
3073
+ cmd.action(async (preKey, opts) => {
3074
+ const client = getClient();
3075
+ const result = await client.preconditions.removeTests({ preKey, keys: opts.test });
3076
+ output(result ?? { removed: true });
3077
+ });
3078
+ }
3079
+
3080
+ // src/commands/xray/precondition/update.ts
3081
+ import { Option as Option11 } from "commander";
3082
+ function update9(parent) {
3083
+ const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. BD-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option11("--type <type>", "New Pre-Condition type").choices(["manual", "generic", "cucumber"]));
3084
+ textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
3085
+ textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
3086
+ examples(cmd, ['BD-50 --summary "Updated precondition"', 'BD-50 --type generic --condition "apiKey != null"']);
3087
+ cmd.action(
3088
+ async (preKey, opts) => {
3089
+ const condition = resolveTextOrFile(opts, "condition", { required: false });
3090
+ const description = resolveTextOrFile(opts, "description", { required: false });
3091
+ const client = getClient();
3092
+ let customFields;
3093
+ if (opts.type !== void 0 || condition !== void 0) {
3094
+ const map = await resolveXrayFields(client);
3095
+ customFields = {};
3096
+ if (opts.type !== void 0) {
3097
+ const projectKey = preKey.split("-")[0];
3098
+ const typeValue = await resolveTypeValue(client, {
3099
+ projectKey,
3100
+ issueTypeName: XRAY_ISSUE_TYPES.PreCondition,
3101
+ fieldConceptKey: "precondition-type",
3102
+ type: opts.type,
3103
+ fieldMap: map
3104
+ });
3105
+ const typeId = map["precondition-type"];
3106
+ if (!typeId) throw new Error("Xray field for 'Pre-Condition Type' not found on this instance.");
3107
+ customFields[typeId] = { value: typeValue };
3108
+ }
3109
+ if (condition !== void 0) {
3110
+ const bodyId = map["precondition-body"];
3111
+ if (!bodyId) throw new Error("Xray field for 'Conditions' not found on this instance.");
3112
+ customFields[bodyId] = condition;
3113
+ }
3114
+ }
3115
+ await updateXrayIssue(client, preKey, { summary: opts.summary, description, customFields });
3116
+ output({ key: preKey });
3117
+ }
3118
+ );
3119
+ }
3120
+
3121
+ // src/commands/xray/precondition/index.ts
3122
+ function registerPreconditionCommands(xray) {
3123
+ const precondition = xray.command("precondition").description(
3124
+ "Xray Pre-Condition issue management (CRUD, membership). Note: Pre-Condition issue type is only available in projects where it is configured (not AI/QA). To list tests covered by a precondition, use: xray test list --precondition <key>."
3125
+ );
3126
+ examples(precondition, [
3127
+ 'create --project BD --summary "User is logged in"',
3128
+ "add BD-50 --test QA-1,QA-2",
3129
+ "list --test AI-584"
3130
+ ]);
3131
+ create10(precondition);
3132
+ update9(precondition);
3133
+ deletePrecondition(precondition);
3134
+ add4(precondition);
3135
+ remove4(precondition);
3136
+ list12(precondition);
3137
+ }
3138
+
3139
+ // src/utils/xray/run-id.ts
3140
+ async function resolveRunId(client, o) {
3141
+ if (o.runId !== void 0) return o.runId;
3142
+ if (!o.execution || !o.test) throw new Error("Provide a run id, or both --execution and --test to resolve the run.");
3143
+ const run = await client.testRuns.resolve({ execKey: o.execution, testKey: o.test });
3144
+ if (!run?.id) throw new Error(`No test run found for ${o.test} in ${o.execution}.`);
3145
+ return Number(run.id);
3146
+ }
3147
+
3148
+ // src/commands/xray/run/defect/add.ts
3149
+ function add5(parent) {
3150
+ 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);
3151
+ examples(cmd, ["42 --defect BUG-1", "--execution QA-1 --test QA-2 --defect BUG-1,BUG-2"]);
3152
+ cmd.action(async (runId, opts) => {
3153
+ const client = getClient();
3154
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3155
+ await client.testRuns.addDefects({ runId: id, keys: opts.defect });
3156
+ output({ runId: id, added: opts.defect });
3157
+ });
3158
+ }
3159
+
3160
+ // src/commands/xray/run/defect/remove.ts
3161
+ function remove5(parent) {
3162
+ 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);
3163
+ examples(cmd, ["42 --defect BUG-1", "--execution QA-1 --test QA-2 --defect BUG-1,BUG-2"]);
3164
+ cmd.action(async (runId, opts) => {
3165
+ const client = getClient();
3166
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3167
+ const results = await Promise.allSettled(
3168
+ opts.defect.map((key) => client.testRuns.removeDefect({ runId: id, key }))
3169
+ );
3170
+ const removed = [];
3171
+ const failed = [];
3172
+ results.forEach((r, i) => {
3173
+ const key = opts.defect[i];
3174
+ if (r.status === "fulfilled") {
3175
+ removed.push(key);
3176
+ } else {
3177
+ const reason = r.reason instanceof Error ? r.reason.message : String(r.reason);
3178
+ failed.push({ key, reason });
3179
+ }
3180
+ });
3181
+ output({
3182
+ runId: id,
3183
+ removed,
3184
+ ...failed.length > 0 && { failed }
3185
+ });
3186
+ if (failed.length > 0) {
3187
+ process.exitCode = 1;
3188
+ }
3189
+ });
3190
+ }
3191
+
3192
+ // src/commands/xray/run/defect/index.ts
3193
+ function registerDefectCommands(run) {
3194
+ const defect = run.command("defect").description(
3195
+ "Manage defects linked to a test run. See also: run update --defect for the one-command fail+link loop."
3196
+ );
3197
+ examples(defect, ["add --execution QA-1 --test QA-2 --defect BUG-1", "remove 42 --defect BUG-1"]);
3198
+ add5(defect);
3199
+ remove5(defect);
3200
+ }
3201
+
3202
+ // src/commands/xray/run/evidence/add.ts
3203
+ import { readFileSync as readFileSync6 } from "fs";
3204
+ import { basename as basename4, extname } from "path";
3205
+ var CONTENT_TYPES = {
3206
+ ".png": "image/png",
3207
+ ".jpg": "image/jpeg",
3208
+ ".jpeg": "image/jpeg",
3209
+ ".gif": "image/gif",
3210
+ ".pdf": "application/pdf",
3211
+ ".txt": "text/plain",
3212
+ ".xml": "application/xml",
3213
+ ".json": "application/json",
3214
+ ".zip": "application/zip",
3215
+ ".html": "text/html",
3216
+ ".log": "text/plain"
3217
+ };
3218
+ function inferContentType(fp) {
3219
+ return CONTENT_TYPES[extname(fp).toLowerCase()] ?? "application/octet-stream";
3220
+ }
3221
+ function add6(parent) {
3222
+ 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);
3223
+ examples(cmd, ["42 --file screenshot.png", "--execution QA-1 --test QA-2 --file report.pdf"]);
3224
+ cmd.action(async (runId, opts) => {
3225
+ const client = getClient();
3226
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3227
+ const fileBuf = readFileSync6(opts.file);
3228
+ const filename = basename4(opts.file);
3229
+ const contentType = inferContentType(opts.file);
3230
+ const data = fileBuf.toString("base64");
3231
+ const result = await client.testRuns.addEvidence({ runId: id, data, filename, contentType });
3232
+ output(result ?? { runId: id, attached: filename });
3233
+ });
3234
+ }
3235
+
3236
+ // src/commands/xray/run/evidence/delete.ts
3237
+ function deleteEvidence(parent) {
3238
+ const cmd = parent.command("delete").description("Delete an evidence attachment from a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--evidence-id <id>", "Id of the evidence attachment (from evidence 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);
3239
+ examples(cmd, ["42 --evidence-id 7", "--execution QA-1 --test QA-2 --evidence-id 7"]);
3240
+ cmd.action(async (runId, opts) => {
3241
+ const client = getClient();
3242
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3243
+ await client.testRuns.deleteEvidence({ runId: id, evidenceId: opts.evidenceId });
3244
+ output({ runId: id, evidenceId: opts.evidenceId, deleted: true });
3245
+ });
3246
+ }
3247
+
3248
+ // src/commands/xray/run/evidence/list.ts
3249
+ function list13(parent) {
3250
+ 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);
3251
+ examples(cmd, ["42", "--execution QA-1 --test QA-2"]);
3252
+ cmd.action(async (runId, opts) => {
3253
+ const client = getClient();
3254
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3255
+ const evidence = await client.testRuns.listEvidence({ runId: id });
3256
+ output(transformEvidence(evidence));
3257
+ });
3258
+ }
3259
+
3260
+ // src/commands/xray/run/evidence/index.ts
3261
+ function registerEvidenceCommands(run) {
3262
+ const evidence = run.command("evidence").description("Manage evidence (file attachments) on a test run");
3263
+ examples(evidence, [
3264
+ "add --execution QA-1 --test QA-2 --file screenshot.png",
3265
+ "list 42",
3266
+ "delete 42 --evidence-id 7"
3267
+ ]);
3268
+ add6(evidence);
3269
+ list13(evidence);
3270
+ deleteEvidence(evidence);
3271
+ }
3272
+
3273
+ // src/commands/xray/run/field/get.ts
3274
+ function get4(parent) {
3275
+ 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);
3276
+ examples(cmd, ["42 --field-id customfield_10100", "--execution QA-1 --test QA-2 --field-id customfield_10100"]);
3277
+ cmd.action(async (runId, opts) => {
3278
+ const client = getClient();
3279
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3280
+ const result = await client.testRuns.getCustomField({ runId: id, fieldId: opts.fieldId });
3281
+ output(result);
3282
+ });
3283
+ }
3284
+
3285
+ // src/commands/xray/run/field/set.ts
3286
+ function set(parent) {
3287
+ const cmd = parent.command("set").description("Set 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).requiredOption("--value <text>", "Value to set for the field", 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);
3288
+ examples(cmd, [
3289
+ '42 --field-id customfield_10100 --value "approved"',
3290
+ '--execution QA-1 --test QA-2 --field-id customfield_10100 --value "reviewed"'
3291
+ ]);
3292
+ cmd.action(
3293
+ async (runId, opts) => {
3294
+ const client = getClient();
3295
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3296
+ const result = await client.testRuns.setCustomField({ runId: id, fieldId: opts.fieldId, value: opts.value });
3297
+ output(result ?? { runId: id, fieldId: opts.fieldId, value: opts.value, updated: true });
3298
+ }
3299
+ );
3300
+ }
3301
+
3302
+ // src/commands/xray/run/field/index.ts
3303
+ function registerRunFieldCommands(run) {
3304
+ const field = run.command("field").description("Get or set Xray custom field values on a test run");
3305
+ examples(field, ["get 42 --field-id customfield_10100", 'set 42 --field-id customfield_10100 --value "approved"']);
3306
+ get4(field);
3307
+ set(field);
3308
+ }
3309
+
3310
+ // src/commands/xray/run/get.ts
3311
+ function get5(parent) {
3312
+ 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);
3313
+ examples(cmd, ["42", "--execution QA-1 --test QA-2"]);
3314
+ cmd.action(async (runId, opts) => {
3315
+ const client = getClient();
3316
+ const id = await resolveRunId(client, { runId, ...opts });
3317
+ const run = await client.testRuns.get({ runId: id });
3318
+ output(transformRun(run));
3319
+ });
3320
+ }
3321
+
3322
+ // src/commands/xray/run/list.ts
3323
+ function list14(parent) {
3324
+ 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);
3325
+ examples(cmd, ["--execution QA-1", "--execution QA-1 --limit 10", "--execution QA-1 --limit 10 --start 10"]);
3326
+ cmd.action(async (opts) => {
3327
+ const client = getClient();
3328
+ const runs = await client.testRuns.listByExecution({ execKey: opts.execution });
3329
+ const start = opts.start ?? 0;
3330
+ const sliced = runs.slice(start, start + opts.limit);
3331
+ output(sliced.map(transformRun));
3332
+ });
3333
+ }
3334
+
3335
+ // src/commands/xray/run/step/list.ts
3336
+ function list15(parent) {
3337
+ 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);
3338
+ examples(cmd, ["42", "--execution QA-1 --test QA-2"]);
3339
+ cmd.action(async (runId, opts) => {
3340
+ const client = getClient();
3341
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3342
+ const steps = await client.testRuns.listSteps({ runId: id });
3343
+ output(transformRunSteps(steps));
3344
+ });
3345
+ }
3346
+
3347
+ // src/commands/xray/run/step/update.ts
3348
+ function update10(parent) {
3349
+ const cmd = parent.command("update").description("Update a step result within a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--step-id <id>", "Id of the step to update (from step list)", positiveInt).option(
3350
+ "--status <status>",
3351
+ "Step status (case-sensitive; run `xray status list --step` for valid values)",
3352
+ nonEmpty
3353
+ ).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);
3354
+ textOrFileOption(cmd, "comment", { description: "Comment text for this step result" });
3355
+ textOrFileOption(cmd, "actual-result", { description: "Actual result text for this step" });
3356
+ examples(cmd, [
3357
+ "42 --step-id 1 --status PASS",
3358
+ '--execution QA-1 --test QA-2 --step-id 2 --status FAIL --comment "button missing"',
3359
+ '42 --step-id 1 --status PASS --actual-result "Form submitted successfully"'
3360
+ ]);
3361
+ cmd.action(
3362
+ async (runId, opts) => {
3363
+ const client = getClient();
3364
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3365
+ const comment = resolveTextOrFile(opts, "comment", { required: false });
3366
+ const actualResult = resolveTextOrFile(opts, "actualResult", { required: false });
3367
+ const result = await client.testRuns.updateStep({
3368
+ runId: id,
3369
+ stepId: opts.stepId,
3370
+ ...opts.status !== void 0 && { status: opts.status },
3371
+ ...comment !== void 0 && { comment },
3372
+ ...actualResult !== void 0 && { actualResult }
3373
+ });
3374
+ output(result ?? { runId: id, stepId: opts.stepId, updated: true });
3375
+ }
3376
+ );
3377
+ }
3378
+
3379
+ // src/commands/xray/run/step/index.ts
3380
+ function registerRunStepCommands(run) {
3381
+ const step = run.command("step").description("Manage step results within a test run. See also: xray step for test step definitions.");
3382
+ examples(step, ["list --execution QA-1 --test QA-2", "update 42 --step-id 1 --status PASS"]);
3383
+ list15(step);
3384
+ update10(step);
3385
+ }
3386
+
3387
+ // src/commands/xray/run/update.ts
3388
+ function update11(parent) {
3389
+ const cmd = parent.command("update").description("Update a test run status, comment, assignee, or link defects").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).option("--status <status>", "Run status (case-sensitive; run `xray status list` for valid values)", nonEmpty).option("--assignee <user>", "Assignee username", text).option("--defect <keys>", "Comma-separated defect issue keys to link", keyList);
3390
+ textOrFileOption(cmd, "comment", { description: "Comment text for this run" });
3391
+ examples(cmd, [
3392
+ "42 --status PASS",
3393
+ '--execution QA-1 --test QA-2 --status FAIL --comment "broken"',
3394
+ '--execution QA-1 --test QA-2 --status FAIL --comment "broken" --defect BUG-1'
3395
+ ]);
3396
+ cmd.action(
3397
+ async (runId, opts) => {
3398
+ const client = getClient();
3399
+ const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3400
+ const comment = resolveTextOrFile(opts, "comment", { required: false });
3401
+ const result = await client.testRuns.update({
3402
+ runId: id,
3403
+ ...opts.status !== void 0 && { status: opts.status },
3404
+ ...comment !== void 0 && { comment },
3405
+ ...opts.assignee !== void 0 && { assignee: opts.assignee }
3406
+ });
3407
+ if (opts.defect && opts.defect.length > 0) {
3408
+ await client.testRuns.addDefects({ runId: id, keys: opts.defect });
3409
+ }
3410
+ output(result ?? { runId: id, updated: true });
3411
+ }
3412
+ );
3413
+ }
3414
+
3415
+ // src/commands/xray/run/index.ts
3416
+ function registerRunCommands(xray) {
3417
+ const run = xray.command("run").description(
3418
+ "Manage test run results (status, comments, defects, evidence, steps). Every command accepts [runId] or --execution + --test."
3419
+ );
3420
+ examples(run, [
3421
+ "get --execution QA-1 --test QA-2",
3422
+ "list --execution QA-1",
3423
+ 'update --execution QA-1 --test QA-2 --status FAIL --comment "broken" --defect BUG-1',
3424
+ "defect add 42 --defect BUG-1,BUG-2",
3425
+ "evidence add --execution QA-1 --test QA-2 --file screenshot.png",
3426
+ "step list --execution QA-1 --test QA-2",
3427
+ "field get 42 --field-id customfield_10100"
3428
+ ]);
3429
+ get5(run);
3430
+ list14(run);
3431
+ update11(run);
3432
+ registerDefectCommands(run);
3433
+ registerEvidenceCommands(run);
3434
+ registerRunStepCommands(run);
3435
+ registerRunFieldCommands(run);
3436
+ }
3437
+
3438
+ // src/commands/xray/status/list.ts
3439
+ function list16(parent) {
3440
+ const cmd = parent.command("list").description("List configured Xray test (run) statuses").option("--step", "List test-step statuses instead of run statuses");
3441
+ examples(cmd, ["", ["--step", "step statuses"]]);
3442
+ cmd.action(async (opts) => {
3443
+ const client = getClient();
3444
+ const data = opts.step ? await client.xraySettings.getStepStatuses() : await client.xraySettings.getTestStatuses();
3445
+ output(transformStatuses(data));
3446
+ });
3447
+ }
3448
+
3449
+ // src/commands/xray/status/index.ts
3450
+ function registerStatusCommands(xray) {
3451
+ const status = xray.command("status").description("Xray status discovery");
3452
+ examples(status, ["list", ["list --step", "step statuses"]]);
3453
+ list16(status);
3454
+ }
3455
+
3456
+ // src/commands/xray/step/add.ts
3457
+ function add7(parent) {
3458
+ const cmd = parent.command("add").description("Add a manual test step to a Test issue").requiredOption("--test <key>", "Test issue key (e.g. AI-584)", issueKey);
3459
+ textOrFileOption(cmd, "action", { description: "Step action text (required: provide --action or --action-file)" });
3460
+ textOrFileOption(cmd, "data", { description: "Step test data text" });
3461
+ textOrFileOption(cmd, "result", { description: "Expected result text" });
3462
+ examples(cmd, [
3463
+ '--test AI-584 --action "Open login page" --result "Login form is shown"',
3464
+ '--test AI-584 --action "Submit form" --data "user=admin" --result "Dashboard loads"'
3465
+ ]);
3466
+ cmd.action(
3467
+ async (opts) => {
3468
+ const action = resolveTextOrFile(opts, "action");
3469
+ const data = resolveTextOrFile(opts, "data", { required: false });
3470
+ const result = resolveTextOrFile(opts, "result", { required: false });
3471
+ const client = getClient();
3472
+ const ref = await client.testSteps.create({ testKey: opts.test, action, data, result });
3473
+ output(ref);
3474
+ }
3475
+ );
3476
+ }
3477
+
3478
+ // src/commands/xray/step/delete.ts
3479
+ function deleteStep(parent) {
3480
+ const cmd = parent.command("delete").description("Delete a manual test step by ID").argument("<stepId>", "Step ID to delete (from step list)", positiveInt).requiredOption("--test <key>", "Test issue key (e.g. AI-584)", issueKey);
3481
+ examples(cmd, ["3 --test AI-584"]);
3482
+ cmd.action(async (stepId, opts) => {
3483
+ const client = getClient();
3484
+ await client.testSteps.delete({ testKey: opts.test, stepId });
3485
+ output({ stepId, deleted: true });
3486
+ });
3487
+ }
3488
+
3489
+ // src/commands/xray/step/list.ts
3490
+ function list17(parent) {
3491
+ const cmd = parent.command("list").description("List manual test steps for a Test issue").requiredOption("--test <key>", "Test issue key (e.g. AI-584)", issueKey);
3492
+ examples(cmd, ["--test AI-584"]);
3493
+ cmd.action(async (opts) => {
3494
+ const client = getClient();
3495
+ const steps = await client.testSteps.list({ testKey: opts.test });
3496
+ output(steps.map(transformStep));
3497
+ });
3498
+ }
3499
+
3500
+ // src/commands/xray/step/update.ts
3501
+ function update12(parent) {
3502
+ const cmd = parent.command("update").description("Update a manual test step by ID").argument("<stepId>", "Step ID to update (from step list)", positiveInt).requiredOption("--test <key>", "Test issue key (e.g. AI-584)", issueKey);
3503
+ textOrFileOption(cmd, "action", { description: "New step action text" });
3504
+ textOrFileOption(cmd, "data", { description: "New step test data text" });
3505
+ textOrFileOption(cmd, "result", { description: "New expected result text" });
3506
+ examples(cmd, [
3507
+ '3 --test AI-584 --action "Click submit button"',
3508
+ '3 --test AI-584 --result "Confirmation email sent"'
3509
+ ]);
3510
+ cmd.action(
3511
+ async (stepId, opts) => {
3512
+ const action = resolveTextOrFile(opts, "action", { required: false });
3513
+ const data = resolveTextOrFile(opts, "data", { required: false });
3514
+ const result = resolveTextOrFile(opts, "result", { required: false });
3515
+ if (action === void 0 && data === void 0 && result === void 0) {
3516
+ throw new Error(
3517
+ "Provide at least one of --action (or --action-file), --data (or --data-file), --result (or --result-file)."
3518
+ );
3519
+ }
3520
+ const client = getClient();
3521
+ const res = await client.testSteps.update({ testKey: opts.test, stepId, action, data, result });
3522
+ output(res ?? { stepId, updated: true });
3523
+ }
3524
+ );
3525
+ }
3526
+
3527
+ // src/commands/xray/step/index.ts
3528
+ function registerStepCommands(xray) {
3529
+ const step = xray.command("step").description(
3530
+ "Manage manual test steps for a Test issue (scope: --test). Steps are ordered by insertion; use step list to see current ids."
3531
+ );
3532
+ examples(step, ['add --test AI-584 --action "Open login" --result "Form shows"', "list --test AI-584"]);
3533
+ list17(step);
3534
+ add7(step);
3535
+ update12(step);
3536
+ deleteStep(step);
3537
+ }
3538
+
3539
+ // src/commands/xray/test/create.ts
3540
+ import { Option as Option12 } from "commander";
3541
+ import { z as z4 } from "zod";
3542
+ var stepsSchema = z4.array(
3543
+ z4.object({ action: z4.string(), data: z4.string().optional(), result: z4.string().optional() })
3544
+ );
3545
+ function create11(parent) {
3546
+ const cmd = parent.command("create").description("Create a new Xray Test issue").requiredOption("--project <key>", "Project key (e.g. AI)", text).requiredOption("--summary <text>", "Test summary (title)", text).addOption(
3547
+ new Option12("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
3548
+ ).addOption(
3549
+ new Option12("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
3550
+ "scenario",
3551
+ "scenario-outline"
3552
+ ])
3553
+ ).option("--labels <labels>", "Comma-separated labels", text).option("--components <names>", "Comma-separated component names", text).option("--assignee <user>", 'Assignee username or "me"', text).option("--reporter <user>", 'Reporter username or "me"', text).option("--steps <json>", "JSON array of manual steps to seed: [{action,data?,result?}]", jsonShape(stepsSchema));
3554
+ textOrFileOption(cmd, "gherkin", { description: "Gherkin scenario text (required when --type cucumber)" });
3555
+ textOrFileOption(cmd, "definition", { description: "Generic test definition (required when --type generic)" });
3556
+ textOrFileOption(cmd, "description", { description: "Test description" });
3557
+ examples(cmd, [
3558
+ [
3559
+ `--project AI --summary "Login test" --type manual --steps '[{"action":"Open browser","result":"Browser opens"}]'`,
3560
+ "Create manual test with step seed"
3561
+ ],
3562
+ [
3563
+ '--project AI --summary "Login scenario" --type cucumber --cucumber-type scenario --gherkin "Given I am on login page\\nWhen I enter credentials\\nThen I am logged in"',
3564
+ "Create cucumber test"
3565
+ ],
3566
+ [
3567
+ '--project AI --summary "Perf baseline" --type generic --definition "Run k6 script and assert p95 < 500ms"',
3568
+ "Create generic test"
3569
+ ]
3570
+ ]);
3571
+ cmd.action(
3572
+ async (opts) => {
3573
+ const gherkin = resolveTextOrFile(opts, "gherkin", { required: false });
3574
+ const definition = resolveTextOrFile(opts, "definition", { required: false });
3575
+ const description = resolveTextOrFile(opts, "description", { required: false });
3576
+ if (opts.type === "cucumber" && !gherkin) {
3577
+ throw new Error("--gherkin (or --gherkin-file) is required when --type is cucumber.");
3578
+ }
3579
+ if (opts.type === "generic" && !definition) {
3580
+ throw new Error("--definition (or --definition-file) is required when --type is generic.");
3581
+ }
3582
+ const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
3583
+ const client = getClient();
3584
+ const xrayFields = await resolveXrayFields(client);
3585
+ const typeValue = await resolveTypeValue(client, {
3586
+ projectKey: opts.project,
3587
+ issueTypeName: XRAY_ISSUE_TYPES.Test,
3588
+ fieldConceptKey: "test-type",
3589
+ type: opts.type,
3590
+ fieldMap: xrayFields
3591
+ });
3592
+ const customFields = buildTestCustomFields(xrayFields, {
3593
+ type: opts.type,
3594
+ typeValue,
3595
+ cucumberType,
3596
+ gherkin,
3597
+ definition
3598
+ });
3599
+ const assignee = opts.assignee !== void 0 ? await resolveUserToken(opts.assignee) : void 0;
3600
+ const reporter = opts.reporter !== void 0 ? await resolveUserToken(opts.reporter) : void 0;
3601
+ const result = await client.issues.create({
3602
+ projectKeyOrId: opts.project,
3603
+ issueTypeName: XRAY_ISSUE_TYPES.Test,
3604
+ summary: opts.summary,
3605
+ description,
3606
+ assignee: assignee ?? void 0,
3607
+ reporter: reporter ?? void 0,
3608
+ labels: opts.labels?.split(",").map((l) => l.trim()),
3609
+ components: opts.components?.split(",").map((c) => c.trim()),
3610
+ customFields
3611
+ });
3612
+ if (opts.steps) {
3613
+ for (const step of opts.steps) {
3614
+ await client.testSteps.create({ testKey: result.key, ...step });
3615
+ }
3616
+ }
3617
+ output(transformCreatedIssue(result));
3618
+ }
3619
+ );
3620
+ }
3621
+
3622
+ // src/commands/xray/test/delete.ts
3623
+ function deleteTest(parent) {
3624
+ const cmd = parent.command("delete").description("Delete an Xray Test issue").argument("<testKey>", "Test issue key (e.g. AI-584)", issueKey);
3625
+ examples(cmd, ["AI-584"]);
3626
+ cmd.action(async (testKey) => {
3627
+ const client = getClient();
3628
+ await client.issues.delete({ issueKeyOrId: testKey });
3629
+ output({ deleted: true, key: testKey });
3630
+ });
3631
+ }
3632
+
3633
+ // src/commands/xray/test/get.ts
3634
+ function get6(parent) {
3635
+ 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. AI-584)", listOf(issueKey));
3636
+ examples(cmd, ["AI-584", "AI-584 AI-585"]);
3637
+ cmd.action(async (testKeys) => {
3638
+ const client = getClient();
3639
+ const tests = await client.xrayTests.getTests({ keys: testKeys });
3640
+ output(tests.map(transformTest));
3641
+ });
3642
+ }
3643
+
3644
+ // src/commands/xray/test/list.ts
3645
+ function list18(parent) {
3646
+ const cmd = parent.command("list").description(
3647
+ "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."
3648
+ ).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);
3649
+ examples(cmd, ["--project QA", "--set QA-100", "--folder 818 --project QA"]);
3650
+ cmd.action(
3651
+ async (opts) => {
3652
+ const client = getClient();
3653
+ if (opts.folder !== void 0) {
3654
+ if (!opts.project) {
3655
+ throw new Error("--project is required when --folder is specified.");
3656
+ }
3657
+ const otherScopes = ["set", "plan", "execution", "precondition"].filter(
3658
+ (k) => opts[k] !== void 0
3659
+ );
3660
+ if (otherScopes.length > 0) {
3661
+ throw new Error(
3662
+ `--folder cannot be combined with ${otherScopes.map((k) => `--${k}`).join(", ")}. Use --folder with --project only.`
3663
+ );
3664
+ }
3665
+ const result2 = await client.testRepository.getFolderTests({
3666
+ projectKey: opts.project,
3667
+ folderId: opts.folder
3668
+ });
3669
+ output(transformTestList(result2));
3670
+ return;
3671
+ }
3672
+ const scope = assertExactlyOne(
3673
+ opts,
3674
+ ["project", "set", "plan", "execution", "precondition"],
3675
+ "test scope"
3676
+ );
3677
+ if (scope === "project") {
3678
+ const result2 = await client.issues.search({
3679
+ jql: `project=${opts.project} AND issuetype=Test`,
3680
+ startAt: opts.start,
3681
+ maxResults: opts.limit
3682
+ });
3683
+ const issues3 = result2.issues.map((i) => ({
3684
+ key: i.key,
3685
+ ...i.fields?.summary !== void 0 && { summary: i.fields.summary },
3686
+ ...i.fields?.status?.name !== void 0 && { status: i.fields.status.name }
3687
+ }));
3688
+ output({
3689
+ total: result2.total,
3690
+ startAt: result2.startAt,
3691
+ maxResults: result2.maxResults,
3692
+ isLast: result2.startAt + issues3.length >= result2.total,
3693
+ issues: issues3
3694
+ });
3695
+ return;
3696
+ }
3697
+ if (scope === "set") {
3698
+ const result2 = await client.testSets.listTests({ setKey: opts.set });
3699
+ output(transformTestList(result2));
3700
+ return;
3701
+ }
3702
+ if (scope === "plan") {
3703
+ const result2 = await client.testPlans.listTests({ planKey: opts.plan });
3704
+ output(transformTestList(result2));
3705
+ return;
3706
+ }
3707
+ if (scope === "execution") {
3708
+ const result2 = await client.testExecutions.listTests({ execKey: opts.execution });
3709
+ output(transformTestList(result2));
3710
+ return;
3711
+ }
3712
+ const result = await client.preconditions.listTests({ preKey: opts.precondition });
3713
+ output(transformTestList(result));
3714
+ }
3715
+ );
3716
+ }
3717
+
3718
+ // src/commands/xray/test/update.ts
3719
+ import { Option as Option13 } from "commander";
3720
+ function update13(parent) {
3721
+ const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. AI-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option13("--type <type>", "Test type").choices(["manual", "cucumber", "generic"])).addOption(
3722
+ new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
3723
+ "scenario",
3724
+ "scenario-outline"
3725
+ ])
3726
+ ).option("--labels <labels>", "Comma-separated labels", text).option("--components <names>", "Comma-separated component names", text).option("--assignee <user>", 'Assignee username or "me"', text).option("--reporter <user>", 'Reporter username or "me"', text);
3727
+ textOrFileOption(cmd, "gherkin", { description: "Gherkin scenario text (required when --type cucumber)" });
3728
+ textOrFileOption(cmd, "definition", { description: "Generic test definition (required when --type generic)" });
3729
+ textOrFileOption(cmd, "description", { description: "Test description" });
3730
+ examples(cmd, [
3731
+ 'AI-584 --summary "Updated title"',
3732
+ 'AI-584 --type cucumber --cucumber-type scenario --gherkin "Given I open the app"'
3733
+ ]);
3734
+ cmd.action(
3735
+ async (testKey, opts) => {
3736
+ const gherkin = resolveTextOrFile(opts, "gherkin", { required: false });
3737
+ const definition = resolveTextOrFile(opts, "definition", { required: false });
3738
+ const description = resolveTextOrFile(opts, "description", { required: false });
3739
+ const fields = {};
3740
+ if (opts.summary !== void 0) fields.summary = opts.summary;
3741
+ if (description !== void 0) fields.description = description;
3742
+ if (opts.labels !== void 0) fields.labels = opts.labels.split(",").map((l) => l.trim());
3743
+ if (opts.components !== void 0)
3744
+ fields.components = opts.components.split(",").map((c) => c.trim()).map((name) => ({ name }));
3745
+ const client = getClient();
3746
+ if (opts.assignee !== void 0) {
3747
+ const resolved = await resolveUserToken(opts.assignee);
3748
+ fields.assignee = resolved === null ? null : { name: resolved };
3749
+ }
3750
+ if (opts.reporter !== void 0) fields.reporter = { name: opts.reporter };
3751
+ if (opts.type !== void 0) {
3752
+ if (opts.type === "cucumber" && !gherkin) {
3753
+ throw new Error("--gherkin (or --gherkin-file) is required when --type is cucumber.");
3754
+ }
3755
+ if (opts.type === "generic" && !definition) {
3756
+ throw new Error("--definition (or --definition-file) is required when --type is generic.");
3757
+ }
3758
+ const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
3759
+ const projectKey = testKey.split("-")[0];
3760
+ const xrayFields = await resolveXrayFields(client);
3761
+ const typeValue = await resolveTypeValue(client, {
3762
+ projectKey,
3763
+ issueTypeName: XRAY_ISSUE_TYPES.Test,
3764
+ fieldConceptKey: "test-type",
3765
+ type: opts.type,
3766
+ fieldMap: xrayFields
3767
+ });
3768
+ const customFields = buildTestCustomFields(xrayFields, {
3769
+ type: opts.type,
3770
+ typeValue,
3771
+ cucumberType,
3772
+ gherkin,
3773
+ definition
3774
+ });
3775
+ Object.assign(fields, customFields);
3776
+ }
3777
+ await client.issues.update({ issueKeyOrId: testKey, fields });
3778
+ output({ key: testKey });
3779
+ }
3780
+ );
3781
+ }
3782
+
3783
+ // src/commands/xray/test/index.ts
3784
+ function registerTestCommands(xray) {
3785
+ const test = xray.command("test").description("Xray Test issue management (CRUD)");
3786
+ examples(test, ["get AI-584", 'create --project AI --summary "Login" --type manual']);
3787
+ get6(test);
3788
+ create11(test);
3789
+ update13(test);
3790
+ deleteTest(test);
3791
+ list18(test);
3792
+ }
3793
+
3794
+ // src/commands/xray/testset/add.ts
3795
+ function add8(parent) {
3796
+ const cmd = parent.command("add").description("Add tests to a Test Set").argument("<setKey>", "Test Set issue key (e.g. AI-100)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. AI-1,AI-2)", keyList);
3797
+ examples(cmd, ["AI-100 --test AI-1", "AI-100 --test AI-1,AI-2,AI-3"]);
3798
+ cmd.action(async (setKey, opts) => {
3799
+ const client = getClient();
3800
+ const result = await client.testSets.addTests({ setKey, keys: opts.test });
3801
+ output(result ?? { added: true });
3802
+ });
3803
+ }
3804
+
3805
+ // src/commands/xray/testset/create.ts
3806
+ function create12(parent) {
3807
+ const cmd = parent.command("create").description("Create a new Xray Test Set issue").requiredOption("--project <key>", "Project key (e.g. AI)", text).requiredOption("--summary <text>", "Test Set summary (title)", text);
3808
+ textOrFileOption(cmd, "description", { description: "Test Set description" });
3809
+ examples(cmd, [
3810
+ '--project AI --summary "Smoke Test Set"',
3811
+ '--project AI --summary "Regression Set" --description "All regression tests"'
3812
+ ]);
3813
+ cmd.action(async (opts) => {
3814
+ const description = resolveTextOrFile(opts, "description", { required: false });
3815
+ const client = getClient();
3816
+ const result = await createXrayIssue(client, {
3817
+ project: opts.project,
3818
+ type: XRAY_ISSUE_TYPES.TestSet,
3819
+ summary: opts.summary,
3820
+ description
3821
+ });
3822
+ output(transformCreatedIssue(result));
3823
+ });
3824
+ }
3825
+
3826
+ // src/commands/xray/testset/delete.ts
3827
+ function deleteTestset(parent) {
3828
+ const cmd = parent.command("delete").description("Delete an Xray Test Set issue").argument("<setKey>", "Test Set issue key (e.g. AI-100)", issueKey);
3829
+ examples(cmd, ["AI-100"]);
3830
+ cmd.action(async (setKey) => {
3831
+ const client = getClient();
3832
+ await deleteXrayIssue(client, setKey);
3833
+ output({ deleted: true, key: setKey });
3834
+ });
3835
+ }
3836
+
3837
+ // src/commands/xray/testset/list.ts
3838
+ function list19(parent) {
3839
+ const cmd = parent.command("list").description(
3840
+ "List Test Sets that contain a given test (inverse view). For the forward view (tests inside a set), use: xray test list --set <key>"
3841
+ ).requiredOption("--test <key>", "Test issue key whose sets to list (e.g. AI-584)", issueKey);
3842
+ examples(cmd, ["--test AI-584"]);
3843
+ cmd.action(async (opts) => {
3844
+ const client = getClient();
3845
+ const sets = await client.xrayTests.getTestSets({ testKey: opts.test });
3846
+ output(transformLinks(sets));
3847
+ });
3848
+ }
3849
+
3850
+ // src/commands/xray/testset/remove.ts
3851
+ function remove6(parent) {
3852
+ const cmd = parent.command("remove").description("Remove tests from a Test Set").argument("<setKey>", "Test Set issue key (e.g. AI-100)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. AI-1,AI-2)", keyList);
3853
+ examples(cmd, ["AI-100 --test AI-1", "AI-100 --test AI-1,AI-2"]);
3854
+ cmd.action(async (setKey, opts) => {
3855
+ const client = getClient();
3856
+ const result = await client.testSets.removeTests({ setKey, keys: opts.test });
3857
+ output(result ?? { removed: true });
3858
+ });
3859
+ }
3860
+
3861
+ // src/commands/xray/testset/update.ts
3862
+ function update14(parent) {
3863
+ const cmd = parent.command("update").description("Update an Xray Test Set issue").argument("<setKey>", "Test Set issue key (e.g. AI-100)", issueKey).option("--summary <text>", "New summary (title)", text);
3864
+ textOrFileOption(cmd, "description", { description: "New Test Set description" });
3865
+ examples(cmd, ['AI-100 --summary "Updated smoke set"', 'AI-100 --description "New description"']);
3866
+ cmd.action(async (setKey, opts) => {
3867
+ const description = resolveTextOrFile(opts, "description", { required: false });
3868
+ const client = getClient();
3869
+ await updateXrayIssue(client, setKey, { summary: opts.summary, description });
3870
+ output({ key: setKey });
3871
+ });
3872
+ }
3873
+
3874
+ // src/commands/xray/testset/index.ts
3875
+ function registerTestsetCommands(xray) {
3876
+ const testset = xray.command("testset").description(
3877
+ "Xray Test Set issue management (CRUD, membership). To list tests inside a set, use: xray test list --set <key>"
3878
+ );
3879
+ examples(testset, ['create --project AI --summary "Smoke tests"', "add QA-100 --test QA-1,QA-2"]);
3880
+ create12(testset);
3881
+ update14(testset);
3882
+ deleteTestset(testset);
3883
+ add8(testset);
3884
+ remove6(testset);
3885
+ list19(testset);
3886
+ }
3887
+
3888
+ // src/commands/xray/index.ts
3889
+ function registerXrayCommands(program) {
3890
+ const xray = program.command("xray").description("Xray test management (tests, runs, repository, import/export)");
3891
+ examples(xray, ["test get AI-584"]);
3892
+ registerStatusCommands(xray);
3893
+ registerFieldCommands2(xray);
3894
+ registerTestCommands(xray);
3895
+ registerTestsetCommands(xray);
3896
+ registerPreconditionCommands(xray);
3897
+ registerExecutionCommands(xray);
3898
+ registerPlanCommands(xray);
3899
+ registerStepCommands(xray);
3900
+ registerRunCommands(xray);
3901
+ registerFolderCommands(xray);
3902
+ registerImportCommands(xray);
3903
+ registerExportCommands(xray);
3904
+ }
3905
+
2249
3906
  // src/program.ts
2250
3907
  var DIM = "\x1B[2m";
2251
3908
  var RESET = "\x1B[0m";
2252
3909
  function buildProgram() {
2253
- const program = new Command11();
3910
+ const program = new Command16();
2254
3911
  program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
2255
3912
  styleTitle: (str) => styleText("bold", str),
2256
3913
  styleUsage: (str) => styleText("dim", str),
@@ -2288,6 +3945,7 @@ ${styleText("bold", "Examples:")}
2288
3945
  registerFieldCommands(program);
2289
3946
  registerUserCommands(program);
2290
3947
  registerTokenCommands(program);
3948
+ registerXrayCommands(program);
2291
3949
  return program;
2292
3950
  }
2293
3951