@mandujs/cli 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/cli",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "Agent-Native Fullstack Framework - 에이전트가 코딩해도 아키텍처가 무너지지 않는 개발 OS",
5
5
  "type": "module",
6
6
  "main": "./src/main.ts",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@mandujs/core": "^0.16.0",
36
- "@mandujs/ate": "0.16.0",
35
+ "@mandujs/core": "0.17.0",
36
+ "@mandujs/ate": "0.17.0",
37
37
  "cfonts": "^3.3.0"
38
38
  },
39
39
  "engines": {
@@ -17,6 +17,7 @@ export interface InitOptions {
17
17
  ui?: UILibrary;
18
18
  theme?: boolean;
19
19
  minimal?: boolean;
20
+ withCi?: boolean;
20
21
  }
21
22
 
22
23
  const ALLOWED_TEMPLATES = ["default", "realtime-chat"] as const;
@@ -214,6 +215,7 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
214
215
  const css: CSSFramework = options.minimal ? "none" : (options.css || "tailwind");
215
216
  const ui: UILibrary = options.minimal ? "none" : (options.ui || "shadcn");
216
217
  const theme = options.theme || false;
218
+ const withCi = options.withCi || false;
217
219
 
218
220
  console.log(`🥟 Mandu Init`);
219
221
  console.log(`📁 프로젝트: ${projectName}`);
@@ -223,6 +225,9 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
223
225
  if (theme) {
224
226
  console.log(`🌙 테마: Dark mode 지원`);
225
227
  }
228
+ if (withCi) {
229
+ console.log(`🔄 CI/CD: GitHub Actions 워크플로우 포함`);
230
+ }
226
231
  console.log();
227
232
 
228
233
  // Check if target directory exists
@@ -269,6 +274,11 @@ export async function init(options: InitOptions = {}): Promise<boolean> {
269
274
  // Create .mandu directory for build output
270
275
  await fs.mkdir(path.join(targetDir, ".mandu/client"), { recursive: true });
271
276
 
277
+ // Setup CI/CD workflows if requested
278
+ if (withCi) {
279
+ await setupCiWorkflows(targetDir);
280
+ }
281
+
272
282
  // Create minimal layout.tsx if css=none (without globals.css import)
273
283
  if (css === "none") {
274
284
  await createMinimalLayout(targetDir, projectName);
@@ -630,3 +640,37 @@ async function setupLockfile(targetDir: string): Promise<LockfileResult> {
630
640
  };
631
641
  }
632
642
  }
643
+
644
+ /**
645
+ * CI/CD 워크플로우 파일 생성 (.github/workflows)
646
+ */
647
+ async function setupCiWorkflows(targetDir: string): Promise<void> {
648
+ const workflowsDir = path.join(targetDir, ".github/workflows");
649
+ await fs.mkdir(workflowsDir, { recursive: true });
650
+
651
+ const templatesDir = getTemplatesDir();
652
+ const sourceWorkflowsDir = path.join(templatesDir, "default/.github/workflows");
653
+
654
+ try {
655
+ // Copy all workflow files
656
+ const workflowFiles = await fs.readdir(sourceWorkflowsDir);
657
+ for (const file of workflowFiles) {
658
+ const src = path.join(sourceWorkflowsDir, file);
659
+ const dest = path.join(workflowsDir, file);
660
+ const content = await fs.readFile(src, "utf-8");
661
+ await fs.writeFile(dest, content);
662
+ }
663
+
664
+ // Copy impact analysis script
665
+ const scriptsDir = path.join(targetDir, "scripts");
666
+ await fs.mkdir(scriptsDir, { recursive: true });
667
+
668
+ const analyzeImpactSrc = path.join(templatesDir, "default/scripts/analyze-impact.ts");
669
+ const analyzeImpactDest = path.join(scriptsDir, "analyze-impact.ts");
670
+ const analyzeImpactContent = await fs.readFile(analyzeImpactSrc, "utf-8");
671
+ await fs.writeFile(analyzeImpactDest, analyzeImpactContent);
672
+ } catch (error) {
673
+ console.warn(`⚠️ CI/CD 워크플로우 설정 경고:`, error);
674
+ // CI 설정 실패는 프로젝트 생성을 중단하지 않음
675
+ }
676
+ }
@@ -75,6 +75,7 @@ registerCommand({
75
75
  ui: ctx.options.ui as any,
76
76
  theme: ctx.options.theme === "true",
77
77
  minimal: ctx.options.minimal === "true",
78
+ withCi: ctx.options["with-ci"] === "true",
78
79
  });
79
80
  },
80
81
  });
package/src/main.ts CHANGED
@@ -74,6 +74,7 @@ Options:
74
74
  --ui <library> init 시 UI 라이브러리: shadcn, ark, none (기본: shadcn)
75
75
  --theme init 시 다크모드 테마 시스템 추가
76
76
  --minimal init 시 CSS/UI 없이 최소 템플릿 생성 (--css none --ui none)
77
+ --with-ci init 시 GitHub Actions CI/CD 워크플로우 포함 (ATE E2E 테스트)
77
78
  --file <path> spec-upsert spec 파일/monitor 로그 파일 경로
78
79
  --watch build/guard arch 파일 감시 모드
