Files
zellyy-finance/scripts/linear-sync.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

276 lines
7.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Linear 이슈 상태 동기화 스크립트
* GitHub 이벤트에 따라 Linear 이슈 상태를 자동으로 업데이트
*/
// Simple command line argument parsing without commander dependency
// 임시 Linear 클라이언트 (실제 구현 시 SDK 사용)
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 {
id
name
}
team {
id
name
}
}
}
`;
const result = await this.query(query, { id: issueId });
return result.issue;
}
async updateIssue(issueId, input) {
const query = `
mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
issueUpdate(id: $id, input: $input) {
success
issue {
id
state {
name
}
}
}
}
`;
const result = await this.query(query, { id: issueId, input });
return result.issueUpdate;
}
async getWorkflowStates(teamId) {
const query = `
query GetWorkflowStates($teamId: ID!) {
workflowStates(filter: { team: { id: { eq: $teamId } } }) {
nodes {
id
name
type
}
}
}
`;
const result = await this.query(query, { teamId });
return result.workflowStates.nodes;
}
async createComment(issueId, body) {
const query = `
mutation CreateComment($issueId: String!, $body: String!) {
commentCreate(input: { issueId: $issueId, body: $body }) {
success
comment {
id
body
}
}
}
`;
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,
prMerged: "false",
apiKey: 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("--pr-merged=")) {
options.prMerged = arg.split("=")[1];
} else if (arg.startsWith("--api-key=")) {
options.apiKey = arg.split("=")[1];
}
});
// Linear 클라이언트 초기화
const apiKey = options.apiKey || process.env.LINEAR_API_KEY;
if (!apiKey) {
console.error("Error: Linear API key not provided");
process.exit(1);
}
const linear = new LinearClient({ apiKey });
/**
* 이슈 상태 업데이트
*/
async function updateIssueStatus() {
try {
if (!options.issueId) {
console.log("No Linear issue ID provided, skipping");
return;
}
console.log(`Processing 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}`);
console.log(`Current state: ${issue.state.name}`);
// 상태 변경 로직
let newStateName = null;
let comment = null;
if (options.event === "pull_request") {
switch (options.action) {
case "opened":
newStateName = "In Progress";
comment = `🔗 Pull Request opened: ${options.prUrl}\nAuthor: @${options.prAuthor}`;
break;
case "ready_for_review":
newStateName = "In Review";
comment = `👀 Pull Request is ready for review: ${options.prUrl}`;
break;
case "closed":
if (options.prMerged === "true") {
newStateName = "Done";
comment = `✅ Pull Request merged: ${options.prUrl}`;
} else {
comment = `❌ Pull Request closed without merging: ${options.prUrl}`;
}
break;
}
} else if (options.event === "push" && options.action === "commit") {
// 커밋 푸시 시 In Progress로 변경
if (issue.state.name === "Todo" || issue.state.name === "Backlog") {
newStateName = "In Progress";
comment = `💻 Work started with new commits`;
}
}
// 상태 업데이트 필요시
if (newStateName && newStateName !== issue.state.name) {
console.log(`Updating state: ${issue.state.name}${newStateName}`);
// 팀의 워크플로우 상태 조회
const states = await linear.getWorkflowStates(issue.team.id);
const targetState = states.find((s) => s.name === newStateName);
if (targetState) {
const result = await linear.updateIssue(issue.id, {
stateId: targetState.id,
});
if (result.success) {
console.log(
`✅ Successfully updated issue state to: ${newStateName}`
);
} else {
console.error("Failed to update issue state");
}
} else {
console.warn(`State "${newStateName}" not found in team workflow`);
}
}
// 코멘트 추가
if (comment) {
const commentResult = await linear.createComment(issue.id, comment);
if (commentResult.success) {
console.log("✅ Added comment to issue");
}
}
} catch (error) {
console.error("Error syncing with Linear:", error.message);
process.exit(1);
}
}
/**
* 디버그 정보 출력
*/
function debugInfo() {
console.log("\n=== Linear Sync Debug Info ===");
console.log("Options:", {
issueId: options.issueId,
event: options.event,
action: options.action,
prUrl: options.prUrl,
prAuthor: options.prAuthor,
prMerged: options.prMerged,
});
console.log("API Key:", apiKey ? "Provided" : "Missing");
console.log("==============================\n");
}
// 메인 실행
async function main() {
console.log("🔄 Starting Linear sync...");
if (process.env.DEBUG) {
debugInfo();
}
await updateIssueStatus();
console.log("✅ Linear sync completed");
}
// 실행
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});