fs-fixture
Version:
Easily create test fixtures at a temporary file-system path
301 lines (293 loc) • 10.6 kB
text/typescript
import { CopyOptions } from 'node:fs';
import fs from 'node:fs/promises';
/**
* A subset of `fs/promises` methods used by FsFixture.
* Compatible with Node.js `fs/promises`, `@platformatic/vfs`,
* `memfs`, and other fs-compatible implementations.
*
* Pass a custom implementation to `createFixture({ fs })`
* to use a virtual filesystem.
*/
type FsPromises = {
readFile: {
(path: string, options?: {
encoding?: null;
} | null): Promise<Buffer>;
(path: string, options: BufferEncoding | {
encoding: BufferEncoding;
}): Promise<string>;
};
writeFile(path: string, data: string | Buffer, options?: BufferEncoding | {
encoding?: BufferEncoding;
} | null): Promise<void>;
readdir: {
(path: string, options?: {
withFileTypes?: false;
}): Promise<string[]>;
(path: string, options: {
withFileTypes: true;
}): Promise<Array<{
name: string;
isFile(): boolean;
isDirectory(): boolean;
}>>;
};
mkdir(path: string, options?: {
recursive?: boolean;
}): Promise<string | undefined>;
rename(oldPath: string, newPath: string): Promise<void>;
access(path: string, mode?: number): Promise<void>;
rm?(path: string, options?: {
recursive?: boolean;
force?: boolean;
}): Promise<void>;
unlink?(path: string): Promise<void>;
rmdir?(path: string): Promise<void>;
symlink?(target: string, path: string, type?: string | null): Promise<void>;
cp?(source: string, destination: string, options?: {
recursive?: boolean;
}): Promise<void>;
mkdtemp?(prefix: string): Promise<string>;
};
declare class FsFixture {
/**
* Path to the fixture directory.
*/
readonly path: string;
readonly fs: FsPromises;
/**
* Create a Fixture instance from a path. Does not create the fixture directory.
*
* @param fixturePath - The path to the fixture directory
* @param fsApi - Optional fs/promises-compatible API. Defaults to real node:fs/promises.
*/
constructor(fixturePath: string, fsApi?: FsPromises);
/**
* Get the full path to a subpath in the fixture directory.
*
* @param subpaths - Path segments to join with the fixture directory
* @returns The absolute path to the subpath
*
* @example
* ```ts
* fixture.getPath('dir', 'file.txt')
* // => '/tmp/fs-fixture-123/dir/file.txt'
* ```
*/
getPath(...subpaths: string[]): string;
/**
* Check if the fixture exists. Pass in a subpath to check if it exists.
*
* @param subpath - Optional subpath to check within the fixture directory
* @returns Promise resolving to true if the path exists, false otherwise
*/
exists(subpath?: string): Promise<boolean>;
/**
* Delete the fixture directory or a subpath within it.
*
* @param subpath - Optional subpath to delete within the fixture directory.
* Defaults to deleting the entire fixture.
* @returns Promise that resolves when deletion is complete
*/
rm(subpath?: string): Promise<void>;
/**
* Copy a file or directory into the fixture directory.
*
* @param sourcePath - The source path to copy from
* @param destinationSubpath - Optional destination path within the fixture.
* If omitted, uses the basename of sourcePath.
* If ends with path separator, appends basename of sourcePath.
* @param options - Copy options (e.g., recursive, filter)
* @returns Promise that resolves when copy is complete
*/
cp(sourcePath: string, destinationSubpath?: string, options?: CopyOptions): Promise<void>;
/**
* Create a new folder in the fixture directory (including parent directories).
*
* @param folderPath - The folder path to create within the fixture
* @returns Promise that resolves when directory is created
*/
mkdir(folderPath: string): Promise<string | undefined>;
/**
* Move or rename a file or directory within the fixture.
* This is a wrapper around `node:fs/promises.rename`.
*
* @param sourcePath - The source path relative to the fixture root
* @param destinationPath - The destination path relative to the fixture root
* @returns Promise that resolves when the move is complete
*
* @example
* ```ts
* // Rename a file
* await fixture.mv('old-name.txt', 'new-name.txt')
*
* // Move a file into a directory
* await fixture.mv('file.txt', 'src/file.txt')
*
* // Rename a directory
* await fixture.mv('src', 'lib')
* ```
*/
mv(sourcePath: string, destinationPath: string): Promise<void>;
/**
* Read a file from the fixture directory.
*
* @param filePath - The file path within the fixture to read
* @param options - Optional encoding or read options.
* When encoding is specified, returns a string; otherwise returns a Buffer.
* @returns Promise resolving to file contents as string or Buffer
*/
readFile: typeof fs.readFile;
/**
* Read the contents of a directory in the fixture.
*
* @param directoryPath - The directory path within the fixture to read.
* Defaults to the fixture root when empty string is passed.
* @param options - Optional read directory options.
* Use `{ withFileTypes: true }` to get Dirent objects.
* @returns Promise resolving to array of file/directory names or Dirent objects
*/
readdir: typeof fs.readdir;
/**
* Create or overwrite a file in the fixture directory.
*
* @param filePath - The file path within the fixture to write
* @param data - The content to write (string or Buffer)
* @param options - Optional encoding or write options
* @returns Promise that resolves when file is written
*/
writeFile: typeof fs.writeFile;
/**
* Read and parse a JSON file from the fixture directory.
*
* @param filePath - The JSON file path within the fixture to read
* @returns Promise resolving to the parsed JSON content
*
* @example
* ```ts
* const data = await fixture.readJson<{ name: string }>('config.json')
* console.log(data.name) // Typed as string
* ```
*/
readJson<T = unknown>(filePath: string): Promise<T>;
/**
* Create or overwrite a JSON file in the fixture directory.
*
* @param filePath - The JSON file path within the fixture to write
* @param json - The data to serialize as JSON
* @param space - Number of spaces or string to use for indentation. Defaults to 2.
* @returns Promise that resolves when file is written
*
* @example
* ```ts
* // Default 2-space indentation
* await fixture.writeJson('config.json', { key: 'value' })
*
* // 4-space indentation
* await fixture.writeJson('config.json', { key: 'value' }, 4)
*
* // Tab indentation
* await fixture.writeJson('config.json', { key: 'value' }, '\t')
*
* // Minified (no formatting)
* await fixture.writeJson('config.json', { key: 'value' }, 0)
* ```
*/
writeJson(filePath: string, json: unknown, space?: string | number): Promise<void>;
/**
* Resource management cleanup
* https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html
*/
[Symbol.asyncDispose](): Promise<void>;
}
type FsFixtureType = FsFixture;
declare class PathBase {
readonly path: string;
constructor(path: string);
}
type SymlinkType = 'file' | 'dir' | 'junction';
declare class Symlink extends PathBase {
readonly target: string;
readonly type?: SymlinkType;
constructor(target: string, type?: SymlinkType, filePath?: string);
}
type ApiBase = {
fixturePath: string;
getPath(...subpaths: string[]): string;
symlink(targetPath: string,
/**
* Symlink type for Windows. Defaults to auto-detect by Node.
*/
type?: SymlinkType): Symlink;
};
type Api = ApiBase & {
filePath: string;
};
type FileTree = {
[path: string]: string | Buffer | FileTree | ((api: Api) => string | Buffer | Symlink);
};
type FilterFunction = CopyOptions['filter'];
type CreateFixtureOptions = {
/**
* The temporary directory to create the fixtures in.
* Defaults to `os.tmpdir()`.
*
* Accepts either a string path or a URL object.
*
* Tip: use `new URL('.', import.meta.url)` to the get the file's directory (not the file).
*/
tempDir?: string | URL;
/**
* Function to filter files to copy when using a template path.
* Return `true` to copy the item, `false` to ignore it.
*/
templateFilter?: FilterFunction;
/**
* Custom fs/promises-compatible API for fixture operations.
* Use this to create fixtures in a virtual filesystem instead of on disk.
*
* Required: readFile, writeFile, readdir (with withFileTypes),
* mkdir, rename, access.
* Optional: rm (or unlink + rmdir as fallback), symlink, cp, mkdtemp.
*
* @example
* ```ts
* import { create, MemoryProvider } from '@platformatic/vfs'
* const vfs = create(new MemoryProvider())
* const fixture = await createFixture({ 'file.txt': 'hi' }, { fs: vfs.promises })
* ```
*/
fs?: FsPromises;
};
/**
* Create a temporary test fixture directory.
*
* @param source - Optional source to create the fixture from:
* - If omitted, creates an empty fixture directory
* - If a string, copies the directory at that path to the fixture
* - If a FileTree object, creates files and directories from the object structure
* @param options - Optional configuration for fixture creation
* @returns Promise resolving to an FsFixture instance
*
* @example
* ```ts
* // Create empty fixture
* const fixture = await createFixture()
*
* // Create from object
* const fixture = await createFixture({
* 'file.txt': 'content',
* 'dir/nested.txt': 'nested content',
* 'binary.bin': Buffer.from('binary'),
* })
*
* // Create from template directory
* const fixture = await createFixture('./my-template')
*
* // Cleanup
* await fixture.rm()
* ```
*/
declare const createFixture: (source?: string | FileTree, options?: CreateFixtureOptions) => Promise<FsFixture>;
export { createFixture };
export type { CreateFixtureOptions, FileTree, FsFixtureType as FsFixture, FsPromises };