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.
@@ -17,15 +17,16 @@
17
17
  const path = require('node:path');
18
18
  const fs = require('fs-extra');
19
19
  const kleur = require('kleur');
20
- const oraPackage = require('ora');
20
+ const ui = require('../utils/ui');
21
+ const { printCompactHeader } = require('../utils/brand');
21
22
  const { exec } = require('node:child_process');
22
23
  const { promisify } = require('node:util');
23
24
 
24
25
  const { getStoredLanguage } = require('../utils/license');
26
+ const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
25
27
  const { createFcmServiceAccountKey } = require('../scaffold/shared/fcm-service-account');
26
28
  const { setSupabaseSecrets } = require('../scaffold/backends/supabase/deploy');
27
29
 
28
- const ora = oraPackage.default || oraPackage;
29
30
  const execAsync = promisify(exec);
30
31
 
31
32
  // ── Helpers ───────────────────────────────────────────────────────────────────
@@ -39,10 +40,10 @@ async function runCmd(cmd, cwd) {
39
40
  }
40
41
  }
41
42
 
42
- function ok(label, detail) { console.log(` ${kleur.green('✓')} ${label}${detail ? kleur.gray(` — ${detail}`) : ''}`); }
43
- function fail(label, detail) { console.log(` ${kleur.red('✗')} ${kleur.red(label)}${detail ? kleur.gray(` — ${detail}`) : ''}`); }
44
- function warn(label, detail) { console.log(` ${kleur.yellow('⚠')} ${kleur.yellow(label)}${detail ? kleur.gray(` — ${detail}`) : ''}`); }
45
- function info(label) { console.log(` ${kleur.gray('–')} ${kleur.gray(label)}`); }
43
+ function ok(label, detail) { ui.log.success(`${label}${detail ? kleur.gray(` — ${detail}`) : ''}`); }
44
+ function fail(label, detail) { ui.log.error(`${label}${detail ? kleur.gray(` — ${detail}`) : ''}`); }
45
+ function warn(label, detail) { ui.log.warn(`${label}${detail ? kleur.gray(` — ${detail}`) : ''}`); }
46
+ function info(label) { ui.log.message(kleur.dim(`– ${label}`)); }
46
47
 
47
48
  /**
48
49
  * Read the linked Supabase project ref.
@@ -105,8 +106,11 @@ async function listDeployedFunctions(projectDir) {
105
106
  */
