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:
449
scripts/linear-workflow-tester.cjs
Normal file
449
scripts/linear-workflow-tester.cjs
Normal file
@@ -0,0 +1,449 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Linear GitHub Actions 워크플로우 테스터
|
||||
* GitHub Actions 환경을 시뮬레이션하여 Linear 통합을 테스트
|
||||
*/
|
||||
|
||||
const { execSync } = require("child_process");
|
||||
const path = require("path");
|
||||
|
||||
// Simple command line argument parsing
|
||||
const args = process.argv.slice(2);
|
||||
const options = {
|
||||
linearApiKey: null,
|
||||
testType: "all",
|
||||
issueId: "ZEL-1",
|
||||
prUrl: "https://github.com/zellycloud/zellyy-finance/pull/1",
|
||||
author: "hansoo",
|
||||
verbose: args.includes("--verbose") || args.includes("-v"),
|
||||
help: args.includes("--help") || args.includes("-h"),
|
||||
};
|
||||
|
||||
// Extract options from arguments
|
||||
const linearApiIndex = args.findIndex((arg) => arg.startsWith("--linear-api="));
|
||||
if (linearApiIndex !== -1) {
|
||||
options.linearApiKey = args[linearApiIndex].split("=")[1];
|
||||
}
|
||||
|
||||
const testTypeIndex = args.findIndex((arg) => arg.startsWith("--test-type="));
|
||||
if (testTypeIndex !== -1) {
|
||||
options.testType = args[testTypeIndex].split("=")[1];
|
||||
}
|
||||
|
||||
const issueIdIndex = args.findIndex((arg) => arg.startsWith("--issue-id="));
|
||||
if (issueIdIndex !== -1) {
|
||||
options.issueId = args[issueIdIndex].split("=")[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub Actions 이벤트 시뮬레이션
|
||||
*/
|
||||
const mockEvents = {
|
||||
pullRequest: {
|
||||
opened: {
|
||||
event: "pull_request",
|
||||
action: "opened",
|
||||
prUrl: options.prUrl,
|
||||
prAuthor: options.author,
|
||||
prMerged: "false",
|
||||
},
|
||||
closed: {
|
||||
event: "pull_request",
|
||||
action: "closed",
|
||||
prUrl: options.prUrl,
|
||||
prAuthor: options.author,
|
||||
prMerged: "true",
|
||||
},
|
||||
review: {
|
||||
event: "pull_request_review",
|
||||
reviewState: "approved",
|
||||
reviewer: "reviewer-user",
|
||||
prUrl: options.prUrl,
|
||||
},
|
||||
},
|
||||
push: {
|
||||
commit: {
|
||||
event: "push",
|
||||
action: "commit",
|
||||
commitSha: "abc123def456",
|
||||
commitMessage: `feat: implement feature [${options.issueId}]`,
|
||||
prAuthor: options.author,
|
||||
},
|
||||
},
|
||||
issue: {
|
||||
opened: {
|
||||
event: "issue",
|
||||
action: "opened",
|
||||
githubIssueUrl: "https://github.com/zellycloud/zellyy-finance/issues/1",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 스크립트 실행 함수
|
||||
*/
|
||||
async function runScript(scriptPath, args, description) {
|
||||
console.log(`\n🔧 ${description}`);
|
||||
console.log(` Script: ${scriptPath}`);
|
||||
console.log(` Args: ${args.join(" ")}`);
|
||||
|
||||
try {
|
||||
const command = `node ${scriptPath} ${args.join(" ")}`;
|
||||
const result = execSync(command, {
|
||||
encoding: "utf8",
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
LINEAR_API_KEY: options.linearApiKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (options.verbose) {
|
||||
console.log("📄 Output:");
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
console.log("✅ Success");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("❌ Failed:");
|
||||
console.error(" Error:", error.message);
|
||||
if (options.verbose && error.stdout) {
|
||||
console.error(" Output:", error.stdout);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull Request 이벤트 테스트
|
||||
*/
|
||||
async function testPullRequestFlow() {
|
||||
console.log("\n🔀 Testing Pull Request Flow");
|
||||
console.log("===============================");
|
||||
|
||||
const results = [];
|
||||
|
||||
// PR opened
|
||||
console.log("\n📝 1. Pull Request Opened");
|
||||
const openedEvent = mockEvents.pullRequest.opened;
|
||||
|
||||
// Sync PR status
|
||||
let success = await runScript(
|
||||
"scripts/linear-sync.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${openedEvent.event}`,
|
||||
`--action=${openedEvent.action}`,
|
||||
`--pr-url=${openedEvent.prUrl}`,
|
||||
`--pr-author=${openedEvent.prAuthor}`,
|
||||
`--pr-merged=${openedEvent.prMerged}`,
|
||||
],
|
||||
"Sync PR opened status"
|
||||
);
|
||||
results.push({ test: "PR Sync (opened)", success });
|
||||
|
||||
// Add PR comment
|
||||
success = await runScript(
|
||||
"scripts/linear-comment.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${openedEvent.event}`,
|
||||
`--action=${openedEvent.action}`,
|
||||
`--pr-url=${openedEvent.prUrl}`,
|
||||
`--pr-author=${openedEvent.prAuthor}`,
|
||||
],
|
||||
"Add PR comment"
|
||||
);
|
||||
results.push({ test: "PR Comment (opened)", success });
|
||||
|
||||
// PR review
|
||||
console.log("\n👁️ 2. Pull Request Review");
|
||||
const reviewEvent = mockEvents.pullRequest.review;
|
||||
|
||||
success = await runScript(
|
||||
"scripts/linear-comment.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${reviewEvent.event}`,
|
||||
`--review-state=${reviewEvent.reviewState}`,
|
||||
`--reviewer=${reviewEvent.reviewer}`,
|
||||
`--pr-url=${reviewEvent.prUrl}`,
|
||||
],
|
||||
"Add review comment"
|
||||
);
|
||||
results.push({ test: "PR Review Comment", success });
|
||||
|
||||
// PR merged
|
||||
console.log("\n✅ 3. Pull Request Merged");
|
||||
const closedEvent = mockEvents.pullRequest.closed;
|
||||
|
||||
success = await runScript(
|
||||
"scripts/linear-sync.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${closedEvent.event}`,
|
||||
`--action=${closedEvent.action}`,
|
||||
`--pr-url=${closedEvent.prUrl}`,
|
||||
`--pr-author=${closedEvent.prAuthor}`,
|
||||
`--pr-merged=${closedEvent.prMerged}`,
|
||||
],
|
||||
"Sync PR merged status"
|
||||
);
|
||||
results.push({ test: "PR Sync (merged)", success });
|
||||
|
||||
success = await runScript(
|
||||
"scripts/linear-comment.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${closedEvent.event}`,
|
||||
`--action=${closedEvent.action}`,
|
||||
`--pr-url=${closedEvent.prUrl}`,
|
||||
`--pr-author=${closedEvent.prAuthor}`,
|
||||
],
|
||||
"Add merge comment"
|
||||
);
|
||||
results.push({ test: "PR Comment (merged)", success });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push 이벤트 테스트
|
||||
*/
|
||||
async function testPushFlow() {
|
||||
console.log("\n🚀 Testing Push Flow");
|
||||
console.log("====================");
|
||||
|
||||
const results = [];
|
||||
const pushEvent = mockEvents.push.commit;
|
||||
|
||||
// Sync commit
|
||||
let success = await runScript(
|
||||
"scripts/linear-sync.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${pushEvent.event}`,
|
||||
`--action=${pushEvent.action}`,
|
||||
],
|
||||
"Sync commit status"
|
||||
);
|
||||
results.push({ test: "Push Sync", success });
|
||||
|
||||
// Add commit comment
|
||||
success = await runScript(
|
||||
"scripts/linear-comment.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${pushEvent.event}`,
|
||||
`--commit-sha=${pushEvent.commitSha}`,
|
||||
`--commit-message=${pushEvent.commitMessage}`,
|
||||
`--pr-author=${pushEvent.prAuthor}`,
|
||||
],
|
||||
"Add commit comment"
|
||||
);
|
||||
results.push({ test: "Push Comment", success });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue 이벤트 테스트
|
||||
*/
|
||||
async function testIssueFlow() {
|
||||
console.log("\n📋 Testing Issue Flow");
|
||||
console.log("=====================");
|
||||
|
||||
const results = [];
|
||||
const issueEvent = mockEvents.issue.opened;
|
||||
|
||||
// Add issue comment
|
||||
const success = await runScript(
|
||||
"scripts/linear-comment.cjs",
|
||||
[
|
||||
`--issue-id=${options.issueId}`,
|
||||
`--event=${issueEvent.event}`,
|
||||
`--action=${issueEvent.action}`,
|
||||
`--github-issue-url=${issueEvent.githubIssueUrl}`,
|
||||
],
|
||||
"Add issue comment"
|
||||
);
|
||||
results.push({ test: "Issue Comment", success });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 릴리즈 플로우 테스트
|
||||
*/
|
||||
async function testReleaseFlow() {
|
||||
console.log("\n🎯 Testing Release Flow");
|
||||
console.log("=======================");
|
||||
|
||||
const results = [];
|
||||
const version = "1.1.0";
|
||||
|
||||
// Release preparation
|
||||
let success = await runScript(
|
||||
"scripts/linear-release-prep.cjs",
|
||||
[version],
|
||||
"Prepare release"
|
||||
);
|
||||
results.push({ test: "Release Prep", success });
|
||||
|
||||
// Release completion
|
||||
success = await runScript(
|
||||
"scripts/linear-release-complete.cjs",
|
||||
[version],
|
||||
"Complete release"
|
||||
);
|
||||
results.push({ test: "Release Complete", success });
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 결과 요약
|
||||
*/
|
||||
function printSummary(allResults) {
|
||||
console.log("\n📊 Test Results Summary");
|
||||
console.log("========================");
|
||||
|
||||
let totalTests = 0;
|
||||
let passedTests = 0;
|
||||
|
||||
allResults.forEach((flowResults) => {
|
||||
flowResults.forEach((result) => {
|
||||
totalTests++;
|
||||
if (result.success) passedTests++;
|
||||
|
||||
const status = result.success ? "✅ PASS" : "❌ FAIL";
|
||||
console.log(`${status} ${result.test}`);
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`\n🎯 Overall: ${passedTests}/${totalTests} tests passed`);
|
||||
|
||||
if (passedTests === totalTests) {
|
||||
console.log("🎉 All workflow tests passed! Linear integration is ready.");
|
||||
} else {
|
||||
console.log("⚠️ Some tests failed. Check the configuration and scripts.");
|
||||
}
|
||||
|
||||
return passedTests === totalTests;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용법 출력
|
||||
*/
|
||||
function printUsage() {
|
||||
console.log(`
|
||||
🧪 Linear GitHub Actions 워크플로우 테스터
|
||||
|
||||
사용법:
|
||||
node scripts/linear-workflow-tester.cjs [옵션]
|
||||
|
||||
옵션:
|
||||
--linear-api=KEY Linear API 키 지정
|
||||
--test-type=TYPE 테스트 타입 (all, pr, push, issue, release)
|
||||
--issue-id=ID 테스트할 Linear 이슈 ID (기본: ZEL-1)
|
||||
--verbose, -v 상세 출력
|
||||
--help, -h 이 도움말 출력
|
||||
|
||||
예시:
|
||||
# 전체 워크플로우 테스트
|
||||
node scripts/linear-workflow-tester.cjs --linear-api=lin_api_xxx
|
||||
|
||||
# PR 플로우만 테스트
|
||||
node scripts/linear-workflow-tester.cjs --test-type=pr --issue-id=ZEL-123
|
||||
|
||||
# 상세 출력과 함께 테스트
|
||||
node scripts/linear-workflow-tester.cjs --linear-api=lin_api_xxx --verbose
|
||||
|
||||
테스트 타입:
|
||||
- all: 모든 워크플로우 테스트 (기본값)
|
||||
- pr: Pull Request 플로우만 테스트
|
||||
- push: Push 이벤트 플로우만 테스트
|
||||
- issue: Issue 이벤트 플로우만 테스트
|
||||
- release: 릴리즈 플로우만 테스트
|
||||
|
||||
환경 변수:
|
||||
LINEAR_API_KEY Linear API 키
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메인 함수
|
||||
*/
|
||||
async function main() {
|
||||
if (options.help) {
|
||||
printUsage();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("🧪 Linear GitHub Actions 워크플로우 테스터");
|
||||
console.log("==========================================\n");
|
||||
|
||||
// API 키 확인
|
||||
const apiKey = options.linearApiKey || process.env.LINEAR_API_KEY;
|
||||
if (!apiKey) {
|
||||
console.error(
|
||||
"❌ Linear API 키가 필요합니다. --linear-api 옵션을 사용하거나 LINEAR_API_KEY 환경 변수를 설정하세요."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
options.linearApiKey = apiKey;
|
||||
|
||||
console.log(`🔑 Linear API Key: ${apiKey.substring(0, 15)}...`);
|
||||
console.log(`🏷️ Test Issue ID: ${options.issueId}`);
|
||||
console.log(`📝 Test Type: ${options.testType}`);
|
||||
|
||||
const allResults = [];
|
||||
|
||||
try {
|
||||
// 테스트 타입에 따라 실행
|
||||
switch (options.testType) {
|
||||
case "pr":
|
||||
allResults.push(await testPullRequestFlow());
|
||||
break;
|
||||
case "push":
|
||||
allResults.push(await testPushFlow());
|
||||
break;
|
||||
case "issue":
|
||||
allResults.push(await testIssueFlow());
|
||||
break;
|
||||
case "release":
|
||||
allResults.push(await testReleaseFlow());
|
||||
break;
|
||||
case "all":
|
||||
default:
|
||||
allResults.push(await testPullRequestFlow());
|
||||
allResults.push(await testPushFlow());
|
||||
allResults.push(await testIssueFlow());
|
||||
allResults.push(await testReleaseFlow());
|
||||
break;
|
||||
}
|
||||
|
||||
// 결과 요약
|
||||
const allPassed = printSummary(allResults);
|
||||
|
||||
process.exit(allPassed ? 0 : 1);
|
||||
} catch (error) {
|
||||
console.error("\n❌ 테스트 실행 중 오류 발생:", error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 실행
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
testPullRequestFlow,
|
||||
testPushFlow,
|
||||
testIssueFlow,
|
||||
testReleaseFlow,
|
||||
};
|
||||
Reference in New Issue
Block a user