A fetcher for local directory packages within the pnpm ecosystem
npx @tessl/cli install tessl/npm-pnpm--directory-fetcher@1000.1.0@pnpm/directory-fetcher is a TypeScript library that provides a fetcher for local directory packages within the pnpm ecosystem. It handles fetching and processing of packages from local directories, supporting both package-file-only inclusion and complete directory content retrieval with configurable symlink resolution.
pnpm add @pnpm/directory-fetcherimport { createDirectoryFetcher, fetchFromDir } from "@pnpm/directory-fetcher";
import type { CreateDirectoryFetcherOptions, FetchResult } from "@pnpm/directory-fetcher";For CommonJS:
const { createDirectoryFetcher, fetchFromDir } = require("@pnpm/directory-fetcher");import { createDirectoryFetcher } from "@pnpm/directory-fetcher";
// Create a basic directory fetcher
const fetcher = createDirectoryFetcher();
// Use the fetcher with pnpm's CAFS system
const result = await fetcher.directory(cafs, {
type: 'directory',
directory: './packages/my-package'
}, {
lockfileDir: process.cwd()
});
// Result contains file index and package metadata
console.log(result.filesIndex);
console.log(result.manifest);
console.log(result.requiresBuild);@pnpm/directory-fetcher is built around several key components:
createDirectoryFetcher function that creates configured fetcher instancesfetchFromDir for direct directory fetching without CAFS integration@pnpm/exec.pkg-requires-build to determine if packages need compilation@pnpm/fs.packlist for npm-compatible file inclusion when includeOnlyPackageFiles is enabledCreates a configured directory fetcher instance with customizable options for file inclusion and symlink handling.
/**
* Creates a directory fetcher with configurable options
* @param opts - Optional configuration for the fetcher
* @returns Object containing the directory fetcher function
*/
function createDirectoryFetcher(
opts?: CreateDirectoryFetcherOptions
): { directory: DirectoryFetcher };
interface CreateDirectoryFetcherOptions {
/** Only include files that would be published (uses npm's file inclusion rules) */
includeOnlyPackageFiles?: boolean;
/** Resolve symlinks to their real file system paths */
resolveSymlinks?: boolean;
}
type DirectoryFetcher = (
cafs: Cafs,
resolution: DirectoryResolution,
opts: DirectoryFetcherOptions
) => Promise<FetchResult>;
/**
* Content Addressable File System interface for pnpm
*/
interface Cafs {
/** Content-addressable file system interface (imported from @pnpm/fetcher-base) */
[key: string]: any;
}Directly fetches files from a directory with specified options, bypassing the CAFS integration layer.
/**
* Directly fetch files from a directory with specified options
* @param dir - The directory path to fetch from
* @param opts - Fetch configuration options
* @returns Promise resolving to fetch result with file index and metadata
*/
function fetchFromDir(
dir: string,
opts: FetchFromDirOptions
): Promise<FetchResult>;
type FetchFromDirOptions = Omit<DirectoryFetcherOptions, 'lockfileDir'> & CreateDirectoryFetcherOptions;interface FetchResult {
/** Always true for directory fetcher */
local: true;
/** Map of relative file paths to absolute file system paths */
filesIndex: Record<string, string>;
/** File statistics (only present when not using includeOnlyPackageFiles) */
filesStats?: Record<string, Stats | null>;
/** Always 'hardlink' for directory packages */
packageImportMethod: 'hardlink';
/** Package manifest (package.json content), may be undefined if no package.json exists */
manifest: DependencyManifest | undefined;
/** Whether the package requires build steps */
requiresBuild: boolean;
}interface DirectoryFetcherOptions {
/** Directory containing the lockfile (required) */
lockfileDir: string;
/** Whether to read package manifest (default: depends on implementation) */
readManifest?: boolean;
}
interface DirectoryResolution {
/** Resolution type identifier */
type: 'directory';
/** Relative path to the directory */
directory: string;
}
interface DependencyManifest {
/** Package name */
name: string;
/** Package version */
version: string;
/** Package dependencies */
dependencies?: Record<string, string>;
/** Development dependencies */
devDependencies?: Record<string, string>;
/** Package scripts */
scripts?: Record<string, string>;
/** Node.js engine requirements */
engines?: Record<string, string>;
/** Additional package.json fields */
[key: string]: any;
}// Uses Node.js built-in fs.Stats interface for file system information
import type { Stats } from 'fs';import { createDirectoryFetcher } from "@pnpm/directory-fetcher";
// Only include files that would be published to npm
const fetcher = createDirectoryFetcher({
includeOnlyPackageFiles: true
});
const result = await fetcher.directory(cafs, {
type: 'directory',
directory: './my-package'
}, {
lockfileDir: process.cwd()
});
// result.filesIndex only contains publishable files
// No result.filesStats propertyThe fetcher provides two symlink handling modes:
import { createDirectoryFetcher } from "@pnpm/directory-fetcher";
// Default behavior: preserve symlink paths
const defaultFetcher = createDirectoryFetcher();
// Resolve symlinks to their real paths
const resolvingFetcher = createDirectoryFetcher({
resolveSymlinks: true
});
const result = await resolvingFetcher.directory(cafs, {
type: 'directory',
directory: './symlinked-package'
}, {
lockfileDir: process.cwd()
});
// With resolveSymlinks: true
// - result.filesIndex contains real file paths, not symlink paths
// - Broken symlinks are gracefully skipped with debug logging
// - Uses fs.realpath() and fs.stat() for resolved paths
// With resolveSymlinks: false (default)
// - result.filesIndex contains original symlink paths
// - Uses fs.stat() directly on symlink paths
// - Broken symlinks still cause debug logging but preserve original pathsimport { fetchFromDir } from "@pnpm/directory-fetcher";
// Fetch directly without CAFS integration
const result = await fetchFromDir('/path/to/package', {
includeOnlyPackageFiles: false,
resolveSymlinks: true,
readManifest: true
});
// Full FetchResult with all files and statistics
console.log(result.filesIndex);
console.log(result.filesStats);
console.log(result.manifest?.name); // manifest may be undefinedThe directory fetcher gracefully handles directories that don't contain a package.json file:
import { createDirectoryFetcher } from "@pnpm/directory-fetcher";
const fetcher = createDirectoryFetcher();
// Fetch from a directory without package.json (e.g., Bit workspace components)
const result = await fetcher.directory(cafs, {
type: 'directory',
directory: './component-without-manifest'
}, {
lockfileDir: process.cwd(),
readManifest: true
});
// result.manifest will be undefined
console.log(result.manifest); // undefined
console.log(result.filesIndex); // Still contains all files
console.log(result.requiresBuild); // Determined without manifestThe directory fetcher includes robust error handling for common file system issues:
import { createDirectoryFetcher } from "@pnpm/directory-fetcher";
const fetcher = createDirectoryFetcher();
try {
const result = await fetcher.directory(cafs, {
type: 'directory',
directory: './nonexistent-package'
}, {
lockfileDir: process.cwd()
});
} catch (error) {
// Handle directory access errors, permission issues, etc.
if (error.code === 'ENOENT') {
console.error('Directory not found:', error.path);
} else if (error.code === 'EACCES') {
console.error('Permission denied:', error.path);
} else {
console.error('Failed to fetch directory:', error.message);
}
}Error Handling Behavior:
undefined manifest using safeReadProjectManifestOnly, fetch continuesDebug Logging:
The fetcher uses @pnpm/logger with category 'directory-fetcher' for debug information:
// Broken symlinks are logged as:
// { brokenSymlink: '/absolute/path/to/broken/symlink' }