@pruddiman/dispatch 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1030,7 +1030,8 @@ function slugify(input3, maxLength) {
1030
1030
 
1031
1031
  // src/datasources/github.ts
1032
1032
  init_logger();
1033
- var exec = promisify(execFile);
1033
+
1034
+ // src/helpers/branch-validation.ts
1034
1035
  var InvalidBranchNameError = class extends Error {
1035
1036
  constructor(branch, reason) {
1036
1037
  const detail = reason ? ` (${reason})` : "";
@@ -1038,14 +1039,6 @@ var InvalidBranchNameError = class extends Error {
1038
1039
  this.name = "InvalidBranchNameError";
1039
1040
  }
1040
1041
  };
1041
- async function git(args, cwd) {
1042
- const { stdout } = await exec("git", args, { cwd });
1043
- return stdout;
1044
- }
1045
- async function gh(args, cwd) {
1046
- const { stdout } = await exec("gh", args, { cwd });
1047
- return stdout;
1048
- }
1049
1042
  var VALID_BRANCH_NAME_RE = /^[a-zA-Z0-9._\-/]+$/;
1050
1043
  function isValidBranchName(name) {
1051
1044
  if (name.length === 0 || name.length > 255) return false;
@@ -1057,6 +1050,17 @@ function isValidBranchName(name) {
1057
1050
  if (name.includes("//")) return false;
1058
1051
  return true;
1059
1052
  }
1053
+
1054
+ // src/datasources/github.ts
1055
+ var exec = promisify(execFile);
1056
+ async function git(args, cwd) {
1057
+ const { stdout } = await exec("git", args, { cwd });
1058
+ return stdout;
1059
+ }
1060
+ async function gh(args, cwd) {
1061
+ const { stdout } = await exec("gh", args, { cwd });
1062
+ return stdout;
1063
+ }
1060
1064
  function buildBranchName(issueNumber, title, username = "unknown") {
1061
1065
  const slug = slugify(title, 50);
1062
1066
  return `${username}/dispatch/${issueNumber}-${slug}`;
@@ -1267,6 +1271,25 @@ import { execFile as execFile2 } from "child_process";
1267
1271
  import { promisify as promisify2 } from "util";
1268
1272
  init_logger();
1269
1273
  var exec2 = promisify2(execFile2);
1274
+ function mapWorkItemToIssueDetails(item, id, comments, defaults) {
1275
+ const fields = item.fields ?? {};
1276
+ return {
1277
+ number: String(item.id ?? id),
1278
+ title: fields["System.Title"] ?? defaults?.title ?? "",
1279
+ body: fields["System.Description"] ?? defaults?.body ?? "",
1280
+ labels: (fields["System.Tags"] ?? "").split(";").map((t) => t.trim()).filter(Boolean),
1281
+ state: fields["System.State"] ?? defaults?.state ?? "",
1282
+ url: item._links?.html?.href ?? item.url ?? "",
1283
+ comments,
1284
+ acceptanceCriteria: fields["Microsoft.VSTS.Common.AcceptanceCriteria"] ?? "",
1285
+ iterationPath: fields["System.IterationPath"] || void 0,
1286
+ areaPath: fields["System.AreaPath"] || void 0,
1287
+ assignee: fields["System.AssignedTo"]?.displayName || void 0,
1288
+ priority: fields["Microsoft.VSTS.Common.Priority"] ?? void 0,
1289
+ storyPoints: fields["Microsoft.VSTS.Scheduling.StoryPoints"] ?? fields["Microsoft.VSTS.Scheduling.Effort"] ?? fields["Microsoft.VSTS.Scheduling.Size"] ?? void 0,
1290
+ workItemType: fields["System.WorkItemType"] || defaults?.workItemType || void 0
1291
+ };
1292
+ }
1270
1293
  async function detectWorkItemType(opts = {}) {
1271
1294
  try {
1272
1295
  const args = ["boards", "work-item", "type", "list", "--output", "json"];
@@ -1325,17 +1348,50 @@ var datasource2 = {
1325
1348
  } catch {
1326
1349
  throw new Error(`Failed to parse Azure CLI output: ${stdout.slice(0, 200)}`);
1327
1350
  }
1328
- const items = [];
1329
- if (Array.isArray(data)) {
1330
- for (const row of data) {
1331
- const id = String(row.id ?? row.ID ?? "");
1332
- if (id) {
1333
- const detail = await datasource2.fetch(id, opts);
1334
- items.push(detail);
1335
- }
1351
+ if (!Array.isArray(data)) return [];
1352
+ const ids = data.map((row) => String(row.id ?? row.ID ?? "")).filter(Boolean);
1353
+ if (ids.length === 0) return [];
1354
+ try {
1355
+ const batchArgs = [
1356
+ "boards",
1357
+ "work-item",
1358
+ "show",
1359
+ "--id",
1360
+ ...ids,
1361
+ "--output",
1362
+ "json"
1363
+ ];
1364
+ if (opts.org) batchArgs.push("--org", opts.org);
1365
+ if (opts.project) batchArgs.push("--project", opts.project);
1366
+ const { stdout: batchStdout } = await exec2("az", batchArgs, {
1367
+ cwd: opts.cwd || process.cwd()
1368
+ });
1369
+ let batchItems;
1370
+ try {
1371
+ batchItems = JSON.parse(batchStdout);
1372
+ } catch {
1373
+ throw new Error(`Failed to parse Azure CLI output: ${batchStdout.slice(0, 200)}`);
1336
1374
  }
1375
+ const itemsArray = Array.isArray(batchItems) ? batchItems : [batchItems];
1376
+ const commentsArray = [];
1377
+ const CONCURRENCY = 5;
1378
+ for (let i = 0; i < itemsArray.length; i += CONCURRENCY) {
1379
+ const batch = itemsArray.slice(i, i + CONCURRENCY);
1380
+ const batchResults = await Promise.all(
1381
+ batch.map((item) => fetchComments(String(item.id), opts))
1382
+ );
1383
+ commentsArray.push(...batchResults);
1384
+ }
1385
+ return itemsArray.map(
1386
+ (item, i) => mapWorkItemToIssueDetails(item, String(item.id), commentsArray[i])
1387
+ );
1388
+ } catch (err) {
1389
+ log.debug(`Batch work-item show failed, falling back to individual fetches: ${log.extractMessage(err)}`);
1390
+ const results = await Promise.all(
1391
+ ids.map((id) => datasource2.fetch(id, opts))
1392
+ );
1393
+ return results;
1337
1394
  }
1338
- return items;
1339
1395
  },
1340
1396
  async fetch(issueId, opts = {}) {
1341
1397
  const args = [
@@ -1362,24 +1418,8 @@ var datasource2 = {
1362
1418
  } catch {
1363
1419
  throw new Error(`Failed to parse Azure CLI output: ${stdout.slice(0, 200)}`);
1364
1420
  }
1365
- const fields = item.fields ?? {};
1366
1421
  const comments = await fetchComments(issueId, opts);
1367
- return {
1368
- number: String(item.id ?? issueId),
1369
- title: fields["System.Title"] ?? "",
1370
- body: fields["System.Description"] ?? "",
1371
- labels: (fields["System.Tags"] ?? "").split(";").map((t) => t.trim()).filter(Boolean),
1372
- state: fields["System.State"] ?? "",
1373
- url: item._links?.html?.href ?? item.url ?? "",
1374
- comments,
1375
- acceptanceCriteria: fields["Microsoft.VSTS.Common.AcceptanceCriteria"] ?? "",
1376
- iterationPath: fields["System.IterationPath"] || void 0,
1377
- areaPath: fields["System.AreaPath"] || void 0,
1378
- assignee: fields["System.AssignedTo"]?.displayName || void 0,
1379
- priority: fields["Microsoft.VSTS.Common.Priority"] ?? void 0,
1380
- storyPoints: fields["Microsoft.VSTS.Scheduling.StoryPoints"] ?? fields["Microsoft.VSTS.Scheduling.Effort"] ?? fields["Microsoft.VSTS.Scheduling.Size"] ?? void 0,
1381
- workItemType: fields["System.WorkItemType"] || void 0
1382
- };
1422
+ return mapWorkItemToIssueDetails(item, issueId, comments);
1383
1423
  },
1384
1424
  async update(issueId, title, body, opts = {}) {
1385
1425
  const args = [
@@ -1442,30 +1482,26 @@ var datasource2 = {
1442
1482
  } catch {
1443
1483
  throw new Error(`Failed to parse Azure CLI output: ${stdout.slice(0, 200)}`);
1444
1484
  }
1445
- const fields = item.fields ?? {};
1446
- return {
1447
- number: String(item.id),
1448
- title: fields["System.Title"] ?? title,
1449
- body: fields["System.Description"] ?? body,
1450
- labels: (fields["System.Tags"] ?? "").split(";").map((t) => t.trim()).filter(Boolean),
1451
- state: fields["System.State"] ?? "New",
1452
- url: item._links?.html?.href ?? item.url ?? "",
1453
- comments: [],
1454
- acceptanceCriteria: fields["Microsoft.VSTS.Common.AcceptanceCriteria"] ?? "",
1455
- iterationPath: fields["System.IterationPath"] || void 0,
1456
- areaPath: fields["System.AreaPath"] || void 0,
1457
- assignee: fields["System.AssignedTo"]?.displayName || void 0,
1458
- priority: fields["Microsoft.VSTS.Common.Priority"] ?? void 0,
1459
- storyPoints: fields["Microsoft.VSTS.Scheduling.StoryPoints"] ?? fields["Microsoft.VSTS.Scheduling.Effort"] ?? fields["Microsoft.VSTS.Scheduling.Size"] ?? void 0,
1460
- workItemType: fields["System.WorkItemType"] || workItemType
1461
- };
1485
+ return mapWorkItemToIssueDetails(item, String(item.id), [], {
1486
+ title,
1487
+ body,
1488
+ state: "New",
1489
+ workItemType
1490
+ });
1462
1491
  },
1463
1492
  async getDefaultBranch(opts) {
1464
1493
  try {
1465
1494
  const { stdout } = await exec2("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], { cwd: opts.cwd });
1466
1495
  const parts = stdout.trim().split("/");
1467
- return parts[parts.length - 1];
1468
- } catch {
1496
+ const branch = parts[parts.length - 1];
1497
+ if (!isValidBranchName(branch)) {
1498
+ throw new InvalidBranchNameError(branch, "from symbolic-ref output");
1499
+ }
1500
+ return branch;
1501
+ } catch (err) {
1502
+ if (err instanceof InvalidBranchNameError) {
1503
+ throw err;
1504
+ }
1469
1505
  try {
1470
1506
  await exec2("git", ["rev-parse", "--verify", "main"], { cwd: opts.cwd });
1471
1507
  return "main";
@@ -1499,9 +1535,16 @@ var datasource2 = {
1499
1535
  },
1500
1536
  buildBranchName(issueNumber, title, username) {
1501
1537
  const slug = slugify(title, 50);
1502
- return `${username}/dispatch/${issueNumber}-${slug}`;
1538
+ const branch = `${username}/dispatch/${issueNumber}-${slug}`;
1539
+ if (!isValidBranchName(branch)) {
1540
+ throw new InvalidBranchNameError(branch);
1541
+ }
1542
+ return branch;
1503
1543
  },
1504
1544
  async createAndSwitchBranch(branchName, opts) {
1545
+ if (!isValidBranchName(branchName)) {
1546
+ throw new InvalidBranchNameError(branchName);
1547
+ }
1505
1548
  try {
1506
1549
  await exec2("git", ["checkout", "-b", branchName], { cwd: opts.cwd });
1507
1550
  } catch (err) {
@@ -5136,7 +5179,7 @@ async function main() {
5136
5179
  process.exit(0);
5137
5180
  }
5138
5181
  if (args.version) {
5139
- console.log(`dispatch v${"1.3.0"}`);
5182
+ console.log(`dispatch v${"1.3.1"}`);
5140
5183
  process.exit(0);
5141
5184
  }
5142
5185
  const orchestrator = await boot9({ cwd: args.cwd });