- When an application uses multiple asars (`webapp.asar`, `anything.asar`, etc.), `EnableEmbeddedAsarIntegrityValidation` fuse breaks the application due to not all asars having integrity generated for them. Fixes: #116 - **Also fixes bug** to correctly test `makeUniversalApp no asar mode should shim two different app folders`, (it was not having an asar integrity generated for the shimmed asar) Functionality added: - Moves all asar integrity generation to **after** all app assets have been merged/shimmed/copied. This allows other asars that were provided to also be scanned and have asar integrity generated for them. - Extracted common Integrity logic to a single file `integrity.ts` - Adds unit test for multi-asar apps
284 lines
9.8 KiB
TypeScript
284 lines
9.8 KiB
TypeScript
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 { 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 () => {
|
|
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/);
|
|
});
|
|
|
|
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 verifyApp(out);
|
|
},
|
|
VERIFY_APP_TIMEOUT,
|
|
);
|
|
});
|
|
|
|
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 verifyApp(out);
|
|
},
|
|
VERIFY_APP_TIMEOUT,
|
|
);
|
|
|
|
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 verifyApp(out);
|
|
},
|
|
VERIFY_APP_TIMEOUT,
|
|
);
|
|
|
|
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 verifyApp(out);
|
|
},
|
|
VERIFY_APP_TIMEOUT,
|
|
);
|
|
|
|
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"/);
|
|
},
|
|
VERIFY_APP_TIMEOUT,
|
|
);
|
|
|
|
it(
|
|
'should not inject ElectronAsarIntegrity into `infoPlistsToIgnore`',
|
|
async () => {
|
|
const arm64AppPath = await templateApp('Arm64-1.app', 'arm64', async (appPath) => {
|
|
const { testPath } = await createTestApp('Arm64-1');
|
|
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
|
await templateApp('SubApp-1.app', 'arm64', async (subArm64AppPath) => {
|
|
await fs.move(
|
|
subArm64AppPath,
|
|
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
|
);
|
|
});
|
|
});
|
|
const x64AppPath = await templateApp('X64-1.app', 'x64', async (appPath) => {
|
|
const { testPath } = await createTestApp('X64-1');
|
|
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
|
await templateApp('SubApp-1.app', 'x64', async (subArm64AppPath) => {
|
|
await fs.move(
|
|
subArm64AppPath,
|
|
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
|
);
|
|
});
|
|
});
|
|
const outAppPath = path.resolve(appsOutPath, 'UnmodifiedPlist.app');
|
|
await makeUniversalApp({
|
|
x64AppPath,
|
|
arm64AppPath,
|
|
outAppPath,
|
|
mergeASARs: true,
|
|
infoPlistsToIgnore: 'SubApp-1.app/Contents/Info.plist',
|
|
});
|
|
await verifyApp(outAppPath);
|
|
},
|
|
VERIFY_APP_TIMEOUT,
|
|
);
|
|
|
|
// 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',
|
|
},
|
|
);
|
|
});
|
|
|
|
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,
|
|
);
|
|
|
|
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', () => {
|
|
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,
|
|
);
|
|
|
|
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 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'));
|
|
});
|
|
|
|
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
|
|
});
|