@arela/uploader 0.0.9 → 0.1.0

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.
Files changed (4) hide show
  1. package/README.md +83 -0
  2. package/package.json +2 -3
  3. package/src/index.js +603 -127
  4. package/upload.log +0 -158
package/README.md CHANGED
@@ -1 +1,84 @@
1
1
  # arela-uploader
2
+
3
+ CLI tool to upload files and directories to Supabase Storage with automatic file renaming and sanitization.
4
+
5
+ ## Features
6
+
7
+ - 📁 Upload entire directories or individual files
8
+ - 🔄 Automatic file renaming to handle problematic characters
9
+ - 📝 Comprehensive logging (local and remote)
10
+ - ⚡ Retry mechanism for failed uploads
11
+ - 🎯 Skip duplicate files automatically
12
+ - 📊 Progress bars and detailed summaries
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install -g @arela/uploader
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Basic Upload
23
+ ```bash
24
+ arela -p "my-folder"
25
+ ```
26
+
27
+ ### Upload with File Renaming
28
+ For files with accents, special characters, or problematic names:
29
+
30
+ ```bash
31
+ # Preview what files would be renamed (dry run)
32
+ arela --rename-files --dry-run
33
+
34
+ # Actually rename and upload files
35
+ arela --rename-files -p "documents"
36
+ ```
37
+
38
+ ### Options
39
+
40
+ - `-p, --prefix <prefix>`: Prefix path in bucket (default: "")
41
+ - `-r, --rename-files`: Rename files with problematic characters before uploading
42
+ - `--dry-run`: Show what files would be renamed without actually renaming them
43
+ - `-h, --help`: Display help information
44
+ - `-v, --version`: Display version number
45
+
46
+ ## Environment Variables
47
+
48
+ Create a `.env` file in your project root:
49
+
50
+ ```env
51
+ SUPABASE_URL=your_supabase_url
52
+ SUPABASE_KEY=your_supabase_anon_key
53
+ SUPABASE_BUCKET=your_bucket_name
54
+ UPLOAD_BASE_PATH=/path/to/your/files
55
+ UPLOAD_SOURCES=folder1|folder2|file.pdf
56
+ ```
57
+
58
+ ## File Renaming
59
+
60
+ The tool automatically handles problematic characters by:
61
+
62
+ - Removing accents (á → a, ñ → n, etc.)
63
+ - Replacing special characters with safe alternatives
64
+ - Converting spaces to dashes
65
+ - Removing or replacing symbols like `{}[]~^`|"<>?*:`
66
+ - Handling Korean characters and other Unicode symbols
67
+
68
+ ### Examples
69
+
70
+ | Original | Renamed |
71
+ |----------|---------|
72
+ | `Facturas Importación.pdf` | `Facturas-Importacion.pdf` |
73
+ | `File{with}brackets.pdf` | `File-with-brackets.pdf` |
74
+ | `Document ^& symbols.pdf` | `Document-and-symbols.pdf` |
75
+ | `CI & PL-20221212(멕시코용).xls` | `CI-and-PL-20221212.xls` |
76
+
77
+ ## Logging
78
+
79
+ The tool maintains logs both locally (`upload.log`) and remotely in your Supabase database. Logs include:
80
+
81
+ - Upload status (success/error/skipped)
82
+ - File paths and sanitization changes
83
+ - Error messages and timestamps
84
+ - Rename operations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arela/uploader",
3
- "version": "0.0.9",
3
+ "version": "0.1.0",
4
4
  "description": "CLI to upload files/directories to Arela",
