From e91222af2069a6049dd7809dc3e9b77e10e4d519 Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Wed, 8 Jan 2025 16:00:22 +0200
Subject: [PATCH 1/8] Add validation for exported ActivityPub tarballs

---
 out/test-export-2024-01-01.tar                | Bin 12288 -> 5120 bytes
 src/verify.ts                                 |  89 ++++++++++++++++++
 test/fixtures/account2.tar                    | Bin 43008 -> 0 bytes
 .../tarball-samples/missing-actor.tar         | Bin 0 -> 10081 bytes
 .../tarball-samples/missing-manifest.tar      | Bin 0 -> 9097 bytes
 .../fixtures/tarball-samples/valid-export.tar | Bin 0 -> 20992 bytes
 test/index.spec.ts                            |   6 +-
 test/verify.spec.ts                           |  76 +++++++++++++++
 8 files changed, 169 insertions(+), 2 deletions(-)
 create mode 100644 src/verify.ts
 delete mode 100644 test/fixtures/account2.tar
 create mode 100644 test/fixtures/tarball-samples/missing-actor.tar
 create mode 100644 test/fixtures/tarball-samples/missing-manifest.tar
 create mode 100644 test/fixtures/tarball-samples/valid-export.tar
 create mode 100644 test/verify.spec.ts

