@beltar/n8n-nodes-extract-archive 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,7 @@ const fs = __importStar(require("fs"));
40
40
  const crypto = __importStar(require("crypto"));
41
41
  const path = __importStar(require("path"));
42
42
  const util_1 = require("util");
43
+ const promises_1 = require("stream/promises");
43
44
  const execAsync = (0, util_1.promisify)(child_process_1.exec);
44
45
  class ExtractArchive {
45
46
  constructor() {
@@ -143,6 +144,56 @@ class ExtractArchive {
143
144
  default: 'file',
144
145
  required: true,
145
146
  description: 'Name of the binary property for output files. For single mode, files are named file_1, file_2, etc.',
147
+ displayOptions: {
148
+ show: {
149
+ saveToDisk: [false],
150
+ },
151
+ },
152
+ },
153
+ {
154
+ displayName: 'Save to Disk',
155
+ name: 'saveToDisk',
156
+ type: 'boolean',
157
+ default: false,
158
+ description: 'Whether to save extracted files to a directory on disk instead of returning binary data. Only metadata (path, size, name) is returned.',
159
+ },
160
+ {
161
+ displayName: 'Output Directory',
162
+ name: 'outputDirectory',
163
+ type: 'string',
164
+ default: '',
165
+ required: true,
166
+ placeholder: '/data/extracted',
167
+ description: 'Directory where extracted files will be saved',
168
+ displayOptions: {
169
+ show: {
170
+ saveToDisk: [true],
171
+ },
172
+ },
173
+ },
174
+ {
175
+ displayName: 'Create Subdirectory from Archive Name',
176
+ name: 'createSubdirectory',
177
+ type: 'boolean',
178
+ default: true,
179
+ description: 'Whether to create a subdirectory named after the archive file',
180
+ displayOptions: {
181
+ show: {
182
+ saveToDisk: [true],
183
+ },
184
+ },
185
+ },
186
+ {
187
+ displayName: 'Keep Folder Structure',
188
+ name: 'keepFolderStructure',
189
+ type: 'boolean',
190
+ default: true,
191
+ description: 'Whether to preserve the folder structure from the archive. If false, all files are placed flat in the output directory.',
192
+ displayOptions: {
193
+ show: {
194
+ saveToDisk: [true],
195
+ },
196
+ },
146
197
  },
147
198
  {
148
199
  displayName: 'Include Subdirectories',
@@ -319,7 +370,10 @@ class ExtractArchive {
319
370
  const archiveTypeParam = this.getNodeParameter('archiveType', itemIndex);
320
371
  const password = this.getNodeParameter('password', itemIndex);
321
372
  const outputMode = this.getNodeParameter('outputMode', itemIndex);
322
- const outputBinaryPropertyName = this.getNodeParameter('outputBinaryPropertyName', itemIndex);
373
+ const saveToDisk = this.getNodeParameter('saveToDisk', itemIndex);
374
+ const outputBinaryPropertyName = !saveToDisk
375
+ ? this.getNodeParameter('outputBinaryPropertyName', itemIndex)
376
+ : '';
323
377
  const includeSubdirectories = this.getNodeParameter('includeSubdirectories', itemIndex);
324
378
  const fileFilter = this.getNodeParameter('fileFilter', itemIndex);
325
379
  const uniqueId = crypto.randomUUID();
@@ -374,6 +428,29 @@ class ExtractArchive {
374
428
  if (files.length === 0) {
375
429
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No files were extracted. The archive might be empty or all files were filtered out.', { itemIndex });
376
430
  }
431
+ // Save to disk: copy files to target directory
432
+ let targetDir = '';
433
+ if (saveToDisk) {
434
+ const outputDirectory = this.getNodeParameter('outputDirectory', itemIndex);
435
+ const createSubdirectory = this.getNodeParameter('createSubdirectory', itemIndex);
436
+ const keepFolderStructure = this.getNodeParameter('keepFolderStructure', itemIndex);
437
+ targetDir = outputDirectory;
438
+ if (createSubdirectory) {
439
+ const archiveName = path.basename(archivePath, path.extname(archivePath));
440
+ targetDir = path.join(outputDirectory, archiveName);
441
+ }
442
+ fs.mkdirSync(targetDir, { recursive: true });
443
+ for (const relPath of files) {
444
+ const srcPath = path.join(extractDir, relPath);
445
+ const fileName = path.basename(relPath);
446
+ const destPath = keepFolderStructure
447
+ ? path.join(targetDir, relPath)
448
+ : path.join(targetDir, fileName);
449
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
450
+ // Use streams instead of copyFileSync to avoid EPERM on network shares (CIFS/SMB)
451
+ await (0, promises_1.pipeline)(fs.createReadStream(srcPath), fs.createWriteStream(destPath));
452
+ }
453
+ }
377
454
  if (outputMode === 'single') {
378
455
  const binary = {};
379
456
  const fileInfos = [];
@@ -382,45 +459,74 @@ class ExtractArchive {
382
459
  const fullPath = path.join(extractDir, relPath);
383
460
  const fileBuffer = fs.readFileSync(fullPath);
384
461
  const fileName = path.basename(relPath);
385
- const propertyName = files.length === 1
386
- ? outputBinaryPropertyName
387
- : `${outputBinaryPropertyName}_${i + 1}`;
388
- binary[propertyName] = await this.helpers.prepareBinaryData(fileBuffer, fileName, extractor.getMimeType(fileName));
389
- fileInfos.push({
462
+ if (!saveToDisk) {
463
+ const propertyName = files.length === 1
464
+ ? outputBinaryPropertyName
465
+ : `${outputBinaryPropertyName}_${i + 1}`;
466
+ binary[propertyName] = await this.helpers.prepareBinaryData(fileBuffer, fileName, extractor.getMimeType(fileName));
467
+ }
468
+ const fileInfo = {
390
469
  name: fileName,
391
470
  path: relPath,
392
471
  size: fileBuffer.length,
393
- });
472
+ };
473
+ if (saveToDisk) {
474
+ const keepFolderStructure = this.getNodeParameter('keepFolderStructure', itemIndex);
475
+ fileInfo.diskPath = keepFolderStructure
476
+ ? path.join(targetDir, relPath)
477
+ : path.join(targetDir, fileName);
478
+ }
479
+ fileInfos.push(fileInfo);
480
+ }
481
+ const json = {
482
+ totalFiles: files.length,
483
+ archiveType,
484
+ files: fileInfos,
485
+ };
486
+ if (!saveToDisk) {
487
+ json.binaryProperties = files.map((_, i) => files.length === 1 ? outputBinaryPropertyName : `${outputBinaryPropertyName}_${i + 1}`);
488
+ }
489
+ if (saveToDisk) {
490
+ json.outputDirectory = targetDir;
394
491
  }
395
492
  returnData.push({
396
- json: {
397
- totalFiles: files.length,
398
- archiveType,
399
- files: fileInfos,
400
- binaryProperties: files.map((_, i) => files.length === 1 ? outputBinaryPropertyName : `${outputBinaryPropertyName}_${i + 1}`),
401
- },
402
- binary,
493
+ json,
494
+ ...(Object.keys(binary).length > 0 ? { binary } : {}),
403
495
  });
404
496
  }
405
497
  else {
498
+ // multiple mode
406
499
  for (let i = 0; i < files.length; i++) {
407
500
  const relPath = files[i];
408
501
  const fullPath = path.join(extractDir, relPath);
409
- const fileBuffer = fs.readFileSync(fullPath);
410
502
  const fileName = path.basename(relPath);
411
- returnData.push({
412
- json: {
413
- index: i + 1,
414
- totalFiles: files.length,
415
- fileName,
416
- filePath: relPath,
417
- fileSize: fileBuffer.length,
418
- archiveType,
419
- },
420
- binary: {
421
- [outputBinaryPropertyName]: await this.helpers.prepareBinaryData(fileBuffer, fileName, extractor.getMimeType(fileName)),
422
- },
423
- });
503
+ const fileSize = fs.statSync(fullPath).size;
504
+ const json = {
505
+ index: i + 1,
506
+ totalFiles: files.length,
507
+ fileName,
508
+ relativePath: relPath,
509
+ fileSize,
510
+ archiveType,
511
+ };
512
+ if (saveToDisk) {
513
+ const keepFolderStructure = this.getNodeParameter('keepFolderStructure', itemIndex);
514
+ json.filePath = keepFolderStructure
515
+ ? path.join(targetDir, relPath)
516
+ : path.join(targetDir, fileName);
517
+ }
518
+ if (saveToDisk) {
519
+ returnData.push({ json });
520
+ }
521
+ else {
522
+ const fileBuffer = fs.readFileSync(fullPath);
523
+ returnData.push({
524
+ json,
525
+ binary: {
526
+ [outputBinaryPropertyName]: await this.helpers.prepareBinaryData(fileBuffer, fileName, extractor.getMimeType(fileName)),
527
+ },
528
+ });
529
+ }
424
530
  }
425
531
  }
426
532
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beltar/n8n-nodes-extract-archive",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "n8n node to extract ZIP and RAR archive files. Supports binary data or file path input.",
5
5
  "keywords": [
6
6
  "n8n",