expo-release-guard 0.1.0 → 0.2.0

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/README.ko.md ADDED
@@ -0,0 +1,114 @@
1
+ # expo-release-guard
2
+
3
+ [English](README.md) · **한국어**
4
+
5
+ > 빌드 성공은 실행 가능을 뜻하지 않는다.
6
+
7
+ Expo 앱의 **prebuild 산출물**을 릴리스 전에 검사하는 CLI입니다. CI에서 한 줄로 돌리고, 실패하면 무엇이 왜 필요한지 함께 알려 줍니다.
8
+
9
+ ```console
10
+ $ npx expo-release-guard check
11
+ ✖ ios-plist: 필수 키 3개 중 1개 누락
12
+ - NSRemindersUsageDescription가 없거나 비었습니다.
13
+ 안 쓰지만 필수 — expo-calendar가 앱 시작 시 조회한다. 없으면 스플래시에서 종료.
14
+
15
+ 1개 검사가 실패했습니다.
16
+ ```
17
+
18
+ ## 왜 만들었나
19
+
20
+ 소셜 루틴 앱을 운영하면서, **EAS 빌드 성공 · 테스트 990개 통과 · App Store Connect 업로드 성공**을 모두 거친 빌드가 실기기에서 켜자마자 꺼진 적이 있습니다.
21
+
22
+ - 캘린더 기능을 붙이며 "쓰지 않는 권한"인 미리알림 키를 지우는 config plugin을 넣었는데,
23
+ - `expo-calendar`의 iOS 모듈은 **앱 시작 시** 그 키를 조회했고, 키가 없으면 즉시 종료했습니다.
24
+ - `app.json`(입력)은 멀쩡했고 prebuild **산출물**만 망가져 있어서, 입력을 보는 테스트로는 잡을 수 없었습니다.
25
+ - 크래시가 오류 수집 도구 초기화보다 먼저 일어나 Sentry에도 기록이 남지 않았습니다.
26
+
27
+ 통과한 신호 중 어느 것도 "앱이 켜지는가"를 보지 않았습니다. 이 도구는 그 구멍을 막기 위해, 서비스 저장소 안에 흩어져 있던 릴리스 사전 검사 스크립트를 설정 기반의 독립 CLI로 옮긴 것입니다.
28
+
29
+ **원칙**
30
+
31
+ 1. **입력이 아니라 산출물을 본다.** `app.json`이 아니라 `expo prebuild`가 만든 `ios/`, `android/`와 Gradle 병합 결과를 검사합니다.
32
+ 2. **이유를 같이 적는다.** 필수 항목마다 `reason`을 받아, 실패 메시지에 "왜 지우면 안 되는지"를 보여 줍니다. 다음 사람이 같은 키를 다시 지우지 않게 하기 위해서입니다.
33
+ 3. **추측하지 않는다.** 대상 파일 후보가 여럿이면 하나를 골라 통과시키지 않고 경로 설정을 요구합니다.
34
+
35
+ ## 설치
36
+
37
+ ```bash
38
+ npm i -D expo-release-guard
39
+ ```
40
+
41
+ Node.js 22.12 이상이 필요합니다.
42
+
43
+ ## 빠른 시작
44
+
45
+ 1. prebuild 뒤 `init`으로 지금 산출물에서 설정 초안을 만들고, `reason`의 TODO를 채웁니다.
46
+
47
+ ```bash
48
+ npx expo prebuild --clean --no-install
49
+ npx expo-release-guard init
50
+ ```
51
+
52
+ 직접 쓰려면 프로젝트 루트에 `release-guard.config.json`을 만듭니다.
53
+
54
+ ```json
55
+ {
56
+ "ios": {
57
+ "infoPlist": {
58
+ "requiredKeys": [
59
+ { "key": "NSCalendarsUsageDescription", "reason": "캘린더 가져오기. 없으면 권한 요청 자체가 불가." },
60
+ { "key": "NSRemindersUsageDescription", "reason": "expo-calendar가 앱 시작 시 조회한다. 없으면 스플래시에서 종료." }
61
+ ]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ 2. prebuild 뒤에 실행합니다.
68
+
69
+ ```bash
70
+ npx expo prebuild --platform ios --clean --no-install
71
+ npx expo-release-guard check
72
+ ```
73
+
74
+ 3. CI에 붙입니다 (GitHub Actions 예시).
75
+
76
+ ```yaml
77
+ - run: npx expo prebuild --clean --no-install
78
+ - run: npx expo-release-guard check # ios-plist · android-manifest
79
+ - run: cd android && ./gradlew :app:processReleaseMainManifest
80
+ - run: npx expo-release-guard check android-merged-manifest
81
+ ```
82
+
83
+ ## 검사 목록
84
+
85
+ | id | 하는 일 | 문서 |
86
+ | --- | --- | --- |
87
+ | `ios-plist` | prebuild 산출물 `Info.plist`에 필수 키가 비어 있지 않은 문자열로 남아 있는지 | [docs/checks/ios-plist.md](docs/checks/ios-plist.md) |
88
+ | `android-manifest` | prebuild 산출물 매니페스트의 필수·금지 권한, `blockedPermissions` 제거 지시자 | [docs/checks/android-manifest.md](docs/checks/android-manifest.md) |
89
+ | `android-merged-manifest` | Gradle 병합 매니페스트(최종본)의 필수·금지 권한 — id를 지정해 실행 | [docs/checks/android-manifest.md](docs/checks/android-manifest.md) |
90
+ | `ios-alternate-icons` | 대체 앱 아이콘이 빌드 설정과 에셋 카탈로그 양쪽에 등록됐는지 | [docs/checks/app-icons.md](docs/checks/app-icons.md) |
91
+ | `android-launcher-aliases` | 아이콘별 런처 별칭, 기본으로 켜진 별칭이 정확히 하나인지, 메인 액티비티 상태 | [docs/checks/app-icons.md](docs/checks/app-icons.md) |
92
+
93
+ ## GitHub Action
94
+
95
+ ```yaml
96
+ - run: npm ci
97
+ - uses: evan7484/expo-release-guard@v0
98
+ with:
99
+ prebuild: all # none | ios | android | all
100
+ version: 0.2.0
101
+ ```
102
+
103
+ 이식 예정: OTA 채널·런타임 버전 대조.
104
+
105
+ ## 문서
106
+
107
+ - [CLI 레퍼런스](docs/cli.md) — 명령, 옵션, 종료 코드, JSON 출력 형식
108
+ - [설정 레퍼런스](docs/configuration.md) — 설정 파일 위치와 스키마
109
+ - [프로그래밍 API](docs/api.md) — 다른 도구에서 검사를 호출하기
110
+ - [검사 추가하기](CONTRIBUTING.md) — 새 검사를 만드는 방법과 디렉터리 구조
111
+
112
+ ## 라이선스
113
+
114
+ MIT
package/README.md CHANGED
@@ -1,93 +1,137 @@
1
1
  # expo-release-guard
2
2
 
3
- > 빌드 성공은 실행 가능을 뜻하지 않는다.
3
+ **English** · [한국어](README.ko.md)
4
4
 
5
- Expo 앱의 **prebuild 산출물**을 릴리스 전에 검사하는 CLI입니다. CI에서 한 줄로 돌리고, 실패하면 무엇이 왜 필요한지 함께 알려 줍니다.
5
+ > A green build is not a launchable build.
6
+
7
+ A CLI that checks your Expo app's **prebuild output** — the generated `Info.plist`, `AndroidManifest.xml` (source and Gradle-merged), and app icon registration — before you ship. Run it in CI after `expo prebuild`; when a check fails, it tells you *what* is wrong and *why it must not be removed*.
6
8
 
7
9
  ```console
