NodeJS - Copy and Rename all contents in existing directory recursively - node.js

I have a directory with folders and files within. I want to copy the entire directory with all its contents to a different location while renaming all the files to something more meaningful. I want to use nodejs to complete this series of operations. What is an easy way to do it, other than moving it one by one and renaming it one by one?
Thanks.
-- Thanks for the comment! So here is an example directory that I have in mind:
-MyFridge
- MyFood.txt
- MyApple.txt
- MyOrange.txt
- ...
- MyDrinks
- MySoda
- MyDietCoke.txt
- MyMilk.txt
- ...
- MyDesserts
- MyIce
...
I want to replace "My" with "Tom," for instance, and I also would like to rename "My" to Tom in all the text files. I am able to copy the directory to a different location using node-fs-extra, but I am having a hard time with renaming the file names.

Define your own tools
const fs = require('fs');
const path = require('path');
function renameFilesRecursive(dir, from, to) {
fs.readdirSync(dir).forEach(it => {
const itsPath = path.resolve(dir, it);
const itsStat = fs.statSync(itsPath);
if (itsPath.search(from) > -1) {
fs.renameSync(itsPath, itsPath.replace(from, to))
}
if (itsStat.isDirectory()) {
renameFilesRecursive(itsPath.replace(from, to), from, to)
}
})
}
Usage
const dir = path.resolve(__dirname, 'src/app');
renameFilesRecursive(dir, /^My/, 'Tom');
renameFilesRecursive(dir, /\.txt$/, '.class');

fs-jetpack has a pretty nice API to deal with problems like that...
const jetpack = require("fs-jetpack");
// Create two fs-jetpack contexts that point
// to source and destination directories.
const src = jetpack.cwd("path/to/source/folder");
const dst = jetpack.cwd("path/to/destination");
// List all files (recursively) in the source directory.
src.find().forEach(path => {
const content = src.read(path, "buffer");
// Transform the path however you need...
const transformedPath = path.replace("My", "Tom");
// Write the file content under new name in the destination directory.
dst.write(transformedPath, content);
});

Related

How can I use Node.js fs to sort a set of folders according to their created date?

I'm building a node.js application in which I need to read all the folders in a parent folder and display their names in the order they were created on the page. Here is what I have so far:
function getMixFolders() {
const { readdirSync } = require('fs');
const folderInfo = readdirSync('./shahspace.com/music mixes/')
.filter(item => item.isDirectory() && item.name !== 'views');
return folderInfo.map(folder => folder.name);
}
As you can see, I haven't implemented sorting. This is because readdirSync doesn't return the information I need. The only things it returns are the name of the folder and something called the Symbol(type) (which seems to indicate whether its a folder or file).
Is there another method for getting more details about the folders I'm reading from the parent folder? Specifically the created date?
There is no super efficient way in nodejs to get a directory listing and get statistics on each item (such as createDate). Instead, you have to distill the listing down to the files/folders you're interested in and then call fs.statSync() (or one of the similar variants) on each one to get that info. Here's a working version that looks like it does what you want:
Get directory list using the {withFileTypes: true} option
Filter to just folders
Ignore any folders named "views"
Get createDate of each folder
Sort the result by that createDate in ascending order (oldest folders first)
This code can be run as it's own program to test:
const fs = require('fs');
const path = require('path');
const mixPath = './shahspace.com/music mixes/';
function getMixFolders() {
const folderInfo = fs.readdirSync(mixPath, { withFileTypes: true })
.filter(item => item.isDirectory() && item.name !== 'views')
.map(folder => {
const fullFolderPath = path.join(path.resolve(mixPath), folder.name);
const stats = fs.statSync(fullFolderPath);
return { path: fullFolderPath, ctimeMs: stats.ctimeMs }
}).sort((a, b) => {
return a.ctimeMs - b.ctimeMs;
});
return folderInfo;
}
let result = getMixFolders();
console.log(result);
If you wanted the final array to be only the folder names without the createDates you could add one more .map() to transform the final result.

How to delete all files and subdirectories in a directory with Node.js

