import debug from "debug" ; import * as fs from "fs" ; import { flatten , sortBy } from "lodash" ; import * as os from "os" ; import * as path from "path" ; import { IListenerInput } from "unmock-core" ; const debugLog = debug ( "unmock:snapshotter:writer" ) ; export interface ISnapshot { timestamp : Date ; testPath : string ; currentTestName : string ; data : IListenerInput ; } export interface ISnapshotWriterReader { write ( snapshot ) : void ; read ( ) : ISnapshot [ ] ; deleteSnapshots ( ) : void ; } export const format = ( snapshot ) : string => { const withStringTs = { ... snapshot , timestamp : snapshot . timestamp . toISOString ( ) , } ; return JSON . stringify ( withStringTs ) ; } ; const ENCODING = "utf-8" ; const SNAPSHOT_FILENAME = "snapshots.jsonl" ; const createTmpDirIn = ( snapshotFolder ) : string => { return fs . mkdtempSync ( `${ snapshotFolder }${ path . sep }` ) ; } ; export const parseSnapshot = ( str ) : ISnapshot => { const parsed = JSON . parse ( str ) ; return { ... parsed , timestamp : new Date ( parsed . timestamp ) } ; } ; export class FsSnapshotWriterReader implements ISnapshotWriterReader { public static readFileContents ( filename ) { const fileContents = fs . readFileSync ( filename , ENCODING ) ; const lines = fileContents . split ( os . EOL ) ; return lines . filter ( line => ! ! line . trim ( ) ) . map ( line => parseSnapshot ( line ) ) ; } private readonly outputFolder : string ; private readonly outputFile : string ; param snapshotFolder constructor ( private readonly snapshotFolder ) { this . outputFolder = createTmpDirIn ( snapshotFolder ) ; this . outputFile = path . join ( this . outputFolder , SNAPSHOT_FILENAME ) ; } public write ( snapshot ) { const contents = format ( snapshot ) ; if ( ! fs . existsSync ( this . outputFolder ) ) { fs . mkdirSync ( this . outputFolder ) ; } if ( ! fs . existsSync ( this . outputFile ) ) { debugLog ( `Writing to new file ${ this . outputFile }` ) ; fs . writeFileSync ( this . outputFile , contents + os . EOL , { encoding : ENCODING , } ) ; } else { debugLog ( `Appending to file ${ this . outputFile }` ) ; fs . appendFileSync ( this . outputFile , contents + os . EOL , { encoding : ENCODING , } ) ; } } public findSnapshotFiles ( ) { return fs . readdirSync ( this . snapshotFolder ) . map ( filename => path . join ( this . snapshotFolder , filename ) ) . filter ( file => fs . lstatSync ( file ) . isDirectory ( ) ) . map ( dir => path . join ( dir , SNAPSHOT_FILENAME ) ) . filter ( filename => fs . existsSync ( filename ) && fs . lstatSync ( filename ) . isFile ( ) ) ; } public read ( ) { if ( ! fs . existsSync ( this . snapshotFolder ) ) { return [ ] ; } const snapshotFiles = this . findSnapshotFiles ( ) ; const snapshots = snapshotFiles . map ( filename => FsSnapshotWriterReader . readFileContents ( filename ) ) ; return sortBy ( flatten ( snapshots ) , ( snapshot ) => snapshot . timestamp . getTime ( ) ) ; } public deleteSnapshots ( ) { if ( ! fs . existsSync ( this . snapshotFolder ) ) { return ; } this . findSnapshotFiles ( ) . forEach ( filename => { fs . unlinkSync ( filename ) ; const directory = path . dirname ( filename ) ; if ( fs . readdirSync ( directory ) . length === 0 ) { debugLog ( `Deleting directory: ${ directory }` ) ; fs . rmdirSync ( directory ) ; } else { debugLog ( `Directory not empty, not removing: ${ directory }` ) ; } } ) ; } }