Escalade is a tiny (183B to 210B) and fast utility for ascending parent directories to locate specific files or directories. It provides both asynchronous and synchronous modes, using a callback-based approach where users define custom search criteria while escalade automatically traverses up the directory tree until either a match is found or the filesystem root is reached.
npm install escaladeAsync mode (default):
import escalade from "escalade";For CommonJS:
const escalade = require("escalade");Sync mode:
import escalade from "escalade/sync";For CommonJS:
const escalade = require("escalade/sync");With TypeScript types:
import escalade from "escalade";
import escaladeSync from "escalade/sync";
// Access Callback types via namespace (due to CommonJS-style export)
type AsyncCallback = escalade.Callback;
type SyncCallback = escaladeSync.Callback;Note: The package uses CommonJS-style exports (
export =), so the Callback types are accessed via the function namespace rather than as named exports.
Async mode example:
import { join } from 'path';
import escalade from 'escalade';
// Find the nearest package.json
const packagePath = await escalade(process.cwd(), (dir, files) => {
if (files.includes('package.json')) {
return 'package.json'; // Returns absolute path
}
});
console.log(packagePath);
// => "/Users/project/package.json" (or undefined if not found)Sync mode example:
import { join } from 'path';
import escalade from 'escalade/sync';
// Find the nearest .git directory
const gitDir = escalade(process.cwd(), (dir, files) => {
return files.includes('.git') && '.git';
});
console.log(gitDir);
// => "/Users/project/.git" (or undefined if not found)Escalade operates using a simple traversal pattern:
process.cwd() if not absoluteAsynchronously ascends parent directories using promises and Node.js async filesystem operations.
/**
* Asynchronously ascend parent directories until callback returns truthy value
* @param directory - Starting path (file or directory)
* @param callback - Function called for each directory level
* @returns Promise resolving to absolute path or undefined
*/
function escalade(
directory: string,
callback: AsyncCallback
): Promise<string | void>;
type Promisable<T> = T | Promise<T>;
type AsyncCallback = (
directory: string,
files: string[]
) => Promisable<string | false | void>;Usage Example:
import escalade from 'escalade';
// Find configuration file with async callback
const configPath = await escalade(__dirname, async (dir, files) => {
// Callback can be async
for (const file of files) {
if (file.endsWith('.config.js')) {
// Verify it's actually a config file
const content = await fs.readFile(path.join(dir, file), 'utf8');
if (content.includes('module.exports')) {
return file;
}
}
}
});Synchronously ascends parent directories using Node.js synchronous filesystem operations.
/**
* Synchronously ascend parent directories until callback returns truthy value
* @param directory - Starting path (file or directory)
* @param callback - Function called for each directory level
* @returns Absolute path or undefined
*/
function escalade(
directory: string,
callback: SyncCallback
): string | void;
type SyncCallback = (
directory: string,
files: string[]
) => string | false | void;Usage Example:
import escalade from 'escalade/sync';
// Find nearest license file
const licensePath = escalade(process.cwd(), (dir, files) => {
const licenseFile = files.find(file =>
file.toLowerCase().startsWith('license')
);
return licenseFile || false;
});The callback function controls the search behavior:
directory (string): Absolute path of current directory being searchedfiles (string[]): Array of file and directory names in current directory (not paths)false, undefined, null): Continues to parent directoryAdvanced callback example:
const found = await escalade('/some/deep/path', (dir, files) => {
console.log(`Searching in: ${dir}`);
console.log(`Contents: ${files.join(', ')}`);
// Multiple search criteria
if (files.includes('package.json')) {
return 'package.json';
}
if (files.includes('yarn.lock')) {
return 'yarn.lock';
}
// Continue searching
return false;
});Escalade relies on Node.js filesystem operations and will throw standard filesystem errors:
Error handling example:
try {
const result = await escalade('/nonexistent/path', (dir, files) => {
return files.includes('target.txt') && 'target.txt';
});
} catch (error) {
if (error.code === 'ENOENT') {
console.log('Starting path does not exist');
} else if (error.code === 'EACCES') {
console.log('Permission denied accessing directory');
} else {
console.log('Unexpected error:', error.message);
}
}https://deno.land/x/escalade