#!/usr/bin/env node /** * extract_archive.js β€” Estrae archivi (zip, tar, rar) e organizza risorse * * Usage: * node extract_archive.js --project * node extract_archive.js brand_assets.zip --project demo_co_srl * node extract_archive.js https://example.com/assets.zip --project campagna_x * * Options: * --base-path Base directory (default: current dir o ENV) * --keep-archive Mantieni file originale * --verbose Log dettagliato * --dry-run Simula senza estrazione * * Environment: * AGENCY_PROJECTS_BASE Base directory per progetti (opzionale) */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // Mapping parole chiave β†’ cartelle const CATEGORY_KEYWORDS = { 'images/logo': ['logo', 'marchio', 'brand', 'logotipo'], 'images/prodotto': ['prodotto', 'product', 'item', 'articolo'], 'images/team': ['team', 'staff', 'ufficio', 'office', 'persone', 'people'], 'images/stock': ['sfondo', 'background', 'texture', 'stock'], 'videos/promo': ['promo', 'reel', 'trailer', 'advertisement'], 'videos/tutorial': ['tutorial', 'howto', 'demo', 'dimostrazione', 'guida'], 'documents/brand': ['brand', 'guideline', 'manual', 'linee guida'], 'documents/product': ['scheda', 'datasheet', 'spec', 'specifiche'], }; function getFileType(filename) { const ext = path.extname(filename).toLowerCase().slice(1); const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'tiff']; const videoExts = ['mp4', 'mov', 'avi', 'mkv', 'webm', 'wmv']; const docExts = ['pdf', 'doc', 'docx', 'txt', 'md', 'ppt', 'pptx', 'xls', 'xlsx']; if (imageExts.includes(ext)) return 'images'; if (videoExts.includes(ext)) return 'videos'; if (docExts.includes(ext)) return 'documents'; return 'other'; } function categorizeFile(filename, fileType) { const filenameLower = filename.toLowerCase(); for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) { const baseType = category.split('/')[0]; if (baseType === fileType) { for (const keyword of keywords) { if (filenameLower.includes(keyword)) { return category; } } } } return fileType !== 'other' ? `${fileType}/` : 'misc/'; } function formatSize(bytes) { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; for (const unit of units) { if (size < 1024) return `${size.toFixed(1)} ${unit}`; size /= 1024; } return `${size.toFixed(1)} TB`; } function downloadFile(url, destPath, verbose = false) { try { if (verbose) console.log(`πŸ“₯ Download: ${url}`); // Usa curl o wget (piΓΉ affidabili) execSync(`curl -L -o "${destPath}" "${url}"`, { stdio: verbose ? 'inherit' : 'pipe' }); if (verbose) console.log(`βœ… Download completato: ${destPath}`); return true; } catch (error) { console.error(`❌ Errore download: ${error.message}`); return false; } } function extractArchive(archivePath, extractTo, verbose = false) { const filename = path.basename(archivePath); const ext = path.extname(filename).toLowerCase(); try { // Crea cartella temporanea if (!fs.existsSync(extractTo)) { fs.mkdirSync(extractTo, { recursive: true }); } if (ext === '.zip') { execSync(`unzip -o "${archivePath}" -d "${extractTo}"`, { stdio: verbose ? 'inherit' : 'pipe' }); const output = execSync(`unzip -l "${archivePath}" | tail -n +4 | head -n -2`, { encoding: 'utf8' }); return output.split('\n').filter(line => line.trim()).map(line => { const parts = line.trim().split(/\s+/); return parts[parts.length - 1]; }); } else if (ext === '.gz' && filename.includes('.tar')) { execSync(`tar -xzf "${archivePath}" -C "${extractTo}"`, { stdio: verbose ? 'inherit' : 'pipe' }); const output = execSync(`tar -tzf "${archivePath}"`, { encoding: 'utf8' }); return output.split('\n').filter(line => line.trim()); } else if (ext === '.rar') { try { execSync(`unrar x -o+ "${archivePath}" "${extractTo}"`, { stdio: verbose ? 'inherit' : 'pipe' }); const output = execSync(`unrar l "${archivePath}" | tail -n +5 | head -n -2`, { encoding: 'utf8' }); return output.split('\n').filter(line => line.trim()).map(line => { const parts = line.trim().split(/\s+/); return parts[parts.length - 1]; }); } catch (error) { console.error('❌ Supporto RAR non disponibile. Installa: sudo apt-get install unrar'); return []; } } else { console.error(`❌ Formato ${ext} non supportato. Usa zip, tar.gz, o rar.`); return []; } } catch (error) { console.error(`❌ Errore estrazione: ${error.message}`); return []; } } function organizeFiles(tempDir, assetsDir, verbose = false) { const organized = []; // Crea struttura cartelle const folders = [ 'images/logo', 'images/prodotto', 'images/team', 'images/stock', 'videos/promo', 'videos/tutorial', 'documents/brand', 'documents/product' ]; for (const folder of folders) { fs.mkdirSync(path.join(assetsDir, folder), { recursive: true }); } // Walk ricorsivo function walkDir(dir) { const files = []; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { files.push(...walkDir(fullPath)); } else if (!entry.name.startsWith('.') && entry.name !== 'Thumbs.db') { files.push(fullPath); } } return files; } const allFiles = walkDir(tempDir); for (const srcPath of allFiles) { const filename = path.basename(srcPath); const fileType = getFileType(filename); const category = categorizeFile(filename, fileType); const destFolder = path.join(assetsDir, category); let destPath = path.join(destFolder, filename); // Gestisci duplicati let counter = 1; const base = path.basename(filename, path.extname(filename)); const ext = path.extname(filename); while (fs.existsSync(destPath)) { destPath = path.join(destFolder, `${base}_${counter}${ext}`); counter++; } // Copia file fs.copyFileSync(srcPath, destPath); const stats = fs.statSync(destPath); organized.push({ original: filename, destination: path.relative(assetsDir, destPath), type: fileType, category: category, size: stats.size }); if (verbose) { console.log(` πŸ“ ${filename} β†’ ${category}/`); } } return organized; } function logOperation(project, archiveName, organizedFiles, opsLogPath) { const timestamp = new Date().toISOString().slice(0, 16).replace('T', ' '); const images = organizedFiles.filter(f => f.type === 'images'); const videos = organizedFiles.filter(f => f.type === 'videos'); const docs = organizedFiles.filter(f => f.type === 'documents'); const logEntry = ` ## ${timestamp} β€” Archivist Upload - **Archivio:** \`${archiveName}\` - **File estratti:** ${organizedFiles.length} - **Status:** βœ… Completato ### Dettagli | Tipo | Count | Dimensione Totale | |------|-------|-------------------| | Immagini | ${images.length} | ${formatSize(images.reduce((sum, f) => sum + f.size, 0))} | | Video | ${videos.length} | ${formatSize(videos.reduce((sum, f) => sum + f.size, 0))} | | Documenti | ${docs.length} | ${formatSize(docs.reduce((sum, f) => sum + f.size, 0))} | `; fs.appendFileSync(opsLogPath, logEntry); } function main() { const args = process.argv.slice(2); // Parse arguments let pathOrUrl = null; let project = null; let basePath = process.env.AGENCY_PROJECTS_BASE || process.cwd(); let keepArchive = false; let verbose = false; let dryRun = false; for (let i = 0; i < args.length; i++) { if (args[i] === '--project' && args[i + 1]) { project = args[++i]; } else if (args[i] === '--base-path' && args[i + 1]) { basePath = args[++i]; } else if (args[i] === '--keep-archive') { keepArchive = true; } else if (args[i] === '--verbose') { verbose = true; } else if (args[i] === '--dry-run') { dryRun = true; } else if (!args[i].startsWith('--')) { pathOrUrl = args[i]; } } if (!pathOrUrl || !project) { console.error('Usage: node extract_archive.js --project '); console.error('Options: --base-path , --keep-archive, --verbose, --dry-run'); console.error('Environment: AGENCY_PROJECTS_BASE (opzionale)'); process.exit(1); } // Path const projectDir = path.join(basePath, project); const assetsDir = path.join(projectDir, 'assets'); const archiveDir = path.join(assetsDir, 'archive'); const opsLog = path.join(projectDir, 'ops', 'run_log.md'); // Verifica cartella progetto if (!fs.existsSync(projectDir)) { console.error(`❌ Cartella progetto non trovata: ${projectDir}`); console.error(' Crea prima il progetto con agency-orchestrator'); process.exit(1); } // Crea cartelle fs.mkdirSync(archiveDir, { recursive: true }); fs.mkdirSync(path.join(projectDir, 'ops'), { recursive: true }); // URL o path locale? const isUrl = pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://') || pathOrUrl.startsWith('ftp://'); let archivePath; let archiveName; if (isUrl) { archiveName = path.basename(pathOrUrl.split('?')[0]); archivePath = path.join(archiveDir, archiveName); if (dryRun) { console.log(`πŸ” [DRY-RUN] Download: ${pathOrUrl} β†’ ${archivePath}`); process.exit(0); } if (!downloadFile(pathOrUrl, archivePath, verbose)) { process.exit(1); } } else { archivePath = pathOrUrl; archiveName = path.basename(archivePath); if (!fs.existsSync(archivePath)) { console.error(`❌ File non trovato: ${archivePath}`); process.exit(1); } if (dryRun) { console.log(`πŸ” [DRY-RUN] Estrai: ${archivePath} β†’ ${assetsDir}`); process.exit(0); } // Copia in archive/ fs.copyFileSync(archivePath, path.join(archiveDir, archiveName)); } if (verbose) { console.log(`\nπŸ“¦ Archivio: ${archiveName}`); console.log(`πŸ“ Destinazione: ${assetsDir}`); console.log(); } // Estrai in temporanea const tempDir = path.join(archiveDir, '.temp_extract'); fs.mkdirSync(tempDir, { recursive: true }); console.log('πŸ”„ Estrazione in corso...'); const extracted = extractArchive(path.join(archiveDir, archiveName), tempDir, verbose); if (extracted.length === 0) { console.error('❌ Nessun file estratto'); fs.rmSync(tempDir, { recursive: true, force: true }); process.exit(1); } // Organizza file console.log('\nπŸ—‚οΈ Organizzazione file...'); const organized = organizeFiles(tempDir, assetsDir, verbose); // Pulisci temporanea fs.rmSync(tempDir, { recursive: true, force: true }); // Log operazione logOperation(project, archiveName, organized, opsLog); // Elimina archivio originale (se non --keep-archive) if (!keepArchive) { fs.unlinkSync(path.join(archiveDir, archiveName)); if (verbose) console.log('\nπŸ—‘οΈ Archivio originale eliminato'); } // Riepilogo console.log('\nβœ… Completato!'); console.log(` πŸ“¦ File estratti: ${organized.length}`); console.log(` πŸ“ Cartella: ${assetsDir}`); console.log(` πŸ“ Log: ${opsLog}`); console.log(`\nπŸ‘‰ Prossimo step: node scripts/scan_resources.js --project ${project}`); } main();