5
5
  "bin": {
6
6
  "arela": "./src/index.js"
@@ -33,8 +33,7 @@
33
33
  "commander": "^13.1.0",
34
34
  "dotenv": "^16.5.0",
35
35
  "globby": "^14.1.0",
36
- "mime-types": "^3.0.1",
37
- "ora": "^8.2.0"
36
+ "mime-types": "^3.0.1"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@trivago/prettier-plugin-sort-imports": "^5.2.2",
package/src/index.js CHANGED
@@ -7,7 +7,6 @@ import fs from 'fs';
7
7
  import { globby } from 'globby';
8
8
  import mime from 'mime-types';
9
9
  import { createRequire } from 'module';
10
- import ora from 'ora';
11
10
  import path from 'path';
12
11
 
13
12
  const require = createRequire(import.meta.url);
@@ -27,14 +26,236 @@ const sources = process.env.UPLOAD_SOURCES?.split('|')
27
26
 
28
27
  const supabase = createClient(supabaseUrl, supabaseKey);
29
28
 
30
- const sanitizePath = (path) =>
31
- path
32
- .replace(/[\\?%*:|"<>[\]~]/g, '-')
33
- .replace(/ +/g, ' ')
34
- .replace(/^\.+/, '')
35
- .replace(/\/+/g, '/');
29
+ // Pre-compiled regex patterns for better performance
30
+ const SANITIZATION_PATTERNS = [
31
+ // Character replacements (grouped for efficiency)
32
+ [/[áàâäãåāăą]/gi, 'a'],
33
+ [/[éèêëēĕėę]/gi, 'e'],
34
+ [/[íìîïīĭį]/gi, 'i'],
35
+ [/[óòôöõōŏő]/gi, 'o'],
36
+ [/[úùûüūŭů]/gi, 'u'],
37
+ [/[ñň]/gi, 'n'],
38
+ [/[ç]/gi, 'c'],
39
+ [/[ý]/gi, 'y'],
40
+ // Korean characters (compiled once)
41
+ [/[멕]/g, 'meok'],
42
+ [/[시]/g, 'si'],
43
+ [/[코]/g, 'ko'],
44
+ [/[용]/g, 'yong'],
45
+ [/[가-힣]/g, 'kr'],
46
+ // Unicode diacritics (after normalize)
47
+ [/[\u0300-\u036f]/g, ''],
48
+ // Problematic symbols
49
+ [/[\\?%*:|"<>[\]~`^]/g, '-'],
50
+ [/[{}]/g, '-'],
51
+ [/[&]/g, 'and'],
52
+ [/[()]/g, ''], // Remove parentheses
53
+ // Cleanup patterns
54
+ [/\s+/g, '-'], // Replace spaces with dashes
55
+ [/-+/g, '-'], // Replace multiple dashes with single dash
56
+ [/^-+|-+$/g, ''], // Remove leading/trailing dashes
57
+ [/^\.+/, ''], // Remove leading dots
58
+ [/[^\w.-]/g, ''], // Remove any remaining non-alphanumeric chars
59
+ ];
60
+
61
+ // Cache for sanitized filenames to avoid repeated processing
62
+ const sanitizationCache = new Map();
63
+
64
+ // Enhanced sanitization function with caching and pre-compiled regex
65
+ const sanitizeFileName = (fileName) => {
66
+ // Check cache first
67
+ if (sanitizationCache.has(fileName)) {
68
+ return sanitizationCache.get(fileName);
69
+ }
70
+
71
+ // Get file extension
72
+ const ext = path.extname(fileName);
73
+ const nameWithoutExt = path.basename(fileName, ext);
74
+
75
+ // Fast path for already clean filenames
76
+ if (/^[a-zA-Z0-9._-]+$/.test(nameWithoutExt)) {
77
+ const result = fileName;
78
+ sanitizationCache.set(fileName, result);
79
+ return result;
80
+ }
81
+
82
+ // Normalize unicode first (more efficient to do once)
83
+ let sanitized = nameWithoutExt.normalize('NFD');
84
+
85
+ // Apply all sanitization patterns
86
+ for (const [pattern, replacement] of SANITIZATION_PATTERNS) {
87
+ sanitized = sanitized.replace(pattern, replacement);
88
+ }
89
+
90
+ // Ensure the filename is not empty
91
+ if (!sanitized) {
92
+ sanitized = 'unnamed_file';
93
+ }
94
+
95
+ const result = sanitized + ext;
96
+
97
+ // Cache the result for future use
98
+ sanitizationCache.set(fileName, result);
99
+
100
+ return result;
101
+ };
102
+
103
+ // Pre-compiled regex patterns for path sanitization
104
+ const PATH_SANITIZATION_PATTERNS = [
105
+ [/[\\?%*:|"<>[\]~]/g, '-'],
106
+ [/ +/g, ' '],
107
+ [/^\.+/, ''],
108
+ [/\/+/g, '/'],
109
+ ];
110
+
111
+ // Cache for sanitized paths
112
+ const pathSanitizationCache = new Map();
113
+
114
+ // Batch logging system for performance
115
+ class LogBatcher {
116
+ constructor(batchSize = 50, flushInterval = 5000) {
117
+ this.batch = [];
118
+ this.batchSize = batchSize;
119
+ this.flushInterval = flushInterval;
120
+ this.lastFlush = Date.now();
121
+ this.flushTimer = null;
122
+ }
123
+
124
+ add(logEntry) {
125
+ this.batch.push({
126
+ filename: path.basename(logEntry.file),
127
+ path: logEntry.uploadPath,
128
+ status: logEntry.status,
129
+ message: logEntry.message,
130
+ });
131
+
132
+ // Auto-flush if batch is full or enough time has passed
133
+ if (
134
+ this.batch.length >= this.batchSize ||
135
+ Date.now() - this.lastFlush > this.flushInterval
136
+ ) {
137
+ this.flush();
138
+ }
139
+ }
140
+
141
+ async flush() {
142
+ if (this.batch.length === 0) return;
143
+
144
+ const logsToSend = [...this.batch];
145
+ this.batch = [];
146
+ this.lastFlush = Date.now();
147
+
148
+ // Clear any pending timer
149
+ if (this.flushTimer) {
150
+ clearTimeout(this.flushTimer);
151
+ this.flushTimer = null;
152
+ }
153
+
154
+ try {
155
+ const { error } = await supabase.from('upload_logs').insert(logsToSend);
156
+ if (error) {
157
+ console.error(
158
+ `⚠️ Error saving batch of ${logsToSend.length} logs to Supabase: ${error.message}`,
159
+ );
160
+ // Re-add failed logs to batch for retry (optional)
161
+ this.batch.unshift(...logsToSend);
162
+ } else {
163
+ // Only show verbose output if requested
164
+ if (process.env.LOG_BATCH_VERBOSE === 'true') {
165
+ console.log(`📊 Flushed ${logsToSend.length} logs to Supabase`);
166
+ }
167
+ }
168
+ } catch (err) {
169
+ console.error(`⚠️ Error during batch flush: ${err.message}`);
170
+ // Re-add failed logs to batch for retry (optional)
171
+ this.batch.unshift(...logsToSend);
172
+ }
173
+ }
174
+
175
+ // Schedule auto-flush if not already scheduled
176
+ scheduleFlush() {
177
+ if (!this.flushTimer && this.batch.length > 0) {
178
+ this.flushTimer = setTimeout(() => {
179
+ this.flush();
180
+ }, this.flushInterval);
181
+ }
182
+ }
183
+
184
+ // Force flush all pending logs (called at end of process)
185
+ async forceFlush() {
186
+ if (this.flushTimer) {
187
+ clearTimeout(this.flushTimer);
188
+ this.flushTimer = null;
189
+ }
190
+ await this.flush();
191
+ }
192
+ }
193
+
194
+ // Global log batcher instance
195
+ const logBatcher = new LogBatcher();
196
+
197
+ // Function to manage cache size (prevent memory issues in long sessions)
198
+ const manageCaches = () => {
199
+ const MAX_CACHE_SIZE = 1000;
200
+
201
+ if (sanitizationCache.size > MAX_CACHE_SIZE) {
202
+ // Keep only the most recent 500 entries
203
+ const entries = Array.from(sanitizationCache.entries());
204
+ sanitizationCache.clear();
205
+ entries.slice(-500).forEach(([key, value]) => {
206
+ sanitizationCache.set(key, value);
207
+ });
208
+ }
209
+
210
+ if (pathSanitizationCache.size > MAX_CACHE_SIZE) {
211
+ const entries = Array.from(pathSanitizationCache.entries());
212
+ pathSanitizationCache.clear();
213
+ entries.slice(-500).forEach(([key, value]) => {
214
+ pathSanitizationCache.set(key, value);
215
+ });
216
+ }
217
+ };
218
+
219
+ const sanitizePath = (inputPath) => {
220
+ // Check cache first
221
+ if (pathSanitizationCache.has(inputPath)) {
222
+ return pathSanitizationCache.get(inputPath);
223
+ }
224
+
225
+ // Fast path for already clean paths
226
+ if (!/[\\?%*:|"<>[\]~]|^ +|^\.+|\/\/+/.test(inputPath)) {
227
+ pathSanitizationCache.set(inputPath, inputPath);
228
+ return inputPath;
229
+ }
230
+
231
+ let sanitized = inputPath;
232
+
233
+ // Apply path sanitization patterns
234
+ for (const [pattern, replacement] of PATH_SANITIZATION_PATTERNS) {
235
+ sanitized = sanitized.replace(pattern, replacement);
236
+ }
237
+
238
+ // Cache the result
239
+ pathSanitizationCache.set(inputPath, sanitized);
240
+
241
+ return sanitized;
242
+ };
36
243
 
37
244
  const sendLogToSupabase = async ({ file, uploadPath, status, message }) => {
245
+ // Add to batch instead of sending immediately
246
+ logBatcher.add({ file, uploadPath, status, message });
247
+
248
+ // Schedule auto-flush if needed
249
+ logBatcher.scheduleFlush();
250
+ };
251
+
252
+ // Enhanced version for immediate sending (used for critical errors)
253
+ const sendLogToSupabaseImmediate = async ({
254
+ file,
255
+ uploadPath,
256
+ status,
257
+ message,
258
+ }) => {
38
259
  const { error } = await supabase.from('upload_logs').insert([
39
260
  {
40
261
  filename: path.basename(file),
@@ -45,7 +266,9 @@ const sendLogToSupabase = async ({ file, uploadPath, status, message }) => {
45
266
  ]);
46
267
 
47
268
  if (error) {
48
- console.error(`⚠️ Error saving the log to Supabase: ${error.message}`);
269
+ console.error(
270
+ `⚠️ Error saving immediate log to Supabase: ${error.message}`,
271
+ );
49
272
  }
50
273
  };
51
274
 
@@ -55,7 +278,7 @@ const checkCredentials = async () => {
55
278
  '⚠️ Missing Supabase credentials. Please set SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET in your environment variables.',
56
279
  );
57
280
  writeLog('⚠️ Missing Supabase credentials.');
58
- await sendLogToSupabase({
281
+ await sendLogToSupabaseImmediate({
59
282
  file: 'Error',
60
283
  uploadPath: 'Error',
61
284
  status: 'error',
@@ -69,7 +292,7 @@ const checkCredentials = async () => {
69
292
  if (error) {
70
293
  console.error('⚠️ Error connecting to Supabase:', error.message);
71
294
  writeLog(`⚠️ Error connecting to Supabase: ${error.message}`);
72
- await sendLogToSupabase({
295
+ await sendLogToSupabaseImmediate({
73
296
  file: 'Error',
74
297
  uploadPath: 'Error',
75
298
  status: 'error',
@@ -80,7 +303,7 @@ const checkCredentials = async () => {
80
303
  } catch (err) {
81
304
  console.error('⚠️ Error:', err.message);
82
305
  writeLog(`⚠️ Error: ${err.message}`);
83
- await sendLogToSupabase({
306
+ await sendLogToSupabaseImmediate({
84
307
  file: 'Error',
85
308
  uploadPath: 'Error',
86
309
  status: 'error',
@@ -101,7 +324,7 @@ const fileExistsInBucket = async (pathInBucket) => {
101
324
  if (error) {
102
325
  console.error(`⚠️ Could not verify duplicate: ${error.message}`);
103
326
  writeLog(`⚠️ Could not verify duplicate: ${error.message}`);
104
- await sendLogToSupabase({
327
+ await sendLogToSupabaseImmediate({
105
328
  file: 'Error',
106
329
  uploadPath: 'Error',
107
330
  status: 'error',
@@ -189,23 +412,326 @@ const getProcessedPaths = async () => {
189
412
 
190
413
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
191
414
 
192
- const uploadWithRetry = async (uploadFn, maxRetries = 3, delayMs = 1000) => {
415
+ const uploadWithRetry = async (uploadFn, maxRetries = 5, delayMs = 2000) => {
193
416
  let attempt = 0;
417
+ let lastError;
418
+
194
419
  while (attempt < maxRetries) {
195
- const result = await uploadFn();
196
- if (!result.error) return result;
197
- attempt++;
198
- if (attempt < maxRetries) await delay(delayMs);
420
+ try {
421
+ const result = await uploadFn();
422
+ if (!result.error) return result;
423
+ lastError = result.error;
424
+ attempt++;
425
+
426
+ // Exponential backoff with jitter
427
+ if (attempt < maxRetries) {
428
+ const backoffDelay =
429
+ delayMs * Math.pow(2, attempt - 1) + Math.random() * 1000;
430
+ console.log(
431
+ `Retry ${attempt}/${maxRetries} after ${Math.round(backoffDelay)}ms...`,
432
+ );
433
+ await delay(backoffDelay);
434
+ }
435
+ } catch (error) {
436
+ lastError = error;
437
+ attempt++;
438
+
439
+ if (attempt < maxRetries) {
440
+ const backoffDelay =
441
+ delayMs * Math.pow(2, attempt - 1) + Math.random() * 1000;
442
+ console.log(
443
+ `Retry ${attempt}/${maxRetries} after ${Math.round(backoffDelay)}ms due to exception...`,
444
+ );
445
+ await delay(backoffDelay);
446
+ }
447
+ }
199
448
  }
200
- return { error: new Error('Max retries exceeded') };
449
+
450
+ return {
451
+ error: new Error(
452
+ `Max retries exceeded. Last error: ${lastError?.message || 'Unknown error'}`,
453
+ ),
454
+ };
455
+ };
456
+
457
+ // Function to process a single file
458
+ const processFile = async (
459
+ file,
460
+ options,
461
+ basePath,
462
+ folder,
463
+ sourcePath,
464
+ processedPaths,
465
+ ) => {
466
+ let currentFile = file;
467
+ let result = {
468
+ success: false,
469
+ skipped: false,
470
+ error: null,
471
+ message: '',
472
+ };
473
+
474
+ try {
475
+ // Check if we need to rename the file
476
+ if (options.renameFiles) {
477
+ const originalName = path.basename(file);
478
+ const sanitizedName = sanitizeFileName(originalName);
479
+
480
+ if (originalName !== sanitizedName) {
481
+ const newFilePath = path.join(path.dirname(file), sanitizedName);
482
+
483
+ if (options.dryRun) {
484
+ result.message = `Would rename: ${originalName} → ${sanitizedName}`;
485
+ result.skipped = true;
486
+ return result;
487
+ } else {
488
+ try {
489
+ fs.renameSync(file, newFilePath);
490
+ currentFile = newFilePath;
491
+ writeLog(`RENAMED: ${originalName} → ${sanitizedName}`);
492
+ await sendLogToSupabase({
493
+ file: originalName,
494
+ uploadPath: sanitizedName,
495
+ status: 'renamed',
496
+ message: `Renamed from ${originalName}`,
497
+ });
498
+ } catch (renameError) {
499
+ result.error = `Failed to rename ${originalName}: ${renameError.message}`;
500
+ writeLog(`RENAME_ERROR: ${originalName} | ${renameError.message}`);
501
+ return result;
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ const content = fs.readFileSync(currentFile);
508
+ const relativePathRaw = path
509
+ .relative(basePath, currentFile)
510
+ .replace(/^[\\/]+/, '')
511
+ .replace(/\\/g, '/');
512
+
513
+ // Always sanitize the filename for upload path
514
+ const pathParts = relativePathRaw.split('/');
515
+ const originalFileName = pathParts[pathParts.length - 1];
516
+ const sanitizedFileName = sanitizeFileName(originalFileName);
517
+ pathParts[pathParts.length - 1] = sanitizedFileName;
518
+ const sanitizedRelativePath = pathParts.join('/');
519
+
520
+ const uploadPathRaw = options.prefix
521
+ ? path.posix.join(options.prefix, sanitizedRelativePath)
522
+ : sanitizedRelativePath;
523
+ const uploadPath = sanitizePath(uploadPathRaw);
524
+
525
+ if (
526
+ uploadPath !== uploadPathRaw ||
527
+ originalFileName !== sanitizedFileName
528
+ ) {
529
+ writeLog(`SANITIZED: ${relativePathRaw} → ${uploadPath}`);
530
+ await sendLogToSupabase({
531
+ file: currentFile,
532
+ uploadPath: relativePathRaw,
533
+ status: 'sanitized',
534
+ message: `Sanitized to ${uploadPath} (Arela Version: ${version})`,
535
+ });
536
+ }
537
+
538
+ if (processedPaths.has(uploadPath)) {
539
+ result.skipped = true;
540
+ result.message = `Already processed (log): ${currentFile}`;
541
+ return result;
542
+ }
543
+
544
+ const contentType = mime.lookup(currentFile) || 'application/octet-stream';
545
+
546
+ const exists = await fileExistsInBucket(uploadPath);
547
+
548
+ if (exists) {
549
+ result.skipped = true;
550
+ result.message = `Skipped (already exists): ${currentFile}`;
551
+ writeLog(`SKIPPED: ${currentFile} -> ${uploadPath}`);
552
+ await sendLogToSupabase({
553
+ file: currentFile,
554
+ uploadPath,
555
+ status: 'skipped',
556
+ message: 'Already exists in bucket',
557
+ });
558
+ return result;
559
+ }
560
+
561
+ const { error } = await uploadWithRetry(() =>
562
+ supabase.storage.from(bucket).upload(uploadPath, content, {
563
+ upsert: true,
564
+ contentType,
565
+ metadata: {
566
+ originalName: path.basename(currentFile),
567
+ sanitizedName: path.basename(uploadPath),
568
+ clientPath: path.posix.join(
569
+ basePath,
570
+ folder,
571
+ path.relative(sourcePath, currentFile).replace(/\\/g, '/'),
572
+ ),
573
+ arelaVersion: version,
574
+ },
575
+ }),
576
+ );
577
+
578
+ if (error) {
579
+ result.error = error.message || JSON.stringify(error);
580
+ writeLog(`ERROR: ${currentFile} -> ${uploadPath} | ${result.error}`);
581
+ await sendLogToSupabase({
582
+ file: currentFile,
583
+ uploadPath,
584
+ status: 'error',
585
+ message: result.error,
586
+ });
587
+ } else {
588
+ result.success = true;
589
+ result.message = `Uploaded ${currentFile} -> ${uploadPath}`;
590
+ writeLog(`SUCCESS: ${currentFile} -> ${uploadPath}`);
591
+ await sendLogToSupabase({
592
+ file: currentFile,
593
+ uploadPath,
594
+ status: 'success',
595
+ message: 'Uploaded successfully',
596
+ });
597
+ }
598
+ } catch (err) {
599
+ result.error = err.message || JSON.stringify(err);
600
+ writeLog(`ERROR: ${currentFile} | ${result.error}`);
601
+ await sendLogToSupabase({
602
+ file: currentFile,
603
+ uploadPath: currentFile,
604
+ status: 'error',
605
+ message: result.error,
606
+ });
607
+ }
608
+
609
+ return result;
610
+ };
611
+
612
+ // Function to process files in parallel batches
613
+ const processFilesInBatches = async (
614
+ files,
615
+ batchSize,
616
+ options,
617
+ basePath,
618
+ folder,
619
+ sourcePath,
620
+ processedPaths,
621
+ ) => {
622
+ let successCount = 0;
623
+ let failureCount = 0;
624
+ let skippedCount = 0;
625
+
626
+ const progressBar = new cliProgress.SingleBar({
627
+ format:
628
+ '📂 Processing [{bar}] {percentage}% | {value}/{total} files | Success: {successCount} | Errors: {failureCount} | Skipped: {skippedCount}',
629
+ barCompleteChar: '█',
630
+ barIncompleteChar: '░',
631
+ hideCursor: true,
632
+ });
633
+
634
+ progressBar.start(files.length, 0, {
635
+ successCount: 0,
636
+ failureCount: 0,
637
+ skippedCount: 0,
638
+ });
639
+
640
+ for (let i = 0; i < files.length; i += batchSize) {
641
+ const batch = files.slice(i, i + batchSize);
642
+
643
+ // Process batch in parallel
644
+ const batchResults = await Promise.all(
645
+ batch.map((file) =>
646
+ processFile(
647
+ file,
648
+ options,
649
+ basePath,
650
+ folder,
651
+ sourcePath,
652
+ processedPaths,
653
+ ),
654
+ ),
655
+ );
656
+
657
+ // Update counters and progress
658
+ for (const result of batchResults) {
659
+ if (result.success) {
660
+ successCount++;
661
+ } else if (result.skipped) {
662
+ skippedCount++;
663
+ } else if (result.error) {
664
+ failureCount++;
665
+ console.error(`❌ ${result.error}`);
666
+ }
667
+
668
+ if (result.message && !result.error) {
669
+ console.log(`✅ ${result.message}`);
670
+ }
671
+ }
672
+
673
+ progressBar.update(i + batch.length, {
674
+ successCount,
675
+ failureCount,
676
+ skippedCount,
677
+ });
678
+
679
+ // Manage cache size periodically (every 100 files processed)
680
+ if ((i + batch.length) % 100 === 0) {
681
+ manageCaches();
682
+ // Also flush logs every 100 files to maintain responsiveness
683
+ await logBatcher.flush();
684
+ }
685
+
686
+ // Small delay between batches to prevent overwhelming the server
687
+ if (i + batchSize < files.length) {
688
+ await delay(100);
689
+ }
690
+ }
691
+
692
+ progressBar.stop();
693
+
694
+ return {
695
+ successCount,
696
+ failureCount,
697
+ skippedCount,
698
+ };
201
699
  };
202
700
 
203
701
  program
204
702
  .name('supabase-uploader')
205
703
  .description('CLI to upload folders from a base path to Supabase Storage')
206
- .version(version)
704
+ .option('-v, --version', 'output the version number')
207
705
  .option('-p, --prefix <prefix>', 'Prefix path in bucket', '')
706
+ .option(
707
+ '-r, --rename-files',
708
+ 'Rename files with problematic characters before uploading',
709
+ )
710
+ .option(
711
+ '--dry-run',
712
+ 'Show what files would be renamed without actually renaming them',
713
+ )
714
+ .option(
715
+ '-c, --concurrency <number>',
716
+ 'Number of files to process concurrently (default: 3)',
717
+ '3',
718
+ )
719
+ .option(
720
+ '--show-cache-stats',
721
+ 'Show cache statistics for performance analysis',
722
+ )
723
+ .option(
724
+ '--batch-size <number>',
725
+ 'Number of logs to batch before sending to Supabase (default: 50)',
726
+ '50',
727
+ )
208
728
  .action(async (options) => {
729
+ // Handle version option
730
+ if (options.version) {
731
+ console.log(version);
732
+ process.exit(0);
733
+ }
734
+
209
735
  if (!basePath || !sources || sources.length === 0) {
210
736
  console.error(
211
737
  '⚠️ UPLOAD_BASE_PATH or UPLOAD_SOURCES not defined in environment variables.',
@@ -213,6 +739,15 @@ program
213
739
  process.exit(1);
214
740
  }
215
741
 
742
+ const concurrency = parseInt(options.concurrency) || 3;
743
+ const batchSize = parseInt(options.batchSize) || 50;
744
+
745
+ // Configure log batcher with custom batch size
746
+ logBatcher.batchSize = batchSize;
747
+
748
+ console.log(`🚀 Using concurrency level: ${concurrency}`);
749
+ console.log(`📦 Using log batch size: ${batchSize}`);
750
+
216
751
  const processedPaths = await getProcessedPaths();
217
752
  let globalSuccess = 0;
218
753
  let globalFailure = 0;
@@ -227,122 +762,58 @@ program
227
762
  ? await globby([`${sourcePath}/**/*`], { onlyFiles: true })
228
763
  : [sourcePath];
229
764
 
230
- const progressBar = new cliProgress.SingleBar({
231
- format: '📂 Reading [{bar}] {percentage}% | {value}/{total} files',
232
- barCompleteChar: '█',
233
- barIncompleteChar: '░',
234
- hideCursor: true,
235
- });
236
- progressBar.start(files.length, 0);
237
-
238
- let successCount = 0;
239
- let failureCount = 0;
240
-
241
- for (const file of files) {
242
- progressBar.increment();
243
- const content = fs.readFileSync(file);
244
- const relativePathRaw = path
245
- .relative(basePath, file)
246
- .replace(/^[\\/]+/, '')
247
- .replace(/\\/g, '/');
248
- const uploadPathRaw = options.prefix
249
- ? path.posix.join(options.prefix, relativePathRaw)
250
- : relativePathRaw;
251
- const uploadPath = sanitizePath(uploadPathRaw);
252
-
253
- if (uploadPath !== uploadPathRaw) {
254
- writeLog(`SANITIZED: ${uploadPathRaw} → ${uploadPath}`);
255
- await sendLogToSupabase({
256
- file,
257
- uploadPath: uploadPathRaw,
258
- status: 'sanitized',
259
- message: `Sanitized to ${uploadPath}`,
260
- });
261
- }
262
-
263
- if (processedPaths.has(uploadPath)) {
264
- ora().info(`⏭️ Already processed (log): ${file}`);
265
- continue;
266
- }
765
+ console.log(`📊 Found ${files.length} files to process`);
267
766
 
268
- const contentType = mime.lookup(file) || 'application/octet-stream';
767
+ // Process files in parallel batches
768
+ const { successCount, failureCount, skippedCount } =
769
+ await processFilesInBatches(
770
+ files,
771
+ concurrency,
772
+ options,
773
+ basePath,
774
+ folder,
775
+ sourcePath,
776
+ processedPaths,
777
+ );
269
778
 
270
- const spinner = ora(`Checking ${file}...`).start();
271
- const exists = await fileExistsInBucket(uploadPath);
779
+ globalSuccess += successCount;
780
+ globalFailure += failureCount;
272
781
 
273
- if (exists) {
274
- spinner.info(`⏭️ Skipped (already exists): ${file}`);
275
- writeLog(`SKIPPED: ${file} -> ${uploadPath}`);
276
- await sendLogToSupabase({
277
- file,
278
- uploadPath,
279
- status: 'skipped',
280
- message: 'Already exists in bucket',
281
- });
282
- continue;
283
- }
284
-
285
- try {
286
- // await delay(5000); // TODO: Remove this delay before production
287
-
288
- const { error } = await uploadWithRetry(() =>
289
- supabase.storage.from(bucket).upload(uploadPath, content, {
290
- upsert: true,
291
- contentType,
292
- metadata: {
293
- originalName: path.basename(file),
294
- clientPath: path.posix.join(
295
- basePath,
296
- folder,
297
- path.relative(sourcePath, file).replace(/\\/g, '/'),
298
- ),
299
- },
300
- }),
301
- );
302
-
303
- if (error) {
304
- failureCount++;
305
- globalFailure++;
306
- spinner.fail(
307
- `❌ Failed to upload ${file}: ${JSON.stringify(error, null, 2)}`,
308
- );
309
- writeLog(`ERROR: ${file} -> ${uploadPath} | ${error.message}`);
310
- await sendLogToSupabase({
311
- file,
312
- uploadPath,
313
- status: 'error',
314
- message: error.message,
315
- });
316
- } else {
317
- successCount++;
318
- globalSuccess++;
319
- spinner.succeed(`✅ Uploaded ${file} -> ${uploadPath}`);
320
- writeLog(`SUCCESS: ${file} -> ${uploadPath}`);
321
- await sendLogToSupabase({
322
- file,
323
- uploadPath,
324
- status: 'success',
325
- message: 'Uploaded successfully',
326
- });
327
- }
328
- } catch (err) {
329
- spinner.fail(`❌ Error uploading ${file}: ${err.message}`);
330
- writeLog(`❌ Error uploading ${file}: ${err.message}`);
331
- }
332
- }
333
-
334
- progressBar.stop();
335
-
336
- console.log(`\n📦 Upload Summary:`);
782
+ console.log(`\n📦 Upload Summary for ${folder}:`);
337
783
  console.log(` ✅ Successfully uploaded files: ${successCount}`);
338
784
  console.log(` ❌ Files with errors: ${failureCount}`);
339
- console.log(
340
- ` ⏭️ Files skipped (already exist): ${files.length - successCount - failureCount}`,
341
- );
785
+ console.log(` ⏭️ Files skipped (already exist): ${skippedCount}`);
342
786
  console.log(` 📜 Log file: ${logFilePath} \n`);
343
787
 
788
+ // Show cache statistics if requested
789
+ if (options.showCacheStats) {
790
+ console.log(`📊 Cache Statistics:`);
791
+ console.log(
792
+ ` 🗂️ Filename sanitization cache: ${sanitizationCache.size} entries`,
793
+ );
794
+ console.log(
795
+ ` 📁 Path sanitization cache: ${pathSanitizationCache.size} entries`,
796
+ );
797
+ console.log(
798
+ ` 📋 Log batch pending: ${logBatcher.batch.length} entries`,
799
+ );
800
+
801
+ // Calculate cache hit rate (rough estimation)
802
+ const totalProcessed = successCount + failureCount + skippedCount;
803
+ const estimatedCacheHitRate =
804
+ totalProcessed > 0
805
+ ? Math.round(
806
+ ((totalProcessed - sanitizationCache.size) / totalProcessed) *
807
+ 100,
808
+ )
809
+ : 0;
810
+ console.log(
811
+ ` 🎯 Estimated cache hit rate: ${Math.max(0, estimatedCacheHitRate)}%\n`,
812
+ );
813
+ }
814
+
344
815
  writeLog(
345
- `📦 Upload Summary for folder ${folder}: Success: ${successCount}, Errors: ${failureCount}, Skipped: ${files.length - successCount - failureCount}`,
816
+ `📦 Upload Summary for folder ${folder}: Success: ${successCount}, Errors: ${failureCount}, Skipped: ${skippedCount}`,
346
817
  );
347
818
  } catch (err) {
348
819
  console.error(`⚠️ Error processing folder ${folder}:`, err.message);
@@ -353,9 +824,14 @@ program
353
824
  status: 'error',
354
825
  message: err.message,
355
826
  });
827
+ globalFailure++;
356
828
  }
357
829
  }
358
830
 
831
+ // Force flush any remaining logs before finishing
832
+ console.log(`📤 Flushing remaining logs...`);
833
+ await logBatcher.forceFlush();
834
+
359
835
  console.log(`🎯 Upload completed.`);
360
836
  console.log(` ✅ Total uploaded: ${globalSuccess}`);
361
837
  console.log(` ❌ Total with errors: ${globalFailure}`);
package/upload.log DELETED
@@ -1,158 +0,0 @@
1
- [2025-05-13T19:46:47.620Z] SKIPPED: ../../Documents/2023/2003180/VU_3429_070_2003180.xml -> 2023/2003180/VU_3429_070_2003180.xml
2
- [2025-05-13T19:46:47.650Z] SKIPPED: ../../Documents/2023/2003189/VU_3429_070_2003189.xml -> 2023/2003189/VU_3429_070_2003189.xml
3
- [2025-05-13T19:46:47.662Z] SKIPPED: ../../Documents/2023/2003202/VU_3429_070_2003202.xml -> 2023/2003202/VU_3429_070_2003202.xml
4
- [2025-05-13T19:46:47.671Z] SKIPPED: ../../Documents/2023/2002089/044321004UKC5.pdf -> 2023/2002089/044321004UKC5.pdf
5
- [2025-05-13T19:46:47.682Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-60280-191230MC0-COVE.xml -> 2023/2002089/3429-07-22002089-60280-191230MC0-COVE.xml
6
- [2025-05-13T19:46:47.688Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-60280-191230MC2-COVE.xml -> 2023/2002089/3429-07-22002089-60280-191230MC2-COVE.xml
7
- [2025-05-13T19:46:47.695Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-60280-191230MC5-COVE.xml -> 2023/2002089/3429-07-22002089-60280-191230MC5-COVE.xml
8
- [2025-05-13T19:46:47.701Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-CoveFact.pdf -> 2023/2002089/3429-07-22002089-CoveFact.pdf
9
- [2025-05-13T19:46:47.708Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-FV-m3429279.006 -> 2023/2002089/3429-07-22002089-FV-m3429279.006
10
- [2025-05-13T19:46:47.714Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-FV-m3429279.err -> 2023/2002089/3429-07-22002089-FV-m3429279.err
11
- [2025-05-13T19:46:47.720Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-PE-A3429811.006 -> 2023/2002089/3429-07-22002089-PE-A3429811.006
12
- [2025-05-13T19:46:47.726Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-PE-E3429811.006 -> 2023/2002089/3429-07-22002089-PE-E3429811.006
13
- [2025-05-13T19:46:47.731Z] SKIPPED: ../../Documents/2023/2002089/3429-07-22002089-Simplif.pdf -> 2023/2002089/3429-07-22002089-Simplif.pdf
14
- [2025-05-13T19:46:47.737Z] SKIPPED: ../../Documents/2023/2002089/COVE-3429-070-2002089-COVE225IIKWC1.pdf -> 2023/2002089/COVE-3429-070-2002089-COVE225IIKWC1.pdf
15
- [2025-05-13T19:46:47.743Z] SKIPPED: ../../Documents/2023/2002089/COVE-3429-070-2002089-COVE225IIKWC1.xml -> 2023/2002089/COVE-3429-070-2002089-COVE225IIKWC1.xml
16
- [2025-05-13T19:46:47.749Z] SKIPPED: ../../Documents/2023/2002089/COVE-3429-070-2002089-COVE225IINHI2.pdf -> 2023/2002089/COVE-3429-070-2002089-COVE225IINHI2.pdf
17
- [2025-05-13T19:46:47.756Z] SKIPPED: ../../Documents/2023/2002089/COVE-3429-070-2002089-COVE225IINHI2.xml -> 2023/2002089/COVE-3429-070-2002089-COVE225IINHI2.xml
18
- [2025-05-13T19:46:47.761Z] SKIPPED: ../../Documents/2023/2002089/DETALLE_COVE225IIKWC1.pdf -> 2023/2002089/DETALLE_COVE225IIKWC1.pdf
19
- [2025-05-13T19:46:47.766Z] SKIPPED: ../../Documents/2023/2002089/DETALLE_COVE225IINHI2.pdf -> 2023/2002089/DETALLE_COVE225IINHI2.pdf
20
- [2025-05-13T19:46:47.773Z] SKIPPED: ../../Documents/2023/2002089/EDOC_044321004UKC5.xml -> 2023/2002089/EDOC_044321004UKC5.xml
21
- [2025-05-13T19:46:47.781Z] SKIPPED: ../../Documents/2023/2002089/VU_3429_070_2002089.xml -> 2023/2002089/VU_3429_070_2002089.xml
22
- [2025-05-13T19:46:47.787Z] SKIPPED: ../../Documents/2023/2002089/VU_REMESA_3429_070_2002089.xml -> 2023/2002089/VU_REMESA_3429_070_2002089.xml
23
- [2025-05-13T19:46:47.792Z] SKIPPED: ../../Documents/2023/2000601/04402200X9L27.pdf -> 2023/2000601/04402200X9L27.pdf
24
- [2025-05-13T19:46:47.798Z] SKIPPED: ../../Documents/2023/2000601/04402200X9L27.xml -> 2023/2000601/04402200X9L27.xml
25
- [2025-05-13T19:46:47.805Z] SKIPPED: ../../Documents/2023/2000601/04412000Z75A8.pdf -> 2023/2000601/04412000Z75A8.pdf
26
- [2025-05-13T19:46:47.811Z] SKIPPED: ../../Documents/2023/2000601/04432000491D6.pdf -> 2023/2000601/04432000491D6.pdf
27
- [2025-05-13T19:46:47.818Z] SKIPPED: ../../Documents/2023/2000601/044320004GF86.pdf -> 2023/2000601/044320004GF86.pdf
28
- [2025-05-13T19:46:47.824Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-73096-COVE.xml -> 2023/2000601/3429-07-22000601-73096-COVE.xml
29
- [2025-05-13T19:46:47.830Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-90972508-0001-DODA.pdf -> 2023/2000601/3429-07-22000601-90972508-0001-DODA.pdf
30
- [2025-05-13T19:46:47.837Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-CoveFact.pdf -> 2023/2000601/3429-07-22000601-CoveFact.pdf
31
- [2025-05-13T19:46:47.842Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-FV-m3429536.009 -> 2023/2000601/3429-07-22000601-FV-m3429536.009
32
- [2025-05-13T19:46:47.847Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-FV-m3429536.err -> 2023/2000601/3429-07-22000601-FV-m3429536.err
33
- [2025-05-13T19:46:47.852Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-MX-B376W_20221212_162523-RespDgs.xml -> 2023/2000601/3429-07-22000601-MX-B376W_20221212_162523-RespDgs.xml
34
- [2025-05-13T19:46:47.856Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-PE-A3429518.009 -> 2023/2000601/3429-07-22000601-PE-A3429518.009
35
- [2025-05-13T19:46:47.861Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-PE-E3429518.009 -> 2023/2000601/3429-07-22000601-PE-E3429518.009
36
- [2025-05-13T19:46:47.866Z] SKIPPED: ../../Documents/2023/2000601/3429-07-22000601-Simplif.pdf -> 2023/2000601/3429-07-22000601-Simplif.pdf
37
- [2025-05-13T19:46:47.872Z] SKIPPED: ../../Documents/2023/2000601/COVE-3429-070-2000601-COVE225GYTBB3.pdf -> 2023/2000601/COVE-3429-070-2000601-COVE225GYTBB3.pdf
38
- [2025-05-13T19:46:47.878Z] SKIPPED: ../../Documents/2023/2000601/COVE-3429-070-2000601-COVE225GYTBB3.xml -> 2023/2000601/COVE-3429-070-2000601-COVE225GYTBB3.xml
39
- [2025-05-13T19:46:47.883Z] SKIPPED: ../../Documents/2023/2000601/DETALLE_COVE225GYTBB3.pdf -> 2023/2000601/DETALLE_COVE225GYTBB3.pdf
40
- [2025-05-13T19:46:47.889Z] SKIPPED: ../../Documents/2023/2000601/EDOC-3429-070-2000601-04402200X9L27.pdf -> 2023/2000601/EDOC-3429-070-2000601-04402200X9L27.pdf
41
- [2025-05-13T19:46:47.894Z] SKIPPED: ../../Documents/2023/2000601/EDOC-3429-070-2000601-04412000Z75A8.pdf -> 2023/2000601/EDOC-3429-070-2000601-04412000Z75A8.pdf
42
- [2025-05-13T19:46:47.900Z] SKIPPED: ../../Documents/2023/2000601/EDOC-3429-070-2000601-04432000491D6.pdf -> 2023/2000601/EDOC-3429-070-2000601-04432000491D6.pdf
43
- [2025-05-13T19:46:47.905Z] SKIPPED: ../../Documents/2023/2000601/EDOC-3429-070-2000601-044320004GF86.pdf -> 2023/2000601/EDOC-3429-070-2000601-044320004GF86.pdf
44
- [2025-05-13T19:46:47.911Z] SKIPPED: ../../Documents/2023/2000601/EDOC_04412000Z75A8.xml -> 2023/2000601/EDOC_04412000Z75A8.xml
45
- [2025-05-13T19:46:47.917Z] SKIPPED: ../../Documents/2023/2000601/EDOC_04432000491D6.xml -> 2023/2000601/EDOC_04432000491D6.xml
46
- [2025-05-13T19:46:47.922Z] SKIPPED: ../../Documents/2023/2000601/EDOC_044320004GF86.xml -> 2023/2000601/EDOC_044320004GF86.xml
47
- [2025-05-13T19:46:47.927Z] SKIPPED: ../../Documents/2023/2000601/VU_3429_070_2000601.xml -> 2023/2000601/VU_3429_070_2000601.xml
48
- [2025-05-13T19:46:47.931Z] SKIPPED: ../../Documents/2023/2000601/VU_REMESA_3429_070_2000601.xml -> 2023/2000601/VU_REMESA_3429_070_2000601.xml
49
- [2025-05-13T19:46:47.932Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
50
- [2025-05-13T19:46:47.934Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
51
- [2025-05-13T19:46:47.939Z] SKIPPED: ../../Documents/2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf -> 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
52
- [2025-05-13T19:46:47.943Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
53
- [2025-05-13T19:46:47.947Z] SKIPPED: ../../Documents/2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf -> 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
54
- [2025-05-13T19:46:47.952Z] SKIPPED: ../../Documents/2024/2003207/VU_3429_070_2003207.xml -> 2024/2003207/VU_3429_070_2003207.xml
55
- [2025-05-13T19:46:47.955Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
56
- [2025-05-13T19:46:47.960Z] SKIPPED: ../../Documents/2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf -> 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
57
- [2025-05-13T19:46:47.965Z] SKIPPED: ../../Documents/2024/2003212/VU_3429_070_2003212.xml -> 2024/2003212/VU_3429_070_2003212.xml
58
- [2025-05-13T19:46:47.967Z] 📦 Upload Summary for folder 2024: Success: 0, Errors: 0, Skipped: 5
59
- [2025-05-13T19:46:47.981Z] SUCCESS: ../../Documents/2025/2003180/VU_3429_070_2003180.xml -> 2025/2003180/VU_3429_070_2003180.xml
60
- [2025-05-13T19:46:47.992Z] SUCCESS: ../../Documents/2025/2003189/VU_3429_070_2003189.xml -> 2025/2003189/VU_3429_070_2003189.xml
61
- [2025-05-13T19:46:48.003Z] SUCCESS: ../../Documents/2025/2003202/VU_3429_070_2003202.xml -> 2025/2003202/VU_3429_070_2003202.xml
62
- [2025-05-13T19:46:48.027Z] SUCCESS: ../../Documents/2025/2002089/044321004UKC5.pdf -> 2025/2002089/044321004UKC5.pdf
63
- [2025-05-13T19:46:48.038Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-60280-191230MC0-COVE.xml -> 2025/2002089/3429-07-22002089-60280-191230MC0-COVE.xml
64
- [2025-05-13T19:46:48.060Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-60280-191230MC2-COVE.xml -> 2025/2002089/3429-07-22002089-60280-191230MC2-COVE.xml
65
- [2025-05-13T19:46:48.077Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-60280-191230MC5-COVE.xml -> 2025/2002089/3429-07-22002089-60280-191230MC5-COVE.xml
66
- [2025-05-13T19:46:48.096Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-CoveFact.pdf -> 2025/2002089/3429-07-22002089-CoveFact.pdf
67
- [2025-05-13T19:46:48.108Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-FV-m3429279.006 -> 2025/2002089/3429-07-22002089-FV-m3429279.006
68
- [2025-05-13T19:46:48.118Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-FV-m3429279.err -> 2025/2002089/3429-07-22002089-FV-m3429279.err
69
- [2025-05-13T19:46:48.128Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-PE-A3429811.006 -> 2025/2002089/3429-07-22002089-PE-A3429811.006
70
- [2025-05-13T19:46:48.138Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-PE-E3429811.006 -> 2025/2002089/3429-07-22002089-PE-E3429811.006
71
- [2025-05-13T19:46:48.153Z] SUCCESS: ../../Documents/2025/2002089/3429-07-22002089-Simplif.pdf -> 2025/2002089/3429-07-22002089-Simplif.pdf
72
- [2025-05-13T19:46:48.164Z] SUCCESS: ../../Documents/2025/2002089/COVE-3429-070-2002089-COVE225IIKWC1.pdf -> 2025/2002089/COVE-3429-070-2002089-COVE225IIKWC1.pdf
73
- [2025-05-13T19:46:48.175Z] SUCCESS: ../../Documents/2025/2002089/COVE-3429-070-2002089-COVE225IIKWC1.xml -> 2025/2002089/COVE-3429-070-2002089-COVE225IIKWC1.xml
74
- [2025-05-13T19:46:48.187Z] SUCCESS: ../../Documents/2025/2002089/COVE-3429-070-2002089-COVE225IINHI2.pdf -> 2025/2002089/COVE-3429-070-2002089-COVE225IINHI2.pdf
75
- [2025-05-13T19:46:48.197Z] SUCCESS: ../../Documents/2025/2002089/COVE-3429-070-2002089-COVE225IINHI2.xml -> 2025/2002089/COVE-3429-070-2002089-COVE225IINHI2.xml
76
- [2025-05-13T19:46:48.212Z] SUCCESS: ../../Documents/2025/2002089/DETALLE_COVE225IIKWC1.pdf -> 2025/2002089/DETALLE_COVE225IIKWC1.pdf
77
- [2025-05-13T19:46:48.225Z] SUCCESS: ../../Documents/2025/2002089/DETALLE_COVE225IINHI2.pdf -> 2025/2002089/DETALLE_COVE225IINHI2.pdf
78
- [2025-05-13T19:46:48.243Z] SUCCESS: ../../Documents/2025/2002089/EDOC_044321004UKC5.xml -> 2025/2002089/EDOC_044321004UKC5.xml
79
- [2025-05-13T19:46:48.253Z] SUCCESS: ../../Documents/2025/2002089/VU_3429_070_2002089.xml -> 2025/2002089/VU_3429_070_2002089.xml
80
- [2025-05-13T19:46:48.265Z] SUCCESS: ../../Documents/2025/2002089/VU_REMESA_3429_070_2002089.xml -> 2025/2002089/VU_REMESA_3429_070_2002089.xml
81
- [2025-05-13T19:46:48.319Z] SUCCESS: ../../Documents/2025/2000601/04402200X9L27.pdf -> 2025/2000601/04402200X9L27.pdf
82
- [2025-05-13T19:46:48.401Z] SUCCESS: ../../Documents/2025/2000601/04402200X9L27.xml -> 2025/2000601/04402200X9L27.xml
83
- [2025-05-13T19:46:48.512Z] SUCCESS: ../../Documents/2025/2000601/04412000Z75A8.pdf -> 2025/2000601/04412000Z75A8.pdf
84
- [2025-05-13T19:46:48.650Z] SUCCESS: ../../Documents/2025/2000601/04432000491D6.pdf -> 2025/2000601/04432000491D6.pdf
85
- [2025-05-13T19:46:48.714Z] SUCCESS: ../../Documents/2025/2000601/044320004GF86.pdf -> 2025/2000601/044320004GF86.pdf
86
- [2025-05-13T19:46:48.725Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-73096-COVE.xml -> 2025/2000601/3429-07-22000601-73096-COVE.xml
87
- [2025-05-13T19:46:48.736Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-90972508-0001-DODA.pdf -> 2025/2000601/3429-07-22000601-90972508-0001-DODA.pdf
88
- [2025-05-13T19:46:48.757Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-CoveFact.pdf -> 2025/2000601/3429-07-22000601-CoveFact.pdf
89
- [2025-05-13T19:46:48.767Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-FV-m3429536.009 -> 2025/2000601/3429-07-22000601-FV-m3429536.009
90
- [2025-05-13T19:46:48.778Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-FV-m3429536.err -> 2025/2000601/3429-07-22000601-FV-m3429536.err
91
- [2025-05-13T19:46:48.787Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-MX-B376W_20221212_162523-RespDgs.xml -> 2025/2000601/3429-07-22000601-MX-B376W_20221212_162523-RespDgs.xml
92
- [2025-05-13T19:46:48.800Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-PE-A3429518.009 -> 2025/2000601/3429-07-22000601-PE-A3429518.009
93
- [2025-05-13T19:46:48.810Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-PE-E3429518.009 -> 2025/2000601/3429-07-22000601-PE-E3429518.009
94
- [2025-05-13T19:46:48.826Z] SUCCESS: ../../Documents/2025/2000601/3429-07-22000601-Simplif.pdf -> 2025/2000601/3429-07-22000601-Simplif.pdf
95
- [2025-05-13T19:46:48.838Z] SUCCESS: ../../Documents/2025/2000601/COVE-3429-070-2000601-COVE225GYTBB3.pdf -> 2025/2000601/COVE-3429-070-2000601-COVE225GYTBB3.pdf
96
- [2025-05-13T19:46:48.848Z] SUCCESS: ../../Documents/2025/2000601/COVE-3429-070-2000601-COVE225GYTBB3.xml -> 2025/2000601/COVE-3429-070-2000601-COVE225GYTBB3.xml
97
- [2025-05-13T19:46:48.861Z] SUCCESS: ../../Documents/2025/2000601/DETALLE_COVE225GYTBB3.pdf -> 2025/2000601/DETALLE_COVE225GYTBB3.pdf
98
- [2025-05-13T19:46:48.872Z] SUCCESS: ../../Documents/2025/2000601/EDOC-3429-070-2000601-04402200X9L27.pdf -> 2025/2000601/EDOC-3429-070-2000601-04402200X9L27.pdf
99
- [2025-05-13T19:46:48.884Z] SUCCESS: ../../Documents/2025/2000601/EDOC-3429-070-2000601-04412000Z75A8.pdf -> 2025/2000601/EDOC-3429-070-2000601-04412000Z75A8.pdf
100
- [2025-05-13T19:46:48.895Z] SUCCESS: ../../Documents/2025/2000601/EDOC-3429-070-2000601-04432000491D6.pdf -> 2025/2000601/EDOC-3429-070-2000601-04432000491D6.pdf
101
- [2025-05-13T19:46:48.906Z] SUCCESS: ../../Documents/2025/2000601/EDOC-3429-070-2000601-044320004GF86.pdf -> 2025/2000601/EDOC-3429-070-2000601-044320004GF86.pdf
102
- [2025-05-13T19:46:48.935Z] SUCCESS: ../../Documents/2025/2000601/EDOC_04412000Z75A8.xml -> 2025/2000601/EDOC_04412000Z75A8.xml
103
- [2025-05-13T19:46:48.976Z] SUCCESS: ../../Documents/2025/2000601/EDOC_04432000491D6.xml -> 2025/2000601/EDOC_04432000491D6.xml
104
- [2025-05-13T19:46:49.010Z] SUCCESS: ../../Documents/2025/2000601/EDOC_044320004GF86.xml -> 2025/2000601/EDOC_044320004GF86.xml
105
- [2025-05-13T19:46:49.025Z] SUCCESS: ../../Documents/2025/2000601/VU_3429_070_2000601.xml -> 2025/2000601/VU_3429_070_2000601.xml
106
- [2025-05-13T19:46:49.036Z] SUCCESS: ../../Documents/2025/2000601/VU_REMESA_3429_070_2000601.xml -> 2025/2000601/VU_REMESA_3429_070_2000601.xml
107
- [2025-05-13T19:46:49.038Z] 📦 Upload Summary for folder 2025: Success: 48, Errors: 0, Skipped: 0
108
- [2025-05-13T23:23:45.703Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
109
- [2025-05-13T23:23:45.708Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
110
- [2025-05-13T23:23:45.727Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
111
- [2025-05-13T23:23:45.733Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
112
- [2025-05-13T23:23:45.737Z] 📦 Upload Summary for folder 2024: Success: 0, Errors: 0, Skipped: 5
113
- [2025-05-13T23:23:45.753Z] 📦 Upload Summary for folder 2025: Success: 0, Errors: 0, Skipped: 48
114
- [2025-05-13T23:25:07.223Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
115
- [2025-05-13T23:25:07.225Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
116
- [2025-05-13T23:25:07.243Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
117
- [2025-05-13T23:25:07.247Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
118
- [2025-05-13T23:25:07.251Z] 📦 Upload Summary for folder 2024: Success: 0, Errors: 0, Skipped: 5
119
- [2025-05-13T23:25:07.293Z] SUCCESS: ../../Documents/2025/2003189888/VU_3429_070_2003180.xml -> 2025/2003189888/VU_3429_070_2003180.xml
120
- [2025-05-13T23:25:07.295Z] 📦 Upload Summary for folder 2025: Success: 1, Errors: 0, Skipped: 48
121
- [2025-05-13T23:37:57.110Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
122
- [2025-05-13T23:37:57.113Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
123
- [2025-05-13T23:37:57.131Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
124
- [2025-05-13T23:37:57.136Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
125
- [2025-05-13T23:37:57.157Z] SUCCESS: ../../Documents/2024/2003212/VU_3429_070_2003.xml -> 2024/2003212/VU_3429_070_2003.xml
126
- [2025-05-13T23:37:57.160Z] 📦 Upload Summary for folder 2024: Success: 1, Errors: 0, Skipped: 5
127
- [2025-05-13T23:37:57.175Z] 📦 Upload Summary for folder 2025: Success: 0, Errors: 0, Skipped: 49
128
- [2025-05-13T23:40:13.585Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
129
- [2025-05-13T23:40:13.587Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
130
- [2025-05-13T23:40:13.602Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
131
- [2025-05-13T23:40:13.606Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
132
- [2025-05-13T23:40:13.609Z] 📦 Upload Summary for folder 2024: Success: 0, Errors: 0, Skipped: 6
133
- [2025-05-13T23:40:13.629Z] 📦 Upload Summary for folder 2025: Success: 0, Errors: 0, Skipped: 49
134
- [2025-05-13T23:40:57.243Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
135
- [2025-05-13T23:40:57.245Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
136
- [2025-05-13T23:40:57.262Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
137
- [2025-05-13T23:40:57.286Z] SUCCESS: ../../Documents/2024/2003207/VU_3429_070_2003.xml -> 2024/2003207/VU_3429_070_2003.xml
138
- [2025-05-13T23:40:57.289Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
139
- [2025-05-13T23:40:57.292Z] 📦 Upload Summary for folder 2024: Success: 1, Errors: 0, Skipped: 6
140
- [2025-05-13T23:40:57.309Z] 📦 Upload Summary for folder 2025: Success: 0, Errors: 0, Skipped: 49
141
- [2025-05-13T23:47:19.890Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
142
- [2025-05-13T23:47:19.892Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
143
- [2025-05-13T23:47:19.912Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
144
- [2025-05-13T23:47:19.919Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
145
- [2025-05-13T23:47:19.923Z] 📦 Upload Summary for folder 2024: Success: 0, Errors: 0, Skipped: 7
146
- [2025-05-13T23:47:19.939Z] 📦 Upload Summary for folder 2025: Success: 0, Errors: 0, Skipped: 49
147
- [2025-05-14T00:00:31.007Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
148
- [2025-05-14T00:00:31.010Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
149
- [2025-05-14T00:00:31.027Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
150
- [2025-05-14T00:00:31.032Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
151
- [2025-05-14T00:00:31.037Z] 📦 Upload Summary for folder 2024: Success: 0, Errors: 0, Skipped: 7
152
- [2025-05-14T00:00:31.054Z] 📦 Upload Summary for folder 2025: Success: 0, Errors: 0, Skipped: 49
153
- [2025-05-14T00:02:01.388Z] 📦 Upload Summary for folder 2023: Success: 0, Errors: 0, Skipped: 48
154
- [2025-05-14T00:02:02.398Z] SANITIZED: 2024/4.- 3 - Prueba ~ 3429-07-22002089-CoveFact copy.pdf → 2024/4.- 3 - Prueba - 3429-07-22002089-CoveFact copy.pdf
155
- [2025-05-14T00:02:03.430Z] SANITIZED: 2024/2003207/2. 3 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003207/2. 3 - Prueba - 3429-07-22002089-CoveFact.pdf
156
- [2025-05-14T00:02:06.448Z] SANITIZED: 2024/2003212/2.- 5 - Prueba ~ 3429-07-22002089-CoveFact.pdf → 2024/2003212/2.- 5 - Prueba - 3429-07-22002089-CoveFact.pdf
157
- [2025-05-14T00:02:08.465Z] 📦 Upload Summary for folder 2024: Success: 0, Errors: 0, Skipped: 7
158
- [2025-05-14T00:02:57.621Z] 📦 Upload Summary for folder 2025: Success: 0, Errors: 0, Skipped: 49