Created
October 23, 2024 20:31
-
-
Save subtleGradient/9bac5ea40810375e1ea137f90bea1249 to your computer and use it in GitHub Desktop.
combine a bunch of files into a single markdown file to paste into an AI or whatever
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 -S bun | |
import { readdir } from "fs/promises" | |
// Function to read and format file content | |
async function readFileContent(filePath: string): Promise<string> { | |
const file = Bun.file(filePath) | |
const content = await file.text() | |
const extension = filePath.split(".").pop() | |
if (extension === "md" || extension === "mdx") return content.replace(/^(#+) /gm, "#$1 ") // increase heading levels by 1 | |
const codeBlockSyntax = getSyntaxForExtension(extension) | |
return content | |
? `\`\`\`${codeBlockSyntax} filename="${filePath}"\n${content.trim()}\n\`\`\`` | |
: "(Contents available upon request)" | |
} | |
// Function to get code block syntax based on file extension | |
function getSyntaxForExtension(extension?: string): string { | |
switch (extension) { | |
case "ts": | |
case "tsx": | |
return "typescript" | |
case "js": | |
case "jsx": | |
return "javascript" | |
case "json": | |
return "json" | |
default: | |
return extension || "" | |
} | |
} | |
// Function to determine if a file is binary based on its MIME type | |
async function isBinaryFile(filePath: string): Promise<boolean> { | |
const file = Bun.file(filePath) | |
const mimeType = file.type | |
return !( | |
(mimeType?.startsWith("text/") || mimeType === "application/json" || mimeType === "image/svg+xml") // Example of non-binary type you might still want to include | |
) | |
} | |
// Recursively walk the target folder and collect file paths | |
async function walkFolder(folderPath: string): Promise<string[]> { | |
const files: string[] = [] | |
const entries = await readdir(folderPath, { withFileTypes: true }) | |
for (const entry of entries) { | |
const entryPath = `${folderPath}/${entry.name}` | |
if (entry.isDirectory()) { | |
files.push(...(await walkFolder(entryPath))) | |
} else { | |
// Check if the file is binary before adding | |
const binary = await isBinaryFile(entryPath) | |
if (binary) continue | |
if (entryPath.includes("/.turbo/")) continue | |
files.push(entryPath) | |
} | |
} | |
return files | |
} | |
const filenameToSortKey = (filename: string) => | |
filename.toLowerCase().includes("/readme") | |
? `0${filename}` // | |
: filename.toLowerCase().endsWith(".md") | |
? `1${filename}` | |
: filename | |
const fileSorter = (a: string, b: string) => filenameToSortKey(b).localeCompare(filenameToSortKey(a)) | |
// Main function | |
async function main() { | |
// Get target folder and output file from command line arguments | |
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |
const [$0, $1, ...sourceFiles] = process.argv | |
if (sourceFiles.length === 0) { | |
const commandName = __filename.replace(__dirname + "/", "") | |
console.warn(`No source files provided`) | |
console.warn(`\t${__filename}`) | |
console.warn(`\t${commandName} $(rg -l 'search term') > ../output.md`) | |
console.warn( | |
`\t${commandName} $(rg -l 'prisma.*relay|relay.*prisma' | grep -vE 'json|yaml|svg|yml|snap') > ../prisma+relay.md`, | |
) | |
process.exit(1) | |
} | |
// Collect all file paths | |
const filePaths = await Promise.all(sourceFiles.map(file => walkFolder(file).catch(() => file))).then(files => | |
files.flat().sort(fileSorter).reverse(), | |
) | |
const header = ` | |
# Combined Files | |
This markdown file was generated by combining an entire folder structure into a single document. | |
## Files Included: | |
${filePaths.map(pathname => `* ${pathname.replace(process.env.HOME!, "~")}`).join("\n")} | |
--- | |
` | |
// Read and format each file content | |
const fileContents = await Promise.all( | |
filePaths.map(async filePath => { | |
const fileName = filePath.replace(`${sourceFiles}/`, "") | |
const content = await readFileContent(filePath) | |
return `\n<details><summary>${fileName}</summary>\n\n${content.trim()}\n</details>\n` | |
}), | |
) | |
// Combine all content into a single markdown string | |
const markdownContent = header + fileContents.join("\n\n") + "\n" | |
console.log(markdownContent) | |
// Write the markdown content to the output file | |
// await Bun.write(outputFile, markdownContent) | |
// console.log(`Combined files into ${outputFile}`) | |
} | |
// Run the main function | |
// @ts-ignore - ignore top-level await error | |
await main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
usage:
uses ripgrep to find files that mention
@RelayResolver
and then copy the combined markdown to your clipboard on macOS