Repository navigation
114 lines (101 loc) · 5.64 KB
/
Copy pathci.yml
File metadata and controls
114 lines (101 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# CI — push/PR 시점 검증
#
# ★왜 생겼나: 종전에 린트·타입·테스트를 강제하는 자동 지점이 **배포 워크플로 하나뿐**이었다.
# 그래서 두 가지가 실제로 일어났다:
# - eslint flat-config 의 함정으로 4개 패키지가 **한 번도 린팅되지 않은** 채
# `build` 게이트를 통과했다(게이트 존재 ≠ 린팅 동작)
# - `tsc --noEmit` 은 **어느 패키지의 build 에도 걸려 있지 않아** 타입 에러가
# 여러 릴리스를 지나 살아남았다(`vite build` 도 `vite-plugin-dts` 도 막지 않는다)
# ⇒ 재발 방지의 논리적 완결은 **머지 전에** 도는 게이트다.
#
# ★왜 12개 리포에 **같은 파일**인가: 패키지마다 스크립트 구성이 다르지만(실측 —
# 린트 6/12 · 테스트 11/12 · vitest 브라우저 프로젝트 7/12 · playwright e2e 1/12 — 2026-09-16 재측정,
# 브라우저 항목은 종전 서술보다 둘 늘었다),
# 그 차이를 파일에 새기면 12벌이 갈라진다. 대신 **런타임 탐지**로 하나의 파일을 유지한다 —
# 이 리포가 반복해서 값을 본 형태다. ⚠구성 수치는 낡는다: 이 파일이 의존하는 것은 숫자가
# 아니라 탐지이므로, 낡아도 동작은 틀어지지 않는다(서술만 정정하면 된다).
#
# ⚠**`--if-present` 는 조용히 건너뛴다.** 그것이 이 파일의 가장 큰 위험이므로 "무엇이
# 돌았는가"를 먼저 출력한다 — 로그를 보면 그 패키지에서 실제로 무엇이 검증됐는지 안다.
#
# ⚠**artifact 를 올리지 않는다** — CI 는 검증만 하고, 산출물 업로드는 릴리스 파이프라인
# 소관이다(스토리지 정책).
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
# 같은 브랜치에 연속 push 하면 앞의 실행을 취소한다 — 검증 결과가 필요한 것은 최신 커밋뿐이다.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
verify:
name: verify
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
# lock 파일이 없는 정책이므로(배포도 `npm install` 을 쓴다) 자동 캐싱을 끈다 —
# setup-node 의 packageManager 기반 캐싱은 lock-less 설치와 충돌한다.
# ⚠Node 24 다. 22 로는 라우터 테스트가 `URLPattern is not defined` 로 죽는다 —
# 이 API 는 브라우저 대상 코드가 런타임에 쓰는 것이고 Node 에는 24 부터 전역으로
# 들어왔다. 로컬은 24 라 드러나지 않았고 첫 CI 실행이 잡았다.
- uses: actions/setup-node@v7
with:
node-version: "24"
package-manager-cache: false
- name: Detect package capabilities
id: probe
run: |
node -e "
const p = require('./package.json');
const s = p.scripts || {};
const d = { ...p.devDependencies, ...p.dependencies };
// ⚠'@playwright/test' 를 빠뜨리면 e2e 를 가진 패키지가 **크로미움 없이** 돈다.
// 실측(cycle-495): 이 이름만 갖고 있어 browser=false 인 패키지가 있었다.
const browser = ('@vitest/browser-playwright' in d) || ('playwright' in d) || ('@playwright/test' in d);
const fs = require('fs');
const out = [
'lint=' + !!s.lint,
'test=' + !!s.test,
'build=' + !!s.build,
'browser=' + browser,
'e2e=' + !!s['test:e2e'],
'types=' + fs.existsSync('tsconfig.json'),
];
fs.appendFileSync(process.env.GITHUB_OUTPUT, out.join('\n') + '\n');
console.log('검증 대상: ' + out.join(' · '));
if (!fs.existsSync('tsconfig.json')) console.log('⚠ tsconfig.json 없음 — 타입 검사를 건너뛴다');
"
- name: Install
run: npm install
# 브라우저 프로젝트를 가진 패키지만 크로미움을 내려받는다(설치가 느리고 무겁다).
- name: Install Chromium
if: steps.probe.outputs.browser == 'true'
run: npx playwright install --with-deps chromium
- name: Lint
if: steps.probe.outputs.lint == 'true'
run: npm run lint
# ★이 게이트는 신설이다 — 어느 패키지의 `build` 에도 타입 검사가 걸려 있지 않았다.
- name: Typecheck
if: steps.probe.outputs.types == 'true'
run: npx tsc --noEmit
# ⚠Build 가 Test 보다 먼저다. 첫 CI 실행이 그 이유를 실측으로 보여 줬다 —
# 어떤 패키지의 테스트는 게시될 번들 자체를 검증하므로 dist 가 있어야 한다
# (`bundle-size` 계열). 로컬에는 이전 빌드의 dist 가 남아 있어 드러나지 않는다.
- name: Build
if: steps.probe.outputs.build == 'true'
run: npm run build
- name: Test
if: steps.probe.outputs.test == 'true'
run: npm test
# ★`test:e2e` 는 `npm test` 와 **다른 명령**이라 여기 걸지 않으면 어디서도 돌지 않는다.
# 실측(cycle-495): 한 패키지의 WCAG 타깃 크기 게이트가 정확히 그 상태였다 —
# 사람이 손으로 `npm run test:e2e` 를 칠 때만 도는 «불변식이 아니라 문장»이었다.
# ⚠`npm test` 에 합치지 않는 이유: 크로미움이 없는 로컬이 전부 빨개진다. 검증이
# 도는 자리는 CI 이고, 로컬 개발 루프는 vitest 로 빠르게 유지한다.
- name: E2E
if: steps.probe.outputs.e2e == 'true'
run: npm run test:e2e