Files
zellyy-finance/scripts/linear-comment.cjs
hansoo 8343b25439 feat: Stage 2 TypeScript 타입 안전성 개선 - any 타입 83개 → 62개 대폭 감소
 주요 개선사항:
- 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>
2025-07-14 10:08:51 +09:00

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 };