@daemux/store-automator 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.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "App Store & Google Play automation for Flutter apps",
8
- "version": "0.1.0"
8
+ "version": "0.2.0"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "store-automator",
13
13
  "source": "./plugins/store-automator",
14
14
  "description": "3 agents for app store publishing: reviewer, meta-creator, media-designer",
15
- "version": "0.1.0",
15
+ "version": "0.2.0",
16
16
  "keywords": ["flutter", "app-store", "google-play", "fastlane", "codemagic"]
17
17
  }
18
18
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daemux/store-automator",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Full App Store & Google Play automation for Flutter apps with Claude Code agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,16 +17,28 @@
17
17
  "templates/"
18
18
  ],
19
19
  "keywords": [
20
- "claude", "claude-code", "plugin", "flutter", "app-store",
21
- "google-play", "fastlane", "codemagic", "ci-cd", "daemux"
20
+ "claude",
21
+ "claude-code",
22
+ "plugin",
23
+ "flutter",
24
+ "app-store",
25
+ "google-play",
26
+ "fastlane",
27
+ "codemagic",
28
+ "ci-cd",
29
+ "daemux"
22
30
  ],
23
- "author": { "name": "Daemux" },
31
+ "author": {
32
+ "name": "Daemux"
33
+ },
24
34
  "license": "MIT",
25
35
  "repository": {
26
36
  "type": "git",
27
37
  "url": "https://github.com/daemux/store-automator.git"
28
38
  },
29
- "engines": { "node": ">=18" },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
30
42
  "dependencies": {
31
43
  "update-notifier": "^7.3.1"
32
44
  }
@@ -148,35 +148,69 @@ fastlane/screenshots/android/
148
148
  icon.png
149
149
  ```
150
150
 
151
- ## Using Stitch MCP for Design
151
+ ## Available MCP Tools
152
152
 
153
- When Stitch MCP is available (GOOGLE_API_KEY configured):
153
+ You MUST use these MCP tools for all design work. Check availability first:
154
154
 
155
- 1. List existing Stitch projects to check for prior work
156
- 2. Create a new Stitch project named "{app-name}-store-screenshots"
157
- 3. For each screenshot scene, generate a screen with a detailed prompt:
155
+ ### 1. Stitch MCP (PRIMARY design tool)
156
+ - Configured via STITCH_API_KEY in .mcp.json
157
+ - Use `stitch_*` tools for all image creation and design
158
+ - This is your main tool for creating professional screenshots and graphics
159
+
160
+ ### 2. mobile-mcp (for real app screenshots)
161
+ - Can capture actual screenshots from iOS Simulator or Android Emulator
162
+ - Use to get real app UI screenshots as source material for Stitch designs
163
+ - Requires the app to be built and running on a simulator
164
+
165
+ ### 3. Playwright MCP (for web screenshots)
166
+ - Can capture screenshots of web pages
167
+ - Useful for marketing page previews
168
+
169
+ ## Design Workflow with Stitch MCP
170
+
171
+ **MANDATORY: All visual assets MUST be created using Stitch MCP. Do NOT create placeholder or text-only images.**
172
+
173
+ ### Step 1: Capture App UI
174
+ Use mobile-mcp to capture real app screens from the simulator, OR read the source code to understand each screen layout.
175
+
176
+ ### Step 2: Create Screenshots via Stitch MCP
177
+ For each of the 5 scenes:
178
+
179
+ 1. Call Stitch MCP to create a new design project named "{app-name}-store-screenshots"
180
+ 2. For each scene, use Stitch MCP with a detailed design prompt:
158
181
  - Describe the exact layout: background color/gradient, app screen placement, headline text
159
182
  - Specify the app's color palette (extract from theme.dart or design specs)
160
183
  - Reference the actual app screen content for that scene
161
- 4. Export each screen at every required device dimension
162
- 5. Apply headline text overlays
163
- 6. Save to the correct directory paths
184
+ 3. Generate at every required device dimension
185
+ 4. Export and save to the correct directory paths
164
186
 
165
- ### Stitch Prompt Template
187
+ ### Stitch Design Prompt Template
166
188
 
167
- For each scene, construct a prompt like:
189
+ For each scene, use a prompt like:
168
190
  ```
