Created
September 22, 2013 09:04
-
Star
(263)
You must be signed in to star a gist -
Fork
(45)
You must be signed in to fork a gist
-
-
Save kethinov/6658166 to your computer and use it in GitHub Desktop.
List all files in a directory in Node.js recursively in a synchronous fashion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// List all files in a directory in Node.js recursively in a synchronous fashion | |
var walkSync = function(dir, filelist) { | |
var fs = fs || require('fs'), | |
files = fs.readdirSync(dir); | |
filelist = filelist || []; | |
files.forEach(function(file) { | |
if (fs.statSync(dir + file).isDirectory()) { | |
filelist = walkSync(dir + file + '/', filelist); | |
} | |
else { | |
filelist.push(file); | |
} | |
}); | |
return filelist; | |
}; |
Hi, here's a modern version:
const fs = require('fs').promises; const path = require('path'); const walk = async (dir, filelist = []) => { const files = await fs.readdir(dir); for (file of files) { const filepath = path.join(dir, file); const stat = await fs.stat(filepath); if (stat.isDirectory()) { filelist = await walk(filepath, filelist); } else { filelist.push(file); } } return filelist; }
Here's what I used (needed import
)
import * as fs from 'fs/promises';
import path from 'path';
export async function directory_read(dir, filelist = []) {
const files = await fs.readdir(dir);
for (let file of files) {
const filepath = path.join(dir, file);
const stat = await fs.stat(filepath);
if (stat.isDirectory()) {
filelist = await directory_read(filepath, filelist);
} else {
filelist.push(file);
}
}
return filelist;
}
Node v20.1+
const filelist = await fs.readdir(dir, { recursive: true });
TypeScript version with filters:
import {readdirSync, statSync} from "node:fs";
import * as path from "node:path";
/**
* Options for scanning a directory.
*
* @template PathType - The type of the relative directory path.
* @interface DirectoryScanOptions
* @property {string | PathType} [baseDir=""] - The base directory for relative paths.
* @property {string[]} [fileExtensions=[]] - An array of file extensions to filter.
* @property {"all" | "dir" | "file"} [collectionType="all"] - Specifies what to collect:
* - "all": collect both files and directories
* - "dir": collect only directories
* - "file": collect only files
*/
interface DirectoryScanOptions<PathType extends string = ""> {
/**
* Optional relative directory path
*
* The base directory for relative paths.
*
* @default ""
*/
baseDir?: string | PathType;
/**
* Optional array of file extensions
*
* An array of file extensions to filter.
*
* @default []
*/
fileExtensions?: string[];
/**
* Optional collection type
*
* Specifies what to collect:
* - "all": collect both files and directories
* - "dir": collect only directories
* - "file": collect only files
*
* @default "all"
*/
collectionType?: "all" | "dir" | "file";
}
interface ScanDirectory {
/**
* Scans a directory and collects paths based on specified options.
*
* @template PathType - The type of the directory path.
* @param {PathType} targetDir - The path of the directory to scan.
* @returns {string[]} - An array of collected paths relative to the specified directory.
*
* @example
* // Example usage of scanDirectory function
* const collectedPaths = scanDirectory('/path/to/directory', {
* baseDir: '/path/to',
* fileExtensions: ['.js', '.ts'],
* collectionType: 'file'
* });
* console.log(collectedPaths); // Outputs an array of relative file paths
*/
<PathType extends string>(targetDir: PathType): string[];
/**
* Scans a directory and collects paths based on specified options.
*
* @template PathType - The type of the directory path.
* @param {PathType} targetDir - The path of the directory to scan.
* @param {DirectoryScanOptions<PathType>} [options] - Options for scanning the directory.
* @returns {string[]} - An array of collected paths relative to the specified directory.
*
* @example
* // Example usage of scanDirectory function
* const collectedPaths = scanDirectory('/path/to/directory', {
* baseDir: '/path/to',
* fileExtensions: ['.js', '.ts'],
* collectionType: 'file'
* });
* console.log(collectedPaths); // Outputs an array of relative file paths
*/
<PathType extends string>(targetDir: PathType, options: DirectoryScanOptions<PathType>): string[];
}
/**
* Scans a directory and collects paths based on specified options.
*
* @template PathType - The type of the directory path.
* @param {PathType} targetDir - The path of the directory to scan.
* @param {DirectoryScanOptions<PathType>} [options] - Options for scanning the directory.
* @returns {string[]} - An array of collected paths relative to the specified directory.
*
* @example
* // Example usage of scanDirectory function
* const collectedPaths = scanDirectory('/path/to/directory', {
* baseDir: '/path/to',
* fileExtensions: ['.js', '.ts'],
* collectionType: 'file'
* });
* console.log(collectedPaths); // Outputs an array of relative file paths
*/
export const scanDirectory: ScanDirectory = <PathType extends string>(
targetDir: PathType, // The directory path to scan
options?: DirectoryScanOptions<PathType> // Options for scanning a directory.
): string[] => {
const {
baseDir = "", // The base directory for relative paths
fileExtensions = [], // Default to an empty array if no extensions are provided
collectionType = "all" // Default to collecting all types
} = options || {};
// Type alias for the collection options
type CollectionArray = Array<typeof collectionType>;
// Array to hold the scanned paths
const collectedPaths: string[] = [];
// Read directory contents
const directoryContents = readdirSync(targetDir, {withFileTypes: true, recursive: true});
// Check if any extensions are provided
const hasExtensions = !!fileExtensions?.length;
// Iterate over each file/directory in the scanned directory
for (const directoryEntry of directoryContents) {
// Construct the full file path
const fullPath = path.join(directoryEntry.parentPath, directoryEntry.name);
// Get the file statistics
const fileStats = statSync(fullPath);
// Get the relative path
const relativePath = path.relative(baseDir, fullPath);
// Check if we should collect files
if ((["all", "file"] as CollectionArray).includes(collectionType) && fileStats.isFile()) {
// If extensions are specified, check if the file matches
if (hasExtensions && !fileExtensions.includes(path.extname(fullPath))) {
continue; // Skip this file if it doesn't match the extensions
}
// Add the relative file path to the collected array
collectedPaths.push(relativePath);
}
// Check if we should collect directories
else if ((["all", "dir"] as CollectionArray).includes(collectionType) && fileStats.isDirectory()) {
// Add the relative directory path to the collected array
collectedPaths.push(relativePath);
}
}
// Return the collected paths
return collectedPaths;
};
Vanilla JS version if you don't use TypeScript:
import {readdirSync, statSync} from "node:fs";
import * as path from "node:path";
/**
* Scans a directory and collects paths based on specified options.
*
* @template PathType - The type of the directory path.
* @param {PathType} targetDir - The path of the directory to scan.
* @param {DirectoryScanOptions<PathType>} [options] - Options for scanning the directory.
* @returns {string[]} - An array of collected paths relative to the specified directory.
*
* @example
* // Example usage of scanDirectory function
* const collectedPaths = scanDirectory('/path/to/directory', {
* baseDir: '/path/to',
* fileExtensions: ['.js', '.ts'],
* collectionType: 'file'
* });
* console.log(collectedPaths); // Outputs an array of relative file paths
*/
export const scanDirectory = (
targetDir, // The directory path to scan
options // Options for scanning a directory.
) => {
const {
baseDir = "", // The base directory for relative paths
fileExtensions = [], // Default to an empty array if no extensions are provided
collectionType = "all" // Default to collecting all types
} = options || {};
// Array to hold the scanned paths
/** @type {string[]} */
const collectedPaths = [];
// Read directory contents
/** @type {Dirent[]} */
const directoryContents = readdirSync(targetDir, {withFileTypes: true, recursive: true});
// Check if any extensions are provided
/** @type {boolean} */
const hasExtensions = !!fileExtensions?.length;
// Iterate over each file/directory in the scanned directory
for (const directoryEntry of directoryContents) {
// Construct the full file path
/** @type {string} */
const fullPath = path.join(directoryEntry.parentPath, directoryEntry.name);
// Get the file statistics
/** @type {Stats} */
const fileStats = statSync(fullPath);
// Get the relative path
/** @type {string} */
const relativePath = path.relative(baseDir, fullPath);
// Check if we should collect files
if (["all", "file"].includes(collectionType) && fileStats.isFile()) {
// If extensions are specified, check if the file matches
if (hasExtensions && !fileExtensions.includes(path.extname(fullPath))) {
continue; // Skip this file if it doesn't match the extensions
}
// Add the relative file path to the collected array
collectedPaths.push(relativePath);
}
// Check if we should collect directories
else if (["all", "dir"].includes(collectionType) && fileStats.isDirectory()) {
// Add the relative directory path to the collected array
collectedPaths.push(relativePath);
}
}
// Return the collected paths
return collectedPaths;
};
// JSODC types
/**
* Options for scanning a directory.
*
* @template PathType - The type of the relative directory path.
* @interface DirectoryScanOptions
* @property {string | PathType} [baseDir=""] - The base directory for relative paths.
* @property {string[]} [fileExtensions=[]] - An array of file extensions to filter.
* @property {"all" | "dir" | "file"} [collectionType="all"] - Specifies what to collect:
* - "all": collect both files and directories
* - "dir": collect only directories
* - "file": collect only files
*/
$ yarn add glob rxjs
/* --- */
import {Observable, of} from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import * as path from 'path';
import {glob, type GlobOptionsWithFileTypesUnset} from "glob";
const listFiles = (root: string, globMask = '*', ignoredFolders: string[] = []): Observable<string> => {
const ignore = ignoredFolders.map(x => path.join(root, x));
const options: GlobOptionsWithFileTypesUnset = {
cwd: root,
nodir: true,
ignore: ignore,
};
// do not use path.join as it uses upper slash instead of lower slash, and it does not work
return of(globMask).pipe(
mergeMap(x=> glob(x, options)),
mergeMap(x => x),
);
}
/* --- */
const topLevel = listFiles('/dev/sdb', '*.ts', []).subscribe(x => { console.log(x); });
const recurse = listFiles('/dev/sdb', '**/*.ts', []).subscribe(x => { console.log(x); });
I just want to say that every time someone comes up with another solution to this problem it makes my day :)
wow.. been receiving notifications for this thing for 9 years!.. it's the most consistent item in my dev career
There are only 3 hard problems in computer science... cache invalidation, naming things, off by one errors, and directory walking,
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@FerreiraRaphael kudos