@daemux/store-automator 0.10.7 → 0.10.9
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/bin/cli.mjs +65 -12
- package/package.json +1 -1
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/src/ci-config.mjs +169 -24
- package/src/guide.mjs +82 -0
- package/src/install.mjs +115 -29
- package/src/prompt.mjs +73 -62
- package/src/prompts/app-identity.mjs +66 -0
- package/src/prompts/credentials.mjs +122 -0
- package/src/prompts/store-settings.mjs +185 -0
- package/src/templates.mjs +7 -2
- package/templates/fastlane/ios/Fastfile.template +1 -0
- package/templates/github/workflows/ios-release.yml +16 -0
- package/templates/scripts/ci/android/check-readiness.sh +23 -0
- package/templates/scripts/ci/android/upload-binary.sh +27 -2
- package/templates/scripts/ci/android/upload-metadata.sh +23 -0
- package/templates/scripts/ci/ios/upload-metadata.sh +1 -27
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "App Store & Google Play automation for Flutter apps",
|
|
8
|
-
"version": "0.10.
|
|
8
|
+
"version": "0.10.9"
|
|
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.10.
|
|
15
|
+
"version": "0.10.9",
|
|
16
16
|
"keywords": [
|
|
17
17
|
"flutter",
|
|
18
18
|
"app-store",
|
package/bin/cli.mjs
CHANGED
|
@@ -23,7 +23,32 @@ const valueFlags = {
|
|
|
23
23
|
'--cloudflare-token=': 'cloudflareToken',
|
|
24
24
|
'--cloudflare-account-id=': 'cloudflareAccountId',
|
|
25
25
|
'--bundle-id=': 'bundleId',
|
|
26
|
+
'--app-name=': 'appName',
|
|
27
|
+
'--key-id=': 'keyId',
|
|
28
|
+
'--issuer-id=': 'issuerId',
|
|
29
|
+
'--keystore-password=': 'keystorePassword',
|
|
30
|
+
'--sku=': 'sku',
|
|
31
|
+
'--apple-id=': 'appleId',
|
|
32
|
+
'--primary-category=': 'primaryCategory',
|
|
33
|
+
'--secondary-category=': 'secondaryCategory',
|
|
34
|
+
'--price-tier=': 'priceTier',
|
|
35
|
+
'--submit-for-review=': 'submitForReview',
|
|
36
|
+
'--automatic-release=': 'automaticRelease',
|
|
37
|
+
'--track=': 'track',
|
|
38
|
+
'--rollout-fraction=': 'rolloutFraction',
|
|
39
|
+
'--in-app-update-priority=': 'inAppUpdatePriority',
|
|
40
|
+
'--domain=': 'domain',
|
|
41
|
+
'--cf-project-name=': 'cfProjectName',
|
|
42
|
+
'--tagline=': 'tagline',
|
|
43
|
+
'--primary-color=': 'primaryColor',
|
|
44
|
+
'--secondary-color=': 'secondaryColor',
|
|
45
|
+
'--company-name=': 'companyName',
|
|
46
|
+
'--contact-email=': 'contactEmail',
|
|
47
|
+
'--support-email=': 'supportEmail',
|
|
48
|
+
'--jurisdiction=': 'jurisdiction',
|
|
49
|
+
'--languages=': 'languages',
|
|
26
50
|
'--match-deploy-key=': 'matchDeployKey',
|
|
51
|
+
'--match-deploy-key-path=': 'matchDeployKeyPath',
|
|
27
52
|
'--match-git-url=': 'matchGitUrl',
|
|
28
53
|
};
|
|
29
54
|
|
|
@@ -60,8 +85,44 @@ Options:
|
|
|
60
85
|
-v, --version Show version number
|
|
61
86
|
-h, --help Show help
|
|
62
87
|
|
|
63
|
-
App
|
|
88
|
+
App Identity:
|
|
89
|
+
--app-name=NAME App display name
|
|
64
90
|
--bundle-id=ID Bundle ID / Package Name (e.g., com.company.app)
|
|
91
|
+
--sku=SKU App Store Connect SKU
|
|
92
|
+
--apple-id=EMAIL Apple Developer Account Email
|
|
93
|
+
|
|
94
|
+
Credentials:
|
|
95
|
+
--key-id=ID App Store Connect Key ID
|
|
96
|
+
--issuer-id=ID App Store Connect Issuer ID
|
|
97
|
+
--keystore-password=PASS Android keystore password
|
|
98
|
+
--match-deploy-key-path=PATH Path to Match deploy key
|
|
99
|
+
--match-git-url=URL Match certificates Git URL (SSH)
|
|
100
|
+
|
|
101
|
+
iOS Store Settings:
|
|
102
|
+
--primary-category=CAT iOS primary category (e.g., UTILITIES)
|
|
103
|
+
--secondary-category=CAT iOS secondary category
|
|
104
|
+
--price-tier=N iOS price tier (0 = free)
|
|
105
|
+
--submit-for-review=BOOL Auto-submit for review (true/false)
|
|
106
|
+
--automatic-release=BOOL Auto-release after approval (true/false)
|
|
107
|
+
|
|
108
|
+
Android Store Settings:
|
|
109
|
+
--track=TRACK Android release track (internal/alpha/beta/production)
|
|
110
|
+
--rollout-fraction=N Rollout fraction (0.0-1.0)
|
|
111
|
+
--in-app-update-priority=N In-app update priority (0-5)
|
|
112
|
+
|
|
113
|
+
Web Settings:
|
|
114
|
+
--domain=DOMAIN Web domain (e.g., myapp-pages.pages.dev)
|
|
115
|
+
--cf-project-name=NAME Cloudflare Pages project name
|
|
116
|
+
--tagline=TEXT App tagline
|
|
117
|
+
--primary-color=HEX Primary color (e.g., #2563EB)
|
|
118
|
+
--secondary-color=HEX Secondary color
|
|
119
|
+
--company-name=NAME Company name
|
|
120
|
+
--contact-email=EMAIL Contact email
|
|
121
|
+
--support-email=EMAIL Support email
|
|
122
|
+
--jurisdiction=TEXT Legal jurisdiction
|
|
123
|
+
|
|
124
|
+
Languages:
|
|
125
|
+
--languages=LANGS Comma-separated language codes (e.g., en-US,de-DE)
|
|
65
126
|
|
|
66
127
|
MCP Token Flags (skip interactive prompts):
|
|
67
128
|
--stitch-key=KEY Stitch MCP API key
|
|
@@ -74,17 +135,9 @@ GitHub Actions CI mode:
|
|
|
74
135
|
--match-git-url=URL Git URL for Match certificates repo
|
|
75
136
|
|
|
76
137
|
Examples:
|
|
77
|
-
npx @daemux/store-automator
|
|
78
|
-
npx @daemux/store-automator -
|
|
79
|
-
npx @daemux/store-automator -
|
|
80
|
-
npx @daemux/store-automator -g -u Uninstall globally
|
|
81
|
-
|
|
82
|
-
GitHub Actions install:
|
|
83
|
-
npx @daemux/store-automator --github-actions --bundle-id=ID --match-deploy-key=PATH --match-git-url=URL
|
|
84
|
-
|
|
85
|
-
Non-interactive install:
|
|
86
|
-
npx @daemux/store-automator --bundle-id=ID --stitch-key=KEY
|
|
87
|
-
npx @daemux/store-automator --cloudflare-token=TOKEN --cloudflare-account-id=ID`);
|
|
138
|
+
npx @daemux/store-automator
|
|
139
|
+
npx @daemux/store-automator --app-name="My App" --bundle-id=com.company.app
|
|
140
|
+
npx @daemux/store-automator --github-actions --bundle-id=ID --match-deploy-key=PATH --match-git-url=URL`);
|
|
88
141
|
process.exit(0);
|
|
89
142
|
case '-v':
|
|
90
143
|
case '--version':
|
package/package.json
CHANGED
package/src/ci-config.mjs
CHANGED
|
@@ -2,51 +2,196 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
|
|
4
4
|
const CI_CONFIG_FILE = 'ci.config.yaml';
|
|
5
|
+
|
|
5
6
|
const FIELD_PATTERNS = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
'app.name': { regex: /^( name: ).*$/m, replacement: (v) => ` name: "${v}"` },
|
|
8
|
+
'app.bundle_id': { regex: /^( bundle_id: ).*$/m, replacement: (v) => ` bundle_id: "${v}"` },
|
|
9
|
+
'app.package_name': { regex: /^( package_name: ).*$/m, replacement: (v) => ` package_name: "${v}"` },
|
|
10
|
+
'app.sku': { regex: /^( sku: ).*$/m, replacement: (v) => ` sku: "${v}"` },
|
|
11
|
+
'app.apple_id': { regex: /^( apple_id: ).*$/m, replacement: (v) => ` apple_id: "${v}"` },
|
|
12
|
+
'credentials.apple.key_id': {
|
|
13
|
+
regex: /^( key_id: ).*$/m, replacement: (v) => ` key_id: "${v}"`,
|
|
14
|
+
},
|
|
15
|
+
'credentials.apple.issuer_id': {
|
|
16
|
+
regex: /^( issuer_id: ).*$/m, replacement: (v) => ` issuer_id: "${v}"`,
|
|
17
|
+
},
|
|
18
|
+
'credentials.android.keystore_password': {
|
|
19
|
+
regex: /^( keystore_password: ).*$/m, replacement: (v) => ` keystore_password: "${v}"`,
|
|
20
|
+
},
|
|
21
|
+
'ios.primary_category': {
|
|
22
|
+
regex: /^( primary_category: ).*$/m, replacement: (v) => ` primary_category: "${v}"`,
|
|
23
|
+
},
|
|
24
|
+
'ios.secondary_category': {
|
|
25
|
+
regex: /^( secondary_category: ).*$/m, replacement: (v) => ` secondary_category: "${v}"`,
|
|
26
|
+
},
|
|
27
|
+
'ios.price_tier': { regex: /^( price_tier: ).*$/m, replacement: (v) => ` price_tier: ${v}` },
|
|
28
|
+
'ios.submit_for_review': {
|
|
29
|
+
regex: /^( submit_for_review: ).*$/m, replacement: (v) => ` submit_for_review: ${v}`,
|
|
30
|
+
},
|
|
31
|
+
'ios.automatic_release': {
|
|
32
|
+
regex: /^( automatic_release: ).*$/m, replacement: (v) => ` automatic_release: ${v}`,
|
|
33
|
+
},
|
|
34
|
+
'android.track': { regex: /^( track: ).*$/m, replacement: (v) => ` track: "${v}"` },
|
|
35
|
+
'android.rollout_fraction': {
|
|
36
|
+
regex: /^( rollout_fraction: ).*$/m, replacement: (v) => ` rollout_fraction: "${v}"`,
|
|
37
|
+
},
|
|
38
|
+
'android.in_app_update_priority': {
|
|
39
|
+
regex: /^( in_app_update_priority: ).*$/m, replacement: (v) => ` in_app_update_priority: ${v}`,
|
|
40
|
+
},
|
|
41
|
+
'web.domain': { regex: /^( domain: ).*$/m, replacement: (v) => ` domain: "${v}"` },
|
|
42
|
+
'web.cloudflare_project_name': {
|
|
43
|
+
regex: /^( cloudflare_project_name: ).*$/m, replacement: (v) => ` cloudflare_project_name: "${v}"`,
|
|
44
|
+
},
|
|
45
|
+
'web.tagline': { regex: /^( tagline: ).*$/m, replacement: (v) => ` tagline: "${v}"` },
|
|
46
|
+
'web.primary_color': {
|
|
47
|
+
regex: /^( primary_color: ).*$/m, replacement: (v) => ` primary_color: "${v}"`,
|
|
48
|
+
},
|
|
49
|
+
'web.secondary_color': {
|
|
50
|
+
regex: /^( secondary_color: ).*$/m, replacement: (v) => ` secondary_color: "${v}"`,
|
|
51
|
+
},
|
|
52
|
+
'web.company_name': {
|
|
53
|
+
regex: /^( company_name: ).*$/m, replacement: (v) => ` company_name: "${v}"`,
|
|
54
|
+
},
|
|
55
|
+
'web.contact_email': {
|
|
56
|
+
regex: /^( contact_email: ).*$/m, replacement: (v) => ` contact_email: "${v}"`,
|
|
57
|
+
},
|
|
58
|
+
'web.support_email': {
|
|
59
|
+
regex: /^( support_email: ).*$/m, replacement: (v) => ` support_email: "${v}"`,
|
|
60
|
+
},
|
|
61
|
+
'web.jurisdiction': {
|
|
62
|
+
regex: /^( jurisdiction: ).*$/m, replacement: (v) => ` jurisdiction: "${v}"`,
|
|
63
|
+
},
|
|
64
|
+
'web.app_store_url': {
|
|
65
|
+
regex: /^( app_store_url: ).*$/m, replacement: (v) => ` app_store_url: "${v}"`,
|
|
66
|
+
},
|
|
67
|
+
'web.google_play_url': {
|
|
68
|
+
regex: /^( google_play_url: ).*$/m, replacement: (v) => ` google_play_url: "${v}"`,
|
|
69
|
+
},
|
|
10
70
|
};
|
|
11
71
|
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
if (!
|
|
72
|
+
function extractFieldValue(content, regex) {
|
|
73
|
+
const match = content.match(regex);
|
|
74
|
+
if (!match) return undefined;
|
|
75
|
+
const line = match[0];
|
|
76
|
+
const colonIdx = line.indexOf(':');
|
|
77
|
+
if (colonIdx < 0) return undefined;
|
|
78
|
+
const raw = line.slice(colonIdx + 1).trim();
|
|
79
|
+
if (raw.length >= 2 && raw[0] === '"' && raw[raw.length - 1] === '"') return raw.slice(1, -1);
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
15
82
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
83
|
+
export function isPlaceholder(value) {
|
|
84
|
+
if (value === undefined || value === null || value === '') return true;
|
|
85
|
+
const s = String(value);
|
|
86
|
+
if (s.startsWith('REPLACE_WITH_')) return true;
|
|
87
|
+
if (s.startsWith('yourapp')) return true;
|
|
88
|
+
if (s.startsWith('com.yourcompany.')) return true;
|
|
89
|
+
if (s === 'your@email.com') return true;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
20
92
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
93
|
+
export function readCiConfig(projectDir) {
|
|
94
|
+
const configPath = join(projectDir, CI_CONFIG_FILE);
|
|
95
|
+
if (!existsSync(configPath)) return {};
|
|
96
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
97
|
+
const config = {};
|
|
98
|
+
for (const [key, { regex }] of Object.entries(FIELD_PATTERNS)) {
|
|
99
|
+
const val = extractFieldValue(content, regex);
|
|
100
|
+
if (val !== undefined) config[key] = val;
|
|
101
|
+
}
|
|
102
|
+
config['metadata.languages'] = extractLanguages(content);
|
|
103
|
+
return config;
|
|
104
|
+
}
|
|
24
105
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
106
|
+
function extractLanguages(content) {
|
|
107
|
+
const lines = content.split('\n');
|
|
108
|
+
const langIdx = lines.findIndex((l) => /^\s*languages:\s*$/.test(l));
|
|
109
|
+
if (langIdx < 0) return [];
|
|
110
|
+
const langs = [];
|
|
111
|
+
for (let i = langIdx + 1; i < lines.length; i++) {
|
|
112
|
+
const match = lines[i].match(/^\s+-\s+(.+)$/);
|
|
113
|
+
if (!match) break;
|
|
114
|
+
langs.push(match[1].trim());
|
|
115
|
+
}
|
|
116
|
+
return langs;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function writeCiFields(projectDir, fields) {
|
|
120
|
+
const configPath = join(projectDir, CI_CONFIG_FILE);
|
|
121
|
+
if (!existsSync(configPath)) return false;
|
|
122
|
+
let content = readFileSync(configPath, 'utf-8');
|
|
123
|
+
let changed = false;
|
|
124
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
125
|
+
if (value === undefined || value === null) continue;
|
|
126
|
+
const pattern = FIELD_PATTERNS[key];
|
|
127
|
+
if (!pattern) continue;
|
|
128
|
+
const safeValue = String(value).replace(/\$/g, '$$$$');
|
|
129
|
+
const updated = content.replace(pattern.regex, pattern.replacement(safeValue));
|
|
130
|
+
if (updated !== content) {
|
|
131
|
+
content = updated;
|
|
132
|
+
changed = true;
|
|
133
|
+
}
|
|
29
134
|
}
|
|
135
|
+
if (changed) writeFileSync(configPath, content, 'utf-8');
|
|
136
|
+
return changed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function writeCiLanguages(projectDir, languagesStr) {
|
|
140
|
+
const configPath = join(projectDir, CI_CONFIG_FILE);
|
|
141
|
+
if (!existsSync(configPath)) return false;
|
|
142
|
+
const langs = languagesStr
|
|
143
|
+
.split(',')
|
|
144
|
+
.map((s) => s.trim())
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
if (langs.length === 0) return false;
|
|
147
|
+
|
|
148
|
+
const yamlLines = langs.map((l) => ` - ${l}`).join('\n');
|
|
149
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
150
|
+
const updated = content.replace(
|
|
151
|
+
/^( languages:\s*)\n(?:\s+-\s+.+\n?)*/m,
|
|
152
|
+
`$1\n${yamlLines}\n`
|
|
153
|
+
);
|
|
154
|
+
if (updated === content) return false;
|
|
155
|
+
writeFileSync(configPath, updated, 'utf-8');
|
|
156
|
+
return true;
|
|
30
157
|
}
|
|
31
158
|
|
|
32
159
|
export function writeCiBundleId(projectDir, bundleId) {
|
|
33
|
-
return
|
|
160
|
+
return writeCiFields(projectDir, {
|
|
161
|
+
'app.bundle_id': bundleId,
|
|
162
|
+
});
|
|
34
163
|
}
|
|
35
164
|
|
|
36
165
|
export function writeCiPackageName(projectDir, packageName) {
|
|
37
|
-
return
|
|
166
|
+
return writeCiFields(projectDir, {
|
|
167
|
+
'app.package_name': packageName,
|
|
168
|
+
});
|
|
38
169
|
}
|
|
39
170
|
|
|
40
171
|
export function writeMatchConfig(projectDir, { deployKeyPath, gitUrl }) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
172
|
+
const configPath = join(projectDir, CI_CONFIG_FILE);
|
|
173
|
+
if (!existsSync(configPath)) return false;
|
|
174
|
+
let content = readFileSync(configPath, 'utf-8');
|
|
175
|
+
let changed = false;
|
|
176
|
+
|
|
177
|
+
const dpRegex = /^(\s*deploy_key_path:\s*)"[^"]*"/m;
|
|
178
|
+
const guRegex = /^(\s*git_url:\s*)"[^"]*"/m;
|
|
179
|
+
|
|
180
|
+
if (deployKeyPath && dpRegex.test(content)) {
|
|
181
|
+
content = content.replace(dpRegex, `$1"${deployKeyPath}"`);
|
|
182
|
+
changed = true;
|
|
183
|
+
}
|
|
184
|
+
if (gitUrl && guRegex.test(content)) {
|
|
185
|
+
content = content.replace(guRegex, `$1"${gitUrl}"`);
|
|
186
|
+
changed = true;
|
|
187
|
+
}
|
|
188
|
+
if (changed) writeFileSync(configPath, content, 'utf-8');
|
|
189
|
+
return changed;
|
|
44
190
|
}
|
|
45
191
|
|
|
46
192
|
export function readFlutterRoot(projectDir) {
|
|
47
193
|
const configPath = join(projectDir, CI_CONFIG_FILE);
|
|
48
194
|
if (!existsSync(configPath)) return '.';
|
|
49
|
-
|
|
50
195
|
try {
|
|
51
196
|
const content = readFileSync(configPath, 'utf8');
|
|
52
197
|
const match = content.match(/^flutter_root:\s*"([^"]*)"/m);
|
package/src/guide.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const BOLD = '\x1b[1m';
|
|
5
|
+
const CYAN = '\x1b[36m';
|
|
6
|
+
const YELLOW = '\x1b[33m';
|
|
7
|
+
const GREEN = '\x1b[32m';
|
|
8
|
+
const RESET = '\x1b[0m';
|
|
9
|
+
|
|
10
|
+
function isNonInteractive() {
|
|
11
|
+
return Boolean(process.env.npm_config_yes) || process.argv.includes('--postinstall');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function printGuide(title, steps) {
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(`${BOLD}${CYAN}=== ${title} ===${RESET}`);
|
|
17
|
+
console.log('');
|
|
18
|
+
for (let i = 0; i < steps.length; i++) {
|
|
19
|
+
console.log(` ${i + 1}. ${steps[i]}`);
|
|
20
|
+
}
|
|
21
|
+
console.log('');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function askConfirmation(rl, question) {
|
|
25
|
+
if (isNonInteractive()) return Promise.resolve('skip');
|
|
26
|
+
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
rl.question(`${question} (y/n/skip): `, (answer) => {
|
|
29
|
+
const val = answer.trim().toLowerCase();
|
|
30
|
+
if (val === 'y' || val === 'yes') return resolve('yes');
|
|
31
|
+
if (val === 's' || val === 'skip') return resolve('skip');
|
|
32
|
+
resolve('no');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function verifyFileExists(filePath, description) {
|
|
38
|
+
if (existsSync(filePath)) {
|
|
39
|
+
console.log(`${GREEN} Found: ${description} (${filePath})${RESET}`);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
console.log(`${YELLOW} Warning: ${description} not found at ${filePath}${RESET}`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function runGuide(rl, guide) {
|
|
47
|
+
printGuide(guide.title, guide.steps);
|
|
48
|
+
|
|
49
|
+
if (guide.verifyPath) {
|
|
50
|
+
verifyFileExists(guide.verifyPath, guide.verifyDescription || guide.verifyPath);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (guide.confirmQuestion) {
|
|
54
|
+
const answer = await askConfirmation(rl, guide.confirmQuestion);
|
|
55
|
+
if (answer === 'no') {
|
|
56
|
+
console.log(`${YELLOW} Skipped. You can complete this step later.${RESET}`);
|
|
57
|
+
}
|
|
58
|
+
return answer;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return 'yes';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function askQuestion(rl, question, defaultValue) {
|
|
65
|
+
if (isNonInteractive()) return Promise.resolve(defaultValue || '');
|
|
66
|
+
|
|
67
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
70
|
+
resolve(answer.trim() || defaultValue || '');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isPlaceholder(value) {
|
|
76
|
+
if (value === undefined || value === null || value === '') return true;
|
|
77
|
+
const s = String(value);
|
|
78
|
+
if (s.startsWith('REPLACE_WITH_')) return true;
|
|
79
|
+
if (s.startsWith('yourapp')) return true;
|
|
80
|
+
if (s.startsWith('com.yourcompany.')) return true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
package/src/install.mjs
CHANGED
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
getPackageDir, exec, ensureDir, ensureFile, readJson, writeJson,
|
|
9
9
|
} from './utils.mjs';
|
|
10
10
|
import { injectEnvVars, injectStatusLine } from './settings.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { promptAll } from './prompt.mjs';
|
|
12
12
|
import { getMcpServers, writeMcpJson } from './mcp-setup.mjs';
|
|
13
13
|
import { installClaudeMd, installCiTemplates, installFirebaseTemplates } from './templates.mjs';
|
|
14
|
-
import {
|
|
14
|
+
import { readCiConfig, writeCiFields, writeCiLanguages, isPlaceholder } from './ci-config.mjs';
|
|
15
15
|
import { installGitHubActionsPath } from './install-paths.mjs';
|
|
16
16
|
|
|
17
17
|
function checkClaudeCli() {
|
|
@@ -94,13 +94,66 @@ function printSummary(scope, oldVersion, newVersion) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
function
|
|
97
|
+
function mapPromptsToCiFields(prompted) {
|
|
98
|
+
return {
|
|
99
|
+
'app.name': prompted.appName,
|
|
100
|
+
'app.bundle_id': prompted.bundleId,
|
|
101
|
+
'app.package_name': prompted.packageName,
|
|
102
|
+
'app.sku': prompted.sku,
|
|
103
|
+
'app.apple_id': prompted.appleId,
|
|
104
|
+
'credentials.apple.key_id': prompted.keyId,
|
|
105
|
+
'credentials.apple.issuer_id': prompted.issuerId,
|
|
106
|
+
'credentials.android.keystore_password': prompted.keystorePassword,
|
|
107
|
+
'ios.primary_category': prompted.primaryCategory,
|
|
108
|
+
'ios.secondary_category': prompted.secondaryCategory,
|
|
109
|
+
'ios.price_tier': prompted.priceTier,
|
|
110
|
+
'ios.submit_for_review': prompted.submitForReview,
|
|
111
|
+
'ios.automatic_release': prompted.automaticRelease,
|
|
112
|
+
'android.track': prompted.track,
|
|
113
|
+
'android.rollout_fraction': prompted.rolloutFraction,
|
|
114
|
+
'android.in_app_update_priority': prompted.inAppUpdatePriority,
|
|
115
|
+
'web.domain': prompted.domain,
|
|
116
|
+
'web.cloudflare_project_name': prompted.cfProjectName,
|
|
117
|
+
'web.tagline': prompted.tagline,
|
|
118
|
+
'web.primary_color': prompted.primaryColor,
|
|
119
|
+
'web.secondary_color': prompted.secondaryColor,
|
|
120
|
+
'web.company_name': prompted.companyName,
|
|
121
|
+
'web.contact_email': prompted.contactEmail,
|
|
122
|
+
'web.support_email': prompted.supportEmail,
|
|
123
|
+
'web.jurisdiction': prompted.jurisdiction,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function printNextSteps(prompted) {
|
|
128
|
+
const missing = [];
|
|
129
|
+
|
|
130
|
+
if (isPlaceholder(prompted.bundleId)) {
|
|
131
|
+
missing.push('Set bundle ID in ci.config.yaml');
|
|
132
|
+
}
|
|
133
|
+
if (isPlaceholder(prompted.keyId)) {
|
|
134
|
+
missing.push('Add App Store Connect credentials (key_id, issuer_id, AuthKey.p8)');
|
|
135
|
+
}
|
|
136
|
+
if (!existsSync(join(process.cwd(), 'creds', 'play-service-account.json'))) {
|
|
137
|
+
missing.push('Add creds/play-service-account.json for Google Play');
|
|
138
|
+
}
|
|
139
|
+
if (isPlaceholder(prompted.matchGitUrl)) {
|
|
140
|
+
missing.push('Configure Match code signing (match_git_url, deploy key)');
|
|
141
|
+
}
|
|
142
|
+
|
|
98
143
|
console.log('');
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
144
|
+
if (missing.length === 0) {
|
|
145
|
+
console.log('All configuration complete! Start Claude Code.');
|
|
146
|
+
} else {
|
|
147
|
+
console.log('Next steps:');
|
|
148
|
+
for (let i = 0; i < missing.length; i++) {
|
|
149
|
+
console.log(` ${i + 1}. ${missing[i]}`);
|
|
150
|
+
}
|
|
151
|
+
console.log(` ${missing.length + 1}. Start Claude Code`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isNonInteractive() {
|
|
156
|
+
return Boolean(process.env.npm_config_yes) || process.argv.includes('--postinstall');
|
|
104
157
|
}
|
|
105
158
|
|
|
106
159
|
export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
@@ -109,21 +162,11 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
109
162
|
console.log('Installing/updating Daemux Store Automator...');
|
|
110
163
|
|
|
111
164
|
const isGitHubActions = Boolean(cliTokens.githubActions);
|
|
112
|
-
|
|
113
|
-
const tokens = isGitHubActions
|
|
114
|
-
? { bundleId: cliTokens.bundleId ?? '' }
|
|
115
|
-
: await promptForTokens(cliTokens);
|
|
116
|
-
|
|
117
165
|
const projectDir = process.cwd();
|
|
118
|
-
|
|
119
|
-
if (!isGitHubActions) {
|
|
120
|
-
const servers = getMcpServers(tokens);
|
|
121
|
-
writeMcpJson(projectDir, servers);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
166
|
const oldVersion = readMarketplaceVersion();
|
|
125
167
|
const packageDir = getPackageDir();
|
|
126
168
|
|
|
169
|
+
// 1. Copy plugin files + register marketplace
|
|
127
170
|
copyPluginFiles(packageDir);
|
|
128
171
|
clearCache();
|
|
129
172
|
registerMarketplace();
|
|
@@ -131,21 +174,51 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
131
174
|
|
|
132
175
|
const newVersion = readMarketplaceVersion('unknown');
|
|
133
176
|
|
|
177
|
+
// 2. Install CI templates (creates ci.config.yaml if missing)
|
|
178
|
+
installCiTemplates(projectDir, packageDir);
|
|
179
|
+
installFirebaseTemplates(projectDir, packageDir);
|
|
180
|
+
|
|
181
|
+
// 3. Read current ci.config.yaml values
|
|
182
|
+
const currentConfig = readCiConfig(projectDir);
|
|
183
|
+
|
|
184
|
+
// 4. Run interactive prompts (or use CLI flags / skip in non-interactive)
|
|
185
|
+
let prompted;
|
|
186
|
+
if (isGitHubActions) {
|
|
187
|
+
prompted = { bundleId: cliTokens.bundleId ?? '' };
|
|
188
|
+
} else if (isNonInteractive()) {
|
|
189
|
+
prompted = { ...cliTokens };
|
|
190
|
+
} else {
|
|
191
|
+
prompted = await promptAll(cliTokens, currentConfig, projectDir);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 5. Write all prompted values to ci.config.yaml
|
|
195
|
+
const ciFields = mapPromptsToCiFields(prompted);
|
|
196
|
+
const wrote = writeCiFields(projectDir, ciFields);
|
|
197
|
+
if (wrote) console.log('Configuration written to ci.config.yaml');
|
|
198
|
+
|
|
199
|
+
// 6. Handle languages separately
|
|
200
|
+
if (prompted.languages) {
|
|
201
|
+
const langStr = Array.isArray(prompted.languages)
|
|
202
|
+
? prompted.languages.join(',')
|
|
203
|
+
: prompted.languages;
|
|
204
|
+
if (writeCiLanguages(projectDir, langStr)) {
|
|
205
|
+
console.log('Languages updated in ci.config.yaml');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 7. Configure MCP, CLAUDE.md, settings
|
|
210
|
+
if (!isGitHubActions) {
|
|
211
|
+
const servers = getMcpServers(prompted);
|
|
212
|
+
writeMcpJson(projectDir, servers);
|
|
213
|
+
}
|
|
214
|
+
|
|
134
215
|
const baseDir = scope === 'user'
|
|
135
216
|
? join(homedir(), '.claude')
|
|
136
217
|
: join(process.cwd(), '.claude');
|
|
137
218
|
|
|
138
219
|
ensureDir(baseDir);
|
|
139
220
|
|
|
140
|
-
installClaudeMd(join(baseDir, 'CLAUDE.md'), packageDir);
|
|
141
|
-
installCiTemplates(projectDir, packageDir);
|
|
142
|
-
installFirebaseTemplates(projectDir, packageDir);
|
|
143
|
-
|
|
144
|
-
if (tokens.bundleId) {
|
|
145
|
-
const written = writeCiBundleId(projectDir, tokens.bundleId);
|
|
146
|
-
if (written) console.log(`Bundle ID set in ci.config.yaml: ${tokens.bundleId}`);
|
|
147
|
-
writeCiPackageName(projectDir, tokens.bundleId);
|
|
148
|
-
}
|
|
221
|
+
installClaudeMd(join(baseDir, 'CLAUDE.md'), packageDir, prompted.appName);
|
|
149
222
|
|
|
150
223
|
installGitHubActionsPath(projectDir, packageDir, cliTokens);
|
|
151
224
|
|
|
@@ -155,6 +228,19 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
155
228
|
injectEnvVars(settingsPath);
|
|
156
229
|
injectStatusLine(settingsPath);
|
|
157
230
|
|
|
231
|
+
// 8. Run post-install guides (interactive only)
|
|
232
|
+
if (!isGitHubActions && !isNonInteractive()) {
|
|
233
|
+
const { createInterface } = await import('node:readline');
|
|
234
|
+
const { runPostInstallGuides } = await import('./prompts/store-settings.mjs');
|
|
235
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
236
|
+
try {
|
|
237
|
+
await runPostInstallGuides(rl);
|
|
238
|
+
} finally {
|
|
239
|
+
rl.close();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 9. Summary + dynamic next steps
|
|
158
244
|
printSummary(scope, oldVersion, newVersion);
|
|
159
|
-
printNextSteps();
|
|
245
|
+
printNextSteps(prompted);
|
|
160
246
|
}
|