smoonb 1.0.6 → 1.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.
@@ -1,202 +1,76 @@
1
- const chalk = require('chalk');
2
- const path = require('path');
3
- const fs = require('fs').promises;
4
- const { ensureDir, writeJson } = require('../../../utils/fsx');
5
- const { extractPasswordFromDbUrl, ensureCleanLink } = require('../../../utils/supabaseLink');
6
- const { cleanDir } = require('../../../utils/fsExtra');
7
- const { t } = require('../../../i18n');
8
-
9
- /**
10
- * Etapa 8: Backup Edge Functions via Docker (reset link + limpeza opcional)
11
- */
12
- module.exports = async (context) => {
13
- const { projectId, accessToken, databaseUrl, backupDir } = context;
14
- try {
15
- const functionsDir = path.join(backupDir, 'edge-functions');
16
- await ensureDir(functionsDir);
17
-
18
- // Reset de link ao projeto de ORIGEM
19
- const dbPassword = extractPasswordFromDbUrl(databaseUrl);
20
- await ensureCleanLink(projectId, accessToken, dbPassword);
21
-
22
- // Preparar diretório supabase/functions (criar se não existir, mas não limpar ainda)
23
- const supabaseFunctionsDir = path.join(process.cwd(), 'supabase', 'functions');
24
-
25
- // Verificar flag de limpeza antes do backup
26
- const shouldCleanAfter = context?.cleanupFlags?.cleanFunctions || false;
27
-
28
- // Registrar funções que já existiam ANTES do processo (para preservar se necessário)
29
- let existingFunctionsBefore = [];
30
- try {
31
- const existingItems = await fs.readdir(supabaseFunctionsDir);
32
- for (const item of existingItems) {
33
- const itemPath = path.join(supabaseFunctionsDir, item);
34
- const stats = await fs.stat(itemPath);
35
- if (stats.isDirectory()) {
36
- existingFunctionsBefore.push(item);
37
- }
38
- }
39
- } catch {
40
- // Diretório não existe, tudo bem
41
- existingFunctionsBefore = [];
42
- }
43
-
44
- // Se o usuário escolheu limpar APÓS, podemos limpar ANTES também para garantir ambiente limpo
45
- // Mas se escolheu NÃO limpar, preservamos o que já existe
46
- const getT = global.smoonbI18n?.t || t;
47
-
48
- if (shouldCleanAfter) {
49
- // Limpar antes se o usuário escolheu limpar após (garante ambiente limpo)
50
- await cleanDir(supabaseFunctionsDir);
51
- console.log(chalk.white(` - ${getT('backup.steps.functions.cleanBefore')}`));
52
- } else {
53
- // Apenas garantir que o diretório existe
54
- await fs.mkdir(supabaseFunctionsDir, { recursive: true });
55
- if (existingFunctionsBefore.length > 0) {
56
- console.log(chalk.white(` - ${getT('backup.steps.functions.preserving', { count: existingFunctionsBefore.length })}`));
57
- } else {
58
- console.log(chalk.white(` - ${getT('backup.steps.functions.prepared')}`));
59
- }
60
- }
61
-
62
- console.log(chalk.white(` - ${getT('backup.steps.functions.listing')}`));
63
-
64
- // Usar fetch direto para Management API com Personal Access Token
65
- const functionsResponse = await fetch(`https://api.supabase.com/v1/projects/${projectId}/functions`, {
66
- headers: {
67
- 'Authorization': `Bearer ${accessToken}`,
68
- 'Content-Type': 'application/json'
69
- }
70
- });
71
-
72
- if (!functionsResponse.ok) {
73
- console.log(chalk.yellow(` ⚠️ ${getT('backup.steps.functions.listError', { status: functionsResponse.status, statusText: functionsResponse.statusText })}`));
74
- return { success: false, reason: 'api_error', functions: [] };
75
- }
76
-
77
- const functions = await functionsResponse.json();
78
-
79
- if (!functions || functions.length === 0) {
80
- console.log(chalk.white(` - ${getT('backup.steps.functions.noneFound')}`));
81
- await writeJson(path.join(functionsDir, 'README.md'), {
82
- message: getT('backup.steps.functions.noFunctionsMessage')
83
- });
84
- return { success: true, reason: 'no_functions', functions: [] };
85
- }
86
-
87
- console.log(chalk.white(` - Encontradas ${functions.length} Edge Function(s)`));
88
-
89
- const downloadedFunctions = [];
90
- let successCount = 0;
91
- let errorCount = 0;
92
- const totalFuncs = functions.length;
93
- const stepStart = Date.now();
94
-
95
- // Baixar cada Edge Function via Supabase CLI
96
- // Nota: O CLI ignora o cwd e sempre baixa para supabase/functions
97
- for (let idx = 0; idx < functions.length; idx++) {
98
- const func = functions[idx];
99
- const current = idx + 1;
100
- const elapsed = Math.floor((Date.now() - stepStart) / 1000);
101
- try {
102
- console.log(chalk.white(` - Baixando ${current}/${totalFuncs}: ${func.name}... (${elapsed}s)`));
103
-
104
- // Criar diretório da função NO BACKUP
105
- const functionTargetDir = path.join(functionsDir, func.name);
106
- await ensureDir(functionTargetDir);
107
-
108
- // Diretório temporário onde o supabase CLI irá baixar (supabase/functions)
109
- const tempDownloadDir = path.join(process.cwd(), 'supabase', 'functions', func.name);
110
-
111
- // Baixar Edge Function via Supabase CLI (sempre vai para supabase/functions)
112
- const { execSync } = require('child_process');
113
-
114
- execSync(`supabase functions download ${func.name}`, {
115
- timeout: 60000,
116
- stdio: 'pipe'
117
- });
118
-
119
- // COPIAR arquivos de supabase/functions para o backup
120
- try {
121
- const stat = await fs.stat(tempDownloadDir);
122
- if (stat.isDirectory()) {
123
- const files = await fs.readdir(tempDownloadDir);
124
- for (const file of files) {
125
- const srcPath = path.join(tempDownloadDir, file);
126
- const dstPath = path.join(functionTargetDir, file);
127
-
128
- const fileStats = await fs.stat(srcPath);
129
- if (fileStats.isDirectory()) {
130
- // Copiar diretórios recursivamente
131
- await fs.cp(srcPath, dstPath, { recursive: true });
132
- } else {
133
- // Copiar arquivos
134
- await fs.copyFile(srcPath, dstPath);
135
- }
136
- }
137
- }
138
- } catch {
139
- // Arquivos não foram baixados, continuar
140
- console.log(chalk.yellow(` ⚠️ Nenhum arquivo encontrado em ${tempDownloadDir}`));
141
- }
142
-
143
- // Limpar função baixada temporariamente APENAS se o usuário escolheu limpar após
144
- // Se não escolheu, preservar (pode ser que já existisse antes)
145
- if (shouldCleanAfter) {
146
- try {
147
- await fs.rm(tempDownloadDir, { recursive: true, force: true });
148
- } catch {
149
- // Ignorar erro de limpeza
150
- }
151
- }
152
- // Se shouldCleanAfter = false, manter a função baixada (e qualquer função que já existia)
153
-
154
- console.log(chalk.green(` ✅ ${func.name} baixada com sucesso`));
155
- successCount++;
156
-
157
- downloadedFunctions.push({
158
- name: func.name,
159
- slug: func.name,
160
- version: func.version || 'unknown',
161
- files: await fs.readdir(functionTargetDir).catch(() => [])
162
- });
163
-
164
- } catch (error) {
165
- console.log(chalk.yellow(` ⚠️ ${getT('backup.steps.functions.downloadError', { funcName: func.name, message: error.message })}`));
166
- errorCount++;
167
- }
168
- }
169
-
170
- console.log(chalk.green(`📊 Backup de Edge Functions concluído:`));
171
- console.log(chalk.green(` ✅ Sucessos: ${successCount}`));
172
- console.log(chalk.green(` ❌ Erros: ${errorCount}`));
173
-
174
- // Limpar pasta supabase/functions APÓS o backup apenas se o usuário escolheu
175
- // Nota: shouldCleanAfter já foi definido acima
176
- if (shouldCleanAfter) {
177
- await cleanDir(supabaseFunctionsDir);
178
- console.log(chalk.white(` - ${getT('backup.steps.functions.cleanAfter')}`));
179
- } else {
180
- // Preservar tudo: tanto as funções que já existiam quanto as que foram baixadas
181
- // As funções baixadas não foram removidas individualmente (linha acima foi ajustada)
182
- console.log(chalk.white(` - ${getT('backup.steps.functions.preserved')}`));
183
- }
184
-
185
- return {
186
- success: true,
187
- reason: 'success',
188
- functions: downloadedFunctions,
189
- functions_count: functions.length,
190
- success_count: successCount,
191
- error_count: errorCount,
192
- method: 'docker'
193
- };
194
-
195
- } catch (error) {
196
- const getT = global.smoonbI18n?.t || t;
197
- console.log(chalk.yellow(` ⚠️ ${getT('backup.steps.functions.error', { message: error.message })}`));
198
- console.log('⏭️ Continuando com outros componentes...');
199
- return { success: false, reason: 'download_error', error: error.message, functions: [] };
200
- }
201
- };
202
-
1
+ const path = require('path');
2
+ const os = require('os');
3
+ const fs = require('fs').promises;
4
+ const { runSupabase } = require('../../../utils/supabaseCli');
5
+ const { management } = require('../../restore/management');
6
+ const { copyDirectoryRecursive } = require('../../restore/utils');
7
+ module.exports = async ({ projectId, accessToken, backupDir }) => {
8
+ const root = await fs.mkdtemp(
9
+ path.join(os.tmpdir(), 'smoonb-functions-backup-'),
10
+ );
11
+ try {
12
+ const functions = await management(
13
+ { targetProjectId: projectId, targetAccessToken: accessToken },
14
+ 'functions',
15
+ );
16
+ await fs.mkdir(path.join(root, 'supabase'), { recursive: true });
17
+ await fs.writeFile(
18
+ path.join(root, 'supabase', 'config.toml'),
19
+ 'project_id = "smoonb-backup"\n',
20
+ );
21
+ const downloaded = [];
22
+ for (const f of functions) {
23
+ const name = f.slug || f.name;
24
+ if (!/^[-a-zA-Z0-9_]+$/.test(name))
25
+ throw new Error('Invalid function slug');
26
+ runSupabase(
27
+ 'supabase',
28
+ [
29
+ 'functions',
30
+ 'download',
31
+ name,
32
+ '--project-ref',
33
+ projectId,
34
+ '--workdir',
35
+ root,
36
+ ],
37
+ {
38
+ cwd: root,
39
+ env: { ...process.env, SUPABASE_ACCESS_TOKEN: accessToken },
40
+ stdio: 'pipe',
41
+ timeout: 180000,
42
+ windowsHide: true,
43
+ },
44
+ );
45
+ const destination = path.join(backupDir, 'edge-functions', name);
46
+ await copyDirectoryRecursive(
47
+ path.join(root, 'supabase', 'functions', name),
48
+ destination,
49
+ );
50
+ downloaded.push({
51
+ name,
52
+ slug: name,
53
+ version: f.version,
54
+ verify_jwt: f.verify_jwt,
55
+ files: await fs.readdir(destination),
56
+ });
57
+ }
58
+ return {
59
+ success: true,
60
+ reason: 'success',
61
+ functions: downloaded,
62
+ functions_count: downloaded.length,
63
+ success_count: downloaded.length,
64
+ error_count: 0,
65
+ method: 'cli-isolated',
66
+ };
67
+ } catch {
68
+ return {
69
+ success: false,
70
+ reason: 'function_download_failed',
71
+ functions: [],
72
+ };
73
+ } finally {
74
+ await fs.rm(root, { recursive: true, force: true });
75
+ }
76
+ };
@@ -0,0 +1,237 @@
1
+ [
2
+ "site_url",
3
+ "disable_signup",
4
+ "jwt_exp",
5
+ "smtp_admin_email",
6
+ "smtp_host",
7
+ "smtp_port",
8
+ "smtp_user",
9
+ "smtp_pass",
10
+ "smtp_max_frequency",
11
+ "smtp_sender_name",
12
+ "mailer_allow_unverified_email_sign_ins",
13
+ "mailer_autoconfirm",
14
+ "mailer_subjects_invite",
15
+ "mailer_subjects_confirmation",
16
+ "mailer_subjects_recovery",
17
+ "mailer_subjects_email_change",
18
+ "mailer_subjects_magic_link",
19
+ "mailer_subjects_reauthentication",
20
+ "mailer_subjects_password_changed_notification",
21
+ "mailer_subjects_email_changed_notification",
22
+ "mailer_subjects_phone_changed_notification",
23
+ "mailer_subjects_mfa_factor_enrolled_notification",
24
+ "mailer_subjects_mfa_factor_unenrolled_notification",
25
+ "mailer_subjects_identity_linked_notification",
26
+ "mailer_subjects_identity_unlinked_notification",
27
+ "mailer_templates_invite_content",
28
+ "mailer_templates_confirmation_content",
29
+ "mailer_templates_recovery_content",
30
+ "mailer_templates_email_change_content",
31
+ "mailer_templates_magic_link_content",
32
+ "mailer_templates_reauthentication_content",
33
+ "mailer_templates_password_changed_notification_content",
34
+ "mailer_templates_email_changed_notification_content",
35
+ "mailer_templates_phone_changed_notification_content",
36
+ "mailer_templates_mfa_factor_enrolled_notification_content",
37
+ "mailer_templates_mfa_factor_unenrolled_notification_content",
38
+ "mailer_templates_identity_linked_notification_content",
39
+ "mailer_templates_identity_unlinked_notification_content",
40
+ "mailer_notifications_password_changed_enabled",
41
+ "mailer_notifications_email_changed_enabled",
42
+ "mailer_notifications_phone_changed_enabled",
43
+ "mailer_notifications_mfa_factor_enrolled_enabled",
44
+ "mailer_notifications_mfa_factor_unenrolled_enabled",
45
+ "mailer_notifications_identity_linked_enabled",
46
+ "mailer_notifications_identity_unlinked_enabled",
47
+ "mfa_max_enrolled_factors",
48
+ "uri_allow_list",
49
+ "external_anonymous_users_enabled",
50
+ "external_email_enabled",
51
+ "external_phone_enabled",
52
+ "saml_enabled",
53
+ "saml_external_url",
54
+ "security_sb_forwarded_for_enabled",
55
+ "security_captcha_enabled",
56
+ "security_captcha_provider",
57
+ "security_captcha_secret",
58
+ "sessions_timebox",
59
+ "sessions_inactivity_timeout",
60
+ "sessions_single_per_user",
61
+ "sessions_tags",
62
+ "rate_limit_anonymous_users",
63
+ "rate_limit_email_sent",
64
+ "rate_limit_sms_sent",
65
+ "rate_limit_verify",
66
+ "rate_limit_token_refresh",
67
+ "rate_limit_otp",
68
+ "rate_limit_web3",
69
+ "mailer_secure_email_change_enabled",
70
+ "refresh_token_rotation_enabled",
71
+ "password_hibp_enabled",
72
+ "password_min_length",
73
+ "password_required_characters",
74
+ "security_manual_linking_enabled",
75
+ "security_update_password_require_reauthentication",
76
+ "security_update_password_require_current_password",
77
+ "security_refresh_token_reuse_interval",
78
+ "mailer_otp_exp",
79
+ "mailer_otp_length",
80
+ "sms_autoconfirm",
81
+ "sms_max_frequency",
82
+ "sms_otp_exp",
83
+ "sms_otp_length",
84
+ "sms_provider",
85
+ "sms_messagebird_access_key",
86
+ "sms_messagebird_originator",
87
+ "sms_test_otp",
88
+ "sms_test_otp_valid_until",
89
+ "sms_textlocal_api_key",
90
+ "sms_textlocal_sender",
91
+ "sms_twilio_account_sid",
92
+ "sms_twilio_auth_token",
93
+ "sms_twilio_content_sid",
94
+ "sms_twilio_message_service_sid",
95
+ "sms_twilio_verify_account_sid",
96
+ "sms_twilio_verify_auth_token",
97
+ "sms_twilio_verify_message_service_sid",
98
+ "sms_vonage_api_key",
99
+ "sms_vonage_api_secret",
100
+ "sms_vonage_from",
101
+ "sms_template",
102
+ "hook_mfa_verification_attempt_enabled",
103
+ "hook_mfa_verification_attempt_uri",
104
+ "hook_mfa_verification_attempt_secrets",
105
+ "hook_password_verification_attempt_enabled",
106
+ "hook_password_verification_attempt_uri",
107
+ "hook_password_verification_attempt_secrets",
108
+ "hook_custom_access_token_enabled",
109
+ "hook_custom_access_token_uri",
110
+ "hook_custom_access_token_secrets",
111
+ "hook_send_sms_enabled",
112
+ "hook_send_sms_uri",
113
+ "hook_send_sms_secrets",
114
+ "hook_send_email_enabled",
115
+ "hook_send_email_uri",
116
+ "hook_send_email_secrets",
117
+ "hook_before_user_created_enabled",
118
+ "hook_before_user_created_uri",
119
+ "hook_before_user_created_secrets",
120
+ "hook_after_user_created_enabled",
121
+ "hook_after_user_created_uri",
122
+ "hook_after_user_created_secrets",
123
+ "external_apple_enabled",
124
+ "external_apple_client_id",
125
+ "external_apple_email_optional",
126
+ "external_apple_secret",
127
+ "external_apple_additional_client_ids",
128
+ "external_azure_enabled",
129
+ "external_azure_client_id",
130
+ "external_azure_email_optional",
131
+ "external_azure_secret",
132
+ "external_azure_url",
133
+ "external_bitbucket_enabled",
134
+ "external_bitbucket_client_id",
135
+ "external_bitbucket_email_optional",
136
+ "external_bitbucket_secret",
137
+ "external_discord_enabled",
138
+ "external_discord_client_id",
139
+ "external_discord_email_optional",
140
+ "external_discord_secret",
141
+ "external_facebook_enabled",
142
+ "external_facebook_client_id",
143
+ "external_facebook_email_optional",
144
+ "external_facebook_secret",
145
+ "external_figma_enabled",
146
+ "external_figma_client_id",
147
+ "external_figma_email_optional",
148
+ "external_figma_secret",
149
+ "external_github_enabled",
150
+ "external_github_client_id",
151
+ "external_github_email_optional",
152
+ "external_github_secret",
153
+ "external_gitlab_enabled",
154
+ "external_gitlab_client_id",
155
+ "external_gitlab_email_optional",
156
+ "external_gitlab_secret",
157
+ "external_gitlab_url",
158
+ "external_google_enabled",
159
+ "external_google_client_id",
160
+ "external_google_email_optional",
161
+ "external_google_secret",
162
+ "external_google_additional_client_ids",
163
+ "external_google_skip_nonce_check",
164
+ "external_kakao_enabled",
165
+ "external_kakao_client_id",
166
+ "external_kakao_email_optional",
167
+ "external_kakao_secret",
168
+ "external_keycloak_enabled",
169
+ "external_keycloak_client_id",
170
+ "external_keycloak_email_optional",
171
+ "external_keycloak_secret",
172
+ "external_keycloak_url",
173
+ "external_linkedin_oidc_enabled",
174
+ "external_linkedin_oidc_client_id",
175
+ "external_linkedin_oidc_email_optional",
176
+ "external_linkedin_oidc_secret",
177
+ "external_slack_oidc_enabled",
178
+ "external_slack_oidc_client_id",
179
+ "external_slack_oidc_email_optional",
180
+ "external_slack_oidc_secret",
181
+ "external_notion_enabled",
182
+ "external_notion_client_id",
183
+ "external_notion_email_optional",
184
+ "external_notion_secret",
185
+ "external_slack_enabled",
186
+ "external_slack_client_id",
187
+ "external_slack_email_optional",
188
+ "external_slack_secret",
189
+ "external_spotify_enabled",
190
+ "external_spotify_client_id",
191
+ "external_spotify_email_optional",
192
+ "external_spotify_secret",
193
+ "external_twitch_enabled",
194
+ "external_twitch_client_id",
195
+ "external_twitch_email_optional",
196
+ "external_twitch_secret",
197
+ "external_twitter_enabled",
198
+ "external_twitter_client_id",
199
+ "external_twitter_email_optional",
200
+ "external_twitter_secret",
201
+ "external_x_enabled",
202
+ "external_x_client_id",
203
+ "external_x_email_optional",
204
+ "external_x_secret",
205
+ "external_workos_enabled",
206
+ "external_workos_client_id",
207
+ "external_workos_secret",
208
+ "external_workos_url",
209
+ "external_web3_solana_enabled",
210
+ "external_web3_ethereum_enabled",
211
+ "external_zoom_enabled",
212
+ "external_zoom_client_id",
213
+ "external_zoom_email_optional",
214
+ "external_zoom_secret",
215
+ "db_max_pool_size",
216
+ "db_max_pool_size_unit",
217
+ "api_max_request_duration",
218
+ "mfa_totp_enroll_enabled",
219
+ "mfa_totp_verify_enabled",
220
+ "mfa_web_authn_enroll_enabled",
221
+ "mfa_web_authn_verify_enabled",
222
+ "passkey_enabled",
223
+ "webauthn_rp_display_name",
224
+ "webauthn_rp_id",
225
+ "webauthn_rp_origins",
226
+ "mfa_phone_enroll_enabled",
227
+ "mfa_phone_verify_enabled",
228
+ "mfa_phone_max_frequency",
229
+ "mfa_phone_otp_length",
230
+ "mfa_phone_template",
231
+ "nimbus_oauth_client_id",
232
+ "nimbus_oauth_client_secret",
233
+ "oauth_server_enabled",
234
+ "oauth_server_allow_dynamic_registration",
235
+ "oauth_server_authorization_path",
236
+ "custom_oauth_enabled"
237
+ ]