diff --git a/packages/nexrender-action-decompress/index.js b/packages/nexrender-action-decompress/index.js index d2b6f8ab..46ac85dd 100644 --- a/packages/nexrender-action-decompress/index.js +++ b/packages/nexrender-action-decompress/index.js @@ -13,7 +13,25 @@ const decompress = (job, settings, asset, action) => { switch (action.format) { case 'zip': const zip = new AdmZip(asset.dest); - zip.extractAllTo(job.workpath, action.overwrite || false); + const entries = zip.getEntries(); + + // Check if zip has a single root folder + const rootFolders = new Set(entries.map(entry => entry.entryName.split('/')[0])); + const hasSingleRootFolder = rootFolders.size === 1 && entries.every(entry => entry.entryName.startsWith([...rootFolders][0] + '/')); + + if (hasSingleRootFolder) { + // Extract all files, removing the root folder from paths + const rootFolder = [...rootFolders][0]; + entries.forEach(entry => { + if (!entry.isDirectory) { + const relativePath = entry.entryName.substring(rootFolder.length + 1); + zip.extractEntryTo(entry.entryName, job.workpath, false, true, false, relativePath); + } + }); + } else { + // Default behavior - extract all files + zip.extractAllTo(job.workpath, action.overwrite || false); + } break; } @@ -23,7 +41,6 @@ const decompress = (job, settings, asset, action) => { } return Promise.resolve(); - } module.exports = (job, settings, action, type) => { diff --git a/packages/nexrender-action-decompress/test.js b/packages/nexrender-action-decompress/test.js index 50e0c43e..ea77ada5 100644 --- a/packages/nexrender-action-decompress/test.js +++ b/packages/nexrender-action-decompress/test.js @@ -27,6 +27,11 @@ describe('actions/decompress', function() { zip2.writeZip(path.join(workpath, 'asset.zip')); fs.writeFileSync(path.join(workpath, 'non-archive.jpg'), 'hello there 4'); + + // create "asset with root folder" zip file + const zip3 = new AdmZip(); + zip3.addFile("root-folder/nested.jpg", Buffer.from("hello there 5")); + zip3.writeZip(path.join(workpath, 'asset-with-root.zip')); }); // Cleanup after tests @@ -39,6 +44,8 @@ describe('actions/decompress', function() { fs.unlinkSync(path.join(workpath, 'asset.jpg')); fs.rmdirSync(path.join(workpath, '(Footage)')); fs.unlinkSync(path.join(workpath, 'non-archive.jpg')); + fs.unlinkSync(path.join(workpath, 'asset-with-root.zip')); + fs.unlinkSync(path.join(workpath, 'nested.jpg')); // Remove test workpath directory fs.rmdirSync(workpath); @@ -110,4 +117,65 @@ describe('actions/decompress', function() { }) .catch(done); }); + + it('should handle zip files with a single root folder', function(done) { + const job = { + workpath: workpath, + template: { + dest: path.join(workpath, 'asset-with-root.zip'), + decompressed: 'nested.jpg', + }, + assets: [] + }; + + decompressAction(job, {}, { format: 'zip' }, 'prerender') + .then(() => { + // ensure file is extracted from nested folder + assert(fs.existsSync(path.join(workpath, 'nested.jpg'))); + assert.strictEqual(fs.readFileSync(path.join(workpath, 'nested.jpg'), 'utf8'), 'hello there 5'); + assert.strictEqual(job.template.dest, path.join(workpath, 'nested.jpg')); + done(); + }) + .catch(done); + }); + + it('should automatically flatten zip files with a single root folder', function(done) { + // Create a zip with a single root folder structure + const zipWithRoot = new AdmZip(); + zipWithRoot.addFile("root-folder/template2.aep", Buffer.from("nested template")); + zipWithRoot.addFile("root-folder/assets/image.jpg", Buffer.from("nested image")); + zipWithRoot.writeZip(path.join(workpath, 'nested-structure.zip')); + + const job = { + workpath: workpath, + template: { + dest: path.join(workpath, 'nested-structure.zip'), + decompressed: 'template2.aep', + }, + assets: [] + }; + + decompressAction(job, {}, { format: 'zip' }, 'prerender') + .then(() => { + // Files should be extracted without the root-folder prefix + assert(fs.existsSync(path.join(workpath, 'template2.aep'))); + assert(fs.existsSync(path.join(workpath, 'assets', 'image.jpg'))); + + // Verify content + assert.strictEqual(fs.readFileSync(path.join(workpath, 'template2.aep'), 'utf8'), 'nested template'); + assert.strictEqual(fs.readFileSync(path.join(workpath, 'assets', 'image.jpg'), 'utf8'), 'nested image'); + + // Verify the root folder itself is not created + assert(!fs.existsSync(path.join(workpath, 'root-folder'))); + + // Cleanup the additional test files + fs.unlinkSync(path.join(workpath, 'template2.aep')); + fs.unlinkSync(path.join(workpath, 'assets', 'image.jpg')); + fs.rmdirSync(path.join(workpath, 'assets')); + fs.unlinkSync(path.join(workpath, 'nested-structure.zip')); + + done(); + }) + .catch(done); + }); });