@hiroleague/taskmanager 0.0.2 → 0.0.4
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 +1 -1
- package/dist/assets/architecture-YZFGNWBL-DoE0KxgG.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-DeuBhy7X.js +36 -0
- package/dist/assets/{blockDiagram-DXYQGD6D-DQzEOPT2.js → blockDiagram-DXYQGD6D-BDBy9ns9.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-B2Yfcwbo.js → c4Diagram-AHTNJAMY-CpqJj_8a.js} +1 -1
- package/dist/assets/channel-PHRyjspt.js +1 -0
- package/dist/assets/{chunk-2KRD3SAO-C2e-_49I.js → chunk-2KRD3SAO-DEpUsxdZ.js} +1 -1
- package/dist/assets/chunk-336JU56O-BGQvSwLk.js +2 -0
- package/dist/assets/chunk-426QAEUC-Cl9nQN9c.js +1 -0
- package/dist/assets/{chunk-4TB4RGXK-AZq3s1Dh.js → chunk-4TB4RGXK-Dq7aiIrZ.js} +2 -2
- package/dist/assets/{chunk-5FUZZQ4R-XEga0hMC.js → chunk-5FUZZQ4R-B_HuuUjf.js} +1 -1
- package/dist/assets/{chunk-5PVQY5BW-BrmXs2Gs.js → chunk-5PVQY5BW-cGfZCZGU.js} +2 -2
- package/dist/assets/{chunk-67CJDMHE-B1-M78qu.js → chunk-67CJDMHE-BMYAVZfw.js} +1 -1
- package/dist/assets/{chunk-7N4EOEYR-D7mYFpz-.js → chunk-7N4EOEYR-Ct-EY7Nc.js} +1 -1
- package/dist/assets/{chunk-AA7GKIK3-VWI9k39i.js → chunk-AA7GKIK3-Bd4HFpeo.js} +1 -1
- package/dist/assets/{chunk-CIAEETIT-hnu4zamm.js → chunk-CIAEETIT-CrFUkPMT.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-DxUqDyxy.js → chunk-EDXVE4YY-DMDyt0NF.js} +1 -1
- package/dist/assets/{chunk-ENJZ2VHE-BgZKYo1l.js → chunk-ENJZ2VHE-DrWzOrpd.js} +1 -1
- package/dist/assets/{chunk-FOC6F5B3-BJsh9nO9.js → chunk-FOC6F5B3-Bemzq96j.js} +1 -1
- package/dist/assets/{chunk-ICPOFSXX-BNR1V8rT.js → chunk-ICPOFSXX-DkUVjrLw.js} +5 -5
- package/dist/assets/{chunk-K5T4RW27-BLIPdXaZ.js → chunk-K5T4RW27-ALKIf000.js} +5 -5
- package/dist/assets/{chunk-KGLVRYIC-DvaW2TkT.js → chunk-KGLVRYIC-Bg6HNTZ-.js} +1 -1
- package/dist/assets/{chunk-LIHQZDEY-CUsM0M11.js → chunk-LIHQZDEY-DeyGongE.js} +1 -1
- package/dist/assets/{chunk-ORNJ4GCN-CfluNV0_.js → chunk-ORNJ4GCN-Bx83s1bJ.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-CkWzw4JX.js → chunk-OYMX7WX6-BqRUtRpL.js} +1 -1
- package/dist/assets/{chunk-U2HBQHQK-DTJPeU7W.js → chunk-U2HBQHQK-DogcerR6.js} +1 -1
- package/dist/assets/{chunk-X2U36JSP-CrTnmMqG.js → chunk-X2U36JSP-CwVWdmZV.js} +1 -1
- package/dist/assets/chunk-XPW4576I-DQpNCogT.js +32 -0
- package/dist/assets/{chunk-YZCP3GAM-9wq0QKUn.js → chunk-YZCP3GAM-crQSbji9.js} +1 -1
- package/dist/assets/{chunk-ZZ45TVLE-D3I1kLlo.js → chunk-ZZ45TVLE-Bk1S1YtS.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B_TabGaU.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CGnZkUWw.js +1 -0
- package/dist/assets/clone-D4ka472w.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BygGvZGW.js → cose-bilkent-S5V4N54A-RBTHUit8.js} +1 -1
- package/dist/assets/cytoscape.esm-BGJwlmkf.js +321 -0
- package/dist/assets/dagre-B32eYLtm.js +1 -0
- package/dist/assets/{dagre-KV5264BT-lveZDhBf.js → dagre-KV5264BT-nX7tuXXn.js} +1 -1
- package/dist/assets/diagram-5BDNPKRD-DRxMXlQr.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-CoojevGm.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-CWtJyfVW.js +43 -0
- package/dist/assets/diagram-TYMM5635-CsDJC4Hq.js +24 -0
- package/dist/assets/{erDiagram-SMLLAGMA-dx09stuy.js → erDiagram-SMLLAGMA-Cf7Xtd9A.js} +2 -2
- package/dist/assets/{flatten-B2BZ0pzY.js → flatten-CYX_pHZ7.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CJi2WISS.js → flowDiagram-DWJPFMVM-DQaeR16a.js} +3 -3
- package/dist/assets/{ganttDiagram-T4ZO3ILL-OCTvbRxF.js → ganttDiagram-T4ZO3ILL-8EIcztcH.js} +1 -1
- package/dist/assets/gitGraph-7Q5UKJZL-BH9A1SAZ.js +1 -0
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-Bjj94M12.js → gitGraphDiagram-UUTBAWPF-DO9ODqYw.js} +1 -1
- package/dist/assets/graphlib-bPBqlJKT.js +1 -0
- package/dist/assets/identity-Me9aart9.js +1 -0
- package/dist/assets/index-BpzHnKdP.css +1 -0
- package/dist/assets/index-DmNErTAP.js +273 -0
- package/dist/assets/info-OMHHGYJF-BvKR-zWh.js +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-pRTXCm5C.js +2 -0
- package/dist/assets/isEmpty-Cu0k-j1j.js +1 -0
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-Cnc1bwBo.js → ishikawaDiagram-UXIWVN3A-BP2YE5QI.js} +2 -2
- package/dist/assets/{journeyDiagram-VCZTEJTY-BkMxoaPq.js → journeyDiagram-VCZTEJTY-B3l2juoL.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-CwHbIze0.js → kanban-definition-6JOO6SKY-BpIpEOZZ.js} +4 -4
- package/dist/assets/{line-DNzQATGr.js → line-otOkzGl8.js} +1 -1
- package/dist/assets/mermaid-parser.core-xWsW24Gq.js +4 -0
- package/dist/assets/{mindmap-definition-QFDTVHPH-DswAJiEd.js → mindmap-definition-QFDTVHPH-B9khyC7X.js} +3 -3
- package/dist/assets/packet-4T2RLAQJ-D8Dw3nmf.js +1 -0
- package/dist/assets/pie-ZZUOXDRM-ZghowlAE.js +1 -0
- package/dist/assets/{pieDiagram-DEJITSTG-DgQTCddl.js → pieDiagram-DEJITSTG-v32hL3i7.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-c0iZxo2I.js → quadrantDiagram-34T5L4WZ-DIL3GDFt.js} +1 -1
- package/dist/assets/radar-PYXPWWZC-D-PK3JOd.js +1 -0
- package/dist/assets/reduce-CImcgAcU.js +1 -0
- package/dist/assets/{requirementDiagram-MS252O5E-D1moa23Z.js → requirementDiagram-MS252O5E-D8os2-4y.js} +2 -2
- package/dist/assets/{sankeyDiagram-XADWPNL6-woJZoQ58.js → sankeyDiagram-XADWPNL6-BV70D4l5.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-Dvhj7HGn.js → sequenceDiagram-FGHM5R23-Cwu8hQW1.js} +1 -1
- package/dist/assets/stateDiagram-FHFEXIEX-oYUWv7Fb.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CFUTpFu-.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-z-IncVmK.js → timeline-definition-GMOUNBTQ-CxSdKxpL.js} +1 -1
- package/dist/assets/treeView-SZITEDCU-uVgaJQzG.js +1 -0
- package/dist/assets/treemap-W4RFUUIX-Dcad_9AN.js +1 -0
- package/dist/assets/vennDiagram-DHZGUBPP-D4wgD7QI.js +34 -0
- package/dist/assets/wardley-RL74JXVD-CFXrK8mx.js +1 -0
- package/dist/assets/{wardleyDiagram-NUSXRM2D-D-kouujI.js → wardleyDiagram-NUSXRM2D-5Q201ea3.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-D1lnM0pL.js → xychartDiagram-5P7HB3ND-BPZv_axd.js} +3 -3
- package/dist/index.html +17 -13
- package/package.json +2 -4
- package/skills/hiro-task-manager-cli/SKILL.md +6 -4
- package/skills/hiro-task-manager-cli/reference/cli-access-policy.md +1 -0
- package/skills/hiro-task-manager-cli/reference/releases.md +14 -0
- package/src/cli/bootstrap/launcher.ts +19 -5
- package/src/cli/bootstrap/program.test.ts +46 -0
- package/src/cli/bootstrap/program.ts +50 -1
- package/src/cli/commands/query.ts +56 -56
- package/src/cli/commands/releases.ts +22 -0
- package/src/cli/handlers/boards.test.ts +669 -669
- package/src/cli/handlers/cli-wiring.test.ts +38 -1
- package/src/cli/handlers/releases.ts +15 -0
- package/src/cli/handlers/search.test.ts +374 -374
- package/src/cli/handlers/search.ts +17 -17
- package/src/cli/lib/cli-http-errors.test.ts +85 -85
- package/src/cli/lib/launcherUi.test.ts +74 -0
- package/src/cli/lib/launcherUi.ts +47 -0
- package/src/cli/lib/write/releases.ts +64 -1
- package/src/cli/lib/write-result.test.ts +3 -0
- package/src/cli/lib/write-result.ts +3 -0
- package/src/cli/lib/writeCommands.breadth.test.ts +143 -0
- package/src/cli/lib/writeCommands.ts +1 -0
- package/src/cli/subprocess.real-stack.test.ts +625 -611
- package/src/cli/subprocess.smoke.test.ts +954 -954
- package/src/client/api/useBoardChangeStream.ts +421 -168
- package/src/client/api/useBoardIndexStream.ts +35 -0
- package/src/client/components/board/BoardStatsChips.tsx +233 -233
- package/src/client/components/board/BoardStatsContext.tsx +41 -41
- package/src/client/components/board/boardHeaderButtonStyles.ts +38 -38
- package/src/client/components/board/shortcuts/useBoardShortcutKeydown.ts +49 -49
- package/src/client/components/board/useBoardCanvasPanScroll.ts +108 -108
- package/src/client/components/board/useBoardTaskContainerDroppableReact.ts +33 -33
- package/src/client/components/board/useBoardTaskSortableReact.ts +26 -26
- package/src/client/components/layout/AppShell.tsx +5 -2
- package/src/client/components/layout/NotificationToasts.tsx +38 -1
- package/src/client/components/multi-select.tsx +1206 -1206
- package/src/client/components/routing/BoardPage.tsx +20 -20
- package/src/client/components/routing/NavigationRegistrar.tsx +13 -13
- package/src/client/components/task/TaskCard.tsx +643 -643
- package/src/client/components/ui/badge.tsx +49 -49
- package/src/client/components/ui/button.tsx +65 -65
- package/src/client/components/ui/command.tsx +193 -193
- package/src/client/components/ui/dialog.tsx +163 -163
- package/src/client/components/ui/input-group.tsx +155 -155
- package/src/client/components/ui/input.tsx +19 -19
- package/src/client/components/ui/popover.tsx +87 -87
- package/src/client/components/ui/separator.tsx +28 -28
- package/src/client/components/ui/textarea.tsx +18 -18
- package/src/client/index.css +248 -248
- package/src/client/lib/appNavigate.ts +16 -16
- package/src/client/lib/taskCardDate.ts +111 -111
- package/src/client/lib/utils.ts +6 -6
- package/src/client/store/notificationUi.ts +14 -0
- package/src/server/auth.ts +351 -351
- package/src/server/events.ts +31 -4
- package/src/server/migrations/registry.ts +43 -43
- package/src/server/notificationEvents.ts +8 -1
- package/src/server/routes/boards.ts +15 -1
- package/src/server/routes/trash.ts +6 -1
- package/src/shared/boardEvents.ts +6 -0
- package/src/shared/runtimeConfig.ts +256 -256
- package/src/shared/skillsInstall.ts +2 -3
- package/dist/assets/architecture-YZFGNWBL-C1MoQeSs.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-DUEfvDBu.js +0 -36
- package/dist/assets/channel-yBmN_ln0.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Dx_f-9b7.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CSfvZ-nt.js +0 -1
- package/dist/assets/clone-CXokakwV.js +0 -1
- package/dist/assets/cytoscape.esm-BIYWHPG0.js +0 -321
- package/dist/assets/dagre-Do0eD9eI.js +0 -1
- package/dist/assets/diagram-5BDNPKRD-Dq5yM_uY.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-D-SYOmKm.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-lU5t9BZA.js +0 -43
- package/dist/assets/diagram-TYMM5635-6tfUbY3R.js +0 -24
- package/dist/assets/gitGraph-7Q5UKJZL-BXTuQaDM.js +0 -1
- package/dist/assets/graphlib-BIlXYGdM.js +0 -1
- package/dist/assets/identity-D4WOnl_h.js +0 -1
- package/dist/assets/index-CZZuue3D.js +0 -304
- package/dist/assets/index-hMFTu7sr.css +0 -1
- package/dist/assets/info-OMHHGYJF-BeeKt8-X.js +0 -1
- package/dist/assets/infoDiagram-42DDH7IO-wq_opQKO.js +0 -2
- package/dist/assets/mermaid-parser.core-DrLhKJ48.js +0 -4
- package/dist/assets/packet-4T2RLAQJ-DQ-H9_jd.js +0 -1
- package/dist/assets/pie-ZZUOXDRM-BSj0Jsyj.js +0 -1
- package/dist/assets/radar-PYXPWWZC-B7-oRPFL.js +0 -1
- package/dist/assets/reduce-Uumu9GdR.js +0 -1
- package/dist/assets/stateDiagram-FHFEXIEX-Dx5CjenB.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-C_PkrTdc.js +0 -1
- package/dist/assets/treeView-SZITEDCU-CFXle9Az.js +0 -1
- package/dist/assets/treemap-W4RFUUIX-CAW3vWh8.js +0 -1
- package/dist/assets/vennDiagram-DHZGUBPP-CT1ehozU.js +0 -34
- package/dist/assets/wardley-RL74JXVD-7q3ju4kc.js +0 -1
- package/scripts/postinstall-message.mjs +0 -160
- /package/dist/assets/{chunk-4BX2VUAB-ean5NKtU.js → chunk-4BX2VUAB-C70mcfQR.js} +0 -0
- /package/dist/assets/{chunk-55IACEB6-CvSRyJqy.js → chunk-55IACEB6-CWfnqcLM.js} +0 -0
- /package/dist/assets/{chunk-BSJP7CBP-D8kBlJsf.js → chunk-BSJP7CBP-B0LrXV9y.js} +0 -0
- /package/dist/assets/{chunk-FMBD7UC4-DrNhFt1N.js → chunk-FMBD7UC4-_mV71Mwu.js} +0 -0
- /package/dist/assets/{chunk-QZHKN3VN-Csp3OYJY.js → chunk-QZHKN3VN-t2nrsegL.js} +0 -0
- /package/dist/assets/{katex-8mXVa4k3.js → katex-B2dtGfSp.js} +0 -0
- /package/dist/assets/{rough.esm-DtEqI08j.js → rough.esm-DEh6Frf9.js} +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { runSearch } from "../lib/read/search";
|
|
2
|
-
import type { CliContext } from "./context";
|
|
3
|
-
|
|
4
|
-
export async function handleSearch(
|
|
5
|
-
ctx: CliContext,
|
|
6
|
-
queryParts: string[],
|
|
7
|
-
options: {
|
|
8
|
-
board?: string;
|
|
9
|
-
limit?: string;
|
|
10
|
-
offset?: string;
|
|
11
|
-
noPrefix?: boolean;
|
|
12
|
-
pageAll?: boolean;
|
|
13
|
-
fields?: string;
|
|
14
|
-
},
|
|
15
|
-
): Promise<void> {
|
|
16
|
-
await runSearch(ctx, queryParts, options);
|
|
17
|
-
}
|
|
1
|
+
import { runSearch } from "../lib/read/search";
|
|
2
|
+
import type { CliContext } from "./context";
|
|
3
|
+
|
|
4
|
+
export async function handleSearch(
|
|
5
|
+
ctx: CliContext,
|
|
6
|
+
queryParts: string[],
|
|
7
|
+
options: {
|
|
8
|
+
board?: string;
|
|
9
|
+
limit?: string;
|
|
10
|
+
offset?: string;
|
|
11
|
+
noPrefix?: boolean;
|
|
12
|
+
pageAll?: boolean;
|
|
13
|
+
fields?: string;
|
|
14
|
+
},
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
await runSearch(ctx, queryParts, options);
|
|
17
|
+
}
|
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { CLI_ERR } from "../types/errors";
|
|
3
|
-
import { enrichNotFoundError, mapHttpStatusToCliFailure } from "./cli-http-errors";
|
|
4
|
-
import { CliError } from "./output";
|
|
5
|
-
|
|
6
|
-
describe("mapHttpStatusToCliFailure", () => {
|
|
7
|
-
test.each([
|
|
8
|
-
[400, CLI_ERR.badRequest, 9, undefined],
|
|
9
|
-
[401, CLI_ERR.unauthenticated, 10, undefined],
|
|
10
|
-
[403, CLI_ERR.forbidden, 4, undefined],
|
|
11
|
-
[404, CLI_ERR.notFound, 3, undefined],
|
|
12
|
-
[408, CLI_ERR.requestTimeout, 7, true],
|
|
13
|
-
[409, CLI_ERR.conflict, 5, undefined],
|
|
14
|
-
[422, CLI_ERR.badRequest, 9, undefined],
|
|
15
|
-
[426, CLI_ERR.versionMismatch, 8, undefined],
|
|
16
|
-
[429, CLI_ERR.rateLimited, 1, true],
|
|
17
|
-
[502, CLI_ERR.internalError, 1, true],
|
|
18
|
-
[418, CLI_ERR.httpError, 1, undefined],
|
|
19
|
-
] as const)(
|
|
20
|
-
"status %i → code %s exit %i retryable %j",
|
|
21
|
-
(status, code, exit, retryable) => {
|
|
22
|
-
const { exitCode, details } = mapHttpStatusToCliFailure(status, {
|
|
23
|
-
status,
|
|
24
|
-
url: "http://127.0.0.1:1/api/x",
|
|
25
|
-
});
|
|
26
|
-
expect(exitCode).toBe(exit);
|
|
27
|
-
expect(details.code).toBe(code);
|
|
28
|
-
if (retryable === undefined) {
|
|
29
|
-
expect(details.retryable).toBeUndefined();
|
|
30
|
-
} else {
|
|
31
|
-
expect(details.retryable).toBe(retryable);
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
test("599 maps to internal_error (5xx branch)", () => {
|
|
37
|
-
const { exitCode, details } = mapHttpStatusToCliFailure(599, { status: 599 });
|
|
38
|
-
expect(exitCode).toBe(1);
|
|
39
|
-
expect(details.code).toBe(CLI_ERR.internalError);
|
|
40
|
-
expect(details.retryable).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("preserves API code as serverCode, not duplicate top-level code", () => {
|
|
44
|
-
const { details } = mapHttpStatusToCliFailure(400, {
|
|
45
|
-
code: "CUSTOM",
|
|
46
|
-
status: 400,
|
|
47
|
-
});
|
|
48
|
-
expect(details.code).toBe(CLI_ERR.badRequest);
|
|
49
|
-
expect(details.serverCode).toBe("CUSTOM");
|
|
50
|
-
expect((details as Record<string, unknown>).code).toBe(CLI_ERR.badRequest);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("non-string code in body is ignored for serverCode", () => {
|
|
54
|
-
const { details } = mapHttpStatusToCliFailure(404, {
|
|
55
|
-
code: 123,
|
|
56
|
-
status: 404,
|
|
57
|
-
} as Record<string, unknown>);
|
|
58
|
-
expect(details.serverCode).toBeUndefined();
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe("enrichNotFoundError", () => {
|
|
63
|
-
test("merges context when details.code is not_found", () => {
|
|
64
|
-
const err = new CliError("Whatever", 3, { code: CLI_ERR.notFound });
|
|
65
|
-
try {
|
|
66
|
-
enrichNotFoundError(err, { board: "my-board", taskId: 9 });
|
|
67
|
-
expect.unreachable();
|
|
68
|
-
} catch (e) {
|
|
69
|
-
expect(e).toBeInstanceOf(CliError);
|
|
70
|
-
const c = e as CliError;
|
|
71
|
-
expect(c.message).toBe("Whatever");
|
|
72
|
-
expect(c.exitCode).toBe(3);
|
|
73
|
-
expect(c.details).toMatchObject({
|
|
74
|
-
code: CLI_ERR.notFound,
|
|
75
|
-
board: "my-board",
|
|
76
|
-
taskId: 9,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("rethrows unchanged when code is not not_found", () => {
|
|
82
|
-
const err = new CliError("Nope", 4, { code: CLI_ERR.forbidden });
|
|
83
|
-
expect(() => enrichNotFoundError(err, { board: "x" })).toThrow(err);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { CLI_ERR } from "../types/errors";
|
|
3
|
+
import { enrichNotFoundError, mapHttpStatusToCliFailure } from "./cli-http-errors";
|
|
4
|
+
import { CliError } from "./output";
|
|
5
|
+
|
|
6
|
+
describe("mapHttpStatusToCliFailure", () => {
|
|
7
|
+
test.each([
|
|
8
|
+
[400, CLI_ERR.badRequest, 9, undefined],
|
|
9
|
+
[401, CLI_ERR.unauthenticated, 10, undefined],
|
|
10
|
+
[403, CLI_ERR.forbidden, 4, undefined],
|
|
11
|
+
[404, CLI_ERR.notFound, 3, undefined],
|
|
12
|
+
[408, CLI_ERR.requestTimeout, 7, true],
|
|
13
|
+
[409, CLI_ERR.conflict, 5, undefined],
|
|
14
|
+
[422, CLI_ERR.badRequest, 9, undefined],
|
|
15
|
+
[426, CLI_ERR.versionMismatch, 8, undefined],
|
|
16
|
+
[429, CLI_ERR.rateLimited, 1, true],
|
|
17
|
+
[502, CLI_ERR.internalError, 1, true],
|
|
18
|
+
[418, CLI_ERR.httpError, 1, undefined],
|
|
19
|
+
] as const)(
|
|
20
|
+
"status %i → code %s exit %i retryable %j",
|
|
21
|
+
(status, code, exit, retryable) => {
|
|
22
|
+
const { exitCode, details } = mapHttpStatusToCliFailure(status, {
|
|
23
|
+
status,
|
|
24
|
+
url: "http://127.0.0.1:1/api/x",
|
|
25
|
+
});
|
|
26
|
+
expect(exitCode).toBe(exit);
|
|
27
|
+
expect(details.code).toBe(code);
|
|
28
|
+
if (retryable === undefined) {
|
|
29
|
+
expect(details.retryable).toBeUndefined();
|
|
30
|
+
} else {
|
|
31
|
+
expect(details.retryable).toBe(retryable);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
test("599 maps to internal_error (5xx branch)", () => {
|
|
37
|
+
const { exitCode, details } = mapHttpStatusToCliFailure(599, { status: 599 });
|
|
38
|
+
expect(exitCode).toBe(1);
|
|
39
|
+
expect(details.code).toBe(CLI_ERR.internalError);
|
|
40
|
+
expect(details.retryable).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("preserves API code as serverCode, not duplicate top-level code", () => {
|
|
44
|
+
const { details } = mapHttpStatusToCliFailure(400, {
|
|
45
|
+
code: "CUSTOM",
|
|
46
|
+
status: 400,
|
|
47
|
+
});
|
|
48
|
+
expect(details.code).toBe(CLI_ERR.badRequest);
|
|
49
|
+
expect(details.serverCode).toBe("CUSTOM");
|
|
50
|
+
expect((details as Record<string, unknown>).code).toBe(CLI_ERR.badRequest);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("non-string code in body is ignored for serverCode", () => {
|
|
54
|
+
const { details } = mapHttpStatusToCliFailure(404, {
|
|
55
|
+
code: 123,
|
|
56
|
+
status: 404,
|
|
57
|
+
} as Record<string, unknown>);
|
|
58
|
+
expect(details.serverCode).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("enrichNotFoundError", () => {
|
|
63
|
+
test("merges context when details.code is not_found", () => {
|
|
64
|
+
const err = new CliError("Whatever", 3, { code: CLI_ERR.notFound });
|
|
65
|
+
try {
|
|
66
|
+
enrichNotFoundError(err, { board: "my-board", taskId: 9 });
|
|
67
|
+
expect.unreachable();
|
|
68
|
+
} catch (e) {
|
|
69
|
+
expect(e).toBeInstanceOf(CliError);
|
|
70
|
+
const c = e as CliError;
|
|
71
|
+
expect(c.message).toBe("Whatever");
|
|
72
|
+
expect(c.exitCode).toBe(3);
|
|
73
|
+
expect(c.details).toMatchObject({
|
|
74
|
+
code: CLI_ERR.notFound,
|
|
75
|
+
board: "my-board",
|
|
76
|
+
taskId: 9,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("rethrows unchanged when code is not not_found", () => {
|
|
82
|
+
const err = new CliError("Nope", 4, { code: CLI_ERR.forbidden });
|
|
83
|
+
expect(() => enrichNotFoundError(err, { board: "x" })).toThrow(err);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { printSetupContinuePrompt, printSetupNextSteps } from "./launcherUi";
|
|
3
|
+
|
|
4
|
+
async function captureConsoleLog(
|
|
5
|
+
run: () => Promise<void> | void,
|
|
6
|
+
): Promise<string> {
|
|
7
|
+
const originalLog = console.log;
|
|
8
|
+
const lines: string[] = [];
|
|
9
|
+
console.log = (...args: unknown[]) => {
|
|
10
|
+
lines.push(args.map(String).join(" "));
|
|
11
|
+
};
|
|
12
|
+
try {
|
|
13
|
+
await run();
|
|
14
|
+
} finally {
|
|
15
|
+
console.log = originalLog;
|
|
16
|
+
}
|
|
17
|
+
return `${lines.join("\n")}\n`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("printSetupNextSteps", () => {
|
|
21
|
+
test("prints a prominent table with required repo skills install guidance", async () => {
|
|
22
|
+
const stdout = await captureConsoleLog(async () => {
|
|
23
|
+
printSetupNextSteps({
|
|
24
|
+
profileName: "default",
|
|
25
|
+
skillsInstalled: false,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(stdout).toContain("+-");
|
|
30
|
+
expect(stdout).toContain("REQUIRED BEFORE USING hirotm");
|
|
31
|
+
expect(stdout).toContain("Install AI agent skills now. The hirotm CLI depends on these skills.");
|
|
32
|
+
expect(stdout).toContain("hirotm --help");
|
|
33
|
+
expect(stdout).toContain("1. Repo skills : npx skills add hiro-league/hirotaskmanager");
|
|
34
|
+
expect(stdout).toContain("2. Update later : npx skills update");
|
|
35
|
+
expect(stdout).toContain("npx skills add hiro-league/hirotaskmanager");
|
|
36
|
+
expect(stdout).not.toContain("npx skills add \"$HOME/.taskmanager/skills\"");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("prints local skills command when bundled skills were copied", async () => {
|
|
40
|
+
const stdout = await captureConsoleLog(async () => {
|
|
41
|
+
printSetupNextSteps({
|
|
42
|
+
profileName: "default",
|
|
43
|
+
skillsInstalled: true,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(stdout).toContain("npx skills add hiro-league/hirotaskmanager");
|
|
48
|
+
expect(stdout).toContain("2. Local skills : npx skills add \"$HOME/.taskmanager/skills\"");
|
|
49
|
+
expect(stdout).toContain("3. Update later : npx skills update");
|
|
50
|
+
expect(stdout).toContain("npx skills add \"$HOME/.taskmanager/skills\"");
|
|
51
|
+
expect(stdout).toContain("npx skills update");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("prints profile-specific CLI guidance for named profiles", async () => {
|
|
55
|
+
const stdout = await captureConsoleLog(async () => {
|
|
56
|
+
printSetupNextSteps({
|
|
57
|
+
profileName: "work",
|
|
58
|
+
skillsInstalled: false,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(stdout).toContain("hirotm --profile work --help");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("printSetupContinuePrompt", () => {
|
|
67
|
+
test("prints a separate enter-to-continue message", async () => {
|
|
68
|
+
const stdout = await captureConsoleLog(() => {
|
|
69
|
+
printSetupContinuePrompt();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(stdout).toContain("Press Enter to continue and start TaskManager...");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -39,6 +39,18 @@ function paintWarning(text: string): string {
|
|
|
39
39
|
return paint(out, text, ansi.bold + ansi.yellow);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function printAsciiTable(lines: readonly string[]): void {
|
|
43
|
+
const innerWidth = Math.max(76, ...lines.map((line) => line.length));
|
|
44
|
+
const border = ` +-${"-".repeat(innerWidth)}-+`;
|
|
45
|
+
line();
|
|
46
|
+
line(border);
|
|
47
|
+
for (const entry of lines) {
|
|
48
|
+
line(` | ${entry.padEnd(innerWidth)} |`);
|
|
49
|
+
}
|
|
50
|
+
line(border);
|
|
51
|
+
line();
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
export type SpinnerHandle = {
|
|
43
55
|
stop: (finalText?: string | null) => void;
|
|
44
56
|
};
|
|
@@ -126,6 +138,41 @@ export function printSavedProfileSummary(opts: {
|
|
|
126
138
|
);
|
|
127
139
|
}
|
|
128
140
|
|
|
141
|
+
export function printSetupNextSteps(opts: {
|
|
142
|
+
profileName: string;
|
|
143
|
+
skillsInstalled: boolean;
|
|
144
|
+
}): void {
|
|
145
|
+
const cliHelpCommand =
|
|
146
|
+
opts.profileName === "default"
|
|
147
|
+
? "hirotm --help"
|
|
148
|
+
: `hirotm --profile ${opts.profileName} --help`;
|
|
149
|
+
const skillLines = [
|
|
150
|
+
"1. Repo skills : npx skills add hiro-league/hirotaskmanager",
|
|
151
|
+
];
|
|
152
|
+
if (opts.skillsInstalled) {
|
|
153
|
+
skillLines.push("2. Local skills : npx skills add \"$HOME/.taskmanager/skills\"");
|
|
154
|
+
skillLines.push("3. Update later : npx skills update");
|
|
155
|
+
} else {
|
|
156
|
+
skillLines.push("2. Update later : npx skills update");
|
|
157
|
+
}
|
|
158
|
+
const lines = [
|
|
159
|
+
"REQUIRED BEFORE USING hirotm",
|
|
160
|
+
"Install AI agent skills now. The hirotm CLI depends on these skills.",
|
|
161
|
+
"",
|
|
162
|
+
...skillLines,
|
|
163
|
+
"",
|
|
164
|
+
`After skills install: ${cliHelpCommand}`,
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
// Package managers may block postinstall hooks, so setup must surface the
|
|
168
|
+
// exact skills commands in a standalone table before startup continues.
|
|
169
|
+
printAsciiTable(lines);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function printSetupContinuePrompt(): void {
|
|
173
|
+
line("Press Enter to continue and start TaskManager...");
|
|
174
|
+
}
|
|
175
|
+
|
|
129
176
|
export async function printPassphraseHint(): Promise<void> {
|
|
130
177
|
// Keep the browser handoff to one line because the recovery key prints later from the server.
|
|
131
178
|
const text =
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ReleaseMutationResult,
|
|
4
4
|
} from "../../../shared/mutationResults";
|
|
5
5
|
import type { PaginatedListBody } from "../../../shared/pagination";
|
|
6
|
-
import type { ReleaseDefinition } from "../../../shared/models";
|
|
6
|
+
import type { Board, ReleaseDefinition } from "../../../shared/models";
|
|
7
7
|
import type { CliContext } from "../../types/context";
|
|
8
8
|
import { CLI_ERR } from "../../types/errors";
|
|
9
9
|
import { CLI_DEFAULTS } from "../constants";
|
|
@@ -19,6 +19,7 @@ import { fetchAllPages } from "../paginatedFetch";
|
|
|
19
19
|
import { CliError } from "../output";
|
|
20
20
|
import { assertMutuallyExclusive } from "../validation";
|
|
21
21
|
import {
|
|
22
|
+
compactBoardEntity,
|
|
22
23
|
compactReleaseEntity,
|
|
23
24
|
writeReleaseDelete,
|
|
24
25
|
writeSuccess,
|
|
@@ -306,3 +307,65 @@ export async function runReleasesDelete(
|
|
|
306
307
|
enrichNotFoundError(e, { board: boardId, releaseId: rid });
|
|
307
308
|
}
|
|
308
309
|
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Set or clear the board default release (PATCH board `defaultReleaseId`).
|
|
313
|
+
* Validates the release exists on the board before PATCH so errors are clearer than a generic 404.
|
|
314
|
+
*/
|
|
315
|
+
export async function runReleasesSetDefault(
|
|
316
|
+
ctx: CliContext,
|
|
317
|
+
opts: {
|
|
318
|
+
port?: number;
|
|
319
|
+
board: string | undefined;
|
|
320
|
+
releaseId: string | undefined;
|
|
321
|
+
clear: boolean;
|
|
322
|
+
},
|
|
323
|
+
): Promise<void> {
|
|
324
|
+
const boardId = opts.board?.trim();
|
|
325
|
+
if (!boardId) {
|
|
326
|
+
throw new CliError("Missing required option: --board", 2, {
|
|
327
|
+
code: CLI_ERR.missingRequired,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
assertMutuallyExclusive([
|
|
331
|
+
["<release-id>", opts.releaseId, "--clear", opts.clear],
|
|
332
|
+
]);
|
|
333
|
+
const ridRaw = opts.releaseId?.trim();
|
|
334
|
+
if (!opts.clear && (ridRaw === undefined || ridRaw === "")) {
|
|
335
|
+
throw new CliError("Provide <release-id> or use --clear", 2, {
|
|
336
|
+
code: CLI_ERR.missingRequired,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
if (!opts.clear) {
|
|
340
|
+
const rid = parsePositiveInt("releaseId", ridRaw);
|
|
341
|
+
if (rid === undefined) {
|
|
342
|
+
throw new CliError("Invalid release id", 2, {
|
|
343
|
+
code: CLI_ERR.invalidValue,
|
|
344
|
+
releaseId: ridRaw,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
const rows = await fetchAllBoardReleases(ctx, opts.port, boardId);
|
|
348
|
+
if (!rows.some((r) => r.releaseId === rid)) {
|
|
349
|
+
throw new CliError("Release not found", 3, {
|
|
350
|
+
code: CLI_ERR.notFound,
|
|
351
|
+
board: boardId,
|
|
352
|
+
releaseId: rid,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const patch: Record<string, unknown> = {
|
|
358
|
+
defaultReleaseId: opts.clear ? null : Number(ridRaw),
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const board = await ctx.fetchApiMutate<Board>(
|
|
363
|
+
`/boards/${encodeURIComponent(boardId)}`,
|
|
364
|
+
{ method: "PATCH", body: patch },
|
|
365
|
+
{ port: opts.port },
|
|
366
|
+
);
|
|
367
|
+
ctx.printJson(writeSuccess(board, compactBoardEntity(board)));
|
|
368
|
+
} catch (e) {
|
|
369
|
+
enrichNotFoundError(e, { board: boardId });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -29,6 +29,7 @@ describe("compact*Entity", () => {
|
|
|
29
29
|
emoji: null,
|
|
30
30
|
createdAt: "c",
|
|
31
31
|
updatedAt: "u",
|
|
32
|
+
defaultReleaseId: null,
|
|
32
33
|
});
|
|
33
34
|
});
|
|
34
35
|
|
|
@@ -103,6 +104,7 @@ describe("writeSuccess / trashedEntity / writeTrashMove", () => {
|
|
|
103
104
|
emoji: null,
|
|
104
105
|
createdAt: "c",
|
|
105
106
|
updatedAt: "u",
|
|
107
|
+
defaultReleaseId: null,
|
|
106
108
|
},
|
|
107
109
|
);
|
|
108
110
|
expect(envelope).toEqual({
|
|
@@ -118,6 +120,7 @@ describe("writeSuccess / trashedEntity / writeTrashMove", () => {
|
|
|
118
120
|
emoji: null,
|
|
119
121
|
createdAt: "c",
|
|
120
122
|
updatedAt: "u",
|
|
123
|
+
defaultReleaseId: null,
|
|
121
124
|
},
|
|
122
125
|
});
|
|
123
126
|
});
|
|
@@ -9,6 +9,8 @@ export type WriteBoardEntity = {
|
|
|
9
9
|
emoji: string | null;
|
|
10
10
|
createdAt: string;
|
|
11
11
|
updatedAt: string;
|
|
12
|
+
/** Board default release for new tasks (when set). */
|
|
13
|
+
defaultReleaseId?: number | null;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
export type WriteListEntity = {
|
|
@@ -91,6 +93,7 @@ export function compactBoardEntity(board: Board): WriteBoardEntity {
|
|
|
91
93
|
emoji: board.emoji ?? null,
|
|
92
94
|
createdAt: board.createdAt,
|
|
93
95
|
updatedAt: board.updatedAt,
|
|
96
|
+
defaultReleaseId: board.defaultReleaseId ?? null,
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
99
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
runListsUpdate,
|
|
26
26
|
runReleasesAdd,
|
|
27
27
|
runReleasesDelete,
|
|
28
|
+
runReleasesSetDefault,
|
|
28
29
|
runReleasesUpdate,
|
|
29
30
|
runTasksAdd,
|
|
30
31
|
runTasksDelete,
|
|
@@ -118,6 +119,48 @@ describe("writeCommands breadth — validation", () => {
|
|
|
118
119
|
});
|
|
119
120
|
});
|
|
120
121
|
|
|
122
|
+
test("runReleasesSetDefault throws without board", async () => {
|
|
123
|
+
await expect(
|
|
124
|
+
runReleasesSetDefault(ctx, {
|
|
125
|
+
port: 1,
|
|
126
|
+
board: undefined,
|
|
127
|
+
releaseId: "1",
|
|
128
|
+
clear: false,
|
|
129
|
+
}),
|
|
130
|
+
).rejects.toMatchObject({
|
|
131
|
+
exitCode: 2,
|
|
132
|
+
details: expect.objectContaining({ code: CLI_ERR.missingRequired }),
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("runReleasesSetDefault throws without release id and without --clear", async () => {
|
|
137
|
+
await expect(
|
|
138
|
+
runReleasesSetDefault(ctx, {
|
|
139
|
+
port: 1,
|
|
140
|
+
board: "b",
|
|
141
|
+
releaseId: undefined,
|
|
142
|
+
clear: false,
|
|
143
|
+
}),
|
|
144
|
+
).rejects.toMatchObject({
|
|
145
|
+
exitCode: 2,
|
|
146
|
+
details: expect.objectContaining({ code: CLI_ERR.missingRequired }),
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("runReleasesSetDefault throws when release id and --clear together", async () => {
|
|
151
|
+
await expect(
|
|
152
|
+
runReleasesSetDefault(ctx, {
|
|
153
|
+
port: 1,
|
|
154
|
+
board: "b",
|
|
155
|
+
releaseId: "2",
|
|
156
|
+
clear: true,
|
|
157
|
+
}),
|
|
158
|
+
).rejects.toMatchObject({
|
|
159
|
+
exitCode: 2,
|
|
160
|
+
details: expect.objectContaining({ code: CLI_ERR.mutuallyExclusiveOptions }),
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
121
164
|
test("runListsMove throws on multiple placement flags", async () => {
|
|
122
165
|
await expect(
|
|
123
166
|
runListsMove(ctx, {
|
|
@@ -687,6 +730,106 @@ describe("writeCommands breadth — mock fetch happy paths", () => {
|
|
|
687
730
|
});
|
|
688
731
|
});
|
|
689
732
|
|
|
733
|
+
test("runReleasesSetDefault GETs releases then PATCHes board defaultReleaseId", async () => {
|
|
734
|
+
setMockFetch(async (input, init) => {
|
|
735
|
+
const u = reqUrl(input);
|
|
736
|
+
if (
|
|
737
|
+
u.includes("/boards/b/releases") &&
|
|
738
|
+
(init?.method === "GET" || init?.method === undefined)
|
|
739
|
+
) {
|
|
740
|
+
return new Response(
|
|
741
|
+
JSON.stringify({
|
|
742
|
+
items: [
|
|
743
|
+
{
|
|
744
|
+
releaseId: 2,
|
|
745
|
+
name: "v1",
|
|
746
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
747
|
+
},
|
|
748
|
+
],
|
|
749
|
+
total: 1,
|
|
750
|
+
limit: 500,
|
|
751
|
+
offset: 0,
|
|
752
|
+
}),
|
|
753
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
if (u.includes("/api/boards/b") && !u.includes("/releases") && init?.method === "PATCH") {
|
|
757
|
+
expect(JSON.parse(String(init?.body))).toEqual({ defaultReleaseId: 2 });
|
|
758
|
+
return new Response(
|
|
759
|
+
JSON.stringify({
|
|
760
|
+
boardId: 1,
|
|
761
|
+
slug: "b",
|
|
762
|
+
name: "B",
|
|
763
|
+
emoji: null,
|
|
764
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
765
|
+
updatedAt: "2026-01-02T00:00:00.000Z",
|
|
766
|
+
defaultReleaseId: 2,
|
|
767
|
+
releases: [],
|
|
768
|
+
lists: [],
|
|
769
|
+
taskGroups: [],
|
|
770
|
+
taskPriorities: [],
|
|
771
|
+
defaultTaskGroupId: 1,
|
|
772
|
+
autoAssignReleaseOnCreateUi: false,
|
|
773
|
+
autoAssignReleaseOnCreateCli: false,
|
|
774
|
+
} as unknown as Board),
|
|
775
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
throw new Error(`unexpected fetch: ${u} ${init?.method}`);
|
|
779
|
+
});
|
|
780
|
+
const out = await captureStdout(() =>
|
|
781
|
+
runReleasesSetDefault(ctx, {
|
|
782
|
+
port: 22025,
|
|
783
|
+
board: "b",
|
|
784
|
+
releaseId: "2",
|
|
785
|
+
clear: false,
|
|
786
|
+
}),
|
|
787
|
+
);
|
|
788
|
+
expect(JSON.parse(out.trim())).toMatchObject({
|
|
789
|
+
ok: true,
|
|
790
|
+
entity: { type: "board", defaultReleaseId: 2 },
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
test("runReleasesSetDefault --clear PATCHes defaultReleaseId null", async () => {
|
|
795
|
+
setMockFetch(async (input, init) => {
|
|
796
|
+
expect(reqUrl(input)).toContain("/api/boards/b");
|
|
797
|
+
expect(init?.method).toBe("PATCH");
|
|
798
|
+
expect(JSON.parse(String(init?.body))).toEqual({ defaultReleaseId: null });
|
|
799
|
+
return new Response(
|
|
800
|
+
JSON.stringify({
|
|
801
|
+
boardId: 1,
|
|
802
|
+
slug: "b",
|
|
803
|
+
name: "B",
|
|
804
|
+
emoji: null,
|
|
805
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
806
|
+
updatedAt: "2026-01-02T00:00:00.000Z",
|
|
807
|
+
defaultReleaseId: null,
|
|
808
|
+
releases: [],
|
|
809
|
+
lists: [],
|
|
810
|
+
taskGroups: [],
|
|
811
|
+
taskPriorities: [],
|
|
812
|
+
defaultTaskGroupId: 1,
|
|
813
|
+
autoAssignReleaseOnCreateUi: false,
|
|
814
|
+
autoAssignReleaseOnCreateCli: false,
|
|
815
|
+
} as unknown as Board),
|
|
816
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
817
|
+
);
|
|
818
|
+
});
|
|
819
|
+
const out = await captureStdout(() =>
|
|
820
|
+
runReleasesSetDefault(ctx, {
|
|
821
|
+
port: 22026,
|
|
822
|
+
board: "b",
|
|
823
|
+
releaseId: undefined,
|
|
824
|
+
clear: true,
|
|
825
|
+
}),
|
|
826
|
+
);
|
|
827
|
+
expect(JSON.parse(out.trim())).toMatchObject({
|
|
828
|
+
ok: true,
|
|
829
|
+
entity: { type: "board", defaultReleaseId: null },
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
|
|
690
833
|
test("mutating fetch sends runtime client name header", async () => {
|
|
691
834
|
setRuntimeCliClientName("Cursor Agent");
|
|
692
835
|
setMockFetch(async (_input, init) => {
|