UNPKG

tscribe

Version:

CLI to dump TypeScript sources with headings for LLM ingestion

137 lines (136 loc) 5.01 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildGlobPattern = buildGlobPattern; exports.tscribe = tscribe; exports.applySort = applySort; const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const glob_1 = require("glob"); const micromatch_1 = __importDefault(require("micromatch")); const natural_orderby_1 = require("natural-orderby"); function log(message, opts) { if (!opts.quiet) console.log(message); } function debug(message, opts) { if (opts.verbose && !opts.quiet) console.log("[debug]", message); } function filterIgnoredPaths(files, ignore, basePath) { if (!ignore?.trim()) return files; const patterns = ignore .split(",") .map((p) => p.trim().replace(/\\/g, "/")) .filter(Boolean); return files.filter((file) => { const relativePath = path_1.default.relative(basePath, file).replace(/\\/g, "/"); return !micromatch_1.default.isMatch(relativePath, patterns); }); } function buildGlobPattern(srcPath, ext) { const resolved = srcPath.replace(/\\/g, "/"); if (ext === "" || ext === "*") { return `${resolved}/**/*`; } const exts = ext .split(",") .map((e) => e.trim()) .filter(Boolean); if (exts.length > 1) { return `${resolved}/**/*.{${exts.join(",")}}`; } return `${resolved}/**/*.${exts[0]}`; } async function tscribe(opts) { const headingTpl = opts.heading || (opts.format === "plain" ? "// --- {file} ---" : "### {file}"); debug(`src = ${opts.src}`, opts); debug(`ext = ${opts.ext}`, opts); debug(`ignore = ${opts.ignore}`, opts); debug(`sort = ${opts.sort}`, opts); debug(`verbose = ${opts.verbose}`, opts); let files = []; const srcPath = path_1.default.resolve(opts.src); // Check if directory exists if (!(0, fs_1.existsSync)(srcPath)) { debug(`Source directory ${srcPath} does not exist`, opts); log(`✅ Processed 0 files.`, opts); return; } const pattern = buildGlobPattern(srcPath, opts.ext); debug(`glob pattern: ${pattern}`, opts); // handle ignore patterns properly const ignoreList = (opts.ignore ?? "node_modules,dist,.git") .split(",") .map((p) => p.trim()) .filter(Boolean); try { debug(`srcPath exists: ${(0, fs_1.existsSync)(srcPath)}`, opts); debug(`ignoreList: ${ignoreList.join(", ")}`, opts); debug(`directory contents: ${(0, fs_1.readdirSync)(srcPath).join(", ")}`, opts); files = await (0, glob_1.glob)(pattern, { ignore: ignoreList, absolute: true, nodir: true, }); debug(`found ${files.length} files via glob`, opts); } catch (err) { log(`error in glob: ${err}`, opts); } // apply manual filtering files = filterIgnoredPaths(files, opts.ignore || "", srcPath); debug(`final filtered files: ${files.length}`, opts); // show matched files only in verbose mode debug(`files matched before output: ${files.join(", ")}`, opts); if (files.length === 0) { debug("No files found", opts); log(`✅ Processed 0 files.`, opts); const empty = ""; if (opts.out) { await fs_1.promises.writeFile(path_1.default.resolve(opts.out), empty, "utf8"); // write empty file } else { process.stdout.write(empty); // default fallback } return; } const sorted = await applySort(files, opts.sort); if (opts.list) { log(sorted.join("\n"), opts); return; } const sections = await Promise.all(sorted.map(async (f) => { const body = await fs_1.promises.readFile(f, "utf8"); const title = headingTpl.replace("{file}", path_1.default.relative(process.cwd(), f).replace(/\\/g, "/")); return `${title}\n\n${body}`; })); const fullOutput = sections.join("\n\n"); if (opts.out) { // Fix: Make sure output file is written properly const outPath = path_1.default.resolve(opts.out); await fs_1.promises.writeFile(outPath, fullOutput, "utf8"); debug(`Output written to ${outPath}`, opts); } else { process.stdout.write(fullOutput); } log(`✅ Processed ${sections.length} files.`, opts); } async function applySort(files, mode) { switch (mode) { case "alpha": return (0, natural_orderby_1.orderBy)([...files], [(f) => path_1.default.basename(f)]); case "mtime": { const stats = await Promise.all(files.map(async (f) => ({ file: f, mtime: (await fs_1.promises.stat(f)).mtimeMs }))); return stats.sort((a, b) => a.mtime - b.mtime).map((s) => s.file); } case "path": default: return files; } }