smoonb 1.0.9 → 1.0.11

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/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.0.11] - 2026-10-10
8
+
9
+ ### Changed
10
+
11
+ - Recommend retaining destination Realtime capacity limits by default: the interactive prompt now uses `(S/n)` in Portuguese and `(Y/n)` in English. Values are still displayed before confirmation; declining, non-interactive execution and strict security checks retain their existing behavior.
12
+
13
+ ## [1.0.10] - 2026-10-10
14
+
15
+ ### Fixed
16
+
17
+ - When a Realtime PATCH succeeds but capacity limits remain different, show the backup and destination values and offer an explicit choice to retain the destination limits. Record accepted differences instead of failing with field names only or claiming an identical restore.
18
+ - Retry readback without repeating PATCH, then re-read all selected Realtime settings after consent. Security switches, connection pools, invalid values, API failures and concurrent configuration changes remain failures.
19
+ - Record refused quota differences in the failure report; allow a retry selecting only Realtime without repeating Database, Auth or Storage. Non-interactive runs never silently accept differences.
20
+ - Preserve SMTP verification limits in combined reports and show completion with notes when any selected component has accepted differences or unverified secrets.
21
+ - Replace obsolete manual-configuration messages in the English and Portuguese Auth/Realtime summaries.
22
+
23
+ ### Tests
24
+
25
+ - Cover legacy and API snapshots, delayed readback, consent and rejection, non-interactive execution, strict security validation, concurrent changes, secret-safe errors, partial reports and Realtime-only retries.
26
+
7
27
  ## [1.0.9] - 2026-10-10
8
28
 
9
29
  ### Fixed
package/README.md CHANGED
@@ -26,7 +26,7 @@ smoonb performs a complete backup of all components of your Supabase project:
26
26
  - ✅ **Edge Functions** (download automático do servidor)
27
27
  - ✅ **Auth Settings** (configurações de autenticação via Management API)
28
28
  - ✅ **Storage Buckets** (backup completo: metadados, configurações e todos os arquivos via Management API + Supabase Client, cria ZIP no padrão do Dashboard)
29
- - ✅ **Realtime Settings** (7 parâmetros capturados interativamente)
29
+ - ✅ **Realtime Settings** (current settings captured through the Management API)
30
30
  - ✅ **Supabase .temp** (arquivos temporários do Supabase CLI)
31
31
  - ✅ **Migrations** (todas as migrations do projeto via `supabase migration fetch`)
32
32
 
@@ -217,7 +217,7 @@ npx smoonb backup --skip-supabase-version-check
217
217
  - ⚡ Edge Functions (explanation about link reset and download)
218
218
  - 📦 Storage (explanation about full backup: file download + ZIP in Dashboard format)
219
219
  - 🔐 Auth Settings (explanation about configurations)
220
- - 🔄 Realtime Settings (explanation about interactive capture of 7 parameters)
220
+ - 🔄 Realtime Settings (current configuration captured through the API)
221
221
  - 🗑️ Cleanup options (functions, .temp, migrations after backup)
222
222
  8. **Configuration summary** - Shows everything that will be done
223
223
  9. **Final confirmation** - Confirms before starting
@@ -226,7 +226,7 @@ npx smoonb backup --skip-supabase-version-check
226
226
  - 📊 2/10 - Separate Database SQL (schema, data, roles)
227
227
  - 🔧 3/10 - Database Extensions and Settings Backup
228
228
  - 🔐 4/10 - Auth Settings Backup (if selected)
229
- - 🔄 5/10 - Realtime Settings Backup (if selected) - 7 parameters captured interactively
229
+ - 🔄 5/10 - Realtime Settings Backup (if selected) - fresh API snapshot
230
230
  - 📦 6/10 - Storage Backup (if selected) - Full file download + ZIP in Dashboard format
231
231
  - 👥 7/10 - Custom Roles Backup
232
232
  - ⚡ 8/10 - Edge Functions Backup (if selected)
@@ -319,6 +319,10 @@ If a 1.0.8 restore stopped at `Configuration verification failed: smtp_pass` and
319
319
 
320
320
  Legacy Realtime captures may be stale: restoration reproduces the recorded values and date, not the source project's current settings. New backups capture Realtime directly through the API. Legacy function backups without the JWT flag default to requiring JWT; new backups preserve that flag.
321
321
 
322
+ Since 1.0.10, if Supabase does not apply numeric Realtime capacity limits, smoonb shows **backup value vs current destination value** and asks whether to retain the destination values. Starting with 1.0.11, the recommended default is **yes — `(Y/n)`**. After acceptance, it re-reads the configuration and records `completed_with_accepted_differences` with exact values, without claiming an identical restore. Security switches, service activation and connection pools still require a match. The tool does not change your plan or spend cap.
323
+
324
+ If an earlier restore ended only with `Configuration verification failed: max_presence_events_per_second, max_payload_size_in_kb`, consult its report and run `npx smoonb@1.0.11 restore` selecting **only Realtime Settings**. Do not repeat completed components. If you agree with the displayed limits, answer **yes** to the new prompt. Refusal leaves the step pending and records the differences; changes already applied are not undone.
325
+
322
326
  Reports are saved after each stage, including failures. External services cannot share the database transaction: earlier stages remain completed if a later stage fails. Do not reload the database into a populated destination. Resolve the reported stage and select only pending components, or use another empty project.