8
10
  $ npx expo-release-guard check
9
11
  ✖ ios-plist: 필수 키 3개 중 1개 누락
10
12
  - NSRemindersUsageDescription가 없거나 비었습니다.
11
- 안 쓰지만 필수 — expo-calendar가 앱 시작 시 조회한다. 없으면 스플래시에서 종료.
13
+ expo-calendar reads this key on launch. Without it the app dies on the splash screen.
14
+ ✔ android-manifest: android/app/src/main/AndroidManifest.xml: 권한 규칙 15개 모두 충족
12
15
 
13
16
  1개 검사가 실패했습니다.
14
17
  ```
15
18
 
16
- ## 왜 만들었나
19
+ > The tool's own messages are currently in Korean (English output is planned). The `reason` lines are whatever you write in your config.
20
+
21
+ ## Why
17
22
 
18
- 소셜 루틴 앱을 운영하면서, **EAS 빌드 성공 · 테스트 990개 통과 · App Store Connect 업로드 성공**을 모두 거친 빌드가 실기기에서 켜자마자 꺼진 적이 있습니다.
23
+ We run a social routine app on the App Store and Google Play. One release **passed the EAS build, all 990 tests, and the App Store Connect upload — and crashed the moment it launched.**
19
24
 
20
- - 캘린더 기능을 붙이며 "쓰지 않는 권한"인 미리알림 키를 지우는 config plugin을 넣었는데,
21
- - `expo-calendar`의 iOS 모듈은 **앱 시작 시** 그 키를 조회했고, 키가 없으면 즉시 종료했습니다.
22
- - `app.json`(입력)은 멀쩡했고 prebuild **산출물**만 망가져 있어서, 입력을 보는 테스트로는 잡을 수 없었습니다.
23
- - 크래시가 오류 수집 도구 초기화보다 먼저 일어나 Sentry에도 기록이 남지 않았습니다.
25
+ - While adding a calendar import, we added a config plugin that stripped the "unused" Reminders permission key.
26
+ - `expo-calendar`'s iOS module reads that key **at startup** and terminates the app if it is missing.
27
+ - `app.json` (the input) was fine. Only the **prebuild output** was broken, so every test that looked at the input passed.
28
+ - The crash happened before Sentry initialized, so nothing was reported.
24
29
 
25
- 통과한 신호 중 어느 것도 "앱이 켜지는가"를 보지 않았습니다. 이 도구는 그 구멍을 막기 위해, 서비스 저장소 안에 흩어져 있던 릴리스 사전 검사 스크립트를 설정 기반의 독립 CLI로 옮긴 것입니다.
30
+ None of the green signals answered "does the app launch?". This tool closes that gap. It started as two scripts inside our app repository and became a config-driven CLI.
26
31
 
27
- **원칙**
32
+ ### Principles
28
33
 
29
- 1. **입력이 아니라 산출물을 본다.** `app.json`이 아니라 `expo prebuild`가 만든 `ios/`, `android/`와 Gradle 병합 결과를 검사합니다.
30
- 2. **이유를 같이 적는다.** 필수 항목마다 `reason`을 받아, 실패 메시지에 "왜 지우면 안 되는지"를 보여 줍니다. 다음 사람이 같은 키를 다시 지우지 않게 하기 위해서입니다.
31
- 3. **추측하지 않는다.** 대상 파일 후보가 여럿이면 하나를 골라 통과시키지 않고 경로 설정을 요구합니다.
34
+ 1. **Check the output, not the input.** It reads what `expo prebuild` and Gradle actually generated, not `app.json`.
35
+ 2. **Every rule carries a reason.** The reason is printed on failure, so the next person does not delete the same key again.
36
+ 3. **Never guess.** If more than one candidate file exists, it fails and asks for an explicit path instead of picking one.
32
37
 
33
- ## 설치
38
+ ## Install
34
39
 
35
40
  ```bash
