@agustin-perticaro/store-pilot 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Agustin Perticaro
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,209 @@
1
+ # store-pilot
2
+
3
+ CLI to automate ASO, builds, and store uploads for Expo/React Native projects **without EAS**.
4
+
5
+ One command to do it all: keywords → screenshots → build → upload.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install -g store-pilot
13
+ ```
14
+
15
+ Requires Node.js ≥ 18, Ruby + Bundler, and fastlane installed.
16
+
17
+ ```bash
18
+ gem install fastlane
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Setup (once per project)
24
+
25
+ ```bash
26
+ cd your-expo-project
27
+ store-pilot init
28
+ ```
29
+
30
+ `init` auto-detects your `app.json`, asks questions, and generates:
31
+
32
+ - `fastlane/Appfile` — identifies your app
33
+ - `fastlane/Fastfile` — all automated lanes
34
+ - `fastlane/Matchfile` — iOS certificate management
35
+ - `fastlane/metadata/` — complete structure per language
36
+ - `.env.store-pilot` — your credentials (DO NOT commit)
37
+ - `.env.store-pilot.example` — safe template for the repo
38
+ - `store-pilot-scripts/` — standalone scripts
39
+
40
+ ### Configure iOS certificates (once)
41
+
42
+ ```bash
43
+ store-pilot match-setup
44
+ ```
45
+
46
+ Uses fastlane match: creates and stores your certificates in a private git repo. Any team Mac can sync them with `bundle exec fastlane match appstore --readonly`.
47
+
48
+ ---
49
+
50
+ ## Required Credentials
51
+
52
+ Fill in `.env.store-pilot` with:
53
+
54
+ | Variable | Where to get it |
55
+ |---|---|
56
+ | `ASC_KEY_ID` | App Store Connect → Users → API Keys |
57
+ | `ASC_ISSUER_ID` | Same page |
58
+ | `ASC_KEY_PATH` | The downloaded `.p8` file |
59
+ | `MATCH_PASSWORD` | Password you chose in match-setup |
60
+ | `GOOGLE_PLAY_JSON_KEY_PATH` | Play Console → Settings → API Access → Service Account |
61
+ | `ANDROID_KEYSTORE_*` | Your release keystore |
62
+ | `APPSCREENS_API_KEY` | appscreens.io → Account → API |
63
+ | `APPSCREENS_PROJECT_ID` | Your AppScreens project ID |
64
+
65
+ ---
66
+
67
+ ## Usage
68
+
69
+ ### Full pipeline (recommended)
70
+
71
+ ```bash
72
+ store-pilot ship
73
+ ```
74
+
75
+ Runs in order: expo prebuild → keywords → screenshots → build iOS + Android → upload.
76
+
77
+ ```bash
78
+ # iOS only, submit for review automatically
79
+ store-pilot ship --platform ios --submit
80
+
81
+ # Android only, upload to beta
82
+ store-pilot ship --platform android --track beta
83
+
84
+ # Skip keywords and screenshots (if unchanged)
85
+ store-pilot ship --skip-keywords --skip-screenshots
86
+ ```
87
+
88
+ ### Individual commands
89
+
90
+ ```bash
91
+ # Update keywords from Astro MCP
92
+ store-pilot keywords
93
+
94
+ # New app not published yet → use a competitor as reference
95
+ store-pilot keywords --competitor "https://apps.apple.com/app/notion/id1232780281"
96
+
97
+ # Generate and download screenshots from AppScreens
98
+ store-pilot screenshots
99
+
100
+ # Generate AND upload directly to stores
101
+ store-pilot screenshots --upload
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Workflow for new apps (pre-publication)
107
+
108
+ Astro works before publishing by using competitors as proxy:
109
+
110
+ 1. Find 2-3 competitor apps in the App Store
111
+ 2. Copy their URLs
112
+ 3. Run for each one:
113
+ ```bash
114
+ store-pilot keywords --competitor "https://apps.apple.com/app/.../id123"
115
+ ```
116
+ 4. store-pilot combines and ranks keywords by popularity/difficulty
117
+
118
+ ---
119
+
120
+ ## Metadata
121
+
122
+ Fill in the files at `fastlane/metadata/` — plain text files versioned in git:
123
+
124
+ ```
125
+ fastlane/metadata/
126
+ ├── ios/
127
+ │ ├── en-US/
128
+ │ │ ├── name.txt ← app name
129
+ │ │ ├── subtitle.txt ← subtitle (30 chars)
130
+ │ │ ├── description.txt ← long description
131
+ │ │ ├── keywords.txt ← generated by store-pilot keywords
132
+ │ │ ├── promotional_text.txt ← updatable without new build
133
+ │ │ └── release_notes.txt ← what's new in this version
134
+ │ └── review_information/
135
+ │ ├── first_name.txt
136
+ │ └── ...
137
+ └── android/
138
+ └── en-US/
139
+ ├── title.txt
140
+ ├── short_description.txt
141
+ ├── full_description.txt
142
+ └── changelogs/
143
+ └── default.txt
144
+ ```
145
+
146
+ ---
147
+
148
+ ## CI/CD (GitHub Actions)
149
+
150
+ ```yaml
151
+ # .github/workflows/ship.yml
152
+ name: Ship to stores
153
+
154
+ on:
155
+ push:
156
+ tags: ['v*']
157
+
158
+ jobs:
159
+ ship:
160
+ runs-on: macos-latest
161
+ steps:
162
+ - uses: actions/checkout@v4
163
+ - uses: actions/setup-node@v4
164
+ with:
165
+ node-version: '20'
166
+ - run: npm install -g store-pilot
167
+ - run: gem install fastlane
168
+ - run: store-pilot ship --skip-keywords
169
+ env:
170
+ ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
171
+ ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
172
+ ASC_KEY_PATH: ${{ secrets.ASC_KEY_PATH }}
173
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
174
+ GOOGLE_PLAY_JSON_KEY_PATH: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
175
+ ANDROID_KEYSTORE_PATH: ${{ secrets.ANDROID_KEYSTORE }}
176
+ ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
177
+ ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
178
+ ANDROID_KEY_PASS: ${{ secrets.ANDROID_KEY_PASS }}
179
+ APPSCREENS_API_KEY: ${{ secrets.APPSCREENS_API_KEY }}
180
+ APPSCREENS_PROJECT_ID: ${{ secrets.APPSCREENS_PROJECT_ID }}
181
+ ```
182
+
183
+ > **Note:** `store-pilot keywords` uses Astro MCP which runs locally on your Mac, so that step is done on your machine before pushing. In CI it's skipped with `--skip-keywords`.
184
+
185
+ ---
186
+
187
+ ## Project structure
188
+
189
+ ```
190
+ store-pilot/
191
+ ├── bin/store-pilot.js CLI entrypoint
192
+ ├── commands/
193
+ │ ├── init.js scaffolding
194
+ │ ├── keywords.js Astro MCP → keywords.txt
195
+ │ ├── screenshots.js AppScreens API → assets
196
+ │ ├── ship.js full pipeline
197
+ │ └── match-setup.js certificate setup
198
+ ├── lib/
199
+ │ └── config.js config + env loader
200
+ └── templates/
201
+ ├── scripts/ copied to project as store-pilot-scripts/
202
+ └── fastlane/metadata/ base metadata structure
203
+ ```
204
+
205
+ ---
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
9
+
10
+ program
11
+ .name('store-pilot')
12
+ .description('Automate ASO, builds & store uploads for Expo/React Native projects')
13
+ .version(pkg.version);
14
+
15
+ program
16
+ .command('init')
17
+ .description('Configure store-pilot in an existing Expo project')
18
+ .option('--yes', 'Saltar confirmaciones interactivas')
19
+ .action(async (opts) => {
20
+ const { runInit } = await import('../commands/init.js');
21
+ await runInit(opts);
22
+ });
23
+
24
+ program
25
+ .command('keywords')
26
+ .description('Generate keywords.txt from Astro MCP (must be running)')
27
+ .option('--lang <lang>', 'Target language (e.g. en-US)', 'en-US')
28
+ .option('--min-popularity <n>', 'Minimum popularity', '20')
29
+ .option('--max-difficulty <n>', 'Maximum difficulty', '50')
30
+ .option('--competitor <url>', 'App Store URL of a competitor to extract keywords from')
31
+ .action(async (opts) => {
32
+ const { runKeywords } = await import('../commands/keywords.js');
33
+ await runKeywords(opts);
34
+ });
35
+
36
+ program
37
+ .command('screenshots')
38
+ .description('Generate and download screenshots from AppScreens API')
39
+ .option('--upload', 'Upload directly to App Store Connect and Google Play')
40
+ .action(async (opts) => {
41
+ const { runScreenshots } = await import('../commands/screenshots.js');
42
+ await runScreenshots(opts);
43
+ });
44
+
45
+ program
46
+ .command('ship')
47
+ .description('Full pipeline: keywords → screenshots → build → upload')
48
+ .option('--platform <p>', 'ios | android | all', 'all')
49
+ .option('--track <t>', 'Android track: internal | alpha | beta | production', 'internal')
50
+ .option('--submit', 'Submit for review automatically (iOS)')
51
+ .option('--skip-keywords', 'Skip keyword update')
52
+ .option('--skip-screenshots', 'Skip screenshot regeneration')
53
+ .action(async (opts) => {
54
+ const { runShip } = await import('../commands/ship.js');
55
+ await runShip(opts);
56
+ });
57
+
58
+ program
59
+ .command('match-setup')
60
+ .description('Configure fastlane match for automatic iOS certificate management')
61
+ .action(async () => {
62
+ const { runMatchSetup } = await import('../commands/match-setup.js');
63
+ await runMatchSetup();
64
+ });
65
+
66
+ program.parse();