1- import type { TarType , DirectoryContent } from './types' ;
1+ import type {
2+ EntryType ,
3+ DirectoryContent ,
4+ HeaderOptions ,
5+ ReadFileOptions ,
6+ WalkDirectoryOptions ,
7+ TarOptions ,
8+ } from './types' ;
29import fs from 'fs' ;
310import path from 'path' ;
4- import { TarTypes } from './types' ;
11+ import { EntryTypes } from './types' ;
512import * as errors from './errors' ;
613
7- /**
8- * The size for each tar block. This is usually 512 bytes.
9- */
10- const BLOCK_SIZE = 512 ;
14+ // Set defaults to the options used by the generators
15+ const defaultHeaderOptions : HeaderOptions = {
16+ fileNameEncoding : 'utf8' ,
17+ blockSize : 512 ,
18+ } ;
19+ const defaultReadFileOptions : ReadFileOptions = {
20+ fs : fs . promises ,
21+ blockSize : 512 ,
22+ } ;
23+ const defaultWalkDirectoryOptions : WalkDirectoryOptions = {
24+ fs : fs . promises ,
25+ blockSize : 512 ,
26+ } ;
27+ const defaultTarOptions : TarOptions = {
28+ fs : fs . promises ,
29+ blockSize : 512 ,
30+ fileNameEncoding : 'utf8' ,
31+ } ;
1132
1233function computeChecksum ( header : Buffer ) : number {
13- let sum = 0 ;
14- for ( let i = 0 ; i < BLOCK_SIZE ; i ++ ) {
15- sum += i >= 148 && i < 156 ? 32 : header [ i ] ; // Fill checksum with spaces
34+ if ( ! header . subarray ( 148 , 156 ) . every ( ( byte ) => byte === 32 ) ) {
35+ throw new errors . ErrorVirtualTarInvalidHeader (
36+ 'Checksum field is not properly initialized with spaces' ,
37+ ) ;
1638 }
17- return sum ;
39+ return header . reduce ( ( sum , byte ) => sum + byte , 0 ) ;
1840}
1941
20- function createHeader ( filePath : string , stat : fs . Stats , type : TarType ) : Buffer {
42+ function createHeader (
43+ filePath : string ,
44+ stat : fs . Stats ,
45+ type : EntryType ,
46+ options : Partial < HeaderOptions > = defaultHeaderOptions ,
47+ ) : Buffer {
2148 if ( filePath . length < 1 || filePath . length > 255 ) {
2249 throw new errors . ErrorVirtualTarInvalidFileName (
2350 'The file name must be longer than 1 character and shorter than 255 characters' ,
2451 ) ;
2552 }
2653
27- const size = type === TarTypes . FILE ? stat . size : 0 ;
28- const header = Buffer . alloc ( BLOCK_SIZE , 0 ) ;
54+ // Merge the defaults with the provided options
55+ const opts : HeaderOptions = { ...defaultHeaderOptions , ...options } ;
56+
57+ const size = type === EntryTypes . FILE ? stat . size : 0 ;
58+ const time = parseInt ( ( stat . mtime . getTime ( ) / 1000 ) . toFixed ( 0 ) ) ; // Unix time
59+ const header = Buffer . alloc ( opts . blockSize , 0 ) ;
2960
3061 // The TAR headers follow this structure
3162 // Start Size Description
@@ -48,12 +79,17 @@ function createHeader(filePath: string, stat: fs.Stats, type: TarType): Buffer {
4879 // 345 155 File name (last 155 bytes, total 255 bytes, null-padded)
4980 // 500 12 '\0' (unused)
5081
51- header . write ( filePath . slice ( 0 , 99 ) . padEnd ( 100 , '\0' ) , 0 , 100 , 'utf8' ) ;
82+ header . write (
83+ filePath . slice ( 0 , 99 ) . padEnd ( 100 , '\0' ) ,
84+ 0 ,
85+ 100 ,
86+ opts . fileNameEncoding ,
87+ ) ;
5288 header . write ( stat . mode . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 100 , 12 , 'ascii' ) ;
5389 header . write ( stat . uid . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 108 , 12 , 'ascii' ) ;
5490 header . write ( stat . gid . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 116 , 12 , 'ascii' ) ;
5591 header . write ( size . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 124 , 12 , 'ascii' ) ;
56- // Mtime will be null
92+ header . write ( time . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 136 , 12 , 'ascii' ) ;
5793 header . write ( ' ' , 148 , 8 , 'ascii' ) ; // Placeholder for checksum
5894 header . write ( type , 156 , 1 , 'ascii' ) ;
5995 // File owner name will be null
@@ -63,7 +99,12 @@ function createHeader(filePath: string, stat: fs.Stats, type: TarType): Buffer {
6399 // Owner group name will be null
64100 // Device major will be null
65101 // Device minor will be null
66- header . write ( filePath . slice ( 100 ) . padEnd ( 155 , '\0' ) , 345 , 155 , 'utf8' ) ;
102+ header . write (
103+ filePath . slice ( 100 ) . padEnd ( 155 , '\0' ) ,
104+ 345 ,
105+ 155 ,
106+ opts . fileNameEncoding ,
107+ ) ;
67108
68109 // Updating with the new checksum
69110 const checksum = computeChecksum ( header ) ;
@@ -72,19 +113,23 @@ function createHeader(filePath: string, stat: fs.Stats, type: TarType): Buffer {
72113 return header ;
73114}
74115
75- async function * readFile ( filePath : string ) : AsyncGenerator < Buffer , void , void > {
76- const fileHandle = await fs . promises . open ( filePath , 'r' ) ;
77- const buffer = Buffer . alloc ( BLOCK_SIZE ) ;
116+ async function * readFile (
117+ filePath : string ,
118+ options : Partial < ReadFileOptions > = defaultReadFileOptions ,
119+ ) : AsyncGenerator < Buffer , void , void > {
120+ const opts : ReadFileOptions = { ...defaultReadFileOptions , ...options } ;
121+ const fileHandle = await opts . fs . open ( filePath , 'r' ) ;
122+ const buffer = Buffer . alloc ( opts . blockSize ) ;
78123 let bytesRead = - 1 ; // Initialisation value
79124
80125 try {
81126 while ( bytesRead !== 0 ) {
82127 buffer . fill ( 0 ) ;
83- const result = await fileHandle . read ( buffer , 0 , BLOCK_SIZE , null ) ;
128+ const result = await fileHandle . read ( buffer , 0 , opts . blockSize , null ) ;
84129 bytesRead = result . bytesRead ;
85130
86131 if ( bytesRead === 0 ) break ; // EOF reached
87- if ( bytesRead < 512 ) buffer . fill ( 0 , bytesRead , BLOCK_SIZE ) ;
132+ if ( bytesRead < 512 ) buffer . fill ( 0 , bytesRead , opts . blockSize ) ;
88133
89134 yield buffer ;
90135 }
@@ -99,38 +144,53 @@ async function* readFile(filePath: string): AsyncGenerator<Buffer, void, void> {
99144async function * walkDirectory (
100145 baseDir : string ,
101146 relativePath : string = '' ,
147+ options : Partial < WalkDirectoryOptions > = defaultWalkDirectoryOptions ,
102148) : AsyncGenerator < DirectoryContent > {
103- const entries = await fs . promises . readdir ( path . join ( baseDir , relativePath ) ) ;
149+ const opts : WalkDirectoryOptions = {
150+ ...defaultWalkDirectoryOptions ,
151+ ...options ,
152+ } ;
153+ const entries = await opts . fs . readdir ( path . join ( baseDir , relativePath ) ) ;
104154
105155 // Sort the entries lexicographically
106156 for ( const entry of entries . sort ( ) ) {
107157 const fullPath = path . join ( baseDir , relativePath , entry ) ;
108- const stat = await fs . promises . stat ( fullPath ) ;
158+ const stat = await opts . fs . stat ( fullPath ) ;
109159 const tarPath = path . join ( relativePath , entry ) ;
110160
111161 if ( stat . isDirectory ( ) ) {
112- yield { path : tarPath + '/' , stat : stat , type : TarTypes . DIRECTORY } ;
162+ yield { path : tarPath + '/' , stat : stat , type : EntryTypes . DIRECTORY } ;
113163 yield * walkDirectory ( baseDir , path . join ( relativePath , entry ) ) ;
114164 } else if ( stat . isFile ( ) ) {
115- yield { path : tarPath , stat : stat , type : TarTypes . FILE } ;
165+ yield { path : tarPath , stat : stat , type : EntryTypes . FILE } ;
116166 }
117167 }
118168}
119169
120- async function * createTar ( baseDir : string ) : AsyncGenerator < Buffer , void , void > {
121- for await ( const entry of walkDirectory ( baseDir ) ) {
122- // Create header
170+ async function * createTar (
171+ baseDir : string ,
172+ options : Partial < TarOptions > = defaultTarOptions ,
173+ ) : AsyncGenerator < Buffer , void , void > {
174+ const opts = { ...defaultTarOptions , ...options } ;
175+ const entryGen = walkDirectory ( baseDir , '' , {
176+ fs : opts . fs ,
177+ blockSize : opts . blockSize ,
178+ } ) ;
179+
180+ for await ( const entry of entryGen ) {
123181 yield createHeader ( entry . path , entry . stat , entry . type ) ;
124182
125- if ( entry . type === TarTypes . FILE ) {
126- // Get file contents
127- yield * readFile ( path . join ( baseDir , entry . path ) ) ;
183+ if ( entry . type === EntryTypes . FILE ) {
184+ yield * readFile ( path . join ( baseDir , entry . path ) , {
185+ fs : opts . fs ,
186+ blockSize : opts . blockSize ,
187+ } ) ;
128188 }
129189 }
130190
131191 // End-of-archive marker - two 512-byte null blocks
132- yield Buffer . alloc ( BLOCK_SIZE , 0 ) ;
133- yield Buffer . alloc ( BLOCK_SIZE , 0 ) ;
192+ yield Buffer . alloc ( opts . blockSize , 0 ) ;
193+ yield Buffer . alloc ( opts . blockSize , 0 ) ;
134194}
135195
136196export { createHeader , readFile , createTar } ;
0 commit comments