1import { Command } from '@expo/commander'; 2 3import * as GitHub from '../GitHub'; 4import * as Linear from '../Linear'; 5import logger from '../Logger'; 6 7type ActionOptions = { 8 issue: string; 9}; 10 11export default (program: Command) => { 12 program 13 .command('close-linear-issue-from-github') 14 .alias('clifg') 15 .description('Close a Linear issue imported from GitHub.') 16 .option('-i, --issue <string>', 'Number of the original GitHub issue.') 17 .asyncAction(action); 18}; 19 20async function action(options: ActionOptions) { 21 if (isNaN(Number(options.issue))) { 22 throw new Error('Flag `--issue` must be provided with a number value'); 23 } 24 if (!process.env.GITHUB_TOKEN) { 25 throw new Error('Environment variable `GITHUB_TOKEN` is required for this command.'); 26 } 27 if (!process.env.LINEAR_API_KEY) { 28 throw new Error('Environment variable `LINEAR_API_KEY` is required for this command.'); 29 } 30 31 try { 32 await closeIssueAsync(+options.issue); 33 } catch (error) { 34 logger.error(error); 35 throw error; 36 } 37} 38 39async function closeIssueAsync(githubIssueNumber: number) { 40 const linearIssues = await Linear.getIssuesAsync({ 41 teamId: Linear.ENG_TEAM_ID, 42 filter: { 43 description: { 44 containsIgnoreCase: `https://github.com/expo/expo/issues/${githubIssueNumber}`, 45 }, 46 labels: { 47 some: { 48 name: { eq: 'GitHub' }, 49 }, 50 }, 51 }, 52 }); 53 const linearIssue = linearIssues?.[0]; 54 55 if (!linearIssue) { 56 throw new Error( 57 `Unable to find a Linear issue referring to the Github issue #${githubIssueNumber}.` 58 ); 59 } 60 61 await Linear.closeIssueAsync({ issueId: linearIssue.id, teamId: Linear.ENG_TEAM_ID }); 62 63 const issueCloserPR = await GitHub.getIssueCloserPrUrlAsync(githubIssueNumber); 64 65 if (issueCloserPR) { 66 await Linear.commentIssueAsync({ 67 issueId: linearIssue.id, 68 comment: `This issue was automatically marked as done by ${issueCloserPR}`, 69 }); 70 } 71} 72