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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "0.0.98",
3
+ "version": "0.0.100",
4
4
  "description": "Complete Supabase backup and migration tool - EXPERIMENTAL VERSION - USE AT YOUR OWN RISK",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -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
- console.log(chalk.gray(` ${getT('backup.error.databaseUrlExampleValue')}`));
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
- console.log(chalk.gray(` ${getT('backup.error.accessTokenStep1Detail')}`));
161
- console.log(chalk.gray(` ${getT('backup.error.accessTokenStep2Detail')}`));
162
- console.log(chalk.gray(` ${getT('backup.error.accessTokenStep3Detail')}`));
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, chalk);
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, chalk);
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, chalk, appUrl, opts) {
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
- console.log(chalk.red(`\n❌ ${getT('license.error.title')}\n`));
141
- console.log(chalk.white(` ${getT('license.error.whatHappened')}`));
142
- console.log(chalk.white(` ${getT(typeKey)}`));
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
- console.log(chalk.white(` ${getT('license.error.status', { status: `${httpStatus} ${(httpStatusText || '').trim()}` })}`));
147
+ ui.info(` ${getT('license.error.status', { status: `${httpStatus} ${(httpStatusText || '').trim()}` })}`);
145
148
  }
146
149
  if (endpoint || fullUrl) {
147
- console.log(chalk.white(` ${getT('license.error.endpoint', { endpoint: fullUrl || endpoint })}`));
150
+ ui.info(` ${getT('license.error.endpoint', { endpoint: fullUrl || endpoint })}`);
148
151
  }
149
- console.log(chalk.white(` ${getT('license.error.correlationId', { id: correlationId })}\n`));
152
+ ui.info(` ${getT('license.error.correlationId', { id: correlationId })}\n`);
150
153
 
151
- console.log(chalk.cyan(` ${getT('license.error.howToHelp')}`));
152
- console.log(chalk.white(` ${getT('license.error.supportStep1', { url: appUrl })}`));
153
- console.log(chalk.white(` ${getT('license.error.supportStep2')}`));
154
- console.log(chalk.white(` ${getT('license.error.supportStep3')}`));
155
- console.log(chalk.white(` ${getT('license.error.supportStep4')}`));
156
- console.log(chalk.white(` ${getT('license.error.supportStep5')}\n`));
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
- console.log(chalk.gray(formatBundleForTerminal(bundleText)));
159
- console.log('');
160
- console.log(chalk.cyan(` ${getT('license.error.visit', { url: appUrl })}\n`));
161
- console.log(chalk.gray(` ${getT('license.error.subjectSuggestion', { id: correlationId })}`));
162
- console.log(chalk.gray(` ${getT('license.error.messageSuggestion')}\n`));
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
- console.log(chalk.red(`❌ ${getT('license.required')}`));
195
- console.log(chalk.cyan(` ${appUrl}`));
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
- { 'User-Agent': `smoonb-cli/${cliVersion}` },
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
- console.log(chalk.yellow(` ${classification.hintUser}`));
237
+ ui.warn(` ${classification.hintUser}`);
230
238
  }
