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>
This commit is contained in:
345
scripts/notification-handler.cjs
Normal file
345
scripts/notification-handler.cjs
Normal file
@@ -0,0 +1,345 @@
|
||||
#!/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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user