323
327
 
324
328
  Unsupported internal data (such as encrypted Vault secrets without key migration) and destination plan/version incompatibilities stop the operation. Verified status covers the selected components; validate login, email delivery and business workflows before production use.
@@ -472,7 +476,7 @@ restore/
472
476
  #### Auth, Realtime
473
477
  - **Management API**: Uses Personal Access Token
474
478
  - **JSON Export**: Configurations exported as JSON
475
- - **Realtime Settings**: Interactive capture of 7 parameters:
479
+ - **Realtime Settings**: Fresh API capture, including:
476
480
  1. Enable Realtime service
477
481
  2. Allow public access
478
482
  3. Database connection pool size
@@ -480,25 +484,25 @@ restore/
480
484
  5. Max events per second
481
485
  6. Max presence events per second
482
486
  7. Max payload size in KB
483
- - **Manual for some**: Some settings need to be applied manually for security
487
+ - **SMTP password**: Not exportable; requested with hidden input during restore.
484
488
 
485
489
  ### Restore Strategy
486
490
 
487
491
  #### Database
488
492
  - **Format Support**:
489
- - `.backup.gz` - Automatically decompresses via Docker
493
+ - `.backup.gz` - Reads compressed content without modifying the archive
490
494
  - `.backup` - Restores directly via `psql` (Docker)
491
- - **Clean Restore**: Can overwrite existing data (with confirmation)
495
+ - **Empty Destination**: Requires an empty destination for the database; skip this component when retrying later stages that failed.
492
496
 
493
497
  #### Edge Functions
494
- - **Clean Deploy**: Cleans `supabase/functions` before deploy
495
- - **Link Reset**: Ensures correct link with target project
498
+ - **Isolated Deploy**: Uses a temporary workspace, preserving local `supabase/functions` files
499
+ - **Explicit Destination**: Passes the target project reference to the CLI
496
500
  - **Automatic Deploy**: Uses `supabase functions deploy`
497
501
 
498
502
  #### Other Components
499
503
  - **Database Settings**: Restores via SQL
500
504
  - **Storage**: Restores buckets and files from ZIP (if available) or displays information for manual configuration
501
- - **Auth/Realtime**: Displays information for manual configuration in Dashboard
505
+ - **Auth/Realtime**: Applies and checks configurations through the API; reports SMTP verification limits and any explicitly accepted Realtime capacity differences.
502
506
 
503
507
  ### Cross-Platform
504
508
 
package/README.pt-BR.md CHANGED
@@ -26,7 +26,7 @@ O smoonb faz backup completo de todos os componentes do seu projeto Supabase:
26
26
  - ✅ **Edge Functions** (download automático do servidor)
27
27
  - ✅ **Auth Settings** (configurações de autenticação via Management API)
28
28
  - ✅ **Storage Buckets** (backup completo: metadados, configurações e todos os arquivos via Management API + Supabase Client, cria ZIP no padrão do Dashboard)
29
- - ✅ **Realtime Settings** (7 parâmetros capturados interativamente)
29
+ - ✅ **Realtime Settings** (configurações atuais capturadas pela Management API)
30
30
  - ✅ **Supabase .temp** (arquivos temporários do Supabase CLI)
31
31
  - ✅ **Migrations** (todas as migrations do projeto via `supabase migration fetch`)
32
32
 
