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 +114 -0
- package/README.md +99 -55
- package/dist/chunk-UFLUGBK5.js +710 -0
- package/dist/chunk-UFLUGBK5.js.map +1 -0
- package/dist/cli.js +35 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +45 -2
- package/dist/index.js +5 -1
- package/package.json +2 -2
- package/dist/chunk-AI4W7LVX.js +0 -389
- package/dist/chunk-AI4W7LVX.js.map +0 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
21
|
-
- `expo-calendar
|
|
22
|
-
- `app.json`(
|
|
23
|
-
-
|
|
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
|
-
|
|
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.
|
|
30
|
-
2.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
### Any CI
|
|
66
114
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
+
## Docs
|
|
83
127
|
|
|
84
|
-
|
|
128
|
+
The reference docs are currently in Korean.
|
|
85
129
|
|
|
86
|
-
- [CLI
|
|
87
|
-
- [
|
|
88
|
-
- [
|
|
89
|
-
- [
|
|
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
|