1- import type { TarType } from './types' ;
1+ import type { TarType , DirectoryContent } from './types' ;
22import fs from 'fs' ;
3+ import path from 'path' ;
4+ import * as errors from './errors' ;
35
46/**
57 * The size for each tar block. This is usually 512 bytes.
@@ -15,8 +17,13 @@ function computeChecksum(header: Buffer): number {
1517}
1618
1719function 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+ }
1925
26+ const size = type === '0' ? stat . size : 0 ;
2027 const header = Buffer . alloc ( BLOCK_SIZE , 0 ) ;
2128
2229 // The TAR headers follow this structure
@@ -41,10 +48,10 @@ function createHeader(filePath: string, stat: fs.Stats, type: TarType): Buffer {
4148 // 500 12 '\0' (unused)
4249
4350 // 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' ) ;
4855 header . write ( size . toString ( 8 ) . padStart ( 7 , '0' ) + '\0' , 124 , 12 , 'ascii' ) ;
4956 // Mtime will be null
5057 header . write ( ' ' , 148 , 8 , 'ascii' ) ; // Placeholder for checksum
@@ -56,7 +63,7 @@ function createHeader(filePath: string, stat: fs.Stats, type: TarType): Buffer {
5663 // Owner group name will be null
5764 // Device major will be null
5865 // Device minor will be null
59- // Extended file name will be null
66+ header . write ( filePath . slice ( 100 ) . padEnd ( 155 , '\0' ) , 345 , 155 , 'utf8' ) ;
6067
6168 // Updating with the new checksum
6269 const checksum = computeChecksum ( header ) ;
@@ -86,18 +93,48 @@ async function* readFile(filePath: string): AsyncGenerator<Buffer, void, void> {
8693 }
8794}
8895
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
97132 yield Buffer . alloc ( BLOCK_SIZE , 0 ) ;
98133 yield Buffer . alloc ( BLOCK_SIZE , 0 ) ;
99134}
100135
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
101138async function writeArchive ( inputFile : string , outputFile : string ) {
102139 const fileHandle = await fs . promises . open ( outputFile , 'w+' ) ;
103140 for await ( const chunk of createTar ( inputFile ) ) {
0 commit comments