✨ 주요 개선사항: - 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>
346 lines
8.7 KiB
JavaScript
346 lines
8.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const { execSync } = require("child_process");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
/**
|
|
* 빌드 결과 알림 핸들러
|
|
* GitHub Actions, Slack, 이메일 등 다양한 알림 채널 지원
|
|
*/
|
|
|
|
// 설정
|
|
const config = {
|
|
// GitHub Actions 환경 변수
|
|
isCI: process.env.CI === "true",
|
|
githubRepository: process.env.GITHUB_REPOSITORY,
|
|
githubRunId: process.env.GITHUB_RUN_ID,
|
|
githubWorkflow: process.env.GITHUB_WORKFLOW,
|
|
githubActor: process.env.GITHUB_ACTOR,
|
|
githubRef: process.env.GITHUB_REF,
|
|
|
|
// Slack 설정 (환경 변수에서)
|
|
slackWebhookUrl: process.env.SLACK_WEBHOOK_URL,
|
|
slackChannel: process.env.SLACK_CHANNEL || "#deployments",
|
|
|
|
// 이메일 설정
|
|
notificationEmail: process.env.NOTIFICATION_EMAIL,
|
|
|
|
// 알림 레벨
|
|
notifyOnSuccess: process.env.NOTIFY_ON_SUCCESS !== "false",
|
|
notifyOnFailure: process.env.NOTIFY_ON_FAILURE !== "false",
|
|
notifyOnWarning: process.env.NOTIFY_ON_WARNING !== "false",
|
|
};
|
|
|
|
// 명령행 인수 파싱
|
|
const args = process.argv.slice(2);
|
|
const event = args[0]; // 'success', 'failure', 'warning'
|
|
const message = args[1] || "";
|
|
const details = args[2] || "";
|
|
|
|
/**
|
|
* 메인 알림 처리 함수
|
|
*/
|
|
async function handleNotification() {
|
|
if (!event) {
|
|
console.error(
|
|
"사용법: node notification-handler.js <event> [message] [details]"
|
|
);
|
|
console.error("이벤트: success, failure, warning");
|
|
process.exit(1);
|
|
}
|
|
|
|
// 알림 여부 확인
|
|
const shouldNotify =
|
|
(event === "success" && config.notifyOnSuccess) ||
|
|
(event === "failure" && config.notifyOnFailure) ||
|
|
(event === "warning" && config.notifyOnWarning);
|
|
|
|
if (!shouldNotify) {
|
|
console.log(`${event} 이벤트에 대한 알림이 비활성화되어 있습니다.`);
|
|
return;
|
|
}
|
|
|
|
console.log(`🔔 ${event} 알림 처리 시작...`);
|
|
|
|
// 알림 메시지 생성
|
|
const notification = createNotificationMessage(event, message, details);
|
|
|
|
// 다양한 채널로 알림 전송
|
|
const results = await Promise.allSettled([
|
|
sendSlackNotification(notification),
|
|
sendGitHubNotification(notification),
|
|
sendEmailNotification(notification),
|
|
]);
|
|
|
|
// 결과 출력
|
|
results.forEach((result, index) => {
|
|
const channels = ["Slack", "GitHub", "Email"];
|
|
if (result.status === "fulfilled") {
|
|
console.log(`✅ ${channels[index]} 알림 전송 성공`);
|
|
} else {
|
|
console.log(`❌ ${channels[index]} 알림 전송 실패:`, result.reason);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 알림 메시지 생성
|
|
*/
|
|
function createNotificationMessage(event, message, details) {
|
|
const timestamp = new Date().toLocaleString("ko-KR");
|
|
const projectName = "Zellyy Finance";
|
|
|
|
// 이벤트별 이모지와 색상
|
|
const eventConfig = {
|
|
success: { emoji: "✅", color: "good", level: "SUCCESS" },
|
|
failure: { emoji: "❌", color: "danger", level: "FAILURE" },
|
|
warning: { emoji: "⚠️", color: "warning", level: "WARNING" },
|
|
};
|
|
|
|
const { emoji, color, level } = eventConfig[event] || eventConfig.warning;
|
|
|
|
// 기본 메시지 구성
|
|
const baseMessage = {
|
|
event,
|
|
level,
|
|
emoji,
|
|
color,
|
|
timestamp,
|
|
projectName,
|
|
message: message || `빌드 ${level}`,
|
|
details: details || "",
|
|
};
|
|
|
|
// CI 환경 정보 추가
|
|
if (config.isCI) {
|
|
baseMessage.ci = {
|
|
repository: config.githubRepository,
|
|
workflow: config.githubWorkflow,
|
|
runId: config.githubRunId,
|
|
actor: config.githubActor,
|
|
ref: config.githubRef,
|
|
runUrl: `https://github.com/${config.githubRepository}/actions/runs/${config.githubRunId}`,
|
|
};
|
|
}
|
|
|
|
return baseMessage;
|
|
}
|
|
|
|
/**
|
|
* Slack 알림 전송
|
|
*/
|
|
async function sendSlackNotification(notification) {
|
|
if (!config.slackWebhookUrl) {
|
|
throw new Error("Slack webhook URL이 설정되지 않음");
|
|
}
|
|
|
|
const slackMessage = {
|
|
channel: config.slackChannel,
|
|
username: "Zellyy Finance CI/CD",
|
|
icon_emoji: ":rocket:",
|
|
attachments: [
|
|
{
|
|
color: notification.color,
|
|
title: `${notification.emoji} ${notification.projectName} - ${notification.level}`,
|
|
text: notification.message,
|
|
fields: [
|
|
{
|
|
title: "Timestamp",
|
|
value: notification.timestamp,
|
|
short: true,
|
|
},
|
|
],
|
|
footer: "Zellyy Finance CI/CD",
|
|
},
|
|
],
|
|
};
|
|
|
|
// CI 정보 추가
|
|
if (notification.ci) {
|
|
slackMessage.attachments[0].fields.push(
|
|
{
|
|
title: "Repository",
|
|
value: notification.ci.repository,
|
|
short: true,
|
|
},
|
|
{
|
|
title: "Workflow",
|
|
value: notification.ci.workflow,
|
|
short: true,
|
|
},
|
|
{
|
|
title: "Triggered by",
|
|
value: notification.ci.actor,
|
|
short: true,
|
|
},
|
|
{
|
|
title: "Branch/Tag",
|
|
value: notification.ci.ref
|
|
.replace("refs/heads/", "")
|
|
.replace("refs/tags/", ""),
|
|
short: true,
|
|
}
|
|
);
|
|
|
|
slackMessage.attachments[0].actions = [
|
|
{
|
|
type: "button",
|
|
text: "View Run",
|
|
url: notification.ci.runUrl,
|
|
},
|
|
];
|
|
}
|
|
|
|
// 세부 정보 추가
|
|
if (notification.details) {
|
|
slackMessage.attachments[0].fields.push({
|
|
title: "Details",
|
|
value: notification.details,
|
|
short: false,
|
|
});
|
|
}
|
|
|
|
// Slack API 호출
|
|
try {
|
|
const response = await fetch(config.slackWebhookUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(slackMessage),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Slack API 오류: ${response.status} ${response.statusText}`
|
|
);
|
|
}
|
|
|
|
return { success: true, channel: "Slack" };
|
|
} catch (error) {
|
|
throw new Error(`Slack 전송 실패: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GitHub 알림 (GitHub Actions 환경에서만)
|
|
*/
|
|
async function sendGitHubNotification(notification) {
|
|
if (!config.isCI) {
|
|
throw new Error("GitHub 알림은 CI 환경에서만 지원");
|
|
}
|
|
|
|
// GitHub Actions 출력 형식
|
|
const githubMessage = `::${notification.event}::${notification.emoji} ${notification.message}`;
|
|
|
|
console.log(githubMessage);
|
|
|
|
// Job Summary 생성
|
|
const summary = `
|
|
## ${notification.emoji} ${notification.projectName} - ${notification.level}
|
|
|
|
**Message:** ${notification.message}
|
|
|
|
**Timestamp:** ${notification.timestamp}
|
|
|
|
${notification.details ? `**Details:**\n${notification.details}` : ""}
|
|
|
|
${
|
|
notification.ci
|
|
? `**Run Details:**
|
|
- Repository: ${notification.ci.repository}
|
|
- Workflow: ${notification.ci.workflow}
|
|
- Triggered by: ${notification.ci.actor}
|
|
- Branch/Tag: ${notification.ci.ref}
|
|
- [View Run](${notification.ci.runUrl})`
|
|
: ""
|
|
}
|
|
`;
|
|
|
|
// GITHUB_STEP_SUMMARY에 작성
|
|
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary);
|
|
}
|
|
|
|
return { success: true, channel: "GitHub" };
|
|
}
|
|
|
|
/**
|
|
* 이메일 알림 (선택사항)
|
|
*/
|
|
async function sendEmailNotification(notification) {
|
|
if (!config.notificationEmail) {
|
|
throw new Error("알림 이메일이 설정되지 않음");
|
|
}
|
|
|
|
// 간단한 로그만 출력 (실제 이메일 전송은 외부 서비스 필요)
|
|
console.log(`📧 이메일 알림 대상: ${config.notificationEmail}`);
|
|
console.log(
|
|
`제목: [${notification.projectName}] ${notification.level} - ${notification.message}`
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
channel: "Email",
|
|
note: "Log only (external service required)",
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 빌드 결과 파일에서 정보 읽기
|
|
*/
|
|
function readBuildResults() {
|
|
const resultsPath = path.join(__dirname, "..", "test-results.json");
|
|
|
|
if (fs.existsSync(resultsPath)) {
|
|
try {
|
|
const results = JSON.parse(fs.readFileSync(resultsPath, "utf8"));
|
|
return results;
|
|
} catch (error) {
|
|
console.warn("빌드 결과 파일 읽기 실패:", error.message);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 사용 예시 출력
|
|
*/
|
|
function printUsage() {
|
|
console.log(`
|
|
🔔 Notification Handler Usage:
|
|
|
|
# 성공 알림
|
|
node scripts/notification-handler.cjs success "빌드 성공" "모든 테스트 통과"
|
|
|
|
# 실패 알림
|
|
node scripts/notification-handler.cjs failure "빌드 실패" "3개 테스트 실패"
|
|
|
|
# 경고 알림
|
|
node scripts/notification-handler.cjs warning "빌드 완료 (경고)" "선택적 테스트 실패"
|
|
|
|
Environment Variables:
|
|
- SLACK_WEBHOOK_URL: Slack 웹훅 URL
|
|
- SLACK_CHANNEL: Slack 채널 (기본: #deployments)
|
|
- NOTIFICATION_EMAIL: 알림 이메일
|
|
- NOTIFY_ON_SUCCESS: 성공 시 알림 (기본: true)
|
|
- NOTIFY_ON_FAILURE: 실패 시 알림 (기본: true)
|
|
- NOTIFY_ON_WARNING: 경고 시 알림 (기본: true)
|
|
`);
|
|
}
|
|
|
|
// 메인 실행
|
|
if (require.main === module) {
|
|
if (args.includes("--help") || args.includes("-h")) {
|
|
printUsage();
|
|
process.exit(0);
|
|
}
|
|
|
|
handleNotification().catch((error) => {
|
|
console.error("알림 처리 실패:", error);
|
|
process.exit(1);
|
|
});
|
|
}
|