@@ -319,6 +319,10 @@ Se uma restauração 1.0.8 parou em `Configuration verification failed: smtp_pas
319
319
 
320
320
  A captura antiga de Realtime pode conter valores desatualizados: a restauração reproduz os valores e a data registrados, não o estado atual do projeto original. Novos backups capturam Realtime diretamente pela API. Em backups antigos sem o sinalizador de JWT de Functions, o padrão seguro é exigir JWT; os novos backups guardam esse sinalizador.
321
321
 
322
+ Desde a versão 1.0.10, se o Supabase não aplicar limites numéricos de Realtime, o smoonb mostra **valor do backup × valor atual do destino** e pergunta se você aceita manter os valores do destino. A partir da versão 1.0.11, o padrão recomendado é **sim — `(S/n)`**. Ao aceitar, ele relê as configurações e registra `completed_with_accepted_differences`, com os valores exatos, sem afirmar que o destino é idêntico ao backup. Configurações de segurança, ativação do serviço e pools de conexão continuam exigindo correspondência. A ferramenta não altera seu plano nem o limite de gastos.
323
+
324
+ Se uma restauração anterior terminou apenas com `Configuration verification failed: max_presence_events_per_second, max_payload_size_in_kb`, consulte o relatório anterior e execute `npx smoonb@1.0.11 restore` selecionando **somente Realtime Settings**. Não repita componentes já concluídos. Se concordar com os limites mostrados, responda **sim** à nova pergunta. Recusar mantém a etapa pendente e registra as diferenças; alterações já aplicadas não são desfeitas.
325
+
322
326
  O relatório é salvo durante cada etapa, inclusive em falhas. Serviços externos não participam da transação do banco: se uma etapa posterior falhar, as anteriores permanecem registradas como concluídas. Não repita a carga do banco em um destino já preenchido. Resolva a etapa indicada e selecione apenas os componentes pendentes, ou use outro projeto vazio.
323
327
 
324
328
  Dados internos não suportados (por exemplo, Vault com segredos criptografados sem migração da chave) e configurações incompatíveis com o plano/versão do destino interrompem o processo. O status verificado cobre os componentes selecionados; ainda é necessário validar login, envio de e-mail e fluxos de negócio antes de usar o destino em produção.
@@ -472,7 +476,7 @@ restore/
472
476
  #### Auth, Realtime
473
477
  - **Management API**: Usa Personal Access Token
474
478
  - **JSON Export**: Configurações exportadas como JSON
475
- - **Realtime Settings**: Captura interativa de 7 parâmetros:
479
+ - **Realtime Settings**: Captura atual pela API, incluindo:
476
480
  1. Habilitar serviço Realtime
477
481
  2. Permitir acesso público
478
482
  3. Tamanho do pool de conexões do database
@@ -480,25 +484,25 @@ restore/
480
484
  5. Máximo de eventos por segundo
481
485
  6. Máximo de eventos de presença por segundo
482
486
  7. Tamanho máximo do payload em KB
483
- - **Manual para alguns**: Alguns settings precisam ser aplicados manualmente por segurança
487
+ - **Senha SMTP**: Não exportável; solicitada em campo oculto durante a restauração.
484
488
 
485
489
  ### Estratégia de Restauração
486
490
 
487
491
  #### Database
488
492
  - **Suporte a Formatos**:
489
- - `.backup.gz` - Descompacta automaticamente via Docker
493
+ - `.backup.gz` - Lê o conteúdo compactado sem alterar o arquivo original
490
494
  - `.backup` - Restaura diretamente via `psql` (Docker)
491
- - **Restauração Limpa**: Pode sobrescrever dados existentes (com confirmação)
495
+ - **Destino Vazio**: Exige destino vazio para restaurar o banco; desmarque esse componente ao repetir etapas posteriores que falharam.
492
496
 
493
497
  #### Edge Functions
494
- - **Deploy Limpo**: Limpa `supabase/functions` antes do deploy
495
- - **Reset de Link**: Garante link correto com projeto destino
498
+ - **Deploy Isolado**: Usa uma pasta temporária, preservando os arquivos locais de `supabase/functions`
499
+ - **Destino Explícito**: Passa a referência do projeto de destino ao CLI
496
500
  - **Deploy Automático**: Usa `supabase functions deploy`
497
501
 
498
502
  #### Outros Componentes
499
503
  - **Database Settings**: Restaura via SQL
500
504
  - **Storage**: Restaura buckets e arquivos do ZIP (se disponível) ou exibe informações para configuração manual
501
- - **Auth/Realtime**: Exibe informações para configuração manual no Dashboard
505
+ - **Auth/Realtime**: Aplica e confere as configurações pela API; registra os limites da verificação SMTP e diferenças de capacidade do Realtime aceitas explicitamente.
502
506
 
503
507
  ### Multiplataforma
504
508
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoonb",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Complete Supabase backup and migration tool. https://www.smoonb.com/#price",
5
5
  "preferGlobal": false,
6
6
  "preventGlobalInstall": true,
@@ -174,7 +174,10 @@ module.exports = async (options = {}) => {
174
174
  const restoreResults = await runRestore({ selectedBackup, components, targetProject, processDir, postgresMajor:parsedMajor, remapUrls });
175
175
 
176
176
  // Exibir resumo final
177
- ui.success(`\n🎉 ${getT('restore.index.restoreComplete')}`);
177
+ if (Object.values(restoreResults).some(result => !result.verified))
178
+ ui.warn(`\n${getT('restore.index.restoreCompleteWithNotes')}`);
179
+ else
180
+ ui.success(`\n🎉 ${getT('restore.index.restoreComplete')}`);
178
181
  ui.link(`🎯 ${getT('restore.index.targetProjectFinal', { projectId: targetProject.targetProjectId || getT('restore.index.notConfigured') })}`);
179
182
 
180
183
  if (restoreResults.database) {
@@ -211,7 +214,10 @@ module.exports = async (options = {}) => {
211
214
  }
212
215
 
213
216
  if (restoreResults.realtimeSettings) {
214
- ui.success(`🔄 ${getT('restore.index.realtimeSettingsRestored')}`);
217
+ if (restoreResults.realtimeSettings.verified)
218
+ ui.success(`🔄 ${getT('restore.index.realtimeSettingsRestored')}`);
219
+ else
220
+ ui.warn(getT('restore.realtime.accepted'));
215
221
  }
216
222
 
217
223
  sendTelemetry({
@@ -1,5 +1,15 @@
1
1
  const { isMasked } = require('../../utils/auth-secrets');
2
2
 
3
+ class ConfigurationVerificationError extends Error {
4
+ constructor(fields, updatedFields) {
5
+ super(`Configuration verification failed: ${fields.join(', ')}`);
6
+ this.name = 'ConfigurationVerificationError';
7
+ // Field names only: Auth readback may contain credentials.
8
+ this.fields = [...fields];
9
+ this.updatedFields = [...updatedFields];
10
+ }
11
+ }
12
+
3
13
  async function management(
4
14
  target,
5
15
  endpoint,
@@ -80,9 +90,7 @@ async function applyConfig(
80
90
  (k) => JSON.stringify(desired[k]) !== JSON.stringify(after[k]),
81
91
  );
82
92
  if (differences.length)
83
- throw new Error(
84
- `Configuration verification failed: ${differences.join(', ')}`,
85
- );
93
+ throw new ConfigurationVerificationError(differences, Object.keys(patch));
86
94
  return {
87
95
  success: true,
88
96
  verified: secretFields.length === 0,
@@ -93,4 +101,4 @@ async function applyConfig(
93
101
  fields_updated: Object.keys(patch),
94
102
  };
95
103
  }
96
- module.exports = { management, applyConfig };
104
+ module.exports = { management, applyConfig, ConfigurationVerificationError };
@@ -4,6 +4,10 @@ const { t } = require('../../i18n');
4
4
  const { buildRestore, readDump } = require('./dump');
5
5
  const { storagePlan } = require('./steps/06-storage');
6
6
  const { prepareAuth } = require('./auth');
7
+ const {
8
+ RealtimeRestoreError,
9
+ capacityLimits,
10
+ } = require('./steps/08-realtime-settings');
7
11
  const steps = {
8
12
  database: require('./steps/03-database'),
9
13
  edgeFunctions: require('./steps/04-edge-functions'),
@@ -110,21 +114,45 @@ async function runRestore(
110
114
  result?.email_delivery_verified === false &&
111
115
  result?.write_only_fields_accepted?.length === 1 &&
112
116
  result.write_only_fields_accepted[0] === 'smtp_pass';
113
- if (!result?.success || (!result?.verified && !acceptedSmtp))
117
+ const acceptedLimits =
118
+ name === 'realtimeSettings' &&
119
+ result?.effective_configuration_verified === true &&
120
+ result?.destination_limits_accepted === true &&
121
+ Array.isArray(result?.accepted_differences) &&
122
+ result.accepted_differences.length > 0 &&
123
+ result.accepted_differences.every(
124
+ (d) =>
125
+ capacityLimits.has(d.field) &&
126
+ Number.isInteger(d.backup_value) &&
127
+ d.backup_value > 0 &&
128
+ Number.isInteger(d.destination_value) &&
129
+ d.destination_value > 0,
130
+ );
131
+ if (
132
+ !result?.success ||
133
+ (!result?.verified && !acceptedSmtp && !acceptedLimits)
134
+ )
114
135
  throw new Error(`Component ${name} did not pass verification`);
115
136
  report.results[name] = {
116
137
  ...result,
117
138
  status: result.verified
118
139
  ? 'verified'
119
- : 'applied_with_unverified_secrets',
140
+ : acceptedLimits
141
+ ? 'completed_with_accepted_differences'
142
+ : 'applied_with_unverified_secrets',
120
143
  };
121
144
  save();
122
145
  if (acceptedSmtp)
123
146
  console.log((global.smoonbI18n?.t || t)('restore.smtp.accepted'));
124
147
  }
125
- report.status = Object.values(report.results).every((r) => r.verified)
126
- ? 'verified'
127
- : 'applied_with_unverified_secrets';
148
+ const results = Object.values(report.results);
149
+ report.status = results.some(
150
+ (r) => r.status === 'completed_with_accepted_differences',
151
+ )
152
+ ? 'completed_with_accepted_differences'
153
+ : results.every((r) => r.verified)
154
+ ? 'verified'
155
+ : 'applied_with_unverified_secrets';
128
156
  report.completed_at = new Date().toISOString();
129
157
  save();
130
158
  return report.results;
@@ -132,6 +160,8 @@ async function runRestore(
132
160
  report.status = 'failed';
133
161
  report.failed_component = current;
134
162
  report.results[current] = { success: false, status: 'failed' };
163
+ if (current === 'realtimeSettings' && error instanceof RealtimeRestoreError)
164
+ report.results[current].unapplied_limits = error.differences;
135
165
  report.completed_at = new Date().toISOString();
136
166
  save();
137
167
  throw error;
@@ -1,6 +1,12 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { applyConfig } = require('../management');
3
+ const {
4
+ applyConfig,
5
+ management,
6
+ ConfigurationVerificationError,
7
+ } = require('../management');
8
+ const { confirm } = require('../../../utils/prompt');
9
+ const { t } = require('../../../i18n');
4
10
  const allowed = [
5
11
  'private_only',
6
12
  'connection_pool',
@@ -15,7 +21,35 @@ const allowed = [
15
21
  'suspend',
16
22
  'presence_enabled',
17
23
  ];
18
- module.exports = async ({ backupPath, targetProject }) => {
24
+ // Only capacity limits can be explicitly retained. Security, service switches
25
+ // and database connection pools must still match the backup exactly.
26
+ const capacityLimits = new Set([
27
+ 'max_concurrent_users',
28
+ 'max_events_per_second',
29
+ 'max_bytes_per_second',
30
+ 'max_channels_per_client',
31
+ 'max_joins_per_second',
32
+ 'max_presence_events_per_second',
33
+ 'max_payload_size_in_kb',
34
+ ]);
35
+ class RealtimeRestoreError extends Error {
36
+ constructor(message, differences = []) {
37
+ super(message);
38
+ this.name = 'RealtimeRestoreError';
39
+ this.differences = differences;
40
+ }
41
+ }
42
+ module.exports = async (
43
+ { backupPath, targetProject },
44
+ {
45
+ request = management,
46
+ confirmLimits = confirm,
47
+ interactive = Boolean(process.stdin.isTTY),
48
+ wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
49
+ log = console.log,
50
+ } = {},
51
+ ) => {
52
+ const getT = global.smoonbI18n?.t || t;
19
53
  const backup = JSON.parse(
20
54
  fs.readFileSync(path.join(backupPath, 'realtime-settings.json'), 'utf8'),
21
55
  );
@@ -44,18 +78,117 @@ module.exports = async ({ backupPath, targetProject }) => {
44
78
  ];
45
79
  }),
46
80
  );
47
- console.log(
48
- `Realtime: restoring the recorded snapshot (${backup.realtime_settings.captured_at || 'date unavailable'}).`,
81
+ log(
82
+ getT('restore.realtime.snapshot', {
83
+ date:
84
+ backup.realtime_settings.captured_at ||
85
+ getT('restore.realtime.unknownDate'),
86
+ }),
49
87
  );
50
88
  }
51
- const result = await applyConfig(
52
- targetProject,
53
- 'config/realtime',
54
- desired,
55
- allowed,
56
- );
57
- return {
58
- ...result,
59
- captured_at: backup.captured_at || backup.realtime_settings?.captured_at,
60
- };
89
+ const capturedAt =
90
+ backup.captured_at || backup.realtime_settings?.captured_at;
91
+ const mismatches = (state) =>
92
+ Object.keys(desired).filter(
93
+ (key) => JSON.stringify(desired[key]) !== JSON.stringify(state[key]),
94
+ );
95
+ try {
96
+ const result = await applyConfig(
97
+ targetProject,
98
+ 'config/realtime',
99
+ desired,
100
+ allowed,
101
+ request,
102
+ );
103
+ return { ...result, captured_at: capturedAt };
104
+ } catch (error) {
105
+ if (!(error instanceof ConfigurationVerificationError)) throw error;
106
+ // A successful PATCH may take time to become visible. Never repeat PATCH
107
+ // just to poll: changing Realtime settings can disconnect clients.
108
+ let actual;
109
+ let fields;
110
+ for (const delay of [1000, 2000]) {
111
+ await wait(delay);
112
+ actual = await request(targetProject, 'config/realtime');
113
+ fields = mismatches(actual);
114
+ if (!fields.length)
115
+ return {
116
+ success: true,
117
+ verified: true,
118
+ configuration_readback_verified: true,
119
+ fields_verified: Object.keys(desired).length,
120
+ fields_updated: error.updatedFields,
121
+ captured_at: capturedAt,
122
+ };
123
+ }
124
+ const canRetain = fields.every(
125
+ (key) =>
126
+ capacityLimits.has(key) &&
127
+ Number.isInteger(desired[key]) &&
128
+ desired[key] > 0 &&
129
+ Number.isInteger(actual[key]) &&
130
+ actual[key] > 0,
131
+ );
132
+ if (!canRetain)
133
+ throw new RealtimeRestoreError(
134
+ getT('restore.realtime.strictMismatch', { fields: fields.join(', ') }),
135
+ );
136
+ const differences = fields.map((field) => ({
137
+ field,
138
+ backup_value: desired[field],
139
+ destination_value: actual[field],
140
+ }));
141
+ log(getT('restore.realtime.limitsNotApplied'));
142
+ for (const difference of differences)
143
+ log(
144
+ getT('restore.realtime.difference', {
145
+ field: getT(`restore.realtime.fields.${difference.field}`),
146
+ backup: difference.backup_value,
147
+ destination: difference.destination_value,
148
+ }),
149
+ );
150
+ log(getT('restore.realtime.limitsExplanation'));
151
+ if (
152
+ !interactive ||
153
+ (await confirmLimits(getT('restore.realtime.acceptLimits'), true)) !==
154
+ true
155
+ )
156
+ throw new RealtimeRestoreError(
157
+ getT('restore.realtime.notAccepted'),
158
+ differences,
159
+ );
160
+ // The user approved these exact numeric differences. A fresh read must
161
+ // still match both the approved limits and every other backed-up field.
162
+ const accepted = {
163
+ ...desired,
164
+ ...Object.fromEntries(
165
+ differences.map((d) => [d.field, d.destination_value]),
166
+ ),
167
+ };
168
+ const final = await request(targetProject, 'config/realtime');
169
+ if (
170
+ Object.keys(accepted).some(
171
+ (key) => JSON.stringify(accepted[key]) !== JSON.stringify(final[key]),
172
+ )
173
+ )
174
+ throw new RealtimeRestoreError(
175
+ getT('restore.realtime.changed'),
176
+ differences,
177
+ );
178
+ log(getT('restore.realtime.accepted'));
179
+ return {
180
+ success: true,
181
+ verified: false,
182
+ effective_configuration_verified: true,
183
+ destination_limits_accepted: true,
184
+ accepted_differences: differences,
185
+ fields_verified: Object.keys(desired).length - differences.length,
186
+ fields_updated: error.updatedFields.filter(
187
+ (key) => !fields.includes(key),
188
+ ),
189
+ captured_at: capturedAt,
190
+ };
191
+ }
61
192
  };
193
+ module.exports.RealtimeRestoreError = RealtimeRestoreError;
194
+ module.exports.capacityLimits = capacityLimits;
@@ -24,6 +24,24 @@
24
24
  "restore.smtp.required": "Enter the real provider password, not the protected value in auth-settings.json. Use Ctrl+C to cancel before restoring any components.",
25
25
  "restore.smtp.interactiveRequired": "Restoring Auth requires the SMTP password. Run in an interactive terminal to enter it securely before any components are restored.",
26
26
  "restore.smtp.accepted": "⚠️ Auth: settings checked; SMTP password accepted by the API. Supabase does not allow password readback verification. Test an authentication email before using the project.",
27
+ "restore.realtime.snapshot": "Realtime: using the snapshot recorded at {date}.",
28
+ "restore.realtime.unknownDate": "unknown date",
29
+ "restore.realtime.limitsNotApplied": "⚠️ Supabase did not apply these Realtime limits from the backup:",
30
+ "restore.realtime.difference": " - {field}: backup = {backup}; destination = {destination}.",
31
+ "restore.realtime.limitsExplanation": "Limits may depend on the plan and organization billing restrictions; this response does not identify the cause. You can keep the current destination values. This choice will be recorded as accepted differences, without claiming an identical copy of the backup. No plan or spend cap will be changed.",
32
+ "restore.realtime.acceptLimits": "Keep these destination limits and complete Realtime with the differences above recorded?",
33
+ "restore.realtime.notAccepted": "Realtime remains pending: the backup limits were not applied and the destination values were not accepted. Changes already applied were not undone. Consult report.json and select only Realtime Settings in an interactive terminal to retry this step.",
34
+ "restore.realtime.changed": "Realtime configuration changed during confirmation. The step remains pending; consult report.json and select only Realtime Settings to retry.",
35
+ "restore.realtime.strictMismatch": "Realtime did not confirm these settings: {fields}. This difference cannot be accepted as a capacity adjustment. The step remains pending; consult report.json and select only Realtime Settings to retry.",
36
+ "restore.realtime.accepted": "⚠️ Realtime completed with destination limits accepted by you. Differences from the backup are recorded in report.json; the other Realtime settings from the backup were checked.",
37
+ "restore.realtime.fields.max_concurrent_users": "Concurrent clients",
38
+ "restore.realtime.fields.max_events_per_second": "Events per second",
39
+ "restore.realtime.fields.max_bytes_per_second": "Bytes per second",
40
+ "restore.realtime.fields.max_channels_per_client": "Channels per client",
41
+ "restore.realtime.fields.max_joins_per_second": "Channel joins per second",
42
+ "restore.realtime.fields.max_presence_events_per_second": "Presence events per second",
43
+ "restore.realtime.fields.max_payload_size_in_kb": "Maximum message size (KB)",
44
+ "restore.index.restoreCompleteWithNotes": "Selected components restored with notes. Review the accepted differences and pending verification below.",
27
45
 
28
46
  "error.generic": "Something went wrong.",
29
47
  "error.pathNotFound": "Path not found: {path}",
@@ -576,7 +594,7 @@
576
594
 
577
595
  "restore.steps.components.auth.title": "Auth Settings:",
578
596
  "restore.steps.components.auth.description1": "Auth settings will be applied through the API and verified on the destination.",
579
- "restore.steps.components.auth.description2": "Includes SMTP and redirects recorded in the backup.",
597
+ "restore.steps.components.auth.description2": "Includes redirects and SMTP settings; the real provider password will be requested when needed.",
580
598
  "restore.steps.components.auth.include": "Do you want to restore Auth",
581
599
 
582
600
  "restore.steps.components.storage.title": "Storage:",
@@ -595,7 +613,7 @@
595
613
 
596
614
  "restore.steps.components.realtime.title": "Realtime Settings:",
597
615
  "restore.steps.components.realtime.description1": "Realtime settings from the backup will be applied and verified through the API.",
598
- "restore.steps.components.realtime.description2": "Legacy captures reproduce the values recorded on their capture date.",
616
+ "restore.steps.components.realtime.description2": "Uses the recorded snapshot; limits that are not applied will be shown for your decision.",
599
617
  "restore.steps.components.realtime.include": "Do you want to restore Realtime",
600
618
  "restore.steps.components.database.include": "Do you want to restore Database",
601
619
 
@@ -752,21 +770,21 @@
752
770
  "restore.index.targetProjectFinal": "Target project: {projectId}",
753
771
  "restore.index.databaseRestored": "Database: Restored successfully via Docker",
754
772
  "restore.index.edgeFunctionsRestored": "Edge Functions: {success}/{total} functions restored",
755
- "restore.index.authSettingsRestored": "Auth Settings: Settings displayed for manual configuration",
773
+ "restore.index.authSettingsRestored": "Auth Settings: Settings applied and checked through the API",
756
774
  "restore.index.storageRestored": "Storage: {buckets} bucket(s) restored, {files} file(s) uploaded",
757
775
  "restore.index.storageMetadataOnly": "Storage: {buckets} bucket(s) found - metadata only (.storage.zip file not found)",
758
776
  "restore.index.databaseSettingsRestored": "Database Settings: Extensions and settings restored via SQL",
759
- "restore.index.realtimeSettingsRestored": "Realtime Settings: Settings displayed for manual configuration",
777
+ "restore.index.realtimeSettingsRestored": "Realtime Settings: Settings applied and checked through the API",
760
778
  "restore.index.error": "Error during restoration: {message}",
761
779
 
762
780
  "restore.utils.componentsTitle": "Components that will be restored:",
763
781
  "restore.utils.database": "Database (psql -f via Docker)",
764
782
  "restore.utils.edgeFunctions": "Edge Functions: {count} function(s)",
765
- "restore.utils.authSettings": "Auth Settings: Display URL and values for manual configuration",
783
+ "restore.utils.authSettings": "Auth Settings: Apply and check through the API; SMTP password requested when needed",
766
784
  "restore.utils.storageBuckets": "Storage Buckets: Automatic restoration of buckets and files via API",
767
785
  "restore.utils.storageMetadataOnly": "Storage Buckets: Display information (metadata only - .storage.zip file not found)",
768
786
  "restore.utils.databaseSettings": "Database Extensions and Settings: Restore via SQL",
769
- "restore.utils.realtimeSettings": "Realtime Settings: Display URL and values for manual configuration",
787
+ "restore.utils.realtimeSettings": "Realtime Settings: Apply and check through the API; limit differences require your choice",
770
788
  "restore.utils.summaryTitle": "Restoration Summary:",
771
789
  "restore.utils.summarySeparator": "═══════════════════════════════════════════════════════════════════════════════",
772
790
  "restore.utils.backup": "Backup: {name}",
@@ -24,6 +24,24 @@
24
24
  "restore.smtp.required": "Informe a senha real do provedor, não o valor protegido de auth-settings.json. Use Ctrl+C para cancelar antes de restaurar os componentes.",
25
25
  "restore.smtp.interactiveRequired": "A restauração de Auth precisa da senha SMTP. Execute em um terminal interativo para informá-la de forma oculta antes de restaurar os componentes.",
26
26
  "restore.smtp.accepted": "⚠️ Auth: configurações conferidas; senha SMTP aceita pela API. O Supabase não permite conferir a senha por leitura. Teste o envio de e-mail de autenticação antes de usar o projeto.",
27
+ "restore.realtime.snapshot": "Realtime: usando a captura registrada em {date}.",
28
+ "restore.realtime.unknownDate": "data não informada",
29
+ "restore.realtime.limitsNotApplied": "⚠️ O Supabase não aplicou estes limites de Realtime do backup:",
30
+ "restore.realtime.difference": " - {field}: backup = {backup}; destino = {destination}.",
31
+ "restore.realtime.limitsExplanation": "Limites podem depender do plano e das restrições de cobrança da organização; esta resposta não informa a causa. Você pode manter os valores atuais do destino. Essa escolha será registrada no relatório como diferença aceita, sem declarar uma cópia idêntica do backup. Nenhum plano ou limite de gastos será alterado.",
32
+ "restore.realtime.acceptLimits": "Manter esses limites do destino e concluir Realtime com as diferenças acima registradas?",
33
+ "restore.realtime.notAccepted": "Realtime permanece pendente: os limites do backup não foram aplicados e os valores do destino não foram aceitos. Alterações já aplicadas não foram desfeitas. Consulte report.json e, para tentar esta etapa novamente, selecione somente Realtime Settings em um terminal interativo.",
34
+ "restore.realtime.changed": "A configuração Realtime mudou durante a confirmação. A etapa permanece pendente; consulte report.json e tente novamente selecionando somente Realtime Settings.",
35
+ "restore.realtime.strictMismatch": "Realtime não confirmou estas configurações: {fields}. Essa diferença não pode ser aceita como ajuste de limites. A etapa permanece pendente; consulte report.json e tente novamente selecionando somente Realtime Settings.",
36
+ "restore.realtime.accepted": "⚠️ Realtime concluído com os limites do destino aceitos por você. As diferenças do backup estão registradas em report.json; as demais configurações de Realtime do backup foram conferidas.",
37
+ "restore.realtime.fields.max_concurrent_users": "Clientes simultâneos",
38
+ "restore.realtime.fields.max_events_per_second": "Eventos por segundo",
39
+ "restore.realtime.fields.max_bytes_per_second": "Bytes por segundo",
40
+ "restore.realtime.fields.max_channels_per_client": "Canais por cliente",
41
+ "restore.realtime.fields.max_joins_per_second": "Entradas em canais por segundo",
42
+ "restore.realtime.fields.max_presence_events_per_second": "Eventos de presença por segundo",
43
+ "restore.realtime.fields.max_payload_size_in_kb": "Tamanho máximo da mensagem (KB)",
44
+ "restore.index.restoreCompleteWithNotes": "Restauração dos componentes selecionados concluída com observações. Confira abaixo as diferenças aceitas e verificações pendentes.",
27
45
 
28
46
  "error.generic": "Algo inesperado aconteceu.",
29
47
  "error.pathNotFound": "Caminho não encontrado: {path}",
@@ -575,7 +593,7 @@
575
593
 
576
594
  "restore.steps.components.auth.title": "Auth Settings:",
577
595
  "restore.steps.components.auth.description1": "As configurações de Auth serão aplicadas pela API e verificadas no destino.",
578
- "restore.steps.components.auth.description2": "Inclui SMTP e redirecionamentos registrados no backup.",
596
+ "restore.steps.components.auth.description2": "Inclui redirecionamentos e configurações SMTP; a senha real do provedor será solicitada quando necessária.",
579
597
  "restore.steps.components.auth.include": "Deseja restaurar Auth",
580
598
 
581
599
  "restore.steps.components.storage.title": "Storage:",
@@ -594,7 +612,7 @@
594
612
 
595
613
  "restore.steps.components.realtime.title": "Realtime Settings:",
596
614
  "restore.steps.components.realtime.description1": "As configurações de Realtime do backup serão aplicadas e verificadas pela API.",
597
- "restore.steps.components.realtime.description2": "Capturas antigas reproduzem os valores registrados naquela data.",
615
+ "restore.steps.components.realtime.description2": "Usa os valores registrados na captura; limites não aplicados serão mostrados para sua decisão.",
598
616
  "restore.steps.components.realtime.include": "Deseja restaurar Realtime",
599
617
  "restore.steps.components.database.include": "Deseja restaurar Database",
600
618
 
@@ -751,21 +769,21 @@
751
769
  "restore.index.targetProjectFinal": "Projeto destino: {projectId}",
752
770
  "restore.index.databaseRestored": "Database: Restaurada com sucesso via Docker",
753
771
  "restore.index.edgeFunctionsRestored": "Edge Functions: {success}/{total} functions restauradas",
754
- "restore.index.authSettingsRestored": "Auth Settings: Configurações exibidas para configuração manual",
772
+ "restore.index.authSettingsRestored": "Auth Settings: Configurações aplicadas e conferidas pela API",
755
773
  "restore.index.storageRestored": "Storage: {buckets} bucket(s) restaurado(s), {files} arquivo(s) enviado(s)",
756
774
  "restore.index.storageMetadataOnly": "Storage: {buckets} bucket(s) encontrado(s) - apenas metadados (arquivo .storage.zip não encontrado)",
757
775
  "restore.index.databaseSettingsRestored": "Database Settings: Extensões e configurações restauradas via SQL",
758
- "restore.index.realtimeSettingsRestored": "Realtime Settings: Configurações exibidas para configuração manual",
776
+ "restore.index.realtimeSettingsRestored": "Realtime Settings: Configurações aplicadas e conferidas pela API",
759
777
  "restore.index.error": "Erro na restauração: {message}",
760
778
 
761
779
  "restore.utils.componentsTitle": "Componentes que serão restaurados:",
762
780
  "restore.utils.database": "Database (psql -f via Docker)",
763
781
  "restore.utils.edgeFunctions": "Edge Functions: {count} function(s)",
764
- "restore.utils.authSettings": "Auth Settings: Exibir URL e valores para configuração manual",
782
+ "restore.utils.authSettings": "Auth Settings: Aplicar pela API e conferir as configurações; senha SMTP solicitada quando necessária",
765
783
  "restore.utils.storageBuckets": "Storage Buckets: Restauração automática de buckets e arquivos via API",
766
784
  "restore.utils.storageMetadataOnly": "Storage Buckets: Exibir informações (apenas metadados - arquivo .storage.zip não encontrado)",
767
785
  "restore.utils.databaseSettings": "Database Extensions and Settings: Restaurar via SQL",
768
- "restore.utils.realtimeSettings": "Realtime Settings: Exibir URL e valores para configuração manual",
786
+ "restore.utils.realtimeSettings": "Realtime Settings: Aplicar pela API e conferir; diferenças nos limites exigem sua escolha",
769
787
  "restore.utils.summaryTitle": "Resumo da Restauração:",
770
788
  "restore.utils.summarySeparator": "═══════════════════════════════════════════════════════════════════════════════",
771
789
  "restore.utils.backup": "Backup: {name}",