pipe-kan 0.20.3 → 0.20.5
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 +85 -50
- package/dist/ui/assets/index-iHkcdIak.js +56 -0
- package/dist/ui/index.html +1 -1
- package/package.json +2 -1
- package/dist/ui/assets/index-B3dEOyrq.js +0 -56
package/dist/pipe-kan.js
CHANGED
|
@@ -261,6 +261,10 @@ function flagsToJql(flags) {
|
|
|
261
261
|
function emptyList(text) {
|
|
262
262
|
return /no result found/i.test(text);
|
|
263
263
|
}
|
|
264
|
+
function rateLimited(text) {
|
|
265
|
+
return /\b429\b/.test(text);
|
|
266
|
+
}
|
|
267
|
+
var RETRY_LIMIT = 4;
|
|
264
268
|
var LIST_PAGE = 100;
|
|
265
269
|
function issueKey(issue) {
|
|
266
270
|
if (!issue || typeof issue !== "object" || !("key" in issue))
|
|
@@ -319,6 +323,10 @@ function resolveJiraBin(bin = process.env.JIRA_BIN ?? "jira", pathVar = process.
|
|
|
319
323
|
}
|
|
320
324
|
function createJiraCli(opts = {}) {
|
|
321
325
|
const bin = opts.bin ?? "jira";
|
|
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
|
+
}
|
|
322
330
|
function run(args) {
|
|
323
331
|
return new Promise((resolveRun, reject) => {
|
|
324
332
|
const env = { ...process.env };
|
|
@@ -339,16 +347,36 @@ function createJiraCli(opts = {}) {
|
|
|
339
347
|
child.on("close", (code) => resolveRun({ code: code ?? 1, stdout, stderr }));
|
|
340
348
|
});
|
|
341
349
|
}
|
|
350
|
+
async function runRetry(args) {
|
|
351
|
+
let result = await run(args);
|
|
352
|
+
for (let attempt = 0;attempt < RETRY_LIMIT; attempt++) {
|
|
353
|
+
if (result.code === 0 || !rateLimited(result.stderr || result.stdout))
|
|
354
|
+
return result;
|
|
355
|
+
const delay = retryDelayMs * 2 ** attempt;
|
|
356
|
+
console.log(`jira 429 retry ${delay}ms ${fmt(args)}`);
|
|
357
|
+
if (retryDelayMs) {
|
|
358
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
359
|
+
}
|
|
360
|
+
result = await run(args);
|
|
361
|
+
}
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
342
364
|
async function listOnce(args) {
|
|
343
|
-
const
|
|
365
|
+
const started = Date.now();
|
|
366
|
+
const result = await runRetry(args);
|
|
344
367
|
if (result.code !== 0) {
|
|
345
368
|
const text = result.stderr || result.stdout || "jira issue list failed";
|
|
346
|
-
if (emptyList(text))
|
|
369
|
+
if (emptyList(text)) {
|
|
370
|
+
console.log(`jira ${fmt(args)} -> 0 (${Date.now() - started}ms)`);
|
|
347
371
|
return [];
|
|
372
|
+
}
|
|
373
|
+
console.log(`jira ${fmt(args)} fail (${Date.now() - started}ms)`);
|
|
348
374
|
throw new Error(text);
|
|
349
375
|
}
|
|
350
376
|
const parsed = JSON.parse(result.stdout);
|
|
351
|
-
|
|
377
|
+
const page = Array.isArray(parsed) ? parsed : [];
|
|
378
|
+
console.log(`jira ${fmt(args)} -> ${page.length} (${Date.now() - started}ms)`);
|
|
379
|
+
return page;
|
|
352
380
|
}
|
|
353
381
|
async function listAll(args) {
|
|
354
382
|
if (hasPaginate(args)) {
|
|
@@ -377,6 +405,7 @@ function createJiraCli(opts = {}) {
|
|
|
377
405
|
if (page.length < LIST_PAGE)
|
|
378
406
|
break;
|
|
379
407
|
}
|
|
408
|
+
console.log(`jira ${fmt(args)} ${issues.length} total`);
|
|
380
409
|
return JSON.stringify(issues);
|
|
381
410
|
}
|
|
382
411
|
return {
|
|
@@ -407,7 +436,7 @@ function createJiraCli(opts = {}) {
|
|
|
407
436
|
]);
|
|
408
437
|
},
|
|
409
438
|
async move(key, status) {
|
|
410
|
-
const result = await
|
|
439
|
+
const result = await runRetry(["issue", "move", key, status]);
|
|
411
440
|
if (result.code !== 0) {
|
|
412
441
|
return {
|
|
413
442
|
ok: false,
|
|
@@ -417,12 +446,12 @@ function createJiraCli(opts = {}) {
|
|
|
417
446
|
return { ok: true };
|
|
418
447
|
},
|
|
419
448
|
async open(key) {
|
|
420
|
-
const result = await
|
|
449
|
+
const result = await runRetry(["open", key, "--no-browser"]);
|
|
421
450
|
return result.stdout.trim().split(`
|
|
422
451
|
`).pop() ?? `/browse/${key}`;
|
|
423
452
|
},
|
|
424
453
|
async view(key) {
|
|
425
|
-
const result = await
|
|
454
|
+
const result = await runRetry(["issue", "view", key, "--raw"]);
|
|
426
455
|
if (result.code !== 0) {
|
|
427
456
|
throw new Error((result.stderr || result.stdout || "jira issue view failed").trim());
|
|
428
457
|
}
|
|
@@ -581,19 +610,6 @@ function stampMissingEpic(board, epic) {
|
|
|
581
610
|
}
|
|
582
611
|
return board;
|
|
583
612
|
}
|
|
584
|
-
function stampRawParent(raw, epic) {
|
|
585
|
-
if (!Array.isArray(raw))
|
|
586
|
-
return [];
|
|
587
|
-
return raw.map((issue) => {
|
|
588
|
-
if (!issue || typeof issue !== "object")
|
|
589
|
-
return issue;
|
|
590
|
-
const current = issue;
|
|
591
|
-
const fields = current.fields;
|
|
592
|
-
if (!fields || typeof fields !== "object" || fields.parent)
|
|
593
|
-
return issue;
|
|
594
|
-
return { ...current, fields: { ...fields, parent: { key: epic } } };
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
613
|
function cardsOf(raw) {
|
|
598
614
|
if (!Array.isArray(raw))
|
|
599
615
|
return [];
|
|
@@ -642,34 +658,46 @@ function createApp(opts) {
|
|
|
642
658
|
async refresh(next) {
|
|
643
659
|
if (next !== undefined)
|
|
644
660
|
flags = next;
|
|
645
|
-
|
|
646
|
-
cli.list(flags),
|
|
647
|
-
cli.listEpics()
|
|
648
|
-
]);
|
|
649
|
-
const nextPayload = JSON.parse(issues);
|
|
650
|
-
const nextEpics = JSON.parse(epics);
|
|
651
|
-
const keys = issuesToBoard(nextEpics).epics.map((epic) => epic.key);
|
|
652
|
-
let nextChildren = [];
|
|
653
|
-
let nextHasCache = false;
|
|
654
|
-
let nextError;
|
|
661
|
+
console.log("Refresh");
|
|
655
662
|
try {
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
663
|
+
const issues = await cli.list(flags);
|
|
664
|
+
const epics = await cli.listEpics();
|
|
665
|
+
const nextPayload = JSON.parse(issues);
|
|
666
|
+
const nextEpics = JSON.parse(epics);
|
|
667
|
+
const keys = issuesToBoard(nextEpics).epics.map((epic) => epic.key);
|
|
668
|
+
const issueCount = Array.isArray(nextPayload) ? nextPayload.length : 0;
|
|
669
|
+
console.log(`Refresh listed ${issueCount} issues, ${keys.length} epics`);
|
|
670
|
+
let nextChildren = [];
|
|
671
|
+
let nextHasCache = false;
|
|
672
|
+
let nextError;
|
|
673
|
+
try {
|
|
674
|
+
nextChildren = JSON.parse(await cli.listChildren(keys));
|
|
675
|
+
const cards = cardsOf(nextChildren);
|
|
676
|
+
if (cards.length > 0 && !cards.some((card) => card.epic)) {
|
|
677
|
+
console.log(`Refresh children missing Epic keys; skip ${keys.length} per-Epic lists`);
|
|
678
|
+
nextChildren = [];
|
|
679
|
+
nextHasCache = false;
|
|
680
|
+
} else {
|
|
681
|
+
nextHasCache = true;
|
|
682
|
+
console.log(`Refresh children ${cards.length}`);
|
|
683
|
+
}
|
|
684
|
+
} catch (err) {
|
|
685
|
+
nextHasCache = false;
|
|
686
|
+
nextChildren = [];
|
|
687
|
+
nextError = err instanceof Error ? err.message : "Epic children list failed";
|
|
688
|
+
console.log("Refresh children failed", nextError);
|
|
660
689
|
}
|
|
661
|
-
|
|
690
|
+
payload = nextPayload;
|
|
691
|
+
epicsPayload = nextEpics;
|
|
692
|
+
childrenRaw = nextChildren;
|
|
693
|
+
hasChildrenCache = nextHasCache;
|
|
694
|
+
childrenError = nextError;
|
|
695
|
+
return app.board();
|
|
662
696
|
} catch (err) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
payload = nextPayload;
|
|
668
|
-
epicsPayload = nextEpics;
|
|
669
|
-
childrenRaw = nextChildren;
|
|
670
|
-
hasChildrenCache = nextHasCache;
|
|
671
|
-
childrenError = nextError;
|
|
672
|
-
return app.board();
|
|
697
|
+
const message = err instanceof Error ? err.message : "Refresh failed";
|
|
698
|
+
console.error("Refresh failed", message);
|
|
699
|
+
return { ...app.board(), error: message };
|
|
700
|
+
}
|
|
673
701
|
},
|
|
674
702
|
async children(epic) {
|
|
675
703
|
if (hasChildrenCache) {
|
|
@@ -883,6 +911,14 @@ function readBody(req) {
|
|
|
883
911
|
function pathOf(req) {
|
|
884
912
|
return new URL(req.url ?? "/", "http://127.0.0.1");
|
|
885
913
|
}
|
|
914
|
+
function reply(req, res, work) {
|
|
915
|
+
readBody(req).then(work).catch((err) => {
|
|
916
|
+
const message = err instanceof Error ? err.message : "request failed";
|
|
917
|
+
console.error(message);
|
|
918
|
+
if (!res.headersSent)
|
|
919
|
+
json(res, 500, { error: message });
|
|
920
|
+
});
|
|
921
|
+
}
|
|
886
922
|
function handleAppApi(req, res, app) {
|
|
887
923
|
const url = pathOf(req);
|
|
888
924
|
const method = (req.method ?? "GET").toUpperCase();
|
|
@@ -891,15 +927,14 @@ function handleAppApi(req, res, app) {
|
|
|
891
927
|
return true;
|
|
892
928
|
}
|
|
893
929
|
if (url.pathname === "/api/refresh" && method === "POST") {
|
|
894
|
-
|
|
930
|
+
reply(req, res, async (text) => {
|
|
895
931
|
const body = text ? JSON.parse(text) : {};
|
|
896
|
-
|
|
897
|
-
json(res, 200, board);
|
|
932
|
+
json(res, 200, await app.refresh(body.flags));
|
|
898
933
|
});
|
|
899
934
|
return true;
|
|
900
935
|
}
|
|
901
936
|
if (url.pathname === "/api/move" && method === "POST") {
|
|
902
|
-
|
|
937
|
+
reply(req, res, async (text) => {
|
|
903
938
|
const body = text ? JSON.parse(text) : {};
|
|
904
939
|
const result = await app.move(String(body.key ?? ""), String(body.status ?? ""));
|
|
905
940
|
json(res, result.ok ? 200 : 409, result);
|
|
@@ -907,14 +942,14 @@ function handleAppApi(req, res, app) {
|
|
|
907
942
|
return true;
|
|
908
943
|
}
|
|
909
944
|
if (url.pathname === "/api/epic" && method === "POST") {
|
|
910
|
-
|
|
945
|
+
reply(req, res, async (text) => {
|
|
911
946
|
const body = text ? JSON.parse(text) : {};
|
|
912
947
|
json(res, 200, await app.children(String(body.key ?? "")));
|
|
913
948
|
});
|
|
914
949
|
return true;
|
|
915
950
|
}
|
|
916
951
|
if (url.pathname === "/api/open" && method === "POST") {
|
|
917
|
-
|
|
952
|
+
reply(req, res, async (text) => {
|
|
918
953
|
const body = text ? JSON.parse(text) : {};
|
|
919
954
|
json(res, 200, await app.open(String(body.key ?? "")));
|
|
920
955
|
});
|