Created
December 9, 2025 10:29
-
-
Save manzke/8765077618bcbd9bcd5c0b62453cd487 to your computer and use it in GitHub Desktop.
Get a report of files in your nexus / maven repository as well as a clean up script to purge certain versions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /** | |
| * Nexus Repository Analyzer | |
| * Analyzes Nexus Maven repositories and generates a CSV report of artifacts by size | |
| */ | |
| const https = require('https'); | |
| const http = require('http'); | |
| const fs = require('fs'); | |
| const { URL } = require('url'); | |
| // Generate timestamped filename | |
| function getTimestampedFilename(baseFilename) { | |
| const timestamp = new Date().toISOString() | |
| .replace(/:/g, '-') // Replace colons with hyphens | |
| .replace(/\..+/, '') // Remove milliseconds | |
| .replace('T', '_'); // Replace T with underscore | |
| // If baseFilename has an extension, insert timestamp before it | |
| const lastDot = baseFilename.lastIndexOf('.'); | |
| if (lastDot > 0) { | |
| return baseFilename.substring(0, lastDot) + '_' + timestamp + baseFilename.substring(lastDot); | |
| } | |
| return baseFilename + '_' + timestamp; | |
| } | |
| // Configuration - Update these values | |
| const config = { | |
| nexusUrl: process.env.NEXUS_URL || 'https://nexus.local.io', | |
| username: process.env.NEXUS_USERNAME || 'USER_NAME', | |
| password: process.env.NEXUS_PASSWORD || 'SECRET_PASSWORD', | |
| repository: process.env.NEXUS_REPO || '', // Leave empty to scan all repos | |
| ignoreRepos: process.env.IGNORE_REPOS ? process.env.IGNORE_REPOS.split(',') : ['public'], // Comma-separated list of repos to ignore | |
| sortBySize: process.env.SORT_BY_SIZE !== 'false', // Set to 'false' to disable sorting | |
| outputFile: process.env.OUTPUT_FILE || getTimestampedFilename('nexus-artifacts.csv'), | |
| pageSize: 1000, // Number of items per API request | |
| }; | |
| // Helper function to make HTTP requests | |
| function makeRequest(url, auth) { | |
| return new Promise((resolve, reject) => { | |
| const urlObj = new URL(url); | |
| const isHttps = urlObj.protocol === 'https:'; | |
| const client = isHttps ? https : http; | |
| const options = { | |
| hostname: urlObj.hostname, | |
| port: urlObj.port || (isHttps ? 443 : 80), | |
| path: urlObj.pathname + urlObj.search, | |
| method: 'GET', | |
| headers: { | |
| 'Authorization': 'Basic ' + Buffer.from(`${auth.username}:${auth.password}`).toString('base64'), | |
| 'Accept': 'application/json' | |
| } | |
| }; | |
| const req = client.request(options, (res) => { | |
| let data = ''; | |
| res.on('data', (chunk) => { | |
| data += chunk; | |
| }); | |
| res.on('end', () => { | |
| if (res.statusCode >= 200 && res.statusCode < 300) { | |
| try { | |
| resolve(JSON.parse(data)); | |
| } catch (e) { | |
| reject(new Error(`Failed to parse JSON: ${e.message}`)); | |
| } | |
| } else { | |
| reject(new Error(`HTTP ${res.statusCode}: ${data}`)); | |
| } | |
| }); | |
| }); | |
| req.on('error', reject); | |
| req.end(); | |
| }); | |
| } | |
| // Check if repository should be ignored | |
| function shouldIgnoreRepo(repoName) { | |
| return config.ignoreRepos.some(pattern => { | |
| // Support wildcards and regex-like patterns | |
| const regexPattern = pattern.replace(/\*/g, '.*'); | |
| const regex = new RegExp(`^${regexPattern}$`, 'i'); | |
| return regex.test(repoName); | |
| }); | |
| } | |
| // Get list of repositories | |
| async function getRepositories() { | |
| console.log('Fetching repository list...'); | |
| const url = `${config.nexusUrl}/service/rest/v1/repositories`; | |
| const repos = await makeRequest(url, config); | |
| // Filter for Maven repositories and apply ignore patterns | |
| const filtered = repos.filter(repo => | |
| repo.format === 'maven2' && | |
| (config.repository === '' || repo.name === config.repository) && | |
| !shouldIgnoreRepo(repo.name) | |
| ); | |
| if (config.ignoreRepos.length > 0) { | |
| const ignored = repos.filter(repo => shouldIgnoreRepo(repo.name)); | |
| if (ignored.length > 0) { | |
| console.log(`Ignoring ${ignored.length} repositories: ${ignored.map(r => r.name).join(', ')}`); | |
| } | |
| } | |
| return filtered; | |
| } | |
| // Get all components from a repository with pagination | |
| async function getComponents(repositoryName) { | |
| console.log(`Fetching components from repository: ${repositoryName}...`); | |
| const components = []; | |
| let continuationToken = null; | |
| do { | |
| const url = `${config.nexusUrl}/service/rest/v1/components?repository=${repositoryName}` + | |
| (continuationToken ? `&continuationToken=${continuationToken}` : ''); | |
| const response = await makeRequest(url, config); | |
| if (response.items) { | |
| components.push(...response.items); | |
| console.log(` Retrieved ${components.length} components so far...`); | |
| } | |
| continuationToken = response.continuationToken; | |
| } while (continuationToken); | |
| return components; | |
| } | |
| // Get detailed asset information for a component | |
| async function getAssets(componentId) { | |
| const url = `${config.nexusUrl}/service/rest/v1/assets?repository=${config.repository}`; | |
| const response = await makeRequest(url, config); | |
| return response.items.filter(asset => asset.componentId === componentId); | |
| } | |
| // Format bytes to human-readable format | |
| function formatBytes(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // Generate artifact URL | |
| function generateArtifactUrl(repositoryName, group, name, version) { | |
| // Convert group to path (replace dots with slashes) | |
| const groupPath = group ? group.replace(/\./g, '/') : ''; | |
| // Construct the repository browser URL | |
| return `${config.nexusUrl}/#browse/browse:${repositoryName}`; | |
| } | |
| // Escape CSV field | |
| function escapeCsvField(field) { | |
| if (field === null || field === undefined) return ''; | |
| const str = String(field); | |
| if (str.includes(',') || str.includes('"') || str.includes('\n')) { | |
| return `"${str.replace(/"/g, '""')}"`; | |
| } | |
| return str; | |
| } | |
| // Main function | |
| async function analyze() { | |
| const startTime = new Date(); | |
| console.log('=== Nexus Repository Analyzer ===\n'); | |
| console.log(`Run Date: ${startTime.toISOString()}`); | |
| console.log(`Nexus URL: ${config.nexusUrl}`); | |
| console.log(`Output file: ${config.outputFile}`); | |
| console.log(`Sort by size: ${config.sortBySize}`); | |
| console.log(`Ignore repos: ${config.ignoreRepos.join(', ')}\n`); | |
| try { | |
| const repositories = await getRepositories(); | |
| console.log(`Found ${repositories.length} Maven repositories to analyze\n`); | |
| if (repositories.length === 0) { | |
| console.log('No repositories found. Check your configuration and credentials.'); | |
| process.exit(1); | |
| } | |
| const allArtifacts = []; | |
| // Process each repository | |
| for (const repo of repositories) { | |
| console.log(`\n--- Processing repository: ${repo.name} ---`); | |
| const components = await getComponents(repo.name); | |
| // Extract artifact information | |
| for (const component of components) { | |
| // Calculate total size of all assets in this component | |
| let totalSize = 0; | |
| if (component.assets) { | |
| component.assets.forEach(asset => { | |
| totalSize += asset.fileSize || 0; | |
| }); | |
| } | |
| allArtifacts.push({ | |
| repository: repo.name, | |
| group: component.group || '', | |
| name: component.name || '', | |
| version: component.version || '', | |
| format: component.format || '', | |
| assetCount: component.assets ? component.assets.length : 0, | |
| sizeBytes: totalSize, | |
| sizeFormatted: formatBytes(totalSize), | |
| lastModified: component.assets && component.assets.length > 0 | |
| ? component.assets[0].lastModified | |
| : '', | |
| url: generateArtifactUrl(repo.name, component.group, component.name, component.version) | |
| }); | |
| } | |
| console.log(` Total components in ${repo.name}: ${components.length}`); | |
| } | |
| // Sort by size (descending) if enabled | |
| if (config.sortBySize) { | |
| console.log('\nSorting artifacts by size...'); | |
| allArtifacts.sort((a, b) => b.sizeBytes - a.sizeBytes); | |
| } | |
| // Calculate total size | |
| const totalSize = allArtifacts.reduce((sum, artifact) => sum + artifact.sizeBytes, 0); | |
| const runDate = new Date().toISOString(); | |
| // Write CSV file | |
| console.log(`\n--- Writing results to ${config.outputFile} ---`); | |
| // Add metadata header as comment | |
| let csvContent = `# Nexus Repository Analysis\n`; | |
| csvContent += `# Generated: ${runDate}\n`; | |
| csvContent += `# Nexus URL: ${config.nexusUrl}\n`; | |
| csvContent += `# Total Artifacts: ${allArtifacts.length}\n`; | |
| csvContent += `# Total Size: ${formatBytes(totalSize)}\n`; | |
| csvContent += `# Sorted by Size: ${config.sortBySize}\n`; | |
| csvContent += `#\n`; | |
| const csvHeaders = [ | |
| 'Repository', | |
| 'Group', | |
| 'Artifact', | |
| 'Version', | |
| 'Format', | |
| 'Asset Count', | |
| 'Size (Bytes)', | |
| 'Size (Formatted)', | |
| 'Last Modified', | |
| 'URL' | |
| ]; | |
| csvContent += csvHeaders.join(',') + '\n'; | |
| for (const artifact of allArtifacts) { | |
| const row = [ | |
| escapeCsvField(artifact.repository), | |
| escapeCsvField(artifact.group), | |
| escapeCsvField(artifact.name), | |
| escapeCsvField(artifact.version), | |
| escapeCsvField(artifact.format), | |
| escapeCsvField(artifact.assetCount), | |
| escapeCsvField(artifact.sizeBytes), | |
| escapeCsvField(artifact.sizeFormatted), | |
| escapeCsvField(artifact.lastModified), | |
| escapeCsvField(artifact.url) | |
| ]; | |
| csvContent += row.join(',') + '\n'; | |
| } | |
| fs.writeFileSync(config.outputFile, csvContent); | |
| // Print summary | |
| console.log('\n=== Summary ==='); | |
| console.log(`Total artifacts: ${allArtifacts.length}`); | |
| console.log(`Total size: ${formatBytes(totalSize)} (${totalSize} bytes)`); | |
| console.log(`\nTop 10 largest artifacts:`); | |
| allArtifacts.slice(0, 10).forEach((artifact, index) => { | |
| console.log(`${index + 1}. ${artifact.group}:${artifact.name}:${artifact.version} - ${artifact.sizeFormatted}`); | |
| }); | |
| console.log(`\n✓ CSV report saved to: ${config.outputFile}`); | |
| } catch (error) { | |
| console.error('Error:', error.message); | |
| if (error.message.includes('401')) { | |
| console.error('\nAuthentication failed. Please check your username and password.'); | |
| } else if (error.message.includes('ECONNREFUSED')) { | |
| console.error('\nCould not connect to Nexus. Please check the URL.'); | |
| } | |
| process.exit(1); | |
| } | |
| } | |
| // Run the analyzer | |
| analyze(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /** | |
| * Nexus Repository Cleanup Script | |
| * Deletes artifacts from Nexus Maven repositories based on repository, component, and version criteria | |
| * | |
| * DANGER: This script permanently deletes artifacts. Use with caution! | |
| */ | |
| const https = require('https'); | |
| const http = require('http'); | |
| const fs = require('fs'); | |
| const readline = require('readline'); | |
| const { URL } = require('url'); | |
| // Configuration | |
| const config = { | |
| nexusUrl: process.env.NEXUS_URL || 'https://nexus.local.io', | |
| username: process.env.NEXUS_USERNAME || 'USER_NAME', | |
| password: process.env.NEXUS_PASSWORD || 'SECRET_PASSWORD', | |
| repository: process.env.NEXUS_REPO || '', // REQUIRED | |
| component: process.env.COMPONENT || '', // Optional - if empty, applies to all components | |
| version: process.env.VERSION || '', // REQUIRED | |
| dryRun: process.env.DRY_RUN !== 'false', // Default to dry-run mode for safety | |
| autoConfirm: process.env.AUTO_CONFIRM === 'true', // Skip confirmation prompts | |
| useCache: process.env.USE_CACHE === 'true', // Use cached data if available | |
| cacheFile: process.env.CACHE_FILE || '.nexus-cache.json', // Cache file location | |
| maxCacheAge: parseInt(process.env.MAX_CACHE_AGE || '3600', 10), // Cache validity in seconds (default 1 hour) | |
| }; | |
| // Helper function to make HTTP requests | |
| function makeRequest(url, auth, method = 'GET') { | |
| return new Promise((resolve, reject) => { | |
| const urlObj = new URL(url); | |
| const isHttps = urlObj.protocol === 'https:'; | |
| const client = isHttps ? https : http; | |
| const options = { | |
| hostname: urlObj.hostname, | |
| port: urlObj.port || (isHttps ? 443 : 80), | |
| path: urlObj.pathname + urlObj.search, | |
| method: method, | |
| headers: { | |
| 'Authorization': 'Basic ' + Buffer.from(`${auth.username}:${auth.password}`).toString('base64'), | |
| 'Accept': 'application/json' | |
| } | |
| }; | |
| const req = client.request(options, (res) => { | |
| let data = ''; | |
| res.on('data', (chunk) => { | |
| data += chunk; | |
| }); | |
| res.on('end', () => { | |
| if (res.statusCode >= 200 && res.statusCode < 300) { | |
| try { | |
| if (data) { | |
| resolve(JSON.parse(data)); | |
| } else { | |
| resolve({ status: 'success', statusCode: res.statusCode }); | |
| } | |
| } catch (e) { | |
| // For DELETE requests, empty response is OK | |
| if (method === 'DELETE') { | |
| resolve({ status: 'deleted', statusCode: res.statusCode }); | |
| } else { | |
| reject(new Error(`Failed to parse JSON: ${e.message}`)); | |
| } | |
| } | |
| } else { | |
| reject(new Error(`HTTP ${res.statusCode}: ${data}`)); | |
| } | |
| }); | |
| }); | |
| req.on('error', reject); | |
| req.end(); | |
| }); | |
| } | |
| // Save cache to file | |
| function saveCache(repositoryName, components) { | |
| const cache = { | |
| repository: repositoryName, | |
| nexusUrl: config.nexusUrl, | |
| timestamp: new Date().toISOString(), | |
| componentCount: components.length, | |
| components: components | |
| }; | |
| try { | |
| fs.writeFileSync(config.cacheFile, JSON.stringify(cache, null, 2)); | |
| console.log(`✓ Cached ${components.length} components to ${config.cacheFile}`); | |
| } catch (error) { | |
| console.warn(`Warning: Failed to save cache: ${error.message}`); | |
| } | |
| } | |
| // Load cache from file | |
| function loadCache(repositoryName) { | |
| try { | |
| if (!fs.existsSync(config.cacheFile)) { | |
| return null; | |
| } | |
| const cacheData = fs.readFileSync(config.cacheFile, 'utf8'); | |
| const cache = JSON.parse(cacheData); | |
| // Validate cache | |
| if (cache.repository !== repositoryName) { | |
| console.log(`Cache is for different repository (${cache.repository} vs ${repositoryName}), fetching fresh data...`); | |
| return null; | |
| } | |
| if (cache.nexusUrl !== config.nexusUrl) { | |
| console.log(`Cache is for different Nexus URL, fetching fresh data...`); | |
| return null; | |
| } | |
| // Check cache age | |
| const cacheAge = (new Date() - new Date(cache.timestamp)) / 1000; | |
| if (cacheAge > config.maxCacheAge) { | |
| const ageMinutes = Math.floor(cacheAge / 60); | |
| console.log(`Cache is too old (${ageMinutes} minutes), fetching fresh data...`); | |
| return null; | |
| } | |
| const cacheMinutes = Math.floor(cacheAge / 60); | |
| const cacheSeconds = Math.floor(cacheAge % 60); | |
| console.log(`✓ Using cached data (${cache.componentCount} components, ${cacheMinutes}m ${cacheSeconds}s old)`); | |
| return cache.components; | |
| } catch (error) { | |
| console.warn(`Warning: Failed to load cache: ${error.message}`); | |
| return null; | |
| } | |
| } | |
| // Get all components from a repository with pagination | |
| async function getComponents(repositoryName) { | |
| // Try to use cache if enabled | |
| if (config.useCache) { | |
| const cachedComponents = loadCache(repositoryName); | |
| if (cachedComponents) { | |
| return cachedComponents; | |
| } | |
| } | |
| console.log(`Fetching components from repository: ${repositoryName}...`); | |
| const components = []; | |
| let continuationToken = null; | |
| do { | |
| const url = `${config.nexusUrl}/service/rest/v1/components?repository=${repositoryName}` + | |
| (continuationToken ? `&continuationToken=${continuationToken}` : ''); | |
| const response = await makeRequest(url, config); | |
| if (response.items) { | |
| components.push(...response.items); | |
| console.log(` Retrieved ${components.length} components so far...`); | |
| } | |
| continuationToken = response.continuationToken; | |
| } while (continuationToken); | |
| // Save to cache if enabled | |
| if (config.useCache) { | |
| saveCache(repositoryName, components); | |
| } | |
| return components; | |
| } | |
| // Delete a component by ID | |
| async function deleteComponent(componentId) { | |
| const url = `${config.nexusUrl}/service/rest/v1/components/${componentId}`; | |
| return await makeRequest(url, config, 'DELETE'); | |
| } | |
| // Format bytes to human-readable format | |
| function formatBytes(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // Check if version matches the pattern (supports wildcards) | |
| function versionMatches(componentVersion, versionPattern) { | |
| // Convert wildcard pattern to regex | |
| const regexPattern = versionPattern | |
| .replace(/\./g, '\\.') // Escape dots | |
| .replace(/\*/g, '.*'); // Convert * to .* | |
| const regex = new RegExp(`^${regexPattern}$`, 'i'); | |
| return regex.test(componentVersion); | |
| } | |
| // Check if component name matches the pattern (supports wildcards) | |
| function componentMatches(componentName, componentPattern) { | |
| if (!componentPattern) return true; // If no pattern specified, match all | |
| const regexPattern = componentPattern | |
| .replace(/\./g, '\\.') // Escape dots | |
| .replace(/\*/g, '.*'); // Convert * to .* | |
| const regex = new RegExp(`^${regexPattern}$`, 'i'); | |
| return regex.test(componentName); | |
| } | |
| // Ask for user confirmation | |
| function askConfirmation(question) { | |
| return new Promise((resolve) => { | |
| const rl = readline.createInterface({ | |
| input: process.stdin, | |
| output: process.stdout | |
| }); | |
| rl.question(question + ' (yes/no): ', (answer) => { | |
| rl.close(); | |
| resolve(answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y'); | |
| }); | |
| }); | |
| } | |
| // Validate configuration | |
| function validateConfig() { | |
| const errors = []; | |
| if (!config.repository) { | |
| errors.push('NEXUS_REPO is required (repository name)'); | |
| } | |
| if (!config.version) { | |
| errors.push('VERSION is required (version pattern to delete)'); | |
| } | |
| if (errors.length > 0) { | |
| console.error('\n❌ Configuration errors:'); | |
| errors.forEach(err => console.error(` - ${err}`)); | |
| console.error('\nUsage:'); | |
| console.error(' NEXUS_REPO=repo-name VERSION=1.0.0 node nexus-cleanup.js'); | |
| console.error(' NEXUS_REPO=repo-name COMPONENT=my-artifact VERSION=1.* node nexus-cleanup.js'); | |
| console.error('\nEnvironment variables:'); | |
| console.error(' NEXUS_REPO (required) - Repository name'); | |
| console.error(' VERSION (required) - Version pattern (supports wildcards: 1.*, *-SNAPSHOT)'); | |
| console.error(' COMPONENT (optional) - Component name pattern (supports wildcards)'); | |
| console.error(' DRY_RUN (default: true) - Set to "false" to actually delete'); | |
| console.error(' AUTO_CONFIRM (default: false) - Set to "true" to skip confirmations'); | |
| console.error(' USE_CACHE (default: false) - Set to "true" to use cached repository data'); | |
| console.error(' CACHE_FILE (default: .nexus-cache.json) - Cache file location'); | |
| console.error(' MAX_CACHE_AGE (default: 3600) - Cache validity in seconds'); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Main function | |
| async function cleanup() { | |
| console.log('=== Nexus Repository Cleanup ===\n'); | |
| // Validate configuration | |
| if (!validateConfig()) { | |
| process.exit(1); | |
| } | |
| console.log(`Nexus URL: ${config.nexusUrl}`); | |
| console.log(`Repository: ${config.repository}`); | |
| console.log(`Component: ${config.component || '* (all components)'}`); | |
| console.log(`Version: ${config.version}`); | |
| console.log(`Dry Run: ${config.dryRun ? 'YES (no actual deletion)' : 'NO (WILL DELETE!)'}`); | |
| console.log(`Use Cache: ${config.useCache ? 'YES' : 'NO'}${config.useCache ? ` (${config.cacheFile}, max age: ${config.maxCacheAge}s)` : ''}\n`); | |
| if (config.dryRun) { | |
| console.log('🔒 Running in DRY-RUN mode. No artifacts will be deleted.'); | |
| console.log(' Set DRY_RUN=false to actually delete artifacts.\n'); | |
| } else { | |
| console.log('⚠️ WARNING: DRY-RUN is DISABLED. Artifacts WILL BE DELETED!\n'); | |
| } | |
| try { | |
| // Get all components from the repository | |
| const components = await getComponents(config.repository); | |
| console.log(`\nTotal components in repository: ${components.length}`); | |
| // Filter components that match our criteria | |
| const matchingComponents = components.filter(component => { | |
| const versionMatch = versionMatches(component.version, config.version); | |
| const nameMatch = componentMatches(component.name, config.component); | |
| return versionMatch && nameMatch; | |
| }); | |
| console.log(`\nFound ${matchingComponents.length} components matching criteria:\n`); | |
| if (matchingComponents.length === 0) { | |
| console.log('No components to delete. Exiting.'); | |
| return; | |
| } | |
| // Calculate total size | |
| let totalSize = 0; | |
| const componentList = []; | |
| matchingComponents.forEach((component, index) => { | |
| let componentSize = 0; | |
| if (component.assets) { | |
| component.assets.forEach(asset => { | |
| componentSize += asset.fileSize || 0; | |
| }); | |
| } | |
| totalSize += componentSize; | |
| componentList.push({ | |
| index: index + 1, | |
| id: component.id, | |
| group: component.group || '', | |
| name: component.name || '', | |
| version: component.version || '', | |
| size: componentSize, | |
| sizeFormatted: formatBytes(componentSize), | |
| assetCount: component.assets ? component.assets.length : 0 | |
| }); | |
| }); | |
| // Display components to be deleted | |
| console.log('Components to be deleted:'); | |
| console.log('─'.repeat(100)); | |
| componentList.forEach(comp => { | |
| console.log(`${comp.index}. ${comp.group}:${comp.name}:${comp.version}`); | |
| console.log(` Size: ${comp.sizeFormatted} (${comp.assetCount} files)`); | |
| }); | |
| console.log('─'.repeat(100)); | |
| console.log(`\nTotal size to be freed: ${formatBytes(totalSize)} (${totalSize} bytes)`); | |
| console.log(`Total components to delete: ${componentList.length}\n`); | |
| // Ask for confirmation unless auto-confirm is enabled | |
| if (!config.autoConfirm) { | |
| if (config.dryRun) { | |
| console.log('This is a DRY-RUN. The above components would be deleted if DRY_RUN=false.\n'); | |
| const proceed = await askConfirmation('Do you want to see the deletion process (no actual deletion)?'); | |
| if (!proceed) { | |
| console.log('Cancelled.'); | |
| return; | |
| } | |
| } else { | |
| console.log('⚠️ THIS WILL PERMANENTLY DELETE THE ABOVE COMPONENTS! ⚠️\n'); | |
| const proceed = await askConfirmation('Are you absolutely sure you want to delete these components?'); | |
| if (!proceed) { | |
| console.log('Cancelled.'); | |
| return; | |
| } | |
| } | |
| } | |
| // Delete components | |
| console.log(`\n${config.dryRun ? 'Simulating' : 'Starting'} deletion...\n`); | |
| let deletedCount = 0; | |
| let failedCount = 0; | |
| const deletedLog = []; | |
| for (const comp of componentList) { | |
| const action = config.dryRun ? '[DRY-RUN]' : '[DELETING]'; | |
| console.log(`${action} ${comp.group}:${comp.name}:${comp.version} (${comp.sizeFormatted})`); | |
| if (!config.dryRun) { | |
| try { | |
| await deleteComponent(comp.id); | |
| deletedCount++; | |
| deletedLog.push({ | |
| group: comp.group, | |
| name: comp.name, | |
| version: comp.version, | |
| size: comp.size, | |
| status: 'deleted' | |
| }); | |
| console.log(` ✓ Deleted successfully`); | |
| } catch (error) { | |
| failedCount++; | |
| deletedLog.push({ | |
| group: comp.group, | |
| name: comp.name, | |
| version: comp.version, | |
| size: comp.size, | |
| status: 'failed', | |
| error: error.message | |
| }); | |
| console.log(` ✗ Failed: ${error.message}`); | |
| } | |
| } else { | |
| deletedCount++; | |
| console.log(` ✓ Would be deleted`); | |
| } | |
| } | |
| // Summary | |
| console.log('\n=== Summary ==='); | |
| if (config.dryRun) { | |
| console.log(`Would delete: ${deletedCount} components`); | |
| console.log(`Would free: ${formatBytes(totalSize)}`); | |
| console.log('\n💡 To actually delete these components, run with: DRY_RUN=false'); | |
| } else { | |
| console.log(`Successfully deleted: ${deletedCount} components`); | |
| console.log(`Failed: ${failedCount} components`); | |
| console.log(`Space freed: ${formatBytes(totalSize)}`); | |
| // Write deletion log | |
| const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '').replace('T', '_'); | |
| const logFile = `deletion-log_${timestamp}.json`; | |
| const fs = require('fs'); | |
| fs.writeFileSync(logFile, JSON.stringify({ | |
| timestamp: new Date().toISOString(), | |
| repository: config.repository, | |
| component: config.component, | |
| version: config.version, | |
| totalDeleted: deletedCount, | |
| totalFailed: failedCount, | |
| spaceFreed: totalSize, | |
| components: deletedLog | |
| }, null, 2)); | |
| console.log(`\nDeletion log saved to: ${logFile}`); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error.message); | |
| if (error.message.includes('401')) { | |
| console.error('\nAuthentication failed. Please check your username and password.'); | |
| } else if (error.message.includes('ECONNREFUSED')) { | |
| console.error('\nCould not connect to Nexus. Please check the URL.'); | |
| } | |
| process.exit(1); | |
| } | |
| } | |
| // Run the cleanup | |
| cleanup(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment