@daemux/store-automator 0.7.0 → 0.8.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.
Files changed (40) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +6 -2
  3. package/bin/cli.mjs +43 -15
  4. package/package.json +1 -1
  5. package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
  6. package/src/ci-config.mjs +33 -1
  7. package/src/install-paths.mjs +93 -0
  8. package/src/install.mjs +39 -78
  9. package/src/prompt.mjs +31 -7
  10. package/src/templates.mjs +71 -1
  11. package/templates/Matchfile.template +8 -0
  12. package/templates/ci.config.yaml.template +3 -0
  13. package/templates/codemagic.template.yaml +329 -111
  14. package/templates/fastlane/android/Fastfile.template +56 -8
  15. package/templates/fastlane/android/Pluginfile.template +4 -1
  16. package/templates/fastlane/ios/Fastfile.template +72 -17
  17. package/templates/fastlane/ios/Pluginfile.template +4 -1
  18. package/templates/github/workflows/android-release.yml +72 -0
  19. package/templates/github/workflows/ios-release.yml +62 -0
  20. package/templates/scripts/ci/android/build.sh +50 -0
  21. package/templates/scripts/ci/android/check-readiness.sh +99 -0
  22. package/templates/scripts/ci/android/manage-version.sh +133 -0
  23. package/templates/scripts/ci/android/setup-keystore.sh +80 -0
  24. package/templates/scripts/ci/android/sync-iap.sh +63 -0
  25. package/templates/scripts/ci/android/update-data-safety.sh +65 -0
  26. package/templates/scripts/ci/android/upload-binary.sh +62 -0
  27. package/templates/scripts/ci/android/upload-metadata.sh +59 -0
  28. package/templates/scripts/ci/common/check-changed.sh +74 -0
  29. package/templates/scripts/ci/common/flutter-setup.sh +22 -0
  30. package/templates/scripts/ci/common/install-fastlane.sh +39 -0
  31. package/templates/scripts/ci/common/link-fastlane.sh +56 -0
  32. package/templates/scripts/ci/common/read-config.sh +71 -0
  33. package/templates/scripts/ci/ios/build.sh +52 -0
  34. package/templates/scripts/ci/ios/manage-version.sh +64 -0
  35. package/templates/scripts/ci/ios/set-build-number.sh +95 -0
  36. package/templates/scripts/ci/ios/setup-signing.sh +242 -0
  37. package/templates/scripts/ci/ios/sync-iap.sh +91 -0
  38. package/templates/scripts/ci/ios/upload-binary.sh +44 -0
  39. package/templates/scripts/ci/ios/upload-metadata.sh +92 -0
  40. package/templates/scripts/update_data_safety.py +220 -0
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "App Store & Google Play automation for Flutter apps",
8
- "version": "0.7.0"
8
+ "version": "0.7.1"
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.7.0",
15
+ "version": "0.7.1",
16
16
  "keywords": ["flutter", "app-store", "google-play", "fastlane", "codemagic"]
17
17
  }
18
18
  ]
package/README.md CHANGED
@@ -27,7 +27,7 @@ npm install @daemux/store-automator
27
27
 
28
28
  The postinstall script will:
29
29
 
30
- 1. Prompt for MCP server tokens (Stitch, Cloudflare, Codemagic)
30
+ 1. Prompt for bundle ID and MCP server tokens (Stitch, Cloudflare, Codemagic)
31
31
  2. Configure `.mcp.json` with MCP servers (Playwright, mobile-mcp, Stitch, Cloudflare, Codemagic)
32
32
  3. Install the plugin marketplace and register agents
33
33
  4. Copy `CLAUDE.md` template to `.claude/CLAUDE.md`
@@ -122,13 +122,14 @@ For CI/CD environments or scripted setups, pass tokens as CLI flags to skip inte
122
122
 
123
123
  ```bash
124
124
  npx @daemux/store-automator \
125
+ --bundle-id=com.company.app \
125
126
  --codemagic-token=YOUR_CM_TOKEN \
126
127
  --stitch-key=YOUR_STITCH_KEY \
127
128
  --cloudflare-token=YOUR_CF_TOKEN \
128
129
  --cloudflare-account-id=YOUR_CF_ACCOUNT_ID
129
130
  ```
