Created
February 13, 2025 09:20
-
-
Save hargup/1287ac4c4e31642e25eb357eb1909015 to your computer and use it in GitHub Desktop.
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 bun | |
import { Command } from 'commander'; | |
import * as ts from 'typescript'; | |
import * as path from 'path'; | |
import * as fs from 'fs'; | |
import fg from 'fast-glob'; | |
import clipboardy from 'clipboardy'; | |
const program = new Command(); | |
program | |
.description('Convert selected TypeScript files (plus their dependencies) into a text format, with optional clipboard copy.') | |
.option('-s, --select <patterns...>', 'Glob patterns to include in code block format') | |
.option('-i, --ignore <patterns...>', 'Glob patterns to ignore', []) | |
.option('--list', 'List matched files with approximate token counts, sorted descending') | |
.option('--out <file>', 'Output file (defaults to clipboard). Use "stdout" for standard output.', 'clipboard') | |
.parse(process.argv); | |
interface Options { | |
select?: string[]; | |
ignore?: string[]; | |
list?: boolean; | |
out: string; | |
} | |
const options = program.opts<Options>(); | |
async function main() { | |
// 1. Check for required --select patterns | |
const patterns: string[] | undefined = options.select; | |
if (!patterns || patterns.length === 0) { | |
console.error('Error: No glob patterns provided. Use --select to specify which files to include.'); | |
process.exit(1); | |
} | |
// 2. Gather the matched files (absolute paths) as our "root" files, respecting any ignored patterns | |
const rootFilePaths = await fg(patterns, { | |
absolute: true, | |
cwd: process.cwd(), | |
ignore: options.ignore, | |
}); | |
if (rootFilePaths.length === 0) { | |
console.log('No files matched the provided patterns (after ignore).'); | |
return; | |
} | |
// 3. Create a TypeScript Program from these root files | |
const compilerOptions: ts.CompilerOptions = { | |
allowJs: true, | |
skipLibCheck: true, | |
esModuleInterop: true, | |
resolveJsonModule: true, | |
}; | |
const tsProgram = ts.createProgram(rootFilePaths, compilerOptions); | |
// 4. Retrieve all source files in the program | |
const allSourceFiles = tsProgram.getSourceFiles(); | |
// Helper to approximate token count | |
// (You could choose a more sophisticated approach if needed) | |
function approximateTokenCount(text: string) { | |
return text.split(/\s+/).filter(Boolean).length; | |
} | |
// 5. Build output in-memory & keep track of file info for --list | |
let output = ''; | |
const fileInfo: Array<{ path: string; tokens: number }> = []; | |
for (const sourceFile of allSourceFiles) { | |
const fileName = sourceFile.fileName; | |
// Skip node_modules and .d.ts | |
if (fileName.includes('node_modules')) { | |
continue; | |
} | |
if (fileName.endsWith('.d.ts')) { | |
continue; | |
} | |
// Read file contents | |
const fileContents = fs.readFileSync(fileName, 'utf8'); | |
// Make path relative to the current working directory | |
const relPath = path.relative(process.cwd(), fileName); | |
// Track approximate token count | |
const tokens = approximateTokenCount(fileContents); | |
fileInfo.push({ path: relPath, tokens }); | |
// Write block | |
output += `<code file:${relPath}>\n`; | |
output += fileContents; | |
output += `\n</code>\n\n`; | |
} | |
// If --list is specified, output a table sorted by descending token count | |
if (options.list) { | |
// Sort file info by descending token count | |
fileInfo.sort((a, b) => b.tokens - a.tokens); | |
console.log('Matched files (approx. token count, descending):'); | |
console.log('--------------------------------------------------'); | |
console.log(`| Tokens | File Path`); | |
console.log('--------------------------------------------------'); | |
for (const info of fileInfo) { | |
// Pad the token count to align columns nicely | |
const tokenStr = info.tokens.toString().padEnd(10); | |
console.log(`| ${tokenStr} | ${info.path}`); | |
} | |
console.log('--------------------------------------------------'); | |
console.log(''); | |
} | |
// 6. Output handling | |
// Default: Copy to clipboard. If --out is specified: | |
// - 'stdout': print to stdout | |
// - otherwise: write to the file path given | |
if (options.out === 'clipboard') { | |
await clipboardy.write(output); | |
console.log('Output has been copied to the clipboard.'); | |
} else if (options.out === 'stdout') { | |
process.stdout.write(output); | |
} else { | |
fs.writeFileSync(options.out, output, 'utf8'); | |
console.log(`Output written to ${options.out}`); | |
} | |
} | |
main().catch(err => { | |
console.error(err); | |
process.exit(1); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment