kasy-cli 1.5.3 → 1.6.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,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
 
@@ -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 };