diff --git a/jest.setup.ts b/jest.setup.ts index cfc822c..a8d3b35 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -27,13 +27,21 @@ const templateApp = async ( export default async () => { await fs.remove(appsDir); await fs.mkdirp(appsDir); - await templateApp('Asar.app', 'arm64', async (appPath) => { + await templateApp('Arm64Asar.app', 'arm64', async (appPath) => { await fs.copy( path.resolve(asarsDir, 'app.asar'), path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), ); }); + // contains `extra-file.txt` + await templateApp('Arm64AsarExtraFile.app', 'arm64', async (appPath) => { + await fs.copy( + path.resolve(asarsDir, 'app2.asar'), + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + ); + }); + await templateApp('X64Asar.app', 'x64', async (appPath) => { await fs.copy( path.resolve(asarsDir, 'app.asar'), @@ -41,13 +49,21 @@ export default async () => { ); }); - await templateApp('NoAsar.app', 'arm64', async (appPath) => { + await templateApp('Arm64NoAsar.app', 'arm64', async (appPath) => { await fs.copy( path.resolve(asarsDir, 'app'), path.resolve(appPath, 'Contents', 'Resources', 'app'), ); }); + // contains `extra-file.txt` + await templateApp('Arm64NoAsarExtraFile.app', 'arm64', async (appPath) => { + await fs.copy( + path.resolve(asarsDir, 'app2'), + path.resolve(appPath, 'Contents', 'Resources', 'app'), + ); + }); + await templateApp('X64NoAsar.app', 'x64', async (appPath) => { await fs.copy( path.resolve(asarsDir, 'app'), diff --git a/test/asar-utils.spec.ts b/test/asar-utils.spec.ts index d509d78..46ec71d 100644 --- a/test/asar-utils.spec.ts +++ b/test/asar-utils.spec.ts @@ -8,11 +8,13 @@ const appsPath = path.resolve(__dirname, 'fixtures', 'apps'); describe('asar-utils', () => { describe('detectAsarMode', () => { it('should correctly detect an asar enabled app', async () => { - expect(await detectAsarMode(path.resolve(appsPath, 'Asar.app'))).toBe(AsarMode.HAS_ASAR); + expect(await detectAsarMode(path.resolve(appsPath, 'Arm64Asar.app'))).toBe(AsarMode.HAS_ASAR); }); it('should correctly detect an app without an asar', async () => { - expect(await detectAsarMode(path.resolve(appsPath, 'NoAsar.app'))).toBe(AsarMode.NO_ASAR); + expect(await detectAsarMode(path.resolve(appsPath, 'Arm64NoAsar.app'))).toBe( + AsarMode.NO_ASAR, + ); }); }); diff --git a/test/file-utils.spec.ts b/test/file-utils.spec.ts index 097b671..38af5b8 100644 --- a/test/file-utils.spec.ts +++ b/test/file-utils.spec.ts @@ -10,8 +10,8 @@ describe('file-utils', () => { let noAsarFiles: AppFile[]; beforeAll(async () => { - asarFiles = await getAllAppFiles(path.resolve(appsPath, 'Asar.app')); - noAsarFiles = await getAllAppFiles(path.resolve(appsPath, 'NoAsar.app')); + asarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64Asar.app')); + noAsarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64NoAsar.app')); }); it('should correctly identify plist files', async () => { diff --git a/test/fixtures/asars/app2.asar b/test/fixtures/asars/app2.asar new file mode 100644 index 0000000..40dc95c Binary files /dev/null and b/test/fixtures/asars/app2.asar differ diff --git a/test/fixtures/asars/app2/extra-file.txt b/test/fixtures/asars/app2/extra-file.txt new file mode 100644 index 0000000..c8e1d77 --- /dev/null +++ b/test/fixtures/asars/app2/extra-file.txt @@ -0,0 +1 @@ +erick was here! \ No newline at end of file diff --git a/test/fixtures/asars/app2/index.js b/test/fixtures/asars/app2/index.js new file mode 100644 index 0000000..a0d6e38 --- /dev/null +++ b/test/fixtures/asars/app2/index.js @@ -0,0 +1,2 @@ +console.log('I am an app.asar', process.arch); +process.exit(0); diff --git a/test/fixtures/asars/app2/package.json b/test/fixtures/asars/app2/package.json new file mode 100644 index 0000000..85e8553 --- /dev/null +++ b/test/fixtures/asars/app2/package.json @@ -0,0 +1,4 @@ +{ + "name": "app", + "main": "index.js" +} \ No newline at end of file diff --git a/test/index.spec.ts b/test/index.spec.ts index 418861e..0d9871b 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -2,9 +2,10 @@ import { spawn } from '@malept/cross-spawn-promise'; import * as fs from 'fs-extra'; import * as path from 'path'; -import { makeUniversalApp } from '../src/index'; +import { makeUniversalApp } from '../dist/cjs/index'; const appsPath = path.resolve(__dirname, 'fixtures', 'apps'); +const appsOutPath = path.resolve(__dirname, 'fixtures', 'apps', 'out'); async function ensureUniversal(app: string) { const exe = path.resolve(app, 'Contents', 'MacOS', 'Electron'); @@ -14,27 +15,147 @@ async function ensureUniversal(app: string) { expect(result2).toContain('x64'); } +// See `jest.setup.ts` for app fixture setup process describe('makeUniversalApp', () => { - it('should correctly merge two identical asars', async () => { - const out = path.resolve(appsPath, 'MergedAsar.app'); - await makeUniversalApp({ - x64AppPath: path.resolve(appsPath, 'X64Asar.app'), - arm64AppPath: path.resolve(appsPath, 'Asar.app'), - outAppPath: out, + afterEach(async () => { + await fs.emptyDir(appsOutPath); + }); + + it('throws an error if asar is only detected in one arch', async () => { + const out = path.resolve(appsOutPath, 'Error.app'); + await expect( + makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64Asar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64NoAsar.app'), + outAppPath: out, + }), + ).rejects.toThrow( + 'Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)', + ); + }); + + it.todo('works for lipo binary resources'); + + describe('force', () => { + it('throws an error if `out` bundle already exists and `force` is `false`', async () => { + const out = path.resolve(appsOutPath, 'Error.app'); + await fs.mkdirp(out); + await expect( + makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64Asar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'), + outAppPath: out, + }), + ).rejects.toThrow(/The out path ".*" already exists and force is not set to true/); }); - await ensureUniversal(out); - // Only a single asar as they were identical - expect( - (await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) => - p.endsWith('asar'), - ), - ).toEqual(['app.asar']); - }, 60000); + + it('packages successfully if `out` bundle already exists and `force` is `true`', async () => { + const out = path.resolve(appsOutPath, 'Error.app'); + await fs.mkdirp(out); + await makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64Asar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'), + outAppPath: out, + force: true, + }); + await ensureUniversal(out); + // Only a single asar as they were identical + expect( + (await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) => + p.endsWith('asar'), + ), + ).toEqual(['app.asar']); + }, 60000); + }); + + describe('asar mode', () => { + it('should correctly merge two identical asars', async () => { + const out = path.resolve(appsOutPath, 'MergedAsar.app'); + await makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64Asar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'), + outAppPath: out, + }); + await ensureUniversal(out); + // Only a single asar as they were identical + expect( + (await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) => + p.endsWith('asar'), + ), + ).toEqual(['app.asar']); + }, 60000); + + it('should create a shim if asars are different between architectures', async () => { + const out = path.resolve(appsOutPath, 'ShimmedAsar.app'); + await makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64Asar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64AsarExtraFile.app'), + outAppPath: out, + }); + await ensureUniversal(out); + // We have three asars including the arch-agnostic shim + expect( + (await fs.readdir(path.resolve(out, 'Contents', 'Resources'))) + .filter((p) => p.endsWith('asar')) + .sort(), + ).toEqual(['app.asar', 'app-x64.asar', 'app-arm64.asar'].sort()); + }, 60000); + + it('should merge two different asars when `mergeASARs` is enabled', async () => { + const out = path.resolve(appsOutPath, 'MergedAsar.app'); + await makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64Asar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64AsarExtraFile.app'), + outAppPath: out, + mergeASARs: true, + singleArchFiles: 'extra-file.txt', + }); + await ensureUniversal(out); + // Only a single merged asar + expect( + (await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) => + p.endsWith('asar'), + ), + ).toEqual(['app.asar']); + }, 60000); + + it('throws an error if `mergeASARs` is enabled and `singleArchFiles` is missing a unique file', async () => { + const out = path.resolve(appsOutPath, 'Error.app'); + await expect( + makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64Asar.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64AsarExtraFile.app'), + outAppPath: out, + mergeASARs: true, + singleArchFiles: 'bad-rule', + }), + ).rejects.toThrow(/Detected unique file "extra-file\.txt"/); + }, 60000); + + it.todo('should not inject ElectronAsarIntegrity into `infoPlistsToIgnore`'); + }); + + 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 ensureUniversal(out); + // Only a single app folder as they were identical + expect( + (await fs.readdir(path.resolve(out, 'Contents', 'Resources'))).filter((p) => + p.startsWith('app'), + ), + ).toEqual(['app']); + }, 60000); + + it.todo('should shim two different app folders'); + }); // TODO: Add tests for - // * different asar files - // * identical app dirs - // * different app dirs // * different app dirs with different macho files // * identical app dirs with universal macho files });