smoonb 0.0.1

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.
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Utilitários de validação para inputs e configurações
3
+ */
4
+
5
+ const chalk = require('chalk');
6
+
7
+ /**
8
+ * Validar Project ID do Supabase
9
+ */
10
+ function validateProjectId(projectId) {
11
+ if (!projectId) {
12
+ return { valid: false, error: 'Project ID é obrigatório' };
13
+ }
14
+
15
+ // Project ID deve ter formato específico (ex: abc123def456)
16
+ const projectIdRegex = /^[a-z0-9]{20}$/;
17
+ if (!projectIdRegex.test(projectId)) {
18
+ return {
19
+ valid: false,
20
+ error: 'Project ID deve ter 20 caracteres alfanuméricos (ex: abc123def456)'
21
+ };
22
+ }
23
+
24
+ return { valid: true };
25
+ }
26
+
27
+ /**
28
+ * Validar URL do Supabase
29
+ */
30
+ function validateSupabaseUrl(url) {
31
+ if (!url) {
32
+ return { valid: false, error: 'URL do Supabase é obrigatória' };
33
+ }
34
+
35
+ try {
36
+ const parsedUrl = new URL(url);
37
+
38
+ // Deve ser HTTPS
39
+ if (parsedUrl.protocol !== 'https:') {
40
+ return { valid: false, error: 'URL deve usar HTTPS' };
41
+ }
42
+
43
+ // Deve ter formato correto
44
+ if (!parsedUrl.hostname.includes('.supabase.co')) {
45
+ return { valid: false, error: 'URL deve ser um domínio Supabase válido' };
46
+ }
47
+
48
+ return { valid: true };
49
+ } catch (error) {
50
+ return { valid: false, error: 'URL inválida' };
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Validar Service Key do Supabase
56
+ */
57
+ function validateServiceKey(serviceKey) {
58
+ if (!serviceKey) {
59
+ return { valid: false, error: 'Service Key é obrigatória' };
60
+ }
61
+
62
+ // Service Key deve ter formato específico
63
+ const serviceKeyRegex = /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
64
+ if (!serviceKeyRegex.test(serviceKey)) {
65
+ return { valid: false, error: 'Service Key deve ser um JWT válido' };
66
+ }
67
+
68
+ return { valid: true };
69
+ }
70
+
71
+ /**
72
+ * Validar Anon Key do Supabase
73
+ */
74
+ function validateAnonKey(anonKey) {
75
+ if (!anonKey) {
76
+ return { valid: false, error: 'Anon Key é obrigatória' };
77
+ }
78
+
79
+ // Anon Key deve ter formato específico
80
+ const anonKeyRegex = /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
81
+ if (!anonKeyRegex.test(anonKey)) {
82
+ return { valid: false, error: 'Anon Key deve ser um JWT válido' };
83
+ }
84
+
85
+ return { valid: true };
86
+ }
87
+
88
+ /**
89
+ * Validar URL de conexão da database
90
+ */
91
+ function validateDatabaseUrl(dbUrl) {
92
+ if (!dbUrl) {
93
+ return { valid: false, error: 'URL da database é obrigatória' };
94
+ }
95
+
96
+ try {
97
+ const parsedUrl = new URL(dbUrl);
98
+
99
+ // Deve ser PostgreSQL
100
+ if (parsedUrl.protocol !== 'postgresql:') {
101
+ return { valid: false, error: 'URL deve usar protocolo postgresql:' };
102
+ }
103
+
104
+ // Deve ter host, port e database
105
+ if (!parsedUrl.hostname || !parsedUrl.port || !parsedUrl.pathname.slice(1)) {
106
+ return { valid: false, error: 'URL deve incluir host, porta e nome da database' };
107
+ }
108
+
109
+ return { valid: true };
110
+ } catch (error) {
111
+ return { valid: false, error: 'URL da database inválida' };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Validar diretório de backup
117
+ */
118
+ function validateBackupDir(backupDir) {
119
+ if (!backupDir) {
120
+ return { valid: false, error: 'Diretório de backup é obrigatório' };
121
+ }
122
+
123
+ // Deve ser um caminho válido
124
+ if (typeof backupDir !== 'string' || backupDir.trim().length === 0) {
125
+ return { valid: false, error: 'Diretório de backup deve ser uma string válida' };
126
+ }
127
+
128
+ // Não deve conter caracteres perigosos
129
+ const dangerousChars = /[<>:"|?*\x00-\x1f]/;
130
+ if (dangerousChars.test(backupDir)) {
131
+ return { valid: false, error: 'Diretório contém caracteres inválidos' };
132
+ }
133
+
134
+ return { valid: true };
135
+ }
136
+
137
+ /**
138
+ * Validar configuração completa
139
+ */
140
+ function validateConfig(config) {
141
+ const errors = [];
142
+
143
+ // Validar Supabase URL
144
+ if (config.supabase?.url) {
145
+ const urlValidation = validateSupabaseUrl(config.supabase.url);
146
+ if (!urlValidation.valid) {
147
+ errors.push(`Supabase URL: ${urlValidation.error}`);
148
+ }
149
+ }
150
+
151
+ // Validar Service Key
152
+ if (config.supabase?.serviceKey) {
153
+ const serviceKeyValidation = validateServiceKey(config.supabase.serviceKey);
154
+ if (!serviceKeyValidation.valid) {
155
+ errors.push(`Service Key: ${serviceKeyValidation.error}`);
156
+ }
157
+ }
158
+
159
+ // Validar Anon Key
160
+ if (config.supabase?.anonKey) {
161
+ const anonKeyValidation = validateAnonKey(config.supabase.anonKey);
162
+ if (!anonKeyValidation.valid) {
163
+ errors.push(`Anon Key: ${anonKeyValidation.error}`);
164
+ }
165
+ }
166
+
167
+ // Validar Database URL
168
+ if (config.supabase?.databaseUrl) {
169
+ const dbUrlValidation = validateDatabaseUrl(config.supabase.databaseUrl);
170
+ if (!dbUrlValidation.valid) {
171
+ errors.push(`Database URL: ${dbUrlValidation.error}`);
172
+ }
173
+ }
174
+
175
+ return {
176
+ valid: errors.length === 0,
177
+ errors
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Validar opções de backup
183
+ */
184
+ function validateBackupOptions(options) {
185
+ const errors = [];
186
+
187
+ // Project ID é obrigatório
188
+ const projectIdValidation = validateProjectId(options.projectId);
189
+ if (!projectIdValidation.valid) {
190
+ errors.push(projectIdValidation.error);
191
+ }
192
+
193
+ // Output dir é obrigatório
194
+ const outputDirValidation = validateBackupDir(options.output);
195
+ if (!outputDirValidation.valid) {
196
+ errors.push(outputDirValidation.error);
197
+ }
198
+
199
+ return {
200
+ valid: errors.length === 0,
201
+ errors
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Validar opções de restore
207
+ */
208
+ function validateRestoreOptions(options) {
209
+ const errors = [];
210
+
211
+ // Project ID é obrigatório
212
+ const projectIdValidation = validateProjectId(options.projectId);
213
+ if (!projectIdValidation.valid) {
214
+ errors.push(projectIdValidation.error);
215
+ }
216
+
217
+ // Backup dir é obrigatório
218
+ const backupDirValidation = validateBackupDir(options.backupDir);
219
+ if (!backupDirValidation.valid) {
220
+ errors.push(backupDirValidation.error);
221
+ }
222
+
223
+ return {
224
+ valid: errors.length === 0,
225
+ errors
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Validar arquivo de secrets
231
+ */
232
+ function validateSecretsFile(filePath) {
233
+ if (!filePath) {
234
+ return { valid: false, error: 'Caminho do arquivo de secrets é obrigatório' };
235
+ }
236
+
237
+ // Deve ser um arquivo .env
238
+ if (!filePath.endsWith('.env')) {
239
+ return { valid: false, error: 'Arquivo deve ter extensão .env' };
240
+ }
241
+
242
+ return { valid: true };
243
+ }
244
+
245
+ /**
246
+ * Validar nome de Edge Function
247
+ */
248
+ function validateFunctionName(functionName) {
249
+ if (!functionName) {
250
+ return { valid: false, error: 'Nome da function é obrigatório' };
251
+ }
252
+
253
+ // Deve seguir convenções de nomenclatura
254
+ const functionNameRegex = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
255
+ if (!functionNameRegex.test(functionName)) {
256
+ return {
257
+ valid: false,
258
+ error: 'Nome da function deve conter apenas letras minúsculas, números e hífens'
259
+ };
260
+ }
261
+
262
+ // Não deve ser muito longo
263
+ if (functionName.length > 50) {
264
+ return { valid: false, error: 'Nome da function não pode ter mais de 50 caracteres' };
265
+ }
266
+
267
+ return { valid: true };
268
+ }
269
+
270
+ /**
271
+ * Validar arquivo de manifesto de backup
272
+ */
273
+ function validateBackupManifest(manifest) {
274
+ const errors = [];
275
+
276
+ if (!manifest) {
277
+ return { valid: false, errors: ['Manifesto é obrigatório'] };
278
+ }
279
+
280
+ // Deve ter timestamp
281
+ if (!manifest.timestamp) {
282
+ errors.push('Timestamp é obrigatório');
283
+ }
284
+
285
+ // Deve ter projectId
286
+ if (!manifest.projectId) {
287
+ errors.push('Project ID é obrigatório');
288
+ } else {
289
+ const projectIdValidation = validateProjectId(manifest.projectId);
290
+ if (!projectIdValidation.valid) {
291
+ errors.push(`Project ID: ${projectIdValidation.error}`);
292
+ }
293
+ }
294
+
295
+ // Deve ter version
296
+ if (!manifest.version) {
297
+ errors.push('Versão é obrigatória');
298
+ }
299
+
300
+ // Deve ter components
301
+ if (!manifest.components) {
302
+ errors.push('Componentes são obrigatórios');
303
+ }
304
+
305
+ return {
306
+ valid: errors.length === 0,
307
+ errors
308
+ };
309
+ }
310
+
311
+ /**
312
+ * Mostrar erros de validação formatados
313
+ */
314
+ function showValidationErrors(errors, context = 'Validação') {
315
+ if (errors.length === 0) {
316
+ return;
317
+ }
318
+
319
+ console.error(chalk.red.bold(`❌ ${context} falhou:`));
320
+ errors.forEach(error => {
321
+ console.error(chalk.red(` - ${error}`));
322
+ });
323
+ }
324
+
325
+ /**
326
+ * Validar e mostrar resultado
327
+ */
328
+ function validateAndShow(validationResult, context = 'Validação') {
329
+ if (!validationResult.valid) {
330
+ showValidationErrors(validationResult.errors, context);
331
+ return false;
332
+ }
333
+ return true;
334
+ }
335
+
336
+ module.exports = {
337
+ validateProjectId,
338
+ validateSupabaseUrl,
339
+ validateServiceKey,
340
+ validateAnonKey,
341
+ validateDatabaseUrl,
342
+ validateBackupDir,
343
+ validateConfig,
344
+ validateBackupOptions,
345
+ validateRestoreOptions,
346
+ validateSecretsFile,
347
+ validateFunctionName,
348
+ validateBackupManifest,
349
+ showValidationErrors,
350
+ validateAndShow
351
+ };