ios-app-review-plugin 1.0.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.
Files changed (205) hide show
  1. package/.claude/settings.local.json +42 -0
  2. package/.github/actions/ios-review/action.yml +106 -0
  3. package/.github/workflows/ci.yml +103 -0
  4. package/.github/workflows/publish.yml +57 -0
  5. package/CHANGELOG.md +66 -0
  6. package/CONTRIBUTING.md +175 -0
  7. package/LICENSE +21 -0
  8. package/README.md +205 -0
  9. package/bitrise/step.sh +128 -0
  10. package/bitrise/step.yml +101 -0
  11. package/dist/analyzer.d.ts.map +1 -0
  12. package/dist/analyzers/asc-iap.d.ts.map +1 -0
  13. package/dist/analyzers/asc-metadata.d.ts.map +1 -0
  14. package/dist/analyzers/asc-screenshots.d.ts.map +1 -0
  15. package/dist/analyzers/asc-version.d.ts.map +1 -0
  16. package/dist/analyzers/code-scanner.d.ts.map +1 -0
  17. package/dist/analyzers/deprecated-api.d.ts.map +1 -0
  18. package/dist/analyzers/entitlements.d.ts.map +1 -0
  19. package/dist/analyzers/index.d.ts.map +1 -0
  20. package/dist/analyzers/info-plist.d.ts.map +1 -0
  21. package/dist/analyzers/privacy.d.ts.map +1 -0
  22. package/dist/analyzers/private-api.d.ts.map +1 -0
  23. package/dist/analyzers/security.d.ts.map +1 -0
  24. package/dist/analyzers/ui-ux.d.ts.map +1 -0
  25. package/dist/asc/auth.d.ts.map +1 -0
  26. package/dist/asc/client.d.ts.map +1 -0
  27. package/dist/asc/endpoints/apps.d.ts.map +1 -0
  28. package/dist/asc/endpoints/iap.d.ts.map +1 -0
  29. package/dist/asc/endpoints/screenshots.d.ts.map +1 -0
  30. package/dist/asc/endpoints/versions.d.ts.map +1 -0
  31. package/dist/asc/errors.d.ts.map +1 -0
  32. package/dist/asc/index.d.ts.map +1 -0
  33. package/dist/asc/types.d.ts.map +1 -0
  34. package/dist/badge/generator.d.ts.map +1 -0
  35. package/dist/badge/index.d.ts.map +1 -0
  36. package/dist/badge/types.d.ts.map +1 -0
  37. package/dist/cache/file-cache.d.ts.map +1 -0
  38. package/dist/cache/index.d.ts.map +1 -0
  39. package/dist/cache/types.d.ts.map +1 -0
  40. package/dist/cli/commands/help.d.ts.map +1 -0
  41. package/dist/cli/commands/scan.d.ts.map +1 -0
  42. package/dist/cli/commands/version.d.ts.map +1 -0
  43. package/dist/cli/index.d.ts.map +1 -0
  44. package/dist/cli/types.d.ts.map +1 -0
  45. package/dist/git/diff.d.ts.map +1 -0
  46. package/dist/git/index.d.ts.map +1 -0
  47. package/dist/git/types.d.ts.map +1 -0
  48. package/dist/guidelines/database.d.ts.map +1 -0
  49. package/dist/guidelines/index.d.ts.map +1 -0
  50. package/dist/guidelines/matcher.d.ts.map +1 -0
  51. package/dist/guidelines/types.d.ts.map +1 -0
  52. package/dist/history/comparator.d.ts.map +1 -0
  53. package/dist/history/index.d.ts.map +1 -0
  54. package/dist/history/store.d.ts.map +1 -0
  55. package/dist/history/types.d.ts.map +1 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +994 -0
  58. package/dist/parsers/index.d.ts.map +1 -0
  59. package/dist/parsers/plist.d.ts.map +1 -0
  60. package/dist/parsers/xcodeproj.d.ts.map +1 -0
  61. package/dist/progress/index.d.ts.map +1 -0
  62. package/dist/progress/reporter.d.ts.map +1 -0
  63. package/dist/progress/types.d.ts.map +1 -0
  64. package/dist/reports/html.d.ts.map +1 -0
  65. package/dist/reports/index.d.ts.map +1 -0
  66. package/dist/reports/json.d.ts.map +1 -0
  67. package/dist/reports/markdown.d.ts.map +1 -0
  68. package/dist/reports/types.d.ts.map +1 -0
  69. package/dist/rules/engine.d.ts.map +1 -0
  70. package/dist/rules/index.d.ts.map +1 -0
  71. package/dist/rules/loader.d.ts.map +1 -0
  72. package/dist/rules/types.d.ts.map +1 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/docs/ANALYZERS.md +237 -0
  75. package/docs/API.md +308 -0
  76. package/docs/BADGES.md +130 -0
  77. package/docs/CI_CD.md +283 -0
  78. package/docs/CLI.md +140 -0
  79. package/docs/REPORTS.md +212 -0
  80. package/docs/ROADMAP.md +267 -0
  81. package/docs/RULES.md +182 -0
  82. package/docs/SECURITY.md +89 -0
  83. package/docs/TROUBLESHOOTING.md +227 -0
  84. package/docs/tutorials/ASC_SETUP.md +188 -0
  85. package/docs/tutorials/CI_INTEGRATION.md +292 -0
  86. package/docs/tutorials/CUSTOM_RULES.md +291 -0
  87. package/docs/tutorials/GETTING_STARTED.md +226 -0
  88. package/docs/video-scripts/01-introduction.md +106 -0
  89. package/docs/video-scripts/02-cli-usage.md +120 -0
  90. package/docs/video-scripts/03-ci-integration.md +198 -0
  91. package/eslint.config.js +33 -0
  92. package/examples/.ios-review-rules.json +82 -0
  93. package/examples/bitrise-workflow.yml +129 -0
  94. package/examples/fastlane-lane.rb +71 -0
  95. package/examples/github-action.yml +147 -0
  96. package/fastlane/Fastfile.example +114 -0
  97. package/fastlane/README.md +99 -0
  98. package/jest.config.js +36 -0
  99. package/package.json +65 -0
  100. package/scripts/benchmark.ts +112 -0
  101. package/scripts/debug-parser.ts +37 -0
  102. package/scripts/debug-pbxproj.ts +36 -0
  103. package/scripts/debug-specific.ts +47 -0
  104. package/scripts/test-analyze.ts +67 -0
  105. package/scripts/xcode-cloud-review.sh +167 -0
  106. package/src/analyzer.ts +227 -0
  107. package/src/analyzers/asc-iap.ts +300 -0
  108. package/src/analyzers/asc-metadata.ts +326 -0
  109. package/src/analyzers/asc-screenshots.ts +310 -0
  110. package/src/analyzers/asc-version.ts +368 -0
  111. package/src/analyzers/code-scanner.ts +408 -0
  112. package/src/analyzers/deprecated-api.ts +390 -0
  113. package/src/analyzers/entitlements.ts +345 -0
  114. package/src/analyzers/index.ts +12 -0
  115. package/src/analyzers/info-plist.ts +409 -0
  116. package/src/analyzers/privacy.ts +376 -0
  117. package/src/analyzers/private-api.ts +377 -0
  118. package/src/analyzers/security.ts +327 -0
  119. package/src/analyzers/ui-ux.ts +509 -0
  120. package/src/asc/auth.ts +204 -0
  121. package/src/asc/client.ts +258 -0
  122. package/src/asc/endpoints/apps.ts +115 -0
  123. package/src/asc/endpoints/iap.ts +171 -0
  124. package/src/asc/endpoints/screenshots.ts +164 -0
  125. package/src/asc/endpoints/versions.ts +174 -0
  126. package/src/asc/errors.ts +109 -0
  127. package/src/asc/index.ts +108 -0
  128. package/src/asc/types.ts +369 -0
  129. package/src/badge/generator.ts +48 -0
  130. package/src/badge/index.ts +2 -0
  131. package/src/badge/types.ts +5 -0
  132. package/src/cache/file-cache.ts +75 -0
  133. package/src/cache/index.ts +2 -0
  134. package/src/cache/types.ts +10 -0
  135. package/src/cli/commands/help.ts +41 -0
  136. package/src/cli/commands/scan.ts +44 -0
  137. package/src/cli/commands/version.ts +12 -0
  138. package/src/cli/index.ts +92 -0
  139. package/src/cli/types.ts +17 -0
  140. package/src/git/diff.ts +21 -0
  141. package/src/git/index.ts +2 -0
  142. package/src/git/types.ts +5 -0
  143. package/src/guidelines/database.ts +344 -0
  144. package/src/guidelines/index.ts +4 -0
  145. package/src/guidelines/matcher.ts +84 -0
  146. package/src/guidelines/types.ts +28 -0
  147. package/src/history/comparator.ts +114 -0
  148. package/src/history/index.ts +3 -0
  149. package/src/history/store.ts +135 -0
  150. package/src/history/types.ts +40 -0
  151. package/src/index.ts +1113 -0
  152. package/src/parsers/index.ts +3 -0
  153. package/src/parsers/plist.ts +253 -0
  154. package/src/parsers/xcodeproj.ts +265 -0
  155. package/src/progress/index.ts +2 -0
  156. package/src/progress/reporter.ts +65 -0
  157. package/src/progress/types.ts +9 -0
  158. package/src/reports/html.ts +322 -0
  159. package/src/reports/index.ts +20 -0
  160. package/src/reports/json.ts +92 -0
  161. package/src/reports/markdown.ts +187 -0
  162. package/src/reports/types.ts +26 -0
  163. package/src/rules/engine.ts +121 -0
  164. package/src/rules/index.ts +3 -0
  165. package/src/rules/loader.ts +83 -0
  166. package/src/rules/types.ts +25 -0
  167. package/src/types/index.ts +247 -0
  168. package/tests/analyzer.test.ts +142 -0
  169. package/tests/analyzers/asc-iap.test.ts +228 -0
  170. package/tests/analyzers/asc-metadata.test.ts +210 -0
  171. package/tests/analyzers/asc-screenshots.test.ts +135 -0
  172. package/tests/analyzers/asc-version.test.ts +259 -0
  173. package/tests/analyzers/code-scanner.test.ts +745 -0
  174. package/tests/analyzers/deprecated-api.test.ts +286 -0
  175. package/tests/analyzers/entitlements.test.ts +411 -0
  176. package/tests/analyzers/info-plist.test.ts +148 -0
  177. package/tests/analyzers/privacy.test.ts +623 -0
  178. package/tests/analyzers/private-api.test.ts +255 -0
  179. package/tests/analyzers/security.test.ts +300 -0
  180. package/tests/analyzers/ui-ux.test.ts +357 -0
  181. package/tests/asc/auth.test.ts +189 -0
  182. package/tests/asc/client.test.ts +207 -0
  183. package/tests/asc/endpoints.test.ts +1359 -0
  184. package/tests/badge/generator.test.ts +73 -0
  185. package/tests/cache/file-cache.test.ts +124 -0
  186. package/tests/cli/cli-index.test.ts +510 -0
  187. package/tests/cli/commands.test.ts +67 -0
  188. package/tests/cli/scan.test.ts +152 -0
  189. package/tests/git/diff.test.ts +69 -0
  190. package/tests/guidelines/matcher.test.ts +209 -0
  191. package/tests/history/comparator.test.ts +272 -0
  192. package/tests/history/store.test.ts +200 -0
  193. package/tests/integration/cli.test.ts +95 -0
  194. package/tests/integration/e2e.test.ts +130 -0
  195. package/tests/parsers/plist.test.ts +240 -0
  196. package/tests/parsers/xcodeproj.test.ts +289 -0
  197. package/tests/progress/reporter.test.ts +117 -0
  198. package/tests/reports/html.test.ts +176 -0
  199. package/tests/reports/json.test.ts +235 -0
  200. package/tests/reports/markdown.test.ts +196 -0
  201. package/tests/rules/engine.test.ts +229 -0
  202. package/tests/rules/loader.test.ts +187 -0
  203. package/tests/setup.ts +15 -0
  204. package/tsconfig.json +27 -0
  205. package/tsconfig.test.json +9 -0
