Upload via GUI (7543 Dateien)
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Contributors to the Electron project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
# @electron/rebuild
|
||||
|
||||
[](https://github.com/electron/rebuild/actions/workflows/test.yml)
|
||||
[](https://npm.im/@electron/rebuild)
|
||||
[](https://codecov.io/gh/electron/rebuild)
|
||||
[](https://packages.electronjs.org/rebuild)
|
||||
|
||||
This executable rebuilds native Node.js modules against the version of Node.js
|
||||
that your Electron project is using. This allows you to use native Node.js
|
||||
modules in Electron apps without your system version of Node.js matching exactly
|
||||
(which is often not the case, and sometimes not even possible).
|
||||
|
||||
### How does it work?
|
||||
|
||||
Install the package with `--save-dev`:
|
||||
|
||||
```sh
|
||||
npm install --save-dev @electron/rebuild
|
||||
```
|
||||
|
||||
Then, whenever you install a new npm package, rerun electron-rebuild:
|
||||
|
||||
```sh
|
||||
$(npm bin)/electron-rebuild
|
||||
```
|
||||
|
||||
Or if you're on Windows:
|
||||
|
||||
```sh
|
||||
.\node_modules\.bin\electron-rebuild.cmd
|
||||
```
|
||||
|
||||
If you have a good node-gyp config but you see an error about a missing element on Windows like `Could not load the Visual C++ component "VCBuild.exe"`, try to launch `electron-rebuild` in an npm script:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"rebuild": "electron-rebuild -f -w yourmodule"
|
||||
}
|
||||
```
|
||||
|
||||
and then
|
||||
|
||||
```sh
|
||||
npm run rebuild
|
||||
```
|
||||
|
||||
### What are the requirements?
|
||||
|
||||
Node v22.12.0 or higher is required. Building native modules from source uses
|
||||
[`node-gyp`](https://github.com/nodejs/node-gyp#installation). Refer to the link for its
|
||||
installation/runtime requirements.
|
||||
|
||||
### CLI arguments
|
||||
|
||||
```
|
||||
Usage: electron-rebuild --version [version] --module-dir [path]
|
||||
|
||||
Options:
|
||||
-v, --version The version of Electron to build against [string]
|
||||
-f, --force Force rebuilding modules, even if we would skip
|
||||
it otherwise [boolean]
|
||||
-a, --arch Override the target architecture to something
|
||||
other than your system's [string]
|
||||
-m, --module-dir The path to the node_modules directory to rebuild
|
||||
[string]
|
||||
-w, --which-module A specific module to build, or comma separated
|
||||
list of modules. Modules will only be rebuilt if
|
||||
they also match the types of dependencies being
|
||||
rebuilt (see --types). [string]
|
||||
-o, --only Only build specified module, or comma separated
|
||||
list of modules. All others are ignored. [string]
|
||||
-e, --electron-prebuilt-dir The path to the prebuilt electron module [string]
|
||||
-d, --dist-url Custom header tarball URL [string]
|
||||
-t, --types The types of dependencies to rebuild. Comma
|
||||
separated list of "prod", "dev" and "optional".
|
||||
Default is "prod,optional" [string]
|
||||
-p, --parallel Rebuild in parallel, this is enabled by default
|
||||
on macOS and Linux [boolean]
|
||||
-s, --sequential Rebuild modules sequentially, this is enabled by
|
||||
default on Windows [boolean]
|
||||
-b, --debug Build debug version of modules [boolean]
|
||||
-j, --jobs Number of parallel compile jobs node-gyp should
|
||||
run (passed as node-gyp --jobs). Defaults to
|
||||
node-gyp's own default. [number]
|
||||
--prebuild-tag-prefix GitHub tag prefix passed to prebuild-install.
|
||||
Default is "v" [string]
|
||||
--force-abi Override the ABI version for the version of
|
||||
Electron you are targeting. Only use when
|
||||
targeting Nightly releases. [number]
|
||||
--use-electron-clang Use the clang executable that Electron used when
|
||||
building its binary. This will guarantee compiler
|
||||
compatibility [boolean]
|
||||
--disable-pre-gyp-copy Disables the pre-gyp copy step [boolean]
|
||||
--build-from-source Skips prebuild download and rebuilds module from
|
||||
source. [boolean]
|
||||
-h, --help Show help [boolean]
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The `--dist-url` value can also be provided via the `ELECTRON_REBUILD_DIST_URL`
|
||||
> environment variable. The `--dist-url` flag takes precedence when both are set.
|
||||
|
||||
> [!NOTE]
|
||||
> By default, only your `prod` and `optional` dependencies are rebuilt —
|
||||
> `devDependencies` are **not** rebuilt. This is because the default value of
|
||||
> `types` is `['prod', 'optional']`. If you have a native module in your
|
||||
> `devDependencies` that you need rebuilt, you must explicitly include `dev` in
|
||||
> the list of types:
|
||||
>
|
||||
> - CLI: `electron-rebuild --types prod,optional,dev` (or `-t prod,optional,dev`)
|
||||
> - API: `rebuild({ ..., types: ['prod', 'optional', 'dev'] })`
|
||||
|
||||
### How can I use this with [Electron Forge](https://github.com/electron/forge)?
|
||||
|
||||
This package is automatically used with Electron Forge when packaging an Electron app.
|
||||
|
||||
### How can I integrate this into [Electron Packager](https://github.com/electron/packager)?
|
||||
|
||||
electron-rebuild provides a function compatible with the [`afterCopy` hook](https://packages.electronjs.org/packager/v19.0.5/interfaces/Options.html#aftercopy)
|
||||
for Electron Packager. For example:
|
||||
|
||||
```javascript
|
||||
import { packager } from "@electron/packager";
|
||||
import { rebuild } from "@electron/rebuild";
|
||||
|
||||
packager({
|
||||
// … other options
|
||||
afterCopy: [
|
||||
async ({ buildPath, electronVersion, arch }) => {
|
||||
await rebuild({ buildPath, electronVersion, arch });
|
||||
},
|
||||
],
|
||||
// … other options
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### How can I integrate this with [prebuild](https://github.com/prebuild/prebuild)?
|
||||
|
||||
If your module uses [prebuild](https://github.com/prebuild/prebuild) for creating prebuilt binaries,
|
||||
it also uses [prebuild-install](https://github.com/prebuild/prebuild-install) to download them. If
|
||||
this is the case, then `electron-rebuild` will run `prebuild-install` to download the correct
|
||||
binaries from the project's GitHub Releases instead of rebuilding them.
|
||||
|
||||
### How can I integrate this into my build pipeline?
|
||||
|
||||
electron-rebuild is also a library that you can require into your app or
|
||||
build process. It has a very simple API:
|
||||
|
||||
```javascript
|
||||
import { rebuild } from '@electron/rebuild';
|
||||
|
||||
// Public: Rebuilds a node_modules directory with the given Electron version.
|
||||
//
|
||||
// options: Object with the following properties
|
||||
// buildPath - An absolute path to your app's directory. (The directory that contains your node_modules)
|
||||
// electronVersion - The version of Electron to rebuild for
|
||||
// arch (optional) - Default: process.arch - The arch to rebuild for
|
||||
// extraModules (optional) - Default: [] - An array of modules to rebuild as well as the detected modules
|
||||
// onlyModules (optional) - Default: null - An array of modules to rebuild, ONLY these module names will be rebuilt.
|
||||
// The "types" property will be ignored if this option is set.
|
||||
// force (optional) - Default: false - Force a rebuild of modules regardless of their current build state
|
||||
// headerURL (optional) - Default: https://www.electronjs.org/headers - The URL to download Electron header files from
|
||||
// types (optional) - Default: ['prod', 'optional'] - The types of modules to rebuild
|
||||
// mode (optional) - The rebuild mode, either 'sequential' or 'parallel' - Default varies per platform (probably shouldn't mess with this one)
|
||||
// useElectronClang (optional) - Whether to use the clang executable that Electron used when building its binary. This will guarantee compiler compatibility
|
||||
|
||||
// Returns a Promise indicating whether the operation succeeded or not
|
||||
```
|
||||
|
||||
For full API usage, see the [API documentation](https://packages.electronjs.org/rebuild).
|
||||
|
||||
A full build process might look something like:
|
||||
|
||||
```javascript
|
||||
// ESM
|
||||
import { rebuild } from "@electron/rebuild";
|
||||
|
||||
rebuild({
|
||||
buildPath: import.meta.dirname,
|
||||
electronVersion: "35.1.5",
|
||||
})
|
||||
.then(() => console.info("Rebuild Successful"))
|
||||
.catch((e) => {
|
||||
console.error("Building modules didn't work!");
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
// CommonJS
|
||||
const { rebuild } = require("@electron/rebuild");
|
||||
|
||||
rebuild({
|
||||
buildPath: __dirname,
|
||||
electronVersion: "35.1.5",
|
||||
})
|
||||
.then(() => console.info("Rebuild Successful"))
|
||||
.catch((e) => {
|
||||
console.error("Building modules didn't work!");
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
```
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { pathToFileURL } from 'node:url';
|
||||
process.argv.splice(1, 1);
|
||||
// This tricks prebuild-install into not validating on the
|
||||
// 1.8.x and 8.x ABI collision
|
||||
Object.defineProperty(process.versions, 'modules', { value: '-1', writable: false });
|
||||
void import(pathToFileURL(process.argv[1]).toString());
|
||||
//# sourceMappingURL=prebuild-shim.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prebuild-shim.js","sourceRoot":"","sources":["../src/prebuild-shim.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE1B,0DAA0D;AAC1D,8BAA8B;AAC9B,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;AAErF,KAAK,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function readPackageJson(dir: string, safe?: boolean): Promise<any>;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export async function readPackageJson(dir, safe = false) {
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(path.resolve(dir, 'package.json'), {
|
||||
encoding: 'utf-8',
|
||||
}));
|
||||
}
|
||||
catch (err) {
|
||||
if (safe) {
|
||||
return {};
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=read-package-json.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"read-package-json.js","sourceRoot":"","sources":["../src/read-package-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW,EAAE,IAAI,GAAG,KAAK;IAC7D,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CACf,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE;YACnD,QAAQ,EAAE,OAAO;SAClB,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,IAAI,EAAE,CAAC;YACT,OAAO,EAAE,CAAC;QACZ,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;AACH,CAAC"}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { BuildType, IRebuilder, RebuildMode } from './types.js';
|
||||
import { ModuleType } from './module-walker.js';
|
||||
export interface RebuildOptions {
|
||||
/**
|
||||
* The path to the `node_modules` directory to rebuild.
|
||||
*/
|
||||
buildPath: string;
|
||||
/**
|
||||
* The version of Electron to build against.
|
||||
*/
|
||||
electronVersion: string;
|
||||
/**
|
||||
* Override the target platform to something other than the host system platform.
|
||||
* Note: This only applies to downloading prebuilt binaries. **It is not possible to cross-compile native modules.**
|
||||
*
|
||||
* @defaultValue The system {@link https://nodejs.org/api/process.html#processplatform | `process.platform`} value
|
||||
*/
|
||||
platform?: NodeJS.Platform;
|
||||
/**
|
||||
* Override the target rebuild architecture to something other than the host system architecture.
|
||||
*
|
||||
* @defaultValue The system {@link https://nodejs.org/api/process.html#processarch | `process.arch`} value
|
||||
*/
|
||||
arch?: string;
|
||||
/**
|
||||
* An array of module names to rebuild in addition to detected modules
|
||||
* @default []
|
||||
*/
|
||||
extraModules?: string[];
|
||||
/**
|
||||
* An array of module names to rebuild. **Only** these modules will be rebuilt.
|
||||
*/
|
||||
onlyModules?: string[] | null;
|
||||
/**
|
||||
* Force a rebuild of modules regardless of their current build state.
|
||||
*/
|
||||
force?: boolean;
|
||||
/**
|
||||
* URL to download Electron header files from.
|
||||
* @defaultValue `https://www.electronjs.org/headers`
|
||||
*/
|
||||
headerURL?: string;
|
||||
/**
|
||||
* Array of types of dependencies to rebuild. Possible values are `prod`, `dev`, and `optional`.
|
||||
*
|
||||
* @defaultValue `['prod', 'optional']`
|
||||
*/
|
||||
types?: ModuleType[];
|
||||
/**
|
||||
* Whether to rebuild modules sequentially or in parallel.
|
||||
*
|
||||
* @defaultValue `sequential`
|
||||
*/
|
||||
mode?: RebuildMode;
|
||||
/**
|
||||
* Rebuilds a Debug build of target modules. If this is `false`, a Release build will be generated instead.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Enables hash-based caching to speed up local rebuilds.
|
||||
*
|
||||
* @experimental
|
||||
* @defaultValue false
|
||||
*/
|
||||
useCache?: boolean;
|
||||
/**
|
||||
* Whether to use the `clang` executable that Electron uses when building.
|
||||
* This will guarantee compiler compatibility.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
useElectronClang?: boolean;
|
||||
/**
|
||||
* Sets a custom cache path for the {@link useCache} option.
|
||||
* @experimental
|
||||
* @defaultValue a `.electron-rebuild-cache` folder in the `os.homedir()` directory
|
||||
*/
|
||||
cachePath?: string;
|
||||
/**
|
||||
* GitHub tag prefix passed to {@link https://www.npmjs.com/package/prebuild-install | `prebuild-install`}.
|
||||
* @defaultValue `v`
|
||||
*/
|
||||
prebuildTagPrefix?: string;
|
||||
/**
|
||||
* Path to the root of the project if using npm or yarn workspaces.
|
||||
*/
|
||||
projectRootPath?: string;
|
||||
/**
|
||||
* Override the Application Binary Interface (ABI) version for the version of Electron you are targeting.
|
||||
* Only use when targeting nightly releases.
|
||||
*
|
||||
* @see the {@link https://github.com/electron/node-abi | electron/node-abi} repository for a list of Electron and Node.js ABIs
|
||||
*/
|
||||
forceABI?: number;
|
||||
/**
|
||||
* Disables the copying of `.node` files if not needed.
|
||||
* @defaultValue false
|
||||
*/
|
||||
disablePreGypCopy?: boolean;
|
||||
/**
|
||||
* Skip prebuild download and rebuild module from source.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
buildFromSource?: boolean;
|
||||
/**
|
||||
* Array of module names to ignore during the rebuild process.
|
||||
*/
|
||||
ignoreModules?: string[];
|
||||
/**
|
||||
* Number of parallel compile jobs `node-gyp` should run, passed through as node-gyp's `--jobs` flag.
|
||||
*
|
||||
* @defaultValue node-gyp's own default when unset
|
||||
*/
|
||||
jobs?: number;
|
||||
}
|
||||
export interface RebuilderOptions extends RebuildOptions {
|
||||
lifecycle: EventEmitter;
|
||||
}
|
||||
export declare class Rebuilder implements IRebuilder {
|
||||
private ABIVersion;
|
||||
private moduleWalker;
|
||||
rebuilds: (() => Promise<void>)[];
|
||||
lifecycle: EventEmitter;
|
||||
buildPath: string;
|
||||
electronVersion: string;
|
||||
platform: NodeJS.Platform;
|
||||
arch: string;
|
||||
force: boolean;
|
||||
headerURL: string;
|
||||
mode: RebuildMode;
|
||||
debug: boolean;
|
||||
useCache: boolean;
|
||||
cachePath: string;
|
||||
prebuildTagPrefix: string;
|
||||
msvsVersion?: string;
|
||||
useElectronClang: boolean;
|
||||
disablePreGypCopy: boolean;
|
||||
buildFromSource: boolean;
|
||||
ignoreModules: string[];
|
||||
jobs?: number;
|
||||
constructor(options: RebuilderOptions);
|
||||
get ABI(): string;
|
||||
get buildType(): BuildType;
|
||||
rebuild(): Promise<void>;
|
||||
modulesToRebuild(): Promise<string[]>;
|
||||
rebuildModuleAt(modulePath: string): Promise<void>;
|
||||
}
|
||||
export type RebuildResult = Promise<void> & {
|
||||
lifecycle: EventEmitter;
|
||||
};
|
||||
export declare function rebuild(options: RebuildOptions): RebuildResult;
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
import debug from 'debug';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import fs from 'node:fs';
|
||||
import { getAbi } from 'node-abi';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { generateCacheKey, lookupModuleState } from './cache.js';
|
||||
import { BuildType } from './types.js';
|
||||
import { ModuleRebuilder } from './module-rebuilder.js';
|
||||
import { ModuleWalker } from './module-walker.js';
|
||||
const d = debug('electron-rebuild');
|
||||
const defaultMode = 'sequential';
|
||||
const defaultTypes = ['prod', 'optional'];
|
||||
export class Rebuilder {
|
||||
ABIVersion;
|
||||
moduleWalker;
|
||||
rebuilds;
|
||||
lifecycle;
|
||||
buildPath;
|
||||
electronVersion;
|
||||
platform;
|
||||
arch;
|
||||
force;
|
||||
headerURL;
|
||||
mode;
|
||||
debug;
|
||||
useCache;
|
||||
cachePath;
|
||||
prebuildTagPrefix;
|
||||
msvsVersion;
|
||||
useElectronClang;
|
||||
disablePreGypCopy;
|
||||
buildFromSource;
|
||||
ignoreModules;
|
||||
jobs;
|
||||
constructor(options) {
|
||||
this.lifecycle = options.lifecycle;
|
||||
this.buildPath = options.buildPath;
|
||||
this.electronVersion = options.electronVersion;
|
||||
this.platform = options.platform || process.platform;
|
||||
this.arch = options.arch || process.arch;
|
||||
this.force = options.force || false;
|
||||
this.headerURL = options.headerURL || 'https://www.electronjs.org/headers';
|
||||
this.mode = options.mode || defaultMode;
|
||||
this.debug = options.debug || false;
|
||||
this.useCache = options.useCache || false;
|
||||
this.useElectronClang = options.useElectronClang || false;
|
||||
this.cachePath = options.cachePath || path.resolve(os.homedir(), '.electron-rebuild-cache');
|
||||
this.prebuildTagPrefix = options.prebuildTagPrefix ?? 'v';
|
||||
this.msvsVersion = process.env.GYP_MSVS_VERSION;
|
||||
this.disablePreGypCopy = options.disablePreGypCopy || false;
|
||||
this.buildFromSource = options.buildFromSource || false;
|
||||
this.ignoreModules = options.ignoreModules || [];
|
||||
this.jobs = options.jobs;
|
||||
d('ignoreModules', this.ignoreModules);
|
||||
if (this.useCache && this.force) {
|
||||
console.warn('[WARNING]: Electron Rebuild has force enabled and cache enabled, force take precedence and the cache will not be used.');
|
||||
this.useCache = false;
|
||||
}
|
||||
// FIXME: this entire guard is unreachable according to the types
|
||||
/* oxlint-disable typescript-eslint(restrict-template-expressions) */
|
||||
if (typeof this.electronVersion === 'number') {
|
||||
if (`${this.electronVersion}`.split('.').length === 1) {
|
||||
this.electronVersion = `${this.electronVersion}.0.0`;
|
||||
}
|
||||
else {
|
||||
this.electronVersion = `${this.electronVersion}.0`;
|
||||
}
|
||||
}
|
||||
/* oxlint-enable typescript-eslint(restrict-template-expressions) */
|
||||
if (typeof this.electronVersion !== 'string') {
|
||||
throw new Error(`Expected a string version for electron version, got a "${typeof this.electronVersion}"`);
|
||||
}
|
||||
this.ABIVersion = options.forceABI?.toString();
|
||||
const onlyModules = options.onlyModules || null;
|
||||
const extraModules = new Set(options.extraModules);
|
||||
const types = options.types || defaultTypes;
|
||||
this.moduleWalker = new ModuleWalker(this.buildPath, options.projectRootPath, types, extraModules, onlyModules);
|
||||
this.rebuilds = [];
|
||||
d('rebuilding with args:', this.buildPath, this.electronVersion, this.platform, this.arch, extraModules, this.force, this.headerURL, types, this.debug);
|
||||
}
|
||||
get ABI() {
|
||||
if (this.ABIVersion === undefined) {
|
||||
this.ABIVersion = getAbi(this.electronVersion, 'electron');
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.ABIVersion;
|
||||
}
|
||||
get buildType() {
|
||||
return this.debug ? BuildType.Debug : BuildType.Release;
|
||||
}
|
||||
async rebuild() {
|
||||
if (!path.isAbsolute(this.buildPath)) {
|
||||
throw new Error('Expected buildPath to be an absolute path');
|
||||
}
|
||||
this.lifecycle.emit('start');
|
||||
const candidatePaths = [...(await this.modulesToRebuild()), this.buildPath];
|
||||
const nativeModulePaths = candidatePaths.filter((p) => fs.existsSync(path.resolve(p, 'binding.gyp')));
|
||||
this.lifecycle.emit('modules-found', nativeModulePaths.map((p) => {
|
||||
const name = path.basename(p);
|
||||
const parent = path.basename(path.dirname(p));
|
||||
return parent !== 'node_modules' ? `${parent}/${name}` : name;
|
||||
}));
|
||||
for (const modulePath of nativeModulePaths) {
|
||||
this.rebuilds.push(() => this.rebuildModuleAt(modulePath));
|
||||
}
|
||||
if (this.mode !== 'sequential') {
|
||||
await Promise.all(this.rebuilds.map((fn) => fn()));
|
||||
}
|
||||
else {
|
||||
for (const rebuildFn of this.rebuilds) {
|
||||
await rebuildFn();
|
||||
}
|
||||
}
|
||||
}
|
||||
async modulesToRebuild() {
|
||||
await this.moduleWalker.walkModules();
|
||||
for (const nodeModulesPath of await this.moduleWalker.nodeModulesPaths) {
|
||||
await this.moduleWalker.findAllModulesIn(nodeModulesPath);
|
||||
}
|
||||
return this.moduleWalker.modulesToRebuild;
|
||||
}
|
||||
async rebuildModuleAt(modulePath) {
|
||||
const moduleRebuilder = new ModuleRebuilder(this, modulePath);
|
||||
let moduleName = path.basename(modulePath);
|
||||
const parentName = path.basename(path.dirname(modulePath));
|
||||
if (parentName !== 'node_modules') {
|
||||
moduleName = `${parentName}/${moduleName}`;
|
||||
}
|
||||
this.lifecycle.emit('module-found', moduleName);
|
||||
if (!this.force && (await moduleRebuilder.alreadyBuiltByRebuild())) {
|
||||
d(`skipping: ${moduleName} as it is already built`);
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
this.lifecycle.emit('module-skip', moduleName);
|
||||
return;
|
||||
}
|
||||
d('checking', moduleName, 'against', this.ignoreModules);
|
||||
if (this.ignoreModules.includes(moduleName)) {
|
||||
d(`skipping: ${moduleName} as it is in the ignoreModules array`);
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
this.lifecycle.emit('module-skip', moduleName);
|
||||
return;
|
||||
}
|
||||
if (await moduleRebuilder.prebuildInstallNativeModuleExists()) {
|
||||
d(`skipping: ${moduleName} as it was prebuilt`);
|
||||
return;
|
||||
}
|
||||
let cacheKey;
|
||||
if (this.useCache) {
|
||||
cacheKey = await generateCacheKey({
|
||||
ABI: this.ABI,
|
||||
arch: this.arch,
|
||||
platform: this.platform,
|
||||
debug: this.debug,
|
||||
electronVersion: this.electronVersion,
|
||||
headerURL: this.headerURL,
|
||||
modulePath,
|
||||
});
|
||||
const applyDiffFn = await lookupModuleState(this.cachePath, cacheKey);
|
||||
if (typeof applyDiffFn === 'function') {
|
||||
await applyDiffFn(modulePath);
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await moduleRebuilder.rebuild(cacheKey)) {
|
||||
this.lifecycle.emit('module-done', moduleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function rebuild(options) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
d('rebuilding with args:', arguments);
|
||||
const lifecycle = new EventEmitter();
|
||||
const rebuilderOptions = { ...options, lifecycle };
|
||||
const rebuilder = new Rebuilder(rebuilderOptions);
|
||||
const ret = rebuilder.rebuild();
|
||||
ret.lifecycle = lifecycle;
|
||||
return ret;
|
||||
}
|
||||
//# sourceMappingURL=rebuild.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Find all instances of a given module in node_modules subdirectories while traversing up
|
||||
* ancestor directories.
|
||||
*
|
||||
* @param cwd the initial directory to traverse
|
||||
* @param moduleName the Node module name (should work for scoped modules as well)
|
||||
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
|
||||
*/
|
||||
export declare function searchForModule(cwd: string, moduleName: string, rootPath?: string): Promise<string[]>;
|
||||
/**
|
||||
* Find all instances of node_modules subdirectories while traversing up ancestor directories.
|
||||
*
|
||||
* @param cwd the initial directory to traverse
|
||||
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
|
||||
*/
|
||||
export declare function searchForNodeModules(cwd: string, rootPath?: string): Promise<string[]>;
|
||||
/**
|
||||
* Determine the root directory of a given project, by looking for a directory with an
|
||||
* NPM or yarn lockfile or pnpm lockfile.
|
||||
*
|
||||
* @param cwd the initial directory to traverse
|
||||
*/
|
||||
export declare function getProjectRootPath(cwd: string): Promise<string>;
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
async function shouldContinueSearch(traversedPath, rootPath, stopAtPackageJSON) {
|
||||
if (rootPath) {
|
||||
return Promise.resolve(traversedPath !== path.dirname(rootPath));
|
||||
}
|
||||
else if (stopAtPackageJSON) {
|
||||
return fs.existsSync(path.join(traversedPath, 'package.json'));
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
async function traverseAncestorDirectories(cwd, pathGenerator, rootPath, maxItems, stopAtPackageJSON) {
|
||||
const paths = [];
|
||||
let traversedPath = path.resolve(cwd);
|
||||
while (await shouldContinueSearch(traversedPath, rootPath, stopAtPackageJSON)) {
|
||||
const generatedPath = pathGenerator(traversedPath);
|
||||
if (fs.existsSync(generatedPath)) {
|
||||
paths.push(generatedPath);
|
||||
}
|
||||
const parentPath = path.dirname(traversedPath);
|
||||
if (parentPath === traversedPath || (maxItems && paths.length >= maxItems)) {
|
||||
break;
|
||||
}
|
||||
traversedPath = parentPath;
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
/**
|
||||
* Find all instances of a given module in node_modules subdirectories while traversing up
|
||||
* ancestor directories.
|
||||
*
|
||||
* @param cwd the initial directory to traverse
|
||||
* @param moduleName the Node module name (should work for scoped modules as well)
|
||||
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
|
||||
*/
|
||||
export async function searchForModule(cwd, moduleName, rootPath) {
|
||||
const pathGenerator = (traversedPath) => path.join(traversedPath, 'node_modules', moduleName);
|
||||
return traverseAncestorDirectories(cwd, pathGenerator, rootPath, undefined, true);
|
||||
}
|
||||
/**
|
||||
* Find all instances of node_modules subdirectories while traversing up ancestor directories.
|
||||
*
|
||||
* @param cwd the initial directory to traverse
|
||||
* @param rootPath the project's root path. If provided, the traversal will stop at this path.
|
||||
*/
|
||||
export async function searchForNodeModules(cwd, rootPath) {
|
||||
const pathGenerator = (traversedPath) => path.join(traversedPath, 'node_modules');
|
||||
return traverseAncestorDirectories(cwd, pathGenerator, rootPath, undefined, true);
|
||||
}
|
||||
/**
|
||||
* Determine the root directory of a given project, by looking for a directory with an
|
||||
* NPM or yarn lockfile or pnpm lockfile.
|
||||
*
|
||||
* @param cwd the initial directory to traverse
|
||||
*/
|
||||
export async function getProjectRootPath(cwd) {
|
||||
for (const lockFilename of ['yarn.lock', 'package-lock.json', 'pnpm-lock.yaml']) {
|
||||
const pathGenerator = (traversedPath) => path.join(traversedPath, lockFilename);
|
||||
const lockPaths = await traverseAncestorDirectories(cwd, pathGenerator, undefined, 1);
|
||||
if (lockPaths.length > 0) {
|
||||
return path.dirname(lockPaths[0]);
|
||||
}
|
||||
}
|
||||
return cwd;
|
||||
}
|
||||
//# sourceMappingURL=search-module.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"search-module.js","sourceRoot":"","sources":["../src/search-module.ts"],"names":[],"mappings":"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"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function downloadLinuxSysroot(electronVersion: string, targetArch: string): Promise<string>;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import crypto from 'node:crypto';
|
||||
import debug from 'debug';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { ELECTRON_GYP_DIR } from './constants.js';
|
||||
import { fetchUrl } from './fetcher.js';
|
||||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
const d = debug('electron-rebuild');
|
||||
const sysrootArchAliases = {
|
||||
x64: 'amd64',
|
||||
ia32: 'i386',
|
||||
};
|
||||
const SYSROOT_BASE_URL = 'https://dev-cdn.electronjs.org/linux-sysroots';
|
||||
export async function downloadLinuxSysroot(electronVersion, targetArch) {
|
||||
d('fetching sysroot for Electron:', electronVersion);
|
||||
const sysrootDir = path.resolve(ELECTRON_GYP_DIR, `${electronVersion}-sysroot`);
|
||||
if (fs.existsSync(path.resolve(sysrootDir, 'lib')))
|
||||
return sysrootDir;
|
||||
await fs.promises.mkdir(sysrootDir, { recursive: true });
|
||||
const linuxArch = sysrootArchAliases[targetArch] || targetArch;
|
||||
const electronSysroots = JSON.parse(await fetchUrl(`https://raw.githubusercontent.com/electron/electron/v${electronVersion}/script/sysroots.json`, 'text'));
|
||||
const { Sha1Sum: sha, Tarball: fileName } = electronSysroots[`sid_${linuxArch}`] || electronSysroots[`bullseye_${linuxArch}`];
|
||||
const sysrootURL = `${SYSROOT_BASE_URL}/${sha}/${fileName}`;
|
||||
const sysrootBuffer = await fetchUrl(sysrootURL, 'buffer');
|
||||
const actualSha = crypto.createHash('SHA1').update(sysrootBuffer).digest('hex');
|
||||
d('expected sha:', sha);
|
||||
d('actual sha:', actualSha);
|
||||
if (sha !== actualSha)
|
||||
throw new Error(`Attempted to download the linux sysroot for ${electronVersion} but the SHA checksum did not match`);
|
||||
d('writing sysroot to disk');
|
||||
const tmpTarFile = path.resolve(ELECTRON_GYP_DIR, `${electronVersion}-${fileName}`);
|
||||
if (fs.existsSync(tmpTarFile))
|
||||
await fs.promises.rm(tmpTarFile, { recursive: true, force: true });
|
||||
await fs.promises.writeFile(tmpTarFile, sysrootBuffer);
|
||||
d('decompressing sysroot');
|
||||
await spawn('tar', ['-xf', tmpTarFile, '-C', sysrootDir], { stdio: 'ignore' });
|
||||
return sysrootDir;
|
||||
}
|
||||
//# sourceMappingURL=sysroot-fetcher.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sysroot-fetcher.js","sourceRoot":"","sources":["../src/sysroot-fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAEpD,MAAM,CAAC,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC;AAEpC,MAAM,kBAAkB,GAA2B;IACjD,GAAG,EAAE,OAAO;IACZ,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,gBAAgB,GAAG,+CAA+C,CAAC;AAEzE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,eAAuB,EACvB,UAAkB;IAElB,CAAC,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,eAAe,UAAU,CAAC,CAAC;IAChF,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC;IACtE,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzD,MAAM,SAAS,GAAG,kBAAkB,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;IAC/D,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CACjC,MAAM,QAAQ,CACZ,wDAAwD,eAAe,uBAAuB,EAC9F,MAAM,CACP,CACF,CAAC;IAEF,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GACvC,gBAAgB,CAAC,OAAO,SAAS,EAAE,CAAC,IAAI,gBAAgB,CAAC,YAAY,SAAS,EAAE,CAAC,CAAC;IACpF,MAAM,UAAU,GAAG,GAAG,gBAAgB,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC5D,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE3D,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAChF,CAAC,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAC5B,IAAI,GAAG,KAAK,SAAS;QACnB,MAAM,IAAI,KAAK,CACb,+CAA+C,eAAe,qCAAqC,CACpG,CAAC;IAEJ,CAAC,CAAC,yBAAyB,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,eAAe,IAAI,QAAQ,EAAE,CAAC,CAAC;IACpF,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClG,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAEvD,CAAC,CAAC,uBAAuB,CAAC,CAAC;IAC3B,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAE/E,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
export declare enum BuildType {
|
||||
Debug = "Debug",
|
||||
Release = "Release"
|
||||
}
|
||||
export type RebuildMode = 'sequential' | 'parallel';
|
||||
export interface IRebuilder {
|
||||
ABI: string;
|
||||
arch: string;
|
||||
buildPath: string;
|
||||
buildType: BuildType;
|
||||
cachePath: string;
|
||||
debug: boolean;
|
||||
disablePreGypCopy: boolean;
|
||||
electronVersion: string;
|
||||
force: boolean;
|
||||
headerURL: string;
|
||||
jobs?: number;
|
||||
lifecycle: EventEmitter;
|
||||
mode: RebuildMode;
|
||||
msvsVersion?: string;
|
||||
platform: NodeJS.Platform;
|
||||
prebuildTagPrefix: string;
|
||||
buildFromSource: boolean;
|
||||
useCache: boolean;
|
||||
useElectronClang: boolean;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export var BuildType;
|
||||
(function (BuildType) {
|
||||
BuildType["Debug"] = "Debug";
|
||||
BuildType["Release"] = "Release";
|
||||
})(BuildType || (BuildType = {}));
|
||||
//# sourceMappingURL=types.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAN,IAAY,SAGX;AAHD,WAAY,SAAS;IACnB,4BAAe,CAAA;IACf,gCAAmB,CAAA;AACrB,CAAC,EAHW,SAAS,KAAT,SAAS,QAGpB"}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "@electron/rebuild",
|
||||
"version": "4.2.1",
|
||||
"description": "Electron supporting package to rebuild native node modules against the currently installed electron",
|
||||
"type": "module",
|
||||
"exports": "./lib/main.js",
|
||||
"typings": "lib/main.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build:docs": "typedoc src/main.ts",
|
||||
"coverage": "yarn prewarm-headers && yarn spec --coverage",
|
||||
"watch": "tsc -w",
|
||||
"prepare": "husky && yarn build",
|
||||
"lint": "oxlint && oxfmt --check",
|
||||
"spec": "tsc && vitest run",
|
||||
"test": "yarn prewarm-headers && yarn lint && yarn spec",
|
||||
"prewarm-headers": "node-gyp install --ensure"
|
||||
},
|
||||
"bin": {
|
||||
"electron-rebuild": "lib/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/electron/rebuild.git"
|
||||
},
|
||||
"keywords": [
|
||||
"electron"
|
||||
],
|
||||
"authors": [
|
||||
"Ani Betts <anais@anaisbetts.org>",
|
||||
"Electron Community"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/electron/rebuild/issues"
|
||||
},
|
||||
"homepage": "https://github.com/electron/rebuild",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"provenance": true
|
||||
},
|
||||
"dependencies": {
|
||||
"@malept/cross-spawn-promise": "^2.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"node-abi": "^4.2.0",
|
||||
"node-api-version": "^0.2.1",
|
||||
"node-gyp": "^12.2.0",
|
||||
"read-binary-file-arch": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/node": "~22.12.0",
|
||||
"@types/node-abi": "^3.0.0",
|
||||
"@vitest/coverage-v8": "^5.0.1",
|
||||
"electron": "^39.8.5",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^17.4.1",
|
||||
"oxfmt": "^0.42.0",
|
||||
"oxlint": "^1.57.0",
|
||||
"oxlint-tsgolint": "^0.18.1",
|
||||
"typedoc": "~0.28.0",
|
||||
"typescript": "^6.0.2",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^5.0.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"oxlint --fix",
|
||||
"oxfmt --write"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"@typescript-eslint/typescript-estree/minimatch": "^9.0.7"
|
||||
},
|
||||
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
|
||||
"dependenciesMeta": {
|
||||
"electron": {
|
||||
"built": true
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AsarMode {
|
||||
NO_ASAR = 0,
|
||||
HAS_ASAR = 1
|
||||
}
|
||||
export type MergeASARsOptions = {
|
||||
x64AsarPath: string;
|
||||
arm64AsarPath: string;
|
||||
outputAsarPath: string;
|
||||
singleArchFiles?: string;
|
||||
};
|
||||
export declare const detectAsarMode: (appPath: string) => Promise<AsarMode>;
|
||||
export declare const generateAsarIntegrity: (asarPath: string) => {
|
||||
algorithm: "SHA256";
|
||||
hash: string;
|
||||
};
|
||||
export declare const mergeASARs: ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }: MergeASARsOptions) => Promise<void>;
|
||||
export declare const isUniversalMachO: (fileContent: Buffer) => boolean;
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isUniversalMachO = exports.mergeASARs = exports.generateAsarIntegrity = exports.detectAsarMode = exports.AsarMode = void 0;
|
||||
const asar_1 = __importDefault(require("@electron/asar"));
|
||||
const child_process_1 = require("child_process");
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const fs_extra_1 = __importDefault(require("fs-extra"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const minimatch_1 = require("minimatch");
|
||||
const os_1 = __importDefault(require("os"));
|
||||
const debug_1 = require("./debug");
|
||||
const LIPO = 'lipo';
|
||||
var AsarMode;
|
||||
(function (AsarMode) {
|
||||
AsarMode[AsarMode["NO_ASAR"] = 0] = "NO_ASAR";
|
||||
AsarMode[AsarMode["HAS_ASAR"] = 1] = "HAS_ASAR";
|
||||
})(AsarMode || (exports.AsarMode = AsarMode = {}));
|
||||
// See: https://github.com/apple-opensource-mirror/llvmCore/blob/0c60489d96c87140db9a6a14c6e82b15f5e5d252/include/llvm/Object/MachOFormat.h#L108-L112
|
||||
const MACHO_MAGIC = new Set([
|
||||
// 32-bit Mach-O
|
||||
0xfeedface, 0xcefaedfe,
|
||||
// 64-bit Mach-O
|
||||
0xfeedfacf, 0xcffaedfe,
|
||||
]);
|
||||
const MACHO_UNIVERSAL_MAGIC = new Set([
|
||||
// universal
|
||||
0xcafebabe, 0xbebafeca,
|
||||
]);
|
||||
const detectAsarMode = async (appPath) => {
|
||||
(0, debug_1.d)('checking asar mode of', appPath);
|
||||
const asarPath = path_1.default.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
||||
if (!(await fs_extra_1.default.pathExists(asarPath))) {
|
||||
(0, debug_1.d)('determined no asar');
|
||||
return AsarMode.NO_ASAR;
|
||||
}
|
||||
(0, debug_1.d)('determined has asar');
|
||||
return AsarMode.HAS_ASAR;
|
||||
};
|
||||
exports.detectAsarMode = detectAsarMode;
|
||||
const generateAsarIntegrity = (asarPath) => {
|
||||
return {
|
||||
algorithm: 'SHA256',
|
||||
hash: crypto_1.default
|
||||
.createHash('SHA256')
|
||||
.update(asar_1.default.getRawHeader(asarPath).headerString)
|
||||
.digest('hex'),
|
||||
};
|
||||
};
|
||||
exports.generateAsarIntegrity = generateAsarIntegrity;
|
||||
function toRelativePath(file) {
|
||||
return file.replace(/^\//, '');
|
||||
}
|
||||
function isDirectory(a, file) {
|
||||
return Boolean('files' in asar_1.default.statFile(a, file));
|
||||
}
|
||||
function checkSingleArch(archive, file, allowList) {
|
||||
if (allowList === undefined || !(0, minimatch_1.minimatch)(file, allowList, { matchBase: true })) {
|
||||
throw new Error(`Detected unique file "${file}" in "${archive}" not covered by ` +
|
||||
`allowList rule: "${allowList}"`);
|
||||
}
|
||||
}
|
||||
const mergeASARs = async ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }) => {
|
||||
(0, debug_1.d)(`merging ${x64AsarPath} and ${arm64AsarPath}`);
|
||||
const x64Files = new Set(asar_1.default.listPackage(x64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
const arm64Files = new Set(asar_1.default.listPackage(arm64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
//
|
||||
// Build set of unpacked directories and files
|
||||
//
|
||||
const unpackedFiles = new Set();
|
||||
function buildUnpacked(a, fileList) {
|
||||
for (const file of fileList) {
|
||||
const stat = asar_1.default.statFile(a, file);
|
||||
if (!('unpacked' in stat) || !stat.unpacked) {
|
||||
continue;
|
||||
}
|
||||
if ('files' in stat) {
|
||||
continue;
|
||||
}
|
||||
unpackedFiles.add(file);
|
||||
}
|
||||
}
|
||||
buildUnpacked(x64AsarPath, x64Files);
|
||||
buildUnpacked(arm64AsarPath, arm64Files);
|
||||
//
|
||||
// Build list of files/directories unique to each asar
|
||||
//
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
checkSingleArch(x64AsarPath, file, singleArchFiles);
|
||||
}
|
||||
}
|
||||
const arm64Unique = [];
|
||||
for (const file of arm64Files) {
|
||||
if (!x64Files.has(file)) {
|
||||
checkSingleArch(arm64AsarPath, file, singleArchFiles);
|
||||
arm64Unique.push(file);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Find common bindings with different content
|
||||
//
|
||||
const commonBindings = [];
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
continue;
|
||||
}
|
||||
// Skip directories
|
||||
if (isDirectory(x64AsarPath, file)) {
|
||||
continue;
|
||||
}
|
||||
const x64Content = asar_1.default.extractFile(x64AsarPath, file);
|
||||
const arm64Content = asar_1.default.extractFile(arm64AsarPath, file);
|
||||
// Skip file if the same content
|
||||
if (x64Content.compare(arm64Content) === 0) {
|
||||
continue;
|
||||
}
|
||||
// Skip universal Mach-O files.
|
||||
if ((0, exports.isUniversalMachO)(x64Content)) {
|
||||
continue;
|
||||
}
|
||||
if (!MACHO_MAGIC.has(x64Content.readUInt32LE(0))) {
|
||||
throw new Error(`Can't reconcile two non-macho files ${file}`);
|
||||
}
|
||||
commonBindings.push(file);
|
||||
}
|
||||
//
|
||||
// Extract both
|
||||
//
|
||||
const x64Dir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'x64-'));
|
||||
const arm64Dir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'arm64-'));
|
||||
try {
|
||||
(0, debug_1.d)(`extracting ${x64AsarPath} to ${x64Dir}`);
|
||||
asar_1.default.extractAll(x64AsarPath, x64Dir);
|
||||
(0, debug_1.d)(`extracting ${arm64AsarPath} to ${arm64Dir}`);
|
||||
asar_1.default.extractAll(arm64AsarPath, arm64Dir);
|
||||
for (const file of arm64Unique) {
|
||||
const source = path_1.default.resolve(arm64Dir, file);
|
||||
const destination = path_1.default.resolve(x64Dir, file);
|
||||
if (isDirectory(arm64AsarPath, file)) {
|
||||
(0, debug_1.d)(`creating unique directory: ${file}`);
|
||||
await fs_extra_1.default.mkdirp(destination);
|
||||
continue;
|
||||
}
|
||||
(0, debug_1.d)(`xopying unique file: ${file}`);
|
||||
await fs_extra_1.default.mkdirp(path_1.default.dirname(destination));
|
||||
await fs_extra_1.default.copy(source, destination);
|
||||
}
|
||||
for (const binding of commonBindings) {
|
||||
const source = await fs_extra_1.default.realpath(path_1.default.resolve(arm64Dir, binding));
|
||||
const destination = await fs_extra_1.default.realpath(path_1.default.resolve(x64Dir, binding));
|
||||
(0, debug_1.d)(`merging binding: ${binding}`);
|
||||
(0, child_process_1.execFileSync)(LIPO, [source, destination, '-create', '-output', destination]);
|
||||
}
|
||||
(0, debug_1.d)(`creating archive at ${outputAsarPath}`);
|
||||
const resolvedUnpack = Array.from(unpackedFiles).map((file) => path_1.default.join(x64Dir, file));
|
||||
let unpack;
|
||||
if (resolvedUnpack.length > 1) {
|
||||
unpack = `{${resolvedUnpack.join(',')}}`;
|
||||
}
|
||||
else if (resolvedUnpack.length === 1) {
|
||||
unpack = resolvedUnpack[0];
|
||||
}
|
||||
await asar_1.default.createPackageWithOptions(x64Dir, outputAsarPath, {
|
||||
unpack,
|
||||
});
|
||||
(0, debug_1.d)('done merging');
|
||||
}
|
||||
finally {
|
||||
await Promise.all([fs_extra_1.default.remove(x64Dir), fs_extra_1.default.remove(arm64Dir)]);
|
||||
}
|
||||
};
|
||||
exports.mergeASARs = mergeASARs;
|
||||
const isUniversalMachO = (fileContent) => {
|
||||
return MACHO_UNIVERSAL_MAGIC.has(fileContent.readUInt32LE(0));
|
||||
};
|
||||
exports.isUniversalMachO = isUniversalMachO;
|
||||
//# sourceMappingURL=asar-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+2
@@ -0,0 +1,2 @@
|
||||
import debug from 'debug';
|
||||
export declare const d: debug.Debugger;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.d = void 0;
|
||||
const debug_1 = __importDefault(require("debug"));
|
||||
exports.d = (0, debug_1.default)('electron-universal');
|
||||
//# sourceMappingURL=debug.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAEb,QAAA,CAAC,GAAG,IAAA,eAAK,EAAC,oBAAoB,CAAC,CAAC"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AppFileType {
|
||||
MACHO = 0,
|
||||
PLAIN = 1,
|
||||
INFO_PLIST = 2,
|
||||
SNAPSHOT = 3,
|
||||
APP_CODE = 4
|
||||
}
|
||||
export type AppFile = {
|
||||
relativePath: string;
|
||||
type: AppFileType;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
export declare const getAllAppFiles: (appPath: string) => Promise<AppFile[]>;
|
||||
export declare const readMachOHeader: (path: string) => Promise<Buffer>;
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var m = o[Symbol.asyncIterator], i;
|
||||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
};
|
||||
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
||||
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||
function fulfill(value) { resume("next", value); }
|
||||
function reject(value) { resume("throw", value); }
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.readMachOHeader = exports.getAllAppFiles = exports.AppFileType = void 0;
|
||||
const cross_spawn_promise_1 = require("@malept/cross-spawn-promise");
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const path = __importStar(require("path"));
|
||||
const node_stream_1 = require("node:stream");
|
||||
const MACHO_PREFIX = 'Mach-O ';
|
||||
var AppFileType;
|
||||
(function (AppFileType) {
|
||||
AppFileType[AppFileType["MACHO"] = 0] = "MACHO";
|
||||
AppFileType[AppFileType["PLAIN"] = 1] = "PLAIN";
|
||||
AppFileType[AppFileType["INFO_PLIST"] = 2] = "INFO_PLIST";
|
||||
AppFileType[AppFileType["SNAPSHOT"] = 3] = "SNAPSHOT";
|
||||
AppFileType[AppFileType["APP_CODE"] = 4] = "APP_CODE";
|
||||
})(AppFileType || (exports.AppFileType = AppFileType = {}));
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
const getAllAppFiles = async (appPath) => {
|
||||
const files = [];
|
||||
const visited = new Set();
|
||||
const traverse = async (p) => {
|
||||
p = await fs.realpath(p);
|
||||
if (visited.has(p))
|
||||
return;
|
||||
visited.add(p);
|
||||
const info = await fs.stat(p);
|
||||
if (info.isSymbolicLink())
|
||||
return;
|
||||
if (info.isFile()) {
|
||||
let fileType = AppFileType.PLAIN;
|
||||
var fileOutput = '';
|
||||
try {
|
||||
fileOutput = await (0, cross_spawn_promise_1.spawn)('file', ['--brief', '--no-pad', p]);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof cross_spawn_promise_1.ExitCodeError) {
|
||||
/* silently accept error codes from "file" */
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (p.endsWith('.asar')) {
|
||||
fileType = AppFileType.APP_CODE;
|
||||
}
|
||||
else if (fileOutput.startsWith(MACHO_PREFIX)) {
|
||||
fileType = AppFileType.MACHO;
|
||||
}
|
||||
else if (p.endsWith('.bin')) {
|
||||
fileType = AppFileType.SNAPSHOT;
|
||||
}
|
||||
else if (path.basename(p) === 'Info.plist') {
|
||||
fileType = AppFileType.INFO_PLIST;
|
||||
}
|
||||
files.push({
|
||||
relativePath: path.relative(appPath, p),
|
||||
type: fileType,
|
||||
});
|
||||
}
|
||||
if (info.isDirectory()) {
|
||||
for (const child of await fs.readdir(p)) {
|
||||
await traverse(path.resolve(p, child));
|
||||
}
|
||||
}
|
||||
};
|
||||
await traverse(appPath);
|
||||
return files;
|
||||
};
|
||||
exports.getAllAppFiles = getAllAppFiles;
|
||||
const readMachOHeader = async (path) => {
|
||||
const chunks = [];
|
||||
// no need to read the entire file, we only need the first 4 bytes of the file to determine the header
|
||||
await node_stream_1.promises.pipeline(fs.createReadStream(path, { start: 0, end: 3 }), function (source) {
|
||||
return __asyncGenerator(this, arguments, function* () {
|
||||
var _a, e_1, _b, _c;
|
||||
try {
|
||||
for (var _d = true, source_1 = __asyncValues(source), source_1_1; source_1_1 = yield __await(source_1.next()), _a = source_1_1.done, !_a; _d = true) {
|
||||
_c = source_1_1.value;
|
||||
_d = false;
|
||||
const chunk = _c;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
});
|
||||
});
|
||||
return Buffer.concat(chunks);
|
||||
};
|
||||
exports.readMachOHeader = readMachOHeader;
|
||||
//# sourceMappingURL=file-utils.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file-utils.ts"],"names":[],"mappings":"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"}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Options to pass into the {@link makeUniversalApp} function.
|
||||
*
|
||||
* Requires absolute paths for input x64 and arm64 apps and an absolute path to the
|
||||
* output universal app.
|
||||
*/
|
||||
export type MakeUniversalOpts = {
|
||||
/**
|
||||
* Absolute file system path to the x64 version of your application (e.g. `/Foo/bar/MyApp_x64.app`).
|
||||
*/
|
||||
x64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path to the arm64 version of your application (e.g. `/Foo/bar/MyApp_arm64.app`).
|
||||
*/
|
||||
arm64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path you want the universal app to be written to (e.g. `/Foo/var/MyApp_universal.app`).
|
||||
*
|
||||
* If this file exists on disk already, it will be overwritten ONLY if {@link MakeUniversalOpts.force} is set to `true`.
|
||||
*/
|
||||
outAppPath: string;
|
||||
/**
|
||||
* Forcefully overwrite any existing files that are in the way of generating the universal application.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
force?: boolean;
|
||||
/**
|
||||
* Merge x64 and arm64 ASARs into one.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
mergeASARs?: boolean;
|
||||
/**
|
||||
* If {@link MakeUniversalOpts.mergeASARs} is enabled, this property provides a
|
||||
* {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of paths that are allowed to be present in one of the ASAR files, but not in the other.
|
||||
*
|
||||
*/
|
||||
singleArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of binaries that are expected to be the same x64 binary in both
|
||||
*
|
||||
* Use this if your application contains binaries that have already been merged into a universal file
|
||||
* using the `lipo` tool.
|
||||
*
|
||||
* @see Apple's {@link https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary | Building a universal macOS binary} documentation
|
||||
*
|
||||
*/
|
||||
x64ArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch} pattern of `Info.plist`
|
||||
* paths that should not receive an injected `ElectronAsarIntegrity` value.
|
||||
*
|
||||
* Use this if your application contains another bundle that's already signed.
|
||||
*/
|
||||
infoPlistsToIgnore?: string;
|
||||
};
|
||||
export declare const makeUniversalApp: (opts: MakeUniversalOpts) => Promise<void>;
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.makeUniversalApp = void 0;
|
||||
const asar = __importStar(require("@electron/asar"));
|
||||
const cross_spawn_promise_1 = require("@malept/cross-spawn-promise");
|
||||
const dircompare = __importStar(require("dir-compare"));
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const minimatch_1 = require("minimatch");
|
||||
const os = __importStar(require("os"));
|
||||
const path = __importStar(require("path"));
|
||||
const plist = __importStar(require("plist"));
|
||||
const asar_utils_1 = require("./asar-utils");
|
||||
const file_utils_1 = require("./file-utils");
|
||||
const sha_1 = require("./sha");
|
||||
const debug_1 = require("./debug");
|
||||
const integrity_1 = require("./integrity");
|
||||
const dupedFiles = (files) => files.filter((f) => f.type !== file_utils_1.AppFileType.SNAPSHOT && f.type !== file_utils_1.AppFileType.APP_CODE);
|
||||
const makeUniversalApp = async (opts) => {
|
||||
(0, debug_1.d)('making a universal app with options', opts);
|
||||
if (process.platform !== 'darwin')
|
||||
throw new Error('@electron/universal is only supported on darwin platforms');
|
||||
if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath))
|
||||
throw new Error('Expected opts.x64AppPath to be an absolute path but it was not');
|
||||
if (!opts.arm64AppPath || !path.isAbsolute(opts.arm64AppPath))
|
||||
throw new Error('Expected opts.arm64AppPath to be an absolute path but it was not');
|
||||
if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath))
|
||||
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
||||
if (await fs.pathExists(opts.outAppPath)) {
|
||||
(0, debug_1.d)('output path exists already');
|
||||
if (!opts.force) {
|
||||
throw new Error(`The out path "${opts.outAppPath}" already exists and force is not set to true`);
|
||||
}
|
||||
else {
|
||||
(0, debug_1.d)('overwriting existing application because force == true');
|
||||
await fs.remove(opts.outAppPath);
|
||||
}
|
||||
}
|
||||
const x64AsarMode = await (0, asar_utils_1.detectAsarMode)(opts.x64AppPath);
|
||||
const arm64AsarMode = await (0, asar_utils_1.detectAsarMode)(opts.arm64AppPath);
|
||||
(0, debug_1.d)('detected x64AsarMode =', x64AsarMode);
|
||||
(0, debug_1.d)('detected arm64AsarMode =', arm64AsarMode);
|
||||
if (x64AsarMode !== arm64AsarMode)
|
||||
throw new Error('Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)');
|
||||
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
||||
(0, debug_1.d)('building universal app in', tmpDir);
|
||||
try {
|
||||
(0, debug_1.d)('copying x64 app as starter template');
|
||||
const tmpApp = path.resolve(tmpDir, 'Tmp.app');
|
||||
await (0, cross_spawn_promise_1.spawn)('cp', ['-R', opts.x64AppPath, tmpApp]);
|
||||
const uniqueToX64 = [];
|
||||
const uniqueToArm64 = [];
|
||||
const x64Files = await (0, file_utils_1.getAllAppFiles)(await fs.realpath(tmpApp));
|
||||
const arm64Files = await (0, file_utils_1.getAllAppFiles)(await fs.realpath(opts.arm64AppPath));
|
||||
for (const file of dupedFiles(x64Files)) {
|
||||
if (!arm64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToX64.push(file.relativePath);
|
||||
}
|
||||
for (const file of dupedFiles(arm64Files)) {
|
||||
if (!x64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToArm64.push(file.relativePath);
|
||||
}
|
||||
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
|
||||
(0, debug_1.d)('some files were not in both builds, aborting');
|
||||
console.error({
|
||||
uniqueToX64,
|
||||
uniqueToArm64,
|
||||
});
|
||||
throw new Error('While trying to merge mach-o files across your apps we found a mismatch, the number of mach-o files is not the same between the arm64 and x64 builds');
|
||||
}
|
||||
for (const file of x64Files.filter((f) => f.type === file_utils_1.AppFileType.PLAIN)) {
|
||||
const x64Sha = await (0, sha_1.sha)(path.resolve(opts.x64AppPath, file.relativePath));
|
||||
const arm64Sha = await (0, sha_1.sha)(path.resolve(opts.arm64AppPath, file.relativePath));
|
||||
if (x64Sha !== arm64Sha) {
|
||||
(0, debug_1.d)('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`);
|
||||
// The MainMenu.nib files generated by Xcode13 are deterministic in effect but not deterministic in generated sequence
|
||||
if (path.basename(path.dirname(file.relativePath)) === 'MainMenu.nib') {
|
||||
// The mismatch here is OK so we just move on to the next one
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`);
|
||||
}
|
||||
}
|
||||
const knownMergedMachOFiles = new Set();
|
||||
for (const machOFile of x64Files.filter((f) => f.type === file_utils_1.AppFileType.MACHO)) {
|
||||
const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath));
|
||||
const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||
if ((0, asar_utils_1.isUniversalMachO)(await (0, file_utils_1.readMachOHeader)(first)) &&
|
||||
(0, asar_utils_1.isUniversalMachO)(await (0, file_utils_1.readMachOHeader)(second))) {
|
||||
(0, debug_1.d)(machOFile.relativePath, `is already universal across builds, skipping lipo`);
|
||||
knownMergedMachOFiles.add(machOFile.relativePath);
|
||||
continue;
|
||||
}
|
||||
const x64Sha = await (0, sha_1.sha)(path.resolve(opts.x64AppPath, machOFile.relativePath));
|
||||
const arm64Sha = await (0, sha_1.sha)(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||
if (x64Sha === arm64Sha) {
|
||||
if (opts.x64ArchFiles === undefined ||
|
||||
!(0, minimatch_1.minimatch)(machOFile.relativePath, opts.x64ArchFiles, { matchBase: true })) {
|
||||
throw new Error(`Detected file "${machOFile.relativePath}" that's the same in both x64 and arm64 builds and not covered by the ` +
|
||||
`x64ArchFiles rule: "${opts.x64ArchFiles}"`);
|
||||
}
|
||||
(0, debug_1.d)('SHA for Mach-O file', machOFile.relativePath, `matches across builds ${x64Sha}===${arm64Sha}, skipping lipo`);
|
||||
continue;
|
||||
}
|
||||
(0, debug_1.d)('joining two MachO files with lipo', {
|
||||
first,
|
||||
second,
|
||||
});
|
||||
await (0, cross_spawn_promise_1.spawn)('lipo', [
|
||||
first,
|
||||
second,
|
||||
'-create',
|
||||
'-output',
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||
]);
|
||||
knownMergedMachOFiles.add(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 === asar_utils_1.AsarMode.NO_ASAR) {
|
||||
(0, debug_1.d)('checking if the x64 and arm64 app folders are identical');
|
||||
const comparison = await dircompare.compare(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), { compareSize: true, compareContent: true });
|
||||
const differences = comparison.diffSet.filter((difference) => difference.state !== 'equal');
|
||||
(0, debug_1.d)(`Found ${differences.length} difference(s) between the x64 and arm64 folders`);
|
||||
const nonMergedDifferences = differences.filter((difference) => !difference.name1 ||
|
||||
!knownMergedMachOFiles.has(path.join('Contents', 'Resources', 'app', difference.relativePath, difference.name1)));
|
||||
(0, debug_1.d)(`After discluding MachO files merged with lipo ${nonMergedDifferences.length} remain.`);
|
||||
if (nonMergedDifferences.length > 0) {
|
||||
(0, debug_1.d)('x64 and arm64 app folders are different, creating dynamic entry ASAR');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'));
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'));
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
await asar.createPackage(entryAsar, path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||
}
|
||||
else {
|
||||
(0, debug_1.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 === asar_utils_1.AsarMode.HAS_ASAR && opts.mergeASARs) {
|
||||
(0, debug_1.d)('merging x64 and arm64 asars');
|
||||
const output = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await (0, asar_utils_1.mergeASARs)({
|
||||
x64AsarPath: path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||
arm64AsarPath: path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
||||
outputAsarPath: output,
|
||||
singleArchFiles: opts.singleArchFiles,
|
||||
});
|
||||
}
|
||||
else if (x64AsarMode === asar_utils_1.AsarMode.HAS_ASAR) {
|
||||
(0, debug_1.d)('checking if the x64 and arm64 asars are identical');
|
||||
const x64AsarSha = await (0, sha_1.sha)(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
|
||||
const arm64AsarSha = await (0, sha_1.sha)(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'));
|
||||
if (x64AsarSha !== arm64AsarSha) {
|
||||
(0, debug_1.d)('x64 and arm64 asars are different');
|
||||
const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath);
|
||||
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(x64Unpacked)) {
|
||||
await fs.move(x64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'));
|
||||
}
|
||||
const arm64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), arm64AsarPath);
|
||||
const arm64Unpacked = path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(arm64Unpacked)) {
|
||||
await fs.copy(arm64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'));
|
||||
}
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = JSON.parse((await asar.extractFile(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 'package.json')).toString('utf8'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await asar.createPackage(entryAsar, asarPath);
|
||||
}
|
||||
else {
|
||||
(0, debug_1.d)('x64 and arm64 asars are the same');
|
||||
}
|
||||
}
|
||||
const generatedIntegrity = await (0, integrity_1.computeIntegrityData)(path.join(tmpApp, 'Contents'));
|
||||
const plistFiles = x64Files.filter((f) => f.type === file_utils_1.AppFileType.INFO_PLIST);
|
||||
for (const plistFile of plistFiles) {
|
||||
const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath);
|
||||
const arm64PlistPath = path.resolve(opts.arm64AppPath, plistFile.relativePath);
|
||||
const _a = plist.parse(await fs.readFile(x64PlistPath, 'utf8')), { ElectronAsarIntegrity: x64Integrity } = _a, x64Plist = __rest(_a, ["ElectronAsarIntegrity"]);
|
||||
const _b = plist.parse(await fs.readFile(arm64PlistPath, 'utf8')), { ElectronAsarIntegrity: arm64Integrity } = _b, arm64Plist = __rest(_b, ["ElectronAsarIntegrity"]);
|
||||
if (JSON.stringify(x64Plist) !== JSON.stringify(arm64Plist)) {
|
||||
throw new Error(`Expected all Info.plist files to be identical when ignoring integrity when creating a universal build but "${plistFile.relativePath}" was not`);
|
||||
}
|
||||
const injectAsarIntegrity = !opts.infoPlistsToIgnore ||
|
||||
(0, minimatch_1.minimatch)(plistFile.relativePath, opts.infoPlistsToIgnore, { matchBase: true });
|
||||
const mergedPlist = injectAsarIntegrity
|
||||
? Object.assign(Object.assign({}, x64Plist), { ElectronAsarIntegrity: generatedIntegrity }) : Object.assign({}, x64Plist);
|
||||
await fs.writeFile(path.resolve(tmpApp, plistFile.relativePath), plist.build(mergedPlist));
|
||||
}
|
||||
for (const snapshotsFile of arm64Files.filter((f) => f.type === file_utils_1.AppFileType.SNAPSHOT)) {
|
||||
(0, debug_1.d)('copying snapshot file', snapshotsFile.relativePath, 'to target application');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath));
|
||||
}
|
||||
(0, debug_1.d)('moving final universal app to target destination');
|
||||
await fs.mkdirp(path.dirname(opts.outAppPath));
|
||||
await (0, cross_spawn_promise_1.spawn)('mv', [tmpApp, opts.outAppPath]);
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
await fs.remove(tmpDir);
|
||||
}
|
||||
};
|
||||
exports.makeUniversalApp = makeUniversalApp;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+8
@@ -0,0 +1,8 @@
|
||||
export interface HeaderHash {
|
||||
algorithm: 'SHA256';
|
||||
hash: string;
|
||||
}
|
||||
export interface AsarIntegrity {
|
||||
[key: string]: HeaderHash;
|
||||
}
|
||||
export declare function computeIntegrityData(contentsPath: string): Promise<AsarIntegrity>;
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.computeIntegrityData = void 0;
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const file_utils_1 = require("./file-utils");
|
||||
const asar_utils_1 = require("./asar-utils");
|
||||
async function computeIntegrityData(contentsPath) {
|
||||
const root = await fs.realpath(contentsPath);
|
||||
const resourcesRelativePath = 'Resources';
|
||||
const resourcesPath = path_1.default.resolve(root, resourcesRelativePath);
|
||||
const resources = await (0, file_utils_1.getAllAppFiles)(resourcesPath);
|
||||
const resourceAsars = resources
|
||||
.filter((file) => file.type === file_utils_1.AppFileType.APP_CODE)
|
||||
.reduce((prev, file) => (Object.assign(Object.assign({}, prev), { [path_1.default.join(resourcesRelativePath, file.relativePath)]: path_1.default.join(resourcesPath, file.relativePath) })), {});
|
||||
// sort to produce constant result
|
||||
const allAsars = Object.entries(resourceAsars).sort(([name1], [name2]) => name1.localeCompare(name2));
|
||||
const hashes = await Promise.all(allAsars.map(async ([, from]) => (0, asar_utils_1.generateAsarIntegrity)(from)));
|
||||
const asarIntegrity = {};
|
||||
for (let i = 0; i < allAsars.length; i++) {
|
||||
const [asar] = allAsars[i];
|
||||
asarIntegrity[asar] = hashes[i];
|
||||
}
|
||||
return asarIntegrity;
|
||||
}
|
||||
exports.computeIntegrityData = computeIntegrityData;
|
||||
//# sourceMappingURL=integrity.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"integrity.js","sourceRoot":"","sources":["../../src/integrity.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAC/B,gDAAwB;AACxB,6CAA2D;AAE3D,6CAAqD;AAe9C,KAAK,UAAU,oBAAoB,CAAC,YAAoB;IAC7D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAE7C,MAAM,qBAAqB,GAAG,WAAW,CAAC;IAC1C,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAEhE,MAAM,SAAS,GAAG,MAAM,IAAA,2BAAc,EAAC,aAAa,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,SAAS;SAC5B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,wBAAW,CAAC,QAAQ,CAAC;SACpD,MAAM,CACL,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,iCACX,IAAI,KACP,CAAC,cAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,cAAI,CAAC,IAAI,CAC9D,aAAa,EACb,IAAI,CAAC,YAAY,CAClB,IACD,EACF,EAAE,CACH,CAAC;IAEJ,kCAAkC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CACvE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAC3B,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAA,kCAAqB,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChG,MAAM,aAAa,GAAkB,EAAE,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACxC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3B,aAAa,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;KACjC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AA/BD,oDA+BC"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare const sha: (filePath: string) => Promise<any>;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sha = void 0;
|
||||
const fs = __importStar(require("fs-extra"));
|
||||
const crypto = __importStar(require("crypto"));
|
||||
const promises_1 = require("stream/promises");
|
||||
const debug_1 = require("./debug");
|
||||
const sha = async (filePath) => {
|
||||
(0, debug_1.d)('hashing', filePath);
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.setEncoding('hex');
|
||||
await (0, promises_1.pipeline)(fs.createReadStream(filePath), hash);
|
||||
return hash.read();
|
||||
};
|
||||
exports.sha = sha;
|
||||
//# sourceMappingURL=sha.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sha.js","sourceRoot":"","sources":["../../src/sha.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAC/B,+CAAiC;AACjC,8CAA2C;AAE3C,mCAA4B;AAErB,MAAM,GAAG,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC5C,IAAA,SAAC,EAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,IAAA,mBAAQ,EAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC;AANW,QAAA,GAAG,OAMd"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AsarMode {
|
||||
NO_ASAR = 0,
|
||||
HAS_ASAR = 1
|
||||
}
|
||||
export type MergeASARsOptions = {
|
||||
x64AsarPath: string;
|
||||
arm64AsarPath: string;
|
||||
outputAsarPath: string;
|
||||
singleArchFiles?: string;
|
||||
};
|
||||
export declare const detectAsarMode: (appPath: string) => Promise<AsarMode>;
|
||||
export declare const generateAsarIntegrity: (asarPath: string) => {
|
||||
algorithm: "SHA256";
|
||||
hash: string;
|
||||
};
|
||||
export declare const mergeASARs: ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }: MergeASARsOptions) => Promise<void>;
|
||||
export declare const isUniversalMachO: (fileContent: Buffer) => boolean;
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
import asar from '@electron/asar';
|
||||
import { execFileSync } from 'child_process';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { minimatch } from 'minimatch';
|
||||
import os from 'os';
|
||||
import { d } from './debug';
|
||||
const LIPO = 'lipo';
|
||||
export var AsarMode;
|
||||
(function (AsarMode) {
|
||||
AsarMode[AsarMode["NO_ASAR"] = 0] = "NO_ASAR";
|
||||
AsarMode[AsarMode["HAS_ASAR"] = 1] = "HAS_ASAR";
|
||||
})(AsarMode || (AsarMode = {}));
|
||||
// See: https://github.com/apple-opensource-mirror/llvmCore/blob/0c60489d96c87140db9a6a14c6e82b15f5e5d252/include/llvm/Object/MachOFormat.h#L108-L112
|
||||
const MACHO_MAGIC = new Set([
|
||||
// 32-bit Mach-O
|
||||
0xfeedface, 0xcefaedfe,
|
||||
// 64-bit Mach-O
|
||||
0xfeedfacf, 0xcffaedfe,
|
||||
]);
|
||||
const MACHO_UNIVERSAL_MAGIC = new Set([
|
||||
// universal
|
||||
0xcafebabe, 0xbebafeca,
|
||||
]);
|
||||
export const detectAsarMode = async (appPath) => {
|
||||
d('checking asar mode of', appPath);
|
||||
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
||||
if (!(await fs.pathExists(asarPath))) {
|
||||
d('determined no asar');
|
||||
return AsarMode.NO_ASAR;
|
||||
}
|
||||
d('determined has asar');
|
||||
return AsarMode.HAS_ASAR;
|
||||
};
|
||||
export const generateAsarIntegrity = (asarPath) => {
|
||||
return {
|
||||
algorithm: 'SHA256',
|
||||
hash: crypto
|
||||
.createHash('SHA256')
|
||||
.update(asar.getRawHeader(asarPath).headerString)
|
||||
.digest('hex'),
|
||||
};
|
||||
};
|
||||
function toRelativePath(file) {
|
||||
return file.replace(/^\//, '');
|
||||
}
|
||||
function isDirectory(a, file) {
|
||||
return Boolean('files' in asar.statFile(a, file));
|
||||
}
|
||||
function checkSingleArch(archive, file, allowList) {
|
||||
if (allowList === undefined || !minimatch(file, allowList, { matchBase: true })) {
|
||||
throw new Error(`Detected unique file "${file}" in "${archive}" not covered by ` +
|
||||
`allowList rule: "${allowList}"`);
|
||||
}
|
||||
}
|
||||
export const mergeASARs = async ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }) => {
|
||||
d(`merging ${x64AsarPath} and ${arm64AsarPath}`);
|
||||
const x64Files = new Set(asar.listPackage(x64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
const arm64Files = new Set(asar.listPackage(arm64AsarPath, { isPack: false }).map(toRelativePath));
|
||||
//
|
||||
// Build set of unpacked directories and files
|
||||
//
|
||||
const unpackedFiles = new Set();
|
||||
function buildUnpacked(a, fileList) {
|
||||
for (const file of fileList) {
|
||||
const stat = asar.statFile(a, file);
|
||||
if (!('unpacked' in stat) || !stat.unpacked) {
|
||||
continue;
|
||||
}
|
||||
if ('files' in stat) {
|
||||
continue;
|
||||
}
|
||||
unpackedFiles.add(file);
|
||||
}
|
||||
}
|
||||
buildUnpacked(x64AsarPath, x64Files);
|
||||
buildUnpacked(arm64AsarPath, arm64Files);
|
||||
//
|
||||
// Build list of files/directories unique to each asar
|
||||
//
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
checkSingleArch(x64AsarPath, file, singleArchFiles);
|
||||
}
|
||||
}
|
||||
const arm64Unique = [];
|
||||
for (const file of arm64Files) {
|
||||
if (!x64Files.has(file)) {
|
||||
checkSingleArch(arm64AsarPath, file, singleArchFiles);
|
||||
arm64Unique.push(file);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Find common bindings with different content
|
||||
//
|
||||
const commonBindings = [];
|
||||
for (const file of x64Files) {
|
||||
if (!arm64Files.has(file)) {
|
||||
continue;
|
||||
}
|
||||
// Skip directories
|
||||
if (isDirectory(x64AsarPath, file)) {
|
||||
continue;
|
||||
}
|
||||
const x64Content = asar.extractFile(x64AsarPath, file);
|
||||
const arm64Content = asar.extractFile(arm64AsarPath, file);
|
||||
// Skip file if the same content
|
||||
if (x64Content.compare(arm64Content) === 0) {
|
||||
continue;
|
||||
}
|
||||
// Skip universal Mach-O files.
|
||||
if (isUniversalMachO(x64Content)) {
|
||||
continue;
|
||||
}
|
||||
if (!MACHO_MAGIC.has(x64Content.readUInt32LE(0))) {
|
||||
throw new Error(`Can't reconcile two non-macho files ${file}`);
|
||||
}
|
||||
commonBindings.push(file);
|
||||
}
|
||||
//
|
||||
// Extract both
|
||||
//
|
||||
const x64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'x64-'));
|
||||
const arm64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'arm64-'));
|
||||
try {
|
||||
d(`extracting ${x64AsarPath} to ${x64Dir}`);
|
||||
asar.extractAll(x64AsarPath, x64Dir);
|
||||
d(`extracting ${arm64AsarPath} to ${arm64Dir}`);
|
||||
asar.extractAll(arm64AsarPath, arm64Dir);
|
||||
for (const file of arm64Unique) {
|
||||
const source = path.resolve(arm64Dir, file);
|
||||
const destination = path.resolve(x64Dir, file);
|
||||
if (isDirectory(arm64AsarPath, file)) {
|
||||
d(`creating unique directory: ${file}`);
|
||||
await fs.mkdirp(destination);
|
||||
continue;
|
||||
}
|
||||
d(`xopying unique file: ${file}`);
|
||||
await fs.mkdirp(path.dirname(destination));
|
||||
await fs.copy(source, destination);
|
||||
}
|
||||
for (const binding of commonBindings) {
|
||||
const source = await fs.realpath(path.resolve(arm64Dir, binding));
|
||||
const destination = await fs.realpath(path.resolve(x64Dir, binding));
|
||||
d(`merging binding: ${binding}`);
|
||||
execFileSync(LIPO, [source, destination, '-create', '-output', destination]);
|
||||
}
|
||||
d(`creating archive at ${outputAsarPath}`);
|
||||
const resolvedUnpack = Array.from(unpackedFiles).map((file) => path.join(x64Dir, file));
|
||||
let unpack;
|
||||
if (resolvedUnpack.length > 1) {
|
||||
unpack = `{${resolvedUnpack.join(',')}}`;
|
||||
}
|
||||
else if (resolvedUnpack.length === 1) {
|
||||
unpack = resolvedUnpack[0];
|
||||
}
|
||||
await asar.createPackageWithOptions(x64Dir, outputAsarPath, {
|
||||
unpack,
|
||||
});
|
||||
d('done merging');
|
||||
}
|
||||
finally {
|
||||
await Promise.all([fs.remove(x64Dir), fs.remove(arm64Dir)]);
|
||||
}
|
||||
};
|
||||
export const isUniversalMachO = (fileContent) => {
|
||||
return MACHO_UNIVERSAL_MAGIC.has(fileContent.readUInt32LE(0));
|
||||
};
|
||||
//# sourceMappingURL=asar-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+2
@@ -0,0 +1,2 @@
|
||||
import debug from 'debug';
|
||||
export declare const d: debug.Debugger;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import debug from 'debug';
|
||||
export const d = debug('electron-universal');
|
||||
//# sourceMappingURL=debug.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,oBAAoB,CAAC,CAAC"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/// <reference types="node" />
|
||||
export declare enum AppFileType {
|
||||
MACHO = 0,
|
||||
PLAIN = 1,
|
||||
INFO_PLIST = 2,
|
||||
SNAPSHOT = 3,
|
||||
APP_CODE = 4
|
||||
}
|
||||
export type AppFile = {
|
||||
relativePath: string;
|
||||
type: AppFileType;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
export declare const getAllAppFiles: (appPath: string) => Promise<AppFile[]>;
|
||||
export declare const readMachOHeader: (path: string) => Promise<Buffer>;
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var m = o[Symbol.asyncIterator], i;
|
||||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||||
};
|
||||
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
||||
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||
function fulfill(value) { resume("next", value); }
|
||||
function reject(value) { resume("throw", value); }
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
};
|
||||
import { spawn, ExitCodeError } from '@malept/cross-spawn-promise';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { promises as stream } from 'node:stream';
|
||||
const MACHO_PREFIX = 'Mach-O ';
|
||||
export var AppFileType;
|
||||
(function (AppFileType) {
|
||||
AppFileType[AppFileType["MACHO"] = 0] = "MACHO";
|
||||
AppFileType[AppFileType["PLAIN"] = 1] = "PLAIN";
|
||||
AppFileType[AppFileType["INFO_PLIST"] = 2] = "INFO_PLIST";
|
||||
AppFileType[AppFileType["SNAPSHOT"] = 3] = "SNAPSHOT";
|
||||
AppFileType[AppFileType["APP_CODE"] = 4] = "APP_CODE";
|
||||
})(AppFileType || (AppFileType = {}));
|
||||
/**
|
||||
*
|
||||
* @param appPath Path to the application
|
||||
*/
|
||||
export const getAllAppFiles = async (appPath) => {
|
||||
const files = [];
|
||||
const visited = new Set();
|
||||
const traverse = async (p) => {
|
||||
p = await fs.realpath(p);
|
||||
if (visited.has(p))
|
||||
return;
|
||||
visited.add(p);
|
||||
const info = await fs.stat(p);
|
||||
if (info.isSymbolicLink())
|
||||
return;
|
||||
if (info.isFile()) {
|
||||
let fileType = AppFileType.PLAIN;
|
||||
var fileOutput = '';
|
||||
try {
|
||||
fileOutput = await spawn('file', ['--brief', '--no-pad', p]);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof ExitCodeError) {
|
||||
/* silently accept error codes from "file" */
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (p.endsWith('.asar')) {
|
||||
fileType = AppFileType.APP_CODE;
|
||||
}
|
||||
else if (fileOutput.startsWith(MACHO_PREFIX)) {
|
||||
fileType = AppFileType.MACHO;
|
||||
}
|
||||
else if (p.endsWith('.bin')) {
|
||||
fileType = AppFileType.SNAPSHOT;
|
||||
}
|
||||
else if (path.basename(p) === 'Info.plist') {
|
||||
fileType = AppFileType.INFO_PLIST;
|
||||
}
|
||||
files.push({
|
||||
relativePath: path.relative(appPath, p),
|
||||
type: fileType,
|
||||
});
|
||||
}
|
||||
if (info.isDirectory()) {
|
||||
for (const child of await fs.readdir(p)) {
|
||||
await traverse(path.resolve(p, child));
|
||||
}
|
||||
}
|
||||
};
|
||||
await traverse(appPath);
|
||||
return files;
|
||||
};
|
||||
export const readMachOHeader = async (path) => {
|
||||
const chunks = [];
|
||||
// no need to read the entire file, we only need the first 4 bytes of the file to determine the header
|
||||
await stream.pipeline(fs.createReadStream(path, { start: 0, end: 3 }), function (source) {
|
||||
return __asyncGenerator(this, arguments, function* () {
|
||||
var _a, e_1, _b, _c;
|
||||
try {
|
||||
for (var _d = true, source_1 = __asyncValues(source), source_1_1; source_1_1 = yield __await(source_1.next()), _a = source_1_1.done, !_a; _d = true) {
|
||||
_c = source_1_1.value;
|
||||
_d = false;
|
||||
const chunk = _c;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
}
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (!_d && !_a && (_b = source_1.return)) yield __await(_b.call(source_1));
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
});
|
||||
});
|
||||
return Buffer.concat(chunks);
|
||||
};
|
||||
//# sourceMappingURL=file-utils.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file-utils.ts"],"names":[],"mappings":"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"}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Options to pass into the {@link makeUniversalApp} function.
|
||||
*
|
||||
* Requires absolute paths for input x64 and arm64 apps and an absolute path to the
|
||||
* output universal app.
|
||||
*/
|
||||
export type MakeUniversalOpts = {
|
||||
/**
|
||||
* Absolute file system path to the x64 version of your application (e.g. `/Foo/bar/MyApp_x64.app`).
|
||||
*/
|
||||
x64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path to the arm64 version of your application (e.g. `/Foo/bar/MyApp_arm64.app`).
|
||||
*/
|
||||
arm64AppPath: string;
|
||||
/**
|
||||
* Absolute file system path you want the universal app to be written to (e.g. `/Foo/var/MyApp_universal.app`).
|
||||
*
|
||||
* If this file exists on disk already, it will be overwritten ONLY if {@link MakeUniversalOpts.force} is set to `true`.
|
||||
*/
|
||||
outAppPath: string;
|
||||
/**
|
||||
* Forcefully overwrite any existing files that are in the way of generating the universal application.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
force?: boolean;
|
||||
/**
|
||||
* Merge x64 and arm64 ASARs into one.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
mergeASARs?: boolean;
|
||||
/**
|
||||
* If {@link MakeUniversalOpts.mergeASARs} is enabled, this property provides a
|
||||
* {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of paths that are allowed to be present in one of the ASAR files, but not in the other.
|
||||
*
|
||||
*/
|
||||
singleArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch}
|
||||
* pattern of binaries that are expected to be the same x64 binary in both
|
||||
*
|
||||
* Use this if your application contains binaries that have already been merged into a universal file
|
||||
* using the `lipo` tool.
|
||||
*
|
||||
* @see Apple's {@link https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary | Building a universal macOS binary} documentation
|
||||
*
|
||||
*/
|
||||
x64ArchFiles?: string;
|
||||
/**
|
||||
* A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch} pattern of `Info.plist`
|
||||
* paths that should not receive an injected `ElectronAsarIntegrity` value.
|
||||
*
|
||||
* Use this if your application contains another bundle that's already signed.
|
||||
*/
|
||||
infoPlistsToIgnore?: string;
|
||||
};
|
||||
export declare const makeUniversalApp: (opts: MakeUniversalOpts) => Promise<void>;
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
import * as asar from '@electron/asar';
|
||||
import { spawn } from '@malept/cross-spawn-promise';
|
||||
import * as dircompare from 'dir-compare';
|
||||
import * as fs from 'fs-extra';
|
||||
import { minimatch } from 'minimatch';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as plist from 'plist';
|
||||
import { AsarMode, detectAsarMode, isUniversalMachO, mergeASARs } from './asar-utils';
|
||||
import { AppFileType, getAllAppFiles, readMachOHeader } from './file-utils';
|
||||
import { sha } from './sha';
|
||||
import { d } from './debug';
|
||||
import { computeIntegrityData } from './integrity';
|
||||
const dupedFiles = (files) => files.filter((f) => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE);
|
||||
export const makeUniversalApp = async (opts) => {
|
||||
d('making a universal app with options', opts);
|
||||
if (process.platform !== 'darwin')
|
||||
throw new Error('@electron/universal is only supported on darwin platforms');
|
||||
if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath))
|
||||
throw new Error('Expected opts.x64AppPath to be an absolute path but it was not');
|
||||
if (!opts.arm64AppPath || !path.isAbsolute(opts.arm64AppPath))
|
||||
throw new Error('Expected opts.arm64AppPath to be an absolute path but it was not');
|
||||
if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath))
|
||||
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
||||
if (await fs.pathExists(opts.outAppPath)) {
|
||||
d('output path exists already');
|
||||
if (!opts.force) {
|
||||
throw new Error(`The out path "${opts.outAppPath}" already exists and force is not set to true`);
|
||||
}
|
||||
else {
|
||||
d('overwriting existing application because force == true');
|
||||
await fs.remove(opts.outAppPath);
|
||||
}
|
||||
}
|
||||
const x64AsarMode = await detectAsarMode(opts.x64AppPath);
|
||||
const arm64AsarMode = await detectAsarMode(opts.arm64AppPath);
|
||||
d('detected x64AsarMode =', x64AsarMode);
|
||||
d('detected arm64AsarMode =', arm64AsarMode);
|
||||
if (x64AsarMode !== arm64AsarMode)
|
||||
throw new Error('Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)');
|
||||
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
||||
d('building universal app in', tmpDir);
|
||||
try {
|
||||
d('copying x64 app as starter template');
|
||||
const tmpApp = path.resolve(tmpDir, 'Tmp.app');
|
||||
await spawn('cp', ['-R', opts.x64AppPath, tmpApp]);
|
||||
const uniqueToX64 = [];
|
||||
const uniqueToArm64 = [];
|
||||
const x64Files = await getAllAppFiles(await fs.realpath(tmpApp));
|
||||
const arm64Files = await getAllAppFiles(await fs.realpath(opts.arm64AppPath));
|
||||
for (const file of dupedFiles(x64Files)) {
|
||||
if (!arm64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToX64.push(file.relativePath);
|
||||
}
|
||||
for (const file of dupedFiles(arm64Files)) {
|
||||
if (!x64Files.some((f) => f.relativePath === file.relativePath))
|
||||
uniqueToArm64.push(file.relativePath);
|
||||
}
|
||||
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
|
||||
d('some files were not in both builds, aborting');
|
||||
console.error({
|
||||
uniqueToX64,
|
||||
uniqueToArm64,
|
||||
});
|
||||
throw new Error('While trying to merge mach-o files across your apps we found a mismatch, the number of mach-o files is not the same between the arm64 and x64 builds');
|
||||
}
|
||||
for (const file of x64Files.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) {
|
||||
d('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`);
|
||||
// The MainMenu.nib files generated by Xcode13 are deterministic in effect but not deterministic in generated sequence
|
||||
if (path.basename(path.dirname(file.relativePath)) === 'MainMenu.nib') {
|
||||
// The mismatch here is OK so we just move on to the next one
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`);
|
||||
}
|
||||
}
|
||||
const knownMergedMachOFiles = new Set();
|
||||
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));
|
||||
if (isUniversalMachO(await readMachOHeader(first)) &&
|
||||
isUniversalMachO(await readMachOHeader(second))) {
|
||||
d(machOFile.relativePath, `is already universal across builds, skipping lipo`);
|
||||
knownMergedMachOFiles.add(machOFile.relativePath);
|
||||
continue;
|
||||
}
|
||||
const x64Sha = await sha(path.resolve(opts.x64AppPath, machOFile.relativePath));
|
||||
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
||||
if (x64Sha === arm64Sha) {
|
||||
if (opts.x64ArchFiles === undefined ||
|
||||
!minimatch(machOFile.relativePath, opts.x64ArchFiles, { matchBase: true })) {
|
||||
throw new Error(`Detected file "${machOFile.relativePath}" that's the same in both x64 and arm64 builds and not covered by the ` +
|
||||
`x64ArchFiles rule: "${opts.x64ArchFiles}"`);
|
||||
}
|
||||
d('SHA for Mach-O file', machOFile.relativePath, `matches across builds ${x64Sha}===${arm64Sha}, skipping lipo`);
|
||||
continue;
|
||||
}
|
||||
d('joining two MachO files with lipo', {
|
||||
first,
|
||||
second,
|
||||
});
|
||||
await spawn('lipo', [
|
||||
first,
|
||||
second,
|
||||
'-create',
|
||||
'-output',
|
||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||
]);
|
||||
knownMergedMachOFiles.add(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) {
|
||||
d('checking if the x64 and arm64 app folders are identical');
|
||||
const comparison = await dircompare.compare(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), { compareSize: true, compareContent: true });
|
||||
const differences = comparison.diffSet.filter((difference) => difference.state !== 'equal');
|
||||
d(`Found ${differences.length} difference(s) between the x64 and arm64 folders`);
|
||||
const nonMergedDifferences = differences.filter((difference) => !difference.name1 ||
|
||||
!knownMergedMachOFiles.has(path.join('Contents', 'Resources', 'app', difference.relativePath, difference.name1)));
|
||||
d(`After discluding MachO files merged with lipo ${nonMergedDifferences.length} remain.`);
|
||||
if (nonMergedDifferences.length > 0) {
|
||||
d('x64 and arm64 app folders are different, creating dynamic entry ASAR');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'));
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'));
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
await asar.createPackage(entryAsar, 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 && opts.mergeASARs) {
|
||||
d('merging x64 and arm64 asars');
|
||||
const output = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await mergeASARs({
|
||||
x64AsarPath: path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||
arm64AsarPath: path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
||||
outputAsarPath: output,
|
||||
singleArchFiles: opts.singleArchFiles,
|
||||
});
|
||||
}
|
||||
else 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 arm64AsarSha = await sha(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'));
|
||||
if (x64AsarSha !== arm64AsarSha) {
|
||||
d('x64 and arm64 asars are different');
|
||||
const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar');
|
||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath);
|
||||
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(x64Unpacked)) {
|
||||
await fs.move(x64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'));
|
||||
}
|
||||
const arm64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), arm64AsarPath);
|
||||
const arm64Unpacked = path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||
if (await fs.pathExists(arm64Unpacked)) {
|
||||
await fs.copy(arm64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'));
|
||||
}
|
||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||
await fs.mkdir(entryAsar);
|
||||
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'), path.resolve(entryAsar, 'index.js'));
|
||||
let pj = JSON.parse((await asar.extractFile(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 'package.json')).toString('utf8'));
|
||||
pj.main = 'index.js';
|
||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
||||
const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||
await asar.createPackage(entryAsar, asarPath);
|
||||
}
|
||||
else {
|
||||
d('x64 and arm64 asars are the same');
|
||||
}
|
||||
}
|
||||
const generatedIntegrity = await computeIntegrityData(path.join(tmpApp, 'Contents'));
|
||||
const plistFiles = x64Files.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);
|
||||
const _a = plist.parse(await fs.readFile(x64PlistPath, 'utf8')), { ElectronAsarIntegrity: x64Integrity } = _a, x64Plist = __rest(_a, ["ElectronAsarIntegrity"]);
|
||||
const _b = plist.parse(await fs.readFile(arm64PlistPath, 'utf8')), { ElectronAsarIntegrity: arm64Integrity } = _b, arm64Plist = __rest(_b, ["ElectronAsarIntegrity"]);
|
||||
if (JSON.stringify(x64Plist) !== JSON.stringify(arm64Plist)) {
|
||||
throw new Error(`Expected all Info.plist files to be identical when ignoring integrity when creating a universal build but "${plistFile.relativePath}" was not`);
|
||||
}
|
||||
const injectAsarIntegrity = !opts.infoPlistsToIgnore ||
|
||||
minimatch(plistFile.relativePath, opts.infoPlistsToIgnore, { matchBase: true });
|
||||
const mergedPlist = injectAsarIntegrity
|
||||
? Object.assign(Object.assign({}, x64Plist), { ElectronAsarIntegrity: generatedIntegrity }) : Object.assign({}, x64Plist);
|
||||
await fs.writeFile(path.resolve(tmpApp, plistFile.relativePath), plist.build(mergedPlist));
|
||||
}
|
||||
for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
|
||||
d('copying snapshot file', snapshotsFile.relativePath, 'to target application');
|
||||
await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath));
|
||||
}
|
||||
d('moving final universal app to target destination');
|
||||
await fs.mkdirp(path.dirname(opts.outAppPath));
|
||||
await spawn('mv', [tmpApp, opts.outAppPath]);
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
await fs.remove(tmpDir);
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user