1
- import type { TarType } from './types' ;
1
+ import type { TarType , DirectoryContent } from './types' ;
2
2
import fs from 'fs' ;
3
+ import path from 'path' ;
4
+ import * as errors from './errors' ;
3
5
4
6
/**
5
7
* The size for each tar block. This is usually 512 bytes.
@@ -15,8 +17,13 @@ function computeChecksum(header: Buffer): number {
15
17
}
16
18
17
19
function createHeader ( filePath : string , stat : fs . Stats , type : TarType ) : Buffer {
18
- const size = type === '0' ? stat . size : 0 ;
20
+ if ( filePath . length < 1 || filePath . length > 255 ) {
21
+ throw new errors . ErrorVirtualTarInvalidFileName (
22
+ 'The file name must be longer than 1 character and shorter than 255 characters' ,
23
+ ) ;
24
+ }
19
25
26
+ const size = type === '0' ? stat . size : 0 ;
20
27
const header = Buffer . alloc ( BLOCK_SIZE , 0 ) ;
21
28
22
29
// The TAR headers follow this structure
@@ -41,10 +48,10 @@ function createHeader(filePath: string, stat: fs.Stats, type: TarType): Buffer {
41
48
// 500 12 '\0' (unused)
42
49
43
50
// FIXME: Assuming file path is under 100 characters long
44
- header . write ( filePath , 0 , 100 , 'utf8' ) ;
45
- // File permissions name will be null
46
- // Owner uid will be null
47
- // Owner gid will be null
51
+ header . write ( filePath . slice ( 0 , 99 ) . padEnd ( 100 , '\0' ) , 0 , 100 , 'utf8' ) ;
52
+ header . write ( stat . mode . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 100 , 12 , 'ascii' ) ;
53
+ header . write ( stat . uid . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 108 , 12 , 'ascii' ) ;
54
+ header . write ( stat . gid . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 116 , 12 , 'ascii' ) ;
48
55
header . write ( size . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 124 , 12 , 'ascii' ) ;
49
56
// Mtime will be null
50
57
header . write ( ' ' , 148 , 8 , 'ascii' ) ; // Placeholder for checksum
@@ -56,7 +63,7 @@ function createHeader(filePath: string, stat: fs.Stats, type: TarType): Buffer {
56
63
// Owner group name will be null
57
64
// Device major will be null
58
65
// Device minor will be null
59
- // Extended file name will be null
66
+ header . write ( filePath . slice ( 100 ) . padEnd ( 155 , '\0' ) , 345 , 155 , 'utf8' ) ;
60
67
61
68
// Updating with the new checksum
62
69
const checksum = computeChecksum ( header ) ;
@@ -86,18 +93,48 @@ async function* readFile(filePath: string): AsyncGenerator<Buffer, void, void> {
86
93
}
87
94
}
88
95
89
- // TODO: change path from filepath to a basedir (plus get a fs)
90
- async function * createTar ( filePath : string ) : AsyncGenerator < Buffer , void , void > {
91
- // Create header
92
- const stat = await fs . promises . stat ( filePath ) ;
93
- yield createHeader ( filePath , stat , '0' ) ;
94
- // Get file contents
95
- yield * readFile ( filePath ) ;
96
- // End-of-archive marker
96
+ /**
97
+ * Traverse a directory recursively and yield file entries.
98
+ */
99
+ async function * walkDirectory (
100
+ baseDir : string ,
101
+ relativePath : string = '' ,
102
+ ) : AsyncGenerator < DirectoryContent > {
103
+ const entries = await fs . promises . readdir ( path . join ( baseDir , relativePath ) ) ;
104
+
105
+ // Sort the entries lexicographically
106
+ for ( const entry of entries . sort ( ) ) {
107
+ const fullPath = path . join ( baseDir , relativePath , entry ) ;
108
+ const stat = await fs . promises . stat ( fullPath ) ;
109
+ const tarPath = path . join ( relativePath , entry ) ;
110
+
111
+ if ( stat . isDirectory ( ) ) {
112
+ yield { path : tarPath + '/' , stat : stat , type : '5' } ;
113
+ yield * walkDirectory ( baseDir , path . join ( relativePath , entry ) ) ;
114
+ } else if ( stat . isFile ( ) ) {
115
+ yield { path : tarPath , stat : stat , type : '0' } ;
116
+ }
117
+ }
118
+ }
119
+
120
+ async function * createTar ( baseDir : string ) : AsyncGenerator < Buffer , void , void > {
121
+ for await ( const entry of walkDirectory ( baseDir ) ) {
122
+ // Create header
123
+ yield createHeader ( entry . path , entry . stat , entry . type ) ;
124
+
125
+ if ( entry . type === '0' ) {
126
+ // Get file contents
127
+ yield * readFile ( path . join ( baseDir , entry . path ) ) ;
128
+ }
129
+ }
130
+
131
+ // End-of-archive marker - two 512-byte null blocks
97
132
yield Buffer . alloc ( BLOCK_SIZE , 0 ) ;
98
133
yield Buffer . alloc ( BLOCK_SIZE , 0 ) ;
99
134
}
100
135
136
+ // NOTE: probably need to remove this, idk
137
+ // this is a library and should only worry about tarring itself and not writing to fs
101
138
async function writeArchive ( inputFile : string , outputFile : string ) {
102
139
const fileHandle = await fs . promises . open ( outputFile , 'w+' ) ;
103
140
for await ( const chunk of createTar ( inputFile ) ) {
0 commit comments