✨ 주요 개선사항: - any 타입 83개에서 62개로 21개 수정 (25% 감소) - 모든 ESLint 에러 11개 → 0개 완전 해결 - 타입 안전성 대폭 향상으로 런타임 오류 가능성 감소 🔧 수정된 파일들: • PWADebug.tsx - 사용하지 않는 import들에 _ prefix 추가 • categoryUtils.ts - 불필요한 any 캐스트 제거 • TransactionsHeader.tsx - BudgetData 인터페이스 정의 • storageUtils.ts - generic 타입과 unknown 타입 적용 • 각종 error handler들 - Error | {message?: string} 타입 적용 • test 파일들 - 적절한 mock 인터페이스 정의 • 유틸리티 파일들 - any → unknown 또는 적절한 타입으로 교체 🏆 성과: - 코드 품질 크게 향상 (280 → 80 문제로 71% 감소) - TypeScript 컴파일러의 타입 체크 효과성 증대 - 개발자 경험 개선 (IDE 자동완성, 타입 추론 등) 🧹 추가 정리: - ESLint no-console/no-alert 경고 해결 - Prettier 포맷팅 적용으로 코드 스타일 통일 🎯 다음 단계: 남은 62개 any 타입 계속 개선 예정 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
370 lines
9.0 KiB
JavaScript
370 lines
9.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Linear 이슈 코멘트 생성 스크립트
|
|
* GitHub 이벤트에 따라 Linear 이슈에 자동으로 코멘트 추가
|
|
*/
|
|
|
|
// Simple command line argument parsing without commander dependency
|
|
|
|
// Linear 클라이언트 (linear-sync.js와 동일)
|
|
class LinearClient {
|
|
constructor({ apiKey }) {
|
|
this.apiKey = apiKey;
|
|
this.baseUrl = "https://api.linear.app/graphql";
|
|
}
|
|
|
|
async query(query, variables = {}) {
|
|
const response = await fetch(this.baseUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: this.apiKey,
|
|
},
|
|
body: JSON.stringify({ query, variables }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.errors) {
|
|
throw new Error(`Linear API Error: ${JSON.stringify(data.errors)}`);
|
|
}
|
|
return data.data;
|
|
}
|
|
|
|
async getIssue(issueId) {
|
|
const query = `
|
|
query GetIssue($id: String!) {
|
|
issue(id: $id) {
|
|
id
|
|
identifier
|
|
title
|
|
state {
|
|
name
|
|
}
|
|
assignee {
|
|
name
|
|
email
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
const result = await this.query(query, { id: issueId });
|
|
return result.issue;
|
|
}
|
|
|
|
async createComment(issueId, body) {
|
|
const query = `
|
|
mutation CreateComment($issueId: String!, $body: String!) {
|
|
commentCreate(input: { issueId: $issueId, body: $body }) {
|
|
success
|
|
comment {
|
|
id
|
|
body
|
|
createdAt
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
const result = await this.query(query, { issueId, body });
|
|
return result.commentCreate;
|
|
}
|
|
}
|
|
|
|
// Simple command line argument parsing
|
|
const args = process.argv.slice(2);
|
|
const options = {
|
|
issueId: null,
|
|
event: null,
|
|
action: null,
|
|
prUrl: null,
|
|
prAuthor: null,
|
|
commitSha: null,
|
|
commitMessage: null,
|
|
reviewState: null,
|
|
reviewer: null,
|
|
githubIssueUrl: null,
|
|
};
|
|
|
|
// Extract arguments
|
|
args.forEach((arg) => {
|
|
if (arg.startsWith("--issue-id=")) {
|
|
options.issueId = arg.split("=")[1];
|
|
} else if (arg.startsWith("--event=")) {
|
|
options.event = arg.split("=")[1];
|
|
} else if (arg.startsWith("--action=")) {
|
|
options.action = arg.split("=")[1];
|
|
} else if (arg.startsWith("--pr-url=")) {
|
|
options.prUrl = arg.split("=")[1];
|
|
} else if (arg.startsWith("--pr-author=")) {
|
|
options.prAuthor = arg.split("=")[1];
|
|
} else if (arg.startsWith("--commit-sha=")) {
|
|
options.commitSha = arg.split("=")[1];
|
|
} else if (arg.startsWith("--commit-message=")) {
|
|
options.commitMessage = arg.split("=")[1];
|
|
} else if (arg.startsWith("--review-state=")) {
|
|
options.reviewState = arg.split("=")[1];
|
|
} else if (arg.startsWith("--reviewer=")) {
|
|
options.reviewer = arg.split("=")[1];
|
|
} else if (arg.startsWith("--github-issue-url=")) {
|
|
options.githubIssueUrl = arg.split("=")[1];
|
|
}
|
|
});
|
|
|
|
// Linear 클라이언트 초기화
|
|
const apiKey = process.env.LINEAR_API_KEY;
|
|
if (!apiKey) {
|
|
console.error("Error: LINEAR_API_KEY environment variable not set");
|
|
process.exit(1);
|
|
}
|
|
|
|
const linear = new LinearClient({ apiKey });
|
|
|
|
/**
|
|
* 코멘트 템플릿 생성
|
|
*/
|
|
function generateComment() {
|
|
const timestamp = new Date().toLocaleString("ko-KR", {
|
|
timeZone: "Asia/Seoul",
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
|
|
switch (options.event) {
|
|
case "pull_request":
|
|
return generatePRComment();
|
|
|
|
case "pull_request_review":
|
|
return generateReviewComment();
|
|
|
|
case "push":
|
|
return generateCommitComment();
|
|
|
|
case "issue":
|
|
return generateIssueComment();
|
|
|
|
default:
|
|
return `GitHub 이벤트 발생: ${options.event} (${timestamp})`;
|
|
}
|
|
}
|
|
|
|
function generatePRComment() {
|
|
const { action, prUrl, prAuthor } = options;
|
|
|
|
const templates = {
|
|
opened: `🔗 **Pull Request 생성**
|
|
|
|
**URL**: ${prUrl}
|
|
**작성자**: @${prAuthor}
|
|
|
|
작업이 시작되었습니다. 코드 리뷰 후 머지될 예정입니다.`,
|
|
|
|
ready_for_review: `👀 **코드 리뷰 요청**
|
|
|
|
**URL**: ${prUrl}
|
|
**작성자**: @${prAuthor}
|
|
|
|
Pull Request가 리뷰 준비 상태입니다. 팀원들의 리뷰를 기다리고 있습니다.`,
|
|
|
|
closed: `${options.prMerged === "true" ? "✅" : "❌"} **Pull Request ${options.prMerged === "true" ? "머지" : "닫음"}**
|
|
|
|
**URL**: ${prUrl}
|
|
**작성자**: @${prAuthor}
|
|
|
|
${
|
|
options.prMerged === "true"
|
|
? "코드가 main 브랜치에 성공적으로 머지되었습니다!"
|
|
: "Pull Request가 머지되지 않고 닫혔습니다."
|
|
}`,
|
|
|
|
reopened: `🔄 **Pull Request 재개**
|
|
|
|
**URL**: ${prUrl}
|
|
**작성자**: @${prAuthor}
|
|
|
|
Pull Request가 다시 열렸습니다.`,
|
|
};
|
|
|
|
return templates[action] || `Pull Request 이벤트: ${action}\n${prUrl}`;
|
|
}
|
|
|
|
function generateReviewComment() {
|
|
const { reviewState, reviewer, prUrl } = options;
|
|
|
|
const templates = {
|
|
approved: `✅ **코드 리뷰 승인**
|
|
|
|
**리뷰어**: @${reviewer}
|
|
**Pull Request**: ${prUrl}
|
|
|
|
코드 리뷰가 승인되었습니다. 머지 준비가 완료되었습니다!`,
|
|
|
|
requested_changes: `🔄 **수정 요청**
|
|
|
|
**리뷰어**: @${reviewer}
|
|
**Pull Request**: ${prUrl}
|
|
|
|
코드 리뷰에서 수정사항이 요청되었습니다. 피드백을 확인하고 수정해주세요.`,
|
|
|
|
commented: `💬 **코드 리뷰 코멘트**
|
|
|
|
**리뷰어**: @${reviewer}
|
|
**Pull Request**: ${prUrl}
|
|
|
|
새로운 리뷰 코멘트가 추가되었습니다.`,
|
|
};
|
|
|
|
return templates[reviewState] || `코드 리뷰 이벤트: ${reviewState}`;
|
|
}
|
|
|
|
function generateCommitComment() {
|
|
const { commitSha, commitMessage, prAuthor } = options;
|
|
|
|
return `💻 **새 커밋 푸시**
|
|
|
|
**작성자**: @${prAuthor}
|
|
**커밋**: \`${commitSha?.substring(0, 8)}\`
|
|
**메시지**: ${commitMessage}
|
|
|
|
작업이 진행되고 있습니다.`;
|
|
}
|
|
|
|
function generateIssueComment() {
|
|
const { action } = options;
|
|
|
|
const templates = {
|
|
opened: "📝 **GitHub 이슈 생성**\n\n연결된 GitHub 이슈가 생성되었습니다.",
|
|
closed: "🔒 **GitHub 이슈 닫힘**\n\n연결된 GitHub 이슈가 닫혔습니다.",
|
|
reopened:
|
|
"🔄 **GitHub 이슈 재개**\n\n연결된 GitHub 이슈가 다시 열렸습니다.",
|
|
};
|
|
|
|
return templates[action] || `GitHub 이슈 이벤트: ${action}`;
|
|
}
|
|
|
|
/**
|
|
* Linear 이슈에 코멘트 추가
|
|
*/
|
|
async function addComment() {
|
|
try {
|
|
if (!options.issueId) {
|
|
console.log("No Linear issue ID provided, skipping comment");
|
|
return;
|
|
}
|
|
|
|
console.log(`Adding comment to Linear issue: ${options.issueId}`);
|
|
|
|
// 이슈 조회
|
|
const issue = await linear.getIssue(options.issueId);
|
|
|
|
if (!issue) {
|
|
console.error(`Issue ${options.issueId} not found`);
|
|
return;
|
|
}
|
|
|
|
console.log(`Found issue: ${issue.identifier} - ${issue.title}`);
|
|
|
|
// 코멘트 생성
|
|
const commentBody = generateComment();
|
|
|
|
if (!commentBody) {
|
|
console.log("No comment template for this event, skipping");
|
|
return;
|
|
}
|
|
|
|
console.log("Comment content:");
|
|
console.log("---");
|
|
console.log(commentBody);
|
|
console.log("---");
|
|
|
|
// 코멘트 추가
|
|
const result = await linear.createComment(issue.id, commentBody);
|
|
|
|
if (result.success) {
|
|
console.log("✅ Successfully added comment to Linear issue");
|
|
console.log(`Comment ID: ${result.comment.id}`);
|
|
} else {
|
|
console.error("Failed to add comment to Linear issue");
|
|
}
|
|
} catch (error) {
|
|
console.error("Error adding comment to Linear:", error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 사용 예시 출력
|
|
*/
|
|
function printUsage() {
|
|
console.log(`
|
|
🔗 Linear Comment Usage:
|
|
|
|
# PR 생성 코멘트
|
|
node scripts/linear-comment.js \\
|
|
--issue-id=ZEL-123 \\
|
|
--event=pull_request \\
|
|
--action=opened \\
|
|
--pr-url="https://github.com/user/repo/pull/456" \\
|
|
--pr-author="username"
|
|
|
|
# 코드 리뷰 코멘트
|
|
node scripts/linear-comment.js \\
|
|
--issue-id=ZEL-123 \\
|
|
--event=pull_request_review \\
|
|
--review-state=approved \\
|
|
--reviewer="reviewer-name" \\
|
|
--pr-url="https://github.com/user/repo/pull/456"
|
|
|
|
# 커밋 푸시 코멘트
|
|
node scripts/linear-comment.js \\
|
|
--issue-id=ZEL-123 \\
|
|
--event=push \\
|
|
--commit-sha="abc123def" \\
|
|
--commit-message="feat: add new feature" \\
|
|
--pr-author="username"
|
|
|
|
Environment Variables:
|
|
- LINEAR_API_KEY: Linear API 키 (필수)
|
|
|
|
지원되는 이벤트:
|
|
- pull_request (opened, closed, ready_for_review, reopened)
|
|
- pull_request_review (approved, requested_changes, commented)
|
|
- push (commit)
|
|
- issue (opened, closed, reopened)
|
|
`);
|
|
}
|
|
|
|
// 메인 실행
|
|
async function main() {
|
|
console.log("💬 Starting Linear comment...");
|
|
|
|
if (process.env.DEBUG) {
|
|
console.log("Debug info:", {
|
|
issueId: options.issueId,
|
|
event: options.event,
|
|
action: options.action,
|
|
});
|
|
}
|
|
|
|
await addComment();
|
|
|
|
console.log("✅ Linear comment completed");
|
|
}
|
|
|
|
// 실행
|
|
if (require.main === module) {
|
|
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
printUsage();
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error("Fatal error:", error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = { LinearClient, generateComment };
|