Skip to content

강조색 계산함수 제작 #185

강조색 계산함수 제작

강조색 계산함수 제작 #185

Workflow file for this run

name: Issue → Notion Sync
on:
issues:
types: [opened, labeled, unlabeled, milestoned, demilestoned, closed, reopened]
pull_request:
types: [closed]
jobs:
sync-to-notion:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }}
steps:
- name: Sync GitHub Issue to Notion
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const notionHeaders = {
"Authorization": `Bearer ${process.env.NOTION_TOKEN}`,
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
};
const issue = context.payload.issue;
const action = context.payload.action;
const createdDate = new Date(issue.created_at).toISOString().split('T')[0];
const targetMatch = issue.body?.match(/⏰ Target 날짜[\s\S]*?\n+(\d{4}-\d{2}-\d{2})/);
const targetDate = targetMatch ? targetMatch[1] : createdDate;
const milestone = issue.milestone ? issue.milestone.title : null;
const findNotionPage = async () => {
const res = await fetch("https://api.notion.com/v1/databases/" + process.env.NOTION_DATABASE_ID + "/query", {
method: "POST",
headers: notionHeaders,
body: JSON.stringify({
filter: {
property: "Issue Number",
rich_text: { equals: `#${issue.number}` }
}
})
});
const data = await res.json();
return data.results.length > 0 ? data.results[0].id : null;
};
if (action === "opened") {
const existingPageId = await findNotionPage();
if (existingPageId) {
console.log(`⚠️ 이미 등록된 이슈입니다: #${issue.number}`);
return;
}
const notionBody = {
parent: { database_id: process.env.NOTION_DATABASE_ID },
properties: {
Issue: {
title: [{ text: { content: issue.title } }]
},
"Issue Number": {
rich_text: [{ text: { content: `#${issue.number}` } }]
},
Date: {
date: { start: createdDate, end: targetDate }
},
Status: {
select: { name: "Todo" }
},
URL: {
url: issue.html_url
}
}
};
if (milestone) {
notionBody.properties.Milestone = {
rich_text: [{ text: { content: milestone } }]
};
}
const res = await fetch("https://api.notion.com/v1/pages", {
method: "POST",
headers: notionHeaders,
body: JSON.stringify(notionBody)
});
if (!res.ok) {
const error = await res.text();
throw new Error(`❌ Notion 등록 실패: ${res.status} ${error}`);
}
console.log(`✅ Notion에 등록 완료: ${issue.title}`);
}
if (action === "labeled" || action === "unlabeled") {
const label = context.payload.label.name;
const allowed = ["Todo", "In Progress", "Done"];
if (!allowed.includes(label)) return;
const pageId = await findNotionPage();
if (!pageId) return;
const res = await fetch("https://api.notion.com/v1/pages/" + pageId, {
method: "PATCH",
headers: notionHeaders,
body: JSON.stringify({
properties: {
Status: { select: { name: label } }
}
})
});
if (!res.ok) {
const error = await res.text();
throw new Error(`❌ Status 업데이트 실패: ${res.status} ${error}`);
}
console.log(`✅ Status 동기화 완료: ${label}`);
}
if (action === "milestoned" || action === "demilestoned") {
const milestoneName = issue.milestone ? issue.milestone.title : null;
const pageId = await findNotionPage();
if (!pageId) return;
const res = await fetch("https://api.notion.com/v1/pages/" + pageId, {
method: "PATCH",
headers: notionHeaders,
body: JSON.stringify({
properties: {
Milestone: milestoneName
? { rich_text: [{ text: { content: milestoneName } }] }
: { rich_text: [] }
}
})
});
if (!res.ok) {
const error = await res.text();
throw new Error(`❌ Milestone 업데이트 실패: ${res.status} ${error}`);
}
console.log(`✅ Milestone 업데이트 완료: ${milestoneName ?? "제거됨"}`);
}
if (action === "closed") {
const pageId = await findNotionPage();
if (!pageId) return;
const res = await fetch("https://api.notion.com/v1/pages/" + pageId, {
method: "PATCH",
headers: notionHeaders,
body: JSON.stringify({
properties: {
Status: { select: { name: "Done" } }
}
})
});
if (!res.ok) {
const error = await res.text();
throw new Error(`❌ 수동 종료 반영 실패: ${res.status} ${error}`);
}
console.log(`✅ 이슈 종료 → Status: Done`);
}
if (action === "reopened") {
const pageId = await findNotionPage();
if (!pageId) return;
const res = await fetch("https://api.notion.com/v1/pages/" + pageId, {
method: "PATCH",
headers: notionHeaders,
body: JSON.stringify({
properties: {
Status: { select: { name: "Todo" } }
}
})
});
if (!res.ok) {
const error = await res.text();
throw new Error(`❌ reopen 반영 실패: ${res.status} ${error}`);
}
console.log(`✅ reopen → Status: Todo 반영 완료`);
}
if (context.eventName === "pull_request" && context.payload.action === "closed") {
const pr = context.payload.pull_request;
const prBody = pr.body || "";
const prUrl = pr.html_url;
const issueMatches = prBody.match(/(?:Fixes|Closes|Resolves):? #\d+/gi);
if (!issueMatches) return;
for (const match of issueMatches) {
const issueNumber = match.match(/\d+/)[0];
const issueRes = await fetch(`https://api.github.com/repos/${context.repo.owner}/${context.repo.repo}/issues/${issueNumber}`, {
headers: { Authorization: `Bearer ${process.env.GH_TOKEN}` }
});
const issueData = await issueRes.json();
const notionSearch = await fetch("https://api.notion.com/v1/databases/" + process.env.NOTION_DATABASE_ID + "/query", {
method: "POST",
headers: notionHeaders,
body: JSON.stringify({
filter: {
property: "Issue Number",
rich_text: { equals: `#${issueNumber}` }
}
})
});
const notionResult = await notionSearch.json();
if (notionResult.results.length === 0) continue;
const pageId = notionResult.results[0].id;
const notionPage = await fetch(`https://api.notion.com/v1/pages/${pageId}`, {
method: "GET",
headers: notionHeaders
});
const notionData = await notionPage.json();
const existingPRText = notionData.properties?.PR_URL?.rich_text?.map(rt => rt.plain_text).join('\n') || "";
const updatedText = existingPRText
? `${existingPRText}\n${prUrl}`
: prUrl;
const updateRes = await fetch("https://api.notion.com/v1/pages/" + pageId, {
method: "PATCH",
headers: notionHeaders,
body: JSON.stringify({
properties: {
PR_URL: {
rich_text: updatedText.split('\n').map(url => ({ text: { content: url } }))
},
Status: {
select: { name: "Done" }
}
}
})
});
if (!updateRes.ok) {
const errorText = await updateRes.text();
throw new Error(`❌ PR URL 저장 실패: ${updateRes.status} ${errorText}`);
} else {
console.log(`✅ PR URL 저장 완료: ${prUrl}`);
}
}
}