1
0
Fork 0
mirror of https://github.com/imjasonh/apkviz synced 2026-07-07 01:22:26 +00:00
apkviz/scripts/fetch-data.js
Jason Hall c9fe336400
Add Wolfi APK dependency visualizer with advanced analytics
This web application visualizes package dependencies in the Wolfi Linux distribution,
providing powerful insights into the package ecosystem.

Features:
- Interactive force-directed dependency graph visualization
- Shows both forward dependencies (what a package depends on) and reverse dependencies (what depends on it)
- Click any node to re-center the visualization on that package
- Transitive dependency analysis with toggle control
- Advanced package analytics including:
  - Criticality scores showing how many packages depend on each package
  - Total size impact analysis (package + all dependencies)
  - Dependency depth metrics
  - Bus factor warnings for critical infrastructure packages
- "Most Critical Packages" dashboard showing system-wide statistics
- Real-time search with auto-complete
- Different visual styles for direct vs transitive dependencies
- Playwright-based testing infrastructure for automated UI testing

Technical implementation:
- TypeScript + D3.js for visualization
- Webpack build system
- Analyzes Wolfi's APKINDEX for package metadata
- Efficient dependency graph algorithms
- Responsive design with detailed package information panel

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-13 15:41:49 -04:00

64 lines
No EOL
2 KiB
JavaScript

const https = require('https');
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
function fetchAPKINDEX() {
return new Promise((resolve, reject) => {
https.get('https://packages.wolfi.dev/os/x86_64/APKINDEX.tar.gz', (res) => {
const chunks = [];
res.on('data', chunk => chunks.push(chunk));
res.on('end', () => {
const buffer = Buffer.concat(chunks);
resolve(buffer);
});
res.on('error', reject);
});
});
}
function extractAPKINDEXFromTar(tarData) {
let offset = 0;
while (offset < tarData.length) {
const header = tarData.slice(offset, offset + 512);
offset += 512;
if (header.every(byte => byte === 0)) break;
const filename = header.slice(0, 100).toString().replace(/\0.*$/, '');
const sizeString = header.slice(124, 135).toString().replace(/\0.*$/, '');
const fileSize = parseInt(sizeString, 8) || 0;
if (filename === 'APKINDEX' || filename.includes('APKINDEX')) {
return tarData.slice(offset, offset + fileSize).toString('utf-8');
}
offset += Math.ceil(fileSize / 512) * 512;
}
throw new Error('APKINDEX not found in tar archive');
}
async function main() {
console.log('Fetching APKINDEX.tar.gz...');
const compressed = await fetchAPKINDEX();
console.log('Decompressing...');
const decompressed = zlib.gunzipSync(compressed);
console.log('Extracting APKINDEX from tar...');
const apkindex = extractAPKINDEXFromTar(decompressed);
const publicDir = path.join(__dirname, '..', 'public');
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
const outputPath = path.join(publicDir, 'APKINDEX');
fs.writeFileSync(outputPath, apkindex);
console.log(`Saved to ${outputPath}`);
console.log(`File size: ${(apkindex.length / 1024 / 1024).toFixed(2)} MB`);
}
main().catch(console.error);