@arela/uploader 1.0.7 → 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.
package/.env.template CHANGED
@@ -9,6 +9,15 @@
9
9
  ARELA_API_URL=https://your-arela-api-url.com
10
10
  ARELA_API_TOKEN=your-api-token-here
11
11
 
12
+ # API Agencia - Configura aquí la URL y Token de la agencia activa
13
+ ARELA_API_AGENCIA_URL=https://agencia-api-example.com
14
+ ARELA_API_AGENCIA_TOKEN=your-agencia-api-token-here
15
+
16
+ # API Cliente - Configura aquí la URL y Token del cliente activo
17
+ ARELA_API_CLIENTE_URL=https://cliente-api-example.com
18
+ ARELA_API_CLIENTE_TOKEN=your-cliente-api-token-here
19
+
20
+
12
21
  # Supabase Configuration (fallback)
13
22
  SUPABASE_URL=https://your-supabase-url.supabase.co
14
23
  SUPABASE_KEY=your-supabase-key-here
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arela/uploader",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "CLI to upload files/directories to Arela",
5
5
  "bin": {
6
6
  "arela": "./src/index.js"
@@ -42,23 +42,20 @@ export class IdentifyCommand {
42
42
  // Validate scan configuration (need same config as scan command)
43
43
  appConfig.validateScanConfig();
44
44
 
45
- // Import ScanApiService dynamically
45
+ // Determine API target
46
+ const apiTarget = options.api || 'default';
47
+
48
+ // Import ScanApiService dynamically and initialize with target
46
49
  const { default: ScanApiService } = await import(
47
50
  '../services/ScanApiService.js'
48
51
  );
49
- this.scanApiService = new ScanApiService();
50
-
51
- // Set API target if specified
52
- if (options.api) {
53
- appConfig.setApiTarget(options.api);
54
- this.scanApiService = new ScanApiService();
55
- }
52
+ this.scanApiService = new ScanApiService(apiTarget);
56
53
 
57
54
  const scanConfig = appConfig.getScanConfig();
58
55
  const batchSize = parseInt(options.batchSize) || 100;
59
56
 
60
57
  logger.info('🔍 Starting arela identify command');
61
- logger.info(`🎯 API Target: ${options.api || 'default'}`);
58
+ logger.info(`🎯 API Target: ${apiTarget}`);
62
59
  logger.info(`📦 Batch Size: ${batchSize}`);
63
60
 
64
61
  // Fetch all tables for this instance
@@ -273,7 +270,7 @@ export class IdentifyCommand {
273
270
  arelaPath: null,
274
271
  detectionError:
275
272
  'FILE_NOT_FOUND: File does not exist on filesystem. May have been moved or deleted after scan.',
276
- isPedimento: null, // Unknown - can't determine
273
+ isPedimento: null, // Unknown - can't determine
277
274
  };
278
275
  }
279
276
 
@@ -289,7 +286,7 @@ export class IdentifyCommand {
289
286
  rfc: null,
290
287
  arelaPath: null,
291
288
  detectionError: `FILE_TOO_LARGE: File size ${(stats.size / 1024 / 1024).toFixed(2)}MB exceeds ${maxSizeBytes / 1024 / 1024}MB limit.`,
292
- isPedimento: null, // Unknown - can't determine
289
+ isPedimento: null, // Unknown - can't determine
293
290
  };
294
291
  }
295
292
 
@@ -306,13 +303,16 @@ export class IdentifyCommand {
306
303
  rfc: result.rfc,
307
304
  arelaPath: result.arelaPath,
308
305
  detectionError: result.error,
309
- isPedimento: true, // Confirmed pedimento
306
+ isPedimento: true, // Confirmed pedimento
310
307
  };
311
308
  }
312
309
 
313
310
  // If no detection, determine if it's definitely not a pedimento
314
311
  // This helps avoid re-processing files we know aren't pedimentos