106
107
  async function runCheck(options = {}) {
107
108
  const projectDir = path.resolve(options.directory || '.');
109
+ const lang = options.language || getStoredLanguage() || detectDefaultLanguage();
110
+ const t = createTranslator(lang);
108
111
 
109
- console.log(kleur.bold(`\n Kasy Check — Push Notifications\n`));
112
+ printCompactHeader(t);
113
+ ui.intro('Kasy Check — Push Notifications');
110
114
 
111
115
  // ── Detect backend ────────────────────────────────────────────────────────
112
116
  const isFirebase = await fs.pathExists(path.join(projectDir, 'firebase.json'));
@@ -115,20 +119,22 @@ async function runCheck(options = {}) {
115
119
  if (isFirebase && !isSupabase) {
116
120
  ok('Firebase backend');
117
121
  info('Firebase usa Application Default Credentials — nenhuma configuração extra necessária.');
118
- console.log('');
119
- // APNs Key is still required for iOS push — show reminder even for Firebase backend.
120
122
  const firebaseProjectId = await readFirebaseProjectId(projectDir);
121
- console.log(kleur.bold().yellow(` ⚠ Push iOS requer APNs Key (não verificável via CLI)`));
122
- console.log(kleur.gray(` Firebase Console Cloud Messaging app iOS → Chave de autenticação APNs`));
123
+ const apnsLines = [
124
+ kleur.yellow('Push iOS requer APNs Key (não verificável via CLI)'),
125
+ kleur.dim('Firebase Console → Cloud Messaging → app iOS → Chave de autenticação APNs'),
126
+ ];
123
127
  if (firebaseProjectId) {
124
- console.log(kleur.cyan(` https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
128
+ apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
125
129
  }
126
- console.log('');
130
+ ui.note(apnsLines.join('\n'));
131
+ ui.outro('Done');
127
132
  return;
128
133
  }
129
134
 
130
135
  if (!isSupabase) {
131
- console.error(kleur.red(` ✗ Diretório não parece ser um projeto Kasy.\n`));
136
+ ui.log.error('Diretório não parece ser um projeto Kasy.');
137
+ ui.cancel('Aborted');
132
138
  process.exit(1);
133
139
  }
134
140
 
@@ -136,15 +142,16 @@ async function runCheck(options = {}) {
136
142
  const projectRef = await readProjectRef(projectDir);
137
143
  if (!projectRef) {
138
144
  fail('Projeto Supabase não vinculado', 'execute: supabase link --project-ref SEU_REF');
139
- console.log('');
145
+ ui.cancel('Aborted');
140
146
  process.exit(1);
141
147
  }
142
148
  ok('Projeto vinculado', projectRef);
143
149
 
144
150
  // ── Read secrets list ─────────────────────────────────────────────────────
145
- const spinner = ora(kleur.gray(' Verificando secrets…')).start();
151
+ const spinner = ui.spinner();
152
+ spinner.start('Verificando secrets…');
146
153
  const secrets = await listSecretNames(projectDir);
147
- spinner.stop();
154
+ spinner.stop('Secrets verificados');
148
155
 
149
156
  if (!secrets) {
150
157
  warn('Não foi possível listar secrets', 'verifique: supabase login');
@@ -158,7 +165,7 @@ async function runCheck(options = {}) {
158
165
  } else {
159
166
  fail('FIREBASE_PROJECT_ID ausente');
160
167
  if (firebaseProjectId) {
161
- console.log(kleur.gray(` Corrija: supabase secrets set FIREBASE_PROJECT_ID="${firebaseProjectId}"`));
168
+ ui.log.message(kleur.gray(`Corrija: supabase secrets set FIREBASE_PROJECT_ID="${firebaseProjectId}"`));
162
169
  if (options.fix) {
163
170
  const r = await runCmd(`supabase secrets set FIREBASE_PROJECT_ID="${firebaseProjectId}"`, projectDir);
164
171
  r.ok
@@ -177,9 +184,10 @@ async function runCheck(options = {}) {
177
184
  fail('FIREBASE_SERVICE_ACCOUNT_JSON ausente');
178
185
 
179
186
  if (options.fix && firebaseProjectId) {
180
- const fixSpinner = ora(kleur.cyan(' Gerando e configurando chave FCM…')).start();
187
+ const fixSpinner = ui.spinner();
188
+ fixSpinner.start('Gerando e configurando chave FCM…');
181
189
  const fcmResult = await createFcmServiceAccountKey(firebaseProjectId);
182
- fixSpinner.stop();
190
+ fixSpinner.stop('Chave FCM gerada');
183
191
 
184
192
  if (fcmResult.ok) {
185
193
  const secretSteps = await setSupabaseSecrets(projectDir, {
@@ -192,21 +200,26 @@ async function runCheck(options = {}) {
192
200
  : fail('FIREBASE_SERVICE_ACCOUNT_JSON → falhou ao configurar', setStep?.error);
193
201
  } else {
194
202
  fail('FIREBASE_SERVICE_ACCOUNT_JSON → não foi possível gerar chave', fcmResult.error);
195
- console.log(kleur.gray(` Manual: Firebase Console → Configurações → Contas de serviço → Gerar chave`));
196
- console.log(kleur.gray(` Depois: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='$(cat chave.json)'`));
203
+ ui.log.message(kleur.gray(
204
+ 'Manual: Firebase Console Configurações → Contas de serviço → Gerar chave\n' +
205
+ "Depois: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='$(cat chave.json)'"
206
+ ));
197
207
  }
198
208
  } else {
199
- console.log(kleur.gray(` Corrija automaticamente: kasy check --fix`));
200
- console.log(kleur.gray(` Ou manualmente: Firebase Console Configurações → Contas de serviço → Gerar chave`));
201
- console.log(kleur.gray(` Depois: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='$(cat chave.json)'`));
209
+ ui.log.message(kleur.gray(
210
+ 'Corrija automaticamente: kasy check --fix\n' +
211
+ 'Ou manualmente: Firebase Console Configurações → Contas de serviço → Gerar chave\n' +
212
+ "Depois: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='$(cat chave.json)'"
213
+ ));
202
214
  }
203
215
  }
204
216
  }
205
217
 
206
218
  // ── 4. Edge function send-push-notification ────────────────────────────────
207
- const fnSpinner = ora(kleur.gray(' Verificando edge functions…')).start();
219
+ const fnSpinner = ui.spinner();
220
+ fnSpinner.start('Verificando edge functions…');
208
221
  const functions = await listDeployedFunctions(projectDir);
209
- fnSpinner.stop();
222
+ fnSpinner.stop('Edge functions verificadas');
210
223
 
211
224
  if (!functions) {
212
225
  warn('Não foi possível listar edge functions', 'verifique: supabase login');
@@ -215,26 +228,30 @@ async function runCheck(options = {}) {
215
228
  } else {
216
229
  fail('Edge function send-push-notification não deployada');
217
230
  if (options.fix) {
218
- const deploySpinner = ora(kleur.cyan(' Publicando send-push-notification…')).start();
231
+ const deploySpinner = ui.spinner();
232
+ deploySpinner.start('Publicando send-push-notification…');
219
233
  const r = await runCmd('supabase functions deploy send-push-notification', projectDir);
220
- deploySpinner.stop();
221
234
  r.ok
222
- ? ok('send-push-notification → deployada automaticamente')
223
- : fail('send-push-notification → falhou no deploy', r.error);
235
+ ? deploySpinner.stop('send-push-notification → deployada automaticamente')
236
+ : deploySpinner.error(`send-push-notification → falhou no deploy${r.error ? ` — ${r.error}` : ''}`);
224
237
  } else {
225
- console.log(kleur.gray(` Corrija automaticamente: kasy check --fix`));
226
- console.log(kleur.gray(` Ou manualmente: supabase functions deploy send-push-notification`));
238
+ ui.log.message(kleur.gray(
239
+ 'Corrija automaticamente: kasy check --fix\n' +
240
+ 'Ou manualmente: supabase functions deploy send-push-notification'
241
+ ));
227
242
  }
228
243
  }
229
244
 
230
245
  // ── 5. APNs reminder ─────────────────────────────────────────────────────
231
- console.log('');
232
- console.log(kleur.bold().yellow(` ⚠ Push iOS requer APNs Key (não verificável via CLI)`));
233
- console.log(kleur.gray(` Firebase Console → Cloud Messaging → app iOS → Chave de autenticação APNs`));
246
+ const apnsLines = [
247
+ kleur.yellow('Push iOS requer APNs Key (não verificável via CLI)'),
248
+ kleur.dim('Firebase Console → Cloud Messaging → app iOS → Chave de autenticação APNs'),
249
+ ];
234
250
  if (firebaseProjectId) {
235
- console.log(kleur.cyan(` https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
251
+ apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
236
252
  }
237
- console.log('');
253
+ ui.note(apnsLines.join('\n'));
254
+ ui.outro('Done');
238
255
  }
239
256
 
240
257
  module.exports = { runCheck };
@@ -2,7 +2,8 @@
2
2
 
3
3
  const path = require('node:path');
4
4
  const kleur = require('kleur');
5
- const prompts = require('prompts');
5
+ const ui = require('../utils/ui');
6
+ const { printCompactHeader } = require('../utils/brand');
6
7
  const { createTranslator, detectDefaultLanguage } = require('../utils/i18n');
7
8
  const { isKasyFlutterProject, openUrl } = require('../utils/apple-release');
8
9
  const { validateGoogleIosUrlScheme } = require('../scaffold/shared/post-build');
@@ -29,53 +30,48 @@ async function runConfigure(directory, options = {}) {
29
30
 
30
31
  await assertProject(projectDir, t);
31
32
 
32
- console.log(kleur.bold(`\n${t('codemagic.configure.title')}\n`));
33
- console.log(kleur.dim(` ${t('codemagic.configure.doc')}: ${codemagicReleaseDocPath(lang)}\n`));
34
- console.log(kleur.yellow(` ${t('codemagic.configure.checklist')}\n`));
33
+ printCompactHeader(t);
34
+ ui.intro(t('codemagic.configure.title'));
35
+ ui.log.message(kleur.dim(`${t('codemagic.configure.doc')}: ${codemagicReleaseDocPath(lang)}`));
36
+ ui.log.warn(t('codemagic.configure.checklist'));
35
37
 
36
- console.log(kleur.cyan(t('codemagic.configure.openingLinks')));
38
+ ui.log.info(t('codemagic.configure.openingLinks'));
37
39
  openUrl(URL_CODEMAGIC_API);
38
40
  openUrl(URL_CODEMAGIC_APPS);
39
41
 
40
- const response = await prompts(
41
- [
42
- {
43
- type: 'password',
44
- name: 'token',
45
- message: t('codemagic.configure.q.token'),
46
- validate: (v) => (v && v.trim().length > 0 ? true : t('codemagic.configure.q.required')),
47
- },
48
- {
49
- type: 'text',
50
- name: 'appId',
51
- message: t('codemagic.configure.q.appId'),
52
- validate: (v) => (v && v.trim().length > 0 ? true : t('codemagic.configure.q.required')),
53
- },
54
- {
55
- type: 'text',
56
- name: 'workflowId',
57
- message: t('codemagic.configure.q.workflowId'),
58
- initial: 'ios-workflow',
59
- },
60
- {
61
- type: 'text',
62
- name: 'branch',
63
- message: t('codemagic.configure.q.branch'),
64
- initial: 'main',
65
- },
66
- ],
67
- { onCancel: () => { throw new Error(t('codemagic.configure.cancelled')); } }
68
- );
42
+ const cancel = () => { ui.cancel(t('codemagic.configure.cancelled')); process.exit(0); };
43
+ const required = (v) => (v && String(v).trim().length > 0 ? undefined : t('codemagic.configure.q.required'));
44
+
45
+ const token = await ui.password({
46
+ message: t('codemagic.configure.q.token'),
47
+ validate: required,
48
+ onCancel: cancel,
49
+ });
50
+ const appId = await ui.text({
51
+ message: t('codemagic.configure.q.appId'),
52
+ validate: required,
53
+ onCancel: cancel,
54
+ });
55
+ const workflowId = await ui.text({
56
+ message: t('codemagic.configure.q.workflowId'),
57
+ initialValue: 'ios-workflow',
58
+ onCancel: cancel,
59
+ });
60
+ const branch = await ui.text({
61
+ message: t('codemagic.configure.q.branch'),
62
+ initialValue: 'main',
63
+ onCancel: cancel,
64
+ });
69
65
 
70
66
  await writeCodemagicEnv(projectDir, {
71
- token: response.token.trim(),
72
- appId: response.appId.trim(),
73
- workflowId: response.workflowId.trim() || 'ios-workflow',
74
- branch: response.branch.trim() || 'main',
67
+ token: String(token).trim(),
68
+ appId: String(appId).trim(),
69
+ workflowId: String(workflowId).trim() || 'ios-workflow',
70
+ branch: String(branch).trim() || 'main',
75
71
  });
76
72
 
77
- console.log(kleur.green(`\n✓ ${t('codemagic.configure.success')}`));
78
- console.log(kleur.cyan(`\n ${t('codemagic.configure.next')}: kasy codemagic release\n`));
73
+ ui.log.success(t('codemagic.configure.success'));
74
+ ui.outro(kleur.cyan(`${t('codemagic.configure.next')}: kasy codemagic release`));
79
75
  }
80
76
 
81
77
  async function runRelease(directory, options = {}) {
@@ -86,12 +82,13 @@ async function runRelease(directory, options = {}) {
86
82
 
87
83
  const validation = await validateCodemagicSetup(projectDir);
88
84
  if (!validation.ok) {
89
- console.log(kleur.red(`\n✗ ${t('codemagic.error.notConfigured')}\n`));
85
+ printCompactHeader(t);
86
+ ui.log.error(t('codemagic.error.notConfigured'));
90
87
  if (validation.issues.includes('missing_yaml')) {
91
- console.log(kleur.dim(` ${t('codemagic.error.addCi')}\n`));
88
+ ui.log.message(kleur.dim(t('codemagic.error.addCi')));
92
89
  }
93
90
  if (validation.issues.includes('missing_env')) {
94
- console.log(kleur.dim(` ${t('codemagic.error.runConfigure')}\n`));
91
+ ui.log.message(kleur.dim(t('codemagic.error.runConfigure')));
95
92
  }
96
93
  process.exitCode = 1;
97
94
  return;
@@ -99,27 +96,30 @@ async function runRelease(directory, options = {}) {
99
96
 
100
97
  const googleScheme = await validateGoogleIosUrlScheme(projectDir);
101
98
  if (!googleScheme.ok) {
102
- console.log(kleur.red(`\n✗ ${t('codemagic.error.googleUrlScheme')}\n`));
103
- console.log(kleur.dim(` ${googleScheme.error}`));
104
- console.log(kleur.cyan(` ${t('ios.error.googleUrlSchemeHint')}\n`));
99
+ printCompactHeader(t);
100
+ ui.log.error(t('codemagic.error.googleUrlScheme'));
101
+ ui.log.message(kleur.dim(googleScheme.error));
102
+ ui.log.info(t('ios.error.googleUrlSchemeHint'));
105
103
  process.exitCode = 1;
106
104
  return;
107
105
  }
108
106
 
109
- console.log(kleur.bold(`\n${t('codemagic.release.title')}\n`));
107
+ printCompactHeader(t);
108
+ ui.intro(t('codemagic.release.title'));
110
109
 
111
110
  try {
112
111
  const result = await triggerBuild(projectDir, validation.env);
113
112
  const buildId = result.buildId || result._id;
114
113
  if (buildId) {
115
- console.log(kleur.green(`✓ ${t('codemagic.release.triggered')}`));
116
- console.log(kleur.cyan(` Build ID: ${buildId}`));
117
- console.log(kleur.dim(` https://codemagic.io/builds/${buildId}\n`));
114
+ ui.log.success(t('codemagic.release.triggered'));
115
+ ui.log.info(`Build ID: ${kleur.cyan(buildId)}`);
116
+ ui.log.message(kleur.dim(`https://codemagic.io/builds/${buildId}`));
117
+ ui.outro('');
118
118
  } else {
119
- console.log(kleur.green(`✓ ${t('codemagic.release.triggered')}\n`));
119
+ ui.outro(t('codemagic.release.triggered'));
120
120
  }
121
121
  } catch (err) {
122
- console.error(kleur.red(`\n✗ ${err.message}\n`));
122
+ ui.log.error(err.message);
123
123
  process.exitCode = 1;
124
124
  }
125
125
  }
@@ -132,24 +132,27 @@ async function runStatus(buildId, directory, options = {}) {
132
132
 
133
133
  const validation = await validateCodemagicSetup(projectDir);
134
134
  if (!validation.env?.CODEMAGIC_API_TOKEN) {
135
- console.log(kleur.red(`\n✗ ${t('codemagic.error.runConfigure')}\n`));
135
+ printCompactHeader(t);
136
+ ui.log.error(t('codemagic.error.runConfigure'));
136
137
  process.exitCode = 1;
137
138
  return;
138
139
  }
139
140
 
140
141
  if (!buildId) {
141
- console.log(kleur.yellow(`\n${t('codemagic.status.usage')}\n`));
142
+ printCompactHeader(t);
143
+ ui.log.warn(t('codemagic.status.usage'));
142
144
  process.exitCode = 1;
143
145
  return;
144
146
  }
145
147
 
146
148
  try {
147
149
  const result = await getBuildStatus(buildId, validation.env.CODEMAGIC_API_TOKEN);
148
- console.log(kleur.bold(`\n${t('codemagic.status.title')}: ${buildId}\n`));
149
- console.log(JSON.stringify(result, null, 2));
150
- console.log('');
150
+ printCompactHeader(t);
151
+ ui.intro(`${t('codemagic.status.title')}: ${buildId}`);
152
+ ui.note(JSON.stringify(result, null, 2));
153
+ ui.outro('');
151
154
  } catch (err) {
152
- console.error(kleur.red(`\n✗ ${err.message}\n`));
155
+ ui.log.error(err.message);
153
156
  process.exitCode = 1;
154
157
  }
155
158
  }
@@ -16,8 +16,8 @@
16
16
  const path = require('node:path');
17
17
  const fs = require('fs-extra');
18
18
  const kleur = require('kleur');
19
- const prompts = require('prompts');
20
- const oraPackage = require('ora');
19
+ const ui = require('../utils/ui');
20
+ const { printCompactHeader } = require('../utils/brand');
21
21
 
22
22
  const { createTranslator } = require('../utils/i18n');
23
23
  const { getStoredLanguage } = require('../utils/license');
@@ -25,14 +25,13 @@ const { runDeploy: runFirebaseDeploy } = require('../scaffold/backends/firebase/
25
25
  const { deployFunctions, setSupabaseSecrets, linkProject } = require('../scaffold/backends/supabase/deploy');
26
26
  const { createFcmServiceAccountKey } = require('../scaffold/shared/fcm-service-account');
27
27
 
28
- const ora = oraPackage.default || oraPackage;
29
-
30
28
  // ── Helpers ───────────────────────────────────────────────────────────────────
31
29
 
32
30
  function printStep(ok, skipped, label, detail) {
33
- const icon = ok ? kleur.green('✓') : skipped ? kleur.gray('') : kleur.red('');
34
- const msg = ok ? label : skipped ? kleur.gray(label) : kleur.red(label);
35
- console.log(` ${icon} ${msg}${detail ? kleur.gray(` ${detail.split('\n')[0]}`) : ''}`);
31
+ const text = `${label}${detail ? kleur.gray(` ${detail.split('\n')[0]}`) : ''}`;
32
+ if (ok) ui.log.success(text);
33
+ else if (skipped) ui.log.message(kleur.dim(`– ${text}`));
34
+ else ui.log.error(text);
36
35
  }
37
36
 
38
37
  /**
@@ -84,7 +83,7 @@ async function isServiceAccountJsonSet(projectDir) {
84
83
  // ── Firebase deploy ───────────────────────────────────────────────────────────
85
84
 
86
85
  async function deployFirebase(projectDir, options, tr) {
87
- const cancel = () => { console.log(''); process.exit(0); };
86
+ const cancel = () => { ui.cancel('Cancelled'); process.exit(0); };
88
87
 
89
88
  // Auto-detect Firebase Project ID
90
89
  let firebaseProjectId = options.project;
@@ -96,17 +95,17 @@ async function deployFirebase(projectDir, options, tr) {
96
95
  const detected = rc.projects?.default;
97
96
  if (detected) {
98
97
  firebaseProjectId = detected;
99
- console.log(kleur.gray(` ${tr('deploy.detected.project')} ${kleur.cyan(detected)}`));
98
+ ui.log.message(kleur.gray(`${tr('deploy.detected.project')} ${kleur.cyan(detected)}`));
100
99
  }
101
100
  } catch (_) {}
102
101
  }
103
102
  }
104
103
  if (!firebaseProjectId) {
105
- const { pid } = await prompts(
106
- { type: 'text', name: 'pid', message: tr('deploy.q.project') },
107
- { onCancel: cancel }
108
- );
109
- firebaseProjectId = pid?.trim();
104
+ const pid = await ui.text({
105
+ message: tr('deploy.q.project'),
106
+ onCancel: cancel,
107
+ });
108
+ firebaseProjectId = String(pid || '').trim();
110
109
  if (!firebaseProjectId) process.exit(0);
111
110
  }
112
111
 
@@ -123,50 +122,48 @@ async function deployFirebase(projectDir, options, tr) {
123
122
  }
124
123
  }
125
124
 
126
- console.log('');
127
- const spinner = ora(kleur.cyan('Deploying Firebase...')).start();
125
+ const spinner = ui.spinner();
126
+ spinner.start('Deploying Firebase...');
128
127
  let steps;
129
128
  try {
130
129
  steps = await runFirebaseDeploy(projectDir, null, firebaseProjectId, {
131
130
  functionsRegion,
132
- onProgress: (key) => { spinner.text = kleur.cyan(key); },
131
+ onProgress: (key) => { spinner.message(String(key)); },
133
132
  });
134
- spinner.stop();
133
+ spinner.stop('Firebase deploy completed');
135
134
  } catch (err) {
136
- spinner.fail(kleur.red(err.message));
135
+ spinner.error(err.message);
137
136
  throw err;
138
137
  }
139
138
 
140
139
  if (steps?.length) {
141
- console.log('');
142
140
  for (const s of steps) {
143
141
  printStep(s.ok, s.skipped, s.name, s.detail);
144
142
  }
145
- console.log('');
146
143
  }
147
144
 
148
145
  // ── APNs reminder — required for iOS push notifications ──────────────────
149
146
  // Cannot be automated: the .p8 key only exists in Apple Developer Portal.
150
- console.log(kleur.bold().yellow(` ⚠ Push iOS: configure a APNs Key no Firebase Console`));
151
- console.log(kleur.gray(` 1. Apple Developer Portal Keys criar APNs Key (.p8)`));
152
- console.log(kleur.gray(` 2. Firebase Console Cloud Messaging app iOS upload da APNs Key`));
153
- console.log(kleur.cyan(` https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
154
- console.log('');
147
+ const apnsBody = [
148
+ kleur.yellow('Push iOS: configure a APNs Key no Firebase Console'),
149
+ kleur.dim('1. Apple Developer PortalKeyscriar APNs Key (.p8)'),
150
+ kleur.dim('2. Firebase Console → Cloud Messaging → app iOS → upload da APNs Key'),
151
+ kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`),
152
+ ].join('\n');
153
+ ui.note(apnsBody);
155
154
  }
156
155
 
157
156
  // ── Supabase deploy ───────────────────────────────────────────────────────────
158
157
 
159
158
  async function deploySupabase(projectDir) {
160
- console.log('');
161
-
162
159
  // ── 1. Get project ref ──────────────────────────────────────────────────
163
160
  const projectRef = await readSupabaseProjectRef(projectDir);
164
161
  if (!projectRef) {
165
- console.error(kleur.red(`\n ✗ Projeto Supabase não está vinculado neste diretório.`));
166
- console.error(kleur.gray(` Execute: supabase link --project-ref SEU_PROJECT_REF`));
162
+ ui.log.error('Projeto Supabase não está vinculado neste diretório.');
163
+ ui.log.message(kleur.gray('Execute: supabase link --project-ref SEU_PROJECT_REF'));
167
164
  process.exit(1);
168
165
  }
169
- console.log(kleur.gray(` Project ref: ${kleur.cyan(projectRef)}`));
166
+ ui.log.message(kleur.gray(`Project ref: ${kleur.cyan(projectRef)}`));
170
167
 
171
168
  // ── 2. FCM Service Account JSON ─────────────────────────────────────────
172
169
  const alreadySet = await isServiceAccountJsonSet(projectDir);
@@ -176,12 +173,12 @@ async function deploySupabase(projectDir) {
176
173
  const firebaseProjectId = await readFirebaseProjectId(projectDir);
177
174
 
178
175
  if (firebaseProjectId) {
179
- const fcmSpinner = ora(kleur.cyan('Gerando chave FCM (Service Account)…')).start();
176
+ const fcmSpinner = ui.spinner();
177
+ fcmSpinner.start('Gerando chave FCM (Service Account)…');
180
178
  const fcmResult = await createFcmServiceAccountKey(firebaseProjectId);
181
- fcmSpinner.stop();
179
+ fcmSpinner.stop('Chave FCM gerada');
182
180
 
183
181
  if (fcmResult.ok) {
184
- // Set the secret via setSupabaseSecrets (reuses existing logic with JSON compaction)
185
182
  const secretSteps = await setSupabaseSecrets(projectDir, {
186
183
  firebaseProjectId,
187
184
  firebaseServiceAccountJson: fcmResult.json,
@@ -189,7 +186,7 @@ async function deploySupabase(projectDir) {
189
186
  for (const s of secretSteps) printStep(s.ok, false, s.name, s.error);
190
187
  } else {
191
188
  printStep(false, false, 'FIREBASE_SERVICE_ACCOUNT_JSON', fcmResult.error);
192
- console.log(kleur.yellow(` Configure manualmente: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='...'`));
189
+ ui.log.warn("Configure manualmente: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='...'");
193
190
  }
194
191
  } else {
195
192
  printStep(false, false, 'FIREBASE_SERVICE_ACCOUNT_JSON', 'google-services.json não encontrado — configure manualmente');
@@ -197,9 +194,10 @@ async function deploySupabase(projectDir) {
197
194
  }
198
195
 
199
196
  // ── 3. Deploy edge functions ────────────────────────────────────────────
200
- const fnSpinner = ora(kleur.cyan('Publicando edge functions…')).start();
197
+ const fnSpinner = ui.spinner();
198
+ fnSpinner.start('Publicando edge functions…');
201
199
  const fnResult = await deployFunctions(projectDir);
202
- fnSpinner.stop();
200
+ fnSpinner.stop('Edge functions processadas');
203
201
 
204
202
  if (Array.isArray(fnResult)) {
205
203
  fnResult.forEach((s) => printStep(s.ok, s.skipped, s.name, s.error));
@@ -211,14 +209,15 @@ async function deploySupabase(projectDir) {
211
209
 
212
210
  // ── 4. APNs reminder ────────────────────────────────────────────────────
213
211
  const firebaseProjectId = await readFirebaseProjectId(projectDir);
214
- console.log('');
215
- console.log(kleur.bold().yellow(` ⚠ Push iOS: configure a APNs Key no Firebase Console`));
216
- console.log(kleur.gray(` 1. Apple Developer Portal → Keys → criar APNs Key (.p8)`));
217
- console.log(kleur.gray(` 2. Firebase Console → Cloud Messaging → app iOS → upload da APNs Key`));
212
+ const apnsLines = [
213
+ kleur.yellow('Push iOS: configure a APNs Key no Firebase Console'),
214
+ kleur.dim('1. Apple Developer Portal → Keys → criar APNs Key (.p8)'),
215
+ kleur.dim('2. Firebase Console → Cloud Messaging → app iOS → upload da APNs Key'),
216
+ ];
218
217
  if (firebaseProjectId) {
219
- console.log(kleur.cyan(` https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
218
+ apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
220
219
  }
221
- console.log('');
220
+ ui.note(apnsLines.join('\n'));
222
221
  }
223
222
 
224
223
  // ── Entry point ───────────────────────────────────────────────────────────────
@@ -240,17 +239,22 @@ async function runDeployCommand(directory, options = {}, { language: langHint }
240
239
  const isSupabase = await fs.pathExists(path.join(projectDir, 'supabase'));
241
240
 
242
241
  if (isFirebase) {
242
+ printCompactHeader(tr);
243
+ ui.intro('Deploy — Firebase');
243
244
  await deployFirebase(projectDir, options, tr);
245
+ ui.outro('Deploy concluído');
244
246
  return;
245
247
  }
246
248
 
247
249
  if (isSupabase) {
248
- console.log(kleur.bold(`\n Deploy — Supabase\n`));
250
+ printCompactHeader(tr);
251
+ ui.intro('Deploy — Supabase');
249
252
  await deploySupabase(projectDir);
253
+ ui.outro('Deploy concluído');
250
254
  return;
251
255
  }
252
256
 
253
- console.error(kleur.red(`\n ✗ ${tr('deploy.error.notProject')}\n`));
257
+ ui.log.error(tr('deploy.error.notProject'));
254
258
  process.exit(1);
255
259
  }
256
260