@aaronshaf/plane 0.1.7 → 0.1.10

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.
@@ -1,5 +1,5 @@
1
1
  import { Command, Options, Args } from "@effect/cli";
2
- import { Console, Effect } from "effect";
2
+ import { Console, Effect, Option } from "effect";
3
3
  import { api, decodeOrFail } from "../api.js";
4
4
  import { IssuesResponseSchema } from "../config.js";
5
5
  import { formatIssue } from "../format.js";
@@ -29,6 +29,62 @@ const priorityOption = Options.optional(
29
29
  Options.choice("priority", ["urgent", "high", "medium", "low", "none"]),
30
30
  ).pipe(Options.withDescription("Filter by priority"));
31
31
 
32
+ export function issuesListHandler({
33
+ project,
34
+ state,
35
+ assignee,
36
+ priority,
37
+ }: {
38
+ project: string;
39
+ state: Option.Option<string>;
40
+ assignee: Option.Option<string>;
41
+ priority: Option.Option<string>;
42
+ }) {
43
+ return Effect.gen(function* () {
44
+ const { key, id } = yield* resolveProject(project);
45
+ const raw = yield* api.get(`projects/${id}/issues/?order_by=sequence_id`);
46
+ const { results } = yield* decodeOrFail(IssuesResponseSchema, raw);
47
+
48
+ let filtered = results;
49
+
50
+ if (state._tag === "Some") {
51
+ filtered = filtered.filter((i) => {
52
+ const s = i.state as State | string;
53
+ if (typeof s !== "object") return false;
54
+ const val = state.value.toLowerCase();
55
+ return s.group === val || s.name.toLowerCase() === val;
56
+ });
57
+ }
58
+
59
+ if (assignee._tag === "Some") {
60
+ const isUuid =
61
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
62
+ assignee.value,
63
+ );
64
+ const memberId = isUuid
65
+ ? assignee.value
66
+ : yield* getMemberId(assignee.value);
67
+ filtered = filtered.filter(
68
+ (i) => Array.isArray(i.assignees) && i.assignees.includes(memberId),
69
+ );
70
+ }
71
+
72
+ if (priority._tag === "Some") {
73
+ filtered = filtered.filter((i) => i.priority === priority.value);
74
+ }
75
+
76
+ if (jsonMode) {
77
+ yield* Console.log(JSON.stringify(filtered, null, 2));
78
+ return;
79
+ }
80
+ if (xmlMode) {
81
+ yield* Console.log(toXml(filtered));
82
+ return;
83
+ }
84
+ yield* Console.log(filtered.map((i) => formatIssue(i, key)).join("\n"));
85
+ });
86
+ }
87
+
32
88
  export const issuesList = Command.make(
33
89
  "list",
34
90
  {
@@ -37,50 +93,7 @@ export const issuesList = Command.make(
37
93
  priority: priorityOption,
38
94
  project: projectArg,
39
95
  },
40
- ({ project, state, assignee, priority }) =>
41
- Effect.gen(function* () {
42
- const { key, id } = yield* resolveProject(project);
43
- const raw = yield* api.get(`projects/${id}/issues/?order_by=sequence_id`);
44
- const { results } = yield* decodeOrFail(IssuesResponseSchema, raw);
45
-
46
- let filtered = results;
47
-
48
- if (state._tag === "Some") {
49
- filtered = filtered.filter((i) => {
50
- const s = i.state as State | string;
51
- if (typeof s !== "object") return false;
52
- const val = state.value.toLowerCase();
53
- return s.group === val || s.name.toLowerCase() === val;
54
- });
55
- }
56
-
57
- if (assignee._tag === "Some") {
58
- const isUuid =
59
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
60
- assignee.value,
61
- );
62
- const memberId = isUuid
63
- ? assignee.value
64
- : yield* getMemberId(assignee.value);
65
- filtered = filtered.filter(
66
- (i) => Array.isArray(i.assignees) && i.assignees.includes(memberId),
67
- );
68
- }
69
-
70
- if (priority._tag === "Some") {
71
- filtered = filtered.filter((i) => i.priority === priority.value);
72
- }
73
-
74
- if (jsonMode) {
75
- yield* Console.log(JSON.stringify(filtered, null, 2));
76
- return;
77
- }
78
- if (xmlMode) {
79
- yield* Console.log(toXml(filtered));
80
- return;
81
- }
82
- yield* Console.log(filtered.map((i) => formatIssue(i, key)).join("\n"));
83
- }),
96
+ issuesListHandler,
84
97
  ).pipe(
85
98
  Command.withDescription(
86
99
  "List issues for a project ordered by sequence ID. Each line shows: REF [state-group] state-name title",
@@ -53,8 +53,12 @@ export const labelsCreate = Command.make(
53
53
  ({ project, name, color }) =>
54
54
  Effect.gen(function* () {
55
55
  const { id } = yield* resolveProject(project);
56
- const body: Record<string, unknown> = { name };
57
- if (color._tag === "Some") body["color"] = color.value;
56
+ interface LabelPayload {
57
+ name: string;
58
+ color?: string;
59
+ }
60
+ const body: LabelPayload = { name };
61
+ if (color._tag === "Some") body.color = color.value;
58
62
  const raw = yield* api.post(`projects/${id}/labels/`, body);
59
63
  const label = yield* decodeOrFail(LabelSchema, raw);
60
64
  yield* Console.log(`Created label: ${label.name} (${label.id})`);
@@ -18,32 +18,35 @@ const moduleIdArg = Args.text({ name: "module-id" }).pipe(
18
18
 
19
19
  // --- modules list ---
20
20
 
21
+ export function modulesListHandler({ project }: { project: string }) {
22
+ return Effect.gen(function* () {
23
+ const { id } = yield* resolveProject(project);
24
+ const raw = yield* api.get(`projects/${id}/modules/`);
25
+ const { results } = yield* decodeOrFail(ModulesResponseSchema, raw);
26
+ if (jsonMode) {
27
+ yield* Console.log(JSON.stringify(results, null, 2));
28
+ return;
29
+ }
30
+ if (xmlMode) {
31
+ yield* Console.log(toXml(results));
32
+ return;
33
+ }
34
+ if (results.length === 0) {
35
+ yield* Console.log("No modules found");
36
+ return;
37
+ }
38
+ const lines = results.map((m) => {
39
+ const status = (m.status ?? "?").padEnd(12);
40
+ return `${m.id} ${status} ${m.name}`;
41
+ });
42
+ yield* Console.log(lines.join("\n"));
43
+ });
44
+ }
45
+
21
46
  export const modulesList = Command.make(
22
47
  "list",
23
48
  { project: projectArg },
24
- ({ project }) =>
25
- Effect.gen(function* () {
26
- const { id } = yield* resolveProject(project);
27
- const raw = yield* api.get(`projects/${id}/modules/`);
28
- const { results } = yield* decodeOrFail(ModulesResponseSchema, raw);
29
- if (jsonMode) {
30
- yield* Console.log(JSON.stringify(results, null, 2));
31
- return;
32
- }
33
- if (xmlMode) {
34
- yield* Console.log(toXml(results));
35
- return;
36
- }
37
- if (results.length === 0) {
38
- yield* Console.log("No modules found");
39
- return;
40
- }
41
- const lines = results.map((m) => {
42
- const status = (m.status ?? "?").padEnd(12);
43
- return `${m.id} ${status} ${m.name}`;
44
- });
45
- yield* Console.log(lines.join("\n"));
46
- }),
49
+ modulesListHandler,
47
50
  ).pipe(
48
51
  Command.withDescription(
49
52
  "List modules for a project. Shows module UUID, status, and name.\n\nExample:\n plane modules list PROJ",
@@ -52,37 +55,46 @@ export const modulesList = Command.make(
52
55
 
53
56
  // --- modules issues list ---
54
57
 
58
+ export function moduleIssuesListHandler({
59
+ project,
60
+ moduleId,
61
+ }: {
62
+ project: string;
63
+ moduleId: string;
64
+ }) {
65
+ return Effect.gen(function* () {
66
+ const { key, id } = yield* resolveProject(project);
67
+ const raw = yield* api.get(
68
+ `projects/${id}/modules/${moduleId}/module-issues/`,
69
+ );
70
+ const { results } = yield* decodeOrFail(ModuleIssuesResponseSchema, raw);
71
+ if (jsonMode) {
72
+ yield* Console.log(JSON.stringify(results, null, 2));
73
+ return;
74
+ }
75
+ if (xmlMode) {
76
+ yield* Console.log(toXml(results));
77
+ return;
78
+ }
79
+ if (results.length === 0) {
80
+ yield* Console.log("No issues in module");
81
+ return;
82
+ }
83
+ const lines = results.map((mi) => {
84
+ if (mi.issue_detail) {
85
+ const seq = String(mi.issue_detail.sequence_id).padStart(3, " ");
86
+ return `${key}-${seq} ${mi.issue_detail.name} (${mi.id})`;
87
+ }
88
+ return `${mi.issue} (module-issue: ${mi.id})`;
89
+ });
90
+ yield* Console.log(lines.join("\n"));
91
+ });
92
+ }
93
+
55
94
  export const moduleIssuesList = Command.make(
56
95
  "list",
57
96
  { project: projectArg, moduleId: moduleIdArg },
58
- ({ project, moduleId }) =>
59
- Effect.gen(function* () {
60
- const { key, id } = yield* resolveProject(project);
61
- const raw = yield* api.get(
62
- `projects/${id}/modules/${moduleId}/module-issues/`,
63
- );
64
- const { results } = yield* decodeOrFail(ModuleIssuesResponseSchema, raw);
65
- if (jsonMode) {
66
- yield* Console.log(JSON.stringify(results, null, 2));
67
- return;
68
- }
69
- if (xmlMode) {
70
- yield* Console.log(toXml(results));
71
- return;
72
- }
73
- if (results.length === 0) {
74
- yield* Console.log("No issues in module");
75
- return;
76
- }
77
- const lines = results.map((mi) => {
78
- if (mi.issue_detail) {
79
- const seq = String(mi.issue_detail.sequence_id).padStart(3, " ");
80
- return `${key}-${seq} ${mi.issue_detail.name} (${mi.id})`;
81
- }
82
- return `${mi.issue} (module-issue: ${mi.id})`;
83
- });
84
- yield* Console.log(lines.join("\n"));
85
- }),
97
+ moduleIssuesListHandler,
86
98
  ).pipe(
87
99
  Command.withDescription(
88
100
  "List issues in a module.\n\nExample:\n plane modules issues list PROJ <module-id>",
@@ -95,22 +107,33 @@ const issueRefArg = Args.text({ name: "ref" }).pipe(
95
107
  Args.withDescription("Issue reference to add (e.g. PROJ-29)"),
96
108
  );
97
109
 
110
+ export function moduleIssuesAddHandler({
111
+ project,
112
+ moduleId,
113
+ ref,
114
+ }: {
115
+ project: string;
116
+ moduleId: string;
117
+ ref: string;
118
+ }) {
119
+ return Effect.gen(function* () {
120
+ const { id: projectId } = yield* resolveProject(project);
121
+ const { seq } = yield* parseIssueRef(ref);
122
+ const issue = yield* findIssueBySeq(projectId, seq);
123
+ yield* api.post(
124
+ `projects/${projectId}/modules/${moduleId}/module-issues/`,
125
+ {
126
+ issues: [issue.id],
127
+ },
128
+ );
129
+ yield* Console.log(`Added ${ref} to module ${moduleId}`);
130
+ });
131
+ }
132
+
98
133
  export const moduleIssuesAdd = Command.make(
99
134
  "add",
100
135
  { project: projectArg, moduleId: moduleIdArg, ref: issueRefArg },
101
- ({ project, moduleId, ref }) =>
102
- Effect.gen(function* () {
103
- const { id: projectId } = yield* resolveProject(project);
104
- const { seq } = yield* parseIssueRef(ref);
105
- const issue = yield* findIssueBySeq(projectId, seq);
106
- yield* api.post(
107
- `projects/${projectId}/modules/${moduleId}/module-issues/`,
108
- {
109
- issues: [issue.id],
110
- },
111
- );
112
- yield* Console.log(`Added ${ref} to module ${moduleId}`);
113
- }),
136
+ moduleIssuesAddHandler,
114
137
  ).pipe(
115
138
  Command.withDescription(
116
139
  "Add an issue to a module.\n\nExample:\n plane modules issues add PROJ <module-id> PROJ-29",
@@ -125,6 +148,26 @@ const moduleIssueIdArg = Args.text({ name: "module-issue-id" }).pipe(
125
148
  ),
126
149
  );
127
150
 
151
+ export function moduleIssuesRemoveHandler({
152
+ project,
153
+ moduleId,
154
+ moduleIssueId,
155
+ }: {
156
+ project: string;
157
+ moduleId: string;
158
+ moduleIssueId: string;
159
+ }) {
160
+ return Effect.gen(function* () {
161
+ const { id } = yield* resolveProject(project);
162
+ yield* api.delete(
163
+ `projects/${id}/modules/${moduleId}/module-issues/${moduleIssueId}/`,
164
+ );
165
+ yield* Console.log(
166
+ `Removed module-issue ${moduleIssueId} from module ${moduleId}`,
167
+ );
168
+ });
169
+ }
170
+
128
171
  export const moduleIssuesRemove = Command.make(
129
172
  "remove",
130
173
  {
@@ -132,16 +175,7 @@ export const moduleIssuesRemove = Command.make(
132
175
  moduleId: moduleIdArg,
133
176
  moduleIssueId: moduleIssueIdArg,
134
177
  },
135
- ({ project, moduleId, moduleIssueId }) =>
136
- Effect.gen(function* () {
137
- const { id } = yield* resolveProject(project);
138
- yield* api.delete(
139
- `projects/${id}/modules/${moduleId}/module-issues/${moduleIssueId}/`,
140
- );
141
- yield* Console.log(
142
- `Removed module-issue ${moduleIssueId} from module ${moduleId}`,
143
- );
144
- }),
178
+ moduleIssuesRemoveHandler,
145
179
  ).pipe(
146
180
  Command.withDescription(
147
181
  "Remove an issue from a module using the module-issue join ID.\n\nExample:\n plane modules issues remove PROJ <module-id> <module-issue-id>",
@@ -11,32 +11,35 @@ const projectArg = Args.text({ name: "project" }).pipe(
11
11
 
12
12
  // --- pages list ---
13
13
 
14
+ export function pagesListHandler({ project }: { project: string }) {
15
+ return Effect.gen(function* () {
16
+ const { id } = yield* resolveProject(project);
17
+ const raw = yield* api.get(`projects/${id}/pages/`);
18
+ const { results } = yield* decodeOrFail(PagesResponseSchema, raw);
19
+ if (jsonMode) {
20
+ yield* Console.log(JSON.stringify(results, null, 2));
21
+ return;
22
+ }
23
+ if (xmlMode) {
24
+ yield* Console.log(toXml(results));
25
+ return;
26
+ }
27
+ if (results.length === 0) {
28
+ yield* Console.log("No pages");
29
+ return;
30
+ }
31
+ const lines = results.map((p) => {
32
+ const updated = (p.updated_at ?? p.created_at).slice(0, 10);
33
+ return `${p.id} ${updated} ${p.name}`;
34
+ });
35
+ yield* Console.log(lines.join("\n"));
36
+ });
37
+ }
38
+
14
39
  export const pagesList = Command.make(
15
40
  "list",
16
41
  { project: projectArg },
17
- ({ project }) =>
18
- Effect.gen(function* () {
19
- const { id } = yield* resolveProject(project);
20
- const raw = yield* api.get(`projects/${id}/pages/`);
21
- const { results } = yield* decodeOrFail(PagesResponseSchema, raw);
22
- if (jsonMode) {
23
- yield* Console.log(JSON.stringify(results, null, 2));
24
- return;
25
- }
26
- if (xmlMode) {
27
- yield* Console.log(toXml(results));
28
- return;
29
- }
30
- if (results.length === 0) {
31
- yield* Console.log("No pages");
32
- return;
33
- }
34
- const lines = results.map((p) => {
35
- const updated = (p.updated_at ?? p.created_at).slice(0, 10);
36
- return `${p.id} ${updated} ${p.name}`;
37
- });
38
- yield* Console.log(lines.join("\n"));
39
- }),
42
+ pagesListHandler,
40
43
  ).pipe(
41
44
  Command.withDescription(
42
45
  "List pages for a project. Shows page UUID, last updated date, and title.\n\nExample:\n plane pages list PROJ",
@@ -49,16 +52,25 @@ const pageIdArg = Args.text({ name: "page-id" }).pipe(
49
52
  Args.withDescription("Page UUID (from 'plane pages list')"),
50
53
  );
51
54
 
55
+ export function pagesGetHandler({
56
+ project,
57
+ pageId,
58
+ }: {
59
+ project: string;
60
+ pageId: string;
61
+ }) {
62
+ return Effect.gen(function* () {
63
+ const { id } = yield* resolveProject(project);
64
+ const raw = yield* api.get(`projects/${id}/pages/${pageId}/`);
65
+ const page = yield* decodeOrFail(PageSchema, raw);
66
+ yield* Console.log(JSON.stringify(page, null, 2));
67
+ });
68
+ }
69
+
52
70
  export const pagesGet = Command.make(
53
71
  "get",
54
72
  { project: projectArg, pageId: pageIdArg },
55
- ({ project, pageId }) =>
56
- Effect.gen(function* () {
57
- const { id } = yield* resolveProject(project);
58
- const raw = yield* api.get(`projects/${id}/pages/${pageId}/`);
59
- const page = yield* decodeOrFail(PageSchema, raw);
60
- yield* Console.log(JSON.stringify(page, null, 2));
61
- }),
73
+ pagesGetHandler,
62
74
  ).pipe(
63
75
  Command.withDescription(
64
76
  "Print full JSON for a page including description_html.\n\nExample:\n plane pages get PROJ <page-id>",
package/src/config.ts CHANGED
@@ -18,7 +18,7 @@ export const IssueSchema = Schema.Struct({
18
18
  state: Schema.Union(Schema.String, StateSchema),
19
19
  assignees: Schema.optional(Schema.NullOr(Schema.Array(Schema.String))),
20
20
  description_html: Schema.optional(Schema.NullOr(Schema.String)),
21
- estimate_point: Schema.optional(Schema.NullOr(Schema.Number)),
21
+ estimate_point: Schema.optional(Schema.NullOr(Schema.String)),
22
22
  });
23
23
  export type Issue = typeof IssueSchema.Type;
24
24
 
package/src/output.ts CHANGED
@@ -15,25 +15,28 @@ function escapeXml(val: unknown): string {
15
15
  .replace(/"/g, "&quot;");
16
16
  }
17
17
 
18
- function toXmlItem(obj: unknown, tag = "item"): string {
19
- if (obj === null || typeof obj !== "object") {
20
- return `<${tag}>${escapeXml(obj)}</${tag}>`;
21
- }
22
- const attrs = Object.entries(obj as Record<string, unknown>)
18
+ function toXmlItem(obj: Record<string, unknown>, tag = "item"): string {
19
+ const attrs = Object.entries(obj)
23
20
  .filter(([, v]) => v === null || typeof v !== "object")
24
21
  .map(([k, v]) => `${k}="${escapeXml(v)}"`)
25
22
  .join(" ");
26
- const children = Object.entries(obj as Record<string, unknown>)
23
+ const children = Object.entries(obj)
27
24
  .filter(([, v]) => v !== null && typeof v === "object")
28
25
  .map(([k, v]) =>
29
26
  Array.isArray(v)
30
- ? `<${k}>${v.map((i) => toXmlItem(i)).join("")}</${k}>`
31
- : toXmlItem(v, k),
27
+ ? `<${k}>${v
28
+ .map((i) =>
29
+ typeof i === "object" && i !== null
30
+ ? toXmlItem(i as Record<string, unknown>)
31
+ : `<item>${escapeXml(i)}</item>`,
32
+ )
33
+ .join("")}</${k}>`
34
+ : toXmlItem(v as Record<string, unknown>, k),
32
35
  )
33
36
  .join("");
34
37
  return `<${tag}${attrs ? " " + attrs : ""}>${children}</${tag}>`;
35
38
  }
36
39
 
37
40
  export function toXml(results: readonly unknown[]): string {
38
- return `<results>\n${results.map((r) => " " + toXmlItem(r)).join("\n")}\n</results>`;
41
+ return `<results>\n${results.map((r) => " " + toXmlItem(r as Record<string, unknown>)).join("\n")}\n</results>`;
39
42
  }
package/src/resolve.ts CHANGED
@@ -7,12 +7,13 @@ import {
7
7
  ProjectsResponseSchema,
8
8
  StatesResponseSchema,
9
9
  } from "./config.js";
10
+ import type { Issue } from "./config.js";
10
11
 
11
12
  // Cache project list within a process invocation
12
13
  let _projectCache: Record<string, string> | null = null;
13
14
 
14
15
  /** Clear the project cache — for use in tests only */
15
- export function _clearProjectCache() {
16
+ export function _clearProjectCache(): void {
16
17
  _projectCache = null;
17
18
  }
18
19
 
@@ -66,7 +67,10 @@ export function parseIssueRef(
66
67
  );
67
68
  }
68
69
 
69
- export function findIssueBySeq(projectId: string, seq: number) {
70
+ export function findIssueBySeq(
71
+ projectId: string,
72
+ seq: number,
73
+ ): Effect.Effect<Issue, Error> {
70
74
  return Effect.gen(function* () {
71
75
  const raw = yield* api.get(`projects/${projectId}/issues/`);
72
76
  const { results } = yield* decodeOrFail(IssuesResponseSchema, raw);
@@ -76,7 +80,9 @@ export function findIssueBySeq(projectId: string, seq: number) {
76
80
  });
77
81
  }
78
82
 
79
- export function getMemberId(nameEmailOrId: string) {
83
+ export function getMemberId(
84
+ nameEmailOrId: string,
85
+ ): Effect.Effect<string, Error> {
80
86
  return Effect.gen(function* () {
81
87
  const results = yield* decodeOrFail(
82
88
  MembersResponseSchema,
@@ -97,7 +103,10 @@ export function getMemberId(nameEmailOrId: string) {
97
103
  });
98
104
  }
99
105
 
100
- export function getStateId(projectId: string, nameOrGroup: string) {
106
+ export function getStateId(
107
+ projectId: string,
108
+ nameOrGroup: string,
109
+ ): Effect.Effect<string, Error> {
101
110
  return Effect.gen(function* () {
102
111
  const raw = yield* api.get(`projects/${projectId}/states/`);
103
112
  const { results } = yield* decodeOrFail(StatesResponseSchema, raw);
@@ -111,7 +120,10 @@ export function getStateId(projectId: string, nameOrGroup: string) {
111
120
  });
112
121
  }
113
122
 
114
- export function getLabelId(projectId: string, name: string) {
123
+ export function getLabelId(
124
+ projectId: string,
125
+ name: string,
126
+ ): Effect.Effect<string, Error> {
115
127
  return Effect.gen(function* () {
116
128
  const raw = yield* api.get(`projects/${projectId}/labels/`);
117
129
  const { results } = yield* decodeOrFail(LabelsResponseSchema, raw);
package/tests/api.test.ts CHANGED
@@ -43,8 +43,10 @@ describe("api.get", () => {
43
43
  }),
44
44
  ),
45
45
  );
46
- const result = await Effect.runPromise(api.get("projects/"));
47
- expect((result as any).results).toHaveLength(1);
46
+ const result = (await Effect.runPromise(api.get("projects/"))) as {
47
+ results: unknown[];
48
+ };
49
+ expect(result.results).toHaveLength(1);
48
50
  });
49
51
 
50
52
  it("strips trailing slash from PLANE_HOST", async () => {
@@ -54,8 +56,10 @@ describe("api.get", () => {
54
56
  HttpResponse.json({ results: [] }),
55
57
  ),
56
58
  );
57
- const result = await Effect.runPromise(api.get("projects/"));
58
- expect((result as any).results).toHaveLength(0);
59
+ const result = (await Effect.runPromise(api.get("projects/"))) as {
60
+ results: unknown[];
61
+ };
62
+ expect(result.results).toHaveLength(0);
59
63
  });
60
64
 
61
65
  it("appends expand=state for issues/ paths", async () => {
@@ -103,7 +107,7 @@ describe("api.post", () => {
103
107
  http.post(
104
108
  `${BASE}/api/v1/workspaces/${WS}/projects/p1/issues/`,
105
109
  async ({ request }) => {
106
- const body = (await request.json()) as any;
110
+ const body = (await request.json()) as { name?: string };
107
111
  return HttpResponse.json({
108
112
  id: "new-issue",
109
113
  sequence_id: 99,
@@ -116,7 +120,7 @@ describe("api.post", () => {
116
120
  );
117
121
  const result = (await Effect.runPromise(
118
122
  api.post("projects/p1/issues/", { name: "New Issue" }),
119
- )) as any;
123
+ )) as { sequence_id: number; name: string };
120
124
  expect(result.sequence_id).toBe(99);
121
125
  expect(result.name).toBe("New Issue");
122
126
  });
@@ -128,7 +132,7 @@ describe("api.patch", () => {
128
132
  http.patch(
129
133
  `${BASE}/api/v1/workspaces/${WS}/projects/p1/issues/i1/`,
130
134
  async ({ request }) => {
131
- const body = (await request.json()) as any;
135
+ const body = (await request.json()) as { priority?: string };
132
136
  return HttpResponse.json({
133
137
  id: "i1",
134
138
  sequence_id: 1,
@@ -141,7 +145,7 @@ describe("api.patch", () => {
141
145
  );
142
146
  const result = (await Effect.runPromise(
143
147
  api.patch("projects/p1/issues/i1/", { priority: "high" }),
144
- )) as any;
148
+ )) as { priority: string };
145
149
  expect(result.priority).toBe("high");
146
150
  });
147
151
  });