jiradc-cli 1.0.8 → 1.0.10

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 +115 -52
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,8 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import { readFileSync } from "fs";
5
+ import { dirname, join as join4 } from "path";
6
+ import { fileURLToPath } from "url";
4
7
  import { styleText } from "util";
5
- import { Command } from "commander";
8
+ import { Command as Command6 } from "commander";
9
+
10
+ // src/commands/board/issues.ts
11
+ import { Argument } from "commander";
12
+
13
+ // src/utils/cli.ts
14
+ import { InvalidArgumentError } from "commander";
15
+ function intInRange(min, max) {
16
+ return (raw) => {
17
+ const n = parseInt(raw, 10);
18
+ if (Number.isNaN(n) || !Number.isFinite(n)) {
19
+ throw new InvalidArgumentError(`Not a number: "${raw}"`);
20
+ }
21
+ if (n < min || n > max) {
22
+ throw new InvalidArgumentError(`Must be between ${min} and ${max} (got ${n})`);
23
+ }
24
+ return n;
25
+ };
26
+ }
27
+ function nonNegativeInt(raw) {
28
+ const n = parseInt(raw, 10);
29
+ if (Number.isNaN(n) || n < 0) {
30
+ throw new InvalidArgumentError(`Must be a non-negative integer (got "${raw}")`);
31
+ }
32
+ return n;
33
+ }
34
+ function positiveInt(raw) {
35
+ const n = parseInt(raw, 10);
36
+ if (Number.isNaN(n) || n < 1) {
37
+ throw new InvalidArgumentError(`Must be a positive integer (got "${raw}")`);
38
+ }
39
+ return n;
40
+ }
6
41
 
7
42
  // src/utils/client.ts
8
43
  import { JiraClient } from "jira-data-center-client";