315
- const isDefinitelyNotPedimento = this.#isDefinitelyNotPedimento(result, file);
312
+ const isDefinitelyNotPedimento = this.#isDefinitelyNotPedimento(
313
+ result,
314
+ file,
315
+ );
316
316
 
317
317
  // Build descriptive error message
318
318
  let detectionError = null;
@@ -340,7 +340,7 @@ export class IdentifyCommand {
340
340
  rfc: result.rfc,
341
341
  arelaPath: result.arelaPath,
342
342
  detectionError,
343
- isPedimento: isDefinitelyNotPedimento ? false : null, // false = not pedimento, null = unknown
343
+ isPedimento: isDefinitelyNotPedimento ? false : null, // false = not pedimento, null = unknown
344
344
  };
345
345
  } catch (error) {
346
346
  logger.warn(
@@ -367,7 +367,7 @@ export class IdentifyCommand {
367
367
  rfc: null,
368
368
  arelaPath: null,
369
369
  detectionError: `${errorCategory}: ${error.message}`,
370
- isPedimento: null, // Unknown - error occurred
370
+ isPedimento: null, // Unknown - error occurred
371
371
  };
372
372
  }
373
373
  }),
@@ -39,8 +39,10 @@ export class PropagateCommand {
39
39
  // Step 1: Validate configuration
40
40
  await this.#validateConfiguration();
41
41
 
42
- // Step 2: Initialize API service
43
- this.scanApiService = new ScanApiService();
42
+ // Step 2: Initialize API service with configured target
43
+ const apiTarget = this.options.api || 'default';
44
+ this.scanApiService = new ScanApiService(apiTarget);
45
+ console.log(`🎯 API Target: ${apiTarget}`);
44
46
 
45
47
  // Step 3: Fetch all tables for this instance
46
48
  const scanConfig = appConfig.getScanConfig();
@@ -15,7 +15,8 @@ import appConfig from '../config/config.js';
15
15
  */
16
16
  export class PushCommand {
17
17
  constructor() {
18
- this.scanApiService = new ScanApiService();
18
+ // ScanApiService will be initialized in execute() with proper API target
19
+ this.scanApiService = null;
19
20
  }
20
21
 
21
22
  /**
@@ -37,7 +38,6 @@ export class PushCommand {
37
38
  // Get configuration
38
39
  const scanConfig = appConfig.getScanConfig();
39
40
  const pushConfig = appConfig.getPushConfig();
40
- const tableName = scanConfig.tableName;
41
41
 
42
42
  // Override folderStructure from command option if provided
43
43
  if (options.folderStructure) {
@@ -48,26 +48,40 @@ export class PushCommand {
48
48
  .replace(/\/+$/, '');
49
49
  }
50
50
 
51
- // Set API target for scan/push operations
52
- const scanApiTarget = options.api || options.scanApi || 'default';
53
- const pushApiTarget = options.pushApi || scanApiTarget;
51
+ // Determine API targets for scan (read) and push (upload) operations
52
+ // Priority: explicit scan-api/push-api > source-api/target-api > api > default
53
+ const scanApiTarget =
54
+ options.scanApi || options.sourceApi || options.api || 'default';
55
+ const pushApiTarget =
56
+ options.pushApi || options.targetApi || options.api || scanApiTarget;
54
57
 
55
- if (scanApiTarget !== 'default') {
56
- appConfig.setApiTarget(scanApiTarget);
57
- this.scanApiService = new ScanApiService(); // Reinitialize with new target
58
- }
58
+ // Initialize ScanApiService with the scan API target
59
+ this.scanApiService = new ScanApiService(scanApiTarget);
59
60
 
60
- // Get upload API configuration
61
+ // Get upload API configuration for the push target
61
62
  const uploadApiConfig = appConfig.getApiConfig(pushApiTarget);
62
63
 
63
- console.log(`🎯 Scan API Target: ${scanApiTarget}`);
64
- console.log(
65
- `🎯 Upload API Target: ${pushApiTarget} → ${uploadApiConfig.baseUrl}`,
66
- );
64
+ // Display API configuration
65
+ const isCrossTenant = scanApiTarget !== pushApiTarget;
66
+ if (isCrossTenant) {
67
+ console.log('🔗 Cross-tenant mode enabled:');
68
+ console.log(
69
+ ` 📖 Scan API (read): ${scanApiTarget} → ${appConfig.getApiConfig(scanApiTarget).baseUrl}`,
70
+ );
71
+ console.log(
72
+ ` 📝 Push API (upload): ${pushApiTarget} → ${uploadApiConfig.baseUrl}`,
73
+ );
74
+ } else {
75
+ console.log(
76
+ `🎯 API Target: ${scanApiTarget} → ${uploadApiConfig.baseUrl}`,
77
+ );
78
+ }
67
79
  console.log(`📦 Fetch Batch Size: ${options.batchSize}`);
68
80
  console.log(`📤 Upload Batch Size: ${options.uploadBatchSize}`);
69
81
  if (pushConfig.folderStructure) {
70
- console.log(`📁 Folder Structure Prefix: ${pushConfig.folderStructure}`);
82
+ console.log(
83
+ `📁 Folder Structure Prefix: ${pushConfig.folderStructure}`,
84
+ );
71
85
  }
72
86
 
73
87
  // Apply filters
@@ -294,7 +308,6 @@ export class PushCommand {
294
308
  );
295
309
 
296
310
  // Update counters from API response
297
- // Note: The CLI endpoint now handles updating the scan table directly
298
311
  batchResults.forEach((result) => {
299
312
  results.processed++;
300
313
  if (result.uploaded) {
@@ -304,6 +317,17 @@ export class PushCommand {
304
317
  }
305
318
  });
306
319
 
320
+ // Update scan table via scanApi (supports cross-tenant mode)
321
+ // This is called on scanApi, not pushApi, because scan tables live on scanApi
322
+ try {
323
+ await this.scanApiService.batchUpdateUpload(tableName, batchResults);
324
+ } catch (updateError) {
325
+ logger.error(
326
+ `Failed to update scan table for batch: ${updateError.message}`,
327
+ );
328
+ // Don't fail the entire process, just log the error
329
+ }
330
+
307
331
  // Update progress bar
308
332
  const elapsed = (Date.now() - results.startTime) / 1000;
309
333
  const speed =
@@ -329,7 +353,7 @@ export class PushCommand {
329
353
 
330
354
  /**
331
355
  * Upload a batch of files using the new CLI upload endpoint
332
- * The endpoint updates the CLI scan table directly
356
+ * Note: Scan table is updated separately via scanApi after this batch completes
333
357
  * @private
334
358
  */
335
359
  async #uploadBatchViaCli(tableName, files, uploadApiConfig) {
@@ -368,8 +392,6 @@ export class PushCommand {
368
392
  if (!fs.existsSync(file.absolute_path)) {
369
393
  result.uploadError =
370
394
  'FILE_NOT_FOUND: File does not exist on filesystem';
371
- // Update the scan table with the error
372
- await this.scanApiService.batchUpdateUpload(tableName, [result]);
373
395
  return result;
374
396
  }
375
397
 
@@ -377,7 +399,6 @@ export class PushCommand {
377
399
  const stats = fs.statSync(file.absolute_path);
378
400
  if (!stats.isFile()) {
379
401
  result.uploadError = 'NOT_A_FILE: Path is not a regular file';
380
- await this.scanApiService.batchUpdateUpload(tableName, [result]);
381
402
  return result;
382
403
  }
383
404
 
@@ -26,6 +26,7 @@ export class ScanCommand {
26
26
  * Execute the scan command
27
27
  * @param {Object} options - Command options
28
28
  * @param {boolean} options.countFirst - Count files first for percentage-based progress
29
+ * @param {string} options.api - API target: 'default', 'agencia', or 'cliente'
29
30
  */
30
31
  async execute(options = {}) {
31
32
  const startTime = Date.now();
@@ -34,17 +35,21 @@ export class ScanCommand {
34
35
  // Validate scan configuration
35
36
  appConfig.validateScanConfig();
36
37
 
37
- // Import ScanApiService dynamically
38
+ // Determine API target
39
+ const apiTarget = options.api || 'default';
40
+
41
+ // Import ScanApiService dynamically and initialize with target
38
42
  const { default: ScanApiService } = await import(
39
43
  '../services/ScanApiService.js'
40
44
  );
41
- this.scanApiService = new ScanApiService();
45
+ this.scanApiService = new ScanApiService(apiTarget);
42
46
 
43
47
  const scanConfig = appConfig.getScanConfig();
44
48
  // Ensure basePath is absolute for scan operations
45
49
  const basePath = PathNormalizer.toAbsolutePath(appConfig.getBasePath());
46
50
 
47
51
  logger.info('🔍 Starting arela scan command');
52
+ logger.info(`🎯 API Target: ${apiTarget}`);
48
53
  logger.info(`📦 Company: ${scanConfig.companySlug}`);
49
54
  logger.info(`🖥️ Server: ${scanConfig.serverId}`);
50
55
  logger.info(`📂 Base Path: ${basePath}`);
@@ -34,10 +34,10 @@ class Config {
34
34
  const __dirname = path.dirname(__filename);
35
35
  const packageJsonPath = path.resolve(__dirname, '../../package.json');
36
36
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
37
- return packageJson.version || '1.0.7';
37
+ return packageJson.version || '1.0.8';
38
38
  } catch (error) {
39
39
  console.warn('⚠️ Could not read package.json version, using fallback');
40
- return '1.0.7';
40
+ return '1.0.8';
41
41
  }
42
42
  }
43
43
 
package/src/index.js CHANGED
@@ -381,6 +381,14 @@ class ArelaUploaderCLI {
381
381
  '--push-api <target>',
382
382
  'API for uploading files: default|agencia|cliente',
383
383
  )
384
+ .option(
385
+ '--source-api <target>',
386
+ 'Source API for reading data (cross-tenant mode): agencia|cliente',
387
+ )
388
+ .option(
389
+ '--target-api <target>',
390
+ 'Target API for uploading files (cross-tenant mode): agencia|cliente',
391
+ )
384
392
  .option(
385
393
  '-b, --batch-size <size>',
386
394
  'Number of files to fetch per batch',
@@ -406,6 +414,18 @@ class ArelaUploaderCLI {
406
414
  .option('--show-stats', 'Show performance statistics')
407
415
  .action(async (options) => {
408
416
  try {
417
+ // Handle cross-tenant mode (source and target APIs)
418
+ // Map source-api/target-api to scan-api/push-api for consistency
419
+ if (options.sourceApi && options.targetApi) {
420
+ appConfig.setCrossTenantTargets(
421
+ options.sourceApi,
422
+ options.targetApi,
423
+ );
424
+ // Also set scan-api and push-api for PushCommand compatibility
425
+ options.scanApi = options.sourceApi;
426
+ options.pushApi = options.targetApi;
427
+ }
428
+
409
429
  // Parse comma-separated values
410
430
  if (options.rfcs) {
411
431
  options.rfcs = options.rfcs
@@ -10,8 +10,12 @@ import logger from './LoggingService.js';
10
10
  * Handles API communication for the arela scan command
11
11
  */
12
12
  export class ScanApiService {
13
- constructor() {
14
- const apiConfig = appConfig.getApiConfig();
13
+ /**
14
+ * @param {string|null} apiTarget - API target: 'default', 'agencia', 'cliente', or null (uses active target)
15
+ */
16
+ constructor(apiTarget = null) {
17
+ this.apiTarget = apiTarget;
18
+ const apiConfig = appConfig.getApiConfig(apiTarget);
15
19
  this.baseUrl = apiConfig.baseUrl;
16
20
  this.token = apiConfig.token;
17
21