@aaronshaf/plane 0.1.8 → 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.
- package/package.json +1 -1
- package/src/commands/cycles.ts +86 -63
- package/src/commands/intake.ts +64 -44
- package/src/commands/issue.ts +386 -310
- package/src/commands/issues.ts +58 -45
- package/src/commands/labels.ts +6 -2
- package/src/commands/modules.ts +108 -74
- package/src/commands/pages.ts +42 -30
- package/src/config.ts +0 -16
- package/src/output.ts +12 -9
- package/src/resolve.ts +17 -29
- package/tests/api.test.ts +12 -8
- package/tests/cycles-extended.test.ts +15 -15
- package/tests/intake.test.ts +12 -12
- package/tests/issue-activity.test.ts +8 -16
- package/tests/issue-commands.test.ts +212 -295
- package/tests/issue-comments-worklogs.test.ts +31 -38
- package/tests/issue-links.test.ts +20 -23
- package/tests/modules.test.ts +17 -23
- package/tests/output.test.ts +11 -0
- package/tests/pages.test.ts +8 -8
package/src/commands/issue.ts
CHANGED
|
@@ -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 {
|
|
5
5
|
IssueSchema,
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from "../config.js";
|
|
13
13
|
import {
|
|
14
14
|
findIssueBySeq,
|
|
15
|
-
getEstimatePointId,
|
|
16
15
|
getLabelId,
|
|
17
16
|
getMemberId,
|
|
18
17
|
getStateId,
|
|
@@ -25,23 +24,48 @@ import { escapeHtmlText } from "../format.js";
|
|
|
25
24
|
const refArg = Args.text({ name: "ref" }).pipe(
|
|
26
25
|
Args.withDescription("Issue reference, e.g. PROJ-29"),
|
|
27
26
|
);
|
|
28
|
-
|
|
27
|
+
// --- Typed payload interfaces ---
|
|
28
|
+
interface IssueUpdatePayload {
|
|
29
|
+
state?: string;
|
|
30
|
+
priority?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
description_html?: string;
|
|
33
|
+
assignees?: string[];
|
|
34
|
+
label_ids?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface IssueCreatePayload {
|
|
38
|
+
name: string;
|
|
39
|
+
priority?: string;
|
|
40
|
+
state?: string;
|
|
41
|
+
description_html?: string;
|
|
42
|
+
assignees?: string[];
|
|
43
|
+
label_ids?: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface WorklogPayload {
|
|
47
|
+
duration: number;
|
|
48
|
+
description?: string;
|
|
49
|
+
}
|
|
29
50
|
// --- issue get ---
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Effect.gen(function* () {
|
|
51
|
+
export function issueGetHandler({ ref }: { ref: string }) {
|
|
52
|
+
return Effect.gen(function* () {
|
|
33
53
|
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
34
54
|
const issue = yield* findIssueBySeq(projectId, seq);
|
|
35
55
|
yield* Console.log(JSON.stringify(issue, null, 2));
|
|
36
|
-
})
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const issueGet = Command.make(
|
|
60
|
+
"get",
|
|
61
|
+
{ ref: refArg },
|
|
62
|
+
issueGetHandler,
|
|
37
63
|
).pipe(
|
|
38
64
|
Command.withDescription(
|
|
39
65
|
"Print full JSON for an issue. Useful for inspecting all fields (state, priority, assignees, labels, etc.).",
|
|
40
66
|
),
|
|
41
67
|
);
|
|
42
|
-
|
|
43
68
|
// --- issue update ---
|
|
44
|
-
|
|
45
69
|
const stateOption = Options.optional(Options.text("state")).pipe(
|
|
46
70
|
Options.withDescription("State group or name (e.g. backlog, completed)"),
|
|
47
71
|
);
|
|
@@ -66,17 +90,81 @@ const labelOption = Options.optional(Options.text("label")).pipe(
|
|
|
66
90
|
Options.withDescription("Set issue label by name"),
|
|
67
91
|
);
|
|
68
92
|
|
|
69
|
-
const estimateOption = Options.optional(Options.text("estimate")).pipe(
|
|
70
|
-
Options.withDescription(
|
|
71
|
-
"Estimate point value (e.g. '3', 'Medium', 'L') — resolved to UUID from the project's estimate scheme",
|
|
72
|
-
),
|
|
73
|
-
);
|
|
74
|
-
|
|
75
93
|
const noAssigneeOption = Options.boolean("no-assignee").pipe(
|
|
76
94
|
Options.withDescription("Clear all assignees"),
|
|
77
95
|
Options.withDefault(false),
|
|
78
96
|
);
|
|
79
97
|
|
|
98
|
+
export function issueUpdateHandler({
|
|
99
|
+
ref,
|
|
100
|
+
state,
|
|
101
|
+
priority,
|
|
102
|
+
title,
|
|
103
|
+
description,
|
|
104
|
+
assignee,
|
|
105
|
+
label,
|
|
106
|
+
noAssignee,
|
|
107
|
+
}: {
|
|
108
|
+
ref: string;
|
|
109
|
+
state: Option.Option<string>;
|
|
110
|
+
priority: Option.Option<string>;
|
|
111
|
+
title: Option.Option<string>;
|
|
112
|
+
description: Option.Option<string>;
|
|
113
|
+
assignee: Option.Option<string>;
|
|
114
|
+
label: Option.Option<string>;
|
|
115
|
+
noAssignee: boolean;
|
|
116
|
+
}) {
|
|
117
|
+
return Effect.gen(function* () {
|
|
118
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
119
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
120
|
+
|
|
121
|
+
const body: IssueUpdatePayload = {};
|
|
122
|
+
|
|
123
|
+
if (state._tag === "Some") {
|
|
124
|
+
body.state = yield* getStateId(projectId, state.value);
|
|
125
|
+
}
|
|
126
|
+
if (priority._tag === "Some") {
|
|
127
|
+
body.priority = priority.value;
|
|
128
|
+
}
|
|
129
|
+
if (title._tag === "Some") {
|
|
130
|
+
body.name = title.value;
|
|
131
|
+
}
|
|
132
|
+
if (description._tag === "Some") {
|
|
133
|
+
const escaped = escapeHtmlText(description.value);
|
|
134
|
+
body.description_html = `<p>${escaped}</p>`;
|
|
135
|
+
}
|
|
136
|
+
if (noAssignee) {
|
|
137
|
+
body.assignees = [];
|
|
138
|
+
} else if (assignee._tag === "Some") {
|
|
139
|
+
const memberId = yield* getMemberId(assignee.value);
|
|
140
|
+
body.assignees = [memberId];
|
|
141
|
+
}
|
|
142
|
+
if (label._tag === "Some") {
|
|
143
|
+
const labelId = yield* getLabelId(projectId, label.value);
|
|
144
|
+
body.label_ids = [labelId];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (Object.keys(body).length === 0) {
|
|
148
|
+
yield* Effect.fail(
|
|
149
|
+
new Error(
|
|
150
|
+
"Nothing to update. Specify --state, --priority, --title, --description, --assignee, --label, or --no-assignee",
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const raw = yield* api.patch(
|
|
156
|
+
`projects/${projectId}/issues/${issue.id}/`,
|
|
157
|
+
body,
|
|
158
|
+
);
|
|
159
|
+
const updated = yield* decodeOrFail(IssueSchema, raw);
|
|
160
|
+
const stateName =
|
|
161
|
+
typeof updated.state === "object" ? updated.state.name : updated.state;
|
|
162
|
+
yield* Console.log(
|
|
163
|
+
`Updated ${ref}: state=${stateName} priority=${updated.priority}`,
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
80
168
|
export const issueUpdate = Command.make(
|
|
81
169
|
"update",
|
|
82
170
|
{
|
|
@@ -86,107 +174,48 @@ export const issueUpdate = Command.make(
|
|
|
86
174
|
description: descriptionOption,
|
|
87
175
|
assignee: assigneeOption,
|
|
88
176
|
label: labelOption,
|
|
89
|
-
estimate: estimateOption,
|
|
90
177
|
noAssignee: noAssigneeOption,
|
|
91
178
|
ref: refArg,
|
|
92
179
|
},
|
|
93
|
-
|
|
94
|
-
ref,
|
|
95
|
-
state,
|
|
96
|
-
priority,
|
|
97
|
-
title,
|
|
98
|
-
description,
|
|
99
|
-
assignee,
|
|
100
|
-
label,
|
|
101
|
-
estimate,
|
|
102
|
-
noAssignee,
|
|
103
|
-
}) =>
|
|
104
|
-
Effect.gen(function* () {
|
|
105
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
106
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
107
|
-
|
|
108
|
-
const body: Record<string, unknown> = {};
|
|
109
|
-
|
|
110
|
-
if (state._tag === "Some") {
|
|
111
|
-
body["state"] = yield* getStateId(projectId, state.value);
|
|
112
|
-
}
|
|
113
|
-
if (priority._tag === "Some") {
|
|
114
|
-
body["priority"] = priority.value;
|
|
115
|
-
}
|
|
116
|
-
if (title._tag === "Some") {
|
|
117
|
-
body["name"] = title.value;
|
|
118
|
-
}
|
|
119
|
-
if (description._tag === "Some") {
|
|
120
|
-
const escaped = escapeHtmlText(description.value);
|
|
121
|
-
body["description_html"] = `<p>${escaped}</p>`;
|
|
122
|
-
}
|
|
123
|
-
if (noAssignee) {
|
|
124
|
-
body["assignees"] = [];
|
|
125
|
-
} else if (assignee._tag === "Some") {
|
|
126
|
-
const memberId = yield* getMemberId(assignee.value);
|
|
127
|
-
body["assignees"] = [memberId];
|
|
128
|
-
}
|
|
129
|
-
if (label._tag === "Some") {
|
|
130
|
-
const labelId = yield* getLabelId(projectId, label.value);
|
|
131
|
-
body["label_ids"] = [labelId];
|
|
132
|
-
}
|
|
133
|
-
if (estimate._tag === "Some") {
|
|
134
|
-
body["estimate_point"] = yield* getEstimatePointId(
|
|
135
|
-
projectId,
|
|
136
|
-
estimate.value,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (Object.keys(body).length === 0) {
|
|
141
|
-
yield* Effect.fail(
|
|
142
|
-
new Error(
|
|
143
|
-
"Nothing to update. Specify --state, --priority, --title, --description, --assignee, --label, --estimate, or --no-assignee",
|
|
144
|
-
),
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const raw = yield* api.patch(
|
|
149
|
-
`projects/${projectId}/issues/${issue.id}/`,
|
|
150
|
-
body,
|
|
151
|
-
);
|
|
152
|
-
const updated = yield* decodeOrFail(IssueSchema, raw);
|
|
153
|
-
yield* Console.log(
|
|
154
|
-
`Updated ${ref}: state=${String(updated.state)} priority=${updated.priority}`,
|
|
155
|
-
);
|
|
156
|
-
}),
|
|
180
|
+
issueUpdateHandler,
|
|
157
181
|
).pipe(
|
|
158
182
|
Command.withDescription(
|
|
159
183
|
'Update an issue\'s state, priority, title, description, or assignee. Options must come before the REF argument.\n\nExamples:\n plane issue update --state completed PROJ-29\n plane issue update --priority high WEB-5\n plane issue update --title "New issue title" PROJ-29\n plane issue update --assignee "Jane Doe" PROJ-29\n plane issue update --no-assignee PROJ-29\n plane issue update --description "New description" PROJ-29',
|
|
160
184
|
),
|
|
161
185
|
);
|
|
162
|
-
|
|
163
186
|
// --- issue comment ---
|
|
164
|
-
|
|
165
187
|
const textArg = Args.text({ name: "text" }).pipe(
|
|
166
188
|
Args.withDescription("Comment text to add"),
|
|
167
189
|
);
|
|
168
190
|
|
|
191
|
+
export function issueCommentHandler({
|
|
192
|
+
ref,
|
|
193
|
+
text,
|
|
194
|
+
}: {
|
|
195
|
+
ref: string;
|
|
196
|
+
text: string;
|
|
197
|
+
}) {
|
|
198
|
+
return Effect.gen(function* () {
|
|
199
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
200
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
201
|
+
const escaped = escapeHtmlText(text);
|
|
202
|
+
yield* api.post(`projects/${projectId}/issues/${issue.id}/comments/`, {
|
|
203
|
+
comment_html: `<p>${escaped}</p>`,
|
|
204
|
+
});
|
|
205
|
+
yield* Console.log(`Comment added to ${ref}`);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
169
209
|
export const issueComment = Command.make(
|
|
170
210
|
"comment",
|
|
171
211
|
{ ref: refArg, text: textArg },
|
|
172
|
-
|
|
173
|
-
Effect.gen(function* () {
|
|
174
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
175
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
176
|
-
const escaped = escapeHtmlText(text);
|
|
177
|
-
yield* api.post(`projects/${projectId}/issues/${issue.id}/comments/`, {
|
|
178
|
-
comment_html: `<p>${escaped}</p>`,
|
|
179
|
-
});
|
|
180
|
-
yield* Console.log(`Comment added to ${ref}`);
|
|
181
|
-
}),
|
|
212
|
+
issueCommentHandler,
|
|
182
213
|
).pipe(
|
|
183
214
|
Command.withDescription(
|
|
184
215
|
'Add a comment to an issue. The text is wrapped in <p> tags and HTML-escaped.\n\nExample:\n plane issue comment PROJ-29 "Fixed in latest build"',
|
|
185
216
|
),
|
|
186
217
|
);
|
|
187
|
-
|
|
188
218
|
// --- issue create ---
|
|
189
|
-
|
|
190
219
|
const titleArg = Args.text({ name: "title" }).pipe(
|
|
191
220
|
Args.withDescription("Issue title"),
|
|
192
221
|
);
|
|
@@ -216,6 +245,49 @@ const createLabelOption = Options.optional(Options.text("label")).pipe(
|
|
|
216
245
|
Options.withDescription("Set issue label by name"),
|
|
217
246
|
);
|
|
218
247
|
|
|
248
|
+
export function issueCreateHandler({
|
|
249
|
+
project,
|
|
250
|
+
title,
|
|
251
|
+
priority,
|
|
252
|
+
state,
|
|
253
|
+
description,
|
|
254
|
+
assignee,
|
|
255
|
+
label,
|
|
256
|
+
}: {
|
|
257
|
+
project: string;
|
|
258
|
+
title: string;
|
|
259
|
+
priority: Option.Option<string>;
|
|
260
|
+
state: Option.Option<string>;
|
|
261
|
+
description: Option.Option<string>;
|
|
262
|
+
assignee: Option.Option<string>;
|
|
263
|
+
label: Option.Option<string>;
|
|
264
|
+
}) {
|
|
265
|
+
return Effect.gen(function* () {
|
|
266
|
+
const { key, id: projectId } = yield* resolveProject(project);
|
|
267
|
+
const body: IssueCreatePayload = { name: title };
|
|
268
|
+
if (priority._tag === "Some") body.priority = priority.value;
|
|
269
|
+
if (state._tag === "Some")
|
|
270
|
+
body.state = yield* getStateId(projectId, state.value);
|
|
271
|
+
if (description._tag === "Some") {
|
|
272
|
+
const escaped = escapeHtmlText(description.value);
|
|
273
|
+
body.description_html = `<p>${escaped}</p>`;
|
|
274
|
+
}
|
|
275
|
+
if (assignee._tag === "Some") {
|
|
276
|
+
const memberId = yield* getMemberId(assignee.value);
|
|
277
|
+
body.assignees = [memberId];
|
|
278
|
+
}
|
|
279
|
+
if (label._tag === "Some") {
|
|
280
|
+
const labelId = yield* getLabelId(projectId, label.value);
|
|
281
|
+
body.label_ids = [labelId];
|
|
282
|
+
}
|
|
283
|
+
const raw = yield* api.post(`projects/${projectId}/issues/`, body);
|
|
284
|
+
const created = yield* decodeOrFail(IssueSchema, raw);
|
|
285
|
+
yield* Console.log(
|
|
286
|
+
`Created ${key}-${created.sequence_id}: ${created.name}`,
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
219
291
|
export const issueCreate = Command.make(
|
|
220
292
|
"create",
|
|
221
293
|
{
|
|
@@ -224,103 +296,62 @@ export const issueCreate = Command.make(
|
|
|
224
296
|
description: createDescriptionOption,
|
|
225
297
|
assignee: createAssigneeOption,
|
|
226
298
|
label: createLabelOption,
|
|
227
|
-
estimate: estimateOption,
|
|
228
299
|
project: projectRefArg,
|
|
229
300
|
title: titleArg,
|
|
230
301
|
},
|
|
231
|
-
|
|
232
|
-
project,
|
|
233
|
-
title,
|
|
234
|
-
priority,
|
|
235
|
-
state,
|
|
236
|
-
description,
|
|
237
|
-
assignee,
|
|
238
|
-
label,
|
|
239
|
-
estimate,
|
|
240
|
-
}) =>
|
|
241
|
-
Effect.gen(function* () {
|
|
242
|
-
const { key, id: projectId } = yield* resolveProject(project);
|
|
243
|
-
const body: Record<string, unknown> = { name: title };
|
|
244
|
-
if (priority._tag === "Some") body["priority"] = priority.value;
|
|
245
|
-
if (state._tag === "Some")
|
|
246
|
-
body["state"] = yield* getStateId(projectId, state.value);
|
|
247
|
-
if (description._tag === "Some") {
|
|
248
|
-
const escaped = escapeHtmlText(description.value);
|
|
249
|
-
body["description_html"] = `<p>${escaped}</p>`;
|
|
250
|
-
}
|
|
251
|
-
if (assignee._tag === "Some") {
|
|
252
|
-
const memberId = yield* getMemberId(assignee.value);
|
|
253
|
-
body["assignees"] = [memberId];
|
|
254
|
-
}
|
|
255
|
-
if (label._tag === "Some") {
|
|
256
|
-
const labelId = yield* getLabelId(projectId, label.value);
|
|
257
|
-
body["label_ids"] = [labelId];
|
|
258
|
-
}
|
|
259
|
-
if (estimate._tag === "Some") {
|
|
260
|
-
body["estimate_point"] = yield* getEstimatePointId(
|
|
261
|
-
projectId,
|
|
262
|
-
estimate.value,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
const raw = yield* api.post(`projects/${projectId}/issues/`, body);
|
|
266
|
-
const created = yield* decodeOrFail(IssueSchema, raw);
|
|
267
|
-
yield* Console.log(
|
|
268
|
-
`Created ${key}-${created.sequence_id}: ${created.name}`,
|
|
269
|
-
);
|
|
270
|
-
}),
|
|
302
|
+
issueCreateHandler,
|
|
271
303
|
).pipe(
|
|
272
304
|
Command.withDescription(
|
|
273
305
|
'Create a new issue in a project.\n\nExamples:\n plane issue create PROJ "Migrate Button component"\n plane issue create --priority high --state started PROJ "Fix lint pipeline"\n plane issue create --description "Detailed context here" PROJ "Add dark mode"\n plane issue create --assignee "Jane Doe" PROJ "Onboarding bug"',
|
|
274
306
|
),
|
|
275
307
|
);
|
|
276
|
-
|
|
277
308
|
// --- issue activity ---
|
|
309
|
+
export function issueActivityHandler({ ref }: { ref: string }) {
|
|
310
|
+
return Effect.gen(function* () {
|
|
311
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
312
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
313
|
+
const raw = yield* api.get(
|
|
314
|
+
`projects/${projectId}/issues/${issue.id}/activities/`,
|
|
315
|
+
);
|
|
316
|
+
const { results } = yield* decodeOrFail(ActivitiesResponseSchema, raw);
|
|
317
|
+
if (jsonMode) {
|
|
318
|
+
yield* Console.log(JSON.stringify(results, null, 2));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (xmlMode) {
|
|
322
|
+
yield* Console.log(toXml(results));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (results.length === 0) {
|
|
326
|
+
yield* Console.log("No activity found");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const lines = results.map((a) => {
|
|
330
|
+
const who = a.actor_detail?.display_name ?? "?";
|
|
331
|
+
const when = a.created_at.slice(0, 16).replace("T", " ");
|
|
332
|
+
if (a.field) {
|
|
333
|
+
const from = a.old_value ?? "—";
|
|
334
|
+
const to = a.new_value ?? "—";
|
|
335
|
+
return `${when} ${who} ${a.field}: ${from} → ${to}`;
|
|
336
|
+
}
|
|
337
|
+
return `${when} ${who} ${a.verb ?? "updated"}`;
|
|
338
|
+
});
|
|
339
|
+
yield* Console.log(lines.join("\n"));
|
|
340
|
+
});
|
|
341
|
+
}
|
|
278
342
|
|
|
279
343
|
export const issueActivity = Command.make(
|
|
280
344
|
"activity",
|
|
281
345
|
{ ref: refArg },
|
|
282
|
-
|
|
283
|
-
Effect.gen(function* () {
|
|
284
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
285
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
286
|
-
const raw = yield* api.get(
|
|
287
|
-
`projects/${projectId}/issues/${issue.id}/activities/`,
|
|
288
|
-
);
|
|
289
|
-
const { results } = yield* decodeOrFail(ActivitiesResponseSchema, raw);
|
|
290
|
-
if (jsonMode) {
|
|
291
|
-
yield* Console.log(JSON.stringify(results, null, 2));
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
if (xmlMode) {
|
|
295
|
-
yield* Console.log(toXml(results));
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (results.length === 0) {
|
|
299
|
-
yield* Console.log("No activity found");
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
const lines = results.map((a) => {
|
|
303
|
-
const who = a.actor_detail?.display_name ?? "?";
|
|
304
|
-
const when = a.created_at.slice(0, 16).replace("T", " ");
|
|
305
|
-
if (a.field) {
|
|
306
|
-
const from = a.old_value ?? "—";
|
|
307
|
-
const to = a.new_value ?? "—";
|
|
308
|
-
return `${when} ${who} ${a.field}: ${from} → ${to}`;
|
|
309
|
-
}
|
|
310
|
-
return `${when} ${who} ${a.verb ?? "updated"}`;
|
|
311
|
-
});
|
|
312
|
-
yield* Console.log(lines.join("\n"));
|
|
313
|
-
}),
|
|
346
|
+
issueActivityHandler,
|
|
314
347
|
).pipe(
|
|
315
348
|
Command.withDescription(
|
|
316
349
|
"Show audit trail for an issue — who changed what and when.\n\nExample:\n plane issue activity PROJ-29",
|
|
317
350
|
),
|
|
318
351
|
);
|
|
319
|
-
|
|
320
352
|
// --- issue link list ---
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
Effect.gen(function* () {
|
|
353
|
+
export function issueLinkListHandler({ ref }: { ref: string }) {
|
|
354
|
+
return Effect.gen(function* () {
|
|
324
355
|
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
325
356
|
const issue = yield* findIssueBySeq(projectId, seq);
|
|
326
357
|
const raw = yield* api.get(
|
|
@@ -343,11 +374,15 @@ export const issueLinkList = Command.make("list", { ref: refArg }, ({ ref }) =>
|
|
|
343
374
|
(l) => `${l.id} ${l.title ?? "(no title)"} ${l.url}`,
|
|
344
375
|
);
|
|
345
376
|
yield* Console.log(lines.join("\n"));
|
|
346
|
-
})
|
|
347
|
-
|
|
377
|
+
});
|
|
378
|
+
}
|
|
348
379
|
|
|
380
|
+
export const issueLinkList = Command.make(
|
|
381
|
+
"list",
|
|
382
|
+
{ ref: refArg },
|
|
383
|
+
issueLinkListHandler,
|
|
384
|
+
).pipe(Command.withDescription("List URL links attached to an issue."));
|
|
349
385
|
// --- issue link add ---
|
|
350
|
-
|
|
351
386
|
const urlArg = Args.text({ name: "url" }).pipe(
|
|
352
387
|
Args.withDescription("URL to link"),
|
|
353
388
|
);
|
|
@@ -355,140 +390,171 @@ const linkTitleOption = Options.optional(Options.text("title")).pipe(
|
|
|
355
390
|
Options.withDescription("Human-readable title for the link"),
|
|
356
391
|
);
|
|
357
392
|
|
|
393
|
+
export function issueLinkAddHandler({
|
|
394
|
+
ref,
|
|
395
|
+
url,
|
|
396
|
+
title,
|
|
397
|
+
}: {
|
|
398
|
+
ref: string;
|
|
399
|
+
url: string;
|
|
400
|
+
title: Option.Option<string>;
|
|
401
|
+
}) {
|
|
402
|
+
return Effect.gen(function* () {
|
|
403
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
404
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
405
|
+
const body: Record<string, string> = { url };
|
|
406
|
+
if (title._tag === "Some") body["title"] = title.value;
|
|
407
|
+
const raw = yield* api.post(
|
|
408
|
+
`projects/${projectId}/issues/${issue.id}/issue-links/`,
|
|
409
|
+
body,
|
|
410
|
+
);
|
|
411
|
+
const link = yield* decodeOrFail(IssueLinkSchema, raw);
|
|
412
|
+
yield* Console.log(`Link added: ${link.id} ${link.url}`);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
358
416
|
export const issueLinkAdd = Command.make(
|
|
359
417
|
"add",
|
|
360
418
|
{ title: linkTitleOption, ref: refArg, url: urlArg },
|
|
361
|
-
|
|
362
|
-
Effect.gen(function* () {
|
|
363
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
364
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
365
|
-
const body: Record<string, string> = { url };
|
|
366
|
-
if (title._tag === "Some") body["title"] = title.value;
|
|
367
|
-
const raw = yield* api.post(
|
|
368
|
-
`projects/${projectId}/issues/${issue.id}/issue-links/`,
|
|
369
|
-
body,
|
|
370
|
-
);
|
|
371
|
-
const link = yield* decodeOrFail(IssueLinkSchema, raw);
|
|
372
|
-
yield* Console.log(`Link added: ${link.id} ${link.url}`);
|
|
373
|
-
}),
|
|
419
|
+
issueLinkAddHandler,
|
|
374
420
|
).pipe(
|
|
375
421
|
Command.withDescription(
|
|
376
422
|
'Attach a URL link to an issue.\n\nExamples:\n plane issue link add PROJ-29 https://github.com/org/repo/pull/42\n plane issue link add --title "Design doc" PROJ-29 https://docs.example.com',
|
|
377
423
|
),
|
|
378
424
|
);
|
|
379
|
-
|
|
380
425
|
// --- issue link remove ---
|
|
381
|
-
|
|
382
426
|
const linkIdArg = Args.text({ name: "link-id" }).pipe(
|
|
383
427
|
Args.withDescription("Link ID (from 'plane issue link list')"),
|
|
384
428
|
);
|
|
385
429
|
|
|
430
|
+
export function issueLinkRemoveHandler({
|
|
431
|
+
ref,
|
|
432
|
+
linkId,
|
|
433
|
+
}: {
|
|
434
|
+
ref: string;
|
|
435
|
+
linkId: string;
|
|
436
|
+
}) {
|
|
437
|
+
return Effect.gen(function* () {
|
|
438
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
439
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
440
|
+
yield* api.delete(
|
|
441
|
+
`projects/${projectId}/issues/${issue.id}/issue-links/${linkId}/`,
|
|
442
|
+
);
|
|
443
|
+
yield* Console.log(`Link ${linkId} removed from ${ref}`);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
386
447
|
export const issueLinkRemove = Command.make(
|
|
387
448
|
"remove",
|
|
388
449
|
{ ref: refArg, linkId: linkIdArg },
|
|
389
|
-
|
|
390
|
-
Effect.gen(function* () {
|
|
391
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
392
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
393
|
-
yield* api.delete(
|
|
394
|
-
`projects/${projectId}/issues/${issue.id}/issue-links/${linkId}/`,
|
|
395
|
-
);
|
|
396
|
-
yield* Console.log(`Link ${linkId} removed from ${ref}`);
|
|
397
|
-
}),
|
|
450
|
+
issueLinkRemoveHandler,
|
|
398
451
|
).pipe(Command.withDescription("Remove a URL link from an issue by link ID."));
|
|
399
|
-
|
|
400
452
|
// --- issue link (parent) ---
|
|
401
|
-
|
|
402
453
|
export const issueLink = Command.make("link").pipe(
|
|
403
454
|
Command.withDescription(
|
|
404
455
|
"Manage URL links on an issue. Subcommands: list, add, remove",
|
|
405
456
|
),
|
|
406
457
|
Command.withSubcommands([issueLinkList, issueLinkAdd, issueLinkRemove]),
|
|
407
458
|
);
|
|
408
|
-
|
|
409
459
|
// --- issue comments list ---
|
|
460
|
+
export function issueCommentsListHandler({ ref }: { ref: string }) {
|
|
461
|
+
return Effect.gen(function* () {
|
|
462
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
463
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
464
|
+
const raw = yield* api.get(
|
|
465
|
+
`projects/${projectId}/issues/${issue.id}/comments/`,
|
|
466
|
+
);
|
|
467
|
+
const { results } = yield* decodeOrFail(CommentsResponseSchema, raw);
|
|
468
|
+
if (jsonMode) {
|
|
469
|
+
yield* Console.log(JSON.stringify(results, null, 2));
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (xmlMode) {
|
|
473
|
+
yield* Console.log(toXml(results));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (results.length === 0) {
|
|
477
|
+
yield* Console.log("No comments");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const lines = results.map((c) => {
|
|
481
|
+
const who = c.actor_detail?.display_name ?? "?";
|
|
482
|
+
const when = c.created_at.slice(0, 16).replace("T", " ");
|
|
483
|
+
const text = (c.comment_html ?? "").replace(/<[^>]+>/g, "").trim();
|
|
484
|
+
return `${c.id} ${when} ${who}: ${text}`;
|
|
485
|
+
});
|
|
486
|
+
yield* Console.log(lines.join("\n"));
|
|
487
|
+
});
|
|
488
|
+
}
|
|
410
489
|
|
|
411
490
|
export const issueCommentsList = Command.make(
|
|
412
491
|
"list",
|
|
413
492
|
{ ref: refArg },
|
|
414
|
-
|
|
415
|
-
Effect.gen(function* () {
|
|
416
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
417
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
418
|
-
const raw = yield* api.get(
|
|
419
|
-
`projects/${projectId}/issues/${issue.id}/comments/`,
|
|
420
|
-
);
|
|
421
|
-
const { results } = yield* decodeOrFail(CommentsResponseSchema, raw);
|
|
422
|
-
if (jsonMode) {
|
|
423
|
-
yield* Console.log(JSON.stringify(results, null, 2));
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
if (xmlMode) {
|
|
427
|
-
yield* Console.log(toXml(results));
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (results.length === 0) {
|
|
431
|
-
yield* Console.log("No comments");
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
const lines = results.map((c) => {
|
|
435
|
-
const who = c.actor_detail?.display_name ?? "?";
|
|
436
|
-
const when = c.created_at.slice(0, 16).replace("T", " ");
|
|
437
|
-
const text = (c.comment_html ?? "").replace(/<[^>]+>/g, "").trim();
|
|
438
|
-
return `${c.id} ${when} ${who}: ${text}`;
|
|
439
|
-
});
|
|
440
|
-
yield* Console.log(lines.join("\n"));
|
|
441
|
-
}),
|
|
493
|
+
issueCommentsListHandler,
|
|
442
494
|
).pipe(
|
|
443
495
|
Command.withDescription(
|
|
444
496
|
"List comments on an issue. Shows comment ID, timestamp, author, and plain text.\n\nExample:\n plane issue comments list PROJ-29",
|
|
445
497
|
),
|
|
446
498
|
);
|
|
447
|
-
|
|
448
499
|
// --- issue comment update ---
|
|
449
|
-
|
|
450
500
|
const commentIdArg = Args.text({ name: "comment-id" }).pipe(
|
|
451
501
|
Args.withDescription("Comment ID (from 'plane issue comments list')"),
|
|
452
502
|
);
|
|
453
503
|
|
|
504
|
+
export function issueCommentUpdateHandler({
|
|
505
|
+
ref,
|
|
506
|
+
commentId,
|
|
507
|
+
text,
|
|
508
|
+
}: {
|
|
509
|
+
ref: string;
|
|
510
|
+
commentId: string;
|
|
511
|
+
text: string;
|
|
512
|
+
}) {
|
|
513
|
+
return Effect.gen(function* () {
|
|
514
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
515
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
516
|
+
const escaped = escapeHtmlText(text);
|
|
517
|
+
yield* api.patch(
|
|
518
|
+
`projects/${projectId}/issues/${issue.id}/comments/${commentId}/`,
|
|
519
|
+
{ comment_html: `<p>${escaped}</p>` },
|
|
520
|
+
);
|
|
521
|
+
yield* Console.log(`Comment ${commentId} updated`);
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
454
525
|
export const issueCommentUpdate = Command.make(
|
|
455
526
|
"update",
|
|
456
527
|
{ ref: refArg, commentId: commentIdArg, text: textArg },
|
|
457
|
-
|
|
458
|
-
Effect.gen(function* () {
|
|
459
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
460
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
461
|
-
const escaped = escapeHtmlText(text);
|
|
462
|
-
yield* api.patch(
|
|
463
|
-
`projects/${projectId}/issues/${issue.id}/comments/${commentId}/`,
|
|
464
|
-
{ comment_html: `<p>${escaped}</p>` },
|
|
465
|
-
);
|
|
466
|
-
yield* Console.log(`Comment ${commentId} updated`);
|
|
467
|
-
}),
|
|
528
|
+
issueCommentUpdateHandler,
|
|
468
529
|
).pipe(
|
|
469
530
|
Command.withDescription(
|
|
470
531
|
'Edit an existing comment.\n\nExample:\n plane issue comments update PROJ-29 <comment-id> "Updated text"',
|
|
471
532
|
),
|
|
472
533
|
);
|
|
473
|
-
|
|
474
534
|
// --- issue comment delete ---
|
|
535
|
+
export function issueCommentDeleteHandler({
|
|
536
|
+
ref,
|
|
537
|
+
commentId,
|
|
538
|
+
}: {
|
|
539
|
+
ref: string;
|
|
540
|
+
commentId: string;
|
|
541
|
+
}) {
|
|
542
|
+
return Effect.gen(function* () {
|
|
543
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
544
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
545
|
+
yield* api.delete(
|
|
546
|
+
`projects/${projectId}/issues/${issue.id}/comments/${commentId}/`,
|
|
547
|
+
);
|
|
548
|
+
yield* Console.log(`Comment ${commentId} deleted`);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
475
551
|
|
|
476
552
|
export const issueCommentDelete = Command.make(
|
|
477
553
|
"delete",
|
|
478
554
|
{ ref: refArg, commentId: commentIdArg },
|
|
479
|
-
|
|
480
|
-
Effect.gen(function* () {
|
|
481
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
482
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
483
|
-
yield* api.delete(
|
|
484
|
-
`projects/${projectId}/issues/${issue.id}/comments/${commentId}/`,
|
|
485
|
-
);
|
|
486
|
-
yield* Console.log(`Comment ${commentId} deleted`);
|
|
487
|
-
}),
|
|
555
|
+
issueCommentDeleteHandler,
|
|
488
556
|
).pipe(Command.withDescription("Delete a comment from an issue."));
|
|
489
|
-
|
|
490
557
|
// --- issue comments (parent) ---
|
|
491
|
-
|
|
492
558
|
export const issueComments = Command.make("comments").pipe(
|
|
493
559
|
Command.withDescription(
|
|
494
560
|
"Manage comments on an issue. Subcommands: list, update, delete\n\nNote: use 'plane issue comment REF TEXT' to add a new comment.",
|
|
@@ -499,49 +565,48 @@ export const issueComments = Command.make("comments").pipe(
|
|
|
499
565
|
issueCommentDelete,
|
|
500
566
|
]),
|
|
501
567
|
);
|
|
502
|
-
|
|
503
568
|
// --- issue worklogs list ---
|
|
569
|
+
export function issueWorklogsListHandler({ ref }: { ref: string }) {
|
|
570
|
+
return Effect.gen(function* () {
|
|
571
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
572
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
573
|
+
const raw = yield* api.get(
|
|
574
|
+
`projects/${projectId}/issues/${issue.id}/worklogs/`,
|
|
575
|
+
);
|
|
576
|
+
const { results } = yield* decodeOrFail(WorklogsResponseSchema, raw);
|
|
577
|
+
if (jsonMode) {
|
|
578
|
+
yield* Console.log(JSON.stringify(results, null, 2));
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (xmlMode) {
|
|
582
|
+
yield* Console.log(toXml(results));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (results.length === 0) {
|
|
586
|
+
yield* Console.log("No worklogs");
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const lines = results.map((w) => {
|
|
590
|
+
const who = w.logged_by_detail?.display_name ?? "?";
|
|
591
|
+
const when = w.created_at.slice(0, 10);
|
|
592
|
+
const hrs = (w.duration / 60).toFixed(1);
|
|
593
|
+
const desc = w.description ?? "";
|
|
594
|
+
return `${w.id} ${when} ${who} ${hrs}h ${desc}`;
|
|
595
|
+
});
|
|
596
|
+
yield* Console.log(lines.join("\n"));
|
|
597
|
+
});
|
|
598
|
+
}
|
|
504
599
|
|
|
505
600
|
export const issueWorklogsList = Command.make(
|
|
506
601
|
"list",
|
|
507
602
|
{ ref: refArg },
|
|
508
|
-
|
|
509
|
-
Effect.gen(function* () {
|
|
510
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
511
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
512
|
-
const raw = yield* api.get(
|
|
513
|
-
`projects/${projectId}/issues/${issue.id}/worklogs/`,
|
|
514
|
-
);
|
|
515
|
-
const { results } = yield* decodeOrFail(WorklogsResponseSchema, raw);
|
|
516
|
-
if (jsonMode) {
|
|
517
|
-
yield* Console.log(JSON.stringify(results, null, 2));
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
if (xmlMode) {
|
|
521
|
-
yield* Console.log(toXml(results));
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
if (results.length === 0) {
|
|
525
|
-
yield* Console.log("No worklogs");
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
const lines = results.map((w) => {
|
|
529
|
-
const who = w.logged_by_detail?.display_name ?? "?";
|
|
530
|
-
const when = w.created_at.slice(0, 10);
|
|
531
|
-
const hrs = (w.duration / 60).toFixed(1);
|
|
532
|
-
const desc = w.description ?? "";
|
|
533
|
-
return `${w.id} ${when} ${who} ${hrs}h ${desc}`;
|
|
534
|
-
});
|
|
535
|
-
yield* Console.log(lines.join("\n"));
|
|
536
|
-
}),
|
|
603
|
+
issueWorklogsListHandler,
|
|
537
604
|
).pipe(
|
|
538
605
|
Command.withDescription(
|
|
539
606
|
"List time log entries for an issue. Duration shown in hours.\n\nExample:\n plane issue worklogs list PROJ-29",
|
|
540
607
|
),
|
|
541
608
|
);
|
|
542
|
-
|
|
543
609
|
// --- issue worklogs add ---
|
|
544
|
-
|
|
545
610
|
const durationArg = Args.integer({ name: "minutes" }).pipe(
|
|
546
611
|
Args.withDescription("Time spent in minutes"),
|
|
547
612
|
);
|
|
@@ -549,55 +614,66 @@ const worklogDescOption = Options.optional(Options.text("description")).pipe(
|
|
|
549
614
|
Options.withDescription("Optional description of work done"),
|
|
550
615
|
);
|
|
551
616
|
|
|
617
|
+
export function issueWorklogsAddHandler({
|
|
618
|
+
ref,
|
|
619
|
+
duration,
|
|
620
|
+
description,
|
|
621
|
+
}: {
|
|
622
|
+
ref: string;
|
|
623
|
+
duration: number;
|
|
624
|
+
description: Option.Option<string>;
|
|
625
|
+
}) {
|
|
626
|
+
return Effect.gen(function* () {
|
|
627
|
+
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
628
|
+
const issue = yield* findIssueBySeq(projectId, seq);
|
|
629
|
+
const body: WorklogPayload = { duration };
|
|
630
|
+
if (description._tag === "Some") body.description = description.value;
|
|
631
|
+
const raw = yield* api.post(
|
|
632
|
+
`projects/${projectId}/issues/${issue.id}/worklogs/`,
|
|
633
|
+
body,
|
|
634
|
+
);
|
|
635
|
+
const log = yield* decodeOrFail(WorklogSchema, raw);
|
|
636
|
+
const hrs = (log.duration / 60).toFixed(1);
|
|
637
|
+
yield* Console.log(`Logged ${hrs}h on ${ref} (${log.id})`);
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
552
641
|
export const issueWorklogsAdd = Command.make(
|
|
553
642
|
"add",
|
|
554
643
|
{ description: worklogDescOption, ref: refArg, duration: durationArg },
|
|
555
|
-
|
|
556
|
-
Effect.gen(function* () {
|
|
557
|
-
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
558
|
-
const issue = yield* findIssueBySeq(projectId, seq);
|
|
559
|
-
const body: Record<string, unknown> = { duration };
|
|
560
|
-
if (description._tag === "Some") body["description"] = description.value;
|
|
561
|
-
const raw = yield* api.post(
|
|
562
|
-
`projects/${projectId}/issues/${issue.id}/worklogs/`,
|
|
563
|
-
body,
|
|
564
|
-
);
|
|
565
|
-
const log = yield* decodeOrFail(WorklogSchema, raw);
|
|
566
|
-
const hrs = (log.duration / 60).toFixed(1);
|
|
567
|
-
yield* Console.log(`Logged ${hrs}h on ${ref} (${log.id})`);
|
|
568
|
-
}),
|
|
644
|
+
issueWorklogsAddHandler,
|
|
569
645
|
).pipe(
|
|
570
646
|
Command.withDescription(
|
|
571
647
|
'Log time spent on an issue (duration in minutes).\n\nExamples:\n plane issue worklogs add PROJ-29 90\n plane issue worklogs add --description "code review" PROJ-29 30',
|
|
572
648
|
),
|
|
573
649
|
);
|
|
574
|
-
|
|
575
650
|
// --- issue worklogs (parent) ---
|
|
576
|
-
|
|
577
651
|
export const issueWorklogs = Command.make("worklogs").pipe(
|
|
578
652
|
Command.withDescription(
|
|
579
653
|
"Manage time logs for an issue. Subcommands: list, add",
|
|
580
654
|
),
|
|
581
655
|
Command.withSubcommands([issueWorklogsList, issueWorklogsAdd]),
|
|
582
656
|
);
|
|
583
|
-
|
|
584
657
|
// --- issue delete ---
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
Effect.gen(function* () {
|
|
658
|
+
export function issueDeleteHandler({ ref }: { ref: string }) {
|
|
659
|
+
return Effect.gen(function* () {
|
|
588
660
|
const { projectId, seq } = yield* parseIssueRef(ref);
|
|
589
661
|
const issue = yield* findIssueBySeq(projectId, seq);
|
|
590
662
|
yield* api.delete(`projects/${projectId}/issues/${issue.id}/`);
|
|
591
663
|
yield* Console.log(`Deleted ${ref}`);
|
|
592
|
-
})
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export const issueDelete = Command.make(
|
|
668
|
+
"delete",
|
|
669
|
+
{ ref: refArg },
|
|
670
|
+
issueDeleteHandler,
|
|
593
671
|
).pipe(
|
|
594
672
|
Command.withDescription(
|
|
595
673
|
"Permanently delete an issue. This cannot be undone.",
|
|
596
674
|
),
|
|
597
675
|
);
|
|
598
|
-
|
|
599
676
|
// --- issue (parent) ---
|
|
600
|
-
|
|
601
677
|
export const issue = Command.make("issue").pipe(
|
|
602
678
|
Command.withDescription(
|
|
603
679
|
"Manage individual issues. Use 'plane issue <subcommand> --help' for details.\n\nSubcommands: get, create, update, delete, comment, activity, link, comments, worklogs",
|