Skip to content

Instantly share code, notes, and snippets.

@drodsou
Last active April 11, 2024 16:06
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save drodsou/de2ba6291aea67ffc5bc4b52d8c32abd to your computer and use it in GitHub Desktop.
Save drodsou/de2ba6291aea67ffc5bc4b52d8c32abd to your computer and use it in GitHub Desktop.
Like writeFileSync but creating all folder paths if not exist
// -- updated in 2020/04/19 covering the issues in the comments to this point
// -- remember you also have things like `ensureDirSync` from https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir-sync.md
const fs = require('fs')
function writeFileSyncRecursive(filename, content, charset) {
// -- normalize path separator to '/' instead of path.sep,
// -- as / works in node for Windows as well, and mixed \\ and / can appear in the path
let filepath = filename.replace(/\\/g,'/');
// -- preparation to allow absolute paths as well
let root = '';
if (filepath[0] === '/') {
root = '/';
filepath = filepath.slice(1);
}
else if (filepath[1] === ':') {
root = filepath.slice(0,3); // c:\
filepath = filepath.slice(3);
}
// -- create folders all the way down
const folders = filepath.split('/').slice(0, -1); // remove last item, file
folders.reduce(
(acc, folder) => {
const folderPath = acc + folder + '/';
if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath);
}
return folderPath
},
root // first 'acc', important
);
// -- write file
fs.writeFileSync(root + filepath, content, charset);
}
// -- Tests done that work properly:
// -- both in WSL and CMD
// writeFileSyncRecursive('data.json', '{}', 'utf8');
// writeFileSyncRecursive('public/new-user/data.json', '{}', 'utf8');
// writeFileSyncRecursive('public\\new-user\\data.json', '{}', 'utf8');
// writeFileSyncRecursive('./public/new-user/data.json', '{}', 'utf8');
// writeFileSyncRecursive('.\\public\\new-user\\data.json', '{}', 'utf8');
// writeFileSyncRecursive('/tmp/public\\new-user\\data.json', '{}', 'utf8');
// -- only in WSL (in CMD creates a 'C:\c' folder)
// writeFileSyncRecursive('/c/tmp/writefilesync/public/new-user/data.json', '{}', 'utf8');
// -- only in CMD (in WSL throws error no such file or directory c:/tmp)
// writeFileSyncRecursive('c:\\tmp\\writefilesync\\public\\new-user\\data.json', '{}', 'utf8');
// writeFileSyncRecursive('c:/tmp/writefilesync/public/new-user/data.json', '{}', 'utf8');
/*
// former implementation that received comments until 2020/04/19
// fixed missing second parameter in reduce (irkreja comment)
// works well only with relative paths
// asumes paths passed with / separator even in windows
function writeFileSyncRecursive (filename, content, charset) {
// create folder path if not exists
filename.split('/').slice(0,-1).reduce( (last, folder)=>{
let folderPath = last ? (last + '/' + folder) : folder
if (!fs.existsSync(folderPath)) fs.mkdirSync(folderPath)
return folderPath
},''); // fixed missing second parameter of reduce
fs.writeFileSync(filename, content, charset)
}
*/
@StJohn3D
Copy link

Thanks! Works great, saved me some time :)

@shig2k1
Copy link

shig2k1 commented Dec 17, 2018

top banana!

@visualjerk
Copy link

visualjerk commented Jun 14, 2019

Thanks for this! To make this platform agnostic, you could use path.sep, instead of hardcoding the separator:

const path = require('path')
const fs = require('fs')

function writeFileSyncRecursive(filename, content, charset) {
  const folders = filename.split(path.sep).slice(0, -1)
  if (folders.length) {
    // create folder path if it doesn't exist
    folders.reduce((last, folder) => {
      const folderPath = last ? last + path.sep + folder : folder
      if (!fs.existsSync(folderPath)) {
        fs.mkdirSync(folderPath)
      }
      return folderPath
    })
  }
  fs.writeFileSync(filename, content, charset)
}

@ashwanth1109
Copy link

Thanks. This worked great :)

@irkreja
Copy link

irkreja commented Apr 17, 2020

writeFileSyncRecursive(`public/new-users/data.json`, content);

this thrown error if public directory does not exist.
Error: ENOENT: no such file or directory, mkdir 'public/new-users'

@drodsou
Copy link
Author

drodsou commented Apr 19, 2020

Thanks all for the unexpected comments.
Due to this attention I've updated the code to work more reliably in more cases.

@safonoi
Copy link

safonoi commented Sep 1, 2020

const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const { promisify } = require('util');

const writeFile = promisify(fs.writeFile);
const mkdir = promisify(fs.mkdir);
const exists = promisify(fs.exists);

async function writeFileRecursive(filename, content, charset = 'UTF-8') {
  const filePathParts = _.split(filename, path.sep);

  if (_.size(filePathParts) > 1) {
    const folderPath = _.join(_.slice(filePathParts, 0, _.size(filePathParts) - 1), path.sep);
    const folderExists = await exists(folderPath);

    if (!folderExists) {
      await mkdir(folderPath, { recursive: true });
    }
  }

  return writeFile(filename, content, charset);
}

@benchesh
Copy link

benchesh commented Apr 1, 2021

This is exactly what I was after. Good stuff.

@juanmendes
Copy link

I'd consider just calling fs.mkdirSync(argv.outputDirectory, {recursive: true}); first instead (or the async version)

@ctf0
Copy link

ctf0 commented Apr 19, 2022

something simpler could be

function writeFileSyncRecursive(filename, content = '') {
    fs.mkdirSync(path.dirname(filename), {recursive: true})
    fs.writeFileSync(filename, content)
}

@Hasankanso
Copy link

Hasankanso commented Sep 13, 2022

something simpler could be

function writeFileSyncRecursive(filename, content = '') {
    fs.mkdirSync(path.dirname(filename), {recursive: true})
    fs.writeFileSync(filename, content)
}

this will create the file name as folder, for example if the filename variable is "c:/users/someone/folder/config.js", config.js will be created as folder, but I agree we can start something from mkdir

@aquacash5
Copy link

something simpler could be

function writeFileSyncRecursive(filename, content = '') {
    fs.mkdirSync(path.dirname(filename), {recursive: true})
    fs.writeFileSync(filename, content)
}

this will create the file name as folder, for example if the filename variable is "c:/users/someone/folder/config.js", config.js will be created as folder, but I agree we can start something from mkdir

path.dirname strips the file name off of the path before creating the folders. The function works as intended.

ex.

// This returns "c:/users/someone/folder"
path.dirname("c:/users/someone/folder/config.js")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment