#!/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, };