pipe-kan 0.20.4 → 0.21.0

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/pipe-kan.js CHANGED
@@ -324,6 +324,9 @@ function resolveJiraBin(bin = process.env.JIRA_BIN ?? "jira", pathVar = process.
324
324
  function createJiraCli(opts = {}) {
325
325
  const bin = opts.bin ?? "jira";
326
326
  const retryDelayMs = opts.retryDelayMs ?? 1000;
327
+ function fmt(args) {
328
+ return args.filter((arg) => arg !== "--raw").map((arg, i, all) => all[i - 1] === "-q" && arg.length > 48 ? `${arg.slice(0, 48)}…` : arg).join(" ");
329
+ }
327
330
  function run(args) {
328
331
  return new Promise((resolveRun, reject) => {
329
332
  const env = { ...process.env };
@@ -349,23 +352,31 @@ function createJiraCli(opts = {}) {
349
352
  for (let attempt = 0;attempt < RETRY_LIMIT; attempt++) {
350
353
  if (result.code === 0 || !rateLimited(result.stderr || result.stdout))
351
354
  return result;
355
+ const delay = retryDelayMs * 2 ** attempt;
356
+ console.log(`jira 429 retry ${delay}ms ${fmt(args)}`);
352
357
  if (retryDelayMs) {
353
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs * 2 ** attempt));
358
+ await new Promise((resolve) => setTimeout(resolve, delay));
354
359
  }
355
360
  result = await run(args);
356
361
  }
357
362
  return result;
358
363
  }
359
364
  async function listOnce(args) {
365
+ const started = Date.now();
360
366
  const result = await runRetry(args);
361
367
  if (result.code !== 0) {
362
368
  const text = result.stderr || result.stdout || "jira issue list failed";
363
- if (emptyList(text))
369
+ if (emptyList(text)) {
370
+ console.log(`jira ${fmt(args)} -> 0 (${Date.now() - started}ms)`);
364
371
  return [];
372
+ }
373
+ console.log(`jira ${fmt(args)} fail (${Date.now() - started}ms)`);
365
374
  throw new Error(text);
366
375
  }
367
376
  const parsed = JSON.parse(result.stdout);
368
- return Array.isArray(parsed) ? parsed : [];
377
+ const page = Array.isArray(parsed) ? parsed : [];
378
+ console.log(`jira ${fmt(args)} -> ${page.length} (${Date.now() - started}ms)`);
379
+ return page;
369
380
  }
370
381
  async function listAll(args) {
371
382
  if (hasPaginate(args)) {
@@ -394,6 +405,7 @@ function createJiraCli(opts = {}) {
394
405
  if (page.length < LIST_PAGE)
395
406
  break;
396
407
  }
408
+ console.log(`jira ${fmt(args)} ${issues.length} total`);
397
409
  return JSON.stringify(issues);
398
410
  }
399
411
  return {
@@ -598,19 +610,6 @@ function stampMissingEpic(board, epic) {
598
610
  }
599
611
  return board;
600
612
  }
601
- function stampRawParent(raw, epic) {
602
- if (!Array.isArray(raw))
603
- return [];
604
- return raw.map((issue) => {
605
- if (!issue || typeof issue !== "object")
606
- return issue;
607
- const current = issue;
608
- const fields = current.fields;
609
- if (!fields || typeof fields !== "object" || fields.parent)
610
- return issue;
611
- return { ...current, fields: { ...fields, parent: { key: epic } } };
612
- });
613
- }
614
613
  function cardsOf(raw) {
615
614
  if (!Array.isArray(raw))
616
615
  return [];
@@ -633,6 +632,18 @@ function createApp(opts) {
633
632
  hasChildrenCache = true;
634
633
  childrenError = undefined;
635
634
  }
635
+ function currentStatus(key) {
636
+ const fromPayload = payload.find((issue) => issue.key === key)?.fields?.status?.name;
637
+ return fromPayload ?? issuesToBoard(epicsPayload).epics.find((epic) => epic.key === key)?.status ?? app.board().epics.find((epic) => epic.key === key)?.status;
638
+ }
639
+ async function tryMove(key, status) {
640
+ const current = currentStatus(key);
641
+ if (current === status) {
642
+ return { ok: true, noop: true, error: undefined };
643
+ }
644
+ const result = await cli.move(key, status);
645
+ return result.ok ? { ok: true, noop: false, error: undefined } : { ok: false, noop: false, error: result.error };
646
+ }
636
647
  const app = {
637
648
  get flags() {
638
649
  return flags;
@@ -659,38 +670,46 @@ function createApp(opts) {
659
670
  async refresh(next) {
660
671
  if (next !== undefined)
661
672
  flags = next;
662
- const [issues, epics] = await Promise.all([
663
- cli.list(flags),
664
- cli.listEpics()
665
- ]);
666
- const nextPayload = JSON.parse(issues);
667
- const nextEpics = JSON.parse(epics);
668
- const keys = issuesToBoard(nextEpics).epics.map((epic) => epic.key);
669
- let nextChildren = [];
670
- let nextHasCache = false;
671
- let nextError;
673
+ console.log("Refresh");
672
674
  try {
673
- nextChildren = JSON.parse(await cli.listChildren(keys));
674
- const cards = cardsOf(nextChildren);
675
- if (cards.length > 0 && !cards.some((card) => card.epic)) {
676
- const stamped = [];
677
- for (const key of keys) {
678
- stamped.push(...stampRawParent(JSON.parse(await cli.listEpic(key)), key));
675
+ const issues = await cli.list(flags);
676
+ const epics = await cli.listEpics();
677
+ const nextPayload = JSON.parse(issues);
678
+ const nextEpics = JSON.parse(epics);
679
+ const keys = issuesToBoard(nextEpics).epics.map((epic) => epic.key);
680
+ const issueCount = Array.isArray(nextPayload) ? nextPayload.length : 0;
681
+ console.log(`Refresh listed ${issueCount} issues, ${keys.length} epics`);
682
+ let nextChildren = [];
683
+ let nextHasCache = false;
684
+ let nextError;
685
+ try {
686
+ nextChildren = JSON.parse(await cli.listChildren(keys));
687
+ const cards = cardsOf(nextChildren);
688
+ if (cards.length > 0 && !cards.some((card) => card.epic)) {
689
+ console.log(`Refresh children missing Epic keys; skip ${keys.length} per-Epic lists`);
690
+ nextChildren = [];
691
+ nextHasCache = false;
692
+ } else {
693
+ nextHasCache = true;
694
+ console.log(`Refresh children ${cards.length}`);
679
695
  }
680
- nextChildren = stamped;
696
+ } catch (err) {
697
+ nextHasCache = false;
698
+ nextChildren = [];
699
+ nextError = err instanceof Error ? err.message : "Epic children list failed";
700
+ console.log("Refresh children failed", nextError);
681
701
  }
682
- nextHasCache = true;
702
+ payload = nextPayload;
703
+ epicsPayload = nextEpics;
704
+ childrenRaw = nextChildren;
705
+ hasChildrenCache = nextHasCache;
706
+ childrenError = nextError;
707
+ return app.board();
683
708
  } catch (err) {
684
- nextHasCache = false;
685
- nextChildren = [];
686
- nextError = err instanceof Error ? err.message : "Epic children list failed";
687
- }
688
- payload = nextPayload;
689
- epicsPayload = nextEpics;
690
- childrenRaw = nextChildren;
691
- hasChildrenCache = nextHasCache;
692
- childrenError = nextError;
693
- return app.board();
709
+ const message = err instanceof Error ? err.message : "Refresh failed";
710
+ console.error("Refresh failed", message);
711
+ return { ...app.board(), error: message };
712
+ }
694
713
  },
695
714
  async children(epic) {
696
715
  if (hasChildrenCache) {
@@ -708,18 +727,20 @@ function createApp(opts) {
708
727
  return stampMissingEpic(issuesToBoard(JSON.parse(await cli.listEpic(epic))), epic);
709
728
  },
710
729
  async move(key, status) {
711
- const fromPayload = payload.find((issue) => issue.key === key)?.fields?.status?.name;
712
- const current = fromPayload ?? issuesToBoard(epicsPayload).epics.find((epic) => epic.key === key)?.status ?? app.board().epics.find((epic) => epic.key === key)?.status;
713
- if (current === status) {
714
- return { ok: true, noop: true, board: app.board() };
715
- }
716
- const result = await cli.move(key, status);
730
+ const result = await tryMove(key, status);
717
731
  if (!result.ok) {
718
732
  return { ok: false, error: result.error, board: app.board() };
719
733
  }
734
+ if (result.noop) {
735
+ return { ok: true, noop: true, board: app.board() };
736
+ }
720
737
  await app.refresh();
721
738
  return { ok: true, board: app.board() };
722
739
  },
740
+ async moveRaw(key, status) {
741
+ const result = await tryMove(key, status);
742
+ return result.ok ? { ok: true, noop: result.noop } : { ok: false, error: result.error };
743
+ },
723
744
  async open(key) {
724
745
  const urlP = cli.open(key);
725
746
  try {
@@ -904,6 +925,14 @@ function readBody(req) {
904
925
  function pathOf(req) {
905
926
  return new URL(req.url ?? "/", "http://127.0.0.1");
906
927
  }
928
+ function reply(req, res, work) {
929
+ readBody(req).then(work).catch((err) => {
930
+ const message = err instanceof Error ? err.message : "request failed";
931
+ console.error(message);
932
+ if (!res.headersSent)
933
+ json(res, 500, { error: message });
934
+ });
935
+ }
907
936
  function handleAppApi(req, res, app) {
908
937
  const url = pathOf(req);
909
938
  const method = (req.method ?? "GET").toUpperCase();
@@ -912,30 +941,36 @@ function handleAppApi(req, res, app) {
912
941
  return true;
913
942
  }
914
943
  if (url.pathname === "/api/refresh" && method === "POST") {
915
- readBody(req).then(async (text) => {
944
+ reply(req, res, async (text) => {
916
945
  const body = text ? JSON.parse(text) : {};
917
- const board = await app.refresh(body.flags);
918
- json(res, 200, board);
946
+ json(res, 200, await app.refresh(body.flags));
919
947
  });
920
948
  return true;
921
949
  }
922
950
  if (url.pathname === "/api/move" && method === "POST") {
923
- readBody(req).then(async (text) => {
951
+ reply(req, res, async (text) => {
924
952
  const body = text ? JSON.parse(text) : {};
925
- const result = await app.move(String(body.key ?? ""), String(body.status ?? ""));
953
+ const key = String(body.key ?? "");
954
+ const status = String(body.status ?? "");
955
+ if (body.raw) {
956
+ const result = await app.moveRaw(key, status);
957
+ json(res, result.ok ? 200 : 409, result);
958
+ return;
959
+ }
960
+ const result = await app.move(key, status);
926
961
  json(res, result.ok ? 200 : 409, result);
927
962
  });
928
963
  return true;
929
964
  }
930
965
  if (url.pathname === "/api/epic" && method === "POST") {
931
- readBody(req).then(async (text) => {
966
+ reply(req, res, async (text) => {
932
967
  const body = text ? JSON.parse(text) : {};
933
968
  json(res, 200, await app.children(String(body.key ?? "")));
934
969
  });
935
970
  return true;
936
971
  }
937
972
  if (url.pathname === "/api/open" && method === "POST") {
938
- readBody(req).then(async (text) => {
973
+ reply(req, res, async (text) => {
939
974
  const body = text ? JSON.parse(text) : {};
940
975
  json(res, 200, await app.open(String(body.key ?? "")));
941
976
  });