fix: fully respect singleArchFiles option (#152)
Some checks failed
Publish documentation / docs (push) Failing after 13s

Files listed under `singleArchFiles` are allowed to be unique for
different platforms so `dupedFiles` should not return them.

Fix: #151
This commit is contained in:
Fedor Indutny
2025-10-29 09:52:59 -07:00
committed by GitHub
parent 2e087ef6c8
commit 0939980564
7 changed files with 221 additions and 22 deletions

View File

@@ -339,6 +339,86 @@ exports[`makeUniversalApp > asar mode > should merge two different asars when \`
}
`;
exports[`makeUniversalApp > asar mode > should merge two different asars with native files when \`mergeASARs\` is enabled 1`] = `
{
"files": {
"hello-world-arm64": "<stripped>",
"hello-world-x64": "<stripped>",
"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 merge two different asars with native files when \`mergeASARs\` is enabled 2`] = `[]`;
exports[`makeUniversalApp > asar mode > should merge two different asars with native files when \`mergeASARs\` is enabled 3`] = `
[
"hello-world-arm64",
"hello-world-x64",
]
`;
exports[`makeUniversalApp > asar mode > should merge two different asars with native files when \`mergeASARs\` is enabled 4`] = `
{
"Contents/Info.plist": {
"Resources/app.asar": {
"algorithm": "SHA256",
"hash": "<stripped>",
},
},
}
`;
exports[`makeUniversalApp > asar mode > should not inject ElectronAsarIntegrity into \`infoPlistsToIgnore\` 1`] = `
{
"files": {

View File

@@ -12,8 +12,8 @@ describe('file-utils', () => {
let noAsarFiles: AppFile[];
beforeAll(async () => {
asarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64Asar.app'));
noAsarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64NoAsar.app'));
asarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64Asar.app'), {});
noAsarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64NoAsar.app'), {});
});
it('should correctly identify plist files', async () => {

View File

@@ -142,6 +142,65 @@ describe('makeUniversalApp', () => {
},
);
it(
'should merge two different asars with native files when `mergeASARs` is enabled',
{ timeout: VERIFY_APP_TIMEOUT },
async () => {
const x64AppPath = await generateNativeApp({
appNameWithExtension: 'SingleArchFiles-x64.app',
arch: 'x64',
createAsar: true,
singleArchBindings: true,
});
const arm64AppPath = await generateNativeApp({
appNameWithExtension: 'SingleArchFiles-arm64.app',
arch: 'arm64',
createAsar: true,
singleArchBindings: true,
});
const out = path.resolve(appsOutPath, 'SingleArchFiles.app');
await makeUniversalApp({
x64AppPath,
arm64AppPath,
outAppPath: out,
mergeASARs: true,
singleArchFiles: 'hello-world-*',
});
await verifyApp(out, true);
},
);
it(
'throws an error if `mergeASARs` is enabled and `singleArchFiles` is missing a unique native file',
{ timeout: VERIFY_APP_TIMEOUT },
async () => {
const x64AppPath = await generateNativeApp({
appNameWithExtension: 'SingleArchFiles-2-x64.app',
arch: 'x64',
createAsar: true,
singleArchBindings: true,
});
const arm64AppPath = await generateNativeApp({
appNameWithExtension: 'SingleArchFiles-2-arm64.app',
arch: 'arm64',
createAsar: true,
singleArchBindings: true,
});
const out = path.resolve(appsOutPath, 'SingleArchFiles-2.app');
await expect(
makeUniversalApp({
x64AppPath,
arm64AppPath,
outAppPath: out,
mergeASARs: true,
singleArchFiles: 'bad-rule',
}),
).rejects.toThrow(
/the number of mach-o files is not the same between the arm64 and x64 builds/,
);
},
);
it(
'should not inject ElectronAsarIntegrity into `infoPlistsToIgnore`',
{ timeout: VERIFY_APP_TIMEOUT },

View File

@@ -33,7 +33,12 @@ export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho =
// verify header
const asarFs = getRawHeader(path.resolve(resourcesDir, asar));
expect(
removeUnstableProperties(asarFs.header, containsRuntimeGeneratedMacho ? ['hello-world'] : []),
removeUnstableProperties(
asarFs.header,
containsRuntimeGeneratedMacho
? ['hello-world', 'hello-world-arm64', 'hello-world-x64']
: [],
),
).toMatchSnapshot();
}
@@ -53,7 +58,7 @@ export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho =
await verifyFileTree(path.resolve(resourcesDir, dir));
}
const allFiles = await fileUtils.getAllAppFiles(appPath);
const allFiles = await fileUtils.getAllAppFiles(appPath, {});
const infoPlists = allFiles
.filter(
(appFile) =>
@@ -89,7 +94,7 @@ const extractAsarIntegrity = async (infoPlist: string) => {
export const verifyFileTree = async (dirPath: string) => {
const { expect } = await import('vitest');
const dirFiles = await fileUtils.getAllAppFiles(dirPath);
const dirFiles = await fileUtils.getAllAppFiles(dirPath, {});
const files = dirFiles.map((file) => {
const it = path.join(dirPath, file.relativePath);
const name = toSystemIndependentPath(file.relativePath);
@@ -229,6 +234,7 @@ export const generateNativeApp = async (options: {
createAsar: boolean;
nativeModuleArch?: string;
additionalFiles?: Record<string, string>;
singleArchBindings?: boolean;
}) => {
const {
appNameWithExtension,
@@ -236,6 +242,7 @@ export const generateNativeApp = async (options: {
createAsar,
nativeModuleArch = arch,
additionalFiles,
singleArchBindings,
} = options;
const appPath = await templateApp(appNameWithExtension, arch, async (appPath) => {
const resources = path.join(appPath, 'Contents', 'Resources');
@@ -247,14 +254,19 @@ export const generateNativeApp = async (options: {
path.basename(appNameWithExtension, '.app'),
additionalFiles,
);
await fs.promises.cp(
path.join(appsDir, `hello-world-${nativeModuleArch}`),
path.join(testPath, 'hello-world'),
{ recursive: true, verbatimSymlinks: true },
);
let targetBinding: string;
if (singleArchBindings) {
targetBinding = path.join(testPath, `hello-world-${nativeModuleArch}`);
} else {
targetBinding = path.join(testPath, 'hello-world');
}
await fs.promises.cp(path.join(appsDir, `hello-world-${nativeModuleArch}`), targetBinding, {
recursive: true,
verbatimSymlinks: true,
});
if (createAsar) {
await createPackageWithOptions(testPath, path.resolve(resources, 'app.asar'), {
unpack: '**/hello-world',
unpack: '**/hello-world*',
});
} else {
await fs.promises.cp(testPath, resourcesApp, { recursive: true, verbatimSymlinks: true });