231
- printLicenseValidationError(getT, chalk, appUrl, {
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 correlationId = createCorrelationId();
258
+ const displayCorrelationId = resBody.correlationId || correlationId;
251
259
  const responseHeadersStr = safeStringifyHeaders(headers);
252
260
  const responseBodyTruncated = truncateBody(rawBody);
253
- printLicenseValidationError(getT, chalk, appUrl, {
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 correlationId = createCorrelationId();
277
+ const displayCorrelationId = resBody.correlationId || correlationId;
270
278
  const responseHeadersStr = safeStringifyHeaders(headers);
271
279
  const responseBodyTruncated = rawBody ? truncateBody(rawBody) : '';
272
- printLicenseValidationError(getT, chalk, appUrl, {
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
- console.log(chalk.red(`❌ ${getT('license.invalidOrInactive')}`));
294
- console.log(chalk.white(` ${getT('license.openDesktopApp')}`));
295
- console.log(chalk.cyan(` ${appUrl}`));
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
- console.log(chalk.yellow(` ⚠️ ${getT('license.trialNotice')}`));
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
- console.log(chalk.gray(` - ${getT('backup.steps.migrations.cleaned')}`));
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
- console.log(chalk.gray(`💡 ${getT('docker.requiredComponents')}`));
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
- console.log(chalk.gray(`💡 ${getT('docker.requiredComponents')}`));
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
- console.log(chalk.gray(`💡 ${getT('supabase.requiredComponents')}`));
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
- console.log(chalk.gray(`💡 ${getT('supabase.cliUpdateLink')}`));
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
- console.log(chalk.gray(` ${data.latestError || getT('supabase.cliLatestErrorUnknown')}`));
100
+ ui.hint(` ${data.latestError || getT('supabase.cliLatestErrorUnknown')}`);
100
101
  console.log('');
101
- console.log(chalk.gray(`💡 ${getT('supabase.cliUpdateLink')}`));
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
- console.log(chalk.gray(` ${getT('docker.installComponents')}`));
108
+ ui.hint(` ${getT('docker.installComponents')}`);
108
109
  console.log('');
109
110
 
110
111
  process.exit(1);
@@ -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
- console.log(chalk.gray(` ${getT('config.initOption')}`));
24
- console.log(chalk.gray(` ${getT('config.showOption')}`));
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
- console.log(chalk.gray(' 1. Edite .smoonbrc com suas credenciais Supabase'));
67
- console.log(chalk.gray(' 2. Substitua os valores placeholder pelos reais'));
68
- console.log(chalk.gray(' 3. Execute: npx smoonb backup'));
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
- console.log(chalk.gray(` - Localização: ${configPath}`));
86
+ ui.hint(` - Localização: ${configPath}`);
86
87
 
87
88
  if (config.supabase?.projectId && config.supabase.projectId !== 'your-project-id-here') {
88
- console.log(chalk.gray(` - Project ID: ${config.supabase.projectId}`));
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
- console.log(chalk.gray(` - Supabase URL: ${config.supabase.url}`));
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
- console.log(chalk.gray(' - Service Key: Configurada'));
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
- console.log(chalk.gray(' - Anon Key: Configurada'));
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
- console.log(chalk.gray(' - Database URL: Configurada'));
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
- console.log(chalk.gray(' - Access Token: Configurado'));
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
- console.log(chalk.gray(` - Output Dir: ${config.backup?.outputDir || './backups'}`));
125
- console.log(chalk.gray(` - Include Functions: ${config.backup?.includeFunctions || true}`));
126
- console.log(chalk.gray(` - Include Storage: ${config.backup?.includeStorage || true}`));
127
- console.log(chalk.gray(` - Include Auth: ${config.backup?.includeAuth || true}`));
128
- console.log(chalk.gray(` - Include Realtime: ${config.backup?.includeRealtime || true}`));
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
- console.log(chalk.gray(` - Clean Restore: ${config.restore?.cleanRestore || true}`));
132
- console.log(chalk.gray(` - Verify After Restore: ${config.restore?.verifyAfterRestore || true}`));
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
- console.log(chalk.gray(' - Use: npx smoonb config --init'));
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
- console.log(chalk.gray(' npx smoonb functions list'));
27
+ ui.hint(' npx smoonb functions list');
27
28
  console.log(chalk.yellow(`\n🚀 ${getT('functions.deploy')}`));
28
- console.log(chalk.gray(' npx smoonb functions push'));
29
+ ui.hint(' npx smoonb functions push');
29
30
  console.log(chalk.yellow(`\n📥 ${getT('functions.pull')}`));
30
- console.log(chalk.gray(' npx smoonb functions pull'));
31
+ ui.hint(' npx smoonb functions pull');
31
32
  console.log(chalk.yellow(`\n💡 ${getT('functions.moreOptions')}`));
32
- console.log(chalk.gray(' supabase functions --help'));
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
- console.log(chalk.gray(' 1. Use o Dashboard do Supabase'));
124
- console.log(chalk.gray(' 2. Ou clone o código do seu repositório Git'));
125
- console.log(chalk.gray(' 3. Ou use a API do Supabase diretamente'));
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
- console.log(chalk.gray(` ℹ️ ${getT('restore.steps.database.normalErrors')}`));
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
- console.log(chalk.gray(` ℹ️ ${getT('restore.steps.database.errorsIgnored')}`));
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;
@@ -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://api.smoonb.com',
7
+ apiBaseUrl: 'https://www.smoonb.com',
7
8
  appUrl: 'https://www.smoonb.com',
8
- telemetryUrl: 'https://api.smoonb.com'
9
+ telemetryUrl: 'https://www.smoonb.com',
10
+ healthCheckUrl: 'https://www.smoonb.com/api/health'
9
11
  };
10
12
 
11
13
  module.exports = { APP_CONFIG };
@@ -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 ? chalk.gray(`(${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
- console.log(chalk.gray(' - Supabase CLI: npm install -g supabase'));
157
+ ui.hint(' - Supabase CLI: npm install -g supabase');
157
158
  }
158
159
 
159
160
  if (missing.includes('pg_dump')) {
160
- console.log(chalk.gray(' - PostgreSQL: https://www.postgresql.org/download/'));
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
- console.log(chalk.gray(` - Localização: ${path.join(process.cwd(), '.env.local')}`));
177
+ ui.hint(` - Localização: ${path.join(process.cwd(), '.env.local')}`);
177
178
 
178
179
  if (config.supabase?.url) {
179
- console.log(chalk.gray(` - Supabase URL: ${config.supabase.url}`));
180
+ ui.hint(` - Supabase URL: ${config.supabase.url}`);
180
181
  }
181
182
 
182
183
  if (config.supabase?.serviceKey) {
183
- console.log(chalk.gray(' - Service Key: Configurada'));
184
+ ui.hint(' - Service Key: Configurada');
184
185
  }
185
186
 
186
187
  if (config.supabase?.anonKey) {
187
- console.log(chalk.gray(' - Anon Key: Configurada'));
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
- console.log(chalk.gray(' - Configure o arquivo .env.local na raiz do projeto'));
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
- console.log(chalk.gray(' - Configure SUPABASE_URL e SUPABASE_ANON_KEY'));
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, chalk) {
9
+ function printSupabasePostgresMajorHintOnce(getT) {
9
10
  if (_printedSupabasePostgresMajorHint) return;
10
11
  _printedSupabasePostgresMajorHint = true;
11
- console.log(chalk.cyan(` ℹ️ ${getT('env.supabasePostgresMajor.hintTitle')}`));
12
- console.log(chalk.white(` ${getT('env.supabasePostgresMajor.hintPath')}`));
13
- console.log(chalk.gray(` ${getT('env.supabasePostgresMajor.hintBody')}`));
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, chalk);
127
+ printSupabasePostgresMajorHintOnce(getT);
127
128
  }
128
129
  if (expected === 'SMOONB_LICENSE_KEY' && instructions.help === '') {
129
- console.log(chalk.gray(` ${getT('env.licenseKey.help')}`));
130
+ ui.hint(` ${getT('env.licenseKey.help')}`);
130
131
  }
131
132
  if (expected === 'SMOONB_TELEMETRY_ENABLED' && instructions.help === '') {
132
- console.log(chalk.gray(` ${getT('env.telemetry.help')}`));
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, chalk);
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;