kasy-cli 1.5.3 → 1.7.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.
@@ -2,6 +2,8 @@ const path = require('node:path');
2
2
  const { exec } = require('node:child_process');
3
3
  const fs = require('fs-extra');
4
4
  const kleur = require('kleur');
5
+ const ui = require('../utils/ui');
6
+ const { printCompactHeader } = require('../utils/brand');
5
7
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
6
8
 
7
9
  const DOCS_FILE = path.join(__dirname, '..', '..', 'docs', 'cli-reference.md');
@@ -18,30 +20,29 @@ function openInBrowser(url) {
18
20
  async function runDocs(options = {}) {
19
21
  const t = createTranslator(options.language || detectDefaultLanguage());
20
22
 
21
- console.log(kleur.bold(`\n Kasy CLI — Documentação\n`));
23
+ printCompactHeader(t);
24
+ ui.intro('Kasy CLI — Documentação');
22
25
 
23
- // Show local CLI reference
24
26
  if (await fs.pathExists(DOCS_FILE)) {
25
27
  const content = await fs.readFile(DOCS_FILE, 'utf8');
26
- // Show the first section (commands overview)
27
28
  const lines = content.split('\n').slice(0, 60);
28
- console.log(kleur.dim(lines.join('\n')));
29
- console.log(kleur.dim(`\n ... (arquivo completo: ${DOCS_FILE})\n`));
29
+ ui.note(`${kleur.dim(lines.join('\n'))}\n\n${kleur.dim(`... (arquivo completo: ${DOCS_FILE})`)}`);
30
30
  }
31
31
 
32
- console.log(kleur.cyan(` Comandos disponíveis:`));
33
- console.log(` ${kleur.bold('kasy new')} — Criar novo projeto`);
34
- console.log(` ${kleur.bold('kasy add')} — Adicionar módulo`);
35
- console.log(` ${kleur.bold('kasy doctor')} — Verificar ambiente`);
36
- console.log(` ${kleur.bold('kasy run')} — Executar app`);
37
- console.log(` ${kleur.bold('kasy deploy')} — Deploy do projeto`);
38
- console.log(` ${kleur.bold('kasy ios')} — Release iOS (configure / release / build)`);
39
- console.log(` ${kleur.bold('kasy codemagic')} — Build iOS na nuvem`);
40
- console.log(` ${kleur.bold('kasy features')} — Listar features`);
41
- console.log(` ${kleur.bold('kasy validate')} — Validar projeto`);
42
- console.log('');
43
- console.log(kleur.dim(` Para detalhes de um comando: kasy help <comando>`));
44
- console.log('');
32
+ const commandsBody = [
33
+ `${kleur.bold('kasy new')} — Criar novo projeto`,
34
+ `${kleur.bold('kasy add')} — Adicionar módulo`,
35
+ `${kleur.bold('kasy doctor')} — Verificar ambiente`,
36
+ `${kleur.bold('kasy run')} — Executar app`,
37
+ `${kleur.bold('kasy deploy')} — Deploy do projeto`,
38
+ `${kleur.bold('kasy ios')} — Release iOS (configure / release / build)`,
39
+ `${kleur.bold('kasy codemagic')} — Build iOS na nuvem`,
40
+ `${kleur.bold('kasy features')} — Listar features`,
41
+ `${kleur.bold('kasy validate')} — Validar projeto`,
42
+ ].join('\n');
43
+ ui.note(commandsBody, kleur.cyan('Comandos disponíveis'));
44
+
45
+ ui.outro(kleur.dim('Para detalhes de um comando: kasy help <comando>'));
45
46
  }
46
47
 
47
48
  module.exports = { runDocs };
@@ -1,6 +1,7 @@
1
1
  const path = require('node:path');
2
2
  const fs = require('fs-extra');
3
3
  const kleur = require('kleur');
4
+ const ui = require('../utils/ui');
4
5
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
5
6
  const {
6
7
  getBaseChecks,
@@ -17,6 +18,7 @@ const {
17
18
  } = require('../utils/apple-release');
18
19
  const { validateCodemagicSetup, codemagicReleaseDocPath } = require('../utils/codemagic-release');
19
20
  const { validateGoogleIosUrlScheme, validateAppleSignInEntitlement, validateFacebookInfoPlist, validateFacebookAndroidStrings, validateRevenueCat } = require('../scaffold/shared/post-build');
21
+ const { printCompactHeader } = require('../utils/brand');
20
22
 
21
23
  function collectOptionalBackendChecks() {
22
24
  return [
@@ -62,26 +64,26 @@ async function runProjectChecks(projectDir, t, options = {}) {
62
64
  return;
63
65
  }
64
66
 
65
- console.log(kleur.bold(`\n${t('doctor.project.title')}`));
67
+ ui.log.step(kleur.bold(t('doctor.project.title')));
66
68
 
67
- console.log(` ${kleur.green('✓')} ${t('doctor.project.appName')}: ${kleur.cyan(config.appName || '—')}`);
68
- console.log(` ${kleur.green('✓')} ${t('doctor.project.backend')}: ${kleur.cyan(config.backendProvider || '—')}`);
69
- console.log(` ${kleur.green('✓')} ${t('doctor.project.bundleId')}: ${kleur.dim(config.bundleId || '—')}`);
69
+ ui.log.success(`${t('doctor.project.appName')}: ${kleur.cyan(config.appName || '—')}`);
70
+ ui.log.success(`${t('doctor.project.backend')}: ${kleur.cyan(config.backendProvider || '—')}`);
71
+ ui.log.success(`${t('doctor.project.bundleId')}: ${kleur.dim(config.bundleId || '—')}`);
70
72
 
71
73
  // pub get check
72
74
  const lockExists = await fs.pathExists(path.join(projectDir, 'pubspec.lock'));
73
75
  if (lockExists) {
74
- console.log(` ${kleur.green('✓')} ${t('doctor.project.pubGet')}`);
76
+ ui.log.success(t('doctor.project.pubGet'));
75
77
  } else {
76
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.project.pubGetMissing')}`);
78
+ ui.log.warn(t('doctor.project.pubGetMissing'));
77
79
  }
78
80
 
79
81
  // Active modules
80
82
  const modules = await getActiveModules(config, projectDir);
81
83
  if (modules.length > 0) {
82
- console.log(` ${kleur.green('✓')} ${t('doctor.project.modules')}: ${kleur.cyan(modules.join(', '))}`);
84
+ ui.log.success(`${t('doctor.project.modules')}: ${kleur.cyan(modules.join(', '))}`);
83
85
  } else {
84
- console.log(` ${kleur.dim('–')} ${t('doctor.project.noModules')}`);
86
+ ui.log.message(kleur.dim(`– ${t('doctor.project.noModules')}`));
85
87
  }
86
88
 
87
89
  await runIosReleaseChecks(projectDir, t, options.language, config);
@@ -92,106 +94,101 @@ async function runIosReleaseChecks(projectDir, t, language, config = {}) {
92
94
  if (!(await isKasyFlutterProject(projectDir))) return;
93
95
 
94
96
  const lang = language || detectDefaultLanguage();
95
- console.log(kleur.bold(`\n${t('doctor.ios.title')}`));
97
+ ui.log.step(kleur.bold(t('doctor.ios.title')));
96
98
 
97
99
  if (process.platform !== 'darwin') {
98
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.ios.notMac')}`);
100
+ ui.log.warn(t('doctor.ios.notMac'));
99
101
  } else {
100
102
  const signing = await checkIosSigning(projectDir);
101
103
  if (signing.ok) {
102
- console.log(` ${kleur.green('✓')} ${t('doctor.ios.signingOk')}`);
104
+ ui.log.success(t('doctor.ios.signingOk'));
103
105
  } else {
104
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.ios.signingMissing')}`);
106
+ ui.log.warn(t('doctor.ios.signingMissing'));
105
107
  }
106
108
  }
107
109
 
108
110
  const googleScheme = await validateGoogleIosUrlScheme(projectDir);
109
111
  if (googleScheme.skipped) {
110
- console.log(` ${kleur.dim('–')} ${t('doctor.ios.googleUrlSchemeSkipped')}`);
112
+ ui.log.message(kleur.dim(`– ${t('doctor.ios.googleUrlSchemeSkipped')}`));
111
113
  } else if (googleScheme.ok) {
112
- console.log(` ${kleur.green('✓')} ${t('doctor.ios.googleUrlSchemeOk')}`);
114
+ ui.log.success(t('doctor.ios.googleUrlSchemeOk'));
113
115
  } else {
114
- console.log(` ${kleur.red('✗')} ${t('doctor.ios.googleUrlSchemeMissing')}`);
115
- console.log(kleur.dim(` ${googleScheme.error}`));
116
+ ui.log.error(`${t('doctor.ios.googleUrlSchemeMissing')}\n${kleur.dim(googleScheme.error)}`);
116
117
  }
117
118
 
118
119
  const appleEntitlement = await validateAppleSignInEntitlement(projectDir);
119
120
  if (appleEntitlement.ok) {
120
- console.log(` ${kleur.green('✓')} ${t('doctor.ios.appleSignInEntitlementOk')}`);
121
+ ui.log.success(t('doctor.ios.appleSignInEntitlementOk'));
121
122
  } else {
122
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.ios.appleSignInEntitlementMissing')}`);
123
- console.log(kleur.dim(` ${appleEntitlement.error}`));
123
+ ui.log.warn(`${t('doctor.ios.appleSignInEntitlementMissing')}\n${kleur.dim(appleEntitlement.error)}`);
124
124
  }
125
125
 
126
126
  if (config.withFacebookPixel) {
127
127
  const facebook = await validateFacebookInfoPlist(projectDir);
128
128
  if (facebook.skipped) {
129
- console.log(` ${kleur.dim('–')} ${t('doctor.ios.facebookSkipped')}`);
129
+ ui.log.message(kleur.dim(`– ${t('doctor.ios.facebookSkipped')}`));
130
130
  } else if (facebook.ok) {
131
- console.log(` ${kleur.green('✓')} ${t('doctor.ios.facebookOk')}`);
131
+ ui.log.success(t('doctor.ios.facebookOk'));
132
132
  } else {
133
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.ios.facebookPlaceholders')}`);
134
- console.log(kleur.dim(` ${facebook.error}`));
133
+ ui.log.warn(`${t('doctor.ios.facebookPlaceholders')}\n${kleur.dim(facebook.error)}`);
135
134
  }
136
135
 
137
136
  const facebookAndroid = await validateFacebookAndroidStrings(projectDir);
138
137
  if (!facebookAndroid.skipped) {
139
138
  if (facebookAndroid.ok) {
140
- console.log(` ${kleur.green('✓')} ${t('doctor.android.facebookOk')}`);
139
+ ui.log.success(t('doctor.android.facebookOk'));
141
140
  } else {
142
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.android.facebookPlaceholders')}`);
143
- console.log(kleur.dim(` ${facebookAndroid.error}`));
141
+ ui.log.warn(`${t('doctor.android.facebookPlaceholders')}\n${kleur.dim(facebookAndroid.error)}`);
144
142
  }
145
143
  }
146
144
  }
147
145
 
148
146
  const apple = await validateAppleSetup(projectDir);
149
147
  if (apple.ok) {
150
- console.log(` ${kleur.green('✓')} ${t('doctor.ios.appleOk')}`);
151
- console.log(` ${kleur.green('✓')} ${t('doctor.ios.p8Ok')}`);
148
+ ui.log.success(t('doctor.ios.appleOk'));
149
+ ui.log.success(t('doctor.ios.p8Ok'));
152
150
  } else {
153
151
  if (apple.issues.includes('missing_env') || apple.issues.includes('missing_credentials')) {
154
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.ios.appleMissing')}`);
152
+ ui.log.warn(t('doctor.ios.appleMissing'));
155
153
  }
156
154
  if (apple.issues.includes('missing_p8')) {
157
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.ios.p8Missing')}`);
155
+ ui.log.warn(t('doctor.ios.p8Missing'));
158
156
  }
159
157
  }
160
158
 
161
159
  const cm = await validateCodemagicSetup(projectDir);
162
160
  if (cm.ok) {
163
- console.log(` ${kleur.green('✓')} ${t('doctor.ios.codemagicOk')}`);
161
+ ui.log.success(t('doctor.ios.codemagicOk'));
164
162
  } else if (cm.issues.includes('missing_env')) {
165
- console.log(` ${kleur.dim('–')} ${t('doctor.ios.codemagicMissing')}`);
163
+ ui.log.message(kleur.dim(`– ${t('doctor.ios.codemagicMissing')}`));
166
164
  }
167
165
 
168
166
  const docPath = iosReleaseDocPath(lang);
169
- console.log(` ${kleur.dim('–')} ${t('doctor.ios.doc')}: ${docPath}, ${codemagicReleaseDocPath(lang)}`);
167
+ ui.log.message(kleur.dim(`– ${t('doctor.ios.doc')}: ${docPath}, ${codemagicReleaseDocPath(lang)}`));
170
168
  }
171
169
 
172
170
  async function runRevenueCatChecks(projectDir, t, config) {
173
171
  if (!config.subscriptionModule) return;
174
172
 
175
- console.log(kleur.bold(`\n${t('doctor.revenuecat.title')}`));
173
+ ui.log.step(kleur.bold(t('doctor.revenuecat.title')));
176
174
 
177
175
  const rc = await validateRevenueCat(projectDir, config);
178
176
  if (rc.skipped) return;
179
177
 
180
178
  if (!rc.iosEmpty && !rc.androidEmpty) {
181
- console.log(` ${kleur.green('✓')} ${t('doctor.revenuecat.keysOk')}`);
179
+ ui.log.success(t('doctor.revenuecat.keysOk'));
182
180
  } else {
183
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.revenuecat.keysEmpty')}`);
181
+ ui.log.warn(t('doctor.revenuecat.keysEmpty'));
184
182
  }
185
183
 
186
184
  if (rc.bothTest) {
187
- console.log(` ${kleur.yellow('⚠')} ${t('doctor.revenuecat.keysTest')}`);
185
+ ui.log.warn(t('doctor.revenuecat.keysTest'));
188
186
  }
189
187
 
190
188
  if (rc.webhookUrl) {
191
- console.log(` ${kleur.green('✓')} ${t('doctor.revenuecat.webhookUrlSupabase')}:`);
192
- console.log(kleur.cyan(` ${rc.webhookUrl}`));
189
+ ui.log.success(`${t('doctor.revenuecat.webhookUrlSupabase')}:\n${kleur.cyan(rc.webhookUrl)}`);
193
190
  } else if (config.backendProvider === 'firebase' || config.firebaseProjectId) {
194
- console.log(` ${kleur.dim('–')} ${t('doctor.revenuecat.webhookUrlFirebase')}`);
191
+ ui.log.message(kleur.dim(`– ${t('doctor.revenuecat.webhookUrlFirebase')}`));
195
192
  }
196
193
  }
197
194
 
@@ -199,27 +196,32 @@ async function runDoctor(options = {}) {
199
196
  const language = options.language || detectDefaultLanguage();
200
197
  const t = createTranslator(language);
201
198
  const projectDir = path.resolve(options.directory || '.');
202
- console.log(kleur.bold(`\n${t('doctor.title')}\n`));
199
+ printCompactHeader(t);
200
+ ui.intro(t('doctor.title'));
203
201
 
204
202
  // Project-specific checks (only when inside a kasy project)
205
203
  await runProjectChecks(projectDir, t, { language });
206
204
 
205
+ // Compact mode integrates with the Clack rail (│). Non-compact mode uses
206
+ // legacy `ora` which would break the visual flow opened by ui.intro above.
207
207
  const baseResults = await runChecks(
208
208
  [...getBaseChecks(), ...getPlatformChecks()],
209
209
  t('doctor.baseEnvironment'),
210
- { t }
210
+ { t, compact: true, spinnerLabel: t('doctor.baseEnvironment'), doneLabel: t('doctor.baseEnvironment') }
211
211
  );
212
212
 
213
213
  const optionalBackend = collectOptionalBackendChecks();
214
214
  if (optionalBackend.length > 0) {
215
- await runChecks(optionalBackend, t('doctor.optionalBackend'), { t });
215
+ await runChecks(optionalBackend, t('doctor.optionalBackend'), {
216
+ t, compact: true, spinnerLabel: t('doctor.optionalBackend'), doneLabel: t('doctor.optionalBackend'),
217
+ });
216
218
  }
217
219
 
218
220
  if (hasRequiredFailures(baseResults)) {
219
221
  throw new Error(t('doctor.requiredMissing'));
220
222
  }
221
223
 
222
- console.log(kleur.green(`\n${t('doctor.requiredPassed')}`));
224
+ ui.outro(t('doctor.requiredPassed'));
223
225
  }
224
226
 
225
227
  module.exports = {
@@ -1,33 +1,55 @@
1
1
  const kleur = require('kleur');
2
+ const ui = require('../utils/ui');
3
+ const { printCompactHeader } = require('../utils/brand');
2
4
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
3
5
  const {
4
6
  AVAILABLE_BACKENDS,
5
- getVisibleFeatures
7
+ getVisibleFeatures,
8
+ getBaseFeatures,
9
+ BASE_FEATURES
6
10
  } = require('../scaffold/catalog');
7
11
 
12
+ function findBaseDisplayName(id) {
13
+ const base = BASE_FEATURES.find((f) => f.id === id);
14
+ return base ? base.displayName : id;
15
+ }
16
+
8
17
  const KASY_AUDIENCE = process.env.KASY_INTERNAL === '1' ? 'internal' : 'public';
9
18
 
10
19
  function runFeatures(options = {}) {
11
20
  const t = createTranslator(options.language || detectDefaultLanguage());
12
- console.log(kleur.bold(`\n${t('modules.backends')}\n`));
13
- for (const backend of AVAILABLE_BACKENDS) {
14
- console.log(
15
- `${kleur.green('✓')} ${kleur.white(backend.id)} - ${kleur.gray(
16
- t(`modules.backend.${backend.id}.description`)
17
- )}`
18
- );
19
- }
20
-
21
- console.log(kleur.bold(`\n${t('modules.features')}\n`));
22
- for (const feature of getVisibleFeatures({ audience: KASY_AUDIENCE })) {
23
- const badge = feature.status === 'internal' ? kleur.yellow(' [beta]') : '';
24
- console.log(
25
- `${kleur.cyan('•')} ${kleur.white(feature.id)}${badge} - ${kleur.gray(
26
- t(`modules.feature.${feature.id}.description`)
27
- )}`
28
- );
29
- }
30
- console.log('');
21
+ printCompactHeader(t);
22
+ ui.intro(t('modules.backends'));
23
+
24
+ const backendLines = AVAILABLE_BACKENDS.map((backend) =>
25
+ `${kleur.green('✓')} ${kleur.white(backend.id)} - ${kleur.gray(
26
+ t(`modules.backend.${backend.id}.description`)
27
+ )}`
28
+ );
29
+ ui.log.message(backendLines.join('\n'));
30
+
31
+ ui.log.step(kleur.bold(t('modules.featuresBase')));
32
+ const baseLines = getBaseFeatures().map((feature) =>
33
+ `${kleur.green('✓')} ${kleur.white(feature.displayName)} - ${kleur.gray(
34
+ t(`modules.feature.base.${feature.id}.description`)
35
+ )}`
36
+ );
37
+ ui.log.message(baseLines.join('\n'));
38
+
39
+ ui.log.step(kleur.bold(t('modules.features')));
40
+ const featureLines = getVisibleFeatures({ audience: KASY_AUDIENCE }).map((feature) => {
41
+ const betaBadge = feature.status === 'internal' ? kleur.yellow(' [beta]') : '';
42
+ const tagBadge = feature.tag ? kleur.yellow(` [${t(`modules.tag.${feature.tag}`)}]`) : '';
43
+ const enhancesBadge = feature.enhances
44
+ ? kleur.magenta(` [${t('modules.tag.enhances', { target: findBaseDisplayName(feature.enhances) })}]`)
45
+ : '';
46
+ return `${kleur.cyan('○')} ${kleur.white(feature.displayName)}${tagBadge}${enhancesBadge}${betaBadge} - ${kleur.gray(
47
+ t(`modules.feature.${feature.id}.description`)
48
+ )}`;
49
+ });
50
+ ui.log.message(featureLines.join('\n'));
51
+
52
+ ui.outro('');
31
53
  }
32
54
 
33
55
  module.exports = {
@@ -3,7 +3,8 @@
3
3
  const path = require('node:path');
4
4
  const fs = require('fs-extra');
5
5
  const kleur = require('kleur');
6
- const prompts = require('prompts');
6
+ const ui = require('../utils/ui');
7
+ const { printCompactHeader } = require('../utils/brand');
7
8
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
8
9
  const {
9
10
  isKasyFlutterProject,
@@ -39,65 +40,61 @@ async function runConfigure(directory, options = {}) {
39
40
  await assertProject(projectDir, t);
40
41
 
41
42
  const bundleId = await readBundleId(projectDir);
42
- console.log(kleur.bold(`\n${t('ios.configure.title')}\n`));
43
+ printCompactHeader(t);
44
+ ui.intro(t('ios.configure.title'));
45
+
43
46
  if (bundleId) {
44
- console.log(` ${t('ios.configure.bundleId')}: ${kleur.cyan(bundleId)}`);
47
+ ui.log.info(`${t('ios.configure.bundleId')}: ${kleur.cyan(bundleId)}`);
45
48
  }
46
- console.log(kleur.dim(` ${t('ios.configure.doc')}: ${iosReleaseDocPath(options.language || detectDefaultLanguage())}\n`));
49
+ ui.log.message(kleur.dim(`${t('ios.configure.doc')}: ${iosReleaseDocPath(options.language || detectDefaultLanguage())}`));
47
50
 
48
- console.log(kleur.cyan(t('ios.configure.openingLinks')));
51
+ ui.log.info(t('ios.configure.openingLinks'));
49
52
  openUrl(URL_APP_STORE_CONNECT_API);
50
53
  openUrl(URL_APP_STORE_CONNECT_APPS);
51
54
 
52
- const response = await prompts(
53
- [
54
- {
55
- type: 'text',
56
- name: 'apiKey',
57
- message: t('ios.configure.q.apiKey'),
58
- validate: (v) => (v && v.trim().length > 0 ? true : t('ios.configure.q.required')),
59
- },
60
- {
61
- type: 'text',
62
- name: 'issuerId',
63
- message: t('ios.configure.q.issuerId'),
64
- validate: (v) => (v && v.trim().length > 0 ? true : t('ios.configure.q.required')),
65
- },
66
- {
67
- type: 'text',
68
- name: 'p8Path',
69
- message: t('ios.configure.q.p8Path'),
70
- validate: async (v) => {
71
- if (!v || !v.trim()) return t('ios.configure.q.required');
72
- const resolved = path.resolve(v.trim().replace(/^~/, process.env.HOME || ''));
73
- if (!(await fs.pathExists(resolved))) return t('ios.configure.q.p8NotFound');
74
- return true;
75
- },
76
- },
77
- {
78
- type: 'text',
79
- name: 'appId',
80
- message: t('ios.configure.q.appId'),
81
- initial: '',
82
- },
83
- ],
84
- { onCancel: () => { throw new Error(t('ios.configure.cancelled')); } }
85
- );
86
-
87
- const apiKey = response.apiKey.trim();
88
- const issuerId = response.issuerId.trim();
89
- const p8Source = path.resolve(response.p8Path.trim().replace(/^~/, process.env.HOME || ''));
55
+ const cancel = () => { ui.cancel(t('ios.configure.cancelled')); process.exit(0); };
56
+ const required = (v) => (v && String(v).trim().length > 0 ? undefined : t('ios.configure.q.required'));
57
+
58
+ const apiKeyRaw = await ui.text({
59
+ message: t('ios.configure.q.apiKey'),
60
+ validate: required,
61
+ onCancel: cancel,
62
+ });
63
+ const issuerIdRaw = await ui.text({
64
+ message: t('ios.configure.q.issuerId'),
65
+ validate: required,
66
+ onCancel: cancel,
67
+ });
68
+ const p8PathRaw = await ui.text({
69
+ message: t('ios.configure.q.p8Path'),
70
+ validate: async (v) => {
71
+ if (!v || !v.trim()) return t('ios.configure.q.required');
72
+ const resolved = path.resolve(v.trim().replace(/^~/, process.env.HOME || ''));
73
+ if (!(await fs.pathExists(resolved))) return t('ios.configure.q.p8NotFound');
74
+ return undefined;
75
+ },
76
+ onCancel: cancel,
77
+ });
78
+ const appIdRaw = await ui.text({
79
+ message: t('ios.configure.q.appId'),
80
+ initialValue: '',
81
+ onCancel: cancel,
82
+ });
83
+
84
+ const apiKey = apiKeyRaw.trim();
85
+ const issuerId = issuerIdRaw.trim();
86
+ const p8Source = path.resolve(p8PathRaw.trim().replace(/^~/, process.env.HOME || ''));
90
87
 
91
88
  const destPath = await installPrivateKey(p8Source, apiKey);
92
89
  await writeAppleEnv(projectDir, {
93
90
  apiKey,
94
91
  issuerId,
95
- appId: response.appId?.trim() || '',
92
+ appId: appIdRaw?.trim() || '',
96
93
  });
97
94
 
98
- console.log(kleur.green(`\n✓ ${t('ios.configure.success')}`));
99
- console.log(kleur.dim(` ${t('ios.configure.p8Installed')}: ${destPath}`));
100
- console.log(kleur.cyan(`\n ${t('ios.configure.next')}: kasy ios release\n`));
95
+ ui.log.success(t('ios.configure.success'));
96
+ ui.log.message(kleur.dim(`${t('ios.configure.p8Installed')}: ${destPath}`));
97
+ ui.outro(kleur.cyan(`${t('ios.configure.next')}: kasy ios release`));
101
98
  }
102
99
 
103
100
  async function runBuildOrRelease(directory, options = {}, mode) {
@@ -107,8 +104,9 @@ async function runBuildOrRelease(directory, options = {}, mode) {
107
104
  await assertProject(projectDir, t);
108
105
 
109
106
  if (mode === 'release' && process.platform !== 'darwin') {
110
- console.log(kleur.yellow(`\n${t('ios.error.notMac')}`));
111
- console.log(kleur.cyan(` ${t('ios.error.useCodemagic')}: kasy codemagic configure\n`));
107
+ printCompactHeader(t);
108
+ ui.log.warn(t('ios.error.notMac'));
109
+ ui.log.info(`${t('ios.error.useCodemagic')}: kasy codemagic configure`);
112
110
  process.exitCode = 1;
113
111
  return;
114
112
  }
@@ -116,8 +114,9 @@ async function runBuildOrRelease(directory, options = {}, mode) {
116
114
  if (process.platform === 'darwin') {
117
115
  const signing = await checkIosSigning(projectDir);
118
116
  if (!signing.ok) {
119
- console.log(kleur.yellow(`\n⚠ ${t('ios.error.signing')}`));
120
- console.log(kleur.dim(` ${t('ios.error.signingHint')}\n`));
117
+ printCompactHeader(t);
118
+ ui.log.warn(t('ios.error.signing'));
119
+ ui.log.message(kleur.dim(t('ios.error.signingHint')));
121
120
  process.exitCode = 1;
122
121
  return;
123
122
  }
@@ -126,9 +125,10 @@ async function runBuildOrRelease(directory, options = {}, mode) {
126
125
  if (mode === 'release') {
127
126
  const validation = await validateAppleSetup(projectDir);
128
127
  if (!validation.ok) {
129
- console.log(kleur.red(`\n✗ ${t('ios.error.notConfigured')}\n`));
128
+ printCompactHeader(t);
129
+ ui.log.error(t('ios.error.notConfigured'));
130
130
  if (validation.issues.includes('missing_env')) {
131
- console.log(kleur.dim(` ${t('ios.error.runConfigure')}\n`));
131
+ ui.log.message(kleur.dim(t('ios.error.runConfigure')));
132
132
  }
133
133
  process.exitCode = 1;
134
134
  return;
@@ -137,9 +137,10 @@ async function runBuildOrRelease(directory, options = {}, mode) {
137
137
 
138
138
  const googleScheme = await validateGoogleIosUrlScheme(projectDir);
139
139
  if (!googleScheme.ok) {
140
- console.log(kleur.red(`\n✗ ${t('ios.error.googleUrlScheme')}\n`));
141
- console.log(kleur.dim(` ${googleScheme.error}`));
142
- console.log(kleur.cyan(` ${t('ios.error.googleUrlSchemeHint')}\n`));
140
+ printCompactHeader(t);
141
+ ui.log.error(t('ios.error.googleUrlScheme'));
142
+ ui.log.message(kleur.dim(googleScheme.error));
143
+ ui.log.info(t('ios.error.googleUrlSchemeHint'));
143
144
  process.exitCode = 1;
144
145
  return;
145
146
  }
@@ -152,22 +153,19 @@ async function runBuildOrRelease(directory, options = {}, mode) {
152
153
  if (process.platform === 'darwin') {
153
154
  const disk = await checkDiskSpaceForIosBuild(projectDir);
154
155
  if (!disk.ok && disk.freeGb !== null) {
155
- console.log(
156
- kleur.yellow(
157
- `\n⚠ ${t('ios.warn.lowDisk', { gb: disk.freeGb, min: MIN_DISK_GB_FOR_IOS_BUILD })}\n`
158
- )
159
- );
156
+ ui.log.warn(t('ios.warn.lowDisk', { gb: disk.freeGb, min: MIN_DISK_GB_FOR_IOS_BUILD }));
160
157
  }
161
158
  }
162
159
 
163
- console.log(kleur.bold(`\n${mode === 'build' ? t('ios.build.title') : t('ios.release.title')}\n`));
160
+ printCompactHeader(t);
161
+ ui.intro(mode === 'build' ? t('ios.build.title') : t('ios.release.title'));
164
162
 
165
163
  try {
166
164
  await runReleaseScript(projectDir, args, t);
167
- console.log(kleur.green(`\n✓ ${mode === 'build' ? t('ios.build.success') : t('ios.release.success')}\n`));
165
+ ui.outro(mode === 'build' ? t('ios.build.success') : t('ios.release.success'));
168
166
  } catch (err) {
169
167
  const output = err.buildOutput || err.message;
170
- console.error(kleur.red(`\n✗ ${output.slice(-4000)}\n`));
168
+ ui.log.error(output.slice(-4000));
171
169
  if (isXcodeCacheBuildError(output)) {
172
170
  printBuildFailureHints(t, projectDir);
173
171
  }
@@ -182,18 +180,20 @@ async function runClean(directory, options = {}) {
182
180
  await assertProject(projectDir, t);
183
181
 
184
182
  if (process.platform !== 'darwin') {
185
- console.log(kleur.yellow(`\n${t('ios.clean.notMac')}\n`));
183
+ printCompactHeader(t);
184
+ ui.log.warn(t('ios.clean.notMac'));
186
185
  process.exitCode = 1;
187
186
  return;
188
187
  }
189
188
 
190
- console.log(kleur.bold(`\n${t('ios.clean.title')}\n`));
189
+ printCompactHeader(t);
190
+ ui.intro(t('ios.clean.title'));
191
191
  try {
192
192
  await runIosClean(projectDir, t);
193
- console.log(kleur.green(`\n✓ ${t('ios.clean.success')}\n`));
194
- console.log(kleur.cyan(` ${t('ios.configure.next')}: kasy ios release\n`));
193
+ ui.log.success(t('ios.clean.success'));
194
+ ui.outro(kleur.cyan(`${t('ios.configure.next')}: kasy ios release`));
195
195
  } catch (err) {
196
- console.error(kleur.red(`\n✗ ${err.message}\n`));
196
+ ui.log.error(err.message);
197
197
  process.exitCode = 1;
198
198
  }
199
199
  }