import * as fs from "fs"; import path from "path"; interface LetterGroup { offset: number; length: number; packages: string[]; } const INPUT = path.join(__dirname, "..", "src", "runtime", "cli", "add_completions.txt"); const OUTPUT = path.join(__dirname, "..", "src", "runtime", "cli", "add_completions.rs"); // Read and parse input file const content = fs.readFileSync(INPUT, "utf8"); const packages = content .split("\n") .map(line => line.trim()) .filter(line => line.length > 0) .sort(); // Group packages by first letter const letterGroups = new Map(); let currentOffset = 0; let maxListSize = 0; for (const pkg of packages) { if (pkg.length === 0) continue; const firstLetter = pkg[0].toLowerCase(); if (!letterGroups.has(firstLetter)) { letterGroups.set(firstLetter, { offset: currentOffset, length: 0, packages: [], }); } const group = letterGroups.get(firstLetter)!; group.packages.push(pkg); group.length++; maxListSize = Math.max(maxListSize, group.length); } // Create a single buffer with all package data const dataChunks: Buffer[] = []; let totalUncompressed = 0; // Store total package count first const totalCountBuf = Buffer.alloc(4); totalCountBuf.writeUInt32LE(packages.length, 0); dataChunks.push(totalCountBuf); totalUncompressed += 4; // Then all packages with length prefixes for (const pkg of packages) { const lenBuf = Buffer.alloc(2); lenBuf.writeUInt16LE(pkg.length, 0); dataChunks.push(lenBuf); dataChunks.push(Buffer.from(pkg, "utf8")); totalUncompressed += 2 + pkg.length; } const uncompressedData = Buffer.concat(dataChunks); // Compress with zstd (level 1, matching the old `zstd -1` CLI invocation) const compressedData = Bun.zstdCompressSync(uncompressedData, { level: 1 }); // Calculate compression ratio const totalCompressed = compressedData.length; const ratio = ((totalCompressed / totalUncompressed) * 100).toFixed(1); console.log("\nCompression statistics:"); console.log(`Uncompressed size: ${totalUncompressed} bytes`); console.log(`Compressed size: ${totalCompressed} bytes`); console.log(`Compression ratio: ${ratio}%`); // Generate index entries const letters = "abcdefghijklmnopqrstuvwxyz"; const indexEntries: string[] = []; let offset = 0; for (const letter of letters) { const group = letterGroups.get(letter); const len = group?.length ?? 0; indexEntries.push(` (${offset}, ${len}), // ${letter}`); offset += len; } // Split the compressed blob into 16-byte rows for readability const blobLines: string[] = []; for (let i = 0; i < compressedData.length; i += 16) { const row = [...compressedData.slice(i, i + 16)].join(", "); blobLines.push(` ${row},`); } const out = `// Auto-generated by misctools/generate-add-completions.ts. Do not edit. // // Regenerate after editing src/runtime/cli/add_completions.txt: // // bun misctools/generate-add-completions.ts // // Compressing the completions list saves about 100 KB of binary size. use bun_core::Once; #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq)] pub(crate) enum FirstLetter { ${[...letters].map(l => ` ${l.toUpperCase()} = b'${l}',`).join("\n")} } /// Largest per-letter package list length. pub(crate) const BIGGEST_LIST: usize = ${maxListSize}; const UNCOMPRESSED_SIZE: usize = ${totalUncompressed}; /// (offset, length) into the decompressed package list for each letter a-z. const INDEX: [(usize, usize); 26] = [ ${indexEntries.join("\n")} ]; static COMPRESSED_DATA: &[u8] = &[ ${blobLines.join("\n")} ]; struct Table { _decompressed: Vec, packages: Vec<&'static [u8]>, } static TABLE: Once = >::new(); fn build_table() -> Table { let decompressed = bun_zstd::decompress_alloc(COMPRESSED_DATA) .expect("add_completions: zstd decompress failed"); debug_assert_eq!(decompressed.len(), UNCOMPRESSED_SIZE); // SAFETY: the decompressed Vec is moved into TABLE (a process-lifetime // Once) and never dropped or reallocated, so its storage is valid for // 'static. The package slices below borrow this storage. let data: &'static [u8] = unsafe { core::slice::from_raw_parts(decompressed.as_ptr(), decompressed.len()) }; let total = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize; let mut packages: Vec<&'static [u8]> = Vec::with_capacity(total); let mut pos: usize = 4; for _ in 0..total { let len = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize; pos += 2; packages.push(&data[pos..pos + len]); pos += len; } Table { _decompressed: decompressed, packages } } /// Decompress the package-name table. Idempotent. pub fn init() { TABLE.get_or_init(build_table); } /// Returns the slice of package names beginning with \`letter\`. pub(crate) fn get_packages(letter: FirstLetter) -> &'static [&'static [u8]] { let table = TABLE.get_or_init(build_table); let (offset, length) = INDEX[(letter as u8 - b'a') as usize]; if length == 0 { return &[]; } &table.packages[offset..offset + length] } `; fs.writeFileSync(OUTPUT, out); try { const { execSync } = await import("child_process"); execSync(`rustfmt "${OUTPUT}"`, { stdio: "inherit" }); } catch { // rustfmt not available; committed output is already formatted reasonably } console.log(`\nGenerated ${OUTPUT} for ${packages.length} packages`);