From 3ba0f9b91a013bf14902e6f249aeb5ddafbb9f91 Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Wed, 19 Feb 2025 08:45:05 -0800 Subject: [PATCH 1/2] fix: When application uses multiple ASARs, `EnableEmbeddedAsarIntegrityValidation` fuse breaks the application due to not all ASARs having integrity generated for them. Fixes: #116 --- .github/workflows/test.yml | 1 + jest.setup.ts | 6 +- src/file-utils.ts | 2 +- src/index.ts | 18 +--- src/integrity.ts | 51 ++++++++++ test/__snapshots__/index.spec.ts.snap | 138 +++++++++++++++++++++++++- test/index.spec.ts | 50 +++++++++- test/util.ts | 13 +-- 8 files changed, 250 insertions(+), 29 deletions(-) create mode 100644 src/integrity.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 93f024b..6a620e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,7 @@ jobs: test: name: Test strategy: + fail-fast: false matrix: node-version: - '20.5' diff --git a/jest.setup.ts b/jest.setup.ts index 296a148..9fdaeb8 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -1,10 +1,10 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { appsDir, asarsDir, templateApp } from './test/util'; +import { appsPath, asarsDir, templateApp } from './test/util'; export default async () => { - await fs.remove(appsDir); - await fs.mkdirp(appsDir); + await fs.remove(appsPath); + await fs.mkdirp(appsPath); await templateApp('Arm64Asar.app', 'arm64', async (appPath) => { await fs.copy( path.resolve(asarsDir, 'app.asar'), diff --git a/src/file-utils.ts b/src/file-utils.ts index 7a603a8..08dc1e3 100644 --- a/src/file-utils.ts +++ b/src/file-utils.ts @@ -45,7 +45,7 @@ export const getAllAppFiles = async (appPath: string): Promise => { throw e; } } - if (p.includes('app.asar')) { + if (p.endsWith('.asar')) { fileType = AppFileType.APP_CODE; } else if (fileOutput.startsWith(MACHO_PREFIX)) { fileType = AppFileType.MACHO; diff --git a/src/index.ts b/src/index.ts index c7492a6..5a1105e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,9 +8,10 @@ import * as plist from 'plist'; import * as dircompare from 'dir-compare'; import { AppFile, AppFileType, getAllAppFiles } from './file-utils'; -import { AsarMode, detectAsarMode, generateAsarIntegrity, mergeASARs } from './asar-utils'; +import { AsarMode, detectAsarMode, mergeASARs } from './asar-utils'; import { sha } from './sha'; import { d } from './debug'; +import { computeIntegrityData } from './integrity'; /** * Options to pass into the {@link makeUniversalApp} function. @@ -251,9 +252,6 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = } } - const generatedIntegrity: Record = {}; - let didSplitAsar = false; - /** * If we have an ASAR we just need to check if the two "app.asar" files have the same hash, * if they are, same as above, we can leave one there and call it a day. If they're different @@ -271,8 +269,6 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = outputAsarPath: output, singleArchFiles: opts.singleArchFiles, }); - - generatedIntegrity['Resources/app.asar'] = generateAsarIntegrity(output); } else if (x64AsarMode === AsarMode.HAS_ASAR) { d('checking if the x64 and arm64 asars are identical'); const x64AsarSha = await sha(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar')); @@ -281,7 +277,6 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = ); if (x64AsarSha !== arm64AsarSha) { - didSplitAsar = true; d('x64 and arm64 asars are different'); const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'); await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath); @@ -329,18 +324,13 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'); await asar.createPackage(entryAsar, asarPath); - - generatedIntegrity['Resources/app.asar'] = generateAsarIntegrity(asarPath); - generatedIntegrity['Resources/app-x64.asar'] = generateAsarIntegrity(x64AsarPath); - generatedIntegrity['Resources/app-arm64.asar'] = generateAsarIntegrity(arm64AsarPath); } else { d('x64 and arm64 asars are the same'); - generatedIntegrity['Resources/app.asar'] = generateAsarIntegrity( - path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), - ); } } + const generatedIntegrity = await computeIntegrityData(path.join(tmpApp, 'Contents')); + const plistFiles = x64Files.filter((f) => f.type === AppFileType.INFO_PLIST); for (const plistFile of plistFiles) { const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath); diff --git a/src/integrity.ts b/src/integrity.ts new file mode 100644 index 0000000..51e1508 --- /dev/null +++ b/src/integrity.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs-extra'; +import path from 'path'; +import { AppFileType, getAllAppFiles } from './file-utils'; +import { sha } from './sha'; +import { generateAsarIntegrity } from './asar-utils'; + +type IntegrityMap = { + [filepath: string]: string; +}; + +export interface HeaderHash { + algorithm: 'SHA256'; + hash: string; +} + +export interface AsarIntegrity { + [key: string]: HeaderHash; +} + +export async function computeIntegrityData(contentsPath: string): Promise { + const root = await fs.realpath(contentsPath); + + const resourcesRelativePath = 'Resources'; + const resourcesPath = path.resolve(root, resourcesRelativePath); + + const resources = await getAllAppFiles(resourcesPath); + const resourceAsars = resources + .filter((file) => file.type === AppFileType.APP_CODE) + .reduce( + (prev, file) => ({ + ...prev, + [path.join(resourcesRelativePath, file.relativePath)]: path.join( + resourcesPath, + file.relativePath, + ), + }), + {}, + ); + + // sort to produce constant result + const allAsars = Object.entries(resourceAsars).sort(([name1], [name2]) => + name1.localeCompare(name2), + ); + const hashes = await Promise.all(allAsars.map(async ([, from]) => generateAsarIntegrity(from))); + const asarIntegrity: AsarIntegrity = {}; + for (let i = 0; i < allAsars.length; i++) { + const [asar] = allAsars[i]; + asarIntegrity[asar] = hashes[i]; + } + return asarIntegrity; +} diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index 2d0341d..895df3c 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -157,6 +157,137 @@ exports[`makeUniversalApp asar mode should create a shim if asars are different } `; +exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars in the application 1`] = ` +{ + "files": { + "index.js": { + "integrity": { + "algorithm": "SHA256", + "blockSize": 4194304, + "blocks": [ + "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8", + ], + "hash": "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8", + }, + "size": 66, + }, + "package.json": { + "integrity": { + "algorithm": "SHA256", + "blockSize": 4194304, + "blocks": [ + "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3", + ], + "hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3", + }, + "size": 41, + }, + "private": { + "files": { + "var": { + "files": { + "app": { + "files": { + "file.txt": { + "link": "private/var/file.txt", + }, + }, + }, + "file.txt": { + "integrity": { + "algorithm": "SHA256", + "blockSize": 4194304, + "blocks": [ + "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", + ], + "hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", + }, + "size": 11, + }, + }, + }, + }, + }, + "var": { + "link": "private/var", + }, + }, +} +`; + +exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars in the application 2`] = ` +{ + "files": { + "index.js": { + "integrity": { + "algorithm": "SHA256", + "blockSize": 4194304, + "blocks": [ + "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8", + ], + "hash": "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8", + }, + "size": 66, + }, + "package.json": { + "integrity": { + "algorithm": "SHA256", + "blockSize": 4194304, + "blocks": [ + "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3", + ], + "hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3", + }, + "size": 41, + }, + "private": { + "files": { + "var": { + "files": { + "app": { + "files": { + "file.txt": { + "link": "private/var/file.txt", + }, + }, + }, + "file.txt": { + "integrity": { + "algorithm": "SHA256", + "blockSize": 4194304, + "blocks": [ + "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", + ], + "hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", + }, + "size": 11, + }, + }, + }, + }, + }, + "var": { + "link": "private/var", + }, + }, +} +`; + +exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars in the application 3`] = ` +{ + "Contents/Info.plist": { + "Resources/app.asar": { + "algorithm": "SHA256", + "hash": "7e6af4d00f4cc737eff922e2b386128a269f80887b79a011022f1276bdbe7832", + }, + "Resources/webbapp.asar": { + "algorithm": "SHA256", + "hash": "7e6af4d00f4cc737eff922e2b386128a269f80887b79a011022f1276bdbe7832", + }, + }, +} +`; + exports[`makeUniversalApp asar mode should merge two different asars when \`mergeASARs\` is enabled 1`] = ` { "files": { @@ -405,6 +536,11 @@ exports[`makeUniversalApp no asar mode should shim two different app folders 4`] exports[`makeUniversalApp no asar mode should shim two different app folders 5`] = ` { - "Contents/Info.plist": {}, + "Contents/Info.plist": { + "Resources/app.asar": { + "algorithm": "SHA256", + "hash": "27433ee3e34b3b0dabb29d18d40646126e80c56dbce8c4bb2adef7278b5a46c0", + }, + }, } `; diff --git a/test/index.spec.ts b/test/index.spec.ts index fffc2b9..70cf7e2 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -2,12 +2,16 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { makeUniversalApp } from '../dist/cjs/index'; -import { createTestApp, templateApp, VERIFY_APP_TIMEOUT, verifyApp } from './util'; +import { + appsPath, + appsOutPath, + createTestApp, + templateApp, + VERIFY_APP_TIMEOUT, + verifyApp, +} from './util'; import { createPackage } from '@electron/asar'; -const appsPath = path.resolve(__dirname, 'fixtures', 'apps'); -const appsOutPath = path.resolve(__dirname, 'fixtures', 'apps', 'out'); - // See `jest.setup.ts` for app fixture setup process describe('makeUniversalApp', () => { afterEach(async () => { @@ -156,6 +160,44 @@ describe('makeUniversalApp', () => { }, VERIFY_APP_TIMEOUT, ); + it( + 'should generate AsarIntegrity for all asars in the application', + async () => { + const { testPath } = await createTestApp('app-2'); + const testAsarPath = path.resolve(appsOutPath, 'app-2.asar'); + await createPackage(testPath, testAsarPath); + + const arm64AppPath = await templateApp('Arm64-2.app', 'arm64', async (appPath) => { + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + ); + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'webapp.asar'), + ); + }); + const x64AppPath = await templateApp('X64-2.app', 'x64', async (appPath) => { + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + ); + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'webbapp.asar'), + ); + }); + const outAppPath = path.resolve(appsOutPath, 'MultipleAsars.app'); + await makeUniversalApp({ + x64AppPath, + arm64AppPath, + outAppPath, + mergeASARs: true, + }); + await verifyApp(outAppPath); + }, + VERIFY_APP_TIMEOUT, + ); }); describe('no asar mode', () => { diff --git a/test/util.ts b/test/util.ts index 03a9d6d..a7c548a 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,3 +1,4 @@ +import { getRawHeader } from '@electron/asar'; import { downloadArtifact } from '@electron/get'; import { spawn } from '@malept/cross-spawn-promise'; import * as zip from 'cross-zip'; @@ -5,7 +6,6 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import plist from 'plist'; import * as fileUtils from '../dist/cjs/file-utils'; -import { getRawHeader } from '@electron/asar'; // We do a LOT of verifications in `verifyApp` 😅 // exec universal binary -> verify ALL asars -> verify ALL app dirs -> verify ALL asar integrity entries @@ -13,7 +13,8 @@ import { getRawHeader } from '@electron/asar'; export const VERIFY_APP_TIMEOUT = 80 * 1000; export const asarsDir = path.resolve(__dirname, 'fixtures', 'asars'); -export const appsDir = path.resolve(__dirname, 'fixtures', 'apps'); +export const appsPath = path.resolve(__dirname, 'fixtures', 'apps'); +export const appsOutPath = path.resolve(appsPath, 'out'); export const verifyApp = async (appPath: string) => { await ensureUniversal(appPath); @@ -129,7 +130,7 @@ export const createTestApp = async ( additionalFiles: Record = {}, ) => { const outDir = (testName || 'app') + Math.floor(Math.random() * 100); // tests run in parallel, randomize dir suffix to prevent naming collisions - const testPath = path.join(appsDir, outDir); + const testPath = path.join(appsPath, outDir); await fs.remove(testPath); await fs.copy(path.join(asarsDir, 'app'), testPath); @@ -170,9 +171,9 @@ export const templateApp = async ( platform: 'darwin', arch, }); - const appPath = path.resolve(appsDir, name); - zip.unzipSync(electronZip, appsDir); - await fs.rename(path.resolve(appsDir, 'Electron.app'), appPath); + const appPath = path.resolve(appsPath, name); + zip.unzipSync(electronZip, appsPath); + await fs.rename(path.resolve(appsPath, 'Electron.app'), appPath); await fs.remove(path.resolve(appPath, 'Contents', 'Resources', 'default_app.asar')); await modify(appPath); From 61eb5ac10377062bea3bd51507a2d767201cb780 Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Sat, 22 Feb 2025 00:16:07 -0800 Subject: [PATCH 2/2] cleanup merge conflict overwrites --- jest.setup.ts | 6 +- test/index.spec.ts | 220 ++++++++++++++++++++++++--------------------- test/util.ts | 14 +-- 3 files changed, 127 insertions(+), 113 deletions(-) diff --git a/jest.setup.ts b/jest.setup.ts index 9fdaeb8..296a148 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -1,10 +1,10 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { appsPath, asarsDir, templateApp } from './test/util'; +import { appsDir, asarsDir, templateApp } from './test/util'; export default async () => { - await fs.remove(appsPath); - await fs.mkdirp(appsPath); + await fs.remove(appsDir); + await fs.mkdirp(appsDir); await templateApp('Arm64Asar.app', 'arm64', async (appPath) => { await fs.copy( path.resolve(asarsDir, 'app.asar'), diff --git a/test/index.spec.ts b/test/index.spec.ts index c1df51a..de0a7cb 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -2,16 +2,12 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { makeUniversalApp } from '../dist/cjs/index'; -import { - appsPath, - appsOutPath, - createTestApp, - templateApp, - VERIFY_APP_TIMEOUT, - verifyApp, -} from './util'; +import { createTestApp, templateApp, VERIFY_APP_TIMEOUT, verifyApp } from './util'; import { createPackage, createPackageWithOptions } from '@electron/asar'; +const appsPath = path.resolve(__dirname, 'fixtures', 'apps'); +const appsOutPath = path.resolve(__dirname, 'fixtures', 'apps', 'out'); + // See `jest.setup.ts` for app fixture setup process describe('makeUniversalApp', () => { afterEach(async () => { @@ -160,110 +156,128 @@ describe('makeUniversalApp', () => { }, VERIFY_APP_TIMEOUT, ); - it('should generate AsarIntegrity for all asars in the application', async () => { - const { testPath } = await createTestApp('app-2'); - const testAsarPath = path.resolve(appsOutPath, 'app-2.asar'); - await createPackage(testPath, testAsarPath); - const arm64AppPath = await templateApp('Arm64-2.app', 'arm64', async (appPath) => { - await fs.copyFile(testAsarPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar')); - await fs.copyFile( - testAsarPath, - path.resolve(appPath, 'Contents', 'Resources', 'webapp.asar'), - ); - }); - const x64AppPath = await templateApp('X64-2.app', 'x64', async (appPath) => { - await fs.copyFile(testAsarPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar')); - await fs.copyFile( - testAsarPath, - path.resolve(appPath, 'Contents', 'Resources', 'webbapp.asar'), - ); - }); - const outAppPath = path.resolve(appsOutPath, 'MultipleAsars.app'); + // TODO: Investigate if this should even be allowed. + // Current logic detects all unpacked files as APP_CODE, which doesn't seem correct since it could also be a macho file requiring lipo + // https://github.com/electron/universal/blob/d90d573ccf69a5b14b91aa818c8b97e0e6840399/src/file-utils.ts#L48-L49 + it.skip( + 'should shim asars with different unpacked dirs', + async () => { + const arm64AppPath = await templateApp('UnpackedArm64.app', 'arm64', async (appPath) => { + const { testPath } = await createTestApp('UnpackedAppArm64'); + await createPackageWithOptions( + testPath, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + { + unpackDir: 'var', + unpack: '*.txt', + }, + ); + }); - it( - 'should shim asars with different unpacked dirs', - async () => { - const arm64AppPath = await templateApp('UnpackedArm64.app', 'arm64', async (appPath) => { - const { testPath } = await createTestApp('UnpackedAppArm64'); - await createPackageWithOptions( - testPath, - path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), - { - unpackDir: 'var', - unpack: '*.txt', - }, - ); - }); + const x64AppPath = await templateApp('UnpackedX64.app', 'x64', async (appPath) => { + const { testPath } = await createTestApp('UnpackedAppX64'); + await createPackageWithOptions( + testPath, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + {}, + ); + }); - const x64AppPath = await templateApp('UnpackedX64.app', 'x64', async (appPath) => { - const { testPath } = await createTestApp('UnpackedAppX64'); - await createPackageWithOptions( - testPath, - path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), - {}, - ); - }); + const outAppPath = path.resolve(appsOutPath, 'UnpackedDir.app'); + await makeUniversalApp({ + x64AppPath, + arm64AppPath, + outAppPath, + }); + await verifyApp(outAppPath); + }, + VERIFY_APP_TIMEOUT, + ); - const outAppPath = path.resolve(appsOutPath, 'UnpackedDir.app'); - await makeUniversalApp({ - x64AppPath, - arm64AppPath, - outAppPath, - mergeASARs: true, - }); - await verifyApp(outAppPath); - }, - VERIFY_APP_TIMEOUT, - ); - }); + it( + 'should generate AsarIntegrity for all asars in the application', + async () => { + const { testPath } = await createTestApp('app-2'); + const testAsarPath = path.resolve(appsOutPath, 'app-2.asar'); + await createPackage(testPath, testAsarPath); - describe('no asar mode', () => { - it( - 'should correctly merge two identical app folders', - async () => { - const out = path.resolve(appsOutPath, 'MergedNoAsar.app'); - await makeUniversalApp({ - x64AppPath: path.resolve(appsPath, 'X64NoAsar.app'), - arm64AppPath: path.resolve(appsPath, 'Arm64NoAsar.app'), - outAppPath: out, - }); - await verifyApp(out); - }, - VERIFY_APP_TIMEOUT, - ); + const arm64AppPath = await templateApp('Arm64-2.app', 'arm64', async (appPath) => { + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + ); + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'webapp.asar'), + ); + }); + const x64AppPath = await templateApp('X64-2.app', 'x64', async (appPath) => { + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + ); + await fs.copyFile( + testAsarPath, + path.resolve(appPath, 'Contents', 'Resources', 'webbapp.asar'), + ); + }); + const outAppPath = path.resolve(appsOutPath, 'MultipleAsars.app'); + await makeUniversalApp({ + x64AppPath, + arm64AppPath, + outAppPath, + mergeASARs: true, + }); + await verifyApp(outAppPath); + }, + VERIFY_APP_TIMEOUT, + ); + }); - it( - 'should shim two different app folders', - async () => { - const arm64AppPath = await templateApp('ShimArm64.app', 'arm64', async (appPath) => { - const { testPath } = await createTestApp('shimArm64', { - 'i-aint-got-no-rhythm.bin': 'boomshakalaka', - }); - await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app')); - }); + describe('no asar mode', () => { + it( + 'should correctly merge two identical app folders', + async () => { + const out = path.resolve(appsOutPath, 'MergedNoAsar.app'); + await makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64NoAsar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64NoAsar.app'), + outAppPath: out, + }); + await verifyApp(out); + }, + VERIFY_APP_TIMEOUT, + ); - const x64AppPath = await templateApp('ShimX64.app', 'x64', async (appPath) => { - const { testPath } = await createTestApp('shimX64', { - 'hello-world.bin': 'Hello World', - }); - await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app')); + it( + 'should shim two different app folders', + async () => { + const arm64AppPath = await templateApp('ShimArm64.app', 'arm64', async (appPath) => { + const { testPath } = await createTestApp('shimArm64', { + 'i-aint-got-no-rhythm.bin': 'boomshakalaka', }); + await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app')); + }); - const outAppPath = path.resolve(appsOutPath, 'ShimNoAsar.app'); - await makeUniversalApp({ - x64AppPath, - arm64AppPath, - outAppPath, - }); - await verifyApp(outAppPath); - }, - VERIFY_APP_TIMEOUT, - ); - }); + const x64AppPath = await templateApp('ShimX64.app', 'x64', async (appPath) => { + const { testPath } = await createTestApp('shimX64', { 'hello-world.bin': 'Hello World' }); + await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app')); + }); - // TODO: Add tests for - // * different app dirs with different macho files - // * identical app dirs with universal macho files + const outAppPath = path.resolve(appsOutPath, 'ShimNoAsar.app'); + await makeUniversalApp({ + x64AppPath, + arm64AppPath, + outAppPath, + }); + await verifyApp(outAppPath); + }, + VERIFY_APP_TIMEOUT, + ); }); + + // TODO: Add tests for + // * different app dirs with different macho files + // * identical app dirs with universal macho files }); diff --git a/test/util.ts b/test/util.ts index a7c548a..1937317 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,4 +1,3 @@ -import { getRawHeader } from '@electron/asar'; import { downloadArtifact } from '@electron/get'; import { spawn } from '@malept/cross-spawn-promise'; import * as zip from 'cross-zip'; @@ -6,6 +5,7 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import plist from 'plist'; import * as fileUtils from '../dist/cjs/file-utils'; +import { getRawHeader } from '@electron/asar'; // We do a LOT of verifications in `verifyApp` 😅 // exec universal binary -> verify ALL asars -> verify ALL app dirs -> verify ALL asar integrity entries @@ -13,8 +13,8 @@ import * as fileUtils from '../dist/cjs/file-utils'; export const VERIFY_APP_TIMEOUT = 80 * 1000; export const asarsDir = path.resolve(__dirname, 'fixtures', 'asars'); -export const appsPath = path.resolve(__dirname, 'fixtures', 'apps'); -export const appsOutPath = path.resolve(appsPath, 'out'); +export const appsDir = path.resolve(__dirname, 'fixtures', 'apps'); +export const appsOutPath = path.resolve(appsDir, 'out'); export const verifyApp = async (appPath: string) => { await ensureUniversal(appPath); @@ -130,7 +130,7 @@ export const createTestApp = async ( additionalFiles: Record = {}, ) => { const outDir = (testName || 'app') + Math.floor(Math.random() * 100); // tests run in parallel, randomize dir suffix to prevent naming collisions - const testPath = path.join(appsPath, outDir); + const testPath = path.join(appsDir, outDir); await fs.remove(testPath); await fs.copy(path.join(asarsDir, 'app'), testPath); @@ -171,9 +171,9 @@ export const templateApp = async ( platform: 'darwin', arch, }); - const appPath = path.resolve(appsPath, name); - zip.unzipSync(electronZip, appsPath); - await fs.rename(path.resolve(appsPath, 'Electron.app'), appPath); + const appPath = path.resolve(appsDir, name); + zip.unzipSync(electronZip, appsDir); + await fs.rename(path.resolve(appsDir, 'Electron.app'), appPath); await fs.remove(path.resolve(appPath, 'Contents', 'Resources', 'default_app.asar')); await modify(appPath);