@@ -0,0 +1,292 @@
1
+ # CI Integration Tutorial
2
+
3
+ This tutorial walks through setting up automated App Store review checks in your CI pipeline, step by step.
4
+
5
+ ## Overview
6
+
7
+ The goal: every pull request runs the review checker automatically, blocks merges when errors are found, and tracks your score over time.
8
+
9
+ **Time to set up:** 10-15 minutes per platform.
10
+
11
+ ---
12
+
13
+ ## GitHub Actions (Recommended)
14
+
15
+ ### Step 1: Create the Workflow File
16
+
17
+ ```bash
18
+ mkdir -p .github/workflows
19
+ ```
20
+
21
+ Create `.github/workflows/app-review.yml`:
22
+
23
+ ```yaml
24
+ name: App Store Review Check
25
+
26
+ on:
27
+ pull_request:
28
+ branches: [main, develop]
29
+ push:
30
+ branches: [main]
31
+
32
+ jobs:
33
+ review-check:
34
+ runs-on: macos-latest
35
+ timeout-minutes: 10
36
+
37
+ steps:
38
+ - name: Checkout
39
+ uses: actions/checkout@v4
40
+ with:
41
+ fetch-depth: 0 # needed for --changed-since
42
+
43
+ - name: Setup Node.js
44
+ uses: actions/setup-node@v4
45
+ with:
46
+ node-version: '20'
47
+ cache: 'npm'
48
+
49
+ - name: Install ios-app-review
50
+ run: npm install -g ios-app-review-plugin
51
+
52
+ - name: Run review check
53
+ run: |
54
+ ios-app-review scan ./MyApp.xcodeproj \
55
+ --format json \
56
+ --output report.json \
57
+ --badge \
58
+ --save-history
59
+
60
+ - name: Upload report
61
+ if: always()
62
+ uses: actions/upload-artifact@v4
63
+ with:
64
+ name: review-report
65
+ path: |
66
+ report.json
67
+ badge.svg
68
+ ```
69
+
70
+ ### Step 2: Test It
71
+
72
+ Push the workflow file and open a pull request. The check appears under the PR's "Checks" tab.
73
+
74
+ ### Step 3: Make It a Required Check
75
+
76
+ 1. Go to Settings > Branches > Branch protection rules.
77
+ 2. Edit the rule for `main`.
78
+ 3. Enable "Require status checks to pass before merging".
79
+ 4. Search for "App Store Review Check" and add it.
80
+
81
+ ### Step 4: Add Incremental Scanning for PRs
82
+
83
+ For faster PR checks, scan only changed files:
84
+
85
+ ```yaml
86
+ - name: Run incremental review
87
+ if: github.event_name == 'pull_request'
88
+ run: |
89
+ ios-app-review scan ./MyApp.xcodeproj \
90
+ --changed-since origin/${{ github.base_ref }} \
91
+ --format json \
92
+ --output report.json
93
+
94
+ - name: Run full review
95
+ if: github.event_name == 'push'
96
+ run: |
97
+ ios-app-review scan ./MyApp.xcodeproj \
98
+ --format json \
99
+ --output report.json \
100
+ --save-history
101
+ ```
102
+
103
+ ### Step 5: Add ASC Validation (Optional)
104
+
105
+ 1. In your repo, go to Settings > Secrets and variables > Actions.
106
+ 2. Add secrets: `ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_PRIVATE_KEY` (paste the `.p8` file contents).
107
+ 3. Update the workflow:
108
+
109
+ ```yaml
110
+ - name: Run full review with ASC
111
+ env:
112
+ ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
113
+ ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
114
+ ASC_PRIVATE_KEY_PATH: ${{ runner.temp }}/AuthKey.p8
115
+ run: |
116
+ echo "${{ secrets.ASC_PRIVATE_KEY }}" > "$ASC_PRIVATE_KEY_PATH"
117
+ ios-app-review scan ./MyApp.xcodeproj \
118
+ --include-asc \
119
+ --format json \
120
+ --output report.json
121
+ rm "$ASC_PRIVATE_KEY_PATH"
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Fastlane
127
+
128
+ ### Step 1: Add a Review Lane
129
+
130
+ Edit `fastlane/Fastfile`:
131
+
132
+ ```ruby
133
+ desc "Check App Store review compliance"
134
+ lane :review_check do |options|
135
+ format = options[:format] || "json"
136
+ output = options[:output] || "../review-report.json"
137
+
138
+ sh("ios-app-review scan ../MyApp.xcodeproj --format #{format} --output #{output}")
139
+
140
+ if format == "json"
141
+ report = JSON.parse(File.read(output))
142
+ score = report["score"]
143
+ errors = report["summary"]["errors"]
144
+
145
+ UI.header("App Store Review Results")
146
+ UI.message("Score: #{score}/100")
147
+ UI.message("Errors: #{errors}")
148
+
149
+ if errors > 0
150
+ UI.user_error!("Review check failed with #{errors} error(s)")
151
+ else
152
+ UI.success("Review check passed!")
153
+ end
154
+ end
155
+ end
156
+ ```
157
+
158
+ ### Step 2: Wire It Into Your Build Lane
159
+
160
+ ```ruby
161
+ lane :beta do
162
+ review_check
163
+ build_app(scheme: "MyApp")
164
+ upload_to_testflight
165
+ end
166
+
167
+ lane :release do
168
+ review_check(format: "html", output: "../review-report.html")
169
+ build_app(scheme: "MyApp")
170
+ upload_to_app_store
171
+ end
172
+ ```
173
+
174
+ ### Step 3: Run It
175
+
176
+ ```bash
177
+ bundle exec fastlane review_check
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Bitrise
183
+
184
+ ### Step 1: Add Steps to bitrise.yml
185
+
186
+ ```yaml
187
+ workflows:
188
+ primary:
189
+ steps:
190
+ - git-clone@8: {}
191
+
192
+ - nvm@1:
193
+ inputs:
194
+ - node_version: "20"
195
+
196
+ - script@1:
197
+ title: Install and run ios-app-review
198
+ inputs:
199
+ - content: |
200
+ npm install -g ios-app-review-plugin
201
+ ios-app-review scan ./MyApp.xcodeproj \
202
+ --format json \
203
+ --output "$BITRISE_DEPLOY_DIR/review-report.json" \
204
+ --badge
205
+ cp badge.svg "$BITRISE_DEPLOY_DIR/" 2>/dev/null || true
206
+
207
+ - deploy-to-bitrise-io@2: {}
208
+ ```
209
+
210
+ ### Step 2: Add Secrets
211
+
212
+ In Bitrise workflow editor, go to Secrets and add `ASC_KEY_ID`, `ASC_ISSUER_ID`, and the base64-encoded private key as `ASC_PRIVATE_KEY_B64`.
213
+
214
+ ```yaml
215
+ - script@1:
216
+ title: Setup ASC credentials
217
+ inputs:
218
+ - content: |
219
+ echo "$ASC_PRIVATE_KEY_B64" | base64 -d > /tmp/AuthKey.p8
220
+ export ASC_PRIVATE_KEY_PATH=/tmp/AuthKey.p8
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Xcode Cloud
226
+
227
+ ### Step 1: Create the Script
228
+
229
+ ```bash
230
+ mkdir -p ci_scripts
231
+ ```
232
+
233
+ Create `ci_scripts/ci_post_clone.sh`:
234
+
235
+ ```bash
236
+ #!/bin/bash
237
+ set -e
238
+
239
+ # Install Node.js
240
+ brew install node 2>/dev/null || true
241
+
242
+ # Install the tool
243
+ npm install -g ios-app-review-plugin
244
+
245
+ # Run scan
246
+ ios-app-review scan "$CI_PRIMARY_REPOSITORY_PATH/MyApp.xcodeproj" \
247
+ --format json \
248
+ --output "$CI_PRIMARY_REPOSITORY_PATH/review-report.json"
249
+
250
+ # Read results
251
+ ERRORS=$(python3 -c "import json; print(json.load(open('$CI_PRIMARY_REPOSITORY_PATH/review-report.json'))['summary']['errors'])")
252
+
253
+ if [ "$ERRORS" -gt 0 ]; then
254
+ echo "error: App Store review check failed with $ERRORS error(s)"
255
+ exit 1
256
+ fi
257
+
258
+ echo "App Store review check passed"
259
+ ```
260
+
261
+ ### Step 2: Make It Executable
262
+
263
+ ```bash
264
+ chmod +x ci_scripts/ci_post_clone.sh
265
+ git add ci_scripts/
266
+ git commit -m "Add App Store review CI check"
267
+ ```
268
+
269
+ ### Step 3: Configure in Xcode Cloud
270
+
271
+ The script runs automatically during the post-clone phase. No additional Xcode Cloud configuration is needed beyond ensuring the script is in the `ci_scripts/` directory.
272
+
273
+ ---
274
+
275
+ ## Verifying Your Setup
276
+
277
+ After setting up on any platform:
278
+
279
+ 1. **Introduce a deliberate error** -- add `UIWebView()` somewhere in your code.
280
+ 2. **Push and trigger the CI.**
281
+ 3. **Verify the check fails** with exit code 1.
282
+ 4. **Remove the error and push again.**
283
+ 5. **Verify the check passes** with exit code 0.
284
+
285
+ ## Best Practices
286
+
287
+ 1. **Start with warnings as non-blocking.** Only fail the build on errors. The exit code already does this.
288
+ 2. **Use incremental scanning** on PRs to keep checks fast (< 10 seconds for typical diffs).
289
+ 3. **Run full scans on main** with `--save-history` to track trends.
290
+ 4. **Generate HTML reports** for release branches -- they are easier for non-developers to read.
291
+ 5. **Cache the npm install** to avoid re-downloading on every build.
292
+ 6. **Keep ASC credentials in secrets**, never in the repo.
@@ -0,0 +1,291 @@
1
+ # Custom Rules Tutorial
2
+
3
+ This tutorial walks through creating, testing, and sharing custom rules for your team.
4
+
5
+ ## Why Custom Rules?
6
+
7
+ Built-in analyzers catch universal App Store issues. Custom rules let you enforce:
8
+
9
+ - Team coding conventions (no force casts, required copyright headers)
10
+ - Project-specific patterns (no direct Core Data access outside the data layer)
11
+ - Compliance requirements (no logging PII, required audit comments)
12
+ - Migration tracking (flag old API wrappers that should be replaced)
13
+
14
+ ## Step 1: Create the Config File
15
+
16
+ Create `.ios-review-rules.json` in your project root (the directory containing your `.xcodeproj`):
17
+
18
+ ```json
19
+ {
20
+ "version": 1,
21
+ "rules": [],
22
+ "disabledRules": [],
23
+ "severityOverrides": {}
24
+ }
25
+ ```
26
+
27
+ ## Step 2: Write Your First Rule
28
+
29
+ Add a rule that flags `as!` force casts:
30
+
31
+ ```json
32
+ {
33
+ "version": 1,
34
+ "rules": [
35
+ {
36
+ "id": "team-no-force-cast",
37
+ "title": "Force cast detected",
38
+ "description": "Force casts (as!) can cause crashes. Use conditional casting (as?) instead.",
39
+ "severity": "warning",
40
+ "pattern": "\\bas!\\s",
41
+ "flags": "g",
42
+ "fileTypes": [".swift"],
43
+ "suggestion": "Replace 'as!' with 'as?' and handle the optional."
44
+ }
45
+ ]
46
+ }
47
+ ```
48
+
49
+ ## Step 3: Test the Rule
50
+
51
+ Run a scan to see it in action:
52
+
53
+ ```bash
54
+ ios-app-review scan ./MyApp.xcodeproj --config .ios-review-rules.json
55
+ ```
56
+
57
+ Or validate the config without running a full scan (via MCP):
58
+
59
+ ```
60
+ Validate my custom rules at ./MyApp.xcodeproj
61
+ ```
62
+
63
+ ## Step 4: Add More Rules
64
+
65
+ Here are practical rules you can adapt:
66
+
67
+ ### Require Logger Instead of print
68
+
69
+ ```json
70
+ {
71
+ "id": "team-use-logger",
72
+ "title": "Use Logger instead of print",
73
+ "description": "Use os.Logger for structured logging instead of print().",
74
+ "severity": "warning",
75
+ "pattern": "\\bprint\\s*\\(",
76
+ "flags": "g",
77
+ "fileTypes": [".swift"],
78
+ "suggestion": "Import os and use Logger.log() for production-appropriate logging."
79
+ }
80
+ ```
81
+
82
+ ### Flag Direct UserDefaults Access
83
+
84
+ ```json
85
+ {
86
+ "id": "team-no-direct-userdefaults",
87
+ "title": "Direct UserDefaults access",
88
+ "description": "Use AppSettings wrapper instead of accessing UserDefaults directly.",
89
+ "severity": "warning",
90
+ "pattern": "UserDefaults\\.standard",
91
+ "flags": "g",
92
+ "fileTypes": [".swift"],
93
+ "suggestion": "Use the AppSettings singleton for consistent defaults access."
94
+ }
95
+ ```
96
+
97
+ ### Require Accessibility Labels on Buttons
98
+
99
+ ```json
100
+ {
101
+ "id": "team-button-accessibility",
102
+ "title": "Button without accessibility setup",
103
+ "description": "UIButton created without accessibility label in the same scope.",
104
+ "severity": "info",
105
+ "pattern": "UIButton\\(",
106
+ "flags": "g",
107
+ "fileTypes": [".swift"],
108
+ "suggestion": "Set accessibilityLabel on all interactive UI elements."
109
+ }
110
+ ```
111
+
112
+ ### Flag Hardcoded Colors
113
+
114
+ ```json
115
+ {
116
+ "id": "team-no-hardcoded-colors",
117
+ "title": "Hardcoded UIColor",
118
+ "description": "Use named colors from the asset catalog for consistent theming and dark mode support.",
119
+ "severity": "info",
120
+ "pattern": "UIColor\\(red:|UIColor\\(white:|#colorLiteral",
121
+ "flags": "g",
122
+ "fileTypes": [".swift"],
123
+ "suggestion": "Use UIColor(named:) with colors defined in Assets.xcassets."
124
+ }
125
+ ```
126
+
127
+ ### Detect Missing Error Handling
128
+
129
+ ```json
130
+ {
131
+ "id": "team-no-try-bang",
132
+ "title": "try! without error handling",
133
+ "description": "try! will crash if the operation throws. Use do/catch instead.",
134
+ "severity": "warning",
135
+ "pattern": "\\btry!\\s",
136
+ "flags": "g",
137
+ "fileTypes": [".swift"],
138
+ "suggestion": "Wrap in do/catch block or use try? if the error can be safely ignored."
139
+ }
140
+ ```
141
+
142
+ ## Step 5: Override Built-in Severities
143
+
144
+ Promote built-in warnings to errors or demote info items:
145
+
146
+ ```json
147
+ {
148
+ "version": 1,
149
+ "rules": [],
150
+ "severityOverrides": {
151
+ "hardcoded-ipv4": "error",
152
+ "insecure-http": "error",
153
+ "print-statement": "warning",
154
+ "todo-comment": "warning"
155
+ }
156
+ }
157
+ ```
158
+
159
+ This makes hardcoded IPs and HTTP URLs fail the build (error), and promotes print statements and TODOs to warnings.
160
+
161
+ ## Step 6: Disable Noisy Rules
162
+
163
+ If certain built-in rules generate too much noise for your project:
164
+
165
+ ```json
166
+ {
167
+ "version": 1,
168
+ "rules": [],
169
+ "disabledRules": [
170
+ "force-unwrap",
171
+ "debug-ifdef",
172
+ "print-statement"
173
+ ]
174
+ }
175
+ ```
176
+
177
+ ## Step 7: Use Inline Suppression
178
+
179
+ When a rule fires on a legitimate use:
180
+
181
+ ```swift
182
+ // ios-review-disable-next-line team-no-force-cast
183
+ let vc = storyboard.instantiateViewController(withIdentifier: "Main") as! MainViewController
184
+ ```
185
+
186
+ Suppress multiple rules on one line:
187
+
188
+ ```swift
189
+ // ios-review-disable-next-line team-no-force-cast, team-no-try-bang
190
+ let data = try! JSONEncoder().encode(model as! Encodable)
191
+ ```
192
+
193
+ ## Step 8: Share Rules Across Projects
194
+
195
+ ### Monorepo Approach
196
+
197
+ Place `.ios-review-rules.json` in the repository root. The rule loader walks up from the project directory, so all projects in the repo share the same rules.
198
+
199
+ ```
200
+ monorepo/
201
+ .ios-review-rules.json <-- shared rules
202
+ AppA/
203
+ AppA.xcodeproj
204
+ AppB/
205
+ AppB.xcodeproj
206
+ ```
207
+
208
+ ### Per-project Overrides
209
+
210
+ Place a project-specific config that takes precedence:
211
+
212
+ ```
213
+ monorepo/
214
+ .ios-review-rules.json <-- base rules
215
+ AppA/
216
+ .ios-review-rules.json <-- AppA overrides (found first)
217
+ AppA.xcodeproj
218
+ ```
219
+
220
+ ### External Config
221
+
222
+ Point to any file path:
223
+
224
+ ```bash
225
+ ios-app-review scan ./MyApp.xcodeproj --config ~/team-configs/strict-rules.json
226
+ ```
227
+
228
+ ## Complete Example Config
229
+
230
+ ```json
231
+ {
232
+ "version": 1,
233
+ "rules": [
234
+ {
235
+ "id": "team-no-force-cast",
236
+ "title": "Force cast detected",
237
+ "description": "Force casts (as!) can cause crashes at runtime.",
238
+ "severity": "warning",
239
+ "pattern": "\\bas!\\s",
240
+ "flags": "g",
241
+ "fileTypes": [".swift"],
242
+ "suggestion": "Use 'as?' with optional binding instead."
243
+ },
244
+ {
245
+ "id": "team-no-try-bang",
246
+ "title": "try! without error handling",
247
+ "description": "try! crashes on throw. Use do/catch.",
248
+ "severity": "warning",
249
+ "pattern": "\\btry!\\s",
250
+ "flags": "g",
251
+ "fileTypes": [".swift"],
252
+ "suggestion": "Wrap in do/catch or use try?."
253
+ },
254
+ {
255
+ "id": "team-use-logger",
256
+ "title": "Use Logger instead of print",
257
+ "description": "print() output is not structured and cannot be filtered.",
258
+ "severity": "warning",
259
+ "pattern": "\\bprint\\s*\\(",
260
+ "flags": "g",
261
+ "fileTypes": [".swift"],
262
+ "suggestion": "Use os.Logger for production logging."
263
+ },
264
+ {
265
+ "id": "team-copyright",
266
+ "title": "Missing copyright header",
267
+ "description": "All Swift files must include the team copyright header.",
268
+ "severity": "info",
269
+ "pattern": "^(?!.*Copyright.*ACME)",
270
+ "flags": "",
271
+ "fileTypes": [".swift"],
272
+ "suggestion": "Add '// Copyright ACME Corp' to the file header."
273
+ }
274
+ ],
275
+ "disabledRules": [
276
+ "force-unwrap"
277
+ ],
278
+ "severityOverrides": {
279
+ "insecure-http": "error",
280
+ "hardcoded-ipv4": "error"
281
+ }
282
+ }
283
+ ```
284
+
285
+ ## Debugging Tips
286
+
287
+ 1. **Validate first:** Use `validate_custom_rules` before running full scans.
288
+ 2. **Test regex in JS:** Open a browser console and test `new RegExp("your-pattern", "g").test("sample code")`.
289
+ 3. **Start with `info` severity** while tuning patterns, then promote to `warning` or `error`.
290
+ 4. **Check escaping:** JSON requires `\\` for regex backslashes. `\b` in regex becomes `"\\b"` in JSON.
291
+ 5. **Limit scope with fileTypes:** Avoid false positives in headers or Objective-C by restricting to `.swift`.