169
- App store screenshot for a [app type] app.
170
- Background: [gradient/color description].
171
- Center: phone mockup showing [specific screen description].
172
- Top text: "[headline text]" in [font style], [color].
173
- Style: clean, modern, professional app store screenshot.
191
+ Professional app store screenshot for a [app type] app.
192
+ Background: [gradient/color description matching app theme].
193
+ Center: phone mockup showing [specific screen description with UI details].
194
+ Top headline: "[headline text]" in bold sans-serif, white color.
195
+ Bottom subheadline: "[subtitle]" in regular weight, semi-transparent white.
196
+ Style: clean, modern, minimal. Apple App Store quality.
174
197
  Dimensions: [width]x[height] pixels.
198
+ Color palette: primary [#hex], secondary [#hex], accent [#hex].
175
199
  ```
176
200
 
177
- ## Fallback Without Stitch MCP
201
+ ### Step 3: Create Feature Graphic (Google Play, required)
202
+ Use Stitch MCP to create the 1024x500 feature graphic:
203
+ - App name prominently displayed
204
+ - App icon or key UI element
205
+ - Tagline or value proposition
206
+ - Brand colors
207
+
208
+ ### Step 4: Create App Icon Variants
209
+ Use Stitch MCP for any icon variants needed (512x512 for Google Play).
210
+
211
+ ## Fallback: If Stitch MCP is unavailable
178
212
 
179
- If Stitch MCP is NOT available, create a detailed design specification file:
213
+ Only if Stitch MCP tools are not available, create a design specification:
180
214
 
181
215
  Save to `fastlane/screenshots/design-spec.json`:
182
216
  ```json
@@ -199,7 +233,7 @@ Save to `fastlane/screenshots/design-spec.json`:
199
233
  }
200
234
  ```
201
235
 
202
- The developer agent can then create screenshots programmatically using Flutter widget tests or a dedicated screenshot generation script.
236
+ Then use mobile-mcp to capture real app screenshots from simulator and composite them with Flutter widget tests or a screenshot generation script.
203
237
 
204
238
  ## Output Verification Checklist
205
239
 
@@ -139,6 +139,41 @@ Read fastlane/app_rating_config.json and verify:
139
139
  - **Subscription policy**: clear terms displayed before purchase, easy cancellation path
140
140
  - **Content rating**: accurate IARC questionnaire responses, consistent with app content
141
141
 
142
+ ## Pre-Submission Checklist
143
+
144
+ Before running the full review, verify these mandatory items. Include checkbox results in the output.
145
+
146
+ ### Apple App Store Mandatory Items
147
+ - [ ] App name (name.txt) present and <= 30 characters
148
+ - [ ] Subtitle (subtitle.txt) present and <= 30 characters
149
+ - [ ] Description (description.txt) present and <= 4000 characters
150
+ - [ ] Keywords (keywords.txt) present and <= 100 characters total
151
+ - [ ] Privacy URL (privacy_url.txt) present with valid https:// URL
152
+ - [ ] Support URL (support_url.txt) present with valid https:// URL
153
+ - [ ] At least 1 screenshot per required device class (6.7", 6.5", iPad 12.9")
154
+ - [ ] App icon present (1024x1024 in Assets.xcassets)
155
+ - [ ] Bundle ID matches ci.config.yaml
156
+ - [ ] Age rating config (app_rating_config.json) present and complete
157
+ - [ ] Privacy policy page loads and contains required sections
158
+
159
+ ### Google Play Mandatory Items
160
+ - [ ] Title (title.txt) present and <= 30 characters
161
+ - [ ] Short description (short_description.txt) present and <= 80 characters
162
+ - [ ] Full description (full_description.txt) present and <= 4000 characters
163
+ - [ ] Feature graphic present (exactly 1024x500)
164
+ - [ ] At least 2 phone screenshots present
165
+ - [ ] App icon (512x512) present
166
+ - [ ] Package name matches ci.config.yaml
167
+ - [ ] Changelog (changelogs/default.txt) present
168
+ - [ ] Privacy policy page loads and contains required sections
169
+ - [ ] Content rating questionnaire answers present
170
+
171
+ ### Both Platforms
172
+ - [ ] All metadata files exist for every language in ci.config.yaml metadata.languages
173
+ - [ ] IAP config (iap_config.json) valid if app has subscriptions
174
+ - [ ] No placeholder text remaining in any metadata file
175
+ - [ ] Web pages (privacy, terms, support) deploy successfully
176
+
142
177
  ## Output Format
143
178
 
144
179
  ```
