mirror of
https://github.com/cline/cline.git
synced 2026-09-12 00:50:27 +08:00
* add github action for creating linear tickets for unconnected PRs * changset * only load fetch if not present * omit fetch * add error handling * fix gql query * only run for opened PRs * break out into actions * fix folders * checkout first * remove the actions * add sync * remove sync
178 lines
7.8 KiB
YAML
178 lines
7.8 KiB
YAML
name: Create Linear Issue on Pull Request
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
pull_request:
|
|
branches: [main]
|
|
types: [opened]
|
|
|
|
permissions:
|
|
pull-requests: write
|
|
|
|
jobs:
|
|
create-linear-issue-on-pull-request:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check for existing Linear link
|
|
id: check-linear
|
|
uses: actions/github-script@v6
|
|
with:
|
|
result-encoding: string
|
|
script: |
|
|
const pr = context.payload.pull_request;
|
|
// 1) PR body
|
|
if (/https?:\/\/linear\.app/.test(pr.body||"")) {
|
|
return "true";
|
|
}
|
|
// 2) Any linked GitHub issues?
|
|
const res = await github.graphql(
|
|
`query($owner:String!,$repo:String!,$prNumber:Int!){
|
|
repository(owner:$owner,name:$repo){
|
|
pullRequest(number:$prNumber){
|
|
closingIssuesReferences(first:10){
|
|
nodes{number}
|
|
}
|
|
}
|
|
}
|
|
}`,
|
|
{
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
prNumber: pr.number
|
|
}
|
|
);
|
|
for (const {number} of res.repository.pullRequest.closingIssuesReferences.nodes) {
|
|
const comments = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: number
|
|
});
|
|
if (comments.data.some(c=>/https?:\/\/linear\.app/.test(c.body))) {
|
|
return "true";
|
|
}
|
|
}
|
|
return "false";
|
|
|
|
- name: Find or create Linear issue via GraphQL
|
|
if: steps.check-linear.outputs.result == 'false'
|
|
id: linear
|
|
uses: actions/github-script@v6
|
|
env:
|
|
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
|
with:
|
|
result-encoding: string
|
|
script: |
|
|
const API = 'https://api.linear.app/graphql';
|
|
const apiKey = process.env.LINEAR_API_KEY;
|
|
|
|
// Check if API key exists
|
|
if (!apiKey) {
|
|
core.setFailed('LINEAR_API_KEY is not set. Please add it to your repository secrets.');
|
|
core.setOutput('error', 'true');
|
|
core.setOutput('error-message', 'LINEAR_API_KEY is not set. Please add it to your repository secrets.');
|
|
return;
|
|
}
|
|
|
|
// Helper to call Linear with error handling
|
|
async function gql(q, v) {
|
|
try {
|
|
const r = await fetch(API, {
|
|
method:'POST',
|
|
headers:{
|
|
'Content-Type':'application/json',
|
|
'Authorization': apiKey
|
|
},
|
|
body: JSON.stringify({ query: q, variables: v })
|
|
});
|
|
|
|
if (!r.ok) {
|
|
throw new Error(`Linear API responded with status ${r.status}: ${await r.text()}`);
|
|
}
|
|
|
|
const json = await r.json();
|
|
|
|
// Check for GraphQL errors
|
|
if (json.errors && json.errors.length > 0) {
|
|
const errorMessages = json.errors.map(e => e.message).join(', ');
|
|
throw new Error(`Linear GraphQL errors: ${errorMessages}`);
|
|
}
|
|
|
|
return json.data;
|
|
} catch (error) {
|
|
core.error(`Error calling Linear API: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
try {
|
|
// 1) Set team ID
|
|
const teamId = "19b9c1b2-5f58-498c-b1bf-23ee8f52a677"
|
|
|
|
// 2) Look for existing issue by PR URL
|
|
const pr = context.payload.pull_request;
|
|
const searchData = await gql(
|
|
`query($team:ID!,$q:String!){
|
|
issues(filter: { team: { id: { eq: $team } } attachments: { some: { url: { eq: $q } } } }){nodes{id,url}}
|
|
}`,
|
|
{ team: teamId, q: pr.html_url }
|
|
);
|
|
let issue = searchData.issues.nodes[0];
|
|
|
|
// 3) Create if missing
|
|
if (!issue) {
|
|
const createData = await gql(
|
|
`mutation($input:IssueCreateInput!){
|
|
issueCreate(input:$input){issue{id,url}}
|
|
}`,
|
|
{
|
|
input: {
|
|
teamId,
|
|
title: `[GITHUB] ${pr.title}`,
|
|
description: `${pr.body||''}\n\n${pr.html_url}`,
|
|
stateId: "4d9bcba2-6712-47e3-b577-6ec1ee023dc2",
|
|
labelIds: ["504e7d60-5037-483f-a9b8-7e298bdf116f"]
|
|
}
|
|
}
|
|
);
|
|
issue = createData.issueCreate.issue;
|
|
}
|
|
|
|
// Set output for next steps
|
|
core.setOutput('linear-issue-url', issue.url);
|
|
core.setOutput('error', 'false');
|
|
} catch (error) {
|
|
core.setOutput('error', 'true');
|
|
core.setOutput('error-message', error.message);
|
|
core.setFailed(`Failed to create or find Linear issue: ${error.message}`);
|
|
}
|
|
|
|
- name: Comment PR with Linear link
|
|
if: steps.check-linear.outputs.result == 'false'
|
|
uses: actions/github-script@v6
|
|
with:
|
|
script: |
|
|
const pr = context.payload.pull_request;
|
|
const url = `${{ steps.linear.outputs.linear-issue-url }}`;
|
|
const body = `🔗 Linear issue created: ${url}`;
|
|
// Fetch existing comments
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
...context.repo,
|
|
issue_number: pr.number
|
|
});
|
|
const botComment = comments.find(c =>
|
|
c.user.type === "Bot" && c.body.startsWith("🔗 Linear issue created:")
|
|
);
|
|
if (botComment) {
|
|
await github.rest.issues.updateComment({
|
|
...context.repo,
|
|
comment_id: botComment.id,
|
|
body
|
|
});
|
|
} else {
|
|
await github.rest.issues.createComment({
|
|
...context.repo,
|
|
issue_number: pr.number,
|
|
body
|
|
});
|
|
}
|