smoonb 0.0.98 → 0.0.100
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.
Potentially problematic release.
This version of smoonb might be problematic. Click here for more details.
- package/package.json +1 -1
- package/src/commands/backup/index.js +7 -6
- package/src/commands/backup/steps/00-license.js +44 -36
- package/src/commands/backup/steps/11-migrations.js +2 -1
- package/src/commands/backup/utils.js +8 -7
- package/src/commands/config.js +21 -20
- package/src/commands/functions.js +8 -7
- package/src/commands/restore/steps/03-database.js +3 -2
- package/src/config/appConfig.js +4 -2
- package/src/i18n/locales/en.json +1 -0
- package/src/i18n/locales/pt-BR.json +1 -0
- package/src/index.js +10 -9
- package/src/interactive/envMapper.js +9 -8
- package/src/utils/cliUi.js +111 -0
package/package.json
CHANGED
|
@@ -24,6 +24,7 @@ const step09EdgeFunctions = require('./steps/09-edge-functions');
|
|
|
24
24
|
const step10SupabaseTemp = require('./steps/10-supabase-temp');
|
|
25
25
|
const step11Migrations = require('./steps/11-migrations');
|
|
26
26
|
const { sendTelemetry } = require('../../telemetry');
|
|
27
|
+
const ui = require('../../utils/cliUi');
|
|
27
28
|
|
|
28
29
|
// Exportar FUNÇÃO em vez de objeto Command
|
|
29
30
|
module.exports = async (options) => {
|
|
@@ -142,7 +143,7 @@ module.exports = async (options) => {
|
|
|
142
143
|
console.log(chalk.yellow(` 2. ${getT('backup.error.databaseUrlStep2')}`));
|
|
143
144
|
console.log('');
|
|
144
145
|
console.log(chalk.blue(`💡 ${getT('backup.error.databaseUrlExample')}:`));
|
|
145
|
-
|
|
146
|
+
ui.hint(` ${getT('backup.error.databaseUrlExampleValue')}`);
|
|
146
147
|
console.log('');
|
|
147
148
|
console.log(chalk.red(`🚫 ${getT('backup.error.databaseUrlCancelled')}`));
|
|
148
149
|
process.exit(1);
|
|
@@ -157,9 +158,9 @@ module.exports = async (options) => {
|
|
|
157
158
|
console.log(chalk.yellow(` 3. ${getT('backup.error.accessTokenStep3')}`));
|
|
158
159
|
console.log('');
|
|
159
160
|
console.log(chalk.blue(`🔗 ${getT('backup.error.accessTokenHowTo')}:`));
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
ui.hint(` ${getT('backup.error.accessTokenStep1Detail')}`);
|
|
162
|
+
ui.hint(` ${getT('backup.error.accessTokenStep2Detail')}`);
|
|
163
|
+
ui.hint(` ${getT('backup.error.accessTokenStep3Detail')}`);
|
|
163
164
|
console.log('');
|
|
164
165
|
console.log(chalk.red(`🚫 ${getT('backup.error.accessTokenCancelled')}`));
|
|
165
166
|
process.exit(1);
|
|
@@ -169,7 +170,7 @@ module.exports = async (options) => {
|
|
|
169
170
|
if (!postgresMajorRaw) {
|
|
170
171
|
console.log(chalk.red(`❌ ${getT('backup.error.postgresMajorNotSet')}`));
|
|
171
172
|
console.log(chalk.white(` ${getT('backup.error.postgresMajorInstructions')}`));
|
|
172
|
-
printSupabasePostgresMajorHintOnce(getT
|
|
173
|
+
printSupabasePostgresMajorHintOnce(getT);
|
|
173
174
|
console.log('');
|
|
174
175
|
process.exit(1);
|
|
175
176
|
}
|
|
@@ -177,7 +178,7 @@ module.exports = async (options) => {
|
|
|
177
178
|
if (Number.isNaN(parsedMajor) || !isPostgresMajorSupported(parsedMajor, true)) {
|
|
178
179
|
console.log(chalk.red(`❌ ${getT('backup.error.postgresMajorInvalid')}`));
|
|
179
180
|
console.log(chalk.white(` ${getT('backup.error.postgresMajorInstructions')}`));
|
|
180
|
-
printSupabasePostgresMajorHintOnce(getT
|
|
181
|
+
printSupabasePostgresMajorHintOnce(getT);
|
|
181
182
|
console.log('');
|
|
182
183
|
process.exit(1);
|
|
183
184
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
1
|
const https = require('https');
|
|
3
2
|
const inquirer = require('inquirer');
|
|
4
3
|
const path = require('path');
|
|
5
4
|
const { readEnvFile, writeEnvFile } = require('../../../utils/env');
|
|
6
5
|
const { APP_CONFIG } = require('../../../config/appConfig');
|
|
7
6
|
const { t } = require('../../../i18n');
|
|
7
|
+
const ui = require('../../../utils/cliUi');
|
|
8
8
|
const {
|
|
9
9
|
createCorrelationId,
|
|
10
10
|
maskSecret,
|
|
@@ -78,12 +78,12 @@ function httpsPost(url, body, headers, timeoutMs) {
|
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Exibe erro estruturado + instruções de suporte + bundle (pronto para colar).
|
|
81
|
+
* Usa ui (sem gray/dim) para legibilidade em Windows e copy/paste.
|
|
81
82
|
* @param {Function} getT
|
|
82
|
-
* @param {object} chalk
|
|
83
83
|
* @param {string} appUrl - APP_CONFIG.appUrl
|
|
84
84
|
* @param {object} opts
|
|
85
85
|
*/
|
|
86
|
-
function printLicenseValidationError(getT,
|
|
86
|
+
function printLicenseValidationError(getT, appUrl, opts) {
|
|
87
87
|
const {
|
|
88
88
|
correlationId,
|
|
89
89
|
kind,
|
|
@@ -137,29 +137,32 @@ function printLicenseValidationError(getT, chalk, appUrl, opts) {
|
|
|
137
137
|
: kind === 'parse' ? 'license.error.typeParse'
|
|
138
138
|
: 'license.error.typeNetwork';
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
ui.errorBold(`\n❌ ${getT('license.error.title')}\n`);
|
|
141
|
+
ui.info(` ${getT('license.error.whatHappened')}`);
|
|
142
|
+
ui.info(` ${getT(typeKey)}`);
|
|
143
|
+
if (kind === 'network' || kind === 'tls') {
|
|
144
|
+
ui.hint(` ${getT('license.error.testConnectivity', { url: APP_CONFIG.healthCheckUrl || appUrl + '/api/health' })}`);
|
|
145
|
+
}
|
|
143
146
|
if (httpStatus != null) {
|
|
144
|
-
|
|
147
|
+
ui.info(` ${getT('license.error.status', { status: `${httpStatus} ${(httpStatusText || '').trim()}` })}`);
|
|
145
148
|
}
|
|
146
149
|
if (endpoint || fullUrl) {
|
|
147
|
-
|
|
150
|
+
ui.info(` ${getT('license.error.endpoint', { endpoint: fullUrl || endpoint })}`);
|
|
148
151
|
}
|
|
149
|
-
|
|
152
|
+
ui.info(` ${getT('license.error.correlationId', { id: correlationId })}\n`);
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
ui.link(` ${getT('license.error.howToHelp')}`);
|
|
155
|
+
ui.info(` ${getT('license.error.supportStep1', { url: appUrl })}`);
|
|
156
|
+
ui.info(` ${getT('license.error.supportStep2')}`);
|
|
157
|
+
ui.info(` ${getT('license.error.supportStep3')}`);
|
|
158
|
+
ui.info(` ${getT('license.error.supportStep4')}`);
|
|
159
|
+
ui.info(` ${getT('license.error.supportStep5')}\n`);
|
|
157
160
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
ui.block(formatBundleForTerminal(bundleText));
|
|
162
|
+
ui.info('');
|
|
163
|
+
ui.link(` ${getT('license.error.visit', { url: appUrl })}\n`);
|
|
164
|
+
ui.hint(` ${getT('license.error.subjectSuggestion', { id: correlationId })}`);
|
|
165
|
+
ui.hint(` ${getT('license.error.messageSuggestion')}\n`);
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
/**
|
|
@@ -191,8 +194,8 @@ module.exports = async (options) => {
|
|
|
191
194
|
}]);
|
|
192
195
|
licenseKey = (prompted || '').toString().trim();
|
|
193
196
|
if (!licenseKey) {
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
ui.error(`❌ ${getT('license.required')}`);
|
|
198
|
+
ui.link(` ${appUrl}`);
|
|
196
199
|
process.exit(1);
|
|
197
200
|
}
|
|
198
201
|
const currentEnv = await readEnvFile(envPath);
|
|
@@ -205,30 +208,35 @@ module.exports = async (options) => {
|
|
|
205
208
|
} catch {
|
|
206
209
|
// ignore
|
|
207
210
|
}
|
|
211
|
+
const correlationId = createCorrelationId();
|
|
208
212
|
const body = {
|
|
209
213
|
licenseKey,
|
|
214
|
+
correlationId,
|
|
210
215
|
cliVersion,
|
|
211
216
|
command,
|
|
212
217
|
platform: process.platform,
|
|
213
218
|
arch: process.arch,
|
|
214
219
|
nodeVersion: process.version
|
|
215
220
|
};
|
|
221
|
+
const requestHeaders = {
|
|
222
|
+
'User-Agent': `smoonb-cli/${cliVersion}`,
|
|
223
|
+
'x-correlation-id': correlationId
|
|
224
|
+
};
|
|
216
225
|
|
|
217
226
|
let response;
|
|
218
227
|
try {
|
|
219
228
|
response = await httpsPost(
|
|
220
229
|
fullUrl,
|
|
221
230
|
body,
|
|
222
|
-
|
|
231
|
+
requestHeaders,
|
|
223
232
|
REQUEST_TIMEOUT_MS
|
|
224
233
|
);
|
|
225
234
|
} catch (err) {
|
|
226
|
-
const correlationId = createCorrelationId();
|
|
227
235
|
const classification = classifyRequestError(err);
|
|
228
236
|
if (classification.hintUser) {
|
|
229
|
-
|
|
237
|
+
ui.warn(` ${classification.hintUser}`);
|
|
230
238
|
}
|
|
231
|
-
printLicenseValidationError(getT,
|
|
239
|
+
printLicenseValidationError(getT, appUrl, {
|
|
232
240
|
correlationId,
|
|
233
241
|
kind: classification.kind,
|
|
234
242
|
endpoint: ENDPOINT_PATH,
|
|
@@ -247,11 +255,11 @@ module.exports = async (options) => {
|
|
|
247
255
|
// Resposta 200 mas corpo inesperado (ex.: HTML em vez de JSON)
|
|
248
256
|
const looksLikeJson = rawBody && rawBody.trim().startsWith('{');
|
|
249
257
|
if (statusCode === 200 && !looksLikeJson && rawBody) {
|
|
250
|
-
const
|
|
258
|
+
const displayCorrelationId = resBody.correlationId || correlationId;
|
|
251
259
|
const responseHeadersStr = safeStringifyHeaders(headers);
|
|
252
260
|
const responseBodyTruncated = truncateBody(rawBody);
|
|
253
|
-
printLicenseValidationError(getT,
|
|
254
|
-
correlationId,
|
|
261
|
+
printLicenseValidationError(getT, appUrl, {
|
|
262
|
+
correlationId: displayCorrelationId,
|
|
255
263
|
kind: 'parse',
|
|
256
264
|
endpoint: ENDPOINT_PATH,
|
|
257
265
|
fullUrl,
|
|
@@ -266,11 +274,11 @@ module.exports = async (options) => {
|
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
if (statusCode !== 200) {
|
|
269
|
-
const
|
|
277
|
+
const displayCorrelationId = resBody.correlationId || correlationId;
|
|
270
278
|
const responseHeadersStr = safeStringifyHeaders(headers);
|
|
271
279
|
const responseBodyTruncated = rawBody ? truncateBody(rawBody) : '';
|
|
272
|
-
printLicenseValidationError(getT,
|
|
273
|
-
correlationId,
|
|
280
|
+
printLicenseValidationError(getT, appUrl, {
|
|
281
|
+
correlationId: displayCorrelationId,
|
|
274
282
|
kind: 'http',
|
|
275
283
|
httpStatus: statusCode,
|
|
276
284
|
httpStatusText: statusMessage,
|
|
@@ -290,14 +298,14 @@ module.exports = async (options) => {
|
|
|
290
298
|
const status = resBody.status || '';
|
|
291
299
|
|
|
292
300
|
if (!valid || ['expired', 'canceled'].includes(status)) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
301
|
+
ui.error(`❌ ${getT('license.invalidOrInactive')}`);
|
|
302
|
+
ui.info(` ${getT('license.openDesktopApp')}`);
|
|
303
|
+
ui.link(` ${appUrl}`);
|
|
296
304
|
process.exit(1);
|
|
297
305
|
}
|
|
298
306
|
|
|
299
307
|
if (status === 'trial') {
|
|
300
|
-
|
|
308
|
+
ui.warn(` ⚠️ ${getT('license.trialNotice')}`);
|
|
301
309
|
}
|
|
302
310
|
|
|
303
311
|
const license = {
|
|
@@ -4,6 +4,7 @@ const { execSync } = require('child_process');
|
|
|
4
4
|
const { extractPasswordFromDbUrl, ensureCleanLink } = require('../../../utils/supabaseLink');
|
|
5
5
|
const { cleanDir, countFiles, copyDirSafe } = require('../../../utils/fsExtra');
|
|
6
6
|
const { t } = require('../../../i18n');
|
|
7
|
+
const ui = require('../../../utils/cliUi');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Etapa 10: Migrations Backup (NOVA ETAPA INDEPENDENTE)
|
|
@@ -62,7 +63,7 @@ module.exports = async (context) => {
|
|
|
62
63
|
|
|
63
64
|
if (shouldClean) {
|
|
64
65
|
await cleanDir(migrationsDir);
|
|
65
|
-
|
|
66
|
+
ui.hint(` - ${getT('backup.steps.migrations.cleaned')}`);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const { t } = require('../../i18n');
|
|
3
|
+
const ui = require('../../utils/cliUi');
|
|
3
4
|
|
|
4
5
|
const SUPPORTED_MAJORS_DEFAULT = [15, 17];
|
|
5
6
|
const SUPPORTED_MAJORS_ADVANCED = [16, 18];
|
|
@@ -53,7 +54,7 @@ function showDockerMessagesAndExit(reason, data = {}) {
|
|
|
53
54
|
console.log('');
|
|
54
55
|
console.log(chalk.blue(`🔗 ${getT('docker.download')}`));
|
|
55
56
|
console.log('');
|
|
56
|
-
|
|
57
|
+
ui.hint(`💡 ${getT('docker.requiredComponents')}`);
|
|
57
58
|
break;
|
|
58
59
|
|
|
59
60
|
case 'docker_not_running':
|
|
@@ -66,7 +67,7 @@ function showDockerMessagesAndExit(reason, data = {}) {
|
|
|
66
67
|
console.log('');
|
|
67
68
|
console.log(chalk.blue(`💡 ${getT('docker.tip')}`));
|
|
68
69
|
console.log('');
|
|
69
|
-
|
|
70
|
+
ui.hint(`💡 ${getT('docker.requiredComponents')}`);
|
|
70
71
|
break;
|
|
71
72
|
|
|
72
73
|
case 'supabase_cli_not_found':
|
|
@@ -78,7 +79,7 @@ function showDockerMessagesAndExit(reason, data = {}) {
|
|
|
78
79
|
console.log('');
|
|
79
80
|
console.log(chalk.blue(`🔗 ${getT('supabase.installLink')}`));
|
|
80
81
|
console.log('');
|
|
81
|
-
|
|
82
|
+
ui.hint(`💡 ${getT('supabase.requiredComponents')}`);
|
|
82
83
|
break;
|
|
83
84
|
|
|
84
85
|
case 'supabase_cli_outdated':
|
|
@@ -89,22 +90,22 @@ function showDockerMessagesAndExit(reason, data = {}) {
|
|
|
89
90
|
console.log(chalk.cyan(` ${getT('supabase.cliUpdateCommandGlobal')}`));
|
|
90
91
|
console.log(chalk.cyan(` ${getT('supabase.cliUpdateCommandLocal')}`));
|
|
91
92
|
console.log('');
|
|
92
|
-
|
|
93
|
+
ui.hint(`💡 ${getT('supabase.cliUpdateLink')}`);
|
|
93
94
|
break;
|
|
94
95
|
|
|
95
96
|
case 'supabase_cli_latest_unknown':
|
|
96
97
|
console.log(chalk.red(`❌ ${getT('supabase.cliLatestUnknown')}`));
|
|
97
98
|
console.log('');
|
|
98
99
|
console.log(chalk.yellow(`📋 ${getT('supabase.cliLatestErrorLabel')}`));
|
|
99
|
-
|
|
100
|
+
ui.hint(` ${data.latestError || getT('supabase.cliLatestErrorUnknown')}`);
|
|
100
101
|
console.log('');
|
|
101
|
-
|
|
102
|
+
ui.hint(`💡 ${getT('supabase.cliUpdateLink')}`);
|
|
102
103
|
break;
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
console.log('');
|
|
106
107
|
console.log(chalk.red(`🚫 ${getT('docker.cancelled')}`));
|
|
107
|
-
|
|
108
|
+
ui.hint(` ${getT('docker.installComponents')}`);
|
|
108
109
|
console.log('');
|
|
109
110
|
|
|
110
111
|
process.exit(1);
|
package/src/commands/config.js
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require('fs').promises;
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { showBetaBanner } = require('../utils/banner');
|
|
5
5
|
const { t } = require('../i18n');
|
|
6
|
+
const ui = require('../utils/cliUi');
|
|
6
7
|
|
|
7
8
|
// Exportar FUNÇÃO em vez de objeto Command
|
|
8
9
|
module.exports = async (options) => {
|
|
@@ -20,8 +21,8 @@ module.exports = async (options) => {
|
|
|
20
21
|
await showConfig(configPath);
|
|
21
22
|
} else {
|
|
22
23
|
console.log(chalk.yellow(`💡 ${getT('config.options')}`));
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
ui.hint(` ${getT('config.initOption')}`);
|
|
25
|
+
ui.hint(` ${getT('config.showOption')}`);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
} catch (error) {
|
|
@@ -63,9 +64,9 @@ async function initializeConfig(configPath) {
|
|
|
63
64
|
await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
64
65
|
console.log(chalk.green(`✅ ${getT('config.fileCreated', { path: '.smoonbrc' })}`));
|
|
65
66
|
console.log(chalk.yellow('\n📝 Próximos passos:'));
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
ui.hint(' 1. Edite .smoonbrc com suas credenciais Supabase');
|
|
68
|
+
ui.hint(' 2. Substitua os valores placeholder pelos reais');
|
|
69
|
+
ui.hint(' 3. Execute: npx smoonb backup');
|
|
69
70
|
} catch (error) {
|
|
70
71
|
const getT = global.smoonbI18n?.t || t;
|
|
71
72
|
throw new Error(`${getT('config.error')}: ${error.message}`);
|
|
@@ -82,60 +83,60 @@ async function showConfig(configPath) {
|
|
|
82
83
|
const config = JSON.parse(configContent);
|
|
83
84
|
|
|
84
85
|
console.log(chalk.green('✅ Arquivo de configuração encontrado'));
|
|
85
|
-
|
|
86
|
+
ui.hint(` - Localização: ${configPath}`);
|
|
86
87
|
|
|
87
88
|
if (config.supabase?.projectId && config.supabase.projectId !== 'your-project-id-here') {
|
|
88
|
-
|
|
89
|
+
ui.hint(` - Project ID: ${config.supabase.projectId}`);
|
|
89
90
|
} else {
|
|
90
91
|
console.log(chalk.yellow(' - Project ID: Não configurado'));
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
if (config.supabase?.url && config.supabase.url !== 'https://your-project-id.supabase.co') {
|
|
94
|
-
|
|
95
|
+
ui.hint(` - Supabase URL: ${config.supabase.url}`);
|
|
95
96
|
} else {
|
|
96
97
|
console.log(chalk.yellow(' - Supabase URL: Não configurado'));
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
if (config.supabase?.serviceKey && config.supabase.serviceKey !== 'your-service-key-here') {
|
|
100
|
-
|
|
101
|
+
ui.hint(' - Service Key: Configurada');
|
|
101
102
|
} else {
|
|
102
103
|
console.log(chalk.yellow(' - Service Key: Não configurada'));
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
if (config.supabase?.anonKey && config.supabase.anonKey !== 'your-anon-key-here') {
|
|
106
|
-
|
|
107
|
+
ui.hint(' - Anon Key: Configurada');
|
|
107
108
|
} else {
|
|
108
109
|
console.log(chalk.yellow(' - Anon Key: Não configurada'));
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
if (config.supabase?.databaseUrl && !config.supabase.databaseUrl.includes('[password]')) {
|
|
112
|
-
|
|
113
|
+
ui.hint(' - Database URL: Configurada');
|
|
113
114
|
} else {
|
|
114
115
|
console.log(chalk.yellow(' - Database URL: Não configurada'));
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
if (config.supabase?.accessToken && config.supabase.accessToken !== 'your-personal-access-token-here') {
|
|
118
|
-
|
|
119
|
+
ui.hint(' - Access Token: Configurado');
|
|
119
120
|
} else {
|
|
120
121
|
console.log(chalk.yellow(' - Access Token: Não configurado (obrigatório para Management API)'));
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
console.log(chalk.blue('\n📊 Configurações de backup:'));
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
ui.hint(` - Output Dir: ${config.backup?.outputDir || './backups'}`);
|
|
126
|
+
ui.hint(` - Include Functions: ${config.backup?.includeFunctions || true}`);
|
|
127
|
+
ui.hint(` - Include Storage: ${config.backup?.includeStorage || true}`);
|
|
128
|
+
ui.hint(` - Include Auth: ${config.backup?.includeAuth || true}`);
|
|
129
|
+
ui.hint(` - Include Realtime: ${config.backup?.includeRealtime || true}`);
|
|
129
130
|
|
|
130
131
|
console.log(chalk.blue('\n🔄 Configurações de restore:'));
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
ui.hint(` - Clean Restore: ${config.restore?.cleanRestore || true}`);
|
|
133
|
+
ui.hint(` - Verify After Restore: ${config.restore?.verifyAfterRestore || true}`);
|
|
133
134
|
|
|
134
135
|
} catch (error) {
|
|
135
136
|
const getT = global.smoonbI18n?.t || t;
|
|
136
137
|
if (error.code === 'ENOENT') {
|
|
137
138
|
console.log(chalk.yellow(`⚠️ ${getT('config.fileNotFound', { path: configPath })}`));
|
|
138
|
-
|
|
139
|
+
ui.hint(' - Use: npx smoonb config --init');
|
|
139
140
|
} else {
|
|
140
141
|
throw new Error(`${getT('config.error')}: ${error.message}`);
|
|
141
142
|
}
|
|
@@ -3,6 +3,7 @@ const { ensureBin, runCommand } = require('../utils/cli');
|
|
|
3
3
|
const { readConfig, validateFor } = require('../utils/config');
|
|
4
4
|
const { showBetaBanner } = require('../utils/banner');
|
|
5
5
|
const { t } = require('../i18n');
|
|
6
|
+
const ui = require('../utils/cliUi');
|
|
6
7
|
|
|
7
8
|
// Exportar FUNÇÃO em vez de objeto Command
|
|
8
9
|
module.exports = async (_options) => {
|
|
@@ -23,13 +24,13 @@ module.exports = async (_options) => {
|
|
|
23
24
|
|
|
24
25
|
console.log(chalk.blue(`⚡ ${getT('functions.availableCommands')}`));
|
|
25
26
|
console.log(chalk.yellow(`\n📋 ${getT('functions.list')}`));
|
|
26
|
-
|
|
27
|
+
ui.hint(' npx smoonb functions list');
|
|
27
28
|
console.log(chalk.yellow(`\n🚀 ${getT('functions.deploy')}`));
|
|
28
|
-
|
|
29
|
+
ui.hint(' npx smoonb functions push');
|
|
29
30
|
console.log(chalk.yellow(`\n📥 ${getT('functions.pull')}`));
|
|
30
|
-
|
|
31
|
+
ui.hint(' npx smoonb functions pull');
|
|
31
32
|
console.log(chalk.yellow(`\n💡 ${getT('functions.moreOptions')}`));
|
|
32
|
-
|
|
33
|
+
ui.hint(' supabase functions --help');
|
|
33
34
|
|
|
34
35
|
} catch (error) {
|
|
35
36
|
const getT = global.smoonbI18n?.t || t;
|
|
@@ -120,9 +121,9 @@ async function pullFunctions(projectRef) {
|
|
|
120
121
|
|
|
121
122
|
console.log(chalk.yellow('⚠️ Pull de Edge Functions não é oficialmente suportado pelo Supabase CLI'));
|
|
122
123
|
console.log(chalk.yellow('💡 Para baixar código das functions remotas:'));
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
ui.hint(' 1. Use o Dashboard do Supabase');
|
|
125
|
+
ui.hint(' 2. Ou clone o código do seu repositório Git');
|
|
126
|
+
ui.hint(' 3. Ou use a API do Supabase diretamente');
|
|
126
127
|
console.log(chalk.blue('\n📚 Documentação: https://supabase.com/docs/guides/functions'));
|
|
127
128
|
|
|
128
129
|
} catch (error) {
|
|
@@ -2,6 +2,7 @@ const chalk = require('chalk');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const { t } = require('../../../i18n');
|
|
5
|
+
const ui = require('../../../utils/cliUi');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Etapa 3: Restaurar Database via psql
|
|
@@ -62,7 +63,7 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
62
63
|
execSync(restoreCmd, { stdio: 'inherit', encoding: 'utf8' });
|
|
63
64
|
|
|
64
65
|
console.log(chalk.green(` ✅ ${getT('restore.steps.database.success')}`));
|
|
65
|
-
|
|
66
|
+
ui.hint(` ℹ️ ${getT('restore.steps.database.normalErrors')}`);
|
|
66
67
|
|
|
67
68
|
} catch (error) {
|
|
68
69
|
// Erros esperados conforme documentação oficial Supabase
|
|
@@ -73,7 +74,7 @@ module.exports = async ({ backupFilePath, targetDatabaseUrl }) => {
|
|
|
73
74
|
error.stdout?.includes('already exists')) {
|
|
74
75
|
console.log(chalk.yellow(` ⚠️ ${getT('restore.steps.database.expectedErrors')}`));
|
|
75
76
|
console.log(chalk.green(` ✅ ${getT('restore.steps.database.success')}`));
|
|
76
|
-
|
|
77
|
+
ui.hint(` ℹ️ ${getT('restore.steps.database.errorsIgnored')}`);
|
|
77
78
|
} else {
|
|
78
79
|
console.error(chalk.red(` ❌ ${getT('restore.steps.database.error', { message: error.message })}`));
|
|
79
80
|
throw error;
|
package/src/config/appConfig.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parâmetros internos do produto (não expostos em env do usuário).
|
|
3
3
|
* Controlados apenas via atualização do CLI.
|
|
4
|
+
* Base única: https://www.smoonb.com
|
|
4
5
|
*/
|
|
5
6
|
const APP_CONFIG = {
|
|
6
|
-
apiBaseUrl: 'https://
|
|
7
|
+
apiBaseUrl: 'https://www.smoonb.com',
|
|
7
8
|
appUrl: 'https://www.smoonb.com',
|
|
8
|
-
telemetryUrl: 'https://
|
|
9
|
+
telemetryUrl: 'https://www.smoonb.com',
|
|
10
|
+
healthCheckUrl: 'https://www.smoonb.com/api/health'
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
module.exports = { APP_CONFIG };
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -280,6 +280,7 @@
|
|
|
280
280
|
"license.error.typeNetwork": "Type: Network error",
|
|
281
281
|
"license.error.typeTls": "Type: TLS / certificate error",
|
|
282
282
|
"license.error.typeParse": "Type: Unexpected response format from API",
|
|
283
|
+
"license.error.testConnectivity": "To test connectivity: GET {url}",
|
|
283
284
|
"license.error.status": "Status: {status}",
|
|
284
285
|
"license.error.endpoint": "Endpoint: {endpoint}",
|
|
285
286
|
"license.error.correlationId": "Correlation ID: {id}",
|
|
@@ -280,6 +280,7 @@
|
|
|
280
280
|
"license.error.typeNetwork": "Tipo: erro de rede",
|
|
281
281
|
"license.error.typeTls": "Tipo: erro TLS / certificado",
|
|
282
282
|
"license.error.typeParse": "Tipo: formato de resposta inesperado da API",
|
|
283
|
+
"license.error.testConnectivity": "Para testar conectividade: GET {url}",
|
|
283
284
|
"license.error.status": "Status: {status}",
|
|
284
285
|
"license.error.endpoint": "Endpoint: {endpoint}",
|
|
285
286
|
"license.error.correlationId": "Correlation ID: {id}",
|
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const chalk = require('chalk');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { showBetaBanner } = require('./utils/banner');
|
|
11
11
|
const { t } = require('./i18n');
|
|
12
|
+
const ui = require('./utils/cliUi');
|
|
12
13
|
|
|
13
14
|
// Exportar comandos
|
|
14
15
|
const backupCommand = require('./commands/backup');
|
|
@@ -139,7 +140,7 @@ function showPrerequisitesStatus() {
|
|
|
139
140
|
Object.entries(prerequisites).forEach(([name, info]) => {
|
|
140
141
|
const icon = info.installed ? '✅' : '❌';
|
|
141
142
|
const status = info.installed ? chalk.green('Instalado') : chalk.red('Não instalado');
|
|
142
|
-
const version = info.version ?
|
|
143
|
+
const version = info.version ? `(${info.version})` : '';
|
|
143
144
|
|
|
144
145
|
console.log(` ${icon} ${chalk.cyan(name)}: ${status} ${version}`);
|
|
145
146
|
});
|
|
@@ -153,11 +154,11 @@ function showPrerequisitesStatus() {
|
|
|
153
154
|
console.log(chalk.yellow.bold('\n💡 Instruções de instalação:'));
|
|
154
155
|
|
|
155
156
|
if (missing.includes('supabase_cli')) {
|
|
156
|
-
|
|
157
|
+
ui.hint(' - Supabase CLI: npm install -g supabase');
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
if (missing.includes('pg_dump')) {
|
|
160
|
-
|
|
161
|
+
ui.hint(' - PostgreSQL: https://www.postgresql.org/download/');
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
}
|
|
@@ -173,29 +174,29 @@ function checkCurrentConfig() {
|
|
|
173
174
|
|
|
174
175
|
if (config) {
|
|
175
176
|
console.log(chalk.green('✅ Arquivo de configuração encontrado'));
|
|
176
|
-
|
|
177
|
+
ui.hint(` - Localização: ${path.join(process.cwd(), '.env.local')}`);
|
|
177
178
|
|
|
178
179
|
if (config.supabase?.url) {
|
|
179
|
-
|
|
180
|
+
ui.hint(` - Supabase URL: ${config.supabase.url}`);
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
if (config.supabase?.serviceKey) {
|
|
183
|
-
|
|
184
|
+
ui.hint(' - Service Key: Configurada');
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
if (config.supabase?.anonKey) {
|
|
187
|
-
|
|
188
|
+
ui.hint(' - Anon Key: Configurada');
|
|
188
189
|
}
|
|
189
190
|
} else {
|
|
190
191
|
console.log(chalk.yellow('⚠️ Arquivo de configuração não encontrado'));
|
|
191
|
-
|
|
192
|
+
ui.hint(' - Configure o arquivo .env.local na raiz do projeto');
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
if (hasCredentials) {
|
|
195
196
|
console.log(chalk.green('✅ Credenciais configuradas'));
|
|
196
197
|
} else {
|
|
197
198
|
console.log(chalk.yellow('⚠️ Credenciais não configuradas'));
|
|
198
|
-
|
|
199
|
+
ui.hint(' - Configure SUPABASE_URL e SUPABASE_ANON_KEY');
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -2,15 +2,16 @@ const inquirer = require('inquirer');
|
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const { confirm } = require('../utils/prompt');
|
|
4
4
|
const { t } = require('../i18n');
|
|
5
|
+
const ui = require('../utils/cliUi');
|
|
5
6
|
|
|
6
7
|
let _printedSupabasePostgresMajorHint = false;
|
|
7
8
|
|
|
8
|
-
function printSupabasePostgresMajorHintOnce(getT
|
|
9
|
+
function printSupabasePostgresMajorHintOnce(getT) {
|
|
9
10
|
if (_printedSupabasePostgresMajorHint) return;
|
|
10
11
|
_printedSupabasePostgresMajorHint = true;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
ui.link(` ℹ️ ${getT('env.supabasePostgresMajor.hintTitle')}`);
|
|
13
|
+
ui.info(` ${getT('env.supabasePostgresMajor.hintPath')}`);
|
|
14
|
+
ui.hint(` ${getT('env.supabasePostgresMajor.hintBody')}`);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
async function mapEnvVariablesInteractively(env, expectedKeys) {
|
|
@@ -123,13 +124,13 @@ async function mapEnvVariablesInteractively(env, expectedKeys) {
|
|
|
123
124
|
for (const expected of expectedKeys) {
|
|
124
125
|
console.log(chalk.blue(`\n🔧 ${getT('env.mapping.title', { variable: expected })}`));
|
|
125
126
|
if (expected === 'SUPABASE_POSTGRES_MAJOR') {
|
|
126
|
-
printSupabasePostgresMajorHintOnce(getT
|
|
127
|
+
printSupabasePostgresMajorHintOnce(getT);
|
|
127
128
|
}
|
|
128
129
|
if (expected === 'SMOONB_LICENSE_KEY' && instructions.help === '') {
|
|
129
|
-
|
|
130
|
+
ui.hint(` ${getT('env.licenseKey.help')}`);
|
|
130
131
|
}
|
|
131
132
|
if (expected === 'SMOONB_TELEMETRY_ENABLED' && instructions.help === '') {
|
|
132
|
-
|
|
133
|
+
ui.hint(` ${getT('env.telemetry.help')}`);
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
let clientKey = undefined;
|
|
@@ -179,7 +180,7 @@ async function mapEnvVariablesInteractively(env, expectedKeys) {
|
|
|
179
180
|
if (!currentValue) {
|
|
180
181
|
console.log(chalk.yellow(getT('env.mapping.notFound', { variable: expected })));
|
|
181
182
|
if (expected === 'SUPABASE_POSTGRES_MAJOR') {
|
|
182
|
-
printSupabasePostgresMajorHintOnce(getT
|
|
183
|
+
printSupabasePostgresMajorHintOnce(getT);
|
|
183
184
|
} else if (instructions.help) {
|
|
184
185
|
// Se o help contém link (https://), mostrar como link
|
|
185
186
|
if (instructions.help.includes('https://')) {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI/console padronizado do smoonb CLI.
|
|
3
|
+
* Toda saída para o usuário usa este módulo para legibilidade (incl. Windows PowerShell).
|
|
4
|
+
* Regras: sem chalk.dim/chalk.gray/chalk.blackBright em blocos longos; cores só para rótulos/ênfase.
|
|
5
|
+
* Saída principal em stdout.
|
|
6
|
+
*/
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
const stdout = process.stdout;
|
|
10
|
+
|
|
11
|
+
function writeOut(msg) {
|
|
12
|
+
stdout.write(msg + (msg.endsWith('\n') ? '' : '\n'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Título / cabeçalho (cyan, boa leitura).
|
|
17
|
+
*/
|
|
18
|
+
function title(text) {
|
|
19
|
+
writeOut(chalk.cyan(text));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Texto informativo (branco/default, nunca dim/gray).
|
|
24
|
+
*/
|
|
25
|
+
function info(text) {
|
|
26
|
+
writeOut(chalk.white(text));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Aviso (amarelo).
|
|
31
|
+
*/
|
|
32
|
+
function warn(text) {
|
|
33
|
+
writeOut(chalk.yellow(text));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Erro (vermelho).
|
|
38
|
+
*/
|
|
39
|
+
function error(text) {
|
|
40
|
+
writeOut(chalk.red(text));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Erro em destaque (vermelho + bold) para títulos de falha.
|
|
45
|
+
*/
|
|
46
|
+
function errorBold(text) {
|
|
47
|
+
writeOut(chalk.red.bold(text));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Passo / label de etapa (azul).
|
|
52
|
+
*/
|
|
53
|
+
function step(text) {
|
|
54
|
+
writeOut(chalk.blue(text));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Sucesso (verde).
|
|
59
|
+
*/
|
|
60
|
+
function success(text) {
|
|
61
|
+
writeOut(chalk.green(text));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Link ou destaque secundário (cyan, não apagado).
|
|
66
|
+
*/
|
|
67
|
+
function link(text) {
|
|
68
|
+
writeOut(chalk.cyan(text));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Bloco de texto longo (ex.: bundle de diagnóstico) — SEM cor para copiar/colar e legibilidade.
|
|
73
|
+
*/
|
|
74
|
+
function block(text) {
|
|
75
|
+
if (Array.isArray(text)) {
|
|
76
|
+
text.forEach((line) => writeOut(line));
|
|
77
|
+
} else {
|
|
78
|
+
writeOut(String(text));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Múltiplas linhas em cor padrão (branco), sem dim/gray.
|
|
84
|
+
*/
|
|
85
|
+
function multiline(text) {
|
|
86
|
+
writeOut(chalk.white(String(text)));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sugestão / hint curto — branco para manter legível (não gray).
|
|
91
|
+
*/
|
|
92
|
+
function hint(text) {
|
|
93
|
+
writeOut(chalk.white(text));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ui = {
|
|
97
|
+
title,
|
|
98
|
+
info,
|
|
99
|
+
warn,
|
|
100
|
+
error,
|
|
101
|
+
errorBold,
|
|
102
|
+
step,
|
|
103
|
+
success,
|
|
104
|
+
link,
|
|
105
|
+
block,
|
|
106
|
+
multiline,
|
|
107
|
+
hint,
|
|
108
|
+
chalk
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
module.exports = ui;
|