or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

Escalade

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.

Package Information

  • Package Name: escalade
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install escalade

Core Imports

Async 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.

Basic Usage

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)

Architecture

Escalade operates using a simple traversal pattern:

  • Input Resolution: Starting path is resolved relative to process.cwd() if not absolute
  • Directory Detection: If input is a file, escalade starts from its parent directory
  • Callback Execution: For each directory level, the callback receives the current directory path and its contents
  • Termination Conditions: Stops when callback returns truthy value or filesystem root is reached
  • Path Resolution: Relative callback returns are resolved to absolute paths from the current directory

Capabilities

Async Directory Traversal

Asynchronously 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;
      }
    }
  }
});

Sync Directory Traversal

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;
});

Callback Function Behavior

The callback function controls the search behavior:

  • Parameters:
    • directory (string): Absolute path of current directory being searched
    • files (string[]): Array of file and directory names in current directory (not paths)
  • Return Values:
    • Truthy string: Stops search, returns resolved absolute path
    • Falsy value (false, undefined, null): Continues to parent directory
    • Absolute path: Returned as-is without resolution
    • Relative path: Resolved relative to current directory

Advanced 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;
});

Error Handling

Escalade relies on Node.js filesystem operations and will throw standard filesystem errors:

  • ENOENT: Directory or file doesn't exist
  • EACCES: Permission denied
  • ENOTDIR: Path component is not a directory

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);
  }
}

Platform Support

  • Node.js: >=6 (sync mode), >=8 (async mode)
  • Deno: Supported via https://deno.land/x/escalade
  • Bundle Size: 183B (sync), 210B (async) when gzipped
  • Dependencies: Zero runtime dependencies