forked from EverMind-AI/EverOS
-
Notifications
You must be signed in to change notification settings - Fork 1
124 lines (108 loc) · 4.23 KB
/
Copy pathlinear-sync.yml
File metadata and controls
124 lines (108 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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
119
120
121
122
123
124
name: Linear sync for tracking mirrors
on:
issues:
types: [opened, labeled]
permissions:
issues: write
contents: read
concurrency:
group: linear-sync-issue-${{ github.event.issue.number }}
cancel-in-progress: false
jobs:
sync:
if: |
contains(github.event.issue.labels.*.name, 'pr-mirror') &&
(github.event.action == 'opened' || github.event.label.name == 'pr-mirror')
runs-on: ubuntu-latest
steps:
- name: Mirror GitHub issue to Linear
uses: actions/github-script@v7
env:
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
LINEAR_TEAM_ID: ${{ vars.LINEAR_TEAM_ID }}
LINEAR_PROJECT_ID: ${{ vars.LINEAR_PROJECT_ID }}
with:
script: |
const issue = context.payload.issue;
const repo = `${context.repo.owner}/${context.repo.repo}`;
// Idempotency: skip if Linear marker comment already exists
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 100
});
if (comments.some(c => c.body.includes('🔗 Linear:'))) {
core.info('Already synced (marker comment present). Skipping.');
return;
}
const isUrgent = issue.labels.some(l => l.name === 'urgent');
const priority = isUrgent ? 1 : 3; // 1=Urgent, 3=Medium
const description = [
`**Source**: [${repo}#${issue.number}](${issue.html_url})`,
'',
'---',
'',
issue.body || '_(no body provided)_',
'',
'---',
'',
`_Auto-synced from GitHub by [linear-sync workflow](https://github.com/${repo}/actions)._`
].join('\n');
const mutation = `
mutation IssueCreate($input: IssueCreateInput!) {
issueCreate(input: $input) {
success
issue { id identifier url }
}
}
`;
const response = await fetch('https://api.linear.app/graphql', {
method: 'POST',
headers: {
'Authorization': process.env.LINEAR_API_KEY,
'Content-Type': 'application/json',
'x-apollo-operation-name': 'IssueCreate'
},
body: JSON.stringify({
query: mutation,
variables: {
input: {
title: issue.title,
description: description,
teamId: process.env.LINEAR_TEAM_ID,
projectId: process.env.LINEAR_PROJECT_ID,
priority: priority
}
}
})
});
const data = await response.json();
if (!response.ok || data.errors || !data?.data?.issueCreate?.success) {
const errMsg = 'Linear API error: ' + JSON.stringify(data, null, 2);
core.error(errMsg);
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'sync-failed',
color: 'D93F0B',
description: 'Linear sync workflow failed for this issue'
});
} catch (e) { /* exists already */ }
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['sync-failed']
});
throw new Error(errMsg);
}
const linearIssue = data.data.issueCreate.issue;
core.info(`Created ${linearIssue.identifier} -> ${linearIssue.url}`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `🔗 Linear: [${linearIssue.identifier}](${linearIssue.url})\n\n_Auto-created by linear-sync workflow._`
});