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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
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
|
-
|
|
702
|
+
payload = nextPayload;
|
|
703
|
+
epicsPayload = nextEpics;
|
|
704
|
+
childrenRaw = nextChildren;
|
|
705
|
+
hasChildrenCache = nextHasCache;
|
|
706
|
+
childrenError = nextError;
|
|
707
|
+
return app.board();
|
|
683
708
|
} catch (err) {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
|
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
|
-
|
|
944
|
+
reply(req, res, async (text) => {
|
|
916
945
|
const body = text ? JSON.parse(text) : {};
|
|
917
|
-
|
|
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
|
-
|
|
951
|
+
reply(req, res, async (text) => {
|
|
924
952
|
const body = text ? JSON.parse(text) : {};
|
|
925
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|