Skip to content

Commit

Permalink
use pipeline from mem-fs@4 (#229)
Browse files Browse the repository at this point in the history
* use pipeline from mem-fs@4

* bump to mem-fs@4.0.0
  • Loading branch information
mshima authored Oct 15, 2023
1 parent 5518e6c commit e5f563a
Show file tree
Hide file tree
Showing 12 changed files with 653 additions and 532 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [18.x, 16.x]
node-version: [18.x, 20.x]
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Copy the `from` file and, if it is not a binary file, parse its content as an [e

You can optionally pass a `templateOptions` object. `mem-fs-editor` automatically setup the filename option so you can easily use partials.

You can also optionally pass a `copyOptions` object (see [copy() documentation for more details](#copyfrom-to-options)).
You can also optionally pass a `copyOptions` object (see [copy() documentation for more details](#copyfrom-to-options-context-templateoptions-)).

Templates syntax looks like this:

Expand Down Expand Up @@ -139,14 +139,17 @@ Move/rename a file from the `from` path to the `to` path.

Returns `true` if a file exists. Returns `false` if the file is not found or deleted.

### `#commit([filters,] [stream,])`
### `#commit([options,] [...transforms])`

Persist every changes made to files in the mem-fs store to disk.
Pass stored files through a pipeline and persist every changes made to files in the mem-fs store to disk.

If provided, `filters` is an array of TransformStream to be applied on a stream of vinyl files (like gulp plugins).
If provided, `stream` is a stream of vinyl files.
If provided, `options` is the pipeline options.
By default only modified files are passed through the pipeline.
Pass a custom filter `options.filter` to customize files passed through the pipeline.
If provided, `...transforms` is a vararg of TransformStream to be applied on a stream of vinyl files (like gulp plugins).
`commitTransform` is appended to `transforms` and persists modified files to disk, non modified files are passed through.

returns promise that is resolved once the files are updated on disk.
returns promise that is resolved once the pipeline is finished.

### `#dump([cwd,] [filter])`

Expand Down
26 changes: 11 additions & 15 deletions __tests__/commit-file-async.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import path from 'path';
import { create as createMemFs } from 'mem-fs';
import sinon from 'sinon';
import { type MemFsEditor, type MemFsEditorFile, create } from '../src/index.js';
import commitFileAsync from '../src/actions/commit-file-async.js';

const rmSync = filesystem.rmSync || filesystem.rmdirSync;

Expand Down Expand Up @@ -48,22 +49,17 @@ describe('#commitFileAsync()', () => {
});

it('writes a modified file to disk', async () => {
await fs.commitFileAsync(store.get(filename));
await commitFileAsync(store.get(filename));
expect(filesystem.readFileSync(filename).toString()).toEqual('foo');
});

it('adds non existing file to store', async () => {
await fs.commitFileAsync(newFile);
expect(store.add.callCount).toEqual(2);
});

it('writes non existing file to disk', async () => {
await fs.commitFileAsync(newFile);
await commitFileAsync(newFile);
expect(filesystem.existsSync(filenameNew)).toBe(true);
});

it("doesn't commit an unmodified file", async () => {
await fs.commitFileAsync({
await commitFileAsync({
...newFile,
state: undefined,
});
Expand All @@ -72,27 +68,27 @@ describe('#commitFileAsync()', () => {

it('throws if the file is a directory', async () => {
filesystem.mkdirSync(filenameNew, { recursive: true });
await expect(fs.commitFileAsync(newFile)).rejects.toThrow();
await expect(commitFileAsync(newFile)).rejects.toThrow();
});

it('throws if the directory is a file', async () => {
filesystem.mkdirSync(outputRoot, { recursive: true });
filesystem.writeFileSync(path.dirname(filenameNew), 'foo');
await expect(fs.commitFileAsync(newFile)).rejects.toThrow();
await expect(commitFileAsync(newFile)).rejects.toThrow();
});

it('deletes a file', async () => {
filesystem.mkdirSync(path.dirname(filenameNew), { recursive: true });
filesystem.writeFileSync(filenameNew, 'foo');
await fs.commitFileAsync({
await commitFileAsync({
...newFile,
state: 'deleted',
});
expect(filesystem.existsSync(filenameNew)).toBe(false);
});

it('sets file permission', async () => {
await fs.commitFileAsync({
await commitFileAsync({
...newFile,
stat: { mode: READ_ONLY_MODE },
});
Expand All @@ -101,12 +97,12 @@ describe('#commitFileAsync()', () => {
});

it('updates file permission', async () => {
await fs.commitFileAsync({
await commitFileAsync({
...newFile,
stat: { mode: READ_WRITE_MODE },
});

await fs.commitFileAsync({
await commitFileAsync({
...newFile,
stat: { mode: READ_ONLY_MODE },
});
Expand All @@ -116,7 +112,7 @@ describe('#commitFileAsync()', () => {
});

it("doesn't readd same file to store", async () => {
await fs.commitFileAsync(store.get(filename));
await commitFileAsync(store.get(filename));
expect(store.add.callCount).toEqual(1);
});
});
97 changes: 54 additions & 43 deletions __tests__/commit.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { describe, beforeEach, it, expect, afterEach } from 'vitest';
import filesystem from 'fs';
import path, { resolve } from 'path';
import { Duplex } from 'stream';
import os from 'os';
import path from 'path';
import { describe, beforeEach, it, expect, afterEach, vi } from 'vitest';
import { create as createMemFs } from 'mem-fs';
import sinon from 'sinon';
import { MemFsEditor, create } from '../src/index.js';
import { createTransform } from '../src/transform.js';
import { MemFsEditor, MemFsEditorFile, create } from '../src/index.js';
import { getFixture } from './fixtures.js';
import { isFilePending } from '../src/state.js';

const rmSync = filesystem.rmSync || filesystem.rmdirSync;

Expand Down Expand Up @@ -45,48 +45,46 @@ describe('#commit()', () => {
it('call filters and trigger callback on error', async () => {
let called = 0;

const filter = createTransform((file, enc, cb) => {
called++;
cb(new Error(`error ${called}`));
const filter = Duplex.from(async function* (generator: AsyncGenerator<MemFsEditorFile>) {
for await (const file of generator) {
called++;
throw new Error(`error ${called}`);
}
});

await expect(fs.commit([filter])).rejects.toMatch(/error 1/);
await expect(fs.commit(filter)).rejects.toMatch(/error 1/);
});

it('call filters and update memory model', async () => {
let called = 0;

const filter = createTransform(function (file, enc, cb) {
called++;
file.contents = Buffer.from('modified');
this.push(file);
cb();
});
await fs.commit(
Duplex.from(async function* (generator: AsyncGenerator<MemFsEditorFile>) {
for await (const file of generator) {
called++;
file.contents = Buffer.from('modified');
yield file;
}
})
);

await fs.commit([filter]);
expect(called).toBe(100);
expect(fs.read(path.join(output, 'file-1.txt'))).toBe('modified');
});

it('call filters, update memory model and commit selected files', async () => {
let called = 0;

const filter = createTransform(function (file, enc, cb) {
called++;
file.contents = Buffer.from('modified');
this.push(file);
cb();
});

const beforeFilter = createTransform(function (file, enc, cb) {
if (file.path.endsWith('1.txt')) {
this.push(file);
}

cb();
});

await fs.commit([filter], store.stream().pipe(beforeFilter));
await fs.commit(
{ filter: (file) => file.path.endsWith('1.txt') && isFilePending(file) },
Duplex.from(async function* (generator: AsyncGenerator<MemFsEditorFile>) {
for await (const file of generator) {
called++;
file.contents = Buffer.from('modified');
yield file;
}
})
);
expect(called).toBe(10);
expect(fs.read(path.join(output, 'file-1.txt'))).toBe('modified');
expect(fs.read(path.join(output, 'file-2.txt'))).not.toBe('modified');
Expand Down Expand Up @@ -140,17 +138,30 @@ describe('#commit()', () => {
fs.delete('copy-to-delete');
fs.store.get('to-delete');

fs.commitFileAsync = sinon.stub().returns(Promise.resolve());
await fs.commit([
createTransform(function (file, enc, cb) {
expect(file.path).not.toEqual(path.resolve('to-delete'));
expect(file.path).not.toEqual(path.resolve('copy-to-delete'));

this.push(file);
cb();
}),
]);
expect(fs.commitFileAsync.callCount).toBe(NUMBER_FILES);
const writeFile = vi.spyOn(filesystem.promises, 'writeFile');

await fs.commit({ filter: () => true });

expect(writeFile).toHaveBeenCalled();
expect(writeFile).not.toBeCalledWith(resolve('to-delete'), expect.anything(), expect.anything());
});

it('does not pass files who are deleted before being commited through the pipeline', async () => {
fs.write('to-delete', 'foo');
fs.delete('to-delete');
fs.copy(getFixture('file-a.txt'), 'copy-to-delete');
fs.delete('copy-to-delete');
fs.store.get('to-delete');

await fs.commit(
Duplex.from(async function* (generator: AsyncGenerator<MemFsEditorFile>) {
for await (const file of generator) {
expect(file.path).not.toEqual(path.resolve('to-delete'));
expect(file.path).not.toEqual(path.resolve('copy-to-delete'));
yield file;
}
})
);
});
});

Expand Down
2 changes: 1 addition & 1 deletion __tests__/dump.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('#dump()', () => {

describe('with custom filter', () => {
it('should match snapshot', () => {
expect(fs.dump(output, file => file.path.endsWith('not-committed'))).toMatchSnapshot();
expect(fs.dump(output, (file) => file.path.endsWith('not-committed'))).toMatchSnapshot();
});
});

Expand Down
Loading

0 comments on commit e5f563a

Please sign in to comment.