diff --git a/src/__tests__/union.test.ts b/src/__tests__/union.test.ts index e0719f6d..792e9f17 100644 --- a/src/__tests__/union.test.ts +++ b/src/__tests__/union.test.ts @@ -489,6 +489,34 @@ describe('union', () => { ufs.unlinkSync(realFile); }); + + describe('createWriteStream', () => { + it('creates a new file when only parent directory exists', async () => { + const vol = Volume.fromJSON({ '/foo': null }); + const vol2 = Volume.fromJSON({ '/bar': null }); + const ufs = new Union(); + ufs.use(vol as any).use(vol2 as any); + + const stream = ufs.createWriteStream('/bar/file'); + stream.end('content'); + await new Promise(resolve => stream.once('close', resolve)); + + expect(vol2.readFileSync('/bar/file', 'utf8')).toBe('content'); + }); + + it('writes to an existing file even if parent dir exists on an earlier fss', async () => { + const vol = Volume.fromJSON({ '/bar': null }); + const vol2 = Volume.fromJSON({ '/bar/file': '' }); + const ufs = new Union(); + ufs.use(vol as any).use(vol2 as any); + + const stream = ufs.createWriteStream('/bar/file'); + stream.end('content'); + await new Promise(resolve => stream.once('close', resolve)); + + expect(vol2.readFileSync('/bar/file', 'utf8')).toBe('content'); + }); + }); }); }); }); diff --git a/src/union.ts b/src/union.ts index 94183001..62a3865d 100644 --- a/src/union.ts +++ b/src/union.ts @@ -1,5 +1,6 @@ import { FSWatcher, Dirent } from 'fs'; import { IFS } from './fs'; +import { dirname } from 'path'; import { Readable, Writable } from 'stream'; const { fsAsyncMethods, fsSyncMethods } = require('fs-monkey/lib/util/lists'); @@ -314,12 +315,31 @@ export class Union { public createWriteStream = (path: string) => { let lastError = null; + const fssWithFilePath: IFS[] = []; + const fssWithParentDir: IFS[] = []; + for (const fs of this.fss) { try { if (!fs.createWriteStream) throw Error(`Method not supported: "createWriteStream"`); - fs.statSync(path); //we simply stat first to exit early for mocked fs'es - //TODO which filesystem to write to? + //we simply stat first to exit early for mocked fs'es + if (!fs.statSync(dirname(path)).isDirectory()) { + throw new Error(`path "${dirname(path)}" is not a directory`); + } + if (fs.existsSync(path)) { + fssWithFilePath.push(fs); + } else { + fssWithParentDir.push(fs); + } + } catch (err) { + lastError = err; + } + } + + // First try writing to filesystems where the file path exists. + // Then try writing to filesystems where the parent directory exists. + for (const fs of [...fssWithFilePath, ...fssWithParentDir]) { + try { const stream = fs.createWriteStream(path); if (!stream) { throw new Error('no valid stream');