Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf1269fe21 | ||
|
|
ca53e14488 | ||
|
|
1b9f5eb340 | ||
|
|
53c72d8c47 | ||
|
|
b61638598d | ||
|
|
0a0b41d115 | ||
|
|
ed0459457f | ||
|
|
0939980564 | ||
|
|
2e087ef6c8 | ||
|
|
355fd7c6e8 | ||
|
|
7a73b7793e | ||
|
|
436b2abeff | ||
|
|
01eec61200 | ||
|
|
9a2c19c940 | ||
|
|
4eb37fab7b | ||
|
|
b8379c01ed | ||
|
|
421713cf80 | ||
|
|
175672e430 | ||
|
|
1695dc9eac |
36
.github/workflows/docs.yml
vendored
Normal file
36
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Publish documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[0-9]+.[0-9]+.[0-9]+*
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: docs-publish
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'yarn'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn --immutable
|
||||||
|
- name: Build API documentation
|
||||||
|
run: yarn build:docs
|
||||||
|
- name: Azure login
|
||||||
|
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.AZURE_OIDC_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ secrets.AZURE_OIDC_TENANT_ID }}
|
||||||
|
subscription-id: ${{ secrets.AZURE_OIDC_SUBSCRIPTION_ID }}
|
||||||
|
- name: Upload to Azure Blob Storage
|
||||||
|
uses: azure/cli@9f7ce6f37c31b777ec6c6b6d1dfe7db79f497956 # v2.2.0
|
||||||
|
with:
|
||||||
|
inlineScript: |
|
||||||
|
az storage blob upload-batch --account-name ${{ secrets.AZURE_ECOSYSTEM_PACKAGES_STORAGE_ACCOUNT_NAME }} -d '$web/${{ github.event.repository.name }}/${{ github.ref_name }}' -s ./docs --overwrite --auth-mode login
|
||||||
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@@ -13,23 +13,25 @@ jobs:
|
|||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: test
|
needs: test
|
||||||
environment: npm
|
environment: npm-trusted-publisher
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write # for CFA and npm provenance
|
id-token: write # for publishing releases
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- name: Install
|
- name: Install
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --immutable
|
||||||
- uses: continuousauth/action@4e8a2573eeb706f6d7300d6a9f3ca6322740b72d # v1.0.5
|
- name: Get GitHub App Token
|
||||||
|
id: secret-service
|
||||||
|
uses: electron/secret-service-action@3476425e8b30555aac15b1b7096938e254b0e155 # v1.0.0
|
||||||
|
- name: Run Semantic Release
|
||||||
|
uses: electron/semantic-trusted-release@5eceb399ac8de8863205cf6e34109bce473ba566 # v1.0.1
|
||||||
with:
|
with:
|
||||||
project-id: ${{ secrets.CFA_PROJECT_ID }}
|
github-token: ${{ fromJSON(steps.secret-service.outputs.secrets).GITHUB_TOKEN }}
|
||||||
secret: ${{ secrets.CFA_SECRET }}
|
|
||||||
npm-token: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|||||||
2
.github/workflows/semantic.yml
vendored
2
.github/workflows/semantic.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: semantic-pull-request
|
- name: semantic-pull-request
|
||||||
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
16
.github/workflows/test.yml
vendored
16
.github/workflows/test.yml
vendored
@@ -18,24 +18,18 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version:
|
node-version:
|
||||||
- '20.5'
|
- 22.12.x
|
||||||
- '18.17'
|
|
||||||
- '16.20'
|
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version: "${{ matrix.node-version }}"
|
node-version: "${{ matrix.node-version }}"
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- name: Install (Node.js v18+)
|
- name: Install
|
||||||
if : ${{ matrix.node-version != '16.20' }}
|
run: yarn install --immutable
|
||||||
run: yarn install --frozen-lockfile
|
|
||||||
- name: Install (Node.js < v18)
|
|
||||||
if : ${{ matrix.node-version == '16.20' }}
|
|
||||||
run: yarn install --frozen-lockfile --ignore-engines
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Lint
|
- name: Lint
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ entry-asar/*.ts
|
|||||||
test/fixtures/apps
|
test/fixtures/apps
|
||||||
coverage
|
coverage
|
||||||
docs
|
docs
|
||||||
.vscode
|
.vscode
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
yarn lint-staged
|
yarn lint-staged
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"plugins": [
|
"plugins": [
|
||||||
"@semantic-release/commit-analyzer",
|
"@semantic-release/commit-analyzer",
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@continuous-auth/semantic-release-npm",
|
"@semantic-release/npm",
|
||||||
"@semantic-release/github"
|
"@semantic-release/github"
|
||||||
],
|
],
|
||||||
"branches": [ "main" ]
|
"branches": [ "main" ]
|
||||||
|
|||||||
942
.yarn/releases/yarn-4.10.3.cjs
vendored
Executable file
942
.yarn/releases/yarn-4.10.3.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
10
.yarnrc.yml
Normal file
10
.yarnrc.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
enableScripts: false
|
||||||
|
|
||||||
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
npmMinimalAgeGate: 10080
|
||||||
|
|
||||||
|
npmPreapprovedPackages:
|
||||||
|
- "@electron/*"
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-4.10.3.cjs
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
[](https://github.com/electron/universal/actions/workflows/test.yml)
|
[](https://github.com/electron/universal/actions/workflows/test.yml)
|
||||||
[](https://npm.im/@electron/universal)
|
[](https://npm.im/@electron/universal)
|
||||||
|
[](https://packages.electronjs.org/universal)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ await makeUniversalApp({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For full API usage, see the [API documentation](https://packages.electronjs.org/universal).
|
||||||
|
|
||||||
## Advanced configuration
|
## Advanced configuration
|
||||||
|
|
||||||
The basic usage patterns will work for most apps out of the box. Additional configuration
|
The basic usage patterns will work for most apps out of the box. Additional configuration
|
||||||
@@ -118,8 +121,4 @@ Note that if you are using `mergeASARs`, you may need to add architecture-specif
|
|||||||
binary resources to the `singleArchFiles` pattern.
|
binary resources to the `singleArchFiles` pattern.
|
||||||
See [Merging ASARs usage](#merging-asar-archives-to-reduce-app-size) for an example.
|
See [Merging ASARs usage](#merging-asar-archives-to-reduce-app-size) for an example.
|
||||||
|
|
||||||
#### How do I build my app for Apple silicon in the first place?
|
|
||||||
|
|
||||||
Check out the [Electron Apple silicon blog post](https://www.electronjs.org/blog/apple-silicon).
|
|
||||||
|
|
||||||
[`minimatch`]: https://github.com/isaacs/minimatch?tab=readme-ov-file#features
|
[`minimatch`]: https://github.com/isaacs/minimatch?tab=readme-ov-file#features
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
||||||
module.exports = {
|
|
||||||
preset: 'ts-jest',
|
|
||||||
testEnvironment: 'node',
|
|
||||||
transform: {
|
|
||||||
'^.+\\.ts?$': [
|
|
||||||
'ts-jest',
|
|
||||||
{
|
|
||||||
tsconfig: 'tsconfig.jest.json',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
testMatch: ['<rootDir>/test/**/*.spec.ts'],
|
|
||||||
globalSetup: './jest.setup.ts',
|
|
||||||
testTimeout: 10000,
|
|
||||||
};
|
|
||||||
39
package.json
39
package.json
@@ -2,8 +2,8 @@
|
|||||||
"name": "@electron/universal",
|
"name": "@electron/universal",
|
||||||
"version": "0.0.0-development",
|
"version": "0.0.0-development",
|
||||||
"description": "Utility for creating Universal macOS applications from two x64 and arm64 Electron applications",
|
"description": "Utility for creating Universal macOS applications from two x64 and arm64 Electron applications",
|
||||||
"main": "dist/cjs/index.js",
|
"type": "module",
|
||||||
"module": "dist/esm/index.js",
|
"exports": "./dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"electron",
|
"electron",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"url": "git+https://github.com/electron/universal.git"
|
"url": "git+https://github.com/electron/universal.git"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.4"
|
"node": ">=22.12.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/*",
|
"dist/*",
|
||||||
@@ -28,38 +28,35 @@
|
|||||||
"provenance": true
|
"provenance": true
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && tsc -p tsconfig.entry-asar.json",
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.entry-asar.json",
|
||||||
"build:docs": "npx typedoc",
|
"build:docs": "npx typedoc",
|
||||||
"lint": "prettier --check \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"",
|
"lint": "prettier --check \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"",
|
||||||
"prettier:write": "prettier --write \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"",
|
"prettier:write": "prettier --write \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"",
|
||||||
"prepublishOnly": "npm run build",
|
"prepack": "npm run build",
|
||||||
"test": "jest",
|
"test": "vitest run",
|
||||||
"prepare": "husky install"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/get": "^3.0.0",
|
"@electron/get": "^4.0.0",
|
||||||
|
"@tsconfig/node22": "^22.0.1",
|
||||||
"@types/cross-zip": "^4.0.1",
|
"@types/cross-zip": "^4.0.1",
|
||||||
"@types/debug": "^4.1.10",
|
"@types/debug": "^4.1.10",
|
||||||
"@types/fs-extra": "^11.0.3",
|
|
||||||
"@types/jest": "^29.5.7",
|
|
||||||
"@types/minimatch": "^5.1.2",
|
"@types/minimatch": "^5.1.2",
|
||||||
"@types/node": "^20.8.10",
|
"@types/node": "~22.10.7",
|
||||||
"@types/plist": "^3.0.4",
|
"@types/plist": "^3.0.4",
|
||||||
"cross-zip": "^4.0.0",
|
"cross-zip": "^4.0.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^9.1.7",
|
||||||
"jest": "^29.7.0",
|
"lint-staged": "^16.1.0",
|
||||||
"lint-staged": "^15.2.10",
|
"prettier": "^3.5.3",
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"ts-jest": "^29.1.1",
|
|
||||||
"typedoc": "~0.25.13",
|
"typedoc": "~0.25.13",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.8.3",
|
||||||
|
"vitest": "^3.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.3.1",
|
"@electron/asar": "^4.0.0",
|
||||||
"@malept/cross-spawn-promise": "^2.0.0",
|
"@malept/cross-spawn-promise": "^2.0.0",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"dir-compare": "^4.2.0",
|
"dir-compare": "^4.2.0",
|
||||||
"fs-extra": "^11.1.1",
|
|
||||||
"minimatch": "^9.0.3",
|
"minimatch": "^9.0.3",
|
||||||
"plist": "^3.1.0"
|
"plist": "^3.1.0"
|
||||||
},
|
},
|
||||||
@@ -68,7 +65,5 @@
|
|||||||
"prettier --write"
|
"prettier --write"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f"
|
||||||
"jackspeak": "2.1.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import asar from '@electron/asar';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { execFileSync } from 'child_process';
|
import crypto from 'node:crypto';
|
||||||
import crypto from 'crypto';
|
import fs from 'node:fs';
|
||||||
import fs from 'fs-extra';
|
import os from 'node:os';
|
||||||
import path from 'path';
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import * as asar from '@electron/asar';
|
||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
import os from 'os';
|
|
||||||
import { d } from './debug';
|
import { d } from './debug.js';
|
||||||
|
|
||||||
const LIPO = 'lipo';
|
const LIPO = 'lipo';
|
||||||
|
|
||||||
@@ -40,7 +42,7 @@ export const detectAsarMode = async (appPath: string) => {
|
|||||||
d('checking asar mode of', appPath);
|
d('checking asar mode of', appPath);
|
||||||
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
|
||||||
|
|
||||||
if (!(await fs.pathExists(asarPath))) {
|
if (!fs.existsSync(asarPath)) {
|
||||||
d('determined no asar');
|
d('determined no asar');
|
||||||
return AsarMode.NO_ASAR;
|
return AsarMode.NO_ASAR;
|
||||||
}
|
}
|
||||||
@@ -169,8 +171,8 @@ export const mergeASARs = async ({
|
|||||||
// Extract both
|
// Extract both
|
||||||
//
|
//
|
||||||
|
|
||||||
const x64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'x64-'));
|
const x64Dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'x64-'));
|
||||||
const arm64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'arm64-'));
|
const arm64Dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'arm64-'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
d(`extracting ${x64AsarPath} to ${x64Dir}`);
|
d(`extracting ${x64AsarPath} to ${x64Dir}`);
|
||||||
@@ -185,18 +187,22 @@ export const mergeASARs = async ({
|
|||||||
|
|
||||||
if (isDirectory(arm64AsarPath, file)) {
|
if (isDirectory(arm64AsarPath, file)) {
|
||||||
d(`creating unique directory: ${file}`);
|
d(`creating unique directory: ${file}`);
|
||||||
await fs.mkdirp(destination);
|
await fs.promises.mkdir(destination, { recursive: true });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
d(`xopying unique file: ${file}`);
|
d(`copying unique file: ${file}`);
|
||||||
await fs.mkdirp(path.dirname(destination));
|
await fs.promises.mkdir(path.dirname(destination), { recursive: true });
|
||||||
await fs.copy(source, destination);
|
await fs.promises.cp(source, destination, {
|
||||||
|
force: true,
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const binding of commonBindings) {
|
for (const binding of commonBindings) {
|
||||||
const source = await fs.realpath(path.resolve(arm64Dir, binding));
|
const source = await fs.promises.realpath(path.resolve(arm64Dir, binding));
|
||||||
const destination = await fs.realpath(path.resolve(x64Dir, binding));
|
const destination = await fs.promises.realpath(path.resolve(x64Dir, binding));
|
||||||
|
|
||||||
d(`merging binding: ${binding}`);
|
d(`merging binding: ${binding}`);
|
||||||
execFileSync(LIPO, [source, destination, '-create', '-output', destination]);
|
execFileSync(LIPO, [source, destination, '-create', '-output', destination]);
|
||||||
@@ -219,7 +225,10 @@ export const mergeASARs = async ({
|
|||||||
|
|
||||||
d('done merging');
|
d('done merging');
|
||||||
} finally {
|
} finally {
|
||||||
await Promise.all([fs.remove(x64Dir), fs.remove(arm64Dir)]);
|
await Promise.all([
|
||||||
|
fs.promises.rm(x64Dir, { recursive: true, force: true }),
|
||||||
|
fs.promises.rm(arm64Dir, { recursive: true, force: true }),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import { spawn, ExitCodeError } from '@malept/cross-spawn-promise';
|
import fs from 'node:fs';
|
||||||
import * as fs from 'fs-extra';
|
import path from 'node:path';
|
||||||
import * as path from 'path';
|
|
||||||
import { promises as stream } from 'node:stream';
|
import { promises as stream } from 'node:stream';
|
||||||
|
|
||||||
|
import { spawn, ExitCodeError } from '@malept/cross-spawn-promise';
|
||||||
|
import { minimatch } from 'minimatch';
|
||||||
|
|
||||||
const MACHO_PREFIX = 'Mach-O ';
|
const MACHO_PREFIX = 'Mach-O ';
|
||||||
|
|
||||||
|
const UNPACKED_ASAR_PATH = path.join('Contents', 'Resources', 'app.asar.unpacked');
|
||||||
|
|
||||||
export enum AppFileType {
|
export enum AppFileType {
|
||||||
MACHO,
|
MACHO,
|
||||||
PLAIN,
|
PLAIN,
|
||||||
INFO_PLIST,
|
INFO_PLIST,
|
||||||
SNAPSHOT,
|
SNAPSHOT,
|
||||||
APP_CODE,
|
APP_CODE,
|
||||||
|
SINGLE_ARCH,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppFile = {
|
export type AppFile = {
|
||||||
@@ -18,22 +23,50 @@ export type AppFile = {
|
|||||||
type: AppFileType;
|
type: AppFileType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetAllAppFilesOpts = {
|
||||||
|
singleArchFiles?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSingleArchFile = (relativePath: string, opts: GetAllAppFilesOpts): boolean => {
|
||||||
|
if (opts.singleArchFiles === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpackedPath = path.relative(UNPACKED_ASAR_PATH, relativePath);
|
||||||
|
|
||||||
|
// Outside of app.asar.unpacked
|
||||||
|
if (unpackedPath.startsWith('..')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return minimatch(unpackedPath, opts.singleArchFiles, {
|
||||||
|
matchBase: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param appPath Path to the application
|
* @param appPath Path to the application
|
||||||
*/
|
*/
|
||||||
export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
|
export const getAllAppFiles = async (
|
||||||
|
appPath: string,
|
||||||
|
opts: GetAllAppFilesOpts,
|
||||||
|
): Promise<AppFile[]> => {
|
||||||
|
const unpackedPath = path.join('Contents', 'Resources', 'app.asar.unpacked');
|
||||||
|
|
||||||
const files: AppFile[] = [];
|
const files: AppFile[] = [];
|
||||||
|
|
||||||
const visited = new Set<string>();
|
const visited = new Set<string>();
|
||||||
const traverse = async (p: string) => {
|
const traverse = async (p: string) => {
|
||||||
p = await fs.realpath(p);
|
p = await fs.promises.realpath(p);
|
||||||
if (visited.has(p)) return;
|
if (visited.has(p)) return;
|
||||||
visited.add(p);
|
visited.add(p);
|
||||||
|
|
||||||
const info = await fs.stat(p);
|
const info = await fs.promises.stat(p);
|
||||||
if (info.isSymbolicLink()) return;
|
if (info.isSymbolicLink()) return;
|
||||||
if (info.isFile()) {
|
if (info.isFile()) {
|
||||||
|
const relativePath = path.relative(appPath, p);
|
||||||
|
|
||||||
let fileType = AppFileType.PLAIN;
|
let fileType = AppFileType.PLAIN;
|
||||||
|
|
||||||
var fileOutput = '';
|
var fileOutput = '';
|
||||||
@@ -48,6 +81,8 @@ export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
|
|||||||
}
|
}
|
||||||
if (p.endsWith('.asar')) {
|
if (p.endsWith('.asar')) {
|
||||||
fileType = AppFileType.APP_CODE;
|
fileType = AppFileType.APP_CODE;
|
||||||
|
} else if (isSingleArchFile(relativePath, opts)) {
|
||||||
|
fileType = AppFileType.SINGLE_ARCH;
|
||||||
} else if (fileOutput.startsWith(MACHO_PREFIX)) {
|
} else if (fileOutput.startsWith(MACHO_PREFIX)) {
|
||||||
fileType = AppFileType.MACHO;
|
fileType = AppFileType.MACHO;
|
||||||
} else if (p.endsWith('.bin')) {
|
} else if (p.endsWith('.bin')) {
|
||||||
@@ -57,13 +92,13 @@ export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
files.push({
|
files.push({
|
||||||
relativePath: path.relative(appPath, p),
|
relativePath,
|
||||||
type: fileType,
|
type: fileType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.isDirectory()) {
|
if (info.isDirectory()) {
|
||||||
for (const child of await fs.readdir(p)) {
|
for (const child of await fs.promises.readdir(p)) {
|
||||||
await traverse(path.resolve(p, child));
|
await traverse(path.resolve(p, child));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,3 +118,21 @@ export const readMachOHeader = async (path: string) => {
|
|||||||
});
|
});
|
||||||
return Buffer.concat(chunks);
|
return Buffer.concat(chunks);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fsMove = async (oldPath: string, newPath: string) => {
|
||||||
|
try {
|
||||||
|
await fs.promises.rename(oldPath, newPath);
|
||||||
|
} catch (err) {
|
||||||
|
if ((err as NodeJS.ErrnoException).code === 'EXDEV') {
|
||||||
|
// Cross-device link, fallback to copy and delete
|
||||||
|
await fs.promises.cp(oldPath, newPath, {
|
||||||
|
force: true,
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
});
|
||||||
|
await fs.promises.rm(oldPath, { force: true, recursive: true });
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
119
src/index.ts
119
src/index.ts
@@ -1,17 +1,18 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
import * as asar from '@electron/asar';
|
import * as asar from '@electron/asar';
|
||||||
import { spawn } from '@malept/cross-spawn-promise';
|
import { spawn } from '@malept/cross-spawn-promise';
|
||||||
import * as dircompare from 'dir-compare';
|
import * as dircompare from 'dir-compare';
|
||||||
import * as fs from 'fs-extra';
|
|
||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
import * as os from 'os';
|
import plist from 'plist';
|
||||||
import * as path from 'path';
|
|
||||||
import * as plist from 'plist';
|
|
||||||
|
|
||||||
import { AsarMode, detectAsarMode, isUniversalMachO, mergeASARs } from './asar-utils';
|
import { AsarMode, detectAsarMode, isUniversalMachO, mergeASARs } from './asar-utils.js';
|
||||||
import { AppFile, AppFileType, getAllAppFiles, readMachOHeader } from './file-utils';
|
import { AppFile, AppFileType, fsMove, getAllAppFiles, readMachOHeader } from './file-utils.js';
|
||||||
import { sha } from './sha';
|
import { sha } from './sha.js';
|
||||||
import { d } from './debug';
|
import { d } from './debug.js';
|
||||||
import { computeIntegrityData } from './integrity';
|
import { computeIntegrityData } from './integrity.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options to pass into the {@link makeUniversalApp} function.
|
* Options to pass into the {@link makeUniversalApp} function.
|
||||||
@@ -74,7 +75,12 @@ export type MakeUniversalOpts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dupedFiles = (files: AppFile[]) =>
|
const dupedFiles = (files: AppFile[]) =>
|
||||||
files.filter((f) => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE);
|
files.filter(
|
||||||
|
(f) =>
|
||||||
|
f.type !== AppFileType.SNAPSHOT &&
|
||||||
|
f.type !== AppFileType.APP_CODE &&
|
||||||
|
f.type !== AppFileType.SINGLE_ARCH,
|
||||||
|
);
|
||||||
|
|
||||||
export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => {
|
export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => {
|
||||||
d('making a universal app with options', opts);
|
d('making a universal app with options', opts);
|
||||||
@@ -88,7 +94,7 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath))
|
if (!opts.outAppPath || !path.isAbsolute(opts.outAppPath))
|
||||||
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
|
||||||
|
|
||||||
if (await fs.pathExists(opts.outAppPath)) {
|
if (fs.existsSync(opts.outAppPath)) {
|
||||||
d('output path exists already');
|
d('output path exists already');
|
||||||
if (!opts.force) {
|
if (!opts.force) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -96,7 +102,7 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
d('overwriting existing application because force == true');
|
d('overwriting existing application because force == true');
|
||||||
await fs.remove(opts.outAppPath);
|
await fs.promises.rm(opts.outAppPath, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +116,7 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
'Both the x64 and arm64 versions of your application need to have been built with the same asar settings (enabled vs disabled)',
|
'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-'));
|
const tmpDir = await fs.promises.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
|
||||||
d('building universal app in', tmpDir);
|
d('building universal app in', tmpDir);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -120,8 +126,8 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
|
|
||||||
const uniqueToX64: string[] = [];
|
const uniqueToX64: string[] = [];
|
||||||
const uniqueToArm64: string[] = [];
|
const uniqueToArm64: string[] = [];
|
||||||
const x64Files = await getAllAppFiles(await fs.realpath(tmpApp));
|
const x64Files = await getAllAppFiles(await fs.promises.realpath(tmpApp), opts);
|
||||||
const arm64Files = await getAllAppFiles(await fs.realpath(opts.arm64AppPath));
|
const arm64Files = await getAllAppFiles(await fs.promises.realpath(opts.arm64AppPath), opts);
|
||||||
|
|
||||||
for (const file of dupedFiles(x64Files)) {
|
for (const file of dupedFiles(x64Files)) {
|
||||||
if (!arm64Files.some((f) => f.relativePath === file.relativePath))
|
if (!arm64Files.some((f) => f.relativePath === file.relativePath))
|
||||||
@@ -142,7 +148,9 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of x64Files.filter((f) => f.type === AppFileType.PLAIN)) {
|
// Single Arch files are copied as is without processing.
|
||||||
|
const multiArchFiles = x64Files.filter((f) => f.type !== AppFileType.SINGLE_ARCH);
|
||||||
|
for (const file of multiArchFiles.filter((f) => f.type === AppFileType.PLAIN)) {
|
||||||
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
|
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
|
||||||
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
|
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
|
||||||
if (x64Sha !== arm64Sha) {
|
if (x64Sha !== arm64Sha) {
|
||||||
@@ -158,9 +166,11 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const knownMergedMachOFiles = new Set();
|
const knownMergedMachOFiles = new Set();
|
||||||
for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) {
|
for (const machOFile of multiArchFiles.filter((f) => f.type === AppFileType.MACHO)) {
|
||||||
const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath));
|
const first = await fs.promises.realpath(path.resolve(tmpApp, machOFile.relativePath));
|
||||||
const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath));
|
const second = await fs.promises.realpath(
|
||||||
|
path.resolve(opts.arm64AppPath, machOFile.relativePath),
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isUniversalMachO(await readMachOHeader(first)) &&
|
isUniversalMachO(await readMachOHeader(first)) &&
|
||||||
@@ -201,7 +211,7 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
second,
|
second,
|
||||||
'-create',
|
'-create',
|
||||||
'-output',
|
'-output',
|
||||||
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
await fs.promises.realpath(path.resolve(tmpApp, machOFile.relativePath)),
|
||||||
]);
|
]);
|
||||||
knownMergedMachOFiles.add(machOFile.relativePath);
|
knownMergedMachOFiles.add(machOFile.relativePath);
|
||||||
}
|
}
|
||||||
@@ -232,26 +242,34 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
|
|
||||||
if (nonMergedDifferences.length > 0) {
|
if (nonMergedDifferences.length > 0) {
|
||||||
d('x64 and arm64 app folders are different, creating dynamic entry ASAR');
|
d('x64 and arm64 app folders are different, creating dynamic entry ASAR');
|
||||||
await fs.move(
|
await fsMove(
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'),
|
||||||
);
|
);
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'),
|
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'),
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'),
|
||||||
|
{ force: true, recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||||
await fs.mkdir(entryAsar);
|
await fs.promises.mkdir(entryAsar, { recursive: true });
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'),
|
path.resolve(import.meta.dirname, '..', 'entry-asar', 'no-asar.js'),
|
||||||
path.resolve(entryAsar, 'index.js'),
|
path.resolve(entryAsar, 'index.js'),
|
||||||
);
|
);
|
||||||
let pj = await fs.readJson(
|
let pj = JSON.parse(
|
||||||
path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'),
|
await fs.promises.readFile(
|
||||||
|
path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'),
|
||||||
|
'utf8',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
pj.main = 'index.js';
|
pj.main = 'index.js';
|
||||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
await fs.promises.writeFile(
|
||||||
|
path.resolve(entryAsar, 'package.json'),
|
||||||
|
JSON.stringify(pj) + '\n',
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
await asar.createPackage(
|
await asar.createPackage(
|
||||||
entryAsar,
|
entryAsar,
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
|
||||||
@@ -288,19 +306,20 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
if (x64AsarSha !== arm64AsarSha) {
|
if (x64AsarSha !== arm64AsarSha) {
|
||||||
d('x64 and arm64 asars are different');
|
d('x64 and arm64 asars are different');
|
||||||
const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar');
|
const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar');
|
||||||
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath);
|
await fsMove(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath);
|
||||||
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
|
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
|
||||||
if (await fs.pathExists(x64Unpacked)) {
|
if (fs.existsSync(x64Unpacked)) {
|
||||||
await fs.move(
|
await fsMove(
|
||||||
x64Unpacked,
|
x64Unpacked,
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const arm64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar');
|
const arm64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar');
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
|
||||||
arm64AsarPath,
|
arm64AsarPath,
|
||||||
|
{ force: true, recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
const arm64Unpacked = path.resolve(
|
const arm64Unpacked = path.resolve(
|
||||||
opts.arm64AppPath,
|
opts.arm64AppPath,
|
||||||
@@ -308,17 +327,18 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
'Resources',
|
'Resources',
|
||||||
'app.asar.unpacked',
|
'app.asar.unpacked',
|
||||||
);
|
);
|
||||||
if (await fs.pathExists(arm64Unpacked)) {
|
if (fs.existsSync(arm64Unpacked)) {
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
arm64Unpacked,
|
arm64Unpacked,
|
||||||
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'),
|
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'),
|
||||||
|
{ force: true, recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
const entryAsar = path.resolve(tmpDir, 'entry-asar');
|
||||||
await fs.mkdir(entryAsar);
|
await fs.promises.mkdir(entryAsar, { recursive: true });
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'),
|
path.resolve(import.meta.dirname, '..', 'entry-asar', 'has-asar.js'),
|
||||||
path.resolve(entryAsar, 'index.js'),
|
path.resolve(entryAsar, 'index.js'),
|
||||||
);
|
);
|
||||||
let pj = JSON.parse(
|
let pj = JSON.parse(
|
||||||
@@ -330,7 +350,11 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
).toString('utf8'),
|
).toString('utf8'),
|
||||||
);
|
);
|
||||||
pj.main = 'index.js';
|
pj.main = 'index.js';
|
||||||
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
|
await fs.promises.writeFile(
|
||||||
|
path.resolve(entryAsar, 'package.json'),
|
||||||
|
JSON.stringify(pj) + '\n',
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
|
||||||
await asar.createPackage(entryAsar, asarPath);
|
await asar.createPackage(entryAsar, asarPath);
|
||||||
} else {
|
} else {
|
||||||
@@ -338,18 +362,18 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatedIntegrity = await computeIntegrityData(path.join(tmpApp, 'Contents'));
|
const generatedIntegrity = await computeIntegrityData(path.join(tmpApp, 'Contents'), opts);
|
||||||
|
|
||||||
const plistFiles = x64Files.filter((f) => f.type === AppFileType.INFO_PLIST);
|
const plistFiles = multiArchFiles.filter((f) => f.type === AppFileType.INFO_PLIST);
|
||||||
for (const plistFile of plistFiles) {
|
for (const plistFile of plistFiles) {
|
||||||
const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath);
|
const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath);
|
||||||
const arm64PlistPath = path.resolve(opts.arm64AppPath, plistFile.relativePath);
|
const arm64PlistPath = path.resolve(opts.arm64AppPath, plistFile.relativePath);
|
||||||
|
|
||||||
const { ElectronAsarIntegrity: x64Integrity, ...x64Plist } = plist.parse(
|
const { ElectronAsarIntegrity: x64Integrity, ...x64Plist } = plist.parse(
|
||||||
await fs.readFile(x64PlistPath, 'utf8'),
|
await fs.promises.readFile(x64PlistPath, 'utf8'),
|
||||||
) as any;
|
) as any;
|
||||||
const { ElectronAsarIntegrity: arm64Integrity, ...arm64Plist } = plist.parse(
|
const { ElectronAsarIntegrity: arm64Integrity, ...arm64Plist } = plist.parse(
|
||||||
await fs.readFile(arm64PlistPath, 'utf8'),
|
await fs.promises.readFile(arm64PlistPath, 'utf8'),
|
||||||
) as any;
|
) as any;
|
||||||
if (JSON.stringify(x64Plist) !== JSON.stringify(arm64Plist)) {
|
if (JSON.stringify(x64Plist) !== JSON.stringify(arm64Plist)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -364,23 +388,26 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
|
|||||||
? { ...x64Plist, ElectronAsarIntegrity: generatedIntegrity }
|
? { ...x64Plist, ElectronAsarIntegrity: generatedIntegrity }
|
||||||
: { ...x64Plist };
|
: { ...x64Plist };
|
||||||
|
|
||||||
await fs.writeFile(path.resolve(tmpApp, plistFile.relativePath), plist.build(mergedPlist));
|
await fs.promises.writeFile(
|
||||||
|
path.resolve(tmpApp, plistFile.relativePath),
|
||||||
|
plist.build(mergedPlist),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
|
for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
|
||||||
d('copying snapshot file', snapshotsFile.relativePath, 'to target application');
|
d('copying snapshot file', snapshotsFile.relativePath, 'to target application');
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(opts.arm64AppPath, snapshotsFile.relativePath),
|
path.resolve(opts.arm64AppPath, snapshotsFile.relativePath),
|
||||||
path.resolve(tmpApp, snapshotsFile.relativePath),
|
path.resolve(tmpApp, snapshotsFile.relativePath),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
d('moving final universal app to target destination');
|
d('moving final universal app to target destination');
|
||||||
await fs.mkdirp(path.dirname(opts.outAppPath));
|
await fs.promises.mkdir(path.dirname(opts.outAppPath), { recursive: true });
|
||||||
await spawn('mv', [tmpApp, opts.outAppPath]);
|
await spawn('mv', [tmpApp, opts.outAppPath]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
await fs.remove(tmpDir);
|
await fs.promises.rm(tmpDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'fs-extra';
|
import fs from 'node:fs';
|
||||||
import path from 'path';
|
import path from 'node:path';
|
||||||
import { AppFileType, getAllAppFiles } from './file-utils';
|
|
||||||
import { sha } from './sha';
|
import { AppFileType, getAllAppFiles } from './file-utils.js';
|
||||||
import { generateAsarIntegrity } from './asar-utils';
|
import { generateAsarIntegrity } from './asar-utils.js';
|
||||||
|
|
||||||
type IntegrityMap = {
|
type IntegrityMap = {
|
||||||
[filepath: string]: string;
|
[filepath: string]: string;
|
||||||
@@ -17,13 +17,20 @@ export interface AsarIntegrity {
|
|||||||
[key: string]: HeaderHash;
|
[key: string]: HeaderHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function computeIntegrityData(contentsPath: string): Promise<AsarIntegrity> {
|
export type ComputeIntegrityDataOpts = {
|
||||||
const root = await fs.realpath(contentsPath);
|
singleArchFiles?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function computeIntegrityData(
|
||||||
|
contentsPath: string,
|
||||||
|
opts: ComputeIntegrityDataOpts,
|
||||||
|
): Promise<AsarIntegrity> {
|
||||||
|
const root = await fs.promises.realpath(contentsPath);
|
||||||
|
|
||||||
const resourcesRelativePath = 'Resources';
|
const resourcesRelativePath = 'Resources';
|
||||||
const resourcesPath = path.resolve(root, resourcesRelativePath);
|
const resourcesPath = path.resolve(root, resourcesRelativePath);
|
||||||
|
|
||||||
const resources = await getAllAppFiles(resourcesPath);
|
const resources = await getAllAppFiles(resourcesPath, opts);
|
||||||
const resourceAsars = resources
|
const resourceAsars = resources
|
||||||
.filter((file) => file.type === AppFileType.APP_CODE)
|
.filter((file) => file.type === AppFileType.APP_CODE)
|
||||||
.reduce<IntegrityMap>(
|
.reduce<IntegrityMap>(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as fs from 'fs-extra';
|
import fs from 'node:fs';
|
||||||
import * as crypto from 'crypto';
|
import crypto from 'node:crypto';
|
||||||
import { pipeline } from 'stream/promises';
|
import { pipeline } from 'node:stream/promises';
|
||||||
|
|
||||||
import { d } from './debug';
|
import { d } from './debug.js';
|
||||||
|
|
||||||
export const sha = async (filePath: string) => {
|
export const sha = async (filePath: string) => {
|
||||||
d('hashing', filePath);
|
d('hashing', filePath);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should correctly merge two identical asars 1`] = `
|
exports[`makeUniversalApp > asar mode > should correctly merge two identical asars 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -29,7 +29,7 @@ exports[`makeUniversalApp asar mode should correctly merge two identical asars 1
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should correctly merge two identical asars 2`] = `
|
exports[`makeUniversalApp > asar mode > should correctly merge two identical asars 2`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
@@ -40,7 +40,7 @@ exports[`makeUniversalApp asar mode should correctly merge two identical asars 2
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 1`] = `
|
exports[`makeUniversalApp > asar mode > should create a shim if asars are different between architectures 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"extra-file.txt": {
|
"extra-file.txt": {
|
||||||
@@ -80,7 +80,7 @@ exports[`makeUniversalApp asar mode should create a shim if asars are different
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 2`] = `
|
exports[`makeUniversalApp > asar mode > should create a shim if asars are different between architectures 2`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -109,7 +109,7 @@ exports[`makeUniversalApp asar mode should create a shim if asars are different
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 3`] = `
|
exports[`makeUniversalApp > asar mode > should create a shim if asars are different between architectures 3`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -138,7 +138,7 @@ exports[`makeUniversalApp asar mode should create a shim if asars are different
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should create a shim if asars are different between architectures 4`] = `
|
exports[`makeUniversalApp > asar mode > should create a shim if asars are different between architectures 4`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app-arm64.asar": {
|
"Resources/app-arm64.asar": {
|
||||||
@@ -157,7 +157,7 @@ exports[`makeUniversalApp asar mode should create a shim if asars are different
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars in the application 1`] = `
|
exports[`makeUniversalApp > asar mode > should generate AsarIntegrity for all asars in the application 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -215,7 +215,7 @@ exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars in the application 2`] = `
|
exports[`makeUniversalApp > asar mode > should generate AsarIntegrity for all asars in the application 2`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -273,7 +273,7 @@ exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars in the application 3`] = `
|
exports[`makeUniversalApp > asar mode > should generate AsarIntegrity for all asars in the application 3`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
@@ -288,7 +288,7 @@ exports[`makeUniversalApp asar mode should generate AsarIntegrity for all asars
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should merge two different asars when \`mergeASARs\` is enabled 1`] = `
|
exports[`makeUniversalApp > asar mode > should merge two different asars when \`mergeASARs\` is enabled 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"extra-file.txt": {
|
"extra-file.txt": {
|
||||||
@@ -328,7 +328,7 @@ exports[`makeUniversalApp asar mode should merge two different asars when \`merg
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should merge two different asars when \`mergeASARs\` is enabled 2`] = `
|
exports[`makeUniversalApp > asar mode > should merge two different asars when \`mergeASARs\` is enabled 2`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
@@ -339,7 +339,87 @@ exports[`makeUniversalApp asar mode should merge two different asars when \`merg
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should not inject ElectronAsarIntegrity into \`infoPlistsToIgnore\` 1`] = `
|
exports[`makeUniversalApp > asar mode > should merge two different asars with native files when \`mergeASARs\` is enabled 1`] = `
|
||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"hello-world-arm64": "<stripped>",
|
||||||
|
"hello-world-x64": "<stripped>",
|
||||||
|
"index.js": {
|
||||||
|
"integrity": {
|
||||||
|
"algorithm": "SHA256",
|
||||||
|
"blockSize": 4194304,
|
||||||
|
"blocks": [
|
||||||
|
"0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
||||||
|
],
|
||||||
|
"hash": "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
||||||
|
},
|
||||||
|
"size": 66,
|
||||||
|
},
|
||||||
|
"package.json": {
|
||||||
|
"integrity": {
|
||||||
|
"algorithm": "SHA256",
|
||||||
|
"blockSize": 4194304,
|
||||||
|
"blocks": [
|
||||||
|
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||||
|
],
|
||||||
|
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
||||||
|
},
|
||||||
|
"size": 41,
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"files": {
|
||||||
|
"var": {
|
||||||
|
"files": {
|
||||||
|
"app": {
|
||||||
|
"files": {
|
||||||
|
"file.txt": {
|
||||||
|
"link": "private/var/file.txt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"file.txt": {
|
||||||
|
"integrity": {
|
||||||
|
"algorithm": "SHA256",
|
||||||
|
"blockSize": 4194304,
|
||||||
|
"blocks": [
|
||||||
|
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
||||||
|
],
|
||||||
|
"hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
||||||
|
},
|
||||||
|
"size": 11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"var": {
|
||||||
|
"link": "private/var",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`makeUniversalApp > asar mode > should merge two different asars with native files when \`mergeASARs\` is enabled 2`] = `[]`;
|
||||||
|
|
||||||
|
exports[`makeUniversalApp > asar mode > should merge two different asars with native files when \`mergeASARs\` is enabled 3`] = `
|
||||||
|
[
|
||||||
|
"hello-world-arm64",
|
||||||
|
"hello-world-x64",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`makeUniversalApp > asar mode > should merge two different asars with native files when \`mergeASARs\` is enabled 4`] = `
|
||||||
|
{
|
||||||
|
"Contents/Info.plist": {
|
||||||
|
"Resources/app.asar": {
|
||||||
|
"algorithm": "SHA256",
|
||||||
|
"hash": "<stripped>",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`makeUniversalApp > asar mode > should not inject ElectronAsarIntegrity into \`infoPlistsToIgnore\` 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -397,190 +477,14 @@ exports[`makeUniversalApp asar mode should not inject ElectronAsarIntegrity into
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should not inject ElectronAsarIntegrity into \`infoPlistsToIgnore\` 2`] = `
|
exports[`makeUniversalApp > asar mode > should not inject ElectronAsarIntegrity into \`infoPlistsToIgnore\` 2`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": undefined,
|
"Contents/Info.plist": undefined,
|
||||||
"Contents/Resources/SubApp-1.app/Contents/Info.plist": undefined,
|
"Contents/Resources/SubApp-1.app/Contents/Info.plist": undefined,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should shim asars with different unpacked dirs 1`] = `
|
exports[`makeUniversalApp > force > packages successfully if \`out\` bundle already exists and \`force\` is \`true\` 1`] = `
|
||||||
{
|
|
||||||
"files": {
|
|
||||||
"index.js": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
|
||||||
],
|
|
||||||
"hash": "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
|
||||||
},
|
|
||||||
"size": 66,
|
|
||||||
},
|
|
||||||
"package.json": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
|
||||||
],
|
|
||||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
|
||||||
},
|
|
||||||
"size": 41,
|
|
||||||
},
|
|
||||||
"private": {
|
|
||||||
"files": {
|
|
||||||
"var": {
|
|
||||||
"files": {
|
|
||||||
"app": {
|
|
||||||
"files": {
|
|
||||||
"file.txt": {
|
|
||||||
"link": "private/var/file.txt",
|
|
||||||
"unpacked": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"file.txt": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
|
||||||
],
|
|
||||||
"hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
|
||||||
},
|
|
||||||
"size": 11,
|
|
||||||
"unpacked": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"var": {
|
|
||||||
"link": "private/var",
|
|
||||||
"unpacked": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should shim asars with different unpacked dirs 2`] = `
|
|
||||||
{
|
|
||||||
"files": {
|
|
||||||
"index.js": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
|
||||||
],
|
|
||||||
"hash": "0f6311dac07f0876c436ce2be042eb88c96e17eaf140b39627cf720dd87ad5b8",
|
|
||||||
},
|
|
||||||
"size": 66,
|
|
||||||
},
|
|
||||||
"package.json": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
|
||||||
],
|
|
||||||
"hash": "d6226276d47adc7aa20e6c46e842e258f5157313074a035051a89612acdd6be3",
|
|
||||||
},
|
|
||||||
"size": 41,
|
|
||||||
},
|
|
||||||
"private": {
|
|
||||||
"files": {
|
|
||||||
"var": {
|
|
||||||
"files": {
|
|
||||||
"app": {
|
|
||||||
"files": {
|
|
||||||
"file.txt": {
|
|
||||||
"link": "private/var/file.txt",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"file.txt": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
|
||||||
],
|
|
||||||
"hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
|
||||||
},
|
|
||||||
"size": 11,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"var": {
|
|
||||||
"link": "private/var",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should shim asars with different unpacked dirs 3`] = `
|
|
||||||
{
|
|
||||||
"files": {
|
|
||||||
"index.js": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"b7e5f58d3c0fddc1a57d1279a7f19a34a01784f4036920d4b60a1e33f6d1635b",
|
|
||||||
],
|
|
||||||
"hash": "b7e5f58d3c0fddc1a57d1279a7f19a34a01784f4036920d4b60a1e33f6d1635b",
|
|
||||||
},
|
|
||||||
"size": 1068,
|
|
||||||
},
|
|
||||||
"package.json": {
|
|
||||||
"integrity": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"blockSize": 4194304,
|
|
||||||
"blocks": [
|
|
||||||
"2873266521e41d58d02e7acfbbbdb046edfa04b6ce262b8987de8e8548671fc7",
|
|
||||||
],
|
|
||||||
"hash": "2873266521e41d58d02e7acfbbbdb046edfa04b6ce262b8987de8e8548671fc7",
|
|
||||||
},
|
|
||||||
"size": 33,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should shim asars with different unpacked dirs 4`] = `
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"content": "hello world",
|
|
||||||
"name": "private/var/file.txt",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`makeUniversalApp asar mode should shim asars with different unpacked dirs 5`] = `
|
|
||||||
{
|
|
||||||
"Contents/Info.plist": {
|
|
||||||
"Resources/app-arm64.asar": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"hash": "d06a628e759f54def7ff8785a077b3a3d756882cb84ee99e9725966226e1f195",
|
|
||||||
},
|
|
||||||
"Resources/app-x64.asar": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"hash": "7e6af4d00f4cc737eff922e2b386128a269f80887b79a011022f1276bdbe7832",
|
|
||||||
},
|
|
||||||
"Resources/app.asar": {
|
|
||||||
"algorithm": "SHA256",
|
|
||||||
"hash": "b62aaaed07ff72dc33da1720d900e0443c060285ef374ce1bdaef1d4f28b5fe4",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`makeUniversalApp force packages successfully if \`out\` bundle already exists and \`force\` is \`true\` 1`] = `
|
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -609,7 +513,7 @@ exports[`makeUniversalApp force packages successfully if \`out\` bundle already
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp force packages successfully if \`out\` bundle already exists and \`force\` is \`true\` 2`] = `
|
exports[`makeUniversalApp > force > packages successfully if \`out\` bundle already exists and \`force\` is \`true\` 2`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
@@ -620,7 +524,7 @@ exports[`makeUniversalApp force packages successfully if \`out\` bundle already
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with different macho files (shim and lipo) 1`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with different macho files (shim and lipo) 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -649,13 +553,13 @@ exports[`makeUniversalApp no asar mode different app dirs with different macho f
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with different macho files (shim and lipo) 2`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with different macho files (shim and lipo) 2`] = `
|
||||||
[
|
[
|
||||||
"private/var/i-aint-got-no-rhythm.bin",
|
"private/var/i-aint-got-no-rhythm.bin",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with different macho files (shim and lipo) 3`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with different macho files (shim and lipo) 3`] = `
|
||||||
[
|
[
|
||||||
"hello-world",
|
"hello-world",
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -674,7 +578,7 @@ exports[`makeUniversalApp no asar mode different app dirs with different macho f
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with different macho files (shim and lipo) 4`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with different macho files (shim and lipo) 4`] = `
|
||||||
[
|
[
|
||||||
"hello-world",
|
"hello-world",
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -693,7 +597,7 @@ exports[`makeUniversalApp no asar mode different app dirs with different macho f
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with different macho files (shim and lipo) 5`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with different macho files (shim and lipo) 5`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
@@ -704,7 +608,7 @@ exports[`makeUniversalApp no asar mode different app dirs with different macho f
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with universal macho files (shim but don't lipo) 1`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with universal macho files (shim but don't lipo) 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -733,13 +637,13 @@ exports[`makeUniversalApp no asar mode different app dirs with universal macho f
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with universal macho files (shim but don't lipo) 2`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with universal macho files (shim but don't lipo) 2`] = `
|
||||||
[
|
[
|
||||||
"private/var/i-aint-got-no-rhythm.bin",
|
"private/var/i-aint-got-no-rhythm.bin",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with universal macho files (shim but don't lipo) 3`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with universal macho files (shim but don't lipo) 3`] = `
|
||||||
[
|
[
|
||||||
"hello-world",
|
"hello-world",
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -758,7 +662,7 @@ exports[`makeUniversalApp no asar mode different app dirs with universal macho f
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with universal macho files (shim but don't lipo) 4`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with universal macho files (shim but don't lipo) 4`] = `
|
||||||
[
|
[
|
||||||
"hello-world",
|
"hello-world",
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -777,7 +681,7 @@ exports[`makeUniversalApp no asar mode different app dirs with universal macho f
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode different app dirs with universal macho files (shim but don't lipo) 5`] = `
|
exports[`makeUniversalApp > no asar mode > different app dirs with universal macho files (shim but don't lipo) 5`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
@@ -788,7 +692,7 @@ exports[`makeUniversalApp no asar mode different app dirs with universal macho f
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode identical app dirs with different macho files (e.g. do not shim, but still lipo) 1`] = `
|
exports[`makeUniversalApp > no asar mode > identical app dirs with different macho files (e.g. do not shim, but still lipo) 1`] = `
|
||||||
[
|
[
|
||||||
"hello-world",
|
"hello-world",
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -806,13 +710,13 @@ exports[`makeUniversalApp no asar mode identical app dirs with different macho f
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode identical app dirs with different macho files (e.g. do not shim, but still lipo) 2`] = `
|
exports[`makeUniversalApp > no asar mode > identical app dirs with different macho files (e.g. do not shim, but still lipo) 2`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {},
|
"Contents/Info.plist": {},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode identical app dirs with universal macho files (e.g., do not shim, just copy x64 dir) 1`] = `
|
exports[`makeUniversalApp > no asar mode > identical app dirs with universal macho files (e.g., do not shim, just copy x64 dir) 1`] = `
|
||||||
[
|
[
|
||||||
"hello-world",
|
"hello-world",
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -830,13 +734,13 @@ exports[`makeUniversalApp no asar mode identical app dirs with universal macho f
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode identical app dirs with universal macho files (e.g., do not shim, just copy x64 dir) 2`] = `
|
exports[`makeUniversalApp > no asar mode > identical app dirs with universal macho files (e.g., do not shim, just copy x64 dir) 2`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {},
|
"Contents/Info.plist": {},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode should correctly merge two identical app folders 1`] = `
|
exports[`makeUniversalApp > no asar mode > should correctly merge two identical app folders 1`] = `
|
||||||
[
|
[
|
||||||
"index.js",
|
"index.js",
|
||||||
{
|
{
|
||||||
@@ -849,13 +753,13 @@ exports[`makeUniversalApp no asar mode should correctly merge two identical app
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode should correctly merge two identical app folders 2`] = `
|
exports[`makeUniversalApp > no asar mode > should correctly merge two identical app folders 2`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {},
|
"Contents/Info.plist": {},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode should shim two different app folders 1`] = `
|
exports[`makeUniversalApp > no asar mode > should shim two different app folders 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"index.js": {
|
"index.js": {
|
||||||
@@ -884,13 +788,13 @@ exports[`makeUniversalApp no asar mode should shim two different app folders 1`]
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode should shim two different app folders 2`] = `
|
exports[`makeUniversalApp > no asar mode > should shim two different app folders 2`] = `
|
||||||
[
|
[
|
||||||
"private/var/i-aint-got-no-rhythm.bin",
|
"private/var/i-aint-got-no-rhythm.bin",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode should shim two different app folders 3`] = `
|
exports[`makeUniversalApp > no asar mode > should shim two different app folders 3`] = `
|
||||||
[
|
[
|
||||||
"index.js",
|
"index.js",
|
||||||
{
|
{
|
||||||
@@ -908,7 +812,7 @@ exports[`makeUniversalApp no asar mode should shim two different app folders 3`]
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode should shim two different app folders 4`] = `
|
exports[`makeUniversalApp > no asar mode > should shim two different app folders 4`] = `
|
||||||
[
|
[
|
||||||
"index.js",
|
"index.js",
|
||||||
{
|
{
|
||||||
@@ -926,7 +830,7 @@ exports[`makeUniversalApp no asar mode should shim two different app folders 4`]
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp no asar mode should shim two different app folders 5`] = `
|
exports[`makeUniversalApp > no asar mode > should shim two different app folders 5`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
@@ -937,7 +841,7 @@ exports[`makeUniversalApp no asar mode should shim two different app folders 5`]
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp works for lipo binary resources 1`] = `
|
exports[`makeUniversalApp > works for lipo binary resources 1`] = `
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"hello-world": "<stripped>",
|
"hello-world": "<stripped>",
|
||||||
@@ -996,15 +900,15 @@ exports[`makeUniversalApp works for lipo binary resources 1`] = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp works for lipo binary resources 2`] = `[]`;
|
exports[`makeUniversalApp > works for lipo binary resources 2`] = `[]`;
|
||||||
|
|
||||||
exports[`makeUniversalApp works for lipo binary resources 3`] = `
|
exports[`makeUniversalApp > works for lipo binary resources 3`] = `
|
||||||
[
|
[
|
||||||
"hello-world",
|
"hello-world",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`makeUniversalApp works for lipo binary resources 4`] = `
|
exports[`makeUniversalApp > works for lipo binary resources 4`] = `
|
||||||
{
|
{
|
||||||
"Contents/Info.plist": {
|
"Contents/Info.plist": {
|
||||||
"Resources/app.asar": {
|
"Resources/app.asar": {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'node:path';
|
||||||
|
|
||||||
import { AsarMode, detectAsarMode, generateAsarIntegrity } from '../src/asar-utils';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { describe, expect, it } from '@jest/globals';
|
|
||||||
|
|
||||||
const asarsPath = path.resolve(__dirname, 'fixtures', 'asars');
|
import { AsarMode, detectAsarMode, generateAsarIntegrity } from '../src/asar-utils.js';
|
||||||
const appsPath = path.resolve(__dirname, 'fixtures', 'apps');
|
|
||||||
|
const asarsPath = path.resolve(import.meta.dirname, 'fixtures', 'asars');
|
||||||
|
const appsPath = path.resolve(import.meta.dirname, 'fixtures', 'apps');
|
||||||
|
|
||||||
describe('asar-utils', () => {
|
describe('asar-utils', () => {
|
||||||
describe('detectAsarMode', () => {
|
describe('detectAsarMode', () => {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'node:path';
|
||||||
|
|
||||||
import { AppFile, AppFileType, getAllAppFiles } from '../src/file-utils';
|
import { beforeAll, describe, expect, it } from 'vitest';
|
||||||
import { beforeAll, describe, expect, it } from '@jest/globals';
|
|
||||||
|
|
||||||
const appsPath = path.resolve(__dirname, 'fixtures', 'apps');
|
import { AppFile, AppFileType, getAllAppFiles } from '../src/file-utils.js';
|
||||||
|
|
||||||
|
const appsPath = path.resolve(import.meta.dirname, 'fixtures', 'apps');
|
||||||
|
|
||||||
describe('file-utils', () => {
|
describe('file-utils', () => {
|
||||||
describe('getAllAppFiles', () => {
|
describe('getAllAppFiles', () => {
|
||||||
@@ -11,8 +12,8 @@ describe('file-utils', () => {
|
|||||||
let noAsarFiles: AppFile[];
|
let noAsarFiles: AppFile[];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
asarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64Asar.app'));
|
asarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64Asar.app'), {});
|
||||||
noAsarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64NoAsar.app'));
|
noAsarFiles = await getAllAppFiles(path.resolve(appsPath, 'Arm64NoAsar.app'), {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly identify plist files', async () => {
|
it('should correctly identify plist files', async () => {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { execFileSync } from 'child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import * as fs from 'fs-extra';
|
import fs from 'node:fs';
|
||||||
import * as path from 'path';
|
import path from 'node:path';
|
||||||
import { appsDir, asarsDir, fixtureDir, templateApp } from './test/util';
|
|
||||||
|
import { appsDir, asarsDir, fixtureDir, templateApp } from './util.js';
|
||||||
|
|
||||||
// generates binaries from hello-world.c
|
// generates binaries from hello-world.c
|
||||||
// hello-world-universal, hello-world-x86_64, hello-world-arm64
|
// hello-world-universal, hello-world-x86_64, hello-world-arm64
|
||||||
@@ -23,53 +24,59 @@ const generateMachO = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
await fs.remove(appsDir);
|
await fs.promises.rm(appsDir, { recursive: true, force: true });
|
||||||
await fs.mkdirp(appsDir);
|
await fs.promises.mkdir(appsDir, { recursive: true });
|
||||||
|
|
||||||
// generate mach-o binaries to be leveraged in lipo tests
|
// generate mach-o binaries to be leveraged in lipo tests
|
||||||
generateMachO();
|
generateMachO();
|
||||||
|
|
||||||
await templateApp('Arm64Asar.app', 'arm64', async (appPath) => {
|
await templateApp('Arm64Asar.app', 'arm64', async (appPath) => {
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(asarsDir, 'app.asar'),
|
path.resolve(asarsDir, 'app.asar'),
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
||||||
|
{ recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// contains `extra-file.txt`
|
// contains `extra-file.txt`
|
||||||
await templateApp('Arm64AsarExtraFile.app', 'arm64', async (appPath) => {
|
await templateApp('Arm64AsarExtraFile.app', 'arm64', async (appPath) => {
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(asarsDir, 'app2.asar'),
|
path.resolve(asarsDir, 'app2.asar'),
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
||||||
|
{ recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await templateApp('X64Asar.app', 'x64', async (appPath) => {
|
await templateApp('X64Asar.app', 'x64', async (appPath) => {
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(asarsDir, 'app.asar'),
|
path.resolve(asarsDir, 'app.asar'),
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
||||||
|
{ recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await templateApp('Arm64NoAsar.app', 'arm64', async (appPath) => {
|
await templateApp('Arm64NoAsar.app', 'arm64', async (appPath) => {
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(asarsDir, 'app'),
|
path.resolve(asarsDir, 'app'),
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app'),
|
||||||
|
{ recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// contains `extra-file.txt`
|
// contains `extra-file.txt`
|
||||||
await templateApp('Arm64NoAsarExtraFile.app', 'arm64', async (appPath) => {
|
await templateApp('Arm64NoAsarExtraFile.app', 'arm64', async (appPath) => {
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(asarsDir, 'app2'),
|
path.resolve(asarsDir, 'app2'),
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app'),
|
||||||
|
{ recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await templateApp('X64NoAsar.app', 'x64', async (appPath) => {
|
await templateApp('X64NoAsar.app', 'x64', async (appPath) => {
|
||||||
await fs.copy(
|
await fs.promises.cp(
|
||||||
path.resolve(asarsDir, 'app'),
|
path.resolve(asarsDir, 'app'),
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app'),
|
||||||
|
{ recursive: true, verbatimSymlinks: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1,24 +1,27 @@
|
|||||||
import * as fs from 'fs-extra';
|
import fs from 'node:fs';
|
||||||
import * as path from 'path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { makeUniversalApp } from '../dist/cjs/index';
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { makeUniversalApp } from '../dist/index.js';
|
||||||
|
import { fsMove } from '../src/file-utils.js';
|
||||||
import {
|
import {
|
||||||
createStagingAppDir,
|
createStagingAppDir,
|
||||||
generateNativeApp,
|
generateNativeApp,
|
||||||
templateApp,
|
templateApp,
|
||||||
VERIFY_APP_TIMEOUT,
|
VERIFY_APP_TIMEOUT,
|
||||||
verifyApp,
|
verifyApp,
|
||||||
} from './util';
|
} from './util.js';
|
||||||
import { createPackage, createPackageWithOptions } from '@electron/asar';
|
import { createPackage, createPackageWithOptions } from '@electron/asar';
|
||||||
import { afterEach, describe, expect, it } from '@jest/globals';
|
|
||||||
|
|
||||||
const appsPath = path.resolve(__dirname, 'fixtures', 'apps');
|
const appsPath = path.resolve(import.meta.dirname, 'fixtures', 'apps');
|
||||||
const appsOutPath = path.resolve(__dirname, 'fixtures', 'apps', 'out');
|
const appsOutPath = path.resolve(import.meta.dirname, 'fixtures', 'apps', 'out');
|
||||||
|
|
||||||
// See `jest.setup.ts` for app fixture setup process
|
// See `globalSetup.ts` for app fixture setup process
|
||||||
describe('makeUniversalApp', () => {
|
describe('makeUniversalApp', () => {
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await fs.emptyDir(appsOutPath);
|
await fs.promises.rm(appsOutPath, { force: true, recursive: true });
|
||||||
|
await fs.promises.mkdir(appsOutPath, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error if asar is only detected in one arch', async () => {
|
it('throws an error if asar is only detected in one arch', async () => {
|
||||||
@@ -34,31 +37,27 @@ describe('makeUniversalApp', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(
|
it('works for lipo binary resources', { timeout: VERIFY_APP_TIMEOUT }, async () => {
|
||||||
'works for lipo binary resources',
|
const x64AppPath = await generateNativeApp({
|
||||||
async () => {
|
appNameWithExtension: 'LipoX64.app',
|
||||||
const x64AppPath = await generateNativeApp({
|
arch: 'x64',
|
||||||
appNameWithExtension: 'LipoX64.app',
|
createAsar: true,
|
||||||
arch: 'x64',
|
});
|
||||||
createAsar: true,
|
const arm64AppPath = await generateNativeApp({
|
||||||
});
|
appNameWithExtension: 'LipoArm64.app',
|
||||||
const arm64AppPath = await generateNativeApp({
|
arch: 'arm64',
|
||||||
appNameWithExtension: 'LipoArm64.app',
|
createAsar: true,
|
||||||
arch: 'arm64',
|
});
|
||||||
createAsar: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const out = path.resolve(appsOutPath, 'Lipo.app');
|
const out = path.resolve(appsOutPath, 'Lipo.app');
|
||||||
await makeUniversalApp({ x64AppPath, arm64AppPath, outAppPath: out, mergeASARs: true });
|
await makeUniversalApp({ x64AppPath, arm64AppPath, outAppPath: out, mergeASARs: true });
|
||||||
await verifyApp(out, true);
|
await verifyApp(out, true);
|
||||||
},
|
});
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('force', () => {
|
describe('force', () => {
|
||||||
it('throws an error if `out` bundle already exists and `force` is `false`', async () => {
|
it('throws an error if `out` bundle already exists and `force` is `false`', async () => {
|
||||||
const out = path.resolve(appsOutPath, 'Error.app');
|
const out = path.resolve(appsOutPath, 'Error.app');
|
||||||
await fs.mkdirp(out);
|
await fs.promises.mkdir(out, { recursive: true });
|
||||||
await expect(
|
await expect(
|
||||||
makeUniversalApp({
|
makeUniversalApp({
|
||||||
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
||||||
@@ -70,9 +69,10 @@ describe('makeUniversalApp', () => {
|
|||||||
|
|
||||||
it(
|
it(
|
||||||
'packages successfully if `out` bundle already exists and `force` is `true`',
|
'packages successfully if `out` bundle already exists and `force` is `true`',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const out = path.resolve(appsOutPath, 'NoError.app');
|
const out = path.resolve(appsOutPath, 'NoError.app');
|
||||||
await fs.mkdirp(out);
|
await fs.promises.mkdir(out, { recursive: true });
|
||||||
await makeUniversalApp({
|
await makeUniversalApp({
|
||||||
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
||||||
arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'),
|
arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'),
|
||||||
@@ -81,27 +81,23 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(out);
|
await verifyApp(out);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('asar mode', () => {
|
describe('asar mode', () => {
|
||||||
it(
|
it('should correctly merge two identical asars', { timeout: VERIFY_APP_TIMEOUT }, async () => {
|
||||||
'should correctly merge two identical asars',
|
const out = path.resolve(appsOutPath, 'MergedAsar.app');
|
||||||
async () => {
|
await makeUniversalApp({
|
||||||
const out = path.resolve(appsOutPath, 'MergedAsar.app');
|
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
||||||
await makeUniversalApp({
|
arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'),
|
||||||
x64AppPath: path.resolve(appsPath, 'X64Asar.app'),
|
outAppPath: out,
|
||||||
arm64AppPath: path.resolve(appsPath, 'Arm64Asar.app'),
|
});
|
||||||
outAppPath: out,
|
await verifyApp(out);
|
||||||
});
|
});
|
||||||
await verifyApp(out);
|
|
||||||
},
|
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'should create a shim if asars are different between architectures',
|
'should create a shim if asars are different between architectures',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const out = path.resolve(appsOutPath, 'ShimmedAsar.app');
|
const out = path.resolve(appsOutPath, 'ShimmedAsar.app');
|
||||||
await makeUniversalApp({
|
await makeUniversalApp({
|
||||||
@@ -111,11 +107,11 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(out);
|
await verifyApp(out);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'should merge two different asars when `mergeASARs` is enabled',
|
'should merge two different asars when `mergeASARs` is enabled',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const out = path.resolve(appsOutPath, 'MergedAsar.app');
|
const out = path.resolve(appsOutPath, 'MergedAsar.app');
|
||||||
await makeUniversalApp({
|
await makeUniversalApp({
|
||||||
@@ -127,11 +123,11 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(out);
|
await verifyApp(out);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'throws an error if `mergeASARs` is enabled and `singleArchFiles` is missing a unique file',
|
'throws an error if `mergeASARs` is enabled and `singleArchFiles` is missing a unique file',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const out = path.resolve(appsOutPath, 'Error.app');
|
const out = path.resolve(appsOutPath, 'Error.app');
|
||||||
await expect(
|
await expect(
|
||||||
@@ -144,17 +140,76 @@ describe('makeUniversalApp', () => {
|
|||||||
}),
|
}),
|
||||||
).rejects.toThrow(/Detected unique file "extra-file\.txt"/);
|
).rejects.toThrow(/Detected unique file "extra-file\.txt"/);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
'should merge two different asars with native files when `mergeASARs` is enabled',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
|
async () => {
|
||||||
|
const x64AppPath = await generateNativeApp({
|
||||||
|
appNameWithExtension: 'SingleArchFiles-x64.app',
|
||||||
|
arch: 'x64',
|
||||||
|
createAsar: true,
|
||||||
|
singleArchBindings: true,
|
||||||
|
});
|
||||||
|
const arm64AppPath = await generateNativeApp({
|
||||||
|
appNameWithExtension: 'SingleArchFiles-arm64.app',
|
||||||
|
arch: 'arm64',
|
||||||
|
createAsar: true,
|
||||||
|
singleArchBindings: true,
|
||||||
|
});
|
||||||
|
const out = path.resolve(appsOutPath, 'SingleArchFiles.app');
|
||||||
|
await makeUniversalApp({
|
||||||
|
x64AppPath,
|
||||||
|
arm64AppPath,
|
||||||
|
outAppPath: out,
|
||||||
|
mergeASARs: true,
|
||||||
|
singleArchFiles: 'hello-world-*',
|
||||||
|
});
|
||||||
|
await verifyApp(out, true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
'throws an error if `mergeASARs` is enabled and `singleArchFiles` is missing a unique native file',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
|
async () => {
|
||||||
|
const x64AppPath = await generateNativeApp({
|
||||||
|
appNameWithExtension: 'SingleArchFiles-2-x64.app',
|
||||||
|
arch: 'x64',
|
||||||
|
createAsar: true,
|
||||||
|
singleArchBindings: true,
|
||||||
|
});
|
||||||
|
const arm64AppPath = await generateNativeApp({
|
||||||
|
appNameWithExtension: 'SingleArchFiles-2-arm64.app',
|
||||||
|
arch: 'arm64',
|
||||||
|
createAsar: true,
|
||||||
|
singleArchBindings: true,
|
||||||
|
});
|
||||||
|
const out = path.resolve(appsOutPath, 'SingleArchFiles-2.app');
|
||||||
|
await expect(
|
||||||
|
makeUniversalApp({
|
||||||
|
x64AppPath,
|
||||||
|
arm64AppPath,
|
||||||
|
outAppPath: out,
|
||||||
|
mergeASARs: true,
|
||||||
|
singleArchFiles: 'bad-rule',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(
|
||||||
|
/the number of mach-o files is not the same between the arm64 and x64 builds/,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'should not inject ElectronAsarIntegrity into `infoPlistsToIgnore`',
|
'should not inject ElectronAsarIntegrity into `infoPlistsToIgnore`',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const arm64AppPath = await templateApp('Arm64-1.app', 'arm64', async (appPath) => {
|
const arm64AppPath = await templateApp('Arm64-1.app', 'arm64', async (appPath) => {
|
||||||
const { testPath } = await createStagingAppDir('Arm64-1');
|
const { testPath } = await createStagingAppDir('Arm64-1');
|
||||||
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
||||||
await templateApp('SubApp-1.app', 'arm64', async (subArm64AppPath) => {
|
await templateApp('SubApp-1.app', 'arm64', async (subArm64AppPath) => {
|
||||||
await fs.move(
|
await fsMove(
|
||||||
subArm64AppPath,
|
subArm64AppPath,
|
||||||
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
||||||
);
|
);
|
||||||
@@ -164,7 +219,7 @@ describe('makeUniversalApp', () => {
|
|||||||
const { testPath } = await createStagingAppDir('X64-1');
|
const { testPath } = await createStagingAppDir('X64-1');
|
||||||
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
await createPackage(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app.asar'));
|
||||||
await templateApp('SubApp-1.app', 'x64', async (subArm64AppPath) => {
|
await templateApp('SubApp-1.app', 'x64', async (subArm64AppPath) => {
|
||||||
await fs.move(
|
await fsMove(
|
||||||
subArm64AppPath,
|
subArm64AppPath,
|
||||||
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
path.resolve(appPath, 'Contents', 'Resources', path.basename(subArm64AppPath)),
|
||||||
);
|
);
|
||||||
@@ -180,7 +235,6 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(outAppPath);
|
await verifyApp(outAppPath);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Investigate if this should even be allowed.
|
// TODO: Investigate if this should even be allowed.
|
||||||
@@ -188,6 +242,7 @@ describe('makeUniversalApp', () => {
|
|||||||
// https://github.com/electron/universal/blob/d90d573ccf69a5b14b91aa818c8b97e0e6840399/src/file-utils.ts#L48-L49
|
// https://github.com/electron/universal/blob/d90d573ccf69a5b14b91aa818c8b97e0e6840399/src/file-utils.ts#L48-L49
|
||||||
it.skip(
|
it.skip(
|
||||||
'should shim asars with different unpacked dirs',
|
'should shim asars with different unpacked dirs',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const arm64AppPath = await templateApp('UnpackedArm64.app', 'arm64', async (appPath) => {
|
const arm64AppPath = await templateApp('UnpackedArm64.app', 'arm64', async (appPath) => {
|
||||||
const { testPath } = await createStagingAppDir('UnpackedAppArm64');
|
const { testPath } = await createStagingAppDir('UnpackedAppArm64');
|
||||||
@@ -218,32 +273,32 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(outAppPath);
|
await verifyApp(outAppPath);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'should generate AsarIntegrity for all asars in the application',
|
'should generate AsarIntegrity for all asars in the application',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const { testPath } = await createStagingAppDir('app-2');
|
const { testPath } = await createStagingAppDir('app-2');
|
||||||
const testAsarPath = path.resolve(appsOutPath, 'app-2.asar');
|
const testAsarPath = path.resolve(appsOutPath, 'app-2.asar');
|
||||||
await createPackage(testPath, testAsarPath);
|
await createPackage(testPath, testAsarPath);
|
||||||
|
|
||||||
const arm64AppPath = await templateApp('Arm64-2.app', 'arm64', async (appPath) => {
|
const arm64AppPath = await templateApp('Arm64-2.app', 'arm64', async (appPath) => {
|
||||||
await fs.copyFile(
|
await fs.promises.copyFile(
|
||||||
testAsarPath,
|
testAsarPath,
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
||||||
);
|
);
|
||||||
await fs.copyFile(
|
await fs.promises.copyFile(
|
||||||
testAsarPath,
|
testAsarPath,
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'webapp.asar'),
|
path.resolve(appPath, 'Contents', 'Resources', 'webapp.asar'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const x64AppPath = await templateApp('X64-2.app', 'x64', async (appPath) => {
|
const x64AppPath = await templateApp('X64-2.app', 'x64', async (appPath) => {
|
||||||
await fs.copyFile(
|
await fs.promises.copyFile(
|
||||||
testAsarPath,
|
testAsarPath,
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
path.resolve(appPath, 'Contents', 'Resources', 'app.asar'),
|
||||||
);
|
);
|
||||||
await fs.copyFile(
|
await fs.promises.copyFile(
|
||||||
testAsarPath,
|
testAsarPath,
|
||||||
path.resolve(appPath, 'Contents', 'Resources', 'webbapp.asar'),
|
path.resolve(appPath, 'Contents', 'Resources', 'webbapp.asar'),
|
||||||
);
|
);
|
||||||
@@ -257,13 +312,13 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(outAppPath);
|
await verifyApp(outAppPath);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('no asar mode', () => {
|
describe('no asar mode', () => {
|
||||||
it(
|
it(
|
||||||
'should correctly merge two identical app folders',
|
'should correctly merge two identical app folders',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const out = path.resolve(appsOutPath, 'MergedNoAsar.app');
|
const out = path.resolve(appsOutPath, 'MergedNoAsar.app');
|
||||||
await makeUniversalApp({
|
await makeUniversalApp({
|
||||||
@@ -273,39 +328,41 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(out);
|
await verifyApp(out);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it('should shim two different app folders', { timeout: VERIFY_APP_TIMEOUT }, async () => {
|
||||||
'should shim two different app folders',
|
const arm64AppPath = await templateApp('ShimArm64.app', 'arm64', async (appPath) => {
|
||||||
async () => {
|
const { testPath } = await createStagingAppDir('shimArm64', {
|
||||||
const arm64AppPath = await templateApp('ShimArm64.app', 'arm64', async (appPath) => {
|
'i-aint-got-no-rhythm.bin': 'boomshakalaka',
|
||||||
const { testPath } = await createStagingAppDir('shimArm64', {
|
|
||||||
'i-aint-got-no-rhythm.bin': 'boomshakalaka',
|
|
||||||
});
|
|
||||||
await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app'));
|
|
||||||
});
|
});
|
||||||
|
await fs.promises.cp(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app'), {
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const x64AppPath = await templateApp('ShimX64.app', 'x64', async (appPath) => {
|
const x64AppPath = await templateApp('ShimX64.app', 'x64', async (appPath) => {
|
||||||
const { testPath } = await createStagingAppDir('shimX64', {
|
const { testPath } = await createStagingAppDir('shimX64', {
|
||||||
'hello-world.bin': 'Hello World',
|
'hello-world.bin': 'Hello World',
|
||||||
});
|
|
||||||
await fs.copy(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app'));
|
|
||||||
});
|
});
|
||||||
|
await fs.promises.cp(testPath, path.resolve(appPath, 'Contents', 'Resources', 'app'), {
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const outAppPath = path.resolve(appsOutPath, 'ShimNoAsar.app');
|
const outAppPath = path.resolve(appsOutPath, 'ShimNoAsar.app');
|
||||||
await makeUniversalApp({
|
await makeUniversalApp({
|
||||||
x64AppPath,
|
x64AppPath,
|
||||||
arm64AppPath,
|
arm64AppPath,
|
||||||
outAppPath,
|
outAppPath,
|
||||||
});
|
});
|
||||||
await verifyApp(outAppPath);
|
await verifyApp(outAppPath);
|
||||||
},
|
});
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'different app dirs with different macho files (shim and lipo)',
|
'different app dirs with different macho files (shim and lipo)',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const x64AppPath = await generateNativeApp({
|
const x64AppPath = await generateNativeApp({
|
||||||
appNameWithExtension: 'DifferentMachoAppX64-1.app',
|
appNameWithExtension: 'DifferentMachoAppX64-1.app',
|
||||||
@@ -332,11 +389,11 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(outAppPath, true);
|
await verifyApp(outAppPath, true);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
"different app dirs with universal macho files (shim but don't lipo)",
|
"different app dirs with universal macho files (shim but don't lipo)",
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const x64AppPath = await generateNativeApp({
|
const x64AppPath = await generateNativeApp({
|
||||||
appNameWithExtension: 'DifferentButUniversalMachoAppX64-2.app',
|
appNameWithExtension: 'DifferentButUniversalMachoAppX64-2.app',
|
||||||
@@ -365,11 +422,11 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(outAppPath, true);
|
await verifyApp(outAppPath, true);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'identical app dirs with different macho files (e.g. do not shim, but still lipo)',
|
'identical app dirs with different macho files (e.g. do not shim, but still lipo)',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const x64AppPath = await generateNativeApp({
|
const x64AppPath = await generateNativeApp({
|
||||||
appNameWithExtension: 'DifferentMachoAppX64-2.app',
|
appNameWithExtension: 'DifferentMachoAppX64-2.app',
|
||||||
@@ -390,11 +447,11 @@ describe('makeUniversalApp', () => {
|
|||||||
});
|
});
|
||||||
await verifyApp(out, true);
|
await verifyApp(out, true);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'identical app dirs with universal macho files (e.g., do not shim, just copy x64 dir)',
|
'identical app dirs with universal macho files (e.g., do not shim, just copy x64 dir)',
|
||||||
|
{ timeout: VERIFY_APP_TIMEOUT },
|
||||||
async () => {
|
async () => {
|
||||||
const x64AppPath = await generateNativeApp({
|
const x64AppPath = await generateNativeApp({
|
||||||
appNameWithExtension: 'UniversalMachoAppX64.app',
|
appNameWithExtension: 'UniversalMachoAppX64.app',
|
||||||
@@ -413,7 +470,6 @@ describe('makeUniversalApp', () => {
|
|||||||
await makeUniversalApp({ x64AppPath, arm64AppPath, outAppPath: out });
|
await makeUniversalApp({ x64AppPath, arm64AppPath, outAppPath: out });
|
||||||
await verifyApp(out, true);
|
await verifyApp(out, true);
|
||||||
},
|
},
|
||||||
VERIFY_APP_TIMEOUT,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import * as path from 'path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { sha } from '../src/sha';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { describe, expect, it } from '@jest/globals';
|
|
||||||
|
import { sha } from '../src/sha.js';
|
||||||
|
|
||||||
describe('sha', () => {
|
describe('sha', () => {
|
||||||
it('should correctly hash a file', async () => {
|
it('should correctly hash a file', async () => {
|
||||||
expect(await sha(path.resolve(__dirname, 'fixtures', 'tohash'))).toEqual(
|
expect(await sha(path.resolve(import.meta.dirname, 'fixtures', 'tohash'))).toEqual(
|
||||||
'12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251',
|
'12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
79
test/util.ts
79
test/util.ts
@@ -1,29 +1,31 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { createPackageWithOptions, getRawHeader } from '@electron/asar';
|
||||||
import { downloadArtifact } from '@electron/get';
|
import { downloadArtifact } from '@electron/get';
|
||||||
import { spawn } from '@malept/cross-spawn-promise';
|
import { spawn } from '@malept/cross-spawn-promise';
|
||||||
import * as zip from 'cross-zip';
|
import * as zip from 'cross-zip';
|
||||||
import * as fs from 'fs-extra';
|
|
||||||
import * as path from 'path';
|
|
||||||
import plist from 'plist';
|
import plist from 'plist';
|
||||||
import * as fileUtils from '../dist/cjs/file-utils';
|
|
||||||
import { createPackageWithOptions, getRawHeader } from '@electron/asar';
|
|
||||||
|
|
||||||
declare const expect: typeof import('@jest/globals').expect;
|
import * as fileUtils from '../dist/file-utils.js';
|
||||||
|
|
||||||
// We do a LOT of verifications in `verifyApp` 😅
|
// We do a LOT of verifications in `verifyApp` 😅
|
||||||
// exec universal binary -> verify ALL asars -> verify ALL app dirs -> verify ALL asar integrity entries
|
// exec universal binary -> verify ALL asars -> verify ALL app dirs -> verify ALL asar integrity entries
|
||||||
// plus some tests create fixtures at runtime
|
// plus some tests create fixtures at runtime
|
||||||
export const VERIFY_APP_TIMEOUT = 80 * 1000;
|
export const VERIFY_APP_TIMEOUT = 80 * 1000;
|
||||||
|
|
||||||
export const fixtureDir = path.resolve(__dirname, 'fixtures');
|
export const fixtureDir = path.resolve(import.meta.dirname, 'fixtures');
|
||||||
export const asarsDir = path.resolve(fixtureDir, 'asars');
|
export const asarsDir = path.resolve(fixtureDir, 'asars');
|
||||||
export const appsDir = path.resolve(fixtureDir, 'apps');
|
export const appsDir = path.resolve(fixtureDir, 'apps');
|
||||||
export const appsOutPath = path.resolve(appsDir, 'out');
|
export const appsOutPath = path.resolve(appsDir, 'out');
|
||||||
|
|
||||||
export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho = false) => {
|
export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho = false) => {
|
||||||
|
const { expect } = await import('vitest');
|
||||||
|
|
||||||
await ensureUniversal(appPath);
|
await ensureUniversal(appPath);
|
||||||
|
|
||||||
const resourcesDir = path.resolve(appPath, 'Contents', 'Resources');
|
const resourcesDir = path.resolve(appPath, 'Contents', 'Resources');
|
||||||
const resourcesDirContents = await fs.readdir(resourcesDir);
|
const resourcesDirContents = await fs.promises.readdir(resourcesDir);
|
||||||
|
|
||||||
// sort for consistent result
|
// sort for consistent result
|
||||||
const asars = resourcesDirContents.filter((p) => p.endsWith('.asar')).sort();
|
const asars = resourcesDirContents.filter((p) => p.endsWith('.asar')).sort();
|
||||||
@@ -31,7 +33,12 @@ export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho =
|
|||||||
// verify header
|
// verify header
|
||||||
const asarFs = getRawHeader(path.resolve(resourcesDir, asar));
|
const asarFs = getRawHeader(path.resolve(resourcesDir, asar));
|
||||||
expect(
|
expect(
|
||||||
removeUnstableProperties(asarFs.header, containsRuntimeGeneratedMacho ? ['hello-world'] : []),
|
removeUnstableProperties(
|
||||||
|
asarFs.header,
|
||||||
|
containsRuntimeGeneratedMacho
|
||||||
|
? ['hello-world', 'hello-world-arm64', 'hello-world-x64']
|
||||||
|
: [],
|
||||||
|
),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +58,7 @@ export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho =
|
|||||||
await verifyFileTree(path.resolve(resourcesDir, dir));
|
await verifyFileTree(path.resolve(resourcesDir, dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
const allFiles = await fileUtils.getAllAppFiles(appPath);
|
const allFiles = await fileUtils.getAllAppFiles(appPath, {});
|
||||||
const infoPlists = allFiles
|
const infoPlists = allFiles
|
||||||
.filter(
|
.filter(
|
||||||
(appFile) =>
|
(appFile) =>
|
||||||
@@ -79,13 +86,15 @@ export const verifyApp = async (appPath: string, containsRuntimeGeneratedMacho =
|
|||||||
|
|
||||||
const extractAsarIntegrity = async (infoPlist: string) => {
|
const extractAsarIntegrity = async (infoPlist: string) => {
|
||||||
const { ElectronAsarIntegrity: integrity, ...otherData } = plist.parse(
|
const { ElectronAsarIntegrity: integrity, ...otherData } = plist.parse(
|
||||||
await fs.readFile(infoPlist, 'utf-8'),
|
await fs.promises.readFile(infoPlist, 'utf-8'),
|
||||||
) as any;
|
) as any;
|
||||||
return integrity;
|
return integrity;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const verifyFileTree = async (dirPath: string) => {
|
export const verifyFileTree = async (dirPath: string) => {
|
||||||
const dirFiles = await fileUtils.getAllAppFiles(dirPath);
|
const { expect } = await import('vitest');
|
||||||
|
|
||||||
|
const dirFiles = await fileUtils.getAllAppFiles(dirPath, {});
|
||||||
const files = dirFiles.map((file) => {
|
const files = dirFiles.map((file) => {
|
||||||
const it = path.join(dirPath, file.relativePath);
|
const it = path.join(dirPath, file.relativePath);
|
||||||
const name = toSystemIndependentPath(file.relativePath);
|
const name = toSystemIndependentPath(file.relativePath);
|
||||||
@@ -98,6 +107,8 @@ export const verifyFileTree = async (dirPath: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ensureUniversal = async (app: string) => {
|
export const ensureUniversal = async (app: string) => {
|
||||||
|
const { expect } = await import('vitest');
|
||||||
|
|
||||||
const exe = path.resolve(app, 'Contents', 'MacOS', 'Electron');
|
const exe = path.resolve(app, 'Contents', 'MacOS', 'Electron');
|
||||||
const result = await spawn(exe);
|
const result = await spawn(exe);
|
||||||
expect(result).toContain('arm64');
|
expect(result).toContain('arm64');
|
||||||
@@ -162,15 +173,18 @@ export const createStagingAppDir = async (
|
|||||||
) => {
|
) => {
|
||||||
const outDir = (testName || 'app') + Math.floor(Math.random() * 100); // tests run in parallel, randomize dir suffix to prevent naming collisions
|
const outDir = (testName || 'app') + Math.floor(Math.random() * 100); // tests run in parallel, randomize dir suffix to prevent naming collisions
|
||||||
const testPath = path.join(appsDir, outDir);
|
const testPath = path.join(appsDir, outDir);
|
||||||
await fs.remove(testPath);
|
await fs.promises.rm(testPath, { recursive: true, force: true });
|
||||||
|
|
||||||
await fs.copy(path.join(asarsDir, 'app'), testPath);
|
await fs.promises.cp(path.join(asarsDir, 'app'), testPath, {
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
});
|
||||||
|
|
||||||
const privateVarPath = path.join(testPath, 'private', 'var');
|
const privateVarPath = path.join(testPath, 'private', 'var');
|
||||||
const varPath = path.join(testPath, 'var');
|
const varPath = path.join(testPath, 'var');
|
||||||
|
|
||||||
await fs.mkdir(privateVarPath, { recursive: true });
|
await fs.promises.mkdir(privateVarPath, { recursive: true });
|
||||||
await fs.symlink(path.relative(testPath, privateVarPath), varPath);
|
await fs.promises.symlink(path.relative(testPath, privateVarPath), varPath);
|
||||||
|
|
||||||
const files = {
|
const files = {
|
||||||
'file.txt': 'hello world',
|
'file.txt': 'hello world',
|
||||||
@@ -178,11 +192,11 @@ export const createStagingAppDir = async (
|
|||||||
};
|
};
|
||||||
for await (const [filename, fileData] of Object.entries(files)) {
|
for await (const [filename, fileData] of Object.entries(files)) {
|
||||||
const originFilePath = path.join(varPath, filename);
|
const originFilePath = path.join(varPath, filename);
|
||||||
await fs.writeFile(originFilePath, fileData);
|
await fs.promises.writeFile(originFilePath, fileData);
|
||||||
}
|
}
|
||||||
const appPath = path.join(varPath, 'app');
|
const appPath = path.join(varPath, 'app');
|
||||||
await fs.mkdirp(appPath);
|
await fs.promises.mkdir(appPath, { recursive: true });
|
||||||
await fs.symlink('../file.txt', path.join(appPath, 'file.txt'));
|
await fs.promises.symlink('../file.txt', path.join(appPath, 'file.txt'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
testPath,
|
testPath,
|
||||||
@@ -204,8 +218,11 @@ export const templateApp = async (
|
|||||||
});
|
});
|
||||||
const appPath = path.resolve(appsDir, name);
|
const appPath = path.resolve(appsDir, name);
|
||||||
zip.unzipSync(electronZip, appsDir);
|
zip.unzipSync(electronZip, appsDir);
|
||||||
await fs.rename(path.resolve(appsDir, 'Electron.app'), appPath);
|
await fs.promises.rename(path.resolve(appsDir, 'Electron.app'), appPath);
|
||||||
await fs.remove(path.resolve(appPath, 'Contents', 'Resources', 'default_app.asar'));
|
await fs.promises.rm(path.resolve(appPath, 'Contents', 'Resources', 'default_app.asar'), {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
await modify(appPath);
|
await modify(appPath);
|
||||||
|
|
||||||
return appPath;
|
return appPath;
|
||||||
@@ -217,6 +234,7 @@ export const generateNativeApp = async (options: {
|
|||||||
createAsar: boolean;
|
createAsar: boolean;
|
||||||
nativeModuleArch?: string;
|
nativeModuleArch?: string;
|
||||||
additionalFiles?: Record<string, string>;
|
additionalFiles?: Record<string, string>;
|
||||||
|
singleArchBindings?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
appNameWithExtension,
|
appNameWithExtension,
|
||||||
@@ -224,27 +242,34 @@ export const generateNativeApp = async (options: {
|
|||||||
createAsar,
|
createAsar,
|
||||||
nativeModuleArch = arch,
|
nativeModuleArch = arch,
|
||||||
additionalFiles,
|
additionalFiles,
|
||||||
|
singleArchBindings,
|
||||||
} = options;
|
} = options;
|
||||||
const appPath = await templateApp(appNameWithExtension, arch, async (appPath) => {
|
const appPath = await templateApp(appNameWithExtension, arch, async (appPath) => {
|
||||||
const resources = path.join(appPath, 'Contents', 'Resources');
|
const resources = path.join(appPath, 'Contents', 'Resources');
|
||||||
const resourcesApp = path.resolve(resources, 'app');
|
const resourcesApp = path.resolve(resources, 'app');
|
||||||
if (!fs.existsSync(resourcesApp)) {
|
if (!fs.existsSync(resourcesApp)) {
|
||||||
await fs.mkdir(resourcesApp);
|
await fs.promises.mkdir(resourcesApp, { recursive: true });
|
||||||
}
|
}
|
||||||
const { testPath } = await createStagingAppDir(
|
const { testPath } = await createStagingAppDir(
|
||||||
path.basename(appNameWithExtension, '.app'),
|
path.basename(appNameWithExtension, '.app'),
|
||||||
additionalFiles,
|
additionalFiles,
|
||||||
);
|
);
|
||||||
await fs.copy(
|
let targetBinding: string;
|
||||||
path.join(appsDir, `hello-world-${nativeModuleArch}`),
|
if (singleArchBindings) {
|
||||||
path.join(testPath, 'hello-world'),
|
targetBinding = path.join(testPath, `hello-world-${nativeModuleArch}`);
|
||||||
);
|
} else {
|
||||||
|
targetBinding = path.join(testPath, 'hello-world');
|
||||||
|
}
|
||||||
|
await fs.promises.cp(path.join(appsDir, `hello-world-${nativeModuleArch}`), targetBinding, {
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
});
|
||||||
if (createAsar) {
|
if (createAsar) {
|
||||||
await createPackageWithOptions(testPath, path.resolve(resources, 'app.asar'), {
|
await createPackageWithOptions(testPath, path.resolve(resources, 'app.asar'), {
|
||||||
unpack: '**/hello-world',
|
unpack: '**/hello-world*',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await fs.copy(testPath, resourcesApp);
|
await fs.promises.cp(testPath, resourcesApp, { recursive: true, verbatimSymlinks: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return appPath;
|
return appPath;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,20 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es2017",
|
||||||
|
"lib": [
|
||||||
|
"es2017"
|
||||||
|
],
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
"outDir": "entry-asar",
|
"outDir": "entry-asar",
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
],
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"declaration": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"entry-asar"
|
"entry-asar"
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "esnext",
|
|
||||||
"outDir": "dist/esm"
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "esnext",
|
|
||||||
"outDir": "dist/esm",
|
|
||||||
"types": [
|
|
||||||
"jest"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,15 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es2017",
|
|
||||||
"lib": [
|
|
||||||
"es2017"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"outDir": "dist",
|
||||||
"outDir": "dist/cjs",
|
|
||||||
"types": [
|
"types": [
|
||||||
"node",
|
"node",
|
||||||
],
|
],
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"declaration": true
|
"declaration": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src",
|
"src"
|
||||||
"entry-asar"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
7
vitest.config.ts
Normal file
7
vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globalSetup: './test/globalSetup.ts',
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user