@@ -6,14 +6,33 @@ import $debug from 'debug'
66const debug = $debug ( 'zipatch:fs' )
77
88const zeroBuffer = Buffer . alloc ( 1024 , 0 )
9- export class FileSystem {
9+ export interface FileRange {
10+ start : number
11+ end : number
12+ }
13+
14+ export interface ZipatchFileSystem {
15+ workspace : string
16+ close ( ) : Promise < void >
17+ isPathAllowed ( path : string ) : boolean
18+ write ( path : string , buf : Buffer , offset ?: number ) : Promise < boolean >
19+ erase ( path : string , length : number , offset : number ) : Promise < boolean >
20+ createDirectory ( path : string ) : Promise < void >
21+ removeDirectory ( path : string ) : Promise < void >
22+ }
23+
24+ export class FileSystem implements ZipatchFileSystem {
1025 private fileHandles = new Map < string , FileHandle > ( )
1126
1227 constructor (
1328 private root : string ,
1429 private allowList : string [ ] = [ ] ,
1530 ) { }
1631
32+ get workspace ( ) : string {
33+ return this . root
34+ }
35+
1736 /**
1837 * Close all open file handles
1938 */
@@ -35,7 +54,14 @@ export class FileSystem {
3554 * Check if a path is allowed based on the allowlist
3655 */
3756 isPathAllowed ( path : string ) : boolean {
38- return ! this . allowList . length || this . allowList . includes ( path )
57+ return (
58+ ! this . allowList . length ||
59+ this . allowList . some ( ( pattern ) =>
60+ pattern . endsWith ( '*' )
61+ ? path . startsWith ( pattern . slice ( 0 , - 1 ) )
62+ : pattern === path ,
63+ )
64+ )
3965 }
4066
4167 /**
@@ -128,3 +154,81 @@ export class FileSystem {
128154 return handle
129155 }
130156}
157+
158+ export class VirtualFileSystem implements ZipatchFileSystem {
159+ private ranges = new Map < string , FileRange [ ] > ( )
160+
161+ constructor (
162+ private root : string ,
163+ private allowList : string [ ] = [ ] ,
164+ ) { }
165+
166+ get workspace ( ) : string {
167+ return this . root
168+ }
169+
170+ async close ( ) : Promise < void > {
171+ // do nothing
172+ }
173+
174+ isPathAllowed ( path : string ) : boolean {
175+ return (
176+ ! this . allowList . length ||
177+ this . allowList . some ( ( pattern ) =>
178+ pattern . endsWith ( '*' )
179+ ? path . startsWith ( pattern . slice ( 0 , - 1 ) )
180+ : pattern === path ,
181+ )
182+ )
183+ }
184+
185+ async write ( path : string , buf : Buffer , offset : number = 0 ) : Promise < boolean > {
186+ this . recordRange ( path , offset , offset + buf . length )
187+ return true
188+ }
189+
190+ async erase ( path : string , length : number , offset : number ) : Promise < boolean > {
191+ this . recordRange ( path , offset , offset + length )
192+ return true
193+ }
194+
195+ async createDirectory ( path : string ) : Promise < void > {
196+ // do nothing
197+ }
198+
199+ async removeDirectory ( path : string ) : Promise < void > {
200+ // do nothing
201+ }
202+
203+ getRecordedRanges ( ) : Map < string , FileRange [ ] > {
204+ return new Map (
205+ [ ...this . ranges . entries ( ) ] . map ( ( [ path , ranges ] ) => [
206+ path ,
207+ ranges . map ( ( range ) => ( { ...range } ) ) ,
208+ ] ) ,
209+ )
210+ }
211+
212+ private recordRange ( path : string , start : number , end : number ) {
213+ if ( ! this . isPathAllowed ( path ) ) {
214+ return
215+ }
216+
217+ const ranges = this . ranges . get ( path ) ?? [ ]
218+ ranges . push ( { start, end } )
219+ ranges . sort ( ( left , right ) => left . start - right . start )
220+
221+ const merged : FileRange [ ] = [ ]
222+ for ( const range of ranges ) {
223+ const current = merged . at ( - 1 )
224+ if ( ! current || range . start > current . end ) {
225+ merged . push ( { ...range } )
226+ continue
227+ }
228+
229+ current . end = Math . max ( current . end , range . end )
230+ }
231+
232+ this . ranges . set ( path , merged )
233+ }
234+ }
0 commit comments