fst utility to create trees from file systems
This package is a utility to create file system trees.
This utility uses file system adapters to recursively read a directory, and create a tree from its contents.
This package is ESM only.
In Node.js (version 20+) with yarn:
yarn add @flex-development/fst-util-from-fsSee Git - Protocols | Yarn Β for details regarding installing from Git.
In Deno with esm.sh:
import { fromFileSystem } from 'https://esm.sh/@flex-development/fst-util-from-fs'In browsers with esm.sh:
<script type="module">
import { fromFileSystem } from 'https://esm.sh/@flex-development/fst-util-from-fs'
</script>import type {
File,
AnyParent as Parent,
Root
} from '@flex-development/fst'
import {
fromFileSystem,
type Dirent,
type FileSystem,
type Stats
} from '@flex-development/fst-util-from-fs'
import pathe from '@flex-development/pathe'
import { inspect } from '@flex-development/unist-util-inspect'
import { ok } from 'devlop'
import fs from 'node:fs'
import { size } from 'unist-util-size'
declare module '@flex-development/fst' {
interface File {
/**
* The size of the file.
*/
size?: bigint | number | undefined
}
}
declare module '@flex-development/fst-util-from-fs' {
interface Stats {
/**
* The size of the entry.
*/
size: bigint | number
}
}
/**
* A glob pattern matching directories to search
* and files to include in the tree.
*
* @const {string} pattern
*/
const pattern: string = 'src/**/**'
/**
* The file system tree.
*
* @const {Root} tree
*/
const tree: Root = await fromFileSystem({
extensions: '.mts',
filters: {
/**
* @this {void}
*
* @param {string} path
* The path to the directory, relative to `tree.path`
* @param {number | null | undefined} depth
* The current search depth
* @param {Dirent} dirent
* The dirent representing the file system entry
* @param {Parent} parent
* The parent node
* @param {Root} tree
* The file system tree
* @return {boolean}
* `true` if node for `path` should be added, `false` otherwise
*/
directory(
this: void,
path: string,
depth: number | null | undefined,
dirent: Dirent,
parent: Parent,
tree: Root
): boolean {
return pathe.matchesGlob(path, pattern, {
cwd: tree.path,
dot: false,
ignore: ['**/__mocks__/**', '**/__snapshots__/**', '**/__tests__/**'],
noglobstar: false
})
},
/**
* @this {void}
*
* @param {string} path
* The path to the file, relative to `tree.path`
* @param {number | null | undefined} depth
* The current search depth
* @param {Dirent} dirent
* The dirent representing the file system entry
* @param {Parent} parent
* The parent node
* @param {Root} tree
* The file system tree
* @return {boolean}
* `true` if node for `path` should be added, `false` otherwise
*/
file(
this: void,
path: string,
depth: number | null | undefined,
dirent: Dirent,
parent: Parent,
tree: Root
): boolean {
return pathe.matchesGlob(path, pattern, {
cwd: tree.path,
dot: true,
ignore: ['**/.DS_Store'],
noglobstar: false
})
}
},
fs: fs.promises,
handles: {
/**
* @async
*
* @this {void}
*
* @param {File} node
* The node representing the file
* @param {Dirent} dirent
* The dirent representing the file
* @param {Parent} parent
* The parent of `node`
* @param {Root} tree
* The current file system tree
* @param {Parent[]} ancestors
* The ancestors of `node`, with the last node being `parent`
* @param {FileSystem} fs
* The file system API
* @return {Promise<undefined>}
*/
async file(
this: void,
node: File,
dirent: Dirent,
parent: Parent,
tree: Root,
ancestors: Parent[],
fs: FileSystem
): Promise<undefined> {
/**
* The list of relative ancestor paths.
*
* @const {string[]} paths
*/
const paths: string[] = [...ancestors.slice(1), node].map(n => {
ok(n.type !== 'root', 'did not expect tree')
return n.name
})
/**
* Info about the file.
*
* @const {Stats} stats
*/
const stats: Stats = await fs.stat(pathe.join(tree.path, ...paths))
return node.size = stats.size, void node
}
}
})
console.log(inspect(tree))
console.dir(size(tree))
console.dir(size(tree, node => node.type === 'directory'))
console.dir(size(tree, node => node.type === 'file'))...yields
root[1]
β path: "/Users/lex/Projects/flex-development/fst-util-from-fs/"
ββ0 directory<src>[6]
ββ0 directory<interfaces>[16]
β ββ0 file<dirent.mts> null
β β size: 950
β ββ1 file<file-system-entries.mts> null
β β size: 559
β ββ2 file<file-system.mts> null
β β size: 643
β ββ3 file<filters.mts> null
β β size: 583
β ββ4 file<handles.mts> null
β β size: 640
β ββ5 file<index.mts> null
β β size: 1102
β ββ6 file<is-directory.mts> null
β β size: 345
β ββ7 file<is-file.mts> null
β β size: 315
β ββ8 file<is-symbolic-link.mts> null
β β size: 365
β ββ9 file<options.mts> null
β β size: 2052
β ββ10 file<readdir-dirent-options.mts> null
β β size: 744
β ββ11 file<readdir-options.mts> null
β β size: 594
β ββ12 file<readdir.mts> null
β β size: 798
β ββ13 file<realpath.mts> null
β β size: 743
β ββ14 file<stat.mts> null
β β size: 566
β ββ15 file<stats.mts> null
β size: 704
ββ1 directory<internal>[12]
β ββ0 file<chain-or-call.mts> null
β β size: 2656
β ββ1 file<combine-paths.mts> null
β β size: 1579
β ββ2 file<constant.mts> null
β β size: 413
β ββ3 file<empty-array.mts> null
β β size: 260
β ββ4 file<empty-file-system-entries.mts> null
β β size: 524
β ββ5 file<fs.browser.mts> null
β β size: 973
β ββ6 file<fs.d.mts> null
β β size: 241
β ββ7 file<fs.node.mts> null
β β size: 414
β ββ8 file<identity.mts> null
β β size: 351
β ββ9 file<is-promise.mts> null
β β size: 640
β ββ10 file<visit-directory.mts> null
β β size: 10232
β ββ11 file<with-trailing-slash.mts> null
β size: 1357
ββ2 directory<types>[10]
β ββ0 file<awaitable.mts> null
β β size: 269
β ββ1 file<extensions.mts> null
β β size: 323
β ββ2 file<filter.mts> null
β β size: 1006
β ββ3 file<get-file-system-entries.mts> null
β β size: 847
β ββ4 file<handle.mts> null
β β size: 1347
β ββ5 file<index.mts> null
β β size: 628
β ββ6 file<list.mts> null
β β size: 241
β ββ7 file<sort.mts> null
β β size: 455
β ββ8 file<to-visit-key.mts> null
β β size: 1083
β ββ9 file<visit-map.mts> null
β size: 321
ββ3 directory<utils>[2]
β ββ0 file<get-file-system-entries.mts> null
β β size: 5805
β ββ1 file<index.mts> null
β size: 157
ββ4 file<index.mts> null
β size: 189
ββ5 file<util.mts> null
size: 2055
47 # total number of nodes
5 # total number of directory nodes
42 # total number of file nodesThis package exports the identifiers listed below.
There is no default export.
Create a file system tree.
π Note: Returns a promise if one of the following methods returns a promise:
fs.realpath,options.getFileSystemEntries,options.handles.directory,options.handlers.file.
T(Awaitable<Root>) β the tree
options(Options|null|undefined, optional) β options for tree creation
(T) The file system tree
This package exports utilities from @flex-development/fst-util-from-fs/utils.
The utilities library exports the identifiers listed below.
There is no default export.
Get a record of accessible file system entries.
Entries are relative to parent.
π Note: Returns a promise if
fs.readdirreturns a promise, or any symbolic links are encountered andfs.statreturns a promise.
T(Awaitable<FileSystemEntries>) β the entries record
parent(URL|string|null|undefined) β the entry id of the parent directoryfs(FileSystem|null|undefined) β the file system api
(T) The file system entries record
This package is fully typed with TypeScript.
Create a union of T and T as a promise-like object (type).
type Awaitable<T> = PromiseLike<T> | TT(any) β the value
Information about a file system entry (interface).
This interface can be augmented to register custom methods and properties.
declare module '@flex-development/fst-util-from-fs' {
interface Dirent {
parentPath: string
}
}isDirectory(IsDirectory) β check if the entry is a directoryisFile(IsFile) β check if the entry is a fileisSymbolicLink(IsSymbolicLink) β check if the entry is a symbolic linkname(string) β the path to the entry, relative to its parent directory
Union of options used to filter files by extension (type).
type Extensions = List<string> | stringInformation about directories and files (interface).
directories(List<Dirent>) β the list of directoriesfiles(List<Dirent>) β the list of files
The file system API (interface).
readdir(Readdir) β read the entire contents of a directoryrealpath(Realpath) β compute a canonical pathname by resolving.,.., and symbolic linksstat(Stat) β get information about a file system entry
Determine if a node for path should be added to the tree (type).
type Filter = (
this: void,
path: string,
depth: number | null | undefined,
dirent: Dirent,
parent: AnyParent,
tree: Root
) => booleanpath(string) β the path to the file system entry, relative to its parent directorydepth(number|null|undefined) β the current search depthdirent(Dirent) β the dirent representing the file system entryparent(AnyParent) β the current parent nodetree(Root) β the file system tree
(boolean) true if node for path should be added, false otherwise
The filters used to determine if a node should be added to a tree (interface).
directory(Filter|null|undefined, optional) β determine if aDirectorynode should be added to the treefile(Filter|null|undefined, optional) β determine if aFilenode should be added to the tree
Get a file system entries record (type).
type GetFileSystemEntries<
T extends Awaitable<FileSystemEntries> = Awaitable<FileSystemEntries>
> = (
this: void,
parent: URL | string,
fs: FileSystem
) => TT(Awaitable<FileSystemEntries>) β the entries record
parent(URL|string) β the entry id of the parent directoryfs(FileSystem) β the file system api
(T) The file system entries record
Handle a node that has been added to the tree (type).
type Handle<
T extends Child = Child,
Result extends Awaitable<null | undefined | void> = Awaitable<null | undefined | void>
> = (
this: void,
node: T,
dirent: Dirent,
parent: AnyParent,
tree: Root,
ancestors: AnyParent[],
fs: FileSystem
) => ResultT(Child, optional) β the fst nodeResult(Awaitable<null | undefined | void>, optional) β the result of the handle
node(T) β the node representing the file system entrydirent(Dirent) β the dirent representing the file system entryparent(AnyParent) β the parent ofnodetree(Root) β the current file system treeancestors(AnyParent[]) β the ancestors ofnode, with the last node beingparentfs(FileSystem) β the file system api
(Result) Nothing
The callbacks to fire after a node is added to a tree (interface).
directory(Handle<Directory>|null|undefined, optional) β handle aDirectorynodefile(Handle<File>|null|undefined, optional) β handle aFilenode
Check if a file system entry is a directory (interface).
(): boolean(boolean) true if entry is directory, false otherwise
Check if a file system entry is a file (interface).
(): boolean(boolean) true if entry is file, false otherwise
Check if a file system entry is a symbolic link (interface).
(): boolean(boolean) true if entry is symbolic link, false otherwise
A list (type).
type List<T = unknown> = ReadonlySet<T> | readonly T[]T(any, optional) β the list item type
Options for creating a file system tree (interface).
depth(number|null|undefined, optional) β the maximum search depth (inclusive)π note: a search depth less than
0will produce an empty treeextensions(Extensions|null|undefined, optional) β the file extensions to filter matched files byπ note: this is alternative way to exclude files from the tree
filters(Filters|null|undefined, optional) β the filters used to determine if nodes should be added to the treefs(FileSystem|null|undefined, optional) β the file system adaptergetFileSystemEntries(GetFileSystemEntries|null|undefined, optional) β get a file system entries record- default:
getFileSystemEntries
- default:
handles(Handles|null|undefined, optional) β the callbacks to fire after a node is added to the treeroot(URL|string|null|undefined, optional) β the module id of the root directory- default:
pathe.cwd() + pathe.sep
- default:
sort(Sort|null|undefined, optional) β the child node sorter.
by default, nodes are sorted bytypeandnamevisitKey(ToVisitKey|null|undefined, optional) β generate a key for thevisiteddirectory map- default:
path => path
- default:
visited(VisitMap|null|undefined, optional) β the map indicating which directories have already been searched- default:
new Map()
- default:
Options for reading the contents of a directory (interface).
withFileTypes(true) β whether the result should be a content object list instead of just strings.
iftrue, the result will be a list ofDirectobjects, which provide methods likeisDirectory()andisFile()to get more information about a file system entry without additionalfs.stat()calls
Options for reading the contents of a directory (interface).
withFileTypes?(boolean|null|undefined) β whether the result should be a content object list instead of just strings.
iftrue, the result will be a list ofDirectobjects, which provide methods likeisDirectory()andisFile()to get more information about a file system entry without additionalfs.stat()calls
Read the entire contents of a directory (interface).
<T extends Awaitable<readonly Dirent[]>>(id: URL | string, options: ReaddirDirentOptions): TT(Awaitable<readonly Dirent[]>) β the directory contents
id(URL|string) β the entry idoptions(ReaddirDirentOptions) β read options
(T) The directory contents
Compute a canonical pathname by resolving ., .., and symbolic links (interface).
π Note: A canonical pathname is not necessarily unique. Hard links and bind mounts can expose an entity through many pathnames.
<T extends Awaitable<string>>(id: URL | string): TT(Awaitable<string>) β the canonical pathname
id(URL|string) β the entry id
(T) The canonical pathname
Decide how two nodes should be sorted (type).
type Sort = (this: void, a: Child, b: Child) => number(number) The comparison result
Get information about a file system entry (interface).
<T extends Awaitable<Stats>>(id: URL | string): TT(Awaitable<Stats>) β the entry info
id(URL|string) β the entry id
(T) The entry info
Information about a file system entry (interface).
This interface can be augmented to register custom methods and properties.
declare module '@flex-development/fst-util-from-fs' {
interface Stats {
size: bigint | number
}
}isDirectory(IsDirectory) β check if the entry is a directoryisFile(IsFile) β check if the entry is a file
Get a visit map key for a pathname (type).
type ToVisitKey<K extends WeakKey | string | null = WeakKey | string | null> = (
this: void,
path: string,
dir: string | null,
parent: AnyParent,
tree: Root,
options: Options
) => KK(WeakKey|string|null, optional) β the map key
path(string) β the canonical pathname (realpath) of the directory to visitdir(string|null) β the path to the directory to visit, relative totree.pathparent(AnyParent) β the current parent nodetree(Root) β the current file system treeoptions(Options) β options for tree creation
(K) The visited map key
Map indicating which directories have already been searched (type).
type VisitMap = Map<string | null, boolean> | WeakMap<WeakKey, boolean>The syntax tree is fst.
See CONTRIBUTING.md.
This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.