fix: add debug logging
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@continuous-auth/semantic-release-npm": "^2.0.0",
|
"@continuous-auth/semantic-release-npm": "^2.0.0",
|
||||||
|
"@types/debug": "^4.1.5",
|
||||||
"@types/fs-extra": "^9.0.4",
|
"@types/fs-extra": "^9.0.4",
|
||||||
"@types/node": "^14.14.7",
|
"@types/node": "^14.14.7",
|
||||||
"husky": "^4.3.0",
|
"husky": "^4.3.0",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@malept/cross-spawn-promise": "^1.1.0",
|
"@malept/cross-spawn-promise": "^1.1.0",
|
||||||
"asar": "^3.0.3",
|
"asar": "^3.0.3",
|
||||||
|
"debug": "^4.3.1",
|
||||||
"dir-compare": "^2.4.0",
|
"dir-compare": "^2.4.0",
|
||||||
"fs-extra": "^9.0.1"
|
"fs-extra": "^9.0.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { d } from './debug';
|
||||||
|
|
||||||
export enum AsarMode {
|
export enum AsarMode {
|
||||||
NO_ASAR,
|
NO_ASAR,
|
||||||
@@ -7,9 +8,14 @@ export enum AsarMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const detectAsarMode = async (appPath: string) => {
|
export const detectAsarMode = async (appPath: string) => {
|
||||||
|
d('checking asar mode of', appPath);
|
||||||
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
||||||
|
|
||||||
if (!(await fs.pathExists(asarPath))) return AsarMode.NO_ASAR;
|
if (!(await fs.pathExists(asarPath))) {
|
||||||
|
d('determined no asar');
|
||||||
|
return AsarMode.NO_ASAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
d('determined has asar');
|
||||||
return AsarMode.HAS_ASAR;
|
return AsarMode.HAS_ASAR;
|
||||||
};
|
};
|
||||||
|
|||||||
3
src/debug.ts
Normal file
3
src/debug.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import * as debug from 'debug';
|
||||||
|
|
||||||
|
export const d = debug('electron-universal');
|
||||||
49
src/index.ts
49
src/index.ts
@@ -7,6 +7,7 @@ import * as dircompare from 'dir-compare';
|
|||||||
import { AppFile, AppFileType, getAllAppFiles } from './file-utils';
|
import { AppFile, AppFileType, getAllAppFiles } from './file-utils';
|
||||||
import { AsarMode, detectAsarMode } from './asar-utils';
|
import { AsarMode, detectAsarMode } from './asar-utils';
|
||||||
import { sha } from './sha';
|
import { sha } from './sha';
|
||||||
|
import { d } from './debug';
|
||||||
|
|
||||||
type MakeUniversalOpts = {
|
type MakeUniversalOpts = {
|
||||||
/**
|
/**
|
||||||
@@ -33,6 +34,8 @@ 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);
|
||||||
|
|
||||||
export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => {
|
export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => {
|
||||||
|
d('making a universal app with options', opts);
|
||||||
|
|
||||||
if (process.platform !== 'darwin')
|
if (process.platform !== 'darwin')
|
||||||
throw new Error('@electron/universal is only supported on darwin platforms');
|
throw new Error('@electron/universal is only supported on darwin platforms');
|
||||||
if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath))
|
if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath))
|
||||||
@@ -43,17 +46,21 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
||||||
|
|
||||||
if (await fs.pathExists(opts.outAppPath)) {
|
if (await fs.pathExists(opts.outAppPath)) {
|
||||||
|
d('output path exists already');
|
||||||
if (!opts.force) {
|
if (!opts.force) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The out path "${opts.outAppPath}" already exists and force is not set to true`,
|
`The out path "${opts.outAppPath}" already exists and force is not set to true`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
d('overwriting existing application because force == true');
|
||||||
await fs.remove(opts.outAppPath);
|
await fs.remove(opts.outAppPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const x64AsarMode = await detectAsarMode(opts.x64AppPath);
|
const x64AsarMode = await detectAsarMode(opts.x64AppPath);
|
||||||
const arm64AsarMode = await detectAsarMode(opts.arm64AppPath);
|
const arm64AsarMode = await detectAsarMode(opts.arm64AppPath);
|
||||||
|
d('detected x64AsarMode =', x64AsarMode);
|
||||||
|
d('detected arm64AsarMode =', arm64AsarMode);
|
||||||
|
|
||||||
if (x64AsarMode !== arm64AsarMode)
|
if (x64AsarMode !== arm64AsarMode)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -61,8 +68,10 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
);
|
);
|
||||||
|
|
||||||
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
||||||
|
d('building universal app in', tmpDir);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
d('copying x64 app as starter template');
|
||||||
const tmpApp = path.resolve(tmpDir, 'Tmp.app');
|
const tmpApp = path.resolve(tmpDir, 'Tmp.app');
|
||||||
await spawn('cp', ['-R', opts.x64AppPath, tmpApp]);
|
await spawn('cp', ['-R', opts.x64AppPath, tmpApp]);
|
||||||
|
|
||||||
@@ -80,6 +89,7 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
uniqueToArm64.push(file.relativePath);
|
uniqueToArm64.push(file.relativePath);
|
||||||
}
|
}
|
||||||
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
|
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
|
||||||
|
d('some files were not in both builds, aborting');
|
||||||
console.error({
|
console.error({
|
||||||
uniqueToX64,
|
uniqueToX64,
|
||||||
uniqueToArm64,
|
uniqueToArm64,
|
||||||
@@ -93,7 +103,7 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
|
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
|
||||||
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
|
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
|
||||||
if (x64Sha !== arm64Sha) {
|
if (x64Sha !== arm64Sha) {
|
||||||
console.error(`${x64Sha} !== ${arm64Sha}`);
|
d('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`,
|
`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`,
|
||||||
);
|
);
|
||||||
@@ -101,23 +111,38 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) {
|
for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) {
|
||||||
|
const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath));
|
||||||
|
const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||||
|
|
||||||
|
d('joining two MachO files with lipo', {
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
});
|
||||||
await spawn('lipo', [
|
await spawn('lipo', [
|
||||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
first,
|
||||||
await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath)),
|
second,
|
||||||
'-create',
|
'-create',
|
||||||
'-output',
|
'-output',
|
||||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we don't have an ASAR we need to check if the two "app" folders are identical, if
|
||||||
|
* they are then we can just leave one there and call it a day. If the app folders for x64
|
||||||
|
* and arm64 are different though we need to rename each folder and create a new fake "app"
|
||||||
|
* entrypoint to dynamically load the correct app folder
|
||||||
|
*/
|
||||||
if (x64AsarMode === AsarMode.NO_ASAR) {
|
if (x64AsarMode === AsarMode.NO_ASAR) {
|
||||||
const comparison = dircompare.compareSync(
|
d('checking if the x64 and arm64 app folders are identical');
|
||||||
|
const comparison = await dircompare.compare(
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
|
||||||
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'),
|
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'),
|
||||||
{ compareSize: true, compareContent: true },
|
{ compareSize: true, compareContent: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!comparison.same) {
|
if (!comparison.same) {
|
||||||
|
d('x64 and arm64 app folders are different, creating dynamic entry ASAR');
|
||||||
await fs.move(
|
await fs.move(
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'),
|
||||||
@@ -142,16 +167,28 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
entryAsar,
|
entryAsar,
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
d('x64 and arm64 app folders are the same');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* we have to make a dynamic entrypoint. There is an assumption made here that every file in
|
||||||
|
* app.asar.unpacked is a native node module. This assumption _may_ not be true so we should
|
||||||
|
* look at codifying that assumption as actual logic.
|
||||||
|
*/
|
||||||
|
// FIXME: Codify the assumption that app.asar.unpacked only contains native modules
|
||||||
if (x64AsarMode === AsarMode.HAS_ASAR) {
|
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'));
|
const x64AsarSha = await sha(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||||
const arm64AsarSha = await sha(
|
const arm64AsarSha = await sha(
|
||||||
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (x64AsarSha !== arm64AsarSha) {
|
if (x64AsarSha !== arm64AsarSha) {
|
||||||
|
d('x64 and arm64 asars are different');
|
||||||
await fs.move(
|
await fs.move(
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'),
|
||||||
@@ -201,16 +238,20 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
entryAsar,
|
entryAsar,
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
d('x64 and arm64 asars are the same');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
|
for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
|
||||||
|
d('copying snapshot file', snapshotsFile.relativePath, 'to target application');
|
||||||
await fs.copy(
|
await fs.copy(
|
||||||
path.resolve(opts.arm64AppPath, snapshotsFile.relativePath),
|
path.resolve(opts.arm64AppPath, snapshotsFile.relativePath),
|
||||||
path.resolve(tmpApp, snapshotsFile.relativePath),
|
path.resolve(tmpApp, snapshotsFile.relativePath),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d('moving final universal app to target destination');
|
||||||
await spawn('mv', [tmpApp, opts.outAppPath]);
|
await spawn('mv', [tmpApp, opts.outAppPath]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
|
import { d } from './debug';
|
||||||
|
|
||||||
export const sha = async (filePath: string) => {
|
export const sha = async (filePath: string) => {
|
||||||
|
d('hashing', filePath);
|
||||||
const hash = crypto.createHash('sha256');
|
const hash = crypto.createHash('sha256');
|
||||||
const fileStream = fs.createReadStream(filePath);
|
const fileStream = fs.createReadStream(filePath);
|
||||||
fileStream.pipe(hash);
|
fileStream.pipe(hash);
|
||||||
|
|||||||
12
yarn.lock
12
yarn.lock
@@ -269,6 +269,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||||
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
|
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
|
||||||
|
|
||||||
|
"@types/debug@^4.1.5":
|
||||||
|
version "4.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
|
||||||
|
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
|
||||||
|
|
||||||
"@types/fs-extra@^9.0.4":
|
"@types/fs-extra@^9.0.4":
|
||||||
version "9.0.4"
|
version "9.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.4.tgz#12553138cf0438db9a31cdc8b0a3aa9332eb67aa"
|
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.4.tgz#12553138cf0438db9a31cdc8b0a3aa9332eb67aa"
|
||||||
@@ -1143,6 +1148,13 @@ debug@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
|
debug@^4.3.1:
|
||||||
|
version "4.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||||
|
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
debuglog@^1.0.1:
|
debuglog@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
|
||||||
|
|||||||
Reference in New Issue
Block a user