UNPKG

@wttp/site

Version:

Web3 Transfer Protocol (WTTP) - Site Contracts and deployment tools

408 lines • 17.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const config_1 = require("hardhat/config"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); class WordPressMinimizer { constructor(sitePath) { this.fileUsage = new Map(); this.htmlFiles = []; this.cssFiles = []; this.indexFileMap = new Map(); // Maps directory paths to index.html files this.sitePath = path.resolve(sitePath); } async analyze() { console.log(`šŸ” Analyzing WordPress site at: ${this.sitePath}`); // Find all files in the site await this.discoverFiles(); // Crawl HTML files for references await this.crawlHtmlFiles(); // Crawl CSS files for references await this.crawlCssFiles(); // Calculate statistics return this.calculateStats(); } async discoverFiles() { const walkDir = (dir) => { const files = fs.readdirSync(dir, { withFileTypes: true }); for (const file of files) { const fullPath = path.join(dir, file.name); const relativePath = path.relative(this.sitePath, fullPath); if (file.isDirectory()) { walkDir(fullPath); } else { // Skip special files that should never be considered unused if (file.name === '.wttpignore' || file.name === 'routes.json') { continue; } const stats = fs.statSync(fullPath); this.fileUsage.set(relativePath, { path: relativePath, usageCount: 0, size: stats.size }); // Track HTML and CSS files for crawling if (file.name.endsWith('.html')) { this.htmlFiles.push(fullPath); // Map index.html files to their directory paths for reference matching if (file.name === 'index.html') { const dirPath = path.dirname(relativePath); // Map both the directory and directory with trailing slash if (dirPath === '.') { // Root index.html maps to empty path and root path this.indexFileMap.set('', relativePath); this.indexFileMap.set('.', relativePath); } else { this.indexFileMap.set(dirPath, relativePath); this.indexFileMap.set(dirPath + path.sep, relativePath); } } } else if (file.name.endsWith('.css')) { this.cssFiles.push(fullPath); } } } }; walkDir(this.sitePath); console.log(`šŸ“ Found ${this.fileUsage.size} files to analyze`); } async crawlHtmlFiles() { console.log(`šŸ” Crawling ${this.htmlFiles.length} HTML files...`); for (const htmlFile of this.htmlFiles) { const content = fs.readFileSync(htmlFile, 'utf-8'); this.findReferencesInHtml(content); } } findReferencesInHtml(content) { // Patterns to match various types of file references const patterns = [ // src attributes /src=["']([^"']+)["']/gi, // href attributes /href=["']([^"']+)["']/gi, // CSS background-image /background-image:\s*url\(["']?([^"')]+)["']?\)/gi, // CSS url() references /url\(["']?([^"')]+)["']?\)/gi, ]; for (const pattern of patterns) { let match; while ((match = pattern.exec(content)) !== null) { const url = match[1]; this.processReference(url); } } // Handle srcset which can have multiple URLs const srcsetPattern = /srcset=["']([^"']+)["']/gi; let srcsetMatch; while ((srcsetMatch = srcsetPattern.exec(content)) !== null) { const srcsetValue = srcsetMatch[1]; // Parse srcset format: "url1 size1, url2 size2, ..." const srcsetUrls = srcsetValue.split(',').map(item => { // Each item is "url width" or "url density" const parts = item.trim().split(/\s+/); return parts[0]; // Return just the URL part }).filter(url => url && url.length > 0); srcsetUrls.forEach(url => this.processReference(url)); } } async crawlCssFiles() { console.log(`šŸŽØ Crawling ${this.cssFiles.length} CSS files...`); for (const cssFile of this.cssFiles) { const content = fs.readFileSync(cssFile, 'utf-8'); this.findReferencesInCss(content); } } findReferencesInCss(content) { // CSS url() references const urlPattern = /url\(["']?([^"')]+)["']?\)/gi; let match; while ((match = urlPattern.exec(content)) !== null) { const url = match[1]; this.processReference(url); } } processReference(url) { // Skip external URLs, data URLs, and anchors if (url.startsWith('http') || url.startsWith('//') || url.startsWith('data:') || url.startsWith('#') || url.startsWith('mailto:')) { return; } // Remove query parameters and fragments const cleanUrl = url.split('?')[0].split('#')[0]; // Convert to relative path from site root let relativePath = cleanUrl; if (relativePath.startsWith('/')) { relativePath = relativePath.substring(1); } // Normalize path separators for Windows relativePath = relativePath.replace(/\//g, path.sep); // First try direct file match let usage = this.fileUsage.get(relativePath); if (usage) { usage.usageCount++; return; } // If no direct match, check if this might be a directory reference to an index.html // Remove trailing path separator if present const normalizedPath = relativePath.endsWith(path.sep) ? relativePath.slice(0, -1) : relativePath; // Check if this directory path maps to an index.html file const indexFile = this.indexFileMap.get(normalizedPath) || this.indexFileMap.get(normalizedPath + path.sep); if (indexFile) { const indexUsage = this.fileUsage.get(indexFile); if (indexUsage) { indexUsage.usageCount++; if (process.env.DEBUG_WP_MINIMIZE) { console.log(`šŸ“‚ Directory reference "${url}" mapped to index file: ${indexFile}`); } return; } } // Debug: log missing references to understand the issue if (process.env.DEBUG_WP_MINIMIZE) { console.log(`šŸ” Reference not found: "${relativePath}" (original: "${url}")`); } } calculateStats() { let totalFiles = 0; let usedFiles = 0; let unusedFiles = 0; let totalSize = 0; let usedSize = 0; let unusedSize = 0; for (const usage of this.fileUsage.values()) { totalFiles++; totalSize += usage.size; if (usage.usageCount > 0) { usedFiles++; usedSize += usage.size; } else { unusedFiles++; unusedSize += usage.size; } } const savings = (unusedSize / totalSize) * 100; return { totalFiles, usedFiles, unusedFiles, totalSize, usedSize, unusedSize, savings }; } getUnusedFiles() { return Array.from(this.fileUsage.values()).filter(usage => usage.usageCount === 0); } getUsedFiles() { return Array.from(this.fileUsage.values()).filter(usage => usage.usageCount > 0); } async generateWttpIgnore() { const unusedFiles = this.getUnusedFiles(); const header = [ "# WordPress Minimizer - Generated .wttpignore", `# Generated on: ${new Date().toISOString()}`, `# Unused files: ${unusedFiles.length}`, `# Space savings: ${this.formatBytes(unusedFiles.reduce((sum, f) => sum + f.size, 0))}`, "", "# Special files (always ignored):", "routes.json", "", "# Unused files (safe to ignore):" ]; const ignorePatterns = unusedFiles.map(file => file.path); return [...header, ...ignorePatterns].join('\n'); } async deleteUnusedFiles() { const unusedFiles = this.getUnusedFiles(); let deletedCount = 0; let deletedSize = 0; const affectedDirectories = new Set(); console.log(`šŸ—‘ļø Deleting ${unusedFiles.length} unused files...`); for (const file of unusedFiles) { const fullPath = path.join(this.sitePath, file.path); try { if (fs.existsSync(fullPath)) { fs.unlinkSync(fullPath); deletedCount++; deletedSize += file.size; // Track the directory that contained this file const dir = path.dirname(file.path); if (dir !== '.' && dir !== '') { affectedDirectories.add(dir); } } } catch (error) { console.warn(`āš ļø Could not delete ${file.path}: ${error}`); } } // Clean up empty directories const deletedDirectories = await this.deleteEmptyDirectories(affectedDirectories); console.log(`āœ… Deleted ${deletedCount} files and ${deletedDirectories} empty folders, saved ${this.formatBytes(deletedSize)}`); } async deleteEmptyDirectories(affectedDirectories) { let deletedDirectoryCount = 0; // Sort directories by depth (deepest first) to ensure we delete child directories before parents const sortedDirectories = Array.from(affectedDirectories).sort((a, b) => { const depthA = a.split(path.sep).length; const depthB = b.split(path.sep).length; return depthB - depthA; // Deepest first }); for (const dir of sortedDirectories) { const fullDirPath = path.join(this.sitePath, dir); try { if (fs.existsSync(fullDirPath)) { const items = fs.readdirSync(fullDirPath); // If directory is empty, delete it if (items.length === 0) { fs.rmdirSync(fullDirPath); deletedDirectoryCount++; if (process.env.DEBUG_WP_MINIMIZE) { console.log(`šŸ“‚ Deleted empty directory: ${dir}`); } // Check if parent directory is now empty too const parentDir = path.dirname(dir); if (parentDir !== '.' && parentDir !== '' && parentDir !== dir) { affectedDirectories.add(parentDir); } } } } catch (error) { if (process.env.DEBUG_WP_MINIMIZE) { console.warn(`āš ļø Could not delete directory ${dir}: ${error}`); } } } // If we deleted directories, recursively check for more empty parent directories if (deletedDirectoryCount > 0) { const remainingDirectories = new Set(); for (const dir of affectedDirectories) { const parentDir = path.dirname(dir); if (parentDir !== '.' && parentDir !== '' && parentDir !== dir) { remainingDirectories.add(parentDir); } } if (remainingDirectories.size > 0) { const additionalDeleted = await this.deleteEmptyDirectories(remainingDirectories); deletedDirectoryCount += additionalDeleted; } } return deletedDirectoryCount; } formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } printReport(stats) { console.log('\nšŸ“Š WordPress Site Analysis Report'); console.log('═'.repeat(50)); console.log(`šŸ“ Total files: ${stats.totalFiles}`); console.log(`āœ… Used files: ${stats.usedFiles}`); console.log(`āŒ Unused files: ${stats.unusedFiles}`); console.log(`šŸ“¦ Total size: ${this.formatBytes(stats.totalSize)}`); console.log(`šŸ’¾ Used size: ${this.formatBytes(stats.usedSize)}`); console.log(`šŸ—‘ļø Unused size: ${this.formatBytes(stats.unusedSize)}`); console.log(`šŸ’° Potential savings: ${stats.savings.toFixed(1)}%`); if (stats.unusedFiles > 0) { console.log('\nšŸ” Top unused files by size:'); const unusedFiles = this.getUnusedFiles() .sort((a, b) => b.size - a.size) .slice(0, 10); for (const file of unusedFiles) { console.log(` ${this.formatBytes(file.size).padStart(8)} - ${file.path}`); } } } } (0, config_1.task)("wp-minimize", "Minimize WordPress site by identifying unused files") .addParam("path", "Path to WordPress site directory") .addFlag("deleteFiles", "Delete unused files instead of creating .wttpignore") .addFlag("dryRun", "Show what would be done without making changes") .addFlag("debug", "Enable debug output to see reference processing") .setAction(async (taskArgs, hre) => { const { path: sitePath, deleteFiles, dryRun, debug } = taskArgs; if (debug) { process.env.DEBUG_WP_MINIMIZE = "true"; } if (!fs.existsSync(sitePath)) { throw new Error(`Site path does not exist: ${sitePath}`); } const minimizer = new WordPressMinimizer(sitePath); try { // Analyze the site const stats = await minimizer.analyze(); // Print report minimizer.printReport(stats); if (stats.unusedFiles === 0) { console.log('\nšŸŽ‰ Site is already optimized! No unused files found.'); return; } if (dryRun) { console.log('\nšŸ” Dry run mode - no changes will be made'); return; } if (deleteFiles) { // Delete unused files console.log('\nāš ļø WARNING: This will permanently delete unused files!'); console.log('Press Ctrl+C to cancel or wait 5 seconds to continue...'); await new Promise(resolve => setTimeout(resolve, 5000)); await minimizer.deleteUnusedFiles(); } else { // Generate .wttpignore file const ignoreContent = await minimizer.generateWttpIgnore(); const ignorePath = path.join(sitePath, '.wttpignore'); fs.writeFileSync(ignorePath, ignoreContent); console.log(`\nšŸ“ Generated .wttpignore file: ${ignorePath}`); console.log('šŸ’” Tip: Use --delete-files flag to permanently remove unused files'); } } catch (error) { console.error('āŒ Error:', error); throw error; } }); //# sourceMappingURL=wp-minimize.js.map