package/src/install.mjs CHANGED
@@ -11,7 +11,7 @@ import { injectEnvVars, injectStatusLine } from './settings.mjs';
11
11
  import { ensureClaudePlugin } from './dependency-check.mjs';
12
12
  import { promptForTokens } from './prompt.mjs';
13
13
  import { getMcpServers, writeMcpJson } from './mcp-setup.mjs';
14
- import { installClaudeMd, installCiTemplates } from './templates.mjs';
14
+ import { installClaudeMd, installCiTemplates, installFirebaseTemplates } from './templates.mjs';
15
15
 
16
16
  function checkClaudeCli() {
17
17
  const result = exec('command -v claude') || exec('which claude');
@@ -124,6 +124,7 @@ export async function runInstall(scope, isPostinstall = false) {
124
124
 
125
125
  installClaudeMd(join(baseDir, 'CLAUDE.md'), packageDir);
126
126
  installCiTemplates(projectDir, packageDir);
127
+ installFirebaseTemplates(projectDir, packageDir);
127
128
 
128
129
  const scopeLabel = scope === 'user' ? 'global' : 'project';
129
130
  console.log(`Configuring ${scopeLabel} settings...`);
package/src/mcp-setup.mjs CHANGED
@@ -17,18 +17,17 @@ export function getMcpServers(tokens) {
17
17
  if (tokens.stitchApiKey) {
18
18
  servers.stitch = {
19
19
  command: 'npx',
20
- args: ['-y', '@_davideast/stitch-mcp'],
21
- env: { GOOGLE_API_KEY: tokens.stitchApiKey },
20
+ args: ['-y', '@_davideast/stitch-mcp', 'proxy'],
21
+ env: { STITCH_API_KEY: tokens.stitchApiKey },
22
22
  };
23
23
  }
24
24
 
25
25
  if (tokens.cloudflareToken && tokens.cloudflareAccountId) {
26
26
  servers.cloudflare = {
27
27
  command: 'npx',
28
- args: ['-y', '@cloudflare/mcp-server-cloudflare'],
28
+ args: ['-y', '@cloudflare/mcp-server-cloudflare', 'run', tokens.cloudflareAccountId],
29
29
  env: {
30
30
  CLOUDFLARE_API_TOKEN: tokens.cloudflareToken,
31
- CLOUDFLARE_ACCOUNT_ID: tokens.cloudflareAccountId,
32
31
  },
33
32
  };
34
33
  }
package/src/prompt.mjs CHANGED
@@ -32,7 +32,7 @@ export async function promptForTokens() {
32
32
  try {
33
33
  const stitchApiKey = await ask(
34
34
  rl,
35
- 'Stitch MCP API Key (X-Goog-Api-Key value): '
35
+ 'Stitch MCP API Key (STITCH_API_KEY value): '
36
36
  );
37
37
 
38
38
  const cloudflareToken = await ask(
package/src/templates.mjs CHANGED
@@ -10,6 +10,14 @@ const FILE_COPIES = [
10
10
 
11
11
  const DIR_COPIES = ['scripts', 'fastlane', 'web'];
12
12
 
13
+ const FIREBASE_COPIES = [
14
+ ['firebase/firestore.rules.template', 'backend/firestore.rules'],
15
+ ['firebase/storage.rules.template', 'backend/storage.rules'],
16
+ ['firebase/firebase.json.template', 'backend/firebase.json'],
17
+ ['firebase/firestore.indexes.json.template', 'backend/firestore.indexes.json'],
18
+ ['firebase/.firebaserc.template', 'backend/.firebaserc'],
19
+ ];
20
+
13
21
  function copyIfMissing(srcPath, destPath, label, isDirectory) {
14
22
  if (!existsSync(srcPath)) return;
15
23
  if (existsSync(destPath)) {
@@ -53,3 +61,13 @@ export function installCiTemplates(projectDir, packageDir) {
53
61
  );
54
62
  }
55
63
  }
64
+
65
+ export function installFirebaseTemplates(projectDir, packageDir) {
66
+ console.log('Installing Firebase backend templates...');
67
+ const templateDir = join(packageDir, 'templates');
68
+ ensureDir(join(projectDir, 'backend'));
69
+
70
+ for (const [src, dest] of FIREBASE_COPIES) {
71
+ copyIfMissing(join(templateDir, src), join(projectDir, dest), dest, false);
72
+ }
73
+ }
@@ -45,7 +45,7 @@ metadata:
45
45
  - en-US
46
46
 
47
47
  web:
48
- domain: "yourapp.example.com"
48
+ domain: "yourapp-pages.pages.dev" # *.pages.dev domain is sufficient, no custom DNS needed
49
49
  cloudflare_project_name: "yourapp-pages"
50
50
  ```
51
51
 
@@ -92,10 +92,11 @@ Intro offer types: FREE, PAY_AS_YOU_GO, PAY_UP_FRONT.
92
92
  ## Flutter Development Standards
93
93
 
94
94
  ### Architecture
95
- - BLoC/Cubit pattern for state management
95
+ - **State management**: Riverpod (recommended) or BLoC/Cubit
96
96
  - Repository pattern for data access
97
- - Dependency injection via get_it + injectable
97
+ - **Dependency injection**: Riverpod providers (recommended) or get_it + injectable
98
98
  - Feature-first folder structure
99
+ - Code generation: build_runner + riverpod_generator (or freezed + json_serializable)
99
100
 
100
101
  ### Folder Structure
101
102
  ```
@@ -111,9 +112,11 @@ lib/
111
112
  widgets/ # Shared widgets
112
113
  features/
113
114
  {feature}/
114
- data/ # Data sources, repositories
115
- domain/ # Entities, use cases
116
- presentation/ # Screens, widgets, cubits
115
+ models/ # Data classes, entities
116
+ providers/ # Riverpod providers (or cubits/)
117
+ repositories/ # Data sources, API calls
118
+ screens/ # Full-page widgets
119
+ widgets/ # Feature-specific widgets
117
120
  services/
118
121
  firebase/
119
122
  api/
@@ -137,7 +140,17 @@ lib/
137
140
  - No TODO/FIXME in committed code
138
141
  - No hardcoded secrets anywhere
139
142
 
140
- ### Dependencies (standard set)
143
+ ### Dependencies
144
+
145
+ **Riverpod stack (recommended):**
146
+ - flutter_riverpod + riverpod_annotation + riverpod_generator -- state management & DI
147
+ - go_router -- navigation
148
+ - dio -- HTTP client
149
+ - freezed + json_serializable -- data classes
150
+ - firebase_core, firebase_auth, cloud_firestore -- Firebase
151
+ - purchases_flutter -- RevenueCat for IAP (or in_app_purchase)
152
+
153
+ **BLoC stack (alternative):**
141
154
  - flutter_bloc / bloc -- state management
142
155
  - go_router -- navigation
143
156
  - get_it + injectable -- dependency injection
@@ -177,7 +190,7 @@ lib/
177
190
 
178
191
  ### Phase 1: Design
179
192
  1. Use designer agent to create UI/UX specs
180
- 2. Use appstore-media-designer to plan screenshot strategy
193
+ 2. Plan screenshot strategy for all required device sizes
181
194
  3. Review designs before implementation
182
195
 
183
196
  ### Phase 2: Develop
@@ -186,32 +199,48 @@ lib/
186
199
  3. Fill ci.config.yaml with real values
187
200
  4. Add creds/AuthKey.p8 (Apple) and creds/play-service-account.json (Google)
188
201
 
189
- ### Phase 3: Store Metadata
202
+ ### Phase 3: Store Metadata + Texts
190
203
  1. Run appstore-meta-creator to generate all metadata texts for configured languages
191
- 2. Run appstore-media-designer to create screenshots for all device sizes
192
- 3. Fill fastlane/iap_config.json if the app has subscriptions or IAP
193
- 4. Run appstore-reviewer to verify full compliance
194
-
195
- ### Phase 4: Web Pages
196
- 1. Customize marketing landing page (web/marketing.html)
204
+ 2. Fill fastlane/iap_config.json if the app has subscriptions or IAP
205
+
206
+ ### Phase 4: Screenshots and App Media (MUST use Stitch MCP)
207
+ Create all store screenshots and promotional images BEFORE web pages. The marketing page needs these images.
208
+
209
+ 1. **Use Stitch MCP** to design all visual assets. Stitch MCP is a design tool available via MCP -- use it for all image creation
210
+ 2. Run appstore-media-designer agent which will use Stitch MCP to create:
211
+ - App Store screenshots: 5 per device (iPhone 6.7", iPhone 6.5", iPad 12.9")
212
+ - Google Play screenshots: 5 per device (Phone, 7" Tablet, 10" Tablet)
213
+ - App Store promotional banner (optional)
214
+ - Google Play feature graphic (1024x500, required)
215
+ 3. Save all screenshots to `fastlane/metadata/en-US/screenshots/` (iOS) and `fastlane/metadata/android/en-US/images/` (Android)
216
+ 4. Each screenshot should have:
217
+ - Real app UI (capture from simulator via mobile-mcp, or design via Stitch MCP)
218
+ - Headline text overlay describing the feature shown
219
+ - Consistent branding (colors, fonts matching the app theme)
220
+ 5. Run appstore-reviewer to verify full compliance with store guidelines
221
+
222
+ ### Phase 5: Web Pages (after screenshots are ready)
223
+ 1. Customize marketing landing page (web/marketing.html) -- **include app screenshots** from Phase 4
197
224
  2. Customize privacy policy page (web/privacy.html)
198
225
  3. Customize terms of service page (web/terms.html)
199
226
  4. Customize support page (web/support.html)
200
- 5. Deploy all via Cloudflare Pages (node web/deploy-cloudflare.mjs)
227
+ 5. **Use Stitch MCP** to design any additional web graphics, hero images, or promotional visuals
228
+ 6. Deploy all via Cloudflare Pages (node web/deploy-cloudflare.mjs)
229
+ 7. The default `*.pages.dev` domain is sufficient -- no custom DNS records needed. Use the generated URL (e.g., `yourapp-pages.pages.dev`) for privacy_url, support_url, and marketing_url in store metadata
201
230
 
202
- ### Phase 5: CI/CD Setup
231
+ ### Phase 6: CI/CD Setup
203
232
  1. Run scripts/generate.sh to create codemagic.yaml from ci.config.yaml
204
233
  2. Commit all files to the private repository
205
234
  3. Push to GitHub
206
235
  4. Codemagic builds and deploys automatically on push to main
207
236
 
208
- ### Phase 6: First Publish
237
+ ### Phase 7: First Publish
209
238
  - **iOS**: fully automated from first push -- builds, signs, uploads metadata + screenshots + binary
210
239
  - **Android**: first build creates AAB + HOW_TO_GOOGLE_PLAY.md artifact
211
240
  - Complete the manual steps listed in HOW_TO_GOOGLE_PLAY.md via Play Console
212
241
  - Push again for full automation on subsequent builds
213
242
 
214
- ### Phase 7: Ongoing Updates
243
+ ### Phase 8: Ongoing Updates
215
244
  - Edit code, metadata, or screenshots as needed
216
245
  - Push to main branch
217
246
  - Codemagic detects what changed and only uploads modified assets
@@ -0,0 +1,40 @@
1
+ include: package:flutter_lints/flutter.yaml
2
+
3
+ linter:
4
+ rules:
5
+ # Style
6
+ always_declare_return_types: true
7
+ prefer_const_constructors: true
8
+ prefer_const_declarations: true
9
+ prefer_single_quotes: true
10
+ require_trailing_commas: true
11
+ sort_child_properties_last: true
12
+
13
+ # Safety
14
+ avoid_print: true
15
+ unawaited_futures: true
16
+ use_build_context_synchronously: true
17
+ cancel_subscriptions: true
18
+ close_sinks: true
19
+
20
+ # Best practices
21
+ avoid_empty_else: true
22
+ avoid_relative_lib_imports: true
23
+ prefer_final_fields: true
24
+ prefer_final_locals: true
25
+ prefer_is_empty: true
26
+ prefer_is_not_empty: true
27
+ unnecessary_await_in_return: true
28
+ unnecessary_const: true
29
+ unnecessary_new: true
30
+ unnecessary_this: true
31
+
32
+ analyzer:
33
+ errors:
34
+ missing_return: error
35
+ unawaited_futures: warning
36
+ avoid_print: warning
37
+ exclude:
38
+ - "**/*.g.dart"
39
+ - "**/*.freezed.dart"
40
+ - "**/*.gen.dart"
@@ -38,7 +38,7 @@ metadata:
38
38
 
39
39
  # === WEB PAGES ===
40
40
  web:
41
- domain: "yourapp.example.com"
41
+ domain: "yourapp-pages.pages.dev" # *.pages.dev domain is sufficient, no custom DNS needed
42
42
  cloudflare_project_name: "yourapp-pages"
43
43
  tagline: "Your app tagline here"
44
44
  primary_color: "#2563EB"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+ gem "fastlane"
3
+
4
+ plugins_path = File.join(File.dirname(__FILE__), 'Pluginfile')
5
+ eval_gemfile(plugins_path) if File.exist?(plugins_path)
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+ gem "fastlane"
3
+
4
+ plugins_path = File.join(File.dirname(__FILE__), 'Pluginfile')
5
+ eval_gemfile(plugins_path) if File.exist?(plugins_path)
@@ -0,0 +1,5 @@
1
+ {
2
+ "projects": {
3
+ "default": "YOUR_FIREBASE_PROJECT_ID"
4
+ }
5
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "firestore": {
3
+ "rules": "firestore.rules",
4
+ "indexes": "firestore.indexes.json"
5
+ },
6
+ "functions": [
7
+ {
8
+ "source": "functions",
9
+ "codebase": "default",
10
+ "ignore": ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log", "*.local"]
11
+ }
12
+ ],
13
+ "storage": {
14
+ "rules": "storage.rules"
15
+ }
16
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "indexes": [],
3
+ "fieldOverrides": []
4
+ }
@@ -0,0 +1,20 @@
1
+ rules_version = '2';
2
+ service cloud.firestore {
3
+ match /databases/{database}/documents {
4
+ function isOwner(userId) {
5
+ return request.auth != null && request.auth.uid == userId;
6
+ }
7
+
8
+ match /users/{userId} {
9
+ allow read, write: if isOwner(userId);
10
+
11
+ match /{subcollection=**} {
12
+ allow read, write: if isOwner(userId);
13
+ }
14
+ }
15
+
16
+ match /system/{document=**} {
17
+ allow read, write: if false;
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,10 @@
1
+ rules_version = '2';
2
+ service firebase.storage {
3
+ match /b/{bucket}/o {
4
+ match /users/{userId}/{allPaths=**} {
5
+ allow read, write: if request.auth != null
6
+ && request.auth.uid == userId
7
+ && request.resource.size < 10 * 1024 * 1024;
8
+ }
9
+ }
10
+ }
@@ -27,6 +27,18 @@ if ! command -v yq &>/dev/null; then
27
27
  exit 1
28
28
  fi
29
29
 
30
+ # Validate that a config value does not contain characters that break sed
31
+ validate_value() {
32
+ local name="$1"
33
+ local value="$2"
34
+ if [[ "$value" =~ [|/\\] ]]; then
35
+ echo "ERROR: $name contains invalid characters (|, /, or \\) that would break substitution."
36
+ echo " Current value: $value"
37
+ echo " Please remove |, /, and \\ characters from $name in $CONFIG"
38
+ exit 1
39
+ fi
40
+ }
41
+
30
42
  # Read app values from ci.config.yaml
31
43
  BUNDLE_ID=$(yq -r '.app.bundle_id' "$CONFIG")
32
44
  PACKAGE_NAME=$(yq -r '.app.package_name' "$CONFIG")
@@ -49,6 +61,18 @@ APPLE_ISSUER_ID=$(yq -r '.credentials.apple.issuer_id' "$CONFIG")
49
61
  GOOGLE_SA_JSON_PATH=$(yq -r '.credentials.google.service_account_json_path' "$CONFIG")
50
62
  KEYSTORE_PASSWORD=$(yq -r '.credentials.android.keystore_password' "$CONFIG")
51
63
 
64
+ # Validate all values before sed substitution
65
+ validate_value "app.bundle_id" "$BUNDLE_ID"
66
+ validate_value "app.package_name" "$PACKAGE_NAME"
67
+ validate_value "app.name" "$APP_NAME"
68
+ validate_value "app.sku" "$SKU"
69
+ validate_value "app.apple_id" "$APPLE_ID"
70
+ validate_value "android.track" "$TRACK"
71
+ validate_value "ios.primary_category" "$PRIMARY_CAT"
72
+ validate_value "ios.secondary_category" "$SECONDARY_CAT"
73
+ validate_value "credentials.apple.key_id" "$APPLE_KEY_ID"
74
+ validate_value "credentials.apple.issuer_id" "$APPLE_ISSUER_ID"
75
+
52
76
  # Generate codemagic.yaml from template
53
77
  sed \
54
78
  -e "s|\${BUNDLE_ID}|$BUNDLE_ID|g" \
@@ -79,9 +79,17 @@ function getCredentials(dir) {
79
79
  const mcpPath = join(dir, ".mcp.json");
80
80
  if (existsSync(mcpPath)) {
81
81
  const mcp = JSON.parse(readFileSync(mcpPath, "utf8"));
82
- const cf = mcp?.mcpServers?.cloudflare?.env || {};
83
- token = token || cf.CLOUDFLARE_API_TOKEN || "";
84
- accountId = accountId || cf.CLOUDFLARE_ACCOUNT_ID || "";
82
+ const cfServer = mcp?.mcpServers?.cloudflare || {};
83
+ const cfEnv = cfServer.env || {};
84
+ const cfArgs = cfServer.args || [];
85
+ token = token || cfEnv.CLOUDFLARE_API_TOKEN || "";
86
+ if (!accountId && cfArgs.length >= 4) {
87
+ const runIdx = cfArgs.indexOf("run");
88
+ if (runIdx >= 0 && cfArgs[runIdx + 1]) {
89
+ accountId = cfArgs[runIdx + 1];
90
+ }
91
+ }
92
+ accountId = accountId || cfEnv.CLOUDFLARE_ACCOUNT_ID || "";
85
93
  }
86
94
  }
87
95
  if (!token || !accountId) {