130
131
 
131
- Any tokens provided via flags will skip the corresponding interactive prompt. If all four tokens are provided, the entire interactive session is skipped.
132
+ Any tokens provided via flags will skip the corresponding interactive prompt. If all four tokens are provided, the entire interactive session is skipped. The bundle ID, if provided, is automatically written to `bundle_id` and `package_name` in `ci.config.yaml`.
132
133
 
133
134
  ## CLI Options
134
135
 
@@ -142,6 +143,9 @@ Options:
142
143
  -v, --version Show version number
143
144
  -h, --help Show help
144
145
 
146
+ App Configuration:
147
+ --bundle-id=ID Bundle ID / Package Name (e.g., com.company.app)
148
+
145
149
  MCP Token Flags (skip interactive prompts):
146
150
  --codemagic-token=TOKEN Codemagic API token
147
151
  --stitch-key=KEY Stitch MCP API key
package/bin/cli.mjs CHANGED
@@ -27,12 +27,15 @@ function flagValue(arg, prefix) {
27
27
  return arg.startsWith(prefix) ? arg.slice(prefix.length) : undefined;
28
28
  }
29
29
 
30
- const tokenFlags = {
30
+ const valueFlags = {
31
31
  '--codemagic-token=': 'codemagicToken',
32
32
  '--codemagic-team-id=': 'codemagicTeamId',
33
33
  '--stitch-key=': 'stitchApiKey',
34
34
  '--cloudflare-token=': 'cloudflareToken',
35
35
  '--cloudflare-account-id=': 'cloudflareAccountId',
36
+ '--bundle-id=': 'bundleId',
37
+ '--match-deploy-key=': 'matchDeployKey',
38
+ '--match-git-url=': 'matchGitUrl',
36
39
  };
37
40
 
38
41
  for (const arg of args) {
@@ -41,15 +44,11 @@ for (const arg of args) {
41
44
  if ((v = flagValue(arg, '--branch=')) !== undefined) { cmBranch = v; continue; }
42
45
  if ((v = flagValue(arg, '--workflow=')) !== undefined) { cmWorkflowId = v; continue; }
43
46
 
44
- let matched = false;
45
- for (const [prefix, key] of Object.entries(tokenFlags)) {
46
- if ((v = flagValue(arg, prefix)) !== undefined) {
47
- cliTokens[key] = v;
48
- matched = true;
49
- break;
50
- }
47
+ const valueFlagEntry = Object.entries(valueFlags).find(([prefix]) => arg.startsWith(prefix));
48
+ if (valueFlagEntry) {
49
+ cliTokens[valueFlagEntry[1]] = arg.slice(valueFlagEntry[0].length);
50
+ continue;
51
51
  }
52
- if (matched) continue;
53
52
 
54
53
  switch (arg) {
55
54
  case '-g':
@@ -69,6 +68,9 @@ for (const arg of args) {
69
68
  case '--github-setup':
70
69
  action = 'github-setup';
71
70
  break;
71
+ case '--github-actions':
72
+ cliTokens.githubActions = true;
73
+ break;
72
74
  case '--trigger':
73
75
  cmTrigger = true;
74
76
  break;
@@ -86,6 +88,9 @@ Options:
86
88
  -v, --version Show version number
87
89
  -h, --help Show help
88
90
 
91
+ App Configuration:
92
+ --bundle-id=ID Bundle ID / Package Name (e.g., com.company.app)
93
+
89
94
  MCP Token Flags (skip interactive prompts):
90
95
  --codemagic-token=TOKEN Codemagic API token
91
96
  --codemagic-team-id=ID Codemagic Team ID (from Teams page)
@@ -101,34 +106,57 @@ Codemagic:
101
106
  --trigger Trigger build after setup
102
107
  --wait Wait for build completion (implies --trigger)
103
108
 
109
+ GitHub Actions CI mode:
110
+ --github-actions Use GitHub Actions instead of Codemagic (skip MCP setup)
111
+ --match-deploy-key=PATH Path to Match deploy key file (required with --github-actions)
112
+ --match-git-url=URL Git URL for Match certificates repo (required with --github-actions)
113
+
104
114
  GitHub Actions (auto-configured during install if gh CLI available):
105
115
  --github-setup Set CM_API_TOKEN secret for GitHub Actions
106
116
  --token=TOKEN API token (or set CM_API_TOKEN env var)
107
117
 
108
118
  Examples:
109
- npx @daemux/store-automator Install for project
119
+ npx @daemux/store-automator Install for project (Codemagic)
110
120
  npx @daemux/store-automator -g Install globally
111
121
  npx @daemux/store-automator -u Uninstall from project
112
122
  npx @daemux/store-automator -g -u Uninstall globally
113
123
  npx @daemux/store-automator --codemagic-setup Register with Codemagic
114
124
  npx @daemux/store-automator --codemagic-setup --trigger --wait Trigger and wait
115
- npx @daemux/store-automator --github-setup Configure GitHub Actions
125
+ npx @daemux/store-automator --github-setup Configure GitHub Actions secret
126
+
127
+ GitHub Actions install:
128
+ npx @daemux/store-automator --github-actions --bundle-id=ID --match-deploy-key=PATH --match-git-url=URL
116
129
 
117
- Non-interactive install (CI/CD):
118
- npx @daemux/store-automator --codemagic-token=TOKEN --codemagic-team-id=ID --stitch-key=KEY
130
+ Non-interactive install (Codemagic):
131
+ npx @daemux/store-automator --bundle-id=ID --codemagic-token=TOKEN --stitch-key=KEY
119
132
  npx @daemux/store-automator --cloudflare-token=TOKEN --cloudflare-account-id=ID`);
120
133
  process.exit(0);
121
- break; // eslint: no-fallthrough
122
134
  case '-v':
123
135
  case '--version':
124
136
  console.log(pkg.version);
125
137
  process.exit(0);
126
- break; // eslint: no-fallthrough
127
138
  }
128
139
  }
129
140
 
130
141
  if (cmWait) cmTrigger = true;
131
142
 
143
+ if (cliTokens.githubActions) {
144
+ const missing = [];
145
+ if (!cliTokens.matchDeployKey) missing.push('--match-deploy-key');
146
+ if (!cliTokens.matchGitUrl) missing.push('--match-git-url');
147
+ if (!cliTokens.bundleId) missing.push('--bundle-id');
148
+ if (missing.length > 0) {
149
+ console.error(`Error: --github-actions requires: ${missing.join(', ')}`);
150
+ console.error('');
151
+ console.error('Example:');
152
+ console.error(' npx @daemux/store-automator --github-actions \\');
153
+ console.error(' --bundle-id=com.company.app \\');
154
+ console.error(' --match-deploy-key=creds/match_deploy_key \\');
155
+ console.error(' --match-git-url=git@github.com:org/certs.git');
156
+ process.exit(1);
157
+ }
158
+ }
159
+
132
160
  notifier.notify();
133
161
 
134
162
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daemux/store-automator",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Full App Store & Google Play automation for Flutter apps with Claude Code agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "store-automator",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "App Store & Google Play automation agents for Flutter app publishing",
5
5
  "author": {
6
6
  "name": "Daemux"
package/src/ci-config.mjs CHANGED
@@ -5,6 +5,10 @@ const CI_CONFIG_FILE = 'ci.config.yaml';
5
5
  const FIELD_PATTERNS = {
6
6
  app_id: /^(\s*app_id:\s*)"[^"]*"/m,
7
7
  team_id: /^(\s*team_id:\s*)"[^"]*"/m,
8
+ bundle_id: /^(\s*bundle_id:\s*)"[^"]*"/m,
9
+ package_name: /^(\s*package_name:\s*)"[^"]*"/m,
10
+ deploy_key_path: /^(\s*deploy_key_path:\s*)"[^"]*"/m,
11
+ git_url: /^(\s*git_url:\s*)"[^"]*"/m,
8
12
  };
9
13
 
10
14
  function writeCiField(projectDir, field, value) {
@@ -16,7 +20,8 @@ function writeCiField(projectDir, field, value) {
16
20
  const content = readFileSync(configPath, 'utf8');
17
21
  if (!pattern.test(content)) return false;
18
22
 
19
- const updated = content.replace(pattern, `$1"${value}"`);
23
+ const safeValue = value.replace(/\$/g, '$$$$');
24
+ const updated = content.replace(pattern, `$1"${safeValue}"`);
20
25
  if (updated === content) return false;
21
26
 
22
27
  writeFileSync(configPath, updated, 'utf8');
@@ -33,3 +38,30 @@ export function writeCiAppId(projectDir, appId) {
33
38
  export function writeCiTeamId(projectDir, teamId) {
34
39
  return writeCiField(projectDir, 'team_id', teamId);
35
40
  }
41
+
42
+ export function writeCiBundleId(projectDir, bundleId) {
43
+ return writeCiField(projectDir, 'bundle_id', bundleId);
44
+ }
45
+
46
+ export function writeCiPackageName(projectDir, packageName) {
47
+ return writeCiField(projectDir, 'package_name', packageName);
48
+ }
49
+
50
+ export function writeMatchConfig(projectDir, { deployKeyPath, gitUrl }) {
51
+ const wrote1 = writeCiField(projectDir, 'deploy_key_path', deployKeyPath);
52
+ const wrote2 = writeCiField(projectDir, 'git_url', gitUrl);
53
+ return wrote1 || wrote2;
54
+ }
55
+
56
+ export function readFlutterRoot(projectDir) {
57
+ const configPath = join(projectDir, CI_CONFIG_FILE);
58
+ if (!existsSync(configPath)) return '.';
59
+
60
+ try {
61
+ const content = readFileSync(configPath, 'utf8');
62
+ const match = content.match(/^flutter_root:\s*"([^"]*)"/m);
63
+ return match ? match[1] : '.';
64
+ } catch {
65
+ return '.';
66
+ }
67
+ }
@@ -0,0 +1,93 @@
1
+ import { execSync, execFileSync } from 'node:child_process';
2
+ import { installGitHubActionsTemplates, installMatchfile } from './templates.mjs';
3
+ import { findAppByRepo, addApp, normalizeRepoUrl } from './codemagic-api.mjs';
4
+ import {
5
+ writeCiAppId, writeCiTeamId, writeMatchConfig, readFlutterRoot,
6
+ } from './ci-config.mjs';
7
+ import { updateMcpAppId, updateMcpTeamId } from './mcp-setup.mjs';
8
+
9
+ function setupGitHubActionsSecret(codemagicToken) {
10
+ if (!codemagicToken) return false;
11
+
12
+ try {
13
+ execFileSync('which', ['gh'], { encoding: 'utf8', stdio: 'pipe' });
14
+ const authStatus = execFileSync('gh', ['auth', 'status'], { encoding: 'utf8', stdio: 'pipe' });
15
+ if (authStatus.includes('not logged')) return false;
16
+
17
+ execFileSync('gh', ['secret', 'set', 'CM_API_TOKEN', '--body', codemagicToken], {
18
+ encoding: 'utf8',
19
+ stdio: 'pipe',
20
+ });
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ async function setupCodemagicApp(projectDir, codemagicToken, codemagicTeamId) {
28
+ if (!codemagicToken) return;
29
+
30
+ let repoUrl;
31
+ try {
32
+ const raw = execSync('git remote get-url origin', {
33
+ encoding: 'utf8',
34
+ stdio: ['pipe', 'pipe', 'pipe'],
35
+ }).trim();
36
+ if (!raw) return;
37
+ repoUrl = normalizeRepoUrl(raw);
38
+ } catch {
39
+ return;
40
+ }
41
+
42
+ try {
43
+ let app = await findAppByRepo(codemagicToken, repoUrl);
44
+ if (!app) {
45
+ app = await addApp(codemagicToken, repoUrl, codemagicTeamId);
46
+ console.log(`Codemagic app created: ${app.appName || app._id}`);
47
+ } else {
48
+ console.log(`Codemagic app found: ${app.appName || app._id}`);
49
+ }
50
+
51
+ const written = writeCiAppId(projectDir, app._id);
52
+ if (written) {
53
+ console.log(`Codemagic app_id written to ci.config.yaml`);
54
+ }
55
+
56
+ updateMcpAppId(projectDir, app._id);
57
+
58
+ if (codemagicTeamId) {
59
+ writeCiTeamId(projectDir, codemagicTeamId);
60
+ updateMcpTeamId(projectDir, codemagicTeamId);
61
+ }
62
+ } catch (err) {
63
+ console.log(`Codemagic auto-setup skipped: ${err.message || err}`);
64
+ }
65
+ }
66
+
67
+ export function installGitHubActionsPath(projectDir, packageDir, cliTokens) {
68
+ console.log('Configuring GitHub Actions mode...');
69
+ installGitHubActionsTemplates(projectDir, packageDir);
70
+
71
+ const flutterRoot = readFlutterRoot(projectDir);
72
+ installMatchfile(projectDir, packageDir, flutterRoot, {
73
+ matchGitUrl: cliTokens.matchGitUrl,
74
+ bundleId: cliTokens.bundleId,
75
+ });
76
+
77
+ const wrote = writeMatchConfig(projectDir, {
78
+ deployKeyPath: cliTokens.matchDeployKey,
79
+ gitUrl: cliTokens.matchGitUrl,
80
+ });
81
+ if (wrote) console.log('Match credentials written to ci.config.yaml');
82
+ }
83
+
84
+ export async function installCodemagicPath(projectDir, tokens) {
85
+ await setupCodemagicApp(projectDir, tokens.codemagicToken, tokens.codemagicTeamId);
86
+
87
+ const ghConfigured = setupGitHubActionsSecret(tokens.codemagicToken);
88
+ if (ghConfigured) {
89
+ console.log('GitHub Actions: CM_API_TOKEN secret configured.');
90
+ } else if (tokens.codemagicToken) {
91
+ console.log('GitHub Actions: secret not set (gh CLI unavailable or not authenticated).');
92
+ }
93
+ }
package/src/install.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, rmSync, cpSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
- import { execSync, execFileSync } from 'node:child_process';
4
+ import { execSync } from 'node:child_process';
5
5
  import {
6
6
  MARKETPLACE_DIR, KNOWN_MP_PATH, CACHE_DIR,
7
7
  MARKETPLACE_NAME, PLUGIN_REF,
@@ -9,10 +9,10 @@ import {
9
9
  } from './utils.mjs';
10
10
  import { injectEnvVars, injectStatusLine } from './settings.mjs';
11
11
  import { promptForTokens } from './prompt.mjs';
12
- import { getMcpServers, writeMcpJson, updateMcpAppId, updateMcpTeamId } from './mcp-setup.mjs';
12
+ import { getMcpServers, writeMcpJson } from './mcp-setup.mjs';
13
13
  import { installClaudeMd, installCiTemplates, installFirebaseTemplates } from './templates.mjs';
14
- import { findAppByRepo, addApp, normalizeRepoUrl } from './codemagic-api.mjs';
15
- import { writeCiAppId, writeCiTeamId } from './ci-config.mjs';
14
+ import { writeCiBundleId, writeCiPackageName } from './ci-config.mjs';
15
+ import { installGitHubActionsPath, installCodemagicPath } from './install-paths.mjs';
16
16
 
17
17
  function checkClaudeCli() {
18
18
  const result = exec('command -v claude') || exec('which claude');
@@ -94,61 +94,19 @@ function printSummary(scope, oldVersion, newVersion) {
94
94
  }
95
95
  }
96
96
 
97
- function setupGitHubActions(codemagicToken) {
98
- if (!codemagicToken) return false;
99
-
100
- try {
101
- execFileSync('which', ['gh'], { encoding: 'utf8', stdio: 'pipe' });
102
- const authStatus = execFileSync('gh', ['auth', 'status'], { encoding: 'utf8', stdio: 'pipe' });
103
- if (authStatus.includes('not logged')) return false;
104
-
105
- execFileSync('gh', ['secret', 'set', 'CM_API_TOKEN', '--body', codemagicToken], {
106
- encoding: 'utf8',
107
- stdio: 'pipe',
108
- });
109
- return true;
110
- } catch {
111
- return false;
112
- }
113
- }
114
-
115
- async function setupCodemagicApp(projectDir, codemagicToken, codemagicTeamId) {
116
- if (!codemagicToken) return;
117
-
118
- let repoUrl;
119
- try {
120
- const raw = execSync('git remote get-url origin', {
121
- encoding: 'utf8',
122
- stdio: ['pipe', 'pipe', 'pipe'],
123
- }).trim();
124
- if (!raw) return;
125
- repoUrl = normalizeRepoUrl(raw);
126
- } catch {
127
- return;
128
- }
129
-
130
- try {
131
- let app = await findAppByRepo(codemagicToken, repoUrl);
132
- if (!app) {
133
- app = await addApp(codemagicToken, repoUrl, codemagicTeamId);
134
- console.log(`Codemagic app created: ${app.appName || app._id}`);
135
- } else {
136
- console.log(`Codemagic app found: ${app.appName || app._id}`);
137
- }
138
-
139
- const written = writeCiAppId(projectDir, app._id);
140
- if (written) {
141
- console.log(`Codemagic app_id written to ci.config.yaml`);
142
- }
143
-
144
- updateMcpAppId(projectDir, app._id);
145
-
146
- if (codemagicTeamId) {
147
- writeCiTeamId(projectDir, codemagicTeamId);
148
- updateMcpTeamId(projectDir, codemagicTeamId);
149
- }
150
- } catch (err) {
151
- console.log(`Codemagic auto-setup skipped: ${err.message || err}`);
97
+ function printNextSteps(isGitHubActions) {
98
+ console.log('');
99
+ console.log('Next steps:');
100
+ if (isGitHubActions) {
101
+ console.log(' 1. Fill ci.config.yaml with credentials');
102
+ console.log(' 2. Add creds/AuthKey.p8 and creds/play-service-account.json');
103
+ console.log(' 3. Set MATCH_PASSWORD secret in GitHub repository settings');
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"');
152
110
  }
153
111
  }
154
112
 
@@ -157,11 +115,18 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
157
115
 
158
116
  console.log('Installing/updating Daemux Store Automator...');
159
117
 
160
- const tokens = await promptForTokens(cliTokens);
118
+ const isGitHubActions = Boolean(cliTokens.githubActions);
119
+
120
+ const tokens = isGitHubActions
121
+ ? { bundleId: cliTokens.bundleId ?? '' }
122
+ : await promptForTokens(cliTokens);
161
123
 
162
124
  const projectDir = process.cwd();
163
- const servers = getMcpServers(tokens);
164
- writeMcpJson(projectDir, servers);
125
+
126
+ if (!isGitHubActions) {
127
+ const servers = getMcpServers(tokens);
128
+ writeMcpJson(projectDir, servers);
129
+ }
165
130
 
166
131
  const oldVersion = readMarketplaceVersion();
167
132
  const packageDir = getPackageDir();
@@ -183,7 +148,17 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
183
148
  installCiTemplates(projectDir, packageDir);
184
149
  installFirebaseTemplates(projectDir, packageDir);
185
150
 
186
- await setupCodemagicApp(projectDir, tokens.codemagicToken, tokens.codemagicTeamId);
151
+ if (tokens.bundleId) {
152
+ const written = writeCiBundleId(projectDir, tokens.bundleId);
153
+ if (written) console.log(`Bundle ID set in ci.config.yaml: ${tokens.bundleId}`);
154
+ writeCiPackageName(projectDir, tokens.bundleId);
155
+ }
156
+
157
+ if (isGitHubActions) {
158
+ installGitHubActionsPath(projectDir, packageDir, cliTokens);
159
+ } else {
160
+ await installCodemagicPath(projectDir, tokens);
161
+ }
187
162
 
188
163
  const scopeLabel = scope === 'user' ? 'global' : 'project';
189
164
  console.log(`Configuring ${scopeLabel} settings...`);
@@ -191,20 +166,6 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
191
166
  injectEnvVars(settingsPath);
192
167
  injectStatusLine(settingsPath);
193
168
 
194
- const ghConfigured = setupGitHubActions(tokens.codemagicToken);
195
- if (ghConfigured) {
196
- console.log('GitHub Actions: CM_API_TOKEN secret configured.');
197
- } else if (tokens.codemagicToken) {
198
- console.log('GitHub Actions: secret not set (gh CLI unavailable or not authenticated).');
199
- }
200
-
201
169
  printSummary(scope, oldVersion, newVersion);
202
- console.log('');
203
- console.log('Next steps:');
204
- console.log(' 1. Fill ci.config.yaml (codemagic.app_id is auto-configured if token was provided)');
205
- console.log(' 2. Add creds/AuthKey.p8 and creds/play-service-account.json');
206
- console.log(' 3. Start Claude Code');
207
- if (!ghConfigured) {
208
- console.log(' Note: For auto-trigger, install gh CLI and run "gh auth login"');
209
- }
170
+ printNextSteps(isGitHubActions);
210
171
  }
package/src/prompt.mjs CHANGED
@@ -22,8 +22,16 @@ function allTokensProvided(cliTokens) {
22
22
  );
23
23
  }
24
24
 
25
+ function allPromptsProvided(cliTokens) {
26
+ return (
27
+ cliTokens.bundleId !== undefined &&
28
+ allTokensProvided(cliTokens)
29
+ );
30
+ }
31
+
25
32
  export async function promptForTokens(cliTokens = {}) {
26
33
  const result = {
34
+ bundleId: cliTokens.bundleId ?? '',
27
35
  stitchApiKey: cliTokens.stitchApiKey ?? '',
28
36
  cloudflareToken: cliTokens.cloudflareToken ?? '',
29
37
  cloudflareAccountId: cliTokens.cloudflareAccountId ?? '',
@@ -31,14 +39,14 @@ export async function promptForTokens(cliTokens = {}) {
31
39
  codemagicTeamId: cliTokens.codemagicTeamId ?? '',
32
40
  };
33
41
 
34
- if (allTokensProvided(cliTokens)) {
35
- console.log('All MCP tokens provided via CLI flags, skipping prompts.');
42
+ if (allPromptsProvided(cliTokens)) {
43
+ console.log('All configuration provided via CLI flags, skipping prompts.');
36
44
  return result;
37
45
  }
38
46
 
39
47
  if (!isInteractive()) {
40
- console.log('Non-interactive terminal detected, skipping token prompts.');
41
- console.log('Run "npx store-automator" manually to configure MCP tokens.');
48
+ console.log('Non-interactive terminal detected, skipping prompts.');
49
+ console.log('Run "npx store-automator" manually to configure.');
42
50
  return result;
43
51
  }
44
52
 
@@ -47,12 +55,28 @@ export async function promptForTokens(cliTokens = {}) {
47
55
  output: process.stdout,
48
56
  });
49
57
 
50
- console.log('');
51
- console.log('MCP Server Configuration');
52
- console.log('Press Enter to skip any token you do not have yet.');
53
58
  console.log('');
54
59
 
55
60
  try {
61
+ if (cliTokens.bundleId === undefined) {
62
+ console.log('App Configuration');
63
+ console.log('');
64
+ result.bundleId = await ask(
65
+ rl,
66
+ 'Bundle ID / Package Name (e.g., com.company.app): '
67
+ );
68
+ console.log('');
69
+ }
70
+
71
+ if (allTokensProvided(cliTokens)) {
72
+ console.log('All MCP tokens provided via CLI flags.');
73
+ return result;
74
+ }
75
+
76
+ console.log('MCP Server Configuration');
77
+ console.log('Press Enter to skip any token you do not have yet.');
78
+ console.log('');
79
+
56
80
  if (cliTokens.stitchApiKey === undefined) {
57
81
  result.stitchApiKey = await ask(
58
82
  rl,
package/src/templates.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, cpSync, copyFileSync } from 'node:fs';
1
+ import { existsSync, cpSync, copyFileSync, chmodSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { ensureDir } from './utils.mjs';
4
4
 
@@ -22,6 +22,17 @@ const FIREBASE_COPIES = [
22
22
  ['firebase/.firebaserc.template', 'backend/.firebaserc'],
23
23
  ];
24
24
 
25
+ const GH_ACTIONS_WORKFLOWS = [
26
+ ['github/workflows/ios-release.yml', '.github/workflows/ios-release.yml'],
27
+ ['github/workflows/android-release.yml', '.github/workflows/android-release.yml'],
28
+ ];
29
+
30
+ const GH_ACTIONS_SCRIPT_DIRS = [
31
+ 'scripts/ci/common',
32
+ 'scripts/ci/android',
33
+ 'scripts/ci/ios',
34
+ ];
35
+
25
36
  function copyIfMissing(srcPath, destPath, label, isDirectory) {
26
37
  if (!existsSync(srcPath)) return;
27
38
  if (existsSync(destPath)) {
@@ -81,3 +92,62 @@ export function installFirebaseTemplates(projectDir, packageDir) {
81
92
  copyIfMissing(join(templateDir, src), join(projectDir, dest), dest, false);
82
93
  }
83
94
  }
95
+
96
+ function copyOrUpdate(srcPath, destPath, label) {
97
+ if (!existsSync(srcPath)) return;
98
+ ensureDir(join(destPath, '..'));
99
+ if (existsSync(destPath)) {
100
+ console.log(` ${label} exists, overwriting.`);
101
+ }
102
+ copyFileSync(srcPath, destPath);
103
+ }
104
+
105
+ function chmodShFiles(dirPath) {
106
+ if (!existsSync(dirPath)) return;
107
+ for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
108
+ const fullPath = join(dirPath, entry.name);
109
+ if (entry.isDirectory()) {
110
+ chmodShFiles(fullPath);
111
+ } else if (entry.name.endsWith('.sh')) {
112
+ chmodSync(fullPath, 0o755);
113
+ }
114
+ }
115
+ }
116
+
117
+ export function installGitHubActionsTemplates(projectDir, packageDir) {
118
+ console.log('Installing GitHub Actions templates...');
119
+ const templateDir = join(packageDir, 'templates');
120
+
121
+ for (const [src, dest] of GH_ACTIONS_WORKFLOWS) {
122
+ copyOrUpdate(join(templateDir, src), join(projectDir, dest), dest);
123
+ }
124
+
125
+ for (const scriptDir of GH_ACTIONS_SCRIPT_DIRS) {
126
+ const scriptsSrc = join(templateDir, scriptDir);
127
+ const scriptsDest = join(projectDir, scriptDir);
128
+ if (existsSync(scriptsSrc)) {
129
+ ensureDir(scriptsDest);
130
+ cpSync(scriptsSrc, scriptsDest, { recursive: true });
131
+ chmodShFiles(scriptsDest);
132
+ console.log(` ${scriptDir}/ copied.`);
133
+ }
134
+ }
135
+ }
136
+
137
+ export function installMatchfile(projectDir, packageDir, flutterRoot, { matchGitUrl, bundleId }) {
138
+ console.log('Installing Matchfile...');
139
+ const src = join(packageDir, 'templates', 'Matchfile.template');
140
+ if (!existsSync(src)) return;
141
+
142
+ const destDir = join(projectDir, flutterRoot, 'ios', 'fastlane');
143
+ ensureDir(destDir);
144
+ const dest = join(destDir, 'Matchfile');
145
+
146
+ copyOrUpdate(src, dest, 'Matchfile');
147
+
148
+ const content = readFileSync(dest, 'utf8');
149
+ const updated = content
150
+ .replace('{{MATCH_GIT_URL}}', matchGitUrl)
151
+ .replace('{{BUNDLE_ID}}', bundleId);
152
+ writeFileSync(dest, updated, 'utf8');
153
+ }
@@ -0,0 +1,8 @@
1
+ git_url(ENV["MATCH_GIT_URL"] || "{{MATCH_GIT_URL}}")
2
+ storage_mode("git")
3
+ type("appstore")
4
+ app_identifier(ENV["BUNDLE_ID"] || "{{BUNDLE_ID}}")
5
+ git_branch("main")
6
+
7
+ # Set to true after initial cert creation for faster CI runs
8
+ # readonly(true)
@@ -12,6 +12,9 @@ credentials:
12
12
  service_account_json_path: creds/play-service-account.json
13
13
  android:
14
14
  keystore_password: "REPLACE_WITH_PASSWORD"
15
+ match:
16
+ deploy_key_path: ""
17
+ git_url: ""
15
18
 
16
19
  # === APP IDENTITY ===
17
20
  app: