@arela/uploader 0.2.5 → 0.2.6
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/package.json +2 -2
- package/src/commands/UploadCommand.js +102 -44
- package/src/config/config.js +15 -10
- package/src/errors/ErrorHandler.js +38 -31
- package/src/errors/ErrorTypes.js +2 -2
- package/src/index-old.js +25 -17
- package/src/index.js +124 -49
- package/src/services/DatabaseService.js +372 -185
- package/src/services/LoggingService.js +3 -3
- package/src/services/upload/ApiUploadService.js +16 -10
- package/src/services/upload/BaseUploadService.js +1 -1
- package/src/services/upload/SupabaseUploadService.js +22 -3
- package/src/services/upload/UploadServiceFactory.js +14 -6
- package/src/utils/FileOperations.js +1 -1
- package/src/utils/FileSanitizer.js +1 -1
- package/src/utils/PathDetector.js +9 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arela/uploader",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "CLI to upload files/directories to Arela",
|
|
5
5
|
"bin": {
|
|
6
6
|
"arela": "./src/index.js"
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"@trivago/prettier-plugin-sort-imports": "5.2.2",
|
|
44
44
|
"prettier": "3.5.3"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
|
@@ -3,12 +3,16 @@ import { globby } from 'globby';
|
|
|
3
3
|
import mime from 'mime-types';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
|
|
6
|
-
import appConfig from '../config/config.js';
|
|
7
|
-
import ErrorHandler from '../errors/ErrorHandler.js';
|
|
8
|
-
import { ConfigurationError, FileOperationError } from '../errors/ErrorTypes.js';
|
|
9
6
|
import databaseService from '../services/DatabaseService.js';
|
|
10
7
|
import logger from '../services/LoggingService.js';
|
|
11
8
|
import uploadServiceFactory from '../services/upload/UploadServiceFactory.js';
|
|
9
|
+
|
|
10
|
+
import appConfig from '../config/config.js';
|
|
11
|
+
import ErrorHandler from '../errors/ErrorHandler.js';
|
|
12
|
+
import {
|
|
13
|
+
ConfigurationError,
|
|
14
|
+
FileOperationError,
|
|
15
|
+
} from '../errors/ErrorTypes.js';
|
|
12
16
|
import FileOperations from '../utils/FileOperations.js';
|
|
13
17
|
import fileSanitizer from '../utils/FileSanitizer.js';
|
|
14
18
|
import pathDetector from '../utils/PathDetector.js';
|
|
@@ -30,15 +34,17 @@ export class UploadCommand {
|
|
|
30
34
|
try {
|
|
31
35
|
// Validate configuration
|
|
32
36
|
this.#validateOptions(options);
|
|
33
|
-
|
|
37
|
+
|
|
34
38
|
// Initialize services
|
|
35
|
-
const uploadService = await uploadServiceFactory.getUploadService(
|
|
39
|
+
const uploadService = await uploadServiceFactory.getUploadService(
|
|
40
|
+
options.forceSupabase,
|
|
41
|
+
);
|
|
36
42
|
const sources = appConfig.getUploadSources();
|
|
37
43
|
const basePath = appConfig.getBasePath();
|
|
38
44
|
|
|
39
45
|
// Log command start
|
|
40
46
|
logger.info(`Starting upload with ${uploadService.getServiceName()}`);
|
|
41
|
-
|
|
47
|
+
|
|
42
48
|
if (options.clearLog) {
|
|
43
49
|
logger.clearLogFile();
|
|
44
50
|
logger.info('Log file cleared');
|
|
@@ -67,7 +73,7 @@ export class UploadCommand {
|
|
|
67
73
|
uploadService,
|
|
68
74
|
basePath,
|
|
69
75
|
source,
|
|
70
|
-
sourcePath
|
|
76
|
+
sourcePath,
|
|
71
77
|
);
|
|
72
78
|
|
|
73
79
|
this.#updateGlobalResults(globalResults, result);
|
|
@@ -79,12 +85,11 @@ export class UploadCommand {
|
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
this.#logFinalSummary(globalResults, options, uploadService);
|
|
82
|
-
|
|
88
|
+
|
|
83
89
|
// Handle additional phases if requested
|
|
84
90
|
if (options.runAllPhases && options.statsOnly) {
|
|
85
91
|
await this.#runAdditionalPhases(options);
|
|
86
92
|
}
|
|
87
|
-
|
|
88
93
|
} catch (error) {
|
|
89
94
|
this.errorHandler.handleFatalError(error, { command: 'upload', options });
|
|
90
95
|
}
|
|
@@ -102,7 +107,10 @@ export class UploadCommand {
|
|
|
102
107
|
throw new ConfigurationError(error.message);
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
if (
|
|
110
|
+
if (
|
|
111
|
+
options.batchSize &&
|
|
112
|
+
(options.batchSize < 1 || options.batchSize > 100)
|
|
113
|
+
) {
|
|
106
114
|
throw new ConfigurationError('Batch size must be between 1 and 100');
|
|
107
115
|
}
|
|
108
116
|
}
|
|
@@ -116,18 +124,24 @@ export class UploadCommand {
|
|
|
116
124
|
async #discoverFiles(sourcePath) {
|
|
117
125
|
try {
|
|
118
126
|
if (!FileOperations.fileExists(sourcePath)) {
|
|
119
|
-
throw new FileOperationError(
|
|
127
|
+
throw new FileOperationError(
|
|
128
|
+
`Source path does not exist: ${sourcePath}`,
|
|
129
|
+
);
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
const stats = FileOperations.getFileStats(sourcePath);
|
|
123
|
-
|
|
133
|
+
|
|
124
134
|
if (stats?.isDirectory()) {
|
|
125
135
|
return await globby([`${sourcePath}/**/*`], { onlyFiles: true });
|
|
126
136
|
} else {
|
|
127
137
|
return [sourcePath];
|
|
128
138
|
}
|
|
129
139
|
} catch (error) {
|
|
130
|
-
throw new FileOperationError(
|
|
140
|
+
throw new FileOperationError(
|
|
141
|
+
`Failed to discover files in ${sourcePath}`,
|
|
142
|
+
sourcePath,
|
|
143
|
+
{ originalError: error.message },
|
|
144
|
+
);
|
|
131
145
|
}
|
|
132
146
|
}
|
|
133
147
|
|
|
@@ -142,7 +156,14 @@ export class UploadCommand {
|
|
|
142
156
|
* @param {string} sourcePath - Source path
|
|
143
157
|
* @returns {Promise<Object>} Processing results
|
|
144
158
|
*/
|
|
145
|
-
async #processFilesInBatches(
|
|
159
|
+
async #processFilesInBatches(
|
|
160
|
+
files,
|
|
161
|
+
options,
|
|
162
|
+
uploadService,
|
|
163
|
+
basePath,
|
|
164
|
+
source,
|
|
165
|
+
sourcePath,
|
|
166
|
+
) {
|
|
146
167
|
const batchSize = parseInt(options.batchSize) || 10;
|
|
147
168
|
const results = {
|
|
148
169
|
successCount: 0,
|
|
@@ -153,7 +174,9 @@ export class UploadCommand {
|
|
|
153
174
|
};
|
|
154
175
|
|
|
155
176
|
// Get processed paths if available
|
|
156
|
-
const processedPaths = options.skipProcessed
|
|
177
|
+
const processedPaths = options.skipProcessed
|
|
178
|
+
? databaseService.getProcessedPaths()
|
|
179
|
+
: new Set();
|
|
157
180
|
|
|
158
181
|
// Create progress bar
|
|
159
182
|
const progressBar = new cliProgress.SingleBar({
|
|
@@ -168,29 +191,34 @@ export class UploadCommand {
|
|
|
168
191
|
// Process files in batches
|
|
169
192
|
for (let i = 0; i < files.length; i += batchSize) {
|
|
170
193
|
const batch = files.slice(i, i + batchSize);
|
|
171
|
-
|
|
194
|
+
|
|
172
195
|
try {
|
|
173
196
|
const batchResult = await this.#processBatch(
|
|
174
|
-
batch,
|
|
175
|
-
options,
|
|
176
|
-
uploadService,
|
|
177
|
-
basePath,
|
|
178
|
-
processedPaths
|
|
197
|
+
batch,
|
|
198
|
+
options,
|
|
199
|
+
uploadService,
|
|
200
|
+
basePath,
|
|
201
|
+
processedPaths,
|
|
179
202
|
);
|
|
180
|
-
|
|
203
|
+
|
|
181
204
|
this.#updateResults(results, batchResult);
|
|
182
|
-
|
|
183
|
-
progressBar.update(Math.min(i + batchSize, files.length), {
|
|
184
|
-
success: results.successCount,
|
|
185
|
-
errors: results.failureCount
|
|
205
|
+
|
|
206
|
+
progressBar.update(Math.min(i + batchSize, files.length), {
|
|
207
|
+
success: results.successCount,
|
|
208
|
+
errors: results.failureCount,
|
|
186
209
|
});
|
|
187
210
|
|
|
188
211
|
// Delay between batches if configured
|
|
189
212
|
if (appConfig.performance.batchDelay > 0) {
|
|
190
|
-
await new Promise(resolve =>
|
|
213
|
+
await new Promise((resolve) =>
|
|
214
|
+
setTimeout(resolve, appConfig.performance.batchDelay),
|
|
215
|
+
);
|
|
191
216
|
}
|
|
192
217
|
} catch (error) {
|
|
193
|
-
this.errorHandler.handleError(error, {
|
|
218
|
+
this.errorHandler.handleError(error, {
|
|
219
|
+
batch: i / batchSize + 1,
|
|
220
|
+
batchSize,
|
|
221
|
+
});
|
|
194
222
|
results.failureCount += batch.length;
|
|
195
223
|
}
|
|
196
224
|
}
|
|
@@ -220,14 +248,17 @@ export class UploadCommand {
|
|
|
220
248
|
|
|
221
249
|
if (options.statsOnly) {
|
|
222
250
|
// Stats-only mode: just record file information
|
|
223
|
-
const fileObjects = batch.map(filePath => ({
|
|
251
|
+
const fileObjects = batch.map((filePath) => ({
|
|
224
252
|
path: filePath,
|
|
225
253
|
originalName: path.basename(filePath),
|
|
226
254
|
stats: FileOperations.getFileStats(filePath),
|
|
227
255
|
}));
|
|
228
256
|
|
|
229
257
|
try {
|
|
230
|
-
const result = await databaseService.insertStatsOnlyToUploaderTable(
|
|
258
|
+
const result = await databaseService.insertStatsOnlyToUploaderTable(
|
|
259
|
+
fileObjects,
|
|
260
|
+
options,
|
|
261
|
+
);
|
|
231
262
|
batchResults.successCount = result.totalInserted;
|
|
232
263
|
batchResults.skippedCount = result.totalSkipped;
|
|
233
264
|
} catch (error) {
|
|
@@ -237,7 +268,14 @@ export class UploadCommand {
|
|
|
237
268
|
// Upload mode: process files for upload
|
|
238
269
|
for (const filePath of batch) {
|
|
239
270
|
try {
|
|
240
|
-
await this.#processFile(
|
|
271
|
+
await this.#processFile(
|
|
272
|
+
filePath,
|
|
273
|
+
options,
|
|
274
|
+
uploadService,
|
|
275
|
+
basePath,
|
|
276
|
+
processedPaths,
|
|
277
|
+
batchResults,
|
|
278
|
+
);
|
|
241
279
|
} catch (error) {
|
|
242
280
|
this.errorHandler.handleError(error, { filePath });
|
|
243
281
|
batchResults.failureCount++;
|
|
@@ -252,7 +290,14 @@ export class UploadCommand {
|
|
|
252
290
|
* Process a single file
|
|
253
291
|
* @private
|
|
254
292
|
*/
|
|
255
|
-
async #processFile(
|
|
293
|
+
async #processFile(
|
|
294
|
+
filePath,
|
|
295
|
+
options,
|
|
296
|
+
uploadService,
|
|
297
|
+
basePath,
|
|
298
|
+
processedPaths,
|
|
299
|
+
batchResults,
|
|
300
|
+
) {
|
|
256
301
|
// Skip if already processed
|
|
257
302
|
if (processedPaths.has(filePath)) {
|
|
258
303
|
batchResults.skippedCount++;
|
|
@@ -260,9 +305,14 @@ export class UploadCommand {
|
|
|
260
305
|
}
|
|
261
306
|
|
|
262
307
|
// Prepare file for upload
|
|
263
|
-
const sanitizedName = fileSanitizer.sanitizeFileName(
|
|
264
|
-
|
|
265
|
-
|
|
308
|
+
const sanitizedName = fileSanitizer.sanitizeFileName(
|
|
309
|
+
path.basename(filePath),
|
|
310
|
+
);
|
|
311
|
+
const pathInfo = pathDetector.extractYearAndPedimentoFromPath(
|
|
312
|
+
filePath,
|
|
313
|
+
basePath,
|
|
314
|
+
);
|
|
315
|
+
|
|
266
316
|
let uploadPath = sanitizedName;
|
|
267
317
|
if (pathInfo.detected && options.autoDetectStructure) {
|
|
268
318
|
uploadPath = `${pathInfo.year}/${pathInfo.pedimento}/${sanitizedName}`;
|
|
@@ -280,10 +330,12 @@ export class UploadCommand {
|
|
|
280
330
|
...options,
|
|
281
331
|
uploadPath,
|
|
282
332
|
});
|
|
283
|
-
|
|
333
|
+
|
|
284
334
|
batchResults.successCount++;
|
|
285
|
-
if (result.detectedCount)
|
|
286
|
-
|
|
335
|
+
if (result.detectedCount)
|
|
336
|
+
batchResults.detectedCount += result.detectedCount;
|
|
337
|
+
if (result.organizedCount)
|
|
338
|
+
batchResults.organizedCount += result.organizedCount;
|
|
287
339
|
} else {
|
|
288
340
|
// Supabase direct upload
|
|
289
341
|
await uploadService.upload([fileObject], { uploadPath });
|
|
@@ -332,8 +384,10 @@ export class UploadCommand {
|
|
|
332
384
|
console.log(` ⏭️ Duplicates: ${result.skippedCount}`);
|
|
333
385
|
} else {
|
|
334
386
|
console.log(` ✅ Uploaded: ${result.successCount}`);
|
|
335
|
-
if (result.detectedCount)
|
|
336
|
-
|
|
387
|
+
if (result.detectedCount)
|
|
388
|
+
console.log(` 🔍 Detected: ${result.detectedCount}`);
|
|
389
|
+
if (result.organizedCount)
|
|
390
|
+
console.log(` 📁 Organized: ${result.organizedCount}`);
|
|
337
391
|
console.log(` ⏭️ Skipped: ${result.skippedCount}`);
|
|
338
392
|
}
|
|
339
393
|
console.log(` ❌ Errors: ${result.failureCount}`);
|
|
@@ -350,10 +404,14 @@ export class UploadCommand {
|
|
|
350
404
|
console.log(` 📊 Total stats recorded: ${results.successCount}`);
|
|
351
405
|
console.log(` ⏭️ Total duplicates: ${results.skippedCount}`);
|
|
352
406
|
} else {
|
|
353
|
-
console.log(
|
|
407
|
+
console.log(
|
|
408
|
+
`🎯 ${uploadService.getServiceName().toUpperCase()} UPLOAD COMPLETED`,
|
|
409
|
+
);
|
|
354
410
|
console.log(` ✅ Total uploaded: ${results.successCount}`);
|
|
355
|
-
if (results.detectedCount)
|
|
356
|
-
|
|
411
|
+
if (results.detectedCount)
|
|
412
|
+
console.log(` 🔍 Total detected: ${results.detectedCount}`);
|
|
413
|
+
if (results.organizedCount)
|
|
414
|
+
console.log(` 📁 Total organized: ${results.organizedCount}`);
|
|
357
415
|
console.log(` ⏭️ Total skipped: ${results.skippedCount}`);
|
|
358
416
|
}
|
|
359
417
|
console.log(` ❌ Total errors: ${results.failureCount}`);
|
|
@@ -385,4 +443,4 @@ export class UploadCommand {
|
|
|
385
443
|
}
|
|
386
444
|
}
|
|
387
445
|
|
|
388
|
-
export default UploadCommand;
|
|
446
|
+
export default UploadCommand;
|
package/src/config/config.js
CHANGED
|
@@ -28,10 +28,10 @@ class Config {
|
|
|
28
28
|
const __dirname = path.dirname(__filename);
|
|
29
29
|
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
30
30
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
31
|
-
return packageJson.version || '
|
|
31
|
+
return packageJson.version || '0.2.6';
|
|
32
32
|
} catch (error) {
|
|
33
33
|
console.warn('⚠️ Could not read package.json version, using fallback');
|
|
34
|
-
return '0.2.
|
|
34
|
+
return '0.2.6';
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -67,7 +67,7 @@ class Config {
|
|
|
67
67
|
const sources = process.env.UPLOAD_SOURCES?.split('|')
|
|
68
68
|
.map((s) => s.trim())
|
|
69
69
|
.filter(Boolean);
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
const uploadRfcs = process.env.UPLOAD_RFCS?.split('|')
|
|
72
72
|
.map((s) => s.trim())
|
|
73
73
|
.filter(Boolean);
|
|
@@ -86,7 +86,8 @@ class Config {
|
|
|
86
86
|
#loadPerformanceConfig() {
|
|
87
87
|
return {
|
|
88
88
|
batchDelay: parseInt(process.env.BATCH_DELAY) || 100,
|
|
89
|
-
progressUpdateInterval:
|
|
89
|
+
progressUpdateInterval:
|
|
90
|
+
parseInt(process.env.PROGRESS_UPDATE_INTERVAL) || 10,
|
|
90
91
|
logBufferSize: 100,
|
|
91
92
|
logFlushInterval: 5000,
|
|
92
93
|
};
|
|
@@ -128,7 +129,7 @@ class Config {
|
|
|
128
129
|
if (forceSupabase) {
|
|
129
130
|
if (!this.isSupabaseModeAvailable()) {
|
|
130
131
|
throw new Error(
|
|
131
|
-
'⚠️ Missing Supabase credentials. Please set SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET'
|
|
132
|
+
'⚠️ Missing Supabase credentials. Please set SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET',
|
|
132
133
|
);
|
|
133
134
|
}
|
|
134
135
|
return;
|
|
@@ -137,8 +138,8 @@ class Config {
|
|
|
137
138
|
if (!this.isApiModeAvailable() && !this.isSupabaseModeAvailable()) {
|
|
138
139
|
throw new Error(
|
|
139
140
|
'⚠️ Missing credentials. Please set either:\n' +
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
' - ARELA_API_URL and ARELA_API_TOKEN for API mode, or\n' +
|
|
142
|
+
' - SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET for direct mode',
|
|
142
143
|
);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
@@ -150,7 +151,9 @@ class Config {
|
|
|
150
151
|
*/
|
|
151
152
|
getUploadSources() {
|
|
152
153
|
if (!this.upload.sources || this.upload.sources.length === 0) {
|
|
153
|
-
throw new Error(
|
|
154
|
+
throw new Error(
|
|
155
|
+
'⚠️ No upload sources configured. Please set UPLOAD_SOURCES environment variable.',
|
|
156
|
+
);
|
|
154
157
|
}
|
|
155
158
|
return this.upload.sources;
|
|
156
159
|
}
|
|
@@ -162,7 +165,9 @@ class Config {
|
|
|
162
165
|
*/
|
|
163
166
|
getBasePath() {
|
|
164
167
|
if (!this.upload.basePath) {
|
|
165
|
-
throw new Error(
|
|
168
|
+
throw new Error(
|
|
169
|
+
'⚠️ No base path configured. Please set UPLOAD_BASE_PATH environment variable.',
|
|
170
|
+
);
|
|
166
171
|
}
|
|
167
172
|
return this.upload.basePath;
|
|
168
173
|
}
|
|
@@ -170,4 +175,4 @@ class Config {
|
|
|
170
175
|
|
|
171
176
|
// Export singleton instance
|
|
172
177
|
export const appConfig = new Config();
|
|
173
|
-
export default appConfig;
|
|
178
|
+
export default appConfig;
|
|
@@ -27,10 +27,10 @@ export class ErrorHandler {
|
|
|
27
27
|
*/
|
|
28
28
|
handleError(error, context = {}) {
|
|
29
29
|
const errorInfo = this.#analyzeError(error, context);
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// Log the error with appropriate level
|
|
32
32
|
this.#logError(errorInfo);
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
return errorInfo;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -41,14 +41,14 @@ export class ErrorHandler {
|
|
|
41
41
|
*/
|
|
42
42
|
handleFatalError(error, context = {}) {
|
|
43
43
|
const errorInfo = this.handleError(error, context);
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
console.error('\n❌ Fatal Error - Application will exit');
|
|
46
46
|
console.error(` ${errorInfo.userMessage}`);
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
if (this.logger.isVerboseEnabled() && errorInfo.technicalDetails) {
|
|
49
49
|
console.error(` Technical Details: ${errorInfo.technicalDetails}`);
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
this.logger.flush();
|
|
53
53
|
process.exit(1);
|
|
54
54
|
}
|
|
@@ -65,7 +65,7 @@ export class ErrorHandler {
|
|
|
65
65
|
originalError: originalError.message,
|
|
66
66
|
originalStack: originalError.stack,
|
|
67
67
|
});
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
return error;
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -103,45 +103,45 @@ export class ErrorHandler {
|
|
|
103
103
|
if (error instanceof ConfigurationError) {
|
|
104
104
|
return `Configuration issue: ${error.message}`;
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
if (error instanceof FileOperationError) {
|
|
108
108
|
const filePath = error.details?.filePath;
|
|
109
109
|
return `File operation failed${filePath ? ` for ${filePath}` : ''}: ${error.message}`;
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
if (error instanceof UploadServiceError) {
|
|
113
113
|
const serviceName = error.details?.serviceName;
|
|
114
114
|
return `Upload failed${serviceName ? ` (${serviceName})` : ''}: ${error.message}`;
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
if (error instanceof ApiError) {
|
|
118
118
|
const statusCode = error.details?.statusCode;
|
|
119
119
|
return `API request failed${statusCode ? ` (HTTP ${statusCode})` : ''}: ${error.message}`;
|
|
120
120
|
}
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
if (error instanceof DatabaseError) {
|
|
123
123
|
const operation = error.details?.operation;
|
|
124
124
|
return `Database operation failed${operation ? ` (${operation})` : ''}: ${error.message}`;
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
if (error instanceof DetectionError) {
|
|
128
128
|
const filePath = error.details?.filePath;
|
|
129
129
|
return `File detection failed${filePath ? ` for ${filePath}` : ''}: ${error.message}`;
|
|
130
130
|
}
|
|
131
|
-
|
|
131
|
+
|
|
132
132
|
if (error instanceof ValidationError) {
|
|
133
133
|
const field = error.details?.field;
|
|
134
134
|
return `Validation failed${field ? ` for ${field}` : ''}: ${error.message}`;
|
|
135
135
|
}
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
if (error instanceof NetworkError) {
|
|
138
138
|
return `Network error: ${error.message}`;
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
if (error instanceof ArelaError) {
|
|
142
142
|
return error.message;
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
// Generic error handling
|
|
146
146
|
return `An unexpected error occurred: ${error.message}`;
|
|
147
147
|
}
|
|
@@ -156,23 +156,26 @@ export class ErrorHandler {
|
|
|
156
156
|
if (error instanceof ConfigurationError) {
|
|
157
157
|
return 'fatal';
|
|
158
158
|
}
|
|
159
|
-
|
|
159
|
+
|
|
160
160
|
if (error instanceof NetworkError || error instanceof ApiError) {
|
|
161
161
|
return 'high';
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
|
|
164
164
|
if (error instanceof DatabaseError || error instanceof UploadServiceError) {
|
|
165
165
|
return 'high';
|
|
166
166
|
}
|
|
167
|
-
|
|
168
|
-
if (
|
|
167
|
+
|
|
168
|
+
if (
|
|
169
|
+
error instanceof FileOperationError ||
|
|
170
|
+
error instanceof DetectionError
|
|
171
|
+
) {
|
|
169
172
|
return 'medium';
|
|
170
173
|
}
|
|
171
|
-
|
|
174
|
+
|
|
172
175
|
if (error instanceof ValidationError) {
|
|
173
176
|
return 'low';
|
|
174
177
|
}
|
|
175
|
-
|
|
178
|
+
|
|
176
179
|
return 'medium';
|
|
177
180
|
}
|
|
178
181
|
|
|
@@ -186,21 +189,21 @@ export class ErrorHandler {
|
|
|
186
189
|
if (error instanceof ConfigurationError) {
|
|
187
190
|
return false;
|
|
188
191
|
}
|
|
189
|
-
|
|
192
|
+
|
|
190
193
|
if (error instanceof NetworkError || error instanceof ApiError) {
|
|
191
194
|
// Network errors might be temporary
|
|
192
195
|
return true;
|
|
193
196
|
}
|
|
194
|
-
|
|
197
|
+
|
|
195
198
|
if (error instanceof FileOperationError) {
|
|
196
199
|
// File errors might be recoverable depending on the specific error
|
|
197
200
|
return true;
|
|
198
201
|
}
|
|
199
|
-
|
|
202
|
+
|
|
200
203
|
if (error instanceof DetectionError || error instanceof ValidationError) {
|
|
201
204
|
return true;
|
|
202
205
|
}
|
|
203
|
-
|
|
206
|
+
|
|
204
207
|
return false;
|
|
205
208
|
}
|
|
206
209
|
|
|
@@ -211,11 +214,15 @@ export class ErrorHandler {
|
|
|
211
214
|
*/
|
|
212
215
|
#logError(errorInfo) {
|
|
213
216
|
const logMessage = `${errorInfo.type}: ${errorInfo.message}`;
|
|
214
|
-
const contextInfo = errorInfo.context
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
const contextInfo = errorInfo.context
|
|
218
|
+
? ` | Context: ${JSON.stringify(errorInfo.context)}`
|
|
219
|
+
: '';
|
|
220
|
+
const detailsInfo = errorInfo.technicalDetails
|
|
221
|
+
? ` | Details: ${JSON.stringify(errorInfo.technicalDetails)}`
|
|
222
|
+
: '';
|
|
223
|
+
|
|
217
224
|
const fullLogMessage = `${logMessage}${contextInfo}${detailsInfo}`;
|
|
218
|
-
|
|
225
|
+
|
|
219
226
|
switch (errorInfo.severity) {
|
|
220
227
|
case 'fatal':
|
|
221
228
|
this.logger.error(`FATAL: ${fullLogMessage}`);
|
|
@@ -232,7 +239,7 @@ export class ErrorHandler {
|
|
|
232
239
|
default:
|
|
233
240
|
this.logger.error(fullLogMessage);
|
|
234
241
|
}
|
|
235
|
-
|
|
242
|
+
|
|
236
243
|
// Log stack trace in verbose mode
|
|
237
244
|
if (this.logger.isVerboseEnabled() && errorInfo.stack) {
|
|
238
245
|
this.logger.debug(`Stack trace: ${errorInfo.stack}`);
|
|
@@ -268,4 +275,4 @@ export class ErrorHandler {
|
|
|
268
275
|
}
|
|
269
276
|
}
|
|
270
277
|
|
|
271
|
-
export default ErrorHandler;
|
|
278
|
+
export default ErrorHandler;
|
package/src/errors/ErrorTypes.js
CHANGED
|
@@ -12,7 +12,7 @@ export class ArelaError extends Error {
|
|
|
12
12
|
this.code = code;
|
|
13
13
|
this.details = details;
|
|
14
14
|
this.timestamp = new Date().toISOString();
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
// Ensure proper stack trace
|
|
17
17
|
if (Error.captureStackTrace) {
|
|
18
18
|
Error.captureStackTrace(this, this.constructor);
|
|
@@ -101,4 +101,4 @@ export class NetworkError extends ArelaError {
|
|
|
101
101
|
constructor(message, url = null, details = null) {
|
|
102
102
|
super(message, 'NETWORK_ERROR', { url, ...details });
|
|
103
103
|
}
|
|
104
|
-
}
|
|
104
|
+
}
|
package/src/index-old.js
CHANGED
|
@@ -158,8 +158,8 @@ const checkCredentials = async (forceSupabase = false) => {
|
|
|
158
158
|
if (!supabaseUrl || !supabaseKey || !bucket) {
|
|
159
159
|
console.error(
|
|
160
160
|
'⚠️ Missing credentials. Please set either:\n' +
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
' - ARELA_API_URL and ARELA_API_TOKEN for API mode, or\n' +
|
|
162
|
+
' - SUPABASE_URL, SUPABASE_KEY, and SUPABASE_BUCKET for direct mode',
|
|
163
163
|
);
|
|
164
164
|
process.exit(1);
|
|
165
165
|
}
|
|
@@ -709,20 +709,29 @@ const insertStatsOnlyToUploaderTable = async (files, options) => {
|
|
|
709
709
|
|
|
710
710
|
try {
|
|
711
711
|
// First, check which records already exist
|
|
712
|
-
const originalPaths = batch.map(r => r.original_path);
|
|
712
|
+
const originalPaths = batch.map((r) => r.original_path);
|
|
713
713
|
const { data: existingRecords, error: checkError } = await supabase
|
|
714
714
|
.from('uploader')
|
|
715
715
|
.select('original_path')
|
|
716
716
|
.in('original_path', originalPaths);
|
|
717
717
|
|
|
718
718
|
if (checkError) {
|
|
719
|
-
console.error(
|
|
719
|
+
console.error(
|
|
720
|
+
`❌ Error checking existing records:`,
|
|
721
|
+
checkError.message,
|
|
722
|
+
);
|
|
720
723
|
continue;
|
|
721
724
|
}
|
|
722
725
|
|
|
723
|
-
const existingPaths = new Set(
|
|
724
|
-
|
|
725
|
-
|
|
726
|
+
const existingPaths = new Set(
|
|
727
|
+
existingRecords?.map((r) => r.original_path) || [],
|
|
728
|
+
);
|
|
729
|
+
const newRecords = batch.filter(
|
|
730
|
+
(r) => !existingPaths.has(r.original_path),
|
|
731
|
+
);
|
|
732
|
+
const updateRecords = batch.filter((r) =>
|
|
733
|
+
existingPaths.has(r.original_path),
|
|
734
|
+
);
|
|
726
735
|
|
|
727
736
|
console.log(
|
|
728
737
|
`📦 Batch ${Math.floor(i / batchSize) + 1}: ${newRecords.length} new, ${updateRecords.length} updates`,
|
|
@@ -763,7 +772,6 @@ const insertStatsOnlyToUploaderTable = async (files, options) => {
|
|
|
763
772
|
totalUpdated += batchUpdated;
|
|
764
773
|
console.log(`🔄 Updated ${batchUpdated} existing records`);
|
|
765
774
|
}
|
|
766
|
-
|
|
767
775
|
} catch (error) {
|
|
768
776
|
console.error(
|
|
769
777
|
`❌ Unexpected error in batch ${Math.floor(i / batchSize) + 1}:`,
|
|
@@ -1785,8 +1793,8 @@ const uploadFilesByRfc = async (options = {}) => {
|
|
|
1785
1793
|
// Set folder structure for this group - concatenate custom prefix with arela_path
|
|
1786
1794
|
const folderStructure = options.folderStructure
|
|
1787
1795
|
? `${options.folderStructure}/${arelaPath}`
|
|
1788
|
-
|
|
1789
|
-
|
|
1796
|
+
.replace(/\/+/g, '/')
|
|
1797
|
+
.replace(/\/$/, '')
|
|
1790
1798
|
: arelaPath;
|
|
1791
1799
|
pathFormData.append('folderStructure', folderStructure);
|
|
1792
1800
|
pathFormData.append('autoDetect', 'true');
|
|
@@ -2300,13 +2308,13 @@ program
|
|
|
2300
2308
|
.name('arela-uploader')
|
|
2301
2309
|
.description(
|
|
2302
2310
|
'CLI to upload folders to Arela API or Supabase Storage with automatic processing\n\n' +
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2311
|
+
'Status workflow:\n' +
|
|
2312
|
+
' fs-stats → detected → file-uploaded\n' +
|
|
2313
|
+
' ├─ Phase 1: --stats-only (collects filesystem stats, status: fs-stats)\n' +
|
|
2314
|
+
' ├─ Phase 2: --detect-pdfs (detects document types, status: detected)\n' +
|
|
2315
|
+
' ├─ Phase 3: --propagate-arela-path (organizes files by pedimento)\n' +
|
|
2316
|
+
' └─ Phase 4: --upload-by-rfc (uploads files, status: file-uploaded)\n\n' +
|
|
2317
|
+
'Use --query-ready-files to see files ready for upload (status: detected with arela_path)',
|
|
2310
2318
|
)
|
|
2311
2319
|
.option('-v, --version', 'output the version number')
|
|
2312
2320
|
.option('-p, --prefix <prefix>', 'Prefix path in bucket', '')
|