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.
- package/bin/kasy.js +7 -4
- package/docs/cli-reference.md +4 -6
- package/lib/commands/add.js +128 -102
- package/lib/commands/check.js +55 -38
- package/lib/commands/codemagic.js +61 -58
- package/lib/commands/deploy.js +49 -45
- package/lib/commands/docs.js +19 -18
- package/lib/commands/doctor.js +46 -44
- package/lib/commands/features.js +42 -20
- package/lib/commands/ios.js +69 -69
- package/lib/commands/new.js +529 -771
- package/lib/commands/notifications.js +59 -59
- package/lib/commands/remove.js +28 -27
- package/lib/commands/run.js +3 -1
- package/lib/commands/update.js +104 -96
- package/lib/commands/validate.js +24 -19
- package/lib/scaffold/catalog.js +45 -11
- package/lib/scaffold/features/README.md +1 -2
- package/lib/scaffold/shared/generator-utils.js +1 -1
- package/lib/utils/apple-release.js +23 -11
- package/lib/utils/brand.js +72 -0
- package/lib/utils/checks.js +20 -9
- package/lib/utils/i18n.js +102 -78
- package/lib/utils/prompts.js +29 -177
- package/lib/utils/ui.js +92 -0
- package/lib/utils/updates.js +9 -8
- package/package.json +2 -1
- package/templates/firebase/lib/features/home/home_page.dart +0 -19
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +0 -1
- package/templates/firebase/lib/router.dart +0 -9
- package/templates/firebase/lib/features/dev/keyboard_test_page.dart +0 -93
package/lib/commands/check.js
CHANGED
|
@@ -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
|
|
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)
|
|
43
|
-
function fail(label, detail)
|
|
44
|
-
function warn(label, detail)
|
|
45
|
-
function info(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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
128
|
+
apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
|
|
125
129
|
}
|
|
126
|
-
|
|
130
|
+
ui.note(apnsLines.join('\n'));
|
|
131
|
+
ui.outro('Done');
|
|
127
132
|
return;
|
|
128
133
|
}
|
|
129
134
|
|
|
130
135
|
if (!isSupabase) {
|
|
131
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
196
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
?
|
|
223
|
-
:
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
251
|
+
apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
|
|
236
252
|
}
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
+
ui.log.info(t('codemagic.configure.openingLinks'));
|
|
37
39
|
openUrl(URL_CODEMAGIC_API);
|
|
38
40
|
openUrl(URL_CODEMAGIC_APPS);
|
|
39
41
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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:
|
|
72
|
-
appId:
|
|
73
|
-
workflowId:
|
|
74
|
-
branch:
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
85
|
+
printCompactHeader(t);
|
|
86
|
+
ui.log.error(t('codemagic.error.notConfigured'));
|
|
90
87
|
if (validation.issues.includes('missing_yaml')) {
|
|
91
|
-
|
|
88
|
+
ui.log.message(kleur.dim(t('codemagic.error.addCi')));
|
|
92
89
|
}
|
|
93
90
|
if (validation.issues.includes('missing_env')) {
|
|
94
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
119
|
+
ui.outro(t('codemagic.release.triggered'));
|
|
120
120
|
}
|
|
121
121
|
} catch (err) {
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
155
|
+
ui.log.error(err.message);
|
|
153
156
|
process.exitCode = 1;
|
|
154
157
|
}
|
|
155
158
|
}
|
package/lib/commands/deploy.js
CHANGED
|
@@ -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
|
|
20
|
-
const
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
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 = () => {
|
|
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
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
);
|
|
109
|
-
firebaseProjectId = pid
|
|
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
|
-
|
|
127
|
-
|
|
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.
|
|
131
|
+
onProgress: (key) => { spinner.message(String(key)); },
|
|
133
132
|
});
|
|
134
|
-
spinner.stop();
|
|
133
|
+
spinner.stop('Firebase deploy completed');
|
|
135
134
|
} catch (err) {
|
|
136
|
-
spinner.
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
const apnsBody = [
|
|
148
|
+
kleur.yellow('Push iOS: configure a APNs Key no Firebase Console'),
|
|
149
|
+
kleur.dim('1. Apple Developer Portal → Keys → criar 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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
218
|
+
apnsLines.push(kleur.cyan(`https://console.firebase.google.com/project/${firebaseProjectId}/settings/cloudmessaging`));
|
|
220
219
|
}
|
|
221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
+
ui.log.error(tr('deploy.error.notProject'));
|
|
254
258
|
process.exit(1);
|
|
255
259
|
}
|
|
256
260
|
|