@@ -247,52 +247,97 @@ export default class Maestro extends BaseProvider<MaestroOptions> {
247247 }
248248
249249 private async uploadApp ( ) {
250- const appPath = this . options . app ;
250+ let appPath = this . options . app ;
251251 const ext = path . extname ( appPath ) . toLowerCase ( ) ;
252+ let tempZipPath : string | null = null ;
252253
253- let contentType :
254- | 'application/vnd.android.package-archive'
255- | 'application/octet-stream'
256- | 'application/zip' ;
257- if ( ext === '.apk' ) {
258- contentType = 'application/vnd.android.package-archive' ;
259- } else if ( ext === '.ipa' || ext === '.app' ) {
260- contentType = 'application/octet-stream' ;
261- } else if ( ext === '.zip' ) {
262- contentType = 'application/zip' ;
263- } else {
264- contentType = 'application/octet-stream' ;
254+ // If .app bundle (directory), zip it first
255+ if ( ext === '.app' ) {
256+ const stat = await fs . promises . stat ( appPath ) ;
257+ if ( stat . isDirectory ( ) ) {
258+ if ( ! this . options . quiet ) {
259+ logger . info ( 'Zipping .app bundle for upload' ) ;
260+ }
261+ tempZipPath = await this . zipAppBundle ( appPath ) ;
262+ appPath = tempZipPath ;
263+ }
265264 }
266265
267- if ( ! this . options . ignoreChecksumCheck ) {
268- const checksum = await this . upload . calculateChecksum ( appPath ) ;
269- const existingApp = await this . checkAppChecksum ( checksum ) ;
266+ try {
267+ let contentType :
268+ | 'application/vnd.android.package-archive'
269+ | 'application/octet-stream'
270+ | 'application/zip' ;
271+ if ( ext === '.apk' ) {
272+ contentType = 'application/vnd.android.package-archive' ;
273+ } else if ( ext === '.ipa' ) {
274+ contentType = 'application/octet-stream' ;
275+ } else if ( ext === '.zip' || ext === '.app' ) {
276+ // .app bundles are zipped, so use application/zip
277+ contentType = 'application/zip' ;
278+ } else {
279+ contentType = 'application/octet-stream' ;
280+ }
270281
271- if ( existingApp ) {
272- this . appId = existingApp . id ;
273- if ( ! this . options . quiet ) {
274- logger . info ( ' App already uploaded, skipping upload' ) ;
282+ if ( ! this . options . ignoreChecksumCheck ) {
283+ const checksum = await this . upload . calculateChecksum ( appPath ) ;
284+ const existingApp = await this . checkAppChecksum ( checksum ) ;
285+
286+ if ( existingApp ) {
287+ this . appId = existingApp . id ;
288+ if ( ! this . options . quiet ) {
289+ logger . info ( ' App already uploaded, skipping upload' ) ;
290+ }
291+ return true ;
275292 }
276- return true ;
277293 }
278- }
279294
280- if ( ! this . options . quiet ) {
281- logger . info ( 'Uploading Maestro App' ) ;
295+ if ( ! this . options . quiet ) {
296+ logger . info ( 'Uploading Maestro App' ) ;
297+ }
298+
299+ const result = await this . upload . upload ( {
300+ filePath : appPath ,
301+ url : `${ this . URL } /app` ,
302+ credentials : this . credentials ,
303+ contentType,
304+ showProgress : ! this . options . quiet ,
305+ validateZipFormat : true ,
306+ } ) ;
307+
308+ this . appId = result . id ;
309+
310+ return true ;
311+ } finally {
312+ // Clean up temporary zip file
313+ if ( tempZipPath ) {
314+ await fs . promises . unlink ( tempZipPath ) . catch ( ( ) => { } ) ;
315+ }
282316 }
317+ }
283318
284- const result = await this . upload . upload ( {
285- filePath : appPath ,
286- url : `${ this . URL } /app` ,
287- credentials : this . credentials ,
288- contentType,
289- showProgress : ! this . options . quiet ,
290- validateZipFormat : true ,
291- } ) ;
319+ /**
320+ * Zip a .app bundle directory into a temporary zip file
321+ */
322+ private async zipAppBundle ( appPath : string ) : Promise < string > {
323+ const appName = path . basename ( appPath ) ;
324+ const tmpDir = await fs . promises . mkdtemp (
325+ path . join ( os . tmpdir ( ) , 'testingbot-app-' ) ,
326+ ) ;
327+ const zipPath = path . join ( tmpDir , `${ appName } .zip` ) ;
292328
293- this . appId = result . id ;
329+ return new Promise ( ( resolve , reject ) => {
330+ const output = fs . createWriteStream ( zipPath ) ;
331+ const archive = archiver ( 'zip' , { zlib : { level : 9 } } ) ;
294332
295- return true ;
333+ output . on ( 'close' , ( ) => resolve ( zipPath ) ) ;
334+ archive . on ( 'error' , ( err ) => reject ( err ) ) ;
335+
336+ archive . pipe ( output ) ;
337+ // Add the .app directory with its name preserved
338+ archive . directory ( appPath , appName ) ;
339+ archive . finalize ( ) ;
340+ } ) ;
296341 }
297342
298343 private async checkAppChecksum (
0 commit comments