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

@@ -3,15 +3,19 @@ import path from 'node:path';
import { promises as stream } from 'node:stream';
import { spawn, ExitCodeError } from '@malept/cross-spawn-promise';
import { minimatch } from 'minimatch';
const MACHO_PREFIX = 'Mach-O ';
const UNPACKED_ASAR_PATH = path.join('Contents', 'Resources', 'app.asar.unpacked');
export enum AppFileType {
MACHO,
PLAIN,
INFO_PLIST,
SNAPSHOT,
APP_CODE,
SINGLE_ARCH,
}
export type AppFile = {
@@ -19,11 +23,37 @@ export type AppFile = {
type: AppFileType;
};
export type GetAllAppFilesOpts = {
singleArchFiles?: string;
};
const isSingleArchFile = (relativePath: string, opts: GetAllAppFilesOpts): boolean => {
if (opts.singleArchFiles === undefined) {
return false;
}
const unpackedPath = path.relative(UNPACKED_ASAR_PATH, relativePath);
// Outside of app.asar.unpacked
if (unpackedPath.startsWith('..')) {
return false;
}
return minimatch(unpackedPath, opts.singleArchFiles, {
matchBase: true,
});
};
/**
*
* @param appPath Path to the application
*/
export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
export const getAllAppFiles = async (
appPath: string,
opts: GetAllAppFilesOpts,
): Promise<AppFile[]> => {
const unpackedPath = path.join('Contents', 'Resources', 'app.asar.unpacked');
const files: AppFile[] = [];
const visited = new Set<string>();
@@ -35,6 +65,8 @@ export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
const info = await fs.promises.stat(p);
if (info.isSymbolicLink()) return;
if (info.isFile()) {
const relativePath = path.relative(appPath, p);
let fileType = AppFileType.PLAIN;
var fileOutput = '';
@@ -49,6 +81,8 @@ export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
}
if (p.endsWith('.asar')) {
fileType = AppFileType.APP_CODE;
} else if (isSingleArchFile(relativePath, opts)) {
fileType = AppFileType.SINGLE_ARCH;
} else if (fileOutput.startsWith(MACHO_PREFIX)) {
fileType = AppFileType.MACHO;
} else if (p.endsWith('.bin')) {
@@ -58,7 +92,7 @@ export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
}
files.push({
relativePath: path.relative(appPath, p),
relativePath,
type: fileType,
});
}

View File

@@ -75,7 +75,12 @@ export type MakeUniversalOpts = {
};
const dupedFiles = (files: AppFile[]) =>
files.filter((f) => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE);
files.filter(
(f) =>
f.type !== AppFileType.SNAPSHOT &&
f.type !== AppFileType.APP_CODE &&
f.type !== AppFileType.SINGLE_ARCH,
);
export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => {
d('making a universal app with options', opts);
@@ -121,8 +126,8 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
const uniqueToX64: string[] = [];
const uniqueToArm64: string[] = [];
const x64Files = await getAllAppFiles(await fs.promises.realpath(tmpApp));
const arm64Files = await getAllAppFiles(await fs.promises.realpath(opts.arm64AppPath));
const x64Files = await getAllAppFiles(await fs.promises.realpath(tmpApp), opts);
const arm64Files = await getAllAppFiles(await fs.promises.realpath(opts.arm64AppPath), opts);
for (const file of dupedFiles(x64Files)) {
if (!arm64Files.some((f) => f.relativePath === file.relativePath))
@@ -143,7 +148,9 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
);
}
for (const file of x64Files.filter((f) => f.type === AppFileType.PLAIN)) {
// Single Arch files are copied as is without processing.
const multiArchFiles = x64Files.filter((f) => f.type !== AppFileType.SINGLE_ARCH);
for (const file of multiArchFiles.filter((f) => f.type === AppFileType.PLAIN)) {
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
if (x64Sha !== arm64Sha) {
@@ -159,7 +166,7 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
}
}
const knownMergedMachOFiles = new Set();
for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) {
for (const machOFile of multiArchFiles.filter((f) => f.type === AppFileType.MACHO)) {
const first = await fs.promises.realpath(path.resolve(tmpApp, machOFile.relativePath));
const second = await fs.promises.realpath(
path.resolve(opts.arm64AppPath, machOFile.relativePath),
@@ -355,9 +362,9 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
}
}
const generatedIntegrity = await computeIntegrityData(path.join(tmpApp, 'Contents'));
const generatedIntegrity = await computeIntegrityData(path.join(tmpApp, 'Contents'), opts);
const plistFiles = x64Files.filter((f) => f.type === AppFileType.INFO_PLIST);
const plistFiles = multiArchFiles.filter((f) => f.type === AppFileType.INFO_PLIST);
for (const plistFile of plistFiles) {
const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath);
const arm64PlistPath = path.resolve(opts.arm64AppPath, plistFile.relativePath);

View File

@@ -17,13 +17,20 @@ export interface AsarIntegrity {
[key: string]: HeaderHash;
}
export async function computeIntegrityData(contentsPath: string): Promise<AsarIntegrity> {
export type ComputeIntegrityDataOpts = {
singleArchFiles?: string;
};
export async function computeIntegrityData(
contentsPath: string,
opts: ComputeIntegrityDataOpts,
): Promise<AsarIntegrity> {
const root = await fs.promises.realpath(contentsPath);
const resourcesRelativePath = 'Resources';
const resourcesPath = path.resolve(root, resourcesRelativePath);
const resources = await getAllAppFiles(resourcesPath);
const resources = await getAllAppFiles(resourcesPath, opts);
const resourceAsars = resources
.filter((file) => file.type === AppFileType.APP_CODE)
.reduce<IntegrityMap>(