the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 1241cd4e999bb3f48dca4cab7bcff14fe6f5d6ad 81 lines 2.6 kB view raw
1import ignore from "ignore"; 2import { readFile, readdir } from "node:fs/promises"; 3import { join, dirname, basename } from "node:path"; 4 5const IGNORE_FILE_NAMES = [ 6 ".pocketenvignore", 7 ".gitignore", 8 ".npmignore", 9 ".dockerignore", 10]; 11 12export type IgnoreContext = { 13 /** Path of the ignore file's directory relative to the scan root. Empty string for root. */ 14 dir: string; 15 ig: ReturnType<typeof ignore>; 16}; 17 18/** 19 * Recursively finds all ignore files under `root` and loads them into 20 * per-directory contexts. Each context's patterns are scoped to its directory, 21 * matching how git resolves nested .gitignore files. 22 */ 23export async function loadIgnoreFiles(root: string): Promise<IgnoreContext[]> { 24 const contexts: IgnoreContext[] = []; 25 26 // readdir with recursive:true finds hidden ignore files (e.g. apps/api/.gitignore) 27 // which Node.js glob("**/.gitignore") silently skips. 28 const ignoreFileSet = new Set(IGNORE_FILE_NAMES); 29 const candidates = (await readdir(root, { recursive: true })).filter( 30 (entry) => ignoreFileSet.has(basename(entry)), 31 ); 32 33 for (const file of candidates) { 34 try { 35 const ig = ignore(); 36 ig.add(await readFile(join(root, file), "utf8")); 37 const dir = dirname(file); 38 contexts.push({ dir: dir === "." ? "" : dir, ig }); 39 } catch { 40 // skip unreadable files 41 } 42 } 43 44 return contexts; 45} 46 47/** 48 * Returns an `isIgnored(path)` function that checks a relative path against 49 * all loaded ignore contexts. 50 * 51 * For each context whose directory is an ancestor of the path, the path is 52 * made relative to that context's directory and then tested with the suffix 53 * approach (see below). 54 * 55 * Why the suffix approach? 56 * `ig.ignores('node_modules')` returns false for pattern `node_modules/` 57 * (trailing slash = directory-only) because the ignore package requires the 58 * tested path to end with `/` to match. Checking each path suffix both with 59 * and without a trailing slash fixes this and also makes unanchored patterns 60 * (e.g. `node_modules`) apply at any depth within the context's directory. 61 */ 62export function makeIsIgnored(contexts: IgnoreContext[]) { 63 return function isIgnored(path: string): boolean { 64 return contexts.some(({ dir, ig }) => { 65 const rel = 66 dir === "" 67 ? path 68 : path.startsWith(dir + "/") 69 ? path.slice(dir.length + 1) 70 : null; 71 72 if (rel === null) return false; 73 74 const parts = rel.split("/"); 75 return parts.some((_, i) => { 76 const sub = parts.slice(i).join("/"); 77 return ig.ignores(sub) || ig.ignores(sub + "/"); 78 }); 79 }); 80 }; 81}