@@ -125,21 +160,29 @@ function handleError(err) {
125
160
  `
126
161
  );
127
162
  } else {
128
- process.stderr.write(`${JSON.stringify({ error: message })}
129
- `);
163
+ const responseData = err?.response?.data;
164
+ const detail = responseData && typeof responseData === "object" ? responseData : void 0;
165
+ process.stderr.write(
166
+ `${JSON.stringify({
167
+ error: message,
168
+ ...axiosStatus !== void 0 && { statusCode: axiosStatus },
169
+ ...detail && { detail }
170
+ })}
171
+ `
172
+ );
130
173
  }
131
174
  process.exit(1);
132
175
  }
133
176
 
134
177
  // src/commands/board/issues.ts
135
178
  function issues(parent) {
136
- parent.command("issues <id>").description("Get issues for a board").option("--max <number>", "Max results", parseInt).option("--start-at <number>", "Starting index for pagination (default: 0)", parseInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board").addHelpText(
179
+ parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--max <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board").addHelpText(
137
180
  "after",
138
181
  '\nExamples:\n jiradc board issues 42\n jiradc board issues 42 --max 10\n jiradc board issues 42 --jql "status = Open" --fields summary,status\n jiradc board issues 42 --start-at 50 --max 25'
139
182
  ).action(async (id, opts) => {
140
183
  const client = getClient();
141
184
  const result = await client.agile.getBoardIssues({
142
- boardId: parseInt(id, 10),
185
+ boardId: id,
143
186
  startAt: opts.startAt,
144
187
  maxResults: opts.max,
145
188
  fields: opts.fields?.split(",").map((f) => f.trim()),
@@ -150,8 +193,10 @@ function issues(parent) {
150
193
  }
151
194
 
152
195
  // src/commands/board/list.ts
196
+ import { Option } from "commander";
197
+ var BOARD_TYPES = ["scrum", "kanban", "simple"];
153
198
  function list(parent) {
154
- parent.command("list").description("List agile boards").option("--max <number>", "Max results", parseInt).option("--project <key>", "Filter boards by project key or ID").option("--type <type>", "Board type filter (scrum, kanban, simple)").option("--name <name>", "Filter boards by name").addHelpText(
199
+ parent.command("list").description("List agile boards").option("--max <number>", "Max results (1-1000)", intInRange(1, 1e3), 25).option("--project <key>", "Filter boards by project key or ID").addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name").addHelpText(
155
200
  "after",
156
201
  '\nExamples:\n jiradc board list\n jiradc board list --max 10\n jiradc board list --project PROJ\n jiradc board list --type scrum --name "Team Board"'
157
202
  ).action(async (opts) => {
@@ -183,7 +228,7 @@ Examples:
183
228
 
184
229
  // src/commands/field/options.ts
185
230
  function options(parent) {
186
- parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--max <number>", "Max results to return", parseInt).option("--page <number>", "Page number", parseInt).addHelpText(
231
+ parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--max <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--page <number>", "Page number (1-indexed)", positiveInt).addHelpText(
187
232
  "after",
188
233
  '\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --max 20 --page 2'
189
234
  ).action(async (id, opts) => {
@@ -200,7 +245,7 @@ function options(parent) {
200
245
 
201
246
  // src/commands/field/search.ts
202
247
  function search(parent) {
203
- parent.command("search <keyword>").description("Search for fields by name or ID").option("--limit <number>", "Maximum number of results (default: 10)", parseInt).addHelpText(
248
+ parent.command("search <keyword>").description("Search for fields by name or ID").option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25).addHelpText(
204
249
  "after",
205
250
  "\nExamples:\n jiradc field search epic\n jiradc field search customfield_10100\n jiradc field search priority --limit 5"
206
251
  ).action(async (keyword, opts) => {
@@ -410,21 +455,23 @@ function attachments(parent) {
410
455
 
411
456
  // src/commands/issue/batch-changelog.ts
412
457
  function batchChangelog(parent) {
413
- parent.command("batch-changelog <keys>").description("Get changelogs for multiple issues at once").option("--max <number>", "Max changelog entries per issue (default: 50)", parseInt).addHelpText(
458
+ parent.command("batch-changelog <keys>").description("Get changelogs for multiple issues at once").option("--max <number>", "Max changelog entries per issue (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
414
459
  "after",
415
460
  "\nExamples:\n jiradc issue batch-changelog PROJ-1,PROJ-2,PROJ-3\n jiradc issue batch-changelog PROJ-123,PROJ-124 --max 10"
416
461
  ).action(async (keys, opts) => {
417
462
  const client = getClient();
418
463
  const keyList = keys.split(",").map((k) => k.trim());
419
- const results = {};
420
- for (const key of keyList) {
421
- try {
422
- results[key] = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.max });
423
- } catch (error) {
424
- results[key] = { error: error instanceof Error ? error.message : "Unknown error" };
425
- }
426
- }
427
- output(results);
464
+ const entries = await Promise.all(
465
+ keyList.map(async (key) => {
466
+ try {
467
+ const data = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.max });
468
+ return [key, data];
469
+ } catch (error) {
470
+ return [key, { error: error instanceof Error ? error.message : "Unknown error" }];
471
+ }
472
+ })
473
+ );
474
+ output(Object.fromEntries(entries));
428
475
  });
429
476
  }
430
477
 
@@ -453,7 +500,7 @@ Examples:
453
500
 
454
501
  // src/commands/issue/changelog.ts
455
502
  function changelog(parent) {
456
- parent.command("changelog <key>").description("Get changelog for an issue").option("--max <number>", "Max changelog entries (default: 50)", parseInt).addHelpText("after", "\nExamples:\n jiradc issue changelog PROJ-123\n jiradc issue changelog PROJ-123 --max 10").action(async (key, opts) => {
503
+ parent.command("changelog <key>").description("Get changelog for an issue").option("--max <number>", "Max changelog entries (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText("after", "\nExamples:\n jiradc issue changelog PROJ-123\n jiradc issue changelog PROJ-123 --max 10").action(async (key, opts) => {
457
504
  const client = getClient();
458
505
  const result = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.max });
459
506
  output(result);
@@ -511,43 +558,44 @@ Examples:
511
558
  newIssue: result
512
559
  };
513
560
  if (opts.includeAttachments && f.attachment?.length) {
514
- const copied = [];
515
561
  const tmpFiles = [];
516
- for (const att of f.attachment) {
517
- const tmpPath = join3(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
518
- tmpFiles.push(tmpPath);
519
- await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
520
- await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
521
- copied.push(att.filename);
522
- }
562
+ const copied = await Promise.all(
563
+ f.attachment.map(async (att) => {
564
+ const tmpPath = join3(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
565
+ tmpFiles.push(tmpPath);
566
+ await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
567
+ await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
568
+ return att.filename;
569
+ })
570
+ );
523
571
  await Promise.all(tmpFiles.map((p) => unlink(p).catch(() => void 0)));
524
572
  cloneResult.attachmentsCopied = copied.length;
525
573
  cloneResult.attachments = copied;
526
574
  }
527
575
  if (opts.includeLinks && f.issuelinks?.length) {
528
- const linkedCount = { created: 0, skipped: 0 };
529
- for (const link2 of f.issuelinks) {
530
- try {
576
+ const outcomes = await Promise.allSettled(
577
+ f.issuelinks.map((link2) => {
531
578
  if (link2.outwardIssue) {
532
- await client.links.create({
579
+ return client.links.create({
533
580
  typeName: link2.type.name,
534
581
  inwardIssueKey: newKey,
535
582
  outwardIssueKey: link2.outwardIssue.key
536
583
  });
537
- } else if (link2.inwardIssue) {
538
- await client.links.create({
584
+ }
585
+ if (link2.inwardIssue) {
586
+ return client.links.create({
539
587
  typeName: link2.type.name,
540
588
  inwardIssueKey: link2.inwardIssue.key,
541
589
  outwardIssueKey: newKey
542
590
  });
543
591
  }
544
- linkedCount.created++;
545
- } catch {
546
- linkedCount.skipped++;
547
- }
548
- }
549
- cloneResult.linksCopied = linkedCount.created;
550
- if (linkedCount.skipped > 0) cloneResult.linksSkipped = linkedCount.skipped;
592
+ return Promise.resolve();
593
+ })
594
+ );
595
+ const created = outcomes.filter((o) => o.status === "fulfilled").length;
596
+ const skipped = outcomes.filter((o) => o.status === "rejected").length;
597
+ cloneResult.linksCopied = created;
598
+ if (skipped > 0) cloneResult.linksSkipped = skipped;
551
599
  }
552
600
  output(cloneResult);
553
601
  }
@@ -698,7 +746,7 @@ function devStatus(parent) {
698
746
 
699
747
  // src/commands/issue/get-worklog.ts
700
748
  function getWorklog(parent) {
701
- parent.command("get-worklog <key>").description("Get worklogs for an issue").option("--start-at <number>", "Starting index for pagination (default: 0)", parseInt).option("--max <number>", "Max results per page", parseInt).addHelpText(
749
+ parent.command("get-worklog <key>").description("Get worklogs for an issue").option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--max <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
702
750
  "after",
703
751
  "\nExamples:\n jiradc issue get-worklog PROJ-123\n jiradc issue get-worklog PROJ-123 --max 10\n jiradc issue get-worklog PROJ-123 --start-at 10 --max 5"
704
752
  ).action(async (key, opts) => {
@@ -785,7 +833,7 @@ function link(parent) {
785
833
 
786
834
  // src/commands/issue/search.ts
787
835
  function search2(parent) {
788
- parent.command("search <jql>").description("Search issues using JQL").option("--max <number>", "Max results (default: 50)", parseInt).option("--start-at <number>", "Starting index for pagination (default: 0)", parseInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").addHelpText(
836
+ parent.command("search <jql>").description("Search issues using JQL").option("--max <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").addHelpText(
789
837
  "after",
790
838
  '\nExamples:\n jiradc issue search "project = PROJ AND status = Open"\n jiradc issue search "assignee = currentUser()" --max 10 --fields summary,status\n jiradc issue search "project = PROJ" --start-at 50 --max 50'
791
839
  ).action(async (jql, opts) => {
@@ -971,14 +1019,14 @@ Examples:
971
1019
 
972
1020
  // src/commands/sprint/create.ts
973
1021
  function create2(parent) {
974
- parent.command("create").description("Create a new sprint").requiredOption("--board <id>", "Board ID to create sprint in").requiredOption("--name <name>", "Sprint name").option("--start-date <date>", "Start date in ISO 8601 format").option("--end-date <date>", "End date in ISO 8601 format").option("--goal <goal>", "Sprint goal").addHelpText(
1022
+ parent.command("create").description("Create a new sprint").requiredOption("--board <id>", "Board ID to create sprint in", positiveInt).requiredOption("--name <name>", "Sprint name").option("--start-date <date>", "Start date in ISO 8601 format").option("--end-date <date>", "End date in ISO 8601 format").option("--goal <goal>", "Sprint goal").addHelpText(
975
1023
  "after",
976
1024
  '\nExamples:\n jiradc sprint create --board 42 --name "Sprint 10"\n jiradc sprint create --board 42 --name "Sprint 10" --start-date 2026-03-20 --end-date 2026-04-03 --goal "Complete auth module"'
977
1025
  ).action(async (opts) => {
978
1026
  const client = getClient();
979
1027
  const result = await client.agile.createSprint({
980
1028
  name: opts.name,
981
- originBoardId: parseInt(opts.board, 10),
1029
+ originBoardId: opts.board,
982
1030
  startDate: opts.startDate,
983
1031
  endDate: opts.endDate,
984
1032
  goal: opts.goal
@@ -988,14 +1036,15 @@ function create2(parent) {
988
1036
  }
989
1037
 
990
1038
  // src/commands/sprint/issues.ts
1039
+ import { Argument as Argument2 } from "commander";
991
1040
  function issues2(parent) {
992
- parent.command("issues <id>").description("Get issues in a sprint").option("--max <number>", "Max results", parseInt).option("--start-at <number>", "Starting index for pagination (default: 0)", parseInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the sprint").addHelpText(
1041
+ parent.command("issues").description("Get issues in a sprint").addArgument(new Argument2("<id>", "Sprint ID").argParser(positiveInt)).option("--max <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the sprint").addHelpText(
993
1042
  "after",
994
1043
  '\nExamples:\n jiradc sprint issues 100\n jiradc sprint issues 100 --max 20\n jiradc sprint issues 100 --jql "status = Done" --fields summary,status\n jiradc sprint issues 100 --start-at 50 --max 25'
995
1044
  ).action(async (id, opts) => {
996
1045
  const client = getClient();
997
1046
  const result = await client.agile.getSprintIssues({
998
- sprintId: parseInt(id, 10),
1047
+ sprintId: id,
999
1048
  startAt: opts.startAt,
1000
1049
  maxResults: opts.max,
1001
1050
  fields: opts.fields?.split(",").map((f) => f.trim()),
@@ -1006,14 +1055,16 @@ function issues2(parent) {
1006
1055
  }
1007
1056
 
1008
1057
  // src/commands/sprint/list.ts
1058
+ import { Option as Option2 } from "commander";
1059
+ var SPRINT_STATES = ["future", "active", "closed"];
1009
1060
  function list4(parent) {
1010
- parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID").option("--state <state>", "Filter by sprint state (future, active, closed)").addHelpText(
1061
+ parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option2("--state <state>", "Filter by sprint state").choices(SPRINT_STATES)).addHelpText(
1011
1062
  "after",
1012
1063
  "\nExamples:\n jiradc sprint list --board 42\n jiradc sprint list --board 42 --state active"
1013
1064
  ).action(async (opts) => {
1014
1065
  const client = getClient();
1015
1066
  const result = await client.agile.getSprints({
1016
- boardId: parseInt(opts.board, 10),
1067
+ boardId: opts.board,
1017
1068
  state: opts.state
1018
1069
  });
1019
1070
  output(result);
@@ -1021,15 +1072,17 @@ function list4(parent) {
1021
1072
  }
1022
1073
 
1023
1074
  // src/commands/sprint/update.ts
1075
+ import { Argument as Argument3, Option as Option3 } from "commander";
1076
+ var SPRINT_STATES2 = ["future", "active", "closed"];
1024
1077
  function update2(parent) {
1025
- parent.command("update <id>").description("Update an existing sprint").option("--name <name>", "New sprint name").option("--state <state>", "New sprint state (future, active, closed)").option("--start-date <date>", "New start date in ISO 8601 format").option("--end-date <date>", "New end date in ISO 8601 format").option("--goal <goal>", "New sprint goal").addHelpText(
1078
+ parent.command("update").description("Update an existing sprint").addArgument(new Argument3("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name").addOption(new Option3("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format").option("--end-date <date>", "New end date in ISO 8601 format").option("--goal <goal>", "New sprint goal").addHelpText(
1026
1079
  "after",
1027
1080
  '\nExamples:\n jiradc sprint update 100 --name "Sprint 10 - Extended"\n jiradc sprint update 100 --state active\n jiradc sprint update 100 --end-date 2026-04-10 --goal "Updated goal"'
1028
1081
  ).action(
1029
1082
  async (id, opts) => {
1030
1083
  const client = getClient();
1031
1084
  const result = await client.agile.updateSprint({
1032
- sprintId: parseInt(id, 10),
1085
+ sprintId: id,
1033
1086
  name: opts.name,
1034
1087
  state: opts.state,
1035
1088
  startDate: opts.startDate,
@@ -1082,10 +1135,20 @@ Examples:
1082
1135
  }
1083
1136
 
1084
1137
  // src/index.ts
1138
+ function readPackageVersion() {
1139
+ try {
1140
+ const here = dirname(fileURLToPath(import.meta.url));
1141
+ const pkgPath = join4(here, "..", "package.json");
1142
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1143
+ return pkg.version ?? "0.0.0";
1144
+ } catch {
1145
+ return "0.0.0";
1146
+ }
1147
+ }
1085
1148
  var DIM = "\x1B[2m";
1086
1149
  var RESET = "\x1B[0m";
1087
- var program = new Command();
1088
- program.name("jiradc").description("Jira Data Center CLI").version("1.0.0").configureHelp({
1150
+ var program = new Command6();
1151
+ program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion()).configureHelp({
1089
1152
  styleTitle: (str) => styleText("bold", str),
1090
1153
  styleUsage: (str) => styleText("dim", str),
1091
1154
  styleCommandDescription: (str) => styleText("dim", str),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiradc-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "dependencies": {
14
14
  "commander": "^13.1.0",
15
- "jira-data-center-client": "1.0.27"
15
+ "jira-data-center-client": "1.0.29"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "24.10.4",
@@ -22,8 +22,8 @@
22
22
  "tsx": "^4.19.2",
23
23
  "typescript": "^5.7.2",
24
24
  "vitest": "^4.0.16",
25
- "config-eslint": "0.0.0",
26
- "config-typescript": "0.0.0"
25
+ "config-typescript": "0.0.0",
26
+ "config-eslint": "0.0.0"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=22.0.0"