jiradc-cli 1.0.41 → 1.0.42

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 +85 -29
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2393,33 +2393,45 @@ async function withNames(client, structureId, rows) {
2393
2393
  itemId: r.itemId
2394
2394
  }));
2395
2395
  }
2396
+ function resolveEnclosingRow(opts, fallback = 0) {
2397
+ const { parent, under } = opts;
2398
+ if (parent !== void 0 && under !== void 0 && parent !== under) {
2399
+ throw new CliUsageError(
2400
+ `--parent (${parent}) and --under (${under}) are the same option and disagree`,
2401
+ "They are aliases; pass only one."
2402
+ );
2403
+ }
2404
+ return parent ?? under ?? fallback;
2405
+ }
2396
2406
 
2397
2407
  // src/commands/structure/folder/create.ts
2398
2408
  function create7(parent) {
2399
2409
  const cmd = parent.command("create").description("Create a folder in a structure");
2400
2410
  scopeIdOption(cmd, "structure", { mandatory: true });
2401
2411
  cmd.requiredOption("--name <text>", "Folder name", text);
2402
- cmd.option("--under <rowId>", "Parent folder row id (0 = top level)", integer, 0);
2412
+ cmd.option("--under <rowId>", "Parent folder row id (0 = top level)", integer);
2413
+ cmd.option("--parent <rowId>", "Alias for --under", integer);
2403
2414
  examples(cmd, ["--structure 42 --name Backlog", '--structure 42 --name "Phase Two" --under 1001']);
2404
2415
  cmd.action(async (opts) => {
2416
+ const under = resolveEnclosingRow(opts);
2405
2417
  const client = getClient();
2406
2418
  const structureId = await resolveStructureId(client, opts.structure);
2407
2419
  const forest = await client.structure.getForest({ structureId });
2408
- if (opts.under !== 0) {
2409
- assertRowExists(parseForest(forest), opts.under, structureId, "Target parent row");
2420
+ if (under !== 0) {
2421
+ assertRowExists(parseForest(forest), under, structureId, "Target parent row");
2410
2422
  }
2411
2423
  const result = await client.structure.createFolder({
2412
2424
  structureId,
2413
2425
  version: forest.version,
2414
2426
  name: opts.name,
2415
- under: opts.under
2427
+ under
2416
2428
  });
2417
2429
  output({
2418
2430
  structureId,
2419
2431
  itemId: result.itemId,
2420
2432
  rowId: result.newRowIds?.[0] ?? null,
2421
2433
  name: opts.name,
2422
- under: opts.under
2434
+ under
2423
2435
  });
2424
2436
  });
2425
2437
  }
@@ -2460,7 +2472,8 @@ function get7(parent) {
2460
2472
  function list8(parent) {
2461
2473
  const cmd = parent.command("list").description("List folders in a structure");
2462
2474
  scopeIdOption(cmd, "structure", { mandatory: true });
2463
- cmd.option("--parent <rowId>", "Only look under this folder row (default: top level)", integer, 0);
2475
+ cmd.option("--parent <rowId>", "Only look under this folder row (default: top level)", integer);
2476
+ cmd.option("--under <rowId>", "Alias for --parent", integer);
2464
2477
  cmd.option("--depth <n>", "How many levels below the parent to include", integer, 1);
2465
2478
  cmd.option("--query <text>", "Only folders whose name contains this text, at any depth", text);
2466
2479
  paginationOptions(cmd, { defaultLimit: 25 });
@@ -2476,7 +2489,8 @@ function list8(parent) {
2476
2489
  const structureId = await resolveStructureId(client, opts.structure);
2477
2490
  const forest = await client.structure.getForest({ structureId });
2478
2491
  const rows = parseForest(forest);
2479
- const scoped = opts.query ? opts.parent === 0 ? rows : descendantsOf(rows, opts.parent) : withinDepth(rows, opts.parent, opts.depth);
2492
+ const parent2 = resolveEnclosingRow(opts);
2493
+ const scoped = opts.query ? parent2 === 0 ? rows : descendantsOf(rows, parent2) : withinDepth(rows, parent2, opts.depth);
2480
2494
  const folders = scoped.filter((r) => r.isFolder);
2481
2495
  let named = await withNames(client, structureId, folders);
2482
2496
  if (opts.query) {
@@ -2515,16 +2529,19 @@ function get8(parent) {
2515
2529
  function list9(parent) {
2516
2530
  const cmd = parent.command("list").description("List every structure visible to you");
2517
2531
  cmd.option("--permissions", "Include each structure\u2019s permission rules");
2532
+ cmd.option("--query <text>", "Only structures whose name contains this text", text);
2518
2533
  paginationOptions(cmd, { defaultLimit: 25 });
2519
2534
  cmd.option("--all", "Collect every match, not just one page");
2520
- examples(cmd, ["", "--limit 50", "--all --permissions"]);
2535
+ examples(cmd, ["", "--query billing", "--limit 50", "--all --permissions"]);
2521
2536
  cmd.action(async (opts) => {
2522
2537
  if (opts.all && process.argv.includes("--limit")) {
2523
2538
  const { CliUsageError: CliUsageError2 } = await import("./dist-JXPWCMQP.js");
2524
2539
  throw new CliUsageError2("--all and --limit ask for opposite things; pass one or the other");
2525
2540
  }
2526
2541
  const client = getClient();
2527
- const { structures } = await client.structure.list({ withPermissions: opts.permissions });
2542
+ const { structures: found } = await client.structure.list({ withPermissions: opts.permissions });
2543
+ const needle = opts.query?.toLowerCase();
2544
+ const structures = needle ? found.filter((s) => (s.name ?? "").toLowerCase().includes(needle)) : found;
2528
2545
  const start = opts.start ?? 0;
2529
2546
  const page = opts.all ? structures.slice(start) : structures.slice(start, start + opts.limit);
2530
2547
  output({ total: structures.length, start, count: page.length, structures: page });
@@ -2536,24 +2553,26 @@ function add(parent) {
2536
2553
  const cmd = parent.command("add").description("Add an issue to a structure, under a folder");
2537
2554
  scopeIdOption(cmd, "structure", { mandatory: true });
2538
2555
  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);
2556
+ cmd.option("--under <rowId>", "Parent folder row id (0 = top level)", integer);
2557
+ cmd.option("--parent <rowId>", "Alias for --under", integer);
2540
2558
  examples(cmd, ["--structure 42 --issue PROJ-123 --under 1001", '--structure "My Structure" --issue PROJ-123']);
2541
2559
  cmd.action(async (opts) => {
2560
+ const under = resolveEnclosingRow(opts);
2542
2561
  const client = getClient();
2543
2562
  const structureId = await resolveStructureId(client, opts.structure);
2544
2563
  const issue = await client.issues.get({ issueKeyOrId: opts.issue, fields: ["summary"] });
2545
2564
  const issueId = Number(issue.id);
2546
2565
  const forest = await client.structure.getForest({ structureId });
2547
2566
  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 }));
2567
+ if (under !== 0) assertRowExists(rows, under, structureId, "Target parent row");
2568
+ const already = childrenOf(rows, under).find((r) => r.itemType === "issue" && r.itemNumericId === issueId);
2569
+ const elsewhere = rows.filter((r) => r.itemType === "issue" && r.itemNumericId === issueId && r.parentRowId !== under).map((r) => ({ rowId: r.rowId, parentRowId: r.parentRowId }));
2551
2570
  if (already) {
2552
2571
  output({
2553
2572
  structureId,
2554
2573
  issue: issue.key,
2555
2574
  rowId: already.rowId,
2556
- under: opts.under,
2575
+ under,
2557
2576
  added: false,
2558
2577
  reason: "already under this parent",
2559
2578
  alsoAppearsAt: elsewhere
@@ -2563,13 +2582,13 @@ function add(parent) {
2563
2582
  const result = await client.structure.updateForest({
2564
2583
  structureId,
2565
2584
  version: forest.version,
2566
- actions: [{ action: "add", under: opts.under, after: 0, before: 0, forest: addSpec(issueId) }]
2585
+ actions: [{ action: "add", under, after: 0, before: 0, forest: addSpec(issueId) }]
2567
2586
  });
2568
2587
  output({
2569
2588
  structureId,
2570
2589
  issue: issue.key,
2571
2590
  rowId: result.newRowIds?.[0] ?? null,
2572
- under: opts.under,
2591
+ under,
2573
2592
  added: true,
2574
2593
  alsoAppearsAt: elsewhere
2575
2594
  });
@@ -2580,17 +2599,36 @@ function add(parent) {
2580
2599
  function list10(parent) {
2581
2600
  const cmd = parent.command("list").description("List rows (issues and folders) under a parent row");
2582
2601
  scopeIdOption(cmd, "structure", { mandatory: true });
2583
- cmd.option("--parent <rowId>", "Parent row id (0 = top level)", integer, 0);
2602
+ cmd.option("--parent <rowId>", "Parent row id (0 = top level)", integer);
2603
+ cmd.option("--under <rowId>", "Alias for --parent", integer);
2584
2604
  cmd.option("--depth <n>", "How many levels below the parent to include", integer, 1);
2605
+ cmd.option("--issue <key>", "Only rows for this issue, wherever they are in the structure", text);
2585
2606
  paginationOptions(cmd, { defaultLimit: 25 });
2586
2607
  cmd.option("--all", "Collect every match, not just one page");
2587
- examples(cmd, ["--structure 42", '--structure "My Structure" --parent 1001 --depth 2']);
2608
+ examples(cmd, [
2609
+ "--structure 42",
2610
+ '--structure "My Structure" --parent 1001 --depth 2',
2611
+ '--structure "My Structure" --issue PROJ-123'
2612
+ ]);
2588
2613
  cmd.action(
2589
2614
  async (opts) => {
2590
2615
  const client = getClient();
2591
2616
  const structureId = await resolveStructureId(client, opts.structure);
2592
2617
  const rows = parseForest(await client.structure.getForest({ structureId }));
2593
- const scoped = withinDepth(rows, opts.parent, opts.depth);
2618
+ if (opts.issue !== void 0 && (opts.parent !== void 0 || opts.under !== void 0)) {
2619
+ throw new CliUsageError(
2620
+ "--issue and --parent ask different questions; pass one",
2621
+ "--issue finds an issue wherever it sits; --parent lists what is under a row."
2622
+ );
2623
+ }
2624
+ let scoped;
2625
+ if (opts.issue !== void 0) {
2626
+ const issue = await client.issues.get({ issueKeyOrId: opts.issue, fields: ["summary"] });
2627
+ const issueId = Number(issue.id);
2628
+ scoped = rows.filter((r) => r.itemType === "issue" && r.itemNumericId === issueId);
2629
+ } else {
2630
+ scoped = withinDepth(rows, resolveEnclosingRow(opts), opts.depth);
2631
+ }
2594
2632
  const start = opts.start ?? 0;
2595
2633
  const page = opts.all ? scoped.slice(start) : scoped.slice(start, start + opts.limit);
2596
2634
  output({
@@ -2609,22 +2647,27 @@ function move(parent) {
2609
2647
  const cmd = parent.command("move").description("Move a row to a different parent");
2610
2648
  subjectArg(cmd, "rowId", { description: "Row id to move (not the issue id)", parser: positiveInt });
2611
2649
  scopeIdOption(cmd, "structure", { mandatory: true });
2612
- cmd.requiredOption("--under <rowId>", "New parent row id (0 = top level)", integer);
2650
+ cmd.option("--under <rowId>", "New parent row id (0 = top level)", integer);
2651
+ cmd.option("--parent <rowId>", "Alias for --under", integer);
2613
2652
  examples(cmd, ["1002 --structure 42 --under 1001", '1002 --structure "My Structure" --under 0']);
2614
2653
  cmd.action(async (rowId, opts) => {
2654
+ if (opts.under === void 0 && opts.parent === void 0) {
2655
+ throw new CliUsageError("A destination is required", "Pass --under <rowId> (or its alias --parent).");
2656
+ }
2657
+ const under = resolveEnclosingRow(opts);
2615
2658
  const client = getClient();
2616
2659
  const structureId = await resolveStructureId(client, opts.structure);
2617
2660
  const forest = await client.structure.getForest({ structureId });
2618
2661
  const rows = parseForest(forest);
2619
2662
  assertRowExists(rows, rowId, structureId);
2620
- if (opts.under !== 0) assertRowExists(rows, opts.under, structureId, "Target parent row");
2663
+ if (under !== 0) assertRowExists(rows, under, structureId, "Target parent row");
2621
2664
  const row = rows.find((r) => r.rowId === rowId);
2622
2665
  const result = await client.structure.updateForest({
2623
2666
  structureId,
2624
2667
  version: forest.version,
2625
- actions: [{ action: "move", rowId, under: opts.under, after: 0, before: 0 }]
2668
+ actions: [{ action: "move", rowId, under, after: 0, before: 0 }]
2626
2669
  });
2627
- output({ structureId, rowId, from: row.parentRowId, under: opts.under, moved: result.successfulActions > 0 });
2670
+ output({ structureId, rowId, from: row.parentRowId, under, moved: result.successfulActions > 0 });
2628
2671
  });
2629
2672
  }
2630
2673
 
@@ -2687,12 +2730,25 @@ function search3(parent) {
2687
2730
  }
2688
2731
  const client = getClient();
2689
2732
  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
- });
2733
+ let res;
2734
+ try {
2735
+ res = await client.issues.search({
2736
+ jql,
2737
+ startAt: opts.start ?? 0,
2738
+ maxResults: opts.limit,
2739
+ fields: opts.fields
2740
+ });
2741
+ } catch (err) {
2742
+ const detail = err.response?.data;
2743
+ const raw = detail === void 0 ? "" : JSON.stringify(detail);
2744
+ if (!raw.includes("Structure query")) throw err;
2745
+ const suggestion = /did you mean ([^"]+?)\?/.exec(raw)?.[1];
2746
+ throw new CliUsageError(
2747
+ `Structure rejected the query ${JSON.stringify(sjql)}`,
2748
+ suggestion !== void 0 ? `The query is S-JQL, not JQL. Structure suggests: ${suggestion}.` : `The query is S-JQL, not JQL \u2014 use shapes like "child of folder('Name')", "descendants of folder('Name')" or "issue in (PROJ-123)", with single quotes inside.`,
2749
+ detail
2750
+ );
2751
+ }
2696
2752
  output({
2697
2753
  jql,
2698
2754
  total: res.total,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiradc-cli",
3
- "version": "1.0.41",
3
+ "version": "1.0.42",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,8 +23,8 @@
23
23
  "tsx": "^4.19.2",
24
24
  "typescript": "^5.7.2",
25
25
  "vitest": "^4.0.16",
26
- "cli-utils": "1.0.0",
27
26
  "config-eslint": "0.0.0",
27
+ "cli-utils": "1.0.0",
28
28
  "config-typescript": "0.0.0"
29
29
  },
30
30
  "engines": {