1
+ import { dir } from 'tmp-promise' ;
2
+ import * as fs from 'fs-extra' ;
3
+ import * as path from 'path' ;
4
+
5
+ type Encoding = 'ascii' | 'base64' | 'hex' | 'ucs2' | 'ucs-2' | 'utf16le' | 'utf-16le' | 'utf8' | 'utf-8' | 'binary' | 'latin1' ;
6
+ type Octal = 0o0 | 0o1 | 0o2 | 0o3 | 0o4 | 0o5 | 0o6 | 0o7 ;
7
+ type Mode = `${Octal } ${Octal } ${Octal } `;
8
+ type Flag = 'a' | 'ax' | 'a+' | 'ax+' | 'as' | 'as+' | 'r' | 'r+' | 'rs+' | 'w' | 'wx' | 'w+' | 'wx+' ;
9
+
10
+ export type MockInstance = {
11
+ reset ( ) : void ;
12
+ }
13
+
14
+ export type DirectoryItem =
15
+ | string
16
+ | Buffer
17
+ | File
18
+ | Directory
19
+ | SymbolicLink
20
+ | DirectoryItems ;
21
+
22
+ export type DirectoryItems = {
23
+ [ name : string ] : DirectoryItem ;
24
+ }
25
+
26
+ export type File = {
27
+ content : string | Buffer ;
28
+ mode ?: Mode | undefined ;
29
+ encoding ?: Encoding | undefined ;
30
+ flag ?: Flag | undefined ;
31
+ uid ?: number | undefined ;
32
+ gid ?: number | undefined ;
33
+ atime ?: Date | undefined ;
34
+ mtime ?: Date | undefined ;
35
+ }
36
+
37
+ export type Directory = {
38
+ items : DirectoryItems | undefined ;
39
+ mode ?: Mode | undefined ;
40
+ encoding ?: Encoding | undefined ;
41
+ flag ?: Flag | undefined ;
42
+ uid ?: number | undefined ;
43
+ gid ?: number | undefined ;
44
+ atime ?: Date | undefined ;
45
+ mtime ?: Date | undefined ;
46
+ birthtime ?: Date | undefined ;
47
+ }
48
+
49
+ export type SymbolicLink = {
50
+ path : string ;
51
+ mode ?: Mode | undefined ;
52
+ encoding ?: Encoding | undefined ;
53
+ flag ?: Flag | undefined ;
54
+ uid ?: number | undefined ;
55
+ gid ?: number | undefined ;
56
+ atime ?: Date | undefined ;
57
+ mtime ?: Date | undefined ;
58
+ birthtime ?: Date | undefined ;
59
+ }
60
+
61
+ export const mock = async ( config : DirectoryItems ) : Promise < MockInstance > => {
62
+ const root = await dir ( { unsafeCleanup : true } ) ;
63
+ const cwd = process . cwd ( ) ;
64
+
65
+ process . chdir ( root . path ) ;
66
+ for ( const entry of Object . entries ( config ) ) await create ( entry ) ;
67
+
68
+ return {
69
+ reset : ( ) => {
70
+ root . cleanup ( ) ;
71
+ process . chdir ( cwd ) ;
72
+ }
73
+ } ;
74
+ }
75
+
76
+ export const create = async ( [ filename , config ] : [ string , DirectoryItem ] ) : Promise < void > => {
77
+ if ( isContent ( config ) ) {
78
+ await writeFile ( filename , config ) ;
79
+ } else if ( isFile ( config ) ) {
80
+ const options : fs . WriteFileOptions = {
81
+ mode : config . mode ? parseInt ( config . mode , 8 ) : undefined ,
82
+ encoding : config . encoding ,
83
+ flag : config . flag
84
+ } ;
85
+ await writeFile ( filename , config . content , options ) ;
86
+
87
+ if ( config . atime && config . mtime ) await fs . utimes ( filename , config . atime , config . mtime ) ;
88
+ if ( config . atime && ! config . mtime ) await fs . utimes ( filename , config . atime , new Date ( 1 ) ) ;
89
+ if ( ! config . atime && config . mtime ) await fs . utimes ( filename , new Date ( 1 ) , config . mtime ) ;
90
+ } else if ( isDirectory ( config ) ) {
91
+ await writeDir ( filename , config . mode ? parseInt ( config . mode , 8 ) : undefined ) ;
92
+ } else if ( isSymlink ( config ) ) {
93
+ await writeSymlink ( filename , config . path ) ;
94
+ } else if ( isDirectoryItems ( config ) ) {
95
+ const items = Object . entries ( config ) ;
96
+ if ( items . length > 0 ) {
97
+ for ( const [ subfile , subconfig ] of items ) await create ( [ path . join ( filename , subfile ) , subconfig ] ) ;
98
+ } else {
99
+ await writeDir ( filename ) ;
100
+ }
101
+
102
+ }
103
+ }
104
+
105
+ const isContent = ( config : DirectoryItem ) : config is string | Buffer => {
106
+ return ( typeof config === 'string' || config instanceof Buffer ) ;
107
+ }
108
+
109
+ export const file = ( config ?: File ) : File => {
110
+ return { content : config ? config . content : '' , ...config }
111
+ }
112
+
113
+ const isFile = ( config : DirectoryItem ) : config is File => {
114
+ return ( config as File ) . content !== undefined ;
115
+ }
116
+
117
+ export const directory = ( config ?: Directory ) : Directory => {
118
+ return { items : config ? config . items : undefined , ...config }
119
+ }
120
+
121
+ const isDirectory = ( config : DirectoryItem ) : config is Directory => {
122
+ return ( config as Directory ) . items !== undefined ;
123
+ }
124
+
125
+ export const symlink = ( config ?: SymbolicLink ) : SymbolicLink => {
126
+ return { path : config ? config . path : '' , ...config }
127
+ }
128
+
129
+ const isSymlink = ( config : DirectoryItem ) : config is SymbolicLink => {
130
+ return ( config as SymbolicLink ) . path !== undefined ;
131
+ }
132
+
133
+ const isDirectoryItems = ( config : DirectoryItem ) : config is DirectoryItems => {
134
+ return Object . keys ( config ) . length !== undefined ;
135
+ }
136
+
137
+
138
+ const writeFile = async ( filepath : fs . PathLike , content : string | Buffer , options ?: fs . WriteFileOptions ) => {
139
+ try {
140
+ await fs . ensureFile ( filepath . toString ( ) ) ;
141
+ await fs . outputFile ( filepath . toString ( ) , content , options ) ;
142
+ } catch ( error ) {
143
+ console . error ( error ) ;
144
+ }
145
+ }
146
+
147
+ const writeDir = async ( dirpath : fs . PathLike , options ?: number | { mode : number } ) => {
148
+ try {
149
+ await fs . ensureDir ( dirpath . toString ( ) , options ) ;
150
+ } catch ( error ) {
151
+ console . error ( error ) ;
152
+ }
153
+ }
154
+
155
+ const writeSymlink = async ( srcPath : fs . PathLike , destPath : fs . PathLike ) => {
156
+ try {
157
+ await fs . ensureSymlink ( srcPath . toString ( ) , destPath . toString ( ) ) ;
158
+ } catch ( error ) {
159
+ console . error ( error ) ;
160
+ }
161
+ }
0 commit comments