const fs = require('fs'); const path = require('path'); const https = require('https'); class DownloadAssetsPlugin { constructor(assets) { this.assets = assets; this.hasRun = false; this.createdDirs = new Set(); } safeUnlink(filePath) { try { fs.unlinkSync(filePath); } catch (err) { // Ignore cleanup errors } } apply(compiler) { compiler.hooks.beforeCompile.tapAsync('DownloadAssetsPlugin', (params, callback) => { // Only run once per webpack process if (this.hasRun) { callback(); return; } console.log('📦 Checking and downloading required assets...'); const downloadPromises = this.assets.map(asset => this.downloadFile(asset)); Promise.allSettled(downloadPromises) .then((results) => { const failed = results.filter(r => r.status === 'rejected'); if (failed.length > 0) { console.warn(`⚠️ ${failed.length} asset(s) failed to download, continuing anyway...`); } console.log('✅ Asset check complete!'); this.hasRun = true; callback(); }); }); } downloadFile({ url, output }) { return new Promise((resolve, reject) => { // Ensure directory exists (only once per unique path) if (!this.createdDirs.has(output)) { fs.mkdirSync(output, { recursive: true }); this.createdDirs.add(output); } const filename = path.basename(url); const filePath = path.join(output, filename); // Skip if file already exists if (fs.existsSync(filePath)) { resolve(); return; } console.log(` Downloading ${filename}...`); const file = fs.createWriteStream(filePath); const downloadFromUrl = (downloadUrl) => { https.get(downloadUrl, (response) => { // Handle redirects if (response.statusCode === 302 || response.statusCode === 301) { file.close(); this.safeUnlink(filePath); downloadFromUrl(response.headers.location); return; } if (response.statusCode !== 200) { file.close(); this.safeUnlink(filePath); reject(new Error(`Failed to download ${filename}: HTTP ${response.statusCode}`)); return; } response.pipe(file); file.on('finish', () => { file.close(); console.log(` ✓ Downloaded ${filename}`); resolve(); }); file.on('error', (err) => { this.safeUnlink(filePath); reject(err); }); }).on('error', (err) => { if (fs.existsSync(filePath)) { this.safeUnlink(filePath); } reject(err); }); }; downloadFromUrl(url); }); } } module.exports = DownloadAssetsPlugin;