linear-sdk-cli 0.1.3 → 0.2.3
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +96 -58
- package/COVERAGE.md +5 -5
- package/MIGRATING.md +71 -69
- package/README.md +115 -90
- package/package.json +6 -7
- package/skills/README.md +15 -0
- package/skills/linear-sdk-cli/SKILL.md +27 -3
- package/skills/linear-sdk-cli/references/api.md +9 -9
- package/skills/linear-sdk-cli/references/auth.md +18 -0
- package/skills/linear-sdk-cli/references/commands.md +2 -2
- package/skills/linear-sdk-cli/references/comment.md +9 -6
- package/skills/linear-sdk-cli/references/config.md +9 -11
- package/skills/linear-sdk-cli/references/issue.md +72 -18
- package/skills/linear-sdk-cli/references/open.md +29 -0
- package/skills/linear-sdk-cli/references/team.md +16 -0
- package/skills/linear-sdk-cli/scripts/generate-docs.ts +6 -2
- package/skills/linear-sdk-cli-maintenance/SKILL.md +97 -0
- package/src/bin/linear.ts +16 -6
- package/src/cli.ts +3 -1
- package/src/commands/agent-session.ts +13 -5
- package/src/commands/api.ts +15 -9
- package/src/commands/attach.ts +15 -4
- package/src/commands/comment.ts +50 -11
- package/src/commands/completion.ts +2 -6
- package/src/commands/cycle.ts +6 -1
- package/src/commands/discover.ts +6 -2
- package/src/commands/document.ts +4 -1
- package/src/commands/initiative.ts +5 -1
- package/src/commands/issue.ts +392 -91
- package/src/commands/meta.ts +139 -31
- package/src/commands/milestone.ts +4 -1
- package/src/commands/notification.ts +15 -6
- package/src/commands/open.ts +84 -0
- package/src/commands/project.ts +17 -4
- package/src/commands/team.ts +13 -0
- package/src/commands/user.ts +2 -2
- package/src/commands/webhook.ts +12 -2
- package/src/config.ts +501 -184
- package/src/context.ts +13 -4
- package/src/git.ts +28 -3
- package/src/lib/body.ts +26 -2
- package/src/lib/errors.ts +33 -8
- package/src/lib/introspect.ts +76 -7
- package/src/lib/keyring.ts +51 -9
- package/src/lib/mentions.ts +49 -0
- package/src/lib/open.ts +32 -0
- package/src/lib/options.ts +108 -12
- package/src/lib/output-shapes.ts +81 -9
- package/src/lib/pagination.ts +50 -17
- package/src/lib/resolve.ts +40 -7
- package/src/lib/shape.ts +16 -12
- package/src/lib/upload.ts +3 -1
- package/src/output/color.ts +52 -0
- package/src/output/format.ts +96 -18
- package/src/output/markdown.ts +133 -0
- package/src/output/pager.ts +131 -0
- package/src/output/sanitize.ts +1 -1
- package/src/output/table.ts +7 -7
- package/src/services/agent-session.ts +11 -3
- package/src/services/attachment.ts +14 -5
- package/src/services/comment.ts +37 -9
- package/src/services/cycle.ts +11 -4
- package/src/services/document.ts +29 -10
- package/src/services/favorite.ts +15 -9
- package/src/services/initiative-update.ts +16 -15
- package/src/services/initiative.ts +15 -10
- package/src/services/issue.ts +263 -19
- package/src/services/label.ts +12 -2
- package/src/services/milestone.ts +8 -5
- package/src/services/notification.ts +16 -2
- package/src/services/organization.ts +23 -18
- package/src/services/project.ts +55 -34
- package/src/services/roadmap.ts +7 -6
- package/src/services/state.ts +9 -3
- package/src/services/team.ts +67 -24
- package/src/services/user.ts +2 -2
- package/src/services/webhook.ts +28 -17
package/src/services/project.ts
CHANGED
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
import type { LinearClient } from "@linear/sdk";
|
|
9
9
|
import { withRetry } from "../client.js";
|
|
10
10
|
import { shape } from "../lib/shape.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
collect,
|
|
13
|
+
collectRawQuery,
|
|
14
|
+
inheritPaginationMetadata,
|
|
15
|
+
pageSize,
|
|
16
|
+
} from "../lib/pagination.js";
|
|
12
17
|
import { usageError, notFound, ambiguous } from "../lib/errors.js";
|
|
13
18
|
import { assertMutation, unwrapMutation } from "../lib/mutation.js";
|
|
14
19
|
import {
|
|
@@ -206,7 +211,10 @@ query CliProjectDetail($filter: ProjectFilter!, $includeArchived: Boolean!) {
|
|
|
206
211
|
}
|
|
207
212
|
}`;
|
|
208
213
|
|
|
209
|
-
export async function getProjectDetail(
|
|
214
|
+
export async function getProjectDetail(
|
|
215
|
+
client: LinearClient,
|
|
216
|
+
idArg: string,
|
|
217
|
+
): Promise<ProjectDetail> {
|
|
210
218
|
const byId = isUuid(idArg);
|
|
211
219
|
const filter = byId ? { id: { eq: idArg } } : { name: { eqIgnoreCase: idArg } };
|
|
212
220
|
const data: any = await withRetry(() =>
|
|
@@ -337,7 +345,10 @@ export async function updateProject(client: LinearClient, idArg: string, opts: U
|
|
|
337
345
|
export async function archiveProject(client: LinearClient, idArg: string) {
|
|
338
346
|
const projectId = await resolveProjectId(client, idArg);
|
|
339
347
|
const project = await withRetry(() => client.project(projectId));
|
|
340
|
-
await assertMutation(
|
|
348
|
+
await assertMutation(
|
|
349
|
+
withRetry(() => client.archiveProject(projectId)),
|
|
350
|
+
"Project archive",
|
|
351
|
+
);
|
|
341
352
|
return project;
|
|
342
353
|
}
|
|
343
354
|
|
|
@@ -350,7 +361,10 @@ export async function archiveProject(client: LinearClient, idArg: string) {
|
|
|
350
361
|
export async function deleteProject(client: LinearClient, idArg: string) {
|
|
351
362
|
const projectId = await resolveProjectId(client, idArg);
|
|
352
363
|
const project = await withRetry(() => client.project(projectId));
|
|
353
|
-
await assertMutation(
|
|
364
|
+
await assertMutation(
|
|
365
|
+
withRetry(() => client.deleteProject(projectId)),
|
|
366
|
+
"Project deletion",
|
|
367
|
+
);
|
|
354
368
|
return project;
|
|
355
369
|
}
|
|
356
370
|
|
|
@@ -376,16 +390,17 @@ export async function listMilestones(
|
|
|
376
390
|
): Promise<MilestoneRow[]> {
|
|
377
391
|
const projectId = await resolveProjectId(client, idArg);
|
|
378
392
|
const project = await withRetry(() => client.project(projectId));
|
|
379
|
-
const conn = await withRetry(() =>
|
|
380
|
-
project.projectMilestones({ first: Math.min(limit === Infinity ? 100 : limit, 100) }),
|
|
381
|
-
);
|
|
393
|
+
const conn = await withRetry(() => project.projectMilestones({ first: pageSize(limit) }));
|
|
382
394
|
const nodes = await collect(conn as any, limit);
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
395
|
+
return inheritPaginationMetadata(
|
|
396
|
+
nodes.map((m: any) => ({
|
|
397
|
+
id: m.id,
|
|
398
|
+
name: m.name,
|
|
399
|
+
targetDate: m.targetDate ?? null,
|
|
400
|
+
progress: m.progress ?? null,
|
|
401
|
+
})),
|
|
402
|
+
nodes,
|
|
403
|
+
);
|
|
389
404
|
}
|
|
390
405
|
|
|
391
406
|
export interface UpdateRow {
|
|
@@ -403,21 +418,22 @@ export async function listUpdates(
|
|
|
403
418
|
): Promise<UpdateRow[]> {
|
|
404
419
|
const projectId = await resolveProjectId(client, idArg);
|
|
405
420
|
const project = await withRetry(() => client.project(projectId));
|
|
406
|
-
const conn = await withRetry(() =>
|
|
407
|
-
project.projectUpdates({ first: Math.min(limit === Infinity ? 100 : limit, 100) }),
|
|
408
|
-
);
|
|
421
|
+
const conn = await withRetry(() => project.projectUpdates({ first: pageSize(limit) }));
|
|
409
422
|
const nodes = await collect(conn as any, limit);
|
|
410
|
-
return
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
423
|
+
return inheritPaginationMetadata(
|
|
424
|
+
await Promise.all(
|
|
425
|
+
nodes.map(async (u: any) => {
|
|
426
|
+
const user = await u.user;
|
|
427
|
+
return {
|
|
428
|
+
id: u.id,
|
|
429
|
+
createdAt: u.createdAt?.toISOString?.() ?? String(u.createdAt),
|
|
430
|
+
user: user?.displayName ?? "unknown",
|
|
431
|
+
body: u.body ?? "",
|
|
432
|
+
health: u.health ?? null,
|
|
433
|
+
};
|
|
434
|
+
}),
|
|
435
|
+
),
|
|
436
|
+
nodes,
|
|
421
437
|
);
|
|
422
438
|
}
|
|
423
439
|
|
|
@@ -457,17 +473,22 @@ async function resolveTeamIds(
|
|
|
457
473
|
/** Resolve a project status (by name or type) to a status id. */
|
|
458
474
|
async function resolveStatusId(client: LinearClient, input: string): Promise<string> {
|
|
459
475
|
if (isUuid(input)) return input;
|
|
460
|
-
const
|
|
476
|
+
const conn = await withRetry(() => client.projectStatuses({ first: 250 }));
|
|
477
|
+
const statuses = (await collect(conn as any, Infinity)) as Array<{
|
|
478
|
+
id: string;
|
|
479
|
+
name: string;
|
|
480
|
+
type: string;
|
|
481
|
+
}>;
|
|
461
482
|
const lower = input.toLowerCase();
|
|
462
|
-
const byName = statuses.
|
|
463
|
-
const matches = byName.length
|
|
464
|
-
? byName
|
|
465
|
-
: statuses.nodes.filter((s) => s.type.toLowerCase() === lower);
|
|
483
|
+
const byName = statuses.filter((s) => s.name.toLowerCase() === lower);
|
|
484
|
+
const matches = byName.length ? byName : statuses.filter((s) => s.type.toLowerCase() === lower);
|
|
466
485
|
if (matches.length === 0)
|
|
467
486
|
throw notFound(
|
|
468
|
-
`No project status '${input}'. Available: ${statuses.
|
|
487
|
+
`No project status '${input}'. Available: ${statuses.map((s) => s.name).join(", ")}`,
|
|
469
488
|
);
|
|
470
489
|
if (matches.length > 1)
|
|
471
|
-
throw ambiguous(
|
|
490
|
+
throw ambiguous(
|
|
491
|
+
`Multiple project statuses match '${input}': ${matches.map((s) => s.name).join(", ")}`,
|
|
492
|
+
);
|
|
472
493
|
return matches[0]!.id;
|
|
473
494
|
}
|
package/src/services/roadmap.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { LinearClient } from "@linear/sdk";
|
|
14
14
|
import { withRetry } from "../client.js";
|
|
15
15
|
import { shape } from "../lib/shape.js";
|
|
16
|
-
import { collect } from "../lib/pagination.js";
|
|
16
|
+
import { collect, inheritPaginationMetadata, pageSize } from "../lib/pagination.js";
|
|
17
17
|
import { usageError, notFound, ambiguous } from "../lib/errors.js";
|
|
18
18
|
import { assertMutation, unwrapMutation } from "../lib/mutation.js";
|
|
19
19
|
import { resolveUserId, isUuid } from "../lib/resolve.js";
|
|
@@ -44,11 +44,9 @@ export function toRow(r: any): RoadmapRow {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export async function listRoadmaps(client: LinearClient, limit: number): Promise<RoadmapRow[]> {
|
|
47
|
-
const conn = await withRetry(() =>
|
|
48
|
-
client.roadmaps({ first: limit === Infinity ? 100 : Math.min(limit, 100) }),
|
|
49
|
-
);
|
|
47
|
+
const conn = await withRetry(() => client.roadmaps({ first: pageSize(limit) }));
|
|
50
48
|
const nodes = await collect(conn as any, limit);
|
|
51
|
-
return nodes.map(toRow);
|
|
49
|
+
return inheritPaginationMetadata(nodes.map(toRow), nodes);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
export interface RoadmapDetail {
|
|
@@ -153,7 +151,10 @@ export async function updateRoadmap(client: LinearClient, idArg: string, opts: U
|
|
|
153
151
|
|
|
154
152
|
export async function deleteRoadmap(client: LinearClient, idArg: string) {
|
|
155
153
|
const roadmap = await resolveRoadmap(client, idArg);
|
|
156
|
-
await assertMutation(
|
|
154
|
+
await assertMutation(
|
|
155
|
+
withRetry(() => client.deleteRoadmap(roadmap.id)),
|
|
156
|
+
"Roadmap deletion",
|
|
157
|
+
);
|
|
157
158
|
return roadmap;
|
|
158
159
|
}
|
|
159
160
|
|
package/src/services/state.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { LinearClient } from "@linear/sdk";
|
|
11
11
|
import { withRetry } from "../client.js";
|
|
12
12
|
import { shape } from "../lib/shape.js";
|
|
13
|
-
import { collect, pageSize } from "../lib/pagination.js";
|
|
13
|
+
import { collect, pageSize, setPaginationMetadata } from "../lib/pagination.js";
|
|
14
14
|
import { usageError } from "../lib/errors.js";
|
|
15
15
|
import { resolveTeam, resolveStateId, isUuid } from "../lib/resolve.js";
|
|
16
16
|
|
|
@@ -41,8 +41,14 @@ export async function listStates(
|
|
|
41
41
|
const team = await resolveTeam(client, teamInput, defaultTeamKey);
|
|
42
42
|
const teamModel = await withRetry(() => client.team(team.id));
|
|
43
43
|
const conn = await withRetry(() => teamModel.states({ first: pageSize(limit) }));
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// The API's connection order is unrelated to workflow position. Sort the
|
|
45
|
+
// complete small state set, then apply the caller's limit.
|
|
46
|
+
const nodes = await collect(conn as any, Infinity);
|
|
47
|
+
const rows = nodes.map(toRow).sort((a, b) => a.position - b.position);
|
|
48
|
+
return setPaginationMetadata(
|
|
49
|
+
limit === Infinity ? rows : rows.slice(0, limit),
|
|
50
|
+
rows.length > limit,
|
|
51
|
+
);
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export interface StateDetail {
|
package/src/services/team.ts
CHANGED
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
import type { LinearClient } from "@linear/sdk";
|
|
10
10
|
import { withRetry } from "../client.js";
|
|
11
11
|
import { shape } from "../lib/shape.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
collect,
|
|
14
|
+
inheritPaginationMetadata,
|
|
15
|
+
pageSize,
|
|
16
|
+
setPaginationMetadata,
|
|
17
|
+
} from "../lib/pagination.js";
|
|
13
18
|
import { usageError } from "../lib/errors.js";
|
|
14
19
|
import { assertMutation, unwrapMutation } from "../lib/mutation.js";
|
|
15
20
|
import { resolveTeam, type ResolvedTeam } from "../lib/resolve.js";
|
|
@@ -23,11 +28,23 @@ export interface TeamRow {
|
|
|
23
28
|
/** The row's shape as `linear commands` advertises it (TES-610); checked against the interface. */
|
|
24
29
|
export const TEAM_ROW_SHAPE = shape<TeamRow>({ id: "string", key: "string", name: "string" });
|
|
25
30
|
|
|
31
|
+
/** Resolve the selected team to its canonical key, UUID, and name. */
|
|
32
|
+
export async function getTeamIdentity(
|
|
33
|
+
client: LinearClient,
|
|
34
|
+
keyArg: string | undefined,
|
|
35
|
+
defaultTeamKey: string | undefined,
|
|
36
|
+
): Promise<ResolvedTeam> {
|
|
37
|
+
return resolveTeam(client, keyArg, defaultTeamKey);
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
/** All teams (key, name, id). */
|
|
27
41
|
export async function listTeams(client: LinearClient, limit: number): Promise<TeamRow[]> {
|
|
28
|
-
const conn = await withRetry(() => client.teams({ first:
|
|
42
|
+
const conn = await withRetry(() => client.teams({ first: pageSize(limit) }));
|
|
29
43
|
const nodes = await collect(conn as any, limit);
|
|
30
|
-
return
|
|
44
|
+
return inheritPaginationMetadata(
|
|
45
|
+
nodes.map((t: any) => ({ id: t.id, key: t.key, name: t.name })),
|
|
46
|
+
nodes,
|
|
47
|
+
);
|
|
31
48
|
}
|
|
32
49
|
|
|
33
50
|
export interface TeamDetail {
|
|
@@ -128,13 +145,16 @@ export async function listMembers(
|
|
|
128
145
|
(await withRetry(() => team.members({ first: pageSize(limit), includeDisabled }))) as any,
|
|
129
146
|
limit,
|
|
130
147
|
);
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
return inheritPaginationMetadata(
|
|
149
|
+
nodes.map((u: any) => ({
|
|
150
|
+
id: u.id,
|
|
151
|
+
displayName: u.displayName,
|
|
152
|
+
name: u.name,
|
|
153
|
+
email: u.email,
|
|
154
|
+
active: !!u.active,
|
|
155
|
+
})),
|
|
156
|
+
nodes,
|
|
157
|
+
);
|
|
138
158
|
}
|
|
139
159
|
|
|
140
160
|
export interface StateRow {
|
|
@@ -161,8 +181,8 @@ export async function listStates(
|
|
|
161
181
|
): Promise<StateRow[]> {
|
|
162
182
|
const resolved = await resolveTeam(client, keyArg, defaultTeamKey);
|
|
163
183
|
const team = await withRetry(() => client.team(resolved.id));
|
|
164
|
-
const nodes = await collect((await withRetry(() => team.states())) as any,
|
|
165
|
-
|
|
184
|
+
const nodes = await collect((await withRetry(() => team.states())) as any, Infinity);
|
|
185
|
+
const rows = nodes
|
|
166
186
|
.map((s: any) => ({
|
|
167
187
|
id: s.id,
|
|
168
188
|
name: s.name,
|
|
@@ -171,6 +191,10 @@ export async function listStates(
|
|
|
171
191
|
position: s.position,
|
|
172
192
|
}))
|
|
173
193
|
.sort((a: StateRow, b: StateRow) => a.position - b.position);
|
|
194
|
+
return setPaginationMetadata(
|
|
195
|
+
limit === Infinity ? rows : rows.slice(0, limit),
|
|
196
|
+
rows.length > limit,
|
|
197
|
+
);
|
|
174
198
|
}
|
|
175
199
|
|
|
176
200
|
export interface LabelRow {
|
|
@@ -179,7 +203,11 @@ export interface LabelRow {
|
|
|
179
203
|
color: string;
|
|
180
204
|
}
|
|
181
205
|
|
|
182
|
-
export const TEAM_LABEL_ROW_SHAPE = shape<LabelRow>({
|
|
206
|
+
export const TEAM_LABEL_ROW_SHAPE = shape<LabelRow>({
|
|
207
|
+
id: "string",
|
|
208
|
+
name: "string",
|
|
209
|
+
color: "string",
|
|
210
|
+
});
|
|
183
211
|
|
|
184
212
|
export async function listLabels(
|
|
185
213
|
client: LinearClient,
|
|
@@ -190,7 +218,10 @@ export async function listLabels(
|
|
|
190
218
|
const resolved = await resolveTeam(client, keyArg, defaultTeamKey);
|
|
191
219
|
const team = await withRetry(() => client.team(resolved.id));
|
|
192
220
|
const nodes = await collect((await withRetry(() => team.labels())) as any, limit);
|
|
193
|
-
return
|
|
221
|
+
return inheritPaginationMetadata(
|
|
222
|
+
nodes.map((l: any) => ({ id: l.id, name: l.name, color: l.color })),
|
|
223
|
+
nodes,
|
|
224
|
+
);
|
|
194
225
|
}
|
|
195
226
|
|
|
196
227
|
export interface CycleRow {
|
|
@@ -217,14 +248,20 @@ export async function listCycles(
|
|
|
217
248
|
): Promise<CycleRow[]> {
|
|
218
249
|
const resolved = await resolveTeam(client, keyArg, defaultTeamKey);
|
|
219
250
|
const team = await withRetry(() => client.team(resolved.id));
|
|
220
|
-
const nodes = await collect((await withRetry(() => team.cycles())) as any,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
251
|
+
const nodes = await collect((await withRetry(() => team.cycles())) as any, Infinity);
|
|
252
|
+
const rows = nodes
|
|
253
|
+
.map((c: any) => ({
|
|
254
|
+
id: c.id,
|
|
255
|
+
number: c.number,
|
|
256
|
+
name: c.name ?? null,
|
|
257
|
+
startsAt: c.startsAt?.toISOString?.() ?? (c.startsAt ? String(c.startsAt) : null),
|
|
258
|
+
endsAt: c.endsAt?.toISOString?.() ?? (c.endsAt ? String(c.endsAt) : null),
|
|
259
|
+
}))
|
|
260
|
+
.sort((a, b) => b.number - a.number);
|
|
261
|
+
return setPaginationMetadata(
|
|
262
|
+
limit === Infinity ? rows : rows.slice(0, limit),
|
|
263
|
+
rows.length > limit,
|
|
264
|
+
);
|
|
228
265
|
}
|
|
229
266
|
|
|
230
267
|
export interface CreateTeamOptions {
|
|
@@ -321,7 +358,10 @@ export async function moveTeamIssues(
|
|
|
321
358
|
to: ResolvedTeam,
|
|
322
359
|
): Promise<number> {
|
|
323
360
|
const team = await withRetry(() => client.team(from.id));
|
|
324
|
-
const issues = await collect(
|
|
361
|
+
const issues = await collect(
|
|
362
|
+
(await withRetry(() => team.issues({ first: 100 }))) as any,
|
|
363
|
+
Infinity,
|
|
364
|
+
);
|
|
325
365
|
const ids = issues.map((i: any) => i.id as string);
|
|
326
366
|
for (let i = 0; i < ids.length; i += MOVE_BATCH) {
|
|
327
367
|
const batch = ids.slice(i, i + MOVE_BATCH);
|
|
@@ -339,5 +379,8 @@ export async function moveTeamIssues(
|
|
|
339
379
|
* user as `feature_not_accessible` like every other plan gate.
|
|
340
380
|
*/
|
|
341
381
|
export async function deleteTeam(client: LinearClient, team: ResolvedTeam): Promise<void> {
|
|
342
|
-
await assertMutation(
|
|
382
|
+
await assertMutation(
|
|
383
|
+
withRetry(() => client.deleteTeam(team.id)),
|
|
384
|
+
"Team deletion",
|
|
385
|
+
);
|
|
343
386
|
}
|
package/src/services/user.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { LinearClient } from "@linear/sdk";
|
|
10
10
|
import { withRetry } from "../client.js";
|
|
11
11
|
import { shape } from "../lib/shape.js";
|
|
12
|
-
import { collect, pageSize } from "../lib/pagination.js";
|
|
12
|
+
import { collect, inheritPaginationMetadata, pageSize } from "../lib/pagination.js";
|
|
13
13
|
import { resolveUserId } from "../lib/resolve.js";
|
|
14
14
|
|
|
15
15
|
export interface UserRow {
|
|
@@ -45,7 +45,7 @@ export async function listUsers(
|
|
|
45
45
|
): Promise<UserRow[]> {
|
|
46
46
|
const conn = await withRetry(() => client.users({ first: pageSize(limit), includeDisabled }));
|
|
47
47
|
const nodes = await collect(conn as any, limit);
|
|
48
|
-
return nodes.map(toRow);
|
|
48
|
+
return inheritPaginationMetadata(nodes.map(toRow), nodes);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export interface UserDetail {
|
package/src/services/webhook.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
import { type LinearClient, WebhookResourceType } from "@linear/sdk";
|
|
10
10
|
import { withRetry } from "../client.js";
|
|
11
11
|
import { shape } from "../lib/shape.js";
|
|
12
|
-
import { collect } from "../lib/pagination.js";
|
|
13
|
-
import { usageError, notFound } from "../lib/errors.js";
|
|
12
|
+
import { collect, inheritPaginationMetadata, pageSize } from "../lib/pagination.js";
|
|
13
|
+
import { usageError, notFound, normalizeError } from "../lib/errors.js";
|
|
14
14
|
import { assertMutation, unwrapMutation } from "../lib/mutation.js";
|
|
15
15
|
import { resolveTeam } from "../lib/resolve.js";
|
|
16
16
|
|
|
@@ -56,17 +56,18 @@ export const WEBHOOK_ROW_SHAPE = shape<WebhookRow>({
|
|
|
56
56
|
|
|
57
57
|
/** All webhooks in the workspace (relation-light, so no N+1). */
|
|
58
58
|
export async function listWebhooks(client: LinearClient, limit: number): Promise<WebhookRow[]> {
|
|
59
|
-
const conn = await withRetry(() =>
|
|
60
|
-
client.webhooks({ first: limit === Infinity ? 100 : Math.min(limit, 100) }),
|
|
61
|
-
);
|
|
59
|
+
const conn = await withRetry(() => client.webhooks({ first: pageSize(limit) }));
|
|
62
60
|
const nodes = await collect(conn as any, limit);
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
return inheritPaginationMetadata(
|
|
62
|
+
nodes.map((w: any) => ({
|
|
63
|
+
id: w.id,
|
|
64
|
+
url: w.url ?? null,
|
|
65
|
+
enabled: w.enabled,
|
|
66
|
+
resourceTypes: w.resourceTypes ?? [],
|
|
67
|
+
label: w.label ?? null,
|
|
68
|
+
})),
|
|
69
|
+
nodes,
|
|
70
|
+
);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
export interface WebhookDetail {
|
|
@@ -174,7 +175,9 @@ export async function updateWebhook(client: LinearClient, id: string, opts: Upda
|
|
|
174
175
|
if (opts.secret !== undefined) input.secret = opts.secret;
|
|
175
176
|
|
|
176
177
|
if (Object.keys(input).length === 0)
|
|
177
|
-
throw usageError(
|
|
178
|
+
throw usageError(
|
|
179
|
+
"Nothing to update; pass at least one of --url, --enabled/--disabled, --resource.",
|
|
180
|
+
);
|
|
178
181
|
|
|
179
182
|
return unwrapMutation(
|
|
180
183
|
withRetry(() => client.updateWebhook(id, input as any)),
|
|
@@ -184,9 +187,17 @@ export async function updateWebhook(client: LinearClient, id: string, opts: Upda
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
export async function deleteWebhook(client: LinearClient, id: string): Promise<WebhookDetail> {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
let webhook: WebhookDetail;
|
|
191
|
+
try {
|
|
192
|
+
webhook = await getWebhookDetail(client, id);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
const normalized = normalizeError(err);
|
|
195
|
+
if (normalized.code === "not_found") throw notFound(`No webhook matching '${id}'.`);
|
|
196
|
+
throw normalized;
|
|
197
|
+
}
|
|
198
|
+
await assertMutation(
|
|
199
|
+
withRetry(() => client.deleteWebhook(id)),
|
|
200
|
+
"Webhook deletion",
|
|
201
|
+
);
|
|
191
202
|
return webhook;
|
|
192
203
|
}
|