import path from 'path' import { spawn } from 'child_process' import { promises as fs } from 'fs' import readline from 'readline' import { chunk } from 'lodash-es' /** * usage: * tsx <path>/.sources.mts <target dir> */ let cwd: string = process.argv[2] ?? process.cwd() if (!path.isAbsolute(cwd)) { cwd = path.join(process.cwd(), cwd) } const dirname = path.basename(cwd) const prompt = readline.createInterface({ input: process.stdin, output: process.stdout }) function ask({ index }: { index: number }): Promise<'yes' | 'no'> { return new Promise(resolve => { prompt.question( `[${index}] continue? ('no' to re-copy) [yes]/no `, input => resolve((input as 'yes' | 'no') || ''), ) }) } const pbcopy = async (data: string) => { var proc = spawn('pbcopy') await proc.stdin.write(data) await proc.stdin.end() } const walk = async ({ dir, prefix = '', files = [], }:{ dir: string; prefix?: string; files?: string[]; }): Promise<string[]> => { for await (const entry of await fs.opendir(dir)) { if (entry.isDirectory()) { await walk({ dir: path.join(dir, entry.name), prefix: path.join(prefix, entry.name), files, }) } else if (entry.isFile() || entry.isSymbolicLink()) { files.push(path.join(prefix, entry.name)) } } return files } const files = (await walk({ dir: cwd })) .filter(name => /(?<!test)\.ts$/.test(name)) const pathOfFile = (name: string) => path.join('.', dirname, name) const codes = await Promise.all( files .map(async file => ({ file, code: await fs.readFile(path.join(cwd, file), { encoding: 'utf-8' }), })) ) type Item = { file: string; code: string; } const chunkSize = 10 const chunks: Item[][] = chunk(codes, chunkSize) console.log(` total files: ${files.length} total chunks: ${chunks.length} (chunk size ${chunkSize}) `) for (const [index, chunk] of chunks.entries()) { const paste = chunk .map(({ file, code }) => [ `// ${pathOfFile(file)}`, code, ].join('\n')) .join('\n-----\n') await pbcopy(paste) while (await ask({ index }) === 'no') { await pbcopy(paste) } }