@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/package.json +17 -5
- package/plugins/store-automator/agents/appstore-media-designer.md +52 -18
- package/plugins/store-automator/agents/appstore-reviewer.md +35 -0
- package/src/install.mjs +2 -1
- package/src/mcp-setup.mjs +3 -4
- package/src/prompt.mjs +1 -1
- package/src/templates.mjs +18 -0
- package/templates/CLAUDE.md.template +48 -19
- package/templates/analysis_options.yaml.template +40 -0
- package/templates/ci.config.yaml.template +1 -1
- package/templates/fastlane/android/Gemfile.template +5 -0
- package/templates/fastlane/ios/Gemfile.template +5 -0
- package/templates/firebase/.firebaserc.template +5 -0
- package/templates/firebase/firebase.json.template +16 -0
- package/templates/firebase/firestore.indexes.json.template +4 -0
- package/templates/firebase/firestore.rules.template +20 -0
- package/templates/firebase/storage.rules.template +10 -0
- package/templates/scripts/generate.sh +24 -0
- package/templates/web/deploy-cloudflare.mjs +11 -3
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "App Store & Google Play automation for Flutter apps",
|
|
8
|
-
"version": "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.
|
|
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.
|
|
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",
|
|
21
|
-
"
|
|
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": {
|
|
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": {
|
|
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
|
-
##
|
|
151
|
+
## Available MCP Tools
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
You MUST use these MCP tools for all design work. Check availability first:
|
|
154
154
|
|
|
155
|
-
1.
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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,
|
|
189
|
+
For each scene, use a prompt like:
|
|
168
190
|
```
|
|
169
|
-
|
|
170
|
-
Background: [gradient/color description].
|
|
171
|
-
Center: phone mockup showing [specific screen description].
|
|
172
|
-
Top
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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
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.
|
|
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
|
-
-
|
|
95
|
+
- **State management**: Riverpod (recommended) or BLoC/Cubit
|
|
96
96
|
- Repository pattern for data access
|
|
97
|
-
- Dependency injection
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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.
|
|
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.
|
|
192
|
-
|
|
193
|
-
4
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
1.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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,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,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
|
+
}
|
|
@@ -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
|
|
83
|
-
|
|
84
|
-
|
|
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) {
|