Skip to content

Commit

Permalink
keep file history (#249)
Browse files Browse the repository at this point in the history
* keep file history

* Update write.ts
  • Loading branch information
mshima committed Aug 18, 2024
1 parent 04d28d4 commit 22ce794
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 33 deletions.
8 changes: 4 additions & 4 deletions __tests__/copy-async.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ describe('#copyAsync()', () => {
describe('using append option', () => {
beforeEach(() => {
sinon.spy(fs, 'append');
sinon.spy(fs, 'write');
sinon.spy(fs, '_write');
});
afterEach(() => {
fs.write.restore();
fs._write.restore();
fs.append.restore();
});

Expand All @@ -41,14 +41,14 @@ describe('#copyAsync()', () => {
const newPath = '/new/path/file.txt';
await fs.copyAsync(filepath, newPath, { append: true });

expect(fs.write.callCount).toBe(1);
expect(fs._write.callCount).toBe(1);
expect(fs.append.callCount).toBe(0);
expect(fs.read(newPath)).toBe(initialContents);
expect(fs.store.get(newPath).state).toBe('modified');

await fs.copyAsync(filepath, newPath, { append: true });

expect(fs.write.callCount).toBe(2);
expect(fs._write.callCount).toBe(2);
expect(fs.append.callCount).toBe(1);
expect(fs.read(newPath)).toBe(initialContents + initialContents);
});
Expand Down
9 changes: 8 additions & 1 deletion __tests__/copy-tpl-async.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, beforeEach, it, expect } from 'vitest';
import os from 'os';
import path from 'path';
import path, { resolve } from 'path';
import { type MemFsEditor, create } from '../src/index.js';
import { create as createMemFs } from 'mem-fs';
import normalize from 'normalize-path';
Expand Down Expand Up @@ -112,4 +112,11 @@ describe('#copyTpl()', () => {
await fs.copyTplAsync(filepath, newPath);
expect(fs.exists(newPath)).toBeTruthy();
});

it('keeps template path in file history', async () => {
const filepath = getFixture('file-tpl.txt');
const newPath = '/new/path/file.txt';
await fs.copyTplAsync(filepath, newPath, { name: 'new content' });
expect(fs.store.get(newPath).history).toMatchObject([resolve(filepath), resolve(newPath)]);
});
});
9 changes: 8 additions & 1 deletion __tests__/copy-tpl.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, beforeEach, it, expect } from 'vitest';
import os from 'os';
import path from 'path';
import path, { resolve } from 'path';
import { type MemFsEditor, create } from '../src/index.js';
import { create as createMemFs } from 'mem-fs';
import normalize from 'normalize-path';
Expand Down Expand Up @@ -114,4 +114,11 @@ describe('#copyTpl()', () => {
fs.copyTpl(filepath, newPath);
expect(fs.exists(newPath)).toBeTruthy();
});

it('keeps template path in file history', () => {
const filepath = getFixture('ejs/file-ejs-extension.txt.ejs');
const newPath = '/new/path/file-ejs-extension.txt.ejs';
fs.copyTpl(filepath, newPath);
expect(fs.store.get(newPath).history).toMatchObject([resolve(filepath), resolve(newPath)]);
});
});
8 changes: 4 additions & 4 deletions __tests__/copy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ describe('#copy()', () => {
describe('using append option', () => {
beforeEach(() => {
sinon.spy(fs, 'append');
sinon.spy(fs, 'write');
sinon.spy(fs, '_write');
});
afterEach(() => {
fs.write.restore();
fs._write.restore();
fs.append.restore();
});

Expand All @@ -44,14 +44,14 @@ describe('#copy()', () => {
const newPath = '/new/path/file.txt';
fs.copy(filepath, newPath, { append: true });

expect(fs.write.callCount).toBe(1);
expect(fs._write.callCount).toBe(1);
expect(fs.append.callCount).toBe(0);
expect(fs.read(newPath)).toBe(initialContents);
expect(fs.store.get(newPath).state).toBe('modified');

fs.copy(filepath, newPath, { append: true });

expect(fs.write.callCount).toBe(2);
expect(fs._write.callCount).toBe(2);
expect(fs.append.callCount).toBe(1);
expect(fs.read(newPath)).toBe(initialContents + initialContents);
});
Expand Down
16 changes: 12 additions & 4 deletions src/actions/copy-async.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import assert from 'assert';
import fs from 'fs';
import fsPromises from 'fs/promises';
import path from 'path';
import path, { resolve } from 'path';
import type { Data, Options } from 'ejs';
import { globbySync, isDynamicPattern, type Options as GlobbyOptions } from 'globby';
import multimatch from 'multimatch';
import { render, globify, getCommonPath } from '../util.js';
import normalize from 'normalize-path';
import File from 'vinyl';
import type { MemFsEditor } from '../index.js';
import { AppendOptions } from './append.js';
import { Data, Options } from 'ejs';
import { CopySingleOptions } from './copy.js';

async function applyProcessingFileFunc(
Expand Down Expand Up @@ -129,6 +130,7 @@ export async function _copySingleAsync(
if (!options.processFile) {
return this._copySingle(from, to, options);
}
from = resolve(from);

const contents = await applyProcessingFileFunc.call(this, options.processFile, from);

Expand All @@ -143,6 +145,12 @@ export async function _copySingleAsync(
}
}

const stat = await fsPromises.stat(from);
this.write(to, contents, stat);
this._write(
new File({
contents,
stat: await fsPromises.stat(from),
path: to,
history: [from],
}),
);
}
22 changes: 20 additions & 2 deletions src/actions/copy.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import assert from 'assert';
import fs from 'fs';
import path from 'path';
import path, { resolve } from 'path';
import { globbySync, isDynamicPattern, type Options as GlobbyOptions } from 'globby';
import multimatch from 'multimatch';
import { Data, Options } from 'ejs';
import normalize from 'normalize-path';
import File, { isVinyl } from 'vinyl';

import type { MemFsEditor } from '../index.js';
import { getCommonPath, globify, render } from '../util.js';
Expand Down Expand Up @@ -104,6 +105,7 @@ export function _copySingle(this: MemFsEditor, from: string, to: string, options
assert(this.exists(from), 'Trying to copy from a source that does not exist: ' + from);

const file = this.store.get(from);
to = resolve(to);

let { contents } = file;
if (!contents) {
Expand All @@ -124,5 +126,21 @@ export function _copySingle(this: MemFsEditor, from: string, to: string, options
}
}

this.write(to, contents, file.stat);
if (isVinyl(file)) {
this._write(
Object.assign(file.clone({ contents: false, deep: false }), {
contents,
path: to,
}),
);
} else {
this._write(
new File({
contents,
stat: (file.stat as any) ?? fs.statSync(file.path),
path: to,
history: [file.path],
}),
);
}
}
53 changes: 37 additions & 16 deletions src/actions/write.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
import assert from 'assert';
import { resolve } from 'path';

import { isFileStateModified, setModifiedFileState } from '../state.js';
import type { MemFsEditor } from '../index.js';
import type { MemFsEditor, MemFsEditorFile } from '../index.js';
import File from 'vinyl';

type CompareFile = { contents: null | Buffer; stat?: { mode?: number } | null };

export const isMemFsEditorFileEqual = (a: CompareFile, b: CompareFile) => {
if (a.stat?.mode !== b.stat?.mode) {
return false;
}
return a.contents === b.contents || a.contents?.equals(b.contents!);
};

export function _write<EditorFile extends MemFsEditorFile>(this: MemFsEditor<EditorFile>, file: EditorFile) {
if (this.store.existsInMemory(file.path)) {
// Backward compatibility, keep behavior for existing files, custom properties may have been added
const existingFile = this.store.get(file.path);
if (!isFileStateModified(existingFile) || !isMemFsEditorFileEqual(existingFile, file)) {
const { contents, stat } = file;
setModifiedFileState(existingFile);
Object.assign(existingFile, { contents, stat: stat ?? existingFile.stat });
this.store.add(existingFile);
}
} else {
setModifiedFileState(file);
this.store.add(file);
}
}

export default function write(
this: MemFsEditor,
filepath: string,
contents: string | Buffer,
stat?: { mode?: number } | null,
stat: { mode?: number } | null = null,
) {
assert(typeof contents === 'string' || Buffer.isBuffer(contents), 'Expected `contents` to be a String or a Buffer');

const file = this.store.get(filepath);
const newContents = Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
if (
!isFileStateModified(file) ||
!Buffer.isBuffer(file.contents) ||
!newContents.equals(file.contents) ||
(stat && file.stat !== stat)
) {
setModifiedFileState(file);
file.contents = newContents;
file.stat = stat ?? null;
this.store.add(file);
}

return file.contents.toString();
this._write(
new File({
path: resolve(filepath),
contents: newContents,
stat: stat as any,
}),
);
return contents.toString();
}
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type { PipelineOptions, FileTransform } from 'mem-fs';
import read from './actions/read.js';
import readJSON from './actions/read-json.js';
import exists from './actions/exists.js';
import write from './actions/write.js';
import write, { _write } from './actions/write.js';
import writeJSON from './actions/write-json.js';
import extendJSON from './actions/extend-json.js';
import append from './actions/append.js';
Expand Down Expand Up @@ -52,6 +52,7 @@ export interface MemFsEditor<EditorFile extends MemFsEditorFile = VinylMemFsEdit
read: typeof read;
readJSON: typeof readJSON;
exists: typeof exists;
_write: typeof _write<EditorFile>;
write: typeof write;
writeJSON: typeof writeJSON;
extendJSON: typeof extendJSON;
Expand All @@ -73,6 +74,7 @@ export interface MemFsEditor<EditorFile extends MemFsEditorFile = VinylMemFsEdit
MemFsEditor.prototype.read = read;
MemFsEditor.prototype.readJSON = readJSON;
MemFsEditor.prototype.exists = exists;
MemFsEditor.prototype._write = _write;
MemFsEditor.prototype.write = write;
MemFsEditor.prototype.writeJSON = writeJSON;
MemFsEditor.prototype.extendJSON = extendJSON;
Expand Down

0 comments on commit 22ce794

Please sign in to comment.