79
80
  --output <path> routes/openapi/doctor/contract/guard 출력 경로
@@ -111,6 +112,7 @@ Notes:
111
112
 
112
113
  Examples:
113
114
  bunx mandu init --name my-app # Tailwind + shadcn/ui 기본
115
+ bunx mandu init --name my-app --with-ci # CI/CD 워크플로우 포함
114
116
  bunx mandu init --name chat-app --template realtime-chat # 실시간 채팅 스타터 템플릿
115
117
  bunx mandu init my-app --minimal # CSS/UI 없이 최소 템플릿
116
118
  bunx mandu dev
@@ -0,0 +1,189 @@
1
+ # GitHub Actions CI/CD for Mandu ATE
2
+
3
+ 이 디렉토리는 Mandu ATE (Automation Test Engine) E2E 테스트를 위한 GitHub Actions 워크플로우를 포함합니다.
4
+
5
+ ## 📋 워크플로우 개요
6
+
7
+ ### 1. `ate-e2e.yml` - 전체 E2E 테스트 실행
8
+
9
+ **트리거:**
10
+ - Pull Request (모든 브랜치)
11
+ - `main` 브랜치로 Push
12
+
13
+ **동작:**
14
+ 1. Bun 환경 설정
15
+ 2. 의존성 설치
16
+ 3. Playwright 브라우저 설치 (Chromium)
17
+ 4. ATE E2E 파이프라인 실행 (`bun run test:e2e:ci`)
18
+ 5. 테스트 리포트 아티팩트 업로드
19
+
20
+ **사용 시나리오:**
21
+ - 모든 E2E 테스트를 실행하여 전체 앱 동작 검증
22
+ - 메인 브랜치 병합 전 안정성 확인
23
+
24
+ ### 2. `ate-e2e-subset.yml` - Impact Analysis 기반 서브셋 테스트
25
+
26
+ **트리거:**
27
+ - Pull Request (opened, synchronize, reopened)
28
+
29
+ **동작:**
30
+ 1. **Impact Analysis 단계:**
31
+ - PR의 base와 head 간 변경 파일 분석
32
+ - `scripts/analyze-impact.ts`로 영향받는 테스트 식별
33
+ - 영향 범위가 없으면 테스트 스킵
34
+
35
+ 2. **서브셋 테스트 실행:**
36
+ - 영향받는 테스트만 선택적으로 실행
37
+ - Playwright의 `--grep` 옵션으로 필터링
38
+ - 테스트 결과를 PR 코멘트로 자동 게시
39
+
40
+ **사용 시나리오:**
41
+ - 빠른 피드백 루프 (변경 영향 범위만 테스트)
42
+ - CI 실행 시간 최적화
43
+ - 리소스 절약
44
+
45
+ ## 🚀 시작하기
46
+
47
+ ### 프로젝트 초기화 시 CI/CD 포함
48
+
49
+ ```bash
50
+ bunx mandu init --name my-app --with-ci
51
+ ```
52
+
53
+ 이 명령은 자동으로 다음을 생성합니다:
54
+ - `.github/workflows/ate-e2e.yml`
55
+ - `.github/workflows/ate-e2e-subset.yml`
56
+ - `scripts/analyze-impact.ts`
57
+
58
+ ### 기존 프로젝트에 추가
59
+
60
+ 1. 이 디렉토리의 워크플로우 파일들을 복사:
61
+ ```bash
62
+ cp -r .github/workflows your-project/.github/workflows
63
+ ```
64
+
65
+ 2. Impact Analysis 스크립트 복사:
66
+ ```bash
67
+ cp scripts/analyze-impact.ts your-project/scripts/
68
+ ```
69
+
70
+ 3. `package.json`에 CI 스크립트 추가:
71
+ ```json
72
+ {
73
+ "scripts": {
74
+ "test:e2e:ci": "bun run test:auto --ci"
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## 📊 Impact Analysis 커스터마이징
80
+
81
+ `scripts/analyze-impact.ts` 파일의 `IMPACT_MAP`을 수정하여 프로젝트 구조에 맞게 조정할 수 있습니다:
82
+
83
+ ```typescript
84
+ const IMPACT_MAP: ImpactMap = {
85
+ // API routes → API 테스트
86
+ "app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
87
+
88
+ // Client components → UI 테스트
89
+ "src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
90
+
91
+ // 커스텀 매핑 추가
92
+ "src/features/auth/**": ["**/auth*.spec.ts"],
93
+ };
94
+ ```
95
+
96
+ ## 🔍 워크플로우 상태 확인
97
+
98
+ ### GitHub Actions UI
99
+ 1. Repository → Actions 탭
100
+ 2. 워크플로우 실행 목록 확인
101
+ 3. 각 실행 클릭하여 상세 로그 확인
102
+
103
+ ### PR 코멘트
104
+ `ate-e2e-subset.yml`는 테스트 결과를 PR에 자동으로 코멘트로 추가합니다:
105
+ ```
106
+ ## 🧪 ATE E2E Test Results (Subset)
107
+
108
+ **Affected tests**: **/ui*.spec.ts|**/component*.spec.ts
109
+
110
+ ✅ Passed: 12
111
+ ❌ Failed: 0
112
+ ⏭️ Skipped: 3
113
+ ```
114
+
115
+ ### 아티팩트 다운로드
116
+ 테스트 실패 시 다음 아티팩트를 다운로드하여 분석:
117
+ - `playwright-report`: HTML 리포트
118
+ - `test-results`: 스크린샷, 비디오 등
119
+
120
+ ## 🛠️ 고급 설정
121
+
122
+ ### 환경 변수 설정
123
+
124
+ GitHub Repository Settings → Secrets and variables → Actions에서 환경 변수 추가:
125
+
126
+ ```yaml
127
+ env:
128
+ API_BASE_URL: ${{ secrets.API_BASE_URL }}
129
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
130
+ ```
131
+
132
+ ### 다중 브라우저 테스트
133
+
134
+ `ate-e2e.yml`을 수정하여 여러 브라우저에서 테스트:
135
+
136
+ ```yaml
137
+ strategy:
138
+ matrix:
139
+ browser: [chromium, firefox, webkit]
140
+ steps:
141
+ - name: Install Playwright browsers
142
+ run: bunx playwright install --with-deps ${{ matrix.browser }}
143
+ ```
144
+
145
+ ### 병렬 실행
146
+
147
+ ```yaml
148
+ strategy:
149
+ matrix:
150
+ shard: [1, 2, 3, 4]
151
+ steps:
152
+ - name: Run tests
153
+ run: bun run test:e2e:ci --shard=${{ matrix.shard }}/4
154
+ ```
155
+
156
+ ### Slack 알림 추가
157
+
158
+ ```yaml
159
+ - name: Notify Slack
160
+ if: failure()
161
+ uses: slackapi/slack-github-action@v1
162
+ with:
163
+ webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
164
+ payload: |
165
+ {
166
+ "text": "E2E Tests Failed: ${{ github.event.pull_request.html_url }}"
167
+ }
168
+ ```
169
+
170
+ ## 📝 트러블슈팅
171
+
172
+ ### 테스트가 스킵되는 경우
173
+ - `scripts/analyze-impact.ts`의 IMPACT_MAP 확인
174
+ - 변경된 파일이 매핑에 포함되어 있는지 확인
175
+ - 로그에서 "Analyzing changes" 출력 확인
176
+
177
+ ### Playwright 브라우저 설치 실패
178
+ - `bunx playwright install --with-deps chromium` 직접 실행
179
+ - Ubuntu 버전 확인 (ubuntu-latest 사용 권장)
180
+
181
+ ### 아티팩트 업로드 실패
182
+ - `.mandu/reports/` 디렉토리 존재 확인
183
+ - 테스트 실행이 성공적으로 완료되었는지 확인
184
+
185
+ ## 📚 참고 자료
186
+
187
+ - [Mandu ATE 문서](../../docs/ATE.md)
188
+ - [GitHub Actions 문서](https://docs.github.com/en/actions)
189
+ - [Playwright CI 가이드](https://playwright.dev/docs/ci)
@@ -0,0 +1,138 @@
1
+ name: ATE E2E Tests (Subset - Impact Analysis)
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
6
+
7
+ jobs:
8
+ analyze-changes:
9
+ runs-on: ubuntu-latest
10
+ timeout-minutes: 5
11
+ outputs:
12
+ affected-tests: ${{ steps.impact.outputs.tests }}
13
+ should-run: ${{ steps.impact.outputs.should-run }}
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - name: Setup Bun
22
+ uses: oven-sh/setup-bun@v1
23
+ with:
24
+ bun-version: latest
25
+
26
+ - name: Install dependencies
27
+ run: bun install
28
+
29
+ - name: Analyze impact
30
+ id: impact
31
+ run: |
32
+ # Get changed files between base and head
33
+ BASE_SHA="${{ github.event.pull_request.base.sha }}"
34
+ HEAD_SHA="${{ github.event.pull_request.head.sha }}"
35
+
36
+ echo "Analyzing changes between $BASE_SHA and $HEAD_SHA"
37
+
38
+ # Get list of changed files
39
+ CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
40
+
41
+ echo "Changed files:"
42
+ echo "$CHANGED_FILES"
43
+
44
+ # Create a temporary file for impact analysis
45
+ echo "$CHANGED_FILES" > changed-files.txt
46
+
47
+ # Run impact analysis (assuming you have a script for this)
48
+ # This is a placeholder - implement actual impact analysis logic
49
+ if [ -f "scripts/analyze-impact.ts" ]; then
50
+ AFFECTED_TESTS=$(bun run scripts/analyze-impact.ts changed-files.txt)
51
+ else
52
+ # Fallback: run all tests if impact analysis script doesn't exist
53
+ AFFECTED_TESTS="all"
54
+ fi
55
+
56
+ echo "Affected tests: $AFFECTED_TESTS"
57
+
58
+ # Set outputs
59
+ if [ "$AFFECTED_TESTS" = "none" ]; then
60
+ echo "should-run=false" >> $GITHUB_OUTPUT
61
+ echo "tests=" >> $GITHUB_OUTPUT
62
+ else
63
+ echo "should-run=true" >> $GITHUB_OUTPUT
64
+ echo "tests=$AFFECTED_TESTS" >> $GITHUB_OUTPUT
65
+ fi
66
+
67
+ e2e-subset:
68
+ needs: analyze-changes
69
+ if: needs.analyze-changes.outputs.should-run == 'true'
70
+ runs-on: ubuntu-latest
71
+ timeout-minutes: 15
72
+
73
+ steps:
74
+ - name: Checkout code
75
+ uses: actions/checkout@v4
76
+
77
+ - name: Setup Bun
78
+ uses: oven-sh/setup-bun@v1
79
+ with:
80
+ bun-version: latest
81
+
82
+ - name: Install dependencies
83
+ run: bun install
84
+
85
+ - name: Install Playwright browsers
86
+ run: bunx playwright install --with-deps chromium
87
+
88
+ - name: Run affected E2E tests
89
+ run: |
90
+ AFFECTED_TESTS="${{ needs.analyze-changes.outputs.affected-tests }}"
91
+
92
+ if [ "$AFFECTED_TESTS" = "all" ]; then
93
+ echo "Running all E2E tests"
94
+ bun run test:e2e:ci
95
+ else
96
+ echo "Running subset of E2E tests: $AFFECTED_TESTS"
97
+ # Pass affected test patterns to Playwright
98
+ bun run test:e2e:ci --grep "$AFFECTED_TESTS"
99
+ fi
100
+ env:
101
+ CI: true
102
+
103
+ - name: Upload Playwright Report
104
+ if: always()
105
+ uses: actions/upload-artifact@v4
106
+ with:
107
+ name: playwright-report-subset
108
+ path: .mandu/reports/
109
+ retention-days: 7
110
+
111
+ - name: Comment PR with test results
112
+ if: always()
113
+ uses: actions/github-script@v7
114
+ with:
115
+ script: |
116
+ const fs = require('fs');
117
+ const affectedTests = '${{ needs.analyze-changes.outputs.affected-tests }}';
118
+
119
+ let body = '## 🧪 ATE E2E Test Results (Subset)\n\n';
120
+ body += `**Affected tests**: ${affectedTests === 'all' ? 'All tests' : affectedTests}\n\n`;
121
+
122
+ // Try to read test results summary
123
+ try {
124
+ const summary = fs.readFileSync('.mandu/reports/summary.json', 'utf8');
125
+ const results = JSON.parse(summary);
126
+ body += `✅ Passed: ${results.passed}\n`;
127
+ body += `❌ Failed: ${results.failed}\n`;
128
+ body += `⏭️ Skipped: ${results.skipped}\n`;
129
+ } catch (e) {
130
+ body += '_Test summary not available_\n';
131
+ }
132
+
133
+ github.rest.issues.createComment({
134
+ issue_number: context.issue.number,
135
+ owner: context.repo.owner,
136
+ repo: context.repo.repo,
137
+ body: body
138
+ });
@@ -0,0 +1,47 @@
1
+ name: ATE E2E Tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ e2e:
10
+ runs-on: ubuntu-latest
11
+ timeout-minutes: 15
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Bun
18
+ uses: oven-sh/setup-bun@v1
19
+ with:
20
+ bun-version: latest
21
+
22
+ - name: Install dependencies
23
+ run: bun install
24
+
25
+ - name: Install Playwright browsers
26
+ run: bunx playwright install --with-deps chromium
27
+
28
+ - name: Run ATE E2E pipeline
29
+ run: bun run test:e2e:ci
30
+ env:
31
+ CI: true
32
+
33
+ - name: Upload Playwright Report
34
+ if: always()
35
+ uses: actions/upload-artifact@v4
36
+ with:
37
+ name: playwright-report
38
+ path: .mandu/reports/
39
+ retention-days: 7
40
+
41
+ - name: Upload test results
42
+ if: always()
43
+ uses: actions/upload-artifact@v4
44
+ with:
45
+ name: test-results
46
+ path: .mandu/test-results/
47
+ retention-days: 7
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Impact Analysis Script
5
+ * 변경된 파일 목록을 기반으로 영향받는 E2E 테스트 식별
6
+ *
7
+ * Usage:
8
+ * bun run scripts/analyze-impact.ts changed-files.txt
9
+ */
10
+
11
+ import fs from "fs/promises";
12
+ import path from "path";
13
+
14
+ interface ImpactMap {
15
+ [pattern: string]: string[];
16
+ }
17
+
18
+ /**
19
+ * 파일 패턴과 영향받는 테스트 매핑
20
+ * 프로젝트 구조에 맞게 커스터마이즈 가능
21
+ */
22
+ const IMPACT_MAP: ImpactMap = {
23
+ // API routes → API 테스트
24
+ "app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
25
+
26
+ // Client components → UI 테스트
27
+ "src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
28
+
29
+ // Server logic → Integration 테스트
30
+ "src/server/**": ["**/integration*.spec.ts", "**/server*.spec.ts"],
31
+
32
+ // Shared contracts → 모든 통합 테스트
33
+ "src/shared/contracts/**": ["**/integration*.spec.ts", "**/e2e*.spec.ts"],
34
+
35
+ // Database/Schema → 데이터 관련 테스트
36
+ "src/shared/schema/**": ["**/db*.spec.ts", "**/data*.spec.ts"],
37
+
38
+ // Config files → 모든 테스트
39
+ "*.config.*": ["**/*.spec.ts"],
40
+ "package.json": ["**/*.spec.ts"],
41
+ };
42
+
43
+ /**
44
+ * Glob 패턴 매칭 (간단한 구현)
45
+ */
46
+ function matchPattern(filePath: string, pattern: string): boolean {
47
+ const regexPattern = pattern
48
+ .replace(/\*\*/g, ".*")
49
+ .replace(/\*/g, "[^/]*")
50
+ .replace(/\./g, "\\.");
51
+
52
+ const regex = new RegExp(`^${regexPattern}$`);
53
+ return regex.test(filePath);
54
+ }
55
+
56
+ /**
57
+ * 변경된 파일 목록을 기반으로 영향받는 테스트 패턴 추출
58
+ */
59
+ function analyzeImpact(changedFiles: string[]): string[] {
60
+ const affectedTestPatterns = new Set<string>();
61
+
62
+ for (const file of changedFiles) {
63
+ const normalizedPath = file.replace(/\\/g, "/");
64
+
65
+ for (const [pattern, tests] of Object.entries(IMPACT_MAP)) {
66
+ if (matchPattern(normalizedPath, pattern)) {
67
+ tests.forEach((test) => affectedTestPatterns.add(test));
68
+ }
69
+ }
70
+ }
71
+
72
+ return Array.from(affectedTestPatterns);
73
+ }
74
+
75
+ /**
76
+ * Main
77
+ */
78
+ async function main() {
79
+ const args = process.argv.slice(2);
80
+
81
+ if (args.length === 0) {
82
+ console.error("Usage: bun run scripts/analyze-impact.ts <changed-files.txt>");
83
+ process.exit(1);
84
+ }
85
+
86
+ const changedFilesPath = args[0];
87
+
88
+ try {
89
+ // Read changed files
90
+ const content = await fs.readFile(changedFilesPath, "utf-8");
91
+ const changedFiles = content
92
+ .split("\n")
93
+ .map((line) => line.trim())
94
+ .filter((line) => line.length > 0);
95
+
96
+ if (changedFiles.length === 0) {
97
+ console.log("none");
98
+ return;
99
+ }
100
+
101
+ // Analyze impact
102
+ const affectedTests = analyzeImpact(changedFiles);
103
+
104
+ if (affectedTests.length === 0) {
105
+ console.log("none");
106
+ return;
107
+ }
108
+
109
+ // Output affected test patterns (Playwright grep format)
110
+ const grepPattern = affectedTests.join("|");
111
+ console.log(grepPattern);
112
+ } catch (error) {
113
+ console.error("Error analyzing impact:", error);
114
+ // Fallback: run all tests
115
+ console.log("all");
116
+ }
117
+ }
118
+
119
+ main();
@@ -0,0 +1,189 @@
1
+ # GitHub Actions CI/CD for Mandu ATE
2
+
3
+ 이 디렉토리는 Mandu ATE (Automation Test Engine) E2E 테스트를 위한 GitHub Actions 워크플로우를 포함합니다.
4
+
5
+ ## 📋 워크플로우 개요
6
+
7
+ ### 1. `ate-e2e.yml` - 전체 E2E 테스트 실행
8
+
9
+ **트리거:**
10
+ - Pull Request (모든 브랜치)
11
+ - `main` 브랜치로 Push
12
+
13
+ **동작:**
14
+ 1. Bun 환경 설정
15
+ 2. 의존성 설치
16
+ 3. Playwright 브라우저 설치 (Chromium)
17
+ 4. ATE E2E 파이프라인 실행 (`bun run test:e2e:ci`)
18
+ 5. 테스트 리포트 아티팩트 업로드
19
+
20
+ **사용 시나리오:**
21
+ - 모든 E2E 테스트를 실행하여 전체 앱 동작 검증
22
+ - 메인 브랜치 병합 전 안정성 확인
23
+
24
+ ### 2. `ate-e2e-subset.yml` - Impact Analysis 기반 서브셋 테스트
25
+
26
+ **트리거:**
27
+ - Pull Request (opened, synchronize, reopened)
28
+
29
+ **동작:**
30
+ 1. **Impact Analysis 단계:**
31
+ - PR의 base와 head 간 변경 파일 분석
32
+ - `scripts/analyze-impact.ts`로 영향받는 테스트 식별
33
+ - 영향 범위가 없으면 테스트 스킵
34
+
35
+ 2. **서브셋 테스트 실행:**
36
+ - 영향받는 테스트만 선택적으로 실행
37
+ - Playwright의 `--grep` 옵션으로 필터링
38
+ - 테스트 결과를 PR 코멘트로 자동 게시
39
+
40
+ **사용 시나리오:**
41
+ - 빠른 피드백 루프 (변경 영향 범위만 테스트)
42
+ - CI 실행 시간 최적화
43
+ - 리소스 절약
44
+
45
+ ## 🚀 시작하기
46
+
47
+ ### 프로젝트 초기화 시 CI/CD 포함
48
+
49
+ ```bash
50
+ bunx mandu init --name my-app --with-ci
51
+ ```
52
+
53
+ 이 명령은 자동으로 다음을 생성합니다:
54
+ - `.github/workflows/ate-e2e.yml`
55
+ - `.github/workflows/ate-e2e-subset.yml`
56
+ - `scripts/analyze-impact.ts`
57
+
58
+ ### 기존 프로젝트에 추가
59
+
60
+ 1. 이 디렉토리의 워크플로우 파일들을 복사:
61
+ ```bash
62
+ cp -r .github/workflows your-project/.github/workflows
63
+ ```
64
+
65
+ 2. Impact Analysis 스크립트 복사:
66
+ ```bash
67
+ cp scripts/analyze-impact.ts your-project/scripts/
68
+ ```
69
+
70
+ 3. `package.json`에 CI 스크립트 추가:
71
+ ```json
72
+ {
73
+ "scripts": {
74
+ "test:e2e:ci": "bun run test:auto --ci"
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## 📊 Impact Analysis 커스터마이징
80
+
81
+ `scripts/analyze-impact.ts` 파일의 `IMPACT_MAP`을 수정하여 프로젝트 구조에 맞게 조정할 수 있습니다:
82
+
83
+ ```typescript
84
+ const IMPACT_MAP: ImpactMap = {
85
+ // API routes → API 테스트
86
+ "app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
87
+
88
+ // Client components → UI 테스트
89
+ "src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
90
+
91
+ // 커스텀 매핑 추가
92
+ "src/features/auth/**": ["**/auth*.spec.ts"],
93
+ };
94
+ ```
95
+
96
+ ## 🔍 워크플로우 상태 확인
97
+
98
+ ### GitHub Actions UI
99
+ 1. Repository → Actions 탭
100
+ 2. 워크플로우 실행 목록 확인
101
+ 3. 각 실행 클릭하여 상세 로그 확인
102
+
103
+ ### PR 코멘트
104
+ `ate-e2e-subset.yml`는 테스트 결과를 PR에 자동으로 코멘트로 추가합니다:
105
+ ```
106
+ ## 🧪 ATE E2E Test Results (Subset)
107
+
108
+ **Affected tests**: **/ui*.spec.ts|**/component*.spec.ts
109
+
110
+ ✅ Passed: 12
111
+ ❌ Failed: 0
112
+ ⏭️ Skipped: 3
113
+ ```
114
+
115
+ ### 아티팩트 다운로드
116
+ 테스트 실패 시 다음 아티팩트를 다운로드하여 분석:
117
+ - `playwright-report`: HTML 리포트
118
+ - `test-results`: 스크린샷, 비디오 등
119
+
120
+ ## 🛠️ 고급 설정
121
+
122
+ ### 환경 변수 설정
123
+
124
+ GitHub Repository Settings → Secrets and variables → Actions에서 환경 변수 추가:
125
+
126
+ ```yaml
127
+ env:
128
+ API_BASE_URL: ${{ secrets.API_BASE_URL }}
129
+ DATABASE_URL: ${{ secrets.DATABASE_URL }}
130
+ ```
131
+
132
+ ### 다중 브라우저 테스트
133
+
134
+ `ate-e2e.yml`을 수정하여 여러 브라우저에서 테스트:
135
+
136
+ ```yaml
137
+ strategy:
138
+ matrix:
139
+ browser: [chromium, firefox, webkit]
140
+ steps:
141
+ - name: Install Playwright browsers
142
+ run: bunx playwright install --with-deps ${{ matrix.browser }}
143
+ ```
144
+
145
+ ### 병렬 실행
146
+
147
+ ```yaml
148
+ strategy:
149
+ matrix:
150
+ shard: [1, 2, 3, 4]
151
+ steps:
152
+ - name: Run tests
153
+ run: bun run test:e2e:ci --shard=${{ matrix.shard }}/4
154
+ ```
155
+
156
+ ### Slack 알림 추가
157
+
158
+ ```yaml
159
+ - name: Notify Slack
160
+ if: failure()
161
+ uses: slackapi/slack-github-action@v1
162
+ with:
163
+ webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
164
+ payload: |
165
+ {
166
+ "text": "E2E Tests Failed: ${{ github.event.pull_request.html_url }}"
167
+ }
168
+ ```
169
+
170
+ ## 📝 트러블슈팅
171
+
172
+ ### 테스트가 스킵되는 경우
173
+ - `scripts/analyze-impact.ts`의 IMPACT_MAP 확인
174
+ - 변경된 파일이 매핑에 포함되어 있는지 확인
175
+ - 로그에서 "Analyzing changes" 출력 확인
176
+
177
+ ### Playwright 브라우저 설치 실패
178
+ - `bunx playwright install --with-deps chromium` 직접 실행
179
+ - Ubuntu 버전 확인 (ubuntu-latest 사용 권장)
180
+
181
+ ### 아티팩트 업로드 실패
182
+ - `.mandu/reports/` 디렉토리 존재 확인
183
+ - 테스트 실행이 성공적으로 완료되었는지 확인
184
+
185
+ ## 📚 참고 자료
186
+
187
+ - [Mandu ATE 문서](../../docs/ATE.md)
188
+ - [GitHub Actions 문서](https://docs.github.com/en/actions)
189
+ - [Playwright CI 가이드](https://playwright.dev/docs/ci)
@@ -0,0 +1,138 @@
1
+ name: ATE E2E Tests (Subset - Impact Analysis)
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
6
+
7
+ jobs:
8
+ analyze-changes:
9
+ runs-on: ubuntu-latest
10
+ timeout-minutes: 5
11
+ outputs:
12
+ affected-tests: ${{ steps.impact.outputs.tests }}
13
+ should-run: ${{ steps.impact.outputs.should-run }}
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - name: Setup Bun
22
+ uses: oven-sh/setup-bun@v1
23
+ with:
24
+ bun-version: latest
25
+
26
+ - name: Install dependencies
27
+ run: bun install
28
+
29
+ - name: Analyze impact
30
+ id: impact
31
+ run: |
32
+ # Get changed files between base and head
33
+ BASE_SHA="${{ github.event.pull_request.base.sha }}"
34
+ HEAD_SHA="${{ github.event.pull_request.head.sha }}"
35
+
36
+ echo "Analyzing changes between $BASE_SHA and $HEAD_SHA"
37
+
38
+ # Get list of changed files
39
+ CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")
40
+
41
+ echo "Changed files:"
42
+ echo "$CHANGED_FILES"
43
+
44
+ # Create a temporary file for impact analysis
45
+ echo "$CHANGED_FILES" > changed-files.txt
46
+
47
+ # Run impact analysis (assuming you have a script for this)
48
+ # This is a placeholder - implement actual impact analysis logic
49
+ if [ -f "scripts/analyze-impact.ts" ]; then
50
+ AFFECTED_TESTS=$(bun run scripts/analyze-impact.ts changed-files.txt)
51
+ else
52
+ # Fallback: run all tests if impact analysis script doesn't exist
53
+ AFFECTED_TESTS="all"
54
+ fi
55
+
56
+ echo "Affected tests: $AFFECTED_TESTS"
57
+
58
+ # Set outputs
59
+ if [ "$AFFECTED_TESTS" = "none" ]; then
60
+ echo "should-run=false" >> $GITHUB_OUTPUT
61
+ echo "tests=" >> $GITHUB_OUTPUT
62
+ else
63
+ echo "should-run=true" >> $GITHUB_OUTPUT
64
+ echo "tests=$AFFECTED_TESTS" >> $GITHUB_OUTPUT
65
+ fi
66
+
67
+ e2e-subset:
68
+ needs: analyze-changes
69
+ if: needs.analyze-changes.outputs.should-run == 'true'
70
+ runs-on: ubuntu-latest
71
+ timeout-minutes: 15
72
+
73
+ steps:
74
+ - name: Checkout code
75
+ uses: actions/checkout@v4
76
+
77
+ - name: Setup Bun
78
+ uses: oven-sh/setup-bun@v1
79
+ with:
80
+ bun-version: latest
81
+
82
+ - name: Install dependencies
83
+ run: bun install
84
+
85
+ - name: Install Playwright browsers
86
+ run: bunx playwright install --with-deps chromium
87
+
88
+ - name: Run affected E2E tests
89
+ run: |
90
+ AFFECTED_TESTS="${{ needs.analyze-changes.outputs.affected-tests }}"
91
+
92
+ if [ "$AFFECTED_TESTS" = "all" ]; then
93
+ echo "Running all E2E tests"
94
+ bun run test:e2e:ci
95
+ else
96
+ echo "Running subset of E2E tests: $AFFECTED_TESTS"
97
+ # Pass affected test patterns to Playwright
98
+ bun run test:e2e:ci --grep "$AFFECTED_TESTS"
99
+ fi
100
+ env:
101
+ CI: true
102
+
103
+ - name: Upload Playwright Report
104
+ if: always()
105
+ uses: actions/upload-artifact@v4
106
+ with:
107
+ name: playwright-report-subset
108
+ path: .mandu/reports/
109
+ retention-days: 7
110
+
111
+ - name: Comment PR with test results
112
+ if: always()
113
+ uses: actions/github-script@v7
114
+ with:
115
+ script: |
116
+ const fs = require('fs');
117
+ const affectedTests = '${{ needs.analyze-changes.outputs.affected-tests }}';
118
+
119
+ let body = '## 🧪 ATE E2E Test Results (Subset)\n\n';
120
+ body += `**Affected tests**: ${affectedTests === 'all' ? 'All tests' : affectedTests}\n\n`;
121
+
122
+ // Try to read test results summary
123
+ try {
124
+ const summary = fs.readFileSync('.mandu/reports/summary.json', 'utf8');
125
+ const results = JSON.parse(summary);
126
+ body += `✅ Passed: ${results.passed}\n`;
127
+ body += `❌ Failed: ${results.failed}\n`;
128
+ body += `⏭️ Skipped: ${results.skipped}\n`;
129
+ } catch (e) {
130
+ body += '_Test summary not available_\n';
131
+ }
132
+
133
+ github.rest.issues.createComment({
134
+ issue_number: context.issue.number,
135
+ owner: context.repo.owner,
136
+ repo: context.repo.repo,
137
+ body: body
138
+ });
@@ -0,0 +1,47 @@
1
+ name: ATE E2E Tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ e2e:
10
+ runs-on: ubuntu-latest
11
+ timeout-minutes: 15
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Bun
18
+ uses: oven-sh/setup-bun@v1
19
+ with:
20
+ bun-version: latest
21
+
22
+ - name: Install dependencies
23
+ run: bun install
24
+
25
+ - name: Install Playwright browsers
26
+ run: bunx playwright install --with-deps chromium
27
+
28
+ - name: Run ATE E2E pipeline
29
+ run: bun run test:e2e:ci
30
+ env:
31
+ CI: true
32
+
33
+ - name: Upload Playwright Report
34
+ if: always()
35
+ uses: actions/upload-artifact@v4
36
+ with:
37
+ name: playwright-report
38
+ path: .mandu/reports/
39
+ retention-days: 7
40
+
41
+ - name: Upload test results
42
+ if: always()
43
+ uses: actions/upload-artifact@v4
44
+ with:
45
+ name: test-results
46
+ path: .mandu/test-results/
47
+ retention-days: 7
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Impact Analysis Script
5
+ * 변경된 파일 목록을 기반으로 영향받는 E2E 테스트 식별
6
+ *
7
+ * Usage:
8
+ * bun run scripts/analyze-impact.ts changed-files.txt
9
+ */
10
+
11
+ import fs from "fs/promises";
12
+ import path from "path";
13
+
14
+ interface ImpactMap {
15
+ [pattern: string]: string[];
16
+ }
17
+
18
+ /**
19
+ * 파일 패턴과 영향받는 테스트 매핑
20
+ * 프로젝트 구조에 맞게 커스터마이즈 가능
21
+ */
22
+ const IMPACT_MAP: ImpactMap = {
23
+ // API routes → API 테스트
24
+ "app/api/**": ["**/api*.spec.ts", "**/api*.test.ts"],
25
+
26
+ // Client components → UI 테스트
27
+ "src/client/**": ["**/ui*.spec.ts", "**/component*.spec.ts"],
28
+
29
+ // Server logic → Integration 테스트
30
+ "src/server/**": ["**/integration*.spec.ts", "**/server*.spec.ts"],
31
+
32
+ // Shared contracts → 모든 통합 테스트
33
+ "src/shared/contracts/**": ["**/integration*.spec.ts", "**/e2e*.spec.ts"],
34
+
35
+ // Database/Schema → 데이터 관련 테스트
36
+ "src/shared/schema/**": ["**/db*.spec.ts", "**/data*.spec.ts"],
37
+
38
+ // Config files → 모든 테스트
39
+ "*.config.*": ["**/*.spec.ts"],
40
+ "package.json": ["**/*.spec.ts"],
41
+ };
42
+
43
+ /**
44
+ * Glob 패턴 매칭 (간단한 구현)
45
+ */
46
+ function matchPattern(filePath: string, pattern: string): boolean {
47
+ const regexPattern = pattern
48
+ .replace(/\*\*/g, ".*")
49
+ .replace(/\*/g, "[^/]*")
50
+ .replace(/\./g, "\\.");
51
+
52
+ const regex = new RegExp(`^${regexPattern}$`);
53
+ return regex.test(filePath);
54
+ }
55
+
56
+ /**
57
+ * 변경된 파일 목록을 기반으로 영향받는 테스트 패턴 추출
58
+ */
59
+ function analyzeImpact(changedFiles: string[]): string[] {
60
+ const affectedTestPatterns = new Set<string>();
61
+
62
+ for (const file of changedFiles) {
63
+ const normalizedPath = file.replace(/\\/g, "/");
64
+
65
+ for (const [pattern, tests] of Object.entries(IMPACT_MAP)) {
66
+ if (matchPattern(normalizedPath, pattern)) {
67
+ tests.forEach((test) => affectedTestPatterns.add(test));
68
+ }
69
+ }
70
+ }
71
+
72
+ return Array.from(affectedTestPatterns);
73
+ }
74
+
75
+ /**
76
+ * Main
77
+ */
78
+ async function main() {
79
+ const args = process.argv.slice(2);
80
+
81
+ if (args.length === 0) {
82
+ console.error("Usage: bun run scripts/analyze-impact.ts <changed-files.txt>");
83
+ process.exit(1);
84
+ }
85
+
86
+ const changedFilesPath = args[0];
87
+
88
+ try {
89
+ // Read changed files
90
+ const content = await fs.readFile(changedFilesPath, "utf-8");
91
+ const changedFiles = content
92
+ .split("\n")
93
+ .map((line) => line.trim())
94
+ .filter((line) => line.length > 0);
95
+
96
+ if (changedFiles.length === 0) {
97
+ console.log("none");
98
+ return;
99
+ }
100
+
101
+ // Analyze impact
102
+ const affectedTests = analyzeImpact(changedFiles);
103
+
104
+ if (affectedTests.length === 0) {
105
+ console.log("none");
106
+ return;
107
+ }
108
+
109
+ // Output affected test patterns (Playwright grep format)
110
+ const grepPattern = affectedTests.join("|");
111
+ console.log(grepPattern);
112
+ } catch (error) {
113
+ console.error("Error analyzing impact:", error);
114
+ // Fallback: run all tests
115
+ console.log("all");
116
+ }
117
+ }
118
+
119
+ main();