smoonb 0.0.6 → 0.0.8
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/.smoonbrc +7 -6
- package/.smoonbrc.example +2 -1
- package/README.md +164 -164
- package/backups/backup-2025-10-17T19-52-20-211Z/auth-config.json +7 -0
- package/backups/backup-2025-10-17T19-52-20-211Z/backup-manifest.json +19 -0
- package/backups/backup-2025-10-17T19-52-20-211Z/database-2025-10-17T19-52-20-215Z.dump +0 -0
- package/backups/backup-2025-10-17T19-52-20-211Z/functions/README.md +4 -0
- package/backups/backup-2025-10-17T19-52-20-211Z/realtime-config.json +7 -0
- package/backups/backup-2025-10-17T19-52-20-211Z/storage/storage-config.json +6 -0
- package/backups/backup-2025-10-17T20-38-13-188Z/auth-config.json +7 -0
- package/backups/backup-2025-10-17T20-38-13-188Z/backup-manifest.json +19 -0
- package/backups/backup-2025-10-17T20-38-13-188Z/database-2025-10-17T20-38-13-194Z.dump +0 -0
- package/backups/backup-2025-10-17T20-38-13-188Z/functions/README.md +4 -0
- package/backups/backup-2025-10-17T20-38-13-188Z/realtime-config.json +7 -0
- package/backups/backup-2025-10-17T20-38-13-188Z/storage/storage-config.json +6 -0
- package/bin/smoonb.js +16 -32
- package/package.json +1 -1
- package/src/commands/backup.js +140 -239
- package/src/commands/check.js +209 -349
- package/src/commands/config.js +78 -77
- package/src/commands/functions.js +123 -349
- package/src/commands/restore.js +122 -294
- package/src/index.js +12 -21
- package/src/services/introspect.js +299 -0
- package/src/utils/cli.js +87 -0
- package/src/utils/config.js +140 -0
- package/src/utils/fsx.js +110 -0
- package/src/utils/hash.js +40 -0
- package/src/utils/supabase.js +447 -387
- package/src/commands/secrets.js +0 -361
- /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/auth-config.json +0 -0
- /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/backup-manifest.json +0 -0
- /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/functions/README.md +0 -0
- /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/realtime-config.json +0 -0
- /package/{backup → backups}/backup-2025-10-17T19-18-58-539Z/storage/storage-config.json +0 -0
package/src/commands/backup.js
CHANGED
|
@@ -1,276 +1,177 @@
|
|
|
1
|
-
|
|
2
|
-
* Comando de backup completo do projeto Supabase
|
|
3
|
-
* Implementação técnica real baseada em pesquisa extensiva
|
|
4
|
-
*/
|
|
5
|
-
|
|
1
|
+
const { Command } = require('commander');
|
|
6
2
|
const chalk = require('chalk');
|
|
7
|
-
const { execSync } = require('child_process');
|
|
8
|
-
const fs = require('fs');
|
|
9
3
|
const path = require('path');
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
4
|
+
const { ensureBin, runCommand } = require('../utils/cli');
|
|
5
|
+
const { ensureDir, writeJson, copyDir } = require('../utils/fsx');
|
|
6
|
+
const { sha256 } = require('../utils/hash');
|
|
7
|
+
const { readConfig, validateFor } = require('../utils/config');
|
|
8
|
+
const { IntrospectionService } = require('../services/introspect');
|
|
9
|
+
const { showBetaBanner } = require('../index');
|
|
10
|
+
|
|
11
|
+
const backupCommand = new Command('backup')
|
|
12
|
+
.description('Backup completo do projeto Supabase usando Supabase CLI')
|
|
13
|
+
.option('-o, --output <dir>', 'Diretório de saída')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
showBetaBanner();
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Verificar se Supabase CLI está disponível
|
|
19
|
+
const supabasePath = await ensureBin('supabase');
|
|
20
|
+
if (!supabasePath) {
|
|
21
|
+
console.error(chalk.red('❌ Supabase CLI não encontrado'));
|
|
22
|
+
console.log(chalk.yellow('💡 Instale o Supabase CLI:'));
|
|
23
|
+
console.log(chalk.yellow(' npm install -g supabase'));
|
|
24
|
+
console.log(chalk.yellow(' ou visite: https://supabase.com/docs/guides/cli'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
12
27
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
17
|
-
async function backupCommand(options) {
|
|
18
|
-
console.log(chalk.red.bold('🚀 smoonb - EXPERIMENTAL VERSION'));
|
|
19
|
-
console.log(chalk.red.bold('⚠️ VERSÃO EXPERIMENTAL - NUNCA TESTADA EM PRODUÇÃO!'));
|
|
20
|
-
console.log(chalk.red.bold('🚨 USE POR SUA CONTA E RISCO - Pode causar perda de dados!'));
|
|
21
|
-
console.log(chalk.red.bold('❌ NÃO NOS RESPONSABILIZAMOS por qualquer perda de dados!\n'));
|
|
22
|
-
|
|
23
|
-
console.log(chalk.cyan.bold('🚀 Iniciando backup COMPLETO do projeto Supabase...\n'));
|
|
28
|
+
// Carregar e validar configuração
|
|
29
|
+
const config = await readConfig();
|
|
30
|
+
validateFor(config, 'backup');
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.log(chalk.yellow('💡 Opções:'));
|
|
32
|
-
console.log(chalk.gray(' 1. Use: smoonb backup --project-id <seu-project-id>'));
|
|
33
|
-
console.log(chalk.gray(' 2. Configure: smoonb config --init'));
|
|
34
|
-
console.log(chalk.gray(' 3. Ou defina SUPABASE_PROJECT_ID no ambiente'));
|
|
35
|
-
console.log(chalk.gray(' 4. Ou edite ~/.smoonbrc e configure o projectId'));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
32
|
+
const databaseUrl = config.supabase.databaseUrl;
|
|
33
|
+
if (!databaseUrl) {
|
|
34
|
+
console.error(chalk.red('❌ databaseUrl não configurada'));
|
|
35
|
+
console.log(chalk.yellow('💡 Configure databaseUrl no .smoonbrc'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
// Resolver diretório de saída
|
|
40
|
+
const outputDir = options.output || config.backup.outputDir;
|
|
41
|
+
|
|
42
|
+
// Criar diretório de backup com timestamp
|
|
43
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
44
|
+
const backupDir = path.join(outputDir, `backup-${timestamp}`);
|
|
45
|
+
await ensureDir(backupDir);
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const backupDir = path.resolve(options.output, `backup-${timestamp}`);
|
|
44
|
-
await fs.promises.mkdir(backupDir, { recursive: true });
|
|
47
|
+
console.log(chalk.blue(`🚀 Iniciando backup do projeto: ${config.supabase.projectId}`));
|
|
48
|
+
console.log(chalk.blue(`📁 Diretório: ${backupDir}`));
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
// 1. Backup da Database usando Supabase CLI
|
|
51
|
+
console.log(chalk.blue('\n📊 1/3 - Backup da Database PostgreSQL...'));
|
|
52
|
+
await backupDatabaseWithSupabaseCLI(databaseUrl, backupDir);
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (dbBackupFile) {
|
|
52
|
-
console.log(chalk.green('✅ Database backupado:'), path.basename(dbBackupFile));
|
|
53
|
-
} else {
|
|
54
|
-
console.log(chalk.yellow('⚠️ Database não foi backupada (credenciais não configuradas)'));
|
|
55
|
-
}
|
|
54
|
+
// 2. Gerar inventário real
|
|
55
|
+
console.log(chalk.blue('\n🔍 2/3 - Gerando inventário completo...'));
|
|
56
|
+
await generateInventory(config, backupDir);
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const functionsDir = await backupEdgeFunctions(projectId, backupDir);
|
|
61
|
-
console.log(chalk.green('✅ Edge Functions backupadas:'), functionsDir);
|
|
62
|
-
}
|
|
58
|
+
// 3. Backup das Edge Functions locais
|
|
59
|
+
console.log(chalk.blue('\n⚡ 3/3 - Backup das Edge Functions locais...'));
|
|
60
|
+
await backupLocalFunctions(backupDir);
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
console.log(chalk.blue.bold('\n🔐 3/5 - Backup das configurações de Auth...'));
|
|
67
|
-
const authConfig = await backupAuthSettings(projectId, backupDir);
|
|
68
|
-
console.log(chalk.green('✅ Auth settings backupadas:'), authConfig);
|
|
69
|
-
}
|
|
62
|
+
// Gerar manifesto do backup
|
|
63
|
+
await generateBackupManifest(config, backupDir);
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
console.log(chalk.blue.bold('\n📁 4/5 - Backup dos Storage Objects...'));
|
|
74
|
-
const storageBackup = await backupStorageObjects(projectId, backupDir);
|
|
75
|
-
console.log(chalk.green('✅ Storage Objects backupados:'), storageBackup);
|
|
76
|
-
}
|
|
65
|
+
console.log(chalk.green('\n🎉 Backup completo finalizado!'));
|
|
66
|
+
console.log(chalk.blue(`📁 Localização: ${backupDir}`));
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const realtimeConfig = await backupRealtimeSettings(projectId, backupDir);
|
|
82
|
-
console.log(chalk.green('✅ Realtime settings backupadas:'), realtimeConfig);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(chalk.red(`❌ Erro no backup: ${error.message}`));
|
|
70
|
+
process.exit(1);
|
|
83
71
|
}
|
|
72
|
+
});
|
|
84
73
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const manifestPath = path.join(backupDir, 'backup-manifest.json');
|
|
107
|
-
await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
108
|
-
|
|
109
|
-
console.log(chalk.green.bold('\n🎉 BACKUP COMPLETO FINALIZADO COM SUCESSO!'));
|
|
110
|
-
console.log(chalk.blue('📁 Diretório:'), backupDir);
|
|
111
|
-
console.log(chalk.blue('🆔 Project ID:'), options.projectId);
|
|
112
|
-
console.log(chalk.blue('📋 Manifesto:'), 'backup-manifest.json');
|
|
113
|
-
console.log(chalk.yellow('\n💡 Este backup inclui TODOS os componentes do Supabase!'));
|
|
114
|
-
console.log(chalk.yellow('🔄 Use "smoonb restore" para restaurar em outro projeto'));
|
|
115
|
-
|
|
74
|
+
// Backup da database usando Supabase CLI
|
|
75
|
+
async function backupDatabaseWithSupabaseCLI(databaseUrl, backupDir) {
|
|
76
|
+
try {
|
|
77
|
+
console.log(chalk.blue(' - Exportando roles...'));
|
|
78
|
+
const { stdout: rolesOutput } = await runCommand(
|
|
79
|
+
`supabase db dump --db-url "${databaseUrl}" -f roles.sql --role-only`
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
console.log(chalk.blue(' - Exportando schema...'));
|
|
83
|
+
const { stdout: schemaOutput } = await runCommand(
|
|
84
|
+
`supabase db dump --db-url "${databaseUrl}" -f schema.sql`
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
console.log(chalk.blue(' - Exportando dados...'));
|
|
88
|
+
const { stdout: dataOutput } = await runCommand(
|
|
89
|
+
`supabase db dump --db-url "${databaseUrl}" -f data.sql --use-copy --data-only`
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
console.log(chalk.green('✅ Database exportada com sucesso'));
|
|
116
93
|
} catch (error) {
|
|
117
|
-
|
|
118
|
-
console.error(chalk.gray('Stack trace:'), error.stack);
|
|
119
|
-
process.exit(1);
|
|
94
|
+
throw new Error(`Falha no backup da database: ${error.message}`);
|
|
120
95
|
}
|
|
121
96
|
}
|
|
122
97
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
* Formato Custom é mais confiável para restauração
|
|
126
|
-
*/
|
|
127
|
-
async function backupDatabase(projectId, outputDir) {
|
|
98
|
+
// Gerar inventário completo
|
|
99
|
+
async function generateInventory(config, backupDir) {
|
|
128
100
|
try {
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// Verificar se a URL contém placeholder de senha
|
|
141
|
-
if (dbUrl.includes('[password]')) {
|
|
142
|
-
console.log(chalk.yellow('⚠️ Database URL contém placeholder [password]'));
|
|
143
|
-
console.log(chalk.gray(' - Substitua [password] pela senha real da database'));
|
|
144
|
-
console.log(chalk.gray(' - Ou configure DATABASE_URL completa no ambiente'));
|
|
145
|
-
return null;
|
|
101
|
+
const introspection = new IntrospectionService(config);
|
|
102
|
+
const inventory = await introspection.generateFullInventory();
|
|
103
|
+
|
|
104
|
+
// Salvar inventário em arquivos separados
|
|
105
|
+
const inventoryDir = path.join(backupDir, 'inventory');
|
|
106
|
+
await ensureDir(inventoryDir);
|
|
107
|
+
|
|
108
|
+
for (const [component, data] of Object.entries(inventory.components)) {
|
|
109
|
+
const filePath = path.join(inventoryDir, `${component}.json`);
|
|
110
|
+
await writeJson(filePath, data);
|
|
146
111
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const filename = `database-${timestamp}.dump`;
|
|
150
|
-
const filepath = path.join(outputDir, filename);
|
|
151
|
-
|
|
152
|
-
console.log(chalk.gray(' - Executando pg_dump com formato Custom (-Fc)...'));
|
|
153
|
-
|
|
154
|
-
// Usar formato Custom (-Fc) para restauração mais segura
|
|
155
|
-
const command = `pg_dump "${dbUrl}" -Fc -f "${filepath}"`;
|
|
156
|
-
execSync(command, { stdio: 'pipe' });
|
|
157
|
-
|
|
158
|
-
return filepath;
|
|
112
|
+
|
|
113
|
+
console.log(chalk.green('✅ Inventário completo gerado'));
|
|
159
114
|
} catch (error) {
|
|
160
|
-
console.log(chalk.yellow(
|
|
161
|
-
console.log(chalk.gray(' - Verifique se DATABASE_URL está correta'));
|
|
162
|
-
console.log(chalk.gray(' - Verifique se pg_dump está instalado'));
|
|
163
|
-
console.log(chalk.gray(' - Verifique se as credenciais estão corretas'));
|
|
164
|
-
return null;
|
|
115
|
+
console.log(chalk.yellow(`⚠️ Erro ao gerar inventário: ${error.message}`));
|
|
165
116
|
}
|
|
166
117
|
}
|
|
167
118
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
119
|
+
// Backup das Edge Functions locais
|
|
120
|
+
async function backupLocalFunctions(backupDir) {
|
|
121
|
+
const localFunctionsPath = 'supabase/functions';
|
|
122
|
+
|
|
172
123
|
try {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
console.log(chalk.gray(' - Copiando código das Edge Functions...'));
|
|
179
|
-
|
|
180
|
-
// Copiar código das functions (Windows compatible)
|
|
181
|
-
const { execSync } = require('child_process');
|
|
182
|
-
execSync(`xcopy "supabase\\functions\\*" "${functionsBackupDir}\\" /E /I /Y`, { stdio: 'pipe' });
|
|
124
|
+
const fs = require('fs');
|
|
125
|
+
if (fs.existsSync(localFunctionsPath)) {
|
|
126
|
+
const functionsBackupDir = path.join(backupDir, 'functions');
|
|
127
|
+
await copyDir(localFunctionsPath, functionsBackupDir);
|
|
128
|
+
console.log(chalk.green('✅ Edge Functions locais copiadas'));
|
|
183
129
|
} else {
|
|
184
|
-
console.log(chalk.
|
|
185
|
-
|
|
186
|
-
// Criar arquivo placeholder
|
|
187
|
-
const placeholderPath = path.join(functionsBackupDir, 'README.md');
|
|
188
|
-
await fs.promises.writeFile(placeholderPath,
|
|
189
|
-
'# Edge Functions Backup\n\nNenhuma Edge Function local foi encontrada.\nUse o Supabase CLI para fazer backup das functions remotas.'
|
|
190
|
-
);
|
|
130
|
+
console.log(chalk.yellow('⚠️ Diretório supabase/functions não encontrado'));
|
|
191
131
|
}
|
|
192
|
-
|
|
193
|
-
return functionsBackupDir;
|
|
194
132
|
} catch (error) {
|
|
195
|
-
console.log(chalk.yellow(
|
|
196
|
-
return null;
|
|
133
|
+
console.log(chalk.yellow(`⚠️ Erro ao copiar Edge Functions: ${error.message}`));
|
|
197
134
|
}
|
|
198
135
|
}
|
|
199
136
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
137
|
+
// Gerar manifesto do backup
|
|
138
|
+
async function generateBackupManifest(config, backupDir) {
|
|
139
|
+
const manifest = {
|
|
140
|
+
created_at: new Date().toISOString(),
|
|
141
|
+
project_id: config.supabase.projectId,
|
|
142
|
+
smoonb_version: require('../../package.json').version,
|
|
143
|
+
backup_type: 'complete',
|
|
144
|
+
files: {
|
|
145
|
+
roles: 'roles.sql',
|
|
146
|
+
schema: 'schema.sql',
|
|
147
|
+
data: 'data.sql'
|
|
148
|
+
},
|
|
149
|
+
hashes: {},
|
|
150
|
+
inventory: {}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Calcular hashes dos arquivos SQL
|
|
154
|
+
const fs = require('fs');
|
|
155
|
+
for (const [type, filename] of Object.entries(manifest.files)) {
|
|
156
|
+
const filePath = path.join(backupDir, filename);
|
|
157
|
+
if (fs.existsSync(filePath)) {
|
|
158
|
+
manifest.hashes[type] = await sha256(filePath);
|
|
159
|
+
}
|
|
222
160
|
}
|
|
223
|
-
}
|
|
224
161
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const storageConfig = {
|
|
235
|
-
timestamp: new Date().toISOString(),
|
|
236
|
-
projectId: projectId,
|
|
237
|
-
buckets: [],
|
|
238
|
-
objects: []
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
const storageConfigPath = path.join(storageBackupDir, 'storage-config.json');
|
|
242
|
-
await fs.promises.writeFile(storageConfigPath, JSON.stringify(storageConfig, null, 2));
|
|
243
|
-
|
|
244
|
-
console.log(chalk.gray(' - Configurações de Storage exportadas'));
|
|
245
|
-
return storageBackupDir;
|
|
246
|
-
} catch (error) {
|
|
247
|
-
console.log(chalk.yellow('⚠️ Backup dos Storage Objects falhou:'), error.message);
|
|
248
|
-
return null;
|
|
162
|
+
// Adicionar referências ao inventário
|
|
163
|
+
const inventoryDir = path.join(backupDir, 'inventory');
|
|
164
|
+
if (fs.existsSync(inventoryDir)) {
|
|
165
|
+
const inventoryFiles = fs.readdirSync(inventoryDir);
|
|
166
|
+
manifest.inventory = inventoryFiles.reduce((acc, file) => {
|
|
167
|
+
const component = path.basename(file, '.json');
|
|
168
|
+
acc[component] = `inventory/${file}`;
|
|
169
|
+
return acc;
|
|
170
|
+
}, {});
|
|
249
171
|
}
|
|
250
|
-
}
|
|
251
172
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
*/
|
|
255
|
-
async function backupRealtimeSettings(projectId, outputDir) {
|
|
256
|
-
try {
|
|
257
|
-
const realtimeConfig = {
|
|
258
|
-
timestamp: new Date().toISOString(),
|
|
259
|
-
projectId: projectId,
|
|
260
|
-
enabled: false,
|
|
261
|
-
channels: [],
|
|
262
|
-
settings: {}
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const realtimeConfigPath = path.join(outputDir, 'realtime-config.json');
|
|
266
|
-
await fs.promises.writeFile(realtimeConfigPath, JSON.stringify(realtimeConfig, null, 2));
|
|
267
|
-
|
|
268
|
-
console.log(chalk.gray(' - Configurações de Realtime exportadas'));
|
|
269
|
-
return realtimeConfigPath;
|
|
270
|
-
} catch (error) {
|
|
271
|
-
console.log(chalk.yellow('⚠️ Backup das configurações de Realtime falhou:'), error.message);
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
173
|
+
const manifestPath = path.join(backupDir, 'backup-manifest.json');
|
|
174
|
+
await writeJson(manifestPath, manifest);
|
|
274
175
|
}
|
|
275
176
|
|
|
276
|
-
module.exports = backupCommand;
|
|
177
|
+
module.exports = backupCommand;
|