pipe-kan 0.30.0 → 0.31.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/README.md +4 -2
- package/dist/pipe-kan.js +202 -44
- package/dist/ui/assets/index-yGNyVuOj.js +56 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-BRqmsBb_.js +0 -56
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npx pipe-kan
|
|
|
12
12
|
|
|
13
13
|
Opens `http://127.0.0.1:5173`. No clone, no `bun install`. `bunx` / `npx` installs the published package into a cache and runs it.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
If `--plane` is set, first paint is live Refresh. A failed live Refresh stays in Plane mode and keeps the last live Board, with the error on the Board. If live Refresh never succeeded, the Fixture is not shown. If `jira` is on PATH and `--plane` is off, first paint is the Fixture, then Refresh from your existing `jira init`. If `jira` is missing and `--plane` is off, first paint is the Fixture and Refresh, Move, Create, and Edit use the in-process store.
|
|
16
16
|
|
|
17
17
|
The published CLI is on npm. From a clone: `bun run build && bun dist/pipe-kan.js`.
|
|
18
18
|
|
|
@@ -57,7 +57,7 @@ bunx pipe-kan --plane --projects APH,PULSE,PKAN,PUI,DEC
|
|
|
57
57
|
| `PLANE_API_KEY` | (required in Plane mode) | personal access token (`X-API-Key`) |
|
|
58
58
|
| `PLANE_HOST` | `https://plane.tail48fe8.ts.net` | Plane origin; API is `{host}/api/v1` |
|
|
59
59
|
|
|
60
|
-
Same `--projects` style as Jira: comma-separated, trimmed, uppercased. The header **Scope flags** field accepts the same string, then **Refresh**.
|
|
60
|
+
Same `--projects` style as Jira: comma-separated, trimmed, uppercased. The header **Scope flags** field accepts the same string, then **Refresh**. In Plane mode a header **Workspace** pill (beside the project-identifier chip) types a slug, writes `--workspace` into Scope flags, and Refresh-confirms all Epics. Jira omits the pill. Next launch still uses argv `--workspace`, else `PLANE_WORKSPACE`, else `personal`.
|
|
61
61
|
|
|
62
62
|
On Refresh:
|
|
63
63
|
|
|
@@ -68,6 +68,8 @@ On Refresh:
|
|
|
68
68
|
|
|
69
69
|
Create and Edit use Plane REST when `--plane` is set. Pulse, Plane Pro, scraping, and SQL workspace moves are out of scope.
|
|
70
70
|
|
|
71
|
+
A Plane `429 RATE_LIMIT_EXCEEDED` does not fall back to Fake Jira. Plane waits the rate-limit window, then continues.
|
|
72
|
+
|
|
71
73
|
## Multiple projects
|
|
72
74
|
|
|
73
75
|
Default Scope is one Project — jira-cli's `jira init` project. To list more than one Project key:
|
package/dist/pipe-kan.js
CHANGED
|
@@ -1231,6 +1231,8 @@ function createApp(opts) {
|
|
|
1231
1231
|
let childrenRaw = [];
|
|
1232
1232
|
let hasChildrenCache = false;
|
|
1233
1233
|
let childrenError;
|
|
1234
|
+
let refreshError;
|
|
1235
|
+
let live = false;
|
|
1234
1236
|
let targetEndFieldId = readTargetEndFieldMap(fieldMapPath).targetEnd ?? targetEndFieldIdFromJiraConfig(safeRead(jiraConfigPath) ?? "");
|
|
1235
1237
|
let workflowStates;
|
|
1236
1238
|
function boardOpts() {
|
|
@@ -1330,11 +1332,13 @@ function createApp(opts) {
|
|
|
1330
1332
|
childrenRaw = mergeSelectedChildren(childrenRaw, hydrated, keys);
|
|
1331
1333
|
hasChildrenCache = true;
|
|
1332
1334
|
childrenError = undefined;
|
|
1335
|
+
refreshError = undefined;
|
|
1333
1336
|
return app.board();
|
|
1334
1337
|
} catch (err) {
|
|
1335
1338
|
const message = err instanceof Error ? err.message : "Refresh failed";
|
|
1339
|
+
refreshError = message;
|
|
1336
1340
|
console.error("Refresh failed", message);
|
|
1337
|
-
return
|
|
1341
|
+
return app.board();
|
|
1338
1342
|
}
|
|
1339
1343
|
}
|
|
1340
1344
|
async function tryMove(key, status) {
|
|
@@ -1350,12 +1354,21 @@ function createApp(opts) {
|
|
|
1350
1354
|
return flags;
|
|
1351
1355
|
},
|
|
1352
1356
|
board() {
|
|
1357
|
+
const error = refreshError ?? childrenError;
|
|
1358
|
+
if (!live && (refreshError || opts.liveBackend)) {
|
|
1359
|
+
const empty = toBoard([]);
|
|
1360
|
+
return {
|
|
1361
|
+
columns: empty.columns,
|
|
1362
|
+
epics: [],
|
|
1363
|
+
...error ? { error } : {}
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1353
1366
|
const board = toBoard(payload);
|
|
1354
1367
|
return {
|
|
1355
1368
|
columns: board.columns,
|
|
1356
1369
|
epics: mergeEpics(toBoard(epicsPayload).epics, board.epics),
|
|
1357
1370
|
...hasChildrenCache ? { children: columnsOf(childrenRaw, targetEndFieldId) } : {},
|
|
1358
|
-
...
|
|
1371
|
+
...error ? { error } : {}
|
|
1359
1372
|
};
|
|
1360
1373
|
},
|
|
1361
1374
|
hydrate(raw, hydrateOpts) {
|
|
@@ -1364,6 +1377,8 @@ function createApp(opts) {
|
|
|
1364
1377
|
childrenRaw = [];
|
|
1365
1378
|
hasChildrenCache = false;
|
|
1366
1379
|
childrenError = undefined;
|
|
1380
|
+
refreshError = undefined;
|
|
1381
|
+
live = false;
|
|
1367
1382
|
if (hydrateOpts?.fromStore)
|
|
1368
1383
|
cacheFromStore();
|
|
1369
1384
|
rememberTargetEndId([...payload, ...epicsPayload, ...childrenRaw]);
|
|
@@ -1417,11 +1432,14 @@ function createApp(opts) {
|
|
|
1417
1432
|
childrenRaw = nextHasCache ? await hydrateRaw(nextChildren) : nextChildren;
|
|
1418
1433
|
hasChildrenCache = nextHasCache;
|
|
1419
1434
|
childrenError = nextError;
|
|
1435
|
+
refreshError = undefined;
|
|
1436
|
+
live = true;
|
|
1420
1437
|
return app.board();
|
|
1421
1438
|
} catch (err) {
|
|
1422
1439
|
const message = err instanceof Error ? err.message : "Refresh failed";
|
|
1440
|
+
refreshError = message;
|
|
1423
1441
|
console.error("Refresh failed", message);
|
|
1424
|
-
return
|
|
1442
|
+
return app.board();
|
|
1425
1443
|
}
|
|
1426
1444
|
},
|
|
1427
1445
|
async children(epic) {
|
|
@@ -1486,6 +1504,12 @@ function createApp(opts) {
|
|
|
1486
1504
|
error: err instanceof Error ? err.message : "jira issue view failed"
|
|
1487
1505
|
};
|
|
1488
1506
|
}
|
|
1507
|
+
},
|
|
1508
|
+
async listWorkspaces() {
|
|
1509
|
+
return cli.listWorkspaces?.() ?? [];
|
|
1510
|
+
},
|
|
1511
|
+
async listProjectIdentifiers(workspace) {
|
|
1512
|
+
return cli.listProjectIdentifiers?.(workspace) ?? [];
|
|
1489
1513
|
}
|
|
1490
1514
|
};
|
|
1491
1515
|
return app;
|
|
@@ -1495,7 +1519,6 @@ function createApp(opts) {
|
|
|
1495
1519
|
var DEFAULT_PLANE_HOST = "https://plane.tail48fe8.ts.net";
|
|
1496
1520
|
var DEFAULT_PLANE_WORKSPACE = "personal";
|
|
1497
1521
|
var STATE_GROUPS = ["backlog", "unstarted", "started", "completed", "cancelled"];
|
|
1498
|
-
var RETRY_LIMIT2 = 4;
|
|
1499
1522
|
function planeHost(env = process.env) {
|
|
1500
1523
|
return (env.PLANE_HOST ?? DEFAULT_PLANE_HOST).replace(/\/$/, "");
|
|
1501
1524
|
}
|
|
@@ -1696,6 +1719,30 @@ function queryPath(path, query) {
|
|
|
1696
1719
|
return path;
|
|
1697
1720
|
return `${path}${path.includes("?") ? "&" : "?"}${encoded}`;
|
|
1698
1721
|
}
|
|
1722
|
+
function rateLimitWaitMs(headers, nowMs, on429) {
|
|
1723
|
+
const retryAfter = headers.get("Retry-After");
|
|
1724
|
+
if (retryAfter) {
|
|
1725
|
+
const seconds = Number(retryAfter.trim());
|
|
1726
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
1727
|
+
return seconds * 1000;
|
|
1728
|
+
const parsed = Date.parse(retryAfter);
|
|
1729
|
+
if (!Number.isNaN(parsed))
|
|
1730
|
+
return Math.max(0, parsed - nowMs);
|
|
1731
|
+
}
|
|
1732
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
1733
|
+
if (reset) {
|
|
1734
|
+
const n = Number(reset.trim());
|
|
1735
|
+
if (Number.isFinite(n)) {
|
|
1736
|
+
const resetMs = n < 1000000000000 ? n * 1000 : n;
|
|
1737
|
+
return Math.max(0, resetMs - nowMs);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
if (on429)
|
|
1741
|
+
return 60000;
|
|
1742
|
+
if (headers.get("X-RateLimit-Remaining")?.trim() === "0")
|
|
1743
|
+
return 60000;
|
|
1744
|
+
return 0;
|
|
1745
|
+
}
|
|
1699
1746
|
function errorMessage(status, body) {
|
|
1700
1747
|
try {
|
|
1701
1748
|
const parsed = JSON.parse(body);
|
|
@@ -1718,33 +1765,63 @@ function createPlaneCli(opts) {
|
|
|
1718
1765
|
const apiBase = planeApiBase(host);
|
|
1719
1766
|
const defaultFlags = opts.flags ?? DEFAULT_FLAGS;
|
|
1720
1767
|
const fetchFn = opts.fetch ?? fetch;
|
|
1721
|
-
const
|
|
1768
|
+
const nowFn = opts.now ?? Date.now;
|
|
1769
|
+
const sleepFn = opts.sleep ?? ((ms) => ms <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1722
1770
|
let lastFlags = defaultFlags;
|
|
1723
1771
|
let workItemResource = "work-items";
|
|
1724
1772
|
let catalog;
|
|
1725
1773
|
function workspaceOf(flags) {
|
|
1726
1774
|
return planeWorkspace(flags || defaultFlags);
|
|
1727
1775
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1776
|
+
let notBefore = 0;
|
|
1777
|
+
let gate = Promise.resolve();
|
|
1778
|
+
function locked(fn) {
|
|
1779
|
+
const run = gate.then(fn, fn);
|
|
1780
|
+
gate = run.then(() => {
|
|
1781
|
+
return;
|
|
1782
|
+
}, () => {
|
|
1783
|
+
return;
|
|
1784
|
+
});
|
|
1785
|
+
return run;
|
|
1786
|
+
}
|
|
1787
|
+
function fail(status, body) {
|
|
1788
|
+
const error = new Error(errorMessage(status, body));
|
|
1789
|
+
error.status = status;
|
|
1790
|
+
throw error;
|
|
1791
|
+
}
|
|
1792
|
+
function noteHeaders(headers, status) {
|
|
1793
|
+
const remaining = headers.get("X-RateLimit-Remaining")?.trim();
|
|
1794
|
+
if (status !== 429 && remaining !== "0")
|
|
1795
|
+
return;
|
|
1796
|
+
const wait = rateLimitWaitMs(headers, nowFn(), status === 429);
|
|
1797
|
+
notBefore = Math.max(notBefore, nowFn() + wait);
|
|
1798
|
+
}
|
|
1799
|
+
async function request(path, init = {}) {
|
|
1800
|
+
return locked(async () => {
|
|
1801
|
+
let retried = false;
|
|
1802
|
+
while (true) {
|
|
1803
|
+
const headers = new Headers(init.headers);
|
|
1804
|
+
headers.set("X-API-Key", opts.apiKey);
|
|
1805
|
+
if (init.body && !headers.has("content-type")) {
|
|
1806
|
+
headers.set("content-type", "application/json");
|
|
1807
|
+
}
|
|
1808
|
+
const delay = notBefore - nowFn();
|
|
1809
|
+
if (delay > 0)
|
|
1810
|
+
await sleepFn(delay);
|
|
1811
|
+
const response = await fetchFn(joinUrl(apiBase, path), { ...init, headers });
|
|
1812
|
+
const body = await response.text();
|
|
1813
|
+
noteHeaders(response.headers, response.status);
|
|
1814
|
+
if (response.status === 429) {
|
|
1815
|
+
if (retried)
|
|
1816
|
+
fail(response.status, body);
|
|
1817
|
+
retried = true;
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
if (!response.ok)
|
|
1821
|
+
fail(response.status, body);
|
|
1822
|
+
return { status: response.status, body };
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
1748
1825
|
}
|
|
1749
1826
|
async function requestJson(path, init) {
|
|
1750
1827
|
const { body } = await request(path, init);
|
|
@@ -1806,6 +1883,19 @@ function createPlaneCli(opts) {
|
|
|
1806
1883
|
return;
|
|
1807
1884
|
return { id, identifier, name: asString(row?.name) ?? identifier };
|
|
1808
1885
|
}
|
|
1886
|
+
function parseWorkspace(raw) {
|
|
1887
|
+
const row = asRecord(raw);
|
|
1888
|
+
const slug = asString(row?.slug);
|
|
1889
|
+
if (!slug)
|
|
1890
|
+
return;
|
|
1891
|
+
return { id: asString(row?.id) ?? slug, name: asString(row?.name) ?? slug, slug };
|
|
1892
|
+
}
|
|
1893
|
+
function parseWorkspaceList(raw) {
|
|
1894
|
+
const rows = Array.isArray(raw) ? raw : asRecord(raw)?.results;
|
|
1895
|
+
if (!Array.isArray(rows))
|
|
1896
|
+
return;
|
|
1897
|
+
return rows.map(parseWorkspace).filter((row) => !!row);
|
|
1898
|
+
}
|
|
1809
1899
|
function parseState(raw) {
|
|
1810
1900
|
const row = asRecord(raw);
|
|
1811
1901
|
const id = asString(row?.id);
|
|
@@ -1853,14 +1943,12 @@ function createPlaneCli(opts) {
|
|
|
1853
1943
|
const modules = new Map;
|
|
1854
1944
|
const columnStates = [];
|
|
1855
1945
|
for (const project of projects) {
|
|
1856
|
-
const
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
})))
|
|
1863
|
-
]);
|
|
1946
|
+
const stateRows = await paginate(`/workspaces/${workspace}/projects/${project.id}/states/`);
|
|
1947
|
+
const labelRows = await paginate(`/workspaces/${workspace}/projects/${project.id}/labels/`);
|
|
1948
|
+
const moduleRows = await paginate(`/workspaces/${workspace}/projects/${project.id}/modules/`);
|
|
1949
|
+
const workRows = await withResource((resource) => paginate(queryPath(`/workspaces/${workspace}/projects/${project.id}/${resource}/`, {
|
|
1950
|
+
expand: "assignees,labels,state,module"
|
|
1951
|
+
})));
|
|
1864
1952
|
const stateById = new Map;
|
|
1865
1953
|
for (const row of stateRows) {
|
|
1866
1954
|
const state = parseState(row);
|
|
@@ -1891,11 +1979,11 @@ function createPlaneCli(opts) {
|
|
|
1891
1979
|
const missingModules = moduleById.size > 0 && workItems.some((item) => item.module_ids == null && item.modules == null && item.module == null);
|
|
1892
1980
|
const issueByModule = new Map;
|
|
1893
1981
|
if (missingModules && moduleById.size) {
|
|
1894
|
-
|
|
1982
|
+
for (const moduleId of moduleById.keys()) {
|
|
1895
1983
|
const rows = await paginate(`/workspaces/${workspace}/projects/${project.id}/modules/${moduleId}/module-issues/`);
|
|
1896
1984
|
const ids = rows.map((row) => asId(asRecord(row)?.issue) ?? asId(row)).filter((id) => !!id);
|
|
1897
1985
|
issueByModule.set(moduleId, ids);
|
|
1898
|
-
}
|
|
1986
|
+
}
|
|
1899
1987
|
}
|
|
1900
1988
|
for (const raw of workItems) {
|
|
1901
1989
|
const item = raw;
|
|
@@ -2023,6 +2111,25 @@ function createPlaneCli(opts) {
|
|
|
2023
2111
|
const loaded = await loadCatalog(flags || defaultFlags);
|
|
2024
2112
|
return loaded.columns;
|
|
2025
2113
|
},
|
|
2114
|
+
async listWorkspaces() {
|
|
2115
|
+
for (const path of ["/users/me/workspaces/", "/workspaces/"]) {
|
|
2116
|
+
try {
|
|
2117
|
+
return parseWorkspaceList(await requestJson(path)) ?? [];
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
const status = err.status;
|
|
2120
|
+
if (status === 404 && path === "/users/me/workspaces/")
|
|
2121
|
+
continue;
|
|
2122
|
+
return [];
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return [];
|
|
2126
|
+
},
|
|
2127
|
+
async listProjectIdentifiers(workspace) {
|
|
2128
|
+
const slug = workspace.trim();
|
|
2129
|
+
if (!slug)
|
|
2130
|
+
return [];
|
|
2131
|
+
return (await paginate(`/workspaces/${slug}/projects/`)).map(parseProject).filter((row) => !!row).map((row) => row.identifier);
|
|
2132
|
+
},
|
|
2026
2133
|
async move(key, status) {
|
|
2027
2134
|
try {
|
|
2028
2135
|
const module = (await loadCatalog(lastFlags)).modules.get(key);
|
|
@@ -2146,7 +2253,8 @@ async function createBoardApp(opts) {
|
|
|
2146
2253
|
store,
|
|
2147
2254
|
cli,
|
|
2148
2255
|
flags,
|
|
2149
|
-
jiraConfigPath: jiraCliConfigPath(env)
|
|
2256
|
+
jiraConfigPath: jiraCliConfigPath(env),
|
|
2257
|
+
liveBackend: plane
|
|
2150
2258
|
});
|
|
2151
2259
|
if (opts.piped) {
|
|
2152
2260
|
app.hydrate(opts.raw);
|
|
@@ -2189,11 +2297,46 @@ function reply(req, res, work) {
|
|
|
2189
2297
|
json(res, 500, { error: message });
|
|
2190
2298
|
});
|
|
2191
2299
|
}
|
|
2192
|
-
function
|
|
2300
|
+
async function boardEnvelope(body, kind, flags, env, app) {
|
|
2301
|
+
if (kind !== "plane")
|
|
2302
|
+
return { ...body, kind };
|
|
2303
|
+
const envelope = { ...body, kind, workspace: planeWorkspace(flags, env) };
|
|
2304
|
+
try {
|
|
2305
|
+
const workspaces = await app.listWorkspaces?.();
|
|
2306
|
+
if (workspaces && workspaces.length)
|
|
2307
|
+
return { ...envelope, workspaces };
|
|
2308
|
+
} catch {}
|
|
2309
|
+
return envelope;
|
|
2310
|
+
}
|
|
2311
|
+
function handleAppApi(req, res, app, opts = {}) {
|
|
2312
|
+
const kind = opts.kind ?? "store";
|
|
2313
|
+
const env = opts.env ?? process.env;
|
|
2193
2314
|
const url = pathOf(req);
|
|
2194
2315
|
const method = (req.method ?? "GET").toUpperCase();
|
|
2195
2316
|
if (url.pathname === "/api/board" && method === "GET") {
|
|
2196
|
-
|
|
2317
|
+
reply(req, res, async () => {
|
|
2318
|
+
json(res, 200, await boardEnvelope({ ...app.board(), flags: app.flags }, kind, app.flags, env, app));
|
|
2319
|
+
});
|
|
2320
|
+
return true;
|
|
2321
|
+
}
|
|
2322
|
+
if (url.pathname === "/api/projects" && method === "GET") {
|
|
2323
|
+
reply(req, res, async () => {
|
|
2324
|
+
if (kind !== "plane") {
|
|
2325
|
+
json(res, 404, { error: "not plane" });
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
const workspace = (url.searchParams.get("workspace") ?? "").trim();
|
|
2329
|
+
if (!workspace) {
|
|
2330
|
+
json(res, 400, { error: "workspace required" });
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
try {
|
|
2334
|
+
json(res, 200, { projects: await app.listProjectIdentifiers?.(workspace) ?? [] });
|
|
2335
|
+
} catch (err) {
|
|
2336
|
+
const message = err instanceof Error ? err.message : "catalog failed";
|
|
2337
|
+
json(res, 409, { error: message });
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2197
2340
|
return true;
|
|
2198
2341
|
}
|
|
2199
2342
|
if (url.pathname === "/api/refresh" && method === "POST") {
|
|
@@ -2205,10 +2348,10 @@ function handleAppApi(req, res, app) {
|
|
|
2205
2348
|
json(res, 400, { error: "selected Refresh requires epicKeys" });
|
|
2206
2349
|
return;
|
|
2207
2350
|
}
|
|
2208
|
-
json(res, 200, await app.refresh(undefined, { scope: "selected", epicKeys }));
|
|
2351
|
+
json(res, 200, await boardEnvelope(await app.refresh(undefined, { scope: "selected", epicKeys }), kind, app.flags, env, app));
|
|
2209
2352
|
return;
|
|
2210
2353
|
}
|
|
2211
|
-
json(res, 200, await app.refresh(body.flags));
|
|
2354
|
+
json(res, 200, await boardEnvelope(await app.refresh(body.flags), kind, app.flags, env, app));
|
|
2212
2355
|
});
|
|
2213
2356
|
return true;
|
|
2214
2357
|
}
|
|
@@ -13237,7 +13380,12 @@ function handleFakeJira(req, res, store) {
|
|
|
13237
13380
|
|
|
13238
13381
|
// src/http.ts
|
|
13239
13382
|
function handleRequest(req, res, ctx) {
|
|
13240
|
-
|
|
13383
|
+
if (handleAppApi(req, res, ctx.app, { kind: ctx.kind }) || handleAgentApi(req, res, ctx.app)) {
|
|
13384
|
+
return true;
|
|
13385
|
+
}
|
|
13386
|
+
if (ctx.kind === "plane")
|
|
13387
|
+
return false;
|
|
13388
|
+
return handleFakeJira(req, res, ctx.store);
|
|
13241
13389
|
}
|
|
13242
13390
|
|
|
13243
13391
|
// src/jira-config.ts
|
|
@@ -13356,7 +13504,7 @@ async function runServer(opts) {
|
|
|
13356
13504
|
flags
|
|
13357
13505
|
});
|
|
13358
13506
|
const server = createServer((req, res) => {
|
|
13359
|
-
if (handleRequest(req, res, { app, store }))
|
|
13507
|
+
if (handleRequest(req, res, { app, store, kind }))
|
|
13360
13508
|
return;
|
|
13361
13509
|
if (opts.fallback) {
|
|
13362
13510
|
opts.fallback(req, res);
|
|
@@ -13367,20 +13515,30 @@ async function runServer(opts) {
|
|
|
13367
13515
|
res.end("pipe-kan UI is missing. Run bun run build.");
|
|
13368
13516
|
}
|
|
13369
13517
|
});
|
|
13518
|
+
if (kind === "plane") {
|
|
13519
|
+
try {
|
|
13520
|
+
await refreshFromJira(app, kind, { piped: Boolean(piped) });
|
|
13521
|
+
} catch (err) {
|
|
13522
|
+
console.error(`${kind} Refresh failed`, err);
|
|
13523
|
+
}
|
|
13524
|
+
const error = app.board().error;
|
|
13525
|
+
if (error)
|
|
13526
|
+
console.error(`${kind} Refresh failed; keeping last live Board`, error);
|
|
13527
|
+
}
|
|
13370
13528
|
const { host, port } = resolveListen();
|
|
13371
13529
|
await bindListen(server, host, port);
|
|
13372
13530
|
const origin = `http://127.0.0.1:${port}`;
|
|
13373
|
-
const fakeConfig = writeJiraConfig(join10(tmpdir3(), "pipe-kan"), origin);
|
|
13374
13531
|
announce(`pipe-kan http://${host}:${port}`);
|
|
13375
13532
|
if (kind === "plane") {
|
|
13376
13533
|
announce(`cli plane ${planeHost()} ${planeWorkspace(flags)}`);
|
|
13377
13534
|
announce(`Plane host default ${DEFAULT_PLANE_HOST}`);
|
|
13378
13535
|
} else {
|
|
13536
|
+
const fakeConfig = writeJiraConfig(join10(tmpdir3(), "pipe-kan"), origin);
|
|
13379
13537
|
announce(`cli ${kind === "jira" ? resolveJiraBin() : "store"}`);
|
|
13380
13538
|
announce(`Fake Jira ${origin}/rest/api/2/search`);
|
|
13381
13539
|
announce(`Fake Jira config ${fakeConfig}`);
|
|
13382
13540
|
}
|
|
13383
|
-
if (kind === "jira"
|
|
13541
|
+
if (kind === "jira") {
|
|
13384
13542
|
try {
|
|
13385
13543
|
await refreshFromJira(app, kind, { piped: Boolean(piped) });
|
|
13386
13544
|
} catch (err) {
|