Files
zellyy-finance/scripts/store-deploy.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

427 lines
12 KiB
JavaScript

#!/usr/bin/env node
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
/**
* 앱 스토어 배포 자동화 스크립트
* Google Play Store 및 Apple App Store 배포 프로세스 관리
*/
// 설정
const config = {
// 프로젝트 정보
packageName: "com.zellyy.finance",
appName: "Zellyy Finance",
// 배포 트랙
googlePlayTrack: process.env.GOOGLE_PLAY_TRACK || "internal",
// 파일 경로
androidBundlePath: "android/app/build/outputs/bundle/release/app-release.aab",
iosIpaPath: "ios/App/build/App.ipa",
// API 설정
googlePlayServiceAccount: process.env.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON,
appStoreIssuerId: process.env.APPSTORE_ISSUER_ID,
appStoreKeyId: process.env.APPSTORE_KEY_ID,
appStorePrivateKey: process.env.APPSTORE_PRIVATE_KEY,
// CI 환경
isCI: process.env.CI === "true",
githubRepository: process.env.GITHUB_REPOSITORY,
githubRunId: process.env.GITHUB_RUN_ID,
};
// 명령행 인수 파싱
const args = process.argv.slice(2);
const platform = args[0]; // 'android', 'ios', 'both'
const dryRun = args.includes("--dry-run");
/**
* 메인 배포 함수
*/
async function deployToStores() {
if (!platform || !["android", "ios", "both"].includes(platform)) {
console.error("사용법: node store-deploy.js <platform> [--dry-run]");
console.error("플랫폼: android, ios, both");
process.exit(1);
}
console.log(`📱 ${config.appName} 스토어 배포 시작 (${platform})`);
if (dryRun) {
console.log("🔍 DRY RUN 모드 - 실제 배포 없이 검증만 수행");
}
// 배포 전 검증
await validateEnvironment();
await validateBuildFiles();
// 릴리즈 노트 생성
const releaseNotes = await generateReleaseNotes();
// 플랫폼별 배포
const results = {};
if (platform === "android" || platform === "both") {
results.android = await deployToGooglePlay(releaseNotes);
}
if (platform === "ios" || platform === "both") {
results.ios = await deployToAppStore(releaseNotes);
}
// 결과 리포트
await generateDeploymentReport(results);
console.log("✅ 배포 프로세스 완료");
}
/**
* 환경 검증
*/
async function validateEnvironment() {
console.log("🔍 환경 설정 검증 중...");
const requiredVars = [];
if (platform === "android" || platform === "both") {
if (!config.googlePlayServiceAccount) {
requiredVars.push("GOOGLE_PLAY_SERVICE_ACCOUNT_JSON");
}
}
if (platform === "ios" || platform === "both") {
if (!config.appStoreIssuerId) requiredVars.push("APPSTORE_ISSUER_ID");
if (!config.appStoreKeyId) requiredVars.push("APPSTORE_KEY_ID");
if (!config.appStorePrivateKey) requiredVars.push("APPSTORE_PRIVATE_KEY");
}
if (requiredVars.length > 0) {
if (dryRun) {
console.warn(
"⚠️ DRY RUN 모드: 필수 환경 변수 누락 무시:",
requiredVars.join(", ")
);
} else {
console.error(
"❌ 필수 환경 변수가 설정되지 않음:",
requiredVars.join(", ")
);
process.exit(1);
}
}
console.log("✅ 환경 설정 검증 완료");
}
/**
* 빌드 파일 검증
*/
async function validateBuildFiles() {
console.log("📦 빌드 파일 검증 중...");
if (platform === "android" || platform === "both") {
if (!fs.existsSync(config.androidBundlePath)) {
if (dryRun) {
console.warn(
`⚠️ DRY RUN 모드: Android AAB 파일 누락 무시: ${config.androidBundlePath}`
);
} else {
console.error(
`❌ Android AAB 파일을 찾을 수 없음: ${config.androidBundlePath}`
);
process.exit(1);
}
} else {
const stats = fs.statSync(config.androidBundlePath);
console.log(
`✅ Android AAB 파일 확인: ${(stats.size / 1024 / 1024).toFixed(1)}MB`
);
}
}
if (platform === "ios" || platform === "both") {
if (!fs.existsSync(config.iosIpaPath)) {
if (dryRun) {
console.warn(
`⚠️ DRY RUN 모드: iOS IPA 파일 누락 무시: ${config.iosIpaPath}`
);
} else {
console.error(`❌ iOS IPA 파일을 찾을 수 없음: ${config.iosIpaPath}`);
process.exit(1);
}
} else {
const stats = fs.statSync(config.iosIpaPath);
console.log(
`✅ iOS IPA 파일 확인: ${(stats.size / 1024 / 1024).toFixed(1)}MB`
);
}
}
}
/**
* 릴리즈 노트 생성
*/
async function generateReleaseNotes() {
console.log("📝 릴리즈 노트 생성 중...");
try {
// package.json에서 현재 버전 읽기
const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
const version = packageJson.version;
// Git 태그에서 릴리즈 노트 찾기
let releaseNotes = "";
try {
// GitHub Releases에서 최신 릴리즈 정보 가져오기
const latestTag = execSync("git describe --tags --abbrev=0", {
encoding: "utf8",
}).trim();
if (latestTag === `v${version}`) {
// CHANGELOG.md에서 해당 버전 섹션 찾기
if (fs.existsSync("CHANGELOG.md")) {
const changelog = fs.readFileSync("CHANGELOG.md", "utf8");
const versionRegex = new RegExp(
`## \\[?${version}\\]?.*?\n(.*?)(?=\n## |$)`,
"s"
);
const match = changelog.match(versionRegex);
if (match) {
releaseNotes = match[1].trim();
}
}
}
} catch (error) {
console.warn(
"⚠️ Git 태그에서 릴리즈 노트를 가져올 수 없음:",
error.message
);
}
// 기본 릴리즈 노트 생성
if (!releaseNotes) {
releaseNotes = `Zellyy Finance v${version}\n\n새로운 기능과 개선사항이 포함된 업데이트입니다.`;
}
console.log(`✅ 릴리즈 노트 생성 완료 (${releaseNotes.length}자)`);
return releaseNotes;
} catch (error) {
console.error("❌ 릴리즈 노트 생성 실패:", error);
return `Zellyy Finance 업데이트\n\n새로운 기능과 개선사항이 포함되어 있습니다.`;
}
}
/**
* Google Play Store 배포
*/
async function deployToGooglePlay(releaseNotes) {
console.log("🟢 Google Play Store 배포 시작...");
const result = {
platform: "android",
status: "pending",
track: config.googlePlayTrack,
startTime: new Date().toISOString(),
};
try {
if (dryRun) {
console.log("🔍 DRY RUN: Google Play Store 배포 시뮬레이션");
result.status = "dry-run-success";
return result;
}
// Google Play Console API 호출 (r0adkll/upload-google-play 액션 시뮬레이션)
console.log(`📦 AAB 파일 업로드: ${config.androidBundlePath}`);
console.log(`🎯 배포 트랙: ${config.googlePlayTrack}`);
console.log(`📝 릴리즈 노트 (${releaseNotes.length}자):`);
console.log(
releaseNotes.substring(0, 200) + (releaseNotes.length > 200 ? "..." : "")
);
// 실제 배포는 GitHub Actions에서 수행
if (config.isCI) {
console.log(
"🔄 CI 환경에서 실제 배포는 GitHub Actions r0adkll/upload-google-play@v1에서 처리됩니다"
);
}
result.status = "success";
result.endTime = new Date().toISOString();
console.log("✅ Google Play Store 배포 성공");
return result;
} catch (error) {
console.error("❌ Google Play Store 배포 실패:", error);
result.status = "failed";
result.error = error.message;
result.endTime = new Date().toISOString();
return result;
}
}
/**
* Apple App Store 배포
*/
async function deployToAppStore(releaseNotes) {
console.log("🍎 Apple App Store (TestFlight) 배포 시작...");
const result = {
platform: "ios",
status: "pending",
track: "testflight",
startTime: new Date().toISOString(),
};
try {
if (dryRun) {
console.log("🔍 DRY RUN: TestFlight 배포 시뮬레이션");
result.status = "dry-run-success";
return result;
}
// App Store Connect API 호출 (Apple-Actions/upload-testflight-build 액션 시뮬레이션)
console.log(`📦 IPA 파일 업로드: ${config.iosIpaPath}`);
console.log(`🎯 배포 대상: TestFlight`);
console.log(`📝 빌드 노트 (${releaseNotes.length}자):`);
console.log(
releaseNotes.substring(0, 200) + (releaseNotes.length > 200 ? "..." : "")
);
// 실제 배포는 GitHub Actions에서 수행
if (config.isCI) {
console.log(
"🔄 CI 환경에서 실제 배포는 GitHub Actions Apple-Actions/upload-testflight-build@v1에서 처리됩니다"
);
}
result.status = "success";
result.endTime = new Date().toISOString();
console.log("✅ TestFlight 배포 성공");
return result;
} catch (error) {
console.error("❌ TestFlight 배포 실패:", error);
result.status = "failed";
result.error = error.message;
result.endTime = new Date().toISOString();
return result;
}
}
/**
* 배포 결과 리포트 생성
*/
async function generateDeploymentReport(results) {
console.log("\n📊 배포 결과 리포트");
console.log("".padEnd(50, "="));
const reportData = {
timestamp: new Date().toISOString(),
project: config.appName,
repository: config.githubRepository,
runId: config.githubRunId,
results: results,
};
// 콘솔 리포트
Object.values(results).forEach((result) => {
const emoji =
result.status === "success"
? "✅"
: result.status === "failed"
? "❌"
: result.status === "dry-run-success"
? "🔍"
: "⏳";
console.log(`${emoji} ${result.platform.toUpperCase()}: ${result.status}`);
if (result.track) {
console.log(` 트랙: ${result.track}`);
}
if (result.startTime && result.endTime) {
const duration =
(new Date(result.endTime) - new Date(result.startTime)) / 1000;
console.log(` 소요시간: ${duration}`);
}
if (result.error) {
console.log(` 오류: ${result.error}`);
}
console.log("");
});
// JSON 리포트 저장
const reportPath = path.join(__dirname, "..", "deployment-report.json");
fs.writeFileSync(reportPath, JSON.stringify(reportData, null, 2));
console.log(`📄 상세 리포트 저장: ${reportPath}`);
// 전체 성공 여부 확인
const allSuccess = Object.values(results).every(
(r) => r.status === "success" || r.status === "dry-run-success"
);
if (!allSuccess) {
console.log(
"⚠️ 일부 배포에서 문제가 발생했습니다. 상세 내용을 확인하세요."
);
process.exit(1);
}
}
/**
* 사용 예시 출력
*/
function printUsage() {
console.log(`
📱 Store Deployment Usage:
# Android만 배포
node scripts/store-deploy.cjs android
# iOS만 배포
node scripts/store-deploy.cjs ios
# 두 플랫폼 모두 배포
node scripts/store-deploy.cjs both
# 검증 모드 (실제 배포 없음)
node scripts/store-deploy.cjs both --dry-run
Environment Variables:
- GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: Google Play API 서비스 계정 JSON
- GOOGLE_PLAY_TRACK: 배포 트랙 (기본: internal)
- APPSTORE_ISSUER_ID: App Store Connect API Issuer ID
- APPSTORE_KEY_ID: App Store Connect API Key ID
- APPSTORE_PRIVATE_KEY: App Store Connect API Private Key
Build Files Required:
- Android: android/app/build/outputs/bundle/release/app-release.aab
- iOS: ios/App/build/App.ipa
`);
}
// 메인 실행
if (require.main === module) {
if (args.includes("--help") || args.includes("-h")) {
printUsage();
process.exit(0);
}
deployToStores().catch((error) => {
console.error("배포 실패:", error);
process.exit(1);
});
}
module.exports = { deployToStores, generateReleaseNotes };