npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fs-syncer

v0.5.3

Published

A helper to recursively read and write text files to a specified directory

Downloads

1,503

Readme

fs-syncer

A helper to recursively read and write text files to a specified directory.

CI npm version

The idea

It's a pain to write tests for tools that interact with the filesystem. It would be useful to write assertions that look something like:

expect(someDirectory.read()).toEqual({
  'file1.txt': 'some info',
  'file2.log': 'something logged',
  nested: {
    sub: {
      directory: {
        'deeply-nested-file.sql': 'SELECT * FROM abc'
      },
    },
  },
})

Similarly, as part of test setup, you might want to write several files, e.g.:

write({
  migrations: {
    'migration1.sql': 'create table one(id text)',
    'migration2.sql': 'create table two(id text)',
    down: {
      'migration1.sql': 'drop table one',
      'migration2.sql': 'drop table two',
    },
  },
})

The problem is that usually, you have to write a recursive directory-walker function, an object-to-filepath converter function, a nested-object-getter-function and a few more functions that tie them all together.

Then, if you have the energy, you should also write a function that cleans up any extraneous files after tests have been run. Or, you can pull in several dependencies that do some of these things for you, then write some functions that tie them together.

Now, you can just use fs-syncer, which does all of the above. Here's the API:

import {fsSyncer} from 'fs-syncer'

const syncer = fsSyncer(__dirname + '/migrations', {
  'migration1.sql': 'create table one(id text)',
  'migration2.sql': 'create table two(id text)',
  down: {
    'migration1.sql': 'drop table one',
    'migration2.sql': 'drop table two',
  },
})

syncer.sync() // replaces all content in `./migrations` with what's described in the target state

syncer.read() // returns the filesystem state as an object, in the same format as the target state

// write a file that's not in part of the target state
require('fs').writeFileSync(__dirname + '/migrations/extraneous.txt', 'abc', 'utf8')

syncer.read() // includes `extraneous.txt: 'abc'`

syncer.sync() // 'extraneous.txt' will now have been removed

syncer.write() // like `syncer.sync()`, but doesn't remove extraneous files

Usage with vitest or jest

⚠️⚠️⚠️ This feature is new and experimental - if you try it out, be aware that the API is in flux. Feedback is welcome! ⚠️⚠️⚠️

If you happen to want to use this in a jest test, there's an opinionated helper which allows you to avoid supplying a baseDir parameter.

Let's say you want to test a file modification tool, which appends // comments to all the files it finds under a certain directory, and also creates a log file:

import {testFixture} from 'fs-syncer'

import {fileModificationToolThatYouWantToTest} from '../src/your-library'

test('files are modified', async () => {
  const fixture = testFixture({
    expect,
    targetState: {
      'file1.txt': 'hello I am a file',
      nested: {
        'file2.txt': 'I am also a file',
      }
    }
  })

  fixture.sync()

  await fileModificationToolThatYouWantToTest.run({
    directory: fixture.baseDir,
    logFile: 'abc.log',
  })

  expect(fixture.yaml()).toMatchInlineSnapshot()
})

fixture.yaml() is a helper that returns a yaml string representing the file tree. It's intended to be human-readable and familiar, and should not be relied on to be valid yaml, it's mostly for test snapshots.

Let's assume the test file containing this test is called my-test-file.test.ts. When run, the above test will generate a directory fixtures/my-test-file.test.ts/files-are-modified next to the test file. The directory structure described in targetState will be created inside that folder. The test above might end up looking something like when run:

import {testFixture} from 'fs-syncer'

import {fileModificationToolThatYouWantToTest} from '../src/your-library'

test('files are modified', async () => {
  const fixture = testFixture({
    expect,
    targetState: {
      'file1.txt': 'hello I am a file',
      nested: {
        'file2.txt': 'I am also a file',
      }
    }
  })

  fixture.sync()

  await fileModificationToolThatYouWantToTest.run({
    directory: fixture.baseDir,
    logFile: 'abc.log',
  })

  expect(fixture.yaml()).toMatchInlineSnapshot(
    `"---
    abc.log: |-
      added content to file1.txt
      added content to nested/file2.txt
    file1.txt: |-
      hello I am a file

      // this content was auto-generated by the tool
    nested:
      file2.txt: |-
        hello I am a file

        // this content was auto-generated by the tool
    "`
  )
})

Not supported (right now)

  • File content other than text, e.g. Buffers. The library assumes you are solely dealing with utf8 strings.
  • Any performance optimisations - you will probably have a bad time if you try to use it to read or write a very large number of files.
  • Any custom symlink behaviour.

Comparison with mock-fs

This isn't a mocking library. There's no magic under the hood, it just calls fs.readFileSync, fs.writeFileSync and fs.mkdirSync directly. Which means you can use it anywhere - it could even be a runtime dependency as a wrapper for the fs module. And using it doesn't have any weird side-effects like breaking jest snapshot testing. Not being a mocking library means you could use it in combination with mock-fs, if you really wanted.