appship-cli 0.1.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/LICENSE +21 -0
- package/README.md +93 -0
- package/data/rules/app-store.yml +47 -0
- package/data/rules/google-play.yml +51 -0
- package/data/sdk-signatures/signatures.yml +376 -0
- package/dist/chunk-7GCQ6I27.js +177 -0
- package/dist/chunk-CVYD5FVI.js +8 -0
- package/dist/chunk-ICHGACSP.js +98 -0
- package/dist/chunk-TLOV7VBN.js +105 -0
- package/dist/flutter-ITOL6J4H.js +12 -0
- package/dist/index.js +3697 -0
- package/dist/native-RCWBM6UF.js +14 -0
- package/dist/upload-FMEKKZSD.js +21 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gunwoo Ko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# AppShip
|
|
2
|
+
|
|
3
|
+
> AppShip is an open-source AI release assistant that analyzes your mobile app and prepares everything required for App Store and Google Play submission.
|
|
4
|
+
|
|
5
|
+
Ship your mobile app without spending hours inside App Store Connect and Google Play Console.
|
|
6
|
+
|
|
7
|
+
**Status: MVP 3 implemented (early development).** React Native, Expo, Flutter, native iOS, and native Android projects. See [docs/PRD.md](./docs/PRD.md) and [docs/TRD.md](./docs/TRD.md) for the full plan.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g appship-cli # installs the `appship` command
|
|
13
|
+
appship --help
|
|
14
|
+
|
|
15
|
+
# or run one-off without installing
|
|
16
|
+
npx appship-cli init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
appship init # analyze your React Native project, ask a few questions
|
|
23
|
+
appship generate # generate store metadata, privacy docs, legal drafts into .appship/
|
|
24
|
+
appship localize ko-KR ja-JP # translate generated listings into more locales
|
|
25
|
+
appship export fastlane # export listings to fastlane deliver/supply layouts (offline)
|
|
26
|
+
appship export ci # generate GitHub Actions workflows (readiness gate + release)
|
|
27
|
+
appship upload ios # upload a built .ipa to TestFlight via fastlane
|
|
28
|
+
appship upload android # upload a built .aab to a Play track (default: internal)
|
|
29
|
+
appship screenshots flows # turn the screenshot plan into Maestro flows (offline)
|
|
30
|
+
appship screenshots capture # run the flows on a device/emulator via Maestro
|
|
31
|
+
appship submit ios # submit the latest uploaded build for App Store review
|
|
32
|
+
appship submit android # promote a tested Play track to production review
|
|
33
|
+
appship doctor # check submission readiness and rejection risks (offline)
|
|
34
|
+
appship review analyze r.txt # turn a store rejection message into a fix plan
|
|
35
|
+
appship rules update # fetch the latest policy rules & SDK signatures
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`export fastlane` maps everything under `.appship/` into `fastlane/metadata/` (deliver) and `fastlane/metadata/android/` (supply), plus starter `Appfile`/`Fastfile` upload lanes — existing fastlane files are never overwritten without `--force`. Upload with `bundle exec fastlane ios upload_metadata` / `... android upload_metadata`.
|
|
39
|
+
|
|
40
|
+
`export ci` writes two GitHub Actions workflows: a readiness gate that runs `appship doctor` on every PR (works out of the box — doctor is offline), and a `workflow_dispatch` release pipeline that builds, uploads via `appship upload --yes`, and optionally submits. The build steps are project-specific, so they carry `TODO(appship)` markers for you to finish (Android is pre-filled for gradle/Expo prebuild; iOS signing needs your fastlane match setup). Existing workflow files are never overwritten without `--force`.
|
|
41
|
+
|
|
42
|
+
`upload` finds your newest built artifact (or takes `--ipa`/`--aab`), shows exactly what will run, asks for confirmation (`--yes` to skip, required in CI), then delegates to the fastlane lanes from `export fastlane`. fastlane owns store auth — set up an App Store Connect API key / Play service account per fastlane's docs.
|
|
43
|
+
|
|
44
|
+
`submit` is the confirmation-gated final step. It first runs the doctor checks for the target store and refuses on error-severity failures (`--force` to override), shows exactly what will run, then asks for confirmation (`--yes` to skip, required in CI). iOS submits the latest build already on App Store Connect (`--build-number` to pick one) with manual release after approval; Android promotes a tested track (`--from-track`, default `internal`) to `--track` (default `production`). Like `upload`, it delegates to the `:submit_review` fastlane lanes from `export fastlane` — re-export with `--force` if your Fastfile predates them.
|
|
45
|
+
|
|
46
|
+
`screenshots flows` generates one [Maestro](https://maestro.mobile.dev) flow per screen in the plan; each flow carries a navigation TODO that only you can fill in (appship can't know how to reach your screens). `capture` refuses to run while TODOs remain, then runs `maestro test` and drops PNGs into `.appship/screenshots/raw/`. Your edited flows are never overwritten without `--force`.
|
|
47
|
+
|
|
48
|
+
`review analyze` reads a rejection message you copied from App Store Connect / Play Console into a text file, and produces per-issue fix steps, the appship commands that help, and (for issues resolvable by replying) a draft response to the review team — written to `.appship/review/rejection-analysis.md`. Guidelines are only cited when the message cites them; nothing is invented beyond the message and the scanned project facts.
|
|
49
|
+
|
|
50
|
+
Store policies change between releases, so the doctor rules and SDK signature database are plain data files that can be refreshed without a new CLI version: `appship rules update` downloads them from this repository into `~/.appship/data-cache` (schema-validated before anything is written; nothing partial is ever observable), `rules status` shows what is in use, and `rules reset` returns to the bundled files. If a cached file ever stops parsing (e.g. you run a much newer rule format with an old CLI), appship silently falls back to its bundled data.
|
|
51
|
+
|
|
52
|
+
`generate`, `localize`, and `review analyze` call an AI provider — Anthropic by default (`ANTHROPIC_API_KEY` or `ant auth login`), and any OpenAI-compatible endpoint via `ai.provider` in appship.yml: `gemini` (free keys at [aistudio.google.com](https://aistudio.google.com/apikey), `GEMINI_API_KEY`, default model `gemini-2.5-flash`), `openai` (`OPENAI_API_KEY`), `ollama` (local, set `ai.model`), or `openai-compatible` (set `ai.base_url` + `ai.model` — works with OpenRouter, Groq, and self-hosted servers). Store character limits are validated with retry regardless of provider. `init` and `doctor` run fully offline. Translations go through the same validation as generation; App Review notes are copied, not translated.
|
|
53
|
+
|
|
54
|
+
Supported projects: React Native CLI, Expo (managed), Flutter, native iOS (`.xcodeproj` at the root), and native Android (`settings.gradle` + `app/` module) — identity and permissions are read from native files (`Info.plist`, `AndroidManifest.xml`, `project.pbxproj`, `build.gradle`), from `app.json`'s `expo` config, or from `pubspec.yaml`. SDK detection matches npm packages, Dart/Flutter packages, CocoaPods, and Gradle Maven coordinates, plus source patterns in JS/TS, Dart, Swift, Kotlin, and Java.
|
|
55
|
+
|
|
56
|
+
## Using doctor as a CI gate
|
|
57
|
+
|
|
58
|
+
`appship doctor` exits with code 1 when any error-severity check fails, so it works as a release gate:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
# .github/workflows/release-readiness.yml
|
|
62
|
+
name: Release readiness
|
|
63
|
+
on: [pull_request]
|
|
64
|
+
jobs:
|
|
65
|
+
doctor:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
- uses: actions/setup-node@v4
|
|
70
|
+
with: { node-version: 22 }
|
|
71
|
+
- run: npx appship-cli doctor # fails the job on ✗ errors
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Add `--json` for machine-readable output; the full report is also written to `.appship/checklist/release-readiness.json`.
|
|
75
|
+
|
|
76
|
+
## Principles
|
|
77
|
+
|
|
78
|
+
1. **AI never invents facts** — every finding carries evidence (file paths), and anything code can't prove is confirmed by you, not assumed.
|
|
79
|
+
2. **Your source code stays local** — scanning happens on your machine; only a summary (project type, permissions, SDK list) is sent to the AI provider.
|
|
80
|
+
3. **Nothing is submitted without explicit confirmation.**
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm install
|
|
86
|
+
npm run dev -- --help # run the CLI
|
|
87
|
+
npm test # run tests
|
|
88
|
+
npm run typecheck
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# doctor rules for the App Store (TRD §7).
|
|
2
|
+
# severity: error -> ✗ (weight 3), warning -> ⚠ (weight 1)
|
|
3
|
+
|
|
4
|
+
- id: apple-name-length
|
|
5
|
+
store: app-store
|
|
6
|
+
category: length
|
|
7
|
+
severity: error
|
|
8
|
+
target: app-store/*/name.txt
|
|
9
|
+
check: { type: max_length, value: 30 }
|
|
10
|
+
message: "App name exceeds Apple's 30 character limit"
|
|
11
|
+
|
|
12
|
+
- id: apple-subtitle-length
|
|
13
|
+
store: app-store
|
|
14
|
+
category: length
|
|
15
|
+
severity: error
|
|
16
|
+
target: app-store/*/subtitle.txt
|
|
17
|
+
check: { type: max_length, value: 30 }
|
|
18
|
+
message: "Subtitle exceeds Apple's 30 character limit"
|
|
19
|
+
|
|
20
|
+
- id: apple-keywords-length
|
|
21
|
+
store: app-store
|
|
22
|
+
category: length
|
|
23
|
+
severity: error
|
|
24
|
+
target: app-store/*/keywords.txt
|
|
25
|
+
check: { type: max_length, value: 100 }
|
|
26
|
+
message: "Keywords exceed Apple's 100 character limit"
|
|
27
|
+
|
|
28
|
+
- id: account-deletion-required
|
|
29
|
+
store: app-store
|
|
30
|
+
category: consistency
|
|
31
|
+
severity: error
|
|
32
|
+
condition: { finding: login_detected }
|
|
33
|
+
check: { type: file_exists, value: legal/account-deletion.md }
|
|
34
|
+
message: "User registration detected but no account deletion flow found"
|
|
35
|
+
guideline: "Apple Guideline 5.1.1"
|
|
36
|
+
fix_suggestions:
|
|
37
|
+
- "Add account deletion under Settings > Account"
|
|
38
|
+
- "Mention the deletion path in App Review Notes"
|
|
39
|
+
|
|
40
|
+
- id: unresolved-confirm-placeholders
|
|
41
|
+
store: app-store
|
|
42
|
+
category: consistency
|
|
43
|
+
severity: error
|
|
44
|
+
target: "**/*.md"
|
|
45
|
+
check: { type: not_contains, value: "[CONFIRM:" }
|
|
46
|
+
message: "Generated document still contains unresolved [CONFIRM] placeholders"
|
|
47
|
+
pass_message: "No unresolved [CONFIRM] placeholders"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# doctor rules for Google Play (TRD §7).
|
|
2
|
+
|
|
3
|
+
- id: play-title-length
|
|
4
|
+
store: google-play
|
|
5
|
+
category: length
|
|
6
|
+
severity: error
|
|
7
|
+
target: google-play/*/title.txt
|
|
8
|
+
check: { type: max_length, value: 30 }
|
|
9
|
+
message: "App title exceeds Google Play's 30 character limit"
|
|
10
|
+
|
|
11
|
+
- id: play-short-description-length
|
|
12
|
+
store: google-play
|
|
13
|
+
category: length
|
|
14
|
+
severity: error
|
|
15
|
+
target: google-play/*/short-description.txt
|
|
16
|
+
check: { type: max_length, value: 80 }
|
|
17
|
+
message: "Short description exceeds Google Play's 80 character limit"
|
|
18
|
+
|
|
19
|
+
- id: play-full-description-length
|
|
20
|
+
store: google-play
|
|
21
|
+
category: length
|
|
22
|
+
severity: error
|
|
23
|
+
target: google-play/*/full-description.txt
|
|
24
|
+
check: { type: max_length, value: 4000 }
|
|
25
|
+
message: "Full description exceeds Google Play's 4000 character limit"
|
|
26
|
+
|
|
27
|
+
- id: play-no-emoji-in-title
|
|
28
|
+
store: google-play
|
|
29
|
+
category: policy
|
|
30
|
+
severity: warning
|
|
31
|
+
target: google-play/*/title.txt
|
|
32
|
+
check: { type: no_emoji }
|
|
33
|
+
message: "Emoji in the title may violate Google Play metadata policy"
|
|
34
|
+
|
|
35
|
+
- id: data-safety-confirm-markers
|
|
36
|
+
store: google-play
|
|
37
|
+
category: consistency
|
|
38
|
+
severity: error
|
|
39
|
+
target: google-play/data-safety.yml
|
|
40
|
+
check: { type: not_contains, value: "confirm_before_submitting" }
|
|
41
|
+
message: "data-safety.yml still contains confirm_before_submitting markers — resolve them before filling the Play Console form"
|
|
42
|
+
pass_message: "Data Safety confirm markers resolved"
|
|
43
|
+
|
|
44
|
+
- id: play-unresolved-confirm-placeholders
|
|
45
|
+
store: google-play
|
|
46
|
+
category: consistency
|
|
47
|
+
severity: error
|
|
48
|
+
target: legal/*.md
|
|
49
|
+
check: { type: not_contains, value: "[CONFIRM:" }
|
|
50
|
+
message: "Generated legal documents still contain unresolved [CONFIRM] placeholders"
|
|
51
|
+
pass_message: "No unresolved [CONFIRM] placeholders in legal documents"
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# SDK signature database (TRD §6).
|
|
2
|
+
# Declarative — adding a signature must never require a code change.
|
|
3
|
+
#
|
|
4
|
+
# confidence rules applied by the scanner:
|
|
5
|
+
# dependencies match -> high
|
|
6
|
+
# source_patterns only -> medium
|
|
7
|
+
# config_keys only -> low
|
|
8
|
+
|
|
9
|
+
- id: firebase-analytics
|
|
10
|
+
name: Firebase Analytics
|
|
11
|
+
category: analytics
|
|
12
|
+
detect:
|
|
13
|
+
dependencies:
|
|
14
|
+
- "@react-native-firebase/analytics"
|
|
15
|
+
- "firebase_analytics"
|
|
16
|
+
- "FirebaseAnalytics"
|
|
17
|
+
- "com.google.firebase:firebase-analytics"
|
|
18
|
+
source_patterns:
|
|
19
|
+
- "analytics\\(\\)\\.logEvent"
|
|
20
|
+
- "FirebaseAnalytics\\.instance"
|
|
21
|
+
- "Analytics\\.logEvent"
|
|
22
|
+
- "Firebase\\.analytics"
|
|
23
|
+
config_keys: []
|
|
24
|
+
data_safety:
|
|
25
|
+
collects: [device_id, app_interactions]
|
|
26
|
+
purpose_defaults: [analytics]
|
|
27
|
+
shared_default: true
|
|
28
|
+
requires_confirmation: false
|
|
29
|
+
|
|
30
|
+
- id: sentry
|
|
31
|
+
name: Sentry
|
|
32
|
+
category: crash-reporting
|
|
33
|
+
detect:
|
|
34
|
+
dependencies:
|
|
35
|
+
- "@sentry/react-native"
|
|
36
|
+
- "sentry_flutter"
|
|
37
|
+
- "Sentry"
|
|
38
|
+
- "io.sentry:sentry-android"
|
|
39
|
+
source_patterns:
|
|
40
|
+
- "Sentry\\.init"
|
|
41
|
+
- "SentryFlutter\\.init"
|
|
42
|
+
- "SentrySDK\\.start"
|
|
43
|
+
config_keys: []
|
|
44
|
+
data_safety:
|
|
45
|
+
collects: [crash_logs, diagnostics, device_id]
|
|
46
|
+
purpose_defaults: [app_functionality]
|
|
47
|
+
shared_default: true
|
|
48
|
+
requires_confirmation: false
|
|
49
|
+
|
|
50
|
+
- id: revenuecat
|
|
51
|
+
name: RevenueCat
|
|
52
|
+
category: payments
|
|
53
|
+
detect:
|
|
54
|
+
dependencies:
|
|
55
|
+
- "react-native-purchases"
|
|
56
|
+
- "purchases_flutter"
|
|
57
|
+
- "RevenueCat"
|
|
58
|
+
- "com.revenuecat.purchases:purchases"
|
|
59
|
+
source_patterns:
|
|
60
|
+
- "Purchases\\.configure"
|
|
61
|
+
config_keys: []
|
|
62
|
+
data_safety:
|
|
63
|
+
collects: [purchase_history]
|
|
64
|
+
purpose_defaults: [app_functionality]
|
|
65
|
+
shared_default: true
|
|
66
|
+
requires_confirmation: false
|
|
67
|
+
|
|
68
|
+
- id: location
|
|
69
|
+
name: Location APIs
|
|
70
|
+
category: device-api
|
|
71
|
+
detect:
|
|
72
|
+
dependencies:
|
|
73
|
+
- "@react-native-community/geolocation"
|
|
74
|
+
- "react-native-geolocation-service"
|
|
75
|
+
- "geolocator"
|
|
76
|
+
- "location"
|
|
77
|
+
source_patterns:
|
|
78
|
+
- "Geolocation\\.getCurrentPosition"
|
|
79
|
+
- "watchPosition"
|
|
80
|
+
- "Geolocator\\."
|
|
81
|
+
config_keys:
|
|
82
|
+
- NSLocationWhenInUseUsageDescription
|
|
83
|
+
- android.permission.ACCESS_FINE_LOCATION
|
|
84
|
+
- android.permission.ACCESS_COARSE_LOCATION
|
|
85
|
+
data_safety:
|
|
86
|
+
collects: [location]
|
|
87
|
+
purpose_defaults: [app_functionality]
|
|
88
|
+
shared_default: false
|
|
89
|
+
# precise vs approximate vs local-only cannot be decided from code
|
|
90
|
+
requires_confirmation: true
|
|
91
|
+
|
|
92
|
+
- id: amplitude
|
|
93
|
+
name: Amplitude
|
|
94
|
+
category: analytics
|
|
95
|
+
detect:
|
|
96
|
+
dependencies:
|
|
97
|
+
- "@amplitude/analytics-react-native"
|
|
98
|
+
- "@amplitude/react-native"
|
|
99
|
+
- "amplitude_flutter"
|
|
100
|
+
- "AmplitudeSwift"
|
|
101
|
+
- "com.amplitude:analytics-android"
|
|
102
|
+
source_patterns:
|
|
103
|
+
- "amplitude\\.(init|track)"
|
|
104
|
+
config_keys: []
|
|
105
|
+
data_safety:
|
|
106
|
+
collects: [device_id, app_interactions]
|
|
107
|
+
purpose_defaults: [analytics]
|
|
108
|
+
shared_default: true
|
|
109
|
+
requires_confirmation: false
|
|
110
|
+
|
|
111
|
+
- id: mixpanel
|
|
112
|
+
name: Mixpanel
|
|
113
|
+
category: analytics
|
|
114
|
+
detect:
|
|
115
|
+
dependencies:
|
|
116
|
+
- "mixpanel-react-native"
|
|
117
|
+
- "mixpanel_flutter"
|
|
118
|
+
- "Mixpanel-swift"
|
|
119
|
+
- "com.mixpanel.android:mixpanel-android"
|
|
120
|
+
source_patterns:
|
|
121
|
+
- "new Mixpanel\\("
|
|
122
|
+
- "Mixpanel\\.initialize"
|
|
123
|
+
config_keys: []
|
|
124
|
+
data_safety:
|
|
125
|
+
collects: [device_id, app_interactions]
|
|
126
|
+
purpose_defaults: [analytics]
|
|
127
|
+
shared_default: true
|
|
128
|
+
requires_confirmation: false
|
|
129
|
+
|
|
130
|
+
- id: firebase-crashlytics
|
|
131
|
+
name: Firebase Crashlytics
|
|
132
|
+
category: crash-reporting
|
|
133
|
+
detect:
|
|
134
|
+
dependencies:
|
|
135
|
+
- "@react-native-firebase/crashlytics"
|
|
136
|
+
- "firebase_crashlytics"
|
|
137
|
+
- "FirebaseCrashlytics"
|
|
138
|
+
- "com.google.firebase:firebase-crashlytics"
|
|
139
|
+
source_patterns:
|
|
140
|
+
- "crashlytics\\(\\)"
|
|
141
|
+
- "FirebaseCrashlytics\\.instance"
|
|
142
|
+
- "Crashlytics\\.crashlytics"
|
|
143
|
+
config_keys: []
|
|
144
|
+
data_safety:
|
|
145
|
+
collects: [crash_logs, diagnostics, device_id]
|
|
146
|
+
purpose_defaults: [app_functionality]
|
|
147
|
+
shared_default: true
|
|
148
|
+
requires_confirmation: false
|
|
149
|
+
|
|
150
|
+
- id: google-signin
|
|
151
|
+
name: Google Sign-In
|
|
152
|
+
category: auth
|
|
153
|
+
detect:
|
|
154
|
+
dependencies:
|
|
155
|
+
- "@react-native-google-signin/google-signin"
|
|
156
|
+
- "google_sign_in"
|
|
157
|
+
- "GoogleSignIn"
|
|
158
|
+
- "com.google.android.gms:play-services-auth"
|
|
159
|
+
source_patterns:
|
|
160
|
+
- "GoogleSignin\\."
|
|
161
|
+
- "GoogleSignIn\\("
|
|
162
|
+
config_keys: []
|
|
163
|
+
data_safety:
|
|
164
|
+
collects: [email, name, account_info]
|
|
165
|
+
purpose_defaults: [app_functionality]
|
|
166
|
+
shared_default: true
|
|
167
|
+
requires_confirmation: false
|
|
168
|
+
|
|
169
|
+
- id: apple-signin
|
|
170
|
+
name: Apple Sign-In
|
|
171
|
+
category: auth
|
|
172
|
+
detect:
|
|
173
|
+
dependencies:
|
|
174
|
+
- "@invertase/react-native-apple-authentication"
|
|
175
|
+
- "sign_in_with_apple"
|
|
176
|
+
source_patterns:
|
|
177
|
+
- "appleAuth\\."
|
|
178
|
+
- "SignInWithApple\\."
|
|
179
|
+
- "ASAuthorizationAppleIDProvider"
|
|
180
|
+
config_keys: []
|
|
181
|
+
data_safety:
|
|
182
|
+
collects: [email, name]
|
|
183
|
+
purpose_defaults: [app_functionality]
|
|
184
|
+
shared_default: false
|
|
185
|
+
requires_confirmation: false
|
|
186
|
+
|
|
187
|
+
- id: facebook-sdk
|
|
188
|
+
name: Facebook SDK
|
|
189
|
+
category: auth
|
|
190
|
+
detect:
|
|
191
|
+
dependencies:
|
|
192
|
+
- "react-native-fbsdk-next"
|
|
193
|
+
- "react-native-fbsdk"
|
|
194
|
+
- "flutter_facebook_auth"
|
|
195
|
+
- "facebook_app_events"
|
|
196
|
+
- "FacebookLogin"
|
|
197
|
+
- "com.facebook.android:facebook-android-sdk"
|
|
198
|
+
source_patterns:
|
|
199
|
+
- "LoginManager\\.logInWithPermissions"
|
|
200
|
+
config_keys: []
|
|
201
|
+
data_safety:
|
|
202
|
+
collects: [device_id, app_interactions, account_info]
|
|
203
|
+
purpose_defaults: [app_functionality, advertising]
|
|
204
|
+
shared_default: true
|
|
205
|
+
requires_confirmation: false
|
|
206
|
+
|
|
207
|
+
- id: stripe
|
|
208
|
+
name: Stripe
|
|
209
|
+
category: payments
|
|
210
|
+
detect:
|
|
211
|
+
dependencies:
|
|
212
|
+
- "@stripe/stripe-react-native"
|
|
213
|
+
- "flutter_stripe"
|
|
214
|
+
- "StripePaymentSheet"
|
|
215
|
+
- "com.stripe:stripe-android"
|
|
216
|
+
source_patterns:
|
|
217
|
+
- "useStripe\\(|initPaymentSheet"
|
|
218
|
+
config_keys: []
|
|
219
|
+
data_safety:
|
|
220
|
+
collects: [payment_info, purchase_history]
|
|
221
|
+
purpose_defaults: [app_functionality]
|
|
222
|
+
shared_default: true
|
|
223
|
+
requires_confirmation: false
|
|
224
|
+
|
|
225
|
+
- id: admob
|
|
226
|
+
name: AdMob
|
|
227
|
+
category: ads
|
|
228
|
+
detect:
|
|
229
|
+
dependencies:
|
|
230
|
+
- "react-native-google-mobile-ads"
|
|
231
|
+
- "google_mobile_ads"
|
|
232
|
+
- "Google-Mobile-Ads-SDK"
|
|
233
|
+
- "com.google.android.gms:play-services-ads"
|
|
234
|
+
source_patterns:
|
|
235
|
+
- "BannerAd|InterstitialAd|RewardedAd"
|
|
236
|
+
- "MobileAds\\.instance"
|
|
237
|
+
- "GADMobileAds|MobileAds\\.initialize"
|
|
238
|
+
config_keys: []
|
|
239
|
+
data_safety:
|
|
240
|
+
collects: [advertising_id, device_id]
|
|
241
|
+
purpose_defaults: [advertising]
|
|
242
|
+
shared_default: true
|
|
243
|
+
requires_confirmation: false
|
|
244
|
+
|
|
245
|
+
- id: appsflyer
|
|
246
|
+
name: AppsFlyer
|
|
247
|
+
category: ads
|
|
248
|
+
detect:
|
|
249
|
+
dependencies:
|
|
250
|
+
- "react-native-appsflyer"
|
|
251
|
+
- "appsflyer_sdk"
|
|
252
|
+
- "AppsFlyerFramework"
|
|
253
|
+
- "com.appsflyer:af-android-sdk"
|
|
254
|
+
source_patterns:
|
|
255
|
+
- "appsFlyer\\."
|
|
256
|
+
- "AppsFlyerLib"
|
|
257
|
+
config_keys: []
|
|
258
|
+
data_safety:
|
|
259
|
+
collects: [advertising_id, device_id, app_interactions]
|
|
260
|
+
purpose_defaults: [analytics, advertising]
|
|
261
|
+
shared_default: true
|
|
262
|
+
requires_confirmation: false
|
|
263
|
+
|
|
264
|
+
- id: onesignal
|
|
265
|
+
name: OneSignal
|
|
266
|
+
category: push
|
|
267
|
+
detect:
|
|
268
|
+
dependencies:
|
|
269
|
+
- "react-native-onesignal"
|
|
270
|
+
- "onesignal_flutter"
|
|
271
|
+
- "OneSignalXCFramework"
|
|
272
|
+
- "com.onesignal:OneSignal"
|
|
273
|
+
source_patterns:
|
|
274
|
+
- "OneSignal\\."
|
|
275
|
+
config_keys: []
|
|
276
|
+
data_safety:
|
|
277
|
+
collects: [device_id]
|
|
278
|
+
purpose_defaults: [app_functionality]
|
|
279
|
+
shared_default: true
|
|
280
|
+
requires_confirmation: false
|
|
281
|
+
|
|
282
|
+
- id: firebase-messaging
|
|
283
|
+
name: Firebase Messaging
|
|
284
|
+
category: push
|
|
285
|
+
detect:
|
|
286
|
+
dependencies:
|
|
287
|
+
- "@react-native-firebase/messaging"
|
|
288
|
+
- "firebase_messaging"
|
|
289
|
+
- "FirebaseMessaging"
|
|
290
|
+
- "com.google.firebase:firebase-messaging"
|
|
291
|
+
source_patterns:
|
|
292
|
+
- "messaging\\(\\)\\.(getToken|onMessage)"
|
|
293
|
+
- "FirebaseMessaging\\.instance"
|
|
294
|
+
- "Messaging\\.messaging"
|
|
295
|
+
config_keys: []
|
|
296
|
+
data_safety:
|
|
297
|
+
collects: [device_id]
|
|
298
|
+
purpose_defaults: [app_functionality]
|
|
299
|
+
shared_default: true
|
|
300
|
+
requires_confirmation: false
|
|
301
|
+
|
|
302
|
+
- id: microphone
|
|
303
|
+
name: Microphone
|
|
304
|
+
category: device-api
|
|
305
|
+
detect:
|
|
306
|
+
dependencies:
|
|
307
|
+
- "record"
|
|
308
|
+
- "flutter_sound"
|
|
309
|
+
- "speech_to_text"
|
|
310
|
+
source_patterns: []
|
|
311
|
+
config_keys:
|
|
312
|
+
- NSMicrophoneUsageDescription
|
|
313
|
+
- android.permission.RECORD_AUDIO
|
|
314
|
+
data_safety:
|
|
315
|
+
collects: [voice_or_audio]
|
|
316
|
+
purpose_defaults: [app_functionality]
|
|
317
|
+
shared_default: false
|
|
318
|
+
# recorded audio may be processed locally only — confirm
|
|
319
|
+
requires_confirmation: true
|
|
320
|
+
|
|
321
|
+
- id: camera
|
|
322
|
+
name: Camera
|
|
323
|
+
category: device-api
|
|
324
|
+
detect:
|
|
325
|
+
dependencies:
|
|
326
|
+
- "react-native-vision-camera"
|
|
327
|
+
- "react-native-camera"
|
|
328
|
+
- "react-native-image-picker"
|
|
329
|
+
- "camera"
|
|
330
|
+
- "image_picker"
|
|
331
|
+
source_patterns: []
|
|
332
|
+
config_keys:
|
|
333
|
+
- NSCameraUsageDescription
|
|
334
|
+
- android.permission.CAMERA
|
|
335
|
+
data_safety:
|
|
336
|
+
collects: [photos_and_videos]
|
|
337
|
+
purpose_defaults: [app_functionality]
|
|
338
|
+
shared_default: false
|
|
339
|
+
requires_confirmation: true
|
|
340
|
+
|
|
341
|
+
- id: contacts
|
|
342
|
+
name: Contacts
|
|
343
|
+
category: device-api
|
|
344
|
+
detect:
|
|
345
|
+
dependencies:
|
|
346
|
+
- "react-native-contacts"
|
|
347
|
+
- "flutter_contacts"
|
|
348
|
+
source_patterns: []
|
|
349
|
+
config_keys:
|
|
350
|
+
- NSContactsUsageDescription
|
|
351
|
+
- android.permission.READ_CONTACTS
|
|
352
|
+
data_safety:
|
|
353
|
+
collects: [contacts]
|
|
354
|
+
purpose_defaults: [app_functionality]
|
|
355
|
+
shared_default: false
|
|
356
|
+
requires_confirmation: true
|
|
357
|
+
|
|
358
|
+
- id: advertising-id
|
|
359
|
+
name: Advertising ID / Tracking
|
|
360
|
+
category: device-api
|
|
361
|
+
detect:
|
|
362
|
+
dependencies:
|
|
363
|
+
- "react-native-tracking-transparency"
|
|
364
|
+
- "app_tracking_transparency"
|
|
365
|
+
- "com.google.android.gms:play-services-ads-identifier"
|
|
366
|
+
source_patterns:
|
|
367
|
+
- "requestTrackingPermission"
|
|
368
|
+
- "ATTrackingManager"
|
|
369
|
+
config_keys:
|
|
370
|
+
- NSUserTrackingUsageDescription
|
|
371
|
+
- com.google.android.gms.permission.AD_ID
|
|
372
|
+
data_safety:
|
|
373
|
+
collects: [advertising_id]
|
|
374
|
+
purpose_defaults: [advertising]
|
|
375
|
+
shared_default: true
|
|
376
|
+
requires_confirmation: true
|