xref: /expo/tools/src/Linear.ts (revision 7195f9f8)
1import { Issue, IssueLabel, LinearClient, User, WorkflowState } from '@linear/sdk';
2import {
3  IssueCreateInput,
4  IssueFilter,
5  IssueLabelFilter,
6  UserFilter,
7} from '@linear/sdk/dist/_generated_documents';
8
9const linearClient = new LinearClient({
10  apiKey: process.env.LINEAR_API_KEY ?? '<LINEAR-API-KEY>',
11});
12
13export const ENG_TEAM_ID = 'e678ab8b-874f-4ee2-bf4b-6c0b60ac2743';
14
15/**
16 * Creates a new issue.
17 * Defaults teamId to the Engineering team.
18 */
19export async function createIssueAsync(
20  issueInput: Omit<IssueCreateInput, 'teamId'> & Partial<IssueCreateInput>
21) {
22  await linearClient.createIssue({
23    teamId: ENG_TEAM_ID,
24    ...issueInput,
25  });
26}
27
28/**
29 * Gets a label by name or creates it if it doesn't exist.
30 *
31 */
32export async function getOrCreateLabelAsync(
33  labelName: string,
34  teamId?: string
35): Promise<IssueLabel> {
36  const filter: IssueLabelFilter = { name: { eq: labelName } };
37  if (teamId) {
38    filter.team = { id: { eq: teamId } };
39  }
40
41  const labels = await linearClient.issueLabels({ filter });
42  if (labels.nodes[0]) {
43    return labels.nodes[0];
44  }
45
46  const payload = await linearClient.createIssueLabel({ name: labelName });
47  const label = await payload.issueLabel;
48
49  if (!label) {
50    throw new Error(`Failed to create Linear label: ${labelName}`);
51  }
52
53  return label;
54}
55
56/**
57 * Gets a workflow state by name and team ID.
58 */
59export async function getTeamWorkflowStateAsync(
60  workflowState: string,
61  teamId: string
62): Promise<WorkflowState> {
63  const team = await linearClient.team(teamId);
64  const states = await team.states({ filter: { name: { eq: workflowState } } });
65  const state = states.nodes?.[0];
66
67  if (!state) {
68    throw new Error(`Failed to find Linear state: ${state}`);
69  }
70
71  return state;
72}
73
74/**
75 * Gets a workflow state by name and team ID.
76 */
77export async function getTeamMembersAsync({
78  filter,
79  teamId,
80}: {
81  filter?: UserFilter;
82  teamId: string;
83}): Promise<User[]> {
84  const team = await linearClient.team(teamId);
85  const states = await team.members({ filter });
86
87  return states.nodes;
88}
89
90/**
91 * Gets issues by filter and team ID.
92 */
93export async function getIssuesAsync({
94  filter,
95  teamId,
96}: {
97  filter?: IssueFilter;
98  teamId: string;
99}): Promise<Issue[]> {
100  const team = await linearClient.team(teamId);
101  const issues = await team.issues({ filter });
102
103  return issues.nodes;
104}
105
106/**
107 * Updates the status of an issue to Done.
108 */
109export async function closeIssueAsync({
110  issueId,
111  teamId,
112}: {
113  issueId: string;
114  teamId: string;
115}): Promise<boolean> {
116  const doneWorkflowState = await getTeamWorkflowStateAsync('Done', teamId);
117  const payload = await linearClient.updateIssue(issueId, { stateId: doneWorkflowState.id });
118
119  return payload.success;
120}
121
122/**
123 * Creates a comment on an issue.
124 */
125export async function commentIssueAsync({
126  issueId,
127  comment,
128}: {
129  issueId: string;
130  comment: string;
131}): Promise<boolean> {
132  const payload = await linearClient.createComment({ issueId, body: comment });
133
134  return payload.success;
135}
136