diff --git a/out/test-export-2024-01-01.tar b/out/test-export-2024-01-01.tar
index 12c677283ff971b17f0f3e4fdea1e91b201b8c9b..e592d65ba54d5bc255944689dd11d8ea5410f9f7 100644
GIT binary patch
delta 287
zcmZojXwaC@Eo5wNY;0m^Y^GpfXlQC=tiWJ8Igv4CGb3XcBeI-<k+C^kj=70Z%D}+X
zM8VL+97(^inSqG{g8@W8D+{AyW{Q%Pf>KFpafxnXa!G!Xk`9*wkY8Dl3K9=UEt-5l
z*kSVq?OBYIM4F@x3=EA7(2O)OFffA~Days@3Nf-EzqrH@Y#Neze)%P-FtOzPypq(s
k5|A|^K<gD?e4zccT(O%MYHwhiq=D*rb7O?#HUF~#07n2wlmGw#

literal 12288
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
JQv-);;J;LPCIJ8d

diff --git a/src/verify.ts b/src/verify.ts
new file mode 100644
index 0000000..d8a6479
--- /dev/null
+++ b/src/verify.ts
@@ -0,0 +1,89 @@
+import * as tar from 'tar-stream'
+import { Readable } from 'stream'
+import YAML from 'yaml'
+
+/**
+ * Validates the structure and content of an exported ActivityPub tarball.
+ * @param tarBuffer - A Buffer containing the .tar archive.
+ * @returns A promise that resolves to an object with `valid` (boolean) and `errors` (string[]).
+ */
+export async function validateExportStream(
+  tarBuffer: Buffer
+): Promise<{ valid: boolean; errors: string[] }> {
+  const extract = tar.extract()
+  const errors: string[] = []
+  const requiredFiles = [
+    'manifest.yaml',
+    'activitypub/actor.json',
+    'activitypub/outbox.json'
+  ]
+  const foundFiles = new Set()
+
+  return await new Promise((resolve) => {
+    extract.on('entry', (header, stream, next) => {
+      const fileName = header.name
+      foundFiles.add(fileName)
+
+      let content = ''
+      stream.on('data', (chunk) => {
+        content += chunk.toString()
+      })
+
+      stream.on('end', () => {
+        try {
+          // Validate JSON files
+          if (fileName.endsWith('.json')) {
+            JSON.parse(content) // Throws an error if content is not valid JSON
+          }
+
+          // Validate manifest file
+          if (fileName === 'manifest.yaml') {
+            const manifest = YAML.parse(content)
+            if (!manifest['ubc-version']) {
+              errors.push('Manifest is missing required field: ubc-version')
+            }
+            if (!manifest.contents?.activitypub) {
+              errors.push(
+                'Manifest is missing required field: contents.activitypub'
+              )
+            }
+          }
+        } catch (error: any) {
+          errors.push(`Error processing file ${fileName}: ${error.message}`)
+        }
+        next()
+      })
+
+      stream.on('error', (error) => {
+        errors.push(`Stream error on file ${fileName}: ${error.message}`)
+        next()
+      })
+    })
+
+    extract.on('finish', () => {
+      // Check if all required files are present
+      for (const file of requiredFiles) {
+        if (!foundFiles.has(file)) {
+          errors.push(`Missing required file: ${file}`)
+        }
+      }
+
+      resolve({
+        valid: errors.length === 0,
+        errors
+      })
+    })
+
+    extract.on('error', (error) => {
+      errors.push(`Error during extraction: ${error.message}`)
+      resolve({
+        valid: false,
+        errors
+      })
+    })
+
+    // Convert Buffer to a Readable stream and pipe it to the extractor
+    const stream = Readable.from(tarBuffer)
+    stream.pipe(extract)
+  })
+}
diff --git a/test/fixtures/account2.tar b/test/fixtures/account2.tar
deleted file mode 100644
index 47e8cca1371956ca5e1120fdfc7ed118f875ae06..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 43008
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
IXXjt?{}H@W-v9sr

diff --git a/test/fixtures/tarball-samples/missing-actor.tar b/test/fixtures/tarball-samples/missing-actor.tar
new file mode 100644
index 0000000000000000000000000000000000000000..64ac15bc3378a0855742e3b4f7bbcc2e00a5d877
GIT binary patch
literal 10081
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
Cir;Sl

literal 0
HcmV?d00001

diff --git a/test/fixtures/tarball-samples/missing-manifest.tar b/test/fixtures/tarball-samples/missing-manifest.tar
new file mode 100644
index 0000000000000000000000000000000000000000..824bf88bcaf238ebe2e73bf3bb06ca36d589a024
GIT binary patch
literal 9097
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
G-Twd(ORiS{

literal 0
HcmV?d00001

diff --git a/test/fixtures/tarball-samples/valid-export.tar b/test/fixtures/tarball-samples/valid-export.tar
new file mode 100644
index 0000000000000000000000000000000000000000..7825814682fd103596453055de8dee67fc9455b5
GIT binary patch
literal 20992
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

literal 0
HcmV?d00001

diff --git a/test/index.spec.ts b/test/index.spec.ts
index 97a4228..6165093 100644
--- a/test/index.spec.ts
+++ b/test/index.spec.ts
@@ -35,13 +35,15 @@ describe('exportActorProfile', () => {
 describe('importActorProfile', () => {
   it('extracts and verifies contents from account2.tar', async () => {
     // Load the tar file as a buffer
-    const tarBuffer = fs.readFileSync('test/fixtures/account2.tar')
+    const tarBuffer = fs.readFileSync(
+      'test/fixtures/tarball-samples/valid-export.tar'
+    )
 
     // Use the importActorProfile function to parse the tar contents
     const importedData = await importActorProfile(tarBuffer)
 
     // Log or inspect the imported data structure
-    console.log('Imported Data:', importedData)
+    // console.log('Imported Data:', importedData)
 
     // Example assertions to check specific files and content
     expect(importedData).to.have.property('activitypub/actor.json')
diff --git a/test/verify.spec.ts b/test/verify.spec.ts
new file mode 100644
index 0000000..a524c61
--- /dev/null
+++ b/test/verify.spec.ts
@@ -0,0 +1,76 @@
+import { expect } from 'chai'
+import { readFileSync } from 'fs'
+import { validateExportStream } from '../src/verify'
+
+describe('validateExportStream', () => {
+  it('should validate a valid tarball', async () => {
+    // Load a valid tarball (e.g., exported-profile-valid.tar)
+    const tarBuffer = readFileSync(
+      'test/fixtures/tarball-samples/valid-export.tar'
+    )
+    const result = await validateExportStream(tarBuffer)
+
+    expect(result.valid).to.be.true
+    expect(result.errors).to.be.an('array').that.is.empty
+  })
+
+  it('should fail if manifest.yaml is missing', async () => {
+    // Load a tarball with missing manifest.yaml
+    const tarBuffer = readFileSync(
+      'test/fixtures/tarball-samples/missing-manifest.tar'
+    )
+    const result = await validateExportStream(tarBuffer)
+
+    expect(result.valid).to.be.false
+  })
+
+  it('should fail if actor.json is missing', async () => {
+    // Load a tarball with missing actor.json
+    const tarBuffer = readFileSync(
+      'test/fixtures/tarball-samples/missing-actor.tar'
+    )
+    const result = await validateExportStream(tarBuffer)
+
+    expect(result.valid).to.be.false
+    console.log(JSON.stringify(result.errors))
+  })
+
+  // it('should fail if outbox.json is missing', async () => {
+  //   // Load a tarball with missing outbox.json
+  //   const tarBuffer = readFileSync(
+  //     'test/fixtures/exported-profile-missing-outbox.tar'
+  //   )
+  //   const result = await validateExportStream(tarBuffer)
+
+  //   expect(result.valid).to.be.false
+  //   expect(result.errors).to.include(
+  //     'Missing required file: activitypub/outbox.json'
+  //   )
+  // })
+
+  // it('should fail if actor.json contains invalid JSON', async () => {
+  //   // Load a tarball with invalid JSON in actor.json
+  //   const tarBuffer = readFileSync(
+  //     'test/fixtures/exported-profile-invalid-actor-json.tar'
+  //   )
+  //   const result = await validateExportStream(tarBuffer)
+
+  //   expect(result.valid).to.be.false
+  //   expect(result.errors).to.include(
+  //     'Error processing file activitypub/actor.json: Unexpected token } in JSON at position 42'
+  //   )
+  // })
+
+  // it('should fail if manifest.yaml is invalid', async () => {
+  //   // Load a tarball with invalid manifest.yaml
+  //   const tarBuffer = readFileSync(
+  //     'test/fixtures/exported-profile-invalid-manifest.tar'
+  //   )
+  //   const result = await validateExportStream(tarBuffer)
+
+  //   expect(result.valid).to.be.false
+  //   expect(result.errors).to.include(
+  //     'Manifest is missing required field: ubc-version'
+  //   )
+  // })
+})

From ac6fb51b7153efc19e491b166f244cd90628e92a Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Thu, 9 Jan 2025 16:32:28 +0200
Subject: [PATCH 2/8] Add validateExportStream to ESM build script

---
 build-dist.sh | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/build-dist.sh b/build-dist.sh
index 2f28cae..ac0812a 100755
--- a/build-dist.sh
+++ b/build-dist.sh
@@ -1,8 +1,9 @@
-mkdir ./dist/esm
+mkdir -p ./dist/esm
 cat >dist/esm/index.js <<!EOF
 import cjsModule from "../index.js";
 export const exportActorProfile = cjsModule.exportActorProfile;
 export const importActorProfile = cjsModule.importActorProfile;
+export const validateExportStream = cjsModule.validateExportStream;
 !EOF
 
 cat >dist/esm/package.json <<!EOF

From 6b5266fe885bee23d7bf308bf0c6ada4cdec53f8 Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Thu, 9 Jan 2025 16:32:38 +0200
Subject: [PATCH 3/8] Export verify module from index.ts

---
 src/index.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/index.ts b/src/index.ts
index fc7e808..b7a86e1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -254,3 +254,5 @@ function addMediaFile(
     lastModified: new Date().toISOString()
   }
 }
+
+export * from './verify.js'

From e7ef58f411bd353851142cbe734a800d08f7a431 Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Thu, 9 Jan 2025 18:43:56 +0200
Subject: [PATCH 4/8] Refactor importActorProfile to use ReadableStream and
 improve error handling

---
 src/index.ts | 49 ++++++++++++++++++++++++-------------------------
 1 file changed, 24 insertions(+), 25 deletions(-)

diff --git a/src/index.ts b/src/index.ts
index b7a86e1..90f90b1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,7 +4,7 @@
 import * as tar from 'tar-stream'
 import { type Pack } from 'tar-stream'
 import YAML from 'yaml'
-import { Readable } from 'stream'
+import { type Readable } from 'stream'
 
 export interface ActorProfileOptions {
   actorProfile?: any
@@ -178,14 +178,21 @@ export async function exportActorProfile({
   }
 }
 
-export async function importActorProfile(tarBuffer: Buffer): Promise<any> {
+/**
+ * Imports an ActivityPub profile from a .tar archive stream.
+ * @param tarStream - A ReadableStream containing the .tar archive.
+ * @returns A promise that resolves to the parsed profile data.
+ */
+export async function importActorProfile(
+  tarStream: Readable
+): Promise<Record<string, any>> {
   const extract = tar.extract()
   const result: Record<string, any> = {}
 
   return await new Promise((resolve, reject) => {
     extract.on('entry', (header, stream, next) => {
+      const fileName = header.name
       let content = ''
-      console.log(`Extracting file: ${header.name}`)
 
       stream.on('data', (chunk) => {
         content += chunk.toString()
@@ -193,42 +200,34 @@ export async function importActorProfile(tarBuffer: Buffer): Promise<any> {
 
       stream.on('end', () => {
         try {
-          if (header.name.endsWith('.json')) {
-            result[header.name] = JSON.parse(content)
-          } else if (
-            header.name.endsWith('.yaml') ||
-            header.name.endsWith('.yml')
-          ) {
-            result[header.name] = YAML.parse(content)
-          } else if (header.name.endsWith('.csv')) {
-            result[header.name] = content
+          if (fileName.endsWith('.json')) {
+            result[fileName] = JSON.parse(content)
+          } else if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
+            result[fileName] = YAML.parse(content)
+          } else if (fileName.endsWith('.csv')) {
+            result[fileName] = content
           }
-          console.log(`Successfully parsed: ${header.name}`)
-        } catch (error) {
-          console.error(`Error processing file ${header.name}:`, error)
-          reject(error)
+        } catch (error: any) {
+          reject(new Error(`Error processing file ${fileName}: ${error}`))
         }
         next()
       })
 
-      stream.on('error', (error) => {
-        console.error(`Stream error on file ${header.name}:`, error)
-        reject(error)
+      stream.on('error', (error: any) => {
+        reject(new Error(`Stream error on file ${fileName}: ${error}`))
       })
     })
 
     extract.on('finish', () => {
-      console.log('Extraction complete', result)
       resolve(result)
     })
 
     extract.on('error', (error) => {
-      console.error('Error during extraction:', error)
-      reject(error)
+      reject(new Error(`Error during extraction: ${error}`))
     })
 
-    const stream = Readable.from(tarBuffer)
-    stream.pipe(extract)
+    // Pipe the ReadableStream into the extractor
+    tarStream.pipe(extract)
   })
 }
 
@@ -255,4 +254,4 @@ function addMediaFile(
   }
 }
 
-export * from './verify.js'
+export * from './verify'

From c86922797a2d84f62a3310bd6f2caabd5ce778dd Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Thu, 9 Jan 2025 18:44:07 +0200
Subject: [PATCH 5/8] Refactor validateExportStream to accept ReadableStream
 and improve file validation logic

---
 src/verify.ts | 27 ++++++++++++++++-----------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/src/verify.ts b/src/verify.ts
index d8a6479..8fab645 100644
--- a/src/verify.ts
+++ b/src/verify.ts
@@ -1,27 +1,30 @@
 import * as tar from 'tar-stream'
-import { Readable } from 'stream'
+import { type Readable } from 'stream'
 import YAML from 'yaml'
 
 /**
  * Validates the structure and content of an exported ActivityPub tarball.
- * @param tarBuffer - A Buffer containing the .tar archive.
+ * @param tarStream - A ReadableStream containing the .tar archive.
  * @returns A promise that resolves to an object with `valid` (boolean) and `errors` (string[]).
  */
 export async function validateExportStream(
-  tarBuffer: Buffer
+  tarStream: Readable
 ): Promise<{ valid: boolean; errors: string[] }> {
+  console.log('Validating export stream...')
+  console.log('length of tarStream: ', tarStream)
   const extract = tar.extract()
   const errors: string[] = []
   const requiredFiles = [
-    'manifest.yaml',
+    'manifest.yaml', // or 'manifest.yml'
     'activitypub/actor.json',
     'activitypub/outbox.json'
-  ]
-  const foundFiles = new Set()
+  ].map((file) => file.toLowerCase()) // Normalize to lowercase for consistent comparison
+  const foundFiles = new Set<string>()
 
   return await new Promise((resolve) => {
     extract.on('entry', (header, stream, next) => {
-      const fileName = header.name
+      const fileName = header.name.toLowerCase() // Normalize file name
+      console.log(`Processing file: ${fileName}`) // Log the file name
       foundFiles.add(fileName)
 
       let content = ''
@@ -37,7 +40,7 @@ export async function validateExportStream(
           }
 
           // Validate manifest file
-          if (fileName === 'manifest.yaml') {
+          if (fileName === 'manifest.yaml' || fileName === 'manifest.yml') {
             const manifest = YAML.parse(content)
             if (!manifest['ubc-version']) {
               errors.push('Manifest is missing required field: ubc-version')
@@ -61,6 +64,9 @@ export async function validateExportStream(
     })
 
     extract.on('finish', () => {
+      console.log('Found files:', Array.from(foundFiles)) // Debug log
+      console.log('Required files:', requiredFiles) // Debug log
+
       // Check if all required files are present
       for (const file of requiredFiles) {
         if (!foundFiles.has(file)) {
@@ -82,8 +88,7 @@ export async function validateExportStream(
       })
     })
 
-    // Convert Buffer to a Readable stream and pipe it to the extractor
-    const stream = Readable.from(tarBuffer)
-    stream.pipe(extract)
+    // Pipe the ReadableStream into the extractor
+    tarStream.pipe(extract)
   })
 }

From 6afa20dcbce496d0a0feb01404d394b3c65e60a9 Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Thu, 9 Jan 2025 18:54:35 +0200
Subject: [PATCH 6/8] Add ReadableStream support for importActorProfile and
 validateExportStream; update dependencies

---
 out/test-export-2024-01-01.tar                | Bin 5120 -> 5120 bytes
 package.json                                  |   2 +
 .../tarball-samples/invalid-actor.tar         | Bin 0 -> 11112 bytes
 .../tarball-samples/invalid-manifest.tar      | Bin 0 -> 10306 bytes
 .../tarball-samples/missing-outbox.tar        | Bin 0 -> 11044 bytes
 test/index.spec.ts                            |   4 +-
 test/verify.spec.ts                           |  74 +++++++++---------
 7 files changed, 42 insertions(+), 38 deletions(-)
 create mode 100644 test/fixtures/tarball-samples/invalid-actor.tar
 create mode 100644 test/fixtures/tarball-samples/invalid-manifest.tar
 create mode 100644 test/fixtures/tarball-samples/missing-outbox.tar

diff --git a/out/test-export-2024-01-01.tar b/out/test-export-2024-01-01.tar
index e592d65ba54d5bc255944689dd11d8ea5410f9f7..46bc8660163cf0d763e0cec1f31236717bc90773 100644
GIT binary patch
delta 136
zcmZqBXwaC@D`0MJZenJjU|?uyYHX&!U^+RGF=aC&V-F)j#=ywb2rk2nEMshDXs*Cu
d0MWzR!;H{lVqjzlm*GT~F*G+p=;8g(1^`0i9=HGi

delta 136
zcmZqBXwaC@D`0GFVrXopU|?uyYGkayU^+RGF=aC&V-F)j#=ywf94^C*EMshDV4}ca
d0MWzR!;H{lVqjnfm*GT~F*G+u=;8g(1^_V79(w=)

diff --git a/package.json b/package.json
index 38e5b79..ce42ab6 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,8 @@
     "./package.json": "./package.json"
   },
   "dependencies": {
+    "eslint-plugin-n": "^17.15.1",
+    "stream": "^0.0.3",
     "tar-stream": "^3.1.7",
     "yaml": "^2.5.1"
   },
diff --git a/test/fixtures/tarball-samples/invalid-actor.tar b/test/fixtures/tarball-samples/invalid-actor.tar
new file mode 100644
index 0000000000000000000000000000000000000000..4c2553c71746565e026c242794695651006fbb05
GIT binary patch
literal 11112
zcmeHNc{r4B_aDn3JK2|zB_d1q3L(3(hwS@4lzk~0d&p9DWgU_wA$uXaY}rz2$P#7W
zLYBPG)Yse86|cVU?{{6_Ki=aS^E}VY+~=In^SRG`?sFbBB@9e55K<oB=Ft0g@Yg?T
z5F<$5$-%*iQPSMp$<5J~QO5JCld~%$yATJ@Wi0{_HvOWX_5RDv6Ay%j`56ra`Zk=}
zw_$NWI6n<b0({I23~Ou*k(5@|)aBteK1jn)3h=-BS^uKI+=SfR+QiIc38JjMM5G<6
zb`w3C3gZ+pE!y%8MywTTFm)(;BomV)F@<g`1!JU)W&<YqhKb30)$X1_B`)?hg~1Mp
zayP7P56w)SQL2qRo{fTaD$Zgm`z)_Oivr>XpYed8b^CRnTSCM9j!ji~^kg%%RJv4p
zDM=|0vXdX==$@xw)#nF+)ReGs65qA!h61P|-TS@cm{cIpNx<=gyyfA(q-m__>f~(s
zZ@*o3zk~aW-}4DQijBNf^>qW?miQUiV3NR_L!!bv!pj;DPCPx=+Zg-iZC>*uYWoT%
z^T*gPt(O-9HqRxOk6O%@2QWI=_!9d<w{Af1#=_A+8je?FKm;K6ChfDlBw?2L;QORO
zmo{tyFwibYW9Z#oWKH64DDLn_^DujNFJ_s)$H3RR`d#4Wst76T9yN)jO8eRKz8Iw=
zUBR=Pk{L^qv0%Zn#2nk?Y~eV$k%ySFFIPHbOKDOKuP!79m^VIkol}A6i%%Juh@>JS
zNa;E#n1*yBt(R4jDlT*s5gMjbCqJ~cM>yJUFt!RQHJkCYRyQ|vyGPDL7dO1At4oUA
zJdJ#uXHWRuqE*FNZ_vopF?vu@WN7Qny>WtZYf8q-u*vJ>_z8z483UtBG?}j;Yoq#^
zdm1NOu#_xlvSKX6NH}8TS?UP7eFe@B4@JwfxX6o0@IHxLXl3&>l+2eFrAf|kbCnQ!
zE&?vh<j|AmM}E|z5dMBD;QifW3u>XkRyMnGka+p81bD?yuI@_QuiZ<KXYr%TOx03(
zvGk0IirK(EiM=8KlWLcw{v5e^W45PTZYpxj@*R?Bd8=khBvXjdi6p84mLU1?d!~2-
zJUW9qMGmHn@$X6VN0X(PmN;OO{9-3abBwY6CWc6GAf{vzIj`Zz@65&vtrrmlEJ9lK
z!``>Ieng5*bP$N`FlIwd9c`^FU0k`nOrZ|n*gZe(XRY}0`|B5Wm)&{p*uaY)ym%ul
zG_~JB>TO1=Si=)adCdx{JC%y>C<Nva);2Lz*evifr-qmJ-dwvNq@o&qR-<5WD%%@D
zhW7eZkOnu*(q>fETFxMf*sC~>=5Enoq*q%RpV*3{!)dwaCKmDnHnU=HKh}4eq1lTN
z#_GCo(<vxUUS|W-76)_=a%=|egr9dRE@c0l6Y%`0+oLl@=!gt#65Uo2rnKPVD>Y9u
z(xO<@^471UR>&^cce)5V@mv;{=p^Wgr7X;`B2w8hdLrN~-8v0RvP@>Ompi8XNj~_b
zFj>(%m6HM;ayw$xOy@9D2={IpRkysBkgAx33z*^y@t2YDTyDW{$4tKD@WS%Ec2#R9
zk<Se*s&pBj3YwYOnoTq$doGi-oTmZUgT(yzccT!vPkcvY&%rn5j~H}K5MYq~U(r^L
zO}-VMkphUep~RGse}`I5E(n)mCue)7k|q0qFz;jDCqn$fs!D>YkYaZ(#d53fWYgW+
zR5t+}Ksxc=J>UR@N;c#<>tSRwHFvdjw{`Wp>Sp$Ri8_R92P4RHL<CW^{Y314G=2<+
z;Rm&}ur>WJkmEQaa^Lj)GciNvEU29G1{QhV!!Sc(0gscLtC^E0x1EcV<4@ip!`&~T
zZZO%<X;_FXcyXH($GA08{0wV7?-TmRDFm-&LeQWX0hy7|N3prlu|w$}{YEqBI*Pff
z^`&Ufgk^Hnjp$sM7KPLKdCIg=?kKedCfwCOd&`EHRW$s;HJTDzQgR19P)E*V<t*MF
z$Ego1R?cPjpOdL!eDF_pWK+3^^-18xXwVpx=3TIGsYs<)9WC?(&Ep9p&c>vhhWtg8
zxHQX;d7XQ&<mxk>*cKQ_rmZbrjn}eyT_$`7PYn+v?ffU>o3K1qQ)m~ypaChul^|%K
z$%HurY*Im%StzfNuh;FqMf=F>Iarv;J^K!)iPg|4n`d^P@=Bj+)x6TaHvCv9r?+Bg
zNN{PeIy!8K^Ie{x4S&8Zc)NGpFQQ!kxg<}bgZ;LR<fsu&)j3DwzT1~1tSl5qFB7^u
z)x4U?eP$QcN}=JKm%HUU(7RMEZf8*<^fD=NM|SwmtKbRW(TWllRxJ$$8JaLYR<dkf
zVzqMR?l%3-%w*CtVA)8HJc1&9Iu@3Uu)<}M#F(b52C$bUm+#bL*%1c6<nA7L62^;*
z?s0dP+UpcSFSOreGolNvMpr*!rxt9+!bX^9(v&3FidksIFk4~&Xi~A)>NfPrxLy{y
zo}D>H&|BPAGY0OjST3mz$5S@hZIsL(nayN067Zl=%6ZT_%B>_XlS)eq*${=S&~!~)
ztp7Yj*45dThO4OXcBVF){9F-@IRk@>>ifD%8gaiNlk}LxK}3s5a(Z=H<SwrD8xjN|
zIe0K+I3pIZ7VIlK{8Cr^ZI1{kySsmhX~t#qGS`m8GBe6#2;s_yXyu59FlM8gSB-38
zrtdq?ULiX7n%lJE^Mj&`3L2PKbSL{y`hO5Gy~f+cAFl#q<ES~tAIjwU#A>-u2rZAW
zcl4p2I@oHR(gehJX1!6Z>$-W{Bk^4{#jH2A&n9X3KbaP5ej&GG^t;BiE*)qg7wEc_
zm{ne!ObyAJGYkbE@30CM<oB#It{z$VI7u*sX^t&*(eSOBMAuG^AeFMEW<s%cKQ6Yz
z@f)&6%z+z1qQu?VS~!7@*BlMkW$vgKkS3lDwO}TnYMLdT%WaSfF1X>C-h|e6O4~6d
zRJONITT=~xOUm0|2A8X^ypu^I5u%!R?{+H|hlZ-0lm41Rb652l2VI8z_K5bG>Sx^(
z7kUCj=pPloEFUjTI8%nD*8ro3-PBf6MyTec#PyWU5?k*&nD1%|sQR-vOPEGmuTI3t
zDvNnMXFZ{}!qC}XTi-gCeq6X}5r0~8)E@G_f6-c2a>PU186#xV9XnMheEtbE_{5so
zvAq;~hux*uQr_+r-u9iuY#rRD8xi7Nw~0auhoO2A9`2_~>rZR)(7Y?>7RTm=!ldqL
zYlvM)p0BAj+Qq&*mfOzrK-ssdvfXB2vbCKfp>YK7b%Y#rMw9z<GlVUREnkx@KRiWQ
z4zi-j_N5upEe8qHWNU7QSY@$|#VS-*w(qpQ-?kc<n&(KUNZ|Q6(c0}%ku<j1emu2=
z+Gykhk@H&Mgmur5rcfPlwmIhF_Tt0f2gArT@loT>R-*JMXj1D3g(7BV?lF%YE-7JR
z%+VzufpJn_zi4v~I*Eh@l{ZgUXI7RU)a>e0W2>ins=mE(hZp}e`I(4k*s$?Riz+|n
z?J2@}vS7Iu71K@9)$2iR8CPSE^(2JSlS&7t6-A4X1$#W7=7bXljnTebC7&V$PeGVm
z*x8+MtE5dg-GLv_hDc2iiBb->qy^&H`)vz6{qmW#FD)>9_APM1A!^Xp(|ATOm`8#q
z`fBm~DxZNXk;^=ol>6xo@nF2AF}FSNsZ`+Ia2L2%FSejHd2**v;uz)F05HT=zd<2q
zw1q`?;wsRF4lXn0=n3<&@s!QGSI6cTPmtFvIqoV{F0Fg$gd4j0#9r;4j|zV+Q&E@i
zH<vj)Ds#&HuM4ekx8C`pz83E4U4;E{O2o!j76aGqaGCMUVFvG8;f{U^DU~1BJy=+Q
zFVGj<3SWqmxV74Uzw+aWuiXoPS=GB@GBd!4pBaI1qQVCN6^bdTdvhv=uG`y{?7&5$
zXUK~*an&E!6V<t^#l(YvQ7OBt!X2YXrzL8pflIz**S~HoeEBk3Gd*3G)C2r9Uww78
zU(BO-p3k;tZ{+^{J*(9FX$x~Z!mFJ!8=YxQU~%Cd15YA9Q6H&s;124jVxA(8y*F+n
z{ifRgt!rKAv)S|Z+n3h@grs$__pG%DYKLm~#%B@z@j>V>BIE7h#3B6&22^Uh6;R_V
z(;j>1$Xv9xjw+%C3j{(gj|XQ`=Z};Rpst|Y#s{sT|Cp=_nITZb|JVYxvVuU!l<~lw
z8mfQ5v6C}Of&lFDcm8_)k|1;?8ir3G6NC+&M1$$Z(REBkTc6&1DIQ!r1c79+Dyf@g
z2WNXfq3Ks2OCRw&pO<4#4A1crv)$>Q)#>w!oHP&@>Gq!vFD%7r3`p?|adlf7tHdp>
zH_TR`D7+b@?mo>*e6vPZXu_T#&jKu<UdQKVhTFNte6>)?s+x(bqmvdfm0_3Vhn;Qt
zrtE`(j=$JkTAW`H>Afg}&`0`hb9e%>QOY(pDTICRuJ@4fVth$0i&{67FsS<aLiOv?
zyTWN`mIZy~LfU-;4#;+FT9rGQ79(b?S0Q~)zLj&1wn|IhTakJ#LlJlJGOHkZ6F7)<
zs9QCA%-DW7x_yN`E++C3)FElukLH-|1i5gh>_v5jW(Rq~uG~hkSc7v*s^BX6_cpX$
z5az<f+uba=_>)_<g>g0xska}lxtEH{b9;K}-Jg}pbF0xpc#rZo4t#EVBvYjN?%u3)
zspYF&SYHAE6^9uJ4cvAHtJiJEh}OzeHbE4lW2_}O8aFKK{Ts?bS@^Y*WrwDyF5j>3
zTQg?m)_Yv~S!yp!cB(Z+YT$gS=qkOzHe1pjQC~CjIQ&cBShM4B|Ap7xYl00P{pDHF
z8&KT%GGarGv(R<K68Ef;cY3tnHK@M$8>dJYTM63vQpCLS6m-SyLfO3P_RgoW?3Ayy
zqt|)_C2(G^Ivc;qwi((6`@3JV;uBH$ykwCJ^);w9ZJMR7!l$n4v};<V{_>Q{*%`qf
z5SMN(LnPW;N^{IdHjIh6xj;5El|<Yku*Z&Nfto~Gwy8k&PAUo2S@cpGg;6454>8VR
zrc=XS;d*sXeJf;^x5!`7mW+4JPSVHe78#o}gcMxpo`oA*BuJ~oub%s)^JcL^VpnRr
zs=J?&VQm$%NSC@LI3!MPutA_I{LIDETkgZq?9eD8Iu2oTYTacBks}&PSG6c!*JbDg
zIxcwK=K;96@X}ZBa9QiEf+;3D%97-$T*O@8-d#N6s(S?#C+#k3hZ74zw8KftA`0Re
z?ZOJ;Pukrpa1Y77ytdYC!|N)FkG*9e7J;ph(CEqj$(f3n+2%7ZOS!9P#4Wyc-qRPV
z>4KZ7e5k0REJPVF%z<y!Q}O9DFi%ic)5&6Gb(mVb%nyr#NlJKK$H(`kUgG1HEue1+
zl1^xRoSs&S7Ayn#b~KD8+l(U`f$Kd2rXpXP)xD`MDM5$v*EusOILqHi9X$efON_NC
zcT+K;F*m))FjQSqb%Hp{Kb4(H7X0jbF!00i6J}cXK05bnkeF%V00w{&hJnCH-Y1bG
z$pBvnBZc^AUjhzvUg7}1Y$1^a-+zOXW!BVfrWJ8DL|w8@tBH!7c``?g(<~y4M504^
z+lWLJ`SJ+}<2(JavjrI2Tq{C8)^?Qhj;)hg`x7me{*iRjh%iAz$MMk&Qv7g)U&oss
zM2O$Fn!%g%Or=toAP_Q0{fN`3;fojz1UmOO;`9NL?2p3qve>s;E!B@&Ee4~|Ma>3$
za_kpMtkcsotQ2IE@R<)R6bZ2u4|3ov@$l2wn9_s3ATviSEcL)@|ID3|fu~JRC$*w?
zoPfeZ49|~J!{%y*I6Yjx`jS4y+)1|JDVG$F@(TzJom?jq6H^<gm1AnWU23w~yl$*R
z*Y^s<axmR`Hc(?gLh=JF)ZHK#044uXW4#r!dhTKA?1B>S53YSR-xYHj<|Pi^*uj17
z!oI9T+Wzq317*VtWo;MxPbI;WX;|n7aaKl^lp^QIb}xpU>_FQX@p&*I_OioJy+E9L
zk@UJM=j8%-4*JZzylZqFF>&H+ap7@7?c&3(VH`I{S_gNlx*U55?lWLXNQHjTTyuN`
z??>w{<1xiUD^Dv0uLM?<q<sj%^ol)QYgx!{l#`hMmdeky<E&T0b6T;v>&juK5&7Xv
z+V`BUDhZ)O*<C|vgJWzUw=b@~mG48xxNzfIvi-`;v7GxHIdrBlUgNvkF?xJgUctY5
z3(ht&ZBEk)XN2|%vf@^R39&jkA%tEJ56hgtsl*O*5sGhFEb6WVjEP!UBQ}k>uUdZI
zQZ#7dGX0a;sOpf}7&hv6XL+m*E4xZI?p)wDAHfk*F8WfD)%EPvXBYm90fJMQ3h_h@
z97FPoG18E#o8_#xSys`DHW;MiOq!OG<u6;S6vXQ8+&-;;Jcm~^HdZ54LJcB0?3?g%
z1UrG}>@IB`<2K<K`nuTSX!P!_?Cl~hg6w<3$|qX{<O~L|+Ob$pI^g-c74n(6m?lk3
zAZCe>`Oh3qLRTI@6C@#iPZeYtOZ;!lt3z1xM|11{voQB-kpw769z6weuyt`g+LQ>7
z1Own=<8ag{6971akZKMF`|kv?18n)n0*!1l9XbA(IXT%wO`YwJ26q0GhMJnQe0LA0
zLLs|)iH6z>b`B2q^1dM+9-bji;a43zibFk|uT+(}+qpWp9&&MWC_*~VX!oJnb||CS
z%D?F8>4tE<;C|I{y1OT!Emn&H?mxK`uAvdGp?PoB#aRQ!Ru5lgYDi^$crmMhEYrNo
z?%M*DZW+7c0(gb=?BIL*cP7#y?*C)&@84KoY#putx4!&uebMDP(*Bd%`1|_uTh{y|
zrUe>08OFg+Bmhz3o777p-wt&7TlIba*#zYKFPm#MrSEF?pHM(;;Uk|=*q3^k4siH?
z@VvsW3Q;@3$R`8#1rAd~JH)Tww|tM_AZjNU*<aolILsRZ#lZss<vx118P!!}?u^>D
zM7E0e1r9q6Eb0epJh(skn{LXlI#GLx$cE^?z+si(qq%#q`rpiwef3B+sM`hPIkI87
zFY}TKK+Wtxmr)Bv|EYhpuW~;XL-iP0joO!a*cPDj-wZxbH@7j0_Mephp})um<G##G
z4q+eS?@`;0KjtbDTvShy4aa?%hY8>u&C?^bAAbcHwc?3v2<{6UhIi}`A5l>KllI`R
z+EJZGR#o=}4!cBr6sJ+m_@f#tsv*d-0ct@3m1l6+8ImKy0N8TG8tAXeQ8(zwbbnvq
zusQNSN8tcF4&nR$WJBHkA=B}Ffy1aNj|jpc+q^%FZ^+zuUjRB#|2Ytc*m49gP<LlP
zGlG}qh+rJB4f{hNer7~5p!`3Fy`SgB0d^b#1k`QJ&x}Z-Jt7E)>{tE}h@Tm8o9@qn
xIK-ACfN}8j@S;B=7>8u3{}mW;hCc`65L<o=1{hdyK_Cj?uK^bXie^Nf{R?GR9$^3g

literal 0
HcmV?d00001

diff --git a/test/fixtures/tarball-samples/invalid-manifest.tar b/test/fixtures/tarball-samples/invalid-manifest.tar
new file mode 100644
index 0000000000000000000000000000000000000000..fb10d1a743df852326ece62c191a1eec51470757
GIT binary patch
literal 10306
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
azwo>-P;6j|0fDH1AK^nF&>CPL1o}VP8ZM#$

literal 0
HcmV?d00001

diff --git a/test/fixtures/tarball-samples/missing-outbox.tar b/test/fixtures/tarball-samples/missing-outbox.tar
new file mode 100644
index 0000000000000000000000000000000000000000..95b56e72d750bfa62e61f6c019de0315b78e6990
GIT binary patch
literal 11044
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

literal 0
HcmV?d00001

diff --git a/test/index.spec.ts b/test/index.spec.ts
index 6165093..ce805f6 100644
--- a/test/index.spec.ts
+++ b/test/index.spec.ts
@@ -5,6 +5,7 @@ import { exportActorProfile, importActorProfile } from '../src'
 import { outbox } from './fixtures/outbox'
 import { actorProfile } from './fixtures/actorProfile'
 import { expect } from 'chai'
+import { Readable } from 'node:stream'
 
 describe('exportActorProfile', () => {
   it('calls function', async () => {
@@ -40,7 +41,8 @@ describe('importActorProfile', () => {
     )
 
     // Use the importActorProfile function to parse the tar contents
-    const importedData = await importActorProfile(tarBuffer)
+    const tarStream = Readable.from(tarBuffer)
+    const importedData = await importActorProfile(tarStream)
 
     // Log or inspect the imported data structure
     // console.log('Imported Data:', importedData)
diff --git a/test/verify.spec.ts b/test/verify.spec.ts
index a524c61..5de6e4a 100644
--- a/test/verify.spec.ts
+++ b/test/verify.spec.ts
@@ -1,6 +1,7 @@
 import { expect } from 'chai'
 import { readFileSync } from 'fs'
-import { validateExportStream } from '../src/verify'
+import { validateExportStream } from '../dist'
+import { Readable } from 'stream'
 
 describe('validateExportStream', () => {
   it('should validate a valid tarball', async () => {
@@ -8,7 +9,9 @@ describe('validateExportStream', () => {
     const tarBuffer = readFileSync(
       'test/fixtures/tarball-samples/valid-export.tar'
     )
-    const result = await validateExportStream(tarBuffer)
+    const tarStream = Readable.from(tarBuffer)
+    const result = await validateExportStream(tarStream)
+    console.log('🚀 ~ it ~ valid result:', result)
 
     expect(result.valid).to.be.true
     expect(result.errors).to.be.an('array').that.is.empty
@@ -19,7 +22,9 @@ describe('validateExportStream', () => {
     const tarBuffer = readFileSync(
       'test/fixtures/tarball-samples/missing-manifest.tar'
     )
-    const result = await validateExportStream(tarBuffer)
+    const tarStream = Readable.from(tarBuffer)
+    const result = await validateExportStream(tarStream)
+    console.log('🚀 ~ it ~ miss mani result:', result)
 
     expect(result.valid).to.be.false
   })
@@ -29,48 +34,43 @@ describe('validateExportStream', () => {
     const tarBuffer = readFileSync(
       'test/fixtures/tarball-samples/missing-actor.tar'
     )
-    const result = await validateExportStream(tarBuffer)
+    const tarStream = Readable.from(tarBuffer)
+    const result = await validateExportStream(tarStream)
 
     expect(result.valid).to.be.false
     console.log(JSON.stringify(result.errors))
   })
 
-  // it('should fail if outbox.json is missing', async () => {
-  //   // Load a tarball with missing outbox.json
-  //   const tarBuffer = readFileSync(
-  //     'test/fixtures/exported-profile-missing-outbox.tar'
-  //   )
-  //   const result = await validateExportStream(tarBuffer)
+  it('should fail if outbox.json is missing', async () => {
+    // Load a tarball with missing outbox.json
+    const tarBuffer = readFileSync(
+      'test/fixtures/tarball-samples/missing-outbox.tar'
+    )
+    const tarStream = Readable.from(tarBuffer)
+    const result = await validateExportStream(tarStream)
 
-  //   expect(result.valid).to.be.false
-  //   expect(result.errors).to.include(
-  //     'Missing required file: activitypub/outbox.json'
-  //   )
-  // })
+    expect(result.valid).to.be.false
+  })
 
-  // it('should fail if actor.json contains invalid JSON', async () => {
-  //   // Load a tarball with invalid JSON in actor.json
-  //   const tarBuffer = readFileSync(
-  //     'test/fixtures/exported-profile-invalid-actor-json.tar'
-  //   )
-  //   const result = await validateExportStream(tarBuffer)
+  it('should fail if actor.json contains invalid JSON', async () => {
+    // Load a tarball with invalid JSON in actor.json
+    const tarBuffer = readFileSync(
+      'test/fixtures/tarball-samples/invalid-actor.tar'
+    )
+    const tarStream = Readable.from(tarBuffer)
+    const result = await validateExportStream(tarStream)
 
-  //   expect(result.valid).to.be.false
-  //   expect(result.errors).to.include(
-  //     'Error processing file activitypub/actor.json: Unexpected token } in JSON at position 42'
-  //   )
-  // })
+    expect(result.valid).to.be.false
+  })
 
-  // it('should fail if manifest.yaml is invalid', async () => {
-  //   // Load a tarball with invalid manifest.yaml
-  //   const tarBuffer = readFileSync(
-  //     'test/fixtures/exported-profile-invalid-manifest.tar'
-  //   )
-  //   const result = await validateExportStream(tarBuffer)
+  it('should fail if manifest.yaml is invalid', async () => {
+    // Load a tarball with invalid manifest.yaml
+    const tarBuffer = readFileSync(
+      'test/fixtures/tarball-samples/invalid-manifest.tar'
+    )
+    const tarStream = Readable.from(tarBuffer)
+    const result = await validateExportStream(tarStream)
 
-  //   expect(result.valid).to.be.false
-  //   expect(result.errors).to.include(
-  //     'Manifest is missing required field: ubc-version'
-  //   )
-  // })
+    expect(result.valid).to.be.false
+  })
 })

From 7bfa1458a2f619d0cd53f98b0bd97de55a0be2e9 Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Thu, 9 Jan 2025 18:56:14 +0200
Subject: [PATCH 7/8] Remove eslint-plugin-n dependency from package.json

---
 package.json | 1 -
 1 file changed, 1 deletion(-)

diff --git a/package.json b/package.json
index ce42ab6..037872e 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,6 @@
     "./package.json": "./package.json"
   },
   "dependencies": {
-    "eslint-plugin-n": "^17.15.1",
     "stream": "^0.0.3",
     "tar-stream": "^3.1.7",
     "yaml": "^2.5.1"

From 3a13b9eb85cac763a12e65342d67781ba7feb4d1 Mon Sep 17 00:00:00 2001
From: Omar Salah <omar.salah1597@gmail.com>
Date: Thu, 9 Jan 2025 19:14:26 +0200
Subject: [PATCH 8/8] Remove debug logging from validateExportStream function

---
 src/verify.ts | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/verify.ts b/src/verify.ts
index 8fab645..f47e943 100644
--- a/src/verify.ts
+++ b/src/verify.ts
@@ -11,7 +11,6 @@ export async function validateExportStream(
   tarStream: Readable
 ): Promise<{ valid: boolean; errors: string[] }> {
   console.log('Validating export stream...')
-  console.log('length of tarStream: ', tarStream)
   const extract = tar.extract()
   const errors: string[] = []
   const requiredFiles = [
@@ -24,7 +23,6 @@ export async function validateExportStream(
   return await new Promise((resolve) => {
     extract.on('entry', (header, stream, next) => {
       const fileName = header.name.toLowerCase() // Normalize file name
-      console.log(`Processing file: ${fileName}`) // Log the file name
       foundFiles.add(fileName)
 
       let content = ''
@@ -64,9 +62,6 @@ export async function validateExportStream(
     })
 
     extract.on('finish', () => {
-      console.log('Found files:', Array.from(foundFiles)) // Debug log
-      console.log('Required files:', requiredFiles) // Debug log
-
       // Check if all required files are present
       for (const file of requiredFiles) {
         if (!foundFiles.has(file)) {