-
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 | |
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; | |
}; |
mmh Carefull with globby ;)
Here is my way (with typescript):
import fs from "fs/promises";
import path from "path";
export default async function walk(directory: string) {
let fileList: string[] = [];
const files = await fs.readdir(directory);
for (const file of files) {
const p = path.join(directory, file);
if ((await fs.stat(p)).isDirectory()) {
fileList = [...fileList, ...(await walk(p))];
} else {
fileList.push(p);
}
}
return fileList;
}
Alternative solution in Just two lines, Just for fun and learning
const path = ''; // 👈 path to your location
const list = require("child_process").execSync(`cd ${path} && ls -R`).toString().split(`\n`);
Be careful, this solution use a bash script and it works fine in MACOS, for windows or linux could be different
What I use is based on @vidul-nikolaev-petrov version, with a callback argument to do something with files:
const walkSync = (dir, callback) => fs.lstatSync(dir).isDirectory()
? fs.readdirSync(dir).map(f => walkSync(path.join(dir, f), callback))
: callback(dir);
// Note: call to flat at the end to have one array with every paths
const svgs = walkSync(path, fileToBase64).flat()
@FerreiraRaphael kudos
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,
@gemyero Great solution, thanks!