@daemux/store-automator 0.9.1 → 0.10.1
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 +3 -3
- package/README.md +5 -23
- package/bin/cli.mjs +7 -66
- package/package.json +1 -2
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/plugins/store-automator/agents/architect.md +1 -1
- package/plugins/store-automator/agents/devops.md +28 -75
- package/plugins/store-automator/agents/product-manager.md +5 -5
- package/scripts/check_changed.sh +1 -1
- package/src/ci-config.mjs +0 -10
- package/src/install-paths.mjs +1 -75
- package/src/install.mjs +8 -19
- package/src/mcp-setup.mjs +1 -35
- package/src/prompt.mjs +1 -19
- package/src/templates.mjs +0 -1
- package/src/uninstall.mjs +0 -4
- package/src/utils.mjs +0 -9
- package/templates/CLAUDE.md.template +18 -18
- package/templates/ci.config.yaml.template +0 -11
- package/templates/fastlane/android/Fastfile.template +2 -2
- package/templates/fastlane/android/Pluginfile.template +1 -1
- package/templates/fastlane/ios/Fastfile.template +8 -6
- package/templates/fastlane/ios/Pluginfile.template +1 -1
- package/templates/fastlane/ios/Snapfile.template +1 -1
- package/templates/github/workflows/android-release.yml +6 -4
- package/templates/github/workflows/ios-release.yml +15 -4
- package/templates/scripts/check_changed.sh +1 -1
- package/templates/scripts/ci/android/sync-iap.sh +8 -11
- package/templates/scripts/ci/android/update-data-safety.sh +20 -21
- package/templates/scripts/ci/android/upload-metadata.sh +11 -21
- package/templates/scripts/ci/common/ci-notify.sh +39 -0
- package/templates/scripts/ci/common/link-fastlane.sh +1 -1
- package/templates/scripts/ci/common/read-config.sh +1 -5
- package/templates/scripts/ci/ios/sync-iap.sh +13 -26
- package/templates/scripts/ci/ios/upload-metadata.sh +6 -13
- package/templates/scripts/update_data_safety.py +14 -10
- package/scripts/codemagic-setup.mjs +0 -44
- package/scripts/generate.sh +0 -107
- package/src/codemagic-api.mjs +0 -75
- package/src/codemagic-setup.mjs +0 -190
- package/src/github-setup.mjs +0 -43
- package/templates/codemagic.template.yaml +0 -551
- package/templates/github/workflows/codemagic-trigger.yml +0 -78
- package/templates/scripts/generate.sh +0 -107
package/src/install.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { promptForTokens } from './prompt.mjs';
|
|
|
12
12
|
import { getMcpServers, writeMcpJson } from './mcp-setup.mjs';
|
|
13
13
|
import { installClaudeMd, installCiTemplates, installFirebaseTemplates } from './templates.mjs';
|
|
14
14
|
import { writeCiBundleId, writeCiPackageName } from './ci-config.mjs';
|
|
15
|
-
import { installGitHubActionsPath
|
|
15
|
+
import { installGitHubActionsPath } from './install-paths.mjs';
|
|
16
16
|
|
|
17
17
|
function checkClaudeCli() {
|
|
18
18
|
const result = exec('command -v claude') || exec('which claude');
|
|
@@ -94,20 +94,13 @@ function printSummary(scope, oldVersion, newVersion) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
function printNextSteps(
|
|
97
|
+
function printNextSteps() {
|
|
98
98
|
console.log('');
|
|
99
99
|
console.log('Next steps:');
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
console.log(' 4. Start Claude Code');
|
|
105
|
-
} else {
|
|
106
|
-
console.log(' 1. Fill ci.config.yaml (codemagic.app_id is auto-configured if token was provided)');
|
|
107
|
-
console.log(' 2. Add creds/AuthKey.p8 and creds/play-service-account.json');
|
|
108
|
-
console.log(' 3. Start Claude Code');
|
|
109
|
-
console.log(' Note: For auto-trigger, install gh CLI and run "gh auth login"');
|
|
110
|
-
}
|
|
100
|
+
console.log(' 1. Fill ci.config.yaml with credentials');
|
|
101
|
+
console.log(' 2. Add creds/AuthKey.p8 and creds/play-service-account.json');
|
|
102
|
+
console.log(' 3. Set MATCH_PASSWORD secret in GitHub repository settings');
|
|
103
|
+
console.log(' 4. Start Claude Code');
|
|
111
104
|
}
|
|
112
105
|
|
|
113
106
|
export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
@@ -154,11 +147,7 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
154
147
|
writeCiPackageName(projectDir, tokens.bundleId);
|
|
155
148
|
}
|
|
156
149
|
|
|
157
|
-
|
|
158
|
-
installGitHubActionsPath(projectDir, packageDir, cliTokens);
|
|
159
|
-
} else {
|
|
160
|
-
await installCodemagicPath(projectDir, tokens);
|
|
161
|
-
}
|
|
150
|
+
installGitHubActionsPath(projectDir, packageDir, cliTokens);
|
|
162
151
|
|
|
163
152
|
const scopeLabel = scope === 'user' ? 'global' : 'project';
|
|
164
153
|
console.log(`Configuring ${scopeLabel} settings...`);
|
|
@@ -167,5 +156,5 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
167
156
|
injectStatusLine(settingsPath);
|
|
168
157
|
|
|
169
158
|
printSummary(scope, oldVersion, newVersion);
|
|
170
|
-
printNextSteps(
|
|
159
|
+
printNextSteps();
|
|
171
160
|
}
|
package/src/mcp-setup.mjs
CHANGED
|
@@ -32,17 +32,6 @@ export function getMcpServers(tokens) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
if (tokens.codemagicToken) {
|
|
36
|
-
const codemagicEnv = { CODEMAGIC_API_TOKEN: tokens.codemagicToken };
|
|
37
|
-
if (tokens.codemagicTeamId) codemagicEnv.CODEMAGIC_TEAM_ID = tokens.codemagicTeamId;
|
|
38
|
-
if (tokens.codemagicAppId) codemagicEnv.CODEMAGIC_APP_ID = tokens.codemagicAppId;
|
|
39
|
-
servers.codemagic = {
|
|
40
|
-
command: 'npx',
|
|
41
|
-
args: ['-y', '@daemux/codemagic-mcp@latest'],
|
|
42
|
-
env: codemagicEnv,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
35
|
return servers;
|
|
47
36
|
}
|
|
48
37
|
|
|
@@ -78,29 +67,6 @@ export function writeMcpJson(projectDir, servers) {
|
|
|
78
67
|
}
|
|
79
68
|
}
|
|
80
69
|
|
|
81
|
-
function updateMcpEnvVar(projectDir, envKey, value) {
|
|
82
|
-
const mcpPath = join(projectDir, '.mcp.json');
|
|
83
|
-
if (!existsSync(mcpPath)) return false;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const data = readJson(mcpPath);
|
|
87
|
-
if (!data.mcpServers?.codemagic?.env) return false;
|
|
88
|
-
data.mcpServers.codemagic.env[envKey] = value;
|
|
89
|
-
writeJson(mcpPath, data);
|
|
90
|
-
return true;
|
|
91
|
-
} catch {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function updateMcpAppId(projectDir, appId) {
|
|
97
|
-
return updateMcpEnvVar(projectDir, 'CODEMAGIC_APP_ID', appId);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function updateMcpTeamId(projectDir, teamId) {
|
|
101
|
-
return updateMcpEnvVar(projectDir, 'CODEMAGIC_TEAM_ID', teamId);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
70
|
export function removeMcpServers(projectDir) {
|
|
105
71
|
const mcpPath = join(projectDir, '.mcp.json');
|
|
106
72
|
if (!existsSync(mcpPath)) return;
|
|
@@ -109,7 +75,7 @@ export function removeMcpServers(projectDir) {
|
|
|
109
75
|
const data = readJson(mcpPath);
|
|
110
76
|
if (!data.mcpServers) return;
|
|
111
77
|
|
|
112
|
-
const toRemove = ['playwright', 'mobile-mcp', 'stitch', 'cloudflare'
|
|
78
|
+
const toRemove = ['playwright', 'mobile-mcp', 'stitch', 'cloudflare'];
|
|
113
79
|
for (const name of toRemove) {
|
|
114
80
|
delete data.mcpServers[name];
|
|
115
81
|
}
|
package/src/prompt.mjs
CHANGED
|
@@ -16,9 +16,7 @@ function allTokensProvided(cliTokens) {
|
|
|
16
16
|
return (
|
|
17
17
|
cliTokens.stitchApiKey !== undefined &&
|
|
18
18
|
cliTokens.cloudflareToken !== undefined &&
|
|
19
|
-
cliTokens.cloudflareAccountId !== undefined
|
|
20
|
-
cliTokens.codemagicToken !== undefined &&
|
|
21
|
-
cliTokens.codemagicTeamId !== undefined
|
|
19
|
+
cliTokens.cloudflareAccountId !== undefined
|
|
22
20
|
);
|
|
23
21
|
}
|
|
24
22
|
|
|
@@ -35,8 +33,6 @@ export async function promptForTokens(cliTokens = {}) {
|
|
|
35
33
|
stitchApiKey: cliTokens.stitchApiKey ?? '',
|
|
36
34
|
cloudflareToken: cliTokens.cloudflareToken ?? '',
|
|
37
35
|
cloudflareAccountId: cliTokens.cloudflareAccountId ?? '',
|
|
38
|
-
codemagicToken: cliTokens.codemagicToken ?? '',
|
|
39
|
-
codemagicTeamId: cliTokens.codemagicTeamId ?? '',
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
if (allPromptsProvided(cliTokens)) {
|
|
@@ -98,20 +94,6 @@ export async function promptForTokens(cliTokens = {}) {
|
|
|
98
94
|
);
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
if (cliTokens.codemagicToken === undefined) {
|
|
102
|
-
result.codemagicToken = await ask(
|
|
103
|
-
rl,
|
|
104
|
-
'Codemagic API Token (CM_API_TOKEN for CI/CD builds): '
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (result.codemagicToken && cliTokens.codemagicTeamId === undefined) {
|
|
109
|
-
result.codemagicTeamId = await ask(
|
|
110
|
-
rl,
|
|
111
|
-
'Codemagic Team ID (optional, from Teams page): '
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
97
|
return result;
|
|
116
98
|
} finally {
|
|
117
99
|
rl.close();
|
package/src/templates.mjs
CHANGED
package/src/uninstall.mjs
CHANGED
|
@@ -43,7 +43,6 @@ function removeFileIfExists(filePath, label) {
|
|
|
43
43
|
function removeCiTemplates(projectDir) {
|
|
44
44
|
const files = [
|
|
45
45
|
'ci.config.yaml',
|
|
46
|
-
'ci-templates/codemagic.template.yaml',
|
|
47
46
|
'Gemfile',
|
|
48
47
|
];
|
|
49
48
|
for (const file of files) {
|
|
@@ -68,9 +67,6 @@ function isDirEmpty(dirPath) {
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
function removeGitHubWorkflow(projectDir) {
|
|
71
|
-
const workflowFile = join(projectDir, '.github', 'workflows', 'codemagic-trigger.yml');
|
|
72
|
-
removeFileIfExists(workflowFile, '.github/workflows/codemagic-trigger.yml');
|
|
73
|
-
|
|
74
70
|
const workflowsDir = join(projectDir, '.github', 'workflows');
|
|
75
71
|
const githubDir = join(projectDir, '.github');
|
|
76
72
|
|
package/src/utils.mjs
CHANGED
|
@@ -44,12 +44,3 @@ export function readJson(filePath) {
|
|
|
44
44
|
export function writeJson(filePath, data) {
|
|
45
45
|
writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
export function resolveToken(tokenArg) {
|
|
49
|
-
const token = process.env.CM_API_TOKEN || tokenArg;
|
|
50
|
-
if (!token) {
|
|
51
|
-
console.error('Codemagic API token required. Set CM_API_TOKEN or pass --token=...');
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
return token;
|
|
55
|
-
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Project Overview
|
|
4
4
|
|
|
5
|
-
This is a Flutter app with Firebase backend, automated for App Store and Google Play publishing via
|
|
5
|
+
This is a Flutter app with Firebase backend, automated for App Store and Google Play publishing via GitHub Actions CI/CD. This repository MUST be PRIVATE as it contains credential files.
|
|
6
6
|
|
|
7
7
|
## Mandatory Rules
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ This is a Flutter app with Firebase backend, automated for App Store and Google
|
|
|
14
14
|
- If unsure which agent to use, use the Task tool with a general-purpose, explore or any other built-in agent
|
|
15
15
|
|
|
16
16
|
### DevOps Agent Required
|
|
17
|
-
ALL deployment and infrastructure operations SHOULD use the devops agent (
|
|
17
|
+
ALL deployment and infrastructure operations SHOULD use the devops agent (deploy, firebase, cloudflare, database/deploy, database/optimize).
|
|
18
18
|
Prefer using the devops agent over direct Bash/SSH for structured operations.
|
|
19
19
|
|
|
20
20
|
### Code Quality (enforced by agents)
|
|
@@ -38,7 +38,7 @@ Prefer using the devops agent over direct Bash/SSH for structured operations.
|
|
|
38
38
|
## Configuration Files
|
|
39
39
|
|
|
40
40
|
### ci.config.yaml
|
|
41
|
-
Single source of truth: credentials, app identity, store settings, web domain
|
|
41
|
+
Single source of truth: credentials, app identity, store settings, web domain.
|
|
42
42
|
|
|
43
43
|
### fastlane/metadata/ Structure
|
|
44
44
|
All store listing texts are stored in fastlane directories and managed by the appstore-meta-creator agent:
|
|
@@ -95,7 +95,7 @@ Detect from user request:
|
|
|
95
95
|
- **design** - App design, store screenshots, web page design (Stitch MCP)
|
|
96
96
|
- **metadata** - Store listing texts, IAP config, app rating
|
|
97
97
|
- **database** - Firestore schema, indexes, data migration
|
|
98
|
-
- **infra** -
|
|
98
|
+
- **infra** - GitHub Actions CI/CD, Firebase deploy, Cloudflare web deploy
|
|
99
99
|
- **standard** - Mixed or unclear scope
|
|
100
100
|
|
|
101
101
|
### Agent Flows
|
|
@@ -107,12 +107,12 @@ architect → product-manager(PRE) → developer → simplifier → reviewer →
|
|
|
107
107
|
|
|
108
108
|
#### Flutter Flow
|
|
109
109
|
```
|
|
110
|
-
architect → product-manager(PRE) → [app-designer] → developer(flutter) → simplifier → reviewer → tester(flutter) → tester(mobile-ui) → product-manager(POST) → [devops(
|
|
110
|
+
architect → product-manager(PRE) → [app-designer] → developer(flutter) → simplifier → reviewer → tester(flutter) → tester(mobile-ui) → product-manager(POST) → [devops(deploy)]
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
#### Backend Flow
|
|
114
114
|
```
|
|
115
|
-
architect → product-manager(PRE) → developer(backend) → simplifier → reviewer → tester(backend) → product-manager(POST) → [devops(
|
|
115
|
+
architect → product-manager(PRE) → developer(backend) → simplifier → reviewer → tester(backend) → product-manager(POST) → [devops(deploy)]
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
#### Design Flow
|
|
@@ -127,7 +127,7 @@ appstore-meta-creator → appstore-reviewer
|
|
|
127
127
|
|
|
128
128
|
#### Database Flow
|
|
129
129
|
```
|
|
130
|
-
architect → developer(backend) → simplifier → reviewer → product-manager(POST) → [devops(
|
|
130
|
+
architect → developer(backend) → simplifier → reviewer → product-manager(POST) → [devops(deploy)]
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
#### Infra Flow
|
|
@@ -137,7 +137,7 @@ devops (standalone)
|
|
|
137
137
|
|
|
138
138
|
#### Full Publishing Flow (Design to App Store)
|
|
139
139
|
```
|
|
140
|
-
architect → product-manager(PRE) → app-designer → developer(flutter) → simplifier → reviewer → tester(flutter) → tester(mobile-ui) → appstore-meta-creator → appstore-reviewer → product-manager(POST) → devops(
|
|
140
|
+
architect → product-manager(PRE) → app-designer → developer(flutter) → simplifier → reviewer → tester(flutter) → tester(mobile-ui) → appstore-meta-creator → appstore-reviewer → product-manager(POST) → devops(deploy)
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
#### Team Size Annotations
|
|
@@ -160,17 +160,17 @@ architect{T:2} → product-manager(PRE) → developer{T:2-4} → simplifier →
|
|
|
160
160
|
|
|
161
161
|
##### Flutter Flow (with teams)
|
|
162
162
|
```
|
|
163
|
-
architect{T:2} → product-manager(PRE) → [app-designer{T:2-3}] → developer(flutter){T:2-4} → simplifier → reviewer{T:2-3} → tester(flutter){T:2} → tester(mobile-ui){T:2} → product-manager(POST) → [devops(
|
|
163
|
+
architect{T:2} → product-manager(PRE) → [app-designer{T:2-3}] → developer(flutter){T:2-4} → simplifier → reviewer{T:2-3} → tester(flutter){T:2} → tester(mobile-ui){T:2} → product-manager(POST) → [devops(deploy)]
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
##### Backend Flow (with teams)
|
|
167
167
|
```
|
|
168
|
-
architect{T:2} → product-manager(PRE) → developer(backend){T:2-4} → simplifier → reviewer{T:2} → tester(backend){T:2} → product-manager(POST) → [devops(
|
|
168
|
+
architect{T:2} → product-manager(PRE) → developer(backend){T:2-4} → simplifier → reviewer{T:2} → tester(backend){T:2} → product-manager(POST) → [devops(deploy)]
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
##### Full Publishing Flow (with teams)
|
|
172
172
|
```
|
|
173
|
-
architect{T:2} → product-manager(PRE) → app-designer{T:2-3} → developer(flutter){T:2-4} → simplifier → reviewer{T:2-3} → tester(flutter){T:2} → tester(mobile-ui){T:2} → appstore-meta-creator{T:2-5} → appstore-reviewer → product-manager(POST) → devops(
|
|
173
|
+
architect{T:2} → product-manager(PRE) → app-designer{T:2-3} → developer(flutter){T:2-4} → simplifier → reviewer{T:2-3} → tester(flutter){T:2} → tester(mobile-ui){T:2} → appstore-meta-creator{T:2-5} → appstore-reviewer → product-manager(POST) → devops(deploy)
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
### Agents Reference
|
|
@@ -183,7 +183,7 @@ architect{T:2} → product-manager(PRE) → app-designer{T:2-3} → developer(fl
|
|
|
183
183
|
| simplifier | AFTER developer - simplifies Dart/Flutter code |
|
|
184
184
|
| reviewer | After ANY code changes - Dart/Flutter quality review |
|
|
185
185
|
| tester | After review passes (type: flutter/mobile-ui/web/backend/integration) |
|
|
186
|
-
| devops | Operations (mode:
|
|
186
|
+
| devops | Operations (mode: deploy/firebase/cloudflare/database+deploy/database+optimize) |
|
|
187
187
|
| app-designer | Stitch MCP design: app screens + store screenshots + web page design |
|
|
188
188
|
| appstore-meta-creator | Generate store listing texts for all configured languages |
|
|
189
189
|
| appstore-reviewer | Full App Store/Play compliance review including live app UI testing via mobile-mcp |
|
|
@@ -228,7 +228,7 @@ If the same error persists unchanged after one full fix cycle, try a fundamental
|
|
|
228
228
|
- `APPROVED` or `COMPLETE` from product-manager
|
|
229
229
|
- Deployment is optional when not configured
|
|
230
230
|
|
|
231
|
-
**Before devops(
|
|
231
|
+
**Before devops(deploy):**
|
|
232
232
|
- appstore-reviewer APPROVED (if publishing)
|
|
233
233
|
- All metadata files present for configured languages
|
|
234
234
|
- All screenshots present and verified (dimensions match requirements)
|
|
@@ -260,13 +260,13 @@ appstore-meta-creator generates texts. Fill fastlane/iap_config.json if needed.
|
|
|
260
260
|
app-designer designs marketing page in Stitch MCP. Develop web pages. Deploy via Cloudflare Pages (*.pages.dev domain sufficient).
|
|
261
261
|
|
|
262
262
|
### Phase 5: Finalize and CI/CD
|
|
263
|
-
Create .gitignore (.claude/.tasks/, Flutter ignores; do NOT ignore *.g.dart).
|
|
263
|
+
Create .gitignore (.claude/.tasks/, Flutter ignores; do NOT ignore *.g.dart). Push to private repo. GitHub Actions auto-triggers on push.
|
|
264
264
|
|
|
265
265
|
### Phase 6: First Publish
|
|
266
266
|
iOS: fully automated. Android: first build creates AAB + manual steps guide; subsequent builds fully automated.
|
|
267
267
|
|
|
268
268
|
### Phase 7: Ongoing Updates
|
|
269
|
-
Push to main.
|
|
269
|
+
Push to main. GitHub Actions detects changes and uploads modified assets. Version auto-incremented.
|
|
270
270
|
|
|
271
271
|
---
|
|
272
272
|
## FOR ORCHESTRATORS ONLY
|
|
@@ -392,11 +392,11 @@ LAUNCHING: [first-agent-name]
|
|
|
392
392
|
|
|
393
393
|
**Expected RECOMMENDED FLOW outputs (copy exactly):**
|
|
394
394
|
|
|
395
|
-
- **flutter**: `architect → product-manager(PRE) → [app-designer] → developer(flutter) → simplifier → reviewer → tester(flutter) → tester(mobile-ui) → product-manager(POST) → [devops(
|
|
396
|
-
- **backend**: `architect → product-manager(PRE) → developer(backend) → simplifier → reviewer → tester(backend) → product-manager(POST) → [devops(
|
|
395
|
+
- **flutter**: `architect → product-manager(PRE) → [app-designer] → developer(flutter) → simplifier → reviewer → tester(flutter) → tester(mobile-ui) → product-manager(POST) → [devops(deploy)]`
|
|
396
|
+
- **backend**: `architect → product-manager(PRE) → developer(backend) → simplifier → reviewer → tester(backend) → product-manager(POST) → [devops(deploy)]`
|
|
397
397
|
- **design**: `app-designer → appstore-reviewer`
|
|
398
398
|
- **metadata**: `appstore-meta-creator → appstore-reviewer`
|
|
399
|
-
- **database**: `architect → developer(backend) → simplifier → reviewer → product-manager(POST) → [devops(
|
|
399
|
+
- **database**: `architect → developer(backend) → simplifier → reviewer → product-manager(POST) → [devops(deploy)]`
|
|
400
400
|
- **infra**: `devops (standalone)`
|
|
401
401
|
- **standard**: `architect → product-manager(PRE) → developer → simplifier → reviewer → tester → product-manager(POST) → [devops]`
|
|
402
402
|
|
|
@@ -56,14 +56,3 @@ web:
|
|
|
56
56
|
jurisdiction: "State of California, United States"
|
|
57
57
|
app_store_url: ""
|
|
58
58
|
google_play_url: ""
|
|
59
|
-
|
|
60
|
-
# === CODEMAGIC CI/CD ===
|
|
61
|
-
# Fill after creating app in Codemagic (https://codemagic.io/apps)
|
|
62
|
-
# Find app_id in your Codemagic dashboard URL: codemagic.io/app/{app_id}
|
|
63
|
-
# API token is stored in .mcp.json (codemagic MCP server, set during install)
|
|
64
|
-
codemagic:
|
|
65
|
-
team_id: "" # Team ID from Codemagic Teams page
|
|
66
|
-
app_id: ""
|
|
67
|
-
workflows:
|
|
68
|
-
- ios-release
|
|
69
|
-
- android-release
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
default_platform(:android)
|
|
2
2
|
|
|
3
3
|
# Resolve paths absolutely to avoid symlink-relative breakage.
|
|
4
|
-
#
|
|
5
|
-
ROOT_DIR = ENV.fetch("
|
|
4
|
+
# PROJECT_ROOT is set by CI scripts (read-config.sh); locally we derive from __FILE__.
|
|
5
|
+
ROOT_DIR = ENV.fetch("PROJECT_ROOT", File.expand_path("../..", __FILE__))
|
|
6
6
|
APP_DIR = ENV.fetch("APP_ROOT", "#{ROOT_DIR}/app")
|
|
7
7
|
APP_DIR = "#{ROOT_DIR}/#{APP_DIR}" unless APP_DIR.start_with?("/")
|
|
8
8
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# fastlane-plugin-iap is not yet published.
|
|
2
|
-
# IAP sync is handled gracefully in
|
|
2
|
+
# IAP sync is handled gracefully in CI workflows (non-blocking).
|
|
3
3
|
# Uncomment when the plugin is available:
|
|
4
4
|
# gem "fastlane-plugin-iap", git: "https://github.com/daemux/fastlane-plugin-iap"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
default_platform(:ios)
|
|
2
2
|
|
|
3
3
|
# Resolve paths absolutely to avoid symlink-relative breakage.
|
|
4
|
-
#
|
|
5
|
-
ROOT_DIR = ENV.fetch("
|
|
4
|
+
# PROJECT_ROOT is set by CI scripts (read-config.sh); locally we derive from __FILE__.
|
|
5
|
+
ROOT_DIR = ENV.fetch("PROJECT_ROOT", File.expand_path("../..", __FILE__))
|
|
6
6
|
APP_DIR = ENV.fetch("APP_ROOT", "#{ROOT_DIR}/app")
|
|
7
7
|
APP_DIR = "#{ROOT_DIR}/#{APP_DIR}" unless APP_DIR.start_with?("/")
|
|
8
8
|
|
|
@@ -80,7 +80,8 @@ platform :ios do
|
|
|
80
80
|
skip_metadata: !metadata_changed?("fastlane/metadata/ios/"),
|
|
81
81
|
skip_screenshots: !metadata_changed?("fastlane/screenshots/ios/"),
|
|
82
82
|
run_precheck_before_submit: true,
|
|
83
|
-
precheck_include_in_app_purchases: false
|
|
83
|
+
precheck_include_in_app_purchases: false,
|
|
84
|
+
wait_processing_timeout_duration: 1800
|
|
84
85
|
)
|
|
85
86
|
)
|
|
86
87
|
end
|
|
@@ -99,12 +100,13 @@ platform :ios do
|
|
|
99
100
|
|
|
100
101
|
lane :upload_binary_ios do
|
|
101
102
|
deliver(
|
|
102
|
-
base_deliver_options.merge(
|
|
103
|
+
base_deliver_options.merge(
|
|
103
104
|
ipa: Dir.glob("#{APP_DIR}/build/ios/ipa/*.ipa").first,
|
|
104
105
|
skip_metadata: true,
|
|
105
106
|
skip_screenshots: true,
|
|
106
|
-
run_precheck_before_submit:
|
|
107
|
-
|
|
107
|
+
run_precheck_before_submit: false,
|
|
108
|
+
submit_for_review: false,
|
|
109
|
+
wait_processing_timeout_duration: 1800
|
|
108
110
|
)
|
|
109
111
|
)
|
|
110
112
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# fastlane-plugin-iap is not yet published.
|
|
2
|
-
# IAP sync is handled gracefully in
|
|
2
|
+
# IAP sync is handled gracefully in CI workflows (non-blocking).
|
|
3
3
|
# Uncomment when the plugin is available:
|
|
4
4
|
# gem "fastlane-plugin-iap", git: "https://github.com/daemux/fastlane-plugin-iap"
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
name: Android Release
|
|
2
|
-
# POST-MIGRATION CLEANUP (after GitHub Actions workflows are verified in production):
|
|
3
|
-
# - Remove .github/workflows/codemagic-trigger.yml
|
|
4
|
-
# - Remove any Codemagic-specific configuration files
|
|
5
|
-
# - Update README/docs to reference GitHub Actions instead of Codemagic
|
|
6
2
|
on:
|
|
7
3
|
push:
|
|
8
4
|
branches: [main]
|
|
@@ -39,6 +35,9 @@ jobs:
|
|
|
39
35
|
- name: Install yq
|
|
40
36
|
run: brew install yq
|
|
41
37
|
|
|
38
|
+
- name: Init Summary
|
|
39
|
+
run: echo "| Step | Status | Details |" >> $GITHUB_STEP_SUMMARY && echo "|------|--------|---------|" >> $GITHUB_STEP_SUMMARY
|
|
40
|
+
|
|
42
41
|
- name: Link Fastlane
|
|
43
42
|
run: scripts/ci/common/link-fastlane.sh android
|
|
44
43
|
|
|
@@ -49,15 +48,18 @@ jobs:
|
|
|
49
48
|
run: scripts/ci/android/check-readiness.sh
|
|
50
49
|
|
|
51
50
|
- name: Upload Metadata & Screenshots
|
|
51
|
+
id: upload-metadata
|
|
52
52
|
run: scripts/ci/android/upload-metadata.sh
|
|
53
53
|
|
|
54
54
|
- name: Sync IAP
|
|
55
|
+
id: sync-iap
|
|
55
56
|
run: scripts/ci/android/sync-iap.sh
|
|
56
57
|
|
|
57
58
|
- name: Install Python dependencies
|
|
58
59
|
run: pip3 install --break-system-packages google-api-python-client google-auth
|
|
59
60
|
|
|
60
61
|
- name: Update Data Safety
|
|
62
|
+
id: update-data-safety
|
|
61
63
|
run: scripts/ci/android/update-data-safety.sh
|
|
62
64
|
|
|
63
65
|
- name: Save .ci-state
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
name: iOS Release
|
|
2
|
-
# POST-MIGRATION CLEANUP (after GitHub Actions workflows are verified in production):
|
|
3
|
-
# - Remove .github/workflows/codemagic-trigger.yml
|
|
4
|
-
# - Remove any Codemagic-specific configuration files
|
|
5
|
-
# - Update README/docs to reference GitHub Actions instead of Codemagic
|
|
6
2
|
on:
|
|
7
3
|
push:
|
|
8
4
|
branches: [main]
|
|
@@ -39,6 +35,9 @@ jobs:
|
|
|
39
35
|
- name: Install yq
|
|
40
36
|
run: brew install yq
|
|
41
37
|
|
|
38
|
+
- name: Init Summary
|
|
39
|
+
run: echo "| Step | Status | Details |" >> $GITHUB_STEP_SUMMARY && echo "|------|--------|---------|" >> $GITHUB_STEP_SUMMARY
|
|
40
|
+
|
|
42
41
|
- name: Link Fastlane
|
|
43
42
|
run: scripts/ci/common/link-fastlane.sh ios
|
|
44
43
|
|
|
@@ -46,9 +45,11 @@ jobs:
|
|
|
46
45
|
run: scripts/ci/common/install-fastlane.sh ios
|
|
47
46
|
|
|
48
47
|
- name: Upload Metadata & Screenshots
|
|
48
|
+
id: upload-metadata
|
|
49
49
|
run: scripts/ci/ios/upload-metadata.sh
|
|
50
50
|
|
|
51
51
|
- name: Sync IAP
|
|
52
|
+
id: sync-iap
|
|
52
53
|
run: scripts/ci/ios/sync-iap.sh
|
|
53
54
|
|
|
54
55
|
- name: Save .ci-state
|
|
@@ -71,6 +72,16 @@ jobs:
|
|
|
71
72
|
with:
|
|
72
73
|
fetch-depth: 0
|
|
73
74
|
|
|
75
|
+
- name: Select Xcode 26
|
|
76
|
+
run: |
|
|
77
|
+
XCODE_PATH=$(ls -d /Applications/Xcode_26*.app 2>/dev/null | sort -V | tail -1)
|
|
78
|
+
if [ -z "$XCODE_PATH" ]; then
|
|
79
|
+
echo "ERROR: No Xcode 26.x found on this runner" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
sudo xcode-select -s "$XCODE_PATH"
|
|
83
|
+
xcodebuild -version
|
|
84
|
+
|
|
74
85
|
- name: Setup Ruby
|
|
75
86
|
uses: ruby/setup-ruby@v1
|
|
76
87
|
with:
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# Requires link-fastlane.sh and install-fastlane.sh to have run first (workflow steps).
|
|
2
3
|
set -euo pipefail
|
|
3
4
|
|
|
4
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
6
|
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
7
|
+
source "$SCRIPT_DIR/../common/ci-notify.sh"
|
|
6
8
|
|
|
7
9
|
# --- Check Google Play readiness ---
|
|
8
10
|
if [ "${GOOGLE_PLAY_READY:-false}" != "true" ]; then
|
|
@@ -13,16 +15,13 @@ fi
|
|
|
13
15
|
# --- Check if IAP config exists ---
|
|
14
16
|
IAP_CONFIG="$PROJECT_ROOT/fastlane/iap_config.json"
|
|
15
17
|
if [ ! -f "$IAP_CONFIG" ]; then
|
|
16
|
-
|
|
17
|
-
exit 0
|
|
18
|
+
ci_skip "No Android IAP config file found"
|
|
18
19
|
fi
|
|
19
20
|
|
|
20
21
|
# --- Check if IAP plugin is available ---
|
|
21
22
|
cd "$APP_ROOT/android"
|
|
22
|
-
if ! bundle exec gem list fastlane-plugin-iap --installed
|
|
23
|
-
|
|
24
|
-
echo "To enable: add it to app/android/Pluginfile and run 'bundle install'."
|
|
25
|
-
exit 0
|
|
23
|
+
if ! bundle exec gem list fastlane-plugin-iap --installed >/dev/null 2>&1; then
|
|
24
|
+
ci_skip "fastlane-plugin-iap not installed"
|
|
26
25
|
fi
|
|
27
26
|
|
|
28
27
|
# --- Hash-based change detection ---
|
|
@@ -35,8 +34,7 @@ STATE_FILE="$STATE_DIR/android-iap-hash"
|
|
|
35
34
|
if [ -f "$STATE_FILE" ]; then
|
|
36
35
|
STORED_HASH=$(cat "$STATE_FILE")
|
|
37
36
|
if [ "$HASH" = "$STORED_HASH" ]; then
|
|
38
|
-
|
|
39
|
-
exit 0
|
|
37
|
+
ci_skip "Android IAP config unchanged since last sync"
|
|
40
38
|
fi
|
|
41
39
|
fi
|
|
42
40
|
|
|
@@ -56,8 +54,7 @@ PACKAGE_NAME="$PACKAGE_NAME" \
|
|
|
56
54
|
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH" \
|
|
57
55
|
bundle exec fastlane sync_google_iap
|
|
58
56
|
|
|
59
|
-
echo "Android IAP sync complete"
|
|
60
|
-
|
|
61
57
|
# --- Update hash on success ---
|
|
62
58
|
echo "$HASH" > "$STATE_FILE"
|
|
63
|
-
|
|
59
|
+
|
|
60
|
+
ci_done "Android IAP synced to Google Play"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# Requires link-fastlane.sh and install-fastlane.sh to have run first (workflow steps).
|
|
2
3
|
set -euo pipefail
|
|
3
4
|
|
|
4
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
6
|
source "$SCRIPT_DIR/../common/read-config.sh"
|
|
7
|
+
source "$SCRIPT_DIR/../common/ci-notify.sh"
|
|
6
8
|
|
|
7
9
|
# --- Check Google Play readiness ---
|
|
8
10
|
if [ "${GOOGLE_PLAY_READY:-false}" != "true" ]; then
|
|
@@ -13,8 +15,7 @@ fi
|
|
|
13
15
|
# --- Check if data safety CSV exists ---
|
|
14
16
|
DATA_SAFETY_CSV="$PROJECT_ROOT/fastlane/data_safety.csv"
|
|
15
17
|
if [ ! -f "$DATA_SAFETY_CSV" ]; then
|
|
16
|
-
|
|
17
|
-
exit 0
|
|
18
|
+
ci_skip "No data safety CSV found"
|
|
18
19
|
fi
|
|
19
20
|
|
|
20
21
|
# --- Hash-based change detection ---
|
|
@@ -27,22 +28,12 @@ STATE_FILE="$STATE_DIR/android-data-safety-hash"
|
|
|
27
28
|
if [ -f "$STATE_FILE" ]; then
|
|
28
29
|
STORED_HASH=$(cat "$STATE_FILE")
|
|
29
30
|
if [ "$HASH" = "$STORED_HASH" ]; then
|
|
30
|
-
|
|
31
|
-
exit 0
|
|
31
|
+
ci_skip "Data safety CSV unchanged since last upload"
|
|
32
32
|
fi
|
|
33
33
|
fi
|
|
34
34
|
|
|
35
35
|
echo "Changes detected in data safety config (hash: ${HASH:0:12}...)"
|
|
36
36
|
|
|
37
|
-
# --- Link fastlane directories ---
|
|
38
|
-
"$SCRIPT_DIR/../common/link-fastlane.sh" android
|
|
39
|
-
|
|
40
|
-
# --- Ensure fastlane is installed ---
|
|
41
|
-
cd "$APP_ROOT/android"
|
|
42
|
-
if ! bundle exec fastlane --version >/dev/null 2>&1; then
|
|
43
|
-
"$SCRIPT_DIR/../common/install-fastlane.sh" android
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
37
|
# --- Resolve service account path ---
|
|
47
38
|
SA_FULL_PATH="$PROJECT_ROOT/$GOOGLE_SA_JSON_PATH"
|
|
48
39
|
if [ ! -f "$SA_FULL_PATH" ]; then
|
|
@@ -50,16 +41,24 @@ if [ ! -f "$SA_FULL_PATH" ]; then
|
|
|
50
41
|
exit 1
|
|
51
42
|
fi
|
|
52
43
|
|
|
53
|
-
# --- Update data safety via
|
|
44
|
+
# --- Update data safety via Python script ---
|
|
45
|
+
# Exit codes: 0 = success, 2 = API limitation (manual update needed), other = error
|
|
54
46
|
echo "Updating Android data safety..."
|
|
55
47
|
|
|
48
|
+
set +e
|
|
56
49
|
PACKAGE_NAME="$PACKAGE_NAME" \
|
|
57
50
|
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH="$SA_FULL_PATH" \
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
python3 "$PROJECT_ROOT/scripts/update_data_safety.py" "$DATA_SAFETY_CSV"
|
|
52
|
+
SAFETY_EXIT=$?
|
|
53
|
+
set -e
|
|
60
54
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
if [ $SAFETY_EXIT -eq 0 ]; then
|
|
56
|
+
echo "$HASH" > "$STATE_FILE"
|
|
57
|
+
ci_done "Data safety section updated"
|
|
58
|
+
elif [ $SAFETY_EXIT -eq 2 ]; then
|
|
59
|
+
# API limitation is non-fatal; hash is NOT saved so the step retries next run
|
|
60
|
+
ci_skip "API limitation — update manually via Google Play Console (will retry next run)"
|
|
61
|
+
else
|
|
62
|
+
echo "ERROR: Data safety update failed (exit code: $SAFETY_EXIT)" >&2
|
|
63
|
+
exit $SAFETY_EXIT
|
|
64
|
+
fi
|