36
41
  npm i -D expo-release-guard
37
42
  ```
38
43
 
39
- Node.js 22.12 이상이 필요합니다.
44
+ Requires Node.js 22.12+.
45
+
46
+ ## Quick start
40
47
 
41
- ## 빠른 시작
48
+ ```bash
49
+ npx expo prebuild --clean --no-install
50
+ npx expo-release-guard init # writes release-guard.config.json from the current output
51
+ # fill in every "TODO" reason, then:
52
+ npx expo-release-guard check
53
+ ```
42
54
 
43
- 1. 프로젝트 루트에 `release-guard.config.json`을 만듭니다.
55
+ `init` freezes **today's output as the baseline**: usage-description keys, alternate icons, Android permissions (including those marked `tools:node="remove"`), and launcher aliases. Add anything that must *never* appear (location, contacts, …) to `forbidden` yourself — the output cannot tell you that.
56
+
57
+ A hand-written config looks like this:
58
+
59
+ ```json
60
+ {
61
+ "ios": {
62
+ "infoPlist": {
63
+ "requiredKeys": [
64
+ { "key": "NSRemindersUsageDescription", "reason": "expo-calendar reads it on launch. Missing = crash on splash." }
65
+ ]
66
+ }
67
+ },
68
+ "android": {
69
+ "permissions": {
70
+ "requiredMerged": [
71
+ { "name": "POST_NOTIFICATIONS", "reason": "Added by expo-notifications. Missing = no push prompt on Android 13+." }
72
+ ],
73
+ "forbidden": [
74
+ { "name": "CAMERA", "reason": "We only pick from the photo library.", "blockedInSource": true }
75
+ ]
76
+ }
77
+ }
78
+ }
79
+ ```
44
80
 
45
- ```json
46
- {
47
- "ios": {
48
- "infoPlist": {
49
- "requiredKeys": [
50
- { "key": "NSCalendarsUsageDescription", "reason": "캘린더 가져오기. 없으면 권한 요청 자체가 불가." },
51
- { "key": "NSRemindersUsageDescription", "reason": "expo-calendar가 앱 시작 시 조회한다. 없으면 스플래시에서 종료." }
52
- ]
53
- }
54
- }
55
- }
56
- ```
81
+ ## Checks
57
82
 
58
- 2. prebuild 뒤에 실행합니다.
83
+ | id | What it checks | Docs |
84
+ | --- | --- | --- |
85
+ | `ios-plist` | Required `Info.plist` keys are present as non-empty strings in the generated project | [ios-plist](docs/checks/ios-plist.md) |
86
+ | `ios-alternate-icons` | Alternate app icons are registered in **both** the build setting and the asset catalog | [app-icons](docs/checks/app-icons.md) |
87
+ | `android-manifest` | Required / forbidden permissions in the generated source manifest, and that blocked permissions keep their `tools:node="remove"` directive | [android-manifest](docs/checks/android-manifest.md) |
88
+ | `android-merged-manifest` | Required / forbidden permissions in the **Gradle-merged** manifest (what actually ships). Run by id after `processReleaseMainManifest` | [android-manifest](docs/checks/android-manifest.md) |
89
+ | `android-launcher-aliases` | One alias per icon, each a LAUNCHER, exactly one enabled by default, main activity enabled and not a launcher | [app-icons](docs/checks/app-icons.md) |
90
+
91
+ Two Android traps these catch that input checks miss:
92
+
93
+ - **"Absent" is not "safe."** If `blockedPermissions` is dropped from `app.json`, the removal line disappears from the source manifest and the permission silently comes back from a library during the merge. The source check therefore requires the removal *directive*, not just absence.
94
+ - **Library permissions only exist after the merge.** `POST_NOTIFICATIONS` from `expo-notifications` never appears in the source manifest, so it is checked on the merged one.
95
+
96
+ ## CI
97
+
98
+ ### GitHub Action
99
+
100
+ ```yaml
101
+ - uses: actions/checkout@v4
102
+ - uses: actions/setup-node@v4
103
+ with: { node-version: 22 }
104
+ - run: npm ci
105
+ - uses: evan7484/expo-release-guard@v0
106
+ with:
107
+ prebuild: all # none | ios | android | all
108
+ version: 0.2.0 # pin for reproducible CI
109
+ ```
59
110
 
60
- ```bash
61
- npx expo prebuild --platform ios --clean --no-install
62
- npx expo-release-guard check
63
- ```
111
+ Inputs: `checks` (space-separated ids), `config`, `working-directory`, `prebuild`, `version`.
64
112
 
65
- 3. CI에 붙입니다 (GitHub Actions 예시).
113
+ ### Any CI
66
114
 
67
- ```yaml
68
- - run: npx expo prebuild --clean --no-install
69
- - run: npx expo-release-guard check # ios-plist · android-manifest
70
- - run: cd android && ./gradlew :app:processReleaseMainManifest
71
- - run: npx expo-release-guard check android-merged-manifest
72
- ```
115
+ ```yaml
116
+ - run: npx expo prebuild --clean --no-install
117
+ - run: npx expo-release-guard check
118
+ - run: cd android && ./gradlew :app:processReleaseMainManifest
119
+ - run: npx expo-release-guard check android-merged-manifest
120
+ ```
73
121
 
74
- ## 검사 목록
122
+ Exit codes: `0` passed · `1` a check failed · `2` usage or config error — so CI can tell "the app is broken" from "the tool is misconfigured".
75
123
 
76
- | id | 하는 일 | 문서 |
77
- | --- | --- | --- |
78
- | `ios-plist` | prebuild 산출물 `Info.plist`에 필수 키가 비어 있지 않은 문자열로 남아 있는지 | [docs/checks/ios-plist.md](docs/checks/ios-plist.md) |
79
- | `android-manifest` | prebuild 산출물 매니페스트의 필수·금지 권한, `blockedPermissions` 제거 지시자 | [docs/checks/android-manifest.md](docs/checks/android-manifest.md) |
80
- | `android-merged-manifest` | Gradle 병합 매니페스트(최종본)의 필수·금지 권한 — id를 지정해 실행 | [docs/checks/android-manifest.md](docs/checks/android-manifest.md) |
124
+ Tip: if adding a dependency or script changes your native fingerprint (and breaks OTA updates), skip `npm i -D` and call `npx --yes expo-release-guard@<version>` in CI instead.
81
125
 
82
- 이식 예정: 대체 앱 아이콘 등록 검사, OTA 채널·런타임 버전 대조.
126
+ ## Docs
83
127
 
84
- ## 문서
128
+ The reference docs are currently in Korean.
85
129
 
86
- - [CLI 레퍼런스](docs/cli.md) — 명령, 옵션, 종료 코드, JSON 출력 형식
87
- - [설정 레퍼런스](docs/configuration.md) — 설정 파일 위치와 스키마
88
- - [프로그래밍 API](docs/api.md) — 다른 도구에서 검사를 호출하기
89
- - [검사 추가하기](CONTRIBUTING.md) — 새 검사를 만드는 방법과 디렉터리 구조
130
+ - [CLI reference](docs/cli.md) — `init`, `check`, `list`, exit codes, JSON output
131
+ - [Configuration](docs/configuration.md) — file lookup and schema
132
+ - [Programmatic API](docs/api.md)
133
+ - [Adding a check](CONTRIBUTING.md)
90
134
 
91
- ## 라이선스
135
+ ## License
92
136
 
93
137
  MIT