I am working with node.js and need to empty a folder. I read a lot of deleting files or folders. But I didn't find answers, how to delete all files AND folders in my folder Test, without deleting my folder Test` itself.
I try to find a solution with fs or extra-fs. Happy for some help!
EDIT 1: Hey #Harald, you should use the del library that #ziishaned posted above. Because it's much more clean and scalable. And use my answer to learn how it works under the hood :)
EDIT: 2 (Dec 26 2021): I didn't know that there is a fs method named fs.rm that you can use to accomplish the task with just one line of code.
fs.rm(path_to_delete, { recursive: true }, callback)
// or use the synchronous version
fs.rmSync(path_to_delete, { recursive: true })
The above code is analogous to the linux shell command: rm -r path_to_delete.
We use fs.unlink and fs.rmdir to remove files and empty directories respectively. To check if a path represents a directory we can use fs.stat().
So we've to list all the contents in your test directory and remove them one by one.
By the way, I'll be using the synchronous version of fs methods mentioned above (e.g., fs.readdirSync instead of fs.readdir) to make my code simple. But if you're writing a production application then you should use asynchronous version of all the fs methods. I leave it up to you to read the docs here Node.js v14.18.1 File System documentation.
const fs = require("fs");
const path = require("path");
const DIR_TO_CLEAR = "./trash";
emptyDir(DIR_TO_CLEAR);
function emptyDir(dirPath) {
const dirContents = fs.readdirSync(dirPath); // List dir content
for (const fileOrDirPath of dirContents) {
try {
// Get Full path
const fullPath = path.join(dirPath, fileOrDirPath);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
// It's a sub directory
if (fs.readdirSync(fullPath).length) emptyDir(fullPath);
// If the dir is not empty then remove it's contents too(recursively)
fs.rmdirSync(fullPath);
} else fs.unlinkSync(fullPath); // It's a file
} catch (ex) {
console.error(ex.message);
}
}
}
Feel free to ask me if you don't understand anything in the code above :)
You can use del package to delete files and folder within a directory recursively without deleting the parent directory:
Install the required dependency:
npm install del
Use below code to delete subdirectories or files within Test directory without deleting Test directory itself:
const del = require("del");
del.sync(['Test/**', '!Test']);

Setting path of fs.appendFileSync?

How do you set the path of fs.appendFileSync? For example, I want to make a file in folder "X" but my code is in the folder above. How would I create the file in another folder than the one my code is in(the file is made in the same folder as the source code).
I've read the documentation but I don't understand how I would be able to specify where to create the file.
The variable __dirname represents the directory that the currently running script is located in. So, if you wanted to put a file in a sub-directory below that (let's say it is in a variable called someDir), then you would do that like this:
const path = require('path');
let pathToFile = path.join(__dirname, someDir, "filename.txt");
fs.appendFileSync(pathToFile, dataToWrite);
If that directory doesn't yet exist and you need to create it, then you could use fs.mkDirSync() to create i.
const path = require('path');
// make sure sub-directory is created
let dirForFile = path.join(__dirname, someDir);
if (!fs.existsSync(dirForFile) {
fs.mkDirSync(dirForFile);
}
// append data to the file
let pathToFile = path.join(dirForFile, "filename.txt");
fs.appendFileSync(pathToFile, dataToWrite);

readdirSync cannot read encrypted home folder when accessing it directly

const fs = require("fs")
//const HOW = "/home/test/everything"
// const HOW = "/home/test/"
// This one fails. My home is encrypted and it cannot read directories, it gets the .Private file. I want to read files and directories in my home folder. But can't.
const HOW = "/home/test/folder/"
// This one works for some reason. It lists all the directories in the folder.
// const HOW = "folder"
// This one works as well
var list = walk(HOW)
console.log(list)
// How do I get contents of /home/test (which happens to be my home folder).
// I'm both root and "test" user of the computer.
I'd like to have walk() work on /home/test/.
The code that fails:
var walk = function(dir) {
var results = []
var list = fs.readdirSync(dir)
list.forEach(function(file) {
file = dir + '/' + file
var stat = fs.statSync(file)
if (stat && stat.isDirectory()) results = results.concat(walk(file))
else results.push(file)
})
return results
}
The exact line causing it (stack trace): var stat = fs.statSync(file)
The error is:
Error: ENOENT: no such file or directory, stat '/home/test/.Private/###############################################################'
Where # is an amount of letters whose importance to safety is unknown to me.
Node.js doesn't have a problem addressing any folder contained within my home folder, but cannot address the home folder itself. Neither my own account nor root account can get access to it.
I think you are adding an unnecessary /. Try changing
const HOW = "/home/test/folder/"
to
const HOW = "/home/test/folder"

mem-fs-editor: How to copy relative symlinks?

I've got a directory which contains a lot of files and one (or more) symlinkname -> . symlink. I want to copy the contents of the entire directory to a new location. the following code copies everything just fine, though it skips the symlink. Adding globOptions.follow = true only makes it loop indefinitely, which makes sense since it will try to dereference it. How can I just make it copy all the content + symlinks without trying to follow them?
this.fs.copy(
this.destinationPath() + '/**',
this.destinationPath('build/html'),
{
globOptions: {
follow: true // This will make the copy loop infinitely, which makes sense.
}
}
});
After finding out that Yeoman is avoiding bad UX by excluding support for symlinks (see Simon Boudrias' comment), I knew I had to work around this issue. I did the following workaround, please note that this should only be applied if you're unable to avoid symlinks like I am.
var fs = require('fs');
// Find out if there are symlinks
var files = fs.readdirSync(this.destinationPath());
for (var i = 0; i < files.length; i++) {
var path = this.destinationPath(files[i]),
stats = fs.lstatSync(path);
if (stats.isSymbolicLink()) {
// Find the target of the symlink and make an identical link into the new location
var link = fs.readlinkSync(path);
fs.symlinkSync(link, this.destinationPath('build/html/' + files[i]));
}
}

Resources