@aaronshaf/plane 0.1.2 → 0.1.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/package.json +1 -1
- package/scripts/check-coverage.ts +2 -2
- package/src/api.ts +67 -59
- package/src/app.ts +78 -0
- package/src/bin.ts +6 -70
- package/src/commands/cycles.ts +104 -80
- package/src/commands/init.ts +57 -55
- package/src/commands/intake.ts +82 -62
- package/src/commands/issue.ts +418 -305
- package/src/commands/issues.ts +51 -40
- package/src/commands/labels.ts +52 -40
- package/src/commands/members.ts +25 -16
- package/src/commands/modules.ts +136 -94
- package/src/commands/pages.ts +58 -46
- package/src/commands/projects.ts +28 -19
- package/src/commands/states.ts +31 -22
- package/src/config.ts +152 -154
- package/src/format.ts +15 -8
- package/src/output.ts +39 -0
- package/src/resolve.ts +66 -53
- package/tests/api.test.ts +178 -155
- package/tests/cycles-extended.test.ts +205 -162
- package/tests/format.test.ts +72 -54
- package/tests/helpers/mock-api.ts +16 -14
- package/tests/intake.test.ts +173 -139
- package/tests/issue-activity.test.ts +191 -158
- package/tests/issue-commands.test.ts +587 -304
- package/tests/issue-comments-worklogs.test.ts +337 -265
- package/tests/issue-links.test.ts +229 -193
- package/tests/modules.test.ts +283 -239
- package/tests/new-schemas.test.ts +203 -183
- package/tests/new-schemas2.test.ts +195 -183
- package/tests/output.test.ts +80 -0
- package/tests/pages.test.ts +122 -108
- package/tests/resolve.test.ts +186 -156
- package/tests/schemas.test.ts +215 -177
package/tests/modules.test.ts
CHANGED
|
@@ -1,255 +1,299 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import {
|
|
2
|
+
afterAll,
|
|
3
|
+
afterEach,
|
|
4
|
+
beforeAll,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
} from "bun:test";
|
|
10
|
+
import { Effect } from "effect";
|
|
11
|
+
import { http, HttpResponse } from "msw";
|
|
12
|
+
import { setupServer } from "msw/node";
|
|
13
|
+
import { _clearProjectCache } from "@/resolve";
|
|
14
|
+
|
|
15
|
+
const BASE = "http://modules-test.local";
|
|
16
|
+
const WS = "testws";
|
|
17
|
+
|
|
18
|
+
const PROJECTS = [
|
|
19
|
+
{ id: "proj-acme", identifier: "ACME", name: "Acme Project" },
|
|
20
|
+
];
|
|
21
|
+
const ISSUES = [
|
|
22
|
+
{
|
|
23
|
+
id: "i1",
|
|
24
|
+
sequence_id: 29,
|
|
25
|
+
name: "Migrate Button",
|
|
26
|
+
priority: "high",
|
|
27
|
+
state: "s1",
|
|
28
|
+
},
|
|
29
|
+
];
|
|
12
30
|
const MODULES = [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
]
|
|
31
|
+
{ id: "mod1", name: "Sprint 1", status: "in_progress" },
|
|
32
|
+
{ id: "mod2", name: "Sprint 2", status: "backlog" },
|
|
33
|
+
];
|
|
16
34
|
const MODULE_ISSUES = [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
]
|
|
35
|
+
{
|
|
36
|
+
id: "mi1",
|
|
37
|
+
issue: "i1",
|
|
38
|
+
issue_detail: { id: "i1", sequence_id: 29, name: "Migrate Button" },
|
|
39
|
+
},
|
|
40
|
+
];
|
|
23
41
|
|
|
24
42
|
const server = setupServer(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/`, () =>
|
|
44
|
+
HttpResponse.json({ results: PROJECTS }),
|
|
45
|
+
),
|
|
46
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/`, () =>
|
|
47
|
+
HttpResponse.json({ results: ISSUES }),
|
|
48
|
+
),
|
|
49
|
+
http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/`, () =>
|
|
50
|
+
HttpResponse.json({ results: MODULES }),
|
|
51
|
+
),
|
|
52
|
+
http.get(
|
|
53
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/mod1/module-issues/`,
|
|
54
|
+
() => HttpResponse.json({ results: MODULE_ISSUES }),
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
|
59
|
+
afterAll(() => server.close());
|
|
41
60
|
|
|
42
61
|
beforeEach(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
62
|
+
_clearProjectCache();
|
|
63
|
+
process.env["PLANE_HOST"] = BASE;
|
|
64
|
+
process.env["PLANE_WORKSPACE"] = WS;
|
|
65
|
+
process.env["PLANE_API_TOKEN"] = "test-token";
|
|
66
|
+
});
|
|
48
67
|
|
|
49
68
|
afterEach(() => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
69
|
+
server.resetHandlers();
|
|
70
|
+
delete process.env["PLANE_HOST"];
|
|
71
|
+
delete process.env["PLANE_WORKSPACE"];
|
|
72
|
+
delete process.env["PLANE_API_TOKEN"];
|
|
73
|
+
});
|
|
55
74
|
|
|
56
75
|
describe("modulesList", () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
76
|
+
it("lists modules for a project", async () => {
|
|
77
|
+
const { modulesList } = await import("@/commands/modules");
|
|
78
|
+
const logs: string[] = [];
|
|
79
|
+
const orig = console.log;
|
|
80
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await Effect.runPromise(
|
|
84
|
+
(modulesList as any).handler({ project: "ACME" }),
|
|
85
|
+
);
|
|
86
|
+
} finally {
|
|
87
|
+
console.log = orig;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const output = logs.join("\n");
|
|
91
|
+
expect(output).toContain("mod1");
|
|
92
|
+
expect(output).toContain("Sprint 1");
|
|
93
|
+
expect(output).toContain("in_progress");
|
|
94
|
+
expect(output).toContain("mod2");
|
|
95
|
+
expect(output).toContain("Sprint 2");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("shows 'No modules found' when empty", async () => {
|
|
99
|
+
server.use(
|
|
100
|
+
http.get(
|
|
101
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/`,
|
|
102
|
+
() => HttpResponse.json({ results: [] }),
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const { modulesList } = await import("@/commands/modules");
|
|
107
|
+
const logs: string[] = [];
|
|
108
|
+
const orig = console.log;
|
|
109
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await Effect.runPromise(
|
|
113
|
+
(modulesList as any).handler({ project: "ACME" }),
|
|
114
|
+
);
|
|
115
|
+
} finally {
|
|
116
|
+
console.log = orig;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
expect(logs.join("\n")).toBe("No modules found");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("shows '?' for missing status", async () => {
|
|
123
|
+
server.use(
|
|
124
|
+
http.get(
|
|
125
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/`,
|
|
126
|
+
() =>
|
|
127
|
+
HttpResponse.json({
|
|
128
|
+
results: [{ id: "mod3", name: "Unstarted Sprint" }],
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const { modulesList } = await import("@/commands/modules");
|
|
134
|
+
const logs: string[] = [];
|
|
135
|
+
const orig = console.log;
|
|
136
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await Effect.runPromise(
|
|
140
|
+
(modulesList as any).handler({ project: "ACME" }),
|
|
141
|
+
);
|
|
142
|
+
} finally {
|
|
143
|
+
console.log = orig;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
expect(logs.join("\n")).toContain("?");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
119
149
|
|
|
120
150
|
describe("moduleIssuesList", () => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
151
|
+
it("lists issues in a module with detail", async () => {
|
|
152
|
+
const { moduleIssuesList } = await import("@/commands/modules");
|
|
153
|
+
const logs: string[] = [];
|
|
154
|
+
const orig = console.log;
|
|
155
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await Effect.runPromise(
|
|
159
|
+
(moduleIssuesList as any).handler({
|
|
160
|
+
project: "ACME",
|
|
161
|
+
moduleId: "mod1",
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
} finally {
|
|
165
|
+
console.log = orig;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const output = logs.join("\n");
|
|
169
|
+
expect(output).toContain("ACME-");
|
|
170
|
+
expect(output).toContain("29");
|
|
171
|
+
expect(output).toContain("Migrate Button");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("falls back to issue UUID when no issue_detail", async () => {
|
|
175
|
+
server.use(
|
|
176
|
+
http.get(
|
|
177
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/mod1/module-issues/`,
|
|
178
|
+
() =>
|
|
179
|
+
HttpResponse.json({ results: [{ id: "mi2", issue: "bare-uuid" }] }),
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const { moduleIssuesList } = await import("@/commands/modules");
|
|
184
|
+
const logs: string[] = [];
|
|
185
|
+
const orig = console.log;
|
|
186
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
await Effect.runPromise(
|
|
190
|
+
(moduleIssuesList as any).handler({
|
|
191
|
+
project: "ACME",
|
|
192
|
+
moduleId: "mod1",
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
} finally {
|
|
196
|
+
console.log = orig;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
expect(logs.join("\n")).toContain("bare-uuid");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("shows 'No issues in module' when empty", async () => {
|
|
203
|
+
server.use(
|
|
204
|
+
http.get(
|
|
205
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/mod1/module-issues/`,
|
|
206
|
+
() => HttpResponse.json({ results: [] }),
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const { moduleIssuesList } = await import("@/commands/modules");
|
|
211
|
+
const logs: string[] = [];
|
|
212
|
+
const orig = console.log;
|
|
213
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
await Effect.runPromise(
|
|
217
|
+
(moduleIssuesList as any).handler({
|
|
218
|
+
project: "ACME",
|
|
219
|
+
moduleId: "mod1",
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
} finally {
|
|
223
|
+
console.log = orig;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
expect(logs.join("\n")).toBe("No issues in module");
|
|
227
|
+
});
|
|
228
|
+
});
|
|
189
229
|
|
|
190
230
|
describe("moduleIssuesAdd", () => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
it("adds an issue to a module", async () => {
|
|
232
|
+
let postedBody: unknown;
|
|
233
|
+
server.use(
|
|
234
|
+
http.post(
|
|
235
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/mod1/module-issues/`,
|
|
236
|
+
async ({ request }) => {
|
|
237
|
+
postedBody = await request.json();
|
|
238
|
+
return HttpResponse.json({ issues: ["i1"] }, { status: 201 });
|
|
239
|
+
},
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const { moduleIssuesAdd } = await import("@/commands/modules");
|
|
244
|
+
const logs: string[] = [];
|
|
245
|
+
const orig = console.log;
|
|
246
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
await Effect.runPromise(
|
|
250
|
+
(moduleIssuesAdd as any).handler({
|
|
251
|
+
project: "ACME",
|
|
252
|
+
moduleId: "mod1",
|
|
253
|
+
ref: "ACME-29",
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
} finally {
|
|
257
|
+
console.log = orig;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
expect((postedBody as any).issues).toContain("i1");
|
|
261
|
+
expect(logs.join("\n")).toContain("ACME-29");
|
|
262
|
+
expect(logs.join("\n")).toContain("mod1");
|
|
263
|
+
});
|
|
264
|
+
});
|
|
221
265
|
|
|
222
266
|
describe("moduleIssuesRemove", () => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
})
|
|
267
|
+
it("removes a module-issue", async () => {
|
|
268
|
+
let deleted = false;
|
|
269
|
+
server.use(
|
|
270
|
+
http.delete(
|
|
271
|
+
`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/modules/mod1/module-issues/mi1/`,
|
|
272
|
+
() => {
|
|
273
|
+
deleted = true;
|
|
274
|
+
return new HttpResponse(null, { status: 204 });
|
|
275
|
+
},
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const { moduleIssuesRemove } = await import("@/commands/modules");
|
|
280
|
+
const logs: string[] = [];
|
|
281
|
+
const orig = console.log;
|
|
282
|
+
console.log = (...args: unknown[]) => logs.push(args.join(" "));
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
await Effect.runPromise(
|
|
286
|
+
(moduleIssuesRemove as any).handler({
|
|
287
|
+
project: "ACME",
|
|
288
|
+
moduleId: "mod1",
|
|
289
|
+
moduleIssueId: "mi1",
|
|
290
|
+
}),
|
|
291
|
+
);
|
|
292
|
+
} finally {
|
|
293
|
+
console.log = orig;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
expect(deleted).toBe(true);
|
|
297
|
+
expect(logs.join("\n")).toContain("mi1");
|
|
298
|
+
});
|
|
299
|
+
});
|