smoonb 1.0.11 → 1.0.13
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 +167 -150
- package/README.md +695 -687
- package/README.pt-BR.md +697 -687
- package/npm-shrinkwrap.json +2030 -0
- package/package.json +71 -68
- package/src/commands/backup/index.js +39 -21
- package/src/commands/backup/outcome.js +16 -0
- package/src/commands/backup/steps/00-license.js +427 -427
- package/src/commands/backup/steps/02-database.js +6 -6
- package/src/commands/backup/steps/03-database-separated.js +129 -161
- package/src/commands/backup/steps/04-database-settings.js +24 -40
- package/src/commands/backup/steps/07-storage.js +11 -10
- package/src/commands/backup/steps/08-custom-roles.js +5 -55
- package/src/commands/backup/steps/10-supabase-temp.js +0 -9
- package/src/commands/backup/steps/11-migrations.js +30 -74
- package/src/i18n/locales/en.json +825 -824
- package/src/i18n/locales/pt-BR.json +824 -823
- package/src/interactive/envMapper.js +6 -12
- package/src/utils/docker.js +6 -53
- package/src/utils/pg-dump-diagnostics.js +68 -64
- package/src/utils/supabaseCli.js +26 -32
|
@@ -307,10 +307,8 @@ async function askComponentsFlags() {
|
|
|
307
307
|
const includeFunctions = await confirm(getT('backup.components.edgeFunctions.include'), true);
|
|
308
308
|
|
|
309
309
|
// Pergunta de limpeza de functions imediatamente após
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
cleanFunctions = await confirm(getT('backup.components.edgeFunctions.cleanup'), false);
|
|
313
|
-
}
|
|
310
|
+
const cleanFunctions = false;
|
|
311
|
+
|
|
314
312
|
|
|
315
313
|
// Explicação sobre .temp
|
|
316
314
|
console.log(chalk.cyan(`\n📁 ${getT('backup.components.temp.title')}`));
|
|
@@ -320,10 +318,8 @@ async function askComponentsFlags() {
|
|
|
320
318
|
const includeTemp = await confirm(getT('backup.components.temp.include'), true);
|
|
321
319
|
|
|
322
320
|
// Pergunta de limpeza de .temp imediatamente após
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
cleanTemp = await confirm(getT('backup.components.temp.cleanup'), false);
|
|
326
|
-
}
|
|
321
|
+
const cleanTemp = false;
|
|
322
|
+
|
|
327
323
|
|
|
328
324
|
// Explicação sobre Migrations
|
|
329
325
|
console.log(chalk.cyan(`\n📋 ${getT('backup.components.migrations.title')}`));
|
|
@@ -334,10 +330,8 @@ async function askComponentsFlags() {
|
|
|
334
330
|
const includeMigrations = await confirm(getT('backup.components.migrations.include'), true);
|
|
335
331
|
|
|
336
332
|
// Pergunta de limpeza de migrations imediatamente após
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
cleanMigrations = await confirm(getT('backup.components.migrations.cleanup'), false);
|
|
340
|
-
}
|
|
333
|
+
const cleanMigrations = false;
|
|
334
|
+
|
|
341
335
|
|
|
342
336
|
// Continuar com outras perguntas
|
|
343
337
|
const includeStorage = await confirm(getT('backup.components.storage.include'), true);
|
package/src/utils/docker.js
CHANGED
|
@@ -2,6 +2,7 @@ const { exec } = require('child_process');
|
|
|
2
2
|
const { promisify } = require('util');
|
|
3
3
|
|
|
4
4
|
const execAsync = promisify(exec);
|
|
5
|
+
const { runSupabase } = require('./supabaseCli');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Detecta se Docker Desktop está instalado no sistema
|
|
@@ -37,7 +38,7 @@ async function detectDockerRunning() {
|
|
|
37
38
|
*/
|
|
38
39
|
async function detectSupabaseCLI() {
|
|
39
40
|
try {
|
|
40
|
-
|
|
41
|
+
runSupabase('supabase', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
41
42
|
return true;
|
|
42
43
|
} catch {
|
|
43
44
|
return false;
|
|
@@ -50,7 +51,7 @@ async function detectSupabaseCLI() {
|
|
|
50
51
|
*/
|
|
51
52
|
async function getSupabaseCLIVersion() {
|
|
52
53
|
try {
|
|
53
|
-
const { stdout } =
|
|
54
|
+
const { stdout } = runSupabase('supabase', ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
54
55
|
const match = stdout.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
55
56
|
return match ? `${match[1]}.${match[2]}.${match[3]}` : null;
|
|
56
57
|
} catch {
|
|
@@ -178,7 +179,7 @@ async function detectDockerDependencies() {
|
|
|
178
179
|
* @param {boolean} [opts.skipSupabaseVersionCheck=false] - Pular checagem de versão do Supabase CLI
|
|
179
180
|
* @returns {Promise<{canBackupComplete: boolean, reason?: string, dockerStatus: any}>}
|
|
180
181
|
*/
|
|
181
|
-
async function canPerformCompleteBackup(
|
|
182
|
+
async function canPerformCompleteBackup() {
|
|
182
183
|
const dockerStatus = await detectDockerDesktop();
|
|
183
184
|
|
|
184
185
|
if (!dockerStatus.installed) {
|
|
@@ -206,54 +207,6 @@ async function canPerformCompleteBackup({ skipSupabaseVersionCheck = false } = {
|
|
|
206
207
|
};
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
|
|
210
|
-
return {
|
|
211
|
-
canBackupComplete: true,
|
|
212
|
-
supabaseVersionCheckSkipped: true,
|
|
213
|
-
dockerStatus
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const supabaseCliVersion = await getSupabaseCLIVersion();
|
|
218
|
-
const latestResult = await getSupabaseCLILatestVersion();
|
|
219
|
-
if (latestResult.error) {
|
|
220
|
-
return {
|
|
221
|
-
canBackupComplete: false,
|
|
222
|
-
reason: 'supabase_cli_latest_unknown',
|
|
223
|
-
latestError: latestResult.error,
|
|
224
|
-
supabaseCliVersion: supabaseCliVersion || null,
|
|
225
|
-
dockerStatus
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const minVersion = getMinAcceptableVersion(latestResult.version);
|
|
230
|
-
if (supabaseCliVersion && minVersion && compareSemver(supabaseCliVersion, minVersion) < 0) {
|
|
231
|
-
return {
|
|
232
|
-
canBackupComplete: false,
|
|
233
|
-
reason: 'supabase_cli_outdated',
|
|
234
|
-
supabaseCliVersion,
|
|
235
|
-
supabaseCliLatest: latestResult.version,
|
|
236
|
-
supabaseCliMinVersion: minVersion,
|
|
237
|
-
dockerStatus
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
canBackupComplete: true,
|
|
243
|
-
dockerStatus
|
|
244
|
-
};
|
|
210
|
+
return { canBackupComplete: true, dockerStatus };
|
|
245
211
|
}
|
|
246
|
-
|
|
247
|
-
module.exports = {
|
|
248
|
-
detectDockerInstallation,
|
|
249
|
-
detectDockerRunning,
|
|
250
|
-
detectSupabaseCLI,
|
|
251
|
-
detectDockerDependencies,
|
|
252
|
-
detectDockerDesktop,
|
|
253
|
-
getDockerVersion,
|
|
254
|
-
getSupabaseCLIVersion,
|
|
255
|
-
getSupabaseCLILatestVersion,
|
|
256
|
-
compareSemver,
|
|
257
|
-
getMinAcceptableVersion,
|
|
258
|
-
canPerformCompleteBackup
|
|
259
|
-
};
|
|
212
|
+
module.exports = { detectDockerInstallation, detectDockerRunning, detectSupabaseCLI, detectDockerDependencies, detectDockerDesktop, getDockerVersion, getSupabaseCLIVersion, compareSemver, getMinAcceptableVersion, getSupabaseCLILatestVersion, canPerformCompleteBackup };
|
|
@@ -1,64 +1,68 @@
|
|
|
1
|
-
const { StringDecoder } = require('string_decoder');
|
|
2
|
-
const fs = require('fs').promises;
|
|
3
|
-
|
|
4
|
-
// Only the exact, known pg_dump circular-FK block is consolidated. All other
|
|
5
|
-
// stderr (including errors and unfamiliar/localized warnings) remains visible.
|
|
6
|
-
function createPgDumpDiagnostics(writeStderr) {
|
|
7
|
-
const decoder = new StringDecoder('utf8');
|
|
8
|
-
let pending = '';
|
|
9
|
-
let inCircularWarning = false;
|
|
10
|
-
const warnings = [];
|
|
11
|
-
const tables = new Set();
|
|
12
|
-
const hints = new Set([
|
|
13
|
-
'pg_dump: hint: You might not be able to restore the dump without using --disable-triggers or temporarily dropping the constraints.',
|
|
14
|
-
'pg_dump: hint: Consider using a full dump instead of a --data-only dump to avoid this problem.'
|
|
15
|
-
]);
|
|
16
|
-
function line(raw) {
|
|
17
|
-
const text = raw.replace(/\r?\n$/, '');
|
|
18
|
-
if (/^pg_dump: warning: there are circular foreign-key constraints (?:among these tables|on this table):$/.test(text)) {
|
|
19
|
-
inCircularWarning = true;
|
|
20
|
-
warnings.push(text);
|
|
21
|
-
} else if (inCircularWarning && text.startsWith('pg_dump: detail: ')) {
|
|
22
|
-
tables.add(text.slice('pg_dump: detail: '.length));
|
|
23
|
-
warnings.push(text);
|
|
24
|
-
} else if (inCircularWarning && hints.has(text)) {
|
|
25
|
-
warnings.push(text);
|
|
26
|
-
} else {
|
|
27
|
-
inCircularWarning = false;
|
|
28
|
-
writeStderr(raw);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function consume(text) {
|
|
32
|
-
pending += text;
|
|
33
|
-
let end;
|
|
34
|
-
while ((end = pending.indexOf('\n')) !== -1) {
|
|
35
|
-
line(pending.slice(0, end + 1));
|
|
36
|
-
pending = pending.slice(end + 1);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
write(chunk) { consume(decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))); },
|
|
41
|
-
end() {
|
|
42
|
-
consume(decoder.end());
|
|
43
|
-
if (pending) line(pending);
|
|
44
|
-
pending = '';
|
|
45
|
-
return { circularForeignKeys: warnings.length > 0, tables: [...tables], messages: warnings };
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Supabase prepends this command before pg_dump output. Check the first line,
|
|
51
|
-
// not arbitrary table contents: a data row containing SQL is not protection.
|
|
52
|
-
async function hasReplicaRolePrologue(file) {
|
|
53
|
-
const handle = await fs.open(file, 'r');
|
|
54
|
-
try {
|
|
55
|
-
const buffer = Buffer.alloc(256);
|
|
56
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
57
|
-
const firstLine = buffer.toString('utf8', 0, bytesRead).split('\n')[0].replace(/^\uFEFF/, '').trim();
|
|
58
|
-
return /^SET\s+session_replication_role\s*=\s*(?:replica|'replica');$/i.test(firstLine);
|
|
59
|
-
} finally {
|
|
60
|
-
await handle.close();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
const { StringDecoder } = require('string_decoder');
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
|
|
4
|
+
// Only the exact, known pg_dump circular-FK block is consolidated. All other
|
|
5
|
+
// stderr (including errors and unfamiliar/localized warnings) remains visible.
|
|
6
|
+
function createPgDumpDiagnostics(writeStderr) {
|
|
7
|
+
const decoder = new StringDecoder('utf8');
|
|
8
|
+
let pending = '';
|
|
9
|
+
let inCircularWarning = false;
|
|
10
|
+
const warnings = [];
|
|
11
|
+
const tables = new Set();
|
|
12
|
+
const hints = new Set([
|
|
13
|
+
'pg_dump: hint: You might not be able to restore the dump without using --disable-triggers or temporarily dropping the constraints.',
|
|
14
|
+
'pg_dump: hint: Consider using a full dump instead of a --data-only dump to avoid this problem.'
|
|
15
|
+
]);
|
|
16
|
+
function line(raw) {
|
|
17
|
+
const text = raw.replace(/\r?\n$/, '');
|
|
18
|
+
if (/^pg_dump: warning: there are circular foreign-key constraints (?:among these tables|on this table):$/.test(text)) {
|
|
19
|
+
inCircularWarning = true;
|
|
20
|
+
warnings.push(text);
|
|
21
|
+
} else if (inCircularWarning && text.startsWith('pg_dump: detail: ')) {
|
|
22
|
+
tables.add(text.slice('pg_dump: detail: '.length));
|
|
23
|
+
warnings.push(text);
|
|
24
|
+
} else if (inCircularWarning && hints.has(text)) {
|
|
25
|
+
warnings.push(text);
|
|
26
|
+
} else {
|
|
27
|
+
inCircularWarning = false;
|
|
28
|
+
writeStderr(raw);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function consume(text) {
|
|
32
|
+
pending += text;
|
|
33
|
+
let end;
|
|
34
|
+
while ((end = pending.indexOf('\n')) !== -1) {
|
|
35
|
+
line(pending.slice(0, end + 1));
|
|
36
|
+
pending = pending.slice(end + 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
write(chunk) { consume(decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))); },
|
|
41
|
+
end() {
|
|
42
|
+
consume(decoder.end());
|
|
43
|
+
if (pending) line(pending);
|
|
44
|
+
pending = '';
|
|
45
|
+
return { circularForeignKeys: warnings.length > 0, tables: [...tables], messages: warnings };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Supabase prepends this command before pg_dump output. Check the first line,
|
|
51
|
+
// not arbitrary table contents: a data row containing SQL is not protection.
|
|
52
|
+
async function hasReplicaRolePrologue(file) {
|
|
53
|
+
const handle = await fs.open(file, 'r');
|
|
54
|
+
try {
|
|
55
|
+
const buffer = Buffer.alloc(256);
|
|
56
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
57
|
+
const firstLine = buffer.toString('utf8', 0, bytesRead).split('\n')[0].replace(/^\uFEFF/, '').trim();
|
|
58
|
+
return /^SET\s+session_replication_role\s*=\s*(?:replica|'replica');$/i.test(firstLine);
|
|
59
|
+
} finally {
|
|
60
|
+
await handle.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The bundled CLI is deliberately pinned. Suppress only its generic upgrade advert.
|
|
65
|
+
function withoutCliUpgradeNotice(text) {
|
|
66
|
+
return String(text).split(/(?<=\n)/).filter(line => !/^A new version of Supabase CLI is available: v[0-9.]+ \(currently installed v[0-9.]+\)\r?\n?$/.test(line) && !/^We recommend updating regularly for new features and bug fixes: https:\/\/supabase\.com\/docs\/guides\/cli\/getting-started#updating-the-supabase-cli\r?\n?$/.test(line)).join('');
|
|
67
|
+
}
|
|
68
|
+
module.exports = { createPgDumpDiagnostics, hasReplicaRolePrologue, withoutCliUpgradeNotice };
|
package/src/utils/supabaseCli.js
CHANGED
|
@@ -1,32 +1,26 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return execFileSync(entry, args, options);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
throw new Error('Supabase CLI executable was not found in PATH');
|
|
31
|
-
}
|
|
32
|
-
module.exports = { runSupabase };
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
function runSupabase(_command, args, options = {}) {
|
|
6
|
+
const root = path.dirname(require.resolve('supabase/package.json'));
|
|
7
|
+
const pkg = require(path.join(root, 'package.json'));
|
|
8
|
+
if (pkg.version !== require('../../package.json').dependencies.supabase) throw new Error('Supabase CLI version mismatch; reinstall smoonb');
|
|
9
|
+
const entry = path.resolve(root, typeof pkg.bin === 'string' ? pkg.bin : pkg.bin.supabase);
|
|
10
|
+
try {
|
|
11
|
+
const result = spawnSync(entry.endsWith('.js') ? process.execPath : entry, entry.endsWith('.js') ? [entry, ...args] : args, { ...options, shell: false, windowsHide: true, timeout: options.timeout || 180000, maxBuffer: 16 * 1024 * 1024 });
|
|
12
|
+
if (result.error || result.status !== 0) throw { status: result.status, code: result.error?.code };
|
|
13
|
+
return options.capture ? result : result.stdout;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new Error(`Supabase CLI operation failed (${error.code === 'ETIMEDOUT' ? 'timeout; check Docker image downloads and connectivity' : 'exit ' + (Number.isInteger(error.status) ? error.status : 'unavailable')}). Check connectivity and credentials.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function isolatedSupabase(args, env = {}, options = {}) {
|
|
19
|
+
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'smoonb-cli-'));
|
|
20
|
+
try {
|
|
21
|
+
await fs.promises.mkdir(path.join(root, 'supabase'));
|
|
22
|
+
await fs.promises.writeFile(path.join(root, 'supabase', 'config.toml'), 'project_id = "smoonb-backup"\n');
|
|
23
|
+
return runSupabase('supabase', [...args, '--workdir', root], { ...options, cwd: root, env: { ...process.env, ...env }, encoding: 'utf8', stdio: 'pipe' });
|
|
24
|
+
} finally { await fs.promises.rm(root, { recursive: true, force: true }); }
|
|
25
|
+
}
|
|
26
|
+
module.exports = { runSupabase, isolatedSupabase };
|