diff --git a/.gitignore b/.gitignore index 8dead59..6a978de 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ entry-asar/*.js* entry-asar/*.ts *.app test/fixtures/apps -coverage \ No newline at end of file +coverage +docs +.vscode \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json index 632a3f5..f9dd2e1 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -3,5 +3,13 @@ "tabWidth": 2, "singleQuote": true, "printWidth": 100, - "parser": "typescript" -} \ No newline at end of file + "parser": "typescript", + "overrides": [ + { + "files": ["*.json", "*.jsonc", "*.json5"], + "options": { + "parser": "json" + } + } + ] +} diff --git a/README.md b/README.md index ee40e57..7afd3ef 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,40 @@ [![CircleCI](https://circleci.com/gh/electron/universal/tree/main.svg?style=shield)](https://circleci.com/gh/electron/universal) [![NPM package](https://img.shields.io/npm/v/@electron/universal)](https://npm.im/@electron/universal) - ## Usage +This package takes an x64 app and an arm64 app and glues them together into a +[Universal macOS binary](https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary). + +Note that parameters need to be **absolute** paths. + +```typescript +import { makeUniversalApp } from '@electron/universal'; + +await makeUniversalApp({ + x64AppPath: 'path/to/App_x64.app', + arm64AppPath: 'path/to/App_arm64.app', + outAppPath: 'path/to/App_universal.app', +}); +``` + +## Advanced configuration + +The basic usage patterns will work for most apps out of the box. Additional configuration +options are available for advanced usecases. + +### Merging ASAR archives to reduce app size + +**Added in [v1.2.0](https://github.com/electron/universal/commit/38ab1c3559e25382957d608e49e624dc72a4409c)** + +If you are using ASAR archives to store your Electron app's JavaScript code, you can use the +`mergeASARs` option to merge your x64 and arm64 ASAR files to reduce the bundle size of +the output Universal app. + +If some files are present in only the x64 app but not the arm64 version (or vice-versa), +you can exclude them from the merging process by specifying a `minimatch` pattern +in `singleArchFiles`. + ```typescript import { makeUniversalApp } from '@electron/universal'; @@ -15,6 +46,55 @@ await makeUniversalApp({ x64AppPath: 'path/to/App_x64.app', arm64AppPath: 'path/to/App_arm64.app', outAppPath: 'path/to/App_universal.app', + mergeASARs: true, + singleArchFiles: 'node_modules/some-native-module/lib/binding/Release/**', // if you have files in your asar that are unique to x64 or arm64 apps +}); +``` + +If `@electron/universal` detects an architecture-unique file that isn't covered by the +`singleArchFiles` rule, an error will be thrown. + +### Skip lipo for certain binaries in your Universal app + +**Added in [1.3.0](https://github.com/electron/universal/commit/01dfb8a9636965fe154192b07934670dd42509f3)** + +If your Electron app contains binary resources that are already merged with the +`lipo` tool, providing a [`minimatch`] pattern to matching files in the `x64ArchFiles` +parameter will prevent `@electron/universal` from attempting to merge them a second time. + +```typescript +import { makeUniversalApp } from '@electron/universal'; + +await makeUniversalApp({ + x64AppPath: 'path/to/App_x64.app', + arm64AppPath: 'path/to/App_arm64.app', + outAppPath: 'path/to/App_universal.app', + mergeASARs: true, + x64ArchFiles: '*/electron-helper', // `electron-helper` is a binary merged using `lipo` +}); +``` + +If `@electron/universal` detects a lipo'd file that isn't covered by the `x64ArchFiles` rule, +an error will be thrown. + +### Including already codesigned app bundles into your Universal app + +**Added in [v1.4.0](https://github.com/electron/universal/commit/b02ce7697fd2a3c2c79e1f6ab6bf7052125865cc)** + +By default, the merging process will generate an `ElectronAsarIntegrity` key for +any `Info.plist` files in your Electron app. + +If your Electron app bundles another `.app` that is already signed, you need to use +the `infoPlistsToIgnore` option to avoid modifying that app's plist. + +```typescript +import { makeUniversalApp } from '@electron/universal'; + +await makeUniversalApp({ + x64AppPath: 'path/to/App_x64.app', + arm64AppPath: 'path/to/App_arm64.app', + outAppPath: 'path/to/App_universal.app', + infoPlistsToIgnore: 'my-internal.app/Contents/Info.plist' }); ``` @@ -22,16 +102,24 @@ await makeUniversalApp({ #### The app is twice as big now, why? -Well, a Universal app isn't anything magical. It is literally the x64 app and -the arm64 app glued together into a single application. It's twice as big -because it contains two apps in one. +A Universal app is just the x64 app and the arm64 app glued together into a single application. +It's twice as big because it contains two apps in one. + +Merging your ASAR bundles can yield significant app size reductions depending on how large +your `app.asar` file is. #### What about native modules? -The way `@electron/universal` works today means you don't need to worry about -things like building universal versions of your native modules. As long as -your x64 and arm64 apps work in isolation the Universal app will work as well. +Out of the box, you don't need to worry about building universal versions of your +native modules. As long as your x64 and arm64 apps work in isolation, the Universal +app will work as well. + +Note that if you are using `mergeASARs`, you may need to add architecture-specific +binary resources to the `singleArchFiles` pattern. +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) +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 diff --git a/package.json b/package.json index 03b8ef2..e62b7ec 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "author": "Samuel Attard", "scripts": { "build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && tsc -p tsconfig.entry-asar.json", + "build:docs": "npx typedoc", "lint": "prettier --check \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"", "prettier:write": "prettier --write \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"", "prepublishOnly": "npm run build", @@ -47,6 +48,7 @@ "lint-staged": "^15.0.2", "prettier": "^3.0.3", "ts-jest": "^29.1.1", + "typedoc": "~0.25.13", "typescript": "^5.2.2" }, "dependencies": { diff --git a/src/index.ts b/src/index.ts index a0bd4f7..c7492a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,39 +12,62 @@ import { AsarMode, detectAsarMode, generateAsarIntegrity, mergeASARs } from './a import { sha } from './sha'; import { d } from './debug'; +/** + * Options to pass into the {@link makeUniversalApp} function. + * + * Requires absolute paths for input x64 and arm64 apps and an absolute path to the + * output universal app. + */ export type MakeUniversalOpts = { /** - * Absolute file system path to the x64 version of your application. E.g. /Foo/bar/MyApp_x64.app + * Absolute file system path to the x64 version of your application (e.g. `/Foo/bar/MyApp_x64.app`). */ x64AppPath: string; /** - * Absolute file system path to the arm64 version of your application. E.g. /Foo/bar/MyApp_arm64.app + * Absolute file system path to the arm64 version of your application (e.g. `/Foo/bar/MyApp_arm64.app`). */ arm64AppPath: string; /** - * Absolute file system path you want the universal app to be written to. E.g. /Foo/var/MyApp_universal.app + * Absolute file system path you want the universal app to be written to (e.g. `/Foo/var/MyApp_universal.app`). * - * If this file exists it will be overwritten ONLY if "force" is set to true + * If this file exists on disk already, it will be overwritten ONLY if {@link MakeUniversalOpts.force} is set to `true`. */ outAppPath: string; /** - * Forcefully overwrite any existing files that are in the way of generating the universal application + * Forcefully overwrite any existing files that are in the way of generating the universal application. + * + * @defaultValue `false` */ force?: boolean; /** * Merge x64 and arm64 ASARs into one. + * + * @defaultValue `false` */ mergeASARs?: boolean; /** - * Minimatch pattern of paths that are allowed to be present in one of the ASAR files, but not in the other. + * If {@link MakeUniversalOpts.mergeASARs} is enabled, this property provides a + * {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch} + * pattern of paths that are allowed to be present in one of the ASAR files, but not in the other. + * */ singleArchFiles?: string; /** - * Minimatch pattern of binaries that are expected to be the same x64 binary in both of the ASAR files. + * A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch} + * pattern of binaries that are expected to be the same x64 binary in both + * + * Use this if your application contains binaries that have already been merged into a universal file + * using the `lipo` tool. + * + * @see Apple's {@link https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary | Building a universal macOS binary} documentation + * */ x64ArchFiles?: string; /** - * Minimatch pattern of paths that should not receive an injected ElectronAsarIntegrity value + * A {@link https://github.com/isaacs/minimatch?tab=readme-ov-file#features | minimatch} pattern of `Info.plist` + * paths that should not receive an injected `ElectronAsarIntegrity` value. + * + * Use this if your application contains another bundle that's already signed. */ infoPlistsToIgnore?: string; }; diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..1761a7f --- /dev/null +++ b/typedoc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts"], + "excludeInternal": true, + "sort": ["source-order"] +} diff --git a/yarn.lock b/yarn.lock index ff8ae96..d911b9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -798,6 +798,11 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== +ansi-sequence-parser@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz#e0aa1cdcbc8f8bb0b5bca625aac41f5f056973cf" + integrity sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -2115,6 +2120,11 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -2228,6 +2238,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lunr@^2.3.9: + version "2.3.9" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== + make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -2247,6 +2262,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +marked@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== + matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -2627,6 +2647,16 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shiki@^0.14.7: + version "0.14.7" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.7.tgz#c3c9e1853e9737845f1d2ef81b31bcfb07056d4e" + integrity sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg== + dependencies: + ansi-sequence-parser "^1.1.0" + jsonc-parser "^3.2.0" + vscode-oniguruma "^1.7.0" + vscode-textmate "^8.0.0" + signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -2843,6 +2873,16 @@ type-fest@^1.0.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +typedoc@~0.25.13: + version "0.25.13" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.25.13.tgz#9a98819e3b2d155a6d78589b46fa4c03768f0922" + integrity sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ== + dependencies: + lunr "^2.3.9" + marked "^4.3.0" + minimatch "^9.0.3" + shiki "^0.14.7" + typescript@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" @@ -2880,6 +2920,16 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +vscode-oniguruma@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== + +vscode-textmate@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-8.0.0.tgz#2c7a3b1163ef0441097e0b5d6389cd5504b59e5d" + integrity sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg== + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"