Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: SBoudrias/mem-fs-editor
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: a522ac377128558c903de0e3ebee3ada2a3af0af
Choose a base ref
...
head repository: SBoudrias/mem-fs-editor
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 19d0f5fe1353e2fdd36cd27a0e593b4099f3531f
Choose a head ref
Loading
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage/**/*.*
14 changes: 14 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "xo-space",
"env": {
"jest": true
},
"rules": {
"eqeqeq": [
2,
"allow-null"
],
"no-eq-null": 0,
"max-params": "off"
}
}
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Node.js CI

on: [push, pull_request]

jobs:
build:
name: Node ${{ matrix.node-version }}
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [14.x, 12.x]

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
sudo: false
language: node_js
node_js:
- 6
- 8
- node
cache:
yarn: true
- 10
- 12
after_script:
- cat ./coverage/lcov.info | coveralls
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
mem-fs-editor [![Build Status](https://api.travis-ci.org/SBoudrias/mem-fs-editor.svg?branch=master)](https://travis-ci.org/SBoudrias/mem-fs-editor) [![NPM version](https://badge.fury.io/js/mem-fs-editor.svg)](http://badge.fury.io/js/mem-fs-editor) [![Coverage Status](https://coveralls.io/repos/github/SBoudrias/mem-fs-editor/badge.svg)](https://coveralls.io/github/SBoudrias/mem-fs-editor)
=============
# mem-fs-editor [![Build Status](https://api.travis-ci.org/SBoudrias/mem-fs-editor.svg?branch=master)](https://travis-ci.org/SBoudrias/mem-fs-editor) [![NPM version](https://badge.fury.io/js/mem-fs-editor.svg)](http://badge.fury.io/js/mem-fs-editor) [![Coverage Status](https://coveralls.io/repos/github/SBoudrias/mem-fs-editor/badge.svg)](https://coveralls.io/github/SBoudrias/mem-fs-editor)

File edition helpers working on top of [mem-fs](https://github.com/SBoudrias/mem-fs)

Usage
-------------
## Usage

```js
var memFs = require('mem-fs');
var editor = require('mem-fs-editor');
var memFs = require("mem-fs");
var editor = require("mem-fs-editor");

var store = memFs.create();
var fs = editor.create(store);

fs.write('somefile.js', 'var a = 1;');
fs.write("somefile.js", "var a = 1;");
```

### `#read(filepath, [options])`
@@ -61,15 +59,19 @@ Optionally take the same JSON formatting arguments than `#writeJSON()`.

Delete a file or a directory.

`filePath` can also be a `glob`. If `filePath` is glob, you can optionally pass in an `options.globOptions` object to change its pattern matching behavior. The full list of options are being described [here](https://github.com/isaacs/node-glob#options). The `sync` flag is forced to be `true` in `globOptions`.
`filePath` can also be a `glob`. If `filePath` is glob, you can optionally pass in an `options.globOptions` object to change its pattern matching behavior. The full list of options are being described [here](https://github.com/mrmlnc/fast-glob#options-1). The `sync` flag is forced to be `true` in `globOptions`.

### `#copy(from, to, [options])`
### `#copy(from, to, [options], context[, templateOptions ])`

Copy a file from the `from` path to the `to` path.

Optionally, pass an `options.process` function (`process(contents)`) returning a string or a buffer who'll become the new file content. The process function will take a single contents argument who is the copied file contents as a `Buffer`.

`from` can be a glob pattern that'll be match against the file system. If that's the case, then `to` must be an output directory. For a globified `from`, you can optionally pass in an `options.globOptions` object to change its pattern matching behavior. The full list of options are being described [here](https://github.com/isaacs/node-glob#options). The `nodir` flag is forced to be `true` in `globOptions` to ensure a vinyl object representing each matching directory is marked as `deleted` in the `mem-fs` store.
`option.ignoreNoMatch` can be used to silence the error thrown if no files match the `from` pattern.

`from` can be a glob pattern that'll be match against the file system. If that's the case, then `to` must be an output directory. For a globified `from`, you can optionally pass in an `options.globOptions` object to change its pattern matching behavior. The full list of options are being described [here](https://github.com/mrmlnc/fast-glob#options-1). The `nodir` flag is forced to be `true` in `globOptions` to ensure a vinyl object representing each matching directory is marked as `deleted` in the `mem-fs` store.

Optionally, when `from` is a glob pattern, pass an `options.processDestinationPath` function (`processDestinationPath(destinationFile)`) returning a string who'll become the new file name.

### `#copyTpl(from, to, context[, templateOptions [, copyOptions]])`

@@ -86,6 +88,12 @@ Templates syntax looks like this:
<%- include('partial.ejs', { name: 'Simon' }) %>
```

Dir syntax looks like this:

```
/some/path/dir<%= value %>/...
```

Refer to the [ejs documentation](http://ejs.co/) for more details.

### `#move(from, to, [options])`
5 changes: 3 additions & 2 deletions __tests__/append.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const os = require('os');
const editor = require('..');
const memFs = require('mem-fs');

@@ -16,7 +17,7 @@ describe('#write()', () => {
fs.write('append.txt', 'a\n\n\n');
fs.append('append.txt', 'b');

expect(fs.read('append.txt')).toBe('a\nb');
expect(fs.read('append.txt')).toBe('a' + os.EOL + 'b');
});

it('allows specifying custom separator', () => {
@@ -30,6 +31,6 @@ describe('#write()', () => {
fs.write('append.txt', 'a\n\n');
fs.append('append.txt', 'b', {trimEnd: false});

expect(fs.read('append.txt')).toBe('a\n\n\nb');
expect(fs.read('append.txt')).toBe('a\n\n' + os.EOL + 'b');
});
});
1 change: 1 addition & 0 deletions __tests__/commit.js
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ describe('#commit()', () => {
while (i--) {
filesystem.writeFileSync(path.join(fixtureDir, 'file-' + i + '.txt'), 'foo');
}

fs.copy(fixtureDir + '/**', output);
rimraf(output, done);
});
42 changes: 39 additions & 3 deletions __tests__/copy-tpl.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const os = require('os');
const path = require('path');
const editor = require('..');
const memFs = require('mem-fs');
@@ -17,7 +18,7 @@ describe('#copyTpl()', () => {
const filepath = path.join(__dirname, 'fixtures/file-tpl.txt');
const newPath = '/new/path/file.txt';
fs.copyTpl(filepath, newPath, {name: 'new content'});
expect(fs.read(newPath)).toBe('new content\n');
expect(fs.read(newPath)).toBe('new content' + os.EOL);
});

it('allow setting custom template delimiters', function () {
@@ -26,14 +27,14 @@ describe('#copyTpl()', () => {
fs.copyTpl(filepath, newPath, {name: 'mustache'}, {
delimiter: '?'
});
expect(fs.read(newPath)).toBe('mustache\n');
expect(fs.read(newPath)).toBe('mustache' + os.EOL);
});

it('allow including partials', function () {
const filepath = path.join(__dirname, 'fixtures/file-tpl-include.txt');
const newPath = '/new/path/file.txt';
fs.copyTpl(filepath, newPath);
expect(fs.read(newPath)).toBe('partial\n\n');
expect(fs.read(newPath)).toBe('partial' + os.EOL + os.EOL);
});

it('allow including glob options', function () {
@@ -58,4 +59,39 @@ describe('#copyTpl()', () => {
fs.copyTpl(filepath, newPath);
expect(fs.read(newPath)).toBe(fs.read(filepath));
});

it('perform no substitution on binary files from memory file store', function () {
const filepath = path.join(__dirname, 'fixtures/file-binary.bin');
const pathCopied = path.resolve('/new/path/file-inmemory.bin');
const newPath = '/new/path/file.bin';
fs.copy(filepath, pathCopied);
fs.copyTpl(pathCopied, newPath);
expect(fs.read(newPath)).toBe(fs.read(filepath));
});

it('allow passing circular function context', function () {
const b = {};
const a = {name: 'new content', b};
b.a = a;
const filepath = path.join(__dirname, 'fixtures/file-circular.txt');
const newPath = '/new/path/file.txt';
fs.copyTpl(filepath, newPath, {}, {
context: {a}
});
expect(fs.read(newPath)).toBe('new content new content' + os.EOL);
});

it('removes ejs extension when globbing', function () {
const filepath = path.join(__dirname, 'fixtures/ejs');
const newPath = '/new/path/';
fs.copyTpl(filepath, newPath);
expect(fs.exists(path.join(newPath, 'file-ejs-extension.txt'))).toBeTruthy();
});

it('doens\'t removes ejs extension when not globbing', function () {
const filepath = path.join(__dirname, 'fixtures/ejs/file-ejs-extension.txt.ejs');
const newPath = '/new/path/file-ejs-extension.txt.ejs';
fs.copyTpl(filepath, newPath);
expect(fs.exists(newPath)).toBeTruthy();
});
});
68 changes: 44 additions & 24 deletions __tests__/copy.js
Original file line number Diff line number Diff line change
@@ -26,26 +26,28 @@ describe('#copy()', () => {
});

it('can copy directory not commited to disk', () => {
fs.write('/test/foo/file-a.txt', 'a');
fs.write('/test/foo/file-b.txt', 'b');
let sourceDir = path.join(__dirname, '../test/foo');
let destDir = path.join(__dirname, '../test/bar');
fs.write(path.join(sourceDir, 'file-a.txt'), 'a');
fs.write(path.join(sourceDir, 'file-b.txt'), 'b');

fs.copy('/test/foo/**', '/test/bar/');
fs.copy(path.join(sourceDir, '**'), destDir);

expect(fs.read('/test/bar/file-a.txt')).toBe('a');
expect(fs.read('/test/bar/file-b.txt')).toBe('b');
expect(fs.read(path.join(destDir, 'file-a.txt'))).toBe('a');
expect(fs.read(path.join(destDir, 'file-b.txt'))).toBe('b');
});

it('throws when trying to copy from a non-existing file', () => {
const filepath = path.join(__dirname, 'fixtures/does-not-exits');
const newPath = '/new/path/file.txt';
const newPath = path.join(__dirname, '../test/new/path/file.txt');
expect(fs.copy.bind(fs, filepath, newPath)).toThrow();
});

it('copy file and process contents', () => {
const filepath = path.join(__dirname, 'fixtures/file-a.txt');
const initialContents = fs.read(filepath);
const contents = 'some processed contents';
const newPath = '/new/path/file.txt';
const newPath = path.join(__dirname, '../test/new/path/file.txt');
fs.copy(filepath, newPath, {
process(contentsArg) {
expect(contentsArg).toBeInstanceOf(Buffer);
@@ -57,36 +59,54 @@ describe('#copy()', () => {
});

it('copy by directory', () => {
fs.copy(path.join(__dirname, '/fixtures'), '/output');
expect(fs.read('/output/file-a.txt')).toBe('foo\n');
expect(fs.read('/output/nested/file.txt')).toBe('nested\n');
let outputDir = path.join(__dirname, '../test/output');
fs.copy(path.join(__dirname, '/fixtures'), outputDir);
expect(fs.read(path.join(outputDir, 'file-a.txt'))).toBe('foo' + os.EOL);
expect(fs.read(path.join(outputDir, '/nested/file.txt'))).toBe('nested' + os.EOL);
});

it('copy by globbing', () => {
fs.copy(path.join(__dirname, '/fixtures/**'), '/output');
expect(fs.read('/output/file-a.txt')).toBe('foo\n');
expect(fs.read('/output/nested/file.txt')).toBe('nested\n');
let outputDir = path.join(__dirname, '../test/output');
fs.copy(path.join(__dirname, '/fixtures/**'), outputDir);
expect(fs.read(path.join(outputDir, 'file-a.txt'))).toBe('foo' + os.EOL);
expect(fs.read(path.join(outputDir, '/nested/file.txt'))).toBe('nested' + os.EOL);
});

it('copy by globbing multiple patterns', () => {
fs.copy([path.join(__dirname, '/fixtures/**'), '!**/*tpl*'], '/output');
expect(fs.read('/output/file-a.txt')).toBe('foo\n');
expect(fs.read('/output/nested/file.txt')).toBe('nested\n');
expect(fs.read.bind(fs, '/output/file-tpl.txt')).toThrow();
let outputDir = path.join(__dirname, '../test/output');
fs.copy([path.join(__dirname, '/fixtures/**'), '!**/*tpl*'], outputDir);
expect(fs.read(path.join(outputDir, 'file-a.txt'))).toBe('foo' + os.EOL);
expect(fs.read(path.join(outputDir, '/nested/file.txt'))).toBe('nested' + os.EOL);
expect(fs.read.bind(fs, path.join(outputDir, 'file-tpl.txt'))).toThrow();
});

it('copy files by globbing and process contents', () => {
let outputDir = path.join(__dirname, '../test/output');
const process = sinon.stub().returnsArg(0);
fs.copy(path.join(__dirname, '/fixtures/**'), '/output', {process});
sinon.assert.callCount(process, 8); // 7 total files under 'fixtures', not counting folders
expect(fs.read('/output/file-a.txt')).toBe('foo\n');
expect(fs.read('/output/nested/file.txt')).toBe('nested\n');
fs.copy(path.join(__dirname, '/fixtures/**'), outputDir, {process});
sinon.assert.callCount(process, 10); // 8 total files under 'fixtures', not counting folders
expect(fs.read(path.join(outputDir, 'file-a.txt'))).toBe('foo' + os.EOL);
expect(fs.read(path.join(outputDir, '/nested/file.txt'))).toBe('nested' + os.EOL);
});

it('accepts directory name with "."', () => {
fs.copy(path.join(__dirname, '/fixtures/**'), '/out.put');
expect(fs.read('/out.put/file-a.txt')).toBe('foo\n');
expect(fs.read('/out.put/nested/file.txt')).toBe('nested\n');
let outputDir = path.join(__dirname, '../test/out.put');
fs.copy(path.join(__dirname, '/fixtures/**'), outputDir);
expect(fs.read(path.join(outputDir, 'file-a.txt'))).toBe('foo' + os.EOL);
expect(fs.read(path.join(outputDir, '/nested/file.txt'))).toBe('nested' + os.EOL);
});

it('accepts template paths', () => {
let outputFile = path.join(__dirname, 'test/<%= category %>/file-a.txt');
fs.copy(
path.join(__dirname, '/fixtures/file-a.txt'),
outputFile,
{},
{category: 'foo'}
);
expect(
fs.read(path.join(__dirname, 'test/foo/file-a.txt'))
).toBe('foo' + os.EOL);
});

it('requires destination directory when globbing', () => {
Empty file.
1 change: 1 addition & 0 deletions __tests__/fixtures/file-circular.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= this.a.name %> <%= this.a.b.a.name %>
3 changes: 2 additions & 1 deletion __tests__/move.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const os = require('os');
const path = require('path');
const editor = require('..');
const memFs = require('mem-fs');
@@ -51,7 +52,7 @@ describe('#move()', () => {
fs.write(filepath, contents);
fs.move(fromdir, dirpath);

expect(fs.read(path.join(dirpath, 'file.txt'))).toBe('nested\n');
expect(fs.read(path.join(dirpath, 'file.txt'))).toBe('nested' + os.EOL);
expect(fs.read(filepath)).toBe(contents);
expect(fs.read.bind(fs, path.join(fromdir, 'file.txt'))).toThrow();
});
13 changes: 7 additions & 6 deletions __tests__/read.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const os = require('os');
const path = require('path');
const editor = require('..');
const memFs = require('mem-fs');
@@ -17,13 +18,13 @@ describe('#read()', () => {

it('read the content of a file', () => {
const content = fs.read(fileA);
expect(content).toBe('foo\n');
expect(content).toBe('foo' + os.EOL);
});

it('get the buffer content of a file', () => {
const content = fs.read(fileA, {raw: true});
expect(content).toBeInstanceOf(Buffer);
expect(content.toString()).toBe('foo\n');
expect(content.toString()).toBe('foo' + os.EOL);
});

it('throws if file does not exist', () => {
@@ -36,8 +37,8 @@ describe('#read()', () => {
});

it('returns defaults as String if file does not exist and defaults is provided', () => {
const content = fs.read('file-who-does-not-exist.txt', {defaults: 'foo\n'});
expect(content).toBe('foo\n');
const content = fs.read('file-who-does-not-exist.txt', {defaults: 'foo' + os.EOL});
expect(content).toBe('foo' + os.EOL);
});

it('returns defaults as String if file does not exist and defaults is provided as empty string', () => {
@@ -47,11 +48,11 @@ describe('#read()', () => {

it('returns defaults as Buffer if file does not exist and defaults is provided', () => {
const content = fs.read('file-who-does-not-exist.txt', {
defaults: Buffer.from('foo\n'),
defaults: Buffer.from('foo' + os.EOL),
raw: true
});
expect(content).toBeInstanceOf(Buffer);
expect(content.toString()).toBe('foo\n');
expect(content.toString()).toBe('foo' + os.EOL);
});

it('returns defaults if file is deleted', () => {
Loading