@daemux/store-automator 0.10.10 → 0.10.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/package.json +1 -1
- package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
- package/src/ci-config.mjs +47 -14
- package/src/guide.mjs +24 -5
- package/src/install-paths.mjs +1 -1
- package/src/install.mjs +29 -14
- package/src/prompt.mjs +15 -77
- package/src/prompts/app-identity.mjs +14 -45
- package/src/prompts/credentials.mjs +51 -59
- package/src/prompts/store-settings.mjs +62 -54
|
@@ -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.11"
|
|
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.11",
|
|
16
16
|
"keywords": [
|
|
17
17
|
"flutter",
|
|
18
18
|
"app-store",
|
package/package.json
CHANGED
package/src/ci-config.mjs
CHANGED
|
@@ -3,6 +3,35 @@ import { join } from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
const CI_CONFIG_FILE = 'ci.config.yaml';
|
|
5
5
|
|
|
6
|
+
const KEY_TO_CAMEL = {
|
|
7
|
+
'app.name': 'appName',
|
|
8
|
+
'app.bundle_id': 'bundleId',
|
|
9
|
+
'app.package_name': 'packageName',
|
|
10
|
+
'app.sku': 'sku',
|
|
11
|
+
'app.apple_id': 'appleId',
|
|
12
|
+
'credentials.apple.key_id': 'keyId',
|
|
13
|
+
'credentials.apple.issuer_id': 'issuerId',
|
|
14
|
+
'credentials.android.keystore_password': 'keystorePassword',
|
|
15
|
+
'ios.primary_category': 'primaryCategory',
|
|
16
|
+
'ios.secondary_category': 'secondaryCategory',
|
|
17
|
+
'ios.price_tier': 'priceTier',
|
|
18
|
+
'ios.submit_for_review': 'submitForReview',
|
|
19
|
+
'ios.automatic_release': 'automaticRelease',
|
|
20
|
+
'android.track': 'track',
|
|
21
|
+
'android.rollout_fraction': 'rolloutFraction',
|
|
22
|
+
'android.in_app_update_priority': 'inAppUpdatePriority',
|
|
23
|
+
'web.domain': 'domain',
|
|
24
|
+
'web.cloudflare_project_name': 'cfProjectName',
|
|
25
|
+
'web.tagline': 'tagline',
|
|
26
|
+
'web.primary_color': 'primaryColor',
|
|
27
|
+
'web.secondary_color': 'secondaryColor',
|
|
28
|
+
'web.company_name': 'companyName',
|
|
29
|
+
'web.contact_email': 'contactEmail',
|
|
30
|
+
'web.support_email': 'supportEmail',
|
|
31
|
+
'web.jurisdiction': 'jurisdiction',
|
|
32
|
+
'metadata.languages': 'languages',
|
|
33
|
+
};
|
|
34
|
+
|
|
6
35
|
const FIELD_PATTERNS = {
|
|
7
36
|
'app.name': { regex: /^( name: ).*$/m, replacement: (v) => ` name: "${v}"` },
|
|
8
37
|
'app.bundle_id': { regex: /^( bundle_id: ).*$/m, replacement: (v) => ` bundle_id: "${v}"` },
|
|
@@ -80,26 +109,28 @@ function extractFieldValue(content, regex) {
|
|
|
80
109
|
return raw;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
|
-
export
|
|
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
|
-
}
|
|
112
|
+
export { isPlaceholder } from './guide.mjs';
|
|
92
113
|
|
|
93
114
|
export function readCiConfig(projectDir) {
|
|
94
115
|
const configPath = join(projectDir, CI_CONFIG_FILE);
|
|
95
116
|
if (!existsSync(configPath)) return {};
|
|
96
117
|
const content = readFileSync(configPath, 'utf-8');
|
|
97
|
-
const
|
|
118
|
+
const raw = {};
|
|
98
119
|
for (const [key, { regex }] of Object.entries(FIELD_PATTERNS)) {
|
|
99
120
|
const val = extractFieldValue(content, regex);
|
|
100
|
-
if (val !== undefined)
|
|
121
|
+
if (val !== undefined) raw[key] = val;
|
|
122
|
+
}
|
|
123
|
+
raw['metadata.languages'] = extractLanguages(content);
|
|
124
|
+
|
|
125
|
+
const config = {};
|
|
126
|
+
for (const [dotKey, value] of Object.entries(raw)) {
|
|
127
|
+
const camelKey = KEY_TO_CAMEL[dotKey];
|
|
128
|
+
if (camelKey) {
|
|
129
|
+
config[camelKey] = value;
|
|
130
|
+
} else {
|
|
131
|
+
config[dotKey] = value;
|
|
132
|
+
}
|
|
101
133
|
}
|
|
102
|
-
config['metadata.languages'] = extractLanguages(content);
|
|
103
134
|
return config;
|
|
104
135
|
}
|
|
105
136
|
|
|
@@ -178,11 +209,13 @@ export function writeMatchConfig(projectDir, { deployKeyPath, gitUrl }) {
|
|
|
178
209
|
const guRegex = /^(\s*git_url:\s*)"[^"]*"/m;
|
|
179
210
|
|
|
180
211
|
if (deployKeyPath && dpRegex.test(content)) {
|
|
181
|
-
|
|
212
|
+
const safe = deployKeyPath.replace(/\$/g, '$$$$');
|
|
213
|
+
content = content.replace(dpRegex, `$1"${safe}"`);
|
|
182
214
|
changed = true;
|
|
183
215
|
}
|
|
184
216
|
if (gitUrl && guRegex.test(content)) {
|
|
185
|
-
|
|
217
|
+
const safe = gitUrl.replace(/\$/g, '$$$$');
|
|
218
|
+
content = content.replace(guRegex, `$1"${safe}"`);
|
|
186
219
|
changed = true;
|
|
187
220
|
}
|
|
188
221
|
if (changed) writeFileSync(configPath, content, 'utf-8');
|
package/src/guide.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createInterface } from 'node:readline';
|
|
2
1
|
import { existsSync } from 'node:fs';
|
|
3
2
|
|
|
4
3
|
const BOLD = '\x1b[1m';
|
|
@@ -7,14 +6,18 @@ const YELLOW = '\x1b[33m';
|
|
|
7
6
|
const GREEN = '\x1b[32m';
|
|
8
7
|
const RESET = '\x1b[0m';
|
|
9
8
|
|
|
10
|
-
function isNonInteractive() {
|
|
9
|
+
export function isNonInteractive() {
|
|
11
10
|
return Boolean(process.env.npm_config_yes) || process.argv.includes('--postinstall');
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
export function
|
|
13
|
+
export function printSectionHeader(title) {
|
|
15
14
|
console.log('');
|
|
16
15
|
console.log(`${BOLD}${CYAN}=== ${title} ===${RESET}`);
|
|
17
16
|
console.log('');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function printGuide(title, steps) {
|
|
20
|
+
printSectionHeader(title);
|
|
18
21
|
for (let i = 0; i < steps.length; i++) {
|
|
19
22
|
console.log(` ${i + 1}. ${steps[i]}`);
|
|
20
23
|
}
|
|
@@ -43,7 +46,9 @@ export function verifyFileExists(filePath, description) {
|
|
|
43
46
|
return false;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
export async function runGuide(rl, guide) {
|
|
49
|
+
export async function runGuide(rl, guide, { skip = false } = {}) {
|
|
50
|
+
if (skip || isNonInteractive()) return 'skip';
|
|
51
|
+
|
|
47
52
|
printGuide(guide.title, guide.steps);
|
|
48
53
|
|
|
49
54
|
if (guide.verifyPath) {
|
|
@@ -61,8 +66,9 @@ export async function runGuide(rl, guide) {
|
|
|
61
66
|
return 'yes';
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
export function askQuestion(rl, question, defaultValue) {
|
|
69
|
+
export function askQuestion(rl, question, defaultValue, { skipIfFilled = false } = {}) {
|
|
65
70
|
if (isNonInteractive()) return Promise.resolve(defaultValue || '');
|
|
71
|
+
if (skipIfFilled && defaultValue) return Promise.resolve(defaultValue);
|
|
66
72
|
|
|
67
73
|
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
68
74
|
return new Promise((resolve) => {
|
|
@@ -72,11 +78,24 @@ export function askQuestion(rl, question, defaultValue) {
|
|
|
72
78
|
});
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
export function getDefault(key, cliFlags, currentConfig) {
|
|
82
|
+
if (cliFlags[key] !== undefined) return cliFlags[key];
|
|
83
|
+
const configVal = currentConfig[key];
|
|
84
|
+
if (!isPlaceholder(configVal)) return configVal;
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getDefaultWithFallback(key, cliFlags, currentConfig, fallback) {
|
|
89
|
+
const val = getDefault(key, cliFlags, currentConfig);
|
|
90
|
+
return val !== '' ? val : fallback;
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
export function isPlaceholder(value) {
|
|
76
94
|
if (value === undefined || value === null || value === '') return true;
|
|
77
95
|
const s = String(value);
|
|
78
96
|
if (s.startsWith('REPLACE_WITH_')) return true;
|
|
79
97
|
if (s.startsWith('yourapp')) return true;
|
|
80
98
|
if (s.startsWith('com.yourcompany.')) return true;
|
|
99
|
+
if (s === 'your@email.com') return true;
|
|
81
100
|
return false;
|
|
82
101
|
}
|
package/src/install-paths.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function installGitHubActionsPath(projectDir, packageDir, cliTokens) {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const wrote = writeMatchConfig(projectDir, {
|
|
14
|
-
deployKeyPath: cliTokens.
|
|
14
|
+
deployKeyPath: cliTokens.matchDeployKeyPath,
|
|
15
15
|
gitUrl: cliTokens.matchGitUrl,
|
|
16
16
|
});
|
|
17
17
|
if (wrote) console.log('Match credentials written to ci.config.yaml');
|
package/src/install.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { injectEnvVars, injectStatusLine } from './settings.mjs';
|
|
|
11
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 { readCiConfig, writeCiFields, writeCiLanguages, isPlaceholder } from './ci-config.mjs';
|
|
14
|
+
import { readCiConfig, writeCiFields, writeCiLanguages, writeMatchConfig, isPlaceholder } from './ci-config.mjs';
|
|
15
15
|
import { installGitHubActionsPath } from './install-paths.mjs';
|
|
16
16
|
|
|
17
17
|
function checkClaudeCli() {
|
|
@@ -152,16 +152,13 @@ function printNextSteps(prompted) {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
function isNonInteractive() {
|
|
156
|
-
return Boolean(process.env.npm_config_yes) || process.argv.includes('--postinstall');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
155
|
export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
160
156
|
checkClaudeCli();
|
|
161
157
|
|
|
162
158
|
console.log('Installing/updating Daemux Store Automator...');
|
|
163
159
|
|
|
164
160
|
const isGitHubActions = Boolean(cliTokens.githubActions);
|
|
161
|
+
const nonInteractive = Boolean(process.env.npm_config_yes) || process.argv.includes('--postinstall');
|
|
165
162
|
const projectDir = process.cwd();
|
|
166
163
|
const oldVersion = readMarketplaceVersion();
|
|
167
164
|
const packageDir = getPackageDir();
|
|
@@ -184,11 +181,21 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
184
181
|
// 4. Run interactive prompts (or use CLI flags / skip in non-interactive)
|
|
185
182
|
let prompted;
|
|
186
183
|
if (isGitHubActions) {
|
|
187
|
-
prompted = {
|
|
188
|
-
|
|
184
|
+
prompted = {
|
|
185
|
+
bundleId: cliTokens.bundleId ?? '',
|
|
186
|
+
matchDeployKeyPath: cliTokens.matchDeployKey,
|
|
187
|
+
matchGitUrl: cliTokens.matchGitUrl,
|
|
188
|
+
};
|
|
189
|
+
} else if (nonInteractive) {
|
|
189
190
|
prompted = { ...cliTokens };
|
|
190
191
|
} else {
|
|
191
|
-
|
|
192
|
+
const { createInterface } = await import('node:readline');
|
|
193
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
194
|
+
try {
|
|
195
|
+
prompted = await promptAll(rl, cliTokens, currentConfig, projectDir);
|
|
196
|
+
} finally {
|
|
197
|
+
rl.close();
|
|
198
|
+
}
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
// 5. Write all prompted values to ci.config.yaml
|
|
@@ -196,6 +203,14 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
196
203
|
const wrote = writeCiFields(projectDir, ciFields);
|
|
197
204
|
if (wrote) console.log('Configuration written to ci.config.yaml');
|
|
198
205
|
|
|
206
|
+
if (prompted.matchDeployKeyPath || prompted.matchGitUrl) {
|
|
207
|
+
const wroteMatch = writeMatchConfig(projectDir, {
|
|
208
|
+
deployKeyPath: prompted.matchDeployKeyPath,
|
|
209
|
+
gitUrl: prompted.matchGitUrl,
|
|
210
|
+
});
|
|
211
|
+
if (wroteMatch) console.log('Match credentials written to ci.config.yaml');
|
|
212
|
+
}
|
|
213
|
+
|
|
199
214
|
// 6. Handle languages separately
|
|
200
215
|
if (prompted.languages) {
|
|
201
216
|
const langStr = Array.isArray(prompted.languages)
|
|
@@ -220,7 +235,7 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
220
235
|
|
|
221
236
|
installClaudeMd(join(baseDir, 'CLAUDE.md'), packageDir, prompted.appName);
|
|
222
237
|
|
|
223
|
-
installGitHubActionsPath(projectDir, packageDir,
|
|
238
|
+
installGitHubActionsPath(projectDir, packageDir, prompted);
|
|
224
239
|
|
|
225
240
|
const scopeLabel = scope === 'user' ? 'global' : 'project';
|
|
226
241
|
console.log(`Configuring ${scopeLabel} settings...`);
|
|
@@ -229,14 +244,14 @@ export async function runInstall(scope, isPostinstall = false, cliTokens = {}) {
|
|
|
229
244
|
injectStatusLine(settingsPath);
|
|
230
245
|
|
|
231
246
|
// 8. Run post-install guides (interactive only)
|
|
232
|
-
if (!isGitHubActions && !
|
|
247
|
+
if (!isGitHubActions && !nonInteractive) {
|
|
233
248
|
const { createInterface } = await import('node:readline');
|
|
234
|
-
const {
|
|
235
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
249
|
+
const guideRl = createInterface({ input: process.stdin, output: process.stdout });
|
|
236
250
|
try {
|
|
237
|
-
await
|
|
251
|
+
const { runPostInstallGuides } = await import('./prompts/store-settings.mjs');
|
|
252
|
+
await runPostInstallGuides(guideRl, currentConfig);
|
|
238
253
|
} finally {
|
|
239
|
-
|
|
254
|
+
guideRl.close();
|
|
240
255
|
}
|
|
241
256
|
}
|
|
242
257
|
|
package/src/prompt.mjs
CHANGED
|
@@ -1,53 +1,36 @@
|
|
|
1
|
-
import { createInterface } from 'node:readline';
|
|
2
1
|
import { promptAppIdentity } from './prompts/app-identity.mjs';
|
|
3
2
|
import { promptCredentials } from './prompts/credentials.mjs';
|
|
4
3
|
import { promptStoreSettings, promptWebSettings } from './prompts/store-settings.mjs';
|
|
5
|
-
import { askQuestion } from './guide.mjs';
|
|
6
|
-
|
|
7
|
-
function isInteractive() {
|
|
8
|
-
return Boolean(process.stdin.isTTY);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function isNonInteractive() {
|
|
12
|
-
return Boolean(process.env.npm_config_yes) || process.argv.includes('--postinstall');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function allPromptsProvided(cliFlags) {
|
|
16
|
-
return (
|
|
17
|
-
cliFlags.bundleId !== undefined &&
|
|
18
|
-
cliFlags.stitchApiKey !== undefined &&
|
|
19
|
-
cliFlags.cloudflareToken !== undefined &&
|
|
20
|
-
cliFlags.cloudflareAccountId !== undefined
|
|
21
|
-
);
|
|
22
|
-
}
|
|
4
|
+
import { askQuestion, getDefault, printSectionHeader } from './guide.mjs';
|
|
23
5
|
|
|
24
6
|
async function promptMcpTokens(rl, cliFlags, currentConfig) {
|
|
7
|
+
const stitchVal = getDefault('stitchApiKey', cliFlags, currentConfig) || '';
|
|
8
|
+
const cfTokenVal = getDefault('cloudflareToken', cliFlags, currentConfig) || '';
|
|
9
|
+
const cfAcctVal = getDefault('cloudflareAccountId', cliFlags, currentConfig) || '';
|
|
10
|
+
|
|
25
11
|
const result = {
|
|
26
|
-
stitchApiKey:
|
|
27
|
-
cloudflareToken:
|
|
28
|
-
cloudflareAccountId:
|
|
12
|
+
stitchApiKey: stitchVal,
|
|
13
|
+
cloudflareToken: cfTokenVal,
|
|
14
|
+
cloudflareAccountId: cfAcctVal,
|
|
29
15
|
};
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
cliFlags.cloudflareAccountId !== undefined
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
if (allProvided) return result;
|
|
17
|
+
if (result.stitchApiKey && result.cloudflareToken && result.cloudflareAccountId) {
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
38
20
|
|
|
21
|
+
printSectionHeader('MCP Tokens (Optional)');
|
|
39
22
|
console.log('Press Enter to skip any token you do not have yet.');
|
|
40
23
|
console.log('');
|
|
41
24
|
|
|
42
|
-
if (
|
|
25
|
+
if (!result.stitchApiKey) {
|
|
43
26
|
result.stitchApiKey = await askQuestion(rl, 'Stitch MCP API Key', currentConfig.stitchApiKey || '');
|
|
44
27
|
}
|
|
45
28
|
|
|
46
|
-
if (
|
|
29
|
+
if (!result.cloudflareToken) {
|
|
47
30
|
result.cloudflareToken = await askQuestion(rl, 'Cloudflare API Token', currentConfig.cloudflareToken || '');
|
|
48
31
|
}
|
|
49
32
|
|
|
50
|
-
if (result.cloudflareToken &&
|
|
33
|
+
if (result.cloudflareToken && !result.cloudflareAccountId) {
|
|
51
34
|
result.cloudflareAccountId = await askQuestion(
|
|
52
35
|
rl, 'Cloudflare Account ID', currentConfig.cloudflareAccountId || ''
|
|
53
36
|
);
|
|
@@ -57,56 +40,11 @@ async function promptMcpTokens(rl, cliFlags, currentConfig) {
|
|
|
57
40
|
}
|
|
58
41
|
|
|
59
42
|
export async function promptAll(rl, cliFlags, currentConfig, projectDir) {
|
|
60
|
-
console.log('');
|
|
61
|
-
console.log('\x1b[1m\x1b[36m=== Store Automator — Interactive Setup ===\x1b[0m');
|
|
62
|
-
|
|
63
43
|
const identity = await promptAppIdentity(rl, cliFlags, currentConfig);
|
|
64
|
-
|
|
65
44
|
const credentials = await promptCredentials(rl, cliFlags, currentConfig, projectDir);
|
|
66
|
-
|
|
67
45
|
const storeSettings = await promptStoreSettings(rl, cliFlags, currentConfig);
|
|
68
|
-
|
|
69
46
|
const webSettings = await promptWebSettings(rl, cliFlags, currentConfig);
|
|
70
|
-
|
|
71
|
-
console.log('');
|
|
72
|
-
console.log('\x1b[1m\x1b[36m=== MCP Tokens (Optional) ===\x1b[0m');
|
|
73
|
-
console.log('');
|
|
74
47
|
const mcpTokens = await promptMcpTokens(rl, cliFlags, currentConfig);
|
|
75
48
|
|
|
76
49
|
return { ...identity, ...credentials, ...storeSettings, ...webSettings, ...mcpTokens };
|
|
77
50
|
}
|
|
78
|
-
|
|
79
|
-
export async function promptForTokens(cliTokens = {}) {
|
|
80
|
-
const result = {
|
|
81
|
-
bundleId: cliTokens.bundleId ?? '',
|
|
82
|
-
stitchApiKey: cliTokens.stitchApiKey ?? '',
|
|
83
|
-
cloudflareToken: cliTokens.cloudflareToken ?? '',
|
|
84
|
-
cloudflareAccountId: cliTokens.cloudflareAccountId ?? '',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
if (allPromptsProvided(cliTokens)) {
|
|
88
|
-
console.log('All configuration provided via CLI flags, skipping prompts.');
|
|
89
|
-
return result;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (!isInteractive() || isNonInteractive()) {
|
|
93
|
-
console.log('Non-interactive terminal detected, skipping prompts.');
|
|
94
|
-
console.log('Run "npx store-automator" manually to configure.');
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const all = await promptAll(rl, cliTokens, {}, process.cwd());
|
|
102
|
-
return {
|
|
103
|
-
...all,
|
|
104
|
-
bundleId: all.bundleId || result.bundleId,
|
|
105
|
-
stitchApiKey: all.stitchApiKey || result.stitchApiKey,
|
|
106
|
-
cloudflareToken: all.cloudflareToken || result.cloudflareToken,
|
|
107
|
-
cloudflareAccountId: all.cloudflareAccountId || result.cloudflareAccountId,
|
|
108
|
-
};
|
|
109
|
-
} finally {
|
|
110
|
-
rl.close();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
@@ -1,61 +1,30 @@
|
|
|
1
|
-
import { askQuestion,
|
|
1
|
+
import { askQuestion, getDefault, printSectionHeader } from '../guide.mjs';
|
|
2
2
|
|
|
3
3
|
const FIELDS = [
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
key: 'bundleId',
|
|
12
|
-
label: 'Bundle ID (e.g. com.company.app)',
|
|
13
|
-
configKey: 'bundle_id',
|
|
14
|
-
flag: 'bundleId',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
key: 'packageName',
|
|
18
|
-
label: 'Android Package Name',
|
|
19
|
-
configKey: 'android.package_name',
|
|
20
|
-
flag: 'packageName',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
key: 'sku',
|
|
24
|
-
label: 'App Store Connect SKU',
|
|
25
|
-
configKey: 'ios.sku',
|
|
26
|
-
flag: 'sku',
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
key: 'appleId',
|
|
30
|
-
label: 'Apple Developer Account Email',
|
|
31
|
-
configKey: 'ios.apple_id',
|
|
32
|
-
flag: 'appleId',
|
|
33
|
-
},
|
|
4
|
+
{ key: 'appName', label: 'App Name (display name)' },
|
|
5
|
+
{ key: 'bundleId', label: 'Bundle ID (e.g. com.company.app)' },
|
|
6
|
+
{ key: 'packageName', label: 'Android Package Name' },
|
|
7
|
+
{ key: 'sku', label: 'App Store Connect SKU' },
|
|
8
|
+
{ key: 'appleId', label: 'Apple Developer Account Email' },
|
|
34
9
|
];
|
|
35
10
|
|
|
36
|
-
function getDefault(key, cliFlags, currentConfig) {
|
|
37
|
-
if (cliFlags[key] !== undefined) return cliFlags[key];
|
|
38
|
-
const configVal = currentConfig[key];
|
|
39
|
-
if (!isPlaceholder(configVal)) return configVal;
|
|
40
|
-
return '';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
11
|
export async function promptAppIdentity(rl, cliFlags, currentConfig) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
12
|
+
const allFilled = FIELDS.every((f) => !!getDefault(f.key, cliFlags, currentConfig));
|
|
13
|
+
|
|
14
|
+
if (!allFilled) {
|
|
15
|
+
printSectionHeader('App Identity');
|
|
16
|
+
}
|
|
47
17
|
|
|
48
18
|
const result = {};
|
|
49
19
|
|
|
50
20
|
for (const field of FIELDS) {
|
|
51
|
-
const flagVal =
|
|
52
|
-
if (flagVal
|
|
21
|
+
const flagVal = getDefault(field.key, cliFlags, currentConfig);
|
|
22
|
+
if (flagVal) {
|
|
53
23
|
result[field.key] = flagVal;
|
|
54
24
|
continue;
|
|
55
25
|
}
|
|
56
26
|
|
|
57
|
-
|
|
58
|
-
result[field.key] = await askQuestion(rl, field.label, defaultVal);
|
|
27
|
+
result[field.key] = await askQuestion(rl, field.label, '');
|
|
59
28
|
}
|
|
60
29
|
|
|
61
30
|
if (!result.packageName && result.bundleId) {
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
function getDefault(key, cliFlags, currentConfig) {
|
|
5
|
-
if (cliFlags[key] !== undefined) return cliFlags[key];
|
|
6
|
-
const configVal = currentConfig[key];
|
|
7
|
-
if (!isPlaceholder(configVal)) return configVal;
|
|
8
|
-
return '';
|
|
9
|
-
}
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { runGuide, askQuestion, getDefault, printSectionHeader } from '../guide.mjs';
|
|
10
4
|
|
|
11
5
|
async function promptAppStoreApiKey(rl, cliFlags, currentConfig, projectDir) {
|
|
6
|
+
const keyId = getDefault('keyId', cliFlags, currentConfig);
|
|
7
|
+
const issuerId = getDefault('issuerId', cliFlags, currentConfig);
|
|
8
|
+
const allFilled = !!(keyId && issuerId);
|
|
9
|
+
|
|
12
10
|
await runGuide(rl, {
|
|
13
11
|
title: 'Create App Store Connect API Key',
|
|
14
12
|
steps: [
|
|
@@ -21,24 +19,18 @@ async function promptAppStoreApiKey(rl, cliFlags, currentConfig, projectDir) {
|
|
|
21
19
|
verifyPath: join(projectDir, 'creds', 'AuthKey.p8'),
|
|
22
20
|
verifyDescription: 'App Store Connect API Key (.p8)',
|
|
23
21
|
confirmQuestion: 'Have you created and saved the API key?',
|
|
24
|
-
});
|
|
22
|
+
}, { skip: allFilled });
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
rl,
|
|
28
|
-
'App Store Connect
|
|
29
|
-
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const issuerId = await askQuestion(
|
|
33
|
-
rl,
|
|
34
|
-
'App Store Connect Issuer ID',
|
|
35
|
-
getDefault('issuerId', cliFlags, currentConfig)
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return { keyId, issuerId };
|
|
24
|
+
return {
|
|
25
|
+
keyId: await askQuestion(rl, 'App Store Connect Key ID', keyId, { skipIfFilled: !!keyId }),
|
|
26
|
+
issuerId: await askQuestion(rl, 'App Store Connect Issuer ID', issuerId, { skipIfFilled: !!issuerId }),
|
|
27
|
+
};
|
|
39
28
|
}
|
|
40
29
|
|
|
41
30
|
async function promptPlayServiceAccount(rl, projectDir) {
|
|
31
|
+
const jsonPath = join(projectDir, 'creds', 'play-service-account.json');
|
|
32
|
+
const fileExists = existsSync(jsonPath);
|
|
33
|
+
|
|
42
34
|
await runGuide(rl, {
|
|
43
35
|
title: 'Create Google Play Service Account',
|
|
44
36
|
steps: [
|
|
@@ -48,13 +40,15 @@ async function promptPlayServiceAccount(rl, projectDir) {
|
|
|
48
40
|
'Download the JSON key file',
|
|
49
41
|
'Save to creds/play-service-account.json in your project',
|
|
50
42
|
],
|
|
51
|
-
verifyPath:
|
|
43
|
+
verifyPath: jsonPath,
|
|
52
44
|
verifyDescription: 'Google Play Service Account JSON',
|
|
53
45
|
confirmQuestion: 'Have you set up the service account?',
|
|
54
|
-
});
|
|
46
|
+
}, { skip: fileExists });
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
async function promptAndroidKeystore(rl, cliFlags, currentConfig) {
|
|
50
|
+
const keystorePassword = getDefault('keystorePassword', cliFlags, currentConfig);
|
|
51
|
+
|
|
58
52
|
await runGuide(rl, {
|
|
59
53
|
title: 'Set up Android Keystore',
|
|
60
54
|
steps: [
|
|
@@ -64,18 +58,18 @@ async function promptAndroidKeystore(rl, cliFlags, currentConfig) {
|
|
|
64
58
|
'Keep the keystore file safe — you cannot replace it once uploaded to Google Play',
|
|
65
59
|
],
|
|
66
60
|
confirmQuestion: 'Have you created or located your Android keystore?',
|
|
67
|
-
});
|
|
61
|
+
}, { skip: !!keystorePassword });
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
rl,
|
|
71
|
-
'Keystore password',
|
|
72
|
-
getDefault('keystorePassword', cliFlags, currentConfig)
|
|
63
|
+
return await askQuestion(
|
|
64
|
+
rl, 'Keystore password', keystorePassword, { skipIfFilled: !!keystorePassword }
|
|
73
65
|
);
|
|
74
|
-
|
|
75
|
-
return keystorePassword;
|
|
76
66
|
}
|
|
77
67
|
|
|
78
68
|
async function promptMatchSigning(rl, cliFlags, currentConfig) {
|
|
69
|
+
const matchDeployKeyPath = getDefault('matchDeployKeyPath', cliFlags, currentConfig) || 'creds/match_deploy_key';
|
|
70
|
+
const matchGitUrl = getDefault('matchGitUrl', cliFlags, currentConfig);
|
|
71
|
+
const allFilled = !!(matchDeployKeyPath && matchGitUrl);
|
|
72
|
+
|
|
79
73
|
await runGuide(rl, {
|
|
80
74
|
title: 'Set up Match Code Signing',
|
|
81
75
|
steps: [
|
|
@@ -84,39 +78,37 @@ async function promptMatchSigning(rl, cliFlags, currentConfig) {
|
|
|
84
78
|
'Add the public key as a deploy key to the certificates repo (with write access)',
|
|
85
79
|
],
|
|
86
80
|
confirmQuestion: 'Have you set up the certificates repository and deploy key?',
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
'Match certificates Git URL (SSH)',
|
|
98
|
-
getDefault('matchGitUrl', cliFlags, currentConfig)
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
return { matchDeployKeyPath, matchGitUrl };
|
|
81
|
+
}, { skip: allFilled });
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
matchDeployKeyPath: await askQuestion(
|
|
85
|
+
rl, 'Path to Match deploy key', matchDeployKeyPath, { skipIfFilled: !!matchDeployKeyPath }
|
|
86
|
+
),
|
|
87
|
+
matchGitUrl: await askQuestion(
|
|
88
|
+
rl, 'Match certificates Git URL (SSH)', matchGitUrl, { skipIfFilled: !!matchGitUrl }
|
|
89
|
+
),
|
|
90
|
+
};
|
|
102
91
|
}
|
|
103
92
|
|
|
104
93
|
export async function promptCredentials(rl, cliFlags, currentConfig, projectDir) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
const keyId = getDefault('keyId', cliFlags, currentConfig);
|
|
95
|
+
const issuerId = getDefault('issuerId', cliFlags, currentConfig);
|
|
96
|
+
const keystorePassword = getDefault('keystorePassword', cliFlags, currentConfig);
|
|
97
|
+
const matchDeployKeyPath = getDefault('matchDeployKeyPath', cliFlags, currentConfig) || 'creds/match_deploy_key';
|
|
98
|
+
const matchGitUrl = getDefault('matchGitUrl', cliFlags, currentConfig);
|
|
99
|
+
const jsonPath = join(projectDir, 'creds', 'play-service-account.json');
|
|
100
|
+
const jsonExists = existsSync(jsonPath);
|
|
108
101
|
|
|
109
|
-
const
|
|
110
|
-
rl, cliFlags, currentConfig, projectDir
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
await promptPlayServiceAccount(rl, projectDir);
|
|
102
|
+
const allFilled = !!(keyId && issuerId && keystorePassword && matchDeployKeyPath && matchGitUrl && jsonExists);
|
|
114
103
|
|
|
115
|
-
|
|
104
|
+
if (!allFilled) {
|
|
105
|
+
printSectionHeader('Credentials Setup');
|
|
106
|
+
}
|
|
116
107
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
);
|
|
108
|
+
const apiKey = await promptAppStoreApiKey(rl, cliFlags, currentConfig, projectDir);
|
|
109
|
+
await promptPlayServiceAccount(rl, projectDir);
|
|
110
|
+
const ksPassword = await promptAndroidKeystore(rl, cliFlags, currentConfig);
|
|
111
|
+
const match = await promptMatchSigning(rl, cliFlags, currentConfig);
|
|
120
112
|
|
|
121
|
-
return {
|
|
113
|
+
return { ...apiKey, keystorePassword: ksPassword, ...match };
|
|
122
114
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
runGuide, askQuestion, getDefault, getDefaultWithFallback, printSectionHeader,
|
|
3
|
+
} from '../guide.mjs';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const configVal = currentConfig[key];
|
|
6
|
-
if (!isPlaceholder(configVal)) return configVal;
|
|
7
|
-
return '';
|
|
8
|
-
}
|
|
5
|
+
const IOS_KEYS = ['primaryCategory', 'secondaryCategory', 'priceTier', 'submitForReview', 'automaticRelease'];
|
|
6
|
+
const ANDROID_KEYS = ['track', 'rolloutFraction', 'inAppUpdatePriority'];
|
|
9
7
|
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
function allKeysFilled(keys, cliFlags, currentConfig) {
|
|
9
|
+
return keys.every((k) => {
|
|
10
|
+
const val = getDefault(k, cliFlags, currentConfig);
|
|
11
|
+
return val !== '' && val !== undefined;
|
|
12
|
+
});
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
async function promptIosSettings(rl, cliFlags, currentConfig) {
|
|
16
|
+
const iosFilled = allKeysFilled(IOS_KEYS, cliFlags, currentConfig);
|
|
17
|
+
|
|
16
18
|
await runGuide(rl, {
|
|
17
19
|
title: 'Create App in App Store Connect',
|
|
18
20
|
steps: [
|
|
@@ -22,36 +24,31 @@ async function promptIosSettings(rl, cliFlags, currentConfig) {
|
|
|
22
24
|
'Save the app',
|
|
23
25
|
],
|
|
24
26
|
confirmQuestion: 'Have you created the app in App Store Connect?',
|
|
25
|
-
});
|
|
27
|
+
}, { skip: iosFilled });
|
|
26
28
|
|
|
29
|
+
const primaryCat = getDefault('primaryCategory', cliFlags, currentConfig);
|
|
27
30
|
const primaryCategory = await askQuestion(
|
|
28
|
-
rl,
|
|
29
|
-
'iOS Primary Category (e.g. GAMES, UTILITIES)',
|
|
30
|
-
getDefault('primaryCategory', cliFlags, currentConfig)
|
|
31
|
+
rl, 'iOS Primary Category (e.g. GAMES, UTILITIES)', primaryCat, { skipIfFilled: !!primaryCat }
|
|
31
32
|
);
|
|
32
33
|
|
|
34
|
+
const secCat = getDefault('secondaryCategory', cliFlags, currentConfig);
|
|
33
35
|
const secondaryCategory = await askQuestion(
|
|
34
|
-
rl,
|
|
35
|
-
'iOS Secondary Category (optional)',
|
|
36
|
-
getDefault('secondaryCategory', cliFlags, currentConfig)
|
|
36
|
+
rl, 'iOS Secondary Category (optional)', secCat, { skipIfFilled: !!secCat }
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
+
const priceVal = getDefaultWithFallback('priceTier', cliFlags, currentConfig, '0');
|
|
39
40
|
const priceTier = await askQuestion(
|
|
40
|
-
rl,
|
|
41
|
-
'iOS Price Tier',
|
|
42
|
-
getDefaultWithFallback('priceTier', cliFlags, currentConfig, '0')
|
|
41
|
+
rl, 'iOS Price Tier', priceVal, { skipIfFilled: !!priceVal }
|
|
43
42
|
);
|
|
44
43
|
|
|
44
|
+
const submitVal = getDefaultWithFallback('submitForReview', cliFlags, currentConfig, 'true');
|
|
45
45
|
const submitForReview = await askQuestion(
|
|
46
|
-
rl,
|
|
47
|
-
'Auto-submit for review? (true/false)',
|
|
48
|
-
getDefaultWithFallback('submitForReview', cliFlags, currentConfig, 'true')
|
|
46
|
+
rl, 'Auto-submit for review? (true/false)', submitVal, { skipIfFilled: !!submitVal }
|
|
49
47
|
);
|
|
50
48
|
|
|
49
|
+
const autoVal = getDefaultWithFallback('automaticRelease', cliFlags, currentConfig, 'true');
|
|
51
50
|
const automaticRelease = await askQuestion(
|
|
52
|
-
rl,
|
|
53
|
-
'Automatic release after approval? (true/false)',
|
|
54
|
-
getDefaultWithFallback('automaticRelease', cliFlags, currentConfig, 'true')
|
|
51
|
+
rl, 'Automatic release after approval? (true/false)', autoVal, { skipIfFilled: !!autoVal }
|
|
55
52
|
);
|
|
56
53
|
|
|
57
54
|
return {
|
|
@@ -64,6 +61,8 @@ async function promptIosSettings(rl, cliFlags, currentConfig) {
|
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
async function promptAndroidSettings(rl, cliFlags, currentConfig) {
|
|
64
|
+
const androidFilled = allKeysFilled(ANDROID_KEYS, cliFlags, currentConfig);
|
|
65
|
+
|
|
67
66
|
await runGuide(rl, {
|
|
68
67
|
title: 'Create App in Google Play Console',
|
|
69
68
|
steps: [
|
|
@@ -73,24 +72,21 @@ async function promptAndroidSettings(rl, cliFlags, currentConfig) {
|
|
|
73
72
|
'Complete the declarations and create',
|
|
74
73
|
],
|
|
75
74
|
confirmQuestion: 'Have you created the app in Google Play Console?',
|
|
76
|
-
});
|
|
75
|
+
}, { skip: androidFilled });
|
|
77
76
|
|
|
77
|
+
const trackVal = getDefaultWithFallback('track', cliFlags, currentConfig, 'internal');
|
|
78
78
|
const track = await askQuestion(
|
|
79
|
-
rl,
|
|
80
|
-
'Android release track (internal/alpha/beta/production)',
|
|
81
|
-
getDefaultWithFallback('track', cliFlags, currentConfig, 'internal')
|
|
79
|
+
rl, 'Android release track (internal/alpha/beta/production)', trackVal, { skipIfFilled: !!trackVal }
|
|
82
80
|
);
|
|
83
81
|
|
|
82
|
+
const rolloutVal = getDefaultWithFallback('rolloutFraction', cliFlags, currentConfig, '1.0');
|
|
84
83
|
const rolloutFraction = await askQuestion(
|
|
85
|
-
rl,
|
|
86
|
-
'Rollout fraction (0.0 - 1.0)',
|
|
87
|
-
getDefaultWithFallback('rolloutFraction', cliFlags, currentConfig, '1.0')
|
|
84
|
+
rl, 'Rollout fraction (0.0 - 1.0)', rolloutVal, { skipIfFilled: !!rolloutVal }
|
|
88
85
|
);
|
|
89
86
|
|
|
87
|
+
const priorityVal = getDefaultWithFallback('inAppUpdatePriority', cliFlags, currentConfig, '3');
|
|
90
88
|
const inAppUpdatePriority = await askQuestion(
|
|
91
|
-
rl,
|
|
92
|
-
'In-app update priority (0-5)',
|
|
93
|
-
getDefaultWithFallback('inAppUpdatePriority', cliFlags, currentConfig, '3')
|
|
89
|
+
rl, 'In-app update priority (0-5)', priorityVal, { skipIfFilled: !!priorityVal }
|
|
94
90
|
);
|
|
95
91
|
|
|
96
92
|
return {
|
|
@@ -101,19 +97,23 @@ async function promptAndroidSettings(rl, cliFlags, currentConfig) {
|
|
|
101
97
|
}
|
|
102
98
|
|
|
103
99
|
async function promptMetadataSettings(rl, cliFlags, currentConfig) {
|
|
100
|
+
const langVal = getDefaultWithFallback('languages', cliFlags, currentConfig, 'en-US');
|
|
104
101
|
const languages = await askQuestion(
|
|
105
|
-
rl,
|
|
106
|
-
'Metadata languages (comma-separated, e.g. en-US,de-DE)',
|
|
107
|
-
getDefaultWithFallback('languages', cliFlags, currentConfig, 'en-US')
|
|
102
|
+
rl, 'Metadata languages (comma-separated, e.g. en-US,de-DE)', langVal, { skipIfFilled: !!langVal }
|
|
108
103
|
);
|
|
109
104
|
|
|
110
105
|
return { languages: languages.split(',').map((l) => l.trim()).filter(Boolean) };
|
|
111
106
|
}
|
|
112
107
|
|
|
113
108
|
export async function promptStoreSettings(rl, cliFlags, currentConfig) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
const iosFilled = allKeysFilled(IOS_KEYS, cliFlags, currentConfig);
|
|
110
|
+
const androidFilled = allKeysFilled(ANDROID_KEYS, cliFlags, currentConfig);
|
|
111
|
+
const langVal = getDefaultWithFallback('languages', cliFlags, currentConfig, 'en-US');
|
|
112
|
+
const allFilled = iosFilled && androidFilled && !!langVal;
|
|
113
|
+
|
|
114
|
+
if (!allFilled) {
|
|
115
|
+
printSectionHeader('Store Settings');
|
|
116
|
+
}
|
|
117
117
|
|
|
118
118
|
const ios = await promptIosSettings(rl, cliFlags, currentConfig);
|
|
119
119
|
const android = await promptAndroidSettings(rl, cliFlags, currentConfig);
|
|
@@ -122,10 +122,17 @@ export async function promptStoreSettings(rl, cliFlags, currentConfig) {
|
|
|
122
122
|
return { ...ios, ...android, ...metadata };
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
const WEB_KEYS = [
|
|
126
|
+
'domain', 'cfProjectName', 'tagline', 'primaryColor', 'secondaryColor',
|
|
127
|
+
'companyName', 'contactEmail', 'supportEmail', 'jurisdiction',
|
|
128
|
+
];
|
|
129
|
+
|
|
125
130
|
export async function promptWebSettings(rl, cliFlags, currentConfig) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
const webFilled = WEB_KEYS.every((k) => !!getDefault(k, cliFlags, currentConfig));
|
|
132
|
+
|
|
133
|
+
if (!webFilled) {
|
|
134
|
+
printSectionHeader('Web Settings');
|
|
135
|
+
}
|
|
129
136
|
|
|
130
137
|
const fields = [
|
|
131
138
|
{ key: 'domain', label: 'Domain (e.g. example.com)' },
|
|
@@ -141,16 +148,17 @@ export async function promptWebSettings(rl, cliFlags, currentConfig) {
|
|
|
141
148
|
|
|
142
149
|
const result = {};
|
|
143
150
|
for (const field of fields) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
field.label,
|
|
147
|
-
getDefault(field.key, cliFlags, currentConfig)
|
|
148
|
-
);
|
|
151
|
+
const val = getDefault(field.key, cliFlags, currentConfig);
|
|
152
|
+
result[field.key] = await askQuestion(rl, field.label, val, { skipIfFilled: !!val });
|
|
149
153
|
}
|
|
150
154
|
return result;
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
export async function runPostInstallGuides(rl) {
|
|
157
|
+
export async function runPostInstallGuides(rl, currentConfig = {}) {
|
|
158
|
+
const hasRepo = !!currentConfig._githubRepoConfigured;
|
|
159
|
+
const hasSecrets = !!currentConfig._githubSecretsConfigured;
|
|
160
|
+
const hasFirebase = !!currentConfig._firebaseConfigured;
|
|
161
|
+
|
|
154
162
|
await runGuide(rl, {
|
|
155
163
|
title: 'Create Private GitHub Repository',
|
|
156
164
|
steps: [
|
|
@@ -159,7 +167,7 @@ export async function runPostInstallGuides(rl) {
|
|
|
159
167
|
'Push your project to the repository',
|
|
160
168
|
],
|
|
161
169
|
confirmQuestion: 'Have you created the GitHub repository?',
|
|
162
|
-
});
|
|
170
|
+
}, { skip: hasRepo });
|
|
163
171
|
|
|
164
172
|
await runGuide(rl, {
|
|
165
173
|
title: 'Set GitHub Actions Secrets',
|
|
@@ -170,7 +178,7 @@ export async function runPostInstallGuides(rl) {
|
|
|
170
178
|
'Upload creds/ files as secrets if using CI',
|
|
171
179
|
],
|
|
172
180
|
confirmQuestion: 'Have you configured GitHub Actions secrets?',
|
|
173
|
-
});
|
|
181
|
+
}, { skip: hasSecrets });
|
|
174
182
|
|
|
175
183
|
await runGuide(rl, {
|
|
176
184
|
title: 'Create Firebase Project (optional)',
|
|
@@ -181,5 +189,5 @@ export async function runPostInstallGuides(rl) {
|
|
|
181
189
|
'Download google-services.json and GoogleService-Info.plist',
|
|
182
190
|
],
|
|
183
191
|
confirmQuestion: 'Have you set up Firebase? (skip if not needed)',
|
|
184
|
-
});
|
|
192
|
+
}, { skip: hasFirebase });
|
|
185
193
|
}
|