import {spawnSync} from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; const shellRoot = new URL('../', import.meta.url); const repoRoot = path.resolve(shellRoot.pathname, '../..'); const androidArtifactPath = path.join( repoRoot, 'build', 'native', 'mobile', 'genarrative-mobile-android.apk', ); const iosSimulatorArtifactPath = path.join( repoRoot, 'build', 'native', 'mobile', 'genarrative-mobile-ios-simulator.tar.gz', ); function assertFile(filePath, label, minimumBytes) { if (!fs.existsSync(filePath)) { throw new Error(`${label} is missing: ${filePath}`); } const stat = fs.statSync(filePath); if (!stat.isFile() || stat.size < minimumBytes) { throw new Error(`${label} must be a real build artifact`); } } function readHeader(filePath, length) { const handle = fs.openSync(filePath, 'r'); try { const header = Buffer.alloc(length); fs.readSync(handle, header, 0, header.length, 0); return header; } finally { fs.closeSync(handle); } } function assertZipHeader(filePath, label) { const header = readHeader(filePath, 4); if ( header[0] !== 0x50 || header[1] !== 0x4b || header[2] !== 0x03 || header[3] !== 0x04 ) { throw new Error(`${label} must be a ZIP based artifact`); } } function assertGzipHeader(filePath, label) { const header = readHeader(filePath, 2); if (header[0] !== 0x1f || header[1] !== 0x8b) { throw new Error(`${label} must be a gzip compressed tar artifact`); } } function run(command, args, label) { const result = spawnSync(command, args, { cwd: repoRoot, encoding: 'utf8', }); if (result.error) { throw new Error(`${label} failed to start: ${result.error.message}`); } if ((result.status ?? 0) !== 0) { throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim()}`); } return result.stdout; } function assertIncludes(source, snippet, label) { if (!source.includes(snippet)) { throw new Error(`${label} missing ${snippet}`); } } assertFile(androidArtifactPath, 'Android mobile shell APK', 1024 * 1024); assertZipHeader(androidArtifactPath, 'Android mobile shell APK'); const androidListing = run('unzip', ['-l', androidArtifactPath], 'Android APK listing'); assertIncludes(androidListing, 'AndroidManifest.xml', 'Android mobile shell APK'); assertIncludes(androidListing, 'classes.dex', 'Android mobile shell APK'); assertIncludes(androidListing, 'assets/index.android.bundle', 'Android mobile shell APK'); assertFile(iosSimulatorArtifactPath, 'iOS simulator mobile shell archive', 1024 * 1024); assertGzipHeader(iosSimulatorArtifactPath, 'iOS simulator mobile shell archive'); const iosListing = run( 'tar', ['-tzf', iosSimulatorArtifactPath], 'iOS simulator archive listing', ); assertIncludes(iosListing, '.app/Info.plist', 'iOS simulator mobile shell archive'); assertIncludes(iosListing, '.app/Genarrative', 'iOS simulator mobile shell archive'); assertIncludes( iosListing, '.app/main.jsbundle', 'iOS simulator mobile shell archive', ); console.log('[mobile-shell:build-artifacts] OK');