-
-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 🎸 implement async verions of snapshotting
- Loading branch information
Showing
6 changed files
with
187 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { fromSnapshot, toSnapshot } from '../async'; | ||
import { memfs } from '../..'; | ||
import { SnapshotNodeType } from '../constants'; | ||
|
||
test('can snapshot a single file', async () => { | ||
const { fs } = memfs({ | ||
'/foo': 'bar', | ||
}); | ||
const snapshot = await toSnapshot({ fs: fs.promises, path: '/foo' }); | ||
expect(snapshot).toStrictEqual([SnapshotNodeType.File, expect.any(Object), new Uint8Array([98, 97, 114])]); | ||
}); | ||
|
||
test('can snapshot a single folder', async () => { | ||
const { fs } = memfs({ | ||
'/foo': null, | ||
}); | ||
const snapshot = await toSnapshot({ fs: fs.promises, path: '/foo' }); | ||
expect(snapshot).toStrictEqual([SnapshotNodeType.Folder, expect.any(Object), {}]); | ||
}); | ||
|
||
test('can snapshot a folder with a file and symlink', async () => { | ||
const { fs } = memfs({ | ||
'/foo': 'bar', | ||
}); | ||
fs.symlinkSync('/foo', '/baz'); | ||
const snapshot = await toSnapshot({ fs: fs.promises, path: '/' }); | ||
expect(snapshot).toStrictEqual([ | ||
SnapshotNodeType.Folder, | ||
expect.any(Object), | ||
{ | ||
foo: [SnapshotNodeType.File, expect.any(Object), new Uint8Array([98, 97, 114])], | ||
baz: [SnapshotNodeType.Symlink, { target: '/foo' }], | ||
}, | ||
]); | ||
}); | ||
|
||
test('can create a snapshot and un-snapshot a complex fs tree', async () => { | ||
const { fs } = memfs({ | ||
'/start': { | ||
file1: 'file1', | ||
file2: 'file2', | ||
'empty-folder': null, | ||
'/folder1': { | ||
file3: 'file3', | ||
file4: 'file4', | ||
'empty-folder': null, | ||
'/folder2': { | ||
file5: 'file5', | ||
file6: 'file6', | ||
'empty-folder': null, | ||
'empty-folde2': null, | ||
}, | ||
}, | ||
}, | ||
}); | ||
fs.symlinkSync('/start/folder1/folder2/file6', '/start/folder1/symlink'); | ||
fs.writeFileSync('/start/binary', new Uint8Array([1, 2, 3])); | ||
const snapshot = await toSnapshot({ fs: fs.promises, path: '/start' })!; | ||
const { fs: fs2, vol: vol2 } = memfs(); | ||
fs2.mkdirSync('/start', { recursive: true }); | ||
await fromSnapshot(snapshot, { fs: fs2.promises, path: '/start' }); | ||
expect(fs2.readFileSync('/start/binary')).toStrictEqual(Buffer.from([1, 2, 3])); | ||
const snapshot2 = await toSnapshot({ fs: fs2.promises, path: '/start' })!; | ||
expect(snapshot2).toStrictEqual(snapshot); | ||
}); |
2 changes: 1 addition & 1 deletion
2
src/snapshot/__tests__/index.test.ts → src/snapshot/__tests__/sync.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { SnapshotNodeType } from './constants'; | ||
import type { AsyncSnapshotOptions, SnapshotNode } from './types'; | ||
|
||
export const toSnapshot = async ({ fs, path = '/', separator = '/' }: AsyncSnapshotOptions): Promise<SnapshotNode> => { | ||
const stats = await fs.lstat(path); | ||
if (stats.isDirectory()) { | ||
const list = await fs.readdir(path); | ||
const entries: { [child: string]: SnapshotNode } = {}; | ||
const dir = path.endsWith(separator) ? path : path + separator; | ||
for (const child of list) { | ||
const childSnapshot = await toSnapshot({ fs, path: `${dir}${child}`, separator }); | ||
if (childSnapshot) entries['' + child] = childSnapshot; | ||
} | ||
return [SnapshotNodeType.Folder, {}, entries]; | ||
} else if (stats.isFile()) { | ||
const buf = await fs.readFile(path) as Buffer; | ||
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); | ||
return [SnapshotNodeType.File, {}, uint8]; | ||
} else if (stats.isSymbolicLink()) { | ||
return [ | ||
SnapshotNodeType.Symlink, | ||
{ | ||
target: await fs.readlink(path, {encoding: 'utf8'}) as string, | ||
}, | ||
]; | ||
} | ||
return null; | ||
}; | ||
|
||
export const fromSnapshot = async ( | ||
snapshot: SnapshotNode, | ||
{ fs, path = '/', separator = '/' }: AsyncSnapshotOptions, | ||
): Promise<void> => { | ||
if (!snapshot) return; | ||
switch (snapshot[0]) { | ||
case SnapshotNodeType.Folder: { | ||
if (!path.endsWith(separator)) path = path + separator; | ||
const [, , entries] = snapshot; | ||
await fs.mkdir(path, { recursive: true }); | ||
for (const [name, child] of Object.entries(entries)) | ||
await fromSnapshot(child, { fs, path: `${path}${name}`, separator }); | ||
break; | ||
} | ||
case SnapshotNodeType.File: { | ||
const [, , data] = snapshot; | ||
await fs.writeFile(path, data); | ||
break; | ||
} | ||
case SnapshotNodeType.Symlink: { | ||
const [, { target }] = snapshot; | ||
await fs.symlink(target, path); | ||
break; | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,4 @@ | ||
import { SnapshotNodeType } from './constants'; | ||
import type { SnapshotNode, SnapshotOptions } from './type'; | ||
|
||
export const toSnapshotSync = ({ fs, path = '/', separator = '/' }: SnapshotOptions): SnapshotNode => { | ||
const stats = fs.lstatSync(path); | ||
if (stats.isDirectory()) { | ||
const list = fs.readdirSync(path); | ||
const entries: { [child: string]: SnapshotNode } = {}; | ||
const dir = path.endsWith(separator) ? path : path + separator; | ||
for (const child of list) { | ||
const childSnapshot = toSnapshotSync({ fs, path: `${dir}${child}`, separator }); | ||
if (childSnapshot) entries['' + child] = childSnapshot; | ||
} | ||
return [SnapshotNodeType.Folder, {}, entries]; | ||
} else if (stats.isFile()) { | ||
const buf = fs.readFileSync(path) as Buffer; | ||
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); | ||
return [SnapshotNodeType.File, {}, uint8]; | ||
} else if (stats.isSymbolicLink()) { | ||
return [ | ||
SnapshotNodeType.Symlink, | ||
{ | ||
target: fs.readlinkSync(path).toString(), | ||
}, | ||
]; | ||
} | ||
return null; | ||
}; | ||
|
||
export const fromSnapshotSync = ( | ||
snapshot: SnapshotNode, | ||
{ fs, path = '/', separator = '/' }: SnapshotOptions, | ||
): void => { | ||
if (!snapshot) return; | ||
switch (snapshot[0]) { | ||
case SnapshotNodeType.Folder: { | ||
if (!path.endsWith(separator)) path = path + separator; | ||
const [, , entries] = snapshot; | ||
fs.mkdirSync(path, { recursive: true }); | ||
for (const [name, child] of Object.entries(entries)) | ||
fromSnapshotSync(child, { fs, path: `${path}${name}`, separator }); | ||
break; | ||
} | ||
case SnapshotNodeType.File: { | ||
const [, , data] = snapshot; | ||
fs.writeFileSync(path, data); | ||
break; | ||
} | ||
case SnapshotNodeType.Symlink: { | ||
const [, { target }] = snapshot; | ||
fs.symlinkSync(target, path); | ||
break; | ||
} | ||
} | ||
}; | ||
export type * from './types'; | ||
export * from './constants'; | ||
export * from './sync'; | ||
export * from './binary'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { SnapshotNodeType } from './constants'; | ||
import type { SnapshotNode, SnapshotOptions } from './types'; | ||
|
||
export const toSnapshotSync = ({ fs, path = '/', separator = '/' }: SnapshotOptions): SnapshotNode => { | ||
const stats = fs.lstatSync(path); | ||
if (stats.isDirectory()) { | ||
const list = fs.readdirSync(path); | ||
const entries: { [child: string]: SnapshotNode } = {}; | ||
const dir = path.endsWith(separator) ? path : path + separator; | ||
for (const child of list) { | ||
const childSnapshot = toSnapshotSync({ fs, path: `${dir}${child}`, separator }); | ||
if (childSnapshot) entries['' + child] = childSnapshot; | ||
} | ||
return [SnapshotNodeType.Folder, {}, entries]; | ||
} else if (stats.isFile()) { | ||
const buf = fs.readFileSync(path) as Buffer; | ||
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); | ||
return [SnapshotNodeType.File, {}, uint8]; | ||
} else if (stats.isSymbolicLink()) { | ||
return [ | ||
SnapshotNodeType.Symlink, | ||
{ | ||
target: fs.readlinkSync(path).toString(), | ||
}, | ||
]; | ||
} | ||
return null; | ||
}; | ||
|
||
export const fromSnapshotSync = ( | ||
snapshot: SnapshotNode, | ||
{ fs, path = '/', separator = '/' }: SnapshotOptions, | ||
): void => { | ||
if (!snapshot) return; | ||
switch (snapshot[0]) { | ||
case SnapshotNodeType.Folder: { | ||
if (!path.endsWith(separator)) path = path + separator; | ||
const [, , entries] = snapshot; | ||
fs.mkdirSync(path, { recursive: true }); | ||
for (const [name, child] of Object.entries(entries)) | ||
fromSnapshotSync(child, { fs, path: `${path}${name}`, separator }); | ||
break; | ||
} | ||
case SnapshotNodeType.File: { | ||
const [, , data] = snapshot; | ||
fs.writeFileSync(path, data); | ||
break; | ||
} | ||
case SnapshotNodeType.Symlink: { | ||
const [, { target }] = snapshot; | ||
fs.symlinkSync(target, path); | ||
break; | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters