@daemux/store-automator 0.7.1 → 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.
- package/bin/cli.mjs +38 -14
- package/package.json +1 -1
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/src/ci-config.mjs +21 -0
- package/src/install-paths.mjs +93 -0
- package/src/install.mjs +33 -78
- package/src/templates.mjs +71 -1
- package/templates/Matchfile.template +8 -0
- package/templates/ci.config.yaml.template +3 -0
- package/templates/fastlane/android/Fastfile.template +2 -2
- package/templates/github/workflows/android-release.yml +72 -0
- package/templates/github/workflows/ios-release.yml +62 -0
- package/templates/scripts/ci/android/build.sh +50 -0
- package/templates/scripts/ci/android/check-readiness.sh +99 -0
- package/templates/scripts/ci/android/manage-version.sh +133 -0
- package/templates/scripts/ci/android/setup-keystore.sh +80 -0
- package/templates/scripts/ci/android/sync-iap.sh +63 -0
- package/templates/scripts/ci/android/update-data-safety.sh +65 -0
- package/templates/scripts/ci/android/upload-binary.sh +62 -0
- package/templates/scripts/ci/android/upload-metadata.sh +59 -0
- package/templates/scripts/ci/common/check-changed.sh +74 -0
- package/templates/scripts/ci/common/flutter-setup.sh +22 -0
- package/templates/scripts/ci/common/install-fastlane.sh +39 -0
- package/templates/scripts/ci/common/link-fastlane.sh +56 -0
- package/templates/scripts/ci/common/read-config.sh +71 -0
- package/templates/scripts/ci/ios/build.sh +52 -0
- package/templates/scripts/ci/ios/manage-version.sh +64 -0
- package/templates/scripts/ci/ios/set-build-number.sh +95 -0
- package/templates/scripts/ci/ios/setup-signing.sh +242 -0
- package/templates/scripts/ci/ios/sync-iap.sh +91 -0
- package/templates/scripts/ci/ios/upload-binary.sh +44 -0
- package/templates/scripts/ci/ios/upload-metadata.sh +92 -0
- package/templates/scripts/update_data_safety.py +220 -0
package/bin/cli.mjs
CHANGED
|
@@ -34,6 +34,8 @@ const valueFlags = {
|
|
|
34
34
|
'--cloudflare-token=': 'cloudflareToken',
|
|
35
35
|
'--cloudflare-account-id=': 'cloudflareAccountId',
|
|
36
36
|
'--bundle-id=': 'bundleId',
|
|
37
|
+
'--match-deploy-key=': 'matchDeployKey',
|
|
38
|
+
'--match-git-url=': 'matchGitUrl',
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
for (const arg of args) {
|
|
@@ -42,15 +44,11 @@ for (const arg of args) {
|
|
|
42
44
|
if ((v = flagValue(arg, '--branch=')) !== undefined) { cmBranch = v; continue; }
|
|
43
45
|
if ((v = flagValue(arg, '--workflow=')) !== undefined) { cmWorkflowId = v; continue; }
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
matched = true;
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
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;
|
|
52
51
|
}
|
|
53
|
-
if (matched) continue;
|
|
54
52
|
|
|
55
53
|
switch (arg) {
|
|
56
54
|
case '-g':
|
|
@@ -70,6 +68,9 @@ for (const arg of args) {
|
|
|
70
68
|
case '--github-setup':
|
|
71
69
|
action = 'github-setup';
|
|
72
70
|
break;
|
|
71
|
+
case '--github-actions':
|
|
72
|
+
cliTokens.githubActions = true;
|
|
73
|
+
break;
|
|
73
74
|
case '--trigger':
|
|
74
75
|
cmTrigger = true;
|
|
75
76
|
break;
|
|
@@ -105,34 +106,57 @@ Codemagic:
|
|
|
105
106
|
--trigger Trigger build after setup
|
|
106
107
|
--wait Wait for build completion (implies --trigger)
|
|
107
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
|
+
|
|
108
114
|
GitHub Actions (auto-configured during install if gh CLI available):
|
|
109
115
|
--github-setup Set CM_API_TOKEN secret for GitHub Actions
|
|
110
116
|
--token=TOKEN API token (or set CM_API_TOKEN env var)
|
|
111
117
|
|
|
112
118
|
Examples:
|
|
113
|
-
npx @daemux/store-automator Install for project
|
|
119
|
+
npx @daemux/store-automator Install for project (Codemagic)
|
|
114
120
|
npx @daemux/store-automator -g Install globally
|
|
115
121
|
npx @daemux/store-automator -u Uninstall from project
|
|
116
122
|
npx @daemux/store-automator -g -u Uninstall globally
|
|
117
123
|
npx @daemux/store-automator --codemagic-setup Register with Codemagic
|
|
118
124
|
npx @daemux/store-automator --codemagic-setup --trigger --wait Trigger and wait
|
|
119
|
-
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
|
|
120
129
|
|
|
121
|
-
Non-interactive install (
|
|
122
|
-
npx @daemux/store-automator --bundle-id=
|
|
130
|
+
Non-interactive install (Codemagic):
|
|
131
|
+
npx @daemux/store-automator --bundle-id=ID --codemagic-token=TOKEN --stitch-key=KEY
|
|
123
132
|
npx @daemux/store-automator --cloudflare-token=TOKEN --cloudflare-account-id=ID`);
|
|
124
133
|
process.exit(0);
|
|
125
|
-
break; // eslint: no-fallthrough
|
|
126
134
|
case '-v':
|
|
127
135
|
case '--version':
|
|
128
136
|
console.log(pkg.version);
|
|
129
137
|
process.exit(0);
|
|
130
|
-
break; // eslint: no-fallthrough
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
if (cmWait) cmTrigger = true;
|
|
135
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
|
+
|
|
136
160
|
notifier.notify();
|
|
137
161
|
|
|
138
162
|
try {
|
package/package.json
CHANGED
package/src/ci-config.mjs
CHANGED
|
@@ -7,6 +7,8 @@ const FIELD_PATTERNS = {
|
|
|
7
7
|
team_id: /^(\s*team_id:\s*)"[^"]*"/m,
|
|
8
8
|
bundle_id: /^(\s*bundle_id:\s*)"[^"]*"/m,
|
|
9
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,
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
function writeCiField(projectDir, field, value) {
|
|
@@ -44,3 +46,22 @@ export function writeCiBundleId(projectDir, bundleId) {
|
|
|
44
46
|
export function writeCiPackageName(projectDir, packageName) {
|
|
45
47
|
return writeCiField(projectDir, 'package_name', packageName);
|
|
46
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
|
|
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
|
|
12
|
+
import { getMcpServers, writeMcpJson } from './mcp-setup.mjs';
|
|
13
13
|
import { installClaudeMd, installCiTemplates, installFirebaseTemplates } from './templates.mjs';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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
|
-
|
|
164
|
-
|
|
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();
|
|
@@ -189,7 +154,11 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
189
154
|
writeCiPackageName(projectDir, tokens.bundleId);
|
|
190
155
|
}
|
|
191
156
|
|
|
192
|
-
|
|
157
|
+
if (isGitHubActions) {
|
|
158
|
+
installGitHubActionsPath(projectDir, packageDir, cliTokens);
|
|
159
|
+
} else {
|
|
160
|
+
await installCodemagicPath(projectDir, tokens);
|
|
161
|
+
}
|
|
193
162
|
|
|
194
163
|
const scopeLabel = scope === 'user' ? 'global' : 'project';
|
|
195
164
|
console.log(`Configuring ${scopeLabel} settings...`);
|
|
@@ -197,20 +166,6 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
197
166
|
injectEnvVars(settingsPath);
|
|
198
167
|
injectStatusLine(settingsPath);
|
|
199
168
|
|
|
200
|
-
const ghConfigured = setupGitHubActions(tokens.codemagicToken);
|
|
201
|
-
if (ghConfigured) {
|
|
202
|
-
console.log('GitHub Actions: CM_API_TOKEN secret configured.');
|
|
203
|
-
} else if (tokens.codemagicToken) {
|
|
204
|
-
console.log('GitHub Actions: secret not set (gh CLI unavailable or not authenticated).');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
169
|
printSummary(scope, oldVersion, newVersion);
|
|
208
|
-
|
|
209
|
-
console.log('Next steps:');
|
|
210
|
-
console.log(' 1. Fill ci.config.yaml (codemagic.app_id is auto-configured if token was provided)');
|
|
211
|
-
console.log(' 2. Add creds/AuthKey.p8 and creds/play-service-account.json');
|
|
212
|
-
console.log(' 3. Start Claude Code');
|
|
213
|
-
if (!ghConfigured) {
|
|
214
|
-
console.log(' Note: For auto-trigger, install gh CLI and run "gh auth login"');
|
|
215
|
-
}
|
|
170
|
+
printNextSteps(isGitHubActions);
|
|
216
171
|
}
|
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
|
+
}
|
|
@@ -24,7 +24,7 @@ end
|
|
|
24
24
|
|
|
25
25
|
def rollout_options
|
|
26
26
|
{
|
|
27
|
-
rollout: ENV.fetch("ROLLOUT_FRACTION", "").empty? ? nil : ENV["ROLLOUT_FRACTION"],
|
|
27
|
+
rollout: ENV.fetch("ROLLOUT_FRACTION", "").empty? ? nil : ENV["ROLLOUT_FRACTION"].to_f,
|
|
28
28
|
in_app_update_priority: ENV.fetch("IN_APP_UPDATE_PRIORITY", "3").to_i
|
|
29
29
|
}
|
|
30
30
|
end
|
|
@@ -86,6 +86,6 @@ platform :android do
|
|
|
86
86
|
lane :update_data_safety do
|
|
87
87
|
script = "#{ROOT_DIR}/scripts/update_data_safety.py"
|
|
88
88
|
csv = "#{ROOT_DIR}/fastlane/data_safety.csv"
|
|
89
|
-
sh("python3", script) if File.exist?(csv)
|
|
89
|
+
sh("python3", script, csv) if File.exist?(csv)
|
|
90
90
|
end
|
|
91
91
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: Android Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
skip_build:
|
|
7
|
+
description: 'Skip build (metadata only)'
|
|
8
|
+
required: false
|
|
9
|
+
default: 'false'
|
|
10
|
+
type: boolean
|
|
11
|
+
|
|
12
|
+
env:
|
|
13
|
+
FLUTTER_VERSION: '3.x'
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
android-release:
|
|
17
|
+
runs-on: macos-latest-xlarge
|
|
18
|
+
timeout-minutes: 60
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Restore CI state cache
|
|
23
|
+
uses: actions/cache@v4
|
|
24
|
+
with:
|
|
25
|
+
path: .ci-state
|
|
26
|
+
key: ci-state-android-${{ github.ref_name }}
|
|
27
|
+
restore-keys: ci-state-android-
|
|
28
|
+
|
|
29
|
+
- name: Setup Flutter
|
|
30
|
+
uses: subosito/flutter-action@v2
|
|
31
|
+
with:
|
|
32
|
+
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
33
|
+
|
|
34
|
+
- name: Setup Java
|
|
35
|
+
uses: actions/setup-java@v4
|
|
36
|
+
with:
|
|
37
|
+
distribution: 'temurin'
|
|
38
|
+
java-version: '17'
|
|
39
|
+
|
|
40
|
+
- name: Upload metadata
|
|
41
|
+
run: scripts/ci/android/upload-metadata.sh
|
|
42
|
+
|
|
43
|
+
- name: Setup keystore
|
|
44
|
+
if: inputs.skip_build != 'true'
|
|
45
|
+
run: source scripts/ci/android/setup-keystore.sh
|
|
46
|
+
|
|
47
|
+
- name: Check Google Play readiness
|
|
48
|
+
if: inputs.skip_build != 'true'
|
|
49
|
+
run: scripts/ci/android/check-readiness.sh
|
|
50
|
+
|
|
51
|
+
- name: Manage Android version
|
|
52
|
+
if: inputs.skip_build != 'true'
|
|
53
|
+
run: source scripts/ci/android/manage-version.sh
|
|
54
|
+
|
|
55
|
+
- name: Flutter pub get
|
|
56
|
+
if: inputs.skip_build != 'true'
|
|
57
|
+
run: scripts/ci/common/flutter-setup.sh
|
|
58
|
+
|
|
59
|
+
- name: Build Android
|
|
60
|
+
if: inputs.skip_build != 'true'
|
|
61
|
+
run: scripts/ci/android/build.sh
|
|
62
|
+
|
|
63
|
+
- name: Upload binary
|
|
64
|
+
if: inputs.skip_build != 'true'
|
|
65
|
+
run: scripts/ci/android/upload-binary.sh
|
|
66
|
+
|
|
67
|
+
- name: Save CI state cache
|
|
68
|
+
if: always()
|
|
69
|
+
uses: actions/cache/save@v4
|
|
70
|
+
with:
|
|
71
|
+
path: .ci-state
|
|
72
|
+
key: ci-state-android-${{ github.ref_name }}-${{ github.run_id }}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: iOS Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
skip_build:
|
|
7
|
+
description: 'Skip build (metadata/screenshots only)'
|
|
8
|
+
required: false
|
|
9
|
+
default: 'false'
|
|
10
|
+
type: boolean
|
|
11
|
+
|
|
12
|
+
env:
|
|
13
|
+
FLUTTER_VERSION: '3.x'
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
ios-release:
|
|
17
|
+
runs-on: macos-latest-xlarge
|
|
18
|
+
timeout-minutes: 60
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Restore CI state cache
|
|
23
|
+
uses: actions/cache@v4
|
|
24
|
+
with:
|
|
25
|
+
path: .ci-state
|
|
26
|
+
key: ci-state-ios-${{ github.ref_name }}
|
|
27
|
+
restore-keys: ci-state-ios-
|
|
28
|
+
|
|
29
|
+
- name: Setup Flutter
|
|
30
|
+
uses: subosito/flutter-action@v2
|
|
31
|
+
with:
|
|
32
|
+
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
33
|
+
|
|
34
|
+
# Note: No separate "Load CI config" step needed here.
|
|
35
|
+
# Each script sources read-config.sh internally.
|
|
36
|
+
|
|
37
|
+
- name: Setup iOS signing (Fastlane Match)
|
|
38
|
+
env:
|
|
39
|
+
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
|
40
|
+
run: scripts/ci/ios/setup-signing.sh
|
|
41
|
+
|
|
42
|
+
- name: Upload metadata & screenshots
|
|
43
|
+
run: scripts/ci/ios/upload-metadata.sh
|
|
44
|
+
|
|
45
|
+
- name: Sync IAP
|
|
46
|
+
run: scripts/ci/ios/sync-iap.sh
|
|
47
|
+
|
|
48
|
+
# Build & upload binary steps (added when build scripts are created)
|
|
49
|
+
# - name: Build iOS
|
|
50
|
+
# if: inputs.skip_build != 'true'
|
|
51
|
+
# run: scripts/ci/ios/build.sh
|
|
52
|
+
|
|
53
|
+
# - name: Upload binary
|
|
54
|
+
# if: inputs.skip_build != 'true'
|
|
55
|
+
# run: scripts/ci/ios/upload-binary.sh
|
|
56
|
+
|
|
57
|
+
- name: Save CI state cache
|
|
58
|
+
if: always()
|
|
59
|
+
uses: actions/cache/save@v4
|
|
60
|
+
with:
|
|
61
|
+
path: .ci-state
|
|
62
|
+
key: ci-state-ios-${{ github.ref_name }}-${{ github.run_id }}
|